code wiki / (root) / nx_chacha20_poly1305_wasm.nx

nx_chacha20_poly1305_wasm.nx source

↩ module page · 468 lines · 16749 B

1// nx_chacha20_poly1305_wasm.nx -- ChaCha20-Poly1305 AEAD (RFC 7539 §2.8). 2// 3// AEAD construction: 4// otk = chacha20_block(key, counter=0, nonce)[:32] 5// ciphertext = chacha20_xor(key, counter=1, nonce, plaintext) 6// mac_data = aad || pad16(aad) || ct || pad16(ct) || 7// le64(aad_len) || le64(ct_len) 8// tag = poly1305(otk, mac_data) 9// return ciphertext || tag 10// 11// pad16(x) = zero-bytes to reach the next 16-byte multiple; empty if 12// already aligned. 13// 14// API for the embedder: 15// 16// nx_chacha20poly1305_seal(key, nonce, 17// aad, aad_len, 18// plaintext, pt_len, 19// scratch, ciphertext_out, tag_out) -> i64 20// nx_chacha20poly1305_open(key, nonce, 21// aad, aad_len, 22// ciphertext, ct_len, 23// tag, 24// scratch, plaintext_out) -> i64 25// returns 0 on auth success + plaintext_out filled, 26// -1 on auth failure (plaintext_out NOT touched). 27// 28// scratch_ptr must be >= 4096 bytes (Poly1305 needs 128 + AEAD staging). 29// 30// Verified against RFC 7539 §2.8.2 test vector. 31// 32// license_tier: INDEPENDENT_REDERIVE 33// genealogy_id: international-research-sources/ietf/rfc_7539 34// lineage_id: nishi_chacha20poly1305_wasm_q11 35 36const M32: i64 = 0xFFFFFFFF 37const M26: i64 = 0x3ffffff 38 39// ===== ChaCha20 (duplicated from nx_chacha20_wasm.nx) ===== 40 41func _le32_read(buf: *u8, off: i64) -> i64 { 42 return ((buf[off] as i64) | 43 ((buf[off + 1] as i64) << 8) | 44 ((buf[off + 2] as i64) << 16) | 45 ((buf[off + 3] as i64) << 24)) & M32 46} 47 48func _le32_write(buf: *u8, off: i64, v: i64) -> i64 { 49 buf[off] = v & 0xFF 50 buf[off + 1] = (v >> 8) & 0xFF 51 buf[off + 2] = (v >> 16) & 0xFF 52 buf[off + 3] = (v >> 24) & 0xFF 53 return 0 54} 55 56func _rotl32(x: i64, n: i64) -> i64 { 57 let nn: i64 = n & 31 58 let lo: i64 = (x << nn) & M32 59 let hi: i64 = (x >> (32 - nn)) & M32 60 return (lo | hi) & M32 61} 62 63func _qr(state: *u8, ai: i64, bi: i64, ci: i64, di: i64) -> i64 { 64 var a: i64 = _le32_read(state, ai * 4) 65 var b: i64 = _le32_read(state, bi * 4) 66 var c: i64 = _le32_read(state, ci * 4) 67 var d: i64 = _le32_read(state, di * 4) 68 a = (a + b) & M32; d = d ^ a; d = _rotl32(d, 16) 69 c = (c + d) & M32; b = b ^ c; b = _rotl32(b, 12) 70 a = (a + b) & M32; d = d ^ a; d = _rotl32(d, 8) 71 c = (c + d) & M32; b = b ^ c; b = _rotl32(b, 7) 72 _le32_write(state, ai * 4, a) 73 _le32_write(state, bi * 4, b) 74 _le32_write(state, ci * 4, c) 75 _le32_write(state, di * 4, d) 76 return 0 77} 78 79func _chacha20_block(init: *u8, work: *u8, keystream: *u8) -> i64 { 80 var i: i64 = 0 81 while i < 64 { work[i] = init[i]; i = i + 1 } 82 var r: i64 = 0 83 while r < 10 { 84 _qr(work, 0, 4, 8, 12) 85 _qr(work, 1, 5, 9, 13) 86 _qr(work, 2, 6, 10, 14) 87 _qr(work, 3, 7, 11, 15) 88 _qr(work, 0, 5, 10, 15) 89 _qr(work, 1, 6, 11, 12) 90 _qr(work, 2, 7, 8, 13) 91 _qr(work, 3, 4, 9, 14) 92 r = r + 1 93 } 94 var w: i64 = 0 95 while w < 16 { 96 let init_w: i64 = _le32_read(init, w * 4) 97 let work_w: i64 = _le32_read(work, w * 4) 98 let sum: i64 = (init_w + work_w) & M32 99 _le32_write(keystream, w * 4, sum) 100 w = w + 1 101 } 102 return 0 103} 104 105func _chacha20_init_state(init: *u8, key: *u8, nonce: *u8, counter: i64) -> i64 { 106 _le32_write(init, 0, 0x61707865) 107 _le32_write(init, 4, 0x3320646e) 108 _le32_write(init, 8, 0x79622d32) 109 _le32_write(init, 12, 0x6b206574) 110 var i: i64 = 0 111 while i < 32 { init[16 + i] = key[i]; i = i + 1 } 112 _le32_write(init, 48, counter & M32) 113 var j: i64 = 0 114 while j < 12 { init[52 + j] = nonce[j]; j = j + 1 } 115 return 0 116} 117 118// XOR `in_len` bytes from in_ptr with the keystream from (key,nonce) 119// starting at the given counter. 120func _chacha20_xor(key: *u8, nonce: *u8, counter: i64, 121 in_ptr: *u8, in_len: i64, 122 init: *u8, work: *u8, ks: *u8, 123 out_ptr: *u8) -> i64 { 124 _chacha20_init_state(init, key, nonce, counter) 125 var pos: i64 = 0 126 var blk: i64 = counter 127 while pos < in_len { 128 _le32_write(init, 48, blk & M32) 129 _chacha20_block(init, work, ks) 130 var b: i64 = 0 131 while b < 64 { 132 if pos + b >= in_len { b = 64 } 133 else { 134 out_ptr[pos + b] = (in_ptr[pos + b] ^ ks[b]) & 0xFF 135 b = b + 1 136 } 137 } 138 pos = pos + 64 139 blk = blk + 1 140 } 141 return 0 142} 143 144// ===== Poly1305 (duplicated from nx_poly1305_wasm.nx) ===== 145 146func _i64_get(buf: *u8, idx: i64) -> i64 { 147 let off: i64 = idx * 8 148 return (buf[off] as i64) | 149 ((buf[off + 1] as i64) << 8) | 150 ((buf[off + 2] as i64) << 16) | 151 ((buf[off + 3] as i64) << 24) | 152 ((buf[off + 4] as i64) << 32) | 153 ((buf[off + 5] as i64) << 40) | 154 ((buf[off + 6] as i64) << 48) | 155 ((buf[off + 7] as i64) << 56) 156} 157func _i64_set(buf: *u8, idx: i64, v: i64) -> i64 { 158 let off: i64 = idx * 8 159 buf[off] = v & 0xFF 160 buf[off + 1] = (v >> 8) & 0xFF 161 buf[off + 2] = (v >> 16) & 0xFF 162 buf[off + 3] = (v >> 24) & 0xFF 163 buf[off + 4] = (v >> 32) & 0xFF 164 buf[off + 5] = (v >> 40) & 0xFF 165 buf[off + 6] = (v >> 48) & 0xFF 166 buf[off + 7] = (v >> 56) & 0xFF 167 return 0 168} 169 170func _clamp_and_split_r(key: *u8, r_lims: *u8) -> i64 { 171 let r0: i64 = _le32_read(key, 0) & 0x0fffffff 172 let r1: i64 = _le32_read(key, 4) & 0x0ffffffc 173 let r2: i64 = _le32_read(key, 8) & 0x0ffffffc 174 let r3: i64 = _le32_read(key, 12) & 0x0ffffffc 175 let l0: i64 = r0 & M26 176 let l1: i64 = ((r0 >> 26) | (r1 << 6)) & M26 177 let l2: i64 = ((r1 >> 20) | (r2 << 12)) & M26 178 let l3: i64 = ((r2 >> 14) | (r3 << 18)) & M26 179 let l4: i64 = (r3 >> 8) & M26 180 _i64_set(r_lims, 0, l0) 181 _i64_set(r_lims, 1, l1) 182 _i64_set(r_lims, 2, l2) 183 _i64_set(r_lims, 3, l3) 184 _i64_set(r_lims, 4, l4) 185 return 0 186} 187 188func _poly_block(acc: *u8, r_lims: *u8, blk: *u8, blk_len: i64, hi_bit: i64) -> i64 { 189 var w0: i64 = 0 190 var w1: i64 = 0 191 var w2: i64 = 0 192 var w3: i64 = 0 193 var i: i64 = 0 194 while i < 4 { 195 let o: i64 = i * 4 196 if o + 3 < blk_len { 197 let w: i64 = _le32_read(blk, o) 198 if i == 0 { w0 = w } else { if i == 1 { w1 = w } else { if i == 2 { w2 = w } else { w3 = w } } } 199 } else { 200 var v: i64 = 0 201 var jj: i64 = 0 202 while jj < 4 { 203 if o + jj < blk_len { v = v | ((blk[o + jj] as i64) << (jj * 8)) } 204 jj = jj + 1 205 } 206 if i == 0 { w0 = v } else { if i == 1 { w1 = v } else { if i == 2 { w2 = v } else { w3 = v } } } 207 } 208 i = i + 1 209 } 210 let n0: i64 = w0 & M26 211 let n1: i64 = ((w0 >> 26) | (w1 << 6)) & M26 212 let n2: i64 = ((w1 >> 20) | (w2 << 12)) & M26 213 let n3: i64 = ((w2 >> 14) | (w3 << 18)) & M26 214 let n4: i64 = ((w3 >> 8) & M26) | (hi_bit << 24) 215 let a0: i64 = _i64_get(acc, 0) + n0 216 let a1: i64 = _i64_get(acc, 1) + n1 217 let a2: i64 = _i64_get(acc, 2) + n2 218 let a3: i64 = _i64_get(acc, 3) + n3 219 let a4: i64 = _i64_get(acc, 4) + n4 220 let r0: i64 = _i64_get(r_lims, 0) 221 let r1: i64 = _i64_get(r_lims, 1) 222 let r2: i64 = _i64_get(r_lims, 2) 223 let r3: i64 = _i64_get(r_lims, 3) 224 let r4: i64 = _i64_get(r_lims, 4) 225 let s1: i64 = r1 * 5 226 let s2: i64 = r2 * 5 227 let s3: i64 = r3 * 5 228 let s4: i64 = r4 * 5 229 let d0: i64 = a0*r0 + a1*s4 + a2*s3 + a3*s2 + a4*s1 230 let d1: i64 = a0*r1 + a1*r0 + a2*s4 + a3*s3 + a4*s2 231 let d2: i64 = a0*r2 + a1*r1 + a2*r0 + a3*s4 + a4*s3 232 let d3: i64 = a0*r3 + a1*r2 + a2*r1 + a3*r0 + a4*s4 233 let d4: i64 = a0*r4 + a1*r3 + a2*r2 + a3*r1 + a4*r0 234 var c: i64 = 0 235 var e0: i64 = (d0 + c) & M26; c = (d0 + c) >> 26 236 var e1: i64 = (d1 + c) & M26; c = (d1 + c) >> 26 237 var e2: i64 = (d2 + c) & M26; c = (d2 + c) >> 26 238 var e3: i64 = (d3 + c) & M26; c = (d3 + c) >> 26 239 var e4: i64 = (d4 + c) & M26; c = (d4 + c) >> 26 240 e0 = e0 + c * 5 241 let c2: i64 = e0 >> 26 242 e0 = e0 & M26 243 e1 = e1 + c2 244 _i64_set(acc, 0, e0) 245 _i64_set(acc, 1, e1) 246 _i64_set(acc, 2, e2) 247 _i64_set(acc, 3, e3) 248 _i64_set(acc, 4, e4) 249 return 0 250} 251 252// Compute MAC into out (16 B). key is 32 bytes (r||s). scratch >=128. 253func _poly1305(key: *u8, msg: *u8, msg_len: i64, scratch: *u8, out: *u8) -> i64 { 254 let r_lims: *u8 = scratch 255 let acc: *u8 = (scratch as i64 + 40) as *u8 256 let blk: *u8 = (scratch as i64 + 80) as *u8 257 _clamp_and_split_r(key, r_lims) 258 var i: i64 = 0 259 while i < 5 { _i64_set(acc, i, 0); i = i + 1 } 260 var pos: i64 = 0 261 while pos + 16 <= msg_len { 262 var bi: i64 = 0 263 while bi < 16 { blk[bi] = msg[pos + bi]; bi = bi + 1 } 264 _poly_block(acc, r_lims, blk, 16, 1) 265 pos = pos + 16 266 } 267 let rem: i64 = msg_len - pos 268 if rem > 0 { 269 var bj: i64 = 0 270 while bj < 16 { blk[bj] = 0; bj = bj + 1 } 271 var bk: i64 = 0 272 while bk < rem { blk[bk] = msg[pos + bk]; bk = bk + 1 } 273 blk[rem] = 1 274 _poly_block(acc, r_lims, blk, 16, 0) 275 } 276 // Final reduce. 277 var h0: i64 = _i64_get(acc, 0) 278 var h1: i64 = _i64_get(acc, 1) 279 var h2: i64 = _i64_get(acc, 2) 280 var h3: i64 = _i64_get(acc, 3) 281 var h4: i64 = _i64_get(acc, 4) 282 var cc: i64 = h1 >> 26; h1 = h1 & M26; h2 = h2 + cc 283 cc = h2 >> 26; h2 = h2 & M26; h3 = h3 + cc 284 cc = h3 >> 26; h3 = h3 & M26; h4 = h4 + cc 285 cc = h4 >> 26; h4 = h4 & M26; h0 = h0 + cc * 5 286 cc = h0 >> 26; h0 = h0 & M26; h1 = h1 + cc 287 var g0: i64 = h0 + 5 288 cc = g0 >> 26; g0 = g0 & M26 289 var g1: i64 = h1 + cc; cc = g1 >> 26; g1 = g1 & M26 290 var g2: i64 = h2 + cc; cc = g2 >> 26; g2 = g2 & M26 291 var g3: i64 = h3 + cc; cc = g3 >> 26; g3 = g3 & M26 292 var g4: i64 = h4 + cc - (1 << 26) 293 var mask: i64 = 0 294 if g4 >= 0 { mask = 0xFFFFFFFFFFFFFFFF as i64 } 295 h0 = (h0 & (~mask & 0xFFFFFFFFFFFFFFFF)) | (g0 & mask) 296 h1 = (h1 & (~mask & 0xFFFFFFFFFFFFFFFF)) | (g1 & mask) 297 h2 = (h2 & (~mask & 0xFFFFFFFFFFFFFFFF)) | (g2 & mask) 298 h3 = (h3 & (~mask & 0xFFFFFFFFFFFFFFFF)) | (g3 & mask) 299 h4 = (h4 & (~mask & 0xFFFFFFFFFFFFFFFF)) | (g4 & mask) 300 let f0: i64 = ( h0 | (h1 << 26)) & M32 301 let f1: i64 = ((h1 >> 6) | (h2 << 20)) & M32 302 let f2: i64 = ((h2 >> 12) | (h3 << 14)) & M32 303 let f3: i64 = ((h3 >> 18) | (h4 << 8)) & M32 304 let s0: i64 = _le32_read(key, 16) 305 let s1k: i64 = _le32_read(key, 20) 306 let s2k: i64 = _le32_read(key, 24) 307 let s3k: i64 = _le32_read(key, 28) 308 var t0: i64 = (f0 + s0) & M32 309 var carry: i64 = (f0 + s0) >> 32 310 var t1: i64 = (f1 + s1k + carry) & M32 311 carry = (f1 + s1k + carry) >> 32 312 var t2: i64 = (f2 + s2k + carry) & M32 313 carry = (f2 + s2k + carry) >> 32 314 var t3: i64 = (f3 + s3k + carry) & M32 315 _le32_write(out, 0, t0) 316 _le32_write(out, 4, t1) 317 _le32_write(out, 8, t2) 318 _le32_write(out, 12, t3) 319 return 0 320} 321 322// ===== AEAD construction ===== 323 324// Compute pad16(len) -- number of bytes needed to round up to 16. 325func _pad16(len: i64) -> i64 { 326 let m: i64 = len & 15 327 if m == 0 { return 0 } 328 return 16 - m 329} 330 331// scratch layout: 332// 0..63 : ChaCha20 init state (initial matrix) 333// 64..127 : ChaCha20 working state 334// 128..191 : ChaCha20 keystream block 335// 192..223 : Poly1305 one-time key (32 bytes from chacha20 block 0) 336// 256..383 : Poly1305 internal scratch (>= 128) 337// 512+ : AAD || pad16(AAD) || CT || pad16(CT) || aad_len_le64 || 338// ct_len_le64 staging area (caller responsibility to size) 339// 340// Total scratch must be >= 4096 bytes (or 512 + 2*(input_max + 16) for 341// the staging buffer; caller must size). 342 343func nx_chacha20poly1305_seal(key: *u8, nonce: *u8, 344 aad: *u8, aad_len: i64, 345 plaintext: *u8, pt_len: i64, 346 scratch: *u8, 347 ciphertext_out: *u8, tag_out: *u8) -> i64 { 348 let init: *u8 = scratch 349 let work: *u8 = (scratch as i64 + 64) as *u8 350 let ks: *u8 = (scratch as i64 + 128) as *u8 351 let otk: *u8 = (scratch as i64 + 192) as *u8 352 let poly_sc: *u8 = (scratch as i64 + 256) as *u8 353 let mac_buf: *u8 = (scratch as i64 + 512) as *u8 354 355 // 1. Generate Poly1305 one-time key from ChaCha20 block 0. 356 _chacha20_init_state(init, key, nonce, 0) 357 _chacha20_block(init, work, ks) 358 var i: i64 = 0 359 while i < 32 { otk[i] = ks[i]; i = i + 1 } 360 361 // 2. Encrypt plaintext with ChaCha20 (counter starting at 1). 362 _chacha20_xor(key, nonce, 1, plaintext, pt_len, init, work, ks, ciphertext_out) 363 364 // 3. Build MAC input: aad || pad16(aad) || ct || pad16(ct) || 365 // aad_len_le64 || ct_len_le64 366 var p: i64 = 0 367 var ai: i64 = 0 368 while ai < aad_len { mac_buf[p] = aad[ai]; p = p + 1; ai = ai + 1 } 369 let pad_a: i64 = _pad16(aad_len) 370 var pa: i64 = 0 371 while pa < pad_a { mac_buf[p] = 0; p = p + 1; pa = pa + 1 } 372 var ci: i64 = 0 373 while ci < pt_len { mac_buf[p] = ciphertext_out[ci]; p = p + 1; ci = ci + 1 } 374 let pad_c: i64 = _pad16(pt_len) 375 var pc: i64 = 0 376 while pc < pad_c { mac_buf[p] = 0; p = p + 1; pc = pc + 1 } 377 // aad_len as little-endian 8 bytes 378 mac_buf[p] = aad_len & 0xFF 379 mac_buf[p + 1] = (aad_len >> 8) & 0xFF 380 mac_buf[p + 2] = (aad_len >> 16) & 0xFF 381 mac_buf[p + 3] = (aad_len >> 24) & 0xFF 382 mac_buf[p + 4] = (aad_len >> 32) & 0xFF 383 mac_buf[p + 5] = (aad_len >> 40) & 0xFF 384 mac_buf[p + 6] = (aad_len >> 48) & 0xFF 385 mac_buf[p + 7] = (aad_len >> 56) & 0xFF 386 p = p + 8 387 mac_buf[p] = pt_len & 0xFF 388 mac_buf[p + 1] = (pt_len >> 8) & 0xFF 389 mac_buf[p + 2] = (pt_len >> 16) & 0xFF 390 mac_buf[p + 3] = (pt_len >> 24) & 0xFF 391 mac_buf[p + 4] = (pt_len >> 32) & 0xFF 392 mac_buf[p + 5] = (pt_len >> 40) & 0xFF 393 mac_buf[p + 6] = (pt_len >> 48) & 0xFF 394 mac_buf[p + 7] = (pt_len >> 56) & 0xFF 395 p = p + 8 396 397 // 4. Tag = Poly1305(otk, mac_buf[0..p]). 398 _poly1305(otk, mac_buf, p, poly_sc, tag_out) 399 return 0 400} 401 402func nx_chacha20poly1305_open(key: *u8, nonce: *u8, 403 aad: *u8, aad_len: i64, 404 ciphertext: *u8, ct_len: i64, 405 tag: *u8, 406 scratch: *u8, 407 plaintext_out: *u8) -> i64 { 408 let init: *u8 = scratch 409 let work: *u8 = (scratch as i64 + 64) as *u8 410 let ks: *u8 = (scratch as i64 + 128) as *u8 411 let otk: *u8 = (scratch as i64 + 192) as *u8 412 let poly_sc: *u8 = (scratch as i64 + 256) as *u8 413 let mac_buf: *u8 = (scratch as i64 + 512) as *u8 414 let expected_tag: *u8 = (scratch as i64 + 384) as *u8 415 416 // 1. Derive otk same way. 417 _chacha20_init_state(init, key, nonce, 0) 418 _chacha20_block(init, work, ks) 419 var i: i64 = 0 420 while i < 32 { otk[i] = ks[i]; i = i + 1 } 421 422 // 2. Build MAC input over the CIPHERTEXT (not plaintext) per RFC. 423 var p: i64 = 0 424 var ai: i64 = 0 425 while ai < aad_len { mac_buf[p] = aad[ai]; p = p + 1; ai = ai + 1 } 426 let pad_a: i64 = _pad16(aad_len) 427 var pa: i64 = 0 428 while pa < pad_a { mac_buf[p] = 0; p = p + 1; pa = pa + 1 } 429 var ci: i64 = 0 430 while ci < ct_len { mac_buf[p] = ciphertext[ci]; p = p + 1; ci = ci + 1 } 431 let pad_c: i64 = _pad16(ct_len) 432 var pc: i64 = 0 433 while pc < pad_c { mac_buf[p] = 0; p = p + 1; pc = pc + 1 } 434 mac_buf[p] = aad_len & 0xFF 435 mac_buf[p + 1] = (aad_len >> 8) & 0xFF 436 mac_buf[p + 2] = (aad_len >> 16) & 0xFF 437 mac_buf[p + 3] = (aad_len >> 24) & 0xFF 438 mac_buf[p + 4] = (aad_len >> 32) & 0xFF 439 mac_buf[p + 5] = (aad_len >> 40) & 0xFF 440 mac_buf[p + 6] = (aad_len >> 48) & 0xFF 441 mac_buf[p + 7] = (aad_len >> 56) & 0xFF 442 p = p + 8 443 mac_buf[p] = ct_len & 0xFF 444 mac_buf[p + 1] = (ct_len >> 8) & 0xFF 445 mac_buf[p + 2] = (ct_len >> 16) & 0xFF 446 mac_buf[p + 3] = (ct_len >> 24) & 0xFF 447 mac_buf[p + 4] = (ct_len >> 32) & 0xFF 448 mac_buf[p + 5] = (ct_len >> 40) & 0xFF 449 mac_buf[p + 6] = (ct_len >> 48) & 0xFF 450 mac_buf[p + 7] = (ct_len >> 56) & 0xFF 451 p = p + 8 452 453 // 3. Compute expected tag. 454 _poly1305(otk, mac_buf, p, poly_sc, expected_tag) 455 456 // 4. Constant-time tag compare. 457 var diff: i64 = 0 458 var j: i64 = 0 459 while j < 16 { 460 diff = diff | (expected_tag[j] ^ tag[j]) 461 j = j + 1 462 } 463 if diff != 0 { return -1 } 464 465 // 5. Auth passed -- decrypt. 466 _chacha20_xor(key, nonce, 1, ciphertext, ct_len, init, work, ks, plaintext_out) 467 return 0 468}