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/serpent.md
CHANGED
|
@@ -1,24 +1,31 @@
|
|
|
1
|
-
# Serpent-256
|
|
1
|
+
# Serpent-256 TypeScript API
|
|
2
2
|
|
|
3
3
|
> [!NOTE]
|
|
4
|
-
> Authenticated encryption via `SerpentSeal`, plus low-level block, CTR, and CBC
|
|
5
|
-
> classes for advanced use.
|
|
6
|
-
>
|
|
7
4
|
> See [Serpent implementation audit](./serpent_audit.md) for algorithm correctness verifications.
|
|
8
5
|
|
|
6
|
+
> ### Table of Contents
|
|
7
|
+
> - [Overview](#overview)
|
|
8
|
+
> - [Security Notes](#security-notes)
|
|
9
|
+
> - [Module Init](#module-init)
|
|
10
|
+
> - [API Reference](#api-reference)
|
|
11
|
+
> - [Usage Examples](#usage-examples)
|
|
12
|
+
> - [Error Conditions](#error-conditions)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
9
16
|
## Overview
|
|
10
17
|
|
|
11
|
-
`
|
|
12
|
-
|
|
13
|
-
no separate MAC step, no room for misuse. Internally it
|
|
14
|
-
(
|
|
18
|
+
`SerpentCipher` is the primary API for authenticated Serpent-256 encryption. Pass it
|
|
19
|
+
to `Seal` for one-shot AEAD, or to `SealStream`/`OpenStream` for streaming. There is
|
|
20
|
+
no manual IV generation, no separate MAC step, and no room for misuse. Internally it
|
|
21
|
+
uses Encrypt-then-MAC (Serpent-CBC + HMAC-SHA-256) with HKDF key derivation.
|
|
15
22
|
|
|
16
23
|
For advanced use cases, three lower-level classes are available: `Serpent` (raw
|
|
17
24
|
16-byte block operations), `SerpentCtr` (counter mode streaming), and `SerpentCbc`
|
|
18
25
|
(cipher block chaining with PKCS7 padding). These are unauthenticated and require
|
|
19
26
|
explicit opt-in.
|
|
20
27
|
|
|
21
|
-
Serpent was an AES finalist. It uses 32 rounds versus AES's 10
|
|
28
|
+
Serpent was an AES finalist. It uses 32 rounds versus AES's 10 to 14, yielding a
|
|
22
29
|
larger security margin at comparable speed in WASM.
|
|
23
30
|
|
|
24
31
|
---
|
|
@@ -33,35 +40,31 @@ larger security margin at comparable speed in WASM.
|
|
|
33
40
|
|
|
34
41
|
This is the most dangerous mistake you can make with this module. An attacker who
|
|
35
42
|
can modify ciphertext encrypted with `SerpentCbc` or `SerpentCtr` will produce
|
|
36
|
-
corrupted plaintext on decryption
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
corrupted plaintext on decryption. Decryption succeeds without any indication of
|
|
44
|
+
tampering. There is no integrity check. Your caller receives garbage and has no way
|
|
45
|
+
to distinguish it from the original message.
|
|
39
46
|
|
|
40
|
-
`
|
|
41
|
-
and verifies it before decryption. If anything has been modified,
|
|
42
|
-
instead of returning corrupted data.
|
|
47
|
+
`Seal` with `SerpentCipher` eliminates this problem. It computes an HMAC tag over
|
|
48
|
+
the ciphertext and verifies it before decryption. If anything has been modified,
|
|
49
|
+
`Seal.decrypt()` throws instead of returning corrupted data.
|
|
43
50
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
[chacha20.md](./chacha20.md).
|
|
51
|
+
`Seal` with `XChaCha20Cipher` is an alternative using a different cipher.
|
|
52
|
+
See [chacha20.md](./chacha20.md).
|
|
47
53
|
|
|
48
54
|
### Never reuse a nonce or IV with the same key
|
|
49
55
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
each encryption. A predictable IV enables chosen-plaintext attacks.
|
|
56
|
+
In CTR mode, reusing a nonce with the same key is catastrophic. It produces the
|
|
57
|
+
same keystream, which means an attacker can XOR two ciphertexts together and
|
|
58
|
+
recover both plaintexts. Always generate a fresh random nonce for each message.
|
|
59
|
+
In CBC mode, the IV must be random and unpredictable for each encryption. A predictable IV enables chosen-plaintext attacks.
|
|
55
60
|
|
|
56
|
-
Use `randomBytes(16)` to generate nonces and IVs. `
|
|
57
|
-
generation internally.
|
|
61
|
+
Use `randomBytes(16)` to generate nonces and IVs. `Seal` with `SerpentCipher` handles IV generation internally.
|
|
58
62
|
|
|
59
63
|
### Always use 256-bit keys
|
|
60
64
|
|
|
61
65
|
Unless you have a specific reason to use a shorter key, pass a 32-byte key to
|
|
62
66
|
every Serpent operation. Shorter keys provide less security margin and there is no
|
|
63
|
-
meaningful performance benefit to using them. `
|
|
64
|
-
(32 bytes encryption + 32 bytes MAC).
|
|
67
|
+
meaningful performance benefit to using them. `SerpentCipher` requires a 32-byte key; HKDF derives enc/mac/iv keys internally.
|
|
65
68
|
|
|
66
69
|
### Call dispose() when done
|
|
67
70
|
|
|
@@ -77,24 +80,25 @@ in memory longer than necessary.
|
|
|
77
80
|
Each module subpath exports its own init function for consumers who want
|
|
78
81
|
tree-shakeable imports.
|
|
79
82
|
|
|
80
|
-
### `serpentInit(
|
|
83
|
+
### `serpentInit(source)`
|
|
81
84
|
|
|
82
85
|
Initializes only the serpent WASM binary. Equivalent to calling the
|
|
83
|
-
root `init(
|
|
86
|
+
root `init({ serpent: serpentWasm })` but without pulling the other three
|
|
84
87
|
modules into the bundle.
|
|
85
88
|
|
|
86
89
|
**Signature:**
|
|
87
90
|
|
|
88
91
|
```typescript
|
|
89
|
-
async function serpentInit(
|
|
92
|
+
async function serpentInit(source: WasmSource): Promise<void>
|
|
90
93
|
```
|
|
91
94
|
|
|
92
95
|
**Usage:**
|
|
93
96
|
|
|
94
97
|
```typescript
|
|
95
98
|
import { serpentInit, Serpent } from 'leviathan-crypto/serpent'
|
|
99
|
+
import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
|
|
96
100
|
|
|
97
|
-
await serpentInit()
|
|
101
|
+
await serpentInit(serpentWasm)
|
|
98
102
|
const cipher = new Serpent()
|
|
99
103
|
```
|
|
100
104
|
|
|
@@ -103,73 +107,66 @@ const cipher = new Serpent()
|
|
|
103
107
|
## API Reference
|
|
104
108
|
|
|
105
109
|
All classes require their WASM modules to be initialized before construction.
|
|
106
|
-
`
|
|
107
|
-
`serpent` only.
|
|
110
|
+
`SerpentCipher` (and therefore `Seal`, `SealStream`, `OpenStream`) requires both
|
|
111
|
+
`serpent` and `sha2`. `Serpent`, `SerpentCtr`, and `SerpentCbc` require `serpent` only.
|
|
108
112
|
|
|
109
|
-
###
|
|
113
|
+
### SerpentCipher
|
|
110
114
|
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
`CipherSuite` implementation for Serpent-256 CBC+HMAC-SHA-256. Pass to `Seal`,
|
|
116
|
+
`SealStream`, or `OpenStream`. Never instantiated directly.
|
|
113
117
|
|
|
114
|
-
|
|
115
|
-
class SerpentSeal {
|
|
116
|
-
constructor()
|
|
117
|
-
encrypt(key: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Uint8Array
|
|
118
|
-
decrypt(key: Uint8Array, data: Uint8Array, aad?: Uint8Array): Uint8Array
|
|
119
|
-
dispose(): void
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
#### `constructor()`
|
|
124
|
-
|
|
125
|
-
Creates a new SerpentSeal instance. Throws if `init(['serpent', 'sha2'])` has not
|
|
126
|
-
been called.
|
|
118
|
+
Requires `init({ serpent: serpentWasm, sha2: sha2Wasm })`.
|
|
127
119
|
|
|
128
|
-
|
|
120
|
+
| Property | Value |
|
|
121
|
+
|----------|-------|
|
|
122
|
+
| `formatEnum` | `0x02` |
|
|
123
|
+
| `keySize` | `32` |
|
|
124
|
+
| `tagSize` | `32` (HMAC-SHA-256) |
|
|
125
|
+
| `padded` | `true` (PKCS7) |
|
|
126
|
+
| `wasmModules` | `['serpent', 'sha2']` |
|
|
129
127
|
|
|
130
|
-
#### `
|
|
128
|
+
#### `SerpentCipher.keygen(): Uint8Array`
|
|
131
129
|
|
|
132
|
-
|
|
133
|
-
authentication data. The output is opaque -- pass it directly to `decrypt()`.
|
|
130
|
+
Returns `randomBytes(32)`. Convenience method. Not on the `CipherSuite` interface.
|
|
134
131
|
|
|
135
|
-
|
|
136
|
-
Throws `RangeError` if the length is not 64.
|
|
137
|
-
- **plaintext** -- any length.
|
|
138
|
-
- **aad** -- optional associated data. Authenticated but not encrypted. Bound
|
|
139
|
-
into the HMAC-SHA256 tag. If omitted, equivalent to empty.
|
|
132
|
+
#### Usage with `Seal`
|
|
140
133
|
|
|
141
|
-
|
|
142
|
-
|
|
134
|
+
```typescript
|
|
135
|
+
import { init, Seal, SerpentCipher } from 'leviathan-crypto'
|
|
136
|
+
import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
|
|
137
|
+
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
143
138
|
|
|
144
|
-
|
|
139
|
+
await init({ serpent: serpentWasm, sha2: sha2Wasm })
|
|
145
140
|
|
|
146
|
-
|
|
141
|
+
const key = SerpentCipher.keygen()
|
|
142
|
+
const blob = Seal.encrypt(SerpentCipher, key, plaintext)
|
|
143
|
+
const pt = Seal.decrypt(SerpentCipher, key, blob) // throws on tamper
|
|
144
|
+
```
|
|
147
145
|
|
|
148
|
-
|
|
149
|
-
happens before decryption -- if the data has been tampered with, `decrypt()` throws
|
|
150
|
-
and never returns corrupted plaintext.
|
|
146
|
+
#### Usage with `SealStream` / `OpenStream`
|
|
151
147
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
`RangeError` if shorter. Throws `Error` if authentication fails.
|
|
156
|
-
- **aad** -- optional associated data. If `aad` was passed to `encrypt()`, the
|
|
157
|
-
same value must be passed to `decrypt()`. Mismatch causes authentication failure.
|
|
148
|
+
```typescript
|
|
149
|
+
import { SealStream, OpenStream } from 'leviathan-crypto/stream'
|
|
150
|
+
import { SerpentCipher } from 'leviathan-crypto/serpent'
|
|
158
151
|
|
|
159
|
-
|
|
152
|
+
const sealer = new SealStream(SerpentCipher, key)
|
|
153
|
+
const preamble = sealer.preamble // 20 bytes, send before first chunk
|
|
154
|
+
const ct0 = sealer.push(chunk0)
|
|
155
|
+
const ctLast = sealer.finalize(lastChunk)
|
|
160
156
|
|
|
161
|
-
|
|
157
|
+
const opener = new OpenStream(SerpentCipher, key, preamble)
|
|
158
|
+
const pt0 = opener.pull(ct0)
|
|
159
|
+
const ptLast = opener.finalize(ctLast)
|
|
160
|
+
```
|
|
162
161
|
|
|
163
|
-
|
|
164
|
-
internal SerpentCbc and HMAC_SHA256 instances.
|
|
162
|
+
See [aead.md](./aead.md) for the full `Seal`, `SealStream`, and `OpenStream` API.
|
|
165
163
|
|
|
166
164
|
---
|
|
167
165
|
|
|
168
166
|
### Serpent
|
|
169
167
|
|
|
170
168
|
Raw Serpent block encryption and decryption. Operates on exactly 16-byte blocks.
|
|
171
|
-
This class is a low-level building block
|
|
172
|
-
instead.
|
|
169
|
+
This class is a low-level building block; use `Seal` with `SerpentCipher` for most purposes.
|
|
173
170
|
|
|
174
171
|
```typescript
|
|
175
172
|
class Serpent {
|
|
@@ -183,7 +180,7 @@ class Serpent {
|
|
|
183
180
|
|
|
184
181
|
#### `constructor()`
|
|
185
182
|
|
|
186
|
-
Creates a new Serpent instance. Throws if `init(
|
|
183
|
+
Creates a new Serpent instance. Throws if `init({ serpent: serpentWasm })` has not been called.
|
|
187
184
|
|
|
188
185
|
---
|
|
189
186
|
|
|
@@ -192,7 +189,7 @@ Creates a new Serpent instance. Throws if `init(['serpent'])` has not been calle
|
|
|
192
189
|
Loads and expands a key for subsequent block operations. Must be called before
|
|
193
190
|
`encryptBlock()` or `decryptBlock()`.
|
|
194
191
|
|
|
195
|
-
- **key
|
|
192
|
+
- **key**: 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
|
|
196
193
|
|
|
197
194
|
---
|
|
198
195
|
|
|
@@ -200,7 +197,7 @@ Loads and expands a key for subsequent block operations. Must be called before
|
|
|
200
197
|
|
|
201
198
|
Encrypts a single 16-byte block and returns the 16-byte ciphertext.
|
|
202
199
|
|
|
203
|
-
- **plaintext
|
|
200
|
+
- **plaintext**: exactly 16 bytes. Throws `RangeError` if the length is not 16.
|
|
204
201
|
|
|
205
202
|
---
|
|
206
203
|
|
|
@@ -208,7 +205,7 @@ Encrypts a single 16-byte block and returns the 16-byte ciphertext.
|
|
|
208
205
|
|
|
209
206
|
Decrypts a single 16-byte block and returns the 16-byte plaintext.
|
|
210
207
|
|
|
211
|
-
- **ciphertext
|
|
208
|
+
- **ciphertext**: exactly 16 bytes. Throws `RangeError` if the length is not 16.
|
|
212
209
|
|
|
213
210
|
---
|
|
214
211
|
|
|
@@ -226,7 +223,7 @@ stream of chunks.
|
|
|
226
223
|
|
|
227
224
|
> [!WARNING]
|
|
228
225
|
> CTR mode is unauthenticated. An attacker can modify ciphertext
|
|
229
|
-
> without detection. Use `
|
|
226
|
+
> without detection. Use `Seal` with `SerpentCipher` for authenticated encryption, or pair
|
|
230
227
|
> with HMAC-SHA256 (Encrypt-then-MAC).
|
|
231
228
|
|
|
232
229
|
```typescript
|
|
@@ -242,11 +239,11 @@ class SerpentCtr {
|
|
|
242
239
|
|
|
243
240
|
#### `constructor(opts: { dangerUnauthenticated: true })`
|
|
244
241
|
|
|
245
|
-
Creates a new SerpentCtr instance. Throws if `init(
|
|
242
|
+
Creates a new SerpentCtr instance. Throws if `init({ serpent: serpentWasm })` has not been
|
|
246
243
|
called. Throws if `{ dangerUnauthenticated: true }` is not passed:
|
|
247
244
|
|
|
248
245
|
```
|
|
249
|
-
leviathan-crypto: SerpentCtr is unauthenticated — use
|
|
246
|
+
leviathan-crypto: SerpentCtr is unauthenticated — use Seal with SerpentCipher instead.
|
|
250
247
|
To use SerpentCtr directly, pass { dangerUnauthenticated: true }.
|
|
251
248
|
```
|
|
252
249
|
|
|
@@ -257,8 +254,8 @@ To use SerpentCtr directly, pass { dangerUnauthenticated: true }.
|
|
|
257
254
|
Initializes the CTR state for encryption. Loads the key, sets the nonce, and
|
|
258
255
|
resets the internal counter to zero.
|
|
259
256
|
|
|
260
|
-
- **key
|
|
261
|
-
- **nonce
|
|
257
|
+
- **key**: 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
|
|
258
|
+
- **nonce**: exactly 16 bytes. Throws `RangeError` if the length is not 16.
|
|
262
259
|
|
|
263
260
|
---
|
|
264
261
|
|
|
@@ -268,24 +265,19 @@ Encrypts a chunk of plaintext and returns the same-length ciphertext. Call this
|
|
|
268
265
|
one or more times after `beginEncrypt()`. The internal counter advances
|
|
269
266
|
automatically.
|
|
270
267
|
|
|
271
|
-
- **chunk
|
|
272
|
-
`RangeError` if the chunk exceeds the maximum size.
|
|
268
|
+
- **chunk**: any length up to the module's internal chunk buffer size. Throws `RangeError` if the chunk exceeds the maximum size.
|
|
273
269
|
|
|
274
270
|
> [!NOTE]
|
|
275
|
-
> Automatically dispatches to the 4-wide SIMD path (`encryptChunk_simd`) when
|
|
276
|
-
> the runtime supports WebAssembly SIMD (`hasSIMD()` returns `true`), otherwise
|
|
277
|
-
> falls back to the scalar unrolled path. The dispatch is transparent — no API
|
|
278
|
-
> change required.
|
|
271
|
+
> Automatically dispatches to the 4-wide SIMD path (`encryptChunk_simd`) when the runtime supports WebAssembly SIMD (`hasSIMD()` returns `true`), otherwise falls back to the scalar unrolled path. The dispatch is transparent with no API change required.
|
|
279
272
|
|
|
280
273
|
---
|
|
281
274
|
|
|
282
275
|
#### `beginDecrypt(key: Uint8Array, nonce: Uint8Array): void`
|
|
283
276
|
|
|
284
|
-
Initializes the CTR state for decryption. Functionally identical to
|
|
285
|
-
`beginEncrypt()` -- CTR mode uses the same operation in both directions.
|
|
277
|
+
Initializes the CTR state for decryption. Functionally identical to `beginEncrypt()`. CTR mode uses the same operation in both directions.
|
|
286
278
|
|
|
287
|
-
- **key
|
|
288
|
-
- **nonce
|
|
279
|
+
- **key**: 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
|
|
280
|
+
- **nonce**: exactly 16 bytes. Throws `RangeError` if the length is not 16.
|
|
289
281
|
|
|
290
282
|
---
|
|
291
283
|
|
|
@@ -294,8 +286,7 @@ Initializes the CTR state for decryption. Functionally identical to
|
|
|
294
286
|
Decrypts a chunk of ciphertext and returns the same-length plaintext.
|
|
295
287
|
Functionally identical to `encryptChunk()`.
|
|
296
288
|
|
|
297
|
-
- **chunk
|
|
298
|
-
`RangeError` if the chunk exceeds the maximum size.
|
|
289
|
+
- **chunk**: any length up to the module's internal chunk buffer size. Throws `RangeError` if the chunk exceeds the maximum size.
|
|
299
290
|
|
|
300
291
|
---
|
|
301
292
|
|
|
@@ -312,7 +303,7 @@ Encrypts and decrypts entire messages in a single call.
|
|
|
312
303
|
|
|
313
304
|
> [!WARNING]
|
|
314
305
|
> CBC mode is unauthenticated. Always authenticate the output with
|
|
315
|
-
> HMAC-SHA256 (Encrypt-then-MAC) or use `
|
|
306
|
+
> HMAC-SHA256 (Encrypt-then-MAC) or use `Seal` with `SerpentCipher` instead.
|
|
316
307
|
|
|
317
308
|
```typescript
|
|
318
309
|
class SerpentCbc {
|
|
@@ -325,11 +316,11 @@ class SerpentCbc {
|
|
|
325
316
|
|
|
326
317
|
#### `constructor(opts: { dangerUnauthenticated: true })`
|
|
327
318
|
|
|
328
|
-
Creates a new SerpentCbc instance. Throws if `init(
|
|
319
|
+
Creates a new SerpentCbc instance. Throws if `init({ serpent: serpentWasm })` has not been
|
|
329
320
|
called. Throws if `{ dangerUnauthenticated: true }` is not passed:
|
|
330
321
|
|
|
331
322
|
```
|
|
332
|
-
leviathan-crypto: SerpentCbc is unauthenticated — use
|
|
323
|
+
leviathan-crypto: SerpentCbc is unauthenticated — use Seal with SerpentCipher instead.
|
|
333
324
|
To use SerpentCbc directly, pass { dangerUnauthenticated: true }.
|
|
334
325
|
```
|
|
335
326
|
|
|
@@ -341,11 +332,9 @@ Encrypts plaintext with Serpent CBC and PKCS7 padding. The returned ciphertext i
|
|
|
341
332
|
always a multiple of 16 bytes and is at least 16 bytes longer than the input (due
|
|
342
333
|
to padding).
|
|
343
334
|
|
|
344
|
-
- **key
|
|
345
|
-
- **iv
|
|
346
|
-
|
|
347
|
-
- **plaintext** -- any length (including zero). PKCS7 padding is applied
|
|
348
|
-
automatically.
|
|
335
|
+
- **key**: 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
|
|
336
|
+
- **iv**: exactly 16 bytes. Must be random and unique per (key, message) pair. Throws `RangeError` if the length is not 16.
|
|
337
|
+
- **plaintext**: any length including zero. PKCS7 padding is applied automatically.
|
|
349
338
|
|
|
350
339
|
Returns the ciphertext as a new `Uint8Array`.
|
|
351
340
|
|
|
@@ -355,21 +344,14 @@ Returns the ciphertext as a new `Uint8Array`.
|
|
|
355
344
|
|
|
356
345
|
Decrypts Serpent CBC ciphertext and strips PKCS7 padding.
|
|
357
346
|
|
|
358
|
-
- **key
|
|
359
|
-
- **iv
|
|
360
|
-
|
|
361
|
-
- **ciphertext** -- must be a non-zero multiple of 16 bytes. Throws `RangeError`
|
|
362
|
-
if the length is zero or not a multiple of 16. Also throws `RangeError` if PKCS7
|
|
363
|
-
padding is invalid (which typically indicates the wrong key, wrong IV, or
|
|
364
|
-
corrupted ciphertext).
|
|
347
|
+
- **key**: 16, 24, or 32 bytes. Throws `RangeError` if the length is invalid.
|
|
348
|
+
- **iv**: exactly 16 bytes. Must match the IV used for encryption. Throws `RangeError` if the length is not 16.
|
|
349
|
+
- **ciphertext**: must be a non-zero multiple of 16 bytes. Throws `RangeError` if the length is zero or not a multiple of 16. Also throws if PKCS7 padding is invalid, which typically indicates the wrong key, wrong IV, or corrupted ciphertext.
|
|
365
350
|
|
|
366
351
|
Returns the decrypted plaintext as a new `Uint8Array`.
|
|
367
352
|
|
|
368
353
|
> [!NOTE]
|
|
369
|
-
> Automatically dispatches to the 4-wide SIMD path (`cbcDecryptChunk_simd`) when
|
|
370
|
-
> the runtime supports WebAssembly SIMD (`hasSIMD()` returns `true`), otherwise
|
|
371
|
-
> falls back to the scalar unrolled path. CBC encryption has no SIMD variant —
|
|
372
|
-
> each ciphertext block depends on the previous one.
|
|
354
|
+
> Automatically dispatches to the 4-wide SIMD path (`cbcDecryptChunk_simd`) when the runtime supports WebAssembly SIMD (`hasSIMD()` returns `true`), otherwise falls back to the scalar unrolled path. CBC encryption has no SIMD variant since each ciphertext block depends on the previous one.
|
|
373
355
|
|
|
374
356
|
---
|
|
375
357
|
|
|
@@ -379,357 +361,40 @@ Wipes all key material and intermediate state from WASM memory.
|
|
|
379
361
|
|
|
380
362
|
---
|
|
381
363
|
|
|
382
|
-
### SerpentStream
|
|
383
|
-
|
|
384
|
-
Chunked authenticated encryption for large payloads. Each chunk is independently
|
|
385
|
-
encrypted with Serpent-CTR and authenticated with HMAC-SHA256 using per-chunk
|
|
386
|
-
keys derived via HKDF-SHA256. Position binding and truncation detection are
|
|
387
|
-
enforced at the key-derivation layer.
|
|
388
|
-
|
|
389
|
-
Use `SerpentStream` when the payload is large or when holding the entire
|
|
390
|
-
plaintext in memory is undesirable. For small/medium payloads where a single
|
|
391
|
-
`encrypt()`/`decrypt()` call is sufficient, use `SerpentSeal` instead.
|
|
392
|
-
|
|
393
|
-
> [!NOTE]
|
|
394
|
-
> `SerpentStream` takes a 32-byte key (HKDF handles expansion internally).
|
|
395
|
-
> This differs from `SerpentSeal`, which takes 64 bytes.
|
|
396
|
-
|
|
397
|
-
```typescript
|
|
398
|
-
class SerpentStream {
|
|
399
|
-
constructor()
|
|
400
|
-
seal(key: Uint8Array, plaintext: Uint8Array, chunkSize?: number): Uint8Array
|
|
401
|
-
open(key: Uint8Array, ciphertext: Uint8Array): Uint8Array
|
|
402
|
-
dispose(): void
|
|
403
|
-
}
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
#### `constructor()`
|
|
407
|
-
|
|
408
|
-
Creates a new SerpentStream instance. Throws if `init(['serpent', 'sha2'])` has
|
|
409
|
-
not been called.
|
|
410
|
-
|
|
411
|
-
---
|
|
412
|
-
|
|
413
|
-
#### `seal(key: Uint8Array, plaintext: Uint8Array, chunkSize?: number): Uint8Array`
|
|
414
|
-
|
|
415
|
-
Encrypts plaintext into a chunked authenticated wire format.
|
|
416
|
-
|
|
417
|
-
- **key** -- exactly 32 bytes. Throws `RangeError` if not.
|
|
418
|
-
- **plaintext** -- any length (including zero).
|
|
419
|
-
- **chunkSize** -- optional, default 64KB. Valid range: 1KB to 64KB. Throws
|
|
420
|
-
`RangeError` if outside range.
|
|
421
|
-
|
|
422
|
-
A fresh random stream nonce is generated internally for each call. Two seals of
|
|
423
|
-
the same plaintext with the same key produce different output.
|
|
424
|
-
|
|
425
|
-
Wire format: `stream_nonce (16) || chunk_size (4, u32_be) || chunk_count (8, u64_be) || chunk_0 || ... || chunk_N-1`
|
|
426
|
-
|
|
427
|
-
Each chunk on the wire: `ciphertext || hmac_tag (32 bytes)`.
|
|
428
|
-
|
|
429
|
-
---
|
|
430
|
-
|
|
431
|
-
#### `open(key: Uint8Array, ciphertext: Uint8Array): Uint8Array`
|
|
432
|
-
|
|
433
|
-
Verifies authentication and decrypts the chunked wire format. Each chunk's MAC
|
|
434
|
-
is verified before decryption (Encrypt-then-MAC). If any chunk fails
|
|
435
|
-
authentication, `open()` throws immediately and never returns partial plaintext.
|
|
436
|
-
|
|
437
|
-
- **key** -- exactly 32 bytes. Must be the same key used for `seal()`.
|
|
438
|
-
- **ciphertext** -- the wire format from `seal()`. Throws `RangeError` if too
|
|
439
|
-
short.
|
|
440
|
-
|
|
441
|
-
---
|
|
442
|
-
|
|
443
|
-
#### `dispose(): void`
|
|
444
|
-
|
|
445
|
-
Wipes all key material and intermediate state from WASM memory. Delegates to
|
|
446
|
-
internal SerpentCtr, HMAC_SHA256, and HKDF_SHA256 instances.
|
|
447
|
-
|
|
448
|
-
**Security properties:**
|
|
449
|
-
|
|
450
|
-
- **Per-chunk EtM** -- HMAC-SHA256 over ciphertext, verified before decrypt.
|
|
451
|
-
- **Position binding** -- chunk index encoded in HKDF `info`. Reordering chunks
|
|
452
|
-
produces wrong keys; MAC fails.
|
|
453
|
-
- **Truncation detection** -- final chunk derives different keys than any
|
|
454
|
-
intermediate chunk at the same index.
|
|
455
|
-
- **Implicit header integrity** -- HKDF `info` embeds the full header. Tampering
|
|
456
|
-
with any header field invalidates every chunk's MAC.
|
|
457
|
-
- **Domain separation** -- `"serpent-stream-v1"` prefix prevents key confusion
|
|
458
|
-
with SerpentSeal or other constructions.
|
|
459
|
-
|
|
460
|
-
> [!IMPORTANT]
|
|
461
|
-
> This is a bespoke construction (no external RFC). The compositional security
|
|
462
|
-
> argument rests on HKDF (RFC 5869), HMAC-EtM, and Serpent-CTR. See
|
|
463
|
-
> [sha2.md](./sha2.md) for HKDF details.
|
|
464
|
-
|
|
465
|
-
> [!NOTE]
|
|
466
|
-
> `sealChunk` and `openChunk` are exported from the serpent submodule for
|
|
467
|
-
> internal use by the pool worker. They are not public API -- callers should use
|
|
468
|
-
> `SerpentStream` or `SerpentStreamPool`.
|
|
469
|
-
|
|
470
|
-
---
|
|
471
|
-
|
|
472
|
-
### SerpentStreamPool
|
|
473
|
-
|
|
474
|
-
Parallel worker pool for `SerpentStream`. Same wire format, same security
|
|
475
|
-
properties, faster on multi-core hardware for large payloads. Each worker owns
|
|
476
|
-
its own `serpent.wasm` and `sha2.wasm` instances with isolated linear memory.
|
|
477
|
-
|
|
478
|
-
`SerpentStream.seal()` and `SerpentStreamPool.seal()` produce compatible wire
|
|
479
|
-
formats -- either can decrypt the other's output.
|
|
480
|
-
|
|
481
|
-
```typescript
|
|
482
|
-
class SerpentStreamPool {
|
|
483
|
-
static async create(opts?: StreamPoolOpts): Promise<SerpentStreamPool>
|
|
484
|
-
seal(key: Uint8Array, plaintext: Uint8Array, chunkSize?: number): Promise<Uint8Array>
|
|
485
|
-
open(key: Uint8Array, ciphertext: Uint8Array): Promise<Uint8Array>
|
|
486
|
-
dispose(): void
|
|
487
|
-
get size(): number
|
|
488
|
-
get queueDepth(): number
|
|
489
|
-
}
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
#### `static async create(opts?: StreamPoolOpts): Promise<SerpentStreamPool>`
|
|
493
|
-
|
|
494
|
-
Creates a new pool. Requires `init(['serpent', 'sha2'])` to have been called.
|
|
495
|
-
Compiles both WASM modules once and distributes them to all workers.
|
|
496
|
-
|
|
497
|
-
- **opts.workers** -- number of workers to spawn. Default:
|
|
498
|
-
`navigator.hardwareConcurrency ?? 4`.
|
|
499
|
-
|
|
500
|
-
Uses a static factory pattern because worker initialization is async (WASM
|
|
501
|
-
compilation and instantiation happen per worker).
|
|
502
|
-
|
|
503
|
-
---
|
|
504
|
-
|
|
505
|
-
#### `seal(key, plaintext, chunkSize?)`
|
|
506
|
-
|
|
507
|
-
Same parameters as `SerpentStream.seal()`, but returns a `Promise`. Key
|
|
508
|
-
derivation happens on the main thread; chunk encryption is parallelised across
|
|
509
|
-
workers.
|
|
510
|
-
|
|
511
|
-
---
|
|
512
|
-
|
|
513
|
-
#### `open(key, ciphertext)`
|
|
514
|
-
|
|
515
|
-
Same parameters as `SerpentStream.open()`, but returns a `Promise`. If any chunk
|
|
516
|
-
fails authentication, the promise rejects immediately -- no partial plaintext is
|
|
517
|
-
returned.
|
|
518
|
-
|
|
519
|
-
---
|
|
520
|
-
|
|
521
|
-
#### `dispose()`
|
|
522
|
-
|
|
523
|
-
Terminates all workers. Rejects all pending and queued jobs. Must be called to
|
|
524
|
-
release worker resources when the pool is no longer needed.
|
|
525
|
-
|
|
526
|
-
---
|
|
527
|
-
|
|
528
|
-
### SerpentStreamSealer / SerpentStreamOpener
|
|
529
|
-
|
|
530
|
-
Incremental streaming AEAD — seal and open one chunk at a time without holding
|
|
531
|
-
the full message in memory. Unlike `SerpentStream` (which is one-shot),
|
|
532
|
-
`SerpentStreamSealer` produces chunks as data arrives and `SerpentStreamOpener`
|
|
533
|
-
authenticates and decrypts them individually.
|
|
534
|
-
|
|
535
|
-
**Wire format (v1.4.0+):**
|
|
536
|
-
```
|
|
537
|
-
header: nonce (16) || chunkSize_u32be (4) = 20 bytes
|
|
538
|
-
chunk: isLast (1) || IV (16) || CBC_ciphertext (PKCS7-padded) || HMAC-SHA256 (32)
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
> [!WARNING]
|
|
542
|
-
> **Breaking change from v1.3.x:** Each chunk now prepends an explicit `isLast`
|
|
543
|
-
> flag byte, and AAD is folded into the HMAC input with a 4-byte length prefix.
|
|
544
|
-
> Ciphertexts produced by v1.3.x are not compatible with v1.4.0 openers, and
|
|
545
|
-
> vice versa, even with empty AAD.
|
|
546
|
-
|
|
547
|
-
Per-chunk keys are derived via HKDF-SHA256 from the stream key and a `chunkInfo`
|
|
548
|
-
blob binding the stream nonce, chunk size, chunk index, and `isLast` flag. Each
|
|
549
|
-
chunk is independently authenticated and position-bound — reordering, truncation,
|
|
550
|
-
and cross-stream splicing are all detected.
|
|
551
|
-
|
|
552
|
-
> [!NOTE]
|
|
553
|
-
> `SerpentStreamSealer` requires a 64-byte key (same as `SerpentSeal`). HKDF
|
|
554
|
-
> derives a fresh `encKey` + `macKey` pair for every chunk.
|
|
555
|
-
|
|
556
|
-
> [!IMPORTANT]
|
|
557
|
-
> The sealer produces a 20-byte header that **must** be transmitted to the opener
|
|
558
|
-
> before any chunks. The opener is initialized with this header.
|
|
559
|
-
|
|
560
|
-
```typescript
|
|
561
|
-
class SerpentStreamSealer {
|
|
562
|
-
constructor(key: Uint8Array, chunkSize?: number, opts?: {
|
|
563
|
-
framed?: boolean;
|
|
564
|
-
aad?: Uint8Array; // authenticated per-chunk, not encrypted
|
|
565
|
-
})
|
|
566
|
-
header(): Uint8Array // call once before seal() — returns 20 bytes
|
|
567
|
-
seal(plaintext: Uint8Array): Uint8Array // exactly chunkSize bytes
|
|
568
|
-
final(plaintext: Uint8Array): Uint8Array // <= chunkSize bytes; wipes on return
|
|
569
|
-
dispose(): void // abort mid-stream; wipes without final chunk
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
class SerpentStreamOpener {
|
|
573
|
-
constructor(key: Uint8Array, header: Uint8Array, opts?: {
|
|
574
|
-
framed?: boolean;
|
|
575
|
-
aad?: Uint8Array; // must match value used by sealer
|
|
576
|
-
})
|
|
577
|
-
open(chunk: Uint8Array): Uint8Array // throws on auth failure or post-final
|
|
578
|
-
feed(bytes: Uint8Array): Uint8Array[] // framed mode only — accumulates and parses frames
|
|
579
|
-
dispose(): void
|
|
580
|
-
}
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
#### Sealer state machine
|
|
584
|
-
|
|
585
|
-
| State | Valid calls |
|
|
586
|
-
|---|---|
|
|
587
|
-
| `fresh` | `header()`, `dispose()` |
|
|
588
|
-
| `sealing` | `seal()`, `final()`, `dispose()` |
|
|
589
|
-
| `dead` | `dispose()` (no-op) |
|
|
590
|
-
|
|
591
|
-
`header()` transitions `fresh → sealing`. `final()` seals the last chunk, wipes
|
|
592
|
-
all key material, and transitions to `dead`. `dispose()` wipes and transitions to
|
|
593
|
-
`dead` from any state — use it to abort a stream before `final()` is called.
|
|
594
|
-
|
|
595
|
-
Calling `header()` twice, `seal()` before `header()`, or any method after `final()`
|
|
596
|
-
all throw immediately.
|
|
597
|
-
|
|
598
|
-
---
|
|
599
|
-
|
|
600
|
-
#### Opener state machine
|
|
601
|
-
|
|
602
|
-
The opener is ready as soon as it is constructed. It calls `open()` for each
|
|
603
|
-
chunk in order. Once a chunk with `isLast` set passes authentication, the opener
|
|
604
|
-
wipes its key material and transitions to `dead`. Subsequent `open()` calls throw.
|
|
605
|
-
|
|
606
|
-
`dispose()` wipes and marks the instance dead from any state.
|
|
607
|
-
|
|
608
|
-
---
|
|
609
|
-
|
|
610
|
-
#### `constructor(key, chunkSize?, opts?)`
|
|
611
|
-
|
|
612
|
-
- **key** — 64-byte key. Throws `RangeError` if wrong length.
|
|
613
|
-
- **chunkSize** — bytes per chunk. Must be 1024–65536. Default: 65536. Throws
|
|
614
|
-
`RangeError` if out of range.
|
|
615
|
-
|
|
616
|
-
##### Options (`opts`)
|
|
617
|
-
|
|
618
|
-
| Option | Type | Default | Description |
|
|
619
|
-
|--------|------|---------|-------------|
|
|
620
|
-
| `framed` | `boolean` | `false` | Prepend `u32be(sealedLen)` to each `seal()`/`final()` output. Use for flat byte streams (files, pipes, TCP). Omit when the transport already frames messages (WebSocket, IPC). |
|
|
621
|
-
| `aad` | `Uint8Array` | `undefined` | Associated data — authenticated but not encrypted. Bound into each chunk's HMAC-SHA256 tag with a 4-byte length prefix for domain separation. If omitted, equivalent to empty. |
|
|
622
|
-
|
|
623
|
-
---
|
|
624
|
-
|
|
625
|
-
#### `header()`
|
|
626
|
-
|
|
627
|
-
Returns the 20-byte stream header (`nonce || u32be(chunkSize)`). Must be called
|
|
628
|
-
once before the first `seal()`. Throws if called a second time or after `final()`.
|
|
629
|
-
|
|
630
|
-
---
|
|
631
|
-
|
|
632
|
-
#### `seal(plaintext)`
|
|
633
|
-
|
|
634
|
-
Seals one chunk. **Plaintext must be exactly `chunkSize` bytes.** Returns
|
|
635
|
-
`IV (16) || ciphertext || HMAC (32)`. Throws `RangeError` if wrong size. Throws
|
|
636
|
-
if called before `header()` or after `final()`.
|
|
637
|
-
|
|
638
|
-
---
|
|
639
|
-
|
|
640
|
-
#### `final(plaintext)`
|
|
641
|
-
|
|
642
|
-
Seals the last chunk. Plaintext may be 0–`chunkSize` bytes (partial chunk is
|
|
643
|
-
valid). After producing output, wipes all key material and marks the sealer dead.
|
|
644
|
-
Throws `RangeError` if plaintext exceeds `chunkSize`.
|
|
645
|
-
|
|
646
|
-
---
|
|
647
|
-
|
|
648
|
-
#### `dispose()` (sealer)
|
|
649
|
-
|
|
650
|
-
Aborts the stream. Wipes key material without producing a final chunk. The opener
|
|
651
|
-
will see an incomplete stream and throw when it detects a missing final chunk.
|
|
652
|
-
Safe to call after `final()` — no-op if already dead.
|
|
653
|
-
|
|
654
|
-
---
|
|
655
|
-
|
|
656
|
-
#### `constructor(key, header, opts?)` (opener)
|
|
657
|
-
|
|
658
|
-
- **key** — 64-byte key. Throws `RangeError` if wrong length.
|
|
659
|
-
- **header** — 20-byte stream header from `sealer.header()`. Throws `RangeError`
|
|
660
|
-
if wrong length.
|
|
661
|
-
|
|
662
|
-
##### Options (`opts`)
|
|
663
|
-
|
|
664
|
-
| Option | Type | Default | Description |
|
|
665
|
-
|--------|------|---------|-------------|
|
|
666
|
-
| `framed` | `boolean` | `false` | Enable byte-accumulation mode. Parses `u32be` length prefixes and dispatches complete frames to `open()` internally. Required to use `feed()`. |
|
|
667
|
-
| `aad` | `Uint8Array` | `undefined` | Associated data — must match the value used by the sealer. Mismatch causes authentication failure on `open()`. |
|
|
668
|
-
|
|
669
|
-
---
|
|
670
|
-
|
|
671
|
-
#### `open(chunk)`
|
|
672
|
-
|
|
673
|
-
Authenticates and decrypts one chunk. Throws `Error` on authentication failure.
|
|
674
|
-
Throws `Error` if called after the final chunk has already been opened. Returns
|
|
675
|
-
plaintext bytes (PKCS7 padding stripped).
|
|
676
|
-
|
|
677
|
-
---
|
|
678
|
-
|
|
679
|
-
#### `feed(bytes: Uint8Array): Uint8Array[]`
|
|
680
|
-
|
|
681
|
-
Only callable when constructed with `{ framed: true }`. Accumulates incoming bytes,
|
|
682
|
-
parses `u32be` length prefixes, dispatches complete frames to `open()` internally.
|
|
683
|
-
Returns an array of decrypted chunks — zero, one, or more per call depending on how
|
|
684
|
-
many complete frames were buffered. Throws if called on an unframed opener.
|
|
685
|
-
|
|
686
|
-
---
|
|
687
|
-
|
|
688
|
-
#### `dispose()` (opener)
|
|
689
|
-
|
|
690
|
-
Wipes key material. Safe to call at any point — use to abort opening a stream
|
|
691
|
-
early.
|
|
692
|
-
|
|
693
|
-
---
|
|
694
|
-
|
|
695
364
|
## Usage Examples
|
|
696
365
|
|
|
697
|
-
### Example 1:
|
|
366
|
+
### Example 1: Seal with SerpentCipher (authenticated encryption)
|
|
698
367
|
|
|
699
368
|
```typescript
|
|
700
|
-
import { init,
|
|
701
|
-
|
|
702
|
-
|
|
369
|
+
import { init, Seal, SerpentCipher } from 'leviathan-crypto'
|
|
370
|
+
import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
|
|
371
|
+
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
703
372
|
|
|
704
|
-
|
|
705
|
-
const key = randomBytes(64);
|
|
373
|
+
await init({ serpent: serpentWasm, sha2: sha2Wasm })
|
|
706
374
|
|
|
707
|
-
const
|
|
375
|
+
const key = SerpentCipher.keygen()
|
|
376
|
+
const plaintext = new TextEncoder().encode('Authenticated secret message.')
|
|
377
|
+
const blob = Seal.encrypt(SerpentCipher, key, plaintext)
|
|
378
|
+
const decrypted = Seal.decrypt(SerpentCipher, key, blob)
|
|
708
379
|
|
|
709
|
-
|
|
710
|
-
const ciphertext = seal.encrypt(key, plaintext);
|
|
711
|
-
const decrypted = seal.decrypt(key, ciphertext);
|
|
712
|
-
|
|
713
|
-
console.log(new TextDecoder().decode(decrypted));
|
|
380
|
+
console.log(new TextDecoder().decode(decrypted))
|
|
714
381
|
// "Authenticated secret message."
|
|
715
|
-
|
|
716
|
-
seal.dispose();
|
|
717
382
|
```
|
|
718
383
|
|
|
719
384
|
### Example 2: CTR mode (advanced)
|
|
720
385
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
that is the same length as the plaintext -- no padding overhead.
|
|
386
|
+
For authenticated encryption, use `Seal` with `SerpentCipher`. Use `SerpentCtr` to
|
|
387
|
+
encrypt data of any length. CTR mode produces ciphertext the same length as the
|
|
388
|
+
plaintext with no padding overhead.
|
|
725
389
|
|
|
726
390
|
```typescript
|
|
727
391
|
import { init, SerpentCtr, randomBytes } from 'leviathan-crypto';
|
|
392
|
+
import { serpentWasm } from 'leviathan-crypto/serpent/embedded';
|
|
728
393
|
|
|
729
|
-
await init(
|
|
394
|
+
await init({ serpent: serpentWasm });
|
|
730
395
|
|
|
731
396
|
const key = randomBytes(32); // 256-bit key
|
|
732
|
-
const nonce = randomBytes(16); // 16-byte nonce
|
|
397
|
+
const nonce = randomBytes(16); // 16-byte nonce, NEVER reuse with the same key
|
|
733
398
|
|
|
734
399
|
const ctr = new SerpentCtr({ dangerUnauthenticated: true });
|
|
735
400
|
|
|
@@ -752,21 +417,21 @@ ctr.dispose();
|
|
|
752
417
|
|
|
753
418
|
> [!IMPORTANT]
|
|
754
419
|
> CTR mode is unauthenticated. An attacker can tamper with the
|
|
755
|
-
> ciphertext without detection. Use `
|
|
420
|
+
> ciphertext without detection. Use `Seal` with `SerpentCipher` for authenticated encryption.
|
|
756
421
|
|
|
757
422
|
### Example 3: CBC mode (advanced)
|
|
758
423
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
Use `SerpentCbc` for message-level encryption with automatic PKCS7 padding.
|
|
424
|
+
For authenticated encryption, use `Seal` with `SerpentCipher`. Use `SerpentCbc` for
|
|
425
|
+
message-level encryption with automatic PKCS7 padding.
|
|
762
426
|
|
|
763
427
|
```typescript
|
|
764
428
|
import { init, SerpentCbc, randomBytes } from 'leviathan-crypto';
|
|
429
|
+
import { serpentWasm } from 'leviathan-crypto/serpent/embedded';
|
|
765
430
|
|
|
766
|
-
await init(
|
|
431
|
+
await init({ serpent: serpentWasm });
|
|
767
432
|
|
|
768
433
|
const key = randomBytes(32); // 256-bit key
|
|
769
|
-
const iv = randomBytes(16); // Random IV
|
|
434
|
+
const iv = randomBytes(16); // Random IV, must be unique per message
|
|
770
435
|
|
|
771
436
|
const cbc = new SerpentCbc({ dangerUnauthenticated: true });
|
|
772
437
|
|
|
@@ -783,109 +448,18 @@ cbc.dispose();
|
|
|
783
448
|
```
|
|
784
449
|
|
|
785
450
|
> [!IMPORTANT]
|
|
786
|
-
> CBC mode is unauthenticated. Use `
|
|
787
|
-
|
|
788
|
-
### Example 4: SerpentStream (chunked authenticated encryption)
|
|
789
|
-
|
|
790
|
-
Use `SerpentStream` for large payloads where holding the entire plaintext in
|
|
791
|
-
memory is undesirable.
|
|
792
|
-
|
|
793
|
-
```typescript
|
|
794
|
-
import { init, SerpentStream, randomBytes } from 'leviathan-crypto';
|
|
795
|
-
|
|
796
|
-
await init(['serpent', 'sha2']);
|
|
451
|
+
> CBC mode is unauthenticated. Use `Seal` with `SerpentCipher` for authenticated encryption.
|
|
797
452
|
|
|
798
|
-
|
|
453
|
+
### Example 4: Raw block operations (low-level)
|
|
799
454
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
const plaintext = new Uint8Array(1024 * 1024); // 1 MB
|
|
803
|
-
crypto.getRandomValues(plaintext);
|
|
804
|
-
|
|
805
|
-
const ciphertext = stream.seal(key, plaintext); // default 64KB chunks
|
|
806
|
-
const decrypted = stream.open(key, ciphertext);
|
|
807
|
-
|
|
808
|
-
// decrypted is byte-identical to plaintext
|
|
809
|
-
|
|
810
|
-
stream.dispose();
|
|
811
|
-
```
|
|
812
|
-
|
|
813
|
-
### Example 5: SerpentStreamPool (parallel chunked encryption)
|
|
814
|
-
|
|
815
|
-
Use `SerpentStreamPool` for maximum throughput on multi-core hardware.
|
|
816
|
-
|
|
817
|
-
```typescript
|
|
818
|
-
import { init, SerpentStreamPool, randomBytes } from 'leviathan-crypto';
|
|
819
|
-
|
|
820
|
-
await init(['serpent', 'sha2']);
|
|
821
|
-
|
|
822
|
-
const pool = await SerpentStreamPool.create({ workers: 4 });
|
|
823
|
-
|
|
824
|
-
const key = randomBytes(32);
|
|
825
|
-
const plaintext = new Uint8Array(10 * 1024 * 1024); // 10 MB
|
|
826
|
-
|
|
827
|
-
const ciphertext = await pool.seal(key, plaintext);
|
|
828
|
-
const decrypted = await pool.open(key, ciphertext);
|
|
829
|
-
|
|
830
|
-
// decrypted is byte-identical to plaintext
|
|
831
|
-
|
|
832
|
-
pool.dispose(); // terminates workers
|
|
833
|
-
```
|
|
834
|
-
|
|
835
|
-
### Example 6: SerpentStreamSealer / SerpentStreamOpener (incremental streaming)
|
|
836
|
-
|
|
837
|
-
Use `SerpentStreamSealer` when data arrives in chunks and you cannot buffer the
|
|
838
|
-
entire plaintext before encrypting — network streams, file processors, live feeds.
|
|
839
|
-
|
|
840
|
-
```typescript
|
|
841
|
-
import { init, SerpentStreamSealer, SerpentStreamOpener, randomBytes } from 'leviathan-crypto';
|
|
842
|
-
|
|
843
|
-
await init(['serpent', 'sha2']);
|
|
844
|
-
|
|
845
|
-
const key = randomBytes(64); // 64-byte key
|
|
846
|
-
const chunkSize = 65536; // 64 KB chunks
|
|
847
|
-
|
|
848
|
-
// ── Seal side ────────────────────────────────────────────────────────────────
|
|
849
|
-
|
|
850
|
-
const sealer = new SerpentStreamSealer(key, chunkSize);
|
|
851
|
-
const header = sealer.header(); // transmit this to the opener first
|
|
852
|
-
|
|
853
|
-
// seal() as data arrives — each chunk must be exactly chunkSize bytes
|
|
854
|
-
const chunk0 = sealer.seal(plaintext0);
|
|
855
|
-
const chunk1 = sealer.seal(plaintext1);
|
|
856
|
-
|
|
857
|
-
// final() for the last chunk — may be shorter than chunkSize
|
|
858
|
-
const lastChunk = sealer.final(lastPlaintext);
|
|
859
|
-
// sealer is now dead — key material wiped
|
|
860
|
-
|
|
861
|
-
// ── Open side ────────────────────────────────────────────────────────────────
|
|
862
|
-
|
|
863
|
-
const opener = new SerpentStreamOpener(key, header);
|
|
864
|
-
|
|
865
|
-
const pt0 = opener.open(chunk0);
|
|
866
|
-
const pt1 = opener.open(chunk1);
|
|
867
|
-
const ptN = opener.open(lastChunk); // opener detects isLast, wipes on return
|
|
868
|
-
// opener is now dead
|
|
869
|
-
|
|
870
|
-
// Truncation and reordering are detected — open() throws on auth failure
|
|
871
|
-
```
|
|
872
|
-
|
|
873
|
-
To abort a stream mid-way (e.g. on connection drop):
|
|
874
|
-
|
|
875
|
-
```typescript
|
|
876
|
-
sealer.dispose(); // wipes key material without producing a final chunk
|
|
877
|
-
// opener will throw when it receives no more chunks
|
|
878
|
-
```
|
|
879
|
-
|
|
880
|
-
### Example 7: Raw block operations (low-level)
|
|
881
|
-
|
|
882
|
-
Use the `Serpent` class for single 16-byte block operations. This is the lowest
|
|
883
|
-
level API, most users should use `SerpentSeal` instead.
|
|
455
|
+
Use the `Serpent` class for single 16-byte block operations. This is the lowest-level
|
|
456
|
+
API; use `Seal` with `SerpentCipher` for most purposes.
|
|
884
457
|
|
|
885
458
|
```typescript
|
|
886
459
|
import { init, Serpent } from 'leviathan-crypto';
|
|
460
|
+
import { serpentWasm } from 'leviathan-crypto/serpent/embedded';
|
|
887
461
|
|
|
888
|
-
await init(
|
|
462
|
+
await init({ serpent: serpentWasm });
|
|
889
463
|
|
|
890
464
|
const cipher = new Serpent();
|
|
891
465
|
|
|
@@ -915,13 +489,9 @@ cipher.dispose();
|
|
|
915
489
|
|
|
916
490
|
| Condition | Error type | Message |
|
|
917
491
|
|-----------|-----------|---------|
|
|
918
|
-
| `
|
|
919
|
-
| `
|
|
920
|
-
| `
|
|
921
|
-
| `SerpentSeal` authentication failed | `Error` | `SerpentSeal: authentication failed` |
|
|
922
|
-
| `init(['serpent'])` not called before constructing `Serpent` | `Error` | `leviathan-crypto: call init(['serpent']) before using this class` |
|
|
923
|
-
| `SerpentCbc` constructed without `{ dangerUnauthenticated: true }` | `Error` | `leviathan-crypto: SerpentCbc is unauthenticated — use SerpentSeal instead. To use SerpentCbc directly, pass { dangerUnauthenticated: true }.` |
|
|
924
|
-
| `SerpentCtr` constructed without `{ dangerUnauthenticated: true }` | `Error` | `leviathan-crypto: SerpentCtr is unauthenticated — use SerpentSeal instead. To use SerpentCtr directly, pass { dangerUnauthenticated: true }.` |
|
|
492
|
+
| `init({ serpent: ... })` not called before constructing `Serpent` | `Error` | `leviathan-crypto: call init({ serpent: ... }) before using this class` |
|
|
493
|
+
| `SerpentCbc` constructed without `{ dangerUnauthenticated: true }` | `Error` | `leviathan-crypto: SerpentCbc is unauthenticated — use Seal with SerpentCipher instead. To use SerpentCbc directly, pass { dangerUnauthenticated: true }.` |
|
|
494
|
+
| `SerpentCtr` constructed without `{ dangerUnauthenticated: true }` | `Error` | `leviathan-crypto: SerpentCtr is unauthenticated — use Seal with SerpentCipher instead. To use SerpentCtr directly, pass { dangerUnauthenticated: true }.` |
|
|
925
495
|
| Key is not 16, 24, or 32 bytes (`Serpent.loadKey`) | `RangeError` | `key must be 16, 24, or 32 bytes (got N)` |
|
|
926
496
|
| Key is not 16, 24, or 32 bytes (`SerpentCbc`) | `RangeError` | `Serpent key must be 16, 24, or 32 bytes (got N)` |
|
|
927
497
|
| Key is not 16, 24, or 32 bytes (`SerpentCtr`) | `RangeError` | `key must be 16, 24, or 32 bytes` |
|
|
@@ -931,38 +501,19 @@ cipher.dispose();
|
|
|
931
501
|
| IV is not 16 bytes (`SerpentCbc`) | `RangeError` | `CBC IV must be 16 bytes (got N)` |
|
|
932
502
|
| Ciphertext length is zero or not a multiple of 16 (`SerpentCbc.decrypt`) | `RangeError` | `ciphertext length must be a non-zero multiple of 16` |
|
|
933
503
|
| Invalid PKCS7 padding on decrypt (`SerpentCbc.decrypt`) | `RangeError` | `invalid PKCS7 padding` |
|
|
934
|
-
| `SerpentStream` constructed before `init(['serpent', 'sha2'])` | `Error` | `leviathan-crypto: call init(['serpent', 'sha2']) before using SerpentStream` |
|
|
935
|
-
| `SerpentStream` key is not 32 bytes | `RangeError` | `SerpentStream key must be 32 bytes (got N)` |
|
|
936
|
-
| `SerpentStream` chunkSize out of range | `RangeError` | `SerpentStream chunkSize must be 1024..65536 (got N)` |
|
|
937
|
-
| `SerpentStream` ciphertext too short | `RangeError` | `SerpentStream: ciphertext too short` |
|
|
938
|
-
| `SerpentStream` authentication failed | `Error` | `SerpentStream: authentication failed` |
|
|
939
|
-
| `SerpentStreamPool.create()` before `init(['serpent', 'sha2'])` | `Error` | `leviathan-crypto: call init(['serpent', 'sha2']) before using SerpentStreamPool` |
|
|
940
|
-
| `SerpentStreamPool` methods after `dispose()` | `Error` | `leviathan-crypto: pool is disposed` |
|
|
941
|
-
| `SerpentStreamSealer` constructed before `init(['serpent', 'sha2'])` | `Error` | `leviathan-crypto: call init(['serpent']) before using SerpentStreamSealer` |
|
|
942
|
-
| `SerpentStreamSealer` key is not 64 bytes | `RangeError` | `SerpentStreamSealer key must be 64 bytes (got N)` |
|
|
943
|
-
| `SerpentStreamSealer` chunkSize out of range | `RangeError` | `SerpentStreamSealer chunkSize must be 1024..65536 (got N)` |
|
|
944
|
-
| `SerpentStreamSealer.header()` called twice | `Error` | `SerpentStreamSealer: header() already called` |
|
|
945
|
-
| `SerpentStreamSealer.seal()` before `header()` | `Error` | `SerpentStreamSealer: call header() first` |
|
|
946
|
-
| `SerpentStreamSealer.seal()` or `final()` after `final()` or `dispose()` | `Error` | `SerpentStreamSealer: stream is closed` |
|
|
947
|
-
| `SerpentStreamSealer.seal()` wrong plaintext size | `RangeError` | `SerpentStreamSealer: seal() requires exactly N bytes (got M)` |
|
|
948
|
-
| `SerpentStreamSealer.final()` plaintext exceeds chunkSize | `RangeError` | `SerpentStreamSealer: final() plaintext exceeds chunkSize (got N)` |
|
|
949
|
-
| `SerpentStreamOpener` constructed before `init(['serpent', 'sha2'])` | `Error` | `leviathan-crypto: call init(['serpent']) before using SerpentStreamOpener` |
|
|
950
|
-
| `SerpentStreamOpener` key is not 64 bytes | `RangeError` | `SerpentStreamOpener key must be 64 bytes (got N)` |
|
|
951
|
-
| `SerpentStreamOpener` header is not 20 bytes | `RangeError` | `SerpentStreamOpener header must be 20 bytes (got N)` |
|
|
952
|
-
| `SerpentStreamOpener.open()` authentication failed | `Error` | `SerpentStreamOpener: authentication failed` |
|
|
953
|
-
| `SerpentStreamOpener.open()` after stream closed | `Error` | `SerpentStreamOpener: stream is closed` |
|
|
954
504
|
|
|
955
505
|
---
|
|
956
506
|
|
|
957
507
|
> ## Cross-References
|
|
958
508
|
>
|
|
959
509
|
> - [index](./README.md) — Project Documentation index
|
|
510
|
+
> - [lexicon](./lexicon.md) — Glossary of cryptographic terms
|
|
960
511
|
> - [architecture](./architecture.md) — architecture overview, module relationships, buffer layouts, and build pipeline
|
|
961
512
|
> - [asm_serpent](./asm_serpent.md) — WASM implementation details and buffer layout
|
|
962
513
|
> - [serpent_reference](./serpent_reference.md) — algorithm specification, S-boxes, linear transform, and known attacks
|
|
963
514
|
> - [serpent_audit](./serpent_audit.md) — security audit findings (correctness, side-channel analysis)
|
|
964
|
-
> - [
|
|
965
|
-
> - [
|
|
515
|
+
> - [authenticated encryption](./aead.md) — `Seal`, `SealStream`, `OpenStream`: use `SerpentCipher` as the suite argument
|
|
516
|
+
> - [chacha20](./chacha20.md) — `XChaCha20Cipher`: alternative `CipherSuite` for `Seal` and streaming
|
|
517
|
+
> - [sha2](./sha2.md) — HMAC-SHA256 and HKDF used internally by `SerpentCipher`
|
|
966
518
|
> - [types](./types.md) — `Blockcipher`, `Streamcipher`, and `AEAD` interfaces implemented by Serpent classes
|
|
967
519
|
> - [utils](./utils.md) — `constantTimeEqual`, `wipe`, `randomBytes` used by Serpent wrappers
|
|
968
|
-
|