@reclaimprotocol/client 0.1.0-dev.1 → 0.1.0-dev.2

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/src/result.ts ADDED
@@ -0,0 +1,448 @@
1
+ /**
2
+ * Consumer-side handling of a delivered callback. Two entry points:
3
+ * - {@link verifyResult}: the offline primitive — decrypt + decode + optional
4
+ * ES256K signature check (caller-supplied key) + `reclaimSessionId` nonce.
5
+ * - {@link verifyResultFull}: the full async verification — discovers the
6
+ * Verification Client's key via JWKS, verifies the top-level JWS, checks
7
+ * `reclaimSessionId`/`aud`/`exp`, and verifies each result's nested attestor
8
+ * proof + `content_hash` (see ./attestor-proof.ts and ./jwks.ts).
9
+ *
10
+ * See docs/verification-session.md → "Verification by Consumer".
11
+ */
12
+ import { sha256 } from '@noble/hashes/sha2.js'
13
+ import { keccak_256 } from '@noble/hashes/sha3.js'
14
+ import type { JWK } from 'jose'
15
+ import { compactDecrypt, CompactEncrypt, importJWK } from 'jose'
16
+ import type { components } from './types/openapi.gen.ts'
17
+ import { type NestedProofOutcome, verifyNestedProof } from './attestor-proof.ts'
18
+ import { canonicalJson } from './canonical.ts'
19
+ import type { DecryptionKey } from './enckeys.ts'
20
+ import { b64url, base64urlToBytes, bytesToHex, hexToBytes } from './encoding.ts'
21
+ import { fetchJwks, jwkToUncompressedPublicKey, selectKey } from './jwks.ts'
22
+ import { addressFromUncompressedPubkey, secp } from './secp.ts'
23
+
24
+ export interface Jws {
25
+ payload: string
26
+ signatures: {
27
+ protected: string
28
+ header?: Record<string, unknown>
29
+ signature: string
30
+ }[]
31
+ }
32
+
33
+ export interface VerifyResultOptions {
34
+ /** Uncompressed ETH public key (`0x04`+128 hex) of the signer; when given,
35
+ * the JWS signature is verified. Omit to only decode + check the nonce. */
36
+ signerPublicKey?: string
37
+ /** The `reclaimSessionId` the consumer expects (the session id they got
38
+ * back at creation). When given, mismatch fails verification. */
39
+ expectedReclaimSessionId?: string
40
+ }
41
+
42
+ export interface VerifyResultOutcome {
43
+ valid: boolean
44
+ reason?: string
45
+ payload: Record<string, unknown>
46
+ reclaimSessionId?: string
47
+ }
48
+
49
+ /**
50
+ * JWE-encrypt `plaintext` for the holder of `publicJwk` (an X25519 public JWK,
51
+ * `{kty:'OKP', crv:'X25519', x}`) as a compact JWE (`ECDH-ES + A256GCM`). Used
52
+ * by the first-party Verification Client to encrypt the signed JWS for each
53
+ * Consumer before submitting it to the Builder (which only relays the
54
+ * ciphertext). Consumers never call this — they only {@link decryptCallback}.
55
+ *
56
+ * `plaintext` is bytes (e.g. `new TextEncoder().encode(JSON.stringify(jws))`);
57
+ * returns the compact JWE string.
58
+ */
59
+ export async function encryptForPublicJwk(
60
+ publicJwk: JWK,
61
+ plaintext: Uint8Array,
62
+ ): Promise<string> {
63
+ const key = await importJWK(publicJwk, 'ECDH-ES')
64
+ return new CompactEncrypt(plaintext)
65
+ .setProtectedHeader({ alg: 'ECDH-ES', enc: 'A256GCM' })
66
+ .encrypt(key)
67
+ }
68
+
69
+ /**
70
+ * What a consumer's callback endpoint receives — the Builder POSTs the JSON
71
+ * `VerificationResultDelivery` envelope: plaintext `sessionId`/`event`/
72
+ * `timestamp` routing hints. For terminal events `data` is a compact JWE
73
+ * string wrapping the signed JWS (or the plaintext JWS JSON when the org has
74
+ * no encryption credential). The envelope is unauthenticated; trust only the
75
+ * decrypted+verified JWS.
76
+ *
77
+ * Derived from the generated spec type so it can't drift; the hint fields
78
+ * are relaxed to optional — decryption only needs `data`.
79
+ */
80
+ export type DeliveryEnvelope = Partial<
81
+ components['schemas']['VerificationResultDelivery']
82
+ > &
83
+ Pick<components['schemas']['VerificationResultDelivery'], 'data'>
84
+
85
+ /** Anything a callback handler may have on hand: the parsed envelope, its
86
+ * JSON text, its raw body bytes — or a bare compact JWE / JWS string. */
87
+ export type CallbackBody =
88
+ | DeliveryEnvelope
89
+ | string
90
+ | Uint8Array
91
+ | ArrayBuffer
92
+
93
+ /** Normalize any {@link CallbackBody} to the terminal-result `data` string —
94
+ * either a compact JWE (`xxxx.yyyy.zzzz.aaaa.bbbb`) or the plaintext JWS JSON
95
+ * (`{…}`). The envelope object/text wraps it in `.data`; raw bytes are decoded
96
+ * to text first. */
97
+ function payloadFrom(body: CallbackBody): string {
98
+ if(typeof body === 'string') {
99
+ const s = body.trim()
100
+ // A JSON envelope/JWS starts with '{'; anything else is the bare `data`
101
+ // (compact JWE). An envelope's `data` is extracted; a bare JWS is returned.
102
+ if(s.startsWith('{')) {
103
+ const parsed = JSON.parse(s) as DeliveryEnvelope | Jws
104
+ if(typeof (parsed as DeliveryEnvelope).data === 'string') {
105
+ return (parsed as DeliveryEnvelope).data
106
+ }
107
+
108
+ return s
109
+ }
110
+
111
+ return s
112
+ }
113
+
114
+ if(body instanceof ArrayBuffer) {
115
+ return payloadFrom(new TextDecoder().decode(new Uint8Array(body)))
116
+ }
117
+
118
+ if(body instanceof Uint8Array) {
119
+ return payloadFrom(new TextDecoder().decode(body))
120
+ }
121
+
122
+ return body.data
123
+ }
124
+
125
+ /**
126
+ * Decrypt a delivered terminal callback to the signed JWS. `data` is either a
127
+ * compact JWE (decrypted with the X25519 `privateKey`) or — when the org has no
128
+ * encryption credential — the plaintext JWS JSON. A compact JWE has exactly
129
+ * five dot-separated base64url segments; a JSON JWS starts with `{`.
130
+ *
131
+ * `privateKey` may be absent (plaintext delivery); an encrypted delivery with
132
+ * no key throws a clear error.
133
+ */
134
+ export async function decryptCallback(
135
+ privateKey: DecryptionKey | undefined,
136
+ body: CallbackBody,
137
+ ): Promise<Jws> {
138
+ const data = payloadFrom(body)
139
+ const trimmed = data.trim()
140
+
141
+ // Plaintext delivery: `data` is the JWS JSON directly.
142
+ if(trimmed.startsWith('{')) {
143
+ return JSON.parse(trimmed) as Jws
144
+ }
145
+
146
+ // Otherwise it's a compact JWE — five base64url segments.
147
+ if(!privateKey) {
148
+ throw new Error(
149
+ 'received an encrypted (JWE) delivery but no decryption credential was '
150
+ + 'configured — pass the X25519 private key (JWK/PEM): '
151
+ + 'results.receive(body, { credential }) or decrypt(body, credential)',
152
+ )
153
+ }
154
+
155
+ const { plaintext } = await compactDecrypt(trimmed, privateKey)
156
+ return JSON.parse(new TextDecoder().decode(plaintext)) as Jws
157
+ }
158
+
159
+ /**
160
+ * Sign a verification result as a flattened JWS (JOSE ES256K = ECDSA/secp256k1
161
+ * over SHA-256 of the `<protected>.<payload>` signing input — a single hash).
162
+ * `kid` is the signer's ETH address (published via JWKS); `iss` is the issuer
163
+ * URL. Used by the Verification Client to sign results before encrypting them.
164
+ */
165
+ export function signResultJws(
166
+ privateKeyHex: string,
167
+ payload: Record<string, unknown>,
168
+ iss: string,
169
+ ): Jws {
170
+ const priv = hexToBytes(privateKeyHex)
171
+ const protectedHeader = b64url(
172
+ JSON.stringify({
173
+ alg: 'ES256K',
174
+ kid: addressFromUncompressedPubkey(secp.getPublicKey(priv, false)),
175
+ typ: 'JWS',
176
+ iss,
177
+ }),
178
+ )
179
+ const payloadB64 = b64url(JSON.stringify(payload))
180
+ const signingInput = new TextEncoder().encode(
181
+ `${protectedHeader}.${payloadB64}`,
182
+ )
183
+ // Standard ES256K: a single SHA-256 of the signing input. `prehash:false`
184
+ // stops @noble hashing the digest again, so any JOSE tool can verify it.
185
+ const sig = secp.sign(sha256(signingInput), priv, { prehash: false })
186
+ return {
187
+ payload: payloadB64,
188
+ signatures: [{ protected: protectedHeader, signature: b64url(sig) }],
189
+ }
190
+ }
191
+
192
+ /** Decode the JWS payload (base64url JSON). */
193
+ export function decodeJwsPayload(jws: Jws): Record<string, unknown> {
194
+ return JSON.parse(
195
+ new TextDecoder().decode(base64urlToBytes(jws.payload)),
196
+ ) as Record<string, unknown>
197
+ }
198
+
199
+ /**
200
+ * Verify a JWS signature (JOSE ES256K = ECDSA/secp256k1 over SHA-256 of the
201
+ * `<protected>.<payload>` signing input) with an uncompressed public key.
202
+ */
203
+ export function verifyJwsSignature(jws: Jws, signerPublicKey: string): boolean {
204
+ const pub = hexToBytes(signerPublicKey)
205
+ return jws.signatures.some((s) => {
206
+ try {
207
+ const signingInput = new TextEncoder().encode(
208
+ `${s.protected}.${jws.payload}`
209
+ )
210
+ const digest = sha256(signingInput)
211
+ const sig = base64urlToBytes(s.signature)
212
+ // digest is the single SHA-256 of the signing input — don't let
213
+ // @noble hash it again (standard ES256K).
214
+ return secp.verify(sig, digest, pub, { prehash: false })
215
+ } catch{
216
+ return false
217
+ }
218
+ })
219
+ }
220
+
221
+ /** Content hash binding extracted parameters — MUST mirror the Verification
222
+ * Client's `contentHash` exactly (keccak256 of the RFC 8785 canonical JSON,
223
+ * via the shared `canonicalJson`). */
224
+ function contentHash(params: Record<string, string>): string {
225
+ const canonical = canonicalJson(params)
226
+ return '0x' + bytesToHex(keccak_256(new TextEncoder().encode(canonical)))
227
+ }
228
+
229
+ export interface VerifyResultFullOptions {
230
+ /** The `reclaimSessionId` the consumer expects (mismatch fails). */
231
+ expectedReclaimSessionId?: string
232
+ /** The consumer's `orgId`; the result's `aud` must match it. */
233
+ expectedAud?: string
234
+ /** Allowlist of trusted attestor ETH addresses (lowercased compared). */
235
+ attestorAddresses?: string[]
236
+ /** Override the VC signer key instead of JWKS discovery via header `iss`. */
237
+ signerPublicKey?: string
238
+ /** Injectable fetch (for JWKS); defaults to global `fetch`. */
239
+ fetchImpl?: typeof fetch
240
+ /** Override "now" (unix seconds) for `exp` checks. */
241
+ now?: number
242
+ }
243
+
244
+ export interface ResultProofOutcome {
245
+ requestId?: string
246
+ proofValid: boolean
247
+ reason?: string
248
+ attestorAddress?: string
249
+ }
250
+
251
+ export interface VerifyResultFullOutcome extends VerifyResultOutcome {
252
+ proofs?: ResultProofOutcome[]
253
+ }
254
+
255
+ /**
256
+ * Thrown by {@link verifyResult} / {@link verifyResultFull} (and therefore
257
+ * `reclaim.results.verify`/`receive`) when a result fails verification. Carries
258
+ * the full invalid outcome so callers can inspect it in a `catch`:
259
+ *
260
+ * try {
261
+ * const { result } = await reclaim.results.receive(body, { credential })
262
+ * // result is verified — use result.payload
263
+ * } catch (err) {
264
+ * if (err instanceof ResultVerificationError) {
265
+ * // e.g. 'expired', { payload, … }
266
+ * console.error(err.reason, err.outcome)
267
+ * }
268
+ * }
269
+ */
270
+ export class ResultVerificationError extends Error {
271
+ /** Machine-readable failure reason — `'bad-signature'`, `'expired'`,
272
+ * `'reclaimSessionId-mismatch'`, `'aud-mismatch'`, `'invalid-proof'`, … */
273
+ readonly reason: string
274
+ /** The full (invalid) outcome: `payload`, `reclaimSessionId`, and per-proof
275
+ * detail (`proofs`) when verification got that far. */
276
+ readonly outcome: VerifyResultFullOutcome
277
+
278
+ constructor(outcome: VerifyResultFullOutcome) {
279
+ super(`result verification failed: ${outcome.reason ?? 'invalid'}`)
280
+ this.name = 'ResultVerificationError'
281
+ this.reason = outcome.reason ?? 'invalid'
282
+ this.outcome = outcome
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Full consumer verification: discovers the Verification Client's signing key
288
+ * via JWKS (header `iss`/`kid`), verifies the top-level JWS, checks
289
+ * `reclaimSessionId`/`aud`/`exp`, then verifies each result's nested attestor
290
+ * proof and its `content_hash`. Network access (JWKS) makes this async; use
291
+ * {@link verifyResult} for the offline primitive.
292
+ */
293
+ export async function verifyResultFull(
294
+ jws: Jws,
295
+ opts: VerifyResultFullOptions = {},
296
+ ): Promise<VerifyResultFullOutcome> {
297
+ const payload = decodeJwsPayload(jws)
298
+ const reclaimSessionId = payload.reclaimSessionId as string | undefined
299
+ /** Throw with the full invalid outcome — verification never *returns* an
300
+ * invalid result. */
301
+ const fail = (
302
+ reason: string,
303
+ extra: Partial<VerifyResultFullOutcome> = {},
304
+ ): never => {
305
+ throw new ResultVerificationError({
306
+ valid: false,
307
+ reason,
308
+ payload,
309
+ reclaimSessionId,
310
+ ...extra,
311
+ })
312
+ }
313
+
314
+ // 1. Resolve the VC signer key: explicit override, else JWKS via header iss.
315
+ let signerPublicKey = opts.signerPublicKey
316
+ let header: Record<string, unknown> = {}
317
+ const sig0 = jws.signatures[0]
318
+ if(sig0) {
319
+ try {
320
+ header = JSON.parse(
321
+ new TextDecoder().decode(base64urlToBytes(sig0.protected)),
322
+ )
323
+ } catch{
324
+ return fail('bad-header')
325
+ }
326
+ }
327
+
328
+ if(!signerPublicKey) {
329
+ const iss = String(header.iss ?? '')
330
+ if(!iss) {
331
+ return fail('no-issuer')
332
+ }
333
+
334
+ const keys = await fetchJwks(iss, opts.fetchImpl)
335
+ const jwk = selectKey(keys, String(header.kid ?? ''))
336
+ if(!jwk) {
337
+ return fail('signer-key-not-found')
338
+ }
339
+
340
+ signerPublicKey = jwkToUncompressedPublicKey(jwk)
341
+ }
342
+
343
+ if(!verifyJwsSignature(jws, signerPublicKey)) {
344
+ return fail('bad-signature')
345
+ }
346
+
347
+ // 2. Freshness / audience / expiry.
348
+ if(
349
+ opts.expectedReclaimSessionId &&
350
+ reclaimSessionId !== opts.expectedReclaimSessionId
351
+ ) {
352
+ return fail('reclaimSessionId-mismatch')
353
+ }
354
+
355
+ if(opts.expectedAud && payload.aud !== opts.expectedAud) {
356
+ return fail('aud-mismatch')
357
+ }
358
+
359
+ const now = opts.now ?? Math.floor(Date.now() / 1000)
360
+ if(typeof payload.exp === 'number' && payload.exp < now) {
361
+ return fail('expired')
362
+ }
363
+
364
+ // 3. Per-result nested attestor proof + content_hash binding.
365
+ const results = Array.isArray(payload.results)
366
+ ? (payload.results as Record<string, unknown>[])
367
+ : []
368
+ const proofs: ResultProofOutcome[] = []
369
+ for(const r of results) {
370
+ const requestId =
371
+ typeof r.request_id === 'string' ? r.request_id : undefined
372
+ if(typeof r.proof !== 'string') {
373
+ proofs.push({ requestId, proofValid: false, reason: 'proof-missing' })
374
+ continue
375
+ }
376
+
377
+ const np: NestedProofOutcome = verifyNestedProof(r.proof, {
378
+ attestorAddresses: opts.attestorAddresses,
379
+ })
380
+ if(!np.valid) {
381
+ proofs.push({
382
+ requestId,
383
+ proofValid: false,
384
+ reason: np.reason,
385
+ attestorAddress: np.attestorAddress,
386
+ })
387
+ continue
388
+ }
389
+
390
+ if(
391
+ typeof r.content_hash === 'string' &&
392
+ r.extracted_parameters &&
393
+ typeof r.extracted_parameters === 'object'
394
+ ) {
395
+ const params = r.extracted_parameters as Record<string, string>
396
+ if(contentHash(params).toLowerCase() !== r.content_hash.toLowerCase()) {
397
+ proofs.push({
398
+ requestId,
399
+ proofValid: false,
400
+ reason: 'content-hash-mismatch',
401
+ attestorAddress: np.attestorAddress,
402
+ })
403
+ continue
404
+ }
405
+ }
406
+
407
+ proofs.push({
408
+ requestId,
409
+ proofValid: true,
410
+ attestorAddress: np.attestorAddress,
411
+ })
412
+ }
413
+
414
+ if(!proofs.every((p) => p.proofValid)) {
415
+ return fail('invalid-proof', { proofs })
416
+ }
417
+
418
+ return { valid: true, payload, reclaimSessionId, proofs }
419
+ }
420
+
421
+ /** Decrypt-and-verify in one step. */
422
+ export function verifyResult(
423
+ jws: Jws,
424
+ opts: VerifyResultOptions = {},
425
+ ): VerifyResultOutcome {
426
+ const payload = decodeJwsPayload(jws)
427
+ const reclaimSessionId = payload.reclaimSessionId as string | undefined
428
+
429
+ if(opts.signerPublicKey && !verifyJwsSignature(jws, opts.signerPublicKey)) {
430
+ throw new ResultVerificationError({
431
+ valid: false, reason: 'bad-signature', payload, reclaimSessionId,
432
+ })
433
+ }
434
+
435
+ if(
436
+ opts.expectedReclaimSessionId &&
437
+ reclaimSessionId !== opts.expectedReclaimSessionId
438
+ ) {
439
+ throw new ResultVerificationError({
440
+ valid: false,
441
+ reason: 'reclaimSessionId-mismatch',
442
+ payload,
443
+ reclaimSessionId,
444
+ })
445
+ }
446
+
447
+ return { valid: true, payload, reclaimSessionId }
448
+ }
package/src/routes.gen.ts CHANGED
@@ -5,10 +5,12 @@
5
5
 
6
6
  export const ROUTES = {
7
7
  "AddOrgMember": {"method":"post","path":"/orgs/{orgId}/members"},
8
+ "AdminCallbackDlq": {"method":"get","path":"/admin/callback-dlq"},
8
9
  "AdminHome": {"method":"get","path":"/admin"},
9
10
  "AdminOrgs": {"method":"get","path":"/admin/orgs"},
10
11
  "AdminProviders": {"method":"get","path":"/admin/providers"},
11
12
  "AdminReviews": {"method":"get","path":"/admin/reviews"},
13
+ "AdminSessions": {"method":"get","path":"/admin/sessions"},
12
14
  "AdminThemes": {"method":"get","path":"/admin/themes"},
13
15
  "AdminUsers": {"method":"get","path":"/admin/users"},
14
16
  "AttachPage": {"method":"get","path":"/attach/{code}"},
@@ -16,15 +18,19 @@ export const ROUTES = {
16
18
  "ComponentsDemo": {"method":"get","path":"/components_demo"},
17
19
  "ConfirmAttachSession": {"method":"post","path":"/credentials/attach/sessions/{code}/confirm"},
18
20
  "CreateAttachSession": {"method":"post","path":"/credentials/attach/sessions"},
21
+ "CreateCallbackSubscription": {"method":"post","path":"/verifications/callback-subscriptions"},
19
22
  "CreateOrg": {"method":"post","path":"/orgs"},
23
+ "CreateOrgCallbackSubscription": {"method":"post","path":"/orgs/{orgId}/callback-subscriptions"},
20
24
  "CreateProvider": {"method":"post","path":"/providers"},
21
25
  "CreateProviderVersion": {"method":"post","path":"/providers/{providerId}/versions"},
22
26
  "CreateTheme": {"method":"post","path":"/orgs/{orgId}/themes"},
23
- "CredentialAuthChallenge": {"method":"post","path":"/credentials/auth/challenge"},
24
- "CredentialAuthVerify": {"method":"post","path":"/credentials/auth/verify"},
27
+ "CreateVerificationSession": {"method":"post","path":"/verifications/sessions"},
25
28
  "DecideProviderVersionReview": {"method":"post","path":"/providers/{providerId}/versions/{version}/review-decision"},
29
+ "DeleteCallbackSubscription": {"method":"delete","path":"/verifications/callback-subscriptions/{subscriptionId}"},
26
30
  "DeleteOrg": {"method":"delete","path":"/orgs/{orgId}"},
27
31
  "DeleteOrgBilling": {"method":"delete","path":"/orgs/{orgId}/billing"},
32
+ "DeleteOrgCallbackSubscription": {"method":"delete","path":"/orgs/{orgId}/callback-subscriptions/{subscriptionId}"},
33
+ "DeleteOrgCredential": {"method":"delete","path":"/orgs/{orgId}/credential"},
28
34
  "DeleteOrgIcon": {"method":"delete","path":"/orgs/{orgId}/icon"},
29
35
  "DeleteProvider": {"method":"delete","path":"/providers/{providerId}"},
30
36
  "DeleteProviderVersion": {"method":"delete","path":"/providers/{providerId}/versions/{version}"},
@@ -32,23 +38,34 @@ export const ROUTES = {
32
38
  "Docs": {"method":"get","path":"/docs"},
33
39
  "ExplorePreviewImage": {"method":"get","path":"/og/explore.png"},
34
40
  "GetAttachSession": {"method":"get","path":"/credentials/attach/sessions/{code}"},
35
- "GetCredential": {"method":"get","path":"/credentials/{credentialId}"},
36
- "GetCredentialByAddress": {"method":"get","path":"/credentials/by-address/{address}"},
37
41
  "GetImage": {"method":"get","path":"/images/{imageId}"},
38
42
  "GetMe": {"method":"get","path":"/me"},
39
43
  "GetOrg": {"method":"get","path":"/orgs/{orgId}"},
40
44
  "GetOrgBilling": {"method":"get","path":"/orgs/{orgId}/billing"},
45
+ "GetOrgCredential": {"method":"get","path":"/orgs/{orgId}/credential"},
46
+ "GetOrgSession": {"method":"get","path":"/orgs/{orgId}/sessions/{sessionId}"},
47
+ "GetOrgToken": {"method":"get","path":"/orgs/{orgId}/token"},
41
48
  "GetProvider": {"method":"get","path":"/providers/{providerId}"},
42
49
  "GetProviderBySlug": {"method":"get","path":"/providers/{providerId}/{slug}"},
43
50
  "GetProviderVersion": {"method":"get","path":"/providers/{providerId}/versions/{version}"},
44
51
  "GetTheme": {"method":"get","path":"/orgs/{orgId}/themes/{themeId}"},
45
52
  "GetUser": {"method":"get","path":"/users/{userId}"},
53
+ "GetVerificationRecipe": {"method":"get","path":"/verifications/sessions/{sessionId}/recipe"},
54
+ "GetVerificationResult": {"method":"get","path":"/verifications/sessions/{sessionId}/result"},
55
+ "GetVerificationSession": {"method":"get","path":"/verifications/sessions/{sessionId}"},
46
56
  "Index": {"method":"get","path":"/"},
47
- "LinkCredential": {"method":"post","path":"/credentials"},
48
- "ListOrgCredentials": {"method":"get","path":"/orgs/{orgId}/credentials"},
57
+ "IssueOrgToken": {"method":"post","path":"/orgs/{orgId}/token"},
58
+ "JwksJson": {"method":"get","path":"/.well-known/jwks.json"},
59
+ "ListCallbackDlq": {"method":"get","path":"/verifications/callback-dlq"},
60
+ "ListCallbackSubscriptions": {"method":"get","path":"/verifications/callback-subscriptions"},
61
+ "ListOrgCallbackSubscriptions": {"method":"get","path":"/orgs/{orgId}/callback-subscriptions"},
49
62
  "ListOrgs": {"method":"get","path":"/orgs"},
63
+ "ListOrgVerificationSessions": {"method":"get","path":"/orgs/{orgId}/sessions"},
50
64
  "ListProviderVersions": {"method":"get","path":"/providers/{providerId}/versions"},
65
+ "ListSessionCallbackKeys": {"method":"get","path":"/verifications/sessions/{sessionId}/callback-keys"},
51
66
  "ListThemes": {"method":"get","path":"/orgs/{orgId}/themes"},
67
+ "ListVerificationDeliveries": {"method":"get","path":"/verifications/sessions/{sessionId}/deliveries"},
68
+ "ListVerificationEvents": {"method":"get","path":"/verifications/sessions/{sessionId}/events"},
52
69
  "Login": {"method":"post","path":"/auth/login"},
53
70
  "LoginPage": {"method":"get","path":"/login"},
54
71
  "Logout": {"method":"post","path":"/logout"},
@@ -58,12 +75,17 @@ export const ROUTES = {
58
75
  "Providers": {"method":"get","path":"/providers"},
59
76
  "PublishProviderVersion": {"method":"post","path":"/providers/{providerId}/versions/{version}/publish"},
60
77
  "RemoveOrgMember": {"method":"delete","path":"/orgs/{orgId}/members/{memberId}"},
78
+ "ReportClaimant": {"method":"post","path":"/v/{sessionId}/claimant"},
79
+ "ReportVerificationEvent": {"method":"post","path":"/verifications/sessions/{sessionId}/events"},
61
80
  "RequestPublicListing": {"method":"post","path":"/providers/{providerId}/request-public"},
62
- "RevokeCredential": {"method":"delete","path":"/credentials/{credentialId}"},
81
+ "RetryCallbackDlq": {"method":"post","path":"/verifications/callback-dlq/{dlqId}/retry"},
82
+ "RetryVerificationCallback": {"method":"post","path":"/verifications/sessions/{sessionId}/retry-callback"},
63
83
  "Robots": {"method":"get","path":"/robots.txt"},
84
+ "SetOrgCredential": {"method":"put","path":"/orgs/{orgId}/credential"},
64
85
  "SetupProviderCreate": {"method":"post","path":"/providers/setup"},
65
86
  "Sitemap": {"method":"get","path":"/sitemap.xml"},
66
87
  "SubmitProviderVersionForReview": {"method":"post","path":"/providers/{providerId}/versions/{version}/review"},
88
+ "SubmitVerificationResult": {"method":"post","path":"/verifications/sessions/{sessionId}/result"},
67
89
  "TransferProvider": {"method":"post","path":"/providers/{providerId}/transfer"},
68
90
  "UpdateMe": {"method":"patch","path":"/me"},
69
91
  "UpdateOrg": {"method":"patch","path":"/orgs/{orgId}"},
@@ -73,6 +95,9 @@ export const ROUTES = {
73
95
  "UpdateProvider": {"method":"patch","path":"/providers/{providerId}"},
74
96
  "UpdateProviderVersion": {"method":"patch","path":"/providers/{providerId}/versions/{version}"},
75
97
  "UpdateTheme": {"method":"patch","path":"/orgs/{orgId}/themes/{themeId}"},
98
+ "VerificationPage": {"method":"get","path":"/v/{sessionId}"},
99
+ "VerificationSessionSse": {"method":"get","path":"/v/{sessionId}/sse"},
100
+ "VerificationStatus": {"method":"get","path":"/v/{sessionId}/status"},
76
101
  } as const
77
102
 
78
103
  export type RouteId = keyof typeof ROUTES
package/src/secp.ts ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared `@noble/secp256k1` instance with the v3 hash hooks wired once
3
+ * (sign + recover need SHA-256 / HMAC-SHA256). Import `secp` from here — never
4
+ * set `secp.hashes` ad-hoc — so the hooks are guaranteed initialised before any
5
+ * sign/verify/recover call, regardless of import order.
6
+ */
7
+ import { hmac } from '@noble/hashes/hmac.js'
8
+ import { sha256 } from '@noble/hashes/sha2.js'
9
+ import { keccak_256 } from '@noble/hashes/sha3.js'
10
+ import * as secp from '@noble/secp256k1'
11
+ import { bytesToHex } from './encoding.ts'
12
+
13
+ secp.hashes.sha256 = sha256
14
+ secp.hashes.hmacSha256 = (key: Uint8Array, msg: Uint8Array) => {
15
+ return hmac(sha256, key, msg)
16
+ }
17
+
18
+ export { keccak_256, secp }
19
+
20
+ /** Lowercase 0x ETH address from an uncompressed (65-byte) public key:
21
+ * keccak256 of the 64-byte X‖Y (drop the 0x04 prefix), last 20 bytes. */
22
+ export function addressFromUncompressedPubkey(pub: Uint8Array): string {
23
+ return '0x' + bytesToHex(keccak_256(pub.slice(1))).slice(-40)
24
+ }
@@ -1,2 +1,2 @@
1
1
  export * from './openapi.ts'
2
- export type { components, operations, paths } from './openapi.gen.ts'
2
+ export * from './openapi.gen.ts'