@reclaimprotocol/client 0.1.29 → 0.1.32

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.
Files changed (110) hide show
  1. package/README.md +255 -234
  2. package/lib/api.d.ts +2 -0
  3. package/lib/api.js +2 -0
  4. package/lib/client.js +2 -0
  5. package/lib/enckeys.d.ts +1 -1
  6. package/lib/enckeys.js +2 -2
  7. package/lib/encoding.d.ts +4 -0
  8. package/lib/encoding.js +10 -0
  9. package/lib/feature-flags.d.ts +9 -0
  10. package/lib/feature-flags.js +19 -0
  11. package/lib/index.d.ts +4 -4
  12. package/lib/index.js +4 -4
  13. package/lib/jwks.d.ts +8 -8
  14. package/lib/jwks.js +38 -17
  15. package/lib/launch/destination.d.ts +14 -0
  16. package/lib/launch/destination.js +89 -0
  17. package/lib/launch/device.d.ts +30 -0
  18. package/lib/launch/device.js +168 -0
  19. package/lib/launch/environment.d.ts +224 -0
  20. package/lib/launch/environment.js +98 -0
  21. package/lib/launch/errors.d.ts +20 -0
  22. package/lib/launch/errors.js +25 -0
  23. package/lib/launch/extension.d.ts +42 -0
  24. package/lib/launch/extension.js +72 -0
  25. package/lib/launch/flow.d.ts +82 -0
  26. package/lib/launch/flow.js +222 -0
  27. package/lib/launch/index.d.ts +16 -0
  28. package/lib/launch/index.js +12 -0
  29. package/lib/launch/mobile.d.ts +77 -0
  30. package/lib/launch/mobile.js +124 -0
  31. package/lib/launch/modal.d.ts +50 -0
  32. package/lib/launch/modal.js +258 -0
  33. package/lib/launch/options.d.ts +74 -0
  34. package/lib/launch/options.js +20 -0
  35. package/lib/launch/portal.d.ts +28 -0
  36. package/lib/launch/portal.js +68 -0
  37. package/lib/launch/qr.d.ts +5 -0
  38. package/lib/launch/qr.js +28 -0
  39. package/lib/launch/target.d.ts +46 -0
  40. package/lib/launch/target.js +128 -0
  41. package/lib/launch/vendor/qrcodegen.d.ts +134 -0
  42. package/lib/launch/vendor/qrcodegen.js +822 -0
  43. package/lib/legacy-proof.d.ts +2 -0
  44. package/lib/legacy-proof.js +37 -7
  45. package/lib/proof-hash.d.ts +5 -5
  46. package/lib/proof-hash.js +1 -1
  47. package/lib/proof-recipe.d.ts +3 -3
  48. package/lib/proof-recipe.js +1 -1
  49. package/lib/proof-types.d.ts +35 -8
  50. package/lib/reclaim.d.ts +81 -70
  51. package/lib/reclaim.js +79 -130
  52. package/lib/request-proof.d.ts +60 -0
  53. package/lib/request-proof.js +241 -0
  54. package/lib/result.d.ts +46 -38
  55. package/lib/result.js +160 -100
  56. package/lib/routes.gen.d.ts +76 -28
  57. package/lib/routes.gen.js +24 -11
  58. package/lib/tee-binding.d.ts +1 -1
  59. package/lib/types/openapi.d.ts +1 -0
  60. package/lib/types/openapi.gen.d.ts +1598 -485
  61. package/lib/types/openapi.gen.js +5 -0
  62. package/lib/verification-clients.d.ts +28 -14
  63. package/lib/verification-clients.js +31 -11
  64. package/lib/verify-attestor-tee.d.ts +19 -2
  65. package/lib/verify-attestor-tee.js +40 -4
  66. package/lib/verify-tee.d.ts +10 -1
  67. package/lib/verify-tee.js +34 -10
  68. package/package.json +5 -1
  69. package/src/api.ts +9 -0
  70. package/src/client.ts +2 -0
  71. package/src/enckeys.ts +2 -2
  72. package/src/encoding.ts +12 -0
  73. package/src/feature-flags.ts +39 -0
  74. package/src/index.ts +13 -14
  75. package/src/jwks.ts +52 -30
  76. package/src/launch/destination.ts +111 -0
  77. package/src/launch/device.ts +242 -0
  78. package/src/launch/environment.ts +337 -0
  79. package/src/launch/errors.ts +49 -0
  80. package/src/launch/extension.ts +124 -0
  81. package/src/launch/flow.ts +369 -0
  82. package/src/launch/index.ts +42 -0
  83. package/src/launch/mobile.ts +184 -0
  84. package/src/launch/modal.ts +378 -0
  85. package/src/launch/options.ts +99 -0
  86. package/src/launch/portal.ts +104 -0
  87. package/src/launch/qr.ts +37 -0
  88. package/src/launch/target.ts +150 -0
  89. package/src/launch/vendor/qrcodegen.ts +1034 -0
  90. package/src/proof-hash.ts +6 -6
  91. package/src/proof-recipe.ts +3 -3
  92. package/src/proof-types.ts +36 -8
  93. package/src/reclaim.ts +185 -218
  94. package/src/{legacy-proof.ts → request-proof.ts} +81 -36
  95. package/src/result.ts +238 -172
  96. package/src/routes.gen.ts +24 -11
  97. package/src/tee-binding.ts +1 -1
  98. package/src/types/openapi.gen.ts +1603 -485
  99. package/src/types/openapi.ts +3 -1
  100. package/src/verification-clients.ts +33 -19
  101. package/src/verify-attestor-tee.ts +66 -8
  102. package/src/verify-tee.ts +44 -13
  103. package/lib/api-client.d.ts +0 -31
  104. package/lib/api-client.js +0 -112
  105. package/lib/api-consts.d.ts +0 -4
  106. package/lib/api-consts.js +0 -4
  107. package/lib/api-errors.d.ts +0 -13
  108. package/lib/api-errors.js +0 -30
  109. package/lib/eip712.d.ts +0 -20
  110. package/lib/eip712.js +0 -108
@@ -1,3 +1,8 @@
1
+ // AUTO-GENERATED by packages/app/scripts/openapi-typegen.ts
2
+ // from packages/app/openapi.yaml (spec v0.1.0, source sha256:099a5bcf4fba).
3
+ // The hash catches drift the version misses (a spec can change without a
4
+ // version bump) — do not remove it as "redundant" with the version.
5
+ // Run `npm run openapi:gen --workspace=packages/app` to regenerate. Do not edit by hand.
1
6
  /**
2
7
  * This file was auto-generated by openapi-typescript.
3
8
  * Do not make direct changes to the file.
@@ -1,17 +1,31 @@
1
1
  /**
2
- * Stable names for the Verification Clients seeded by Builder.
2
+ * Production Verification Client URLs. Use `custom(uri)` for a separately
3
+ * registered client or a non-production Builder deployment.
3
4
  *
4
- * A name identifies a registry record; session creation still selects a
5
- * client with that deployment's exact registered URL.
5
+ * Builder resolves a session's `verificationClientUrl` by EXACT string match
6
+ * against the registered `uri` and 400s for anything unregistered — these
7
+ * constants MUST track `packages/app/src/verification/builtin-clients.ts`'s
8
+ * `clientUrl()` seed exactly, or a consumer passing one of these gets a
9
+ * failed session creation instead of a warning (see
10
+ * `tests/verification-clients.test.ts`, which pins all six against that
11
+ * seed).
12
+ *
13
+ * As of migration `20260917130000000.share-routes-uris.sql`, all six
14
+ * built-ins (`builder` included) share one uniform shape,
15
+ * `{origin}/share/{name}` — what each `/share/{name}` page actually does
16
+ * differs (a redirect for `builder`/`portals`, the QR/App-Clip/deep-link
17
+ * launcher for `verifier-app`/`reclaimBrowserExtension`, a static
18
+ * informational page for `inAppSdk`/`zkFetch` — see
19
+ * `packages/app/src/routes/SharePage.tsx`), but the registry entry is
20
+ * uniform. `../launch/target.ts`'s `parseFlowUrl` is what turns one of these
21
+ * back into a client.
6
22
  */
7
- export declare const KNOWN_VERIFICATION_CLIENTS: {
8
- readonly builder: "builder";
9
- readonly portals: "portals";
10
- readonly verifierApp: "verifier-app";
11
- readonly inAppSdk: "inapp-sdk";
12
- readonly reclaimBrowserExtension: "reclaim-browser-extension";
13
- readonly zkFetch: "zkfetch";
14
- };
15
- export type KnownVerificationClientName = typeof KNOWN_VERIFICATION_CLIENTS[keyof typeof KNOWN_VERIFICATION_CLIENTS];
16
- /** Deployment-specific registered URLs keyed by a known client name. */
17
- export type KnownVerificationClientUrlMap = Partial<Record<KnownVerificationClientName, string>>;
23
+ export declare const VerificationClient: Readonly<{
24
+ builder: "https://build.reclaimprotocol.org/share/builder";
25
+ portals: "https://build.reclaimprotocol.org/share/portals";
26
+ verifierApp: "https://build.reclaimprotocol.org/share/verifier-app";
27
+ inAppSdk: "https://build.reclaimprotocol.org/share/inapp-sdk";
28
+ reclaimBrowserExtension: "https://build.reclaimprotocol.org/share/reclaim-browser-extension";
29
+ zkFetch: "https://build.reclaimprotocol.org/share/zkfetch";
30
+ custom(uri: string): string;
31
+ }>;
@@ -1,14 +1,34 @@
1
+ import { DEFAULT_BASE_URL } from "./consts.js";
1
2
  /**
2
- * Stable names for the Verification Clients seeded by Builder.
3
+ * Production Verification Client URLs. Use `custom(uri)` for a separately
4
+ * registered client or a non-production Builder deployment.
3
5
  *
4
- * A name identifies a registry record; session creation still selects a
5
- * client with that deployment's exact registered URL.
6
+ * Builder resolves a session's `verificationClientUrl` by EXACT string match
7
+ * against the registered `uri` and 400s for anything unregistered — these
8
+ * constants MUST track `packages/app/src/verification/builtin-clients.ts`'s
9
+ * `clientUrl()` seed exactly, or a consumer passing one of these gets a
10
+ * failed session creation instead of a warning (see
11
+ * `tests/verification-clients.test.ts`, which pins all six against that
12
+ * seed).
13
+ *
14
+ * As of migration `20260917130000000.share-routes-uris.sql`, all six
15
+ * built-ins (`builder` included) share one uniform shape,
16
+ * `{origin}/share/{name}` — what each `/share/{name}` page actually does
17
+ * differs (a redirect for `builder`/`portals`, the QR/App-Clip/deep-link
18
+ * launcher for `verifier-app`/`reclaimBrowserExtension`, a static
19
+ * informational page for `inAppSdk`/`zkFetch` — see
20
+ * `packages/app/src/routes/SharePage.tsx`), but the registry entry is
21
+ * uniform. `../launch/target.ts`'s `parseFlowUrl` is what turns one of these
22
+ * back into a client.
6
23
  */
7
- export const KNOWN_VERIFICATION_CLIENTS = {
8
- builder: 'builder',
9
- portals: 'portals',
10
- verifierApp: 'verifier-app',
11
- inAppSdk: 'inapp-sdk',
12
- reclaimBrowserExtension: 'reclaim-browser-extension',
13
- zkFetch: 'zkfetch',
14
- };
24
+ export const VerificationClient = Object.freeze({
25
+ builder: `${DEFAULT_BASE_URL}/share/builder`,
26
+ portals: `${DEFAULT_BASE_URL}/share/portals`,
27
+ verifierApp: `${DEFAULT_BASE_URL}/share/verifier-app`,
28
+ inAppSdk: `${DEFAULT_BASE_URL}/share/inapp-sdk`,
29
+ reclaimBrowserExtension: `${DEFAULT_BASE_URL}/share/reclaim-browser-extension`,
30
+ zkFetch: `${DEFAULT_BASE_URL}/share/zkfetch`,
31
+ custom(uri) {
32
+ return uri;
33
+ },
34
+ });
@@ -1,4 +1,4 @@
1
- import type { LegacyProof as Proof } from './proof-types.ts';
1
+ import { type AttestorClaimAttestation, type ClaimAttestationType, type ReclaimRequestProof as Proof } from './proof-types.ts';
2
2
  /**
3
3
  * Result of verifying an attestor TEE attestation.
4
4
  */
@@ -51,6 +51,17 @@ export type AttestorTeeAttestationConfig = {
51
51
  */
52
52
  expectedImageDigests?: string[];
53
53
  };
54
+ /** A witness's attestation kind. Required: every client sets it explicitly. */
55
+ export declare function claimAttestationType(att: AttestorClaimAttestation): ClaimAttestationType;
56
+ /** What `runAttestorTeeVerification` actually checked, per witness. */
57
+ export interface AttestorTeeVerificationSummary {
58
+ /** Witnesses whose `attestation_report` was verified (and pinned, when
59
+ * `expectedImageDigests` was set). */
60
+ verified: number;
61
+ /** Witnesses that carried no `attestation_report`. Their attestor address
62
+ * and claim signature were still verified. */
63
+ unattested: number;
64
+ }
54
65
  /**
55
66
  * Verifies the attestor's TEE attestation for every witness of every
56
67
  * provided proof. Throws `AttestorTeeVerificationError` on the first
@@ -62,15 +73,21 @@ export type AttestorTeeAttestationConfig = {
62
73
  * 3. Recovers the signer of `claim_signature` from the claim data and
63
74
  * asserts it equals `witness.id` (binds the signature to the
64
75
  * attestor that the TEE attestation will cover).
76
+ *
77
+ * Steps 4 and 5 apply only to a witness that carries an
78
+ * `attestation_report`, which is optional — see `AttestorClaimAttestation`:
65
79
  * 4. Calls `verifyAttestorTeeAttestation` to validate the JWT against
66
80
  * the pinned GCP Confidential Space Root CA and the attestor-key
67
81
  * nonce.
68
82
  * 5. If `expectedImageDigests` is provided, asserts the attestation's
69
83
  * container image digest is in the allowlist.
70
84
  *
85
+ * The returned summary says which witnesses were attested, so a caller can
86
+ * tell "every report checked out" from "there were no reports to check".
87
+ *
71
88
  * Node-only (uses node:crypto), like `verifyTeeAttestation`.
72
89
  *
73
90
  * @param proofs - The proofs to verify.
74
91
  * @param config - Optional config; see {@link AttestorTeeAttestationConfig}.
75
92
  */
76
- export declare function runAttestorTeeVerification(proofs: Proof[], config?: AttestorTeeAttestationConfig): Promise<void>;
93
+ export declare function runAttestorTeeVerification(proofs: Proof[], config?: AttestorTeeAttestationConfig): Promise<AttestorTeeVerificationSummary>;
@@ -155,17 +155,21 @@ export async function verifyAttestorTeeAttestation(report, expectedAttestorAddre
155
155
  };
156
156
  }
157
157
  }
158
+ /** A witness's attestation kind. Required: every client sets it explicitly. */
159
+ export function claimAttestationType(att) {
160
+ return att.type;
161
+ }
158
162
  function normalizeAttestorAddress(address) {
159
163
  return address.trim().toLowerCase();
160
164
  }
161
165
  function normalizeSignature(sig) {
162
166
  return sig.trim().toLowerCase();
163
167
  }
164
- async function verifyAttestorTeeForProof(proof, config) {
168
+ async function verifyAttestorTeeForProof(proof, config, summary) {
165
169
  if (!proof.witnesses || proof.witnesses.length === 0) {
166
170
  throw new AttestorTeeVerificationError('Proof has no witnesses');
167
171
  }
168
- const expectedDigests = config.expectedImageDigests?.map(d => d.trim());
172
+ const expectedDigests = config.expectedImageDigests?.map((digest) => digest.trim().toLowerCase());
169
173
  const proofSignatures = new Set((proof.signatures || []).map(normalizeSignature));
170
174
  const claimSignData = createSignDataForClaim(proof.claimData);
171
175
  for (const witness of proof.witnesses) {
@@ -189,15 +193,36 @@ async function verifyAttestorTeeForProof(proof, config) {
189
193
  if (normalizeAttestorAddress(recoveredSigner) !== normalizeAttestorAddress(witness.id)) {
190
194
  throw new AttestorTeeVerificationError(`claim_signature recovers to ${recoveredSigner}, expected attestor ${witness.id}`);
191
195
  }
196
+ // A `confidential-space` witness (portals) proves its runtime through
197
+ // `tee_attestation`, which `runTeeVerification` checks against
198
+ // `context.attestationNonce` — a different chain with a different nonce
199
+ // binding. The address and signature bindings above still applied; the
200
+ // attestor-report checks below are not this type's evidence.
201
+ if (claimAttestationType(att) === 'confidential-space') {
202
+ summary.unattested++;
203
+ continue;
204
+ }
205
+ // An attestor that signs claims without returning its own Confidential
206
+ // Space attestation is the common production shape — the in-app SDK
207
+ // (via reclaim-tee-operator-flutter) emits witnesses carrying only
208
+ // `attestor_address` and `claim_signature`. The bindings above are what
209
+ // tie the claim to a trusted attestor address; the report, when present,
210
+ // additionally proves that attestor ran a pinned image. Absent one,
211
+ // there is nothing to check and nothing to pin.
212
+ if (!att.attestation_report) {
213
+ summary.unattested++;
214
+ continue;
215
+ }
192
216
  const result = await verifyAttestorTeeAttestation(att.attestation_report, witness.id);
193
217
  if (!result.isVerified) {
194
218
  throw new AttestorTeeVerificationError(`Attestor TEE attestation verification failed for witness ${witness.id}: ${result.error}`);
195
219
  }
220
+ summary.verified++;
196
221
  if (expectedDigests && expectedDigests.length > 0) {
197
222
  if (!result.imageDigest) {
198
223
  throw new AttestorTeeVerificationError(`Attestor TEE attestation for witness ${witness.id} did not expose an image digest to check against expectedImageDigests`);
199
224
  }
200
- if (!expectedDigests.includes(result.imageDigest)) {
225
+ if (!expectedDigests.includes(result.imageDigest.trim().toLowerCase())) {
201
226
  throw new AttestorTeeVerificationError(`Attestor image digest ${result.imageDigest} for witness ${witness.id} is not in expectedImageDigests`);
202
227
  }
203
228
  }
@@ -214,12 +239,18 @@ async function verifyAttestorTeeForProof(proof, config) {
214
239
  * 3. Recovers the signer of `claim_signature` from the claim data and
215
240
  * asserts it equals `witness.id` (binds the signature to the
216
241
  * attestor that the TEE attestation will cover).
242
+ *
243
+ * Steps 4 and 5 apply only to a witness that carries an
244
+ * `attestation_report`, which is optional — see `AttestorClaimAttestation`:
217
245
  * 4. Calls `verifyAttestorTeeAttestation` to validate the JWT against
218
246
  * the pinned GCP Confidential Space Root CA and the attestor-key
219
247
  * nonce.
220
248
  * 5. If `expectedImageDigests` is provided, asserts the attestation's
221
249
  * container image digest is in the allowlist.
222
250
  *
251
+ * The returned summary says which witnesses were attested, so a caller can
252
+ * tell "every report checked out" from "there were no reports to check".
253
+ *
223
254
  * Node-only (uses node:crypto), like `verifyTeeAttestation`.
224
255
  *
225
256
  * @param proofs - The proofs to verify.
@@ -229,10 +260,15 @@ export async function runAttestorTeeVerification(proofs, config = {}) {
229
260
  if (!proofs || proofs.length === 0) {
230
261
  throw new AttestorTeeVerificationError('No proofs provided for attestor TEE verification');
231
262
  }
263
+ const summary = {
264
+ verified: 0,
265
+ unattested: 0,
266
+ };
232
267
  try {
233
268
  for (const proof of proofs) {
234
- await verifyAttestorTeeForProof(proof, config);
269
+ await verifyAttestorTeeForProof(proof, config, summary);
235
270
  }
271
+ return summary;
236
272
  }
237
273
  catch (error) {
238
274
  if (error instanceof AttestorTeeVerificationError) {
@@ -1,4 +1,4 @@
1
- import type { LegacyProof as Proof } from './proof-types.ts';
1
+ import type { ReclaimRequestProof as Proof } from './proof-types.ts';
2
2
  export interface TeeAttestationConfig {
3
3
  appSecret: string;
4
4
  }
@@ -29,4 +29,13 @@ export declare function verifyTeeAttestation(proof: Proof, appSecret: string): P
29
29
  * @param config - TEE attestation configuration containing the app secret
30
30
  * @throws {TeeVerificationError} When TEE data is missing or verification fails
31
31
  */
32
+ /**
33
+ * The TEE attestation to verify for a proof.
34
+ *
35
+ * Builder-mode proofs carry it on the witness, inside a `confidential-space`
36
+ * `claimAttestation`, so every verification client presents the same envelope.
37
+ * Legacy proofs carry it at the proof's top level. Prefer the witness copy and
38
+ * fall back, so both shapes verify through one path.
39
+ */
40
+ export declare function teeAttestationOf(proof: Proof): unknown;
32
41
  export declare function runTeeVerification(proofs: Proof[], config: TeeAttestationConfig): Promise<void>;
package/lib/verify-tee.js CHANGED
@@ -226,19 +226,24 @@ function getProofVersion(teeAttestation) {
226
226
  return teeAttestation.proof_version ?? teeAttestation.proofVersion;
227
227
  }
228
228
  function assertProofShape(teeAttestation) {
229
- if (teeAttestation.error) {
230
- throw new Error(`${teeAttestation.error.code}: ${teeAttestation.error.message}`);
229
+ const reportedError = teeAttestation.error;
230
+ if (isRecord(reportedError)) {
231
+ throw new Error(`${reportedError.code}: ${reportedError.message}`);
231
232
  }
232
- const proofVersion = getProofVersion(teeAttestation);
233
+ const proofVersion = teeAttestation.proof_version
234
+ ?? teeAttestation.proofVersion;
233
235
  assert(typeof proofVersion === 'string' && SUPPORTED_PROOF_VERSIONS.includes(proofVersion), `unexpected proof version: ${proofVersion}`);
234
236
  assert(teeAttestation.tee_provider === EXPECTED_TEE_PROVIDER, `unexpected tee provider: ${teeAttestation.tee_provider}`);
235
237
  assert(teeAttestation.tee_technology === EXPECTED_TEE_TECHNOLOGY, `unexpected tee technology: ${teeAttestation.tee_technology}`);
236
238
  assert(typeof teeAttestation.nonce === 'string' && teeAttestation.nonce.length > 0, 'tee attestation nonce missing');
237
239
  assert(typeof teeAttestation.timestamp === 'string' && teeAttestation.timestamp.length > 0, 'tee attestation timestamp missing');
238
240
  assert(!Number.isNaN(Date.parse(teeAttestation.timestamp)), 'tee attestation timestamp is invalid');
239
- assert(typeof teeAttestation.workload?.image_digest === 'string' && teeAttestation.workload.image_digest.length > 0, 'workload image digest missing');
240
- assert(typeof teeAttestation.verifier?.image_digest === 'string' && teeAttestation.verifier.image_digest.length > 0, 'verifier image digest missing');
241
- assert(typeof teeAttestation.attestation?.token === 'string' && teeAttestation.attestation.token.length > 0, 'attestation token missing');
241
+ assert(isRecord(teeAttestation.workload), 'workload missing');
242
+ assert(typeof teeAttestation.workload.image_digest === 'string' && teeAttestation.workload.image_digest.length > 0, 'workload image digest missing');
243
+ assert(isRecord(teeAttestation.verifier), 'verifier missing');
244
+ assert(typeof teeAttestation.verifier.image_digest === 'string' && teeAttestation.verifier.image_digest.length > 0, 'verifier image digest missing');
245
+ assert(isRecord(teeAttestation.attestation), 'attestation missing');
246
+ assert(typeof teeAttestation.attestation.token === 'string' && teeAttestation.attestation.token.length > 0, 'attestation token missing');
242
247
  }
243
248
  async function computeDigestBinding(teeAttestation) {
244
249
  const proofVersion = getProofVersion(teeAttestation);
@@ -290,23 +295,25 @@ export async function verifyTeeAttestation(proof, appSecret) {
290
295
  assertNonBrowserEnvironment();
291
296
  try {
292
297
  const appId = legacyApplicationIdFromPrivateKey(appSecret);
293
- let teeAttestation = proof.teeAttestation;
298
+ let teeAttestation = teeAttestationOf(proof);
294
299
  if (!teeAttestation) {
295
300
  throw new Error('Missing teeAttestation in proof');
296
301
  }
297
302
  if (typeof teeAttestation === 'string') {
298
303
  teeAttestation = JSON.parse(teeAttestation);
299
304
  }
305
+ assert(isRecord(teeAttestation), 'TEE attestation must be an object');
300
306
  assertProofShape(teeAttestation);
307
+ const supportedTeeAttestation = teeAttestation;
301
308
  const { parsedContext, nonceDataObj, expectedNonce } = parseProofContext(proof);
302
309
  verifyApplicationAndSessionBinding(proof, parsedContext, nonceDataObj, appId);
303
310
  verifyNonceMaterial(expectedNonce, nonceDataObj, appSecret);
304
311
  const cleanExpectedNonce = normalizeHex(expectedNonce);
305
- const cleanTeeNonce = normalizeHex(teeAttestation.nonce);
312
+ const cleanTeeNonce = normalizeHex(supportedTeeAttestation.nonce);
306
313
  assert(cleanTeeNonce.length > 0, 'TEE attestation nonce is empty');
307
314
  assert(isHex(cleanTeeNonce), 'TEE attestation nonce is not valid hex');
308
315
  assert(cleanTeeNonce === cleanExpectedNonce, `Nonce Mismatch! Expected ${cleanExpectedNonce}, got ${cleanTeeNonce}`);
309
- await verifyGcpClaims(teeAttestation, cleanExpectedNonce);
316
+ await verifyGcpClaims(supportedTeeAttestation, cleanExpectedNonce);
310
317
  return { isVerified: true };
311
318
  }
312
319
  catch (error) {
@@ -324,9 +331,26 @@ export async function verifyTeeAttestation(proof, appSecret) {
324
331
  * @param config - TEE attestation configuration containing the app secret
325
332
  * @throws {TeeVerificationError} When TEE data is missing or verification fails
326
333
  */
334
+ /**
335
+ * The TEE attestation to verify for a proof.
336
+ *
337
+ * Builder-mode proofs carry it on the witness, inside a `confidential-space`
338
+ * `claimAttestation`, so every verification client presents the same envelope.
339
+ * Legacy proofs carry it at the proof's top level. Prefer the witness copy and
340
+ * fall back, so both shapes verify through one path.
341
+ */
342
+ export function teeAttestationOf(proof) {
343
+ for (const witness of proof.witnesses ?? []) {
344
+ const att = witness.claimAttestation;
345
+ if (att?.type === 'confidential-space' && att.tee_attestation) {
346
+ return att.tee_attestation;
347
+ }
348
+ }
349
+ return proof.teeAttestation;
350
+ }
327
351
  export async function runTeeVerification(proofs, config) {
328
352
  const hasTeeData = proofs.every(proof => {
329
- if (proof.teeAttestation) {
353
+ if (teeAttestationOf(proof)) {
330
354
  return true;
331
355
  }
332
356
  try {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reclaimprotocol/client",
3
3
  "title": "Reclaim Client",
4
- "version": "0.1.29",
4
+ "version": "0.1.32",
5
5
  "description": "Typed SDK/HTTP client SDK for the Reclaim Protocol — the programmatic way to use Reclaim Protocol to verify **data points** about your users (a follower count, an account balance, an order). Can be directly integrated by apps to request verification for their users using Reclaim Protocol and verify the results.",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -23,6 +23,10 @@
23
23
  "./enckeys": {
24
24
  "types": "./lib/enckeys.d.ts",
25
25
  "import": "./lib/enckeys.js"
26
+ },
27
+ "./launch": {
28
+ "types": "./lib/launch/index.d.ts",
29
+ "import": "./lib/launch/index.js"
26
30
  }
27
31
  },
28
32
  "files": [
package/src/api.ts CHANGED
@@ -7,9 +7,18 @@ export {
7
7
  throwProblemError,
8
8
  } from './errors.ts'
9
9
  export type { ProblemError } from './errors.ts'
10
+ export {
11
+ getBooleanFeatureFlag,
12
+ getFeatureFlag,
13
+ getNumberFeatureFlag,
14
+ getStringFeatureFlag,
15
+ } from './feature-flags.ts'
10
16
  export {
11
17
  DEFAULT_BASE_URL,
12
18
  PROBLEM_CONTENT_TYPE,
13
19
  } from './consts.ts'
20
+ export {
21
+ VerificationClient,
22
+ } from './verification-clients.ts'
14
23
  export type * from './types/openapi.ts'
15
24
  export type * from './types/openapi.gen.ts'
package/src/client.ts CHANGED
@@ -115,6 +115,8 @@ export class ReclaimClient {
115
115
 
116
116
  const empty = res.status === 204
117
117
  || res.headers.get('content-length') === '0'
118
+ // Fetch exposes parsed JSON as `any`; OpenAPI owns the operation-specific
119
+ // response type, and callers can inspect the original Response if needed.
118
120
  const data = (
119
121
  empty ? undefined : await res.json()
120
122
  ) as IOperationResponse<T>
package/src/enckeys.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * secp256k1 (Ethereum) encryption-key helpers for ECIES result delivery.
3
3
  *
4
4
  * The org's encryption key is its **Ethereum public key** (secp256k1). The
5
- * Verification Client ECIES-encrypts the signed JWS to it (see ./result.ts) and
5
+ * Builder ECIES-encrypts the signed JWS to it (see ./result.ts) and
6
6
  * the Consumer decrypts with the matching eth **private key**. Eth-native `0x`
7
7
  * hex is used everywhere at the edges (the format MetaMask/ethers/wallets
8
8
  * emit); the wire/DB store the public key as a secp256k1 JWK
@@ -48,7 +48,7 @@ export function loadDecryptionKey(input: DecryptionKeyInput): DecryptionKey {
48
48
 
49
49
  /**
50
50
  * Convert an uncompressed secp256k1 public key (`0x04` + 128 hex) to the public
51
- * JWK the API stores and the Verification Client encrypts to — so callers only
51
+ * JWK the API stores and Builder encrypts to — so callers only
52
52
  * ever handle eth-native hex, never a JWK. Mirrors the `x`/`y` coordinate split
53
53
  * in `jwksFromPrivateKey` (./jwks.ts).
54
54
  */
package/src/encoding.ts CHANGED
@@ -55,3 +55,15 @@ export function b64url(input: Uint8Array | string): string {
55
55
  .replace(/\//g, '_')
56
56
  .replace(/=+$/, '')
57
57
  }
58
+
59
+ /** `value` without trailing `/`s. A loop, not `/\/+$/`: that regex backtracks
60
+ * quadratically on a long run of slashes followed by anything else, and
61
+ * some callers get attacker-supplied input (a JWS header's `iss`). */
62
+ export function stripTrailingSlashes(value: string): string {
63
+ let end = value.length
64
+ while(end > 0 && value.charCodeAt(end - 1) === 0x2f) {
65
+ end--
66
+ }
67
+
68
+ return value.slice(0, end)
69
+ }
@@ -0,0 +1,39 @@
1
+ import type { VerificationRuntimeConfig } from './types/openapi.ts'
2
+
3
+ /** Read a resolved session flag without assuming its runtime type. */
4
+ export function getFeatureFlag(
5
+ config: VerificationRuntimeConfig | null | undefined,
6
+ key: string,
7
+ ) {
8
+ return config?.values[key]
9
+ }
10
+
11
+ /** Read a boolean flag, returning `fallback` for absent or mistyped values. */
12
+ export function getBooleanFeatureFlag(
13
+ config: VerificationRuntimeConfig | null | undefined,
14
+ key: string,
15
+ fallback = false,
16
+ ) {
17
+ const value = getFeatureFlag(config, key)
18
+ return typeof value === 'boolean' ? value : fallback
19
+ }
20
+
21
+ /** Read a string flag, returning `fallback` for absent or mistyped values. */
22
+ export function getStringFeatureFlag(
23
+ config: VerificationRuntimeConfig | null | undefined,
24
+ key: string,
25
+ fallback: string,
26
+ ) {
27
+ const value = getFeatureFlag(config, key)
28
+ return typeof value === 'string' ? value : fallback
29
+ }
30
+
31
+ /** Read a numeric flag, returning `fallback` for absent or mistyped values. */
32
+ export function getNumberFeatureFlag(
33
+ config: VerificationRuntimeConfig | null | undefined,
34
+ key: string,
35
+ fallback: number,
36
+ ) {
37
+ const value = getFeatureFlag(config, key)
38
+ return typeof value === 'number' && Number.isFinite(value) ? value : fallback
39
+ }
package/src/index.ts CHANGED
@@ -46,16 +46,16 @@ export {
46
46
  uncompressedPubkeyToJwk,
47
47
  } from './enckeys.ts'
48
48
  export {
49
- createReclaim,
49
+ type BindSessionTeeNonceInput,
50
50
  type CreateSessionInput,
51
- type CreateSessionTeeAttestation,
52
- type Reclaim,
51
+ type ImportReclaimVerificationConfig,
53
52
  type ReclaimConfig,
53
+ type ReclaimFlowFactory,
54
+ ReclaimVerification,
55
+ type ReclaimVerificationState,
54
56
  type ReceiveOptions,
55
57
  type ReceivedDelivery,
56
- type RegisterCallbackInput,
57
- type SetKeypairInput,
58
- TERMINAL_EVENTS,
58
+ type SessionFlow,
59
59
  } from './reclaim.ts'
60
60
  export {
61
61
  fetchJwks,
@@ -69,19 +69,20 @@ export {
69
69
  selectVerificationResultKey,
70
70
  trustedAttestorAddresses,
71
71
  validateAttestorAddresses,
72
+ validateAttestorImageDigests,
72
73
  } from './jwks.ts'
73
74
  export {
74
75
  type NestedProofOutcome,
75
76
  verifyNestedProof,
76
77
  } from './attestor-proof.ts'
77
78
  export {
78
- type LegacyProof,
79
- type LegacyProofOutcome,
80
- type LegacyVerifyProofOptions,
81
79
  type ProofHashRequirement,
80
+ type ReclaimRequestProof,
81
+ type ReclaimRequestProofOutcome,
82
+ type VerifyRequestProofOptions,
82
83
  proofHashes,
83
- verifyProof,
84
- } from './legacy-proof.ts'
84
+ verifyRequestProof,
85
+ } from './request-proof.ts'
85
86
  export {
86
87
  type BuilderProofRecipe,
87
88
  proofHashesFromBuilderRecipe,
@@ -111,7 +112,5 @@ export {
111
112
  type LegacyTeeNonceMode,
112
113
  } from './tee-binding.ts'
113
114
  export {
114
- KNOWN_VERIFICATION_CLIENTS,
115
- type KnownVerificationClientName,
116
- type KnownVerificationClientUrlMap,
115
+ VerificationClient,
117
116
  } from './verification-clients.ts'