From c97b01e1fe14895dcbdcb3f41cba70d926c4a83d Mon Sep 17 00:00:00 2001 From: armfazh Date: Wed, 10 Jun 2020 01:56:31 -0700 Subject: [PATCH] Adapting ed448 for using the internal ted448 package. --- ecc/decaf/decaf.go | 44 +++- ecc/goldilocks/constants.go | 98 --------- ecc/goldilocks/curve.go | 59 ------ ecc/goldilocks/doc.go | 43 ---- ecc/goldilocks/isogeny.go | 52 ----- ecc/goldilocks/isogeny_test.go | 41 ---- ecc/goldilocks/point.go | 142 ------------- ecc/goldilocks/point_test.go | 105 ---------- sign/ed448/ed448.go | 27 +-- sign/ed448/internal/goldilocks/goldilocks.go | 190 ++++++++++++++++++ .../internal/goldilocks/goldilocks_test.go | 122 +++++++++++ 11 files changed, 363 insertions(+), 560 deletions(-) delete mode 100644 ecc/goldilocks/constants.go delete mode 100644 ecc/goldilocks/curve.go delete mode 100644 ecc/goldilocks/doc.go delete mode 100644 ecc/goldilocks/isogeny.go delete mode 100644 ecc/goldilocks/isogeny_test.go delete mode 100644 ecc/goldilocks/point.go delete mode 100644 ecc/goldilocks/point_test.go create mode 100644 sign/ed448/internal/goldilocks/goldilocks.go create mode 100644 sign/ed448/internal/goldilocks/goldilocks_test.go diff --git a/ecc/decaf/decaf.go b/ecc/decaf/decaf.go index ce686f2e2..85c4bb3ba 100644 --- a/ecc/decaf/decaf.go +++ b/ecc/decaf/decaf.go @@ -1,12 +1,40 @@ -// Package decaf provides operations on a prime-order group derived from the goldilocks curve. +// Package decaf provides a prime-order group derived from a quotient of +// Edwards curves. // -// Its internal implementation uses the twist of the goldilocks curve. -// This implementation uses Decaf v1.0 of the encoding. +// Decaf Group // -// References: -// - https://www.shiftleft.org/papers/decaf/ -// - https://www.shiftleft.org/papers/goldilocks -// - https://sourceforge.net/p/ed448goldilocks/code/ci/v1.0/tree/ +// Decaf (3) is a prime-order group constructed as a quotient of groups. A Decaf +// element can be represented by any point in the coset P+J[2], where J is a +// Jacobi quartic and J[2] are its 2-torsion points. +// Since P+J[2] has four points, Decaf specifies rules to choose one canonical +// representative, which has a unique encoding. Two representations are +// equivalent if they belong to the same coset. +// +// The types Elt and Scalar provide methods to perform arithmetic operations on +// the Decaf group. +// +// Version +// +// This implementation uses Decaf v1.0 of the encoding (see (4) for a complete +// specification). +// +// Internals +// +// Decaf uses as internal representation the curve +// ted448: ax^2+y^2 = 1 + dx^2y^2, where a=-1 and d=-39082. +// This curve is 4-degree isogeneous to the Goldilocks curve, and 2-degree +// isogeneous to the Jacobi quartic. The ted448 curve was chosen because it +// provides faster arithmetic operations. +// +// References +// +// (1) https://www.shiftleft.org/papers/goldilocks +// +// (2) https://tools.ietf.org/html/rfc7748 +// +// (3) https://doi.org/10.1007/978-3-662-47989-6_34 and https://www.shiftleft.org/papers/decaf +// +// (4) https://sourceforge.net/p/ed448goldilocks/code/ci/v1.0/tree/ package decaf import ( @@ -16,7 +44,7 @@ import ( fp "github.com/cloudflare/circl/math/fp448" ) -// Version targets Decaf v1.0 of the encoding. As implemented in https://sourceforge.net/p/ed448goldilocks/code/ci/v1.0/tree/. +// Decaf v1.0 of the encoding. const Version = "v1.0" // Elt is an element of the Decaf group. It must be always initialized using diff --git a/ecc/goldilocks/constants.go b/ecc/goldilocks/constants.go deleted file mode 100644 index 212c09e2b..000000000 --- a/ecc/goldilocks/constants.go +++ /dev/null @@ -1,98 +0,0 @@ -package goldilocks - -import ( - "errors" - - fp "github.com/cloudflare/circl/math/fp448" -) - -const ( - // ScalarSize is the size (in bytes) of scalars. - ScalarSize = 56 - // CurveEncodingSize is the size (in bytes) of an encoded point on the Goldilocks curve. - CurveEncodingSize = fp.Size + 1 - // DecafEncodingSize is the size (in bytes) of an encoded Decaf element. - DecafEncodingSize = fp.Size -) - -// All these values are in RFC-7748 (https://tools.ietf.org/html/rfc7748). -var ( - // genX is the x-coordinate of the generator of Goldilocks curve. - genX = fp.Elt{ - 0x5e, 0xc0, 0x0c, 0xc7, 0x2b, 0xa8, 0x26, 0x26, - 0x8e, 0x93, 0x00, 0x8b, 0xe1, 0x80, 0x3b, 0x43, - 0x11, 0x65, 0xb6, 0x2a, 0xf7, 0x1a, 0xae, 0x12, - 0x64, 0xa4, 0xd3, 0xa3, 0x24, 0xe3, 0x6d, 0xea, - 0x67, 0x17, 0x0f, 0x47, 0x70, 0x65, 0x14, 0x9e, - 0xda, 0x36, 0xbf, 0x22, 0xa6, 0x15, 0x1d, 0x22, - 0xed, 0x0d, 0xed, 0x6b, 0xc6, 0x70, 0x19, 0x4f, - } - // genY is the y-coordinate of the generator of Goldilocks curve. - genY = fp.Elt{ - 0x14, 0xfa, 0x30, 0xf2, 0x5b, 0x79, 0x08, 0x98, - 0xad, 0xc8, 0xd7, 0x4e, 0x2c, 0x13, 0xbd, 0xfd, - 0xc4, 0x39, 0x7c, 0xe6, 0x1c, 0xff, 0xd3, 0x3a, - 0xd7, 0xc2, 0xa0, 0x05, 0x1e, 0x9c, 0x78, 0x87, - 0x40, 0x98, 0xa3, 0x6c, 0x73, 0x73, 0xea, 0x4b, - 0x62, 0xc7, 0xc9, 0x56, 0x37, 0x20, 0x76, 0x88, - 0x24, 0xbc, 0xb6, 0x6e, 0x71, 0x46, 0x3f, 0x69, - } - // paramD is -39081 in Fp. - paramD = fp.Elt{ - 0x56, 0x67, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - } - // paramDTwist is -39082 in Fp. The D parameter of the twist curve. - paramDTwist = fp.Elt{ - 0x55, 0x67, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - } - // aMinusD is paramA-paramD used in Decaf. - aMinusDTwist = fp.Elt{0xa9, 0x98} - // sqrtAMinusDTwist is the smallest sqrt(paramATwist-paramDTwist) used in Decaf. - sqrtAMinusDTwist = fp.Elt{ - 0x36, 0x27, 0x57, 0x45, 0x0f, 0xef, 0x42, 0x96, - 0x52, 0xce, 0x20, 0xaa, 0xf6, 0x7b, 0x33, 0x60, - 0xd2, 0xde, 0x6e, 0xfd, 0xf4, 0x66, 0x9a, 0x83, - 0xba, 0x14, 0x8c, 0x96, 0x80, 0xd7, 0xa2, 0x64, - 0x4b, 0xd5, 0xb8, 0xa5, 0xb8, 0xa7, 0xf1, 0xa1, - 0xa0, 0x6a, 0xa2, 0x2f, 0x72, 0x8d, 0xf6, 0x3b, - 0x68, 0xf7, 0x24, 0xeb, 0xfb, 0x62, 0xd9, 0x22, - } - // order is 2^446-0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d, - // which is the number of points in the prime subgroup. - order = Scalar{ - 0xf3, 0x44, 0x58, 0xab, 0x92, 0xc2, 0x78, 0x23, - 0x55, 0x8f, 0xc5, 0x8d, 0x72, 0xc2, 0x6c, 0x21, - 0x90, 0x36, 0xd6, 0xae, 0x49, 0xdb, 0x4e, 0xc4, - 0xe9, 0x23, 0xca, 0x7c, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, - } - // residue448 is 2^448 mod order. - residue448 = [4]uint64{ - 0x721cf5b5529eec34, 0x7a4cf635c8e9c2ab, 0xeec492d944a725bf, 0x20cd77058, - } - // invFour is 1/4 mod order. - invFour = Scalar{ - 0x3d, 0x11, 0xd6, 0xaa, 0xa4, 0x30, 0xde, 0x48, - 0xd5, 0x63, 0x71, 0xa3, 0x9c, 0x30, 0x5b, 0x08, - 0xa4, 0x8d, 0xb5, 0x6b, 0xd2, 0xb6, 0x13, 0x71, - 0xfa, 0x88, 0x32, 0xdf, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, - } - ErrInvalidDecoding = errors.New("invalid decoding") -) diff --git a/ecc/goldilocks/curve.go b/ecc/goldilocks/curve.go deleted file mode 100644 index 4608d5409..000000000 --- a/ecc/goldilocks/curve.go +++ /dev/null @@ -1,59 +0,0 @@ -package goldilocks - -import ( - fp "github.com/cloudflare/circl/math/fp448" -) - -// Curve provides operations on the Goldilocks curve. -// Curve is a zero-length datatype. -type Curve struct{} - -// Identity returns the identity point. -func (Curve) Identity() *Point { return &Point{y: fp.One(), z: fp.One()} } - -// Generator returns the generator point. -func (Curve) Generator() *Point { return &Point{x: genX, y: genY, z: fp.One(), ta: genX, tb: genY} } - -// IsOnCurve returns true if the point lies on the curve. -func (Curve) IsOnCurve(P *Point) bool { return isOnCurve(&P.x, &P.y, &P.ta, &P.tb, &P.z, false) } - -// Order returns the number of points in the prime subgroup. -func (Curve) Order() Scalar { return order } - -// Double calculates R = 2P. -func (Curve) Double(R, P *Point) { *R = *P; R.Double() } - -// Add calculates R = P+Q. -func (Curve) Add(R, P, Q *Point) { S := *P; S.Add(Q); *R = S } - -// ScalarMult calculates Q = kP. This function runs in constant time. -func (Curve) ScalarMult(Q *Point, k *Scalar, P *Point) { - var t twistCurve - k4 := &Scalar{} - k4.divBy4(k) - R := &twistPoint{} - t.ScalarMult(R, k4, t.pull(P)) - *Q = *t.push(R) -} - -// ScalarBaseMult calculates Q = kG, where G is the generator of the Goldilocks curve. This function runs in constant time. -func (Curve) ScalarBaseMult(Q *Point, k *Scalar) { - var t twistCurve - k4 := &Scalar{} - k4.divBy4(k) - R := &twistPoint{} - t.ScalarBaseMult(R, k4) - *Q = *t.push(R) -} - -// CombinedMult calculates Q = mG+nP, where G is the generator of the Goldilocks curve. This function does NOT run in constant time. -func (Curve) CombinedMult(Q *Point, m, n *Scalar, P *Point) { - var t twistCurve - m4 := &Scalar{} - n4 := &Scalar{} - m4.divBy4(m) - n4.divBy4(n) - R := &twistPoint{} - t.CombinedMult(R, m4, n4, t.pull(P)) - *Q = *t.push(R) -} diff --git a/ecc/goldilocks/doc.go b/ecc/goldilocks/doc.go deleted file mode 100644 index 42f695c62..000000000 --- a/ecc/goldilocks/doc.go +++ /dev/null @@ -1,43 +0,0 @@ -// Package goldilocks provides elliptic curve operations over the goldilocks -// curve and the decaf group. -// -// Goldilocks Curve -// -// The goldilocks curve is defined over GF(2^448-2^224-1) by the twisted Edwards -// curve -// 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). -// -// The datatypes Curve, Point, and Scalar provide methods to perform arithmetic -// operations on the Goldilocks curve. -// -// Decaf Group -// -// Decaf (3) is a prime-order group constructed as a quotient of groups. A Decaf -// element can be represented by any point in the coset P+J[2], where J is a -// Jacobi quartic and J[2] are its 2-torsion points. -// Since P+J[2] has four points, Decaf specifies rules to choose one canonical -// representative, which has a unique encoding. Two representations are -// equivalent if they belong to the same coset. -// -// The types Decaf, Elt, and Scalar provide methods to perform arithmetic -// operations on the Decaf group. -// -// Internals -// -// Both Goldilocks and Decaf use as internal representation the curve -// 4Iso-Goldilocks: ax^2+y^2 = 1 + dx^2y^2, where a=-1 and d=-39082. -// This curve is 4-degree isogeneous to the Goldilocks curve, and 2-degree -// isogeneous to the Jacobi quartic. The 4Iso-Goldilocks curve was chosen as -// provides faster arithmetic operations. -// -// References -// -// (1) https://www.shiftleft.org/papers/goldilocks -// -// (2) https://tools.ietf.org/html/rfc7748 -// -// (3) https://doi.org/10.1007/978-3-662-47989-6_34 -// -package goldilocks diff --git a/ecc/goldilocks/isogeny.go b/ecc/goldilocks/isogeny.go deleted file mode 100644 index b1daab851..000000000 --- a/ecc/goldilocks/isogeny.go +++ /dev/null @@ -1,52 +0,0 @@ -package goldilocks - -import fp "github.com/cloudflare/circl/math/fp448" - -func (Curve) pull(P *twistPoint) *Point { return twistCurve{}.push(P) } -func (twistCurve) pull(P *Point) *twistPoint { return Curve{}.push(P) } - -// push sends a point on the Goldilocks curve to a point on the twist curve. -func (Curve) push(P *Point) *twistPoint { - Q := &twistPoint{} - 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 - return Q -} - -// push sends a point on the twist curve to a point on the Goldilocks curve. -func (twistCurve) push(P *twistPoint) *Point { - Q := &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 - 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 - 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 - return Q -} diff --git a/ecc/goldilocks/isogeny_test.go b/ecc/goldilocks/isogeny_test.go deleted file mode 100644 index 0bee47496..000000000 --- a/ecc/goldilocks/isogeny_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package goldilocks - -import ( - "crypto/rand" - "testing" - - "github.com/cloudflare/circl/internal/test" -) - -func randomPoint() *Point { - var k Scalar - _, _ = rand.Read(k[:]) - P := &Point{} - Curve{}.ScalarBaseMult(P, &k) - return P -} - -func TestIsogeny(t *testing.T) { - const testTimes = 1 << 10 - var gold Curve - var twist twistCurve - - for i := 0; i < testTimes; i++ { - P := randomPoint() - Q := gold.pull(gold.push(P)) // phi^-(phi^+(P)) - gold.Double(P, P) // 2P - gold.Double(P, P) // 4P - got := Q - want := P - if !got.IsEqual(want) { - test.ReportError(t, got, want, P) - } - got = twist.push(twist.pull(Q)) // phi^-(phi^+(Q)) - gold.Double(Q, Q) // 2Q - gold.Double(Q, Q) // 4Q - want = Q - if !got.IsEqual(want) { - test.ReportError(t, got, want, P) - } - } -} diff --git a/ecc/goldilocks/point.go b/ecc/goldilocks/point.go deleted file mode 100644 index e0ac5245a..000000000 --- a/ecc/goldilocks/point.go +++ /dev/null @@ -1,142 +0,0 @@ -package goldilocks - -import ( - "unsafe" - - fp "github.com/cloudflare/circl/math/fp448" -) - -// Point is a point on the Goldilocks Curve. -type Point struct{ x, y, z, ta, tb fp.Elt } - -// 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 { - i := len(x) - 1 - for i > 0 && x[i] == y[i] { - i-- - } - return x[i] < y[i] -} - -// IsIdentity returns True is P is the identity. -func (P *Point) IsIdentity() bool { - return fp.IsZero(&P.x) && !fp.IsZero(&P.y) && !fp.IsZero(&P.z) && P.y == P.z -} - -// IsEqual returns True if P is equivalent to Q. -func (P *Point) IsEqual(Q *Point) bool { - l, r := &fp.Elt{}, &fp.Elt{} - fp.Mul(l, &P.x, &Q.z) - fp.Mul(r, &Q.x, &P.z) - fp.Sub(l, l, r) - b := fp.IsZero(l) - fp.Mul(l, &P.y, &Q.z) - fp.Mul(r, &Q.y, &P.z) - fp.Sub(l, l, r) - b = b && fp.IsZero(l) - fp.Mul(l, &P.ta, &P.tb) - fp.Mul(l, l, &Q.z) - fp.Mul(r, &Q.ta, &Q.tb) - fp.Mul(r, r, &P.z) - fp.Sub(l, l, r) - b = b && fp.IsZero(l) - return b -} - -// Neg obtains the inverse of P. -func (P *Point) Neg() { fp.Neg(&P.x, &P.x); fp.Neg(&P.ta, &P.ta) } - -// ToAffine returns the x,y affine coordinates of P. -func (P *Point) ToAffine() (x, y fp.Elt) { - fp.Inv(&P.z, &P.z) // 1/z - fp.Mul(&P.x, &P.x, &P.z) // x/z - fp.Mul(&P.y, &P.y, &P.z) // y/z - fp.Modp(&P.x) - fp.Modp(&P.y) - fp.SetOne(&P.z) - P.ta = P.x - P.tb = P.y - return P.x, P.y -} - -// Double sets P = 2Q. -func (P *Point) Double() { P.Add(P) } - -// Add sets P = P+Q. -func (P *Point) Add(Q *Point) { - // This is formula (5) from "Twisted Edwards Curves Revisited" by - // Hisil H., Wong K.KH., Carter G., Dawson E. (2008) - // https://doi.org/10.1007/978-3-540-89255-7_20 - x1, y1, z1, ta1, tb1 := &P.x, &P.y, &P.z, &P.ta, &P.tb - x2, y2, z2, ta2, tb2 := &Q.x, &Q.y, &Q.z, &Q.ta, &Q.tb - x3, y3, z3, E, H := &P.x, &P.y, &P.z, &P.ta, &P.tb - A, B, C, D := &fp.Elt{}, &fp.Elt{}, &fp.Elt{}, &fp.Elt{} - t1, t2, F, G := C, D, &fp.Elt{}, &fp.Elt{} - fp.Mul(t1, ta1, tb1) // t1 = ta1*tb1 - fp.Mul(t2, ta2, tb2) // t2 = ta2*tb2 - fp.Mul(A, x1, x2) // A = x1*x2 - fp.Mul(B, y1, y2) // B = y1*y2 - fp.Mul(C, t1, t2) // t1*t2 - fp.Mul(C, C, ¶mD) // C = d*t1*t2 - fp.Mul(D, z1, z2) // D = z1*z2 - fp.Add(F, x1, y1) // x1+y1 - fp.Add(E, x2, y2) // x2+y2 - fp.Mul(E, E, F) // (x1+y1)*(x2+y2) - fp.Sub(E, E, A) // (x1+y1)*(x2+y2)-A - fp.Sub(E, E, B) // E = (x1+y1)*(x2+y2)-A-B - fp.Sub(F, D, C) // F = D-C - fp.Add(G, D, C) // G = D+C - fp.Sub(H, B, A) // H = B-A - fp.Mul(z3, F, G) // Z = F * G - fp.Mul(x3, E, F) // X = E * F - fp.Mul(y3, G, H) // Y = G * H, T = E * H -} - -// UnmarshalBinary if succeeds constructs a point by decoding the first -// CurveEncodingSize bytes of data. -func (P *Point) UnmarshalBinary(data []byte) error { - if len(data) < CurveEncodingSize { - return ErrInvalidDecoding - } - - x, y := &fp.Elt{}, &fp.Elt{} - signX := data[fp.Size] >> 7 - copy(y[:], data[:fp.Size]) - p := fp.P() - isLessThanP := isLessThan(y[:], p[:]) - - u, v := &fp.Elt{}, &fp.Elt{} - one := fp.One() - fp.Sqr(u, y) // u = y^2 - fp.Mul(v, u, ¶mD) // v = dy^2 - fp.Sub(u, u, &one) // u = y^2-1 - fp.Sub(v, v, &one) // v = dy^2-a - isQR := fp.InvSqrt(x, u, v) // x = sqrt(u/v) - isValidXSign := !(fp.IsZero(x) && signX == 1) - fp.Neg(u, x) // u = -x - fp.Cmov(x, u, uint(signX^(x[0]&1))) // if signX != x mod 2 - - isValid := isLessThanP && isQR && isValidXSign - b := uint(*((*byte)(unsafe.Pointer(&isValid)))) - fp.Cmov(&P.x, x, b) - fp.Cmov(&P.y, y, b) - 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 err -} - -// MarshalBinary returns a unique encoding of the point P. -func (P *Point) MarshalBinary() ([]byte, error) { - out := make([]byte, CurveEncodingSize) - x, y := P.ToAffine() - out[fp.Size] = (x[0] & 1) << 7 - err := fp.ToBytes(out[:fp.Size], &y) - return out, err -} diff --git a/ecc/goldilocks/point_test.go b/ecc/goldilocks/point_test.go deleted file mode 100644 index 76739dc66..000000000 --- a/ecc/goldilocks/point_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package goldilocks_test - -import ( - "crypto/rand" - "encoding" - "testing" - - "github.com/cloudflare/circl/ecc/goldilocks" - "github.com/cloudflare/circl/internal/test" - fp "github.com/cloudflare/circl/math/fp448" -) - -func randomPoint() *goldilocks.Point { - var k goldilocks.Scalar - _, _ = rand.Read(k[:]) - P := &goldilocks.Point{} - goldilocks.Curve{}.ScalarBaseMult(P, &k) - return P -} - -func TestPointAdd(t *testing.T) { - const testTimes = 1 << 10 - var e goldilocks.Curve - R := &goldilocks.Point{} - for i := 0; i < testTimes; i++ { - P := randomPoint() - // 16P = 2^4P - e.Double(R, P) // 2P - e.Double(R, R) // 4P - e.Double(R, R) // 8P - e.Double(R, R) // 16P - got := R - // 16P = P+P...+P - Q := e.Identity() - for j := 0; j < 16; j++ { - e.Add(Q, Q, P) - } - want := Q - if !e.IsOnCurve(got) || !e.IsOnCurve(want) || !got.IsEqual(want) { - test.ReportError(t, got, want, P) - } - } -} - -func TestPointNeg(t *testing.T) { - const testTimes = 1 << 10 - var e goldilocks.Curve - R := &goldilocks.Point{} - for i := 0; i < testTimes; i++ { - P := randomPoint() - Q := *P - Q.Neg() - e.Add(R, P, &Q) - got := R.IsIdentity() - want := true - if got != want { - test.ReportError(t, got, want, P) - } - } -} - -func TestPointMarshal(t *testing.T) { - const testTimes = 1 << 10 - var want error - for i := 0; i < testTimes; i++ { - var P interface{} = randomPoint() - mar, _ := P.(encoding.BinaryMarshaler) - data, got := mar.MarshalBinary() - if got != want { - test.ReportError(t, got, want, P) - } - unmar, _ := P.(encoding.BinaryUnmarshaler) - got = unmar.UnmarshalBinary(data) - if got != want { - test.ReportError(t, got, want, P) - } - } -} - -func TestPointInvalid(t *testing.T) { - p := fp.P() - one := fp.One() - - var bigY, wrongSignX, nonQR [goldilocks.CurveEncodingSize]byte - copy(bigY[:], p[:]) - copy(wrongSignX[:], one[:]) - wrongSignX[goldilocks.CurveEncodingSize-1] = 1 << 7 - nonQR[0] = 2 // smallest y such that (y^2+a)/(dy^2-a) is not a square. - - badEncodings := [][]byte{ - {}, // wrong size input. - bigY[:], // y is out of the interval [0,p-1]. - wrongSignX[:], // x has wrong sign. - nonQR[:], // y=2 and (y^2+a)/(dy^2-a) is not a square. - } - - var P goldilocks.Point - for _, enc := range badEncodings { - got := P.UnmarshalBinary(enc) - want := goldilocks.ErrInvalidDecoding - if got != want { - test.ReportError(t, got, want, enc) - } - } -} diff --git a/sign/ed448/ed448.go b/sign/ed448/ed448.go index 15b99926f..83984a326 100644 --- a/sign/ed448/ed448.go +++ b/sign/ed448/ed448.go @@ -13,8 +13,8 @@ import ( "errors" "io" - "github.com/cloudflare/circl/ecc/goldilocks" sha3 "github.com/cloudflare/circl/internal/shake" + "github.com/cloudflare/circl/sign/ed448/internal/goldilocks" ) const ( @@ -97,12 +97,11 @@ func NewKeyFromSeed(seed PrivateKey) *KeyPair { pair := &KeyPair{} copy(pair.private[:], seed) P := &goldilocks.Point{} - goldilocks.Curve{}.ScalarBaseMult(P, s) - enc, err := P.MarshalBinary() + goldilocks.ScalarBaseMult(P, s) + err := P.Encode(&pair.public) if err != nil { panic(err) } - copy(pair.public[:], enc) return pair } @@ -136,15 +135,16 @@ func (kp *KeyPair) SignWithContext(message, context []byte) ([]byte, error) { r := &goldilocks.Scalar{} r.FromBytes(rPM[:]) R := &goldilocks.Point{} - goldilocks.Curve{}.ScalarBaseMult(R, r) - encR, err := R.MarshalBinary() + var encR [goldilocks.EncodingSize]byte + goldilocks.ScalarBaseMult(R, r) + err := R.Encode(&encR) // 4. Compute SHAKE256(dom4(F, C) || R || A || PH(M), 114) var hRAM [hashSize]byte H.Reset() _, _ = H.Write(dom4[:]) _, _ = H.Write(context) - _, _ = H.Write(encR) + _, _ = H.Write(encR[:]) _, _ = H.Write(kp.public[:]) _, _ = H.Write(message) _, _ = H.Read(hRAM[:]) @@ -172,8 +172,10 @@ func Verify(public PublicKey, message, context, signature []byte) bool { !isLessThanOrder(signature[paramB:]) { return false } + var encPublic [goldilocks.EncodingSize]byte + copy(encPublic[:], public) P := &goldilocks.Point{} - err := P.UnmarshalBinary(public) + err := P.Decode(&encPublic) if err != nil { return false } @@ -196,12 +198,13 @@ func Verify(public PublicKey, message, context, signature []byte) bool { P.Neg() Q := &goldilocks.Point{} - goldilocks.Curve{}.CombinedMult(Q, S, k, P) - encR, err := Q.MarshalBinary() + goldilocks.CombinedMult(Q, S, k, P) + var encR [goldilocks.EncodingSize]byte + err = Q.Encode(&encR) if err != nil { return false } - return bytes.Equal(R, encR) + return bytes.Equal(R, encR[:]) } func deriveSecretScalar(s *goldilocks.Scalar, h []byte) { @@ -213,7 +216,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 new file mode 100644 index 000000000..a8dc57bf6 --- /dev/null +++ b/sign/ed448/internal/goldilocks/goldilocks.go @@ -0,0 +1,190 @@ +// Package goldilocks provides goldilocks curve operations required by Ed448. +// +// Goldilocks Curve +// +// The goldilocks curve is defined over GF(2^448-2^224-1) by the twisted Edwards +// curve +// 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). +// +// The datatypes Point and Scalar provide methods to perform arithmetic +// operations on the Goldilocks curve. +// +// References +// +// (1) https://www.shiftleft.org/papers/goldilocks +// +// (2) https://tools.ietf.org/html/rfc7748 +package goldilocks + +import ( + "errors" + "unsafe" + + "github.com/cloudflare/circl/internal/ted448" + fp "github.com/cloudflare/circl/math/fp448" +) + +type Scalar = ted448.Scalar + +type Point ted448.Point + +// EncodingSize is the size (in bytes) of an encoded point on the Goldilocks curve. +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 { + x, y := &fp.Elt{}, &fp.Elt{} + isByteZero := (data[EncodingSize-1] & 0x7F) == 0x00 + signX := data[EncodingSize-1] >> 7 + copy(y[:], data[:fp.Size]) + p := fp.P() + isLessThanP := isLessThan(y[:], p[:]) + + u, v := &fp.Elt{}, &fp.Elt{} + one := fp.One() + fp.Sqr(u, y) // u = y^2 + fp.Mul(v, u, ¶mD) // v = dy^2 + fp.Sub(u, u, &one) // u = y^2-1 + fp.Sub(v, v, &one) // v = dy^2-a + isQR := fp.InvSqrt(x, u, v) // x = sqrt(u/v) + isValidXSign := !(fp.IsZero(x) && signX == 1) + fp.Neg(u, x) // u = -x + fp.Cmov(x, u, uint(signX^(x[0]&1))) // if signX != x mod 2 + + isValid := isByteZero && isLessThanP && isQR && isValidXSign + b := uint(*(*byte)(unsafe.Pointer(&isValid))) + fp.Cmov(&P.X, x, b) + fp.Cmov(&P.Y, y, b) + 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 err +} + +// Encode sets data with the unique encoding of the point P. +func (P *Point) Encode(data *[EncodingSize]byte) error { + x, y, invZ := &fp.Elt{}, &fp.Elt{}, &fp.Elt{} + fp.Inv(invZ, &P.Z) // 1/z + fp.Mul(x, &P.X, invZ) // x/z + fp.Mul(y, &P.Y, invZ) // y/z + fp.Modp(x) + fp.Modp(y) + data[EncodingSize-1] = (x[0] & 1) << 7 + err := fp.ToBytes(data[:fp.Size], y) + return err +} + +// 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) { + k4 := &Scalar{} + divBy4(k4, k) + R := &ted448.Point{} + ted448.ScalarBaseMult(R, k4) + push(Q, R) +} + +// 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) { + 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) +} + +// 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 +} + +// push sends a point on the twist curve to a point on the Goldilocks curve. +func push(Q *Point, P *ted448.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 + 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 + 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 +} + +// 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 { + i := len(x) - 1 + for i > 0 && x[i] == y[i] { + i-- + } + return x[i] < y[i] +} + +var ( + // invFour is 1/4 mod order, where order = ted448.Order(). + invFour = Scalar{ + 0x3d, 0x11, 0xd6, 0xaa, 0xa4, 0x30, 0xde, 0x48, + 0xd5, 0x63, 0x71, 0xa3, 0x9c, 0x30, 0x5b, 0x08, + 0xa4, 0x8d, 0xb5, 0x6b, 0xd2, 0xb6, 0x13, 0x71, + 0xfa, 0x88, 0x32, 0xdf, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 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 = fp.Elt{ + 0x56, 0x67, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 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 new file mode 100644 index 000000000..39503dd50 --- /dev/null +++ b/sign/ed448/internal/goldilocks/goldilocks_test.go @@ -0,0 +1,122 @@ +package goldilocks + +import ( + "crypto/rand" + "testing" + + "github.com/cloudflare/circl/internal/ted448" + "github.com/cloudflare/circl/internal/test" + fp "github.com/cloudflare/circl/math/fp448" +) + +func randomTwistPoint() ted448.Point { + var k ted448.Scalar + _, _ = rand.Read(k[:]) + var P ted448.Point + ted448.ScalarBaseMult(&P, &k) + return P +} + +func TestIsogeny(t *testing.T) { + const testTimes = 1 << 10 + var phiP Point + var Q ted448.Point + for i := 0; i < testTimes; i++ { + P := randomTwistPoint() + R := P + push(&phiP, &P) + pull(&Q, &phiP) + R.Double() // 2P + R.Double() // 4P + got := Q + want := R + if !got.IsEqual(&want) { + test.ReportError(t, got, want, P) + } + } +} + +func TestScalarMult(t *testing.T) { + const testTimes = 1 << 10 + k := &Scalar{} + zero := &Scalar{} + var P, Q, I Point + var got, want [EncodingSize]byte + _I := ted448.Identity() + push(&I, &_I) + for i := 0; i < testTimes; i++ { + _, _ = rand.Read(k[:]) + + ScalarBaseMult(&P, k) + CombinedMult(&Q, k, zero, &I) // k*G + 0*I + err0 := P.Encode(&got) + err1 := Q.Encode(&want) + if err0 != nil || err1 != nil || got != want { + test.ReportError(t, got, want, k) + } + } +} + +func TestPointEncoding(t *testing.T) { + const testTimes = 1 << 10 + var want, got [EncodingSize]byte + var P Point + for i := 0; i < testTimes; i++ { + for found := false; !found; { + _, _ = rand.Read(want[:]) + want[EncodingSize-1] &= 0x80 + err := P.Decode(&want) + found = err == nil + } + err := P.Encode(&got) + if err != nil || got != want { + test.ReportError(t, got, want, P) + } + } +} + +func TestPointInvalid(t *testing.T) { + p := fp.P() + one := fp.One() + + var byteDirty, bigY, wrongSignX, nonQR [EncodingSize]byte + byteDirty[EncodingSize-1] = 0x33 + copy(bigY[:], p[:]) + copy(wrongSignX[:], one[:]) + wrongSignX[EncodingSize-1] = 1 << 7 + nonQR[0] = 2 // smallest y such that (y^2+a)/(dy^2-a) is not a square. + + badEncodings := []*[EncodingSize]byte{ + &byteDirty, // the last byte is not {0x00,0x80}. + &bigY, // y is out of the interval [0,p-1]. + &wrongSignX, // x has wrong sign. + &nonQR, // y=2 and (y^2+a)/(dy^2-a) is not a square. + } + + var P Point + for _, enc := range badEncodings { + got := P.Decode(enc) + want := ErrInvalidDecoding + if got != want { + test.ReportError(t, got, want, enc) + } + } +} + +func BenchmarkEncoding(b *testing.B) { + var data [EncodingSize]byte + var k Scalar + _, _ = rand.Read(k[:]) + var P Point + ScalarBaseMult(&P, &k) + b.Run("Marshal", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = P.Encode(&data) + } + }) + b.Run("Unmarshal", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = P.Decode(&data) + } + }) +}