leviathan-crypto 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/CLAUDE.md +86 -443
  2. package/README.md +198 -65
  3. package/dist/aes/aes-cbc.d.ts +40 -0
  4. package/dist/aes/aes-cbc.js +158 -0
  5. package/dist/aes/aes-ctr.d.ts +50 -0
  6. package/dist/aes/aes-ctr.js +141 -0
  7. package/dist/aes/aes-gcm-siv.d.ts +67 -0
  8. package/dist/aes/aes-gcm-siv.js +217 -0
  9. package/dist/aes/aes-gcm.d.ts +61 -0
  10. package/dist/aes/aes-gcm.js +226 -0
  11. package/dist/aes/cipher-suite.d.ts +21 -0
  12. package/dist/aes/cipher-suite.js +179 -0
  13. package/dist/aes/embedded.d.ts +1 -0
  14. package/dist/aes/embedded.js +26 -0
  15. package/dist/aes/generator.d.ts +14 -0
  16. package/dist/aes/generator.js +103 -0
  17. package/dist/aes/index.d.ts +58 -0
  18. package/dist/aes/index.js +125 -0
  19. package/dist/aes/ops.d.ts +60 -0
  20. package/dist/aes/ops.js +164 -0
  21. package/dist/aes/pool-worker.d.ts +1 -0
  22. package/dist/aes/pool-worker.js +92 -0
  23. package/dist/aes/types.d.ts +1 -0
  24. package/dist/aes/types.js +23 -0
  25. package/dist/aes.wasm +0 -0
  26. package/dist/blake3/embedded.d.ts +1 -0
  27. package/dist/blake3/embedded.js +26 -0
  28. package/dist/blake3/index.d.ts +143 -0
  29. package/dist/blake3/index.js +620 -0
  30. package/dist/blake3/types.d.ts +102 -0
  31. package/dist/blake3/types.js +31 -0
  32. package/dist/blake3/validate.d.ts +29 -0
  33. package/dist/blake3/validate.js +80 -0
  34. package/dist/blake3.wasm +0 -0
  35. package/dist/chacha20/cipher-suite.js +47 -25
  36. package/dist/chacha20/generator.d.ts +2 -2
  37. package/dist/chacha20/generator.js +4 -4
  38. package/dist/chacha20/index.d.ts +16 -15
  39. package/dist/chacha20/index.js +52 -46
  40. package/dist/chacha20/ops.d.ts +7 -7
  41. package/dist/chacha20/ops.js +34 -34
  42. package/dist/chacha20/pool-worker.js +5 -3
  43. package/dist/cte-wasm.d.ts +1 -0
  44. package/dist/cte-wasm.js +3 -0
  45. package/dist/curve25519.wasm +0 -0
  46. package/dist/ecdsa/der.d.ts +23 -0
  47. package/dist/ecdsa/der.js +192 -0
  48. package/dist/ecdsa/ecprivatekey-der.d.ts +32 -0
  49. package/dist/ecdsa/ecprivatekey-der.js +230 -0
  50. package/dist/ecdsa/embedded.d.ts +1 -0
  51. package/dist/ecdsa/embedded.js +25 -0
  52. package/dist/ecdsa/index.d.ts +124 -0
  53. package/dist/ecdsa/index.js +366 -0
  54. package/dist/ecdsa/types.d.ts +31 -0
  55. package/dist/ecdsa/types.js +28 -0
  56. package/dist/ecdsa/validate.d.ts +18 -0
  57. package/dist/ecdsa/validate.js +92 -0
  58. package/dist/ed25519/embedded.d.ts +1 -0
  59. package/dist/ed25519/embedded.js +31 -0
  60. package/dist/ed25519/index.d.ts +70 -0
  61. package/dist/ed25519/index.js +308 -0
  62. package/dist/ed25519/types.d.ts +27 -0
  63. package/dist/ed25519/types.js +27 -0
  64. package/dist/ed25519/validate.d.ts +7 -0
  65. package/dist/ed25519/validate.js +77 -0
  66. package/dist/embedded/aes-pool-worker.d.ts +1 -0
  67. package/dist/embedded/aes-pool-worker.js +5 -0
  68. package/dist/embedded/aes.d.ts +1 -0
  69. package/dist/embedded/aes.js +3 -0
  70. package/dist/embedded/blake3.d.ts +1 -0
  71. package/dist/embedded/blake3.js +3 -0
  72. package/dist/embedded/chacha20-pool-worker.d.ts +1 -1
  73. package/dist/embedded/chacha20-pool-worker.js +2 -2
  74. package/dist/embedded/chacha20.d.ts +1 -1
  75. package/dist/embedded/chacha20.js +2 -2
  76. package/dist/embedded/curve25519.d.ts +1 -0
  77. package/dist/embedded/curve25519.js +3 -0
  78. package/dist/embedded/mldsa.d.ts +1 -0
  79. package/dist/embedded/mldsa.js +3 -0
  80. package/dist/embedded/mlkem.d.ts +1 -0
  81. package/dist/embedded/mlkem.js +3 -0
  82. package/dist/embedded/p256.d.ts +1 -0
  83. package/dist/embedded/p256.js +3 -0
  84. package/dist/embedded/serpent-pool-worker.d.ts +1 -1
  85. package/dist/embedded/serpent-pool-worker.js +2 -2
  86. package/dist/embedded/serpent.d.ts +1 -1
  87. package/dist/embedded/serpent.js +2 -2
  88. package/dist/embedded/sha2.d.ts +1 -1
  89. package/dist/embedded/sha2.js +2 -2
  90. package/dist/embedded/sha3.d.ts +1 -1
  91. package/dist/embedded/sha3.js +2 -2
  92. package/dist/embedded/slhdsa.d.ts +1 -0
  93. package/dist/embedded/slhdsa.js +3 -0
  94. package/dist/errors.d.ts +92 -1
  95. package/dist/errors.js +111 -1
  96. package/dist/fortuna.d.ts +5 -5
  97. package/dist/fortuna.js +37 -64
  98. package/dist/index.d.ts +38 -9
  99. package/dist/index.js +63 -19
  100. package/dist/init.d.ts +1 -1
  101. package/dist/init.js +11 -25
  102. package/dist/keccak/embedded.js +1 -1
  103. package/dist/keccak/index.d.ts +2 -0
  104. package/dist/keccak/index.js +4 -2
  105. package/dist/loader.d.ts +1 -24
  106. package/dist/loader.js +13 -16
  107. package/dist/merkle/blake3-tree.d.ts +35 -0
  108. package/dist/merkle/blake3-tree.js +187 -0
  109. package/dist/merkle/checkpoint.d.ts +58 -0
  110. package/dist/merkle/checkpoint.js +217 -0
  111. package/dist/merkle/index.d.ts +19 -0
  112. package/dist/merkle/index.js +37 -0
  113. package/dist/merkle/merkle-log.d.ts +130 -0
  114. package/dist/merkle/merkle-log.js +207 -0
  115. package/dist/merkle/merkle-verifier.d.ts +126 -0
  116. package/dist/merkle/merkle-verifier.js +296 -0
  117. package/dist/merkle/proof.d.ts +70 -0
  118. package/dist/merkle/proof.js +300 -0
  119. package/dist/merkle/sha256-tree.d.ts +33 -0
  120. package/dist/merkle/sha256-tree.js +145 -0
  121. package/dist/merkle/signed-log.d.ts +156 -0
  122. package/dist/merkle/signed-log.js +356 -0
  123. package/dist/merkle/signed-note.d.ts +309 -0
  124. package/dist/merkle/signed-note.js +648 -0
  125. package/dist/merkle/sth.d.ts +31 -0
  126. package/dist/merkle/sth.js +31 -0
  127. package/dist/merkle/storage.d.ts +40 -0
  128. package/dist/merkle/storage.js +71 -0
  129. package/dist/merkle/tree.d.ts +68 -0
  130. package/dist/merkle/tree.js +94 -0
  131. package/dist/mldsa/embedded.d.ts +1 -0
  132. package/dist/{kyber → mldsa}/embedded.js +5 -5
  133. package/dist/mldsa/expand.d.ts +53 -0
  134. package/dist/mldsa/expand.js +188 -0
  135. package/dist/mldsa/format.d.ts +16 -0
  136. package/dist/mldsa/format.js +68 -0
  137. package/dist/mldsa/hashvariant.d.ts +32 -0
  138. package/dist/mldsa/hashvariant.js +248 -0
  139. package/dist/mldsa/index.d.ts +142 -0
  140. package/dist/mldsa/index.js +463 -0
  141. package/dist/mldsa/keygen.d.ts +16 -0
  142. package/dist/mldsa/keygen.js +232 -0
  143. package/dist/mldsa/params.d.ts +21 -0
  144. package/dist/mldsa/params.js +55 -0
  145. package/dist/mldsa/sha3-helpers.d.ts +30 -0
  146. package/dist/mldsa/sha3-helpers.js +124 -0
  147. package/dist/mldsa/sign.d.ts +36 -0
  148. package/dist/mldsa/sign.js +380 -0
  149. package/dist/mldsa/types.d.ts +91 -0
  150. package/dist/mldsa/types.js +25 -0
  151. package/dist/mldsa/validate.d.ts +55 -0
  152. package/dist/mldsa/validate.js +125 -0
  153. package/dist/mldsa/verify.d.ts +29 -0
  154. package/dist/mldsa/verify.js +269 -0
  155. package/dist/mldsa.wasm +0 -0
  156. package/dist/mlkem/embedded.d.ts +1 -0
  157. package/dist/mlkem/embedded.js +27 -0
  158. package/dist/mlkem/indcpa.d.ts +49 -0
  159. package/dist/{kyber → mlkem}/indcpa.js +44 -44
  160. package/dist/mlkem/index.d.ts +37 -0
  161. package/dist/{kyber → mlkem}/index.js +24 -34
  162. package/dist/mlkem/kem.d.ts +21 -0
  163. package/dist/{kyber → mlkem}/kem.js +44 -64
  164. package/dist/{kyber → mlkem}/params.d.ts +4 -4
  165. package/dist/{kyber → mlkem}/params.js +2 -2
  166. package/dist/mlkem/suite.d.ts +12 -0
  167. package/dist/{kyber → mlkem}/suite.js +17 -12
  168. package/dist/{kyber → mlkem}/types.d.ts +3 -3
  169. package/dist/{kyber → mlkem}/types.js +1 -1
  170. package/dist/{kyber → mlkem}/validate.d.ts +7 -7
  171. package/dist/{kyber → mlkem}/validate.js +7 -7
  172. package/dist/{kyber.wasm → mlkem.wasm} +0 -0
  173. package/dist/p256.wasm +0 -0
  174. package/dist/ratchet/index.d.ts +2 -0
  175. package/dist/ratchet/index.js +1 -0
  176. package/dist/ratchet/kdf-chain.js +3 -3
  177. package/dist/ratchet/ratchet-keypair.js +2 -2
  178. package/dist/ratchet/root-kdf.js +7 -7
  179. package/dist/ratchet/skipped-key-store.js +4 -4
  180. package/dist/ratchet/types.d.ts +1 -1
  181. package/dist/serpent/cipher-suite.js +20 -17
  182. package/dist/serpent/generator.d.ts +1 -1
  183. package/dist/serpent/generator.js +2 -2
  184. package/dist/serpent/index.d.ts +8 -7
  185. package/dist/serpent/index.js +18 -27
  186. package/dist/serpent/pool-worker.js +7 -5
  187. package/dist/serpent/serpent-cbc.d.ts +4 -4
  188. package/dist/serpent/serpent-cbc.js +11 -8
  189. package/dist/serpent/shared-ops.d.ts +3 -23
  190. package/dist/serpent/shared-ops.js +50 -85
  191. package/dist/serpent.wasm +0 -0
  192. package/dist/sha2/hkdf.js +5 -5
  193. package/dist/sha2/index.d.ts +21 -1
  194. package/dist/sha2/index.js +65 -10
  195. package/dist/sha2/types.d.ts +41 -2
  196. package/dist/sha2.wasm +0 -0
  197. package/dist/sha3/index.d.ts +72 -3
  198. package/dist/sha3/index.js +240 -14
  199. package/dist/sha3/kmac.d.ts +121 -0
  200. package/dist/sha3/kmac.js +800 -0
  201. package/dist/sha3.wasm +0 -0
  202. package/dist/shared/pkcs7.d.ts +22 -0
  203. package/dist/shared/pkcs7.js +84 -0
  204. package/dist/sign/ctx.d.ts +41 -0
  205. package/dist/sign/ctx.js +102 -0
  206. package/dist/sign/envelope.d.ts +45 -0
  207. package/dist/sign/envelope.js +152 -0
  208. package/dist/sign/hasher.d.ts +9 -0
  209. package/dist/sign/hasher.js +132 -0
  210. package/dist/sign/index.d.ts +11 -0
  211. package/dist/sign/index.js +34 -0
  212. package/dist/sign/sign-stream.d.ts +25 -0
  213. package/dist/sign/sign-stream.js +112 -0
  214. package/dist/sign/suites/ecdsa-p256.d.ts +2 -0
  215. package/dist/sign/suites/ecdsa-p256.js +120 -0
  216. package/dist/sign/suites/ed25519.d.ts +3 -0
  217. package/dist/sign/suites/ed25519.js +165 -0
  218. package/dist/sign/suites/hybrid-classical.d.ts +23 -0
  219. package/dist/sign/suites/hybrid-classical.js +526 -0
  220. package/dist/sign/suites/hybrid-pq.d.ts +4 -0
  221. package/dist/sign/suites/hybrid-pq.js +234 -0
  222. package/dist/sign/suites/mldsa.d.ts +7 -0
  223. package/dist/sign/suites/mldsa.js +161 -0
  224. package/dist/sign/suites/slhdsa.d.ts +7 -0
  225. package/dist/sign/suites/slhdsa.js +176 -0
  226. package/dist/sign/types.d.ts +106 -0
  227. package/dist/sign/types.js +28 -0
  228. package/dist/sign/verify-stream.d.ts +30 -0
  229. package/dist/sign/verify-stream.js +227 -0
  230. package/dist/slhdsa/embedded.d.ts +1 -0
  231. package/dist/slhdsa/embedded.js +26 -0
  232. package/dist/slhdsa/index.d.ts +149 -0
  233. package/dist/slhdsa/index.js +493 -0
  234. package/dist/slhdsa/params.d.ts +26 -0
  235. package/dist/slhdsa/params.js +70 -0
  236. package/dist/slhdsa/prehash.d.ts +68 -0
  237. package/dist/slhdsa/prehash.js +307 -0
  238. package/dist/slhdsa/sign.d.ts +39 -0
  239. package/dist/slhdsa/sign.js +116 -0
  240. package/dist/slhdsa/types.d.ts +129 -0
  241. package/dist/slhdsa/types.js +27 -0
  242. package/dist/slhdsa/validate.d.ts +60 -0
  243. package/dist/slhdsa/validate.js +127 -0
  244. package/dist/slhdsa/verify.d.ts +32 -0
  245. package/dist/slhdsa/verify.js +107 -0
  246. package/dist/slhdsa.wasm +0 -0
  247. package/dist/stream/header.js +3 -3
  248. package/dist/stream/index.d.ts +1 -0
  249. package/dist/stream/index.js +1 -0
  250. package/dist/stream/open-stream.js +31 -10
  251. package/dist/stream/seal-stream-pool.d.ts +1 -0
  252. package/dist/stream/seal-stream-pool.js +63 -26
  253. package/dist/stream/seal-stream.d.ts +1 -1
  254. package/dist/stream/seal-stream.js +20 -9
  255. package/dist/stream/seal.js +6 -6
  256. package/dist/stream/types.d.ts +3 -1
  257. package/dist/stream/types.js +1 -1
  258. package/dist/types.d.ts +1 -1
  259. package/dist/types.js +1 -1
  260. package/dist/utils.d.ts +3 -3
  261. package/dist/utils.js +46 -54
  262. package/dist/wasm-source.d.ts +7 -7
  263. package/dist/wasm-source.js +1 -1
  264. package/dist/x25519/embedded.d.ts +1 -0
  265. package/dist/x25519/embedded.js +31 -0
  266. package/dist/x25519/index.d.ts +43 -0
  267. package/dist/x25519/index.js +159 -0
  268. package/dist/x25519/types.d.ts +25 -0
  269. package/dist/x25519/types.js +27 -0
  270. package/dist/x25519/validate.d.ts +2 -0
  271. package/dist/x25519/validate.js +39 -0
  272. package/package.json +70 -26
  273. package/SECURITY.md +0 -163
  274. package/dist/ct-wasm.d.ts +0 -1
  275. package/dist/ct-wasm.js +0 -3
  276. package/dist/docs/aead.md +0 -363
  277. package/dist/docs/architecture.md +0 -1011
  278. package/dist/docs/argon2id.md +0 -305
  279. package/dist/docs/chacha20.md +0 -781
  280. package/dist/docs/exports.md +0 -277
  281. package/dist/docs/fortuna.md +0 -530
  282. package/dist/docs/init.md +0 -301
  283. package/dist/docs/loader.md +0 -256
  284. package/dist/docs/serpent.md +0 -617
  285. package/dist/docs/sha2.md +0 -671
  286. package/dist/docs/sha3.md +0 -612
  287. package/dist/docs/types.md +0 -416
  288. package/dist/docs/utils.md +0 -457
  289. package/dist/embedded/kyber.d.ts +0 -1
  290. package/dist/embedded/kyber.js +0 -3
  291. package/dist/kyber/embedded.d.ts +0 -1
  292. package/dist/kyber/indcpa.d.ts +0 -49
  293. package/dist/kyber/index.d.ts +0 -38
  294. package/dist/kyber/kem.d.ts +0 -21
  295. package/dist/kyber/suite.d.ts +0 -12
  296. /package/dist/{ct.wasm → cte.wasm} +0 -0
@@ -0,0 +1,50 @@
1
+ /**
2
+ * AES-128/192/256 in CTR mode.
3
+ *
4
+ * **WARNING: CTR mode is unauthenticated.** An attacker can flip ciphertext
5
+ * bits without detection. Always pair with HMAC-SHA256 (Encrypt-then-MAC)
6
+ * or use an authenticated cipher (`AESGCM`, `AESGCMSIV`, or `Seal` with
7
+ * `AESGCMSIVCipher` / `SerpentCipher` / `XChaCha20Cipher`) instead.
8
+ *
9
+ * The constructor requires `{ dangerUnauthenticated: true }` so callers
10
+ * cannot reach the unauthenticated path by accident, same gate as
11
+ * `AESCbc` and `SerpentCtr`.
12
+ *
13
+ * The counter is 128-bit big-endian (SP 800-38A Appendix B.1 / §F.5).
14
+ *
15
+ * Stateful, the counter advances across `encrypt`/`decrypt` calls. Reset
16
+ * with `setNonce()` before each new message. Holds exclusive access to the
17
+ * `aes` WASM module from construction until `dispose()`.
18
+ */
19
+ export declare class AESCtr {
20
+ private readonly x;
21
+ private _tok;
22
+ constructor(opts?: {
23
+ dangerUnauthenticated: true;
24
+ });
25
+ /**
26
+ * Expand `key` into the WASM key schedule. Must be called before
27
+ * `setNonce` / `encrypt` / `decrypt`.
28
+ * @param key 16, 24, or 32 bytes (AES-128 / 192 / 256)
29
+ */
30
+ loadKey(key: Uint8Array): void;
31
+ /**
32
+ * Set the 128-bit initial counter block (the full IC, not a separate
33
+ * nonce/counter split). Resets the working counter so subsequent
34
+ * encrypt/decrypt calls start at this value.
35
+ * @param nonce 16 bytes, must be unique per (key, message)
36
+ */
37
+ setNonce(nonce: Uint8Array): void;
38
+ /**
39
+ * XOR `plaintext` with AES CTR keystream. The counter advances by
40
+ * ceil(plaintext.length / 16) blocks; counter state persists across
41
+ * calls until `setNonce()` resets it.
42
+ * @param plaintext any length; internally chunked to WASM CHUNK_SIZE
43
+ * @returns ciphertext of the same length
44
+ */
45
+ encrypt(plaintext: Uint8Array): Uint8Array;
46
+ /** Alias for `encrypt`, CTR mode is symmetric. */
47
+ decrypt(ciphertext: Uint8Array): Uint8Array;
48
+ /** Wipe WASM state and release exclusive module access. Idempotent. */
49
+ dispose(): void;
50
+ }
@@ -0,0 +1,141 @@
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/aes/aes-ctr.ts
23
+ //
24
+ // AESCtr, AES-128/192/256 in CTR mode, stateful TS wrapper.
25
+ // SP 800-38A §6.5 (mode), Appendix B.1 (counter increment).
26
+ // Counter direction: 128-bit big-endian, matching the SP 800-38A §F.5
27
+ // worked examples and the canonical AES CTR convention. Configured in
28
+ // the underlying WASM (`src/asm/aes/ctr.ts`).
29
+ import { getInstance, _acquireModule, _releaseModule } from '../init.js';
30
+ /** Returns the raw AES WASM export object. @internal */
31
+ function getExports() {
32
+ return getInstance('aes').exports;
33
+ }
34
+ // ── AESCtr ──────────────────────────────────────────────────────────────────
35
+ /**
36
+ * AES-128/192/256 in CTR mode.
37
+ *
38
+ * **WARNING: CTR mode is unauthenticated.** An attacker can flip ciphertext
39
+ * bits without detection. Always pair with HMAC-SHA256 (Encrypt-then-MAC)
40
+ * or use an authenticated cipher (`AESGCM`, `AESGCMSIV`, or `Seal` with
41
+ * `AESGCMSIVCipher` / `SerpentCipher` / `XChaCha20Cipher`) instead.
42
+ *
43
+ * The constructor requires `{ dangerUnauthenticated: true }` so callers
44
+ * cannot reach the unauthenticated path by accident, same gate as
45
+ * `AESCbc` and `SerpentCtr`.
46
+ *
47
+ * The counter is 128-bit big-endian (SP 800-38A Appendix B.1 / §F.5).
48
+ *
49
+ * Stateful, the counter advances across `encrypt`/`decrypt` calls. Reset
50
+ * with `setNonce()` before each new message. Holds exclusive access to the
51
+ * `aes` WASM module from construction until `dispose()`.
52
+ */
53
+ export class AESCtr {
54
+ x;
55
+ _tok;
56
+ constructor(opts) {
57
+ if (!opts?.dangerUnauthenticated) {
58
+ throw new Error('leviathan-crypto: AESCtr is unauthenticated, use Seal with AESGCMSIVCipher, SerpentCipher, or XChaCha20Cipher instead. ' +
59
+ 'To use AESCtr directly, pass { dangerUnauthenticated: true }.');
60
+ }
61
+ this.x = getExports();
62
+ this._tok = _acquireModule('aes');
63
+ }
64
+ /** View over WASM linear memory. Rebind on every access, memory can be detached after grow. @internal */
65
+ get mem() {
66
+ return new Uint8Array(this.x.memory.buffer);
67
+ }
68
+ /**
69
+ * Expand `key` into the WASM key schedule. Must be called before
70
+ * `setNonce` / `encrypt` / `decrypt`.
71
+ * @param key 16, 24, or 32 bytes (AES-128 / 192 / 256)
72
+ */
73
+ loadKey(key) {
74
+ if (this._tok === undefined)
75
+ throw new Error('AESCtr: instance has been disposed');
76
+ if (key.length !== 16 && key.length !== 24 && key.length !== 32)
77
+ throw new RangeError(`AES key must be 16, 24, or 32 bytes (got ${key.length})`);
78
+ this.mem.set(key, this.x.getKeyOffset());
79
+ if (this.x.loadKey(key.length) !== 0) {
80
+ this.x.wipeBuffers();
81
+ throw new Error('loadKey failed');
82
+ }
83
+ }
84
+ /**
85
+ * Set the 128-bit initial counter block (the full IC, not a separate
86
+ * nonce/counter split). Resets the working counter so subsequent
87
+ * encrypt/decrypt calls start at this value.
88
+ * @param nonce 16 bytes, must be unique per (key, message)
89
+ */
90
+ setNonce(nonce) {
91
+ if (this._tok === undefined)
92
+ throw new Error('AESCtr: instance has been disposed');
93
+ if (nonce.length !== 16)
94
+ throw new RangeError(`AES CTR nonce must be 16 bytes (got ${nonce.length})`);
95
+ this.mem.set(nonce, this.x.getNonceOffset());
96
+ this.x.resetCounter();
97
+ }
98
+ /**
99
+ * XOR `plaintext` with AES CTR keystream. The counter advances by
100
+ * ceil(plaintext.length / 16) blocks; counter state persists across
101
+ * calls until `setNonce()` resets it.
102
+ * @param plaintext any length; internally chunked to WASM CHUNK_SIZE
103
+ * @returns ciphertext of the same length
104
+ */
105
+ encrypt(plaintext) {
106
+ if (this._tok === undefined)
107
+ throw new Error('AESCtr: instance has been disposed');
108
+ const output = new Uint8Array(plaintext.length);
109
+ if (plaintext.length === 0)
110
+ return output;
111
+ const ptOff = this.x.getChunkPtOffset();
112
+ const ctOff = this.x.getChunkCtOffset();
113
+ const maxChunk = this.x.getChunkSize();
114
+ for (let off = 0; off < plaintext.length; off += maxChunk) {
115
+ const chunk = plaintext.subarray(off, Math.min(off + maxChunk, plaintext.length));
116
+ this.mem.set(chunk, ptOff);
117
+ const ret = this.x.encryptChunk_simd(chunk.length);
118
+ if (ret < 0)
119
+ throw new RangeError(`encryptChunk_simd rejected len=${chunk.length}` +
120
+ ` (WASM CHUNK_SIZE=${this.x.getChunkSize()})`);
121
+ output.set(new Uint8Array(this.x.memory.buffer).subarray(ctOff, ctOff + chunk.length), off);
122
+ }
123
+ return output;
124
+ }
125
+ /** Alias for `encrypt`, CTR mode is symmetric. */
126
+ decrypt(ciphertext) {
127
+ return this.encrypt(ciphertext);
128
+ }
129
+ /** Wipe WASM state and release exclusive module access. Idempotent. */
130
+ dispose() {
131
+ if (this._tok === undefined)
132
+ return;
133
+ try {
134
+ this.x.wipeBuffers();
135
+ }
136
+ finally {
137
+ _releaseModule('aes', this._tok);
138
+ this._tok = undefined;
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * AES-128-GCM-SIV / AES-256-GCM-SIV (RFC 8452). Nonce-misuse-resistant
3
+ * authenticated AEAD with a 128-bit tag. AES-192 keys are rejected
4
+ * (RFC 8452 §6, no AES-192-GCM-SIV variant exists).
5
+ *
6
+ * Single-shot only: each `seal` / `open` call processes one complete
7
+ * message bounded by 64 KiB of plaintext. Larger messages are out of
8
+ * scope for this primitive; a future streaming variant will lift the
9
+ * cap via the seal/sealstream layer.
10
+ *
11
+ * `seal(nonce, plaintext, aad?)` returns `ciphertext || tag` (length
12
+ * pt.length + 16). `open(nonce, sealed, aad?)` verifies the tag and
13
+ * returns the plaintext; throws `AuthenticationError('siv')` on any
14
+ * verification failure.
15
+ *
16
+ * Atomic, does not hold exclusive access between calls. `dispose()`
17
+ * wipes the stored key from the JS-side cache.
18
+ */
19
+ export declare class AESGCMSIV {
20
+ private readonly _key;
21
+ private _disposed;
22
+ /**
23
+ * @param key 16 bytes (AES-128-GCM-SIV) or 32 bytes (AES-256-GCM-SIV).
24
+ * 24-byte keys are rejected, RFC 8452 §6 does not define
25
+ * an AES-192-GCM-SIV variant.
26
+ */
27
+ constructor(key: Uint8Array);
28
+ /**
29
+ * Authenticated encryption.
30
+ *
31
+ * @param nonce exactly 12 bytes (RFC 8452 §6 fixes nonce length)
32
+ * @param plaintext any length up to 64 KiB; may be empty
33
+ * @param aad any length up to 64 KiB; may be empty
34
+ * @returns ciphertext concatenated with the 128-bit tag
35
+ * (length = plaintext.length + 16)
36
+ *
37
+ * @throws RangeError if any input length violates the spec or the
38
+ * buffer-bounded API.
39
+ */
40
+ seal(nonce: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Uint8Array;
41
+ /**
42
+ * Authenticated decryption. `sealed` is the output of a matching
43
+ * `seal(nonce, plaintext, aad)` call.
44
+ *
45
+ * Verification routes through `constantTimeEqual` from
46
+ * `../utils.js` (the dedicated `ct` WASM module). On mismatch the
47
+ * WASM `sivWipeOnFail` helper zeroes the decrypted-but-
48
+ * unauthenticated plaintext at CHUNK_PT_OFFSET before this method
49
+ * throws, the bytes never become reachable from JS.
50
+ *
51
+ * @throws AuthenticationError('siv') if the tag fails to verify, or
52
+ * if `sealed` is too short, or any input length violates the
53
+ * spec.
54
+ */
55
+ open(nonce: Uint8Array, sealed: Uint8Array, aad?: Uint8Array): Uint8Array;
56
+ /**
57
+ * Wipe the in-memory copy of the key. Idempotent. Subsequent calls
58
+ * to `seal` / `open` throw. WASM-side state is wiped at the end of
59
+ * every successful operation regardless of `dispose()`.
60
+ */
61
+ dispose(): void;
62
+ private _mem;
63
+ private _assertAlive;
64
+ private _validate;
65
+ /** Push KGK + nonce + AAD into WASM memory. Common to seal/open. */
66
+ private _stage;
67
+ }
@@ -0,0 +1,217 @@
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/aes/aes-gcm-siv.ts
23
+ import { getInstance, _acquireModule, _releaseModule } from '../init.js';
24
+ import { constantTimeEqual, wipe } from '../utils.js';
25
+ import { AuthenticationError } from '../errors.js';
26
+ // RFC 8452 §6: K_LEN ∈ {16, 32}, AES-128-GCM-SIV or AES-256-GCM-SIV.
27
+ const KEY_LEN_128 = 16;
28
+ const KEY_LEN_256 = 32;
29
+ // RFC 8452 §6: nonce length is fixed at 96 bits (12 bytes).
30
+ const NONCE_LEN = 12;
31
+ // Single-shot bound: plaintext fits in CHUNK_PT (64 KiB). Larger inputs
32
+ // would need a streaming SIV API; not currently in scope.
33
+ const MAX_PT_BYTES = 65536;
34
+ // AAD is bounded by the dedicated AAD_BUFFER (64 KiB).
35
+ const MAX_AAD_BYTES = 65536;
36
+ // 16-byte authentication tag.
37
+ const TAG_LEN = 16;
38
+ /** Returns the raw AES WASM export object. @internal */
39
+ function getExports() {
40
+ return getInstance('aes').exports;
41
+ }
42
+ // ── AESGCMSIV ───────────────────────────────────────────────────────────────
43
+ /**
44
+ * AES-128-GCM-SIV / AES-256-GCM-SIV (RFC 8452). Nonce-misuse-resistant
45
+ * authenticated AEAD with a 128-bit tag. AES-192 keys are rejected
46
+ * (RFC 8452 §6, no AES-192-GCM-SIV variant exists).
47
+ *
48
+ * Single-shot only: each `seal` / `open` call processes one complete
49
+ * message bounded by 64 KiB of plaintext. Larger messages are out of
50
+ * scope for this primitive; a future streaming variant will lift the
51
+ * cap via the seal/sealstream layer.
52
+ *
53
+ * `seal(nonce, plaintext, aad?)` returns `ciphertext || tag` (length
54
+ * pt.length + 16). `open(nonce, sealed, aad?)` verifies the tag and
55
+ * returns the plaintext; throws `AuthenticationError('siv')` on any
56
+ * verification failure.
57
+ *
58
+ * Atomic, does not hold exclusive access between calls. `dispose()`
59
+ * wipes the stored key from the JS-side cache.
60
+ */
61
+ export class AESGCMSIV {
62
+ _key;
63
+ _disposed = false;
64
+ /**
65
+ * @param key 16 bytes (AES-128-GCM-SIV) or 32 bytes (AES-256-GCM-SIV).
66
+ * 24-byte keys are rejected, RFC 8452 §6 does not define
67
+ * an AES-192-GCM-SIV variant.
68
+ */
69
+ constructor(key) {
70
+ if (key.length !== KEY_LEN_128 && key.length !== KEY_LEN_256) {
71
+ throw new RangeError(`AESGCMSIV key must be 16 or 32 bytes (got ${key.length}); `
72
+ + 'AES-192-GCM-SIV is not defined by RFC 8452');
73
+ }
74
+ // Defensive copy so external mutation cannot change the live key.
75
+ this._key = new Uint8Array(key);
76
+ }
77
+ /**
78
+ * Authenticated encryption.
79
+ *
80
+ * @param nonce exactly 12 bytes (RFC 8452 §6 fixes nonce length)
81
+ * @param plaintext any length up to 64 KiB; may be empty
82
+ * @param aad any length up to 64 KiB; may be empty
83
+ * @returns ciphertext concatenated with the 128-bit tag
84
+ * (length = plaintext.length + 16)
85
+ *
86
+ * @throws RangeError if any input length violates the spec or the
87
+ * buffer-bounded API.
88
+ */
89
+ seal(nonce, plaintext, aad = new Uint8Array(0)) {
90
+ this._assertAlive();
91
+ this._validate(nonce, plaintext.length, aad);
92
+ const x = getExports();
93
+ const tok = _acquireModule('aes');
94
+ try {
95
+ this._stage(x, nonce, aad);
96
+ this._mem(x).set(plaintext, x.getChunkPtOffset());
97
+ x.sivDeriveKeys(x.getNonceOffset());
98
+ x.sivSeal(aad.length, plaintext.length);
99
+ const ctOff = x.getChunkPtOffset();
100
+ const tagOff = x.getTagOffset();
101
+ const out = new Uint8Array(plaintext.length + TAG_LEN);
102
+ out.set(this._mem(x).subarray(ctOff, ctOff + plaintext.length), 0);
103
+ out.set(this._mem(x).subarray(tagOff, tagOff + TAG_LEN), plaintext.length);
104
+ return out;
105
+ }
106
+ finally {
107
+ x.wipeBuffers();
108
+ _releaseModule('aes', tok);
109
+ }
110
+ }
111
+ /**
112
+ * Authenticated decryption. `sealed` is the output of a matching
113
+ * `seal(nonce, plaintext, aad)` call.
114
+ *
115
+ * Verification routes through `constantTimeEqual` from
116
+ * `../utils.js` (the dedicated `ct` WASM module). On mismatch the
117
+ * WASM `sivWipeOnFail` helper zeroes the decrypted-but-
118
+ * unauthenticated plaintext at CHUNK_PT_OFFSET before this method
119
+ * throws, the bytes never become reachable from JS.
120
+ *
121
+ * @throws AuthenticationError('siv') if the tag fails to verify, or
122
+ * if `sealed` is too short, or any input length violates the
123
+ * spec.
124
+ */
125
+ open(nonce, sealed, aad = new Uint8Array(0)) {
126
+ this._assertAlive();
127
+ if (sealed.length < TAG_LEN) {
128
+ throw new AuthenticationError('siv');
129
+ }
130
+ const ctLen = sealed.length - TAG_LEN;
131
+ try {
132
+ this._validate(nonce, ctLen, aad);
133
+ }
134
+ catch {
135
+ // Same generic error so failure modes are indistinguishable.
136
+ throw new AuthenticationError('siv');
137
+ }
138
+ const ct = sealed.subarray(0, ctLen);
139
+ const providedTag = sealed.subarray(ctLen, sealed.length);
140
+ const x = getExports();
141
+ const tok = _acquireModule('aes');
142
+ try {
143
+ this._stage(x, nonce, aad);
144
+ this._mem(x).set(ct, x.getChunkCtOffset());
145
+ // Stage the provided tag at SIV_IC_OFFSET, sivOpen will
146
+ // read it from there as the input to the CTR initial counter.
147
+ this._mem(x).set(providedTag, x.getSivIcOffset());
148
+ x.sivDeriveKeys(x.getNonceOffset());
149
+ x.sivOpen(aad.length, ctLen);
150
+ // Read the EXPECTED tag computed by sivOpen from TAG_OFFSET.
151
+ // Use slice() rather than subarray() so the buffer survives
152
+ // any subsequent WASM memory growth or wipe.
153
+ const expectedTag = this._mem(x).slice(x.getTagOffset(), x.getTagOffset() + TAG_LEN);
154
+ // Defensive copy of providedTag for the constant-time compare,
155
+ // the input may be a view over a caller-controlled buffer.
156
+ const providedTagCopy = new Uint8Array(providedTag);
157
+ const ok = constantTimeEqual(expectedTag, providedTagCopy);
158
+ if (!ok) {
159
+ // Wipe before finally; covers JS-heap tag copies.
160
+ x.sivWipeOnFail();
161
+ wipe(expectedTag);
162
+ wipe(providedTagCopy);
163
+ throw new AuthenticationError('siv');
164
+ }
165
+ // Match, read PT before wiping. pt is a JS-heap slice copy.
166
+ const ptOff = x.getChunkPtOffset();
167
+ const pt = this._mem(x).slice(ptOff, ptOff + ctLen);
168
+ wipe(expectedTag);
169
+ wipe(providedTagCopy);
170
+ return pt;
171
+ }
172
+ finally {
173
+ x.wipeBuffers();
174
+ _releaseModule('aes', tok);
175
+ }
176
+ }
177
+ /**
178
+ * Wipe the in-memory copy of the key. Idempotent. Subsequent calls
179
+ * to `seal` / `open` throw. WASM-side state is wiped at the end of
180
+ * every successful operation regardless of `dispose()`.
181
+ */
182
+ dispose() {
183
+ if (this._disposed)
184
+ return;
185
+ this._key.fill(0);
186
+ this._disposed = true;
187
+ }
188
+ // ── Internal helpers ───────────────────────────────────────────────────
189
+ _mem(x) {
190
+ return new Uint8Array(x.memory.buffer);
191
+ }
192
+ _assertAlive() {
193
+ if (this._disposed)
194
+ throw new Error('AESGCMSIV: instance has been disposed');
195
+ }
196
+ _validate(nonce, dataLen, aad) {
197
+ if (nonce.length !== NONCE_LEN)
198
+ throw new RangeError(`AESGCMSIV nonce must be ${NONCE_LEN} bytes (got ${nonce.length})`);
199
+ if (dataLen > MAX_PT_BYTES)
200
+ throw new RangeError(`AESGCMSIV plaintext must be ≤ ${MAX_PT_BYTES} bytes (got ${dataLen})`);
201
+ if (aad.length > MAX_AAD_BYTES)
202
+ throw new RangeError(`AESGCMSIV AAD must be ≤ ${MAX_AAD_BYTES} bytes (got ${aad.length})`);
203
+ }
204
+ /** Push KGK + nonce + AAD into WASM memory. Common to seal/open. */
205
+ _stage(x, nonce, aad) {
206
+ const mem = this._mem(x);
207
+ mem.set(this._key, x.getKeyOffset());
208
+ if (x.loadKey(this._key.length) !== 0) {
209
+ x.wipeBuffers();
210
+ throw new Error('AESGCMSIV: loadKey failed');
211
+ }
212
+ mem.set(nonce, x.getNonceOffset());
213
+ if (aad.length > 0) {
214
+ mem.set(aad, x.getAadOffset());
215
+ }
216
+ }
217
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * AES-128/192/256 in GCM mode (SP 800-38D §7). Authenticated AEAD with
3
+ * 128-bit tag. Tag length is fixed; shorter tags (32/64/96/104/112/120)
4
+ * are out of scope for this version.
5
+ *
6
+ * `seal(key, iv, aad, pt)` returns `ciphertext || tag` (length pt.length + 16).
7
+ * `open(key, iv, aad, sealed)` verifies the tag and returns the plaintext;
8
+ * throws `RangeError('authentication failed')` on any verification failure
9
+ * (the same generic error as a tag mismatch, no detail leak).
10
+ *
11
+ * Holds exclusive access to the `aes` WASM module from construction until
12
+ * `dispose()`. Constructing a second AES-using class while this instance is
13
+ * live throws. Always dispose when done so key material is wiped.
14
+ */
15
+ export declare class AESGCM {
16
+ private readonly x;
17
+ private _tok;
18
+ constructor();
19
+ /**
20
+ * Authenticated encryption.
21
+ *
22
+ * @param key 16, 24, or 32 bytes (AES-128 / 192 / 256)
23
+ * @param iv 1+ bytes; 12-byte (96-bit) IV is the recommended fast path
24
+ * @param aad any length up to 64 KiB; may be empty
25
+ * @param pt any length up to 2^36 - 32 bytes; may be empty
26
+ * @returns ciphertext concatenated with the 128-bit tag
27
+ * (length = pt.length + 16)
28
+ *
29
+ * @throws RangeError if key/iv/aad/pt lengths violate the spec or the
30
+ * buffer-bounded API.
31
+ */
32
+ seal(key: Uint8Array, iv: Uint8Array, aad: Uint8Array, pt: Uint8Array): Uint8Array;
33
+ /**
34
+ * Authenticated decryption.
35
+ *
36
+ * Performs verify-before-decrypt (SP 800-38D §7.2 permits the tag check
37
+ * to precede plaintext computation): the entire ciphertext is absorbed
38
+ * into GHASH, the tag is computed and constant-time-compared with the
39
+ * received tag, and only then is the ciphertext decrypted to plaintext.
40
+ * This avoids leaking decrypted bytes to higher layers when the tag
41
+ * fails to verify.
42
+ *
43
+ * @param key same constraints as `seal`
44
+ * @param iv same iv used during the matching `seal` call
45
+ * @param aad same aad used during the matching `seal` call
46
+ * @param sealed output of a previous `seal` call (ciphertext || tag)
47
+ * @returns plaintext (length = sealed.length - 16)
48
+ *
49
+ * @throws RangeError('authentication failed') if the tag fails to
50
+ * verify, or if the sealed input is too short, or any input
51
+ * length violates the spec. The same generic error covers all
52
+ * failure modes, no detail is leaked about which check failed.
53
+ */
54
+ open(key: Uint8Array, iv: Uint8Array, aad: Uint8Array, sealed: Uint8Array): Uint8Array;
55
+ /** Wipe WASM state and release exclusive module access. Idempotent. */
56
+ dispose(): void;
57
+ private _validateInputs;
58
+ private _loadKey;
59
+ private _writeIv;
60
+ private _writeAad;
61
+ }