// Package p8k tests exercise the signer's decision surface without any // network or spawning: key/secret parsing and validation, the exact BIP-340 // vector-0 derivation and deterministic signature, request/response shape // (lengths and rejection mapping), Zero(), and ECDH symmetry/hashing. // // The vectors are the first rows of the vendored BIP-340 test vector CSV // (src/crypto/secp256k1/testdata/bip0340.csv), so a derivation or signing // regression fails here against the specification, not against the signer's // own output. package p8k import ( "crypto/sha256" "encoding/hex" "testing" ) // BIP-340 vector 0: secret key, matching x-only public key, and the // deterministic signature over an all-zero 32-byte message with zero aux. const ( p8kVecSk0 = "0000000000000000000000000000000000000000000000000000000000000003" p8kVecPub0 = "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9" p8kVecSig0 = "E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0" ) // p8kHex decodes a hex fixture, failing the test when the fixture itself is // malformed. func p8kHex(t *testing.T, s string) (b []byte) { dec, err := hex.DecodeString([]byte(s)) if err != nil { t.Fatalf("fixture %q is not hex: %s", s, err.Error()) return } return dec } // p8kBytes returns n bytes all set to fill. func p8kBytes(n int32, fill byte) (b []byte) { b = []byte{:n} for i := 0; i < n; i++ { b[i] = fill } return } // p8kCopy returns an independent copy of src. func p8kCopy(src []byte) (dst []byte) { dst = []byte{:len(src)} for i := 0; i < len(src); i++ { dst[i] = src[i] } return } // p8kEq compares two byte strings without bytes.Equal (whose needles can be // missed in long haystacks under this toolchain). func p8kEq(a, b []byte) (ok bool) { if len(a) != len(b) { return } for i := 0; i < len(a); i++ { if a[i] != b[i] { return } } ok = true return } func TestNewAndMustNew(t *testing.T) { var err error var s *Signer s, err = New() if err != nil || s == nil { t.Fatal("New must return a live signer") } if MustNew() == nil { t.Fatal("MustNew must return a live signer") } // A fresh signer has neither key loaded. if s.Sec() != nil { t.Fatal("a fresh signer must expose no secret") } if s.Pub() != nil { t.Fatal("a fresh signer must expose no public key") } } func TestInitSecRoundTrip(t *testing.T) { var err error sk := p8kHex(t, p8kVecSk0) wantPub := p8kHex(t, p8kVecPub0) s := MustNew() err = s.InitSec(sk) if err != nil { t.Fatalf("InitSec(valid): %s", err.Error()) } if !p8kEq(s.Sec(), sk) { t.Fatal("Sec must round-trip the secret key") } if !p8kEq(s.Pub(), wantPub) { t.Fatal("InitSec must derive the BIP-340 public key") } // Sec returns a copy, not a view: mutating it must not change the signer. got := s.Sec() if len(got) == 0 { t.Fatal("Sec returned nothing to mutate") return } got[0] = got[0] + 1 if p8kEq(s.Sec(), got) { t.Fatal("Sec must return an independent copy") } } func TestInitSecRejects(t *testing.T) { var err error s := MustNew() err = s.InitSec([]byte("short")) if err == nil { t.Fatal("InitSec must reject a non-32-byte key") } err = s.InitSec([]byte{}) if err == nil { t.Fatal("InitSec must reject an empty key") } err = s.InitSec(p8kBytes(33, 0x01)) if err == nil { t.Fatal("InitSec must reject a 33-byte key") } // The all-zero scalar is outside [1, n-1]. err = s.InitSec(p8kBytes(32, 0)) if err == nil { t.Fatal("InitSec must reject the zero scalar") } if s.Sec() != nil || s.Pub() != nil { t.Fatal("a rejected InitSec must leave no keys") } } func TestInitPubRoundTripAndRejects(t *testing.T) { var err error pub := p8kHex(t, p8kVecPub0) s := MustNew() err = s.InitPub(pub) if err != nil { t.Fatalf("InitPub(valid): %s", err.Error()) } if !p8kEq(s.Pub(), pub) { t.Fatal("Pub must round-trip the public key") } if s.Sec() != nil { t.Fatal("InitPub alone must not expose a secret") } err = s.InitPub(p8kBytes(31, 0x02)) if err == nil { t.Fatal("InitPub must reject a short key") } err = s.InitPub(p8kBytes(33, 0x02)) if err == nil { t.Fatal("InitPub must reject a long key") } // An x coordinate at or above the field size has no curve point. err = s.InitPub(p8kBytes(32, 0xFF)) if err == nil { t.Fatal("InitPub must reject an out-of-field x coordinate") } } func TestInitPubClearsSecret(t *testing.T) { var err error sk := p8kHex(t, p8kVecSk0) pub := p8kHex(t, p8kVecPub0) s := MustNew() err = s.InitSec(sk) if err != nil { t.Fatalf("InitSec(valid): %s", err.Error()) } err = s.InitPub(pub) if err != nil { t.Fatalf("InitPub(valid): %s", err.Error()) } if s.Sec() != nil { t.Fatal("InitPub must drop a previously loaded secret") } if !p8kEq(s.Pub(), pub) { t.Fatal("InitPub must keep the loaded public key") } } func TestSignDeterministicVector(t *testing.T) { var err error var sig []byte var ok bool sk := p8kHex(t, p8kVecSk0) pub := p8kHex(t, p8kVecPub0) wantSig := p8kHex(t, p8kVecSig0) msg := p8kBytes(32, 0) s := MustNew() err = s.InitSec(sk) if err != nil { t.Fatalf("InitSec(valid): %s", err.Error()) } sig, err = s.Sign(msg) if err != nil { t.Fatalf("Sign(vector 0 message): %s", err.Error()) } if !p8kEq(sig, wantSig) { t.Fatal("Sign must reproduce the BIP-340 vector-0 signature (zero aux)") } v := MustNew() err = v.InitPub(pub) if err != nil { t.Fatalf("InitPub(valid): %s", err.Error()) } ok, err = v.Verify(msg, sig) if err != nil { t.Fatalf("Verify(vector 0): %s", err.Error()) } if !ok { t.Fatal("the vector-0 signature must verify") } // A different message must not verify against the same signature. other := p8kBytes(32, 0x01) ok, err = v.Verify(other, sig) if err != nil { t.Fatalf("Verify(other message): %s", err.Error()) } if ok { t.Fatal("a signature must not verify against a different message") } // A corrupted signature must not verify. corrupt := p8kCopy(sig) corrupt[0] = corrupt[0] + 1 ok, err = v.Verify(msg, corrupt) if err != nil { t.Fatalf("Verify(corrupted signature): %s", err.Error()) } if ok { t.Fatal("a corrupted signature must not verify") } } func TestSignVerifyRejections(t *testing.T) { var err error var ok bool msg := p8kBytes(32, 0x22) s := MustNew() _, err = s.Sign(msg) if err == nil { t.Fatal("Sign without a secret key must fail") } nokey := MustNew() _, err = nokey.Verify(msg, p8kBytes(64, 0)) if err == nil { t.Fatal("Verify without a public key must fail") } err = s.InitSec(p8kHex(t, p8kVecSk0)) if err != nil { t.Fatalf("InitSec(valid): %s", err.Error()) } _, err = s.Sign(p8kBytes(31, 0)) if err == nil { t.Fatal("Sign must reject a short message") } _, err = s.Sign(p8kBytes(33, 0)) if err == nil { t.Fatal("Sign must reject a long message") } v := MustNew() err = v.InitPub(p8kHex(t, p8kVecPub0)) if err != nil { t.Fatalf("InitPub(valid): %s", err.Error()) } _, err = v.Verify(p8kBytes(31, 0), p8kBytes(64, 0)) if err == nil { t.Fatal("Verify must reject a short message") } _, err = v.Verify(msg, p8kBytes(63, 0)) if err == nil { t.Fatal("Verify must reject a short signature") } _, err = v.Verify(msg, p8kBytes(65, 0)) if err == nil { t.Fatal("Verify must reject a long signature") } ok, err = v.Verify(msg, p8kBytes(64, 0)) if err != nil { t.Fatalf("Verify(all-zero signature): %s", err.Error()) } if ok { t.Fatal("an all-zero signature must not verify") } } func TestGenerateAndZero(t *testing.T) { var err error var sig []byte var ok bool s := MustNew() err = s.Generate() if err != nil { t.Fatalf("Generate: %s", err.Error()) } sec := s.Sec() pub := s.Pub() if len(sec) != 32 { t.Fatalf("generated secret length = %d", int32(len(sec))) } if len(pub) != 32 { t.Fatalf("generated public length = %d", int32(len(pub))) } msg := p8kBytes(32, 0x11) sig, err = s.Sign(msg) if err != nil { t.Fatalf("Sign after Generate: %s", err.Error()) } if len(sig) != 64 { t.Fatalf("signature length = %d", int32(len(sig))) } ok, err = s.Verify(msg, sig) if err != nil { t.Fatalf("Verify after Generate: %s", err.Error()) } if !ok { t.Fatal("a generated key must verify its own signature") } s.Zero() if s.Sec() != nil { t.Fatal("Zero must clear the secret") } if s.Pub() != nil { t.Fatal("Zero must clear the public key") } _, err = s.Sign(msg) if err == nil { t.Fatal("Sign after Zero must fail") } _, err = s.Verify(msg, sig) if err == nil { t.Fatal("Verify after Zero must fail") } _, err = s.ECDHRaw(pub) if err == nil { t.Fatal("ECDHRaw after Zero must fail") } } func TestECDH(t *testing.T) { var err error var raw []byte var hashed []byte var peer []byte a := MustNew() err = a.InitSec(p8kHex(t, p8kVecSk0)) if err != nil { t.Fatalf("InitSec(valid): %s", err.Error()) } b := MustNew() err = b.Generate() if err != nil { t.Fatalf("Generate: %s", err.Error()) } fresh := MustNew() _, err = fresh.ECDHRaw(b.Pub()) if err == nil { t.Fatal("ECDHRaw without a secret key must fail") } _, err = fresh.ECDH(b.Pub()) if err == nil { t.Fatal("ECDH without a secret key must fail") } _, err = a.ECDHRaw(p8kBytes(31, 0)) if err == nil { t.Fatal("ECDHRaw must reject a short pubkey") } _, err = a.ECDHRaw(p8kBytes(33, 0)) if err == nil { t.Fatal("ECDHRaw must reject a long pubkey") } // An x coordinate at or above the field prime is not a curve point: // BIP-340 fails it, and secp256k1.LiftX now refuses it instead of lifting a // non-canonical value (p8k only checks the 32-byte length, so this is the // stdlib's verdict propagating through ECDHRaw/ECDH). _, err = a.ECDHRaw(p8kBytes(32, 0xFF)) if err == nil { t.Fatal("ECDHRaw must reject an out-of-field x coordinate") } _, err = a.ECDH(p8kBytes(32, 0xFF)) if err == nil { t.Fatal("ECDH must reject an out-of-field x coordinate") } _, err = a.ECDH(p8kBytes(31, 0)) if err == nil { t.Fatal("ECDH must propagate a rejected pubkey") } // x = 0 has no curve point (7 is a non-residue mod p), so a well-formed // 32-byte all-zero pubkey must surface ECDH's own failure, not a length // error. _, err = a.ECDHRaw(p8kBytes(32, 0)) if err == nil { t.Fatal("ECDHRaw must report an x coordinate with no curve point") } raw, err = a.ECDHRaw(b.Pub()) if err != nil { t.Fatalf("ECDHRaw: %s", err.Error()) } if len(raw) != 32 { t.Fatalf("ECDHRaw length = %d", int32(len(raw))) } peer, err = b.ECDHRaw(a.Pub()) if err != nil { t.Fatalf("ECDHRaw(peer): %s", err.Error()) } if !p8kEq(raw, peer) { t.Fatal("ECDH must be symmetric") } hashed, err = a.ECDH(b.Pub()) if err != nil { t.Fatalf("ECDH: %s", err.Error()) } h := sha256.Sum256(raw) if !p8kEq(hashed, h[:]) { t.Fatal("ECDH must be sha256 of ECDHRaw") } }