glv_test.go raw
1 //go:build !js && !wasm && !tinygo && !wasm32
2
3 package p256k1
4
5 import (
6 "encoding/hex"
7 "testing"
8 )
9
10 // TestGLVScalarConstants verifies that the GLV scalar constants are correctly defined
11 func TestGLVScalarConstants(t *testing.T) {
12 // Test lambda constant
13 // Expected: 0x5363AD4CC05C30E0A5261C028812645A122E22EA20816678DF02967C1B23BD72
14 var lambdaBytes [32]byte
15 scalarLambda.getB32(lambdaBytes[:])
16 expectedLambda := "5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72"
17 if hex.EncodeToString(lambdaBytes[:]) != expectedLambda {
18 t.Errorf("scalarLambda mismatch:\n got: %s\n want: %s", hex.EncodeToString(lambdaBytes[:]), expectedLambda)
19 }
20
21 // Test g1 constant
22 // Expected: 0x3086D221A7D46BCDE86C90E49284EB153DAA8A1471E8CA7FE893209A45DBB031
23 var g1Bytes [32]byte
24 scalarG1.getB32(g1Bytes[:])
25 expectedG1 := "3086d221a7d46bcde86c90e49284eb153daa8a1471e8ca7fe893209a45dbb031"
26 if hex.EncodeToString(g1Bytes[:]) != expectedG1 {
27 t.Errorf("scalarG1 mismatch:\n got: %s\n want: %s", hex.EncodeToString(g1Bytes[:]), expectedG1)
28 }
29
30 // Test g2 constant
31 // Expected: 0xE4437ED6010E88286F547FA90ABFE4C4221208AC9DF506C61571B4AE8AC47F71
32 var g2Bytes [32]byte
33 scalarG2.getB32(g2Bytes[:])
34 expectedG2 := "e4437ed6010e88286f547fa90abfe4c4221208ac9df506c61571b4ae8ac47f71"
35 if hex.EncodeToString(g2Bytes[:]) != expectedG2 {
36 t.Errorf("scalarG2 mismatch:\n got: %s\n want: %s", hex.EncodeToString(g2Bytes[:]), expectedG2)
37 }
38
39 // Test -b1 constant
40 // Expected: 0x00000000000000000000000000000000E4437ED6010E88286F547FA90ABFE4C3
41 var minusB1Bytes [32]byte
42 scalarMinusB1.getB32(minusB1Bytes[:])
43 expectedMinusB1 := "00000000000000000000000000000000e4437ed6010e88286f547fa90abfe4c3"
44 if hex.EncodeToString(minusB1Bytes[:]) != expectedMinusB1 {
45 t.Errorf("scalarMinusB1 mismatch:\n got: %s\n want: %s", hex.EncodeToString(minusB1Bytes[:]), expectedMinusB1)
46 }
47
48 // Test -b2 constant
49 // Expected: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8A280AC50774346DD765CDA83DB1562C
50 var minusB2Bytes [32]byte
51 scalarMinusB2.getB32(minusB2Bytes[:])
52 expectedMinusB2 := "fffffffffffffffffffffffffffffffe8a280ac50774346dd765cda83db1562c"
53 if hex.EncodeToString(minusB2Bytes[:]) != expectedMinusB2 {
54 t.Errorf("scalarMinusB2 mismatch:\n got: %s\n want: %s", hex.EncodeToString(minusB2Bytes[:]), expectedMinusB2)
55 }
56 }
57
58 // TestGLVLambdaCubeRoot verifies that λ^3 ≡ 1 (mod n)
59 func TestGLVLambdaCubeRoot(t *testing.T) {
60 // Compute λ^2
61 var lambda2 Scalar
62 lambda2.mul(&scalarLambda, &scalarLambda)
63
64 // Compute λ^3 = λ^2 * λ
65 var lambda3 Scalar
66 lambda3.mul(&lambda2, &scalarLambda)
67
68 // Check if λ^3 ≡ 1 (mod n)
69 if !lambda3.equal(&ScalarOne) {
70 var bytes [32]byte
71 lambda3.getB32(bytes[:])
72 t.Errorf("λ^3 ≠ 1 (mod n), got: %s", hex.EncodeToString(bytes[:]))
73 }
74 }
75
76 // TestGLVLambdaProperty verifies that λ^2 + λ + 1 ≡ 0 (mod n)
77 func TestGLVLambdaProperty(t *testing.T) {
78 // Compute λ^2
79 var lambda2 Scalar
80 lambda2.mul(&scalarLambda, &scalarLambda)
81
82 // Compute λ^2 + λ
83 var sum Scalar
84 sum.add(&lambda2, &scalarLambda)
85
86 // Compute λ^2 + λ + 1
87 sum.add(&sum, &ScalarOne)
88
89 // Check if λ^2 + λ + 1 ≡ 0 (mod n)
90 if !sum.isZero() {
91 var bytes [32]byte
92 sum.getB32(bytes[:])
93 t.Errorf("λ^2 + λ + 1 ≠ 0 (mod n), got: %s", hex.EncodeToString(bytes[:]))
94 }
95 }
96
97 // TestGLVBetaConstant verifies that the Beta field constant is correctly defined
98 func TestGLVBetaConstant(t *testing.T) {
99 // Expected: 0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee
100 var betaBytes [32]byte
101 fieldBeta.getB32(betaBytes[:])
102 expectedBeta := "7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"
103 if hex.EncodeToString(betaBytes[:]) != expectedBeta {
104 t.Errorf("fieldBeta mismatch:\n got: %s\n want: %s", hex.EncodeToString(betaBytes[:]), expectedBeta)
105 }
106 }
107
108 // TestGLVBetaCubeRoot verifies that β^3 ≡ 1 (mod p)
109 func TestGLVBetaCubeRoot(t *testing.T) {
110 // Compute β^2
111 var beta2 FieldElement
112 beta2.sqr(&fieldBeta)
113 beta2.normalize()
114
115 // Compute β^3 = β^2 * β
116 var beta3 FieldElement
117 beta3.mul(&beta2, &fieldBeta)
118 beta3.normalize()
119
120 // Check if β^3 ≡ 1 (mod p)
121 if !beta3.equal(&FieldElementOne) {
122 var bytes [32]byte
123 beta3.getB32(bytes[:])
124 t.Errorf("β^3 ≠ 1 (mod p), got: %s", hex.EncodeToString(bytes[:]))
125 }
126 }
127
128 // TestGLVBetaProperty verifies that β^2 + β + 1 ≡ 0 (mod p)
129 func TestGLVBetaProperty(t *testing.T) {
130 // Compute β^2
131 var beta2 FieldElement
132 beta2.sqr(&fieldBeta)
133
134 // Compute β^2 + β
135 var sum FieldElement
136 sum = beta2
137 sum.add(&fieldBeta)
138
139 // Compute β^2 + β + 1
140 sum.add(&FieldElementOne)
141 sum.normalize()
142
143 // Check if β^2 + β + 1 ≡ 0 (mod p)
144 if !sum.normalizesToZeroVar() {
145 var bytes [32]byte
146 sum.getB32(bytes[:])
147 t.Errorf("β^2 + β + 1 ≠ 0 (mod p), got: %s", hex.EncodeToString(bytes[:]))
148 }
149 }
150
151 // TestGLVEndomorphismOnGenerator verifies that λ·G = (β·Gx, Gy)
152 // This confirms that the endomorphism relationship holds
153 func TestGLVEndomorphismOnGenerator(t *testing.T) {
154 // Compute λ·G using scalar multiplication
155 var lambdaG GroupElementJacobian
156 EcmultConst(&lambdaG, &Generator, &scalarLambda)
157
158 // Convert to affine
159 var lambdaGAff GroupElementAffine
160 lambdaGAff.setGEJ(&lambdaG)
161 lambdaGAff.x.normalize()
162 lambdaGAff.y.normalize()
163
164 // Compute β·Gx
165 var betaGx FieldElement
166 betaGx.mul(&Generator.x, &fieldBeta)
167 betaGx.normalize()
168
169 // Check X coordinate: λ·G.x should equal β·G.x
170 if !lambdaGAff.x.equal(&betaGx) {
171 var got, want [32]byte
172 lambdaGAff.x.getB32(got[:])
173 betaGx.getB32(want[:])
174 t.Errorf("λ·G.x ≠ β·G.x:\n got: %s\n want: %s", hex.EncodeToString(got[:]), hex.EncodeToString(want[:]))
175 }
176
177 // Check Y coordinate: λ·G.y should equal G.y
178 genY := Generator.y
179 genY.normalize()
180 if !lambdaGAff.y.equal(&genY) {
181 var got, want [32]byte
182 lambdaGAff.y.getB32(got[:])
183 genY.getB32(want[:])
184 t.Errorf("λ·G.y ≠ G.y:\n got: %s\n want: %s", hex.EncodeToString(got[:]), hex.EncodeToString(want[:]))
185 }
186 }
187
188 // =============================================================================
189 // Phase 2 Tests: Scalar Splitting
190 // =============================================================================
191
192 // TestScalarSplitLambdaProperty verifies that k1 + k2*λ ≡ k (mod n)
193 func TestScalarSplitLambdaProperty(t *testing.T) {
194 testCases := []struct {
195 name string
196 kHex string
197 }{
198 {"one", "0000000000000000000000000000000000000000000000000000000000000001"},
199 {"small", "0000000000000000000000000000000000000000000000000000000000000100"},
200 {"medium", "0000000000000000000000000000000100000000000000000000000000000000"},
201 {"large", "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f"}, // n-2
202 {"random1", "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"},
203 {"random2", "0000000000000000000000000000000014551231950b75fc4402da1732fc9bebe"},
204 }
205
206 for _, tc := range testCases {
207 t.Run(tc.name, func(t *testing.T) {
208 // Parse k
209 kBytes, _ := hex.DecodeString(tc.kHex)
210 var k Scalar
211 k.setB32(kBytes)
212
213 // Split k into k1, k2
214 var k1, k2 Scalar
215 scalarSplitLambda(&k1, &k2, &k)
216
217 // Verify: k1 + k2*λ ≡ k (mod n)
218 var k2Lambda, sum Scalar
219 k2Lambda.mul(&k2, &scalarLambda)
220 sum.add(&k1, &k2Lambda)
221
222 if !sum.equal(&k) {
223 var sumBytes, kBytes [32]byte
224 sum.getB32(sumBytes[:])
225 k.getB32(kBytes[:])
226 t.Errorf("k1 + k2*λ ≠ k:\n sum: %s\n k: %s",
227 hex.EncodeToString(sumBytes[:]), hex.EncodeToString(kBytes[:]))
228 }
229 })
230 }
231 }
232
233 // TestScalarSplitLambdaBounds verifies that k1 and k2 are bounded (< 2^128)
234 func TestScalarSplitLambdaBounds(t *testing.T) {
235 // Test with various random-ish scalars
236 testScalars := []string{
237 "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
238 "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", // n-1
239 "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", // n/2
240 "0000000000000000000000000000000100000000000000000000000000000000",
241 "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
242 }
243
244 // Bounds from libsecp256k1: k1 < 2^128 or -k1 < 2^128, same for k2
245 // Actually the bounds are tighter: (a1 + a2 + 1)/2 for k1, (-b1 + b2)/2 + 1 for k2
246 // But we'll just check they fit in ~128 bits (upper limbs are small)
247
248 for _, hexStr := range testScalars {
249 kBytes, _ := hex.DecodeString(hexStr)
250 var k Scalar
251 k.setB32(kBytes)
252
253 var k1, k2 Scalar
254 scalarSplitLambda(&k1, &k2, &k)
255
256 // Check that k1 is small (upper two limbs should be 0 or the scalar is negated)
257 // If k1 is "high", then -k1 should be small
258 k1Small := (k1.d[2] == 0 && k1.d[3] == 0)
259 var negK1 Scalar
260 negK1.negate(&k1)
261 negK1Small := (negK1.d[2] == 0 && negK1.d[3] == 0)
262
263 if !k1Small && !negK1Small {
264 t.Errorf("k1 not bounded for k=%s: k1.d[2]=%x, k1.d[3]=%x", hexStr, k1.d[2], k1.d[3])
265 }
266
267 // Same for k2
268 k2Small := (k2.d[2] == 0 && k2.d[3] == 0)
269 var negK2 Scalar
270 negK2.negate(&k2)
271 negK2Small := (negK2.d[2] == 0 && negK2.d[3] == 0)
272
273 if !k2Small && !negK2Small {
274 t.Errorf("k2 not bounded for k=%s: k2.d[2]=%x, k2.d[3]=%x", hexStr, k2.d[2], k2.d[3])
275 }
276 }
277 }
278
279 // TestScalarSplitLambdaRandom tests splitLambda with random scalars
280 func TestScalarSplitLambdaRandom(t *testing.T) {
281 // Use deterministic "random" values based on hashing
282 for i := 0; i < 100; i++ {
283 // Create a pseudo-random scalar
284 sha := NewSHA256()
285 sha.Write([]byte{byte(i), byte(i >> 8)})
286 var kBytes [32]byte
287 sha.Finalize(kBytes[:])
288
289 var k Scalar
290 k.setB32(kBytes[:])
291
292 // Split
293 var k1, k2 Scalar
294 scalarSplitLambda(&k1, &k2, &k)
295
296 // Verify property
297 var k2Lambda, sum Scalar
298 k2Lambda.mul(&k2, &scalarLambda)
299 sum.add(&k1, &k2Lambda)
300
301 if !sum.equal(&k) {
302 t.Errorf("Iteration %d: k1 + k2*λ ≠ k", i)
303 }
304 }
305 }
306
307 // TestScalarSplit128 tests the 128-bit split function
308 func TestScalarSplit128(t *testing.T) {
309 // Test scalar: 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20
310 kBytes, _ := hex.DecodeString("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20")
311 var k Scalar
312 k.setB32(kBytes)
313
314 var k1, k2 Scalar
315 scalarSplit128(&k1, &k2, &k)
316
317 // k1 should be low 128 bits
318 // k2 should be high 128 bits
319 // In limb order: k.d[0], k.d[1] are low, k.d[2], k.d[3] are high
320 if k1.d[0] != k.d[0] || k1.d[1] != k.d[1] || k1.d[2] != 0 || k1.d[3] != 0 {
321 t.Errorf("k1 incorrect: got d[0:4]=%x,%x,%x,%x", k1.d[0], k1.d[1], k1.d[2], k1.d[3])
322 }
323 if k2.d[0] != k.d[2] || k2.d[1] != k.d[3] || k2.d[2] != 0 || k2.d[3] != 0 {
324 t.Errorf("k2 incorrect: got d[0:4]=%x,%x,%x,%x", k2.d[0], k2.d[1], k2.d[2], k2.d[3])
325 }
326 }
327
328 // TestMulShiftVar tests the mul_shift_var function
329 func TestMulShiftVar(t *testing.T) {
330 // Test case: multiply two known values and shift by 384
331 // This is the exact operation used in splitLambda
332
333 // Simple test: 1 * g1 >> 384 should give a small result
334 var result Scalar
335 result.mulShiftVar(&ScalarOne, &scalarG1, 384)
336
337 // The result should be 0 since 1 * g1 < 2^384
338 // (g1 is a 256-bit value, so 1 * g1 = g1 which is < 2^256 < 2^384)
339 if result.d[0] != 0 || result.d[1] != 0 || result.d[2] != 0 || result.d[3] != 0 {
340 t.Errorf("1 * g1 >> 384 should be 0, got: %x %x %x %x",
341 result.d[0], result.d[1], result.d[2], result.d[3])
342 }
343
344 // Test with a larger value that will produce non-zero result
345 // Use a value close to n to get a meaningful result
346 nMinus1Bytes, _ := hex.DecodeString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140")
347 var nMinus1 Scalar
348 nMinus1.setB32(nMinus1Bytes)
349
350 result.mulShiftVar(&nMinus1, &scalarG1, 384)
351
352 // Result should be non-zero and fit in ~128 bits
353 // (since g1 is ~256 bits, (n-1)*g1 is ~512 bits, >> 384 gives ~128 bits)
354 if result.d[2] != 0 || result.d[3] != 0 {
355 t.Logf("Warning: result has high limbs set, may indicate issue")
356 }
357 t.Logf("(n-1) * g1 >> 384 = %x %x %x %x", result.d[3], result.d[2], result.d[1], result.d[0])
358 }
359
360 // TestIsHigh tests the isHigh function
361 func TestIsHigh(t *testing.T) {
362 testCases := []struct {
363 name string
364 hexValue string
365 expected bool
366 }{
367 {"zero", "0000000000000000000000000000000000000000000000000000000000000000", false},
368 {"one", "0000000000000000000000000000000000000000000000000000000000000001", false},
369 {"n/2", "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", false},
370 {"n/2+1", "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1", true},
371 {"n-1", "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", true},
372 {"n-2", "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f", true},
373 }
374
375 for _, tc := range testCases {
376 t.Run(tc.name, func(t *testing.T) {
377 bytes, _ := hex.DecodeString(tc.hexValue)
378 var s Scalar
379 s.setB32(bytes)
380
381 result := s.isHigh()
382 if result != tc.expected {
383 t.Errorf("isHigh(%s) = %v, want %v", tc.name, result, tc.expected)
384 }
385 })
386 }
387 }
388
389 // BenchmarkScalarSplitLambda benchmarks the scalar splitting operation
390 func BenchmarkScalarSplitLambda(b *testing.B) {
391 // Use a representative scalar
392 kBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
393 var k Scalar
394 k.setB32(kBytes)
395
396 var k1, k2 Scalar
397
398 b.ResetTimer()
399 for i := 0; i < b.N; i++ {
400 scalarSplitLambda(&k1, &k2, &k)
401 }
402 }
403
404 // BenchmarkMulShiftVar benchmarks the mulShiftVar operation
405 func BenchmarkMulShiftVar(b *testing.B) {
406 kBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
407 var k Scalar
408 k.setB32(kBytes)
409
410 var result Scalar
411
412 b.ResetTimer()
413 for i := 0; i < b.N; i++ {
414 result.mulShiftVar(&k, &scalarG1, 384)
415 }
416 }
417
418 // =============================================================================
419 // Phase 3 Tests: Point Operations with Endomorphism
420 // =============================================================================
421
422 // TestMulLambdaAffine tests the affine mulLambda operation
423 func TestMulLambdaAffine(t *testing.T) {
424 // Test that mulLambda(G) produces the same result as λ*G via scalar multiplication
425 var lambdaG GroupElementJacobian
426 EcmultConst(&lambdaG, &Generator, &scalarLambda)
427
428 var lambdaGAff GroupElementAffine
429 lambdaGAff.setGEJ(&lambdaG)
430 lambdaGAff.x.normalize()
431 lambdaGAff.y.normalize()
432
433 // Now test mulLambda
434 var mulLambdaG GroupElementAffine
435 mulLambdaG.mulLambda(&Generator)
436 mulLambdaG.x.normalize()
437 mulLambdaG.y.normalize()
438
439 // They should be equal
440 if !lambdaGAff.x.equal(&mulLambdaG.x) {
441 var got, want [32]byte
442 mulLambdaG.x.getB32(got[:])
443 lambdaGAff.x.getB32(want[:])
444 t.Errorf("mulLambda(G).x ≠ λ*G.x:\n got: %s\n want: %s",
445 hex.EncodeToString(got[:]), hex.EncodeToString(want[:]))
446 }
447
448 if !lambdaGAff.y.equal(&mulLambdaG.y) {
449 var got, want [32]byte
450 mulLambdaG.y.getB32(got[:])
451 lambdaGAff.y.getB32(want[:])
452 t.Errorf("mulLambda(G).y ≠ λ*G.y:\n got: %s\n want: %s",
453 hex.EncodeToString(got[:]), hex.EncodeToString(want[:]))
454 }
455 }
456
457 // TestMulLambdaJacobian tests the Jacobian mulLambda operation
458 func TestMulLambdaJacobian(t *testing.T) {
459 // Convert generator to Jacobian
460 var gJac GroupElementJacobian
461 gJac.setGE(&Generator)
462
463 // Apply mulLambda
464 var mulLambdaG GroupElementJacobian
465 mulLambdaG.mulLambda(&gJac)
466
467 // Convert back to affine for comparison
468 var mulLambdaGAff GroupElementAffine
469 mulLambdaGAff.setGEJ(&mulLambdaG)
470 mulLambdaGAff.x.normalize()
471 mulLambdaGAff.y.normalize()
472
473 // Compute expected via scalar multiplication
474 var lambdaG GroupElementJacobian
475 EcmultConst(&lambdaG, &Generator, &scalarLambda)
476 var lambdaGAff GroupElementAffine
477 lambdaGAff.setGEJ(&lambdaG)
478 lambdaGAff.x.normalize()
479 lambdaGAff.y.normalize()
480
481 // They should be equal
482 if !lambdaGAff.equal(&mulLambdaGAff) {
483 t.Errorf("Jacobian mulLambda(G) ≠ λ*G")
484 }
485 }
486
487 // TestMulLambdaInfinity tests that mulLambda handles infinity correctly
488 func TestMulLambdaInfinity(t *testing.T) {
489 var inf GroupElementAffine
490 inf.setInfinity()
491
492 var result GroupElementAffine
493 result.mulLambda(&inf)
494
495 if !result.isInfinity() {
496 t.Errorf("mulLambda(infinity) should be infinity")
497 }
498
499 // Jacobian version
500 var infJac GroupElementJacobian
501 infJac.setInfinity()
502
503 var resultJac GroupElementJacobian
504 resultJac.mulLambda(&infJac)
505
506 if !resultJac.isInfinity() {
507 t.Errorf("Jacobian mulLambda(infinity) should be infinity")
508 }
509 }
510
511 // TestMulLambdaCubed tests that λ^3 * P = P (applying mulLambda 3 times returns to original)
512 func TestMulLambdaCubed(t *testing.T) {
513 // Start with generator
514 p := Generator
515
516 // Apply mulLambda three times
517 var p1, p2, p3 GroupElementAffine
518 p1.mulLambda(&p)
519 p2.mulLambda(&p1)
520 p3.mulLambda(&p2)
521
522 // Normalize for comparison
523 p3.x.normalize()
524 p3.y.normalize()
525
526 genNorm := Generator
527 genNorm.x.normalize()
528 genNorm.y.normalize()
529
530 // p3 should equal the original generator
531 if !p3.equal(&genNorm) {
532 var got, want [32]byte
533 p3.x.getB32(got[:])
534 genNorm.x.getB32(want[:])
535 t.Errorf("λ^3 * G ≠ G:\n got x: %s\n want x: %s",
536 hex.EncodeToString(got[:]), hex.EncodeToString(want[:]))
537 }
538 }
539
540 // TestEcmultEndoSplit tests the combined endomorphism split operation
541 func TestEcmultEndoSplit(t *testing.T) {
542 testCases := []string{
543 "0000000000000000000000000000000000000000000000000000000000000001",
544 "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
545 "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", // n-1
546 "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", // n/2
547 }
548
549 for _, hexStr := range testCases {
550 t.Run(hexStr[:16]+"...", func(t *testing.T) {
551 // Parse scalar
552 sBytes, _ := hex.DecodeString(hexStr)
553 var s Scalar
554 s.setB32(sBytes)
555
556 // Use generator as the point
557 p := Generator
558
559 // Split
560 var s1, s2 Scalar
561 var p1, p2 GroupElementAffine
562 ecmultEndoSplit(&s1, &s2, &p1, &p2, &s, &p)
563
564 // Verify s1 and s2 are not high (after negation adjustment)
565 if s1.isHigh() {
566 t.Errorf("s1 should not be high after split")
567 }
568 if s2.isHigh() {
569 t.Errorf("s2 should not be high after split")
570 }
571
572 // Verify: s1*p1 + s2*p2 = s*p
573 var s1p1, s2p2, sum, expected GroupElementJacobian
574
575 EcmultConst(&s1p1, &p1, &s1)
576 EcmultConst(&s2p2, &p2, &s2)
577 sum.addVar(&s1p1, &s2p2)
578
579 EcmultConst(&expected, &p, &s)
580
581 // Convert to affine for comparison
582 var sumAff, expectedAff GroupElementAffine
583 sumAff.setGEJ(&sum)
584 expectedAff.setGEJ(&expected)
585 sumAff.x.normalize()
586 sumAff.y.normalize()
587 expectedAff.x.normalize()
588 expectedAff.y.normalize()
589
590 if !sumAff.equal(&expectedAff) {
591 t.Errorf("s1*p1 + s2*p2 ≠ s*p")
592 }
593 })
594 }
595 }
596
597 // TestEcmultEndoSplitRandom tests endomorphism split with random scalars
598 func TestEcmultEndoSplitRandom(t *testing.T) {
599 for i := 0; i < 20; i++ {
600 // Generate pseudo-random scalar
601 sha := NewSHA256()
602 sha.Write([]byte{byte(i), byte(i >> 8), 0xAB, 0xCD})
603 var sBytes [32]byte
604 sha.Finalize(sBytes[:])
605
606 var s Scalar
607 s.setB32(sBytes[:])
608
609 // Use generator
610 p := Generator
611
612 // Split
613 var s1, s2 Scalar
614 var p1, p2 GroupElementAffine
615 ecmultEndoSplit(&s1, &s2, &p1, &p2, &s, &p)
616
617 // Verify: s1*p1 + s2*p2 = s*p
618 var s1p1, s2p2, sum, expected GroupElementJacobian
619
620 EcmultConst(&s1p1, &p1, &s1)
621 EcmultConst(&s2p2, &p2, &s2)
622 sum.addVar(&s1p1, &s2p2)
623
624 EcmultConst(&expected, &p, &s)
625
626 var sumAff, expectedAff GroupElementAffine
627 sumAff.setGEJ(&sum)
628 expectedAff.setGEJ(&expected)
629 sumAff.x.normalize()
630 sumAff.y.normalize()
631 expectedAff.x.normalize()
632 expectedAff.y.normalize()
633
634 if !sumAff.equal(&expectedAff) {
635 t.Errorf("Iteration %d: s1*p1 + s2*p2 ≠ s*p", i)
636 }
637 }
638 }
639
640 // BenchmarkMulLambdaAffine benchmarks the affine mulLambda operation
641 func BenchmarkMulLambdaAffine(b *testing.B) {
642 p := Generator
643 var result GroupElementAffine
644
645 b.ResetTimer()
646 for i := 0; i < b.N; i++ {
647 result.mulLambda(&p)
648 }
649 }
650
651 // BenchmarkMulLambdaJacobian benchmarks the Jacobian mulLambda operation
652 func BenchmarkMulLambdaJacobian(b *testing.B) {
653 var p GroupElementJacobian
654 p.setGE(&Generator)
655 var result GroupElementJacobian
656
657 b.ResetTimer()
658 for i := 0; i < b.N; i++ {
659 result.mulLambda(&p)
660 }
661 }
662
663 // BenchmarkEcmultEndoSplit benchmarks the combined endomorphism split
664 func BenchmarkEcmultEndoSplit(b *testing.B) {
665 sBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
666 var s Scalar
667 s.setB32(sBytes)
668 p := Generator
669
670 var s1, s2 Scalar
671 var p1, p2 GroupElementAffine
672
673 b.ResetTimer()
674 for i := 0; i < b.N; i++ {
675 ecmultEndoSplit(&s1, &s2, &p1, &p2, &s, &p)
676 }
677 }
678
679 // =============================================================================
680 // Phase 4 Tests: Strauss-GLV Algorithm
681 // =============================================================================
682
683 // TestBuildOddMultiplesTableAffine tests the odd multiples table building
684 func TestBuildOddMultiplesTableAffine(t *testing.T) {
685 // Build table from generator
686 var gJac GroupElementJacobian
687 gJac.setGE(&Generator)
688
689 const tableSize = 16 // Window size 5
690 preA := make([]GroupElementAffine, tableSize)
691 preBetaX := make([]FieldElement, tableSize)
692
693 buildOddMultiplesTableAffine(preA, preBetaX, &gJac, tableSize)
694
695 // Verify: preA[i] should equal (2*i+1)*G
696 for i := 0; i < tableSize; i++ {
697 var scalar Scalar
698 scalar.setInt(uint(2*i + 1))
699
700 var expected GroupElementJacobian
701 EcmultConst(&expected, &Generator, &scalar)
702
703 var expectedAff GroupElementAffine
704 expectedAff.setGEJ(&expected)
705 expectedAff.x.normalize()
706 expectedAff.y.normalize()
707
708 preA[i].x.normalize()
709 preA[i].y.normalize()
710
711 if !preA[i].equal(&expectedAff) {
712 t.Errorf("preA[%d] ≠ %d*G", i, 2*i+1)
713 }
714
715 // Verify β*x is correct
716 var expectedBetaX FieldElement
717 expectedBetaX.mul(&expectedAff.x, &fieldBeta)
718 expectedBetaX.normalize()
719 preBetaX[i].normalize()
720
721 if !preBetaX[i].equal(&expectedBetaX) {
722 t.Errorf("preBetaX[%d] ≠ β * preA[%d].x", i, i)
723 }
724 }
725 }
726
727 // TestTableGetGE tests the table lookup function
728 func TestTableGetGE(t *testing.T) {
729 // Build table from generator
730 var gJac GroupElementJacobian
731 gJac.setGE(&Generator)
732
733 const tableSize = 16
734 preA := make([]GroupElementAffine, tableSize)
735 preBetaX := make([]FieldElement, tableSize)
736
737 buildOddMultiplesTableAffine(preA, preBetaX, &gJac, tableSize)
738
739 testCases := []struct {
740 name string
741 n int
742 expected int // expected multiple (negative means negated)
743 }{
744 {"n=1", 1, 1},
745 {"n=3", 3, 3},
746 {"n=5", 5, 5},
747 {"n=15", 15, 15},
748 {"n=-1", -1, -1},
749 {"n=-3", -3, -3},
750 {"n=-15", -15, -15},
751 }
752
753 for _, tc := range testCases {
754 t.Run(tc.name, func(t *testing.T) {
755 var pt GroupElementAffine
756 tableGetGE(&pt, preA, tc.n)
757
758 // Compute expected point
759 absN := tc.expected
760 if absN < 0 {
761 absN = -absN
762 }
763 var scalar Scalar
764 scalar.setInt(uint(absN))
765
766 var expectedJac GroupElementJacobian
767 EcmultConst(&expectedJac, &Generator, &scalar)
768
769 var expectedAff GroupElementAffine
770 expectedAff.setGEJ(&expectedJac)
771
772 // Negate if needed
773 if tc.expected < 0 {
774 expectedAff.negate(&expectedAff)
775 }
776
777 expectedAff.x.normalize()
778 expectedAff.y.normalize()
779 pt.x.normalize()
780 pt.y.normalize()
781
782 if !pt.equal(&expectedAff) {
783 t.Errorf("tableGetGE(%d) returned wrong point", tc.n)
784 }
785 })
786 }
787
788 // Test n=0 returns infinity
789 var pt GroupElementAffine
790 tableGetGE(&pt, preA, 0)
791 if !pt.isInfinity() {
792 t.Error("tableGetGE(0) should return infinity")
793 }
794 }
795
796 // TestTableGetGELambda tests the lambda-transformed table lookup
797 func TestTableGetGELambda(t *testing.T) {
798 // Build table from generator
799 var gJac GroupElementJacobian
800 gJac.setGE(&Generator)
801
802 const tableSize = 16
803 preA := make([]GroupElementAffine, tableSize)
804 preBetaX := make([]FieldElement, tableSize)
805
806 buildOddMultiplesTableAffine(preA, preBetaX, &gJac, tableSize)
807
808 // Test that tableGetGELambda(n) returns λ * tableGetGE(n)
809 for n := 1; n <= 15; n += 2 {
810 var ptLambda GroupElementAffine
811 tableGetGELambda(&ptLambda, preA, preBetaX, n)
812
813 // Get regular point and apply lambda
814 var pt GroupElementAffine
815 tableGetGE(&pt, preA, n)
816 var expected GroupElementAffine
817 expected.mulLambda(&pt)
818
819 ptLambda.x.normalize()
820 ptLambda.y.normalize()
821 expected.x.normalize()
822 expected.y.normalize()
823
824 if !ptLambda.equal(&expected) {
825 t.Errorf("tableGetGELambda(%d) ≠ λ * tableGetGE(%d)", n, n)
826 }
827
828 // Test negative n
829 tableGetGELambda(&ptLambda, preA, preBetaX, -n)
830 tableGetGE(&pt, preA, -n)
831 expected.mulLambda(&pt)
832
833 ptLambda.x.normalize()
834 ptLambda.y.normalize()
835 expected.x.normalize()
836 expected.y.normalize()
837
838 if !ptLambda.equal(&expected) {
839 t.Errorf("tableGetGELambda(%d) ≠ λ * tableGetGE(%d)", -n, -n)
840 }
841 }
842 }
843
844 // TestEcmultStraussWNAFGLV tests the full Strauss-GLV algorithm
845 func TestEcmultStraussWNAFGLV(t *testing.T) {
846 testCases := []string{
847 "0000000000000000000000000000000000000000000000000000000000000001", // 1
848 "0000000000000000000000000000000000000000000000000000000000000002", // 2
849 "0000000000000000000000000000000000000000000000000000000000000010", // 16
850 "00000000000000000000000000000000000000000000000000000000000000ff", // 255
851 "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", // random
852 "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", // n-1
853 "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", // n/2
854 }
855
856 for _, hexStr := range testCases {
857 t.Run(hexStr[:8]+"...", func(t *testing.T) {
858 // Parse scalar
859 sBytes, _ := hex.DecodeString(hexStr)
860 var s Scalar
861 s.setB32(sBytes)
862
863 // Compute using Strauss-GLV
864 var resultGLV GroupElementJacobian
865 ecmultStraussWNAFGLV(&resultGLV, &Generator, &s)
866
867 // Compute using reference implementation
868 var resultRef GroupElementJacobian
869 EcmultConst(&resultRef, &Generator, &s)
870
871 // Convert to affine for comparison
872 var resultGLVAff, resultRefAff GroupElementAffine
873 resultGLVAff.setGEJ(&resultGLV)
874 resultRefAff.setGEJ(&resultRef)
875 resultGLVAff.x.normalize()
876 resultGLVAff.y.normalize()
877 resultRefAff.x.normalize()
878 resultRefAff.y.normalize()
879
880 if !resultGLVAff.equal(&resultRefAff) {
881 var gotX, wantX [32]byte
882 resultGLVAff.x.getB32(gotX[:])
883 resultRefAff.x.getB32(wantX[:])
884 t.Errorf("GLV result mismatch:\n got x: %s\n want x: %s",
885 hex.EncodeToString(gotX[:]), hex.EncodeToString(wantX[:]))
886 }
887 })
888 }
889 }
890
891 // TestEcmultStraussWNAFGLVRandom tests the Strauss-GLV algorithm with random scalars
892 func TestEcmultStraussWNAFGLVRandom(t *testing.T) {
893 for i := 0; i < 50; i++ {
894 // Generate pseudo-random scalar
895 sha := NewSHA256()
896 sha.Write([]byte{byte(i), byte(i >> 8), 0xDE, 0xAD})
897 var sBytes [32]byte
898 sha.Finalize(sBytes[:])
899
900 var s Scalar
901 s.setB32(sBytes[:])
902
903 // Compute using Strauss-GLV
904 var resultGLV GroupElementJacobian
905 ecmultStraussWNAFGLV(&resultGLV, &Generator, &s)
906
907 // Compute using reference implementation
908 var resultRef GroupElementJacobian
909 EcmultConst(&resultRef, &Generator, &s)
910
911 // Convert to affine for comparison
912 var resultGLVAff, resultRefAff GroupElementAffine
913 resultGLVAff.setGEJ(&resultGLV)
914 resultRefAff.setGEJ(&resultRef)
915 resultGLVAff.x.normalize()
916 resultGLVAff.y.normalize()
917 resultRefAff.x.normalize()
918 resultRefAff.y.normalize()
919
920 if !resultGLVAff.equal(&resultRefAff) {
921 t.Errorf("Iteration %d: GLV result mismatch", i)
922 }
923 }
924 }
925
926 // TestEcmultStraussWNAFGLVNonGenerator tests with a non-generator point
927 func TestEcmultStraussWNAFGLVNonGenerator(t *testing.T) {
928 // Create a non-generator point: 2*G
929 var two Scalar
930 two.setInt(2)
931 var twoGJac GroupElementJacobian
932 EcmultConst(&twoGJac, &Generator, &two)
933 var twoG GroupElementAffine
934 twoG.setGEJ(&twoGJac)
935
936 testCases := []string{
937 "0000000000000000000000000000000000000000000000000000000000000001",
938 "0000000000000000000000000000000000000000000000000000000000000003",
939 "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
940 }
941
942 for _, hexStr := range testCases {
943 t.Run(hexStr[:8]+"...", func(t *testing.T) {
944 sBytes, _ := hex.DecodeString(hexStr)
945 var s Scalar
946 s.setB32(sBytes)
947
948 // Compute using Strauss-GLV
949 var resultGLV GroupElementJacobian
950 ecmultStraussWNAFGLV(&resultGLV, &twoG, &s)
951
952 // Compute using reference implementation
953 var resultRef GroupElementJacobian
954 EcmultConst(&resultRef, &twoG, &s)
955
956 // Convert to affine for comparison
957 var resultGLVAff, resultRefAff GroupElementAffine
958 resultGLVAff.setGEJ(&resultGLV)
959 resultRefAff.setGEJ(&resultRef)
960 resultGLVAff.x.normalize()
961 resultGLVAff.y.normalize()
962 resultRefAff.x.normalize()
963 resultRefAff.y.normalize()
964
965 if !resultGLVAff.equal(&resultRefAff) {
966 t.Errorf("GLV result mismatch for 2*G")
967 }
968 })
969 }
970 }
971
972 // TestEcmultStraussWNAFGLVEdgeCases tests edge cases
973 func TestEcmultStraussWNAFGLVEdgeCases(t *testing.T) {
974 // Test with zero scalar
975 var zero Scalar
976 var result GroupElementJacobian
977 ecmultStraussWNAFGLV(&result, &Generator, &zero)
978 if !result.isInfinity() {
979 t.Error("0 * G should be infinity")
980 }
981
982 // Test with infinity point
983 var inf GroupElementAffine
984 inf.setInfinity()
985 var one Scalar
986 one.setInt(1)
987 ecmultStraussWNAFGLV(&result, &inf, &one)
988 if !result.isInfinity() {
989 t.Error("1 * infinity should be infinity")
990 }
991 }
992
993 // BenchmarkEcmultStraussWNAFGLV benchmarks the Strauss-GLV algorithm
994 func BenchmarkEcmultStraussWNAFGLV(b *testing.B) {
995 sBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
996 var s Scalar
997 s.setB32(sBytes)
998
999 var result GroupElementJacobian
1000
1001 b.ResetTimer()
1002 for i := 0; i < b.N; i++ {
1003 ecmultStraussWNAFGLV(&result, &Generator, &s)
1004 }
1005 }
1006
1007 // BenchmarkEcmultConst benchmarks the reference constant-time implementation
1008 func BenchmarkEcmultConst(b *testing.B) {
1009 sBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
1010 var s Scalar
1011 s.setB32(sBytes)
1012
1013 var result GroupElementJacobian
1014
1015 b.ResetTimer()
1016 for i := 0; i < b.N; i++ {
1017 EcmultConst(&result, &Generator, &s)
1018 }
1019 }
1020
1021 // BenchmarkEcmultWindowedVar benchmarks the windowed variable-time implementation
1022 func BenchmarkEcmultWindowedVar(b *testing.B) {
1023 sBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
1024 var s Scalar
1025 s.setB32(sBytes)
1026
1027 var result GroupElementJacobian
1028
1029 b.ResetTimer()
1030 for i := 0; i < b.N; i++ {
1031 ecmultWindowedVar(&result, &Generator, &s)
1032 }
1033 }
1034
1035 // BenchmarkBuildOddMultiplesTableAffine benchmarks table building
1036 func BenchmarkBuildOddMultiplesTableAffine(b *testing.B) {
1037 var gJac GroupElementJacobian
1038 gJac.setGE(&Generator)
1039
1040 const tableSize = 16
1041 preA := make([]GroupElementAffine, tableSize)
1042 preBetaX := make([]FieldElement, tableSize)
1043
1044 b.ResetTimer()
1045 for i := 0; i < b.N; i++ {
1046 buildOddMultiplesTableAffine(preA, preBetaX, &gJac, tableSize)
1047 }
1048 }
1049
1050 // =============================================================================
1051 // Phase 5 Tests: Generator Precomputation
1052 // =============================================================================
1053
1054 // TestGenTablesInitialization tests that the generator tables are properly initialized
1055 func TestGenTablesInitialization(t *testing.T) {
1056 // Force initialization
1057 EnsureGenTablesInitialized()
1058
1059 // Verify preGenG[0] = 1*G = G
1060 preGenG[0].x.normalize()
1061 preGenG[0].y.normalize()
1062 genNorm := Generator
1063 genNorm.x.normalize()
1064 genNorm.y.normalize()
1065
1066 if !preGenG[0].equal(&genNorm) {
1067 t.Error("preGenG[0] should equal G")
1068 }
1069
1070 // Verify preGenG[i] = (2*i+1)*G
1071 for i := 0; i < 8; i++ { // Check first 8 entries
1072 var scalar Scalar
1073 scalar.setInt(uint(2*i + 1))
1074
1075 var expected GroupElementJacobian
1076 EcmultConst(&expected, &Generator, &scalar)
1077
1078 var expectedAff GroupElementAffine
1079 expectedAff.setGEJ(&expected)
1080 expectedAff.x.normalize()
1081 expectedAff.y.normalize()
1082
1083 preGenG[i].x.normalize()
1084 preGenG[i].y.normalize()
1085
1086 if !preGenG[i].equal(&expectedAff) {
1087 t.Errorf("preGenG[%d] ≠ %d*G", i, 2*i+1)
1088 }
1089 }
1090
1091 // Verify preGenLambdaG[0] = λ*G
1092 var lambdaG GroupElementAffine
1093 lambdaG.mulLambda(&Generator)
1094 lambdaG.x.normalize()
1095 lambdaG.y.normalize()
1096
1097 preGenLambdaG[0].x.normalize()
1098 preGenLambdaG[0].y.normalize()
1099
1100 if !preGenLambdaG[0].equal(&lambdaG) {
1101 t.Error("preGenLambdaG[0] should equal λ*G")
1102 }
1103
1104 // Verify preGenLambdaG[i] = (2*i+1)*(λ*G)
1105 for i := 0; i < 8; i++ {
1106 var scalar Scalar
1107 scalar.setInt(uint(2*i + 1))
1108
1109 var expected GroupElementJacobian
1110 EcmultConst(&expected, &lambdaG, &scalar)
1111
1112 var expectedAff GroupElementAffine
1113 expectedAff.setGEJ(&expected)
1114 expectedAff.x.normalize()
1115 expectedAff.y.normalize()
1116
1117 preGenLambdaG[i].x.normalize()
1118 preGenLambdaG[i].y.normalize()
1119
1120 if !preGenLambdaG[i].equal(&expectedAff) {
1121 t.Errorf("preGenLambdaG[%d] ≠ %d*(λ*G)", i, 2*i+1)
1122 }
1123 }
1124 }
1125
1126 // TestEcmultGenGLV tests the GLV generator multiplication
1127 func TestEcmultGenGLV(t *testing.T) {
1128 testCases := []string{
1129 "0000000000000000000000000000000000000000000000000000000000000001", // 1
1130 "0000000000000000000000000000000000000000000000000000000000000002", // 2
1131 "0000000000000000000000000000000000000000000000000000000000000010", // 16
1132 "00000000000000000000000000000000000000000000000000000000000000ff", // 255
1133 "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", // random
1134 "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", // n-1
1135 "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", // n/2
1136 }
1137
1138 for _, hexStr := range testCases {
1139 t.Run(hexStr[:8]+"...", func(t *testing.T) {
1140 // Parse scalar
1141 sBytes, _ := hex.DecodeString(hexStr)
1142 var s Scalar
1143 s.setB32(sBytes)
1144
1145 // Compute using GLV generator multiplication
1146 var resultGLV GroupElementJacobian
1147 ecmultGenGLV(&resultGLV, &s)
1148
1149 // Compute using reference implementation
1150 var resultRef GroupElementJacobian
1151 EcmultConst(&resultRef, &Generator, &s)
1152
1153 // Convert to affine for comparison
1154 var resultGLVAff, resultRefAff GroupElementAffine
1155 resultGLVAff.setGEJ(&resultGLV)
1156 resultRefAff.setGEJ(&resultRef)
1157 resultGLVAff.x.normalize()
1158 resultGLVAff.y.normalize()
1159 resultRefAff.x.normalize()
1160 resultRefAff.y.normalize()
1161
1162 if !resultGLVAff.equal(&resultRefAff) {
1163 var gotX, wantX [32]byte
1164 resultGLVAff.x.getB32(gotX[:])
1165 resultRefAff.x.getB32(wantX[:])
1166 t.Errorf("GLV gen result mismatch:\n got x: %s\n want x: %s",
1167 hex.EncodeToString(gotX[:]), hex.EncodeToString(wantX[:]))
1168 }
1169 })
1170 }
1171 }
1172
1173 // TestEcmultGenGLVRandom tests GLV generator multiplication with random scalars
1174 func TestEcmultGenGLVRandom(t *testing.T) {
1175 for i := 0; i < 50; i++ {
1176 // Generate pseudo-random scalar
1177 sha := NewSHA256()
1178 sha.Write([]byte{byte(i), byte(i >> 8), 0xBE, 0xEF})
1179 var sBytes [32]byte
1180 sha.Finalize(sBytes[:])
1181
1182 var s Scalar
1183 s.setB32(sBytes[:])
1184
1185 // Compute using GLV
1186 var resultGLV GroupElementJacobian
1187 ecmultGenGLV(&resultGLV, &s)
1188
1189 // Compute using reference
1190 var resultRef GroupElementJacobian
1191 EcmultConst(&resultRef, &Generator, &s)
1192
1193 // Compare
1194 var resultGLVAff, resultRefAff GroupElementAffine
1195 resultGLVAff.setGEJ(&resultGLV)
1196 resultRefAff.setGEJ(&resultRef)
1197 resultGLVAff.x.normalize()
1198 resultGLVAff.y.normalize()
1199 resultRefAff.x.normalize()
1200 resultRefAff.y.normalize()
1201
1202 if !resultGLVAff.equal(&resultRefAff) {
1203 t.Errorf("Iteration %d: GLV gen result mismatch", i)
1204 }
1205 }
1206 }
1207
1208 // TestEcmultGenSimple tests the simple generator multiplication
1209 func TestEcmultGenSimple(t *testing.T) {
1210 testCases := []string{
1211 "0000000000000000000000000000000000000000000000000000000000000001",
1212 "00000000000000000000000000000000000000000000000000000000000000ff",
1213 "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
1214 }
1215
1216 for _, hexStr := range testCases {
1217 t.Run(hexStr[:8]+"...", func(t *testing.T) {
1218 sBytes, _ := hex.DecodeString(hexStr)
1219 var s Scalar
1220 s.setB32(sBytes)
1221
1222 // Compute using simple generator multiplication
1223 var resultSimple GroupElementJacobian
1224 ecmultGenSimple(&resultSimple, &s)
1225
1226 // Compute using reference
1227 var resultRef GroupElementJacobian
1228 EcmultConst(&resultRef, &Generator, &s)
1229
1230 // Compare
1231 var resultSimpleAff, resultRefAff GroupElementAffine
1232 resultSimpleAff.setGEJ(&resultSimple)
1233 resultRefAff.setGEJ(&resultRef)
1234 resultSimpleAff.x.normalize()
1235 resultSimpleAff.y.normalize()
1236 resultRefAff.x.normalize()
1237 resultRefAff.y.normalize()
1238
1239 if !resultSimpleAff.equal(&resultRefAff) {
1240 t.Errorf("Simple gen result mismatch")
1241 }
1242 })
1243 }
1244 }
1245
1246 // TestEcmultGenGLVEdgeCases tests edge cases
1247 func TestEcmultGenGLVEdgeCases(t *testing.T) {
1248 // Test with zero scalar
1249 var zero Scalar
1250 var result GroupElementJacobian
1251 ecmultGenGLV(&result, &zero)
1252 if !result.isInfinity() {
1253 t.Error("0 * G should be infinity")
1254 }
1255
1256 // Test with scalar 1
1257 var one Scalar
1258 one.setInt(1)
1259 ecmultGenGLV(&result, &one)
1260
1261 var resultAff GroupElementAffine
1262 resultAff.setGEJ(&result)
1263 resultAff.x.normalize()
1264 resultAff.y.normalize()
1265
1266 genNorm := Generator
1267 genNorm.x.normalize()
1268 genNorm.y.normalize()
1269
1270 if !resultAff.equal(&genNorm) {
1271 t.Error("1 * G should equal G")
1272 }
1273 }
1274
1275 // BenchmarkEcmultGenGLV benchmarks the GLV generator multiplication
1276 func BenchmarkEcmultGenGLV(b *testing.B) {
1277 sBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
1278 var s Scalar
1279 s.setB32(sBytes)
1280
1281 // Ensure tables are initialized before benchmark
1282 EnsureGenTablesInitialized()
1283
1284 var result GroupElementJacobian
1285
1286 b.ResetTimer()
1287 for i := 0; i < b.N; i++ {
1288 ecmultGenGLV(&result, &s)
1289 }
1290 }
1291
1292 // BenchmarkEcmultGenSimple benchmarks the simple generator multiplication
1293 func BenchmarkEcmultGenSimple(b *testing.B) {
1294 sBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
1295 var s Scalar
1296 s.setB32(sBytes)
1297
1298 // Ensure tables are initialized before benchmark
1299 EnsureGenTablesInitialized()
1300
1301 var result GroupElementJacobian
1302
1303 b.ResetTimer()
1304 for i := 0; i < b.N; i++ {
1305 ecmultGenSimple(&result, &s)
1306 }
1307 }
1308
1309 // BenchmarkEcmultGenConstRef benchmarks the reference constant-time implementation for G
1310 func BenchmarkEcmultGenConstRef(b *testing.B) {
1311 sBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
1312 var s Scalar
1313 s.setB32(sBytes)
1314
1315 var result GroupElementJacobian
1316
1317 b.ResetTimer()
1318 for i := 0; i < b.N; i++ {
1319 EcmultConst(&result, &Generator, &s)
1320 }
1321 }
1322
1323 // =============================================================================
1324 // Phase 6: Cross-Validation Tests
1325 // =============================================================================
1326
1327 // TestCrossValidationAllImplementations verifies that all scalar multiplication
1328 // implementations produce the same results
1329 func TestCrossValidationAllImplementations(t *testing.T) {
1330 testCases := []struct {
1331 name string
1332 scalar string
1333 }{
1334 {"one", "0000000000000000000000000000000000000000000000000000000000000001"},
1335 {"two", "0000000000000000000000000000000000000000000000000000000000000002"},
1336 {"random1", "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"},
1337 {"random2", "deadbeefcafebabe1234567890abcdef0123456789abcdef0123456789abcdef"},
1338 {"high_bits", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
1339 {"half_order", "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0"},
1340 {"small", "0000000000000000000000000000000000000000000000000000000000000010"},
1341 }
1342
1343 for _, tc := range testCases {
1344 t.Run(tc.name, func(t *testing.T) {
1345 sBytes, _ := hex.DecodeString(tc.scalar)
1346 var s Scalar
1347 s.setB32(sBytes)
1348
1349 if s.isZero() {
1350 return // Skip zero scalar
1351 }
1352
1353 // Reference: EcmultConst (constant-time binary method)
1354 var refResult GroupElementJacobian
1355 EcmultConst(&refResult, &Generator, &s)
1356 var refAff GroupElementAffine
1357 refAff.setGEJ(&refResult)
1358 refAff.x.normalize()
1359 refAff.y.normalize()
1360
1361 // Test 1: ecmultGenGLV (GLV generator multiplication)
1362 var glvResult GroupElementJacobian
1363 ecmultGenGLV(&glvResult, &s)
1364 var glvAff GroupElementAffine
1365 glvAff.setGEJ(&glvResult)
1366 glvAff.x.normalize()
1367 glvAff.y.normalize()
1368
1369 if !refAff.x.equal(&glvAff.x) || !refAff.y.equal(&glvAff.y) {
1370 t.Errorf("ecmultGenGLV mismatch for %s", tc.name)
1371 }
1372
1373 // Test 2: ecmultGenSimple (simple generator multiplication)
1374 var simpleResult GroupElementJacobian
1375 ecmultGenSimple(&simpleResult, &s)
1376 var simpleAff GroupElementAffine
1377 simpleAff.setGEJ(&simpleResult)
1378 simpleAff.x.normalize()
1379 simpleAff.y.normalize()
1380
1381 if !refAff.x.equal(&simpleAff.x) || !refAff.y.equal(&simpleAff.y) {
1382 t.Errorf("ecmultGenSimple mismatch for %s", tc.name)
1383 }
1384
1385 // Test 3: EcmultGen (public interface)
1386 var publicResult GroupElementJacobian
1387 EcmultGen(&publicResult, &s)
1388 var publicAff GroupElementAffine
1389 publicAff.setGEJ(&publicResult)
1390 publicAff.x.normalize()
1391 publicAff.y.normalize()
1392
1393 if !refAff.x.equal(&publicAff.x) || !refAff.y.equal(&publicAff.y) {
1394 t.Errorf("EcmultGen mismatch for %s", tc.name)
1395 }
1396
1397 // Test 4: ecmultStraussWNAFGLV with Generator
1398 var straussResult GroupElementJacobian
1399 ecmultStraussWNAFGLV(&straussResult, &Generator, &s)
1400 var straussAff GroupElementAffine
1401 straussAff.setGEJ(&straussResult)
1402 straussAff.x.normalize()
1403 straussAff.y.normalize()
1404
1405 if !refAff.x.equal(&straussAff.x) || !refAff.y.equal(&straussAff.y) {
1406 t.Errorf("ecmultStraussWNAFGLV mismatch for %s", tc.name)
1407 }
1408
1409 // Test 5: Ecmult (public interface with Jacobian input)
1410 var genJac GroupElementJacobian
1411 genJac.setGE(&Generator)
1412 var ecmultResult GroupElementJacobian
1413 Ecmult(&ecmultResult, &genJac, &s)
1414 var ecmultAff GroupElementAffine
1415 ecmultAff.setGEJ(&ecmultResult)
1416 ecmultAff.x.normalize()
1417 ecmultAff.y.normalize()
1418
1419 if !refAff.x.equal(&ecmultAff.x) || !refAff.y.equal(&ecmultAff.y) {
1420 t.Errorf("Ecmult mismatch for %s", tc.name)
1421 }
1422
1423 // Test 6: ecmultWindowedVar
1424 var windowedResult GroupElementJacobian
1425 ecmultWindowedVar(&windowedResult, &Generator, &s)
1426 var windowedAff GroupElementAffine
1427 windowedAff.setGEJ(&windowedResult)
1428 windowedAff.x.normalize()
1429 windowedAff.y.normalize()
1430
1431 if !refAff.x.equal(&windowedAff.x) || !refAff.y.equal(&windowedAff.y) {
1432 t.Errorf("ecmultWindowedVar mismatch for %s", tc.name)
1433 }
1434 })
1435 }
1436 }
1437
1438 // TestCrossValidationArbitraryPoint tests all implementations with a non-generator point
1439 func TestCrossValidationArbitraryPoint(t *testing.T) {
1440 // Create an arbitrary point P = 7*G
1441 var sevenScalar Scalar
1442 sevenScalar.d[0] = 7
1443
1444 var pJac GroupElementJacobian
1445 EcmultConst(&pJac, &Generator, &sevenScalar)
1446 var p GroupElementAffine
1447 p.setGEJ(&pJac)
1448 p.x.normalize()
1449 p.y.normalize()
1450
1451 testCases := []struct {
1452 name string
1453 scalar string
1454 }{
1455 {"one", "0000000000000000000000000000000000000000000000000000000000000001"},
1456 {"random1", "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"},
1457 {"random2", "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"},
1458 }
1459
1460 for _, tc := range testCases {
1461 t.Run(tc.name, func(t *testing.T) {
1462 sBytes, _ := hex.DecodeString(tc.scalar)
1463 var s Scalar
1464 s.setB32(sBytes)
1465
1466 if s.isZero() {
1467 return
1468 }
1469
1470 // Reference: EcmultConst
1471 var refResult GroupElementJacobian
1472 EcmultConst(&refResult, &p, &s)
1473 var refAff GroupElementAffine
1474 refAff.setGEJ(&refResult)
1475 refAff.x.normalize()
1476 refAff.y.normalize()
1477
1478 // Test 1: ecmultStraussWNAFGLV
1479 var straussResult GroupElementJacobian
1480 ecmultStraussWNAFGLV(&straussResult, &p, &s)
1481 var straussAff GroupElementAffine
1482 straussAff.setGEJ(&straussResult)
1483 straussAff.x.normalize()
1484 straussAff.y.normalize()
1485
1486 if !refAff.x.equal(&straussAff.x) || !refAff.y.equal(&straussAff.y) {
1487 t.Errorf("ecmultStraussWNAFGLV mismatch for arbitrary point with %s", tc.name)
1488 }
1489
1490 // Test 2: ecmultWindowedVar
1491 var windowedResult GroupElementJacobian
1492 ecmultWindowedVar(&windowedResult, &p, &s)
1493 var windowedAff GroupElementAffine
1494 windowedAff.setGEJ(&windowedResult)
1495 windowedAff.x.normalize()
1496 windowedAff.y.normalize()
1497
1498 if !refAff.x.equal(&windowedAff.x) || !refAff.y.equal(&windowedAff.y) {
1499 t.Errorf("ecmultWindowedVar mismatch for arbitrary point with %s", tc.name)
1500 }
1501
1502 // Test 3: Ecmult (public interface)
1503 var pJac2 GroupElementJacobian
1504 pJac2.setGE(&p)
1505 var ecmultResult GroupElementJacobian
1506 Ecmult(&ecmultResult, &pJac2, &s)
1507 var ecmultAff GroupElementAffine
1508 ecmultAff.setGEJ(&ecmultResult)
1509 ecmultAff.x.normalize()
1510 ecmultAff.y.normalize()
1511
1512 if !refAff.x.equal(&ecmultAff.x) || !refAff.y.equal(&ecmultAff.y) {
1513 t.Errorf("Ecmult mismatch for arbitrary point with %s", tc.name)
1514 }
1515 })
1516 }
1517 }
1518
1519 // TestCrossValidationRandomScalars tests with random scalars
1520 func TestCrossValidationRandomScalars(t *testing.T) {
1521 seed := uint64(0xdeadbeef12345678)
1522
1523 for i := 0; i < 50; i++ {
1524 // Generate pseudo-random scalar
1525 var sBytes [32]byte
1526 for j := 0; j < 32; j++ {
1527 seed = seed*6364136223846793005 + 1442695040888963407
1528 sBytes[j] = byte(seed >> 56)
1529 }
1530
1531 var s Scalar
1532 s.setB32(sBytes[:])
1533
1534 if s.isZero() {
1535 continue
1536 }
1537
1538 // Reference
1539 var refResult GroupElementJacobian
1540 EcmultConst(&refResult, &Generator, &s)
1541 var refAff GroupElementAffine
1542 refAff.setGEJ(&refResult)
1543 refAff.x.normalize()
1544 refAff.y.normalize()
1545
1546 // GLV
1547 var glvResult GroupElementJacobian
1548 ecmultGenGLV(&glvResult, &s)
1549 var glvAff GroupElementAffine
1550 glvAff.setGEJ(&glvResult)
1551 glvAff.x.normalize()
1552 glvAff.y.normalize()
1553
1554 if !refAff.x.equal(&glvAff.x) || !refAff.y.equal(&glvAff.y) {
1555 var sHex [32]byte
1556 s.getB32(sHex[:])
1557 t.Errorf("Random test %d failed: scalar=%s", i, hex.EncodeToString(sHex[:]))
1558 }
1559 }
1560 }
1561
1562 // =============================================================================
1563 // Phase 6: Property-Based Tests
1564 // =============================================================================
1565
1566 // TestPropertyScalarSplitReconstruction verifies that k1 + k2*λ ≡ k (mod n)
1567 func TestPropertyScalarSplitReconstruction(t *testing.T) {
1568 seed := uint64(0xcafebabe98765432)
1569
1570 for i := 0; i < 100; i++ {
1571 // Generate pseudo-random scalar
1572 var kBytes [32]byte
1573 for j := 0; j < 32; j++ {
1574 seed = seed*6364136223846793005 + 1442695040888963407
1575 kBytes[j] = byte(seed >> 56)
1576 }
1577
1578 var k Scalar
1579 k.setB32(kBytes[:])
1580
1581 if k.isZero() {
1582 continue
1583 }
1584
1585 // Split k
1586 var k1, k2 Scalar
1587 scalarSplitLambda(&k1, &k2, &k)
1588
1589 // Compute k2 * λ
1590 var k2Lambda Scalar
1591 k2Lambda.mul(&k2, &scalarLambda)
1592
1593 // Compute k1 + k2*λ
1594 var reconstructed Scalar
1595 reconstructed.add(&k1, &k2Lambda)
1596
1597 // Verify k1 + k2*λ ≡ k (mod n)
1598 if !reconstructed.equal(&k) {
1599 var kHex, rHex [32]byte
1600 k.getB32(kHex[:])
1601 reconstructed.getB32(rHex[:])
1602 t.Errorf("Scalar split reconstruction failed:\n k=%s\n reconstructed=%s",
1603 hex.EncodeToString(kHex[:]), hex.EncodeToString(rHex[:]))
1604 }
1605 }
1606 }
1607
1608 // TestPropertyLambdaEndomorphism verifies that λ·P = (β·x, y) for points on the curve
1609 func TestPropertyLambdaEndomorphism(t *testing.T) {
1610 seed := uint64(0x1234567890abcdef)
1611
1612 for i := 0; i < 50; i++ {
1613 // Generate a random point by multiplying G by a random scalar
1614 var sBytes [32]byte
1615 for j := 0; j < 32; j++ {
1616 seed = seed*6364136223846793005 + 1442695040888963407
1617 sBytes[j] = byte(seed >> 56)
1618 }
1619
1620 var s Scalar
1621 s.setB32(sBytes[:])
1622
1623 if s.isZero() {
1624 continue
1625 }
1626
1627 // P = s * G
1628 var pJac GroupElementJacobian
1629 EcmultConst(&pJac, &Generator, &s)
1630 var p GroupElementAffine
1631 p.setGEJ(&pJac)
1632 p.x.normalize()
1633 p.y.normalize()
1634
1635 if p.isInfinity() {
1636 continue
1637 }
1638
1639 // Method 1: λ·P via scalar multiplication
1640 var lambdaPJac GroupElementJacobian
1641 EcmultConst(&lambdaPJac, &p, &scalarLambda)
1642 var lambdaP1 GroupElementAffine
1643 lambdaP1.setGEJ(&lambdaPJac)
1644 lambdaP1.x.normalize()
1645 lambdaP1.y.normalize()
1646
1647 // Method 2: λ·P via endomorphism (β·x, y)
1648 var lambdaP2 GroupElementAffine
1649 lambdaP2.mulLambda(&p)
1650 lambdaP2.x.normalize()
1651 lambdaP2.y.normalize()
1652
1653 // Verify they match
1654 if !lambdaP1.x.equal(&lambdaP2.x) || !lambdaP1.y.equal(&lambdaP2.y) {
1655 t.Errorf("Lambda endomorphism mismatch at iteration %d", i)
1656 }
1657 }
1658 }
1659
1660 // TestPropertyBetaCubeRoot verifies that β^3 ≡ 1 (mod p)
1661 func TestPropertyBetaCubeRoot(t *testing.T) {
1662 // Compute β^2
1663 var beta2 FieldElement
1664 beta2.mul(&fieldBeta, &fieldBeta)
1665
1666 // Compute β^3 = β^2 * β
1667 var beta3 FieldElement
1668 beta3.mul(&beta2, &fieldBeta)
1669 beta3.normalize()
1670
1671 // Check if β^3 ≡ 1 (mod p)
1672 if !beta3.equal(&FieldElementOne) {
1673 var bytes [32]byte
1674 beta3.getB32(bytes[:])
1675 t.Errorf("β^3 ≠ 1 (mod p), got: %s", hex.EncodeToString(bytes[:]))
1676 }
1677 }
1678
1679 // TestPropertyDoubleNegate verifies that -(-P) = P
1680 func TestPropertyDoubleNegate(t *testing.T) {
1681 seed := uint64(0xfedcba0987654321)
1682
1683 for i := 0; i < 50; i++ {
1684 // Generate a random point
1685 var sBytes [32]byte
1686 for j := 0; j < 32; j++ {
1687 seed = seed*6364136223846793005 + 1442695040888963407
1688 sBytes[j] = byte(seed >> 56)
1689 }
1690
1691 var s Scalar
1692 s.setB32(sBytes[:])
1693
1694 if s.isZero() {
1695 continue
1696 }
1697
1698 var pJac GroupElementJacobian
1699 ecmultGenGLV(&pJac, &s)
1700 var p GroupElementAffine
1701 p.setGEJ(&pJac)
1702 p.x.normalize()
1703 p.y.normalize()
1704
1705 if p.isInfinity() {
1706 continue
1707 }
1708
1709 // Negate twice
1710 var negP, negNegP GroupElementAffine
1711 negP.negate(&p)
1712 negNegP.negate(&negP)
1713 negNegP.x.normalize()
1714 negNegP.y.normalize()
1715
1716 // Verify -(-P) = P
1717 if !p.x.equal(&negNegP.x) || !p.y.equal(&negNegP.y) {
1718 t.Errorf("Double negation property failed at iteration %d", i)
1719 }
1720 }
1721 }
1722
1723 // TestPropertyScalarAddition verifies that (a+b)*G = a*G + b*G
1724 func TestPropertyScalarAddition(t *testing.T) {
1725 seed := uint64(0xabcdef1234567890)
1726
1727 for i := 0; i < 50; i++ {
1728 // Generate two random scalars
1729 var aBytes, bBytes [32]byte
1730 for j := 0; j < 32; j++ {
1731 seed = seed*6364136223846793005 + 1442695040888963407
1732 aBytes[j] = byte(seed >> 56)
1733 seed = seed*6364136223846793005 + 1442695040888963407
1734 bBytes[j] = byte(seed >> 56)
1735 }
1736
1737 var a, b Scalar
1738 a.setB32(aBytes[:])
1739 b.setB32(bBytes[:])
1740
1741 // Compute (a+b)*G
1742 var sum Scalar
1743 sum.add(&a, &b)
1744
1745 var sumG GroupElementJacobian
1746 ecmultGenGLV(&sumG, &sum)
1747 var sumGAff GroupElementAffine
1748 sumGAff.setGEJ(&sumG)
1749 sumGAff.x.normalize()
1750 sumGAff.y.normalize()
1751
1752 // Compute a*G + b*G
1753 var aG, bG GroupElementJacobian
1754 ecmultGenGLV(&aG, &a)
1755 ecmultGenGLV(&bG, &b)
1756
1757 var aGplusBG GroupElementJacobian
1758 aGplusBG.addVar(&aG, &bG)
1759 var aGplusBGAff GroupElementAffine
1760 aGplusBGAff.setGEJ(&aGplusBG)
1761 aGplusBGAff.x.normalize()
1762 aGplusBGAff.y.normalize()
1763
1764 // Verify (a+b)*G = a*G + b*G
1765 if sumGAff.isInfinity() != aGplusBGAff.isInfinity() {
1766 t.Errorf("Scalar addition property failed at iteration %d (infinity mismatch)", i)
1767 continue
1768 }
1769 if !sumGAff.isInfinity() {
1770 if !sumGAff.x.equal(&aGplusBGAff.x) || !sumGAff.y.equal(&aGplusBGAff.y) {
1771 t.Errorf("Scalar addition property failed at iteration %d", i)
1772 }
1773 }
1774 }
1775 }
1776
1777 // TestPropertyScalarMultiplication verifies that (a*b)*G = a*(b*G)
1778 func TestPropertyScalarMultiplication(t *testing.T) {
1779 seed := uint64(0x9876543210fedcba)
1780
1781 for i := 0; i < 50; i++ {
1782 // Generate two random scalars
1783 var aBytes, bBytes [32]byte
1784 for j := 0; j < 32; j++ {
1785 seed = seed*6364136223846793005 + 1442695040888963407
1786 aBytes[j] = byte(seed >> 56)
1787 seed = seed*6364136223846793005 + 1442695040888963407
1788 bBytes[j] = byte(seed >> 56)
1789 }
1790
1791 var a, b Scalar
1792 a.setB32(aBytes[:])
1793 b.setB32(bBytes[:])
1794
1795 // Compute (a*b)*G
1796 var product Scalar
1797 product.mul(&a, &b)
1798
1799 var productG GroupElementJacobian
1800 ecmultGenGLV(&productG, &product)
1801 var productGAff GroupElementAffine
1802 productGAff.setGEJ(&productG)
1803 productGAff.x.normalize()
1804 productGAff.y.normalize()
1805
1806 // Compute a*(b*G)
1807 var bG GroupElementJacobian
1808 ecmultGenGLV(&bG, &b)
1809 var bGAff GroupElementAffine
1810 bGAff.setGEJ(&bG)
1811 bGAff.x.normalize()
1812 bGAff.y.normalize()
1813
1814 var aBG GroupElementJacobian
1815 ecmultStraussWNAFGLV(&aBG, &bGAff, &a)
1816 var aBGAff GroupElementAffine
1817 aBGAff.setGEJ(&aBG)
1818 aBGAff.x.normalize()
1819 aBGAff.y.normalize()
1820
1821 // Verify (a*b)*G = a*(b*G)
1822 if productGAff.isInfinity() != aBGAff.isInfinity() {
1823 t.Errorf("Scalar multiplication property failed at iteration %d (infinity mismatch)", i)
1824 continue
1825 }
1826 if !productGAff.isInfinity() {
1827 if !productGAff.x.equal(&aBGAff.x) || !productGAff.y.equal(&aBGAff.y) {
1828 t.Errorf("Scalar multiplication property failed at iteration %d", i)
1829 }
1830 }
1831 }
1832 }
1833
1834 // TestEcmultCombined tests the combined na*a + ng*G function
1835 func TestEcmultCombined(t *testing.T) {
1836 testCases := []struct {
1837 name string
1838 na string
1839 ng string
1840 }{
1841 {"both_one", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001"},
1842 {"na_only", "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", "0000000000000000000000000000000000000000000000000000000000000000"},
1843 {"ng_only", "0000000000000000000000000000000000000000000000000000000000000000", "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"},
1844 {"random_both", "deadbeefcafebabe1234567890abcdef0123456789abcdef0123456789abcdef", "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"},
1845 }
1846
1847 // Create an arbitrary point P = 7*G
1848 var sevenScalar Scalar
1849 sevenScalar.d[0] = 7
1850 var pJac GroupElementJacobian
1851 EcmultConst(&pJac, &Generator, &sevenScalar)
1852
1853 for _, tc := range testCases {
1854 t.Run(tc.name, func(t *testing.T) {
1855 naBytes, _ := hex.DecodeString(tc.na)
1856 ngBytes, _ := hex.DecodeString(tc.ng)
1857
1858 var na, ng Scalar
1859 na.setB32(naBytes)
1860 ng.setB32(ngBytes)
1861
1862 // Compute using EcmultCombined
1863 var combined GroupElementJacobian
1864 EcmultCombined(&combined, &pJac, &na, &ng)
1865 var combinedAff GroupElementAffine
1866 combinedAff.setGEJ(&combined)
1867 combinedAff.x.normalize()
1868 combinedAff.y.normalize()
1869
1870 // Compute reference: na*P + ng*G separately
1871 var naP, ngG, ref GroupElementJacobian
1872
1873 if !na.isZero() {
1874 var pAff GroupElementAffine
1875 pAff.setGEJ(&pJac)
1876 EcmultConst(&naP, &pAff, &na)
1877 } else {
1878 naP.setInfinity()
1879 }
1880
1881 if !ng.isZero() {
1882 EcmultConst(&ngG, &Generator, &ng)
1883 } else {
1884 ngG.setInfinity()
1885 }
1886
1887 ref.addVar(&naP, &ngG)
1888 var refAff GroupElementAffine
1889 refAff.setGEJ(&ref)
1890 refAff.x.normalize()
1891 refAff.y.normalize()
1892
1893 // Verify
1894 if combinedAff.isInfinity() != refAff.isInfinity() {
1895 t.Errorf("EcmultCombined infinity mismatch for %s", tc.name)
1896 return
1897 }
1898
1899 if !combinedAff.isInfinity() {
1900 if !combinedAff.x.equal(&refAff.x) || !combinedAff.y.equal(&refAff.y) {
1901 t.Errorf("EcmultCombined result mismatch for %s", tc.name)
1902 }
1903 }
1904 })
1905 }
1906 }
1907
1908 // BenchmarkEcmultCombined benchmarks the combined multiplication
1909 func BenchmarkEcmultCombined(b *testing.B) {
1910 naBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
1911 ngBytes, _ := hex.DecodeString("deadbeefcafebabe1234567890abcdef0123456789abcdef0123456789abcdef")
1912
1913 var na, ng Scalar
1914 na.setB32(naBytes)
1915 ng.setB32(ngBytes)
1916
1917 // Create point P = 7*G
1918 var sevenScalar Scalar
1919 sevenScalar.d[0] = 7
1920 var pJac GroupElementJacobian
1921 EcmultConst(&pJac, &Generator, &sevenScalar)
1922
1923 EnsureGenTablesInitialized()
1924
1925 var result GroupElementJacobian
1926
1927 b.ResetTimer()
1928 for i := 0; i < b.N; i++ {
1929 EcmultCombined(&result, &pJac, &na, &ng)
1930 }
1931 }
1932
1933 // BenchmarkEcmultSeparate benchmarks separate na*P and ng*G computations
1934 func BenchmarkEcmultSeparate(b *testing.B) {
1935 naBytes, _ := hex.DecodeString("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
1936 ngBytes, _ := hex.DecodeString("deadbeefcafebabe1234567890abcdef0123456789abcdef0123456789abcdef")
1937
1938 var na, ng Scalar
1939 na.setB32(naBytes)
1940 ng.setB32(ngBytes)
1941
1942 // Create point P = 7*G
1943 var sevenScalar Scalar
1944 sevenScalar.d[0] = 7
1945 var pJac GroupElementJacobian
1946 EcmultConst(&pJac, &Generator, &sevenScalar)
1947 var pAff GroupElementAffine
1948 pAff.setGEJ(&pJac)
1949
1950 EnsureGenTablesInitialized()
1951
1952 var naP, ngG, result GroupElementJacobian
1953
1954 b.ResetTimer()
1955 for i := 0; i < b.N; i++ {
1956 ecmultStraussWNAFGLV(&naP, &pAff, &na)
1957 ecmultGenGLV(&ngG, &ng)
1958 result.addVar(&naP, &ngG)
1959 }
1960 }
1961