diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..04d0759 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,27 @@ +name: test + +on: [push, pull_request] + +jobs: + unit_tests: + strategy: + matrix: + go-version: [1.18.x, 1.17.x, 1.16.x] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Build + run: go build + + - name: Test + run: | + go test -v ./... + go test -v ./... -race diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 94567db..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: go -go: - - 1.12.x - - 1.13.x - - 1.14.x -os: - - linux - - osx - - windows -sudo: false -script: - - go test ./... - - cd tests && go test ./... diff --git a/README.md b/README.md index 6270cd3..225bb4c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Travis](https://travis-ci.org/vechain/go-ecvrf.svg?branch=master)](https://travis-ci.org/vechain/go-ecvrf) [![License](https://img.shields.io/github/license/vechain/go-ecvrf)](https://github.com/vechain/go-ecvrf/blob/master/LICENSE) -Zero-dependency Golang implementation of Elliptic Curve Verifiable Random Function (VRF) follows [draft-irtf-cfrg-vrf-06](https://tools.ietf.org/id/draft-irtf-cfrg-vrf-06.html) and [RFC 6979](https://tools.ietf.org/html/rfc6979). +Golang implementation of Elliptic Curve Verifiable Random Function (VRF) follows [draft-irtf-cfrg-vrf-06](https://tools.ietf.org/id/draft-irtf-cfrg-vrf-06.html) and [RFC 6979](https://tools.ietf.org/html/rfc6979). # What's VRF @@ -35,7 +35,7 @@ Using SECP256K1_SHA256_TAI cipher suite: // `beta`: the VRF hash output // `pi`: the VRF proof - beta, pi, err := ecvrf.NewSecp256k1Sha256Tai().Prove(sk, []byte(alpha)) + beta, pi, err := ecvrf.Secp256k1Sha256Tai.Prove(sk, []byte(alpha)) if err != nil { // something wrong. // most likely sk is not properly loaded. @@ -55,7 +55,7 @@ Using SECP256K1_SHA256_TAI cipher suite: alpha := "Hello VeChain" // `pi` is the VRF proof - beta, err := ecvrf.NewSecp256k1Sha256Tai().Verify(pk, []byte(alpha), pi) + beta, err := ecvrf.Secp256k1Sha256Tai.Verify(pk, []byte(alpha), pi) if err != nil { // invalid proof return @@ -75,46 +75,28 @@ It's easy to extends this library to use different Weierstrass curves and Hash a ```golang // the following codes build a new P256_SHA256_TAI VRF object. vrf := ecvrf.New(&ecvrf.Config{ + Curve: elliptic.P256(), SuiteString: 0x01, Cofactor: 0x01, NewHasher: sha256.New, - Y2: func(c elliptic.Curve, x *big.Int) *big.Int { - // y² = x³ - 3x + b - x3 := new(big.Int).Mul(x, x) - x3.Mul(x3, x) - - threeX := new(big.Int).Lsh(x, 1) - threeX.Add(threeX, x) - - x3.Sub(x3, threeX) - x3.Add(x3, c.Params().B) - x3.Mod(x3, c.Params().P) - return x3 - }, - Sqrt: ecvrf.DefaultSqrt, + Decompress: elliptic.UnmarshalCompressed, }) ``` # Benchmark -On quad-core i5 13" macbook pro 2018 - -``` -goos: darwin +```bash +$ go test -benchmem -run=^$ -bench ^BenchmarkVRF$ github.com/vechain/go-ecvrf -benchtime=5s +goos: linux goarch: amd64 -pkg: github.com/vechain/go-ecvrf/tests -BenchmarkVRF -BenchmarkVRF/secp256k1sha256tai-proving -BenchmarkVRF/secp256k1sha256tai-proving-8 2198 598180 ns/op 16680 B/op 604 allocs/op -BenchmarkVRF/secp256k1sha256tai-verifying -BenchmarkVRF/secp256k1sha256tai-verifying-8 1587 739716 ns/op 14214 B/op 406 allocs/op -BenchmarkVRF/p256sha256tai-proving -BenchmarkVRF/p256sha256tai-proving-8 4887 263843 ns/op 9509 B/op 243 allocs/op -BenchmarkVRF/p256sha256tai-verifying -BenchmarkVRF/p256sha256tai-verifying-8 3172 507240 ns/op 17636 B/op 428 allocs/op +pkg: github.com/vechain/go-ecvrf +cpu: Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz +BenchmarkVRF/secp256k1sha256tai-proving-8 22207 279600 ns/op 4881 B/op 95 allocs/op +BenchmarkVRF/secp256k1sha256tai-verifying-8 15150 399938 ns/op 5009 B/op 114 allocs/op +BenchmarkVRF/p256sha256tai-proving-8 31328 193911 ns/op 9083 B/op 294 allocs/op +BenchmarkVRF/p256sha256tai-verifying-8 19875 300613 ns/op 19472 B/op 515 allocs/op PASS -ok github.com/vechain/go-ecvrf/tests 5.668s -Success: Benchmarks passed. +ok github.com/vechain/go-ecvrf 36.060s ``` # References diff --git a/config.go b/config.go index 6e9eb1a..b1f8ff7 100644 --- a/config.go +++ b/config.go @@ -11,23 +11,14 @@ import ( // Config contains VRF parameters. type Config struct { + // the elliptic curve. + Curve elliptic.Curve // a single nonzero octet specifying the ECVRF ciphersuite. SuiteString byte // number of points on curve divided by group order. Cofactor byte // create cryptographic hash function. NewHasher func() hash.Hash - // function to calculate y^2. - Y2 func(c elliptic.Curve, x *big.Int) *big.Int - // function to calculate square root. - Sqrt func(c elliptic.Curve, s *big.Int) *big.Int -} - -// DefaultSqrt is the default sqrt method. nil is returned if s is not a square. -func DefaultSqrt(c elliptic.Curve, s *big.Int) *big.Int { - var r big.Int - if nil == r.ModSqrt(s, c.Params().P) { - return nil // s is not a square - } - return &r + // decompress the compressed public key into x and y coordinate. + Decompress func(c elliptic.Curve, pk []byte) (x, y *big.Int) } diff --git a/config_test.go b/config_test.go deleted file mode 100644 index 0f2767c..0000000 --- a/config_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2020 vechain.org. -// Licensed under the MIT license. - -package ecvrf - -import ( - "crypto/elliptic" - "math/big" - "testing" -) - -func TestDefaultSqrt(t *testing.T) { - type args struct { - c elliptic.Curve - s string - } - tests := []struct { - name string - args args - want string - }{ - { - "p256", - args{ - elliptic.P256(), - "23641628374218637252523134409825450466172496366265976434932954203032325458800", - }, - "108980937802188484198425629766080801309523465968363373048763527645240613153665", - }, - { - "p256 invalid", - args{ - elliptic.P256(), - "23641628374218637252523134409825450466172496366265976434932954203032325458801", - }, - "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := new(big.Int) - s.UnmarshalText([]byte(tt.args.s)) - - got := DefaultSqrt(tt.args.c, s) - gotStr := "" - if got != nil { - gotStr = got.String() - } - - if gotStr != tt.want { - t.Errorf("DefaultSqrt() = %v, want %v", gotStr, tt.want) - } - }) - } -} diff --git a/core.go b/core.go index 32c1030..9a3dcbd 100644 --- a/core.go +++ b/core.go @@ -4,12 +4,12 @@ package ecvrf import ( - "bytes" "crypto/elliptic" - "crypto/hmac" "errors" "hash" "math/big" + + "github.com/decred/dcrd/dcrec/secp256k1/v4" ) type point struct { @@ -18,110 +18,72 @@ type point struct { type core struct { *Config - curve elliptic.Curve cachedHasher hash.Hash } // Q returns prime order of large prime order subgroup. func (c *core) Q() *big.Int { - return c.curve.Params().N + return c.Curve.Params().N } // N return half of length, in octets, of a field element in F, rounded up to the nearest even integer func (c *core) N() int { - return ((c.curve.Params().P.BitLen()+1)/2 + 7) / 8 + return ((c.Curve.Params().P.BitLen()+1)/2 + 7) / 8 } -func (c *core) getCachedHasher() hash.Hash { - if c.cachedHasher != nil { - return c.cachedHasher +func (c *core) getHasher() hash.Hash { + if c.cachedHasher == nil { + c.cachedHasher = c.NewHasher() + } else { + c.cachedHasher.Reset() } - c.cachedHasher = c.NewHasher() return c.cachedHasher } // Marshal marshals a point into compressed form specified in section 4.3.6 of ANSI X9.62. // It's the alias of `point_to_string` specified in [draft-irtf-cfrg-vrf-06 section 5.5](https://tools.ietf.org/id/draft-irtf-cfrg-vrf-06.html#rfc.section.5.5). func (c *core) Marshal(pt *point) []byte { - byteLen := (c.curve.Params().BitSize + 7) / 8 - out := make([]byte, byteLen+1) - - // compress format, 3 for odd y - out[0] = 2 + byte(pt.Y.Bit(0)) - - bytes := pt.X.Bytes() - - if n := len(bytes); byteLen > n { - copy(out[1+byteLen-n:], bytes) - } else { - copy(out[1:], bytes) - } - return out + return elliptic.MarshalCompressed(c.Curve, pt.X, pt.Y) } // Unmarshal unmarshals a compressed point in the form specified in section 4.3.6 of ANSI X9.62. // It's the alias of `string_to_point` specified in [draft-irtf-cfrg-vrf-06 section 5.5](https://tools.ietf.org/id/draft-irtf-cfrg-vrf-06.html#rfc.section.5.5). // This is borrowed from the project https://github.com/google/keytransparency. -func (c *core) Unmarshal(in []byte) (*point, error) { - byteLen := (c.curve.Params().BitSize + 7) / 8 - if (in[0] &^ 1) != 2 { - return nil, errors.New("unrecognized point encoding") - } - if len(in) != 1+byteLen { - return nil, errors.New("invalid point data length") - } - // Based on Routine 2.2.4 in NIST Mathematical routines paper - p := c.curve.Params().P - x := new(big.Int).SetBytes(in[1 : 1+byteLen]) - y2 := c.Y2(c.curve, x) - - y := c.Sqrt(c.curve, y2) - if y == nil { - return nil, errors.New("invalid point: y^2 is not a squire") - } - - var y2c big.Int - y2c.Mul(y, y).Mod(&y2c, p) - if y2c.Cmp(y2) != 0 { - return nil, errors.New("invalid point: sqrt(y2)^2 != y2") - } - - if y.Bit(0) != uint(in[0]&1) { - y.Sub(p, y) +func (c *core) Unmarshal(in []byte) *point { + if x, y := c.Decompress(c.Curve, in); x != nil && y != nil { + return &point{x, y} } - - // valid point: return it - return &point{x, y}, nil + return nil } func (c *core) ScalarMult(pt *point, k []byte) *point { - x, y := c.curve.ScalarMult(pt.X, pt.Y, k) + x, y := c.Curve.ScalarMult(pt.X, pt.Y, k) return &point{x, y} } func (c *core) ScalarBaseMult(k []byte) *point { - x, y := c.curve.ScalarBaseMult(k) + x, y := c.Curve.ScalarBaseMult(k) return &point{x, y} } func (c *core) Add(pt1, pt2 *point) *point { - x, y := c.curve.Add(pt1.X, pt1.Y, pt2.X, pt2.Y) + x, y := c.Curve.Add(pt1.X, pt1.Y, pt2.X, pt2.Y) return &point{x, y} } func (c *core) Sub(pt1, pt2 *point) *point { // pt1 - pt2 = pt1 + invert(pt2), // where invert(pt2) = (x2, P - y2) - x, y := c.curve.Add( + x, y := c.Curve.Add( pt1.X, pt1.Y, - pt2.X, new(big.Int).Sub(c.curve.Params().P, pt2.Y)) + pt2.X, new(big.Int).Sub(c.Curve.Params().P, pt2.Y)) return &point{x, y} } // HashToCurveTryAndIncrement takes in the VRF input `alpha` and converts it to H, using the try_and_increment algorithm. // See: [draft-irtf-cfrg-vrf-06 section 5.4.1.1](https://tools.ietf.org/id/draft-irtf-cfrg-vrf-06.html#rfc.section.5.4.1.1). -func (c *core) HashToCurveTryAndIncrement(pk *point, alpha []byte) (H *point, err error) { - hasher := c.getCachedHasher() +func (c *core) HashToCurveTryAndIncrement(pk *point, alpha []byte) (*point, error) { + hasher := c.getHasher() hash := make([]byte, 1+hasher.Size()) hash[0] = 2 // compress format @@ -146,7 +108,7 @@ func (c *core) HashToCurveTryAndIncrement(pk *point, alpha []byte) (H *point, er hasher.Sum(hash[1:1]) // H = arbitrary_string_to_point(hash_string) - if H, err = c.Unmarshal(hash); err == nil { + if H := c.Unmarshal(hash); H != nil { if c.Cofactor > 1 { // If H is not "INVALID" and cofactor > 1, set H = cofactor * H H = c.ScalarMult(H, []byte{c.Cofactor}) @@ -159,9 +121,7 @@ func (c *core) HashToCurveTryAndIncrement(pk *point, alpha []byte) (H *point, er // See: [draft-irtf-cfrg-vrf-06 section 5.4.3](https://tools.ietf.org/id/draft-irtf-cfrg-vrf-06.html#rfc.section.5.4.3) func (c *core) HashPoints(points ...*point) *big.Int { - hasher := c.getCachedHasher() - hasher.Reset() - + hasher := c.getHasher() hasher.Write([]byte{c.SuiteString, 0x2}) for _, pt := range points { hasher.Write(c.Marshal(pt)) @@ -170,9 +130,11 @@ func (c *core) HashPoints(points ...*point) *big.Int { } func (c *core) GammaToHash(gamma *point) []byte { - gammaCof := c.ScalarMult(gamma, []byte{c.Cofactor}) - hasher := c.getCachedHasher() - hasher.Reset() + gammaCof := gamma + if c.Cofactor != 1 { + gammaCof = c.ScalarMult(gamma, []byte{c.Cofactor}) + } + hasher := c.getHasher() hasher.Write([]byte{c.SuiteString, 0x03}) hasher.Write(c.Marshal(gammaCof)) return hasher.Sum(nil) @@ -190,7 +152,7 @@ func (c *core) EncodeProof(gamma *point, C, S *big.Int) []byte { // See: [draft-irtf-cfrg-vrf-06 section 5.4.4](https://tools.ietf.org/id/draft-irtf-cfrg-vrf-06.html#rfc.section.5.4.4) func (c *core) DecodeProof(pi []byte) (gamma *point, C, S *big.Int, err error) { var ( - ptlen = (c.curve.Params().BitSize+7)/8 + 1 + ptlen = (c.Curve.Params().BitSize+7)/8 + 1 clen = c.N() slen = (c.Q().BitLen() + 7) / 8 ) @@ -199,7 +161,8 @@ func (c *core) DecodeProof(pi []byte) (gamma *point, C, S *big.Int, err error) { return } - if gamma, err = c.Unmarshal(pi[:ptlen]); err != nil { + if gamma = c.Unmarshal(pi[:ptlen]); gamma == nil { + err = errors.New("invalid point") return } @@ -208,6 +171,27 @@ func (c *core) DecodeProof(pi []byte) (gamma *point, C, S *big.Int, err error) { return } +// rfc6979nonce generates nonce according to [RFC6979](https://tools.ietf.org/html/rfc6979). +func (c *core) rfc6979nonce(sk *big.Int, m []byte) []byte { + var ( + q = c.Q() + qlen = q.BitLen() + rolen = (qlen + 7) / 8 + hasher = c.getHasher() + ) + + // Step A + // Process m through the hash function H, yielding: + // h1 = H(m) + // (h1 is a sequence of hlen bits). + hasher.Write(m) + bx := int2octets(sk, rolen) + bh := bits2octets(hasher.Sum(nil), q, rolen) + + nonce := secp256k1.NonceRFC6979(bx, bh, nil, nil, 0).Bytes() + return nonce[:] +} + // https://tools.ietf.org/html/rfc6979#section-2.3.2 func bits2int(in []byte, qlen int) *big.Int { out := new(big.Int).SetBytes(in) @@ -244,85 +228,3 @@ func bits2octets(in []byte, q *big.Int, rolen int) []byte { } return int2octets(z2, rolen) } - -// rfc6979nonce generates nonce according to [RFC6979](https://tools.ietf.org/html/rfc6979). -func rfc6979nonce( - sk *big.Int, - m []byte, - q *big.Int, - newHasher func() hash.Hash, -) *big.Int { - var ( - qlen = q.BitLen() - rolen = (qlen + 7) / 8 - hasher = newHasher() - ) - - // Step A - // Process m through the hash function H, yielding: - // h1 = H(m) - // (h1 is a sequence of hlen bits). - hasher.Write(m) - h1 := hasher.Sum(nil) - hlen := len(h1) - - bx := int2octets(sk, rolen) - bh := bits2octets(h1, q, rolen) - - // Step B - // Set: - // V = 0x01 0x01 0x01 ... 0x01 - v := bytes.Repeat([]byte{1}, hlen) - - // Step C - // Set: - // K = 0x00 0x00 0x00 ... 0x00 - k := make([]byte, hlen) - - // Step D ~ G - for i := 0; i < 2; i++ { - // Set: - // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1)) - mac := hmac.New(newHasher, k) - mac.Write(v) - mac.Write([]byte{byte(i)}) // internal octet - mac.Write(bx) - mac.Write(bh) - mac.Sum(k[:0]) - - // Set: - // V = HMAC_K(V) - mac = hmac.New(newHasher, k) - mac.Write(v) - mac.Sum(v[:0]) - } - - // Step H - for { - // Step H1 - var t []byte - - // Step H2 - mac := hmac.New(newHasher, k) - for len(t)*8 < qlen { - mac.Write(v) - mac.Sum(v[:0]) - mac.Reset() - - t = append(t, v...) - } - - // Step H3 - secret := bits2int(t, qlen) - if secret.Sign() > 0 && secret.Cmp(q) < 0 { - return secret - } - mac.Write(v) - mac.Write([]byte{0x00}) - mac.Sum(k[:0]) - - mac = hmac.New(newHasher, k) - mac.Write(v) - mac.Sum(v[:0]) - } -} diff --git a/go.mod b/go.mod index ee6eb30..cba6e2b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/vechain/go-ecvrf -go 1.12 +go 1.16 + +require github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 diff --git a/go.sum b/go.sum index e69de29..698c93e 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,3 @@ +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= diff --git a/tests/p256_sha256_tai.json b/p256_sha256_tai.json similarity index 100% rename from tests/p256_sha256_tai.json rename to p256_sha256_tai.json diff --git a/tests/secp256_k1_sha256_tai.json b/secp256_k1_sha256_tai.json similarity index 100% rename from tests/secp256_k1_sha256_tai.json rename to secp256_k1_sha256_tai.json diff --git a/tests/go.mod b/tests/go.mod deleted file mode 100644 index 9f37ca9..0000000 --- a/tests/go.mod +++ /dev/null @@ -1,11 +0,0 @@ -module github.com/vechain/go-ecvrf/tests - -go 1.12 - -require ( - github.com/btcsuite/btcd v0.20.1-beta - github.com/stretchr/testify v1.5.1 - github.com/vechain/go-ecvrf v0.0.0-20200305101714-4252ed3a3b96 -) - -replace github.com/vechain/go-ecvrf => ../ diff --git a/tests/go.sum b/tests/go.sum deleted file mode 100644 index 625c8b0..0000000 --- a/tests/go.sum +++ /dev/null @@ -1,39 +0,0 @@ -github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= -github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= -github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= -github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= -github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/tests/vrf_benchmark_test.go b/tests/vrf_benchmark_test.go deleted file mode 100644 index 1fa1d2b..0000000 --- a/tests/vrf_benchmark_test.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2020 vechain.org. -// Licensed under the MIT license. - -package tests - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "testing" - - "github.com/btcsuite/btcd/btcec" - "github.com/vechain/go-ecvrf" -) - -func BenchmarkVRF(b *testing.B) { - b.Run("secp256k1sha256tai-proving", func(b *testing.B) { - sk, _ := ecdsa.GenerateKey(btcec.S256(), rand.Reader) - alpha := []byte("Hello VeChain") - - for i := 0; i < b.N; i++ { - _, _, err := ecvrf.NewSecp256k1Sha256Tai().Prove(sk, alpha) - if err != nil { - b.Fatal(err) - } - } - }) - b.Run("secp256k1sha256tai-verifying", func(b *testing.B) { - sk, _ := ecdsa.GenerateKey(btcec.S256(), rand.Reader) - alpha := []byte("Hello VeChain") - - _, pi, _ := ecvrf.NewSecp256k1Sha256Tai().Prove(sk, alpha) - for i := 0; i < b.N; i++ { - _, err := ecvrf.NewSecp256k1Sha256Tai().Verify(&sk.PublicKey, alpha, pi) - if err != nil { - b.Fatal(err) - } - } - }) - b.Run("p256sha256tai-proving", func(b *testing.B) { - sk, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - alpha := []byte("Hello VeChain") - - for i := 0; i < b.N; i++ { - _, _, err := ecvrf.NewP256Sha256Tai().Prove(sk, alpha) - if err != nil { - b.Fatal(err) - } - } - }) - b.Run("p256sha256tai-verifying", func(b *testing.B) { - sk, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - alpha := []byte("Hello VeChain") - - _, pi, _ := ecvrf.NewP256Sha256Tai().Prove(sk, alpha) - for i := 0; i < b.N; i++ { - _, err := ecvrf.NewP256Sha256Tai().Verify(&sk.PublicKey, alpha, pi) - if err != nil { - b.Fatal(err) - } - } - }) -} diff --git a/vrf.go b/vrf.go index 66dc3cf..af35fce 100644 --- a/vrf.go +++ b/vrf.go @@ -10,6 +10,8 @@ import ( "crypto/sha256" "errors" "math/big" + + "github.com/decred/dcrd/dcrec/secp256k1/v4" ) // VRF is the interface that wraps VRF methods. @@ -23,65 +25,66 @@ type VRF interface { Verify(pk *ecdsa.PublicKey, alpha, pi []byte) (beta []byte, err error) } -// New creates and initializes a VRF object using customized config. -func New(cfg *Config) VRF { - return &vrf{func(c elliptic.Curve) *core { - return &core{Config: cfg, curve: c} - }} -} - -// NewSecp256k1Sha256Tai creates the VRF object configured with secp256k1/SHA256 and hash_to_curve_try_and_increment algorithm. -func NewSecp256k1Sha256Tai() VRF { - return New(&Config{ +var ( + // Secp256k1Sha256Tai is the pre-configured VRF object with secp256k1/SHA256 and hash_to_curve_try_and_increment algorithm. + Secp256k1Sha256Tai = New(&Config{ + Curve: secp256k1.S256(), SuiteString: 0xfe, Cofactor: 0x01, NewHasher: sha256.New, - Y2: func(c elliptic.Curve, x *big.Int) *big.Int { - // y² = x³ + b - x3 := new(big.Int).Mul(x, x) - x3.Mul(x3, x) - - x3.Add(x3, c.Params().B) - x3.Mod(x3, c.Params().P) - return x3 + Decompress: func(c elliptic.Curve, pk []byte) (x, y *big.Int) { + var fx, fy secp256k1.FieldVal + // Reject unsupported public key formats for the given length. + format := pk[0] + switch format { + case secp256k1.PubKeyFormatCompressedEven, secp256k1.PubKeyFormatCompressedOdd: + default: + return + } + + // Parse the x coordinate while ensuring that it is in the allowed + // range. + if overflow := fx.SetByteSlice(pk[1:33]); overflow { + return + } + + // Attempt to calculate the y coordinate for the given x coordinate such + // that the result pair is a point on the secp256k1 curve and the + // solution with desired oddness is chosen. + wantOddY := format == secp256k1.PubKeyFormatCompressedOdd + if !secp256k1.DecompressY(&fx, wantOddY, &fy) { + return + } + fy.Normalize() + return new(big.Int).SetBytes(fx.Bytes()[:]), new(big.Int).SetBytes(fy.Bytes()[:]) }, - Sqrt: DefaultSqrt, }) -} - -// NewP256Sha256Tai creates the VRF object configured with P256/SHA256 and hash_to_curve_try_and_increment algorithm. -func NewP256Sha256Tai() VRF { - return New(&Config{ + // P256Sha256Tai is the pre-configured VRF object with P256/SHA256 and hash_to_curve_try_and_increment algorithm. + P256Sha256Tai = New(&Config{ + Curve: elliptic.P256(), SuiteString: 0x01, Cofactor: 0x01, NewHasher: sha256.New, - Y2: func(c elliptic.Curve, x *big.Int) *big.Int { - // y² = x³ - 3x + b - x3 := new(big.Int).Mul(x, x) - x3.Mul(x3, x) - - threeX := new(big.Int).Lsh(x, 1) - threeX.Add(threeX, x) - - x3.Sub(x3, threeX) - x3.Add(x3, c.Params().B) - x3.Mod(x3, c.Params().P) - return x3 - }, - Sqrt: DefaultSqrt, + Decompress: elliptic.UnmarshalCompressed, }) +) + +// New creates and initializes a VRF object using customized config. +func New(cfg *Config) VRF { + return &vrf{cfg: *cfg} } type vrf struct { - newCore func(c elliptic.Curve) *core + cfg Config } // Prove constructs VRF proof following [draft-irtf-cfrg-vrf-06 section 5.1](https://tools.ietf.org/id/draft-irtf-cfrg-vrf-06.html#rfc.section.5.1). func (v *vrf) Prove(sk *ecdsa.PrivateKey, alpha []byte) (beta, pi []byte, err error) { var ( - core = v.newCore(sk.Curve) + core = core{Config: &v.cfg} q = core.Q() ) + // step 1 is done by the caller. // step 2: H = ECVRF_hash_to_curve(suite_string, Y, alpha_string) @@ -99,8 +102,8 @@ func (v *vrf) Prove(sk *ecdsa.PrivateKey, alpha []byte) (beta, pi []byte, err er // step 5: k = ECVRF_nonce_generation(SK, h_string) // it follows RFC6979 - k := rfc6979nonce(sk.D, hbytes, core.Q(), core.NewHasher) - kbytes := k.Bytes() + kbytes := core.rfc6979nonce(sk.D, hbytes) + k := new(big.Int).SetBytes(kbytes) // step 6: c = ECVRF_hash_points(H, Gamma, k*B, k*H) kB := core.ScalarBaseMult(kbytes) @@ -127,8 +130,7 @@ func (v *vrf) Prove(sk *ecdsa.PrivateKey, alpha []byte) (beta, pi []byte, err er // Verify checks the correctness of proof following [draft-irtf-cfrg-vrf-06 section 5.3](https://tools.ietf.org/id/draft-irtf-cfrg-vrf-06.html#rfc.section.5.3). func (v *vrf) Verify(pk *ecdsa.PublicKey, alpha, pi []byte) (beta []byte, err error) { - core := v.newCore(pk.Curve) - + core := core{Config: &v.cfg} // step 1: D = ECVRF_decode_proof(pi_string) gamma, c, s, err := core.DecodeProof(pi) diff --git a/tests/vrf_test.go b/vrf_test.go similarity index 79% rename from tests/vrf_test.go rename to vrf_test.go index d9b95dd..0075d2c 100644 --- a/tests/vrf_test.go +++ b/vrf_test.go @@ -1,7 +1,7 @@ -// Copyright (c) 2020 vechain.org. +// Copyright (c) 2022 vechain.org. // Licensed under the MIT license. -package tests +package ecvrf import ( "bytes" @@ -17,8 +17,7 @@ import ( "testing" "testing/quick" - "github.com/btcsuite/btcd/btcec" - "github.com/vechain/go-ecvrf" + "github.com/decred/dcrd/dcrec/secp256k1/v4" ) // Case Testing cases structure. @@ -67,7 +66,7 @@ func Test_Secp256K1Sha256Tai_vrf_Prove(t *testing.T) { tests := []Test{} for _, c := range cases { skBytes, _ := hex.DecodeString(c.Sk) - sk, _ := btcec.PrivKeyFromBytes(btcec.S256(), skBytes) + sk := secp256k1.PrivKeyFromBytes(skBytes) alpha, _ := hex.DecodeString(c.Alpha) wantBeta, _ := hex.DecodeString(c.Beta) @@ -83,7 +82,7 @@ func Test_Secp256K1Sha256Tai_vrf_Prove(t *testing.T) { }) } - vrf := ecvrf.NewSecp256k1Sha256Tai() + vrf := Secp256k1Sha256Tai for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -119,7 +118,7 @@ func Test_Secp256K1Sha256Tai_vrf_Verify(t *testing.T) { tests := []Test{} for _, c := range cases { skBytes, _ := hex.DecodeString(c.Sk) - sk, _ := btcec.PrivKeyFromBytes(btcec.S256(), skBytes) + sk := secp256k1.PrivKeyFromBytes(skBytes) pk := sk.PubKey().ToECDSA() @@ -139,7 +138,7 @@ func Test_Secp256K1Sha256Tai_vrf_Verify(t *testing.T) { }) } - vrf := ecvrf.NewSecp256k1Sha256Tai() + vrf := Secp256k1Sha256Tai for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -168,7 +167,7 @@ func Test_Secp256K1Sha256Tai_vrf_Verify_bad_message(t *testing.T) { // sk skBytes, _ := hex.DecodeString("c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721") - sk, _ := btcec.PrivKeyFromBytes(btcec.S256(), skBytes) + sk := secp256k1.PrivKeyFromBytes(skBytes) // pk pk := sk.PubKey().ToECDSA() @@ -192,7 +191,7 @@ func Test_Secp256K1Sha256Tai_vrf_Verify_bad_message(t *testing.T) { true, } - vrf := ecvrf.NewSecp256k1Sha256Tai() + vrf := Secp256k1Sha256Tai t.Run(tt.name, func(t *testing.T) { v := vrf @@ -245,7 +244,7 @@ func Test_P256Sha256Tai_vrf_Prove(t *testing.T) { }) } - vrf := ecvrf.NewP256Sha256Tai() + vrf := P256Sha256Tai for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -304,7 +303,7 @@ func Test_P256Sha256Tai_vrf_Verify(t *testing.T) { }) } - vrf := ecvrf.NewP256Sha256Tai() + vrf := P256Sha256Tai for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -328,13 +327,13 @@ type secp256k1gen struct { func (secp256k1gen) Generate(rand *rand.Rand, size int) reflect.Value { for { - sk, err := ecdsa.GenerateKey(btcec.S256(), rand) + sk, err := secp256k1.GeneratePrivateKey() if err != nil { continue } alpha := make([]byte, rand.Intn(256)) rand.Read(alpha) - return reflect.ValueOf(secp256k1gen{sk, alpha}) + return reflect.ValueOf(secp256k1gen{sk.ToECDSA(), alpha}) } } @@ -358,7 +357,7 @@ func (p256gen) Generate(rand *rand.Rand, size int) reflect.Value { func TestRandSkAndAlpha(t *testing.T) { t.Run("secp256k1", func(t *testing.T) { if err := quick.Check(func(gen secp256k1gen) bool { - vrf := ecvrf.NewSecp256k1Sha256Tai() + vrf := Secp256k1Sha256Tai beta1, pi, err := vrf.Prove(gen.sk, gen.alpha) if err != nil { return false @@ -367,7 +366,7 @@ func TestRandSkAndAlpha(t *testing.T) { if err != nil { return false } - return bytes.Compare(beta1, beta2) == 0 + return bytes.Equal(beta1, beta2) }, nil); err != nil { t.Fatal(err) } @@ -375,7 +374,7 @@ func TestRandSkAndAlpha(t *testing.T) { t.Run("p256", func(t *testing.T) { if err := quick.Check(func(gen p256gen) bool { - vrf := ecvrf.NewP256Sha256Tai() + vrf := P256Sha256Tai beta1, pi, err := vrf.Prove(gen.sk, gen.alpha) if err != nil { return false @@ -384,9 +383,63 @@ func TestRandSkAndAlpha(t *testing.T) { if err != nil { return false } - return bytes.Compare(beta1, beta2) == 0 + return bytes.Equal(beta1, beta2) }, nil); err != nil { t.Fatal(err) } }) } + +func BenchmarkVRF(b *testing.B) { + b.Run("secp256k1sha256tai-proving", func(b *testing.B) { + sk, _ := secp256k1.GeneratePrivateKey() + esk := sk.ToECDSA() + alpha := []byte("Hello VeChain") + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _, err := Secp256k1Sha256Tai.Prove(esk, alpha) + if err != nil { + b.Fatal(err) + } + } + }) + b.Run("secp256k1sha256tai-verifying", func(b *testing.B) { + sk, _ := secp256k1.GeneratePrivateKey() + esk := sk.ToECDSA() + alpha := []byte("Hello VeChain") + + _, pi, _ := Secp256k1Sha256Tai.Prove(esk, alpha) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := Secp256k1Sha256Tai.Verify(&esk.PublicKey, alpha, pi) + if err != nil { + b.Fatal(err) + } + } + }) + b.Run("p256sha256tai-proving", func(b *testing.B) { + sk, _ := ecdsa.GenerateKey(elliptic.P256(), rand.New(rand.NewSource(1))) + alpha := []byte("Hello VeChain") + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _, err := P256Sha256Tai.Prove(sk, alpha) + if err != nil { + b.Fatal(err) + } + } + }) + b.Run("p256sha256tai-verifying", func(b *testing.B) { + sk, _ := ecdsa.GenerateKey(elliptic.P256(), rand.New(rand.NewSource(1))) + alpha := []byte("Hello VeChain") + + _, pi, _ := P256Sha256Tai.Prove(sk, alpha) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := P256Sha256Tai.Verify(&sk.PublicKey, alpha, pi) + if err != nil { + b.Fatal(err) + } + } + }) +}