leviathan-crypto 2.0.1 → 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 +88 -281
- package/LICENSE +4 -0
- package/README.md +275 -87
- 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.d.ts +10 -0
- package/dist/chacha20/cipher-suite.js +98 -13
- package/dist/chacha20/generator.d.ts +12 -0
- package/dist/chacha20/generator.js +91 -0
- package/dist/chacha20/index.d.ts +100 -3
- package/dist/chacha20/index.js +169 -35
- package/dist/chacha20/ops.d.ts +57 -6
- package/dist/chacha20/ops.js +107 -27
- package/dist/chacha20/pool-worker.js +14 -0
- package/dist/chacha20/types.d.ts +1 -32
- package/dist/cte-wasm.d.ts +1 -0
- package/dist/cte-wasm.js +3 -0
- package/dist/cte.wasm +0 -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 -0
- package/dist/embedded/chacha20-pool-worker.js +5 -0
- 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 -0
- package/dist/embedded/serpent-pool-worker.js +5 -0
- 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 +18 -12
- package/dist/fortuna.js +166 -99
- package/dist/index.d.ts +42 -11
- package/dist/index.js +65 -20
- package/dist/init.d.ts +1 -3
- package/dist/init.js +73 -7
- 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 -19
- package/dist/loader.js +26 -32
- 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 +48 -48
- package/dist/mlkem/index.d.ts +37 -0
- package/dist/{kyber → mlkem}/index.js +41 -31
- package/dist/mlkem/kem.d.ts +21 -0
- package/dist/{kyber → mlkem}/kem.js +48 -13
- 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 +4 -3
- package/dist/{kyber → mlkem}/types.js +1 -1
- package/dist/mlkem/validate.d.ts +23 -0
- package/dist/{kyber → mlkem}/validate.js +24 -20
- package/dist/{kyber.wasm → mlkem.wasm} +0 -0
- package/dist/p256.wasm +0 -0
- package/dist/ratchet/index.d.ts +8 -0
- package/dist/ratchet/index.js +38 -0
- package/dist/ratchet/kdf-chain.d.ts +13 -0
- package/dist/ratchet/kdf-chain.js +85 -0
- package/dist/ratchet/ratchet-keypair.d.ts +9 -0
- package/dist/ratchet/ratchet-keypair.js +61 -0
- package/dist/ratchet/root-kdf.d.ts +4 -0
- package/dist/ratchet/root-kdf.js +124 -0
- package/dist/ratchet/skipped-key-store.d.ts +14 -0
- package/dist/ratchet/skipped-key-store.js +154 -0
- package/dist/ratchet/types.d.ts +36 -0
- package/dist/ratchet/types.js +26 -0
- package/dist/serpent/cipher-suite.d.ts +10 -0
- package/dist/serpent/cipher-suite.js +144 -56
- package/dist/serpent/generator.d.ts +12 -0
- package/dist/serpent/generator.js +97 -0
- package/dist/serpent/index.d.ts +62 -1
- package/dist/serpent/index.js +97 -21
- package/dist/serpent/pool-worker.js +28 -102
- package/dist/serpent/serpent-cbc.d.ts +16 -6
- package/dist/serpent/serpent-cbc.js +58 -37
- package/dist/serpent/shared-ops.d.ts +63 -0
- package/dist/serpent/shared-ops.js +178 -0
- package/dist/serpent/types.d.ts +1 -5
- package/dist/serpent.wasm +0 -0
- package/dist/sha2/hash.d.ts +2 -0
- package/dist/sha2/hash.js +53 -0
- package/dist/sha2/hkdf.js +5 -5
- package/dist/sha2/index.d.ts +22 -1
- package/dist/sha2/index.js +80 -11
- package/dist/sha2/types.d.ts +41 -2
- package/dist/sha2.wasm +0 -0
- package/dist/sha3/hash.d.ts +2 -0
- package/dist/sha3/hash.js +53 -0
- package/dist/sha3/index.d.ts +87 -3
- package/dist/sha3/index.js +317 -19
- 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 +8 -8
- package/dist/stream/index.d.ts +1 -0
- package/dist/stream/index.js +1 -0
- package/dist/stream/open-stream.js +65 -22
- package/dist/stream/seal-stream-pool.d.ts +2 -0
- package/dist/stream/seal-stream-pool.js +100 -33
- package/dist/stream/seal-stream.d.ts +1 -1
- package/dist/stream/seal-stream.js +48 -19
- 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 +22 -1
- package/dist/types.js +1 -1
- package/dist/utils.d.ts +9 -10
- package/dist/utils.js +84 -59
- package/dist/wasm-source.d.ts +9 -8
- 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 +123 -64
- package/SECURITY.md +0 -276
- package/dist/ct-wasm.d.ts +0 -1
- package/dist/ct-wasm.js +0 -3
- package/dist/ct.wasm +0 -0
- package/dist/docs/aead.md +0 -323
- package/dist/docs/architecture.md +0 -932
- package/dist/docs/argon2id.md +0 -302
- package/dist/docs/chacha20.md +0 -674
- package/dist/docs/exports.md +0 -241
- package/dist/docs/fortuna.md +0 -313
- package/dist/docs/init.md +0 -302
- package/dist/docs/loader.md +0 -161
- package/dist/docs/serpent.md +0 -519
- package/dist/docs/sha2.md +0 -613
- package/dist/docs/sha3.md +0 -546
- package/dist/docs/types.md +0 -276
- package/dist/docs/utils.md +0 -367
- 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 -13
- package/dist/kyber/validate.d.ts +0 -19
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
//
|
|
22
22
|
// src/ts/stream/seal-stream-pool.ts
|
|
23
23
|
//
|
|
24
|
-
// SealStreamPool
|
|
24
|
+
// SealStreamPool, parallel batch encryption/decryption using the STREAM
|
|
25
25
|
// construction. Dispatches per-chunk seal/open jobs across Web Workers.
|
|
26
26
|
// Any error is fatal: auth failure, crash, or timeout kills all workers,
|
|
27
27
|
// wipes keys, and rejects all pending promises.
|
|
28
|
-
import { randomBytes, wipe } from '../utils.js';
|
|
28
|
+
import { randomBytes, wipe, constantTimeEqual } from '../utils.js';
|
|
29
29
|
import { isInitialized } from '../init.js';
|
|
30
30
|
import { compileWasm } from '../loader.js';
|
|
31
31
|
import { AuthenticationError } from '../errors.js';
|
|
@@ -44,6 +44,7 @@ export class SealStreamPool {
|
|
|
44
44
|
_framed;
|
|
45
45
|
_timeout;
|
|
46
46
|
_header;
|
|
47
|
+
_commitment;
|
|
47
48
|
_workers;
|
|
48
49
|
_idle;
|
|
49
50
|
_queue;
|
|
@@ -53,7 +54,7 @@ export class SealStreamPool {
|
|
|
53
54
|
_sealed;
|
|
54
55
|
_keys;
|
|
55
56
|
_masterKey;
|
|
56
|
-
constructor(cipher, workers, keys, masterKey, header, chunkSize, framed, timeout) {
|
|
57
|
+
constructor(cipher, workers, keys, masterKey, header, commitment, chunkSize, framed, timeout) {
|
|
57
58
|
this._cipher = cipher;
|
|
58
59
|
this._workers = workers;
|
|
59
60
|
this._idle = [...workers];
|
|
@@ -65,6 +66,7 @@ export class SealStreamPool {
|
|
|
65
66
|
this._keys = keys;
|
|
66
67
|
this._masterKey = masterKey;
|
|
67
68
|
this._header = header;
|
|
69
|
+
this._commitment = commitment;
|
|
68
70
|
this._chunkSize = chunkSize;
|
|
69
71
|
this._framed = framed;
|
|
70
72
|
this._timeout = timeout;
|
|
@@ -75,10 +77,10 @@ export class SealStreamPool {
|
|
|
75
77
|
}
|
|
76
78
|
static async create(cipher, key, opts) {
|
|
77
79
|
if (!isInitialized('sha2'))
|
|
78
|
-
throw new Error('leviathan-crypto: stream layer requires sha2 for key derivation
|
|
80
|
+
throw new Error('leviathan-crypto: stream layer requires sha2 for key derivation, '
|
|
79
81
|
+ 'call init({ sha2: ... }) before creating a SealStreamPool');
|
|
80
82
|
if (cipher.kemCtSize > 0)
|
|
81
|
-
throw new Error('leviathan-crypto: SealStreamPool does not support KEM-enabled cipher suites
|
|
83
|
+
throw new Error('leviathan-crypto: SealStreamPool does not support KEM-enabled cipher suites, '
|
|
82
84
|
+ 'KEM encryption is asymmetric (seal uses encapsulation key, open requires decapsulation key) '
|
|
83
85
|
+ 'and cannot share a single key across both directions. '
|
|
84
86
|
+ 'Use SealStream / OpenStream directly for hybrid KEM encryption.');
|
|
@@ -101,7 +103,7 @@ export class SealStreamPool {
|
|
|
101
103
|
}
|
|
102
104
|
else {
|
|
103
105
|
if (required.length > 1)
|
|
104
|
-
throw new Error(`leviathan-crypto: cipher requires ${required.length} WASM modules (${required.join(', ')})
|
|
106
|
+
throw new Error(`leviathan-crypto: cipher requires ${required.length} WASM modules (${required.join(', ')}), provide a Record`);
|
|
105
107
|
modules[required[0]] = await compileWasm(opts.wasm);
|
|
106
108
|
}
|
|
107
109
|
// For padded ciphers, validate that a full plaintext chunk fits in the WASM
|
|
@@ -115,10 +117,20 @@ export class SealStreamPool {
|
|
|
115
117
|
}
|
|
116
118
|
if (key.length !== cipher.keySize)
|
|
117
119
|
throw new RangeError(`key must be ${cipher.keySize} bytes (got ${key.length})`);
|
|
118
|
-
// Generate nonce and
|
|
120
|
+
// Generate nonce and build header before deriveKeys, XChaCha20 binds
|
|
121
|
+
// the header into HKDF info; SerpentCipher accepts and ignores it.
|
|
119
122
|
const nonce = randomBytes(16);
|
|
120
|
-
const keys = cipher.deriveKeys(key, nonce);
|
|
121
123
|
const header = writeHeader(cipher.formatEnum, framed, nonce, chunkSize);
|
|
124
|
+
const keys = cipher.deriveKeys(key, nonce, undefined, header);
|
|
125
|
+
let commitment = null;
|
|
126
|
+
if (cipher.commitmentSize > 0) {
|
|
127
|
+
if (!keys.commitment || keys.commitment.length !== cipher.commitmentSize) {
|
|
128
|
+
cipher.wipeKeys(keys);
|
|
129
|
+
throw new Error(`leviathan-crypto: ${cipher.formatName}.deriveKeys returned `
|
|
130
|
+
+ `${keys.commitment?.length ?? 'no'} commitment bytes, expected ${cipher.commitmentSize}`);
|
|
131
|
+
}
|
|
132
|
+
commitment = keys.commitment;
|
|
133
|
+
}
|
|
122
134
|
// Spawn workers sequentially (compatible with @vitest/web-worker)
|
|
123
135
|
const workers = [];
|
|
124
136
|
for (let i = 0; i < n; i++) {
|
|
@@ -147,7 +159,7 @@ export class SealStreamPool {
|
|
|
147
159
|
});
|
|
148
160
|
workers.push(w);
|
|
149
161
|
}
|
|
150
|
-
return new SealStreamPool(cipher, workers, keys, key.slice(), header, chunkSize, framed, timeout);
|
|
162
|
+
return new SealStreamPool(cipher, workers, keys, key.slice(), header, commitment, chunkSize, framed, timeout);
|
|
151
163
|
}
|
|
152
164
|
get header() {
|
|
153
165
|
return this._header;
|
|
@@ -176,12 +188,17 @@ export class SealStreamPool {
|
|
|
176
188
|
}
|
|
177
189
|
try {
|
|
178
190
|
const results = await Promise.all(jobs);
|
|
179
|
-
|
|
191
|
+
const commitmentLen = this._cipher.commitmentSize;
|
|
192
|
+
let totalLen = HEADER_SIZE + commitmentLen;
|
|
180
193
|
for (const r of results)
|
|
181
194
|
totalLen += this._framed ? r.length + 4 : r.length;
|
|
182
195
|
const ciphertext = new Uint8Array(totalLen);
|
|
183
196
|
ciphertext.set(this._header, 0);
|
|
184
197
|
let pos = HEADER_SIZE;
|
|
198
|
+
if (this._commitment) {
|
|
199
|
+
ciphertext.set(this._commitment, pos);
|
|
200
|
+
pos += commitmentLen;
|
|
201
|
+
}
|
|
185
202
|
for (const r of results) {
|
|
186
203
|
if (this._framed) {
|
|
187
204
|
new DataView(ciphertext.buffer, pos).setUint32(0, r.length, false);
|
|
@@ -202,30 +219,52 @@ export class SealStreamPool {
|
|
|
202
219
|
if (this._dead)
|
|
203
220
|
throw new Error('leviathan-crypto: pool is dead');
|
|
204
221
|
if (ciphertext.length < HEADER_SIZE)
|
|
205
|
-
throw new RangeError(`leviathan-crypto: ciphertext too short
|
|
222
|
+
throw new RangeError(`leviathan-crypto: ciphertext too short, need at least ${HEADER_SIZE} bytes for header`);
|
|
206
223
|
// Validate header before splitting chunks
|
|
207
224
|
const h = readHeader(ciphertext.subarray(0, HEADER_SIZE));
|
|
208
225
|
if (h.formatEnum !== this._cipher.formatEnum)
|
|
209
226
|
throw new Error(`leviathan-crypto: pool expected format 0x${this._cipher.formatEnum.toString(16).padStart(2, '0')}, `
|
|
210
227
|
+ `got 0x${h.formatEnum.toString(16).padStart(2, '0')}`);
|
|
211
228
|
if (h.chunkSize !== this._chunkSize)
|
|
212
|
-
throw new RangeError(`leviathan-crypto: pool chunkSize mismatch
|
|
229
|
+
throw new RangeError(`leviathan-crypto: pool chunkSize mismatch, pool expects ${this._chunkSize}, `
|
|
213
230
|
+ `header says ${h.chunkSize}`);
|
|
214
231
|
if (h.framed !== this._framed)
|
|
215
|
-
throw new Error(`leviathan-crypto: pool framing mismatch
|
|
232
|
+
throw new Error(`leviathan-crypto: pool framing mismatch, pool is ${this._framed ? 'framed' : 'unframed'}, `
|
|
216
233
|
+ `header says ${h.framed ? 'framed' : 'unframed'}`);
|
|
217
234
|
// Re-derive keys from the nonce embedded in this ciphertext's header.
|
|
218
|
-
// The pool's _keys are tied to its own seal nonce
|
|
235
|
+
// The pool's _keys are tied to its own seal nonce, for arbitrary incoming
|
|
219
236
|
// ciphertext the nonce may differ, so we derive fresh keys here.
|
|
220
237
|
if (!this._masterKey)
|
|
221
238
|
throw new Error('leviathan-crypto: pool master key has been wiped');
|
|
222
|
-
const
|
|
239
|
+
const headerBytes = ciphertext.subarray(0, HEADER_SIZE);
|
|
240
|
+
const commitmentLen = this._cipher.commitmentSize;
|
|
241
|
+
const minLen = HEADER_SIZE + commitmentLen;
|
|
242
|
+
if (ciphertext.length < minLen)
|
|
243
|
+
throw new RangeError(`leviathan-crypto: ciphertext too short, need at least ${minLen} bytes for header + commitment`);
|
|
244
|
+
const openKeys = this._cipher.deriveKeys(this._masterKey, h.nonce, undefined, headerBytes);
|
|
223
245
|
let openKeysWiped = false;
|
|
246
|
+
// Verify commitment before any chunk dispatch. Wrong key fails fast
|
|
247
|
+
// with AuthenticationError, before Poly1305 is consulted.
|
|
248
|
+
if (commitmentLen > 0) {
|
|
249
|
+
const derivedCommitment = openKeys.commitment;
|
|
250
|
+
if (!derivedCommitment || derivedCommitment.length !== commitmentLen) {
|
|
251
|
+
this._cipher.wipeKeys(openKeys);
|
|
252
|
+
throw new Error(`leviathan-crypto: ${this._cipher.formatName}.deriveKeys returned `
|
|
253
|
+
+ `${derivedCommitment?.length ?? 'no'} commitment bytes, expected ${commitmentLen}`);
|
|
254
|
+
}
|
|
255
|
+
const recvCommitment = ciphertext.subarray(HEADER_SIZE, HEADER_SIZE + commitmentLen);
|
|
256
|
+
if (!constantTimeEqual(derivedCommitment, recvCommitment)) {
|
|
257
|
+
this._cipher.wipeKeys(openKeys);
|
|
258
|
+
const err = new AuthenticationError(`commitment-${this._cipher.formatName}`);
|
|
259
|
+
this._killAll(err);
|
|
260
|
+
throw err;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
224
263
|
try {
|
|
225
|
-
// Strip header before chunk splitting
|
|
226
|
-
const body = ciphertext.subarray(HEADER_SIZE);
|
|
264
|
+
// Strip header + commitment before chunk splitting
|
|
265
|
+
const body = ciphertext.subarray(HEADER_SIZE + commitmentLen);
|
|
227
266
|
if (body.length === 0)
|
|
228
|
-
throw new RangeError('leviathan-crypto: empty ciphertext
|
|
267
|
+
throw new RangeError('leviathan-crypto: empty ciphertext, seal() always produces at least one chunk');
|
|
229
268
|
// Compute max wire chunk size for per-chunk validation
|
|
230
269
|
const tagSize = this._cipher.tagSize;
|
|
231
270
|
const paddedSize = this._cipher.padded
|
|
@@ -265,7 +304,7 @@ export class SealStreamPool {
|
|
|
265
304
|
const jobs = [];
|
|
266
305
|
for (let i = 0; i < chunks.length; i++) {
|
|
267
306
|
if (chunks[i].length < tagSize)
|
|
268
|
-
throw new RangeError(`leviathan-crypto: chunk ${i} too short
|
|
307
|
+
throw new RangeError(`leviathan-crypto: chunk ${i} too short, need at least ${tagSize} bytes for tag `
|
|
269
308
|
+ `(got ${chunks[i].length})`);
|
|
270
309
|
if (chunks[i].length > maxWireChunk)
|
|
271
310
|
throw new RangeError(`leviathan-crypto: chunk ${i} exceeds max wire size `
|
|
@@ -277,10 +316,8 @@ export class SealStreamPool {
|
|
|
277
316
|
derivedKeyBytes: openKeys.bytes.slice(),
|
|
278
317
|
}));
|
|
279
318
|
}
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
// finally below only fires on pre-dispatch throws (empty body, frame errors,
|
|
283
|
-
// chunk validation), not as a redundant second call on the normal path.
|
|
319
|
+
// Per-job copies made; wipe main-thread openKeys immediately. openKeysWiped
|
|
320
|
+
// guards the finally so it only fires on pre-dispatch throws.
|
|
284
321
|
this._cipher.wipeKeys(openKeys);
|
|
285
322
|
openKeysWiped = true;
|
|
286
323
|
const results = await Promise.all(jobs);
|
|
@@ -329,7 +366,7 @@ export class SealStreamPool {
|
|
|
329
366
|
_send(worker, job) {
|
|
330
367
|
const transfer = [];
|
|
331
368
|
// Only transfer data.buffer when the Uint8Array owns the buffer exclusively.
|
|
332
|
-
// Subarrays from open() share the caller's ciphertext buffer
|
|
369
|
+
// Subarrays from open() share the caller's ciphertext buffer, transferring
|
|
333
370
|
// one would detach all sibling views dispatched as parallel jobs.
|
|
334
371
|
if (job.data.buffer instanceof ArrayBuffer
|
|
335
372
|
&& job.data.byteOffset === 0
|
|
@@ -340,7 +377,7 @@ export class SealStreamPool {
|
|
|
340
377
|
transfer.push(job.counterNonce.buffer);
|
|
341
378
|
if (job.derivedKeyBytes?.buffer instanceof ArrayBuffer)
|
|
342
379
|
transfer.push(job.derivedKeyBytes.buffer);
|
|
343
|
-
// aad is intentionally not transferred
|
|
380
|
+
// aad is intentionally not transferred, caller may retain the reference
|
|
344
381
|
worker.postMessage(job, { transfer });
|
|
345
382
|
}
|
|
346
383
|
_onMessage(worker, e) {
|
|
@@ -379,15 +416,14 @@ export class SealStreamPool {
|
|
|
379
416
|
reject(error);
|
|
380
417
|
this._pending.clear();
|
|
381
418
|
this._queue.length = 0;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
w.postMessage({ type: 'wipe' });
|
|
385
|
-
}
|
|
386
|
-
catch { /* worker may be terminated */ }
|
|
387
|
-
w.terminate();
|
|
388
|
-
}
|
|
389
|
-
this._workers.length = 0;
|
|
419
|
+
const workers = this._workers;
|
|
420
|
+
this._workers = [];
|
|
390
421
|
this._idle.length = 0;
|
|
422
|
+
// Fire-and-forget: wipe each worker's key material, then terminate.
|
|
423
|
+
// On timeout, terminate anyway, the main-thread key handles are
|
|
424
|
+
// wiped below so the owning surface no longer has access.
|
|
425
|
+
for (const w of workers)
|
|
426
|
+
this._wipeThenTerminate(w);
|
|
391
427
|
if (this._keys) {
|
|
392
428
|
wipe(this._keys.bytes);
|
|
393
429
|
this._keys = null;
|
|
@@ -397,4 +433,35 @@ export class SealStreamPool {
|
|
|
397
433
|
this._masterKey = null;
|
|
398
434
|
}
|
|
399
435
|
}
|
|
436
|
+
_wipeThenTerminate(w) {
|
|
437
|
+
const WIPE_ACK_TIMEOUT_MS = 100;
|
|
438
|
+
let done = false;
|
|
439
|
+
// prefer-const false positive: `finish` (defined below) closes over
|
|
440
|
+
// `t` and is invoked synchronously from the catch path before the
|
|
441
|
+
// `t = setTimeout(...)` assignment runs.
|
|
442
|
+
// eslint-disable-next-line prefer-const
|
|
443
|
+
let t;
|
|
444
|
+
const finish = () => {
|
|
445
|
+
if (done)
|
|
446
|
+
return;
|
|
447
|
+
done = true;
|
|
448
|
+
if (t !== undefined)
|
|
449
|
+
clearTimeout(t);
|
|
450
|
+
w.removeEventListener('message', onMsg);
|
|
451
|
+
w.terminate();
|
|
452
|
+
};
|
|
453
|
+
const onMsg = (e) => {
|
|
454
|
+
if (e.data && e.data.type === 'wiped')
|
|
455
|
+
finish();
|
|
456
|
+
};
|
|
457
|
+
w.addEventListener('message', onMsg);
|
|
458
|
+
try {
|
|
459
|
+
w.postMessage({ type: 'wipe' });
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
finish();
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
t = setTimeout(finish, WIPE_ACK_TIMEOUT_MS);
|
|
466
|
+
}
|
|
400
467
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CipherSuite, SealStreamOpts } from './types.js';
|
|
2
2
|
export declare class SealStream {
|
|
3
|
-
/** Preamble sent before the first chunk: header [|| kemCiphertext]. */
|
|
3
|
+
/** Preamble sent before the first chunk: header [|| kemCiphertext] [|| commitment]. */
|
|
4
4
|
readonly preamble: Uint8Array;
|
|
5
5
|
private readonly cipher;
|
|
6
6
|
private readonly keys;
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
//
|
|
22
22
|
// src/ts/stream/seal-stream.ts
|
|
23
23
|
//
|
|
24
|
-
// SealStream
|
|
24
|
+
// SealStream, cipher-agnostic streaming encryption using the STREAM
|
|
25
25
|
// construction (Hoang/Reyhanitabar/Rogaway/Vizár, CRYPTO 2015).
|
|
26
26
|
import { randomBytes, concat } from '../utils.js';
|
|
27
27
|
import { isInitialized } from '../init.js';
|
|
@@ -32,11 +32,11 @@ function u32beFrame(n) {
|
|
|
32
32
|
new DataView(b.buffer).setUint32(0, n, false);
|
|
33
33
|
return b;
|
|
34
34
|
}
|
|
35
|
-
// Module-level nonce injection slot
|
|
35
|
+
// Module-level nonce injection slot, used only by _fromNonce for KAT tests.
|
|
36
36
|
// Set immediately before constructing, cleared inside the constructor.
|
|
37
37
|
let _injectNonce;
|
|
38
38
|
export class SealStream {
|
|
39
|
-
/** Preamble sent before the first chunk: header [|| kemCiphertext]. */
|
|
39
|
+
/** Preamble sent before the first chunk: header [|| kemCiphertext] [|| commitment]. */
|
|
40
40
|
preamble;
|
|
41
41
|
cipher;
|
|
42
42
|
keys;
|
|
@@ -49,7 +49,7 @@ export class SealStream {
|
|
|
49
49
|
this.chunkSize = opts?.chunkSize ?? 65536;
|
|
50
50
|
this.framed = opts?.framed ?? false;
|
|
51
51
|
if (!isInitialized('sha2'))
|
|
52
|
-
throw new Error('leviathan-crypto: stream layer requires sha2 for key derivation
|
|
52
|
+
throw new Error('leviathan-crypto: stream layer requires sha2 for key derivation, '
|
|
53
53
|
+ 'call init({ sha2: ... }) before creating a SealStream');
|
|
54
54
|
if (key.length !== cipher.keySize)
|
|
55
55
|
throw new RangeError(`key must be ${cipher.keySize} bytes (got ${key.length})`);
|
|
@@ -57,14 +57,25 @@ export class SealStream {
|
|
|
57
57
|
throw new RangeError(`chunkSize must be in [${CHUNK_MIN}, ${CHUNK_MAX}] (got ${this.chunkSize})`);
|
|
58
58
|
const nonce = _injectNonce ?? randomBytes(16);
|
|
59
59
|
_injectNonce = undefined;
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
// Header must be built before deriveKeys, XChaCha20 binds it into
|
|
61
|
+
// the HKDF info string. SerpentCipher accepts and ignores it.
|
|
62
62
|
const header = writeHeader(cipher.formatEnum, this.framed, nonce, this.chunkSize);
|
|
63
|
-
this.
|
|
63
|
+
this.keys = cipher.deriveKeys(key, nonce, undefined, header);
|
|
64
|
+
const kemCt = this.keys.kemCiphertext;
|
|
65
|
+
const commitment = cipher.commitmentSize > 0 ? this.keys.commitment : undefined;
|
|
66
|
+
if (cipher.commitmentSize > 0 && (!commitment || commitment.length !== cipher.commitmentSize))
|
|
67
|
+
throw new Error(`leviathan-crypto: ${cipher.formatName}.deriveKeys returned `
|
|
68
|
+
+ `${commitment?.length ?? 'no'} commitment bytes, expected ${cipher.commitmentSize}`);
|
|
69
|
+
const parts = [header];
|
|
70
|
+
if (kemCt)
|
|
71
|
+
parts.push(kemCt);
|
|
72
|
+
if (commitment)
|
|
73
|
+
parts.push(commitment);
|
|
74
|
+
this.preamble = parts.length === 1 ? header : concat(...parts);
|
|
64
75
|
}
|
|
65
76
|
/**
|
|
66
77
|
* @internal
|
|
67
|
-
* KAT-only factory
|
|
78
|
+
* KAT-only factory, injects a fixed nonce so seal output is deterministic.
|
|
68
79
|
* Stripped from published `.d.ts` by `stripInternal`. Do not use in production.
|
|
69
80
|
*/
|
|
70
81
|
static _fromNonce(cipher, key, opts, nonce) {
|
|
@@ -80,30 +91,48 @@ export class SealStream {
|
|
|
80
91
|
}
|
|
81
92
|
push(chunk, opts) {
|
|
82
93
|
if (this.state !== 'ready')
|
|
83
|
-
throw new Error(
|
|
94
|
+
throw new Error(`SealStream: cannot push in state '${this.state}'`);
|
|
95
|
+
// Argument validation runs before the crypto-failure try/catch so a
|
|
96
|
+
// too-big chunk throws without wiping keys or transitioning to 'failed'.
|
|
97
|
+
// The caller can retry with a correctly-sized chunk.
|
|
84
98
|
if (chunk.length > this.chunkSize)
|
|
85
99
|
throw new RangeError(`chunk exceeds chunkSize (${chunk.length} > ${this.chunkSize})`);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
100
|
+
try {
|
|
101
|
+
const nonce = makeCounterNonce(this.counter, TAG_DATA);
|
|
102
|
+
const result = this.cipher.sealChunk(this.keys, nonce, chunk, opts?.aad);
|
|
103
|
+
this.counter++;
|
|
104
|
+
return this.framed ? concat(u32beFrame(result.length), result) : result;
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
this.cipher.wipeKeys(this.keys);
|
|
108
|
+
this.state = 'failed';
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
90
111
|
}
|
|
91
112
|
finalize(chunk, opts) {
|
|
92
113
|
if (this.state !== 'ready')
|
|
93
|
-
throw new Error(
|
|
114
|
+
throw new Error(`SealStream: cannot finalize in state '${this.state}'`);
|
|
94
115
|
if (chunk.length > this.chunkSize)
|
|
95
116
|
throw new RangeError(`chunk exceeds chunkSize (${chunk.length} > ${this.chunkSize})`);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
117
|
+
try {
|
|
118
|
+
const nonce = makeCounterNonce(this.counter, TAG_FINAL);
|
|
119
|
+
const result = this.cipher.sealChunk(this.keys, nonce, chunk, opts?.aad);
|
|
120
|
+
this.cipher.wipeKeys(this.keys);
|
|
121
|
+
this.state = 'finalized';
|
|
122
|
+
return this.framed ? concat(u32beFrame(result.length), result) : result;
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
this.cipher.wipeKeys(this.keys);
|
|
126
|
+
this.state = 'failed';
|
|
127
|
+
throw err;
|
|
128
|
+
}
|
|
101
129
|
}
|
|
102
130
|
dispose() {
|
|
103
131
|
if (this.state === 'ready') {
|
|
104
132
|
this.cipher.wipeKeys(this.keys);
|
|
105
133
|
this.state = 'finalized';
|
|
106
134
|
}
|
|
135
|
+
// 'failed' already wiped keys; 'finalized' already wiped keys, no-op.
|
|
107
136
|
}
|
|
108
137
|
toTransformStream() {
|
|
109
138
|
let headerSent = false;
|
package/dist/stream/seal.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
//
|
|
22
22
|
// src/ts/stream/seal.ts
|
|
23
23
|
//
|
|
24
|
-
// Seal
|
|
24
|
+
// Seal, unified single-shot encrypt/decrypt using the STREAM construction.
|
|
25
25
|
// Seal blobs are valid SealStream blobs with a single final chunk.
|
|
26
26
|
// OpenStream can decrypt a Seal blob without modification.
|
|
27
27
|
import { concat } from '../utils.js';
|
|
@@ -32,7 +32,7 @@ import { HEADER_SIZE, CHUNK_MAX, CHUNK_MIN } from './constants.js';
|
|
|
32
32
|
export class Seal {
|
|
33
33
|
static encrypt(suite, key, pt, opts) {
|
|
34
34
|
if (pt.length > CHUNK_MAX)
|
|
35
|
-
throw new RangeError(`Seal.encrypt: plaintext exceeds maximum (${CHUNK_MAX} bytes)
|
|
35
|
+
throw new RangeError(`Seal.encrypt: plaintext exceeds maximum (${CHUNK_MAX} bytes), use SealStream for large data`);
|
|
36
36
|
const sealer = new SealStream(suite, key, { chunkSize: Math.max(pt.length, CHUNK_MIN) });
|
|
37
37
|
try {
|
|
38
38
|
const ct = sealer.finalize(pt, opts);
|
|
@@ -43,9 +43,9 @@ export class Seal {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
static decrypt(suite, key, blob, opts) {
|
|
46
|
-
const preambleLen = HEADER_SIZE + suite.kemCtSize;
|
|
46
|
+
const preambleLen = HEADER_SIZE + suite.kemCtSize + suite.commitmentSize;
|
|
47
47
|
if (blob.length < preambleLen)
|
|
48
|
-
throw new RangeError(`Seal.decrypt: blob too short
|
|
48
|
+
throw new RangeError(`Seal.decrypt: blob too short, need at least ${preambleLen} bytes (got ${blob.length})`);
|
|
49
49
|
const preamble = blob.subarray(0, preambleLen);
|
|
50
50
|
const opener = new OpenStream(suite, key, preamble);
|
|
51
51
|
try {
|
|
@@ -57,12 +57,12 @@ export class Seal {
|
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
59
|
* @internal
|
|
60
|
-
* KAT-only
|
|
60
|
+
* KAT-only, injects a fixed nonce so output is deterministic.
|
|
61
61
|
* Stripped from published `.d.ts` by `stripInternal`. Do not use in production.
|
|
62
62
|
*/
|
|
63
63
|
static _fromNonce(suite, key, pt, nonce, opts) {
|
|
64
64
|
if (pt.length > CHUNK_MAX)
|
|
65
|
-
throw new RangeError(`Seal._fromNonce: plaintext exceeds maximum (${CHUNK_MAX} bytes)
|
|
65
|
+
throw new RangeError(`Seal._fromNonce: plaintext exceeds maximum (${CHUNK_MAX} bytes), use SealStream for large data`);
|
|
66
66
|
const sealer = SealStream._fromNonce(suite, key, { chunkSize: Math.max(pt.length, CHUNK_MIN) }, nonce);
|
|
67
67
|
try {
|
|
68
68
|
const ct = sealer.finalize(pt, opts);
|
package/dist/stream/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface DerivedKeys {
|
|
2
2
|
readonly bytes: Uint8Array;
|
|
3
3
|
readonly kemCiphertext?: Uint8Array;
|
|
4
|
+
readonly commitment?: Uint8Array;
|
|
4
5
|
}
|
|
5
6
|
export interface CipherSuite {
|
|
6
7
|
readonly formatEnum: number;
|
|
@@ -9,10 +10,11 @@ export interface CipherSuite {
|
|
|
9
10
|
readonly keySize: number;
|
|
10
11
|
readonly decKeySize?: number;
|
|
11
12
|
readonly kemCtSize: number;
|
|
13
|
+
readonly commitmentSize: number;
|
|
12
14
|
readonly tagSize: number;
|
|
13
15
|
readonly padded: boolean;
|
|
14
16
|
readonly wasmChunkSize: number;
|
|
15
|
-
deriveKeys(key: Uint8Array, nonce: Uint8Array, kemCt?: Uint8Array): DerivedKeys;
|
|
17
|
+
deriveKeys(key: Uint8Array, nonce: Uint8Array, kemCt?: Uint8Array, header?: Uint8Array): DerivedKeys;
|
|
16
18
|
sealChunk(keys: DerivedKeys, counterNonce: Uint8Array, chunk: Uint8Array, aad?: Uint8Array): Uint8Array;
|
|
17
19
|
openChunk(keys: DerivedKeys, counterNonce: Uint8Array, chunk: Uint8Array, aad?: Uint8Array): Uint8Array;
|
|
18
20
|
wipeKeys(keys: DerivedKeys): void;
|
package/dist/stream/types.js
CHANGED
|
@@ -21,6 +21,6 @@
|
|
|
21
21
|
//
|
|
22
22
|
// src/ts/stream/types.ts
|
|
23
23
|
//
|
|
24
|
-
// CipherSuite interface
|
|
24
|
+
// CipherSuite interface, cipher-specific logic injected into SealStream
|
|
25
25
|
// and OpenStream. Implementations are plain objects (not classes).
|
|
26
26
|
export {};
|
package/dist/types.d.ts
CHANGED
|
@@ -18,7 +18,28 @@ export interface Streamcipher {
|
|
|
18
18
|
}
|
|
19
19
|
export interface AEAD {
|
|
20
20
|
encrypt(msg: Uint8Array, aad?: Uint8Array): Uint8Array;
|
|
21
|
-
/** Decrypt and authenticate. Throws `Error` on authentication failure
|
|
21
|
+
/** Decrypt and authenticate. Throws `Error` on authentication failure, never returns null. */
|
|
22
22
|
decrypt(ciphertext: Uint8Array, aad?: Uint8Array): Uint8Array;
|
|
23
23
|
dispose(): void;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Stateless cipher PRF for Fortuna's generator slot. Produces `n` bytes of
|
|
27
|
+
* keystream from `(key, counter)` without mutating either input. Implementations
|
|
28
|
+
* are plain const objects, not classes.
|
|
29
|
+
*/
|
|
30
|
+
export interface Generator {
|
|
31
|
+
readonly keySize: number;
|
|
32
|
+
readonly blockSize: number;
|
|
33
|
+
readonly counterSize: number;
|
|
34
|
+
readonly wasmModules: readonly string[];
|
|
35
|
+
generate(key: Uint8Array, counter: Uint8Array, n: number): Uint8Array;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Stateless hash function for Fortuna's accumulator and reseed slots. Output
|
|
39
|
+
* size must match the generator's key size when paired in Fortuna.
|
|
40
|
+
*/
|
|
41
|
+
export interface HashFn {
|
|
42
|
+
readonly outputSize: number;
|
|
43
|
+
readonly wasmModules: readonly string[];
|
|
44
|
+
digest(msg: Uint8Array): Uint8Array;
|
|
45
|
+
}
|
package/dist/types.js
CHANGED
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Hex string to Uint8Array. Accepts lowercase/uppercase, optional 0x prefix. Throws RangeError on odd-length input. */
|
|
1
|
+
/** Hex string to Uint8Array. Accepts lowercase/uppercase, optional 0x prefix. Throws RangeError on odd-length or non-hex input. */
|
|
2
2
|
export declare const hexToBytes: (hex: string) => Uint8Array;
|
|
3
3
|
/** Uint8Array to lowercase hex string. */
|
|
4
4
|
export declare const bytesToHex: (bytes: Uint8Array) => string;
|
|
@@ -6,18 +6,17 @@ export declare const bytesToHex: (bytes: Uint8Array) => string;
|
|
|
6
6
|
export declare const utf8ToBytes: (str: string) => Uint8Array;
|
|
7
7
|
/** Uint8Array to UTF-8 string. */
|
|
8
8
|
export declare const bytesToUtf8: (bytes: Uint8Array) => string;
|
|
9
|
-
/** Base64 or base64url string to Uint8Array. Handles padded, unpadded, and legacy %3d padding.
|
|
10
|
-
export declare const base64ToBytes: (b64: string) => Uint8Array
|
|
11
|
-
/** Uint8Array to base64 string. Pass url=true for base64url (RFC 4648 §5
|
|
9
|
+
/** Base64 or base64url string to Uint8Array. Handles padded, unpadded, and legacy %3d padding. Throws RangeError on invalid input. */
|
|
10
|
+
export declare const base64ToBytes: (b64: string) => Uint8Array;
|
|
11
|
+
/** Uint8Array to base64 string. Pass url=true for base64url (RFC 4648 §5, no padding characters). */
|
|
12
12
|
export declare const bytesToBase64: (bytes: Uint8Array, url?: boolean) => string;
|
|
13
|
-
export declare const
|
|
13
|
+
export declare const CTE_MAX_BYTES = 32768;
|
|
14
14
|
/**
|
|
15
15
|
* Constant-time byte-array equality.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* Max input size: 32768 bytes per side (enforced regardless of code path).
|
|
16
|
+
* Runs entirely inside a WASM SIMD module (v128 XOR accumulate with
|
|
17
|
+
* branch-free reduction). Throws on runtimes without SIMD support,
|
|
18
|
+
* no JS fallback. Length check is not constant-time (length is
|
|
19
|
+
* non-secret in all protocols). Max input size: 32768 bytes per side.
|
|
21
20
|
*/
|
|
22
21
|
export declare const constantTimeEqual: (a: Uint8Array, b: Uint8Array) => boolean;
|
|
23
22
|
/** Zero a typed array in place. */
|