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
@@ -21,56 +21,45 @@
21
21
  //
22
22
  // src/ts/serpent/serpent-cbc.ts
23
23
  //
24
- // SerpentCbc Serpent-256 CBC + PKCS7, internal module.
24
+ // SerpentCbc, Serpent-256 CBC + PKCS7, internal module.
25
25
  // Extracted to break the cipher-suite.ts ↔ index.ts circular dependency.
26
26
  // Import from here directly; index.ts re-exports for the public API surface.
27
- import { getInstance } from '../init.js';
27
+ import { getInstance, _acquireModule, _releaseModule } from '../init.js';
28
+ import { pkcs7Pad, pkcs7Strip, PKCS7_INVALID } from './shared-ops.js';
29
+ /** Returns the raw serpent WASM export object. @internal */
28
30
  function getExports() {
29
31
  return getInstance('serpent').exports;
30
32
  }
31
- // ── PKCS7 helpers ────────────────────────────────────────────────────────────
32
- function pkcs7Pad(data) {
33
- const padLen = 16 - (data.length % 16); // 1..16
34
- const out = new Uint8Array(data.length + padLen);
35
- out.set(data);
36
- out.fill(padLen, data.length);
37
- return out;
38
- }
39
- // pkcs7Strip is only called after HMAC authentication succeeds (verify-then-decrypt).
40
- // The early throw on invalid padLen is not a padding oracle in this context —
41
- // the HMAC check is the oracle gate and runs in constant time before this point.
42
- // If you move this call to a pre-auth site, revisit the timing properties.
43
- function pkcs7Strip(data) {
44
- if (data.length === 0)
45
- throw new RangeError('empty ciphertext');
46
- const padLen = data[data.length - 1];
47
- if (padLen === 0 || padLen > 16)
48
- throw new RangeError(`invalid PKCS7 padding byte: ${padLen}`);
49
- if (padLen > data.length)
50
- throw new RangeError(`invalid PKCS7 padding: pad length ${padLen} exceeds data length ${data.length}`);
51
- let bad = 0;
52
- for (let i = data.length - padLen; i < data.length; i++)
53
- bad |= data[i] ^ padLen;
54
- if (bad !== 0)
55
- throw new RangeError('invalid PKCS7 padding');
56
- return data.subarray(0, data.length - padLen);
57
- }
58
- // ── SerpentCbc ───────────────────────────────────────────────────────────────
33
+ // ── PKCS7 helpers ───────────────────────────────────────────────────────────
34
+ //
35
+ // The canonical `pkcs7Pad` / `pkcs7Strip` live in `./shared-ops.ts`; this
36
+ // file re-uses them so the main-thread class and the pool worker share one
37
+ // implementation. See `shared-ops.ts` for the branch-free, Vaudenay-2002-
38
+ // closed padding check.
39
+ // ── SerpentCbc ──────────────────────────────────────────────────────────────
59
40
  /**
60
41
  * Serpent-256 in CBC mode with PKCS7 padding.
61
42
  *
62
43
  * **WARNING: CBC mode is unauthenticated.** Always authenticate the output
63
44
  * with HMAC-SHA256 (Encrypt-then-MAC) or use `XChaCha20Poly1305` instead.
45
+ *
46
+ * Holds exclusive access to the `serpent` WASM module from construction
47
+ * until `dispose()`. Constructing a second SerpentCbc/SerpentCtr/
48
+ * SerpentCipher or any other serpent user while this instance is live
49
+ * throws. Call `dispose()` when done.
64
50
  */
65
51
  export class SerpentCbc {
66
52
  x;
53
+ _tok;
67
54
  constructor(opts) {
68
55
  if (!opts?.dangerUnauthenticated) {
69
- throw new Error('leviathan-crypto: SerpentCbc is unauthenticated use Seal with SerpentCipher instead. ' +
56
+ throw new Error('leviathan-crypto: SerpentCbc is unauthenticated, use Seal with SerpentCipher instead. ' +
70
57
  'To use SerpentCbc directly, pass { dangerUnauthenticated: true }.');
71
58
  }
72
59
  this.x = getExports();
60
+ this._tok = _acquireModule('serpent');
73
61
  }
62
+ /** View over WASM linear memory. Rebind on every access, memory can be detached after grow. @internal */
74
63
  get mem() {
75
64
  return new Uint8Array(this.x.memory.buffer);
76
65
  }
@@ -78,11 +67,13 @@ export class SerpentCbc {
78
67
  * Encrypt plaintext with Serpent-256 CBC + PKCS7 padding.
79
68
  *
80
69
  * @param key 16, 24, or 32 bytes
81
- * @param iv 16 bytes must be random and unique per (key, message)
82
- * @param plaintext any length PKCS7 padding applied automatically
70
+ * @param iv 16 bytes, must be random and unique per (key, message)
71
+ * @param plaintext any length, PKCS7 padding applied automatically
83
72
  * @returns ciphertext (length = ceil((plaintext.length + 1) / 16) * 16)
84
73
  */
85
74
  encrypt(key, iv, plaintext) {
75
+ if (this._tok === undefined)
76
+ throw new Error('SerpentCbc: instance has been disposed');
86
77
  this._loadKey(key);
87
78
  this._setIv(iv);
88
79
  const padded = pkcs7Pad(plaintext);
@@ -103,11 +94,19 @@ export class SerpentCbc {
103
94
  }
104
95
  /**
105
96
  * Decrypt Serpent-256 CBC + PKCS7.
106
- * Throws if ciphertext length is not a non-zero multiple of 16 or PKCS7 is invalid.
97
+ *
98
+ * All failure modes, empty input, non-multiple-of-16 length, and any
99
+ * PKCS7 validation failure, throw the same generic `RangeError` with
100
+ * message `'invalid ciphertext'`. Padding validation runs branch-free
101
+ * over the last 16 bytes regardless of where the mismatch is, closing
102
+ * the Vaudenay 2002 padding-oracle surface for callers using
103
+ * `{ dangerUnauthenticated: true }` without an outer HMAC.
107
104
  */
108
105
  decrypt(key, iv, ciphertext) {
106
+ if (this._tok === undefined)
107
+ throw new Error('SerpentCbc: instance has been disposed');
109
108
  if (ciphertext.length === 0 || ciphertext.length % 16 !== 0)
110
- throw new RangeError('ciphertext length must be a non-zero multiple of 16');
109
+ throw new RangeError(PKCS7_INVALID);
111
110
  this._loadKey(key);
112
111
  this._setIv(iv);
113
112
  const output = new Uint8Array(ciphertext.length);
@@ -125,15 +124,37 @@ export class SerpentCbc {
125
124
  }
126
125
  return pkcs7Strip(output);
127
126
  }
127
+ /** Wipe WASM state and release exclusive module access. Idempotent. */
128
128
  dispose() {
129
- this.x.wipeBuffers();
129
+ if (this._tok === undefined)
130
+ return;
131
+ try {
132
+ this.x.wipeBuffers();
133
+ }
134
+ finally {
135
+ _releaseModule('serpent', this._tok);
136
+ this._tok = undefined;
137
+ }
130
138
  }
139
+ /**
140
+ * Validate and load `key` into the WASM key schedule.
141
+ * @param key 16, 24, or 32 bytes
142
+ * @internal
143
+ */
131
144
  _loadKey(key) {
132
145
  if (key.length !== 16 && key.length !== 24 && key.length !== 32)
133
146
  throw new RangeError(`Serpent key must be 16, 24, or 32 bytes (got ${key.length})`);
134
147
  this.mem.set(key, this.x.getKeyOffset());
135
- this.x.loadKey(key.length);
148
+ if (this.x.loadKey(key.length) !== 0) {
149
+ this.x.wipeBuffers();
150
+ throw new Error('SerpentCbc: loadKey failed');
151
+ }
136
152
  }
153
+ /**
154
+ * Write `iv` into the WASM CBC IV buffer.
155
+ * @param iv 16 bytes
156
+ * @internal
157
+ */
137
158
  _setIv(iv) {
138
159
  if (iv.length !== 16)
139
160
  throw new RangeError(`CBC IV must be 16 bytes (got ${iv.length})`);
@@ -0,0 +1,63 @@
1
+ export { pkcs7Pad, pkcs7Strip, PKCS7_INVALID } from '../shared/pkcs7.js';
2
+ /** Subset of the sha2 WASM exports used by `hmacSha256`. */
3
+ export interface Sha2OpsExports {
4
+ memory: WebAssembly.Memory;
5
+ getSha256InputOffset: () => number;
6
+ getSha256OutOffset: () => number;
7
+ sha256Init: () => void;
8
+ sha256Update: (len: number) => void;
9
+ sha256Final: () => void;
10
+ hmac256Init: (keyLen: number) => void;
11
+ hmac256Update: (len: number) => void;
12
+ hmac256Final: () => void;
13
+ }
14
+ /** Subset of the serpent WASM exports used by `cbcEncryptChunk`/`cbcDecryptChunk`. */
15
+ export interface SerpentOpsExports {
16
+ memory: WebAssembly.Memory;
17
+ getKeyOffset: () => number;
18
+ getChunkPtOffset: () => number;
19
+ getChunkCtOffset: () => number;
20
+ getChunkSize: () => number;
21
+ getCbcIvOffset: () => number;
22
+ loadKey: (n: number) => number;
23
+ cbcEncryptChunk: (n: number) => number;
24
+ cbcDecryptChunk_simd: (n: number) => number;
25
+ wipeBuffers: () => void;
26
+ }
27
+ /**
28
+ * Compute HMAC-SHA-256 using raw WASM sha2 exports.
29
+ *
30
+ * Keys longer than 64 bytes are pre-hashed per RFC 2104 §3. The SHA-256
31
+ * input buffer is fed in 64-byte chunks to match the WASM block size.
32
+ * Does not call `_acquireModule`, callers must ensure no stateful instance
33
+ * owns the sha2 module before calling.
34
+ * @param sx sha2 WASM exports
35
+ * @param key HMAC key of any length
36
+ * @param msg Message to authenticate
37
+ * @returns 32-byte HMAC-SHA-256 tag
38
+ */
39
+ export declare function hmacSha256(sx: Sha2OpsExports, key: Uint8Array, msg: Uint8Array): Uint8Array;
40
+ /**
41
+ * Encrypt one plaintext chunk with Serpent-256 CBC + PKCS7 padding.
42
+ *
43
+ * The padded chunk must fit within the WASM CHUNK_SIZE. Callers are
44
+ * responsible for splitting larger payloads before calling.
45
+ * @param kx Serpent WASM exports
46
+ * @param key 16, 24, or 32-byte key
47
+ * @param iv 16-byte CBC initialisation vector
48
+ * @param chunk Plaintext chunk (padded length must be ≤ WASM CHUNK_SIZE)
49
+ * @returns Ciphertext with PKCS7 padding applied
50
+ */
51
+ export declare function cbcEncryptChunk(kx: SerpentOpsExports, key: Uint8Array, iv: Uint8Array, chunk: Uint8Array): Uint8Array;
52
+ /**
53
+ * Decrypt one Serpent-256 CBC chunk using the SIMD path and strip PKCS7 padding.
54
+ *
55
+ * Output matches `SerpentCbc.decrypt` byte-for-byte. Throws
56
+ * `RangeError('invalid ciphertext')` on any length or padding failure.
57
+ * @param kx Serpent WASM exports
58
+ * @param key 16, 24, or 32-byte key
59
+ * @param iv 16-byte CBC initialisation vector
60
+ * @param ct Ciphertext (must be non-empty and a multiple of 16 bytes)
61
+ * @returns Plaintext with PKCS7 padding removed
62
+ */
63
+ export declare function cbcDecryptChunk(kx: SerpentOpsExports, key: Uint8Array, iv: Uint8Array, ct: Uint8Array): Uint8Array;
@@ -0,0 +1,178 @@
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/serpent/shared-ops.ts
23
+ //
24
+ // Pure-function primitives shared between the main-thread `SerpentCipher`
25
+ // (cipher-suite.ts) and the `SealStreamPool` worker (pool-worker.ts). Both
26
+ // call sites hold their own WASM exports, pool workers instantiate modules
27
+ // locally, the main thread fetches via `getInstance()`, so every function
28
+ // here takes the sha2/serpent exports as parameters. No dependency on
29
+ // `init.ts`, no module-level state, no instance wrappers.
30
+ //
31
+ // These helpers are strictly single-chunk: the caller already divided the
32
+ // payload into chunks ≤ WASM CHUNK_SIZE. For multi-chunk use, see
33
+ // `SerpentCbc.encrypt`/`decrypt`, which loop over the same WASM exports.
34
+ //
35
+ // `pkcs7Pad` / `pkcs7Strip` / `PKCS7_INVALID` live in `../shared/pkcs7.js`
36
+ // and are re-exported here so existing serpent-side imports keep working.
37
+ // A single source of truth keeps the branch-free, Vaudenay-2002-closed
38
+ // padding check identical across all CBC paths.
39
+ export { pkcs7Pad, pkcs7Strip, PKCS7_INVALID } from '../shared/pkcs7.js';
40
+ import { pkcs7Pad, pkcs7Strip, PKCS7_INVALID } from '../shared/pkcs7.js';
41
+ // ── HMAC-SHA-256 ────────────────────────────────────────────────────────────
42
+ /**
43
+ * Compute HMAC-SHA-256 using raw WASM sha2 exports.
44
+ *
45
+ * Keys longer than 64 bytes are pre-hashed per RFC 2104 §3. The SHA-256
46
+ * input buffer is fed in 64-byte chunks to match the WASM block size.
47
+ * Does not call `_acquireModule`, callers must ensure no stateful instance
48
+ * owns the sha2 module before calling.
49
+ * @param sx sha2 WASM exports
50
+ * @param key HMAC key of any length
51
+ * @param msg Message to authenticate
52
+ * @returns 32-byte HMAC-SHA-256 tag
53
+ */
54
+ export function hmacSha256(sx, key, msg) {
55
+ const inOff = sx.getSha256InputOffset();
56
+ const outOff = sx.getSha256OutOffset();
57
+ let k = key;
58
+ if (k.length > 64) {
59
+ sx.sha256Init();
60
+ feedMemory(sx.memory, inOff, k, 64, sx.sha256Update);
61
+ sx.sha256Final();
62
+ k = new Uint8Array(sx.memory.buffer).slice(outOff, outOff + 32);
63
+ }
64
+ const mem = new Uint8Array(sx.memory.buffer);
65
+ mem.set(k, inOff);
66
+ sx.hmac256Init(k.length);
67
+ feedMemory(sx.memory, inOff, msg, 64, sx.hmac256Update);
68
+ sx.hmac256Final();
69
+ return new Uint8Array(sx.memory.buffer).slice(outOff, outOff + 32);
70
+ }
71
+ /**
72
+ * Copy `msg` into WASM linear memory at `inputOff` in `chunkSize`-byte
73
+ * increments, calling `update(n)` after each write.
74
+ * @param memory WASM memory object
75
+ * @param inputOff Byte offset of the WASM input buffer
76
+ * @param msg Data to feed
77
+ * @param chunkSize Maximum bytes per write (must match WASM buffer size)
78
+ * @param update WASM update function to call after each write
79
+ * @internal
80
+ */
81
+ function feedMemory(memory, inputOff, msg, chunkSize, update) {
82
+ const mem = new Uint8Array(memory.buffer);
83
+ let pos = 0;
84
+ while (pos < msg.length) {
85
+ const n = Math.min(msg.length - pos, chunkSize);
86
+ mem.set(msg.subarray(pos, pos + n), inputOff);
87
+ update(n);
88
+ pos += n;
89
+ }
90
+ }
91
+ // ── Serpent-CBC (single chunk) ──────────────────────────────────────────────
92
+ /**
93
+ * Encrypt one plaintext chunk with Serpent-256 CBC + PKCS7 padding.
94
+ *
95
+ * The padded chunk must fit within the WASM CHUNK_SIZE. Callers are
96
+ * responsible for splitting larger payloads before calling.
97
+ * @param kx Serpent WASM exports
98
+ * @param key 16, 24, or 32-byte key
99
+ * @param iv 16-byte CBC initialisation vector
100
+ * @param chunk Plaintext chunk (padded length must be ≤ WASM CHUNK_SIZE)
101
+ * @returns Ciphertext with PKCS7 padding applied
102
+ */
103
+ export function cbcEncryptChunk(kx, key, iv, chunk) {
104
+ loadKeyAndIv(kx, key, iv);
105
+ try {
106
+ const padded = pkcs7Pad(chunk);
107
+ const ptOff = kx.getChunkPtOffset();
108
+ const ctOff = kx.getChunkCtOffset();
109
+ const mem = new Uint8Array(kx.memory.buffer);
110
+ mem.set(padded, ptOff);
111
+ const ret = kx.cbcEncryptChunk(padded.length);
112
+ if (ret < 0)
113
+ throw new RangeError(`cbcEncryptChunk rejected len=${padded.length}` +
114
+ ` (WASM CHUNK_SIZE=${kx.getChunkSize()})`);
115
+ return new Uint8Array(kx.memory.buffer).slice(ctOff, ctOff + padded.length);
116
+ }
117
+ catch (err) {
118
+ // Length-error or any other throw past loadKeyAndIv leaves key+iv staged.
119
+ // Wipe before re-throwing, the cipher-suite caller may not call dispose.
120
+ kx.wipeBuffers();
121
+ throw err;
122
+ }
123
+ }
124
+ /**
125
+ * Decrypt one Serpent-256 CBC chunk using the SIMD path and strip PKCS7 padding.
126
+ *
127
+ * Output matches `SerpentCbc.decrypt` byte-for-byte. Throws
128
+ * `RangeError('invalid ciphertext')` on any length or padding failure.
129
+ * @param kx Serpent WASM exports
130
+ * @param key 16, 24, or 32-byte key
131
+ * @param iv 16-byte CBC initialisation vector
132
+ * @param ct Ciphertext (must be non-empty and a multiple of 16 bytes)
133
+ * @returns Plaintext with PKCS7 padding removed
134
+ */
135
+ export function cbcDecryptChunk(kx, key, iv, ct) {
136
+ if (ct.length === 0 || ct.length % 16 !== 0)
137
+ throw new RangeError(PKCS7_INVALID);
138
+ loadKeyAndIv(kx, key, iv);
139
+ try {
140
+ const ctOff = kx.getChunkCtOffset();
141
+ const ptOff = kx.getChunkPtOffset();
142
+ const mem = new Uint8Array(kx.memory.buffer);
143
+ mem.set(ct, ctOff);
144
+ const ret = kx.cbcDecryptChunk_simd(ct.length);
145
+ if (ret < 0)
146
+ throw new RangeError(`cbcDecryptChunk_simd rejected len=${ct.length}` +
147
+ ` (WASM CHUNK_SIZE=${kx.getChunkSize()})`);
148
+ const raw = new Uint8Array(kx.memory.buffer).slice(ptOff, ptOff + ct.length);
149
+ return pkcs7Strip(raw);
150
+ }
151
+ catch (err) {
152
+ // Length-error, padding mismatch, or any other throw past loadKeyAndIv
153
+ // leaves key+iv staged. Wipe before re-throwing, bad PKCS7 padding is
154
+ // a Vaudenay-attack signal; per-chunk staged key+iv must not survive it.
155
+ kx.wipeBuffers();
156
+ throw err;
157
+ }
158
+ }
159
+ /**
160
+ * Validate, then write `key` and `iv` into the WASM buffers and expand the key schedule.
161
+ * @param kx Serpent WASM exports
162
+ * @param key 16, 24, or 32-byte Serpent key
163
+ * @param iv 16-byte CBC initialisation vector
164
+ * @internal
165
+ */
166
+ function loadKeyAndIv(kx, key, iv) {
167
+ if (key.length !== 16 && key.length !== 24 && key.length !== 32)
168
+ throw new RangeError(`Serpent key must be 16, 24, or 32 bytes (got ${key.length})`);
169
+ if (iv.length !== 16)
170
+ throw new RangeError(`CBC IV must be 16 bytes (got ${iv.length})`);
171
+ const mem = new Uint8Array(kx.memory.buffer);
172
+ mem.set(key, kx.getKeyOffset());
173
+ if (kx.loadKey(key.length) !== 0) {
174
+ kx.wipeBuffers();
175
+ throw new Error('Serpent shared-ops: loadKey failed');
176
+ }
177
+ mem.set(iv, kx.getCbcIvOffset());
178
+ }
@@ -1,5 +1 @@
1
- /** WASM exports for the serpent module */
2
- export interface SerpentExports {
3
- memory: WebAssembly.Memory;
4
- getModuleId(): number;
5
- }
1
+ export {};
package/dist/serpent.wasm CHANGED
Binary file
@@ -0,0 +1,2 @@
1
+ import type { HashFn } from '../types.js';
2
+ export declare const SHA256Hash: HashFn;
@@ -0,0 +1,53 @@
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/sha2/hash.ts
23
+ //
24
+ // Stateless SHA-256 HashFn for Fortuna's accumulator and reseed slots.
25
+ import { _assertNotOwned, getInstance } from '../init.js';
26
+ export const SHA256Hash = {
27
+ outputSize: 32,
28
+ wasmModules: ['sha2'],
29
+ digest(msg) {
30
+ _assertNotOwned('sha2');
31
+ const x = getInstance('sha2').exports;
32
+ const mem = new Uint8Array(x.memory.buffer);
33
+ try {
34
+ x.sha256Init();
35
+ const inOff = x.getSha256InputOffset();
36
+ let pos = 0;
37
+ while (pos < msg.length) {
38
+ const n = Math.min(msg.length - pos, 64);
39
+ mem.set(msg.subarray(pos, pos + n), inOff);
40
+ x.sha256Update(n);
41
+ pos += n;
42
+ }
43
+ x.sha256Final();
44
+ const outOff = x.getSha256OutOffset();
45
+ return mem.slice(outOff, outOff + 32);
46
+ }
47
+ finally {
48
+ // Wipe the SHA-256 input/output/state scratch so secret-derived
49
+ // inputs (e.g. Fortuna pool entropy) do not outlive this call.
50
+ x.wipeBuffers();
51
+ }
52
+ },
53
+ };
package/dist/sha2/hkdf.js CHANGED
@@ -21,7 +21,7 @@
21
21
  //
22
22
  // src/ts/sha2/hkdf.ts
23
23
  //
24
- // RFC 5869 HKDF (HMAC-based Extract-and-Expand Key Derivation Function)
24
+ // RFC 5869, HKDF (HMAC-based Extract-and-Expand Key Derivation Function)
25
25
  // Pure TS composition over HMAC_SHA256 and HMAC_SHA512.
26
26
  import { HMAC_SHA256, HMAC_SHA512 } from './index.js';
27
27
  // ── HKDF_SHA256 ─────────────────────────────────────────────────────────────
@@ -30,12 +30,12 @@ export class HKDF_SHA256 {
30
30
  constructor() {
31
31
  this.hmac = new HMAC_SHA256();
32
32
  }
33
- // RFC 5869 §2.2 Extract
33
+ // RFC 5869 §2.2, Extract
34
34
  extract(salt, ikm) {
35
35
  const s = (!salt || salt.length === 0) ? new Uint8Array(32) : salt;
36
36
  return this.hmac.hash(s, ikm);
37
37
  }
38
- // RFC 5869 §2.3 Expand
38
+ // RFC 5869 §2.3, Expand
39
39
  expand(prk, info, length) {
40
40
  if (prk.length !== 32)
41
41
  throw new RangeError('HKDF expand: PRK must be 32 bytes');
@@ -79,12 +79,12 @@ export class HKDF_SHA512 {
79
79
  constructor() {
80
80
  this.hmac = new HMAC_SHA512();
81
81
  }
82
- // RFC 5869 §2.2 Extract
82
+ // RFC 5869 §2.2, Extract
83
83
  extract(salt, ikm) {
84
84
  const s = (!salt || salt.length === 0) ? new Uint8Array(64) : salt;
85
85
  return this.hmac.hash(s, ikm);
86
86
  }
87
- // RFC 5869 §2.3 Expand
87
+ // RFC 5869 §2.3, Expand
88
88
  expand(prk, info, length) {
89
89
  if (prk.length !== 64)
90
90
  throw new RangeError('HKDF expand: PRK must be 64 bytes');
@@ -1,7 +1,9 @@
1
1
  import type { WasmSource } from '../wasm-source.js';
2
+ import type { Sha2Exports } from './types.js';
2
3
  export declare function sha2Init(source: WasmSource): Promise<void>;
3
4
  export type { WasmSource };
4
- export declare function _sha2Ready(): boolean;
5
+ export type { Sha2Exports };
6
+ export { isInitialized } from '../init.js';
5
7
  export declare class SHA256 {
6
8
  private readonly x;
7
9
  constructor();
@@ -20,6 +22,24 @@ export declare class SHA384 {
20
22
  hash(msg: Uint8Array): Uint8Array;
21
23
  dispose(): void;
22
24
  }
25
+ export declare class SHA224 {
26
+ private readonly x;
27
+ constructor();
28
+ hash(msg: Uint8Array): Uint8Array;
29
+ dispose(): void;
30
+ }
31
+ export declare class SHA512_224 {
32
+ private readonly x;
33
+ constructor();
34
+ hash(msg: Uint8Array): Uint8Array;
35
+ dispose(): void;
36
+ }
37
+ export declare class SHA512_256 {
38
+ private readonly x;
39
+ constructor();
40
+ hash(msg: Uint8Array): Uint8Array;
41
+ dispose(): void;
42
+ }
23
43
  export declare class HMAC_SHA256 {
24
44
  private readonly x;
25
45
  constructor();
@@ -39,3 +59,4 @@ export declare class HMAC_SHA384 {
39
59
  dispose(): void;
40
60
  }
41
61
  export { HKDF_SHA256, HKDF_SHA512 } from './hkdf.js';
62
+ export { SHA256Hash } from './hash.js';