diff --git a/btcec/field.go b/btcec/field.go index fef6f345da..f200419856 100644 --- a/btcec/field.go +++ b/btcec/field.go @@ -1,6 +1,8 @@ package btcec -import secp "github.com/decred/dcrd/dcrec/secp256k1/v4" +import ( + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" +) // FieldVal implements optimized fixed-precision arithmetic over the secp256k1 // finite field. This means all arithmetic is performed modulo diff --git a/btcec/pubkey.go b/btcec/pubkey.go index 2c3a5ccbef..f8f996a846 100644 --- a/btcec/pubkey.go +++ b/btcec/pubkey.go @@ -52,6 +52,15 @@ func NewPublicKey(x, y *FieldVal) *PublicKey { return secp.NewPublicKey(x, y) } +// CopyPublicKey returns a deep copy of the public key. +func CopyPublicKey(pk *PublicKey) *PublicKey { + var result secp.JacobianPoint + pk.AsJacobian(&result) + result.ToAffine() + + return NewPublicKey(&result.X, &result.Y) +} + // SerializedKey is a type for representing a public key in its compressed // serialized form. // diff --git a/btcec/pubkey_test.go b/btcec/pubkey_test.go index 7ee7cd8044..98ed0481e5 100644 --- a/btcec/pubkey_test.go +++ b/btcec/pubkey_test.go @@ -6,7 +6,9 @@ package btcec import ( "bytes" + "math/rand" "testing" + "testing/quick" "github.com/davecgh/go-spew/spew" ) @@ -292,3 +294,61 @@ func TestIsCompressed(t *testing.T) { } } } + +// TestCopyPublicKeyProperties tests that the CopyPublicKey function copies the +// original public key and that the copy is equal to the original. +func TestCopyPublicKeyProperties(t *testing.T) { + f := func(x, y [32]byte) bool { + // Convert byte slices to FieldVals. + xf := new(FieldVal) + yf := new(FieldVal) + if overflow := xf.SetByteSlice(x[:]); overflow { + // Only check the function for valid coordinates. + return true + } + + if overflow := xf.SetByteSlice(y[:]); overflow { + // Only check the function for valid coordinates. + return true + } + + // Create an original public key. + original := NewPublicKey(xf, yf) + if original == nil { + // Skip invalid inputs. + return true + } + + // Make a copy of the original public key. + copied := CopyPublicKey(original) + if copied == nil { + // copy should succeed if original was valid. + return false + } + + // The copy should not be the same instance. + if original == copied { + return false + } + + // The copy should be equal to the original + // First check the serialized forms. + originalBytes := original.SerializeCompressed() + copiedBytes := copied.SerializeCompressed() + if !bytes.Equal(originalBytes, copiedBytes) { + return false + } + + return original.IsEqual(copied) + } + + // Create a deterministic random source using a fixed seed. + rng := rand.New(rand.NewSource(42)) + config := &quick.Config{ + Rand: rng, + } + + if err := quick.Check(f, config); err != nil { + t.Error(err) + } +}