nx_p256_solinas_fast.nx source
↩ module page · 119 lines · 5981 B
1// nx_p256_solinas_fast.nx -- a FASTER NIST P-256 Solinas reducer, bit-exact with the production
2// _p256_solinas_reduce. The production version issues ~60 _r_addw FUNCTION CALLS, each propagating
3// a carry up a 9-limb memory accumulator on every word add. This version DEFERS the carry: it sums
4// each limb as a wide 64-bit value (max ~6*(2^32-1) < 2^35, no overflow) with plain array adds (no
5// calls, no per-add carry loop), then does ONE normalization pass before the identical reduce/subtract
6// tail. Same S-term table, same _r_reduce9 / _r_cmp8 helpers, same final POS-NEG mod p -> the result
7// is identical (proven by nx_p256_solinas_fast_difftest). Uses NO compiler intrinsic, so it builds
8// with the live compiler; kept SEPARATE so the production reducer is untouched until measured+greenlit.
9// license_tier: ORIGINAL
10import "nx_p256_field_mul.nx" // _r_reduce9, _r_cmp8, u256_alloc, p256_field_load_p, nx_scratch, constants
11
12func _p256_solinas_reduce_fast(out8: *i64, c: *i64) -> i64 {
13 let p: *i64 = u256_alloc()
14 p256_field_load_p(p)
15 let sc: *i64 = (nx_scratch(18 * 8)) as *i64
16 var z: i64 = 0
17 while z < 18 { sc[z] = 0; z = z + 1 }
18 let POS: *i64 = sc
19 let NEG: *i64 = (sc as i64 + 9 * 8) as *i64
20
21 let c0: i64 = c[0] & NX_U256_LIMB_MASK
22 let c1: i64 = c[1] & NX_U256_LIMB_MASK
23 let c2: i64 = c[2] & NX_U256_LIMB_MASK
24 let c3: i64 = c[3] & NX_U256_LIMB_MASK
25 let c4: i64 = c[4] & NX_U256_LIMB_MASK
26 let c5: i64 = c[5] & NX_U256_LIMB_MASK
27 let c6: i64 = c[6] & NX_U256_LIMB_MASK
28 let c7: i64 = c[7] & NX_U256_LIMB_MASK
29 let c8: i64 = c[8] & NX_U256_LIMB_MASK
30 let c9: i64 = c[9] & NX_U256_LIMB_MASK
31 let c10: i64 = c[10] & NX_U256_LIMB_MASK
32 let c11: i64 = c[11] & NX_U256_LIMB_MASK
33 let c12: i64 = c[12] & NX_U256_LIMB_MASK
34 let c13: i64 = c[13] & NX_U256_LIMB_MASK
35 let c14: i64 = c[14] & NX_U256_LIMB_MASK
36 let c15: i64 = c[15] & NX_U256_LIMB_MASK
37
38 // ---- POS = s1 + 2*s2 + 2*s3 + s4 + s5, accumulated WIDE (deferred carry) ----
39 POS[0] = c0; POS[1] = c1; POS[2] = c2; POS[3] = c3
40 POS[4] = c4; POS[5] = c5; POS[6] = c6; POS[7] = c7; POS[8] = 0
41 // 2*s2 = (c15,c14,c13,c12,c11,0,0,0) doubled -> limbs 3..7
42 POS[3] = POS[3] + c11 + c11; POS[4] = POS[4] + c12 + c12; POS[5] = POS[5] + c13 + c13
43 POS[6] = POS[6] + c14 + c14; POS[7] = POS[7] + c15 + c15
44 // 2*s3 = (0,c15,c14,c13,c12,0,0,0) doubled -> limbs 3..6
45 POS[3] = POS[3] + c12 + c12; POS[4] = POS[4] + c13 + c13; POS[5] = POS[5] + c14 + c14; POS[6] = POS[6] + c15 + c15
46 // s4 = (c15,c14,0,0,0,c10,c9,c8)
47 POS[0] = POS[0] + c8; POS[1] = POS[1] + c9; POS[2] = POS[2] + c10; POS[6] = POS[6] + c14; POS[7] = POS[7] + c15
48 // s5 = (c8,c13,c15,c14,c13,c11,c10,c9)
49 POS[0] = POS[0] + c9; POS[1] = POS[1] + c10; POS[2] = POS[2] + c11; POS[3] = POS[3] + c13
50 POS[4] = POS[4] + c14; POS[5] = POS[5] + c15; POS[6] = POS[6] + c13; POS[7] = POS[7] + c8
51
52 // ---- NEG = s6 + s7 + s8 + s9 ----
53 // s6 = (c10,c8,0,0,0,c13,c12,c11)
54 NEG[0] = c11; NEG[1] = c12; NEG[2] = c13; NEG[3] = 0; NEG[4] = 0; NEG[5] = 0; NEG[6] = c8; NEG[7] = c10; NEG[8] = 0
55 // s7 = (c11,c9,0,0,c15,c14,c13,c12)
56 NEG[0] = NEG[0] + c12; NEG[1] = NEG[1] + c13; NEG[2] = NEG[2] + c14; NEG[3] = NEG[3] + c15; NEG[6] = NEG[6] + c9; NEG[7] = NEG[7] + c11
57 // s8 = (c12,0,c10,c9,c8,c15,c14,c13)
58 NEG[0] = NEG[0] + c13; NEG[1] = NEG[1] + c14; NEG[2] = NEG[2] + c15; NEG[3] = NEG[3] + c8
59 NEG[4] = NEG[4] + c9; NEG[5] = NEG[5] + c10; NEG[7] = NEG[7] + c12
60 // s9 = (c13,0,c11,c10,c9,0,c15,c14)
61 NEG[0] = NEG[0] + c14; NEG[1] = NEG[1] + c15; NEG[3] = NEG[3] + c9; NEG[4] = NEG[4] + c10; NEG[5] = NEG[5] + c11; NEG[7] = NEG[7] + c13
62
63 // ---- ONE normalization pass each (wide 64-bit limbs -> canonical 32-bit + top carry) ----
64 var cr: i64 = 0
65 var i: i64 = 0
66 while i < 9 { let t: i64 = POS[i] + cr; POS[i] = t & NX_U256_LIMB_MASK; cr = t >> NX_U256_LIMB_BITS; i = i + 1 }
67 cr = 0; i = 0
68 while i < 9 { let t: i64 = NEG[i] + cr; NEG[i] = t & NX_U256_LIMB_MASK; cr = t >> NX_U256_LIMB_BITS; i = i + 1 }
69
70 // ---- identical tail: reduce each to [0,p), then out = (POS - NEG) mod p ----
71 _r_reduce9(POS, p)
72 _r_reduce9(NEG, p)
73
74 if _r_cmp8(POS, NEG) >= 0 {
75 var borrow: i64 = 0
76 var j: i64 = 0
77 while j < NX_U256_LIMBS {
78 let d: i64 = (POS[j] & NX_U256_LIMB_MASK) - (NEG[j] & NX_U256_LIMB_MASK) - borrow
79 if d < 0 { out8[j] = (d + (1 << NX_U256_LIMB_BITS)) & NX_U256_LIMB_MASK; borrow = 1 }
80 else { out8[j] = d & NX_U256_LIMB_MASK; borrow = 0 }
81 j = j + 1
82 }
83 } else {
84 let t: *i64 = (nx_scratch(9 * 8)) as *i64
85 var z2: i64 = 0
86 while z2 < 9 { t[z2] = 0; z2 = z2 + 1 }
87 var carry: i64 = 0
88 var k: i64 = 0
89 while k < NX_U256_LIMBS {
90 let v: i64 = (POS[k] & NX_U256_LIMB_MASK) + (p[k] & NX_U256_LIMB_MASK) + carry
91 t[k] = v & NX_U256_LIMB_MASK
92 carry = v >> NX_U256_LIMB_BITS
93 k = k + 1
94 }
95 t[8] = carry
96 var borrow: i64 = 0
97 var j: i64 = 0
98 while j < NX_U256_LIMBS {
99 let d: i64 = (t[j] & NX_U256_LIMB_MASK) - (NEG[j] & NX_U256_LIMB_MASK) - borrow
100 if d < 0 { t[j] = (d + (1 << NX_U256_LIMB_BITS)) & NX_U256_LIMB_MASK; borrow = 1 }
101 else { t[j] = d & NX_U256_LIMB_MASK; borrow = 0 }
102 j = j + 1
103 }
104 t[8] = (t[8] & NX_U256_LIMB_MASK) - borrow
105 _r_reduce9(t, p)
106 var m: i64 = 0
107 while m < NX_U256_LIMBS { out8[m] = t[m] & NX_U256_LIMB_MASK; m = m + 1 }
108 }
109 return 0
110}
111
112// Public framed entry (mirrors p256_field_reduce_solinas) for differential validation on arbitrary
113// 512-bit inputs.
114func p256_field_reduce_solinas_fast(out_8: *i64, c_16: *i64) -> i64 {
115 let _fm: i64 = nx_scratch_save()
116 _p256_solinas_reduce_fast(out_8, c_16)
117 nx_scratch_restore(_fm)
118 return 0
119}