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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
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-verifier.ts
|
|
23
|
+
//
|
|
24
|
+
// `MerkleVerifier`, verify-only normie surface. Wire format per
|
|
25
|
+
// c2sp.org/signed-note §Format, c2sp.org/tlog-checkpoint §Note text,
|
|
26
|
+
// and c2sp.org/tlog-cosignature §Format.
|
|
27
|
+
import { isInitialized } from '../init.js';
|
|
28
|
+
import { MerkleLogError, MerkleCodecError } from '../errors.js';
|
|
29
|
+
import { constantTimeEqual } from '../utils.js';
|
|
30
|
+
import { parseSignedNote, lookupAlgoEntryByFormatEnum, deriveKeyId, buildCosigSignedMessage, buildCosignedMessage, parseCosigSignaturePayload, } from './signed-note.js';
|
|
31
|
+
import { parseCheckpointBody } from './checkpoint.js';
|
|
32
|
+
import { verifyInclusionProof, verifyConsistencyProof } from './proof.js';
|
|
33
|
+
import { Sha256Hasher } from './sha256-tree.js';
|
|
34
|
+
import { Blake3Hasher } from './blake3-tree.js';
|
|
35
|
+
// Empty ctx for suite.verify; domain separation lives in the
|
|
36
|
+
// cosignature signed-message construction (cosignature/v1 prefix for
|
|
37
|
+
// Ed25519, cosigned_message label for ML-DSA-44) per
|
|
38
|
+
// c2sp.org/tlog-cosignature §Format.
|
|
39
|
+
const EMPTY_CTX = new Uint8Array(0);
|
|
40
|
+
const SHA2_MODULE = 'sha2';
|
|
41
|
+
/**
|
|
42
|
+
* Trust-anchored verifier for c2sp.org/tlog-checkpoint envelopes.
|
|
43
|
+
* Takes a fixed log identity at construction and exposes three verify
|
|
44
|
+
* methods (`verifyCheckpoint`, `verifyInclusion`, `verifyConsistency`)
|
|
45
|
+
* that return `boolean`.
|
|
46
|
+
*
|
|
47
|
+
* Construction is the only place this class throws; every verify path
|
|
48
|
+
* returns `false` on any failure mode including malformed bytes,
|
|
49
|
+
* tampered envelopes, wrong origin, wrong leaf, and signature failure.
|
|
50
|
+
* The convention matches `SignatureSuite.verify` and lets normie
|
|
51
|
+
* callers write a single `if (!verifier.verifyX(...)) reject()` line
|
|
52
|
+
* per check without a try / catch.
|
|
53
|
+
*/
|
|
54
|
+
export class MerkleVerifier {
|
|
55
|
+
origin;
|
|
56
|
+
pubkey;
|
|
57
|
+
hasher;
|
|
58
|
+
suite;
|
|
59
|
+
_algoEntry;
|
|
60
|
+
_keyId;
|
|
61
|
+
constructor(opts) {
|
|
62
|
+
const { origin, pubkey, hashing, suite } = opts;
|
|
63
|
+
if (typeof origin !== 'string' || origin.length === 0)
|
|
64
|
+
throw new MerkleLogError('origin-invalid', 'MerkleVerifier: origin must be a non-empty string');
|
|
65
|
+
// c2sp.org/tlog-checkpoint §Note text MUSTs, mirrored from
|
|
66
|
+
// `SignedLog`'s constructor: the origin is the first body line
|
|
67
|
+
// and may not contain whitespace or plus characters.
|
|
68
|
+
if (/\s/.test(origin) || origin.includes('+'))
|
|
69
|
+
throw new MerkleLogError('origin-invalid', 'MerkleVerifier: origin must not contain whitespace or plus characters');
|
|
70
|
+
if (!(pubkey instanceof Uint8Array))
|
|
71
|
+
throw new MerkleLogError('pubkey-size', 'MerkleVerifier: pubkey must be a Uint8Array');
|
|
72
|
+
const hasher = resolveHasher(hashing);
|
|
73
|
+
const algoEntry = lookupAlgoEntryByFormatEnum(suite.formatEnum);
|
|
74
|
+
if (algoEntry === undefined)
|
|
75
|
+
throw new MerkleLogError('unsupported-suite', `MerkleVerifier: suite '${suite.formatName}' (formatEnum 0x${suite.formatEnum
|
|
76
|
+
.toString(16)
|
|
77
|
+
.padStart(2, '0')}) has no c2sp.org/tlog-cosignature §Format algorithm byte; `
|
|
78
|
+
+ 'use Ed25519Suite or MlDsa44Suite, or open an issue for a newly C2SP-registered suite');
|
|
79
|
+
if (pubkey.length !== suite.pkSize)
|
|
80
|
+
throw new MerkleLogError('pubkey-size', `MerkleVerifier: pubkey length ${pubkey.length} != suite.pkSize ${suite.pkSize}`);
|
|
81
|
+
// Same modules `SignedLog` requires: the suite's modules, the
|
|
82
|
+
// hasher's module, and sha2 for `deriveKeyId`. Constructor-time
|
|
83
|
+
// check so a verifier built before `init()` fails at construction
|
|
84
|
+
// rather than on first `verifyCheckpoint` call.
|
|
85
|
+
assertModulesInitialized([
|
|
86
|
+
...suite.wasmModules,
|
|
87
|
+
...hasher.wasmModules,
|
|
88
|
+
SHA2_MODULE,
|
|
89
|
+
]);
|
|
90
|
+
this.origin = origin;
|
|
91
|
+
this.pubkey = pubkey.slice();
|
|
92
|
+
this.hasher = hasher;
|
|
93
|
+
this.suite = suite;
|
|
94
|
+
this._algoEntry = algoEntry;
|
|
95
|
+
this._keyId = deriveKeyId(origin, algoEntry.algoByte, this.pubkey);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Verify a signed-note envelope against this verifier's identity.
|
|
99
|
+
* Returns `true` iff the envelope parses, the body's origin equals
|
|
100
|
+
* the constructor origin, the body's root-hash length equals the
|
|
101
|
+
* hasher's `outputSize`, a signature line's keyId equals the
|
|
102
|
+
* pubkey-derived keyId, the `timestamped_signature` payload on
|
|
103
|
+
* that line decodes cleanly, and `suite.verify` accepts the
|
|
104
|
+
* reconstructed cosignature signed message.
|
|
105
|
+
*
|
|
106
|
+
* Returns `false` on every other path. Never throws on envelope
|
|
107
|
+
* content.
|
|
108
|
+
*/
|
|
109
|
+
verifyCheckpoint(envelopeBytes) {
|
|
110
|
+
const parsed = this._parseAndVerify(envelopeBytes);
|
|
111
|
+
return parsed !== null;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Verify a leaf's inclusion in the tree committed by an envelope.
|
|
115
|
+
* Runs `verifyCheckpoint` first; on failure returns `false`
|
|
116
|
+
* without examining the proof. On success, hashes `leafBytes`
|
|
117
|
+
* with the verifier's `Hasher` and calls `verifyInclusionProof`
|
|
118
|
+
* against the body's `treeSize` and `rootHash` per RFC 9162 §2.1.3.
|
|
119
|
+
*
|
|
120
|
+
* The "verify checkpoint first" ordering is the security-critical
|
|
121
|
+
* step: the proof is bound to the root hash inside the signed body,
|
|
122
|
+
* so trusting the proof before checking the signature would let any
|
|
123
|
+
* forger pair a malicious proof with their own root.
|
|
124
|
+
*/
|
|
125
|
+
verifyInclusion(opts) {
|
|
126
|
+
const parsed = this._parseAndVerify(opts.envelopeBytes);
|
|
127
|
+
if (parsed === null)
|
|
128
|
+
return false;
|
|
129
|
+
if (!(opts.leafBytes instanceof Uint8Array))
|
|
130
|
+
return false;
|
|
131
|
+
if (!Number.isInteger(opts.leafIndex) || opts.leafIndex < 0)
|
|
132
|
+
return false;
|
|
133
|
+
if (opts.leafIndex >= parsed.treeSize)
|
|
134
|
+
return false;
|
|
135
|
+
if (!Array.isArray(opts.proof))
|
|
136
|
+
return false;
|
|
137
|
+
for (const h of opts.proof)
|
|
138
|
+
if (!(h instanceof Uint8Array))
|
|
139
|
+
return false;
|
|
140
|
+
// RFC 9162 §2.1.1: leaf-hash domain separation happens here;
|
|
141
|
+
// the proof verifier expects the MTH({d}) of the leaf, not the
|
|
142
|
+
// raw leaf bytes. Computing it locally rather than accepting a
|
|
143
|
+
// caller-supplied leaf hash closes the "we trust the proof
|
|
144
|
+
// because we trust the leaf hash the caller gave us" gap.
|
|
145
|
+
const leafHash = this.hasher.hashLeaf(opts.leafBytes);
|
|
146
|
+
try {
|
|
147
|
+
return verifyInclusionProof({
|
|
148
|
+
hasher: this.hasher,
|
|
149
|
+
leafHash,
|
|
150
|
+
leafIndex: opts.leafIndex,
|
|
151
|
+
treeSize: parsed.treeSize,
|
|
152
|
+
proof: opts.proof,
|
|
153
|
+
rootHash: parsed.rootHash,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// `verifyInclusionProof` throws on a wrong-sized rootHash or
|
|
158
|
+
// out-of-range leafIndex. Convert to a verify-false: the
|
|
159
|
+
// normie surface keeps a single failure mode.
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Verify that the tree committed by `oldEnvelopeBytes` is a prefix
|
|
165
|
+
* of the tree committed by `newEnvelopeBytes`. Both envelopes must
|
|
166
|
+
* verify under this verifier's identity; if either fails, returns
|
|
167
|
+
* `false`. On success, calls `verifyConsistencyProof` per
|
|
168
|
+
* RFC 9162 §2.1.4 against the two sizes and roots.
|
|
169
|
+
*/
|
|
170
|
+
verifyConsistency(opts) {
|
|
171
|
+
const oldParsed = this._parseAndVerify(opts.oldEnvelopeBytes);
|
|
172
|
+
if (oldParsed === null)
|
|
173
|
+
return false;
|
|
174
|
+
const newParsed = this._parseAndVerify(opts.newEnvelopeBytes);
|
|
175
|
+
if (newParsed === null)
|
|
176
|
+
return false;
|
|
177
|
+
if (!Array.isArray(opts.proof))
|
|
178
|
+
return false;
|
|
179
|
+
for (const h of opts.proof)
|
|
180
|
+
if (!(h instanceof Uint8Array))
|
|
181
|
+
return false;
|
|
182
|
+
try {
|
|
183
|
+
return verifyConsistencyProof({
|
|
184
|
+
hasher: this.hasher,
|
|
185
|
+
oldSize: oldParsed.treeSize,
|
|
186
|
+
newSize: newParsed.treeSize,
|
|
187
|
+
oldRoot: oldParsed.rootHash,
|
|
188
|
+
newRoot: newParsed.rootHash,
|
|
189
|
+
proof: opts.proof,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// ── internal ────────────────────────────────────────────────────────
|
|
197
|
+
/**
|
|
198
|
+
* Parse a signed-note envelope, verify the cosignature, and return
|
|
199
|
+
* the decoded `Checkpoint`. Returns `null` on any failure mode:
|
|
200
|
+
* malformed envelope, malformed body, wrong origin, wrong root-hash
|
|
201
|
+
* length, no matching keyId line, malformed payload, signature
|
|
202
|
+
* failure. Keyed-ID comparison uses `constantTimeEqual` for hygiene
|
|
203
|
+
* around key-material-adjacent state.
|
|
204
|
+
*/
|
|
205
|
+
_parseAndVerify(bytes) {
|
|
206
|
+
if (!(bytes instanceof Uint8Array))
|
|
207
|
+
return null;
|
|
208
|
+
let env;
|
|
209
|
+
try {
|
|
210
|
+
env = parseSignedNote(bytes);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
let checkpoint;
|
|
216
|
+
try {
|
|
217
|
+
checkpoint = parseCheckpointBody(env.body, this.hasher.outputSize);
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
if (checkpoint.origin !== this.origin)
|
|
223
|
+
return null;
|
|
224
|
+
if (checkpoint.rootHash.length !== this.hasher.outputSize)
|
|
225
|
+
return null;
|
|
226
|
+
const matching = env.signatures.find(s => s.keyId.length === this._keyId.length
|
|
227
|
+
&& constantTimeEqual(s.keyId, this._keyId));
|
|
228
|
+
if (!matching)
|
|
229
|
+
return null;
|
|
230
|
+
let payload;
|
|
231
|
+
try {
|
|
232
|
+
payload = parseCosigSignaturePayload(matching.signature, this._algoEntry.sigSize);
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
if (err instanceof MerkleCodecError)
|
|
236
|
+
return null;
|
|
237
|
+
throw err;
|
|
238
|
+
}
|
|
239
|
+
let signedMessage;
|
|
240
|
+
try {
|
|
241
|
+
signedMessage = this._buildSignedMessage(env.body, payload.timestamp, checkpoint);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
const ok = this.suite.verify(this.pubkey, signedMessage, payload.signature, EMPTY_CTX);
|
|
247
|
+
return ok ? checkpoint : null;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Dispatch cosignature signed-message construction on the algorithm
|
|
251
|
+
* registry entry's `messageConstruction`. Mirrors `SignedLog`'s
|
|
252
|
+
* dispatch so producer and verifier always agree on the bytes the
|
|
253
|
+
* suite verifies against.
|
|
254
|
+
*
|
|
255
|
+
* 'cosig' c2sp.org/tlog-cosignature §"Ed25519 signed
|
|
256
|
+
* message". The full envelope body is embedded
|
|
257
|
+
* verbatim after the cosignature/v1 + time
|
|
258
|
+
* prefix.
|
|
259
|
+
*
|
|
260
|
+
* 'cosigned-message' c2sp.org/tlog-cosignature §"ML-DSA-44
|
|
261
|
+
* signed message". cosigner_name and
|
|
262
|
+
* log_origin both equal the checkpoint origin
|
|
263
|
+
* for a log's self-cosignature; start == 0;
|
|
264
|
+
* end == treeSize; hash == rootHash.
|
|
265
|
+
*/
|
|
266
|
+
_buildSignedMessage(body, timestamp, cp) {
|
|
267
|
+
if (this._algoEntry.messageConstruction === 'cosig')
|
|
268
|
+
return buildCosigSignedMessage(body, timestamp);
|
|
269
|
+
return buildCosignedMessage({
|
|
270
|
+
cosignerName: this.origin,
|
|
271
|
+
timestamp,
|
|
272
|
+
logOrigin: this.origin,
|
|
273
|
+
start: 0,
|
|
274
|
+
end: cp.treeSize,
|
|
275
|
+
hash: cp.rootHash,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function resolveHasher(hashing) {
|
|
280
|
+
if (hashing === 'sha256')
|
|
281
|
+
return Sha256Hasher;
|
|
282
|
+
if (hashing === 'blake3')
|
|
283
|
+
return Blake3Hasher;
|
|
284
|
+
throw new MerkleLogError('unsupported-hashing', `MerkleVerifier: hashing must be 'sha256' or 'blake3', got '${hashing}'`);
|
|
285
|
+
}
|
|
286
|
+
function assertModulesInitialized(modules) {
|
|
287
|
+
const seen = new Set();
|
|
288
|
+
for (const mod of modules) {
|
|
289
|
+
if (seen.has(mod))
|
|
290
|
+
continue;
|
|
291
|
+
seen.add(mod);
|
|
292
|
+
if (!isInitialized(mod))
|
|
293
|
+
throw new MerkleLogError('module-not-initialized', `MerkleVerifier: WASM module '${mod}' is not initialized; `
|
|
294
|
+
+ 'call init() with the appropriate sources before constructing MerkleVerifier');
|
|
295
|
+
}
|
|
296
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Hasher } from './tree.js';
|
|
2
|
+
export interface VerifyInclusionInput {
|
|
3
|
+
hasher: Hasher;
|
|
4
|
+
leafHash: Uint8Array;
|
|
5
|
+
leafIndex: number;
|
|
6
|
+
treeSize: number;
|
|
7
|
+
proof: readonly Uint8Array[];
|
|
8
|
+
rootHash: Uint8Array;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* RFC 9162 §2.1.3, Inclusion Proof Verification. Returns true if the
|
|
12
|
+
* proof reconstructs `rootHash` from `leafHash` at position
|
|
13
|
+
* (leafIndex, treeSize). Wrong proof length, wrong leaf-hash size, or
|
|
14
|
+
* a reconstructed root that differs from `rootHash` all return false.
|
|
15
|
+
* Contract violations (negative or out-of-range index, treeSize <= 0,
|
|
16
|
+
* wrong-sized rootHash) throw RangeError.
|
|
17
|
+
*
|
|
18
|
+
* `leafHash` is the leaf's MTH ({d_m} hashed under the leaf prefix), not
|
|
19
|
+
* the raw leaf bytes. Thin verifiers receiving a leaf over the wire
|
|
20
|
+
* should compute `hasher.hashLeaf(bytes)` before calling.
|
|
21
|
+
*/
|
|
22
|
+
export declare function verifyInclusionProof(input: VerifyInclusionInput): boolean;
|
|
23
|
+
export interface VerifyConsistencyInput {
|
|
24
|
+
hasher: Hasher;
|
|
25
|
+
oldSize: number;
|
|
26
|
+
newSize: number;
|
|
27
|
+
oldRoot: Uint8Array;
|
|
28
|
+
newRoot: Uint8Array;
|
|
29
|
+
proof: readonly Uint8Array[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* RFC 9162 §2.1.4, Consistency Proof Verification. Returns true if
|
|
33
|
+
* `proof` proves that the size-`oldSize` tree with root `oldRoot` is a
|
|
34
|
+
* prefix of the size-`newSize` tree with root `newRoot`.
|
|
35
|
+
*
|
|
36
|
+
* Malformed-proof conditions (wrong proof length, non-empty proof when
|
|
37
|
+
* one is forbidden, mismatched old/new root reconstruction) return
|
|
38
|
+
* false. Contract violations (`oldSize > newSize`, wrong-sized root)
|
|
39
|
+
* throw RangeError; the special "consistency from empty tree" form is
|
|
40
|
+
* not part of the wire format and returns false.
|
|
41
|
+
*/
|
|
42
|
+
export declare function verifyConsistencyProof(input: VerifyConsistencyInput): boolean;
|
|
43
|
+
/** Callback the builders use to read the tree without knowing how it is stored. */
|
|
44
|
+
export type GetNode = (level: number, index: number) => Uint8Array;
|
|
45
|
+
export interface BuildInclusionInput {
|
|
46
|
+
hasher: Hasher;
|
|
47
|
+
leafIndex: number;
|
|
48
|
+
treeSize: number;
|
|
49
|
+
getNode: GetNode;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* RFC 9162 §2.1.3: build the inclusion proof for leaf `leafIndex` in
|
|
53
|
+
* a tree of size `treeSize`. The returned bytes are ordered from the
|
|
54
|
+
* lowest level upward (leaf sibling first, root-adjacent last), the
|
|
55
|
+
* order `verifyInclusionProof` consumes.
|
|
56
|
+
*/
|
|
57
|
+
export declare function buildInclusionProof(input: BuildInclusionInput): Uint8Array[];
|
|
58
|
+
export interface BuildConsistencyInput {
|
|
59
|
+
hasher: Hasher;
|
|
60
|
+
oldSize: number;
|
|
61
|
+
newSize: number;
|
|
62
|
+
getNode: GetNode;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* RFC 9162 §2.1.4: build the consistency proof between two tree
|
|
66
|
+
* sizes. Returns an empty array when oldSize equals newSize or
|
|
67
|
+
* oldSize is zero (the verifier rejects the latter, but the builder
|
|
68
|
+
* is symmetric for inspection-time use).
|
|
69
|
+
*/
|
|
70
|
+
export declare function buildConsistencyProof(input: BuildConsistencyInput): Uint8Array[];
|