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