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,202 @@
|
|
|
1
|
+
// / <reference lib="webworker" />
|
|
2
|
+
// src/ts/serpent/pool-worker.ts
|
|
3
|
+
//
|
|
4
|
+
// Worker for SealStreamPool with SerpentCipher.
|
|
5
|
+
// Holds 3 derived keys (enc/mac/iv) and raw WASM instances.
|
|
6
|
+
// Direct WASM calls — no initModule (avoids same-thread module cache conflicts
|
|
7
|
+
// in @vitest/web-worker test environment).
|
|
8
|
+
import { constantTimeEqual, wipe, concat } from '../utils.js';
|
|
9
|
+
import { AuthenticationError } from '../errors.js';
|
|
10
|
+
let sha2;
|
|
11
|
+
let serpent;
|
|
12
|
+
let keys;
|
|
13
|
+
function hmacSha256(key, msg) {
|
|
14
|
+
const x = sha2;
|
|
15
|
+
let k = key;
|
|
16
|
+
if (k.length > 64) {
|
|
17
|
+
x.sha256Init();
|
|
18
|
+
feedSha2(k);
|
|
19
|
+
x.sha256Final();
|
|
20
|
+
const mem = new Uint8Array(x.memory.buffer);
|
|
21
|
+
k = mem.slice(x.getSha256OutOffset(), x.getSha256OutOffset() + 32);
|
|
22
|
+
}
|
|
23
|
+
const mem = new Uint8Array(x.memory.buffer);
|
|
24
|
+
mem.set(k, x.getSha256InputOffset());
|
|
25
|
+
x.hmac256Init(k.length);
|
|
26
|
+
feedHmac(msg);
|
|
27
|
+
x.hmac256Final();
|
|
28
|
+
return new Uint8Array(x.memory.buffer).slice(x.getSha256OutOffset(), x.getSha256OutOffset() + 32);
|
|
29
|
+
}
|
|
30
|
+
function feedSha2(data) {
|
|
31
|
+
const x = sha2;
|
|
32
|
+
const mem = new Uint8Array(x.memory.buffer);
|
|
33
|
+
const off = x.getSha256InputOffset();
|
|
34
|
+
let pos = 0;
|
|
35
|
+
while (pos < data.length) {
|
|
36
|
+
const n = Math.min(data.length - pos, 64);
|
|
37
|
+
mem.set(data.subarray(pos, pos + n), off);
|
|
38
|
+
x.sha256Update(n);
|
|
39
|
+
pos += n;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function feedHmac(data) {
|
|
43
|
+
const x = sha2;
|
|
44
|
+
const mem = new Uint8Array(x.memory.buffer);
|
|
45
|
+
const off = x.getSha256InputOffset();
|
|
46
|
+
let pos = 0;
|
|
47
|
+
while (pos < data.length) {
|
|
48
|
+
const n = Math.min(data.length - pos, 64);
|
|
49
|
+
mem.set(data.subarray(pos, pos + n), off);
|
|
50
|
+
x.hmac256Update(n);
|
|
51
|
+
pos += n;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function pkcs7Pad(data) {
|
|
55
|
+
const padLen = 16 - (data.length % 16);
|
|
56
|
+
const out = new Uint8Array(data.length + padLen);
|
|
57
|
+
out.set(data);
|
|
58
|
+
out.fill(padLen, data.length);
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
// pkcs7Strip is only called after HMAC authentication succeeds (verify-then-decrypt).
|
|
62
|
+
// The early throw on invalid padLen is not a padding oracle in this context —
|
|
63
|
+
// the HMAC check is the oracle gate and runs in constant time before this point.
|
|
64
|
+
// If you move this call to a pre-auth site, revisit the timing properties.
|
|
65
|
+
function pkcs7Strip(data) {
|
|
66
|
+
if (data.length === 0)
|
|
67
|
+
throw new RangeError('empty ciphertext');
|
|
68
|
+
const padLen = data[data.length - 1];
|
|
69
|
+
if (padLen === 0 || padLen > 16)
|
|
70
|
+
throw new RangeError('invalid PKCS7 padding');
|
|
71
|
+
if (padLen > data.length)
|
|
72
|
+
throw new RangeError('invalid PKCS7 padding');
|
|
73
|
+
let bad = 0;
|
|
74
|
+
for (let i = data.length - padLen; i < data.length; i++)
|
|
75
|
+
bad |= data[i] ^ padLen;
|
|
76
|
+
if (bad !== 0)
|
|
77
|
+
throw new RangeError('invalid PKCS7 padding');
|
|
78
|
+
return data.subarray(0, data.length - padLen);
|
|
79
|
+
}
|
|
80
|
+
function cbcEncrypt(encKey, iv, plaintext) {
|
|
81
|
+
const s = serpent;
|
|
82
|
+
const mem = new Uint8Array(s.memory.buffer);
|
|
83
|
+
mem.set(encKey, s.getKeyOffset());
|
|
84
|
+
s.loadKey(encKey.length);
|
|
85
|
+
mem.set(iv, s.getCbcIvOffset());
|
|
86
|
+
const padded = pkcs7Pad(plaintext);
|
|
87
|
+
mem.set(padded, s.getChunkPtOffset());
|
|
88
|
+
s.cbcEncryptChunk(padded.length);
|
|
89
|
+
return new Uint8Array(s.memory.buffer).slice(s.getChunkCtOffset(), s.getChunkCtOffset() + padded.length);
|
|
90
|
+
}
|
|
91
|
+
function cbcDecrypt(encKey, iv, ct) {
|
|
92
|
+
const s = serpent;
|
|
93
|
+
const mem = new Uint8Array(s.memory.buffer);
|
|
94
|
+
mem.set(encKey, s.getKeyOffset());
|
|
95
|
+
s.loadKey(encKey.length);
|
|
96
|
+
mem.set(iv, s.getCbcIvOffset());
|
|
97
|
+
mem.set(ct, s.getChunkCtOffset());
|
|
98
|
+
s.cbcDecryptChunk(ct.length);
|
|
99
|
+
const raw = new Uint8Array(s.memory.buffer).slice(s.getChunkPtOffset(), s.getChunkPtOffset() + ct.length);
|
|
100
|
+
return pkcs7Strip(raw);
|
|
101
|
+
}
|
|
102
|
+
self.onmessage = async (e) => {
|
|
103
|
+
const msg = e.data;
|
|
104
|
+
if (msg.type === 'init') {
|
|
105
|
+
try {
|
|
106
|
+
const sha2Mem = new WebAssembly.Memory({ initial: 3, maximum: 3 });
|
|
107
|
+
const sha2Inst = await WebAssembly.instantiate(msg.modules.sha2, { env: { memory: sha2Mem } });
|
|
108
|
+
sha2 = sha2Inst.exports;
|
|
109
|
+
const serpentMem = new WebAssembly.Memory({ initial: 3, maximum: 3 });
|
|
110
|
+
const serpentInst = await WebAssembly.instantiate(msg.modules.serpent, { env: { memory: serpentMem } });
|
|
111
|
+
serpent = serpentInst.exports;
|
|
112
|
+
keys = new Uint8Array(msg.derivedKeyBytes);
|
|
113
|
+
if (keys.length !== 96)
|
|
114
|
+
throw new Error(`expected 96 derived key bytes (got ${keys.length})`);
|
|
115
|
+
msg.derivedKeyBytes.fill(0);
|
|
116
|
+
self.postMessage({ type: 'ready' });
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
self.postMessage({ type: 'error', id: -1, message: err.message, isAuthError: false });
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (msg.type === 'wipe') {
|
|
124
|
+
if (keys)
|
|
125
|
+
keys.fill(0);
|
|
126
|
+
keys = undefined;
|
|
127
|
+
if (sha2)
|
|
128
|
+
sha2.wipeBuffers();
|
|
129
|
+
if (serpent)
|
|
130
|
+
serpent.wipeBuffers();
|
|
131
|
+
sha2 = undefined;
|
|
132
|
+
serpent = undefined;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (!keys || !sha2 || !serpent) {
|
|
136
|
+
self.postMessage({ type: 'error', id: msg.id, message: 'worker not initialized', isAuthError: false });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const { id, op, counterNonce, data, aad } = msg;
|
|
141
|
+
const aadBytes = aad ?? new Uint8Array(0);
|
|
142
|
+
const jobKey = msg.derivedKeyBytes ?? keys;
|
|
143
|
+
const encKey = jobKey.subarray(0, 32);
|
|
144
|
+
const macKey = jobKey.subarray(32, 64);
|
|
145
|
+
const ivKey = jobKey.subarray(64, 96);
|
|
146
|
+
let result;
|
|
147
|
+
if (op === 'seal') {
|
|
148
|
+
const ivFull = hmacSha256(ivKey, counterNonce);
|
|
149
|
+
const iv = ivFull.slice(0, 16);
|
|
150
|
+
wipe(ivFull);
|
|
151
|
+
const ct = cbcEncrypt(encKey, iv, data);
|
|
152
|
+
const aadLenBuf = new Uint8Array(4);
|
|
153
|
+
new DataView(aadLenBuf.buffer).setUint32(0, aadBytes.length, false);
|
|
154
|
+
const tagInput = concat(counterNonce, aadLenBuf, aadBytes, ct);
|
|
155
|
+
const tag = hmacSha256(macKey, tagInput);
|
|
156
|
+
result = concat(ct, tag);
|
|
157
|
+
wipe(iv);
|
|
158
|
+
wipe(tagInput);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
const ct = data.subarray(0, data.length - 32);
|
|
162
|
+
const receivedTag = data.subarray(data.length - 32);
|
|
163
|
+
const ivFull = hmacSha256(ivKey, counterNonce);
|
|
164
|
+
const iv = ivFull.slice(0, 16);
|
|
165
|
+
wipe(ivFull);
|
|
166
|
+
const aadLenBuf = new Uint8Array(4);
|
|
167
|
+
new DataView(aadLenBuf.buffer).setUint32(0, aadBytes.length, false);
|
|
168
|
+
const tagInput = concat(counterNonce, aadLenBuf, aadBytes, ct);
|
|
169
|
+
const expectedTag = hmacSha256(macKey, tagInput);
|
|
170
|
+
// CRITICAL: verify HMAC before decrypting (Vaudenay 2002)
|
|
171
|
+
if (!constantTimeEqual(expectedTag, receivedTag)) {
|
|
172
|
+
wipe(iv);
|
|
173
|
+
wipe(tagInput);
|
|
174
|
+
wipe(expectedTag);
|
|
175
|
+
throw new AuthenticationError('serpent');
|
|
176
|
+
}
|
|
177
|
+
wipe(tagInput);
|
|
178
|
+
wipe(expectedTag);
|
|
179
|
+
result = cbcDecrypt(encKey, iv, ct);
|
|
180
|
+
wipe(iv);
|
|
181
|
+
}
|
|
182
|
+
const transfer = result.buffer instanceof ArrayBuffer ? [result.buffer] : [];
|
|
183
|
+
self.postMessage({ type: 'result', id, data: result }, { transfer });
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
const isAuth = err instanceof AuthenticationError;
|
|
187
|
+
self.postMessage({
|
|
188
|
+
type: 'error', id: msg.id,
|
|
189
|
+
message: err.message,
|
|
190
|
+
cipher: isAuth ? 'serpent' : undefined,
|
|
191
|
+
isAuthError: isAuth,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
if (msg.derivedKeyBytes)
|
|
196
|
+
msg.derivedKeyBytes.fill(0);
|
|
197
|
+
if (sha2)
|
|
198
|
+
sha2.wipeBuffers();
|
|
199
|
+
if (serpent)
|
|
200
|
+
serpent.wipeBuffers();
|
|
201
|
+
}
|
|
202
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serpent-256 in CBC mode with PKCS7 padding.
|
|
3
|
+
*
|
|
4
|
+
* **WARNING: CBC mode is unauthenticated.** Always authenticate the output
|
|
5
|
+
* with HMAC-SHA256 (Encrypt-then-MAC) or use `XChaCha20Poly1305` instead.
|
|
6
|
+
*/
|
|
7
|
+
export declare class SerpentCbc {
|
|
8
|
+
private readonly x;
|
|
9
|
+
constructor(opts?: {
|
|
10
|
+
dangerUnauthenticated: true;
|
|
11
|
+
});
|
|
12
|
+
private get mem();
|
|
13
|
+
/**
|
|
14
|
+
* Encrypt plaintext with Serpent-256 CBC + PKCS7 padding.
|
|
15
|
+
*
|
|
16
|
+
* @param key 16, 24, or 32 bytes
|
|
17
|
+
* @param iv 16 bytes — must be random and unique per (key, message)
|
|
18
|
+
* @param plaintext any length — PKCS7 padding applied automatically
|
|
19
|
+
* @returns ciphertext (length = ceil((plaintext.length + 1) / 16) * 16)
|
|
20
|
+
*/
|
|
21
|
+
encrypt(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array): Uint8Array;
|
|
22
|
+
/**
|
|
23
|
+
* Decrypt Serpent-256 CBC + PKCS7.
|
|
24
|
+
* Throws if ciphertext length is not a non-zero multiple of 16 or PKCS7 is invalid.
|
|
25
|
+
*/
|
|
26
|
+
decrypt(key: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array): Uint8Array;
|
|
27
|
+
dispose(): void;
|
|
28
|
+
private _loadKey;
|
|
29
|
+
private _setIv;
|
|
30
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
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/serpent/serpent-cbc.ts
|
|
23
|
+
//
|
|
24
|
+
// SerpentCbc — Serpent-256 CBC + PKCS7, internal module.
|
|
25
|
+
// Extracted to break the cipher-suite.ts ↔ index.ts circular dependency.
|
|
26
|
+
// Import from here directly; index.ts re-exports for the public API surface.
|
|
27
|
+
import { getInstance } from '../init.js';
|
|
28
|
+
function getExports() {
|
|
29
|
+
return getInstance('serpent').exports;
|
|
30
|
+
}
|
|
31
|
+
// ── PKCS7 helpers ────────────────────────────────────────────────────────────
|
|
32
|
+
function pkcs7Pad(data) {
|
|
33
|
+
const padLen = 16 - (data.length % 16); // 1..16
|
|
34
|
+
const out = new Uint8Array(data.length + padLen);
|
|
35
|
+
out.set(data);
|
|
36
|
+
out.fill(padLen, data.length);
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
// pkcs7Strip is only called after HMAC authentication succeeds (verify-then-decrypt).
|
|
40
|
+
// The early throw on invalid padLen is not a padding oracle in this context —
|
|
41
|
+
// the HMAC check is the oracle gate and runs in constant time before this point.
|
|
42
|
+
// If you move this call to a pre-auth site, revisit the timing properties.
|
|
43
|
+
function pkcs7Strip(data) {
|
|
44
|
+
if (data.length === 0)
|
|
45
|
+
throw new RangeError('empty ciphertext');
|
|
46
|
+
const padLen = data[data.length - 1];
|
|
47
|
+
if (padLen === 0 || padLen > 16)
|
|
48
|
+
throw new RangeError(`invalid PKCS7 padding byte: ${padLen}`);
|
|
49
|
+
if (padLen > data.length)
|
|
50
|
+
throw new RangeError(`invalid PKCS7 padding: pad length ${padLen} exceeds data length ${data.length}`);
|
|
51
|
+
let bad = 0;
|
|
52
|
+
for (let i = data.length - padLen; i < data.length; i++)
|
|
53
|
+
bad |= data[i] ^ padLen;
|
|
54
|
+
if (bad !== 0)
|
|
55
|
+
throw new RangeError('invalid PKCS7 padding');
|
|
56
|
+
return data.subarray(0, data.length - padLen);
|
|
57
|
+
}
|
|
58
|
+
// ── SerpentCbc ───────────────────────────────────────────────────────────────
|
|
59
|
+
/**
|
|
60
|
+
* Serpent-256 in CBC mode with PKCS7 padding.
|
|
61
|
+
*
|
|
62
|
+
* **WARNING: CBC mode is unauthenticated.** Always authenticate the output
|
|
63
|
+
* with HMAC-SHA256 (Encrypt-then-MAC) or use `XChaCha20Poly1305` instead.
|
|
64
|
+
*/
|
|
65
|
+
export class SerpentCbc {
|
|
66
|
+
x;
|
|
67
|
+
constructor(opts) {
|
|
68
|
+
if (!opts?.dangerUnauthenticated) {
|
|
69
|
+
throw new Error('leviathan-crypto: SerpentCbc is unauthenticated — use Seal with SerpentCipher instead. ' +
|
|
70
|
+
'To use SerpentCbc directly, pass { dangerUnauthenticated: true }.');
|
|
71
|
+
}
|
|
72
|
+
this.x = getExports();
|
|
73
|
+
}
|
|
74
|
+
get mem() {
|
|
75
|
+
return new Uint8Array(this.x.memory.buffer);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Encrypt plaintext with Serpent-256 CBC + PKCS7 padding.
|
|
79
|
+
*
|
|
80
|
+
* @param key 16, 24, or 32 bytes
|
|
81
|
+
* @param iv 16 bytes — must be random and unique per (key, message)
|
|
82
|
+
* @param plaintext any length — PKCS7 padding applied automatically
|
|
83
|
+
* @returns ciphertext (length = ceil((plaintext.length + 1) / 16) * 16)
|
|
84
|
+
*/
|
|
85
|
+
encrypt(key, iv, plaintext) {
|
|
86
|
+
this._loadKey(key);
|
|
87
|
+
this._setIv(iv);
|
|
88
|
+
const padded = pkcs7Pad(plaintext);
|
|
89
|
+
const output = new Uint8Array(padded.length);
|
|
90
|
+
const ptOff = this.x.getChunkPtOffset();
|
|
91
|
+
const ctOff = this.x.getChunkCtOffset();
|
|
92
|
+
const maxChunk = this.x.getChunkSize();
|
|
93
|
+
for (let off = 0; off < padded.length; off += maxChunk) {
|
|
94
|
+
const chunk = padded.subarray(off, Math.min(off + maxChunk, padded.length));
|
|
95
|
+
this.mem.set(chunk, ptOff);
|
|
96
|
+
this.x.cbcEncryptChunk(chunk.length);
|
|
97
|
+
output.set(new Uint8Array(this.x.memory.buffer).subarray(ctOff, ctOff + chunk.length), off);
|
|
98
|
+
}
|
|
99
|
+
return output;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Decrypt Serpent-256 CBC + PKCS7.
|
|
103
|
+
* Throws if ciphertext length is not a non-zero multiple of 16 or PKCS7 is invalid.
|
|
104
|
+
*/
|
|
105
|
+
decrypt(key, iv, ciphertext) {
|
|
106
|
+
if (ciphertext.length === 0 || ciphertext.length % 16 !== 0)
|
|
107
|
+
throw new RangeError('ciphertext length must be a non-zero multiple of 16');
|
|
108
|
+
this._loadKey(key);
|
|
109
|
+
this._setIv(iv);
|
|
110
|
+
const output = new Uint8Array(ciphertext.length);
|
|
111
|
+
const ctOff = this.x.getChunkCtOffset();
|
|
112
|
+
const ptOff = this.x.getChunkPtOffset();
|
|
113
|
+
const maxChunk = this.x.getChunkSize();
|
|
114
|
+
for (let off = 0; off < ciphertext.length; off += maxChunk) {
|
|
115
|
+
const chunk = ciphertext.subarray(off, Math.min(off + maxChunk, ciphertext.length));
|
|
116
|
+
this.mem.set(chunk, ctOff);
|
|
117
|
+
this.x.cbcDecryptChunk_simd(chunk.length);
|
|
118
|
+
output.set(new Uint8Array(this.x.memory.buffer).subarray(ptOff, ptOff + chunk.length), off);
|
|
119
|
+
}
|
|
120
|
+
return pkcs7Strip(output);
|
|
121
|
+
}
|
|
122
|
+
dispose() {
|
|
123
|
+
this.x.wipeBuffers();
|
|
124
|
+
}
|
|
125
|
+
_loadKey(key) {
|
|
126
|
+
if (key.length !== 16 && key.length !== 24 && key.length !== 32)
|
|
127
|
+
throw new RangeError(`Serpent key must be 16, 24, or 32 bytes (got ${key.length})`);
|
|
128
|
+
this.mem.set(key, this.x.getKeyOffset());
|
|
129
|
+
this.x.loadKey(key.length);
|
|
130
|
+
}
|
|
131
|
+
_setIv(iv) {
|
|
132
|
+
if (iv.length !== 16)
|
|
133
|
+
throw new RangeError(`CBC IV must be 16 bytes (got ${iv.length})`);
|
|
134
|
+
this.mem.set(iv, this.x.getCbcIvOffset());
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { WASM_GZ_BASE64 as sha2Wasm } from '../embedded/sha2.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
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/sha2/embedded.ts
|
|
23
|
+
//
|
|
24
|
+
// Exports the gzip+base64 sha2 WASM blob for use as a WasmSource.
|
|
25
|
+
// This is the only file in the sha2 subpath that references the embedded blob.
|
|
26
|
+
// Import via `leviathan-crypto/sha2/embedded`.
|
|
27
|
+
export { WASM_GZ_BASE64 as sha2Wasm } from '../embedded/sha2.js';
|
package/dist/sha2/hkdf.js
CHANGED
|
@@ -58,7 +58,9 @@ export class HKDF_SHA256 {
|
|
|
58
58
|
oldPrev.fill(0);
|
|
59
59
|
}
|
|
60
60
|
prev.fill(0);
|
|
61
|
-
|
|
61
|
+
const result = okm.slice(0, length);
|
|
62
|
+
okm.fill(0);
|
|
63
|
+
return result;
|
|
62
64
|
}
|
|
63
65
|
// One-shot: extract then expand
|
|
64
66
|
derive(ikm, salt, info, length) {
|
|
@@ -105,7 +107,9 @@ export class HKDF_SHA512 {
|
|
|
105
107
|
oldPrev.fill(0);
|
|
106
108
|
}
|
|
107
109
|
prev.fill(0);
|
|
108
|
-
|
|
110
|
+
const result = okm.slice(0, length);
|
|
111
|
+
okm.fill(0);
|
|
112
|
+
return result;
|
|
109
113
|
}
|
|
110
114
|
// One-shot: extract then expand
|
|
111
115
|
derive(ikm, salt, info, length) {
|
package/dist/sha2/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function sha2Init(
|
|
1
|
+
import type { WasmSource } from '../wasm-source.js';
|
|
2
|
+
export declare function sha2Init(source: WasmSource): Promise<void>;
|
|
3
|
+
export type { WasmSource };
|
|
3
4
|
export declare function _sha2Ready(): boolean;
|
|
4
5
|
export declare class SHA256 {
|
|
5
6
|
private readonly x;
|
package/dist/sha2/index.js
CHANGED
|
@@ -22,11 +22,10 @@
|
|
|
22
22
|
// src/ts/sha2/index.ts
|
|
23
23
|
//
|
|
24
24
|
// Public API classes for the SHA-2 WASM module.
|
|
25
|
-
// Uses the init() module cache — call
|
|
25
|
+
// Uses the init() module cache — call sha2Init(source) before constructing.
|
|
26
26
|
import { getInstance, initModule } from '../init.js';
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return initModule('sha2', _embedded, mode, opts);
|
|
27
|
+
export async function sha2Init(source) {
|
|
28
|
+
return initModule('sha2', source);
|
|
30
29
|
}
|
|
31
30
|
function getExports() {
|
|
32
31
|
return getInstance('sha2').exports;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { WASM_GZ_BASE64 as sha3Wasm } from '../embedded/sha3.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
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/sha3/embedded.ts
|
|
23
|
+
//
|
|
24
|
+
// Exports the gzip+base64 sha3 WASM blob for use as a WasmSource.
|
|
25
|
+
// This is the only file in the sha3 subpath that references the embedded blob.
|
|
26
|
+
// Import via `leviathan-crypto/sha3/embedded`.
|
|
27
|
+
export { WASM_GZ_BASE64 as sha3Wasm } from '../embedded/sha3.js';
|
package/dist/sha3/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function sha3Init(
|
|
1
|
+
import type { WasmSource } from '../wasm-source.js';
|
|
2
|
+
export declare function sha3Init(source: WasmSource): Promise<void>;
|
|
3
|
+
export type { WasmSource };
|
|
3
4
|
export declare function _sha3Ready(): boolean;
|
|
4
5
|
export declare class SHA3_256 {
|
|
5
6
|
private readonly x;
|
package/dist/sha3/index.js
CHANGED
|
@@ -22,11 +22,10 @@
|
|
|
22
22
|
// src/ts/sha3/index.ts
|
|
23
23
|
//
|
|
24
24
|
// Public API classes for the SHA-3 WASM module.
|
|
25
|
-
// Uses the init() module cache — call
|
|
25
|
+
// Uses the init() module cache — call sha3Init(source) before constructing.
|
|
26
26
|
import { getInstance, initModule } from '../init.js';
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return initModule('sha3', _embedded, mode, opts);
|
|
27
|
+
export async function sha3Init(source) {
|
|
28
|
+
return initModule('sha3', source);
|
|
30
29
|
}
|
|
31
30
|
function getExports() {
|
|
32
31
|
return getInstance('sha3').exports;
|
|
@@ -0,0 +1,30 @@
|
|
|
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/constants.ts
|
|
23
|
+
//
|
|
24
|
+
// Shared constants for the STREAM construction.
|
|
25
|
+
export const FLAG_FRAMED = 0x80;
|
|
26
|
+
export const TAG_DATA = 0x00;
|
|
27
|
+
export const TAG_FINAL = 0x01;
|
|
28
|
+
export const HEADER_SIZE = 20;
|
|
29
|
+
export const CHUNK_MIN = 1024;
|
|
30
|
+
export const CHUNK_MAX = 16_777_215; // u24 max
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function writeHeader(formatEnum: number, framed: boolean, nonce: Uint8Array, chunkSize: number): Uint8Array;
|
|
2
|
+
export declare function readHeader(header: Uint8Array): {
|
|
3
|
+
formatEnum: number;
|
|
4
|
+
framed: boolean;
|
|
5
|
+
nonce: Uint8Array;
|
|
6
|
+
chunkSize: number;
|
|
7
|
+
};
|
|
8
|
+
/** 12-byte counter nonce: 11-byte BE counter + 1-byte final flag. */
|
|
9
|
+
export declare function makeCounterNonce(counter: number, finalFlag: number): Uint8Array;
|
|
@@ -0,0 +1,77 @@
|
|
|
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/header.ts
|
|
23
|
+
//
|
|
24
|
+
// Wire format header encoding/decoding and counter nonce construction.
|
|
25
|
+
import { FLAG_FRAMED, HEADER_SIZE, TAG_DATA, TAG_FINAL } from './constants.js';
|
|
26
|
+
// The 16-byte nonce is a HKDF salt — not a direct cipher nonce.
|
|
27
|
+
// Both XChaCha20Cipher and SerpentCipher derive their actual key material
|
|
28
|
+
// and nonces from this value via HKDF-SHA-256. The 16-byte size is chosen
|
|
29
|
+
// to satisfy HChaCha20's 16-byte input requirement while also serving as a
|
|
30
|
+
// sufficient HKDF salt for the Serpent construction.
|
|
31
|
+
export function writeHeader(formatEnum, framed, nonce, chunkSize) {
|
|
32
|
+
if (!Number.isInteger(formatEnum) || formatEnum < 0 || formatEnum > 0x3f)
|
|
33
|
+
throw new RangeError(`formatEnum must be an integer in [0, 0x3f] (got ${formatEnum})`);
|
|
34
|
+
if (nonce.length !== 16)
|
|
35
|
+
throw new RangeError(`nonce must be 16 bytes (got ${nonce.length})`);
|
|
36
|
+
if (!Number.isInteger(chunkSize) || chunkSize < 0 || chunkSize > 0xffffff)
|
|
37
|
+
throw new RangeError(`chunkSize must be an integer in [0, 0xFFFFFF] (got ${chunkSize})`);
|
|
38
|
+
const h = new Uint8Array(HEADER_SIZE);
|
|
39
|
+
h[0] = (framed ? FLAG_FRAMED : 0) | formatEnum;
|
|
40
|
+
h.set(nonce, 1);
|
|
41
|
+
// u24 big-endian chunk size
|
|
42
|
+
h[17] = (chunkSize >> 16) & 0xff;
|
|
43
|
+
h[18] = (chunkSize >> 8) & 0xff;
|
|
44
|
+
h[19] = chunkSize & 0xff;
|
|
45
|
+
return h;
|
|
46
|
+
}
|
|
47
|
+
export function readHeader(header) {
|
|
48
|
+
if (header.length !== HEADER_SIZE)
|
|
49
|
+
throw new RangeError(`header must be exactly ${HEADER_SIZE} bytes (got ${header.length})`);
|
|
50
|
+
const byte0 = header[0];
|
|
51
|
+
if (byte0 & 0x40)
|
|
52
|
+
throw new RangeError(`header has reserved bit 6 set (byte0=0x${byte0.toString(16).padStart(2, '0')}) — unknown or malformed wire format`);
|
|
53
|
+
return {
|
|
54
|
+
formatEnum: byte0 & 0x3f,
|
|
55
|
+
framed: !!(byte0 & FLAG_FRAMED),
|
|
56
|
+
nonce: header.slice(1, 17),
|
|
57
|
+
chunkSize: (header[17] << 16) | (header[18] << 8) | header[19],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** 12-byte counter nonce: 11-byte BE counter + 1-byte final flag. */
|
|
61
|
+
export function makeCounterNonce(counter, finalFlag) {
|
|
62
|
+
if (!Number.isInteger(counter) || counter < 0 || counter > Number.MAX_SAFE_INTEGER)
|
|
63
|
+
throw new RangeError(`counter must be an integer in [0, ${Number.MAX_SAFE_INTEGER}]`);
|
|
64
|
+
if (finalFlag !== TAG_DATA && finalFlag !== TAG_FINAL)
|
|
65
|
+
throw new RangeError(`finalFlag must be TAG_DATA (0x00) or TAG_FINAL (0x01) (got 0x${finalFlag.toString(16).padStart(2, '0')})`);
|
|
66
|
+
const n = new Uint8Array(12);
|
|
67
|
+
// Write counter as 11-byte big-endian.
|
|
68
|
+
// JS safe integers fit in 53 bits — we only need the lower 53 bits.
|
|
69
|
+
// Pack from the right (byte 10 down to byte 0).
|
|
70
|
+
let c = counter;
|
|
71
|
+
for (let i = 10; i >= 0; i--) {
|
|
72
|
+
n[i] = c & 0xff;
|
|
73
|
+
c = Math.floor(c / 256);
|
|
74
|
+
}
|
|
75
|
+
n[11] = finalFlag;
|
|
76
|
+
return n;
|
|
77
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { SealStream } from './seal-stream.js';
|
|
2
|
+
export { OpenStream } from './open-stream.js';
|
|
3
|
+
export { Seal } from './seal.js';
|
|
4
|
+
export type { CipherSuite, DerivedKeys, SealStreamOpts } from './types.js';
|
|
5
|
+
export { FLAG_FRAMED, TAG_DATA, TAG_FINAL, HEADER_SIZE, CHUNK_MIN, CHUNK_MAX, } from './constants.js';
|
|
6
|
+
export { SealStreamPool } from './seal-stream-pool.js';
|
|
7
|
+
export type { PoolOpts } from './seal-stream-pool.js';
|