leviathan-crypto 1.4.0 → 2.0.1
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/CLAUDE.md +129 -94
- package/README.md +166 -223
- package/SECURITY.md +90 -45
- package/dist/chacha20/cipher-suite.d.ts +4 -0
- package/dist/chacha20/cipher-suite.js +79 -0
- package/dist/chacha20/embedded.d.ts +1 -0
- package/dist/chacha20/embedded.js +27 -0
- package/dist/chacha20/index.d.ts +20 -27
- package/dist/chacha20/index.js +40 -59
- package/dist/chacha20/ops.d.ts +1 -1
- package/dist/chacha20/ops.js +19 -18
- package/dist/chacha20/pool-worker.js +77 -0
- package/dist/ct-wasm.d.ts +1 -0
- package/dist/ct-wasm.js +3 -0
- package/dist/ct.wasm +0 -0
- package/dist/docs/aead.md +323 -0
- package/dist/docs/architecture.md +427 -292
- package/dist/docs/argon2id.md +42 -30
- package/dist/docs/chacha20.md +192 -266
- package/dist/docs/exports.md +241 -0
- package/dist/docs/fortuna.md +60 -69
- package/dist/docs/init.md +172 -178
- package/dist/docs/loader.md +87 -142
- package/dist/docs/serpent.md +134 -583
- package/dist/docs/sha2.md +91 -103
- package/dist/docs/sha3.md +70 -36
- package/dist/docs/types.md +94 -16
- package/dist/docs/utils.md +109 -32
- package/dist/embedded/kyber.d.ts +1 -0
- package/dist/embedded/kyber.js +3 -0
- package/dist/embedded/serpent.d.ts +1 -1
- package/dist/embedded/serpent.js +1 -1
- package/dist/errors.d.ts +10 -0
- package/dist/errors.js +38 -0
- package/dist/fortuna.d.ts +0 -6
- package/dist/fortuna.js +5 -5
- package/dist/index.d.ts +25 -9
- package/dist/index.js +36 -7
- package/dist/init.d.ts +3 -7
- package/dist/init.js +18 -35
- package/dist/keccak/embedded.d.ts +1 -0
- package/dist/keccak/embedded.js +27 -0
- package/dist/keccak/index.d.ts +4 -0
- package/dist/keccak/index.js +31 -0
- package/dist/kyber/embedded.d.ts +1 -0
- package/dist/kyber/embedded.js +27 -0
- package/dist/kyber/indcpa.d.ts +49 -0
- package/dist/kyber/indcpa.js +352 -0
- package/dist/kyber/index.d.ts +38 -0
- package/dist/kyber/index.js +150 -0
- package/dist/kyber/kem.d.ts +21 -0
- package/dist/kyber/kem.js +160 -0
- package/dist/kyber/params.d.ts +14 -0
- package/dist/kyber/params.js +37 -0
- package/dist/kyber/suite.d.ts +13 -0
- package/dist/kyber/suite.js +94 -0
- package/dist/kyber/types.d.ts +98 -0
- package/dist/kyber/types.js +25 -0
- package/dist/kyber/validate.d.ts +19 -0
- package/dist/kyber/validate.js +68 -0
- package/dist/kyber.wasm +0 -0
- package/dist/loader.d.ts +15 -6
- package/dist/loader.js +65 -21
- package/dist/serpent/cipher-suite.d.ts +4 -0
- package/dist/serpent/cipher-suite.js +122 -0
- package/dist/serpent/embedded.d.ts +1 -0
- package/dist/serpent/embedded.js +27 -0
- package/dist/serpent/index.d.ts +6 -37
- package/dist/serpent/index.js +9 -118
- package/dist/serpent/pool-worker.d.ts +1 -0
- package/dist/serpent/pool-worker.js +208 -0
- package/dist/serpent/serpent-cbc.d.ts +30 -0
- package/dist/serpent/serpent-cbc.js +142 -0
- package/dist/serpent.wasm +0 -0
- package/dist/sha2/embedded.d.ts +1 -0
- package/dist/sha2/embedded.js +27 -0
- package/dist/sha2/hkdf.js +6 -2
- package/dist/sha2/index.d.ts +3 -2
- package/dist/sha2/index.js +3 -4
- package/dist/sha3/embedded.d.ts +1 -0
- package/dist/sha3/embedded.js +27 -0
- package/dist/sha3/index.d.ts +3 -2
- package/dist/sha3/index.js +3 -4
- package/dist/stream/constants.d.ts +6 -0
- package/dist/stream/constants.js +30 -0
- package/dist/stream/header.d.ts +9 -0
- package/dist/stream/header.js +77 -0
- package/dist/stream/index.d.ts +7 -0
- package/dist/stream/index.js +27 -0
- package/dist/stream/open-stream.d.ts +21 -0
- package/dist/stream/open-stream.js +146 -0
- package/dist/stream/seal-stream-pool.d.ts +38 -0
- package/dist/stream/seal-stream-pool.js +400 -0
- package/dist/stream/seal-stream.d.ts +20 -0
- package/dist/stream/seal-stream.js +142 -0
- package/dist/stream/seal.d.ts +9 -0
- package/dist/stream/seal.js +75 -0
- package/dist/stream/types.d.ts +25 -0
- package/dist/stream/types.js +26 -0
- package/dist/utils.d.ts +7 -2
- package/dist/utils.js +49 -3
- package/dist/wasm-source.d.ts +12 -0
- package/dist/wasm-source.js +26 -0
- package/package.json +13 -5
- package/dist/chacha20/pool.d.ts +0 -52
- package/dist/chacha20/pool.js +0 -178
- package/dist/chacha20/pool.worker.js +0 -37
- package/dist/chacha20/stream-sealer.d.ts +0 -49
- package/dist/chacha20/stream-sealer.js +0 -327
- package/dist/docs/chacha20_pool.md +0 -309
- package/dist/docs/wasm.md +0 -194
- package/dist/serpent/seal.d.ts +0 -8
- package/dist/serpent/seal.js +0 -72
- package/dist/serpent/stream-pool.d.ts +0 -48
- package/dist/serpent/stream-pool.js +0 -275
- package/dist/serpent/stream-sealer.d.ts +0 -55
- package/dist/serpent/stream-sealer.js +0 -342
- package/dist/serpent/stream.d.ts +0 -28
- package/dist/serpent/stream.js +0 -205
- package/dist/serpent/stream.worker.d.ts +0 -32
- package/dist/serpent/stream.worker.js +0 -117
- /package/dist/chacha20/{pool.worker.d.ts → pool-worker.d.ts} +0 -0
package/dist/docs/chacha20.md
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
|
-
# ChaCha20
|
|
1
|
+
# ChaCha20 TypeScript API
|
|
2
2
|
|
|
3
3
|
> [!NOTE]
|
|
4
|
-
>
|
|
4
|
+
> API reference for `ChaCha20`, `Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305`, and `XChaCha20Cipher`. Covers initialization, all class methods, usage examples, and error conditions.
|
|
5
|
+
|
|
6
|
+
> ### Table of Contents
|
|
7
|
+
> - [Overview](#overview)
|
|
8
|
+
> - [Security Notes](#security-notes)
|
|
9
|
+
> - [Module Init](#module-init)
|
|
10
|
+
> - [API Reference](#api-reference)
|
|
11
|
+
> - [`ChaCha20`](#chacha20)
|
|
12
|
+
> - [`Poly1305`](#poly1305)
|
|
13
|
+
> - [`ChaCha20Poly1305`](#chacha20poly1305)
|
|
14
|
+
> - [`XChaCha20Poly1305`](#xchacha20poly1305)
|
|
15
|
+
> - [XChaCha20Cipher](#xchacha20cipher)
|
|
16
|
+
> - [Usage Examples](#usage-examples)
|
|
17
|
+
> - [Error Conditions](#error-conditions)
|
|
18
|
+
|
|
19
|
+
---
|
|
5
20
|
|
|
6
21
|
## Overview
|
|
7
22
|
|
|
@@ -9,37 +24,38 @@
|
|
|
9
24
|
on all platforms (including those without hardware AES), resistant to timing attacks
|
|
10
25
|
by design, and widely deployed in TLS, SSH, and WireGuard. ChaCha20 encrypts data
|
|
11
26
|
by generating a pseudorandom keystream from a 256-bit key and a nonce, then XORing
|
|
12
|
-
it with the plaintext. It does **not** provide authentication on its own
|
|
13
|
-
modified message will decrypt to garbage with no warning.
|
|
27
|
+
it with the plaintext. It does **not** provide authentication on its own. A modified message will decrypt to garbage with no warning.
|
|
14
28
|
|
|
15
29
|
**Poly1305** is a one-time message authentication code (MAC). Given a unique 256-bit
|
|
16
30
|
key and a message, it produces a 16-byte tag that proves the message has not been
|
|
17
31
|
tampered with. The critical requirement is that each Poly1305 key is used **exactly
|
|
18
|
-
once
|
|
32
|
+
once**. Reusing a key completely breaks its security. You almost never need to use
|
|
19
33
|
Poly1305 directly; the AEAD constructions below handle key derivation for you.
|
|
20
34
|
|
|
21
35
|
**ChaCha20-Poly1305** (RFC 8439) combines both primitives into an AEAD
|
|
22
36
|
(Authenticated Encryption with Associated Data). It encrypts your data and
|
|
23
37
|
produces an authentication tag in a single operation. On decryption, it verifies
|
|
24
|
-
the tag before returning any plaintext
|
|
38
|
+
the tag before returning any plaintext. If someone tampered with the ciphertext,
|
|
25
39
|
you get an error instead of corrupted data. The nonce is 96 bits (12 bytes).
|
|
26
40
|
|
|
27
41
|
**XChaCha20-Poly1305** extends the nonce to 192 bits (24 bytes) using the HChaCha20
|
|
28
|
-
subkey derivation step. This makes random nonce generation completely safe
|
|
42
|
+
subkey derivation step. This makes random nonce generation completely safe. With a
|
|
29
43
|
24-byte nonce, the probability of a collision is negligible even after billions of
|
|
30
|
-
messages. **For most users, `
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`XChaCha20Poly1305` directly.
|
|
44
|
+
messages. **For most users, `Seal` with `XChaCha20Cipher` is the recommended choice.** It
|
|
45
|
+
produces a self-contained authenticated blob with no nonce management, no
|
|
46
|
+
instantiation, and no `dispose()`. For protocol interop requiring explicit nonce
|
|
47
|
+
control, use `XChaCha20Poly1305` directly.
|
|
48
|
+
|
|
49
|
+
---
|
|
34
50
|
|
|
35
51
|
## Security Notes
|
|
36
52
|
|
|
37
53
|
> [!IMPORTANT]
|
|
38
|
-
> Read this section before writing any code. These are not theoretical concerns
|
|
39
|
-
>
|
|
54
|
+
> Read this section before writing any code. These are not theoretical concerns.
|
|
55
|
+
> They are the mistakes that cause real-world breaches.
|
|
40
56
|
|
|
41
|
-
- **Use `
|
|
42
|
-
safest default: authenticated encryption
|
|
57
|
+
- **Use `Seal` with `XChaCha20Cipher` unless you need explicit nonce control.**
|
|
58
|
+
It is the safest default: authenticated encryption in a single static call.
|
|
43
59
|
If you are unsure which class to pick, pick this one. Use `XChaCha20Poly1305`
|
|
44
60
|
when protocol interop requires you to manage nonces yourself.
|
|
45
61
|
|
|
@@ -49,7 +65,7 @@ correct API. For protocol interop requiring explicit nonce control, use
|
|
|
49
65
|
With `ChaCha20Poly1305` (12-byte nonce), random generation has a meaningful
|
|
50
66
|
collision risk after roughly 2^32 messages under one key. With
|
|
51
67
|
`XChaCha20Poly1305` (24-byte nonce), random generation is safe for any practical
|
|
52
|
-
message count
|
|
68
|
+
message count. Just call `randomBytes(24)` for each message.
|
|
53
69
|
|
|
54
70
|
- **Poly1305 keys are single-use.** Each Poly1305 key must be used to authenticate
|
|
55
71
|
exactly one message. The AEAD classes (`ChaCha20Poly1305` and
|
|
@@ -58,13 +74,12 @@ correct API. For protocol interop requiring explicit nonce control, use
|
|
|
58
74
|
`Poly1305` class directly, it is your responsibility to never reuse a key.
|
|
59
75
|
|
|
60
76
|
- **AEAD protects both confidentiality and authenticity.** If authentication fails
|
|
61
|
-
during decryption, the plaintext is never returned
|
|
62
|
-
intentional. Do not try to work around it. If decryption fails, the ciphertext
|
|
63
|
-
was corrupted or tampered with.
|
|
77
|
+
during decryption, the plaintext is never returned. You get an error. This is
|
|
78
|
+
intentional. Do not try to work around it. If decryption fails, the ciphertext was corrupted or tampered with.
|
|
64
79
|
|
|
65
80
|
- **Associated data (AAD) is authenticated but not encrypted.** Use AAD for data
|
|
66
81
|
that must travel in the clear (headers, routing metadata, user IDs) but must be
|
|
67
|
-
verified as unmodified. If someone changes the AAD, decryption will fail
|
|
82
|
+
verified as unmodified. If someone changes the AAD, decryption will fail even
|
|
68
83
|
if the ciphertext itself is untouched.
|
|
69
84
|
|
|
70
85
|
- **Always call `dispose()` when you are done.** This wipes key material and
|
|
@@ -77,29 +92,32 @@ correct API. For protocol interop requiring explicit nonce control, use
|
|
|
77
92
|
building your own authenticated construction (and you probably should not be),
|
|
78
93
|
use one of the AEAD classes instead.
|
|
79
94
|
|
|
95
|
+
---
|
|
96
|
+
|
|
80
97
|
## Module Init
|
|
81
98
|
|
|
82
99
|
Each module subpath exports its own init function for consumers who want
|
|
83
100
|
tree-shakeable imports.
|
|
84
101
|
|
|
85
|
-
### `chacha20Init(
|
|
102
|
+
### `chacha20Init(source)`
|
|
86
103
|
|
|
87
104
|
Initializes only the chacha20 WASM binary. Equivalent to calling the
|
|
88
|
-
root `init(
|
|
105
|
+
root `init({ chacha20: chacha20Wasm })` but without pulling the other three
|
|
89
106
|
modules into the bundle.
|
|
90
107
|
|
|
91
108
|
**Signature:**
|
|
92
109
|
|
|
93
110
|
```typescript
|
|
94
|
-
async function chacha20Init(
|
|
111
|
+
async function chacha20Init(source: WasmSource): Promise<void>
|
|
95
112
|
```
|
|
96
113
|
|
|
97
114
|
**Usage:**
|
|
98
115
|
|
|
99
116
|
```typescript
|
|
100
117
|
import { chacha20Init, XChaCha20Poly1305 } from 'leviathan-crypto/chacha20'
|
|
118
|
+
import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
|
|
101
119
|
|
|
102
|
-
await chacha20Init()
|
|
120
|
+
await chacha20Init(chacha20Wasm)
|
|
103
121
|
const aead = new XChaCha20Poly1305()
|
|
104
122
|
```
|
|
105
123
|
|
|
@@ -107,17 +125,17 @@ const aead = new XChaCha20Poly1305()
|
|
|
107
125
|
|
|
108
126
|
## API Reference
|
|
109
127
|
|
|
110
|
-
All classes require calling `await init(
|
|
128
|
+
All classes require calling `await init({ chacha20: chacha20Wasm })` or the subpath `chacha20Init()`
|
|
111
129
|
before construction. If you construct a class before initialization, it throws:
|
|
112
130
|
```
|
|
113
|
-
Error: leviathan-crypto: call init(
|
|
131
|
+
Error: leviathan-crypto: call init({ chacha20: ... }) before using this class
|
|
114
132
|
```
|
|
115
133
|
|
|
116
134
|
---
|
|
117
135
|
|
|
118
136
|
### `ChaCha20`
|
|
119
137
|
|
|
120
|
-
Raw ChaCha20 stream cipher. **No authentication
|
|
138
|
+
Raw ChaCha20 stream cipher. **No authentication.** Use `XChaCha20Poly1305` instead
|
|
121
139
|
unless you are building a custom protocol and understand the risks.
|
|
122
140
|
|
|
123
141
|
#### Constructor
|
|
@@ -126,7 +144,7 @@ unless you are building a custom protocol and understand the risks.
|
|
|
126
144
|
new ChaCha20()
|
|
127
145
|
```
|
|
128
146
|
|
|
129
|
-
Throws if `init(
|
|
147
|
+
Throws if `init({ chacha20: chacha20Wasm })` has not been called.
|
|
130
148
|
|
|
131
149
|
---
|
|
132
150
|
|
|
@@ -158,8 +176,7 @@ a new `Uint8Array` containing the ciphertext (same length as input).
|
|
|
158
176
|
|
|
159
177
|
#### `beginDecrypt(key: Uint8Array, nonce: Uint8Array): void`
|
|
160
178
|
|
|
161
|
-
Prepares the cipher for decryption. Identical to `beginEncrypt
|
|
162
|
-
symmetric (encryption and decryption are the same XOR operation).
|
|
179
|
+
Prepares the cipher for decryption. Identical to `beginEncrypt`. ChaCha20 is symmetric; encryption and decryption are the same XOR operation.
|
|
163
180
|
|
|
164
181
|
| Parameter | Type | Description |
|
|
165
182
|
|-----------|------|-------------|
|
|
@@ -193,7 +210,7 @@ when you are done with the instance.
|
|
|
193
210
|
### `Poly1305`
|
|
194
211
|
|
|
195
212
|
Standalone Poly1305 one-time MAC. **Each key must be used exactly once.** You
|
|
196
|
-
almost certainly want `ChaCha20Poly1305` or `XChaCha20Poly1305` instead
|
|
213
|
+
almost certainly want `ChaCha20Poly1305` or `XChaCha20Poly1305` instead. They
|
|
197
214
|
handle Poly1305 key derivation automatically.
|
|
198
215
|
|
|
199
216
|
#### Constructor
|
|
@@ -202,7 +219,7 @@ handle Poly1305 key derivation automatically.
|
|
|
202
219
|
new Poly1305()
|
|
203
220
|
```
|
|
204
221
|
|
|
205
|
-
Throws if `init(
|
|
222
|
+
Throws if `init({ chacha20: chacha20Wasm })` has not been called.
|
|
206
223
|
|
|
207
224
|
---
|
|
208
225
|
|
|
@@ -212,10 +229,10 @@ Computes a 16-byte Poly1305 authentication tag over the given message.
|
|
|
212
229
|
|
|
213
230
|
| Parameter | Type | Description |
|
|
214
231
|
|-----------|------|-------------|
|
|
215
|
-
| `key` | `Uint8Array` | 32 bytes
|
|
232
|
+
| `key` | `Uint8Array` | 32 bytes. Must be unique per message. |
|
|
216
233
|
| `msg` | `Uint8Array` | The message to authenticate (any length) |
|
|
217
234
|
|
|
218
|
-
**Returns** `Uint8Array
|
|
235
|
+
**Returns** `Uint8Array`: a 16-byte authentication tag.
|
|
219
236
|
|
|
220
237
|
**Throws** `RangeError` if `key` is not 32 bytes.
|
|
221
238
|
|
|
@@ -242,54 +259,59 @@ to avoid collision risk.
|
|
|
242
259
|
new ChaCha20Poly1305()
|
|
243
260
|
```
|
|
244
261
|
|
|
245
|
-
Throws if `init(
|
|
262
|
+
Throws if `init({ chacha20: chacha20Wasm })` has not been called.
|
|
246
263
|
|
|
247
264
|
---
|
|
248
265
|
|
|
249
|
-
#### `encrypt(key, nonce, plaintext, aad?):
|
|
266
|
+
#### `encrypt(key, nonce, plaintext, aad?): Uint8Array`
|
|
250
267
|
|
|
251
|
-
Encrypts plaintext and
|
|
268
|
+
Encrypts plaintext and returns the ciphertext with the 16-byte Poly1305 tag
|
|
269
|
+
appended.
|
|
270
|
+
|
|
271
|
+
> [!WARNING]
|
|
272
|
+
> Each `ChaCha20Poly1305` instance allows only **one** `encrypt()` call. A second
|
|
273
|
+
> call throws to prevent accidental nonce reuse. Create a new instance for each
|
|
274
|
+
> encryption, or use `Seal` with `XChaCha20Cipher` for automatic nonce management.
|
|
252
275
|
|
|
253
276
|
| Parameter | Type | Default | Description |
|
|
254
277
|
|-----------|------|---------|-------------|
|
|
255
278
|
| `key` | `Uint8Array` | | 32 bytes (256-bit key) |
|
|
256
279
|
| `nonce` | `Uint8Array` | | 12 bytes (96-bit nonce) |
|
|
257
280
|
| `plaintext` | `Uint8Array` | | Data to encrypt (up to the module's chunk size limit) |
|
|
258
|
-
| `aad` | `Uint8Array` | `new Uint8Array(0)` | Associated data
|
|
281
|
+
| `aad` | `Uint8Array` | `new Uint8Array(0)` | Associated data. Authenticated but not encrypted. |
|
|
259
282
|
|
|
260
|
-
**Returns** `
|
|
261
|
-
length as plaintext) and a 16-byte authentication tag. You need both to decrypt.
|
|
283
|
+
**Returns** `Uint8Array`: ciphertext + 16-byte tag (length = plaintext.length + 16).
|
|
262
284
|
|
|
263
285
|
**Throws:**
|
|
264
286
|
- `RangeError` if `key` is not 32 bytes
|
|
265
287
|
- `RangeError` if `nonce` is not 12 bytes
|
|
266
288
|
- `RangeError` if `plaintext` exceeds the maximum chunk size
|
|
289
|
+
- `Error` if `encrypt()` has already been called on this instance
|
|
267
290
|
|
|
268
291
|
---
|
|
269
292
|
|
|
270
|
-
#### `decrypt(key, nonce, ciphertext,
|
|
293
|
+
#### `decrypt(key, nonce, ciphertext, aad?): Uint8Array`
|
|
271
294
|
|
|
272
|
-
Verifies the authentication tag and decrypts the ciphertext.
|
|
273
|
-
|
|
295
|
+
Verifies the authentication tag and decrypts the ciphertext. The `ciphertext`
|
|
296
|
+
parameter must include the appended 16-byte tag (i.e., the exact output of
|
|
297
|
+
`encrypt()`). If authentication fails, an error is thrown and no plaintext is
|
|
298
|
+
returned.
|
|
274
299
|
|
|
275
|
-
Tag comparison uses a constant-time XOR-accumulate pattern
|
|
276
|
-
channel leaks whether the tag was "close" to correct.
|
|
300
|
+
Tag comparison uses a constant-time XOR-accumulate pattern; no timing side channel leaks whether the tag was "close" to correct.
|
|
277
301
|
|
|
278
302
|
| Parameter | Type | Default | Description |
|
|
279
303
|
|-----------|------|---------|-------------|
|
|
280
304
|
| `key` | `Uint8Array` | | 32 bytes (same key used for encryption) |
|
|
281
305
|
| `nonce` | `Uint8Array` | | 12 bytes (same nonce used for encryption) |
|
|
282
|
-
| `ciphertext` | `Uint8Array` | | Encrypted data |
|
|
283
|
-
| `tag` | `Uint8Array` | | 16-byte authentication tag from `encrypt()` |
|
|
306
|
+
| `ciphertext` | `Uint8Array` | | Encrypted data with appended tag (output of `encrypt()`) |
|
|
284
307
|
| `aad` | `Uint8Array` | `new Uint8Array(0)` | Associated data (must match what was passed to `encrypt()`) |
|
|
285
308
|
|
|
286
|
-
**Returns** `Uint8Array
|
|
309
|
+
**Returns** `Uint8Array`: the decrypted plaintext.
|
|
287
310
|
|
|
288
311
|
**Throws:**
|
|
289
312
|
- `RangeError` if `key` is not 32 bytes
|
|
290
313
|
- `RangeError` if `nonce` is not 12 bytes
|
|
291
|
-
- `RangeError` if `
|
|
292
|
-
- `RangeError` if `ciphertext` exceeds the maximum chunk size
|
|
314
|
+
- `RangeError` if `ciphertext` is shorter than 16 bytes (no room for a tag)
|
|
293
315
|
- `Error('ChaCha20Poly1305: authentication failed')` if the tag does not match
|
|
294
316
|
|
|
295
317
|
---
|
|
@@ -303,15 +325,15 @@ Wipes all key material and intermediate state from WASM memory.
|
|
|
303
325
|
### `XChaCha20Poly1305`
|
|
304
326
|
|
|
305
327
|
XChaCha20-Poly1305 AEAD (draft-irtf-cfrg-xchacha). RFC-faithful stateless
|
|
306
|
-
primitive
|
|
307
|
-
requires explicit nonce control. For most use cases, prefer `
|
|
308
|
-
(
|
|
328
|
+
primitive. Key and nonce are passed per-call. Use when protocol interop
|
|
329
|
+
requires explicit nonce control. For most use cases, prefer `Seal` with
|
|
330
|
+
`XChaCha20Cipher` (automatic nonce management, no instantiation).
|
|
309
331
|
|
|
310
332
|
It uses a 24-byte (192-bit) nonce, which is large enough that randomly generated
|
|
311
333
|
nonces will never collide in practice. Internally, it derives a subkey via
|
|
312
334
|
HChaCha20 and delegates to `ChaCha20Poly1305`.
|
|
313
335
|
|
|
314
|
-
|
|
336
|
+
Like `ChaCha20Poly1305`, the `encrypt()` method returns a single `Uint8Array`
|
|
315
337
|
with the tag appended to the ciphertext. The `decrypt()` method expects this
|
|
316
338
|
combined format and splits it internally.
|
|
317
339
|
|
|
@@ -321,7 +343,7 @@ combined format and splits it internally.
|
|
|
321
343
|
new XChaCha20Poly1305()
|
|
322
344
|
```
|
|
323
345
|
|
|
324
|
-
Throws if `init(
|
|
346
|
+
Throws if `init({ chacha20: chacha20Wasm })` has not been called.
|
|
325
347
|
|
|
326
348
|
---
|
|
327
349
|
|
|
@@ -334,9 +356,9 @@ Encrypts plaintext and returns the ciphertext with the 16-byte tag appended.
|
|
|
334
356
|
| `key` | `Uint8Array` | | 32 bytes (256-bit key) |
|
|
335
357
|
| `nonce` | `Uint8Array` | | 24 bytes (192-bit nonce) |
|
|
336
358
|
| `plaintext` | `Uint8Array` | | Data to encrypt |
|
|
337
|
-
| `aad` | `Uint8Array` | `new Uint8Array(0)` | Associated data
|
|
359
|
+
| `aad` | `Uint8Array` | `new Uint8Array(0)` | Associated data. Authenticated but not encrypted. |
|
|
338
360
|
|
|
339
|
-
**Returns** `Uint8Array
|
|
361
|
+
**Returns** `Uint8Array`: ciphertext + 16-byte tag (length = plaintext.length + 16).
|
|
340
362
|
|
|
341
363
|
**Throws:**
|
|
342
364
|
- `RangeError` if `key` is not 32 bytes
|
|
@@ -357,7 +379,7 @@ parameter must include the appended 16-byte tag (i.e., the exact output of
|
|
|
357
379
|
| `ciphertext` | `Uint8Array` | | Encrypted data with appended tag (output of `encrypt()`) |
|
|
358
380
|
| `aad` | `Uint8Array` | `new Uint8Array(0)` | Associated data (must match what was passed to `encrypt()`) |
|
|
359
381
|
|
|
360
|
-
**Returns** `Uint8Array
|
|
382
|
+
**Returns** `Uint8Array`: the decrypted plaintext.
|
|
361
383
|
|
|
362
384
|
**Throws:**
|
|
363
385
|
- `RangeError` if `key` is not 32 bytes
|
|
@@ -373,261 +395,159 @@ Wipes all key material and intermediate state from WASM memory.
|
|
|
373
395
|
|
|
374
396
|
---
|
|
375
397
|
|
|
376
|
-
##
|
|
398
|
+
## XChaCha20Cipher
|
|
377
399
|
|
|
378
|
-
XChaCha20-Poly1305
|
|
379
|
-
|
|
380
|
-
`AEAD` interface — `encrypt()` and `decrypt()` require only plaintext and
|
|
381
|
-
optional AAD. Each `encrypt()` call generates a fresh 24-byte random nonce
|
|
382
|
-
internally, eliminating any risk of nonce reuse.
|
|
400
|
+
`CipherSuite` implementation for XChaCha20-Poly1305. Pass to `Seal`,
|
|
401
|
+
`SealStream`, or `OpenStream`. Never instantiated directly.
|
|
383
402
|
|
|
384
|
-
|
|
403
|
+
Requires `init({ chacha20: chacha20Wasm, sha2: sha2Wasm })`.
|
|
385
404
|
|
|
386
405
|
> [!NOTE]
|
|
387
|
-
>
|
|
388
|
-
>
|
|
389
|
-
> use `XChaCha20Poly1305` directly.
|
|
390
|
-
|
|
391
|
-
#### Constructor
|
|
392
|
-
|
|
393
|
-
```typescript
|
|
394
|
-
new XChaCha20Seal(key: Uint8Array) // 32 bytes
|
|
395
|
-
```
|
|
406
|
+
> `sha2` is required by the stream layer for HKDF key derivation, not by
|
|
407
|
+
> `XChaCha20Poly1305` itself.
|
|
396
408
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
409
|
+
| Property | Value |
|
|
410
|
+
|----------|-------|
|
|
411
|
+
| `formatEnum` | `0x01` |
|
|
412
|
+
| `keySize` | `32` |
|
|
413
|
+
| `tagSize` | `16` (Poly1305) |
|
|
414
|
+
| `padded` | `false` |
|
|
415
|
+
| `wasmModules` | `['chacha20', 'sha2']` |
|
|
400
416
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
#### `encrypt(plaintext, aad?): Uint8Array`
|
|
404
|
-
|
|
405
|
-
Encrypts plaintext and returns a sealed blob with nonce prepended and tag
|
|
406
|
-
appended.
|
|
407
|
-
|
|
408
|
-
| Parameter | Type | Default | Description |
|
|
409
|
-
|-----------|------|---------|-------------|
|
|
410
|
-
| `plaintext` | `Uint8Array` | | Data to encrypt (any length, including empty) |
|
|
411
|
-
| `aad` | `Uint8Array` | `new Uint8Array(0)` | Associated data — authenticated but not encrypted |
|
|
412
|
-
|
|
413
|
-
**Returns** `Uint8Array` — `nonce(24) || ciphertext || tag(16)` (length = plaintext.length + 40).
|
|
414
|
-
|
|
415
|
-
---
|
|
417
|
+
#### `XChaCha20Cipher.keygen(): Uint8Array`
|
|
416
418
|
|
|
417
|
-
|
|
419
|
+
Returns `randomBytes(32)`. Convenience method. Not on the `CipherSuite` interface.
|
|
418
420
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
| Parameter | Type | Default | Description |
|
|
422
|
-
|-----------|------|---------|-------------|
|
|
423
|
-
| `ciphertext` | `Uint8Array` | | Sealed blob from `encrypt()` |
|
|
424
|
-
| `aad` | `Uint8Array` | `new Uint8Array(0)` | Associated data (must match what was passed to `encrypt()`) |
|
|
425
|
-
|
|
426
|
-
**Returns** `Uint8Array` — the decrypted plaintext.
|
|
427
|
-
|
|
428
|
-
**Throws:**
|
|
429
|
-
- `RangeError` if `ciphertext` is shorter than 40 bytes (nonce + tag minimum)
|
|
430
|
-
- `Error('ChaCha20Poly1305: authentication failed')` if the tag does not match
|
|
431
|
-
|
|
432
|
-
---
|
|
433
|
-
|
|
434
|
-
#### `dispose(): void`
|
|
435
|
-
|
|
436
|
-
Wipes the key copy and all WASM buffers.
|
|
437
|
-
|
|
438
|
-
---
|
|
439
|
-
|
|
440
|
-
#### Usage
|
|
421
|
+
#### Usage with `Seal`
|
|
441
422
|
|
|
442
423
|
```typescript
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const ct = seal.encrypt(plaintext) // nonce generated internally
|
|
447
|
-
const pt = seal.decrypt(ct) // throws on tamper
|
|
448
|
-
seal.dispose()
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
---
|
|
424
|
+
import { init, Seal, XChaCha20Cipher } from 'leviathan-crypto'
|
|
425
|
+
import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
|
|
426
|
+
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
452
427
|
|
|
453
|
-
|
|
428
|
+
await init({ chacha20: chacha20Wasm, sha2: sha2Wasm })
|
|
454
429
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
Each chunk is independently encrypted with `XChaCha20-Poly1305` using a fresh
|
|
459
|
-
random 24-byte nonce. Stream binding (preventing cross-stream splice and
|
|
460
|
-
reordering) is achieved through per-chunk AAD that includes the stream ID,
|
|
461
|
-
chunk index, and finality flag. Simpler than `SerpentStreamSealer` — no HKDF
|
|
462
|
-
key derivation, no separate HMAC.
|
|
463
|
-
|
|
464
|
-
**Wire format:**
|
|
465
|
-
```
|
|
466
|
-
header: stream_id (16) || chunkSize_u32be (4) = 20 bytes
|
|
467
|
-
chunk: isLast (1) || nonce (24) || ciphertext || tag (16)
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
```typescript
|
|
471
|
-
class XChaCha20StreamSealer {
|
|
472
|
-
constructor(key: Uint8Array, chunkSize?: number, opts?: {
|
|
473
|
-
framed?: boolean;
|
|
474
|
-
aad?: Uint8Array;
|
|
475
|
-
})
|
|
476
|
-
header(): Uint8Array
|
|
477
|
-
seal(plaintext: Uint8Array): Uint8Array
|
|
478
|
-
final(plaintext: Uint8Array): Uint8Array
|
|
479
|
-
dispose(): void
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
class XChaCha20StreamOpener {
|
|
483
|
-
constructor(key: Uint8Array, header: Uint8Array, opts?: {
|
|
484
|
-
framed?: boolean;
|
|
485
|
-
aad?: Uint8Array;
|
|
486
|
-
})
|
|
487
|
-
open(chunk: Uint8Array): Uint8Array
|
|
488
|
-
feed(bytes: Uint8Array): Uint8Array[]
|
|
489
|
-
dispose(): void
|
|
490
|
-
}
|
|
430
|
+
const key = XChaCha20Cipher.keygen()
|
|
431
|
+
const blob = Seal.encrypt(XChaCha20Cipher, key, plaintext)
|
|
432
|
+
const pt = Seal.decrypt(XChaCha20Cipher, key, blob) // throws on tamper
|
|
491
433
|
```
|
|
492
434
|
|
|
493
|
-
|
|
494
|
-
- **chunkSize** — 1024–65536. Default: 65536.
|
|
495
|
-
- **opts.framed** — prepend `u32be(sealedLen)` to each output for flat streams.
|
|
496
|
-
- **opts.aad** — associated data folded into every chunk's internal AAD.
|
|
497
|
-
|
|
498
|
-
The API shape is identical to `SerpentStreamSealer` / `SerpentStreamOpener` —
|
|
499
|
-
same state machine (`fresh → sealing → dead`), same `header()` / `seal()` /
|
|
500
|
-
`final()` / `open()` / `feed()` contract.
|
|
501
|
-
|
|
502
|
-
#### Usage
|
|
435
|
+
#### Usage with `SealStream` / `OpenStream`
|
|
503
436
|
|
|
504
437
|
```typescript
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const key = randomBytes(32)
|
|
508
|
-
const sealer = new XChaCha20StreamSealer(key, 65536)
|
|
509
|
-
const header = sealer.header()
|
|
438
|
+
import { SealStream, OpenStream } from 'leviathan-crypto/stream'
|
|
439
|
+
import { XChaCha20Cipher } from 'leviathan-crypto/chacha20'
|
|
510
440
|
|
|
511
|
-
const
|
|
512
|
-
const
|
|
441
|
+
const sealer = new SealStream(XChaCha20Cipher, key)
|
|
442
|
+
const preamble = sealer.preamble // 20 bytes, send before first chunk
|
|
443
|
+
const ct0 = sealer.push(chunk0)
|
|
444
|
+
const ctLast = sealer.finalize(lastChunk)
|
|
513
445
|
|
|
514
|
-
const opener = new
|
|
515
|
-
const pt0 = opener.
|
|
516
|
-
const ptLast = opener.
|
|
446
|
+
const opener = new OpenStream(XChaCha20Cipher, key, preamble)
|
|
447
|
+
const pt0 = opener.pull(ct0)
|
|
448
|
+
const ptLast = opener.finalize(ctLast)
|
|
517
449
|
```
|
|
518
450
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
## Parallel pool — `XChaCha20Poly1305Pool`
|
|
522
|
-
|
|
523
|
-
For high-throughput workloads where multiple XChaCha20-Poly1305 operations should
|
|
524
|
-
run concurrently, `XChaCha20Poly1305Pool` dispatches work across a configurable
|
|
525
|
-
number of Web Workers, each holding an isolated `chacha20.wasm` instance in its own
|
|
526
|
-
linear memory. This removes the single-threaded bottleneck of a shared WASM
|
|
527
|
-
instance and allows encryption and decryption operations to proceed in parallel.
|
|
528
|
-
The pool requires `init(['chacha20'])` to be called before construction and is
|
|
529
|
-
created via the static factory `XChaCha20Poly1305Pool.create(opts?)` — see
|
|
530
|
-
[chacha20_pool.md](./chacha20_pool.md) for the full API reference, pool sizing
|
|
531
|
-
guidance, and lifecycle docs.
|
|
451
|
+
See [aead.md](./aead.md) for the full `Seal`, `SealStream`, and `OpenStream` API.
|
|
532
452
|
|
|
533
453
|
---
|
|
534
454
|
|
|
535
455
|
## Usage Examples
|
|
536
456
|
|
|
537
|
-
### Example 1:
|
|
457
|
+
### Example 1: Seal with XChaCha20Cipher (recommended)
|
|
538
458
|
|
|
539
|
-
|
|
540
|
-
|
|
459
|
+
One-shot AEAD. No instantiation, no `dispose()`. The blob format is
|
|
460
|
+
`preamble(20) || ciphertext || tag(16)`.
|
|
541
461
|
|
|
542
462
|
```typescript
|
|
543
|
-
import { init,
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
await init(['chacha20'])
|
|
547
|
-
|
|
548
|
-
// Step 2: Generate a 256-bit encryption key
|
|
549
|
-
const key = randomBytes(32)
|
|
463
|
+
import { init, Seal, XChaCha20Cipher, utf8ToBytes, bytesToUtf8 } from 'leviathan-crypto'
|
|
464
|
+
import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
|
|
465
|
+
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
550
466
|
|
|
551
|
-
|
|
552
|
-
const seal = new XChaCha20Seal(key)
|
|
467
|
+
await init({ chacha20: chacha20Wasm, sha2: sha2Wasm })
|
|
553
468
|
|
|
554
|
-
|
|
555
|
-
const
|
|
556
|
-
const
|
|
557
|
-
// `ciphertext` = nonce(24) || encrypted data || tag(16)
|
|
469
|
+
const key = XChaCha20Cipher.keygen()
|
|
470
|
+
const blob = Seal.encrypt(XChaCha20Cipher, key, utf8ToBytes('Hello, world!'))
|
|
471
|
+
const pt = Seal.decrypt(XChaCha20Cipher, key, blob) // throws on tamper
|
|
558
472
|
|
|
559
|
-
//
|
|
560
|
-
const decrypted = seal.decrypt(ciphertext)
|
|
561
|
-
console.log(bytesToUtf8(decrypted)) // "Hello, world!"
|
|
473
|
+
console.log(bytesToUtf8(pt)) // "Hello, world!"
|
|
562
474
|
|
|
563
|
-
//
|
|
564
|
-
|
|
475
|
+
// Optional: bind metadata without encrypting it (AAD)
|
|
476
|
+
const aad = utf8ToBytes('document-v2')
|
|
477
|
+
const blob2 = Seal.encrypt(XChaCha20Cipher, key, utf8ToBytes('Hello, world!'), { aad })
|
|
478
|
+
const pt2 = Seal.decrypt(XChaCha20Cipher, key, blob2, { aad })
|
|
565
479
|
```
|
|
566
480
|
|
|
567
|
-
>
|
|
568
|
-
>
|
|
481
|
+
> For explicit nonce control, use `XChaCha20Poly1305` directly. Same
|
|
482
|
+
> API shape, 24-byte nonce passed per call. See Example 2.
|
|
569
483
|
|
|
570
|
-
### Example 2: ChaCha20Poly1305
|
|
484
|
+
### Example 2: ChaCha20Poly1305
|
|
571
485
|
|
|
572
486
|
Same idea as above, but with a 12-byte nonce. Use this if you are implementing
|
|
573
487
|
a protocol that specifies RFC 8439 ChaCha20-Poly1305 explicitly.
|
|
574
488
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
489
|
+
Both `ChaCha20Poly1305` and `XChaCha20Poly1305` return a single `Uint8Array`
|
|
490
|
+
with the tag appended, and `decrypt()` expects the same combined format.
|
|
491
|
+
The only difference is the nonce size (12 vs 24 bytes).
|
|
492
|
+
|
|
493
|
+
> [!NOTE]
|
|
494
|
+
> Each `ChaCha20Poly1305` instance allows only one `encrypt()` call. A second
|
|
495
|
+
> call throws to prevent accidental nonce reuse. Create a new instance for each
|
|
496
|
+
> encryption.
|
|
579
497
|
|
|
580
498
|
```typescript
|
|
581
499
|
import { init, ChaCha20Poly1305, randomBytes, utf8ToBytes, bytesToUtf8 } from 'leviathan-crypto'
|
|
500
|
+
import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
|
|
582
501
|
|
|
583
|
-
await init(
|
|
502
|
+
await init({ chacha20: chacha20Wasm })
|
|
584
503
|
|
|
585
504
|
const key = randomBytes(32)
|
|
586
505
|
const aead = new ChaCha20Poly1305()
|
|
587
506
|
|
|
588
507
|
// Encrypt
|
|
589
|
-
const nonce = randomBytes(12) // 12 bytes
|
|
508
|
+
const nonce = randomBytes(12) // 12 bytes, use XChaCha20Poly1305 for high-volume random generation
|
|
590
509
|
const plaintext = utf8ToBytes('Sensitive data')
|
|
591
|
-
const
|
|
592
|
-
//
|
|
510
|
+
const sealed = aead.encrypt(key, nonce, plaintext)
|
|
511
|
+
// sealed = ciphertext || tag(16), store/transmit nonce and sealed together
|
|
593
512
|
|
|
594
|
-
// Decrypt
|
|
595
|
-
const
|
|
513
|
+
// Decrypt (new instance, encrypt is single-use)
|
|
514
|
+
const aead2 = new ChaCha20Poly1305()
|
|
515
|
+
const decrypted = aead2.decrypt(key, nonce, sealed)
|
|
596
516
|
console.log(bytesToUtf8(decrypted)) // "Sensitive data"
|
|
597
517
|
|
|
598
518
|
aead.dispose()
|
|
519
|
+
aead2.dispose()
|
|
599
520
|
```
|
|
600
521
|
|
|
601
522
|
### Example 3: Detecting Tampered Ciphertext
|
|
602
523
|
|
|
603
524
|
AEAD decryption fails loudly if anyone has modified the ciphertext or
|
|
604
|
-
the associated data. This is a feature
|
|
525
|
+
the associated data. This is a feature. It prevents you from processing
|
|
605
526
|
corrupted or maliciously altered data.
|
|
606
527
|
|
|
607
528
|
```typescript
|
|
608
|
-
import { init,
|
|
529
|
+
import { init, Seal, XChaCha20Cipher, randomBytes, utf8ToBytes } from 'leviathan-crypto'
|
|
530
|
+
import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
|
|
531
|
+
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
609
532
|
|
|
610
|
-
await init(
|
|
533
|
+
await init({ chacha20: chacha20Wasm, sha2: sha2Wasm })
|
|
611
534
|
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
const sealed = seal.encrypt(utf8ToBytes('Original message'))
|
|
535
|
+
const key = XChaCha20Cipher.keygen()
|
|
536
|
+
const sealed = Seal.encrypt(XChaCha20Cipher, key, utf8ToBytes('Original message'))
|
|
615
537
|
|
|
616
538
|
// Simulate tampering: flip one bit in the ciphertext
|
|
617
539
|
const tampered = new Uint8Array(sealed)
|
|
618
|
-
tampered[
|
|
540
|
+
tampered[20] ^= 0x01 // byte 20 is the first ciphertext byte (after the 20-byte preamble)
|
|
619
541
|
|
|
620
542
|
try {
|
|
621
|
-
const plaintext =
|
|
543
|
+
const plaintext = Seal.decrypt(XChaCha20Cipher, key, tampered)
|
|
622
544
|
// This line is never reached
|
|
623
545
|
console.log(plaintext)
|
|
624
546
|
} catch (err) {
|
|
625
547
|
console.error(err.message)
|
|
626
548
|
// "ChaCha20Poly1305: authentication failed"
|
|
627
|
-
// The plaintext is never returned
|
|
549
|
+
// The plaintext is never returned. Decryption stops immediately on failure.
|
|
628
550
|
}
|
|
629
|
-
|
|
630
|
-
seal.dispose()
|
|
631
551
|
```
|
|
632
552
|
|
|
633
553
|
### Example 4: Using Associated Data (AAD)
|
|
@@ -637,68 +557,69 @@ not encrypt. Common uses: user IDs, message sequence numbers, protocol version
|
|
|
637
557
|
headers, routing information.
|
|
638
558
|
|
|
639
559
|
```typescript
|
|
640
|
-
import { init,
|
|
560
|
+
import { init, Seal, XChaCha20Cipher, randomBytes, utf8ToBytes, bytesToUtf8 } from 'leviathan-crypto'
|
|
561
|
+
import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
|
|
562
|
+
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
641
563
|
|
|
642
|
-
await init(
|
|
564
|
+
await init({ chacha20: chacha20Wasm, sha2: sha2Wasm })
|
|
643
565
|
|
|
644
|
-
const
|
|
566
|
+
const key = XChaCha20Cipher.keygen()
|
|
645
567
|
|
|
646
568
|
// The user ID travels in the clear, but decryption will fail if anyone changes it
|
|
647
569
|
const userId = utf8ToBytes('user-12345')
|
|
648
570
|
const message = utf8ToBytes('Your account balance is $1,000,000')
|
|
649
571
|
|
|
650
|
-
const sealed =
|
|
572
|
+
const sealed = Seal.encrypt(XChaCha20Cipher, key, message, { aad: userId })
|
|
651
573
|
|
|
652
|
-
// Decrypt
|
|
653
|
-
const decrypted =
|
|
574
|
+
// Decrypt, pass the same AAD
|
|
575
|
+
const decrypted = Seal.decrypt(XChaCha20Cipher, key, sealed, { aad: userId })
|
|
654
576
|
console.log(bytesToUtf8(decrypted))
|
|
655
577
|
// "Your account balance is $1,000,000"
|
|
656
578
|
|
|
657
579
|
// If someone changes the AAD, decryption fails
|
|
658
580
|
const wrongUserId = utf8ToBytes('user-99999')
|
|
659
581
|
try {
|
|
660
|
-
|
|
582
|
+
Seal.decrypt(XChaCha20Cipher, key, sealed, { aad: wrongUserId })
|
|
661
583
|
} catch (err) {
|
|
662
584
|
console.error(err.message)
|
|
663
585
|
// "ChaCha20Poly1305: authentication failed"
|
|
664
586
|
// Even though the ciphertext was not modified, the AAD mismatch is detected.
|
|
665
587
|
}
|
|
666
|
-
|
|
667
|
-
seal.dispose()
|
|
668
588
|
```
|
|
669
589
|
|
|
670
590
|
### Example 5: Encrypting and Decrypting Binary Data
|
|
671
591
|
|
|
672
|
-
The API works with raw bytes
|
|
592
|
+
The API works with raw bytes, not just text. Here is an example encrypting
|
|
673
593
|
arbitrary binary content.
|
|
674
594
|
|
|
675
595
|
```typescript
|
|
676
|
-
import { init,
|
|
596
|
+
import { init, Seal, XChaCha20Cipher, randomBytes } from 'leviathan-crypto'
|
|
597
|
+
import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
|
|
598
|
+
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
677
599
|
|
|
678
|
-
await init(
|
|
600
|
+
await init({ chacha20: chacha20Wasm, sha2: sha2Wasm })
|
|
679
601
|
|
|
680
|
-
const
|
|
602
|
+
const key = XChaCha20Cipher.keygen()
|
|
681
603
|
|
|
682
604
|
// Encrypt binary data (e.g., an image thumbnail, a protobuf, a file chunk)
|
|
683
605
|
const binaryData = new Uint8Array([0x89, 0x50, 0x4e, 0x47, /* ...more bytes... */])
|
|
684
|
-
const sealed =
|
|
606
|
+
const sealed = Seal.encrypt(XChaCha20Cipher, key, binaryData)
|
|
685
607
|
|
|
686
608
|
// Decrypt
|
|
687
|
-
const recovered =
|
|
609
|
+
const recovered = Seal.decrypt(XChaCha20Cipher, key, sealed)
|
|
688
610
|
// `recovered` is byte-identical to `binaryData`
|
|
689
|
-
|
|
690
|
-
seal.dispose()
|
|
691
611
|
```
|
|
692
612
|
|
|
693
613
|
### Example 6: Raw ChaCha20 Stream Cipher (Advanced)
|
|
694
614
|
|
|
695
615
|
Use this only if you are building a custom protocol and will add your own
|
|
696
|
-
authentication layer. For almost all use cases, use `
|
|
616
|
+
authentication layer. For almost all use cases, use `Seal` with `XChaCha20Cipher` instead.
|
|
697
617
|
|
|
698
618
|
```typescript
|
|
699
619
|
import { init, ChaCha20, randomBytes } from 'leviathan-crypto'
|
|
620
|
+
import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
|
|
700
621
|
|
|
701
|
-
await init(
|
|
622
|
+
await init({ chacha20: chacha20Wasm })
|
|
702
623
|
|
|
703
624
|
const key = randomBytes(32)
|
|
704
625
|
const nonce = randomBytes(12)
|
|
@@ -709,7 +630,7 @@ cipher.beginEncrypt(key, nonce)
|
|
|
709
630
|
const ct1 = cipher.encryptChunk(new Uint8Array([1, 2, 3, 4]))
|
|
710
631
|
const ct2 = cipher.encryptChunk(new Uint8Array([5, 6, 7, 8]))
|
|
711
632
|
|
|
712
|
-
// Decrypt
|
|
633
|
+
// Decrypt, uses the same key and nonce
|
|
713
634
|
cipher.beginDecrypt(key, nonce)
|
|
714
635
|
const pt1 = cipher.decryptChunk(ct1)
|
|
715
636
|
const pt2 = cipher.decryptChunk(ct2)
|
|
@@ -722,27 +643,32 @@ const pt2 = cipher.decryptChunk(ct2)
|
|
|
722
643
|
cipher.dispose()
|
|
723
644
|
```
|
|
724
645
|
|
|
646
|
+
---
|
|
647
|
+
|
|
725
648
|
## Error Conditions
|
|
726
649
|
|
|
727
650
|
| Condition | Error Type | Message |
|
|
728
651
|
|-----------|-----------|---------|
|
|
729
|
-
| `init(
|
|
652
|
+
| `init({ chacha20: ... })` not called before constructing a class | `Error` | `leviathan-crypto: call init({ chacha20: ... }) before using this class` |
|
|
730
653
|
| Key is not 32 bytes | `RangeError` | `ChaCha20 key must be 32 bytes (got N)` / `key must be 32 bytes (got N)` / `Poly1305 key must be 32 bytes (got N)` |
|
|
731
654
|
| `ChaCha20` nonce is not 12 bytes | `RangeError` | `ChaCha20 nonce must be 12 bytes (got N)` |
|
|
732
655
|
| `ChaCha20Poly1305` nonce is not 12 bytes | `RangeError` | `nonce must be 12 bytes (got N)` |
|
|
733
656
|
| `XChaCha20Poly1305` nonce is not 24 bytes | `RangeError` | `XChaCha20 nonce must be 24 bytes (got N)` |
|
|
734
|
-
| `ChaCha20Poly1305`
|
|
657
|
+
| `ChaCha20Poly1305` ciphertext shorter than 16 bytes | `RangeError` | `ciphertext too short — must include 16-byte tag (got N)` |
|
|
735
658
|
| `XChaCha20Poly1305` ciphertext shorter than 16 bytes | `RangeError` | `ciphertext too short — must include 16-byte tag (got N)` |
|
|
659
|
+
| `ChaCha20Poly1305.encrypt()` called a second time | `Error` | Single-use encrypt guard. Create a new instance for each encryption. |
|
|
736
660
|
| Chunk or plaintext exceeds WASM buffer size | `RangeError` | `plaintext exceeds N bytes — split into smaller chunks` / `chunk exceeds maximum size of N bytes — split into smaller chunks` |
|
|
737
661
|
| Authentication tag does not match on decrypt | `Error` | `ChaCha20Poly1305: authentication failed` |
|
|
738
|
-
| Empty plaintext |
|
|
662
|
+
| Empty plaintext | | Allowed. Encrypting zero bytes produces just a 16-byte tag (AEAD) or zero bytes (raw ChaCha20). |
|
|
739
663
|
|
|
740
664
|
> ## Cross-References
|
|
741
665
|
>
|
|
742
666
|
> - [index](./README.md) — Project Documentation index
|
|
667
|
+
> - [lexicon](./lexicon.md) — Glossary of cryptographic terms
|
|
743
668
|
> - [asm_chacha](./asm_chacha.md) — WASM (AssemblyScript) implementation details for the chacha20 module
|
|
744
|
-
> - [
|
|
745
|
-
> - [serpent](./serpent.md) — alternative
|
|
746
|
-
> - [sha2](./sha2.md) — SHA-2 hashes and HMAC
|
|
669
|
+
> - [authenticated encryption](./aead.md) — `Seal`, `SealStream`, `OpenStream`: use `XChaCha20Cipher` as the suite argument
|
|
670
|
+
> - [serpent](./serpent.md) — `SerpentCipher`: alternative `CipherSuite` for `Seal` and streaming
|
|
671
|
+
> - [sha2](./sha2.md) — SHA-2 hashes and HMAC. Needed for Encrypt-then-MAC if using raw ChaCha20
|
|
747
672
|
> - [types](./types.md) — `AEAD` and `Streamcipher` interfaces implemented by ChaCha20 classes
|
|
748
|
-
> - [
|
|
673
|
+
> - [architecture](./architecture.md) — architecture overview, module relationships, buffer layouts, and build pipeline
|
|
674
|
+
> - [chacha_audit](./chacha_audit.md) — XChaCha20-Poly1305 implementation audit
|