code wiki / (root) / x25519.nx

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}