code wiki / (root) / nx_p384_scalar_mul.nx

nx_p384_scalar_mul.nx source

↩ module page · 100 lines · 3958 B

1// nx_p384_scalar_mul.nx -- scalar multiplication k * P on P-384. 2// 3// Double-and-add MSB-first. 384 iterations. 4// 5// Aliasing-safe: out may equal p (input snapshotted). 6// 7// license_tier: INDEPENDENT_REDERIVE 8// genealogy_id: international-research-sources/nist/sec1_v2_3.2.1 9// lineage_id: nishi_p384_scalar_mul_q10 10 11// nx_safety_envelope: 12// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 13// sil_target: SIL1 14// evidence: [bulk_applied_2026-05-20, p384-scalar-mul-double-and-add] 15// verdict: NOT_YET_EVALUATED 16 17import "nx_syscalls.nx" 18import "nx_u384.nx" 19import "nx_p384_field.nx" 20import "nx_p384_point.nx" 21import "nx_p384_point_add.nx" 22 23// out = k * p on the curve. 24// REFERENCE double-and-add -- kept as the differential oracle for the windowed default below (nx_p384_scalar_mul_gate). 25func p384_scalar_mul_dbladd(out: *P384Point, k_12: *i64, p: *P384Point) -> i64 { 26 // certloop leak fix: this loop runs point_double/add 384x, each allocating field scratch from the u384 arena. 27 // Wrap the whole thing in a LIFO frame -> all ~16k transients (base/result + every iteration's scratch) are 28 // reclaimed in O(1) at return. `out` is the caller's buffer (outside this frame), copied BEFORE restore. 29 let _sav: i64 = nx_u384_scratch_save() 30 let base: *P384Point = p384_point_alloc() 31 p384_point_copy(base, p) 32 33 let result: *P384Point = p384_point_alloc() 34 p384_point_zero(result) 35 36 var bit_pos: i64 = 383 37 while bit_pos >= 0 { 38 p384_point_double(result, result) 39 let limb_idx: i64 = bit_pos / 32 40 let bit_in_limb: i64 = bit_pos - limb_idx * 32 41 let limb: i64 = k_12[limb_idx] & 0xFFFFFFFF 42 let bit: i64 = (limb >> bit_in_limb) & 1 43 if bit == 1 { 44 p384_point_add(result, result, base) 45 } 46 bit_pos = bit_pos - 1 47 } 48 49 p384_point_copy(out, result) 50 nx_u384_scratch_restore(_sav) 51 return 0 52} 53 54// out = k * p -- 4-bit FIXED-WINDOW, MSB-first: 96 windows x (4 doublings + <=1 table-add) over a 16-entry [i]P 55// table. Same 384 doublings as double-and-add but ~HALF the adds (192 -> ~96), trimming the certloop that is 56// ~85% of every TLS handshake. Byte-identical result to p384_scalar_mul_dbladd -- differential + KAT gated 57// (nx_p384_scalar_mul_gate). Aliasing-safe (base snapshots p; out copied before the LIFO scratch frame is reclaimed). 58func p384_scalar_mul(out: *P384Point, k_12: *i64, p: *P384Point) -> i64 { 59 let _sav: i64 = nx_u384_scratch_save() 60 let base: *P384Point = p384_point_alloc() 61 p384_point_copy(base, p) 62 63 // table[i] = i*P. Only table[2]=P+P is a DOUBLE; table[3..15]=(i-1)P+P add DISTINCT points (exactly the regime 64 // dbladd's own adds stay in), and a 0 nibble skips the add -> no incomplete-addition-formula hazard is introduced. 65 let table: *i64 = sys_mmap(16 * 8) 66 var ti: i64 = 0 67 while ti < 16 { table[ti] = p384_point_alloc() as i64; ti = ti + 1 } 68 p384_point_zero(table[0] as *P384Point) 69 p384_point_copy(table[1] as *P384Point, base) 70 p384_point_double(table[2] as *P384Point, base) 71 var tj: i64 = 3 72 while tj < 16 { 73 p384_point_add(table[tj] as *P384Point, table[tj - 1] as *P384Point, base) 74 tj = tj + 1 75 } 76 77 let result: *P384Point = p384_point_alloc() 78 p384_point_zero(result) 79 var w: i64 = 95 80 while w >= 0 { 81 p384_point_double(result, result) 82 p384_point_double(result, result) 83 p384_point_double(result, result) 84 p384_point_double(result, result) 85 let limb_idx: i64 = w / 8 86 let shift: i64 = (w - limb_idx * 8) * 4 87 let limb: i64 = k_12[limb_idx] & 0xFFFFFFFF 88 let nib: i64 = (limb >> shift) & 0xF 89 if nib != 0 { p384_point_add(result, result, table[nib] as *P384Point) } 90 w = w - 1 91 } 92 93 p384_point_copy(out, result) 94 nx_u384_scratch_restore(_sav) 95 return 0 96} 97 98func main() -> i64 { 99 return 0 100}