Skip to content

Commit d014ddb

Browse files
committed
g
1 parent c56ab99 commit d014ddb

2 files changed

Lines changed: 72 additions & 2 deletions

File tree

worker/src/index.js

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { catalogTick, catalogReport, catalogFull, observatoryPage, observatoryJs
3030
// su loro invito. Zero costo: 1 lettura + 1 firma per tick; niente chain.
3131
// Secret WITNESS_KEY assente → disattivato in silenzio (fail-safe).
3232
import { witnessTick, witnessIndex, witnessLatestNote, witnessAddCheckpoint, witnessHistory, WITNESSED_LOGS } from "./witness.mjs";
33-
import { sealAction, getReceipt, rlogStatus, demoAllowed, treeRoot, signedCheckpoint, proofFor, verifyReceipt, anchorConsistency, PROVENANCE_LEVELS, RLOG_ORIGIN } from "./rlog.mjs";
33+
import { sealAction, getReceipt, rlogStatus, demoAllowed, treeRoot, signedCheckpoint, proofFor, verifyReceipt, anchorConsistency, consistencyProof, leaves, PROVENANCE_LEVELS, RLOG_ORIGIN } from "./rlog.mjs";
3434

3535
const GBLIN = "0x36C81d7E1966310F305eA637e761Cf77F90852f0";
3636
const BASKET_SELECTOR = "0x8c7e0875"; // basket(uint256)
@@ -44,7 +44,7 @@ const FALLBACK_RPCS = [
4444
];
4545
const SITE = "https://gblin.digital";
4646
const SUPPORTED_PROTOCOLS = ["2025-06-18", "2025-03-26", "2024-11-05"];
47-
const SERVER_INFO = { name: "gblin-mcp-http", version: "0.5.3" };
47+
const SERVER_INFO = { name: "gblin-mcp-http", version: "0.6.2" };
4848

4949
// ── Tools ───────────────────────────────────────────────────────────────────
5050

@@ -947,6 +947,8 @@ function howtoSeal() {
947947
price: "0.01 USDC on Base via x402 (unlimited)",
948948
demo: "MCP tool receipts.seal (mode demo) or POST https://gblin-mcp.gblin-mcp-worker.workers.dev/v1/seal-demo (5/day/IP, receipts marked demo:true)",
949949
fields: { action: "string <=128 (required)", input_hash: "sha256 hex of your input (required)", output_hash: "sha256 hex (optional)", agent_id: "string <=128 (optional)", tool: "string <=128 (optional)", meta: "JSON <=512 chars (optional)" },
950+
human_page: "GET /receipt/:index — HTML page that verifies the receipt in the browser",
951+
catalog_probe: "GET /observatory (human) · /observatory.json · /catalog — liveness probes of the public x402 catalog, our own endpoints included under the same rules",
950952
read_free: "GET /v1/receipt/:index · /log/checkpoint · /log/proof/:index · human page /receipt/:index",
951953
verify_offline: "MCP tool receipts.verify (pure math) or verify-receipt.mjs in github.com/gblinproject/gblin-treasury-risk-regime — zero dependencies",
952954
};
@@ -1272,6 +1274,10 @@ export default {
12721274
witness: "/witness (we cosign third-party transparency-log checkpoints; C2SP tlog-cosignature v1)",
12731275
audit: "/meta · /tools.json · /resources.json · /conformance · /v1/verify/:index (GET-only audit of the MCP surface)",
12741276
receipts: "/log (AI Action Receipts: seal what your agent did — $0.01 via x402 at gblin.digital/api/x402/seal, demo via MCP tool receipts.seal)",
1277+
prompts: PROMPTS.map((p) => p.name),
1278+
resources: RESOURCES.map((r) => r.uri),
1279+
observatory: "/observatory (human) · /observatory.json · /catalog · /observatory/badge.svg?host=… — liveness probes of the public x402 catalog, our own endpoints under the same rules",
1280+
coherence: "/coherence (promises vs conduct, sealed daily on Base)",
12751281
});
12761282
}
12771283

@@ -1366,10 +1372,37 @@ export default {
13661372
seal_demo: "POST /v1/seal-demo (5/day/IP, marked demo:true)",
13671373
read: "GET /v1/receipt/:index (free forever) · GET /log/proof/:index · GET /log/checkpoint",
13681374
explorer: "GET /receipt/:index (human page)",
1375+
for_witnesses: "GET /log/checkpoint (C2SP signed note) · GET /log/consistency?old=<m>&new=<n> (RFC 6962 append-only proof) · GET /log/leaves?start=&end= (raw records, recompute the tree yourself) · GET /log/proof/<i> (inclusion). Cosigning invitation open.",
13691376
anchor: "tree root anchored daily on Base via EAS (schema " + "0x9f433a96..., promiseId keccak256('gblin-receipts-log'))",
13701377
offline_verifier: "verify-receipt.mjs in github.com/gblinproject/gblin-treasury-risk-regime (zero deps)",
13711378
}, 200, { "cache-control": "public, max-age=60" });
13721379
}
1380+
// Ciò che serve a un WITNESS indipendente per firmare senza fidarsi di noi:
1381+
// prova di consistenza (append-only) e foglie in chiaro per ricalcolare l'albero.
1382+
if (url.pathname === "/log/consistency" && request.method === "GET") {
1383+
const m = Number(url.searchParams.get("old")), n0 = url.searchParams.get("new");
1384+
const N = Number((await env.COHERENCE.get("rlog:size")) || 0);
1385+
const n = n0 === null ? N : Number(n0);
1386+
if (!Number.isInteger(m) || !Number.isInteger(n) || !(0 < m && m <= n && n <= N))
1387+
return json({ error: `need 0 < old <= new <= ${N}` }, 400);
1388+
try {
1389+
const [proof, oldRoot, newRoot] = await Promise.all([
1390+
consistencyProof(env, m, n), treeRoot(env, m), treeRoot(env, n),
1391+
]);
1392+
return json({
1393+
origin: RLOG_ORIGIN, old: m, new: n,
1394+
old_root: btoa(String.fromCharCode(...oldRoot)), new_root: btoa(String.fromCharCode(...newRoot)),
1395+
proof, algorithm: "RFC 6962 §2.1.2 (SUBPROOF); node hash = SHA256(0x01 || left || right)",
1396+
note: "Proves the tree of `old` leaves is a prefix of the tree of `new` leaves: nothing was rewritten.",
1397+
}, 200, { "cache-control": "public, max-age=60" });
1398+
} catch (e) { return json({ error: String(e.message || e) }, 500); }
1399+
}
1400+
if (url.pathname === "/log/leaves" && request.method === "GET") {
1401+
const start = Number(url.searchParams.get("start") || 0);
1402+
const end = Number(url.searchParams.get("end") || start + 256);
1403+
if (!Number.isInteger(start) || !Number.isInteger(end)) return json({ error: "start/end must be integers" }, 400);
1404+
return json(await leaves(env, start, end), 200, { "cache-control": "public, max-age=60" });
1405+
}
13731406
if (url.pathname.startsWith("/log/proof/") && request.method === "GET") {
13741407
const idx = Number(url.pathname.slice("/log/proof/".length));
13751408
const N = Number((await env.COHERENCE?.get("rlog:size")) || 0);

worker/src/rlog.mjs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,43 @@ export async function proofFor(env, index, N) {
151151
return path.map(b64);
152152
}
153153

154+
// Consistency proof RFC 6962 (SUBPROOF): dimostra che l'albero di m foglie e'
155+
// un PREFISSO di quello di n foglie, cioe' che il log e' append-only e nessuna
156+
// voce e' stata riscritta. Senza questo un witness dovrebbe firmare alla cieca.
157+
async function subproof(env, m, a, b, isRoot) {
158+
const n = b - a;
159+
if (m === n) return isRoot ? [] : [await rangeRoot(env, a, b)];
160+
let k = 1; while (k * 2 < n) k *= 2;
161+
if (m <= k) {
162+
const sub = await subproof(env, m, a, a + k, isRoot);
163+
sub.push(await rangeRoot(env, a + k, b));
164+
return sub;
165+
}
166+
const sub = await subproof(env, m - k, a + k, b, false);
167+
sub.push(await rangeRoot(env, a, a + k));
168+
return sub;
169+
}
170+
export async function consistencyProof(env, m, n) {
171+
if (!(0 < m && m <= n)) throw new Error("need 0 < old <= new");
172+
if (m === n) return [];
173+
const path = await subproof(env, m, 0, n, true);
174+
return path.map(b64);
175+
}
176+
177+
// Foglie in chiaro [start,end): permette a chiunque di ricalcolare l'albero da zero.
178+
export async function leaves(env, start, end) {
179+
const N = Number((await env.COHERENCE.get("rlog:size")) || 0);
180+
end = Math.min(end, N);
181+
if (!(start >= 0 && start < end)) return { start, end, size: N, leaves: [] };
182+
if (end - start > 256) end = start + 256; // stesso tetto del log di Markovian
183+
const out = [];
184+
for (let i = start; i < end; i++) {
185+
const c = await env.COHERENCE.get(`rlog:entry:${i}`);
186+
out.push(c === null ? null : c);
187+
}
188+
return { start, end, size: N, encoding: "raw canonical JSON (gblin-canonical-json/1); leaf = SHA256(0x00 || record)", leaves: out };
189+
}
190+
154191
// ---------- checkpoint (signed note C2SP) ----------
155192
export async function signedCheckpoint(env, N, root) {
156193
const kp = parseKey(env.RLOG_KEY);

0 commit comments

Comments
 (0)