diff --git a/sign/ed448/ed448.go b/sign/ed448/ed448.go index bd804af84..db39918f6 100644 --- a/sign/ed448/ed448.go +++ b/sign/ed448/ed448.go @@ -13,10 +13,9 @@ import ( "errors" "fmt" "io" - "strconv" - "github.com/cloudflare/circl/ecc/goldilocks" sha3 "github.com/cloudflare/circl/internal/shake" + "github.com/cloudflare/circl/sign/ed448/internal/goldilocks" ) const ( @@ -128,7 +127,12 @@ func NewKeyFromSeed(seed PrivateKey) *KeyPair { pair := &KeyPair{} copy(pair.private[:], seed) - _ = goldilocks.Curve{}.ScalarBaseMult(s).ToBytes(pair.public[:]) + var P goldilocks.Point + P.ScalarBaseMult(s) + err := P.Encode(&pair.public) + if err != nil { + panic(err) + } return pair } @@ -167,8 +171,10 @@ func sign(kp *KeyPair, message, ctx []byte, preHash bool) ([]byte, error) { // 3. Compute the point [r]B. r := &goldilocks.Scalar{} r.FromBytes(rPM[:]) - R := (&[paramB]byte{})[:] - err := goldilocks.Curve{}.ScalarBaseMult(r).ToBytes(R) + var R goldilocks.Point + var encR [goldilocks.EncodingSize]byte + R.ScalarBaseMult(r) + err := R.Encode(&encR) // 4. Compute SHAKE256(dom4(F, C) || R || A || PH(M), 114) var hRAM [hashSize]byte @@ -176,7 +182,7 @@ func sign(kp *KeyPair, message, ctx []byte, preHash bool) ([]byte, error) { writeDom(&H, ctx, preHash) - _, _ = H.Write(R) + _, _ = H.Write(encR[:]) _, _ = H.Write(kp.public[:]) _, _ = H.Write(d) _, _ = H.Read(hRAM[:]) @@ -190,7 +196,7 @@ func sign(kp *KeyPair, message, ctx []byte, preHash bool) ([]byte, error) { // 6. The signature is the concatenation of R and S. var signature [SignatureSize]byte - copy(signature[:paramB], R[:]) + copy(signature[:paramB], encR[:]) copy(signature[paramB:], S[:]) return signature[:], err } @@ -200,7 +206,7 @@ func sign(kp *KeyPair, message, ctx []byte, preHash bool) ([]byte, error) { // also known as the pure version of EdDSA. func (kp *KeyPair) SignPure(message []byte, ctx string) ([]byte, error) { if len(ctx) > ContextMaxSize { - return nil, fmt.Errorf("ed448: bad context length: " + strconv.Itoa(len(ctx))) + return nil, fmt.Errorf("ed448: bad context length: %v", len(ctx)) } return sign(kp, message, []byte(ctx), false) @@ -213,7 +219,7 @@ func (kp *KeyPair) SignPure(message []byte, ctx string) ([]byte, error) { // 255. It can be empty. func (kp *KeyPair) SignPh(message []byte, ctx string) ([]byte, error) { if len(ctx) > ContextMaxSize { - return nil, fmt.Errorf("ed448: bad context length: " + strconv.Itoa(len(ctx))) + return nil, fmt.Errorf("ed448: bad context length: %v", len(ctx)) } return sign(kp, message, []byte(ctx), true) @@ -227,7 +233,10 @@ func verify(public PublicKey, message, signature, ctx []byte, preHash bool) bool return false } - P, err := goldilocks.FromBytes(public) + var encPublic [goldilocks.EncodingSize]byte + copy(encPublic[:], public) + P := &goldilocks.Point{} + err := P.Decode(&encPublic) if err != nil { return false } @@ -260,10 +269,15 @@ func verify(public PublicKey, message, signature, ctx []byte, preHash bool) bool S := &goldilocks.Scalar{} S.FromBytes(signature[paramB:]) - encR := (&[paramB]byte{})[:] P.Neg() - _ = goldilocks.Curve{}.CombinedMult(S, k, P).ToBytes(encR) - return bytes.Equal(R, encR) + var Q goldilocks.Point + Q.CombinedMult(S, k, P) + var encR [goldilocks.EncodingSize]byte + err = Q.Encode(&encR) + if err != nil { + return false + } + return bytes.Equal(R, encR[:]) } // Verify returns true if the signature is valid. Failure cases are invalid @@ -316,7 +330,7 @@ func deriveSecretScalar(s *goldilocks.Scalar, h []byte) { // isLessThanOrder returns true if 0 <= x < order and if the last byte of x is zero. func isLessThanOrder(x []byte) bool { - order := goldilocks.Curve{}.Order() + order := goldilocks.Order() i := len(order) - 1 for i > 0 && x[i] == order[i] { i-- diff --git a/sign/ed448/internal/goldilocks/goldilocks.go b/sign/ed448/internal/goldilocks/goldilocks.go index a8dc57bf6..93e406e61 100644 --- a/sign/ed448/internal/goldilocks/goldilocks.go +++ b/sign/ed448/internal/goldilocks/goldilocks.go @@ -1,9 +1,8 @@ -// Package goldilocks provides goldilocks curve operations required by Ed448. +// Package goldilocks provides arithmetic operations on the Goldilocks curve. // // Goldilocks Curve // -// The goldilocks curve is defined over GF(2^448-2^224-1) by the twisted Edwards -// curve +// The goldilocks curve is defined over GF(2^448-2^224-1) as // Goldilocks: ax^2+y^2 = 1 + dx^2y^2, where a=1 and d=-39081. // This curve was proposed by Hamburg (1) and is also known as edwards448 // after RFC-7748 (2). @@ -36,8 +35,6 @@ const EncodingSize = fp.Size + 1 // ErrInvalidDecoding alerts of an error during decoding a point. var ErrInvalidDecoding = errors.New("invalid decoding") -func (P *Point) Neg() { fp.Neg(&P.X, &P.X); fp.Neg(&P.Ta, &P.Ta) } - // Decode if succeeds constructs a point by decoding the first // EncodingSize bytes of data. func (P *Point) Decode(data *[EncodingSize]byte) error { @@ -66,12 +63,10 @@ func (P *Point) Decode(data *[EncodingSize]byte) error { fp.Cmov(&P.Ta, x, b) fp.Cmov(&P.Tb, y, b) fp.Cmov(&P.Z, &one, b) - - var err error if !isValid { - err = ErrInvalidDecoding + return ErrInvalidDecoding } - return err + return nil } // Encode sets data with the unique encoding of the point P. @@ -83,65 +78,60 @@ func (P *Point) Encode(data *[EncodingSize]byte) error { fp.Modp(x) fp.Modp(y) data[EncodingSize-1] = (x[0] & 1) << 7 - err := fp.ToBytes(data[:fp.Size], y) - return err + return fp.ToBytes(data[:fp.Size], y) } -// Order returns the number of points in the prime subgroup. -func Order() Scalar { return ted448.Order() } - -// ScalarBaseMult calculates Q = kG, where G is the generator of the Goldilocks curve. This function runs in constant time. -func ScalarBaseMult(Q *Point, k *Scalar) { +// ScalarBaseMult calculates P = kG, where G is the generator of the Goldilocks +// curve. This function runs in constant time. +func (P *Point) ScalarBaseMult(k *Scalar) { k4 := &Scalar{} divBy4(k4, k) - R := &ted448.Point{} - ted448.ScalarBaseMult(R, k4) - push(Q, R) + var Q ted448.Point + ted448.ScalarBaseMult(&Q, k4) + push(P, &Q) } -// CombinedMult calculates Q = mG+nP, where G is the generator of the Goldilocks curve. This function does NOT run in constant time. -func CombinedMult(Q *Point, m, n *Scalar, P *Point) { +// CombinedMult calculates P = mG+nQ, where G is the generator of the Goldilocks +// curve. This function does NOT run in constant time as is only used for +// signature verification. +func (P *Point) CombinedMult(m, n *Scalar, Q *Point) { m4, n4 := &Scalar{}, &Scalar{} divBy4(m4, m) divBy4(n4, n) - phiP := &ted448.Point{} - R := &ted448.Point{} - pull(phiP, P) - ted448.CombinedMult(R, m4, n4, phiP) - push(Q, R) + var R, phiQ ted448.Point + pull(&phiQ, Q) + ted448.CombinedMult(&R, m4, n4, &phiQ) + push(P, &R) } -// pull sends a point on the Goldilocks curve to a point on the twist curve. -func pull(Q *ted448.Point, P *Point) { - Px, Py, Pz := &P.X, &P.Y, &P.Z - a, b, c, d, e, f, g, h := &Q.X, &Q.Y, &Q.Z, &fp.Elt{}, &Q.Ta, &Q.X, &Q.Y, &Q.Tb - fp.Add(e, Px, Py) // x+y - fp.Sqr(a, Px) // A = x^2 - fp.Sqr(b, Py) // B = y^2 - fp.Sqr(c, Pz) // z^2 - fp.Add(c, c, c) // C = 2*z^2 - *d = *a // D = A - fp.Sqr(e, e) // (x+y)^2 - fp.Sub(e, e, a) // (x+y)^2-A - fp.Sub(e, e, b) // E = (x+y)^2-A-B - fp.Add(h, b, d) // H = B+D - fp.Sub(g, b, d) // G = B-D - fp.Sub(f, c, h) // F = C-H - fp.Mul(&Q.Z, f, g) // Z = F * G - fp.Mul(&Q.X, e, f) // X = E * F - fp.Mul(&Q.Y, g, h) // Y = G * H, // T = E * H -} +func (P *Point) Neg() { fp.Neg(&P.X, &P.X); fp.Neg(&P.Ta, &P.Ta) } + +// Order returns a scalar with the order of the group. +func Order() Scalar { return ted448.Order() } + +// divBy4 calculates z = x/4 mod order. +func divBy4(z, x *Scalar) { z.Mul(x, &invFour) } + +// pull calculates Q = Iso4(P), where P is a Goldilocks point and Q is a ted448 point. +func pull(Q *ted448.Point, P *Point) { isogeny4(Q, (*ted448.Point)(P), true) } -// push sends a point on the twist curve to a point on the Goldilocks curve. -func push(Q *Point, P *ted448.Point) { +// push calculates Q = Iso4^-1(P), where P is a ted448 point and Q is a Goldilocks point. +func push(Q *Point, P *ted448.Point) { isogeny4((*ted448.Point)(Q), P, false) } + +// isogeny4 is a birational map between ted448 and Goldilocks curves. +func isogeny4(Q, P *ted448.Point, isPull bool) { Px, Py, Pz := &P.X, &P.Y, &P.Z a, b, c, d, e, f, g, h := &Q.X, &Q.Y, &Q.Z, &fp.Elt{}, &Q.Ta, &Q.X, &Q.Y, &Q.Tb - fp.Add(e, Px, Py) // x+y - fp.Sqr(a, Px) // A = x^2 - fp.Sqr(b, Py) // B = y^2 - fp.Sqr(c, Pz) // z^2 - fp.Add(c, c, c) // C = 2*z^2 - fp.Neg(d, a) // D = -A + fp.Add(e, Px, Py) // x+y + fp.Sqr(a, Px) // A = x^2 + fp.Sqr(b, Py) // B = y^2 + fp.Sqr(c, Pz) // z^2 + fp.Add(c, c, c) // C = 2*z^2 + if isPull { + *d = *a // D = A + } else { + fp.Neg(d, a) // D = -A + } fp.Sqr(e, e) // (x+y)^2 fp.Sub(e, e, a) // (x+y)^2-A fp.Sub(e, e, b) // E = (x+y)^2-A-B @@ -153,9 +143,6 @@ func push(Q *Point, P *ted448.Point) { fp.Mul(&Q.Y, g, h) // Y = G * H, // T = E * H } -// divBy4 calculates z = x/4 mod order. -func divBy4(z, x *Scalar) { z.Mul(x, &invFour) } - // isLessThan returns true if 0 <= x < y, and assumes that slices are of the // same length and are interpreted in little-endian order. func isLessThan(x, y []byte) bool { @@ -177,7 +164,7 @@ var ( 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, } - // paramD is -39081 in Fp. This is the D parameter of the goldilocks curve. + // paramD is the D parameter of the Goldilocks curve, D=-39081 in Fp. paramD = fp.Elt{ 0x56, 0x67, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, diff --git a/sign/ed448/internal/goldilocks/goldilocks_test.go b/sign/ed448/internal/goldilocks/goldilocks_test.go index 39503dd50..3dde2a3fb 100644 --- a/sign/ed448/internal/goldilocks/goldilocks_test.go +++ b/sign/ed448/internal/goldilocks/goldilocks_test.go @@ -47,8 +47,8 @@ func TestScalarMult(t *testing.T) { for i := 0; i < testTimes; i++ { _, _ = rand.Read(k[:]) - ScalarBaseMult(&P, k) - CombinedMult(&Q, k, zero, &I) // k*G + 0*I + P.ScalarBaseMult(k) + Q.CombinedMult(k, zero, &I) // k*G + 0*I err0 := P.Encode(&got) err1 := Q.Encode(&want) if err0 != nil || err1 != nil || got != want { @@ -108,7 +108,7 @@ func BenchmarkEncoding(b *testing.B) { var k Scalar _, _ = rand.Read(k[:]) var P Point - ScalarBaseMult(&P, &k) + P.ScalarBaseMult(&k) b.Run("Marshal", func(b *testing.B) { for i := 0; i < b.N; i++ { _ = P.Encode(&data)