x25519.nx source
↩ module page · 485 lines · 19358 B
1// x25519.nx -- Bernstein's Curve25519 X25519 (RFC 7748).
2//
3// Elliptic-curve Diffie-Hellman on Curve25519 in Montgomery form.
4// Primary classical KEX for TLS 1.3, SSH, Signal, WireGuard, and
5// every modern protocol that cares about correctness. Hybrid-
6// deploys alongside ML-KEM-768 for post-quantum TLS 1.3 (RFC 9578
7// X25519Kyber768Draft00).
8//
9// Field: GF(p) with p = 2^255 - 19.
10// Curve equation: y^2 = x^3 + 486662*x^2 + x (Montgomery form).
11// Scalar input: 32 bytes, clamped per RFC 7748 §5.
12// u-coordinate input: 32 bytes, high bit cleared per §5.
13// Output: 32-byte shared secret.
14//
15// Representation: 10 x 25.5-bit limbs (Bernstein ref10 style).
16// Offsets: 0, 26, 51, 77, 102, 128, 153, 179, 204, 230 (bits).
17// Limb widths alternate 26 / 25 bits so 10 * 25.5 = 255 bits.
18// Each limb fits in i64 comfortably (26 bits plus headroom).
19// Multiplication of two limbs: 26 + 26 = 52 bits -- well within
20// i64 range. Accumulating up to 10 partial products per output
21// digit: ~56 bits peak -- still within i64.
22//
23// Why 10x25.5 and not 5x51:
24// NishiLang has no u128 / 128-bit multiply. 5x51-bit limbs give
25// 102-bit products that don't fit in i64. The 10x25.5 layout
26// avoids this entirely; every intermediate fits in a plain i64.
27//
28// Constant-time discipline:
29// X25519 MUST run in time independent of scalar/u bits (leaking
30// would recover the private key). The Montgomery ladder + cswap
31// achieves this naturally: same instruction sequence regardless
32// of bit values; conditional swap via bitmask select rather than
33// branch. No table lookups on secret bits. No divisions (used
34// only in fe_invert, which runs over a fixed chain of squarings
35// and multiplications regardless of input).
36//
37// Invariants (enforced, not hoped):
38// XC1 Scalar clamping applied once at entry per RFC 7748 §5:
39// scalar[0] &= 0xf8; scalar[31] &= 0x7f; scalar[31] |= 0x40.
40// XC2 u-coordinate high bit cleared at entry (RFC 7748 §5).
41// XC3 All field operations mod p are constant-time. cswap
42// runs in fixed time via bitmask; ladder body has identical
43// instruction count regardless of bit value.
44// XC4 fe_invert uses a fixed exponentiation chain for p-2
45// (Fermat's little theorem), not binary-method with early
46// exit on low bits of the exponent.
47//
48// References:
49// Bernstein 2006, "Curve25519: new Diffie-Hellman speed records"
50// RFC 7748 (2016), "Elliptic Curves for Security"
51// djb ref10 implementation in SUPERCOP (canonical source)
52// Langley+Hamburg+Turner 2014 IETF draft
53
54import "syscalls.nx"
55
56// ---- field element construction ----------------------------------
57//
58// A field element is 10 i64 words stored contiguously. We pass
59// pointers around rather than structs for explicit sizing.
60
61const FE_LIMBS: i64 = 10
62
63func fe_alloc() -> *i64 {
64 let raw: *u8 = sys_mmap(FE_LIMBS * 8)
65 return raw as *i64
66}
67
68func fe_copy(dst: *i64, src: *i64) -> i64 {
69 var i: i64 = 0
70 while i < FE_LIMBS { dst[i] = src[i]; i = i + 1 }
71 return 0
72}
73
74func fe_zero(h: *i64) -> i64 {
75 var i: i64 = 0
76 while i < FE_LIMBS { h[i] = 0; i = i + 1 }
77 return 0
78}
79
80func fe_one(h: *i64) -> i64 {
81 fe_zero(h)
82 h[0] = 1
83 return 0
84}
85
86// ---- field addition / subtraction --------------------------------
87//
88// Limb-wise; result limbs may temporarily exceed 26/25 bits. A
89// subsequent fe_mul / fe_sqr call carries out the reduction during
90// its own carry-propagation step.
91
92func fe_add(h: *i64, f: *i64, g: *i64) -> i64 {
93 var i: i64 = 0
94 while i < FE_LIMBS { h[i] = f[i] + g[i]; i = i + 1 }
95 return 0
96}
97
98func fe_sub(h: *i64, f: *i64, g: *i64) -> i64 {
99 var i: i64 = 0
100 while i < FE_LIMBS { h[i] = f[i] - g[i]; i = i + 1 }
101 return 0
102}
103
104// ---- multiplication and squaring ---------------------------------
105//
106// Schoolbook 10x10 multiplication, followed by reduction of the
107// 19 output digits back into 10 limbs using the modular identity
108// 2^255 ≡ 19 (mod p). Digit positions 10..18 wrap around: h[i+10]
109// becomes a contribution of 19 * h[i+10] into h[i].
110//
111// Because odd-position limbs sit at half-bit offsets, products that
112// span two odd positions (e.g. f[1]*g[1] at bit 26+26=52, which is
113// 1 bit past limb-2's 51-bit base) pick up a factor of 2 in the
114// accumulation. This is the classic Bernstein shift: we double
115// specific cross-products to absorb the half-offset.
116
117func fe_mul(h: *i64, f: *i64, g: *i64) -> i64 {
118 // Pre-multiply odd-position limbs of g by 2 -- this captures
119 // the half-limb-offset doubling in one precomputation rather
120 // than scattering *2 across the 100 partial products.
121 let g1_2: i64 = 2 * g[1]
122 let g3_2: i64 = 2 * g[3]
123 let g5_2: i64 = 2 * g[5]
124 let g7_2: i64 = 2 * g[7]
125 let g9_2: i64 = 2 * g[9]
126
127 // 19 * g[i] for the wraparound: digit i+10 contributes 19*g
128 // into digit i. Precompute so each partial uses a plain value.
129 let g1_19: i64 = 19 * g[1]
130 let g2_19: i64 = 19 * g[2]
131 let g3_19: i64 = 19 * g[3]
132 let g4_19: i64 = 19 * g[4]
133 let g5_19: i64 = 19 * g[5]
134 let g6_19: i64 = 19 * g[6]
135 let g7_19: i64 = 19 * g[7]
136 let g8_19: i64 = 19 * g[8]
137 let g9_19: i64 = 19 * g[9]
138
139 // 38 * odd = 19 * (2 * odd), used when BOTH the wrap AND the
140 // half-offset doubling apply.
141 let g1_38: i64 = 38 * g[1]
142 let g3_38: i64 = 38 * g[3]
143 let g5_38: i64 = 38 * g[5]
144 let g7_38: i64 = 38 * g[7]
145 let g9_38: i64 = 38 * g[9]
146
147 // 10 output limbs; schoolbook expansion of (f0 + f1*x + ... +
148 // f9*x^9) * (g0 + g1*x + ...) mod (x^10 - ...). NishiLang's
149 // auto-semicolon-at-line-break forces one line per expression.
150 let h0: i64 = f[0]*g[0] + f[1]*g9_38 + f[2]*g8_19 + f[3]*g7_38 + f[4]*g6_19 + f[5]*g5_38 + f[6]*g4_19 + f[7]*g3_38 + f[8]*g2_19 + f[9]*g1_38
151 let h1: i64 = f[0]*g[1] + f[1]*g[0] + f[2]*g9_19 + f[3]*g8_19 + f[4]*g7_19 + f[5]*g6_19 + f[6]*g5_19 + f[7]*g4_19 + f[8]*g3_19 + f[9]*g2_19
152 let h2: i64 = f[0]*g[2] + f[1]*g1_2 + f[2]*g[0] + f[3]*g9_38 + f[4]*g8_19 + f[5]*g7_38 + f[6]*g6_19 + f[7]*g5_38 + f[8]*g4_19 + f[9]*g3_38
153 let h3: i64 = f[0]*g[3] + f[1]*g[2] + f[2]*g[1] + f[3]*g[0] + f[4]*g9_19 + f[5]*g8_19 + f[6]*g7_19 + f[7]*g6_19 + f[8]*g5_19 + f[9]*g4_19
154 let h4: i64 = f[0]*g[4] + f[1]*g3_2 + f[2]*g[2] + f[3]*g1_2 + f[4]*g[0] + f[5]*g9_38 + f[6]*g8_19 + f[7]*g7_38 + f[8]*g6_19 + f[9]*g5_38
155 let h5: i64 = f[0]*g[5] + f[1]*g[4] + f[2]*g[3] + f[3]*g[2] + f[4]*g[1] + f[5]*g[0] + f[6]*g9_19 + f[7]*g8_19 + f[8]*g7_19 + f[9]*g6_19
156 let h6: i64 = f[0]*g[6] + f[1]*g5_2 + f[2]*g[4] + f[3]*g3_2 + f[4]*g[2] + f[5]*g1_2 + f[6]*g[0] + f[7]*g9_38 + f[8]*g8_19 + f[9]*g7_38
157 let h7: i64 = f[0]*g[7] + f[1]*g[6] + f[2]*g[5] + f[3]*g[4] + f[4]*g[3] + f[5]*g[2] + f[6]*g[1] + f[7]*g[0] + f[8]*g9_19 + f[9]*g8_19
158 let h8: i64 = f[0]*g[8] + f[1]*g7_2 + f[2]*g[6] + f[3]*g5_2 + f[4]*g[4] + f[5]*g3_2 + f[6]*g[2] + f[7]*g1_2 + f[8]*g[0] + f[9]*g9_38
159 let h9: i64 = f[0]*g[9] + f[1]*g[8] + f[2]*g[7] + f[3]*g[6] + f[4]*g[5] + f[5]*g[4] + f[6]*g[3] + f[7]*g[2] + f[8]*g[1] + f[9]*g[0]
160
161 // Carry propagation. Odd limbs use 25-bit mask, even use 26.
162 var c0: i64 = h0
163 var c1: i64 = h1
164 var c2: i64 = h2
165 var c3: i64 = h3
166 var c4: i64 = h4
167 var c5: i64 = h5
168 var c6: i64 = h6
169 var c7: i64 = h7
170 var c8: i64 = h8
171 var c9: i64 = h9
172
173 let k: i64 = 1 << 25
174
175 // Two-pass carry; Bernstein ref10 uses a specific order to
176 // minimise register pressure and keep intermediate magnitudes
177 // bounded.
178 let carry0: i64 = (c0 + (1 << 25)) >> 26; c1 = c1 + carry0; c0 = c0 - (carry0 << 26)
179 let carry4: i64 = (c4 + (1 << 25)) >> 26; c5 = c5 + carry4; c4 = c4 - (carry4 << 26)
180 let carry1: i64 = (c1 + (1 << 24)) >> 25; c2 = c2 + carry1; c1 = c1 - (carry1 << 25)
181 let carry5: i64 = (c5 + (1 << 24)) >> 25; c6 = c6 + carry5; c5 = c5 - (carry5 << 25)
182 let carry2: i64 = (c2 + (1 << 25)) >> 26; c3 = c3 + carry2; c2 = c2 - (carry2 << 26)
183 let carry6: i64 = (c6 + (1 << 25)) >> 26; c7 = c7 + carry6; c6 = c6 - (carry6 << 26)
184 let carry3: i64 = (c3 + (1 << 24)) >> 25; c4 = c4 + carry3; c3 = c3 - (carry3 << 25)
185 let carry7: i64 = (c7 + (1 << 24)) >> 25; c8 = c8 + carry7; c7 = c7 - (carry7 << 25)
186 let carry4b: i64 = (c4 + (1 << 25)) >> 26; c5 = c5 + carry4b; c4 = c4 - (carry4b << 26)
187 let carry8: i64 = (c8 + (1 << 25)) >> 26; c9 = c9 + carry8; c8 = c8 - (carry8 << 26)
188 let carry9: i64 = (c9 + (1 << 24)) >> 25; c0 = c0 + 19 * carry9; c9 = c9 - (carry9 << 25)
189 let carry0b: i64 = (c0 + (1 << 25)) >> 26; c1 = c1 + carry0b; c0 = c0 - (carry0b << 26)
190
191 h[0] = c0; h[1] = c1; h[2] = c2; h[3] = c3; h[4] = c4
192 h[5] = c5; h[6] = c6; h[7] = c7; h[8] = c8; h[9] = c9
193 return 0
194}
195
196// Squaring is multiplication by self; the specialised fe_sq saves
197// ~50% partial products by exploiting symmetry. For simplicity
198// and to keep the code reviewable, we reuse fe_mul here; the
199// specialised form is a follow-up optimization.
200func fe_sq(h: *i64, f: *i64) -> i64 {
201 return fe_mul(h, f, f)
202}
203
204// Multiply by the Montgomery curve constant a24 = (486662 - 2)/4
205// = 121665. Used inside ladder_step.
206func fe_mul_a24(h: *i64, f: *i64) -> i64 {
207 let a: *i64 = fe_alloc()
208 var i: i64 = 0
209 while i < FE_LIMBS { a[i] = 0; i = i + 1 }
210 a[0] = 121665
211 return fe_mul(h, f, a)
212}
213
214// ---- conditional swap --------------------------------------------
215//
216// cswap(b, x, y): if b == 1 swap x and y; if b == 0 leave untouched.
217// Branchless via bitmask: mask = -b = 0 (b=0) or all-ones (b=1).
218// t = mask & (x ^ y); x ^= t; y ^= t.
219
220func fe_cswap(f: *i64, g: *i64, swap: i64) -> i64 {
221 let mask: i64 = 0 - swap
222 var i: i64 = 0
223 while i < FE_LIMBS {
224 let t: i64 = mask & (f[i] ^ g[i])
225 f[i] = f[i] ^ t
226 g[i] = g[i] ^ t
227 i = i + 1
228 }
229 return 0
230}
231
232// ---- inversion ---------------------------------------------------
233//
234// Fermat's little theorem: f^(p-2) = f^-1 (mod p). Uses a fixed
235// chain of 11 squarings + multiplications following Bernstein's
236// ref10 recipe -- 254 squarings + 11 multiplications total.
237// Independent of the input value (XC4 invariant).
238func fe_invert(out: *i64, z: *i64) -> i64 {
239 let t0: *i64 = fe_alloc()
240 let t1: *i64 = fe_alloc()
241 let t2: *i64 = fe_alloc()
242 let t3: *i64 = fe_alloc()
243 var i: i64 = 0
244
245 fe_sq(t0, z) // z^2
246 fe_sq(t1, t0); fe_sq(t1, t1) // z^8
247 fe_mul(t1, z, t1) // z^9
248 fe_mul(t0, t0, t1) // z^11
249 fe_sq(t2, t0) // z^22
250 fe_mul(t1, t1, t2) // z^(2^5 - 1)
251 fe_sq(t2, t1)
252 i = 1; while i < 5 { fe_sq(t2, t2); i = i + 1 }
253 fe_mul(t1, t2, t1) // z^(2^10 - 1)
254 fe_sq(t2, t1)
255 i = 1; while i < 10 { fe_sq(t2, t2); i = i + 1 }
256 fe_mul(t2, t2, t1) // z^(2^20 - 1)
257 fe_sq(t3, t2)
258 i = 1; while i < 20 { fe_sq(t3, t3); i = i + 1 }
259 fe_mul(t2, t3, t2) // z^(2^40 - 1)
260 fe_sq(t2, t2)
261 i = 1; while i < 10 { fe_sq(t2, t2); i = i + 1 }
262 fe_mul(t1, t2, t1) // z^(2^50 - 1)
263 fe_sq(t2, t1)
264 i = 1; while i < 50 { fe_sq(t2, t2); i = i + 1 }
265 fe_mul(t2, t2, t1) // z^(2^100 - 1)
266 fe_sq(t3, t2)
267 i = 1; while i < 100 { fe_sq(t3, t3); i = i + 1 }
268 fe_mul(t2, t3, t2) // z^(2^200 - 1)
269 fe_sq(t2, t2)
270 i = 1; while i < 50 { fe_sq(t2, t2); i = i + 1 }
271 fe_mul(t1, t2, t1) // z^(2^250 - 1)
272 fe_sq(t1, t1); fe_sq(t1, t1); fe_sq(t1, t1); fe_sq(t1, t1); fe_sq(t1, t1)
273 fe_mul(out, t1, t0) // z^(p-2)
274 return 0
275}
276
277// ---- byte decode / encode ---------------------------------------
278
279func fe_from_bytes(h: *i64, s: *u8) -> i64 {
280 // Load 255 bits as 10 limbs of 26/25 bits (Bernstein layout).
281 let b0: i64 = s[0] | (s[1] << 8) | (s[2] << 16) | ((s[3] & 0x03) << 24)
282 let b1: i64 = (s[3] >> 2) | (s[4] << 6) | (s[5] << 14) | ((s[6] & 0x07) << 22)
283 let b2: i64 = (s[6] >> 3) | (s[7] << 5) | (s[8] << 13) | ((s[9] & 0x1F) << 21)
284 let b3: i64 = (s[9] >> 5) | (s[10] << 3) | (s[11] << 11) | ((s[12] & 0x3F) << 19)
285 let b4: i64 = (s[12] >> 6) | (s[13] << 2) | (s[14] << 10) | (s[15] << 18)
286 let b5: i64 = s[16] | (s[17] << 8) | (s[18] << 16) | ((s[19] & 0x01) << 24)
287 let b6: i64 = (s[19] >> 1) | (s[20] << 7) | (s[21] << 15) | ((s[22] & 0x07) << 23)
288 let b7: i64 = (s[22] >> 3) | (s[23] << 5) | (s[24] << 13) | ((s[25] & 0x0F) << 21)
289 let b8: i64 = (s[25] >> 4) | (s[26] << 4) | (s[27] << 12) | ((s[28] & 0x3F) << 20)
290 let b9: i64 = (s[28] >> 6) | (s[29] << 2) | (s[30] << 10) | ((s[31] & 0x7F) << 18)
291 h[0] = b0; h[1] = b1; h[2] = b2; h[3] = b3; h[4] = b4
292 h[5] = b5; h[6] = b6; h[7] = b7; h[8] = b8; h[9] = b9
293 return 0
294}
295
296// Serialize a field element to 32 bytes. Reduces mod p first so
297// the output is canonical (unique representation of each class).
298func fe_to_bytes(s: *u8, h_in: *i64) -> i64 {
299 // Defensive full reduction: add 19, propagate, subtract 2^255
300 // if needed. Implementation follows ref10 line-for-line.
301 let h: *i64 = fe_alloc()
302 fe_copy(h, h_in)
303
304 var q: i64 = (19 * h[9] + (1 << 24)) >> 25
305 q = (h[0] + q) >> 26
306 q = (h[1] + q) >> 25
307 q = (h[2] + q) >> 26
308 q = (h[3] + q) >> 25
309 q = (h[4] + q) >> 26
310 q = (h[5] + q) >> 25
311 q = (h[6] + q) >> 26
312 q = (h[7] + q) >> 25
313 q = (h[8] + q) >> 26
314 q = (h[9] + q) >> 25
315
316 h[0] = h[0] + 19 * q
317 let c0: i64 = h[0] >> 26; h[1] = h[1] + c0; h[0] = h[0] - (c0 << 26)
318 let c1: i64 = h[1] >> 25; h[2] = h[2] + c1; h[1] = h[1] - (c1 << 25)
319 let c2: i64 = h[2] >> 26; h[3] = h[3] + c2; h[2] = h[2] - (c2 << 26)
320 let c3: i64 = h[3] >> 25; h[4] = h[4] + c3; h[3] = h[3] - (c3 << 25)
321 let c4: i64 = h[4] >> 26; h[5] = h[5] + c4; h[4] = h[4] - (c4 << 26)
322 let c5: i64 = h[5] >> 25; h[6] = h[6] + c5; h[5] = h[5] - (c5 << 25)
323 let c6: i64 = h[6] >> 26; h[7] = h[7] + c6; h[6] = h[6] - (c6 << 26)
324 let c7: i64 = h[7] >> 25; h[8] = h[8] + c7; h[7] = h[7] - (c7 << 25)
325 let c8: i64 = h[8] >> 26; h[9] = h[9] + c8; h[8] = h[8] - (c8 << 26)
326 let c9: i64 = h[9] >> 25; h[9] = h[9] - (c9 << 25)
327
328 s[0] = h[0] & 0xFF
329 s[1] = (h[0] >> 8) & 0xFF
330 s[2] = (h[0] >> 16) & 0xFF
331 s[3] = ((h[0] >> 24) | (h[1] << 2)) & 0xFF
332 s[4] = (h[1] >> 6) & 0xFF
333 s[5] = (h[1] >> 14) & 0xFF
334 s[6] = ((h[1] >> 22) | (h[2] << 3)) & 0xFF
335 s[7] = (h[2] >> 5) & 0xFF
336 s[8] = (h[2] >> 13) & 0xFF
337 s[9] = ((h[2] >> 21) | (h[3] << 5)) & 0xFF
338 s[10] = (h[3] >> 3) & 0xFF
339 s[11] = (h[3] >> 11) & 0xFF
340 s[12] = ((h[3] >> 19) | (h[4] << 6)) & 0xFF
341 s[13] = (h[4] >> 2) & 0xFF
342 s[14] = (h[4] >> 10) & 0xFF
343 s[15] = (h[4] >> 18) & 0xFF
344 s[16] = h[5] & 0xFF
345 s[17] = (h[5] >> 8) & 0xFF
346 s[18] = (h[5] >> 16) & 0xFF
347 s[19] = ((h[5] >> 24) | (h[6] << 1)) & 0xFF
348 s[20] = (h[6] >> 7) & 0xFF
349 s[21] = (h[6] >> 15) & 0xFF
350 s[22] = ((h[6] >> 23) | (h[7] << 3)) & 0xFF
351 s[23] = (h[7] >> 5) & 0xFF
352 s[24] = (h[7] >> 13) & 0xFF
353 s[25] = ((h[7] >> 21) | (h[8] << 4)) & 0xFF
354 s[26] = (h[8] >> 4) & 0xFF
355 s[27] = (h[8] >> 12) & 0xFF
356 s[28] = ((h[8] >> 20) | (h[9] << 6)) & 0xFF
357 s[29] = (h[9] >> 2) & 0xFF
358 s[30] = (h[9] >> 10) & 0xFF
359 s[31] = (h[9] >> 18) & 0xFF
360 return 0
361}
362
363// ---- Montgomery ladder -------------------------------------------
364//
365// One step processes one bit of the scalar. Maintains (x2, z2) =
366// kP and (x3, z3) = (k+1)P where k is the scalar prefix so far.
367// Formula from RFC 7748 §5, adapted from Bernstein's ref10.
368
369func x25519_ladder_step(x1: *i64,
370 x2: *i64, z2: *i64,
371 x3: *i64, z3: *i64,
372 swap: *i64) -> i64 {
373 let s: i64 = *swap
374 fe_cswap(x2, x3, s)
375 fe_cswap(z2, z3, s)
376 *swap = 0
377
378 let a: *i64 = fe_alloc()
379 let aa: *i64 = fe_alloc()
380 let b: *i64 = fe_alloc()
381 let bb: *i64 = fe_alloc()
382 let e: *i64 = fe_alloc()
383 let c: *i64 = fe_alloc()
384 let d: *i64 = fe_alloc()
385 let da: *i64 = fe_alloc()
386 let cb: *i64 = fe_alloc()
387
388 fe_add(a, x2, z2)
389 fe_sq(aa, a)
390 fe_sub(b, x2, z2)
391 fe_sq(bb, b)
392 fe_sub(e, aa, bb)
393 fe_add(c, x3, z3)
394 fe_sub(d, x3, z3)
395 fe_mul(da, d, a)
396 fe_mul(cb, c, b)
397
398 // x3' = (da + cb)^2
399 let t: *i64 = fe_alloc()
400 fe_add(t, da, cb)
401 fe_sq(x3, t)
402 // z3' = x1 * (da - cb)^2
403 fe_sub(t, da, cb)
404 fe_sq(t, t)
405 fe_mul(z3, t, x1)
406
407 // x2' = aa * bb
408 fe_mul(x2, aa, bb)
409 // z2' = e * (aa + 121665 * e)
410 fe_mul_a24(t, e)
411 fe_add(t, t, aa)
412 fe_mul(z2, e, t)
413 return 0
414}
415
416// ---- public entry -------------------------------------------------
417
418// scalar: 32 bytes (will be clamped internally per RFC 7748).
419// u: 32 bytes (high bit cleared per RFC 7748).
420// out: 32 bytes of shared secret.
421func x25519(scalar: *u8, u: *u8, out: *u8) -> i64 {
422 // XC1: clamp scalar.
423 let e: *u8 = sys_mmap(32)
424 var i: i64 = 0
425 while i < 32 { e[i] = scalar[i]; i = i + 1 }
426 e[0] = e[0] & 0xF8
427 e[31] = e[31] & 0x7F
428 e[31] = e[31] | 0x40
429
430 // XC2: clear u high bit.
431 let um: *u8 = sys_mmap(32)
432 i = 0
433 while i < 32 { um[i] = u[i]; i = i + 1 }
434 um[31] = um[31] & 0x7F
435
436 let x1: *i64 = fe_alloc()
437 let x2: *i64 = fe_alloc()
438 let z2: *i64 = fe_alloc()
439 let x3: *i64 = fe_alloc()
440 let z3: *i64 = fe_alloc()
441
442 fe_from_bytes(x1, um)
443 fe_one(x2); fe_zero(z2)
444 fe_copy(x3, x1); fe_one(z3)
445
446 let swap_raw: *u8 = sys_mmap(16)
447 let swap_p: *i64 = swap_raw as *i64
448 *swap_p = 0
449
450 // 255 bits of scalar, top-down.
451 var t: i64 = 254
452 while t >= 0 {
453 let byte_idx: i64 = t >> 3
454 let bit_idx: i64 = t & 7
455 let bit: i64 = (e[byte_idx] >> bit_idx) & 1
456 *swap_p = *swap_p ^ bit
457 x25519_ladder_step(x1, x2, z2, x3, z3, swap_p)
458 *swap_p = bit
459 t = t - 1
460 }
461 fe_cswap(x2, x3, *swap_p)
462 fe_cswap(z2, z3, *swap_p)
463
464 // Shared secret = x2 / z2 = x2 * z2^-1.
465 let z2_inv: *i64 = fe_alloc()
466 fe_invert(z2_inv, z2)
467 let result: *i64 = fe_alloc()
468 fe_mul(result, x2, z2_inv)
469 fe_to_bytes(out, result)
470 return 0
471}
472
473// Compile-only smoke. RFC 7748 §5.2 test vector:
474// scalar = a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4
475// u = e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c
476// output = c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552
477func main() -> i64 {
478 let scalar: *u8 = sys_mmap(32)
479 let u: *u8 = sys_mmap(32)
480 let out: *u8 = sys_mmap(32)
481 var i: i64 = 0
482 while i < 32 { scalar[i] = 1; u[i] = 9; i = i + 1 }
483 x25519(scalar, u, out)
484 return out[0] as i64
485}