mumpix 1.0.3 → 1.0.6

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/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ ## 1.0.6 - 2026-02-27
4
+
5
+ ### Security hardening
6
+ - Added file identity metadata (`fileId`) and hash algorithm metadata (`hashAlg`) in headers for new/opened stores.
7
+ - Added strict integrity verification during load/WAL replay for `strict` and `verified` modes.
8
+ - Added monotonic/unique record ID validation in `strict` and `verified` modes.
9
+ - Added support for keyed record hashes (`hmac256`) when `MUMPIX_INTEGRITY_SECRET` is set.
10
+ - Kept backward compatibility for legacy hash records in migration scenarios.
11
+
12
+ ### Licensing and tier model
13
+ - Updated mode capability gating:
14
+ - Community/Free: `eventual`
15
+ - Developer/Teams: `eventual`, `strict`
16
+ - Compliance/Enterprise: `eventual`, `strict`, `verified`
17
+ - Removed free-tier write-count enforcement; gating now focuses on production capabilities.
18
+ - Added optional file-bound license context support (`fid`) for stronger license binding.
19
+
20
+ ### Reliability
21
+ - Hardened strict-mode behavior to fail fast on malformed or tampered WAL/record entries.
22
+
package/README.md CHANGED
@@ -257,3 +257,26 @@ node examples/verified-mode.js # Compliance audit log export
257
257
 
258
258
  Proprietary — COMMERCIAL RESTRICTED. See [LICENSE](LICENSE) for terms.
259
259
  Copyright © 2026 Mumpix (vdsx.cloud). All Rights Reserved.
260
+
261
+ ---
262
+
263
+ ## Licensing & Tiers
264
+
265
+ Mumpix requires a license key for commercial use and advanced features.
266
+
267
+ | Feature | Free | Pro ($79/mo) | Enterprise ($5K) |
268
+ |---|---|---|---|
269
+ | Max Records | 100 | Unlimited | Unlimited |
270
+ | `strict` mode | ✅ | ✅ | ✅ |
271
+ | `verified` mode | ❌ | ✅ | ✅ |
272
+ | Offline Use | ✅ | ✅ | ✅ |
273
+
274
+ ### Using a License Key
275
+
276
+ ```javascript
277
+ const db = await Mumpix.open('./agent.mumpix', {
278
+ licenseKey: 'your-license-key-here'
279
+ });
280
+ ```
281
+
282
+ Get your key at [mumpixdb.com](https://mumpixdb.com).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mumpix",
3
- "version": "1.0.3",
3
+ "version": "1.0.6",
4
4
  "description": "SQLite for AI — embedded, zero-config memory database for AI agents and LLM applications",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -34,8 +34,9 @@
34
34
  "src/",
35
35
  "bin/",
36
36
  "examples/",
37
+ "CHANGELOG.md",
37
38
  "README.md",
38
39
  "LICENSE"
39
40
  ],
40
41
  "homepage": "https://mumpixdb.com"
41
- }
42
+ }
@@ -16,6 +16,7 @@
16
16
  const { MumpixStore } = require('./store');
17
17
  const { MumpixAudit } = require('./audit');
18
18
  const { recall, recallMany } = require('./recall');
19
+ const { LicenseManager } = require('./license');
19
20
 
20
21
  const VALID_MODES = ['eventual', 'strict', 'verified'];
21
22
 
@@ -23,13 +24,15 @@ class MumpixDB {
23
24
  /**
24
25
  * @param {MumpixStore} store
25
26
  * @param {MumpixAudit|null} audit
27
+ * @param {LicenseManager} license
26
28
  * @param {object} opts
27
29
  */
28
- constructor(store, audit, opts = {}) {
29
- this._store = store;
30
- this._audit = audit; // non-null only in 'verified' mode
31
- this._opts = opts;
32
- this._closed = false;
30
+ constructor(store, audit, license, opts = {}) {
31
+ this._store = store;
32
+ this._audit = audit; // non-null only in 'verified' mode
33
+ this._opts = opts;
34
+ this._closed = false;
35
+ this._license = license;
33
36
  }
34
37
 
35
38
  // ─────────────────────────────────────────
@@ -43,9 +46,10 @@ class MumpixDB {
43
46
  * @param {object} [opts]
44
47
  * @param {string} [opts.consistency] 'eventual' | 'strict' | 'verified' (default: 'eventual')
45
48
  * @param {Function} [opts.embedFn] async (texts: string[]) => number[][] — optional custom embeddings
46
- * @returns {MumpixDB}
49
+ * @param {string} [opts.licenseKey] Commercial license key for Pro/Enterprise features
50
+ * @returns {Promise<MumpixDB>}
47
51
  */
48
- static open(filePath, opts = {}) {
52
+ static async open(filePath, opts = {}) {
49
53
  if (!filePath || typeof filePath !== 'string') {
50
54
  throw new TypeError('Mumpix.open() requires a file path string');
51
55
  }
@@ -58,13 +62,21 @@ class MumpixDB {
58
62
  const store = new MumpixStore(filePath);
59
63
  store.open({ consistency });
60
64
 
65
+ // License Initialization & Check
66
+ const license = new LicenseManager(opts.licenseKey);
67
+ license.setFileContext(store.header && store.header.fileId ? store.header.fileId : null);
68
+ await license.init();
69
+
70
+ // Capability-gated consistency mode check (Option B model)
71
+ license.checkLimit('mode', consistency);
72
+
61
73
  let auditLog = null;
62
74
  if (consistency === 'verified') {
63
75
  auditLog = new MumpixAudit(filePath);
64
76
  auditLog.open();
65
77
  }
66
78
 
67
- return new MumpixDB(store, auditLog, { ...opts, consistency });
79
+ return new MumpixDB(store, auditLog, license, { ...opts, consistency });
68
80
  }
69
81
 
70
82
  // ─────────────────────────────────────────
@@ -83,6 +95,9 @@ class MumpixDB {
83
95
  throw new TypeError('remember() requires a non-empty string');
84
96
  }
85
97
 
98
+ // License Check for Record Limit
99
+ this._license.checkLimit('records', this._store.records.length);
100
+
86
101
  const record = this._store.write(content);
87
102
 
88
103
  if (this._audit) {
@@ -90,8 +105,8 @@ class MumpixDB {
90
105
  }
91
106
 
92
107
  return {
93
- written: true,
94
- id: record.id,
108
+ written: true,
109
+ id: record.id,
95
110
  consistency: this._store.header.consistency,
96
111
  };
97
112
  }
@@ -115,7 +130,7 @@ class MumpixDB {
115
130
  const records = this._store.all();
116
131
  if (!records.length) return null;
117
132
 
118
- const t0 = Date.now();
133
+ const t0 = Date.now();
119
134
  const result = await recall(query, records, {
120
135
  embedFn: this._opts.embedFn,
121
136
  ...opts,
@@ -153,10 +168,10 @@ class MumpixDB {
153
168
  });
154
169
 
155
170
  return results.map(r => ({
156
- id: r.id,
171
+ id: r.id,
157
172
  content: r.content,
158
- ts: r.ts,
159
- score: r._score,
173
+ ts: r.ts,
174
+ score: r._score,
160
175
  }));
161
176
  }
162
177
 
@@ -168,9 +183,9 @@ class MumpixDB {
168
183
  async list() {
169
184
  this._assertOpen();
170
185
  return this._store.all().map(r => ({
171
- id: r.id,
186
+ id: r.id,
172
187
  content: r.content,
173
- ts: r.ts,
188
+ ts: r.ts,
174
189
  }));
175
190
  }
176
191
 
@@ -238,6 +253,10 @@ class MumpixDB {
238
253
  */
239
254
  async close() {
240
255
  if (this._closed) return;
256
+
257
+ // Sync usage metrics in background on close
258
+ this._license.syncUsage(this._store.stats()).catch(() => { });
259
+
241
260
  this._store.close();
242
261
  if (this._audit) this._audit.close();
243
262
  this._closed = true;
@@ -0,0 +1,135 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const os = require('os');
5
+ const https = require('https');
6
+ const path = require('path');
7
+
8
+ // Master Public Key (ML-DSA-44 / Dilithium2 - NIST Standard)
9
+ const MASTER_PUBLIC_KEY_B64 = `szA4EoME83k0zoSQnzMWSC5BOEnr1VazfZlyP4JKqufZHIjlc0TalnK4gi4Jsn0PMHAJlzEOp/0PvUiW8L0RN3rj0pKEkuwDY5c1Q5C2u1Dh7VfcyiCzj+JusviaANuJQxoEdi6yeBUXJO7tZpwpTrekF5hrc5V9ccY0U3gAofg+4prZz0bA7aaLxK+/WIYXtO+PR7VOAavMzR536FAdJtfcgz08L1hu7RUiE38hQnAH2yRuBkmcGesw4k0XSx3HZXjVwaVxZsOZpL3DihS2EwbluDIzrb3Tz5nf7a2ak/FRFcTS+LLjM4ywxuq6toKT/laAOt/0AEVp67nFGVm8OU4blpmgO3gA0APpglrVMGFcxrBQyYLqMsgwxmb6dQMydILriHQ9vN+jR8jD3qs5f14FwsZkkMtBgBMNRFybQvrXOxvPF36rknnkeDuE0W+9sSpzOdzM8/UZKInzh+v0yOYPIW1y9oiJb2RtoIz0kJkg0IwR8JOIZ7Svfa62ifT/CFhIsBr6zIVdK7mlxmDqiD3MnlLvfqhyPfdgIbCRLmClXI6Jjpt4d7shzBHvOxCpEGwYw4hVCGTnVhPne8UWmgqU3BkSVBQsHU/7s6jNH/tvZjfkOgWoz81RrnrE8xMGRh5yn/F/bEa0igbyJ/4XlmAPRMkQ+CCIo/NbBlYtKvnMHTwatj3B4Z06hTFl0rnuGbQ9u9JXp/1x7bVztMdSM/T65VW8H/zcvZ7aZTbEMzcTUqMdo9IwOWVfyb6lwg5FSCANKzmyT//CJQtpacS9TjPZ0m4AOFyAwhj57fxY7xMPSYQIyTrpMRkoNACJ9QoSSAnyJ0u1aBTMbiFr8IgjgZm0XL2giz8yD5bi/kS+85qPGut+Hvx66VztumAQUoLpJYJSgVXO4ZYMNY0DraTE48l3Y2RG/KmerSGbjAjZjXPJlKxF94M8Y4Su18iy0BVpS7Ax+8nEzDPVfTqPUoSJctv/YLY6bdMqhi9/rTIrE/3Htm5YwHfZP+BV59JLFflT7iud4nBuRZ5rF7ONGMDELg/sglBOHqkv7bDaSoxgcD2SA0L3sptnW57ohUHwmFWuSE8zYkoUsT0sCwvClbmpcgPUxisSY3duAEC3Q+l+NibYhXsavUuHZ9h/O2oMUUvIxtmcF1t17c1f6QwhYpZlJYuYZ/wWTGWqD5MADslUUHCvla5hoZRhWOsDdQspP8O1Sa2Cpzh8zWuKAr2YaSNsUQi/orwzfWFZ56ss9JAH9d+W1Sqc0qOxeQgMoSF3UmHalDQ+xrRl/b1fcN5ZBNmDaM1o0JHGNl7gB7q0Va7NXH2KD1k21f7POPIAdTXlqquFS+MOdy+atPnhncht3bVzSRcerelIk/YGxKCJbsXqFB38s2tR7u4D56B6LiIl/1x4UmP9MmgvJDPMDwrpD9ExgCIyoordPiixYuOeq5LVG6VanIrrVgRlrGmW2lbVX5PMZfZsfhCPW9/JrvrqBPVtnqAjcappUGG4yJyHYFIXeE6Z1nd5DKdBHXEl6zACV6SwxJJ6qgapyOrXCnTmXqkmtI0ebJWGRJ+NHtgl29FF4LXV7HmNgrYrSFdgc6tv/ZBycEFPmsc/HK2Omo+dQKmVI/6w95ctKOsTQmim+pqz5QcZYE8LnCm+U/8iosivWaJ2s6jB+YXQ6Y4+IF9vSPonyqOViw97Moe9uq7qkSM+sfm2pL/oyGTsluOUftREbtIWFK4f4L9EEIWp9em/dAP6mQ==`;
10
+
11
+ class LicenseManager {
12
+ constructor(key = null) {
13
+ this.key = key;
14
+ this.tier = 'free';
15
+ this.expiry = null;
16
+ this.verified = false;
17
+ this.userId = 'guest';
18
+ this._ml_dsa = null;
19
+ this.fileId = null;
20
+ }
21
+
22
+ // Capability matrix for Option B model.
23
+ // free/community: eventual only
24
+ // developer/teams: eventual + strict
25
+ // compliance/enterprise: eventual + strict + verified
26
+ _capsForTier(tier) {
27
+ const t = String(tier || 'free').toLowerCase();
28
+ if (t === 'developer') return { modes: ['eventual', 'strict'] };
29
+ if (t === 'teams' || t === 'team') return { modes: ['eventual', 'strict'] };
30
+ if (t === 'compliance' || t === 'enterprise' || t === 'verified' || t === 'pro') {
31
+ return { modes: ['eventual', 'strict', 'verified'] };
32
+ }
33
+ return { modes: ['eventual'] };
34
+ }
35
+
36
+ setFileContext(fileId) {
37
+ this.fileId = fileId ? String(fileId) : null;
38
+ }
39
+
40
+ async init() {
41
+ if (!this.key) return false;
42
+ try {
43
+ if (typeof globalThis.crypto === 'undefined') {
44
+ globalThis.crypto = require('node:crypto').webcrypto;
45
+ }
46
+ const mlDsaPath = 'file://' + path.resolve(__dirname, 'ml-dsa.mjs');
47
+ const { ml_dsa44 } = await import(mlDsaPath);
48
+ this._ml_dsa = ml_dsa44;
49
+ return await this.validate(this.key);
50
+ } catch (e) {
51
+ console.error('Mumpix Licensing Initialization Failed:', e.message);
52
+ return false;
53
+ }
54
+ }
55
+
56
+ async validate(key) {
57
+ try {
58
+ const [payloadB64, signatureB64] = key.split('.');
59
+ if (!payloadB64 || !signatureB64) throw new Error('Invalid key format');
60
+
61
+ const payloadRaw = Buffer.from(payloadB64, 'base64').toString();
62
+ const payload = JSON.parse(payloadRaw);
63
+
64
+ // Verify Quantum-Safe Signature (ML-DSA-44)
65
+ const msg = Buffer.from(payloadB64, 'utf-8');
66
+ const sig = Buffer.from(signatureB64, 'base64');
67
+ const pub = Buffer.from(MASTER_PUBLIC_KEY_B64, 'base64');
68
+
69
+ const isValid = this._ml_dsa.verify(sig, msg, pub);
70
+ if (!isValid) throw new Error('Quantum signature verification failed');
71
+
72
+ if (payload.exp && Date.now() > payload.exp) {
73
+ throw new Error('License key has expired');
74
+ }
75
+
76
+ // Optional binding for newly issued licenses.
77
+ // Backward-compatible: if fid missing from payload, accept legacy license.
78
+ if (payload.fid && this.fileId && String(payload.fid) !== String(this.fileId)) {
79
+ throw new Error('License key is bound to a different file');
80
+ }
81
+
82
+ this.userId = payload.id;
83
+ this.tier = payload.tier || 'free';
84
+ this.expiry = payload.exp;
85
+ this.verified = true;
86
+ return true;
87
+ } catch (e) {
88
+ this.verified = false;
89
+ this.tier = 'free';
90
+ return false;
91
+ }
92
+ }
93
+
94
+ checkLimit(type, currentCount) {
95
+ // Option B: no record count gate. Gating is by capabilities/mode.
96
+ if (type === 'records') return true;
97
+
98
+ if (type === 'mode') {
99
+ const requested = String(currentCount || '').toLowerCase();
100
+ const caps = this._capsForTier(this.tier);
101
+ if (!caps.modes.includes(requested)) {
102
+ if (requested === 'strict') {
103
+ throw new Error('MumpixDB "strict" mode requires Developer tier or higher. Upgrade at mumpixdb.com.');
104
+ }
105
+ if (requested === 'verified') {
106
+ throw new Error('MumpixDB "verified" mode requires Compliance tier. Upgrade at mumpixdb.com.');
107
+ }
108
+ throw new Error(`MumpixDB mode "${requested}" is not available on your current tier.`);
109
+ }
110
+ return true;
111
+ }
112
+ return true;
113
+ }
114
+
115
+ async syncUsage(stats) {
116
+ if (process.env.MUMPIX_DISABLE_TELEMETRY === 'true') return;
117
+ const body = JSON.stringify({
118
+ userId: this.userId, tier: this.tier, machine: os.hostname(),
119
+ platform: os.platform(), stats: stats, ts: Date.now()
120
+ });
121
+ const options = {
122
+ hostname: 'api.vdsx.cloud', port: 443, path: '/api/mumpix/usage',
123
+ method: 'POST', headers: {
124
+ 'Content-Type': 'application/json', 'Content-Length': body.length,
125
+ 'X-Mumpix-Key': this.key || 'free'
126
+ }, timeout: 2000
127
+ };
128
+ const req = https.request(options);
129
+ req.on('error', () => { });
130
+ req.write(body);
131
+ req.end();
132
+ }
133
+ }
134
+
135
+ module.exports = { LicenseManager };
@@ -0,0 +1,25 @@
1
+ // Patch for browser and server environment
2
+ if (typeof window !== 'undefined') {
3
+ if (typeof window.require === 'undefined') {
4
+ window.require = function (name) {
5
+ if (name === 'semver') return window.semver;
6
+ return {};
7
+ };
8
+ }
9
+ }
10
+ const require = (typeof window !== 'undefined') ? window.require : function (n) { return {}; };
11
+ /* esm.sh - @noble/post-quantum@0.5.2/ml-dsa */
12
+ function Mt(t) { return t instanceof Uint8Array || ArrayBuffer.isView(t) && t.constructor.name === "Uint8Array" } function Ut(t, e = "") { if (!Number.isSafeInteger(t) || t < 0) { let n = e && `"${e}" `; throw new Error(`${n}expected integer >= 0, got ${t} `) } } function A(t, e, n = "") { let o = Mt(t), r = t?.length, i = e !== void 0; if (!o || i && r !== e) { let c = n && `"${n}" `, a = i ? ` of length ${e} ` : "", g = o ? `length = ${r} ` : `type = ${typeof t} `; throw new Error(c + "expected Uint8Array" + a + ", got " + g) } return t } function Ht(t, e = !0) { if (t.destroyed) throw new Error("Hash instance has been destroyed"); if (e && t.finished) throw new Error("Hash#digest() has already been called") } function ce(t, e) { A(t, void 0, "digestInto() output"); let n = e.outputLen; if (t.length < n) throw new Error('"digestInto() output" expected to be of length >=' + n) } function ie(t) { return new Uint32Array(t.buffer, t.byteOffset, Math.floor(t.byteLength / 4)) } function St(...t) { for (let e = 0; e < t.length; e++)t[e].fill(0) } var je = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68; function Ne(t) { return t << 24 & 4278190080 | t << 8 & 16711680 | t >>> 8 & 65280 | t >>> 24 & 255 } function Fe(t) { for (let e = 0; e < t.length; e++)t[e] = Ne(t[e]); return t } var Ct = je ? t => t : Fe; function Rt(...t) { let e = 0; for (let o = 0; o < t.length; o++) { let r = t[o]; A(r), e += r.length } let n = new Uint8Array(e); for (let o = 0, r = 0; o < t.length; o++) { let i = t[o]; n.set(i, r), r += i.length } return n } function fe(t, e = {}) { let n = (r, i) => t(i).update(r).digest(), o = t(void 0); return n.outputLen = o.outputLen, n.blockLen = o.blockLen, n.create = r => t(r), Object.assign(n, e), Object.freeze(n) } function ue(t = 32) { let e = typeof globalThis == "object" ? globalThis.crypto : null; if (typeof e?.getRandomValues != "function") throw new Error("crypto.getRandomValues must be defined"); return e.getRandomValues(new Uint8Array(t)) } var vt = t => ({ oid: Uint8Array.from([6, 9, 96, 134, 72, 1, 101, 3, 4, 2, t]) }); function le(t, e = "") { if (typeof t != "boolean") { let n = e && `"${e}" `; throw new Error(n + "expected boolean, got type=" + typeof t) } return t } var Et = BigInt(4294967295), ae = BigInt(32); function $e(t, e = !1) { return e ? { h: Number(t & Et), l: Number(t >> ae & Et) } : { h: Number(t >> ae & Et) | 0, l: Number(t & Et) | 0 } } function de(t, e = !1) { let n = t.length, o = new Uint32Array(n), r = new Uint32Array(n); for (let i = 0; i < n; i++) { let { h: c, l: a } = $e(t[i], e);[o[i], r[i]] = [c, a] } return [o, r] } var he = (t, e, n) => t << n | e >>> 32 - n, pe = (t, e, n) => e << n | t >>> 32 - n, ge = (t, e, n) => e << n - 32 | t >>> 64 - n, ye = (t, e, n) => t << n - 32 | e >>> 64 - n; var Ge = BigInt(0), dt = BigInt(1), Ke = BigInt(2), De = BigInt(7), Ye = BigInt(256), Xe = BigInt(113), be = [], me = [], Ae = []; for (let t = 0, e = dt, n = 1, o = 0; t < 24; t++) { [n, o] = [o, (2 * n + 3 * o) % 5], be.push(2 * (5 * o + n)), me.push((t + 1) * (t + 2) / 2 % 64); let r = Ge; for (let i = 0; i < 7; i++)e = (e << dt ^ (e >> De) * Xe) % Ye, e & Ke && (r ^= dt << (dt << BigInt(i)) - dt); Ae.push(r) } var Ee = de(Ae, !0), Ve = Ee[0], Ze = Ee[1], xe = (t, e, n) => n > 32 ? ge(t, e, n) : he(t, e, n), we = (t, e, n) => n > 32 ? ye(t, e, n) : pe(t, e, n); function ze(t, e = 24) { let n = new Uint32Array(10); for (let o = 24 - e; o < 24; o++) { for (let c = 0; c < 10; c++)n[c] = t[c] ^ t[c + 10] ^ t[c + 20] ^ t[c + 30] ^ t[c + 40]; for (let c = 0; c < 10; c += 2) { let a = (c + 8) % 10, g = (c + 2) % 10, E = n[g], y = n[g + 1], C = xe(E, y, 1) ^ n[a], z = we(E, y, 1) ^ n[a + 1]; for (let R = 0; R < 50; R += 10)t[c + R] ^= C, t[c + R + 1] ^= z } let r = t[2], i = t[3]; for (let c = 0; c < 24; c++) { let a = me[c], g = xe(r, i, a), E = we(r, i, a), y = be[c]; r = t[y], i = t[y + 1], t[y] = g, t[y + 1] = E } for (let c = 0; c < 50; c += 10) { for (let a = 0; a < 10; a++)n[a] = t[c + a]; for (let a = 0; a < 10; a++)t[c + a] ^= ~n[(a + 2) % 10] & n[(a + 4) % 10] } t[0] ^= Ve[o], t[1] ^= Ze[o] } St(n) } var Pt = class t { state; pos = 0; posOut = 0; finished = !1; state32; destroyed = !1; blockLen; suffix; outputLen; enableXOF = !1; rounds; constructor(e, n, o, r = !1, i = 24) { if (this.blockLen = e, this.suffix = n, this.outputLen = o, this.enableXOF = r, this.rounds = i, Ut(o, "outputLen"), !(0 < e && e < 200)) throw new Error("only keccak-f1600 function is supported"); this.state = new Uint8Array(200), this.state32 = ie(this.state) } clone() { return this._cloneInto() } keccak() { Ct(this.state32), ze(this.state32, this.rounds), Ct(this.state32), this.posOut = 0, this.pos = 0 } update(e) { Ht(this), A(e); let { blockLen: n, state: o } = this, r = e.length; for (let i = 0; i < r;) { let c = Math.min(n - this.pos, r - i); for (let a = 0; a < c; a++)o[this.pos++] ^= e[i++]; this.pos === n && this.keccak() } return this } finish() { if (this.finished) return; this.finished = !0; let { state: e, suffix: n, pos: o, blockLen: r } = this; e[o] ^= n, (n & 128) !== 0 && o === r - 1 && this.keccak(), e[r - 1] ^= 128, this.keccak() } writeInto(e) { Ht(this, !1), A(e), this.finish(); let n = this.state, { blockLen: o } = this; for (let r = 0, i = e.length; r < i;) { this.posOut >= o && this.keccak(); let c = Math.min(o - this.posOut, i - r); e.set(n.subarray(this.posOut, this.posOut + c), r), this.posOut += c, r += c } return e } xofInto(e) { if (!this.enableXOF) throw new Error("XOF is not possible for this instance"); return this.writeInto(e) } xof(e) { return Ut(e), this.xofInto(new Uint8Array(e)) } digestInto(e) { if (ce(e, this), this.finished) throw new Error("digest() was already called"); return this.writeInto(e), this.destroy(), e } digest() { return this.digestInto(new Uint8Array(this.outputLen)) } destroy() { this.destroyed = !0, St(this.state) } _cloneInto(e) { let { blockLen: n, suffix: o, outputLen: r, rounds: i, enableXOF: c } = this; return e ||= new t(n, o, r, c, i), e.state32.set(this.state32), e.pos = this.pos, e.posOut = this.posOut, e.finished = this.finished, e.rounds = i, e.suffix = o, e.outputLen = r, e.enableXOF = c, e.destroyed = this.destroyed, e } }; var Be = (t, e, n, o = {}) => fe((r = {}) => new Pt(e, t, r.dkLen === void 0 ? n : r.dkLen, !0), o), Te = Be(31, 168, 16, vt(11)), P = Be(31, 136, 32, vt(12)); function jt(t) { if (!Number.isSafeInteger(t) || t < 0 || t > 4294967295) throw new Error("wrong u32 integer:" + t); return t } function ke(t) { return jt(t), (t & t - 1) === 0 && t !== 0 } function Nt(t, e) { jt(t); let n = 0; for (let o = 0; o < e; o++, t >>>= 1)n = n << 1 | t & 1; return n } function _e(t) { return jt(t), 31 - Math.clz32(t) } function Le(t) { let e = t.length; if (e < 2 || !ke(e)) throw new Error("n must be a power of 2 and greater than 1. Got " + e); let n = _e(e); for (let o = 0; o < e; o++) { let r = Nt(o, n); if (o < r) { let i = t[o]; t[o] = t[r], t[r] = i } } return t } var Ft = (t, e) => { let { N: n, roots: o, dit: r, invertButterflies: i = !1, skipStages: c = 0, brp: a = !0 } = e, g = _e(n); if (!ke(n)) throw new Error("FFT: Polynomial size should be power of two"); let E = r !== i; return y => { if (y.length !== n) throw new Error("FFT: wrong Polynomial length"); r && a && Le(y); for (let C = 0, z = 1; C < g - c; C++) { let R = r ? C + 1 + c : g - C, q = 1 << R, J = q >> 1, yt = n >> R; for (let rt = 0; rt < n; rt += q)for (let p = 0, w = z++; p < J; p++) { let I = i ? r ? n - w : w : p * yt, K = rt + p, j = rt + p + J, D = o[I], H = y[j], B = y[K]; if (E) { let S = t.mul(H, D); y[K] = t.add(B, S), y[j] = t.sub(B, S) } else i ? (y[K] = t.add(H, B), y[j] = t.mul(t.sub(H, B), D)) : (y[K] = t.add(B, H), y[j] = t.mul(t.sub(B, H), D)) } } return !r && a && Le(y), y } }; var $t = ue; function Gt(t, e) { if (t.length !== e.length) return !1; let n = 0; for (let o = 0; o < t.length; o++)n |= t[o] ^ e[o]; return n === 0 } function Kt(t) { if (typeof t != "object" || t === null || Mt(t)) throw new Error("expected opts to be an object") } function Bt(t) { Kt(t), t.context !== void 0 && A(t.context, void 0, "opts.context") } function Tt(t) { Bt(t), t.extraEntropy !== !1 && t.extraEntropy !== void 0 && A(t.extraEntropy, void 0, "opts.extraEntropy") } function ht(t, ...e) { let n = r => typeof r == "number" ? r : r.bytesLen, o = e.reduce((r, i) => r + n(i), 0); return { bytesLen: o, encode: r => { let i = new Uint8Array(o); for (let c = 0, a = 0; c < e.length; c++) { let g = e[c], E = n(g), y = typeof g == "number" ? r[c] : g.encode(r[c]); A(y, E, t), i.set(y, a), typeof g != "number" && y.fill(0), a += E } return i }, decode: r => { A(r, o, t); let i = []; for (let c of e) { let a = n(c), g = r.subarray(0, a); i.push(typeof c == "number" ? g : c.decode(g)), r = r.subarray(a) } return i } } } function st(t, e) { let n = e * t.bytesLen; return { bytesLen: n, encode: o => { if (o.length !== e) throw new Error(`vecCoder.encode: wrong length = ${o.length}.Expected: ${e} `); let r = new Uint8Array(n); for (let i = 0, c = 0; i < o.length; i++) { let a = t.encode(o[i]); r.set(a, c), a.fill(0), c += a.length } return r }, decode: o => { A(o, n); let r = []; for (let i = 0; i < o.length; i += t.bytesLen)r.push(t.decode(o.subarray(i, i + t.bytesLen))); return r } } } function Z(...t) { for (let e of t) if (Array.isArray(e)) for (let n of e) n.fill(0); else e.fill(0) } function Dt(t) { return (1 << t) - 1 } var Oe = Uint8Array.of(); function Yt(t, e = Oe) { if (A(t), A(e), e.length > 255) throw new Error("context should be less than 255 bytes"); return Rt(new Uint8Array([0, e.length]), e, t) } var qe = Uint8Array.from([6, 9, 96, 134, 72, 1, 101, 3, 4, 2]); function Ie(t, e = 0) { if (!t.oid || !Gt(t.oid.subarray(0, 10), qe)) throw new Error("hash.oid is invalid: expected NIST hash"); let n = t.outputLen * 8 / 2; if (e > n) throw new Error("Pre-hash security strength too low: " + n + ", required: " + e) } function Xt(t, e, n = Oe) { if (A(e), A(n), n.length > 255) throw new Error("context should be less than 255 bytes"); let o = t(e); return Rt(new Uint8Array([1, n.length]), n, t.oid, o) } var Me = t => { let { newPoly: e, N: n, Q: o, F: r, ROOT_OF_UNITY: i, brvBits: c, isKyber: a } = t, g = (p, w = o) => { let I = p % w | 0; return (I >= 0 ? I | 0 : w + I | 0) | 0 }, E = (p, w = o) => { let I = g(p, w) | 0; return (I > w >> 1 ? I - w | 0 : I) | 0 }; function y() { let p = e(n); for (let w = 0; w < n; w++) { let I = Nt(w, c), K = BigInt(i) ** BigInt(I) % BigInt(o); p[w] = Number(K) | 0 } return p } let C = y(), z = { add: (p, w) => g((p | 0) + (w | 0)) | 0, sub: (p, w) => g((p | 0) - (w | 0)) | 0, mul: (p, w) => g((p | 0) * (w | 0)) | 0, inv: p => { throw new Error("not implemented") } }, R = { N: n, roots: C, invertButterflies: !0, skipStages: a ? 1 : 0, brp: !1 }, q = Ft(z, { dit: !1, ...R }), J = Ft(z, { dit: !0, ...R }); return { mod: g, smod: E, nttZetas: C, NTT: { encode: p => q(p), decode: p => { J(p); for (let w = 0; w < p.length; w++)p[w] = g(r * p[w]); return p } }, bitsCoder: (p, w) => { let I = Dt(p), K = p * (n / 8); return { bytesLen: K, encode: j => { let D = new Uint8Array(K); for (let H = 0, B = 0, S = 0, ft = 0; H < j.length; H++)for (B |= (w.encode(j[H]) & I) << S, S += p; S >= 8; S -= 8, B >>= 8)D[ft++] = B & Dt(S); return D }, decode: j => { let D = e(n); for (let H = 0, B = 0, S = 0, ft = 0; H < j.length; H++)for (B |= j[H] << S, S += 8; S >= p; S -= p, B >>= p)D[ft++] = w.decode(B & I); return D } } } } }, Ue = t => (e, n) => { n || (n = t.blockLen); let o = new Uint8Array(e.length + 2); o.set(e); let r = e.length, i = new Uint8Array(n), c = t.create({}), a = 0, g = 0; return { stats: () => ({ calls: a, xofs: g }), get: (E, y) => (o[r + 0] = E, o[r + 1] = y, c.destroy(), c = t.create({}).update(o), a++, () => (g++, c.xofInto(i))), clean: () => { c.destroy(), Z(i, o) } } }, Lt = Ue(Te), kt = Ue(P); function He(t) { Kt(t), t.externalMu !== void 0 && le(t.externalMu, "opts.externalMu") } var m = 256, ot = 8380417, We = 1753, Qe = 8347681, it = 13, Vt = Math.floor((ot - 1) / 88) | 0, Zt = Math.floor((ot - 1) / 32) | 0, zt = { 2: { K: 4, L: 4, D: it, GAMMA1: 2 ** 17, GAMMA2: Vt, TAU: 39, ETA: 2, OMEGA: 80 }, 3: { K: 6, L: 5, D: it, GAMMA1: 2 ** 19, GAMMA2: Zt, TAU: 49, ETA: 4, OMEGA: 55 }, 5: { K: 8, L: 7, D: it, GAMMA1: 2 ** 19, GAMMA2: Zt, TAU: 60, ETA: 2, OMEGA: 75 } }, $ = t => new Int32Array(t), { mod: ct, smod: Ot, NTT: O, bitsCoder: Je } = Me({ N: m, Q: ot, F: Qe, ROOT_OF_UNITY: We, newPoly: $, isKyber: !1, brvBits: 8 }), Se = t => t, pt = (t, e = Se, n = Se) => Je(t, { encode: o => e(n(o)), decode: o => n(e(o)) }), et = (t, e) => { for (let n = 0; n < t.length; n++)t[n] = ct(t[n] + e[n]); return t }, Ce = (t, e) => { for (let n = 0; n < t.length; n++)t[n] = ct(t[n] - e[n]); return t }, tn = t => { for (let e = 0; e < m; e++)t[e] <<= it; return t }, gt = (t, e) => { for (let n = 0; n < m; n++)if (Math.abs(Ot(t[n])) >= e) return !0; return !1 }, nt = (t, e) => { let n = $(m); for (let o = 0; o < t.length; o++)n[o] = ct(t[o] * e[o]); return n }; function _t(t) { let e = $(m); for (let n = 0; n < m;) { let o = t(); if (o.length % 3) throw new Error("RejNTTPoly: unaligned block"); for (let r = 0; n < m && r <= o.length - 3; r += 3) { let i = (o[r + 0] | o[r + 1] << 8 | o[r + 2] << 16) & 8388607; i < ot && (e[n++] = i) } } return e } function qt(t) { let { K: e, L: n, GAMMA1: o, GAMMA2: r, TAU: i, ETA: c, OMEGA: a } = t, { CRH_BYTES: g, TR_BYTES: E, C_TILDE_BYTES: y, XOF128: C, XOF256: z, securityLevel: R } = t; if (![2, 4].includes(c)) throw new Error("Wrong ETA"); if (![1 << 17, 1 << 19].includes(o)) throw new Error("Wrong GAMMA1"); if (![Vt, Zt].includes(r)) throw new Error("Wrong GAMMA2"); let q = i * c, J = s => { let l = ct(s), f = Ot(l, 2 * r) | 0; return l - f === ot - 1 ? { r1: 0, r0: f - 1 | 0 } : { r1: Math.floor((l - f) / (2 * r)) | 0, r0: f } }, yt = s => J(s).r1, rt = s => J(s).r0, p = (s, l) => s <= r || s > ot - r || s === ot - r && l === 0 ? 0 : 1, w = (s, l) => { let f = Math.floor((ot - 1) / (2 * r)), { r1: u, r0: h } = J(l); return s === 1 ? h > 0 ? ct(u + 1, f) | 0 : ct(u - 1, f) | 0 : u | 0 }, I = s => { let l = ct(s), f = Ot(l, 2 ** it) | 0; return { r1: Math.floor((l - f) / 2 ** it) | 0, r0: f } }, K = { bytesLen: a + e, encode: s => { if (s === !1) throw new Error("hint.encode: hint is false"); let l = new Uint8Array(a + e); for (let f = 0, u = 0; f < e; f++) { for (let h = 0; h < m; h++)s[f][h] !== 0 && (l[u++] = h); l[a + f] = u } return l }, decode: s => { let l = [], f = 0; for (let u = 0; u < e; u++) { let h = $(m); if (s[a + u] < f || s[a + u] > a) return !1; for (let x = f; x < s[a + u]; x++) { if (x > f && s[x] <= s[x - 1]) return !1; h[s[x]] = 1 } f = s[a + u], l.push(h) } for (let u = f; u < a; u++)if (s[u] !== 0) return !1; return l } }, j = pt(c === 2 ? 3 : 4, s => c - s, s => { if (!(-c <= s && s <= c)) throw new Error(`malformed key s1 / s3 ${s} outside of ETA range[${-c}, ${c}]`); return s }), D = pt(13, s => (1 << it - 1) - s), H = pt(10), B = pt(o === 1 << 17 ? 18 : 20, s => Ot(o - s)), S = pt(r === Vt ? 6 : 4), ft = st(S, e), xt = ht("publicKey", 32, st(H, e)), wt = ht("secretKey", 32, 32, E, st(j, n), st(j, e), st(D, e)), bt = ht("signature", y, st(B, n), K), Wt = c === 2 ? s => s < 15 ? 2 - s % 5 : !1 : s => s < 9 ? 4 - s : !1; function Qt(s) { let l = $(m); for (let f = 0; f < m;) { let u = s(); for (let h = 0; f < m && h < u.length; h += 1) { let x = Wt(u[h] & 15), M = Wt(u[h] >> 4 & 15); x !== !1 && (l[f++] = x), f < m && M !== !1 && (l[f++] = M) } } return l } let Jt = s => { let l = $(m), f = P.create({}).update(s), u = new Uint8Array(P.blockLen); f.xofInto(u); let h = u.slice(0, 8); for (let x = m - i, M = 8, v = 0, T = 0; x < m; x++) { let L = x + 1; for (; L > x;)L = u[M++], !(M < P.blockLen) && (f.xofInto(u), M = 0); l[x] = l[L], l[L] = 1 - ((h[v] >> T++ & 1) << 1), T >= 8 && (v++, T = 0) } return l }, te = s => { let l = $(m), f = $(m); for (let u = 0; u < s.length; u++) { let { r0: h, r1: x } = I(s[u]); l[u] = h, f[u] = x } return { r0: l, r1: f } }, Re = (s, l) => { for (let f = 0; f < m; f++)s[f] = w(l[f], s[f]); return s }, ve = (s, l) => { let f = $(m), u = 0; for (let h = 0; h < m; h++) { let x = p(s[h], l[h]); f[h] = x, u += x } return { v: f, cnt: u } }, ee = 32, ne = ht("seed", 32, 64, 32), Y = { info: { type: "internal-ml-dsa" }, lengths: { secretKey: wt.bytesLen, publicKey: xt.bytesLen, seed: 32, signature: bt.bytesLen, signRand: ee }, keygen: s => { let l = new Uint8Array(34), f = s === void 0; f && (s = $t(32)), A(s, 32, "seed"), l.set(s), f && Z(s), l[32] = e, l[33] = n; let [u, h, x] = ne.decode(P(l, { dkLen: ne.bytesLen })), M = z(h), v = []; for (let d = 0; d < n; d++)v.push(Qt(M.get(d & 255, d >> 8 & 255))); let T = []; for (let d = n; d < n + e; d++)T.push(Qt(M.get(d & 255, d >> 8 & 255))); let L = v.map(d => O.encode(d.slice())), k = [], U = [], W = C(u), _ = $(m); for (let d = 0; d < e; d++) { Z(_); for (let F = 0; F < n; F++) { let V = _t(W.get(F, d)); et(_, nt(V, L[F])) } O.decode(_); let { r0: G, r1: N } = te(et(_, T[d])); k.push(G), U.push(N) } let X = xt.encode([u, U]), Q = P(X, { dkLen: E }), ut = wt.encode([u, x, Q, v, T, k]); return W.clean(), M.clean(), Z(u, h, x, v, T, L, _, k, U, Q, l), { publicKey: X, secretKey: ut } }, getPublicKey: s => { let [l, f, u, h, x, M] = wt.decode(s), v = C(l), T = h.map(U => O.encode(U.slice())), L = [], k = $(m); for (let U = 0; U < e; U++) { k.fill(0); for (let _ = 0; _ < n; _++) { let X = _t(v.get(_, U)); et(k, nt(X, T[_])) } O.decode(k), et(k, x[U]); let { r1: W } = te(k); L.push(W) } return v.clean(), Z(k, T, M, h, x), xt.encode([l, L]) }, sign: (s, l, f = {}) => { Tt(f), He(f); let { extraEntropy: u, externalMu: h = !1 } = f, [x, M, v, T, L, k] = wt.decode(l), U = [], W = C(x); for (let d = 0; d < e; d++) { let G = []; for (let N = 0; N < n; N++)G.push(_t(W.get(N, d))); U.push(G) } W.clean(); for (let d = 0; d < n; d++)O.encode(T[d]); for (let d = 0; d < e; d++)O.encode(L[d]), O.encode(k[d]); let _ = h ? s : P.create({ dkLen: g }).update(v).update(s).digest(), X = u === !1 ? new Uint8Array(32) : u === void 0 ? $t(ee) : u; A(X, 32, "extraEntropy"); let Q = P.create({ dkLen: g }).update(M).update(X).update(_).digest(); A(Q, g); let ut = z(Q, B.bytesLen); t: for (let d = 0; ;) { let G = []; for (let b = 0; b < n; b++, d++)G.push(B.decode(ut.get(d & 255, d >> 8)())); let N = G.map(b => O.encode(b.slice())), F = []; for (let b = 0; b < e; b++) { let at = $(m); for (let tt = 0; tt < n; tt++)et(at, nt(U[b][tt], N[tt])); O.decode(at), F.push(at) } let V = F.map(b => b.map(yt)), lt = P.create({ dkLen: y }).update(_).update(ft.encode(V)).digest(), mt = O.encode(Jt(lt)), At = T.map(b => nt(b, mt)); for (let b = 0; b < n; b++)if (et(O.decode(At[b]), G[b]), gt(At[b], o - q)) continue t; let oe = 0, It = []; for (let b = 0; b < e; b++) { let at = O.decode(nt(L[b], mt)), tt = Ce(F[b], at).map(rt); if (gt(tt, r - q)) continue t; let re = O.decode(nt(k[b], mt)); if (gt(re, r)) continue t; et(tt, re); let se = ve(tt, V[b]); It.push(se.v), oe += se.cnt } if (oe > a) continue; ut.clean(); let Pe = bt.encode([lt, At, It]); return Z(lt, At, It, mt, V, F, N, G, Q, _, T, L, k, ...U), Pe } throw new Error("Unreachable code path reached, report this error") }, verify: (s, l, f, u = {}) => { He(u); let { externalMu: h = !1 } = u, [x, M] = xt.decode(f), v = P(f, { dkLen: E }); if (s.length !== bt.bytesLen) return !1; let [T, L, k] = bt.decode(s); if (k === !1) return !1; for (let d = 0; d < n; d++)if (gt(L[d], o - q)) return !1; let U = h ? l : P.create({ dkLen: g }).update(v).update(l).digest(), W = O.encode(Jt(T)), _ = L.map(d => d.slice()); for (let d = 0; d < n; d++)O.encode(_[d]); let X = [], Q = C(x); for (let d = 0; d < e; d++) { let G = nt(O.encode(tn(M[d])), W), N = $(m); for (let V = 0; V < n; V++) { let lt = _t(Q.get(V, d)); et(N, nt(lt, _[V])) } let F = O.decode(Ce(N, G)); X.push(Re(F, k[d])) } Q.clean(); let ut = P.create({ dkLen: y }).update(U).update(ft.encode(X)).digest(); for (let d of k) if (!(d.reduce((N, F) => N + F, 0) <= a)) return !1; for (let d of L) if (gt(d, o - q)) return !1; return Gt(T, ut) } }; return { info: { type: "ml-dsa" }, internal: Y, securityLevel: R, keygen: Y.keygen, lengths: Y.lengths, getPublicKey: Y.getPublicKey, sign: (s, l, f = {}) => { Tt(f); let u = Yt(s, f.context), h = Y.sign(u, l, f); return Z(u), h }, verify: (s, l, f, u = {}) => (Bt(u), Y.verify(s, Yt(l, u.context), f)), prehash: s => (Ie(s, R), { info: { type: "hashml-dsa" }, securityLevel: R, lengths: Y.lengths, keygen: Y.keygen, getPublicKey: Y.getPublicKey, sign: (l, f, u = {}) => { Tt(u); let h = Xt(s, l, u.context), x = Y.sign(h, f, u); return Z(h), x }, verify: (l, f, u, h = {}) => (Bt(h), Y.verify(l, Xt(s, f, h.context), u)) }) } } var mn = qt({ ...zt[2], CRH_BYTES: 64, TR_BYTES: 64, C_TILDE_BYTES: 32, XOF128: Lt, XOF256: kt, securityLevel: 128 }), An = qt({ ...zt[3], CRH_BYTES: 64, TR_BYTES: 64, C_TILDE_BYTES: 48, XOF128: Lt, XOF256: kt, securityLevel: 192 }), En = qt({ ...zt[5], CRH_BYTES: 64, TR_BYTES: 64, C_TILDE_BYTES: 64, XOF128: Lt, XOF256: kt, securityLevel: 256 }); export { zt as PARAMS, mn as ml_dsa44, An as ml_dsa65, En as ml_dsa87 };
13
+ /*! Bundled license information:
14
+
15
+ @noble/hashes/utils.js:
16
+ (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
17
+
18
+ @noble/curves/utils.js:
19
+ (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
20
+
21
+ @noble/post-quantum/utils.js:
22
+ @noble/post-quantum/_crystals.js:
23
+ @noble/post-quantum/ml-dsa.js:
24
+ (*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) *)
25
+ */
@@ -0,0 +1,32 @@
1
+
2
+ // Patch for browser and server environment
3
+ if (typeof window !== 'undefined') {
4
+ if (typeof window.require === 'undefined') {
5
+ window.require = function (name) {
6
+ if (name === 'semver') {
7
+ return {
8
+ valid: () => true,
9
+ clean: (v) => v,
10
+ satisfies: () => true,
11
+ gt: () => false,
12
+ lt: () => false,
13
+ coerce: (v) => v
14
+ };
15
+ }
16
+ return {};
17
+ };
18
+ }
19
+ }
20
+ const require = (typeof window !== 'undefined') ? window.require : function (n) { return {}; };
21
+ /* esm.sh - @noble/post-quantum@0.5.2/ml-kem */
22
+ var $ = BigInt(4294967295), ut = BigInt(32); function Ct(t, e = !1) { return e ? { h: Number(t & $), l: Number(t >> ut & $) } : { h: Number(t >> ut & $) | 0, l: Number(t & $) | 0 } } function lt(t, e = !1) { let n = t.length, r = new Uint32Array(n), o = new Uint32Array(n); for (let c = 0; c < n; c++) { let { h: s, l: i } = Ct(t[c], e);[r[c], o[c]] = [s, i] } return [r, o] } var at = (t, e, n) => t << n | e >>> 32 - n, dt = (t, e, n) => e << n | t >>> 32 - n, pt = (t, e, n) => e << n - 32 | t >>> 64 - n, ht = (t, e, n) => t << n - 32 | e >>> 64 - n; function yt(t) { return t instanceof Uint8Array || ArrayBuffer.isView(t) && t.constructor.name === "Uint8Array" } function Y(t, e = "") { if (!Number.isSafeInteger(t) || t < 0) { let n = e && `"${e}" `; throw new Error(`${n}expected integer >= 0, got ${t}`) } } function O(t, e, n = "") { let r = yt(t), o = t?.length, c = e !== void 0; if (!r || c && o !== e) { let s = n && `"${n}" `, i = c ? ` of length ${e}` : "", a = r ? `length=${o}` : `type=${typeof t}`; throw new Error(s + "expected Uint8Array" + i + ", got " + a) } return t } function Q(t, e = !0) { if (t.destroyed) throw new Error("Hash instance has been destroyed"); if (e && t.finished) throw new Error("Hash#digest() has already been called") } function gt(t, e) { O(t, void 0, "digestInto() output"); let n = e.outputLen; if (t.length < n) throw new Error('"digestInto() output" expected to be of length >=' + n) } function X(t) { return new Uint32Array(t.buffer, t.byteOffset, Math.floor(t.byteLength / 4)) } function J(...t) { for (let e = 0; e < t.length; e++)t[e].fill(0) } var Pt = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68; function jt(t) { return t << 24 & 4278190080 | t << 8 & 16711680 | t >>> 8 & 65280 | t >>> 24 & 255 } function vt(t) { for (let e = 0; e < t.length; e++)t[e] = jt(t[e]); return t } var W = Pt ? t => t : vt; function tt(t, e = {}) { let n = (o, c) => t(c).update(o).digest(), r = t(void 0); return n.outputLen = r.outputLen, n.blockLen = r.blockLen, n.create = o => t(o), Object.assign(n, e), Object.freeze(n) } function xt(t = 32) { let e = typeof globalThis == "object" ? globalThis.crypto : null; if (typeof e?.getRandomValues != "function") throw new Error("crypto.getRandomValues must be defined"); return e.getRandomValues(new Uint8Array(t)) } var R = t => ({ oid: Uint8Array.from([6, 9, 96, 134, 72, 1, 101, 3, 4, 2, t]) }); var Mt = BigInt(0), F = BigInt(1), $t = BigInt(2), Xt = BigInt(7), Dt = BigInt(256), Vt = BigInt(113), mt = [], At = [], kt = []; for (let t = 0, e = F, n = 1, r = 0; t < 24; t++) { [n, r] = [r, (2 * n + 3 * r) % 5], mt.push(2 * (5 * r + n)), At.push((t + 1) * (t + 2) / 2 % 64); let o = Mt; for (let c = 0; c < 7; c++)e = (e << F ^ (e >> Xt) * Vt) % Dt, e & $t && (o ^= F << (F << BigInt(c)) - F); kt.push(o) } var Et = lt(kt, !0), Zt = Et[0], zt = Et[1], bt = (t, e, n) => n > 32 ? pt(t, e, n) : at(t, e, n), wt = (t, e, n) => n > 32 ? ht(t, e, n) : dt(t, e, n); function Gt(t, e = 24) { let n = new Uint32Array(10); for (let r = 24 - e; r < 24; r++) { for (let s = 0; s < 10; s++)n[s] = t[s] ^ t[s + 10] ^ t[s + 20] ^ t[s + 30] ^ t[s + 40]; for (let s = 0; s < 10; s += 2) { let i = (s + 8) % 10, a = (s + 2) % 10, w = n[a], f = n[a + 1], p = bt(w, f, 1) ^ n[i], b = wt(w, f, 1) ^ n[i + 1]; for (let d = 0; d < 50; d += 10)t[s + d] ^= p, t[s + d + 1] ^= b } let o = t[2], c = t[3]; for (let s = 0; s < 24; s++) { let i = At[s], a = bt(o, c, i), w = wt(o, c, i), f = mt[s]; o = t[f], c = t[f + 1], t[f] = a, t[f + 1] = w } for (let s = 0; s < 50; s += 10) { for (let i = 0; i < 10; i++)n[i] = t[s + i]; for (let i = 0; i < 10; i++)t[s + i] ^= ~n[(i + 2) % 10] & n[(i + 4) % 10] } t[0] ^= Zt[r], t[1] ^= zt[r] } J(n) } var D = class t { state; pos = 0; posOut = 0; finished = !1; state32; destroyed = !1; blockLen; suffix; outputLen; enableXOF = !1; rounds; constructor(e, n, r, o = !1, c = 24) { if (this.blockLen = e, this.suffix = n, this.outputLen = r, this.enableXOF = o, this.rounds = c, Y(r, "outputLen"), !(0 < e && e < 200)) throw new Error("only keccak-f1600 function is supported"); this.state = new Uint8Array(200), this.state32 = X(this.state) } clone() { return this._cloneInto() } keccak() { W(this.state32), Gt(this.state32, this.rounds), W(this.state32), this.posOut = 0, this.pos = 0 } update(e) { Q(this), O(e); let { blockLen: n, state: r } = this, o = e.length; for (let c = 0; c < o;) { let s = Math.min(n - this.pos, o - c); for (let i = 0; i < s; i++)r[this.pos++] ^= e[c++]; this.pos === n && this.keccak() } return this } finish() { if (this.finished) return; this.finished = !0; let { state: e, suffix: n, pos: r, blockLen: o } = this; e[r] ^= n, (n & 128) !== 0 && r === o - 1 && this.keccak(), e[o - 1] ^= 128, this.keccak() } writeInto(e) { Q(this, !1), O(e), this.finish(); let n = this.state, { blockLen: r } = this; for (let o = 0, c = e.length; o < c;) { this.posOut >= r && this.keccak(); let s = Math.min(r - this.posOut, c - o); e.set(n.subarray(this.posOut, this.posOut + s), o), this.posOut += s, o += s } return e } xofInto(e) { if (!this.enableXOF) throw new Error("XOF is not possible for this instance"); return this.writeInto(e) } xof(e) { return Y(e), this.xofInto(new Uint8Array(e)) } digestInto(e) { if (gt(e, this), this.finished) throw new Error("digest() was already called"); return this.writeInto(e), this.destroy(), e } digest() { return this.digestInto(new Uint8Array(this.outputLen)) } destroy() { this.destroyed = !0, J(this.state) } _cloneInto(e) { let { blockLen: n, suffix: r, outputLen: o, rounds: c, enableXOF: s } = this; return e ||= new t(n, r, o, s, c), e.state32.set(this.state32), e.pos = this.pos, e.posOut = this.posOut, e.finished = this.finished, e.rounds = c, e.suffix = r, e.outputLen = o, e.enableXOF = s, e.destroyed = this.destroyed, e } }, Bt = (t, e, n, r = {}) => tt(() => new D(e, t, n), r); var Ot = Bt(6, 136, 32, R(8)); var Lt = Bt(6, 72, 64, R(10)); var Tt = (t, e, n, r = {}) => tt((o = {}) => new D(e, t, o.dkLen === void 0 ? n : o.dkLen, !0), r), _t = Tt(31, 168, 16, R(11)), V = Tt(31, 136, 32, R(12)); function et(t) { if (!Number.isSafeInteger(t) || t < 0 || t > 4294967295) throw new Error("wrong u32 integer:" + t); return t } function It(t) { return et(t), (t & t - 1) === 0 && t !== 0 } function nt(t, e) { et(t); let n = 0; for (let r = 0; r < e; r++, t >>>= 1)n = n << 1 | t & 1; return n } function Ut(t) { return et(t), 31 - Math.clz32(t) } function Ht(t) { let e = t.length; if (e < 2 || !It(e)) throw new Error("n must be a power of 2 and greater than 1. Got " + e); let n = Ut(e); for (let r = 0; r < e; r++) { let o = nt(r, n); if (r < o) { let c = t[r]; t[r] = t[o], t[o] = c } } return t } var rt = (t, e) => { let { N: n, roots: r, dit: o, invertButterflies: c = !1, skipStages: s = 0, brp: i = !0 } = e, a = Ut(n); if (!It(n)) throw new Error("FFT: Polynomial size should be power of two"); let w = o !== c; return f => { if (f.length !== n) throw new Error("FFT: wrong Polynomial length"); o && i && Ht(f); for (let p = 0, b = 1; p < a - s; p++) { let d = o ? p + 1 + s : a - p, B = 1 << d, I = B >> 1, H = n >> d; for (let L = 0; L < n; L += B)for (let u = 0, l = b++; u < I; u++) { let y = c ? o ? n - l : l : u * H, g = L + u, x = L + u + I, A = r[y], m = f[x], h = f[g]; if (w) { let k = t.mul(m, A); f[g] = t.add(h, k), f[x] = t.sub(h, k) } else c ? (f[g] = t.add(m, h), f[x] = t.mul(t.sub(m, h), A)) : (f[g] = t.add(h, m), f[x] = t.mul(t.sub(h, m), A)) } } return !o && i && Ht(f), f } }; var ot = xt; function Z(t, e) { if (t.length !== e.length) return !1; let n = 0; for (let r = 0; r < t.length; r++)n |= t[r] ^ e[r]; return n === 0 } function St(t) { return Uint8Array.from(t) } function N(t, ...e) { let n = o => typeof o == "number" ? o : o.bytesLen, r = e.reduce((o, c) => o + n(c), 0); return { bytesLen: r, encode: o => { let c = new Uint8Array(r); for (let s = 0, i = 0; s < e.length; s++) { let a = e[s], w = n(a), f = typeof a == "number" ? o[s] : a.encode(o[s]); O(f, w, t), c.set(f, i), typeof a != "number" && f.fill(0), i += w } return c }, decode: o => { O(o, r, t); let c = []; for (let s of e) { let i = n(s), a = o.subarray(0, i); c.push(typeof s == "number" ? a : s.decode(a)), o = o.subarray(i) } return c } } } function z(t, e) { let n = e * t.bytesLen; return { bytesLen: n, encode: r => { if (r.length !== e) throw new Error(`vecCoder.encode: wrong length=${r.length}. Expected: ${e}`); let o = new Uint8Array(n); for (let c = 0, s = 0; c < r.length; c++) { let i = t.encode(r[c]); o.set(i, s), i.fill(0), s += i.length } return o }, decode: r => { O(r, n); let o = []; for (let c = 0; c < r.length; c += t.bytesLen)o.push(t.decode(r.subarray(c, c + t.bytesLen))); return o } } } function _(...t) { for (let e of t) if (Array.isArray(e)) for (let n of e) n.fill(0); else e.fill(0) } function st(t) { return (1 << t) - 1 } var pe = Uint8Array.of(); var Kt = t => { let { newPoly: e, N: n, Q: r, F: o, ROOT_OF_UNITY: c, brvBits: s, isKyber: i } = t, a = (u, l = r) => { let y = u % l | 0; return (y >= 0 ? y | 0 : l + y | 0) | 0 }, w = (u, l = r) => { let y = a(u, l) | 0; return (y > l >> 1 ? y - l | 0 : y) | 0 }; function f() { let u = e(n); for (let l = 0; l < n; l++) { let y = nt(l, s), g = BigInt(c) ** BigInt(y) % BigInt(r); u[l] = Number(g) | 0 } return u } let p = f(), b = { add: (u, l) => a((u | 0) + (l | 0)) | 0, sub: (u, l) => a((u | 0) - (l | 0)) | 0, mul: (u, l) => a((u | 0) * (l | 0)) | 0, inv: u => { throw new Error("not implemented") } }, d = { N: n, roots: p, invertButterflies: !0, skipStages: i ? 1 : 0, brp: !1 }, B = rt(b, { dit: !1, ...d }), I = rt(b, { dit: !0, ...d }); return { mod: a, smod: w, nttZetas: p, NTT: { encode: u => B(u), decode: u => { I(u); for (let l = 0; l < u.length; l++)u[l] = a(o * u[l]); return u } }, bitsCoder: (u, l) => { let y = st(u), g = u * (n / 8); return { bytesLen: g, encode: x => { let A = new Uint8Array(g); for (let m = 0, h = 0, k = 0, E = 0; m < x.length; m++)for (h |= (l.encode(x[m]) & y) << k, k += u; k >= 8; k -= 8, h >>= 8)A[E++] = h & st(k); return A }, decode: x => { let A = e(n); for (let m = 0, h = 0, k = 0, E = 0; m < x.length; m++)for (h |= x[m] << k, k += 8; k >= u; k -= u, h >>= u)A[E++] = l.decode(h & y); return A } } } } }, qt = t => (e, n) => { n || (n = t.blockLen); let r = new Uint8Array(e.length + 2); r.set(e); let o = e.length, c = new Uint8Array(n), s = t.create({}), i = 0, a = 0; return { stats: () => ({ calls: i, xofs: a }), get: (w, f) => (r[o + 0] = w, r[o + 1] = f, s.destroy(), s = t.create({}).update(r), i++, () => (a++, s.xofInto(c))), clean: () => { s.destroy(), _(c, r) } } }, Rt = qt(_t); var T = 256, U = 3329, Yt = 3303, Qt = 17, { mod: j, nttZetas: Jt, NTT: S, bitsCoder: Wt } = Kt({ N: T, Q: U, F: Yt, ROOT_OF_UNITY: Qt, newPoly: t => new Uint16Array(t), brvBits: 7, isKyber: !0 }), ct = { 512: { N: T, Q: U, K: 2, ETA1: 3, ETA2: 2, du: 10, dv: 4, RBGstrength: 128 }, 768: { N: T, Q: U, K: 3, ETA1: 2, ETA2: 2, du: 10, dv: 4, RBGstrength: 192 }, 1024: { N: T, Q: U, K: 4, ETA1: 2, ETA2: 2, du: 11, dv: 5, RBGstrength: 256 } }, te = t => { if (t >= 12) return { encode: n => n, decode: n => n }; let e = 2 ** (t - 1); return { encode: n => ((n << t) + U / 2) / U, decode: n => n * U + e >>> t } }, C = t => Wt(t, te(t)); function K(t, e) { for (let n = 0; n < T; n++)t[n] = j(t[n] + e[n]) } function ee(t, e) { for (let n = 0; n < T; n++)t[n] = j(t[n] - e[n]) } function ne(t, e, n, r, o) { let c = j(e * r * o + t * n), s = j(t * r + e * n); return { c0: c, c1: s } } function G(t, e) { for (let n = 0; n < T / 2; n++) { let r = Jt[64 + (n >> 1)]; n & 1 && (r = -r); let { c0: o, c1: c } = ne(t[2 * n + 0], t[2 * n + 1], e[2 * n + 0], e[2 * n + 1], r); t[2 * n + 0] = o, t[2 * n + 1] = c } return t } function Ft(t) { let e = new Uint16Array(T); for (let n = 0; n < T;) { let r = t(); if (r.length % 3) throw new Error("SampleNTT: unaligned block"); for (let o = 0; n < T && o + 3 <= r.length; o += 3) { let c = (r[o + 0] >> 0 | r[o + 1] << 8) & 4095, s = (r[o + 1] >> 4 | r[o + 2] << 4) & 4095; c < U && (e[n++] = c), n < T && s < U && (e[n++] = s) } } return e } function P(t, e, n, r) { let o = t(r * T / 4, e, n), c = new Uint16Array(T), s = X(o), i = 0; for (let a = 0, w = 0, f = 0, p = 0; a < s.length; a++) { let b = s[a]; for (let d = 0; d < 32; d++)f += b & 1, b >>= 1, i += 1, i === r ? (p = f, f = 0) : i === 2 * r && (c[w++] = j(p - f), f = 0, i = 0) } if (i) throw new Error(`sampleCBD: leftover bits: ${i}`); return c } var re = t => { let { K: e, PRF: n, XOF: r, HASH512: o, ETA1: c, ETA2: s, du: i, dv: a } = t, w = C(1), f = C(a), p = C(i), b = N("publicKey", z(C(12), e), 32), d = z(C(12), e), B = N("ciphertext", z(p, e), f), I = N("seed", 32, 32); return { secretCoder: d, lengths: { secretKey: d.bytesLen, publicKey: b.bytesLen, cipherText: B.bytesLen }, keygen: H => { O(H, 32, "seed"); let L = new Uint8Array(33); L.set(H), L[32] = e; let u = o(L), [l, y] = I.decode(u), g = [], x = []; for (let h = 0; h < e; h++)g.push(S.encode(P(n, y, h, c))); let A = r(l); for (let h = 0; h < e; h++) { let k = S.encode(P(n, y, e + h, c)); for (let E = 0; E < e; E++) { let v = Ft(A.get(E, h)); K(k, G(v, g[E])) } x.push(k) } A.clean(); let m = { publicKey: b.encode([x, l]), secretKey: d.encode(g) }; return _(l, y, g, x, L, u), m }, encrypt: (H, L, u) => { let [l, y] = b.decode(H), g = []; for (let E = 0; E < e; E++)g.push(S.encode(P(n, u, E, c))); let x = r(y), A = new Uint16Array(T), m = []; for (let E = 0; E < e; E++) { let v = P(n, u, e + E, s), q = new Uint16Array(T); for (let M = 0; M < e; M++) { let Nt = Ft(x.get(E, M)); K(q, G(Nt, g[M])) } K(v, S.decode(q)), m.push(v), K(A, G(l[E], g[E])), _(q) } x.clean(); let h = P(n, u, 2 * e, s); K(h, S.decode(A)); let k = w.decode(L); return K(k, h), _(l, g, A, h), B.encode([m, k]) }, decrypt: (H, L) => { let [u, l] = B.decode(H), y = d.decode(L), g = new Uint16Array(T); for (let x = 0; x < e; x++)K(g, G(y[x], S.encode(u[x]))); return ee(l, S.decode(g)), _(g, y, u), w.encode(l) } } }; function it(t) { let e = re(t), { HASH256: n, HASH512: r, KDF: o } = t, { secretCoder: c, lengths: s } = e, i = N("secretKey", s.secretKey, s.publicKey, 32, 32), a = 32, w = 64; return { info: { type: "ml-kem" }, lengths: { ...s, seed: w, msg: a, msgRand: a, secretKey: i.bytesLen }, keygen: (f = ot(w)) => { O(f, w, "seed"); let { publicKey: p, secretKey: b } = e.keygen(f.subarray(0, 32)), d = n(p), B = i.encode([b, p, d, f.subarray(32)]); return _(b, d), { publicKey: p, secretKey: B } }, getPublicKey: f => { let [p, b, d, B] = i.decode(f); return Uint8Array.from(b) }, encapsulate: (f, p = ot(a)) => { O(f, s.publicKey, "publicKey"), O(p, a, "message"); let b = f.subarray(0, 384 * t.K), d = c.encode(c.decode(St(b))); if (!Z(d, b)) throw _(d), new Error("ML-KEM.encapsulate: wrong publicKey modulus"); _(d); let B = r.create().update(p).update(n(f)).digest(), I = e.encrypt(f, p, B.subarray(32, 64)); return _(B.subarray(32)), { cipherText: I, sharedSecret: B.subarray(0, 32) } }, decapsulate: (f, p) => { O(p, i.bytesLen, "secretKey"), O(f, s.cipherText, "cipherText"); let b = i.bytesLen - 96, d = b + 32, B = n(p.subarray(b / 2, d)); if (!Z(B, p.subarray(d, d + 32))) throw new Error("invalid secretKey: hash check failed"); let [I, H, L, u] = i.decode(p), l = e.decrypt(f, I), y = r.create().update(l).update(L).digest(), g = y.subarray(0, 32), x = e.encrypt(H, l, y.subarray(32, 64)), A = Z(f, x), m = o.create({ dkLen: 32 }).update(u).update(f).digest(); return _(l, x, A ? m : g), A ? g : m } } } function oe(t, e, n) { return V.create({ dkLen: t }).update(e).update(new Uint8Array([n])).digest() } var ft = { HASH256: Ot, HASH512: Lt, KDF: V, XOF: Rt, PRF: oe }, Be = it({ ...ft, ...ct[512] }), Oe = it({ ...ft, ...ct[768] }), Le = it({ ...ft, ...ct[1024] }); export { ct as PARAMS, Le as ml_kem1024, Be as ml_kem512, Oe as ml_kem768 };
23
+ /*! Bundled license information:
24
+
25
+ @noble/hashes/utils.js:
26
+ (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
27
+
28
+ @noble/post-quantum/utils.js:
29
+ @noble/post-quantum/_crystals.js:
30
+ @noble/post-quantum/ml-kem.js:
31
+ (*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) *)
32
+ */
package/src/core/store.js CHANGED
@@ -15,6 +15,7 @@
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
17
  const os = require('os');
18
+ const crypto = require('crypto');
18
19
 
19
20
  const MAGIC_VERSION = 1;
20
21
 
@@ -26,12 +27,14 @@ class MumpixStore {
26
27
  this.records = []; // { id, content, ts, h }
27
28
  this._nextId = 1;
28
29
  this._fd = null; // open file descriptor for appends
30
+ this._strictIntegrity = false;
29
31
  }
30
32
 
31
33
  // ── Public ──────────────────────────────────────
32
34
 
33
35
  open(opts = {}) {
34
36
  const consistency = opts.consistency || 'eventual';
37
+ this._strictIntegrity = consistency === 'strict' || consistency === 'verified';
35
38
 
36
39
  if (fs.existsSync(this.filePath)) {
37
40
  this._load();
@@ -43,10 +46,21 @@ class MumpixStore {
43
46
  consistency,
44
47
  created: Date.now(),
45
48
  path: path.basename(this.filePath),
49
+ fileId: crypto.randomUUID ? crypto.randomUUID() : crypto.randomBytes(16).toString('hex'),
50
+ hashAlg: 'hmac-sha256',
46
51
  };
47
52
  this._writeHeader();
48
53
  }
49
54
 
55
+ if (!this.header.fileId) {
56
+ this.header.fileId = crypto.randomUUID ? crypto.randomUUID() : crypto.randomBytes(16).toString('hex');
57
+ this._rewriteFull();
58
+ }
59
+ if (!this.header.hashAlg) {
60
+ this.header.hashAlg = 'hmac-sha256';
61
+ this._rewriteFull();
62
+ }
63
+
50
64
  // Update consistency if caller changed it
51
65
  if (this.header.consistency !== consistency) {
52
66
  this.header.consistency = consistency;
@@ -70,11 +84,12 @@ class MumpixStore {
70
84
  * Returns the new record.
71
85
  */
72
86
  write(content) {
87
+ const trimmed = content.trim();
73
88
  const record = {
74
89
  id: this._nextId++,
75
- content: content.trim(),
90
+ content: trimmed,
76
91
  ts: Date.now(),
77
- h: this._hash(content),
92
+ h: this._hash(trimmed),
78
93
  };
79
94
 
80
95
  // 1. Write to WAL
@@ -150,14 +165,31 @@ class MumpixStore {
150
165
  }
151
166
 
152
167
  this.records = [];
168
+ let lastId = 0;
169
+ const seenIds = new Set();
153
170
  for (let i = 1; i < lines.length; i++) {
154
171
  try {
155
172
  const r = JSON.parse(lines[i]);
156
- if (r && r.id && r.content) {
173
+ if (r && Number.isInteger(r.id) && typeof r.content === 'string' && r.content.length > 0) {
174
+ const monotonic = r.id > lastId;
175
+ const duplicate = seenIds.has(r.id);
176
+ const hashOk = this._verifyHash(r);
177
+ if (!monotonic || duplicate || !hashOk) {
178
+ const msg = !hashOk
179
+ ? `mumpix: integrity hash mismatch for id=${r.id}`
180
+ : `mumpix: invalid record order/id at id=${r.id}`;
181
+ if (this._strictIntegrity) throw new Error(msg);
182
+ continue;
183
+ }
157
184
  this.records.push(r);
185
+ seenIds.add(r.id);
186
+ lastId = r.id;
158
187
  if (r.id >= this._nextId) this._nextId = r.id + 1;
159
188
  }
160
- } catch (_) { /* skip corrupt lines */ }
189
+ } catch (err) {
190
+ if (this._strictIntegrity) throw err;
191
+ // skip corrupt lines in eventual mode
192
+ }
161
193
  }
162
194
  }
163
195
 
@@ -167,23 +199,37 @@ class MumpixStore {
167
199
  const raw = fs.readFileSync(this.walPath, 'utf8');
168
200
  const lines = raw.split('\n').filter(l => l.trim());
169
201
  let dirty = false;
202
+ let lastId = this.records.length ? this.records[this.records.length - 1].id : 0;
203
+ const seenIds = new Set(this.records.map(r => r.id));
170
204
 
171
205
  for (const line of lines) {
172
206
  try {
173
207
  const entry = JSON.parse(line);
174
208
  if (entry.op === 'write' && entry.entry) {
175
- const exists = this.records.find(r => r.id === entry.entry.id);
176
- if (!exists) {
209
+ const candidate = entry.entry;
210
+ const exists = seenIds.has(candidate.id);
211
+ const monotonic = Number.isInteger(candidate.id) && candidate.id > lastId;
212
+ const hashOk = this._verifyHash(candidate);
213
+ if (!exists && monotonic && hashOk) {
177
214
  this.records.push(entry.entry);
178
215
  if (entry.entry.id >= this._nextId) this._nextId = entry.entry.id + 1;
216
+ seenIds.add(entry.entry.id);
217
+ lastId = entry.entry.id;
179
218
  dirty = true;
219
+ } else if (this._strictIntegrity) {
220
+ throw new Error(`mumpix: rejected WAL write id=${candidate.id}`);
180
221
  }
181
222
  } else if (entry.op === 'clear') {
182
223
  this.records = [];
183
224
  this._nextId = 1;
225
+ seenIds.clear();
226
+ lastId = 0;
184
227
  dirty = true;
185
228
  }
186
- } catch (_) { /* skip corrupt WAL lines */ }
229
+ } catch (err) {
230
+ if (this._strictIntegrity) throw err;
231
+ // skip corrupt WAL lines in eventual mode
232
+ }
187
233
  }
188
234
 
189
235
  if (dirty) this._rewriteFull();
@@ -218,6 +264,34 @@ class MumpixStore {
218
264
  }
219
265
 
220
266
  _hash(s) {
267
+ const secret = process.env.MUMPIX_INTEGRITY_SECRET;
268
+ if (secret && secret.trim()) {
269
+ const digest = crypto
270
+ .createHmac('sha256', secret)
271
+ .update(String(s), 'utf8')
272
+ .digest('hex');
273
+ return `hmac256:${digest}`;
274
+ }
275
+ let h = 0x811c9dc5;
276
+ for (let i = 0; i < s.length; i++) {
277
+ h ^= s.charCodeAt(i);
278
+ h = Math.imul(h, 0x01000193);
279
+ }
280
+ return '0x' + (h >>> 0).toString(16).padStart(8, '0');
281
+ }
282
+
283
+ _verifyHash(record) {
284
+ if (!record || typeof record.content !== 'string' || typeof record.h !== 'string') return false;
285
+ if (record.h.startsWith('hmac256:')) {
286
+ const secret = process.env.MUMPIX_INTEGRITY_SECRET;
287
+ if (!secret || !secret.trim()) return false;
288
+ return this._hash(record.content) === record.h;
289
+ }
290
+ // Backward compatibility for legacy records while migrating.
291
+ return this._hashLegacy(record.content) === record.h;
292
+ }
293
+
294
+ _hashLegacy(s) {
221
295
  let h = 0x811c9dc5;
222
296
  for (let i = 0; i < s.length; i++) {
223
297
  h ^= s.charCodeAt(i);