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
package/dist/embedded/kyber.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const WASM_GZ_BASE64 = "
|
|
1
|
+
export declare const WASM_GZ_BASE64 = "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";
|
package/dist/embedded/kyber.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Generated by scripts/embed-wasm.ts — do not edit
|
|
2
2
|
// gzip-compressed WASM binary, base64-encoded. Decoded and decompressed by loadEmbedded().
|
|
3
|
-
export const WASM_GZ_BASE64 = '
|
|
3
|
+
export const WASM_GZ_BASE64 = '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';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const WORKER_SOURCE = "\"use strict\";(()=>{var U=new Uint8Array([0,97,115,109,1,0,0,0,1,8,1,96,3,127,127,127,1,127,3,2,1,0,5,4,1,1,1,1,7,20,2,7,99,111,109,112,97,114,101,0,0,6,109,101,109,111,114,121,2,0,10,133,1,1,130,1,3,2,127,1,126,1,123,3,64,32,3,65,16,106,34,4,32,2,76,4,64,32,6,32,0,32,3,106,253,0,4,0,32,1,32,3,106,253,0,4,0,253,81,253,80,33,6,32,4,33,3,12,1,11,11,3,64,32,2,32,3,74,4,64,32,5,32,0,32,3,106,49,0,0,32,1,32,3,106,49,0,0,133,132,33,5,32,3,65,1,106,33,3,12,1,11,11,66,0,32,5,32,6,253,29,0,32,6,253,29,1,132,132,34,5,125,32,5,132,66,63,135,66,127,133,167,65,1,113,11]);var R=null,K=null,I=!1,m=null,T=32768;function P(){if(I){if(m)throw m;return}if(I=!0,!V())throw m=new Error(\"leviathan-crypto: constantTimeEqual requires WebAssembly SIMD \\u2014 this runtime does not support it\"),m;try{let e=U.buffer.slice(U.byteOffset,U.byteOffset+U.byteLength),t=new WebAssembly.Module(e),o=new WebAssembly.Instance(t).exports;K=o.memory,R=o.compare}catch(e){throw m=new Error(`leviathan-crypto: ct WASM module failed to instantiate: ${e.message}`),m}}var _=(e,t)=>{if(e.length!==t.length)return!1;if(e.length>T)throw new RangeError(`constantTimeEqual: max ${T} bytes (got ${e.length})`);P();let n=K,o=R;if(!n||!o)throw new Error(\"leviathan-crypto: ct init invariant violated\");let r=new Uint8Array(n.buffer);r.set(e,0),r.set(t,e.length);try{return o(0,e.length,e.length)===1}finally{r.fill(0,0,e.length*2)}};var i=e=>{e.fill(0)};var S=(...e)=>{let t=e.reduce((r,s)=>r+s.length,0),n=new Uint8Array(t),o=0;for(let r of e)n.set(r,o),o+=r.length;return n};var h=null;function V(){if(h!==null)return h;if(typeof WebAssembly>\"u\"||typeof WebAssembly.validate!=\"function\")return h=!1,h;try{h=WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11]))}catch{h=!1}return h}var E=class e extends Error{constructor(t){super(`${t}: authentication failed`),this.name=\"AuthenticationError\",Object.setPrototypeOf(this,e.prototype)}};var x=\"invalid ciphertext\";function q(e){let t=16-e.length%16,n=new Uint8Array(e.length+t);return n.set(e),n.fill(t,e.length),n}function j(e){if(e.length===0||e.length%16!==0)throw new RangeError(x);let t=e[e.length-1],n=0;n|=t-1>>>31,n|=16-t>>>31;for(let r=0;r<16;r++){let s=e.length-16+r,a=16-t-r-1>>31&255;n|=(e[s]^t)&a}if(n-1>>>31^1)throw new RangeError(x);return e.subarray(0,e.length-t)}function v(e,t,n){let o=e.getSha256InputOffset(),r=e.getSha256OutOffset(),s=t;return s.length>64&&(e.sha256Init(),$(e.memory,o,s,64,e.sha256Update),e.sha256Final(),s=new Uint8Array(e.memory.buffer).slice(r,r+32)),new Uint8Array(e.memory.buffer).set(s,o),e.hmac256Init(s.length),$(e.memory,o,n,64,e.hmac256Update),e.hmac256Final(),new Uint8Array(e.memory.buffer).slice(r,r+32)}function $(e,t,n,o,r){let s=new Uint8Array(e.buffer),a=0;for(;a<n.length;){let l=Math.min(n.length-a,o);s.set(n.subarray(a,a+l),t),r(l),a+=l}}function F(e,t,n,o){k(e,t,n);let r=q(o),s=e.getChunkPtOffset(),a=e.getChunkCtOffset();if(new Uint8Array(e.memory.buffer).set(r,s),e.cbcEncryptChunk(r.length)<0)throw new RangeError(`cbcEncryptChunk rejected len=${r.length} (WASM CHUNK_SIZE=${e.getChunkSize()})`);return new Uint8Array(e.memory.buffer).slice(a,a+r.length)}function D(e,t,n,o){if(o.length===0||o.length%16!==0)throw new RangeError(x);k(e,t,n);let r=e.getChunkCtOffset(),s=e.getChunkPtOffset();if(new Uint8Array(e.memory.buffer).set(o,r),e.cbcDecryptChunk_simd(o.length)<0)throw new RangeError(`cbcDecryptChunk_simd rejected len=${o.length} (WASM CHUNK_SIZE=${e.getChunkSize()})`);let y=new Uint8Array(e.memory.buffer).slice(s,s+o.length);return j(y)}function k(e,t,n){if(t.length!==16&&t.length!==24&&t.length!==32)throw new RangeError(`Serpent key must be 16, 24, or 32 bytes (got ${t.length})`);if(n.length!==16)throw new RangeError(`CBC IV must be 16 bytes (got ${n.length})`);let o=new Uint8Array(e.memory.buffer);o.set(t,e.getKeyOffset()),e.loadKey(t.length),o.set(n,e.getCbcIvOffset())}var c,f,u;self.onmessage=async e=>{let t=e.data;if(t.type===\"init\"){try{let n=new WebAssembly.Memory({initial:3,maximum:3});c=(await WebAssembly.instantiate(t.modules.sha2,{env:{memory:n}})).exports;let r=new WebAssembly.Memory({initial:3,maximum:3});if(f=(await WebAssembly.instantiate(t.modules.serpent,{env:{memory:r}})).exports,u=new Uint8Array(t.derivedKeyBytes),u.length!==96)throw new Error(`expected 96 derived key bytes (got ${u.length})`);t.derivedKeyBytes.fill(0),self.postMessage({type:\"ready\"})}catch(n){self.postMessage({type:\"error\",id:-1,message:n.message,isAuthError:!1})}return}if(t.type===\"wipe\"){u&&u.fill(0),u=void 0,c&&c.wipeBuffers(),f&&f.wipeBuffers(),c=void 0,f=void 0,self.postMessage({type:\"wiped\"});return}if(!u||!c||!f){self.postMessage({type:\"error\",id:t.id,message:\"worker not initialized\",isAuthError:!1});return}try{let{id:n,op:o,counterNonce:r,data:s,aad:a}=t,l=a??new Uint8Array(0),y=t.derivedKeyBytes??u,W=y.subarray(0,32),M=y.subarray(32,64),B=y.subarray(64,96),g;if(o===\"seal\"){let d=v(c,B,r),C=d.slice(0,16);i(d);let A=F(f,W,C,s),p=new Uint8Array(4);new DataView(p.buffer).setUint32(0,l.length,!1);let b=S(r,p,l,A),w=v(c,M,b);g=S(A,w),i(C),i(b)}else{let d=s.subarray(0,s.length-32),C=s.subarray(s.length-32),A=v(c,B,r),p=A.slice(0,16);i(A);let b=new Uint8Array(4);new DataView(b.buffer).setUint32(0,l.length,!1);let w=S(r,b,l,d),O=v(c,M,w);if(!_(O,C))throw i(p),i(w),i(O),new E(\"serpent\");i(w),i(O),g=D(f,W,p,d),i(p)}let L=g.buffer instanceof ArrayBuffer?[g.buffer]:[];self.postMessage({type:\"result\",id:n,data:g},{transfer:L})}catch(n){let o=n instanceof E;self.postMessage({type:\"error\",id:t.id,message:n.message,cipher:o?\"serpent\":void 0,isAuthError:o})}finally{t.derivedKeyBytes&&t.derivedKeyBytes.fill(0),c&&c.wipeBuffers(),f&&f.wipeBuffers()}};})();\n";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Generated by scripts/embed-workers.ts — do not edit
|
|
2
|
+
// IIFE-bundled pool worker source. Spawned via blob URL by
|
|
3
|
+
// the cipher-suite.ts createPoolWorker; consumers do not
|
|
4
|
+
// import this directly.
|
|
5
|
+
export const WORKER_SOURCE = "\"use strict\";(()=>{var U=new Uint8Array([0,97,115,109,1,0,0,0,1,8,1,96,3,127,127,127,1,127,3,2,1,0,5,4,1,1,1,1,7,20,2,7,99,111,109,112,97,114,101,0,0,6,109,101,109,111,114,121,2,0,10,133,1,1,130,1,3,2,127,1,126,1,123,3,64,32,3,65,16,106,34,4,32,2,76,4,64,32,6,32,0,32,3,106,253,0,4,0,32,1,32,3,106,253,0,4,0,253,81,253,80,33,6,32,4,33,3,12,1,11,11,3,64,32,2,32,3,74,4,64,32,5,32,0,32,3,106,49,0,0,32,1,32,3,106,49,0,0,133,132,33,5,32,3,65,1,106,33,3,12,1,11,11,66,0,32,5,32,6,253,29,0,32,6,253,29,1,132,132,34,5,125,32,5,132,66,63,135,66,127,133,167,65,1,113,11]);var R=null,K=null,I=!1,m=null,T=32768;function P(){if(I){if(m)throw m;return}if(I=!0,!V())throw m=new Error(\"leviathan-crypto: constantTimeEqual requires WebAssembly SIMD \\u2014 this runtime does not support it\"),m;try{let e=U.buffer.slice(U.byteOffset,U.byteOffset+U.byteLength),t=new WebAssembly.Module(e),o=new WebAssembly.Instance(t).exports;K=o.memory,R=o.compare}catch(e){throw m=new Error(`leviathan-crypto: ct WASM module failed to instantiate: ${e.message}`),m}}var _=(e,t)=>{if(e.length!==t.length)return!1;if(e.length>T)throw new RangeError(`constantTimeEqual: max ${T} bytes (got ${e.length})`);P();let n=K,o=R;if(!n||!o)throw new Error(\"leviathan-crypto: ct init invariant violated\");let r=new Uint8Array(n.buffer);r.set(e,0),r.set(t,e.length);try{return o(0,e.length,e.length)===1}finally{r.fill(0,0,e.length*2)}};var i=e=>{e.fill(0)};var S=(...e)=>{let t=e.reduce((r,s)=>r+s.length,0),n=new Uint8Array(t),o=0;for(let r of e)n.set(r,o),o+=r.length;return n};var h=null;function V(){if(h!==null)return h;if(typeof WebAssembly>\"u\"||typeof WebAssembly.validate!=\"function\")return h=!1,h;try{h=WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11]))}catch{h=!1}return h}var E=class e extends Error{constructor(t){super(`${t}: authentication failed`),this.name=\"AuthenticationError\",Object.setPrototypeOf(this,e.prototype)}};var x=\"invalid ciphertext\";function q(e){let t=16-e.length%16,n=new Uint8Array(e.length+t);return n.set(e),n.fill(t,e.length),n}function j(e){if(e.length===0||e.length%16!==0)throw new RangeError(x);let t=e[e.length-1],n=0;n|=t-1>>>31,n|=16-t>>>31;for(let r=0;r<16;r++){let s=e.length-16+r,a=16-t-r-1>>31&255;n|=(e[s]^t)&a}if(n-1>>>31^1)throw new RangeError(x);return e.subarray(0,e.length-t)}function v(e,t,n){let o=e.getSha256InputOffset(),r=e.getSha256OutOffset(),s=t;return s.length>64&&(e.sha256Init(),$(e.memory,o,s,64,e.sha256Update),e.sha256Final(),s=new Uint8Array(e.memory.buffer).slice(r,r+32)),new Uint8Array(e.memory.buffer).set(s,o),e.hmac256Init(s.length),$(e.memory,o,n,64,e.hmac256Update),e.hmac256Final(),new Uint8Array(e.memory.buffer).slice(r,r+32)}function $(e,t,n,o,r){let s=new Uint8Array(e.buffer),a=0;for(;a<n.length;){let l=Math.min(n.length-a,o);s.set(n.subarray(a,a+l),t),r(l),a+=l}}function F(e,t,n,o){k(e,t,n);let r=q(o),s=e.getChunkPtOffset(),a=e.getChunkCtOffset();if(new Uint8Array(e.memory.buffer).set(r,s),e.cbcEncryptChunk(r.length)<0)throw new RangeError(`cbcEncryptChunk rejected len=${r.length} (WASM CHUNK_SIZE=${e.getChunkSize()})`);return new Uint8Array(e.memory.buffer).slice(a,a+r.length)}function D(e,t,n,o){if(o.length===0||o.length%16!==0)throw new RangeError(x);k(e,t,n);let r=e.getChunkCtOffset(),s=e.getChunkPtOffset();if(new Uint8Array(e.memory.buffer).set(o,r),e.cbcDecryptChunk_simd(o.length)<0)throw new RangeError(`cbcDecryptChunk_simd rejected len=${o.length} (WASM CHUNK_SIZE=${e.getChunkSize()})`);let y=new Uint8Array(e.memory.buffer).slice(s,s+o.length);return j(y)}function k(e,t,n){if(t.length!==16&&t.length!==24&&t.length!==32)throw new RangeError(`Serpent key must be 16, 24, or 32 bytes (got ${t.length})`);if(n.length!==16)throw new RangeError(`CBC IV must be 16 bytes (got ${n.length})`);let o=new Uint8Array(e.memory.buffer);o.set(t,e.getKeyOffset()),e.loadKey(t.length),o.set(n,e.getCbcIvOffset())}var c,f,u;self.onmessage=async e=>{let t=e.data;if(t.type===\"init\"){try{let n=new WebAssembly.Memory({initial:3,maximum:3});c=(await WebAssembly.instantiate(t.modules.sha2,{env:{memory:n}})).exports;let r=new WebAssembly.Memory({initial:3,maximum:3});if(f=(await WebAssembly.instantiate(t.modules.serpent,{env:{memory:r}})).exports,u=new Uint8Array(t.derivedKeyBytes),u.length!==96)throw new Error(`expected 96 derived key bytes (got ${u.length})`);t.derivedKeyBytes.fill(0),self.postMessage({type:\"ready\"})}catch(n){self.postMessage({type:\"error\",id:-1,message:n.message,isAuthError:!1})}return}if(t.type===\"wipe\"){u&&u.fill(0),u=void 0,c&&c.wipeBuffers(),f&&f.wipeBuffers(),c=void 0,f=void 0,self.postMessage({type:\"wiped\"});return}if(!u||!c||!f){self.postMessage({type:\"error\",id:t.id,message:\"worker not initialized\",isAuthError:!1});return}try{let{id:n,op:o,counterNonce:r,data:s,aad:a}=t,l=a??new Uint8Array(0),y=t.derivedKeyBytes??u,W=y.subarray(0,32),M=y.subarray(32,64),B=y.subarray(64,96),g;if(o===\"seal\"){let d=v(c,B,r),C=d.slice(0,16);i(d);let A=F(f,W,C,s),p=new Uint8Array(4);new DataView(p.buffer).setUint32(0,l.length,!1);let b=S(r,p,l,A),w=v(c,M,b);g=S(A,w),i(C),i(b)}else{let d=s.subarray(0,s.length-32),C=s.subarray(s.length-32),A=v(c,B,r),p=A.slice(0,16);i(A);let b=new Uint8Array(4);new DataView(b.buffer).setUint32(0,l.length,!1);let w=S(r,b,l,d),O=v(c,M,w);if(!_(O,C))throw i(p),i(w),i(O),new E(\"serpent\");i(w),i(O),g=D(f,W,p,d),i(p)}let L=g.buffer instanceof ArrayBuffer?[g.buffer]:[];self.postMessage({type:\"result\",id:n,data:g},{transfer:L})}catch(n){let o=n instanceof E;self.postMessage({type:\"error\",id:t.id,message:n.message,cipher:o?\"serpent\":void 0,isAuthError:o})}finally{t.derivedKeyBytes&&t.derivedKeyBytes.fill(0),c&&c.wipeBuffers(),f&&f.wipeBuffers()}};})();\n";
|
package/dist/fortuna.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type { Generator, HashFn } from './types.js';
|
|
1
2
|
/**
|
|
2
3
|
* Fortuna CSPRNG — spec §9.3–§9.5
|
|
3
4
|
*
|
|
4
|
-
* Use `Fortuna.create()` to instantiate. Direct construction is not allowed.
|
|
5
|
+
* Use `Fortuna.create({ generator, hash })` to instantiate. Direct construction is not allowed.
|
|
5
6
|
*/
|
|
6
7
|
export declare class Fortuna {
|
|
7
8
|
private static readonly NUM_POOLS;
|
|
@@ -9,8 +10,8 @@ export declare class Fortuna {
|
|
|
9
10
|
private static readonly MS_PER_RESEED;
|
|
10
11
|
private static readonly NODE_STATS_INTERVAL;
|
|
11
12
|
private static readonly CRYPTO_INTERVAL;
|
|
12
|
-
private
|
|
13
|
-
private
|
|
13
|
+
private gen;
|
|
14
|
+
private hash;
|
|
14
15
|
private poolHash;
|
|
15
16
|
private poolEntropy;
|
|
16
17
|
private genKey;
|
|
@@ -25,7 +26,9 @@ export declare class Fortuna {
|
|
|
25
26
|
private robin;
|
|
26
27
|
private boundCollectors;
|
|
27
28
|
private timers;
|
|
28
|
-
static create(opts
|
|
29
|
+
static create(opts: {
|
|
30
|
+
generator: Generator;
|
|
31
|
+
hash: HashFn;
|
|
29
32
|
msPerReseed?: number;
|
|
30
33
|
entropy?: Uint8Array;
|
|
31
34
|
}): Promise<Fortuna>;
|
|
@@ -38,15 +41,18 @@ export declare class Fortuna {
|
|
|
38
41
|
getEntropy(): number;
|
|
39
42
|
/** Permanently dispose this instance. Wipes key material, stops all collectors. */
|
|
40
43
|
stop(): void;
|
|
41
|
-
/** Generate n blocks of 16 bytes each. — spec §9.4 */
|
|
42
|
-
private generateBlocks;
|
|
43
44
|
/** Get length pseudo-random bytes. — spec §9.4 */
|
|
44
45
|
private pseudoRandomData;
|
|
45
46
|
/** Reseed the generator — spec §9.4 */
|
|
46
47
|
private reseed;
|
|
47
|
-
/**
|
|
48
|
+
/** Drain pool 0 into a fresh seed and reseed. Used by the deterministic
|
|
49
|
+
* test factory; production reseeds in get() walk the §9.5.5 schedule
|
|
50
|
+
* across all pools, not just pool 0. Caller is responsible for any
|
|
51
|
+
* entropy-threshold check. */
|
|
52
|
+
private reseedFromPool0;
|
|
53
|
+
/** Increment little-endian counter. — spec §9.4 */
|
|
48
54
|
private incrementCounter;
|
|
49
|
-
/** Add an event to a pool via hash chaining: poolHash[i] =
|
|
55
|
+
/** Add an event to a pool via hash chaining: poolHash[i] = hash(poolHash[i] ‖ eventId ‖ data). */
|
|
50
56
|
private addRandomEvent;
|
|
51
57
|
private initialize;
|
|
52
58
|
private startCollectors;
|
package/dist/fortuna.js
CHANGED
|
@@ -22,18 +22,16 @@
|
|
|
22
22
|
// src/ts/fortuna.ts
|
|
23
23
|
//
|
|
24
24
|
// Fortuna CSPRNG — Ferguson & Schneier, Practical Cryptography (2003), Chapter 9.
|
|
25
|
-
// Backed by
|
|
26
|
-
// Requires init(
|
|
27
|
-
import { isInitialized } from './init.js';
|
|
28
|
-
import { Serpent } from './serpent/index.js';
|
|
29
|
-
import { SHA256 } from './sha2/index.js';
|
|
25
|
+
// Backed by a pluggable Generator (cipher PRF) and HashFn (accumulator hash).
|
|
26
|
+
// Requires init() for the modules used by the chosen generator and hash pair.
|
|
27
|
+
import { isInitialized, getInstance } from './init.js';
|
|
30
28
|
import { wipe, utf8ToBytes, concat } from './utils.js';
|
|
31
29
|
const isBrowser = typeof window !== 'undefined';
|
|
32
30
|
const isNode = typeof process !== 'undefined' && typeof process.pid === 'number';
|
|
33
31
|
/**
|
|
34
32
|
* Fortuna CSPRNG — spec §9.3–§9.5
|
|
35
33
|
*
|
|
36
|
-
* Use `Fortuna.create()` to instantiate. Direct construction is not allowed.
|
|
34
|
+
* Use `Fortuna.create({ generator, hash })` to instantiate. Direct construction is not allowed.
|
|
37
35
|
*/
|
|
38
36
|
export class Fortuna {
|
|
39
37
|
// ── Constants ──────────────────────────────────────────────────────────
|
|
@@ -43,9 +41,9 @@ export class Fortuna {
|
|
|
43
41
|
static NODE_STATS_INTERVAL = 1000; // ms — OS stats collector interval
|
|
44
42
|
static CRYPTO_INTERVAL = 3000; // ms — crypto.randomBytes interval
|
|
45
43
|
// ── State ─────────────────────────────────────────────────────────────
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
poolHash; // 32 running
|
|
44
|
+
gen;
|
|
45
|
+
hash;
|
|
46
|
+
poolHash; // 32 running hash chain values
|
|
49
47
|
poolEntropy;
|
|
50
48
|
genKey;
|
|
51
49
|
genCnt;
|
|
@@ -62,25 +60,33 @@ export class Fortuna {
|
|
|
62
60
|
timers = [];
|
|
63
61
|
// ── Static factory ────────────────────────────────────────────────────
|
|
64
62
|
static async create(opts) {
|
|
65
|
-
if (!
|
|
66
|
-
throw new
|
|
67
|
-
if (
|
|
68
|
-
throw new
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
if (!opts || !opts.generator || !opts.hash)
|
|
64
|
+
throw new TypeError('leviathan-crypto: Fortuna.create() requires { generator, hash }');
|
|
65
|
+
if (opts.hash.outputSize !== opts.generator.keySize)
|
|
66
|
+
throw new RangeError(`leviathan-crypto: Fortuna requires hash.outputSize (${opts.hash.outputSize}) `
|
|
67
|
+
+ `to match generator.keySize (${opts.generator.keySize})`);
|
|
68
|
+
const required = new Set([...opts.generator.wasmModules, ...opts.hash.wasmModules]);
|
|
69
|
+
for (const mod of required) {
|
|
70
|
+
if (!isInitialized(mod)) {
|
|
71
|
+
const args = [...required].map(m => `${m}: ...`).join(', ');
|
|
72
|
+
throw new Error(`leviathan-crypto: call init({ ${args} }) before using Fortuna`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const f = new Fortuna(opts.generator, opts.hash, opts.msPerReseed ?? Fortuna.MS_PER_RESEED);
|
|
76
|
+
f.initialize(opts.entropy);
|
|
71
77
|
// Force the first reseed — pool[0] is saturated by initialize(),
|
|
72
78
|
// so this call triggers an immediate reseed and guarantees get() never
|
|
73
79
|
// returns undefined. The byte is discarded.
|
|
74
80
|
f.get(1);
|
|
75
81
|
return f;
|
|
76
82
|
}
|
|
77
|
-
constructor(msPerReseed) {
|
|
78
|
-
this.
|
|
79
|
-
this.
|
|
83
|
+
constructor(gen, hash, msPerReseed) {
|
|
84
|
+
this.gen = gen;
|
|
85
|
+
this.hash = hash;
|
|
80
86
|
this.poolHash = [];
|
|
81
87
|
this.poolEntropy = [];
|
|
82
|
-
this.genKey = new Uint8Array(
|
|
83
|
-
this.genCnt = new Uint8Array(
|
|
88
|
+
this.genKey = new Uint8Array(gen.keySize);
|
|
89
|
+
this.genCnt = new Uint8Array(gen.counterSize);
|
|
84
90
|
this.reseedCnt = 0;
|
|
85
91
|
this.lastReseed = 0;
|
|
86
92
|
this.entropyLevel = 0;
|
|
@@ -90,7 +96,7 @@ export class Fortuna {
|
|
|
90
96
|
this.msPerReseed = msPerReseed;
|
|
91
97
|
this.robin = { kbd: 0, mouse: 0, scroll: 0, touch: 0, motion: 0, time: 0, rnd: 0, dom: 0 };
|
|
92
98
|
for (let i = 0; i < Fortuna.NUM_POOLS; i++) {
|
|
93
|
-
this.poolHash.push(new Uint8Array(
|
|
99
|
+
this.poolHash.push(new Uint8Array(hash.outputSize)); // zero-initialized chain value
|
|
94
100
|
this.poolEntropy.push(0);
|
|
95
101
|
}
|
|
96
102
|
}
|
|
@@ -109,17 +115,23 @@ export class Fortuna {
|
|
|
109
115
|
let seed = new Uint8Array(0);
|
|
110
116
|
let strength = 0;
|
|
111
117
|
for (let i = 0; i < Fortuna.NUM_POOLS; i++) {
|
|
112
|
-
|
|
118
|
+
// Practical Cryptography (Ferguson & Schneier, 2003) §9.5.5:
|
|
119
|
+
// pool P_i is used in reseed r iff 2^i divides r.
|
|
120
|
+
if ((this.reseedCnt & ((1 << i) - 1)) === 0) {
|
|
113
121
|
// Pool digest = current chain hash
|
|
114
122
|
seed = concat(seed, this.poolHash[i]);
|
|
115
123
|
strength += this.poolEntropy[i];
|
|
116
|
-
// Reset pool
|
|
117
|
-
this.poolHash[i]
|
|
124
|
+
// Reset pool — wipe old chain hash before dropping the reference.
|
|
125
|
+
const old = this.poolHash[i];
|
|
126
|
+
this.poolHash[i] = new Uint8Array(this.hash.outputSize);
|
|
127
|
+
wipe(old);
|
|
118
128
|
this.poolEntropy[i] = 0;
|
|
119
129
|
}
|
|
120
130
|
}
|
|
121
131
|
this.entropyLevel -= strength;
|
|
122
132
|
this.reseed(seed);
|
|
133
|
+
// seed is built from concatenated pool-hash copies; wipe the temp.
|
|
134
|
+
wipe(seed);
|
|
123
135
|
}
|
|
124
136
|
return this.pseudoRandomData(length);
|
|
125
137
|
}
|
|
@@ -140,11 +152,33 @@ export class Fortuna {
|
|
|
140
152
|
stop() {
|
|
141
153
|
if (this.disposed)
|
|
142
154
|
throw new Error('Fortuna instance has been disposed');
|
|
155
|
+
// Mark disposed FIRST. WASM wipeBuffers can throw if a stateful instance
|
|
156
|
+
// holds the module; we must not allow get()/addEntropy()/getEntropy() to
|
|
157
|
+
// run on a partially-disposed instance.
|
|
158
|
+
this.disposed = true;
|
|
143
159
|
this.stopCollectors();
|
|
144
160
|
wipe(this.genKey);
|
|
145
161
|
wipe(this.genCnt);
|
|
162
|
+
// Wipe all 32 pool-hash chain values so residual entropy-bearing
|
|
163
|
+
// bytes do not outlive the instance.
|
|
164
|
+
for (const p of this.poolHash)
|
|
165
|
+
wipe(p);
|
|
146
166
|
this.reseedCnt = 0;
|
|
147
|
-
|
|
167
|
+
// Best-effort wipe of WASM scratch buffers for every module the chosen
|
|
168
|
+
// generator and hash touched. Surface the first error so the caller
|
|
169
|
+
// knows the WASM scratch leak occurred.
|
|
170
|
+
const required = new Set([...this.gen.wasmModules, ...this.hash.wasmModules]);
|
|
171
|
+
let err;
|
|
172
|
+
for (const mod of required) {
|
|
173
|
+
try {
|
|
174
|
+
getInstance(mod).exports.wipeBuffers();
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
err ??= e;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (err)
|
|
181
|
+
throw err;
|
|
148
182
|
}
|
|
149
183
|
// ── Test-only accessors ───────────────────────────────────────────────
|
|
150
184
|
/** @internal — exposed for testing key replacement */
|
|
@@ -159,45 +193,93 @@ export class Fortuna {
|
|
|
159
193
|
_getReseedCnt() {
|
|
160
194
|
return this.reseedCnt;
|
|
161
195
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
196
|
+
/** @internal — exposed for testing pool-hash backing arrays */
|
|
197
|
+
_getPoolHash() {
|
|
198
|
+
return this.poolHash;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* @internal — test-only deterministic factory. Seeds pool[0] with the provided
|
|
202
|
+
* entropy and triggers one reseed directly, bypassing all OS entropy collection
|
|
203
|
+
* and the hrtime jitter capture in get(). This makes KAT vectors reproducible
|
|
204
|
+
* across runs. Not suitable for production use.
|
|
205
|
+
*/
|
|
206
|
+
static async _createDeterministicForTesting(opts) {
|
|
207
|
+
if (!opts || !opts.generator || !opts.hash)
|
|
208
|
+
throw new TypeError('Fortuna._createDeterministicForTesting() requires { generator, hash, entropy }');
|
|
209
|
+
if (opts.hash.outputSize !== opts.generator.keySize)
|
|
210
|
+
throw new RangeError(`leviathan-crypto: Fortuna requires hash.outputSize (${opts.hash.outputSize}) `
|
|
211
|
+
+ `to match generator.keySize (${opts.generator.keySize})`);
|
|
212
|
+
const required = new Set([...opts.generator.wasmModules, ...opts.hash.wasmModules]);
|
|
213
|
+
for (const mod of required) {
|
|
214
|
+
if (!isInitialized(mod)) {
|
|
215
|
+
const args = [...required].map(m => `${m}: ...`).join(', ');
|
|
216
|
+
throw new Error(`leviathan-crypto: call init({ ${args} }) before using Fortuna`);
|
|
217
|
+
}
|
|
171
218
|
}
|
|
172
|
-
|
|
219
|
+
const f = new Fortuna(opts.generator, opts.hash, 0);
|
|
220
|
+
// Seed pool[0] with the provided entropy, no OS collection.
|
|
221
|
+
f.addRandomEvent(opts.entropy, 0, opts.entropy.length * 8);
|
|
222
|
+
// Manually trigger reseed #1 without calling get() — get() calls captureHrtime()
|
|
223
|
+
// in Node.js which adds non-deterministic data before the reseed fires.
|
|
224
|
+
f.reseedFromPool0();
|
|
225
|
+
return f;
|
|
173
226
|
}
|
|
227
|
+
// ── Generator (spec §9.4) ─────────────────────────────────────────────
|
|
174
228
|
/** Get length pseudo-random bytes. — spec §9.4 */
|
|
175
229
|
pseudoRandomData(length) {
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
230
|
+
const blocks = Math.ceil(length / this.gen.blockSize);
|
|
231
|
+
const out = this.gen.generate(this.genKey, this.genCnt, length);
|
|
232
|
+
// External counter advance — generator is stateless and does not mutate caller's counter
|
|
233
|
+
for (let i = 0; i < blocks; i++)
|
|
234
|
+
this.incrementCounter();
|
|
235
|
+
// Key replacement — mandatory forward secrecy (spec §9.4).
|
|
236
|
+
// Wipe the prior key BEFORE dropping its reference so no key bytes are
|
|
237
|
+
// reachable after key replacement; anyone holding a Uint8Array view to
|
|
238
|
+
// the old key now observes zero.
|
|
239
|
+
const newKey = this.gen.generate(this.genKey, this.genCnt, this.gen.keySize);
|
|
240
|
+
for (let i = 0; i < Math.ceil(this.gen.keySize / this.gen.blockSize); i++)
|
|
241
|
+
this.incrementCounter();
|
|
242
|
+
wipe(this.genKey);
|
|
243
|
+
this.genKey = newKey;
|
|
244
|
+
return out;
|
|
183
245
|
}
|
|
184
246
|
/** Reseed the generator — spec §9.4 */
|
|
185
247
|
reseed(seed) {
|
|
186
|
-
// genKey =
|
|
187
|
-
|
|
248
|
+
// genKey = hash(genKey ‖ seed). Wipe both the hash input and the
|
|
249
|
+
// prior key before dropping references.
|
|
250
|
+
const combined = concat(this.genKey, seed);
|
|
251
|
+
const newKey = this.hash.digest(combined);
|
|
252
|
+
wipe(combined);
|
|
253
|
+
wipe(this.genKey);
|
|
254
|
+
this.genKey = newKey;
|
|
188
255
|
// Increment counter — makes it nonzero on first reseed, marking generator as seeded
|
|
189
256
|
this.incrementCounter();
|
|
190
257
|
this.lastReseed = Date.now();
|
|
191
258
|
}
|
|
192
|
-
/**
|
|
259
|
+
/** Drain pool 0 into a fresh seed and reseed. Used by the deterministic
|
|
260
|
+
* test factory; production reseeds in get() walk the §9.5.5 schedule
|
|
261
|
+
* across all pools, not just pool 0. Caller is responsible for any
|
|
262
|
+
* entropy-threshold check. */
|
|
263
|
+
reseedFromPool0() {
|
|
264
|
+
this.reseedCnt = (this.reseedCnt + 1) >>> 0;
|
|
265
|
+
const seed = this.poolHash[0].slice();
|
|
266
|
+
const old = this.poolHash[0];
|
|
267
|
+
this.poolHash[0] = new Uint8Array(this.hash.outputSize);
|
|
268
|
+
wipe(old);
|
|
269
|
+
this.entropyLevel -= this.poolEntropy[0];
|
|
270
|
+
this.poolEntropy[0] = 0;
|
|
271
|
+
this.reseed(seed);
|
|
272
|
+
wipe(seed);
|
|
273
|
+
}
|
|
274
|
+
/** Increment little-endian counter. — spec §9.4 */
|
|
193
275
|
incrementCounter() {
|
|
194
|
-
for (let i = 0; i <
|
|
276
|
+
for (let i = 0; i < this.genCnt.length; i++) {
|
|
195
277
|
if (++this.genCnt[i] !== 0)
|
|
196
278
|
break;
|
|
197
279
|
}
|
|
198
280
|
}
|
|
199
281
|
// ── Accumulator (spec §9.5) ───────────────────────────────────────────
|
|
200
|
-
/** Add an event to a pool via hash chaining: poolHash[i] =
|
|
282
|
+
/** Add an event to a pool via hash chaining: poolHash[i] = hash(poolHash[i] ‖ eventId ‖ data). */
|
|
201
283
|
addRandomEvent(data, poolIdx, entropyBits) {
|
|
202
284
|
// Encode eventId as 4 bytes little-endian
|
|
203
285
|
const id = new Uint8Array(4);
|
|
@@ -206,8 +288,13 @@ export class Fortuna {
|
|
|
206
288
|
id[2] = (this.eventId >>> 16) & 0xff;
|
|
207
289
|
id[3] = (this.eventId >>> 24) & 0xff;
|
|
208
290
|
this.eventId = (this.eventId + 1) >>> 0; // u32 wrap
|
|
209
|
-
// Chain: poolHash[i] =
|
|
210
|
-
|
|
291
|
+
// Chain: poolHash[i] = hash(poolHash[i] ‖ id ‖ data).
|
|
292
|
+
// Wipe the chain input and the prior chain value before dropping refs.
|
|
293
|
+
const combined = concat(this.poolHash[poolIdx], id, data);
|
|
294
|
+
const newChain = this.hash.digest(combined);
|
|
295
|
+
wipe(combined);
|
|
296
|
+
wipe(this.poolHash[poolIdx]);
|
|
297
|
+
this.poolHash[poolIdx] = newChain;
|
|
211
298
|
this.poolEntropy[poolIdx] += entropyBits;
|
|
212
299
|
this.entropyLevel += entropyBits;
|
|
213
300
|
}
|
|
@@ -226,6 +313,13 @@ export class Fortuna {
|
|
|
226
313
|
this.addRandomEvent(entropy, this.robin.rnd, entropy.length * 8);
|
|
227
314
|
this.robin.rnd = (this.robin.rnd + 1) % Fortuna.NUM_POOLS;
|
|
228
315
|
}
|
|
316
|
+
// F-2 invariant: fail loudly if no OS entropy source delivered anything.
|
|
317
|
+
// The try/catch in collectorCryptoRandom is preserved to protect against
|
|
318
|
+
// platforms where crypto.getRandomValues itself throws (non-standard
|
|
319
|
+
// runtimes). This post-init check covers all silent-failure paths uniformly.
|
|
320
|
+
if (this.poolEntropy[0] < Fortuna.RESEED_LIMIT)
|
|
321
|
+
throw new Error('leviathan-crypto: Fortuna initialization could not gather sufficient entropy. '
|
|
322
|
+
+ 'No working crypto.getRandomValues or node:crypto in this environment.');
|
|
229
323
|
this.startCollectors();
|
|
230
324
|
}
|
|
231
325
|
// ── Collectors ────────────────────────────────────────────────────────
|
|
@@ -358,7 +452,7 @@ export class Fortuna {
|
|
|
358
452
|
}
|
|
359
453
|
collectorDom() {
|
|
360
454
|
if (typeof document !== 'undefined' && document.documentElement) {
|
|
361
|
-
this.addRandomEvent(this.
|
|
455
|
+
this.addRandomEvent(this.hash.digest(utf8ToBytes(document.documentElement.innerHTML)), this.robin.dom, 2);
|
|
362
456
|
this.robin.dom = (this.robin.dom + 1) % Fortuna.NUM_POOLS;
|
|
363
457
|
}
|
|
364
458
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -13,17 +13,19 @@ import type { WasmSource } from './wasm-source.js';
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function init(sources: Partial<Record<Module, WasmSource>>): Promise<void>;
|
|
15
15
|
export type { Module, WasmSource };
|
|
16
|
-
export { isInitialized
|
|
16
|
+
export { isInitialized } from './init.js';
|
|
17
17
|
export { AuthenticationError } from './errors.js';
|
|
18
|
-
export { serpentInit, Serpent, SerpentCtr, SerpentCbc, SerpentCipher, _serpentReady } from './serpent/index.js';
|
|
19
|
-
export { chacha20Init, ChaCha20, Poly1305, ChaCha20Poly1305, XChaCha20Poly1305, XChaCha20Cipher, _chachaReady } from './chacha20/index.js';
|
|
20
|
-
export { sha2Init, SHA256, SHA512, SHA384, HMAC_SHA256, HMAC_SHA512, HMAC_SHA384, HKDF_SHA256, HKDF_SHA512, _sha2Ready } from './sha2/index.js';
|
|
21
|
-
export { sha3Init, SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256, _sha3Ready } from './sha3/index.js';
|
|
18
|
+
export { serpentInit, Serpent, SerpentCtr, SerpentCbc, SerpentCipher, SerpentGenerator, _serpentReady } from './serpent/index.js';
|
|
19
|
+
export { chacha20Init, ChaCha20, Poly1305, ChaCha20Poly1305, XChaCha20Poly1305, XChaCha20Cipher, ChaCha20Generator, _chachaReady } from './chacha20/index.js';
|
|
20
|
+
export { sha2Init, SHA256, SHA512, SHA384, HMAC_SHA256, HMAC_SHA512, HMAC_SHA384, HKDF_SHA256, HKDF_SHA512, SHA256Hash, _sha2Ready } from './sha2/index.js';
|
|
21
|
+
export { sha3Init, SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256, SHA3_256Hash, _sha3Ready } from './sha3/index.js';
|
|
22
22
|
export { keccakInit } from './keccak/index.js';
|
|
23
23
|
export { kyberInit, MlKem512, MlKem768, MlKem1024, MlKemBase, KyberSuite, _kyberReady } from './kyber/index.js';
|
|
24
24
|
export type { KyberKeyPair, KyberEncapsulation, KyberParams } from './kyber/index.js';
|
|
25
25
|
export { SealStream, OpenStream, Seal, SealStreamPool, FLAG_FRAMED, TAG_DATA, TAG_FINAL, HEADER_SIZE, CHUNK_MIN, CHUNK_MAX } from './stream/index.js';
|
|
26
26
|
export type { CipherSuite, DerivedKeys, SealStreamOpts, PoolOpts } from './stream/index.js';
|
|
27
27
|
export { Fortuna } from './fortuna.js';
|
|
28
|
-
export type { Hash, KeyedHash, Blockcipher, Streamcipher, AEAD } from './types.js';
|
|
28
|
+
export type { Hash, KeyedHash, Blockcipher, Streamcipher, AEAD, Generator, HashFn } from './types.js';
|
|
29
|
+
export { KDFChain, ratchetInit, kemRatchetEncap, kemRatchetDecap, ratchetReady, SkippedKeyStore, RatchetKeypair, } from './ratchet/index.js';
|
|
30
|
+
export type { RatchetInitResult, KemEncapResult, KemDecapResult, MlKemLike, RatchetMessageHeader, ResolveHandle, SkippedKeyStoreOpts, } from './ratchet/index.js';
|
|
29
31
|
export { hexToBytes, bytesToHex, utf8ToBytes, bytesToUtf8, base64ToBytes, bytesToBase64, constantTimeEqual, CT_MAX_BYTES, wipe, xor, concat, randomBytes, hasSIMD, } from './utils.js';
|
package/dist/index.js
CHANGED
|
@@ -60,14 +60,15 @@ export async function init(sources) {
|
|
|
60
60
|
}
|
|
61
61
|
await Promise.all(entries.map(([mod, src]) => _dispatchers[mod](src)));
|
|
62
62
|
}
|
|
63
|
-
export { isInitialized
|
|
63
|
+
export { isInitialized } from './init.js';
|
|
64
64
|
export { AuthenticationError } from './errors.js';
|
|
65
|
-
export { serpentInit, Serpent, SerpentCtr, SerpentCbc, SerpentCipher, _serpentReady } from './serpent/index.js';
|
|
66
|
-
export { chacha20Init, ChaCha20, Poly1305, ChaCha20Poly1305, XChaCha20Poly1305, XChaCha20Cipher, _chachaReady } from './chacha20/index.js';
|
|
67
|
-
export { sha2Init, SHA256, SHA512, SHA384, HMAC_SHA256, HMAC_SHA512, HMAC_SHA384, HKDF_SHA256, HKDF_SHA512, _sha2Ready } from './sha2/index.js';
|
|
68
|
-
export { sha3Init, SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256, _sha3Ready } from './sha3/index.js';
|
|
65
|
+
export { serpentInit, Serpent, SerpentCtr, SerpentCbc, SerpentCipher, SerpentGenerator, _serpentReady } from './serpent/index.js';
|
|
66
|
+
export { chacha20Init, ChaCha20, Poly1305, ChaCha20Poly1305, XChaCha20Poly1305, XChaCha20Cipher, ChaCha20Generator, _chachaReady } from './chacha20/index.js';
|
|
67
|
+
export { sha2Init, SHA256, SHA512, SHA384, HMAC_SHA256, HMAC_SHA512, HMAC_SHA384, HKDF_SHA256, HKDF_SHA512, SHA256Hash, _sha2Ready } from './sha2/index.js';
|
|
68
|
+
export { sha3Init, SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256, SHA3_256Hash, _sha3Ready } from './sha3/index.js';
|
|
69
69
|
export { keccakInit } from './keccak/index.js';
|
|
70
70
|
export { kyberInit, MlKem512, MlKem768, MlKem1024, MlKemBase, KyberSuite, _kyberReady } from './kyber/index.js';
|
|
71
71
|
export { SealStream, OpenStream, Seal, SealStreamPool, FLAG_FRAMED, TAG_DATA, TAG_FINAL, HEADER_SIZE, CHUNK_MIN, CHUNK_MAX } from './stream/index.js';
|
|
72
72
|
export { Fortuna } from './fortuna.js';
|
|
73
|
+
export { KDFChain, ratchetInit, kemRatchetEncap, kemRatchetDecap, ratchetReady, SkippedKeyStore, RatchetKeypair, } from './ratchet/index.js';
|
|
73
74
|
export { hexToBytes, bytesToHex, utf8ToBytes, bytesToUtf8, base64ToBytes, bytesToBase64, constantTimeEqual, CT_MAX_BYTES, wipe, xor, concat, randomBytes, hasSIMD, } from './utils.js';
|
package/dist/init.d.ts
CHANGED
|
@@ -3,5 +3,3 @@ export type Module = 'serpent' | 'chacha20' | 'sha2' | 'sha3' | 'keccak' | 'kybe
|
|
|
3
3
|
export declare function initModule(mod: Module, source: WasmSource): Promise<void>;
|
|
4
4
|
export declare function getInstance(mod: Module): WebAssembly.Instance;
|
|
5
5
|
export declare function isInitialized(mod: Module): boolean;
|
|
6
|
-
/** Reset all cached instances — for testing only */
|
|
7
|
-
export declare function _resetForTesting(): void;
|
package/dist/init.js
CHANGED
|
@@ -7,26 +7,106 @@ function resolve(mod) {
|
|
|
7
7
|
}
|
|
8
8
|
// Module-scope cache: one WebAssembly.Instance per canonical module
|
|
9
9
|
const instances = new Map();
|
|
10
|
+
// Pending inits — coalesces concurrent initModule calls for the same module.
|
|
11
|
+
const pending = new Map();
|
|
12
|
+
// Exclusivity registry: per-module ownership token held by a stateful wrapper
|
|
13
|
+
// for its entire lifetime. Prevents shared-WASM-state clobber when two
|
|
14
|
+
// instances from the same module would otherwise trample each other's memory.
|
|
15
|
+
const owners = new Map();
|
|
10
16
|
export async function initModule(mod, source) {
|
|
11
17
|
const resolved = resolve(mod);
|
|
12
18
|
if (instances.has(resolved))
|
|
13
19
|
return;
|
|
20
|
+
const inflight = pending.get(resolved);
|
|
21
|
+
if (inflight) {
|
|
22
|
+
await inflight;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
14
25
|
if ((resolved === 'serpent' || resolved === 'chacha20' || resolved === 'kyber') && !hasSIMD())
|
|
15
26
|
throw new Error('leviathan-crypto: serpent, chacha20, and kyber require WebAssembly SIMD — '
|
|
16
27
|
+ 'this runtime does not support it');
|
|
17
|
-
|
|
28
|
+
const p = loadWasm(source);
|
|
29
|
+
pending.set(resolved, p);
|
|
30
|
+
try {
|
|
31
|
+
instances.set(resolved, await p);
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
pending.delete(resolved);
|
|
35
|
+
}
|
|
18
36
|
}
|
|
19
37
|
export function getInstance(mod) {
|
|
20
|
-
const
|
|
38
|
+
const r = resolve(mod);
|
|
39
|
+
const inst = instances.get(r);
|
|
21
40
|
if (!inst) {
|
|
22
41
|
throw new Error(`leviathan-crypto: call init({ ${mod}: ... }) before using this class`);
|
|
23
42
|
}
|
|
43
|
+
if (owners.has(r)) {
|
|
44
|
+
throw new Error(`leviathan-crypto: another stateful instance is using the '${r}' WASM module — `
|
|
45
|
+
+ 'call dispose() on it before constructing a new one');
|
|
46
|
+
}
|
|
24
47
|
return inst;
|
|
25
48
|
}
|
|
26
49
|
export function isInitialized(mod) {
|
|
27
50
|
return instances.has(resolve(mod));
|
|
28
51
|
}
|
|
29
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Acquire exclusive access to `mod`. Throws if another stateful instance
|
|
54
|
+
* currently holds it. Returned token must be passed to `_releaseModule`.
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
export function _acquireModule(mod) {
|
|
58
|
+
const r = resolve(mod);
|
|
59
|
+
if (owners.has(r))
|
|
60
|
+
throw new Error(`leviathan-crypto: another stateful instance is using the '${r}' WASM module — `
|
|
61
|
+
+ 'call dispose() on it before constructing a new one');
|
|
62
|
+
const tok = Symbol(r);
|
|
63
|
+
owners.set(r, tok);
|
|
64
|
+
return tok;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Release exclusive access. No-op if the token doesn't match the current
|
|
68
|
+
* owner (makes dispose idempotent).
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
export function _releaseModule(mod, tok) {
|
|
72
|
+
const r = resolve(mod);
|
|
73
|
+
if (owners.get(r) === tok)
|
|
74
|
+
owners.delete(r);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* True if a stateful instance currently holds the module.
|
|
78
|
+
* @internal
|
|
79
|
+
*/
|
|
80
|
+
export function _isModuleBusy(mod) {
|
|
81
|
+
return owners.has(resolve(mod));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Throw if `mod` is currently held by a stateful instance. Called at the top
|
|
85
|
+
* of every atomic WASM-touching method so that cached-exports access paths
|
|
86
|
+
* cannot silently clobber a live stateful instance's WASM state.
|
|
87
|
+
*
|
|
88
|
+
* The error message is intentionally identical to `_acquireModule`'s so that
|
|
89
|
+
* error handlers matching on text work uniformly across construction-time and
|
|
90
|
+
* method-time ownership failures.
|
|
91
|
+
* @internal
|
|
92
|
+
*/
|
|
93
|
+
export function _assertNotOwned(mod) {
|
|
94
|
+
// Deliberately unoptimized. Do not add caching or epoch tracking: the
|
|
95
|
+
// check must read current ownership on every call so an atomic op cannot
|
|
96
|
+
// race ahead of a stateful acquirer.
|
|
97
|
+
const r = resolve(mod);
|
|
98
|
+
if (owners.has(r))
|
|
99
|
+
throw new Error(`leviathan-crypto: another stateful instance is using the '${r}' WASM module — `
|
|
100
|
+
+ 'call dispose() on it before constructing a new one');
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Reset all cached instances — for testing only. Clears `instances`, `pending`,
|
|
104
|
+
* and `owners` so tests can re-exercise module lifecycle (init, exclusivity,
|
|
105
|
+
* race) from a known-empty state.
|
|
106
|
+
* @internal
|
|
107
|
+
*/
|
|
30
108
|
export function _resetForTesting() {
|
|
31
109
|
instances.clear();
|
|
110
|
+
pending.clear();
|
|
111
|
+
owners.clear();
|
|
32
112
|
}
|