capacity-attest 0.4.0 → 0.5.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 +7 -1
- package/dist/discovery-fixture.d.ts +26 -0
- package/dist/discovery-fixture.d.ts.map +1 -0
- package/dist/discovery-fixture.js +95 -0
- package/dist/discovery-fixture.js.map +1 -0
- package/dist/discovery.d.ts +80 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +199 -0
- package/dist/discovery.js.map +1 -0
- package/dist/eas.d.ts +77 -0
- package/dist/eas.d.ts.map +1 -0
- package/dist/eas.js +193 -0
- package/dist/eas.js.map +1 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -74,6 +74,12 @@ Eerlijke grens: ook zelf-herrekenen betrapt geen verborgen laatste claim en geen
|
|
|
74
74
|
|
|
75
75
|
Een openbaar, zelf-controleerbaar voorbeeld met een nagebootste verbergende host en expres-kapotte testgevallen staat in [docs/COMPLETENESS-FIXTURE.md](./docs/COMPLETENESS-FIXTURE.md). Draai het met `npm run fixture`; dezelfde controles draaien bij elke push als test. Zo kun je onze claim zelf natellen in plaats van ons op ons woord te geloven.
|
|
76
76
|
|
|
77
|
+
## Claims van andere installaties vinden (D-005)
|
|
78
|
+
|
|
79
|
+
`get_delivery_history` is per definitie lokaal: koper B ziet niet wat koper A op een andere installatie vastlegde over dezelfde verkoper. Omdat elke claim zelf-verifieerbaar is, heeft vindbaarheid geen vertrouwde index nodig. `discoverDeliveryHistory(seller, sources)` (zie `src/discovery.ts`) leest een verkopers claims uit meerdere onafhankelijke, ONvertrouwde bronnen (je lokale ledger plus elk host-onafhankelijk substraat dat je wilt lezen), ontdubbelt, herverifieert elke claim, filtert andere verkopers eruit, en draait de completeness-check over het geheel. Een bron die nep injecteert wordt geweigerd; een bron die weglaat is het D-006-probleem, meegenomen maar niet magisch opgelost.
|
|
80
|
+
|
|
81
|
+
De productie-onderlaag (EAS op Base, ERC-8004) is bewust nog niet live gekoppeld: dat kost gas en wacht op een echte integrator. De naad staat klaar. Een openbaar, draaibaar voorbeeld met twee nagebootste installaties staat in [docs/DISCOVERY-FIXTURE.md](./docs/DISCOVERY-FIXTURE.md), draai het met `npm run discovery-fixture`. Zie [DECISIONS.md](./DECISIONS.md) D-005.
|
|
82
|
+
|
|
77
83
|
### 3. `resolve_agent_identity` *(sinds 0.3.0)*
|
|
78
84
|
|
|
79
85
|
Read-only opzoeking tegen een ERC-8004 Identity Registry: wie bezit `agentId` (`ownerOf`) en waar staat zijn registratiebestand (`tokenURI`). Alleen de standaard ERC-721-interface wordt aangeroepen, niets ERC-8004-specifieks. Vereist van de aanroeper zowel `agentRegistryRef` (`"eip155:<chainId>:<registryAddress>"`) als een `rpcUrl` voor die chain: dit project bundelt bewust geen eigen RPC-provider en geen canoniek registry-adres, want ERC-8004 heeft onafhankelijke deployments per chain en de EIP-tekst zelf noemt geen vast adres. Haalt bewust NOOIT op wat `tokenURI` aanwijst (dat blijft een pointer die de aanroeper zelf desgewenst opvraagt); dat zou een SSRF-vormig risico zijn op aanroeper-gecontroleerde on-chain data.
|
|
@@ -89,7 +95,7 @@ De claim wordt ondertekend door de **koper** (de partij die betaalde en dus weet
|
|
|
89
95
|
Elke claim in de ledger is met alleen het npm-package en de rauwe claim-bytes na te rekenen, zonder toegang tot dit project of een netwerkoproep naar ons. Geen account, geen hosted call.
|
|
90
96
|
|
|
91
97
|
```bash
|
|
92
|
-
npm install capacity-attest
|
|
98
|
+
npm install capacity-attest
|
|
93
99
|
```
|
|
94
100
|
|
|
95
101
|
```js
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ethers } from "ethers";
|
|
2
|
+
import { type DeliveryClaim } from "./schema.js";
|
|
3
|
+
export declare const BUYER_A: ethers.Wallet;
|
|
4
|
+
export declare const BUYER_B: ethers.Wallet;
|
|
5
|
+
export declare const SELLER_X = "0x00000000000000000000000000000000000000aa";
|
|
6
|
+
export interface DiscoveryFixture {
|
|
7
|
+
a1: DeliveryClaim;
|
|
8
|
+
a2: DeliveryClaim;
|
|
9
|
+
a3: DeliveryClaim;
|
|
10
|
+
b1: DeliveryClaim;
|
|
11
|
+
y1: DeliveryClaim;
|
|
12
|
+
forged: DeliveryClaim;
|
|
13
|
+
tampered: DeliveryClaim;
|
|
14
|
+
}
|
|
15
|
+
export declare function buildDiscoveryFixture(): Promise<DiscoveryFixture>;
|
|
16
|
+
export type ControlKind = "GREEN" | "RED";
|
|
17
|
+
export interface Control {
|
|
18
|
+
id: string;
|
|
19
|
+
kind: ControlKind;
|
|
20
|
+
what: string;
|
|
21
|
+
expected: string;
|
|
22
|
+
actual: string;
|
|
23
|
+
pass: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function runControls(): Promise<Control[]>;
|
|
26
|
+
//# sourceMappingURL=discovery-fixture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery-fixture.d.ts","sourceRoot":"","sources":["../src/discovery-fixture.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAKpE,eAAO,MAAM,OAAO,eAA4C,CAAC;AACjE,eAAO,MAAM,OAAO,eAA4C,CAAC;AAGjE,eAAO,MAAM,QAAQ,+CAA+C,CAAC;AASrE,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,aAAa,CAAC;IAClB,EAAE,EAAE,aAAa,CAAC;IAClB,EAAE,EAAE,aAAa,CAAC;IAClB,EAAE,EAAE,aAAa,CAAC;IAClB,EAAE,EAAE,aAAa,CAAC;IAClB,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAkBvE;AAED,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,KAAK,CAAC;AAC1C,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf;AASD,wBAAsB,WAAW,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CA2CtD"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// discovery-fixture.ts — a public, reproducible fixture for cross-installation
|
|
2
|
+
// discovery (D-005), the companion to completeness-fixture.ts (D-006).
|
|
3
|
+
//
|
|
4
|
+
// It demonstrates, with claims you can rebuild from fixed test keys, that:
|
|
5
|
+
// 1. The gap is real: buyer B, reading only B's own installation, does not
|
|
6
|
+
// see buyer A's claims about the same seller.
|
|
7
|
+
// 2. Discovery closes it: aggregating B's local ledger with a shared,
|
|
8
|
+
// UNTRUSTED substrate (simulated here, EAS/ERC-8004 in production) makes
|
|
9
|
+
// A's claims visible to B, and every one is re-verified locally.
|
|
10
|
+
// 3. It stays trustless: a malicious source's forged/tampered claims are
|
|
11
|
+
// rejected, and claims about a different seller are filtered out.
|
|
12
|
+
// 4. It composes with D-006: a source that hides a MIDDLE claim in a chain
|
|
13
|
+
// it otherwise reveals is still caught by the completeness analysis, now
|
|
14
|
+
// across installations.
|
|
15
|
+
//
|
|
16
|
+
// Honest boundary, same as everywhere else: discovery is bounded by what
|
|
17
|
+
// sources reveal. It solves findability, not completeness. A source that omits
|
|
18
|
+
// a claim (a hidden tail, or a whole buyer) cannot be forced to reveal it; no
|
|
19
|
+
// aggregation invents what no source shows.
|
|
20
|
+
//
|
|
21
|
+
// Pure, offline, deterministic: fixed keys, fixed timestamps, no ledger, no
|
|
22
|
+
// network. Consumed by examples/discovery-fixture.ts and the discovery tests.
|
|
23
|
+
import { ethers } from "ethers";
|
|
24
|
+
import { signClaim } from "./signing.js";
|
|
25
|
+
import { discoverDeliveryHistory, staticSource } from "./discovery.js";
|
|
26
|
+
// Two fixed TEST buyers on two notional installations, and one impostor.
|
|
27
|
+
// source: simulated (deterministic keys, not real identities).
|
|
28
|
+
export const BUYER_A = new ethers.Wallet("0x" + "a1".repeat(32));
|
|
29
|
+
export const BUYER_B = new ethers.Wallet("0x" + "b2".repeat(32));
|
|
30
|
+
const IMPOSTOR = new ethers.Wallet("0x" + "cc".repeat(32));
|
|
31
|
+
export const SELLER_X = "0x00000000000000000000000000000000000000aa";
|
|
32
|
+
const SELLER_Y = "0x00000000000000000000000000000000000000bb"; // a different seller, for the filter control
|
|
33
|
+
const EVIDENCE_HASH = "a".repeat(64);
|
|
34
|
+
async function sign(wallet, content) {
|
|
35
|
+
const { claimId, signature } = await signClaim(wallet, content);
|
|
36
|
+
return { ...content, claimId, signature };
|
|
37
|
+
}
|
|
38
|
+
export async function buildDiscoveryFixture() {
|
|
39
|
+
const aBase = { sellerAddress: SELLER_X, buyerAddress: BUYER_A.address, assetType: "gpu-hours", evidenceHash: EVIDENCE_HASH };
|
|
40
|
+
const a1 = await sign(BUYER_A, { ...aBase, promisedSpec: "1x A100, 4h", delivered: "yes", settlementRef: "0x" + "a1".repeat(32), timestamp: "2026-01-01T00:00:00.000Z" });
|
|
41
|
+
const a2 = await sign(BUYER_A, { ...aBase, promisedSpec: "1x A100, 8h", delivered: "no", settlementRef: "0x" + "a2".repeat(32), timestamp: "2026-02-01T00:00:00.000Z", priorClaimId: a1.claimId });
|
|
42
|
+
const a3 = await sign(BUYER_A, { ...aBase, promisedSpec: "1x A100, 2h", delivered: "yes", settlementRef: "0x" + "a3".repeat(32), timestamp: "2026-03-01T00:00:00.000Z", priorClaimId: a2.claimId });
|
|
43
|
+
const b1 = await sign(BUYER_B, { sellerAddress: SELLER_X, buyerAddress: BUYER_B.address, assetType: "gpu-hours", promisedSpec: "1x A100, 1h", delivered: "yes", evidenceHash: EVIDENCE_HASH, settlementRef: "0x" + "b1".repeat(32), timestamp: "2026-02-15T00:00:00.000Z" });
|
|
44
|
+
const y1 = await sign(BUYER_A, { sellerAddress: SELLER_Y, buyerAddress: BUYER_A.address, assetType: "gpu-hours", promisedSpec: "other seller", delivered: "yes", evidenceHash: EVIDENCE_HASH, settlementRef: "0x" + "01".repeat(32), timestamp: "2026-01-15T00:00:00.000Z" });
|
|
45
|
+
// forged: content claims buyerAddress = A, but signed by the impostor, so the
|
|
46
|
+
// signature does not recover to A. verifyClaim must reject it.
|
|
47
|
+
const forgedContent = { sellerAddress: SELLER_X, buyerAddress: BUYER_A.address, assetType: "gpu-hours", promisedSpec: "forged", delivered: "yes", evidenceHash: EVIDENCE_HASH, settlementRef: "0x" + "de".repeat(32), timestamp: "2026-04-01T00:00:00.000Z" };
|
|
48
|
+
const forgedSig = await signClaim(IMPOSTOR, forgedContent);
|
|
49
|
+
const forged = { ...forgedContent, ...forgedSig };
|
|
50
|
+
// tampered: take a genuine a1 and flip delivered without recomputing claimId.
|
|
51
|
+
const tampered = { ...a1, delivered: "no" };
|
|
52
|
+
return { a1, a2, a3, b1, y1, forged, tampered };
|
|
53
|
+
}
|
|
54
|
+
function control(id, kind, what, expected, actual) {
|
|
55
|
+
return { id, kind, what, expected, actual, pass: expected === actual };
|
|
56
|
+
}
|
|
57
|
+
function has(result, claim) {
|
|
58
|
+
return result.claims.some((c) => c.claimId.toLowerCase() === claim.claimId.toLowerCase());
|
|
59
|
+
}
|
|
60
|
+
export async function runControls() {
|
|
61
|
+
const { a1, a2, a3, b1, y1, forged, tampered } = await buildDiscoveryFixture();
|
|
62
|
+
const controls = [];
|
|
63
|
+
// D1 GREEN — the gap: B, reading only its own installation, does not see A's negative claim.
|
|
64
|
+
const bOnly = await discoverDeliveryHistory(SELLER_X, [staticSource("installation-B-local", [b1])]);
|
|
65
|
+
controls.push(control("D1", "GREEN", "B's own installation alone does NOT contain A's negative claim (the D-005 gap)", "a2 absent, count=1", `a2 ${has(bOnly, a2) ? "present" : "absent"}, count=${bOnly.count}`));
|
|
66
|
+
// D2 GREEN — findability: B + shared substrate surfaces A's claims to B. The
|
|
67
|
+
// substrate is a RAW source (does not pre-filter by seller) that also carries
|
|
68
|
+
// a Y-seller claim, so discovery's OWN seller filter is what must drop y1.
|
|
69
|
+
const substrate = { name: "shared-substrate (simulated EAS/ERC-8004)", fetchForSeller: async () => [a1, a2, a3, y1] };
|
|
70
|
+
const discovered = await discoverDeliveryHistory(SELLER_X, [staticSource("installation-B-local", [b1]), substrate]);
|
|
71
|
+
controls.push(control("D2", "GREEN", "Discovery surfaces A's negative claim to B across installations", "a2 present, count=4", `a2 ${has(discovered, a2) ? "present" : "absent"}, count=${discovered.count}`));
|
|
72
|
+
// D3 GREEN — every claim in the aggregate is verified.
|
|
73
|
+
const { verifyClaim } = await import("./signing.js");
|
|
74
|
+
const allVerify = discovered.claims.every((c) => verifyClaim(c).ok);
|
|
75
|
+
controls.push(control("D3", "GREEN", "Every claim in the aggregate re-verifies locally", "true", String(allVerify)));
|
|
76
|
+
// D4 GREEN — a claim about a different seller (y1) is filtered out of X's aggregate.
|
|
77
|
+
controls.push(control("D4", "GREEN", "A different-seller claim from a source is filtered out", "y1 absent", `y1 ${has(discovered, y1) ? "present" : "absent"}`));
|
|
78
|
+
// D5 GREEN — dedup: the same claim from two sources is counted once.
|
|
79
|
+
const deduped = await discoverDeliveryHistory(SELLER_X, [staticSource("src1", [b1, a1]), staticSource("src2", [b1, a1])]);
|
|
80
|
+
const src2 = deduped.sources[1];
|
|
81
|
+
controls.push(control("D5", "GREEN", "The same claim from two sources is counted once", "count=2, dup=2", `count=${deduped.count}, dup=${src2?.duplicates}`));
|
|
82
|
+
// D6 RED — a forged claim (wrong signer) from a source is rejected, not surfaced.
|
|
83
|
+
const withForged = await discoverDeliveryHistory(SELLER_X, [staticSource("malicious", [forged])]);
|
|
84
|
+
controls.push(control("D6", "RED", "A forged claim (wrong signer) is rejected, not surfaced", "count=0, rejected>=1", `count=${withForged.count}, ${(withForged.sources[0]?.rejected ?? 0) >= 1 ? "rejected>=1" : "rejected=0"}`));
|
|
85
|
+
// D7 RED — a tampered claim (flipped byte, claimId not recomputed) is rejected.
|
|
86
|
+
const withTampered = await discoverDeliveryHistory(SELLER_X, [staticSource("malicious", [tampered])]);
|
|
87
|
+
controls.push(control("D7", "RED", "A tampered claim (flipped byte) is rejected, not surfaced", "count=0, rejected>=1", `count=${withTampered.count}, ${(withTampered.sources[0]?.rejected ?? 0) >= 1 ? "rejected>=1" : "rejected=0"}`));
|
|
88
|
+
// D8 GREEN — composition with D-006: a substrate that hides A's MIDDLE claim
|
|
89
|
+
// (shows a1 and a3, omits a2) is still caught, now across installations.
|
|
90
|
+
const hiding = await discoverDeliveryHistory(SELLER_X, [staticSource("installation-B-local", [b1]), staticSource("substrate-hiding-a2", [a1, a3])]);
|
|
91
|
+
const flaggedA2 = hiding.completeness.possibleOmissions.some((o) => o.missingPriorClaimId.toLowerCase() === a2.claimId.toLowerCase());
|
|
92
|
+
controls.push(control("D8", "GREEN", "A source hiding a middle claim is caught by completeness, across installations", "true", String(flaggedA2)));
|
|
93
|
+
return controls;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=discovery-fixture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery-fixture.js","sourceRoot":"","sources":["../src/discovery-fixture.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type DeliveryClaim } from "./schema.js";
|
|
2
|
+
import { type CompletenessReport } from "./completeness.js";
|
|
3
|
+
/**
|
|
4
|
+
* An untrusted place claims about a seller can be discovered: the local
|
|
5
|
+
* ledger, a shared file, an EAS query on Base, another installation's public
|
|
6
|
+
* endpoint, an IPFS/git convention, etc. `fetchForSeller` may return anything,
|
|
7
|
+
* including forged or junk claims; discoverDeliveryHistory re-verifies all of
|
|
8
|
+
* them, so a source is never trusted, only read.
|
|
9
|
+
*/
|
|
10
|
+
export interface ClaimSource {
|
|
11
|
+
/** Human label for per-source accounting in the result. */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Return whatever this source believes are claims about sellerAddress. Untrusted. */
|
|
14
|
+
fetchForSeller(sellerAddress: string): Promise<DeliveryClaim[]>;
|
|
15
|
+
}
|
|
16
|
+
/** Per-source accounting: what a source returned, and how much survived verification. */
|
|
17
|
+
export interface SourceReport {
|
|
18
|
+
name: string;
|
|
19
|
+
/** How many entries the source returned (before any cap). */
|
|
20
|
+
fetched: number;
|
|
21
|
+
/** How many verified, concerned this seller, AND were not already contributed by an earlier source. */
|
|
22
|
+
accepted: number;
|
|
23
|
+
/** How many FAILED verifyClaim (forged/tampered/junk). Cryptographic failures only. */
|
|
24
|
+
rejected: number;
|
|
25
|
+
/** How many verified fine but were about a DIFFERENT seller (honest-but-off-topic, not a forgery). */
|
|
26
|
+
wrongSeller: number;
|
|
27
|
+
/** How many verified but were duplicates of a claim already seen (same claimId). */
|
|
28
|
+
duplicates: number;
|
|
29
|
+
/** Set when the source returned more than the per-source cap; only the first cap entries were examined. */
|
|
30
|
+
truncated?: boolean;
|
|
31
|
+
/** Set only when fetchForSeller threw OR resolved to a non-array; the source is skipped, others still run. */
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
34
|
+
/** Optional per-call limits, mainly so tests can exercise the caps cheaply; production uses the defaults above. */
|
|
35
|
+
export interface DiscoverOptions {
|
|
36
|
+
maxClaimsPerSource?: number;
|
|
37
|
+
maxTotalClaims?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface AggregatedHistory {
|
|
40
|
+
sellerAddress: string;
|
|
41
|
+
/** Distinct, verified claims for this seller across all sources, oldest first. */
|
|
42
|
+
count: number;
|
|
43
|
+
claims: DeliveryClaim[];
|
|
44
|
+
/** Per-source contribution accounting. */
|
|
45
|
+
sources: SourceReport[];
|
|
46
|
+
/** Completeness analysis over the aggregated set (same meaning + limits as D-006). */
|
|
47
|
+
completeness: CompletenessReport;
|
|
48
|
+
/** Fixed, factual note about what aggregation does and does not guarantee. */
|
|
49
|
+
note: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Trustless cross-installation aggregation. Reads a seller's claims from every
|
|
53
|
+
* source, de-duplicates by claimId, re-verifies each one (dropping anything
|
|
54
|
+
* that fails), and runs completeness over the survivors. No source is trusted;
|
|
55
|
+
* a source that throws is recorded and skipped, never fatal.
|
|
56
|
+
*
|
|
57
|
+
* Order note: sources are processed in the order given, purely so per-source
|
|
58
|
+
* `accepted`/`duplicates` accounting is deterministic (the first source to
|
|
59
|
+
* carry a given claimId gets the `accepted` credit; later ones counting it as
|
|
60
|
+
* a duplicate). The final `claims` array is sorted by timestamp regardless of
|
|
61
|
+
* source order, so the aggregate itself does not depend on which source is
|
|
62
|
+
* listed first.
|
|
63
|
+
*/
|
|
64
|
+
export declare function discoverDeliveryHistory(sellerAddress: string, sources: ClaimSource[], opts?: DiscoverOptions): Promise<AggregatedHistory>;
|
|
65
|
+
/**
|
|
66
|
+
* A source backed by THIS installation's local ledger. This is the one source
|
|
67
|
+
* that is already trusted in the sense that its own read path re-verifies, but
|
|
68
|
+
* discoverDeliveryHistory re-verifies it anyway (harmless, keeps the trust
|
|
69
|
+
* model uniform: no source is special).
|
|
70
|
+
*/
|
|
71
|
+
export declare function localLedgerSource(name?: string): ClaimSource;
|
|
72
|
+
/**
|
|
73
|
+
* A source over a fixed, in-memory list of claims. Used to simulate a
|
|
74
|
+
* host-independent substrate (a shared file, an EAS query result, another
|
|
75
|
+
* installation's export) in tests and the discovery fixture, without a network
|
|
76
|
+
* or a chain. Deliberately does NO verification itself, so the aggregation's
|
|
77
|
+
* own verification is what the tests actually exercise.
|
|
78
|
+
*/
|
|
79
|
+
export declare function staticSource(name: string, claims: DeliveryClaim[]): ClaimSource;
|
|
80
|
+
//# sourceMappingURL=discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAqCA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAGtE,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AASjF;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;CACjE;AAED,yFAAyF;AACzF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,uGAAuG;IACvG,QAAQ,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,QAAQ,EAAE,MAAM,CAAC;IACjB,sGAAsG;IACtG,WAAW,EAAE,MAAM,CAAC;IACpB,oFAAoF;IACpF,UAAU,EAAE,MAAM,CAAC;IACnB,2GAA2G;IAC3G,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,8GAA8G;IAC9G,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,mHAAmH;AACnH,MAAM,WAAW,eAAe;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,0CAA0C;IAC1C,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,sFAAsF;IACtF,YAAY,EAAE,kBAAkB,CAAC;IACjC,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;CACd;AAoBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,WAAW,EAAE,EACtB,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,iBAAiB,CAAC,CAiG5B;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,SAAiB,GAAG,WAAW,CAKpE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,WAAW,CAK/E"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// discovery.ts — cross-installation claim discovery (D-005).
|
|
2
|
+
//
|
|
3
|
+
// THE PROBLEM (DECISIONS.md D-005): a buyer's claim about a seller lives only
|
|
4
|
+
// in that buyer's local ledger. A different buyer, on a separate installation,
|
|
5
|
+
// cannot see it via get_delivery_history before deciding to pay the same
|
|
6
|
+
// seller. get_delivery_history is, by construction, local-only.
|
|
7
|
+
//
|
|
8
|
+
// THE INSIGHT that makes findability solvable WITHOUT a trusted index: our
|
|
9
|
+
// claims are already self-verifying (claimId is a content hash, signature
|
|
10
|
+
// recovers to buyerAddress). So discovery does not need anyone to be trusted.
|
|
11
|
+
// Claims can be published anywhere, and whoever discovers them re-verifies each
|
|
12
|
+
// one locally. A source that injects a forged claim is caught by verifyClaim;
|
|
13
|
+
// a source that omits claims is the D-006 completeness problem, unchanged and
|
|
14
|
+
// carried over here (analyzeCompleteness still runs on the aggregate). So
|
|
15
|
+
// findability and verification compose cleanly: gather from anywhere untrusted,
|
|
16
|
+
// verify everything locally.
|
|
17
|
+
//
|
|
18
|
+
// WHAT THIS MODULE IS: the trustless client-side aggregation. Given several
|
|
19
|
+
// independent SOURCES (the local ledger, and any host-independent substrate a
|
|
20
|
+
// buyer chooses to also read), it fetches claims for a seller from each,
|
|
21
|
+
// de-duplicates by claimId, RE-VERIFIES every single one regardless of which
|
|
22
|
+
// source produced it, and runs the completeness analysis over the surviving
|
|
23
|
+
// set. The result carries per-source accounting so the caller can see what
|
|
24
|
+
// each source contributed and what was rejected.
|
|
25
|
+
//
|
|
26
|
+
// WHAT THIS MODULE IS NOT, stated so it is never oversold:
|
|
27
|
+
// - It does not run, host, or endorse any index. A `ClaimSource` is an
|
|
28
|
+
// UNTRUSTED input; the trust comes entirely from re-verifying each claim.
|
|
29
|
+
// - It does not solve completeness. If a source silently omits claims, this
|
|
30
|
+
// cannot invent them; analyzeCompleteness carries over the same honest
|
|
31
|
+
// detection/limits from D-006 (catches a hidden middle claim in a chain
|
|
32
|
+
// it can see, not a hidden tail or a hidden whole buyer).
|
|
33
|
+
// - The production substrate (EAS on Base, ERC-8004) is deliberately NOT
|
|
34
|
+
// wired here. That needs gas, a live chain, and a real integrator; see
|
|
35
|
+
// DECISIONS.md D-005/D-012. This module is the substrate-agnostic seam
|
|
36
|
+
// plus a reference in-memory/static source for simulation and tests.
|
|
37
|
+
import { DeliveryClaimSchema } from "./schema.js";
|
|
38
|
+
import { verifyClaim } from "./signing.js";
|
|
39
|
+
import { claimsForSeller } from "./ledger.js";
|
|
40
|
+
import { analyzeCompleteness } from "./completeness.js";
|
|
41
|
+
// Bound the work a single untrusted source can force. Defense against VOLUME
|
|
42
|
+
// (a source returning millions of entries, or an attacker generating millions
|
|
43
|
+
// of genuinely-valid self-signed claims), not against Sybil (see the note).
|
|
44
|
+
// Overridable per call via DiscoverOptions.
|
|
45
|
+
const MAX_CLAIMS_PER_SOURCE = 10_000;
|
|
46
|
+
const MAX_TOTAL_CLAIMS = 50_000;
|
|
47
|
+
const DISCOVERY_NOTE = "Aggregated from independent, UNTRUSTED sources. Every claim shown was re-verified locally (claimId recomputed and " +
|
|
48
|
+
"signature recovered to buyerAddress), so a source cannot inject a forged claim. What verification proves is narrow: " +
|
|
49
|
+
"it proves AUTHORSHIP of the content, NOT that a real payment or delivery happened (settlementRef is not checked " +
|
|
50
|
+
"on-chain here) and NOT that buyers are distinct. So a single keypair can still flood valid positive claims to " +
|
|
51
|
+
"inflate a seller, or valid negatives to grief one; volume and Sybil are not addressed by aggregation. This solves " +
|
|
52
|
+
"findability across installations, NOT completeness: a source can still omit claims, and no aggregation can invent " +
|
|
53
|
+
"what no source reveals. The completeness field carries the same D-006 detection and limits over the aggregate. Add " +
|
|
54
|
+
"more independent sources to raise the cost of a coordinated omission; it never reaches a proof. See DECISIONS.md D-005.";
|
|
55
|
+
function lower(s) {
|
|
56
|
+
return s.toLowerCase();
|
|
57
|
+
}
|
|
58
|
+
function timestampMs(claim) {
|
|
59
|
+
return Date.parse(claim.timestamp);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Trustless cross-installation aggregation. Reads a seller's claims from every
|
|
63
|
+
* source, de-duplicates by claimId, re-verifies each one (dropping anything
|
|
64
|
+
* that fails), and runs completeness over the survivors. No source is trusted;
|
|
65
|
+
* a source that throws is recorded and skipped, never fatal.
|
|
66
|
+
*
|
|
67
|
+
* Order note: sources are processed in the order given, purely so per-source
|
|
68
|
+
* `accepted`/`duplicates` accounting is deterministic (the first source to
|
|
69
|
+
* carry a given claimId gets the `accepted` credit; later ones counting it as
|
|
70
|
+
* a duplicate). The final `claims` array is sorted by timestamp regardless of
|
|
71
|
+
* source order, so the aggregate itself does not depend on which source is
|
|
72
|
+
* listed first.
|
|
73
|
+
*/
|
|
74
|
+
export async function discoverDeliveryHistory(sellerAddress, sources, opts = {}) {
|
|
75
|
+
const maxPerSource = opts.maxClaimsPerSource ?? MAX_CLAIMS_PER_SOURCE;
|
|
76
|
+
const maxTotal = opts.maxTotalClaims ?? MAX_TOTAL_CLAIMS;
|
|
77
|
+
const byId = new Map();
|
|
78
|
+
const sourceReports = [];
|
|
79
|
+
for (const source of sources) {
|
|
80
|
+
let fetched;
|
|
81
|
+
try {
|
|
82
|
+
fetched = await source.fetchForSeller(sellerAddress);
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
sourceReports.push({ name: source.name, fetched: 0, accepted: 0, rejected: 0, wrongSeller: 0, duplicates: 0, error: e.message });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
// An untrusted source may RESOLVE (not reject) to anything, including a
|
|
89
|
+
// non-array: malformed JSON from a real HTTP/EAS source, or a hostile one
|
|
90
|
+
// trying to abort the whole aggregation. Treat that as a per-source error
|
|
91
|
+
// and keep going. Without this guard the `for...of` below throws
|
|
92
|
+
// "not iterable", rejects discoverDeliveryHistory entirely, and censors
|
|
93
|
+
// every other source including the local ledger — defeating the very
|
|
94
|
+
// fault-isolation this function promises.
|
|
95
|
+
if (!Array.isArray(fetched)) {
|
|
96
|
+
sourceReports.push({ name: source.name, fetched: 0, accepted: 0, rejected: 0, wrongSeller: 0, duplicates: 0, error: "source did not return an array" });
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
let accepted = 0;
|
|
100
|
+
let rejected = 0;
|
|
101
|
+
let wrongSeller = 0;
|
|
102
|
+
let duplicates = 0;
|
|
103
|
+
// Bound the work this source can force: examine at most maxPerSource
|
|
104
|
+
// entries, and stop growing the aggregate past maxTotal. A source that
|
|
105
|
+
// returns more is marked `truncated` rather than allowed to exhaust
|
|
106
|
+
// CPU/memory on untrusted input.
|
|
107
|
+
const limit = Math.min(fetched.length, maxPerSource);
|
|
108
|
+
const truncated = fetched.length > maxPerSource;
|
|
109
|
+
for (let i = 0; i < limit; i++) {
|
|
110
|
+
if (byId.size >= maxTotal)
|
|
111
|
+
break;
|
|
112
|
+
const claim = fetched[i];
|
|
113
|
+
// Re-verify EVERY claim, from EVERY source, no exceptions: this is the
|
|
114
|
+
// entire trust model. A source is just bytes until verifyClaim passes.
|
|
115
|
+
let verdict;
|
|
116
|
+
try {
|
|
117
|
+
verdict = verifyClaim(claim);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// computeClaimId can throw on pathological content (e.g. promisedSpec
|
|
121
|
+
// past MAX_DEPTH); treat exactly like a failed verification.
|
|
122
|
+
rejected++;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!verdict.ok) {
|
|
126
|
+
rejected++;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
// Valid, but about a DIFFERENT seller: honest-but-off-topic, tracked
|
|
130
|
+
// separately from cryptographic rejections so an honest source that
|
|
131
|
+
// simply returns other sellers' claims is not mislabeled as feeding
|
|
132
|
+
// forgeries (case-insensitive, matching ledger semantics).
|
|
133
|
+
if (lower(claim.sellerAddress) !== lower(sellerAddress)) {
|
|
134
|
+
wrongSeller++;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const key = lower(claim.claimId);
|
|
138
|
+
if (byId.has(key)) {
|
|
139
|
+
duplicates++;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
// Store the schema-parsed form so no unverified, attacker-attached
|
|
143
|
+
// top-level key rides into the result. verifyClaim already proved the
|
|
144
|
+
// content hashes to claimId; parsing strips anything outside the signed
|
|
145
|
+
// shape. It cannot fail here (the claim already passed verifyClaim,
|
|
146
|
+
// whose computeClaimId parses the same content), but guard anyway since
|
|
147
|
+
// the input is untrusted.
|
|
148
|
+
let parsed;
|
|
149
|
+
try {
|
|
150
|
+
parsed = DeliveryClaimSchema.parse(claim);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
rejected++;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
byId.set(key, parsed);
|
|
157
|
+
accepted++;
|
|
158
|
+
}
|
|
159
|
+
const report = { name: source.name, fetched: fetched.length, accepted, rejected, wrongSeller, duplicates };
|
|
160
|
+
if (truncated)
|
|
161
|
+
report.truncated = true;
|
|
162
|
+
sourceReports.push(report);
|
|
163
|
+
}
|
|
164
|
+
const claims = [...byId.values()].sort((a, b) => timestampMs(a) - timestampMs(b));
|
|
165
|
+
return {
|
|
166
|
+
sellerAddress,
|
|
167
|
+
count: claims.length,
|
|
168
|
+
claims,
|
|
169
|
+
sources: sourceReports,
|
|
170
|
+
completeness: analyzeCompleteness(claims),
|
|
171
|
+
note: DISCOVERY_NOTE,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* A source backed by THIS installation's local ledger. This is the one source
|
|
176
|
+
* that is already trusted in the sense that its own read path re-verifies, but
|
|
177
|
+
* discoverDeliveryHistory re-verifies it anyway (harmless, keeps the trust
|
|
178
|
+
* model uniform: no source is special).
|
|
179
|
+
*/
|
|
180
|
+
export function localLedgerSource(name = "local-ledger") {
|
|
181
|
+
return {
|
|
182
|
+
name,
|
|
183
|
+
fetchForSeller: (sellerAddress) => claimsForSeller(sellerAddress),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* A source over a fixed, in-memory list of claims. Used to simulate a
|
|
188
|
+
* host-independent substrate (a shared file, an EAS query result, another
|
|
189
|
+
* installation's export) in tests and the discovery fixture, without a network
|
|
190
|
+
* or a chain. Deliberately does NO verification itself, so the aggregation's
|
|
191
|
+
* own verification is what the tests actually exercise.
|
|
192
|
+
*/
|
|
193
|
+
export function staticSource(name, claims) {
|
|
194
|
+
return {
|
|
195
|
+
name,
|
|
196
|
+
fetchForSeller: async (sellerAddress) => claims.filter((c) => lower(c.sellerAddress) === lower(sellerAddress)),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}
|
package/dist/eas.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ethers } from "ethers";
|
|
2
|
+
import { type DeliveryClaim } from "./schema.js";
|
|
3
|
+
import { verifyClaim } from "./signing.js";
|
|
4
|
+
import type { ClaimSource } from "./discovery.js";
|
|
5
|
+
export declare const EAS_ADDRESS = "0x4200000000000000000000000000000000000021";
|
|
6
|
+
export declare const SCHEMA_REGISTRY_ADDRESS = "0x4200000000000000000000000000000000000020";
|
|
7
|
+
export declare const EAS_EXPLORER: {
|
|
8
|
+
readonly 8453: "https://base.easscan.org";
|
|
9
|
+
readonly 84532: "https://base-sepolia.easscan.org";
|
|
10
|
+
};
|
|
11
|
+
export declare const SCHEMA_STRING = "bytes32 claimId,string claim";
|
|
12
|
+
/** Deterministic schema UID: keccak256(abi.encodePacked(schema, resolver, revocable)). */
|
|
13
|
+
export declare function computeSchemaUID(schema?: string, resolver?: string, revocable?: boolean): string;
|
|
14
|
+
export declare const SCHEMA_UID: string;
|
|
15
|
+
/** ABI-encode a claim into EAS attestation data for SCHEMA_STRING. */
|
|
16
|
+
export declare function encodeClaimData(claim: DeliveryClaim): string;
|
|
17
|
+
/**
|
|
18
|
+
* Decode EAS attestation data back into a claim. Returns null if the data does
|
|
19
|
+
* not decode or the JSON does not parse into our claim shape. NEVER trusts the
|
|
20
|
+
* result: the caller (easSource / discoverDeliveryHistory) re-verifies with
|
|
21
|
+
* verifyClaim. The on-chain bytes32 claimId is cross-checked against the JSON's
|
|
22
|
+
* own claimId, so a mismatch is dropped here as malformed.
|
|
23
|
+
*/
|
|
24
|
+
export declare function decodeClaimData(data: string): DeliveryClaim | null;
|
|
25
|
+
/**
|
|
26
|
+
* Register our schema if it does not already exist on this chain. The schema
|
|
27
|
+
* UID is deterministic, so a duplicate register() reverts with AlreadyExists;
|
|
28
|
+
* we check first via getSchema and only register when absent. Requires a
|
|
29
|
+
* funded signer. Returns the schema UID.
|
|
30
|
+
*/
|
|
31
|
+
export declare function ensureSchema(signer: ethers.Signer): Promise<string>;
|
|
32
|
+
export interface PublishResult {
|
|
33
|
+
uid: string;
|
|
34
|
+
txHash: string;
|
|
35
|
+
recipient: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Publish one claim to EAS as an attestation with recipient = sellerAddress, so
|
|
39
|
+
* it is discoverable by that seller. The attester is the signer (normally the
|
|
40
|
+
* buyer who signed the claim). Requires a funded signer. Returns the new
|
|
41
|
+
* attestation UID and tx hash.
|
|
42
|
+
*/
|
|
43
|
+
export declare function publishClaim(signer: ethers.Signer, claim: DeliveryClaim): Promise<PublishResult>;
|
|
44
|
+
/**
|
|
45
|
+
* The minimal on-chain read surface easSource needs. Split out as an injection
|
|
46
|
+
* seam so the decode/aggregation path is unit-testable without a live chain
|
|
47
|
+
* (same pattern as erc8004.ts's ContractFactory). Real implementation reads
|
|
48
|
+
* Attested logs filtered by recipient + schema, then getAttestation per uid.
|
|
49
|
+
*/
|
|
50
|
+
export interface AttestationReader {
|
|
51
|
+
/** UIDs of attestations of our schema whose recipient == seller. */
|
|
52
|
+
uidsForSeller(sellerAddress: string): Promise<string[]>;
|
|
53
|
+
/** The raw ABI-encoded `data` field of one attestation. */
|
|
54
|
+
dataForUid(uid: string): Promise<string>;
|
|
55
|
+
}
|
|
56
|
+
/** Build a real AttestationReader over a JSON-RPC endpoint (getLogs + getAttestation). */
|
|
57
|
+
export declare function rpcAttestationReader(rpcUrl: string, opts?: {
|
|
58
|
+
fromBlock?: number;
|
|
59
|
+
schemaUID?: string;
|
|
60
|
+
}): AttestationReader;
|
|
61
|
+
/**
|
|
62
|
+
* An untrusted ClaimSource backed by EAS. fetchForSeller reads the seller's
|
|
63
|
+
* attestation UIDs, decodes each back into a claim, and returns them. It does
|
|
64
|
+
* NOT verify (discoverDeliveryHistory does that for every source uniformly); it
|
|
65
|
+
* only drops entries that fail to decode into our claim shape. A malformed or
|
|
66
|
+
* hostile attestation therefore either fails to decode here or fails
|
|
67
|
+
* verifyClaim in discovery, never reaching the result as genuine.
|
|
68
|
+
*/
|
|
69
|
+
export declare function easSource(reader: AttestationReader, name?: string): ClaimSource;
|
|
70
|
+
/** Convenience: an EAS-backed source directly from an RPC URL. */
|
|
71
|
+
export declare function easSourceFromRpc(rpcUrl: string, opts?: {
|
|
72
|
+
fromBlock?: number;
|
|
73
|
+
schemaUID?: string;
|
|
74
|
+
name?: string;
|
|
75
|
+
}): ClaimSource;
|
|
76
|
+
export { verifyClaim };
|
|
77
|
+
//# sourceMappingURL=eas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eas.d.ts","sourceRoot":"","sources":["../src/eas.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGlD,eAAO,MAAM,WAAW,+CAA+C,CAAC;AACxE,eAAO,MAAM,uBAAuB,+CAA+C,CAAC;AAEpF,eAAO,MAAM,YAAY;;;CAGf,CAAC;AAqCX,eAAO,MAAM,aAAa,iCAAiC,CAAC;AAG5D,0FAA0F;AAC1F,wBAAgB,gBAAgB,CAAC,MAAM,SAAgB,EAAE,QAAQ,SAAqB,EAAE,SAAS,UAAmB,GAAG,MAAM,CAE5H;AAED,eAAO,MAAM,UAAU,QAAqB,CAAC;AAI7C,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAE5D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAgBlE;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAUzE;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CA6BtG;AAMD;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACxD,2DAA2D;IAC3D,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1C;AAED,0FAA0F;AAC1F,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,iBAAiB,CAqB7H;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,iBAAiB,EAAE,IAAI,SAAQ,GAAG,WAAW,CAmB9E;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,WAAW,CAElI;AAID,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/eas.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// eas.ts — publish a delivery claim to, and discover it from, the Ethereum
|
|
2
|
+
// Attestation Service (EAS) on Base. This is the concrete, host-independent
|
|
3
|
+
// substrate D-005/D-012 pointed at: a public chain anyone can read, so a buyer
|
|
4
|
+
// on one installation can find a claim another buyer published, and verify it
|
|
5
|
+
// locally (discovery.ts re-verifies every claim, so EAS is just an untrusted
|
|
6
|
+
// ClaimSource like any other).
|
|
7
|
+
//
|
|
8
|
+
// Plain ethers only, no eas-sdk dependency. EAS is an OP-Stack predeploy, so
|
|
9
|
+
// the contract addresses are identical on Base mainnet (8453) and Base Sepolia
|
|
10
|
+
// (84532). Everything here is caller-parameterised (rpcUrl, signer): this
|
|
11
|
+
// package never bundles an RPC, a key, or gas, and never becomes the index.
|
|
12
|
+
//
|
|
13
|
+
// Verified against the EAS contract sources (IEAS.sol, Common.sol,
|
|
14
|
+
// SchemaRegistry.sol) and the base / base-sepolia deployment artifacts, 2026-09-06.
|
|
15
|
+
import { ethers } from "ethers";
|
|
16
|
+
import { DeliveryClaimSchema } from "./schema.js";
|
|
17
|
+
import { verifyClaim } from "./signing.js";
|
|
18
|
+
// OP-Stack predeploys: same on every Base network.
|
|
19
|
+
export const EAS_ADDRESS = "0x4200000000000000000000000000000000000021";
|
|
20
|
+
export const SCHEMA_REGISTRY_ADDRESS = "0x4200000000000000000000000000000000000020";
|
|
21
|
+
export const EAS_EXPLORER = {
|
|
22
|
+
8453: "https://base.easscan.org",
|
|
23
|
+
84532: "https://base-sepolia.easscan.org",
|
|
24
|
+
};
|
|
25
|
+
const EAS_ABI = [
|
|
26
|
+
"function attest((bytes32 schema,(address recipient,uint64 expirationTime,bool revocable,bytes32 refUID,bytes data,uint256 value) data) request) payable returns (bytes32)",
|
|
27
|
+
"function getAttestation(bytes32 uid) view returns ((bytes32 uid,bytes32 schema,uint64 time,uint64 expirationTime,uint64 revocationTime,bytes32 refUID,address recipient,address attester,bool revocable,bytes data))",
|
|
28
|
+
"event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID)",
|
|
29
|
+
];
|
|
30
|
+
const SCHEMA_REGISTRY_ABI = [
|
|
31
|
+
"function register(string schema, address resolver, bool revocable) returns (bytes32)",
|
|
32
|
+
"function getSchema(bytes32 uid) view returns ((bytes32 uid,address resolver,bool revocable,string schema))",
|
|
33
|
+
];
|
|
34
|
+
// Our schema. We carry the WHOLE signed claim as JSON in one field, so a
|
|
35
|
+
// discovered attestation is fully self-verifying offline (decode -> parse ->
|
|
36
|
+
// verifyClaim), with no second lookup anywhere. claimId is also stored as a
|
|
37
|
+
// bytes32 for a cheap on-chain cross-reference. Types + order are what matter
|
|
38
|
+
// for ABI-encoding; the names are metadata.
|
|
39
|
+
export const SCHEMA_STRING = "bytes32 claimId,string claim";
|
|
40
|
+
const SCHEMA_REVOCABLE = true;
|
|
41
|
+
/** Deterministic schema UID: keccak256(abi.encodePacked(schema, resolver, revocable)). */
|
|
42
|
+
export function computeSchemaUID(schema = SCHEMA_STRING, resolver = ethers.ZeroAddress, revocable = SCHEMA_REVOCABLE) {
|
|
43
|
+
return ethers.keccak256(ethers.solidityPacked(["string", "address", "bool"], [schema, resolver, revocable]));
|
|
44
|
+
}
|
|
45
|
+
export const SCHEMA_UID = computeSchemaUID();
|
|
46
|
+
const coder = ethers.AbiCoder.defaultAbiCoder();
|
|
47
|
+
/** ABI-encode a claim into EAS attestation data for SCHEMA_STRING. */
|
|
48
|
+
export function encodeClaimData(claim) {
|
|
49
|
+
return coder.encode(["bytes32", "string"], [claim.claimId, JSON.stringify(claim)]);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Decode EAS attestation data back into a claim. Returns null if the data does
|
|
53
|
+
* not decode or the JSON does not parse into our claim shape. NEVER trusts the
|
|
54
|
+
* result: the caller (easSource / discoverDeliveryHistory) re-verifies with
|
|
55
|
+
* verifyClaim. The on-chain bytes32 claimId is cross-checked against the JSON's
|
|
56
|
+
* own claimId, so a mismatch is dropped here as malformed.
|
|
57
|
+
*/
|
|
58
|
+
export function decodeClaimData(data) {
|
|
59
|
+
let claimId;
|
|
60
|
+
let json;
|
|
61
|
+
try {
|
|
62
|
+
[claimId, json] = coder.decode(["bytes32", "string"], data);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
let parsed;
|
|
68
|
+
try {
|
|
69
|
+
parsed = DeliveryClaimSchema.parse(JSON.parse(json));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (parsed.claimId.toLowerCase() !== claimId.toLowerCase())
|
|
75
|
+
return null;
|
|
76
|
+
return parsed;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Register our schema if it does not already exist on this chain. The schema
|
|
80
|
+
* UID is deterministic, so a duplicate register() reverts with AlreadyExists;
|
|
81
|
+
* we check first via getSchema and only register when absent. Requires a
|
|
82
|
+
* funded signer. Returns the schema UID.
|
|
83
|
+
*/
|
|
84
|
+
export async function ensureSchema(signer) {
|
|
85
|
+
const registry = new ethers.Contract(SCHEMA_REGISTRY_ADDRESS, SCHEMA_REGISTRY_ABI, signer);
|
|
86
|
+
const existing = await registry.getSchema(SCHEMA_UID);
|
|
87
|
+
// getSchema returns a zero-uid struct when the schema is not registered.
|
|
88
|
+
if (existing && existing.uid && existing.uid.toLowerCase() === SCHEMA_UID.toLowerCase()) {
|
|
89
|
+
return SCHEMA_UID;
|
|
90
|
+
}
|
|
91
|
+
const tx = await registry.register(SCHEMA_STRING, ethers.ZeroAddress, SCHEMA_REVOCABLE);
|
|
92
|
+
await tx.wait();
|
|
93
|
+
return SCHEMA_UID;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Publish one claim to EAS as an attestation with recipient = sellerAddress, so
|
|
97
|
+
* it is discoverable by that seller. The attester is the signer (normally the
|
|
98
|
+
* buyer who signed the claim). Requires a funded signer. Returns the new
|
|
99
|
+
* attestation UID and tx hash.
|
|
100
|
+
*/
|
|
101
|
+
export async function publishClaim(signer, claim) {
|
|
102
|
+
const eas = new ethers.Contract(EAS_ADDRESS, EAS_ABI, signer);
|
|
103
|
+
const tx = await eas.attest({
|
|
104
|
+
schema: SCHEMA_UID,
|
|
105
|
+
data: {
|
|
106
|
+
recipient: claim.sellerAddress,
|
|
107
|
+
expirationTime: 0n,
|
|
108
|
+
revocable: SCHEMA_REVOCABLE,
|
|
109
|
+
refUID: ethers.ZeroHash,
|
|
110
|
+
data: encodeClaimData(claim),
|
|
111
|
+
value: 0n,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
const receipt = await tx.wait();
|
|
115
|
+
if (!receipt)
|
|
116
|
+
throw new Error("attest transaction had no receipt (dropped or replaced)");
|
|
117
|
+
const iface = new ethers.Interface(EAS_ABI);
|
|
118
|
+
let uid = "";
|
|
119
|
+
for (const log of receipt.logs) {
|
|
120
|
+
try {
|
|
121
|
+
const parsed = iface.parseLog(log);
|
|
122
|
+
if (parsed && parsed.name === "Attested") {
|
|
123
|
+
uid = parsed.args.uid;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// not an EAS log, skip
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return { uid, txHash: receipt.hash, recipient: claim.sellerAddress };
|
|
132
|
+
}
|
|
133
|
+
/** Build a real AttestationReader over a JSON-RPC endpoint (getLogs + getAttestation). */
|
|
134
|
+
export function rpcAttestationReader(rpcUrl, opts = {}) {
|
|
135
|
+
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
136
|
+
const eas = new ethers.Contract(EAS_ADDRESS, EAS_ABI, provider);
|
|
137
|
+
const schemaUID = opts.schemaUID ?? SCHEMA_UID;
|
|
138
|
+
const attestedTopic0 = ethers.id("Attested(address,address,bytes32,bytes32)");
|
|
139
|
+
return {
|
|
140
|
+
uidsForSeller: async (sellerAddress) => {
|
|
141
|
+
const logs = await provider.getLogs({
|
|
142
|
+
address: EAS_ADDRESS,
|
|
143
|
+
topics: [attestedTopic0, ethers.zeroPadValue(sellerAddress, 32), null, schemaUID],
|
|
144
|
+
fromBlock: opts.fromBlock ?? 0,
|
|
145
|
+
toBlock: "latest",
|
|
146
|
+
});
|
|
147
|
+
// uid is the only non-indexed value: it sits in log.data.
|
|
148
|
+
return logs.map((l) => l.data);
|
|
149
|
+
},
|
|
150
|
+
dataForUid: async (uid) => {
|
|
151
|
+
const att = await eas.getAttestation(uid);
|
|
152
|
+
return att.data;
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* An untrusted ClaimSource backed by EAS. fetchForSeller reads the seller's
|
|
158
|
+
* attestation UIDs, decodes each back into a claim, and returns them. It does
|
|
159
|
+
* NOT verify (discoverDeliveryHistory does that for every source uniformly); it
|
|
160
|
+
* only drops entries that fail to decode into our claim shape. A malformed or
|
|
161
|
+
* hostile attestation therefore either fails to decode here or fails
|
|
162
|
+
* verifyClaim in discovery, never reaching the result as genuine.
|
|
163
|
+
*/
|
|
164
|
+
export function easSource(reader, name = "eas") {
|
|
165
|
+
return {
|
|
166
|
+
name,
|
|
167
|
+
fetchForSeller: async (sellerAddress) => {
|
|
168
|
+
const uids = await reader.uidsForSeller(sellerAddress);
|
|
169
|
+
const claims = [];
|
|
170
|
+
for (const uid of uids) {
|
|
171
|
+
let data;
|
|
172
|
+
try {
|
|
173
|
+
data = await reader.dataForUid(uid);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
continue; // unreadable attestation, skip
|
|
177
|
+
}
|
|
178
|
+
const claim = decodeClaimData(data);
|
|
179
|
+
if (claim)
|
|
180
|
+
claims.push(claim);
|
|
181
|
+
}
|
|
182
|
+
return claims;
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/** Convenience: an EAS-backed source directly from an RPC URL. */
|
|
187
|
+
export function easSourceFromRpc(rpcUrl, opts = {}) {
|
|
188
|
+
return easSource(rpcAttestationReader(rpcUrl, opts), opts.name ?? "eas");
|
|
189
|
+
}
|
|
190
|
+
// Re-export so callers see verifyClaim is what makes an EAS-sourced claim
|
|
191
|
+
// trustworthy, not EAS itself.
|
|
192
|
+
export { verifyClaim };
|
|
193
|
+
//# sourceMappingURL=eas.js.map
|
package/dist/eas.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eas.js","sourceRoot":"","sources":["../src/eas.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "capacity-attest",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "MCP server for capacity-delivery attestations: after an x402 payment for compute capacity (GPU-hours, storage, API credits, bandwidth), the paying agent leaves a signed, factual claim of what was actually delivered, so other agents can check a seller's delivery history before paying. No reputation score, no judgment, no financial instrument.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -40,7 +40,9 @@
|
|
|
40
40
|
"test": "vitest run",
|
|
41
41
|
"start": "node dist/index.js",
|
|
42
42
|
"demo": "tsx examples/demo.ts",
|
|
43
|
-
"fixture": "tsx examples/completeness-fixture.ts"
|
|
43
|
+
"fixture": "tsx examples/completeness-fixture.ts",
|
|
44
|
+
"discovery-fixture": "tsx examples/discovery-fixture.ts",
|
|
45
|
+
"eas-demo": "tsx examples/eas-live-demo.ts"
|
|
44
46
|
},
|
|
45
47
|
"dependencies": {
|
|
46
48
|
"@modelcontextprotocol/sdk": "^1.30.0",
|