@reclaimprotocol/client 0.1.0 → 0.1.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/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@reclaimprotocol/client",
3
3
  "title": "Reclaim Client",
4
- "version": "0.1.0",
4
+ "version": "0.1.2",
5
5
  "description": "Typed SDK client for @reclaimprotocol/app. Used by @reclaimprotocol/agent and any other consumer that needs programmatic API access without the full agent.",
6
6
  "type": "module",
7
+ "sideEffects": false,
7
8
  "main": "./lib/index.js",
8
9
  "types": "./lib/index.d.ts",
9
10
  "exports": {
@@ -14,6 +15,10 @@
14
15
  "./openapi": {
15
16
  "types": "./lib/types/index.d.ts",
16
17
  "import": "./lib/types/index.js"
18
+ },
19
+ "./enckeys": {
20
+ "types": "./lib/enckeys.d.ts",
21
+ "import": "./lib/enckeys.js"
17
22
  }
18
23
  },
19
24
  "files": [
@@ -54,7 +59,7 @@
54
59
  "@noble/secp256k1": "^3.1.0",
55
60
  "@reclaimprotocol/attestor-core": "^5.0.7",
56
61
  "canonicalize": "^2.1.0",
57
- "jose": "^5.10.0"
62
+ "eciesjs": "^0.4.15"
58
63
  },
59
64
  "devDependencies": {
60
65
  "@types/node": "^24.0.0",
package/src/enckeys.ts CHANGED
@@ -1,46 +1,125 @@
1
1
  /**
2
- * X25519 encryption-key helpers for JWE result delivery (RFC 7516,
3
- * `ECDH-ES + A256GCM`), all via `jose`. Consumers mint the keypair themselves
4
- * with openssl (we never generate keys for them):
2
+ * secp256k1 (Ethereum) encryption-key helpers for ECIES result delivery.
5
3
  *
6
- * # private (PKCS#8):
7
- * openssl genpkey -algorithm X25519 -out reclaim-x25519.pem
8
- * # public (SPKI):
9
- * openssl pkey -in reclaim-x25519.pem -pubout \
10
- * -out reclaim-x25519.pub.pem
4
+ * The org's encryption key is its **Ethereum public key** (secp256k1). The
5
+ * Verification Client ECIES-encrypts the signed JWS to it (see ./result.ts) and
6
+ * the Consumer decrypts with the matching eth **private key**. Eth-native `0x`
7
+ * hex is used everywhere at the edges (the format MetaMask/ethers/wallets
8
+ * emit); the wire/DB store the public key as a secp256k1 JWK
9
+ * (`{kty:'EC',crv:'secp256k1',x,y}`), matching the signing JWKS shape in
10
+ * ./jwks.ts.
11
11
  *
12
- * Register the PUBLIC PEM as the org credential (`reclaim.credential.set` turns
13
- * it into the JWK the API stores) and keep the PRIVATE PEM to decrypt delivered
14
- * results (`results.receive(body, { credential: <private PEM> })`). Pass PEM
15
- * *content* — never a file path or env var. Eth is not involved in encryption;
16
- * it only signs/verifies the inner JWS (see ./result.ts, ./secp.ts).
12
+ * No PEM, no DER, no WebCrypto — `@noble/secp256k1` handles hex in every
13
+ * runtime.
17
14
  */
18
- import type { JWK } from 'jose'
19
- import { exportJWK, importPKCS8, importSPKI } from 'jose'
15
+ import { b64url, hexToBytes } from './encoding.ts'
16
+ import { type Jwk, jwkToUncompressedPublicKey } from './jwks.ts'
17
+ import {
18
+ addressFromPublicKey,
19
+ deriveFromPrivateKey,
20
+ generateKeypair,
21
+ } from './keypair.ts'
20
22
 
21
- /** A jose-usable private key — what `importPKCS8` returns and what
22
- * `compactDecrypt` accepts as the recipient key. */
23
- export type DecryptionKey = Awaited<ReturnType<typeof importPKCS8>>
24
-
25
- /** The consumer's X25519 private key as a PKCS#8 PEM string — the *content* of
26
- * `openssl genpkey -algorithm X25519` (the `-----BEGIN PRIVATE KEY-----`
27
- * block), not a path or env-var name. */
23
+ /** The Consumer's eth private key as `0x`+64 hex (a 32-byte secp256k1
24
+ * scalar). */
28
25
  export type DecryptionKeyInput = string
26
+ /** The raw 32-byte private scalar — what `eciesjs.decrypt` consumes. */
27
+ export type DecryptionKey = Uint8Array
28
+
29
+ const ETH_PRIVATE_HEX = /^0x[0-9a-fA-F]{64}$/
30
+ const UNCOMPRESSED_PUBKEY_HEX = /^0x04[0-9a-fA-F]{128}$/
31
+
32
+ /**
33
+ * Validate and load an eth private key (`0x`+64 hex) into the raw scalar bytes
34
+ * `eciesjs.decrypt` needs. Pass the hex content — never a path or env-var name.
35
+ */
36
+ export function loadDecryptionKey(input: DecryptionKeyInput): DecryptionKey {
37
+ const v = input.trim().replace(/^['"]|['"]$/g, '')
38
+ const hex = v.startsWith('0x') ? v : '0x' + v
39
+ if(!ETH_PRIVATE_HEX.test(hex)) {
40
+ throw new Error(
41
+ 'not a valid Ethereum private key (expected a 32-byte / 0x+64-hex '
42
+ + 'scalar)',
43
+ )
44
+ }
45
+
46
+ return hexToBytes(hex)
47
+ }
48
+
49
+ /**
50
+ * Convert an uncompressed secp256k1 public key (`0x04` + 128 hex) to the public
51
+ * JWK the API stores and the Verification Client encrypts to — so callers only
52
+ * ever handle eth-native hex, never a JWK. Mirrors the `x`/`y` coordinate split
53
+ * in `jwksFromPrivateKey` (./jwks.ts).
54
+ */
55
+ export function uncompressedPubkeyToJwk(publicKeyHex: string): Jwk {
56
+ const v = publicKeyHex.trim()
57
+ const hex = v.startsWith('0x') ? v : '0x' + v
58
+ if(!UNCOMPRESSED_PUBKEY_HEX.test(hex)) {
59
+ throw new Error(
60
+ 'not a valid uncompressed secp256k1 public key '
61
+ + '(expected `0x04` + 128 hex)',
62
+ )
63
+ }
64
+
65
+ const bytes = hexToBytes(hex) // 65 bytes: 0x04 ‖ X ‖ Y
66
+ return {
67
+ kty: 'EC',
68
+ crv: 'secp256k1',
69
+ x: b64url(bytes.subarray(1, 33)),
70
+ y: b64url(bytes.subarray(33, 65)),
71
+ }
72
+ }
29
73
 
30
- const ALG = 'ECDH-ES'
74
+ /** Lowercase 0x ETH address that owns a secp256k1 public JWK (the org's
75
+ * identity address). */
76
+ export function pubkeyJwkToEthAddress(jwk: Jwk): string {
77
+ return addressFromPublicKey(jwkToUncompressedPublicKey(jwk))
78
+ }
79
+
80
+ /** A freshly generated org encryption keypair, in every form callers need:
81
+ * the eth `0x`-hex private key (kept by the Consumer to decrypt), the
82
+ * uncompressed public key hex, the public JWK (sent to the API / stored), and
83
+ * the derived eth address. */
84
+ export interface GeneratedEthKeypair {
85
+ privateKeyHex: string
86
+ publicKeyHex: string
87
+ publicJwk: Jwk
88
+ ethAddress: string
89
+ }
31
90
 
32
- /** Load an X25519 PKCS#8 PEM (the openssl `BEGIN PRIVATE KEY` block) as the
33
- * `ECDH-ES` recipient key for `compactDecrypt`. Pass the PEM content. */
34
- export async function loadDecryptionKey(
35
- pem: DecryptionKeyInput,
36
- ): Promise<DecryptionKey> {
37
- return (await importPKCS8(pem.trim(), ALG))
91
+ /** Mint a fresh secp256k1 keypair for org encryption — the in-browser dashboard
92
+ * generator, the SDK, and tests all use this so every keypair is shaped the
93
+ * same way. The private key MUST stay local; only the public JWK is sent. */
94
+ export function generateEthKeypair(): GeneratedEthKeypair {
95
+ const { privateKey, publicKey, address } = generateKeypair()
96
+ return {
97
+ privateKeyHex: privateKey,
98
+ publicKeyHex: publicKey,
99
+ publicJwk: uncompressedPubkeyToJwk(publicKey),
100
+ ethAddress: address,
101
+ }
38
102
  }
39
103
 
40
- /** Convert an X25519 SPKI public PEM (the openssl `BEGIN PUBLIC KEY` block) to
41
- * the public JWK the API stores and the Verification Client encrypts to — so a
42
- * consumer only ever handles PEM, never a JWK. */
43
- export async function publicPemToJwk(pem: string): Promise<JWK> {
44
- const key = await importSPKI(pem.trim(), ALG, { extractable: true })
45
- return exportJWK(key)
104
+ /** Derive the same `GeneratedEthKeypair` shape from a private key the user
105
+ * already holds (`0x`+64 hex), so they can BYO a key instead of generating
106
+ * one. Validates the format and throws otherwise. The private key stays
107
+ * local — only the derived public key/address are ever surfaced. */
108
+ export function ethKeypairFromPrivateKey(input: string): GeneratedEthKeypair {
109
+ const v = input.trim().replace(/^['"]|['"]$/g, '')
110
+ const hex = v.startsWith('0x') ? v : '0x' + v
111
+ if(!ETH_PRIVATE_HEX.test(hex)) {
112
+ throw new Error(
113
+ 'not a valid Ethereum private key (expected a 32-byte / 0x+64-hex '
114
+ + 'scalar)',
115
+ )
116
+ }
117
+
118
+ const { privateKey, publicKey, address } = deriveFromPrivateKey(hex)
119
+ return {
120
+ privateKeyHex: privateKey,
121
+ publicKeyHex: publicKey,
122
+ publicJwk: uncompressedPubkeyToJwk(publicKey),
123
+ ethAddress: address,
124
+ }
46
125
  }
package/src/encoding.ts CHANGED
@@ -2,6 +2,11 @@
2
2
  * Byte / hex / base64url encoders shared across the verification crypto paths
3
3
  * (result, jwks, attestor-proof). `hexToBytes` tolerates an optional `0x`
4
4
  * prefix, matching how ETH keys/addresses are passed around.
5
+ *
6
+ * base64url uses the `atob`/`btoa` globals + `TextEncoder` rather than Node's
7
+ * `Buffer` so the same code runs in the browser (the in-page keypair
8
+ * generator) — `Buffer` is undefined there. Both globals exist in Node ≥ 16
9
+ * and every browser.
5
10
  */
6
11
  export function hexToBytes(hex: string): Uint8Array {
7
12
  const clean = hex.startsWith('0x') ? hex.slice(2) : hex
@@ -26,13 +31,26 @@ export function bytesToHex(bytes: Uint8Array): string {
26
31
  export function base64urlToBytes(s: string): Uint8Array {
27
32
  const b64 = s.replace(/-/g, '+').replace(/_/g, '/')
28
33
  const pad = b64.length % 4 ? '='.repeat(4 - (b64.length % 4)) : ''
29
- return Uint8Array.from(Buffer.from(b64 + pad, 'base64'))
34
+ const bin = atob(b64 + pad)
35
+ const out = new Uint8Array(bin.length)
36
+ for(let i = 0; i < bin.length; i++) {
37
+ out[i] = bin.charCodeAt(i)
38
+ }
39
+
40
+ return out
30
41
  }
31
42
 
32
43
  /** Encode bytes (or a UTF-8 string) as unpadded base64url. */
33
44
  export function b64url(input: Uint8Array | string): string {
34
- return Buffer.from(input as Uint8Array)
35
- .toString('base64')
45
+ const bytes = typeof input === 'string'
46
+ ? new TextEncoder().encode(input)
47
+ : input
48
+ let bin = ''
49
+ for(const b of bytes) {
50
+ bin += String.fromCharCode(b)
51
+ }
52
+
53
+ return btoa(bin)
36
54
  .replace(/\+/g, '-')
37
55
  .replace(/\//g, '_')
38
56
  .replace(/=+$/, '')
package/src/index.ts CHANGED
@@ -25,7 +25,7 @@ export {
25
25
  decodeJwsPayload,
26
26
  decryptCallback,
27
27
  type DeliveryEnvelope,
28
- encryptForPublicJwk,
28
+ encryptForEthPublicKey,
29
29
  type Jws,
30
30
  ResultVerificationError,
31
31
  type ResultProofOutcome,
@@ -41,8 +41,11 @@ export {
41
41
  export {
42
42
  type DecryptionKey,
43
43
  type DecryptionKeyInput,
44
+ generateEthKeypair,
45
+ type GeneratedEthKeypair,
44
46
  loadDecryptionKey,
45
- publicPemToJwk,
47
+ pubkeyJwkToEthAddress,
48
+ uncompressedPubkeyToJwk,
46
49
  } from './enckeys.ts'
47
50
  export {
48
51
  createReclaim,
@@ -52,7 +55,7 @@ export {
52
55
  type ReceiveOptions,
53
56
  type ReceivedDelivery,
54
57
  type RegisterCallbackInput,
55
- type SetCredentialInput,
58
+ type SetKeypairInput,
56
59
  TERMINAL_EVENTS,
57
60
  } from './reclaim.ts'
58
61
  export {
package/src/keypair.ts CHANGED
@@ -6,11 +6,28 @@ export type EthKeypair = {
6
6
  address: string
7
7
  privateKey: string
8
8
  /** Uncompressed secp256k1 public key (`0x04` + 128 hex). Used to derive the
9
- * ETH address and to verify ES256K JWS signatures (published via JWKS as
10
- * `x`/`y`). Result *encryption* now uses X25519 JWE, not this key. */
9
+ * ETH address, to verify ES256K JWS signatures (published via JWKS as
10
+ * `x`/`y`), and — for an org's encryption key — to ECIES-encrypt results
11
+ * (see ./enckeys.ts, ./result.ts). */
11
12
  publicKey: string
12
13
  }
13
14
 
15
+ /** Bytes of CSPRNG seed `@noble`'s `randomSecretKey` expects (it reduces a
16
+ * ≥48-byte seed into a valid secp256k1 scalar). */
17
+ const SECRET_KEY_SEED_LEN = 48
18
+
19
+ /**
20
+ * Fresh CSPRNG bytes from the Web Crypto API. We read `crypto`
21
+ * (`getRandomValues`) — the WHATWG global present in browsers and Node ≥ 20
22
+ * alike — and SEED `@noble`'s key generation from it, rather than letting
23
+ * `@noble` dereference `globalThis.crypto` itself: its access is unguarded
24
+ * (`(globalThis?.crypto).getRandomValues(...)`) and throws an opaque
25
+ * TypeError wherever the global is absent.
26
+ */
27
+ function randomSeed(): Uint8Array {
28
+ return crypto.getRandomValues(new Uint8Array(SECRET_KEY_SEED_LEN))
29
+ }
30
+
14
31
  /**
15
32
  * Generate a fresh secp256k1 key-pair and derive its lowercase
16
33
  * Ethereum address (keccak-256 of the uncompressed pubkey minus the
@@ -23,7 +40,7 @@ export type EthKeypair = {
23
40
  * intermediate value used to compute the address and is not retained.
24
41
  */
25
42
  export function generateKeypair(): EthKeypair {
26
- const priv = secp.utils.randomSecretKey()
43
+ const priv = secp.utils.randomSecretKey(randomSeed())
27
44
  const pub = secp.getPublicKey(priv, false)
28
45
  return {
29
46
  address: addressFromPubBytes(pub),
package/src/reclaim.ts CHANGED
@@ -2,18 +2,18 @@
2
2
  * Declarative entry point for the consumer SDK. The ONLY config is the org
3
3
  * secret (`rorg_…`) — it authenticates every call and identifies the org
4
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
5
+ * to the org-scoped `keypair.set`/`get`, and the eth decryption key to
6
6
  * `results.decrypt`/`receive` (only when results arrive encrypted):
7
7
  *
8
8
  * const reclaim = createReclaim({ orgSecret })
9
9
  * // one-time setup:
10
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 })
11
+ * // enable encryption (eth public key, hex):
12
+ * await reclaim.keypair.set(orgId, { publicKey, canEncryptResult: true })
13
+ * const session = await reclaim.sessions.create({ providers, context })
14
14
  * // …end-user completes session.verificationUrl…
15
15
  * const outcome = await reclaim.results.receive(callbackBody, {
16
- * credential: privateJwk, // omit for plaintext
16
+ * credential: ethPrivateKeyHex, // omit for plaintext
17
17
  * expectedReclaimSessionId: session.id,
18
18
  * })
19
19
  *
@@ -27,7 +27,6 @@ import {
27
27
  type DecryptionKey,
28
28
  type DecryptionKeyInput,
29
29
  loadDecryptionKey,
30
- publicPemToJwk,
31
30
  } from './enckeys.ts'
32
31
  import {
33
32
  type CallbackBody,
@@ -42,8 +41,8 @@ type CallbackSubscription = components['schemas']['CallbackSubscription']
42
41
  type VerificationSession = components['schemas']['VerificationSession']
43
42
  type VerificationEvent = components['schemas']['VerificationEvent']
44
43
  type VerificationEventRecord = components['schemas']['VerificationEventRecord']
45
- type OrgCredential = components['schemas']['OrgCredential']
46
- type JwkX25519Public = components['schemas']['JwkX25519Public']
44
+ type OrgKeypair = components['schemas']['OrgKeypair']
45
+ type OrgKeypairRequest = components['schemas']['OrgKeypairRequest']
47
46
 
48
47
  /** The three terminal events a subscription can receive. */
49
48
  export const TERMINAL_EVENTS = [
@@ -58,22 +57,23 @@ export interface RegisterCallbackInput {
58
57
  }
59
58
 
60
59
  export interface CreateSessionInput {
61
- providerId: string
62
- version?: string
60
+ /** Providers to verify, in run order. The end-user proves each (one browser
61
+ * session) and one combined result is delivered; all are required. */
62
+ providers: { providerId: string, version?: string }[]
63
63
  context: Record<string, unknown>
64
64
  /** URL with a `{sessionId}` placeholder the end-user opens. */
65
65
  verificationClientUrl: string
66
66
  }
67
67
 
68
- export interface SetCredentialInput {
69
- /** The org's X25519 encryption public key as an SPKI PEM — the content of
70
- * `openssl pkey -in key.pem -pubout` (the `-----BEGIN PUBLIC KEY-----`
71
- * block). It's converted to a JWK and stored; results are JWE-encrypted to
72
- * it. Keep the matching private PEM to decrypt (pass it to
73
- * `results.receive`/`decrypt`). */
74
- publicKeyPem: string
75
- label?: string
76
- }
68
+ /** Input for `keypair.set`: the org's eth public key as uncompressed hex
69
+ * (`publicKey`, the form wallets/ethers emit) OR as a secp256k1 JWK
70
+ * (`publicKeyJwk`); keep the matching eth private key to decrypt results.
71
+ * `canEncryptResult` toggles ECIES encryption (default off). The two request
72
+ * shapes are the spec's `OrgKeypairRequest` union members, relaxed to optional
73
+ * so the SDK caller supplies just one key form. */
74
+ export type SetKeypairInput =
75
+ & Partial<components['schemas']['OrgKeypairHexRequest']>
76
+ & Partial<components['schemas']['OrgKeypairJwkRequest']>
77
77
 
78
78
  export interface ReclaimConfig {
79
79
  /** The org secret (`rorg_…`). Authenticates ALL API calls and identifies
@@ -88,8 +88,8 @@ export interface ReclaimConfig {
88
88
  }
89
89
 
90
90
  /** Options for `results.receive` — the verification options plus an optional
91
- * X25519 decryption key (a JWK, a PKCS#8 PEM string, or `{ env }` / env-var
92
- * name). The key is needed only when the delivery is an encrypted (JWE). */
91
+ * eth decryption key (the org's eth private key as `0x`-hex). The key is
92
+ * needed only when the delivery is an ECIES ciphertext. */
93
93
  export type ReceiveOptions = VerifyResultFullOptions & {
94
94
  credential?: DecryptionKeyInput
95
95
  }
@@ -110,9 +110,9 @@ export interface Reclaim {
110
110
  }
111
111
  callbacks: {
112
112
  /** Register a callback subscription (one-time setup). Delivery is scoped
113
- * to the token's org; results are JWE-encrypted to the org's credential
114
- * when one is set (see `credential.set`), else plaintext. `events`
115
- * defaults to all three terminal events. */
113
+ * to the token's org; results are ECIES-encrypted to the org's eth key
114
+ * when `canEncryptResult` is enabled (see `keypair.set`), else plaintext.
115
+ * `events` defaults to all three terminal events. */
116
116
  register(
117
117
  input: Partial<RegisterCallbackInput> &
118
118
  Pick<RegisterCallbackInput, 'callbackUrl'>,
@@ -120,18 +120,19 @@ export interface Reclaim {
120
120
  list(): Promise<CallbackSubscription[]>
121
121
  remove(subscriptionId: string): Promise<void>
122
122
  }
123
- credential: {
124
- /** Set/replace org `orgId`'s single X25519 encryption credential. Once
125
- * set, terminal results are delivered as a compact JWE encrypted to it. */
126
- set(orgId: string, input: SetCredentialInput): Promise<OrgCredential>
127
- /** Get org `orgId`'s encryption credential, or `undefined` when none is
128
- * set (delivery is plaintext). */
129
- get(orgId: string): Promise<OrgCredential | undefined>
123
+ keypair: {
124
+ /** Set/rotate org `orgId`'s single eth encryption keypair. When
125
+ * `canEncryptResult` is enabled, terminal results are delivered as an
126
+ * ECIES ciphertext encrypted to it. */
127
+ set(orgId: string, input: SetKeypairInput): Promise<OrgKeypair>
128
+ /** Get org `orgId`'s encryption keypair, or `undefined` when none is set
129
+ * (the org is keyless ⇒ plaintext delivery). */
130
+ get(orgId: string): Promise<OrgKeypair | undefined>
130
131
  }
131
132
  results: {
132
- /** Decrypt a callback body (JWE or plaintext JWS) to the signed JWS.
133
- * Pass the X25519 `credential` (JWK/PEM/env) only when the delivery is
134
- * a JWE. */
133
+ /** Decrypt a callback body (ECIES ciphertext or plaintext JWS) to the
134
+ * signed JWS. Pass the eth `credential` (`0x`-hex private key) only when
135
+ * the delivery is encrypted. */
135
136
  decrypt(body: CallbackBody, credential?: DecryptionKeyInput): Promise<Jws>
136
137
  /** Verify a decrypted JWS: JWKS discovery, ES256K signature,
137
138
  * `reclaimSessionId`/`aud`/`exp`, nested attestor proofs. */
@@ -177,14 +178,12 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
177
178
  headers: config.headers,
178
179
  })
179
180
 
180
- // Load an X25519 decryption key on demand — `undefined` input ⇒ no key, so
181
+ // Load the eth decryption key on demand — `undefined` input ⇒ no key, so
181
182
  // plaintext-only consumers never touch the loader.
182
183
  const resolveKey = (
183
184
  input: DecryptionKeyInput | undefined,
184
- ): Promise<DecryptionKey | undefined> => (
185
- input === undefined
186
- ? Promise.resolve(undefined)
187
- : loadDecryptionKey(input)
185
+ ): DecryptionKey | undefined => (
186
+ input === undefined ? undefined : loadDecryptionKey(input)
188
187
  )
189
188
 
190
189
  return {
@@ -232,27 +231,31 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
232
231
  })
233
232
  },
234
233
  },
235
- credential: {
234
+ keypair: {
236
235
  async set(orgId, input) {
237
- // Convert the consumer's SPKI public PEM to the JWK the API stores
238
- // (PEM at the edge; JWK on the wire/DB/VC stays unchanged).
239
- const publicKeyJwk = (await publicPemToJwk(
240
- input.publicKeyPem,
241
- )) as JwkX25519Public
242
- const { data } = await client.call('SetOrgCredential', {
236
+ // Forward the eth public key as given (hex or JWK); the API stores
237
+ // the secp256k1 JWK + derived address.
238
+ const body = {
239
+ ...(input.publicKeyJwk
240
+ ? { publicKeyJwk: input.publicKeyJwk }
241
+ : { publicKey: input.publicKey }),
242
+ canEncryptResult: input.canEncryptResult ?? false,
243
+ ...(input.label ? { label: input.label } : {}),
244
+ } as OrgKeypairRequest
245
+ const { data } = await client.call('SetOrgKeypair', {
243
246
  params: { orgId },
244
- body: { publicKeyJwk, label: input.label },
247
+ body,
245
248
  })
246
249
  return data
247
250
  },
248
251
  async get(orgId) {
249
252
  try {
250
- const { data } = await client.call('GetOrgCredential', {
253
+ const { data } = await client.call('GetOrgKeypair', {
251
254
  params: { orgId },
252
255
  })
253
256
  return data
254
257
  } catch(err) {
255
- // 404 ⇒ no credential set (plaintext delivery).
258
+ // 404 ⇒ keyless org (plaintext delivery).
256
259
  if(isNotFound(err)) {
257
260
  return undefined
258
261
  }
@@ -263,7 +266,7 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
263
266
  },
264
267
  results: {
265
268
  async decrypt(body, credential) {
266
- return decryptCallback(await resolveKey(credential), body)
269
+ return decryptCallback(resolveKey(credential), body)
267
270
  },
268
271
  verify(jws, opts) {
269
272
  return verifyResultFull(jws, { fetchImpl: config.fetch, ...opts })
@@ -292,7 +295,7 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
292
295
  }
293
296
 
294
297
  const { credential, ...verifyOpts } = opts ?? {}
295
- const key = await resolveKey(credential)
298
+ const key = resolveKey(credential)
296
299
  const jws = await decryptCallback(key, body)
297
300
  const result = await verifyResultFull(jws, {
298
301
  fetchImpl: config.fetch,