code wiki / (root) / nx_hmac_sha256_wasm.nx

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}