leviathan-crypto 1.4.0 → 2.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.
- package/CLAUDE.md +129 -94
- package/README.md +166 -223
- package/SECURITY.md +85 -45
- package/dist/chacha20/cipher-suite.d.ts +4 -0
- package/dist/chacha20/cipher-suite.js +78 -0
- package/dist/chacha20/embedded.d.ts +1 -0
- package/dist/chacha20/embedded.js +27 -0
- package/dist/chacha20/index.d.ts +20 -27
- package/dist/chacha20/index.js +40 -59
- package/dist/chacha20/ops.d.ts +1 -1
- package/dist/chacha20/ops.js +19 -18
- package/dist/chacha20/pool-worker.js +77 -0
- package/dist/ct-wasm.d.ts +1 -0
- package/dist/ct-wasm.js +3 -0
- package/dist/ct.wasm +0 -0
- package/dist/docs/aead.md +320 -0
- package/dist/docs/architecture.md +419 -285
- package/dist/docs/argon2id.md +42 -30
- package/dist/docs/chacha20.md +192 -266
- package/dist/docs/exports.md +241 -0
- package/dist/docs/fortuna.md +60 -69
- package/dist/docs/init.md +172 -178
- package/dist/docs/loader.md +87 -142
- package/dist/docs/serpent.md +134 -583
- package/dist/docs/sha2.md +91 -103
- package/dist/docs/sha3.md +70 -36
- package/dist/docs/types.md +93 -16
- package/dist/docs/utils.md +109 -32
- package/dist/embedded/kyber.d.ts +1 -0
- package/dist/embedded/kyber.js +3 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.js +38 -0
- package/dist/fortuna.d.ts +0 -6
- package/dist/fortuna.js +5 -5
- package/dist/index.d.ts +25 -9
- package/dist/index.js +36 -7
- package/dist/init.d.ts +3 -7
- package/dist/init.js +18 -35
- package/dist/keccak/embedded.d.ts +1 -0
- package/dist/keccak/embedded.js +27 -0
- package/dist/keccak/index.d.ts +4 -0
- package/dist/keccak/index.js +31 -0
- package/dist/kyber/embedded.d.ts +1 -0
- package/dist/kyber/embedded.js +27 -0
- package/dist/kyber/indcpa.d.ts +49 -0
- package/dist/kyber/indcpa.js +352 -0
- package/dist/kyber/index.d.ts +38 -0
- package/dist/kyber/index.js +150 -0
- package/dist/kyber/kem.d.ts +21 -0
- package/dist/kyber/kem.js +160 -0
- package/dist/kyber/params.d.ts +14 -0
- package/dist/kyber/params.js +37 -0
- package/dist/kyber/suite.d.ts +13 -0
- package/dist/kyber/suite.js +93 -0
- package/dist/kyber/types.d.ts +98 -0
- package/dist/kyber/types.js +25 -0
- package/dist/kyber/validate.d.ts +19 -0
- package/dist/kyber/validate.js +68 -0
- package/dist/kyber.wasm +0 -0
- package/dist/loader.d.ts +15 -6
- package/dist/loader.js +65 -21
- package/dist/serpent/cipher-suite.d.ts +4 -0
- package/dist/serpent/cipher-suite.js +121 -0
- package/dist/serpent/embedded.d.ts +1 -0
- package/dist/serpent/embedded.js +27 -0
- package/dist/serpent/index.d.ts +6 -37
- package/dist/serpent/index.js +9 -118
- package/dist/serpent/pool-worker.d.ts +1 -0
- package/dist/serpent/pool-worker.js +202 -0
- package/dist/serpent/serpent-cbc.d.ts +30 -0
- package/dist/serpent/serpent-cbc.js +136 -0
- package/dist/sha2/embedded.d.ts +1 -0
- package/dist/sha2/embedded.js +27 -0
- package/dist/sha2/hkdf.js +6 -2
- package/dist/sha2/index.d.ts +3 -2
- package/dist/sha2/index.js +3 -4
- package/dist/sha3/embedded.d.ts +1 -0
- package/dist/sha3/embedded.js +27 -0
- package/dist/sha3/index.d.ts +3 -2
- package/dist/sha3/index.js +3 -4
- package/dist/stream/constants.d.ts +6 -0
- package/dist/stream/constants.js +30 -0
- package/dist/stream/header.d.ts +9 -0
- package/dist/stream/header.js +77 -0
- package/dist/stream/index.d.ts +7 -0
- package/dist/stream/index.js +27 -0
- package/dist/stream/open-stream.d.ts +21 -0
- package/dist/stream/open-stream.js +146 -0
- package/dist/stream/seal-stream-pool.d.ts +38 -0
- package/dist/stream/seal-stream-pool.js +391 -0
- package/dist/stream/seal-stream.d.ts +20 -0
- package/dist/stream/seal-stream.js +142 -0
- package/dist/stream/seal.d.ts +9 -0
- package/dist/stream/seal.js +75 -0
- package/dist/stream/types.d.ts +24 -0
- package/dist/stream/types.js +26 -0
- package/dist/utils.d.ts +7 -2
- package/dist/utils.js +49 -3
- package/dist/wasm-source.d.ts +12 -0
- package/dist/wasm-source.js +26 -0
- package/package.json +13 -5
- package/dist/chacha20/pool.d.ts +0 -52
- package/dist/chacha20/pool.js +0 -178
- package/dist/chacha20/pool.worker.js +0 -37
- package/dist/chacha20/stream-sealer.d.ts +0 -49
- package/dist/chacha20/stream-sealer.js +0 -327
- package/dist/docs/chacha20_pool.md +0 -309
- package/dist/docs/wasm.md +0 -194
- package/dist/serpent/seal.d.ts +0 -8
- package/dist/serpent/seal.js +0 -72
- package/dist/serpent/stream-pool.d.ts +0 -48
- package/dist/serpent/stream-pool.js +0 -275
- package/dist/serpent/stream-sealer.d.ts +0 -55
- package/dist/serpent/stream-sealer.js +0 -342
- package/dist/serpent/stream.d.ts +0 -28
- package/dist/serpent/stream.js +0 -205
- package/dist/serpent/stream.worker.d.ts +0 -32
- package/dist/serpent/stream.worker.js +0 -117
- /package/dist/chacha20/{pool.worker.d.ts → pool-worker.d.ts} +0 -0
|
@@ -0,0 +1,142 @@
|
|
|
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/stream/seal-stream.ts
|
|
23
|
+
//
|
|
24
|
+
// SealStream — cipher-agnostic streaming encryption using the STREAM
|
|
25
|
+
// construction (Hoang/Reyhanitabar/Rogaway/Vizár, CRYPTO 2015).
|
|
26
|
+
import { randomBytes, concat } from '../utils.js';
|
|
27
|
+
import { isInitialized } from '../init.js';
|
|
28
|
+
import { CHUNK_MIN, CHUNK_MAX, TAG_DATA, TAG_FINAL } from './constants.js';
|
|
29
|
+
import { writeHeader, makeCounterNonce } from './header.js';
|
|
30
|
+
function u32beFrame(n) {
|
|
31
|
+
const b = new Uint8Array(4);
|
|
32
|
+
new DataView(b.buffer).setUint32(0, n, false);
|
|
33
|
+
return b;
|
|
34
|
+
}
|
|
35
|
+
// Module-level nonce injection slot — used only by _fromNonce for KAT tests.
|
|
36
|
+
// Set immediately before constructing, cleared inside the constructor.
|
|
37
|
+
let _injectNonce;
|
|
38
|
+
export class SealStream {
|
|
39
|
+
/** Preamble sent before the first chunk: header [|| kemCiphertext]. */
|
|
40
|
+
preamble;
|
|
41
|
+
cipher;
|
|
42
|
+
keys;
|
|
43
|
+
chunkSize;
|
|
44
|
+
framed;
|
|
45
|
+
counter = 0;
|
|
46
|
+
state = 'ready';
|
|
47
|
+
constructor(cipher, key, opts) {
|
|
48
|
+
this.cipher = cipher;
|
|
49
|
+
this.chunkSize = opts?.chunkSize ?? 65536;
|
|
50
|
+
this.framed = opts?.framed ?? false;
|
|
51
|
+
if (!isInitialized('sha2'))
|
|
52
|
+
throw new Error('leviathan-crypto: stream layer requires sha2 for key derivation — '
|
|
53
|
+
+ 'call init({ sha2: ... }) before creating a SealStream');
|
|
54
|
+
if (key.length !== cipher.keySize)
|
|
55
|
+
throw new RangeError(`key must be ${cipher.keySize} bytes (got ${key.length})`);
|
|
56
|
+
if (this.chunkSize < CHUNK_MIN || this.chunkSize > CHUNK_MAX)
|
|
57
|
+
throw new RangeError(`chunkSize must be in [${CHUNK_MIN}, ${CHUNK_MAX}] (got ${this.chunkSize})`);
|
|
58
|
+
const nonce = _injectNonce ?? randomBytes(16);
|
|
59
|
+
_injectNonce = undefined;
|
|
60
|
+
this.keys = cipher.deriveKeys(key, nonce);
|
|
61
|
+
const kemCt = this.keys.kemCiphertext;
|
|
62
|
+
const header = writeHeader(cipher.formatEnum, this.framed, nonce, this.chunkSize);
|
|
63
|
+
this.preamble = kemCt ? concat(header, kemCt) : header;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* @internal
|
|
67
|
+
* KAT-only factory — injects a fixed nonce so seal output is deterministic.
|
|
68
|
+
* Stripped from published `.d.ts` by `stripInternal`. Do not use in production.
|
|
69
|
+
*/
|
|
70
|
+
static _fromNonce(cipher, key, opts, nonce) {
|
|
71
|
+
if (nonce.length !== 16)
|
|
72
|
+
throw new RangeError(`_nonce must be 16 bytes (got ${nonce.length})`);
|
|
73
|
+
_injectNonce = nonce;
|
|
74
|
+
try {
|
|
75
|
+
return new SealStream(cipher, key, opts);
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
_injectNonce = undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
push(chunk, opts) {
|
|
82
|
+
if (this.state !== 'ready')
|
|
83
|
+
throw new Error('SealStream: cannot push after finalize');
|
|
84
|
+
if (chunk.length > this.chunkSize)
|
|
85
|
+
throw new RangeError(`chunk exceeds chunkSize (${chunk.length} > ${this.chunkSize})`);
|
|
86
|
+
const nonce = makeCounterNonce(this.counter, TAG_DATA);
|
|
87
|
+
const result = this.cipher.sealChunk(this.keys, nonce, chunk, opts?.aad);
|
|
88
|
+
this.counter++;
|
|
89
|
+
return this.framed ? concat(u32beFrame(result.length), result) : result;
|
|
90
|
+
}
|
|
91
|
+
finalize(chunk, opts) {
|
|
92
|
+
if (this.state !== 'ready')
|
|
93
|
+
throw new Error('SealStream: already finalized');
|
|
94
|
+
if (chunk.length > this.chunkSize)
|
|
95
|
+
throw new RangeError(`chunk exceeds chunkSize (${chunk.length} > ${this.chunkSize})`);
|
|
96
|
+
const nonce = makeCounterNonce(this.counter, TAG_FINAL);
|
|
97
|
+
const result = this.cipher.sealChunk(this.keys, nonce, chunk, opts?.aad);
|
|
98
|
+
this.cipher.wipeKeys(this.keys);
|
|
99
|
+
this.state = 'finalized';
|
|
100
|
+
return this.framed ? concat(u32beFrame(result.length), result) : result;
|
|
101
|
+
}
|
|
102
|
+
dispose() {
|
|
103
|
+
if (this.state === 'ready') {
|
|
104
|
+
this.cipher.wipeKeys(this.keys);
|
|
105
|
+
this.state = 'finalized';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
toTransformStream() {
|
|
109
|
+
let headerSent = false;
|
|
110
|
+
let buffered = null;
|
|
111
|
+
return new TransformStream({
|
|
112
|
+
transform: (chunk, controller) => {
|
|
113
|
+
try {
|
|
114
|
+
if (!headerSent) {
|
|
115
|
+
controller.enqueue(this.preamble);
|
|
116
|
+
headerSent = true;
|
|
117
|
+
}
|
|
118
|
+
if (buffered !== null) {
|
|
119
|
+
controller.enqueue(this.push(buffered));
|
|
120
|
+
}
|
|
121
|
+
buffered = chunk;
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
this.dispose();
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
flush: (controller) => {
|
|
129
|
+
try {
|
|
130
|
+
if (!headerSent) {
|
|
131
|
+
controller.enqueue(this.preamble);
|
|
132
|
+
}
|
|
133
|
+
controller.enqueue(this.finalize(buffered ?? new Uint8Array(0)));
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
this.dispose();
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CipherSuite } from './types.js';
|
|
2
|
+
export declare class Seal {
|
|
3
|
+
static encrypt(suite: CipherSuite, key: Uint8Array, pt: Uint8Array, opts?: {
|
|
4
|
+
aad?: Uint8Array;
|
|
5
|
+
}): Uint8Array;
|
|
6
|
+
static decrypt(suite: CipherSuite, key: Uint8Array, blob: Uint8Array, opts?: {
|
|
7
|
+
aad?: Uint8Array;
|
|
8
|
+
}): Uint8Array;
|
|
9
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
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/stream/seal.ts
|
|
23
|
+
//
|
|
24
|
+
// Seal — unified single-shot encrypt/decrypt using the STREAM construction.
|
|
25
|
+
// Seal blobs are valid SealStream blobs with a single final chunk.
|
|
26
|
+
// OpenStream can decrypt a Seal blob without modification.
|
|
27
|
+
import { concat } from '../utils.js';
|
|
28
|
+
import { SealStream } from './seal-stream.js';
|
|
29
|
+
import { OpenStream } from './open-stream.js';
|
|
30
|
+
import { HEADER_SIZE, CHUNK_MAX, CHUNK_MIN } from './constants.js';
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-extraneous-class -- static-only class required for stripInternal to strip _fromNonce from .d.ts
|
|
32
|
+
export class Seal {
|
|
33
|
+
static encrypt(suite, key, pt, opts) {
|
|
34
|
+
if (pt.length > CHUNK_MAX)
|
|
35
|
+
throw new RangeError(`Seal.encrypt: plaintext exceeds maximum (${CHUNK_MAX} bytes) — use SealStream for large data`);
|
|
36
|
+
const sealer = new SealStream(suite, key, { chunkSize: Math.max(pt.length, CHUNK_MIN) });
|
|
37
|
+
try {
|
|
38
|
+
const ct = sealer.finalize(pt, opts);
|
|
39
|
+
return concat(sealer.preamble, ct);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
sealer.dispose();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
static decrypt(suite, key, blob, opts) {
|
|
46
|
+
const preambleLen = HEADER_SIZE + suite.kemCtSize;
|
|
47
|
+
if (blob.length < preambleLen)
|
|
48
|
+
throw new RangeError(`Seal.decrypt: blob too short — need at least ${preambleLen} bytes (got ${blob.length})`);
|
|
49
|
+
const preamble = blob.subarray(0, preambleLen);
|
|
50
|
+
const opener = new OpenStream(suite, key, preamble);
|
|
51
|
+
try {
|
|
52
|
+
return opener.finalize(blob.subarray(preambleLen), opts);
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
opener.dispose();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* @internal
|
|
60
|
+
* KAT-only — injects a fixed nonce so output is deterministic.
|
|
61
|
+
* Stripped from published `.d.ts` by `stripInternal`. Do not use in production.
|
|
62
|
+
*/
|
|
63
|
+
static _fromNonce(suite, key, pt, nonce, opts) {
|
|
64
|
+
if (pt.length > CHUNK_MAX)
|
|
65
|
+
throw new RangeError(`Seal._fromNonce: plaintext exceeds maximum (${CHUNK_MAX} bytes) — use SealStream for large data`);
|
|
66
|
+
const sealer = SealStream._fromNonce(suite, key, { chunkSize: Math.max(pt.length, CHUNK_MIN) }, nonce);
|
|
67
|
+
try {
|
|
68
|
+
const ct = sealer.finalize(pt, opts);
|
|
69
|
+
return concat(sealer.preamble, ct);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
sealer.dispose();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface DerivedKeys {
|
|
2
|
+
readonly bytes: Uint8Array;
|
|
3
|
+
readonly kemCiphertext?: Uint8Array;
|
|
4
|
+
}
|
|
5
|
+
export interface CipherSuite {
|
|
6
|
+
readonly formatEnum: number;
|
|
7
|
+
readonly formatName: string;
|
|
8
|
+
readonly hkdfInfo: string;
|
|
9
|
+
readonly keySize: number;
|
|
10
|
+
readonly decKeySize?: number;
|
|
11
|
+
readonly kemCtSize: number;
|
|
12
|
+
readonly tagSize: number;
|
|
13
|
+
readonly padded: boolean;
|
|
14
|
+
deriveKeys(key: Uint8Array, nonce: Uint8Array, kemCt?: Uint8Array): DerivedKeys;
|
|
15
|
+
sealChunk(keys: DerivedKeys, counterNonce: Uint8Array, chunk: Uint8Array, aad?: Uint8Array): Uint8Array;
|
|
16
|
+
openChunk(keys: DerivedKeys, counterNonce: Uint8Array, chunk: Uint8Array, aad?: Uint8Array): Uint8Array;
|
|
17
|
+
wipeKeys(keys: DerivedKeys): void;
|
|
18
|
+
readonly wasmModules: readonly string[];
|
|
19
|
+
createPoolWorker(): Worker;
|
|
20
|
+
}
|
|
21
|
+
export interface SealStreamOpts {
|
|
22
|
+
chunkSize?: number;
|
|
23
|
+
framed?: boolean;
|
|
24
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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/stream/types.ts
|
|
23
|
+
//
|
|
24
|
+
// CipherSuite interface — cipher-specific logic injected into SealStream
|
|
25
|
+
// and OpenStream. Implementations are plain objects (not classes).
|
|
26
|
+
export {};
|
package/dist/utils.d.ts
CHANGED
|
@@ -10,10 +10,14 @@ export declare const bytesToUtf8: (bytes: Uint8Array) => string;
|
|
|
10
10
|
export declare const base64ToBytes: (b64: string) => Uint8Array | undefined;
|
|
11
11
|
/** Uint8Array to base64 string. Pass url=true for base64url (RFC 4648 §5 — no padding characters). */
|
|
12
12
|
export declare const bytesToBase64: (bytes: Uint8Array, url?: boolean) => string;
|
|
13
|
+
export declare const CT_MAX_BYTES = 32768;
|
|
13
14
|
/**
|
|
14
15
|
* Constant-time byte-array equality.
|
|
15
|
-
*
|
|
16
|
+
* Uses WASM SIMD when available (no JIT short-circuiting, no speculative
|
|
17
|
+
* optimization). Falls back to a JS XOR-accumulate loop on runtimes
|
|
18
|
+
* without SIMD support.
|
|
16
19
|
* Length check is not constant-time (length is non-secret in all protocols).
|
|
20
|
+
* Max input size: 32768 bytes per side (enforced regardless of code path).
|
|
17
21
|
*/
|
|
18
22
|
export declare const constantTimeEqual: (a: Uint8Array, b: Uint8Array) => boolean;
|
|
19
23
|
/** Zero a typed array in place. */
|
|
@@ -26,6 +30,7 @@ export declare const concat: (...arrays: Uint8Array[]) => Uint8Array;
|
|
|
26
30
|
export declare const randomBytes: (n: number) => Uint8Array;
|
|
27
31
|
/**
|
|
28
32
|
* Detects WASM SIMD support once and caches the result.
|
|
29
|
-
*
|
|
33
|
+
* Used by init() to preflight-check before loading serpent/chacha20 modules.
|
|
34
|
+
* Exported for consumers who want to feature-detect before calling init().
|
|
30
35
|
*/
|
|
31
36
|
export declare function hasSIMD(): boolean;
|
package/dist/utils.js
CHANGED
|
@@ -136,15 +136,60 @@ export const bytesToBase64 = (bytes, url = false) => {
|
|
|
136
136
|
}
|
|
137
137
|
return base64;
|
|
138
138
|
};
|
|
139
|
-
// ──
|
|
139
|
+
// ── Constant-time comparison ─────────────────────────────────────────────────
|
|
140
|
+
import { CT_WASM } from './ct-wasm.js';
|
|
141
|
+
let _ctCompare = null;
|
|
142
|
+
let _ctMem = null;
|
|
143
|
+
let _ctInit = false;
|
|
144
|
+
// CT WASM module uses 1 page (64KB) of linear memory with both buffers
|
|
145
|
+
// laid out side-by-side: a at offset 0, b at offset a.length.
|
|
146
|
+
// Max per-side = _ctMem.buffer.byteLength >>> 1 = 32768 bytes.
|
|
147
|
+
// In practice the largest comparison is a 32-byte HMAC-SHA-256 tag.
|
|
148
|
+
export const CT_MAX_BYTES = 32768;
|
|
149
|
+
/** Try to compile the SIMD WASM ct module. Returns false if unavailable. */
|
|
150
|
+
function _initCt() {
|
|
151
|
+
if (_ctInit)
|
|
152
|
+
return _ctCompare !== null;
|
|
153
|
+
_ctInit = true;
|
|
154
|
+
try {
|
|
155
|
+
if (!hasSIMD())
|
|
156
|
+
return false;
|
|
157
|
+
_ctMem = new WebAssembly.Memory({ initial: 1, maximum: 1 });
|
|
158
|
+
const buf = CT_WASM.buffer.slice(CT_WASM.byteOffset, CT_WASM.byteOffset + CT_WASM.byteLength);
|
|
159
|
+
const mod = new WebAssembly.Module(buf);
|
|
160
|
+
const inst = new WebAssembly.Instance(mod, { env: { memory: _ctMem } });
|
|
161
|
+
_ctCompare = inst.exports.compare;
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
140
168
|
/**
|
|
141
169
|
* Constant-time byte-array equality.
|
|
142
|
-
*
|
|
170
|
+
* Uses WASM SIMD when available (no JIT short-circuiting, no speculative
|
|
171
|
+
* optimization). Falls back to a JS XOR-accumulate loop on runtimes
|
|
172
|
+
* without SIMD support.
|
|
143
173
|
* Length check is not constant-time (length is non-secret in all protocols).
|
|
174
|
+
* Max input size: 32768 bytes per side (enforced regardless of code path).
|
|
144
175
|
*/
|
|
145
176
|
export const constantTimeEqual = (a, b) => {
|
|
146
177
|
if (a.length !== b.length)
|
|
147
178
|
return false;
|
|
179
|
+
if (a.length > CT_MAX_BYTES)
|
|
180
|
+
throw new RangeError(`constantTimeEqual: max ${CT_MAX_BYTES} bytes (got ${a.length})`);
|
|
181
|
+
if (_initCt() && _ctMem && _ctCompare) {
|
|
182
|
+
const mem = new Uint8Array(_ctMem.buffer);
|
|
183
|
+
mem.set(a, 0);
|
|
184
|
+
mem.set(b, a.length);
|
|
185
|
+
try {
|
|
186
|
+
return _ctCompare(0, a.length, a.length) === 1;
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
mem.fill(0, 0, a.length * 2);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// JS fallback — best-effort constant-time via XOR accumulate
|
|
148
193
|
let diff = 0;
|
|
149
194
|
for (let i = 0; i < a.length; i++)
|
|
150
195
|
diff |= a[i] ^ b[i];
|
|
@@ -181,7 +226,8 @@ export const randomBytes = (n) => {
|
|
|
181
226
|
let _simd = null;
|
|
182
227
|
/**
|
|
183
228
|
* Detects WASM SIMD support once and caches the result.
|
|
184
|
-
*
|
|
229
|
+
* Used by init() to preflight-check before loading serpent/chacha20 modules.
|
|
230
|
+
* Exported for consumers who want to feature-detect before calling init().
|
|
185
231
|
*/
|
|
186
232
|
export function hasSIMD() {
|
|
187
233
|
if (_simd !== null)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All accepted forms of WASM input for init functions.
|
|
3
|
+
*
|
|
4
|
+
* - `string` — gzip+base64 embedded blob (from `/embedded` subpath)
|
|
5
|
+
* - `URL` — fetched via `WebAssembly.instantiateStreaming`
|
|
6
|
+
* - `ArrayBuffer` — raw WASM bytes, compiled inline
|
|
7
|
+
* - `Uint8Array` — raw WASM bytes, compiled inline
|
|
8
|
+
* - `WebAssembly.Module` — pre-compiled module (Cloudflare Workers, edge runtimes)
|
|
9
|
+
* - `Response` — streaming instantiation from an in-flight fetch response
|
|
10
|
+
* - `Promise<Response>` — streaming instantiation from a deferred fetch
|
|
11
|
+
*/
|
|
12
|
+
export type WasmSource = string | URL | ArrayBuffer | Uint8Array | WebAssembly.Module | Response | Promise<Response>;
|
|
@@ -0,0 +1,26 @@
|
|
|
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/wasm-source.ts
|
|
23
|
+
//
|
|
24
|
+
// Union type for all accepted WASM loading strategies.
|
|
25
|
+
// The argument type determines the loading path — no mode string required.
|
|
26
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leviathan-crypto",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"author": "xero (https://x-e.ro)",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"description": "Zero-dependency
|
|
6
|
+
"description": "Zero-dependency WASM cryptography for TypeScript. Paranoid ciphers, post-quantum key encapsulation, and Fortuna CSPRNG behind a strictly typed API. All computation runs outside the JS JIT on vector-verified primitives.",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"sideEffects": false,
|
|
9
9
|
"exports": {
|
|
10
10
|
".": "./dist/index.js",
|
|
11
|
+
"./stream": "./dist/stream/index.js",
|
|
11
12
|
"./serpent": "./dist/serpent/index.js",
|
|
13
|
+
"./serpent/embedded": "./dist/serpent/embedded.js",
|
|
12
14
|
"./chacha20": "./dist/chacha20/index.js",
|
|
13
|
-
"./chacha20/
|
|
15
|
+
"./chacha20/embedded": "./dist/chacha20/embedded.js",
|
|
14
16
|
"./sha2": "./dist/sha2/index.js",
|
|
15
|
-
"./
|
|
17
|
+
"./sha2/embedded": "./dist/sha2/embedded.js",
|
|
18
|
+
"./sha3": "./dist/sha3/index.js",
|
|
19
|
+
"./sha3/embedded": "./dist/sha3/embedded.js",
|
|
20
|
+
"./keccak": "./dist/keccak/index.js",
|
|
21
|
+
"./keccak/embedded": "./dist/keccak/embedded.js",
|
|
22
|
+
"./kyber": "./dist/kyber/index.js",
|
|
23
|
+
"./kyber/embedded": "./dist/kyber/embedded.js"
|
|
16
24
|
},
|
|
17
25
|
"types": "./dist/index.d.ts",
|
|
18
26
|
"files": [
|
|
@@ -24,7 +32,7 @@
|
|
|
24
32
|
"type": "git",
|
|
25
33
|
"url": "git+https://github.com/xero/leviathan-crypto.git"
|
|
26
34
|
},
|
|
27
|
-
"homepage": "https://
|
|
35
|
+
"homepage": "https://leviathan.3xi.club",
|
|
28
36
|
"bugs": {
|
|
29
37
|
"url": "https://github.com/xero/leviathan-crypto/issues"
|
|
30
38
|
},
|
package/dist/chacha20/pool.d.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
export interface PoolOpts {
|
|
2
|
-
/** Number of workers. Default: navigator.hardwareConcurrency ?? 4 */
|
|
3
|
-
workers?: number;
|
|
4
|
-
}
|
|
5
|
-
/**
|
|
6
|
-
* Parallel worker pool for XChaCha20-Poly1305 AEAD.
|
|
7
|
-
*
|
|
8
|
-
* Each worker owns its own `WebAssembly.Instance` with isolated linear memory.
|
|
9
|
-
* Jobs are dispatched round-robin to idle workers; excess jobs queue until a
|
|
10
|
-
* worker frees up.
|
|
11
|
-
*
|
|
12
|
-
* **Warning:** Input buffers (`key`, `nonce`, `plaintext`/`ciphertext`, `aad`)
|
|
13
|
-
* are transferred to the worker and neutered on the calling side. The caller
|
|
14
|
-
* must copy any buffer they need to retain after calling `encrypt()`/`decrypt()`.
|
|
15
|
-
*/
|
|
16
|
-
export declare class XChaCha20Poly1305Pool {
|
|
17
|
-
private readonly _workers;
|
|
18
|
-
private readonly _idle;
|
|
19
|
-
private readonly _queue;
|
|
20
|
-
private readonly _pending;
|
|
21
|
-
private _nextId;
|
|
22
|
-
private _disposed;
|
|
23
|
-
private constructor();
|
|
24
|
-
/**
|
|
25
|
-
* Create a new pool. Requires `init(['chacha20'])` to have been called.
|
|
26
|
-
* Compiles the WASM module once and distributes it to all workers.
|
|
27
|
-
*/
|
|
28
|
-
static create(opts?: PoolOpts): Promise<XChaCha20Poly1305Pool>;
|
|
29
|
-
/**
|
|
30
|
-
* Encrypt plaintext with XChaCha20-Poly1305.
|
|
31
|
-
* Returns `ciphertext || tag` (plaintext.length + 16 bytes).
|
|
32
|
-
*
|
|
33
|
-
* **Warning:** All input buffers are transferred and neutered after dispatch.
|
|
34
|
-
*/
|
|
35
|
-
encrypt(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Promise<Uint8Array>;
|
|
36
|
-
/**
|
|
37
|
-
* Decrypt ciphertext with XChaCha20-Poly1305.
|
|
38
|
-
* Input is `ciphertext || tag` (at least 16 bytes).
|
|
39
|
-
*
|
|
40
|
-
* **Warning:** All input buffers are transferred and neutered after dispatch.
|
|
41
|
-
*/
|
|
42
|
-
decrypt(key: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, aad?: Uint8Array): Promise<Uint8Array>;
|
|
43
|
-
/** Terminates all workers. Rejects all pending and queued jobs. */
|
|
44
|
-
dispose(): void;
|
|
45
|
-
/** Number of workers in the pool. */
|
|
46
|
-
get size(): number;
|
|
47
|
-
/** Number of jobs currently queued (waiting for a free worker). */
|
|
48
|
-
get queueDepth(): number;
|
|
49
|
-
private _dispatch;
|
|
50
|
-
private _send;
|
|
51
|
-
private _onMessage;
|
|
52
|
-
}
|