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
package/dist/init.js CHANGED
@@ -1,32 +1,98 @@
1
1
  import { loadWasm } from './loader.js';
2
2
  import { hasSIMD } from './utils.js';
3
- // 'keccak' is an alias for 'sha3' same WASM binary, same instance slot
3
+ // 'keccak' is an alias for 'sha3', same WASM binary, same instance slot
4
4
  const ALIASES = { keccak: 'sha3' };
5
5
  function resolve(mod) {
6
6
  return ALIASES[mod] ?? mod;
7
7
  }
8
- // Module-scope cache: one WebAssembly.Instance per canonical module
8
+ // Per-canonical-module instance cache.
9
9
  const instances = new Map();
10
+ // Coalesces concurrent initModule calls.
11
+ const pending = new Map();
12
+ // Per-module exclusivity token held by a stateful wrapper.
13
+ const owners = new Map();
10
14
  export async function initModule(mod, source) {
11
15
  const resolved = resolve(mod);
12
16
  if (instances.has(resolved))
13
17
  return;
14
- if ((resolved === 'serpent' || resolved === 'chacha20' || resolved === 'kyber') && !hasSIMD())
15
- throw new Error('leviathan-crypto: serpent, chacha20, and kyber require WebAssembly SIMD — '
18
+ const inflight = pending.get(resolved);
19
+ if (inflight) {
20
+ await inflight;
21
+ return;
22
+ }
23
+ if ((resolved === 'serpent' || resolved === 'chacha20' || resolved === 'mlkem' || resolved === 'aes' || resolved === 'mldsa' || resolved === 'blake3') && !hasSIMD())
24
+ throw new Error('leviathan-crypto: serpent, chacha20, mlkem, aes, mldsa, and blake3 require WebAssembly SIMD, '
16
25
  + 'this runtime does not support it');
17
- instances.set(resolved, await loadWasm(source));
26
+ const p = loadWasm(source);
27
+ pending.set(resolved, p);
28
+ try {
29
+ instances.set(resolved, await p);
30
+ }
31
+ finally {
32
+ pending.delete(resolved);
33
+ }
18
34
  }
19
35
  export function getInstance(mod) {
20
- const inst = instances.get(resolve(mod));
36
+ const r = resolve(mod);
37
+ const inst = instances.get(r);
21
38
  if (!inst) {
22
39
  throw new Error(`leviathan-crypto: call init({ ${mod}: ... }) before using this class`);
23
40
  }
41
+ if (owners.has(r)) {
42
+ throw new Error(`leviathan-crypto: another stateful instance is using the '${r}' WASM module, `
43
+ + 'call dispose() on it before constructing a new one');
44
+ }
24
45
  return inst;
25
46
  }
26
47
  export function isInitialized(mod) {
27
48
  return instances.has(resolve(mod));
28
49
  }
29
- /** Reset all cached instances — for testing only */
50
+ /**
51
+ * Acquire exclusive access to `mod`. Throws if another stateful instance
52
+ * currently holds it. Returned token must be passed to `_releaseModule`.
53
+ * @internal
54
+ */
55
+ export function _acquireModule(mod) {
56
+ const r = resolve(mod);
57
+ if (owners.has(r))
58
+ throw new Error(`leviathan-crypto: another stateful instance is using the '${r}' WASM module, `
59
+ + 'call dispose() on it before constructing a new one');
60
+ const tok = Symbol(r);
61
+ owners.set(r, tok);
62
+ return tok;
63
+ }
64
+ /**
65
+ * Release exclusive access. No-op if the token doesn't match the current
66
+ * owner (makes dispose idempotent).
67
+ * @internal
68
+ */
69
+ export function _releaseModule(mod, tok) {
70
+ const r = resolve(mod);
71
+ if (owners.get(r) === tok)
72
+ owners.delete(r);
73
+ }
74
+ /**
75
+ * True if a stateful instance currently holds the module.
76
+ * @internal
77
+ */
78
+ export function _isModuleBusy(mod) {
79
+ return owners.has(resolve(mod));
80
+ }
81
+ /** @internal */
82
+ export function _assertNotOwned(mod) {
83
+ const r = resolve(mod);
84
+ if (owners.has(r))
85
+ throw new Error(`leviathan-crypto: another stateful instance is using the '${r}' WASM module, `
86
+ + 'call dispose() on it before constructing a new one');
87
+ }
88
+ /**
89
+ * Reset all cached instances, for testing only. Clears `instances`, `pending`,
90
+ * and `owners` so tests can re-exercise module lifecycle (init, exclusivity,
91
+ * race) from a known-empty state.
92
+ * @internal
93
+ */
30
94
  export function _resetForTesting() {
31
95
  instances.clear();
96
+ pending.clear();
97
+ owners.clear();
32
98
  }
@@ -22,6 +22,6 @@
22
22
  // src/ts/keccak/embedded.ts
23
23
  //
24
24
  // Re-exports the sha3 WASM blob under the keccak name.
25
- // The keccak alias shares the sha3 binary no separate keccak.wasm exists.
25
+ // The keccak alias shares the sha3 binary, no separate keccak.wasm exists.
26
26
  // Import via `leviathan-crypto/keccak/embedded`.
27
27
  export { WASM_GZ_BASE64 as keccakWasm } from '../embedded/sha3.js';
@@ -1,4 +1,6 @@
1
1
  import type { WasmSource } from '../wasm-source.js';
2
2
  export declare function keccakInit(source: WasmSource): Promise<void>;
3
3
  export type { WasmSource };
4
+ export { isInitialized } from '../init.js';
4
5
  export { SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256 } from '../sha3/index.js';
6
+ export { CSHAKE128, CSHAKE256, KMAC128, KMAC256, KMACXOF128, KMACXOF256 } from '../sha3/index.js';
@@ -21,11 +21,13 @@
21
21
  //
22
22
  // src/ts/keccak/index.ts
23
23
  //
24
- // Keccak alias subpath resolves to the sha3 WASM module slot.
25
- // Consumers who prefer the Keccak name (e.g. Kyber/ML-KEM implementations)
24
+ // Keccak alias subpath, resolves to the sha3 WASM module slot.
25
+ // Consumers who prefer the Keccak name (e.g. ML-KEM implementations)
26
26
  // can import from `leviathan-crypto/keccak` instead of `leviathan-crypto/sha3`.
27
27
  import { initModule } from '../init.js';
28
28
  export async function keccakInit(source) {
29
29
  return initModule('keccak', source);
30
30
  }
31
+ export { isInitialized } from '../init.js';
31
32
  export { SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256 } from '../sha3/index.js';
33
+ export { CSHAKE128, CSHAKE256, KMAC128, KMAC256, KMACXOF128, KMACXOF256 } from '../sha3/index.js';
package/dist/loader.d.ts CHANGED
@@ -1,19 +1 @@
1
- import type { WasmSource } from './wasm-source.js';
2
- /**
3
- * Decode a gzip+base64 embedded WASM string to raw bytes.
4
- * Guards against missing DecompressionStream (Node <18, non-browser runtimes).
5
- * Exported for pool worker launchers that decode blobs before spawning threads.
6
- */
7
- export declare function decodeWasm(b64: string): Promise<Uint8Array>;
8
- /**
9
- * Compile a WASM source to a Module without instantiating.
10
- * Used by pool infrastructure to send compiled modules to workers.
11
- */
12
- export declare function compileWasm(source: WasmSource): Promise<WebAssembly.Module>;
13
- /**
14
- * Load a WASM module from any accepted source type.
15
- * The loading strategy is inferred from the argument type — no mode string.
16
- *
17
- * Throws `TypeError` for null, numeric, or unrecognised inputs.
18
- */
19
- export declare function loadWasm(source: WasmSource): Promise<WebAssembly.Instance>;
1
+ export {};
package/dist/loader.js CHANGED
@@ -1,8 +1,4 @@
1
1
  import { base64ToBytes as _b64 } from './utils.js';
2
- // Each WASM module gets its own fresh Memory — never shared between instances.
3
- function makeImports() {
4
- return { env: { memory: new WebAssembly.Memory({ initial: 3, maximum: 3 }) } };
5
- }
6
2
  // TS 5.9 generified Uint8Array<TArrayBuffer> with default ArrayBufferLike, which
7
3
  // no longer satisfies BufferSource = ArrayBufferView<ArrayBuffer> | ArrayBuffer.
8
4
  // Convert Uint8Array to a proper ArrayBuffer before calling WebAssembly APIs.
@@ -17,14 +13,14 @@ function toArrayBuffer(bytes) {
17
13
  * Decode a gzip+base64 embedded WASM string to raw bytes.
18
14
  * Guards against missing DecompressionStream (Node <18, non-browser runtimes).
19
15
  * Exported for pool worker launchers that decode blobs before spawning threads.
16
+ * @internal
20
17
  */
21
18
  export async function decodeWasm(b64) {
22
19
  if (typeof DecompressionStream === 'undefined')
23
- throw new Error('leviathan-crypto: DecompressionStream not available '
20
+ throw new Error('leviathan-crypto: DecompressionStream not available, '
24
21
  + 'use a URL, ArrayBuffer, or WebAssembly.Module source in this runtime');
22
+ // _b64 throws RangeError on invalid base64, no nullish check required.
25
23
  const compressed = _b64(b64);
26
- if (!compressed)
27
- throw new Error('leviathan-crypto: corrupt embedded WASM — base64 decode failed');
28
24
  const ds = new DecompressionStream('gzip');
29
25
  const writer = ds.writable.getWriter();
30
26
  const reader = ds.readable.getReader();
@@ -44,14 +40,23 @@ export async function decodeWasm(b64) {
44
40
  }
45
41
  return out;
46
42
  }
43
+ // Cap thenable-source nesting at 3 to prevent runaway recursion.
44
+ const MAX_THENABLE_DEPTH = 3;
47
45
  /**
48
46
  * Compile a WASM source to a Module without instantiating.
49
47
  * Used by pool infrastructure to send compiled modules to workers.
48
+ *
49
+ * Thenable sources (Promise<Response>, Promise<ArrayBuffer>, etc.) are
50
+ * resolved and then re-dispatched by the runtime type of the resolved value.
51
+ * Depth is capped at `MAX_THENABLE_DEPTH` to prevent runaway recursion.
52
+ * @internal
50
53
  */
51
- export async function compileWasm(source) {
54
+ export async function compileWasm(source, depth = 0) {
55
+ if (depth > MAX_THENABLE_DEPTH)
56
+ throw new TypeError(`leviathan-crypto: thenable nesting too deep (max ${MAX_THENABLE_DEPTH})`);
52
57
  if (typeof source === 'string') {
53
58
  if (source.length === 0)
54
- throw new TypeError('leviathan-crypto: invalid WasmSource empty string');
59
+ throw new TypeError('leviathan-crypto: invalid WasmSource, empty string');
55
60
  return WebAssembly.compile(toArrayBuffer(await decodeWasm(source)));
56
61
  }
57
62
  if (source instanceof URL)
@@ -64,33 +69,22 @@ export async function compileWasm(source) {
64
69
  return source;
65
70
  if (typeof Response !== 'undefined' && source instanceof Response)
66
71
  return WebAssembly.compileStreaming(source);
67
- if (source != null && typeof source.then === 'function')
68
- return WebAssembly.compileStreaming(source);
69
- throw new TypeError(`leviathan-crypto: invalid WasmSource — got ${source === null ? 'null' : typeof source}`);
72
+ if (source != null && typeof source.then === 'function') {
73
+ const resolved = await source;
74
+ return compileWasm(resolved, depth + 1);
75
+ }
76
+ throw new TypeError(`leviathan-crypto: invalid WasmSource, got ${source === null ? 'null' : typeof source}`);
70
77
  }
71
78
  /**
72
79
  * Load a WASM module from any accepted source type.
73
- * The loading strategy is inferred from the argument type no mode string.
80
+ * The loading strategy is inferred from the argument type, no mode string.
74
81
  *
75
- * Throws `TypeError` for null, numeric, or unrecognised inputs.
82
+ * Throws `TypeError` for null, numeric, or unrecognised inputs, or if a
83
+ * thenable source nests deeper than `MAX_THENABLE_DEPTH`.
84
+ * @internal
76
85
  */
77
86
  export async function loadWasm(source) {
78
- if (typeof source === 'string') {
79
- if (source.length === 0)
80
- throw new TypeError('leviathan-crypto: invalid WasmSource — empty string');
81
- return (await WebAssembly.instantiate(toArrayBuffer(await decodeWasm(source)), makeImports())).instance;
82
- }
83
- if (source instanceof URL)
84
- return (await WebAssembly.instantiateStreaming(fetch(source.href), makeImports())).instance;
85
- if (source instanceof ArrayBuffer)
86
- return (await WebAssembly.instantiate(source, makeImports())).instance;
87
- if (source instanceof Uint8Array)
88
- return (await WebAssembly.instantiate(toArrayBuffer(source), makeImports())).instance;
89
- if (source instanceof WebAssembly.Module)
90
- return WebAssembly.instantiate(source, makeImports());
91
- if (typeof Response !== 'undefined' && source instanceof Response)
92
- return (await WebAssembly.instantiateStreaming(source, makeImports())).instance;
93
- if (source != null && typeof source.then === 'function')
94
- return (await WebAssembly.instantiateStreaming(source, makeImports())).instance;
95
- throw new TypeError(`leviathan-crypto: invalid WasmSource — got ${source === null ? 'null' : typeof source}`);
87
+ // All modules export their own memory; no host imports today.
88
+ const mod = await compileWasm(source);
89
+ return WebAssembly.instantiate(mod);
96
90
  }
@@ -0,0 +1,35 @@
1
+ import type { Hasher, MerkleTree } from './tree.js';
2
+ import type { MerkleStorage } from './storage.js';
3
+ /**
4
+ * BLAKE3-native `Hasher` (BLAKE3 §2.3, §2.4, §2.5).
5
+ *
6
+ * Empty-tree value is `BLAKE3()`; leaves are `BLAKE3(leaf)`; internal
7
+ * nodes are the §2.5 parent compress over the two child CVs with the
8
+ * BLAKE3 IV as the starting CV and `modeFlags = 0`, `isRoot = 0`.
9
+ *
10
+ * Stateless and reentrant: each method acquires the blake3 module
11
+ * fresh, runs the operation, and releases. No `dispose()` is needed.
12
+ */
13
+ export declare const Blake3Hasher: Hasher;
14
+ /**
15
+ * Stateful BLAKE3 Merkle log. Same surface and storage discipline as
16
+ * `Sha256Tree`: leaf hashes and every perfect aligned internal subtree
17
+ * hash live in the injected `MerkleStorage`; partial right-edge subtrees
18
+ * are recomputed on demand.
19
+ *
20
+ * `append` is the only mutator and is the leaf-hash factory; consumers
21
+ * feed leaf bytes, not pre-computed leaf hashes.
22
+ */
23
+ export declare class Blake3Tree implements MerkleTree {
24
+ readonly hasher: Hasher;
25
+ private readonly storage;
26
+ constructor(storage: MerkleStorage);
27
+ size(): number;
28
+ rootHash(): Uint8Array;
29
+ append(leafBytes: Uint8Array): {
30
+ leafIndex: number;
31
+ leafHash: Uint8Array;
32
+ };
33
+ getInclusionProof(leafIndex: number, treeSize?: number): Uint8Array[];
34
+ getConsistencyProof(oldSize: number, newSize: number): Uint8Array[];
35
+ }
@@ -0,0 +1,187 @@
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/blake3-tree.ts
23
+ //
24
+ // BLAKE3 Hasher / MerkleTree. Domain separation comes from BLAKE3's own
25
+ // §2.4 CHUNK_START / CHUNK_END / ROOT and §2.5 PARENT flags, not from
26
+ // RFC 6962-style 0x00 / 0x01 prefix bytes (those would be redundant and
27
+ // discard `compress4` parallelism at the internal-node layer).
28
+ //
29
+ // Composition:
30
+ // hashEmpty() = BLAKE3() §2.5
31
+ // hashLeaf(leaf) = BLAKE3(leaf) §2.4
32
+ // hashInternal(left, right) = _testParentCV(left, right, IV, 0, 0) §2.5
33
+ //
34
+ // Parent compress runs with modeFlags = 0 and isRoot = 0 at every level.
35
+ // The root flag is the SignedLog layer's concern; the tree's top hash
36
+ // exits as a plain CV, keeping `hashInternal` symmetric.
37
+ import { BLAKE3 } from '../blake3/index.js';
38
+ import { getInstance } from '../init.js';
39
+ import { buildConsistencyProof, buildInclusionProof, subtreeHash, } from './proof.js';
40
+ function getBlake3Exports() {
41
+ return getInstance('blake3').exports;
42
+ }
43
+ // ── Scratch layout for `_testParentCV` ──────────────────────────────────────
44
+ //
45
+ // Second-page offsets (past BUFFER_END = 26328 from
46
+ // `src/asm/blake3/buffers.ts`) are untouched by the §2.4 chunk pipeline
47
+ // and §2.5 tree-assembly queues; safe for caller-supplied scratch.
48
+ const PARENT_LEFT_OFF = 65536;
49
+ const PARENT_RIGHT_OFF = PARENT_LEFT_OFF + 32;
50
+ const PARENT_START_OFF = PARENT_RIGHT_OFF + 32;
51
+ const PARENT_OUT_OFF = PARENT_START_OFF + 32;
52
+ const PARENT_SCRATCH_LEN = 128;
53
+ // BLAKE3 §2.2 Table 1: the BLAKE3 IV equals the FIPS 180-4 SHA-256 IV,
54
+ // packed as eight u32 little-endian words. The IV is the starting CV
55
+ // for default-mode parent compresses (BLAKE3 §2.5, Tree Mode).
56
+ const BLAKE3_IV_BYTES = (() => {
57
+ const iv32 = new Uint32Array([
58
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
59
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
60
+ ]);
61
+ return new Uint8Array(iv32.buffer);
62
+ })();
63
+ const BLAKE3_OUTPUT = 32;
64
+ const BLAKE3_WASM_MODULES = Object.freeze(['blake3']);
65
+ // ── Blake3Hasher const ──────────────────────────────────────────────────────
66
+ /**
67
+ * BLAKE3-native `Hasher` (BLAKE3 §2.3, §2.4, §2.5).
68
+ *
69
+ * Empty-tree value is `BLAKE3()`; leaves are `BLAKE3(leaf)`; internal
70
+ * nodes are the §2.5 parent compress over the two child CVs with the
71
+ * BLAKE3 IV as the starting CV and `modeFlags = 0`, `isRoot = 0`.
72
+ *
73
+ * Stateless and reentrant: each method acquires the blake3 module
74
+ * fresh, runs the operation, and releases. No `dispose()` is needed.
75
+ */
76
+ export const Blake3Hasher = Object.freeze({
77
+ name: 'blake3',
78
+ outputSize: BLAKE3_OUTPUT,
79
+ wasmModules: BLAKE3_WASM_MODULES,
80
+ hashEmpty() {
81
+ // BLAKE3 §2.5, Tree Mode: the natural tree-mode root for an
82
+ // empty input is `BLAKE3()` itself. The chunk machine handles
83
+ // the single empty chunk (CHUNK_START | CHUNK_END | ROOT)
84
+ // internally and returns the 32-byte XOF prefix.
85
+ const h = new BLAKE3();
86
+ try {
87
+ return h.hash(new Uint8Array(0));
88
+ }
89
+ finally {
90
+ h.dispose();
91
+ }
92
+ },
93
+ hashLeaf(leaf) {
94
+ // BLAKE3 §2.4, Chunks: the chunk pipeline applies CHUNK_START,
95
+ // CHUNK_END, and ROOT flags internally. The caller sees a plain
96
+ // 32-byte hash; leaf-vs-internal domain separation is BLAKE3's
97
+ // job through these flag bytes, not the caller's job through
98
+ // prefix bytes.
99
+ const h = new BLAKE3();
100
+ try {
101
+ return h.hash(leaf);
102
+ }
103
+ finally {
104
+ h.dispose();
105
+ }
106
+ },
107
+ hashInternal(left, right) {
108
+ if (left.length !== BLAKE3_OUTPUT)
109
+ throw new RangeError(`Blake3Hasher.hashInternal: left must be ${BLAKE3_OUTPUT} bytes, got ${left.length}`);
110
+ if (right.length !== BLAKE3_OUTPUT)
111
+ throw new RangeError(`Blake3Hasher.hashInternal: right must be ${BLAKE3_OUTPUT} bytes, got ${right.length}`);
112
+ // BLAKE3 §2.5, Tree Mode: parent compress over (left || right)
113
+ // with IV as the starting CV and PARENT as the only flag bit
114
+ // (modeFlags = 0 selects default mode; isRoot = 0 keeps the
115
+ // node generic so callers can stack identical compresses up
116
+ // the tree).
117
+ const x = getBlake3Exports();
118
+ const mem = new Uint8Array(x.memory.buffer);
119
+ mem.set(left, PARENT_LEFT_OFF);
120
+ mem.set(right, PARENT_RIGHT_OFF);
121
+ mem.set(BLAKE3_IV_BYTES, PARENT_START_OFF);
122
+ try {
123
+ x._testParentCV(PARENT_LEFT_OFF, PARENT_RIGHT_OFF, PARENT_START_OFF, 0, 0, PARENT_OUT_OFF);
124
+ return mem.slice(PARENT_OUT_OFF, PARENT_OUT_OFF + BLAKE3_OUTPUT);
125
+ }
126
+ finally {
127
+ mem.fill(0, PARENT_LEFT_OFF, PARENT_LEFT_OFF + PARENT_SCRATCH_LEN);
128
+ x.wipeBuffers();
129
+ }
130
+ },
131
+ });
132
+ // ── Blake3Tree class ────────────────────────────────────────────────────────
133
+ /**
134
+ * Stateful BLAKE3 Merkle log. Same surface and storage discipline as
135
+ * `Sha256Tree`: leaf hashes and every perfect aligned internal subtree
136
+ * hash live in the injected `MerkleStorage`; partial right-edge subtrees
137
+ * are recomputed on demand.
138
+ *
139
+ * `append` is the only mutator and is the leaf-hash factory; consumers
140
+ * feed leaf bytes, not pre-computed leaf hashes.
141
+ */
142
+ export class Blake3Tree {
143
+ hasher = Blake3Hasher;
144
+ storage;
145
+ constructor(storage) {
146
+ this.storage = storage;
147
+ }
148
+ size() {
149
+ return this.storage.size();
150
+ }
151
+ rootHash() {
152
+ const n = this.storage.size();
153
+ if (n === 0)
154
+ return this.hasher.hashEmpty();
155
+ const getNode = (level, index) => this.storage.getNode(level, index);
156
+ return subtreeHash(this.hasher, 0, n, getNode);
157
+ }
158
+ append(leafBytes) {
159
+ const leafIndex = this.storage.size();
160
+ const leafHash = this.hasher.hashLeaf(leafBytes);
161
+ this.storage.appendLeaf(leafIndex, leafHash);
162
+ let level = 0;
163
+ let idx = leafIndex;
164
+ while ((idx & 1) === 1) {
165
+ const left = this.storage.getNode(level, idx - 1);
166
+ const right = this.storage.getNode(level, idx);
167
+ const parent = this.hasher.hashInternal(left, right);
168
+ this.storage.putNode(level + 1, idx >>> 1, parent);
169
+ idx = idx >>> 1;
170
+ level++;
171
+ }
172
+ return { leafIndex, leafHash };
173
+ }
174
+ getInclusionProof(leafIndex, treeSize) {
175
+ const ts = treeSize ?? this.storage.size();
176
+ if (!Number.isInteger(ts) || ts < 1 || ts > this.storage.size())
177
+ throw new RangeError(`Blake3Tree.getInclusionProof: treeSize ${ts} out of range [1, ${this.storage.size()}]`);
178
+ const getNode = (level, index) => this.storage.getNode(level, index);
179
+ return buildInclusionProof({ hasher: this.hasher, leafIndex, treeSize: ts, getNode });
180
+ }
181
+ getConsistencyProof(oldSize, newSize) {
182
+ if (!Number.isInteger(newSize) || newSize < 0 || newSize > this.storage.size())
183
+ throw new RangeError(`Blake3Tree.getConsistencyProof: newSize ${newSize} out of range [0, ${this.storage.size()}]`);
184
+ const getNode = (level, index) => this.storage.getNode(level, index);
185
+ return buildConsistencyProof({ hasher: this.hasher, oldSize, newSize, getNode });
186
+ }
187
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Decoded form of a c2sp.org/tlog-checkpoint body. The body shape is
3
+ * hash-and-algo-agnostic: `rootHash` is 32 bytes for both the SHA-256 and
4
+ * BLAKE3 trees Phase 7 ships, but the codec only enforces a caller-supplied
5
+ * length in `parseCheckpointBody`. The signed-note envelope that wraps a
6
+ * checkpoint is handled in `signed-note.ts`.
7
+ */
8
+ export interface Checkpoint {
9
+ /**
10
+ * Log identity, non-empty UTF-8 with no Unicode spaces, plus signs, or
11
+ * embedded newlines. Per c2sp.org/tlog-checkpoint §Note text the origin
12
+ * SHOULD be a schemeless URL such as `example.com/log42`, but the codec
13
+ * only enforces the MUST-level structural constraints; broader URL
14
+ * shape policy is a caller concern.
15
+ */
16
+ readonly origin: string;
17
+ /**
18
+ * Number of leaves in the tree at signing time. Must be a non-negative
19
+ * safe integer; ASCII decimal serialization carries no leading zeroes
20
+ * (the literal `0` is the only valid string starting with `0`).
21
+ */
22
+ readonly treeSize: number;
23
+ /**
24
+ * Merkle root hash. 32 bytes for both Sha256Tree and Blake3Tree; the
25
+ * caller-supplied `expectedHashLen` parameter on `parseCheckpointBody`
26
+ * pins the exact length for a given hasher.
27
+ */
28
+ readonly rootHash: Uint8Array;
29
+ }
30
+ /**
31
+ * Serialize a Checkpoint into its canonical body bytes per
32
+ * c2sp.org/tlog-checkpoint §Note text. Layout:
33
+ *
34
+ * utf8(origin) || 0x0A || utf8(decimal(treeSize)) || 0x0A
35
+ * || base64(rootHash) || 0x0A
36
+ *
37
+ * Base64 uses the RFC 4648 §4 standard alphabet with `=` padding (NOT the
38
+ * URL-safe variant from §5 and NOT padding-stripped). The body has no
39
+ * leading or trailing whitespace beyond the final 0x0A; byte stability
40
+ * is the entire purpose of the codec, since the body bytes are what the
41
+ * STH signature is computed over.
42
+ */
43
+ export declare function serializeCheckpointBody(c: Checkpoint): Uint8Array;
44
+ /**
45
+ * Parse a canonical checkpoint body. Inverse of `serializeCheckpointBody`;
46
+ * round-trips byte-for-byte. Rejects extension lines, leading or trailing
47
+ * whitespace beyond the mandatory final 0x0A, non-newline ASCII control
48
+ * characters, malformed base64, and root hashes whose decoded length does
49
+ * not match `expectedHashLen` (default 32, the size for both Sha256Tree
50
+ * and Blake3Tree).
51
+ *
52
+ * The caller pins `expectedHashLen` to its hasher's `outputSize`; a future
53
+ * SignedLog (TASK-4) will bind this to the tree's hasher automatically.
54
+ *
55
+ * Per c2sp.org/tlog-checkpoint §Note text and c2sp.org/signed-note
56
+ * §Format.
57
+ */
58
+ export declare function parseCheckpointBody(bytes: Uint8Array, expectedHashLen?: number): Checkpoint;