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
@@ -1,12 +1,45 @@
1
1
  import type { WasmSource } from '../wasm-source.js';
2
+ /**
3
+ * Load and initialise the Serpent WASM module from `source`.
4
+ * Must be called before constructing any Serpent class.
5
+ * @param source WASM binary, gzip+base64 string, URL, ArrayBuffer, Uint8Array,
6
+ * pre-compiled WebAssembly.Module, Response, or Promise<Response>
7
+ */
2
8
  export declare function serpentInit(source: WasmSource): Promise<void>;
3
9
  export type { WasmSource };
10
+ export { isInitialized } from '../init.js';
11
+ /**
12
+ * Low-level Serpent-256 block cipher, raw ECB encrypt/decrypt.
13
+ *
14
+ * Atomic (stateless) class: each method call is independent.
15
+ * Does not hold exclusive module access; cannot be used while a stateful
16
+ * instance (`SerpentCtr`, `SerpentCbc`, `SerpentCipher`) is alive.
17
+ * Call `dispose()` after use to wipe WASM key material.
18
+ */
4
19
  export declare class Serpent {
5
20
  private readonly x;
6
21
  constructor();
22
+ /**
23
+ * Expand `key` into the WASM key schedule. Must be called before
24
+ * `encryptBlock` / `decryptBlock`.
25
+ * @param key 16, 24, or 32 bytes
26
+ */
7
27
  loadKey(key: Uint8Array): void;
28
+ /**
29
+ * Encrypt one 128-bit block with the previously loaded key schedule.
30
+ * Serpent AES submission §2.2.
31
+ * @param plaintext 16-byte plaintext block
32
+ * @returns 16-byte ciphertext block
33
+ */
8
34
  encryptBlock(plaintext: Uint8Array): Uint8Array;
35
+ /**
36
+ * Decrypt one 128-bit block with the previously loaded key schedule.
37
+ * Serpent AES submission §2.2.
38
+ * @param ciphertext 16-byte ciphertext block
39
+ * @returns 16-byte plaintext block
40
+ */
9
41
  decryptBlock(ciphertext: Uint8Array): Uint8Array;
42
+ /** Wipe WASM key material and release memory. */
10
43
  dispose(): void;
11
44
  }
12
45
  /**
@@ -15,19 +48,47 @@ export declare class Serpent {
15
48
  * **WARNING: CTR mode is unauthenticated.** An attacker can flip ciphertext
16
49
  * bits without detection. Always pair with HMAC-SHA256 (Encrypt-then-MAC)
17
50
  * or use `XChaCha20Poly1305` instead.
51
+ *
52
+ * Holds exclusive access to the `serpent` WASM module from construction
53
+ * until `dispose()`. Constructing a second SerpentCtr/SerpentCbc/
54
+ * SerpentCipher or any other serpent user while this instance is live
55
+ * throws. Call `dispose()` when done.
18
56
  */
19
57
  export declare class SerpentCtr {
20
58
  private readonly x;
59
+ private _tok;
21
60
  constructor(opts?: {
22
61
  dangerUnauthenticated: true;
23
62
  });
63
+ /**
64
+ * Load key and nonce into WASM state and reset the block counter to 0.
65
+ * Must be called before each message.
66
+ * @param key 16, 24, or 32 bytes
67
+ * @param nonce 16 bytes, must be unique per (key, message)
68
+ */
24
69
  beginEncrypt(key: Uint8Array, nonce: Uint8Array): void;
70
+ /**
71
+ * XOR `chunk` with the next keystream block(s). Counter advances automatically.
72
+ * @param chunk Plaintext chunk, must not exceed WASM CHUNK_SIZE
73
+ * @returns Ciphertext of the same length
74
+ */
25
75
  encryptChunk(chunk: Uint8Array): Uint8Array;
76
+ /**
77
+ * Alias for `beginEncrypt`, CTR mode is symmetric.
78
+ * @param key 16, 24, or 32 bytes
79
+ * @param nonce 16 bytes, must match the value used to encrypt
80
+ */
26
81
  beginDecrypt(key: Uint8Array, nonce: Uint8Array): void;
82
+ /**
83
+ * Alias for `encryptChunk`, CTR mode is symmetric.
84
+ * @param chunk Ciphertext chunk
85
+ * @returns Plaintext of the same length
86
+ */
27
87
  decryptChunk(chunk: Uint8Array): Uint8Array;
88
+ /** Wipe WASM state and release exclusive module access. Idempotent. */
28
89
  dispose(): void;
29
90
  }
30
91
  export { SerpentCbc } from './serpent-cbc.js';
31
92
  export { AuthenticationError } from '../errors.js';
32
93
  export { SerpentCipher } from './cipher-suite.js';
33
- export declare function _serpentReady(): boolean;
94
+ export { SerpentGenerator } from './generator.js';
@@ -22,29 +22,60 @@
22
22
  // src/ts/serpent/index.ts
23
23
  //
24
24
  // Public API classes for the Serpent-256 WASM module.
25
- // Uses the init() module cache call serpentInit(source) before constructing.
26
- import { getInstance, initModule } from '../init.js';
25
+ // Uses the init() module cache, call serpentInit(source) before constructing.
26
+ import { getInstance, initModule, _acquireModule, _releaseModule, _assertNotOwned } from '../init.js';
27
+ /**
28
+ * Load and initialise the Serpent WASM module from `source`.
29
+ * Must be called before constructing any Serpent class.
30
+ * @param source WASM binary, gzip+base64 string, URL, ArrayBuffer, Uint8Array,
31
+ * pre-compiled WebAssembly.Module, Response, or Promise<Response>
32
+ */
27
33
  export async function serpentInit(source) {
28
34
  return initModule('serpent', source);
29
35
  }
36
+ export { isInitialized } from '../init.js';
37
+ /** Returns the raw serpent WASM export object. @internal */
30
38
  function getExports() {
31
39
  return getInstance('serpent').exports;
32
40
  }
33
- // ── Serpent ──────────────────────────────────────────────────────────────────
41
+ // ── Serpent ─────────────────────────────────────────────────────────────────
42
+ /**
43
+ * Low-level Serpent-256 block cipher, raw ECB encrypt/decrypt.
44
+ *
45
+ * Atomic (stateless) class: each method call is independent.
46
+ * Does not hold exclusive module access; cannot be used while a stateful
47
+ * instance (`SerpentCtr`, `SerpentCbc`, `SerpentCipher`) is alive.
48
+ * Call `dispose()` after use to wipe WASM key material.
49
+ */
34
50
  export class Serpent {
35
51
  x;
36
52
  constructor() {
37
53
  this.x = getExports();
38
54
  }
55
+ /**
56
+ * Expand `key` into the WASM key schedule. Must be called before
57
+ * `encryptBlock` / `decryptBlock`.
58
+ * @param key 16, 24, or 32 bytes
59
+ */
39
60
  loadKey(key) {
61
+ _assertNotOwned('serpent');
40
62
  if (key.length !== 16 && key.length !== 24 && key.length !== 32)
41
63
  throw new RangeError(`key must be 16, 24, or 32 bytes (got ${key.length})`);
42
64
  const mem = new Uint8Array(this.x.memory.buffer);
43
65
  mem.set(key, this.x.getKeyOffset());
44
- if (this.x.loadKey(key.length) !== 0)
66
+ if (this.x.loadKey(key.length) !== 0) {
67
+ this.x.wipeBuffers();
45
68
  throw new Error('loadKey failed');
69
+ }
46
70
  }
71
+ /**
72
+ * Encrypt one 128-bit block with the previously loaded key schedule.
73
+ * Serpent AES submission §2.2.
74
+ * @param plaintext 16-byte plaintext block
75
+ * @returns 16-byte ciphertext block
76
+ */
47
77
  encryptBlock(plaintext) {
78
+ _assertNotOwned('serpent');
48
79
  if (plaintext.length !== 16)
49
80
  throw new RangeError(`block must be 16 bytes (got ${plaintext.length})`);
50
81
  const mem = new Uint8Array(this.x.memory.buffer);
@@ -54,7 +85,14 @@ export class Serpent {
54
85
  this.x.encryptBlock();
55
86
  return mem.slice(ctOff, ctOff + 16);
56
87
  }
88
+ /**
89
+ * Decrypt one 128-bit block with the previously loaded key schedule.
90
+ * Serpent AES submission §2.2.
91
+ * @param ciphertext 16-byte ciphertext block
92
+ * @returns 16-byte plaintext block
93
+ */
57
94
  decryptBlock(ciphertext) {
95
+ _assertNotOwned('serpent');
58
96
  if (ciphertext.length !== 16)
59
97
  throw new RangeError(`block must be 16 bytes (got ${ciphertext.length})`);
60
98
  const mem = new Uint8Array(this.x.memory.buffer);
@@ -64,28 +102,45 @@ export class Serpent {
64
102
  this.x.decryptBlock();
65
103
  return mem.slice(ptOff, ptOff + 16);
66
104
  }
105
+ /** Wipe WASM key material and release memory. */
67
106
  dispose() {
107
+ _assertNotOwned('serpent');
68
108
  this.x.wipeBuffers();
69
109
  }
70
110
  }
71
- // ── SerpentCtr ───────────────────────────────────────────────────────────────
111
+ // ── SerpentCtr ──────────────────────────────────────────────────────────────
72
112
  /**
73
113
  * Serpent-256 in CTR mode.
74
114
  *
75
115
  * **WARNING: CTR mode is unauthenticated.** An attacker can flip ciphertext
76
116
  * bits without detection. Always pair with HMAC-SHA256 (Encrypt-then-MAC)
77
117
  * or use `XChaCha20Poly1305` instead.
118
+ *
119
+ * Holds exclusive access to the `serpent` WASM module from construction
120
+ * until `dispose()`. Constructing a second SerpentCtr/SerpentCbc/
121
+ * SerpentCipher or any other serpent user while this instance is live
122
+ * throws. Call `dispose()` when done.
78
123
  */
79
124
  export class SerpentCtr {
80
125
  x;
126
+ _tok;
81
127
  constructor(opts) {
82
128
  if (!opts?.dangerUnauthenticated) {
83
- throw new Error('leviathan-crypto: SerpentCtr is unauthenticated use Seal with SerpentCipher instead. ' +
129
+ throw new Error('leviathan-crypto: SerpentCtr is unauthenticated, use Seal with SerpentCipher instead. ' +
84
130
  'To use SerpentCtr directly, pass { dangerUnauthenticated: true }.');
85
131
  }
86
132
  this.x = getExports();
133
+ this._tok = _acquireModule('serpent');
87
134
  }
135
+ /**
136
+ * Load key and nonce into WASM state and reset the block counter to 0.
137
+ * Must be called before each message.
138
+ * @param key 16, 24, or 32 bytes
139
+ * @param nonce 16 bytes, must be unique per (key, message)
140
+ */
88
141
  beginEncrypt(key, nonce) {
142
+ if (this._tok === undefined)
143
+ throw new Error('SerpentCtr: instance has been disposed');
89
144
  if (key.length !== 16 && key.length !== 24 && key.length !== 32)
90
145
  throw new RangeError('key must be 16, 24, or 32 bytes');
91
146
  if (nonce.length !== 16)
@@ -93,13 +148,23 @@ export class SerpentCtr {
93
148
  const mem = new Uint8Array(this.x.memory.buffer);
94
149
  mem.set(key, this.x.getKeyOffset());
95
150
  mem.set(nonce, this.x.getNonceOffset());
96
- this.x.loadKey(key.length);
151
+ if (this.x.loadKey(key.length) !== 0) {
152
+ this.x.wipeBuffers();
153
+ throw new Error('SerpentCtr: loadKey failed');
154
+ }
97
155
  this.x.resetCounter();
98
156
  }
157
+ /**
158
+ * XOR `chunk` with the next keystream block(s). Counter advances automatically.
159
+ * @param chunk Plaintext chunk, must not exceed WASM CHUNK_SIZE
160
+ * @returns Ciphertext of the same length
161
+ */
99
162
  encryptChunk(chunk) {
163
+ if (this._tok === undefined)
164
+ throw new Error('SerpentCtr: instance has been disposed');
100
165
  const maxChunk = this.x.getChunkSize();
101
166
  if (chunk.length > maxChunk)
102
- throw new RangeError(`chunk exceeds maximum size of ${maxChunk} bytes split into smaller chunks`);
167
+ throw new RangeError(`chunk exceeds maximum size of ${maxChunk} bytes, split into smaller chunks`);
103
168
  const mem = new Uint8Array(this.x.memory.buffer);
104
169
  const ptOff = this.x.getChunkPtOffset();
105
170
  const ctOff = this.x.getChunkCtOffset();
@@ -107,28 +172,39 @@ export class SerpentCtr {
107
172
  this.x.encryptChunk_simd(chunk.length);
108
173
  return mem.slice(ctOff, ctOff + chunk.length);
109
174
  }
175
+ /**
176
+ * Alias for `beginEncrypt`, CTR mode is symmetric.
177
+ * @param key 16, 24, or 32 bytes
178
+ * @param nonce 16 bytes, must match the value used to encrypt
179
+ */
110
180
  beginDecrypt(key, nonce) {
111
181
  this.beginEncrypt(key, nonce);
112
182
  }
183
+ /**
184
+ * Alias for `encryptChunk`, CTR mode is symmetric.
185
+ * @param chunk Ciphertext chunk
186
+ * @returns Plaintext of the same length
187
+ */
113
188
  decryptChunk(chunk) {
114
189
  return this.encryptChunk(chunk);
115
190
  }
191
+ /** Wipe WASM state and release exclusive module access. Idempotent. */
116
192
  dispose() {
117
- this.x.wipeBuffers();
193
+ if (this._tok === undefined)
194
+ return;
195
+ try {
196
+ this.x.wipeBuffers();
197
+ }
198
+ finally {
199
+ _releaseModule('serpent', this._tok);
200
+ this._tok = undefined;
201
+ }
118
202
  }
119
203
  }
120
- // ── SerpentCbc ───────────────────────────────────────────────────────────────
204
+ // ── SerpentCbc ──────────────────────────────────────────────────────────────
121
205
  export { SerpentCbc } from './serpent-cbc.js';
122
206
  export { AuthenticationError } from '../errors.js';
123
- // ── SerpentCipher re-export ───────────────────────────────────────────────────
207
+ // ── SerpentCipher re-export ─────────────────────────────────────────────────
124
208
  export { SerpentCipher } from './cipher-suite.js';
125
- // ── Ready check ──────────────────────────────────────────────────────────────
126
- export function _serpentReady() {
127
- try {
128
- getInstance('serpent');
129
- return true;
130
- }
131
- catch {
132
- return false;
133
- }
134
- }
209
+ // ── SerpentGenerator ────────────────────────────────────────────────────────
210
+ export { SerpentGenerator } from './generator.js';
@@ -3,108 +3,31 @@
3
3
  //
4
4
  // Worker for SealStreamPool with SerpentCipher.
5
5
  // Holds 3 derived keys (enc/mac/iv) and raw WASM instances.
6
- // Direct WASM calls no initModule (avoids same-thread module cache conflicts
6
+ // Direct WASM calls, no initModule (avoids same-thread module cache conflicts
7
7
  // in @vitest/web-worker test environment).
8
+ //
9
+ // All HMAC / CBC / PKCS7 primitives come from `./shared-ops.js`, the same
10
+ // module the main-thread `SerpentCipher` uses. Byte-identical output with
11
+ // the main thread is the regression guard (see
12
+ // test/unit/stream/pool-byte-exact.test.ts). Must NOT import from `../init.js`:
13
+ // workers have their own isolated WASM instances, no shared-state registry.
8
14
  import { constantTimeEqual, wipe, concat } from '../utils.js';
9
15
  import { AuthenticationError } from '../errors.js';
16
+ import { hmacSha256, cbcEncryptChunk, cbcDecryptChunk, } from './shared-ops.js';
10
17
  let sha2;
11
18
  let serpent;
12
19
  let keys;
13
- function hmacSha256(key, msg) {
14
- const x = sha2;
15
- let k = key;
16
- if (k.length > 64) {
17
- x.sha256Init();
18
- feedSha2(k);
19
- x.sha256Final();
20
- const mem = new Uint8Array(x.memory.buffer);
21
- k = mem.slice(x.getSha256OutOffset(), x.getSha256OutOffset() + 32);
22
- }
23
- const mem = new Uint8Array(x.memory.buffer);
24
- mem.set(k, x.getSha256InputOffset());
25
- x.hmac256Init(k.length);
26
- feedHmac(msg);
27
- x.hmac256Final();
28
- return new Uint8Array(x.memory.buffer).slice(x.getSha256OutOffset(), x.getSha256OutOffset() + 32);
29
- }
30
- function feedSha2(data) {
31
- const x = sha2;
32
- const mem = new Uint8Array(x.memory.buffer);
33
- const off = x.getSha256InputOffset();
34
- let pos = 0;
35
- while (pos < data.length) {
36
- const n = Math.min(data.length - pos, 64);
37
- mem.set(data.subarray(pos, pos + n), off);
38
- x.sha256Update(n);
39
- pos += n;
40
- }
41
- }
42
- function feedHmac(data) {
43
- const x = sha2;
44
- const mem = new Uint8Array(x.memory.buffer);
45
- const off = x.getSha256InputOffset();
46
- let pos = 0;
47
- while (pos < data.length) {
48
- const n = Math.min(data.length - pos, 64);
49
- mem.set(data.subarray(pos, pos + n), off);
50
- x.hmac256Update(n);
51
- pos += n;
52
- }
53
- }
54
- function pkcs7Pad(data) {
55
- const padLen = 16 - (data.length % 16);
56
- const out = new Uint8Array(data.length + padLen);
57
- out.set(data);
58
- out.fill(padLen, data.length);
59
- return out;
60
- }
61
- // pkcs7Strip is only called after HMAC authentication succeeds (verify-then-decrypt).
62
- // The early throw on invalid padLen is not a padding oracle in this context —
63
- // the HMAC check is the oracle gate and runs in constant time before this point.
64
- // If you move this call to a pre-auth site, revisit the timing properties.
65
- function pkcs7Strip(data) {
66
- if (data.length === 0)
67
- throw new RangeError('empty ciphertext');
68
- const padLen = data[data.length - 1];
69
- if (padLen === 0 || padLen > 16)
70
- throw new RangeError('invalid PKCS7 padding');
71
- if (padLen > data.length)
72
- throw new RangeError('invalid PKCS7 padding');
73
- let bad = 0;
74
- for (let i = data.length - padLen; i < data.length; i++)
75
- bad |= data[i] ^ padLen;
76
- if (bad !== 0)
77
- throw new RangeError('invalid PKCS7 padding');
78
- return data.subarray(0, data.length - padLen);
79
- }
80
- function cbcEncrypt(encKey, iv, plaintext) {
81
- const s = serpent;
82
- const mem = new Uint8Array(s.memory.buffer);
83
- mem.set(encKey, s.getKeyOffset());
84
- s.loadKey(encKey.length);
85
- mem.set(iv, s.getCbcIvOffset());
86
- const padded = pkcs7Pad(plaintext);
87
- mem.set(padded, s.getChunkPtOffset());
88
- const ret = s.cbcEncryptChunk(padded.length);
89
- if (ret < 0)
90
- throw new RangeError(`cbcEncryptChunk rejected len=${padded.length}` +
91
- ` (WASM CHUNK_SIZE=${s.getChunkSize()})`);
92
- return new Uint8Array(s.memory.buffer).slice(s.getChunkCtOffset(), s.getChunkCtOffset() + padded.length);
93
- }
94
- function cbcDecrypt(encKey, iv, ct) {
95
- const s = serpent;
96
- const mem = new Uint8Array(s.memory.buffer);
97
- mem.set(encKey, s.getKeyOffset());
98
- s.loadKey(encKey.length);
99
- mem.set(iv, s.getCbcIvOffset());
100
- mem.set(ct, s.getChunkCtOffset());
101
- const ret = s.cbcDecryptChunk(ct.length);
102
- if (ret < 0)
103
- throw new RangeError(`cbcDecryptChunk rejected len=${ct.length}` +
104
- ` (WASM CHUNK_SIZE=${s.getChunkSize()})`);
105
- const raw = new Uint8Array(s.memory.buffer).slice(s.getChunkPtOffset(), s.getChunkPtOffset() + ct.length);
106
- return pkcs7Strip(raw);
107
- }
20
+ /**
21
+ * Message handler for the Serpent pool worker.
22
+ *
23
+ * Accepts three message types:
24
+ * - `'init'` , instantiate sha2 + serpent WASM modules and store derived keys
25
+ * - `'wipe'` , zero keys and WASM buffers, then post `{ type: 'wiped' }`
26
+ * - `{ op: 'seal' | 'open', ... }`, encrypt or decrypt one chunk
27
+ *
28
+ * Replies with `{ type: 'result', id, data }` on success or
29
+ * `{ type: 'error', id, message, isAuthError }` on failure.
30
+ */
108
31
  self.onmessage = async (e) => {
109
32
  const msg = e.data;
110
33
  if (msg.type === 'init') {
@@ -122,6 +45,8 @@ self.onmessage = async (e) => {
122
45
  self.postMessage({ type: 'ready' });
123
46
  }
124
47
  catch (err) {
48
+ if (msg.derivedKeyBytes)
49
+ msg.derivedKeyBytes.fill(0);
125
50
  self.postMessage({ type: 'error', id: -1, message: err.message, isAuthError: false });
126
51
  }
127
52
  return;
@@ -136,6 +61,7 @@ self.onmessage = async (e) => {
136
61
  serpent.wipeBuffers();
137
62
  sha2 = undefined;
138
63
  serpent = undefined;
64
+ self.postMessage({ type: 'wiped' });
139
65
  return;
140
66
  }
141
67
  if (!keys || !sha2 || !serpent) {
@@ -151,14 +77,14 @@ self.onmessage = async (e) => {
151
77
  const ivKey = jobKey.subarray(64, 96);
152
78
  let result;
153
79
  if (op === 'seal') {
154
- const ivFull = hmacSha256(ivKey, counterNonce);
80
+ const ivFull = hmacSha256(sha2, ivKey, counterNonce);
155
81
  const iv = ivFull.slice(0, 16);
156
82
  wipe(ivFull);
157
- const ct = cbcEncrypt(encKey, iv, data);
83
+ const ct = cbcEncryptChunk(serpent, encKey, iv, data);
158
84
  const aadLenBuf = new Uint8Array(4);
159
85
  new DataView(aadLenBuf.buffer).setUint32(0, aadBytes.length, false);
160
86
  const tagInput = concat(counterNonce, aadLenBuf, aadBytes, ct);
161
- const tag = hmacSha256(macKey, tagInput);
87
+ const tag = hmacSha256(sha2, macKey, tagInput);
162
88
  result = concat(ct, tag);
163
89
  wipe(iv);
164
90
  wipe(tagInput);
@@ -166,13 +92,13 @@ self.onmessage = async (e) => {
166
92
  else {
167
93
  const ct = data.subarray(0, data.length - 32);
168
94
  const receivedTag = data.subarray(data.length - 32);
169
- const ivFull = hmacSha256(ivKey, counterNonce);
95
+ const ivFull = hmacSha256(sha2, ivKey, counterNonce);
170
96
  const iv = ivFull.slice(0, 16);
171
97
  wipe(ivFull);
172
98
  const aadLenBuf = new Uint8Array(4);
173
99
  new DataView(aadLenBuf.buffer).setUint32(0, aadBytes.length, false);
174
100
  const tagInput = concat(counterNonce, aadLenBuf, aadBytes, ct);
175
- const expectedTag = hmacSha256(macKey, tagInput);
101
+ const expectedTag = hmacSha256(sha2, macKey, tagInput);
176
102
  // CRITICAL: verify HMAC before decrypting (Vaudenay 2002)
177
103
  if (!constantTimeEqual(expectedTag, receivedTag)) {
178
104
  wipe(iv);
@@ -182,7 +108,7 @@ self.onmessage = async (e) => {
182
108
  }
183
109
  wipe(tagInput);
184
110
  wipe(expectedTag);
185
- result = cbcDecrypt(encKey, iv, ct);
111
+ result = cbcDecryptChunk(serpent, encKey, iv, ct);
186
112
  wipe(iv);
187
113
  }
188
114
  const transfer = result.buffer instanceof ArrayBuffer ? [result.buffer] : [];
@@ -3,28 +3,38 @@
3
3
  *
4
4
  * **WARNING: CBC mode is unauthenticated.** Always authenticate the output
5
5
  * with HMAC-SHA256 (Encrypt-then-MAC) or use `XChaCha20Poly1305` instead.
6
+ *
7
+ * Holds exclusive access to the `serpent` WASM module from construction
8
+ * until `dispose()`. Constructing a second SerpentCbc/SerpentCtr/
9
+ * SerpentCipher or any other serpent user while this instance is live
10
+ * throws. Call `dispose()` when done.
6
11
  */
7
12
  export declare class SerpentCbc {
8
13
  private readonly x;
14
+ private _tok;
9
15
  constructor(opts?: {
10
16
  dangerUnauthenticated: true;
11
17
  });
12
- private get mem();
13
18
  /**
14
19
  * Encrypt plaintext with Serpent-256 CBC + PKCS7 padding.
15
20
  *
16
21
  * @param key 16, 24, or 32 bytes
17
- * @param iv 16 bytes must be random and unique per (key, message)
18
- * @param plaintext any length PKCS7 padding applied automatically
22
+ * @param iv 16 bytes, must be random and unique per (key, message)
23
+ * @param plaintext any length, PKCS7 padding applied automatically
19
24
  * @returns ciphertext (length = ceil((plaintext.length + 1) / 16) * 16)
20
25
  */
21
26
  encrypt(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array): Uint8Array;
22
27
  /**
23
28
  * Decrypt Serpent-256 CBC + PKCS7.
24
- * Throws if ciphertext length is not a non-zero multiple of 16 or PKCS7 is invalid.
29
+ *
30
+ * All failure modes, empty input, non-multiple-of-16 length, and any
31
+ * PKCS7 validation failure, throw the same generic `RangeError` with
32
+ * message `'invalid ciphertext'`. Padding validation runs branch-free
33
+ * over the last 16 bytes regardless of where the mismatch is, closing
34
+ * the Vaudenay 2002 padding-oracle surface for callers using
35
+ * `{ dangerUnauthenticated: true }` without an outer HMAC.
25
36
  */
26
37
  decrypt(key: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array): Uint8Array;
38
+ /** Wipe WASM state and release exclusive module access. Idempotent. */
27
39
  dispose(): void;
28
- private _loadKey;
29
- private _setIv;
30
40
  }