secretvm-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 ADDED
@@ -0,0 +1,312 @@
1
+ # secretvm-verify
2
+
3
+ Attestation verification SDK for confidential computing environments. Verifies Intel TDX, AMD SEV-SNP, and NVIDIA GPU attestation quotes, with end-to-end Secret VM verification that validates CPU attestation, GPU attestation, and the cryptographic bindings between them.
4
+
5
+ ## What it verifies
6
+
7
+ - **Intel TDX** — Parses a TDX Quote v4, verifies the ECDSA-P256 signature chain (PCK -> Intermediate -> Root), validates QE report binding, and checks TCB status against Intel's Provisioning Certification Service.
8
+ - **AMD SEV-SNP** — Parses a SEV-SNP attestation report, fetches the VCEK certificate from AMD's Key Distribution Service, verifies the ECDSA-P384 report signature, and validates the certificate chain (VCEK -> ASK -> ARK).
9
+ - **NVIDIA GPU** — Submits GPU attestation evidence to NVIDIA's Remote Attestation Service (NRAS), verifies the returned JWT signatures against NVIDIA's published JWKS keys, and extracts per-GPU attestation claims.
10
+ - **SecretVM workload** — Given a TDX or SEV-SNP quote and a `docker-compose.yaml`, determines whether the quote was produced by a known SecretVM image and verifies the exact compose file that was booted.
11
+ - **Secret VM** — End-to-end verification that connects to a VM's attestation endpoints, verifies CPU and GPU attestation, and validates TLS and GPU cryptographic bindings.
12
+ - **ERC-8004 Agent verification** — End-to-end verification of on-chain AI agents registered under the [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) standard. Resolves agent metadata from any supported blockchain (Ethereum, Base, Arbitrum, Polygon, and 14 more), discovers the agent's TEE attestation endpoints, and runs the full verification flow. Three composable functions:
13
+ - **`resolveAgent`** — Queries the on-chain registry contract for the agent's metadata.
14
+ - **`verifyAgent`** — Takes agent metadata and runs full TEE verification against the agent's declared endpoints.
15
+ - **`checkAgent`** — End-to-end: resolves the agent on-chain, then verifies it.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install secretvm-verify
21
+ ```
22
+
23
+ ## Quick start
24
+
25
+ ### Verify a Secret VM (recommended)
26
+
27
+ The simplest way to verify a VM — handles CPU detection, GPU detection, and all binding checks automatically.
28
+
29
+ ```typescript
30
+ import { checkSecretVm } from 'secretvm-verify';
31
+
32
+ const result = await checkSecretVm('my-vm.example.com');
33
+
34
+ console.log(result.valid); // true if all checks pass
35
+ console.log(result.attestationType); // "SECRET-VM"
36
+ console.log(result.checks); // { tls_cert_obtained: true, cpu_attestation_valid: true, ... }
37
+ console.log(result.report); // { tls_fingerprint: "...", cpu: {...}, cpu_type: "TDX", ... }
38
+ console.log(result.errors); // [] if no errors
39
+ ```
40
+
41
+ ### Verify an ERC-8004 agent
42
+
43
+ Verify an AI agent registered on-chain using the ERC-8004 standard. Supports 18 chains including Ethereum, Base, Arbitrum, and more.
44
+
45
+ ```typescript
46
+ import { checkAgent } from 'secretvm-verify';
47
+
48
+ // End-to-end: resolve on-chain + verify TEE attestation
49
+ const result = await checkAgent(38114, 'base');
50
+
51
+ console.log(result.valid); // true if all checks pass
52
+ console.log(result.attestationType); // "ERC-8004"
53
+ console.log(result.checks); // { agent_resolved: true, metadata_valid: true, ... }
54
+ ```
55
+
56
+ You can also work with the individual steps:
57
+
58
+ ```typescript
59
+ import { resolveAgent, verifyAgent } from 'secretvm-verify';
60
+
61
+ // Step 1: Resolve agent metadata from the blockchain
62
+ const metadata = await resolveAgent(38114, 'base');
63
+ console.log(metadata.name); // Agent name
64
+ console.log(metadata.services); // [{ name: "teequote", endpoint: "..." }, ...]
65
+ console.log(metadata.supportedTrust); // ["tee-attestation"]
66
+
67
+ // Step 2: Verify the agent's TEE attestation
68
+ const result = await verifyAgent(metadata);
69
+ ```
70
+
71
+ **RPC configuration:** Set `SECRETVM_RPC_BASE` (or `SECRETVM_RPC_<CHAIN>`) environment variable to use your own RPC endpoint. Falls back to public RPCs if not set.
72
+
73
+ ### Resolve SecretVM version from a quote
74
+
75
+ Given a TDX or SEV-SNP quote, determine which official SecretVM template and version produced it:
76
+
77
+ ```typescript
78
+ import { resolveSecretVmVersion } from 'secretvm-verify';
79
+ import { readFileSync } from 'fs';
80
+
81
+ const result = resolveSecretVmVersion(readFileSync('cpu_quote.txt', 'utf8'));
82
+ if (result) {
83
+ console.log(result.template_name); // e.g. "small"
84
+ console.log(result.artifacts_ver); // e.g. "v0.0.25"
85
+ } else {
86
+ console.log('Not a known SecretVM');
87
+ }
88
+ ```
89
+
90
+ ### Verify a workload (quote + docker-compose)
91
+
92
+ Verify that a quote was produced by a known SecretVM *and* that it was running a specific `docker-compose.yaml`:
93
+
94
+ ```typescript
95
+ import { verifyWorkload, formatWorkloadResult } from 'secretvm-verify';
96
+ import { readFileSync } from 'fs';
97
+
98
+ // Auto-detects TDX vs SEV-SNP:
99
+ const result = verifyWorkload(
100
+ readFileSync('cpu_quote.txt', 'utf8'),
101
+ readFileSync('docker-compose.yaml', 'utf8'),
102
+ );
103
+
104
+ console.log(result.status); // "authentic_match" | "authentic_mismatch" | "not_authentic"
105
+ console.log(result.template_name); // e.g. "small" (undefined when not_authentic)
106
+ console.log(result.artifacts_ver); // e.g. "v0.0.25" (undefined when not_authentic)
107
+ console.log(result.env); // e.g. "prod" (undefined when not_authentic)
108
+ console.log(formatWorkloadResult(result)); // human-readable summary
109
+ ```
110
+
111
+ ### Verify a CPU quote (auto-detect TDX vs SEV-SNP)
112
+
113
+ ```typescript
114
+ import { checkCpuAttestation } from 'secretvm-verify';
115
+ import { readFileSync } from 'fs';
116
+
117
+ const result = await checkCpuAttestation(readFileSync('cpu_quote.txt', 'utf8'));
118
+
119
+ console.log(result.attestationType); // "TDX" or "SEV-SNP"
120
+ console.log(result.valid);
121
+ ```
122
+
123
+ ## API reference
124
+
125
+ ### `AttestationResult`
126
+
127
+ All functions return an `AttestationResult` with these fields:
128
+
129
+ | Field | Type | Description |
130
+ |-------|------|-------------|
131
+ | `valid` | `boolean` | Overall pass/fail |
132
+ | `attestationType` | `string` | `"TDX"`, `"SEV-SNP"`, `"NVIDIA-GPU"`, or `"SECRET-VM"` |
133
+ | `checks` | `Record<string, boolean>` | Individual verification steps |
134
+ | `report` | `Record<string, any>` | Parsed attestation fields |
135
+ | `errors` | `string[]` | Error messages for failed checks |
136
+
137
+ ### Functions
138
+
139
+ #### `checkSecretVm(url, product?)`
140
+
141
+ End-to-end Secret VM verification. Connects to `<url>:29343`, fetches CPU and GPU quotes, verifies both, and checks TLS and GPU bindings.
142
+
143
+ **Parameters:**
144
+ - `url` — VM address (e.g., `"my-vm.example.com"`, `"https://my-vm:29343"`)
145
+ - `product` — AMD product name (`"Genoa"`, `"Milan"`, `"Turin"`). Only needed for SEV-SNP, auto-detected if omitted.
146
+
147
+ #### `checkCpuAttestation(data, product?)`
148
+
149
+ Auto-detects Intel TDX vs AMD SEV-SNP and delegates to the appropriate function.
150
+
151
+ #### `checkTdxCpuAttestation(data)`
152
+
153
+ Verifies an Intel TDX Quote v4.
154
+
155
+ #### `checkSevCpuAttestation(data, product?)`
156
+
157
+ Verifies an AMD SEV-SNP attestation report.
158
+
159
+ #### `checkNvidiaGpuAttestation(data)`
160
+
161
+ Verifies NVIDIA GPU attestation via NRAS.
162
+
163
+ #### `resolveSecretVmVersion(data)`
164
+
165
+ Looks up a quote in the SecretVM artifact registry. Returns the matching template name and version, or `null` if not found.
166
+
167
+ #### `verifyWorkload(data, dockerComposeYaml)`
168
+
169
+ Auto-detects quote type and verifies that it was produced by a known SecretVM running the given docker-compose.
170
+
171
+ #### `verifyTdxWorkload(data, dockerComposeYaml)`
172
+
173
+ TDX-specific workload verification.
174
+
175
+ #### `verifySevWorkload(data, dockerComposeYaml)`
176
+
177
+ SEV-SNP-specific workload verification.
178
+
179
+ #### `formatWorkloadResult(result)`
180
+
181
+ Formats a `WorkloadResult` as a human-readable string.
182
+
183
+ ### `WorkloadResult`
184
+
185
+ | Field | Type | Description |
186
+ |-------|------|-------------|
187
+ | `status` | `WorkloadStatus` | `"authentic_match"`, `"authentic_mismatch"`, or `"not_authentic"` |
188
+ | `template_name` | `string \| undefined` | SecretVM template (e.g. `"small"`) |
189
+ | `artifacts_ver` | `string \| undefined` | Artifacts version (e.g. `"v0.0.25"`) |
190
+ | `env` | `string \| undefined` | Environment (e.g. `"prod"`) |
191
+
192
+ ### ERC-8004 Agent Functions
193
+
194
+ #### `checkAgent(agentId, chain)`
195
+
196
+ End-to-end ERC-8004 agent verification. Resolves agent metadata from the on-chain registry, then verifies TEE attestation.
197
+
198
+ **Parameters:**
199
+ - `agentId` — The agent's on-chain token ID (number)
200
+ - `chain` — Chain name (e.g. `"base"`, `"ethereum"`, `"arbitrum"`)
201
+
202
+ #### `resolveAgent(agentId, chain)`
203
+
204
+ Resolves an agent's metadata from the on-chain registry contract. Returns an `AgentMetadata` object.
205
+
206
+ #### `verifyAgent(metadata)`
207
+
208
+ Verifies an ERC-8004 agent given its metadata. Discovers teequote/workload endpoints and runs the full verification flow.
209
+
210
+ **Parameters:**
211
+ - `metadata` — `AgentMetadata` object with `name`, `supportedTrust`, and `services`
212
+
213
+ ### `AgentMetadata`
214
+
215
+ | Field | Type | Description |
216
+ |-------|------|-------------|
217
+ | `name` | `string` | Agent name |
218
+ | `description` | `string \| undefined` | Agent description |
219
+ | `supportedTrust` | `string[]` | Trust models (must include `"tee-attestation"`) |
220
+ | `services` | `AgentService[]` | Service endpoints (`name` + `endpoint`) |
221
+
222
+ ### RPC Configuration
223
+
224
+ An RPC URL **must** be provided via environment variable to use the ERC-8004 agent functions. No default RPCs are shipped with the package.
225
+
226
+ Set one of:
227
+ - `SECRETVM_RPC_<CHAIN>` — chain-specific (e.g. `SECRETVM_RPC_BASE`, `SECRETVM_RPC_ETHEREUM`)
228
+ - `SECRETVM_RPC_URL` — generic fallback for all chains
229
+
230
+ Example:
231
+ ```bash
232
+ export SECRETVM_RPC_BASE="https://base-mainnet.g.alchemy.com/v2/YOUR_KEY"
233
+ ```
234
+
235
+ Supported chains: ethereum, base, arbitrum, sepolia, polygon, bnb, gnosis, linea, taiko, celo, avalanche, optimism, abstract, megaeth, mantle, soneium, xlayer, metis.
236
+
237
+ ## CLI usage
238
+
239
+ Install globally:
240
+
241
+ ```bash
242
+ npm install -g secretvm-verify
243
+ ```
244
+
245
+ Or from the repo:
246
+
247
+ ```bash
248
+ cd node
249
+ npm install && npm run build
250
+ npm install -g .
251
+ ```
252
+
253
+ Then use from anywhere:
254
+
255
+ ```bash
256
+ # Verify a Secret VM (CPU + GPU + TLS binding)
257
+ secretvm-verify --secretvm yellow-krill.vm.scrtlabs.com
258
+
259
+ # Verify individual attestation quotes from files
260
+ secretvm-verify --tdx cpu_quote.txt
261
+ secretvm-verify --sev amd_cpu_quote.txt --product Genoa
262
+ secretvm-verify --gpu gpu_attest.txt
263
+
264
+ # Auto-detect CPU quote type (TDX vs SEV-SNP)
265
+ secretvm-verify --cpu cpu_quote.txt
266
+
267
+ # Resolve which SecretVM version produced a quote
268
+ secretvm-verify --resolve-version cpu_quote.txt
269
+ secretvm-verify -rv cpu_quote.txt
270
+
271
+ # Verify a quote + docker-compose match
272
+ secretvm-verify --verify-workload cpu_quote.txt --compose docker-compose.yaml
273
+ secretvm-verify -vw cpu_quote.txt --compose docker-compose.yaml
274
+
275
+ # JSON output (any command)
276
+ secretvm-verify --secretvm yellow-krill.vm.scrtlabs.com --raw
277
+
278
+ # Verbose output (all attestation fields)
279
+ secretvm-verify --secretvm yellow-krill.vm.scrtlabs.com -v
280
+
281
+ # Verify an ERC-8004 agent on-chain
282
+ SECRETVM_RPC_BASE="https://..." secretvm-verify --check-agent 38114 --chain base
283
+ secretvm-verify --check-agent 38114 --chain base -v
284
+
285
+ # Verify an agent from a metadata JSON file
286
+ secretvm-verify --agent metadata.json
287
+
288
+ # A bare URL defaults to --secretvm
289
+ secretvm-verify yellow-krill.vm.scrtlabs.com
290
+ ```
291
+
292
+ ## External services
293
+
294
+ The library contacts these services during verification:
295
+
296
+ | Service | Used by | Purpose |
297
+ |---------|---------|---------|
298
+ | [Intel PCS](https://api.trustedservices.intel.com) | TDX | TCB status lookup |
299
+ | [AMD KDS](https://kdsintf.amd.com) | SEV-SNP | VCEK certificate and cert chain |
300
+ | [NVIDIA NRAS](https://nras.attestation.nvidia.com) | GPU | GPU attestation verification |
301
+
302
+ **Note:** AMD KDS has rate limits. If you encounter 429 errors, specify the `product` parameter to reduce the number of requests.
303
+
304
+ ## Requirements
305
+
306
+ - Node.js >= 18 (uses built-in `crypto`, `fetch`)
307
+ - `openssl` CLI (required for AMD SEV-SNP certificate chain verification)
308
+ - `yaml` (npm dependency, included)
309
+
310
+ ## License
311
+
312
+ MIT
@@ -0,0 +1,29 @@
1
+ import { AttestationResult } from "./types.js";
2
+ import type { AgentMetadata } from "./types.js";
3
+ /**
4
+ * Resolve an ERC-8004 agent's metadata from the on-chain registry.
5
+ *
6
+ * Queries the registry contract for the agent's tokenURI, fetches the
7
+ * metadata JSON, and returns a normalized AgentMetadata object.
8
+ *
9
+ * RPC URL resolution priority:
10
+ * 1. SECRETVM_RPC_<CHAIN> env var (e.g. SECRETVM_RPC_BASE)
11
+ * 2. SECRETVM_RPC_URL env var (generic fallback)
12
+ * 3. Default public RPC for the chain
13
+ */
14
+ export declare function resolveAgent(agentId: number, chain: string): Promise<AgentMetadata>;
15
+ /**
16
+ * Verify an ERC-8004 agent given its metadata.
17
+ *
18
+ * Discovers teequote and workload endpoints from the metadata, then runs
19
+ * the full verification flow: TLS cert, CPU quote, TLS binding, GPU quote,
20
+ * GPU binding, and workload verification.
21
+ */
22
+ export declare function verifyAgent(metadata: AgentMetadata): Promise<AttestationResult>;
23
+ /**
24
+ * End-to-end ERC-8004 agent verification.
25
+ *
26
+ * Resolves the agent's metadata from the on-chain registry, then runs
27
+ * the full verification flow via verifyAgent.
28
+ */
29
+ export declare function checkAgent(agentId: number, chain: string): Promise<AttestationResult>;
package/dist/agent.js ADDED
@@ -0,0 +1,353 @@
1
+ import crypto from "node:crypto";
2
+ import tls from "node:tls";
3
+ import { ethers } from "ethers";
4
+ import { getChainConfig, getRpcUrl } from "./chains.js";
5
+ import { makeResult } from "./types.js";
6
+ import { checkCpuAttestation } from "./cpu.js";
7
+ import { checkNvidiaGpuAttestation } from "./nvidia.js";
8
+ import { verifyWorkload } from "./workload.js";
9
+ const REGISTRY_ABI = [
10
+ "function tokenURI(uint256 tokenId) view returns (string)",
11
+ "function agentURI(uint256 agentId) view returns (string)",
12
+ ];
13
+ // ---------------------------------------------------------------------------
14
+ // Helpers
15
+ // ---------------------------------------------------------------------------
16
+ function normalizeServices(raw) {
17
+ if (!Array.isArray(raw))
18
+ return [];
19
+ return raw.map((service, index) => {
20
+ const entry = (service ?? {});
21
+ const name = typeof entry.name === "string" ? entry.name : "";
22
+ const endpoint = typeof entry.endpoint === "string" ? entry.endpoint : "";
23
+ return {
24
+ name: name || `service-${index + 1}`,
25
+ endpoint,
26
+ };
27
+ });
28
+ }
29
+ function getTlsCertFingerprint(host, port) {
30
+ return new Promise((resolve, reject) => {
31
+ const socket = tls.connect({ host, port, rejectUnauthorized: true }, () => {
32
+ const cert = socket.getPeerX509Certificate();
33
+ if (!cert) {
34
+ socket.destroy();
35
+ return reject(new Error("No certificate received"));
36
+ }
37
+ const fingerprint = crypto
38
+ .createHash("sha256")
39
+ .update(cert.raw)
40
+ .digest();
41
+ socket.destroy();
42
+ resolve(fingerprint);
43
+ });
44
+ socket.on("error", reject);
45
+ socket.setTimeout(10_000, () => {
46
+ socket.destroy();
47
+ reject(new Error("TLS connection timed out"));
48
+ });
49
+ });
50
+ }
51
+ function extractDockerCompose(raw) {
52
+ let text = raw.trim();
53
+ const preMatch = text.match(/<pre>([\s\S]*?)<\/pre>/i);
54
+ if (preMatch)
55
+ text = preMatch[1];
56
+ text = text
57
+ .replace(/&#(\d+);/g, (_, code) => String.fromCharCode(Number(code)))
58
+ .replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
59
+ .replace(/&amp;/g, "&")
60
+ .replace(/&lt;/g, "<")
61
+ .replace(/&gt;/g, ">")
62
+ .replace(/&quot;/g, '"')
63
+ .replace(/&apos;/g, "'");
64
+ text = text.replace(/[\u200B\u200C\u200D\uFEFF]/g, "");
65
+ return text;
66
+ }
67
+ function findTeequoteEndpoint(services) {
68
+ for (const s of services) {
69
+ if (s.name.toLowerCase() === "teequote" && s.endpoint)
70
+ return s.endpoint;
71
+ }
72
+ for (const s of services) {
73
+ if (s.endpoint && s.endpoint.includes(":29343"))
74
+ return s.endpoint;
75
+ }
76
+ return undefined;
77
+ }
78
+ function findWorkloadEndpoint(services) {
79
+ for (const s of services) {
80
+ if (s.name.toLowerCase() === "workload" && s.endpoint)
81
+ return s.endpoint;
82
+ }
83
+ return undefined;
84
+ }
85
+ function normalizeEndpoint(endpoint) {
86
+ if (!endpoint.startsWith("http://") && !endpoint.startsWith("https://")) {
87
+ return `https://${endpoint}`;
88
+ }
89
+ return endpoint;
90
+ }
91
+ // ---------------------------------------------------------------------------
92
+ // Public: resolveAgent
93
+ // ---------------------------------------------------------------------------
94
+ /**
95
+ * Resolve an ERC-8004 agent's metadata from the on-chain registry.
96
+ *
97
+ * Queries the registry contract for the agent's tokenURI, fetches the
98
+ * metadata JSON, and returns a normalized AgentMetadata object.
99
+ *
100
+ * RPC URL resolution priority:
101
+ * 1. SECRETVM_RPC_<CHAIN> env var (e.g. SECRETVM_RPC_BASE)
102
+ * 2. SECRETVM_RPC_URL env var (generic fallback)
103
+ * 3. Default public RPC for the chain
104
+ */
105
+ export async function resolveAgent(agentId, chain) {
106
+ const chainConfig = getChainConfig(chain);
107
+ const rpcUrl = getRpcUrl(chain);
108
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
109
+ const contract = new ethers.Contract(chainConfig.registryAddress, REGISTRY_ABI, provider);
110
+ let tokenUri;
111
+ try {
112
+ tokenUri = await contract.tokenURI(agentId);
113
+ }
114
+ catch {
115
+ try {
116
+ tokenUri = await contract.agentURI(agentId);
117
+ }
118
+ catch {
119
+ throw new Error(`Could not find tokenURI or agentURI for agent ${agentId} on ${chainConfig.name}`);
120
+ }
121
+ }
122
+ if (!tokenUri || tokenUri.trim() === "") {
123
+ throw new Error(`Registry returned empty tokenURI for agent ${agentId}`);
124
+ }
125
+ let manifest;
126
+ if (tokenUri.startsWith("data:")) {
127
+ // Handle data URIs (e.g. data:application/json;base64,...)
128
+ const encoded = tokenUri.split(",", 2)[1];
129
+ manifest = JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
130
+ }
131
+ else {
132
+ let fetchUrl = tokenUri;
133
+ if (fetchUrl.startsWith("ipfs://")) {
134
+ fetchUrl = fetchUrl.replace("ipfs://", "https://ipfs.io/ipfs/");
135
+ }
136
+ const resp = await fetch(fetchUrl);
137
+ if (!resp.ok) {
138
+ throw new Error(`Failed to fetch agent metadata from ${fetchUrl}: HTTP ${resp.status}`);
139
+ }
140
+ manifest = (await resp.json());
141
+ }
142
+ const trust = manifest.supportedTrust ??
143
+ manifest.supported_trust ??
144
+ [];
145
+ return {
146
+ name: typeof manifest.name === "string" && manifest.name.trim()
147
+ ? manifest.name
148
+ : `Agent ${agentId}`,
149
+ description: typeof manifest.description === "string" ? manifest.description : undefined,
150
+ supportedTrust: Array.isArray(trust) ? trust : [],
151
+ services: normalizeServices(manifest.services ?? manifest.endpoints),
152
+ };
153
+ }
154
+ // ---------------------------------------------------------------------------
155
+ // Public: verifyAgent
156
+ // ---------------------------------------------------------------------------
157
+ /**
158
+ * Verify an ERC-8004 agent given its metadata.
159
+ *
160
+ * Discovers teequote and workload endpoints from the metadata, then runs
161
+ * the full verification flow: TLS cert, CPU quote, TLS binding, GPU quote,
162
+ * GPU binding, and workload verification.
163
+ */
164
+ export async function verifyAgent(metadata) {
165
+ const errors = [];
166
+ const checks = {};
167
+ const report = {};
168
+ report.agent_name = metadata.name;
169
+ // 1. Validate metadata
170
+ const hasTeeAttestation = metadata.supportedTrust
171
+ .map((t) => t.toLowerCase())
172
+ .includes("tee-attestation");
173
+ if (!hasTeeAttestation) {
174
+ errors.push("Agent does not support tee-attestation");
175
+ checks.metadata_valid = false;
176
+ return makeResult("ERC-8004", { checks, report, errors });
177
+ }
178
+ const teequoteEndpoint = findTeequoteEndpoint(metadata.services);
179
+ if (!teequoteEndpoint) {
180
+ errors.push("No teequote service endpoint found in agent metadata");
181
+ checks.metadata_valid = false;
182
+ return makeResult("ERC-8004", { checks, report, errors });
183
+ }
184
+ checks.metadata_valid = true;
185
+ // 2. Derive URLs
186
+ const baseUrl = normalizeEndpoint(teequoteEndpoint).replace(/\/+$/, "");
187
+ const cpuUrl = baseUrl.endsWith("/cpu") ? baseUrl : `${baseUrl}/cpu`;
188
+ const gpuUrl = baseUrl.endsWith("/cpu")
189
+ ? baseUrl.replace(/\/cpu$/, "/gpu")
190
+ : `${baseUrl}/gpu`;
191
+ const workloadService = findWorkloadEndpoint(metadata.services);
192
+ const workloadUrl = workloadService
193
+ ? normalizeEndpoint(workloadService)
194
+ : baseUrl.endsWith("/cpu")
195
+ ? baseUrl.replace(/\/cpu$/, "/docker-compose")
196
+ : `${baseUrl}/docker-compose`;
197
+ const parsed = new URL(cpuUrl.endsWith("/cpu") ? cpuUrl.replace(/\/cpu$/, "") : cpuUrl);
198
+ const host = parsed.hostname;
199
+ const port = parsed.port ? Number(parsed.port) : 443;
200
+ // 3. TLS certificate fingerprint
201
+ let tlsFingerprint;
202
+ try {
203
+ tlsFingerprint = await getTlsCertFingerprint(host, port);
204
+ checks.tls_cert_obtained = true;
205
+ report.tls_fingerprint = tlsFingerprint.toString("hex");
206
+ }
207
+ catch (e) {
208
+ errors.push(`Failed to get TLS certificate: ${e.message}`);
209
+ checks.tls_cert_obtained = false;
210
+ return makeResult("ERC-8004", { checks, report, errors });
211
+ }
212
+ // 4. Fetch and verify CPU quote
213
+ let cpuData;
214
+ try {
215
+ const resp = await fetch(cpuUrl);
216
+ if (!resp.ok)
217
+ throw new Error(`HTTP ${resp.status}`);
218
+ cpuData = await resp.text();
219
+ checks.cpu_quote_fetched = true;
220
+ }
221
+ catch (e) {
222
+ errors.push(`Failed to fetch CPU quote: ${e.message}`);
223
+ checks.cpu_quote_fetched = false;
224
+ return makeResult("ERC-8004", { checks, report, errors });
225
+ }
226
+ const cpuResult = await checkCpuAttestation(cpuData);
227
+ checks.cpu_attestation_valid = cpuResult.valid;
228
+ report.cpu = cpuResult.report;
229
+ report.cpu_type = cpuResult.attestationType;
230
+ if (!cpuResult.valid)
231
+ errors.push(...cpuResult.errors);
232
+ // 5. TLS binding
233
+ const reportDataHex = cpuResult.report.report_data ?? "";
234
+ if (reportDataHex.length >= 64) {
235
+ const firstHalf = reportDataHex.slice(0, 64);
236
+ checks.tls_binding = firstHalf === tlsFingerprint.toString("hex");
237
+ if (!checks.tls_binding) {
238
+ errors.push(`TLS binding failed: report_data first half (${firstHalf.slice(0, 16)}...) ` +
239
+ `!= TLS fingerprint (${tlsFingerprint.toString("hex").slice(0, 16)}...)`);
240
+ }
241
+ }
242
+ else {
243
+ checks.tls_binding = false;
244
+ errors.push("report_data too short for TLS binding check");
245
+ }
246
+ // 6. GPU quote (optional)
247
+ let gpuPresent = false;
248
+ let gpuData = "";
249
+ try {
250
+ const resp = await fetch(gpuUrl);
251
+ if (resp.ok) {
252
+ gpuData = await resp.text();
253
+ const gpuJson = JSON.parse(gpuData);
254
+ if ("error" in gpuJson) {
255
+ checks.gpu_quote_fetched = false;
256
+ }
257
+ else {
258
+ gpuPresent = true;
259
+ checks.gpu_quote_fetched = true;
260
+ }
261
+ }
262
+ else {
263
+ checks.gpu_quote_fetched = false;
264
+ }
265
+ }
266
+ catch {
267
+ checks.gpu_quote_fetched = false;
268
+ }
269
+ if (gpuPresent) {
270
+ const gpuResult = await checkNvidiaGpuAttestation(gpuData);
271
+ checks.gpu_attestation_valid = gpuResult.valid;
272
+ report.gpu = gpuResult.report;
273
+ if (!gpuResult.valid)
274
+ errors.push(...gpuResult.errors);
275
+ const gpuJson = JSON.parse(gpuData);
276
+ const gpuNonce = gpuJson.nonce ?? "";
277
+ if (reportDataHex.length >= 128) {
278
+ const secondHalf = reportDataHex.slice(64, 128);
279
+ checks.gpu_binding = secondHalf === gpuNonce;
280
+ if (!checks.gpu_binding) {
281
+ errors.push(`GPU binding failed: report_data second half (${secondHalf.slice(0, 16)}...) ` +
282
+ `!= GPU nonce (${gpuNonce.slice(0, 16)}...)`);
283
+ }
284
+ }
285
+ else {
286
+ checks.gpu_binding = false;
287
+ errors.push("report_data too short for GPU binding check");
288
+ }
289
+ }
290
+ // 7. Workload verification
291
+ try {
292
+ const resp = await fetch(workloadUrl);
293
+ if (!resp.ok)
294
+ throw new Error(`HTTP ${resp.status}`);
295
+ const dockerCompose = extractDockerCompose(await resp.text());
296
+ checks.workload_fetched = true;
297
+ const workloadResult = verifyWorkload(cpuData, dockerCompose);
298
+ checks.workload_verified = workloadResult.status === "authentic_match";
299
+ report.workload = workloadResult;
300
+ if (workloadResult.status === "authentic_mismatch") {
301
+ errors.push("Workload mismatch: VM is authentic but docker-compose does not match");
302
+ }
303
+ else if (workloadResult.status === "not_authentic") {
304
+ errors.push("Workload verification failed: not an authentic SecretVM");
305
+ }
306
+ }
307
+ catch (e) {
308
+ errors.push(`Failed to fetch workload: ${e.message}`);
309
+ checks.workload_fetched = false;
310
+ }
311
+ // Overall validity
312
+ const requiredChecks = [
313
+ checks.metadata_valid,
314
+ checks.tls_cert_obtained,
315
+ checks.cpu_quote_fetched,
316
+ checks.cpu_attestation_valid,
317
+ checks.tls_binding,
318
+ !!checks.workload_verified,
319
+ ];
320
+ if (gpuPresent) {
321
+ requiredChecks.push(!!checks.gpu_attestation_valid);
322
+ requiredChecks.push(!!checks.gpu_binding);
323
+ }
324
+ const valid = requiredChecks.every(Boolean);
325
+ return makeResult("ERC-8004", { valid, checks, report, errors });
326
+ }
327
+ // ---------------------------------------------------------------------------
328
+ // Public: checkAgent
329
+ // ---------------------------------------------------------------------------
330
+ /**
331
+ * End-to-end ERC-8004 agent verification.
332
+ *
333
+ * Resolves the agent's metadata from the on-chain registry, then runs
334
+ * the full verification flow via verifyAgent.
335
+ */
336
+ export async function checkAgent(agentId, chain) {
337
+ const errors = [];
338
+ const checks = {};
339
+ let metadata;
340
+ try {
341
+ metadata = await resolveAgent(agentId, chain);
342
+ checks.agent_resolved = true;
343
+ }
344
+ catch (e) {
345
+ errors.push(`Failed to resolve agent: ${e.message}`);
346
+ checks.agent_resolved = false;
347
+ return makeResult("ERC-8004", { checks, errors });
348
+ }
349
+ const result = await verifyAgent(metadata);
350
+ result.checks = { agent_resolved: true, ...result.checks };
351
+ return result;
352
+ }
353
+ //# sourceMappingURL=agent.js.map