code wiki / (root) / nx_sha3_512_wasm.nx

nx_sha3_512_wasm.nx source

↩ module page · 255 lines · 9169 B

1// nx_sha3_512_wasm.nx -- SHA-3-512 (FIPS 202) self-contained for WAT target. 2// 3// Same Keccak-f[1600] sponge as SHA-3-256 with a different rate/capacity: 4// * Rate r = 576 bits = 72 bytes (SHA-3-512: capacity c = 1024 bits) 5// * Domain separator = 0x06 (same as SHA-3 family) 6// * Output: first 64 bytes of state 7// 8// Used by ML-KEM-768 keygen to split the 32-byte seed d into the 9// (rho, sigma) pair via G(d || k) = SHA-3-512(d || k). 10// 11// Inlined Keccak-f[1600]: 24 rounds of theta, rho, pi, chi, iota. 12// Each lane is 64-bit; we pack i64 in/out at byte level (LE) to keep 13// the WAT codegen path narrow (no i64 loads through u8 pointers). 14// 15// API: 16// nx_sha3_256_one_shot(msg_ptr, msg_len, state_ptr, out_ptr) -> i64 17// msg_ptr -- input bytes 18// msg_len -- input length 19// state_ptr -- >= 256 bytes scratch (200 for state + 56 slack) 20// out_ptr -- 32 bytes for digest 21// 22// Verified against FIPS 202 / NIST CAVP test vectors (empty + "abc" + 23// "The quick brown fox..." per common reference set). 24// 25// license_tier: INDEPENDENT_REDERIVE 26// genealogy_id: international-research-sources/nist/fips_202 27// lineage_id: nishi_sha3_512_wasm_q1 28 29// === Lane I/O: read/write 64-bit lanes as 8 LE bytes at byte pointer === 30 31func _lane_load(p: *u8) -> i64 { 32 let b0: i64 = p[0] 33 let b1: i64 = p[1] 34 let b2: i64 = p[2] 35 let b3: i64 = p[3] 36 let b4: i64 = p[4] 37 let b5: i64 = p[5] 38 let b6: i64 = p[6] 39 let b7: i64 = p[7] 40 return b0 41 | (b1 << 8) 42 | (b2 << 16) 43 | (b3 << 24) 44 | (b4 << 32) 45 | (b5 << 40) 46 | (b6 << 48) 47 | (b7 << 56) 48} 49 50func _lane_store(p: *u8, v: i64) -> i64 { 51 p[0] = v & 0xff 52 p[1] = (v >> 8) & 0xff 53 p[2] = (v >> 16) & 0xff 54 p[3] = (v >> 24) & 0xff 55 p[4] = (v >> 32) & 0xff 56 p[5] = (v >> 40) & 0xff 57 p[6] = (v >> 48) & 0xff 58 p[7] = (v >> 56) & 0xff 59 return 0 60} 61 62// 64-bit rotate left. NishiLang `>>` is arithmetic shift; we mask to 63// retain only the low `nn` bits of the right-shifted half so the rotate 64// is logical. 65func _rotl64(x: i64, n: i64) -> i64 { 66 let nn: i64 = n & 63 67 if nn == 0 { return x } 68 let shr_amt: i64 = 64 - nn 69 let mask: i64 = (1 << nn) - 1 70 return ((x << nn) | ((x >> shr_amt) & mask)) & 0xffffffffffffffff 71} 72 73// === Keccak round constants (FIPS 202 §3.2.5) === 74func _keccak_rc(i: i64) -> i64 { 75 if i == 0 { return 0x0000000000000001 } 76 if i == 1 { return 0x0000000000008082 } 77 if i == 2 { return 0x800000000000808a } 78 if i == 3 { return 0x8000000080008000 } 79 if i == 4 { return 0x000000000000808b } 80 if i == 5 { return 0x0000000080000001 } 81 if i == 6 { return 0x8000000080008081 } 82 if i == 7 { return 0x8000000000008009 } 83 if i == 8 { return 0x000000000000008a } 84 if i == 9 { return 0x0000000000000088 } 85 if i == 10 { return 0x0000000080008009 } 86 if i == 11 { return 0x000000008000000a } 87 if i == 12 { return 0x000000008000808b } 88 if i == 13 { return 0x800000000000008b } 89 if i == 14 { return 0x8000000000008089 } 90 if i == 15 { return 0x8000000000008003 } 91 if i == 16 { return 0x8000000000008002 } 92 if i == 17 { return 0x8000000000000080 } 93 if i == 18 { return 0x000000000000800a } 94 if i == 19 { return 0x800000008000000a } 95 if i == 20 { return 0x8000000080008081 } 96 if i == 21 { return 0x8000000000008080 } 97 if i == 22 { return 0x0000000080000001 } 98 return 0x8000000080008008 99} 100 101// === Rho rotation offsets r[x,y] per FIPS 202 §3.2.2 === 102// Indexed as lane = 5*y + x (column-major). 103func _rho_off(lane_idx: i64) -> i64 { 104 if lane_idx == 0 { return 0 } 105 if lane_idx == 1 { return 1 } 106 if lane_idx == 2 { return 62 } 107 if lane_idx == 3 { return 28 } 108 if lane_idx == 4 { return 27 } 109 if lane_idx == 5 { return 36 } 110 if lane_idx == 6 { return 44 } 111 if lane_idx == 7 { return 6 } 112 if lane_idx == 8 { return 55 } 113 if lane_idx == 9 { return 20 } 114 if lane_idx == 10 { return 3 } 115 if lane_idx == 11 { return 10 } 116 if lane_idx == 12 { return 43 } 117 if lane_idx == 13 { return 25 } 118 if lane_idx == 14 { return 39 } 119 if lane_idx == 15 { return 41 } 120 if lane_idx == 16 { return 45 } 121 if lane_idx == 17 { return 15 } 122 if lane_idx == 18 { return 21 } 123 if lane_idx == 19 { return 8 } 124 if lane_idx == 20 { return 18 } 125 if lane_idx == 21 { return 2 } 126 if lane_idx == 22 { return 61 } 127 if lane_idx == 23 { return 56 } 128 return 14 // lane 24 129} 130 131// === Keccak-f[1600] permutation, in-place on 200-byte state === 132// scratch must hold 2 * 200 + 5*8 = 440 bytes; we use: 133// state_ptr+0..199 = working state A (lane 5*y + x at byte offset 8*(5*y+x)) 134// state_ptr+200..239 = C[5] column parities (theta) 135// state_ptr+240..439 = B[25] permuted lanes (rho + pi) 136func _keccak_f1600(state_ptr: *u8) -> i64 { 137 let A: *u8 = state_ptr 138 let C: *u8 = (state_ptr as i64 + 200) as *u8 139 let B: *u8 = (state_ptr as i64 + 240) as *u8 140 141 var round: i64 = 0 142 while round < 24 { 143 // === theta === 144 var x: i64 = 0 145 while x < 5 { 146 let c0: i64 = _lane_load((A as i64 + 8 * (x + 0)) as *u8) 147 let c1: i64 = _lane_load((A as i64 + 8 * (x + 5)) as *u8) 148 let c2: i64 = _lane_load((A as i64 + 8 * (x + 10)) as *u8) 149 let c3: i64 = _lane_load((A as i64 + 8 * (x + 15)) as *u8) 150 let c4: i64 = _lane_load((A as i64 + 8 * (x + 20)) as *u8) 151 _lane_store((C as i64 + 8 * x) as *u8, c0 ^ c1 ^ c2 ^ c3 ^ c4) 152 x = x + 1 153 } 154 var x2: i64 = 0 155 while x2 < 5 { 156 let xm: i64 = (x2 + 4) % 5 157 let xp: i64 = (x2 + 1) % 5 158 let cl: i64 = _lane_load((C as i64 + 8 * xm) as *u8) 159 let cr: i64 = _lane_load((C as i64 + 8 * xp) as *u8) 160 let d: i64 = cl ^ _rotl64(cr, 1) 161 var y: i64 = 0 162 while y < 5 { 163 let off: i64 = 8 * (x2 + 5 * y) 164 let v: i64 = _lane_load((A as i64 + off) as *u8) ^ d 165 _lane_store((A as i64 + off) as *u8, v) 166 y = y + 1 167 } 168 x2 = x2 + 1 169 } 170 171 // === rho + pi (combine: B[y, 2x+3y] = rot(A[x,y], r[x,y])) === 172 var y3: i64 = 0 173 while y3 < 5 { 174 var x3: i64 = 0 175 while x3 < 5 { 176 let src_idx: i64 = x3 + 5 * y3 177 let rot: i64 = _rho_off(src_idx) 178 let lane: i64 = _lane_load((A as i64 + 8 * src_idx) as *u8) 179 let rotated: i64 = _rotl64(lane, rot) 180 let new_x: i64 = y3 181 let new_y: i64 = (2 * x3 + 3 * y3) % 5 182 let dst_idx: i64 = new_x + 5 * new_y 183 _lane_store((B as i64 + 8 * dst_idx) as *u8, rotated) 184 x3 = x3 + 1 185 } 186 y3 = y3 + 1 187 } 188 189 // === chi: A[x,y] = B[x,y] XOR ((NOT B[x+1,y]) AND B[x+2,y]) === 190 var y4: i64 = 0 191 while y4 < 5 { 192 var x4: i64 = 0 193 while x4 < 5 { 194 let xp1: i64 = (x4 + 1) % 5 195 let xp2: i64 = (x4 + 2) % 5 196 let b0: i64 = _lane_load((B as i64 + 8 * (x4 + 5 * y4)) as *u8) 197 let b1: i64 = _lane_load((B as i64 + 8 * (xp1 + 5 * y4)) as *u8) 198 let b2: i64 = _lane_load((B as i64 + 8 * (xp2 + 5 * y4)) as *u8) 199 let nb1: i64 = (~b1) & 0xffffffffffffffff 200 let v: i64 = b0 ^ (nb1 & b2) 201 _lane_store((A as i64 + 8 * (x4 + 5 * y4)) as *u8, v) 202 x4 = x4 + 1 203 } 204 y4 = y4 + 1 205 } 206 207 // === iota: A[0,0] ^= RC[round] === 208 let a00: i64 = _lane_load(A) ^ _keccak_rc(round) 209 _lane_store(A, a00) 210 211 round = round + 1 212 } 213 return 0 214} 215 216// === SHA-3-512 sponge: absorb + squeeze === 217func nx_sha3_512_one_shot(msg_ptr: *u8, msg_len: i64, 218 state_ptr: *u8, out_ptr: *u8) -> i64 { 219 // Zero state 220 var i: i64 = 0 221 while i < 200 { state_ptr[i] = 0; i = i + 1 } 222 223 let rate: i64 = 72 // bytes (SHA-3-512: r = 576, c = 1024) 224 var pos: i64 = 0 225 226 // Absorb full rate-sized blocks 227 while pos + rate <= msg_len { 228 var b: i64 = 0 229 while b < rate { 230 state_ptr[b] = (state_ptr[b] ^ msg_ptr[pos + b]) & 0xff 231 b = b + 1 232 } 233 _keccak_f1600(state_ptr) 234 pos = pos + rate 235 } 236 237 // Absorb tail (0 to rate-1 bytes), then pad with 0x06 ... 0x80. 238 let tail: i64 = msg_len - pos 239 var t: i64 = 0 240 while t < tail { 241 state_ptr[t] = (state_ptr[t] ^ msg_ptr[pos + t]) & 0xff 242 t = t + 1 243 } 244 // Domain separator: 0x06 (SHA-3 suffix per FIPS 202 §B.2, LE bit ordering) 245 state_ptr[tail] = (state_ptr[tail] ^ 0x06) & 0xff 246 // High bit of last rate byte 247 state_ptr[rate - 1] = (state_ptr[rate - 1] ^ 0x80) & 0xff 248 _keccak_f1600(state_ptr) 249 250 // Squeeze 64 bytes (SHA-3-512 fits within one rate block since 251 // 64 < 72 = rate; no additional permutation needed). 252 var j: i64 = 0 253 while j < 64 { out_ptr[j] = state_ptr[j]; j = j + 1 } 254 return 0 255}