blindseal 0.5.0 → 0.5.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/dashboard/assets/blindseal_verifier_bg-BYF7rQG1.wasm +0 -0
- package/dashboard/assets/geist-400-gapTbOY8.woff2 +0 -0
- package/dashboard/assets/geist-500-uokXdC-Q.woff2 +0 -0
- package/dashboard/assets/geist-600-DFOURf8L.woff2 +0 -0
- package/dashboard/assets/geist-mono-400-DTRLJnHl.woff2 +0 -0
- package/dashboard/assets/geist-mono-600-V-KvD_pi.woff2 +0 -0
- package/dashboard/assets/index-ErSVkiAF.css +1 -0
- package/dashboard/assets/index-cOSTL8eT.js +1 -0
- package/dashboard/assets/index-p0eB3k2y.js +70 -0
- package/dashboard/index.html +76 -0
- package/dist/attest-VWYZAOLY.js +346 -0
- package/dist/{audit-C756XFXR.js → audit-G4DDMAXH.js} +1 -1
- package/dist/chunk-6EU4GH4P.js +513 -0
- package/dist/chunk-B44GZXDX.js +136 -0
- package/dist/chunk-CIUQ6WHJ.js +88 -0
- package/dist/{chunk-BTIXDNTV.js → chunk-DJL2E6RF.js} +69 -6
- package/dist/{chunk-PQ457W6T.js → chunk-VSOUJM6X.js} +1 -0
- package/dist/{chunk-JJY62YXJ.js → chunk-ZZTWGFH2.js} +0 -20
- package/dist/dashboard-J6BA7OBL.js +105 -0
- package/dist/decide.js +1 -1
- package/dist/index.js +122 -1307
- package/dist/{init.js → init-ZP3SLHM3.js} +122 -36
- package/dist/run-YQBNEPSD.js +17 -0
- package/dist/{submit-2UALCRX2.js → submit-D6MQTAXT.js} +1 -1
- package/dist/verify-RISAGKXC.js +98 -0
- package/fixtures/attestation-sample.json +25 -0
- package/fixtures/attestation-tampered.json +29 -0
- package/fixtures/attestations.json +174 -0
- package/fixtures/audit.json +1810 -0
- package/fixtures/decisions.json +9899 -0
- package/fixtures/policy.json +216 -0
- package/package.json +8 -8
- package/dist/attest-QLIE2OSQ.js +0 -195
- package/dist/verify-HO4IU7XV.js +0 -45
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/paths.ts
|
|
4
|
+
import { randomBytes } from "crypto";
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
6
|
+
import os from "os";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
var cachedRoot;
|
|
10
|
+
function packageRoot() {
|
|
11
|
+
if (cachedRoot) return cachedRoot;
|
|
12
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
for (let i = 0; i < 6; i++) {
|
|
14
|
+
const pkg = path.join(dir, "package.json");
|
|
15
|
+
if (existsSync(pkg)) {
|
|
16
|
+
try {
|
|
17
|
+
const name = JSON.parse(readFileSync(pkg, "utf8")).name;
|
|
18
|
+
if (name === "blindseal") {
|
|
19
|
+
cachedRoot = dir;
|
|
20
|
+
return dir;
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const parent = path.dirname(dir);
|
|
26
|
+
if (parent === dir) break;
|
|
27
|
+
dir = parent;
|
|
28
|
+
}
|
|
29
|
+
cachedRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
30
|
+
return cachedRoot;
|
|
31
|
+
}
|
|
32
|
+
function packageVersion() {
|
|
33
|
+
try {
|
|
34
|
+
const pkg = JSON.parse(readFileSync(path.join(packageRoot(), "package.json"), "utf8"));
|
|
35
|
+
return pkg.version ? String(pkg.version) : "0.0.0";
|
|
36
|
+
} catch {
|
|
37
|
+
return "0.0.0";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function blindsealHome() {
|
|
41
|
+
return path.join(os.homedir(), ".blindseal");
|
|
42
|
+
}
|
|
43
|
+
function shippedDashboardDir() {
|
|
44
|
+
const dir = path.join(packageRoot(), "dashboard");
|
|
45
|
+
return existsSync(path.join(dir, "index.html")) ? dir : void 0;
|
|
46
|
+
}
|
|
47
|
+
function shippedFixturesDir() {
|
|
48
|
+
const dir = path.join(packageRoot(), "fixtures");
|
|
49
|
+
return existsSync(path.join(dir, "decisions.json")) ? dir : void 0;
|
|
50
|
+
}
|
|
51
|
+
function nearestFixturesDir(from = process.cwd()) {
|
|
52
|
+
let dir = from;
|
|
53
|
+
for (let i = 0; i < 7; i++) {
|
|
54
|
+
const candidate = path.join(dir, "fixtures");
|
|
55
|
+
if (existsSync(path.join(candidate, "decisions.json"))) return candidate;
|
|
56
|
+
const parent = path.dirname(dir);
|
|
57
|
+
if (parent === dir) break;
|
|
58
|
+
dir = parent;
|
|
59
|
+
}
|
|
60
|
+
return void 0;
|
|
61
|
+
}
|
|
62
|
+
function dashboardTokenPath() {
|
|
63
|
+
return path.join(blindsealHome(), "dashboard.token");
|
|
64
|
+
}
|
|
65
|
+
function resolveDashboardToken(explicit) {
|
|
66
|
+
if (explicit) return explicit;
|
|
67
|
+
const file = dashboardTokenPath();
|
|
68
|
+
try {
|
|
69
|
+
if (existsSync(file)) return readFileSync(file, "utf8").trim();
|
|
70
|
+
mkdirSync(path.dirname(file), { recursive: true, mode: 448 });
|
|
71
|
+
const tok = randomBytes(24).toString("base64url");
|
|
72
|
+
writeFileSync(file, tok + "\n", { mode: 384 });
|
|
73
|
+
return tok;
|
|
74
|
+
} catch {
|
|
75
|
+
return randomBytes(24).toString("base64url");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export {
|
|
80
|
+
packageRoot,
|
|
81
|
+
packageVersion,
|
|
82
|
+
blindsealHome,
|
|
83
|
+
shippedDashboardDir,
|
|
84
|
+
shippedFixturesDir,
|
|
85
|
+
nearestFixturesDir,
|
|
86
|
+
dashboardTokenPath,
|
|
87
|
+
resolveDashboardToken
|
|
88
|
+
};
|
|
@@ -36,9 +36,28 @@ var u64le = (n) => {
|
|
|
36
36
|
b.writeBigUInt64LE(n);
|
|
37
37
|
return b;
|
|
38
38
|
};
|
|
39
|
+
var PUBLIC_INPUTS_LEN = 168;
|
|
39
40
|
function decodePublicInputs(blob) {
|
|
40
|
-
if (blob
|
|
41
|
-
|
|
41
|
+
if (typeof blob === "string") {
|
|
42
|
+
const h = blob.trim().replace(/^0x/i, "").toLowerCase();
|
|
43
|
+
if (!/^[0-9a-f]*$/.test(h) || h.length % 2 !== 0)
|
|
44
|
+
throw new Error("publicInputs is not a hex string");
|
|
45
|
+
const b = decodeBytes(Buffer.from(h, "hex"));
|
|
46
|
+
const hx = (x) => "0x" + x.toString("hex");
|
|
47
|
+
return {
|
|
48
|
+
payloadHash: hx(b.payloadHash),
|
|
49
|
+
policyRoot: hx(b.policyRoot),
|
|
50
|
+
detectorVersion: hx(b.detectorVersion),
|
|
51
|
+
actionHash: hx(b.actionHash),
|
|
52
|
+
nullifier: hx(b.nullifier),
|
|
53
|
+
expirySlot: b.expirySlot.toString()
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return decodeBytes(blob);
|
|
57
|
+
}
|
|
58
|
+
function decodeBytes(blob) {
|
|
59
|
+
if (blob.length !== PUBLIC_INPUTS_LEN)
|
|
60
|
+
throw new Error(`publicInputs must be ${PUBLIC_INPUTS_LEN} bytes, got ${blob.length}`);
|
|
42
61
|
const b = Buffer.from(blob);
|
|
43
62
|
return {
|
|
44
63
|
payloadHash: b.subarray(0, 32),
|
|
@@ -122,17 +141,61 @@ async function isConsumed(att, conn, programId = PROGRAM_ID) {
|
|
|
122
141
|
const info = await conn.getAccountInfo(nullifierPda(nullifier, programId), "confirmed");
|
|
123
142
|
return info !== null;
|
|
124
143
|
}
|
|
125
|
-
|
|
144
|
+
var LABELED_FIELDS = ["payloadHash", "policyRoot", "detectorVersion", "actionHash", "nullifier", "expirySlot"];
|
|
145
|
+
var normHex = (v) => "0x" + String(v ?? "").trim().replace(/^0x/i, "").toLowerCase();
|
|
146
|
+
var normSlot = (v) => {
|
|
147
|
+
if (typeof v === "bigint")
|
|
148
|
+
return v.toString();
|
|
149
|
+
if (typeof v === "number")
|
|
150
|
+
return Number.isInteger(v) && v >= 0 ? String(v) : null;
|
|
151
|
+
if (typeof v === "string" && /^\d+$/.test(v.trim()))
|
|
152
|
+
return BigInt(v.trim()).toString();
|
|
153
|
+
return null;
|
|
154
|
+
};
|
|
155
|
+
function labelMismatches(att, decoded) {
|
|
156
|
+
const out = [];
|
|
157
|
+
for (const k of LABELED_FIELDS) {
|
|
158
|
+
const same = k === "expirySlot" ? normSlot(att.expirySlot) === decoded.expirySlot : normHex(att[k]) === decoded[k];
|
|
159
|
+
if (!same)
|
|
160
|
+
out.push(k);
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
var wasmReady = null;
|
|
165
|
+
var loadWasm = () => wasmReady ??= (async () => {
|
|
126
166
|
const wasm = await import("blindseal-verifier-wasm");
|
|
127
167
|
const { readFileSync } = await import("fs");
|
|
128
168
|
const req = createRequire(import.meta.url);
|
|
129
|
-
|
|
130
|
-
wasm
|
|
131
|
-
|
|
169
|
+
wasm.initSync({ module: readFileSync(req.resolve("blindseal-verifier-wasm/bg.wasm")) });
|
|
170
|
+
return wasm;
|
|
171
|
+
})().catch((e) => {
|
|
172
|
+
wasmReady = null;
|
|
173
|
+
throw e;
|
|
174
|
+
});
|
|
175
|
+
async function verifyOffchain(att) {
|
|
176
|
+
const wasm = await loadWasm();
|
|
177
|
+
let decoded;
|
|
178
|
+
let decodeError;
|
|
179
|
+
try {
|
|
180
|
+
decoded = decodePublicInputs(String(att.publicInputs?.[0] ?? ""));
|
|
181
|
+
} catch (e) {
|
|
182
|
+
decodeError = e instanceof Error ? e.message : String(e);
|
|
183
|
+
}
|
|
184
|
+
const forWasm = decoded ? { ...att, detectorVersion: decoded.detectorVersion } : att;
|
|
185
|
+
const proof = JSON.parse(wasm.verify_attestation_json(JSON.stringify(forWasm)));
|
|
186
|
+
const mismatches = decoded ? labelMismatches(att, decoded) : [];
|
|
187
|
+
if (!proof.ok)
|
|
188
|
+
return { ok: false, reason: proof.reason ?? "proof did not verify", decoded, mismatches };
|
|
189
|
+
if (!decoded)
|
|
190
|
+
return { ok: false, reason: decodeError ?? "publicInputs did not decode", mismatches };
|
|
191
|
+
if (mismatches.length)
|
|
192
|
+
return { ok: false, reason: `label mismatch: ${mismatches.join(", ")}`, decoded, mismatches };
|
|
193
|
+
return { ok: true, decoded, mismatches };
|
|
132
194
|
}
|
|
133
195
|
|
|
134
196
|
export {
|
|
135
197
|
DEFAULT_RPC_URL,
|
|
198
|
+
decodePublicInputs,
|
|
136
199
|
WrongClusterError,
|
|
137
200
|
submit,
|
|
138
201
|
isConsumed,
|
|
@@ -275,7 +275,6 @@ function loadConfig() {
|
|
|
275
275
|
capability: loadCapabilityConfig(),
|
|
276
276
|
tradePolicy: loadTradePolicyConfig(),
|
|
277
277
|
manifest: loadManifestConfig(),
|
|
278
|
-
telemetry: loadTelemetryConfig(),
|
|
279
278
|
audit: loadAuditConfig(),
|
|
280
279
|
persistence: loadScanStoreConfig()
|
|
281
280
|
};
|
|
@@ -284,25 +283,6 @@ function loadAuditConfig() {
|
|
|
284
283
|
const filepath = process.env.BLINDSEAL_AUDIT_LOG;
|
|
285
284
|
return { enabled: !!filepath, filepath };
|
|
286
285
|
}
|
|
287
|
-
function loadTelemetryConfig() {
|
|
288
|
-
const flag = process.env.BLINDSEAL_TELEMETRY?.toLowerCase();
|
|
289
|
-
const explicitlyOff = flag === "0" || flag === "false" || flag === "off" || flag === "no";
|
|
290
|
-
const explicitlyOn = flag === "1" || flag === "true" || flag === "on";
|
|
291
|
-
const url = process.env.BLINDSEAL_TELEMETRY_URL;
|
|
292
|
-
const enabled = !explicitlyOff && !!url;
|
|
293
|
-
const secret = process.env.BLINDSEAL_TELEMETRY_SECRET;
|
|
294
|
-
const batchSize = positiveInt(process.env.BLINDSEAL_TELEMETRY_BATCH, 100);
|
|
295
|
-
const flushIntervalMs = positiveInt(process.env.BLINDSEAL_TELEMETRY_FLUSH_MS, 5e3);
|
|
296
|
-
return {
|
|
297
|
-
enabled,
|
|
298
|
-
url,
|
|
299
|
-
secret,
|
|
300
|
-
batchSize,
|
|
301
|
-
flushIntervalMs,
|
|
302
|
-
bannerOnStart: enabled && !explicitlyOn
|
|
303
|
-
// implicit-on path
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
286
|
function loadManifestConfig() {
|
|
307
287
|
const raw = process.env.BLINDSEAL_MANIFEST_CHECK?.toLowerCase();
|
|
308
288
|
let mode;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
runRun
|
|
4
|
+
} from "./chunk-6EU4GH4P.js";
|
|
5
|
+
import "./chunk-B44GZXDX.js";
|
|
6
|
+
import "./chunk-VSOUJM6X.js";
|
|
7
|
+
import {
|
|
8
|
+
dashboardTokenPath,
|
|
9
|
+
resolveDashboardToken
|
|
10
|
+
} from "./chunk-CIUQ6WHJ.js";
|
|
11
|
+
import "./chunk-PDX44BCA.js";
|
|
12
|
+
|
|
13
|
+
// src/cli/dashboard.ts
|
|
14
|
+
import { spawn } from "child_process";
|
|
15
|
+
var HELP = `blindseal dashboard \u2014 open the local dashboard in your browser
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
blindseal dashboard [flags]
|
|
19
|
+
|
|
20
|
+
Flags
|
|
21
|
+
--port <n> local API port (default 7777)
|
|
22
|
+
--host <addr> local API host (default 127.0.0.1)
|
|
23
|
+
--no-open print the URL only, do not launch a browser
|
|
24
|
+
--help, -h this help
|
|
25
|
+
|
|
26
|
+
If nothing is listening, \`blindseal run --mock\` is started for you (Ctrl-C to stop it).
|
|
27
|
+
The URL carries the token from ${"~/.blindseal/dashboard.token"} in its fragment; it never leaves this machine.
|
|
28
|
+
`;
|
|
29
|
+
function parseDashboardArgs(argv) {
|
|
30
|
+
const out = { port: 7777, host: "127.0.0.1", open: true };
|
|
31
|
+
for (let i = 0; i < argv.length; i++) {
|
|
32
|
+
const a = argv[i];
|
|
33
|
+
if (a === "--help" || a === "-h") out.help = true;
|
|
34
|
+
else if (a === "--port") out.port = Number.parseInt(argv[++i] ?? "", 10);
|
|
35
|
+
else if (a === "--host") out.host = argv[++i] ?? out.host;
|
|
36
|
+
else if (a === "--no-open") out.open = false;
|
|
37
|
+
else if (a && a.startsWith("--")) throw new Error(`unknown flag: ${a}`);
|
|
38
|
+
}
|
|
39
|
+
if (!Number.isFinite(out.port) || out.port <= 0) throw new Error("--port must be a positive integer");
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
function dashboardUrl(host, port, token) {
|
|
43
|
+
return `http://${host}:${port}/#token=${token}`;
|
|
44
|
+
}
|
|
45
|
+
async function isListening(host, port, timeoutMs = 1500) {
|
|
46
|
+
try {
|
|
47
|
+
await fetch(`http://${host}:${port}/api/status`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function openBrowser(url) {
|
|
54
|
+
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
55
|
+
try {
|
|
56
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
57
|
+
child.on("error", () => {
|
|
58
|
+
});
|
|
59
|
+
child.unref();
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function runDashboardCli(argv) {
|
|
64
|
+
let o;
|
|
65
|
+
try {
|
|
66
|
+
o = parseDashboardArgs(argv);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
process.stderr.write(`blindseal dashboard: ${e.message}
|
|
69
|
+
${HELP}`);
|
|
70
|
+
return 2;
|
|
71
|
+
}
|
|
72
|
+
if (o.help) {
|
|
73
|
+
process.stdout.write(HELP);
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
const token = resolveDashboardToken();
|
|
77
|
+
const url = dashboardUrl(o.host, o.port, token);
|
|
78
|
+
if (await isListening(o.host, o.port)) {
|
|
79
|
+
process.stdout.write(`blindseal dashboard: ${url}
|
|
80
|
+
(token from ${dashboardTokenPath()})
|
|
81
|
+
`);
|
|
82
|
+
if (o.open) openBrowser(url);
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
process.stdout.write(`blindseal dashboard: nothing listening on ${o.host}:${o.port} \u2014 starting \`blindseal run --mock\`
|
|
86
|
+
`);
|
|
87
|
+
const running = runRun(["--mock", "--port", String(o.port), "--host", o.host]);
|
|
88
|
+
for (let i = 0; i < 20; i++) {
|
|
89
|
+
if (await isListening(o.host, o.port, 500)) {
|
|
90
|
+
process.stdout.write(`blindseal dashboard: ${url}
|
|
91
|
+
`);
|
|
92
|
+
if (o.open) openBrowser(url);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
96
|
+
}
|
|
97
|
+
return running;
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
dashboardUrl,
|
|
101
|
+
isListening,
|
|
102
|
+
openBrowser,
|
|
103
|
+
parseDashboardArgs,
|
|
104
|
+
runDashboardCli
|
|
105
|
+
};
|