ths-csprng 1.1.0 → 1.1.1
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/.nomedia +0 -0
- package/README.md +124 -21
- package/dist/index.min.js +2 -0
- package/dist/index.min.js.map +1 -0
- package/dist/util.min.js +2 -0
- package/dist/util.min.js.map +1 -0
- package/package.json +34 -20
- package/src/index.d.ts +108 -0
- package/src/index.js +84 -15
- package/src/util.d.ts +61 -0
- package/src/util.js +54 -17
- package/src/index.mjs +0 -117
- package/src/util.mjs +0 -186
package/.nomedia
ADDED
|
File without changes
|
package/README.md
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img src="
|
|
2
|
+
<img src="https://raw.githubusercontent.com/harnuma9/temporal-hardening-solution-csprng/refs/heads/main/media/banner.png" alt="Banner" width="1250">
|
|
3
3
|
</div>
|
|
4
4
|
|
|
5
5
|
<br />
|
|
6
6
|
|
|
7
|
+
# ths-csprng
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/ths-csprng)
|
|
10
|
+

|
|
11
|
+
[](https://github.com/harnuma9/temporal-hardening-solution-csprng/blob/main/LICENSE)
|
|
12
|
+

|
|
13
|
+
[](https://badge.socket.dev/npm/package/ths-csprng/1.1.1)
|
|
14
|
+
[](https://harnuma9.github.io/donate/)
|
|
15
|
+
|
|
16
|
+
<br />
|
|
17
|
+
|
|
7
18
|
Standard RNGs assume that once you’ve seeded them, the whole history of how that seed was created just disappears. In reality, if someone can reconstruct the exact physical state of your system—or somehow “look back” in time—your randomness stops being random.
|
|
8
19
|
|
|
9
20
|
If you buy into a **block universe** or **[superdeterminism](https://en.wikipedia.org/wiki/Superdeterminism)**, that “random” seed was never random at all. It’s just a fixed point in spacetime. To an observer with enough computing power or a view further down the timeline, your private key isn’t secret — it’s already history.
|
|
@@ -19,7 +30,7 @@ In short: it uses macroscopic messiness as a defense against microscopic observa
|
|
|
19
30
|
<br />
|
|
20
31
|
|
|
21
32
|
<div align="center">
|
|
22
|
-
<img src="
|
|
33
|
+
<img src="https://raw.githubusercontent.com/harnuma9/temporal-hardening-solution-csprng/refs/heads/main/media/illustration_1.png" alt="Illustration 1" width="1250">
|
|
23
34
|
</div>
|
|
24
35
|
|
|
25
36
|
<br />
|
|
@@ -34,6 +45,11 @@ THS fixes this by replacing a single seed with a temporal sequence of many momen
|
|
|
34
45
|
|
|
35
46
|
<br />
|
|
36
47
|
|
|
48
|
+
> **Full API context optimized for AI assistants and contributors** is available in [`llms-full.txt`](https://github.com/harnuma9/temporal-hardening-solution-csprng/blob/main/llms-full.txt).
|
|
49
|
+
|
|
50
|
+
<br />
|
|
51
|
+
<br />
|
|
52
|
+
|
|
37
53
|
---
|
|
38
54
|
|
|
39
55
|
<br />
|
|
@@ -68,7 +84,7 @@ const masterKey = await THS.random(64, {
|
|
|
68
84
|
#### Legacy / Standard CommonJS
|
|
69
85
|
|
|
70
86
|
```javascript
|
|
71
|
-
const THS = require('ths-csprng');
|
|
87
|
+
const { THS } = require('ths-csprng');
|
|
72
88
|
|
|
73
89
|
(async () => {
|
|
74
90
|
const entropy = await THS.random(32);
|
|
@@ -103,13 +119,13 @@ const options = {
|
|
|
103
119
|
harden: 2, // 0–3 (hardening depth)
|
|
104
120
|
trng: false, // Force /dev/urandom usage (w/ fallback)
|
|
105
121
|
buffer: true, // Return Node.js Buffer (false for Uint8Array)
|
|
106
|
-
|
|
122
|
+
|
|
107
123
|
// Argon2id Specifics (Requires harden: 3)
|
|
108
124
|
memoryH: 1, // Number of Argon2id cycles per layer
|
|
109
125
|
mem: 16384, // Memory cost in KB (default 16MB)
|
|
110
126
|
passes: 3, // (t) iterations
|
|
111
127
|
parallelism: 4, // (p) parallel threads
|
|
112
|
-
|
|
128
|
+
|
|
113
129
|
label: 'THS-Default-v1' // KMAC personalization string
|
|
114
130
|
};
|
|
115
131
|
|
|
@@ -122,13 +138,15 @@ const bytes = await THS.random(32, options);
|
|
|
122
138
|
|
|
123
139
|
### 🧬 Advanced API
|
|
124
140
|
|
|
141
|
+
<br />
|
|
142
|
+
|
|
125
143
|
#### Direct Utils
|
|
126
144
|
|
|
127
145
|
```javascript
|
|
128
146
|
// ESM
|
|
129
147
|
import Utils from 'ths-csprng/utils';
|
|
130
|
-
// CJS
|
|
131
|
-
const Utils = require('ths-csprng/utils');
|
|
148
|
+
// or CJS
|
|
149
|
+
const { Utils } = require('ths-csprng/utils');
|
|
132
150
|
|
|
133
151
|
// Get a raw metabolic snapshot (*T=n*)
|
|
134
152
|
const snap = await Utils.snapshot(2);
|
|
@@ -141,17 +159,34 @@ const raw = await THS.raw(32);
|
|
|
141
159
|
#### Browser Support
|
|
142
160
|
|
|
143
161
|
```javascript
|
|
144
|
-
|
|
145
|
-
|
|
162
|
+
// Uses window.crypto or node:crypto depending on environment
|
|
163
|
+
const buf1 = new Uint32Array(32);
|
|
164
|
+
const buf2 = new Uint16Array(32);
|
|
165
|
+
const buf3 = new Uint8Array(32);
|
|
166
|
+
|
|
167
|
+
THS.fillRandom(buf1);
|
|
168
|
+
THS.fillRandom(buf2);
|
|
169
|
+
THS.fillRandom(buf3);
|
|
146
170
|
```
|
|
147
171
|
|
|
148
172
|
|
|
149
173
|
#### Convenient Aliases
|
|
150
174
|
|
|
151
175
|
```javascript
|
|
152
|
-
import { randomBytes } from 'ths-csprng';
|
|
176
|
+
import { randomBytes, THS } from 'ths-csprng';
|
|
177
|
+
|
|
178
|
+
// Sync: Standard CSPRNG fill
|
|
179
|
+
const uintBytes = randomBytes(32); // Uint8Array
|
|
180
|
+
const bufferBytes = Buffer.from(randomBytes(32)); // Buffer
|
|
181
|
+
|
|
182
|
+
// Async: Uses THS.random() temporal hardening
|
|
183
|
+
const secureBytes = await randomBytes(
|
|
184
|
+
32,
|
|
185
|
+
true, // isHardenRNG flag
|
|
186
|
+
{ layers: 64, harden: 2 }
|
|
187
|
+
);
|
|
153
188
|
|
|
154
|
-
|
|
189
|
+
// Method Aliases
|
|
155
190
|
await THS.rand(32);
|
|
156
191
|
await THS.rnd(32);
|
|
157
192
|
```
|
|
@@ -167,42 +202,110 @@ THS tries to bridge raw hardware chaos with solid cryptographic primitives, forc
|
|
|
167
202
|
<br />
|
|
168
203
|
|
|
169
204
|
<div align="center">
|
|
170
|
-
<img src="
|
|
205
|
+
<img src="https://raw.githubusercontent.com/harnuma9/temporal-hardening-solution-csprng/refs/heads/main/media/illustration_2.png" alt="Illustration 2" width="1250">
|
|
171
206
|
</div>
|
|
172
207
|
|
|
173
208
|
<br />
|
|
174
209
|
|
|
175
210
|
## Use Cases
|
|
176
211
|
|
|
177
|
-
* **Everyday apps:** Use `THS.fillRandom()` or `
|
|
212
|
+
* **Everyday apps:** Use `THS.fillRandom(arr)` or `randomBytes(len)` for **fast, reliable, standard randomness**.
|
|
178
213
|
* **Entropy Research / Custom PRNGs:** Import the subpath `ths-csprng/utils` to feed metabolic snapshots directly into your own DRBGs as a source of high-variance, hardware-bound entropy.
|
|
179
214
|
* **High-value keys:** Use `THS.random()` with `harden: 3` when you want seeds that are computationally irreducible — an attacker would need to reconstruct exact nanosecond-level CPU and memory states across hundreds of noisy iterations.
|
|
180
215
|
|
|
181
|
-
|
|
182
216
|
<br />
|
|
183
217
|
|
|
184
218
|
## Threat Model
|
|
185
219
|
|
|
186
|
-
|
|
220
|
+
Threat models range from the **hypothetical** ([retrocausality](https://www.nwo.nl/en/projects/hexwa91373) is possible in real-life + proofs) to the **theoretically niche** (such as the “[cold start](https://www.ijcrt.org/papers/IJCRT2602064.pdf)” entropy problem in serverless environments).
|
|
221
|
+
|
|
222
|
+
However, the most critical threats involve **real-world hardware vulnerabilities** that compromise the security of every cryptographic seed and encryption key generated on a device. Whether you are using post-quantum libraries or legacy suites, security is merely an illusion if:
|
|
223
|
+
|
|
224
|
+
* The entropy source is a single, static “snapshot” in time AND the adversary successfully recovers it.
|
|
225
|
+
* The source relies on a mathematically predictable DRBG source, such as the infamous **[DUAL_EC_DRBG](https://en.wikipedia.org/wiki/Dual_EC_DRBG)**.
|
|
226
|
+
|
|
227
|
+
<br />
|
|
228
|
+
|
|
229
|
+
### ✅ Scope of Protection
|
|
230
|
+
|
|
231
|
+
The **THS-CSPRNG wrapper** is specifically engineered to mitigate scenarios where an adversary attempts to:
|
|
187
232
|
|
|
188
|
-
1. Reconstruct the historical state of your machine
|
|
189
|
-
2. Exploit VM snapshots that cause
|
|
190
|
-
3. Bypass or predict hardware entropy sources
|
|
233
|
+
1. **State Reconstruction**: Reconstruct the historical state of your machine at the precise moment a key was generated. (a core concern in [Cryptanalysis](https://en.wikipedia.org/wiki/Cryptanalysis)).
|
|
234
|
+
2. **VM Replication**: Exploit **VM snapshots** or “cloning” events that cause multiple instances to produce identical, non-unique “random” output.
|
|
235
|
+
3. **Entropy Subversion**: Bypass or predict hardware entropy sources via [manufacturer backdoors](https://en.wikipedia.org/wiki/Hardware_backdoor) or side-channel influence.
|
|
191
236
|
|
|
192
237
|
<br />
|
|
193
238
|
|
|
194
|
-
|
|
239
|
+
### ❌ Limitations (What it can’t protect)
|
|
195
240
|
|
|
196
|
-
|
|
241
|
+
Although it is a CSPRNG wrapper, engineered to protect generated secrets from theoretical to real-world risks, it does not protect you from:
|
|
242
|
+
|
|
243
|
+
1. **Actual time travelers**: They can travel from any time physically; that is cheating. A threat model specifically mitigates against any “observations” from 3D observers in the future using speculative “sci-fi” technologies.
|
|
244
|
+
2. **Malware, Spyware, Exploits, etc.**: If your device is compromised at the OS level or pre-loaded with spyware, an adversary doesn't need to predict your entropy—they can simply **[intercept the final generated secrets](https://en.wikipedia.org/wiki/Form_grabbing)** from memory.
|
|
245
|
+
3. **Physical Extraction**: If an adversary has physical access to your hardware and a soldering iron (or a very cold can of compressed air for a “[Cold Boot](https://en.wikipedia.org/wiki/Cold_boot_attack)” attack), they are bypassing the RNG logic entirely to target the stored result *(while TEEs and memory encryption help, physical access might be a game-over scenario)*.
|
|
246
|
+
4. **Human errors**: High-quality entropy cannot save you from logic errors. Hardened seeds are useless if you accidentally log them to a public console, hardcode them into an automated script, or fall victim to a social engineering / **[MITM attack](https://en.wikipedia.org/wiki/Man-in-the-middle_attack)**.
|
|
247
|
+
|
|
248
|
+
<br />
|
|
249
|
+
|
|
250
|
+
> [!NOTE]
|
|
251
|
+
>
|
|
252
|
+
> **Hardening Level 3 (Heavy)** is computationally expensive by design. It is intended for generating long-term master keys or seeds.
|
|
253
|
+
> If your use case requires high-frequency random bytes (e.g., UUIDs or nonces), use **Level 0** or **Level 1**.
|
|
197
254
|
|
|
198
255
|
<br />
|
|
199
256
|
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
<br />
|
|
260
|
+
|
|
261
|
+
## 🛠 ️Development & Verification
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
npm run check # test + verify checksums
|
|
265
|
+
npm run build # minify + generate docs + checksums
|
|
266
|
+
npm test
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
<br />
|
|
270
|
+
|
|
271
|
+
The project uses:
|
|
272
|
+
|
|
273
|
+
* `terser` for minification
|
|
274
|
+
* SHA-256 checksums for all built files
|
|
275
|
+
* Strict mode + comprehensive test suite
|
|
276
|
+
|
|
277
|
+
<br />
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
<br />
|
|
282
|
+
|
|
283
|
+
## ⭐ Contributor(s)
|
|
284
|
+
|
|
285
|
+
<a href="https://github.com/harnuma9/temporal-hardening-solution-csprng/graphs/contributors">
|
|
286
|
+
<img alt="contributors" src="https://contrib.rocks/image?repo=harnuma9/temporal-hardening-solution-csprng"/>
|
|
287
|
+
</a>
|
|
288
|
+
|
|
289
|
+
<br />
|
|
290
|
+
<br />
|
|
291
|
+
<br />
|
|
292
|
+
|
|
200
293
|
## License
|
|
201
294
|
|
|
202
295
|
Licensed under the **Apache License, Version 2.0**; a robust, permissive license that includes an explicit grant of patent rights and provides protection against contributor liability.
|
|
203
296
|
|
|
204
|
-
Copyright © 2026 Aries Harbinger. See the **[LICENSE](
|
|
297
|
+
Copyright © 2026 Aries Harbinger. See the **[LICENSE](https://github.com/harnuma9/temporal-hardening-solution-csprng/blob/main/LICENSE)** file for full details.
|
|
205
298
|
|
|
299
|
+
<br />
|
|
300
|
+
<br />
|
|
301
|
+
|
|
302
|
+
## 🔗 Links
|
|
303
|
+
|
|
304
|
+
* [Github Repository](https://github.com/harnuma9/temporal-hardening-solution-csprng)
|
|
305
|
+
* [npm Package](https://www.npmjs.com/package/ths-csprng)
|
|
306
|
+
* Full AI Context **[llms-full.txt](https://github.com/harnuma9/temporal-hardening-solution-csprng/blob/main/llms-full.txt)** (for contributors / LLM assistants)
|
|
307
|
+
|
|
308
|
+
<br />
|
|
206
309
|
<br />
|
|
207
310
|
<br />
|
|
208
311
|
<br />
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import Utils from"ths-csprng/utils";import{kmac256}from"@noble/hashes/sha3-addons.js";import{createHash,webcrypto}from"node:crypto";const THS=Object.freeze({async random(e=32,{layers:r=128,harden:t=2,trng:a=!1,buffer:o=!0,memoryH:n=1,mem:i=16384,passes:s=3,parallelism:l=4,label:c="THS-Default-v1"}={}){if(e<=0)return Buffer.alloc(0);if(r<2||r>65536)throw new Error(`Layer count [${r}] is invalid. Requires a temporal span of 2-65536 cycles to ensure metabolic variance.`);if(t<0||t>3)throw new Error(`Hardening level [${t}] is unknown. Use 0 (Basic) through 3 (Heavy).`);if(t>=3&&(n<1||n>r))throw new Error(`Memory hardening cycle count [${n}] exceeds available temporal layers [${r}].`);if("number"!=typeof i||i<1024)throw new Error(`Memory set [${i}] for Argon2id must be a valid number and not be less than 1024 or 1KB.`);const d=await Utils.getRandom(32,a);let m;try{m=kmac256.create(d,{personalization:Utils.toBytes(c),dkLen:e});for(let e=0;e<r;e++){const o=await Utils.snapshot(t,n,i,s,l,e,a);if(m.update(o),o.fill(0),t>=1){const e=await Utils.getRandom(12,a);m.update(e),e.fill(0)}e<r-1&&await Utils.jitter(a)}const f=m.digest();if(!o)return f;const w=Buffer.from(f);return f?.fill?.(0),w}catch(e){throw e}finally{d?.fill?.(0),m=null}},rand:async(e,r)=>await THS.random(e,r),rnd:async(e,r)=>await THS.random(e,r),raw:async e=>await Utils.getRandom(e,!0),fillRandom:e=>("undefined"!=typeof window&&window.crypto?window.crypto:webcrypto).getRandomValues(e)}),randomBytes=(e,r=!1,t)=>r?new Promise((r,a)=>THS.random(e,t).then(e=>r(e)).catch(e=>a(e))):THS.fillRandom(new Uint8Array(e));export{THS,randomBytes};export default THS;
|
|
2
|
+
//# sourceMappingURL=index.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dist/index.min.js.map","names":["Utils","kmac256","createHash","webcrypto","THS","Object","freeze","random","length","layers","harden","trng","buffer","memoryH","mem","passes","parallelism","label","Buffer","alloc","Error","seed","getRandom","k","create","personalization","toBytes","dkLen","i","moment","snapshot","update","fill","sup","jitter","out","digest","b","from","e","async","len","o","fillRandom","arr","window","crypto","getRandomValues","randomBytes","isHardenRNG","Promise","resolve","reject","then","catch","err","Uint8Array"],"sources":["src/index.js"],"mappings":"OAgBOA,UAAW,0BACTC,YAAe,sCACfC,WAAYC,cAAiB,cAsBtC,MAAMC,IAAMC,OAAOC,OAAO,CAYtB,YAAMC,CAAOC,EAAS,IAAIC,OAEtBA,EAAS,IAAGC,OACZA,EAAS,EAACC,KACVA,GAAO,EAAKC,OACZA,GAAS,EAAIC,QAEbA,EAAU,EAACC,IACXA,EAAM,MAAKC,OACXA,EAAS,EAACC,YACVA,EAAc,EAACC,MAEfA,EAAQ,kBAER,CAAC,GAED,GAAIT,GAAU,EACV,OAAOU,OAAOC,MAAM,GAExB,GAAIV,EAAS,GAAKA,EAAS,MACvB,MAAM,IAAIW,MAAM,gBAAgBX,2FAEpC,GAAIC,EAAS,GAAKA,EAAS,EACvB,MAAM,IAAIU,MAAM,oBAAoBV,mDAExC,GAAIA,GAAU,IAAMG,EAAU,GAAKA,EAAUJ,GACzC,MAAM,IAAIW,MAAM,iCAAiCP,yCAA+CJ,OAEpG,GAAI,iBAAoBK,GAAOA,EAAM,KACjC,MAAM,IAAIM,MAAM,eAAeN,4EAEnC,MAAMO,QAAarB,MAAMsB,UAAU,GAAIX,GACvC,IAAIY,EAEJ,IACIA,EAAItB,QAAQuB,OAAOH,EAAM,CACrBI,gBAAiBzB,MAAM0B,QAAQT,GAC/BU,MAAOnB,IAGX,IAAK,IAAIoB,EAAI,EAAGA,EAAInB,EAAQmB,IAAK,CAC7B,MAAMC,QAAe7B,MAAM8B,SAASpB,EAAQG,EAASC,EAAKC,EAAQC,EAAaY,EAAGjB,GAGlF,GAFAY,EAAEQ,OAAOF,GAASA,EAAOG,KAAK,GAE1BtB,GAAU,EAAG,CAEb,MAAMuB,QAAYjC,MAAMsB,UAAU,GAAIX,GACtCY,EAAEQ,OAAOE,GAAMA,EAAID,KAAK,EAC5B,CAEIJ,EAAInB,EAAS,SAAST,MAAMkC,OAAOvB,EAC3C,CAEA,MAAMwB,EAAMZ,EAAEa,SACd,IAAKxB,EAAQ,OAAOuB,EAEpB,MAAME,EAAInB,OAAOoB,KAAKH,GAEtB,OADAA,GAAKH,OAAO,GACLK,CACX,CAEA,MAAOE,GAAK,MAAMA,CAAG,CAErB,QACIlB,GAAMW,OAAO,GACbT,EAAI,IACR,CACJ,EAQAiB,KAAU,MAACC,EAAKC,UAAkBtC,IAAIG,OAAOkC,EAAKC,GAQlDF,IAAS,MAACC,EAAKC,UAAkBtC,IAAIG,OAAOkC,EAAKC,GAUjDF,IAAS,MAACC,SAAqBzC,MAAMsB,UAAUmB,GAAK,GASpDE,WAAWC,IAEgB,oBAAXC,QAA0BA,OAAOC,OACnCD,OAAOC,OACP3C,WACR4C,gBAAgBH,KAYpBI,YAAc,CAACP,EAAKQ,GAAc,EAAOP,IAEtCO,EAIE,IAAIC,QAAQ,CAACC,EAASC,IACzBhD,IAAIG,OAAOkC,EAAKC,GACXW,KAAKlB,GAAOgB,EAAQhB,IACpBmB,MAAMC,GAAOH,EAAOG,KANlBnD,IAAIuC,WAAW,IAAIa,WAAWf,WASpCrC,IAAK4C,4BACC5C","ignoreList":[]}
|
package/dist/util.min.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import fs from"node:fs/promises";import{promisify}from"node:util";import{randomBytes,timingSafeEqual,createHash,argon2 as _argon2}from"node:crypto";const argon2=promisify(_argon2),defined_past=process.hrtime.bigint();export const Utils=Object.freeze({async snapshot(e,t,n,r,i,a,o){if(e<=1)return this.bigIntToBuffer(process.hrtime.bigint());const s=process.resourceUsage(),f=process.memoryUsage(),g=[process.hrtime.bigint(),BigInt(Math.round(1e6*process.uptime())),BigInt(s.userCPUTime),BigInt(s.systemCPUTime),BigInt(s.minorPageFault),BigInt(s.majorPageFault),BigInt(s.voluntaryContextSwitches),BigInt(s.involuntaryContextSwitches),BigInt(f.heapUsed),BigInt(f.heapTotal),BigInt(f.external),BigInt(f.arrayBuffers||0),BigInt(f.rss),BigInt(Math.round(1e6*performance.now())),defined_past,BigInt(Date.now()),BigInt(a||0)].map(e=>this.bigIntToBuffer(e)),c=Buffer.allocUnsafe(128),l=createHash("sha3-512").update(c).update(process.hrtime.bigint().toString()).digest();c.fill(0);const u=Buffer.concat([l,...g]);if(l.fill(0),e<=2||a>=t)return u;let m,B;try{m=await this.getRandom(16,o),B=await argon2("argon2id",{nonce:m,message:u,memory:n,passes:r,parallelism:i,tagLength:64});const t=Buffer.concat([B,u]);if(e<=3)return t;throw new Error(`Hardening level [${e}] is unknown. Use 0 (Basic) through 3 (Heavy).`)}catch(e){throw e}finally{B?.fill?.(0),m?.fill?.(0),u?.fill?.(0)}},async getRandom(e,t){if(!t)return randomBytes(e);let n;try{n=await fs.open("/dev/urandom","r");const t=Buffer.allocUnsafe(e),{bytesRead:r}=await n.read(t,0,e,0);return r<e?randomBytes(e):t}catch(t){return randomBytes(e)}finally{n&&await n.close()}},async jitter(e){const t=await this.getRandom(64,e);timingSafeEqual(t,t),t.fill(0)},bigIntToBuffer(e){if("bigint"!=typeof e&&(e=BigInt(e)),e<0n)throw new RangeError("Negative values not supported");if(0n===e){const e=Buffer.allocUnsafe(3);return e.writeUInt16BE(1,0),e[2]=0,e}let t=e.toString(16);t.length%2!=0&&(t="0"+t);const n=Buffer.from(t,"hex");if(n.length>65535)throw new RangeError("BigInt exceeds 16-bit length header capacity");const r=Buffer.allocUnsafe(2);return r.writeUInt16BE(n.length,0),Buffer.concat([r,n])},toBytes:e=>e instanceof Uint8Array?e:"string"==typeof e?(new TextEncoder).encode(e):(new TextEncoder).encode(JSON.stringify(e)||"")});export default Utils;
|
|
2
|
+
//# sourceMappingURL=util.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dist/util.min.js.map","names":["fs","promisify","randomBytes","timingSafeEqual","createHash","_argon2","argon2","defined_past","process","hrtime","bigint","Utils","Object","freeze","snapshot","h","mh","mem","t","p","s","useUrandom","this","bigIntToBuffer","u","resourceUsage","m","memoryUsage","metrics","BigInt","Math","round","uptime","userCPUTime","systemCPUTime","minorPageFault","majorPageFault","voluntaryContextSwitches","involuntaryContextSwitches","heapUsed","heapTotal","external","arrayBuffers","rss","performance","now","Date","map","n","noise","Buffer","allocUnsafe","hashedNoise","update","toString","digest","fill","b","concat","nonce","memHard","getRandom","message","memory","passes","parallelism","tagLength","result","Error","e","len","fd","open","buffer","bytesRead","read","close","jitter","RangeError","buf","writeUInt16BE","hex","length","data","from","header","toBytes","input","Uint8Array","TextEncoder","encode","JSON","stringify"],"sources":["src/util.js"],"mappings":"OAgBOA,OAAQ,0BACNC,cAAiB,mBACjBC,YAAaC,gBAAiBC,qBAAsBC,YAAe,cAE5E,MAAMC,OAASL,UAAUI,SACnBE,aAAeC,QAAQC,OAAOC,gBAQ7B,MAAMC,MAAQC,OAAOC,OAAO,CAiB/B,cAAMC,CAASC,EAAGC,EAAIC,EAAKC,EAAGC,EAAGC,EAAGC,GAEhC,GAAIN,GAAK,EAAG,OAAOO,KAAKC,eAAef,QAAQC,OAAOC,UAEtD,MAAMc,EAAIhB,QAAQiB,gBACZC,EAAIlB,QAAQmB,cAGZC,EAAU,CACZpB,QAAQC,OAAOC,SACfmB,OAAOC,KAAKC,MAAyB,IAAnBvB,QAAQwB,WAC1BH,OAAOL,EAAES,aACTJ,OAAOL,EAAEU,eACTL,OAAOL,EAAEW,gBACTN,OAAOL,EAAEY,gBACTP,OAAOL,EAAEa,0BACTR,OAAOL,EAAEc,4BACTT,OAAOH,EAAEa,UACTV,OAAOH,EAAEc,WACTX,OAAOH,EAAEe,UACTZ,OAAOH,EAAEgB,cAAgB,GACzBb,OAAOH,EAAEiB,KACTd,OAAOC,KAAKC,MAA0B,IAApBa,YAAYC,QAC9BtC,aACAsB,OAAOiB,KAAKD,OACZhB,OAAOT,GAAK,IACd2B,IAAIC,GAAK1B,KAAKC,eAAeyB,IAIzBC,EAAQC,OAAOC,YAAY,KAG3BC,EAAchD,WAAW,YAC1BiD,OAAOJ,GACPI,OAAO7C,QAAQC,OAAOC,SAAS4C,YAC/BC,SAGLN,EAAMO,KAAK,GAGX,MAAMC,EAAIP,OAAOQ,OAAO,CACpBN,KACGxB,IAIP,GAFAwB,EAAYI,KAAK,GAEbzC,GAAK,GAAKK,GAAKJ,EAAI,OAAOyC,EAG9B,IAAIE,EAAOC,EAEX,IACID,QAAgBrC,KAAKuC,UAAU,GAAIxC,GACnCuC,QAAgBtD,OAAO,WAAY,CAC/BqD,QACAG,QAASL,EACTM,OAAQ9C,EACR+C,OAAQ9C,EACR+C,YAAa9C,EACb+C,UAAW,KAGf,MAAMC,EAASjB,OAAOQ,OAAO,CAACE,EAASH,IACvC,GAAI1C,GAAK,EAAG,OAAOoD,EAEnB,MAAM,IAAIC,MAAM,oBAAoBrD,kDACxC,CAEA,MAAOsD,GAAK,MAAMA,CAAG,CAErB,QAEIT,GAASJ,OAAO,GAChBG,GAAOH,OAAO,GACdC,GAAGD,OAAO,EACd,CACJ,EAaA,eAAMK,CAAUS,EAAKjD,GACjB,IAAKA,EAAY,OAAOnB,YAAYoE,GAEpC,IAAIC,EACJ,IACIA,QAAWvE,GAAGwE,KAAK,eAAgB,KACnC,MAAMC,EAASvB,OAAOC,YAAYmB,IAC5BI,UAAEA,SAAoBH,EAAGI,KAAKF,EAAQ,EAAGH,EAAK,GAGpD,OAAQI,EAAYJ,EACdpE,YAAYoE,GACZG,CACV,CAEA,MAAOJ,GAAK,OAAOnE,YAAYoE,EAAO,CACtC,QAAgBC,SAAUA,EAAGK,OAAS,CAC1C,EAWA,YAAMC,CAAOxD,GACT,MAAMoC,QAAUnC,KAAKuC,UAAU,GAAIxC,GACnClB,gBAAgBsD,EAAGA,GACnBA,EAAED,KAAK,EACX,EAWA,cAAAjC,CAAeyB,GAEX,GADiB,iBAANA,IAAgBA,EAAInB,OAAOmB,IAClCA,EAAI,GAAI,MAAM,IAAI8B,WAAW,iCAGjC,GAAU,KAAN9B,EAAU,CACV,MAAM+B,EAAM7B,OAAOC,YAAY,GAG/B,OAFA4B,EAAIC,cAAc,EAAG,GACrBD,EAAI,GAAK,EACFA,CACX,CAGA,IAAIE,EAAMjC,EAAEM,SAAS,IACjB2B,EAAIC,OAAS,GAAM,IAAGD,EAAM,IAAMA,GACtC,MAAME,EAAOjC,OAAOkC,KAAKH,EAAK,OAG9B,GAAIE,EAAKD,OAAS,MACd,MAAM,IAAIJ,WAAW,gDAGzB,MAAMO,EAASnC,OAAOC,YAAY,GAGlC,OAFAkC,EAAOL,cAAcG,EAAKD,OAAQ,GAE3BhC,OAAOQ,OAAO,CAAC2B,EAAQF,GAClC,EAUAG,QAAQC,GACAA,aAAiBC,WAAmBD,EACnB,iBAAVA,GAA2B,IAAIE,aAAcC,OAAOH,IACxD,IAAIE,aAAcC,OAAOC,KAAKC,UAAUL,IAAU,qBAIlD5E","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,36 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ths-csprng",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Temporal-Hardening Solution: A CSPRNG wrapper designed to resist historical state reconstruction and hardware backdoors.",
|
|
5
|
-
"main": "./src/index.js",
|
|
6
|
-
"files": [
|
|
7
|
-
"src",
|
|
8
|
-
"LICENSE",
|
|
9
|
-
"README.md",
|
|
10
|
-
"npm-shrinkwrap.json"
|
|
11
|
-
],
|
|
12
|
-
"exports": {
|
|
13
|
-
".": {
|
|
14
|
-
"import": "./src/index.mjs",
|
|
15
|
-
"require": "./src/index.js"
|
|
16
|
-
},
|
|
17
|
-
"./utils": {
|
|
18
|
-
"import": "./src/util.mjs",
|
|
19
|
-
"require": "./src/util.js"
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
5
|
"author": "Aries Harbinger",
|
|
23
6
|
"license": "Apache-2.0",
|
|
24
|
-
"homepage": "https://github.com/harnuma9/temporal-hardening-solution-csprng",
|
|
7
|
+
"homepage": "https://github.com/harnuma9/temporal-hardening-solution-csprng#readme",
|
|
25
8
|
"repository": {
|
|
26
9
|
"type": "git",
|
|
27
10
|
"url": "git+https://github.com/harnuma9/temporal-hardening-solution-csprng.git"
|
|
28
11
|
},
|
|
12
|
+
"funding": [
|
|
13
|
+
{
|
|
14
|
+
"type": "individual",
|
|
15
|
+
"url": "https://harnuma9.github.io/donate/"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
29
18
|
"bugs": {
|
|
30
19
|
"url": "https://github.com/harnuma9/temporal-hardening-solution-csprng/issues"
|
|
31
20
|
},
|
|
21
|
+
"type": "module",
|
|
22
|
+
"types": "./src/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./src/index.d.ts",
|
|
26
|
+
"import": "./dist/index.min.js",
|
|
27
|
+
"require": "./dist/index.min.js"
|
|
28
|
+
},
|
|
29
|
+
"./utils": {
|
|
30
|
+
"types": "./src/util.d.ts",
|
|
31
|
+
"import": "./dist/util.min.js",
|
|
32
|
+
"require": "./dist/util.min.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
32
35
|
"engines": {
|
|
33
|
-
"node": ">=
|
|
36
|
+
"node": ">=20"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "bash update.sh",
|
|
40
|
+
"verify": "bash verify.sh",
|
|
41
|
+
"test": "node --test ./test/test_suite.js && node --test ./test/benchmark.js",
|
|
42
|
+
"check": "npm run test && npm run verify",
|
|
43
|
+
"prepublishOnly": "npm run check"
|
|
34
44
|
},
|
|
35
45
|
"keywords": [
|
|
36
46
|
"csprng",
|
|
@@ -47,6 +57,10 @@
|
|
|
47
57
|
"dependencies": {
|
|
48
58
|
"@noble/hashes": "^2.2.0"
|
|
49
59
|
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/node": "^25.6.0",
|
|
62
|
+
"docdash": "^2.0.2"
|
|
63
|
+
},
|
|
50
64
|
"publishConfig": {
|
|
51
65
|
"access": "public"
|
|
52
66
|
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export default THS;
|
|
2
|
+
/**
|
|
3
|
+
* Configuration options for temporal hardening.
|
|
4
|
+
*/
|
|
5
|
+
export type THSOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* - Number of timeline slices (iterations). Range: 2-65536.
|
|
8
|
+
*/
|
|
9
|
+
layers?: number | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* - Hardening level: 0 (hrtime), 1 (+entropy), 2 (+internal), 3 (+Argon2id).
|
|
12
|
+
*/
|
|
13
|
+
harden?: number | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* - Attempts to read from /dev/urandom; falls back to CSPRNG.
|
|
16
|
+
*/
|
|
17
|
+
trng?: boolean | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* - If true, returns a Node.js Buffer; otherwise, returns a Uint8Array.
|
|
20
|
+
*/
|
|
21
|
+
buffer?: boolean | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* - Number of Argon2id runs (Requires harden level 3).
|
|
24
|
+
*/
|
|
25
|
+
memoryH?: number | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* - Memory hardness for Argon2id in KiB (Default: 16MB).
|
|
28
|
+
*/
|
|
29
|
+
mem?: number | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* - Number of iterations (t) for Argon2id.
|
|
32
|
+
*/
|
|
33
|
+
passes?: number | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* - Degree of parallelism (p) for Argon2id.
|
|
36
|
+
*/
|
|
37
|
+
parallelism?: number | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* - KMAC personalization string.
|
|
40
|
+
*/
|
|
41
|
+
label?: string | undefined;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Configuration options for temporal hardening.
|
|
45
|
+
* @typedef {Object} THSOptions
|
|
46
|
+
* @property {number} [layers=128] - Number of timeline slices (iterations). Range: 2-65536.
|
|
47
|
+
* @property {number} [harden=2] - Hardening level: 0 (hrtime), 1 (+entropy), 2 (+internal), 3 (+Argon2id).
|
|
48
|
+
* @property {boolean} [trng=false] - Attempts to read from /dev/urandom; falls back to CSPRNG.
|
|
49
|
+
* @property {boolean} [buffer=true] - If true, returns a Node.js Buffer; otherwise, returns a Uint8Array.
|
|
50
|
+
* @property {number} [memoryH=1] - Number of Argon2id runs (Requires harden level 3).
|
|
51
|
+
* @property {number} [mem=16384] - Memory hardness for Argon2id in KiB (Default: 16MB).
|
|
52
|
+
* @property {number} [passes=3] - Number of iterations (t) for Argon2id.
|
|
53
|
+
* @property {number} [parallelism=4] - Degree of parallelism (p) for Argon2id.
|
|
54
|
+
* @property {string} [label='THS-Default-v1'] - KMAC personalization string.
|
|
55
|
+
*/
|
|
56
|
+
/**
|
|
57
|
+
* Temporal-Hardening Solution - Applied in Random Number Generator.
|
|
58
|
+
* @namespace THS
|
|
59
|
+
* @readonly
|
|
60
|
+
*/
|
|
61
|
+
export const THS: Readonly<{
|
|
62
|
+
/**
|
|
63
|
+
* Generates cryptographically secure random bytes with temporal hardening.
|
|
64
|
+
* Incorporates time-variance and system entropy over multiple metabolic cycles.
|
|
65
|
+
* @async
|
|
66
|
+
* @memberof THS
|
|
67
|
+
* @param {number} [length=32] - The desired length of the output bytes.
|
|
68
|
+
* @param {THSOptions} [options={}] - Hardening and hashing configuration.
|
|
69
|
+
* @returns {Promise<Buffer|Uint8Array>} The hardened random bytes.
|
|
70
|
+
* @throws {Error} If layers, harden level, or memory parameters are out of bounds.
|
|
71
|
+
*/
|
|
72
|
+
random(length?: number, { layers, harden, trng, buffer, memoryH, mem, passes, parallelism, label }?: THSOptions): Promise<Buffer | Uint8Array>;
|
|
73
|
+
/**
|
|
74
|
+
* Alias for {@link THS.random}
|
|
75
|
+
* @async
|
|
76
|
+
* @memberof THS
|
|
77
|
+
*/
|
|
78
|
+
rand(len: any, o: any): Promise<any>;
|
|
79
|
+
/**
|
|
80
|
+
* Alias for {@link THS.random}
|
|
81
|
+
* @async
|
|
82
|
+
* @memberof THS
|
|
83
|
+
*/
|
|
84
|
+
rnd(len: any, o: any): Promise<any>;
|
|
85
|
+
/**
|
|
86
|
+
* Direct access to entropy sources (e.g., /dev/urandom) without temporal hardening.
|
|
87
|
+
* @async
|
|
88
|
+
* @memberof THS
|
|
89
|
+
* @param {number} len - Byte length to retrieve.
|
|
90
|
+
* @returns {Promise<Buffer>}
|
|
91
|
+
*/
|
|
92
|
+
raw(len: number): Promise<Buffer>;
|
|
93
|
+
/**
|
|
94
|
+
* Fills a typed array with random values using the environment's Webcrypto implementation.
|
|
95
|
+
* @memberof THS
|
|
96
|
+
* @param {Uint8Array|Int8Array|Uint16Array|Int16Array|Uint32Array|Int32Array} arr - The array to fill.
|
|
97
|
+
* @returns {Uint8Array|Int8Array|Uint16Array|Int16Array|Uint32Array|Int32Array} The filled array.
|
|
98
|
+
*/
|
|
99
|
+
fillRandom(arr: Uint8Array | Int8Array | Uint16Array | Int16Array | Uint32Array | Int32Array): Uint8Array | Int8Array | Uint16Array | Int16Array | Uint32Array | Int32Array;
|
|
100
|
+
}>;
|
|
101
|
+
/**
|
|
102
|
+
* Universal wrapper for random byte generation.
|
|
103
|
+
* @param {number} len - The number of bytes to generate.
|
|
104
|
+
* @param {boolean} [isHardenRNG=false] - If true, uses the asynchronous THS hardening logic.
|
|
105
|
+
* @param {THSOptions} [o] - Options passed only if isHardenRNG is true.
|
|
106
|
+
* @returns {Uint8Array|Promise<Buffer|Uint8Array>} Returns Uint8Array if synchronous, otherwise a Promise.
|
|
107
|
+
*/
|
|
108
|
+
export function randomBytes(len: number, isHardenRNG?: boolean, o?: THSOptions): Uint8Array | Promise<Buffer | Uint8Array>;
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* THS (Temporal-Hardening Solution)
|
|
3
|
-
*
|
|
4
2
|
* Copyright 2026 Aries Harbinger
|
|
5
3
|
*
|
|
6
4
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -16,17 +14,41 @@
|
|
|
16
14
|
* limitations under the License.
|
|
17
15
|
*/
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
import Utils from 'ths-csprng/utils';
|
|
18
|
+
import { kmac256 } from '@noble/hashes/sha3-addons.js';
|
|
19
|
+
import { createHash, webcrypto } from 'node:crypto';
|
|
20
|
+
|
|
22
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Configuration options for temporal hardening.
|
|
24
|
+
* @typedef {Object} THSOptions
|
|
25
|
+
* @property {number} [layers=128] - Number of timeline slices (iterations). Range: 2-65536.
|
|
26
|
+
* @property {number} [harden=2] - Hardening level: 0 (hrtime), 1 (+entropy), 2 (+internal), 3 (+Argon2id).
|
|
27
|
+
* @property {boolean} [trng=false] - Attempts to read from /dev/urandom; falls back to CSPRNG.
|
|
28
|
+
* @property {boolean} [buffer=true] - If true, returns a Node.js Buffer; otherwise, returns a Uint8Array.
|
|
29
|
+
* @property {number} [memoryH=1] - Number of Argon2id runs (Requires harden level 3).
|
|
30
|
+
* @property {number} [mem=16384] - Memory hardness for Argon2id in KiB (Default: 16MB).
|
|
31
|
+
* @property {number} [passes=3] - Number of iterations (t) for Argon2id.
|
|
32
|
+
* @property {number} [parallelism=4] - Degree of parallelism (p) for Argon2id.
|
|
33
|
+
* @property {string} [label='THS-Default-v1'] - KMAC personalization string.
|
|
34
|
+
*/
|
|
23
35
|
|
|
24
36
|
/**
|
|
25
|
-
* Temporal-Hardening Solution - Applied in Random Number Generator
|
|
37
|
+
* Temporal-Hardening Solution - Applied in Random Number Generator.
|
|
38
|
+
* @namespace THS
|
|
39
|
+
* @readonly
|
|
26
40
|
*/
|
|
27
41
|
const THS = Object.freeze({
|
|
42
|
+
|
|
28
43
|
/**
|
|
29
44
|
* Generates cryptographically secure random bytes with temporal hardening.
|
|
45
|
+
* Incorporates time-variance and system entropy over multiple metabolic cycles.
|
|
46
|
+
* @async
|
|
47
|
+
* @memberof THS
|
|
48
|
+
* @param {number} [length=32] - The desired length of the output bytes.
|
|
49
|
+
* @param {THSOptions} [options={}] - Hardening and hashing configuration.
|
|
50
|
+
* @returns {Promise<Buffer|Uint8Array>} The hardened random bytes.
|
|
51
|
+
* @throws {Error} If layers, harden level, or memory parameters are out of bounds.
|
|
30
52
|
*/
|
|
31
53
|
async random(length = 32, {
|
|
32
54
|
|
|
@@ -39,6 +61,7 @@ const THS = Object.freeze({
|
|
|
39
61
|
mem = 16384, // memory hardness of Argon2id (default: 16MB)
|
|
40
62
|
passes = 3, // (t) passes for Argon2id (default: 3)
|
|
41
63
|
parallelism = 4, // (p) parallelism for Argon2id (default: 4)
|
|
64
|
+
|
|
42
65
|
label = 'THS-Default-v1' // kmac label
|
|
43
66
|
|
|
44
67
|
} = {}) {
|
|
@@ -84,24 +107,51 @@ const THS = Object.freeze({
|
|
|
84
107
|
if (!buffer) return out;
|
|
85
108
|
|
|
86
109
|
const b = Buffer.from(out);
|
|
87
|
-
|
|
110
|
+
out?.fill?.(0);
|
|
111
|
+
return b;
|
|
88
112
|
}
|
|
89
113
|
|
|
90
114
|
catch (e) { throw e; }
|
|
91
115
|
|
|
92
116
|
finally {
|
|
93
|
-
seed
|
|
117
|
+
seed?.fill?.(0);
|
|
94
118
|
k = null;
|
|
95
119
|
}
|
|
96
120
|
},
|
|
97
121
|
|
|
98
|
-
rand(len, o) { return THS.random(len, o) },
|
|
99
|
-
rnd (len, o) { return THS.random(len, o) },
|
|
100
122
|
|
|
101
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Alias for {@link THS.random}
|
|
125
|
+
* @async
|
|
126
|
+
* @memberof THS
|
|
127
|
+
*/
|
|
128
|
+
async rand(len, o) { return await THS.random(len, o) },
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Alias for {@link THS.random}
|
|
133
|
+
* @async
|
|
134
|
+
* @memberof THS
|
|
135
|
+
*/
|
|
136
|
+
async rnd(len, o) { return await THS.random(len, o) },
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Direct access to entropy sources (e.g., /dev/urandom) without temporal hardening.
|
|
141
|
+
* @async
|
|
142
|
+
* @memberof THS
|
|
143
|
+
* @param {number} len - Byte length to retrieve.
|
|
144
|
+
* @returns {Promise<Buffer>}
|
|
145
|
+
*/
|
|
102
146
|
async raw(len) { return await Utils.getRandom(len, true) },
|
|
103
147
|
|
|
104
|
-
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Fills a typed array with random values using the environment's Webcrypto implementation.
|
|
151
|
+
* @memberof THS
|
|
152
|
+
* @param {Uint8Array|Int8Array|Uint16Array|Int16Array|Uint32Array|Int32Array} arr - The array to fill.
|
|
153
|
+
* @returns {Uint8Array|Int8Array|Uint16Array|Int16Array|Uint32Array|Int32Array} The filled array.
|
|
154
|
+
*/
|
|
105
155
|
fillRandom(arr) {
|
|
106
156
|
return (
|
|
107
157
|
(typeof window !== 'undefined' && window.crypto)
|
|
@@ -111,6 +161,25 @@ const THS = Object.freeze({
|
|
|
111
161
|
}
|
|
112
162
|
});
|
|
113
163
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Universal wrapper for random byte generation.
|
|
167
|
+
* @param {number} len - The number of bytes to generate.
|
|
168
|
+
* @param {boolean} [isHardenRNG=false] - If true, uses the asynchronous THS hardening logic.
|
|
169
|
+
* @param {THSOptions} [o] - Options passed only if isHardenRNG is true.
|
|
170
|
+
* @returns {Uint8Array|Promise<Buffer|Uint8Array>} Returns Uint8Array if synchronous, otherwise a Promise.
|
|
171
|
+
*/
|
|
172
|
+
const randomBytes = (len, isHardenRNG = false, o) => {
|
|
173
|
+
// Simple Random (Synchronous)
|
|
174
|
+
if (!isHardenRNG)
|
|
175
|
+
return THS.fillRandom(new Uint8Array(len));
|
|
176
|
+
|
|
177
|
+
// Hardened Random (Asynchronous)
|
|
178
|
+
return new Promise((resolve, reject) =>
|
|
179
|
+
THS.random(len, o)
|
|
180
|
+
.then(out => resolve(out))
|
|
181
|
+
.catch(err => reject(err)));
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export { THS, randomBytes };
|
|
185
|
+
export default THS;
|
package/src/util.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @namespace Utils
|
|
3
|
+
* @description A collection of cryptographic and system-entropy utilities for the THS framework.
|
|
4
|
+
* @readonly
|
|
5
|
+
*/
|
|
6
|
+
export const Utils: Readonly<{
|
|
7
|
+
/**
|
|
8
|
+
* Captures a high-resolution snapshot of the Node.js metabolic state.
|
|
9
|
+
* Uses concatenation to prevent entropy collisions and optionally applies Argon2id hardening.
|
|
10
|
+
* @async
|
|
11
|
+
* @memberof Utils
|
|
12
|
+
* @param {number} h - Hardening level: 1 (Basic), 2 (Machine State), 3 (Argon2id).
|
|
13
|
+
* @param {number} mh - Maximum hardening threshold for the sequential salt.
|
|
14
|
+
* @param {number} mem - Memory cost for Argon2id (in KiB).
|
|
15
|
+
* @param {number} t - Iterations (time cost) for Argon2id.
|
|
16
|
+
* @param {number} p - Parallelism factor for Argon2id.
|
|
17
|
+
* @param {number} s - Sequential salt/counter to prevent state collisions.
|
|
18
|
+
* @param {boolean} useUrandom - If true, attempts to read from /dev/urandom directly.
|
|
19
|
+
* @returns {Promise<Buffer>} A buffer containing the metabolic snapshot.
|
|
20
|
+
* @throws {Error} Throws if an unknown hardening level is provided or if Argon2 fails.
|
|
21
|
+
*/
|
|
22
|
+
snapshot(h: number, mh: number, mem: number, t: number, p: number, s: number, useUrandom: boolean): Promise<Buffer>;
|
|
23
|
+
/**
|
|
24
|
+
* Attempts to harvest raw entropy from the OS device (/dev/urandom).
|
|
25
|
+
* Fallbacks to hardware-backed Node.js `crypto.randomBytes` on failure or non-linux systems.
|
|
26
|
+
*
|
|
27
|
+
* @async
|
|
28
|
+
* @memberof Utils
|
|
29
|
+
* @param {number} len - The number of bytes to generate.
|
|
30
|
+
* @param {boolean} useUrandom - Whether to force usage of /dev/urandom.
|
|
31
|
+
* @returns {Promise<Buffer>} A buffer containing random bytes.
|
|
32
|
+
*/
|
|
33
|
+
getRandom(len: number, useUrandom: boolean): Promise<Buffer>;
|
|
34
|
+
/**
|
|
35
|
+
* Micro-architectural noise generator.
|
|
36
|
+
* Performs a timing-safe comparison on random data to create CPU jitter and then wipes the buffer.
|
|
37
|
+
* @async
|
|
38
|
+
* @memberof Utils
|
|
39
|
+
* @param {boolean} useUrandom - Whether to use /dev/urandom for the jitter source.
|
|
40
|
+
* @returns {Promise<void>}
|
|
41
|
+
*/
|
|
42
|
+
jitter(useUrandom: boolean): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Optimized BigInt to Buffer conversion with a 16-bit Length Header.
|
|
45
|
+
* Prepends a 2-byte length header (Big-Endian) followed by the big-endian representation of the number.
|
|
46
|
+
* @memberof Utils
|
|
47
|
+
* @param {bigint|number} n - The BigInt or number to convert.
|
|
48
|
+
* @returns {Buffer} Buffer formatted as `[Length(2 bytes)][Data]`.
|
|
49
|
+
* @throws {RangeError} If the value is negative or the resulting byte length exceeds 65535.
|
|
50
|
+
*/
|
|
51
|
+
bigIntToBuffer(n: bigint | number): Buffer;
|
|
52
|
+
/**
|
|
53
|
+
* Normalizes various input types (objects, strings, Uint8Arrays) into a Uint8Array.
|
|
54
|
+
* Useful for preparing data for cryptographic hashing.
|
|
55
|
+
* @memberof Utils
|
|
56
|
+
* @param {Uint8Array|string|object} input - The data to normalize.
|
|
57
|
+
* @returns {Uint8Array} The input represented as bytes.
|
|
58
|
+
*/
|
|
59
|
+
toBytes(input: Uint8Array | string | object): Uint8Array;
|
|
60
|
+
}>;
|
|
61
|
+
export default Utils;
|
package/src/util.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* THS (Temporal-Hardening Solution)
|
|
3
|
-
*
|
|
4
2
|
* Copyright 2026 Aries Harbinger
|
|
5
3
|
*
|
|
6
4
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -16,18 +14,35 @@
|
|
|
16
14
|
* limitations under the License.
|
|
17
15
|
*/
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
import fs from 'node:fs/promises';
|
|
18
|
+
import { promisify } from 'node:util';
|
|
19
|
+
import { randomBytes, timingSafeEqual, createHash, argon2 as _argon2 } from 'node:crypto';
|
|
22
20
|
|
|
23
21
|
const argon2 = promisify(_argon2);
|
|
24
22
|
const defined_past = process.hrtime.bigint();
|
|
25
23
|
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
/**
|
|
26
|
+
* @namespace Utils
|
|
27
|
+
* @description A collection of cryptographic and system-entropy utilities for the THS framework.
|
|
28
|
+
* @readonly
|
|
29
|
+
*/
|
|
30
|
+
export const Utils = Object.freeze({
|
|
31
|
+
|
|
28
32
|
/**
|
|
29
33
|
* Captures a high-resolution snapshot of the Node.js metabolic state.
|
|
30
|
-
* Uses concatenation to prevent entropy collisions.
|
|
34
|
+
* Uses concatenation to prevent entropy collisions and optionally applies Argon2id hardening.
|
|
35
|
+
* @async
|
|
36
|
+
* @memberof Utils
|
|
37
|
+
* @param {number} h - Hardening level: 1 (Basic), 2 (Machine State), 3 (Argon2id).
|
|
38
|
+
* @param {number} mh - Maximum hardening threshold for the sequential salt.
|
|
39
|
+
* @param {number} mem - Memory cost for Argon2id (in KiB).
|
|
40
|
+
* @param {number} t - Iterations (time cost) for Argon2id.
|
|
41
|
+
* @param {number} p - Parallelism factor for Argon2id.
|
|
42
|
+
* @param {number} s - Sequential salt/counter to prevent state collisions.
|
|
43
|
+
* @param {boolean} useUrandom - If true, attempts to read from /dev/urandom directly.
|
|
44
|
+
* @returns {Promise<Buffer>} A buffer containing the metabolic snapshot.
|
|
45
|
+
* @throws {Error} Throws if an unknown hardening level is provided or if Argon2 fails.
|
|
31
46
|
*/
|
|
32
47
|
async snapshot(h, mh, mem, t, p, s, useUrandom) {
|
|
33
48
|
// Level 1: Minimal data
|
|
@@ -103,15 +118,22 @@ const Utils = Object.freeze({
|
|
|
103
118
|
|
|
104
119
|
finally {
|
|
105
120
|
// wipe result
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
b
|
|
121
|
+
memHard?.fill?.(0);
|
|
122
|
+
nonce?.fill?.(0);
|
|
123
|
+
b?.fill?.(0);
|
|
109
124
|
}
|
|
110
125
|
},
|
|
111
126
|
|
|
127
|
+
|
|
112
128
|
/**
|
|
113
|
-
* Attempts to harvest raw entropy from the OS device.
|
|
114
|
-
*
|
|
129
|
+
* Attempts to harvest raw entropy from the OS device (/dev/urandom).
|
|
130
|
+
* Fallbacks to hardware-backed Node.js `crypto.randomBytes` on failure or non-linux systems.
|
|
131
|
+
*
|
|
132
|
+
* @async
|
|
133
|
+
* @memberof Utils
|
|
134
|
+
* @param {number} len - The number of bytes to generate.
|
|
135
|
+
* @param {boolean} useUrandom - Whether to force usage of /dev/urandom.
|
|
136
|
+
* @returns {Promise<Buffer>} A buffer containing random bytes.
|
|
115
137
|
*/
|
|
116
138
|
async getRandom(len, useUrandom) {
|
|
117
139
|
if (!useUrandom) return randomBytes(len);
|
|
@@ -132,8 +154,14 @@ const Utils = Object.freeze({
|
|
|
132
154
|
finally { if (fd) await fd.close(); }
|
|
133
155
|
},
|
|
134
156
|
|
|
157
|
+
|
|
135
158
|
/**
|
|
136
159
|
* Micro-architectural noise generator.
|
|
160
|
+
* Performs a timing-safe comparison on random data to create CPU jitter and then wipes the buffer.
|
|
161
|
+
* @async
|
|
162
|
+
* @memberof Utils
|
|
163
|
+
* @param {boolean} useUrandom - Whether to use /dev/urandom for the jitter source.
|
|
164
|
+
* @returns {Promise<void>}
|
|
137
165
|
*/
|
|
138
166
|
async jitter(useUrandom) {
|
|
139
167
|
const b = await this.getRandom(64, useUrandom);
|
|
@@ -141,9 +169,14 @@ const Utils = Object.freeze({
|
|
|
141
169
|
b.fill(0);
|
|
142
170
|
},
|
|
143
171
|
|
|
172
|
+
|
|
144
173
|
/**
|
|
145
|
-
* Optimized BigInt to Buffer conversion with 16-bit Length Header.
|
|
146
|
-
* Prepends a 2-byte length header (Big-Endian) followed by the
|
|
174
|
+
* Optimized BigInt to Buffer conversion with a 16-bit Length Header.
|
|
175
|
+
* Prepends a 2-byte length header (Big-Endian) followed by the big-endian representation of the number.
|
|
176
|
+
* @memberof Utils
|
|
177
|
+
* @param {bigint|number} n - The BigInt or number to convert.
|
|
178
|
+
* @returns {Buffer} Buffer formatted as `[Length(2 bytes)][Data]`.
|
|
179
|
+
* @throws {RangeError} If the value is negative or the resulting byte length exceeds 65535.
|
|
147
180
|
*/
|
|
148
181
|
bigIntToBuffer(n) {
|
|
149
182
|
if (typeof n !== 'bigint') n = BigInt(n);
|
|
@@ -173,8 +206,13 @@ const Utils = Object.freeze({
|
|
|
173
206
|
return Buffer.concat([header, data]);
|
|
174
207
|
},
|
|
175
208
|
|
|
209
|
+
|
|
176
210
|
/**
|
|
177
|
-
* Normalizes various input types into a Uint8Array.
|
|
211
|
+
* Normalizes various input types (objects, strings, Uint8Arrays) into a Uint8Array.
|
|
212
|
+
* Useful for preparing data for cryptographic hashing.
|
|
213
|
+
* @memberof Utils
|
|
214
|
+
* @param {Uint8Array|string|object} input - The data to normalize.
|
|
215
|
+
* @returns {Uint8Array} The input represented as bytes.
|
|
178
216
|
*/
|
|
179
217
|
toBytes(input) {
|
|
180
218
|
if (input instanceof Uint8Array) return input;
|
|
@@ -183,5 +221,4 @@ const Utils = Object.freeze({
|
|
|
183
221
|
}
|
|
184
222
|
});
|
|
185
223
|
|
|
186
|
-
|
|
187
|
-
module.exports.Utils = Utils;
|
|
224
|
+
export default Utils;
|
package/src/index.mjs
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* THS (Temporal-Hardening Solution)
|
|
3
|
-
*
|
|
4
|
-
* Copyright 2026 Aries Harbinger
|
|
5
|
-
*
|
|
6
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
-
* you may not use this file except in compliance with the License.
|
|
8
|
-
* You may obtain a copy of the License at
|
|
9
|
-
*
|
|
10
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
-
*
|
|
12
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
-
* See the License for the specific language governing permissions and
|
|
16
|
-
* limitations under the License.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import Utils from './util.mjs';
|
|
20
|
-
import { kmac256 } from '@noble/hashes/sha3-addons.js';
|
|
21
|
-
import { createHash, webcrypto } from 'node:crypto';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Temporal-Hardening Solution - Applied in Random Number Generator
|
|
26
|
-
*/
|
|
27
|
-
export const THS = Object.freeze({
|
|
28
|
-
/**
|
|
29
|
-
* Generates cryptographically secure random bytes with temporal hardening.
|
|
30
|
-
*/
|
|
31
|
-
async random(length = 32, {
|
|
32
|
-
|
|
33
|
-
layers = 128, // number of timeline slices (iterations)
|
|
34
|
-
harden = 2, // levels: 0 (hrtime), 1 (+entropy), 2 (+internal), 3 (+Argon2id)
|
|
35
|
-
trng = false, // attempts to read /dev/urandom, fallback to randomBytes()
|
|
36
|
-
buffer = true, // return as buffer
|
|
37
|
-
|
|
38
|
-
memoryH = 1, // number of Argon2id runs (requires harden=3 level)
|
|
39
|
-
mem = 16384, // memory hardness of Argon2id (default: 16MB)
|
|
40
|
-
passes = 3, // (t) passes for Argon2id (default: 3)
|
|
41
|
-
parallelism = 4, // (p) parallelism for Argon2id (default: 4)
|
|
42
|
-
label = 'THS-Default-v1' // kmac label
|
|
43
|
-
|
|
44
|
-
} = {}) {
|
|
45
|
-
|
|
46
|
-
if (length <= 0)
|
|
47
|
-
return Buffer.alloc(0);
|
|
48
|
-
|
|
49
|
-
if (layers < 2 || layers > 65536)
|
|
50
|
-
throw new Error(`Layer count [${layers}] is invalid. Requires a temporal span of 2-65536 cycles to ensure metabolic variance.`);
|
|
51
|
-
|
|
52
|
-
if (harden < 0 || harden > 3)
|
|
53
|
-
throw new Error(`Hardening level [${harden}] is unknown. Use 0 (Basic) through 3 (Heavy).`);
|
|
54
|
-
|
|
55
|
-
if (harden >= 3 && (memoryH < 1 || memoryH > layers))
|
|
56
|
-
throw new Error(`Memory hardening cycle count [${memoryH}] exceeds available temporal layers [${layers}].`);
|
|
57
|
-
|
|
58
|
-
if ('number' !== typeof mem || mem < 1024)
|
|
59
|
-
throw new Error(`Memory set [${mem}] for Argon2id must be a valid number and not be less than 1024 or 1KB.`);
|
|
60
|
-
|
|
61
|
-
const seed = await Utils.getRandom(32, trng);
|
|
62
|
-
let k;
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
k = kmac256.create(seed, {
|
|
66
|
-
personalization: Utils.toBytes(label),
|
|
67
|
-
dkLen: length
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
for (let i = 0; i < layers; i++) {
|
|
71
|
-
const moment = await Utils.snapshot(harden, memoryH, mem, passes, parallelism, i, trng);
|
|
72
|
-
k.update(moment), moment.fill(0);
|
|
73
|
-
|
|
74
|
-
if (harden >= 1) {
|
|
75
|
-
// Supplemental entropy injected into the moment collection
|
|
76
|
-
const sup = await Utils.getRandom(12, trng);
|
|
77
|
-
k.update(sup), sup.fill(0);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (i < layers - 1) await Utils.jitter(trng);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const out = k.digest();
|
|
84
|
-
if (!buffer) return out;
|
|
85
|
-
|
|
86
|
-
const b = Buffer.from(out);
|
|
87
|
-
return out.fill(0), b;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
catch (e) { throw e; }
|
|
91
|
-
|
|
92
|
-
finally {
|
|
93
|
-
seed.fill(0);
|
|
94
|
-
k = null;
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
rand(len, o) { return THS.random(len, o) },
|
|
99
|
-
rnd (len, o) { return THS.random(len, o) },
|
|
100
|
-
|
|
101
|
-
// Direct use of /dev/urandom (fallback to randomBytes)
|
|
102
|
-
async raw(len) { return await Utils.getRandom(len, true) },
|
|
103
|
-
|
|
104
|
-
// Direct Webcrypto access
|
|
105
|
-
fillRandom(arr) {
|
|
106
|
-
return (
|
|
107
|
-
(typeof window !== 'undefined' && window.crypto)
|
|
108
|
-
? window.crypto
|
|
109
|
-
: webcrypto
|
|
110
|
-
).getRandomValues(arr)
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// Must use await
|
|
115
|
-
export const randomBytes = (len, o) => THS.random(len, o);
|
|
116
|
-
|
|
117
|
-
export default THS;
|
package/src/util.mjs
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* THS (Temporal-Hardening Solution)
|
|
3
|
-
*
|
|
4
|
-
* Copyright 2026 Aries Harbinger
|
|
5
|
-
*
|
|
6
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
-
* you may not use this file except in compliance with the License.
|
|
8
|
-
* You may obtain a copy of the License at
|
|
9
|
-
*
|
|
10
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
-
*
|
|
12
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
-
* See the License for the specific language governing permissions and
|
|
16
|
-
* limitations under the License.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import fs from 'node:fs/promises';
|
|
20
|
-
import { promisify } from 'node:util';
|
|
21
|
-
import { randomBytes, timingSafeEqual, createHash, argon2 as _argon2 } from 'node:crypto';
|
|
22
|
-
|
|
23
|
-
const argon2 = promisify(_argon2);
|
|
24
|
-
const defined_past = process.hrtime.bigint();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export const Utils = Object.freeze({
|
|
28
|
-
/**
|
|
29
|
-
* Captures a high-resolution snapshot of the Node.js metabolic state.
|
|
30
|
-
* Uses concatenation to prevent entropy collisions.
|
|
31
|
-
*/
|
|
32
|
-
async snapshot(h, mh, mem, t, p, s, useUrandom) {
|
|
33
|
-
// Level 1: Minimal data
|
|
34
|
-
if (h <= 1) return this.bigIntToBuffer(process.hrtime.bigint());
|
|
35
|
-
|
|
36
|
-
const u = process.resourceUsage();
|
|
37
|
-
const m = process.memoryUsage();
|
|
38
|
-
|
|
39
|
-
// Level 2: Primary metabolic collection (Machine State)
|
|
40
|
-
const metrics = [
|
|
41
|
-
process.hrtime.bigint(), // CPU Nanoseconds
|
|
42
|
-
BigInt(Math.round(process.uptime() * 1_000_000)),
|
|
43
|
-
BigInt(u.userCPUTime), // User-space CPU usage
|
|
44
|
-
BigInt(u.systemCPUTime), // Kernel-space CPU usage
|
|
45
|
-
BigInt(u.minorPageFault), // Soft memory faults
|
|
46
|
-
BigInt(u.majorPageFault), // Hard I/O faults
|
|
47
|
-
BigInt(u.voluntaryContextSwitches),
|
|
48
|
-
BigInt(u.involuntaryContextSwitches),
|
|
49
|
-
BigInt(m.heapUsed), // V8 actual usage
|
|
50
|
-
BigInt(m.heapTotal), // V8 allocated total
|
|
51
|
-
BigInt(m.external), // C++ object memory
|
|
52
|
-
BigInt(m.arrayBuffers || 0), // Raw buffer memory
|
|
53
|
-
BigInt(m.rss), // Resident Set Size (Physical RAM)
|
|
54
|
-
BigInt(Math.round(performance.now() * 1_000_000)),
|
|
55
|
-
defined_past, // Process start anchor
|
|
56
|
-
BigInt(Date.now()), // Wall clock (Temporal anchor)
|
|
57
|
-
BigInt(s || 0) // Sequential salt
|
|
58
|
-
].map(n => this.bigIntToBuffer(n));
|
|
59
|
-
|
|
60
|
-
// 128 bytes of raw, uninitialized system memory
|
|
61
|
-
// (May contain fragments of previous internal function calls/pointers, which is an additional entropy source)
|
|
62
|
-
const noise = Buffer.allocUnsafe(128);
|
|
63
|
-
|
|
64
|
-
// Hash that insecure system memory with SHA3-512
|
|
65
|
-
const hashedNoise = createHash('sha3-512')
|
|
66
|
-
.update(noise)
|
|
67
|
-
.update(process.hrtime.bigint().toString())
|
|
68
|
-
.digest();
|
|
69
|
-
|
|
70
|
-
// Wipe the noise immediately
|
|
71
|
-
noise.fill(0);
|
|
72
|
-
|
|
73
|
-
// Map metrics to uniquely structured buffers
|
|
74
|
-
const b = Buffer.concat([
|
|
75
|
-
hashedNoise, // This is a COPY of the digest
|
|
76
|
-
...metrics
|
|
77
|
-
]);
|
|
78
|
-
hashedNoise.fill(0); // Wipe the hashed noise
|
|
79
|
-
|
|
80
|
-
if (h <= 2 || s >= mh) return b;
|
|
81
|
-
|
|
82
|
-
// Level 3: Argon2id Memory-Hardening
|
|
83
|
-
let nonce, memHard;
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
nonce = await this.getRandom(16, useUrandom);
|
|
87
|
-
memHard = await argon2('argon2id', {
|
|
88
|
-
nonce,
|
|
89
|
-
message: b,
|
|
90
|
-
memory: mem,
|
|
91
|
-
passes: t,
|
|
92
|
-
parallelism: p,
|
|
93
|
-
tagLength: 64
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
const result = Buffer.concat([memHard, b]);
|
|
97
|
-
if (h <= 3) return result;
|
|
98
|
-
|
|
99
|
-
throw new Error(`Hardening level [${h}] is unknown. Use 0 (Basic) through 3 (Heavy).`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
catch (e) { throw e; }
|
|
103
|
-
|
|
104
|
-
finally {
|
|
105
|
-
// wipe result
|
|
106
|
-
if (memHard) memHard.fill(0);
|
|
107
|
-
if (nonce) nonce.fill(0);
|
|
108
|
-
b.fill(0);
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Attempts to harvest raw entropy from the OS device.
|
|
114
|
-
* Fallback to hardware-backed Node.js randomBytes on failure.
|
|
115
|
-
*/
|
|
116
|
-
async getRandom(len, useUrandom) {
|
|
117
|
-
if (!useUrandom) return randomBytes(len);
|
|
118
|
-
|
|
119
|
-
let fd;
|
|
120
|
-
try {
|
|
121
|
-
fd = await fs.open('/dev/urandom', 'r');
|
|
122
|
-
const buffer = Buffer.allocUnsafe(len);
|
|
123
|
-
const { bytesRead } = await fd.read(buffer, 0, len, 0);
|
|
124
|
-
|
|
125
|
-
// If we didn't read enough bytes for some reason, fallback
|
|
126
|
-
return (bytesRead < len)
|
|
127
|
-
? randomBytes(len)
|
|
128
|
-
: buffer;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
catch (e) { return randomBytes(len); }
|
|
132
|
-
finally { if (fd) await fd.close(); }
|
|
133
|
-
},
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Micro-architectural noise generator.
|
|
137
|
-
*/
|
|
138
|
-
async jitter(useUrandom) {
|
|
139
|
-
const b = await this.getRandom(64, useUrandom);
|
|
140
|
-
timingSafeEqual(b, b);
|
|
141
|
-
b.fill(0);
|
|
142
|
-
},
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Optimized BigInt to Buffer conversion with 16-bit Length Header.
|
|
146
|
-
* Prepends a 2-byte length header (Big-Endian) followed by the data.
|
|
147
|
-
*/
|
|
148
|
-
bigIntToBuffer(n) {
|
|
149
|
-
if (typeof n !== 'bigint') n = BigInt(n);
|
|
150
|
-
if (n < 0n) throw new RangeError('Negative values not supported');
|
|
151
|
-
|
|
152
|
-
// Fast path for zero: [Length: 0x0001] [Value: 0x00]
|
|
153
|
-
if (n === 0n) {
|
|
154
|
-
const buf = Buffer.allocUnsafe(3);
|
|
155
|
-
buf.writeUInt16BE(1, 0); // Length is 1 byte
|
|
156
|
-
buf[2] = 0; // Value is 0
|
|
157
|
-
return buf;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Convert BigInt to Buffer via Hex
|
|
161
|
-
let hex = n.toString(16);
|
|
162
|
-
if (hex.length % 2 !== 0) hex = '0' + hex;
|
|
163
|
-
const data = Buffer.from(hex, 'hex');
|
|
164
|
-
|
|
165
|
-
// Safety check for 16-bit overflow
|
|
166
|
-
if (data.length > 0xFFFF)
|
|
167
|
-
throw new RangeError('BigInt exceeds 16-bit length header capacity');
|
|
168
|
-
|
|
169
|
-
// Structure: [Header (2 bytes)] + [Data]
|
|
170
|
-
const header = Buffer.allocUnsafe(2);
|
|
171
|
-
header.writeUInt16BE(data.length, 0);
|
|
172
|
-
|
|
173
|
-
return Buffer.concat([header, data]);
|
|
174
|
-
},
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Normalizes various input types into a Uint8Array.
|
|
178
|
-
*/
|
|
179
|
-
toBytes(input) {
|
|
180
|
-
if (input instanceof Uint8Array) return input;
|
|
181
|
-
if (typeof input === 'string') return new TextEncoder().encode(input);
|
|
182
|
-
return new TextEncoder().encode(JSON.stringify(input) || '');
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
export default Utils;
|