sxs_validation_test.go raw
1 //go:build !js
2
3 package p256k1
4
5 import (
6 "bytes"
7 "crypto/sha256"
8 "encoding/hex"
9 "fmt"
10 "testing"
11 )
12
13 // Side-by-Side (SxS) Validation Tests
14 // These tests compare Go implementation results directly against libsecp256k1
15
16 // TestSxSGeneratorPoint verifies the generator point G matches
17 func TestSxSGeneratorPoint(t *testing.T) {
18 // Known generator point coordinates from secp256k1 spec
19 expectedGx := "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
20 expectedGy := "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"
21
22 var gx, gy [32]byte
23 gxFe := Generator.x
24 gyFe := Generator.y
25 gxFe.normalize()
26 gyFe.normalize()
27 gxFe.getB32(gx[:])
28 gyFe.getB32(gy[:])
29
30 if hex.EncodeToString(gx[:]) != expectedGx {
31 t.Errorf("Generator.x mismatch:\n got: %s\n want: %s",
32 hex.EncodeToString(gx[:]), expectedGx)
33 }
34 if hex.EncodeToString(gy[:]) != expectedGy {
35 t.Errorf("Generator.y mismatch:\n got: %s\n want: %s",
36 hex.EncodeToString(gy[:]), expectedGy)
37 }
38 }
39
40 // TestSxSScalarMultGenerator tests k*G for various scalars
41 func TestSxSScalarMultGenerator(t *testing.T) {
42 lib, err := GetLibSecp256k1()
43 if err != nil {
44 t.Skipf("libsecp256k1 not available: %v", err)
45 }
46
47 testCases := []string{
48 "0000000000000000000000000000000000000000000000000000000000000001", // 1
49 "0000000000000000000000000000000000000000000000000000000000000002", // 2
50 "0000000000000000000000000000000000000000000000000000000000000003", // 3
51 "0000000000000000000000000000000000000000000000000000000000000007", // 7
52 "000000000000000000000000000000000000000000000000000000000000000f", // 15
53 "00000000000000000000000000000000000000000000000000000000000000ff", // 255
54 "000000000000000000000000000000000000000000000000000000000000ffff", // 65535
55 "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", // random
56 "deadbeefcafebabe1234567890abcdef0123456789abcdef0123456789abcdef", // random
57 "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", // n-1
58 "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", // n/2
59 "5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72", // lambda
60 }
61
62 for _, scalarHex := range testCases {
63 t.Run(scalarHex[:8], func(t *testing.T) {
64 seckey, _ := hex.DecodeString(scalarHex)
65
66 // Get libsecp256k1 result (full compressed pubkey)
67 libPubkey, libParity, err := lib.CreatePubkeyCompressed(seckey)
68 if err != nil {
69 t.Skipf("libsecp256k1 rejected key: %v", err)
70 }
71
72 // Parse scalar
73 var s Scalar
74 s.setB32(seckey)
75
76 // Compute using each Go implementation
77 implementations := []struct {
78 name string
79 fn func(*GroupElementJacobian, *Scalar)
80 }{
81 {"EcmultConst", func(r *GroupElementJacobian, s *Scalar) { EcmultConst(r, &Generator, s) }},
82 {"ecmultWindowedVar", func(r *GroupElementJacobian, s *Scalar) { ecmultWindowedVar(r, &Generator, s) }},
83 {"ecmultGenGLV", func(r *GroupElementJacobian, s *Scalar) { ecmultGenGLV(r, s) }},
84 {"ecmultStraussWNAFGLV", func(r *GroupElementJacobian, s *Scalar) { ecmultStraussWNAFGLV(r, &Generator, s) }},
85 }
86
87 for _, impl := range implementations {
88 t.Run(impl.name, func(t *testing.T) {
89 var result GroupElementJacobian
90 impl.fn(&result, &s)
91
92 var aff GroupElementAffine
93 aff.setGEJ(&result)
94 aff.x.normalize()
95 aff.y.normalize()
96
97 // Serialize to compressed format
98 var goCompressed [33]byte
99 var xBytes, yBytes [32]byte
100 aff.x.getB32(xBytes[:])
101 aff.y.getB32(yBytes[:])
102
103 if yBytes[31]&1 == 0 {
104 goCompressed[0] = 0x02
105 } else {
106 goCompressed[0] = 0x03
107 }
108 copy(goCompressed[1:], xBytes[:])
109
110 // Compare
111 if !bytes.Equal(goCompressed[:], libPubkey) {
112 t.Errorf("Mismatch:\n Go: %s\n Lib: %s",
113 hex.EncodeToString(goCompressed[:]),
114 hex.EncodeToString(libPubkey))
115 }
116
117 // Also verify parity
118 goParity := 0
119 if yBytes[31]&1 == 1 {
120 goParity = 1
121 }
122 if goParity != libParity {
123 t.Errorf("Parity mismatch: Go=%d, Lib=%d", goParity, libParity)
124 }
125 })
126 }
127 })
128 }
129 }
130
131 // TestSxSFullPubkey tests full X,Y coordinates match
132 func TestSxSFullPubkey(t *testing.T) {
133 lib, err := GetLibSecp256k1()
134 if err != nil {
135 t.Skipf("libsecp256k1 not available: %v", err)
136 }
137
138 testCases := []string{
139 "0000000000000000000000000000000000000000000000000000000000000001",
140 "0000000000000000000000000000000000000000000000000000000000000002",
141 "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
142 }
143
144 for _, scalarHex := range testCases {
145 t.Run(scalarHex[:8], func(t *testing.T) {
146 seckey, _ := hex.DecodeString(scalarHex)
147
148 // Get libsecp256k1 uncompressed pubkey (65 bytes: 0x04 || X || Y)
149 libPubkey, err := lib.CreatePubkeyUncompressed(seckey)
150 if err != nil {
151 t.Fatalf("libsecp256k1 failed: %v", err)
152 }
153
154 if len(libPubkey) != 65 || libPubkey[0] != 0x04 {
155 t.Fatalf("Invalid uncompressed pubkey format")
156 }
157
158 libX := libPubkey[1:33]
159 libY := libPubkey[33:65]
160
161 // Compute using Go
162 var s Scalar
163 s.setB32(seckey)
164
165 var result GroupElementJacobian
166 EcmultConst(&result, &Generator, &s)
167
168 var aff GroupElementAffine
169 aff.setGEJ(&result)
170 aff.x.normalize()
171 aff.y.normalize()
172
173 var goX, goY [32]byte
174 aff.x.getB32(goX[:])
175 aff.y.getB32(goY[:])
176
177 t.Logf("Scalar: %s", scalarHex)
178 t.Logf("Lib X: %s", hex.EncodeToString(libX))
179 t.Logf("Go X: %s", hex.EncodeToString(goX[:]))
180 t.Logf("Lib Y: %s", hex.EncodeToString(libY))
181 t.Logf("Go Y: %s", hex.EncodeToString(goY[:]))
182
183 if !bytes.Equal(goX[:], libX) {
184 t.Errorf("X coordinate mismatch")
185 }
186 if !bytes.Equal(goY[:], libY) {
187 t.Errorf("Y coordinate mismatch")
188 }
189 })
190 }
191 }
192
193 // TestSxSArbitraryPointMult tests k*P for arbitrary points P
194 func TestSxSArbitraryPointMult(t *testing.T) {
195 lib, err := GetLibSecp256k1()
196 if err != nil {
197 t.Skipf("libsecp256k1 not available: %v", err)
198 }
199
200 // Generate a base point P = baseScalar * G
201 baseScalars := []string{
202 "0000000000000000000000000000000000000000000000000000000000000007",
203 "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
204 }
205
206 multScalars := []string{
207 "0000000000000000000000000000000000000000000000000000000000000002",
208 "0000000000000000000000000000000000000000000000000000000000000003",
209 "deadbeefcafebabe1234567890abcdef0123456789abcdef0123456789abcdef",
210 }
211
212 for _, baseHex := range baseScalars {
213 for _, multHex := range multScalars {
214 name := fmt.Sprintf("base_%s_mult_%s", baseHex[:4], multHex[:4])
215 t.Run(name, func(t *testing.T) {
216 baseBytes, _ := hex.DecodeString(baseHex)
217 multBytes, _ := hex.DecodeString(multHex)
218
219 // Compute P = baseScalar * G using Go
220 var baseScalar Scalar
221 baseScalar.setB32(baseBytes)
222
223 var pJac GroupElementJacobian
224 EcmultConst(&pJac, &Generator, &baseScalar)
225 var p GroupElementAffine
226 p.setGEJ(&pJac)
227 p.x.normalize()
228 p.y.normalize()
229
230 // Serialize P as compressed for libsecp256k1
231 pCompressed := make([]byte, 33)
232 var pX [32]byte
233 var pY [32]byte
234 p.x.getB32(pX[:])
235 p.y.getB32(pY[:])
236 if pY[31]&1 == 0 {
237 pCompressed[0] = 0x02
238 } else {
239 pCompressed[0] = 0x03
240 }
241 copy(pCompressed[1:], pX[:])
242
243 // Compute multScalar * P using libsecp256k1 via ECDH
244 // ECDH(seckey, pubkey) computes seckey * pubkey
245 libResult, err := lib.ECDH(multBytes, pCompressed)
246 if err != nil {
247 t.Fatalf("libsecp256k1 ECDH failed: %v", err)
248 }
249
250 // Compute multScalar * P using Go implementations
251 var multScalar Scalar
252 multScalar.setB32(multBytes)
253
254 implementations := []struct {
255 name string
256 fn func(*GroupElementJacobian, *GroupElementAffine, *Scalar)
257 }{
258 {"EcmultConst", func(r *GroupElementJacobian, a *GroupElementAffine, s *Scalar) { EcmultConst(r, a, s) }},
259 {"ecmultWindowedVar", func(r *GroupElementJacobian, a *GroupElementAffine, s *Scalar) { ecmultWindowedVar(r, a, s) }},
260 {"ecmultStraussWNAFGLV", func(r *GroupElementJacobian, a *GroupElementAffine, s *Scalar) { ecmultStraussWNAFGLV(r, a, s) }},
261 }
262
263 for _, impl := range implementations {
264 t.Run(impl.name, func(t *testing.T) {
265 var result GroupElementJacobian
266 impl.fn(&result, &p, &multScalar)
267
268 var resultAff GroupElementAffine
269 resultAff.setGEJ(&result)
270 resultAff.x.normalize()
271 resultAff.y.normalize()
272
273 // libsecp256k1 ECDH returns sha256(compressed_point)
274 // so we need to hash our result the same way
275 var goCompressed [33]byte
276 var resultX, resultY [32]byte
277 resultAff.x.getB32(resultX[:])
278 resultAff.y.getB32(resultY[:])
279
280 if resultY[31]&1 == 0 {
281 goCompressed[0] = 0x02
282 } else {
283 goCompressed[0] = 0x03
284 }
285 copy(goCompressed[1:], resultX[:])
286
287 goHash := sha256Sum(goCompressed[:])
288
289 if !bytes.Equal(goHash[:], libResult) {
290 t.Errorf("Mismatch:\n Go hash: %s\n Lib hash: %s\n Go point: %s",
291 hex.EncodeToString(goHash[:]),
292 hex.EncodeToString(libResult),
293 hex.EncodeToString(goCompressed[:]))
294 }
295 })
296 }
297 })
298 }
299 }
300 }
301
302 // sha256Sum computes SHA256 hash
303 func sha256Sum(data []byte) [32]byte {
304 return sha256.Sum256(data)
305 }
306
307 // TestSxSGLVConstants verifies GLV constants match libsecp256k1
308 func TestSxSGLVConstants(t *testing.T) {
309 // These are the known GLV constants from libsecp256k1
310 // Lambda: cube root of unity mod n
311 expectedLambda := "5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72"
312 // Beta: cube root of unity mod p
313 expectedBeta := "7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"
314
315 var lambdaBytes [32]byte
316 scalarLambda.getB32(lambdaBytes[:])
317 if hex.EncodeToString(lambdaBytes[:]) != expectedLambda {
318 t.Errorf("Lambda mismatch:\n got: %s\n want: %s",
319 hex.EncodeToString(lambdaBytes[:]), expectedLambda)
320 }
321
322 var betaBytes [32]byte
323 fieldBeta.getB32(betaBytes[:])
324 if hex.EncodeToString(betaBytes[:]) != expectedBeta {
325 t.Errorf("Beta mismatch:\n got: %s\n want: %s",
326 hex.EncodeToString(betaBytes[:]), expectedBeta)
327 }
328
329 // Verify lambda^3 = 1 (mod n)
330 var lambda2, lambda3 Scalar
331 lambda2.mul(&scalarLambda, &scalarLambda)
332 lambda3.mul(&lambda2, &scalarLambda)
333 if !lambda3.equal(&ScalarOne) {
334 t.Error("lambda^3 != 1 (mod n)")
335 }
336
337 // Verify beta^3 = 1 (mod p)
338 var beta2, beta3 FieldElement
339 beta2.sqr(&fieldBeta)
340 beta3.mul(&beta2, &fieldBeta)
341 beta3.normalize()
342 if !beta3.equal(&FieldElementOne) {
343 t.Error("beta^3 != 1 (mod p)")
344 }
345 }
346
347 // TestSxSEndomorphism verifies λ*P = (β*x, y)
348 func TestSxSEndomorphism(t *testing.T) {
349 lib, err := GetLibSecp256k1()
350 if err != nil {
351 t.Skipf("libsecp256k1 not available: %v", err)
352 }
353
354 testCases := []string{
355 "0000000000000000000000000000000000000000000000000000000000000001",
356 "0000000000000000000000000000000000000000000000000000000000000002",
357 "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
358 }
359
360 for _, scalarHex := range testCases {
361 t.Run(scalarHex[:8], func(t *testing.T) {
362 seckey, _ := hex.DecodeString(scalarHex)
363
364 // Compute P = k * G
365 var s Scalar
366 s.setB32(seckey)
367
368 var pJac GroupElementJacobian
369 EcmultConst(&pJac, &Generator, &s)
370 var p GroupElementAffine
371 p.setGEJ(&pJac)
372 p.x.normalize()
373 p.y.normalize()
374
375 // Compute λ*P using scalar multiplication
376 var lambdaPJac GroupElementJacobian
377 EcmultConst(&lambdaPJac, &p, &scalarLambda)
378 var lambdaP GroupElementAffine
379 lambdaP.setGEJ(&lambdaPJac)
380 lambdaP.x.normalize()
381 lambdaP.y.normalize()
382
383 // Compute λ*P using endomorphism (β*x, y)
384 var endoP GroupElementAffine
385 endoP.mulLambda(&p)
386 endoP.x.normalize()
387 endoP.y.normalize()
388
389 // They should match
390 var lambdaPX, lambdaPY, endoPX, endoPY [32]byte
391 lambdaP.x.getB32(lambdaPX[:])
392 lambdaP.y.getB32(lambdaPY[:])
393 endoP.x.getB32(endoPX[:])
394 endoP.y.getB32(endoPY[:])
395
396 if !bytes.Equal(lambdaPX[:], endoPX[:]) {
397 t.Errorf("X mismatch:\n scalar mult: %s\n endomorphism: %s",
398 hex.EncodeToString(lambdaPX[:]), hex.EncodeToString(endoPX[:]))
399 }
400 if !bytes.Equal(lambdaPY[:], endoPY[:]) {
401 t.Errorf("Y mismatch:\n scalar mult: %s\n endomorphism: %s",
402 hex.EncodeToString(lambdaPY[:]), hex.EncodeToString(endoPY[:]))
403 }
404
405 // Also verify against libsecp256k1
406 // Compute (k * lambda) * G via libsecp256k1
407 var kLambda Scalar
408 kLambda.mul(&s, &scalarLambda)
409 var kLambdaBytes [32]byte
410 kLambda.getB32(kLambdaBytes[:])
411
412 libPubkey, err := lib.CreatePubkeyUncompressed(kLambdaBytes[:])
413 if err != nil {
414 t.Fatalf("libsecp256k1 failed: %v", err)
415 }
416
417 libX := libPubkey[1:33]
418 libY := libPubkey[33:65]
419
420 if !bytes.Equal(lambdaPX[:], libX) {
421 t.Errorf("X vs libsecp256k1 mismatch:\n Go: %s\n Lib: %s",
422 hex.EncodeToString(lambdaPX[:]), hex.EncodeToString(libX))
423 }
424 if !bytes.Equal(lambdaPY[:], libY) {
425 t.Errorf("Y vs libsecp256k1 mismatch:\n Go: %s\n Lib: %s",
426 hex.EncodeToString(lambdaPY[:]), hex.EncodeToString(libY))
427 }
428 })
429 }
430 }
431
432 // TestSxSScalarSplit validates the GLV scalar splitting
433 func TestSxSScalarSplit(t *testing.T) {
434 lib, err := GetLibSecp256k1()
435 if err != nil {
436 t.Skipf("libsecp256k1 not available: %v", err)
437 }
438
439 testCases := []string{
440 "0000000000000000000000000000000000000000000000000000000000000001",
441 "0000000000000000000000000000000000000000000000000000000000000002",
442 "00000000000000000000000000000000ffffffffffffffffffffffffffffffff",
443 "ffffffffffffffffffffffffffffffff00000000000000000000000000000000",
444 "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
445 "deadbeefcafebabe1234567890abcdef0123456789abcdef0123456789abcdef",
446 "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", // n-1
447 }
448
449 for _, scalarHex := range testCases {
450 t.Run(scalarHex[:8], func(t *testing.T) {
451 kBytes, _ := hex.DecodeString(scalarHex)
452
453 var k Scalar
454 k.setB32(kBytes)
455
456 // Split k into k1, k2
457 var k1, k2 Scalar
458 scalarSplitLambda(&k1, &k2, &k)
459
460 // Log the split
461 var k1Bytes, k2Bytes [32]byte
462 k1.getB32(k1Bytes[:])
463 k2.getB32(k2Bytes[:])
464 t.Logf("k = %s", scalarHex)
465 t.Logf("k1 = %s", hex.EncodeToString(k1Bytes[:]))
466 t.Logf("k2 = %s", hex.EncodeToString(k2Bytes[:]))
467
468 // Verify k1 + k2*lambda = k
469 var k2Lambda, reconstructed Scalar
470 k2Lambda.mul(&k2, &scalarLambda)
471 reconstructed.add(&k1, &k2Lambda)
472
473 if !reconstructed.equal(&k) {
474 var recBytes [32]byte
475 reconstructed.getB32(recBytes[:])
476 t.Errorf("Scalar reconstruction failed:\n k1+k2*lambda = %s\n k = %s",
477 hex.EncodeToString(recBytes[:]), scalarHex)
478 }
479
480 // Verify k1*G + k2*(lambda*G) = k*G via libsecp256k1
481 expectedPubkey, err := lib.CreatePubkeyUncompressed(kBytes)
482 if err != nil {
483 t.Skipf("libsecp256k1 rejected key")
484 }
485
486 // Compute k1*G
487 var k1G GroupElementJacobian
488 EcmultConst(&k1G, &Generator, &k1)
489
490 // Compute lambda*G
491 var lambdaG GroupElementAffine
492 lambdaG.mulLambda(&Generator)
493
494 // Compute k2*(lambda*G)
495 var k2LambdaG GroupElementJacobian
496 EcmultConst(&k2LambdaG, &lambdaG, &k2)
497
498 // Add k1*G + k2*(lambda*G)
499 var sum GroupElementJacobian
500 sum.addVar(&k1G, &k2LambdaG)
501
502 var sumAff GroupElementAffine
503 sumAff.setGEJ(&sum)
504 sumAff.x.normalize()
505 sumAff.y.normalize()
506
507 var sumX, sumY [32]byte
508 sumAff.x.getB32(sumX[:])
509 sumAff.y.getB32(sumY[:])
510
511 expectedX := expectedPubkey[1:33]
512 expectedY := expectedPubkey[33:65]
513
514 if !bytes.Equal(sumX[:], expectedX) {
515 t.Errorf("X mismatch:\n k1*G + k2*(λ*G) = %s\n k*G (lib) = %s",
516 hex.EncodeToString(sumX[:]), hex.EncodeToString(expectedX))
517 }
518 if !bytes.Equal(sumY[:], expectedY) {
519 t.Errorf("Y mismatch:\n k1*G + k2*(λ*G) = %s\n k*G (lib) = %s",
520 hex.EncodeToString(sumY[:]), hex.EncodeToString(expectedY))
521 }
522 })
523 }
524 }
525
526 // TestSxSRandomScalars runs extensive random scalar tests
527 func TestSxSRandomScalars(t *testing.T) {
528 lib, err := GetLibSecp256k1()
529 if err != nil {
530 t.Skipf("libsecp256k1 not available: %v", err)
531 }
532
533 seed := uint64(0xdeadbeef12345678)
534 const numTests = 500
535
536 var failures int
537 for i := 0; i < numTests; i++ {
538 // Generate pseudo-random scalar
539 var seckey [32]byte
540 for j := 0; j < 32; j++ {
541 seed = seed*6364136223846793005 + 1442695040888963407
542 seckey[j] = byte(seed >> 56)
543 }
544
545 // Get libsecp256k1 result
546 libPubkey, _, err := lib.CreatePubkeyCompressed(seckey[:])
547 if err != nil {
548 continue // Skip invalid keys
549 }
550
551 var s Scalar
552 s.setB32(seckey[:])
553
554 // Test each implementation
555 implementations := []struct {
556 name string
557 fn func(*GroupElementJacobian, *Scalar)
558 }{
559 {"EcmultConst", func(r *GroupElementJacobian, s *Scalar) { EcmultConst(r, &Generator, s) }},
560 {"ecmultWindowedVar", func(r *GroupElementJacobian, s *Scalar) { ecmultWindowedVar(r, &Generator, s) }},
561 {"ecmultGenGLV", func(r *GroupElementJacobian, s *Scalar) { ecmultGenGLV(r, s) }},
562 {"ecmultStraussWNAFGLV", func(r *GroupElementJacobian, s *Scalar) { ecmultStraussWNAFGLV(r, &Generator, s) }},
563 }
564
565 for _, impl := range implementations {
566 var result GroupElementJacobian
567 impl.fn(&result, &s)
568
569 var aff GroupElementAffine
570 aff.setGEJ(&result)
571 aff.x.normalize()
572 aff.y.normalize()
573
574 var goCompressed [33]byte
575 var xBytes, yBytes [32]byte
576 aff.x.getB32(xBytes[:])
577 aff.y.getB32(yBytes[:])
578
579 if yBytes[31]&1 == 0 {
580 goCompressed[0] = 0x02
581 } else {
582 goCompressed[0] = 0x03
583 }
584 copy(goCompressed[1:], xBytes[:])
585
586 if !bytes.Equal(goCompressed[:], libPubkey) {
587 t.Errorf("Test %d %s mismatch:\n scalar: %s\n Go: %s\n Lib: %s",
588 i, impl.name, hex.EncodeToString(seckey[:]),
589 hex.EncodeToString(goCompressed[:]),
590 hex.EncodeToString(libPubkey))
591 failures++
592 if failures > 10 {
593 t.Fatalf("Too many failures, stopping")
594 }
595 }
596 }
597 }
598
599 t.Logf("Tested %d random scalars with 4 implementations each (%d total operations)", numTests, numTests*4)
600 }
601
602 // TestSxSECDSASignVerify tests ECDSA signature creation and verification
603 func TestSxSECDSASignVerify(t *testing.T) {
604 lib, err := GetLibSecp256k1()
605 if err != nil {
606 t.Skipf("libsecp256k1 not available: %v", err)
607 }
608
609 testCases := []struct {
610 name string
611 seckey string
612 msg string
613 }{
614 {"simple", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
615 {"random", "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", "deadbeefcafebabe1234567890abcdef0123456789abcdef0123456789abcdef"},
616 {"msg_all_ff", "0000000000000000000000000000000000000000000000000000000000000002", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
617 {"high_scalar", "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"},
618 }
619
620 for _, tc := range testCases {
621 t.Run(tc.name, func(t *testing.T) {
622 seckey, _ := hex.DecodeString(tc.seckey)
623 msg, _ := hex.DecodeString(tc.msg)
624
625 // Get compressed pubkey from libsecp256k1
626 pubkeyCompressed, _, err := lib.CreatePubkeyCompressed(seckey)
627 if err != nil {
628 t.Skipf("libsecp256k1 rejected key: %v", err)
629 }
630
631 // 1. Sign with libsecp256k1, verify with libsecp256k1
632 libSig, err := lib.ECDSASign(msg, seckey)
633 if err != nil {
634 t.Fatalf("lib ECDSASign failed: %v", err)
635 }
636 if !lib.ECDSAVerify(libSig, msg, pubkeyCompressed) {
637 t.Error("lib failed to verify its own signature")
638 }
639
640 // 2. Sign with libsecp256k1, verify with Go
641 // Parse the pubkey for Go
642 var pubkey PublicKey
643 if err := ECPubkeyParse(&pubkey, pubkeyCompressed); err != nil {
644 t.Fatalf("ECPubkeyParse failed: %v", err)
645 }
646
647 var goSig ECDSASignature
648 if err := goSig.FromCompact((*ECDSASignatureCompact)(libSig)); err != nil {
649 t.Fatalf("FromCompact failed: %v", err)
650 }
651 if !ECDSAVerify(&goSig, msg, &pubkey) {
652 t.Error("Go failed to verify libsecp256k1 signature")
653 }
654
655 // 3. Sign with Go, verify with libsecp256k1
656 var goGenSig ECDSASignature
657 if err := ECDSASign(&goGenSig, msg, seckey); err != nil {
658 t.Fatalf("Go ECDSASign failed: %v", err)
659 }
660 goCompact := goGenSig.ToCompact()
661 if !lib.ECDSAVerify(goCompact[:], msg, pubkeyCompressed) {
662 t.Errorf("libsecp256k1 failed to verify Go signature")
663 }
664
665 // 4. Sign with Go, verify with Go
666 if !ECDSAVerify(&goGenSig, msg, &pubkey) {
667 t.Error("Go failed to verify its own signature")
668 }
669
670 // 5. Test DER encoding round-trip
671 t.Run("DER", func(t *testing.T) {
672 // Get DER from libsecp256k1
673 libDER, err := lib.ECDSASignDER(msg, seckey)
674 if err != nil {
675 t.Fatalf("lib ECDSASignDER failed: %v", err)
676 }
677
678 // Verify with libsecp256k1
679 if !lib.ECDSAVerifyDER(libDER, msg, pubkeyCompressed) {
680 t.Error("lib failed to verify its own DER signature")
681 }
682
683 // Parse with Go
684 var derSig ECDSASignature
685 if err := derSig.ParseDER(libDER); err != nil {
686 t.Fatalf("Go ParseDER failed: %v", err)
687 }
688 if !ECDSAVerify(&derSig, msg, &pubkey) {
689 t.Error("Go failed to verify lib DER signature")
690 }
691
692 // Sign DER with Go
693 goDER, err := ECDSASignDER(msg, seckey)
694 if err != nil {
695 t.Fatalf("Go ECDSASignDER failed: %v", err)
696 }
697
698 // Verify Go DER with libsecp256k1
699 if !lib.ECDSAVerifyDER(goDER, msg, pubkeyCompressed) {
700 t.Errorf("lib failed to verify Go DER signature:\n Go: %s\n Lib: %s",
701 hex.EncodeToString(goDER), hex.EncodeToString(libDER))
702 }
703 })
704 })
705 }
706 }
707
708 // TestSxSECDSARandomMessages tests ECDSA with random messages
709 func TestSxSECDSARandomMessages(t *testing.T) {
710 lib, err := GetLibSecp256k1()
711 if err != nil {
712 t.Skipf("libsecp256k1 not available: %v", err)
713 }
714
715 // Fixed secret key
716 seckey, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
717 pubkeyCompressed, _, err := lib.CreatePubkeyCompressed(seckey)
718 if err != nil {
719 t.Fatalf("CreatePubkeyCompressed failed: %v", err)
720 }
721 var pubkey PublicKey
722 if err := ECPubkeyParse(&pubkey, pubkeyCompressed); err != nil {
723 t.Fatalf("ECPubkeyParse failed: %v", err)
724 }
725
726 seed := uint64(0xdeadbeef12345678)
727 const numTests = 100
728
729 for i := 0; i < numTests; i++ {
730 // Generate random message
731 var msg [32]byte
732 for j := 0; j < 32; j++ {
733 seed = seed*6364136223846793005 + 1442695040888963407
734 msg[j] = byte(seed >> 56)
735 }
736
737 // Sign with Go
738 var goSig ECDSASignature
739 if err := ECDSASign(&goSig, msg[:], seckey); err != nil {
740 t.Fatalf("Test %d: ECDSASign failed: %v", i, err)
741 }
742
743 // Verify with libsecp256k1
744 goCompact := goSig.ToCompact()
745 if !lib.ECDSAVerify(goCompact[:], msg[:], pubkeyCompressed) {
746 t.Errorf("Test %d: libsecp256k1 failed to verify Go signature", i)
747 }
748
749 // Verify with Go
750 if !ECDSAVerify(&goSig, msg[:], &pubkey) {
751 t.Errorf("Test %d: Go failed to verify its own signature", i)
752 }
753 }
754
755 t.Logf("Tested %d random messages", numTests)
756 }
757
758 // TestSxSECDSARecover tests public key recovery from ECDSA signatures
759 func TestSxSECDSARecover(t *testing.T) {
760 testCases := []struct {
761 name string
762 seckey string
763 msg string
764 }{
765 {"simple", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
766 {"random", "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", "deadbeefcafebabe1234567890abcdef0123456789abcdef0123456789abcdef"},
767 }
768
769 for _, tc := range testCases {
770 t.Run(tc.name, func(t *testing.T) {
771 seckey, _ := hex.DecodeString(tc.seckey)
772 msg, _ := hex.DecodeString(tc.msg)
773
774 // Create public key from secret key
775 var originalPubkey PublicKey
776 if err := ECPubkeyCreate(&originalPubkey, seckey); err != nil {
777 t.Fatalf("ECPubkeyCreate failed: %v", err)
778 }
779
780 // Sign with recovery
781 var sig ECDSARecoverableSignature
782 if err := ECDSASignRecoverable(&sig, msg, seckey); err != nil {
783 t.Fatalf("ECDSASignRecoverable failed: %v", err)
784 }
785
786 t.Logf("Recovery ID: %d", sig.recid)
787
788 // Recover public key
789 var recoveredPubkey PublicKey
790 if err := ECDSARecover(&recoveredPubkey, &sig, msg); err != nil {
791 t.Fatalf("ECDSARecover failed: %v", err)
792 }
793
794 // Compare public keys
795 if ECPubkeyCmp(&originalPubkey, &recoveredPubkey) != 0 {
796 // Serialize both for comparison
797 original := make([]byte, 33)
798 recovered := make([]byte, 33)
799 ECPubkeySerialize(original, &originalPubkey, 258) // compressed
800 ECPubkeySerialize(recovered, &recoveredPubkey, 258)
801 t.Errorf("Public key mismatch:\n original: %s\n recovered: %s",
802 hex.EncodeToString(original), hex.EncodeToString(recovered))
803 }
804
805 // Also verify the signature
806 compact, recid := sig.ToCompact()
807 t.Logf("Compact signature: %s (recid=%d)", hex.EncodeToString(compact), recid)
808
809 var verifySig ECDSASignature
810 verifySig.r = sig.r
811 verifySig.s = sig.s
812 if !ECDSAVerify(&verifySig, msg, &originalPubkey) {
813 t.Error("Signature verification failed")
814 }
815 })
816 }
817 }
818
819 // TestSxSECDSARecoverRandom tests recovery with random keys
820 func TestSxSECDSARecoverRandom(t *testing.T) {
821 seed := uint64(0xdeadbeef12345678)
822 const numTests = 50
823
824 for i := 0; i < numTests; i++ {
825 // Generate random secret key
826 var seckey [32]byte
827 for j := 0; j < 32; j++ {
828 seed = seed*6364136223846793005 + 1442695040888963407
829 seckey[j] = byte(seed >> 56)
830 }
831
832 // Create public key
833 var originalPubkey PublicKey
834 if err := ECPubkeyCreate(&originalPubkey, seckey[:]); err != nil {
835 continue // Skip invalid keys
836 }
837
838 // Generate random message
839 var msg [32]byte
840 for j := 0; j < 32; j++ {
841 seed = seed*6364136223846793005 + 1442695040888963407
842 msg[j] = byte(seed >> 56)
843 }
844
845 // Sign with recovery
846 var sig ECDSARecoverableSignature
847 if err := ECDSASignRecoverable(&sig, msg[:], seckey[:]); err != nil {
848 t.Fatalf("Test %d: ECDSASignRecoverable failed: %v", i, err)
849 }
850
851 // Recover public key
852 var recoveredPubkey PublicKey
853 if err := ECDSARecover(&recoveredPubkey, &sig, msg[:]); err != nil {
854 t.Fatalf("Test %d: ECDSARecover failed: %v", i, err)
855 }
856
857 // Compare
858 if ECPubkeyCmp(&originalPubkey, &recoveredPubkey) != 0 {
859 t.Errorf("Test %d: Public key mismatch", i)
860 }
861 }
862
863 t.Logf("Tested %d random key/message pairs", numTests)
864 }
865
866 // TestSxSECDSALowS verifies signatures have low-S values
867 func TestSxSECDSALowS(t *testing.T) {
868 lib, err := GetLibSecp256k1()
869 if err != nil {
870 t.Skipf("libsecp256k1 not available: %v", err)
871 }
872
873 seckey, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
874
875 seed := uint64(0xcafebabe)
876 for i := 0; i < 50; i++ {
877 var msg [32]byte
878 for j := 0; j < 32; j++ {
879 seed = seed*6364136223846793005 + 1442695040888963407
880 msg[j] = byte(seed >> 56)
881 }
882
883 // Sign with Go
884 var goSig ECDSASignature
885 if err := ECDSASign(&goSig, msg[:], seckey); err != nil {
886 t.Fatalf("ECDSASign failed: %v", err)
887 }
888
889 // Verify it's low-S
890 if !goSig.IsLowS() {
891 t.Errorf("Test %d: Go signature has high-S", i)
892 }
893
894 // Sign with libsecp256k1
895 libSig, err := lib.ECDSASign(msg[:], seckey)
896 if err != nil {
897 t.Fatalf("lib ECDSASign failed: %v", err)
898 }
899
900 var libGoSig ECDSASignature
901 libGoSig.FromCompact((*ECDSASignatureCompact)(libSig))
902 if !libGoSig.IsLowS() {
903 t.Errorf("Test %d: lib signature has high-S", i)
904 }
905 }
906 }
907
908 // TestSxSSchnorrSignVerify tests Schnorr signature creation and verification
909 func TestSxSSchnorrSignVerify(t *testing.T) {
910 lib, err := GetLibSecp256k1()
911 if err != nil {
912 t.Skipf("libsecp256k1 not available: %v", err)
913 }
914
915 testCases := []struct {
916 name string
917 seckey string
918 msg string
919 }{
920 {"simple", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
921 {"random", "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", "deadbeefcafebabe1234567890abcdef0123456789abcdef0123456789abcdef"},
922 {"msg_all_ff", "0000000000000000000000000000000000000000000000000000000000000002", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
923 }
924
925 for _, tc := range testCases {
926 t.Run(tc.name, func(t *testing.T) {
927 seckey, _ := hex.DecodeString(tc.seckey)
928 msg, _ := hex.DecodeString(tc.msg)
929
930 // Get pubkey from libsecp256k1
931 pubkey, err := lib.CreatePubkey(seckey)
932 if err != nil {
933 t.Fatalf("CreatePubkey failed: %v", err)
934 }
935
936 // 1. Sign with libsecp256k1, verify with libsecp256k1
937 libSig, err := lib.SchnorrSign(msg, seckey)
938 if err != nil {
939 t.Fatalf("lib SchnorrSign failed: %v", err)
940 }
941 if !lib.SchnorrVerify(libSig, msg, pubkey) {
942 t.Error("lib failed to verify its own signature")
943 }
944
945 // 2. Sign with libsecp256k1, verify with Go
946 xonlyPubkey, err := XOnlyPubkeyParse(pubkey)
947 if err != nil {
948 t.Fatalf("XOnlyPubkeyParse failed: %v", err)
949 }
950 if !SchnorrVerify(libSig, msg, xonlyPubkey) {
951 t.Error("Go failed to verify libsecp256k1 signature")
952 }
953
954 // 3. Sign with Go, verify with libsecp256k1
955 keypair, err := KeyPairCreate(seckey)
956 if err != nil {
957 t.Fatalf("KeyPairCreate failed: %v", err)
958 }
959
960 var goSig [64]byte
961 if err := SchnorrSign(goSig[:], msg, keypair, nil); err != nil {
962 t.Fatalf("Go SchnorrSign failed: %v", err)
963 }
964 if !lib.SchnorrVerify(goSig[:], msg, pubkey) {
965 t.Errorf("libsecp256k1 failed to verify Go signature:\n sig: %s",
966 hex.EncodeToString(goSig[:]))
967 }
968
969 // 4. Sign with Go, verify with Go
970 if !SchnorrVerify(goSig[:], msg, xonlyPubkey) {
971 t.Error("Go failed to verify its own signature")
972 }
973 })
974 }
975 }
976