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
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { Hasher } from './tree.js';
|
|
2
|
+
import type { SignatureSuite } from '../sign/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for `MerkleLog.create`. The signing key and pubkey are
|
|
5
|
+
* caller-supplied: `MerkleLog` does not persist keys. For ephemeral
|
|
6
|
+
* use cases the companion factory `MerkleLog.generate` materialises a
|
|
7
|
+
* fresh keypair via `suite.keygen()` and returns the keypair to the
|
|
8
|
+
* caller so it can be persisted externally.
|
|
9
|
+
*/
|
|
10
|
+
export interface MerkleLogCreateOpts {
|
|
11
|
+
/**
|
|
12
|
+
* Log identity, the first line of every checkpoint body. Validated
|
|
13
|
+
* by the inner `SignedLog` (non-empty, no whitespace, no plus
|
|
14
|
+
* characters) per c2sp.org/tlog-checkpoint §Note text.
|
|
15
|
+
*/
|
|
16
|
+
readonly origin: string;
|
|
17
|
+
/** Signing key. Length must equal `suite.skSize`. */
|
|
18
|
+
readonly signingKey: Uint8Array;
|
|
19
|
+
/** Public key. Length must equal `suite.pkSize`. */
|
|
20
|
+
readonly pubkey: Uint8Array;
|
|
21
|
+
/**
|
|
22
|
+
* Hash function the tree uses. `'sha256'` (default) resolves to
|
|
23
|
+
* `Sha256Tree`, `'blake3'` resolves to `Blake3Tree`. SHA-256 is the
|
|
24
|
+
* C2SP-interop choice; the BLAKE3 specialisation is for callers who
|
|
25
|
+
* already invest in BLAKE3 elsewhere in their stack.
|
|
26
|
+
*/
|
|
27
|
+
readonly hashing?: 'sha256' | 'blake3';
|
|
28
|
+
/**
|
|
29
|
+
* Cosignature signature suite. Defaults to `MlDsa44Suite` per the
|
|
30
|
+
* project's PQ-first principle and c2sp.org/tlog-checkpoint
|
|
31
|
+
* §Format's MUST/SHOULD wording on ML-DSA-44. Must be registered
|
|
32
|
+
* in the c2sp.org/tlog-cosignature §Format algorithm-byte registry;
|
|
33
|
+
* other suites raise `MerkleLogError('unsupported-suite')`.
|
|
34
|
+
*/
|
|
35
|
+
readonly suite?: SignatureSuite;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Options for `MerkleLog.generate`. Identical to `MerkleLogCreateOpts`
|
|
39
|
+
* minus the key fields; `generate` materialises a fresh keypair via
|
|
40
|
+
* `suite.keygen()`.
|
|
41
|
+
*/
|
|
42
|
+
export interface MerkleLogGenerateOpts {
|
|
43
|
+
readonly origin: string;
|
|
44
|
+
readonly hashing?: 'sha256' | 'blake3';
|
|
45
|
+
readonly suite?: SignatureSuite;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Memory-backed signed transparency log. The normie producer surface.
|
|
49
|
+
* Construct via `MerkleLog.create` (caller supplies keys) or
|
|
50
|
+
* `MerkleLog.generate` (the class materialises a fresh keypair and
|
|
51
|
+
* returns it). Methods after construction are synchronous; module-init
|
|
52
|
+
* readiness and keygen are the only async steps.
|
|
53
|
+
*
|
|
54
|
+
* Methods delegate to an inner `SignedLog<S>` with a fresh
|
|
55
|
+
* `MemoryStorage` backend. For file or database storage, construct
|
|
56
|
+
* `SignedLog` directly with a custom `MerkleStorage` implementation,
|
|
57
|
+
* see `docs/merkle.md` for the extension pattern.
|
|
58
|
+
*/
|
|
59
|
+
export declare class MerkleLog {
|
|
60
|
+
readonly origin: string;
|
|
61
|
+
readonly hasher: Hasher;
|
|
62
|
+
readonly suite: SignatureSuite;
|
|
63
|
+
private readonly _inner;
|
|
64
|
+
private constructor();
|
|
65
|
+
/**
|
|
66
|
+
* Construct a `MerkleLog` with caller-supplied keys. Validates the
|
|
67
|
+
* suite against the c2sp.org/tlog-cosignature §Format algorithm-byte
|
|
68
|
+
* registry before instantiating the inner `SignedLog`; an
|
|
69
|
+
* unregistered suite raises `MerkleLogError('unsupported-suite')`
|
|
70
|
+
* with a message naming the suite and pointing at the spec.
|
|
71
|
+
*
|
|
72
|
+
* Async to keep the construction surface uniform with `generate`,
|
|
73
|
+
* which is async because `suite.keygen()` may route through async
|
|
74
|
+
* WASM acquisition under load. The hot-path methods (`append`,
|
|
75
|
+
* `head`, `size`, etc.) stay sync per the merkle layer's locked
|
|
76
|
+
* sync invariant.
|
|
77
|
+
*/
|
|
78
|
+
static create(opts: MerkleLogCreateOpts): Promise<MerkleLog>;
|
|
79
|
+
/**
|
|
80
|
+
* Construct a `MerkleLog` with a freshly generated keypair. Returns
|
|
81
|
+
* the log plus the keypair; the caller is responsible for
|
|
82
|
+
* persisting the keys externally if the log outlives the process.
|
|
83
|
+
*
|
|
84
|
+
* The returned `signingKey` is a copy, the log retains its own
|
|
85
|
+
* internal copy that `dispose()` wipes; modifying the returned
|
|
86
|
+
* buffer after construction does not affect the log.
|
|
87
|
+
*/
|
|
88
|
+
static generate(opts: MerkleLogGenerateOpts): Promise<{
|
|
89
|
+
log: MerkleLog;
|
|
90
|
+
signingKey: Uint8Array;
|
|
91
|
+
pubkey: Uint8Array;
|
|
92
|
+
}>;
|
|
93
|
+
/**
|
|
94
|
+
* Append a leaf and return its index, hash, and inclusion proof
|
|
95
|
+
* against the post-append tree size. Delegates to the inner
|
|
96
|
+
* `SignedLog.append`.
|
|
97
|
+
*/
|
|
98
|
+
append(leafBytes: Uint8Array): {
|
|
99
|
+
leafIndex: number;
|
|
100
|
+
leafHash: Uint8Array;
|
|
101
|
+
inclusionProof: Uint8Array[];
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Emit the current checkpoint as a signed-note envelope. Re-signed
|
|
105
|
+
* on every call; the body reflects the live tree size and root
|
|
106
|
+
* hash. Timestamp defaults to `Math.floor(Date.now() / 1000)`.
|
|
107
|
+
*/
|
|
108
|
+
head(opts?: {
|
|
109
|
+
timestamp?: number;
|
|
110
|
+
}): Uint8Array;
|
|
111
|
+
/** Current number of leaves in the tree. */
|
|
112
|
+
size(): number;
|
|
113
|
+
/** Current Merkle root hash. */
|
|
114
|
+
rootHash(): Uint8Array;
|
|
115
|
+
/**
|
|
116
|
+
* Inclusion proof for `leafIndex` in a tree of the given size, or
|
|
117
|
+
* the current tree size if omitted. Per RFC 9162 §2.1.3.
|
|
118
|
+
*/
|
|
119
|
+
inclusionProof(leafIndex: number, treeSize?: number): Uint8Array[];
|
|
120
|
+
/**
|
|
121
|
+
* Consistency proof between two tree sizes per RFC 9162 §2.1.4.
|
|
122
|
+
* `oldSize` must be `<= newSize <= size()`.
|
|
123
|
+
*/
|
|
124
|
+
consistencyProof(oldSize: number, newSize: number): Uint8Array[];
|
|
125
|
+
/**
|
|
126
|
+
* Zero the stored signing-key copy. Idempotent. Subsequent calls
|
|
127
|
+
* to any public method throw.
|
|
128
|
+
*/
|
|
129
|
+
dispose(): void;
|
|
130
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
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/merkle/merkle-log.ts
|
|
23
|
+
//
|
|
24
|
+
// `MerkleLog`, the producer-side normie surface. Memory-backed via
|
|
25
|
+
// `MemoryStorage`. Real deployments drop down to `SignedLog<S>` with a
|
|
26
|
+
// custom `MerkleStorage`.
|
|
27
|
+
//
|
|
28
|
+
// Defaults: `hashing: 'sha256'`, `suite: MlDsa44Suite`. ML-DSA-44 is
|
|
29
|
+
// the PQ default per c2sp.org/tlog-checkpoint, the only PQ suite
|
|
30
|
+
// currently in the c2sp.org/tlog-cosignature §Format algorithm-byte
|
|
31
|
+
// registry. Sigsum interop: pass `suite: Ed25519Suite`.
|
|
32
|
+
import { isInitialized } from '../init.js';
|
|
33
|
+
import { MerkleLogError } from '../errors.js';
|
|
34
|
+
import { SignedLog } from './signed-log.js';
|
|
35
|
+
import { MemoryStorage } from './storage.js';
|
|
36
|
+
import { Sha256Tree, Sha256Hasher } from './sha256-tree.js';
|
|
37
|
+
import { Blake3Tree, Blake3Hasher } from './blake3-tree.js';
|
|
38
|
+
import { lookupAlgoEntryByFormatEnum } from './signed-note.js';
|
|
39
|
+
import { MlDsa44Suite } from '../sign/suites/mldsa.js';
|
|
40
|
+
const SHA2_MODULE = 'sha2';
|
|
41
|
+
/**
|
|
42
|
+
* Memory-backed signed transparency log. The normie producer surface.
|
|
43
|
+
* Construct via `MerkleLog.create` (caller supplies keys) or
|
|
44
|
+
* `MerkleLog.generate` (the class materialises a fresh keypair and
|
|
45
|
+
* returns it). Methods after construction are synchronous; module-init
|
|
46
|
+
* readiness and keygen are the only async steps.
|
|
47
|
+
*
|
|
48
|
+
* Methods delegate to an inner `SignedLog<S>` with a fresh
|
|
49
|
+
* `MemoryStorage` backend. For file or database storage, construct
|
|
50
|
+
* `SignedLog` directly with a custom `MerkleStorage` implementation,
|
|
51
|
+
* see `docs/merkle.md` for the extension pattern.
|
|
52
|
+
*/
|
|
53
|
+
export class MerkleLog {
|
|
54
|
+
origin;
|
|
55
|
+
hasher;
|
|
56
|
+
suite;
|
|
57
|
+
_inner;
|
|
58
|
+
constructor(inner) {
|
|
59
|
+
this._inner = inner;
|
|
60
|
+
this.origin = inner.origin;
|
|
61
|
+
this.hasher = inner.tree.hasher;
|
|
62
|
+
this.suite = inner.suite;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Construct a `MerkleLog` with caller-supplied keys. Validates the
|
|
66
|
+
* suite against the c2sp.org/tlog-cosignature §Format algorithm-byte
|
|
67
|
+
* registry before instantiating the inner `SignedLog`; an
|
|
68
|
+
* unregistered suite raises `MerkleLogError('unsupported-suite')`
|
|
69
|
+
* with a message naming the suite and pointing at the spec.
|
|
70
|
+
*
|
|
71
|
+
* Async to keep the construction surface uniform with `generate`,
|
|
72
|
+
* which is async because `suite.keygen()` may route through async
|
|
73
|
+
* WASM acquisition under load. The hot-path methods (`append`,
|
|
74
|
+
* `head`, `size`, etc.) stay sync per the merkle layer's locked
|
|
75
|
+
* sync invariant.
|
|
76
|
+
*/
|
|
77
|
+
static async create(opts) {
|
|
78
|
+
const hashing = opts.hashing ?? 'sha256';
|
|
79
|
+
const suite = opts.suite ?? MlDsa44Suite;
|
|
80
|
+
if (lookupAlgoEntryByFormatEnum(suite.formatEnum) === undefined)
|
|
81
|
+
throw new MerkleLogError('unsupported-suite', `MerkleLog: suite '${suite.formatName}' (formatEnum 0x${suite.formatEnum
|
|
82
|
+
.toString(16)
|
|
83
|
+
.padStart(2, '0')}) has no c2sp.org/tlog-cosignature §Format algorithm byte; `
|
|
84
|
+
+ 'use Ed25519Suite or MlDsa44Suite, or open an issue for a newly C2SP-registered suite');
|
|
85
|
+
const tree = buildTree(hashing);
|
|
86
|
+
assertModulesInitialized([
|
|
87
|
+
...suite.wasmModules,
|
|
88
|
+
...tree.hasher.wasmModules,
|
|
89
|
+
SHA2_MODULE,
|
|
90
|
+
]);
|
|
91
|
+
const inner = new SignedLog({
|
|
92
|
+
tree,
|
|
93
|
+
suite,
|
|
94
|
+
origin: opts.origin,
|
|
95
|
+
signingKey: opts.signingKey,
|
|
96
|
+
pubkey: opts.pubkey,
|
|
97
|
+
});
|
|
98
|
+
return new MerkleLog(inner);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Construct a `MerkleLog` with a freshly generated keypair. Returns
|
|
102
|
+
* the log plus the keypair; the caller is responsible for
|
|
103
|
+
* persisting the keys externally if the log outlives the process.
|
|
104
|
+
*
|
|
105
|
+
* The returned `signingKey` is a copy, the log retains its own
|
|
106
|
+
* internal copy that `dispose()` wipes; modifying the returned
|
|
107
|
+
* buffer after construction does not affect the log.
|
|
108
|
+
*/
|
|
109
|
+
static async generate(opts) {
|
|
110
|
+
const hashing = opts.hashing ?? 'sha256';
|
|
111
|
+
const suite = opts.suite ?? MlDsa44Suite;
|
|
112
|
+
if (lookupAlgoEntryByFormatEnum(suite.formatEnum) === undefined)
|
|
113
|
+
throw new MerkleLogError('unsupported-suite', `MerkleLog.generate: suite '${suite.formatName}' (formatEnum 0x${suite.formatEnum
|
|
114
|
+
.toString(16)
|
|
115
|
+
.padStart(2, '0')}) has no c2sp.org/tlog-cosignature §Format algorithm byte; `
|
|
116
|
+
+ 'use Ed25519Suite or MlDsa44Suite, or open an issue for a newly C2SP-registered suite');
|
|
117
|
+
// suite.keygen() requires the suite's modules already initialised;
|
|
118
|
+
// the same modules are checked again inside create() before
|
|
119
|
+
// instantiating SignedLog. Checking here keeps the throw surface
|
|
120
|
+
// consistent regardless of which factory the caller used.
|
|
121
|
+
const tmpHasher = resolveHasher(hashing);
|
|
122
|
+
assertModulesInitialized([
|
|
123
|
+
...suite.wasmModules,
|
|
124
|
+
...tmpHasher.wasmModules,
|
|
125
|
+
SHA2_MODULE,
|
|
126
|
+
]);
|
|
127
|
+
const { pk, sk } = suite.keygen();
|
|
128
|
+
const log = await MerkleLog.create({
|
|
129
|
+
origin: opts.origin,
|
|
130
|
+
signingKey: sk,
|
|
131
|
+
pubkey: pk,
|
|
132
|
+
hashing,
|
|
133
|
+
suite,
|
|
134
|
+
});
|
|
135
|
+
return { log, signingKey: sk, pubkey: pk };
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Append a leaf and return its index, hash, and inclusion proof
|
|
139
|
+
* against the post-append tree size. Delegates to the inner
|
|
140
|
+
* `SignedLog.append`.
|
|
141
|
+
*/
|
|
142
|
+
append(leafBytes) {
|
|
143
|
+
return this._inner.append(leafBytes);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Emit the current checkpoint as a signed-note envelope. Re-signed
|
|
147
|
+
* on every call; the body reflects the live tree size and root
|
|
148
|
+
* hash. Timestamp defaults to `Math.floor(Date.now() / 1000)`.
|
|
149
|
+
*/
|
|
150
|
+
head(opts) {
|
|
151
|
+
return this._inner.signCheckpoint(opts);
|
|
152
|
+
}
|
|
153
|
+
/** Current number of leaves in the tree. */
|
|
154
|
+
size() {
|
|
155
|
+
return this._inner.size();
|
|
156
|
+
}
|
|
157
|
+
/** Current Merkle root hash. */
|
|
158
|
+
rootHash() {
|
|
159
|
+
return this._inner.rootHash();
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Inclusion proof for `leafIndex` in a tree of the given size, or
|
|
163
|
+
* the current tree size if omitted. Per RFC 9162 §2.1.3.
|
|
164
|
+
*/
|
|
165
|
+
inclusionProof(leafIndex, treeSize) {
|
|
166
|
+
return this._inner.getInclusionProof(leafIndex, treeSize);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Consistency proof between two tree sizes per RFC 9162 §2.1.4.
|
|
170
|
+
* `oldSize` must be `<= newSize <= size()`.
|
|
171
|
+
*/
|
|
172
|
+
consistencyProof(oldSize, newSize) {
|
|
173
|
+
return this._inner.getConsistencyProof(oldSize, newSize);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Zero the stored signing-key copy. Idempotent. Subsequent calls
|
|
177
|
+
* to any public method throw.
|
|
178
|
+
*/
|
|
179
|
+
dispose() {
|
|
180
|
+
this._inner.dispose();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function buildTree(hashing) {
|
|
184
|
+
if (hashing === 'sha256')
|
|
185
|
+
return new Sha256Tree(new MemoryStorage());
|
|
186
|
+
if (hashing === 'blake3')
|
|
187
|
+
return new Blake3Tree(new MemoryStorage());
|
|
188
|
+
throw new MerkleLogError('unsupported-hashing', `MerkleLog: hashing must be 'sha256' or 'blake3', got '${hashing}'`);
|
|
189
|
+
}
|
|
190
|
+
function resolveHasher(hashing) {
|
|
191
|
+
if (hashing === 'sha256')
|
|
192
|
+
return Sha256Hasher;
|
|
193
|
+
if (hashing === 'blake3')
|
|
194
|
+
return Blake3Hasher;
|
|
195
|
+
throw new MerkleLogError('unsupported-hashing', `MerkleLog: hashing must be 'sha256' or 'blake3', got '${hashing}'`);
|
|
196
|
+
}
|
|
197
|
+
function assertModulesInitialized(modules) {
|
|
198
|
+
const seen = new Set();
|
|
199
|
+
for (const mod of modules) {
|
|
200
|
+
if (seen.has(mod))
|
|
201
|
+
continue;
|
|
202
|
+
seen.add(mod);
|
|
203
|
+
if (!isInitialized(mod))
|
|
204
|
+
throw new MerkleLogError('module-not-initialized', `MerkleLog: WASM module '${mod}' is not initialized; `
|
|
205
|
+
+ 'call init() with the appropriate sources before constructing MerkleLog');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { Hasher } from './tree.js';
|
|
2
|
+
import type { SignatureSuite } from '../sign/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Construction options for `MerkleVerifier`.
|
|
5
|
+
*/
|
|
6
|
+
export interface MerkleVerifierOpts {
|
|
7
|
+
/**
|
|
8
|
+
* Log identity, the first line of every checkpoint body. Validated
|
|
9
|
+
* at construction (non-empty, no whitespace, no plus characters)
|
|
10
|
+
* per c2sp.org/tlog-checkpoint §Note text.
|
|
11
|
+
*/
|
|
12
|
+
readonly origin: string;
|
|
13
|
+
/**
|
|
14
|
+
* Trusted public key for the log's primary cosignature line. Length
|
|
15
|
+
* must equal `suite.pkSize`; a constructor-time mismatch raises
|
|
16
|
+
* `MerkleLogError('pubkey-size')`.
|
|
17
|
+
*/
|
|
18
|
+
readonly pubkey: Uint8Array;
|
|
19
|
+
/**
|
|
20
|
+
* Hash function the log's tree uses. `'sha256'` resolves to
|
|
21
|
+
* `Sha256Hasher`, `'blake3'` resolves to `Blake3Hasher`. The
|
|
22
|
+
* verifier hashes leaf bytes with this function before calling
|
|
23
|
+
* `verifyInclusionProof`.
|
|
24
|
+
*/
|
|
25
|
+
readonly hashing: 'sha256' | 'blake3';
|
|
26
|
+
/**
|
|
27
|
+
* Signature suite the log signs cosignatures with. Must be a suite
|
|
28
|
+
* whose `formatEnum` is registered in the c2sp.org/tlog-cosignature
|
|
29
|
+
* §Format algorithm-byte registry; today that is `Ed25519Suite`
|
|
30
|
+
* and `MlDsa44Suite`. Other suites raise
|
|
31
|
+
* `MerkleLogError('unsupported-suite')`.
|
|
32
|
+
*/
|
|
33
|
+
readonly suite: SignatureSuite;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Trust-anchored verifier for c2sp.org/tlog-checkpoint envelopes.
|
|
37
|
+
* Takes a fixed log identity at construction and exposes three verify
|
|
38
|
+
* methods (`verifyCheckpoint`, `verifyInclusion`, `verifyConsistency`)
|
|
39
|
+
* that return `boolean`.
|
|
40
|
+
*
|
|
41
|
+
* Construction is the only place this class throws; every verify path
|
|
42
|
+
* returns `false` on any failure mode including malformed bytes,
|
|
43
|
+
* tampered envelopes, wrong origin, wrong leaf, and signature failure.
|
|
44
|
+
* The convention matches `SignatureSuite.verify` and lets normie
|
|
45
|
+
* callers write a single `if (!verifier.verifyX(...)) reject()` line
|
|
46
|
+
* per check without a try / catch.
|
|
47
|
+
*/
|
|
48
|
+
export declare class MerkleVerifier {
|
|
49
|
+
readonly origin: string;
|
|
50
|
+
readonly pubkey: Uint8Array;
|
|
51
|
+
readonly hasher: Hasher;
|
|
52
|
+
readonly suite: SignatureSuite;
|
|
53
|
+
private readonly _algoEntry;
|
|
54
|
+
private readonly _keyId;
|
|
55
|
+
constructor(opts: MerkleVerifierOpts);
|
|
56
|
+
/**
|
|
57
|
+
* Verify a signed-note envelope against this verifier's identity.
|
|
58
|
+
* Returns `true` iff the envelope parses, the body's origin equals
|
|
59
|
+
* the constructor origin, the body's root-hash length equals the
|
|
60
|
+
* hasher's `outputSize`, a signature line's keyId equals the
|
|
61
|
+
* pubkey-derived keyId, the `timestamped_signature` payload on
|
|
62
|
+
* that line decodes cleanly, and `suite.verify` accepts the
|
|
63
|
+
* reconstructed cosignature signed message.
|
|
64
|
+
*
|
|
65
|
+
* Returns `false` on every other path. Never throws on envelope
|
|
66
|
+
* content.
|
|
67
|
+
*/
|
|
68
|
+
verifyCheckpoint(envelopeBytes: Uint8Array): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Verify a leaf's inclusion in the tree committed by an envelope.
|
|
71
|
+
* Runs `verifyCheckpoint` first; on failure returns `false`
|
|
72
|
+
* without examining the proof. On success, hashes `leafBytes`
|
|
73
|
+
* with the verifier's `Hasher` and calls `verifyInclusionProof`
|
|
74
|
+
* against the body's `treeSize` and `rootHash` per RFC 9162 §2.1.3.
|
|
75
|
+
*
|
|
76
|
+
* The "verify checkpoint first" ordering is the security-critical
|
|
77
|
+
* step: the proof is bound to the root hash inside the signed body,
|
|
78
|
+
* so trusting the proof before checking the signature would let any
|
|
79
|
+
* forger pair a malicious proof with their own root.
|
|
80
|
+
*/
|
|
81
|
+
verifyInclusion(opts: {
|
|
82
|
+
envelopeBytes: Uint8Array;
|
|
83
|
+
leafBytes: Uint8Array;
|
|
84
|
+
leafIndex: number;
|
|
85
|
+
proof: readonly Uint8Array[];
|
|
86
|
+
}): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Verify that the tree committed by `oldEnvelopeBytes` is a prefix
|
|
89
|
+
* of the tree committed by `newEnvelopeBytes`. Both envelopes must
|
|
90
|
+
* verify under this verifier's identity; if either fails, returns
|
|
91
|
+
* `false`. On success, calls `verifyConsistencyProof` per
|
|
92
|
+
* RFC 9162 §2.1.4 against the two sizes and roots.
|
|
93
|
+
*/
|
|
94
|
+
verifyConsistency(opts: {
|
|
95
|
+
oldEnvelopeBytes: Uint8Array;
|
|
96
|
+
newEnvelopeBytes: Uint8Array;
|
|
97
|
+
proof: readonly Uint8Array[];
|
|
98
|
+
}): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Parse a signed-note envelope, verify the cosignature, and return
|
|
101
|
+
* the decoded `Checkpoint`. Returns `null` on any failure mode:
|
|
102
|
+
* malformed envelope, malformed body, wrong origin, wrong root-hash
|
|
103
|
+
* length, no matching keyId line, malformed payload, signature
|
|
104
|
+
* failure. Keyed-ID comparison uses `constantTimeEqual` for hygiene
|
|
105
|
+
* around key-material-adjacent state.
|
|
106
|
+
*/
|
|
107
|
+
private _parseAndVerify;
|
|
108
|
+
/**
|
|
109
|
+
* Dispatch cosignature signed-message construction on the algorithm
|
|
110
|
+
* registry entry's `messageConstruction`. Mirrors `SignedLog`'s
|
|
111
|
+
* dispatch so producer and verifier always agree on the bytes the
|
|
112
|
+
* suite verifies against.
|
|
113
|
+
*
|
|
114
|
+
* 'cosig' c2sp.org/tlog-cosignature §"Ed25519 signed
|
|
115
|
+
* message". The full envelope body is embedded
|
|
116
|
+
* verbatim after the cosignature/v1 + time
|
|
117
|
+
* prefix.
|
|
118
|
+
*
|
|
119
|
+
* 'cosigned-message' c2sp.org/tlog-cosignature §"ML-DSA-44
|
|
120
|
+
* signed message". cosigner_name and
|
|
121
|
+
* log_origin both equal the checkpoint origin
|
|
122
|
+
* for a log's self-cosignature; start == 0;
|
|
123
|
+
* end == treeSize; hash == rootHash.
|
|
124
|
+
*/
|
|
125
|
+
private _buildSignedMessage;
|
|
126
|
+
}
|