├── .gitignore ├── go.mod ├── .github └── workflows │ └── go.yml ├── CHANGELOG.md ├── LICENSE ├── go.sum ├── README.md ├── .golangci.yml └── ecvrf ├── ecvrf_test.go └── ecvrf.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | bin 3 | dist 4 | vendor 5 | .vscode 6 | *.out 7 | *.html 8 | reports 9 | .idea 10 | 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ProtonMail/go-ecvrf 2 | 3 | go 1.17 4 | 5 | require ( 6 | filippo.io/edwards25519 v1.0.0-rc.1 7 | github.com/stretchr/testify v1.6.1 8 | github.com/davecgh/go-spew v1.1.0 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go test and lint 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | test: 12 | name: Test 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.17 20 | id: go 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Get dependencies 26 | run: | 27 | go get -v -t -d ./... 28 | if [ -f Gopkg.toml ]; then 29 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 30 | dep ensure 31 | fi 32 | go get github.com/golangci/golangci-lint/cmd/golangci-lint 33 | 34 | - name: Test 35 | run: go test -v -race ./... 36 | 37 | - name: Lint 38 | run: golangci-lint run ./... 39 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [0.0.1] 2021-12-14 8 | ### Added 9 | - `PrivateKey` and `PublicKey` types, offering the following functions: 10 | ```go 11 | GenerateKey(rnd io.Reader) (sk *PrivateKey, err error) // to generate a new keypair 12 | NewPrivateKey(skBytes []byte) (sk *PrivateKey, err error) // To deserialise a private key 13 | (sk *PrivateKey) Public() PublicKey // To export a public key from a private 14 | (sk *PrivateKey) Bytes() // To serialise a private key 15 | 16 | NewPublicKey(pkBytes []byte) (*PublicKey, error) // To deserialise a public key 17 | (sk *PublicKey) Bytes() // To serialise a public key 18 | 19 | (sk *PrivateKey) Prove(alpha []byte) (beta, proof []byte, err error) // To generate proof and VRF 20 | (pk *PublicKey) Verify(alpha, proof []byte) (verified bool, beta []byte, err error) // To verify proofs and VRFs 21 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ProtonMail 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= 2 | filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 8 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 9 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 12 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 13 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go-ECVRF 2 | Go-ECVRF is a library that implements ECVRF-EDWARDS25519-SHA512-TAI, 3 | a verifiable random function described in draft-irtf-cfrg-vrf-10. 4 | 5 | By design this VRF is **not suitable to be used on secret input**, 6 | as the try-and-increment procedure leaks information of the message 7 | (alpha string), but it is constant time for different secret keys. 8 | 9 | ## Download/Install 10 | ### Vendored install 11 | To use this library using [Go Modules](https://github.com/golang/go/wiki/Modules) just edit your 12 | `go.mod` configuration to contain: 13 | ```gomod 14 | require ( 15 | ... 16 | github.com/ProtonMail/go-ecvrf latest 17 | ) 18 | ``` 19 | 20 | It can then be installed by running: 21 | ```sh 22 | go mod vendor 23 | ``` 24 | Finally your software can include it in your software as follows: 25 | ```go 26 | import "github.com/ProtonMail/go-ecvrf/ecvrf" 27 | ``` 28 | 29 | ## Documentation 30 | A full overview of the API can be found here: 31 | https://godoc.org/github.com/ProtonMail/go-ecvrf/ecvrf 32 | 33 | In this document we provide some examples, more can be found in the tests. 34 | 35 | ## Examples 36 | ### Generating a new VRF key 37 | Using `nil` as a source will automatically use randomness from `crypto/random` 38 | ```go 39 | import "github.com/ProtonMail/go-ecvrf/ecvrf" 40 | 41 | privateKey, err := ecvrf.GenerateKey(nil) 42 | publicKey, err := privateKey.Public() 43 | 44 | PrivateKeyBin := privateKey.Bytes() 45 | PublicKeyBin := []bytes(publicKey) 46 | ``` 47 | The private key can be used to prove, the public key to verify. 48 | 49 | ### Proving 50 | ```go 51 | import "github.com/ProtonMail/go-ecvrf/ecvrf" 52 | 53 | privateKey, err = ecvrf.NewPrivateKey(PrivateKeyBin) 54 | message := []byte("alice") 55 | vrf, proof, err := privateKey.Prove(message) 56 | if err != nil { 57 | // handle error 58 | } 59 | 60 | // vrf contains the VRF output (or hash) 61 | // proof contains the proof for that message and private/public keypair 62 | ``` 63 | 64 | ### Verifying 65 | ```go 66 | import "github.com/ProtonMail/go-ecvrf/ecvrf" 67 | 68 | publicKey, err = ecvrf.NewPublicKey(PublicKeyBin) 69 | verified, verificationVRF, err := publicKey.Verify(alice, proof) 70 | if err != nil { 71 | // handle error 72 | } 73 | 74 | if verified { 75 | // proof verification succeeded, use verificationVRF 76 | } else { 77 | // verification of the proof failed 78 | } 79 | ``` -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | godox: 3 | keywords: # default keywords are TODO, BUG, and FIXME, but we override this by ignoring TODO 4 | - BUG 5 | - FIXME 6 | funlen: 7 | lines: 100 8 | statements: 80 9 | cyclop: 10 | # the minimal code complexity to report 11 | max-complexity: 20 12 | gocognit: 13 | min-complexity: 45 14 | 15 | issues: 16 | exclude-use-default: false 17 | exclude: 18 | - Using the variable on range scope `tt` in function literal 19 | - GetJsonSHA256Fingerprints should be GetJSONSHA256Fingerprints 20 | - ST1003 # CamelCase variables; see constants/cipher.go 21 | 22 | linters: 23 | enable-all: true 24 | disable: 25 | - dupl # Tool for code clone detection [fast: true, auto-fix: false] 26 | - gochecknoglobals # Checks that no globals are present in Go code [fast: true, auto-fix: false] 27 | - gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false] 28 | - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false] 29 | - goerr113 # Golang linter to check the errors handling expressions [fast: true, auto-fix: false] 30 | - gomnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false] 31 | - lll # Reports long lines [fast: true, auto-fix: false] 32 | - testpackage # Makes you use a separate _test package [fast: true, auto-fix: false] 33 | - wsl # Whitespace Linter - Forces you to use empty lines! [fast: true, auto-fix: false] 34 | - gofumpt # Enforce a stricter format than gofmt 35 | - gci # Enforce blank lines check 36 | - nlreturn # Enforce blank lines for return statements 37 | - exhaustivestruct # Enforce structures to be fully filled on instantiation - terrible with openpgp configs 38 | - paralleltest # Detects missing usage of t.Parallel() method in your Go test 39 | - forbidigo # Static analysis tool to forbid use of particular identifiers 40 | - thelper # Enforce test helper formatting 41 | - revive # Force CamelCase instead of all caps 42 | - nilerr # Force return err when not nil 43 | - wrapcheck # Force wrapping of external error TODO: when the bug is fixed update the linter 44 | - gomoddirectives # Prohibits the use of replace statements 45 | - varnamelen # Forbids short var names 46 | - ireturn # Prevents returning interfaces 47 | 48 | -------------------------------------------------------------------------------- /ecvrf/ecvrf_test.go: -------------------------------------------------------------------------------- 1 | package ecvrf 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ed25519" 6 | "encoding/hex" 7 | "testing" 8 | 9 | "filippo.io/edwards25519" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | var testVectors []testVector 14 | 15 | type testVector struct { 16 | sk, pk, x, h, k, u, v, pi, alpha, beta []byte 17 | } 18 | 19 | func decodeHex(str string) []byte { 20 | b, err := hex.DecodeString(str) 21 | if err != nil { 22 | panic(err) 23 | } 24 | return b 25 | } 26 | 27 | func init() { 28 | testVectors = make([]testVector, 3) 29 | 30 | // A.3 Example 7 31 | testVectors[0] = testVector{ 32 | decodeHex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"), 33 | decodeHex("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"), 34 | decodeHex("307c83864f2833cb427a2ef1c00a013cfdff2768d980c0a3a520f006904de94f"), 35 | decodeHex("91bbed02a99461df1ad4c6564a5f5d829d0b90cfc7903e7a5797bd658abf3318"), 36 | decodeHex("7100f3d9eadb6dc4743b029736ff283f5be494128df128df2817106f345b8594b6d6da2d6fb0b4c0257eb337675d96eab49cf39e66cc2c9547c2bf8b2a6afae4"), 37 | decodeHex("aef27c725be964c6a9bf4c45ca8e35df258c1878b838f37d9975523f09034071"), 38 | decodeHex("5016572f71466c646c119443455d6cb9b952f07d060ec8286d678615d55f954f"), 39 | decodeHex("8657106690b5526245a92b003bb079ccd1a92130477671f6fc01ad16f26f723f5e8bd1839b414219e8626d393787a192241fc442e6569e96c462f62b8079b9ed83ff2ee21c90c7c398802fdeebea4001"), 40 | decodeHex(""), 41 | decodeHex("90cf1df3b703cce59e2a35b925d411164068269d7b2d29f3301c03dd757876ff66b71dda49d2de59d03450451af026798e8f81cd2e333de5cdf4f3e140fdd8ae"), 42 | } 43 | 44 | // A.3 Example 8 45 | testVectors[1] = testVector{ 46 | decodeHex("4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb"), 47 | decodeHex("3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c"), 48 | decodeHex("68bd9ed75882d52815a97585caf4790a7f6c6b3b7f821c5e259a24b02e502e51"), 49 | decodeHex("5b659fc3d4e9263fd9a4ed1d022d75eaacc20df5e09f9ea937502396598dc551"), 50 | decodeHex("42589bbf0c485c3c91c1621bb4bfe04aed7be76ee48f9b00793b2342acb9c167cab856f9f9d4febc311330c20b0a8afd3743d05433e8be8d32522ecdc16cc5ce"), 51 | decodeHex("1dcb0a4821a2c48bf53548228b7f170962988f6d12f5439f31987ef41f034ab3"), 52 | decodeHex("fd03c0bf498c752161bae4719105a074630a2aa5f200ff7b3995f7bfb1513423"), 53 | decodeHex("f3141cd382dc42909d19ec5110469e4feae18300e94f304590abdced48aed593f7eaf3eb2f1a968cba3f6e23b386aeeaab7b1ea44a256e811892e13eeae7c9f6ea8992557453eac11c4d5476b1f35a08"), 54 | decodeHex("72"), 55 | decodeHex("eb4440665d3891d668e7e0fcaf587f1b4bd7fbfe99d0eb2211ccec90496310eb5e33821bc613efb94db5e5b54c70a848a0bef4553a41befc57663b56373a5031"), 56 | } 57 | 58 | // A.3 Example 9 59 | testVectors[2] = testVector{ 60 | decodeHex("c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7"), 61 | decodeHex("fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025"), 62 | decodeHex("909a8b755ed902849023a55b15c23d11ba4d7f4ec5c2f51b1325a181991ea95c"), 63 | decodeHex("bf4339376f5542811de615e3313d2b36f6f53c0acfebb482159711201192576a"), 64 | decodeHex("38b868c335ccda94a088428cbf3ec8bc7955bfaffe1f3bd2aa2c59fc31a0febc59d0e1af3715773ce11b3bbdd7aba8e3505d4b9de6f7e4a96e67e0d6bb6d6c3a"), 65 | decodeHex("2bae73e15a64042fcebf062abe7e432b2eca6744f3e8265bc38e009cd577ecd5"), 66 | decodeHex("88cba1cb0d4f9b649d9a86026b69de076724a93a65c349c988954f0961c5d506"), 67 | decodeHex("9bc0f79119cc5604bf02d23b4caede71393cedfbb191434dd016d30177ccbf80e29dc513c01c3a980e0e545bcd848222d08a6c3e3665ff5a4cab13a643bef812e284c6b2ee063a2cb4f456794723ad0a"), 68 | decodeHex("af82"), 69 | decodeHex("645427e5d00c62a23fb703732fa5d892940935942101e456ecca7bb217c61c452118fec1219202a0edcf038bb6373241578be7217ba85a2687f7a0310b2df19f"), 70 | } 71 | } 72 | 73 | func TestRFCVectors_GenerateKey(t *testing.T) { 74 | for _, vector := range testVectors { 75 | key, err := GenerateKey(bytes.NewReader(vector.sk)) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | 80 | assert.Equal(t, vector.sk, key.sk) 81 | assert.Equal(t, vector.pk, key.pk) 82 | 83 | s, _ := edwards25519.NewScalar().SetBytesWithClamping(vector.x) 84 | assert.Equal(t, s, key.x) 85 | } 86 | } 87 | 88 | func TestRFCVectors_NewPrivateKey(t *testing.T) { 89 | for _, vector := range testVectors { 90 | key, err := NewPrivateKey(append(vector.sk, vector.pk...)) 91 | if err != nil { 92 | t.Fatal(err) 93 | } 94 | 95 | assert.Equal(t, vector.sk, key.sk) 96 | assert.Equal(t, vector.pk, key.pk) 97 | 98 | s, _ := edwards25519.NewScalar().SetBytesWithClamping(vector.x) 99 | assert.Equal(t, s, key.x) 100 | } 101 | } 102 | 103 | func TestRFCVectors_hashToCurveTAI(t *testing.T) { 104 | for _, vector := range testVectors { 105 | h, err := hashToCurveTAI(vector.pk, vector.alpha) 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | 110 | assert.Equal(t, vector.h, h.Bytes()) 111 | } 112 | } 113 | 114 | func TestRFCVectors_generateNonceHash(t *testing.T) { 115 | for _, vector := range testVectors { 116 | k := generateNonceHash(vector.sk, vector.h) 117 | assert.Equal(t, vector.k, k) 118 | } 119 | } 120 | 121 | func TestRFCVectors_Prove(t *testing.T) { 122 | for _, vector := range testVectors { 123 | key, err := NewPrivateKey(append(vector.sk, vector.pk...)) 124 | if err != nil { 125 | t.Fatal(err) 126 | } 127 | 128 | beta, proof, err := key.Prove(vector.alpha) 129 | if err != nil { 130 | t.Fatal(err) 131 | } 132 | 133 | assert.Equal(t, vector.pi, proof) 134 | assert.Equal(t, vector.beta, beta) 135 | } 136 | } 137 | 138 | func TestRFCVectors_Verify(t *testing.T) { 139 | for _, vector := range testVectors { 140 | key, _ := NewPublicKey(vector.pk) 141 | 142 | verified, beta, err := key.Verify(vector.alpha, vector.pi) 143 | if err != nil { 144 | t.Fatal(err) 145 | } 146 | 147 | assert.True(t, verified) 148 | assert.Equal(t, vector.beta, beta) 149 | } 150 | } 151 | 152 | func TestHonestComplete(t *testing.T) { 153 | sk, err := GenerateKey(nil) 154 | if err != nil { 155 | t.Fatal(err) 156 | } 157 | 158 | pk, _ := sk.Public() 159 | alice := []byte("alice") 160 | aliceVRF, aliceProof, err := sk.Prove(alice) 161 | if err != nil { 162 | t.Fatal(err) 163 | } 164 | 165 | verified, aliceVRFFromVerification, err := pk.Verify(alice, aliceProof) 166 | if err != nil { 167 | t.Fatal(err) 168 | } 169 | if !verified { 170 | t.Error("Gen -> Compute -> Prove -> Verify -> FALSE") 171 | } 172 | 173 | assert.Equal(t, aliceVRF, aliceVRFFromVerification) 174 | } 175 | 176 | func TestConvertPrivateKeyToPublicKey(t *testing.T) { 177 | sk, err := GenerateKey(nil) 178 | if err != nil { 179 | t.Fatal(err) 180 | } 181 | 182 | pk, _ := sk.Public() 183 | 184 | assert.Equal(t, sk.pk, pk.Bytes()) 185 | 186 | edKey := ed25519.NewKeyFromSeed(sk.sk) 187 | assert.Equal(t, []byte(edKey[:scalarSize]), sk.sk) 188 | assert.Equal(t, []byte(edKey[scalarSize:]), sk.pk) 189 | } 190 | 191 | func TestFlipBitForgery(t *testing.T) { 192 | sk, err := GenerateKey(nil) 193 | if err != nil { 194 | t.Fatal(err) 195 | } 196 | 197 | pk, _ := sk.Public() 198 | alice := []byte("alice") 199 | 200 | for i := 0; i < ProofSize; i++ { 201 | _, aliceProof, _ := sk.Prove(alice) 202 | for j := uint(0); j < 8; j++ { 203 | aliceProof[i] ^= 1 << j 204 | verified, _, _ := pk.Verify(alice, aliceProof) 205 | if verified { 206 | t.Fatalf("forged by using aliceVRF[%d]^%d:\n (sk=%X)", i, j, sk.sk) 207 | } 208 | } 209 | } 210 | } 211 | 212 | func BenchmarkProve(b *testing.B) { 213 | sk, err := GenerateKey(nil) 214 | if err != nil { 215 | b.Fatal(err) 216 | } 217 | alice := []byte("alice") 218 | b.ResetTimer() 219 | for n := 0; n < b.N; n++ { 220 | _, _, _ = sk.Prove(alice) 221 | } 222 | } 223 | 224 | func BenchmarkVerify(b *testing.B) { 225 | sk, err := GenerateKey(nil) 226 | if err != nil { 227 | b.Fatal(err) 228 | } 229 | alice := []byte("alice") 230 | _, proof, _ := sk.Prove(alice) 231 | pk, _ := sk.Public() 232 | 233 | b.ResetTimer() 234 | 235 | for n := 0; n < b.N; n++ { 236 | _, _, _ = pk.Verify(alice, proof) 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /ecvrf/ecvrf.go: -------------------------------------------------------------------------------- 1 | // Package ecvrf implements ECVRF-EDWARDS25519-SHA512-TAI, a verifiable random 2 | // function described in draft-irtf-cfrg-vrf-10. 3 | // This VRF uses the Edwards form of Curve25519, SHA512 and the try-and-increment 4 | // hash-to-curve function. 5 | // See: https://datatracker.ietf.org/doc/draft-irtf-cfrg-vrf/ 6 | package ecvrf 7 | 8 | import ( 9 | "crypto/ed25519" 10 | "crypto/rand" 11 | "crypto/sha512" 12 | "crypto/subtle" 13 | "errors" 14 | "io" 15 | 16 | "filippo.io/edwards25519" 17 | ) 18 | 19 | const ( 20 | scalarSize = ed25519.SeedSize 21 | pointSize = ed25519.PublicKeySize 22 | intermediateSize = 16 23 | PublicKeySize = pointSize 24 | PrivateKeySize = scalarSize + pointSize 25 | ProofSize = pointSize + intermediateSize + scalarSize 26 | suiteID = 0x03 27 | ) 28 | 29 | // PrivateKey contains the VRF private key, a standard ed25519 key and the precomputed secret scalar. 30 | type PrivateKey struct { 31 | sk, pk []byte 32 | x *edwards25519.Scalar 33 | } 34 | 35 | // PublicKey contains the VRF public key, the canonical representation of a point on the ed25519 curve. 36 | type PublicKey struct { 37 | pk []byte 38 | } 39 | 40 | // GenerateKey creates a public/private key pair using rnd for randomness. 41 | // If rnd is nil, crypto/rand is used. 42 | func GenerateKey(rnd io.Reader) (sk *PrivateKey, err error) { 43 | if rnd == nil { 44 | rnd = rand.Reader 45 | } 46 | 47 | seed := make([]byte, scalarSize) 48 | if _, err := io.ReadFull(rnd, seed); err != nil { 49 | return nil, err 50 | } 51 | 52 | // Generate the private key, the secret scalar, and the public key 53 | // according to Section 5.1.5 of RFC8032 and cache the values. 54 | h := sha512.Sum512(seed) 55 | s, err := edwards25519.NewScalar().SetBytesWithClamping(h[:scalarSize]) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | A := (&edwards25519.Point{}).ScalarBaseMult(s) 61 | return &PrivateKey{seed, A.Bytes(), s}, err 62 | } 63 | 64 | // NewPrivateKey generates a PrivateKey object from a standard RFC8032 65 | // Ed25519 64-byte private key. 66 | func NewPrivateKey(skBytes []byte) (sk *PrivateKey, err error) { 67 | if len(skBytes) != PrivateKeySize { 68 | return nil, errors.New("ecvrf: bad private key size") 69 | } 70 | 71 | // Generate the secret scalar according to Section 5.1.5 of RFC8032. 72 | h := sha512.Sum512(skBytes[:scalarSize]) 73 | s, err := edwards25519.NewScalar().SetBytesWithClamping(h[:scalarSize]) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | return &PrivateKey{skBytes[:scalarSize], skBytes[scalarSize:], s}, err 79 | } 80 | 81 | // Public extracts the public VRF key from the underlying private-key. 82 | func (sk *PrivateKey) Public() (*PublicKey, error) { 83 | return NewPublicKey(sk.pk) 84 | } 85 | 86 | // Bytes serialises the private VRF key in a bytearray. 87 | func (sk *PrivateKey) Bytes() []byte { 88 | buf := make([]byte, PrivateKeySize) 89 | copy(buf, sk.sk) 90 | copy(buf[scalarSize:], sk.pk) 91 | return buf 92 | } 93 | 94 | // NewPublicKey generates a PublicKey object from a standard RFC8032 95 | // Ed25519 32-byte public key. 96 | func NewPublicKey(pkBytes []byte) (*PublicKey, error) { 97 | return &PublicKey{pkBytes}, nil 98 | } 99 | 100 | // Bytes serialises the private VRF key in a bytearray. 101 | func (pk *PublicKey) Bytes() []byte { 102 | return pk.pk 103 | } 104 | 105 | // Prove returns a proof such that Verify(pk, message, vrf, proof) == true 106 | // for a given message and public key pair sk/pk. 107 | // This function is defined in section 5.1 of draft-irtf-cfrg-vrf-10. 108 | func (sk *PrivateKey) Prove(message []byte) (vrf, proof []byte, err error) { 109 | // Step 1 is done in key generation/parsing 110 | h, err := hashToCurveTAI(sk.pk, message) 111 | if err != nil { 112 | return nil, nil, err 113 | } 114 | 115 | gamma := (&edwards25519.Point{}).ScalarMult(sk.x, h) 116 | kHash := generateNonceHash(sk.sk, h.Bytes()) 117 | k, err := edwards25519.NewScalar().SetUniformBytes(kHash) 118 | if err != nil { 119 | return nil, nil, err 120 | } 121 | 122 | c := hashPoints( 123 | h, 124 | gamma, 125 | (&edwards25519.Point{}).ScalarBaseMult(k), 126 | (&edwards25519.Point{}).ScalarMult(k, h), 127 | ) 128 | 129 | // append 16 zeroes to c to convert it to a scalar 130 | cScal, err := cToScalar(c) 131 | if err != nil { 132 | return nil, nil, err 133 | } 134 | 135 | // s = (k + c*x) mod q 136 | s := edwards25519.NewScalar().Add(k, edwards25519.NewScalar().Multiply(cScal, sk.x)) 137 | 138 | proof = make([]byte, ProofSize) 139 | copy(proof, gamma.Bytes()) 140 | copy(proof[pointSize:], c) 141 | copy(proof[pointSize+intermediateSize:], s.Bytes()) 142 | 143 | return proofToHash(gamma), proof, nil 144 | } 145 | 146 | // Verify verifies that the given proof matches the message and the public 147 | // key pk. When true it also returns the expected VRF string. 148 | // This function is defined in section 5.3 of draft-irtf-cfrg-vrf-10. 149 | func (pk *PublicKey) Verify(message, proof []byte) (verified bool, vrf []byte, err error) { 150 | if len(proof) != ProofSize { 151 | return false, nil, errors.New("ecvrf: bad proof length") 152 | } 153 | 154 | y, err := (&edwards25519.Point{}).SetBytes(pk.pk) 155 | if err != nil { 156 | return false, nil, err 157 | } 158 | 159 | gamma, err := (&edwards25519.Point{}).SetBytes(proof[:pointSize]) 160 | if err != nil { 161 | return false, nil, err 162 | } 163 | 164 | c, err := cToScalar(proof[pointSize : pointSize+intermediateSize]) 165 | if err != nil { 166 | return false, nil, err 167 | } 168 | 169 | s, err := edwards25519.NewScalar().SetCanonicalBytes(proof[pointSize+intermediateSize:]) 170 | if err != nil { 171 | return false, nil, err 172 | } 173 | 174 | h, err := hashToCurveTAI(pk.pk, message) 175 | if err != nil { 176 | return false, nil, err 177 | } 178 | 179 | // U = s*B - c*Y 180 | u := (&edwards25519.Point{}).Subtract( 181 | (&edwards25519.Point{}).ScalarBaseMult(s), 182 | (&edwards25519.Point{}).ScalarMult(c, y), 183 | ) 184 | 185 | // V = s*H - c*Gamma 186 | v := (&edwards25519.Point{}).Subtract( 187 | (&edwards25519.Point{}).ScalarMult(s, h), 188 | (&edwards25519.Point{}).ScalarMult(c, gamma), 189 | ) 190 | 191 | // If c and c' are different 192 | if subtle.ConstantTimeCompare(hashPoints(h, gamma, u, v), proof[pointSize:pointSize+intermediateSize]) == 0 { 193 | return false, nil, nil 194 | } 195 | 196 | return true, proofToHash(gamma), nil 197 | } 198 | 199 | // -- internal functions -- 200 | 201 | // Step 5.1.2 of draft-irtf-cfrg-vrf-10 implemented as defined by the section 202 | // 5.4.1.1, ECVRF_hash_to_curve_try_and_increment. 203 | func hashToCurveTAI(pk, alpha []byte) (*edwards25519.Point, error) { 204 | // CTR needs to be encoded in a string of length 1, therefore at most 255 205 | for ctr := 0; ctr < 256; ctr++ { 206 | h := sha512.New() 207 | h.Write([]byte{suiteID, 0x01}) 208 | h.Write(pk) 209 | h.Write(alpha) 210 | h.Write([]byte{uint8(ctr), 0x00}) 211 | 212 | p, err := (&edwards25519.Point{}).SetBytes(h.Sum(nil)[:scalarSize]) 213 | if err == nil && p.Equal(edwards25519.NewIdentityPoint()) == 0 { 214 | return (&edwards25519.Point{}).MultByCofactor(p), nil 215 | } 216 | } 217 | 218 | // Abort - too many CTR attempts 219 | return nil, errors.New("ecvrf: unable to find suitable ctr value") 220 | } 221 | 222 | // generateNonceHash implements step 5.1.5 of draft-irtf-cfrg-vrf-10 as defined by the section 223 | // 5.4.2.2, ECVRF_nonce_generation_RFC8032. 224 | func generateNonceHash(sk, h []byte) []byte { 225 | skHash := sha512.New() 226 | skHash.Write(sk) 227 | 228 | nonceHash := sha512.New() 229 | nonceHash.Write(skHash.Sum(nil)[scalarSize:]) 230 | nonceHash.Write(h) 231 | 232 | return nonceHash.Sum(nil) 233 | } 234 | 235 | // hashPoints implements step 5.1.6 of draft-irtf-cfrg-vrf-10 as defined by the section 236 | // 5.4.3, ECVRF_hash_points. 237 | func hashPoints(points ...*edwards25519.Point) []byte { 238 | h := sha512.New() 239 | h.Write([]byte{suiteID, 0x02}) 240 | for _, point := range points { 241 | h.Write(point.Bytes()) 242 | } 243 | h.Write([]byte{0x00}) 244 | 245 | return h.Sum(nil)[:intermediateSize] 246 | } 247 | 248 | // proofToHash implements section 5.2 of draft-irtf-cfrg-vrf-10. 249 | func proofToHash(gamma *edwards25519.Point) []byte { 250 | h := sha512.New() 251 | gammaC := (&edwards25519.Point{}).MultByCofactor(gamma) 252 | h.Write([]byte{suiteID, 0x03}) 253 | h.Write(gammaC.Bytes()) 254 | h.Write([]byte{0x00}) 255 | 256 | return h.Sum(nil) 257 | } 258 | 259 | // cToScalar transforms the 16-byte c into an ed25519 scalar. 260 | func cToScalar(c []byte) (*edwards25519.Scalar, error) { 261 | cRaw := make([]byte, 32) 262 | copy(cRaw, c) 263 | return edwards25519.NewScalar().SetCanonicalBytes(cRaw) 264 | } 265 | --------------------------------------------------------------------------------