field_asm_test.go raw
1 //go:build !js && !wasm && !tinygo && !wasm32
2
3 package p256k1
4
5 import (
6 "testing"
7 )
8
9 // fieldMulPureGo is the pure Go implementation for comparison
10 func fieldMulPureGo(r, a, b *FieldElement) {
11 // Extract limbs for easier access
12 a0, a1, a2, a3, a4 := a.n[0], a.n[1], a.n[2], a.n[3], a.n[4]
13 b0, b1, b2, b3, b4 := b.n[0], b.n[1], b.n[2], b.n[3], b.n[4]
14
15 const M = uint64(0xFFFFFFFFFFFFF) // 2^52 - 1
16 const R = uint64(fieldReductionConstantShifted) // 0x1000003D10
17
18 // Following the C implementation algorithm exactly
19 var c, d uint128
20 d = mulU64ToU128(a0, b3)
21 d = addMulU128(d, a1, b2)
22 d = addMulU128(d, a2, b1)
23 d = addMulU128(d, a3, b0)
24
25 c = mulU64ToU128(a4, b4)
26
27 d = addMulU128(d, R, c.lo())
28 c = c.rshift(64)
29
30 t3 := d.lo() & M
31 d = d.rshift(52)
32
33 d = addMulU128(d, a0, b4)
34 d = addMulU128(d, a1, b3)
35 d = addMulU128(d, a2, b2)
36 d = addMulU128(d, a3, b1)
37 d = addMulU128(d, a4, b0)
38
39 d = addMulU128(d, R<<12, c.lo())
40
41 t4 := d.lo() & M
42 d = d.rshift(52)
43 tx := t4 >> 48
44 t4 &= (M >> 4)
45
46 c = mulU64ToU128(a0, b0)
47
48 d = addMulU128(d, a1, b4)
49 d = addMulU128(d, a2, b3)
50 d = addMulU128(d, a3, b2)
51 d = addMulU128(d, a4, b1)
52
53 u0 := d.lo() & M
54 d = d.rshift(52)
55 u0 = (u0 << 4) | tx
56
57 c = addMulU128(c, u0, R>>4)
58
59 r.n[0] = c.lo() & M
60 c = c.rshift(52)
61
62 c = addMulU128(c, a0, b1)
63 c = addMulU128(c, a1, b0)
64
65 d = addMulU128(d, a2, b4)
66 d = addMulU128(d, a3, b3)
67 d = addMulU128(d, a4, b2)
68
69 c = addMulU128(c, R, d.lo()&M)
70 d = d.rshift(52)
71
72 r.n[1] = c.lo() & M
73 c = c.rshift(52)
74
75 c = addMulU128(c, a0, b2)
76 c = addMulU128(c, a1, b1)
77 c = addMulU128(c, a2, b0)
78
79 d = addMulU128(d, a3, b4)
80 d = addMulU128(d, a4, b3)
81
82 c = addMulU128(c, R, d.lo())
83 d = d.rshift(64)
84
85 r.n[2] = c.lo() & M
86 c = c.rshift(52)
87
88 c = addMulU128(c, R<<12, d.lo())
89 c = addU128(c, t3)
90
91 r.n[3] = c.lo() & M
92 c = c.rshift(52)
93
94 r.n[4] = c.lo() + t4
95
96 r.magnitude = 1
97 r.normalized = false
98 }
99
100 func TestFieldMulAsmVsPureGo(t *testing.T) {
101 // Test with simple values first
102 a := FieldElement{n: [5]uint64{1, 0, 0, 0, 0}, magnitude: 1, normalized: true}
103 b := FieldElement{n: [5]uint64{2, 0, 0, 0, 0}, magnitude: 1, normalized: true}
104
105 var rAsm, rGo FieldElement
106
107 // Pure Go
108 fieldMulPureGo(&rGo, &a, &b)
109
110 // Assembly
111 if hasFieldAsm() {
112 fieldMulAsm(&rAsm, &a, &b)
113 rAsm.magnitude = 1
114 rAsm.normalized = false
115
116 t.Logf("a = %v", a.n)
117 t.Logf("b = %v", b.n)
118 t.Logf("Go result: %v", rGo.n)
119 t.Logf("Asm result: %v", rAsm.n)
120
121 for i := 0; i < 5; i++ {
122 if rAsm.n[i] != rGo.n[i] {
123 t.Errorf("limb %d mismatch: asm=%x, go=%x", i, rAsm.n[i], rGo.n[i])
124 }
125 }
126 } else {
127 t.Skip("Assembly not available")
128 }
129 }
130
131 func TestFieldMulAsmVsPureGoLarger(t *testing.T) {
132 // Test with larger values
133 a := FieldElement{
134 n: [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
135 magnitude: 1,
136 normalized: true,
137 }
138 b := FieldElement{
139 n: [5]uint64{0xabcdef1234567890 & 0xFFFFFFFFFFFFF, 0x9876543210fedcba & 0xFFFFFFFFFFFFF, 0xfedcba1234567890 & 0xFFFFFFFFFFFFF, 0x0987654321abcdef & 0xFFFFFFFFFFFFF, 0x0fedcba98765 & 0x0FFFFFFFFFFFF},
140 magnitude: 1,
141 normalized: true,
142 }
143
144 var rAsm, rGo FieldElement
145
146 // Pure Go
147 fieldMulPureGo(&rGo, &a, &b)
148
149 // Assembly
150 if hasFieldAsm() {
151 fieldMulAsm(&rAsm, &a, &b)
152 rAsm.magnitude = 1
153 rAsm.normalized = false
154
155 t.Logf("a = %v", a.n)
156 t.Logf("b = %v", b.n)
157 t.Logf("Go result: %v", rGo.n)
158 t.Logf("Asm result: %v", rAsm.n)
159
160 for i := 0; i < 5; i++ {
161 if rAsm.n[i] != rGo.n[i] {
162 t.Errorf("limb %d mismatch: asm=%x, go=%x", i, rAsm.n[i], rGo.n[i])
163 }
164 }
165 } else {
166 t.Skip("Assembly not available")
167 }
168 }
169
170 func TestFieldSqrAsmVsPureGo(t *testing.T) {
171 a := FieldElement{
172 n: [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
173 magnitude: 1,
174 normalized: true,
175 }
176
177 var rAsm, rGo FieldElement
178
179 // Pure Go (a * a)
180 fieldMulPureGo(&rGo, &a, &a)
181
182 // Assembly
183 if hasFieldAsm() {
184 fieldSqrAsm(&rAsm, &a)
185 rAsm.magnitude = 1
186 rAsm.normalized = false
187
188 t.Logf("a = %v", a.n)
189 t.Logf("Go result: %v", rGo.n)
190 t.Logf("Asm result: %v", rAsm.n)
191
192 for i := 0; i < 5; i++ {
193 if rAsm.n[i] != rGo.n[i] {
194 t.Errorf("limb %d mismatch: asm=%x, go=%x", i, rAsm.n[i], rGo.n[i])
195 }
196 }
197 } else {
198 t.Skip("Assembly not available")
199 }
200 }
201
202 // BMI2 tests
203
204 func TestFieldMulAsmBMI2VsPureGo(t *testing.T) {
205 if !hasFieldAsmBMI2() {
206 t.Skip("BMI2+ADX assembly not available")
207 }
208
209 // Test with simple values first
210 a := FieldElement{n: [5]uint64{1, 0, 0, 0, 0}, magnitude: 1, normalized: true}
211 b := FieldElement{n: [5]uint64{2, 0, 0, 0, 0}, magnitude: 1, normalized: true}
212
213 var rBMI2, rGo FieldElement
214
215 // Pure Go
216 fieldMulPureGo(&rGo, &a, &b)
217
218 // BMI2 Assembly
219 fieldMulAsmBMI2(&rBMI2, &a, &b)
220 rBMI2.magnitude = 1
221 rBMI2.normalized = false
222
223 t.Logf("a = %v", a.n)
224 t.Logf("b = %v", b.n)
225 t.Logf("Go result: %v", rGo.n)
226 t.Logf("BMI2 result: %v", rBMI2.n)
227
228 for i := 0; i < 5; i++ {
229 if rBMI2.n[i] != rGo.n[i] {
230 t.Errorf("limb %d mismatch: bmi2=%x, go=%x", i, rBMI2.n[i], rGo.n[i])
231 }
232 }
233 }
234
235 func TestFieldMulAsmBMI2VsPureGoLarger(t *testing.T) {
236 if !hasFieldAsmBMI2() {
237 t.Skip("BMI2+ADX assembly not available")
238 }
239
240 // Test with larger values
241 a := FieldElement{
242 n: [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
243 magnitude: 1,
244 normalized: true,
245 }
246 b := FieldElement{
247 n: [5]uint64{0xabcdef1234567890 & 0xFFFFFFFFFFFFF, 0x9876543210fedcba & 0xFFFFFFFFFFFFF, 0xfedcba1234567890 & 0xFFFFFFFFFFFFF, 0x0987654321abcdef & 0xFFFFFFFFFFFFF, 0x0fedcba98765 & 0x0FFFFFFFFFFFF},
248 magnitude: 1,
249 normalized: true,
250 }
251
252 var rBMI2, rGo FieldElement
253
254 // Pure Go
255 fieldMulPureGo(&rGo, &a, &b)
256
257 // BMI2 Assembly
258 fieldMulAsmBMI2(&rBMI2, &a, &b)
259 rBMI2.magnitude = 1
260 rBMI2.normalized = false
261
262 t.Logf("a = %v", a.n)
263 t.Logf("b = %v", b.n)
264 t.Logf("Go result: %v", rGo.n)
265 t.Logf("BMI2 result: %v", rBMI2.n)
266
267 for i := 0; i < 5; i++ {
268 if rBMI2.n[i] != rGo.n[i] {
269 t.Errorf("limb %d mismatch: bmi2=%x, go=%x", i, rBMI2.n[i], rGo.n[i])
270 }
271 }
272 }
273
274 func TestFieldMulAsmBMI2VsRegularAsm(t *testing.T) {
275 if !hasFieldAsmBMI2() {
276 t.Skip("BMI2+ADX assembly not available")
277 }
278 if !hasFieldAsm() {
279 t.Skip("Regular assembly not available")
280 }
281
282 // Test with larger values
283 a := FieldElement{
284 n: [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
285 magnitude: 1,
286 normalized: true,
287 }
288 b := FieldElement{
289 n: [5]uint64{0xabcdef1234567890 & 0xFFFFFFFFFFFFF, 0x9876543210fedcba & 0xFFFFFFFFFFFFF, 0xfedcba1234567890 & 0xFFFFFFFFFFFFF, 0x0987654321abcdef & 0xFFFFFFFFFFFFF, 0x0fedcba98765 & 0x0FFFFFFFFFFFF},
290 magnitude: 1,
291 normalized: true,
292 }
293
294 var rBMI2, rAsm FieldElement
295
296 // Regular Assembly
297 fieldMulAsm(&rAsm, &a, &b)
298 rAsm.magnitude = 1
299 rAsm.normalized = false
300
301 // BMI2 Assembly
302 fieldMulAsmBMI2(&rBMI2, &a, &b)
303 rBMI2.magnitude = 1
304 rBMI2.normalized = false
305
306 t.Logf("a = %v", a.n)
307 t.Logf("b = %v", b.n)
308 t.Logf("Asm result: %v", rAsm.n)
309 t.Logf("BMI2 result: %v", rBMI2.n)
310
311 for i := 0; i < 5; i++ {
312 if rBMI2.n[i] != rAsm.n[i] {
313 t.Errorf("limb %d mismatch: bmi2=%x, asm=%x", i, rBMI2.n[i], rAsm.n[i])
314 }
315 }
316 }
317
318 func TestFieldSqrAsmBMI2VsPureGo(t *testing.T) {
319 if !hasFieldAsmBMI2() {
320 t.Skip("BMI2+ADX assembly not available")
321 }
322
323 a := FieldElement{
324 n: [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
325 magnitude: 1,
326 normalized: true,
327 }
328
329 var rBMI2, rGo FieldElement
330
331 // Pure Go (a * a)
332 fieldMulPureGo(&rGo, &a, &a)
333
334 // BMI2 Assembly
335 fieldSqrAsmBMI2(&rBMI2, &a)
336 rBMI2.magnitude = 1
337 rBMI2.normalized = false
338
339 t.Logf("a = %v", a.n)
340 t.Logf("Go result: %v", rGo.n)
341 t.Logf("BMI2 result: %v", rBMI2.n)
342
343 for i := 0; i < 5; i++ {
344 if rBMI2.n[i] != rGo.n[i] {
345 t.Errorf("limb %d mismatch: bmi2=%x, go=%x", i, rBMI2.n[i], rGo.n[i])
346 }
347 }
348 }
349
350 func TestFieldSqrAsmBMI2VsRegularAsm(t *testing.T) {
351 if !hasFieldAsmBMI2() {
352 t.Skip("BMI2+ADX assembly not available")
353 }
354 if !hasFieldAsm() {
355 t.Skip("Regular assembly not available")
356 }
357
358 a := FieldElement{
359 n: [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
360 magnitude: 1,
361 normalized: true,
362 }
363
364 var rBMI2, rAsm FieldElement
365
366 // Regular Assembly
367 fieldSqrAsm(&rAsm, &a)
368 rAsm.magnitude = 1
369 rAsm.normalized = false
370
371 // BMI2 Assembly
372 fieldSqrAsmBMI2(&rBMI2, &a)
373 rBMI2.magnitude = 1
374 rBMI2.normalized = false
375
376 t.Logf("a = %v", a.n)
377 t.Logf("Asm result: %v", rAsm.n)
378 t.Logf("BMI2 result: %v", rBMI2.n)
379
380 for i := 0; i < 5; i++ {
381 if rBMI2.n[i] != rAsm.n[i] {
382 t.Errorf("limb %d mismatch: bmi2=%x, asm=%x", i, rBMI2.n[i], rAsm.n[i])
383 }
384 }
385 }
386
387 // TestFieldMulAsmBMI2Random tests with many random values
388 func TestFieldMulAsmBMI2Random(t *testing.T) {
389 if !hasFieldAsmBMI2() {
390 t.Skip("BMI2+ADX assembly not available")
391 }
392 if !hasFieldAsm() {
393 t.Skip("Regular assembly not available")
394 }
395
396 // Test with many random values
397 for iter := 0; iter < 10000; iter++ {
398 var a, b FieldElement
399 a.magnitude = 1
400 a.normalized = true
401 b.magnitude = 1
402 b.normalized = true
403
404 // Generate deterministic but varied test data
405 seed := uint64(iter * 12345678901234567)
406 for j := 0; j < 5; j++ {
407 seed = seed*6364136223846793005 + 1442695040888963407 // LCG
408 a.n[j] = seed & 0xFFFFFFFFFFFFF
409
410 seed = seed*6364136223846793005 + 1442695040888963407
411 b.n[j] = seed & 0xFFFFFFFFFFFFF
412 }
413 // Limb 4 is only 48 bits
414 a.n[4] &= 0x0FFFFFFFFFFFF
415 b.n[4] &= 0x0FFFFFFFFFFFF
416
417 var rAsm, rBMI2 FieldElement
418
419 // Regular Assembly
420 fieldMulAsm(&rAsm, &a, &b)
421 rAsm.magnitude = 1
422 rAsm.normalized = false
423
424 // BMI2 Assembly
425 fieldMulAsmBMI2(&rBMI2, &a, &b)
426 rBMI2.magnitude = 1
427 rBMI2.normalized = false
428
429 // Compare results
430 for j := 0; j < 5; j++ {
431 if rAsm.n[j] != rBMI2.n[j] {
432 t.Errorf("Iteration %d: limb %d mismatch", iter, j)
433 t.Errorf(" a = %v", a.n)
434 t.Errorf(" b = %v", b.n)
435 t.Errorf(" Asm: %v", rAsm.n)
436 t.Errorf(" BMI2: %v", rBMI2.n)
437 return
438 }
439 }
440 }
441 }
442
443 // TestFieldSqrAsmBMI2Random tests squaring with many random values
444 func TestFieldSqrAsmBMI2Random(t *testing.T) {
445 if !hasFieldAsmBMI2() {
446 t.Skip("BMI2+ADX assembly not available")
447 }
448 if !hasFieldAsm() {
449 t.Skip("Regular assembly not available")
450 }
451
452 // Test with many random values
453 for iter := 0; iter < 10000; iter++ {
454 var a FieldElement
455 a.magnitude = 1
456 a.normalized = true
457
458 // Generate deterministic but varied test data
459 seed := uint64(iter * 98765432109876543)
460 for j := 0; j < 5; j++ {
461 seed = seed*6364136223846793005 + 1442695040888963407 // LCG
462 a.n[j] = seed & 0xFFFFFFFFFFFFF
463 }
464 // Limb 4 is only 48 bits
465 a.n[4] &= 0x0FFFFFFFFFFFF
466
467 var rAsm, rBMI2 FieldElement
468
469 // Regular Assembly
470 fieldSqrAsm(&rAsm, &a)
471 rAsm.magnitude = 1
472 rAsm.normalized = false
473
474 // BMI2 Assembly
475 fieldSqrAsmBMI2(&rBMI2, &a)
476 rBMI2.magnitude = 1
477 rBMI2.normalized = false
478
479 // Compare results
480 for j := 0; j < 5; j++ {
481 if rAsm.n[j] != rBMI2.n[j] {
482 t.Errorf("Iteration %d: limb %d mismatch", iter, j)
483 t.Errorf(" a = %v", a.n)
484 t.Errorf(" Asm: %v", rAsm.n)
485 t.Errorf(" BMI2: %v", rBMI2.n)
486 return
487 }
488 }
489 }
490 }
491