@reclaimprotocol/client 0.1.26 → 0.1.28
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 +634 -42
- package/lib/api-client.d.ts +31 -0
- package/lib/api-client.js +112 -0
- package/lib/api-consts.d.ts +4 -0
- package/lib/api-consts.js +4 -0
- package/lib/api-errors.d.ts +13 -0
- package/lib/api-errors.js +30 -0
- package/lib/api.d.ts +7 -0
- package/lib/api.js +3 -0
- package/lib/attestor-proof.d.ts +6 -0
- package/lib/attestor-proof.js +45 -18
- package/lib/client.d.ts +1 -6
- package/lib/client.js +12 -33
- package/lib/consts.d.ts +1 -1
- package/lib/consts.js +1 -1
- package/lib/eip191.d.ts +4 -2
- package/lib/eip191.js +17 -1
- package/lib/errors.d.ts +38 -4
- package/lib/errors.js +99 -16
- package/lib/index.d.ts +9 -7
- package/lib/index.js +8 -4
- package/lib/jwks.d.ts +25 -1
- package/lib/jwks.js +69 -4
- package/lib/legacy-proof.d.ts +57 -0
- package/lib/legacy-proof.js +211 -0
- package/lib/proof-hash.d.ts +35 -0
- package/lib/proof-hash.js +216 -0
- package/lib/proof-recipe.d.ts +18 -0
- package/lib/proof-recipe.js +37 -0
- package/lib/proof-trust-constants.d.ts +3 -0
- package/lib/proof-trust-constants.js +43 -0
- package/lib/proof-types.d.ts +62 -0
- package/lib/proof-types.js +1 -0
- package/lib/proof-verification-error.d.ts +13 -0
- package/lib/proof-verification-error.js +11 -0
- package/lib/reclaim.d.ts +27 -5
- package/lib/reclaim.js +140 -14
- package/lib/result.d.ts +58 -6
- package/lib/result.js +252 -61
- package/lib/routes.gen.d.ts +58 -6
- package/lib/routes.gen.js +16 -3
- package/lib/tee-binding.d.ts +82 -0
- package/lib/tee-binding.js +112 -0
- package/lib/types/openapi.gen.d.ts +1412 -193
- package/lib/verification-clients.d.ts +17 -0
- package/lib/verification-clients.js +14 -0
- package/lib/verify-attestor-tee.d.ts +76 -0
- package/lib/verify-attestor-tee.js +243 -0
- package/lib/verify-tee.d.ts +32 -0
- package/lib/verify-tee.js +347 -0
- package/package.json +5 -1
- package/src/api.ts +15 -0
- package/src/attestor-proof.ts +57 -20
- package/src/client.ts +15 -38
- package/src/consts.ts +1 -2
- package/src/eip191.ts +31 -3
- package/src/errors.ts +132 -18
- package/src/index.ts +50 -5
- package/src/jwks.ts +128 -5
- package/src/legacy-proof.ts +367 -0
- package/src/proof-hash.ts +316 -0
- package/src/proof-recipe.ts +75 -0
- package/src/proof-trust-constants.ts +45 -0
- package/src/proof-types.ts +57 -0
- package/src/proof-verification-error.ts +13 -0
- package/src/reclaim.ts +206 -19
- package/src/result.ts +403 -68
- package/src/routes.gen.ts +16 -3
- package/src/tee-binding.ts +192 -0
- package/src/types/openapi.gen.ts +1412 -193
- package/src/verification-clients.ts +22 -0
- package/src/verify-attestor-tee.ts +365 -0
- package/src/verify-tee.ts +507 -0
package/lib/reclaim.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { ReclaimClient } from "./client.js";
|
|
2
2
|
import { loadDecryptionKey, } from "./enckeys.js";
|
|
3
|
+
import { isProblemError } from "./errors.js";
|
|
4
|
+
import { proofHashesFromBuilderRecipe } from "./proof-recipe.js";
|
|
3
5
|
import { decryptCallback, verifyResultFull, } from "./result.js";
|
|
4
|
-
|
|
6
|
+
import { createBuilderTeeBinding, } from "./tee-binding.js";
|
|
7
|
+
/** Terminal lifecycle events selected by default for callback subscriptions. */
|
|
5
8
|
export const TERMINAL_EVENTS = [
|
|
6
9
|
'verification_success',
|
|
7
10
|
'verification_rejected',
|
|
8
11
|
'verification_error',
|
|
12
|
+
'verification_cancelled',
|
|
13
|
+
'session_expired',
|
|
14
|
+
];
|
|
15
|
+
const SIGNED_RESULT_EVENTS = [
|
|
16
|
+
'verification_success',
|
|
17
|
+
'verification_rejected',
|
|
18
|
+
'verification_error',
|
|
9
19
|
];
|
|
10
20
|
export function createReclaim(config) {
|
|
11
21
|
if (!config?.orgSecret) {
|
|
@@ -25,14 +35,23 @@ export function createReclaim(config) {
|
|
|
25
35
|
api: client.call.bind(client),
|
|
26
36
|
sessions: {
|
|
27
37
|
async create(input) {
|
|
38
|
+
const { teeAttestation, ...request } = input;
|
|
28
39
|
const body = {
|
|
29
|
-
...
|
|
30
|
-
verificationClientUrl:
|
|
40
|
+
...request,
|
|
41
|
+
verificationClientUrl: request.verificationClientUrl ?? `${client.baseUrl}/v/{sessionId}`,
|
|
31
42
|
};
|
|
32
43
|
const { data } = await client.call('CreateVerificationSession', {
|
|
33
44
|
body,
|
|
34
45
|
});
|
|
35
|
-
return
|
|
46
|
+
return teeAttestation
|
|
47
|
+
? bindTeeNonce(data, teeAttestation)
|
|
48
|
+
: data;
|
|
49
|
+
},
|
|
50
|
+
async bindTeeNonce(sessionId, input) {
|
|
51
|
+
const { data } = await client.call('GetVerificationSession', {
|
|
52
|
+
params: { sessionId },
|
|
53
|
+
});
|
|
54
|
+
return bindTeeNonce(data, input);
|
|
36
55
|
},
|
|
37
56
|
async get(sessionId) {
|
|
38
57
|
const { data } = await client.call('GetVerificationSession', {
|
|
@@ -103,7 +122,10 @@ export function createReclaim(config) {
|
|
|
103
122
|
return decryptCallback(resolveKey(credential), body);
|
|
104
123
|
},
|
|
105
124
|
verify(jws, opts) {
|
|
106
|
-
return verifyResultFull(jws, {
|
|
125
|
+
return verifyResultFull(jws, {
|
|
126
|
+
fetchImpl: config.fetch,
|
|
127
|
+
...withRegisteredIssuer(opts),
|
|
128
|
+
});
|
|
107
129
|
},
|
|
108
130
|
async receive(body, opts) {
|
|
109
131
|
// A delivery envelope tagged with a non-terminal event carries a
|
|
@@ -114,7 +136,7 @@ export function createReclaim(config) {
|
|
|
114
136
|
&& !(body instanceof ArrayBuffer)) {
|
|
115
137
|
const env = body;
|
|
116
138
|
if (env.event
|
|
117
|
-
&& !
|
|
139
|
+
&& !SIGNED_RESULT_EVENTS.includes(env.event)) {
|
|
118
140
|
const decoded = parseEventData(env.data);
|
|
119
141
|
return {
|
|
120
142
|
kind: 'event',
|
|
@@ -128,12 +150,121 @@ export function createReclaim(config) {
|
|
|
128
150
|
const jws = await decryptCallback(key, body);
|
|
129
151
|
const result = await verifyResultFull(jws, {
|
|
130
152
|
fetchImpl: config.fetch,
|
|
131
|
-
...verifyOpts,
|
|
153
|
+
...withRegisteredIssuer(verifyOpts),
|
|
132
154
|
});
|
|
133
155
|
return { kind: 'result', result };
|
|
134
156
|
},
|
|
135
157
|
},
|
|
136
158
|
};
|
|
159
|
+
async function bindTeeNonce(session, input) {
|
|
160
|
+
const binding = createBuilderTeeBinding({
|
|
161
|
+
organizationId: session.orgId,
|
|
162
|
+
organizationVerificationPrivateKey: input.appSecret,
|
|
163
|
+
sessionId: session.id,
|
|
164
|
+
timestamp: input.timestamp ?? Date.now().toString(),
|
|
165
|
+
nonceMode: input.nonceMode,
|
|
166
|
+
});
|
|
167
|
+
const { data } = await client.call('BindVerificationSessionTeeNonce', {
|
|
168
|
+
params: { sessionId: session.id },
|
|
169
|
+
body: {
|
|
170
|
+
attestationNonce: binding.attestationNonce,
|
|
171
|
+
attestationNonceData: binding.attestationNonceData,
|
|
172
|
+
signature: binding.signature,
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
return data;
|
|
176
|
+
}
|
|
177
|
+
async function resolvedSession(id) {
|
|
178
|
+
const { data } = await client.call('GetVerificationRecipe', { params: { sessionId: id } });
|
|
179
|
+
return { recipes: data.recipes };
|
|
180
|
+
}
|
|
181
|
+
async function resolvedProvider(providerId, providerVersion, _allowedTags, proofs) {
|
|
182
|
+
let exactVersion = providerVersion;
|
|
183
|
+
if (!exactVersion) {
|
|
184
|
+
const { data: provider } = await client.call('GetProvider', {
|
|
185
|
+
params: { providerId },
|
|
186
|
+
});
|
|
187
|
+
if (!provider.latestVersion) {
|
|
188
|
+
throw new Error(`Provider ${providerId} has no live version`);
|
|
189
|
+
}
|
|
190
|
+
exactVersion = canonicalVersion(provider.latestVersion);
|
|
191
|
+
}
|
|
192
|
+
const { data: version } = await client.call('GetProviderVersion', {
|
|
193
|
+
params: { providerId, version: exactVersion },
|
|
194
|
+
});
|
|
195
|
+
return [{
|
|
196
|
+
hashes: proofHashesFromBuilderRecipe({
|
|
197
|
+
providerId,
|
|
198
|
+
resolvedVersion: exactVersion,
|
|
199
|
+
initialUrl: version.initialUrl,
|
|
200
|
+
requests: version.requests,
|
|
201
|
+
allowedJsRequests: version.allowedJsRequests,
|
|
202
|
+
}, proofs),
|
|
203
|
+
}];
|
|
204
|
+
}
|
|
205
|
+
function withRegisteredIssuer(opts = {}) {
|
|
206
|
+
const sessionId = opts.sessionId ?? opts.expectedReclaimSessionId;
|
|
207
|
+
const requestedProofValidation = opts.proofValidation
|
|
208
|
+
?? opts.proofVerification;
|
|
209
|
+
const resolvedOpts = {
|
|
210
|
+
...opts,
|
|
211
|
+
proofValidation: {
|
|
212
|
+
...requestedProofValidation,
|
|
213
|
+
resolveProvider: requestedProofValidation?.resolveProvider
|
|
214
|
+
?? resolvedProvider,
|
|
215
|
+
},
|
|
216
|
+
...(sessionId && !opts.resolveSession && !opts.proofRecipes
|
|
217
|
+
? { resolveSession: resolvedSession }
|
|
218
|
+
: {}),
|
|
219
|
+
};
|
|
220
|
+
if (opts.signerPublicKey
|
|
221
|
+
|| opts.issuer
|
|
222
|
+
|| opts.trustedIssuer
|
|
223
|
+
|| opts.issuers?.length
|
|
224
|
+
|| opts.trustedIssuers?.length
|
|
225
|
+
|| opts.resolveIssuers
|
|
226
|
+
|| opts.issuerResolver
|
|
227
|
+
|| opts.issuersByVerificationClientId
|
|
228
|
+
|| opts.jwksUrl
|
|
229
|
+
|| opts.jwksUrlsByVerificationClientId
|
|
230
|
+
|| opts.resolveJwksUrl) {
|
|
231
|
+
return resolvedOpts;
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
...resolvedOpts,
|
|
235
|
+
resolveIssuers: async (verificationClientId) => {
|
|
236
|
+
const session = await registeredClient(verificationClientId);
|
|
237
|
+
if (!session) {
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
return session.verificationClientIssuer
|
|
241
|
+
? [session.verificationClientIssuer]
|
|
242
|
+
: [];
|
|
243
|
+
},
|
|
244
|
+
resolveJwksUrl: async (verificationClientId) => {
|
|
245
|
+
const session = await registeredClient(verificationClientId);
|
|
246
|
+
return session?.verificationClientJwksUrl;
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
async function registeredClient(verificationClientId) {
|
|
250
|
+
if (!sessionId) {
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
const { data: session } = await client.call('GetVerificationSession', { params: { sessionId } });
|
|
254
|
+
if (session.verificationClientId !== verificationClientId) {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
return session;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function canonicalVersion(version) {
|
|
262
|
+
const base = `${version.major}.${version.minor}.${version.patch}`;
|
|
263
|
+
return version.branch
|
|
264
|
+
? `${base}-${version.branch}${version.buildNumber === undefined
|
|
265
|
+
? ''
|
|
266
|
+
: `.${version.buildNumber}`}`
|
|
267
|
+
: base;
|
|
137
268
|
}
|
|
138
269
|
/** Non-terminal `data` is a base64-encoded JSON `{ event, eventData }`. */
|
|
139
270
|
function parseEventData(data) {
|
|
@@ -148,12 +279,7 @@ function parseEventData(data) {
|
|
|
148
279
|
return {};
|
|
149
280
|
}
|
|
150
281
|
}
|
|
151
|
-
/** True for a `404` thrown by {@link ReclaimClient.call}
|
|
152
|
-
* `output.statusCode` is 404 (see ./errors.ts). */
|
|
282
|
+
/** True for a `404` thrown by {@link ReclaimClient.call}. */
|
|
153
283
|
function isNotFound(err) {
|
|
154
|
-
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
const boom = err;
|
|
158
|
-
return boom.output?.statusCode === 404 || boom.statusCode === 404;
|
|
284
|
+
return isProblemError(err) && err.output.statusCode === 404;
|
|
159
285
|
}
|
package/lib/result.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { components } from './types/openapi.gen.ts';
|
|
2
2
|
import type { DecryptionKey } from './enckeys.ts';
|
|
3
3
|
import { type Jwk } from './jwks.ts';
|
|
4
|
+
import { type LegacyVerifyProofOptions, type ProofHashRequirement } from './legacy-proof.ts';
|
|
5
|
+
import { type BuilderProofRecipe } from './proof-recipe.ts';
|
|
6
|
+
import type { TrustedData } from './proof-types.ts';
|
|
4
7
|
export interface Jws {
|
|
5
8
|
payload: string;
|
|
6
9
|
signatures: {
|
|
@@ -77,18 +80,62 @@ export declare function decodeJwsPayload(jws: Jws): Record<string, unknown>;
|
|
|
77
80
|
*/
|
|
78
81
|
export declare function verifyJwsSignature(jws: Jws, signerPublicKey: string): boolean;
|
|
79
82
|
export interface VerifyResultFullOptions {
|
|
83
|
+
/** Builder session id. Alias for `expectedReclaimSessionId`. */
|
|
84
|
+
sessionId?: string;
|
|
80
85
|
/** The `reclaimSessionId` the consumer expects (mismatch fails). */
|
|
81
86
|
expectedReclaimSessionId?: string;
|
|
82
87
|
/** The consumer's `orgId`; the result's `aud` must match it. */
|
|
83
88
|
expectedAud?: string;
|
|
84
89
|
/** Allowlist of trusted attestor ETH addresses (lowercased compared). */
|
|
85
90
|
attestorAddresses?: string[];
|
|
91
|
+
/** One explicitly trusted Verification Client issuer. */
|
|
92
|
+
issuer?: string;
|
|
93
|
+
/** Alias for `issuer`. */
|
|
94
|
+
trustedIssuer?: string;
|
|
95
|
+
/** Explicitly trusted Verification Client issuers. */
|
|
96
|
+
issuers?: string[];
|
|
97
|
+
/** Alias for `issuers`, retained for security-focused callers. */
|
|
98
|
+
trustedIssuers?: string[];
|
|
99
|
+
/** Resolve registered issuer URLs for the signed session's client id. */
|
|
100
|
+
resolveIssuers?: (verificationClientId: string) => Promise<readonly string[]> | readonly string[];
|
|
101
|
+
/** Alias for `resolveIssuers`. */
|
|
102
|
+
issuerResolver?: (verificationClientId: string) => Promise<readonly string[]> | readonly string[];
|
|
103
|
+
/** Registered issuer URLs keyed by `verificationClientId`. */
|
|
104
|
+
issuersByVerificationClientId?: Record<string, string[]>;
|
|
105
|
+
/** Explicit trusted JWKS URL. This value must come from your registry. */
|
|
106
|
+
jwksUrl?: string;
|
|
107
|
+
/** Trusted JWKS URLs keyed by `verificationClientId`. */
|
|
108
|
+
jwksUrlsByVerificationClientId?: Record<string, string>;
|
|
109
|
+
/** Resolve a registered JWKS URL for the signed session's client id. */
|
|
110
|
+
resolveJwksUrl?: (verificationClientId: string) => Promise<string | undefined> | string | undefined;
|
|
111
|
+
/** Legacy-compatible proof validation options applied to all proofs. */
|
|
112
|
+
proofValidation?: LegacyVerifyProofOptions;
|
|
113
|
+
/** Alias for `proofValidation`. */
|
|
114
|
+
proofVerification?: LegacyVerifyProofOptions;
|
|
115
|
+
/** Per-provider overrides for sessions containing different providers. */
|
|
116
|
+
proofValidationByProviderId?: Record<string, LegacyVerifyProofOptions>;
|
|
117
|
+
/** Legacy-compatible hash requirements, for the common simple case. */
|
|
118
|
+
hashes?: ProofHashRequirement[];
|
|
119
|
+
/** Resolved Builder recipes, used to derive strict proof hashes offline. */
|
|
120
|
+
proofRecipes?: BuilderProofRecipe[];
|
|
121
|
+
/** Resolve a Builder session and its immutable, resolved recipes. */
|
|
122
|
+
resolveSession?: (sessionId: string) => Promise<{
|
|
123
|
+
recipes: BuilderProofRecipe[];
|
|
124
|
+
}> | {
|
|
125
|
+
recipes: BuilderProofRecipe[];
|
|
126
|
+
};
|
|
86
127
|
/** Override the VC signer key instead of JWKS discovery via header `iss`. */
|
|
87
128
|
signerPublicKey?: string;
|
|
88
129
|
/** Injectable fetch (for JWKS); defaults to global `fetch`. */
|
|
89
130
|
fetchImpl?: typeof fetch;
|
|
90
|
-
/** Override "now" (
|
|
131
|
+
/** Override "now" (Unix seconds) when checking a legacy result's `exp`. */
|
|
91
132
|
now?: number;
|
|
133
|
+
/**
|
|
134
|
+
* Accept a legacy Builder result that contains an expired `exp` claim.
|
|
135
|
+
* New Builder results don't include `exp`; use this only while consuming
|
|
136
|
+
* older stored results whose signatures and proofs must remain verifiable.
|
|
137
|
+
*/
|
|
138
|
+
ignoreExpiration?: boolean;
|
|
92
139
|
}
|
|
93
140
|
export interface ResultProofOutcome {
|
|
94
141
|
/** Which provider this proof belongs to (results grouped per provider). */
|
|
@@ -97,6 +144,10 @@ export interface ResultProofOutcome {
|
|
|
97
144
|
proofValid: boolean;
|
|
98
145
|
reason?: string;
|
|
99
146
|
attestorAddress?: string;
|
|
147
|
+
/** Trusted only when `proofValid` is true. */
|
|
148
|
+
data?: TrustedData;
|
|
149
|
+
/** Provider-supplied data. It isn't cryptographically validated. */
|
|
150
|
+
publicData?: unknown;
|
|
100
151
|
}
|
|
101
152
|
export interface VerifyResultFullOutcome extends VerifyResultOutcome {
|
|
102
153
|
proofs?: ResultProofOutcome[];
|
|
@@ -126,11 +177,12 @@ export declare class ResultVerificationError extends Error {
|
|
|
126
177
|
constructor(outcome: VerifyResultFullOutcome);
|
|
127
178
|
}
|
|
128
179
|
/**
|
|
129
|
-
* Full consumer verification:
|
|
130
|
-
*
|
|
131
|
-
* `reclaimSessionId`/`aud
|
|
132
|
-
* proof
|
|
133
|
-
*
|
|
180
|
+
* Full consumer verification: resolves a trusted, registered issuer before
|
|
181
|
+
* using JWKS (header `iss`/`kid`), verifies the top-level JWS, checks
|
|
182
|
+
* `reclaimSessionId`/`aud` and any legacy `exp`, then verifies each exact
|
|
183
|
+
* legacy proof.
|
|
184
|
+
* Network access (JWKS and optional provider resolution) makes this async.
|
|
185
|
+
* Use {@link verifyResult} for the offline primitive.
|
|
134
186
|
*/
|
|
135
187
|
export declare function verifyResultFull(jws: Jws, opts?: VerifyResultFullOptions): Promise<VerifyResultFullOutcome>;
|
|
136
188
|
/** Decrypt-and-verify in one step. */
|