leviathan-crypto 1.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 (78) hide show
  1. package/CLAUDE.md +265 -0
  2. package/LICENSE +21 -0
  3. package/README.md +322 -0
  4. package/SECURITY.md +174 -0
  5. package/dist/chacha.wasm +0 -0
  6. package/dist/chacha20/index.d.ts +49 -0
  7. package/dist/chacha20/index.js +177 -0
  8. package/dist/chacha20/ops.d.ts +16 -0
  9. package/dist/chacha20/ops.js +146 -0
  10. package/dist/chacha20/pool.d.ts +52 -0
  11. package/dist/chacha20/pool.js +188 -0
  12. package/dist/chacha20/pool.worker.d.ts +1 -0
  13. package/dist/chacha20/pool.worker.js +37 -0
  14. package/dist/chacha20/types.d.ts +30 -0
  15. package/dist/chacha20/types.js +1 -0
  16. package/dist/docs/architecture.md +795 -0
  17. package/dist/docs/argon2id.md +290 -0
  18. package/dist/docs/chacha20.md +602 -0
  19. package/dist/docs/chacha20_pool.md +306 -0
  20. package/dist/docs/fortuna.md +322 -0
  21. package/dist/docs/init.md +308 -0
  22. package/dist/docs/loader.md +206 -0
  23. package/dist/docs/serpent.md +914 -0
  24. package/dist/docs/sha2.md +620 -0
  25. package/dist/docs/sha3.md +509 -0
  26. package/dist/docs/types.md +198 -0
  27. package/dist/docs/utils.md +273 -0
  28. package/dist/docs/wasm.md +193 -0
  29. package/dist/embedded/chacha.d.ts +1 -0
  30. package/dist/embedded/chacha.js +2 -0
  31. package/dist/embedded/serpent.d.ts +1 -0
  32. package/dist/embedded/serpent.js +2 -0
  33. package/dist/embedded/sha2.d.ts +1 -0
  34. package/dist/embedded/sha2.js +2 -0
  35. package/dist/embedded/sha3.d.ts +1 -0
  36. package/dist/embedded/sha3.js +2 -0
  37. package/dist/fortuna.d.ts +72 -0
  38. package/dist/fortuna.js +445 -0
  39. package/dist/index.d.ts +13 -0
  40. package/dist/index.js +44 -0
  41. package/dist/init.d.ts +11 -0
  42. package/dist/init.js +49 -0
  43. package/dist/loader.d.ts +4 -0
  44. package/dist/loader.js +30 -0
  45. package/dist/serpent/index.d.ts +65 -0
  46. package/dist/serpent/index.js +242 -0
  47. package/dist/serpent/seal.d.ts +8 -0
  48. package/dist/serpent/seal.js +70 -0
  49. package/dist/serpent/stream-encoder.d.ts +20 -0
  50. package/dist/serpent/stream-encoder.js +167 -0
  51. package/dist/serpent/stream-pool.d.ts +48 -0
  52. package/dist/serpent/stream-pool.js +285 -0
  53. package/dist/serpent/stream-sealer.d.ts +34 -0
  54. package/dist/serpent/stream-sealer.js +223 -0
  55. package/dist/serpent/stream.d.ts +28 -0
  56. package/dist/serpent/stream.js +205 -0
  57. package/dist/serpent/stream.worker.d.ts +32 -0
  58. package/dist/serpent/stream.worker.js +117 -0
  59. package/dist/serpent/types.d.ts +5 -0
  60. package/dist/serpent/types.js +1 -0
  61. package/dist/serpent.wasm +0 -0
  62. package/dist/sha2/hkdf.d.ts +16 -0
  63. package/dist/sha2/hkdf.js +108 -0
  64. package/dist/sha2/index.d.ts +40 -0
  65. package/dist/sha2/index.js +190 -0
  66. package/dist/sha2/types.d.ts +5 -0
  67. package/dist/sha2/types.js +1 -0
  68. package/dist/sha2.wasm +0 -0
  69. package/dist/sha3/index.d.ts +55 -0
  70. package/dist/sha3/index.js +246 -0
  71. package/dist/sha3/types.d.ts +5 -0
  72. package/dist/sha3/types.js +1 -0
  73. package/dist/sha3.wasm +0 -0
  74. package/dist/types.d.ts +24 -0
  75. package/dist/types.js +26 -0
  76. package/dist/utils.d.ts +26 -0
  77. package/dist/utils.js +169 -0
  78. package/package.json +90 -0
@@ -0,0 +1,188 @@
1
+ // src/ts/chacha20/pool.ts
2
+ //
3
+ // XChaCha20Poly1305Pool — parallel worker pool for XChaCha20-Poly1305 AEAD.
4
+ // Dispatches independent encrypt/decrypt jobs across Web Workers, each with
5
+ // its own WebAssembly.Instance and isolated linear memory.
6
+ import { isInitialized } from '../init.js';
7
+ // ── Module-private base64 decoder (copied from loader.ts) ────────────────────
8
+ function base64ToBytes(b64) {
9
+ if (typeof atob === 'function') {
10
+ const raw = atob(b64);
11
+ const out = new Uint8Array(raw.length);
12
+ for (let i = 0; i < raw.length; i++)
13
+ out[i] = raw.charCodeAt(i);
14
+ return out;
15
+ }
16
+ return new Uint8Array(Buffer.from(b64, 'base64'));
17
+ }
18
+ // ── WASM module singleton ────────────────────────────────────────────────────
19
+ let _wasmModule;
20
+ async function getWasmModule() {
21
+ if (_wasmModule)
22
+ return _wasmModule;
23
+ const { WASM_BASE64 } = await import('../embedded/chacha.js');
24
+ const bytes = base64ToBytes(WASM_BASE64);
25
+ _wasmModule = await WebAssembly.compile(bytes.buffer);
26
+ return _wasmModule;
27
+ }
28
+ // ── Worker spawning ──────────────────────────────────────────────────────────
29
+ function spawnWorker(mod) {
30
+ return new Promise((resolve, reject) => {
31
+ const worker = new Worker(new URL('./pool.worker.js', import.meta.url), { type: 'module' });
32
+ const onMessage = (e) => {
33
+ cleanup();
34
+ if (e.data.type === 'ready') {
35
+ resolve(worker);
36
+ }
37
+ else {
38
+ worker.terminate();
39
+ reject(new Error(`leviathan-crypto: worker init failed: ${e.data.message}`));
40
+ }
41
+ };
42
+ const onError = (e) => {
43
+ cleanup();
44
+ worker.terminate();
45
+ reject(new Error(`leviathan-crypto: worker init failed: ${e.message}`));
46
+ };
47
+ const cleanup = () => {
48
+ worker.removeEventListener('message', onMessage);
49
+ worker.removeEventListener('error', onError);
50
+ };
51
+ worker.addEventListener('message', onMessage);
52
+ worker.addEventListener('error', onError);
53
+ worker.postMessage({ type: 'init', module: mod });
54
+ });
55
+ }
56
+ // ── Pool class ───────────────────────────────────────────────────────────────
57
+ /**
58
+ * Parallel worker pool for XChaCha20-Poly1305 AEAD.
59
+ *
60
+ * Each worker owns its own `WebAssembly.Instance` with isolated linear memory.
61
+ * Jobs are dispatched round-robin to idle workers; excess jobs queue until a
62
+ * worker frees up.
63
+ *
64
+ * **Warning:** Input buffers (`key`, `nonce`, `plaintext`/`ciphertext`, `aad`)
65
+ * are transferred to the worker and neutered on the calling side. The caller
66
+ * must copy any buffer they need to retain after calling `encrypt()`/`decrypt()`.
67
+ */
68
+ export class XChaCha20Poly1305Pool {
69
+ _workers;
70
+ _idle;
71
+ _queue;
72
+ _pending;
73
+ _nextId;
74
+ _disposed;
75
+ constructor(workers) {
76
+ this._workers = workers;
77
+ this._idle = [...workers];
78
+ this._queue = [];
79
+ this._pending = new Map();
80
+ this._nextId = 0;
81
+ this._disposed = false;
82
+ for (const w of workers) {
83
+ w.onmessage = (e) => this._onMessage(w, e);
84
+ }
85
+ }
86
+ /**
87
+ * Create a new pool. Requires `init(['chacha20'])` to have been called.
88
+ * Compiles the WASM module once and distributes it to all workers.
89
+ */
90
+ static async create(opts) {
91
+ if (!isInitialized('chacha20'))
92
+ throw new Error('leviathan-crypto: call init([\'chacha20\']) before using XChaCha20Poly1305Pool');
93
+ const n = opts?.workers ?? (typeof navigator !== 'undefined' ? navigator.hardwareConcurrency : undefined) ?? 4;
94
+ const mod = await getWasmModule();
95
+ // Sequential spawn — compatible with inline-worker test environments
96
+ const workers = [];
97
+ for (let i = 0; i < n; i++)
98
+ workers.push(await spawnWorker(mod));
99
+ return new XChaCha20Poly1305Pool(workers);
100
+ }
101
+ /**
102
+ * Encrypt plaintext with XChaCha20-Poly1305.
103
+ * Returns `ciphertext || tag` (plaintext.length + 16 bytes).
104
+ *
105
+ * **Warning:** All input buffers are transferred and neutered after dispatch.
106
+ */
107
+ encrypt(key, nonce, plaintext, aad = new Uint8Array(0)) {
108
+ if (this._disposed)
109
+ return Promise.reject(new Error('leviathan-crypto: pool is disposed'));
110
+ if (key.length !== 32)
111
+ return Promise.reject(new RangeError(`key must be 32 bytes (got ${key.length})`));
112
+ if (nonce.length !== 24)
113
+ return Promise.reject(new RangeError(`XChaCha20 nonce must be 24 bytes (got ${nonce.length})`));
114
+ return this._dispatch('encrypt', key, nonce, plaintext, aad);
115
+ }
116
+ /**
117
+ * Decrypt ciphertext with XChaCha20-Poly1305.
118
+ * Input is `ciphertext || tag` (at least 16 bytes).
119
+ *
120
+ * **Warning:** All input buffers are transferred and neutered after dispatch.
121
+ */
122
+ decrypt(key, nonce, ciphertext, aad = new Uint8Array(0)) {
123
+ if (this._disposed)
124
+ return Promise.reject(new Error('leviathan-crypto: pool is disposed'));
125
+ if (key.length !== 32)
126
+ return Promise.reject(new RangeError(`key must be 32 bytes (got ${key.length})`));
127
+ if (nonce.length !== 24)
128
+ return Promise.reject(new RangeError(`XChaCha20 nonce must be 24 bytes (got ${nonce.length})`));
129
+ if (ciphertext.length < 16)
130
+ return Promise.reject(new RangeError(`ciphertext too short — must include 16-byte tag (got ${ciphertext.length})`));
131
+ return this._dispatch('decrypt', key, nonce, ciphertext, aad);
132
+ }
133
+ /** Terminates all workers. Rejects all pending and queued jobs. */
134
+ dispose() {
135
+ if (this._disposed)
136
+ return;
137
+ this._disposed = true;
138
+ for (const w of this._workers)
139
+ w.terminate();
140
+ const err = new Error('leviathan-crypto: pool disposed');
141
+ for (const { reject } of this._pending.values())
142
+ reject(err);
143
+ for (const job of this._queue)
144
+ this._pending.get(job.id)?.reject(err);
145
+ this._pending.clear();
146
+ this._queue.length = 0;
147
+ }
148
+ /** Number of workers in the pool. */
149
+ get size() {
150
+ return this._workers.length;
151
+ }
152
+ /** Number of jobs currently queued (waiting for a free worker). */
153
+ get queueDepth() {
154
+ return this._queue.length;
155
+ }
156
+ // ── Internals ────────────────────────────────────────────────────────────
157
+ _dispatch(op, key, nonce, data, aad) {
158
+ return new Promise((resolve, reject) => {
159
+ const id = this._nextId++;
160
+ const job = { id, op, key, nonce, data, aad };
161
+ this._pending.set(id, { resolve, reject });
162
+ const worker = this._idle.pop();
163
+ if (worker)
164
+ this._send(worker, job);
165
+ else
166
+ this._queue.push(job);
167
+ });
168
+ }
169
+ _send(worker, job) {
170
+ worker.postMessage({ type: 'job', ...job }, [job.key.buffer, job.nonce.buffer, job.data.buffer, job.aad.buffer]);
171
+ }
172
+ _onMessage(worker, e) {
173
+ const msg = e.data;
174
+ const job = this._pending.get(msg.id);
175
+ if (!job)
176
+ return;
177
+ this._pending.delete(msg.id);
178
+ if (msg.type === 'result')
179
+ job.resolve(msg.data);
180
+ else
181
+ job.reject(new Error(msg.message));
182
+ const next = this._queue.shift();
183
+ if (next)
184
+ this._send(worker, next);
185
+ else
186
+ this._idle.push(worker);
187
+ }
188
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ // / <reference lib="webworker" />
2
+ // src/ts/chacha20/pool.worker.ts
3
+ //
4
+ // Worker entry point for XChaCha20Poly1305Pool. Runs in a Web Worker or
5
+ // worker_threads context — no access to the main thread's module cache.
6
+ // Owns its own WebAssembly.Instance with its own linear memory.
7
+ import { xcEncrypt, xcDecrypt } from './ops.js';
8
+ let x;
9
+ self.onmessage = async (e) => {
10
+ const msg = e.data;
11
+ if (msg.type === 'init') {
12
+ try {
13
+ const mem = new WebAssembly.Memory({ initial: 3, maximum: 3 });
14
+ const inst = await WebAssembly.instantiate(msg.module, { env: { memory: mem } });
15
+ x = inst.exports;
16
+ self.postMessage({ type: 'ready' });
17
+ }
18
+ catch (err) {
19
+ self.postMessage({ type: 'error', id: -1, message: err.message });
20
+ }
21
+ return;
22
+ }
23
+ if (!x) {
24
+ self.postMessage({ type: 'error', id: msg.id, message: 'worker not initialized' });
25
+ return;
26
+ }
27
+ try {
28
+ const { id, op, key, nonce, data, aad } = msg;
29
+ const result = op === 'encrypt'
30
+ ? xcEncrypt(x, key, nonce, data, aad)
31
+ : xcDecrypt(x, key, nonce, data, aad);
32
+ self.postMessage({ type: 'result', id, data: result }, [result.buffer]);
33
+ }
34
+ catch (err) {
35
+ self.postMessage({ type: 'error', id: msg.id, message: err.message });
36
+ }
37
+ };
@@ -0,0 +1,30 @@
1
+ /** WASM exports for the chacha module */
2
+ export interface ChaChaExports {
3
+ memory: WebAssembly.Memory;
4
+ getModuleId(): number;
5
+ getKeyOffset(): number;
6
+ getChachaNonceOffset(): number;
7
+ getChachaCtrOffset(): number;
8
+ getChachaBlockOffset(): number;
9
+ getChachaStateOffset(): number;
10
+ getChunkPtOffset(): number;
11
+ getChunkCtOffset(): number;
12
+ getChunkSize(): number;
13
+ getPolyKeyOffset(): number;
14
+ getPolyMsgOffset(): number;
15
+ getPolyTagOffset(): number;
16
+ getPolyBufLenOffset(): number;
17
+ getXChaChaNonceOffset(): number;
18
+ getXChaChaSubkeyOffset(): number;
19
+ chachaLoadKey(): void;
20
+ chachaSetCounter(n: number): void;
21
+ chachaResetCounter(): void;
22
+ chachaEncryptChunk(n: number): number;
23
+ chachaDecryptChunk(n: number): number;
24
+ chachaGenPolyKey(): void;
25
+ hchacha20(): void;
26
+ polyInit(): void;
27
+ polyUpdate(n: number): void;
28
+ polyFinal(): void;
29
+ wipeBuffers(): void;
30
+ }
@@ -0,0 +1 @@
1
+ export {};