nx_hmac_sha256_wasm.nx source
↩ module page · 383 lines · 14934 B
1// nx_hmac_sha256_wasm.nx -- HMAC-SHA-256 (RFC 2104) for WAT target.
2//
3// HMAC(K, M) = H( (K' XOR opad) || H( (K' XOR ipad) || M ) )
4// K' = K when len(K) <= B (= 64 for SHA-256)
5// H(K) padded with zeros to B otherwise
6// ipad = 0x36 repeated B times
7// opad = 0x5c repeated B times
8//
9// Built on top of nx_sha256_wasm.nx -- the SHA-256 helper functions
10// are pasted in directly (NishiLang doesn't yet have multi-file
11// imports for the WAT target, and the import would drag in the
12// nx_syscalls atomics IR that the WAT backend doesn't lower).
13//
14// API for the embedder:
15// nx_hmac_sha256_one_shot(key_ptr, key_len, msg_ptr, msg_len,
16// scratch_ptr, out_ptr) -> i64
17// key_ptr -- HMAC key bytes
18// key_len -- key byte count
19// msg_ptr -- message bytes
20// msg_len -- message byte count
21// scratch_ptr -- 1024-byte scratch area (caller-allocated)
22// out_ptr -- 32-byte HMAC output destination
23//
24// Memory layout of scratch_ptr (caller need not zero):
25// bytes 0.. 63 : K' (block-sized key) buffer
26// bytes 64.. 127 : K' XOR ipad buffer
27// bytes 128.. 191 : K' XOR opad buffer
28// bytes 192.. 223 : H(K' XOR ipad || M) inner hash output
29// bytes 256.. 767 : SHA-256 ctx scratch (512 bytes, see nx_sha256_wasm)
30// bytes 768.. 832 : 64-byte buffer used to concat K' XOR opad || inner_hash
31// (64 + 32 = 96 bytes total, so reserve 96)
32//
33// Verified against RFC 4231 test vectors -- see bench/wasm_hmac_smoke.js.
34//
35// license_tier: INDEPENDENT_REDERIVE
36// genealogy_id: international-research-sources/ietf/rfc_2104 + rfc_4231
37// lineage_id: nishi_hmac_sha256_wasm_q11
38
39const M32: i64 = 0xFFFFFFFF
40const HMAC_B: i64 = 64 // SHA-256 block size in bytes
41
42// ---- SHA-256 helpers (duplicated from nx_sha256_wasm; see explanation
43// in the file header) ----
44
45func _rotr32(x: i64, n: i64) -> i64 {
46 let nn: i64 = n & 31
47 let low: i64 = (x >> nn) & M32
48 let high: i64 = (x << (32 - nn)) & M32
49 return (low | high) & M32
50}
51
52func _sha256_k(i: i64) -> i64 {
53 if i == 0 { return 0x428a2f98 } if i == 1 { return 0x71374491 }
54 if i == 2 { return 0xb5c0fbcf } if i == 3 { return 0xe9b5dba5 }
55 if i == 4 { return 0x3956c25b } if i == 5 { return 0x59f111f1 }
56 if i == 6 { return 0x923f82a4 } if i == 7 { return 0xab1c5ed5 }
57 if i == 8 { return 0xd807aa98 } if i == 9 { return 0x12835b01 }
58 if i == 10 { return 0x243185be } if i == 11 { return 0x550c7dc3 }
59 if i == 12 { return 0x72be5d74 } if i == 13 { return 0x80deb1fe }
60 if i == 14 { return 0x9bdc06a7 } if i == 15 { return 0xc19bf174 }
61 if i == 16 { return 0xe49b69c1 } if i == 17 { return 0xefbe4786 }
62 if i == 18 { return 0x0fc19dc6 } if i == 19 { return 0x240ca1cc }
63 if i == 20 { return 0x2de92c6f } if i == 21 { return 0x4a7484aa }
64 if i == 22 { return 0x5cb0a9dc } if i == 23 { return 0x76f988da }
65 if i == 24 { return 0x983e5152 } if i == 25 { return 0xa831c66d }
66 if i == 26 { return 0xb00327c8 } if i == 27 { return 0xbf597fc7 }
67 if i == 28 { return 0xc6e00bf3 } if i == 29 { return 0xd5a79147 }
68 if i == 30 { return 0x06ca6351 } if i == 31 { return 0x14292967 }
69 if i == 32 { return 0x27b70a85 } if i == 33 { return 0x2e1b2138 }
70 if i == 34 { return 0x4d2c6dfc } if i == 35 { return 0x53380d13 }
71 if i == 36 { return 0x650a7354 } if i == 37 { return 0x766a0abb }
72 if i == 38 { return 0x81c2c92e } if i == 39 { return 0x92722c85 }
73 if i == 40 { return 0xa2bfe8a1 } if i == 41 { return 0xa81a664b }
74 if i == 42 { return 0xc24b8b70 } if i == 43 { return 0xc76c51a3 }
75 if i == 44 { return 0xd192e819 } if i == 45 { return 0xd6990624 }
76 if i == 46 { return 0xf40e3585 } if i == 47 { return 0x106aa070 }
77 if i == 48 { return 0x19a4c116 } if i == 49 { return 0x1e376c08 }
78 if i == 50 { return 0x2748774c } if i == 51 { return 0x34b0bcb5 }
79 if i == 52 { return 0x391c0cb3 } if i == 53 { return 0x4ed8aa4a }
80 if i == 54 { return 0x5b9cca4f } if i == 55 { return 0x682e6ff3 }
81 if i == 56 { return 0x748f82ee } if i == 57 { return 0x78a5636f }
82 if i == 58 { return 0x84c87814 } if i == 59 { return 0x8cc70208 }
83 if i == 60 { return 0x90befffa } if i == 61 { return 0xa4506ceb }
84 if i == 62 { return 0xbef9a3f7 }
85 return 0xc67178f2
86}
87
88func _blk_word_be(ctx_ptr: *u8, off: i64) -> i64 {
89 let b0: i64 = ctx_ptr[off]
90 let b1: i64 = ctx_ptr[off + 1]
91 let b2: i64 = ctx_ptr[off + 2]
92 let b3: i64 = ctx_ptr[off + 3]
93 return ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3) & M32
94}
95
96func _h_get(ctx_ptr: *u8, i: i64) -> i64 {
97 let off: i64 = 64 + i * 4
98 let b0: i64 = ctx_ptr[off]
99 let b1: i64 = ctx_ptr[off + 1]
100 let b2: i64 = ctx_ptr[off + 2]
101 let b3: i64 = ctx_ptr[off + 3]
102 return (b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)) & M32
103}
104func _h_set(ctx_ptr: *u8, i: i64, v: i64) -> i64 {
105 let off: i64 = 64 + i * 4
106 ctx_ptr[off] = v & 0xFF
107 ctx_ptr[off + 1] = (v >> 8) & 0xFF
108 ctx_ptr[off + 2] = (v >> 16) & 0xFF
109 ctx_ptr[off + 3] = (v >> 24) & 0xFF
110 return 0
111}
112
113func _sha256_compress(ctx_ptr: *u8) -> i64 {
114 var i: i64 = 0
115 while i < 16 {
116 let w: i64 = _blk_word_be(ctx_ptr, i * 4)
117 let woff: i64 = 105 + i * 4
118 ctx_ptr[woff] = w & 0xFF
119 ctx_ptr[woff + 1] = (w >> 8) & 0xFF
120 ctx_ptr[woff + 2] = (w >> 16) & 0xFF
121 ctx_ptr[woff + 3] = (w >> 24) & 0xFF
122 i = i + 1
123 }
124 i = 16
125 while i < 64 {
126 let w_2_o: i64 = 105 + (i - 2) * 4
127 let w_15_o: i64 = 105 + (i - 15) * 4
128 let w_7_o: i64 = 105 + (i - 7) * 4
129 let w_16_o: i64 = 105 + (i - 16) * 4
130 let w_2: i64 = (ctx_ptr[w_2_o] as i64) | ((ctx_ptr[w_2_o+1] as i64) << 8) |
131 ((ctx_ptr[w_2_o+2] as i64) << 16) | ((ctx_ptr[w_2_o+3] as i64) << 24)
132 let w_15: i64 = (ctx_ptr[w_15_o] as i64) | ((ctx_ptr[w_15_o+1] as i64) << 8) |
133 ((ctx_ptr[w_15_o+2] as i64) << 16) | ((ctx_ptr[w_15_o+3] as i64) << 24)
134 let w_7: i64 = (ctx_ptr[w_7_o] as i64) | ((ctx_ptr[w_7_o+1] as i64) << 8) |
135 ((ctx_ptr[w_7_o+2] as i64) << 16) | ((ctx_ptr[w_7_o+3] as i64) << 24)
136 let w_16: i64 = (ctx_ptr[w_16_o] as i64) | ((ctx_ptr[w_16_o+1] as i64) << 8) |
137 ((ctx_ptr[w_16_o+2] as i64) << 16) | ((ctx_ptr[w_16_o+3] as i64) << 24)
138 let s0: i64 = _rotr32(w_15, 7) ^ _rotr32(w_15, 18) ^ ((w_15 >> 3) & M32)
139 let s1: i64 = _rotr32(w_2, 17) ^ _rotr32(w_2, 19) ^ ((w_2 >> 10) & M32)
140 let wi: i64 = ((w_16 + s0 + w_7 + s1) as i64) & M32
141 let woff: i64 = 105 + i * 4
142 ctx_ptr[woff] = wi & 0xFF
143 ctx_ptr[woff + 1] = (wi >> 8) & 0xFF
144 ctx_ptr[woff + 2] = (wi >> 16) & 0xFF
145 ctx_ptr[woff + 3] = (wi >> 24) & 0xFF
146 i = i + 1
147 }
148 var a: i64 = _h_get(ctx_ptr, 0)
149 var b: i64 = _h_get(ctx_ptr, 1)
150 var c: i64 = _h_get(ctx_ptr, 2)
151 var d: i64 = _h_get(ctx_ptr, 3)
152 var e: i64 = _h_get(ctx_ptr, 4)
153 var f: i64 = _h_get(ctx_ptr, 5)
154 var g: i64 = _h_get(ctx_ptr, 6)
155 var h: i64 = _h_get(ctx_ptr, 7)
156 i = 0
157 while i < 64 {
158 let woff: i64 = 105 + i * 4
159 let w: i64 = (ctx_ptr[woff] as i64) | ((ctx_ptr[woff+1] as i64) << 8) |
160 ((ctx_ptr[woff+2] as i64) << 16) | ((ctx_ptr[woff+3] as i64) << 24)
161 let s1: i64 = _rotr32(e, 6) ^ _rotr32(e, 11) ^ _rotr32(e, 25)
162 let ch: i64 = ((e & f) ^ ((~e) & g & M32)) & M32
163 let temp1: i64 = (h + s1 + ch + _sha256_k(i) + w) & M32
164 let s0: i64 = _rotr32(a, 2) ^ _rotr32(a, 13) ^ _rotr32(a, 22)
165 let maj: i64 = ((a & b) ^ (a & c) ^ (b & c)) & M32
166 let temp2: i64 = (s0 + maj) & M32
167 h = g; g = f; f = e
168 e = (d + temp1) & M32
169 d = c; c = b; b = a
170 a = (temp1 + temp2) & M32
171 i = i + 1
172 }
173 _h_set(ctx_ptr, 0, (_h_get(ctx_ptr, 0) + a) & M32)
174 _h_set(ctx_ptr, 1, (_h_get(ctx_ptr, 1) + b) & M32)
175 _h_set(ctx_ptr, 2, (_h_get(ctx_ptr, 2) + c) & M32)
176 _h_set(ctx_ptr, 3, (_h_get(ctx_ptr, 3) + d) & M32)
177 _h_set(ctx_ptr, 4, (_h_get(ctx_ptr, 4) + e) & M32)
178 _h_set(ctx_ptr, 5, (_h_get(ctx_ptr, 5) + f) & M32)
179 _h_set(ctx_ptr, 6, (_h_get(ctx_ptr, 6) + g) & M32)
180 _h_set(ctx_ptr, 7, (_h_get(ctx_ptr, 7) + h) & M32)
181 return 0
182}
183
184// One-shot SHA-256: hash `in_len` bytes from in_ptr into out_ptr (32 B).
185// Same body as nx_sha256_wasm.nx::nx_sha256_one_shot; duplicated to keep
186// HMAC self-contained for the WAT target.
187func _sha256_one_shot(in_ptr: *u8, in_len: i64, ctx_ptr: *u8, out_ptr: *u8) -> i64 {
188 _h_set(ctx_ptr, 0, 0x6a09e667)
189 _h_set(ctx_ptr, 1, 0xbb67ae85)
190 _h_set(ctx_ptr, 2, 0x3c6ef372)
191 _h_set(ctx_ptr, 3, 0xa54ff53a)
192 _h_set(ctx_ptr, 4, 0x510e527f)
193 _h_set(ctx_ptr, 5, 0x9b05688c)
194 _h_set(ctx_ptr, 6, 0x1f83d9ab)
195 _h_set(ctx_ptr, 7, 0x5be0cd19)
196
197 var idx: i64 = 0
198 var bit_len: i64 = 0
199 var i: i64 = 0
200 while i < in_len {
201 ctx_ptr[idx] = in_ptr[i]
202 idx = idx + 1
203 bit_len = bit_len + 8
204 if idx == 64 {
205 _sha256_compress(ctx_ptr)
206 idx = 0
207 }
208 i = i + 1
209 }
210 ctx_ptr[idx] = 0x80
211 idx = idx + 1
212 if idx > 56 {
213 while idx < 64 {
214 ctx_ptr[idx] = 0
215 idx = idx + 1
216 }
217 _sha256_compress(ctx_ptr)
218 idx = 0
219 }
220 while idx < 56 {
221 ctx_ptr[idx] = 0
222 idx = idx + 1
223 }
224 ctx_ptr[56] = (bit_len >> 56) & 0xFF
225 ctx_ptr[57] = (bit_len >> 48) & 0xFF
226 ctx_ptr[58] = (bit_len >> 40) & 0xFF
227 ctx_ptr[59] = (bit_len >> 32) & 0xFF
228 ctx_ptr[60] = (bit_len >> 24) & 0xFF
229 ctx_ptr[61] = (bit_len >> 16) & 0xFF
230 ctx_ptr[62] = (bit_len >> 8) & 0xFF
231 ctx_ptr[63] = bit_len & 0xFF
232 _sha256_compress(ctx_ptr)
233
234 var k: i64 = 0
235 while k < 8 {
236 let hv: i64 = _h_get(ctx_ptr, k)
237 out_ptr[k * 4] = (hv >> 24) & 0xFF
238 out_ptr[k * 4 + 1] = (hv >> 16) & 0xFF
239 out_ptr[k * 4 + 2] = (hv >> 8) & 0xFF
240 out_ptr[k * 4 + 3] = hv & 0xFF
241 k = k + 1
242 }
243 return 0
244}
245
246// ---- HMAC-SHA-256 proper (RFC 2104 ยง2) ----
247
248// Compute HMAC-SHA-256(key, msg) into out_ptr (32 bytes).
249// `scratch_ptr` must point to >=1024 bytes of caller-owned linear memory.
250// Exported to JS as `nx_hmac_sha256_one_shot`.
251func nx_hmac_sha256_one_shot(key_ptr: *u8, key_len: i64,
252 msg_ptr: *u8, msg_len: i64,
253 scratch_ptr: *u8, out_ptr: *u8) -> i64 {
254 // K' = key normalized to B bytes. bytes 0..63 of scratch.
255 let kprime: *u8 = scratch_ptr
256 var j: i64 = 0
257 while j < HMAC_B {
258 kprime[j] = 0
259 j = j + 1
260 }
261 if key_len > HMAC_B {
262 // K' = SHA-256(key) || zero-pad.
263 let h_ctx: *u8 = (scratch_ptr as i64 + 256) as *u8 // bytes 256..767
264 _sha256_one_shot(key_ptr, key_len, h_ctx, kprime) // writes 32 B, rest already 0
265 } else {
266 var ki: i64 = 0
267 while ki < key_len {
268 kprime[ki] = key_ptr[ki]
269 ki = ki + 1
270 }
271 }
272
273 // ipad-keyed block at bytes 64..127.
274 let ipad_blk: *u8 = (scratch_ptr as i64 + 64) as *u8
275 var ii: i64 = 0
276 while ii < HMAC_B {
277 ipad_blk[ii] = kprime[ii] ^ 0x36
278 ii = ii + 1
279 }
280 // opad-keyed block at bytes 128..191.
281 let opad_blk: *u8 = (scratch_ptr as i64 + 128) as *u8
282 var oi: i64 = 0
283 while oi < HMAC_B {
284 opad_blk[oi] = kprime[oi] ^ 0x5c
285 oi = oi + 1
286 }
287
288 // Inner hash: H( ipad_blk || msg ). We need to feed ipad_blk (64 B)
289 // followed by msg (msg_len) into SHA-256. Since _sha256_one_shot
290 // takes a single contiguous buffer, we use a streaming approach by
291 // building a temporary buffer at bytes 768.. of scratch only when
292 // msg fits; for larger msg we instead inline the SHA-256 streaming
293 // logic. Simplest robust path: concat into a heap-allocated tmp.
294 // For our use cases (chat row hashing, MLS key schedule) msg is
295 // small (<= 512 bytes typically), but for generality we support
296 // arbitrary length by inlining the streaming.
297
298 // Inline streaming SHA-256 over (ipad_blk || msg):
299 let inner_ctx: *u8 = (scratch_ptr as i64 + 256) as *u8 // 512 bytes
300 _h_set(inner_ctx, 0, 0x6a09e667)
301 _h_set(inner_ctx, 1, 0xbb67ae85)
302 _h_set(inner_ctx, 2, 0x3c6ef372)
303 _h_set(inner_ctx, 3, 0xa54ff53a)
304 _h_set(inner_ctx, 4, 0x510e527f)
305 _h_set(inner_ctx, 5, 0x9b05688c)
306 _h_set(inner_ctx, 6, 0x1f83d9ab)
307 _h_set(inner_ctx, 7, 0x5be0cd19)
308 var inner_idx: i64 = 0
309 var inner_bits: i64 = 0
310 var ip: i64 = 0
311 while ip < HMAC_B {
312 inner_ctx[inner_idx] = ipad_blk[ip]
313 inner_idx = inner_idx + 1
314 inner_bits = inner_bits + 8
315 if inner_idx == 64 {
316 _sha256_compress(inner_ctx)
317 inner_idx = 0
318 }
319 ip = ip + 1
320 }
321 var mp: i64 = 0
322 while mp < msg_len {
323 inner_ctx[inner_idx] = msg_ptr[mp]
324 inner_idx = inner_idx + 1
325 inner_bits = inner_bits + 8
326 if inner_idx == 64 {
327 _sha256_compress(inner_ctx)
328 inner_idx = 0
329 }
330 mp = mp + 1
331 }
332 inner_ctx[inner_idx] = 0x80
333 inner_idx = inner_idx + 1
334 if inner_idx > 56 {
335 while inner_idx < 64 {
336 inner_ctx[inner_idx] = 0
337 inner_idx = inner_idx + 1
338 }
339 _sha256_compress(inner_ctx)
340 inner_idx = 0
341 }
342 while inner_idx < 56 {
343 inner_ctx[inner_idx] = 0
344 inner_idx = inner_idx + 1
345 }
346 inner_ctx[56] = (inner_bits >> 56) & 0xFF
347 inner_ctx[57] = (inner_bits >> 48) & 0xFF
348 inner_ctx[58] = (inner_bits >> 40) & 0xFF
349 inner_ctx[59] = (inner_bits >> 32) & 0xFF
350 inner_ctx[60] = (inner_bits >> 24) & 0xFF
351 inner_ctx[61] = (inner_bits >> 16) & 0xFF
352 inner_ctx[62] = (inner_bits >> 8) & 0xFF
353 inner_ctx[63] = inner_bits & 0xFF
354 _sha256_compress(inner_ctx)
355 // Inner hash digest stored at bytes 192..223 of scratch.
356 let inner_out: *u8 = (scratch_ptr as i64 + 192) as *u8
357 var dk: i64 = 0
358 while dk < 8 {
359 let hv: i64 = _h_get(inner_ctx, dk)
360 inner_out[dk * 4] = (hv >> 24) & 0xFF
361 inner_out[dk * 4 + 1] = (hv >> 16) & 0xFF
362 inner_out[dk * 4 + 2] = (hv >> 8) & 0xFF
363 inner_out[dk * 4 + 3] = hv & 0xFF
364 dk = dk + 1
365 }
366
367 // Outer hash: H( opad_blk || inner_out ). Reuse the same inner_ctx
368 // memory as the SHA-256 scratch (it gets re-init'd inside).
369 // Build the 96-byte concat at scratch[768..864].
370 let outer_in: *u8 = (scratch_ptr as i64 + 768) as *u8
371 var op: i64 = 0
372 while op < HMAC_B {
373 outer_in[op] = opad_blk[op]
374 op = op + 1
375 }
376 var ohi: i64 = 0
377 while ohi < 32 {
378 outer_in[HMAC_B + ohi] = inner_out[ohi]
379 ohi = ohi + 1
380 }
381 _sha256_one_shot(outer_in, HMAC_B + 32, inner_ctx, out_ptr)
382 return 0
383}