@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/lib/index.js CHANGED
@@ -1,4 +1,12 @@
1
1
  export * from "./client.js";
2
+ export { throwProblemError } from "./errors.js";
2
3
  export { DEFAULT_BASE_URL, PROBLEM_CONTENT_TYPE, USER_AGENT } from "./consts.js";
3
- export { deriveFromPrivateKey, generateKeypair, } from "./keypair.js";
4
- export * from "./types/index.js";
4
+ export { addressFromPublicKey, deriveFromPrivateKey, generateKeypair, } from "./keypair.js";
5
+ export { canonicalJson } from "./canonical.js";
6
+ export { b64url, base64urlToBytes, bytesToHex, hexToBytes, } from "./encoding.js";
7
+ export { addressFromUncompressedPubkey, secp, } from "./secp.js";
8
+ export { decodeJwsPayload, decryptCallback, encryptForPublicJwk, ResultVerificationError, signResultJws, verifyJwsSignature, verifyResult, verifyResultFull, } from "./result.js";
9
+ export { loadDecryptionKey, publicPemToJwk, } from "./enckeys.js";
10
+ export { createReclaim, TERMINAL_EVENTS, } from "./reclaim.js";
11
+ export { fetchJwks, jwksFromPrivateKey, jwkToUncompressedPublicKey, selectKey, } from "./jwks.js";
12
+ export { verifyNestedProof, } from "./attestor-proof.js";
package/lib/jwks.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ export interface Jwk {
2
+ kty: string;
3
+ crv: string;
4
+ x: string;
5
+ y: string;
6
+ kid?: string;
7
+ alg?: string;
8
+ use?: string;
9
+ }
10
+ /**
11
+ * Build the JWK set for an ES256K signing key (the Verification Client's
12
+ * result-signing key). `kid` is the key's ETH address; `x`/`y` are the
13
+ * uncompressed public-key coordinates. Empty set when no key is configured.
14
+ */
15
+ export declare function jwksFromPrivateKey(privateKeyHex?: string): {
16
+ keys: Jwk[];
17
+ };
18
+ /** Fetch the JWK set published at `<iss>/.well-known/jwks.json`. */
19
+ export declare function fetchJwks(iss: string, fetchImpl?: typeof fetch): Promise<Jwk[]>;
20
+ /** Find the key matching `kid` (case-insensitive — kids are ETH addresses). */
21
+ export declare function selectKey(keys: Jwk[], kid: string): Jwk | undefined;
22
+ /**
23
+ * Reconstruct the uncompressed secp256k1 public key (`0x04` + X + Y) from an
24
+ * EC JWK's base64url `x`/`y` coordinates, for signature verification.
25
+ */
26
+ export declare function jwkToUncompressedPublicKey(jwk: Jwk): string;
package/lib/jwks.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * JWKS (RFC 7517) discovery for verifying result/proof signatures. Signers
3
+ * (the Verification Client, the Attestor) publish their secp256k1 public keys
4
+ * at `<iss>/.well-known/jwks.json` with `kid` = the signer's ETH address.
5
+ */
6
+ import { b64url, base64urlToBytes, bytesToHex } from "./encoding.js";
7
+ import { addressFromUncompressedPubkey, secp } from "./secp.js";
8
+ /**
9
+ * Build the JWK set for an ES256K signing key (the Verification Client's
10
+ * result-signing key). `kid` is the key's ETH address; `x`/`y` are the
11
+ * uncompressed public-key coordinates. Empty set when no key is configured.
12
+ */
13
+ export function jwksFromPrivateKey(privateKeyHex) {
14
+ if (!privateKeyHex) {
15
+ return { keys: [] };
16
+ }
17
+ const priv = privateKeyHex.replace(/^0x/, '');
18
+ const pub = secp.getPublicKey(Uint8Array.from(Buffer.from(priv, 'hex')), false);
19
+ return {
20
+ keys: [
21
+ {
22
+ kty: 'EC',
23
+ crv: 'secp256k1',
24
+ alg: 'ES256K',
25
+ use: 'sig',
26
+ kid: addressFromUncompressedPubkey(pub),
27
+ x: b64url(pub.subarray(1, 33)),
28
+ y: b64url(pub.subarray(33, 65)),
29
+ },
30
+ ],
31
+ };
32
+ }
33
+ /** Fetch the JWK set published at `<iss>/.well-known/jwks.json`. */
34
+ export async function fetchJwks(iss, fetchImpl = fetch) {
35
+ const base = iss.replace(/\/+$/, '');
36
+ const res = await fetchImpl(`${base}/.well-known/jwks.json`);
37
+ if (!res.ok) {
38
+ throw new Error(`JWKS fetch failed for ${base}: HTTP ${res.status}`);
39
+ }
40
+ const body = (await res.json());
41
+ return body.keys ?? [];
42
+ }
43
+ /** Find the key matching `kid` (case-insensitive — kids are ETH addresses). */
44
+ export function selectKey(keys, kid) {
45
+ const want = kid.toLowerCase();
46
+ return keys.find((k) => (k.kid ?? '').toLowerCase() === want);
47
+ }
48
+ /**
49
+ * Reconstruct the uncompressed secp256k1 public key (`0x04` + X + Y) from an
50
+ * EC JWK's base64url `x`/`y` coordinates, for signature verification.
51
+ */
52
+ export function jwkToUncompressedPublicKey(jwk) {
53
+ const x = base64urlToBytes(jwk.x);
54
+ const y = base64urlToBytes(jwk.y);
55
+ if (x.length !== 32 || y.length !== 32) {
56
+ throw new Error('invalid EC JWK: x/y must be 32 bytes each');
57
+ }
58
+ const out = new Uint8Array(65);
59
+ out[0] = 0x04;
60
+ out.set(x, 1);
61
+ out.set(y, 33);
62
+ return '0x' + bytesToHex(out);
63
+ }
package/lib/keypair.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  export type EthKeypair = {
2
2
  address: string;
3
3
  privateKey: string;
4
+ /** Uncompressed secp256k1 public key (`0x04` + 128 hex). Used to derive the
5
+ * ETH address and to verify ES256K JWS signatures (published via JWKS as
6
+ * `x`/`y`). Result *encryption* now uses X25519 JWE, not this key. */
7
+ publicKey: string;
4
8
  };
5
9
  /**
6
10
  * Generate a fresh secp256k1 key-pair and derive its lowercase
@@ -19,3 +23,11 @@ export declare function generateKeypair(): EthKeypair;
19
23
  * importing a key the user already holds.
20
24
  */
21
25
  export declare function deriveFromPrivateKey(privateKey: string): EthKeypair;
26
+ /**
27
+ * Derive the lowercase Ethereum address that owns an uncompressed secp256k1
28
+ * public key (`0x04` + 128 hex) — keccak-256 of the key minus the `0x04`
29
+ * prefix byte, last 20 bytes. The holder of the matching private key controls
30
+ * this address; used to scope a callback subscription to its key's owner
31
+ * without ever needing the private key.
32
+ */
33
+ export declare function addressFromPublicKey(publicKey: string): string;
package/lib/keypair.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { keccak_256 } from '@noble/hashes/sha3.js';
2
+ import { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
2
3
  import * as secp from '@noble/secp256k1';
3
4
  /**
4
5
  * Generate a fresh secp256k1 key-pair and derive its lowercase
@@ -15,8 +16,9 @@ export function generateKeypair() {
15
16
  const priv = secp.utils.randomSecretKey();
16
17
  const pub = secp.getPublicKey(priv, false);
17
18
  return {
18
- address: '0x' + bytesToHex(keccak_256(pub.slice(1))).slice(-40),
19
+ address: addressFromPubBytes(pub),
19
20
  privateKey: '0x' + bytesToHex(priv),
21
+ publicKey: '0x' + bytesToHex(pub),
20
22
  };
21
23
  }
22
24
  /**
@@ -28,24 +30,23 @@ export function deriveFromPrivateKey(privateKey) {
28
30
  const priv = hexToBytes(hex);
29
31
  const pub = secp.getPublicKey(priv, false);
30
32
  return {
31
- address: '0x' + bytesToHex(keccak_256(pub.slice(1))).slice(-40),
33
+ address: addressFromPubBytes(pub),
32
34
  privateKey: '0x' + hex,
35
+ publicKey: '0x' + bytesToHex(pub),
33
36
  };
34
37
  }
35
- function bytesToHex(bytes) {
36
- let out = '';
37
- for (const b of bytes) {
38
- out += b.toString(16).padStart(2, '0');
39
- }
40
- return out;
38
+ /**
39
+ * Derive the lowercase Ethereum address that owns an uncompressed secp256k1
40
+ * public key (`0x04` + 128 hex) — keccak-256 of the key minus the `0x04`
41
+ * prefix byte, last 20 bytes. The holder of the matching private key controls
42
+ * this address; used to scope a callback subscription to its key's owner
43
+ * without ever needing the private key.
44
+ */
45
+ export function addressFromPublicKey(publicKey) {
46
+ return addressFromPubBytes(hexToBytes(publicKey.replace(/^0x/, '')));
41
47
  }
42
- function hexToBytes(hex) {
43
- if (hex.length % 2 !== 0) {
44
- throw new Error('private key hex must have even length');
45
- }
46
- const out = new Uint8Array(hex.length / 2);
47
- for (let i = 0; i < out.length; i++) {
48
- out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
49
- }
50
- return out;
48
+ /** keccak-256 of the uncompressed pubkey minus its `0x04` prefix byte,
49
+ * last 20 bytes — the Ethereum address derivation, lowercase (no checksum). */
50
+ function addressFromPubBytes(pub) {
51
+ return '0x' + bytesToHex(keccak_256(pub.slice(1))).slice(-40);
51
52
  }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Declarative entry point for the consumer SDK. The ONLY config is the org
3
+ * secret (`rorg_…`) — it authenticates every call and identifies the org
4
+ * server-side. Everything else is passed where it's actually used: the `orgId`
5
+ * to the org-scoped `credential.set`/`get`, and the X25519 decryption key to
6
+ * `results.decrypt`/`receive` (only when results arrive encrypted):
7
+ *
8
+ * const reclaim = createReclaim({ orgSecret })
9
+ * // one-time setup:
10
+ * await reclaim.callbacks.register({ callbackUrl })
11
+ * // enable encryption:
12
+ * await reclaim.credential.set(orgId, { publicKeyJwk })
13
+ * const session = await reclaim.sessions.create({ providerId, context })
14
+ * // …end-user completes session.verificationUrl…
15
+ * const outcome = await reclaim.results.receive(callbackBody, {
16
+ * credential: privateJwk, // omit for plaintext
17
+ * expectedReclaimSessionId: session.id,
18
+ * })
19
+ *
20
+ * Built by composition over the package's pure helpers (`decryptCallback`,
21
+ * `verifyResultFull`, …) — drop down to those, or to `reclaim.api`, for full
22
+ * control.
23
+ */
24
+ import type { components } from './types/openapi.gen.ts';
25
+ import { ReclaimClient } from './client.ts';
26
+ import { type DecryptionKeyInput } from './enckeys.ts';
27
+ import { type CallbackBody, type Jws, type VerifyResultFullOptions, type VerifyResultFullOutcome } from './result.ts';
28
+ type CallbackSubscription = components['schemas']['CallbackSubscription'];
29
+ type VerificationSession = components['schemas']['VerificationSession'];
30
+ type VerificationEvent = components['schemas']['VerificationEvent'];
31
+ type VerificationEventRecord = components['schemas']['VerificationEventRecord'];
32
+ type OrgCredential = components['schemas']['OrgCredential'];
33
+ /** The three terminal events a subscription can receive. */
34
+ export declare const TERMINAL_EVENTS: readonly ["verification_success", "verification_rejected", "verification_error"];
35
+ export interface RegisterCallbackInput {
36
+ callbackUrl: string;
37
+ events: VerificationEvent[];
38
+ }
39
+ export interface CreateSessionInput {
40
+ providerId: string;
41
+ version?: string;
42
+ context: Record<string, unknown>;
43
+ /** URL with a `{sessionId}` placeholder the end-user opens. */
44
+ verificationClientUrl: string;
45
+ }
46
+ export interface SetCredentialInput {
47
+ /** The org's X25519 encryption public key as an SPKI PEM — the content of
48
+ * `openssl pkey -in key.pem -pubout` (the `-----BEGIN PUBLIC KEY-----`
49
+ * block). It's converted to a JWK and stored; results are JWE-encrypted to
50
+ * it. Keep the matching private PEM to decrypt (pass it to
51
+ * `results.receive`/`decrypt`). */
52
+ publicKeyPem: string;
53
+ label?: string;
54
+ }
55
+ export interface ReclaimConfig {
56
+ /** The org secret (`rorg_…`). Authenticates ALL API calls and identifies
57
+ * the org server-side. The only required config. */
58
+ orgSecret: string;
59
+ /** Backend base URL. Defaults to the production Builder. */
60
+ baseUrl?: string;
61
+ /** Custom fetch implementation. */
62
+ fetch?: typeof fetch;
63
+ /** Extra headers merged into every request. */
64
+ headers?: Record<string, string>;
65
+ }
66
+ /** Options for `results.receive` — the verification options plus an optional
67
+ * X25519 decryption key (a JWK, a PKCS#8 PEM string, or `{ env }` / env-var
68
+ * name). The key is needed only when the delivery is an encrypted (JWE). */
69
+ export type ReceiveOptions = VerifyResultFullOptions & {
70
+ credential?: DecryptionKeyInput;
71
+ };
72
+ export interface Reclaim {
73
+ /** Typed escape hatch: `reclaim.api('OperationId', args)` for any spec
74
+ * operation. */
75
+ api: ReclaimClient['call'];
76
+ sessions: {
77
+ /** Create a verification session. `verificationClientUrl` defaults to
78
+ * the Builder's own `/v/{sessionId}` page. */
79
+ create(input: Omit<CreateSessionInput, 'verificationClientUrl'> & Partial<Pick<CreateSessionInput, 'verificationClientUrl'>>): Promise<VerificationSession>;
80
+ get(sessionId: string): Promise<VerificationSession>;
81
+ listEvents(sessionId: string): Promise<VerificationEventRecord[]>;
82
+ };
83
+ callbacks: {
84
+ /** Register a callback subscription (one-time setup). Delivery is scoped
85
+ * to the token's org; results are JWE-encrypted to the org's credential
86
+ * when one is set (see `credential.set`), else plaintext. `events`
87
+ * defaults to all three terminal events. */
88
+ register(input: Partial<RegisterCallbackInput> & Pick<RegisterCallbackInput, 'callbackUrl'>): Promise<CallbackSubscription>;
89
+ list(): Promise<CallbackSubscription[]>;
90
+ remove(subscriptionId: string): Promise<void>;
91
+ };
92
+ credential: {
93
+ /** Set/replace org `orgId`'s single X25519 encryption credential. Once
94
+ * set, terminal results are delivered as a compact JWE encrypted to it. */
95
+ set(orgId: string, input: SetCredentialInput): Promise<OrgCredential>;
96
+ /** Get org `orgId`'s encryption credential, or `undefined` when none is
97
+ * set (delivery is plaintext). */
98
+ get(orgId: string): Promise<OrgCredential | undefined>;
99
+ };
100
+ results: {
101
+ /** Decrypt a callback body (JWE or plaintext JWS) to the signed JWS.
102
+ * Pass the X25519 `credential` (JWK/PEM/env) only when the delivery is
103
+ * a JWE. */
104
+ decrypt(body: CallbackBody, credential?: DecryptionKeyInput): Promise<Jws>;
105
+ /** Verify a decrypted JWS: JWKS discovery, ES256K signature,
106
+ * `reclaimSessionId`/`aud`/`exp`, nested attestor proofs. */
107
+ verify(jws: Jws, opts?: VerifyResultFullOptions): Promise<VerifyResultFullOutcome>;
108
+ /** Handle any callback delivery in one call — the 90% path. Branches on
109
+ * the envelope's `event`: a terminal event is decrypted (with
110
+ * `opts.credential` when encrypted) + verified into a `result`; a
111
+ * non-terminal event is returned as a plaintext (unsigned) `event`
112
+ * notification without decryption. (A raw JWE/JWS body with no envelope
113
+ * `event` is treated as a terminal result.) */
114
+ receive(body: CallbackBody, opts?: ReceiveOptions): Promise<ReceivedDelivery>;
115
+ };
116
+ }
117
+ /** Outcome of `results.receive` — a verified terminal result or a plaintext
118
+ * non-terminal progress notification. Branch on `kind`. */
119
+ export type ReceivedDelivery = {
120
+ kind: 'result';
121
+ result: VerifyResultFullOutcome;
122
+ } | {
123
+ kind: 'event';
124
+ event: VerificationEvent;
125
+ eventData: Record<string, unknown>;
126
+ };
127
+ export declare function createReclaim(config: ReclaimConfig): Reclaim;
128
+ export {};
package/lib/reclaim.js ADDED
@@ -0,0 +1,155 @@
1
+ import { ReclaimClient } from "./client.js";
2
+ import { loadDecryptionKey, publicPemToJwk, } from "./enckeys.js";
3
+ import { decryptCallback, verifyResultFull, } from "./result.js";
4
+ /** The three terminal events a subscription can receive. */
5
+ export const TERMINAL_EVENTS = [
6
+ 'verification_success',
7
+ 'verification_rejected',
8
+ 'verification_error',
9
+ ];
10
+ export function createReclaim(config) {
11
+ if (!config?.orgSecret) {
12
+ throw new Error('createReclaim requires an org secret — pass '
13
+ + 'createReclaim({ orgSecret: <rorg_… org secret> })');
14
+ }
15
+ const client = new ReclaimClient({
16
+ baseUrl: config.baseUrl,
17
+ token: config.orgSecret,
18
+ fetch: config.fetch,
19
+ headers: config.headers,
20
+ });
21
+ // Load an X25519 decryption key on demand — `undefined` input ⇒ no key, so
22
+ // plaintext-only consumers never touch the loader.
23
+ const resolveKey = (input) => (input === undefined
24
+ ? Promise.resolve(undefined)
25
+ : loadDecryptionKey(input));
26
+ return {
27
+ api: client.call.bind(client),
28
+ sessions: {
29
+ async create(input) {
30
+ const body = {
31
+ ...input,
32
+ verificationClientUrl: input.verificationClientUrl ?? `${client.baseUrl}/v/{sessionId}`,
33
+ };
34
+ const { data } = await client.call('CreateVerificationSession', {
35
+ body,
36
+ });
37
+ return data;
38
+ },
39
+ async get(sessionId) {
40
+ const { data } = await client.call('GetVerificationSession', {
41
+ params: { sessionId },
42
+ });
43
+ return data;
44
+ },
45
+ async listEvents(sessionId) {
46
+ const { data } = await client.call('ListVerificationEvents', {
47
+ params: { sessionId },
48
+ });
49
+ return data.items;
50
+ },
51
+ },
52
+ callbacks: {
53
+ async register(input) {
54
+ const events = input.events ?? [...TERMINAL_EVENTS];
55
+ const { data } = await client.call('CreateCallbackSubscription', {
56
+ body: { callbackUrl: input.callbackUrl, events },
57
+ });
58
+ return data;
59
+ },
60
+ async list() {
61
+ const { data } = await client.call('ListCallbackSubscriptions');
62
+ return data.items;
63
+ },
64
+ async remove(subscriptionId) {
65
+ await client.call('DeleteCallbackSubscription', {
66
+ params: { subscriptionId },
67
+ });
68
+ },
69
+ },
70
+ credential: {
71
+ async set(orgId, input) {
72
+ // Convert the consumer's SPKI public PEM to the JWK the API stores
73
+ // (PEM at the edge; JWK on the wire/DB/VC stays unchanged).
74
+ const publicKeyJwk = (await publicPemToJwk(input.publicKeyPem));
75
+ const { data } = await client.call('SetOrgCredential', {
76
+ params: { orgId },
77
+ body: { publicKeyJwk, label: input.label },
78
+ });
79
+ return data;
80
+ },
81
+ async get(orgId) {
82
+ try {
83
+ const { data } = await client.call('GetOrgCredential', {
84
+ params: { orgId },
85
+ });
86
+ return data;
87
+ }
88
+ catch (err) {
89
+ // 404 ⇒ no credential set (plaintext delivery).
90
+ if (isNotFound(err)) {
91
+ return undefined;
92
+ }
93
+ throw err;
94
+ }
95
+ },
96
+ },
97
+ results: {
98
+ async decrypt(body, credential) {
99
+ return decryptCallback(await resolveKey(credential), body);
100
+ },
101
+ verify(jws, opts) {
102
+ return verifyResultFull(jws, { fetchImpl: config.fetch, ...opts });
103
+ },
104
+ async receive(body, opts) {
105
+ // A delivery envelope tagged with a non-terminal event carries a
106
+ // plaintext, unsigned `{ event, eventData }` payload — return it
107
+ // directly, no decryption/verification.
108
+ if (body && typeof body === 'object'
109
+ && !(body instanceof Uint8Array)
110
+ && !(body instanceof ArrayBuffer)) {
111
+ const env = body;
112
+ if (env.event
113
+ && !TERMINAL_EVENTS.includes(env.event)) {
114
+ const decoded = parseEventData(env.data);
115
+ return {
116
+ kind: 'event',
117
+ event: env.event,
118
+ eventData: decoded,
119
+ };
120
+ }
121
+ }
122
+ const { credential, ...verifyOpts } = opts ?? {};
123
+ const key = await resolveKey(credential);
124
+ const jws = await decryptCallback(key, body);
125
+ const result = await verifyResultFull(jws, {
126
+ fetchImpl: config.fetch,
127
+ ...verifyOpts,
128
+ });
129
+ return { kind: 'result', result };
130
+ },
131
+ },
132
+ };
133
+ }
134
+ /** Non-terminal `data` is a base64-encoded JSON `{ event, eventData }`. */
135
+ function parseEventData(data) {
136
+ if (!data) {
137
+ return {};
138
+ }
139
+ try {
140
+ const decoded = JSON.parse(Buffer.from(data, 'base64').toString('utf8'));
141
+ return decoded.eventData ?? {};
142
+ }
143
+ catch {
144
+ return {};
145
+ }
146
+ }
147
+ /** True for a `404` thrown by {@link ReclaimClient.call} — a Boom error whose
148
+ * `output.statusCode` is 404 (see ./errors.ts). */
149
+ function isNotFound(err) {
150
+ if (typeof err !== 'object' || err === null) {
151
+ return false;
152
+ }
153
+ const boom = err;
154
+ return boom.output?.statusCode === 404 || boom.statusCode === 404;
155
+ }
@@ -0,0 +1,132 @@
1
+ import type { JWK } from 'jose';
2
+ import type { components } from './types/openapi.gen.ts';
3
+ import type { DecryptionKey } from './enckeys.ts';
4
+ export interface Jws {
5
+ payload: string;
6
+ signatures: {
7
+ protected: string;
8
+ header?: Record<string, unknown>;
9
+ signature: string;
10
+ }[];
11
+ }
12
+ export interface VerifyResultOptions {
13
+ /** Uncompressed ETH public key (`0x04`+128 hex) of the signer; when given,
14
+ * the JWS signature is verified. Omit to only decode + check the nonce. */
15
+ signerPublicKey?: string;
16
+ /** The `reclaimSessionId` the consumer expects (the session id they got
17
+ * back at creation). When given, mismatch fails verification. */
18
+ expectedReclaimSessionId?: string;
19
+ }
20
+ export interface VerifyResultOutcome {
21
+ valid: boolean;
22
+ reason?: string;
23
+ payload: Record<string, unknown>;
24
+ reclaimSessionId?: string;
25
+ }
26
+ /**
27
+ * JWE-encrypt `plaintext` for the holder of `publicJwk` (an X25519 public JWK,
28
+ * `{kty:'OKP', crv:'X25519', x}`) as a compact JWE (`ECDH-ES + A256GCM`). Used
29
+ * by the first-party Verification Client to encrypt the signed JWS for each
30
+ * Consumer before submitting it to the Builder (which only relays the
31
+ * ciphertext). Consumers never call this — they only {@link decryptCallback}.
32
+ *
33
+ * `plaintext` is bytes (e.g. `new TextEncoder().encode(JSON.stringify(jws))`);
34
+ * returns the compact JWE string.
35
+ */
36
+ export declare function encryptForPublicJwk(publicJwk: JWK, plaintext: Uint8Array): Promise<string>;
37
+ /**
38
+ * What a consumer's callback endpoint receives — the Builder POSTs the JSON
39
+ * `VerificationResultDelivery` envelope: plaintext `sessionId`/`event`/
40
+ * `timestamp` routing hints. For terminal events `data` is a compact JWE
41
+ * string wrapping the signed JWS (or the plaintext JWS JSON when the org has
42
+ * no encryption credential). The envelope is unauthenticated; trust only the
43
+ * decrypted+verified JWS.
44
+ *
45
+ * Derived from the generated spec type so it can't drift; the hint fields
46
+ * are relaxed to optional — decryption only needs `data`.
47
+ */
48
+ export type DeliveryEnvelope = Partial<components['schemas']['VerificationResultDelivery']> & Pick<components['schemas']['VerificationResultDelivery'], 'data'>;
49
+ /** Anything a callback handler may have on hand: the parsed envelope, its
50
+ * JSON text, its raw body bytes — or a bare compact JWE / JWS string. */
51
+ export type CallbackBody = DeliveryEnvelope | string | Uint8Array | ArrayBuffer;
52
+ /**
53
+ * Decrypt a delivered terminal callback to the signed JWS. `data` is either a
54
+ * compact JWE (decrypted with the X25519 `privateKey`) or — when the org has no
55
+ * encryption credential — the plaintext JWS JSON. A compact JWE has exactly
56
+ * five dot-separated base64url segments; a JSON JWS starts with `{`.
57
+ *
58
+ * `privateKey` may be absent (plaintext delivery); an encrypted delivery with
59
+ * no key throws a clear error.
60
+ */
61
+ export declare function decryptCallback(privateKey: DecryptionKey | undefined, body: CallbackBody): Promise<Jws>;
62
+ /**
63
+ * Sign a verification result as a flattened JWS (JOSE ES256K = ECDSA/secp256k1
64
+ * over SHA-256 of the `<protected>.<payload>` signing input — a single hash).
65
+ * `kid` is the signer's ETH address (published via JWKS); `iss` is the issuer
66
+ * URL. Used by the Verification Client to sign results before encrypting them.
67
+ */
68
+ export declare function signResultJws(privateKeyHex: string, payload: Record<string, unknown>, iss: string): Jws;
69
+ /** Decode the JWS payload (base64url JSON). */
70
+ export declare function decodeJwsPayload(jws: Jws): Record<string, unknown>;
71
+ /**
72
+ * Verify a JWS signature (JOSE ES256K = ECDSA/secp256k1 over SHA-256 of the
73
+ * `<protected>.<payload>` signing input) with an uncompressed public key.
74
+ */
75
+ export declare function verifyJwsSignature(jws: Jws, signerPublicKey: string): boolean;
76
+ export interface VerifyResultFullOptions {
77
+ /** The `reclaimSessionId` the consumer expects (mismatch fails). */
78
+ expectedReclaimSessionId?: string;
79
+ /** The consumer's `orgId`; the result's `aud` must match it. */
80
+ expectedAud?: string;
81
+ /** Allowlist of trusted attestor ETH addresses (lowercased compared). */
82
+ attestorAddresses?: string[];
83
+ /** Override the VC signer key instead of JWKS discovery via header `iss`. */
84
+ signerPublicKey?: string;
85
+ /** Injectable fetch (for JWKS); defaults to global `fetch`. */
86
+ fetchImpl?: typeof fetch;
87
+ /** Override "now" (unix seconds) for `exp` checks. */
88
+ now?: number;
89
+ }
90
+ export interface ResultProofOutcome {
91
+ requestId?: string;
92
+ proofValid: boolean;
93
+ reason?: string;
94
+ attestorAddress?: string;
95
+ }
96
+ export interface VerifyResultFullOutcome extends VerifyResultOutcome {
97
+ proofs?: ResultProofOutcome[];
98
+ }
99
+ /**
100
+ * Thrown by {@link verifyResult} / {@link verifyResultFull} (and therefore
101
+ * `reclaim.results.verify`/`receive`) when a result fails verification. Carries
102
+ * the full invalid outcome so callers can inspect it in a `catch`:
103
+ *
104
+ * try {
105
+ * const { result } = await reclaim.results.receive(body, { credential })
106
+ * // result is verified — use result.payload
107
+ * } catch (err) {
108
+ * if (err instanceof ResultVerificationError) {
109
+ * // e.g. 'expired', { payload, … }
110
+ * console.error(err.reason, err.outcome)
111
+ * }
112
+ * }
113
+ */
114
+ export declare class ResultVerificationError extends Error {
115
+ /** Machine-readable failure reason — `'bad-signature'`, `'expired'`,
116
+ * `'reclaimSessionId-mismatch'`, `'aud-mismatch'`, `'invalid-proof'`, … */
117
+ readonly reason: string;
118
+ /** The full (invalid) outcome: `payload`, `reclaimSessionId`, and per-proof
119
+ * detail (`proofs`) when verification got that far. */
120
+ readonly outcome: VerifyResultFullOutcome;
121
+ constructor(outcome: VerifyResultFullOutcome);
122
+ }
123
+ /**
124
+ * Full consumer verification: discovers the Verification Client's signing key
125
+ * via JWKS (header `iss`/`kid`), verifies the top-level JWS, checks
126
+ * `reclaimSessionId`/`aud`/`exp`, then verifies each result's nested attestor
127
+ * proof and its `content_hash`. Network access (JWKS) makes this async; use
128
+ * {@link verifyResult} for the offline primitive.
129
+ */
130
+ export declare function verifyResultFull(jws: Jws, opts?: VerifyResultFullOptions): Promise<VerifyResultFullOutcome>;
131
+ /** Decrypt-and-verify in one step. */
132
+ export declare function verifyResult(jws: Jws, opts?: VerifyResultOptions): VerifyResultOutcome;