leviathan-crypto 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +86 -443
- package/README.md +198 -65
- package/dist/aes/aes-cbc.d.ts +40 -0
- package/dist/aes/aes-cbc.js +158 -0
- package/dist/aes/aes-ctr.d.ts +50 -0
- package/dist/aes/aes-ctr.js +141 -0
- package/dist/aes/aes-gcm-siv.d.ts +67 -0
- package/dist/aes/aes-gcm-siv.js +217 -0
- package/dist/aes/aes-gcm.d.ts +61 -0
- package/dist/aes/aes-gcm.js +226 -0
- package/dist/aes/cipher-suite.d.ts +21 -0
- package/dist/aes/cipher-suite.js +179 -0
- package/dist/aes/embedded.d.ts +1 -0
- package/dist/aes/embedded.js +26 -0
- package/dist/aes/generator.d.ts +14 -0
- package/dist/aes/generator.js +103 -0
- package/dist/aes/index.d.ts +58 -0
- package/dist/aes/index.js +125 -0
- package/dist/aes/ops.d.ts +60 -0
- package/dist/aes/ops.js +164 -0
- package/dist/aes/pool-worker.d.ts +1 -0
- package/dist/aes/pool-worker.js +92 -0
- package/dist/aes/types.d.ts +1 -0
- package/dist/aes/types.js +23 -0
- package/dist/aes.wasm +0 -0
- package/dist/blake3/embedded.d.ts +1 -0
- package/dist/blake3/embedded.js +26 -0
- package/dist/blake3/index.d.ts +143 -0
- package/dist/blake3/index.js +620 -0
- package/dist/blake3/types.d.ts +102 -0
- package/dist/blake3/types.js +31 -0
- package/dist/blake3/validate.d.ts +29 -0
- package/dist/blake3/validate.js +80 -0
- package/dist/blake3.wasm +0 -0
- package/dist/chacha20/cipher-suite.js +47 -25
- package/dist/chacha20/generator.d.ts +2 -2
- package/dist/chacha20/generator.js +4 -4
- package/dist/chacha20/index.d.ts +16 -15
- package/dist/chacha20/index.js +52 -46
- package/dist/chacha20/ops.d.ts +7 -7
- package/dist/chacha20/ops.js +34 -34
- package/dist/chacha20/pool-worker.js +5 -3
- package/dist/cte-wasm.d.ts +1 -0
- package/dist/cte-wasm.js +3 -0
- package/dist/curve25519.wasm +0 -0
- package/dist/ecdsa/der.d.ts +23 -0
- package/dist/ecdsa/der.js +192 -0
- package/dist/ecdsa/ecprivatekey-der.d.ts +32 -0
- package/dist/ecdsa/ecprivatekey-der.js +230 -0
- package/dist/ecdsa/embedded.d.ts +1 -0
- package/dist/ecdsa/embedded.js +25 -0
- package/dist/ecdsa/index.d.ts +124 -0
- package/dist/ecdsa/index.js +366 -0
- package/dist/ecdsa/types.d.ts +31 -0
- package/dist/ecdsa/types.js +28 -0
- package/dist/ecdsa/validate.d.ts +18 -0
- package/dist/ecdsa/validate.js +92 -0
- package/dist/ed25519/embedded.d.ts +1 -0
- package/dist/ed25519/embedded.js +31 -0
- package/dist/ed25519/index.d.ts +70 -0
- package/dist/ed25519/index.js +308 -0
- package/dist/ed25519/types.d.ts +27 -0
- package/dist/ed25519/types.js +27 -0
- package/dist/ed25519/validate.d.ts +7 -0
- package/dist/ed25519/validate.js +77 -0
- package/dist/embedded/aes-pool-worker.d.ts +1 -0
- package/dist/embedded/aes-pool-worker.js +5 -0
- package/dist/embedded/aes.d.ts +1 -0
- package/dist/embedded/aes.js +3 -0
- package/dist/embedded/blake3.d.ts +1 -0
- package/dist/embedded/blake3.js +3 -0
- package/dist/embedded/chacha20-pool-worker.d.ts +1 -1
- package/dist/embedded/chacha20-pool-worker.js +2 -2
- package/dist/embedded/chacha20.d.ts +1 -1
- package/dist/embedded/chacha20.js +2 -2
- package/dist/embedded/curve25519.d.ts +1 -0
- package/dist/embedded/curve25519.js +3 -0
- package/dist/embedded/mldsa.d.ts +1 -0
- package/dist/embedded/mldsa.js +3 -0
- package/dist/embedded/mlkem.d.ts +1 -0
- package/dist/embedded/mlkem.js +3 -0
- package/dist/embedded/p256.d.ts +1 -0
- package/dist/embedded/p256.js +3 -0
- package/dist/embedded/serpent-pool-worker.d.ts +1 -1
- package/dist/embedded/serpent-pool-worker.js +2 -2
- package/dist/embedded/serpent.d.ts +1 -1
- package/dist/embedded/serpent.js +2 -2
- package/dist/embedded/sha2.d.ts +1 -1
- package/dist/embedded/sha2.js +2 -2
- package/dist/embedded/sha3.d.ts +1 -1
- package/dist/embedded/sha3.js +2 -2
- package/dist/embedded/slhdsa.d.ts +1 -0
- package/dist/embedded/slhdsa.js +3 -0
- package/dist/errors.d.ts +92 -1
- package/dist/errors.js +111 -1
- package/dist/fortuna.d.ts +5 -5
- package/dist/fortuna.js +37 -64
- package/dist/index.d.ts +38 -9
- package/dist/index.js +63 -19
- package/dist/init.d.ts +1 -1
- package/dist/init.js +11 -25
- package/dist/keccak/embedded.js +1 -1
- package/dist/keccak/index.d.ts +2 -0
- package/dist/keccak/index.js +4 -2
- package/dist/loader.d.ts +1 -24
- package/dist/loader.js +13 -16
- package/dist/merkle/blake3-tree.d.ts +35 -0
- package/dist/merkle/blake3-tree.js +187 -0
- package/dist/merkle/checkpoint.d.ts +58 -0
- package/dist/merkle/checkpoint.js +217 -0
- package/dist/merkle/index.d.ts +19 -0
- package/dist/merkle/index.js +37 -0
- package/dist/merkle/merkle-log.d.ts +130 -0
- package/dist/merkle/merkle-log.js +207 -0
- package/dist/merkle/merkle-verifier.d.ts +126 -0
- package/dist/merkle/merkle-verifier.js +296 -0
- package/dist/merkle/proof.d.ts +70 -0
- package/dist/merkle/proof.js +300 -0
- package/dist/merkle/sha256-tree.d.ts +33 -0
- package/dist/merkle/sha256-tree.js +145 -0
- package/dist/merkle/signed-log.d.ts +156 -0
- package/dist/merkle/signed-log.js +356 -0
- package/dist/merkle/signed-note.d.ts +309 -0
- package/dist/merkle/signed-note.js +648 -0
- package/dist/merkle/sth.d.ts +31 -0
- package/dist/merkle/sth.js +31 -0
- package/dist/merkle/storage.d.ts +40 -0
- package/dist/merkle/storage.js +71 -0
- package/dist/merkle/tree.d.ts +68 -0
- package/dist/merkle/tree.js +94 -0
- package/dist/mldsa/embedded.d.ts +1 -0
- package/dist/{kyber → mldsa}/embedded.js +5 -5
- package/dist/mldsa/expand.d.ts +53 -0
- package/dist/mldsa/expand.js +188 -0
- package/dist/mldsa/format.d.ts +16 -0
- package/dist/mldsa/format.js +68 -0
- package/dist/mldsa/hashvariant.d.ts +32 -0
- package/dist/mldsa/hashvariant.js +248 -0
- package/dist/mldsa/index.d.ts +142 -0
- package/dist/mldsa/index.js +463 -0
- package/dist/mldsa/keygen.d.ts +16 -0
- package/dist/mldsa/keygen.js +232 -0
- package/dist/mldsa/params.d.ts +21 -0
- package/dist/mldsa/params.js +55 -0
- package/dist/mldsa/sha3-helpers.d.ts +30 -0
- package/dist/mldsa/sha3-helpers.js +124 -0
- package/dist/mldsa/sign.d.ts +36 -0
- package/dist/mldsa/sign.js +380 -0
- package/dist/mldsa/types.d.ts +91 -0
- package/dist/mldsa/types.js +25 -0
- package/dist/mldsa/validate.d.ts +55 -0
- package/dist/mldsa/validate.js +125 -0
- package/dist/mldsa/verify.d.ts +29 -0
- package/dist/mldsa/verify.js +269 -0
- package/dist/mldsa.wasm +0 -0
- package/dist/mlkem/embedded.d.ts +1 -0
- package/dist/mlkem/embedded.js +27 -0
- package/dist/mlkem/indcpa.d.ts +49 -0
- package/dist/{kyber → mlkem}/indcpa.js +44 -44
- package/dist/mlkem/index.d.ts +37 -0
- package/dist/{kyber → mlkem}/index.js +24 -34
- package/dist/mlkem/kem.d.ts +21 -0
- package/dist/{kyber → mlkem}/kem.js +44 -64
- package/dist/{kyber → mlkem}/params.d.ts +4 -4
- package/dist/{kyber → mlkem}/params.js +2 -2
- package/dist/mlkem/suite.d.ts +12 -0
- package/dist/{kyber → mlkem}/suite.js +17 -12
- package/dist/{kyber → mlkem}/types.d.ts +3 -3
- package/dist/{kyber → mlkem}/types.js +1 -1
- package/dist/{kyber → mlkem}/validate.d.ts +7 -7
- package/dist/{kyber → mlkem}/validate.js +7 -7
- package/dist/{kyber.wasm → mlkem.wasm} +0 -0
- package/dist/p256.wasm +0 -0
- package/dist/ratchet/index.d.ts +2 -0
- package/dist/ratchet/index.js +1 -0
- package/dist/ratchet/kdf-chain.js +3 -3
- package/dist/ratchet/ratchet-keypair.js +2 -2
- package/dist/ratchet/root-kdf.js +7 -7
- package/dist/ratchet/skipped-key-store.js +4 -4
- package/dist/ratchet/types.d.ts +1 -1
- package/dist/serpent/cipher-suite.js +20 -17
- package/dist/serpent/generator.d.ts +1 -1
- package/dist/serpent/generator.js +2 -2
- package/dist/serpent/index.d.ts +8 -7
- package/dist/serpent/index.js +18 -27
- package/dist/serpent/pool-worker.js +7 -5
- package/dist/serpent/serpent-cbc.d.ts +4 -4
- package/dist/serpent/serpent-cbc.js +11 -8
- package/dist/serpent/shared-ops.d.ts +3 -23
- package/dist/serpent/shared-ops.js +50 -85
- package/dist/serpent.wasm +0 -0
- package/dist/sha2/hkdf.js +5 -5
- package/dist/sha2/index.d.ts +21 -1
- package/dist/sha2/index.js +65 -10
- package/dist/sha2/types.d.ts +41 -2
- package/dist/sha2.wasm +0 -0
- package/dist/sha3/index.d.ts +72 -3
- package/dist/sha3/index.js +240 -14
- package/dist/sha3/kmac.d.ts +121 -0
- package/dist/sha3/kmac.js +800 -0
- package/dist/sha3.wasm +0 -0
- package/dist/shared/pkcs7.d.ts +22 -0
- package/dist/shared/pkcs7.js +84 -0
- package/dist/sign/ctx.d.ts +41 -0
- package/dist/sign/ctx.js +102 -0
- package/dist/sign/envelope.d.ts +45 -0
- package/dist/sign/envelope.js +152 -0
- package/dist/sign/hasher.d.ts +9 -0
- package/dist/sign/hasher.js +132 -0
- package/dist/sign/index.d.ts +11 -0
- package/dist/sign/index.js +34 -0
- package/dist/sign/sign-stream.d.ts +25 -0
- package/dist/sign/sign-stream.js +112 -0
- package/dist/sign/suites/ecdsa-p256.d.ts +2 -0
- package/dist/sign/suites/ecdsa-p256.js +120 -0
- package/dist/sign/suites/ed25519.d.ts +3 -0
- package/dist/sign/suites/ed25519.js +165 -0
- package/dist/sign/suites/hybrid-classical.d.ts +23 -0
- package/dist/sign/suites/hybrid-classical.js +526 -0
- package/dist/sign/suites/hybrid-pq.d.ts +4 -0
- package/dist/sign/suites/hybrid-pq.js +234 -0
- package/dist/sign/suites/mldsa.d.ts +7 -0
- package/dist/sign/suites/mldsa.js +161 -0
- package/dist/sign/suites/slhdsa.d.ts +7 -0
- package/dist/sign/suites/slhdsa.js +176 -0
- package/dist/sign/types.d.ts +106 -0
- package/dist/sign/types.js +28 -0
- package/dist/sign/verify-stream.d.ts +30 -0
- package/dist/sign/verify-stream.js +227 -0
- package/dist/slhdsa/embedded.d.ts +1 -0
- package/dist/slhdsa/embedded.js +26 -0
- package/dist/slhdsa/index.d.ts +149 -0
- package/dist/slhdsa/index.js +493 -0
- package/dist/slhdsa/params.d.ts +26 -0
- package/dist/slhdsa/params.js +70 -0
- package/dist/slhdsa/prehash.d.ts +68 -0
- package/dist/slhdsa/prehash.js +307 -0
- package/dist/slhdsa/sign.d.ts +39 -0
- package/dist/slhdsa/sign.js +116 -0
- package/dist/slhdsa/types.d.ts +129 -0
- package/dist/slhdsa/types.js +27 -0
- package/dist/slhdsa/validate.d.ts +60 -0
- package/dist/slhdsa/validate.js +127 -0
- package/dist/slhdsa/verify.d.ts +32 -0
- package/dist/slhdsa/verify.js +107 -0
- package/dist/slhdsa.wasm +0 -0
- package/dist/stream/header.js +3 -3
- package/dist/stream/index.d.ts +1 -0
- package/dist/stream/index.js +1 -0
- package/dist/stream/open-stream.js +31 -10
- package/dist/stream/seal-stream-pool.d.ts +1 -0
- package/dist/stream/seal-stream-pool.js +63 -26
- package/dist/stream/seal-stream.d.ts +1 -1
- package/dist/stream/seal-stream.js +20 -9
- package/dist/stream/seal.js +6 -6
- package/dist/stream/types.d.ts +3 -1
- package/dist/stream/types.js +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utils.d.ts +3 -3
- package/dist/utils.js +46 -54
- package/dist/wasm-source.d.ts +7 -7
- package/dist/wasm-source.js +1 -1
- package/dist/x25519/embedded.d.ts +1 -0
- package/dist/x25519/embedded.js +31 -0
- package/dist/x25519/index.d.ts +43 -0
- package/dist/x25519/index.js +159 -0
- package/dist/x25519/types.d.ts +25 -0
- package/dist/x25519/types.js +27 -0
- package/dist/x25519/validate.d.ts +2 -0
- package/dist/x25519/validate.js +39 -0
- package/package.json +70 -26
- package/SECURITY.md +0 -163
- package/dist/ct-wasm.d.ts +0 -1
- package/dist/ct-wasm.js +0 -3
- package/dist/docs/aead.md +0 -363
- package/dist/docs/architecture.md +0 -1011
- package/dist/docs/argon2id.md +0 -305
- package/dist/docs/chacha20.md +0 -781
- package/dist/docs/exports.md +0 -277
- package/dist/docs/fortuna.md +0 -530
- package/dist/docs/init.md +0 -301
- package/dist/docs/loader.md +0 -256
- package/dist/docs/serpent.md +0 -617
- package/dist/docs/sha2.md +0 -671
- package/dist/docs/sha3.md +0 -612
- package/dist/docs/types.md +0 -416
- package/dist/docs/utils.md +0 -457
- package/dist/embedded/kyber.d.ts +0 -1
- package/dist/embedded/kyber.js +0 -3
- package/dist/kyber/embedded.d.ts +0 -1
- package/dist/kyber/indcpa.d.ts +0 -49
- package/dist/kyber/index.d.ts +0 -38
- package/dist/kyber/kem.d.ts +0 -21
- package/dist/kyber/suite.d.ts +0 -12
- /package/dist/{ct.wasm → cte.wasm} +0 -0
package/CLAUDE.md
CHANGED
|
@@ -1,484 +1,127 @@
|
|
|
1
|
-
# leviathan-crypto
|
|
1
|
+
# leviathan-crypto: AI Assistant Guide
|
|
2
2
|
|
|
3
3
|
> [!NOTE]
|
|
4
|
-
>
|
|
5
|
-
|
|
6
|
-
> ### Table of Contents
|
|
7
|
-
> - [What This Library Is](#what-this-library-is)
|
|
8
|
-
> - [Critical: stateful classes hold exclusive access](#critical-stateful-classes-hold-exclusive-access)
|
|
9
|
-
> - [Critical: `init()` is required](#critical-init-is-required)
|
|
10
|
-
> - [Critical: call `dispose()` after use](#critical-call-dispose-after-use)
|
|
11
|
-
> - [Critical: `decrypt()` throws on authentication failure](#critical-decrypt-throws-on-authentication-failure--never-returns-null)
|
|
12
|
-
> - [Critical: subpath init function names](#critical-subpath-init-function-names)
|
|
13
|
-
> - [Which module does each class require?](#which-module-does-each-class-require)
|
|
14
|
-
> - [Recommended patterns](#recommended-patterns)
|
|
15
|
-
> - [`SerpentCbc` arg order](#serpentcbc-arg-order)
|
|
16
|
-
> - [Utilities (no `init()` required)](#utilities-no-init-required)
|
|
17
|
-
> - [Full documentation](#full-documentation)
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## What This Library Is
|
|
22
|
-
|
|
23
|
-
`leviathan-crypto` is a zero-dependency WebAssembly cryptography library for
|
|
24
|
-
TypeScript and JavaScript. All cryptographic computation runs in WASM, outside
|
|
25
|
-
the JavaScript JIT. The TypeScript layer provides the public API: input
|
|
26
|
-
validation, type safety, and ergonomics. It never implements cryptographic
|
|
27
|
-
algorithms itself.
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## Critical: stateful classes hold exclusive access
|
|
32
|
-
|
|
33
|
-
> [!CAUTION]
|
|
34
|
-
> Stateful classes (`SHAKE128`, `SHAKE256`, `ChaCha20`, `SerpentCtr`,
|
|
35
|
-
> `SerpentCbc`) hold exclusive access to their WASM module for their entire
|
|
36
|
-
> lifetime. Construct, use, `dispose()` — in that order. Attempting to
|
|
37
|
-
> construct a second stateful instance on the same module throws. Atomic
|
|
38
|
-
> one-shot classes (`SHA256`, `SHA3_*`, `HMAC_*`, `Poly1305`, AEAD classes)
|
|
39
|
-
> also throw if the module is held by a stateful class. Pool workers are
|
|
40
|
-
> unaffected (each worker has its own WASM instance).
|
|
41
|
-
|
|
42
|
-
Every TS wrapper in a given WASM module shares one `WebAssembly.Instance` and
|
|
43
|
-
therefore one linear memory. A runtime exclusivity guard prevents two stateful
|
|
44
|
-
instances from silently clobbering each other's state:
|
|
4
|
+
> Ships with the npm package. Inside the repo? Read `AGENTS.md` instead.
|
|
45
5
|
|
|
46
|
-
|
|
47
|
-
const a = new SHAKE128()
|
|
48
|
-
a.absorb(msg1)
|
|
49
|
-
const b = new SHAKE128() // throws — a still owns the 'sha3' module
|
|
50
|
-
a.squeeze(32)
|
|
51
|
-
a.dispose() // release
|
|
52
|
-
const c = new SHAKE128() // ok
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
The same applies across class boundaries on the same module — e.g. a live
|
|
56
|
-
`SerpentCbc` blocks `new SerpentCtr()`, `new Serpent()`, and
|
|
57
|
-
`Seal.encrypt(SerpentCipher, ...)` until `dispose()`. Always wrap stateful
|
|
58
|
-
use in `try { ... } finally { x.dispose() }`.
|
|
6
|
+
## What this is
|
|
59
7
|
|
|
60
|
-
|
|
61
|
-
`HMAC_SHA256.hash`, `Poly1305.mac`, `Serpent.encryptBlock`, `XChaCha20Cipher.sealChunk`,
|
|
62
|
-
and their peers) when another stateful instance holds the same WASM module.
|
|
63
|
-
This protects pre-existing long-lived atomic instances from having their
|
|
64
|
-
WASM state silently clobbered by a later-constructed stateful user. If you
|
|
65
|
-
hold a `Fortuna` instance backed by `SerpentGenerator` and also want to use `SerpentCtr`/`SerpentCbc`, or one backed by `ChaCha20Generator` while also using `ChaCha20Poly1305`/`XChaCha20Poly1305`, you must `dispose()` one before operating the other. The library will throw a clear error rather than silently corrupting state.
|
|
8
|
+
Zero-dependency WASM crypto for TS/JS. All compute in WASM (outside JS JIT); TS layer = input validation + ergonomics.
|
|
66
9
|
|
|
67
|
-
|
|
10
|
+
| Family | Primitives |
|
|
11
|
+
|---|---|
|
|
12
|
+
| Symmetric AEAD | Serpent-256, XChaCha20-Poly1305, AES-256-GCM-SIV |
|
|
13
|
+
| Post-quantum sig | ML-DSA, SLH-DSA, PQ-only hybrid composites |
|
|
14
|
+
| Classical sig | Ed25519, ECDSA-P256, classical+PQ hybrid composites |
|
|
15
|
+
| Key agreement | ML-KEM, X25519 |
|
|
16
|
+
| Transparency log | Merkle log (C2SP-conformant) |
|
|
17
|
+
| Forward-secret ratchet | Signal SPQR KDF (rule 8) |
|
|
68
18
|
|
|
69
|
-
##
|
|
19
|
+
## API shape
|
|
70
20
|
|
|
71
|
-
|
|
72
|
-
module is loaded throws immediately with a clear error. Call `init()` once at
|
|
73
|
-
startup, before any cryptographic operations.
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
import { init, Serpent } from 'leviathan-crypto'
|
|
77
|
-
import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
|
|
78
|
-
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
21
|
+
Two hierarchies, one suite extension point. Tier = data shape. Suite = crypto choice.
|
|
79
22
|
|
|
80
|
-
|
|
81
|
-
|
|
23
|
+
| Tier | AEAD | Signatures |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| One-shot | Seal | Sign |
|
|
26
|
+
| Streaming | SealStream / OpenStream | SignStream / VerifyStream |
|
|
27
|
+
| Parallel | SealStreamPool (Web Workers) | n/a |
|
|
28
|
+
| Suite arg | CipherSuite | SignatureSuite |
|
|
82
29
|
|
|
83
|
-
`
|
|
84
|
-
`WasmSource`: a gzip+base64 string, `URL`, `ArrayBuffer`, `Uint8Array`,
|
|
85
|
-
pre-compiled `WebAssembly.Module`, `Response`, or `Promise<Response>`.
|
|
30
|
+
`Seal` blob = single-chunk `SealStream` output (interchangeable). Symmetric: `SerpentCipher`, `XChaCha20Cipher`, `AESGCMSIVCipher`. `MlKemSuite(MlKem*, inner)` wraps any of them for PQ hybrid (same `CipherSuite` interface).
|
|
86
31
|
|
|
87
|
-
|
|
88
|
-
gzip+base64 blobs for each module, bundled with the package.
|
|
32
|
+
**Prefer the high-level surface (Seal / Sign / Fortuna).** Handles KDF, nonce management, auth, key wipes, counter binding. Rejects tampered/reordered/spliced inputs before plaintext release. Low-level primitives (raw `ChaCha20`, `SerpentCbc`, etc.) require reading their wiki page first.
|
|
89
33
|
|
|
90
|
-
|
|
34
|
+
## Rules (cross-cutting foot-guns)
|
|
91
35
|
|
|
92
|
-
|
|
36
|
+
1. **`init()` required.** Nothing works before `init()`. Throws on missing module. Idempotent. Use `/embedded` subpath for bundled gzip+base64 blob.
|
|
93
37
|
|
|
94
|
-
|
|
95
|
-
done; it zeroes that memory. Not calling `dispose()` leaks key material.
|
|
38
|
+
2. **`dispose()` stateful in `finally`.** Stateful classes hold key material in WASM until `dispose()` zeros it. Wrap in `try { ... } finally { x.dispose() }`. Atomic one-shots (`Seal.encrypt`, `Sign.sign`, hashes, MACs) self-wipe.
|
|
96
39
|
|
|
97
|
-
|
|
98
|
-
const cipher = new XChaCha20Poly1305()
|
|
99
|
-
try {
|
|
100
|
-
return cipher.encrypt(key, nonce, plaintext)
|
|
101
|
-
} finally {
|
|
102
|
-
cipher.dispose()
|
|
103
|
-
}
|
|
104
|
-
```
|
|
40
|
+
3. **Stateful = exclusive module access.** A stateful class (`SHAKE128`, `ChaCha20`, `SerpentCtr/Cbc`, `SealStream`, `MlKem*`, etc.) owns its WASM module for its lifetime. Second stateful instance on same module throws. Atomic methods on same module throw while a stateful holder is alive. Pool workers isolated.
|
|
105
41
|
|
|
106
|
-
|
|
42
|
+
4. **AEAD `decrypt()` throws on auth failure.** `Seal.decrypt`, AEAD `decrypt()`, `OpenStream.pull` never return null or corrupted plaintext. Wrong key, tampered blob, corrupted bytes all surface as exceptions.
|
|
107
43
|
|
|
108
|
-
|
|
44
|
+
5. **Raw `verify()` returns bool. `Sign.verify` throws.** Raw primitives (`MlDsa*.verify`, `SlhDsa*.verify`, hybrid `verifyPrehashed`) return `false` on bad sig; only contract violations throw. `Sign.verify` envelope throws on bad sig (parity with `Seal.decrypt`).
|
|
109
45
|
|
|
110
|
-
|
|
111
|
-
null return; catch the exception.
|
|
46
|
+
6. **Pure-mode and prehash sigs NOT interchangeable.** `dsa.sign` vs `dsa.verifyHash` bind different M' domain bytes (0x00 vs 0x01, FIPS 204 §3.6.4 / FIPS 205 §10.2.2). Sigs don't cross even with identical messages. SignatureSuite enforces at the type level.
|
|
112
47
|
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const plaintext = seal.decrypt(key, ciphertext)
|
|
116
|
-
} catch {
|
|
117
|
-
// wrong key or tampered data
|
|
118
|
-
}
|
|
119
|
-
```
|
|
48
|
+
7. **v3 sign envelope: `ctx` required.** `Sign.sign` / `Sign.verify` / `SignStream` / `VerifyStream` all require `ctx`. Pass `new Uint8Array()` for empty. Each suite prepends `ctxDomain` (blocks cross-suite verify). Per-call `ctx` ≤ 255 bytes (FIPS 204 §3.6.1); longer throws `SigningError('sig-ctx-too-long')`. Per-call ceiling = `253 - len(ctxDomain)` (221-234 bytes across catalog).
|
|
120
49
|
|
|
121
|
-
|
|
50
|
+
8. **Ratchet = KDF primitives, not a session.** Forward secrecy + post-compromise security primitives only. State machine, message counters, header format, epoch orchestration, transport = app concerns. NOT a drop-in Signal client.
|
|
122
51
|
|
|
123
|
-
##
|
|
52
|
+
## Subpath imports
|
|
124
53
|
|
|
125
|
-
|
|
126
|
-
These are only needed for tree-shakeable imports. The root barrel `init()` is
|
|
127
|
-
the normal path.
|
|
54
|
+
Pattern: `leviathan-crypto/<mod>` exports `<mod>Init(source)`; `leviathan-crypto/<mod>/embedded` exports `<mod>Wasm`. Twelve modules: `serpent`, `chacha20`, `aes`, `sha2`, `sha3`, `keccak`, `mlkem`, `mldsa`, `slhdsa`, `blake3`, `curve25519`, `p256`.
|
|
128
55
|
|
|
129
|
-
|
|
130
|
-
`/embedded` subpath to get the bundled blob as a ready-to-use WasmSource.
|
|
56
|
+
Aliases share binary + instance slot:
|
|
131
57
|
|
|
132
|
-
|
|
|
133
|
-
|
|
134
|
-
|
|
|
135
|
-
|
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
| `leviathan-crypto/keccak` | `keccakInit(source)` | `leviathan-crypto/keccak/embedded` → `keccakWasm` |
|
|
139
|
-
| `leviathan-crypto/kyber` | `kyberInit(source)` | `leviathan-crypto/kyber/embedded` → `kyberWasm` |
|
|
58
|
+
| Alias | Backed by |
|
|
59
|
+
|---|---|
|
|
60
|
+
| keccak | sha3 |
|
|
61
|
+
| ed25519 | curve25519 |
|
|
62
|
+
| x25519 | curve25519 |
|
|
63
|
+
| ecdsa | p256 |
|
|
140
64
|
|
|
141
|
-
|
|
142
|
-
// Tree-shakeable — loads only serpent WASM
|
|
143
|
-
import { serpentInit, Serpent } from 'leviathan-crypto/serpent'
|
|
144
|
-
import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
|
|
145
|
-
await serpentInit(serpentWasm)
|
|
146
|
-
```
|
|
65
|
+
No `/embedded`: `leviathan-crypto/ratchet`, `leviathan-crypto/stream`, `leviathan-crypto/sign`, `leviathan-crypto/merkle`.
|
|
147
66
|
|
|
148
|
-
|
|
67
|
+
## Class → init modules + wiki
|
|
149
68
|
|
|
150
|
-
|
|
69
|
+
| Class | init modules | wiki |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| Seal / SealStream / OpenStream / SealStreamPool | varies by suite | https://github.com/xero/leviathan-crypto/wiki/aead |
|
|
72
|
+
| SerpentCipher | serpent, sha2 | https://github.com/xero/leviathan-crypto/wiki/serpent |
|
|
73
|
+
| XChaCha20Cipher | chacha20, sha2 | https://github.com/xero/leviathan-crypto/wiki/chacha20 |
|
|
74
|
+
| AESGCMSIVCipher | aes, sha2 | https://github.com/xero/leviathan-crypto/wiki/aes |
|
|
75
|
+
| MlKemSuite(MlKem*, inner) | mlkem, sha3 + inner | https://github.com/xero/leviathan-crypto/wiki/mlkem |
|
|
76
|
+
| Sign / SignStream / VerifyStream | varies by suite | https://github.com/xero/leviathan-crypto/wiki/signing |
|
|
77
|
+
| MlDsa{44,65,87}Suite (pure, prehash) | mldsa, sha3 (+sha2 for SHA-2 prehash) | https://github.com/xero/leviathan-crypto/wiki/mldsa |
|
|
78
|
+
| SlhDsa{128f,192f,256f}Suite | slhdsa, sha3 (+sha2 for SHA-2 prehash) | https://github.com/xero/leviathan-crypto/wiki/slhdsa |
|
|
79
|
+
| Ed25519Suite / Ed25519PreHashSuite | curve25519 (+sha2 for PreHash) | https://github.com/xero/leviathan-crypto/wiki/ed25519 |
|
|
80
|
+
| EcdsaP256Suite (hedged, low-S) | p256, sha2 | https://github.com/xero/leviathan-crypto/wiki/ecdsa-p256 |
|
|
81
|
+
| MlDsa{44,65}Ed25519Suite (0x20, 0x21) | mldsa, sha3, curve25519, sha2 | https://github.com/xero/leviathan-crypto/wiki/signaturesuite |
|
|
82
|
+
| MlDsa{44,65}EcdsaP256Suite (0x22, 0x23) | mldsa, sha3, p256, sha2 | https://github.com/xero/leviathan-crypto/wiki/signaturesuite |
|
|
83
|
+
| MlDsa{44,65,87}SlhDsa{128f,192f,256f}Suite (0x30-0x32) | mldsa, sha3, slhdsa | https://github.com/xero/leviathan-crypto/wiki/signaturesuite |
|
|
84
|
+
| X25519 | curve25519 | https://github.com/xero/leviathan-crypto/wiki/x25519 |
|
|
85
|
+
| MerkleVerifier / MerkleLog | sha2 + suite (+blake3 if `hashing: 'blake3'`) | https://github.com/xero/leviathan-crypto/wiki/merkle |
|
|
86
|
+
| Sparse PQ Ratchet (KDF, rule 8) | sha2, mlkem, sha3 | https://github.com/xero/leviathan-crypto/wiki/ratchet |
|
|
87
|
+
| Fortuna | one cipher + one hash | https://github.com/xero/leviathan-crypto/wiki/fortuna |
|
|
88
|
+
| SHA-2 / HMAC / HKDF | sha2 | https://github.com/xero/leviathan-crypto/wiki/sha2 |
|
|
89
|
+
| SHA-3 / SHAKE | sha3 | https://github.com/xero/leviathan-crypto/wiki/sha3 |
|
|
90
|
+
| CSHAKE / KMAC / KMACXOF | sha3 | https://github.com/xero/leviathan-crypto/wiki/kmac |
|
|
91
|
+
| BLAKE3 family | blake3 | https://github.com/xero/leviathan-crypto/wiki/blake3 |
|
|
151
92
|
|
|
152
|
-
|
|
153
|
-
|---------|-----------------|
|
|
154
|
-
| `Serpent`, `SerpentCtr`, `SerpentCbc`, `SerpentCipher` | `init({ serpent: serpentWasm, sha2: sha2Wasm })` |
|
|
155
|
-
| `SealStream`, `OpenStream`, `SerpentCipher` (when using SerpentCipher) | `init({ serpent: serpentWasm, sha2: sha2Wasm })` |
|
|
156
|
-
| `SealStream`, `OpenStream`, `XChaCha20Cipher` (when using XChaCha20Cipher) | `init({ chacha20: chacha20Wasm, sha2: sha2Wasm })` |
|
|
157
|
-
| `SealStreamPool` | depends on cipher: same modules as the cipher suite + `sha2` |
|
|
158
|
-
| `ChaCha20`, `Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305` | `init({ chacha20: chacha20Wasm })` |
|
|
159
|
-
| `SHA256`, `SHA384`, `SHA512`, `HMAC_SHA256`, `HMAC_SHA384`, `HMAC_SHA512`, `HKDF_SHA256`, `HKDF_SHA512` | `init({ sha2: sha2Wasm })` |
|
|
160
|
-
| `SHA3_224`, `SHA3_256`, `SHA3_384`, `SHA3_512`, `SHAKE128`, `SHAKE256` | `init({ sha3: sha3Wasm })` or `init({ keccak: keccakWasm })` — `'keccak'` is an alias for `'sha3'` |
|
|
161
|
-
| `MlKem512`, `MlKem768`, `MlKem1024` | `init({ kyber: kyberWasm, sha3: sha3Wasm })` — both modules required |
|
|
162
|
-
| `Fortuna` | `init(...)` with one cipher module (`serpent` or `chacha20`) plus one hash module (`sha2` or `sha3`). All four combinations are valid. |
|
|
163
|
-
| `KDFChain`, `ratchetInit`, `ratchetReady`, `SkippedKeyStore` | `init({ sha2: sha2Wasm })` |
|
|
164
|
-
| `kemRatchetEncap`, `kemRatchetDecap`, `RatchetKeypair` | `init({ sha2: sha2Wasm, kyber: kyberWasm, sha3: sha3Wasm })` |
|
|
93
|
+
Other refs:
|
|
165
94
|
|
|
166
|
-
|
|
95
|
+
| Topic | wiki |
|
|
96
|
+
|---|---|
|
|
97
|
+
| init() / WasmSource | https://github.com/xero/leviathan-crypto/wiki/init |
|
|
98
|
+
| Loading strategies | https://github.com/xero/leviathan-crypto/wiki/loader |
|
|
99
|
+
| CDN usage | https://github.com/xero/leviathan-crypto/wiki/cdn |
|
|
100
|
+
| Content-Security-Policy | https://github.com/xero/leviathan-crypto/wiki/csp |
|
|
101
|
+
| Worked examples | https://github.com/xero/leviathan-crypto/wiki/examples |
|
|
102
|
+
| Utilities | https://github.com/xero/leviathan-crypto/wiki/utils |
|
|
103
|
+
| Argon2id integration | https://github.com/xero/leviathan-crypto/wiki/argon2id |
|
|
104
|
+
| CipherSuite interface | https://github.com/xero/leviathan-crypto/wiki/ciphersuite |
|
|
105
|
+
| SignatureSuite catalog | https://github.com/xero/leviathan-crypto/wiki/signaturesuite |
|
|
167
106
|
|
|
168
|
-
##
|
|
107
|
+
## Canonical example
|
|
169
108
|
|
|
170
|
-
|
|
109
|
+
`Seal` + `SerpentCipher` round-trip:
|
|
171
110
|
|
|
172
111
|
```typescript
|
|
173
|
-
import { init, Seal, SerpentCipher
|
|
112
|
+
import { init, Seal, SerpentCipher } from 'leviathan-crypto'
|
|
174
113
|
import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
|
|
175
114
|
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
176
115
|
|
|
177
116
|
await init({ serpent: serpentWasm, sha2: sha2Wasm })
|
|
178
117
|
|
|
179
|
-
const key
|
|
180
|
-
const blob
|
|
181
|
-
const decrypted = Seal.decrypt(SerpentCipher, key, blob)
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### Incremental streaming AEAD
|
|
185
|
-
|
|
186
|
-
Use when you cannot buffer the full message before encrypting.
|
|
187
|
-
|
|
188
|
-
```typescript
|
|
189
|
-
import { init, SealStream, OpenStream, SerpentCipher, randomBytes } from 'leviathan-crypto'
|
|
190
|
-
import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
|
|
191
|
-
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
192
|
-
|
|
193
|
-
await init({ serpent: serpentWasm, sha2: sha2Wasm })
|
|
194
|
-
|
|
195
|
-
const key = randomBytes(32)
|
|
196
|
-
const sealer = new SealStream(SerpentCipher, key)
|
|
197
|
-
const preamble = sealer.preamble // 20 bytes — send first
|
|
198
|
-
const ct0 = sealer.push(chunk0)
|
|
199
|
-
const ct1 = sealer.push(chunk1)
|
|
200
|
-
const ctLast = sealer.finalize(lastChunk)
|
|
201
|
-
|
|
202
|
-
const opener = new OpenStream(SerpentCipher, key, preamble)
|
|
203
|
-
const pt0 = opener.pull(ct0)
|
|
204
|
-
const pt1 = opener.pull(ct1)
|
|
205
|
-
const ptLast = opener.finalize(ctLast)
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### Length-prefixed streaming (for files and buffered transports)
|
|
209
|
-
|
|
210
|
-
Pass `{ framed: true }` to `SealStream` for self-delimiting `u32be` length-prefixed
|
|
211
|
-
framing. Use when chunks will be concatenated into a flat byte stream. Omit when the
|
|
212
|
-
transport frames messages itself (WebSocket, IPC).
|
|
213
|
-
|
|
214
|
-
```typescript
|
|
215
|
-
const sealer = new SealStream(SerpentCipher, key, { framed: true })
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### XChaCha20-Poly1305
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
import { init, XChaCha20Poly1305, randomBytes } from 'leviathan-crypto'
|
|
222
|
-
import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
|
|
223
|
-
|
|
224
|
-
await init({ chacha20: chacha20Wasm })
|
|
225
|
-
|
|
226
|
-
const aead = new XChaCha20Poly1305()
|
|
227
|
-
const key = randomBytes(32)
|
|
228
|
-
const nonce = randomBytes(24)
|
|
229
|
-
const sealed = aead.encrypt(key, nonce, plaintext, aad?) // ciphertext || tag
|
|
230
|
-
const plaintext = aead.decrypt(key, nonce, sealed, aad?) // throws on tamper
|
|
231
|
-
aead.dispose()
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
Note: `encrypt()` returns ciphertext with the 16-byte Poly1305 tag appended.
|
|
235
|
-
`decrypt()` expects the same concatenated format, not separate ciphertext and tag.
|
|
236
|
-
|
|
237
|
-
> [!CAUTION]
|
|
238
|
-
> **Strict single-use on `encrypt()`.** `ChaCha20Poly1305.encrypt()` and
|
|
239
|
-
> `XChaCha20Poly1305.encrypt()` are terminal on **any** throw — including
|
|
240
|
-
> `RangeError` on key/nonce length. A retry on the same instance always
|
|
241
|
-
> raises the single-use guard, never a fresh length error. Always allocate
|
|
242
|
-
> a new AEAD per message. This tightens the 2.0-beta semantics where
|
|
243
|
-
> length-validation throws were recoverable.
|
|
244
|
-
>
|
|
245
|
-
> **`SealStream` / `OpenStream` have a `'failed'` terminal state for crypto
|
|
246
|
-
> failures.** Crypto-path throws from `push()`, `pull()`, or `finalize()`
|
|
247
|
-
> (auth failure, WASM errors, cipher exceptions) wipe derived keys and
|
|
248
|
-
> transition the stream to `'failed'`. Subsequent operations and `seek()`
|
|
249
|
-
> throw with `'failed'` in the message. `dispose()` on a `'failed'` stream
|
|
250
|
-
> is a no-op.
|
|
251
|
-
>
|
|
252
|
-
> **Argument errors are non-terminal on both `SealStream` and `OpenStream`.**
|
|
253
|
-
> `push()` / `finalize()` throwing `RangeError` for a chunk larger than
|
|
254
|
-
> `chunkSize` does NOT wipe keys or enter `'failed'`. Symmetrically,
|
|
255
|
-
> `pull()` / `finalize()` throwing `RangeError` for a too-short chunk,
|
|
256
|
-
> an oversize chunk, or a framed length-prefix mismatch does NOT wipe keys
|
|
257
|
-
> or enter `'failed'` either. The stream stays in `'ready'` and accepts a
|
|
258
|
-
> corrected retry. Only auth failures from the crypto path transition to
|
|
259
|
-
> `'failed'`. Validation errors depend only on attacker-observable input
|
|
260
|
-
> lengths, so this distinction creates no cryptographic oracle.
|
|
261
|
-
>
|
|
262
|
-
> **`SealStreamPool.seal()` is terminal on any throw.** Worker errors,
|
|
263
|
-
> auth failures, output-size overflows, or any other rejection kill the
|
|
264
|
-
> pool (`pool is dead`, keys wiped). Construct a new pool to continue.
|
|
265
|
-
>
|
|
266
|
-
> **`OpenStream.seek` is forward-only and fully validates before mutating.**
|
|
267
|
-
> Backward seeks (`index < this.counter`) throw a `RangeError` with
|
|
268
|
-
> `'forward-only'` in the message. Indices above `Number.MAX_SAFE_INTEGER`
|
|
269
|
-
> throw without mutating `counter`, so the stream stays usable. Construct
|
|
270
|
-
> a fresh `OpenStream` from the same preamble to restart from the
|
|
271
|
-
> beginning.
|
|
272
|
-
>
|
|
273
|
-
> **Loader accepts any `PromiseLike<WasmSource>`.** `Promise<Response>`,
|
|
274
|
-
> `Promise<ArrayBuffer>`, `Promise<Uint8Array>`, and `Promise<string>`
|
|
275
|
-
> (gzip+base64 blob) all work — the loader resolves the thenable and
|
|
276
|
-
> re-dispatches by the resolved runtime type. Nesting is capped at depth
|
|
277
|
-
> 3; deeper chains throw `TypeError: thenable nesting too deep (max 3)`.
|
|
278
|
-
|
|
279
|
-
### Hashing
|
|
280
|
-
|
|
281
|
-
```typescript
|
|
282
|
-
import { init, SHA256, HMAC_SHA256 } from 'leviathan-crypto'
|
|
283
|
-
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
284
|
-
|
|
285
|
-
await init({ sha2: sha2Wasm })
|
|
286
|
-
|
|
287
|
-
const hasher = new SHA256()
|
|
288
|
-
const digest = hasher.hash(data) // returns Uint8Array
|
|
289
|
-
hasher.dispose()
|
|
290
|
-
|
|
291
|
-
const mac = new HMAC_SHA256()
|
|
292
|
-
const tag = mac.hash(key, data)
|
|
293
|
-
mac.dispose()
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
### SHAKE (XOF — variable-length output)
|
|
297
|
-
|
|
298
|
-
```typescript
|
|
299
|
-
import { init, SHAKE128 } from 'leviathan-crypto'
|
|
300
|
-
import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
|
|
301
|
-
|
|
302
|
-
await init({ sha3: sha3Wasm })
|
|
303
|
-
|
|
304
|
-
const xof = new SHAKE128()
|
|
305
|
-
xof.absorb(data)
|
|
306
|
-
const out1 = xof.squeeze(32) // first 32 bytes of output stream
|
|
307
|
-
const out2 = xof.squeeze(32) // next 32 bytes — contiguous XOF stream
|
|
308
|
-
xof.dispose()
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
### ML-KEM post-quantum key encapsulation
|
|
312
|
-
|
|
313
|
-
```typescript
|
|
314
|
-
import { init, MlKem768 } from 'leviathan-crypto'
|
|
315
|
-
import { kyberWasm } from 'leviathan-crypto/kyber/embedded'
|
|
316
|
-
import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
|
|
317
|
-
|
|
318
|
-
await init({ kyber: kyberWasm, sha3: sha3Wasm })
|
|
319
|
-
|
|
320
|
-
const kem = new MlKem768()
|
|
321
|
-
const { encapsulationKey, decapsulationKey } = kem.keygen()
|
|
322
|
-
|
|
323
|
-
// Encapsulation (sender — public encapsulationKey only)
|
|
324
|
-
const { ciphertext, sharedSecret: senderSecret } = kem.encapsulate(encapsulationKey)
|
|
325
|
-
|
|
326
|
-
// Decapsulation (recipient — private decapsulationKey)
|
|
327
|
-
const recipientSecret = kem.decapsulate(decapsulationKey, ciphertext)
|
|
328
|
-
|
|
329
|
-
// senderSecret === recipientSecret (32 bytes)
|
|
330
|
-
kem.dispose()
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
Kyber classes require **both** `kyber` and `sha3` initialized. ML-KEM produces
|
|
334
|
-
a 32-byte shared secret suitable for use as a symmetric key.
|
|
335
|
-
|
|
336
|
-
- `encapsulate(ek)` and `decapsulate(dk, c)` now throw `RangeError` on FIPS 203
|
|
337
|
-
§7.2/§7.3 validation failure. This is a breaking change from 1.x. Callers that
|
|
338
|
-
want to probe a key without triggering an exception can still call the public
|
|
339
|
-
`checkEncapsulationKey(ek)` / `checkDecapsulationKey(dk)` boolean methods. The
|
|
340
|
-
§7.3 throw is a local-integrity check on key material (ML-KEM assumes `dk` is
|
|
341
|
-
recipient-controlled local storage) and is distinct from the FO transform's
|
|
342
|
-
implicit-rejection path for tampered ciphertext, which returns a pseudorandom
|
|
343
|
-
shared secret rather than throwing.
|
|
344
|
-
|
|
345
|
-
### Fortuna CSPRNG
|
|
346
|
-
|
|
347
|
-
`Fortuna.create()` requires explicit `generator` and `hash` parameters. There are no defaults.
|
|
348
|
-
|
|
349
|
-
```typescript
|
|
350
|
-
import { init, Fortuna } from 'leviathan-crypto'
|
|
351
|
-
import { ChaCha20Generator } from 'leviathan-crypto/chacha20'
|
|
352
|
-
import { SHA256Hash } from 'leviathan-crypto/sha2'
|
|
353
|
-
import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
|
|
354
|
-
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
355
|
-
|
|
356
|
-
await init({ chacha20: chacha20Wasm, sha2: sha2Wasm })
|
|
357
|
-
const fortuna = await Fortuna.create({ generator: ChaCha20Generator, hash: SHA256Hash })
|
|
358
|
-
const bytes = fortuna.get(32)
|
|
359
|
-
fortuna.stop()
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
Substitute `SerpentGenerator` for `ChaCha20Generator`, or `SHA3_256Hash` for `SHA256Hash`, to use other primitive combinations. Match the `init()` modules to whichever pair you pick.
|
|
363
|
-
|
|
364
|
-
### Sparse Post-Quantum Ratchet (KDF layer only)
|
|
365
|
-
|
|
366
|
-
```typescript
|
|
367
|
-
import { init, MlKem768, ratchetInit, kemRatchetEncap, kemRatchetDecap, KDFChain } from 'leviathan-crypto'
|
|
368
|
-
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
369
|
-
import { kyberWasm } from 'leviathan-crypto/kyber/embedded'
|
|
370
|
-
import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
|
|
371
|
-
|
|
372
|
-
await init({ sha2: sha2Wasm, kyber: kyberWasm, sha3: sha3Wasm })
|
|
373
|
-
|
|
374
|
-
const kem = new MlKem768()
|
|
375
|
-
const { encapsulationKey: bobEk, decapsulationKey: bobDk } = kem.keygen()
|
|
376
|
-
|
|
377
|
-
// Both parties derive initial keys from a shared secret
|
|
378
|
-
const alice = ratchetInit(sharedSecret)
|
|
379
|
-
const bob = ratchetInit(sharedSecret)
|
|
380
|
-
|
|
381
|
-
// Alice performs a KEM ratchet step; kemCt goes in the message header
|
|
382
|
-
const aliceEpoch = kemRatchetEncap(kem, alice.nextRootKey, bobEk)
|
|
383
|
-
|
|
384
|
-
// Bob decapsulates after receiving kemCt. Pass bobEk as ownEk — both sides
|
|
385
|
-
// bind (peerEk, kemCt, context) into HKDF info with u32be length prefixes.
|
|
386
|
-
const bobEpoch = kemRatchetDecap(kem, bob.nextRootKey, bobDk, aliceEpoch.kemCt, bobEk)
|
|
387
|
-
|
|
388
|
-
// Both construct KDFChains and derive per-message keys
|
|
389
|
-
const aliceSend = new KDFChain(aliceEpoch.sendChainKey)
|
|
390
|
-
const bobRecv = new KDFChain(bobEpoch.recvChainKey)
|
|
391
|
-
const msgKey = aliceSend.step() // alice encrypts; bob decrypts with bobRecv.step()
|
|
392
|
-
aliceSend.dispose()
|
|
393
|
-
bobRecv.dispose()
|
|
394
|
-
kem.dispose()
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
Additional ratchet exports:
|
|
398
|
-
|
|
399
|
-
- `SkippedKeyStore` — MKSKIPPED cache (DR spec §3.2/§3.5). `resolve(chain, counter)` returns a `ResolveHandle` — call `handle.commit()` on successful decrypt (wipes the key) and `handle.rollback()` on auth failure (returns the key to the store so a later legitimate delivery at the same counter can still decrypt). Double-settle throws; accessing `handle.key` after settling throws. Split budgets: `maxCacheSize` (default 100) bounds memory, `maxSkipPerResolve` (default 50) bounds per-message HKDF work. Legacy `{ ceiling: N }` still accepted — sets both. `advanceToBoundary(chain, pn)` for epoch transitions; `wipeAll()` on teardown. Requires `sha2`. **Breaking change from 1.x and 2.0-beta:** `resolve` used to return a raw key with delete-on-retrieval semantics.
|
|
400
|
-
- `RatchetKeypair` — single-use ek/dk wrapper; `new RatchetKeypair(kem)` generates a keypair, `decap(kem, rk, kemCt)` decapsulates exactly once then wipes the dk, `dispose()` is idempotent. Requires `sha2`, `kyber`, `sha3`.
|
|
401
|
-
- `RatchetMessageHeader` — interface `{ epoch, counter, pn?, kemCt? }`; `pn` and `kemCt` present only on the first message of a new epoch.
|
|
402
|
-
- `KDFChain.stepWithCounter()` — returns `{ key, counter }` atomically; eliminates the separate `.n` read after `step()`.
|
|
403
|
-
|
|
404
|
-
Idiomatic `resolve` usage:
|
|
405
|
-
|
|
406
|
-
```typescript
|
|
407
|
-
const h = store.resolve(chain, counter)
|
|
118
|
+
const key = SerpentCipher.keygen()
|
|
119
|
+
const blob = Seal.encrypt(SerpentCipher, key, plaintext)
|
|
408
120
|
try {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
} catch (e) {
|
|
413
|
-
h.rollback()
|
|
414
|
-
throw e
|
|
121
|
+
const pt = Seal.decrypt(SerpentCipher, key, blob)
|
|
122
|
+
} catch {
|
|
123
|
+
// wrong key, tampered blob, or corrupted bytes
|
|
415
124
|
}
|
|
416
125
|
```
|
|
417
126
|
|
|
418
|
-
|
|
419
|
-
error conditions, the A2B direction split, bilateral exchange, group usage,
|
|
420
|
-
and context-based session separation.
|
|
421
|
-
|
|
422
|
-
---
|
|
423
|
-
|
|
424
|
-
## `SerpentCbc` arg order
|
|
425
|
-
|
|
426
|
-
IV is the **second** argument, not the third:
|
|
427
|
-
|
|
428
|
-
```typescript
|
|
429
|
-
cipher.encrypt(key, iv, plaintext) // correct
|
|
430
|
-
cipher.decrypt(key, iv, ciphertext) // correct
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
`SerpentCbc` is unauthenticated. Always pair with `HMAC_SHA256`
|
|
434
|
-
(Encrypt-then-MAC) or use `Seal` with `SerpentCipher` instead.
|
|
435
|
-
|
|
436
|
-
`SerpentCbc.decrypt()` throws a single generic `RangeError('invalid ciphertext')`
|
|
437
|
-
for every failure mode (empty input, non-multiple-of-16 length, any PKCS7 padding
|
|
438
|
-
mismatch) and validates the trailing 16 bytes branch-free. This closes the
|
|
439
|
-
Vaudenay 2002 padding-oracle surface on `{ dangerUnauthenticated: true }` callers,
|
|
440
|
-
but it is **not a substitute for authentication**. Power users must still apply
|
|
441
|
-
Encrypt-then-MAC with `HMAC_SHA256` and verify the tag with `constantTimeEqual`
|
|
442
|
-
before calling `decrypt()` — the CT-safe padding check only prevents one class
|
|
443
|
-
of leakage, not forgery.
|
|
444
|
-
|
|
445
|
-
---
|
|
446
|
-
|
|
447
|
-
## Utilities (no `init()` required)
|
|
448
|
-
|
|
449
|
-
```typescript
|
|
450
|
-
import { hexToBytes, bytesToHex, randomBytes, constantTimeEqual, wipe, hasSIMD } from 'leviathan-crypto'
|
|
451
|
-
|
|
452
|
-
// available immediately — no await init() needed
|
|
453
|
-
const key = randomBytes(32)
|
|
454
|
-
const hex = bytesToHex(key)
|
|
455
|
-
const back = hexToBytes(hex)
|
|
456
|
-
const safe = constantTimeEqual(a, b) // constant-time equality (branch-free SIMD tail, no post-loop conditional on secret bits) — never use ===
|
|
457
|
-
wipe(key) // zero a Uint8Array in place
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
`hasSIMD()` returns `true` if the runtime supports WebAssembly SIMD.
|
|
461
|
-
Serpent, ChaCha20, and Kyber modules all require SIMD; `init()` throws
|
|
462
|
-
a clear error on runtimes without support. SIMD has been a baseline
|
|
463
|
-
feature of all major browsers and runtimes since 2021. SHA-2 and SHA-3
|
|
464
|
-
modules run on any WASM-capable runtime.
|
|
465
|
-
|
|
466
|
-
---
|
|
467
|
-
|
|
468
|
-
## Full documentation
|
|
469
|
-
|
|
470
|
-
The complete API reference ships in `docs/` alongside this file:
|
|
471
|
-
|
|
472
|
-
| File | Contents |
|
|
473
|
-
|------|----------|
|
|
474
|
-
| `docs/serpent.md` | `SerpentCipher`, `Serpent`, `SerpentCtr`, `SerpentCbc` |
|
|
475
|
-
| `docs/chacha20.md` | `ChaCha20`, `Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305`, `XChaCha20Cipher` |
|
|
476
|
-
| `docs/sha2.md` | `SHA256`, `SHA384`, `SHA512`, `HMAC_SHA256`, `HMAC_SHA384`, `HMAC_SHA512`, `HKDF_SHA256`, `HKDF_SHA512` |
|
|
477
|
-
| `docs/sha3.md` | `SHA3_224`, `SHA3_256`, `SHA3_384`, `SHA3_512`, `SHAKE128`, `SHAKE256` |
|
|
478
|
-
| `docs/aead.md` | `Seal`, `SealStream`, `OpenStream`, `SealStreamPool`, `CipherSuite` |
|
|
479
|
-
| `docs/kyber.md` | `MlKem512`, `MlKem768`, `MlKem1024`, `KyberSuite` — ML-KEM (FIPS 203) API reference |
|
|
480
|
-
| `docs/fortuna.md` | `Fortuna` CSPRNG |
|
|
481
|
-
| `docs/init.md` | `init()` API, loading modes, subpath imports |
|
|
482
|
-
| `docs/utils.md` | Encoding helpers, `constantTimeEqual`, `wipe`, `randomBytes` |
|
|
483
|
-
| `docs/types.md` | `Hash`, `KeyedHash`, `Blockcipher`, `Streamcipher`, `AEAD` interfaces; `CipherSuite`, `DerivedKeys`, `SealStreamOpts`, `PoolOpts`, `WasmSource` |
|
|
484
|
-
| `docs/architecture.md` | Module structure, WASM layer, three-tier design |
|
|
127
|
+
Streaming: `Seal` → `SealStream` + `OpenStream`. PQ hybrid: `SerpentCipher` → `MlKemSuite(new MlKem768(), SerpentCipher)`. Same call site, wire format, catch semantics.
|