aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--compat_test.go453
-rw-r--r--go.mod8
-rw-r--r--go.sum10
3 files changed, 471 insertions, 0 deletions
diff --git a/compat_test.go b/compat_test.go
new file mode 100644
index 0000000..b5cbde7
--- /dev/null
+++ b/compat_test.go
@@ -0,0 +1,453 @@
1package secp256k1
2
3import (
4 "bytes"
5 "crypto/sha256"
6 "encoding/hex"
7 "math/big"
8 "testing"
9
10 "github.com/btcsuite/btcd/btcec/v2"
11 "github.com/btcsuite/btcd/btcec/v2/schnorr"
12)
13
14// TestKeyDerivationCompatibility verifies that our key derivation
15// produces the same public key as btcec for the same private key
16func TestKeyDerivationCompatibility(t *testing.T) {
17 testCases := []string{
18 "0000000000000000000000000000000000000000000000000000000000000001",
19 "0000000000000000000000000000000000000000000000000000000000000002",
20 "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", // N-1
21 "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", // random
22 "b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfef", // from BIP-340
23 }
24
25 for _, tc := range testCases {
26 t.Run(tc[:8]+"...", func(t *testing.T) {
27 privBytes, err := hex.DecodeString(tc)
28 if err != nil {
29 t.Fatalf("invalid hex: %v", err)
30 }
31
32 // Our implementation
33 ourPriv, err := NewPrivateKeyFromBytes(privBytes)
34 if err != nil {
35 t.Fatalf("our NewPrivateKeyFromBytes failed: %v", err)
36 }
37 ourPub := ourPriv.PublicKey()
38
39 // btcec implementation
40 btcPriv, btcPub := btcec.PrivKeyFromBytes(privBytes)
41 _ = btcPriv
42
43 // Compare uncompressed public keys
44 ourPubBytes := ourPub.Bytes()
45 btcPubBytes := btcPub.SerializeUncompressed()
46
47 if !bytes.Equal(ourPubBytes, btcPubBytes) {
48 t.Errorf("uncompressed public key mismatch\nours: %x\nbtcec: %x",
49 ourPubBytes, btcPubBytes)
50 }
51
52 // Compare compressed public keys
53 ourPubCompressed := ourPub.BytesCompressed()
54 btcPubCompressed := btcPub.SerializeCompressed()
55
56 if !bytes.Equal(ourPubCompressed, btcPubCompressed) {
57 t.Errorf("compressed public key mismatch\nours: %x\nbtcec: %x",
58 ourPubCompressed, btcPubCompressed)
59 }
60
61 // Compare x-only public keys (BIP-340)
62 ourXOnly := ourPub.XOnlyBytes()
63 btcXOnly := schnorr.SerializePubKey(btcPub)
64
65 if !bytes.Equal(ourXOnly, btcXOnly) {
66 t.Errorf("x-only public key mismatch\nours: %x\nbtcec: %x",
67 ourXOnly, btcXOnly)
68 }
69 })
70 }
71}
72
73// TestOurSignatureVerifiesWithBtcec tests that signatures we create
74// can be verified by btcec
75func TestOurSignatureVerifiesWithBtcec(t *testing.T) {
76 messages := [][]byte{
77 []byte("Hello, Bitcoin!"),
78 []byte(""),
79 []byte("The quick brown fox jumps over the lazy dog"),
80 make([]byte, 32), // 32 zeros
81 }
82
83 for i, msg := range messages {
84 t.Run(string(rune('A'+i)), func(t *testing.T) {
85 // Generate a key pair with our implementation
86 ourPriv, err := GeneratePrivateKey()
87 if err != nil {
88 t.Fatalf("key generation failed: %v", err)
89 }
90 ourPub := ourPriv.PublicKey()
91
92 // Hash the message (BIP-340 signs 32-byte messages)
93 msgHash := sha256.Sum256(msg)
94
95 // Sign with our implementation
96 ourSig, err := Sign(ourPriv, msgHash[:])
97 if err != nil {
98 t.Fatalf("our Sign failed: %v", err)
99 }
100
101 // Parse the signature with btcec
102 btcSig, err := schnorr.ParseSignature(ourSig.Bytes())
103 if err != nil {
104 t.Fatalf("btcec couldn't parse our signature: %v", err)
105 }
106
107 // Parse our public key with btcec
108 btcPub, err := schnorr.ParsePubKey(ourPub.XOnlyBytes())
109 if err != nil {
110 t.Fatalf("btcec couldn't parse our pubkey: %v", err)
111 }
112
113 // Verify with btcec
114 if !btcSig.Verify(msgHash[:], btcPub) {
115 t.Error("btcec failed to verify our signature")
116 }
117 })
118 }
119}
120
121// TestBtcecSignatureVerifiesWithOurs tests that signatures created by btcec
122// can be verified by our implementation
123func TestBtcecSignatureVerifiesWithOurs(t *testing.T) {
124 messages := [][]byte{
125 []byte("Hello, Bitcoin!"),
126 []byte(""),
127 []byte("The quick brown fox jumps over the lazy dog"),
128 make([]byte, 32), // 32 zeros
129 }
130
131 for i, msg := range messages {
132 t.Run(string(rune('A'+i)), func(t *testing.T) {
133 // Generate a key pair with btcec
134 btcPriv, err := btcec.NewPrivateKey()
135 if err != nil {
136 t.Fatalf("btcec key generation failed: %v", err)
137 }
138 _ = btcPriv.PubKey() // ensure it's valid
139
140 // Hash the message
141 msgHash := sha256.Sum256(msg)
142
143 // Sign with btcec
144 btcSig, err := schnorr.Sign(btcPriv, msgHash[:])
145 if err != nil {
146 t.Fatalf("btcec Sign failed: %v", err)
147 }
148
149 // Parse the private key with our implementation
150 ourPriv, err := NewPrivateKeyFromBytes(btcPriv.Serialize())
151 if err != nil {
152 t.Fatalf("our NewPrivateKeyFromBytes failed: %v", err)
153 }
154 ourPub := ourPriv.PublicKey()
155
156 // Parse the signature with our implementation
157 ourSig, err := SignatureFromBytes(btcSig.Serialize())
158 if err != nil {
159 t.Fatalf("our SignatureFromBytes failed: %v", err)
160 }
161
162 // Verify with our implementation
163 if !Verify(ourPub, msgHash[:], ourSig) {
164 t.Error("our implementation failed to verify btcec signature")
165 }
166 })
167 }
168}
169
170// TestCrossSignAndVerify tests sign with one, verify with the other,
171// for the same key pair
172func TestCrossSignAndVerify(t *testing.T) {
173 // Generate key with our implementation
174 ourPriv, err := GeneratePrivateKey()
175 if err != nil {
176 t.Fatalf("key generation failed: %v", err)
177 }
178 ourPub := ourPriv.PublicKey()
179
180 // Get the same key in btcec
181 btcPriv, btcPub := btcec.PrivKeyFromBytes(ourPriv.Bytes())
182
183 msg := sha256.Sum256([]byte("cross-implementation test"))
184
185 // Test 1: Sign with ours, verify with both
186 ourSig, err := Sign(ourPriv, msg[:])
187 if err != nil {
188 t.Fatalf("our Sign failed: %v", err)
189 }
190
191 if !Verify(ourPub, msg[:], ourSig) {
192 t.Error("our signature failed our verification")
193 }
194
195 btcSigFromOurs, _ := schnorr.ParseSignature(ourSig.Bytes())
196 btcPubParsed, _ := schnorr.ParsePubKey(ourPub.XOnlyBytes())
197 if !btcSigFromOurs.Verify(msg[:], btcPubParsed) {
198 t.Error("our signature failed btcec verification")
199 }
200
201 // Test 2: Sign with btcec, verify with both
202 btcSig, err := schnorr.Sign(btcPriv, msg[:])
203 if err != nil {
204 t.Fatalf("btcec Sign failed: %v", err)
205 }
206
207 btcPubForVerify, _ := schnorr.ParsePubKey(schnorr.SerializePubKey(btcPub))
208 if !btcSig.Verify(msg[:], btcPubForVerify) {
209 t.Error("btcec signature failed btcec verification")
210 }
211
212 sigFromBtcec, _ := SignatureFromBytes(btcSig.Serialize())
213 if !Verify(ourPub, msg[:], sigFromBtcec) {
214 t.Error("btcec signature failed our verification")
215 }
216}
217
218// BIP340TestVector represents an official BIP-340 test vector
219type BIP340TestVector struct {
220 Index int
221 SecretKey string // hex, empty for verification-only tests
222 PublicKey string // hex, 32 bytes x-only
223 AuxRand string // hex, 32 bytes
224 Message string // hex, 32 bytes
225 Signature string // hex, 64 bytes
226 Valid bool
227 Comment string
228}
229
230// TestBIP340Vectors tests against official BIP-340 test vectors
231func TestBIP340Vectors(t *testing.T) {
232 // Official test vectors from BIP-340
233 // https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv
234 vectors := []BIP340TestVector{
235 {
236 Index: 0,
237 SecretKey: "0000000000000000000000000000000000000000000000000000000000000003",
238 PublicKey: "f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
239 AuxRand: "0000000000000000000000000000000000000000000000000000000000000000",
240 Message: "0000000000000000000000000000000000000000000000000000000000000000",
241 Signature: "e907831f80848d1069a5371b402410364bdf1c5f8307b0084c55f1ce2dca821525f66a4a85ea8b71e482a74f382d2ce5ebeee8fdb2172f477df4900d310536c0",
242 Valid: true,
243 },
244 {
245 Index: 1,
246 SecretKey: "b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfef",
247 PublicKey: "dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
248 AuxRand: "0000000000000000000000000000000000000000000000000000000000000001",
249 Message: "243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
250 Signature: "6896bd60eeae296db48a229ff71dfe071bde413e6d43f917dc8dcf8c78de33418906d11ac976abccb20b091292bff4ea897efcb639ea871cfa95f6de339e4b0a",
251 Valid: true,
252 },
253 {
254 Index: 2,
255 SecretKey: "c90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b14e5c9",
256 PublicKey: "dd308afec5777e13121fa72b9cc1b7cc0139715309b086c960e18fd969774eb8",
257 AuxRand: "c87aa53824b4d7ae2eb035a2b5bbbccc080e76cdc6d1692c4b0b62d798e6d906",
258 Message: "7e2d58d8b3bcdf1abadec7829054f90dda9805aab56c77333024b9d0a508b75c",
259 Signature: "5831aaeed7b44bb74e5eab94ba9d4294c49bcf2a60728d8b4c200f50dd313c1bab745879a5ad954a72c45a91c3a51d3c7adea98d82f8481e0e1e03674a6f3fb7",
260 Valid: true,
261 },
262 {
263 Index: 3,
264 SecretKey: "0b432b2677937381aef05bb02a66ecd012773062cf3fa2549e44f58ed2401710",
265 PublicKey: "25d1dff95105f5253c4022f628a996ad3a0d95fbf21d468a1b33f8c160d8f517",
266 AuxRand: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
267 Message: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
268 Signature: "7eb0509757e246f19449885651611cb965ecc1a187dd51b64fda1edc9637d5ec97582b9cb13db3933705b32ba982af5af25fd78881ebb32771fc5922efc66ea3",
269 Valid: true,
270 },
271 // Verification-only vectors (public key only, testing invalid signatures)
272 {
273 Index: 4,
274 PublicKey: "d69c3509bb99e412e68b0fe8544e72837dfa30746d8be2aa65975f29d22dc7b9",
275 Message: "4df3c3f68fcc83b27e9d42c90431a72499f17875c81a599b566c9889b9696703",
276 Signature: "00000000000000000000003b78ce563f89a0ed9414f5aa28ad0d96d6795f9c6376afb1548af603b3eb45c9f8207dee1060cb71c04e80f593060b07d28308d7f4",
277 Valid: true,
278 Comment: "sG - eP is infinite",
279 },
280 {
281 Index: 5,
282 PublicKey: "eefdea4cdb677750a420fee807eacf21eb9898ae79b9768766e4faa04a2d4a34",
283 Message: "243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
284 Signature: "6cff5c3ba86c69ea4b7376f31a9bcb4f74c1976089b2d9963da2e5543e17776969e89b4c5564d00349106b8497785dd7d1d713a8ae82b32fa79d5f7fc407d39b",
285 Valid: false,
286 Comment: "public key not on curve",
287 },
288 {
289 Index: 6,
290 PublicKey: "dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
291 Message: "243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
292 Signature: "fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a14602975563cc27944640ac607cd107ae10923d9ef7a73c643e166be5ebeafa34b1ac553e2",
293 Valid: false,
294 Comment: "has_even_y(R) is false",
295 },
296 {
297 Index: 7,
298 PublicKey: "dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
299 Message: "243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
300 Signature: "1fa62e331edbc21c394792d2ab1100a7b432b013df3f6ff4f99fcb33e0e1515f28890b3edb6e7189b630448b515ce4f8622a954cfe545735aaea5134fccdb2bd",
301 Valid: false,
302 Comment: "negated message",
303 },
304 {
305 Index: 8,
306 PublicKey: "dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
307 Message: "243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
308 Signature: "6cff5c3ba86c69ea4b7376f31a9bcb4f74c1976089b2d9963da2e5543e177769961764b3aa9b2ffcb6ef947b6887a226e8d7c93e00c5ed0c1834ff0d0c2e6da6",
309 Valid: false,
310 Comment: "negated s value",
311 },
312 {
313 Index: 9,
314 PublicKey: "dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
315 Message: "243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
316 Signature: "0000000000000000000000000000000000000000000000000000000000000000123dda8328af9c23a94c1feecfd123ba4fb73476f0d594dcb65c6425bd186051",
317 Valid: false,
318 Comment: "sG - eP is infinite (edge case)",
319 },
320 {
321 Index: 10,
322 PublicKey: "dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
323 Message: "243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
324 Signature: "00000000000000000000000000000000000000000000000000000000000000017615fbaf5ae28864013c099742deadb4dba87f11ac6754f93780d5a1837cf197",
325 Valid: false,
326 Comment: "sig[0:32] is not an X coordinate on the curve",
327 },
328 {
329 Index: 11,
330 PublicKey: "dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
331 Message: "243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
332 Signature: "4a298dacae57395a15d0795ddbfd1dcb564da82b0f269bc70a74f8220429ba1d69e89b4c5564d00349106b8497785dd7d1d713a8ae82b32fa79d5f7fc407d39b",
333 Valid: false,
334 Comment: "sig[0:32] is equal to field size",
335 },
336 {
337 Index: 12,
338 PublicKey: "dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
339 Message: "243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
340 Signature: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f69e89b4c5564d00349106b8497785dd7d1d713a8ae82b32fa79d5f7fc407d39b",
341 Valid: false,
342 Comment: "sig[0:32] is equal to field size",
343 },
344 {
345 Index: 13,
346 PublicKey: "dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
347 Message: "243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
348 Signature: "6cff5c3ba86c69ea4b7376f31a9bcb4f74c1976089b2d9963da2e5543e177769fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141",
349 Valid: false,
350 Comment: "sig[32:64] is equal to curve order",
351 },
352 }
353
354 for _, v := range vectors {
355 name := "vec" + string(rune('0'+v.Index))
356 if v.Comment != "" {
357 name += "_" + v.Comment[:min(20, len(v.Comment))]
358 }
359
360 t.Run(name, func(t *testing.T) {
361 pubBytes, _ := hex.DecodeString(v.PublicKey)
362 msgBytes, _ := hex.DecodeString(v.Message)
363 sigBytes, _ := hex.DecodeString(v.Signature)
364
365 // If we have a secret key, test signing as well
366 if v.SecretKey != "" {
367 secBytes, _ := hex.DecodeString(v.SecretKey)
368 auxBytes, _ := hex.DecodeString(v.AuxRand)
369
370 // Test our signing produces the expected signature
371 priv, err := NewPrivateKeyFromBytes(secBytes)
372 if err != nil {
373 t.Fatalf("failed to create private key: %v", err)
374 }
375
376 sig, err := Sign(priv, msgBytes, auxBytes)
377 if err != nil {
378 t.Fatalf("Sign failed: %v", err)
379 }
380
381 if !bytes.Equal(sig.Bytes(), sigBytes) {
382 t.Errorf("signature mismatch\ngot: %x\nwant: %x",
383 sig.Bytes(), sigBytes)
384 }
385
386 // Verify public key matches
387 pub := priv.PublicKey()
388 if !bytes.Equal(pub.XOnlyBytes(), pubBytes) {
389 t.Errorf("public key mismatch\ngot: %x\nwant: %x",
390 pub.XOnlyBytes(), pubBytes)
391 }
392 }
393
394 // Test verification
395 pubPoint, err := liftX(new(big.Int).SetBytes(pubBytes))
396 if err != nil {
397 if v.Valid {
398 t.Errorf("liftX failed for valid vector: %v", err)
399 }
400 return // Can't test verification if pubkey is invalid
401 }
402 pub := &PublicKey{Point: pubPoint}
403
404 sig, err := SignatureFromBytes(sigBytes)
405 if err != nil {
406 if v.Valid {
407 t.Errorf("SignatureFromBytes failed for valid vector: %v", err)
408 }
409 return
410 }
411
412 result := Verify(pub, msgBytes, sig)
413 if result != v.Valid {
414 t.Errorf("Verify returned %v, expected %v", result, v.Valid)
415 }
416 })
417 }
418}
419
420// TestTaggedHashFormat verifies our tagged hash follows BIP-340 format:
421// SHA256(SHA256(tag) || SHA256(tag) || msg)
422func TestTaggedHashFormat(t *testing.T) {
423 // Verify the structure is correct by checking determinism and length
424 tags := []string{"BIP0340/challenge", "BIP0340/aux", "BIP0340/nonce"}
425
426 for _, tag := range tags {
427 t.Run(tag, func(t *testing.T) {
428 msg := []byte("test message")
429 result1 := TaggedHash(tag, msg)
430 result2 := TaggedHash(tag, msg)
431
432 if len(result1) != 32 {
433 t.Errorf("expected 32 bytes, got %d", len(result1))
434 }
435
436 if !bytes.Equal(result1, result2) {
437 t.Error("TaggedHash is not deterministic")
438 }
439
440 // Different tags should produce different results
441 otherResult := TaggedHash("different/tag", msg)
442 if bytes.Equal(result1, otherResult) {
443 t.Error("different tags produced same hash")
444 }
445
446 // Different messages should produce different results
447 differentMsg := TaggedHash(tag, []byte("different"))
448 if bytes.Equal(result1, differentMsg) {
449 t.Error("different messages produced same hash")
450 }
451 })
452 }
453}
diff --git a/go.mod b/go.mod
index f8eed0f..7d2f45d 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,11 @@
1module secp256k1-learn 1module secp256k1-learn
2 2
3go 1.23.5 3go 1.23.5
4
5require github.com/btcsuite/btcd/btcec/v2 v2.3.6
6
7require (
8 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
9 github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
10 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
11)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..3f06a00
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,10 @@
1github.com/btcsuite/btcd/btcec/v2 v2.3.6 h1:IzlsEr9olcSRKB/n7c4351F3xHKxS2lma+1UFGCYd4E=
2github.com/btcsuite/btcd/btcec/v2 v2.3.6/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ=
3github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
4github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
5github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
6github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
8github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
9github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
10github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=