diff --git a/dcrec/secp256k1/ellipticadaptor.go b/dcrec/secp256k1/ellipticadaptor.go index a3a45af31..1d22f0a89 100644 --- a/dcrec/secp256k1/ellipticadaptor.go +++ b/dcrec/secp256k1/ellipticadaptor.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 The Decred developers +// Copyright 2020-2026 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -63,8 +63,8 @@ type KoblitzCurve struct { // bigAffineToJacobian takes an affine point (x, y) as big integers and converts // it to Jacobian point with Z=1. func bigAffineToJacobian(x, y *big.Int, result *JacobianPoint) { - result.X.SetByteSlice(x.Bytes()) - result.Y.SetByteSlice(y.Bytes()) + result.X.SetByteSlice(new(big.Int).Mod(x, curveParams.P).Bytes()) + result.Y.SetByteSlice(new(big.Int).Mod(y, curveParams.P).Bytes()) result.Z.SetInt(1) } @@ -91,6 +91,15 @@ func (curve *KoblitzCurve) Params() *elliptic.CurveParams { // // This is part of the elliptic.Curve interface implementation. This function // differs from the crypto/elliptic algorithm since a = 0 not -3. +// +// NOTE: Unfortunately, the Go stdlib elliptic.Curve interface requires that the +// conventional point at infinity (0, 0) is not considered on the curve which is +// contrary to what is typically expected since the point at infinity is in fact +// is a valid curve point. +// +// Deprecated: The standard library elliptic.Curve interface is now deprecated +// and callers should interact with the safer, and much faster, specialized +// methods instead. func (curve *KoblitzCurve) IsOnCurve(x, y *big.Int) bool { // Convert big ints to a Jacobian point for faster arithmetic. var point JacobianPoint @@ -101,6 +110,14 @@ func (curve *KoblitzCurve) IsOnCurve(x, y *big.Int) bool { // Add returns the sum of (x1,y1) and (x2,y2). // // This is part of the elliptic.Curve interface implementation. +// +// NOTE: Per the documentation of the elliptic.Curve interface, the behavior +// when the input is not a point on the curve is undefined. Callers must ensure +// they are calling this method with valid points. +// +// Deprecated: The standard library elliptic.Curve interface is now deprecated +// and callers should interact with the safer, and much faster, specialized +// methods instead. func (curve *KoblitzCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) { // The point at infinity is the identity according to the group law for // elliptic curve cryptography. Thus, ∞ + P = P and P + ∞ = P. @@ -124,6 +141,14 @@ func (curve *KoblitzCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) { // Double returns 2*(x1,y1). // // This is part of the elliptic.Curve interface implementation. +// +// NOTE: Per the documentation of the elliptic.Curve interface, the behavior +// when the input is not a point on the curve is undefined. Callers must ensure +// they are calling this method with valid points. +// +// Deprecated: The standard library elliptic.Curve interface is now deprecated +// and callers should interact with the safer, and much faster, specialized +// methods instead. func (curve *KoblitzCurve) Double(x1, y1 *big.Int) (*big.Int, *big.Int) { if y1.Sign() == 0 { return new(big.Int), new(big.Int) @@ -156,6 +181,14 @@ func moduloReduce(k []byte) []byte { // ScalarMult returns k*(bx, by) where k is a big endian integer. // // This is part of the elliptic.Curve interface implementation. +// +// NOTE: Per the documentation of the elliptic.Curve interface, the behavior +// when the input is not a point on the curve is undefined. Callers must ensure +// they are calling this method with valid points. +// +// Deprecated: The standard library elliptic.Curve interface is now deprecated +// and callers should interact with the safer, and much faster, specialized +// methods instead. func (curve *KoblitzCurve) ScalarMult(bx, by *big.Int, k []byte) (*big.Int, *big.Int) { // Convert the affine coordinates from big integers to Jacobian points, // do the multiplication in Jacobian projective space, and convert the @@ -172,6 +205,10 @@ func (curve *KoblitzCurve) ScalarMult(bx, by *big.Int, k []byte) (*big.Int, *big // big endian integer. // // This is part of the elliptic.Curve interface implementation. +// +// Deprecated: The standard library elliptic.Curve interface is now deprecated +// and callers should interact with the safer, and much faster, specialized +// methods instead. func (curve *KoblitzCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) { // Perform the multiplication and convert the Jacobian point back to affine // big.Ints. @@ -250,6 +287,10 @@ var secp256k1 = &KoblitzCurve{ } // S256 returns an elliptic.Curve which implements secp256k1. +// +// Deprecated: The standard library elliptic.Curve interface is now deprecated +// and callers should interact with the safer, and much faster, specialized +// methods instead. func S256() *KoblitzCurve { return secp256k1 } diff --git a/dcrec/secp256k1/ellipticadaptor_test.go b/dcrec/secp256k1/ellipticadaptor_test.go index 3c9b01260..cf9af87d9 100644 --- a/dcrec/secp256k1/ellipticadaptor_test.go +++ b/dcrec/secp256k1/ellipticadaptor_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022 The Decred developers +// Copyright (c) 2020-2026 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -24,12 +24,131 @@ func randBytes(t *testing.T, rng *rand.Rand, numBytes uint8) []byte { return buf } -// TestIsOnCurveAdaptor ensures the IsOnCurve method used to satisfy the -// elliptic.Curve interface works as intended. +// TestBigAffineToJacobian ensures [bigAffineToJacobian] reduces an affine point +// with coordinates that are larger than the field prime to a jacobian point +// with the fields reduced modulo the field prime. +func TestBigAffineToJacobian(t *testing.T) { + tests := []struct { + name string // test description + x1, y1 string // hex encoded coordinates of point to test + x2, y2 string // hex encoded coordinates of expected point + }{{ + name: "normal reduced point", + x1: "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c", + y1: "4d1f1522047b33068bbb9b07d1e9f40564749b062b3fc0666479bc08a94be98c", + x2: "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c", + y2: "4d1f1522047b33068bbb9b07d1e9f40564749b062b3fc0666479bc08a94be98c", + }, { + name: "unreduced x coord", + x1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", + y1: "4218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee", + x2: "1", + y2: "4218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee", + }, { + name: "unreduced y coord", + x1: "1", + y1: "014218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d577e76a41d", + x2: "1", + y2: "4218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee", + }, { + name: "unreduced x and y coord", + x1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", + y1: "014218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d577e76a41d", + x2: "1", + y2: "4218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee", + }} + + for _, test := range tests { + // Parse the test data. + x := fromHex(test.x1) + y := fromHex(test.y1) + want := jacobianPointFromHex(test.x2, test.y2, "1") + + // Convert to the point to Jacobian and ensure the resulting point is + // reduced as expected. + var r JacobianPoint + bigAffineToJacobian(x, y, &r) + if !r.IsStrictlyEqual(&want) { + t.Errorf("%s: wrong result\ngot: (%v, %v, %v)\nwant: (%v, %v, %v)", + test.name, r.X, r.Y, r.Z, want.X, want.Y, want.Z) + } + } +} + +// TestIsOnCurveAdaptor ensures the [KoblitzCurve.IsOnCurve] method used to +// satisfy the [crypto/elliptic.Curve] interface works as intended. func TestIsOnCurveAdaptor(t *testing.T) { + tests := []struct { + name string // test description + x, y string // hex encoded coordinates of point to test + want bool // expected result + }{{ + name: "curve generator", + x: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + y: "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + want: true, + }, { + // Note that the [crypto/elliptic.Curve.IsOnCurve] interface explicitly + // states "Note that the conventional point at infinity (0, 0) is not + // considered on the curve" even though it really should be because it + // is a valid curve point. Make sure the interface is satisfied. + name: "point at infinity", + x: "0", + y: "0", + want: false, + }, { + // See previous explanation about for why it's expecting false. + name: "unreduced point at infinity", + x: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + y: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + want: false, + }, { + name: "valid with even y", + x: "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c", + y: "4d1f1522047b33068bbb9b07d1e9f40564749b062b3fc0666479bc08a94be98c", + want: true, + }, { + name: "valid with odd y", + x: "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c", + y: "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3", + want: true, + }, { + name: "invalid due to x coord", + x: "15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c", + y: "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3", + want: false, + }, { + name: "invalid due to y coord", + x: "15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c", + y: "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a4", + want: false, + }, { + name: "unreduced x coord", + x: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", + y: "4218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee", + want: true, + }, { + name: "unreduced y coord", + x: "1", + y: "014218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d577e76a41d", + want: true, + }, { + name: "unreduced x and y coord", + x: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", + y: "014218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d577e76a41d", + want: true, + }} + s256 := S256() - if !s256.IsOnCurve(s256.Params().Gx, s256.Params().Gy) { - t.Fatal("generator point does not claim to be on the curve") + for _, test := range tests { + // Parse the test data. + x := fromHex(test.x) + y := fromHex(test.y) + result := s256.IsOnCurve(x, y) + if result != test.want { + t.Errorf("%s: mismatched is on curve result -- got %v, want %v", + test.name, result, test.want) + } } } @@ -96,6 +215,38 @@ func TestAddAffineAdaptor(t *testing.T) { y2: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", x3: "59477d88ae64a104dbb8d31ec4ce2d91b2fe50fa628fb6a064e22582196b365b", y3: "938dc8c0f13d1e75c987cb1a220501bd614b0d3dd9eb5c639847e1240216e3b6", + }, { + // Addition with same point where the x coordinate in the first point is + // an unreduced value larger than the field prime. + name: "P(x, y) + P(x+p, y) = 2P", + x1: "1", + y1: "4218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee", + x2: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", + y2: "4218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee", + x3: "c7ffffffffffffffffffffffffffffffffffffffffffffffffffffff37fffd03", + y3: "4298c557a7ddcc570e8bf054c4cad9e99f396b3ce19d50f1b91c9df4bb00d333", + }, { + // Symmetric variant of the previous. + // + // Addition with same point where the x coordinate in the second point + // is an unreduced value larger than the field prime. + name: "P(x+p, y) + P(x, y) = 2P", + x1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", + y1: "4218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee", + x2: "1", + y2: "4218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee", + x3: "c7ffffffffffffffffffffffffffffffffffffffffffffffffffffff37fffd03", + y3: "4298c557a7ddcc570e8bf054c4cad9e99f396b3ce19d50f1b91c9df4bb00d333", + }, { + // Addition with same point where the x coordinate of both points is an + // unreduced value larger than the field prime. + name: "P(x+p, y) + P(x+p, y) = 2P", + x1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", + y1: "4218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee", + x2: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", + y2: "4218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee", + x3: "c7ffffffffffffffffffffffffffffffffffffffffffffffffffffff37fffd03", + y3: "4298c557a7ddcc570e8bf054c4cad9e99f396b3ce19d50f1b91c9df4bb00d333", }} curve := S256()