@zeke-02/tinfoil 0.0.5 → 0.0.6

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.
@@ -2,33 +2,50 @@
2
2
  * VERIFIER COMPONENT OVERVIEW
3
3
  * ==========================
4
4
  *
5
- * This implementation performs three security checks entirely on the client using
6
- * a Go WebAssembly module, and exposes a small TypeScript API around it.
5
+ * This implementation performs end-to-end enclave and code verification entirely on the
6
+ * client using a Go WebAssembly module, and exposes a small TypeScript API around it.
7
7
  *
8
- * 1) REMOTE ATTESTATION (Enclave Verification)
9
- * - Invokes Go WASM `verifyEnclave(host)` against the target enclave hostname
10
- * - Verifies vendor certificate chains inside WASM (AMD SEV-SNP / Intel TDX)
11
- * - Returns the enclave's runtime measurement and at least one public key (TLS fingerprint and/or HPKE)
12
- * - Falls back to TLS-only verification if only TLS key is available (Node.js only)
8
+ * UNIFIED VERIFICATION FLOW
9
+ * The primary API is `verify()`, which invokes the Go WASM `verify(enclaveHost, repo)`
10
+ * function that performs all verification steps atomically:
11
+ *
12
+ * 1) DIGEST FETCH
13
+ * - Fetches the latest release digest from GitHub
14
+ * - Uses Tinfoil GitHub proxy (https://api-github-proxy.tinfoil.sh) to avoid rate limits
13
15
  *
14
16
  * 2) CODE INTEGRITY (Release Verification)
15
- * - Fetches the latest release notes via the Tinfoil GitHub proxy and extracts a digest
16
- * (endpoint: https://api-github-proxy.tinfoil.sh)
17
- * - Invokes Go WASM `verifyCode(configRepo, digest)` to obtain the expected code measurement
18
- * - The Go implementation verifies provenance using Sigstore/Rekor for GitHub Actions builds
17
+ * - Verifies code provenance using Sigstore/Rekor for GitHub Actions builds
18
+ * - Returns the expected code measurement for the release
19
+ *
20
+ * 3) REMOTE ATTESTATION (Enclave Verification)
21
+ * - Performs runtime attestation against the target enclave hostname
22
+ * - Verifies vendor certificate chains inside WASM (AMD SEV-SNP / Intel TDX)
23
+ * - Returns the enclave's runtime measurement and cryptographic keys (TLS fingerprint and HPKE)
19
24
  *
20
- * 3) CODE CONSISTENCY (Measurement Comparison)
21
- * - Compares the runtime measurement with the expected code measurement using
22
- * platform-aware rules implemented in `compareMeasurements()`
25
+ * 4) HARDWARE VERIFICATION (for TDX platforms)
26
+ * - Fetches and verifies TDX platform measurements if required
27
+ * - Validates hardware attestation against expected measurements
28
+ *
29
+ * 5) CODE CONSISTENCY (Measurement Comparison)
30
+ * - Compares the runtime measurement with the expected code measurement
31
+ * - Uses platform-aware comparison rules for different TEE types
32
+ *
33
+ * ERROR HANDLING
34
+ * When verification fails, errors are prefixed with the failing step:
35
+ * - `fetchDigest:` - Failed to fetch GitHub release digest
36
+ * - `verifyCode:` - Failed to verify code provenance
37
+ * - `verifyEnclave:` - Failed runtime attestation
38
+ * - `verifyHardware:` - Failed TDX hardware verification
39
+ * - `validateTLS:` - TLS public key validation failed
40
+ * - `measurements:` - Measurement comparison failed
23
41
  *
24
42
  * RUNTIME AND DELIVERY
25
43
  * - All verification executes locally via WebAssembly (Go → WASM)
26
44
  * - WASM loader: `wasm-exec.js`
27
- * - WASM module URL: https://tinfoilsh.github.io/verifier-js/tinfoil-verifier.wasm
45
+ * - WASM module URL: https://tinfoilsh.github.io/verifier/tinfoil-verifier.wasm
28
46
  * - Works in Node 20+ and modern browsers with lightweight polyfills for
29
47
  * `performance`, `TextEncoder`/`TextDecoder`, and `crypto.getRandomValues`
30
48
  * - Go stdout/stderr is suppressed by default; toggle via `suppressWasmLogs()`
31
- * - Module auto-initializes the WASM runtime on import
32
49
  *
33
50
  * PROXIES AND TRUST
34
51
  * - GitHub proxy is used only to avoid rate limits; the WASM logic independently
@@ -36,19 +53,16 @@
36
53
  * - AMD KDS access may be proxied within the WASM for availability; AMD roots are
37
54
  * embedded and the full chain is verified in Go to prevent forgery
38
55
  *
39
- * SUPPORTED PLATFORMS AND PREDICATES
40
- * - Predicate types supported by this client: SNP/TDX multi-platform v1,
41
- * TDX guest v1, SEV-SNP guest v1
42
- * - See `compareMeasurements()` for exact register mapping rules
56
+ * SUPPORTED PLATFORMS
57
+ * - AMD SEV-SNP
58
+ * - Intel TDX (with hardware platform verification)
59
+ * - Predicate types: SNP/TDX multi-platform v1, TDX guest v1/v2, SEV-SNP guest v1
43
60
  *
44
61
  * PUBLIC API (this module)
45
- * - `new Verifier({ serverURL?, configRepo? })`
46
- * - `verify()` → full end-to-end verification and attestation response
47
- * - `verifyEnclave(host?)` → runtime attestation only
48
- * - `verifyCode(configRepo, digest)` → expected measurement for a specific release
49
- * - `compareMeasurements(code, runtime)` → predicate-based comparison
50
- * - `fetchLatestDigest(configRepo?)` → release digest via proxy
51
- * - `suppressWasmLogs(suppress?)` → control WASM log output
62
+ * - `new Verifier({ serverURL, configRepo? })`
63
+ * - `verify()` → Promise<AttestationResponse> - full end-to-end verification returning cryptographic keys and measurement
64
+ * - `getVerificationDocument()` → VerificationDocument | undefined - detailed step-by-step verification results
65
+ * - `suppressWasmLogs(suppress?)` → void - control WASM log output
52
66
  */
53
67
  import { TINFOIL_CONFIG } from "./config.mjs";
54
68
  import { getFetch } from "./fetch-adapter.mjs";
@@ -74,114 +88,19 @@ function getTextDecoder() {
74
88
  }
75
89
  const nodeRequire = createNodeRequire();
76
90
  let wasmExecLoader = null;
77
- // Platform type constants
78
- // See https://github.com/tinfoilsh/verifier/
79
- const PLATFORM_TYPES = {
80
- SNP_TDX_MULTI_PLATFORM_V1: "https://tinfoil.sh/predicate/snp-tdx-multiplatform/v1",
81
- TDX_GUEST_V1: "https://tinfoil.sh/predicate/tdx-guest/v1",
82
- TDX_GUEST_V2: "https://tinfoil.sh/predicate/tdx-guest/v2",
83
- SEV_GUEST_V1: "https://tinfoil.sh/predicate/sev-snp-guest/v1",
84
- SEV_GUEST_V2: "https://tinfoil.sh/predicate/sev-snp-guest/v2",
85
- HARDWARE_MEASUREMENTS_V1: "https://tinfoil.sh/predicate/hardware-measurements/v1",
86
- };
87
- const MEASUREMENT_ERROR_MESSAGES = {
88
- FORMAT_MISMATCH: "attestation format mismatch",
89
- MEASUREMENT_MISMATCH: "measurement mismatch",
90
- RTMR1_MISMATCH: "RTMR1 mismatch",
91
- RTMR2_MISMATCH: "RTMR2 mismatch",
92
- FEW_REGISTERS: "fewer registers than expected",
93
- MULTI_PLATFORM_MISMATCH: "multi-platform measurement mismatch",
94
- MULTI_PLATFORM_SEV_SNP_MISMATCH: "multi-platform SEV-SNP measurement mismatch",
95
- };
96
- function registersEqual(a, b) {
97
- if (a.length !== b.length) {
98
- return false;
99
- }
100
- return a.every((value, index) => value === b[index]);
101
- }
102
- function compareMeasurementsError(codeMeasurement, runtimeMeasurement) {
103
- if (codeMeasurement.type === PLATFORM_TYPES.SNP_TDX_MULTI_PLATFORM_V1 &&
104
- runtimeMeasurement.type === PLATFORM_TYPES.SNP_TDX_MULTI_PLATFORM_V1) {
105
- if (!registersEqual(codeMeasurement.registers, runtimeMeasurement.registers)) {
106
- return new Error(MEASUREMENT_ERROR_MESSAGES.MULTI_PLATFORM_MISMATCH);
107
- }
108
- return null;
109
- }
110
- if (runtimeMeasurement.type === PLATFORM_TYPES.SNP_TDX_MULTI_PLATFORM_V1) {
111
- return compareMeasurementsError(runtimeMeasurement, codeMeasurement);
112
- }
113
- if (codeMeasurement.type === PLATFORM_TYPES.SNP_TDX_MULTI_PLATFORM_V1) {
114
- switch (runtimeMeasurement.type) {
115
- case PLATFORM_TYPES.TDX_GUEST_V1:
116
- case PLATFORM_TYPES.TDX_GUEST_V2: {
117
- if (codeMeasurement.registers.length < 3 ||
118
- runtimeMeasurement.registers.length < 4) {
119
- return new Error(MEASUREMENT_ERROR_MESSAGES.FEW_REGISTERS);
120
- }
121
- const expectedRtmr1 = codeMeasurement.registers[1];
122
- const expectedRtmr2 = codeMeasurement.registers[2];
123
- const actualRtmr1 = runtimeMeasurement.registers[2];
124
- const actualRtmr2 = runtimeMeasurement.registers[3];
125
- if (expectedRtmr1 !== actualRtmr1) {
126
- return new Error(MEASUREMENT_ERROR_MESSAGES.RTMR1_MISMATCH);
127
- }
128
- if (expectedRtmr2 !== actualRtmr2) {
129
- return new Error(MEASUREMENT_ERROR_MESSAGES.RTMR2_MISMATCH);
130
- }
131
- return null;
132
- }
133
- case PLATFORM_TYPES.SEV_GUEST_V1:
134
- case PLATFORM_TYPES.SEV_GUEST_V2: {
135
- if (codeMeasurement.registers.length < 1 ||
136
- runtimeMeasurement.registers.length < 1) {
137
- return new Error(MEASUREMENT_ERROR_MESSAGES.FEW_REGISTERS);
138
- }
139
- const expectedSevSnp = codeMeasurement.registers[0];
140
- const actualSevSnp = runtimeMeasurement.registers[0];
141
- if (expectedSevSnp !== actualSevSnp) {
142
- return new Error(MEASUREMENT_ERROR_MESSAGES.MULTI_PLATFORM_SEV_SNP_MISMATCH);
143
- }
144
- return null;
145
- }
146
- default:
147
- return new Error(`unsupported enclave platform for multi-platform code measurements: ${runtimeMeasurement.type}`);
148
- }
149
- }
150
- if (codeMeasurement.type !== runtimeMeasurement.type) {
151
- return new Error(MEASUREMENT_ERROR_MESSAGES.FORMAT_MISMATCH);
152
- }
153
- if (!registersEqual(codeMeasurement.registers, runtimeMeasurement.registers)) {
154
- return new Error(MEASUREMENT_ERROR_MESSAGES.MEASUREMENT_MISMATCH);
155
- }
156
- return null;
157
- }
158
- export function compareMeasurementsDetailed(codeMeasurement, runtimeMeasurement) {
159
- const error = compareMeasurementsError(codeMeasurement, runtimeMeasurement);
160
- if (error) {
161
- return { match: false, error };
162
- }
163
- return { match: true };
164
- }
165
- /**
166
- * Compare two measurements according to platform-specific rules
167
- * This is predicate function for comparing attestation measurements
168
- * taken from https://github.com/tinfoilsh/verifier/blob/main/attestation/attestation.go
169
- *
170
- * @param codeMeasurement - Expected measurement from code attestation
171
- * @param runtimeMeasurement - Actual measurement from runtime attestation
172
- * @returns true if measurements match according to platform rules
173
- */
174
- export function compareMeasurements(codeMeasurement, runtimeMeasurement) {
175
- return compareMeasurementsError(codeMeasurement, runtimeMeasurement) === null;
176
- }
177
91
  /**
178
92
  * Verifier performs attestation verification for Tinfoil enclaves
179
93
  *
180
- * The verifier loads a WebAssembly module that:
94
+ * The verifier loads a WebAssembly module (compiled from Go) that performs
95
+ * end-to-end attestation verification:
181
96
  * 1. Fetches the latest code release digest from GitHub
182
- * 2. Performs runtime attestation against the enclave
183
- * 3. Performs code attestation using the digest
184
- * 4. Compares measurements using platform-specific logic
97
+ * 2. Verifies code provenance using Sigstore/Rekor
98
+ * 3. Performs runtime attestation against the enclave
99
+ * 4. Verifies hardware measurements (for TDX platforms)
100
+ * 5. Compares code and runtime measurements using platform-specific logic
101
+ *
102
+ * Primary method: verify() - Returns AttestationResponse with cryptographic keys
103
+ * Verification details: getVerificationDocument() - Returns step-by-step results
185
104
  */
186
105
  export class Verifier {
187
106
  constructor(options) {
@@ -250,173 +169,46 @@ export class Verifier {
250
169
  }
251
170
  }
252
171
  /**
253
- * Fetch the latest release digest from GitHub
254
- * @param configRepo - Repository name (e.g., "tinfoilsh/confidential-model-router")
255
- * @returns The digest hash
256
- */
257
- async fetchLatestDigest(configRepo) {
258
- // GitHub Proxy Note:
259
- // We use api-github-proxy.tinfoil.sh instead of the direct GitHub API to avoid
260
- // rate limiting that could degrade UX. The proxy caches responses while the
261
- // integrity of the data is independently verified in `verifyCode` via
262
- // Sigstore transparency logs (Rekor). Using the proxy therefore does not
263
- // weaken security.
264
- const targetRepo = configRepo || this.configRepo;
265
- const fetchFn = getFetch();
266
- const releaseResponse = await fetchFn(`https://api-github-proxy.tinfoil.sh/repos/${targetRepo}/releases/latest`, {
267
- headers: {
268
- Accept: "application/vnd.github.v3+json",
269
- "User-Agent": "tinfoil-node-client",
270
- },
271
- });
272
- if (!releaseResponse.ok) {
273
- throw new Error(`GitHub API request failed: ${releaseResponse.status} ${releaseResponse.statusText}`);
274
- }
275
- const releaseData = (await releaseResponse.json());
276
- // Extract digest from release notes
277
- const digestRegex = /Digest: `([a-f0-9]{64})`/;
278
- const digestMatch = releaseData.body?.match(digestRegex);
279
- if (!digestMatch) {
280
- throw new Error("Could not find digest in release notes");
281
- }
282
- const digest = digestMatch[1];
283
- return digest;
284
- }
285
- /**
286
- * Perform runtime attestation on the enclave
287
- * @param enclaveHost - The enclave hostname
288
- * @returns Attestation response with measurement and keys
289
- */
290
- async verifyEnclave(enclaveHost) {
291
- // Expose errors via explicit Promise rejection and add a timeout
292
- return new Promise(async (resolve, reject) => {
293
- try {
294
- const targetHost = enclaveHost || this.serverURL;
295
- if (typeof globalThis.verifyEnclave !== "function") {
296
- reject(new Error("WASM verifyEnclave function not available"));
297
- return;
298
- }
299
- let attestationResponse;
300
- let timeoutHandle;
301
- try {
302
- const timeoutPromise = new Promise((_, timeoutReject) => {
303
- timeoutHandle = setTimeout(() => timeoutReject(new Error("WASM verifyEnclave timed out after 10 seconds")), 10000);
304
- });
305
- attestationResponse = await Promise.race([
306
- globalThis.verifyEnclave(targetHost),
307
- timeoutPromise,
308
- ]);
309
- // Clear timeout on success
310
- if (timeoutHandle !== undefined) {
311
- clearTimeout(timeoutHandle);
312
- }
313
- }
314
- catch (error) {
315
- // Clear timeout on error
316
- if (timeoutHandle !== undefined) {
317
- clearTimeout(timeoutHandle);
318
- }
319
- reject(new Error(`WASM verifyEnclave failed: ${error}`));
320
- return;
321
- }
322
- // Validate required fields - fail fast with explicit rejection
323
- // At least one key must be present (TLS or HPKE)
324
- if (!attestationResponse?.tls_public_key &&
325
- !attestationResponse?.hpke_public_key) {
326
- reject(new Error("Missing both tls_public_key and hpke_public_key in attestation response"));
327
- return;
328
- }
329
- // Parse runtime measurement
330
- let parsedRuntimeMeasurement;
331
- try {
332
- if (attestationResponse.measurement &&
333
- typeof attestationResponse.measurement === "string") {
334
- parsedRuntimeMeasurement = JSON.parse(attestationResponse.measurement);
335
- }
336
- else if (attestationResponse.measurement &&
337
- typeof attestationResponse.measurement === "object") {
338
- parsedRuntimeMeasurement = attestationResponse.measurement;
339
- }
340
- else {
341
- reject(new Error("Invalid runtime measurement format"));
342
- return;
343
- }
344
- }
345
- catch (parseError) {
346
- reject(new Error(`Failed to parse runtime measurement: ${parseError}`));
347
- return;
348
- }
349
- const result = {
350
- measurement: parsedRuntimeMeasurement,
351
- };
352
- // Include keys if available
353
- if (attestationResponse.tls_public_key) {
354
- result.tlsPublicKeyFingerprint = attestationResponse.tls_public_key;
355
- }
356
- if (attestationResponse.hpke_public_key) {
357
- result.hpkePublicKey = attestationResponse.hpke_public_key;
358
- }
359
- resolve(result);
360
- }
361
- catch (outerError) {
362
- reject(outerError);
363
- }
364
- });
365
- }
366
- /**
367
- * Perform code attestation
368
- * @param configRepo - Repository name
369
- * @param digest - Code digest hash
370
- * @returns Code measurement
371
- */
372
- async verifyCode(configRepo, digest) {
373
- if (typeof globalThis.verifyCode !== "function") {
374
- throw new Error("WASM verifyCode function not available");
375
- }
376
- const rawMeasurement = await globalThis.verifyCode(configRepo, digest);
377
- const normalizedMeasurement = typeof rawMeasurement === "string"
378
- ? (() => {
379
- try {
380
- return JSON.parse(rawMeasurement);
381
- }
382
- catch (error) {
383
- throw new Error(`Invalid code measurement format: ${error.message}`);
384
- }
385
- })()
386
- : rawMeasurement;
387
- if (!normalizedMeasurement || typeof normalizedMeasurement !== "object") {
388
- throw new Error("Invalid code measurement format");
389
- }
390
- const measurementObject = normalizedMeasurement;
391
- if (typeof measurementObject.type !== "string" ||
392
- !Array.isArray(measurementObject.registers)) {
393
- throw new Error("Invalid code measurement format");
394
- }
395
- const parsedCodeMeasurement = {
396
- type: measurementObject.type,
397
- registers: measurementObject.registers.map((value) => String(value)),
398
- };
399
- return { measurement: parsedCodeMeasurement };
400
- }
401
- /**
402
- * Perform attestation verification
172
+ * Perform end-to-end attestation verification
403
173
  *
404
- * This method:
174
+ * This method performs all verification steps atomically via the Go WASM verify() function:
405
175
  * 1. Fetches the latest code digest from GitHub releases
406
- * 2. Calls verifyCode to get the expected measurement for the code
407
- * 3. Calls verifyEnclave to get the actual runtime measurement
408
- * 4. Compares measurements using platform-specific logic (see `compareMeasurements()`)
409
- * 5. Returns the attestation response if verification succeeds
176
+ * 2. Verifies code provenance using Sigstore/Rekor
177
+ * 3. Performs runtime attestation against the enclave
178
+ * 4. Verifies hardware measurements (for TDX platforms)
179
+ * 5. Compares code and runtime measurements using platform-specific logic
410
180
  *
411
181
  * The WASM runtime is automatically initialized and cleaned up within this method.
182
+ * A detailed verification document is saved and can be accessed via getVerificationDocument().
412
183
  *
413
- * @throws Error if measurements don't match or verification fails
184
+ * @returns AttestationResponse containing cryptographic keys (TLS/HPKE) and enclave measurement
185
+ * @throws Error if measurements don't match or verification fails at any step
414
186
  */
415
187
  async verify() {
416
188
  return Verifier.executeWithWasm(async () => {
417
189
  return this.verifyInternal();
418
190
  });
419
191
  }
192
+ /**
193
+ * Save a failed verification document
194
+ */
195
+ saveFailedVerificationDocument(steps) {
196
+ this.lastVerificationDocument = {
197
+ configRepo: this.configRepo,
198
+ enclaveHost: this.serverURL,
199
+ releaseDigest: "",
200
+ codeMeasurement: { type: "", registers: [] },
201
+ enclaveMeasurement: { measurement: { type: "", registers: [] } },
202
+ tlsPublicKey: "",
203
+ hpkePublicKey: "",
204
+ hardwareMeasurement: undefined,
205
+ codeFingerprint: "",
206
+ enclaveFingerprint: "",
207
+ selectedRouterEndpoint: this.serverURL,
208
+ securityVerified: false,
209
+ steps,
210
+ };
211
+ }
420
212
  /**
421
213
  * Internal verification logic that runs within WASM context
422
214
  */
@@ -427,96 +219,90 @@ export class Verifier {
427
219
  verifyEnclave: { status: "pending" },
428
220
  compareMeasurements: { status: "pending" },
429
221
  };
430
- let releaseDigest;
431
- let codeMeasurement;
432
- let attestation;
433
- try {
434
- releaseDigest = await this.fetchLatestDigest(this.configRepo);
435
- steps.fetchDigest = { status: "success" };
436
- }
437
- catch (error) {
438
- steps.fetchDigest = { status: "failed", error: error.message };
439
- this.lastVerificationDocument = {
440
- configRepo: this.configRepo,
441
- enclaveHost: this.serverURL,
442
- releaseDigest: "",
443
- codeMeasurement: { type: "", registers: [] },
444
- enclaveMeasurement: { measurement: { type: "", registers: [] } },
445
- securityVerified: false,
446
- steps,
222
+ if (typeof globalThis.verify !== "function") {
223
+ steps.fetchDigest = {
224
+ status: "failed",
225
+ error: "WASM verify function not available",
447
226
  };
448
- throw error;
227
+ this.saveFailedVerificationDocument(steps);
228
+ throw new Error("WASM verify function not available");
449
229
  }
230
+ let groundTruth;
450
231
  try {
451
- const results = await Promise.all([
452
- this.verifyCode(this.configRepo, releaseDigest).then((result) => {
453
- steps.verifyCode = { status: "success" };
454
- return result;
455
- }, (error) => {
456
- steps.verifyCode = {
457
- status: "failed",
458
- error: error.message,
459
- };
460
- throw error;
461
- }),
462
- this.verifyEnclave(this.serverURL).then((result) => {
463
- steps.verifyEnclave = { status: "success" };
464
- return result;
465
- }, (error) => {
466
- steps.verifyEnclave = {
467
- status: "failed",
468
- error: error.message,
469
- };
470
- throw error;
471
- }),
472
- ]);
473
- codeMeasurement = results[0].measurement;
474
- attestation = results[1];
232
+ const groundTruthJSON = await globalThis.verify(this.serverURL, this.configRepo);
233
+ groundTruth = JSON.parse(groundTruthJSON);
234
+ // Mark all steps as successful since WASM verify() succeeded
235
+ steps.fetchDigest = { status: "success" };
236
+ steps.verifyCode = { status: "success" };
237
+ steps.verifyEnclave = { status: "success" };
238
+ steps.compareMeasurements = { status: "success" };
475
239
  }
476
240
  catch (error) {
477
- this.lastVerificationDocument = {
478
- configRepo: this.configRepo,
479
- enclaveHost: this.serverURL,
480
- releaseDigest: releaseDigest,
481
- codeMeasurement: codeMeasurement,
482
- enclaveMeasurement: attestation,
483
- securityVerified: false,
484
- steps,
485
- };
241
+ const errorMessage = error instanceof Error ? error.message : String(error);
242
+ if (errorMessage.startsWith("fetchDigest:")) {
243
+ steps.fetchDigest = { status: "failed", error: errorMessage };
244
+ }
245
+ else if (errorMessage.startsWith("verifyCode:")) {
246
+ steps.fetchDigest = { status: "success" };
247
+ steps.verifyCode = { status: "failed", error: errorMessage };
248
+ }
249
+ else if (errorMessage.startsWith("verifyEnclave:")) {
250
+ steps.fetchDigest = { status: "success" };
251
+ steps.verifyCode = { status: "success" };
252
+ steps.verifyEnclave = { status: "failed", error: errorMessage };
253
+ }
254
+ else if (errorMessage.startsWith("measurements:")) {
255
+ steps.fetchDigest = { status: "success" };
256
+ steps.verifyCode = { status: "success" };
257
+ steps.verifyEnclave = { status: "success" };
258
+ steps.compareMeasurements = { status: "failed", error: errorMessage };
259
+ }
260
+ else if (errorMessage.startsWith("verifyHardware:") ||
261
+ errorMessage.startsWith("validateTLS:")) {
262
+ steps.fetchDigest = { status: "success" };
263
+ steps.verifyCode = { status: "success" };
264
+ steps.verifyEnclave = { status: "success" };
265
+ steps.otherError = { status: "failed", error: errorMessage };
266
+ }
267
+ else {
268
+ steps.otherError = { status: "failed", error: errorMessage };
269
+ }
270
+ this.saveFailedVerificationDocument(steps);
486
271
  throw error;
487
272
  }
488
- const measurementsMatchError = compareMeasurementsError(codeMeasurement, attestation.measurement);
489
- if (measurementsMatchError) {
490
- steps.compareMeasurements = {
491
- status: "failed",
492
- error: measurementsMatchError.message,
493
- };
494
- this.lastVerificationDocument = {
495
- configRepo: this.configRepo,
496
- enclaveHost: this.serverURL,
497
- releaseDigest: releaseDigest,
498
- codeMeasurement,
499
- enclaveMeasurement: attestation,
500
- securityVerified: false,
501
- steps,
502
- };
503
- throw new Error(`Verification failed: measurements did not match.\nCode measurement (${codeMeasurement.type}: ${codeMeasurement.registers})\n` +
504
- `Runtime measurement (${attestation.measurement.type}: ${attestation.measurement.registers}:)\n ${measurementsMatchError.message}`);
505
- }
506
- steps.compareMeasurements = { status: "success" };
273
+ const attestation = {
274
+ tlsPublicKeyFingerprint: groundTruth.tls_public_key,
275
+ hpkePublicKey: groundTruth.hpke_public_key,
276
+ measurement: groundTruth.enclave_measurement,
277
+ };
507
278
  this.lastVerificationDocument = {
508
279
  configRepo: this.configRepo,
509
280
  enclaveHost: this.serverURL,
510
- releaseDigest: releaseDigest,
511
- codeMeasurement,
281
+ releaseDigest: groundTruth.digest,
282
+ codeMeasurement: groundTruth.code_measurement,
512
283
  enclaveMeasurement: attestation,
284
+ tlsPublicKey: groundTruth.tls_public_key,
285
+ hpkePublicKey: groundTruth.hpke_public_key,
286
+ hardwareMeasurement: groundTruth.hardware_measurement,
287
+ codeFingerprint: groundTruth.code_fingerprint,
288
+ enclaveFingerprint: groundTruth.enclave_fingerprint,
289
+ selectedRouterEndpoint: this.serverURL,
513
290
  securityVerified: true,
514
291
  steps,
515
292
  };
516
293
  return attestation;
517
294
  }
518
295
  /**
519
- * Returns the full verification document from the last successful verify() call
296
+ * Returns the verification document from the last verify() call
297
+ *
298
+ * The document contains detailed step-by-step verification results including:
299
+ * - Step status (pending/success/failed) for each verification phase
300
+ * - Measurements, fingerprints, and cryptographic keys
301
+ * - Error messages for any failed steps
302
+ *
303
+ * Available even if verification failed, allowing inspection of which step failed.
304
+ *
305
+ * @returns VerificationDocument with complete verification details, or undefined if verify() hasn't been called
520
306
  */
521
307
  getVerificationDocument() {
522
308
  return this.lastVerificationDocument;
@@ -524,7 +310,7 @@ export class Verifier {
524
310
  }
525
311
  Verifier.goInstance = null;
526
312
  Verifier.initializationPromise = null;
527
- Verifier.defaultWasmUrl = "https://tinfoilsh.github.io/verifier-js/tinfoil-verifier.wasm";
313
+ Verifier.defaultWasmUrl = "https://tinfoilsh.github.io/verifier/tinfoil-verifier.wasm";
528
314
  Verifier.originalFsWriteSync = null;
529
315
  Verifier.wasmLogsSuppressed = true;
530
316
  Verifier.globalsInitialized = false;
@@ -553,10 +339,12 @@ function shouldAutoInitializeWasm() {
553
339
  /**
554
340
  * Control WASM log output
555
341
  *
556
- * The Go WASM runtime outputs logs through a polyfilled fs.writeSync.
342
+ * The Go WASM runtime outputs logs (stdout/stderr) through a polyfilled fs.writeSync.
557
343
  * This function allows suppressing those logs without affecting other console output.
344
+ * By default, WASM logs are suppressed to reduce noise.
558
345
  *
559
346
  * @param suppress - Whether to suppress WASM logs (default: true)
347
+ * @returns void
560
348
  */
561
349
  export function suppressWasmLogs(suppress = true) {
562
350
  globalThis.__tinfoilSuppressWasmLogs = suppress;
@@ -85,12 +85,20 @@ class SecureClient {
85
85
  releaseDigest: "",
86
86
  codeMeasurement: { type: "", registers: [] },
87
87
  enclaveMeasurement: { measurement: { type: "", registers: [] } },
88
+ tlsPublicKey: "",
89
+ hpkePublicKey: "",
90
+ hardwareMeasurement: undefined,
91
+ codeFingerprint: "",
92
+ enclaveFingerprint: "",
93
+ selectedRouterEndpoint: new URL(this.enclaveURL).hostname,
88
94
  securityVerified: false,
89
95
  steps: {
90
96
  fetchDigest: { status: "pending" },
91
97
  verifyCode: { status: "pending" },
92
98
  verifyEnclave: { status: "pending" },
93
99
  compareMeasurements: { status: "pending" },
100
+ createTransport: undefined,
101
+ verifyHPKEKey: undefined,
94
102
  otherError: { status: "failed", error: error.message },
95
103
  },
96
104
  };