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.
Files changed (312) hide show
  1. package/CLAUDE.md +88 -281
  2. package/LICENSE +4 -0
  3. package/README.md +275 -87
  4. package/dist/aes/aes-cbc.d.ts +40 -0
  5. package/dist/aes/aes-cbc.js +158 -0
  6. package/dist/aes/aes-ctr.d.ts +50 -0
  7. package/dist/aes/aes-ctr.js +141 -0
  8. package/dist/aes/aes-gcm-siv.d.ts +67 -0
  9. package/dist/aes/aes-gcm-siv.js +217 -0
  10. package/dist/aes/aes-gcm.d.ts +61 -0
  11. package/dist/aes/aes-gcm.js +226 -0
  12. package/dist/aes/cipher-suite.d.ts +21 -0
  13. package/dist/aes/cipher-suite.js +179 -0
  14. package/dist/aes/embedded.d.ts +1 -0
  15. package/dist/aes/embedded.js +26 -0
  16. package/dist/aes/generator.d.ts +14 -0
  17. package/dist/aes/generator.js +103 -0
  18. package/dist/aes/index.d.ts +58 -0
  19. package/dist/aes/index.js +125 -0
  20. package/dist/aes/ops.d.ts +60 -0
  21. package/dist/aes/ops.js +164 -0
  22. package/dist/aes/pool-worker.d.ts +1 -0
  23. package/dist/aes/pool-worker.js +92 -0
  24. package/dist/aes/types.d.ts +1 -0
  25. package/dist/aes/types.js +23 -0
  26. package/dist/aes.wasm +0 -0
  27. package/dist/blake3/embedded.d.ts +1 -0
  28. package/dist/blake3/embedded.js +26 -0
  29. package/dist/blake3/index.d.ts +143 -0
  30. package/dist/blake3/index.js +620 -0
  31. package/dist/blake3/types.d.ts +102 -0
  32. package/dist/blake3/types.js +31 -0
  33. package/dist/blake3/validate.d.ts +29 -0
  34. package/dist/blake3/validate.js +80 -0
  35. package/dist/blake3.wasm +0 -0
  36. package/dist/chacha20/cipher-suite.d.ts +10 -0
  37. package/dist/chacha20/cipher-suite.js +98 -13
  38. package/dist/chacha20/generator.d.ts +12 -0
  39. package/dist/chacha20/generator.js +91 -0
  40. package/dist/chacha20/index.d.ts +100 -3
  41. package/dist/chacha20/index.js +169 -35
  42. package/dist/chacha20/ops.d.ts +57 -6
  43. package/dist/chacha20/ops.js +107 -27
  44. package/dist/chacha20/pool-worker.js +14 -0
  45. package/dist/chacha20/types.d.ts +1 -32
  46. package/dist/cte-wasm.d.ts +1 -0
  47. package/dist/cte-wasm.js +3 -0
  48. package/dist/cte.wasm +0 -0
  49. package/dist/curve25519.wasm +0 -0
  50. package/dist/ecdsa/der.d.ts +23 -0
  51. package/dist/ecdsa/der.js +192 -0
  52. package/dist/ecdsa/ecprivatekey-der.d.ts +32 -0
  53. package/dist/ecdsa/ecprivatekey-der.js +230 -0
  54. package/dist/ecdsa/embedded.d.ts +1 -0
  55. package/dist/ecdsa/embedded.js +25 -0
  56. package/dist/ecdsa/index.d.ts +124 -0
  57. package/dist/ecdsa/index.js +366 -0
  58. package/dist/ecdsa/types.d.ts +31 -0
  59. package/dist/ecdsa/types.js +28 -0
  60. package/dist/ecdsa/validate.d.ts +18 -0
  61. package/dist/ecdsa/validate.js +92 -0
  62. package/dist/ed25519/embedded.d.ts +1 -0
  63. package/dist/ed25519/embedded.js +31 -0
  64. package/dist/ed25519/index.d.ts +70 -0
  65. package/dist/ed25519/index.js +308 -0
  66. package/dist/ed25519/types.d.ts +27 -0
  67. package/dist/ed25519/types.js +27 -0
  68. package/dist/ed25519/validate.d.ts +7 -0
  69. package/dist/ed25519/validate.js +77 -0
  70. package/dist/embedded/aes-pool-worker.d.ts +1 -0
  71. package/dist/embedded/aes-pool-worker.js +5 -0
  72. package/dist/embedded/aes.d.ts +1 -0
  73. package/dist/embedded/aes.js +3 -0
  74. package/dist/embedded/blake3.d.ts +1 -0
  75. package/dist/embedded/blake3.js +3 -0
  76. package/dist/embedded/chacha20-pool-worker.d.ts +1 -0
  77. package/dist/embedded/chacha20-pool-worker.js +5 -0
  78. package/dist/embedded/chacha20.d.ts +1 -1
  79. package/dist/embedded/chacha20.js +2 -2
  80. package/dist/embedded/curve25519.d.ts +1 -0
  81. package/dist/embedded/curve25519.js +3 -0
  82. package/dist/embedded/mldsa.d.ts +1 -0
  83. package/dist/embedded/mldsa.js +3 -0
  84. package/dist/embedded/mlkem.d.ts +1 -0
  85. package/dist/embedded/mlkem.js +3 -0
  86. package/dist/embedded/p256.d.ts +1 -0
  87. package/dist/embedded/p256.js +3 -0
  88. package/dist/embedded/serpent-pool-worker.d.ts +1 -0
  89. package/dist/embedded/serpent-pool-worker.js +5 -0
  90. package/dist/embedded/serpent.d.ts +1 -1
  91. package/dist/embedded/serpent.js +2 -2
  92. package/dist/embedded/sha2.d.ts +1 -1
  93. package/dist/embedded/sha2.js +2 -2
  94. package/dist/embedded/sha3.d.ts +1 -1
  95. package/dist/embedded/sha3.js +2 -2
  96. package/dist/embedded/slhdsa.d.ts +1 -0
  97. package/dist/embedded/slhdsa.js +3 -0
  98. package/dist/errors.d.ts +92 -1
  99. package/dist/errors.js +111 -1
  100. package/dist/fortuna.d.ts +18 -12
  101. package/dist/fortuna.js +166 -99
  102. package/dist/index.d.ts +42 -11
  103. package/dist/index.js +65 -20
  104. package/dist/init.d.ts +1 -3
  105. package/dist/init.js +73 -7
  106. package/dist/keccak/embedded.js +1 -1
  107. package/dist/keccak/index.d.ts +2 -0
  108. package/dist/keccak/index.js +4 -2
  109. package/dist/loader.d.ts +1 -19
  110. package/dist/loader.js +26 -32
  111. package/dist/merkle/blake3-tree.d.ts +35 -0
  112. package/dist/merkle/blake3-tree.js +187 -0
  113. package/dist/merkle/checkpoint.d.ts +58 -0
  114. package/dist/merkle/checkpoint.js +217 -0
  115. package/dist/merkle/index.d.ts +19 -0
  116. package/dist/merkle/index.js +37 -0
  117. package/dist/merkle/merkle-log.d.ts +130 -0
  118. package/dist/merkle/merkle-log.js +207 -0
  119. package/dist/merkle/merkle-verifier.d.ts +126 -0
  120. package/dist/merkle/merkle-verifier.js +296 -0
  121. package/dist/merkle/proof.d.ts +70 -0
  122. package/dist/merkle/proof.js +300 -0
  123. package/dist/merkle/sha256-tree.d.ts +33 -0
  124. package/dist/merkle/sha256-tree.js +145 -0
  125. package/dist/merkle/signed-log.d.ts +156 -0
  126. package/dist/merkle/signed-log.js +356 -0
  127. package/dist/merkle/signed-note.d.ts +309 -0
  128. package/dist/merkle/signed-note.js +648 -0
  129. package/dist/merkle/sth.d.ts +31 -0
  130. package/dist/merkle/sth.js +31 -0
  131. package/dist/merkle/storage.d.ts +40 -0
  132. package/dist/merkle/storage.js +71 -0
  133. package/dist/merkle/tree.d.ts +68 -0
  134. package/dist/merkle/tree.js +94 -0
  135. package/dist/mldsa/embedded.d.ts +1 -0
  136. package/dist/{kyber → mldsa}/embedded.js +5 -5
  137. package/dist/mldsa/expand.d.ts +53 -0
  138. package/dist/mldsa/expand.js +188 -0
  139. package/dist/mldsa/format.d.ts +16 -0
  140. package/dist/mldsa/format.js +68 -0
  141. package/dist/mldsa/hashvariant.d.ts +32 -0
  142. package/dist/mldsa/hashvariant.js +248 -0
  143. package/dist/mldsa/index.d.ts +142 -0
  144. package/dist/mldsa/index.js +463 -0
  145. package/dist/mldsa/keygen.d.ts +16 -0
  146. package/dist/mldsa/keygen.js +232 -0
  147. package/dist/mldsa/params.d.ts +21 -0
  148. package/dist/mldsa/params.js +55 -0
  149. package/dist/mldsa/sha3-helpers.d.ts +30 -0
  150. package/dist/mldsa/sha3-helpers.js +124 -0
  151. package/dist/mldsa/sign.d.ts +36 -0
  152. package/dist/mldsa/sign.js +380 -0
  153. package/dist/mldsa/types.d.ts +91 -0
  154. package/dist/mldsa/types.js +25 -0
  155. package/dist/mldsa/validate.d.ts +55 -0
  156. package/dist/mldsa/validate.js +125 -0
  157. package/dist/mldsa/verify.d.ts +29 -0
  158. package/dist/mldsa/verify.js +269 -0
  159. package/dist/mldsa.wasm +0 -0
  160. package/dist/mlkem/embedded.d.ts +1 -0
  161. package/dist/mlkem/embedded.js +27 -0
  162. package/dist/mlkem/indcpa.d.ts +49 -0
  163. package/dist/{kyber → mlkem}/indcpa.js +48 -48
  164. package/dist/mlkem/index.d.ts +37 -0
  165. package/dist/{kyber → mlkem}/index.js +41 -31
  166. package/dist/mlkem/kem.d.ts +21 -0
  167. package/dist/{kyber → mlkem}/kem.js +48 -13
  168. package/dist/{kyber → mlkem}/params.d.ts +4 -4
  169. package/dist/{kyber → mlkem}/params.js +2 -2
  170. package/dist/mlkem/suite.d.ts +12 -0
  171. package/dist/{kyber → mlkem}/suite.js +17 -12
  172. package/dist/{kyber → mlkem}/types.d.ts +4 -3
  173. package/dist/{kyber → mlkem}/types.js +1 -1
  174. package/dist/mlkem/validate.d.ts +23 -0
  175. package/dist/{kyber → mlkem}/validate.js +24 -20
  176. package/dist/{kyber.wasm → mlkem.wasm} +0 -0
  177. package/dist/p256.wasm +0 -0
  178. package/dist/ratchet/index.d.ts +8 -0
  179. package/dist/ratchet/index.js +38 -0
  180. package/dist/ratchet/kdf-chain.d.ts +13 -0
  181. package/dist/ratchet/kdf-chain.js +85 -0
  182. package/dist/ratchet/ratchet-keypair.d.ts +9 -0
  183. package/dist/ratchet/ratchet-keypair.js +61 -0
  184. package/dist/ratchet/root-kdf.d.ts +4 -0
  185. package/dist/ratchet/root-kdf.js +124 -0
  186. package/dist/ratchet/skipped-key-store.d.ts +14 -0
  187. package/dist/ratchet/skipped-key-store.js +154 -0
  188. package/dist/ratchet/types.d.ts +36 -0
  189. package/dist/ratchet/types.js +26 -0
  190. package/dist/serpent/cipher-suite.d.ts +10 -0
  191. package/dist/serpent/cipher-suite.js +144 -56
  192. package/dist/serpent/generator.d.ts +12 -0
  193. package/dist/serpent/generator.js +97 -0
  194. package/dist/serpent/index.d.ts +62 -1
  195. package/dist/serpent/index.js +97 -21
  196. package/dist/serpent/pool-worker.js +28 -102
  197. package/dist/serpent/serpent-cbc.d.ts +16 -6
  198. package/dist/serpent/serpent-cbc.js +58 -37
  199. package/dist/serpent/shared-ops.d.ts +63 -0
  200. package/dist/serpent/shared-ops.js +178 -0
  201. package/dist/serpent/types.d.ts +1 -5
  202. package/dist/serpent.wasm +0 -0
  203. package/dist/sha2/hash.d.ts +2 -0
  204. package/dist/sha2/hash.js +53 -0
  205. package/dist/sha2/hkdf.js +5 -5
  206. package/dist/sha2/index.d.ts +22 -1
  207. package/dist/sha2/index.js +80 -11
  208. package/dist/sha2/types.d.ts +41 -2
  209. package/dist/sha2.wasm +0 -0
  210. package/dist/sha3/hash.d.ts +2 -0
  211. package/dist/sha3/hash.js +53 -0
  212. package/dist/sha3/index.d.ts +87 -3
  213. package/dist/sha3/index.js +317 -19
  214. package/dist/sha3/kmac.d.ts +121 -0
  215. package/dist/sha3/kmac.js +800 -0
  216. package/dist/sha3.wasm +0 -0
  217. package/dist/shared/pkcs7.d.ts +22 -0
  218. package/dist/shared/pkcs7.js +84 -0
  219. package/dist/sign/ctx.d.ts +41 -0
  220. package/dist/sign/ctx.js +102 -0
  221. package/dist/sign/envelope.d.ts +45 -0
  222. package/dist/sign/envelope.js +152 -0
  223. package/dist/sign/hasher.d.ts +9 -0
  224. package/dist/sign/hasher.js +132 -0
  225. package/dist/sign/index.d.ts +11 -0
  226. package/dist/sign/index.js +34 -0
  227. package/dist/sign/sign-stream.d.ts +25 -0
  228. package/dist/sign/sign-stream.js +112 -0
  229. package/dist/sign/suites/ecdsa-p256.d.ts +2 -0
  230. package/dist/sign/suites/ecdsa-p256.js +120 -0
  231. package/dist/sign/suites/ed25519.d.ts +3 -0
  232. package/dist/sign/suites/ed25519.js +165 -0
  233. package/dist/sign/suites/hybrid-classical.d.ts +23 -0
  234. package/dist/sign/suites/hybrid-classical.js +526 -0
  235. package/dist/sign/suites/hybrid-pq.d.ts +4 -0
  236. package/dist/sign/suites/hybrid-pq.js +234 -0
  237. package/dist/sign/suites/mldsa.d.ts +7 -0
  238. package/dist/sign/suites/mldsa.js +161 -0
  239. package/dist/sign/suites/slhdsa.d.ts +7 -0
  240. package/dist/sign/suites/slhdsa.js +176 -0
  241. package/dist/sign/types.d.ts +106 -0
  242. package/dist/sign/types.js +28 -0
  243. package/dist/sign/verify-stream.d.ts +30 -0
  244. package/dist/sign/verify-stream.js +227 -0
  245. package/dist/slhdsa/embedded.d.ts +1 -0
  246. package/dist/slhdsa/embedded.js +26 -0
  247. package/dist/slhdsa/index.d.ts +149 -0
  248. package/dist/slhdsa/index.js +493 -0
  249. package/dist/slhdsa/params.d.ts +26 -0
  250. package/dist/slhdsa/params.js +70 -0
  251. package/dist/slhdsa/prehash.d.ts +68 -0
  252. package/dist/slhdsa/prehash.js +307 -0
  253. package/dist/slhdsa/sign.d.ts +39 -0
  254. package/dist/slhdsa/sign.js +116 -0
  255. package/dist/slhdsa/types.d.ts +129 -0
  256. package/dist/slhdsa/types.js +27 -0
  257. package/dist/slhdsa/validate.d.ts +60 -0
  258. package/dist/slhdsa/validate.js +127 -0
  259. package/dist/slhdsa/verify.d.ts +32 -0
  260. package/dist/slhdsa/verify.js +107 -0
  261. package/dist/slhdsa.wasm +0 -0
  262. package/dist/stream/header.js +8 -8
  263. package/dist/stream/index.d.ts +1 -0
  264. package/dist/stream/index.js +1 -0
  265. package/dist/stream/open-stream.js +65 -22
  266. package/dist/stream/seal-stream-pool.d.ts +2 -0
  267. package/dist/stream/seal-stream-pool.js +100 -33
  268. package/dist/stream/seal-stream.d.ts +1 -1
  269. package/dist/stream/seal-stream.js +48 -19
  270. package/dist/stream/seal.js +6 -6
  271. package/dist/stream/types.d.ts +3 -1
  272. package/dist/stream/types.js +1 -1
  273. package/dist/types.d.ts +22 -1
  274. package/dist/types.js +1 -1
  275. package/dist/utils.d.ts +9 -10
  276. package/dist/utils.js +84 -59
  277. package/dist/wasm-source.d.ts +9 -8
  278. package/dist/wasm-source.js +1 -1
  279. package/dist/x25519/embedded.d.ts +1 -0
  280. package/dist/x25519/embedded.js +31 -0
  281. package/dist/x25519/index.d.ts +43 -0
  282. package/dist/x25519/index.js +159 -0
  283. package/dist/x25519/types.d.ts +25 -0
  284. package/dist/x25519/types.js +27 -0
  285. package/dist/x25519/validate.d.ts +2 -0
  286. package/dist/x25519/validate.js +39 -0
  287. package/package.json +123 -64
  288. package/SECURITY.md +0 -276
  289. package/dist/ct-wasm.d.ts +0 -1
  290. package/dist/ct-wasm.js +0 -3
  291. package/dist/ct.wasm +0 -0
  292. package/dist/docs/aead.md +0 -323
  293. package/dist/docs/architecture.md +0 -932
  294. package/dist/docs/argon2id.md +0 -302
  295. package/dist/docs/chacha20.md +0 -674
  296. package/dist/docs/exports.md +0 -241
  297. package/dist/docs/fortuna.md +0 -313
  298. package/dist/docs/init.md +0 -302
  299. package/dist/docs/loader.md +0 -161
  300. package/dist/docs/serpent.md +0 -519
  301. package/dist/docs/sha2.md +0 -613
  302. package/dist/docs/sha3.md +0 -546
  303. package/dist/docs/types.md +0 -276
  304. package/dist/docs/utils.md +0 -367
  305. package/dist/embedded/kyber.d.ts +0 -1
  306. package/dist/embedded/kyber.js +0 -3
  307. package/dist/kyber/embedded.d.ts +0 -1
  308. package/dist/kyber/indcpa.d.ts +0 -49
  309. package/dist/kyber/index.d.ts +0 -38
  310. package/dist/kyber/kem.d.ts +0 -21
  311. package/dist/kyber/suite.d.ts +0 -13
  312. 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[];