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}