p8k_test.mx raw

   1  // Package p8k tests exercise the signer's decision surface without any
   2  // network or spawning: key/secret parsing and validation, the exact BIP-340
   3  // vector-0 derivation and deterministic signature, request/response shape
   4  // (lengths and rejection mapping), Zero(), and ECDH symmetry/hashing.
   5  //
   6  // The vectors are the first rows of the vendored BIP-340 test vector CSV
   7  // (src/crypto/secp256k1/testdata/bip0340.csv), so a derivation or signing
   8  // regression fails here against the specification, not against the signer's
   9  // own output.
  10  package p8k
  11  
  12  import (
  13  	"crypto/sha256"
  14  	"encoding/hex"
  15  	"testing"
  16  )
  17  
  18  // BIP-340 vector 0: secret key, matching x-only public key, and the
  19  // deterministic signature over an all-zero 32-byte message with zero aux.
  20  const (
  21  	p8kVecSk0  = "0000000000000000000000000000000000000000000000000000000000000003"
  22  	p8kVecPub0 = "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9"
  23  	p8kVecSig0 = "E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0"
  24  )
  25  
  26  // p8kHex decodes a hex fixture, failing the test when the fixture itself is
  27  // malformed.
  28  func p8kHex(t *testing.T, s string) (b []byte) {
  29  	dec, err := hex.DecodeString([]byte(s))
  30  	if err != nil {
  31  		t.Fatalf("fixture %q is not hex: %s", s, err.Error())
  32  		return
  33  	}
  34  	return dec
  35  }
  36  
  37  // p8kBytes returns n bytes all set to fill.
  38  func p8kBytes(n int32, fill byte) (b []byte) {
  39  	b = []byte{:n}
  40  	for i := 0; i < n; i++ {
  41  		b[i] = fill
  42  	}
  43  	return
  44  }
  45  
  46  // p8kCopy returns an independent copy of src.
  47  func p8kCopy(src []byte) (dst []byte) {
  48  	dst = []byte{:len(src)}
  49  	for i := 0; i < len(src); i++ {
  50  		dst[i] = src[i]
  51  	}
  52  	return
  53  }
  54  
  55  // p8kEq compares two byte strings without bytes.Equal (whose needles can be
  56  // missed in long haystacks under this toolchain).
  57  func p8kEq(a, b []byte) (ok bool) {
  58  	if len(a) != len(b) {
  59  		return
  60  	}
  61  	for i := 0; i < len(a); i++ {
  62  		if a[i] != b[i] {
  63  			return
  64  		}
  65  	}
  66  	ok = true
  67  	return
  68  }
  69  
  70  func TestNewAndMustNew(t *testing.T) {
  71  	var err error
  72  	var s *Signer
  73  	s, err = New()
  74  	if err != nil || s == nil {
  75  		t.Fatal("New must return a live signer")
  76  	}
  77  	if MustNew() == nil {
  78  		t.Fatal("MustNew must return a live signer")
  79  	}
  80  	// A fresh signer has neither key loaded.
  81  	if s.Sec() != nil {
  82  		t.Fatal("a fresh signer must expose no secret")
  83  	}
  84  	if s.Pub() != nil {
  85  		t.Fatal("a fresh signer must expose no public key")
  86  	}
  87  }
  88  
  89  func TestInitSecRoundTrip(t *testing.T) {
  90  	var err error
  91  	sk := p8kHex(t, p8kVecSk0)
  92  	wantPub := p8kHex(t, p8kVecPub0)
  93  	s := MustNew()
  94  	err = s.InitSec(sk)
  95  	if err != nil {
  96  		t.Fatalf("InitSec(valid): %s", err.Error())
  97  	}
  98  	if !p8kEq(s.Sec(), sk) {
  99  		t.Fatal("Sec must round-trip the secret key")
 100  	}
 101  	if !p8kEq(s.Pub(), wantPub) {
 102  		t.Fatal("InitSec must derive the BIP-340 public key")
 103  	}
 104  	// Sec returns a copy, not a view: mutating it must not change the signer.
 105  	got := s.Sec()
 106  	if len(got) == 0 {
 107  		t.Fatal("Sec returned nothing to mutate")
 108  		return
 109  	}
 110  	got[0] = got[0] + 1
 111  	if p8kEq(s.Sec(), got) {
 112  		t.Fatal("Sec must return an independent copy")
 113  	}
 114  }
 115  
 116  func TestInitSecRejects(t *testing.T) {
 117  	var err error
 118  	s := MustNew()
 119  	err = s.InitSec([]byte("short"))
 120  	if err == nil {
 121  		t.Fatal("InitSec must reject a non-32-byte key")
 122  	}
 123  	err = s.InitSec([]byte{})
 124  	if err == nil {
 125  		t.Fatal("InitSec must reject an empty key")
 126  	}
 127  	err = s.InitSec(p8kBytes(33, 0x01))
 128  	if err == nil {
 129  		t.Fatal("InitSec must reject a 33-byte key")
 130  	}
 131  	// The all-zero scalar is outside [1, n-1].
 132  	err = s.InitSec(p8kBytes(32, 0))
 133  	if err == nil {
 134  		t.Fatal("InitSec must reject the zero scalar")
 135  	}
 136  	if s.Sec() != nil || s.Pub() != nil {
 137  		t.Fatal("a rejected InitSec must leave no keys")
 138  	}
 139  }
 140  
 141  func TestInitPubRoundTripAndRejects(t *testing.T) {
 142  	var err error
 143  	pub := p8kHex(t, p8kVecPub0)
 144  	s := MustNew()
 145  	err = s.InitPub(pub)
 146  	if err != nil {
 147  		t.Fatalf("InitPub(valid): %s", err.Error())
 148  	}
 149  	if !p8kEq(s.Pub(), pub) {
 150  		t.Fatal("Pub must round-trip the public key")
 151  	}
 152  	if s.Sec() != nil {
 153  		t.Fatal("InitPub alone must not expose a secret")
 154  	}
 155  	err = s.InitPub(p8kBytes(31, 0x02))
 156  	if err == nil {
 157  		t.Fatal("InitPub must reject a short key")
 158  	}
 159  	err = s.InitPub(p8kBytes(33, 0x02))
 160  	if err == nil {
 161  		t.Fatal("InitPub must reject a long key")
 162  	}
 163  	// An x coordinate at or above the field size has no curve point.
 164  	err = s.InitPub(p8kBytes(32, 0xFF))
 165  	if err == nil {
 166  		t.Fatal("InitPub must reject an out-of-field x coordinate")
 167  	}
 168  }
 169  
 170  func TestInitPubClearsSecret(t *testing.T) {
 171  	var err error
 172  	sk := p8kHex(t, p8kVecSk0)
 173  	pub := p8kHex(t, p8kVecPub0)
 174  	s := MustNew()
 175  	err = s.InitSec(sk)
 176  	if err != nil {
 177  		t.Fatalf("InitSec(valid): %s", err.Error())
 178  	}
 179  	err = s.InitPub(pub)
 180  	if err != nil {
 181  		t.Fatalf("InitPub(valid): %s", err.Error())
 182  	}
 183  	if s.Sec() != nil {
 184  		t.Fatal("InitPub must drop a previously loaded secret")
 185  	}
 186  	if !p8kEq(s.Pub(), pub) {
 187  		t.Fatal("InitPub must keep the loaded public key")
 188  	}
 189  }
 190  
 191  func TestSignDeterministicVector(t *testing.T) {
 192  	var err error
 193  	var sig []byte
 194  	var ok bool
 195  	sk := p8kHex(t, p8kVecSk0)
 196  	pub := p8kHex(t, p8kVecPub0)
 197  	wantSig := p8kHex(t, p8kVecSig0)
 198  	msg := p8kBytes(32, 0)
 199  
 200  	s := MustNew()
 201  	err = s.InitSec(sk)
 202  	if err != nil {
 203  		t.Fatalf("InitSec(valid): %s", err.Error())
 204  	}
 205  	sig, err = s.Sign(msg)
 206  	if err != nil {
 207  		t.Fatalf("Sign(vector 0 message): %s", err.Error())
 208  	}
 209  	if !p8kEq(sig, wantSig) {
 210  		t.Fatal("Sign must reproduce the BIP-340 vector-0 signature (zero aux)")
 211  	}
 212  
 213  	v := MustNew()
 214  	err = v.InitPub(pub)
 215  	if err != nil {
 216  		t.Fatalf("InitPub(valid): %s", err.Error())
 217  	}
 218  	ok, err = v.Verify(msg, sig)
 219  	if err != nil {
 220  		t.Fatalf("Verify(vector 0): %s", err.Error())
 221  	}
 222  	if !ok {
 223  		t.Fatal("the vector-0 signature must verify")
 224  	}
 225  
 226  	// A different message must not verify against the same signature.
 227  	other := p8kBytes(32, 0x01)
 228  	ok, err = v.Verify(other, sig)
 229  	if err != nil {
 230  		t.Fatalf("Verify(other message): %s", err.Error())
 231  	}
 232  	if ok {
 233  		t.Fatal("a signature must not verify against a different message")
 234  	}
 235  
 236  	// A corrupted signature must not verify.
 237  	corrupt := p8kCopy(sig)
 238  	corrupt[0] = corrupt[0] + 1
 239  	ok, err = v.Verify(msg, corrupt)
 240  	if err != nil {
 241  		t.Fatalf("Verify(corrupted signature): %s", err.Error())
 242  	}
 243  	if ok {
 244  		t.Fatal("a corrupted signature must not verify")
 245  	}
 246  }
 247  
 248  func TestSignVerifyRejections(t *testing.T) {
 249  	var err error
 250  	var ok bool
 251  	msg := p8kBytes(32, 0x22)
 252  	s := MustNew()
 253  	_, err = s.Sign(msg)
 254  	if err == nil {
 255  		t.Fatal("Sign without a secret key must fail")
 256  	}
 257  	nokey := MustNew()
 258  	_, err = nokey.Verify(msg, p8kBytes(64, 0))
 259  	if err == nil {
 260  		t.Fatal("Verify without a public key must fail")
 261  	}
 262  	err = s.InitSec(p8kHex(t, p8kVecSk0))
 263  	if err != nil {
 264  		t.Fatalf("InitSec(valid): %s", err.Error())
 265  	}
 266  	_, err = s.Sign(p8kBytes(31, 0))
 267  	if err == nil {
 268  		t.Fatal("Sign must reject a short message")
 269  	}
 270  	_, err = s.Sign(p8kBytes(33, 0))
 271  	if err == nil {
 272  		t.Fatal("Sign must reject a long message")
 273  	}
 274  
 275  	v := MustNew()
 276  	err = v.InitPub(p8kHex(t, p8kVecPub0))
 277  	if err != nil {
 278  		t.Fatalf("InitPub(valid): %s", err.Error())
 279  	}
 280  	_, err = v.Verify(p8kBytes(31, 0), p8kBytes(64, 0))
 281  	if err == nil {
 282  		t.Fatal("Verify must reject a short message")
 283  	}
 284  	_, err = v.Verify(msg, p8kBytes(63, 0))
 285  	if err == nil {
 286  		t.Fatal("Verify must reject a short signature")
 287  	}
 288  	_, err = v.Verify(msg, p8kBytes(65, 0))
 289  	if err == nil {
 290  		t.Fatal("Verify must reject a long signature")
 291  	}
 292  	ok, err = v.Verify(msg, p8kBytes(64, 0))
 293  	if err != nil {
 294  		t.Fatalf("Verify(all-zero signature): %s", err.Error())
 295  	}
 296  	if ok {
 297  		t.Fatal("an all-zero signature must not verify")
 298  	}
 299  }
 300  
 301  func TestGenerateAndZero(t *testing.T) {
 302  	var err error
 303  	var sig []byte
 304  	var ok bool
 305  	s := MustNew()
 306  	err = s.Generate()
 307  	if err != nil {
 308  		t.Fatalf("Generate: %s", err.Error())
 309  	}
 310  	sec := s.Sec()
 311  	pub := s.Pub()
 312  	if len(sec) != 32 {
 313  		t.Fatalf("generated secret length = %d", int32(len(sec)))
 314  	}
 315  	if len(pub) != 32 {
 316  		t.Fatalf("generated public length = %d", int32(len(pub)))
 317  	}
 318  	msg := p8kBytes(32, 0x11)
 319  	sig, err = s.Sign(msg)
 320  	if err != nil {
 321  		t.Fatalf("Sign after Generate: %s", err.Error())
 322  	}
 323  	if len(sig) != 64 {
 324  		t.Fatalf("signature length = %d", int32(len(sig)))
 325  	}
 326  	ok, err = s.Verify(msg, sig)
 327  	if err != nil {
 328  		t.Fatalf("Verify after Generate: %s", err.Error())
 329  	}
 330  	if !ok {
 331  		t.Fatal("a generated key must verify its own signature")
 332  	}
 333  
 334  	s.Zero()
 335  	if s.Sec() != nil {
 336  		t.Fatal("Zero must clear the secret")
 337  	}
 338  	if s.Pub() != nil {
 339  		t.Fatal("Zero must clear the public key")
 340  	}
 341  	_, err = s.Sign(msg)
 342  	if err == nil {
 343  		t.Fatal("Sign after Zero must fail")
 344  	}
 345  	_, err = s.Verify(msg, sig)
 346  	if err == nil {
 347  		t.Fatal("Verify after Zero must fail")
 348  	}
 349  	_, err = s.ECDHRaw(pub)
 350  	if err == nil {
 351  		t.Fatal("ECDHRaw after Zero must fail")
 352  	}
 353  }
 354  
 355  func TestECDH(t *testing.T) {
 356  	var err error
 357  	var raw []byte
 358  	var hashed []byte
 359  	var peer []byte
 360  	a := MustNew()
 361  	err = a.InitSec(p8kHex(t, p8kVecSk0))
 362  	if err != nil {
 363  		t.Fatalf("InitSec(valid): %s", err.Error())
 364  	}
 365  	b := MustNew()
 366  	err = b.Generate()
 367  	if err != nil {
 368  		t.Fatalf("Generate: %s", err.Error())
 369  	}
 370  
 371  	fresh := MustNew()
 372  	_, err = fresh.ECDHRaw(b.Pub())
 373  	if err == nil {
 374  		t.Fatal("ECDHRaw without a secret key must fail")
 375  	}
 376  	_, err = fresh.ECDH(b.Pub())
 377  	if err == nil {
 378  		t.Fatal("ECDH without a secret key must fail")
 379  	}
 380  
 381  	_, err = a.ECDHRaw(p8kBytes(31, 0))
 382  	if err == nil {
 383  		t.Fatal("ECDHRaw must reject a short pubkey")
 384  	}
 385  	_, err = a.ECDHRaw(p8kBytes(33, 0))
 386  	if err == nil {
 387  		t.Fatal("ECDHRaw must reject a long pubkey")
 388  	}
 389  	// An x coordinate at or above the field prime is not a curve point:
 390  	// BIP-340 fails it, and secp256k1.LiftX now refuses it instead of lifting a
 391  	// non-canonical value (p8k only checks the 32-byte length, so this is the
 392  	// stdlib's verdict propagating through ECDHRaw/ECDH).
 393  	_, err = a.ECDHRaw(p8kBytes(32, 0xFF))
 394  	if err == nil {
 395  		t.Fatal("ECDHRaw must reject an out-of-field x coordinate")
 396  	}
 397  	_, err = a.ECDH(p8kBytes(32, 0xFF))
 398  	if err == nil {
 399  		t.Fatal("ECDH must reject an out-of-field x coordinate")
 400  	}
 401  	_, err = a.ECDH(p8kBytes(31, 0))
 402  	if err == nil {
 403  		t.Fatal("ECDH must propagate a rejected pubkey")
 404  	}
 405  	// x = 0 has no curve point (7 is a non-residue mod p), so a well-formed
 406  	// 32-byte all-zero pubkey must surface ECDH's own failure, not a length
 407  	// error.
 408  	_, err = a.ECDHRaw(p8kBytes(32, 0))
 409  	if err == nil {
 410  		t.Fatal("ECDHRaw must report an x coordinate with no curve point")
 411  	}
 412  
 413  	raw, err = a.ECDHRaw(b.Pub())
 414  	if err != nil {
 415  		t.Fatalf("ECDHRaw: %s", err.Error())
 416  	}
 417  	if len(raw) != 32 {
 418  		t.Fatalf("ECDHRaw length = %d", int32(len(raw)))
 419  	}
 420  	peer, err = b.ECDHRaw(a.Pub())
 421  	if err != nil {
 422  		t.Fatalf("ECDHRaw(peer): %s", err.Error())
 423  	}
 424  	if !p8kEq(raw, peer) {
 425  		t.Fatal("ECDH must be symmetric")
 426  	}
 427  
 428  	hashed, err = a.ECDH(b.Pub())
 429  	if err != nil {
 430  		t.Fatalf("ECDH: %s", err.Error())
 431  	}
 432  	h := sha256.Sum256(raw)
 433  	if !p8kEq(hashed, h[:]) {
 434  		t.Fatal("ECDH must be sha256 of ECDHRaw")
 435  	}
 436  }
 437