code wiki / (root) / nx_p384_scalar_mul_gate.nx

nx_p384_scalar_mul_gate.nx source

↩ module page · 74 lines · 5126 B

1// nx_p384_scalar_mul_gate.nx -- DIFFERENTIAL referee: the new 4-bit windowed p384_scalar_mul MUST equal the 2// trusted double-and-add p384_scalar_mul_dbladd for k*G across edge, limb/nibble-boundary, and spread scalars. 3// p384_point_eq normalizes to affine so the two algorithms' different projective representations don't false-fail. 4// A wrong scalar-mul diverges from the trusted oracle -> RED. GREEN + exit 0 iff every case matches. 5// license_tier: ORIGINAL expect_exit: 0 6import "nx_p384_scalar_mul.nx" 7import "nx_p384_point.nx" 8import "nx_syscalls.nx" 9import "nx_gate_verdict.nx" 10 11func pg_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 12func pg_putn(v: i64) -> i64 { 13 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } 14 var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 15 let d: *u8 = sys_mmap(24); var k: i64 = 0 16 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 17 var j: i64 = k - 1 18 while j >= 0 { sys_write(1, ((d as i64)+j) as *u8, 1); j = j - 1 } 19 return 0 20} 21func pg_zero12(k: *i64) -> i64 { var i: i64 = 0; while i < 12 { k[i] = 0; i = i + 1 } return 0 } 22// 1 iff windowed p384_scalar_mul(k,G) == trusted p384_scalar_mul_dbladd(k,G) 23func pg_match(g: *P384Point, k: *i64) -> i64 { 24 let ow: *P384Point = p384_point_alloc() 25 let od: *P384Point = p384_point_alloc() 26 p384_scalar_mul(ow, k, g) 27 p384_scalar_mul_dbladd(od, k, g) 28 return p384_point_eq(ow, od) 29} 30 31func main() -> i64 { 32 pg_puts("p384 scalar-mul DIFFERENTIAL gate (windowed default == trusted double-and-add, k*G, affine-normalized eq)\n" as *u8) 33 let g: *P384Point = p384_point_alloc() 34 p384_point_load_g(g) 35 let k: *i64 = sys_mmap(12 * 8) 36 var pass: i64 = 0 37 var total: i64 = 0 38 39 pg_zero12(k); total = total + 1; pass = pass + pg_match(g, k) // k=0 (O) 40 pg_zero12(k); k[0] = 1; total = total + 1; pass = pass + pg_match(g, k) // k=1 (G) 41 pg_zero12(k); k[0] = 2; total = total + 1; pass = pass + pg_match(g, k) // k=2 42 pg_zero12(k); k[0] = 3; total = total + 1; pass = pass + pg_match(g, k) // k=3 43 pg_zero12(k); k[0] = 15; total = total + 1; pass = pass + pg_match(g, k) // k=15 (max nibble) 44 pg_zero12(k); k[0] = 16; total = total + 1; pass = pass + pg_match(g, k) // k=16 (nibble carry) 45 pg_zero12(k); k[0] = 255; total = total + 1; pass = pass + pg_match(g, k) // k=255 46 pg_zero12(k); k[0] = 0xFFFFFFFF; total = total + 1; pass = pass + pg_match(g, k) // limb0 full 47 pg_zero12(k); k[1] = 1; total = total + 1; pass = pass + pg_match(g, k) // k=2^32 (limb boundary) 48 pg_zero12(k); k[11] = 0xF0000000; total = total + 1; pass = pass + pg_match(g, k) // top nibble (MSB window) 49 let a: *i64 = k; var ai: i64 = 0; pg_zero12(k); while ai < 12 { a[ai] = 0x12345678; ai = ai + 1 } 50 total = total + 1; pass = pass + pg_match(g, k) // spread 0x12345678 51 var bi: i64 = 0; pg_zero12(k); while bi < 12 { k[bi] = 0xFFFFFFFF; bi = bi + 1 } 52 total = total + 1; pass = pass + pg_match(g, k) // all-Fs (>= group order) 53 var ci: i64 = 0; pg_zero12(k); while ci < 12 { k[ci] = 0xA5A5A5A5; ci = ci + 1 } 54 total = total + 1; pass = pass + pg_match(g, k) // alternating 0xA5 55 56 // ALIASING: out == in (a caller reusing one buffer). Structurally safe -- base snapshots p up front, out is 57 // written only at the very end -- but PROVEN here: 5*G computed in place must still equal dbladd(5*G). 58 let gp: *P384Point = p384_point_alloc(); p384_point_copy(gp, g) 59 pg_zero12(k); k[0] = 5 60 p384_scalar_mul(gp, k, gp) 61 let od5: *P384Point = p384_point_alloc(); p384_scalar_mul_dbladd(od5, k, g) 62 total = total + 1; pass = pass + p384_point_eq(gp, od5) // in-place 5*G == dbladd(5*G) 63 64 pg_puts("p384_scalar_mul_gate pass=" as *u8); pg_putn(pass); pg_puts("/" as *u8); pg_putn(total); pg_puts("\n" as *u8) 65 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 66 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 67 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 68 let ctr__dry: *i64 = gv_ctr() 69 ctr__dry[0] = pass 70 ctr__dry[1] = total 71 let rc__dry: i64 = gv_verdict("P384-SCALAR-MUL-GATE" as *u8, ctr__dry, "windowed scalar-mul byte-identical to trusted double-and-add across edge+boundary+spread scalars" as *u8) 72 sys_exit(rc__dry) 73 return rc__dry 74}