From f57584555bc57b865efac92acf120407c72c0ee7 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 Feb 2022 02:41:21 -0600 Subject: [PATCH 01/16] secp256k1: Correct several comments. --- dcrec/secp256k1/curve.go | 32 +++++++++---------- dcrec/secp256k1/curve_test.go | 12 +++---- dcrec/secp256k1/ecdsa/bench_test.go | 4 +-- dcrec/secp256k1/ellipticadaptor.go | 6 ++-- dcrec/secp256k1/ellipticadaptor_test.go | 6 ++-- dcrec/secp256k1/field.go | 6 ++-- dcrec/secp256k1/pubkey.go | 8 ++--- dcrec/secp256k1/schnorr/signature.go | 4 +-- .../secp256k1/schnorr/signature_bench_test.go | 4 +-- 9 files changed, 41 insertions(+), 41 deletions(-) diff --git a/dcrec/secp256k1/curve.go b/dcrec/secp256k1/curve.go index c7748c378d..f2af9246fc 100644 --- a/dcrec/secp256k1/curve.go +++ b/dcrec/secp256k1/curve.go @@ -178,7 +178,7 @@ func addZ1AndZ2EqualsOne(p1, p2, result *JacobianPoint) { y3.Set(&v).Add(&negX3).Mul(&r).Add(&j) // Y3 = r*(V-X3)-2*Y1*J (mag: 4) z3.Set(&h).MulInt(2) // Z3 = 2*H (mag: 6) - // Normalize the resulting field values to a magnitude of 1 as needed. + // Normalize the resulting field values as needed. x3.Normalize() y3.Normalize() z3.Normalize() @@ -248,7 +248,7 @@ func addZ1EqualsZ2(p1, p2, result *JacobianPoint) { y3.Add(e.Add(&negX3).Mul(&c)) // Y3 = C*(E-X3)+Y3 (mag: 5) z3.Mul2(z1, &a) // Z3 = Z1*A (mag: 1) - // Normalize the resulting field values to a magnitude of 1 as needed. + // Normalize the resulting field values as needed. x3.Normalize() y3.Normalize() z3.Normalize() @@ -330,7 +330,7 @@ func addZ2EqualsOne(p1, p2, result *JacobianPoint) { z3.Add2(z1, &h).Square() // Z3 = (Z1+H)^2 (mag: 1) z3.Add(z1z1.Add(&hh).Negate(2)) // Z3 = Z3-(Z1Z1+HH) (mag: 4) - // Normalize the resulting field values to a magnitude of 1 as needed. + // Normalize the resulting field values as needed. x3.Normalize() y3.Normalize() z3.Normalize() @@ -397,7 +397,7 @@ func addGeneric(p1, p2, result *JacobianPoint) { var negU1, negS1, negX3 FieldVal negU1.Set(&u1).Negate(1) // negU1 = -U1 (mag: 2) h.Add2(&u2, &negU1) // H = U2-U1 (mag: 3) - i.Set(&h).MulInt(2).Square() // I = (2*H)^2 (mag: 2) + i.Set(&h).MulInt(2).Square() // I = (2*H)^2 (mag: 1) j.Mul2(&h, &i) // J = H*I (mag: 1) negS1.Set(&s1).Negate(1) // negS1 = -S1 (mag: 2) r.Set(&s2).Add(&negS1).MulInt(2) // r = 2*(S2-S1) (mag: 6) @@ -412,7 +412,7 @@ func addGeneric(p1, p2, result *JacobianPoint) { z3.Add(z1z1.Add(&z2z2).Negate(2)) // Z3 = Z3-(Z1Z1+Z2Z2) (mag: 4) z3.Mul(&h) // Z3 = Z3*H (mag: 1) - // Normalize the resulting field values to a magnitude of 1 as needed. + // Normalize the resulting field values as needed. x3.Normalize() y3.Normalize() z3.Normalize() @@ -424,7 +424,7 @@ func addGeneric(p1, p2, result *JacobianPoint) { // NOTE: The points must be normalized for this function to return the correct // result. The resulting point will be normalized. func AddNonConst(p1, p2, result *JacobianPoint) { - // A point at infinity is the identity according to the group law for + // The point at infinity is the identity according to the group law for // elliptic curve cryptography. Thus, ∞ + P = P and P + ∞ = P. if (p1.X.IsZero() && p1.Y.IsZero()) || p1.Z.IsZero() { result.Set(p2) @@ -508,7 +508,7 @@ func doubleZ1EqualsOne(p, result *JacobianPoint) { y3.Set(&c).MulInt(8).Negate(8) // Y3 = -(8*C) (mag: 9) y3.Add(f.Mul(&e)) // Y3 = E*F+Y3 (mag: 10) - // Normalize the field values back to a magnitude of 1. + // Normalize the resulting field values as needed. x3.Normalize() y3.Normalize() z3.Normalize() @@ -562,7 +562,7 @@ func doubleGeneric(p, result *JacobianPoint) { y3.Set(&c).MulInt(8).Negate(8) // Y3 = -(8*C) (mag: 9) y3.Add(f.Mul(&e)) // Y3 = E*F+Y3 (mag: 10) - // Normalize the field values back to a magnitude of 1. + // Normalize the resulting field values as needed. x3.Normalize() y3.Normalize() z3.Normalize() @@ -574,7 +574,7 @@ func doubleGeneric(p, result *JacobianPoint) { // NOTE: The point must be normalized for this function to return the correct // result. The resulting point will be normalized. func DoubleNonConst(p, result *JacobianPoint) { - // Doubling a point at infinity is still infinity. + // Doubling the point at infinity is still infinity. if p.Y.IsZero() || p.Z.IsZero() { result.X.SetInt(0) result.Y.SetInt(0) @@ -883,9 +883,9 @@ func ScalarMultNonConst(k *ModNScalar, point, result *JacobianPoint) { result.Set(&q) } -// ScalarBaseMultNonConst multiplies k*G where G is the base point of the group -// and k is a big endian integer. The result is stored in Jacobian coordinates -// (x1, y1, z1). +// ScalarBaseMultNonConst multiplies k*G where k is a scalar modulo the curve +// order and G is the base point of the group and stores the result in the +// provided Jacobian point. // // NOTE: The resulting point will be normalized. func ScalarBaseMultNonConst(k *ModNScalar, result *JacobianPoint) { @@ -894,10 +894,10 @@ func ScalarBaseMultNonConst(k *ModNScalar, result *JacobianPoint) { // Point Q = ∞ (point at infinity). var q JacobianPoint - // curve.bytePoints has all 256 byte points for each 8-bit window. The - // strategy is to add up the byte points. This is best understood by - // expressing k in base-256 which it already sort of is. Each "digit" in - // the 8-bit window can be looked up using bytePoints and added together. + // bytePoints has all 256 byte points for each 8-bit window. The strategy + // is to add up the byte points. This is best understood by expressing k in + // base-256 which it already sort of is. Each "digit" in the 8-bit window + // can be looked up using bytePoints and added together. var pt JacobianPoint for i, byteVal := range k.Bytes() { p := bytePoints[i][byteVal] diff --git a/dcrec/secp256k1/curve_test.go b/dcrec/secp256k1/curve_test.go index 800d1823ad..9f12223db6 100644 --- a/dcrec/secp256k1/curve_test.go +++ b/dcrec/secp256k1/curve_test.go @@ -58,7 +58,7 @@ func TestAddJacobian(t *testing.T) { x2, y2, z2 string // Coordinates (in hex) of second point to add x3, y3, z3 string // Coordinates (in hex) of expected point }{ - // Addition with a point at infinity (left hand side). + // Addition with the point at infinity (left hand side). // ∞ + P = P { "0", @@ -71,7 +71,7 @@ func TestAddJacobian(t *testing.T) { "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", "1", }, - // Addition with a point at infinity (right hand side). + // Addition with the point at infinity (right hand side). // P + ∞ = P { "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", @@ -286,7 +286,7 @@ func TestAddAffine(t *testing.T) { x2, y2 string // Coordinates (in hex) of second point to add x3, y3 string // Coordinates (in hex) of expected point }{ - // Addition with a point at infinity (left hand side). + // Addition with the point at infinity (left hand side). // ∞ + P = P { "0", @@ -296,7 +296,7 @@ func TestAddAffine(t *testing.T) { "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", }, - // Addition with a point at infinity (right hand side). + // Addition with the point at infinity (right hand side). // P + ∞ = P { "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", @@ -382,7 +382,7 @@ func TestDoubleJacobian(t *testing.T) { x1, y1, z1 string // Coordinates (in hex) of point to double x3, y3, z3 string // Coordinates (in hex) of expected point }{ - // Doubling a point at infinity is still infinity. + // Doubling the point at infinity is still infinity. { "0", "0", @@ -459,7 +459,7 @@ func TestDoubleAffine(t *testing.T) { x1, y1 string // Coordinates (in hex) of point to double x3, y3 string // Coordinates (in hex) of expected point }{ - // Doubling a point at infinity is still infinity. + // Doubling the point at infinity is still infinity. // 2*∞ = ∞ (point at infinity) { diff --git a/dcrec/secp256k1/ecdsa/bench_test.go b/dcrec/secp256k1/ecdsa/bench_test.go index 199bfe1a50..0ce00da21b 100644 --- a/dcrec/secp256k1/ecdsa/bench_test.go +++ b/dcrec/secp256k1/ecdsa/bench_test.go @@ -1,5 +1,5 @@ // Copyright 2013-2016 The btcsuite developers -// Copyright (c) 2015-2020 The Decred developers +// Copyright (c) 2015-2022 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -158,7 +158,7 @@ func BenchmarkSignCompact(b *testing.B) { } } -// BenchmarkSignCompact benchmarks how long it takes to recover a public key +// BenchmarkRecoverCompact benchmarks how long it takes to recover a public key // given a compact signature and message. func BenchmarkRecoverCompact(b *testing.B) { // Private key: 9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d diff --git a/dcrec/secp256k1/ellipticadaptor.go b/dcrec/secp256k1/ellipticadaptor.go index a271ff6b36..42022646b1 100644 --- a/dcrec/secp256k1/ellipticadaptor.go +++ b/dcrec/secp256k1/ellipticadaptor.go @@ -1,4 +1,4 @@ -// Copyright 2020-2021 The Decred developers +// Copyright 2020-2022 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -102,7 +102,7 @@ func (curve *KoblitzCurve) IsOnCurve(x, y *big.Int) bool { // // This is part of the elliptic.Curve interface implementation. func (curve *KoblitzCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) { - // A point at infinity is the identity according to the group law for + // The point at infinity is the identity according to the group law for // elliptic curve cryptography. Thus, ∞ + P = P and P + ∞ = P. if x1.Sign() == 0 && y1.Sign() == 0 { return x2, y2 @@ -249,7 +249,7 @@ var secp256k1 = &KoblitzCurve{ }, } -// S256 returns a Curve which implements secp256k1. +// S256 returns an elliptic.Curve which implements secp256k1. func S256() *KoblitzCurve { return secp256k1 } diff --git a/dcrec/secp256k1/ellipticadaptor_test.go b/dcrec/secp256k1/ellipticadaptor_test.go index da2b55c703..3b038cce57 100644 --- a/dcrec/secp256k1/ellipticadaptor_test.go +++ b/dcrec/secp256k1/ellipticadaptor_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Decred developers +// Copyright (c) 2020-2022 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -11,7 +11,7 @@ import ( ) // TestIsOnCurveAdaptor ensures the IsOnCurve method used to satisfy the -// elliptic.Curve interfaces works as intended. +// elliptic.Curve interface works as intended. func TestIsOnCurveAdaptor(t *testing.T) { s256 := S256() if !s256.IsOnCurve(s256.Params().Gx, s256.Params().Gy) { @@ -20,7 +20,7 @@ func TestIsOnCurveAdaptor(t *testing.T) { } // TestScalarBaseMultAdaptor ensures the ScalarBaseMult method used to satisfy -// the elliptic.Curve interfaces works as intended. +// the elliptic.Curve interface works as intended. func TestScalarBaseMultAdaptor(t *testing.T) { tests := []struct { k string diff --git a/dcrec/secp256k1/field.go b/dcrec/secp256k1/field.go index 43e27a91d2..2bf128fd86 100644 --- a/dcrec/secp256k1/field.go +++ b/dcrec/secp256k1/field.go @@ -1,6 +1,6 @@ // Copyright (c) 2013-2014 The btcsuite developers -// Copyright (c) 2015-2021 The Decred developers -// Copyright (c) 2013-2021 Dave Collins +// Copyright (c) 2015-2022 The Decred developers +// Copyright (c) 2013-2022 Dave Collins // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -1156,7 +1156,7 @@ func (f *FieldVal) SquareRootVal(val *FieldVal) bool { // 10111111 11111111 11111111 00001100 // // Notice that can be broken up into three windows of consecutive 1s (in - // order of least to most signifcant) as: + // order of least to most significant) as: // // 6-bit window with two bits set (bits 4, 5, 6, 7 unset) // 23-bit window with 22 bits set (bit 30 unset) diff --git a/dcrec/secp256k1/pubkey.go b/dcrec/secp256k1/pubkey.go index 67165833ff..23af4666c6 100644 --- a/dcrec/secp256k1/pubkey.go +++ b/dcrec/secp256k1/pubkey.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2014 The btcsuite developers -// Copyright (c) 2015-2021 The Decred developers +// Copyright (c) 2015-2022 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -209,9 +209,9 @@ func (p PublicKey) SerializeCompressed() []byte { return b[:] } -// IsEqual compares this PublicKey instance to the one passed, returning true if -// both PublicKeys are equivalent. A PublicKey is equivalent to another, if they -// both have the same X and Y coordinate. +// IsEqual compares this public key instance to the one passed, returning true +// if both public keys are equivalent. A public key is equivalent to another, +// if they both have the same X and Y coordinates. func (p *PublicKey) IsEqual(otherPubKey *PublicKey) bool { return p.x.Equals(&otherPubKey.x) && p.y.Equals(&otherPubKey.y) } diff --git a/dcrec/secp256k1/schnorr/signature.go b/dcrec/secp256k1/schnorr/signature.go index 9e454c26d3..f3099e4271 100644 --- a/dcrec/secp256k1/schnorr/signature.go +++ b/dcrec/secp256k1/schnorr/signature.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2014 The btcsuite developers -// Copyright (c) 2015-2020 The Decred developers +// Copyright (c) 2015-2022 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -51,7 +51,7 @@ func NewSignature(r *secp256k1.FieldVal, s *secp256k1.ModNScalar) *Signature { // Serialize returns the Schnorr signature in the more strict format. // -// The signatures are encoded as +// The signatures are encoded as: // sig[0:32] x coordinate of the point R, encoded as a big-endian uint256 // sig[32:64] s, encoded also as big-endian uint256 func (sig Signature) Serialize() []byte { diff --git a/dcrec/secp256k1/schnorr/signature_bench_test.go b/dcrec/secp256k1/schnorr/signature_bench_test.go index b9c8a831bd..4584b44979 100644 --- a/dcrec/secp256k1/schnorr/signature_bench_test.go +++ b/dcrec/secp256k1/schnorr/signature_bench_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015-2020 The Decred developers +// Copyright (c) 2015-2022 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -94,7 +94,7 @@ func BenchmarkSigVerify(b *testing.B) { } } -// BenchmarkSigVerify benchmarks how long it takes to serialize Schnorr +// BenchmarkSigSerialize benchmarks how long it takes to serialize Schnorr // signatures. func BenchmarkSigSerialize(b *testing.B) { // From randomly generated keypair. From 087aa1ab79d167598eefd920a666ebf77de3c192 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 Feb 2022 02:41:23 -0600 Subject: [PATCH 02/16] secp256k1: Benchmark consistency pass. This makes several of the benchmarks more consistent in terms of resetting the timer and reporting memory allocations as well as moves the ones that only apply when accessing the package via the standard library elliptic curve adaptor code to a new file named ellipticadaptor_bench_test.go. It also converts the ecdsa signature verification benchmark to use bytes directly instead of standard lib big ints and remove the no longer necessary hexToBigInt helper. --- dcrec/secp256k1/bench_test.go | 48 +++++++---------- dcrec/secp256k1/ecdsa/bench_test.go | 23 ++------ dcrec/secp256k1/ellipticadaptor_bench_test.go | 54 +++++++++++++++++++ dcrec/secp256k1/field_bench_test.go | 5 +- dcrec/secp256k1/modnscalar_bench_test.go | 7 ++- 5 files changed, 88 insertions(+), 49 deletions(-) create mode 100644 dcrec/secp256k1/ellipticadaptor_bench_test.go diff --git a/dcrec/secp256k1/bench_test.go b/dcrec/secp256k1/bench_test.go index b5424e82e3..23bfa46d37 100644 --- a/dcrec/secp256k1/bench_test.go +++ b/dcrec/secp256k1/bench_test.go @@ -1,5 +1,5 @@ // Copyright 2013-2016 The btcsuite developers -// Copyright (c) 2015-2021 The Decred developers +// Copyright (c) 2015-2022 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -52,44 +52,34 @@ func BenchmarkAddNonConstNotZOne(b *testing.B) { } } -// BenchmarkScalarBaseMult benchmarks the secp256k1 curve ScalarBaseMult -// function. -func BenchmarkScalarBaseMult(b *testing.B) { - k := fromHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575") - curve := S256() - for i := 0; i < b.N; i++ { - curve.ScalarBaseMult(k.Bytes()) - } -} - -// BenchmarkScalarBaseMultNonConst benchmarks the ScalarBaseMultNonConst -// function. +// BenchmarkScalarBaseMultNonConst benchmarks multiplying a scalar by the base +// point of the curve. func BenchmarkScalarBaseMultNonConst(b *testing.B) { k := new(ModNScalar).SetHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575") + + b.ReportAllocs() + b.ResetTimer() var result JacobianPoint for i := 0; i < b.N; i++ { ScalarBaseMultNonConst(k, &result) } } -// BenchmarkScalarBaseMultLarge benchmarks the secp256k1 curve ScalarBaseMult -// function with abnormally large k values. -func BenchmarkScalarBaseMultLarge(b *testing.B) { - k := fromHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c005751111111011111110") - curve := S256() - for i := 0; i < b.N; i++ { - curve.ScalarBaseMult(k.Bytes()) - } -} +// BenchmarkScalarMultNonConst benchmarks multiplying a scalar by an arbitrary +// point on the curve. +func BenchmarkScalarMultNonConst(b *testing.B) { + k := new(ModNScalar).SetHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575") + point := jacobianPointFromHex( + "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", + "1", + ) -// BenchmarkScalarMult benchmarks the secp256k1 curve ScalarMult function. -func BenchmarkScalarMult(b *testing.B) { - x := fromHex("34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6") - y := fromHex("0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232") - k := fromHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575") - curve := S256() + b.ReportAllocs() + b.ResetTimer() + var result JacobianPoint for i := 0; i < b.N; i++ { - curve.ScalarMult(x, y, k.Bytes()) + ScalarMultNonConst(k, &point, &result) } } diff --git a/dcrec/secp256k1/ecdsa/bench_test.go b/dcrec/secp256k1/ecdsa/bench_test.go index 0ce00da21b..ed0c99d67a 100644 --- a/dcrec/secp256k1/ecdsa/bench_test.go +++ b/dcrec/secp256k1/ecdsa/bench_test.go @@ -7,7 +7,6 @@ package ecdsa import ( "encoding/hex" - "math/big" "testing" "github.com/decred/dcrd/dcrec/secp256k1/v4" @@ -29,18 +28,6 @@ func hexToModNScalar(s string) *secp256k1.ModNScalar { return &scalar } -// hexToBigInt converts the passed hex string into a big integer pointer and -// will panic is there is an error. This is only provided for the hard-coded -// constants so errors in the source code can bet detected. It will only (and -// must only) be called for initialization purposes. -func hexToBigInt(s string) *big.Int { - r, ok := new(big.Int).SetString(s, 16) - if !ok { - panic("invalid hex in source file: " + s) - } - return r -} - // hexToFieldVal converts the passed hex string into a FieldVal and will panic // if there is an error. This is only provided for the hard-coded constants so // errors in the source code can be detected. It will only (and must only) be @@ -60,7 +47,6 @@ func hexToFieldVal(s string) *secp256k1.FieldVal { // BenchmarkSigVerify benchmarks how long it takes the secp256k1 curve to // verify signatures. func BenchmarkSigVerify(b *testing.B) { - b.StopTimer() // Randomly generated keypair. // Private key: 9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d pubKey := secp256k1.NewPublicKey( @@ -69,20 +55,21 @@ func BenchmarkSigVerify(b *testing.B) { ) // Double sha256 of []byte{0x01, 0x02, 0x03, 0x04} - msgHash := hexToBigInt("8de472e2399610baaa7f84840547cd409434e31f5d3bd71e4d947f283874f9c0") + msgHash := hexToBytes("8de472e2399610baaa7f84840547cd409434e31f5d3bd71e4d947f283874f9c0") sig := NewSignature( hexToModNScalar("fef45d2892953aa5bbcdb057b5e98b208f1617a7498af7eb765574e29b5d9c2c"), hexToModNScalar("d47563f52aac6b04b55de236b7c515eb9311757db01e02cff079c3ca6efb063f"), ) - if !sig.Verify(msgHash.Bytes(), pubKey) { + if !sig.Verify(msgHash, pubKey) { b.Errorf("Signature failed to verify") return } - b.StartTimer() + b.ReportAllocs() + b.ResetTimer() for i := 0; i < b.N; i++ { - sig.Verify(msgHash.Bytes(), pubKey) + sig.Verify(msgHash, pubKey) } } diff --git a/dcrec/secp256k1/ellipticadaptor_bench_test.go b/dcrec/secp256k1/ellipticadaptor_bench_test.go new file mode 100644 index 0000000000..5b531d98a8 --- /dev/null +++ b/dcrec/secp256k1/ellipticadaptor_bench_test.go @@ -0,0 +1,54 @@ +// Copyright 2013-2016 The btcsuite developers +// Copyright (c) 2015-2022 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package secp256k1 + +import ( + "testing" +) + +// BenchmarkScalarBaseMultAdaptor benchmarks multiplying a scalar by the base +// point of the curve via the method used to satisfy the elliptic.Curve +// interface. +func BenchmarkScalarBaseMultAdaptor(b *testing.B) { + k := fromHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575") + curve := S256() + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + curve.ScalarBaseMult(k.Bytes()) + } +} + +// BenchmarkScalarBaseMultLargeAdaptor benchmarks multiplying an abnormally +// large scalar by the base point of the curve via the method used to satisfy +// the elliptic.Curve interface. +func BenchmarkScalarBaseMultLargeAdaptor(b *testing.B) { + k := fromHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c005751111111011111110") + curve := S256() + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + curve.ScalarBaseMult(k.Bytes()) + } +} + +// BenchmarkScalarMultAdaptor benchmarks multiplying a scalar by an arbitrary +// point on the curve via the method used to satisfy the elliptic.Curve +// interface. +func BenchmarkScalarMultAdaptor(b *testing.B) { + x := fromHex("34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6") + y := fromHex("0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232") + k := fromHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575") + curve := S256() + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + curve.ScalarMult(x, y, k.Bytes()) + } +} diff --git a/dcrec/secp256k1/field_bench_test.go b/dcrec/secp256k1/field_bench_test.go index e7937ba646..ec77447f07 100644 --- a/dcrec/secp256k1/field_bench_test.go +++ b/dcrec/secp256k1/field_bench_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Decred developers +// Copyright (c) 2020-2022 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -17,6 +17,9 @@ func BenchmarkFieldNormalize(b *testing.B) { 0x000148f6, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x00000007, }} + + b.ReportAllocs() + b.ResetTimer() for i := 0; i < b.N; i++ { f.Normalize() } diff --git a/dcrec/secp256k1/modnscalar_bench_test.go b/dcrec/secp256k1/modnscalar_bench_test.go index 3a99816411..aeb160eceb 100644 --- a/dcrec/secp256k1/modnscalar_bench_test.go +++ b/dcrec/secp256k1/modnscalar_bench_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Decred developers +// Copyright (c) 2020-2022 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -22,6 +22,9 @@ func benchmarkVals() [2][]byte { // big-endian integer modulo the group order with stdlib big integers. func BenchmarkBigIntModN(b *testing.B) { buf := benchmarkVals()[0] + + b.ReportAllocs() + b.ResetTimer() for i := 0; i < b.N; i++ { v := new(big.Int).SetBytes(buf) v.Mod(v, curveParams.N) @@ -35,6 +38,8 @@ func BenchmarkModNScalar(b *testing.B) { var buf [32]byte copy(buf[:], slice) + b.ReportAllocs() + b.ResetTimer() for i := 0; i < b.N; i++ { var s ModNScalar s.SetBytes(&buf) From 39c2b1867595cd80e972817e458492f1eff4de15 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 Feb 2022 02:41:25 -0600 Subject: [PATCH 03/16] secp256k1: Consolidate Jacobian group chk in tests. In order to simplify and consolidate the test logic that ensures test points in Jacobian coordinates are valid group elements, this modifies the test helper function that determines if a given Jacobian point is on the secp256k1 curve to also check for the point at infinity, renames it to isValidJacobianPoint, and updates all callers accordingly. --- dcrec/secp256k1/curve_test.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/dcrec/secp256k1/curve_test.go b/dcrec/secp256k1/curve_test.go index 9f12223db6..c4992ee519 100644 --- a/dcrec/secp256k1/curve_test.go +++ b/dcrec/secp256k1/curve_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015-2021 The Decred developers +// Copyright (c) 2015-2022 The Decred developers // Copyright 2013-2016 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -14,9 +14,13 @@ import ( "time" ) -// isJacobianOnS256Curve returns boolean if the point (x,y,z) is on the -// secp256k1 curve. -func isJacobianOnS256Curve(point *JacobianPoint) bool { +// isValidJacobianPoint returns true if the point (x,y,z) is on the secp256k1 +// curve or is the point at infinity. +func isValidJacobianPoint(point *JacobianPoint) bool { + if (point.X.IsZero() && point.Y.IsZero()) || point.Z.IsZero() { + return true + } + // Elliptic curve equation for secp256k1 is: y^2 = x^3 + 7 // In Jacobian coordinates, Y = y/z^3 and X = x/z^2 // Thus: @@ -250,17 +254,17 @@ func TestAddJacobian(t *testing.T) { // Ensure the test data is using points that are actually on // the curve (or the point at infinity). - if !p1.Z.IsZero() && !isJacobianOnS256Curve(&p1) { + if !isValidJacobianPoint(&p1) { t.Errorf("#%d first point is not on the curve -- "+ "invalid test data", i) continue } - if !p2.Z.IsZero() && !isJacobianOnS256Curve(&p2) { + if !isValidJacobianPoint(&p2) { t.Errorf("#%d second point is not on the curve -- "+ "invalid test data", i) continue } - if !want.Z.IsZero() && !isJacobianOnS256Curve(&want) { + if !isValidJacobianPoint(&want) { t.Errorf("#%d expected point is not on the curve -- "+ "invalid test data", i) continue @@ -428,12 +432,12 @@ func TestDoubleJacobian(t *testing.T) { // Ensure the test data is using points that are actually on // the curve (or the point at infinity). - if !p1.Z.IsZero() && !isJacobianOnS256Curve(&p1) { + if !isValidJacobianPoint(&p1) { t.Errorf("#%d first point is not on the curve -- "+ "invalid test data", i) continue } - if !want.Z.IsZero() && !isJacobianOnS256Curve(&want) { + if !isValidJacobianPoint(&want) { t.Errorf("#%d expected point is not on the curve -- "+ "invalid test data", i) continue From 1360ae65bdefd12ca73ebdb2fbda820c63a58ef3 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 Feb 2022 02:41:26 -0600 Subject: [PATCH 04/16] secp256k1: Consolidate affine group check in tests. In order to simplify and consolidate the test logic that ensures test points in affine coordinates are valid group elements, this introduces a new test helper function that determines if a given affine point is on the secp256k1 curve or is the point at infinity and updates all callers accordingly. --- dcrec/secp256k1/curve_test.go | 10 +++++----- dcrec/secp256k1/ellipticadaptor_test.go | 9 +++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/dcrec/secp256k1/curve_test.go b/dcrec/secp256k1/curve_test.go index c4992ee519..02fdd48ab6 100644 --- a/dcrec/secp256k1/curve_test.go +++ b/dcrec/secp256k1/curve_test.go @@ -351,17 +351,17 @@ func TestAddAffine(t *testing.T) { // Ensure the test data is using points that are actually on // the curve (or the point at infinity). - if !(x1.Sign() == 0 && y1.Sign() == 0) && !S256().IsOnCurve(x1, y1) { + if !isValidAffinePoint(x1, y1) { t.Errorf("#%d first point is not on the curve -- "+ "invalid test data", i) continue } - if !(x2.Sign() == 0 && y2.Sign() == 0) && !S256().IsOnCurve(x2, y2) { + if !isValidAffinePoint(x2, y2) { t.Errorf("#%d second point is not on the curve -- "+ "invalid test data", i) continue } - if !(x3.Sign() == 0 && y3.Sign() == 0) && !S256().IsOnCurve(x3, y3) { + if !isValidAffinePoint(x3, y3) { t.Errorf("#%d expected point is not on the curve -- "+ "invalid test data", i) continue @@ -508,12 +508,12 @@ func TestDoubleAffine(t *testing.T) { // Ensure the test data is using points that are actually on // the curve (or the point at infinity). - if !(x1.Sign() == 0 && y1.Sign() == 0) && !S256().IsOnCurve(x1, y1) { + if !isValidAffinePoint(x1, y1) { t.Errorf("#%d first point is not on the curve -- "+ "invalid test data", i) continue } - if !(x3.Sign() == 0 && y3.Sign() == 0) && !S256().IsOnCurve(x3, y3) { + if !isValidAffinePoint(x3, y3) { t.Errorf("#%d expected point is not on the curve -- "+ "invalid test data", i) continue diff --git a/dcrec/secp256k1/ellipticadaptor_test.go b/dcrec/secp256k1/ellipticadaptor_test.go index 3b038cce57..0f4f928f22 100644 --- a/dcrec/secp256k1/ellipticadaptor_test.go +++ b/dcrec/secp256k1/ellipticadaptor_test.go @@ -19,6 +19,15 @@ func TestIsOnCurveAdaptor(t *testing.T) { } } +// isValidAffinePoint returns true if the point (x,y) is on the secp256k1 curve +// or is the point at infinity. +func isValidAffinePoint(x, y *big.Int) bool { + if x.Sign() == 0 && y.Sign() == 0 { + return true + } + return S256().IsOnCurve(x, y) +} + // TestScalarBaseMultAdaptor ensures the ScalarBaseMult method used to satisfy // the elliptic.Curve interface works as intended. func TestScalarBaseMultAdaptor(t *testing.T) { From f2b42cccf555a63ac6c24877dcbce86691edf6b5 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 Feb 2022 02:41:27 -0600 Subject: [PATCH 05/16] secp256k1: Cleanup and move affine addition tests. This cleans up the tests for performing affine addition to make them more consistent with modern practices in the code and also moves them to the ellipticadaptor_test.go file since they only apply when accessing the package via the standard library elliptic curve adaptor code. The following is a high level overview of the changes: - Renames TestAddAffine to TestAddAffineAdaptor to make it clear it is testing via the adaptor - Adds names to each individual test for easier identification - Makes the individual test definitions consistent with the rest of the code --- dcrec/secp256k1/curve_test.go | 96 ------------------------- dcrec/secp256k1/ellipticadaptor_test.go | 88 +++++++++++++++++++++++ 2 files changed, 88 insertions(+), 96 deletions(-) diff --git a/dcrec/secp256k1/curve_test.go b/dcrec/secp256k1/curve_test.go index 02fdd48ab6..11b1dbc7ad 100644 --- a/dcrec/secp256k1/curve_test.go +++ b/dcrec/secp256k1/curve_test.go @@ -283,102 +283,6 @@ func TestAddJacobian(t *testing.T) { } } -// TestAddAffine tests addition of points in affine coordinates. -func TestAddAffine(t *testing.T) { - tests := []struct { - x1, y1 string // Coordinates (in hex) of first point to add - x2, y2 string // Coordinates (in hex) of second point to add - x3, y3 string // Coordinates (in hex) of expected point - }{ - // Addition with the point at infinity (left hand side). - // ∞ + P = P - { - "0", - "0", - "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", - "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", - "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", - "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", - }, - // Addition with the point at infinity (right hand side). - // P + ∞ = P - { - "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", - "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", - "0", - "0", - "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", - "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", - }, - - // Addition with different x values. - { - "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", - "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", - "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", - "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", - "fd5b88c21d3143518d522cd2796f3d726793c88b3e05636bc829448e053fed69", - "21cf4f6a5be5ff6380234c50424a970b1f7e718f5eb58f68198c108d642a137f", - }, - // Addition with same x opposite y. - // P(x, y) + P(x, -y) = infinity - { - "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", - "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", - "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", - "f48e156428cf0276dc092da5856e182288d7569f97934a56fe44be60f0d359fd", - "0", - "0", - }, - // Addition with same point. - // P(x, y) + P(x, y) = 2P - { - "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", - "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", - "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", - "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", - "59477d88ae64a104dbb8d31ec4ce2d91b2fe50fa628fb6a064e22582196b365b", - "938dc8c0f13d1e75c987cb1a220501bd614b0d3dd9eb5c639847e1240216e3b6", - }, - } - - t.Logf("Running %d tests", len(tests)) - for i, test := range tests { - // Convert hex to field values. - x1, y1 := fromHex(test.x1), fromHex(test.y1) - x2, y2 := fromHex(test.x2), fromHex(test.y2) - x3, y3 := fromHex(test.x3), fromHex(test.y3) - - // Ensure the test data is using points that are actually on - // the curve (or the point at infinity). - if !isValidAffinePoint(x1, y1) { - t.Errorf("#%d first point is not on the curve -- "+ - "invalid test data", i) - continue - } - if !isValidAffinePoint(x2, y2) { - t.Errorf("#%d second point is not on the curve -- "+ - "invalid test data", i) - continue - } - if !isValidAffinePoint(x3, y3) { - t.Errorf("#%d expected point is not on the curve -- "+ - "invalid test data", i) - continue - } - - // Add the two points. - rx, ry := S256().Add(x1, y1, x2, y2) - - // Ensure result matches expected. - if rx.Cmp(x3) != 00 || ry.Cmp(y3) != 0 { - t.Errorf("#%d wrong result\ngot: (%x, %x)\n"+ - "want: (%x, %x)", i, rx, ry, x3, y3) - continue - } - } -} - // TestDoubleJacobian tests doubling of points projected in Jacobian // coordinates. func TestDoubleJacobian(t *testing.T) { diff --git a/dcrec/secp256k1/ellipticadaptor_test.go b/dcrec/secp256k1/ellipticadaptor_test.go index 0f4f928f22..f7614d7ea7 100644 --- a/dcrec/secp256k1/ellipticadaptor_test.go +++ b/dcrec/secp256k1/ellipticadaptor_test.go @@ -28,6 +28,94 @@ func isValidAffinePoint(x, y *big.Int) bool { return S256().IsOnCurve(x, y) } +// TestAddAffineAdaptor tests addition of points in affine coordinates via the +// method used to satisfy the elliptic.Curve interface works as intended for +// some edge cases and known good values. +func TestAddAffineAdaptor(t *testing.T) { + tests := []struct { + name string // test description + x1, y1 string // hex encoded coordinates of first point to add + x2, y2 string // hex encoded coordinates of second point to add + x3, y3 string // hex encoded coordinates of expected point + }{{ + // Addition with the point at infinity (left hand side). + name: "∞ + P = P", + x1: "0", + y1: "0", + x2: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", + y2: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", + x3: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", + y3: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", + }, { + // Addition with the point at infinity (right hand side). + name: "P + ∞ = P", + x1: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", + y1: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", + x2: "0", + y2: "0", + x3: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", + y3: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", + }, { + // Addition with different x values. + name: "P(x1, y1) + P(x2, y2)", + x1: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + y1: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", + x2: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", + y2: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", + x3: "fd5b88c21d3143518d522cd2796f3d726793c88b3e05636bc829448e053fed69", + y3: "21cf4f6a5be5ff6380234c50424a970b1f7e718f5eb58f68198c108d642a137f", + }, { + // Addition with same x opposite y. + name: "P(x, y) + P(x, -y) = ∞", + x1: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + y1: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", + x2: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + y2: "f48e156428cf0276dc092da5856e182288d7569f97934a56fe44be60f0d359fd", + x3: "0", + y3: "0", + }, { + // Addition with same point. + name: "P(x, y) + P(x, y) = 2P", + x1: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + y1: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", + x2: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + y2: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", + x3: "59477d88ae64a104dbb8d31ec4ce2d91b2fe50fa628fb6a064e22582196b365b", + y3: "938dc8c0f13d1e75c987cb1a220501bd614b0d3dd9eb5c639847e1240216e3b6", + }} + + curve := S256() + for _, test := range tests { + // Parse the test data. + x1, y1 := fromHex(test.x1), fromHex(test.y1) + x2, y2 := fromHex(test.x2), fromHex(test.y2) + x3, y3 := fromHex(test.x3), fromHex(test.y3) + + // Ensure the test data is using points that are actually on the curve + // (or the point at infinity). + if !isValidAffinePoint(x1, y1) { + t.Errorf("%s: first point is not on curve", test.name) + continue + } + if !isValidAffinePoint(x2, y2) { + t.Errorf("%s: second point is not on curve", test.name) + continue + } + if !isValidAffinePoint(x3, y3) { + t.Errorf("%s: expected point is not on curve", test.name) + continue + } + + // Add the two points and ensure the result matches expected. + rx, ry := curve.Add(x1, y1, x2, y2) + if rx.Cmp(x3) != 0 || ry.Cmp(y3) != 0 { + t.Errorf("%s: wrong result\ngot: (%x, %x)\nwant: (%x, %x)", + test.name, rx, ry, x3, y3) + continue + } + } +} + // TestScalarBaseMultAdaptor ensures the ScalarBaseMult method used to satisfy // the elliptic.Curve interface works as intended. func TestScalarBaseMultAdaptor(t *testing.T) { From 22185c13aa0591ce462e1e89672345b88adecba3 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 Feb 2022 02:41:29 -0600 Subject: [PATCH 06/16] secp256k1: Cleanup and move affine double tests. This cleans up the tests for performing affine point doubling to make them more consistent with modern practices in the code and also moves them to the ellipticadaptor_test.go file since they only apply when accessing the package via the standard library elliptic curve adaptor code. The following is a high level overview of the changes: - Renames TestDoubleAffine to TestDoubleAffineAdaptor to make it clear it is testing via the adaptor - Adds names to each individual test for easier identification - Makes the individual test definitions consistent with the rest of the code --- dcrec/secp256k1/curve_test.go | 74 ------------------------- dcrec/secp256k1/ellipticadaptor_test.go | 68 +++++++++++++++++++++++ 2 files changed, 68 insertions(+), 74 deletions(-) diff --git a/dcrec/secp256k1/curve_test.go b/dcrec/secp256k1/curve_test.go index 11b1dbc7ad..42c2175f7c 100644 --- a/dcrec/secp256k1/curve_test.go +++ b/dcrec/secp256k1/curve_test.go @@ -361,80 +361,6 @@ func TestDoubleJacobian(t *testing.T) { } } -// TestDoubleAffine tests doubling of points in affine coordinates. -func TestDoubleAffine(t *testing.T) { - tests := []struct { - x1, y1 string // Coordinates (in hex) of point to double - x3, y3 string // Coordinates (in hex) of expected point - }{ - // Doubling the point at infinity is still infinity. - // 2*∞ = ∞ (point at infinity) - - { - "0", - "0", - "0", - "0", - }, - - // Random points. - { - "e41387ffd8baaeeb43c2faa44e141b19790e8ac1f7ff43d480dc132230536f86", - "1b88191d430f559896149c86cbcb703193105e3cf3213c0c3556399836a2b899", - "88da47a089d333371bd798c548ef7caae76e737c1980b452d367b3cfe3082c19", - "3b6f659b09a362821dfcfefdbfbc2e59b935ba081b6c249eb147b3c2100b1bc1", - }, - { - "b3589b5d984f03ef7c80aeae444f919374799edf18d375cab10489a3009cff0c", - "c26cf343875b3630e15bccc61202815b5d8f1fd11308934a584a5babe69db36a", - "e193860172998751e527bb12563855602a227fc1f612523394da53b746bb2fb1", - "2bfcf13d2f5ab8bb5c611fab5ebbed3dc2f057062b39a335224c22f090c04789", - }, - { - "2b31a40fbebe3440d43ac28dba23eee71c62762c3fe3dbd88b4ab82dc6a82340", - "9ba7deb02f5c010e217607fd49d58db78ec273371ea828b49891ce2fd74959a1", - "2c8d5ef0d343b1a1a48aa336078eadda8481cb048d9305dc4fdf7ee5f65973a2", - "bb4914ac729e26d3cd8f8dc8f702f3f4bb7e0e9c5ae43335f6e94c2de6c3dc95", - }, - { - "61c64b760b51981fab54716d5078ab7dffc93730b1d1823477e27c51f6904c7a", - "ef6eb16ea1a36af69d7f66524c75a3a5e84c13be8fbc2e811e0563c5405e49bd", - "5f0dcdd2595f5ad83318a0f9da481039e36f135005420393e72dfca985b482f4", - "a01c849b0837065c1cb481b0932c441f49d1cab1b4b9f355c35173d93f110ae0", - }, - } - - t.Logf("Running %d tests", len(tests)) - for i, test := range tests { - // Convert hex to field values. - x1, y1 := fromHex(test.x1), fromHex(test.y1) - x3, y3 := fromHex(test.x3), fromHex(test.y3) - - // Ensure the test data is using points that are actually on - // the curve (or the point at infinity). - if !isValidAffinePoint(x1, y1) { - t.Errorf("#%d first point is not on the curve -- "+ - "invalid test data", i) - continue - } - if !isValidAffinePoint(x3, y3) { - t.Errorf("#%d expected point is not on the curve -- "+ - "invalid test data", i) - continue - } - - // Double the point. - rx, ry := S256().Double(x1, y1) - - // Ensure result matches expected. - if rx.Cmp(x3) != 00 || ry.Cmp(y3) != 0 { - t.Errorf("#%d wrong result\ngot: (%x, %x)\n"+ - "want: (%x, %x)", i, rx, ry, x3, y3) - continue - } - } -} - // checkNAFEncoding returns an error if the provided positive and negative // portions of an overall NAF encoding do not adhere to the requirements or they // do not sum back to the provided original value. diff --git a/dcrec/secp256k1/ellipticadaptor_test.go b/dcrec/secp256k1/ellipticadaptor_test.go index f7614d7ea7..0e2e7dab32 100644 --- a/dcrec/secp256k1/ellipticadaptor_test.go +++ b/dcrec/secp256k1/ellipticadaptor_test.go @@ -116,6 +116,74 @@ func TestAddAffineAdaptor(t *testing.T) { } } +// TestDoubleAffineAdaptor tests doubling of points in affine coordinates via +// the method used to satisfy the elliptic.Curve interface works as intended for +// some edge cases and known good values. +func TestDoubleAffineAdaptor(t *testing.T) { + tests := []struct { + name string // test description + x1, y1 string // hex encoded coordinates of point to double + x3, y3 string // hex encoded coordinates of expected point + }{{ + // Doubling the point at infinity is still the point at infinity. + name: "2*∞ = ∞ (point at infinity)", + x1: "0", + y1: "0", + x3: "0", + y3: "0", + }, { + name: "random point 1", + x1: "e41387ffd8baaeeb43c2faa44e141b19790e8ac1f7ff43d480dc132230536f86", + y1: "1b88191d430f559896149c86cbcb703193105e3cf3213c0c3556399836a2b899", + x3: "88da47a089d333371bd798c548ef7caae76e737c1980b452d367b3cfe3082c19", + y3: "3b6f659b09a362821dfcfefdbfbc2e59b935ba081b6c249eb147b3c2100b1bc1", + }, { + name: "random point 2", + x1: "b3589b5d984f03ef7c80aeae444f919374799edf18d375cab10489a3009cff0c", + y1: "c26cf343875b3630e15bccc61202815b5d8f1fd11308934a584a5babe69db36a", + x3: "e193860172998751e527bb12563855602a227fc1f612523394da53b746bb2fb1", + y3: "2bfcf13d2f5ab8bb5c611fab5ebbed3dc2f057062b39a335224c22f090c04789", + }, { + name: "random point 3", + x1: "2b31a40fbebe3440d43ac28dba23eee71c62762c3fe3dbd88b4ab82dc6a82340", + y1: "9ba7deb02f5c010e217607fd49d58db78ec273371ea828b49891ce2fd74959a1", + x3: "2c8d5ef0d343b1a1a48aa336078eadda8481cb048d9305dc4fdf7ee5f65973a2", + y3: "bb4914ac729e26d3cd8f8dc8f702f3f4bb7e0e9c5ae43335f6e94c2de6c3dc95", + }, { + name: "random point 4", + x1: "61c64b760b51981fab54716d5078ab7dffc93730b1d1823477e27c51f6904c7a", + y1: "ef6eb16ea1a36af69d7f66524c75a3a5e84c13be8fbc2e811e0563c5405e49bd", + x3: "5f0dcdd2595f5ad83318a0f9da481039e36f135005420393e72dfca985b482f4", + y3: "a01c849b0837065c1cb481b0932c441f49d1cab1b4b9f355c35173d93f110ae0", + }} + + curve := S256() + for _, test := range tests { + // Parse test data. + x1, y1 := fromHex(test.x1), fromHex(test.y1) + x3, y3 := fromHex(test.x3), fromHex(test.y3) + + // Ensure the test data is using points that are actually on + // the curve (or the point at infinity). + if !isValidAffinePoint(x1, y1) { + t.Errorf("%s: first point is not on the curve", test.name) + continue + } + if !isValidAffinePoint(x3, y3) { + t.Errorf("%s: expected point is not on the curve", test.name) + continue + } + + // Double the point and ensure the result matches expected. + rx, ry := curve.Double(x1, y1) + if rx.Cmp(x3) != 0 || ry.Cmp(y3) != 0 { + t.Errorf("%s: wrong result\ngot: (%x, %x)\nwant: (%x, %x)", + test.name, rx, ry, x3, y3) + continue + } + } +} + // TestScalarBaseMultAdaptor ensures the ScalarBaseMult method used to satisfy // the elliptic.Curve interface works as intended. func TestScalarBaseMultAdaptor(t *testing.T) { From f057e9b946a86ade4d5250e2efb189a32d981fd8 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 Feb 2022 02:41:30 -0600 Subject: [PATCH 07/16] secp256k1: Cleanup and move key generation tests. This cleans up the test for key generation to make it more consistent with modern practices in the code and also moves it to the privkey_test.go file since the associated function is defined in privkey.go. The following is a high level overview of the changes: - Rename TestKeyGeneration to TestGeneratePrivateKey to match the func name - Remove the curve-based indirection since it only works with secp256k1 --- dcrec/secp256k1/curve_test.go | 17 ----------------- dcrec/secp256k1/privkey_test.go | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/dcrec/secp256k1/curve_test.go b/dcrec/secp256k1/curve_test.go index 42c2175f7c..2a078d7d17 100644 --- a/dcrec/secp256k1/curve_test.go +++ b/dcrec/secp256k1/curve_test.go @@ -688,23 +688,6 @@ func TestSplitKRand(t *testing.T) { } } -// Test this curve's usage with the ecdsa package. -func testKeyGeneration(t *testing.T, tag string) { - priv, err := GeneratePrivateKey() - if err != nil { - t.Errorf("%s: error: %s", tag, err) - return - } - pub := priv.PubKey() - if !isOnCurve(&pub.x, &pub.y) { - t.Errorf("%s: public key invalid: %s", tag, err) - } -} - -func TestKeyGeneration(t *testing.T) { - testKeyGeneration(t, "S256") -} - // TestDecompressY ensures that decompressY works as expected for some edge // cases. func TestDecompressY(t *testing.T) { diff --git a/dcrec/secp256k1/privkey_test.go b/dcrec/secp256k1/privkey_test.go index bb3f541196..f89b6fbe3d 100644 --- a/dcrec/secp256k1/privkey_test.go +++ b/dcrec/secp256k1/privkey_test.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2020 The Decred developers +// Copyright (c) 2015-2022 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -10,6 +10,19 @@ import ( "testing" ) +// TestGeneratePrivateKey ensures the key generation works as expected. +func TestGeneratePrivateKey(t *testing.T) { + priv, err := GeneratePrivateKey() + if err != nil { + t.Errorf("failed to generate private key: %s", err) + return + } + pub := priv.PubKey() + if !isOnCurve(&pub.x, &pub.y) { + t.Error("public key is not on the curve") + } +} + func TestPrivKeys(t *testing.T) { tests := []struct { name string From 3b1afe632ac8eebfd8b41cbd32d0de1ee72ef3c9 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 Feb 2022 02:41:32 -0600 Subject: [PATCH 08/16] secp256k1: Cleanup and move affine scalar mul tests. This cleans up the tests for performing affine scalar point multiplication to make them more consistent with modern practices in the code and also moves them to the ellipticadaptor_test.go file since they only apply when accessing the package via the standard library elliptic curve adaptor code. The following is a high level overview of the changes: - Renames TestScalarMult to TestScalarMultAdaptor to make it clear it is testing via the adaptor - Adds names to each individual test for easier identification - Makes the individual test definitions consistent with the rest of the code - Adds some additional edge cases --- dcrec/secp256k1/curve_test.go | 40 -------- dcrec/secp256k1/ellipticadaptor_test.go | 117 ++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 40 deletions(-) diff --git a/dcrec/secp256k1/curve_test.go b/dcrec/secp256k1/curve_test.go index 2a078d7d17..6ab9070f86 100644 --- a/dcrec/secp256k1/curve_test.go +++ b/dcrec/secp256k1/curve_test.go @@ -502,46 +502,6 @@ func TestBaseMultVerify(t *testing.T) { } } -func TestScalarMult(t *testing.T) { - tests := []struct { - x string - y string - k string - rx string - ry string - }{ - // base mult, essentially. - { - "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", - "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", - "18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725", - "50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352", - "2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6", - }, - // From btcd issue #709. - { - "000000000000000000000000000000000000000000000000000000000000002c", - "420e7a99bba18a9d3952597510fd2b6728cfeafc21a4e73951091d4d8ddbe94e", - "a2e8ba2e8ba2e8ba2e8ba2e8ba2e8ba219b51835b55cc30ebfe2f6599bc56f58", - "a2112dcdfbcd10ae1133a358de7b82db68e0a3eb4b492cc8268d1e7118c98788", - "27fc7463b7bb3c5f98ecf2c84a6272bb1681ed553d92c69f2dfe25a9f9fd3836", - }, - } - - s256 := S256() - for i, test := range tests { - x, _ := new(big.Int).SetString(test.x, 16) - y, _ := new(big.Int).SetString(test.y, 16) - k, _ := new(big.Int).SetString(test.k, 16) - xWant, _ := new(big.Int).SetString(test.rx, 16) - yWant, _ := new(big.Int).SetString(test.ry, 16) - xGot, yGot := s256.ScalarMult(x, y, k.Bytes()) - if xGot.Cmp(xWant) != 0 || yGot.Cmp(yWant) != 0 { - t.Fatalf("%d: bad output: got (%X, %X), want (%X, %X)", i, xGot, yGot, xWant, yWant) - } - } -} - func TestScalarMultRand(t *testing.T) { // Strategy for this test: // diff --git a/dcrec/secp256k1/ellipticadaptor_test.go b/dcrec/secp256k1/ellipticadaptor_test.go index 0e2e7dab32..d59772ad0b 100644 --- a/dcrec/secp256k1/ellipticadaptor_test.go +++ b/dcrec/secp256k1/ellipticadaptor_test.go @@ -228,3 +228,120 @@ func TestScalarBaseMultAdaptor(t *testing.T) { } } } + +// TestScalarMultAdaptor ensures the ScalarMult method used to satisfy the +// elliptic.Curve interface works as intended for some edge cases and known good +// values. +func TestScalarMultAdaptor(t *testing.T) { + tests := []struct { + name string // test description + k string // hex encoded scalar + x, y string // hex encoded coordinates of point to multiply + rx, ry string // hex encoded coordinates of expected point + }{{ + name: "0*P = ∞ (point at infinity)", + k: "0", + x: "7e660beda020e9cc20391cef85374576853b0f22b8925d5d81c5845bb834c21e", + y: "2d114a5edb320cc9806527d1daf1bbb96a8fedc6f9e8ead421eaef2c7208e409", + rx: "0", + ry: "0", + }, { + name: "1*P = P", + k: "1", + x: "c00be8830995d1e44f1420dd3b90d3441fb66f6861c84a35f959c495a3be5440", + y: "ecf9665e6eba45720de652a340600c7356efe24d228bfe6ea2043e7791c51bb7", + rx: "c00be8830995d1e44f1420dd3b90d3441fb66f6861c84a35f959c495a3be5440", + ry: "ecf9665e6eba45720de652a340600c7356efe24d228bfe6ea2043e7791c51bb7", + }, { + name: "(group order - 1)*P = -P (aka -1*P = -P)", + k: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + x: "74a1ad6b5f76e39db2dd249410eac7f99e74c59cb83d2d0ed5ff1543da7703e9", + y: "cc6157ef18c9c63cd6193d83631bbea0093e0968942e8c33d5737fd790e0db08", + rx: "74a1ad6b5f76e39db2dd249410eac7f99e74c59cb83d2d0ed5ff1543da7703e9", + ry: "339ea810e73639c329e6c27c9ce4415ff6c1f6976bd173cc2a8c80276f1f2127", + }, { + name: "(group order - 1)*-P = P (aka -1*-P = -P, with P from prev test)", + k: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + x: "74a1ad6b5f76e39db2dd249410eac7f99e74c59cb83d2d0ed5ff1543da7703e9", + y: "339ea810e73639c329e6c27c9ce4415ff6c1f6976bd173cc2a8c80276f1f2127", + rx: "74a1ad6b5f76e39db2dd249410eac7f99e74c59cb83d2d0ed5ff1543da7703e9", + ry: "cc6157ef18c9c63cd6193d83631bbea0093e0968942e8c33d5737fd790e0db08", + }, { + name: "known good point from base mult tests (aka k*G)", + k: "aa5e28d6a97a2479a65527f7290311a3624d4cc0fa1578598ee3c2613bf99522", + x: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + y: "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + rx: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + ry: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", + }, { + name: "known good result 1", + k: "7e2b897b8cebc6361663ad410835639826d590f393d90a9538881735256dfae3", + x: "1697ffa6fd9de627c077e3d2fe541084ce13300b0bec1146f95ae57f0d0bd6a5", + y: "b9c398f186806f5d27561506e4557433a2cf15009e498ae7adee9d63d01b2396", + rx: "6951f3b50aafbc63e21707dd53623b7f42badd633a0567ef1b37f6e42a4237ad", + ry: "9c930796a49110122fbfdedc36418af726197ed950b783a2d29058f8c02130de", + }, { + name: "known good result 2", + k: "6461e6df0fe7dfd05329f41bf771b86578143d4dd1f7866fb4ca7e97c5fa945d", + x: "659214ac1a1790023f53c4cf55a0a63b9e20c1151efa971215b395a558aa151", + y: "b126363aa4243d2759320a356230569a4eea355d9dabd94ed7f4590701e5364d", + rx: "4ffad856833396ef753c0bd4ea40319295f107c476793df0adac2caea53b3df4", + ry: "586fa6b1e9a3ff7df8a2b9b3698badcf40aa06af5600fefc56dd8ae4db5451c5", + }, { + name: "known good result 3", + k: "376a3a2cdcd12581efff13ee4ad44c4044b8a0524c42422a7e1e181e4deeccec", + x: "3f0e80e574456d8f8fa64e044b2eb72ea22eb53fe1efe3a443933aca7f8cb0e3", + y: "cb66d7d7296cbc91e90b9c08485d01b39501253aa65b53a4cb0289e2ea5f404f", + rx: "35ae6480b18e48070709d9276ed97a50c6ee1fc05ac44386c85826533233d28f", + ry: "f88abee3efabd95e80ce8c664bbc3d4d12b24e1a0f4d2b98ba6542789c6715fd", + }, { + name: "known good result 4", + k: "1b22644a7be026548810c378d0b2994eefa6d2b9881803cb02ceff865287d1b9", + x: "d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e", + y: "581e2872a86c72a683842ec228cc6defea40af2bd896d3a5c504dc9ff6a26b58", + rx: "cca7f9a4b0d379c31c438050e163a8945f2f910498bd3b545be20ed862bd6cd9", + ry: "cfc7bbf37bef62da6e5753ed419168fa1376a3fe949c139a8dd0f5303f4ae947", + }, { + name: "known good result 5", + k: "7f5b2cb4b43840c75e4afad83d792e1965d8c21c1109505f45c7d46df422d73e", + x: "bce74de6d5f98dc027740c2bbff05b6aafe5fd8d103f827e48894a2bd3460117", + y: "5bea1fa17a41b115525a3e7dbf0d8d5a4f7ce5c6fc73a6f4f216512417c9f6b4", + rx: "3d96b9290fe6c4f2d62fe2175f4333907d0c3637fada1010b45c7d80690e16de", + ry: "d59c0e8192d7fbd4846172d6479630b751cd03d0d9be0dca2759c6212b70575d", + }, { + // From btcd issue #709. + name: "early implementation regression point", + k: "a2e8ba2e8ba2e8ba2e8ba2e8ba2e8ba219b51835b55cc30ebfe2f6599bc56f58", + x: "000000000000000000000000000000000000000000000000000000000000002c", + y: "420e7a99bba18a9d3952597510fd2b6728cfeafc21a4e73951091d4d8ddbe94e", + rx: "a2112dcdfbcd10ae1133a358de7b82db68e0a3eb4b492cc8268d1e7118c98788", + ry: "27fc7463b7bb3c5f98ecf2c84a6272bb1681ed553d92c69f2dfe25a9f9fd3836", + }} + + curve := S256() + for _, test := range tests { + // Parse the test data. + k := fromHex(test.k) + x, y := fromHex(test.x), fromHex(test.y) + xWant, yWant := fromHex(test.rx), fromHex(test.ry) + + // Ensure the test data is using points that are actually on the curve + // (or the point at infinity). + if !isValidAffinePoint(x, y) { + t.Errorf("%s: point is not on curve", test.name) + continue + } + if !isValidAffinePoint(xWant, yWant) { + t.Errorf("%s: expected point is not on curve", test.name) + continue + } + + // Perform scalar point multiplication ensure the result matches + // expected. + rx, ry := curve.ScalarMult(x, y, k.Bytes()) + if rx.Cmp(xWant) != 0 || ry.Cmp(yWant) != 0 { + t.Errorf("%s: wrong result\ngot: (%x, %x)\nwant: (%x, %x)", + test.name, rx, ry, xWant, yWant) + } + } +} From 636070e7b416c66518371a6bdee9624a2a1f8105 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 Feb 2022 02:41:33 -0600 Subject: [PATCH 09/16] secp256k1: Cleanup affine scalar base mult tests. This cleans up the tests for performing affine scalar base multiplication to make them more consistent with modern practices in the code. The following is a high level overview of the changes: - Adds names to each individual test for easier identification - Makes the individual test definitions consistent with the rest of the code - Adds some additional edge cases --- dcrec/secp256k1/ellipticadaptor_test.go | 87 ++++++++++++++++--------- 1 file changed, 56 insertions(+), 31 deletions(-) diff --git a/dcrec/secp256k1/ellipticadaptor_test.go b/dcrec/secp256k1/ellipticadaptor_test.go index d59772ad0b..8a646428c1 100644 --- a/dcrec/secp256k1/ellipticadaptor_test.go +++ b/dcrec/secp256k1/ellipticadaptor_test.go @@ -5,7 +5,6 @@ package secp256k1 import ( - "fmt" "math/big" "testing" ) @@ -185,46 +184,72 @@ func TestDoubleAffineAdaptor(t *testing.T) { } // TestScalarBaseMultAdaptor ensures the ScalarBaseMult method used to satisfy -// the elliptic.Curve interface works as intended. +// the elliptic.Curve interface works as intended for some edge cases and known +// good values. func TestScalarBaseMultAdaptor(t *testing.T) { tests := []struct { - k string - x, y string + name string // test description + k string // hex encoded scalar + rx, ry string // hex encoded coordinates of expected point }{{ - "aa5e28d6a97a2479a65527f7290311a3624d4cc0fa1578598ee3c2613bf99522", - "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", - "b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", + name: "zero", + k: "0000000000000000000000000000000000000000000000000000000000000000", + rx: "0000000000000000000000000000000000000000000000000000000000000000", + ry: "0000000000000000000000000000000000000000000000000000000000000000", }, { - "7e2b897b8cebc6361663ad410835639826d590f393d90a9538881735256dfae3", - "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", - "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", + name: "one (aka 1*G = G)", + k: "0000000000000000000000000000000000000000000000000000000000000001", + rx: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + ry: "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", }, { - "6461e6df0fe7dfd05329f41bf771b86578143d4dd1f7866fb4ca7e97c5fa945d", - "e8aecc370aedd953483719a116711963ce201ac3eb21d3f3257bb48668c6a72f", - "c25caf2f0eba1ddb2f0f3f47866299ef907867b7d27e95b3873bf98397b24ee1", + name: "group order - 1 (aka -1*G = -G)", + k: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + rx: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + ry: "b7c52588d95c3b9aa25b0403f1eef75702e84bb7597aabe663b82f6f04ef2777", }, { - "376a3a2cdcd12581efff13ee4ad44c4044b8a0524c42422a7e1e181e4deeccec", - "14890e61fcd4b0bd92e5b36c81372ca6fed471ef3aa60a3e415ee4fe987daba1", - "297b858d9f752ab42d3bca67ee0eb6dcd1c2b7b0dbe23397e66adc272263f982", + name: "known good point 1", + k: "aa5e28d6a97a2479a65527f7290311a3624d4cc0fa1578598ee3c2613bf99522", + rx: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + ry: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", }, { - "1b22644a7be026548810c378d0b2994eefa6d2b9881803cb02ceff865287d1b9", - "f73c65ead01c5126f28f442d087689bfa08e12763e0cec1d35b01751fd735ed3", - "f449a8376906482a84ed01479bd18882b919c140d638307f0c0934ba12590bde", + name: "known good point 2", + k: "7e2b897b8cebc6361663ad410835639826d590f393d90a9538881735256dfae3", + rx: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", + ry: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", + }, { + name: "known good point 3", + k: "6461e6df0fe7dfd05329f41bf771b86578143d4dd1f7866fb4ca7e97c5fa945d", + rx: "e8aecc370aedd953483719a116711963ce201ac3eb21d3f3257bb48668c6a72f", + ry: "c25caf2f0eba1ddb2f0f3f47866299ef907867b7d27e95b3873bf98397b24ee1", + }, { + name: "known good point 4", + k: "376a3a2cdcd12581efff13ee4ad44c4044b8a0524c42422a7e1e181e4deeccec", + rx: "14890e61fcd4b0bd92e5b36c81372ca6fed471ef3aa60a3e415ee4fe987daba1", + ry: "297b858d9f752ab42d3bca67ee0eb6dcd1c2b7b0dbe23397e66adc272263f982", + }, { + name: "known good point 5", + k: "1b22644a7be026548810c378d0b2994eefa6d2b9881803cb02ceff865287d1b9", + rx: "f73c65ead01c5126f28f442d087689bfa08e12763e0cec1d35b01751fd735ed3", + ry: "f449a8376906482a84ed01479bd18882b919c140d638307f0c0934ba12590bde", }} - s256 := S256() - for i, test := range tests { - k, ok := new(big.Int).SetString(test.k, 16) - if !ok { - t.Errorf("%d: bad value for k: %s", i, test.k) - } - x, y := s256.ScalarBaseMult(k.Bytes()) - if fmt.Sprintf("%x", x) != test.x || fmt.Sprintf("%x", y) != test.y { - t.Errorf("%d: bad output for k=%s: got (%x, %x), want (%s, %s)", i, - test.k, x, y, test.x, test.y) + curve := S256() + for _, test := range tests { + // Parse the test data. + k := fromHex(test.k) + xWant, yWant := fromHex(test.rx), fromHex(test.ry) + + // Ensure the test data is using points that are actually on the curve + // (or the point at infinity). + if !isValidAffinePoint(xWant, yWant) { + t.Errorf("%s: expected point is not on curve", test.name) + continue } - if testing.Short() && i > 5 { - break + + rx, ry := curve.ScalarBaseMult(k.Bytes()) + if rx.Cmp(xWant) != 0 || ry.Cmp(yWant) != 0 { + t.Errorf("%s: wrong result:\ngot (%x, %x)\nwant (%x, %x)", + test.name, rx, ry, xWant, yWant) } } } From fde9b812c6023e50d655c0070a5c7bc4da49c09d Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 Feb 2022 02:41:35 -0600 Subject: [PATCH 10/16] secp256k1: Cleanup and move base mult rand tests. This cleans up the test for performing affine scalar base multiplication with random scalars to make it more consistent with modern practices in the code and also moves it to the ellipticadaptor_test.go file since it only applies when accessing the package via the standard library elliptic curve adaptor code. The following is a high level overview of the changes: - Adds a randBytes convenience func to acquire a specified number of random bytes from a given rng - Renames TestBaseMultVerify to TestScalarBaseMultAdaptorRandom to make it clear it is testing via the adaptor and based on random values - Uses a new seed for each invocation to ensure new random values are tested each run as opposed to always testing the same random values generated by the same seed --- dcrec/secp256k1/curve_test.go | 22 ----------- dcrec/secp256k1/ellipticadaptor_test.go | 49 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/dcrec/secp256k1/curve_test.go b/dcrec/secp256k1/curve_test.go index 6ab9070f86..47196f1871 100644 --- a/dcrec/secp256k1/curve_test.go +++ b/dcrec/secp256k1/curve_test.go @@ -480,28 +480,6 @@ func TestNAFRandom(t *testing.T) { } } -func TestBaseMultVerify(t *testing.T) { - s256 := S256() - for bytes := 1; bytes < 40; bytes++ { - for i := 0; i < 30; i++ { - data := make([]byte, bytes) - _, err := rand.Read(data) - if err != nil { - t.Errorf("failed to read random data for %d", i) - continue - } - x, y := s256.ScalarBaseMult(data) - xWant, yWant := s256.ScalarMult(s256.Gx, s256.Gy, data) - if x.Cmp(xWant) != 0 || y.Cmp(yWant) != 0 { - t.Errorf("%d: bad output for %X: got (%X, %X), want (%X, %X)", i, data, x, y, xWant, yWant) - } - if testing.Short() && i > 2 { - break - } - } - } -} - func TestScalarMultRand(t *testing.T) { // Strategy for this test: // diff --git a/dcrec/secp256k1/ellipticadaptor_test.go b/dcrec/secp256k1/ellipticadaptor_test.go index 8a646428c1..3c9b012608 100644 --- a/dcrec/secp256k1/ellipticadaptor_test.go +++ b/dcrec/secp256k1/ellipticadaptor_test.go @@ -6,9 +6,24 @@ package secp256k1 import ( "math/big" + "math/rand" "testing" + "time" ) +// randBytes returns a byte slice of the required size created from a random +// value generated by the passed rng. +func randBytes(t *testing.T, rng *rand.Rand, numBytes uint8) []byte { + t.Helper() + + buf := make([]byte, numBytes) + if _, err := rng.Read(buf); err != nil { + t.Fatalf("failed to read random: %v", err) + } + + return buf +} + // TestIsOnCurveAdaptor ensures the IsOnCurve method used to satisfy the // elliptic.Curve interface works as intended. func TestIsOnCurveAdaptor(t *testing.T) { @@ -254,6 +269,40 @@ func TestScalarBaseMultAdaptor(t *testing.T) { } } +// TestScalarBaseMultAdaptorRandom ensures that the ScalarBaseMult method used +// to satisfy the elliptic.Curve interface works as intended for +// randomly-generated scalars of all lengths up to 40 bytes. +func TestScalarBaseMultAdaptorRandom(t *testing.T) { + // Use a unique random seed each test instance and log it if the tests fail. + seed := time.Now().Unix() + rng := rand.New(rand.NewSource(seed)) + defer func(t *testing.T, seed int64) { + if t.Failed() { + t.Logf("random seed: %d", seed) + } + }(t, seed) + + s256 := S256() + const maxBytes = 40 + const iterations = 10 + for numBytes := uint8(1); numBytes < maxBytes; numBytes++ { + for i := 0; i < iterations; i++ { + // Generate a random scalar of the current length. + k := randBytes(t, rng, numBytes) + + // Ensure the correct results by performing the multiplication with + // both the func under test as well as the generic scalar mult func. + x, y := s256.ScalarBaseMult(k) + xWant, yWant := s256.ScalarMult(s256.Gx, s256.Gy, k) + if x.Cmp(xWant) != 0 || y.Cmp(yWant) != 0 { + t.Errorf("bad output for %x: got (%x, %x), want (%x, %x)", k, + x, y, xWant, yWant) + continue + } + } + } +} + // TestScalarMultAdaptor ensures the ScalarMult method used to satisfy the // elliptic.Curve interface works as intended for some edge cases and known good // values. From 82faa8bc833ca9616a18bb4b448ec6a20dd5b9aa Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 Feb 2022 02:41:36 -0600 Subject: [PATCH 11/16] secp256k1: Cleanup private key tests. This cleans up the test for key generation to make it more consistent with modern practices in the code and adds a couple more test cases. --- dcrec/secp256k1/privkey_test.go | 47 ++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/dcrec/secp256k1/privkey_test.go b/dcrec/secp256k1/privkey_test.go index f89b6fbe3d..dd0464405e 100644 --- a/dcrec/secp256k1/privkey_test.go +++ b/dcrec/secp256k1/privkey_test.go @@ -23,36 +23,41 @@ func TestGeneratePrivateKey(t *testing.T) { } } +// TestPrivKeys ensures a private key created from bytes produces both the +// correct associated public key as well serializes back to the original bytes. func TestPrivKeys(t *testing.T) { tests := []struct { name string - key []byte - }{ - { - name: "check curve", - key: []byte{ - 0xea, 0xf0, 0x2c, 0xa3, 0x48, 0xc5, 0x24, 0xe6, - 0x39, 0x26, 0x55, 0xba, 0x4d, 0x29, 0x60, 0x3c, - 0xd1, 0xa7, 0x34, 0x7d, 0x9d, 0x65, 0xcf, 0xe9, - 0x3c, 0xe1, 0xeb, 0xff, 0xdc, 0xa2, 0x26, 0x94, - }, - }, - } + priv string // hex encoded private key to test + pub string // expected hex encoded serialized compressed public key + }{{ + name: "random private key 1", + priv: "eaf02ca348c524e6392655ba4d29603cd1a7347d9d65cfe93ce1ebffdca22694", + pub: "025ceeba2ab4a635df2c0301a3d773da06ac5a18a7c3e0d09a795d7e57d233edf1", + }, { + name: "random private key 2", + priv: "24b860d0651db83feba821e7a94ba8b87162665509cefef0cbde6a8fbbedfe7c", + pub: "032a6e51bf218085647d330eac2fafaeee07617a777ad9e8e7141b4cdae92cb637", + }} for _, test := range tests { - priv := PrivKeyFromBytes(test.key) + // Parse test data. + privKeyBytes := hexToBytes(test.priv) + wantPubKeyBytes := hexToBytes(test.pub) + + priv := PrivKeyFromBytes(privKeyBytes) pub := priv.PubKey() - _, err := ParsePubKey(pub.SerializeUncompressed()) - if err != nil { - t.Errorf("%s privkey: %v", test.name, err) - continue + serializedPubKey := pub.SerializeCompressed() + if !bytes.Equal(serializedPubKey, wantPubKeyBytes) { + t.Errorf("%s unexpected serialized public key - got: %x, want: %x", + test.name, serializedPubKey, wantPubKeyBytes) } - serializedKey := priv.Serialize() - if !bytes.Equal(serializedKey, test.key) { - t.Errorf("%s unexpected serialized bytes - got: %x, "+ - "want: %x", test.name, serializedKey, test.key) + serializedPrivKey := priv.Serialize() + if !bytes.Equal(serializedPrivKey, privKeyBytes) { + t.Errorf("%s unexpected serialized private key - got: %x, want: %x", + test.name, serializedPrivKey, privKeyBytes) } } } From 939e51418f95f8d3679295d4a930924122959fb1 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 Feb 2022 02:41:38 -0600 Subject: [PATCH 12/16] secp256k1: Cleanup Jacobian addition tests. This cleans up the tests for performing Jacobian addition to make them more consistent with modern practices in the code. The following is a high level overview of the changes: - Adds names to each individual test for easier identification - Makes the individual test definitions consistent with the rest of the code --- dcrec/secp256k1/curve_test.go | 356 ++++++++++++++++------------------ 1 file changed, 170 insertions(+), 186 deletions(-) diff --git a/dcrec/secp256k1/curve_test.go b/dcrec/secp256k1/curve_test.go index 47196f1871..37c1f2b594 100644 --- a/dcrec/secp256k1/curve_test.go +++ b/dcrec/secp256k1/curve_test.go @@ -55,218 +55,202 @@ func (p *JacobianPoint) IsStrictlyEqual(other *JacobianPoint) bool { return p.X.Equals(&other.X) && p.Y.Equals(&other.Y) && p.Z.Equals(&other.Z) } -// TestAddJacobian tests addition of points projected in Jacobian coordinates. +// TestAddJacobian tests addition of points projected in Jacobian coordinates +// works as intended. func TestAddJacobian(t *testing.T) { tests := []struct { - x1, y1, z1 string // Coordinates (in hex) of first point to add - x2, y2, z2 string // Coordinates (in hex) of second point to add - x3, y3, z3 string // Coordinates (in hex) of expected point - }{ + name string // test description + x1, y1, z1 string // hex encoded coordinates of first point to add + x2, y2, z2 string // hex encoded coordinates of second point to add + x3, y3, z3 string // hex encoded coordinates of expected point + }{{ // Addition with the point at infinity (left hand side). - // ∞ + P = P - { - "0", - "0", - "0", - "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", - "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", - "1", - "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", - "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", - "1", - }, + name: "∞ + P = P", + x1: "0", + y1: "0", + z1: "0", + x2: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", + y2: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", + z2: "1", + x3: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", + y3: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", + z3: "1", + }, { // Addition with the point at infinity (right hand side). - // P + ∞ = P - { - "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", - "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", - "1", - "0", - "0", - "0", - "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", - "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", - "1", - }, + name: "P + ∞ = P", + x1: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", + y1: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", + z1: "1", + x2: "0", + y2: "0", + z2: "0", + x3: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", + y3: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", + z3: "1", + }, { // Addition with z1=z2=1 different x values. - { - "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", - "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", - "1", - "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", - "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", - "1", - "0cfbc7da1e569b334460788faae0286e68b3af7379d5504efc25e4dba16e46a6", - "e205f79361bbe0346b037b4010985dbf4f9e1e955e7d0d14aca876bfa79aad87", - "44a5646b446e3877a648d6d381370d9ef55a83b666ebce9df1b1d7d65b817b2f", - }, + name: "P(x1, y1, 1) + P(x2, y1, 1)", + x1: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + y1: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", + z1: "1", + x2: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", + y2: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", + z2: "1", + x3: "0cfbc7da1e569b334460788faae0286e68b3af7379d5504efc25e4dba16e46a6", + y3: "e205f79361bbe0346b037b4010985dbf4f9e1e955e7d0d14aca876bfa79aad87", + z3: "44a5646b446e3877a648d6d381370d9ef55a83b666ebce9df1b1d7d65b817b2f", + }, { // Addition with z1=z2=1 same x opposite y. - // P(x, y, z) + P(x, -y, z) = infinity - { - "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", - "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", - "1", - "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", - "f48e156428cf0276dc092da5856e182288d7569f97934a56fe44be60f0d359fd", - "1", - "0", - "0", - "0", - }, + name: "P(x, y, 1) + P(x, -y, 1) = ∞", + x1: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + y1: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", + z1: "1", + x2: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + y2: "f48e156428cf0276dc092da5856e182288d7569f97934a56fe44be60f0d359fd", + z2: "1", + x3: "0", + y3: "0", + z3: "0", + }, { // Addition with z1=z2=1 same point. - // P(x, y, z) + P(x, y, z) = 2P - { - "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", - "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", - "1", - "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", - "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", - "1", - "ec9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee64f87c50c27", - "b082b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd0755c8f2a", - "16e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c1e594464", - }, - + name: "P(x, y, 1) + P(x, y, 1) = 2P", + x1: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + y1: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", + z1: "1", + x2: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + y2: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", + z2: "1", + x3: "ec9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee64f87c50c27", + y3: "b082b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd0755c8f2a", + z3: "16e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c1e594464", + }, { // Addition with z1=z2 (!=1) different x values. - { - "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", - "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", - "2", - "5d2fe112c21891d440f65a98473cb626111f8a234d2cd82f22172e369f002147", - "98e3386a0a622a35c4561ffb32308d8e1c6758e10ebb1b4ebd3d04b4eb0ecbe8", - "2", - "cfbc7da1e569b334460788faae0286e68b3af7379d5504efc25e4dba16e46a60", - "817de4d86ef80d1ac0ded00426176fd3e787a5579f43452b2a1db021e6ac3778", - "129591ad11b8e1de99235b4e04dc367bd56a0ed99baf3a77c6c75f5a6e05f08d", - }, + name: "P(x1, y1, 2) + P(x2, y2, 2)", + x1: "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", + y1: "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", + z1: "2", + x2: "5d2fe112c21891d440f65a98473cb626111f8a234d2cd82f22172e369f002147", + y2: "98e3386a0a622a35c4561ffb32308d8e1c6758e10ebb1b4ebd3d04b4eb0ecbe8", + z2: "2", + x3: "cfbc7da1e569b334460788faae0286e68b3af7379d5504efc25e4dba16e46a60", + y3: "817de4d86ef80d1ac0ded00426176fd3e787a5579f43452b2a1db021e6ac3778", + z3: "129591ad11b8e1de99235b4e04dc367bd56a0ed99baf3a77c6c75f5a6e05f08d", + }, { // Addition with z1=z2 (!=1) same x opposite y. - // P(x, y, z) + P(x, -y, z) = infinity - { - "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", - "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", - "2", - "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", - "a470ab21467813b6e0496d2c2b70c11446bab4fcbc9a52b7f225f30e869aea9f", - "2", - "0", - "0", - "0", - }, + name: "P(x, y, 2) + P(x, -y, 2) = ∞", + x1: "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", + y1: "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", + z1: "2", + x2: "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", + y2: "a470ab21467813b6e0496d2c2b70c11446bab4fcbc9a52b7f225f30e869aea9f", + z2: "2", + x3: "0", + y3: "0", + z3: "0", + }, { // Addition with z1=z2 (!=1) same point. - // P(x, y, z) + P(x, y, z) = 2P - { - "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", - "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", - "2", - "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", - "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", - "2", - "9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee65073c50fabac", - "2b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd125dc91cb988", - "6e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c2e5944a11", - }, - + name: "P(x, y, 2) + P(x, y, 2) = 2P", + x1: "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", + y1: "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", + z1: "2", + x2: "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", + y2: "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", + z2: "2", + x3: "9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee65073c50fabac", + y3: "2b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd125dc91cb988", + z3: "6e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c2e5944a11", + }, { // Addition with z1!=z2 and z2=1 different x values. - { - "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", - "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", - "2", - "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", - "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", - "1", - "3ef1f68795a6ccd1181e23eab80a1b9a2cebdcde755413bf097936eb5b91b4f3", - "0bef26c377c068d606f6802130bb7e9f3c3d2abcfa1a295950ed81133561cb04", - "252b235a2371c3bd3246b69c09b86cf7aad41db3375e74ef8d8ebeb4dc0be11a", - }, + name: "P(x1, y1, 2) + P(x2, y2, 1)", + x1: "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", + y1: "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", + z1: "2", + x2: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", + y2: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", + z2: "1", + x3: "3ef1f68795a6ccd1181e23eab80a1b9a2cebdcde755413bf097936eb5b91b4f3", + y3: "0bef26c377c068d606f6802130bb7e9f3c3d2abcfa1a295950ed81133561cb04", + z3: "252b235a2371c3bd3246b69c09b86cf7aad41db3375e74ef8d8ebeb4dc0be11a", + }, { // Addition with z1!=z2 and z2=1 same x opposite y. - // P(x, y, z) + P(x, -y, z) = infinity - { - "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", - "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", - "2", - "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", - "f48e156428cf0276dc092da5856e182288d7569f97934a56fe44be60f0d359fd", - "1", - "0", - "0", - "0", - }, + name: "P(x, y, 2) + P(x, -y, 1) = ∞", + x1: "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", + y1: "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", + z1: "2", + x2: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + y2: "f48e156428cf0276dc092da5856e182288d7569f97934a56fe44be60f0d359fd", + z2: "1", + x3: "0", + y3: "0", + z3: "0", + }, { // Addition with z1!=z2 and z2=1 same point. - // P(x, y, z) + P(x, y, z) = 2P - { - "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", - "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", - "2", - "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", - "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", - "1", - "9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee65073c50fabac", - "2b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd125dc91cb988", - "6e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c2e5944a11", - }, - + name: "P(x, y, 2) + P(x, y, 1) = 2P", + x1: "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", + y1: "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", + z1: "2", + x2: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + y2: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", + z2: "1", + x3: "9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee65073c50fabac", + y3: "2b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd125dc91cb988", + z3: "6e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c2e5944a11", + }, { // Addition with z1!=z2 and z2!=1 different x values. - // P(x, y, z) + P(x, y, z) = 2P - { - "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", - "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", - "2", - "91abba6a34b7481d922a4bd6a04899d5a686f6cf6da4e66a0cb427fb25c04bd4", - "03fede65e30b4e7576a2abefc963ddbf9fdccbf791b77c29beadefe49951f7d1", - "3", - "3f07081927fd3f6dadd4476614c89a09eba7f57c1c6c3b01fa2d64eac1eef31e", - "949166e04ebc7fd95a9d77e5dfd88d1492ecffd189792e3944eb2b765e09e031", - "eb8cba81bcffa4f44d75427506737e1f045f21e6d6f65543ee0e1d163540c931", - }, // Addition with z1!=z2 and z2!=1 same x opposite y. - // P(x, y, z) + P(x, -y, z) = infinity - { - "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", - "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", - "2", - "dcc3768780c74a0325e2851edad0dc8a566fa61a9e7fc4a34d13dcb509f99bc7", - "cafc41904dd5428934f7d075129c8ba46eb622d4fc88d72cd1401452664add18", - "3", - "0", - "0", - "0", - }, + name: "P(x1, y1, 2) + P(x2, y2, 3)", + x1: "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", + y1: "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", + z1: "2", + x2: "91abba6a34b7481d922a4bd6a04899d5a686f6cf6da4e66a0cb427fb25c04bd4", + y2: "03fede65e30b4e7576a2abefc963ddbf9fdccbf791b77c29beadefe49951f7d1", + z2: "3", + x3: "3f07081927fd3f6dadd4476614c89a09eba7f57c1c6c3b01fa2d64eac1eef31e", + y3: "949166e04ebc7fd95a9d77e5dfd88d1492ecffd189792e3944eb2b765e09e031", + z3: "eb8cba81bcffa4f44d75427506737e1f045f21e6d6f65543ee0e1d163540c931", + }, { + // Addition with z1!=z2 and z2!=1 same x opposite y. + name: "P(x, y, 2) + P(x, -y, 3) = ∞", + x1: "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", + y1: "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", + z1: "2", + x2: "dcc3768780c74a0325e2851edad0dc8a566fa61a9e7fc4a34d13dcb509f99bc7", + y2: "cafc41904dd5428934f7d075129c8ba46eb622d4fc88d72cd1401452664add18", + z2: "3", + x3: "0", + y3: "0", + z3: "0", + }, { // Addition with z1!=z2 and z2!=1 same point. - // P(x, y, z) + P(x, y, z) = 2P - { - "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", - "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", - "2", - "dcc3768780c74a0325e2851edad0dc8a566fa61a9e7fc4a34d13dcb509f99bc7", - "3503be6fb22abd76cb082f8aed63745b9149dd2b037728d32ebfebac99b51f17", - "3", - "9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee65073c50fabac", - "2b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd125dc91cb988", - "6e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c2e5944a11", - }, - } + name: "P(x, y, 2) + P(x, y, 3) = 2P", + x1: "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", + y1: "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", + z1: "2", + x2: "dcc3768780c74a0325e2851edad0dc8a566fa61a9e7fc4a34d13dcb509f99bc7", + y2: "3503be6fb22abd76cb082f8aed63745b9149dd2b037728d32ebfebac99b51f17", + z2: "3", + x3: "9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee65073c50fabac", + y3: "2b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd125dc91cb988", + z3: "6e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c2e5944a11", + }} - t.Logf("Running %d tests", len(tests)) - for i, test := range tests { + for _, test := range tests { // Convert hex to Jacobian points. p1 := jacobianPointFromHex(test.x1, test.y1, test.z1) p2 := jacobianPointFromHex(test.x2, test.y2, test.z2) want := jacobianPointFromHex(test.x3, test.y3, test.z3) - // Ensure the test data is using points that are actually on - // the curve (or the point at infinity). + // Ensure the test data is using points that are actually on the curve + // (or the point at infinity). if !isValidJacobianPoint(&p1) { - t.Errorf("#%d first point is not on the curve -- "+ - "invalid test data", i) + t.Errorf("%s: first point is not on the curve", test.name) continue } if !isValidJacobianPoint(&p2) { - t.Errorf("#%d second point is not on the curve -- "+ - "invalid test data", i) + t.Errorf("%s: second point is not on the curve", test.name) continue } if !isValidJacobianPoint(&want) { - t.Errorf("#%d expected point is not on the curve -- "+ - "invalid test data", i) + t.Errorf("%s: expected point is not on the curve", test.name) continue } @@ -276,8 +260,8 @@ func TestAddJacobian(t *testing.T) { // Ensure result matches expected. if !r.IsStrictlyEqual(&want) { - t.Errorf("#%d wrong result\ngot: (%v, %v, %v)\n"+ - "want: (%v, %v, %v)", i, r.X, r.Y, r.Z, want.X, want.Y, want.Z) + 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) continue } } From b2f6fd7bc8a5510efb3eb690f795824f813aceae Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 Feb 2022 02:41:39 -0600 Subject: [PATCH 13/16] secp256k1: Cleanup Jacobian double tests. This cleans up the tests for performing Jacobian point doubling to make them more consistent with modern practices in the code. The following is a high level overview of the changes: - Adds names to each individual test for easier identification - Makes the individual test definitions consistent with the rest of the code --- dcrec/secp256k1/curve_test.go | 95 +++++++++++++++++------------------ 1 file changed, 46 insertions(+), 49 deletions(-) diff --git a/dcrec/secp256k1/curve_test.go b/dcrec/secp256k1/curve_test.go index 37c1f2b594..f6cdc768b0 100644 --- a/dcrec/secp256k1/curve_test.go +++ b/dcrec/secp256k1/curve_test.go @@ -267,67 +267,64 @@ func TestAddJacobian(t *testing.T) { } } -// TestDoubleJacobian tests doubling of points projected in Jacobian -// coordinates. +// TestDoubleJacobian tests doubling of points projected in Jacobian coordinates +// works as intended for some edge cases and known good values. func TestDoubleJacobian(t *testing.T) { tests := []struct { - x1, y1, z1 string // Coordinates (in hex) of point to double - x3, y3, z3 string // Coordinates (in hex) of expected point - }{ + name string // test description + x1, y1, z1 string // hex encoded coordinates of point to double + x3, y3, z3 string // hex encoded coordinates of expected point + }{{ // Doubling the point at infinity is still infinity. - { - "0", - "0", - "0", - "0", - "0", - "0", - }, + name: "2*∞ = ∞ (point at infinity)", + x1: "0", + y1: "0", + z1: "0", + x3: "0", + y3: "0", + z3: "0", + }, { // Doubling with z1=1. - { - "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", - "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", - "1", - "ec9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee64f87c50c27", - "b082b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd0755c8f2a", - "16e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c1e594464", - }, + name: "2*P(x, y, 1)", + x1: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + y1: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", + z1: "1", + x3: "ec9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee64f87c50c27", + y3: "b082b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd0755c8f2a", + z3: "16e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c1e594464", + }, { // Doubling with z1!=1. - { - "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", - "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", - "2", - "9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee65073c50fabac", - "2b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd125dc91cb988", - "6e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c2e5944a11", - }, + name: "2*P(x, y, 2)", + x1: "d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718", + y1: "5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190", + z1: "2", + x3: "9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee65073c50fabac", + y3: "2b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd125dc91cb988", + z3: "6e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c2e5944a11", + }, { // From btcd issue #709. - { - "201e3f75715136d2f93c4f4598f91826f94ca01f4233a5bd35de9708859ca50d", - "bdf18566445e7562c6ada68aef02d498d7301503de5b18c6aef6e2b1722412e1", - "0000000000000000000000000000000000000000000000000000000000000001", - "4a5e0559863ebb4e9ed85f5c4fa76003d05d9a7626616e614a1f738621e3c220", - "00000000000000000000000000000000000000000000000000000001b1388778", - "7be30acc88bceac58d5b4d15de05a931ae602a07bcb6318d5dedc563e4482993", - }, - } + name: "carry to bit 256 during normalize", + x1: "201e3f75715136d2f93c4f4598f91826f94ca01f4233a5bd35de9708859ca50d", + y1: "bdf18566445e7562c6ada68aef02d498d7301503de5b18c6aef6e2b1722412e1", + z1: "0000000000000000000000000000000000000000000000000000000000000001", + x3: "4a5e0559863ebb4e9ed85f5c4fa76003d05d9a7626616e614a1f738621e3c220", + y3: "00000000000000000000000000000000000000000000000000000001b1388778", + z3: "7be30acc88bceac58d5b4d15de05a931ae602a07bcb6318d5dedc563e4482993", + }} - t.Logf("Running %d tests", len(tests)) - for i, test := range tests { + for _, test := range tests { // Convert hex to field values. p1 := jacobianPointFromHex(test.x1, test.y1, test.z1) want := jacobianPointFromHex(test.x3, test.y3, test.z3) - // Ensure the test data is using points that are actually on - // the curve (or the point at infinity). + // Ensure the test data is using points that are actually on the curve + // (or the point at infinity). if !isValidJacobianPoint(&p1) { - t.Errorf("#%d first point is not on the curve -- "+ - "invalid test data", i) + t.Errorf("%s: first point is not on the curve", test.name) continue } if !isValidJacobianPoint(&want) { - t.Errorf("#%d expected point is not on the curve -- "+ - "invalid test data", i) + t.Errorf("%s: expected point is not on the curve", test.name) continue } @@ -337,9 +334,9 @@ func TestDoubleJacobian(t *testing.T) { // Ensure result matches expected. if !result.IsStrictlyEqual(&want) { - t.Errorf("#%d wrong result\ngot: (%v, %v, %v)\n"+ - "want: (%v, %v, %v)", i, result.X, result.Y, result.Z, - want.X, want.Y, want.Z) + t.Errorf("%s: wrong result\ngot: (%v, %v, %v)\nwant: (%v, %v, %v)", + test.name, result.X, result.Y, result.Z, want.X, want.Y, + want.Z) continue } } From 53a9ec0897033d64e4d364c15e72067fcd25fdee Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 Feb 2022 02:41:43 -0600 Subject: [PATCH 14/16] secp256k1: Add test gen for random mod n scalars. This adds a new test helper for generating random mod n scalars without also generating a companion big integer and updates the tests to make use of it. It also marks the combined version as a test helper while here. The new helper will be useful for future tests. --- dcrec/secp256k1/field_test.go | 6 +++--- dcrec/secp256k1/modnscalar_test.go | 22 ++++++++++++++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/dcrec/secp256k1/field_test.go b/dcrec/secp256k1/field_test.go index b144051b5d..edc25b4ddf 100644 --- a/dcrec/secp256k1/field_test.go +++ b/dcrec/secp256k1/field_test.go @@ -1,6 +1,6 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2020 The Decred developers -// Copyright (c) 2013-2020 Dave Collins +// Copyright (c) 2015-2022 The Decred developers +// Copyright (c) 2013-2022 Dave Collins // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -43,7 +43,7 @@ func randFieldVal(t *testing.T, rng *rand.Rand) *FieldVal { t.Fatalf("failed to read random: %v", err) } - // Create and return both a big integer and a field value. + // Create and return a field value. var fv FieldVal fv.SetBytes(&buf) return &fv diff --git a/dcrec/secp256k1/modnscalar_test.go b/dcrec/secp256k1/modnscalar_test.go index d14a5dda3b..26a727391f 100644 --- a/dcrec/secp256k1/modnscalar_test.go +++ b/dcrec/secp256k1/modnscalar_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Decred developers +// Copyright (c) 2020-2022 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -32,9 +32,27 @@ func (s *ModNScalar) SetHex(hexString string) *ModNScalar { return s } +// randModNScalar returns a mod N scalar created from a random value generated +// by the passed rng. +func randModNScalar(t *testing.T, rng *rand.Rand) *ModNScalar { + t.Helper() + + var buf [32]byte + if _, err := rng.Read(buf[:]); err != nil { + t.Fatalf("failed to read random: %v", err) + } + + // Create and return a mod N scalar. + var modNVal ModNScalar + modNVal.SetBytes(&buf) + return &modNVal +} + // randIntAndModNScalar returns a big integer and mod N scalar both created from // the same random value generated by the passed rng. func randIntAndModNScalar(t *testing.T, rng *rand.Rand) (*big.Int, *ModNScalar) { + t.Helper() + var buf [32]byte if _, err := rng.Read(buf[:]); err != nil { t.Fatalf("failed to read random: %v", err) @@ -473,7 +491,7 @@ func TestModNScalarEqualsRandom(t *testing.T) { for i := 0; i < 100; i++ { // Ensure a randomly-generated scalar equals itself. - _, s := randIntAndModNScalar(t, rng) + s := randModNScalar(t, rng) if !s.Equals(s) { t.Fatalf("failed equality check\nscalar in: %v", s) } From 331adb1f15cf9aa5c27e28dc1e11332a23564242 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 Feb 2022 02:41:45 -0600 Subject: [PATCH 15/16] secp256k1: Add Jacobian scalar base mult tests. This adds tests for scalar base multiplication for points projected in Jacobian coordinates. --- dcrec/secp256k1/curve_test.go | 139 ++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/dcrec/secp256k1/curve_test.go b/dcrec/secp256k1/curve_test.go index f6cdc768b0..cc346de316 100644 --- a/dcrec/secp256k1/curve_test.go +++ b/dcrec/secp256k1/curve_test.go @@ -7,6 +7,7 @@ package secp256k1 import ( "crypto/rand" + "encoding/hex" "fmt" "math/big" mrand "math/rand" @@ -14,6 +15,33 @@ import ( "time" ) +// hexToModNScalar converts the passed hex string into a ModNScalar and will +// panic if there is an error. This is only provided for the hard-coded +// constants so errors in the source code can be detected. It will only (and +// must only) be called with hard-coded values. +func hexToModNScalar(s string) *ModNScalar { + var isNegative bool + if len(s) > 0 && s[0] == '-' { + isNegative = true + s = s[1:] + } + if len(s)%2 != 0 { + s = "0" + s + } + b, err := hex.DecodeString(s) + if err != nil { + panic("invalid hex in source file: " + s) + } + var scalar ModNScalar + if overflow := scalar.SetByteSlice(b); overflow { + panic("hex in source file overflows mod N scalar: " + s) + } + if isNegative { + scalar.Negate() + } + return &scalar +} + // isValidJacobianPoint returns true if the point (x,y,z) is on the secp256k1 // curve or is the point at infinity. func isValidJacobianPoint(point *JacobianPoint) bool { @@ -461,6 +489,117 @@ func TestNAFRandom(t *testing.T) { } } +// TestScalarBaseMultJacobian ensures multiplying a given scalar by the base +// point projected in Jacobian coordinates works as intended for some edge cases +// and known values. It also verifies in affine coordinates as well. +func TestScalarBaseMultJacobian(t *testing.T) { + tests := []struct { + name string // test description + k string // hex encoded scalar + x1, y1, z1 string // hex encoded Jacobian coordinates of expected point + x2, y2 string // hex encoded affine coordinates of expected point + }{{ + name: "zero", + k: "0000000000000000000000000000000000000000000000000000000000000000", + x1: "0000000000000000000000000000000000000000000000000000000000000000", + y1: "0000000000000000000000000000000000000000000000000000000000000000", + z1: "0000000000000000000000000000000000000000000000000000000000000001", + x2: "0000000000000000000000000000000000000000000000000000000000000000", + y2: "0000000000000000000000000000000000000000000000000000000000000000", + }, { + name: "one (aka 1*G = G)", + k: "0000000000000000000000000000000000000000000000000000000000000001", + x1: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + y1: "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + z1: "0000000000000000000000000000000000000000000000000000000000000001", + x2: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + y2: "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + }, { + name: "group order - 1 (aka -1*G = -G)", + k: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + x1: "667d5346809ba7602db1ea0bd990eee6ff75d7a64004d563534123e6f12a12d7", + y1: "344f2f772f8f4cbd04709dba7837ff1422db8fa6f99a00f93852de2c45284838", + z1: "19e5a058ef4eaada40d19063917bb4dc07f50c3a0f76bd5348a51057a3721c57", + x2: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + y2: "b7c52588d95c3b9aa25b0403f1eef75702e84bb7597aabe663b82f6f04ef2777", + }, { + name: "known good point 1", + k: "aa5e28d6a97a2479a65527f7290311a3624d4cc0fa1578598ee3c2613bf99522", + x1: "5f64fd9364bac24dc32bc01b7d63aaa8249babbdc26b03233e14120840ae20f6", + y1: "a4ced9be1e1ed6ef73bec6866c3adc0695347303c30b814fb0dfddb3a22b090d", + z1: "931a3477a1b1d866842b22577618e134c89ba12e5bb38c465265c8a2cefa69dc", + x2: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6", + y2: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232", + }, { + name: "known good point 2", + k: "7e2b897b8cebc6361663ad410835639826d590f393d90a9538881735256dfae3", + x1: "c2cb761af4d6410bea0ed7d5f3c7397b63739b0f37e5c3047f8a45537a9d413e", + y1: "34b9204c55336d2fb94e20e53d5aa2ffe4da6f80d72315b4dcafca11e7c0f768", + z1: "ca5d9e8024575c80fe185416ff4736aff8278873da60cf101d10ab49780ee33b", + x2: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575", + y2: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d", + }, { + name: "known good point 3", + k: "6461e6df0fe7dfd05329f41bf771b86578143d4dd1f7866fb4ca7e97c5fa945d", + x1: "09160b87ee751ef9fd51db49afc7af9c534917fad72bf461d21fec2590878267", + y1: "dbc2757c5038e0b059d1e05c2d3706baf1a164e3836a02c240173b22c92da7c0", + z1: "c157ea3f784c37603d9f55e661dd1d6b8759fccbfb2c8cf64c46529d94c8c950", + x2: "e8aecc370aedd953483719a116711963ce201ac3eb21d3f3257bb48668c6a72f", + y2: "c25caf2f0eba1ddb2f0f3f47866299ef907867b7d27e95b3873bf98397b24ee1", + }, { + name: "known good point 4", + k: "376a3a2cdcd12581efff13ee4ad44c4044b8a0524c42422a7e1e181e4deeccec", + x1: "7820c46de3b5a0202bea06870013fcb23adb4a000f89d5b86fe1df24be58fa79", + y1: "95e5a977eb53a582677ff0432eef5bc66f1dd983c3e8c07e1c77c3655542c31e", + z1: "7d71ecfdfa66b003fe96f925b5907f67a1a4a6489f4940ec3b78edbbf847334f", + x2: "14890e61fcd4b0bd92e5b36c81372ca6fed471ef3aa60a3e415ee4fe987daba1", + y2: "297b858d9f752ab42d3bca67ee0eb6dcd1c2b7b0dbe23397e66adc272263f982", + }, { + name: "known good point 5", + k: "1b22644a7be026548810c378d0b2994eefa6d2b9881803cb02ceff865287d1b9", + x1: "68a934fa2d28fb0b0d2b6801a9335d62e65acef9467be2ea67f5b11614b59c78", + y1: "5edd7491e503acf61ed651a10cf466de06bf5c6ba285a7a2885a384bbdd32898", + z1: "f3b28d36c3132b6f4bd66bf0da64b8dc79d66f9a854ba8b609558b6328796755", + x2: "f73c65ead01c5126f28f442d087689bfa08e12763e0cec1d35b01751fd735ed3", + y2: "f449a8376906482a84ed01479bd18882b919c140d638307f0c0934ba12590bde", + }} + + for _, test := range tests { + // Parse test data. + want := jacobianPointFromHex(test.x1, test.y1, test.z1) + wantAffine := jacobianPointFromHex(test.x2, test.y2, "01") + k := hexToModNScalar(test.k) + + // Ensure the test data is using points that are actually on the curve + // (or the point at infinity). + if !isValidJacobianPoint(&want) { + t.Errorf("%q: expected point is not on the curve", test.name) + continue + } + if !isValidJacobianPoint(&wantAffine) { + t.Errorf("%q: expected affine point is not on the curve", test.name) + continue + } + + // Ensure the result matches the expected value in Jacobian coordinates. + var r JacobianPoint + ScalarBaseMultNonConst(k, &r) + if !r.IsStrictlyEqual(&want) { + t.Errorf("%q: wrong result:\ngot: (%s, %s, %s)\nwant: (%s, %s, %s)", + test.name, r.X, r.Y, r.Z, want.X, want.Y, want.Z) + continue + } + + // Ensure the result matches the expected value in affine coordinates. + r.ToAffine() + if !r.IsStrictlyEqual(&wantAffine) { + t.Errorf("%q: wrong affine result:\ngot: (%s, %s)\nwant: (%s, %s)", + test.name, r.X, r.Y, wantAffine.X, wantAffine.Y) + continue + } + } +} + func TestScalarMultRand(t *testing.T) { // Strategy for this test: // From 4a6438a13e0f5a8a15b38c630240cae337e84f62 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 27 Feb 2022 02:41:47 -0600 Subject: [PATCH 16/16] secp256k1: Rework Jacobian rand scalar mult tests. This reworks the tests that deal with scalar point multiplication with points projected into Jacobian coordinates for randomly-generated scalars and points to make them more consistent with modern practices in the code as well as to expand the testing methodology to include additional assurances. It also updates the tests to ensure new random values are tested each run as opposed to the existing tests which always test the same values since they use the same random seed. Specifically: - The points are no longer converted to affine with each iteration which ensures z values other than 1 are tested - Each iteration now ensures calculating the negative version of the point and adding it results in the point at infinity - The check to ensure the same final point was calculated is now done outside of the loop to speed up the test --- dcrec/secp256k1/curve_test.go | 123 ++++++++++++++++++++++++---------- 1 file changed, 87 insertions(+), 36 deletions(-) diff --git a/dcrec/secp256k1/curve_test.go b/dcrec/secp256k1/curve_test.go index cc346de316..96314ca976 100644 --- a/dcrec/secp256k1/curve_test.go +++ b/dcrec/secp256k1/curve_test.go @@ -600,42 +600,6 @@ func TestScalarBaseMultJacobian(t *testing.T) { } } -func TestScalarMultRand(t *testing.T) { - // Strategy for this test: - // - // Get a random exponent from the generator point at first - // This creates a new point which is used in the next iteration - // Use another random exponent on the new point. - // We use BaseMult to verify by multiplying the previous exponent - // and the new random exponent together (mod N) - var want JacobianPoint - var point JacobianPoint - bigAffineToJacobian(curveParams.Gx, curveParams.Gy, &point) - exponent := new(ModNScalar).SetInt(1) - for i := 0; i < 1024; i++ { - data := make([]byte, 32) - _, err := rand.Read(data) - if err != nil { - t.Fatalf("failed to read random data at %d", i) - break - } - var k ModNScalar - k.SetByteSlice(data) - ScalarMultNonConst(&k, &point, &point) - - exponent.Mul(&k) - ScalarBaseMultNonConst(exponent, &want) - point.ToAffine() - want.ToAffine() - if !point.IsStrictlyEqual(&want) { - t.Fatalf("%d: bad output for %x:\ngot (%x, %x, %x)\n"+ - "want (%x, %x, %x)", i, data, point.X, point.Y, point.Z, want.X, - want.Y, want.Z) - break - } - } -} - func TestSplitK(t *testing.T) { tests := []struct { k string @@ -746,6 +710,93 @@ func TestSplitKRand(t *testing.T) { } } +// TestScalarMultJacobianRandom ensures scalar point multiplication with points +// projected into Jacobian coordinates works as intended for randomly-generated +// scalars and points. +func TestScalarMultJacobianRandom(t *testing.T) { + // Use a unique random seed each test instance and log it if the tests fail. + seed := time.Now().Unix() + rng := mrand.New(mrand.NewSource(seed)) + defer func(t *testing.T, seed int64) { + if t.Failed() { + t.Logf("random seed: %d", seed) + } + }(t, seed) + + // isSamePoint returns whether or not the two Jacobian points represent the + // same affine point without modifying the provided points. + isSamePoint := func(p1, p2 *JacobianPoint) bool { + var p1Affine, p2Affine JacobianPoint + p1Affine.Set(p1) + p1Affine.ToAffine() + p2Affine.Set(p2) + p2Affine.ToAffine() + return p1Affine.IsStrictlyEqual(&p2Affine) + } + + // The overall idea is to compute the same point different ways. The + // strategy uses two properties: + // + // 1) Compatibility of scalar multiplication with field multiplication + // 2) A point added to its negation is the point at infinity (P+(-P) = ∞) + // + // First, calculate a "chained" point by starting with the base (generator) + // point and then consecutively multiply the resulting points by a series of + // random scalars. + // + // Then, multiply the base point by the product of all of the random scalars + // and ensure the "chained" point matches. + // + // In other words: + // + // k[n]*(...*(k[2]*(k[1]*(k[0]*G)))) = (k[0]*k[1]*k[2]*...*k[n])*G + // + // Along the way, also calculate (-k)*P for each chained point and ensure it + // sums with the current point to the point at infinity. + // + // That is: + // + // k*P + ((-k)*P) = ∞ + const numIterations = 1024 + var infinity JacobianPoint + var chained, negChained, result JacobianPoint + var negK ModNScalar + bigAffineToJacobian(curveParams.Gx, curveParams.Gy, &chained) + product := new(ModNScalar).SetInt(1) + for i := 0; i < numIterations; i++ { + // Generate a random scalar and calculate: + // + // P = k*P + // -P = (-k)*P + // + // Notice that this is intentionally doing the full scalar mult with -k + // as opposed to just flipping the Y coordinate in order to test scalar + // multiplication. + k := randModNScalar(t, rng) + negK.NegateVal(k) + ScalarMultNonConst(&negK, &chained, &negChained) + ScalarMultNonConst(k, &chained, &chained) + + // Ensure kP + ((-k)P) = ∞. + AddNonConst(&chained, &negChained, &result) + if !isSamePoint(&result, &infinity) { + t.Fatalf("%d: expected point at infinity\ngot (%v, %v, %v)\n", i, + result.X, result.Y, result.Z) + } + + product.Mul(k) + } + + // Ensure the point calculated above matches the product of the scalars + // times the base point. + ScalarBaseMultNonConst(product, &result) + if !isSamePoint(&chained, &result) { + t.Fatalf("unexpected result \ngot (%v, %v, %v)\n"+ + "want (%v, %v, %v)", chained.X, chained.Y, chained.Z, result.X, + result.Y, result.Z) + } +} + // TestDecompressY ensures that decompressY works as expected for some edge // cases. func TestDecompressY(t *testing.T) {