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}