@zeke-02/tinfoil 0.0.5 → 0.0.7

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