leviathan-crypto 2.0.1 → 2.1.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 +171 -7
- package/LICENSE +4 -0
- package/README.md +109 -54
- package/SECURITY.md +125 -238
- package/dist/chacha20/cipher-suite.d.ts +10 -0
- package/dist/chacha20/cipher-suite.js +65 -2
- package/dist/chacha20/generator.d.ts +12 -0
- package/dist/chacha20/generator.js +91 -0
- package/dist/chacha20/index.d.ts +97 -1
- package/dist/chacha20/index.js +139 -11
- package/dist/chacha20/ops.d.ts +57 -6
- package/dist/chacha20/ops.js +93 -13
- package/dist/chacha20/pool-worker.js +12 -0
- package/dist/chacha20/types.d.ts +1 -32
- package/dist/ct-wasm.js +1 -1
- package/dist/ct.wasm +0 -0
- package/dist/docs/aead.md +66 -26
- package/dist/docs/architecture.md +600 -521
- package/dist/docs/argon2id.md +17 -14
- package/dist/docs/chacha20.md +146 -39
- package/dist/docs/exports.md +46 -10
- package/dist/docs/fortuna.md +339 -122
- package/dist/docs/init.md +24 -25
- package/dist/docs/loader.md +142 -47
- package/dist/docs/serpent.md +139 -41
- package/dist/docs/sha2.md +77 -19
- package/dist/docs/sha3.md +81 -15
- package/dist/docs/types.md +155 -15
- package/dist/docs/utils.md +171 -81
- package/dist/embedded/chacha20-pool-worker.d.ts +1 -0
- package/dist/embedded/chacha20-pool-worker.js +5 -0
- package/dist/embedded/kyber.d.ts +1 -1
- package/dist/embedded/kyber.js +1 -1
- package/dist/embedded/serpent-pool-worker.d.ts +1 -0
- package/dist/embedded/serpent-pool-worker.js +5 -0
- package/dist/fortuna.d.ts +14 -8
- package/dist/fortuna.js +144 -50
- package/dist/index.d.ts +8 -6
- package/dist/index.js +6 -5
- package/dist/init.d.ts +0 -2
- package/dist/init.js +83 -3
- package/dist/kyber/indcpa.js +4 -4
- package/dist/kyber/index.js +25 -5
- package/dist/kyber/kem.js +56 -1
- package/dist/kyber/suite.d.ts +1 -2
- package/dist/kyber/types.d.ts +1 -0
- package/dist/kyber/validate.d.ts +8 -4
- package/dist/kyber/validate.js +18 -14
- package/dist/kyber.wasm +0 -0
- package/dist/loader.d.ts +7 -2
- package/dist/loader.js +25 -28
- package/dist/ratchet/index.d.ts +6 -0
- package/dist/ratchet/index.js +37 -0
- package/dist/ratchet/kdf-chain.d.ts +13 -0
- package/dist/ratchet/kdf-chain.js +85 -0
- package/dist/ratchet/ratchet-keypair.d.ts +9 -0
- package/dist/ratchet/ratchet-keypair.js +61 -0
- package/dist/ratchet/root-kdf.d.ts +4 -0
- package/dist/ratchet/root-kdf.js +124 -0
- package/dist/ratchet/skipped-key-store.d.ts +14 -0
- package/dist/ratchet/skipped-key-store.js +154 -0
- package/dist/ratchet/types.d.ts +36 -0
- package/dist/ratchet/types.js +26 -0
- package/dist/serpent/cipher-suite.d.ts +10 -0
- package/dist/serpent/cipher-suite.js +135 -50
- package/dist/serpent/generator.d.ts +12 -0
- package/dist/serpent/generator.js +97 -0
- package/dist/serpent/index.d.ts +61 -1
- package/dist/serpent/index.js +92 -7
- package/dist/serpent/pool-worker.js +25 -101
- package/dist/serpent/serpent-cbc.d.ts +14 -4
- package/dist/serpent/serpent-cbc.js +50 -32
- package/dist/serpent/shared-ops.d.ts +83 -0
- package/dist/serpent/shared-ops.js +213 -0
- package/dist/serpent/types.d.ts +1 -5
- package/dist/sha2/hash.d.ts +2 -0
- package/dist/sha2/hash.js +53 -0
- package/dist/sha2/index.d.ts +1 -0
- package/dist/sha2/index.js +15 -1
- package/dist/sha3/hash.d.ts +2 -0
- package/dist/sha3/hash.js +53 -0
- package/dist/sha3/index.d.ts +17 -2
- package/dist/sha3/index.js +79 -7
- package/dist/stream/header.js +5 -5
- package/dist/stream/open-stream.js +36 -14
- package/dist/stream/seal-stream-pool.d.ts +1 -0
- package/dist/stream/seal-stream-pool.js +38 -8
- package/dist/stream/seal-stream.js +29 -11
- package/dist/types.d.ts +21 -0
- package/dist/utils.d.ts +7 -8
- package/dist/utils.js +73 -40
- package/dist/wasm-source.d.ts +9 -8
- package/package.json +79 -64
|
@@ -24,53 +24,42 @@
|
|
|
24
24
|
// SerpentCbc — Serpent-256 CBC + PKCS7, internal module.
|
|
25
25
|
// Extracted to break the cipher-suite.ts ↔ index.ts circular dependency.
|
|
26
26
|
// Import from here directly; index.ts re-exports for the public API surface.
|
|
27
|
-
import { getInstance } from '../init.js';
|
|
27
|
+
import { getInstance, _acquireModule, _releaseModule } from '../init.js';
|
|
28
|
+
import { pkcs7Pad, pkcs7Strip, PKCS7_INVALID } from './shared-ops.js';
|
|
29
|
+
/** Returns the raw serpent WASM export object. @internal */
|
|
28
30
|
function getExports() {
|
|
29
31
|
return getInstance('serpent').exports;
|
|
30
32
|
}
|
|
31
|
-
// ── PKCS7 helpers
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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 ───────────────────────────────────────────────────────────────
|
|
33
|
+
// ── PKCS7 helpers ───────────────────────────────────────────────────────────
|
|
34
|
+
//
|
|
35
|
+
// The canonical `pkcs7Pad` / `pkcs7Strip` live in `./shared-ops.ts`; this
|
|
36
|
+
// file re-uses them so the main-thread class and the pool worker share one
|
|
37
|
+
// implementation. See `shared-ops.ts` for the branch-free, Vaudenay-2002-
|
|
38
|
+
// closed padding check.
|
|
39
|
+
// ── SerpentCbc ──────────────────────────────────────────────────────────────
|
|
59
40
|
/**
|
|
60
41
|
* Serpent-256 in CBC mode with PKCS7 padding.
|
|
61
42
|
*
|
|
62
43
|
* **WARNING: CBC mode is unauthenticated.** Always authenticate the output
|
|
63
44
|
* with HMAC-SHA256 (Encrypt-then-MAC) or use `XChaCha20Poly1305` instead.
|
|
45
|
+
*
|
|
46
|
+
* Holds exclusive access to the `serpent` WASM module from construction
|
|
47
|
+
* until `dispose()`. Constructing a second SerpentCbc/SerpentCtr/
|
|
48
|
+
* SerpentCipher or any other serpent user while this instance is live
|
|
49
|
+
* throws. Call `dispose()` when done.
|
|
64
50
|
*/
|
|
65
51
|
export class SerpentCbc {
|
|
66
52
|
x;
|
|
53
|
+
_tok;
|
|
67
54
|
constructor(opts) {
|
|
68
55
|
if (!opts?.dangerUnauthenticated) {
|
|
69
56
|
throw new Error('leviathan-crypto: SerpentCbc is unauthenticated — use Seal with SerpentCipher instead. ' +
|
|
70
57
|
'To use SerpentCbc directly, pass { dangerUnauthenticated: true }.');
|
|
71
58
|
}
|
|
72
59
|
this.x = getExports();
|
|
60
|
+
this._tok = _acquireModule('serpent');
|
|
73
61
|
}
|
|
62
|
+
/** View over WASM linear memory. Rebind on every access — memory can be detached after grow. @internal */
|
|
74
63
|
get mem() {
|
|
75
64
|
return new Uint8Array(this.x.memory.buffer);
|
|
76
65
|
}
|
|
@@ -83,6 +72,8 @@ export class SerpentCbc {
|
|
|
83
72
|
* @returns ciphertext (length = ceil((plaintext.length + 1) / 16) * 16)
|
|
84
73
|
*/
|
|
85
74
|
encrypt(key, iv, plaintext) {
|
|
75
|
+
if (this._tok === undefined)
|
|
76
|
+
throw new Error('SerpentCbc: instance has been disposed');
|
|
86
77
|
this._loadKey(key);
|
|
87
78
|
this._setIv(iv);
|
|
88
79
|
const padded = pkcs7Pad(plaintext);
|
|
@@ -103,11 +94,19 @@ export class SerpentCbc {
|
|
|
103
94
|
}
|
|
104
95
|
/**
|
|
105
96
|
* Decrypt Serpent-256 CBC + PKCS7.
|
|
106
|
-
*
|
|
97
|
+
*
|
|
98
|
+
* All failure modes — empty input, non-multiple-of-16 length, and any
|
|
99
|
+
* PKCS7 validation failure — throw the same generic `RangeError` with
|
|
100
|
+
* message `'invalid ciphertext'`. Padding validation runs branch-free
|
|
101
|
+
* over the last 16 bytes regardless of where the mismatch is, closing
|
|
102
|
+
* the Vaudenay 2002 padding-oracle surface for callers using
|
|
103
|
+
* `{ dangerUnauthenticated: true }` without an outer HMAC.
|
|
107
104
|
*/
|
|
108
105
|
decrypt(key, iv, ciphertext) {
|
|
106
|
+
if (this._tok === undefined)
|
|
107
|
+
throw new Error('SerpentCbc: instance has been disposed');
|
|
109
108
|
if (ciphertext.length === 0 || ciphertext.length % 16 !== 0)
|
|
110
|
-
throw new RangeError(
|
|
109
|
+
throw new RangeError(PKCS7_INVALID);
|
|
111
110
|
this._loadKey(key);
|
|
112
111
|
this._setIv(iv);
|
|
113
112
|
const output = new Uint8Array(ciphertext.length);
|
|
@@ -125,15 +124,34 @@ export class SerpentCbc {
|
|
|
125
124
|
}
|
|
126
125
|
return pkcs7Strip(output);
|
|
127
126
|
}
|
|
127
|
+
/** Wipe WASM state and release exclusive module access. Idempotent. */
|
|
128
128
|
dispose() {
|
|
129
|
-
this.
|
|
129
|
+
if (this._tok === undefined)
|
|
130
|
+
return;
|
|
131
|
+
try {
|
|
132
|
+
this.x.wipeBuffers();
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
_releaseModule('serpent', this._tok);
|
|
136
|
+
this._tok = undefined;
|
|
137
|
+
}
|
|
130
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Validate and load `key` into the WASM key schedule.
|
|
141
|
+
* @param key 16, 24, or 32 bytes
|
|
142
|
+
* @internal
|
|
143
|
+
*/
|
|
131
144
|
_loadKey(key) {
|
|
132
145
|
if (key.length !== 16 && key.length !== 24 && key.length !== 32)
|
|
133
146
|
throw new RangeError(`Serpent key must be 16, 24, or 32 bytes (got ${key.length})`);
|
|
134
147
|
this.mem.set(key, this.x.getKeyOffset());
|
|
135
148
|
this.x.loadKey(key.length);
|
|
136
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Write `iv` into the WASM CBC IV buffer.
|
|
152
|
+
* @param iv 16 bytes
|
|
153
|
+
* @internal
|
|
154
|
+
*/
|
|
137
155
|
_setIv(iv) {
|
|
138
156
|
if (iv.length !== 16)
|
|
139
157
|
throw new RangeError(`CBC IV must be 16 bytes (got ${iv.length})`);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/** Subset of the sha2 WASM exports used by `hmacSha256`. */
|
|
2
|
+
export interface Sha2OpsExports {
|
|
3
|
+
memory: WebAssembly.Memory;
|
|
4
|
+
getSha256InputOffset: () => number;
|
|
5
|
+
getSha256OutOffset: () => number;
|
|
6
|
+
sha256Init: () => void;
|
|
7
|
+
sha256Update: (len: number) => void;
|
|
8
|
+
sha256Final: () => void;
|
|
9
|
+
hmac256Init: (keyLen: number) => void;
|
|
10
|
+
hmac256Update: (len: number) => void;
|
|
11
|
+
hmac256Final: () => void;
|
|
12
|
+
}
|
|
13
|
+
/** Subset of the serpent WASM exports used by `cbcEncryptChunk`/`cbcDecryptChunk`. */
|
|
14
|
+
export interface SerpentOpsExports {
|
|
15
|
+
memory: WebAssembly.Memory;
|
|
16
|
+
getKeyOffset: () => number;
|
|
17
|
+
getChunkPtOffset: () => number;
|
|
18
|
+
getChunkCtOffset: () => number;
|
|
19
|
+
getChunkSize: () => number;
|
|
20
|
+
getCbcIvOffset: () => number;
|
|
21
|
+
loadKey: (n: number) => number;
|
|
22
|
+
cbcEncryptChunk: (n: number) => number;
|
|
23
|
+
cbcDecryptChunk_simd: (n: number) => number;
|
|
24
|
+
}
|
|
25
|
+
export declare const PKCS7_INVALID = "invalid ciphertext";
|
|
26
|
+
/**
|
|
27
|
+
* Apply PKCS7 padding to `data` so the result length is a multiple of 16.
|
|
28
|
+
* Padding length is always 1–16 bytes so a full pad block is appended when
|
|
29
|
+
* `data.length` is already block-aligned.
|
|
30
|
+
* @param data Input bytes of any length
|
|
31
|
+
* @returns New Uint8Array padded to the next 16-byte boundary
|
|
32
|
+
*/
|
|
33
|
+
export declare function pkcs7Pad(data: Uint8Array): Uint8Array;
|
|
34
|
+
/**
|
|
35
|
+
* Remove PKCS7 padding from a block-aligned buffer in constant time.
|
|
36
|
+
*
|
|
37
|
+
* Branch-free over all secret bits — padding length and per-byte comparisons
|
|
38
|
+
* are accumulated into a single `bad` flag with no early exit. Closes the
|
|
39
|
+
* Vaudenay 2002 padding-oracle surface. Throws a single generic
|
|
40
|
+
* `RangeError('invalid ciphertext')` for every failure mode: empty input,
|
|
41
|
+
* non-block-aligned length, padding byte out of range 1–16, and any per-byte
|
|
42
|
+
* mismatch in the padding region.
|
|
43
|
+
* @param data Block-aligned ciphertext (length must be a multiple of 16)
|
|
44
|
+
* @returns Plaintext with padding removed
|
|
45
|
+
*/
|
|
46
|
+
export declare function pkcs7Strip(data: Uint8Array): Uint8Array;
|
|
47
|
+
/**
|
|
48
|
+
* Compute HMAC-SHA-256 using raw WASM sha2 exports.
|
|
49
|
+
*
|
|
50
|
+
* Keys longer than 64 bytes are pre-hashed per RFC 2104 §3. The SHA-256
|
|
51
|
+
* input buffer is fed in 64-byte chunks to match the WASM block size.
|
|
52
|
+
* Does not call `_acquireModule` — callers must ensure no stateful instance
|
|
53
|
+
* owns the sha2 module before calling.
|
|
54
|
+
* @param sx sha2 WASM exports
|
|
55
|
+
* @param key HMAC key of any length
|
|
56
|
+
* @param msg Message to authenticate
|
|
57
|
+
* @returns 32-byte HMAC-SHA-256 tag
|
|
58
|
+
*/
|
|
59
|
+
export declare function hmacSha256(sx: Sha2OpsExports, key: Uint8Array, msg: Uint8Array): Uint8Array;
|
|
60
|
+
/**
|
|
61
|
+
* Encrypt one plaintext chunk with Serpent-256 CBC + PKCS7 padding.
|
|
62
|
+
*
|
|
63
|
+
* The padded chunk must fit within the WASM CHUNK_SIZE. Callers are
|
|
64
|
+
* responsible for splitting larger payloads before calling.
|
|
65
|
+
* @param kx Serpent WASM exports
|
|
66
|
+
* @param key 16, 24, or 32-byte key
|
|
67
|
+
* @param iv 16-byte CBC initialisation vector
|
|
68
|
+
* @param chunk Plaintext chunk (padded length must be ≤ WASM CHUNK_SIZE)
|
|
69
|
+
* @returns Ciphertext with PKCS7 padding applied
|
|
70
|
+
*/
|
|
71
|
+
export declare function cbcEncryptChunk(kx: SerpentOpsExports, key: Uint8Array, iv: Uint8Array, chunk: Uint8Array): Uint8Array;
|
|
72
|
+
/**
|
|
73
|
+
* Decrypt one Serpent-256 CBC chunk using the SIMD path and strip PKCS7 padding.
|
|
74
|
+
*
|
|
75
|
+
* Output matches `SerpentCbc.decrypt` byte-for-byte. Throws
|
|
76
|
+
* `RangeError('invalid ciphertext')` on any length or padding failure.
|
|
77
|
+
* @param kx Serpent WASM exports
|
|
78
|
+
* @param key 16, 24, or 32-byte key
|
|
79
|
+
* @param iv 16-byte CBC initialisation vector
|
|
80
|
+
* @param ct Ciphertext (must be non-empty and a multiple of 16 bytes)
|
|
81
|
+
* @returns Plaintext with PKCS7 padding removed
|
|
82
|
+
*/
|
|
83
|
+
export declare function cbcDecryptChunk(kx: SerpentOpsExports, key: Uint8Array, iv: Uint8Array, ct: Uint8Array): Uint8Array;
|
|
@@ -0,0 +1,213 @@
|
|
|
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/shared-ops.ts
|
|
23
|
+
//
|
|
24
|
+
// Pure-function primitives shared between the main-thread `SerpentCipher`
|
|
25
|
+
// (cipher-suite.ts) and the `SealStreamPool` worker (pool-worker.ts). Both
|
|
26
|
+
// call sites hold their own WASM exports — pool workers instantiate modules
|
|
27
|
+
// locally, the main thread fetches via `getInstance()` — so every function
|
|
28
|
+
// here takes the sha2/serpent exports as parameters. No dependency on
|
|
29
|
+
// `init.ts`, no module-level state, no instance wrappers.
|
|
30
|
+
//
|
|
31
|
+
// These helpers are strictly single-chunk: the caller already divided the
|
|
32
|
+
// payload into chunks ≤ WASM CHUNK_SIZE. For multi-chunk use, see
|
|
33
|
+
// `SerpentCbc.encrypt`/`decrypt`, which loop over the same WASM exports.
|
|
34
|
+
//
|
|
35
|
+
// This file owns the canonical `pkcs7Pad` / `pkcs7Strip`; `serpent-cbc.ts`
|
|
36
|
+
// re-exports them. A single source of truth keeps the branch-free,
|
|
37
|
+
// Vaudenay-2002-closed padding check identical on the main-thread and
|
|
38
|
+
// pool-worker paths — divergence between the two would reintroduce an
|
|
39
|
+
// oracle.
|
|
40
|
+
// ── PKCS7 ───────────────────────────────────────────────────────────────────
|
|
41
|
+
// Generic error string used by every failure mode of `pkcs7Strip` and the
|
|
42
|
+
// length/alignment gate in `SerpentCbc.decrypt`. No numeric leaks, no
|
|
43
|
+
// structural disclosure — a caller cannot distinguish "bad length" from
|
|
44
|
+
// "bad padding" by message or by timing.
|
|
45
|
+
export const PKCS7_INVALID = 'invalid ciphertext';
|
|
46
|
+
/**
|
|
47
|
+
* Apply PKCS7 padding to `data` so the result length is a multiple of 16.
|
|
48
|
+
* Padding length is always 1–16 bytes so a full pad block is appended when
|
|
49
|
+
* `data.length` is already block-aligned.
|
|
50
|
+
* @param data Input bytes of any length
|
|
51
|
+
* @returns New Uint8Array padded to the next 16-byte boundary
|
|
52
|
+
*/
|
|
53
|
+
export function pkcs7Pad(data) {
|
|
54
|
+
const padLen = 16 - (data.length % 16); // 1..16
|
|
55
|
+
const out = new Uint8Array(data.length + padLen);
|
|
56
|
+
out.set(data);
|
|
57
|
+
out.fill(padLen, data.length);
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Remove PKCS7 padding from a block-aligned buffer in constant time.
|
|
62
|
+
*
|
|
63
|
+
* Branch-free over all secret bits — padding length and per-byte comparisons
|
|
64
|
+
* are accumulated into a single `bad` flag with no early exit. Closes the
|
|
65
|
+
* Vaudenay 2002 padding-oracle surface. Throws a single generic
|
|
66
|
+
* `RangeError('invalid ciphertext')` for every failure mode: empty input,
|
|
67
|
+
* non-block-aligned length, padding byte out of range 1–16, and any per-byte
|
|
68
|
+
* mismatch in the padding region.
|
|
69
|
+
* @param data Block-aligned ciphertext (length must be a multiple of 16)
|
|
70
|
+
* @returns Plaintext with padding removed
|
|
71
|
+
*/
|
|
72
|
+
export function pkcs7Strip(data) {
|
|
73
|
+
if (data.length === 0 || data.length % 16 !== 0)
|
|
74
|
+
throw new RangeError(PKCS7_INVALID);
|
|
75
|
+
const padLen = data[data.length - 1];
|
|
76
|
+
let bad = 0;
|
|
77
|
+
bad |= ((padLen - 1) >>> 31); // 1 if padLen == 0
|
|
78
|
+
bad |= ((16 - padLen) >>> 31); // 1 if padLen > 16
|
|
79
|
+
// Per-byte pad-region mask without branches on secret bits.
|
|
80
|
+
// inPadRegion = 0xff when i >= 16 - padLen
|
|
81
|
+
// = 0x00 otherwise
|
|
82
|
+
//
|
|
83
|
+
// (16 - padLen - i - 1) is negative iff i >= 16 - padLen. A signed
|
|
84
|
+
// arithmetic shift by 31 yields -1 for negative, 0 for non-negative;
|
|
85
|
+
// ANDing with 0xff collapses those to 0xff and 0x00.
|
|
86
|
+
for (let i = 0; i < 16; i++) {
|
|
87
|
+
const idx = data.length - 16 + i;
|
|
88
|
+
const mask = ((16 - padLen - i - 1) >> 31) & 0xff;
|
|
89
|
+
bad |= (data[idx] ^ padLen) & mask;
|
|
90
|
+
}
|
|
91
|
+
const invalid = ((bad - 1) >>> 31) ^ 1;
|
|
92
|
+
if (invalid)
|
|
93
|
+
throw new RangeError(PKCS7_INVALID);
|
|
94
|
+
return data.subarray(0, data.length - padLen);
|
|
95
|
+
}
|
|
96
|
+
// ── HMAC-SHA-256 ────────────────────────────────────────────────────────────
|
|
97
|
+
/**
|
|
98
|
+
* Compute HMAC-SHA-256 using raw WASM sha2 exports.
|
|
99
|
+
*
|
|
100
|
+
* Keys longer than 64 bytes are pre-hashed per RFC 2104 §3. The SHA-256
|
|
101
|
+
* input buffer is fed in 64-byte chunks to match the WASM block size.
|
|
102
|
+
* Does not call `_acquireModule` — callers must ensure no stateful instance
|
|
103
|
+
* owns the sha2 module before calling.
|
|
104
|
+
* @param sx sha2 WASM exports
|
|
105
|
+
* @param key HMAC key of any length
|
|
106
|
+
* @param msg Message to authenticate
|
|
107
|
+
* @returns 32-byte HMAC-SHA-256 tag
|
|
108
|
+
*/
|
|
109
|
+
export function hmacSha256(sx, key, msg) {
|
|
110
|
+
const inOff = sx.getSha256InputOffset();
|
|
111
|
+
const outOff = sx.getSha256OutOffset();
|
|
112
|
+
let k = key;
|
|
113
|
+
if (k.length > 64) {
|
|
114
|
+
sx.sha256Init();
|
|
115
|
+
feedMemory(sx.memory, inOff, k, 64, sx.sha256Update);
|
|
116
|
+
sx.sha256Final();
|
|
117
|
+
k = new Uint8Array(sx.memory.buffer).slice(outOff, outOff + 32);
|
|
118
|
+
}
|
|
119
|
+
const mem = new Uint8Array(sx.memory.buffer);
|
|
120
|
+
mem.set(k, inOff);
|
|
121
|
+
sx.hmac256Init(k.length);
|
|
122
|
+
feedMemory(sx.memory, inOff, msg, 64, sx.hmac256Update);
|
|
123
|
+
sx.hmac256Final();
|
|
124
|
+
return new Uint8Array(sx.memory.buffer).slice(outOff, outOff + 32);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Copy `msg` into WASM linear memory at `inputOff` in `chunkSize`-byte
|
|
128
|
+
* increments, calling `update(n)` after each write.
|
|
129
|
+
* @param memory WASM memory object
|
|
130
|
+
* @param inputOff Byte offset of the WASM input buffer
|
|
131
|
+
* @param msg Data to feed
|
|
132
|
+
* @param chunkSize Maximum bytes per write (must match WASM buffer size)
|
|
133
|
+
* @param update WASM update function to call after each write
|
|
134
|
+
* @internal
|
|
135
|
+
*/
|
|
136
|
+
function feedMemory(memory, inputOff, msg, chunkSize, update) {
|
|
137
|
+
const mem = new Uint8Array(memory.buffer);
|
|
138
|
+
let pos = 0;
|
|
139
|
+
while (pos < msg.length) {
|
|
140
|
+
const n = Math.min(msg.length - pos, chunkSize);
|
|
141
|
+
mem.set(msg.subarray(pos, pos + n), inputOff);
|
|
142
|
+
update(n);
|
|
143
|
+
pos += n;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// ── Serpent-CBC (single chunk) ──────────────────────────────────────────────
|
|
147
|
+
/**
|
|
148
|
+
* Encrypt one plaintext chunk with Serpent-256 CBC + PKCS7 padding.
|
|
149
|
+
*
|
|
150
|
+
* The padded chunk must fit within the WASM CHUNK_SIZE. Callers are
|
|
151
|
+
* responsible for splitting larger payloads before calling.
|
|
152
|
+
* @param kx Serpent WASM exports
|
|
153
|
+
* @param key 16, 24, or 32-byte key
|
|
154
|
+
* @param iv 16-byte CBC initialisation vector
|
|
155
|
+
* @param chunk Plaintext chunk (padded length must be ≤ WASM CHUNK_SIZE)
|
|
156
|
+
* @returns Ciphertext with PKCS7 padding applied
|
|
157
|
+
*/
|
|
158
|
+
export function cbcEncryptChunk(kx, key, iv, chunk) {
|
|
159
|
+
loadKeyAndIv(kx, key, iv);
|
|
160
|
+
const padded = pkcs7Pad(chunk);
|
|
161
|
+
const ptOff = kx.getChunkPtOffset();
|
|
162
|
+
const ctOff = kx.getChunkCtOffset();
|
|
163
|
+
const mem = new Uint8Array(kx.memory.buffer);
|
|
164
|
+
mem.set(padded, ptOff);
|
|
165
|
+
const ret = kx.cbcEncryptChunk(padded.length);
|
|
166
|
+
if (ret < 0)
|
|
167
|
+
throw new RangeError(`cbcEncryptChunk rejected len=${padded.length}` +
|
|
168
|
+
` (WASM CHUNK_SIZE=${kx.getChunkSize()})`);
|
|
169
|
+
return new Uint8Array(kx.memory.buffer).slice(ctOff, ctOff + padded.length);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Decrypt one Serpent-256 CBC chunk using the SIMD path and strip PKCS7 padding.
|
|
173
|
+
*
|
|
174
|
+
* Output matches `SerpentCbc.decrypt` byte-for-byte. Throws
|
|
175
|
+
* `RangeError('invalid ciphertext')` on any length or padding failure.
|
|
176
|
+
* @param kx Serpent WASM exports
|
|
177
|
+
* @param key 16, 24, or 32-byte key
|
|
178
|
+
* @param iv 16-byte CBC initialisation vector
|
|
179
|
+
* @param ct Ciphertext (must be non-empty and a multiple of 16 bytes)
|
|
180
|
+
* @returns Plaintext with PKCS7 padding removed
|
|
181
|
+
*/
|
|
182
|
+
export function cbcDecryptChunk(kx, key, iv, ct) {
|
|
183
|
+
if (ct.length === 0 || ct.length % 16 !== 0)
|
|
184
|
+
throw new RangeError(PKCS7_INVALID);
|
|
185
|
+
loadKeyAndIv(kx, key, iv);
|
|
186
|
+
const ctOff = kx.getChunkCtOffset();
|
|
187
|
+
const ptOff = kx.getChunkPtOffset();
|
|
188
|
+
const mem = new Uint8Array(kx.memory.buffer);
|
|
189
|
+
mem.set(ct, ctOff);
|
|
190
|
+
const ret = kx.cbcDecryptChunk_simd(ct.length);
|
|
191
|
+
if (ret < 0)
|
|
192
|
+
throw new RangeError(`cbcDecryptChunk_simd rejected len=${ct.length}` +
|
|
193
|
+
` (WASM CHUNK_SIZE=${kx.getChunkSize()})`);
|
|
194
|
+
const raw = new Uint8Array(kx.memory.buffer).slice(ptOff, ptOff + ct.length);
|
|
195
|
+
return pkcs7Strip(raw);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Validate, then write `key` and `iv` into the WASM buffers and expand the key schedule.
|
|
199
|
+
* @param kx Serpent WASM exports
|
|
200
|
+
* @param key 16, 24, or 32-byte Serpent key
|
|
201
|
+
* @param iv 16-byte CBC initialisation vector
|
|
202
|
+
* @internal
|
|
203
|
+
*/
|
|
204
|
+
function loadKeyAndIv(kx, key, iv) {
|
|
205
|
+
if (key.length !== 16 && key.length !== 24 && key.length !== 32)
|
|
206
|
+
throw new RangeError(`Serpent key must be 16, 24, or 32 bytes (got ${key.length})`);
|
|
207
|
+
if (iv.length !== 16)
|
|
208
|
+
throw new RangeError(`CBC IV must be 16 bytes (got ${iv.length})`);
|
|
209
|
+
const mem = new Uint8Array(kx.memory.buffer);
|
|
210
|
+
mem.set(key, kx.getKeyOffset());
|
|
211
|
+
kx.loadKey(key.length);
|
|
212
|
+
mem.set(iv, kx.getCbcIvOffset());
|
|
213
|
+
}
|
package/dist/serpent/types.d.ts
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
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/hash.ts
|
|
23
|
+
//
|
|
24
|
+
// Stateless SHA-256 HashFn for Fortuna's accumulator and reseed slots.
|
|
25
|
+
import { _assertNotOwned, getInstance } from '../init.js';
|
|
26
|
+
export const SHA256Hash = {
|
|
27
|
+
outputSize: 32,
|
|
28
|
+
wasmModules: ['sha2'],
|
|
29
|
+
digest(msg) {
|
|
30
|
+
_assertNotOwned('sha2');
|
|
31
|
+
const x = getInstance('sha2').exports;
|
|
32
|
+
const mem = new Uint8Array(x.memory.buffer);
|
|
33
|
+
try {
|
|
34
|
+
x.sha256Init();
|
|
35
|
+
const inOff = x.getSha256InputOffset();
|
|
36
|
+
let pos = 0;
|
|
37
|
+
while (pos < msg.length) {
|
|
38
|
+
const n = Math.min(msg.length - pos, 64);
|
|
39
|
+
mem.set(msg.subarray(pos, pos + n), inOff);
|
|
40
|
+
x.sha256Update(n);
|
|
41
|
+
pos += n;
|
|
42
|
+
}
|
|
43
|
+
x.sha256Final();
|
|
44
|
+
const outOff = x.getSha256OutOffset();
|
|
45
|
+
return mem.slice(outOff, outOff + 32);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
// Wipe the SHA-256 input/output/state scratch so secret-derived
|
|
49
|
+
// inputs (e.g. Fortuna pool entropy) do not outlive this call.
|
|
50
|
+
x.wipeBuffers();
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
package/dist/sha2/index.d.ts
CHANGED
package/dist/sha2/index.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
//
|
|
24
24
|
// Public API classes for the SHA-2 WASM module.
|
|
25
25
|
// Uses the init() module cache — call sha2Init(source) before constructing.
|
|
26
|
-
import { getInstance, initModule } from '../init.js';
|
|
26
|
+
import { getInstance, initModule, _assertNotOwned } from '../init.js';
|
|
27
27
|
export async function sha2Init(source) {
|
|
28
28
|
return initModule('sha2', source);
|
|
29
29
|
}
|
|
@@ -57,6 +57,7 @@ export class SHA256 {
|
|
|
57
57
|
this.x = getExports();
|
|
58
58
|
}
|
|
59
59
|
hash(msg) {
|
|
60
|
+
_assertNotOwned('sha2');
|
|
60
61
|
this.x.sha256Init();
|
|
61
62
|
feedHash(this.x, msg, this.x.getSha256InputOffset(), 64, this.x.sha256Update);
|
|
62
63
|
this.x.sha256Final();
|
|
@@ -64,6 +65,7 @@ export class SHA256 {
|
|
|
64
65
|
return mem.slice(this.x.getSha256OutOffset(), this.x.getSha256OutOffset() + 32);
|
|
65
66
|
}
|
|
66
67
|
dispose() {
|
|
68
|
+
_assertNotOwned('sha2');
|
|
67
69
|
this.x.wipeBuffers();
|
|
68
70
|
}
|
|
69
71
|
}
|
|
@@ -74,6 +76,7 @@ export class SHA512 {
|
|
|
74
76
|
this.x = getExports();
|
|
75
77
|
}
|
|
76
78
|
hash(msg) {
|
|
79
|
+
_assertNotOwned('sha2');
|
|
77
80
|
this.x.sha512Init();
|
|
78
81
|
feedHash(this.x, msg, this.x.getSha512InputOffset(), 128, this.x.sha512Update);
|
|
79
82
|
this.x.sha512Final();
|
|
@@ -81,6 +84,7 @@ export class SHA512 {
|
|
|
81
84
|
return mem.slice(this.x.getSha512OutOffset(), this.x.getSha512OutOffset() + 64);
|
|
82
85
|
}
|
|
83
86
|
dispose() {
|
|
87
|
+
_assertNotOwned('sha2');
|
|
84
88
|
this.x.wipeBuffers();
|
|
85
89
|
}
|
|
86
90
|
}
|
|
@@ -91,6 +95,7 @@ export class SHA384 {
|
|
|
91
95
|
this.x = getExports();
|
|
92
96
|
}
|
|
93
97
|
hash(msg) {
|
|
98
|
+
_assertNotOwned('sha2');
|
|
94
99
|
this.x.sha384Init();
|
|
95
100
|
feedHash(this.x, msg, this.x.getSha512InputOffset(), 128, this.x.sha512Update);
|
|
96
101
|
this.x.sha384Final();
|
|
@@ -98,6 +103,7 @@ export class SHA384 {
|
|
|
98
103
|
return mem.slice(this.x.getSha512OutOffset(), this.x.getSha512OutOffset() + 48);
|
|
99
104
|
}
|
|
100
105
|
dispose() {
|
|
106
|
+
_assertNotOwned('sha2');
|
|
101
107
|
this.x.wipeBuffers();
|
|
102
108
|
}
|
|
103
109
|
}
|
|
@@ -108,6 +114,7 @@ export class HMAC_SHA256 {
|
|
|
108
114
|
this.x = getExports();
|
|
109
115
|
}
|
|
110
116
|
hash(key, msg) {
|
|
117
|
+
_assertNotOwned('sha2');
|
|
111
118
|
let k = key;
|
|
112
119
|
// RFC 2104 §3: keys longer than block size are pre-hashed
|
|
113
120
|
if (k.length > 64) {
|
|
@@ -126,6 +133,7 @@ export class HMAC_SHA256 {
|
|
|
126
133
|
return out.slice(this.x.getSha256OutOffset(), this.x.getSha256OutOffset() + 32);
|
|
127
134
|
}
|
|
128
135
|
dispose() {
|
|
136
|
+
_assertNotOwned('sha2');
|
|
129
137
|
this.x.wipeBuffers();
|
|
130
138
|
}
|
|
131
139
|
}
|
|
@@ -136,6 +144,7 @@ export class HMAC_SHA512 {
|
|
|
136
144
|
this.x = getExports();
|
|
137
145
|
}
|
|
138
146
|
hash(key, msg) {
|
|
147
|
+
_assertNotOwned('sha2');
|
|
139
148
|
let k = key;
|
|
140
149
|
// RFC 2104 §3: keys longer than block size (128) are pre-hashed
|
|
141
150
|
if (k.length > 128) {
|
|
@@ -154,6 +163,7 @@ export class HMAC_SHA512 {
|
|
|
154
163
|
return out.slice(this.x.getSha512OutOffset(), this.x.getSha512OutOffset() + 64);
|
|
155
164
|
}
|
|
156
165
|
dispose() {
|
|
166
|
+
_assertNotOwned('sha2');
|
|
157
167
|
this.x.wipeBuffers();
|
|
158
168
|
}
|
|
159
169
|
}
|
|
@@ -164,6 +174,7 @@ export class HMAC_SHA384 {
|
|
|
164
174
|
this.x = getExports();
|
|
165
175
|
}
|
|
166
176
|
hash(key, msg) {
|
|
177
|
+
_assertNotOwned('sha2');
|
|
167
178
|
let k = key;
|
|
168
179
|
// RFC 2104 §3: keys longer than block size (128) are pre-hashed with SHA-384
|
|
169
180
|
if (k.length > 128) {
|
|
@@ -182,8 +193,11 @@ export class HMAC_SHA384 {
|
|
|
182
193
|
return out.slice(this.x.getSha512OutOffset(), this.x.getSha512OutOffset() + 48);
|
|
183
194
|
}
|
|
184
195
|
dispose() {
|
|
196
|
+
_assertNotOwned('sha2');
|
|
185
197
|
this.x.wipeBuffers();
|
|
186
198
|
}
|
|
187
199
|
}
|
|
188
200
|
// ── HKDF ────────────────────────────────────────────────────────────────────
|
|
189
201
|
export { HKDF_SHA256, HKDF_SHA512 } from './hkdf.js';
|
|
202
|
+
// ── SHA256Hash ──────────────────────────────────────────────────────────────
|
|
203
|
+
export { SHA256Hash } from './hash.js';
|
|
@@ -0,0 +1,53 @@
|
|
|
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/hash.ts
|
|
23
|
+
//
|
|
24
|
+
// Stateless SHA3-256 HashFn for Fortuna's accumulator and reseed slots.
|
|
25
|
+
import { _assertNotOwned, getInstance } from '../init.js';
|
|
26
|
+
export const SHA3_256Hash = {
|
|
27
|
+
outputSize: 32,
|
|
28
|
+
wasmModules: ['sha3'],
|
|
29
|
+
digest(msg) {
|
|
30
|
+
_assertNotOwned('sha3');
|
|
31
|
+
const x = getInstance('sha3').exports;
|
|
32
|
+
const mem = new Uint8Array(x.memory.buffer);
|
|
33
|
+
try {
|
|
34
|
+
x.sha3_256Init();
|
|
35
|
+
const inOff = x.getInputOffset();
|
|
36
|
+
let pos = 0;
|
|
37
|
+
while (pos < msg.length) {
|
|
38
|
+
const n = Math.min(msg.length - pos, 168);
|
|
39
|
+
mem.set(msg.subarray(pos, pos + n), inOff);
|
|
40
|
+
x.keccakAbsorb(n);
|
|
41
|
+
pos += n;
|
|
42
|
+
}
|
|
43
|
+
x.sha3_256Final();
|
|
44
|
+
const outOff = x.getOutOffset();
|
|
45
|
+
return mem.slice(outOff, outOff + 32);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
// Wipe the keccak input/output/sponge state so secret-derived
|
|
49
|
+
// inputs (e.g. Fortuna pool entropy) do not outlive this call.
|
|
50
|
+
x.wipeBuffers();
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|