code wiki / (root) / nx_x25519_wasm.nx

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}