code wiki / (root) / nx_u384.nx

nx_u384.nx source

↩ module page · 241 lines · 8197 B

1// nx_u384.nx -- 384-bit unsigned big-int big-int (12 × 32-bit limbs). 2// 3// Mirrors nx_u256.nx structure exactly with limb count 12 instead 4// of 8. Needed for ECDSA P-384 (task #67) -- the curve has 384-bit 5// field elements and scalars. Real public chains (example.com etc.) 6// use P-384 keys on intermediate + root certs. 7// 8// Layout: 12 i64 slots, each holding one 32-bit limb in low bits. 9// Limb 0 is least-significant; limb 11 is most-significant. 10// Byte serialization: u384_load_be / u384_store_be use 48-byte 11// big-endian (DER) layout: bytes[0] is MSB, bytes[47] is LSB. 12// 13// API (mirrors u256 1:1): 14// u384_alloc() -> *i64 (12-limb zeroed buffer) 15// u384_zero(out) 16// u384_one(out) 17// u384_copy(out, src) 18// u384_load_be(out, bytes) bytes is 48 BE octets 19// u384_store_be(bytes, src) 20// u384_add_with_carry(out, a, b) -> final carry-out 0|1 21// u384_sub_with_borrow(out, a, b) -> final borrow-out 0|1 22// u384_cmp(a, b) -> -1 | 0 | +1 23// u384_is_zero(a) -> 0 | 1 24// u384_eq(a, b) -> 0 | 1 25// 26// license_tier: INDEPENDENT_REDERIVE 27// genealogy_id: international-research-sources/nist/sec1_v2_appendix_b + nist/fips_186_5 28// lineage_id: nishi_u384_q10 29 30// nx_safety_envelope: 31// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 32// sil_target: SIL1 33// evidence: [bulk_applied_2026-05-20, p384-bigint-foundation] 34// verdict: NOT_YET_EVALUATED 35 36import "nx_syscalls.nx" 37 38const NX_U384_LIMBS: i64 = 12 39const NX_U384_BYTES: i64 = 48 40const NX_U384_LIMB_BITS: i64 = 32 41const NX_U384_LIMB_MASK: i64 = 0xFFFFFFFF 42 43// --- u384 scratch arena (bump allocator) -- mirrors nx_u256's NX_SCRATCH (proven). ---------------------- 44// THE certloop mmap-leak fix: the hot P-384 paths (point double/add, scalar-mul) allocate field scratch per 45// call; one ECDSA-P384 verify cumulatively touched ~16k tiny mmaps and never freed them, so a TLS cert chain 46// (3 ECDSA links) exhausted the process's mmap regions after ~15 fetches. Now u384_alloc bump-allocates from a 47// reused arena and the verify/scalar-mul wrap themselves in nx_u384_scratch_save/restore (LIFO O(1) reclaim) -> 48// every transient is reclaimed per verify, the arena peak stays a couple MiB and is reused, zero VMA growth. 49const NX_U384_SCRATCH_BLOCK: i64 = 1048576 // 1 MiB per block 50const NX_U384_SCRATCH_MAXBLOCKS: i64 = 8192 // 8 GiB ceiling (pathological-leak backstop only) 51static NX_U384_SCRATCH_BLOCKS: i64 // addr of i64[MAXBLOCKS] (block base addrs); 0 = uninit 52static NX_U384_SCRATCH_NBLOCKS: i64 // blocks mmap'd so far 53static NX_U384_SCRATCH_CUR: i64 // virtual cursor (bytes) 54 55func nx_u384_scratch_init() -> i64 { 56 if NX_U384_SCRATCH_BLOCKS == 0 { 57 let arr: *u8 = sys_mmap(NX_U384_SCRATCH_MAXBLOCKS * 8) 58 NX_U384_SCRATCH_BLOCKS = arr as i64 59 let blocks: *i64 = NX_U384_SCRATCH_BLOCKS as *i64 60 let b0: *u8 = sys_mmap(NX_U384_SCRATCH_BLOCK) 61 blocks[0] = b0 as i64 62 NX_U384_SCRATCH_NBLOCKS = 1 63 NX_U384_SCRATCH_CUR = 0 64 } 65 return 0 66} 67 68func nx_u384_scratch(n: i64) -> *u8 { 69 nx_u384_scratch_init() 70 let aligned: i64 = (n + 7) / 8 * 8 71 if aligned > NX_U384_SCRATCH_BLOCK { 72 sys_write(2, "FATAL: u384 scratch alloc exceeds block size\n" as *u8, 45) 73 sys_exit(70) 74 } 75 var bi: i64 = NX_U384_SCRATCH_CUR / NX_U384_SCRATCH_BLOCK 76 var off: i64 = NX_U384_SCRATCH_CUR - bi * NX_U384_SCRATCH_BLOCK 77 if off + aligned > NX_U384_SCRATCH_BLOCK { 78 bi = bi + 1 79 off = 0 80 NX_U384_SCRATCH_CUR = bi * NX_U384_SCRATCH_BLOCK 81 } 82 let blocks: *i64 = NX_U384_SCRATCH_BLOCKS as *i64 83 while NX_U384_SCRATCH_NBLOCKS <= bi { 84 if NX_U384_SCRATCH_NBLOCKS >= NX_U384_SCRATCH_MAXBLOCKS { 85 sys_write(2, "FATAL: u384 scratch arena hit block ceiling (leak?)\n" as *u8, 52) 86 sys_exit(71) 87 } 88 let nb: *u8 = sys_mmap(NX_U384_SCRATCH_BLOCK) 89 blocks[NX_U384_SCRATCH_NBLOCKS] = nb as i64 90 NX_U384_SCRATCH_NBLOCKS = NX_U384_SCRATCH_NBLOCKS + 1 91 } 92 let base: i64 = blocks[bi] 93 let p: *u8 = (base + off) as *u8 94 NX_U384_SCRATCH_CUR = bi * NX_U384_SCRATCH_BLOCK + off + aligned 95 return p 96} 97 98// LIFO frame: save returns the cursor; restore rewinds, reclaiming every alloc since (blocks stay + reused). 99func nx_u384_scratch_save() -> i64 { nx_u384_scratch_init(); return NX_U384_SCRATCH_CUR } 100func nx_u384_scratch_restore(mark: i64) -> i64 { NX_U384_SCRATCH_CUR = mark; return 0 } 101 102func u384_alloc() -> *i64 { 103 let p: *i64 = nx_u384_scratch(NX_U384_LIMBS * 8) as *i64 104 var i: i64 = 0 105 while i < NX_U384_LIMBS { 106 p[i] = 0 107 i = i + 1 108 } 109 return p 110} 111 112// Arena-backed: freeing is the caller's save/restore frame (O(1) bulk reclaim). No-op kept for API compat so the 113// hundreds of existing u384_free call sites stay valid; real reclamation is the per-verify nx_u384_scratch_restore. 114func u384_free(p: *i64) -> i64 { 115 return 0 116} 117 118func u384_zero(out: *i64) -> i64 { 119 var i: i64 = 0 120 while i < NX_U384_LIMBS { 121 out[i] = 0 122 i = i + 1 123 } 124 return 0 125} 126 127func u384_one(out: *i64) -> i64 { 128 u384_zero(out) 129 out[0] = 1 130 return 0 131} 132 133func u384_copy(out: *i64, src: *i64) -> i64 { 134 var i: i64 = 0 135 while i < NX_U384_LIMBS { 136 out[i] = src[i] 137 i = i + 1 138 } 139 return 0 140} 141 142// Load 48 BE bytes into the 12-limb little-endian limb layout. 143// bytes[i*4..i*4+3] map BE to limb[11-i] for i in 0..12. Inside 144// each 4-byte group: bytes[off] is the high byte of the limb, 145// bytes[off+3] is the low byte. 146func u384_load_be(out: *i64, bytes: *u8) -> i64 { 147 var i: i64 = 0 148 while i < NX_U384_LIMBS { 149 let off: i64 = i * 4 150 let limb_idx: i64 = NX_U384_LIMBS - 1 - i 151 let b0: i64 = bytes[off] & 0xff 152 let b1: i64 = bytes[off + 1] & 0xff 153 let b2: i64 = bytes[off + 2] & 0xff 154 let b3: i64 = bytes[off + 3] & 0xff 155 out[limb_idx] = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3 156 i = i + 1 157 } 158 return 0 159} 160 161func u384_store_be(bytes: *u8, src: *i64) -> i64 { 162 var i: i64 = 0 163 while i < NX_U384_LIMBS { 164 let off: i64 = i * 4 165 let limb_idx: i64 = NX_U384_LIMBS - 1 - i 166 let limb: i64 = src[limb_idx] 167 bytes[off] = ((limb >> 24) & 0xff) as u8 168 bytes[off + 1] = ((limb >> 16) & 0xff) as u8 169 bytes[off + 2] = ((limb >> 8) & 0xff) as u8 170 bytes[off + 3] = (limb & 0xff) as u8 171 i = i + 1 172 } 173 return 0 174} 175 176func u384_add_with_carry(out: *i64, a: *i64, b: *i64) -> i64 { 177 var i: i64 = 0 178 var carry: i64 = 0 179 while i < NX_U384_LIMBS { 180 let s: i64 = (a[i] & NX_U384_LIMB_MASK) + (b[i] & NX_U384_LIMB_MASK) + carry 181 out[i] = s & NX_U384_LIMB_MASK 182 carry = (s >> NX_U384_LIMB_BITS) & 1 183 i = i + 1 184 } 185 return carry 186} 187 188func u384_sub_with_borrow(out: *i64, a: *i64, b: *i64) -> i64 { 189 var i: i64 = 0 190 var borrow: i64 = 0 191 while i < NX_U384_LIMBS { 192 let d: i64 = (a[i] & NX_U384_LIMB_MASK) - (b[i] & NX_U384_LIMB_MASK) - borrow 193 if d < 0 { 194 out[i] = (d + (1 << NX_U384_LIMB_BITS)) & NX_U384_LIMB_MASK 195 borrow = 1 196 } else { 197 out[i] = d & NX_U384_LIMB_MASK 198 borrow = 0 199 } 200 i = i + 1 201 } 202 return borrow 203} 204 205func u384_cmp(a: *i64, b: *i64) -> i64 { 206 var i: i64 = NX_U384_LIMBS - 1 207 while i >= 0 { 208 let av: i64 = a[i] & NX_U384_LIMB_MASK 209 let bv: i64 = b[i] & NX_U384_LIMB_MASK 210 if av < bv { return 0 - 1 } 211 if av > bv { return 1 } 212 i = i - 1 213 } 214 return 0 215} 216 217func u384_is_zero(a: *i64) -> i64 { 218 var i: i64 = 0 219 var acc: i64 = 0 220 while i < NX_U384_LIMBS { 221 acc = acc | (a[i] & NX_U384_LIMB_MASK) 222 i = i + 1 223 } 224 if acc == 0 { return 1 } 225 return 0 226} 227 228func u384_eq(a: *i64, b: *i64) -> i64 { 229 var i: i64 = 0 230 var diff: i64 = 0 231 while i < NX_U384_LIMBS { 232 diff = diff | ((a[i] ^ b[i]) & NX_U384_LIMB_MASK) 233 i = i + 1 234 } 235 if diff == 0 { return 1 } 236 return 0 237} 238 239func main() -> i64 { 240 return 0 241}