nx_hkdf_sha256_wasm.nx source
↩ module page · 415 lines · 16430 B
1// nx_hkdf_sha256_wasm.nx -- HKDF-SHA-256 (RFC 5869) for WAT target.
2//
3// HKDF = Extract + Expand built on HMAC-SHA-256.
4// PRK = HKDF-Extract(salt, IKM) = HMAC(salt, IKM)
5// OKM = HKDF-Expand(PRK, info, L)
6// T(1) = HMAC(PRK, info || 0x01)
7// T(i) = HMAC(PRK, T(i-1) || info || i)
8// OKM = first L bytes of T(1) || T(2) || ...
9//
10// API for the embedder:
11//
12// nx_hkdf_extract(salt_ptr, salt_len, ikm_ptr, ikm_len,
13// scratch_ptr, prk_out_ptr) -> i64
14// prk_out_ptr -- 32 bytes
15// scratch_ptr -- 1024 bytes
16//
17// nx_hkdf_expand(prk_ptr, info_ptr, info_len, okm_len,
18// scratch_ptr, okm_out_ptr) -> i64
19// prk_ptr -- 32 bytes (PRK from extract)
20// okm_out_ptr -- okm_len bytes
21// scratch_ptr -- 2048 bytes
22// okm_len -- must be <= 8160 (= 255 * 32)
23//
24// nx_hkdf_extract_and_expand(salt, salt_len, ikm, ikm_len,
25// info, info_len, okm_len,
26// scratch, okm_out) -> i64
27// One-shot convenience that does both at once.
28//
29// Verified against RFC 5869 §A.1, §A.2 test vectors -- see
30// AppData/Local/Temp/hkdf_smoke.js.
31//
32// license_tier: INDEPENDENT_REDERIVE
33// genealogy_id: international-research-sources/ietf/rfc_5869 +
34// ietf/rfc_2104
35// lineage_id: nishi_hkdf_sha256_wasm_q11
36
37const M32: i64 = 0xFFFFFFFF
38const HMAC_B: i64 = 64
39
40// ---- SHA-256 + HMAC helpers (same pattern as nx_hmac_sha256_wasm) ----
41// Compiled-clean duplication: each WAT module is self-contained per
42// the L85 WAT-backend strategy.
43
44func _rotr32(x: i64, n: i64) -> i64 {
45 let nn: i64 = n & 31
46 let low: i64 = (x >> nn) & M32
47 let high: i64 = (x << (32 - nn)) & M32
48 return (low | high) & M32
49}
50
51func _sha256_k(i: i64) -> i64 {
52 if i == 0 { return 0x428a2f98 } if i == 1 { return 0x71374491 }
53 if i == 2 { return 0xb5c0fbcf } if i == 3 { return 0xe9b5dba5 }
54 if i == 4 { return 0x3956c25b } if i == 5 { return 0x59f111f1 }
55 if i == 6 { return 0x923f82a4 } if i == 7 { return 0xab1c5ed5 }
56 if i == 8 { return 0xd807aa98 } if i == 9 { return 0x12835b01 }
57 if i == 10 { return 0x243185be } if i == 11 { return 0x550c7dc3 }
58 if i == 12 { return 0x72be5d74 } if i == 13 { return 0x80deb1fe }
59 if i == 14 { return 0x9bdc06a7 } if i == 15 { return 0xc19bf174 }
60 if i == 16 { return 0xe49b69c1 } if i == 17 { return 0xefbe4786 }
61 if i == 18 { return 0x0fc19dc6 } if i == 19 { return 0x240ca1cc }
62 if i == 20 { return 0x2de92c6f } if i == 21 { return 0x4a7484aa }
63 if i == 22 { return 0x5cb0a9dc } if i == 23 { return 0x76f988da }
64 if i == 24 { return 0x983e5152 } if i == 25 { return 0xa831c66d }
65 if i == 26 { return 0xb00327c8 } if i == 27 { return 0xbf597fc7 }
66 if i == 28 { return 0xc6e00bf3 } if i == 29 { return 0xd5a79147 }
67 if i == 30 { return 0x06ca6351 } if i == 31 { return 0x14292967 }
68 if i == 32 { return 0x27b70a85 } if i == 33 { return 0x2e1b2138 }
69 if i == 34 { return 0x4d2c6dfc } if i == 35 { return 0x53380d13 }
70 if i == 36 { return 0x650a7354 } if i == 37 { return 0x766a0abb }
71 if i == 38 { return 0x81c2c92e } if i == 39 { return 0x92722c85 }
72 if i == 40 { return 0xa2bfe8a1 } if i == 41 { return 0xa81a664b }
73 if i == 42 { return 0xc24b8b70 } if i == 43 { return 0xc76c51a3 }
74 if i == 44 { return 0xd192e819 } if i == 45 { return 0xd6990624 }
75 if i == 46 { return 0xf40e3585 } if i == 47 { return 0x106aa070 }
76 if i == 48 { return 0x19a4c116 } if i == 49 { return 0x1e376c08 }
77 if i == 50 { return 0x2748774c } if i == 51 { return 0x34b0bcb5 }
78 if i == 52 { return 0x391c0cb3 } if i == 53 { return 0x4ed8aa4a }
79 if i == 54 { return 0x5b9cca4f } if i == 55 { return 0x682e6ff3 }
80 if i == 56 { return 0x748f82ee } if i == 57 { return 0x78a5636f }
81 if i == 58 { return 0x84c87814 } if i == 59 { return 0x8cc70208 }
82 if i == 60 { return 0x90befffa } if i == 61 { return 0xa4506ceb }
83 if i == 62 { return 0xbef9a3f7 }
84 return 0xc67178f2
85}
86
87func _blk_word_be(ctx_ptr: *u8, off: i64) -> i64 {
88 let b0: i64 = ctx_ptr[off]
89 let b1: i64 = ctx_ptr[off + 1]
90 let b2: i64 = ctx_ptr[off + 2]
91 let b3: i64 = ctx_ptr[off + 3]
92 return ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3) & M32
93}
94
95func _h_get(ctx_ptr: *u8, i: i64) -> i64 {
96 let off: i64 = 64 + i * 4
97 let b0: i64 = ctx_ptr[off]
98 let b1: i64 = ctx_ptr[off + 1]
99 let b2: i64 = ctx_ptr[off + 2]
100 let b3: i64 = ctx_ptr[off + 3]
101 return (b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)) & M32
102}
103func _h_set(ctx_ptr: *u8, i: i64, v: i64) -> i64 {
104 let off: i64 = 64 + i * 4
105 ctx_ptr[off] = v & 0xFF
106 ctx_ptr[off + 1] = (v >> 8) & 0xFF
107 ctx_ptr[off + 2] = (v >> 16) & 0xFF
108 ctx_ptr[off + 3] = (v >> 24) & 0xFF
109 return 0
110}
111
112func _sha256_compress(ctx_ptr: *u8) -> i64 {
113 var i: i64 = 0
114 while i < 16 {
115 let w: i64 = _blk_word_be(ctx_ptr, i * 4)
116 let woff: i64 = 105 + i * 4
117 ctx_ptr[woff] = w & 0xFF
118 ctx_ptr[woff + 1] = (w >> 8) & 0xFF
119 ctx_ptr[woff + 2] = (w >> 16) & 0xFF
120 ctx_ptr[woff + 3] = (w >> 24) & 0xFF
121 i = i + 1
122 }
123 i = 16
124 while i < 64 {
125 let w_2_o: i64 = 105 + (i - 2) * 4
126 let w_15_o: i64 = 105 + (i - 15) * 4
127 let w_7_o: i64 = 105 + (i - 7) * 4
128 let w_16_o: i64 = 105 + (i - 16) * 4
129 let w_2: i64 = (ctx_ptr[w_2_o] as i64) | ((ctx_ptr[w_2_o+1] as i64) << 8) |
130 ((ctx_ptr[w_2_o+2] as i64) << 16) | ((ctx_ptr[w_2_o+3] as i64) << 24)
131 let w_15: i64 = (ctx_ptr[w_15_o] as i64) | ((ctx_ptr[w_15_o+1] as i64) << 8) |
132 ((ctx_ptr[w_15_o+2] as i64) << 16) | ((ctx_ptr[w_15_o+3] as i64) << 24)
133 let w_7: i64 = (ctx_ptr[w_7_o] as i64) | ((ctx_ptr[w_7_o+1] as i64) << 8) |
134 ((ctx_ptr[w_7_o+2] as i64) << 16) | ((ctx_ptr[w_7_o+3] as i64) << 24)
135 let w_16: i64 = (ctx_ptr[w_16_o] as i64) | ((ctx_ptr[w_16_o+1] as i64) << 8) |
136 ((ctx_ptr[w_16_o+2] as i64) << 16) | ((ctx_ptr[w_16_o+3] as i64) << 24)
137 let s0: i64 = _rotr32(w_15, 7) ^ _rotr32(w_15, 18) ^ ((w_15 >> 3) & M32)
138 let s1: i64 = _rotr32(w_2, 17) ^ _rotr32(w_2, 19) ^ ((w_2 >> 10) & M32)
139 let wi: i64 = ((w_16 + s0 + w_7 + s1) as i64) & M32
140 let woff: i64 = 105 + i * 4
141 ctx_ptr[woff] = wi & 0xFF
142 ctx_ptr[woff + 1] = (wi >> 8) & 0xFF
143 ctx_ptr[woff + 2] = (wi >> 16) & 0xFF
144 ctx_ptr[woff + 3] = (wi >> 24) & 0xFF
145 i = i + 1
146 }
147 var a: i64 = _h_get(ctx_ptr, 0)
148 var b: i64 = _h_get(ctx_ptr, 1)
149 var c: i64 = _h_get(ctx_ptr, 2)
150 var d: i64 = _h_get(ctx_ptr, 3)
151 var e: i64 = _h_get(ctx_ptr, 4)
152 var f: i64 = _h_get(ctx_ptr, 5)
153 var g: i64 = _h_get(ctx_ptr, 6)
154 var h: i64 = _h_get(ctx_ptr, 7)
155 i = 0
156 while i < 64 {
157 let woff: i64 = 105 + i * 4
158 let w: i64 = (ctx_ptr[woff] as i64) | ((ctx_ptr[woff+1] as i64) << 8) |
159 ((ctx_ptr[woff+2] as i64) << 16) | ((ctx_ptr[woff+3] as i64) << 24)
160 let s1: i64 = _rotr32(e, 6) ^ _rotr32(e, 11) ^ _rotr32(e, 25)
161 let ch: i64 = ((e & f) ^ ((~e) & g & M32)) & M32
162 let temp1: i64 = (h + s1 + ch + _sha256_k(i) + w) & M32
163 let s0: i64 = _rotr32(a, 2) ^ _rotr32(a, 13) ^ _rotr32(a, 22)
164 let maj: i64 = ((a & b) ^ (a & c) ^ (b & c)) & M32
165 let temp2: i64 = (s0 + maj) & M32
166 h = g; g = f; f = e
167 e = (d + temp1) & M32
168 d = c; c = b; b = a
169 a = (temp1 + temp2) & M32
170 i = i + 1
171 }
172 _h_set(ctx_ptr, 0, (_h_get(ctx_ptr, 0) + a) & M32)
173 _h_set(ctx_ptr, 1, (_h_get(ctx_ptr, 1) + b) & M32)
174 _h_set(ctx_ptr, 2, (_h_get(ctx_ptr, 2) + c) & M32)
175 _h_set(ctx_ptr, 3, (_h_get(ctx_ptr, 3) + d) & M32)
176 _h_set(ctx_ptr, 4, (_h_get(ctx_ptr, 4) + e) & M32)
177 _h_set(ctx_ptr, 5, (_h_get(ctx_ptr, 5) + f) & M32)
178 _h_set(ctx_ptr, 6, (_h_get(ctx_ptr, 6) + g) & M32)
179 _h_set(ctx_ptr, 7, (_h_get(ctx_ptr, 7) + h) & M32)
180 return 0
181}
182
183func _sha256_one_shot(in_ptr: *u8, in_len: i64, ctx_ptr: *u8, out_ptr: *u8) -> i64 {
184 _h_set(ctx_ptr, 0, 0x6a09e667)
185 _h_set(ctx_ptr, 1, 0xbb67ae85)
186 _h_set(ctx_ptr, 2, 0x3c6ef372)
187 _h_set(ctx_ptr, 3, 0xa54ff53a)
188 _h_set(ctx_ptr, 4, 0x510e527f)
189 _h_set(ctx_ptr, 5, 0x9b05688c)
190 _h_set(ctx_ptr, 6, 0x1f83d9ab)
191 _h_set(ctx_ptr, 7, 0x5be0cd19)
192 var idx: i64 = 0
193 var bit_len: i64 = 0
194 var i: i64 = 0
195 while i < in_len {
196 ctx_ptr[idx] = in_ptr[i]
197 idx = idx + 1
198 bit_len = bit_len + 8
199 if idx == 64 {
200 _sha256_compress(ctx_ptr)
201 idx = 0
202 }
203 i = i + 1
204 }
205 ctx_ptr[idx] = 0x80
206 idx = idx + 1
207 if idx > 56 {
208 while idx < 64 {
209 ctx_ptr[idx] = 0
210 idx = idx + 1
211 }
212 _sha256_compress(ctx_ptr)
213 idx = 0
214 }
215 while idx < 56 {
216 ctx_ptr[idx] = 0
217 idx = idx + 1
218 }
219 ctx_ptr[56] = (bit_len >> 56) & 0xFF
220 ctx_ptr[57] = (bit_len >> 48) & 0xFF
221 ctx_ptr[58] = (bit_len >> 40) & 0xFF
222 ctx_ptr[59] = (bit_len >> 32) & 0xFF
223 ctx_ptr[60] = (bit_len >> 24) & 0xFF
224 ctx_ptr[61] = (bit_len >> 16) & 0xFF
225 ctx_ptr[62] = (bit_len >> 8) & 0xFF
226 ctx_ptr[63] = bit_len & 0xFF
227 _sha256_compress(ctx_ptr)
228 var k: i64 = 0
229 while k < 8 {
230 let hv: i64 = _h_get(ctx_ptr, k)
231 out_ptr[k * 4] = (hv >> 24) & 0xFF
232 out_ptr[k * 4 + 1] = (hv >> 16) & 0xFF
233 out_ptr[k * 4 + 2] = (hv >> 8) & 0xFF
234 out_ptr[k * 4 + 3] = hv & 0xFF
235 k = k + 1
236 }
237 return 0
238}
239
240// HMAC-SHA-256 used internally. Identical to nx_hmac_sha256_wasm's
241// implementation but inlined here to keep this module self-contained.
242// scratch_ptr layout (caller-allocated, >=1024 bytes):
243// 0..63 : K' block buffer
244// 64..127 : K' XOR ipad
245// 128..191 : K' XOR opad
246// 192..223 : inner hash output
247// 256..767 : SHA-256 ctx scratch
248// 768..863 : opad || inner_hash concat for outer
249func _hmac_sha256(key_ptr: *u8, key_len: i64, msg_ptr: *u8, msg_len: i64,
250 scratch_ptr: *u8, out_ptr: *u8) -> i64 {
251 let kprime: *u8 = scratch_ptr
252 var j: i64 = 0
253 while j < HMAC_B { kprime[j] = 0; j = j + 1 }
254 if key_len > HMAC_B {
255 let h_ctx: *u8 = (scratch_ptr as i64 + 256) as *u8
256 _sha256_one_shot(key_ptr, key_len, h_ctx, kprime)
257 } else {
258 var ki: i64 = 0
259 while ki < key_len { kprime[ki] = key_ptr[ki]; ki = ki + 1 }
260 }
261 let ipad_blk: *u8 = (scratch_ptr as i64 + 64) as *u8
262 var ii: i64 = 0
263 while ii < HMAC_B { ipad_blk[ii] = kprime[ii] ^ 0x36; ii = ii + 1 }
264 let opad_blk: *u8 = (scratch_ptr as i64 + 128) as *u8
265 var oi: i64 = 0
266 while oi < HMAC_B { opad_blk[oi] = kprime[oi] ^ 0x5c; oi = oi + 1 }
267 let inner_ctx: *u8 = (scratch_ptr as i64 + 256) as *u8
268 _h_set(inner_ctx, 0, 0x6a09e667)
269 _h_set(inner_ctx, 1, 0xbb67ae85)
270 _h_set(inner_ctx, 2, 0x3c6ef372)
271 _h_set(inner_ctx, 3, 0xa54ff53a)
272 _h_set(inner_ctx, 4, 0x510e527f)
273 _h_set(inner_ctx, 5, 0x9b05688c)
274 _h_set(inner_ctx, 6, 0x1f83d9ab)
275 _h_set(inner_ctx, 7, 0x5be0cd19)
276 var inner_idx: i64 = 0
277 var inner_bits: i64 = 0
278 var ip: i64 = 0
279 while ip < HMAC_B {
280 inner_ctx[inner_idx] = ipad_blk[ip]
281 inner_idx = inner_idx + 1
282 inner_bits = inner_bits + 8
283 if inner_idx == 64 { _sha256_compress(inner_ctx); inner_idx = 0 }
284 ip = ip + 1
285 }
286 var mp: i64 = 0
287 while mp < msg_len {
288 inner_ctx[inner_idx] = msg_ptr[mp]
289 inner_idx = inner_idx + 1
290 inner_bits = inner_bits + 8
291 if inner_idx == 64 { _sha256_compress(inner_ctx); inner_idx = 0 }
292 mp = mp + 1
293 }
294 inner_ctx[inner_idx] = 0x80
295 inner_idx = inner_idx + 1
296 if inner_idx > 56 {
297 while inner_idx < 64 { inner_ctx[inner_idx] = 0; inner_idx = inner_idx + 1 }
298 _sha256_compress(inner_ctx)
299 inner_idx = 0
300 }
301 while inner_idx < 56 { inner_ctx[inner_idx] = 0; inner_idx = inner_idx + 1 }
302 inner_ctx[56] = (inner_bits >> 56) & 0xFF
303 inner_ctx[57] = (inner_bits >> 48) & 0xFF
304 inner_ctx[58] = (inner_bits >> 40) & 0xFF
305 inner_ctx[59] = (inner_bits >> 32) & 0xFF
306 inner_ctx[60] = (inner_bits >> 24) & 0xFF
307 inner_ctx[61] = (inner_bits >> 16) & 0xFF
308 inner_ctx[62] = (inner_bits >> 8) & 0xFF
309 inner_ctx[63] = inner_bits & 0xFF
310 _sha256_compress(inner_ctx)
311 let inner_out: *u8 = (scratch_ptr as i64 + 192) as *u8
312 var dk: i64 = 0
313 while dk < 8 {
314 let hv: i64 = _h_get(inner_ctx, dk)
315 inner_out[dk * 4] = (hv >> 24) & 0xFF
316 inner_out[dk * 4 + 1] = (hv >> 16) & 0xFF
317 inner_out[dk * 4 + 2] = (hv >> 8) & 0xFF
318 inner_out[dk * 4 + 3] = hv & 0xFF
319 dk = dk + 1
320 }
321 let outer_in: *u8 = (scratch_ptr as i64 + 768) as *u8
322 var op: i64 = 0
323 while op < HMAC_B { outer_in[op] = opad_blk[op]; op = op + 1 }
324 var ohi: i64 = 0
325 while ohi < 32 { outer_in[HMAC_B + ohi] = inner_out[ohi]; ohi = ohi + 1 }
326 _sha256_one_shot(outer_in, HMAC_B + 32, inner_ctx, out_ptr)
327 return 0
328}
329
330// ---- HKDF proper ----
331
332// HKDF-Extract(salt, IKM) = HMAC(salt, IKM) -> 32-byte PRK.
333// If salt_len == 0, RFC 5869 §2.2 says use 32 zero bytes as the salt.
334func nx_hkdf_extract(salt_ptr: *u8, salt_len: i64,
335 ikm_ptr: *u8, ikm_len: i64,
336 scratch_ptr: *u8, prk_out: *u8) -> i64 {
337 if salt_len == 0 {
338 // Use 32 zero bytes from scratch[960..991] as the salt.
339 let zero_salt: *u8 = (scratch_ptr as i64 + 960) as *u8
340 var z: i64 = 0
341 while z < 32 { zero_salt[z] = 0; z = z + 1 }
342 _hmac_sha256(zero_salt, 32, ikm_ptr, ikm_len, scratch_ptr, prk_out)
343 } else {
344 _hmac_sha256(salt_ptr, salt_len, ikm_ptr, ikm_len, scratch_ptr, prk_out)
345 }
346 return 0
347}
348
349// HKDF-Expand(PRK, info, L) -> first L bytes of T(1) || T(2) || ...
350// T(i) = HMAC(PRK, T(i-1) || info || i_byte)
351// T(0) = "" (empty)
352// L MUST be <= 255 * 32 = 8160 bytes.
353//
354// scratch_ptr layout (>=2048 bytes):
355// 0..1023 : HMAC scratch (passed to _hmac_sha256)
356// 1024..1055: T(i-1) buffer (32 B)
357// 1056..1087: T(i) output buffer (32 B)
358// 1088..1599: HMAC input buffer (T(i-1) || info || counter; up to 32 + 480 + 1)
359func nx_hkdf_expand(prk_ptr: *u8, info_ptr: *u8, info_len: i64,
360 okm_len: i64, scratch_ptr: *u8, okm_out: *u8) -> i64 {
361 if okm_len > 8160 { return -1 }
362 let hmac_scratch: *u8 = scratch_ptr
363 let t_prev: *u8 = (scratch_ptr as i64 + 1024) as *u8
364 let t_cur: *u8 = (scratch_ptr as i64 + 1056) as *u8
365 let hmac_in: *u8 = (scratch_ptr as i64 + 1088) as *u8
366
367 let n: i64 = (okm_len + 31) / 32 // number of T blocks
368 var written: i64 = 0
369 var i: i64 = 1
370 var prev_len: i64 = 0
371 while i <= n {
372 // Build hmac_in = T(i-1) || info || i (1-byte counter)
373 var p: i64 = 0
374 var pp: i64 = 0
375 while pp < prev_len { hmac_in[p] = t_prev[pp]; p = p + 1; pp = pp + 1 }
376 var ii: i64 = 0
377 while ii < info_len { hmac_in[p] = info_ptr[ii]; p = p + 1; ii = ii + 1 }
378 hmac_in[p] = i & 0xFF
379 p = p + 1
380
381 _hmac_sha256(prk_ptr, 32, hmac_in, p, hmac_scratch, t_cur)
382
383 // Copy as many bytes as remain to okm_out.
384 let need: i64 = okm_len - written
385 var take: i64 = 32
386 if need < 32 { take = need }
387 var ki: i64 = 0
388 while ki < take {
389 okm_out[written + ki] = t_cur[ki]
390 ki = ki + 1
391 }
392 written = written + take
393
394 // T(i) becomes T(i-1) for the next round.
395 var ci: i64 = 0
396 while ci < 32 { t_prev[ci] = t_cur[ci]; ci = ci + 1 }
397 prev_len = 32
398 i = i + 1
399 }
400 return 0
401}
402
403// One-shot convenience: Extract then Expand.
404// scratch_ptr must be >=2080 bytes (1024 for HMAC + 32 PRK + 32+32+512 expand).
405// We carve PRK out of scratch[2048..2079].
406func nx_hkdf_extract_and_expand(
407 salt_ptr: *u8, salt_len: i64,
408 ikm_ptr: *u8, ikm_len: i64,
409 info_ptr: *u8, info_len: i64,
410 okm_len: i64,
411 scratch_ptr: *u8, okm_out: *u8) -> i64 {
412 let prk: *u8 = (scratch_ptr as i64 + 2048) as *u8
413 nx_hkdf_extract(salt_ptr, salt_len, ikm_ptr, ikm_len, scratch_ptr, prk)
414 return nx_hkdf_expand(prk, info_ptr, info_len, okm_len, scratch_ptr, okm_out)
415}