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}