@amritk/nish-aarch64-linux 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,698 @@
1
+ /**
2
+ * `nish/crypto/chacha20poly1305` — the ChaCha20 stream cipher, the Poly1305
3
+ * one-time authenticator and the AEAD built from the two, as RFC 8439 defines
4
+ * them, plus the ChaCha20 header-protection mask of RFC 9001 §5.4.4.
5
+ *
6
+ * import { chacha20Poly1305Seal, chacha20Poly1305Open } from "nish/crypto/chacha20poly1305";
7
+ *
8
+ * const sealed: u8[] | null = chacha20Poly1305Seal(key, nonce, aad, plaintext);
9
+ * const opened: u8[] | null = chacha20Poly1305Open(key, nonce, aad, sealed);
10
+ *
11
+ * A sealed message is the ciphertext followed by the 16-byte tag, the layout
12
+ * TLS 1.3 and QUIC put on the wire. Every function answers `null` for an input
13
+ * of the wrong length, and `chacha20Poly1305Open` answers `null` for a message
14
+ * that does not authenticate; nothing here panics on what a peer sent.
15
+ *
16
+ * Written from the specification (RFC 8439 §2 and RFC 9001 §5.4.4), in this
17
+ * module's own structure: nothing here is ported from another implementation.
18
+ *
19
+ * **ChaCha20** is sixteen `u32` words of additions, rotations and xors, which
20
+ * is constant time as written: `u32` arithmetic wraps by definition, and there
21
+ * is nothing to branch on. The twenty rounds run on locals rather than on an
22
+ * array so that the words stay in registers, and, as RFC 8439 §3 suggests,
23
+ * the state is built once per message and only its counter moves per block.
24
+ *
25
+ * **Poly1305** keeps its accumulator and its key `r` as five limbs of 26 bits
26
+ * each in `u64`, so limb `i` holds bits `26i` to `26i + 25` of a 130-bit
27
+ * number. RFC 8439 §3 asks for constant-time arithmetic without saying how;
28
+ * 26-bit limbs are the common idea for 64-bit multiplies, and they are what
29
+ * makes the arithmetic fit: a limb product is below 2^56, five of them sum
30
+ * below 2^58, and 2^130 = 5 (mod p) folds the top of a product back into the
31
+ * bottom as a multiply by five.
32
+ * `poly1305Block` says why each sum fits. The reduction is by carries and
33
+ * masks, and the final reduction into `[0, p)` picks between `h` and `h - p`
34
+ * with `ctSelect` on a mask made from a borrow, never with a comparison.
35
+ *
36
+ * **Constant time.** Nothing branches on, or indexes by, a key, a keystream
37
+ * word, the accumulator or a tag. Every `if` and every loop bound is on a
38
+ * length, an offset or a block number, all of which the peer already knows.
39
+ * `chacha20Poly1305Open` computes the tag over the received ciphertext and
40
+ * compares all sixteen bytes by ORing their differences into one word before
41
+ * it decrypts anything, and a mismatch is decided on that one word. Verified by
42
+ * disassembly (`tests/cases/ct_asm_chacha20poly1305`, read by
43
+ * `tests/ct-asm.js` for x86-64 and aarch64): the key clamp, one Poly1305 block
44
+ * (the load, the multiply and the partial reduction), the final reduction and
45
+ * the addition of `s`, and the tag compare, each straight-line with no
46
+ * secret-addressed load. The fixture holds copies of those four functions,
47
+ * because the check reads one compiled module; `tests/link/crypto_chacha20poly1305`
48
+ * holds the copies to these originals. The ChaCha20 rounds and the loops that
49
+ * drive both halves are discipline, not proof: the check refuses a loop's own
50
+ * back-edge, so it cannot read them.
51
+ *
52
+ * Private names carry the `chacha20` / `poly1305` prefix because a `std/`
53
+ * module's private functions share the importing program's flat symbol
54
+ * namespace (`docs/wp26-stdlib.md` §3e).
55
+ */
56
+
57
+ /** The length of a ChaCha20 key in bytes (RFC 8439 §2.3). */
58
+ export const CHACHA20_KEY_SIZE: i32 = 32
59
+
60
+ /** The length of the AEAD's nonce, and of ChaCha20's, in bytes (RFC 8439 §2.3, §2.8). */
61
+ export const CHACHA20POLY1305_NONCE_SIZE: i32 = 12
62
+
63
+ /** The length of a Poly1305 tag in bytes (RFC 8439 §2.5). */
64
+ export const POLY1305_TAG_SIZE: i32 = 16
65
+
66
+ /** The length of a Poly1305 one-time key, `r` then `s`, in bytes (RFC 8439 §2.5). */
67
+ export const POLY1305_KEY_SIZE: i32 = 32
68
+
69
+ /** The length of a ChaCha20 block, and of the keystream one block makes, in bytes. */
70
+ export const CHACHA20_BLOCK_SIZE: i32 = 64
71
+
72
+ /** The length of the header-protection sample in bytes (RFC 9001 §5.4.2). */
73
+ export const CHACHA20_HEADER_SAMPLE_SIZE: i32 = 16
74
+
75
+ /** The length of a header-protection mask in bytes (RFC 9001 §5.4.1). */
76
+ export const CHACHA20_HEADER_MASK_SIZE: i32 = 5
77
+
78
+ /**
79
+ * 2^128 as limb 4 sees it (bit 24 of the limb at bit 104): the one bit a
80
+ * whole 16-byte block gets above its last byte (RFC 8439 §2.5.1's "add one
81
+ * bit beyond the number of octets").
82
+ */
83
+ const POLY1305_HIGH_BIT: i32 = 0x1000000
84
+
85
+ /** `x` rotated left by `n` bits, `n` in 1 to 31. */
86
+ const chacha20Rotl = (x: u32, n: u32): u32 => (x << n) | (x >>> (32 - n))
87
+
88
+ /** The little-endian `u32` at `buf[at]` to `buf[at + 3]`. */
89
+ const chacha20Word = (buf: u8[], at: i32): u32 =>
90
+ toU32(buf[at]) | (toU32(buf[at + 1]) << 8) | (toU32(buf[at + 2]) << 16) | (toU32(buf[at + 3]) << 24)
91
+
92
+ /**
93
+ * The sixteen-word state of RFC 8439 §2.3: the four constant words
94
+ * ("expand 32-byte k"), the key, the block counter and the nonce, the last
95
+ * read from `nonce[nonceAt]` on so the header mask can take it from its
96
+ * sample in place. The caller has checked every length.
97
+ */
98
+ const chacha20State = (key: u8[], counter: u32, nonce: u8[], nonceAt: i32): u32[] => {
99
+ const state: u32[] = new Array<u32>(16)
100
+ state[0] = 0x61707865
101
+ state[1] = 0x3320646e
102
+ state[2] = 0x79622d32
103
+ state[3] = 0x6b206574
104
+ for (let i: i32 = 0; i < 8; i++) {
105
+ state[4 + i] = chacha20Word(key, 4 * i)
106
+ }
107
+ state[12] = counter
108
+ state[13] = chacha20Word(nonce, nonceAt)
109
+ state[14] = chacha20Word(nonce, nonceAt + 4)
110
+ state[15] = chacha20Word(nonce, nonceAt + 8)
111
+ return state
112
+ }
113
+
114
+ /**
115
+ * The ChaCha20 block function (RFC 8439 §2.3) on words: `out` gets the twenty
116
+ * rounds of `state` added to `state`. Ten double rounds, each a column round
117
+ * and a diagonal round of four quarter rounds (§2.1, §2.2), run on sixteen
118
+ * locals so nothing goes back to memory until the end.
119
+ *
120
+ * The early return cannot happen — every state and output is made here with
121
+ * sixteen words — but it is what lets the compiler prove the literal indices
122
+ * below in range and drop their bounds checks.
123
+ */
124
+ const chacha20Core = (state: u32[], out: u32[]): void => {
125
+ if (toI32(state.length) < 16 || toI32(out.length) < 16) {
126
+ return
127
+ }
128
+ let x0: u32 = state[0]
129
+ let x1: u32 = state[1]
130
+ let x2: u32 = state[2]
131
+ let x3: u32 = state[3]
132
+ let x4: u32 = state[4]
133
+ let x5: u32 = state[5]
134
+ let x6: u32 = state[6]
135
+ let x7: u32 = state[7]
136
+ let x8: u32 = state[8]
137
+ let x9: u32 = state[9]
138
+ let x10: u32 = state[10]
139
+ let x11: u32 = state[11]
140
+ let x12: u32 = state[12]
141
+ let x13: u32 = state[13]
142
+ let x14: u32 = state[14]
143
+ let x15: u32 = state[15]
144
+ for (let round: i32 = 0; round < 10; round++) {
145
+ // The column round.
146
+ x0 = x0 + x4
147
+ x12 = chacha20Rotl(x12 ^ x0, 16)
148
+ x8 = x8 + x12
149
+ x4 = chacha20Rotl(x4 ^ x8, 12)
150
+ x0 = x0 + x4
151
+ x12 = chacha20Rotl(x12 ^ x0, 8)
152
+ x8 = x8 + x12
153
+ x4 = chacha20Rotl(x4 ^ x8, 7)
154
+ x1 = x1 + x5
155
+ x13 = chacha20Rotl(x13 ^ x1, 16)
156
+ x9 = x9 + x13
157
+ x5 = chacha20Rotl(x5 ^ x9, 12)
158
+ x1 = x1 + x5
159
+ x13 = chacha20Rotl(x13 ^ x1, 8)
160
+ x9 = x9 + x13
161
+ x5 = chacha20Rotl(x5 ^ x9, 7)
162
+ x2 = x2 + x6
163
+ x14 = chacha20Rotl(x14 ^ x2, 16)
164
+ x10 = x10 + x14
165
+ x6 = chacha20Rotl(x6 ^ x10, 12)
166
+ x2 = x2 + x6
167
+ x14 = chacha20Rotl(x14 ^ x2, 8)
168
+ x10 = x10 + x14
169
+ x6 = chacha20Rotl(x6 ^ x10, 7)
170
+ x3 = x3 + x7
171
+ x15 = chacha20Rotl(x15 ^ x3, 16)
172
+ x11 = x11 + x15
173
+ x7 = chacha20Rotl(x7 ^ x11, 12)
174
+ x3 = x3 + x7
175
+ x15 = chacha20Rotl(x15 ^ x3, 8)
176
+ x11 = x11 + x15
177
+ x7 = chacha20Rotl(x7 ^ x11, 7)
178
+ // The diagonal round.
179
+ x0 = x0 + x5
180
+ x15 = chacha20Rotl(x15 ^ x0, 16)
181
+ x10 = x10 + x15
182
+ x5 = chacha20Rotl(x5 ^ x10, 12)
183
+ x0 = x0 + x5
184
+ x15 = chacha20Rotl(x15 ^ x0, 8)
185
+ x10 = x10 + x15
186
+ x5 = chacha20Rotl(x5 ^ x10, 7)
187
+ x1 = x1 + x6
188
+ x12 = chacha20Rotl(x12 ^ x1, 16)
189
+ x11 = x11 + x12
190
+ x6 = chacha20Rotl(x6 ^ x11, 12)
191
+ x1 = x1 + x6
192
+ x12 = chacha20Rotl(x12 ^ x1, 8)
193
+ x11 = x11 + x12
194
+ x6 = chacha20Rotl(x6 ^ x11, 7)
195
+ x2 = x2 + x7
196
+ x13 = chacha20Rotl(x13 ^ x2, 16)
197
+ x8 = x8 + x13
198
+ x7 = chacha20Rotl(x7 ^ x8, 12)
199
+ x2 = x2 + x7
200
+ x13 = chacha20Rotl(x13 ^ x2, 8)
201
+ x8 = x8 + x13
202
+ x7 = chacha20Rotl(x7 ^ x8, 7)
203
+ x3 = x3 + x4
204
+ x14 = chacha20Rotl(x14 ^ x3, 16)
205
+ x9 = x9 + x14
206
+ x4 = chacha20Rotl(x4 ^ x9, 12)
207
+ x3 = x3 + x4
208
+ x14 = chacha20Rotl(x14 ^ x3, 8)
209
+ x9 = x9 + x14
210
+ x4 = chacha20Rotl(x4 ^ x9, 7)
211
+ }
212
+ out[0] = x0 + state[0]
213
+ out[1] = x1 + state[1]
214
+ out[2] = x2 + state[2]
215
+ out[3] = x3 + state[3]
216
+ out[4] = x4 + state[4]
217
+ out[5] = x5 + state[5]
218
+ out[6] = x6 + state[6]
219
+ out[7] = x7 + state[7]
220
+ out[8] = x8 + state[8]
221
+ out[9] = x9 + state[9]
222
+ out[10] = x10 + state[10]
223
+ out[11] = x11 + state[11]
224
+ out[12] = x12 + state[12]
225
+ out[13] = x13 + state[13]
226
+ out[14] = x14 + state[14]
227
+ out[15] = x15 + state[15]
228
+ }
229
+
230
+ /**
231
+ * `dst[dstAt + i] = src[srcAt + i] ^ keystream` for `len` bytes, the keystream
232
+ * running from `state`'s counter on, and the counter left one past the last
233
+ * block used (RFC 8439 §2.4). The caller has checked every window and that
234
+ * the counter does not wrap.
235
+ */
236
+ const chacha20XorInto = (state: u32[], src: u8[], srcAt: i32, len: i32, dst: u8[], dstAt: i32): void => {
237
+ const keystream: u32[] = new Array<u32>(16)
238
+ let done: i32 = 0
239
+ while (done < len) {
240
+ chacha20Core(state, keystream)
241
+ state[12] = state[12] + 1
242
+ const take: i32 = len - done < 64 ? len - done : 64
243
+ for (let w: i32 = 0; w < 16 && w < toI32(keystream.length); w++) {
244
+ const word: u32 = keystream[w]
245
+ const at: i32 = done + 4 * w
246
+ if (at + 4 <= done + take) {
247
+ // A whole word, the common case: four bytes with fixed shifts.
248
+ dst[dstAt + at] = src[srcAt + at] ^ toU8(word)
249
+ dst[dstAt + at + 1] = src[srcAt + at + 1] ^ toU8(word >>> 8)
250
+ dst[dstAt + at + 2] = src[srcAt + at + 2] ^ toU8(word >>> 16)
251
+ dst[dstAt + at + 3] = src[srcAt + at + 3] ^ toU8(word >>> 24)
252
+ } else {
253
+ for (let k: i32 = 0; at + k < done + take; k++) {
254
+ dst[dstAt + at + k] = src[srcAt + at + k] ^ toU8(word >>> toU32(8 * k))
255
+ }
256
+ }
257
+ }
258
+ done = done + take
259
+ }
260
+ }
261
+
262
+ /**
263
+ * The one-time key and the ChaCha20 state for the AEAD's payload: block 0
264
+ * of the keystream gives the Poly1305 key (RFC 8439 §2.6), and `state` is
265
+ * left at counter 1, where §2.8 starts the encryption.
266
+ */
267
+ const chacha20Poly1305Setup = (state: u32[]): u8[] => {
268
+ const polyKey: u8[] = new Array<u8>(POLY1305_KEY_SIZE)
269
+ chacha20XorInto(state, polyKey, 0, POLY1305_KEY_SIZE, polyKey, 0)
270
+ return polyKey
271
+ }
272
+
273
+ /**
274
+ * Whether the blocks `len` bytes need, from `counter` on, fit before the
275
+ * 32-bit counter wraps.
276
+ * RFC 8439 §2.4 leaves a wrap undefined, and a wrapped counter repeats the
277
+ * keystream, so a request that would need one answers `null` instead.
278
+ */
279
+ const chacha20CounterFits = (counter: u32, len: i32): boolean => {
280
+ const blocks: i64 = (toI64(len) + toI64(63)) >> toI64(6)
281
+ return toI64(counter) + blocks <= toI64(1) << toI64(32)
282
+ }
283
+
284
+ /**
285
+ * One quarter round (RFC 8439 §2.1, §2.2) on a copy of `state`, at the four
286
+ * word positions `a`, `b`, `c` and `d`, which is what §2.1.1 and §2.2.1 test.
287
+ * `chacha20Core` does the same on locals. Answers `null`
288
+ * when a position is not inside `state`.
289
+ */
290
+ export const chacha20QuarterRound = (state: u32[], a: i32, b: i32, c: i32, d: i32): u32[] | null => {
291
+ const n: i32 = toI32(state.length)
292
+ if (a < 0 || a >= n || b < 0 || b >= n || c < 0 || c >= n || d < 0 || d >= n) {
293
+ return null
294
+ }
295
+ const x: u32[] = new Array<u32>(n)
296
+ for (let i: i32 = 0; i < toI32(x.length) && i < toI32(state.length); i++) {
297
+ x[i] = state[i]
298
+ }
299
+ x[a] = x[a] + x[b]
300
+ x[d] = chacha20Rotl(x[d] ^ x[a], 16)
301
+ x[c] = x[c] + x[d]
302
+ x[b] = chacha20Rotl(x[b] ^ x[c], 12)
303
+ x[a] = x[a] + x[b]
304
+ x[d] = chacha20Rotl(x[d] ^ x[a], 8)
305
+ x[c] = x[c] + x[d]
306
+ x[b] = chacha20Rotl(x[b] ^ x[c], 7)
307
+ return x
308
+ }
309
+
310
+ /**
311
+ * The ChaCha20 block function (RFC 8439 §2.3): the 64 bytes of keystream for
312
+ * block `counter` under `key` and `nonce`. Answers `null` unless `key` is
313
+ * `CHACHA20_KEY_SIZE` bytes and `nonce` is `CHACHA20POLY1305_NONCE_SIZE`.
314
+ */
315
+ export const chacha20Block = (key: u8[], counter: u32, nonce: u8[]): u8[] | null => {
316
+ if (toI32(key.length) !== CHACHA20_KEY_SIZE || toI32(nonce.length) !== CHACHA20POLY1305_NONCE_SIZE) {
317
+ return null
318
+ }
319
+ const out: u8[] = new Array<u8>(CHACHA20_BLOCK_SIZE)
320
+ chacha20XorInto(chacha20State(key, counter, nonce, 0), out, 0, CHACHA20_BLOCK_SIZE, out, 0)
321
+ return out
322
+ }
323
+
324
+ /**
325
+ * ChaCha20 encryption (RFC 8439 §2.4): `data` xored with the keystream from
326
+ * block `counter` on, which both encrypts and decrypts. Answers `null` unless
327
+ * `key` is `CHACHA20_KEY_SIZE` bytes and `nonce` is
328
+ * `CHACHA20POLY1305_NONCE_SIZE`, and `null` when `data` would run the 32-bit
329
+ * counter past its last value rather than repeat the keystream.
330
+ */
331
+ export const chacha20 = (key: u8[], counter: u32, nonce: u8[], data: u8[]): u8[] | null => {
332
+ const len: i32 = toI32(data.length)
333
+ if (
334
+ toI32(key.length) !== CHACHA20_KEY_SIZE ||
335
+ toI32(nonce.length) !== CHACHA20POLY1305_NONCE_SIZE ||
336
+ !chacha20CounterFits(counter, len)
337
+ ) {
338
+ return null
339
+ }
340
+ const out: u8[] = new Array<u8>(len)
341
+ chacha20XorInto(chacha20State(key, counter, nonce, 0), data, 0, len, out, 0)
342
+ return out
343
+ }
344
+
345
+ /** The little-endian `u64` at `buf[at]` to `buf[at + 7]`. */
346
+ const poly1305Le64 = (buf: u8[], at: i32): u64 =>
347
+ toU64(buf[at]) |
348
+ (toU64(buf[at + 1]) << 8) |
349
+ (toU64(buf[at + 2]) << 16) |
350
+ (toU64(buf[at + 3]) << 24) |
351
+ (toU64(buf[at + 4]) << 32) |
352
+ (toU64(buf[at + 5]) << 40) |
353
+ (toU64(buf[at + 6]) << 48) |
354
+ (toU64(buf[at + 7]) << 56)
355
+
356
+ /**
357
+ * `r` from the first 16 bytes of a one-time key, clamped as RFC 8439 §2.5
358
+ * says (the top four bits of bytes 3, 7, 11 and 15 and the bottom two of
359
+ * bytes 4, 8 and 12 cleared), into the five limbs of `r`.
360
+ *
361
+ * Straight-line and without a length check so that
362
+ * `tests/cases/ct_asm_chacha20poly1305` can hold a copy of it to the
363
+ * disassembly check; every caller passes a 32-byte key and a 5-limb `r`.
364
+ */
365
+ const poly1305Clamp = (key: u8[], r: u64[]): void => {
366
+ const lo: u64 = poly1305Le64(key, 0) & ((toU64(0x0ffffffc) << 32) | 0x0fffffff)
367
+ const hi: u64 = poly1305Le64(key, 8) & ((toU64(0x0ffffffc) << 32) | 0x0ffffffc)
368
+ r[0] = lo & 0x3ffffff
369
+ r[1] = (lo >>> 26) & 0x3ffffff
370
+ r[2] = ((lo >>> 52) | (hi << 12)) & 0x3ffffff
371
+ r[3] = (hi >>> 14) & 0x3ffffff
372
+ r[4] = hi >>> 40
373
+ }
374
+
375
+ /**
376
+ * One Poly1305 step (RFC 8439 §2.5.1): `h = (h + block) * r mod p`, the block
377
+ * the 16 bytes at `m[at]` plus `high` at bit 128, reduced only far enough for
378
+ * the next step. `high` is `POLY1305_HIGH_BIT` for a whole block and for an
379
+ * AEAD's zero-padded one, and zero for §2.5.1's short last block, whose 0x01
380
+ * byte is already inside the sixteen.
381
+ *
382
+ * **Why the sums fit in `u64`.** On entry every limb of `h` is below
383
+ * 2^26 + 2^9 — what the last step's carries leave — and adding the block,
384
+ * whose limbs are below 2^26 and whose top limb is below 2^25, keeps each
385
+ * below 2^27.01. The clamped `r` has limbs below 2^26, and `5 * r` below
386
+ * 2^28.33. A product is therefore below 2^55.34 and each `d` is five of them,
387
+ * below 2^57.66. The carries out of `d` are below 2^31.7, `5 *` the one out of
388
+ * limb 4 is below 2^34.1, and what carries on from limb 0 into limb 1 is
389
+ * below 2^9, which is the entry bound again.
390
+ *
391
+ * Straight-line and without a length check so that
392
+ * `tests/cases/ct_asm_chacha20poly1305` can hold a copy of it to the
393
+ * disassembly check; every caller passes 5-limb arrays and a window of 16.
394
+ */
395
+ const poly1305Block = (h: u64[], r: u64[], m: u8[], at: i32, high: u64): void => {
396
+ const lo: u64 = poly1305Le64(m, at)
397
+ const hi: u64 = poly1305Le64(m, at + 8)
398
+ const h0: u64 = h[0] + (lo & 0x3ffffff)
399
+ const h1: u64 = h[1] + ((lo >>> 26) & 0x3ffffff)
400
+ const h2: u64 = h[2] + (((lo >>> 52) | (hi << 12)) & 0x3ffffff)
401
+ const h3: u64 = h[3] + ((hi >>> 14) & 0x3ffffff)
402
+ const h4: u64 = h[4] + ((hi >>> 40) | high)
403
+
404
+ const r0: u64 = r[0]
405
+ const r1: u64 = r[1]
406
+ const r2: u64 = r[2]
407
+ const r3: u64 = r[3]
408
+ const r4: u64 = r[4]
409
+ // A product that lands at limb 5 or above is 2^130 times something, and
410
+ // 2^130 = 5 (mod p), so it comes back in at limb `i - 5` times five.
411
+ const s1: u64 = r1 * 5
412
+ const s2: u64 = r2 * 5
413
+ const s3: u64 = r3 * 5
414
+ const s4: u64 = r4 * 5
415
+
416
+ let d0: u64 = h0 * r0 + h1 * s4 + h2 * s3 + h3 * s2 + h4 * s1
417
+ let d1: u64 = h0 * r1 + h1 * r0 + h2 * s4 + h3 * s3 + h4 * s2
418
+ let d2: u64 = h0 * r2 + h1 * r1 + h2 * r0 + h3 * s4 + h4 * s3
419
+ let d3: u64 = h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * s4
420
+ let d4: u64 = h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0
421
+
422
+ // Carry each limb into the next, and the carry out of limb 4 into limb 0
423
+ // times five; then limb 0's own carry once more.
424
+ d1 = d1 + (d0 >>> 26)
425
+ d2 = d2 + (d1 >>> 26)
426
+ d3 = d3 + (d2 >>> 26)
427
+ d4 = d4 + (d3 >>> 26)
428
+ d0 = (d0 & 0x3ffffff) + (d4 >>> 26) * 5
429
+ h[0] = d0 & 0x3ffffff
430
+ h[1] = (d1 & 0x3ffffff) + (d0 >>> 26)
431
+ h[2] = d2 & 0x3ffffff
432
+ h[3] = d3 & 0x3ffffff
433
+ h[4] = d4 & 0x3ffffff
434
+ }
435
+
436
+ /**
437
+ * The tag (RFC 8439 §2.5.1): `h` reduced into `[0, p)`, plus `s` (bytes 16 to
438
+ * 31 of the one-time key) modulo 2^128, as 16 little-endian bytes into `tag`.
439
+ *
440
+ * Two carry passes make every limb below 2^26: the first leaves limb 0 at most
441
+ * 2^26 + 4, the second carries at most one bit all the way up, and a carry
442
+ * out of limb 4 then leaves limb 0 below ten. So `h` is below 2^130, which is
443
+ * `[0, p)` or `[p, 2^130)`. `g = h + 5 - 2^130` is `h - p`, and it has no
444
+ * borrow out of its top limb exactly when `h >= p`: the borrow's sign bit
445
+ * becomes an all-zeros or all-ones mask, and `ctSelect` keeps `g` or `h`
446
+ * limb by limb without a comparison. Adding `s` runs in 32-bit words with the
447
+ * carry in the upper half of a `u64`, so there is no compare there either.
448
+ *
449
+ * Straight-line and without a length check so that
450
+ * `tests/cases/ct_asm_chacha20poly1305` can hold a copy of it to the
451
+ * disassembly check; every caller passes a 5-limb `h`, a 32-byte key and a
452
+ * 16-byte `tag`.
453
+ */
454
+ const poly1305Finish = (h: u64[], key: u8[], tag: u8[]): void => {
455
+ let h0: u64 = h[0]
456
+ let h1: u64 = h[1]
457
+ let h2: u64 = h[2]
458
+ let h3: u64 = h[3]
459
+ let h4: u64 = h[4]
460
+ for (let pass: i32 = 0; pass < 2; pass++) {
461
+ h1 = h1 + (h0 >>> 26)
462
+ h0 = h0 & 0x3ffffff
463
+ h2 = h2 + (h1 >>> 26)
464
+ h1 = h1 & 0x3ffffff
465
+ h3 = h3 + (h2 >>> 26)
466
+ h2 = h2 & 0x3ffffff
467
+ h4 = h4 + (h3 >>> 26)
468
+ h3 = h3 & 0x3ffffff
469
+ h0 = h0 + (h4 >>> 26) * 5
470
+ h4 = h4 & 0x3ffffff
471
+ }
472
+
473
+ const g0: u64 = h0 + 5
474
+ const g1: u64 = h1 + (g0 >>> 26)
475
+ const g2: u64 = h2 + (g1 >>> 26)
476
+ const g3: u64 = h3 + (g2 >>> 26)
477
+ const g4: u64 = h4 + (g3 >>> 26) - 0x4000000
478
+ // All ones when `g4` did not borrow, which is when `h >= p` and `g` is the
479
+ // reduced value; all zeros otherwise.
480
+ const keep: u64 = (g4 >>> 63) - 1
481
+ h0 = ctSelect(keep, g0 & 0x3ffffff, h0)
482
+ h1 = ctSelect(keep, g1 & 0x3ffffff, h1)
483
+ h2 = ctSelect(keep, g2 & 0x3ffffff, h2)
484
+ h3 = ctSelect(keep, g3 & 0x3ffffff, h3)
485
+ h4 = ctSelect(keep, g4 & 0x3ffffff, h4)
486
+
487
+ const word: u64 = 0xffffffff
488
+ let f: u64 = ((h0 | (h1 << 26)) & word) + toU64(chacha20Word(key, 16))
489
+ tag[0] = toU8(f)
490
+ tag[1] = toU8(f >>> 8)
491
+ tag[2] = toU8(f >>> 16)
492
+ tag[3] = toU8(f >>> 24)
493
+ f = (((h1 >>> 6) | (h2 << 20)) & word) + toU64(chacha20Word(key, 20)) + (f >>> 32)
494
+ tag[4] = toU8(f)
495
+ tag[5] = toU8(f >>> 8)
496
+ tag[6] = toU8(f >>> 16)
497
+ tag[7] = toU8(f >>> 24)
498
+ f = (((h2 >>> 12) | (h3 << 14)) & word) + toU64(chacha20Word(key, 24)) + (f >>> 32)
499
+ tag[8] = toU8(f)
500
+ tag[9] = toU8(f >>> 8)
501
+ tag[10] = toU8(f >>> 16)
502
+ tag[11] = toU8(f >>> 24)
503
+ f = (((h3 >>> 18) | (h4 << 8)) & word) + toU64(chacha20Word(key, 28)) + (f >>> 32)
504
+ tag[12] = toU8(f)
505
+ tag[13] = toU8(f >>> 8)
506
+ tag[14] = toU8(f >>> 16)
507
+ tag[15] = toU8(f >>> 24)
508
+ }
509
+
510
+ /**
511
+ * Feeds the `len` bytes of `buf` from `at` to the accumulator `h`, sixteen at
512
+ * a time. A short last block is padded one of two ways: `zeroPad` is the
513
+ * AEAD's (RFC 8439 §2.8, zeros up to 16 bytes and the block counted as whole),
514
+ * and otherwise it is §2.5.1's, a 0x01 byte after the message and zeros after
515
+ * that, with no bit at 2^128.
516
+ */
517
+ const poly1305Absorb = (h: u64[], r: u64[], buf: u8[], at: i32, len: i32, zeroPad: boolean): void => {
518
+ const whole: i32 = len >> 4
519
+ for (let i: i32 = 0; i < whole; i++) {
520
+ poly1305Block(h, r, buf, at + 16 * i, toU64(POLY1305_HIGH_BIT))
521
+ }
522
+ const tail: i32 = len & 15
523
+ if (tail > 0) {
524
+ const last: u8[] = new Array<u8>(16)
525
+ for (let i: i32 = 0; i < tail && i < toI32(last.length); i++) {
526
+ last[i] = buf[at + 16 * whole + i]
527
+ }
528
+ if (zeroPad) {
529
+ poly1305Block(h, r, last, 0, toU64(POLY1305_HIGH_BIT))
530
+ } else {
531
+ last[tail] = 1
532
+ poly1305Block(h, r, last, 0, 0)
533
+ }
534
+ }
535
+ }
536
+
537
+ /**
538
+ * Poly1305 (RFC 8439 §2.5): the 16-byte tag of `msg` under the one-time key
539
+ * `key`, `r` then `s`. A key must never authenticate two messages; the AEAD
540
+ * below makes a fresh one per nonce. Answers `null` unless `key` is
541
+ * `POLY1305_KEY_SIZE` bytes.
542
+ */
543
+ export const poly1305 = (key: u8[], msg: u8[]): u8[] | null => {
544
+ if (toI32(key.length) !== POLY1305_KEY_SIZE) {
545
+ return null
546
+ }
547
+ const h: u64[] = new Array<u64>(5)
548
+ const r: u64[] = new Array<u64>(5)
549
+ poly1305Clamp(key, r)
550
+ poly1305Absorb(h, r, msg, 0, toI32(msg.length), false)
551
+ const tag: u8[] = new Array<u8>(POLY1305_TAG_SIZE)
552
+ poly1305Finish(h, key, tag)
553
+ return tag
554
+ }
555
+
556
+ /**
557
+ * The Poly1305 one-time key of RFC 8439 §2.6: the first 32 bytes of the
558
+ * ChaCha20 block with counter 0 under the AEAD's key and nonce. Answers
559
+ * `null` unless `key` is `CHACHA20_KEY_SIZE` bytes and `nonce` is
560
+ * `CHACHA20POLY1305_NONCE_SIZE`.
561
+ */
562
+ export const poly1305KeyGen = (key: u8[], nonce: u8[]): u8[] | null => {
563
+ if (toI32(key.length) !== CHACHA20_KEY_SIZE || toI32(nonce.length) !== CHACHA20POLY1305_NONCE_SIZE) {
564
+ return null
565
+ }
566
+ return chacha20Poly1305Setup(chacha20State(key, 0, nonce, 0))
567
+ }
568
+
569
+ /**
570
+ * The AEAD tag of RFC 8439 §2.8 over `aad` and the `len` bytes of ciphertext
571
+ * at `ct[at]`: Poly1305 under `polyKey` of the AAD and the ciphertext, each
572
+ * zero-padded to 16 bytes, and then both lengths as little-endian `u64`s.
573
+ */
574
+ const chacha20Poly1305Tag = (polyKey: u8[], aad: u8[], ct: u8[], at: i32, len: i32): u8[] => {
575
+ const aadLen: i32 = toI32(aad.length)
576
+ const h: u64[] = new Array<u64>(5)
577
+ const r: u64[] = new Array<u64>(5)
578
+ poly1305Clamp(polyKey, r)
579
+ poly1305Absorb(h, r, aad, 0, aadLen, true)
580
+ poly1305Absorb(h, r, ct, at, len, true)
581
+ const lengths: u8[] = new Array<u8>(16)
582
+ for (let i: i32 = 0; i < 4; i++) {
583
+ lengths[i] = toU8(aadLen >>> (8 * i))
584
+ lengths[8 + i] = toU8(len >>> (8 * i))
585
+ }
586
+ poly1305Block(h, r, lengths, 0, toU64(POLY1305_HIGH_BIT))
587
+ const tag: u8[] = new Array<u8>(POLY1305_TAG_SIZE)
588
+ poly1305Finish(h, polyKey, tag)
589
+ return tag
590
+ }
591
+
592
+ /**
593
+ * All ones when the 16 bytes of `tag` equal the 16 at `sealed[at]`, zero
594
+ * otherwise. Every pair is read, the differences are ORed into one word, and
595
+ * `ctEq` turns that word into the answer, so nothing depends on where the
596
+ * first difference is.
597
+ *
598
+ * Written out byte by byte and without a length check so that `tests/cases/ct_asm_chacha20poly1305` can hold a copy of it to the
599
+ * disassembly check; the caller has checked that `sealed` holds the window.
600
+ */
601
+ const chacha20Poly1305TagMatch = (tag: u8[], sealed: u8[], at: i32): u32 => {
602
+ const diff: u32 =
603
+ toU32(tag[0] ^ sealed[at]) |
604
+ toU32(tag[1] ^ sealed[at + 1]) |
605
+ toU32(tag[2] ^ sealed[at + 2]) |
606
+ toU32(tag[3] ^ sealed[at + 3]) |
607
+ toU32(tag[4] ^ sealed[at + 4]) |
608
+ toU32(tag[5] ^ sealed[at + 5]) |
609
+ toU32(tag[6] ^ sealed[at + 6]) |
610
+ toU32(tag[7] ^ sealed[at + 7]) |
611
+ toU32(tag[8] ^ sealed[at + 8]) |
612
+ toU32(tag[9] ^ sealed[at + 9]) |
613
+ toU32(tag[10] ^ sealed[at + 10]) |
614
+ toU32(tag[11] ^ sealed[at + 11]) |
615
+ toU32(tag[12] ^ sealed[at + 12]) |
616
+ toU32(tag[13] ^ sealed[at + 13]) |
617
+ toU32(tag[14] ^ sealed[at + 14]) |
618
+ toU32(tag[15] ^ sealed[at + 15])
619
+ return ctEq(diff, 0)
620
+ }
621
+
622
+ /**
623
+ * AEAD_CHACHA20_POLY1305 encryption (RFC 8439 §2.8): the ciphertext of
624
+ * `plaintext` followed by the 16-byte tag over it and `aad`. A nonce must
625
+ * never be used twice with one key. Answers `null` unless `key` is
626
+ * `CHACHA20_KEY_SIZE` bytes and `nonce` is `CHACHA20POLY1305_NONCE_SIZE`, or
627
+ * when the sealed message would not fit in an array.
628
+ */
629
+ export const chacha20Poly1305Seal = (key: u8[], nonce: u8[], aad: u8[], plaintext: u8[]): u8[] | null => {
630
+ const len: i32 = toI32(plaintext.length)
631
+ // 2^31 - 17: the longest plaintext whose sealed form, sixteen bytes longer,
632
+ // is still an array length.
633
+ const longest: i32 = 0x7fffffef
634
+ if (
635
+ toI32(key.length) !== CHACHA20_KEY_SIZE ||
636
+ toI32(nonce.length) !== CHACHA20POLY1305_NONCE_SIZE ||
637
+ len > longest
638
+ ) {
639
+ return null
640
+ }
641
+ const state: u32[] = chacha20State(key, 0, nonce, 0)
642
+ const polyKey: u8[] = chacha20Poly1305Setup(state)
643
+ const sealed: u8[] = new Array<u8>(len + POLY1305_TAG_SIZE)
644
+ chacha20XorInto(state, plaintext, 0, len, sealed, 0)
645
+ const tag: u8[] = chacha20Poly1305Tag(polyKey, aad, sealed, 0, len)
646
+ for (let i: i32 = 0; i < toI32(tag.length); i++) {
647
+ sealed[len + i] = tag[i]
648
+ }
649
+ return sealed
650
+ }
651
+
652
+ /**
653
+ * AEAD_CHACHA20_POLY1305 decryption (RFC 8439 §2.8): the plaintext of
654
+ * `sealed`, ciphertext then tag, when the tag authenticates it and `aad`.
655
+ *
656
+ * The tag is computed over the received ciphertext and compared in constant
657
+ * time before a byte is decrypted, so a forgery gets `null` and nothing else.
658
+ * Answers `null` too unless `key` is `CHACHA20_KEY_SIZE` bytes and `nonce` is
659
+ * `CHACHA20POLY1305_NONCE_SIZE`, and when `sealed` is shorter than a tag.
660
+ */
661
+ export const chacha20Poly1305Open = (key: u8[], nonce: u8[], aad: u8[], sealed: u8[]): u8[] | null => {
662
+ const total: i32 = toI32(sealed.length)
663
+ if (
664
+ toI32(key.length) !== CHACHA20_KEY_SIZE ||
665
+ toI32(nonce.length) !== CHACHA20POLY1305_NONCE_SIZE ||
666
+ total < POLY1305_TAG_SIZE
667
+ ) {
668
+ return null
669
+ }
670
+ const len: i32 = total - POLY1305_TAG_SIZE
671
+ const state: u32[] = chacha20State(key, 0, nonce, 0)
672
+ const polyKey: u8[] = chacha20Poly1305Setup(state)
673
+ const tag: u8[] = chacha20Poly1305Tag(polyKey, aad, sealed, 0, len)
674
+ // The one branch on the comparison, and it is on the answer the caller is
675
+ // about to learn anyway.
676
+ if (chacha20Poly1305TagMatch(tag, sealed, len) === 0) {
677
+ return null
678
+ }
679
+ const plaintext: u8[] = new Array<u8>(len)
680
+ chacha20XorInto(state, sealed, 0, len, plaintext, 0)
681
+ return plaintext
682
+ }
683
+
684
+ /**
685
+ * The ChaCha20 header-protection mask of RFC 9001 §5.4.4: the first five
686
+ * bytes of the keystream block whose counter is the first four bytes of
687
+ * `sample` (little-endian) and whose nonce is the other twelve, under the
688
+ * header-protection key `hpKey`. Answers `null` unless `hpKey` is
689
+ * `CHACHA20_KEY_SIZE` bytes and `sample` is `CHACHA20_HEADER_SAMPLE_SIZE`.
690
+ */
691
+ export const chacha20HeaderMask = (hpKey: u8[], sample: u8[]): u8[] | null => {
692
+ if (toI32(hpKey.length) !== CHACHA20_KEY_SIZE || toI32(sample.length) !== CHACHA20_HEADER_SAMPLE_SIZE) {
693
+ return null
694
+ }
695
+ const mask: u8[] = new Array<u8>(CHACHA20_HEADER_MASK_SIZE)
696
+ chacha20XorInto(chacha20State(hpKey, chacha20Word(sample, 0), sample, 4), mask, 0, 5, mask, 0)
697
+ return mask
698
+ }