diff --git a/crypto/internal/ed25519/edwards25519/edwards25519.go b/crypto/internal/ed25519/edwards25519/edwards25519.go index 71949d1..eb38c0a 100644 --- a/crypto/internal/ed25519/edwards25519/edwards25519.go +++ b/crypto/internal/ed25519/edwards25519/edwards25519.go @@ -7,8 +7,6 @@ // http://ed25519.cr.yp.to/. package edwards25519 -import "math" - // This code is a port of the public domain, "ref10" implementation of ed25519 // from SUPERCOP. @@ -861,6 +859,22 @@ func FeSquare2(h, f *FieldElement) { h[9] = int32(h9) } +func FeSqrt(out, a *FieldElement) { + var exp, b, b2, bi, i FieldElement + + i = SqrtM1 + FePow22523(&exp, a) /* b = a^(q-5)/8 */ + FeMul(&b, a, &exp) /* b = a * a^(q-5)/8 */ + FeSquare(&b2, &b) /* b^2 = a * a^(q-1)/4 */ + + /* note b^4 == a^2, so b^2 == a or -a + * if b^2 != a, multiply it by sqrt(-1) */ + FeMul(&bi, &b, &i) + FeCMove(&b, &bi, int32(1^FeIsequal(b2, *a))) + + FeCopy(out, &b) +} + func FeInvert(out, z *FieldElement) { var t0, t1, t2, t3 FieldElement var i int @@ -916,7 +930,7 @@ func FeInvert(out, z *FieldElement) { FeMul(out, &t1, &t0) // 254..5,3,1,0 } -func fePow22523(out, z *FieldElement) { +func FePow22523(out, z *FieldElement) { var t0, t1, t2 FieldElement var i int @@ -977,6 +991,28 @@ func fePow22523(out, z *FieldElement) { FeMul(out, &t0, z) } +func FeIsequal(f, g FieldElement) int { + var h FieldElement + FeSub(&h, &f, &g) + return 1 ^ (1 & (feIsNonzero(h) >> 8)) +} + +func feIsNonzero(f FieldElement) int { + var s [32]byte + FeToBytes(&s, &f) + var zero [32]byte + + return FeCompare(s, zero) +} + +func FeCompare(x, y [32]byte) int { + d := 0 + for i := 0; i < 32; i++ { + d |= int(x[i]) ^ int(y[i]) + } + return (1 & ((d - 1) >> 8)) - 1 +} + // Group elements are members of the elliptic curve -x^2 + y^2 = 1 + d * x^2 * // y^2 where d = -121665/121666. // @@ -1055,6 +1091,13 @@ func (p *ExtendedGroupElement) Double(r *CompletedGroupElement) { q.Double(r) } +func GeNeg(r *ExtendedGroupElement, p ExtendedGroupElement) { + FeNeg(&r.X, &p.X) + FeCopy(&r.Y, &p.Y) + FeCopy(&r.Z, &p.Z) + FeNeg(&r.T, &p.T) +} + func GeDouble(r, p *ExtendedGroupElement) { var q ProjectiveGroupElement p.ToProjective(&q) @@ -1128,10 +1171,11 @@ func (p *ExtendedGroupElement) FromBytesBaseGroup(s *[32]byte) bool { func (p *ExtendedGroupElement) FromBytes(s *[32]byte) bool { FeFromBytes(&p.Y, s) - return p.FromParityAndY(s[31]>>7, &p.Y) + return p.FromParityAndY((s[31] >> 7), &p.Y) } func (p *ExtendedGroupElement) FromParityAndY(bit byte, y *FieldElement) bool { + // compare to ge_frombytes_negate_vartime var u, v, v3, vxx, check FieldElement FeCopy(&p.Y, y) @@ -1147,7 +1191,7 @@ func (p *ExtendedGroupElement) FromParityAndY(bit byte, y *FieldElement) bool { FeMul(&p.X, &p.X, &v) FeMul(&p.X, &p.X, &u) // x = uv^7 - fePow22523(&p.X, &p.X) // x = (uv^7)^((q-5)/8) + FePow22523(&p.X, &p.X) // x = (uv^7)^((q-5)/8) FeMul(&p.X, &p.X, &v3) FeMul(&p.X, &p.X, &u) // x = uv^3(uv^7)^((q-5)/8) @@ -1168,7 +1212,6 @@ func (p *ExtendedGroupElement) FromParityAndY(bit byte, y *FieldElement) bool { tmp2[31-i] = v } } - if FeIsNegative(&p.X) != bit { FeNeg(&p.X, &p.X) } @@ -1327,6 +1370,16 @@ func GeScalarMult(r *ExtendedGroupElement, a *[32]byte, A *ExtendedGroupElement) ExtendedGroupElementCopy(r, &q) } +// GeIsNeutral +// returns 1 if p is the neutral point +// returns 0 otherwise +func GeIsNeutral(p *ExtendedGroupElement) bool { + var zero FieldElement + FeZero(&zero) + // Check if p == neutral element == (0, 1) + return FeIsequal(p.X, zero)&FeIsequal(p.Y, p.Z) == 1 +} + // GeDoubleScalarMultVartime sets r = a*A + b*B // where a = a[0]+256*a[1]+...+256^31 a[31]. // and b = b[0]+256*b[1]+...+256^31 b[31]. @@ -1382,6 +1435,23 @@ func GeDoubleScalarMultVartime(r *ProjectiveGroupElement, a *[32]byte, A *Extend } } +func GeToMontX(u *FieldElement, ed *ExtendedGroupElement) { + /* + u = (y + 1) / (1 - y) + or + u = (y + z) / (z - y) + + NOTE: y=1 is converted to u=0 since fe_invert is mod-exp + */ + var yPlusOne, oneMinusY, invOneMinusY FieldElement + + FeAdd(&yPlusOne, &ed.Y, &ed.Z) + FeSub(&oneMinusY, &ed.Z, &ed.Y) + FeInvert(&invOneMinusY, &oneMinusY) + FeMul(u, &yPlusOne, &invOneMinusY) + +} + // equal returns 1 if b == c and 0 otherwise. func equal(b, c int32) int32 { x := uint32(b ^ c) @@ -1902,29 +1972,17 @@ func ScMulAdd(s, a, b, c *[32]byte) { s[31] = byte(s11 >> 17) } -// Input: -// s[0]+256*s[1]+...+256^63*s[63] = s -// s <= l -// -// Output: -// s[0]+256*s[1]+...+256^31*s[31] = l - s -// where l = 2^252 + 27742317777372353535851937790883648493. -func ScNeg(r, s *[32]byte) { - l := [32]byte{237, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16} - var carry int32 - for i := 0; i < 32; i++ { - carry = carry + int32(l[i]) - int32(s[i]) - negative := carry & math.MinInt32 // extract the sign bit (min=0b100...) - negative |= negative >> 16 - negative |= negative >> 8 - negative |= negative >> 4 - negative |= negative >> 2 - negative |= negative >> 1 - carry += negative & 256 // +=256 if negative, unmodified otherwise - r[i] = byte(carry) - // carry for next iteration - carry = negative & (-1) // -1 if negative, 0 otherwise - } +var lMinus1 = [32]byte{0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, + 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10} + +// ScNeg computes: +// b = -a (mod l) +// where l = 2^252 + 27742317777372353535851937790883648493. +func ScNeg(b, a *[32]byte) { + var zero [32]byte + ScMulAdd(b, &lMinus1, a, &zero) } // Input: @@ -2250,3 +2308,27 @@ func ScReduce(out *[32]byte, s *[64]byte) { out[30] = byte(s11 >> 9) out[31] = byte(s11 >> 17) } + +// ScCMove is equivalent to FeCMove but operates directly on the [32]byte +// representation instead on the FieldElement. Can be used to spare a +// FieldElement.FromBytes operation. +func ScCMove(f, g *[32]byte, b int32) { + var x [32]byte + for i := range x { + x[i] = (f[i] ^ g[i]) + } + b = -b + for i := range x { + x[i] &= byte(b) + } + for i := range f { + f[i] ^= x[i] + } +} + +// ScClamp Sets and clears bits to make a random 32 bytes into a private key +func ScClamp(a *[32]byte) { + a[0] &= 248 + a[31] &= 127 + a[31] |= 64 +} diff --git a/crypto/internal/ed25519/extra25519/extra25519.go b/crypto/internal/ed25519/extra25519/extra25519.go index 33dde2b..07ba881 100644 --- a/crypto/internal/ed25519/extra25519/extra25519.go +++ b/crypto/internal/ed25519/extra25519/extra25519.go @@ -347,7 +347,44 @@ func representativeToMontgomeryX(v, rr2 *edwards25519.FieldElement) { edwards25519.FeSub(v, v, &v2) } -func montgomeryXToEdwardsY(out, x *edwards25519.FieldElement) { +func FeMontRhs(v2, u *edwards25519.FieldElement) { + var u2, Au, inner edwards25519.FieldElement + var one edwards25519.FieldElement + edwards25519.FeOne(&one) + + edwards25519.FeSquare(&u2, u) /* u^2 */ + edwards25519.FeMul(&Au, &edwards25519.A, u) /* Au */ + edwards25519.FeAdd(&inner, &u2, &Au) /* u^2 + Au */ + edwards25519.FeAdd(&inner, &inner, &one) /* u^2 + Au + 1 */ + edwards25519.FeMul(v2, u, &inner) /* u(u^2 + Au + 1) */ +} + +func legendreIsNonsquare(in edwards25519.FieldElement) int32 { + var temp edwards25519.FieldElement + edwards25519.FePow22523(&temp, &in) /* temp = in^((q-5)/8) */ + edwards25519.FeSquare(&temp, &temp) /* in^((q-5)/4) */ + edwards25519.FeSquare(&temp, &temp) /* in^((q-5)/2) */ + edwards25519.FeMul(&temp, &temp, &in) /* in^((q-3)/2) */ + edwards25519.FeMul(&temp, &temp, &in) /* in^((q-1)/2) */ + + /* temp is now the Legendre symbol: + * 1 = square + * 0 = input is zero + * -1 = nonsquare + */ + var b [32]byte + edwards25519.FeToBytes(&b, &temp) + //fmt.Println(hex.Dump(b[:])) + return int32(1 & b[31]) +} + +// compare to fe_montx_to_edy +func FeMontgomeryXToEdwardsY(out, x *edwards25519.FieldElement) { + /* + y = (u - 1) / (u + 1) + + NOTE: u=-1 is converted to y=0 since fe_invert is mod-exp + */ var t, tt edwards25519.FieldElement edwards25519.FeOne(&t) edwards25519.FeAdd(&tt, x, &t) // u+1 @@ -356,6 +393,38 @@ func montgomeryXToEdwardsY(out, x *edwards25519.FieldElement) { edwards25519.FeMul(out, &tt, &t) // (u-1)/(u+1) } +func elligator(u *edwards25519.FieldElement, r edwards25519.FieldElement) { + /* r = input + * x = -A/(1+2r^2) # 2 is nonsquare + * e = (x^3 + Ax^2 + x)^((q-1)/2) # legendre symbol + * if e == 1 (square) or e == 0 (because x == 0 and 2r^2 + 1 == 0) + * u = x + * if e == -1 (nonsquare) + * u = -x - A + */ + var A, one, twor2, twor2plus1, twor2plus1inv edwards25519.FieldElement + var x, e, Atemp, uneg edwards25519.FieldElement + A = edwards25519.A /* A = 486662 */ + edwards25519.FeOne(&one) + + edwards25519.FeSquare2(&twor2, &r) /* 2r^2 */ + edwards25519.FeAdd(&twor2plus1, &twor2, &one) /* 1+2r^2 */ + edwards25519.FeInvert(&twor2plus1inv, &twor2plus1) /* 1/(1+2r^2) */ + edwards25519.FeMul(&x, &twor2plus1inv, &A) /* A/(1+2r^2) */ + edwards25519.FeNeg(&x, &x) /* x = -A/(1+2r^2) */ + + FeMontRhs(&e, &x) /* e = x^3 + Ax^2 + x */ + + nonsquare := legendreIsNonsquare(e) + + edwards25519.FeZero(&Atemp) + + edwards25519.FeCMove(&Atemp, &A, nonsquare) /* 0, or A if nonsquare */ + edwards25519.FeAdd(u, &x, &Atemp) /* x, or x+A if nonsquare */ + edwards25519.FeNeg(&uneg, u) /* -x, or -x-A if nonsquare */ + edwards25519.FeCMove(u, &uneg, nonsquare) /* x, or -x-A if nonsquare */ +} + // HashToEdwards converts a 256-bit hash output into a point on the Edwards // curve isomorphic to Curve25519 in a manner that preserves // collision-resistance. The returned curve points are NOT indistinguishable @@ -369,8 +438,57 @@ func HashToEdwards(out *edwards25519.ExtendedGroupElement, h *[32]byte) { hh[31] &= 127 edwards25519.FeFromBytes(&out.Y, &hh) representativeToMontgomeryX(&out.X, &out.Y) - montgomeryXToEdwardsY(&out.Y, &out.X) + FeMontgomeryXToEdwardsY(&out.Y, &out.X) if ok := out.FromParityAndY(bit, &out.Y); !ok { panic("HashToEdwards: point not on curve") } } + +func HashToPoint(m []byte) *edwards25519.ExtendedGroupElement { + // H(n) = (f(h(n))^8) + var hmb [32]byte + h64 := sha512.Sum512(m) + copy(hmb[:], h64[:32]) + var hm edwards25519.ExtendedGroupElement + HashToEdwards(&hm, &hmb) + edwards25519.GeDouble(&hm, &hm) + edwards25519.GeDouble(&hm, &hm) + edwards25519.GeDouble(&hm, &hm) + return &hm +} + +/* sqrt(-(A+2)) */ +var a_bytes = [32]byte{ + 0x06, 0x7e, 0x45, 0xff, 0xaa, 0x04, 0x6e, 0xcc, + 0x82, 0x1a, 0x7d, 0x4b, 0xd1, 0xd3, 0xa1, 0xc5, + 0x7e, 0x4f, 0xfc, 0x03, 0xdc, 0x08, 0x7b, 0xd2, + 0xbb, 0x06, 0xa0, 0x60, 0xf4, 0xed, 0x26, 0x0f, +} + +func GeMontXtoExtendedGroupElement(p *edwards25519.ExtendedGroupElement, u edwards25519.FieldElement, edSignBit byte) { + var x, y, v, A, v2, iv, nx edwards25519.FieldElement + + edwards25519.FeFromBytes(&A, &a_bytes) + + /* given u, recover edwards y */ + /* given u, recover v */ + /* given u and v, recover edwards x */ + + FeMontgomeryXToEdwardsY(&y, &u) /* y = (u - 1) / (u + 1) */ + + FeMontRhs(&v2, &u) /* v^2 = u(u^2 + Au + 1) */ + + edwards25519.FeSqrt(&v, &v2) /* v = sqrt(v^2) */ + + edwards25519.FeMul(&x, &u, &A) /* x = u * sqrt(-(A+2)) */ + edwards25519.FeInvert(&iv, &v) /* 1/v */ + edwards25519.FeMul(&x, &x, &iv) /* x = (u/v) * sqrt(-(A+2)) */ + + edwards25519.FeNeg(&nx, &x) /* negate x to match sign bit */ + edwards25519.FeCMove(&x, &nx, int32(edwards25519.FeIsNegative(&x)^edSignBit)) + + edwards25519.FeCopy(&(p.X), &x) + edwards25519.FeCopy(&(p.Y), &y) + edwards25519.FeOne(&(p.Z)) + edwards25519.FeMul(&(p.T), &(p.X), &(p.Y)) +} diff --git a/crypto/internal/ed25519/extra25519/extra25519_test.go b/crypto/internal/ed25519/extra25519/extra25519_test.go index 2f9c309..5f1bbdb 100644 --- a/crypto/internal/ed25519/extra25519/extra25519_test.go +++ b/crypto/internal/ed25519/extra25519/extra25519_test.go @@ -10,6 +10,8 @@ import ( "crypto/sha512" "testing" + "encoding/hex" + "fmt" "github.com/coniks-sys/coniks-go/crypto/internal/ed25519/edwards25519" "golang.org/x/crypto/curve25519" "golang.org/x/crypto/ed25519" @@ -122,3 +124,138 @@ func BenchmarkMap(b *testing.B) { RepresentativeToPublicKey(&publicKey, &representative) } } + +// copied test-vectors from: +// https://github.com/WhisperSystems/curve25519-java/blob/master/android/jni/ed25519/tests/tests.c +var sha512_correct_output = [64]byte{ + 0x8E, 0x95, 0x9B, 0x75, 0xDA, 0xE3, 0x13, 0xDA, + 0x8C, 0xF4, 0xF7, 0x28, 0x14, 0xFC, 0x14, 0x3F, + 0x8F, 0x77, 0x79, 0xC6, 0xEB, 0x9F, 0x7F, 0xA1, + 0x72, 0x99, 0xAE, 0xAD, 0xB6, 0x88, 0x90, 0x18, + 0x50, 0x1D, 0x28, 0x9E, 0x49, 0x00, 0xF7, 0xE4, + 0x33, 0x1B, 0x99, 0xDE, 0xC4, 0xB5, 0x43, 0x3A, + 0xC7, 0xD3, 0x29, 0xEE, 0xB6, 0xDD, 0x26, 0x54, + 0x5E, 0x96, 0xE5, 0x5B, 0x87, 0x4B, 0xE9, 0x09, +} + +var elligator_correct_output = [32]byte{ + 0x5f, 0x35, 0x20, 0x00, 0x1c, 0x6c, 0x99, 0x36, + 0xa3, 0x12, 0x06, 0xaf, 0xe7, 0xc7, 0xac, 0x22, + 0x4e, 0x88, 0x61, 0x61, 0x9b, 0xf9, 0x88, 0x72, + 0x44, 0x49, 0x15, 0x89, 0x9d, 0x95, 0xf4, 0x6e, +} + +var hashtopoint_correct_output1 = [32]byte{ + 0xce, 0x89, 0x9f, 0xb2, 0x8f, 0xf7, 0x20, 0x91, + 0x5e, 0x14, 0xf5, 0xb7, 0x99, 0x08, 0xab, 0x17, + 0xaa, 0x2e, 0xe2, 0x45, 0xb4, 0xfc, 0x2b, 0xf6, + 0x06, 0x36, 0x29, 0x40, 0xed, 0x7d, 0xe7, 0xed, +} + +var hashtopoint_correct_output2 = [32]byte{ + 0xa0, 0x35, 0xbb, 0xa9, 0x4d, 0x30, 0x55, 0x33, + 0x0d, 0xce, 0xc2, 0x7f, 0x83, 0xde, 0x79, 0xd0, + 0x89, 0x67, 0x72, 0x4c, 0x07, 0x8d, 0x68, 0x9d, + 0x61, 0x52, 0x1d, 0xf9, 0x2c, 0x5c, 0xba, 0x77, +} + +var calculatev_correct_output = [32]byte{ + 0x1b, 0x77, 0xb5, 0xa0, 0x44, 0x84, 0x7e, 0xb9, + 0x23, 0xd7, 0x93, 0x18, 0xce, 0xc2, 0xc5, 0xe2, + 0x84, 0xd5, 0x79, 0x6f, 0x65, 0x63, 0x1b, 0x60, + 0x9b, 0xf1, 0xf8, 0xce, 0x88, 0x0b, 0x50, 0x9c, +} + +func TestElligatorFast(t *testing.T) { + var b [32]byte + for count := 0; count < 32; count++ { + b[count] = byte(count) + } + var in edwards25519.FieldElement + var out edwards25519.FieldElement + edwards25519.FeFromBytes(&in, &b) + + elligator(&out, in) + + var b2 [32]byte + edwards25519.FeToBytes(&b2, &out) + //TEST("Elligator vector", memcmp(bytes, elligator_correct_output, 32) == 0); + if !bytes.Equal(b2[:], elligator_correct_output[:]) { + t.Fatal("Elligator test vector faile") + } + + /* Elligator(0) == 0 test */ + edwards25519.FeZero(&in) + elligator(&out, in) + + var bi, bo [32]byte + edwards25519.FeToBytes(&bi, &in) + edwards25519.FeToBytes(&bo, &out) + + if !bytes.Equal(bi[:], bo[:]) { + t.Error("Elligator(0) != 0") + } + + /* ge_montx_to_p3(0) -> order2 point test */ + var one, negone, zero edwards25519.FieldElement + edwards25519.FeOne(&one) + edwards25519.FeZero(&zero) + edwards25519.FeSub(&negone, &zero, &one) + var p3 edwards25519.ExtendedGroupElement + GeMontXtoExtendedGroupElement(&p3, zero, 0) + if !(edwards25519.FeIsequal(p3.X, zero) == 1 && + edwards25519.FeIsequal(p3.Y, negone) == 1 && + edwards25519.FeIsequal(p3.Z, one) == 1 && + edwards25519.FeIsequal(p3.T, zero) == 1) { + t.Fatal("ge_montx_to_p3(0) isn't a order 2 point") + } + + /* Hash to point vector test */ + var htp [32]byte + for count := 0; count < 32; count++ { + htp[count] = byte(count) + } + + p3 = *HashToPoint(htp[:]) + + p3.ToBytes(&htp) + if !bytes.Equal(htp[:], hashtopoint_correct_output1[:]) { + fmt.Println(hex.Dump(htp[:])) + fmt.Println(hex.Dump(hashtopoint_correct_output1[:])) + t.Fatal("hash_to_point #1 failed") + } + + for count := 0; count < 32; count++ { + htp[count] = byte(count + 1) + } + + p3 = *HashToPoint(htp[:]) + p3.ToBytes(&htp) + //TEST("hash_to_point #2", memcmp(htp, hashtopoint_correct_output2, 32) == 0); + if !bytes.Equal(htp[:], hashtopoint_correct_output2[:]) { + fmt.Println(hex.Dump(htp[:])) + fmt.Println(hex.Dump(hashtopoint_correct_output2[:])) + t.Fatal("hash_to_point #2 failed") + } + + /* TODO move to VRF package: calculate_V vector test */ + //Bv := edwards25519.ExtendedGroupElement{} + //var V [32]byte + //Vbuf := make([]byte, 200) + //var a [32]byte + //var A [32]byte + //Vmsg := []byte{0x00, 0x01, 0x02} + // + //for count:=0; count < 32; count++ { + // a[count] = byte(8 + count) + // A[count] = byte(9 + count) + //} + //// sc_clamp(a): + //a[0] &= 248; a[31] &= 127; a[31] |= 64 + //_ = calculateBvAndV(&V, Vbuf, a, A, Vmsg) + //if !bytes.Equal(V[:], calculatev_correct_output[:]) { + // fmt.Println(hex.Dump(V[:])) + // fmt.Println(hex.Dump(calculatev_correct_output[:])) + // t.Fatal("calculate_Bv_and_V vector failed") + //} +} diff --git a/crypto/vrf/vrf.go b/crypto/vrf/vrf.go index eb753d1..65f1b85 100644 --- a/crypto/vrf/vrf.go +++ b/crypto/vrf/vrf.go @@ -1,7 +1,7 @@ // Package vrf implements a verifiable random function using the Edwards form // of Curve25519, SHA3 and the Elligator map. // -// E is Curve25519 (in Edwards coordinates), h is SHA3. +// E is Curve25519 (in Edwards coordinates), h is SHA512. // f is the elligator map (bytes->E) that covers half of E. // 8 is the cofactor of E, the group order is 8*l for prime l. // Setup : the prover publicly commits to a public key (P : E) @@ -18,16 +18,12 @@ package vrf import ( - "bytes" "crypto/rand" - "errors" "io" - "golang.org/x/crypto/sha3" + "bytes" "github.com/coniks-sys/coniks-go/crypto/internal/ed25519/edwards25519" - "github.com/coniks-sys/coniks-go/crypto/internal/ed25519/extra25519" - "golang.org/x/crypto/ed25519" ) const ( @@ -38,178 +34,109 @@ const ( ProofSize = 32 + 32 + intermediateSize ) -var ( - ErrGetPubKey = errors.New("[vrf] Couldn't get corresponding public-key from private-key") -) +// PrivateKey represents a Curve25519 private key. +type PrivateKey [PrivateKeySize]byte -type PrivateKey []byte -type PublicKey []byte +// PublicKey represents a Curve25519 private key. +type PublicKey [PublicKeySize]byte -// GenerateKey creates a public/private key pair using rnd for randomness. +// GenerateKey creates a Curve25519 public/private key pair using rnd for +// randomness. +// Only the private key sk is returned (call sk.Public() the get the +// corresponding public key). // If rnd is nil, crypto/rand is used. func GenerateKey(rnd io.Reader) (sk PrivateKey, err error) { if rnd == nil { rnd = rand.Reader } - sk = make([]byte, 64) _, err = io.ReadFull(rnd, sk[:32]) if err != nil { return } - x, _ := sk.expandSecret() - - var pkP edwards25519.ExtendedGroupElement - edwards25519.GeScalarMultBase(&pkP, x) + sk[0] &= 248 + sk[31] &= 127 + sk[31] |= 64 + + var ed edwards25519.ExtendedGroupElement + var u edwards25519.FieldElement + var x [32]byte + copy(x[:], sk[:]) + + // cache the public-key: + edwards25519.GeScalarMultBase(&ed, &x) + edwards25519.GeToMontX(&u, &ed) var pkBytes [PublicKeySize]byte - pkP.ToBytes(&pkBytes) - + edwards25519.FeToBytes(&pkBytes, &u) copy(sk[32:], pkBytes[:]) + return } // Public extracts the public VRF key from the underlying private-key -// and returns a boolean indicating if the operation was successful. -func (sk PrivateKey) Public() (PublicKey, bool) { - pk, ok := ed25519.PrivateKey(sk).Public().(ed25519.PublicKey) - return PublicKey(pk), ok -} - -func (sk PrivateKey) expandSecret() (x, skhr *[32]byte) { - x, skhr = new([32]byte), new([32]byte) - hash := sha3.NewShake256() - hash.Write(sk[:32]) - hash.Read(x[:]) - hash.Read(skhr[:]) - x[0] &= 248 - x[31] &= 127 - x[31] |= 64 +func (sk PrivateKey) Public() (publicKey PublicKey) { + publicKeyB := new([PublicKeySize]byte) + copy(publicKeyB[:], sk[32:]) + publicKey = PublicKey(*publicKeyB) return } // Compute generates the vrf value for the byte slice m using the // underlying private key sk. func (sk PrivateKey) Compute(m []byte) []byte { - x, _ := sk.expandSecret() - var ii edwards25519.ExtendedGroupElement - var iiB [32]byte - edwards25519.GeScalarMult(&ii, x, hashToCurve(m)) - ii.ToBytes(&iiB) - - hash := sha3.NewShake256() - hash.Write(iiB[:]) // const length: Size - hash.Write(m) - var vrf [Size]byte - hash.Read(vrf[:]) - return vrf[:] -} - -func hashToCurve(m []byte) *edwards25519.ExtendedGroupElement { - // H(n) = (f(h(n))^8) - var hmb [32]byte - sha3.ShakeSum256(hmb[:], m) - var hm edwards25519.ExtendedGroupElement - extra25519.HashToEdwards(&hm, &hmb) - edwards25519.GeDouble(&hm, &hm) - edwards25519.GeDouble(&hm, &hm) - edwards25519.GeDouble(&hm, &hm) - return &hm + var a, aNeg, A [32]byte + copy(a[:], sk[:32]) + // copy(uB[:], sk[32:64]) + + // XXX use the cached public key instead: + var x [32]byte + copy(x[:], sk[:]) + var edPubKey edwards25519.ExtendedGroupElement + edwards25519.GeScalarMultBase(&edPubKey, &x) + edPubKey.ToBytes(&A) + + // Force Edwards sign bit to zero + //copy(A[:], []byte(pkBytes)) + signBit := (A[31] & 0x80) >> 7 + copy(a[:], sk[:32]) + edwards25519.ScNeg(&aNeg, &a) + edwards25519.ScCMove(&a, &aNeg, int32(signBit)) + A[31] &= 0x7F + + _, Vbytes := calculateBvAndV(a, A, m) + vrfB := computeVrfFromV(*Vbytes) + return vrfB[:] } -// Prove returns the vrf value and a proof such that +// Sign returns the vrf value and a proof such that // Verify(m, vrf, proof) == true. The vrf value is the // same as returned by Compute(m). +func (sk PrivateKey) Sign(m []byte) (signature []byte) { + signature = sk.signInternal(m, nil) + return +} + +// Prove returns the vrf value and a proof such that Verify(pk, m, vrf, proof) +// == true. The vrf value is the same as returned by Compute(m, sk). func (sk PrivateKey) Prove(m []byte) (vrf, proof []byte) { - x, skhr := sk.expandSecret() - var cH, rH [64]byte - var r, c, minusC, t, grB, hrB, iiB [32]byte - var ii, gr, hr edwards25519.ExtendedGroupElement - - hm := hashToCurve(m) - edwards25519.GeScalarMult(&ii, x, hm) - ii.ToBytes(&iiB) - - hash := sha3.NewShake256() - hash.Write(skhr[:]) - hash.Write(sk[32:]) // public key, as in ed25519 - hash.Write(m) - hash.Read(rH[:]) - hash.Reset() - edwards25519.ScReduce(&r, &rH) - - edwards25519.GeScalarMultBase(&gr, &r) - edwards25519.GeScalarMult(&hr, &r, hm) - gr.ToBytes(&grB) - hr.ToBytes(&hrB) - - hash.Write(grB[:]) - hash.Write(hrB[:]) - hash.Write(m) - hash.Read(cH[:]) - hash.Reset() - edwards25519.ScReduce(&c, &cH) - - edwards25519.ScNeg(&minusC, &c) - edwards25519.ScMulAdd(&t, x, &minusC, &r) - - proof = make([]byte, ProofSize) - copy(proof[:32], c[:]) - copy(proof[32:64], t[:]) - copy(proof[64:96], iiB[:]) - - hash.Write(iiB[:]) // const length: Size - hash.Write(m) - vrf = make([]byte, Size) - hash.Read(vrf[:]) + proof = sk.Sign(m) + var V [32]byte + copy(V[:], proof[:32]) + v := computeVrfFromV(V) + vrf = make([]byte, 32) + copy(vrf, v[:]) return } // Verify returns true iff vrf=Compute(m) for the sk that // corresponds to pk. -func (pkBytes PublicKey) Verify(m, vrfBytes, proof []byte) bool { - if len(proof) != ProofSize || len(vrfBytes) != Size || len(pkBytes) != PublicKeySize { +func (pk PublicKey) Verify(m, vrfBytes, signature []byte) bool { + if len(vrfBytes) != Size { return false } - var pk, c, cRef, t, vrf, iiB, ABytes, BBytes [32]byte - copy(vrf[:], vrfBytes) - copy(pk[:], pkBytes[:]) - copy(c[:32], proof[:32]) - copy(t[:32], proof[32:64]) - copy(iiB[:], proof[64:96]) - - hash := sha3.NewShake256() - hash.Write(iiB[:]) // const length - hash.Write(m) - var hCheck [Size]byte - hash.Read(hCheck[:]) - if !bytes.Equal(hCheck[:], vrf[:]) { - return false - } - hash.Reset() + if ok, vrf := pk.verifyInteral(m, signature); ok && + bytes.Equal(vrfBytes, vrf[:]) { + return true - var P, B, ii, iic edwards25519.ExtendedGroupElement - var A, hmtP, iicP edwards25519.ProjectiveGroupElement - if !P.FromBytesBaseGroup(&pk) { - return false - } - if !ii.FromBytesBaseGroup(&iiB) { - return false } - edwards25519.GeDoubleScalarMultVartime(&A, &c, &P, &t) - A.ToBytes(&ABytes) - - hm := hashToCurve(m) - edwards25519.GeDoubleScalarMultVartime(&hmtP, &t, hm, &[32]byte{}) - edwards25519.GeDoubleScalarMultVartime(&iicP, &c, &ii, &[32]byte{}) - iicP.ToExtended(&iic) - hmtP.ToExtended(&B) - edwards25519.GeAdd(&B, &B, &iic) - B.ToBytes(&BBytes) - - var cH [64]byte - hash.Write(ABytes[:]) // const length - hash.Write(BBytes[:]) // const length - hash.Write(m) - hash.Read(cH[:]) - edwards25519.ScReduce(&cRef, &cH) - return cRef == c + return false } diff --git a/crypto/vrf/vrf_test.go b/crypto/vrf/vrf_test.go index 73a8021..6ff99c4 100644 --- a/crypto/vrf/vrf_test.go +++ b/crypto/vrf/vrf_test.go @@ -3,7 +3,8 @@ package vrf import ( "bytes" "testing" - // "fmt" + + "github.com/coniks-sys/coniks-go/crypto/internal/ed25519/extra25519" ) func TestHonestComplete(t *testing.T) { @@ -11,7 +12,7 @@ func TestHonestComplete(t *testing.T) { if err != nil { t.Fatal(err) } - pk, _ := sk.Public() + pk := sk.Public() alice := []byte("alice") aliceVRF := sk.Compute(alice) aliceVRFFromProof, aliceProof := sk.Prove(alice) @@ -22,11 +23,11 @@ func TestHonestComplete(t *testing.T) { // fmt.Printf("aliceVRF: %X\n", aliceVRF) // fmt.Printf("aliceProof: %X\n", aliceProof) - if !pk.Verify(alice, aliceVRF, aliceProof) { - t.Errorf("Gen -> Compute -> Prove -> Verify -> FALSE") + if ok, _ := pk.verifyInteral(alice, aliceProof); !ok { + t.Error("Gen -> Sign -> Verify -> FALSE") } - if !bytes.Equal(aliceVRF, aliceVRFFromProof) { - t.Errorf("Compute != Prove") + if !bytes.Equal(aliceVRF[:], aliceVRFFromProof) { + t.Error("Compute != Prove") } } @@ -36,11 +37,8 @@ func TestConvertPrivateKeyToPublicKey(t *testing.T) { t.Fatal(err) } - pk, ok := sk.Public() - if !ok { - t.Fatal("Couldn't obtain public key.") - } - if !bytes.Equal(sk[32:], pk) { + pk := sk.Public() + if !bytes.Equal(sk[32:], pk[:]) { t.Fatal("Raw byte respresentation doesn't match public key.") } } @@ -50,85 +48,25 @@ func TestFlipBitForgery(t *testing.T) { if err != nil { t.Fatal(err) } - pk, _ := sk.Public() + pk := sk.Public() alice := []byte("alice") for i := 0; i < 32; i++ { for j := uint(0); j < 8; j++ { aliceVRF := sk.Compute(alice) aliceVRF[i] ^= 1 << j _, aliceProof := sk.Prove(alice) - if pk.Verify(alice, aliceVRF, aliceProof) { + if pk.Verify(alice, aliceVRF[:], aliceProof) { t.Fatalf("forged by using aliceVRF[%d]^=%d:\n (sk=%x)", i, j, sk) } } } } -func sampleVectorTest(pk PublicKey, aliceVRF, aliceProof []byte, t *testing.T) { - alice := []byte{97, 108, 105, 99, 101} - - // Positive test case - if !pk.Verify(alice, aliceVRF, aliceProof) { - t.Errorf("TestSampleVectors HonestVector Failed") - } - - // Negative test cases - try increment the first byte of every vector - pk[0]++ - if pk.Verify(alice, aliceVRF, aliceProof) { - t.Errorf("TestSampleVectors ForgedVector (pk modified) Passed") - } - pk[0]-- - - alice[0]++ - if pk.Verify(alice, aliceVRF, aliceProof) { - t.Errorf("TestSampleVectors ForgedVector (alice modified) Passed") - } - alice[0]-- - - aliceVRF[0]++ - if pk.Verify(alice, aliceVRF, aliceProof) { - t.Errorf("TestSampleVectors ForgedVector (aliceVRF modified) Passed") - } - aliceVRF[0]-- - - aliceProof[0]++ - if pk.Verify(alice, aliceVRF, aliceProof) { - t.Errorf("TestSampleVectors ForgedVector (aliceProof modified) Passed") - } - aliceProof[0]-- -} - -func TestSampleVectorSets(t *testing.T) { - - var aliceVRF, aliceProof []byte - var pk []byte - - // Following sets of test vectors are collected from TestHonestComplete(), - // and are used for testing the JS implementation of vrf.verify() - // Reference: https://github.com/yahoo/end-to-end/pull/58 - - pk = []byte{194, 191, 96, 139, 106, 249, 24, 253, 198, 131, 88, 169, 100, 231, 7, 211, 70, 171, 171, 207, 24, 30, 150, 114, 77, 124, 240, 123, 191, 14, 29, 111} - aliceVRF = []byte{68, 98, 55, 78, 153, 189, 11, 15, 8, 238, 132, 5, 53, 28, 232, 22, 222, 98, 21, 139, 89, 67, 111, 197, 213, 75, 86, 226, 178, 71, 245, 159} - aliceProof = []byte{49, 128, 4, 253, 103, 241, 164, 51, 21, 45, 168, 55, 18, 103, 22, 233, 245, 136, 242, 238, 113, 218, 160, 122, 129, 89, 72, 103, 250, 222, 3, 3, 239, 235, 93, 98, 173, 115, 168, 24, 222, 165, 186, 224, 138, 76, 201, 237, 130, 201, 47, 18, 191, 24, 61, 80, 113, 139, 246, 233, 23, 94, 177, 12, 193, 106, 38, 172, 66, 192, 22, 188, 177, 14, 144, 100, 38, 179, 96, 70, 55, 157, 80, 139, 145, 62, 94, 195, 181, 224, 183, 42, 64, 66, 145, 162} - sampleVectorTest(pk, aliceVRF, aliceProof, t) - - pk = []byte{133, 36, 180, 21, 60, 103, 35, 92, 204, 245, 236, 174, 242, 50, 212, 69, 124, 230, 1, 106, 94, 95, 201, 55, 208, 252, 195, 13, 12, 96, 87, 170} - aliceVRF = []byte{35, 127, 188, 177, 246, 242, 213, 46, 16, 72, 1, 196, 69, 181, 160, 204, 69, 230, 17, 147, 251, 207, 203, 184, 154, 122, 118, 10, 144, 76, 229, 234} - aliceProof = []byte{253, 33, 80, 241, 250, 172, 198, 28, 16, 171, 161, 194, 110, 175, 158, 233, 250, 89, 35, 174, 221, 101, 98, 136, 32, 191, 82, 127, 92, 208, 199, 10, 123, 46, 70, 95, 56, 102, 63, 137, 53, 160, 128, 216, 134, 152, 87, 58, 19, 244, 167, 108, 144, 13, 97, 232, 207, 75, 107, 57, 193, 124, 231, 5, 242, 122, 182, 247, 155, 187, 86, 165, 114, 46, 188, 52, 21, 121, 238, 100, 85, 32, 119, 116, 250, 208, 32, 60, 145, 53, 145, 76, 84, 153, 185, 28} - sampleVectorTest(pk, aliceVRF, aliceProof, t) - - pk = []byte{85, 126, 176, 228, 114, 43, 110, 223, 111, 129, 204, 38, 215, 110, 165, 148, 223, 232, 79, 254, 150, 107, 61, 29, 216, 14, 238, 104, 55, 163, 121, 185} - aliceVRF = []byte{171, 240, 42, 215, 128, 5, 247, 64, 164, 154, 198, 231, 6, 174, 207, 10, 95, 231, 117, 189, 88, 103, 72, 229, 43, 218, 184, 162, 44, 183, 196, 159} - aliceProof = []byte{99, 103, 243, 119, 251, 30, 21, 57, 69, 162, 192, 80, 7, 49, 244, 136, 13, 252, 150, 165, 215, 181, 55, 203, 141, 124, 197, 36, 20, 183, 239, 14, 238, 213, 240, 96, 181, 187, 24, 137, 152, 152, 38, 186, 80, 141, 72, 15, 209, 178, 60, 205, 22, 31, 101, 185, 225, 159, 22, 118, 84, 179, 95, 0, 124, 140, 237, 187, 8, 77, 233, 213, 207, 211, 251, 153, 71, 112, 61, 89, 53, 26, 195, 167, 254, 73, 218, 135, 145, 89, 12, 4, 16, 255, 63, 89} - sampleVectorTest(pk, aliceVRF, aliceProof, t) - -} - func BenchmarkHashToGE(b *testing.B) { alice := []byte("alice") b.ResetTimer() for n := 0; n < b.N; n++ { - hashToCurve(alice) + extra25519.HashToPoint(alice) } } @@ -152,7 +90,7 @@ func BenchmarkProve(b *testing.B) { alice := []byte("alice") b.ResetTimer() for n := 0; n < b.N; n++ { - sk.Prove(alice) + sk.signInternal(alice, nil) } } @@ -162,11 +100,10 @@ func BenchmarkVerify(b *testing.B) { b.Fatal(err) } alice := []byte("alice") - aliceVRF := sk.Compute(alice) - _, aliceProof := sk.Prove(alice) - pk, _ := sk.Public() + aliceProof := sk.signInternal(alice, nil) + pk := sk.Public() b.ResetTimer() for n := 0; n < b.N; n++ { - pk.Verify(alice, aliceVRF, aliceProof) + pk.verifyInteral(alice, aliceProof) } } diff --git a/crypto/vrf/vxeddsa.go b/crypto/vrf/vxeddsa.go new file mode 100644 index 0000000..65cf0e9 --- /dev/null +++ b/crypto/vrf/vxeddsa.go @@ -0,0 +1,268 @@ +package vrf + +import ( + "bytes" + "crypto/rand" + "crypto/sha512" + "io" + + "github.com/coniks-sys/coniks-go/crypto/internal/ed25519/edwards25519" + "github.com/coniks-sys/coniks-go/crypto/internal/ed25519/extra25519" +) + +// This code is a port of the public domain, VXEdDSA C implementation by Trevor +// Perrin / Open Whisper Systems. Specification: +// https://whispersystems.org/docs/specifications/xeddsa/#vxeddsa + +func calculateBv(A [32]byte, msg []byte) (Bv *edwards25519.ExtendedGroupElement) { + /* Calculate SHA512(label(2) || A || msg) */ + buf := make([]byte, 32) + buf[0] = 0xFD + for count := 1; count < 32; count++ { + buf[count] = 0xFF + } + buffer := bytes.NewBuffer(buf) + buffer.Write(A[:]) + buffer.Write(msg) + Bv = extra25519.HashToPoint(buffer.Bytes()) + + return +} + +func (sk PrivateKey) signInternal(m []byte, randr io.Reader) (signature []byte) { + if randr == nil { + randr = rand.Reader + } + + // TODO check for maxMsgLen + var a, aNeg, rB [32]byte + var R edwards25519.ExtendedGroupElement + var x [32]byte + copy(x[:], sk[:]) + var edPubKey edwards25519.ExtendedGroupElement + edwards25519.GeScalarMultBase(&edPubKey, &x) + var A [32]byte + edPubKey.ToBytes(&A) + + // Force Edwards sign bit to zero + signBit := (A[31] & 0x80) >> 7 + copy(a[:], sk[:32]) + edwards25519.ScNeg(&aNeg, &a) + edwards25519.ScCMove(&a, &aNeg, int32(signBit)) + + A[31] &= 0x7F + Bv, V := calculateBvAndV(a, A, m) + + /* r = SHA512(label(3) || a || V || random(64)) */ + var Rv edwards25519.ExtendedGroupElement + buf := make([]byte, 160) + buf[0] = 0xFC + for i := 1; i < 32; i++ { + buf[i] = 0xFF + } + copy(buf[32:], a[:]) + copy(buf[64:], V[:]) + random := make([]byte, 64) + if _, err := randr.Read(random); err != nil { + panic("Couldn't read from random") + } + copy(buf[96:], random) + + rH := sha512.Sum512(buf[:160]) + + edwards25519.ScReduce(&rB, &rH) + edwards25519.GeScalarMultBase(&R, &rB) + edwards25519.GeScalarMult(&Rv, &rB, Bv) + + /* h = SHA512(label(4) || A || V || R || Rv || M) */ + var Rb [32]byte + R.ToBytes(&Rb) + var RvB [32]byte + Rv.ToBytes(&RvB) + buf = append(buf, m...) + buf[0] = 0xFB + for i := 1; i < 32; i++ { + buf[i] = 0xFF + } + copy(buf[32:], A[:]) + copy(buf[64:], V[:]) + copy(buf[96:], Rb[:]) + copy(buf[128:], RvB[:]) + //copy(buf[:160], m) + hB := sha512.Sum512(buf[:160+len(m)]) + var h [32]byte + edwards25519.ScReduce(&h, &hB) + var s [32]byte + edwards25519.ScMulAdd(&s, &h, &a, &rB) + + signature = make([]byte, ProofSize) + copy(signature[:32], V[:]) + copy(signature[32:64], h[:]) + copy(signature[64:96], s[:]) + + return +} + +func (pkB PublicKey) verifyInteral(m, signature []byte) (bool, [32]byte) { + var vrf [32]byte + if len(signature) != ProofSize || len(pkB) != PublicKeySize { + return false, vrf + } + // TODO check for max. message length + var u edwards25519.FieldElement + pubKey := [32]byte(pkB) + edwards25519.FeFromBytes(&u, &pubKey) + var strict [32]byte + edwards25519.FeToBytes(&strict, &u) + if !(edwards25519.FeCompare(strict, pubKey) == 0) { + return false, vrf + } + var y edwards25519.FieldElement + extra25519.FeMontgomeryXToEdwardsY(&y, &u) + var edPubKey [32]byte + edwards25519.FeToBytes(&edPubKey, &y) + Bv := calculateBv(edPubKey, m) + + // verifybuf = V || h || s || m + verifBuf := make([]byte, len(m)+160) + copy(verifBuf, signature[:96]) + copy(verifBuf[96:], m) + + if verifBuf[63]&224 == 1 { + return false, vrf + } + if verifBuf[95]&224 == 1 { + return false, vrf + } + + // Load -A: + var minusA edwards25519.ExtendedGroupElement + edwards25519.FeFromBytes(&minusA.Y, &edPubKey) + if !minusA.FromParityAndY((edPubKey[31]>>7)^0x01, &minusA.Y) { + return false, vrf + } + + // Load -V + var minusV edwards25519.ExtendedGroupElement + var Vb [32]byte + copy(Vb[:], signature[:32]) + edwards25519.FeFromBytes(&minusV.Y, &Vb) + if !minusV.FromParityAndY((Vb[31]>>7)^0x01, &minusV.Y) { + return false, vrf + } + + // Load h, s + var h, s [32]byte + copy(h[:], verifBuf[32:64]) + copy(s[:], verifBuf[64:96]) + if h[31]&224 == 1 { + return false, vrf + } /* strict parsing of h */ + if s[31]&224 == 1 { + return false, vrf + } /* strict parsing of s */ + + var A, cA, V, cV edwards25519.ExtendedGroupElement + edwards25519.GeNeg(&A, minusA) + edwards25519.GeNeg(&V, minusV) + + edwards25519.GeDouble(&cA, &A) + edwards25519.GeDouble(&cA, &cA) + edwards25519.GeDouble(&cA, &cA) + + edwards25519.GeDouble(&cV, &V) + edwards25519.GeDouble(&cV, &cV) + edwards25519.GeDouble(&cV, &cV) + + if edwards25519.GeIsNeutral(&cA) || edwards25519.GeIsNeutral(&cV) || + edwards25519.GeIsNeutral(Bv) { + return false, vrf + } + + // R = (s*B) + (h * -A)) + var R edwards25519.ProjectiveGroupElement + edwards25519.GeDoubleScalarMultVartime(&R, &h, &minusA, &s) + + // s * Bv + var sBv edwards25519.ExtendedGroupElement + edwards25519.GeScalarMult(&sBv, &s, Bv) + + // h * -V + var hMinusV edwards25519.ExtendedGroupElement + edwards25519.GeScalarMult(&hMinusV, &h, &minusV) + + // Rv = (sc * Bv) + (hc * (-V)) + var Rv edwards25519.ExtendedGroupElement + edwards25519.GeAdd(&Rv, &sBv, &hMinusV) + + // Check h == SHA512(label(4) || A || V || R || Rv || M) + var VBytes, RBytes, RVBytes [32]byte + V.ToBytes(&VBytes) + R.ToBytes(&RBytes) + Rv.ToBytes(&RVBytes) + + vrfBuf := make([]byte, 160+len(m)) + vrfBuf[0] = 0xFB // label 4 + for count := 1; count < 32; count++ { + vrfBuf[count] = 0xFF + } + + //copy(vrfBuf, vrfBuf)) + copy(vrfBuf[32:], edPubKey[:]) + copy(vrfBuf[64:], VBytes[:]) + copy(vrfBuf[96:], RBytes[:]) + copy(vrfBuf[128:], RVBytes[:]) + copy(vrfBuf[160:], verifBuf[96:96+len(m)]) + + hCheck := sha512.Sum512(vrfBuf[:160+len(m)]) + + var hCheckReduced [32]byte + edwards25519.ScReduce(&hCheckReduced, &hCheck) + + if edwards25519.FeCompare(hCheckReduced, h) == 0 { + // compute VRF from cV: + var cVBytes [32]byte + cV.ToBytes(&cVBytes) + vrfBuf[0] = 0xFA // label 5 + copy(vrfBuf[32:], cVBytes[:]) + vrfOutput := sha512.Sum512(vrfBuf[:64]) + copy(vrf[:], vrfOutput[:32]) + // vrf = cV || hash_5(cV) (mod 2^b) + return true, vrf + } + + return false, vrf +} + +func calculateBvAndV(a, Abytes [32]byte, + msg []byte) (Bv *edwards25519.ExtendedGroupElement, V *[32]byte) { + p3 := edwards25519.ExtendedGroupElement{} + + Bv = calculateBv(Abytes, msg) + edwards25519.GeScalarMult(&p3, &a, Bv) + V = new([32]byte) + p3.ToBytes(V) + return +} + +func computeVrfFromV(Vbytes [32]byte) (vrf [32]byte) { + var V, cV edwards25519.ExtendedGroupElement + V.FromBytes(&Vbytes) + + edwards25519.GeDouble(&cV, &V) + edwards25519.GeDouble(&cV, &cV) + edwards25519.GeDouble(&cV, &cV) + buffer := make([]byte, 32) + buffer[0] = 0xFA // label 5 + for count := 1; count < 32; count++ { + buffer[count] = 0xFF + } + var cVBytes [32]byte + cV.ToBytes(&cVBytes) + buf := bytes.NewBuffer(buffer) + buf.Write(cVBytes[:]) + + hash := sha512.Sum512(buf.Bytes()) + copy(vrf[:], hash[:32]) + return +} diff --git a/crypto/vrf/vxeddsa_test.go b/crypto/vrf/vxeddsa_test.go new file mode 100644 index 0000000..2be768b --- /dev/null +++ b/crypto/vrf/vxeddsa_test.go @@ -0,0 +1,184 @@ +package vrf + +import ( + "bytes" + "crypto/sha512" + "testing" + + "github.com/coniks-sys/coniks-go/crypto/internal/ed25519/edwards25519" +) + +func TestVxed25519Vectors(t *testing.T) { + signature_correct := [96]byte{ + 0x23, 0xc6, 0xe5, 0x93, 0x3f, 0xcd, 0x56, 0x47, + 0x7a, 0x86, 0xc9, 0x9b, 0x76, 0x2c, 0xb5, 0x24, + 0xc3, 0xd6, 0x05, 0x55, 0x38, 0x83, 0x4d, 0x4f, + 0x8d, 0xb8, 0xf0, 0x31, 0x07, 0xec, 0xeb, 0xa0, + 0xa0, 0x01, 0x50, 0xb8, 0x4c, 0xbb, 0x8c, 0xcd, + 0x23, 0xdc, 0x65, 0xfd, 0x0e, 0x81, 0xb2, 0x86, + 0x06, 0xa5, 0x6b, 0x0c, 0x4f, 0x53, 0x6d, 0xc8, + 0x8b, 0x8d, 0xc9, 0x04, 0x6e, 0x4a, 0xeb, 0x08, + 0xce, 0x08, 0x71, 0xfc, 0xc7, 0x00, 0x09, 0xa4, + 0xd6, 0xc0, 0xfd, 0x2d, 0x1a, 0xe5, 0xb6, 0xc0, + 0x7c, 0xc7, 0x22, 0x3b, 0x69, 0x59, 0xa8, 0x26, + 0x2b, 0x57, 0x78, 0xd5, 0x46, 0x0e, 0x0f, 0x05, + } + var privKey [32]byte + var vrfOutPrev, vrfOut [32]byte + privKey[8] = 189 + edwards25519.ScClamp(&privKey) + msgLen := 200 + msg := make([]byte, msgLen) + r := bytes.NewReader(privKey[:]) + sk, _ := GenerateKey(r) + + random := make([]byte, 64) + signature := sk.signInternal(msg, bytes.NewReader(random)) + if !bytes.Equal(signature, signature_correct[:]) { + t.Fatal("VXEdDSA sign failed") + } + var ok bool + if ok, vrfOut = sk.Public().verifyInteral(msg, signature); !ok { + t.Fatal("VXEdDSA verify #1 failed") + } + + copy(vrfOutPrev[:], vrfOut[:]) + signature[0] ^= 1 + if ok, _ := sk.Public().verifyInteral(msg, signature); ok { + t.Fatal("VXEdDSA verify #2 should have failed!") + } + var sigPrev [96]byte + copy(sigPrev[:], signature[:]) + sigPrev[0] ^= 1 // undo prev disturbance + + random[0] ^= 1 + signature = sk.signInternal(msg, bytes.NewReader(random)) + if ok, vrfOut = sk.Public().verifyInteral(msg, signature); !ok { + t.Fatal("VXEdDSA verify #3 failed") + } + if !bytes.Equal(vrfOut[:], vrfOutPrev[:]) { + t.Fatal("VXEdDSA VRF value has changed") + } + if bytes.Equal(signature[32:96], sigPrev[32:96]) { + t.Fatal("VXEdDSA (h, s) changed") + } +} + +func TestVxed25519VectorsSlow(t *testing.T) { + if testing.Short() { + t.Skip("Skipped slow test in short mode") + } + signature_10k_correct := [96]byte{ + 0xa1, 0x96, 0x96, 0xe5, 0x87, 0x3f, 0x6e, 0x5c, + 0x2e, 0xd3, 0x73, 0xab, 0x04, 0x0c, 0x1f, 0x26, + 0x3c, 0xca, 0x52, 0xc4, 0x7e, 0x49, 0xaa, 0xce, + 0xb5, 0xd6, 0xa2, 0x29, 0x46, 0x3f, 0x1b, 0x54, + 0x45, 0x94, 0x9b, 0x6c, 0x27, 0xf9, 0x2a, 0xed, + 0x17, 0xa4, 0x72, 0xbf, 0x35, 0x37, 0xc1, 0x90, + 0xac, 0xb3, 0xfd, 0x2d, 0xf1, 0x01, 0x05, 0xbe, + 0x56, 0x5c, 0xaf, 0x63, 0x65, 0xad, 0x38, 0x04, + 0x70, 0x53, 0xdf, 0x2b, 0xc1, 0x45, 0xc8, 0xee, + 0x02, 0x0d, 0x2b, 0x22, 0x23, 0x7a, 0xbf, 0xfa, + 0x43, 0x31, 0xb3, 0xac, 0x26, 0xd9, 0x76, 0xfc, + 0xfe, 0x30, 0xa1, 0x7c, 0xce, 0x10, 0x67, 0x0e, + } + + signature_100k_correct := [96]byte{ + 0xc9, 0x11, 0x2b, 0x55, 0xfa, 0xc4, 0xb2, 0xfe, + 0x00, 0x7d, 0xf6, 0x45, 0xcb, 0xd2, 0x73, 0xc9, + 0x43, 0xba, 0x20, 0xf6, 0x9c, 0x18, 0x84, 0xef, + 0x6c, 0x65, 0x7a, 0xdb, 0x49, 0xfc, 0x1e, 0xbe, + 0x31, 0xb3, 0xe6, 0xa4, 0x68, 0x2f, 0xd0, 0x30, + 0x81, 0xfc, 0x0d, 0xcd, 0x2d, 0x00, 0xab, 0xae, + 0x9f, 0x08, 0xf0, 0x99, 0xff, 0x9f, 0xdc, 0x2d, + 0x68, 0xd6, 0xe7, 0xe8, 0x44, 0x2a, 0x5b, 0x0e, + 0x48, 0x67, 0xe2, 0x41, 0x4a, 0xd9, 0x0c, 0x2a, + 0x2b, 0x4e, 0x66, 0x09, 0x87, 0xa0, 0x6b, 0x3b, + 0xd1, 0xd9, 0xa3, 0xe3, 0xa5, 0x69, 0xed, 0xc1, + 0x42, 0x03, 0x93, 0x0d, 0xbc, 0x7e, 0xe9, 0x08, + } + + signature_1m_correct := [96]byte{ + 0xf8, 0xb1, 0x20, 0xf2, 0x1e, 0x5c, 0xbf, 0x5f, + 0xea, 0x07, 0xcb, 0xb5, 0x77, 0xb8, 0x03, 0xbc, + 0xcb, 0x6d, 0xf1, 0xc1, 0xa5, 0x03, 0x05, 0x7b, + 0x01, 0x63, 0x9b, 0xf9, 0xed, 0x3e, 0x57, 0x47, + 0xd2, 0x5b, 0xf4, 0x7e, 0x7c, 0x45, 0xce, 0xfc, + 0x06, 0xb3, 0xf4, 0x05, 0x81, 0x9f, 0x53, 0xb0, + 0x18, 0xe3, 0xfa, 0xcb, 0xb2, 0x52, 0x3e, 0x57, + 0xcb, 0x34, 0xcc, 0x81, 0x60, 0xb9, 0x0b, 0x04, + 0x07, 0x79, 0xc0, 0x53, 0xad, 0xc4, 0x4b, 0xd0, + 0xb5, 0x7d, 0x95, 0x4e, 0xbe, 0xa5, 0x75, 0x0c, + 0xd4, 0xbf, 0xa7, 0xc0, 0xcf, 0xba, 0xe7, 0x7c, + 0xe2, 0x90, 0xef, 0x61, 0xa9, 0x29, 0x66, 0x0d, + } + + signature_10m_correct := [96]byte{ + 0xf5, 0xa4, 0xbc, 0xec, 0xc3, 0x3d, 0xd0, 0x43, + 0xd2, 0x81, 0x27, 0x9e, 0xf0, 0x4c, 0xbe, 0xf3, + 0x77, 0x01, 0x56, 0x41, 0x0e, 0xff, 0x0c, 0xb9, + 0x66, 0xec, 0x4d, 0xe0, 0xb7, 0x25, 0x63, 0x6b, + 0x5c, 0x08, 0x39, 0x80, 0x4e, 0x37, 0x1b, 0x2c, + 0x46, 0x6f, 0x86, 0x99, 0x1c, 0x4e, 0x31, 0x60, + 0xdb, 0x4c, 0xfe, 0xc5, 0xa2, 0x4d, 0x71, 0x2b, + 0xd6, 0xd0, 0xc3, 0x98, 0x88, 0xdb, 0x0e, 0x0c, + 0x68, 0x4a, 0xd3, 0xc7, 0x56, 0xac, 0x8d, 0x95, + 0x7b, 0xbd, 0x99, 0x50, 0xe8, 0xd3, 0xea, 0xf3, + 0x7b, 0x26, 0xf2, 0xa2, 0x2b, 0x02, 0x58, 0xca, + 0xbd, 0x2c, 0x2b, 0xf7, 0x77, 0x58, 0xfe, 0x09, + } + + const msgLen = 200 + var privateKey [32]byte + msg := make([]byte, msgLen) + var random [64]byte + signature := bytes.Repeat([]byte{3}, 96) + + t.Log("Pseudorandom VXEdDSA...\n") + + // up to 10000000 iterations (super slow ... ) + const iterations = 100000 + for count := 1; count <= iterations; count++ { + b := sha512.Sum512(signature[:96]) + copy(privateKey[:], b[:32]) + b = sha512.Sum512(privateKey[:32]) + copy(random[:], b[:64]) + + edwards25519.ScClamp(&privateKey) + sk, err := GenerateKey(bytes.NewReader(privateKey[:])) + if err != nil { + t.Fatalf("Couldn't generate key in %d\n", count) + } + pk := sk.Public() + signature = sk.signInternal(msg[:], bytes.NewReader(random[:])) + if ok, _ := pk.verifyInteral(msg[:], signature); !ok { + t.Fatalf("VXEdDSA verify failure #1 %d\n", count) + } + if (b[63] & 1) == 1 { + signature[count%96] ^= 1 + } else { + msg[count%msgLen] ^= 1 + } + + if count == 10000 { + if !bytes.Equal(signature, signature_10k_correct[:]) { + t.Errorf("VXEDDSA 10K doesn't match %d\n", count) + } + } + if count == 100000 { + if !bytes.Equal(signature, signature_100k_correct[:]) { + t.Errorf("VXEDDSA 100K doesn't match %d\n", count) + } + } + if count == 1000000 { + if !bytes.Equal(signature, signature_1m_correct[:]) { + t.Errorf("VXEDDSA 1m doesn't match %d\n", count) + } + } + if count == 10000000 { + if !bytes.Equal(signature, signature_10m_correct[:]) { + t.Errorf("VXEDDSA 10m doesn't match %d\n", count) + } + } + } +} diff --git a/keyserver/coniksserver/internal/cmd/init.go b/keyserver/coniksserver/internal/cmd/init.go index 94eac7c..e5c265f 100644 --- a/keyserver/coniksserver/internal/cmd/init.go +++ b/keyserver/coniksserver/internal/cmd/init.go @@ -96,12 +96,12 @@ func mkVrfKey(dir string) { log.Print(err) return } - pk, _ := sk.Public() - if err := utils.WriteFile(path.Join(dir, "vrf.priv"), sk, 0600); err != nil { + pk := sk.Public() + if err := utils.WriteFile(path.Join(dir, "vrf.priv"), sk[:], 0600); err != nil { log.Println(err) return } - if err := utils.WriteFile(path.Join(dir, "vrf.pub"), pk, 0600); err != nil { + if err := utils.WriteFile(path.Join(dir, "vrf.pub"), pk[:], 0600); err != nil { log.Println(err) return } diff --git a/keyserver/server.go b/keyserver/server.go index 2c19d5b..8f36f86 100644 --- a/keyserver/server.go +++ b/keyserver/server.go @@ -124,7 +124,7 @@ func LoadServerConfig(file string) (*ServerConfig, error) { } conf.configFilePath = file - conf.Policies.vrfKey = vrfKey + copy(conf.Policies.vrfKey[:], vrfKey) conf.Policies.signKey = signKey // also update path for TLS cert files for _, addr := range conf.Addresses { diff --git a/protocol/directory.go b/protocol/directory.go index 1bb053b..36ac71e 100644 --- a/protocol/directory.go +++ b/protocol/directory.go @@ -45,10 +45,7 @@ func NewDirectory(epDeadline Timestamp, vrfKey vrf.PrivateKey, panic("Currently the server is forced to use TBs") } d := new(ConiksDirectory) - vrfPublicKey, ok := vrfKey.Public() - if !ok { - panic(vrf.ErrGetPubKey) - } + vrfPublicKey := vrfKey.Public() d.policies = NewPolicies(epDeadline, vrfPublicKey) pad, err := merkletree.NewPAD(d.policies, signKey, vrfKey, dirSize) if err != nil { diff --git a/protocol/policy.go b/protocol/policy.go index af08544..dff63b0 100644 --- a/protocol/policy.go +++ b/protocol/policy.go @@ -44,7 +44,7 @@ func (p *Policies) Serialize() []byte { var bs []byte bs = append(bs, []byte(p.Version)...) // protocol version bs = append(bs, []byte(p.HashID)...) // cryptographic algorithms in use - bs = append(bs, p.VrfPublicKey...) // vrf public key + bs = append(bs, p.VrfPublicKey[:]...) // vrf public key bs = append(bs, utils.ULongToBytes(uint64(p.EpochDeadline))...) // epoch deadline return bs }