nx_x25519_wasm.nx source
↩ module page · 551 lines · 21127 B
1// nx_x25519_wasm.nx -- Curve25519 ECDH (RFC 7748) for WAT target.
2//
3// Field: GF(2^255 - 19). Curve: y^2 = x^3 + 486662*x^2 + x.
4// Only the x-coordinate is needed for the ladder.
5//
6// Field representation: 10 limbs alternating 26 / 25 bits = 255 bits.
7// limb[0] -> bits 0..25 (26-bit slot, but only low 26 used)
8// limb[1] -> bits 26..50 (25 bits)
9// limb[2] -> bits 51..76 (26 bits)
10// ...alternating up to limb[9] which is the high 25 bits.
11//
12// This split (donna 32-bit-style) means each per-limb multiplication
13// result fits comfortably in i64 (max 2^51 ish). Carry chain
14// distributes the overflow back into 26/25-bit form after each round.
15//
16// Reduction: 2^255 mod (2^255 - 19) = 19. Any bit at position 255+
17// folds back by multiplying by 19.
18//
19// API for the embedder:
20//
21// nx_x25519_scalarmult(scalar_ptr, point_ptr, scratch_ptr, out_ptr) -> i64
22// scalar_ptr -- 32 bytes (LE). Clamped per RFC 7748 §5 inside.
23// point_ptr -- 32 bytes (LE; the u-coord of the input point).
24// scratch_ptr-- >= 1024 bytes scratch area
25// out_ptr -- 32 bytes (the resulting u-coord)
26//
27// Verified against RFC 7748 §5.2 test vectors.
28//
29// license_tier: INDEPENDENT_REDERIVE
30// genealogy_id: international-research-sources/ietf/rfc_7748 +
31// daniel_j_bernstein/curve25519_donna
32// lineage_id: nishi_x25519_wasm_q11
33
34const M25: i64 = 0x1ffffff // 2^25 - 1
35const M26: i64 = 0x3ffffff // 2^26 - 1
36
37// Limb buffer layout: 10 i64 limbs at offset (idx*8) in u8 buffer.
38func _fe_get(fe: *u8, i: i64) -> i64 {
39 let off: i64 = i * 8
40 return (fe[off] as i64) |
41 ((fe[off + 1] as i64) << 8) |
42 ((fe[off + 2] as i64) << 16) |
43 ((fe[off + 3] as i64) << 24) |
44 ((fe[off + 4] as i64) << 32) |
45 ((fe[off + 5] as i64) << 40) |
46 ((fe[off + 6] as i64) << 48) |
47 ((fe[off + 7] as i64) << 56)
48}
49func _fe_set(fe: *u8, i: i64, v: i64) -> i64 {
50 let off: i64 = i * 8
51 fe[off] = v & 0xFF
52 fe[off + 1] = (v >> 8) & 0xFF
53 fe[off + 2] = (v >> 16) & 0xFF
54 fe[off + 3] = (v >> 24) & 0xFF
55 fe[off + 4] = (v >> 32) & 0xFF
56 fe[off + 5] = (v >> 40) & 0xFF
57 fe[off + 6] = (v >> 48) & 0xFF
58 fe[off + 7] = (v >> 56) & 0xFF
59 return 0
60}
61
62// Unpack 32 bytes (LE) into 10 limbs.
63func _fe_unpack(fe: *u8, bytes: *u8) -> i64 {
64 // We use the standard donna trick: read 4-byte chunks then re-slice.
65 // Bit positions: 0,26,51,77,102,128,153,179,204,230
66 let w0: i64 = (bytes[0] as i64) | ((bytes[1] as i64) << 8) |
67 ((bytes[2] as i64) << 16) | ((bytes[3] as i64) << 24)
68 let w1: i64 = (bytes[4] as i64) | ((bytes[5] as i64) << 8) |
69 ((bytes[6] as i64) << 16) | ((bytes[7] as i64) << 24)
70 let w2: i64 = (bytes[8] as i64) | ((bytes[9] as i64) << 8) |
71 ((bytes[10] as i64) << 16) | ((bytes[11] as i64) << 24)
72 let w3: i64 = (bytes[12] as i64) | ((bytes[13] as i64) << 8) |
73 ((bytes[14] as i64) << 16) | ((bytes[15] as i64) << 24)
74 let w4: i64 = (bytes[16] as i64) | ((bytes[17] as i64) << 8) |
75 ((bytes[18] as i64) << 16) | ((bytes[19] as i64) << 24)
76 let w5: i64 = (bytes[20] as i64) | ((bytes[21] as i64) << 8) |
77 ((bytes[22] as i64) << 16) | ((bytes[23] as i64) << 24)
78 let w6: i64 = (bytes[24] as i64) | ((bytes[25] as i64) << 8) |
79 ((bytes[26] as i64) << 16) | ((bytes[27] as i64) << 24)
80 let w7: i64 = (bytes[28] as i64) | ((bytes[29] as i64) << 8) |
81 ((bytes[30] as i64) << 16) | (((bytes[31] & 0x7F) as i64) << 24)
82 // Mask the top bit per RFC 7748 (high bit of byte 31 ignored).
83
84 // Slice the 256-bit value (lo to hi: w0..w7) into 26,25,26,25,26,25,26,25,26,25 bit limbs.
85 _fe_set(fe, 0, w0 & M26)
86 _fe_set(fe, 1, ((w0 >> 26) | (w1 << 6)) & M25)
87 _fe_set(fe, 2, ((w1 >> 19) | (w2 << 13)) & M26)
88 _fe_set(fe, 3, ((w2 >> 13) | (w3 << 19)) & M25)
89 _fe_set(fe, 4, ((w3 >> 6) ) & M26)
90 _fe_set(fe, 5, w4 & M25)
91 _fe_set(fe, 6, ((w4 >> 25) | (w5 << 7)) & M26)
92 _fe_set(fe, 7, ((w5 >> 19) | (w6 << 13)) & M25)
93 _fe_set(fe, 8, ((w6 >> 12) | (w7 << 20)) & M26)
94 _fe_set(fe, 9, (w7 >> 6) & M25)
95 return 0
96}
97
98// Pack 10 limbs into 32 bytes (LE), fully reduced.
99func _fe_pack(bytes: *u8, fe: *u8) -> i64 {
100 // First do a final carry-reduce sweep so each limb is canonical.
101 // Even-index limbs are 26-bit; odd-index limbs are 25-bit.
102 var c: i64 = 0
103 var i: i64 = 0
104 while i < 10 {
105 let v: i64 = _fe_get(fe, i) + c
106 var bits: i64 = 26
107 var mask: i64 = M26
108 if (i & 1) == 1 { bits = 25; mask = M25 }
109 _fe_set(fe, i, v & mask)
110 c = v >> bits
111 i = i + 1
112 }
113 // Final carry wraps via 2^255 -> 19.
114 let h0: i64 = _fe_get(fe, 0) + c * 19
115 var c2: i64 = h0 >> 26
116 _fe_set(fe, 0, h0 & M26)
117 let h1: i64 = _fe_get(fe, 1) + c2
118 _fe_set(fe, 1, h1)
119
120 // Now check if result >= p (= 2^255 - 19). If so, subtract p.
121 // Try adding 19 and seeing if carry propagates all the way.
122 // For simplicity we do a conditional subtract.
123 var g0: i64 = _fe_get(fe, 0) + 19
124 var cc: i64 = g0 >> 26; g0 = g0 & M26
125 var g1: i64 = _fe_get(fe, 1) + cc; cc = g1 >> 25; g1 = g1 & M25
126 var g2: i64 = _fe_get(fe, 2) + cc; cc = g2 >> 26; g2 = g2 & M26
127 var g3: i64 = _fe_get(fe, 3) + cc; cc = g3 >> 25; g3 = g3 & M25
128 var g4: i64 = _fe_get(fe, 4) + cc; cc = g4 >> 26; g4 = g4 & M26
129 var g5: i64 = _fe_get(fe, 5) + cc; cc = g5 >> 25; g5 = g5 & M25
130 var g6: i64 = _fe_get(fe, 6) + cc; cc = g6 >> 26; g6 = g6 & M26
131 var g7: i64 = _fe_get(fe, 7) + cc; cc = g7 >> 25; g7 = g7 & M25
132 var g8: i64 = _fe_get(fe, 8) + cc; cc = g8 >> 26; g8 = g8 & M26
133 var g9: i64 = _fe_get(fe, 9) + cc - (1 << 25)
134 // If g9 >= 0, the value was >= p and we should use g; else use original fe.
135 var mask: i64 = 0
136 if g9 >= 0 { mask = -1 }
137 let f0: i64 = (_fe_get(fe, 0) & ~mask) | (g0 & mask)
138 let f1: i64 = (_fe_get(fe, 1) & ~mask) | (g1 & mask)
139 let f2: i64 = (_fe_get(fe, 2) & ~mask) | (g2 & mask)
140 let f3: i64 = (_fe_get(fe, 3) & ~mask) | (g3 & mask)
141 let f4: i64 = (_fe_get(fe, 4) & ~mask) | (g4 & mask)
142 let f5: i64 = (_fe_get(fe, 5) & ~mask) | (g5 & mask)
143 let f6: i64 = (_fe_get(fe, 6) & ~mask) | (g6 & mask)
144 let f7: i64 = (_fe_get(fe, 7) & ~mask) | (g7 & mask)
145 let f8: i64 = (_fe_get(fe, 8) & ~mask) | (g8 & mask)
146 let f9: i64 = (_fe_get(fe, 9) & ~mask) | (g9 & mask)
147
148 // Recompose as 32 LE bytes.
149 // bit positions for limbs: 0, 26, 51, 77, 102, 128, 153, 179, 204, 230
150 let bb0: i64 = f0 | (f1 << 26)
151 let bb1: i64 = (f1 >> 6) | (f2 << 19)
152 let bb2: i64 = (f2 >> 13) | (f3 << 13)
153 let bb3: i64 = (f3 >> 19) | (f4 << 6)
154 let bb4: i64 = f5 | (f6 << 25)
155 let bb5: i64 = (f6 >> 7) | (f7 << 19)
156 let bb6: i64 = (f7 >> 13) | (f8 << 12)
157 let bb7: i64 = (f8 >> 20) | (f9 << 6)
158 // Write 32 bytes as 8 LE i32s (from bb0..bb7).
159 var i2: i64 = 0
160 while i2 < 8 {
161 var v: i64 = 0
162 if i2 == 0 { v = bb0 } else { if i2 == 1 { v = bb1 } else { if i2 == 2 { v = bb2 } else { if i2 == 3 { v = bb3 } else { if i2 == 4 { v = bb4 } else { if i2 == 5 { v = bb5 } else { if i2 == 6 { v = bb6 } else { v = bb7 } } } } } } }
163 bytes[i2 * 4] = v & 0xFF
164 bytes[i2 * 4 + 1] = (v >> 8) & 0xFF
165 bytes[i2 * 4 + 2] = (v >> 16) & 0xFF
166 bytes[i2 * 4 + 3] = (v >> 24) & 0xFF
167 i2 = i2 + 1
168 }
169 return 0
170}
171
172// fe_copy.
173func _fe_copy(dst: *u8, src: *u8) -> i64 {
174 var i: i64 = 0
175 while i < 10 { _fe_set(dst, i, _fe_get(src, i)); i = i + 1 }
176 return 0
177}
178
179// fe_add: out = a + b (no reduction; caller may need to reduce eventually)
180func _fe_add(out: *u8, a: *u8, b: *u8) -> i64 {
181 var i: i64 = 0
182 while i < 10 {
183 _fe_set(out, i, _fe_get(a, i) + _fe_get(b, i))
184 i = i + 1
185 }
186 return 0
187}
188
189// fe_sub: out = a - b + (2*p) to avoid negatives; then carry.
190// 2p in limb form has each limb = 2 * (2^26 - 38)/... -- simpler to
191// add 2p as a pre-computed constant per limb based on canonical form.
192// We use the donna approach: add a multiple of p that's large enough
193// to ensure positivity. For our limb widths the easy constant is:
194// limb[0] += 0x7ffffda (= 2*(2^26 - 19))
195// limb[i>0] += 0x7fffffe (= 2*(2^25 - 1)) for i odd
196// or 0xffffffe for i even
197// then subtract b limbwise.
198func _fe_sub(out: *u8, a: *u8, b: *u8) -> i64 {
199 let off0: i64 = 0x7ffffda
200 let off_odd: i64 = 0x3fffffe // 2 * (2^25 - 1)
201 let off_even: i64 = 0x7fffffe // 2 * (2^26 - 1)
202 var i: i64 = 0
203 while i < 10 {
204 var addv: i64 = 0
205 if i == 0 { addv = off0 }
206 else { if (i & 1) == 0 { addv = off_even } else { addv = off_odd } }
207 _fe_set(out, i, _fe_get(a, i) + addv - _fe_get(b, i))
208 i = i + 1
209 }
210 return 0
211}
212
213// fe_mul: out = a * b mod p (schoolbook with 10x10 partials + 2*reduce)
214func _fe_mul(out: *u8, a_in: *u8, b_in: *u8) -> i64 {
215 // Read all 10 limbs of each into locals.
216 let a0: i64 = _fe_get(a_in, 0)
217 let a1: i64 = _fe_get(a_in, 1)
218 let a2: i64 = _fe_get(a_in, 2)
219 let a3: i64 = _fe_get(a_in, 3)
220 let a4: i64 = _fe_get(a_in, 4)
221 let a5: i64 = _fe_get(a_in, 5)
222 let a6: i64 = _fe_get(a_in, 6)
223 let a7: i64 = _fe_get(a_in, 7)
224 let a8: i64 = _fe_get(a_in, 8)
225 let a9: i64 = _fe_get(a_in, 9)
226 let b0: i64 = _fe_get(b_in, 0)
227 let b1: i64 = _fe_get(b_in, 1)
228 let b2: i64 = _fe_get(b_in, 2)
229 let b3: i64 = _fe_get(b_in, 3)
230 let b4: i64 = _fe_get(b_in, 4)
231 let b5: i64 = _fe_get(b_in, 5)
232 let b6: i64 = _fe_get(b_in, 6)
233 let b7: i64 = _fe_get(b_in, 7)
234 let b8: i64 = _fe_get(b_in, 8)
235 let b9: i64 = _fe_get(b_in, 9)
236
237 // The 19-precomputed b for cross-products (folds high bits back).
238 let b1_19: i64 = b1 * 19
239 let b2_19: i64 = b2 * 19
240 let b3_19: i64 = b3 * 19
241 let b4_19: i64 = b4 * 19
242 let b5_19: i64 = b5 * 19
243 let b6_19: i64 = b6 * 19
244 let b7_19: i64 = b7 * 19
245 let b8_19: i64 = b8 * 19
246 let b9_19: i64 = b9 * 19
247 // Also need a_odd * 2 for some cross products (donna pattern):
248 let a1_2: i64 = a1 * 2
249 let a3_2: i64 = a3 * 2
250 let a5_2: i64 = a5 * 2
251 let a7_2: i64 = a7 * 2
252 let a9_2: i64 = a9 * 2
253
254 // Schoolbook with reduce-by-19 for crossings of 2^255.
255 // h[i] = sum over j+k=i of a[j]*b[k]; with j+k>=10 we fold by *19.
256 // Even-index limbs get double weight on odd*odd cross products
257 // because of the 26+25 spacing trick.
258 // Single-line expressions because the parser doesn't accept leading-+ continuation.
259 let h0: i64 = a0*b0 + a1_2*b9_19 + a2*b8_19 + a3_2*b7_19 + a4*b6_19 + a5_2*b5_19 + a6*b4_19 + a7_2*b3_19 + a8*b2_19 + a9_2*b1_19
260 let h1: i64 = a0*b1 + a1*b0 + a2*b9_19 + a3*b8_19 + a4*b7_19 + a5*b6_19 + a6*b5_19 + a7*b4_19 + a8*b3_19 + a9*b2_19
261 let h2: i64 = a0*b2 + a1_2*b1 + a2*b0 + a3_2*b9_19 + a4*b8_19 + a5_2*b7_19 + a6*b6_19 + a7_2*b5_19 + a8*b4_19 + a9_2*b3_19
262 let h3: i64 = a0*b3 + a1*b2 + a2*b1 + a3*b0 + a4*b9_19 + a5*b8_19 + a6*b7_19 + a7*b6_19 + a8*b5_19 + a9*b4_19
263 let h4: i64 = a0*b4 + a1_2*b3 + a2*b2 + a3_2*b1 + a4*b0 + a5_2*b9_19 + a6*b8_19 + a7_2*b7_19 + a8*b6_19 + a9_2*b5_19
264 let h5: i64 = a0*b5 + a1*b4 + a2*b3 + a3*b2 + a4*b1 + a5*b0 + a6*b9_19 + a7*b8_19 + a8*b7_19 + a9*b6_19
265 let h6: i64 = a0*b6 + a1_2*b5 + a2*b4 + a3_2*b3 + a4*b2 + a5_2*b1 + a6*b0 + a7_2*b9_19 + a8*b8_19 + a9_2*b7_19
266 let h7: i64 = a0*b7 + a1*b6 + a2*b5 + a3*b4 + a4*b3 + a5*b2 + a6*b1 + a7*b0 + a8*b9_19 + a9*b8_19
267 let h8: i64 = a0*b8 + a1_2*b7 + a2*b6 + a3_2*b5 + a4*b4 + a5_2*b3 + a6*b2 + a7_2*b1 + a8*b0 + a9_2*b9_19
268 let h9: i64 = a0*b9 + a1*b8 + a2*b7 + a3*b6 + a4*b5 + a5*b4 + a6*b3 + a7*b2 + a8*b1 + a9*b0
269
270 // Carry chain twice (donna does 2 passes for full reduction).
271 var c: i64 = 0
272 var H0: i64 = h0 + c; c = H0 >> 26; H0 = H0 & M26
273 var H1: i64 = h1 + c; c = H1 >> 25; H1 = H1 & M25
274 var H2: i64 = h2 + c; c = H2 >> 26; H2 = H2 & M26
275 var H3: i64 = h3 + c; c = H3 >> 25; H3 = H3 & M25
276 var H4: i64 = h4 + c; c = H4 >> 26; H4 = H4 & M26
277 var H5: i64 = h5 + c; c = H5 >> 25; H5 = H5 & M25
278 var H6: i64 = h6 + c; c = H6 >> 26; H6 = H6 & M26
279 var H7: i64 = h7 + c; c = H7 >> 25; H7 = H7 & M25
280 var H8: i64 = h8 + c; c = H8 >> 26; H8 = H8 & M26
281 var H9: i64 = h9 + c; c = H9 >> 25; H9 = H9 & M25
282 H0 = H0 + c * 19
283 c = H0 >> 26; H0 = H0 & M26
284 H1 = H1 + c
285
286 _fe_set(out, 0, H0); _fe_set(out, 1, H1); _fe_set(out, 2, H2)
287 _fe_set(out, 3, H3); _fe_set(out, 4, H4); _fe_set(out, 5, H5)
288 _fe_set(out, 6, H6); _fe_set(out, 7, H7); _fe_set(out, 8, H8)
289 _fe_set(out, 9, H9)
290 return 0
291}
292
293// fe_sqr: out = a * a mod p (could specialize but using fe_mul is fine for size)
294func _fe_sqr(out: *u8, a: *u8) -> i64 {
295 return _fe_mul(out, a, a)
296}
297
298// fe_mul_a24: out = a * 121665 mod p (used in Montgomery ladder)
299func _fe_mul_a24(out: *u8, a: *u8) -> i64 {
300 let A24: i64 = 121665
301 // Multiply each limb by 121665, then carry.
302 let h0: i64 = _fe_get(a, 0) * A24
303 let h1: i64 = _fe_get(a, 1) * A24
304 let h2: i64 = _fe_get(a, 2) * A24
305 let h3: i64 = _fe_get(a, 3) * A24
306 let h4: i64 = _fe_get(a, 4) * A24
307 let h5: i64 = _fe_get(a, 5) * A24
308 let h6: i64 = _fe_get(a, 6) * A24
309 let h7: i64 = _fe_get(a, 7) * A24
310 let h8: i64 = _fe_get(a, 8) * A24
311 let h9: i64 = _fe_get(a, 9) * A24
312 var c: i64 = 0
313 var H0: i64 = h0 + c; c = H0 >> 26; H0 = H0 & M26
314 var H1: i64 = h1 + c; c = H1 >> 25; H1 = H1 & M25
315 var H2: i64 = h2 + c; c = H2 >> 26; H2 = H2 & M26
316 var H3: i64 = h3 + c; c = H3 >> 25; H3 = H3 & M25
317 var H4: i64 = h4 + c; c = H4 >> 26; H4 = H4 & M26
318 var H5: i64 = h5 + c; c = H5 >> 25; H5 = H5 & M25
319 var H6: i64 = h6 + c; c = H6 >> 26; H6 = H6 & M26
320 var H7: i64 = h7 + c; c = H7 >> 25; H7 = H7 & M25
321 var H8: i64 = h8 + c; c = H8 >> 26; H8 = H8 & M26
322 var H9: i64 = h9 + c; c = H9 >> 25; H9 = H9 & M25
323 H0 = H0 + c * 19
324 _fe_set(out, 0, H0); _fe_set(out, 1, H1); _fe_set(out, 2, H2)
325 _fe_set(out, 3, H3); _fe_set(out, 4, H4); _fe_set(out, 5, H5)
326 _fe_set(out, 6, H6); _fe_set(out, 7, H7); _fe_set(out, 8, H8)
327 _fe_set(out, 9, H9)
328 return 0
329}
330
331// fe_invert: out = 1/a mod p via Fermat's little theorem a^(p-2).
332// p-2 = 2^255 - 21. Standard addition chain ~ 254 squares + 11 mults.
333// We use the donna addition chain.
334//
335// Layout of working FEs in scratch (each 10 limbs = 80 bytes):
336// z2 at scratch+0, z9 at +80, z11 at +160, z2_5_0 at +240,
337// z2_10_0 at +320, z2_20_0 at +400, z2_50_0 at +480,
338// z2_100_0 at +560, t0 at +640, t1 at +720.
339func _fe_invert(out: *u8, z: *u8, scratch: *u8) -> i64 {
340 let z2: *u8 = scratch
341 let z9: *u8 = (scratch as i64 + 80) as *u8
342 let z11: *u8 = (scratch as i64 + 160) as *u8
343 let z2_5_0: *u8 = (scratch as i64 + 240) as *u8
344 let z2_10_0: *u8 = (scratch as i64 + 320) as *u8
345 let z2_20_0: *u8 = (scratch as i64 + 400) as *u8
346 let z2_50_0: *u8 = (scratch as i64 + 480) as *u8
347 let z2_100_0: *u8 = (scratch as i64 + 560) as *u8
348 let t0: *u8 = (scratch as i64 + 640) as *u8
349 let t1: *u8 = (scratch as i64 + 720) as *u8
350
351 _fe_sqr(z2, z) // 2
352 _fe_sqr(t1, z2) // 4
353 _fe_sqr(t0, t1) // 8
354 _fe_mul(z9, t0, z) // 9
355 _fe_mul(z11, z9, z2) // 11
356 _fe_sqr(t0, z11) // 22
357 _fe_mul(z2_5_0, t0, z9) // 2^5-2^0 = 31
358
359 _fe_sqr(t0, z2_5_0) // 2^6-2^1
360 _fe_sqr(t1, t0) // 2^7-2^2
361 _fe_sqr(t0, t1) // 2^8-2^3
362 _fe_sqr(t1, t0) // 2^9-2^4
363 _fe_sqr(t0, t1) // 2^10-2^5
364 _fe_mul(z2_10_0, t0, z2_5_0) // 2^10-2^0
365
366 _fe_sqr(t0, z2_10_0)
367 _fe_sqr(t1, t0)
368 var i: i64 = 1
369 while i < 5 {
370 _fe_sqr(t0, t1)
371 _fe_sqr(t1, t0)
372 i = i + 1
373 }
374 _fe_mul(z2_20_0, t1, z2_10_0) // 2^20-2^0
375
376 _fe_sqr(t0, z2_20_0)
377 _fe_sqr(t1, t0)
378 i = 1
379 while i < 10 {
380 _fe_sqr(t0, t1)
381 _fe_sqr(t1, t0)
382 i = i + 1
383 }
384 _fe_mul(t0, t1, z2_20_0) // 2^40-2^0
385
386 _fe_sqr(t1, t0)
387 _fe_sqr(t0, t1)
388 i = 1
389 while i < 5 {
390 _fe_sqr(t1, t0)
391 _fe_sqr(t0, t1)
392 i = i + 1
393 }
394 _fe_mul(z2_50_0, t0, z2_10_0) // 2^50-2^0
395
396 _fe_sqr(t0, z2_50_0)
397 _fe_sqr(t1, t0)
398 i = 1
399 while i < 25 {
400 _fe_sqr(t0, t1)
401 _fe_sqr(t1, t0)
402 i = i + 1
403 }
404 _fe_mul(z2_100_0, t1, z2_50_0) // 2^100-2^0
405
406 _fe_sqr(t1, z2_100_0)
407 _fe_sqr(t0, t1)
408 i = 1
409 while i < 50 {
410 _fe_sqr(t1, t0)
411 _fe_sqr(t0, t1)
412 i = i + 1
413 }
414 _fe_mul(t1, t0, z2_100_0) // 2^200-2^0
415
416 _fe_sqr(t0, t1)
417 _fe_sqr(t1, t0)
418 i = 1
419 while i < 25 {
420 _fe_sqr(t0, t1)
421 _fe_sqr(t1, t0)
422 i = i + 1
423 }
424 _fe_mul(t0, t1, z2_50_0) // 2^250-2^0
425
426 _fe_sqr(t1, t0)
427 _fe_sqr(t0, t1)
428 _fe_sqr(t1, t0)
429 _fe_sqr(t0, t1)
430 _fe_sqr(t1, t0) // 2^255-2^5
431
432 _fe_mul(out, t1, z11) // 2^255-21 = p-2
433 return 0
434}
435
436// Conditional swap: if c == 1, swap a and b; if c == 0, leave both.
437// We use the standard XOR-mask trick to keep it side-channel friendly.
438func _fe_cswap(a: *u8, b: *u8, c: i64) -> i64 {
439 var mask: i64 = 0
440 if c == 1 { mask = -1 }
441 var i: i64 = 0
442 while i < 10 {
443 let av: i64 = _fe_get(a, i)
444 let bv: i64 = _fe_get(b, i)
445 let xv: i64 = (av ^ bv) & mask
446 _fe_set(a, i, av ^ xv)
447 _fe_set(b, i, bv ^ xv)
448 i = i + 1
449 }
450 return 0
451}
452
453// X25519 scalar multiplication: out = scalar * point on Curve25519.
454// scratch_ptr layout (>= 1024 bytes):
455// 0..79: x1 (input u)
456// 80..159: x2
457// 160..239: z2
458// 240..319: x3
459// 320..399: z3
460// 400..479: tmp_a
461// 480..559: tmp_b
462// 560..639: tmp_c
463// 640..719: tmp_d
464// 720..799: tmp_e
465// 800+: invert scratch (>=800 more bytes)
466func nx_x25519_scalarmult(scalar_ptr: *u8, point_ptr: *u8,
467 scratch_ptr: *u8, out_ptr: *u8) -> i64 {
468 // Clamp the scalar per RFC 7748 §5.
469 var clamped: *u8 = (scratch_ptr as i64 + 960) as *u8
470 var i: i64 = 0
471 while i < 32 { clamped[i] = scalar_ptr[i]; i = i + 1 }
472 clamped[0] = clamped[0] & 0xF8
473 clamped[31] = clamped[31] & 0x7F
474 clamped[31] = clamped[31] | 0x40
475
476 let x1: *u8 = scratch_ptr
477 let x2: *u8 = (scratch_ptr as i64 + 80) as *u8
478 let z2: *u8 = (scratch_ptr as i64 + 160) as *u8
479 let x3: *u8 = (scratch_ptr as i64 + 240) as *u8
480 let z3: *u8 = (scratch_ptr as i64 + 320) as *u8
481 let A: *u8 = (scratch_ptr as i64 + 400) as *u8
482 let B: *u8 = (scratch_ptr as i64 + 480) as *u8
483 let AA: *u8 = (scratch_ptr as i64 + 560) as *u8
484 let BB: *u8 = (scratch_ptr as i64 + 640) as *u8
485 let E: *u8 = (scratch_ptr as i64 + 720) as *u8
486 let CB: *u8 = (scratch_ptr as i64 + 880) as *u8 // 880..959
487
488 _fe_unpack(x1, point_ptr)
489
490 // x2 = 1, z2 = 0, x3 = x1, z3 = 1
491 var k: i64 = 0
492 while k < 10 { _fe_set(x2, k, 0); _fe_set(z2, k, 0); _fe_set(z3, k, 0); k = k + 1 }
493 _fe_set(x2, 0, 1)
494 _fe_copy(x3, x1)
495 _fe_set(z3, 0, 1)
496
497 // Montgomery ladder: 255 steps, bits 254 down to 0.
498 var swap: i64 = 0
499 var pos: i64 = 254
500 while pos >= 0 {
501 let bit: i64 = (clamped[pos / 8] >> (pos & 7)) & 1
502 swap = swap ^ bit
503 _fe_cswap(x2, x3, swap)
504 _fe_cswap(z2, z3, swap)
505 swap = bit
506
507 // A = x2 + z2
508 _fe_add(A, x2, z2)
509 // AA = A^2
510 _fe_sqr(AA, A)
511 // B = x2 - z2
512 _fe_sub(B, x2, z2)
513 // BB = B^2
514 _fe_sqr(BB, B)
515 // E = AA - BB
516 _fe_sub(E, AA, BB)
517 // C = x3 + z3 (stored in z2 temporarily)
518 _fe_add(z2, x3, z3)
519 // D = x3 - z3 (stored in z3 temporarily)
520 _fe_sub(z3, x3, z3)
521 // DA = D * A (stored in z3)
522 _fe_mul(z3, z3, A)
523 // CB = C * B
524 _fe_mul(CB, z2, B)
525 // x3 = (DA + CB)^2
526 _fe_add(x3, z3, CB)
527 _fe_sqr(x3, x3)
528 // z3 = x1 * (DA - CB)^2
529 _fe_sub(z2, z3, CB)
530 _fe_sqr(z2, z2)
531 _fe_mul(z3, z2, x1)
532 // x2 = AA * BB
533 _fe_mul(x2, AA, BB)
534 // z2 = E * (AA + 121665*E)
535 _fe_mul_a24(z2, E)
536 _fe_add(z2, z2, AA)
537 _fe_mul(z2, z2, E)
538
539 pos = pos - 1
540 }
541 _fe_cswap(x2, x3, swap)
542 _fe_cswap(z2, z3, swap)
543
544 // Result = x2 / z2 = x2 * z2^(-1)
545 let inv_scratch: *u8 = (scratch_ptr as i64 + 800) as *u8 // share 800..879 + use scratch below 1024
546 let invz: *u8 = (scratch_ptr as i64 + 880) as *u8 // reuse CB
547 _fe_invert(invz, z2, inv_scratch)
548 _fe_mul(x2, x2, invz)
549 _fe_pack(out_ptr, x2)
550 return 0
551}