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
@@ -1,781 +0,0 @@
1
- <img src="https://github.com/xero/leviathan-crypto/raw/main/docs/logo.svg" alt="logo" width="120" align="left" margin="10">
2
-
3
- ### ChaCha20 TypeScript API
4
-
5
- API reference for `ChaCha20`, `Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305`, and `XChaCha20Cipher`. Covers initialization, all class methods, usage examples, and error conditions.
6
-
7
- > ### Table of Contents
8
- > - [Overview](#overview)
9
- > - [Security Notes](#security-notes)
10
- > - [Module Init](#module-init)
11
- > - [API Reference](#api-reference)
12
- > - [`ChaCha20`](#chacha20)
13
- > - [`Poly1305`](#poly1305)
14
- > - [`ChaCha20Poly1305`](#chacha20poly1305)
15
- > - [`XChaCha20Poly1305`](#xchacha20poly1305)
16
- > - [`ChaCha20Generator`](#chacha20generator)
17
- > - [XChaCha20Cipher](#xchacha20cipher)
18
- > - [Usage Examples](#usage-examples)
19
- > - [Error Conditions](#error-conditions)
20
-
21
- ---
22
-
23
- ## Overview
24
-
25
- **ChaCha20** is a modern stream cipher designed by Daniel J. Bernstein. It is
26
- fast on all platforms (including those without hardware AES), resistant to
27
- timing attacks by design, and widely deployed in TLS, SSH, and WireGuard.
28
- ChaCha20 encrypts data by generating a pseudorandom keystream from a 256-bit
29
- key and a nonce, then XORing it with the plaintext. It does **not** provide
30
- authentication on its own. A modified message will decrypt to garbage with no
31
- warning.
32
-
33
- **Poly1305** is a one-time message authentication code (MAC). Given a unique 256-bit
34
- key and a message, it produces a 16-byte tag that proves the message has not been
35
- tampered with. The critical requirement is that each Poly1305 key is used **exactly
36
- once**. Reusing a key completely breaks its security. You almost never need to use
37
- Poly1305 directly; the AEAD constructions below handle key derivation for you.
38
-
39
- **ChaCha20-Poly1305** (RFC 8439) combines both primitives into an AEAD
40
- (Authenticated Encryption with Associated Data). It encrypts your data and
41
- produces an authentication tag in a single operation. On decryption, it verifies
42
- the tag before returning any plaintext. If someone tampered with the ciphertext,
43
- you get an error instead of corrupted data. The nonce is 96 bits (12 bytes).
44
-
45
- **XChaCha20-Poly1305** extends the nonce to 192 bits (24 bytes) using the HChaCha20
46
- subkey derivation step. This makes random nonce generation completely safe. With a
47
- 24-byte nonce, the probability of a collision is negligible even after billions of
48
- messages. **For most users, [`Seal`](./aead.md#seal) with [`XChaCha20Cipher`](./ciphersuite.md#xchacha20cipher)
49
- is the recommended choice.** It produces a self-contained authenticated blob with
50
- no nonce management, no instantiation, and no `dispose()`. For protocol interop
51
- requiring explicit nonce control, use `XChaCha20Poly1305` directly.
52
-
53
- ---
54
-
55
- ## Security Notes
56
-
57
- > [!IMPORTANT]
58
- > Read this section before writing any code. These are not theoretical concerns.
59
- > They are the mistakes that cause real-world breaches.
60
-
61
- - **Use [`Seal`](./aead.md#seal) with [`XChaCha20Cipher`](./ciphersuite.md#xchacha20cipher)
62
- unless you need explicit nonce control.** It is the safest default: authenticated
63
- encryption in a single static call. If you are unsure which class to pick, pick this
64
- one. Use `XChaCha20Poly1305` when protocol interop requires you to manage nonces yourself.
65
-
66
- - **Never reuse a nonce with the same key.** This is the single most important
67
- rule. If you encrypt two different messages with the same key and the same nonce,
68
- an attacker can XOR the two ciphertexts together and recover both plaintexts.
69
- With `ChaCha20Poly1305` (12-byte nonce), random generation has a meaningful
70
- collision risk after roughly 2^32 messages under one key. With
71
- `XChaCha20Poly1305` (24-byte nonce), random generation is safe for any practical
72
- message count. Just call `randomBytes(24)` for each message.
73
-
74
- - **Poly1305 keys are single-use.** Each Poly1305 key must be used to authenticate
75
- exactly one message. The AEAD classes (`ChaCha20Poly1305` and
76
- `XChaCha20Poly1305`) handle this automatically by deriving a fresh Poly1305 key
77
- from the ChaCha20 keystream for each encryption. If you use the standalone
78
- `Poly1305` class directly, it is your responsibility to never reuse a key.
79
-
80
- - **AEAD protects both confidentiality and authenticity.** If authentication fails
81
- during decryption, the plaintext is never returned. You get an error. This is
82
- intentional. Do not try to work around it. If decryption fails, the ciphertext was corrupted or tampered with.
83
-
84
- - **Associated data (AAD) is authenticated but not encrypted.** Use AAD for data
85
- that must travel in the clear (headers, routing metadata, user IDs) but must be
86
- verified as unmodified. If someone changes the AAD, decryption will fail even
87
- if the ciphertext itself is untouched.
88
-
89
- - **Always call `dispose()` when you are done.** This wipes key material and
90
- intermediate state from WASM memory. Failing to call `dispose()` leaves
91
- sensitive data in memory longer than necessary.
92
-
93
- - **`ChaCha20` alone has no authentication.** If you use the raw `ChaCha20` class
94
- without pairing it with a MAC, an attacker can flip bits in the ciphertext and
95
- the corresponding bits in the plaintext will flip silently. Unless you are
96
- building your own authenticated construction (and you probably should not be),
97
- use one of the AEAD classes instead.
98
-
99
- ---
100
-
101
- ## Module Init
102
-
103
- Each module subpath exports its own init function for consumers who want
104
- tree-shakeable imports.
105
-
106
- ### `chacha20Init(source)`
107
-
108
- Initializes only the chacha20 WASM binary. Equivalent to calling the
109
- root `init({ chacha20: chacha20Wasm })` but without pulling the other three
110
- modules into the bundle.
111
-
112
- **Signature:**
113
-
114
- ```typescript
115
- async function chacha20Init(source: WasmSource): Promise<void>
116
- ```
117
-
118
- **Usage:**
119
-
120
- ```typescript
121
- import { chacha20Init, XChaCha20Poly1305 } from 'leviathan-crypto/chacha20'
122
- import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
123
-
124
- await chacha20Init(chacha20Wasm)
125
- const aead = new XChaCha20Poly1305()
126
- ```
127
-
128
- ---
129
-
130
- ## API Reference
131
-
132
- All classes require calling `await init({ chacha20: chacha20Wasm })` or the subpath `chacha20Init()`
133
- before construction. If you construct a class before initialization, it throws:
134
- ```
135
- Error: leviathan-crypto: call init({ chacha20: ... }) before using this class
136
- ```
137
-
138
- ---
139
-
140
- ### `ChaCha20`
141
-
142
- Raw ChaCha20 stream cipher. **No authentication.** Use `XChaCha20Poly1305` instead
143
- unless you are building a custom protocol and understand the risks.
144
-
145
- > [!CAUTION]
146
- > `ChaCha20` is stateful and holds exclusive access to the `chacha20` WASM
147
- > module for its entire lifetime. Constructing a second `ChaCha20`, or any
148
- > atomic chacha20 class (`Poly1305`, `ChaCha20Poly1305`, `XChaCha20Poly1305`,
149
- > or a `XChaCha20Cipher`-backed `Seal`/`SealStream`), while this instance is
150
- > live throws. Call `dispose()` when done. Pool workers are unaffected.
151
-
152
- #### Constructor
153
-
154
- ```typescript
155
- new ChaCha20()
156
- ```
157
-
158
- Throws if `init({ chacha20: chacha20Wasm })` has not been called, or if
159
- another stateful chacha20 instance is still live.
160
-
161
- ---
162
-
163
- #### `beginEncrypt(key: Uint8Array, nonce: Uint8Array): void`
164
-
165
- Prepares the cipher for encryption with the given key and nonce.
166
-
167
- | Parameter | Type | Description |
168
- |-----------|------|-------------|
169
- | `key` | `Uint8Array` | 32 bytes (256 bits) |
170
- | `nonce` | `Uint8Array` | 12 bytes (96 bits) |
171
-
172
- **Throws** `RangeError` if `key` is not 32 bytes or `nonce` is not 12 bytes.
173
-
174
- > [!NOTE]
175
- > **Block counter convention.** `beginEncrypt()` starts the ChaCha20 keystream
176
- > at block counter 1, not 0. Block 0 is reserved for Poly1305 one-time key
177
- > generation per RFC 8439 §2.6. The class is designed for use inside an AEAD
178
- > construction; if you need to match RFC 8439 §2.4.2 verbatim (keystream
179
- > starting at counter 0), use the lower-level primitives directly.
180
-
181
- ---
182
-
183
- #### `encryptChunk(chunk: Uint8Array): Uint8Array`
184
-
185
- Encrypts a chunk of plaintext. Call repeatedly for streaming encryption. Returns
186
- a new `Uint8Array` containing the ciphertext (same length as input).
187
-
188
- | Parameter | Type | Description |
189
- |-----------|------|-------------|
190
- | `chunk` | `Uint8Array` | Plaintext bytes (up to the module's chunk size limit) |
191
-
192
- **Throws** `RangeError` if the chunk exceeds the maximum chunk size.
193
-
194
- ---
195
-
196
- #### `beginDecrypt(key: Uint8Array, nonce: Uint8Array): void`
197
-
198
- Prepares the cipher for decryption. Identical to `beginEncrypt`. ChaCha20 is
199
- symmetric; encryption and decryption are the same XOR operation.
200
-
201
- | Parameter | Type | Description |
202
- |-----------|------|-------------|
203
- | `key` | `Uint8Array` | 32 bytes (256 bits) |
204
- | `nonce` | `Uint8Array` | 12 bytes (96 bits) |
205
-
206
- **Throws** `RangeError` if `key` is not 32 bytes or `nonce` is not 12 bytes.
207
-
208
- ---
209
-
210
- #### `decryptChunk(chunk: Uint8Array): Uint8Array`
211
-
212
- Decrypts a chunk of ciphertext. Returns a new `Uint8Array` containing the
213
- plaintext (same length as input).
214
-
215
- | Parameter | Type | Description |
216
- |-----------|------|-------------|
217
- | `chunk` | `Uint8Array` | Ciphertext bytes |
218
-
219
- **Throws** `RangeError` if the chunk exceeds the maximum chunk size.
220
-
221
- ---
222
-
223
- #### `dispose(): void`
224
-
225
- Wipes all key material and intermediate state from WASM memory. Always call this
226
- when you are done with the instance.
227
-
228
- After `dispose()`, all instance methods (`beginEncrypt`, `encryptChunk`,
229
- `beginDecrypt`, `decryptChunk`) throw `Error: ChaCha20: instance has been
230
- disposed`. Disposal is permanent; construct a new instance if you need to
231
- continue.
232
-
233
- ---
234
-
235
- ### `Poly1305`
236
-
237
- Standalone Poly1305 one-time MAC. **Each key must be used exactly once.** You
238
- almost certainly want `ChaCha20Poly1305` or `XChaCha20Poly1305` instead. They
239
- handle Poly1305 key derivation automatically.
240
-
241
- #### Constructor
242
-
243
- ```typescript
244
- new Poly1305()
245
- ```
246
-
247
- Throws if `init({ chacha20: chacha20Wasm })` has not been called.
248
-
249
- ---
250
-
251
- #### `mac(key: Uint8Array, msg: Uint8Array): Uint8Array`
252
-
253
- Computes a 16-byte Poly1305 authentication tag over the given message.
254
-
255
- | Parameter | Type | Description |
256
- |-----------|------|-------------|
257
- | `key` | `Uint8Array` | 32 bytes. Must be unique per message. |
258
- | `msg` | `Uint8Array` | The message to authenticate (any length) |
259
-
260
- **Returns** `Uint8Array`: a 16-byte authentication tag.
261
-
262
- **Throws** `RangeError` if `key` is not 32 bytes.
263
-
264
- ---
265
-
266
- #### `dispose(): void`
267
-
268
- Wipes all key material and intermediate state from WASM memory.
269
-
270
- ---
271
-
272
- ### `ChaCha20Poly1305`
273
-
274
- ChaCha20-Poly1305 AEAD as specified in RFC 8439. Provides authenticated encryption
275
- with a 12-byte (96-bit) nonce. The Poly1305 one-time key is derived automatically
276
- from the ChaCha20 keystream (counter 0).
277
-
278
- If you are generating nonces randomly, prefer `XChaCha20Poly1305` (24-byte nonce)
279
- to avoid collision risk.
280
-
281
- #### Constructor
282
-
283
- ```typescript
284
- new ChaCha20Poly1305()
285
- ```
286
-
287
- Throws if `init({ chacha20: chacha20Wasm })` has not been called.
288
-
289
- ---
290
-
291
- #### `encrypt(key, nonce, plaintext, aad?): Uint8Array`
292
-
293
- Encrypts plaintext and returns the ciphertext with the 16-byte Poly1305 tag
294
- appended.
295
-
296
- > [!WARNING]
297
- > Each `ChaCha20Poly1305` instance allows only **one** `encrypt()` call. A second
298
- > call throws to prevent accidental nonce reuse. Create a new instance for each
299
- > encryption, or use `Seal` with `XChaCha20Cipher` for automatic nonce management.
300
- >
301
- > **Strict single-use on any throw.** Any throw from `encrypt()`, including
302
- > `RangeError` from wrong-length `key` or `nonce`, exclusivity errors, or any
303
- > failure inside the WASM call, is *terminal*. The guard is set before
304
- > argument validation, so a subsequent `encrypt()` on the same instance throws
305
- > the single-use error rather than retrying. Always allocate a new AEAD
306
- > instance per message.
307
-
308
- | Parameter | Type | Default | Description |
309
- |-----------|------|---------|-------------|
310
- | `key` | `Uint8Array` | | 32 bytes (256-bit key) |
311
- | `nonce` | `Uint8Array` | | 12 bytes (96-bit nonce) |
312
- | `plaintext` | `Uint8Array` | | Data to encrypt (up to the module's chunk size limit) |
313
- | `aad` | `Uint8Array` | `new Uint8Array(0)` | Associated data. Authenticated but not encrypted. |
314
-
315
- **Returns** `Uint8Array`: ciphertext + 16-byte tag (length = plaintext.length + 16).
316
-
317
- **Throws:**
318
- - `RangeError` if `key` is not 32 bytes *(terminal: instance locked)*
319
- - `RangeError` if `nonce` is not 12 bytes *(terminal: instance locked)*
320
- - `RangeError` if `plaintext` exceeds the maximum chunk size *(terminal: instance locked)*
321
- - `Error` if `encrypt()` has already been called on this instance
322
-
323
- ---
324
-
325
- #### `decrypt(key, nonce, ciphertext, aad?): Uint8Array`
326
-
327
- Verifies the authentication tag and decrypts the ciphertext. The `ciphertext`
328
- parameter must include the appended 16-byte tag (i.e., the exact output of
329
- `encrypt()`). If authentication fails, an error is thrown and no plaintext is
330
- returned.
331
-
332
- Tag comparison uses a constant-time XOR-accumulate pattern; no timing side
333
- channel leaks whether the tag was "close" to correct.
334
-
335
- | Parameter | Type | Default | Description |
336
- |-----------|------|---------|-------------|
337
- | `key` | `Uint8Array` | | 32 bytes (same key used for encryption) |
338
- | `nonce` | `Uint8Array` | | 12 bytes (same nonce used for encryption) |
339
- | `ciphertext` | `Uint8Array` | | Encrypted data with appended tag (output of `encrypt()`) |
340
- | `aad` | `Uint8Array` | `new Uint8Array(0)` | Associated data (must match what was passed to `encrypt()`) |
341
-
342
- **Returns** `Uint8Array`: the decrypted plaintext.
343
-
344
- **Throws:**
345
- - `RangeError` if `key` is not 32 bytes
346
- - `RangeError` if `nonce` is not 12 bytes
347
- - `RangeError` if `ciphertext` is shorter than 16 bytes (no room for a tag)
348
- - `Error('ChaCha20Poly1305: authentication failed')` if the tag does not match
349
-
350
- ---
351
-
352
- #### `dispose(): void`
353
-
354
- Wipes all key material and intermediate state from WASM memory.
355
-
356
- ---
357
-
358
- ### `XChaCha20Poly1305`
359
-
360
- XChaCha20-Poly1305 AEAD (draft-irtf-cfrg-xchacha). RFC-faithful stateless
361
- primitive. Key and nonce are passed per-call. Use when protocol interop
362
- requires explicit nonce control. For most use cases, prefer [`Seal`](./aead.md#seal)
363
- with [`XChaCha20Cipher`](./ciphersuite.md#xchacha20cipher)(automatic nonce management, no instantiation).
364
-
365
- It uses a 24-byte (192-bit) nonce, which is large enough that randomly generated
366
- nonces will never collide in practice. Internally, it derives a subkey via
367
- HChaCha20 and delegates to `ChaCha20Poly1305`.
368
-
369
- Like `ChaCha20Poly1305`, the `encrypt()` method returns a single `Uint8Array`
370
- with the tag appended to the ciphertext. The `decrypt()` method expects this
371
- combined format and splits it internally.
372
-
373
- #### Constructor
374
-
375
- ```typescript
376
- new XChaCha20Poly1305()
377
- ```
378
-
379
- Throws if `init({ chacha20: chacha20Wasm })` has not been called.
380
-
381
- ---
382
-
383
- #### `encrypt(key, nonce, plaintext, aad?): Uint8Array`
384
-
385
- Encrypts plaintext and returns the ciphertext with the 16-byte tag appended.
386
-
387
- > [!WARNING]
388
- > Same single-use contract as `ChaCha20Poly1305.encrypt()`. Each instance
389
- > allows only **one** `encrypt()` call. A second call throws to prevent
390
- > accidental nonce reuse. Create a new instance for each encryption, or use
391
- > `Seal` with `XChaCha20Cipher` for automatic nonce management.
392
- >
393
- > **Strict single-use on any throw.** Any throw from `encrypt()` is *terminal*,
394
- > whether the cause is a `RangeError` from wrong-length `key` or `nonce`, an
395
- > exclusivity error, or a failure inside the WASM call. The guard is set
396
- > before argument validation, so a subsequent `encrypt()` on the same instance
397
- > throws the single-use error rather than retrying with a reused nonce. Always
398
- > allocate a new AEAD instance per message.
399
-
400
- | Parameter | Type | Default | Description |
401
- |-----------|------|---------|-------------|
402
- | `key` | `Uint8Array` | | 32 bytes (256-bit key) |
403
- | `nonce` | `Uint8Array` | | 24 bytes (192-bit nonce) |
404
- | `plaintext` | `Uint8Array` | | Data to encrypt |
405
- | `aad` | `Uint8Array` | `new Uint8Array(0)` | Associated data. Authenticated but not encrypted. |
406
-
407
- **Returns** `Uint8Array`: ciphertext + 16-byte tag (length = plaintext.length + 16).
408
-
409
- **Throws:**
410
- - `RangeError` if `key` is not 32 bytes *(terminal: instance locked)*
411
- - `RangeError` if `nonce` is not 24 bytes *(terminal: instance locked)*
412
- - `RangeError` if `plaintext` exceeds the maximum chunk size *(terminal: instance locked)*
413
- - `Error` if `encrypt()` has already been called on this instance
414
-
415
- ---
416
-
417
- #### `decrypt(key, nonce, ciphertext, aad?): Uint8Array`
418
-
419
- Verifies the authentication tag and decrypts the ciphertext. The `ciphertext`
420
- parameter must include the appended 16-byte tag (i.e., the exact output of
421
- `encrypt()`).
422
-
423
- | Parameter | Type | Default | Description |
424
- |-----------|------|---------|-------------|
425
- | `key` | `Uint8Array` | | 32 bytes (same key used for encryption) |
426
- | `nonce` | `Uint8Array` | | 24 bytes (same nonce used for encryption) |
427
- | `ciphertext` | `Uint8Array` | | Encrypted data with appended tag (output of `encrypt()`) |
428
- | `aad` | `Uint8Array` | `new Uint8Array(0)` | Associated data (must match what was passed to `encrypt()`) |
429
-
430
- **Returns** `Uint8Array`: the decrypted plaintext.
431
-
432
- **Throws:**
433
- - `RangeError` if `key` is not 32 bytes
434
- - `RangeError` if `nonce` is not 24 bytes
435
- - `RangeError` if `ciphertext` is shorter than 16 bytes (no room for a tag)
436
- - `Error('ChaCha20Poly1305: authentication failed')` if the tag does not match
437
-
438
- ---
439
-
440
- #### `dispose(): void`
441
-
442
- Wipes all key material and intermediate state from WASM memory.
443
-
444
- ---
445
-
446
- ### ChaCha20Generator
447
-
448
- ChaCha20 block-function PRF for Fortuna's generator slot (RFC 8439 §2.3).
449
- Uses a fixed zero nonce; Fortuna's counter is the only varying input.
450
- Implements the `Generator` interface. Plain `const` object — no instantiation,
451
- no `dispose()`.
452
-
453
- Requires `init({ chacha20: chacha20Wasm })`. See [fortuna.md](./fortuna.md)
454
- for full usage with `Fortuna.create()`.
455
-
456
- | Property | Value |
457
- |----------|-------|
458
- | `keySize` | `32` |
459
- | `blockSize` | `64` |
460
- | `counterSize` | `4` |
461
- | `wasmModules` | `['chacha20']` |
462
-
463
- #### `ChaCha20Generator.generate(key, counter, n): Uint8Array`
464
-
465
- Produces `n` bytes from `(key, counter)`. Neither input is mutated. Wipes WASM
466
- key/state/keystream scratch before returning.
467
-
468
- | Parameter | Type | Description |
469
- |-----------|------|-------------|
470
- | `key` | `Uint8Array` | 32 bytes |
471
- | `counter` | `Uint8Array` | 4 bytes, read as a little-endian u32 |
472
- | `n` | `number` | Output byte count: 0 ≤ n ≤ 2³⁰ |
473
-
474
- **Returns** a new `Uint8Array` of length `n`.
475
-
476
- **Throws:**
477
- - `RangeError('ChaCha20Generator: key must be 32 bytes (got N)')` if key length ≠ 32
478
- - `RangeError('ChaCha20Generator: counter must be 4 bytes (got N)')` if counter length ≠ 4
479
- - `RangeError('ChaCha20Generator: n must be a non-negative safe integer <= 2^30 (got N)')` if n is out of range
480
- - `Error` if another stateful instance currently owns the `chacha20` WASM module
481
-
482
- #### Usage with `Fortuna`
483
-
484
- ```typescript
485
- import { init, Fortuna } from 'leviathan-crypto'
486
- import { ChaCha20Generator } from 'leviathan-crypto/chacha20'
487
- import { SHA256Hash } from 'leviathan-crypto/sha2'
488
- import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
489
- import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
490
-
491
- await init({ chacha20: chacha20Wasm, sha2: sha2Wasm })
492
- const rng = await Fortuna.create({ generator: ChaCha20Generator, hash: SHA256Hash })
493
- const bytes = rng.get(32)
494
- rng.stop()
495
- ```
496
-
497
- ---
498
-
499
- ## XChaCha20Cipher
500
-
501
- `CipherSuite` implementation for XChaCha20-Poly1305. Pass to `Seal`,
502
- `SealStream`, or `OpenStream`. Never instantiated directly.
503
-
504
- Requires `init({ chacha20: chacha20Wasm, sha2: sha2Wasm })`.
505
-
506
- > [!NOTE]
507
- > `sha2` is required by the stream layer for HKDF key derivation, not by
508
- > `XChaCha20Poly1305` itself.
509
-
510
- | Property | Value |
511
- |----------|-------|
512
- | `formatEnum` | `0x01` |
513
- | `keySize` | `32` |
514
- | `tagSize` | `16` (Poly1305) |
515
- | `padded` | `false` |
516
- | `wasmModules` | `['chacha20']` |
517
-
518
- #### `XChaCha20Cipher.keygen(): Uint8Array`
519
-
520
- Returns `randomBytes(32)`. Convenience method. Not on the `CipherSuite` interface.
521
-
522
- #### Usage with `Seal`
523
-
524
- ```typescript
525
- import { init, Seal, XChaCha20Cipher } from 'leviathan-crypto'
526
- import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
527
- import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
528
-
529
- await init({ chacha20: chacha20Wasm, sha2: sha2Wasm })
530
-
531
- const key = XChaCha20Cipher.keygen()
532
- const blob = Seal.encrypt(XChaCha20Cipher, key, plaintext)
533
- const pt = Seal.decrypt(XChaCha20Cipher, key, blob) // throws on tamper
534
- ```
535
-
536
- #### Usage with `SealStream` / `OpenStream`
537
-
538
- ```typescript
539
- import { SealStream, OpenStream } from 'leviathan-crypto/stream'
540
- import { XChaCha20Cipher } from 'leviathan-crypto/chacha20'
541
-
542
- const sealer = new SealStream(XChaCha20Cipher, key)
543
- const preamble = sealer.preamble // 20 bytes, send before first chunk
544
- const ct0 = sealer.push(chunk0)
545
- const ctLast = sealer.finalize(lastChunk)
546
-
547
- const opener = new OpenStream(XChaCha20Cipher, key, preamble)
548
- const pt0 = opener.pull(ct0)
549
- const ptLast = opener.finalize(ctLast)
550
- ```
551
-
552
- See [aead.md](./aead.md) for the full `Seal`, `SealStream`, and `OpenStream` API.
553
-
554
- ---
555
-
556
- ## Usage Examples
557
-
558
- ### Example 1: Seal with XChaCha20Cipher (recommended)
559
-
560
- One-shot AEAD. No instantiation, no `dispose()`. The blob format is
561
- `preamble(20) || ciphertext || tag(16)`.
562
-
563
- ```typescript
564
- import { init, Seal, XChaCha20Cipher, utf8ToBytes, bytesToUtf8 } from 'leviathan-crypto'
565
- import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
566
- import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
567
-
568
- await init({ chacha20: chacha20Wasm, sha2: sha2Wasm })
569
-
570
- const key = XChaCha20Cipher.keygen()
571
- const blob = Seal.encrypt(XChaCha20Cipher, key, utf8ToBytes('Hello, world!'))
572
- const pt = Seal.decrypt(XChaCha20Cipher, key, blob) // throws on tamper
573
-
574
- console.log(bytesToUtf8(pt)) // "Hello, world!"
575
-
576
- // Optional: bind metadata without encrypting it (AAD)
577
- const aad = utf8ToBytes('document-v2')
578
- const blob2 = Seal.encrypt(XChaCha20Cipher, key, utf8ToBytes('Hello, world!'), { aad })
579
- const pt2 = Seal.decrypt(XChaCha20Cipher, key, blob2, { aad })
580
- ```
581
-
582
- > For explicit nonce control, use `XChaCha20Poly1305` directly. Same
583
- > API shape, 24-byte nonce passed per call. See Example 2.
584
-
585
- ### Example 2: ChaCha20Poly1305
586
-
587
- Same idea as above, but with a 12-byte nonce. Use this if you are implementing
588
- a protocol that specifies RFC 8439 ChaCha20-Poly1305 explicitly.
589
-
590
- Both `ChaCha20Poly1305` and `XChaCha20Poly1305` return a single `Uint8Array`
591
- with the tag appended, and `decrypt()` expects the same combined format.
592
- The only difference is the nonce size (12 vs 24 bytes).
593
-
594
- > [!NOTE]
595
- > Each `ChaCha20Poly1305` instance allows only one `encrypt()` call. A second
596
- > call throws to prevent accidental nonce reuse. Create a new instance for each
597
- > encryption.
598
-
599
- ```typescript
600
- import { init, ChaCha20Poly1305, randomBytes, utf8ToBytes, bytesToUtf8 } from 'leviathan-crypto'
601
- import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
602
-
603
- await init({ chacha20: chacha20Wasm })
604
-
605
- const key = randomBytes(32)
606
- const aead = new ChaCha20Poly1305()
607
-
608
- // Encrypt
609
- const nonce = randomBytes(12) // 12 bytes, use XChaCha20Poly1305 for high-volume random generation
610
- const plaintext = utf8ToBytes('Sensitive data')
611
- const sealed = aead.encrypt(key, nonce, plaintext)
612
- // sealed = ciphertext || tag(16), store/transmit nonce and sealed together
613
-
614
- // Decrypt (new instance, encrypt is single-use)
615
- const aead2 = new ChaCha20Poly1305()
616
- const decrypted = aead2.decrypt(key, nonce, sealed)
617
- console.log(bytesToUtf8(decrypted)) // "Sensitive data"
618
-
619
- aead.dispose()
620
- aead2.dispose()
621
- ```
622
-
623
- ### Example 3: Detecting Tampered Ciphertext
624
-
625
- AEAD decryption fails loudly if anyone has modified the ciphertext or
626
- the associated data. This is a feature. It prevents you from processing
627
- corrupted or maliciously altered data.
628
-
629
- ```typescript
630
- import { init, Seal, XChaCha20Cipher, randomBytes, utf8ToBytes } from 'leviathan-crypto'
631
- import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
632
- import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
633
-
634
- await init({ chacha20: chacha20Wasm, sha2: sha2Wasm })
635
-
636
- const key = XChaCha20Cipher.keygen()
637
- const sealed = Seal.encrypt(XChaCha20Cipher, key, utf8ToBytes('Original message'))
638
-
639
- // Simulate tampering: flip one bit in the ciphertext
640
- const tampered = new Uint8Array(sealed)
641
- tampered[20] ^= 0x01 // byte 20 is the first ciphertext byte (after the 20-byte preamble)
642
-
643
- try {
644
- const plaintext = Seal.decrypt(XChaCha20Cipher, key, tampered)
645
- // This line is never reached
646
- console.log(plaintext)
647
- } catch (err) {
648
- console.error(err.message)
649
- // "ChaCha20Poly1305: authentication failed"
650
- // The plaintext is never returned. Decryption stops immediately on failure.
651
- }
652
- ```
653
-
654
- ### Example 4: Using Associated Data (AAD)
655
-
656
- Associated data is metadata that you want to authenticate (prove unmodified) but
657
- not encrypt. Common uses: user IDs, message sequence numbers, protocol version
658
- headers, routing information.
659
-
660
- ```typescript
661
- import { init, Seal, XChaCha20Cipher, randomBytes, utf8ToBytes, bytesToUtf8 } from 'leviathan-crypto'
662
- import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
663
- import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
664
-
665
- await init({ chacha20: chacha20Wasm, sha2: sha2Wasm })
666
-
667
- const key = XChaCha20Cipher.keygen()
668
-
669
- // The user ID travels in the clear, but decryption will fail if anyone changes it
670
- const userId = utf8ToBytes('user-12345')
671
- const message = utf8ToBytes('Your account balance is $1,000,000')
672
-
673
- const sealed = Seal.encrypt(XChaCha20Cipher, key, message, { aad: userId })
674
-
675
- // Decrypt, pass the same AAD
676
- const decrypted = Seal.decrypt(XChaCha20Cipher, key, sealed, { aad: userId })
677
- console.log(bytesToUtf8(decrypted))
678
- // "Your account balance is $1,000,000"
679
-
680
- // If someone changes the AAD, decryption fails
681
- const wrongUserId = utf8ToBytes('user-99999')
682
- try {
683
- Seal.decrypt(XChaCha20Cipher, key, sealed, { aad: wrongUserId })
684
- } catch (err) {
685
- console.error(err.message)
686
- // "ChaCha20Poly1305: authentication failed"
687
- // Even though the ciphertext was not modified, the AAD mismatch is detected.
688
- }
689
- ```
690
-
691
- ### Example 5: Encrypting and Decrypting Binary Data
692
-
693
- The API works with raw bytes, not just text. Here is an example encrypting
694
- arbitrary binary content.
695
-
696
- ```typescript
697
- import { init, Seal, XChaCha20Cipher, randomBytes } from 'leviathan-crypto'
698
- import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
699
- import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
700
-
701
- await init({ chacha20: chacha20Wasm, sha2: sha2Wasm })
702
-
703
- const key = XChaCha20Cipher.keygen()
704
-
705
- // Encrypt binary data (e.g., an image thumbnail, a protobuf, a file chunk)
706
- const binaryData = new Uint8Array([0x89, 0x50, 0x4e, 0x47, /* ...more bytes... */])
707
- const sealed = Seal.encrypt(XChaCha20Cipher, key, binaryData)
708
-
709
- // Decrypt
710
- const recovered = Seal.decrypt(XChaCha20Cipher, key, sealed)
711
- // `recovered` is byte-identical to `binaryData`
712
- ```
713
-
714
- ### Example 6: Raw ChaCha20 Stream Cipher (Advanced)
715
-
716
- Use this only if you are building a custom protocol and will add your own
717
- authentication layer. For almost all use cases, use `Seal` with `XChaCha20Cipher` instead.
718
-
719
- ```typescript
720
- import { init, ChaCha20, randomBytes } from 'leviathan-crypto'
721
- import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
722
-
723
- await init({ chacha20: chacha20Wasm })
724
-
725
- const key = randomBytes(32)
726
- const nonce = randomBytes(12)
727
- const cipher = new ChaCha20()
728
-
729
- // Encrypt
730
- cipher.beginEncrypt(key, nonce)
731
- const ct1 = cipher.encryptChunk(new Uint8Array([1, 2, 3, 4]))
732
- const ct2 = cipher.encryptChunk(new Uint8Array([5, 6, 7, 8]))
733
-
734
- // Decrypt, uses the same key and nonce
735
- cipher.beginDecrypt(key, nonce)
736
- const pt1 = cipher.decryptChunk(ct1)
737
- const pt2 = cipher.decryptChunk(ct2)
738
- // pt1 = [1, 2, 3, 4], pt2 = [5, 6, 7, 8]
739
-
740
- // WARNING: Without authentication, an attacker can flip bits in ciphertext
741
- // and the corresponding plaintext bits will flip with no error.
742
- // Use the seal api or XChaCha20Poly1305 instead.
743
-
744
- cipher.dispose()
745
- ```
746
-
747
- ---
748
-
749
- ## Error Conditions
750
-
751
- | Condition | Error Type | Message |
752
- |-----------|-----------|---------|
753
- | `init({ chacha20: ... })` not called before constructing a class | `Error` | `leviathan-crypto: call init({ chacha20: ... }) before using this class` |
754
- | Key is not 32 bytes | `RangeError` | `ChaCha20 key must be 32 bytes (got N)` / `key must be 32 bytes (got N)` / `Poly1305 key must be 32 bytes (got N)` |
755
- | `ChaCha20` nonce is not 12 bytes | `RangeError` | `ChaCha20 nonce must be 12 bytes (got N)` |
756
- | `ChaCha20Poly1305` nonce is not 12 bytes | `RangeError` | `nonce must be 12 bytes (got N)` |
757
- | `XChaCha20Poly1305` nonce is not 24 bytes | `RangeError` | `XChaCha20 nonce must be 24 bytes (got N)` |
758
- | `ChaCha20Poly1305` ciphertext shorter than 16 bytes | `RangeError` | `ciphertext too short — must include 16-byte tag (got N)` |
759
- | `XChaCha20Poly1305` ciphertext shorter than 16 bytes | `RangeError` | `ciphertext too short — must include 16-byte tag (got N)` |
760
- | `ChaCha20Poly1305.encrypt()` called a second time | `Error` | Single-use encrypt guard. Create a new instance for each encryption. |
761
- | Chunk or plaintext exceeds WASM buffer size | `RangeError` | `plaintext exceeds N bytes — split into smaller chunks` / `chunk exceeds maximum size of N bytes — split into smaller chunks` |
762
- | Authentication tag does not match on decrypt | `Error` | `ChaCha20Poly1305: authentication failed` |
763
- | Empty plaintext | | Allowed. Encrypting zero bytes produces just a 16-byte tag (AEAD) or zero bytes (raw ChaCha20). |
764
- | `ChaCha20Generator.generate()` key ≠ 32 bytes | `RangeError` | `ChaCha20Generator: key must be 32 bytes (got N)` |
765
- | `ChaCha20Generator.generate()` counter ≠ 4 bytes | `RangeError` | `ChaCha20Generator: counter must be 4 bytes (got N)` |
766
- | `ChaCha20Generator.generate()` n out of range | `RangeError` | `ChaCha20Generator: n must be a non-negative safe integer <= 2^30 (got N)` |
767
-
768
- ## Cross-References
769
-
770
- | Document | Description |
771
- | -------- | ----------- |
772
- | [index](./README.md) | Project Documentation index |
773
- | [lexicon](./lexicon.md) | Glossary of cryptographic terms |
774
- | [asm_chacha](./asm_chacha.md) | WASM (AssemblyScript) implementation details for the chacha20 module |
775
- | [authenticated encryption](./aead.md) | `Seal`, `SealStream`, `OpenStream`: use `XChaCha20Cipher` as the suite argument |
776
- | [serpent](./serpent.md) | `SerpentCipher`: alternative `CipherSuite` for `Seal` and streaming |
777
- | [sha2](./sha2.md) | SHA-2 hashes and HMAC. Needed for Encrypt-then-MAC if using raw ChaCha20 |
778
- | [types](./types.md) | `AEAD` and `Streamcipher` interfaces implemented by ChaCha20 classes |
779
- | [architecture](./architecture.md) | architecture overview, module relationships, buffer layouts, and build pipeline |
780
- | [chacha_audit](./chacha_audit.md) | XChaCha20-Poly1305 implementation audit |
781
-