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
|
@@ -0,0 +1,205 @@
|
|
|
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/stream.ts
|
|
23
|
+
//
|
|
24
|
+
// SerpentStream — chunked authenticated encryption for large payloads.
|
|
25
|
+
// Tier 2 pure-TS composition: SerpentCtr + HMAC_SHA256 + HKDF_SHA256.
|
|
26
|
+
import { SerpentCtr, _serpentReady } from './index.js';
|
|
27
|
+
import { HMAC_SHA256, HKDF_SHA256, _sha2Ready } from '../sha2/index.js';
|
|
28
|
+
import { constantTimeEqual } from '../utils.js';
|
|
29
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
30
|
+
const DOMAIN = 'serpent-stream-v1'; // UTF-8, 17 bytes
|
|
31
|
+
const ZERO_IV = new Uint8Array(16); // fixed zero IV for CTR
|
|
32
|
+
const CHUNK_MIN = 1024; // 1 KB
|
|
33
|
+
const CHUNK_MAX = 65536; // 64 KB
|
|
34
|
+
const CHUNK_DEF = 65536; // default
|
|
35
|
+
// ── Internal helpers ──────────────────────────────────────────────────────────
|
|
36
|
+
export function u32be(n) {
|
|
37
|
+
const b = new Uint8Array(4);
|
|
38
|
+
b[0] = (n >>> 24) & 0xff;
|
|
39
|
+
b[1] = (n >>> 16) & 0xff;
|
|
40
|
+
b[2] = (n >>> 8) & 0xff;
|
|
41
|
+
b[3] = n & 0xff;
|
|
42
|
+
return b;
|
|
43
|
+
}
|
|
44
|
+
export function u64be(n) {
|
|
45
|
+
const b = new Uint8Array(8);
|
|
46
|
+
// high 32 bits (safe for n < 2^53)
|
|
47
|
+
const hi = Math.floor(n / 0x100000000);
|
|
48
|
+
const lo = n >>> 0;
|
|
49
|
+
b[0] = (hi >>> 24) & 0xff;
|
|
50
|
+
b[1] = (hi >>> 16) & 0xff;
|
|
51
|
+
b[2] = (hi >>> 8) & 0xff;
|
|
52
|
+
b[3] = hi & 0xff;
|
|
53
|
+
b[4] = (lo >>> 24) & 0xff;
|
|
54
|
+
b[5] = (lo >>> 16) & 0xff;
|
|
55
|
+
b[6] = (lo >>> 8) & 0xff;
|
|
56
|
+
b[7] = lo & 0xff;
|
|
57
|
+
return b;
|
|
58
|
+
}
|
|
59
|
+
const DOMAIN_BYTES = new TextEncoder().encode(DOMAIN);
|
|
60
|
+
export function chunkInfo(streamNonce, chunkSize, chunkCount, index, isLast) {
|
|
61
|
+
// 17 + 16 + 4 + 8 + 8 + 1 = 54 bytes
|
|
62
|
+
const info = new Uint8Array(54);
|
|
63
|
+
let off = 0;
|
|
64
|
+
info.set(DOMAIN_BYTES, off);
|
|
65
|
+
off += 17;
|
|
66
|
+
info.set(streamNonce, off);
|
|
67
|
+
off += 16;
|
|
68
|
+
info.set(u32be(chunkSize), off);
|
|
69
|
+
off += 4;
|
|
70
|
+
info.set(u64be(chunkCount), off);
|
|
71
|
+
off += 8;
|
|
72
|
+
info.set(u64be(index), off);
|
|
73
|
+
off += 8;
|
|
74
|
+
info[off] = isLast ? 0x01 : 0x00;
|
|
75
|
+
return info;
|
|
76
|
+
}
|
|
77
|
+
export function deriveChunkKeys(hkdf, masterKey, streamNonce, chunkSize, chunkCount, index, isLast) {
|
|
78
|
+
const info = chunkInfo(streamNonce, chunkSize, chunkCount, index, isLast);
|
|
79
|
+
const derived = hkdf.derive(masterKey, streamNonce, info, 64);
|
|
80
|
+
return {
|
|
81
|
+
encKey: derived.subarray(0, 32),
|
|
82
|
+
macKey: derived.subarray(32, 64),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// ── Exported chunk-level ops (used by Part 2 pool worker) ─────────────────────
|
|
86
|
+
/**
|
|
87
|
+
* Encrypt one chunk. Returns ciphertext || hmac_tag (32 bytes).
|
|
88
|
+
* Does not generate keys -- caller provides encKey and macKey.
|
|
89
|
+
*/
|
|
90
|
+
export function sealChunk(ctr, hmac, encKey, macKey, chunk) {
|
|
91
|
+
ctr.beginEncrypt(encKey, ZERO_IV);
|
|
92
|
+
const ciphertext = ctr.encryptChunk(chunk);
|
|
93
|
+
const tag = hmac.hash(macKey, ciphertext);
|
|
94
|
+
const out = new Uint8Array(ciphertext.length + 32);
|
|
95
|
+
out.set(ciphertext, 0);
|
|
96
|
+
out.set(tag, ciphertext.length);
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Decrypt one chunk. Throws 'SerpentStream: authentication failed' on bad tag.
|
|
101
|
+
* Returns plaintext.
|
|
102
|
+
*/
|
|
103
|
+
export function openChunk(ctr, hmac, encKey, macKey, wire) {
|
|
104
|
+
if (wire.length < 32)
|
|
105
|
+
throw new RangeError('SerpentStream: chunk wire data too short');
|
|
106
|
+
const ciphertext = wire.subarray(0, wire.length - 32);
|
|
107
|
+
const tag = wire.subarray(wire.length - 32);
|
|
108
|
+
const expectedTag = hmac.hash(macKey, ciphertext);
|
|
109
|
+
if (!constantTimeEqual(tag, expectedTag))
|
|
110
|
+
throw new Error('SerpentStream: authentication failed');
|
|
111
|
+
ctr.beginEncrypt(encKey, ZERO_IV);
|
|
112
|
+
return ctr.encryptChunk(ciphertext);
|
|
113
|
+
}
|
|
114
|
+
// ── SerpentStream class ───────────────────────────────────────────────────────
|
|
115
|
+
export class SerpentStream {
|
|
116
|
+
_ctr;
|
|
117
|
+
_hmac;
|
|
118
|
+
_hkdf;
|
|
119
|
+
constructor() {
|
|
120
|
+
if (!_serpentReady() || !_sha2Ready())
|
|
121
|
+
throw new Error('leviathan-crypto: call init([\'serpent\', \'sha2\']) before using SerpentStream');
|
|
122
|
+
this._ctr = new SerpentCtr({ dangerUnauthenticated: true });
|
|
123
|
+
this._hmac = new HMAC_SHA256();
|
|
124
|
+
this._hkdf = new HKDF_SHA256();
|
|
125
|
+
}
|
|
126
|
+
// _nonce: test seam only — inject a fixed nonce for deterministic KAT vectors
|
|
127
|
+
seal(key, plaintext, chunkSize, _nonce) {
|
|
128
|
+
if (key.length !== 32)
|
|
129
|
+
throw new RangeError(`SerpentStream key must be 32 bytes (got ${key.length})`);
|
|
130
|
+
const cs = chunkSize ?? CHUNK_DEF;
|
|
131
|
+
if (cs < CHUNK_MIN || cs > CHUNK_MAX)
|
|
132
|
+
throw new RangeError(`SerpentStream chunkSize must be ${CHUNK_MIN}..${CHUNK_MAX} (got ${cs})`);
|
|
133
|
+
const streamNonce = (_nonce && _nonce.length === 16) ? _nonce : new Uint8Array(16);
|
|
134
|
+
if (!_nonce || _nonce.length !== 16)
|
|
135
|
+
crypto.getRandomValues(streamNonce);
|
|
136
|
+
const chunkCount = plaintext.length === 0 ? 1 : Math.ceil(plaintext.length / cs);
|
|
137
|
+
// Compute total output size
|
|
138
|
+
let totalWire = 28; // header
|
|
139
|
+
for (let i = 0; i < chunkCount; i++) {
|
|
140
|
+
const start = i * cs;
|
|
141
|
+
const end = Math.min(start + cs, plaintext.length);
|
|
142
|
+
totalWire += (end - start) + 32;
|
|
143
|
+
}
|
|
144
|
+
const out = new Uint8Array(totalWire);
|
|
145
|
+
// Write header
|
|
146
|
+
out.set(streamNonce, 0);
|
|
147
|
+
out.set(u32be(cs), 16);
|
|
148
|
+
out.set(u64be(chunkCount), 20);
|
|
149
|
+
let pos = 28;
|
|
150
|
+
for (let i = 0; i < chunkCount; i++) {
|
|
151
|
+
const start = i * cs;
|
|
152
|
+
const end = Math.min(start + cs, plaintext.length);
|
|
153
|
+
const slice = plaintext.subarray(start, end);
|
|
154
|
+
const isLast = i === chunkCount - 1;
|
|
155
|
+
const { encKey, macKey } = deriveChunkKeys(this._hkdf, key, streamNonce, cs, chunkCount, i, isLast);
|
|
156
|
+
const wire = sealChunk(this._ctr, this._hmac, encKey, macKey, slice);
|
|
157
|
+
out.set(wire, pos);
|
|
158
|
+
pos += wire.length;
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
open(key, ciphertext) {
|
|
163
|
+
if (key.length !== 32)
|
|
164
|
+
throw new RangeError(`SerpentStream key must be 32 bytes (got ${key.length})`);
|
|
165
|
+
if (ciphertext.length < 28 + 32)
|
|
166
|
+
throw new RangeError('SerpentStream: ciphertext too short');
|
|
167
|
+
// Parse header
|
|
168
|
+
const streamNonce = ciphertext.subarray(0, 16);
|
|
169
|
+
const csView = ciphertext.subarray(16, 20);
|
|
170
|
+
const cs = (csView[0] << 24) | (csView[1] << 16) | (csView[2] << 8) | csView[3];
|
|
171
|
+
const ccView = ciphertext.subarray(20, 28);
|
|
172
|
+
let chunkCount = 0;
|
|
173
|
+
for (let i = 0; i < 8; i++)
|
|
174
|
+
chunkCount = chunkCount * 256 + ccView[i];
|
|
175
|
+
// Compute total plaintext size
|
|
176
|
+
let totalPt = 0;
|
|
177
|
+
let pos = 28;
|
|
178
|
+
for (let i = 0; i < chunkCount; i++) {
|
|
179
|
+
const isLast = i === chunkCount - 1;
|
|
180
|
+
const wireLen = isLast ? ciphertext.length - pos : cs + 32;
|
|
181
|
+
totalPt += wireLen - 32;
|
|
182
|
+
if (!isLast)
|
|
183
|
+
pos += wireLen;
|
|
184
|
+
}
|
|
185
|
+
const plaintext = new Uint8Array(totalPt);
|
|
186
|
+
pos = 28;
|
|
187
|
+
let ptPos = 0;
|
|
188
|
+
for (let i = 0; i < chunkCount; i++) {
|
|
189
|
+
const isLast = i === chunkCount - 1;
|
|
190
|
+
const wireLen = isLast ? ciphertext.length - pos : cs + 32;
|
|
191
|
+
const wireSlice = ciphertext.subarray(pos, pos + wireLen);
|
|
192
|
+
const { encKey, macKey } = deriveChunkKeys(this._hkdf, key, streamNonce, cs, chunkCount, i, isLast);
|
|
193
|
+
const pt = openChunk(this._ctr, this._hmac, encKey, macKey, wireSlice);
|
|
194
|
+
plaintext.set(pt, ptPos);
|
|
195
|
+
ptPos += pt.length;
|
|
196
|
+
pos += wireLen;
|
|
197
|
+
}
|
|
198
|
+
return plaintext;
|
|
199
|
+
}
|
|
200
|
+
dispose() {
|
|
201
|
+
this._ctr.dispose();
|
|
202
|
+
this._hmac.dispose();
|
|
203
|
+
this._hkdf.dispose();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
interface SerpentExports {
|
|
2
|
+
memory: WebAssembly.Memory;
|
|
3
|
+
getKeyOffset: () => number;
|
|
4
|
+
getNonceOffset: () => number;
|
|
5
|
+
getChunkPtOffset: () => number;
|
|
6
|
+
getChunkCtOffset: () => number;
|
|
7
|
+
getChunkSize: () => number;
|
|
8
|
+
loadKey: (n: number) => number;
|
|
9
|
+
resetCounter: () => void;
|
|
10
|
+
encryptChunk: (n: number) => number;
|
|
11
|
+
wipeBuffers: () => void;
|
|
12
|
+
}
|
|
13
|
+
interface Sha2Exports {
|
|
14
|
+
memory: WebAssembly.Memory;
|
|
15
|
+
getSha256InputOffset: () => number;
|
|
16
|
+
getSha256OutOffset: () => number;
|
|
17
|
+
sha256Init: () => void;
|
|
18
|
+
sha256Update: (len: number) => void;
|
|
19
|
+
sha256Final: () => void;
|
|
20
|
+
hmac256Init: (keyLen: number) => void;
|
|
21
|
+
hmac256Update: (len: number) => void;
|
|
22
|
+
hmac256Final: () => void;
|
|
23
|
+
wipeBuffers: () => void;
|
|
24
|
+
}
|
|
25
|
+
declare let sx: SerpentExports | undefined;
|
|
26
|
+
declare let hx: Sha2Exports | undefined;
|
|
27
|
+
declare const ZERO_IV: Uint8Array<ArrayBuffer>;
|
|
28
|
+
declare function hmacSha256(hx: Sha2Exports, key: Uint8Array, msg: Uint8Array): Uint8Array;
|
|
29
|
+
declare function ctrEncrypt(sx: SerpentExports, key: Uint8Array, chunk: Uint8Array): Uint8Array;
|
|
30
|
+
declare function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean;
|
|
31
|
+
declare function workerSealChunk(encKey: Uint8Array, macKey: Uint8Array, chunk: Uint8Array): Uint8Array;
|
|
32
|
+
declare function workerOpenChunk(encKey: Uint8Array, macKey: Uint8Array, wire: Uint8Array): Uint8Array;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// / <reference lib="webworker" />
|
|
3
|
+
// src/ts/serpent/stream.worker.ts
|
|
4
|
+
//
|
|
5
|
+
// Worker entry point for SerpentStreamPool. Runs in a Web Worker —
|
|
6
|
+
// no access to the main thread's module cache. Owns its own
|
|
7
|
+
// serpent.wasm and sha2.wasm instances with isolated linear memory.
|
|
8
|
+
// Implements sealChunk/openChunk inline using raw WASM exports.
|
|
9
|
+
let sx;
|
|
10
|
+
let hx;
|
|
11
|
+
const ZERO_IV = new Uint8Array(16);
|
|
12
|
+
// ── Inline chunk ops ──────────────────────────────────────────────────────────
|
|
13
|
+
function hmacSha256(hx, key, msg) {
|
|
14
|
+
// RFC 2104 §3: keys longer than block size (64 bytes) are pre-hashed.
|
|
15
|
+
// mac_key is always 32 bytes in normal usage (half of HKDF 64-byte output),
|
|
16
|
+
// but this guard must match the main-thread HMAC_SHA256.hash() behaviour
|
|
17
|
+
// exactly — any divergence would cause authentication failures if key sizes
|
|
18
|
+
// ever change.
|
|
19
|
+
let k = key;
|
|
20
|
+
if (k.length > 64) {
|
|
21
|
+
hx.sha256Init();
|
|
22
|
+
let pos = 0;
|
|
23
|
+
while (pos < k.length) {
|
|
24
|
+
const n = Math.min(k.length - pos, 64);
|
|
25
|
+
new Uint8Array(hx.memory.buffer).set(k.subarray(pos, pos + n), hx.getSha256InputOffset());
|
|
26
|
+
hx.sha256Update(n);
|
|
27
|
+
pos += n;
|
|
28
|
+
}
|
|
29
|
+
hx.sha256Final();
|
|
30
|
+
const out = new Uint8Array(hx.memory.buffer);
|
|
31
|
+
k = out.slice(hx.getSha256OutOffset(), hx.getSha256OutOffset() + 32);
|
|
32
|
+
}
|
|
33
|
+
const mem = new Uint8Array(hx.memory.buffer);
|
|
34
|
+
const inputOff = hx.getSha256InputOffset();
|
|
35
|
+
mem.set(k, inputOff);
|
|
36
|
+
hx.hmac256Init(k.length);
|
|
37
|
+
let pos = 0;
|
|
38
|
+
while (pos < msg.length) {
|
|
39
|
+
const n = Math.min(msg.length - pos, 64);
|
|
40
|
+
new Uint8Array(hx.memory.buffer).set(msg.subarray(pos, pos + n), inputOff);
|
|
41
|
+
hx.hmac256Update(n);
|
|
42
|
+
pos += n;
|
|
43
|
+
}
|
|
44
|
+
hx.hmac256Final();
|
|
45
|
+
const out = new Uint8Array(hx.memory.buffer);
|
|
46
|
+
return out.slice(hx.getSha256OutOffset(), hx.getSha256OutOffset() + 32);
|
|
47
|
+
}
|
|
48
|
+
function ctrEncrypt(sx, key, chunk) {
|
|
49
|
+
const mem = new Uint8Array(sx.memory.buffer);
|
|
50
|
+
mem.set(key, sx.getKeyOffset());
|
|
51
|
+
mem.set(ZERO_IV, sx.getNonceOffset());
|
|
52
|
+
sx.loadKey(key.length);
|
|
53
|
+
sx.resetCounter();
|
|
54
|
+
new Uint8Array(sx.memory.buffer).set(chunk, sx.getChunkPtOffset());
|
|
55
|
+
sx.encryptChunk(chunk.length);
|
|
56
|
+
const out = new Uint8Array(sx.memory.buffer);
|
|
57
|
+
return out.slice(sx.getChunkCtOffset(), sx.getChunkCtOffset() + chunk.length);
|
|
58
|
+
}
|
|
59
|
+
function constantTimeEqual(a, b) {
|
|
60
|
+
if (a.length !== b.length)
|
|
61
|
+
return false;
|
|
62
|
+
let diff = 0;
|
|
63
|
+
for (let i = 0; i < a.length; i++)
|
|
64
|
+
diff |= a[i] ^ b[i];
|
|
65
|
+
return diff === 0;
|
|
66
|
+
}
|
|
67
|
+
function workerSealChunk(encKey, macKey, chunk) {
|
|
68
|
+
const ciphertext = ctrEncrypt(sx, encKey, chunk);
|
|
69
|
+
const tag = hmacSha256(hx, macKey, ciphertext);
|
|
70
|
+
const out = new Uint8Array(ciphertext.length + 32);
|
|
71
|
+
out.set(ciphertext, 0);
|
|
72
|
+
out.set(tag, ciphertext.length);
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
function workerOpenChunk(encKey, macKey, wire) {
|
|
76
|
+
if (wire.length < 32)
|
|
77
|
+
throw new RangeError('SerpentStream: chunk wire data too short');
|
|
78
|
+
const ciphertext = wire.subarray(0, wire.length - 32);
|
|
79
|
+
const tag = wire.subarray(wire.length - 32);
|
|
80
|
+
const expectedTag = hmacSha256(hx, macKey, ciphertext);
|
|
81
|
+
if (!constantTimeEqual(tag, expectedTag))
|
|
82
|
+
throw new Error('SerpentStream: authentication failed');
|
|
83
|
+
return ctrEncrypt(sx, encKey, ciphertext);
|
|
84
|
+
}
|
|
85
|
+
// ── Message handler ───────────────────────────────────────────────────────────
|
|
86
|
+
self.onmessage = async (e) => {
|
|
87
|
+
const msg = e.data;
|
|
88
|
+
if (msg.type === 'init') {
|
|
89
|
+
try {
|
|
90
|
+
const serpentMem = new WebAssembly.Memory({ initial: 3, maximum: 3 });
|
|
91
|
+
const sha2Mem = new WebAssembly.Memory({ initial: 3, maximum: 3 });
|
|
92
|
+
const serpentInst = await WebAssembly.instantiate(msg.serpentModule, { env: { memory: serpentMem } });
|
|
93
|
+
const sha2Inst = await WebAssembly.instantiate(msg.sha2Module, { env: { memory: sha2Mem } });
|
|
94
|
+
sx = serpentInst.exports;
|
|
95
|
+
hx = sha2Inst.exports;
|
|
96
|
+
self.postMessage({ type: 'ready' });
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
self.postMessage({ type: 'error', id: -1, message: err.message });
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!sx || !hx) {
|
|
104
|
+
self.postMessage({ type: 'error', id: msg.id, message: 'worker not initialized' });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const { id, op, encKey, macKey, data } = msg;
|
|
109
|
+
const result = op === 'seal'
|
|
110
|
+
? workerSealChunk(encKey, macKey, data)
|
|
111
|
+
: workerOpenChunk(encKey, macKey, data);
|
|
112
|
+
self.postMessage({ type: 'result', id, data: result }, [result.buffer]);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
self.postMessage({ type: 'error', id: msg.id, message: err.message });
|
|
116
|
+
}
|
|
117
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class HKDF_SHA256 {
|
|
2
|
+
private readonly hmac;
|
|
3
|
+
constructor();
|
|
4
|
+
extract(salt: Uint8Array | null, ikm: Uint8Array): Uint8Array;
|
|
5
|
+
expand(prk: Uint8Array, info: Uint8Array, length: number): Uint8Array;
|
|
6
|
+
derive(ikm: Uint8Array, salt: Uint8Array | null, info: Uint8Array, length: number): Uint8Array;
|
|
7
|
+
dispose(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare class HKDF_SHA512 {
|
|
10
|
+
private readonly hmac;
|
|
11
|
+
constructor();
|
|
12
|
+
extract(salt: Uint8Array | null, ikm: Uint8Array): Uint8Array;
|
|
13
|
+
expand(prk: Uint8Array, info: Uint8Array, length: number): Uint8Array;
|
|
14
|
+
derive(ikm: Uint8Array, salt: Uint8Array | null, info: Uint8Array, length: number): Uint8Array;
|
|
15
|
+
dispose(): void;
|
|
16
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
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/hkdf.ts
|
|
23
|
+
//
|
|
24
|
+
// RFC 5869 — HKDF (HMAC-based Extract-and-Expand Key Derivation Function)
|
|
25
|
+
// Pure TS composition over HMAC_SHA256 and HMAC_SHA512.
|
|
26
|
+
import { HMAC_SHA256, HMAC_SHA512 } from './index.js';
|
|
27
|
+
// ── HKDF_SHA256 ─────────────────────────────────────────────────────────────
|
|
28
|
+
export class HKDF_SHA256 {
|
|
29
|
+
hmac;
|
|
30
|
+
constructor() {
|
|
31
|
+
this.hmac = new HMAC_SHA256();
|
|
32
|
+
}
|
|
33
|
+
// RFC 5869 §2.2 — Extract
|
|
34
|
+
extract(salt, ikm) {
|
|
35
|
+
const s = (!salt || salt.length === 0) ? new Uint8Array(32) : salt;
|
|
36
|
+
return this.hmac.hash(s, ikm);
|
|
37
|
+
}
|
|
38
|
+
// RFC 5869 §2.3 — Expand
|
|
39
|
+
expand(prk, info, length) {
|
|
40
|
+
if (prk.length !== 32)
|
|
41
|
+
throw new RangeError('HKDF expand: PRK must be 32 bytes');
|
|
42
|
+
if (length < 1)
|
|
43
|
+
throw new RangeError('HKDF expand: length must be at least 1');
|
|
44
|
+
if (length > 255 * 32)
|
|
45
|
+
throw new RangeError(`HKDF expand: length exceeds maximum (${255 * 32} bytes)`);
|
|
46
|
+
const N = Math.ceil(length / 32);
|
|
47
|
+
const okm = new Uint8Array(N * 32);
|
|
48
|
+
let prev = new Uint8Array(0);
|
|
49
|
+
for (let i = 1; i <= N; i++) {
|
|
50
|
+
const buf = new Uint8Array(prev.length + info.length + 1);
|
|
51
|
+
buf.set(prev, 0);
|
|
52
|
+
buf.set(info, prev.length);
|
|
53
|
+
buf[prev.length + info.length] = i;
|
|
54
|
+
prev = this.hmac.hash(prk, buf);
|
|
55
|
+
okm.set(prev, (i - 1) * 32);
|
|
56
|
+
}
|
|
57
|
+
return okm.slice(0, length);
|
|
58
|
+
}
|
|
59
|
+
// One-shot: extract then expand
|
|
60
|
+
derive(ikm, salt, info, length) {
|
|
61
|
+
const prk = this.extract(salt, ikm);
|
|
62
|
+
return this.expand(prk, info, length);
|
|
63
|
+
}
|
|
64
|
+
dispose() {
|
|
65
|
+
this.hmac.dispose();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// ── HKDF_SHA512 ─────────────────────────────────────────────────────────────
|
|
69
|
+
export class HKDF_SHA512 {
|
|
70
|
+
hmac;
|
|
71
|
+
constructor() {
|
|
72
|
+
this.hmac = new HMAC_SHA512();
|
|
73
|
+
}
|
|
74
|
+
// RFC 5869 §2.2 — Extract
|
|
75
|
+
extract(salt, ikm) {
|
|
76
|
+
const s = (!salt || salt.length === 0) ? new Uint8Array(64) : salt;
|
|
77
|
+
return this.hmac.hash(s, ikm);
|
|
78
|
+
}
|
|
79
|
+
// RFC 5869 §2.3 — Expand
|
|
80
|
+
expand(prk, info, length) {
|
|
81
|
+
if (prk.length !== 64)
|
|
82
|
+
throw new RangeError('HKDF expand: PRK must be 64 bytes');
|
|
83
|
+
if (length < 1)
|
|
84
|
+
throw new RangeError('HKDF expand: length must be at least 1');
|
|
85
|
+
if (length > 255 * 64)
|
|
86
|
+
throw new RangeError(`HKDF expand: length exceeds maximum (${255 * 64} bytes)`);
|
|
87
|
+
const N = Math.ceil(length / 64);
|
|
88
|
+
const okm = new Uint8Array(N * 64);
|
|
89
|
+
let prev = new Uint8Array(0);
|
|
90
|
+
for (let i = 1; i <= N; i++) {
|
|
91
|
+
const buf = new Uint8Array(prev.length + info.length + 1);
|
|
92
|
+
buf.set(prev, 0);
|
|
93
|
+
buf.set(info, prev.length);
|
|
94
|
+
buf[prev.length + info.length] = i;
|
|
95
|
+
prev = this.hmac.hash(prk, buf);
|
|
96
|
+
okm.set(prev, (i - 1) * 64);
|
|
97
|
+
}
|
|
98
|
+
return okm.slice(0, length);
|
|
99
|
+
}
|
|
100
|
+
// One-shot: extract then expand
|
|
101
|
+
derive(ikm, salt, info, length) {
|
|
102
|
+
const prk = this.extract(salt, ikm);
|
|
103
|
+
return this.expand(prk, info, length);
|
|
104
|
+
}
|
|
105
|
+
dispose() {
|
|
106
|
+
this.hmac.dispose();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Mode, InitOpts } from '../init.js';
|
|
2
|
+
export declare function sha2Init(mode?: Mode, opts?: InitOpts): Promise<void>;
|
|
3
|
+
export declare function _sha2Ready(): boolean;
|
|
4
|
+
export declare class SHA256 {
|
|
5
|
+
private readonly x;
|
|
6
|
+
constructor();
|
|
7
|
+
hash(msg: Uint8Array): Uint8Array;
|
|
8
|
+
dispose(): void;
|
|
9
|
+
}
|
|
10
|
+
export declare class SHA512 {
|
|
11
|
+
private readonly x;
|
|
12
|
+
constructor();
|
|
13
|
+
hash(msg: Uint8Array): Uint8Array;
|
|
14
|
+
dispose(): void;
|
|
15
|
+
}
|
|
16
|
+
export declare class SHA384 {
|
|
17
|
+
private readonly x;
|
|
18
|
+
constructor();
|
|
19
|
+
hash(msg: Uint8Array): Uint8Array;
|
|
20
|
+
dispose(): void;
|
|
21
|
+
}
|
|
22
|
+
export declare class HMAC_SHA256 {
|
|
23
|
+
private readonly x;
|
|
24
|
+
constructor();
|
|
25
|
+
hash(key: Uint8Array, msg: Uint8Array): Uint8Array;
|
|
26
|
+
dispose(): void;
|
|
27
|
+
}
|
|
28
|
+
export declare class HMAC_SHA512 {
|
|
29
|
+
private readonly x;
|
|
30
|
+
constructor();
|
|
31
|
+
hash(key: Uint8Array, msg: Uint8Array): Uint8Array;
|
|
32
|
+
dispose(): void;
|
|
33
|
+
}
|
|
34
|
+
export declare class HMAC_SHA384 {
|
|
35
|
+
private readonly x;
|
|
36
|
+
constructor();
|
|
37
|
+
hash(key: Uint8Array, msg: Uint8Array): Uint8Array;
|
|
38
|
+
dispose(): void;
|
|
39
|
+
}
|
|
40
|
+
export { HKDF_SHA256, HKDF_SHA512 } from './hkdf.js';
|