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/fortuna.js CHANGED
@@ -21,31 +21,29 @@
21
21
  //
22
22
  // src/ts/fortuna.ts
23
23
  //
24
- // Fortuna CSPRNG Ferguson & Schneier, Practical Cryptography (2003), Chapter 9.
25
- // Backed by WASM Serpent-256 ECB (generator) and WASM SHA-256 (accumulator pools).
26
- // Requires init({ serpent: ..., sha2: ... }) before Fortuna.create().
27
- import { isInitialized } from './init.js';
28
- import { Serpent } from './serpent/index.js';
29
- import { SHA256 } from './sha2/index.js';
24
+ // Fortuna CSPRNG, Ferguson & Schneier, Practical Cryptography (2003), Chapter 9.
25
+ // Backed by a pluggable Generator (cipher PRF) and HashFn (accumulator hash).
26
+ // Requires init() for the modules used by the chosen generator and hash pair.
27
+ import { isInitialized, getInstance } from './init.js';
30
28
  import { wipe, utf8ToBytes, concat } from './utils.js';
31
29
  const isBrowser = typeof window !== 'undefined';
32
30
  const isNode = typeof process !== 'undefined' && typeof process.pid === 'number';
33
31
  /**
34
- * Fortuna CSPRNG spec §9.3–§9.5
32
+ * Fortuna CSPRNG, spec §9.39.5
35
33
  *
36
- * Use `Fortuna.create()` to instantiate. Direct construction is not allowed.
34
+ * Use `Fortuna.create({ generator, hash })` to instantiate. Direct construction is not allowed.
37
35
  */
38
36
  export class Fortuna {
39
37
  // ── Constants ──────────────────────────────────────────────────────────
40
38
  static NUM_POOLS = 32;
41
- static RESEED_LIMIT = 64; // bits pool 0 threshold (spec §9.5)
42
- static MS_PER_RESEED = 100; // ms minimum reseed interval (spec §9.5)
43
- static NODE_STATS_INTERVAL = 1000; // ms OS stats collector interval
44
- static CRYPTO_INTERVAL = 3000; // ms crypto.randomBytes interval
39
+ static RESEED_LIMIT = 64; // bits, pool 0 threshold (spec §9.5)
40
+ static MS_PER_RESEED = 100; // ms, minimum reseed interval (spec §9.5)
41
+ static NODE_STATS_INTERVAL = 1000; // ms, OS stats collector interval
42
+ static CRYPTO_INTERVAL = 3000; // ms, crypto.randomBytes interval
45
43
  // ── State ─────────────────────────────────────────────────────────────
46
- serpent;
47
- sha;
48
- poolHash; // 32 running SHA-256 chain hashes (32 bytes each)
44
+ gen;
45
+ hash;
46
+ poolHash; // 32 running hash chain values
49
47
  poolEntropy;
50
48
  genKey;
51
49
  genCnt;
@@ -62,25 +60,33 @@ export class Fortuna {
62
60
  timers = [];
63
61
  // ── Static factory ────────────────────────────────────────────────────
64
62
  static async create(opts) {
65
- if (!isInitialized('serpent'))
66
- throw new Error('leviathan-crypto: call init({ serpent: ..., sha2: ... }) before using Fortuna');
67
- if (!isInitialized('sha2'))
68
- throw new Error('leviathan-crypto: call init({ serpent: ..., sha2: ... }) before using Fortuna');
69
- const f = new Fortuna(opts?.msPerReseed ?? Fortuna.MS_PER_RESEED);
70
- f.initialize(opts?.entropy);
71
- // Force the first reseed — pool[0] is saturated by initialize(),
63
+ if (!opts || !opts.generator || !opts.hash)
64
+ throw new TypeError('leviathan-crypto: Fortuna.create() requires { generator, hash }');
65
+ if (opts.hash.outputSize !== opts.generator.keySize)
66
+ throw new RangeError(`leviathan-crypto: Fortuna requires hash.outputSize (${opts.hash.outputSize}) `
67
+ + `to match generator.keySize (${opts.generator.keySize})`);
68
+ const required = new Set([...opts.generator.wasmModules, ...opts.hash.wasmModules]);
69
+ for (const mod of required) {
70
+ if (!isInitialized(mod)) {
71
+ const args = [...required].map(m => `${m}: ...`).join(', ');
72
+ throw new Error(`leviathan-crypto: call init({ ${args} }) before using Fortuna`);
73
+ }
74
+ }
75
+ const f = new Fortuna(opts.generator, opts.hash, opts.msPerReseed ?? Fortuna.MS_PER_RESEED);
76
+ f.initialize(opts.entropy);
77
+ // Force the first reseed, pool[0] is saturated by initialize(),
72
78
  // so this call triggers an immediate reseed and guarantees get() never
73
79
  // returns undefined. The byte is discarded.
74
80
  f.get(1);
75
81
  return f;
76
82
  }
77
- constructor(msPerReseed) {
78
- this.serpent = new Serpent();
79
- this.sha = new SHA256();
83
+ constructor(gen, hash, msPerReseed) {
84
+ this.gen = gen;
85
+ this.hash = hash;
80
86
  this.poolHash = [];
81
87
  this.poolEntropy = [];
82
- this.genKey = new Uint8Array(32);
83
- this.genCnt = new Uint8Array(16);
88
+ this.genKey = new Uint8Array(gen.keySize);
89
+ this.genCnt = new Uint8Array(gen.counterSize);
84
90
  this.reseedCnt = 0;
85
91
  this.lastReseed = 0;
86
92
  this.entropyLevel = 0;
@@ -90,36 +96,42 @@ export class Fortuna {
90
96
  this.msPerReseed = msPerReseed;
91
97
  this.robin = { kbd: 0, mouse: 0, scroll: 0, touch: 0, motion: 0, time: 0, rnd: 0, dom: 0 };
92
98
  for (let i = 0; i < Fortuna.NUM_POOLS; i++) {
93
- this.poolHash.push(new Uint8Array(32)); // zero-initialized chain value
99
+ this.poolHash.push(new Uint8Array(hash.outputSize)); // zero-initialized chain value
94
100
  this.poolEntropy.push(0);
95
101
  }
96
102
  }
97
103
  // ── Public API ────────────────────────────────────────────────────────
98
- /** Get n random bytes. Always returns Uint8Array instance is guaranteed seeded after create(). */
104
+ /** Get n random bytes. Always returns Uint8Array, instance is guaranteed seeded after create(). */
99
105
  get(length) {
100
106
  if (this.disposed)
101
107
  throw new Error('Fortuna instance has been disposed');
102
- // Capture hrtime jitter at call time (Node.js) spec §9.5
108
+ // Capture hrtime jitter at call time (Node.js), spec §9.5
103
109
  if (isNode)
104
110
  this.captureHrtime();
105
- // Check reseed trigger spec §9.5
111
+ // Check reseed trigger, spec §9.5
106
112
  if (this.poolEntropy[0] >= Fortuna.RESEED_LIMIT &&
107
113
  Date.now() >= this.lastReseed + this.msPerReseed) {
108
114
  this.reseedCnt = (this.reseedCnt + 1) >>> 0; // u32 wrap
109
115
  let seed = new Uint8Array(0);
110
116
  let strength = 0;
111
117
  for (let i = 0; i < Fortuna.NUM_POOLS; i++) {
112
- if ((this.reseedCnt & (1 << i)) !== 0) {
118
+ // Practical Cryptography (Ferguson & Schneier, 2003) §9.5.5:
119
+ // pool P_i is used in reseed r iff 2^i divides r.
120
+ if ((this.reseedCnt & ((1 << i) - 1)) === 0) {
113
121
  // Pool digest = current chain hash
114
122
  seed = concat(seed, this.poolHash[i]);
115
123
  strength += this.poolEntropy[i];
116
- // Reset pool
117
- this.poolHash[i] = new Uint8Array(32);
124
+ // Reset pool, wipe old chain hash before dropping the reference.
125
+ const old = this.poolHash[i];
126
+ this.poolHash[i] = new Uint8Array(this.hash.outputSize);
127
+ wipe(old);
118
128
  this.poolEntropy[i] = 0;
119
129
  }
120
130
  }
121
131
  this.entropyLevel -= strength;
122
132
  this.reseed(seed);
133
+ // seed is built from concatenated pool-hash copies; wipe the temp.
134
+ wipe(seed);
123
135
  }
124
136
  return this.pseudoRandomData(length);
125
137
  }
@@ -140,64 +152,130 @@ export class Fortuna {
140
152
  stop() {
141
153
  if (this.disposed)
142
154
  throw new Error('Fortuna instance has been disposed');
155
+ // Mark disposed first so partially-disposed reentry throws cleanly.
156
+ this.disposed = true;
143
157
  this.stopCollectors();
144
158
  wipe(this.genKey);
145
159
  wipe(this.genCnt);
160
+ // Wipe the 32 pool-hash chain values so residual entropy-bearing
161
+ // bytes do not outlive the instance.
162
+ for (const p of this.poolHash)
163
+ wipe(p);
146
164
  this.reseedCnt = 0;
147
- this.disposed = true;
165
+ // Best-effort wipe of WASM scratch; surface the first error.
166
+ const required = new Set([...this.gen.wasmModules, ...this.hash.wasmModules]);
167
+ let err;
168
+ for (const mod of required) {
169
+ try {
170
+ getInstance(mod).exports.wipeBuffers();
171
+ }
172
+ catch (e) {
173
+ err ??= e;
174
+ }
175
+ }
176
+ if (err)
177
+ throw err;
148
178
  }
149
179
  // ── Test-only accessors ───────────────────────────────────────────────
150
- /** @internal exposed for testing key replacement */
180
+ /** @internal, exposed for testing key replacement */
151
181
  _getGenKey() {
152
182
  return this.genKey;
153
183
  }
154
- /** @internal exposed for testing pool state */
184
+ /** @internal, exposed for testing pool state */
155
185
  _getPoolEntropy() {
156
186
  return this.poolEntropy;
157
187
  }
158
- /** @internal exposed for testing reseed count */
188
+ /** @internal, exposed for testing reseed count */
159
189
  _getReseedCnt() {
160
190
  return this.reseedCnt;
161
191
  }
162
- // ── Generator (spec §9.4) ─────────────────────────────────────────────
163
- /** Generate n blocks of 16 bytes each. — spec §9.4 */
164
- generateBlocks(n) {
165
- const out = new Uint8Array(n * 16);
166
- for (let i = 0; i < n; i++) {
167
- // Encrypt genCnt with Serpent-256 ECB
168
- this.serpent.loadKey(this.genKey);
169
- out.set(this.serpent.encryptBlock(this.genCnt), i * 16);
170
- this.incrementCounter();
192
+ /** @internal, exposed for testing pool-hash backing arrays */
193
+ _getPoolHash() {
194
+ return this.poolHash;
195
+ }
196
+ /**
197
+ * @internal, test-only deterministic factory. Seeds pool[0] with the provided
198
+ * entropy and triggers one reseed directly, bypassing all OS entropy collection
199
+ * and the hrtime jitter capture in get(). This makes KAT vectors reproducible
200
+ * across runs. Not suitable for production use.
201
+ */
202
+ static async _createDeterministicForTesting(opts) {
203
+ if (!opts || !opts.generator || !opts.hash)
204
+ throw new TypeError('Fortuna._createDeterministicForTesting() requires { generator, hash, entropy }');
205
+ if (opts.hash.outputSize !== opts.generator.keySize)
206
+ throw new RangeError(`leviathan-crypto: Fortuna requires hash.outputSize (${opts.hash.outputSize}) `
207
+ + `to match generator.keySize (${opts.generator.keySize})`);
208
+ const required = new Set([...opts.generator.wasmModules, ...opts.hash.wasmModules]);
209
+ for (const mod of required) {
210
+ if (!isInitialized(mod)) {
211
+ const args = [...required].map(m => `${m}: ...`).join(', ');
212
+ throw new Error(`leviathan-crypto: call init({ ${args} }) before using Fortuna`);
213
+ }
171
214
  }
172
- return out;
215
+ const f = new Fortuna(opts.generator, opts.hash, 0);
216
+ // Seed pool[0] with the provided entropy, no OS collection.
217
+ f.addRandomEvent(opts.entropy, 0, opts.entropy.length * 8);
218
+ // Manually trigger reseed #1 without calling get(), get() calls captureHrtime()
219
+ // in Node.js which adds non-deterministic data before the reseed fires.
220
+ f.reseedFromPool0();
221
+ return f;
173
222
  }
174
- /** Get length pseudo-random bytes. — spec §9.4 */
223
+ // ── Generator (spec §9.4) ─────────────────────────────────────────────
224
+ /** Get length pseudo-random bytes., spec §9.4 */
175
225
  pseudoRandomData(length) {
176
- // Generate ceil(length/16) blocks — spec §9.4
177
- const blocks = Math.ceil(length / 16);
178
- const raw = this.generateBlocks(blocks);
179
- const output = raw.slice(0, length);
180
- // Key replacement — mandatory forward secrecy (spec §9.4)
181
- this.genKey = this.generateBlocks(2);
182
- return output;
226
+ const blocks = Math.ceil(length / this.gen.blockSize);
227
+ const out = this.gen.generate(this.genKey, this.genCnt, length);
228
+ // External counter advance, generator is stateless and does not mutate caller's counter
229
+ for (let i = 0; i < blocks; i++)
230
+ this.incrementCounter();
231
+ // Key replacement, mandatory forward secrecy (spec §9.4).
232
+ // Wipe the prior key BEFORE dropping its reference so no key bytes are
233
+ // reachable after key replacement; anyone holding a Uint8Array view to
234
+ // the old key now observes zero.
235
+ const newKey = this.gen.generate(this.genKey, this.genCnt, this.gen.keySize);
236
+ for (let i = 0; i < Math.ceil(this.gen.keySize / this.gen.blockSize); i++)
237
+ this.incrementCounter();
238
+ wipe(this.genKey);
239
+ this.genKey = newKey;
240
+ return out;
183
241
  }
184
- /** Reseed the generator spec §9.4 */
242
+ /** Reseed the generator, spec §9.4 */
185
243
  reseed(seed) {
186
- // genKey = SHA256(genKey ‖ seed)
187
- this.genKey = this.sha.hash(concat(this.genKey, seed));
188
- // Increment counter — makes it nonzero on first reseed, marking generator as seeded
244
+ // genKey = hash(genKey ‖ seed). Wipe both the hash input and the
245
+ // prior key before dropping references.
246
+ const combined = concat(this.genKey, seed);
247
+ const newKey = this.hash.digest(combined);
248
+ wipe(combined);
249
+ wipe(this.genKey);
250
+ this.genKey = newKey;
251
+ // Increment counter, makes it nonzero on first reseed, marking generator as seeded
189
252
  this.incrementCounter();
190
253
  this.lastReseed = Date.now();
191
254
  }
192
- /** Increment 16-byte little-endian counter. spec §9.4 */
255
+ /** Drain pool 0 into a fresh seed and reseed. Used by the deterministic
256
+ * test factory; production reseeds in get() walk the §9.5.5 schedule
257
+ * across all pools, not just pool 0. Caller is responsible for any
258
+ * entropy-threshold check. */
259
+ reseedFromPool0() {
260
+ this.reseedCnt = (this.reseedCnt + 1) >>> 0;
261
+ const seed = this.poolHash[0].slice();
262
+ const old = this.poolHash[0];
263
+ this.poolHash[0] = new Uint8Array(this.hash.outputSize);
264
+ wipe(old);
265
+ this.entropyLevel -= this.poolEntropy[0];
266
+ this.poolEntropy[0] = 0;
267
+ this.reseed(seed);
268
+ wipe(seed);
269
+ }
270
+ /** Increment little-endian counter., spec §9.4 */
193
271
  incrementCounter() {
194
- for (let i = 0; i < 16; i++) {
272
+ for (let i = 0; i < this.genCnt.length; i++) {
195
273
  if (++this.genCnt[i] !== 0)
196
274
  break;
197
275
  }
198
276
  }
199
277
  // ── Accumulator (spec §9.5) ───────────────────────────────────────────
200
- /** Add an event to a pool via hash chaining: poolHash[i] = SHA256(poolHash[i] ‖ eventId ‖ data). */
278
+ /** Add an event to a pool via hash chaining: poolHash[i] = hash(poolHash[i] ‖ eventId ‖ data). */
201
279
  addRandomEvent(data, poolIdx, entropyBits) {
202
280
  // Encode eventId as 4 bytes little-endian
203
281
  const id = new Uint8Array(4);
@@ -206,14 +284,19 @@ export class Fortuna {
206
284
  id[2] = (this.eventId >>> 16) & 0xff;
207
285
  id[3] = (this.eventId >>> 24) & 0xff;
208
286
  this.eventId = (this.eventId + 1) >>> 0; // u32 wrap
209
- // Chain: poolHash[i] = SHA256(poolHash[i] ‖ id ‖ data)
210
- this.poolHash[poolIdx] = this.sha.hash(concat(this.poolHash[poolIdx], id, data));
287
+ // Chain: poolHash[i] = hash(poolHash[i] ‖ id ‖ data).
288
+ // Wipe the chain input and the prior chain value before dropping refs.
289
+ const combined = concat(this.poolHash[poolIdx], id, data);
290
+ const newChain = this.hash.digest(combined);
291
+ wipe(combined);
292
+ wipe(this.poolHash[poolIdx]);
293
+ this.poolHash[poolIdx] = newChain;
211
294
  this.poolEntropy[poolIdx] += entropyBits;
212
295
  this.entropyLevel += entropyBits;
213
296
  }
214
297
  // ── Initialization ────────────────────────────────────────────────────
215
298
  initialize(entropy) {
216
- // Initial seeding crypto random per pool (spec §9.5)
299
+ // Initial seeding, crypto random per pool (spec §9.5)
217
300
  for (let i = 0; i < Fortuna.NUM_POOLS * 4; i++) {
218
301
  this.collectorCryptoRandom();
219
302
  }
@@ -226,6 +309,13 @@ export class Fortuna {
226
309
  this.addRandomEvent(entropy, this.robin.rnd, entropy.length * 8);
227
310
  this.robin.rnd = (this.robin.rnd + 1) % Fortuna.NUM_POOLS;
228
311
  }
312
+ // F-2 invariant: fail loudly if no OS entropy source delivered anything.
313
+ // The try/catch in collectorCryptoRandom is preserved to protect against
314
+ // platforms where crypto.getRandomValues itself throws (non-standard
315
+ // runtimes). This post-init check covers all silent-failure paths uniformly.
316
+ if (this.poolEntropy[0] < Fortuna.RESEED_LIMIT)
317
+ throw new Error('leviathan-crypto: Fortuna initialization could not gather sufficient entropy. '
318
+ + 'No working crypto.getRandomValues in this environment.');
229
319
  this.startCollectors();
230
320
  }
231
321
  // ── Collectors ────────────────────────────────────────────────────────
@@ -255,7 +345,7 @@ export class Fortuna {
255
345
  // OS stats timer
256
346
  this.timers.push(setInterval(() => this.collectNodeStats(), Fortuna.NODE_STATS_INTERVAL));
257
347
  }
258
- // Crypto timer both environments
348
+ // Crypto timer, both environments
259
349
  this.timers.push(setInterval(() => this.collectorCryptoRandom(), Fortuna.CRYPTO_INTERVAL));
260
350
  this.active = true;
261
351
  }
@@ -358,25 +448,18 @@ export class Fortuna {
358
448
  }
359
449
  collectorDom() {
360
450
  if (typeof document !== 'undefined' && document.documentElement) {
361
- this.addRandomEvent(this.sha.hash(utf8ToBytes(document.documentElement.innerHTML)), this.robin.dom, 2);
451
+ this.addRandomEvent(this.hash.digest(utf8ToBytes(document.documentElement.innerHTML)), this.robin.dom, 2);
362
452
  this.robin.dom = (this.robin.dom + 1) % Fortuna.NUM_POOLS;
363
453
  }
364
454
  }
365
455
  collectorCryptoRandom() {
366
456
  try {
457
+ // Web Crypto is the only secure source. No node:crypto fallback: an absent
458
+ // source must fail loud via the init invariant below, never be polyfilled.
459
+ if (typeof globalThis.crypto === 'undefined' || typeof globalThis.crypto.getRandomValues !== 'function')
460
+ return;
367
461
  const rnd = new Uint8Array(128);
368
- if (typeof globalThis.crypto !== 'undefined' && typeof globalThis.crypto.getRandomValues === 'function') {
369
- globalThis.crypto.getRandomValues(rnd);
370
- }
371
- else if (isNode) {
372
- // eslint-disable-next-line @typescript-eslint/no-require-imports
373
- const nodeCrypto = require('node:crypto');
374
- const buf = nodeCrypto.randomBytes(128);
375
- rnd.set(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
376
- }
377
- else {
378
- return; // no crypto source available
379
- }
462
+ globalThis.crypto.getRandomValues(rnd);
380
463
  this.addRandomEvent(rnd, this.robin.rnd, 1024);
381
464
  this.robin.rnd = (this.robin.rnd + 1) % Fortuna.NUM_POOLS;
382
465
  }
@@ -395,14 +478,14 @@ export class Fortuna {
395
478
  }
396
479
  collectNodeStats() {
397
480
  try {
398
- // hrtime nanosecond scheduling jitter
481
+ // hrtime, nanosecond scheduling jitter
399
482
  const hr = process.hrtime.bigint();
400
483
  const hrBytes = new Uint8Array(8);
401
484
  for (let i = 0; i < 8; i++)
402
485
  hrBytes[i] = Number((hr >> BigInt(i * 8)) & 0xffn);
403
486
  this.addRandomEvent(hrBytes, this.robin.time, 8);
404
487
  this.robin.time = (this.robin.time + 1) % Fortuna.NUM_POOLS;
405
- // cpuUsage user + system CPU microseconds
488
+ // cpuUsage, user + system CPU microseconds
406
489
  const cpu = process.cpuUsage();
407
490
  const cpuBytes = new Uint8Array(8);
408
491
  cpuBytes[0] = cpu.user & 0xff;
@@ -415,7 +498,7 @@ export class Fortuna {
415
498
  cpuBytes[7] = (cpu.system >>> 24) & 0xff;
416
499
  this.addRandomEvent(cpuBytes, this.robin.rnd, 2);
417
500
  this.robin.rnd = (this.robin.rnd + 1) % Fortuna.NUM_POOLS;
418
- // memoryUsage heapUsed changes constantly
501
+ // memoryUsage, heapUsed changes constantly
419
502
  const mem = process.memoryUsage();
420
503
  const memVal = mem.heapUsed;
421
504
  const memBytes = new Uint8Array(4);
@@ -425,22 +508,6 @@ export class Fortuna {
425
508
  memBytes[3] = (memVal >>> 24) & 0xff;
426
509
  this.addRandomEvent(memBytes, this.robin.rnd, 1);
427
510
  this.robin.rnd = (this.robin.rnd + 1) % Fortuna.NUM_POOLS;
428
- // loadavg — slow-changing but real system state
429
- // eslint-disable-next-line @typescript-eslint/no-require-imports
430
- const os = require('node:os');
431
- const la = os.loadavg();
432
- const laStr = la.map((n) => Math.round(n * 1000).toString()).join('');
433
- this.addRandomEvent(utf8ToBytes(laStr), this.robin.time, 1);
434
- this.robin.time = (this.robin.time + 1) % Fortuna.NUM_POOLS;
435
- // freemem — changes with allocation activity
436
- const fm = os.freemem();
437
- const fmBytes = new Uint8Array(4);
438
- fmBytes[0] = fm & 0xff;
439
- fmBytes[1] = (fm >>> 8) & 0xff;
440
- fmBytes[2] = (fm >>> 16) & 0xff;
441
- fmBytes[3] = (fm >>> 24) & 0xff;
442
- this.addRandomEvent(fmBytes, this.robin.rnd, 1);
443
- this.robin.rnd = (this.robin.rnd + 1) % Fortuna.NUM_POOLS;
444
511
  }
445
512
  catch { /* Node APIs may not be available */ }
446
513
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import type { Module } from './init.js';
2
2
  import type { WasmSource } from './wasm-source.js';
3
+ /**
4
+ * Top-level init() input. Accepts the canonical module keys plus the
5
+ * `ed25519` and `x25519` aliases; both aliases resolve to the underlying
6
+ * `curve25519` WASM module and are de-duped if given identical sources.
7
+ */
8
+ export type InitInput = Partial<Record<Module | 'ed25519' | 'x25519', WasmSource>>;
3
9
  /**
4
10
  * Load one or more WASM modules. Each key is a module name; the value is the
5
11
  * WasmSource to load it from (embedded blob, URL, ArrayBuffer, etc.).
@@ -10,20 +16,45 @@ import type { WasmSource } from './wasm-source.js';
10
16
  * import { sha2Wasm } from 'leviathan-crypto/sha2/embedded';
11
17
  * await init({ serpent: serpentWasm, sha2: sha2Wasm });
12
18
  * ```
19
+ *
20
+ * `ed25519` and `x25519` are aliases for the underlying `curve25519`
21
+ * module. `init({ ed25519: src })` and `init({ x25519: src })` both work,
22
+ * and `init({ ed25519: src, x25519: src })` is accepted (single underlying
23
+ * init). Two different sources for the same underlying module is rejected.
13
24
  */
14
- export declare function init(sources: Partial<Record<Module, WasmSource>>): Promise<void>;
25
+ export declare function init(sources: InitInput): Promise<void>;
15
26
  export type { Module, WasmSource };
16
- export { isInitialized, _resetForTesting } from './init.js';
17
- export { AuthenticationError } from './errors.js';
18
- export { serpentInit, Serpent, SerpentCtr, SerpentCbc, SerpentCipher, _serpentReady } from './serpent/index.js';
19
- export { chacha20Init, ChaCha20, Poly1305, ChaCha20Poly1305, XChaCha20Poly1305, XChaCha20Cipher, _chachaReady } from './chacha20/index.js';
20
- export { sha2Init, SHA256, SHA512, SHA384, HMAC_SHA256, HMAC_SHA512, HMAC_SHA384, HKDF_SHA256, HKDF_SHA512, _sha2Ready } from './sha2/index.js';
21
- export { sha3Init, SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256, _sha3Ready } from './sha3/index.js';
27
+ export { isInitialized } from './init.js';
28
+ export { AuthenticationError, SigningError, KeyAgreementError, MerkleCodecError, MerkleLogError } from './errors.js';
29
+ export { serpentInit, Serpent, SerpentCtr, SerpentCbc, SerpentCipher, SerpentGenerator } from './serpent/index.js';
30
+ export { chacha20Init, ChaCha20, Poly1305, ChaCha20Poly1305, XChaCha20Poly1305, XChaCha20Cipher, ChaCha20Generator } from './chacha20/index.js';
31
+ export { sha2Init, SHA256, SHA224, SHA384, SHA512, SHA512_224, SHA512_256, HMAC_SHA256, HMAC_SHA512, HMAC_SHA384, HKDF_SHA256, HKDF_SHA512, SHA256Hash } from './sha2/index.js';
32
+ export { sha3Init, SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHA3_256Stream, SHA3_512Stream, SHAKE128, SHAKE256, SHAKE128Stream, SHAKE256Stream, SHA3_256Hash, CSHAKE128, CSHAKE256, KMAC128, KMAC256, KMACXOF128, KMACXOF256 } from './sha3/index.js';
22
33
  export { keccakInit } from './keccak/index.js';
23
- export { kyberInit, MlKem512, MlKem768, MlKem1024, MlKemBase, KyberSuite, _kyberReady } from './kyber/index.js';
24
- export type { KyberKeyPair, KyberEncapsulation, KyberParams } from './kyber/index.js';
34
+ export { mlkemInit, MlKem512, MlKem768, MlKem1024, MlKemBase, MlKemSuite } from './mlkem/index.js';
35
+ export { aesInit, AES, AESCbc, AESCtr, AESGCM, AESGCMSIV, AESGenerator, AESGCMSIVCipher } from './aes/index.js';
36
+ export { mldsaInit, MlDsa44, MlDsa65, MlDsa87, MlDsaBase, MLDSA44, MLDSA65, MLDSA87 } from './mldsa/index.js';
37
+ export { slhdsaInit, SlhDsaBase, SlhDsa128f, SlhDsa192f, SlhDsa256f, SLHDSA128F, SLHDSA192F, SLHDSA256F, } from './slhdsa/index.js';
38
+ export { blake3Init, BLAKE3, BLAKE3KeyedHash, BLAKE3DeriveKey, BLAKE3Stream, BLAKE3KeyedHashStream, BLAKE3DeriveKeyStream, BLAKE3OutputReader, BLAKE3Hash, } from './blake3/index.js';
39
+ export { ecdsaP256Init, EcdsaP256, pointDecompress, encodeEcPrivateKey, decodeEcPrivateKey, } from './ecdsa/index.js';
40
+ export type { EcdsaP256KeyPair } from './ecdsa/index.js';
41
+ export { ecdsaSignatureToDer, ecdsaSignatureFromDer } from './ecdsa/der.js';
42
+ export { ed25519Init, Ed25519 } from './ed25519/index.js';
43
+ export type { Ed25519KeyPair } from './ed25519/index.js';
44
+ export { x25519Init, X25519 } from './x25519/index.js';
45
+ export type { X25519KeyPair } from './x25519/index.js';
46
+ export type { MlKemKeyPair, MlKemEncapsulation, MlKemParams } from './mlkem/index.js';
47
+ export type { MlDsaKeyPair, MlDsaParams, PreHashAlgorithm } from './mldsa/index.js';
48
+ export type { SlhDsaKeyPair, SlhDsaParams } from './slhdsa/index.js';
25
49
  export { SealStream, OpenStream, Seal, SealStreamPool, FLAG_FRAMED, TAG_DATA, TAG_FINAL, HEADER_SIZE, CHUNK_MIN, CHUNK_MAX } from './stream/index.js';
26
50
  export type { CipherSuite, DerivedKeys, SealStreamOpts, PoolOpts } from './stream/index.js';
51
+ export { MemoryStorage, Sha256Hasher, Sha256Tree, Blake3Hasher, Blake3Tree, splitPoint, verifyInclusionProof, verifyConsistencyProof, buildInclusionProof, buildConsistencyProof, serializeCheckpointBody, parseCheckpointBody, emitSignedNote, parseSignedNote, deriveKeyId, suiteFormatEnumToAlgoByte, lookupAlgoEntryByFormatEnum, lookupAlgoEntryByByte, buildCosigSignedMessage, buildCosignedMessage, emitCosigSignaturePayload, parseCosigSignaturePayload, ALGO_BYTE_ED25519_NOTE, ALGO_BYTE_ED25519_COSIG, ALGO_BYTE_MLDSA44_COSIG, SignedLog, MerkleVerifier, MerkleLog, } from './merkle/index.js';
52
+ export type { Hasher, MerkleTree, MerkleStorage, VerifyInclusionInput, VerifyConsistencyInput, BuildInclusionInput, BuildConsistencyInput, GetNode, Checkpoint, SignatureLine, SignedNote, SignedTreeHead, AlgoEntry, MessageConstruction, SignaturePayload, CosignedMessageInput, SignedLogOpts, MerkleVerifierOpts, MerkleLogCreateOpts, MerkleLogGenerateOpts, } from './merkle/index.js';
53
+ export { Sign, SignStream, VerifyStream } from './sign/index.js';
54
+ export type { SignatureSuite, StreamableSignatureSuite, PrehashAlgorithm, } from './sign/index.js';
55
+ export { Ed25519Suite, Ed25519PreHashSuite, EcdsaP256Suite, MlDsa44Suite, MlDsa65Suite, MlDsa87Suite, MlDsa44PreHashSuite, MlDsa65PreHashSuite, MlDsa87PreHashSuite, SlhDsa128fSuite, SlhDsa192fSuite, SlhDsa256fSuite, SlhDsa128fPreHashSuite, SlhDsa192fPreHashSuite, SlhDsa256fPreHashSuite, MlDsa44SlhDsa128fSuite, MlDsa65SlhDsa192fSuite, MlDsa87SlhDsa256fSuite, MlDsa44Ed25519Suite, MlDsa65Ed25519Suite, MlDsa44EcdsaP256Suite, MlDsa65EcdsaP256Suite, } from './sign/index.js';
27
56
  export { Fortuna } from './fortuna.js';
28
- export type { Hash, KeyedHash, Blockcipher, Streamcipher, AEAD } from './types.js';
29
- export { hexToBytes, bytesToHex, utf8ToBytes, bytesToUtf8, base64ToBytes, bytesToBase64, constantTimeEqual, CT_MAX_BYTES, wipe, xor, concat, randomBytes, hasSIMD, } from './utils.js';
57
+ export type { Hash, KeyedHash, Blockcipher, Streamcipher, AEAD, Generator, HashFn } from './types.js';
58
+ export { KDFChain, ratchetInit, kemRatchetEncap, kemRatchetDecap, ratchetReady, SkippedKeyStore, RatchetKeypair, } from './ratchet/index.js';
59
+ export type { RatchetInitResult, KemEncapResult, KemDecapResult, MlKemLike, RatchetMessageHeader, ResolveHandle, SkippedKeyStoreOpts, } from './ratchet/index.js';
60
+ export { hexToBytes, bytesToHex, utf8ToBytes, bytesToUtf8, base64ToBytes, bytesToBase64, constantTimeEqual, CTE_MAX_BYTES, wipe, xor, concat, randomBytes, hasSIMD, } from './utils.js';
package/dist/index.js CHANGED
@@ -19,21 +19,35 @@
19
19
  // ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
20
20
  // ▀█████▀▀
21
21
  //
22
- // Root barrel re-exports everything
22
+ // Root barrel, re-exports everything
23
23
  import { serpentInit } from './serpent/index.js';
24
24
  import { chacha20Init } from './chacha20/index.js';
25
25
  import { sha2Init } from './sha2/index.js';
26
26
  import { sha3Init } from './sha3/index.js';
27
27
  import { keccakInit } from './keccak/index.js';
28
- import { kyberInit } from './kyber/index.js';
28
+ import { mlkemInit } from './mlkem/index.js';
29
+ import { aesInit } from './aes/index.js';
30
+ import { mldsaInit } from './mldsa/index.js';
31
+ import { slhdsaInit } from './slhdsa/index.js';
32
+ import { blake3Init } from './blake3/index.js';
33
+ import { ecdsaP256Init } from './ecdsa/index.js';
34
+ import { initModule } from './init.js';
29
35
  import { hasSIMD } from './utils.js';
36
+ // curve25519 backs ed25519 + x25519. Public surface is the
37
+ // per-primitive alias; 'curve25519' key accepted for Module symmetry.
30
38
  const _dispatchers = {
31
39
  serpent: serpentInit,
32
40
  chacha20: chacha20Init,
33
41
  sha2: sha2Init,
34
42
  sha3: sha3Init,
35
43
  keccak: keccakInit,
36
- kyber: kyberInit,
44
+ mlkem: mlkemInit,
45
+ aes: aesInit,
46
+ mldsa: mldsaInit,
47
+ slhdsa: slhdsaInit,
48
+ blake3: blake3Init,
49
+ curve25519: (source) => initModule('curve25519', source),
50
+ p256: ecdsaP256Init,
37
51
  };
38
52
  /**
39
53
  * Load one or more WASM modules. Each key is a module name; the value is the
@@ -45,29 +59,60 @@ const _dispatchers = {
45
59
  * import { sha2Wasm } from 'leviathan-crypto/sha2/embedded';
46
60
  * await init({ serpent: serpentWasm, sha2: sha2Wasm });
47
61
  * ```
62
+ *
63
+ * `ed25519` and `x25519` are aliases for the underlying `curve25519`
64
+ * module. `init({ ed25519: src })` and `init({ x25519: src })` both work,
65
+ * and `init({ ed25519: src, x25519: src })` is accepted (single underlying
66
+ * init). Two different sources for the same underlying module is rejected.
48
67
  */
49
68
  export async function init(sources) {
50
69
  const entries = Object.entries(sources);
51
- // SIMD preflight serpent, chacha20, and kyber modules contain SIMD instructions
52
- if (('serpent' in sources || 'chacha20' in sources || 'kyber' in sources) && !hasSIMD())
53
- throw new Error('leviathan-crypto: serpent, chacha20, and kyber require WebAssembly SIMD — '
54
- + 'this runtime does not support it');
55
- for (const [mod, src] of entries) {
56
- if (!Object.hasOwn(_dispatchers, mod))
57
- throw new Error(`leviathan-crypto: unknown module "${mod}" — expected one of: ${Object.keys(_dispatchers).join(', ')}`);
70
+ const resolved = new Map();
71
+ for (const [key, src] of entries) {
58
72
  if (src == null)
59
- throw new TypeError(`leviathan-crypto: source for "${mod}" is null or undefined`);
73
+ throw new TypeError(`leviathan-crypto: source for "${key}" is null or undefined`);
74
+ const target = (key === 'ed25519' || key === 'x25519') ? 'curve25519' : key;
75
+ if (!Object.hasOwn(_dispatchers, target))
76
+ throw new Error(`leviathan-crypto: unknown module "${key}", expected one of: `
77
+ + `${Object.keys(_dispatchers).join(', ')}, ed25519, x25519`);
78
+ const prior = resolved.get(target);
79
+ if (prior !== undefined) {
80
+ if (prior !== src)
81
+ throw new Error('leviathan-crypto: init() called with different sources for "ed25519" and "x25519" '
82
+ + '(both alias to curve25519, sources must be identical)');
83
+ continue;
84
+ }
85
+ resolved.set(target, src);
60
86
  }
61
- await Promise.all(entries.map(([mod, src]) => _dispatchers[mod](src)));
87
+ // SIMD preflight for modules that contain v128 instructions
88
+ // (see scripts/lib/modules.ts).
89
+ if ((resolved.has('serpent') || resolved.has('chacha20') || resolved.has('mlkem')
90
+ || resolved.has('aes') || resolved.has('mldsa') || resolved.has('blake3'))
91
+ && !hasSIMD())
92
+ throw new Error('leviathan-crypto: serpent, chacha20, mlkem, aes, mldsa, and blake3 require WebAssembly SIMD, '
93
+ + 'this runtime does not support it');
94
+ await Promise.all(Array.from(resolved.entries()).map(([mod, src]) => _dispatchers[mod](src)));
62
95
  }
63
- export { isInitialized, _resetForTesting } from './init.js';
64
- export { AuthenticationError } from './errors.js';
65
- export { serpentInit, Serpent, SerpentCtr, SerpentCbc, SerpentCipher, _serpentReady } from './serpent/index.js';
66
- export { chacha20Init, ChaCha20, Poly1305, ChaCha20Poly1305, XChaCha20Poly1305, XChaCha20Cipher, _chachaReady } from './chacha20/index.js';
67
- export { sha2Init, SHA256, SHA512, SHA384, HMAC_SHA256, HMAC_SHA512, HMAC_SHA384, HKDF_SHA256, HKDF_SHA512, _sha2Ready } from './sha2/index.js';
68
- export { sha3Init, SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256, _sha3Ready } from './sha3/index.js';
96
+ export { isInitialized } from './init.js';
97
+ export { AuthenticationError, SigningError, KeyAgreementError, MerkleCodecError, MerkleLogError } from './errors.js';
98
+ export { serpentInit, Serpent, SerpentCtr, SerpentCbc, SerpentCipher, SerpentGenerator } from './serpent/index.js';
99
+ export { chacha20Init, ChaCha20, Poly1305, ChaCha20Poly1305, XChaCha20Poly1305, XChaCha20Cipher, ChaCha20Generator } from './chacha20/index.js';
100
+ export { sha2Init, SHA256, SHA224, SHA384, SHA512, SHA512_224, SHA512_256, HMAC_SHA256, HMAC_SHA512, HMAC_SHA384, HKDF_SHA256, HKDF_SHA512, SHA256Hash } from './sha2/index.js';
101
+ export { sha3Init, SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHA3_256Stream, SHA3_512Stream, SHAKE128, SHAKE256, SHAKE128Stream, SHAKE256Stream, SHA3_256Hash, CSHAKE128, CSHAKE256, KMAC128, KMAC256, KMACXOF128, KMACXOF256 } from './sha3/index.js';
69
102
  export { keccakInit } from './keccak/index.js';
70
- export { kyberInit, MlKem512, MlKem768, MlKem1024, MlKemBase, KyberSuite, _kyberReady } from './kyber/index.js';
103
+ export { mlkemInit, MlKem512, MlKem768, MlKem1024, MlKemBase, MlKemSuite } from './mlkem/index.js';
104
+ export { aesInit, AES, AESCbc, AESCtr, AESGCM, AESGCMSIV, AESGenerator, AESGCMSIVCipher } from './aes/index.js';
105
+ export { mldsaInit, MlDsa44, MlDsa65, MlDsa87, MlDsaBase, MLDSA44, MLDSA65, MLDSA87 } from './mldsa/index.js';
106
+ export { slhdsaInit, SlhDsaBase, SlhDsa128f, SlhDsa192f, SlhDsa256f, SLHDSA128F, SLHDSA192F, SLHDSA256F, } from './slhdsa/index.js';
107
+ export { blake3Init, BLAKE3, BLAKE3KeyedHash, BLAKE3DeriveKey, BLAKE3Stream, BLAKE3KeyedHashStream, BLAKE3DeriveKeyStream, BLAKE3OutputReader, BLAKE3Hash, } from './blake3/index.js';
108
+ export { ecdsaP256Init, EcdsaP256, pointDecompress, encodeEcPrivateKey, decodeEcPrivateKey, } from './ecdsa/index.js';
109
+ export { ecdsaSignatureToDer, ecdsaSignatureFromDer } from './ecdsa/der.js';
110
+ export { ed25519Init, Ed25519 } from './ed25519/index.js';
111
+ export { x25519Init, X25519 } from './x25519/index.js';
71
112
  export { SealStream, OpenStream, Seal, SealStreamPool, FLAG_FRAMED, TAG_DATA, TAG_FINAL, HEADER_SIZE, CHUNK_MIN, CHUNK_MAX } from './stream/index.js';
113
+ export { MemoryStorage, Sha256Hasher, Sha256Tree, Blake3Hasher, Blake3Tree, splitPoint, verifyInclusionProof, verifyConsistencyProof, buildInclusionProof, buildConsistencyProof, serializeCheckpointBody, parseCheckpointBody, emitSignedNote, parseSignedNote, deriveKeyId, suiteFormatEnumToAlgoByte, lookupAlgoEntryByFormatEnum, lookupAlgoEntryByByte, buildCosigSignedMessage, buildCosignedMessage, emitCosigSignaturePayload, parseCosigSignaturePayload, ALGO_BYTE_ED25519_NOTE, ALGO_BYTE_ED25519_COSIG, ALGO_BYTE_MLDSA44_COSIG, SignedLog, MerkleVerifier, MerkleLog, } from './merkle/index.js';
114
+ export { Sign, SignStream, VerifyStream } from './sign/index.js';
115
+ export { Ed25519Suite, Ed25519PreHashSuite, EcdsaP256Suite, MlDsa44Suite, MlDsa65Suite, MlDsa87Suite, MlDsa44PreHashSuite, MlDsa65PreHashSuite, MlDsa87PreHashSuite, SlhDsa128fSuite, SlhDsa192fSuite, SlhDsa256fSuite, SlhDsa128fPreHashSuite, SlhDsa192fPreHashSuite, SlhDsa256fPreHashSuite, MlDsa44SlhDsa128fSuite, MlDsa65SlhDsa192fSuite, MlDsa87SlhDsa256fSuite, MlDsa44Ed25519Suite, MlDsa65Ed25519Suite, MlDsa44EcdsaP256Suite, MlDsa65EcdsaP256Suite, } from './sign/index.js';
72
116
  export { Fortuna } from './fortuna.js';
73
- export { hexToBytes, bytesToHex, utf8ToBytes, bytesToUtf8, base64ToBytes, bytesToBase64, constantTimeEqual, CT_MAX_BYTES, wipe, xor, concat, randomBytes, hasSIMD, } from './utils.js';
117
+ export { KDFChain, ratchetInit, kemRatchetEncap, kemRatchetDecap, ratchetReady, SkippedKeyStore, RatchetKeypair, } from './ratchet/index.js';
118
+ export { hexToBytes, bytesToHex, utf8ToBytes, bytesToUtf8, base64ToBytes, bytesToBase64, constantTimeEqual, CTE_MAX_BYTES, wipe, xor, concat, randomBytes, hasSIMD, } from './utils.js';
package/dist/init.d.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  import type { WasmSource } from './wasm-source.js';
2
- export type Module = 'serpent' | 'chacha20' | 'sha2' | 'sha3' | 'keccak' | 'kyber';
2
+ export type Module = 'serpent' | 'chacha20' | 'sha2' | 'sha3' | 'keccak' | 'mlkem' | 'aes' | 'mldsa' | 'slhdsa' | 'blake3' | 'curve25519' | 'p256';
3
3
  export declare function initModule(mod: Module, source: WasmSource): Promise<void>;
4
4
  export declare function getInstance(mod: Module): WebAssembly.Instance;
5
5
  export declare function isInitialized(mod: Module): boolean;
6
- /** Reset all cached instances — for testing only */
7
- export declare function _resetForTesting(): void;