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
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
// ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
|
|
20
20
|
// ▀█████▀▀
|
|
21
21
|
//
|
|
22
|
-
// src/ts/
|
|
22
|
+
// src/ts/mlkem/suite.ts
|
|
23
23
|
//
|
|
24
|
-
//
|
|
24
|
+
// MlKemSuite, wraps a MlKemBase + inner CipherSuite into a unified CipherSuite.
|
|
25
25
|
// Provides hybrid KEM + symmetric AEAD for use with SealStream / OpenStream / Seal.
|
|
26
26
|
import { concat } from '../utils.js';
|
|
27
27
|
import { HKDF_SHA256 } from '../sha2/index.js';
|
|
@@ -36,7 +36,7 @@ const KEM_LABEL = {
|
|
|
36
36
|
3: 'mlkem768',
|
|
37
37
|
4: 'mlkem1024',
|
|
38
38
|
};
|
|
39
|
-
export function
|
|
39
|
+
export function MlKemSuite(kem, inner) {
|
|
40
40
|
const p = kem.params;
|
|
41
41
|
const kemNibble = KEM_NIBBLE[p.k];
|
|
42
42
|
if (!kemNibble)
|
|
@@ -50,23 +50,24 @@ export function KyberSuite(kem, inner) {
|
|
|
50
50
|
keySize: p.ekBytes,
|
|
51
51
|
decKeySize: p.dkBytes,
|
|
52
52
|
kemCtSize: p.ctBytes,
|
|
53
|
+
commitmentSize: inner.commitmentSize,
|
|
53
54
|
tagSize: inner.tagSize,
|
|
54
55
|
padded: inner.padded,
|
|
55
56
|
wasmChunkSize: inner.wasmChunkSize,
|
|
56
|
-
wasmModules: [...inner.wasmModules, '
|
|
57
|
-
deriveKeys(key, nonce, kemCt) {
|
|
57
|
+
wasmModules: [...inner.wasmModules, 'mlkem', 'sha3'],
|
|
58
|
+
deriveKeys(key, nonce, kemCt, header) {
|
|
58
59
|
let sharedSecret;
|
|
59
60
|
let outKemCt;
|
|
60
61
|
let ctForInfo;
|
|
61
62
|
if (kemCt === undefined) {
|
|
62
|
-
// encrypt path: key = ek
|
|
63
|
+
// encrypt path: key = ek, encapsulate to produce shared secret + ct
|
|
63
64
|
const result = kem.encapsulate(key);
|
|
64
65
|
sharedSecret = result.sharedSecret;
|
|
65
66
|
outKemCt = result.ciphertext;
|
|
66
67
|
ctForInfo = outKemCt;
|
|
67
68
|
}
|
|
68
69
|
else {
|
|
69
|
-
// decrypt path: key = dk
|
|
70
|
+
// decrypt path: key = dk, decapsulate to recover shared secret
|
|
70
71
|
sharedSecret = kem.decapsulate(key, kemCt);
|
|
71
72
|
ctForInfo = kemCt;
|
|
72
73
|
}
|
|
@@ -76,12 +77,16 @@ export function KyberSuite(kem, inner) {
|
|
|
76
77
|
const derived = hkdf.derive(sharedSecret, nonce, info, inner.keySize);
|
|
77
78
|
hkdf.dispose();
|
|
78
79
|
sharedSecret.fill(0);
|
|
79
|
-
// Delegate actual per-cipher key derivation to the inner suite
|
|
80
|
-
|
|
80
|
+
// Delegate actual per-cipher key derivation to the inner suite.
|
|
81
|
+
// Header is forwarded so XChaCha20's deriveKeys can bind it into
|
|
82
|
+
// HKDF info; SerpentCipher accepts and ignores it.
|
|
83
|
+
const innerKeys = inner.deriveKeys(derived, nonce, undefined, header);
|
|
81
84
|
derived.fill(0);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
if (!outKemCt)
|
|
86
|
+
return innerKeys;
|
|
87
|
+
return innerKeys.commitment
|
|
88
|
+
? { bytes: innerKeys.bytes, kemCiphertext: outKemCt, commitment: innerKeys.commitment }
|
|
89
|
+
: { bytes: innerKeys.bytes, kemCiphertext: outKemCt };
|
|
85
90
|
},
|
|
86
91
|
sealChunk: inner.sealChunk.bind(inner),
|
|
87
92
|
openChunk: inner.openChunk.bind(inner),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface MlKemExports {
|
|
2
2
|
memory: WebAssembly.Memory;
|
|
3
3
|
getModuleId: () => number;
|
|
4
4
|
getMemoryPages: () => number;
|
|
@@ -89,11 +89,11 @@ export interface Sha3Exports {
|
|
|
89
89
|
shakeSqueezeBlock: () => void;
|
|
90
90
|
wipeBuffers: () => void;
|
|
91
91
|
}
|
|
92
|
-
export interface
|
|
92
|
+
export interface MlKemKeyPair {
|
|
93
93
|
encapsulationKey: Uint8Array;
|
|
94
94
|
decapsulationKey: Uint8Array;
|
|
95
95
|
}
|
|
96
|
-
export interface
|
|
96
|
+
export interface MlKemEncapsulation {
|
|
97
97
|
ciphertext: Uint8Array;
|
|
98
98
|
sharedSecret: Uint8Array;
|
|
99
99
|
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { MlKemExports, Sha3Exports } from './types.js';
|
|
2
|
+
import type { MlKemParams } from './params.js';
|
|
3
3
|
/**
|
|
4
|
-
* Encapsulation key check
|
|
4
|
+
* Encapsulation key check, FIPS 203 §7.2 (EncapsulationKeyCheck).
|
|
5
5
|
*
|
|
6
6
|
* 1. Length gate: ek.length must equal params.ekBytes.
|
|
7
7
|
* 2. Decode the polyvec portion via ByteDecode₁₂ (polyvec_frombytes). The
|
|
8
|
-
* decoded coefficients are raw 12-bit values in [0, 4095]
|
|
8
|
+
* decoded coefficients are raw 12-bit values in [0, 4095], frombytes
|
|
9
9
|
* does not reduce mod q.
|
|
10
10
|
* 3. Modulus scan: every coefficient must satisfy c < Q = 3329.
|
|
11
11
|
*
|
|
12
12
|
* Returns true iff both gates pass. The seed ρ (final 32 bytes of ek) is
|
|
13
13
|
* not checked; any 32-byte value is a valid ρ per FIPS 203.
|
|
14
14
|
*/
|
|
15
|
-
export declare function checkEncapsulationKey(kx:
|
|
15
|
+
export declare function checkEncapsulationKey(kx: MlKemExports, params: MlKemParams, ek: Uint8Array): boolean;
|
|
16
16
|
/**
|
|
17
|
-
* Decapsulation key check
|
|
17
|
+
* Decapsulation key check, FIPS 203 §7.3 (DecapsulationKeyCheck).
|
|
18
18
|
*
|
|
19
19
|
* 1. Length check: dk.length == params.dkBytes
|
|
20
20
|
* 2. Extract embedded ek and H(ek), verify SHA3-256(ek) matches stored H
|
|
21
21
|
* 3. Also run checkEncapsulationKey on the embedded ek
|
|
22
22
|
*/
|
|
23
|
-
export declare function checkDecapsulationKey(kx:
|
|
23
|
+
export declare function checkDecapsulationKey(kx: MlKemExports, sx: Sha3Exports, params: MlKemParams, dk: Uint8Array): boolean;
|
|
@@ -19,17 +19,17 @@
|
|
|
19
19
|
// ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
|
|
20
20
|
// ▀█████▀▀
|
|
21
21
|
//
|
|
22
|
-
// src/ts/
|
|
22
|
+
// src/ts/mlkem/validate.ts
|
|
23
23
|
//
|
|
24
|
-
// ML-KEM key validation
|
|
24
|
+
// ML-KEM key validation, FIPS 203 §7.2 and §7.3.
|
|
25
25
|
import { sha3_256Hash } from './indcpa.js';
|
|
26
26
|
import { constantTimeEqual } from '../utils.js';
|
|
27
27
|
/**
|
|
28
|
-
* Encapsulation key check
|
|
28
|
+
* Encapsulation key check, FIPS 203 §7.2 (EncapsulationKeyCheck).
|
|
29
29
|
*
|
|
30
30
|
* 1. Length gate: ek.length must equal params.ekBytes.
|
|
31
31
|
* 2. Decode the polyvec portion via ByteDecode₁₂ (polyvec_frombytes). The
|
|
32
|
-
* decoded coefficients are raw 12-bit values in [0, 4095]
|
|
32
|
+
* decoded coefficients are raw 12-bit values in [0, 4095], frombytes
|
|
33
33
|
* does not reduce mod q.
|
|
34
34
|
* 3. Modulus scan: every coefficient must satisfy c < Q = 3329.
|
|
35
35
|
*
|
|
@@ -40,15 +40,15 @@ export function checkEncapsulationKey(kx, params, ek) {
|
|
|
40
40
|
if (ek.length !== params.ekBytes)
|
|
41
41
|
return false;
|
|
42
42
|
const { k } = params;
|
|
43
|
-
const
|
|
43
|
+
const mlkemMem = new Uint8Array(kx.memory.buffer);
|
|
44
44
|
const pkOff = kx.getPkOffset();
|
|
45
45
|
const pvecOff = kx.getPolyvecSlot0();
|
|
46
|
-
|
|
46
|
+
mlkemMem.set(ek.subarray(0, k * 384), pkOff);
|
|
47
47
|
kx.polyvec_frombytes(pvecOff, pkOff, k);
|
|
48
48
|
return kx.polyvec_modulus_check(pvecOff, k) === 0;
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
|
-
* Decapsulation key check
|
|
51
|
+
* Decapsulation key check, FIPS 203 §7.3 (DecapsulationKeyCheck).
|
|
52
52
|
*
|
|
53
53
|
* 1. Length check: dk.length == params.dkBytes
|
|
54
54
|
* 2. Extract embedded ek and H(ek), verify SHA3-256(ek) matches stored H
|
|
Binary file
|
package/dist/p256.wasm
ADDED
|
Binary file
|
package/dist/ratchet/index.d.ts
CHANGED
|
@@ -3,4 +3,6 @@ export { ratchetInit, kemRatchetEncap, kemRatchetDecap } from './root-kdf.js';
|
|
|
3
3
|
export { SkippedKeyStore } from './skipped-key-store.js';
|
|
4
4
|
export { RatchetKeypair } from './ratchet-keypair.js';
|
|
5
5
|
export type { RatchetInitResult, KemEncapResult, KemDecapResult, MlKemLike, RatchetMessageHeader, ResolveHandle, SkippedKeyStoreOpts, } from './types.js';
|
|
6
|
+
import { isInitialized } from '../init.js';
|
|
7
|
+
export { isInitialized };
|
|
6
8
|
export declare function ratchetReady(): boolean;
|
package/dist/ratchet/index.js
CHANGED
|
@@ -27,6 +27,7 @@ export { ratchetInit, kemRatchetEncap, kemRatchetDecap } from './root-kdf.js';
|
|
|
27
27
|
export { SkippedKeyStore } from './skipped-key-store.js';
|
|
28
28
|
export { RatchetKeypair } from './ratchet-keypair.js';
|
|
29
29
|
import { isInitialized } from '../init.js';
|
|
30
|
+
export { isInitialized };
|
|
30
31
|
export function ratchetReady() {
|
|
31
32
|
try {
|
|
32
33
|
return isInitialized('sha2');
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
//
|
|
22
22
|
// src/ts/ratchet/kdf-chain.ts
|
|
23
23
|
//
|
|
24
|
-
// KDFChain
|
|
24
|
+
// KDFChain, stateful symmetric ratchet chain step (spec §5.2, KDF_SCKA_CK).
|
|
25
25
|
// Each step derives a message key and advances the chain key via HKDF-SHA-256.
|
|
26
26
|
import { HKDF_SHA256 } from '../sha2/index.js';
|
|
27
27
|
import { isInitialized } from '../init.js';
|
|
28
28
|
import { wipe, concat, utf8ToBytes } from '../utils.js';
|
|
29
|
-
// Signal Double Ratchet §7.2
|
|
29
|
+
// Signal Double Ratchet §7.2, chain step info string
|
|
30
30
|
const INFO_CHAIN_BYTES = utf8ToBytes('leviathan-ratchet-v1 Chain Step');
|
|
31
31
|
const ZERO_SALT = new Uint8Array(32);
|
|
32
32
|
export class KDFChain {
|
|
@@ -48,7 +48,7 @@ export class KDFChain {
|
|
|
48
48
|
if (this._n >= Number.MAX_SAFE_INTEGER)
|
|
49
49
|
throw new RangeError('KDFChain: counter exceeds maximum safe integer');
|
|
50
50
|
const nextN = this._n + 1;
|
|
51
|
-
// Encode counter as big-endian uint64
|
|
51
|
+
// Encode counter as big-endian uint64, two u32 calls, no BigInt
|
|
52
52
|
const ctrBuf = new Uint8Array(8);
|
|
53
53
|
const dv = new DataView(ctrBuf.buffer);
|
|
54
54
|
dv.setUint32(0, Math.floor(nextN / 0x100000000), false);
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
//
|
|
22
22
|
// src/ts/ratchet/ratchet-keypair.ts
|
|
23
23
|
//
|
|
24
|
-
// RatchetKeypair
|
|
24
|
+
// RatchetKeypair, single-use ek/dk lifecycle for one KEM ratchet step.
|
|
25
25
|
// Enforces the DR spec requirement that both parties rotate encapsulation
|
|
26
26
|
// keys after each KEM ratchet step.
|
|
27
27
|
import { wipe } from '../utils.js';
|
|
@@ -37,7 +37,7 @@ export class RatchetKeypair {
|
|
|
37
37
|
this._used = false;
|
|
38
38
|
}
|
|
39
39
|
// Decapsulate using the stored dk. May only be called once per instance.
|
|
40
|
-
// Wipes the dk immediately after decap
|
|
40
|
+
// Wipes the dk immediately after decap, the dk never leaves this class.
|
|
41
41
|
// The stored ek is passed as `ownEk` so both sides bind the identical
|
|
42
42
|
// (peerEk, kemCt) pair into the HKDF info string.
|
|
43
43
|
decap(kem, rk, kemCt, context) {
|
package/dist/ratchet/root-kdf.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
import { HKDF_SHA256 } from '../sha2/index.js';
|
|
27
27
|
import { isInitialized } from '../init.js';
|
|
28
28
|
import { wipe, concat, utf8ToBytes } from '../utils.js';
|
|
29
|
-
// Signal Double Ratchet §7.2
|
|
29
|
+
// Signal Double Ratchet §7.2, info strings
|
|
30
30
|
const INFO_INIT = utf8ToBytes('leviathan-ratchet-v1 Chain Start');
|
|
31
31
|
const INFO_ROOT = utf8ToBytes('leviathan-ratchet-v1 Chain Add Epoch');
|
|
32
32
|
// INFO_CHAIN ('leviathan-ratchet-v1 Chain Step') is used in kdf-chain.ts
|
|
@@ -52,7 +52,7 @@ function kdfRoot(secret, salt, info) {
|
|
|
52
52
|
h.dispose();
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
// u32 big-endian length prefix
|
|
55
|
+
// u32 big-endian length prefix, same convention as serpent/cipher-suite.ts AAD encoding.
|
|
56
56
|
function u32be(n) {
|
|
57
57
|
if (!Number.isInteger(n) || n < 0 || n > 0xFFFFFFFF)
|
|
58
58
|
throw new RangeError(`u32be: n must be an integer in [0, 0xFFFFFFFF] (got ${n})`);
|
|
@@ -60,7 +60,7 @@ function u32be(n) {
|
|
|
60
60
|
new DataView(b.buffer).setUint32(0, n, false);
|
|
61
61
|
return b;
|
|
62
62
|
}
|
|
63
|
-
// KEM ratchet info
|
|
63
|
+
// KEM ratchet info, binds peerEk, kemCt, and context with u32be length prefixes.
|
|
64
64
|
// Defense-in-depth: the HKDF output is tied to the exact (peerEk, kemCt, context)
|
|
65
65
|
// tuple used, not just the KEM shared secret. An attacker who substitutes any of
|
|
66
66
|
// these inputs derives a different chain key trio, regardless of the KEM transcript.
|
|
@@ -68,7 +68,7 @@ function buildRootInfo(peerEk, kemCt, context) {
|
|
|
68
68
|
const ctxBytes = context ?? new Uint8Array(0);
|
|
69
69
|
return concat(INFO_ROOT, u32be(peerEk.length), peerEk, u32be(kemCt.length), kemCt, u32be(ctxBytes.length), ctxBytes);
|
|
70
70
|
}
|
|
71
|
-
// KDF_SCKA_INIT
|
|
71
|
+
// KDF_SCKA_INIT, spec §7.2
|
|
72
72
|
// Derives initial root key, send chain key, and receive chain key from a
|
|
73
73
|
// shared secret sk. Optional context bytes are appended to the info string.
|
|
74
74
|
export function ratchetInit(sk, context) {
|
|
@@ -80,7 +80,7 @@ export function ratchetInit(sk, context) {
|
|
|
80
80
|
const { nextRootKey, sendChainKey, recvChainKey } = kdfRoot(sk, ZERO_SALT, info);
|
|
81
81
|
return { nextRootKey, sendChainKey, recvChainKey };
|
|
82
82
|
}
|
|
83
|
-
// KDF_SCKA_RK
|
|
83
|
+
// KDF_SCKA_RK, encapsulation side (spec §7.2)
|
|
84
84
|
// Generates a fresh KEM ciphertext, derives next epoch keys from the shared secret.
|
|
85
85
|
// `peerEk` and `kemCt` are bound into the HKDF info string (defense-in-depth).
|
|
86
86
|
export function kemRatchetEncap(kem, rk, peerEk, context) {
|
|
@@ -98,11 +98,11 @@ export function kemRatchetEncap(kem, rk, peerEk, context) {
|
|
|
98
98
|
wipe(sharedSecret);
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
-
// KDF_SCKA_RK
|
|
101
|
+
// KDF_SCKA_RK, decapsulation side (spec §7.2)
|
|
102
102
|
// Recovers the shared secret from the KEM ciphertext, derives next epoch keys.
|
|
103
103
|
// The chain key slots are swapped relative to the encap side: what the KDF
|
|
104
104
|
// labels 'sendChainKey' (A→B direction) becomes the decap side's recvChainKey,
|
|
105
|
-
// and vice versa
|
|
105
|
+
// and vice versa, Alice's send IS Bob's receive.
|
|
106
106
|
//
|
|
107
107
|
// `ownEk` is the local party's encapsulation key (the same public key the
|
|
108
108
|
// encap side targeted as `peerEk`). It must be passed explicitly so both
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
//
|
|
22
22
|
// src/ts/ratchet/skipped-key-store.ts
|
|
23
23
|
//
|
|
24
|
-
// SkippedKeyStore
|
|
24
|
+
// SkippedKeyStore, MKSKIPPED cache for a single KDFChain (DR spec §3.2/§3.5).
|
|
25
25
|
// Manages out-of-order and skipped message key storage with transactional
|
|
26
26
|
// resolve via ResolveHandle. Split budgets: maxCacheSize bounds memory;
|
|
27
27
|
// maxSkipPerResolve bounds per-message HKDF work (DoS mitigation).
|
|
@@ -46,7 +46,7 @@ export class SkippedKeyStore {
|
|
|
46
46
|
this._maxCacheSize = cache;
|
|
47
47
|
this._maxSkipPerResolve = skip;
|
|
48
48
|
}
|
|
49
|
-
// O(1) eviction
|
|
49
|
+
// O(1) eviction, Map iteration is insertion order, and keys are inserted
|
|
50
50
|
// in strictly increasing counter order, so the first key yielded IS the
|
|
51
51
|
// oldest (lowest counter).
|
|
52
52
|
_evictOldest() {
|
|
@@ -59,8 +59,8 @@ export class SkippedKeyStore {
|
|
|
59
59
|
this._store.delete(oldest);
|
|
60
60
|
}
|
|
61
61
|
// Resolve a message key for the given counter. Returns a ResolveHandle the
|
|
62
|
-
// caller settles via commit() (success
|
|
63
|
-
// (failure
|
|
62
|
+
// caller settles via commit() (success, key wiped) or rollback()
|
|
63
|
+
// (failure, key returned to the store so a later legitimate message at
|
|
64
64
|
// the same counter can still decrypt).
|
|
65
65
|
//
|
|
66
66
|
// Three paths based on counter vs chain.n:
|
package/dist/ratchet/types.d.ts
CHANGED
|
@@ -21,16 +21,21 @@
|
|
|
21
21
|
//
|
|
22
22
|
// src/ts/serpent/cipher-suite.ts
|
|
23
23
|
//
|
|
24
|
-
// SerpentCipher
|
|
24
|
+
// SerpentCipher, CipherSuite implementation for the STREAM construction.
|
|
25
25
|
// 3-key HKDF derivation, HMAC-derived CBC IV, Serpent-CBC + HMAC-SHA-256.
|
|
26
26
|
// Verify-then-decrypt ordering prevents padding oracle attacks (Vaudenay 2002).
|
|
27
|
+
//
|
|
28
|
+
// Salamander immunity: HMAC-SHA-256 is key-committing under SHA-256 collision
|
|
29
|
+
// resistance, so Serpent suites need no separate commitment in the preamble
|
|
30
|
+
// (commitmentSize: 0). Two distinct master keys cannot produce a tag that
|
|
31
|
+
// validates under both for the same chunk.
|
|
27
32
|
import { HKDF_SHA256 } from '../sha2/index.js';
|
|
28
33
|
import { constantTimeEqual, wipe, concat, randomBytes } from '../utils.js';
|
|
29
34
|
import { AuthenticationError } from '../errors.js';
|
|
30
35
|
import { getInstance, _assertNotOwned } from '../init.js';
|
|
31
36
|
import { hmacSha256, cbcEncryptChunk, cbcDecryptChunk, } from './shared-ops.js';
|
|
32
37
|
import { WORKER_SOURCE } from '../embedded/serpent-pool-worker.js';
|
|
33
|
-
const INFO = new TextEncoder().encode('serpent-sealstream-
|
|
38
|
+
const INFO = new TextEncoder().encode('serpent-sealstream-v3');
|
|
34
39
|
/**
|
|
35
40
|
* `CipherSuite` implementation for the stream construction using Serpent-256.
|
|
36
41
|
*
|
|
@@ -44,9 +49,10 @@ const INFO = new TextEncoder().encode('serpent-sealstream-v2');
|
|
|
44
49
|
export const SerpentCipher = {
|
|
45
50
|
formatEnum: 0x02,
|
|
46
51
|
formatName: 'serpent',
|
|
47
|
-
hkdfInfo: 'serpent-sealstream-
|
|
52
|
+
hkdfInfo: 'serpent-sealstream-v3',
|
|
48
53
|
keySize: 32,
|
|
49
54
|
kemCtSize: 0,
|
|
55
|
+
commitmentSize: 0,
|
|
50
56
|
tagSize: 32,
|
|
51
57
|
padded: true,
|
|
52
58
|
wasmChunkSize: 65552, // src/asm/serpent/buffers.ts CHUNK_SIZE (65536 + 16 PKCS7 max overhead)
|
|
@@ -62,10 +68,15 @@ export const SerpentCipher = {
|
|
|
62
68
|
* @param nonce Stream nonce (16 bytes minimum)
|
|
63
69
|
* @returns `DerivedKeys` holding the 96-byte material
|
|
64
70
|
*/
|
|
65
|
-
deriveKeys(masterKey, nonce, _kemCt) {
|
|
71
|
+
deriveKeys(masterKey, nonce, _kemCt, _header) {
|
|
66
72
|
const hkdf = new HKDF_SHA256();
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
let derived;
|
|
74
|
+
try {
|
|
75
|
+
derived = hkdf.derive(masterKey, nonce, INFO, 96);
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
hkdf.dispose();
|
|
79
|
+
}
|
|
69
80
|
// bytes[0:32]=enc_key, bytes[32:64]=mac_key, bytes[64:96]=iv_key
|
|
70
81
|
return { bytes: derived };
|
|
71
82
|
},
|
|
@@ -113,7 +124,7 @@ export const SerpentCipher = {
|
|
|
113
124
|
wipe(iv);
|
|
114
125
|
if (tagInput)
|
|
115
126
|
wipe(tagInput);
|
|
116
|
-
// No hmac/cbc instance to dispose
|
|
127
|
+
// No hmac/cbc instance to dispose, shared-ops functions are instance-free.
|
|
117
128
|
}
|
|
118
129
|
},
|
|
119
130
|
/**
|
|
@@ -121,7 +132,7 @@ export const SerpentCipher = {
|
|
|
121
132
|
* to prevent padding oracle attacks (Vaudenay 2002). Throws
|
|
122
133
|
* `AuthenticationError` on tag mismatch.
|
|
123
134
|
* @param keys Derived keys from `deriveKeys`
|
|
124
|
-
* @param counterNonce Per-chunk nonce
|
|
135
|
+
* @param counterNonce Per-chunk nonce, must match the value used by `sealChunk`
|
|
125
136
|
* @param chunk Ciphertext || 32-byte HMAC tag
|
|
126
137
|
* @param aad Optional additional authenticated data
|
|
127
138
|
* @returns Plaintext with PKCS7 padding removed
|
|
@@ -189,18 +200,10 @@ export const SerpentCipher = {
|
|
|
189
200
|
* @returns Newly constructed `Worker` instance
|
|
190
201
|
*/
|
|
191
202
|
createPoolWorker() {
|
|
192
|
-
//
|
|
193
|
-
// Avoids the syntactic `new Worker(new URL(..., import.meta.url))`
|
|
194
|
-
// pattern that triggers eager worker-chunk emission in Vite's
|
|
195
|
-
// transform hook (issue.md). Classic worker via blob URL —
|
|
196
|
-
// module workers fail on file:// in Chromium (issue2.md).
|
|
203
|
+
// See docs/architecture.md#pool-worker-spawn-pattern.
|
|
197
204
|
const blob = new Blob([WORKER_SOURCE], { type: 'application/javascript' });
|
|
198
205
|
const url = URL.createObjectURL(blob);
|
|
199
206
|
const w = new Worker(url);
|
|
200
|
-
// Worker spec fetches the URL synchronously at construction. Revoke
|
|
201
|
-
// in a macrotask so the spawn completes first; releases the Blob
|
|
202
|
-
// (~5 KB per spawn × N workers) instead of leaking it for the
|
|
203
|
-
// document's lifetime.
|
|
204
207
|
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
205
208
|
return w;
|
|
206
209
|
},
|
|
@@ -6,7 +6,7 @@ import type { Generator } from '../types.js';
|
|
|
6
6
|
* one block of pseudorandom output. Practical Cryptography (Ferguson &
|
|
7
7
|
* Schneier, 2003) §9.4.
|
|
8
8
|
*
|
|
9
|
-
* Pass to `Fortuna.create({ generator: SerpentGenerator, ... })
|
|
9
|
+
* Pass to `Fortuna.create({ generator: SerpentGenerator, ... })`, do not
|
|
10
10
|
* call `generate()` directly outside of Fortuna.
|
|
11
11
|
*/
|
|
12
12
|
export declare const SerpentGenerator: Generator;
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
//
|
|
22
22
|
// src/ts/serpent/generator.ts
|
|
23
23
|
//
|
|
24
|
-
// Practical Cryptography (Ferguson & Schneier, 2003) §9.4
|
|
24
|
+
// Practical Cryptography (Ferguson & Schneier, 2003) §9.4, generator
|
|
25
25
|
// Serpent-256 ECB counter-mode PRF for Fortuna's generator slot.
|
|
26
26
|
import { _assertNotOwned, getInstance } from '../init.js';
|
|
27
27
|
import { wipe } from '../utils.js';
|
|
@@ -32,7 +32,7 @@ import { wipe } from '../utils.js';
|
|
|
32
32
|
* one block of pseudorandom output. Practical Cryptography (Ferguson &
|
|
33
33
|
* Schneier, 2003) §9.4.
|
|
34
34
|
*
|
|
35
|
-
* Pass to `Fortuna.create({ generator: SerpentGenerator, ... })
|
|
35
|
+
* Pass to `Fortuna.create({ generator: SerpentGenerator, ... })`, do not
|
|
36
36
|
* call `generate()` directly outside of Fortuna.
|
|
37
37
|
*/
|
|
38
38
|
export const SerpentGenerator = {
|
package/dist/serpent/index.d.ts
CHANGED
|
@@ -2,13 +2,14 @@ import type { WasmSource } from '../wasm-source.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Load and initialise the Serpent WASM module from `source`.
|
|
4
4
|
* Must be called before constructing any Serpent class.
|
|
5
|
-
* @param source WASM binary
|
|
5
|
+
* @param source WASM binary, gzip+base64 string, URL, ArrayBuffer, Uint8Array,
|
|
6
6
|
* pre-compiled WebAssembly.Module, Response, or Promise<Response>
|
|
7
7
|
*/
|
|
8
8
|
export declare function serpentInit(source: WasmSource): Promise<void>;
|
|
9
9
|
export type { WasmSource };
|
|
10
|
+
export { isInitialized } from '../init.js';
|
|
10
11
|
/**
|
|
11
|
-
* Low-level Serpent-256 block cipher
|
|
12
|
+
* Low-level Serpent-256 block cipher, raw ECB encrypt/decrypt.
|
|
12
13
|
*
|
|
13
14
|
* Atomic (stateless) class: each method call is independent.
|
|
14
15
|
* Does not hold exclusive module access; cannot be used while a stateful
|
|
@@ -63,23 +64,23 @@ export declare class SerpentCtr {
|
|
|
63
64
|
* Load key and nonce into WASM state and reset the block counter to 0.
|
|
64
65
|
* Must be called before each message.
|
|
65
66
|
* @param key 16, 24, or 32 bytes
|
|
66
|
-
* @param nonce 16 bytes
|
|
67
|
+
* @param nonce 16 bytes, must be unique per (key, message)
|
|
67
68
|
*/
|
|
68
69
|
beginEncrypt(key: Uint8Array, nonce: Uint8Array): void;
|
|
69
70
|
/**
|
|
70
71
|
* XOR `chunk` with the next keystream block(s). Counter advances automatically.
|
|
71
|
-
* @param chunk Plaintext chunk
|
|
72
|
+
* @param chunk Plaintext chunk, must not exceed WASM CHUNK_SIZE
|
|
72
73
|
* @returns Ciphertext of the same length
|
|
73
74
|
*/
|
|
74
75
|
encryptChunk(chunk: Uint8Array): Uint8Array;
|
|
75
76
|
/**
|
|
76
|
-
* Alias for `beginEncrypt
|
|
77
|
+
* Alias for `beginEncrypt`, CTR mode is symmetric.
|
|
77
78
|
* @param key 16, 24, or 32 bytes
|
|
78
|
-
* @param nonce 16 bytes
|
|
79
|
+
* @param nonce 16 bytes, must match the value used to encrypt
|
|
79
80
|
*/
|
|
80
81
|
beginDecrypt(key: Uint8Array, nonce: Uint8Array): void;
|
|
81
82
|
/**
|
|
82
|
-
* Alias for `encryptChunk
|
|
83
|
+
* Alias for `encryptChunk`, CTR mode is symmetric.
|
|
83
84
|
* @param chunk Ciphertext chunk
|
|
84
85
|
* @returns Plaintext of the same length
|
|
85
86
|
*/
|
package/dist/serpent/index.js
CHANGED
|
@@ -22,24 +22,25 @@
|
|
|
22
22
|
// src/ts/serpent/index.ts
|
|
23
23
|
//
|
|
24
24
|
// Public API classes for the Serpent-256 WASM module.
|
|
25
|
-
// Uses the init() module cache
|
|
25
|
+
// Uses the init() module cache, call serpentInit(source) before constructing.
|
|
26
26
|
import { getInstance, initModule, _acquireModule, _releaseModule, _assertNotOwned } from '../init.js';
|
|
27
27
|
/**
|
|
28
28
|
* Load and initialise the Serpent WASM module from `source`.
|
|
29
29
|
* Must be called before constructing any Serpent class.
|
|
30
|
-
* @param source WASM binary
|
|
30
|
+
* @param source WASM binary, gzip+base64 string, URL, ArrayBuffer, Uint8Array,
|
|
31
31
|
* pre-compiled WebAssembly.Module, Response, or Promise<Response>
|
|
32
32
|
*/
|
|
33
33
|
export async function serpentInit(source) {
|
|
34
34
|
return initModule('serpent', source);
|
|
35
35
|
}
|
|
36
|
+
export { isInitialized } from '../init.js';
|
|
36
37
|
/** Returns the raw serpent WASM export object. @internal */
|
|
37
38
|
function getExports() {
|
|
38
39
|
return getInstance('serpent').exports;
|
|
39
40
|
}
|
|
40
41
|
// ── Serpent ─────────────────────────────────────────────────────────────────
|
|
41
42
|
/**
|
|
42
|
-
* Low-level Serpent-256 block cipher
|
|
43
|
+
* Low-level Serpent-256 block cipher, raw ECB encrypt/decrypt.
|
|
43
44
|
*
|
|
44
45
|
* Atomic (stateless) class: each method call is independent.
|
|
45
46
|
* Does not hold exclusive module access; cannot be used while a stateful
|
|
@@ -62,8 +63,10 @@ export class Serpent {
|
|
|
62
63
|
throw new RangeError(`key must be 16, 24, or 32 bytes (got ${key.length})`);
|
|
63
64
|
const mem = new Uint8Array(this.x.memory.buffer);
|
|
64
65
|
mem.set(key, this.x.getKeyOffset());
|
|
65
|
-
if (this.x.loadKey(key.length) !== 0)
|
|
66
|
+
if (this.x.loadKey(key.length) !== 0) {
|
|
67
|
+
this.x.wipeBuffers();
|
|
66
68
|
throw new Error('loadKey failed');
|
|
69
|
+
}
|
|
67
70
|
}
|
|
68
71
|
/**
|
|
69
72
|
* Encrypt one 128-bit block with the previously loaded key schedule.
|
|
@@ -123,7 +126,7 @@ export class SerpentCtr {
|
|
|
123
126
|
_tok;
|
|
124
127
|
constructor(opts) {
|
|
125
128
|
if (!opts?.dangerUnauthenticated) {
|
|
126
|
-
throw new Error('leviathan-crypto: SerpentCtr is unauthenticated
|
|
129
|
+
throw new Error('leviathan-crypto: SerpentCtr is unauthenticated, use Seal with SerpentCipher instead. ' +
|
|
127
130
|
'To use SerpentCtr directly, pass { dangerUnauthenticated: true }.');
|
|
128
131
|
}
|
|
129
132
|
this.x = getExports();
|
|
@@ -133,7 +136,7 @@ export class SerpentCtr {
|
|
|
133
136
|
* Load key and nonce into WASM state and reset the block counter to 0.
|
|
134
137
|
* Must be called before each message.
|
|
135
138
|
* @param key 16, 24, or 32 bytes
|
|
136
|
-
* @param nonce 16 bytes
|
|
139
|
+
* @param nonce 16 bytes, must be unique per (key, message)
|
|
137
140
|
*/
|
|
138
141
|
beginEncrypt(key, nonce) {
|
|
139
142
|
if (this._tok === undefined)
|
|
@@ -145,12 +148,15 @@ export class SerpentCtr {
|
|
|
145
148
|
const mem = new Uint8Array(this.x.memory.buffer);
|
|
146
149
|
mem.set(key, this.x.getKeyOffset());
|
|
147
150
|
mem.set(nonce, this.x.getNonceOffset());
|
|
148
|
-
this.x.loadKey(key.length)
|
|
151
|
+
if (this.x.loadKey(key.length) !== 0) {
|
|
152
|
+
this.x.wipeBuffers();
|
|
153
|
+
throw new Error('SerpentCtr: loadKey failed');
|
|
154
|
+
}
|
|
149
155
|
this.x.resetCounter();
|
|
150
156
|
}
|
|
151
157
|
/**
|
|
152
158
|
* XOR `chunk` with the next keystream block(s). Counter advances automatically.
|
|
153
|
-
* @param chunk Plaintext chunk
|
|
159
|
+
* @param chunk Plaintext chunk, must not exceed WASM CHUNK_SIZE
|
|
154
160
|
* @returns Ciphertext of the same length
|
|
155
161
|
*/
|
|
156
162
|
encryptChunk(chunk) {
|
|
@@ -158,7 +164,7 @@ export class SerpentCtr {
|
|
|
158
164
|
throw new Error('SerpentCtr: instance has been disposed');
|
|
159
165
|
const maxChunk = this.x.getChunkSize();
|
|
160
166
|
if (chunk.length > maxChunk)
|
|
161
|
-
throw new RangeError(`chunk exceeds maximum size of ${maxChunk} bytes
|
|
167
|
+
throw new RangeError(`chunk exceeds maximum size of ${maxChunk} bytes, split into smaller chunks`);
|
|
162
168
|
const mem = new Uint8Array(this.x.memory.buffer);
|
|
163
169
|
const ptOff = this.x.getChunkPtOffset();
|
|
164
170
|
const ctOff = this.x.getChunkCtOffset();
|
|
@@ -167,15 +173,15 @@ export class SerpentCtr {
|
|
|
167
173
|
return mem.slice(ctOff, ctOff + chunk.length);
|
|
168
174
|
}
|
|
169
175
|
/**
|
|
170
|
-
* Alias for `beginEncrypt
|
|
176
|
+
* Alias for `beginEncrypt`, CTR mode is symmetric.
|
|
171
177
|
* @param key 16, 24, or 32 bytes
|
|
172
|
-
* @param nonce 16 bytes
|
|
178
|
+
* @param nonce 16 bytes, must match the value used to encrypt
|
|
173
179
|
*/
|
|
174
180
|
beginDecrypt(key, nonce) {
|
|
175
181
|
this.beginEncrypt(key, nonce);
|
|
176
182
|
}
|
|
177
183
|
/**
|
|
178
|
-
* Alias for `encryptChunk
|
|
184
|
+
* Alias for `encryptChunk`, CTR mode is symmetric.
|
|
179
185
|
* @param chunk Ciphertext chunk
|
|
180
186
|
* @returns Plaintext of the same length
|
|
181
187
|
*/
|
|
@@ -202,18 +208,3 @@ export { AuthenticationError } from '../errors.js';
|
|
|
202
208
|
export { SerpentCipher } from './cipher-suite.js';
|
|
203
209
|
// ── SerpentGenerator ────────────────────────────────────────────────────────
|
|
204
210
|
export { SerpentGenerator } from './generator.js';
|
|
205
|
-
// ── Ready check ─────────────────────────────────────────────────────────────
|
|
206
|
-
/**
|
|
207
|
-
* Returns `true` if the serpent WASM module has been initialised.
|
|
208
|
-
* Used by tests and internal guards; not part of the public API.
|
|
209
|
-
* @internal
|
|
210
|
-
*/
|
|
211
|
-
export function _serpentReady() {
|
|
212
|
-
try {
|
|
213
|
-
getInstance('serpent');
|
|
214
|
-
return true;
|
|
215
|
-
}
|
|
216
|
-
catch {
|
|
217
|
-
return false;
|
|
218
|
-
}
|
|
219
|
-
}
|