@reclaimprotocol/client 0.1.28 → 0.1.31
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 +255 -234
- package/lib/api.d.ts +2 -0
- package/lib/api.js +2 -0
- package/lib/client.js +2 -0
- package/lib/enckeys.d.ts +1 -1
- package/lib/enckeys.js +2 -2
- package/lib/encoding.d.ts +4 -0
- package/lib/encoding.js +10 -0
- package/lib/feature-flags.d.ts +9 -0
- package/lib/feature-flags.js +19 -0
- package/lib/index.d.ts +4 -4
- package/lib/index.js +4 -4
- package/lib/jwks.d.ts +8 -8
- package/lib/jwks.js +38 -17
- package/lib/launch/destination.d.ts +14 -0
- package/lib/launch/destination.js +89 -0
- package/lib/launch/device.d.ts +30 -0
- package/lib/launch/device.js +168 -0
- package/lib/launch/environment.d.ts +224 -0
- package/lib/launch/environment.js +98 -0
- package/lib/launch/errors.d.ts +20 -0
- package/lib/launch/errors.js +25 -0
- package/lib/launch/extension.d.ts +42 -0
- package/lib/launch/extension.js +72 -0
- package/lib/launch/flow.d.ts +82 -0
- package/lib/launch/flow.js +222 -0
- package/lib/launch/index.d.ts +16 -0
- package/lib/launch/index.js +12 -0
- package/lib/launch/mobile.d.ts +77 -0
- package/lib/launch/mobile.js +124 -0
- package/lib/launch/modal.d.ts +50 -0
- package/lib/launch/modal.js +258 -0
- package/lib/launch/options.d.ts +74 -0
- package/lib/launch/options.js +20 -0
- package/lib/launch/portal.d.ts +28 -0
- package/lib/launch/portal.js +68 -0
- package/lib/launch/qr.d.ts +5 -0
- package/lib/launch/qr.js +28 -0
- package/lib/launch/target.d.ts +46 -0
- package/lib/launch/target.js +128 -0
- package/lib/launch/vendor/qrcodegen.d.ts +134 -0
- package/lib/launch/vendor/qrcodegen.js +822 -0
- package/lib/legacy-proof.d.ts +2 -0
- package/lib/legacy-proof.js +37 -7
- package/lib/proof-hash.d.ts +5 -5
- package/lib/proof-hash.js +1 -1
- package/lib/proof-recipe.d.ts +3 -3
- package/lib/proof-recipe.js +1 -1
- package/lib/proof-types.d.ts +35 -8
- package/lib/reclaim.d.ts +81 -70
- package/lib/reclaim.js +79 -130
- package/lib/request-proof.d.ts +60 -0
- package/lib/request-proof.js +241 -0
- package/lib/result.d.ts +46 -38
- package/lib/result.js +160 -100
- package/lib/routes.gen.d.ts +76 -28
- package/lib/routes.gen.js +24 -11
- package/lib/tee-binding.d.ts +1 -1
- package/lib/types/openapi.d.ts +1 -0
- package/lib/types/openapi.gen.d.ts +1594 -488
- package/lib/types/openapi.gen.js +5 -0
- package/lib/verification-clients.d.ts +28 -14
- package/lib/verification-clients.js +31 -11
- package/lib/verify-attestor-tee.d.ts +19 -2
- package/lib/verify-attestor-tee.js +40 -4
- package/lib/verify-tee.d.ts +10 -1
- package/lib/verify-tee.js +34 -10
- package/package.json +5 -1
- package/src/api.ts +9 -0
- package/src/client.ts +2 -0
- package/src/enckeys.ts +2 -2
- package/src/encoding.ts +12 -0
- package/src/feature-flags.ts +39 -0
- package/src/index.ts +13 -14
- package/src/jwks.ts +52 -30
- package/src/launch/destination.ts +111 -0
- package/src/launch/device.ts +242 -0
- package/src/launch/environment.ts +337 -0
- package/src/launch/errors.ts +49 -0
- package/src/launch/extension.ts +124 -0
- package/src/launch/flow.ts +369 -0
- package/src/launch/index.ts +42 -0
- package/src/launch/mobile.ts +184 -0
- package/src/launch/modal.ts +378 -0
- package/src/launch/options.ts +99 -0
- package/src/launch/portal.ts +104 -0
- package/src/launch/qr.ts +37 -0
- package/src/launch/target.ts +150 -0
- package/src/launch/vendor/qrcodegen.ts +1034 -0
- package/src/proof-hash.ts +6 -6
- package/src/proof-recipe.ts +3 -3
- package/src/proof-types.ts +36 -8
- package/src/reclaim.ts +185 -218
- package/src/{legacy-proof.ts → request-proof.ts} +81 -36
- package/src/result.ts +238 -172
- package/src/routes.gen.ts +24 -11
- package/src/tee-binding.ts +1 -1
- package/src/types/openapi.gen.ts +1599 -488
- package/src/types/openapi.ts +3 -1
- package/src/verification-clients.ts +33 -19
- package/src/verify-attestor-tee.ts +66 -8
- package/src/verify-tee.ts +44 -13
- package/lib/api-client.d.ts +0 -31
- package/lib/api-client.js +0 -112
- package/lib/api-consts.d.ts +0 -4
- package/lib/api-consts.js +0 -4
- package/lib/api-errors.d.ts +0 -13
- package/lib/api-errors.js +0 -30
- package/lib/eip712.d.ts +0 -20
- package/lib/eip712.js +0 -108
package/lib/types/openapi.gen.js
CHANGED
|
@@ -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:9ba46b5cbd7c).
|
|
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
|
-
*
|
|
2
|
+
* Production Verification Client URLs. Use `custom(uri)` for a separately
|
|
3
|
+
* registered client or a non-production Builder deployment.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
*
|
|
3
|
+
* Production Verification Client URLs. Use `custom(uri)` for a separately
|
|
4
|
+
* registered client or a non-production Builder deployment.
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
|
8
|
-
builder:
|
|
9
|
-
portals:
|
|
10
|
-
verifierApp:
|
|
11
|
-
inAppSdk:
|
|
12
|
-
reclaimBrowserExtension:
|
|
13
|
-
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
|
|
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<
|
|
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(
|
|
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) {
|
package/lib/verify-tee.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
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
|
-
|
|
230
|
-
|
|
229
|
+
const reportedError = teeAttestation.error;
|
|
230
|
+
if (isRecord(reportedError)) {
|
|
231
|
+
throw new Error(`${reportedError.code}: ${reportedError.message}`);
|
|
231
232
|
}
|
|
232
|
-
const proofVersion =
|
|
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(
|
|
240
|
-
assert(typeof teeAttestation.
|
|
241
|
-
assert(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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.
|
|
4
|
+
"version": "0.1.31",
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
49
|
+
type BindSessionTeeNonceInput,
|
|
50
50
|
type CreateSessionInput,
|
|
51
|
-
type
|
|
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
|
|
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
|
-
|
|
84
|
-
} from './
|
|
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
|
-
|
|
115
|
-
type KnownVerificationClientName,
|
|
116
|
-
type KnownVerificationClientUrlMap,
|
|
115
|
+
VerificationClient,
|
|
117
116
|
} from './verification-clients.ts'
|