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
|
@@ -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;
|
|
@@ -63,6 +63,7 @@ export interface KyberExports {
|
|
|
63
63
|
polyvec_reduce: (pvOffset: number, k: number) => void;
|
|
64
64
|
polyvec_add: (rOffset: number, aOffset: number, bOffset: number, k: number) => void;
|
|
65
65
|
polyvec_basemul_acc_montgomery: (rOffset: number, aOffset: number, bOffset: number, k: number) => void;
|
|
66
|
+
polyvec_modulus_check: (pvOffset: number, k: number) => number;
|
|
66
67
|
rej_uniform: (polyOffset: number, ctrStart: number, bufOffset: number, buflen: number) => number;
|
|
67
68
|
ct_verify: (aOffset: number, bOffset: number, len: number) => number;
|
|
68
69
|
ct_cmov: (rOffset: number, xOffset: number, len: number, b: number) => void;
|
|
@@ -88,11 +89,11 @@ export interface Sha3Exports {
|
|
|
88
89
|
shakeSqueezeBlock: () => void;
|
|
89
90
|
wipeBuffers: () => void;
|
|
90
91
|
}
|
|
91
|
-
export interface
|
|
92
|
+
export interface MlKemKeyPair {
|
|
92
93
|
encapsulationKey: Uint8Array;
|
|
93
94
|
decapsulationKey: Uint8Array;
|
|
94
95
|
}
|
|
95
|
-
export interface
|
|
96
|
+
export interface MlKemEncapsulation {
|
|
96
97
|
ciphertext: Uint8Array;
|
|
97
98
|
sharedSecret: Uint8Array;
|
|
98
99
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { MlKemExports, Sha3Exports } from './types.js';
|
|
2
|
+
import type { MlKemParams } from './params.js';
|
|
3
|
+
/**
|
|
4
|
+
* Encapsulation key check, FIPS 203 §7.2 (EncapsulationKeyCheck).
|
|
5
|
+
*
|
|
6
|
+
* 1. Length gate: ek.length must equal params.ekBytes.
|
|
7
|
+
* 2. Decode the polyvec portion via ByteDecode₁₂ (polyvec_frombytes). The
|
|
8
|
+
* decoded coefficients are raw 12-bit values in [0, 4095], frombytes
|
|
9
|
+
* does not reduce mod q.
|
|
10
|
+
* 3. Modulus scan: every coefficient must satisfy c < Q = 3329.
|
|
11
|
+
*
|
|
12
|
+
* Returns true iff both gates pass. The seed ρ (final 32 bytes of ek) is
|
|
13
|
+
* not checked; any 32-byte value is a valid ρ per FIPS 203.
|
|
14
|
+
*/
|
|
15
|
+
export declare function checkEncapsulationKey(kx: MlKemExports, params: MlKemParams, ek: Uint8Array): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Decapsulation key check, FIPS 203 §7.3 (DecapsulationKeyCheck).
|
|
18
|
+
*
|
|
19
|
+
* 1. Length check: dk.length == params.dkBytes
|
|
20
|
+
* 2. Extract embedded ek and H(ek), verify SHA3-256(ek) matches stored H
|
|
21
|
+
* 3. Also run checkEncapsulationKey on the embedded ek
|
|
22
|
+
*/
|
|
23
|
+
export declare function checkDecapsulationKey(kx: MlKemExports, sx: Sha3Exports, params: MlKemParams, dk: Uint8Array): boolean;
|
|
@@ -19,37 +19,36 @@
|
|
|
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
|
-
* 1. Length
|
|
31
|
-
* 2.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
30
|
+
* 1. Length gate: ek.length must equal params.ekBytes.
|
|
31
|
+
* 2. Decode the polyvec portion via ByteDecode₁₂ (polyvec_frombytes). The
|
|
32
|
+
* decoded coefficients are raw 12-bit values in [0, 4095], frombytes
|
|
33
|
+
* does not reduce mod q.
|
|
34
|
+
* 3. Modulus scan: every coefficient must satisfy c < Q = 3329.
|
|
35
|
+
*
|
|
36
|
+
* Returns true iff both gates pass. The seed ρ (final 32 bytes of ek) is
|
|
37
|
+
* not checked; any 32-byte value is a valid ρ per FIPS 203.
|
|
34
38
|
*/
|
|
35
39
|
export function checkEncapsulationKey(kx, params, ek) {
|
|
36
40
|
if (ek.length !== params.ekBytes)
|
|
37
41
|
return false;
|
|
38
42
|
const { k } = params;
|
|
39
|
-
const
|
|
43
|
+
const mlkemMem = new Uint8Array(kx.memory.buffer);
|
|
40
44
|
const pkOff = kx.getPkOffset();
|
|
41
|
-
const skOff = kx.getSkOffset();
|
|
42
45
|
const pvecOff = kx.getPolyvecSlot0();
|
|
43
|
-
|
|
44
|
-
kyberMem.set(ek.subarray(0, k * 384), pkOff);
|
|
46
|
+
mlkemMem.set(ek.subarray(0, k * 384), pkOff);
|
|
45
47
|
kx.polyvec_frombytes(pvecOff, pkOff, k);
|
|
46
|
-
kx.
|
|
47
|
-
// orig is at pkOff (written above); reEnc is at skOff (polyvec_tobytes output)
|
|
48
|
-
const mismatch = kx.ct_verify(pkOff, skOff, k * 384);
|
|
49
|
-
return mismatch === 0;
|
|
48
|
+
return kx.polyvec_modulus_check(pvecOff, k) === 0;
|
|
50
49
|
}
|
|
51
50
|
/**
|
|
52
|
-
* Decapsulation key check
|
|
51
|
+
* Decapsulation key check, FIPS 203 §7.3 (DecapsulationKeyCheck).
|
|
53
52
|
*
|
|
54
53
|
* 1. Length check: dk.length == params.dkBytes
|
|
55
54
|
* 2. Extract embedded ek and H(ek), verify SHA3-256(ek) matches stored H
|
|
@@ -61,8 +60,13 @@ export function checkDecapsulationKey(kx, sx, params, dk) {
|
|
|
61
60
|
const { skCpaBytes, ekBytes } = params;
|
|
62
61
|
const ek = dk.slice(skCpaBytes, skCpaBytes + ekBytes);
|
|
63
62
|
const h = dk.slice(skCpaBytes + ekBytes, skCpaBytes + ekBytes + 32);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
try {
|
|
64
|
+
const hComputed = sha3_256Hash(sx, ek);
|
|
65
|
+
if (!constantTimeEqual(hComputed, h))
|
|
66
|
+
return false;
|
|
67
|
+
return checkEncapsulationKey(kx, params, ek);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
sx.wipeBuffers();
|
|
71
|
+
}
|
|
68
72
|
}
|
|
Binary file
|
package/dist/p256.wasm
ADDED
|
Binary file
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { KDFChain } from './kdf-chain.js';
|
|
2
|
+
export { ratchetInit, kemRatchetEncap, kemRatchetDecap } from './root-kdf.js';
|
|
3
|
+
export { SkippedKeyStore } from './skipped-key-store.js';
|
|
4
|
+
export { RatchetKeypair } from './ratchet-keypair.js';
|
|
5
|
+
export type { RatchetInitResult, KemEncapResult, KemDecapResult, MlKemLike, RatchetMessageHeader, ResolveHandle, SkippedKeyStoreOpts, } from './types.js';
|
|
6
|
+
import { isInitialized } from '../init.js';
|
|
7
|
+
export { isInitialized };
|
|
8
|
+
export declare function ratchetReady(): boolean;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// ▄▄▄▄▄▄▄▄▄▄
|
|
2
|
+
// ▄████████████████████▄▄ ▒ ▄▀▀ ▒ ▒ █ ▄▀▄ ▀█▀ █ ▒ ▄▀▄ █▀▄
|
|
3
|
+
// ▄██████████████████████ ▀████▄ ▓ ▓▀ ▓ ▓ ▓ ▓▄▓ ▓ ▓▀▓ ▓▄▓ ▓ ▓
|
|
4
|
+
// ▄█████████▀▀▀ ▀███████▄▄███████▌ ▀▄ ▀▄▄ ▀▄▀ ▒ ▒ ▒ ▒ ▒ █ ▒ ▒ ▒ █
|
|
5
|
+
// ▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
|
|
6
|
+
// ████████ ███▀▀ ████▀ █▀ █▀ Leviathan Crypto Library
|
|
7
|
+
// ███████▌ ▀██▀ ███
|
|
8
|
+
// ███████ ▀███ ▀██ ▀█▄ Repository & Mirror:
|
|
9
|
+
// ▀██████ ▄▄██ ▀▀ ██▄ github.com/xero/leviathan-crypto
|
|
10
|
+
// ▀█████▄ ▄██▄ ▄▀▄▀ unpkg.com/leviathan-crypto
|
|
11
|
+
// ▀████▄ ▄██▄
|
|
12
|
+
// ▐████ ▐███ Author: xero (https://x-e.ro)
|
|
13
|
+
// ▄▄██████████ ▐███ ▄▄ License: MIT
|
|
14
|
+
// ▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
|
|
15
|
+
// ▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███ This file is provided completely
|
|
16
|
+
// ▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀ free, "as is", and without
|
|
17
|
+
// ████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄ warranty of any kind. The author
|
|
18
|
+
// █████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄████ assumes absolutely no liability
|
|
19
|
+
// ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
|
|
20
|
+
// ▀█████▀▀
|
|
21
|
+
//
|
|
22
|
+
// src/ts/ratchet/index.ts
|
|
23
|
+
//
|
|
24
|
+
// Public barrel for the ratchet module.
|
|
25
|
+
export { KDFChain } from './kdf-chain.js';
|
|
26
|
+
export { ratchetInit, kemRatchetEncap, kemRatchetDecap } from './root-kdf.js';
|
|
27
|
+
export { SkippedKeyStore } from './skipped-key-store.js';
|
|
28
|
+
export { RatchetKeypair } from './ratchet-keypair.js';
|
|
29
|
+
import { isInitialized } from '../init.js';
|
|
30
|
+
export { isInitialized };
|
|
31
|
+
export function ratchetReady() {
|
|
32
|
+
try {
|
|
33
|
+
return isInitialized('sha2');
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// ▄▄▄▄▄▄▄▄▄▄
|
|
2
|
+
// ▄████████████████████▄▄ ▒ ▄▀▀ ▒ ▒ █ ▄▀▄ ▀█▀ █ ▒ ▄▀▄ █▀▄
|
|
3
|
+
// ▄██████████████████████ ▀████▄ ▓ ▓▀ ▓ ▓ ▓ ▓▄▓ ▓ ▓▀▓ ▓▄▓ ▓ ▓
|
|
4
|
+
// ▄█████████▀▀▀ ▀███████▄▄███████▌ ▀▄ ▀▄▄ ▀▄▀ ▒ ▒ ▒ ▒ ▒ █ ▒ ▒ ▒ █
|
|
5
|
+
// ▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
|
|
6
|
+
// ████████ ███▀▀ ████▀ █▀ █▀ Leviathan Crypto Library
|
|
7
|
+
// ███████▌ ▀██▀ ███
|
|
8
|
+
// ███████ ▀███ ▀██ ▀█▄ Repository & Mirror:
|
|
9
|
+
// ▀██████ ▄▄██ ▀▀ ██▄ github.com/xero/leviathan-crypto
|
|
10
|
+
// ▀█████▄ ▄██▄ ▄▀▄▀ unpkg.com/leviathan-crypto
|
|
11
|
+
// ▀████▄ ▄██▄
|
|
12
|
+
// ▐████ ▐███ Author: xero (https://x-e.ro)
|
|
13
|
+
// ▄▄██████████ ▐███ ▄▄ License: MIT
|
|
14
|
+
// ▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
|
|
15
|
+
// ▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███ This file is provided completely
|
|
16
|
+
// ▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀ free, "as is", and without
|
|
17
|
+
// ████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄ warranty of any kind. The author
|
|
18
|
+
// █████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄████ assumes absolutely no liability
|
|
19
|
+
// ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
|
|
20
|
+
// ▀█████▀▀
|
|
21
|
+
//
|
|
22
|
+
// src/ts/ratchet/kdf-chain.ts
|
|
23
|
+
//
|
|
24
|
+
// KDFChain, stateful symmetric ratchet chain step (spec §5.2, KDF_SCKA_CK).
|
|
25
|
+
// Each step derives a message key and advances the chain key via HKDF-SHA-256.
|
|
26
|
+
import { HKDF_SHA256 } from '../sha2/index.js';
|
|
27
|
+
import { isInitialized } from '../init.js';
|
|
28
|
+
import { wipe, concat, utf8ToBytes } from '../utils.js';
|
|
29
|
+
// Signal Double Ratchet §7.2, chain step info string
|
|
30
|
+
const INFO_CHAIN_BYTES = utf8ToBytes('leviathan-ratchet-v1 Chain Step');
|
|
31
|
+
const ZERO_SALT = new Uint8Array(32);
|
|
32
|
+
export class KDFChain {
|
|
33
|
+
_ck;
|
|
34
|
+
_n;
|
|
35
|
+
_disposed;
|
|
36
|
+
constructor(ck) {
|
|
37
|
+
if (!isInitialized('sha2'))
|
|
38
|
+
throw new Error('leviathan-crypto: call init({ sha2: ... }) before using KDFChain');
|
|
39
|
+
if (ck.length !== 32)
|
|
40
|
+
throw new RangeError('KDFChain: ck must be 32 bytes');
|
|
41
|
+
this._ck = ck.slice();
|
|
42
|
+
this._n = 0;
|
|
43
|
+
this._disposed = false;
|
|
44
|
+
}
|
|
45
|
+
step() {
|
|
46
|
+
if (this._disposed)
|
|
47
|
+
throw new Error('KDFChain: instance has been disposed');
|
|
48
|
+
if (this._n >= Number.MAX_SAFE_INTEGER)
|
|
49
|
+
throw new RangeError('KDFChain: counter exceeds maximum safe integer');
|
|
50
|
+
const nextN = this._n + 1;
|
|
51
|
+
// Encode counter as big-endian uint64, two u32 calls, no BigInt
|
|
52
|
+
const ctrBuf = new Uint8Array(8);
|
|
53
|
+
const dv = new DataView(ctrBuf.buffer);
|
|
54
|
+
dv.setUint32(0, Math.floor(nextN / 0x100000000), false);
|
|
55
|
+
dv.setUint32(4, nextN >>> 0, false);
|
|
56
|
+
const info = concat(INFO_CHAIN_BYTES, ctrBuf);
|
|
57
|
+
const h = new HKDF_SHA256();
|
|
58
|
+
try {
|
|
59
|
+
const okm = h.derive(this._ck, ZERO_SALT, info, 64);
|
|
60
|
+
const nextCk = okm.slice(0, 32);
|
|
61
|
+
const msgKey = okm.slice(32, 64);
|
|
62
|
+
wipe(this._ck);
|
|
63
|
+
this._ck = nextCk;
|
|
64
|
+
this._n = nextN;
|
|
65
|
+
wipe(okm);
|
|
66
|
+
return msgKey;
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
h.dispose();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Returns both the message key and the post-step counter atomically.
|
|
73
|
+
// Eliminates the two-step step() + .n read pattern and the off-by-one risk.
|
|
74
|
+
stepWithCounter() {
|
|
75
|
+
const key = this.step();
|
|
76
|
+
return { key, counter: this._n };
|
|
77
|
+
}
|
|
78
|
+
get n() {
|
|
79
|
+
return this._n;
|
|
80
|
+
}
|
|
81
|
+
dispose() {
|
|
82
|
+
wipe(this._ck);
|
|
83
|
+
this._disposed = true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MlKemLike, KemDecapResult } from './types.js';
|
|
2
|
+
export declare class RatchetKeypair {
|
|
3
|
+
readonly ek: Uint8Array;
|
|
4
|
+
private _dk;
|
|
5
|
+
private _used;
|
|
6
|
+
constructor(kem: MlKemLike);
|
|
7
|
+
decap(kem: MlKemLike, rk: Uint8Array, kemCt: Uint8Array, context?: Uint8Array): KemDecapResult;
|
|
8
|
+
dispose(): void;
|
|
9
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// ▄▄▄▄▄▄▄▄▄▄
|
|
2
|
+
// ▄████████████████████▄▄ ▒ ▄▀▀ ▒ ▒ █ ▄▀▄ ▀█▀ █ ▒ ▄▀▄ █▀▄
|
|
3
|
+
// ▄██████████████████████ ▀████▄ ▓ ▓▀ ▓ ▓ ▓ ▓▄▓ ▓ ▓▀▓ ▓▄▓ ▓ ▓
|
|
4
|
+
// ▄█████████▀▀▀ ▀███████▄▄███████▌ ▀▄ ▀▄▄ ▀▄▀ ▒ ▒ ▒ ▒ ▒ █ ▒ ▒ ▒ █
|
|
5
|
+
// ▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
|
|
6
|
+
// ████████ ███▀▀ ████▀ █▀ █▀ Leviathan Crypto Library
|
|
7
|
+
// ███████▌ ▀██▀ ███
|
|
8
|
+
// ███████ ▀███ ▀██ ▀█▄ Repository & Mirror:
|
|
9
|
+
// ▀██████ ▄▄██ ▀▀ ██▄ github.com/xero/leviathan-crypto
|
|
10
|
+
// ▀█████▄ ▄██▄ ▄▀▄▀ unpkg.com/leviathan-crypto
|
|
11
|
+
// ▀████▄ ▄██▄
|
|
12
|
+
// ▐████ ▐███ Author: xero (https://x-e.ro)
|
|
13
|
+
// ▄▄██████████ ▐███ ▄▄ License: MIT
|
|
14
|
+
// ▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
|
|
15
|
+
// ▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███ This file is provided completely
|
|
16
|
+
// ▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀ free, "as is", and without
|
|
17
|
+
// ████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄ warranty of any kind. The author
|
|
18
|
+
// █████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄████ assumes absolutely no liability
|
|
19
|
+
// ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
|
|
20
|
+
// ▀█████▀▀
|
|
21
|
+
//
|
|
22
|
+
// src/ts/ratchet/ratchet-keypair.ts
|
|
23
|
+
//
|
|
24
|
+
// RatchetKeypair, single-use ek/dk lifecycle for one KEM ratchet step.
|
|
25
|
+
// Enforces the DR spec requirement that both parties rotate encapsulation
|
|
26
|
+
// keys after each KEM ratchet step.
|
|
27
|
+
import { wipe } from '../utils.js';
|
|
28
|
+
import { kemRatchetDecap } from './root-kdf.js';
|
|
29
|
+
export class RatchetKeypair {
|
|
30
|
+
ek;
|
|
31
|
+
_dk;
|
|
32
|
+
_used;
|
|
33
|
+
constructor(kem) {
|
|
34
|
+
const { encapsulationKey, decapsulationKey } = kem.keygen();
|
|
35
|
+
this.ek = encapsulationKey;
|
|
36
|
+
this._dk = decapsulationKey;
|
|
37
|
+
this._used = false;
|
|
38
|
+
}
|
|
39
|
+
// Decapsulate using the stored dk. May only be called once per instance.
|
|
40
|
+
// Wipes the dk immediately after decap, the dk never leaves this class.
|
|
41
|
+
// The stored ek is passed as `ownEk` so both sides bind the identical
|
|
42
|
+
// (peerEk, kemCt) pair into the HKDF info string.
|
|
43
|
+
decap(kem, rk, kemCt, context) {
|
|
44
|
+
if (this._used)
|
|
45
|
+
throw new Error('RatchetKeypair: already consumed or disposed. generate a new keypair for the next ratchet step');
|
|
46
|
+
this._used = true;
|
|
47
|
+
try {
|
|
48
|
+
return kemRatchetDecap(kem, rk, this._dk, kemCt, this.ek, context);
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
wipe(this._dk);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Wipe the dk if not already wiped by decap. Idempotent.
|
|
55
|
+
dispose() {
|
|
56
|
+
if (!this._used) {
|
|
57
|
+
wipe(this._dk);
|
|
58
|
+
this._used = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MlKemLike, RatchetInitResult, KemEncapResult, KemDecapResult } from './types.js';
|
|
2
|
+
export declare function ratchetInit(sk: Uint8Array, context?: Uint8Array): RatchetInitResult;
|
|
3
|
+
export declare function kemRatchetEncap(kem: MlKemLike, rk: Uint8Array, peerEk: Uint8Array, context?: Uint8Array): KemEncapResult;
|
|
4
|
+
export declare function kemRatchetDecap(kem: MlKemLike, rk: Uint8Array, dk: Uint8Array, kemCt: Uint8Array, ownEk: Uint8Array, context?: Uint8Array): KemDecapResult;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// ▄▄▄▄▄▄▄▄▄▄
|
|
2
|
+
// ▄████████████████████▄▄ ▒ ▄▀▀ ▒ ▒ █ ▄▀▄ ▀█▀ █ ▒ ▄▀▄ █▀▄
|
|
3
|
+
// ▄██████████████████████ ▀████▄ ▓ ▓▀ ▓ ▓ ▓ ▓▄▓ ▓ ▓▀▓ ▓▄▓ ▓ ▓
|
|
4
|
+
// ▄█████████▀▀▀ ▀███████▄▄███████▌ ▀▄ ▀▄▄ ▀▄▀ ▒ ▒ ▒ ▒ ▒ █ ▒ ▒ ▒ █
|
|
5
|
+
// ▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
|
|
6
|
+
// ████████ ███▀▀ ████▀ █▀ █▀ Leviathan Crypto Library
|
|
7
|
+
// ███████▌ ▀██▀ ███
|
|
8
|
+
// ███████ ▀███ ▀██ ▀█▄ Repository & Mirror:
|
|
9
|
+
// ▀██████ ▄▄██ ▀▀ ██▄ github.com/xero/leviathan-crypto
|
|
10
|
+
// ▀█████▄ ▄██▄ ▄▀▄▀ unpkg.com/leviathan-crypto
|
|
11
|
+
// ▀████▄ ▄██▄
|
|
12
|
+
// ▐████ ▐███ Author: xero (https://x-e.ro)
|
|
13
|
+
// ▄▄██████████ ▐███ ▄▄ License: MIT
|
|
14
|
+
// ▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
|
|
15
|
+
// ▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███ This file is provided completely
|
|
16
|
+
// ▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀ free, "as is", and without
|
|
17
|
+
// ████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄ warranty of any kind. The author
|
|
18
|
+
// █████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄████ assumes absolutely no liability
|
|
19
|
+
// ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
|
|
20
|
+
// ▀█████▀▀
|
|
21
|
+
//
|
|
22
|
+
// src/ts/ratchet/root-kdf.ts
|
|
23
|
+
//
|
|
24
|
+
// Root KDF functions for the Sparse Post-Quantum Ratchet (spec §7.2).
|
|
25
|
+
// Implements KDF_SCKA_INIT (ratchetInit) and KDF_SCKA_RK (kemRatchetEncap/Decap).
|
|
26
|
+
import { HKDF_SHA256 } from '../sha2/index.js';
|
|
27
|
+
import { isInitialized } from '../init.js';
|
|
28
|
+
import { wipe, concat, utf8ToBytes } from '../utils.js';
|
|
29
|
+
// Signal Double Ratchet §7.2, info strings
|
|
30
|
+
const INFO_INIT = utf8ToBytes('leviathan-ratchet-v1 Chain Start');
|
|
31
|
+
const INFO_ROOT = utf8ToBytes('leviathan-ratchet-v1 Chain Add Epoch');
|
|
32
|
+
// INFO_CHAIN ('leviathan-ratchet-v1 Chain Step') is used in kdf-chain.ts
|
|
33
|
+
const ZERO_SALT = new Uint8Array(32);
|
|
34
|
+
// HKDF-SHA-256 root step (KDF_SCKA_INIT and KDF_SCKA_RK share this shape).
|
|
35
|
+
// ikm = 32-byte secret (shared secret or sk)
|
|
36
|
+
// salt = 32-byte salt (zero bytes for init, rk for KEM ratchet)
|
|
37
|
+
// info = protocol info bytes
|
|
38
|
+
// length = 96
|
|
39
|
+
// → { nextRootKey: [0:32], sendChainKey: [32:64], recvChainKey: [64:96] }
|
|
40
|
+
// Wipes okm after slicing.
|
|
41
|
+
function kdfRoot(secret, salt, info) {
|
|
42
|
+
const h = new HKDF_SHA256();
|
|
43
|
+
try {
|
|
44
|
+
const okm = h.derive(secret, salt, info, 96);
|
|
45
|
+
const nextRootKey = okm.slice(0, 32);
|
|
46
|
+
const sendChainKey = okm.slice(32, 64);
|
|
47
|
+
const recvChainKey = okm.slice(64, 96);
|
|
48
|
+
wipe(okm);
|
|
49
|
+
return { nextRootKey, sendChainKey, recvChainKey };
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
h.dispose();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// u32 big-endian length prefix, same convention as serpent/cipher-suite.ts AAD encoding.
|
|
56
|
+
function u32be(n) {
|
|
57
|
+
if (!Number.isInteger(n) || n < 0 || n > 0xFFFFFFFF)
|
|
58
|
+
throw new RangeError(`u32be: n must be an integer in [0, 0xFFFFFFFF] (got ${n})`);
|
|
59
|
+
const b = new Uint8Array(4);
|
|
60
|
+
new DataView(b.buffer).setUint32(0, n, false);
|
|
61
|
+
return b;
|
|
62
|
+
}
|
|
63
|
+
// KEM ratchet info, binds peerEk, kemCt, and context with u32be length prefixes.
|
|
64
|
+
// Defense-in-depth: the HKDF output is tied to the exact (peerEk, kemCt, context)
|
|
65
|
+
// tuple used, not just the KEM shared secret. An attacker who substitutes any of
|
|
66
|
+
// these inputs derives a different chain key trio, regardless of the KEM transcript.
|
|
67
|
+
function buildRootInfo(peerEk, kemCt, context) {
|
|
68
|
+
const ctxBytes = context ?? new Uint8Array(0);
|
|
69
|
+
return concat(INFO_ROOT, u32be(peerEk.length), peerEk, u32be(kemCt.length), kemCt, u32be(ctxBytes.length), ctxBytes);
|
|
70
|
+
}
|
|
71
|
+
// KDF_SCKA_INIT, spec §7.2
|
|
72
|
+
// Derives initial root key, send chain key, and receive chain key from a
|
|
73
|
+
// shared secret sk. Optional context bytes are appended to the info string.
|
|
74
|
+
export function ratchetInit(sk, context) {
|
|
75
|
+
if (!isInitialized('sha2'))
|
|
76
|
+
throw new Error('leviathan-crypto: call init({ sha2: ... }) before using ratchetInit');
|
|
77
|
+
if (sk.length !== 32)
|
|
78
|
+
throw new RangeError('ratchetInit: sk must be 32 bytes');
|
|
79
|
+
const info = context != null && context.length > 0 ? concat(INFO_INIT, context) : INFO_INIT;
|
|
80
|
+
const { nextRootKey, sendChainKey, recvChainKey } = kdfRoot(sk, ZERO_SALT, info);
|
|
81
|
+
return { nextRootKey, sendChainKey, recvChainKey };
|
|
82
|
+
}
|
|
83
|
+
// KDF_SCKA_RK, encapsulation side (spec §7.2)
|
|
84
|
+
// Generates a fresh KEM ciphertext, derives next epoch keys from the shared secret.
|
|
85
|
+
// `peerEk` and `kemCt` are bound into the HKDF info string (defense-in-depth).
|
|
86
|
+
export function kemRatchetEncap(kem, rk, peerEk, context) {
|
|
87
|
+
if (!isInitialized('sha2'))
|
|
88
|
+
throw new Error('leviathan-crypto: call init({ sha2: ... }) before using kemRatchetEncap');
|
|
89
|
+
if (rk.length !== 32)
|
|
90
|
+
throw new RangeError('kemRatchetEncap: rk must be 32 bytes');
|
|
91
|
+
const { ciphertext: kemCt, sharedSecret } = kem.encapsulate(peerEk);
|
|
92
|
+
const info = buildRootInfo(peerEk, kemCt, context);
|
|
93
|
+
try {
|
|
94
|
+
const { nextRootKey, sendChainKey, recvChainKey } = kdfRoot(sharedSecret, rk, info);
|
|
95
|
+
return { nextRootKey, sendChainKey, recvChainKey, kemCt };
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
wipe(sharedSecret);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// KDF_SCKA_RK, decapsulation side (spec §7.2)
|
|
102
|
+
// Recovers the shared secret from the KEM ciphertext, derives next epoch keys.
|
|
103
|
+
// The chain key slots are swapped relative to the encap side: what the KDF
|
|
104
|
+
// labels 'sendChainKey' (A→B direction) becomes the decap side's recvChainKey,
|
|
105
|
+
// and vice versa, Alice's send IS Bob's receive.
|
|
106
|
+
//
|
|
107
|
+
// `ownEk` is the local party's encapsulation key (the same public key the
|
|
108
|
+
// encap side targeted as `peerEk`). It must be passed explicitly so both
|
|
109
|
+
// sides bind the identical (peerEk, kemCt) pair into the HKDF info string.
|
|
110
|
+
export function kemRatchetDecap(kem, rk, dk, kemCt, ownEk, context) {
|
|
111
|
+
if (!isInitialized('sha2'))
|
|
112
|
+
throw new Error('leviathan-crypto: call init({ sha2: ... }) before using kemRatchetDecap');
|
|
113
|
+
if (rk.length !== 32)
|
|
114
|
+
throw new RangeError('kemRatchetDecap: rk must be 32 bytes');
|
|
115
|
+
const sharedSecret = kem.decapsulate(dk, kemCt);
|
|
116
|
+
const info = buildRootInfo(ownEk, kemCt, context);
|
|
117
|
+
try {
|
|
118
|
+
const { nextRootKey, sendChainKey: recvChainKey, recvChainKey: sendChainKey } = kdfRoot(sharedSecret, rk, info);
|
|
119
|
+
return { nextRootKey, sendChainKey, recvChainKey };
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
wipe(sharedSecret);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { KDFChain } from './kdf-chain.js';
|
|
2
|
+
import type { ResolveHandle, SkippedKeyStoreOpts } from './types.js';
|
|
3
|
+
export declare class SkippedKeyStore {
|
|
4
|
+
private _store;
|
|
5
|
+
private _maxCacheSize;
|
|
6
|
+
private _maxSkipPerResolve;
|
|
7
|
+
constructor(opts?: SkippedKeyStoreOpts);
|
|
8
|
+
private _evictOldest;
|
|
9
|
+
resolve(chain: KDFChain, counter: number): ResolveHandle;
|
|
10
|
+
private _makeHandle;
|
|
11
|
+
advanceToBoundary(chain: KDFChain, pn: number): void;
|
|
12
|
+
get size(): number;
|
|
13
|
+
wipeAll(): void;
|
|
14
|
+
}
|