@reclaimprotocol/client 0.1.29 → 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/src/types/openapi.ts
CHANGED
|
@@ -83,6 +83,8 @@ export type ProblemDetails = components['schemas']['Problem'] & {
|
|
|
83
83
|
// `@reclaimprotocol/client` instead of reaching into the generated file.
|
|
84
84
|
export type VerificationSession =
|
|
85
85
|
components['schemas']['VerificationSession']
|
|
86
|
+
export type VerificationRuntimeConfig =
|
|
87
|
+
components['schemas']['VerificationRuntimeConfig']
|
|
86
88
|
export type VerificationStatus = components['schemas']['VerificationStatus']
|
|
87
89
|
export type VerificationEventRecord =
|
|
88
90
|
components['schemas']['VerificationEventRecord']
|
|
@@ -105,4 +107,4 @@ export type OrgTokenMeta = components['schemas']['OrgTokenMeta']
|
|
|
105
107
|
export type Organization = components['schemas']['Organization']
|
|
106
108
|
export type CreateProviderVersionRequest =
|
|
107
109
|
components['schemas']['CreateProviderVersionRequest']
|
|
108
|
-
export type ProviderVersionRef = components['schemas']['ProviderVersionRef']
|
|
110
|
+
export type ProviderVersionRef = components['schemas']['ProviderVersionRef']
|
|
@@ -1,22 +1,36 @@
|
|
|
1
|
+
import { DEFAULT_BASE_URL } from './consts.ts'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
4
|
+
* Production Verification Client URLs. Use `custom(uri)` for a separately
|
|
5
|
+
* registered client or a non-production Builder deployment.
|
|
6
|
+
*
|
|
7
|
+
* Builder resolves a session's `verificationClientUrl` by EXACT string match
|
|
8
|
+
* against the registered `uri` and 400s for anything unregistered — these
|
|
9
|
+
* constants MUST track `packages/app/src/verification/builtin-clients.ts`'s
|
|
10
|
+
* `clientUrl()` seed exactly, or a consumer passing one of these gets a
|
|
11
|
+
* failed session creation instead of a warning (see
|
|
12
|
+
* `tests/verification-clients.test.ts`, which pins all six against that
|
|
13
|
+
* seed).
|
|
3
14
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
15
|
+
* As of migration `20260917130000000.share-routes-uris.sql`, all six
|
|
16
|
+
* built-ins (`builder` included) share one uniform shape,
|
|
17
|
+
* `{origin}/share/{name}` — what each `/share/{name}` page actually does
|
|
18
|
+
* differs (a redirect for `builder`/`portals`, the QR/App-Clip/deep-link
|
|
19
|
+
* launcher for `verifier-app`/`reclaimBrowserExtension`, a static
|
|
20
|
+
* informational page for `inAppSdk`/`zkFetch` — see
|
|
21
|
+
* `packages/app/src/routes/SharePage.tsx`), but the registry entry is
|
|
22
|
+
* uniform. `../launch/target.ts`'s `parseFlowUrl` is what turns one of these
|
|
23
|
+
* back into a client.
|
|
6
24
|
*/
|
|
7
|
-
export const
|
|
8
|
-
builder:
|
|
9
|
-
portals:
|
|
10
|
-
verifierApp:
|
|
11
|
-
inAppSdk:
|
|
12
|
-
reclaimBrowserExtension:
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/** Deployment-specific registered URLs keyed by a known client name. */
|
|
20
|
-
export type KnownVerificationClientUrlMap = Partial<
|
|
21
|
-
Record<KnownVerificationClientName, string>
|
|
22
|
-
>
|
|
25
|
+
export const VerificationClient = Object.freeze({
|
|
26
|
+
builder: `${DEFAULT_BASE_URL}/share/builder`,
|
|
27
|
+
portals: `${DEFAULT_BASE_URL}/share/portals`,
|
|
28
|
+
verifierApp: `${DEFAULT_BASE_URL}/share/verifier-app`,
|
|
29
|
+
inAppSdk: `${DEFAULT_BASE_URL}/share/inapp-sdk`,
|
|
30
|
+
reclaimBrowserExtension:
|
|
31
|
+
`${DEFAULT_BASE_URL}/share/reclaim-browser-extension`,
|
|
32
|
+
zkFetch: `${DEFAULT_BASE_URL}/share/zkfetch`,
|
|
33
|
+
custom(uri: string) {
|
|
34
|
+
return uri
|
|
35
|
+
},
|
|
36
|
+
})
|
|
@@ -7,9 +7,10 @@ import {
|
|
|
7
7
|
GCP_CONFIDENTIAL_SPACE_ISSUER,
|
|
8
8
|
GCP_CONFIDENTIAL_SPACE_ROOT_CA,
|
|
9
9
|
} from './proof-trust-constants.ts'
|
|
10
|
-
import
|
|
11
|
-
AttestorClaimAttestation,
|
|
12
|
-
|
|
10
|
+
import {
|
|
11
|
+
type AttestorClaimAttestation,
|
|
12
|
+
type ClaimAttestationType,
|
|
13
|
+
type ReclaimRequestProof as Proof
|
|
13
14
|
} from './proof-types.ts'
|
|
14
15
|
import { legacyErrorClass } from './proof-verification-error.ts'
|
|
15
16
|
const AttestorTeeVerificationError = legacyErrorClass('AttestorTeeVerificationError')
|
|
@@ -238,6 +239,23 @@ export type AttestorTeeAttestationConfig = {
|
|
|
238
239
|
expectedImageDigests?: string[]
|
|
239
240
|
}
|
|
240
241
|
|
|
242
|
+
/** A witness's attestation kind. Required: every client sets it explicitly. */
|
|
243
|
+
export function claimAttestationType(
|
|
244
|
+
att: AttestorClaimAttestation,
|
|
245
|
+
): ClaimAttestationType {
|
|
246
|
+
return att.type
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** What `runAttestorTeeVerification` actually checked, per witness. */
|
|
250
|
+
export interface AttestorTeeVerificationSummary {
|
|
251
|
+
/** Witnesses whose `attestation_report` was verified (and pinned, when
|
|
252
|
+
* `expectedImageDigests` was set). */
|
|
253
|
+
verified: number
|
|
254
|
+
/** Witnesses that carried no `attestation_report`. Their attestor address
|
|
255
|
+
* and claim signature were still verified. */
|
|
256
|
+
unattested: number
|
|
257
|
+
}
|
|
258
|
+
|
|
241
259
|
function normalizeAttestorAddress(address: string): string {
|
|
242
260
|
return address.trim().toLowerCase()
|
|
243
261
|
}
|
|
@@ -248,13 +266,16 @@ function normalizeSignature(sig: string): string {
|
|
|
248
266
|
|
|
249
267
|
async function verifyAttestorTeeForProof(
|
|
250
268
|
proof: Proof,
|
|
251
|
-
config: AttestorTeeAttestationConfig
|
|
269
|
+
config: AttestorTeeAttestationConfig,
|
|
270
|
+
summary: AttestorTeeVerificationSummary,
|
|
252
271
|
): Promise<void> {
|
|
253
272
|
if(!proof.witnesses || proof.witnesses.length === 0) {
|
|
254
273
|
throw new AttestorTeeVerificationError('Proof has no witnesses')
|
|
255
274
|
}
|
|
256
275
|
|
|
257
|
-
const expectedDigests = config.expectedImageDigests?.map(
|
|
276
|
+
const expectedDigests = config.expectedImageDigests?.map(
|
|
277
|
+
(digest) => digest.trim().toLowerCase(),
|
|
278
|
+
)
|
|
258
279
|
|
|
259
280
|
const proofSignatures = new Set((proof.signatures || []).map(normalizeSignature))
|
|
260
281
|
const claimSignData = createSignDataForClaim(proof.claimData)
|
|
@@ -295,6 +316,28 @@ async function verifyAttestorTeeForProof(
|
|
|
295
316
|
)
|
|
296
317
|
}
|
|
297
318
|
|
|
319
|
+
// A `confidential-space` witness (portals) proves its runtime through
|
|
320
|
+
// `tee_attestation`, which `runTeeVerification` checks against
|
|
321
|
+
// `context.attestationNonce` — a different chain with a different nonce
|
|
322
|
+
// binding. The address and signature bindings above still applied; the
|
|
323
|
+
// attestor-report checks below are not this type's evidence.
|
|
324
|
+
if(claimAttestationType(att) === 'confidential-space') {
|
|
325
|
+
summary.unattested++
|
|
326
|
+
continue
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// An attestor that signs claims without returning its own Confidential
|
|
330
|
+
// Space attestation is the common production shape — the in-app SDK
|
|
331
|
+
// (via reclaim-tee-operator-flutter) emits witnesses carrying only
|
|
332
|
+
// `attestor_address` and `claim_signature`. The bindings above are what
|
|
333
|
+
// tie the claim to a trusted attestor address; the report, when present,
|
|
334
|
+
// additionally proves that attestor ran a pinned image. Absent one,
|
|
335
|
+
// there is nothing to check and nothing to pin.
|
|
336
|
+
if(!att.attestation_report) {
|
|
337
|
+
summary.unattested++
|
|
338
|
+
continue
|
|
339
|
+
}
|
|
340
|
+
|
|
298
341
|
const result = await verifyAttestorTeeAttestation(att.attestation_report, witness.id)
|
|
299
342
|
if(!result.isVerified) {
|
|
300
343
|
throw new AttestorTeeVerificationError(
|
|
@@ -302,6 +345,8 @@ async function verifyAttestorTeeForProof(
|
|
|
302
345
|
)
|
|
303
346
|
}
|
|
304
347
|
|
|
348
|
+
summary.verified++
|
|
349
|
+
|
|
305
350
|
if(expectedDigests && expectedDigests.length > 0) {
|
|
306
351
|
if(!result.imageDigest) {
|
|
307
352
|
throw new AttestorTeeVerificationError(
|
|
@@ -309,7 +354,7 @@ async function verifyAttestorTeeForProof(
|
|
|
309
354
|
)
|
|
310
355
|
}
|
|
311
356
|
|
|
312
|
-
if(!expectedDigests.includes(result.imageDigest)) {
|
|
357
|
+
if(!expectedDigests.includes(result.imageDigest.trim().toLowerCase())) {
|
|
313
358
|
throw new AttestorTeeVerificationError(
|
|
314
359
|
`Attestor image digest ${result.imageDigest} for witness ${witness.id} is not in expectedImageDigests`
|
|
315
360
|
)
|
|
@@ -329,12 +374,18 @@ async function verifyAttestorTeeForProof(
|
|
|
329
374
|
* 3. Recovers the signer of `claim_signature` from the claim data and
|
|
330
375
|
* asserts it equals `witness.id` (binds the signature to the
|
|
331
376
|
* attestor that the TEE attestation will cover).
|
|
377
|
+
*
|
|
378
|
+
* Steps 4 and 5 apply only to a witness that carries an
|
|
379
|
+
* `attestation_report`, which is optional — see `AttestorClaimAttestation`:
|
|
332
380
|
* 4. Calls `verifyAttestorTeeAttestation` to validate the JWT against
|
|
333
381
|
* the pinned GCP Confidential Space Root CA and the attestor-key
|
|
334
382
|
* nonce.
|
|
335
383
|
* 5. If `expectedImageDigests` is provided, asserts the attestation's
|
|
336
384
|
* container image digest is in the allowlist.
|
|
337
385
|
*
|
|
386
|
+
* The returned summary says which witnesses were attested, so a caller can
|
|
387
|
+
* tell "every report checked out" from "there were no reports to check".
|
|
388
|
+
*
|
|
338
389
|
* Node-only (uses node:crypto), like `verifyTeeAttestation`.
|
|
339
390
|
*
|
|
340
391
|
* @param proofs - The proofs to verify.
|
|
@@ -343,15 +394,22 @@ async function verifyAttestorTeeForProof(
|
|
|
343
394
|
export async function runAttestorTeeVerification(
|
|
344
395
|
proofs: Proof[],
|
|
345
396
|
config: AttestorTeeAttestationConfig = {}
|
|
346
|
-
): Promise<
|
|
397
|
+
): Promise<AttestorTeeVerificationSummary> {
|
|
347
398
|
if(!proofs || proofs.length === 0) {
|
|
348
399
|
throw new AttestorTeeVerificationError('No proofs provided for attestor TEE verification')
|
|
349
400
|
}
|
|
350
401
|
|
|
402
|
+
const summary: AttestorTeeVerificationSummary = {
|
|
403
|
+
verified: 0,
|
|
404
|
+
unattested: 0,
|
|
405
|
+
}
|
|
406
|
+
|
|
351
407
|
try {
|
|
352
408
|
for(const proof of proofs) {
|
|
353
|
-
await verifyAttestorTeeForProof(proof, config)
|
|
409
|
+
await verifyAttestorTeeForProof(proof, config, summary)
|
|
354
410
|
}
|
|
411
|
+
|
|
412
|
+
return summary
|
|
355
413
|
} catch(error) {
|
|
356
414
|
if(error instanceof AttestorTeeVerificationError) {
|
|
357
415
|
throw error
|
package/src/verify-tee.ts
CHANGED
|
@@ -3,7 +3,10 @@ import { keccak_256 } from '@noble/hashes/sha3.js'
|
|
|
3
3
|
import { webcrypto } from 'node:crypto'
|
|
4
4
|
import { recoverEip191Address } from './eip191.ts'
|
|
5
5
|
import { GCP_CONFIDENTIAL_SPACE_ISSUER } from './proof-trust-constants.ts'
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
ReclaimRequestProof as Proof,
|
|
8
|
+
TeeAttestation
|
|
9
|
+
} from './proof-types.ts'
|
|
7
10
|
import { legacyErrorClass } from './proof-verification-error.ts'
|
|
8
11
|
import {
|
|
9
12
|
deriveLegacyTeeNonce,
|
|
@@ -352,21 +355,28 @@ function getProofVersion(teeAttestation: TeeAttestation): string | undefined {
|
|
|
352
355
|
return teeAttestation.proof_version ?? teeAttestation.proofVersion
|
|
353
356
|
}
|
|
354
357
|
|
|
355
|
-
function assertProofShape(
|
|
356
|
-
|
|
357
|
-
|
|
358
|
+
function assertProofShape(
|
|
359
|
+
teeAttestation: Record<string, unknown>,
|
|
360
|
+
): asserts teeAttestation is Record<string, unknown> & TeeAttestation {
|
|
361
|
+
const reportedError = teeAttestation.error
|
|
362
|
+
if(isRecord(reportedError)) {
|
|
363
|
+
throw new Error(`${reportedError.code}: ${reportedError.message}`)
|
|
358
364
|
}
|
|
359
365
|
|
|
360
|
-
const proofVersion =
|
|
366
|
+
const proofVersion = teeAttestation.proof_version
|
|
367
|
+
?? teeAttestation.proofVersion
|
|
361
368
|
assert(typeof proofVersion === 'string' && SUPPORTED_PROOF_VERSIONS.includes(proofVersion), `unexpected proof version: ${proofVersion}`)
|
|
362
369
|
assert(teeAttestation.tee_provider === EXPECTED_TEE_PROVIDER, `unexpected tee provider: ${teeAttestation.tee_provider}`)
|
|
363
370
|
assert(teeAttestation.tee_technology === EXPECTED_TEE_TECHNOLOGY, `unexpected tee technology: ${teeAttestation.tee_technology}`)
|
|
364
371
|
assert(typeof teeAttestation.nonce === 'string' && teeAttestation.nonce.length > 0, 'tee attestation nonce missing')
|
|
365
372
|
assert(typeof teeAttestation.timestamp === 'string' && teeAttestation.timestamp.length > 0, 'tee attestation timestamp missing')
|
|
366
373
|
assert(!Number.isNaN(Date.parse(teeAttestation.timestamp)), 'tee attestation timestamp is invalid')
|
|
367
|
-
assert(
|
|
368
|
-
assert(typeof teeAttestation.
|
|
369
|
-
assert(
|
|
374
|
+
assert(isRecord(teeAttestation.workload), 'workload missing')
|
|
375
|
+
assert(typeof teeAttestation.workload.image_digest === 'string' && teeAttestation.workload.image_digest.length > 0, 'workload image digest missing')
|
|
376
|
+
assert(isRecord(teeAttestation.verifier), 'verifier missing')
|
|
377
|
+
assert(typeof teeAttestation.verifier.image_digest === 'string' && teeAttestation.verifier.image_digest.length > 0, 'verifier image digest missing')
|
|
378
|
+
assert(isRecord(teeAttestation.attestation), 'attestation missing')
|
|
379
|
+
assert(typeof teeAttestation.attestation.token === 'string' && teeAttestation.attestation.token.length > 0, 'attestation token missing')
|
|
370
380
|
}
|
|
371
381
|
|
|
372
382
|
async function computeDigestBinding(teeAttestation: TeeAttestation): Promise<string> {
|
|
@@ -439,28 +449,30 @@ export async function verifyTeeAttestation(
|
|
|
439
449
|
try {
|
|
440
450
|
const appId = legacyApplicationIdFromPrivateKey(appSecret)
|
|
441
451
|
|
|
442
|
-
let teeAttestation = proof
|
|
452
|
+
let teeAttestation = teeAttestationOf(proof)
|
|
443
453
|
if(!teeAttestation) {
|
|
444
454
|
throw new Error('Missing teeAttestation in proof')
|
|
445
455
|
}
|
|
446
456
|
|
|
447
457
|
if(typeof teeAttestation === 'string') {
|
|
448
|
-
teeAttestation = JSON.parse(teeAttestation)
|
|
458
|
+
teeAttestation = JSON.parse(teeAttestation)
|
|
449
459
|
}
|
|
450
460
|
|
|
461
|
+
assert(isRecord(teeAttestation), 'TEE attestation must be an object')
|
|
451
462
|
assertProofShape(teeAttestation)
|
|
463
|
+
const supportedTeeAttestation = teeAttestation
|
|
452
464
|
|
|
453
465
|
const { parsedContext, nonceDataObj, expectedNonce } = parseProofContext(proof)
|
|
454
466
|
verifyApplicationAndSessionBinding(proof, parsedContext, nonceDataObj, appId)
|
|
455
467
|
verifyNonceMaterial(expectedNonce, nonceDataObj, appSecret)
|
|
456
468
|
|
|
457
469
|
const cleanExpectedNonce = normalizeHex(expectedNonce)
|
|
458
|
-
const cleanTeeNonce = normalizeHex(
|
|
470
|
+
const cleanTeeNonce = normalizeHex(supportedTeeAttestation.nonce)
|
|
459
471
|
assert(cleanTeeNonce.length > 0, 'TEE attestation nonce is empty')
|
|
460
472
|
assert(isHex(cleanTeeNonce), 'TEE attestation nonce is not valid hex')
|
|
461
473
|
assert(cleanTeeNonce === cleanExpectedNonce, `Nonce Mismatch! Expected ${cleanExpectedNonce}, got ${cleanTeeNonce}`)
|
|
462
474
|
|
|
463
|
-
await verifyGcpClaims(
|
|
475
|
+
await verifyGcpClaims(supportedTeeAttestation, cleanExpectedNonce)
|
|
464
476
|
|
|
465
477
|
return { isVerified: true }
|
|
466
478
|
} catch(error) {
|
|
@@ -479,9 +491,28 @@ export async function verifyTeeAttestation(
|
|
|
479
491
|
* @param config - TEE attestation configuration containing the app secret
|
|
480
492
|
* @throws {TeeVerificationError} When TEE data is missing or verification fails
|
|
481
493
|
*/
|
|
494
|
+
/**
|
|
495
|
+
* The TEE attestation to verify for a proof.
|
|
496
|
+
*
|
|
497
|
+
* Builder-mode proofs carry it on the witness, inside a `confidential-space`
|
|
498
|
+
* `claimAttestation`, so every verification client presents the same envelope.
|
|
499
|
+
* Legacy proofs carry it at the proof's top level. Prefer the witness copy and
|
|
500
|
+
* fall back, so both shapes verify through one path.
|
|
501
|
+
*/
|
|
502
|
+
export function teeAttestationOf(proof: Proof): unknown {
|
|
503
|
+
for(const witness of proof.witnesses ?? []) {
|
|
504
|
+
const att = witness.claimAttestation
|
|
505
|
+
if(att?.type === 'confidential-space' && att.tee_attestation) {
|
|
506
|
+
return att.tee_attestation
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return proof.teeAttestation
|
|
511
|
+
}
|
|
512
|
+
|
|
482
513
|
export async function runTeeVerification(proofs: Proof[], config: TeeAttestationConfig): Promise<void> {
|
|
483
514
|
const hasTeeData = proofs.every(proof => {
|
|
484
|
-
if(proof
|
|
515
|
+
if(teeAttestationOf(proof)) {
|
|
485
516
|
return true
|
|
486
517
|
}
|
|
487
518
|
|
package/lib/api-client.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { IOperationArgs, IOperationId, IOperationResponse } from './types/openapi.ts';
|
|
2
|
-
export type ReclaimApiClientOptions = {
|
|
3
|
-
/** Backend base URL. Defaults to the public Builder API. */
|
|
4
|
-
baseUrl?: string;
|
|
5
|
-
/** Bearer token for authenticated operations. */
|
|
6
|
-
token?: string;
|
|
7
|
-
/** Custom fetch implementation. Defaults to global `fetch`. */
|
|
8
|
-
fetch?: typeof fetch;
|
|
9
|
-
/** Extra headers merged into every request. */
|
|
10
|
-
headers?: Record<string, string>;
|
|
11
|
-
/** Error mapper for unsuccessful responses. */
|
|
12
|
-
handleErrorResponse?: (response: Response) => Promise<never>;
|
|
13
|
-
};
|
|
14
|
-
/**
|
|
15
|
-
* Browser-safe typed HTTP client generated from the Builder OpenAPI contract.
|
|
16
|
-
*
|
|
17
|
-
* This transport doesn't import proof, signing, attestor, or Node-only code.
|
|
18
|
-
* Validation remains in the OpenAPI schema; this client only serializes the
|
|
19
|
-
* typed request and maps unsuccessful responses.
|
|
20
|
-
*/
|
|
21
|
-
export declare class ReclaimApiClient {
|
|
22
|
-
#private;
|
|
23
|
-
readonly baseUrl: string;
|
|
24
|
-
constructor(opts?: ReclaimApiClientOptions);
|
|
25
|
-
setToken(token: string | undefined): void;
|
|
26
|
-
getToken(): string | undefined;
|
|
27
|
-
call<T extends IOperationId>(operationId: T, ...argsRest: {} extends IOperationArgs<T> ? [args?: IOperationArgs<T>] : [args: IOperationArgs<T>]): Promise<{
|
|
28
|
-
data: IOperationResponse<T>;
|
|
29
|
-
response: Response;
|
|
30
|
-
}>;
|
|
31
|
-
}
|
package/lib/api-client.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
-
};
|
|
7
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
-
};
|
|
12
|
-
var _ReclaimApiClient_token, _ReclaimApiClient_fetch, _ReclaimApiClient_headers, _ReclaimApiClient_handleErrorResponse;
|
|
13
|
-
import { API_DEFAULT_BASE_URL } from "./api-consts.js";
|
|
14
|
-
import { throwApiProblemError } from "./api-errors.js";
|
|
15
|
-
import { ROUTES } from "./routes.gen.js";
|
|
16
|
-
/**
|
|
17
|
-
* Browser-safe typed HTTP client generated from the Builder OpenAPI contract.
|
|
18
|
-
*
|
|
19
|
-
* This transport doesn't import proof, signing, attestor, or Node-only code.
|
|
20
|
-
* Validation remains in the OpenAPI schema; this client only serializes the
|
|
21
|
-
* typed request and maps unsuccessful responses.
|
|
22
|
-
*/
|
|
23
|
-
export class ReclaimApiClient {
|
|
24
|
-
constructor(opts = {}) {
|
|
25
|
-
_ReclaimApiClient_token.set(this, void 0);
|
|
26
|
-
_ReclaimApiClient_fetch.set(this, void 0);
|
|
27
|
-
_ReclaimApiClient_headers.set(this, void 0);
|
|
28
|
-
_ReclaimApiClient_handleErrorResponse.set(this, void 0);
|
|
29
|
-
this.baseUrl = (opts.baseUrl || API_DEFAULT_BASE_URL).replace(/\/$/, '');
|
|
30
|
-
__classPrivateFieldSet(this, _ReclaimApiClient_token, opts.token, "f");
|
|
31
|
-
__classPrivateFieldSet(this, _ReclaimApiClient_fetch, opts.fetch || fetch, "f");
|
|
32
|
-
__classPrivateFieldSet(this, _ReclaimApiClient_headers, {
|
|
33
|
-
Accept: 'application/json',
|
|
34
|
-
...opts.headers,
|
|
35
|
-
}, "f");
|
|
36
|
-
__classPrivateFieldSet(this, _ReclaimApiClient_handleErrorResponse, opts.handleErrorResponse
|
|
37
|
-
|| throwApiProblemError, "f");
|
|
38
|
-
}
|
|
39
|
-
setToken(token) {
|
|
40
|
-
__classPrivateFieldSet(this, _ReclaimApiClient_token, token, "f");
|
|
41
|
-
}
|
|
42
|
-
getToken() {
|
|
43
|
-
return __classPrivateFieldGet(this, _ReclaimApiClient_token, "f");
|
|
44
|
-
}
|
|
45
|
-
async call(operationId, ...argsRest) {
|
|
46
|
-
const args = argsRest[0] ?? {};
|
|
47
|
-
const op = ROUTES[operationId];
|
|
48
|
-
// A browser entry point can't depend on node:assert. This guard also
|
|
49
|
-
// protects JavaScript callers that bypass the generated operation type.
|
|
50
|
-
if (!op) {
|
|
51
|
-
throw Object.assign(new Error(`Unknown operation: ${String(operationId)}`), { name: 'UnknownOperationError' });
|
|
52
|
-
}
|
|
53
|
-
let url = `${this.baseUrl}${op.path}`;
|
|
54
|
-
if (args.params) {
|
|
55
|
-
for (const [key, value] of Object.entries(args.params)) {
|
|
56
|
-
url = url.replace(`{${key}}`, String(value));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
if (args.query && Object.keys(args.query).length > 0) {
|
|
60
|
-
const search = new URLSearchParams();
|
|
61
|
-
for (const [key, value] of Object.entries(args.query)) {
|
|
62
|
-
if (value === undefined || value === null) {
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
if (Array.isArray(value)) {
|
|
66
|
-
for (const item of value) {
|
|
67
|
-
search.append(key, String(item));
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
search.append(key, String(value));
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
url += `?${search.toString()}`;
|
|
75
|
-
}
|
|
76
|
-
const headers = {
|
|
77
|
-
...__classPrivateFieldGet(this, _ReclaimApiClient_headers, "f"),
|
|
78
|
-
Accept: 'application/json',
|
|
79
|
-
};
|
|
80
|
-
if (__classPrivateFieldGet(this, _ReclaimApiClient_token, "f")) {
|
|
81
|
-
headers['Authorization'] = `Bearer ${__classPrivateFieldGet(this, _ReclaimApiClient_token, "f")}`;
|
|
82
|
-
}
|
|
83
|
-
// Spec-declared headers win over client defaults, but not authorization.
|
|
84
|
-
if (args.headers) {
|
|
85
|
-
for (const [key, value] of Object.entries(args.headers)) {
|
|
86
|
-
if (value === undefined || value === null) {
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
headers[key] = String(value);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
const init = {
|
|
93
|
-
method: op.method.toUpperCase(),
|
|
94
|
-
headers,
|
|
95
|
-
};
|
|
96
|
-
const method = op.method.toLowerCase();
|
|
97
|
-
if (method !== 'get' && method !== 'head') {
|
|
98
|
-
init.body = JSON.stringify(args.body ?? {});
|
|
99
|
-
headers['Content-Type'] = 'application/json';
|
|
100
|
-
}
|
|
101
|
-
const res = await __classPrivateFieldGet(this, _ReclaimApiClient_fetch, "f").call(this, url, init);
|
|
102
|
-
if (!res.ok) {
|
|
103
|
-
await __classPrivateFieldGet(this, _ReclaimApiClient_handleErrorResponse, "f").call(this, res);
|
|
104
|
-
}
|
|
105
|
-
if (res.status === 204 || res.headers.get('content-length') === '0') {
|
|
106
|
-
return { data: undefined, response: res };
|
|
107
|
-
}
|
|
108
|
-
const data = await res.json();
|
|
109
|
-
return { data, response: res };
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
_ReclaimApiClient_token = new WeakMap(), _ReclaimApiClient_fetch = new WeakMap(), _ReclaimApiClient_headers = new WeakMap(), _ReclaimApiClient_handleErrorResponse = new WeakMap();
|
package/lib/api-consts.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
/** Public Builder API origin used when a caller doesn't provide `baseUrl`. */
|
|
2
|
-
export declare const API_DEFAULT_BASE_URL = "https://build.reclaimprotocol.org";
|
|
3
|
-
/** Media type defined by RFC 9457 for HTTP problem details. */
|
|
4
|
-
export declare const PROBLEM_CONTENT_TYPE = "application/problem+json";
|
package/lib/api-consts.js
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
/** Public Builder API origin used when a caller doesn't provide `baseUrl`. */
|
|
2
|
-
export const API_DEFAULT_BASE_URL = 'https://build.reclaimprotocol.org';
|
|
3
|
-
/** Media type defined by RFC 9457 for HTTP problem details. */
|
|
4
|
-
export const PROBLEM_CONTENT_TYPE = 'application/problem+json';
|
package/lib/api-errors.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { ProblemDetails } from './types/openapi.ts';
|
|
2
|
-
/** A browser-safe HTTP error with the Builder's RFC 9457 response attached. */
|
|
3
|
-
export type ApiProblemError = Error & {
|
|
4
|
-
readonly name: 'ApiProblemError';
|
|
5
|
-
readonly status: number;
|
|
6
|
-
readonly problem: ProblemDetails;
|
|
7
|
-
};
|
|
8
|
-
/** Returns whether an unknown thrown value is an {@link ApiProblemError}. */
|
|
9
|
-
export declare function isApiProblemError(error: unknown): error is ApiProblemError;
|
|
10
|
-
/** Reads an RFC 9457 response, with a status-only fallback for other bodies. */
|
|
11
|
-
export declare function readProblemDetails(res: Response): Promise<ProblemDetails>;
|
|
12
|
-
/** Throws a browser-safe error for an unsuccessful Builder response. */
|
|
13
|
-
export declare function throwApiProblemError(res: Response): Promise<never>;
|
package/lib/api-errors.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { PROBLEM_CONTENT_TYPE } from "./api-consts.js";
|
|
2
|
-
/** Returns whether an unknown thrown value is an {@link ApiProblemError}. */
|
|
3
|
-
export function isApiProblemError(error) {
|
|
4
|
-
return error instanceof Error
|
|
5
|
-
&& error.name === 'ApiProblemError'
|
|
6
|
-
&& 'status' in error
|
|
7
|
-
&& typeof error.status === 'number'
|
|
8
|
-
&& 'problem' in error;
|
|
9
|
-
}
|
|
10
|
-
/** Reads an RFC 9457 response, with a status-only fallback for other bodies. */
|
|
11
|
-
export async function readProblemDetails(res) {
|
|
12
|
-
const contentType = res.headers.get('Content-Type') || '';
|
|
13
|
-
if (contentType.includes(PROBLEM_CONTENT_TYPE)) {
|
|
14
|
-
return await res.json();
|
|
15
|
-
}
|
|
16
|
-
return {
|
|
17
|
-
type: 'about:blank',
|
|
18
|
-
status: res.status,
|
|
19
|
-
title: res.statusText,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
/** Throws a browser-safe error for an unsuccessful Builder response. */
|
|
23
|
-
export async function throwApiProblemError(res) {
|
|
24
|
-
const problem = await readProblemDetails(res);
|
|
25
|
-
throw Object.assign(new Error(problem.title), {
|
|
26
|
-
name: 'ApiProblemError',
|
|
27
|
-
status: res.status,
|
|
28
|
-
problem,
|
|
29
|
-
});
|
|
30
|
-
}
|
package/lib/eip712.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
declare const TYPES: {
|
|
2
|
-
readonly CreateVerificationSession: readonly [readonly ["providerId", "string"], readonly ["version", "string"], readonly ["context", "string"], readonly ["verificationClientUrl", "string"], readonly ["timestamp", "uint256"]];
|
|
3
|
-
readonly CreateCallbackSubscription: readonly [readonly ["callbackUrl", "string"], readonly ["events", "string"], readonly ["publicKey", "string"], readonly ["address", "string"], readonly ["timestamp", "uint256"]];
|
|
4
|
-
readonly VerificationAuth: readonly [readonly ["timestamp", "uint256"]];
|
|
5
|
-
};
|
|
6
|
-
export type Eip712PrimaryType = keyof typeof TYPES;
|
|
7
|
-
/** Deterministic JSON via RFC 8785 (JSON Canonicalization Scheme): object
|
|
8
|
-
* keys sorted recursively, no whitespace, canonical number/string forms.
|
|
9
|
-
* Both signer and verifier canonicalize `context`/`events` this way so the
|
|
10
|
-
* string fields hash identically. Single source of truth for canonical
|
|
11
|
-
* stringification in the repo — `contentHash` (result binding) uses it too;
|
|
12
|
-
* do not reimplement with sorted JSON.stringify. */
|
|
13
|
-
export declare function canonicalJson(value: unknown): string;
|
|
14
|
-
/** EIP-712 digest: keccak256(0x1901 ‖ domainSeparator ‖ structHash). */
|
|
15
|
-
export declare function eip712Digest(primaryType: Eip712PrimaryType, message: Record<string, string | bigint>): Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>;
|
|
16
|
-
/** Recover the signer of a typed message, or null on failure. Used by the
|
|
17
|
-
* Builder (`packages/app`) to verify legacy typed-data signatures; the client
|
|
18
|
-
* itself no longer signs — all consumer auth is the org bearer token. */
|
|
19
|
-
export declare function recoverTypedSigner(primaryType: Eip712PrimaryType, message: Record<string, string | bigint>, signature: string): string | null;
|
|
20
|
-
export {};
|
package/lib/eip712.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EIP-712 typed-data primitives for the legacy verification-session signature
|
|
3
|
-
* scheme — kept for the Builder verifier (`eip712Digest` + `recoverTypedSigner`,
|
|
4
|
-
* imported by `packages/app`). The consumer client no longer signs (all auth is
|
|
5
|
-
* the org bearer token), so there is no `buildAuthHeaders` here anymore. The
|
|
6
|
-
* typed data is tiny (only `string` / `uint256` / `address` fields), so it's
|
|
7
|
-
* hashed directly with keccak-256; recovery is standard secp256k1 over the
|
|
8
|
-
* digest (`prehash:false`, see ./secp.ts).
|
|
9
|
-
*/
|
|
10
|
-
import { badData } from '@hapi/boom';
|
|
11
|
-
import * as canonicalizeMod from 'canonicalize';
|
|
12
|
-
import assert from 'node:assert';
|
|
13
|
-
import { hexToBytes } from "./encoding.js";
|
|
14
|
-
import { keccak_256, recoverAddress } from "./secp.js";
|
|
15
|
-
// canonicalize ships as a CJS default export; resolve it across interop
|
|
16
|
-
// modes (same dance as ajv-formats in packages/app).
|
|
17
|
-
const canonicalize = (canonicalizeMod.default ??
|
|
18
|
-
canonicalizeMod);
|
|
19
|
-
const DOMAIN = {
|
|
20
|
-
name: 'Reclaim Builder',
|
|
21
|
-
version: '1',
|
|
22
|
-
chainId: 1n,
|
|
23
|
-
verifyingContract: '0x0000000000000000000000000000000000000000',
|
|
24
|
-
};
|
|
25
|
-
const TYPES = {
|
|
26
|
-
CreateVerificationSession: [
|
|
27
|
-
['providerId', 'string'],
|
|
28
|
-
['version', 'string'],
|
|
29
|
-
['context', 'string'],
|
|
30
|
-
['verificationClientUrl', 'string'],
|
|
31
|
-
['timestamp', 'uint256'],
|
|
32
|
-
],
|
|
33
|
-
CreateCallbackSubscription: [
|
|
34
|
-
['callbackUrl', 'string'],
|
|
35
|
-
['events', 'string'],
|
|
36
|
-
['publicKey', 'string'],
|
|
37
|
-
['address', 'string'],
|
|
38
|
-
['timestamp', 'uint256'],
|
|
39
|
-
],
|
|
40
|
-
VerificationAuth: [['timestamp', 'uint256']],
|
|
41
|
-
};
|
|
42
|
-
/** Deterministic JSON via RFC 8785 (JSON Canonicalization Scheme): object
|
|
43
|
-
* keys sorted recursively, no whitespace, canonical number/string forms.
|
|
44
|
-
* Both signer and verifier canonicalize `context`/`events` this way so the
|
|
45
|
-
* string fields hash identically. Single source of truth for canonical
|
|
46
|
-
* stringification in the repo — `contentHash` (result binding) uses it too;
|
|
47
|
-
* do not reimplement with sorted JSON.stringify. */
|
|
48
|
-
export function canonicalJson(value) {
|
|
49
|
-
const out = canonicalize(value);
|
|
50
|
-
assert(out !== undefined, badData('value is not JSON-serializable'));
|
|
51
|
-
return out;
|
|
52
|
-
}
|
|
53
|
-
function utf8(s) {
|
|
54
|
-
return new TextEncoder().encode(s);
|
|
55
|
-
}
|
|
56
|
-
function uint256(value) {
|
|
57
|
-
const out = new Uint8Array(32);
|
|
58
|
-
let v = value;
|
|
59
|
-
for (let i = 31; i >= 0 && v > 0n; i--) {
|
|
60
|
-
out[i] = Number(v & 0xffn);
|
|
61
|
-
v >>= 8n;
|
|
62
|
-
}
|
|
63
|
-
return out;
|
|
64
|
-
}
|
|
65
|
-
function addressWord(addr) {
|
|
66
|
-
const out = new Uint8Array(32);
|
|
67
|
-
out.set(hexToBytes(addr), 12);
|
|
68
|
-
return out;
|
|
69
|
-
}
|
|
70
|
-
function concat(...parts) {
|
|
71
|
-
const total = parts.reduce((n, p) => n + p.length, 0);
|
|
72
|
-
const out = new Uint8Array(total);
|
|
73
|
-
let off = 0;
|
|
74
|
-
for (const p of parts) {
|
|
75
|
-
out.set(p, off);
|
|
76
|
-
off += p.length;
|
|
77
|
-
}
|
|
78
|
-
return out;
|
|
79
|
-
}
|
|
80
|
-
function typeHash(primaryType) {
|
|
81
|
-
const fields = TYPES[primaryType].map(([n, t]) => `${t} ${n}`).join(',');
|
|
82
|
-
return keccak_256(utf8(`${primaryType}(${fields})`));
|
|
83
|
-
}
|
|
84
|
-
function encodeField(type, value) {
|
|
85
|
-
return type === 'string'
|
|
86
|
-
? keccak_256(utf8(value))
|
|
87
|
-
: uint256(value);
|
|
88
|
-
}
|
|
89
|
-
function structHash(primaryType, message) {
|
|
90
|
-
const parts = [typeHash(primaryType)];
|
|
91
|
-
for (const [name, type] of TYPES[primaryType]) {
|
|
92
|
-
parts.push(encodeField(type, message[name]));
|
|
93
|
-
}
|
|
94
|
-
return keccak_256(concat(...parts));
|
|
95
|
-
}
|
|
96
|
-
const DOMAIN_SEPARATOR = keccak_256(concat(keccak_256(utf8('EIP712Domain(string name,string version,uint256 chainId,' +
|
|
97
|
-
'address verifyingContract)')), keccak_256(utf8(DOMAIN.name)), keccak_256(utf8(DOMAIN.version)), uint256(DOMAIN.chainId), addressWord(DOMAIN.verifyingContract)));
|
|
98
|
-
/** EIP-712 digest: keccak256(0x1901 ‖ domainSeparator ‖ structHash). */
|
|
99
|
-
export function eip712Digest(primaryType, message) {
|
|
100
|
-
return keccak_256(concat(Uint8Array.from([0x19, 0x01]), DOMAIN_SEPARATOR, structHash(primaryType, message)));
|
|
101
|
-
}
|
|
102
|
-
/** Recover the signer of a typed message, or null on failure. Used by the
|
|
103
|
-
* Builder (`packages/app`) to verify legacy typed-data signatures; the client
|
|
104
|
-
* itself no longer signs — all consumer auth is the org bearer token. */
|
|
105
|
-
export function recoverTypedSigner(primaryType, message, signature) {
|
|
106
|
-
const digest = eip712Digest(primaryType, message);
|
|
107
|
-
return recoverAddress(digest, hexToBytes(signature));
|
|
108
|
-
}
|