@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.
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +156 -0
- package/runtime/nish.h +89 -0
- package/runtime/nish.mjs +58 -0
- package/runtime/runtime-host.c +178 -0
- package/runtime/runtime-net.c +528 -0
- package/runtime/runtime-os.c +15 -0
- package/runtime/shim.mjs +137 -0
- package/scripts/build.sh +9 -7
- package/std/README.md +80 -0
- package/std/crypto/LICENSE-bearssl +21 -0
- package/std/crypto/LICENSE-fiat-crypto +21 -0
- package/std/crypto/aes.ts +899 -0
- package/std/crypto/base64url.ts +145 -0
- package/std/crypto/chacha20poly1305.ts +698 -0
- package/std/crypto/ct.ts +64 -0
- package/std/crypto/hkdf.ts +118 -0
- package/std/crypto/hmac.ts +155 -0
- package/std/crypto/p256.ts +4337 -0
- package/std/crypto/sha256.ts +444 -0
- package/std/crypto/sha512.ts +510 -0
- package/std/crypto/x25519.ts +509 -0
- package/std/crypto/x509.ts +1214 -0
|
@@ -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
|
+
}
|