leviathan-crypto 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CLAUDE.md +265 -0
  2. package/LICENSE +21 -0
  3. package/README.md +322 -0
  4. package/SECURITY.md +174 -0
  5. package/dist/chacha.wasm +0 -0
  6. package/dist/chacha20/index.d.ts +49 -0
  7. package/dist/chacha20/index.js +177 -0
  8. package/dist/chacha20/ops.d.ts +16 -0
  9. package/dist/chacha20/ops.js +146 -0
  10. package/dist/chacha20/pool.d.ts +52 -0
  11. package/dist/chacha20/pool.js +188 -0
  12. package/dist/chacha20/pool.worker.d.ts +1 -0
  13. package/dist/chacha20/pool.worker.js +37 -0
  14. package/dist/chacha20/types.d.ts +30 -0
  15. package/dist/chacha20/types.js +1 -0
  16. package/dist/docs/architecture.md +795 -0
  17. package/dist/docs/argon2id.md +290 -0
  18. package/dist/docs/chacha20.md +602 -0
  19. package/dist/docs/chacha20_pool.md +306 -0
  20. package/dist/docs/fortuna.md +322 -0
  21. package/dist/docs/init.md +308 -0
  22. package/dist/docs/loader.md +206 -0
  23. package/dist/docs/serpent.md +914 -0
  24. package/dist/docs/sha2.md +620 -0
  25. package/dist/docs/sha3.md +509 -0
  26. package/dist/docs/types.md +198 -0
  27. package/dist/docs/utils.md +273 -0
  28. package/dist/docs/wasm.md +193 -0
  29. package/dist/embedded/chacha.d.ts +1 -0
  30. package/dist/embedded/chacha.js +2 -0
  31. package/dist/embedded/serpent.d.ts +1 -0
  32. package/dist/embedded/serpent.js +2 -0
  33. package/dist/embedded/sha2.d.ts +1 -0
  34. package/dist/embedded/sha2.js +2 -0
  35. package/dist/embedded/sha3.d.ts +1 -0
  36. package/dist/embedded/sha3.js +2 -0
  37. package/dist/fortuna.d.ts +72 -0
  38. package/dist/fortuna.js +445 -0
  39. package/dist/index.d.ts +13 -0
  40. package/dist/index.js +44 -0
  41. package/dist/init.d.ts +11 -0
  42. package/dist/init.js +49 -0
  43. package/dist/loader.d.ts +4 -0
  44. package/dist/loader.js +30 -0
  45. package/dist/serpent/index.d.ts +65 -0
  46. package/dist/serpent/index.js +242 -0
  47. package/dist/serpent/seal.d.ts +8 -0
  48. package/dist/serpent/seal.js +70 -0
  49. package/dist/serpent/stream-encoder.d.ts +20 -0
  50. package/dist/serpent/stream-encoder.js +167 -0
  51. package/dist/serpent/stream-pool.d.ts +48 -0
  52. package/dist/serpent/stream-pool.js +285 -0
  53. package/dist/serpent/stream-sealer.d.ts +34 -0
  54. package/dist/serpent/stream-sealer.js +223 -0
  55. package/dist/serpent/stream.d.ts +28 -0
  56. package/dist/serpent/stream.js +205 -0
  57. package/dist/serpent/stream.worker.d.ts +32 -0
  58. package/dist/serpent/stream.worker.js +117 -0
  59. package/dist/serpent/types.d.ts +5 -0
  60. package/dist/serpent/types.js +1 -0
  61. package/dist/serpent.wasm +0 -0
  62. package/dist/sha2/hkdf.d.ts +16 -0
  63. package/dist/sha2/hkdf.js +108 -0
  64. package/dist/sha2/index.d.ts +40 -0
  65. package/dist/sha2/index.js +190 -0
  66. package/dist/sha2/types.d.ts +5 -0
  67. package/dist/sha2/types.js +1 -0
  68. package/dist/sha2.wasm +0 -0
  69. package/dist/sha3/index.d.ts +55 -0
  70. package/dist/sha3/index.js +246 -0
  71. package/dist/sha3/types.d.ts +5 -0
  72. package/dist/sha3/types.js +1 -0
  73. package/dist/sha3.wasm +0 -0
  74. package/dist/types.d.ts +24 -0
  75. package/dist/types.js +26 -0
  76. package/dist/utils.d.ts +26 -0
  77. package/dist/utils.js +169 -0
  78. package/package.json +90 -0
@@ -0,0 +1,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,5 @@
1
+ /** WASM exports for the serpent module */
2
+ export interface SerpentExports {
3
+ memory: WebAssembly.Memory;
4
+ getModuleId(): number;
5
+ }
@@ -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';