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.
- package/CLAUDE.md +265 -0
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/SECURITY.md +174 -0
- package/dist/chacha.wasm +0 -0
- package/dist/chacha20/index.d.ts +49 -0
- package/dist/chacha20/index.js +177 -0
- package/dist/chacha20/ops.d.ts +16 -0
- package/dist/chacha20/ops.js +146 -0
- package/dist/chacha20/pool.d.ts +52 -0
- package/dist/chacha20/pool.js +188 -0
- package/dist/chacha20/pool.worker.d.ts +1 -0
- package/dist/chacha20/pool.worker.js +37 -0
- package/dist/chacha20/types.d.ts +30 -0
- package/dist/chacha20/types.js +1 -0
- package/dist/docs/architecture.md +795 -0
- package/dist/docs/argon2id.md +290 -0
- package/dist/docs/chacha20.md +602 -0
- package/dist/docs/chacha20_pool.md +306 -0
- package/dist/docs/fortuna.md +322 -0
- package/dist/docs/init.md +308 -0
- package/dist/docs/loader.md +206 -0
- package/dist/docs/serpent.md +914 -0
- package/dist/docs/sha2.md +620 -0
- package/dist/docs/sha3.md +509 -0
- package/dist/docs/types.md +198 -0
- package/dist/docs/utils.md +273 -0
- package/dist/docs/wasm.md +193 -0
- package/dist/embedded/chacha.d.ts +1 -0
- package/dist/embedded/chacha.js +2 -0
- package/dist/embedded/serpent.d.ts +1 -0
- package/dist/embedded/serpent.js +2 -0
- package/dist/embedded/sha2.d.ts +1 -0
- package/dist/embedded/sha2.js +2 -0
- package/dist/embedded/sha3.d.ts +1 -0
- package/dist/embedded/sha3.js +2 -0
- package/dist/fortuna.d.ts +72 -0
- package/dist/fortuna.js +445 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +44 -0
- package/dist/init.d.ts +11 -0
- package/dist/init.js +49 -0
- package/dist/loader.d.ts +4 -0
- package/dist/loader.js +30 -0
- package/dist/serpent/index.d.ts +65 -0
- package/dist/serpent/index.js +242 -0
- package/dist/serpent/seal.d.ts +8 -0
- package/dist/serpent/seal.js +70 -0
- package/dist/serpent/stream-encoder.d.ts +20 -0
- package/dist/serpent/stream-encoder.js +167 -0
- package/dist/serpent/stream-pool.d.ts +48 -0
- package/dist/serpent/stream-pool.js +285 -0
- package/dist/serpent/stream-sealer.d.ts +34 -0
- package/dist/serpent/stream-sealer.js +223 -0
- package/dist/serpent/stream.d.ts +28 -0
- package/dist/serpent/stream.js +205 -0
- package/dist/serpent/stream.worker.d.ts +32 -0
- package/dist/serpent/stream.worker.js +117 -0
- package/dist/serpent/types.d.ts +5 -0
- package/dist/serpent/types.js +1 -0
- package/dist/serpent.wasm +0 -0
- package/dist/sha2/hkdf.d.ts +16 -0
- package/dist/sha2/hkdf.js +108 -0
- package/dist/sha2/index.d.ts +40 -0
- package/dist/sha2/index.js +190 -0
- package/dist/sha2/types.d.ts +5 -0
- package/dist/sha2/types.js +1 -0
- package/dist/sha2.wasm +0 -0
- package/dist/sha3/index.d.ts +55 -0
- package/dist/sha3/index.js +246 -0
- package/dist/sha3/types.d.ts +5 -0
- package/dist/sha3/types.js +1 -0
- package/dist/sha3.wasm +0 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.js +26 -0
- package/dist/utils.d.ts +26 -0
- package/dist/utils.js +169 -0
- package/package.json +90 -0
package/dist/fortuna.js
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
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/fortuna.ts
|
|
23
|
+
//
|
|
24
|
+
// Fortuna CSPRNG — Ferguson & Schneier, Practical Cryptography (2003), Chapter 9.
|
|
25
|
+
// Backed by WASM Serpent-256 ECB (generator) and WASM SHA-256 (accumulator pools).
|
|
26
|
+
// Requires init(['serpent', 'sha2']) before Fortuna.create().
|
|
27
|
+
import { isInitialized } from './init.js';
|
|
28
|
+
import { Serpent } from './serpent/index.js';
|
|
29
|
+
import { SHA256 } from './sha2/index.js';
|
|
30
|
+
import { wipe, utf8ToBytes, concat } from './utils.js';
|
|
31
|
+
const isBrowser = typeof window !== 'undefined';
|
|
32
|
+
const isNode = typeof process !== 'undefined' && typeof process.pid === 'number';
|
|
33
|
+
/**
|
|
34
|
+
* Fortuna CSPRNG — spec §9.3–§9.5
|
|
35
|
+
*
|
|
36
|
+
* Use `Fortuna.create()` to instantiate. Direct construction is not allowed.
|
|
37
|
+
*/
|
|
38
|
+
export class Fortuna {
|
|
39
|
+
// ── Constants ──────────────────────────────────────────────────────────
|
|
40
|
+
static NUM_POOLS = 32;
|
|
41
|
+
static RESEED_LIMIT = 64; // bits — pool 0 threshold (spec §9.5)
|
|
42
|
+
static MS_PER_RESEED = 100; // ms — minimum reseed interval (spec §9.5)
|
|
43
|
+
static NODE_STATS_INTERVAL = 1000; // ms — OS stats collector interval
|
|
44
|
+
static CRYPTO_INTERVAL = 3000; // ms — crypto.randomBytes interval
|
|
45
|
+
// ── State ─────────────────────────────────────────────────────────────
|
|
46
|
+
serpent;
|
|
47
|
+
sha;
|
|
48
|
+
poolHash; // 32 running SHA-256 chain hashes (32 bytes each)
|
|
49
|
+
poolEntropy;
|
|
50
|
+
genKey;
|
|
51
|
+
genCnt;
|
|
52
|
+
reseedCnt;
|
|
53
|
+
lastReseed;
|
|
54
|
+
entropyLevel;
|
|
55
|
+
eventId;
|
|
56
|
+
active;
|
|
57
|
+
disposed;
|
|
58
|
+
msPerReseed;
|
|
59
|
+
robin;
|
|
60
|
+
// Collector references for cleanup
|
|
61
|
+
boundCollectors = {};
|
|
62
|
+
timers = [];
|
|
63
|
+
// ── Static factory ────────────────────────────────────────────────────
|
|
64
|
+
static async create(opts) {
|
|
65
|
+
if (!isInitialized('serpent'))
|
|
66
|
+
throw new Error('leviathan-crypto: call init([\'serpent\', \'sha2\']) before using Fortuna');
|
|
67
|
+
if (!isInitialized('sha2'))
|
|
68
|
+
throw new Error('leviathan-crypto: call init([\'serpent\', \'sha2\']) before using Fortuna');
|
|
69
|
+
const f = new Fortuna(opts?.msPerReseed ?? Fortuna.MS_PER_RESEED);
|
|
70
|
+
f.initialize(opts?.entropy);
|
|
71
|
+
return f;
|
|
72
|
+
}
|
|
73
|
+
constructor(msPerReseed) {
|
|
74
|
+
this.serpent = new Serpent();
|
|
75
|
+
this.sha = new SHA256();
|
|
76
|
+
this.poolHash = [];
|
|
77
|
+
this.poolEntropy = [];
|
|
78
|
+
this.genKey = new Uint8Array(32);
|
|
79
|
+
this.genCnt = new Uint8Array(16);
|
|
80
|
+
this.reseedCnt = 0;
|
|
81
|
+
this.lastReseed = 0;
|
|
82
|
+
this.entropyLevel = 0;
|
|
83
|
+
this.eventId = 0;
|
|
84
|
+
this.active = false;
|
|
85
|
+
this.disposed = false;
|
|
86
|
+
this.msPerReseed = msPerReseed;
|
|
87
|
+
this.robin = { kbd: 0, mouse: 0, scroll: 0, touch: 0, motion: 0, time: 0, rnd: 0, dom: 0 };
|
|
88
|
+
for (let i = 0; i < Fortuna.NUM_POOLS; i++) {
|
|
89
|
+
this.poolHash.push(new Uint8Array(32)); // zero-initialized chain value
|
|
90
|
+
this.poolEntropy.push(0);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// ── Public API ────────────────────────────────────────────────────────
|
|
94
|
+
/** Get n random bytes. Returns undefined if not yet seeded (reseedCnt === 0). */
|
|
95
|
+
get(length) {
|
|
96
|
+
if (this.disposed)
|
|
97
|
+
throw new Error('Fortuna instance has been disposed');
|
|
98
|
+
// Capture hrtime jitter at call time (Node.js) — spec §9.5
|
|
99
|
+
if (isNode)
|
|
100
|
+
this.captureHrtime();
|
|
101
|
+
// Check reseed trigger — spec §9.5
|
|
102
|
+
if (this.poolEntropy[0] >= Fortuna.RESEED_LIMIT &&
|
|
103
|
+
Date.now() >= this.lastReseed + this.msPerReseed) {
|
|
104
|
+
this.reseedCnt = (this.reseedCnt + 1) >>> 0; // u32 wrap
|
|
105
|
+
let seed = new Uint8Array(0);
|
|
106
|
+
let strength = 0;
|
|
107
|
+
for (let i = 0; i < Fortuna.NUM_POOLS; i++) {
|
|
108
|
+
if ((this.reseedCnt & (1 << i)) !== 0) {
|
|
109
|
+
// Pool digest = current chain hash
|
|
110
|
+
seed = concat(seed, this.poolHash[i]);
|
|
111
|
+
strength += this.poolEntropy[i];
|
|
112
|
+
// Reset pool
|
|
113
|
+
this.poolHash[i] = new Uint8Array(32);
|
|
114
|
+
this.poolEntropy[i] = 0;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
this.entropyLevel -= strength;
|
|
118
|
+
this.reseed(seed);
|
|
119
|
+
}
|
|
120
|
+
if (this.reseedCnt === 0)
|
|
121
|
+
return undefined;
|
|
122
|
+
return this.pseudoRandomData(length);
|
|
123
|
+
}
|
|
124
|
+
/** Add external entropy to the pools. */
|
|
125
|
+
addEntropy(entropy) {
|
|
126
|
+
if (this.disposed)
|
|
127
|
+
throw new Error('Fortuna instance has been disposed');
|
|
128
|
+
this.addRandomEvent(entropy, this.robin.rnd, entropy.length * 8);
|
|
129
|
+
this.robin.rnd = (this.robin.rnd + 1) % Fortuna.NUM_POOLS;
|
|
130
|
+
}
|
|
131
|
+
/** Get estimated available entropy in bytes. */
|
|
132
|
+
getEntropy() {
|
|
133
|
+
if (this.disposed)
|
|
134
|
+
throw new Error('Fortuna instance has been disposed');
|
|
135
|
+
return Math.floor(this.entropyLevel / 8);
|
|
136
|
+
}
|
|
137
|
+
/** Permanently dispose this instance. Wipes key material, stops all collectors. */
|
|
138
|
+
stop() {
|
|
139
|
+
if (this.disposed)
|
|
140
|
+
throw new Error('Fortuna instance has been disposed');
|
|
141
|
+
this.stopCollectors();
|
|
142
|
+
wipe(this.genKey);
|
|
143
|
+
wipe(this.genCnt);
|
|
144
|
+
this.reseedCnt = 0;
|
|
145
|
+
this.disposed = true;
|
|
146
|
+
}
|
|
147
|
+
// ── Test-only accessors ───────────────────────────────────────────────
|
|
148
|
+
/** @internal — exposed for testing key replacement */
|
|
149
|
+
_getGenKey() {
|
|
150
|
+
return this.genKey;
|
|
151
|
+
}
|
|
152
|
+
/** @internal — exposed for testing pool state */
|
|
153
|
+
_getPoolEntropy() {
|
|
154
|
+
return this.poolEntropy;
|
|
155
|
+
}
|
|
156
|
+
/** @internal — exposed for testing reseed count */
|
|
157
|
+
_getReseedCnt() {
|
|
158
|
+
return this.reseedCnt;
|
|
159
|
+
}
|
|
160
|
+
// ── Generator (spec §9.4) ─────────────────────────────────────────────
|
|
161
|
+
/** Generate n blocks of 16 bytes each. — spec §9.4 */
|
|
162
|
+
generateBlocks(n) {
|
|
163
|
+
const out = new Uint8Array(n * 16);
|
|
164
|
+
for (let i = 0; i < n; i++) {
|
|
165
|
+
// Encrypt genCnt with Serpent-256 ECB
|
|
166
|
+
this.serpent.loadKey(this.genKey);
|
|
167
|
+
out.set(this.serpent.encryptBlock(this.genCnt), i * 16);
|
|
168
|
+
this.incrementCounter();
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
/** Get length pseudo-random bytes. — spec §9.4 */
|
|
173
|
+
pseudoRandomData(length) {
|
|
174
|
+
// Generate ceil(length/16) + 1 blocks — +1 ensures extra block before key replacement
|
|
175
|
+
const blocks = Math.ceil(length / 16) + 1;
|
|
176
|
+
const raw = this.generateBlocks(blocks);
|
|
177
|
+
const output = raw.slice(0, length);
|
|
178
|
+
// Key replacement — mandatory forward secrecy (spec §9.4)
|
|
179
|
+
this.genKey = this.generateBlocks(2);
|
|
180
|
+
return output;
|
|
181
|
+
}
|
|
182
|
+
/** Reseed the generator — spec §9.4 */
|
|
183
|
+
reseed(seed) {
|
|
184
|
+
// genKey = SHA256(genKey ‖ seed)
|
|
185
|
+
this.genKey = this.sha.hash(concat(this.genKey, seed));
|
|
186
|
+
// Increment counter — makes it nonzero on first reseed, marking generator as seeded
|
|
187
|
+
this.incrementCounter();
|
|
188
|
+
this.lastReseed = Date.now();
|
|
189
|
+
}
|
|
190
|
+
/** Increment 16-byte little-endian counter. — spec §9.4 */
|
|
191
|
+
incrementCounter() {
|
|
192
|
+
for (let i = 0; i < 16; i++) {
|
|
193
|
+
if (++this.genCnt[i] !== 0)
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// ── Accumulator (spec §9.5) ───────────────────────────────────────────
|
|
198
|
+
/** Add an event to a pool via hash chaining: poolHash[i] = SHA256(poolHash[i] ‖ eventId ‖ data). */
|
|
199
|
+
addRandomEvent(data, poolIdx, entropyBits) {
|
|
200
|
+
// Encode eventId as 4 bytes little-endian
|
|
201
|
+
const id = new Uint8Array(4);
|
|
202
|
+
id[0] = this.eventId & 0xff;
|
|
203
|
+
id[1] = (this.eventId >>> 8) & 0xff;
|
|
204
|
+
id[2] = (this.eventId >>> 16) & 0xff;
|
|
205
|
+
id[3] = (this.eventId >>> 24) & 0xff;
|
|
206
|
+
this.eventId = (this.eventId + 1) >>> 0; // u32 wrap
|
|
207
|
+
// Chain: poolHash[i] = SHA256(poolHash[i] ‖ id ‖ data)
|
|
208
|
+
this.poolHash[poolIdx] = this.sha.hash(concat(concat(this.poolHash[poolIdx], id), data));
|
|
209
|
+
this.poolEntropy[poolIdx] += entropyBits;
|
|
210
|
+
this.entropyLevel += entropyBits;
|
|
211
|
+
}
|
|
212
|
+
// ── Initialization ────────────────────────────────────────────────────
|
|
213
|
+
initialize(entropy) {
|
|
214
|
+
// Initial seeding — crypto random per pool (spec §9.5)
|
|
215
|
+
for (let i = 0; i < Fortuna.NUM_POOLS * 4; i++) {
|
|
216
|
+
this.collectorCryptoRandom();
|
|
217
|
+
}
|
|
218
|
+
// Timing entropy
|
|
219
|
+
this.collectorTime();
|
|
220
|
+
// DOM entropy (browser only)
|
|
221
|
+
this.collectorDom();
|
|
222
|
+
// Extra entropy from caller
|
|
223
|
+
if (entropy) {
|
|
224
|
+
this.addRandomEvent(entropy, this.robin.rnd, entropy.length * 8);
|
|
225
|
+
this.robin.rnd = (this.robin.rnd + 1) % Fortuna.NUM_POOLS;
|
|
226
|
+
}
|
|
227
|
+
this.startCollectors();
|
|
228
|
+
}
|
|
229
|
+
// ── Collectors ────────────────────────────────────────────────────────
|
|
230
|
+
startCollectors() {
|
|
231
|
+
if (this.active)
|
|
232
|
+
return;
|
|
233
|
+
if (isBrowser) {
|
|
234
|
+
const target = typeof window !== 'undefined' ? window : document;
|
|
235
|
+
if (target) {
|
|
236
|
+
this.boundCollectors.click = this.collectorClick.bind(this);
|
|
237
|
+
this.boundCollectors.keydown = this.collectorKeyboard.bind(this);
|
|
238
|
+
this.boundCollectors.scroll = this.collectorScroll.bind(this);
|
|
239
|
+
this.boundCollectors.mousemove = this.throttle(this.collectorMouse, 50, this);
|
|
240
|
+
this.boundCollectors.devicemotion = this.throttle(this.collectorMotion, 100, this);
|
|
241
|
+
this.boundCollectors.deviceorientation = this.collectorMotion.bind(this);
|
|
242
|
+
this.boundCollectors.orientationchange = this.collectorMotion.bind(this);
|
|
243
|
+
this.boundCollectors.touchmove = this.throttle(this.collectorTouch, 50, this);
|
|
244
|
+
this.boundCollectors.touchstart = this.collectorTouch.bind(this);
|
|
245
|
+
this.boundCollectors.touchend = this.collectorTouch.bind(this);
|
|
246
|
+
this.boundCollectors.load = this.collectorTime.bind(this);
|
|
247
|
+
for (const [event, handler] of Object.entries(this.boundCollectors)) {
|
|
248
|
+
target.addEventListener(event, handler, true);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (isNode) {
|
|
253
|
+
// OS stats timer
|
|
254
|
+
this.timers.push(setInterval(() => this.collectNodeStats(), Fortuna.NODE_STATS_INTERVAL));
|
|
255
|
+
}
|
|
256
|
+
// Crypto timer — both environments
|
|
257
|
+
this.timers.push(setInterval(() => this.collectorCryptoRandom(), Fortuna.CRYPTO_INTERVAL));
|
|
258
|
+
this.active = true;
|
|
259
|
+
}
|
|
260
|
+
stopCollectors() {
|
|
261
|
+
if (!this.active)
|
|
262
|
+
return;
|
|
263
|
+
if (isBrowser && typeof window !== 'undefined') {
|
|
264
|
+
for (const [event, handler] of Object.entries(this.boundCollectors)) {
|
|
265
|
+
window.removeEventListener(event, handler, true);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
for (const timer of this.timers)
|
|
269
|
+
clearInterval(timer);
|
|
270
|
+
this.timers = [];
|
|
271
|
+
this.boundCollectors = {};
|
|
272
|
+
this.active = false;
|
|
273
|
+
}
|
|
274
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -- legacy throttle utility
|
|
275
|
+
throttle(fn, threshold, scope) {
|
|
276
|
+
let last;
|
|
277
|
+
let deferTimer;
|
|
278
|
+
return function (...args) {
|
|
279
|
+
const context = scope || this;
|
|
280
|
+
const now = Date.now();
|
|
281
|
+
if (last && now < last + threshold) {
|
|
282
|
+
clearTimeout(deferTimer);
|
|
283
|
+
deferTimer = setTimeout(() => {
|
|
284
|
+
last = now;
|
|
285
|
+
fn.apply(context, args);
|
|
286
|
+
}, threshold);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
last = now;
|
|
290
|
+
fn.apply(context, args);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
collectorKeyboard(ev) {
|
|
295
|
+
const key = ev.key || '';
|
|
296
|
+
const b = new Uint8Array([key.charCodeAt(0) || 0, (ev.timeStamp || 0) & 0xff]);
|
|
297
|
+
this.addRandomEvent(b, this.robin.kbd, 1);
|
|
298
|
+
this.robin.kbd = (this.robin.kbd + 1) % Fortuna.NUM_POOLS;
|
|
299
|
+
this.collectorTime();
|
|
300
|
+
}
|
|
301
|
+
collectorMouse(ev) {
|
|
302
|
+
const x = ev.clientX || 0, y = ev.clientY || 0;
|
|
303
|
+
this.addRandomEvent(new Uint8Array([x >>> 8, x & 0xff, y >>> 8, y & 0xff]), this.robin.mouse, 2);
|
|
304
|
+
this.robin.mouse = (this.robin.mouse + 1) % Fortuna.NUM_POOLS;
|
|
305
|
+
}
|
|
306
|
+
collectorClick(ev) {
|
|
307
|
+
const x = ev.clientX || 0, y = ev.clientY || 0;
|
|
308
|
+
this.addRandomEvent(new Uint8Array([x >>> 8, x & 0xff, y >>> 8, y & 0xff]), this.robin.mouse, 2);
|
|
309
|
+
this.robin.mouse = (this.robin.mouse + 1) % Fortuna.NUM_POOLS;
|
|
310
|
+
this.collectorTime();
|
|
311
|
+
}
|
|
312
|
+
collectorTouch(ev) {
|
|
313
|
+
const touch = ev.touches[0] || ev.changedTouches[0];
|
|
314
|
+
if (!touch)
|
|
315
|
+
return;
|
|
316
|
+
const x = touch.pageX || touch.clientX || 0;
|
|
317
|
+
const y = touch.pageY || touch.clientY || 0;
|
|
318
|
+
this.addRandomEvent(new Uint8Array([x >>> 8, x & 0xff, y >>> 8, y & 0xff]), this.robin.touch, 2);
|
|
319
|
+
this.robin.touch = (this.robin.touch + 1) % Fortuna.NUM_POOLS;
|
|
320
|
+
this.collectorTime();
|
|
321
|
+
}
|
|
322
|
+
collectorScroll() {
|
|
323
|
+
if (typeof window === 'undefined')
|
|
324
|
+
return;
|
|
325
|
+
const x = window.scrollX || 0, y = window.scrollY || 0;
|
|
326
|
+
this.addRandomEvent(new Uint8Array([x >>> 8, x & 0xff, y >>> 8, y & 0xff]), this.robin.scroll, 1);
|
|
327
|
+
this.robin.scroll = (this.robin.scroll + 1) % Fortuna.NUM_POOLS;
|
|
328
|
+
}
|
|
329
|
+
collectorMotion(ev) {
|
|
330
|
+
const motion = ev;
|
|
331
|
+
const orient = ev;
|
|
332
|
+
if (motion.accelerationIncludingGravity) {
|
|
333
|
+
const a = motion.accelerationIncludingGravity;
|
|
334
|
+
const x = a.x || 0, y = a.y || 0, z = a.z || 0;
|
|
335
|
+
this.addRandomEvent(new Uint8Array([(x * 100) & 0xff, (y * 100) & 0xff, (z * 100) & 0xff]), this.robin.motion, 3);
|
|
336
|
+
}
|
|
337
|
+
if (typeof orient.alpha === 'number' && typeof orient.beta === 'number' && typeof orient.gamma === 'number') {
|
|
338
|
+
this.addRandomEvent(utf8ToBytes(orient.alpha.toString() + orient.beta.toString() + orient.gamma.toString()), this.robin.motion, 3);
|
|
339
|
+
}
|
|
340
|
+
this.robin.motion = (this.robin.motion + 1) % Fortuna.NUM_POOLS;
|
|
341
|
+
}
|
|
342
|
+
collectorTime() {
|
|
343
|
+
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
|
|
344
|
+
this.addRandomEvent(utf8ToBytes(performance.now().toString()), this.robin.time, 2);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
const t = Date.now();
|
|
348
|
+
const b = new Uint8Array(4);
|
|
349
|
+
b[0] = t & 0xff;
|
|
350
|
+
b[1] = (t >>> 8) & 0xff;
|
|
351
|
+
b[2] = (t >>> 16) & 0xff;
|
|
352
|
+
b[3] = (t >>> 24) & 0xff;
|
|
353
|
+
this.addRandomEvent(b, this.robin.time, 2);
|
|
354
|
+
}
|
|
355
|
+
this.robin.time = (this.robin.time + 1) % Fortuna.NUM_POOLS;
|
|
356
|
+
}
|
|
357
|
+
collectorDom() {
|
|
358
|
+
if (typeof document !== 'undefined' && document.documentElement) {
|
|
359
|
+
this.addRandomEvent(this.sha.hash(utf8ToBytes(document.documentElement.innerHTML)), this.robin.dom, 2);
|
|
360
|
+
this.robin.dom = (this.robin.dom + 1) % Fortuna.NUM_POOLS;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
collectorCryptoRandom() {
|
|
364
|
+
try {
|
|
365
|
+
const rnd = new Uint8Array(128);
|
|
366
|
+
if (typeof globalThis.crypto !== 'undefined' && typeof globalThis.crypto.getRandomValues === 'function') {
|
|
367
|
+
globalThis.crypto.getRandomValues(rnd);
|
|
368
|
+
}
|
|
369
|
+
else if (isNode) {
|
|
370
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
371
|
+
const nodeCrypto = require('node:crypto');
|
|
372
|
+
const buf = nodeCrypto.randomBytes(128);
|
|
373
|
+
rnd.set(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
return; // no crypto source available
|
|
377
|
+
}
|
|
378
|
+
this.addRandomEvent(rnd, this.robin.rnd, 1024);
|
|
379
|
+
this.robin.rnd = (this.robin.rnd + 1) % Fortuna.NUM_POOLS;
|
|
380
|
+
}
|
|
381
|
+
catch { /* crypto may not be available */ }
|
|
382
|
+
}
|
|
383
|
+
captureHrtime() {
|
|
384
|
+
try {
|
|
385
|
+
const hr = process.hrtime.bigint();
|
|
386
|
+
const hrBytes = new Uint8Array(8);
|
|
387
|
+
for (let i = 0; i < 8; i++)
|
|
388
|
+
hrBytes[i] = Number((hr >> BigInt(i * 8)) & 0xffn);
|
|
389
|
+
this.addRandomEvent(hrBytes, this.robin.time, 8);
|
|
390
|
+
this.robin.time = (this.robin.time + 1) % Fortuna.NUM_POOLS;
|
|
391
|
+
}
|
|
392
|
+
catch { /* hrtime may not be available */ }
|
|
393
|
+
}
|
|
394
|
+
collectNodeStats() {
|
|
395
|
+
try {
|
|
396
|
+
// hrtime — nanosecond scheduling jitter
|
|
397
|
+
const hr = process.hrtime.bigint();
|
|
398
|
+
const hrBytes = new Uint8Array(8);
|
|
399
|
+
for (let i = 0; i < 8; i++)
|
|
400
|
+
hrBytes[i] = Number((hr >> BigInt(i * 8)) & 0xffn);
|
|
401
|
+
this.addRandomEvent(hrBytes, this.robin.time, 8);
|
|
402
|
+
this.robin.time = (this.robin.time + 1) % Fortuna.NUM_POOLS;
|
|
403
|
+
// cpuUsage — user + system CPU microseconds
|
|
404
|
+
const cpu = process.cpuUsage();
|
|
405
|
+
const cpuBytes = new Uint8Array(8);
|
|
406
|
+
cpuBytes[0] = cpu.user & 0xff;
|
|
407
|
+
cpuBytes[1] = (cpu.user >>> 8) & 0xff;
|
|
408
|
+
cpuBytes[2] = (cpu.user >>> 16) & 0xff;
|
|
409
|
+
cpuBytes[3] = (cpu.user >>> 24) & 0xff;
|
|
410
|
+
cpuBytes[4] = cpu.system & 0xff;
|
|
411
|
+
cpuBytes[5] = (cpu.system >>> 8) & 0xff;
|
|
412
|
+
cpuBytes[6] = (cpu.system >>> 16) & 0xff;
|
|
413
|
+
cpuBytes[7] = (cpu.system >>> 24) & 0xff;
|
|
414
|
+
this.addRandomEvent(cpuBytes, this.robin.rnd, 2);
|
|
415
|
+
this.robin.rnd = (this.robin.rnd + 1) % Fortuna.NUM_POOLS;
|
|
416
|
+
// memoryUsage — heapUsed changes constantly
|
|
417
|
+
const mem = process.memoryUsage();
|
|
418
|
+
const memVal = mem.heapUsed;
|
|
419
|
+
const memBytes = new Uint8Array(4);
|
|
420
|
+
memBytes[0] = memVal & 0xff;
|
|
421
|
+
memBytes[1] = (memVal >>> 8) & 0xff;
|
|
422
|
+
memBytes[2] = (memVal >>> 16) & 0xff;
|
|
423
|
+
memBytes[3] = (memVal >>> 24) & 0xff;
|
|
424
|
+
this.addRandomEvent(memBytes, this.robin.rnd, 1);
|
|
425
|
+
this.robin.rnd = (this.robin.rnd + 1) % Fortuna.NUM_POOLS;
|
|
426
|
+
// loadavg — slow-changing but real system state
|
|
427
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
428
|
+
const os = require('node:os');
|
|
429
|
+
const la = os.loadavg();
|
|
430
|
+
const laStr = la.map((n) => Math.round(n * 1000).toString()).join('');
|
|
431
|
+
this.addRandomEvent(utf8ToBytes(laStr), this.robin.time, 1);
|
|
432
|
+
this.robin.time = (this.robin.time + 1) % Fortuna.NUM_POOLS;
|
|
433
|
+
// freemem — changes with allocation activity
|
|
434
|
+
const fm = os.freemem();
|
|
435
|
+
const fmBytes = new Uint8Array(4);
|
|
436
|
+
fmBytes[0] = fm & 0xff;
|
|
437
|
+
fmBytes[1] = (fm >>> 8) & 0xff;
|
|
438
|
+
fmBytes[2] = (fm >>> 16) & 0xff;
|
|
439
|
+
fmBytes[3] = (fm >>> 24) & 0xff;
|
|
440
|
+
this.addRandomEvent(fmBytes, this.robin.rnd, 1);
|
|
441
|
+
this.robin.rnd = (this.robin.rnd + 1) % Fortuna.NUM_POOLS;
|
|
442
|
+
}
|
|
443
|
+
catch { /* Node APIs may not be available */ }
|
|
444
|
+
}
|
|
445
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Module, Mode, InitOpts } from './init.js';
|
|
2
|
+
export declare function init(modules: Module | Module[], mode?: Mode, opts?: InitOpts): Promise<void>;
|
|
3
|
+
export { type Module, type Mode, type InitOpts, isInitialized, _resetForTesting } from './init.js';
|
|
4
|
+
export { serpentInit, SerpentSeal, Serpent, SerpentCtr, SerpentCbc, SerpentStream, SerpentStreamPool, SerpentStreamSealer, SerpentStreamOpener, SerpentStreamEncoder, SerpentStreamDecoder, _serpentReady } from './serpent/index.js';
|
|
5
|
+
export type { StreamPoolOpts } from './serpent/index.js';
|
|
6
|
+
export { chacha20Init, ChaCha20, Poly1305, ChaCha20Poly1305, XChaCha20Poly1305, _chachaReady } from './chacha20/index.js';
|
|
7
|
+
export { XChaCha20Poly1305Pool } from './chacha20/pool.js';
|
|
8
|
+
export type { PoolOpts } from './chacha20/pool.js';
|
|
9
|
+
export { sha2Init, SHA256, SHA512, SHA384, HMAC_SHA256, HMAC_SHA512, HMAC_SHA384, HKDF_SHA256, HKDF_SHA512, _sha2Ready } from './sha2/index.js';
|
|
10
|
+
export { sha3Init, SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256, _sha3Ready } from './sha3/index.js';
|
|
11
|
+
export { Fortuna } from './fortuna.js';
|
|
12
|
+
export type { Hash, KeyedHash, Blockcipher, Streamcipher, AEAD } from './types.js';
|
|
13
|
+
export { hexToBytes, bytesToHex, utf8ToBytes, bytesToUtf8, base64ToBytes, bytesToBase64, constantTimeEqual, wipe, xor, concat, randomBytes, } from './utils.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
// Root barrel — re-exports everything
|
|
23
|
+
import { serpentInit } from './serpent/index.js';
|
|
24
|
+
import { chacha20Init } from './chacha20/index.js';
|
|
25
|
+
import { sha2Init } from './sha2/index.js';
|
|
26
|
+
import { sha3Init } from './sha3/index.js';
|
|
27
|
+
const _dispatchers = {
|
|
28
|
+
serpent: serpentInit,
|
|
29
|
+
chacha20: chacha20Init,
|
|
30
|
+
sha2: sha2Init,
|
|
31
|
+
sha3: sha3Init,
|
|
32
|
+
};
|
|
33
|
+
export async function init(modules, mode = 'embedded', opts) {
|
|
34
|
+
const list = Array.isArray(modules) ? modules : [modules];
|
|
35
|
+
await Promise.all(list.map(mod => _dispatchers[mod](mode, opts)));
|
|
36
|
+
}
|
|
37
|
+
export { isInitialized, _resetForTesting } from './init.js';
|
|
38
|
+
export { serpentInit, SerpentSeal, Serpent, SerpentCtr, SerpentCbc, SerpentStream, SerpentStreamPool, SerpentStreamSealer, SerpentStreamOpener, SerpentStreamEncoder, SerpentStreamDecoder, _serpentReady } from './serpent/index.js';
|
|
39
|
+
export { chacha20Init, ChaCha20, Poly1305, ChaCha20Poly1305, XChaCha20Poly1305, _chachaReady } from './chacha20/index.js';
|
|
40
|
+
export { XChaCha20Poly1305Pool } from './chacha20/pool.js';
|
|
41
|
+
export { sha2Init, SHA256, SHA512, SHA384, HMAC_SHA256, HMAC_SHA512, HMAC_SHA384, HKDF_SHA256, HKDF_SHA512, _sha2Ready } from './sha2/index.js';
|
|
42
|
+
export { sha3Init, SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256, _sha3Ready } from './sha3/index.js';
|
|
43
|
+
export { Fortuna } from './fortuna.js';
|
|
44
|
+
export { hexToBytes, bytesToHex, utf8ToBytes, bytesToUtf8, base64ToBytes, bytesToBase64, constantTimeEqual, wipe, xor, concat, randomBytes, } from './utils.js';
|
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type Module = 'serpent' | 'chacha20' | 'sha2' | 'sha3';
|
|
2
|
+
export type Mode = 'embedded' | 'streaming' | 'manual';
|
|
3
|
+
export interface InitOpts {
|
|
4
|
+
wasmUrl?: URL | string;
|
|
5
|
+
wasmBinary?: Partial<Record<Module, Uint8Array | ArrayBuffer>>;
|
|
6
|
+
}
|
|
7
|
+
export declare function initModule(mod: Module, embeddedThunk: () => Promise<string>, mode?: Mode, opts?: InitOpts): Promise<void>;
|
|
8
|
+
export declare function getInstance(mod: Module): WebAssembly.Instance;
|
|
9
|
+
export declare function isInitialized(mod: Module): boolean;
|
|
10
|
+
/** Reset all cached instances — for testing only */
|
|
11
|
+
export declare function _resetForTesting(): void;
|
package/dist/init.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Module-scope cache: one WebAssembly.Instance per module
|
|
2
|
+
const instances = new Map();
|
|
3
|
+
// Map from public module name to WASM filename
|
|
4
|
+
const WASM_FILES = {
|
|
5
|
+
serpent: 'serpent.wasm',
|
|
6
|
+
chacha20: 'chacha.wasm',
|
|
7
|
+
sha2: 'sha2.wasm',
|
|
8
|
+
sha3: 'sha3.wasm',
|
|
9
|
+
};
|
|
10
|
+
export async function initModule(mod, embeddedThunk, mode = 'embedded', opts) {
|
|
11
|
+
if (instances.has(mod))
|
|
12
|
+
return;
|
|
13
|
+
let instance;
|
|
14
|
+
if (mode === 'embedded') {
|
|
15
|
+
const { loadEmbedded } = await import('./loader.js');
|
|
16
|
+
instance = await loadEmbedded(embeddedThunk);
|
|
17
|
+
}
|
|
18
|
+
else if (mode === 'streaming') {
|
|
19
|
+
if (!opts?.wasmUrl)
|
|
20
|
+
throw new Error('leviathan-crypto: streaming mode requires wasmUrl');
|
|
21
|
+
const { loadStreaming } = await import('./loader.js');
|
|
22
|
+
instance = await loadStreaming(mod, opts.wasmUrl, WASM_FILES[mod]);
|
|
23
|
+
}
|
|
24
|
+
else if (mode === 'manual') {
|
|
25
|
+
const binary = opts?.wasmBinary?.[mod];
|
|
26
|
+
if (!binary)
|
|
27
|
+
throw new Error(`leviathan-crypto: manual mode requires wasmBinary['${mod}']`);
|
|
28
|
+
const { loadManual } = await import('./loader.js');
|
|
29
|
+
instance = await loadManual(binary);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
throw new Error(`leviathan-crypto: unknown mode '${mode}'`);
|
|
33
|
+
}
|
|
34
|
+
instances.set(mod, instance);
|
|
35
|
+
}
|
|
36
|
+
export function getInstance(mod) {
|
|
37
|
+
const inst = instances.get(mod);
|
|
38
|
+
if (!inst) {
|
|
39
|
+
throw new Error(`leviathan-crypto: call init(['${mod}']) before using this class`);
|
|
40
|
+
}
|
|
41
|
+
return inst;
|
|
42
|
+
}
|
|
43
|
+
export function isInitialized(mod) {
|
|
44
|
+
return instances.has(mod);
|
|
45
|
+
}
|
|
46
|
+
/** Reset all cached instances — for testing only */
|
|
47
|
+
export function _resetForTesting() {
|
|
48
|
+
instances.clear();
|
|
49
|
+
}
|
package/dist/loader.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Module } from './init.js';
|
|
2
|
+
export declare function loadEmbedded(thunk: () => Promise<string>): Promise<WebAssembly.Instance>;
|
|
3
|
+
export declare function loadStreaming(_mod: Module, baseUrl: URL | string, filename: string): Promise<WebAssembly.Instance>;
|
|
4
|
+
export declare function loadManual(binary: Uint8Array | ArrayBuffer): Promise<WebAssembly.Instance>;
|
package/dist/loader.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function base64ToBytes(b64) {
|
|
2
|
+
if (typeof atob === 'function') {
|
|
3
|
+
const raw = atob(b64);
|
|
4
|
+
const out = new Uint8Array(raw.length);
|
|
5
|
+
for (let i = 0; i < raw.length; i++)
|
|
6
|
+
out[i] = raw.charCodeAt(i);
|
|
7
|
+
return out;
|
|
8
|
+
}
|
|
9
|
+
return new Uint8Array(Buffer.from(b64, 'base64'));
|
|
10
|
+
}
|
|
11
|
+
async function instantiateFromBytes(bytes) {
|
|
12
|
+
const result = await WebAssembly.instantiate(bytes.buffer, { env: { memory: new WebAssembly.Memory({ initial: 3, maximum: 3 }) } });
|
|
13
|
+
return result.instance;
|
|
14
|
+
}
|
|
15
|
+
export async function loadEmbedded(thunk) {
|
|
16
|
+
const b64 = await thunk();
|
|
17
|
+
const bytes = base64ToBytes(b64);
|
|
18
|
+
return instantiateFromBytes(bytes);
|
|
19
|
+
}
|
|
20
|
+
export async function loadStreaming(_mod, baseUrl, filename) {
|
|
21
|
+
const url = new URL(filename, baseUrl).href;
|
|
22
|
+
const result = await WebAssembly.instantiateStreaming(fetch(url), {
|
|
23
|
+
env: { memory: new WebAssembly.Memory({ initial: 3, maximum: 3 }) },
|
|
24
|
+
});
|
|
25
|
+
return result.instance;
|
|
26
|
+
}
|
|
27
|
+
export async function loadManual(binary) {
|
|
28
|
+
const bytes = binary instanceof ArrayBuffer ? new Uint8Array(binary) : binary;
|
|
29
|
+
return instantiateFromBytes(bytes);
|
|
30
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Mode, InitOpts } from '../init.js';
|
|
2
|
+
export declare function serpentInit(mode?: Mode, opts?: InitOpts): Promise<void>;
|
|
3
|
+
export declare class Serpent {
|
|
4
|
+
private readonly x;
|
|
5
|
+
constructor();
|
|
6
|
+
loadKey(key: Uint8Array): void;
|
|
7
|
+
encryptBlock(plaintext: Uint8Array): Uint8Array;
|
|
8
|
+
decryptBlock(ciphertext: Uint8Array): Uint8Array;
|
|
9
|
+
dispose(): void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Serpent-256 in CTR mode.
|
|
13
|
+
*
|
|
14
|
+
* **WARNING: CTR mode is unauthenticated.** An attacker can flip ciphertext
|
|
15
|
+
* bits without detection. Always pair with HMAC-SHA256 (Encrypt-then-MAC)
|
|
16
|
+
* or use `XChaCha20Poly1305` instead.
|
|
17
|
+
*/
|
|
18
|
+
export declare class SerpentCtr {
|
|
19
|
+
private readonly x;
|
|
20
|
+
constructor(opts?: {
|
|
21
|
+
dangerUnauthenticated: true;
|
|
22
|
+
});
|
|
23
|
+
beginEncrypt(key: Uint8Array, nonce: Uint8Array): void;
|
|
24
|
+
encryptChunk(chunk: Uint8Array): Uint8Array;
|
|
25
|
+
beginDecrypt(key: Uint8Array, nonce: Uint8Array): void;
|
|
26
|
+
decryptChunk(chunk: Uint8Array): Uint8Array;
|
|
27
|
+
dispose(): void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Serpent-256 in CBC mode with PKCS7 padding.
|
|
31
|
+
*
|
|
32
|
+
* **WARNING: CBC mode is unauthenticated.** Always authenticate the output
|
|
33
|
+
* with HMAC-SHA256 (Encrypt-then-MAC) or use `XChaCha20Poly1305` instead.
|
|
34
|
+
*/
|
|
35
|
+
export declare class SerpentCbc {
|
|
36
|
+
private readonly x;
|
|
37
|
+
constructor(opts?: {
|
|
38
|
+
dangerUnauthenticated: true;
|
|
39
|
+
});
|
|
40
|
+
private get mem();
|
|
41
|
+
/**
|
|
42
|
+
* Encrypt plaintext with Serpent-256 CBC + PKCS7 padding.
|
|
43
|
+
*
|
|
44
|
+
* @param key 16, 24, or 32 bytes
|
|
45
|
+
* @param iv 16 bytes — must be random and unique per (key, message)
|
|
46
|
+
* @param plaintext any length — PKCS7 padding applied automatically
|
|
47
|
+
* @returns ciphertext (length = ceil((plaintext.length + 1) / 16) * 16)
|
|
48
|
+
*/
|
|
49
|
+
encrypt(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array): Uint8Array;
|
|
50
|
+
/**
|
|
51
|
+
* Decrypt Serpent-256 CBC + PKCS7.
|
|
52
|
+
* Throws if ciphertext length is not a non-zero multiple of 16 or PKCS7 is invalid.
|
|
53
|
+
*/
|
|
54
|
+
decrypt(key: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array): Uint8Array;
|
|
55
|
+
dispose(): void;
|
|
56
|
+
private _loadKey;
|
|
57
|
+
private _setIv;
|
|
58
|
+
}
|
|
59
|
+
export { SerpentSeal } from './seal.js';
|
|
60
|
+
export { SerpentStream, sealChunk, openChunk } from './stream.js';
|
|
61
|
+
export { SerpentStreamPool } from './stream-pool.js';
|
|
62
|
+
export type { StreamPoolOpts } from './stream-pool.js';
|
|
63
|
+
export { SerpentStreamSealer, SerpentStreamOpener } from './stream-sealer.js';
|
|
64
|
+
export { SerpentStreamEncoder, SerpentStreamDecoder } from './stream-encoder.js';
|
|
65
|
+
export declare function _serpentReady(): boolean;
|