├── LICENSE ├── README.md ├── libsodium-fork-wrapper ├── libs │ └── libsodium.a ├── other │ └── test.c ├── test.go └── vrfbenchmark_test.go ├── libsodium-vanilla-wrapper ├── README ├── a.out ├── alpha ├── beta ├── main.c ├── pi ├── pk ├── sk ├── testvector ├── vrf.c └── vrf.h └── python ├── debug.py ├── ed25519.py ├── test.py ├── testvectors.py └── vrf.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Algorand, Inc. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `libsodium-vanilla-wrapper/` contains a C implementation of the VRF and a test executable. To build it, just link against vanilla libsodium, e.g. `gcc -lsodium main.c vrf.c`. No libsodium-fork needed! 2 | 3 | This code has not been audited yet! Use at your own risk. 4 | 5 | This is essentially just the VRF code from libsodium-fork but adapted to use the point / scalar arithmetic functions that libsodium now exports rather than internal libsodium functions. Despite my best efforts, it may have a few subtle differences in behavior from libsodium-fork. In particular, at the moment this implementation will explicitly reject proofs where gamma is not on the main subgroup or is of low order, whereas libsodium-fork will not. (I haven't thought too hard about whether it's possible to make a valid proof where gamma is not on the main subgroup or is of low order; if not then this difference doesn't matter because libsodium-fork would eventually reject the proof anyway.) There may be other differences I missed -- definitely this code should be carefully checked before using it in production. For go-algorand it may be wise to do a protocol-upgrade just in case. 6 | 7 | One thing to note: The libsodium docs say that the `crypto_scalarmult_ed25519_noclamp` and `crypto_scalarmult_ed25519_base_noclamp` functions return an error code if passed a 0 scalar. When verifying a proof, we will pass a 0 scalar to these functions if the `c` or `s` in the proof is zero. As of libsodium 1.0.18, it appears that despite returning an error code these functions will give the correct answer (the identity point) so this isn't a problem. However, if this behavior changes in a future version of libsodium, we'll need to handle this case then. 8 | 9 | The other directories are from 2018 and not as interesting. 10 | 11 | `python/` contains a (slow, variable-time) Python implementation of the VRF (to generate test vectors and validate the C implementation) 12 | In particular, `debug.py` can be given a (hex-encoded) secret key and will output the (hex-encoded) public key, the VRF proof for "hello", and the corresponding VRF output hash. 13 | The python implementation uses djb's reference python implementation of ed25519, which works with Python 2 only. 14 | 15 | `libsodium-fork-wrapper/` contains a `test.go`, a command line tool that wraps our libsodium fork. To build, place your built `libsodium.a` library into `libsodium-wrapper/libs/` and update the hardcoded include path in the `// #cgo CFLAGS: ` line near the top of `test.go`. Alternatively, configure libsodium-fork with `--prefix=/tmp`, install it with `make install`, and then run `test.go` with `LD_LIBRARY_PATH=/tmp/lib` 16 | To use: 17 | * `go run test.go keygen` will generate a keypair and create two files `vrf.priv` and `vrf.pub` containing the hex-encoded private and public key, respectively. 18 | * `go run test.go prove "hello"` will use the private key in `vrf.priv` to output a (hex-encoded) proof for the string "hello" 19 | * `go run test.go verify {hex-encoded proof} "hello"` will use the public key in `vrf.pub` to verify the given (hex-encoded) proof for the string "hello", and if verification succeeds, output the VRF hash 20 | -------------------------------------------------------------------------------- /libsodium-fork-wrapper/libs/libsodium.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorand/vrf/efdf2576f689d2399694630155b6d11f140ab181/libsodium-fork-wrapper/libs/libsodium.a -------------------------------------------------------------------------------- /libsodium-fork-wrapper/other/test.c: -------------------------------------------------------------------------------- 1 | #include "stdlib.h" 2 | #include 3 | 4 | 5 | int printhex(unsigned char *x, size_t len) { 6 | for (size_t i = 0; i < len; i ++) { 7 | printf("%02x",x[i]); 8 | } 9 | } 10 | 11 | int main(int argc, char **argv){ 12 | if (sodium_init() != 0){ 13 | printf("init failed"); 14 | return -1; 15 | } 16 | 17 | unsigned char pk[32], sk[32]; 18 | crypto_vrf_keypair(pk, sk); 19 | printf("pk = "); 20 | printhex(pk, 32); 21 | printf("\n"); 22 | printf("sk = "); 23 | printhex(sk, 32); 24 | printf("\n"); 25 | 26 | unsigned char proof[80]; 27 | 28 | unsigned char *msg = "hello"; 29 | printf("msg = "); 30 | printhex(msg, 5); 31 | printf("\n"); 32 | crypto_vrf_prove(proof, sk, msg, 5); 33 | printf("prove(\"hello\") = "); 34 | printhex(proof, 80); 35 | printf("\n"); 36 | 37 | unsigned char beta[32]; 38 | if (!crypto_vrf_verify(pk, proof, msg, 5)) { 39 | printf("verify failed\n"); 40 | return -1; 41 | } 42 | 43 | if (!crypto_vrf_proof2hash(beta, proof)){ 44 | printf("proof2hash error\n"); 45 | return -1; 46 | } 47 | char beta_out[65]; 48 | for (int j = 0; j < 32; j++){ 49 | printf("%02x",beta[j]); 50 | } 51 | printf("\n"); 52 | sodium_bin2hex(beta_out, 65, beta, 32); 53 | printf("%s\n", beta_out); 54 | return 0; 55 | } 56 | 57 | 58 | -------------------------------------------------------------------------------- /libsodium-fork-wrapper/test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "io/ioutil" 7 | "encoding/hex" 8 | ) 9 | 10 | // #cgo CFLAGS: -I/tmp/include 11 | // #cgo LDFLAGS: -L /tmp/lib -lsodium 12 | // #include 13 | import "C" 14 | 15 | func init(){ 16 | if C.sodium_init() == -1 { 17 | panic("sodium_init() failed") 18 | } 19 | } 20 | 21 | 22 | type ( 23 | VrfProof [80]uint8 24 | VrfOutput [64]uint8 25 | VrfPubkey [32]uint8 26 | VrfPrivkey [64]uint8 27 | ) 28 | 29 | // Note: Go arrays are copied by value, so any call to VrfProve makes a copy of the secret key that lingers in memory. Do we want to have secret keys live in the C heap and instead pass pointers to them, e.g., allocate a privkey with sodium_malloc and have VrfPrivkey be of type unsafe.Pointer? 30 | 31 | func VrfKeygen() (pub VrfPubkey, priv VrfPrivkey) { 32 | C.crypto_vrf_keypair((*C.uchar)(&pub[0]), (*C.uchar)(&priv[0])) 33 | return pub, priv 34 | } 35 | 36 | func (sk VrfPrivkey) Prove(msg []byte) (proof VrfProof, ok bool) { 37 | ret := C.crypto_vrf_prove((*C.uchar)(&proof[0]), (*C.uchar)(&sk[0]), (*C.uchar)(&msg[0]), (C.ulonglong)(len(msg))) 38 | return proof, ret == 0 39 | } 40 | 41 | func (proof VrfProof) Hash() (hash VrfOutput, ok bool) { 42 | ret := C.crypto_vrf_proof_to_hash((*C.uchar)(&hash[0]), (*C.uchar)(&proof[0])) 43 | return hash, ret == 0 44 | } 45 | 46 | func (pk VrfPubkey) Verify(msg []byte, proof VrfProof) (bool, VrfOutput) { 47 | var out VrfOutput 48 | ret := C.crypto_vrf_verify((*C.uchar)(&out[0]), (*C.uchar)(&pk[0]), (*C.uchar)(&proof[0]), (*C.uchar)(&msg[0]), (C.ulonglong)(len(msg))) 49 | return ret == 0, out 50 | } 51 | 52 | func main(){ 53 | if len(os.Args) < 2 { 54 | fmt.Printf("Usage:\n\t%[1]s keygen\n\t%[1]s prove {msg}\n\t%[1]s verify {proof} {msg}\n", os.Args[0]) 55 | os.Exit(-1) 56 | } 57 | 58 | switch os.Args[1] { 59 | case "keygen": 60 | pub, priv := VrfKeygen() 61 | if err := ioutil.WriteFile("vrf.priv", []byte(fmt.Sprintf("%x", priv)), 0600); err != nil { 62 | panic(err) 63 | } 64 | if err := ioutil.WriteFile("vrf.pub", []byte(fmt.Sprintf("%x", pub)), 0644); err != nil { 65 | panic(err) 66 | } 67 | fmt.Printf("Wrote keypair to ./vrf.priv and ./vrf.pub\n") 68 | return 69 | case "prove": 70 | var priv VrfPrivkey 71 | privhex, err := ioutil.ReadFile("vrf.priv") 72 | if err != nil { 73 | panic(err) 74 | } 75 | if privhex[len(privhex)-1] == '\n' { 76 | privhex = privhex[:len(privhex)-1] 77 | } 78 | n, err := hex.Decode(priv[:], privhex) 79 | if err != nil || n != len(priv[:]) { 80 | panic("hex decode of privkey failed") 81 | } 82 | 83 | proof, ok := priv.Prove([]byte(os.Args[2])) 84 | if !ok { 85 | panic("proof failed") 86 | } 87 | fmt.Printf("Prove(%s) = %x\n", os.Args[2], proof) 88 | case "verify": 89 | var pub VrfPubkey 90 | pubhex, err := ioutil.ReadFile("vrf.pub") 91 | if err != nil { 92 | panic(err) 93 | } 94 | if pubhex[len(pubhex)-1] == '\n' { 95 | pubhex = pubhex[:len(pubhex)-1] 96 | } 97 | n, err := hex.Decode(pub[:], pubhex) 98 | if err != nil || n != len(pub[:]) { 99 | panic("hex decode of pubkey failed") 100 | } 101 | 102 | var proof VrfProof 103 | n, err = hex.Decode(proof[:], []byte(os.Args[2])) 104 | if err != nil || n != len(proof) { 105 | panic("hex decode of proof failed") 106 | } 107 | 108 | ok, hash := pub.Verify([]byte(os.Args[3]), proof) 109 | if !ok { 110 | panic("verify failed") 111 | } 112 | fmt.Printf("Verification succeeded\n") 113 | fmt.Printf("Output is %x\n", hash) 114 | default: 115 | fmt.Printf("Usage:\n\t%[1]s keygen {file}\n\t%[1]s prove {msg}\n\t%[1]s verify {proof} {msg}\n", os.Args[0]) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /libsodium-fork-wrapper/vrfbenchmark_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | // use `go test -bench=.' to run. 6 | 7 | var pk VrfPubkey 8 | var sk VrfPrivkey 9 | var proof VrfProof 10 | 11 | const msg string = "Hello VRF!" 12 | 13 | func BenchmarkKeygen(b *testing.B) { 14 | for i := 0; i < b.N-1; i++ { 15 | VrfKeygen() 16 | } 17 | // last iteration store pub/sk for other tests 18 | pub, pri := VrfKeygen() 19 | pk = pub 20 | sk = pri 21 | } 22 | 23 | func BenchmarkProve(b *testing.B) { 24 | 25 | for i := 0; i < b.N-1; i++ { 26 | _, ok := sk.Prove([]byte(msg)) 27 | if !ok { 28 | panic("proof failed") 29 | } 30 | } 31 | pr, ok := sk.Prove([]byte(msg)) 32 | proof = pr 33 | if !ok { 34 | panic("proof failed") 35 | } 36 | } 37 | 38 | func BenchmarkVerify(b *testing.B) { 39 | for i := 0; i < b.N; i++ { 40 | 41 | ok, out := pk.Verify([]byte(msg), proof) 42 | if !ok { 43 | panic("verify failed") 44 | } 45 | _ = out 46 | } 47 | } 48 | 49 | func BenchmarkProof2Hash(b *testing.B) { 50 | for i := 0; i < b.N; i++ { 51 | hash, ok := proof.Hash() 52 | if !ok { 53 | panic("proof2hash failed") 54 | } 55 | _ = hash 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /libsodium-vanilla-wrapper/README: -------------------------------------------------------------------------------- 1 | To build test executable: 2 | `gcc -lsodium main.c vrf.c` 3 | The test executable reads a testvector from files alpha, beta, pi, pk, and sk. (The length of alpha is hardcoded in main.c.) It checks that the prove and verify functions work as expected on the test vector. 4 | -------------------------------------------------------------------------------- /libsodium-vanilla-wrapper/a.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorand/vrf/efdf2576f689d2399694630155b6d11f140ab181/libsodium-vanilla-wrapper/a.out -------------------------------------------------------------------------------- /libsodium-vanilla-wrapper/alpha: -------------------------------------------------------------------------------- 1 | r 2 | -------------------------------------------------------------------------------- /libsodium-vanilla-wrapper/beta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorand/vrf/efdf2576f689d2399694630155b6d11f140ab181/libsodium-vanilla-wrapper/beta -------------------------------------------------------------------------------- /libsodium-vanilla-wrapper/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "vrf.h" 6 | 7 | 8 | int readfile(unsigned char *out, int len, const char *name) { 9 | int fd = 0; 10 | fd = open(name, O_RDONLY); 11 | if (fd == -1) { 12 | return 0; 13 | } 14 | ssize_t n = 0, dn = 0; 15 | do { 16 | dn = read(fd, out+n, len-n); 17 | if (dn <= 0) { 18 | break; 19 | } 20 | n += dn; 21 | } while(n < len); 22 | close(fd); 23 | return (n == len && dn >= 0); 24 | } 25 | 26 | int main(int argc, char **argv) { 27 | unsigned char pk[32], skpk[64], alpha[1], pi_good[80], beta[64]; 28 | if (!readfile(pk, 32, "pk")) { 29 | fprintf(stderr, "pk read error\n"); 30 | return (1); 31 | } 32 | if (!readfile(skpk, 32, "sk")) { 33 | fprintf(stderr, "sk read error\n"); 34 | return (2); 35 | } 36 | memmove(skpk+32, pk, 32); 37 | if (!readfile(alpha, 1, "alpha")) { 38 | fprintf(stderr, "alpha read error\n"); 39 | return (3); 40 | } 41 | if (!readfile(pi_good, 80, "pi")) { 42 | fprintf(stderr, "pi read error\n"); 43 | return (4); 44 | } 45 | if (!readfile(beta, 64, "beta")) { 46 | fprintf(stderr, "beta read error\n"); 47 | return (5); 48 | } 49 | 50 | unsigned char pi_ours[80]; 51 | int err = vrf_prove(pi_ours, skpk, alpha, sizeof alpha); 52 | if (err != 0) { 53 | fprintf(stderr, "prove() returned error\n"); 54 | return (6); 55 | } 56 | if (memcmp(pi_ours, pi_good, 80) != 0) { 57 | fprintf(stderr, "Produced wrong proof\n"); 58 | return (7); 59 | } 60 | 61 | unsigned char hash[64]; 62 | err = vrf_verify(hash, pk, pi_ours, alpha, sizeof alpha); 63 | if (err != 0) { 64 | fprintf(stderr, "Proof did not verify\n"); 65 | return (8); 66 | } 67 | if (memcmp(hash, beta, 64) != 0) { 68 | fprintf(stderr, "verify() returned wrong hash\n"); 69 | return (9); 70 | } 71 | 72 | fprintf(stderr, "PASS\n"); 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /libsodium-vanilla-wrapper/pi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorand/vrf/efdf2576f689d2399694630155b6d11f140ab181/libsodium-vanilla-wrapper/pi -------------------------------------------------------------------------------- /libsodium-vanilla-wrapper/pk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorand/vrf/efdf2576f689d2399694630155b6d11f140ab181/libsodium-vanilla-wrapper/pk -------------------------------------------------------------------------------- /libsodium-vanilla-wrapper/sk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorand/vrf/efdf2576f689d2399694630155b6d11f140ab181/libsodium-vanilla-wrapper/sk -------------------------------------------------------------------------------- /libsodium-vanilla-wrapper/testvector: -------------------------------------------------------------------------------- 1 | SK 4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb 2 | PK 3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c 3 | alpha 72 4 | pi ae5b66bdf04b4c010bfe32b2fc126ead2107b697634f6f7337b9bff8785ee111200095ece87dde4dbe87343f6df3b107d91798c8a7eb1245d3bb9c5aafb093358c13e6ae1111a55717e895fd15f99f07 5 | beta 94f4487e1b2fec954309ef1289ecb2e15043a2461ecc7b2ae7d4470607ef82eb1cfa97d84991fe4a7bfdfd715606bc27e2967a6c557cfb5875879b671740b7d8 6 | -------------------------------------------------------------------------------- /libsodium-vanilla-wrapper/vrf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "vrf.h" 4 | 5 | // ----- vrf_ietfdraft03.h ----- 6 | static const unsigned char SUITE = 0x04; /* ECVRF-ED25519-SHA512-Elligator2 */ 7 | 8 | 9 | // ----- convert.c ----- 10 | 11 | static const unsigned char ONE = 0x01; 12 | static const unsigned char TWO = 0x02; 13 | 14 | /* Hash a message to a curve point using Elligator2. 15 | * Specified in VRF draft spec section 5.4.1.2. 16 | * The actual elligator2 implementation is ge25519_from_uniform. 17 | * Runtime depends only on alphalen (the message length) 18 | * Note: caller is responsible for ensuring Y_point is canonical. 19 | */ 20 | void 21 | hash_to_curve(unsigned char H_string[32], 22 | const unsigned char Y_point[32], 23 | const unsigned char *alpha, 24 | const unsigned long long alphalen) 25 | { 26 | crypto_hash_sha512_state hs; 27 | unsigned char r_string[64]; 28 | 29 | /* r = first 32 bytes of SHA512(suite || 0x01 || Y || alpha) */ 30 | crypto_hash_sha512_init(&hs); 31 | crypto_hash_sha512_update(&hs, &SUITE, 1); 32 | crypto_hash_sha512_update(&hs, &ONE, 1); 33 | crypto_hash_sha512_update(&hs, Y_point, 32); 34 | crypto_hash_sha512_update(&hs, alpha, alphalen); 35 | crypto_hash_sha512_final(&hs, r_string); 36 | 37 | r_string[31] &= 0x7f; /* clear sign bit */ 38 | crypto_core_ed25519_from_uniform(H_string, r_string); /* elligator2 */ 39 | } 40 | 41 | /* Subroutine specified in draft spec section 5.4.3. 42 | * Hashes four points to a 16-byte string. 43 | * NOTE: Caller must ensure P1, P2, P3, and P4 are already canonical! 44 | * Constant time. */ 45 | void 46 | hash_points(unsigned char c[16], const unsigned char P1[32], 47 | const unsigned char P2[32], const unsigned char P3[32], 48 | const unsigned char P4[32]) 49 | { 50 | unsigned char str[2+32*4], c1[64]; 51 | 52 | str[0] = SUITE; 53 | str[1] = TWO; 54 | memmove(str+2+32*0, P1, 32); 55 | memmove(str+2+32*1, P2, 32); 56 | memmove(str+2+32*2, P3, 32); 57 | memmove(str+2+32*3, P4, 32); 58 | crypto_hash_sha512(c1, str, sizeof str); 59 | memmove(c, c1, 16); 60 | sodium_memzero(c1, 64); 61 | } 62 | 63 | /* Decode an 80-byte proof pi into a point gamma, a 16-byte scalar c, and a 64 | * 32-byte scalar s, as specified in IETF draft section 5.4.4. 65 | * Returns 0 on success, nonzero on failure. 66 | * Compare to _vrf_ietfdraft03_decode_proof in libsodium-fork's convert.c 67 | * Note that this implementation requires gamma to be not just canonical but also on the main subgroup and not of low-order. In theory this could cause us to reject some (unusually constructed) proofs that libsodium-fork would accept. 68 | */ 69 | int 70 | decode_proof(unsigned char Gamma[32], unsigned char c[16], 71 | unsigned char s[32], const unsigned char pi[80]) 72 | { 73 | if (crypto_core_ed25519_is_valid_point(pi) != 1) { 74 | return -1; 75 | } 76 | memmove(Gamma, pi, 32); /* gamma = pi[0:32] */ 77 | memmove(c, pi+32, 16); /* c = pi[32:48] */ 78 | memmove(s, pi+48, 32); /* s = pi[48:80] */ 79 | return 0; 80 | } 81 | 82 | 83 | 84 | // ----- prove.c ----- 85 | 86 | /* Utility function to convert a "secret key" (32-byte seed || 32-byte PK) 87 | * into the public point Y, the private saclar x, and truncated hash of the 88 | * seed to be used later in nonce generation. 89 | * Return 0 on success, -1 on failure decoding the public point Y. 90 | * NOTE: Unlike in libsodium-fork, if the public-key half of skpk is of low 91 | * order or is not in the main subgroup, this function will return -1. 92 | * This shouldn't matter in practice -- if users manually corrupt their secret key, 93 | * that's their own problem. 94 | */ 95 | static int 96 | vrf_expand_sk(unsigned char pk[32], unsigned char x_scalar[32], 97 | unsigned char truncated_hashed_sk_string[32], const unsigned char skpk[64]) 98 | { 99 | unsigned char h[64]; 100 | 101 | crypto_hash_sha512(h, skpk, 32); 102 | h[0] &= 248; 103 | h[31] &= 127; 104 | h[31] |= 64; 105 | memmove(x_scalar, h, 32); 106 | memmove(truncated_hashed_sk_string, h + 32, 32); 107 | sodium_memzero(h, 64); 108 | 109 | memmove(pk, skpk+32, 32); 110 | return crypto_core_ed25519_is_valid_point(pk) - 1; 111 | } 112 | 113 | /* In libsodium 1.0.18, crypto_core_ed25519_scalar_add leaves secrets on the stack. 114 | * (in particular, two arrays of size crypto_core_ed25519_NONREDUCEDSCALARBYTES.) 115 | * Its implementation is copied here but with sodium_memzeros added to clear the secrets. 116 | */ 117 | static void scalar_add_clearsecrets(unsigned char *z, const unsigned char *x, const unsigned char *y) { 118 | unsigned char x_[crypto_core_ed25519_NONREDUCEDSCALARBYTES]; 119 | unsigned char y_[crypto_core_ed25519_NONREDUCEDSCALARBYTES]; 120 | 121 | memset(x_, 0, sizeof x_); 122 | memset(y_, 0, sizeof y_); 123 | memcpy(x_, x, crypto_core_ed25519_SCALARBYTES); 124 | memcpy(y_, y, crypto_core_ed25519_SCALARBYTES); 125 | sodium_add(x_, y_, crypto_core_ed25519_SCALARBYTES); 126 | crypto_core_ed25519_scalar_reduce(z, x_); 127 | sodium_memzero(x_, sizeof x_); 128 | sodium_memzero(y_, sizeof y_); 129 | } 130 | 131 | /* Deterministically generate a (secret) nonce to be used in a proof. 132 | * Specified in draft spec section 5.4.2.2. 133 | * Note: In the spec, this subroutine computes truncated_hashed_sk_string 134 | * Here we instead takes it as an argument, and we compute it in vrf_expand_sk 135 | */ 136 | static void 137 | nonce_generation(unsigned char k_scalar[32], 138 | const unsigned char truncated_hashed_sk_string[32], 139 | const unsigned char h_string[32]) 140 | { 141 | crypto_hash_sha512_state hs; 142 | unsigned char k_string[64]; 143 | 144 | /* k_string = SHA512(truncated_hashed_sk_string || h_string) */ 145 | crypto_hash_sha512_init(&hs); 146 | crypto_hash_sha512_update(&hs, truncated_hashed_sk_string, 32); 147 | crypto_hash_sha512_update(&hs, h_string, 32); 148 | crypto_hash_sha512_final(&hs, k_string); 149 | 150 | crypto_core_ed25519_scalar_reduce(k_scalar, k_string); /* k_scalar[0:32] = string_to_int(k_string) mod q */ 151 | 152 | sodium_memzero(k_string, sizeof k_string); 153 | } 154 | 155 | // Compare to vrf_prove in prove.c 156 | static void prove_helper(unsigned char pi[80], const unsigned char Y_point[32], const unsigned char x_scalar[32], const unsigned char truncated_hashed_sk_string[32], const unsigned char *alpha, const unsigned long long alphalen) { 157 | unsigned char H_point[32], k_scalar[32], c_scalar[32], cx_scalar[32], Gamma_point[32], kB_point[32], kH_point[32]; 158 | // expand_sk already checked that Y was a valid point 159 | hash_to_curve(H_point, Y_point, alpha, alphalen); 160 | 161 | crypto_scalarmult_ed25519_noclamp(Gamma_point, x_scalar, H_point); /* Gamma = x*H */ 162 | nonce_generation(k_scalar, truncated_hashed_sk_string, H_point); 163 | crypto_scalarmult_ed25519_base_noclamp(kB_point, k_scalar); /* compute k*B */ 164 | crypto_scalarmult_ed25519_noclamp(kH_point, k_scalar, H_point); /* compute k*H */ 165 | 166 | /* c = hash_points(h, gamma, k*B, k*H) 167 | * (writes only to first 16 bytes of c_scalar */ 168 | hash_points(c_scalar, H_point, Gamma_point, kB_point, kH_point); 169 | memset(c_scalar+16, 0, 16); /* zero remaining 16 bytes of c_scalar */ 170 | 171 | memmove(pi, Gamma_point, 32); /* pi[0:32] = Gamma */ 172 | memmove(pi+32, c_scalar, 16); /* pi[32:48] = c (16 bytes) */ 173 | 174 | crypto_core_ed25519_scalar_mul(cx_scalar, c_scalar, x_scalar); 175 | scalar_add_clearsecrets(pi+48, cx_scalar, k_scalar); /* pi[48:80] = s = c*x + k (mod q) */ 176 | 177 | /* k and cx must remain secret */ 178 | sodium_memzero(cx_scalar, sizeof cx_scalar); 179 | sodium_memzero(k_scalar, sizeof k_scalar); 180 | /* erase other non-sensitive intermediate state for good measure */ 181 | sodium_memzero(H_point, sizeof H_point); 182 | sodium_memzero(c_scalar, sizeof c_scalar); 183 | sodium_memzero(Gamma_point, sizeof Gamma_point); 184 | sodium_memzero(kB_point, sizeof kB_point); 185 | sodium_memzero(kH_point, sizeof kH_point); 186 | } 187 | 188 | int vrf_prove(unsigned char proof[80], const unsigned char skpk[64], const unsigned char *msg, unsigned long long msglen) { 189 | unsigned char Y_point[32], x_scalar[32], truncated_hashed_sk_string[32]; 190 | if (vrf_expand_sk(Y_point, x_scalar, truncated_hashed_sk_string, skpk) != 0) { 191 | sodium_memzero(x_scalar, 32); 192 | sodium_memzero(truncated_hashed_sk_string, 32); 193 | sodium_memzero(Y_point, 32); /* for good measure */ 194 | return -1; 195 | } 196 | prove_helper(proof, Y_point, x_scalar, truncated_hashed_sk_string, msg, msglen); 197 | sodium_memzero(x_scalar, 32); 198 | sodium_memzero(truncated_hashed_sk_string, 32); 199 | sodium_memzero(Y_point, 32); /* for good measure */ 200 | return 0; 201 | } 202 | 203 | 204 | // ----- verify.c ----- 205 | 206 | static const unsigned char THREE = 0x03; 207 | 208 | /* Utility function to multiply a point by the cofactor (8) in place 209 | * NOTE: assumes input is a valid point */ 210 | static void multiply_by_cofactor(unsigned char pt[32]) { 211 | crypto_core_ed25519_add(pt, pt, pt); // pt = 2 * pt_orig 212 | crypto_core_ed25519_add(pt, pt, pt); // pt = 4 * pt_orig 213 | crypto_core_ed25519_add(pt, pt, pt); // pt = 8 * pt_orig 214 | } 215 | 216 | /* Convert a VRF proof pi into a VRF output hash beta per draft spec section 5.2. 217 | * This function does not verify the proof! For an untrusted proof, instead call 218 | * vrf_verify, which will output the hash if verification succeeds. 219 | * Returns 0 on success, -1 on failure decoding the proof. 220 | * NOTE: Unlike in the spec, and unlike the libsodium-fork implementation, here we'll reject gamma that's not in the main subgroup or is of low order 221 | */ 222 | int 223 | vrf_proof_to_hash(unsigned char beta[64], 224 | const unsigned char pi[80]) 225 | { 226 | unsigned char Gamma_point[32]; 227 | unsigned char hash_input[2+32]; 228 | 229 | /* Gamma = pi_string[0:32] */ 230 | memcpy(Gamma_point, pi, 32); 231 | /* NOTE: Unlike in the spec, and unlike the libsodium-fork implementation, here we'll reject gamma that's not in the main subgroup or is of low order */ 232 | if (!crypto_core_ed25519_is_valid_point(Gamma_point)) { 233 | return -1; 234 | } 235 | 236 | /* beta_string = Hash(suite_string || three_string || point_to_string(cofactor * Gamma)) */ 237 | hash_input[0] = SUITE; 238 | hash_input[1] = THREE; 239 | multiply_by_cofactor(Gamma_point); 240 | memcpy(hash_input+2, Gamma_point, 32); 241 | crypto_hash_sha512(beta, hash_input, sizeof hash_input); 242 | 243 | return 0; 244 | } 245 | 246 | /* Validate an untrusted public key as specified in the draft spec section 247 | * 5.6.1. 248 | * 249 | * This means check that it is not of low order and that it is canonically 250 | * encoded (i.e., y coordinate is already reduced mod p) 251 | * However, unlike in the spec, and unlike in the libsodium-fork implementation, 252 | * we _do_ check if the point is on the main subgroup. 253 | * 254 | * Returns 0 on success, -1 on failure. 255 | */ 256 | int 257 | vrf_validate_key(const unsigned char pk_string[32]) 258 | { 259 | if (!crypto_core_ed25519_is_valid_point(pk_string)) { 260 | return -1; 261 | } 262 | return 0; 263 | } 264 | 265 | /* Verify a proof per draft section 5.3. Return 0 on success, -1 on failure. 266 | * We assume Y_point has passed public key validation already. 267 | * Assuming verification succeeds, runtime does not depend on the message alpha 268 | * (but does depend on its length alphalen) 269 | * Compare to vrf_verify in libsodium-fork's verify.c 270 | * This will differ from libsodium-fork in that it rejects proofs where gamma is not on the main subgroup or is of low order whereas libsodium-fork might not. 271 | */ 272 | int 273 | verify_helper(const unsigned char Y_point[32], const unsigned char pi[80], 274 | const unsigned char *alpha, const unsigned long long alphalen) 275 | { 276 | /* Note: c fits in 16 bytes, but ge25519_scalarmult expects a 32-byte scalar. 277 | * Similarly, s_scalar fits in 32 bytes but sc25519_reduce takes in 64 bytes. */ 278 | unsigned char c_scalar[32], s_scalar[64], s_scalar_reduced[32], cprime[16]; 279 | 280 | //ge25519_p3 H_point, Gamma_point, U_point, V_point, tmp_p3_point; 281 | unsigned char H_point[32], Gamma_point[32], U_point[32], V_point[32], tmp_point[32], tmp2_point[32]; 282 | //ge25519_p1p1 tmp_p1p1_point; 283 | //ge25519_cached tmp_cached_point; 284 | 285 | // decode_proof will reject gamma not on the main subgroup -- this differs from libsodium-fork 286 | if (decode_proof(Gamma_point, c_scalar, s_scalar, pi) != 0) { 287 | return -1; 288 | } 289 | /* vrf_decode_proof writes to the first 16 bytes of c_scalar; we zero the 290 | * second 16 bytes ourselves, as ge25519_scalarmult expects a 32-byte scalar. 291 | */ 292 | memset(c_scalar+16, 0, 16); 293 | 294 | /* vrf_decode_proof sets only the first 32 bytes of s_scalar; we zero the 295 | * second 32 bytes ourselves, as sc25519_reduce expects a 64-byte scalar. 296 | * Reducing the scalar s mod q ensures the high order bit of s is 0, which 297 | * ref10's scalarmult functions require. 298 | */ 299 | memset(s_scalar+32, 0, 32); 300 | crypto_core_ed25519_scalar_reduce(s_scalar_reduced, s_scalar); 301 | 302 | // Y_point is assumed to have already passed public key validation, so we know it's canonical. 303 | hash_to_curve(H_point, Y_point, alpha, alphalen); 304 | 305 | /* calculate U = s*B - c*Y */ 306 | /* Note: libsodium docs say that crypto_scalarmult_ed25519_noclamp 307 | * expects a point on the main subgroup and a scalar where 0 < scalar < L 308 | * (and likewise for the _base variant); they return -1 otherwise. 309 | * Y and Gamma are both known to be on the main subgroup, but s or c 310 | * could be 0. (s is reduced and c has the top 16 bits clear so neither can be >= L). 311 | * Fortunately, it appears that as of libsodium 1.0.18 the functions do 312 | * correctly give the identity if 0 is passed in for the scalar. 313 | */ 314 | crypto_scalarmult_ed25519_noclamp(tmp_point, c_scalar, Y_point); /* tmp_point = c*Y */ 315 | crypto_scalarmult_ed25519_base_noclamp(tmp2_point, s_scalar_reduced); /* tmp2_point = s*B */ 316 | crypto_core_ed25519_sub(U_point, tmp2_point, tmp_point); /* U = tmp2_point - tmp_point = s*B - c*Y */ 317 | 318 | /* calculate V = s*H - c*Gamma */ 319 | /* See the comment above for an explanation of the special handling 320 | * when c and s are 0 */ 321 | crypto_scalarmult_ed25519_noclamp(tmp_point, c_scalar, Gamma_point); /* tmp_point = c*Gamma */ 322 | crypto_scalarmult_ed25519_noclamp(tmp2_point, s_scalar_reduced, H_point); /* tmp2_point = s*H */ 323 | crypto_core_ed25519_sub(V_point, tmp2_point, tmp_point); /* V = tmp2_point - tmp_point = s*H - c*Gamma */ 324 | 325 | hash_points(cprime, H_point, Gamma_point, U_point, V_point); 326 | return sodium_memcmp(c_scalar, cprime, 16); 327 | } 328 | 329 | /* Verify a VRF proof (for a given a public key and message) and validate the 330 | * public key. If verification succeeds, store the VRF output hash in output[]. 331 | * Specified in draft spec section 5.3. 332 | * 333 | * This will differ from libsodium-fork in that it rejects proofs where gamma is not on the main subgroup or is of low order whereas libsodium-fork might not. 334 | * 335 | * For a given public key and message, there are many possible proofs but only 336 | * one possible output hash. 337 | * 338 | * Returns 0 if verification succeeds (and stores output hash in output[]), 339 | * nonzero on failure. 340 | */ 341 | int 342 | vrf_verify(unsigned char output[64], 343 | const unsigned char pk[32], 344 | const unsigned char proof[32], 345 | const unsigned char *msg, const unsigned long long msglen) 346 | { 347 | if ((vrf_validate_key(pk) == 0) && (verify_helper(pk, proof, msg, msglen) == 0)) { 348 | return vrf_proof_to_hash(output, proof); 349 | } else { 350 | return -1; 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /libsodium-vanilla-wrapper/vrf.h: -------------------------------------------------------------------------------- 1 | // See vrf.c for documentation 2 | int vrf_prove(unsigned char proof[80], const unsigned char skpk[64], const unsigned char *msg, unsigned long long msglen); 3 | int vrf_verify(unsigned char output[64], const unsigned char pk[32], const unsigned char proof[80], const unsigned char *msg, unsigned long long msglen); 4 | 5 | int vrf_proof_to_hash(unsigned char hash[64], const unsigned char proof[80]); // Doesn't verify the proof; always use vrf_verify instead (unless the proof is one you just created yourself with vrf_prove) 6 | -------------------------------------------------------------------------------- /python/debug.py: -------------------------------------------------------------------------------- 1 | import vrf 2 | import sys 3 | if len(sys.argv) < 2: 4 | print("Usage: %s {hex-encoded sk}" % sys.argv[0]) 5 | exit(1) 6 | sk = sys.argv[1].decode('hex') 7 | pk = vrf.publickey(sk) 8 | print("pk = %s" % pk.encode('hex')) 9 | print("sk = %s" % sk.encode('hex')) 10 | proof = vrf.vrf_prove(sk, "hello") 11 | print("prove(%s) = %s" % ("hello".encode('hex'), proof.encode('hex'))) 12 | print("verify:") 13 | y = vrf.validate_pk(pk) 14 | print(vrf.vrf_fullverify(pk, proof, "hello").encode('hex')) 15 | -------------------------------------------------------------------------------- /python/ed25519.py: -------------------------------------------------------------------------------- 1 | # The following is the reference python ed25519 implementation (from https://ed25519.cr.yp.to/software.html) 2 | # It is slow and does not include protection against side-channel attacks. I'm using it to generate test vectors. 3 | 4 | import hashlib 5 | 6 | b = 256 7 | q = 2**255 - 19 8 | l = 2**252 + 27742317777372353535851937790883648493 9 | 10 | def H(m): 11 | return hashlib.sha512(m).digest() 12 | 13 | def expmod(b,e,m): 14 | if e == 0: return 1 15 | t = expmod(b,e/2,m)**2 % m 16 | if e & 1: t = (t*b) % m 17 | return t 18 | 19 | def inv(x): 20 | return expmod(x,q-2,q) 21 | 22 | d = -121665 * inv(121666) 23 | I = expmod(2,(q-1)/4,q) 24 | 25 | def xrecover(y): 26 | xx = (y*y-1) * inv(d*y*y+1) 27 | x = expmod(xx,(q+3)/8,q) 28 | if (x*x - xx) % q != 0: x = (x*I) % q 29 | if x % 2 != 0: x = q-x 30 | return x 31 | 32 | By = 4 * inv(5) 33 | Bx = xrecover(By) 34 | B = [Bx % q,By % q] 35 | 36 | def edwards(P,Q): 37 | x1 = P[0] 38 | y1 = P[1] 39 | x2 = Q[0] 40 | y2 = Q[1] 41 | x3 = (x1*y2+x2*y1) * inv(1+d*x1*x2*y1*y2) 42 | y3 = (y1*y2+x1*x2) * inv(1-d*x1*x2*y1*y2) 43 | return [x3 % q,y3 % q] 44 | 45 | def scalarmult(P,e): 46 | if e == 0: return [0,1] 47 | Q = scalarmult(P,e/2) 48 | Q = edwards(Q,Q) 49 | if e & 1: Q = edwards(Q,P) 50 | return Q 51 | 52 | def encodeint(y): 53 | bits = [(y >> i) & 1 for i in range(b)] 54 | return ''.join([chr(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b/8)]) 55 | 56 | def encodepoint(P): 57 | x = P[0] 58 | y = P[1] 59 | bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1] 60 | return ''.join([chr(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b/8)]) 61 | 62 | def bit(h,i): 63 | return (ord(h[i/8]) >> (i%8)) & 1 64 | 65 | def publickey(sk): 66 | h = H(sk) 67 | a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2)) 68 | A = scalarmult(B,a) 69 | return encodepoint(A) 70 | 71 | def Hint(m): 72 | h = H(m) 73 | return sum(2**i * bit(h,i) for i in range(2*b)) 74 | 75 | def signature(m,sk,pk): 76 | h = H(sk) 77 | a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2)) 78 | r = Hint(''.join([h[i] for i in range(b/8,b/4)]) + m) 79 | R = scalarmult(B,r) 80 | S = (r + Hint(encodepoint(R) + pk + m) * a) % l 81 | return encodepoint(R) + encodeint(S) 82 | 83 | def isoncurve(P): 84 | x = P[0] 85 | y = P[1] 86 | return (-x*x + y*y - 1 - d*x*x*y*y) % q == 0 87 | 88 | def decodeint(s): 89 | return sum(2**i * bit(s,i) for i in range(0,b)) 90 | 91 | def decodepoint(s): 92 | y = sum(2**i * bit(s,i) for i in range(0,b-1)) 93 | x = xrecover(y) 94 | if x & 1 != bit(s,b-1): x = q-x 95 | P = [x,y] 96 | if not isoncurve(P): raise Exception("decoding point that is not on curve") 97 | return P 98 | 99 | def checkvalid(s,m,pk): 100 | if len(s) != b/4: raise Exception("signature length is wrong") 101 | if len(pk) != b/8: raise Exception("public-key length is wrong") 102 | R = decodepoint(s[0:b/8]) 103 | A = decodepoint(pk) 104 | S = decodeint(s[b/8:b/4]) 105 | h = Hint(encodepoint(R) + pk + m) 106 | if scalarmult(B,S) != edwards(R,scalarmult(A,h)): 107 | raise Exception("signature does not pass verification") 108 | -------------------------------------------------------------------------------- /python/test.py: -------------------------------------------------------------------------------- 1 | import vrf 2 | 3 | # Test vectors come from the draft spec 4 | testvectors = [ 5 | { 6 | "sk": "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", 7 | "pk": "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a", 8 | "alpha": "", 9 | "x": "307c83864f2833cb427a2ef1c00a013cfdff2768d980c0a3a520f006904de94f", 10 | "r": "9ddd071cd5837e591a3a40c57a46701bb7f49b1b53c670d490c2766a08fa6e3d", 11 | "w": "c7b5d6239e52a473a2b57a92825e0e5de4656e349bb198de5afd6a76e5a07066", 12 | "e": -1, 13 | "H": "1c5672d919cc0a800970cd7e05cb36ed27ed354c33519948e5a9eaf89aee12b7", 14 | "k": "868b56b8b3faf5fc7e276ff0a65aaa896aa927294d768d0966277d94599b7afe4a6330770da5fdc2875121e0cbecbffbd4ea5e491eb35be53fa7511d9f5a61f2", 15 | "U": "c4743a22340131a2323174bfc397a6585cbe0cc521bfad09f34b11dd4bcf5936", 16 | "V": "e309cf5272f0af2f54d9dc4a6bad6998a9d097264e17ae6fce2b25dcbdd10e8b", 17 | "pi": "b6b4699f87d56126c9117a7da55bd0085246f4c56dbc95d20172612e9d38e8d7ca65e573a126ed88d4e30a46f80a666854d675cf3ba81de0de043c3774f061560f55edc256a787afe701677c0f602900", 18 | "beta": "5b49b554d05c0cd5a5325376b3387de59d924fd1e13ded44648ab33c21349a603f25b84ec5ed887995b33da5e3bfcb87cd2f64521c4c62cf825cffabbe5d31cc", 19 | }, 20 | { 21 | "sk": "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb", 22 | "pk": "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", 23 | "alpha": "72", 24 | "x": "68bd9ed75882d52815a97585caf4790a7f6c6b3b7f821c5e259a24b02e502e51", 25 | "r": "92181bd612695e464049590eb1f9746750d6057441789c9759af8308ac77fd4a", 26 | "w": "7ff6d8b773bfbae57b2ab9d49f9d3cb7d9af40a03d3ed3c6beaaf2d486b1fe6e", 27 | "e": 1, 28 | "H": "86725262c971bf064168bca2a87f593d425a49835bd52beb9f52ea59352d80fa", 29 | "k": "fd919e9d43c61203c4cd948cdaea0ad4488060db105d25b8fb4a5da2bd40e4b8330ca44a0538cc275ac7d568686660ccfd6323c805b917e91e28a4ab352b9575", 30 | "U": "04b1ba4d8129f0d4cec522b0fd0dff84283401df791dcc9b93a219c51cf27324", 31 | "V": "ca8a97ce1947d2a0aaa280f03153388fa7aa754eedfca2b4a7ad405707599ba5", 32 | "pi": "ae5b66bdf04b4c010bfe32b2fc126ead2107b697634f6f7337b9bff8785ee111200095ece87dde4dbe87343f6df3b107d91798c8a7eb1245d3bb9c5aafb093358c13e6ae1111a55717e895fd15f99f07", 33 | "beta": "94f4487e1b2fec954309ef1289ecb2e15043a2461ecc7b2ae7d4470607ef82eb1cfa97d84991fe4a7bfdfd715606bc27e2967a6c557cfb5875879b671740b7d8", 34 | }, 35 | { 36 | "sk": "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7", 37 | "pk": "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025", 38 | "alpha": "af82", 39 | "x": "909a8b755ed902849023a55b15c23d11ba4d7f4ec5c2f51b1325a181991ea95c", 40 | "r": "dcd7cda88d6798599e07216de5a48a27dcd1cde197ab39ccaf6a906ae6b25c7f", 41 | "w": "2ceaa2c2ff3028c34f9fbe076ff99520b925f18d652285b4daad5ccc467e523b", 42 | "e": -1, 43 | "H": "9d8663faeb6ab14a239bfc652648b34f783c2e99f758c0e1b6f4f863f9419b56", 44 | "k": "8f675784cdc984effc459e1054f8d386050ec400dc09d08d2372c6fe0850eaaa50defd02d965b79930dcbca5ba9222a3d99510411894e63f66bbd5d13d25db4b", 45 | "U": "d6f8a95a4ce86812e3e50febd9d48196b3bc5d1d9fa7b6dfa33072641b45d029", 46 | "V": "f77cd4ce0b49b386e80c3ce404185f93bb07463600dc14c31b0a09beaff4d592", 47 | "pi": "dfa2cba34b611cc8c833a6ea83b8eb1bb5e2ef2dd1b0c481bc42ff36ae7847f6ab52b976cfd5def172fa412defde270c8b8bdfbaae1c7ece17d9833b1bcf31064fff78ef493f820055b561ece45e1009", 48 | "beta": "2031837f582cd17a9af9e0c7ef5a6540e3453ed894b62c293686ca3c1e319dde9d0aa489a4b59a9594fc2328bc3deff3c8a0929a369a72b1180a596e016b5ded", 49 | }, 50 | ] 51 | 52 | for tv in testvectors: 53 | for k in tv: 54 | if k != "e": 55 | tv[k] = tv[k].decode("hex") 56 | 57 | for tv in testvectors: 58 | sk = tv["sk"] 59 | myx, mypk = vrf.sk_to_privpub(sk) 60 | assert vrf.ec2osp(mypk) == tv["pk"] 61 | assert vrf.encodeint(myx) == tv["x"] 62 | mypi = vrf.vrf_prove(sk, tv["alpha"]) 63 | mybeta = vrf.vrf_proof2hash(mypi) 64 | assert mypi == tv["pi"] 65 | assert mybeta == tv["beta"] 66 | assert vrf.vrf_fullverify(tv["pk"], tv["pi"], tv["alpha"]) == tv["beta"] 67 | print("OK!") 68 | -------------------------------------------------------------------------------- /python/testvectors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # Generate some VRF test vectors of the form: 3 | # {seed, pk, proof, hash, msg} 4 | 5 | import vrf 6 | import os 7 | 8 | def make_testvector(msglen): 9 | sk = os.urandom(32) 10 | msg = os.urandom(msglen) 11 | #_, pk = vrf.sk_to_privpub(sk) 12 | pk = vrf.publickey(sk) 13 | proof = vrf.vrf_prove(sk, msg) 14 | hash = vrf.vrf_fullverify(pk, proof, msg) 15 | return (sk, pk, proof, hash, msg) 16 | 17 | # format a test vector as C source to be included in a test 18 | def format_testvector(sk, pk, proof, hash, msg): 19 | tobytearray = lambda s : '{' + ','.join('0x%02x' % ord(c) for c in s) + '}' 20 | tobytestring = lambda s : '"' + ''.join('\\x%02x' % ord(c) for c in s) + '"' 21 | return "{%s,%s,%s,%s,%s}" % (tobytearray(sk), tobytearray(pk), tobytearray(proof), tobytearray(hash), tobytestring(msg)) 22 | 23 | if __name__ == '__main__': 24 | for i in range(10): 25 | print(format_testvector(*make_testvector(i)) + ",") 26 | -------------------------------------------------------------------------------- /python/vrf.py: -------------------------------------------------------------------------------- 1 | # Usage: 2 | # use vrf_fullverify and vrf_prove 3 | # 4 | from ed25519 import * 5 | 6 | SUITE = chr(0x04) # ECVRF-ED25519-SHA512-Elligator2 7 | 8 | def os2ecp(s): 9 | y = sum(2**i * bit(s,i) for i in range(0,b-1)) 10 | if y >= q: 11 | raise Exception("point is non-canonically encoded") 12 | return decodepoint(s) 13 | 14 | def ec2osp(P): 15 | return encodepoint([P[0]%q, P[1]%q]) 16 | 17 | def validate_pk(pk): 18 | y = os2ecp(pk) 19 | y8 = scalarmult(y,8) 20 | if y8[0] % q == 0 and y8[1] % q == 0: 21 | raise Exception("public key is a low-order point") 22 | return y 23 | 24 | def decode_proof(pi): 25 | assert len(pi) == 80 26 | gamma = os2ecp(pi[0:32]) 27 | c = decodeint(pi[32:48] + 16*chr(0)) 28 | s = decodeint(pi[48:80]) 29 | return (gamma, c, s) 30 | 31 | def hash_points(*args): 32 | hashinput = SUITE + chr(0x02) 33 | for P_i in args: 34 | hashinput += ec2osp(P_i) 35 | c1 = H(hashinput) 36 | c2 = c1[:16] 37 | c = decodeint(c2 + 16*chr(0)) 38 | return c 39 | 40 | def hash_to_curve_try_and_increment(y, alpha): 41 | ctr = 0 42 | pk = ec2osp(y) 43 | one = chr(0x01) 44 | h = "invalid" 45 | while h == "invalid" or (h[0] % q == 0 and h[1] % q == 1): 46 | CTR = chr((ctr >> 24)%256) + chr((ctr>>16) % 256) + chr((ctr>>8) % 256) + chr(ctr % 256) # big endian 47 | ctr += 1 48 | attempted_hash = H(SUITE + one + pk + alpha + CTR)[0:32] 49 | try: 50 | h = os2ecp(attempted_hash) 51 | h = scalarmult(h, 8) 52 | except: 53 | h = "invalid" 54 | return h 55 | 56 | def hash_to_curve_elligator2(y, alpha): 57 | A = 486662 58 | pk = ec2osp(y) 59 | one = chr(0x01) 60 | hash_ = H(SUITE + one + pk + alpha) 61 | truncated_h_string = hash_[0:32] 62 | #x_0 = (ord(r[31]) >> 7) & 1 # deviating from spec by using the highest bit of r[31] instead of the lowest bit of r[32] 63 | truncated_h_string = truncated_h_string[0:31] + chr(ord(truncated_h_string[31]) & 127) # clear high-order bit of octet 31 64 | r = decodeint(truncated_h_string) 65 | u = (-A * inv(1 + 2*expmod(r, 2, q))) % q 66 | v = (u * (u*u + A*u + 1)) % q 67 | e = expmod(v, (q-1)/2, q) 68 | finalu = u if e == 1 else (- A - u) % q 69 | y = ((finalu - 1) * inv(finalu + 1)) % q 70 | 71 | h_string = encodeint(y) 72 | H_prelim = decodepoint(h_string) 73 | 74 | #x = xrecover(y) 75 | #if x & 1 != x_0: x = q-x 76 | #h = [x,y] 77 | h = scalarmult(H_prelim, 8) 78 | assert isoncurve(h) 79 | return h 80 | 81 | def vrf_verify(y, pi, alpha): 82 | gamma, c, s = decode_proof(pi) 83 | #h = hash_to_curve_try_and_increment(y, alpha) 84 | h = hash_to_curve_elligator2(y, alpha) 85 | 86 | gs = scalarmult(B,s) 87 | yc = scalarmult(y,c) 88 | ycinv = [q-yc[0], yc[1]] 89 | u = edwards(gs, ycinv) 90 | 91 | hs = scalarmult(h,s) 92 | gammac = scalarmult(gamma,c) 93 | gammacinv = [q-gammac[0], gammac[1]] 94 | v = edwards(hs, gammacinv) 95 | 96 | cprime = hash_points(h, gamma, u, v) 97 | return (cprime == c) 98 | 99 | def sk_to_privpub(sk): 100 | h = H(sk) 101 | a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2)) 102 | return (a, scalarmult(B,a)) 103 | 104 | def nonce_generation(sk, h1): 105 | prefix = H(sk)[32:64] 106 | r = Hint(prefix + h1) % l 107 | return r 108 | 109 | def vrf_prove(sk, alpha): 110 | x, y = sk_to_privpub(sk) 111 | #h = hash_to_curve_try_and_increment(y, alpha) 112 | h = hash_to_curve_elligator2(y, alpha) 113 | gamma = scalarmult(h, x) 114 | k = nonce_generation(sk, ec2osp(h)) 115 | c = hash_points(h, gamma, scalarmult(B,k), scalarmult(h,k)) 116 | s = (k + c * x) % l 117 | return ec2osp(gamma) + encodeint(c)[0:16] + encodeint(s) 118 | 119 | def vrf_proof2hash(pi): 120 | gamma = decode_proof(pi)[0] 121 | return H(SUITE + chr(0x03) + ec2osp(scalarmult(gamma, 8))) 122 | 123 | def vrf_fullverify(pk, pi, alpha): 124 | y = validate_pk(pk) 125 | if vrf_verify(y, pi, alpha): 126 | return vrf_proof2hash(pi) 127 | else: 128 | raise Exception("proof is incorrect") 129 | --------------------------------------------------------------------------------