@coproduct_inc/verify 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -0
- package/dist/attestation.d.ts +182 -0
- package/dist/attestation.js +295 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.js +108 -0
- package/dist/index.d.ts +96 -0
- package/dist/index.js +69 -0
- package/examples/openclaw-replay.mjs +80 -0
- package/package.json +61 -0
- package/wasm/nodejs/nucleus_wasm.d.ts +199 -0
- package/wasm/nodejs/nucleus_wasm.js +538 -0
- package/wasm/nodejs/nucleus_wasm_bg.wasm +0 -0
- package/wasm/nodejs/nucleus_wasm_bg.wasm.d.ts +23 -0
- package/wasm/nodejs/package.json +9 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `nucleus-verify` — offline verifier for capability-boundary in-bounds
|
|
4
|
+
* attestation receipts. Exit code is the gate:
|
|
5
|
+
* 0 → receipt verified AND the agent stayed in bounds
|
|
6
|
+
* 1 → receipt failed verification, or the agent went out of bounds
|
|
7
|
+
* 2 → usage / I/O error
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* nucleus-verify <receipt.json> [--expect-principal <spiffe-id>]
|
|
11
|
+
* [--expect-key <base64url>] [--json] [--quiet]
|
|
12
|
+
* nucleus-verify --help
|
|
13
|
+
*
|
|
14
|
+
* Reads from stdin if `<receipt.json>` is "-" or omitted with piped input.
|
|
15
|
+
* Designed as a drop-in CI / GitHub Action step.
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
import { verifyReceiptJson } from "./attestation.js";
|
|
19
|
+
const HELP = `nucleus-verify — offline capability-boundary in-bounds attestation verifier
|
|
20
|
+
|
|
21
|
+
USAGE:
|
|
22
|
+
nucleus-verify <receipt.json> [options]
|
|
23
|
+
cat receipt.json | nucleus-verify [options]
|
|
24
|
+
|
|
25
|
+
OPTIONS:
|
|
26
|
+
--expect-principal <id> require the boundary principal to equal <id> (SPIFFE ID)
|
|
27
|
+
--expect-key <b64url> require the signer public key to equal <b64url>
|
|
28
|
+
--json print the full VerifyReport as JSON
|
|
29
|
+
--quiet suppress the human-readable line (exit code only)
|
|
30
|
+
-h, --help show this help
|
|
31
|
+
|
|
32
|
+
EXIT CODES:
|
|
33
|
+
0 verified and in bounds 1 verification failed / out of bounds 2 usage / I/O error`;
|
|
34
|
+
function parseArgs(argv) {
|
|
35
|
+
const args = { json: false, quiet: false, opts: {}, help: false };
|
|
36
|
+
for (let i = 0; i < argv.length; i++) {
|
|
37
|
+
const a = argv[i];
|
|
38
|
+
switch (a) {
|
|
39
|
+
case "-h":
|
|
40
|
+
case "--help":
|
|
41
|
+
args.help = true;
|
|
42
|
+
break;
|
|
43
|
+
case "--json":
|
|
44
|
+
args.json = true;
|
|
45
|
+
break;
|
|
46
|
+
case "--quiet":
|
|
47
|
+
args.quiet = true;
|
|
48
|
+
break;
|
|
49
|
+
case "--expect-principal":
|
|
50
|
+
args.opts.expectPrincipal = argv[++i];
|
|
51
|
+
break;
|
|
52
|
+
case "--expect-key":
|
|
53
|
+
args.opts.expectPublicKey = argv[++i];
|
|
54
|
+
break;
|
|
55
|
+
default:
|
|
56
|
+
if (a.startsWith("--")) {
|
|
57
|
+
throw new Error(`unknown option ${a}`);
|
|
58
|
+
}
|
|
59
|
+
args.path = a;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return args;
|
|
63
|
+
}
|
|
64
|
+
function readInput(path) {
|
|
65
|
+
if (!path || path === "-") {
|
|
66
|
+
return readFileSync(0, "utf8"); // fd 0 = stdin
|
|
67
|
+
}
|
|
68
|
+
return readFileSync(path, "utf8");
|
|
69
|
+
}
|
|
70
|
+
function summarize(report) {
|
|
71
|
+
if (report.ok) {
|
|
72
|
+
const n = report.recomputed.events.length;
|
|
73
|
+
return `✓ in bounds — verified ${n} event${n === 1 ? "" : "s"}; signature, hash-chain, and recomputed verdict all agree`;
|
|
74
|
+
}
|
|
75
|
+
return `✗ refused to attest — ${report.reason ?? "verification failed"}`;
|
|
76
|
+
}
|
|
77
|
+
function main() {
|
|
78
|
+
let args;
|
|
79
|
+
try {
|
|
80
|
+
args = parseArgs(process.argv.slice(2));
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
process.stderr.write(`error: ${e.message}\n\n${HELP}\n`);
|
|
84
|
+
return 2;
|
|
85
|
+
}
|
|
86
|
+
if (args.help) {
|
|
87
|
+
process.stdout.write(HELP + "\n");
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
let receiptJson;
|
|
91
|
+
try {
|
|
92
|
+
receiptJson = readInput(args.path);
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
process.stderr.write(`error: cannot read receipt: ${e.message}\n`);
|
|
96
|
+
return 2;
|
|
97
|
+
}
|
|
98
|
+
const report = verifyReceiptJson(receiptJson, args.opts);
|
|
99
|
+
if (args.json) {
|
|
100
|
+
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
101
|
+
}
|
|
102
|
+
else if (!args.quiet) {
|
|
103
|
+
const line = summarize(report);
|
|
104
|
+
(report.ok ? process.stdout : process.stderr).write(line + "\n");
|
|
105
|
+
}
|
|
106
|
+
return report.ok ? 0 : 1;
|
|
107
|
+
}
|
|
108
|
+
process.exit(main());
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@coproduct_inc/verify` — offline, zero-trust verification for Nucleus.
|
|
3
|
+
*
|
|
4
|
+
* Two receipt families share one package. The headline is the
|
|
5
|
+
* **capability-boundary in-bounds attestation** — see `./attestation`
|
|
6
|
+
* (re-exported below): given an agent run's tool-call trace + a declared
|
|
7
|
+
* capability boundary, emit and offline-verify an Ed25519-signed receipt
|
|
8
|
+
* that the agent stayed within bounds, keyed on a cryptographic principal
|
|
9
|
+
* rather than a mutable display name. The auction-receipt surface documented
|
|
10
|
+
* here is the original use case and remains available.
|
|
11
|
+
*
|
|
12
|
+
* ---
|
|
13
|
+
*
|
|
14
|
+
* Auction receipts: the agent NEVER trusts the hub's clearing price.
|
|
15
|
+
*
|
|
16
|
+
* The agent NEVER trusts the hub's clearing price. This package's WASM
|
|
17
|
+
* core (the SAME `nucleus-wasm` binary the browser demo runs) re-runs the
|
|
18
|
+
* auction clearing locally from the SIGNED bid set and asserts the
|
|
19
|
+
* recomputed price equals the price the receipt claims — on top of the
|
|
20
|
+
* Ed25519 signature + BLAKE3 root-hash check.
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { verify } from "@coproduct_inc/verify";
|
|
24
|
+
* const r = await verify(receiptJson, jwksJson);
|
|
25
|
+
* if (!r.ok) throw new Error(r.reason ?? "receipt failed verification");
|
|
26
|
+
* // r.ok === signature_ok && root_hash_ok && price_recomputed_ok
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* The Node target loads the wasm synchronously on first import; the
|
|
30
|
+
* `verify` API is async-shaped so a future web/bundler entry can stream
|
|
31
|
+
* the wasm without changing call sites.
|
|
32
|
+
*/
|
|
33
|
+
/** Which clearing rule the receipt commits to. */
|
|
34
|
+
export type RecomputePath = "vickrey" | "pigou-vcg";
|
|
35
|
+
/**
|
|
36
|
+
* Full verification result. `ok` is the single boolean to gate on; the
|
|
37
|
+
* component flags + recovered fields are for diagnostics / UI. All prices
|
|
38
|
+
* are integer micro-USD that fit a JS `number` (the kernel's internal
|
|
39
|
+
* u128 math never crosses this boundary).
|
|
40
|
+
*/
|
|
41
|
+
export interface FullVerifyReport {
|
|
42
|
+
/** `signatureOk && rootHashOk && priceRecomputedOk`. */
|
|
43
|
+
ok: boolean;
|
|
44
|
+
/** Ed25519 signature verified under the JWKS key matched by `kid`. */
|
|
45
|
+
signature_ok: boolean;
|
|
46
|
+
/** BLAKE3 of the canonical envelope equals the receipt's root hash. */
|
|
47
|
+
root_hash_ok: boolean;
|
|
48
|
+
/** The locally recomputed clearing price equals the receipt's price. */
|
|
49
|
+
price_recomputed_ok: boolean;
|
|
50
|
+
/** Price the recompute produced (micro-USD). */
|
|
51
|
+
recomputed_price_micro_usd: number;
|
|
52
|
+
/** Price the receipt claims (micro-USD), or null. */
|
|
53
|
+
signed_price_micro_usd: number | null;
|
|
54
|
+
/** Clearing rule the receipt commits to (`"vickrey"` today). */
|
|
55
|
+
path: RecomputePath;
|
|
56
|
+
/** Theorem-backed Pigou-VCG cross-check clearing, if externality data
|
|
57
|
+
* was present (reported, not gated on). */
|
|
58
|
+
pigou_vcg_clearing_micro_usd: number | null;
|
|
59
|
+
/** First failing reason, or null when `ok`. */
|
|
60
|
+
reason: string | null;
|
|
61
|
+
}
|
|
62
|
+
/** Recompute-only result (no signature check). */
|
|
63
|
+
export interface RecomputeReport {
|
|
64
|
+
recomputed_price_micro_usd: number;
|
|
65
|
+
receipt_price_micro_usd: number | null;
|
|
66
|
+
matches_receipt: boolean;
|
|
67
|
+
path: RecomputePath;
|
|
68
|
+
recomputed_winner_spiffe_id: string | null;
|
|
69
|
+
winner_matches: boolean;
|
|
70
|
+
pigou_vcg_clearing_micro_usd: number | null;
|
|
71
|
+
reason: string | null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Verify a signed auction receipt fully: signature + root hash + a LOCAL
|
|
75
|
+
* recomputation of the clearing price from the signed bids.
|
|
76
|
+
*
|
|
77
|
+
* @param receiptJson the receipt as a JSON string (the hub's wire form).
|
|
78
|
+
* @param jwksJson the pinned issuer JWKS document as a JSON string.
|
|
79
|
+
* @returns the structured report; `ok === true` only when all three
|
|
80
|
+
* checks pass. Never throws on a bad receipt — failures surface as
|
|
81
|
+
* `ok: false` with a `reason`.
|
|
82
|
+
*/
|
|
83
|
+
export declare function verify(receiptJson: string, jwksJson: string): Promise<FullVerifyReport>;
|
|
84
|
+
/**
|
|
85
|
+
* Recompute the clearing price from the signed bids WITHOUT checking the
|
|
86
|
+
* signature. Use when you have already verified the signature separately,
|
|
87
|
+
* or to inspect the recomputed-vs-claimed delta. Most callers want
|
|
88
|
+
* {@link verify} instead.
|
|
89
|
+
*/
|
|
90
|
+
export declare function recomputeClearing(receiptJson: string): Promise<RecomputeReport>;
|
|
91
|
+
/**
|
|
92
|
+
* Signature + root-hash check only (the pre-recompute verifier). Kept for
|
|
93
|
+
* back-compat / when you only need authenticity, not price-correctness.
|
|
94
|
+
*/
|
|
95
|
+
export declare function verifySignature(receiptJson: string, jwksJson: string): Promise<unknown>;
|
|
96
|
+
export * from "./attestation.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@coproduct_inc/verify` — offline, zero-trust verification for Nucleus.
|
|
3
|
+
*
|
|
4
|
+
* Two receipt families share one package. The headline is the
|
|
5
|
+
* **capability-boundary in-bounds attestation** — see `./attestation`
|
|
6
|
+
* (re-exported below): given an agent run's tool-call trace + a declared
|
|
7
|
+
* capability boundary, emit and offline-verify an Ed25519-signed receipt
|
|
8
|
+
* that the agent stayed within bounds, keyed on a cryptographic principal
|
|
9
|
+
* rather than a mutable display name. The auction-receipt surface documented
|
|
10
|
+
* here is the original use case and remains available.
|
|
11
|
+
*
|
|
12
|
+
* ---
|
|
13
|
+
*
|
|
14
|
+
* Auction receipts: the agent NEVER trusts the hub's clearing price.
|
|
15
|
+
*
|
|
16
|
+
* The agent NEVER trusts the hub's clearing price. This package's WASM
|
|
17
|
+
* core (the SAME `nucleus-wasm` binary the browser demo runs) re-runs the
|
|
18
|
+
* auction clearing locally from the SIGNED bid set and asserts the
|
|
19
|
+
* recomputed price equals the price the receipt claims — on top of the
|
|
20
|
+
* Ed25519 signature + BLAKE3 root-hash check.
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { verify } from "@coproduct_inc/verify";
|
|
24
|
+
* const r = await verify(receiptJson, jwksJson);
|
|
25
|
+
* if (!r.ok) throw new Error(r.reason ?? "receipt failed verification");
|
|
26
|
+
* // r.ok === signature_ok && root_hash_ok && price_recomputed_ok
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* The Node target loads the wasm synchronously on first import; the
|
|
30
|
+
* `verify` API is async-shaped so a future web/bundler entry can stream
|
|
31
|
+
* the wasm without changing call sites.
|
|
32
|
+
*/
|
|
33
|
+
// The wasm-pack `nodejs` target: CommonJS, wasm instantiated eagerly on
|
|
34
|
+
// require. Bundled as a sibling artifact so this package is self-contained.
|
|
35
|
+
import * as wasm from "../wasm/nodejs/nucleus_wasm.js";
|
|
36
|
+
/**
|
|
37
|
+
* Verify a signed auction receipt fully: signature + root hash + a LOCAL
|
|
38
|
+
* recomputation of the clearing price from the signed bids.
|
|
39
|
+
*
|
|
40
|
+
* @param receiptJson the receipt as a JSON string (the hub's wire form).
|
|
41
|
+
* @param jwksJson the pinned issuer JWKS document as a JSON string.
|
|
42
|
+
* @returns the structured report; `ok === true` only when all three
|
|
43
|
+
* checks pass. Never throws on a bad receipt — failures surface as
|
|
44
|
+
* `ok: false` with a `reason`.
|
|
45
|
+
*/
|
|
46
|
+
export async function verify(receiptJson, jwksJson) {
|
|
47
|
+
return wasm.verify_auction_receipt_full(receiptJson, jwksJson);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Recompute the clearing price from the signed bids WITHOUT checking the
|
|
51
|
+
* signature. Use when you have already verified the signature separately,
|
|
52
|
+
* or to inspect the recomputed-vs-claimed delta. Most callers want
|
|
53
|
+
* {@link verify} instead.
|
|
54
|
+
*/
|
|
55
|
+
export async function recomputeClearing(receiptJson) {
|
|
56
|
+
return wasm.verify_clearing_recompute(receiptJson);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Signature + root-hash check only (the pre-recompute verifier). Kept for
|
|
60
|
+
* back-compat / when you only need authenticity, not price-correctness.
|
|
61
|
+
*/
|
|
62
|
+
export async function verifySignature(receiptJson, jwksJson) {
|
|
63
|
+
return wasm.verify_auction_receipt(receiptJson, jwksJson);
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Capability-boundary in-bounds attestation (the headline evidence layer).
|
|
67
|
+
// Pure Node (`node:crypto`), no WASM — runs in CI, the CLI, and the Action.
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
export * from "./attestation.js";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// OpenClaw replay demo — capability-boundary in-bounds attestation.
|
|
2
|
+
//
|
|
3
|
+
// Replays the OpenClaw-class trust-boundary bypass (CVE-2026-25253): an agent
|
|
4
|
+
// run where a malicious tool call presents a `displayName` that MATCHES an
|
|
5
|
+
// allowlisted agent, but whose cryptographic `principal` (SPIFFE ID) differs.
|
|
6
|
+
// A gate that keys on the display name is fooled. Ours keys on the principal,
|
|
7
|
+
// so the receipt REFUSES to attest the bypass — while a clean run attests.
|
|
8
|
+
//
|
|
9
|
+
// Run after building: pnpm build && node examples/openclaw-replay.mjs
|
|
10
|
+
// (No persisted keys: an ephemeral Ed25519 keypair is generated in-memory.)
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
makeBoundary,
|
|
14
|
+
generateKeypair,
|
|
15
|
+
signReceipt,
|
|
16
|
+
verifyReceipt,
|
|
17
|
+
} from "../dist/index.js";
|
|
18
|
+
|
|
19
|
+
// The legitimate agent's cryptographic identity and what it may do.
|
|
20
|
+
const PRINCIPAL = "spiffe://acme.example/agent/billing-assistant";
|
|
21
|
+
const boundary = makeBoundary(PRINCIPAL, [
|
|
22
|
+
"read_invoice",
|
|
23
|
+
"list_invoices",
|
|
24
|
+
"summarize",
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
// Ephemeral attestor key (in-memory only — no custody, no provisioning).
|
|
28
|
+
const { privateKey } = generateKeypair();
|
|
29
|
+
|
|
30
|
+
function show(title, report) {
|
|
31
|
+
const verdict = report.ok ? "ATTESTED ✓" : "REFUSED ✗";
|
|
32
|
+
console.log(`\n── ${title} ──`);
|
|
33
|
+
console.log(` verdict: ${verdict}`);
|
|
34
|
+
console.log(` ok: ${report.ok}`);
|
|
35
|
+
console.log(` signature: ${report.signatureOk} hash-chain: ${report.rootHashOk} verdict-consistent: ${report.verdictConsistentOk}`);
|
|
36
|
+
if (!report.ok) console.log(` reason: ${report.reason}`);
|
|
37
|
+
return report;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 1) CLEAN RUN — every call is the real principal, every tool allowed.
|
|
41
|
+
const cleanTrace = [
|
|
42
|
+
{ seq: 0, tool: "list_invoices", principal: PRINCIPAL, displayName: "Billing Assistant" },
|
|
43
|
+
{ seq: 1, tool: "read_invoice", principal: PRINCIPAL, displayName: "Billing Assistant" },
|
|
44
|
+
{ seq: 2, tool: "summarize", principal: PRINCIPAL, displayName: "Billing Assistant" },
|
|
45
|
+
];
|
|
46
|
+
const cleanReceipt = signReceipt(boundary, cleanTrace, privateKey);
|
|
47
|
+
const cleanReport = show("CLEAN RUN", verifyReceipt(cleanReceipt));
|
|
48
|
+
|
|
49
|
+
// 2) OPENCLAW BYPASS — the injected call wears the allowlisted DISPLAY NAME
|
|
50
|
+
// ("Billing Assistant") but carries an ATTACKER principal. A name-keyed
|
|
51
|
+
// gate would wave it through; the boundary is bound to the SPIFFE ID, so
|
|
52
|
+
// the recomputed verdict is out-of-bounds and the receipt refuses.
|
|
53
|
+
const ATTACKER = "spiffe://acme.example/agent/unknown-7f3a";
|
|
54
|
+
const bypassTrace = [
|
|
55
|
+
{ seq: 0, tool: "list_invoices", principal: PRINCIPAL, displayName: "Billing Assistant" },
|
|
56
|
+
{ seq: 1, tool: "read_invoice", principal: ATTACKER, displayName: "Billing Assistant" },
|
|
57
|
+
];
|
|
58
|
+
const bypassReceipt = signReceipt(boundary, bypassTrace, privateKey);
|
|
59
|
+
const bypassReport = show("OPENCLAW BYPASS (display-name match, principal mismatch)", verifyReceipt(bypassReceipt));
|
|
60
|
+
|
|
61
|
+
// 3) FORGERY — attacker hand-edits the bypass receipt to claim inBounds:true.
|
|
62
|
+
// The verifier recomputes the verdict independently AND the signature was
|
|
63
|
+
// taken over the honest body, so both the verdict-consistency check and the
|
|
64
|
+
// signature check fail.
|
|
65
|
+
const forged = JSON.parse(JSON.stringify(bypassReceipt));
|
|
66
|
+
forged.verdict.inBounds = true;
|
|
67
|
+
forged.verdict.events = forged.verdict.events.map((e) => ({ ...e, inBounds: true, code: "ok", detail: "in bounds" }));
|
|
68
|
+
const forgedReport = show("FORGED inBounds:true (tampered claim)", verifyReceipt(forged));
|
|
69
|
+
|
|
70
|
+
console.log("\n── summary ──");
|
|
71
|
+
const pass =
|
|
72
|
+
cleanReport.ok === true &&
|
|
73
|
+
bypassReport.ok === false &&
|
|
74
|
+
bypassReport.recomputed.events[1]?.code === "principal_mismatch" &&
|
|
75
|
+
forgedReport.ok === false;
|
|
76
|
+
console.log(` clean attests: ${cleanReport.ok === true}`);
|
|
77
|
+
console.log(` bypass refused: ${bypassReport.ok === false} (${bypassReport.recomputed.events[1]?.code})`);
|
|
78
|
+
console.log(` forged claim rejected: ${forgedReport.ok === false}`);
|
|
79
|
+
console.log(pass ? "\nDEMO OK ✓" : "\nDEMO FAILED ✗");
|
|
80
|
+
process.exit(pass ? 0 : 1);
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@coproduct_inc/verify",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Offline, zero-trust verification of agent capability-boundary in-bounds attestation receipts: emit and verify an Ed25519-signed, hash-chained receipt that an agent run stayed within a declared boundary, keyed on a cryptographic principal (not a mutable display name) — the evidence layer above probabilistic guardrails. Also verifies Nucleus auction receipts by re-running the clearing locally. Zero runtime deps for the attestation core (node:crypto).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"nucleus-verify": "dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./attestation": {
|
|
18
|
+
"types": "./dist/attestation.d.ts",
|
|
19
|
+
"import": "./dist/attestation.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"wasm/nodejs/package.json",
|
|
25
|
+
"wasm/nodejs/nucleus_wasm.js",
|
|
26
|
+
"wasm/nodejs/nucleus_wasm.d.ts",
|
|
27
|
+
"wasm/nodejs/nucleus_wasm_bg.wasm",
|
|
28
|
+
"wasm/nodejs/nucleus_wasm_bg.wasm.d.ts",
|
|
29
|
+
"examples/openclaw-replay.mjs",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build:wasm": "wasm-pack build ../../crates/nucleus-wasm --target nodejs --out-dir ../../packages/nucleus-verify/wasm/nodejs && rm -f wasm/nodejs/.gitignore wasm/nodejs/package.json",
|
|
34
|
+
"build": "tsc -p tsconfig.json",
|
|
35
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
36
|
+
"test": "node --test",
|
|
37
|
+
"demo": "node examples/openclaw-replay.mjs"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"nucleus",
|
|
41
|
+
"agent-security",
|
|
42
|
+
"capability-boundary",
|
|
43
|
+
"in-bounds-attestation",
|
|
44
|
+
"verification",
|
|
45
|
+
"ed25519",
|
|
46
|
+
"spiffe",
|
|
47
|
+
"openclaw",
|
|
48
|
+
"auction",
|
|
49
|
+
"vcg",
|
|
50
|
+
"agentic-commerce"
|
|
51
|
+
],
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "git+https://github.com/coproduct/nucleus-platform.git",
|
|
55
|
+
"directory": "packages/nucleus-verify"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^25.9.1",
|
|
59
|
+
"typescript": "^5.7.2"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* **Day 3-4 export**: run the selective-recomputation analyzer.
|
|
6
|
+
*
|
|
7
|
+
* Takes the JSONL chain + a JSON-serialized [`CorrectionEvent`].
|
|
8
|
+
* Returns a [`RecomputationPlan`] serialized as a `JsValue`. The
|
|
9
|
+
* browser orchestrator renders the partition + lets the visitor
|
|
10
|
+
* expand any decision to see its `PreservationProof` or
|
|
11
|
+
* `RecomputationProof` inline.
|
|
12
|
+
*/
|
|
13
|
+
export function analyze_correction_event(chain_jsonl: string, correction_json: string): any;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* **Day 3-4 export**: build the dependency graph from a JSONL chain.
|
|
17
|
+
*
|
|
18
|
+
* Returns a [`DependencyGraph`] serialized as a `JsValue` — the JS
|
|
19
|
+
* side gets a plain object with `nodes: Vec<StepNode>` it can
|
|
20
|
+
* visualize without further parsing.
|
|
21
|
+
*/
|
|
22
|
+
export function compute_dependency_graph(chain_jsonl: string): any;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* **Day 10 export**: compute the typed savings summary from a
|
|
26
|
+
* recomputation plan + a per-step cost map.
|
|
27
|
+
*
|
|
28
|
+
* `plan_json` is a serialized [`RecomputationPlan`]; `cost_map_json` is
|
|
29
|
+
* `{ step_id: cost_micro_usd, ... }`. Returns:
|
|
30
|
+
* ```json
|
|
31
|
+
* {
|
|
32
|
+
* "total_steps": N,
|
|
33
|
+
* "preserved_steps": M,
|
|
34
|
+
* "recomputed_steps": K,
|
|
35
|
+
* "preservation_ratio": 0.0..=1.0,
|
|
36
|
+
* "baseline_cost_micro_usd": sum-of-all-step-costs,
|
|
37
|
+
* "selective_cost_micro_usd": sum-of-recompute-step-costs,
|
|
38
|
+
* "naive_restart_cost_micro_usd": 2 × baseline (the modeled "start over" worst case),
|
|
39
|
+
* "savings_vs_naive_ratio": 1 - selective / naive_restart
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* Costs are integer micro-USD (per workspace f64-discipline rule —
|
|
44
|
+
* money math stays in integers). The ratio is computed as `f64` only
|
|
45
|
+
* because it's a display-layer value (the page renders `XX.X%`).
|
|
46
|
+
*
|
|
47
|
+
* Steps absent from `cost_map` contribute zero — the JS side should
|
|
48
|
+
* pre-populate every step_id; the missing-key case is a no-op rather
|
|
49
|
+
* than a hard error so a malformed cost map doesn't blow up the
|
|
50
|
+
* browser tab.
|
|
51
|
+
*/
|
|
52
|
+
export function compute_savings_summary(plan_json: string, cost_map_json: string): any;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Install a browser-friendly panic hook. The hook routes any wasm-side
|
|
56
|
+
* `panic!` into `console.error`, which means failed assertions in the
|
|
57
|
+
* dependency analysis or signature verification path become visible in
|
|
58
|
+
* the visitor's DevTools instead of an opaque `RuntimeError: unreachable`.
|
|
59
|
+
*
|
|
60
|
+
* Idempotent — calling it more than once is harmless. Browsers should
|
|
61
|
+
* invoke this once on module load.
|
|
62
|
+
*/
|
|
63
|
+
export function init(): void;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* **Day 10 export**: parse a raw Claude Code session JSONL (the format
|
|
67
|
+
* Claude Code writes under `~/.claude/projects/.../<UUID>.jsonl`) into
|
|
68
|
+
* a renderable step list.
|
|
69
|
+
*
|
|
70
|
+
* This is the drag-and-drop path: the visitor's browser receives a
|
|
71
|
+
* session log it didn't sign and runs the dependency analyzer against
|
|
72
|
+
* it locally. The mapper produces unsigned [`nucleus_lineage::LineageEdge`]s;
|
|
73
|
+
* `signed = false` is set on the returned [`RenderableChain`] so the UI
|
|
74
|
+
* can label the source ("unsigned — your local session" vs. "signed
|
|
75
|
+
* corpus from coproduct-opensource/nucleus-recompute-corpus").
|
|
76
|
+
*
|
|
77
|
+
* Source-of-truth: the parse + map logic lives in `claude-code-capture`
|
|
78
|
+
* (the same code the server-side capture binary uses). One canonical
|
|
79
|
+
* implementation, shared between native + wasm.
|
|
80
|
+
*/
|
|
81
|
+
export function parse_claude_code_session(session_jsonl: string): any;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* **Day 10 export**: parse a JSONL chain of signed lineage edges into
|
|
85
|
+
* a renderable step list (one [`RenderableStep`] per edge in chain
|
|
86
|
+
* order). The orchestrator on the JS side consumes this directly to
|
|
87
|
+
* render the dependency graph + per-step drawer.
|
|
88
|
+
*
|
|
89
|
+
* Wire format: `chain_jsonl` is one `LineageEdge` per line (the same
|
|
90
|
+
* format `verify_chain_signatures` expects). Signature verification is
|
|
91
|
+
* NOT performed here — call `verify_chain_signatures` first if the
|
|
92
|
+
* chain's provenance matters.
|
|
93
|
+
*/
|
|
94
|
+
export function parse_lineage_chain(chain_jsonl: string): any;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* **Offline auction-receipt verifier — the "verify this receipt"
|
|
98
|
+
* button.** Given a signed `AuctionReceipt` JSON + a pinned JWKS
|
|
99
|
+
* document, re-build the canonical envelope, re-hash it (BLAKE3), and
|
|
100
|
+
* Ed25519-verify the signature against the issuer key matched by `kid`
|
|
101
|
+
* — all in the visitor's tab, with zero hub access.
|
|
102
|
+
*
|
|
103
|
+
* Returns a [`ReceiptVerifyReport`] serialized as a `JsValue`:
|
|
104
|
+
* ```json
|
|
105
|
+
* {
|
|
106
|
+
* "ok": true,
|
|
107
|
+
* "reason": null,
|
|
108
|
+
* "auction_id": "...", "issuer_kid": "...", "root_hash_hex": "...",
|
|
109
|
+
* "issued_at_micros": 1717…, "winner_spiffe_id": "spiffe://…",
|
|
110
|
+
* "clearing_price_micro_usd": 250000, "bid_count": 3
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* Never throws on a bad receipt — `ok = false` with a `reason` string
|
|
115
|
+
* is the FAIL path (so the page renders PASS/FAIL rather than a thrown
|
|
116
|
+
* error). A `JsValue` error is only returned if the report itself
|
|
117
|
+
* can't be serialized, which shouldn't happen.
|
|
118
|
+
*/
|
|
119
|
+
export function verify_auction_receipt(receipt_json: string, jwks_json: string): any;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* **Bet C — the full offline verifier.** Combines the signature +
|
|
123
|
+
* root-hash check ([`verify_auction_receipt`]) with the local
|
|
124
|
+
* clearing-price recomputation ([`verify_clearing_recompute`]) into a
|
|
125
|
+
* single call, so an agent NEVER trusts the hub's price: the wasm
|
|
126
|
+
* recomputes it locally from the signed bids and asserts equality.
|
|
127
|
+
*
|
|
128
|
+
* The drop-in story (`@nucleus/verify`):
|
|
129
|
+
* ```js
|
|
130
|
+
* import { verify } from '@nucleus/verify';
|
|
131
|
+
* const r = await verify(receiptJson, jwksJson);
|
|
132
|
+
* if (!r.ok) throw new Error(r.reason);
|
|
133
|
+
* // r.ok === signature_ok && root_hash_ok && price_recomputed_ok
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* Returns a [`FullVerifyReport`] as a `JsValue`. All prices are `u64`
|
|
137
|
+
* micro-USD (fit JS `Number` / Python `int`); the kernel's internal
|
|
138
|
+
* `u128` math never crosses this boundary.
|
|
139
|
+
*/
|
|
140
|
+
export function verify_auction_receipt_full(receipt_json: string, jwks_json: string): any;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Day-1 entry point. Parses a JSONL chain + a JWKS document and runs
|
|
144
|
+
* per-edge Ed25519 signature verification.
|
|
145
|
+
*
|
|
146
|
+
* Wire format:
|
|
147
|
+
* - `chain_jsonl`: one signed [`nucleus_lineage::LineageEdge`] per line.
|
|
148
|
+
* - `jwks_json`: a JSON document with a top-level `{ "keys": [...] }` array,
|
|
149
|
+
* each entry an Ed25519 JWK.
|
|
150
|
+
*
|
|
151
|
+
* Returns a [`VerifyReport`] serialized as a `JsValue` (so the JS side
|
|
152
|
+
* gets a plain object, not a string it has to re-parse). Errors are
|
|
153
|
+
* surfaced as `JsValue` strings — wrap with `wasm_bindgen::throw_str`
|
|
154
|
+
* downstream if richer error types are needed.
|
|
155
|
+
*/
|
|
156
|
+
export function verify_chain_signatures(chain_jsonl: string, jwks_json: string): any;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* **Bet C moat-closer — recompute-only export.** Re-runs the auction
|
|
160
|
+
* clearing locally from the receipt's signed bid set and reports whether
|
|
161
|
+
* the recomputed price matches the receipt's claimed price. Does NOT
|
|
162
|
+
* check the signature — pair with [`verify_auction_receipt`] (or use
|
|
163
|
+
* [`verify_auction_receipt_full`], which does both).
|
|
164
|
+
*
|
|
165
|
+
* Returns a [`recompute::RecomputeReport`] as a `JsValue`:
|
|
166
|
+
* ```json
|
|
167
|
+
* {
|
|
168
|
+
* "recomputed_price_micro_usd": 400000,
|
|
169
|
+
* "receipt_price_micro_usd": 400000,
|
|
170
|
+
* "matches_receipt": true,
|
|
171
|
+
* "path": "vickrey",
|
|
172
|
+
* "recomputed_winner_spiffe_id": "spiffe://…",
|
|
173
|
+
* "winner_matches": true,
|
|
174
|
+
* "pigou_vcg_clearing_micro_usd": null,
|
|
175
|
+
* "reason": null
|
|
176
|
+
* }
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
export function verify_clearing_recompute(receipt_json: string): any;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* **Day 3-4 export**: independently verify a single
|
|
183
|
+
* [`PreservationProof`].
|
|
184
|
+
*
|
|
185
|
+
* The browser surfaces this on the demo's "expand any preserved step
|
|
186
|
+
* to see its proof" affordance. The proof is JSON-serializable; the
|
|
187
|
+
* WASM call re-runs the structural check and returns `null` on
|
|
188
|
+
* success or an error string on falsification.
|
|
189
|
+
*/
|
|
190
|
+
export function verify_preservation(proof_json: string): void;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* **Day 3-4 export**: independently verify a single
|
|
194
|
+
* [`RecomputationProof`].
|
|
195
|
+
*
|
|
196
|
+
* Same shape as [`verify_preservation`] — re-runs the witness-hash
|
|
197
|
+
* check, returns a `JsValue` error on falsification.
|
|
198
|
+
*/
|
|
199
|
+
export function verify_recomputation_necessity(proof_json: string): void;
|