code wiki / (root) / nx_hkdf_sha256_wasm.nx

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}