From e5fa7c1a85e9dd44ee92cb5da1797c82a0268fdb Mon Sep 17 00:00:00 2001 From: Clawd Date: Thu, 19 Feb 2026 21:08:14 -0800 Subject: Add key generation and serialization --- keys_test.go | 221 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 keys_test.go (limited to 'keys_test.go') diff --git a/keys_test.go b/keys_test.go new file mode 100644 index 0000000..e17ac98 --- /dev/null +++ b/keys_test.go @@ -0,0 +1,221 @@ +package secp256k1 + +import ( + "bytes" + "math/big" + "testing" +) + +func TestGeneratePrivateKey(t *testing.T) { + priv, err := GeneratePrivateKey() + if err != nil { + t.Fatalf("failed to generate key: %v", err) + } + + // Should be in valid range [1, N-1] + if priv.D.Sign() <= 0 { + t.Error("private key should be positive") + } + if priv.D.Cmp(N) >= 0 { + t.Error("private key should be less than N") + } +} + +func TestGeneratePrivateKeyUnique(t *testing.T) { + // Generate two keys, they should be different + priv1, _ := GeneratePrivateKey() + priv2, _ := GeneratePrivateKey() + + if priv1.D.Cmp(priv2.D) == 0 { + t.Error("two generated keys should not be identical") + } +} + +func TestPrivateKeyFromBytes(t *testing.T) { + // 32 bytes of 0x01 + b := make([]byte, 32) + b[31] = 0x01 // value = 1 + + priv, err := NewPrivateKeyFromBytes(b) + if err != nil { + t.Fatalf("failed to create key: %v", err) + } + + if priv.D.Cmp(big.NewInt(1)) != 0 { + t.Errorf("expected D=1, got %s", priv.D.String()) + } +} + +func TestPrivateKeyFromBytesInvalidLength(t *testing.T) { + b := make([]byte, 31) // wrong length + _, err := NewPrivateKeyFromBytes(b) + if err == nil { + t.Error("should reject non-32-byte input") + } +} + +func TestPrivateKeyFromBytesZero(t *testing.T) { + b := make([]byte, 32) // all zeros + _, err := NewPrivateKeyFromBytes(b) + if err == nil { + t.Error("should reject zero private key") + } +} + +func TestPrivateKeyFromHex(t *testing.T) { + hex := "0000000000000000000000000000000000000000000000000000000000000001" + priv, err := NewPrivateKeyFromHex(hex) + if err != nil { + t.Fatalf("failed to create key: %v", err) + } + + if priv.D.Cmp(big.NewInt(1)) != 0 { + t.Errorf("expected D=1, got %s", priv.D.String()) + } +} + +func TestPrivateKeyFromHexInvalid(t *testing.T) { + _, err := NewPrivateKeyFromHex("not-hex") + if err == nil { + t.Error("should reject invalid hex") + } +} + +func TestPublicKeyDerivation(t *testing.T) { + // Private key = 1, public key should be G + priv, _ := NewPrivateKeyFromHex("0000000000000000000000000000000000000000000000000000000000000001") + pub := priv.PublicKey() + + if !pub.Point.Equal(G) { + t.Error("1 * G should equal G") + } +} + +func TestPublicKeyDerivation2(t *testing.T) { + // Private key = 2, public key should be 2G + priv, _ := NewPrivateKeyFromHex("0000000000000000000000000000000000000000000000000000000000000002") + pub := priv.PublicKey() + + expected := G.Double() + if !pub.Point.Equal(expected) { + t.Error("2 * G should equal 2G") + } +} + +func TestPublicKeyOnCurve(t *testing.T) { + priv, _ := GeneratePrivateKey() + pub := priv.PublicKey() + + if !pub.Point.IsOnCurve() { + t.Error("derived public key should be on curve") + } +} + +func TestPrivateKeyBytes(t *testing.T) { + priv, _ := NewPrivateKeyFromHex("0000000000000000000000000000000000000000000000000000000000000001") + b := priv.Bytes() + + if len(b) != 32 { + t.Errorf("expected 32 bytes, got %d", len(b)) + } + if b[31] != 0x01 { + t.Errorf("expected last byte to be 0x01, got 0x%02x", b[31]) + } +} + +func TestPrivateKeyRoundTrip(t *testing.T) { + priv1, _ := GeneratePrivateKey() + b := priv1.Bytes() + priv2, _ := NewPrivateKeyFromBytes(b) + + if priv1.D.Cmp(priv2.D) != 0 { + t.Error("private key should survive bytes round-trip") + } +} + +func TestPublicKeyBytesUncompressed(t *testing.T) { + priv, _ := NewPrivateKeyFromHex("0000000000000000000000000000000000000000000000000000000000000001") + pub := priv.PublicKey() + b := pub.Bytes() + + if len(b) != 65 { + t.Errorf("uncompressed pubkey should be 65 bytes, got %d", len(b)) + } + if b[0] != 0x04 { + t.Errorf("uncompressed prefix should be 0x04, got 0x%02x", b[0]) + } +} + +func TestPublicKeyBytesCompressed(t *testing.T) { + priv, _ := NewPrivateKeyFromHex("0000000000000000000000000000000000000000000000000000000000000001") + pub := priv.PublicKey() + b := pub.BytesCompressed() + + if len(b) != 33 { + t.Errorf("compressed pubkey should be 33 bytes, got %d", len(b)) + } + // G has odd y, so prefix should be 0x03 + if b[0] != 0x02 && b[0] != 0x03 { + t.Errorf("compressed prefix should be 0x02 or 0x03, got 0x%02x", b[0]) + } +} + +func TestPublicKeyHex(t *testing.T) { + priv, _ := NewPrivateKeyFromHex("0000000000000000000000000000000000000000000000000000000000000001") + pub := priv.PublicKey() + + hex := pub.Hex() + if len(hex) != 130 { // 65 bytes * 2 + t.Errorf("uncompressed hex should be 130 chars, got %d", len(hex)) + } + + hexComp := pub.HexCompressed() + if len(hexComp) != 66 { // 33 bytes * 2 + t.Errorf("compressed hex should be 66 chars, got %d", len(hexComp)) + } +} + +func TestPublicKeyEqual(t *testing.T) { + priv1, _ := NewPrivateKeyFromHex("0000000000000000000000000000000000000000000000000000000000000001") + priv2, _ := NewPrivateKeyFromHex("0000000000000000000000000000000000000000000000000000000000000001") + priv3, _ := NewPrivateKeyFromHex("0000000000000000000000000000000000000000000000000000000000000002") + + pub1 := priv1.PublicKey() + pub2 := priv2.PublicKey() + pub3 := priv3.PublicKey() + + if !pub1.Equal(pub2) { + t.Error("same private key should produce equal public keys") + } + if pub1.Equal(pub3) { + t.Error("different private keys should produce different public keys") + } +} + +// Known test vector from Bitcoin wiki +func TestKnownKeyVector(t *testing.T) { + // Private key = 1 + priv, _ := NewPrivateKeyFromHex("0000000000000000000000000000000000000000000000000000000000000001") + pub := priv.PublicKey() + + // Public key should be G + expectedX := Gx + expectedY := Gy + + if pub.Point.x.value.Cmp(expectedX) != 0 { + t.Error("public key x doesn't match G.x") + } + if pub.Point.y.value.Cmp(expectedY) != 0 { + t.Error("public key y doesn't match G.y") + } + + // Check compressed format starts with correct x + compressed := pub.BytesCompressed() + xFromCompressed := compressed[1:33] + expectedXBytes := make([]byte, 32) + copy(expectedXBytes[32-len(expectedX.Bytes()):], expectedX.Bytes()) + + if !bytes.Equal(xFromCompressed, expectedXBytes) { + t.Error("compressed pubkey x doesn't match") + } +} -- cgit v1.2.3