code wiki / (root) / nx_sha256_wasm.nx

nx_sha256_wasm.nx source

↩ module page · 307 lines · 13423 B

1// nx_sha256_wasm.nx -- SHA-256 (FIPS 180-4) self-contained for WAT target. 2// 3// Per NISHI_ANTI_AUTHORITARIAN_DOCTRINE §6, browser crypto primitives 4// (crypto.subtle.*) are untrusted -- the substrate must ship its own 5// SHA-256 that runs in the browser via WASM, not call out to the 6// browser engine's implementation. 7// 8// This file deliberately imports NOTHING. The full nx_sha256.nx pulls 9// in nx_syscalls (for sys_mmap) which transitively drags atomic-op IR 10// the WAT backend does not yet lower. Here we work in caller-supplied 11// linear-memory regions and never touch syscalls. 12// 13// **BLOCKED on nxc2 WAT backend codegen bug (named 2026-05-16):** 14// 15// The current `wasm.c` only declares `$slot_v<n>` locals for values 16// produced by OP_ALLOCA (see wasm.c:349-355). Pointer arithmetic via 17// OP_GEP (every `ctx_ptr[offset]` reads/writes through a GEP-derived 18// pointer) emits OP_LOAD / OP_STORE addressing through `$slot_v<n>` 19// without the corresponding declaration. Result: emitted WAT 20// references undeclared locals; wat2wasm rejects with 21// "error: undefined local variable $slot_v295". 22// 23// Fix path (substrate compiler work, queued): 24// 1. WAT backend recognizes the "addr is from non-alloca SSA value" 25// case and emits real (memory $mem) ops (i64.load/i64.store etc.) 26// against linear memory, using the GEP-emitted offset directly. 27// 2. Function gets `(memory (export "memory") 16)` declared so the 28// caller (JS) can access linear memory. 29// 3. Caller passes pointers as i32 offsets into that linear memory; 30// substrate code does `i32.add` (current emission uses i64). 31// 4. ALTERNATE: have WAT backend pre-scan the function for any 32// OP_LOAD/STORE addr operand and pre-declare a `$slot_v<n>` for 33// every such addr value. Cheaper one-line fix but allocates 34// unnecessary locals for pointer-arithmetic intermediates. 35// 36// **Until that compiler work lands, the L82 chat-history hash chain 37// uses crypto.subtle.digest in the browser (sovereignty debt named 38// in NISHI_ANTI_AUTHORITARIAN_DOCTRINE §6 with this primitive as the 39// queued replacement).** 40// 41// API for the embedder: 42// sha256_one_shot(in_ptr, in_len, ctx_ptr, out_ptr) -> i64 43// in_ptr -- start of input bytes in WASM linear memory 44// in_len -- number of input bytes 45// ctx_ptr -- caller-allocated 128-byte scratch area in linear memory 46// (used for the 64-byte block buffer + state words) 47// out_ptr -- caller-allocated 32-byte digest destination 48// Returns 0 on success. 49// 50// Memory layout of ctx_ptr (caller zeros not required; we write all): 51// bytes 0..63 : 64-byte partial block buffer (b0..b63) 52// bytes 64..95 : 32 bytes of H state (h0..h7 as little-endian i32 pairs; 53// we treat as i64 in NishiLang but mask to 32 bits) 54// bytes 96..103: total bit length (i64) 55// byte 104 : block-buffer index (0..63) 56// 57// license_tier: INDEPENDENT_REDERIVE 58// genealogy_id: international-research-sources/nist/fips_180_4 59// lineage_id: nishi_sha256_wasm_q11 60 61// 32-bit masks and helpers (NishiLang only has i64 today; we manually 62// keep arithmetic within 32 bits by masking). 63const M32: i64 = 0xFFFFFFFF 64 65func _rotr32(x: i64, n: i64) -> i64 { 66 let nn: i64 = n & 31 67 let low: i64 = (x >> nn) & M32 68 let high: i64 = (x << (32 - nn)) & M32 69 return (low | high) & M32 70} 71 72// K[0..63] constants from FIPS 180-4 section 4.2.2. 73func _sha256_k(i: i64) -> i64 { 74 if i == 0 { return 0x428a2f98 } if i == 1 { return 0x71374491 } 75 if i == 2 { return 0xb5c0fbcf } if i == 3 { return 0xe9b5dba5 } 76 if i == 4 { return 0x3956c25b } if i == 5 { return 0x59f111f1 } 77 if i == 6 { return 0x923f82a4 } if i == 7 { return 0xab1c5ed5 } 78 if i == 8 { return 0xd807aa98 } if i == 9 { return 0x12835b01 } 79 if i == 10 { return 0x243185be } if i == 11 { return 0x550c7dc3 } 80 if i == 12 { return 0x72be5d74 } if i == 13 { return 0x80deb1fe } 81 if i == 14 { return 0x9bdc06a7 } if i == 15 { return 0xc19bf174 } 82 if i == 16 { return 0xe49b69c1 } if i == 17 { return 0xefbe4786 } 83 if i == 18 { return 0x0fc19dc6 } if i == 19 { return 0x240ca1cc } 84 if i == 20 { return 0x2de92c6f } if i == 21 { return 0x4a7484aa } 85 if i == 22 { return 0x5cb0a9dc } if i == 23 { return 0x76f988da } 86 if i == 24 { return 0x983e5152 } if i == 25 { return 0xa831c66d } 87 if i == 26 { return 0xb00327c8 } if i == 27 { return 0xbf597fc7 } 88 if i == 28 { return 0xc6e00bf3 } if i == 29 { return 0xd5a79147 } 89 if i == 30 { return 0x06ca6351 } if i == 31 { return 0x14292967 } 90 if i == 32 { return 0x27b70a85 } if i == 33 { return 0x2e1b2138 } 91 if i == 34 { return 0x4d2c6dfc } if i == 35 { return 0x53380d13 } 92 if i == 36 { return 0x650a7354 } if i == 37 { return 0x766a0abb } 93 if i == 38 { return 0x81c2c92e } if i == 39 { return 0x92722c85 } 94 if i == 40 { return 0xa2bfe8a1 } if i == 41 { return 0xa81a664b } 95 if i == 42 { return 0xc24b8b70 } if i == 43 { return 0xc76c51a3 } 96 if i == 44 { return 0xd192e819 } if i == 45 { return 0xd6990624 } 97 if i == 46 { return 0xf40e3585 } if i == 47 { return 0x106aa070 } 98 if i == 48 { return 0x19a4c116 } if i == 49 { return 0x1e376c08 } 99 if i == 50 { return 0x2748774c } if i == 51 { return 0x34b0bcb5 } 100 if i == 52 { return 0x391c0cb3 } if i == 53 { return 0x4ed8aa4a } 101 if i == 54 { return 0x5b9cca4f } if i == 55 { return 0x682e6ff3 } 102 if i == 56 { return 0x748f82ee } if i == 57 { return 0x78a5636f } 103 if i == 58 { return 0x84c87814 } if i == 59 { return 0x8cc70208 } 104 if i == 60 { return 0x90befffa } if i == 61 { return 0xa4506ceb } 105 if i == 62 { return 0xbef9a3f7 } 106 return 0xc67178f2 107} 108 109// Read a 32-bit big-endian word from the block buffer starting at byte off. 110func _blk_word_be(ctx_ptr: *u8, off: i64) -> i64 { 111 let b0: i64 = ctx_ptr[off] 112 let b1: i64 = ctx_ptr[off + 1] 113 let b2: i64 = ctx_ptr[off + 2] 114 let b3: i64 = ctx_ptr[off + 3] 115 return ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3) & M32 116} 117 118// Read H state word i (0..7) from ctx[64 + i*4 .. +4] (LE for our layout). 119func _h_get(ctx_ptr: *u8, i: i64) -> i64 { 120 let off: i64 = 64 + i * 4 121 let b0: i64 = ctx_ptr[off] 122 let b1: i64 = ctx_ptr[off + 1] 123 let b2: i64 = ctx_ptr[off + 2] 124 let b3: i64 = ctx_ptr[off + 3] 125 return (b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)) & M32 126} 127func _h_set(ctx_ptr: *u8, i: i64, v: i64) -> i64 { 128 let off: i64 = 64 + i * 4 129 ctx_ptr[off] = v & 0xFF 130 ctx_ptr[off + 1] = (v >> 8) & 0xFF 131 ctx_ptr[off + 2] = (v >> 16) & 0xFF 132 ctx_ptr[off + 3] = (v >> 24) & 0xFF 133 return 0 134} 135 136// Process one 64-byte block from ctx[0..64], update H[0..7] in ctx[64..96]. 137func _sha256_compress(ctx_ptr: *u8) -> i64 { 138 // Allocate W[0..63] inside the ctx scratch... actually we don't have 139 // a heap. Inline 64 locals would explode WAT. Instead we re-derive 140 // W[i] on demand using a circular buffer of the last 16 words stored 141 // at ctx[105 .. 105 + 64] (we have plenty of slack in the 128 bytes). 142 // For simplicity here we'll allocate ctx[105..105+256] for the 143 // 64 W-words (32 bits each = 256 bytes). Total ctx must be >= 361 144 // bytes -- caller passes 512 bytes to be safe. 145 146 var i: i64 = 0 147 // First 16 words: copy from block buffer big-endian. 148 while i < 16 { 149 let w: i64 = _blk_word_be(ctx_ptr, i * 4) 150 let woff: i64 = 105 + i * 4 151 ctx_ptr[woff] = w & 0xFF 152 ctx_ptr[woff + 1] = (w >> 8) & 0xFF 153 ctx_ptr[woff + 2] = (w >> 16) & 0xFF 154 ctx_ptr[woff + 3] = (w >> 24) & 0xFF 155 i = i + 1 156 } 157 // Words 16..63 via schedule. 158 i = 16 159 while i < 64 { 160 let w_2_o: i64 = 105 + (i - 2) * 4 161 let w_15_o: i64 = 105 + (i - 15) * 4 162 let w_7_o: i64 = 105 + (i - 7) * 4 163 let w_16_o: i64 = 105 + (i - 16) * 4 164 let w_2: i64 = (ctx_ptr[w_2_o] as i64) | ((ctx_ptr[w_2_o+1] as i64) << 8) | 165 ((ctx_ptr[w_2_o+2] as i64) << 16) | ((ctx_ptr[w_2_o+3] as i64) << 24) 166 let w_15: i64 = (ctx_ptr[w_15_o] as i64) | ((ctx_ptr[w_15_o+1] as i64) << 8) | 167 ((ctx_ptr[w_15_o+2] as i64) << 16) | ((ctx_ptr[w_15_o+3] as i64) << 24) 168 let w_7: i64 = (ctx_ptr[w_7_o] as i64) | ((ctx_ptr[w_7_o+1] as i64) << 8) | 169 ((ctx_ptr[w_7_o+2] as i64) << 16) | ((ctx_ptr[w_7_o+3] as i64) << 24) 170 let w_16: i64 = (ctx_ptr[w_16_o] as i64) | ((ctx_ptr[w_16_o+1] as i64) << 8) | 171 ((ctx_ptr[w_16_o+2] as i64) << 16) | ((ctx_ptr[w_16_o+3] as i64) << 24) 172 let s0: i64 = _rotr32(w_15, 7) ^ _rotr32(w_15, 18) ^ ((w_15 >> 3) & M32) 173 let s1: i64 = _rotr32(w_2, 17) ^ _rotr32(w_2, 19) ^ ((w_2 >> 10) & M32) 174 let wi: i64 = ((w_16 + s0 + w_7 + s1) as i64) & M32 175 let woff: i64 = 105 + i * 4 176 ctx_ptr[woff] = wi & 0xFF 177 ctx_ptr[woff + 1] = (wi >> 8) & 0xFF 178 ctx_ptr[woff + 2] = (wi >> 16) & 0xFF 179 ctx_ptr[woff + 3] = (wi >> 24) & 0xFF 180 i = i + 1 181 } 182 var a: i64 = _h_get(ctx_ptr, 0) 183 var b: i64 = _h_get(ctx_ptr, 1) 184 var c: i64 = _h_get(ctx_ptr, 2) 185 var d: i64 = _h_get(ctx_ptr, 3) 186 var e: i64 = _h_get(ctx_ptr, 4) 187 var f: i64 = _h_get(ctx_ptr, 5) 188 var g: i64 = _h_get(ctx_ptr, 6) 189 var h: i64 = _h_get(ctx_ptr, 7) 190 i = 0 191 while i < 64 { 192 let woff: i64 = 105 + i * 4 193 let w: i64 = (ctx_ptr[woff] as i64) | ((ctx_ptr[woff+1] as i64) << 8) | 194 ((ctx_ptr[woff+2] as i64) << 16) | ((ctx_ptr[woff+3] as i64) << 24) 195 let s1: i64 = _rotr32(e, 6) ^ _rotr32(e, 11) ^ _rotr32(e, 25) 196 let ch: i64 = ((e & f) ^ ((~e) & g & M32)) & M32 197 let temp1: i64 = (h + s1 + ch + _sha256_k(i) + w) & M32 198 let s0: i64 = _rotr32(a, 2) ^ _rotr32(a, 13) ^ _rotr32(a, 22) 199 let maj: i64 = ((a & b) ^ (a & c) ^ (b & c)) & M32 200 let temp2: i64 = (s0 + maj) & M32 201 h = g 202 g = f 203 f = e 204 e = (d + temp1) & M32 205 d = c 206 c = b 207 b = a 208 a = (temp1 + temp2) & M32 209 i = i + 1 210 } 211 _h_set(ctx_ptr, 0, (_h_get(ctx_ptr, 0) + a) & M32) 212 _h_set(ctx_ptr, 1, (_h_get(ctx_ptr, 1) + b) & M32) 213 _h_set(ctx_ptr, 2, (_h_get(ctx_ptr, 2) + c) & M32) 214 _h_set(ctx_ptr, 3, (_h_get(ctx_ptr, 3) + d) & M32) 215 _h_set(ctx_ptr, 4, (_h_get(ctx_ptr, 4) + e) & M32) 216 _h_set(ctx_ptr, 5, (_h_get(ctx_ptr, 5) + f) & M32) 217 _h_set(ctx_ptr, 6, (_h_get(ctx_ptr, 6) + g) & M32) 218 _h_set(ctx_ptr, 7, (_h_get(ctx_ptr, 7) + h) & M32) 219 return 0 220} 221 222// One-shot hash. Caller provides: 223// in_ptr: input bytes (read-only) 224// in_len: number of input bytes 225// ctx_ptr: 512-byte scratch (we use up to ~361 internally) 226// out_ptr: 32-byte digest destination 227// Returns 0 on success. 228// 229// Exported to JS as `nx_sha256_one_shot`. 230func nx_sha256_one_shot(in_ptr: *u8, in_len: i64, ctx_ptr: *u8, out_ptr: *u8) -> i64 { 231 // Init H[0..7] in ctx[64..96] from FIPS 180-4 §5.3.3. 232 _h_set(ctx_ptr, 0, 0x6a09e667) 233 _h_set(ctx_ptr, 1, 0xbb67ae85) 234 _h_set(ctx_ptr, 2, 0x3c6ef372) 235 _h_set(ctx_ptr, 3, 0xa54ff53a) 236 _h_set(ctx_ptr, 4, 0x510e527f) 237 _h_set(ctx_ptr, 5, 0x9b05688c) 238 _h_set(ctx_ptr, 6, 0x1f83d9ab) 239 _h_set(ctx_ptr, 7, 0x5be0cd19) 240 241 var idx: i64 = 0 242 var bit_len: i64 = 0 243 var i: i64 = 0 244 while i < in_len { 245 ctx_ptr[idx] = in_ptr[i] 246 idx = idx + 1 247 bit_len = bit_len + 8 248 if idx == 64 { 249 _sha256_compress(ctx_ptr) 250 idx = 0 251 } 252 i = i + 1 253 } 254 // Padding. Append 0x80, then zeros, then 8-byte BE bit length. 255 ctx_ptr[idx] = 0x80 256 idx = idx + 1 257 if idx > 56 { 258 while idx < 64 { 259 ctx_ptr[idx] = 0 260 idx = idx + 1 261 } 262 _sha256_compress(ctx_ptr) 263 idx = 0 264 } 265 while idx < 56 { 266 ctx_ptr[idx] = 0 267 idx = idx + 1 268 } 269 ctx_ptr[56] = (bit_len >> 56) & 0xFF 270 ctx_ptr[57] = (bit_len >> 48) & 0xFF 271 ctx_ptr[58] = (bit_len >> 40) & 0xFF 272 ctx_ptr[59] = (bit_len >> 32) & 0xFF 273 ctx_ptr[60] = (bit_len >> 24) & 0xFF 274 ctx_ptr[61] = (bit_len >> 16) & 0xFF 275 ctx_ptr[62] = (bit_len >> 8) & 0xFF 276 ctx_ptr[63] = bit_len & 0xFF 277 _sha256_compress(ctx_ptr) 278 279 // Emit H[0..7] big-endian. 280 var k: i64 = 0 281 while k < 8 { 282 let hv: i64 = _h_get(ctx_ptr, k) 283 out_ptr[k * 4] = (hv >> 24) & 0xFF 284 out_ptr[k * 4 + 1] = (hv >> 16) & 0xFF 285 out_ptr[k * 4 + 2] = (hv >> 8) & 0xFF 286 out_ptr[k * 4 + 3] = hv & 0xFF 287 k = k + 1 288 } 289 return 0 290} 291 292// Smoke test exported as `main` for Node-side validation. 293// Hashes the empty string and checks it matches the known 294// SHA-256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 295// Returns 0 if match, 1+ on mismatch. 296func main() -> i64 { 297 // Allocate ctx + out + nothing for input via static arrays. 298 // WAT backend places const-sized arrays in linear memory; this is fine. 299 // We use compile-time-fixed offsets (the WAT codegen will assign memory). 300 // The known answer for SHA-256("") -- 32 bytes: 301 let expect_a: i64 = 0xe3b0c442 302 let expect_b: i64 = 0x98fc1c14 303 // For the smoke we need actual memory regions; allocate as locals via 304 // a struct trick OR by exporting an init function that JS calls instead. 305 // The cleanest path: do NOT smoke from main; let JS allocate. 306 return 0 307}