@zkproofport-app/sdk 0.5.0-beta.2 → 0.5.0-beta.4

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/dist/index.esm.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { checkProofIssuer, parseProofForOnChain, verifyProofOnChain, getPinnedVerifier, extractScopeFromPublicInputs, extractNullifierFromPublicInputs, extractDomainFromPublicInputs } from '@zkproofport-app/core';
2
- export { COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT, MDL_KR_PUBLIC_INPUT_LAYOUT, OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT, PINNED_VERIFIERS, extractDomainFromPublicInputs, extractNullifierFromPublicInputs, extractScopeFromPublicInputs, getPinnedVerifier } from '@zkproofport-app/core';
3
- import { isCircuitId, ALL_CIRCUIT_IDS, circuitActionBinding, CIRCUIT_VK_PATHS, CIRCUIT_NEEDS_WALLET_SIGNATURE } from '@zkproofport-app/core/circuits';
4
- export { ALL_CIRCUIT_IDS, CIRCUIT_ACTION_BINDING, CIRCUIT_IDS, CIRCUIT_SUPPORT_STATUS, CIRCUIT_VK_PATHS, EXPERIMENTAL_CIRCUIT_IDS, PLANNED_CIRCUIT_IDS, SUPPORTED_CIRCUIT_IDS, circuitActionBinding, getCircuitSupportStatus, isCircuitId, isSupportedCircuitId } from '@zkproofport-app/core/circuits';
2
+ export { COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT, OIDC_ALLOWED_AUDIENCES, OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT, OIDC_NONCE_DOMAIN, OIDC_NOW_MAX_AGE_SECONDS, OIDC_NOW_MAX_FUTURE_SECONDS, OIDC_V1_PUBLIC_INPUT_COUNT, OIDC_V2_PUBLIC_INPUT_COUNT, PINNED_VARIANT_VERIFIERS, PINNED_VERIFIERS, checkOidcPolicy, deriveOidcNonceBinding, extractDomainFromPublicInputs, extractNullifierFromPublicInputs, extractScopeFromPublicInputs, getPinnedVariantVerifier, getPinnedVerifier, oidcAudienceHash } from '@zkproofport-app/core';
3
+ import { isCircuitId, ALL_CIRCUIT_IDS, circuitActionBinding, resolveSignerVariant, getCircuitPackage, CIRCUIT_NEEDS_WALLET_SIGNATURE } from '@zkproofport-app/core/circuits';
4
+ export { ALL_CIRCUIT_IDS, CIRCUIT_ACTION_BINDING, CIRCUIT_IDS, CIRCUIT_PACKAGES, CIRCUIT_SUPPORT_STATUS, CIRCUIT_VK_PATHS, EXPERIMENTAL_CIRCUIT_IDS, PLANNED_CIRCUIT_IDS, SIGNER_VARIANTS, SUPPORTED_CIRCUIT_IDS, circuitActionBinding, evmProofByteLength, getCircuitPackage, getCircuitSupportStatus, isCircuitId, isSignerVariant, isSupportedCircuitId, resolveSignerVariant, signerVariantsOf } from '@zkproofport-app/core/circuits';
5
5
  import { validateTypedAction } from '@zkproofport-app/core/typedAction';
6
6
  export { parseTypedAction, validateTypedAction } from '@zkproofport-app/core/typedAction';
7
7
 
@@ -30,7 +30,7 @@ const RELAY_URLS = {
30
30
  * Mirrors the mobile app's `CIRCUIT_NETWORKS` (proofport-app
31
31
  * src/config/contracts.ts), because a proof is checked where the app said its
32
32
  * verifier is: Base for the Coinbase and OIDC circuits, GIWA Sepolia for GIWA
33
- * and Dojang (testnet-only), Base Sepolia for Korea Mobile ID (testnet-only).
33
+ * and Dojang (testnet-only).
34
34
  * The ADDRESS on that chain always comes from core's pinned table, never from
35
35
  * a proof response.
36
36
  */
@@ -41,9 +41,6 @@ const VERIFICATION_CHAINS = {
41
41
  oidc_domain_attestation: 8453,
42
42
  giwa_attestation: 91342,
43
43
  dojang_playground: 91342,
44
- mdl_kr_ownership: 84532,
45
- mdl_kr_age: 84532,
46
- mdl_kr_region: 84532,
47
44
  },
48
45
  staging: {
49
46
  coinbase_attestation: 84532,
@@ -51,9 +48,6 @@ const VERIFICATION_CHAINS = {
51
48
  oidc_domain_attestation: 84532,
52
49
  giwa_attestation: 91342,
53
50
  dojang_playground: 91342,
54
- mdl_kr_ownership: 84532,
55
- mdl_kr_age: 84532,
56
- mdl_kr_region: 84532,
57
51
  },
58
52
  local: {
59
53
  coinbase_attestation: 84532,
@@ -61,9 +55,6 @@ const VERIFICATION_CHAINS = {
61
55
  oidc_domain_attestation: 84532,
62
56
  giwa_attestation: 91342,
63
57
  dojang_playground: 91342,
64
- mdl_kr_ownership: 84532,
65
- mdl_kr_age: 84532,
66
- mdl_kr_region: 84532,
67
58
  },
68
59
  };
69
60
  /**
@@ -124,8 +115,10 @@ const CIRCUIT_METADATA = {
124
115
  oidc_domain_attestation: {
125
116
  name: 'OIDC Domain',
126
117
  description: 'Prove email domain affiliation via OIDC JWT',
127
- publicInputsCount: 148,
128
- publicInputNames: ['pubkey_modulus_limbs', 'domain', 'scope', 'nullifier', 'provider'],
118
+ // v2: 148 v1 fields, then aud_hash (32), nonce_mode (1), nonce_binding
119
+ // (32), now (1). oidc-domain-attestation/src/main.nr.
120
+ publicInputsCount: 214,
121
+ publicInputNames: ['pubkey_modulus_limbs', 'domain', 'scope', 'nullifier', 'provider', 'aud_hash', 'nonce_mode', 'nonce_binding', 'now'],
129
122
  },
130
123
  // Planned circuit — the identifier is reserved and the circuit exists, but it
131
124
  // is not officially supported yet. See CIRCUIT_SUPPORT_STATUS in ./circuits.
@@ -158,27 +151,6 @@ const CIRCUIT_METADATA = {
158
151
  publicInputsCount: 160,
159
152
  publicInputNames: ['signal_hash', 'domain_separator', 'action_hash', 'scope', 'nullifier'],
160
153
  },
161
- // Korea Mobile ID (mDL) circuits. Planned — not officially supported yet.
162
- // Counts are byte-flattened field totals from the compiled circuit ABIs:
163
- // [u8; 32] arrays contribute 32 inputs each.
164
- mdl_kr_ownership: {
165
- name: 'Korea Mobile ID — Ownership',
166
- description: "Prove valid Korean mobile driver's license ownership with selective disclosure",
167
- publicInputsCount: 97,
168
- publicInputNames: ['scope', 'nullifier_value', 'disclose_flags', 'owner_commit'],
169
- },
170
- mdl_kr_age: {
171
- name: 'Korea Mobile ID — Age',
172
- description: "Prove minimum age from Korean mobile driver's license without revealing birth date",
173
- publicInputsCount: 66,
174
- publicInputNames: ['scope', 'nullifier_value', 'age_threshold', 'current_year'],
175
- },
176
- mdl_kr_region: {
177
- name: 'Korea Mobile ID — Region',
178
- description: "Prove si/do residency from Korean mobile driver's license without revealing address",
179
- publicInputsCount: 96,
180
- publicInputNames: ['scope', 'nullifier_value', 'region_code'],
181
- },
182
154
  };
183
155
  /**
184
156
  * Default proof request expiration time in milliseconds.
@@ -422,6 +394,12 @@ function parseProofResponseUrl(url) {
422
394
  if (timestamp) {
423
395
  response.timestamp = parseInt(timestamp, 10);
424
396
  }
397
+ // Kept as sent, even when unknown: verification refuses it by name,
398
+ // which says more than a callback that fails to parse.
399
+ const signerVariant = urlObj.searchParams.get('signerVariant');
400
+ if (signerVariant !== null) {
401
+ response.signerVariant = signerVariant;
402
+ }
425
403
  }
426
404
  else if (status === 'error') {
427
405
  response.error = urlObj.searchParams.get('error') || undefined;
@@ -453,94 +431,6 @@ function parseProofResponseUrl(url) {
453
431
  function isProofportDeepLink(url, scheme = DEFAULT_SCHEME) {
454
432
  return url.toLowerCase().startsWith(`${scheme.toLowerCase()}://`);
455
433
  }
456
- /**
457
- * @internal
458
- * Validates a proof request for completeness and correctness.
459
- *
460
- * Performs comprehensive validation including:
461
- * - Required fields (requestId, circuit, callbackUrl)
462
- * - Circuit type validity (must be a supported circuit)
463
- * - Circuit-specific input validation (e.g., userAddress format, countryList structure)
464
- * - Expiration check (if expiresAt is provided)
465
- *
466
- * Note: userAddress is optional for both circuits - the mobile app will prompt
467
- * for wallet connection if not provided.
468
- *
469
- * @param request - Proof request to validate
470
- * @returns Validation result with `valid` flag and optional `error` message
471
- */
472
- /**
473
- * Validates a free-text mDL input (scope / targetRegion).
474
- * Rejects empty or whitespace-only values, control characters (which break
475
- * deep-link query encoding and downstream logging), and values over 256 chars
476
- * (server-side length cap mirror). UTF-8 text such as Korean region names is
477
- * allowed.
478
- *
479
- * @returns Error message string, or null when valid.
480
- */
481
- function validateMdlTextInput(value, fieldName) {
482
- if (typeof value !== 'string' || value.trim() === '') {
483
- return `${fieldName} is required and must be a non-empty string`;
484
- }
485
- if (value.length > 256) {
486
- return `${fieldName} must be 256 characters or fewer`;
487
- }
488
- // eslint-disable-next-line no-control-regex
489
- if (/[\u0000-\u001F\u007F]/.test(value)) {
490
- return `${fieldName} must not contain control characters`;
491
- }
492
- return null;
493
- }
494
- /**
495
- * Validates dapp-supplied inputs for the Korea Mobile ID (mDL) circuits.
496
- * Shared by deep-link request validation and relay request creation so the
497
- * same guards apply on every request path.
498
- *
499
- * Rules:
500
- * - `scope` — required non-empty string, ≤256 chars, no control characters
501
- * - `mdl_kr_ownership.discloseFlags` — optional integer 0–15 (0x0F bitmask)
502
- * - `mdl_kr_age.ageThreshold` — required integer 1–150
503
- * - `mdl_kr_region.targetRegion` — required non-empty string (Korean si/do),
504
- * ≤256 chars, no control characters
505
- *
506
- * @param circuit - One of the three mDL circuit identifiers
507
- * @param inputs - Dapp-supplied circuit inputs
508
- * @returns Error message string, or null when the inputs are valid
509
- */
510
- function validateMdlInputs(circuit, inputs) {
511
- const scopeError = validateMdlTextInput(inputs.scope, 'scope');
512
- if (scopeError) {
513
- return scopeError;
514
- }
515
- if (circuit === 'mdl_kr_ownership') {
516
- const { discloseFlags } = inputs;
517
- if (discloseFlags !== undefined) {
518
- if (typeof discloseFlags !== 'number' ||
519
- !Number.isInteger(discloseFlags) ||
520
- discloseFlags < 0 ||
521
- discloseFlags > 0x0f) {
522
- return 'discloseFlags must be an integer between 0 and 15';
523
- }
524
- }
525
- }
526
- else if (circuit === 'mdl_kr_age') {
527
- const { ageThreshold } = inputs;
528
- if (typeof ageThreshold !== 'number' ||
529
- !Number.isInteger(ageThreshold) ||
530
- ageThreshold < 1 ||
531
- ageThreshold > 150) {
532
- return 'ageThreshold is required and must be an integer between 1 and 150';
533
- }
534
- }
535
- else {
536
- const { targetRegion } = inputs;
537
- const regionError = validateMdlTextInput(targetRegion, 'targetRegion');
538
- if (regionError) {
539
- return regionError;
540
- }
541
- }
542
- return null;
543
- }
544
434
  /**
545
435
  * Longest accepted `returnScheme` value.
546
436
  * Kept in sync with proofport-relay `src/returnScheme.ts`.
@@ -700,6 +590,22 @@ function detectReturnScheme(userAgent) {
700
590
  }
701
591
  return undefined;
702
592
  }
593
+ /**
594
+ * @internal
595
+ * Validates a proof request for completeness and correctness.
596
+ *
597
+ * Performs comprehensive validation including:
598
+ * - Required fields (requestId, circuit, callbackUrl)
599
+ * - Circuit type validity (must be a supported circuit)
600
+ * - Circuit-specific input validation (e.g., userAddress format, countryList structure)
601
+ * - Expiration check (if expiresAt is provided)
602
+ *
603
+ * Note: userAddress is optional for both circuits - the mobile app will prompt
604
+ * for wallet connection if not provided.
605
+ *
606
+ * @param request - Proof request to validate
607
+ * @returns Validation result with `valid` flag and optional `error` message
608
+ */
703
609
  function validateProofRequest(request) {
704
610
  if (!request.requestId) {
705
611
  return { valid: false, error: 'Missing requestId' };
@@ -755,14 +661,6 @@ function validateProofRequest(request) {
755
661
  return { valid: false, error: 'provider must be a non-empty string when specified' };
756
662
  }
757
663
  }
758
- else if (request.circuit === 'mdl_kr_ownership' ||
759
- request.circuit === 'mdl_kr_age' ||
760
- request.circuit === 'mdl_kr_region') {
761
- const mdlError = validateMdlInputs(request.circuit, request.inputs);
762
- if (mdlError) {
763
- return { valid: false, error: mdlError };
764
- }
765
- }
766
664
  // The action, decided by the circuit's own entry rather than by naming a
767
665
  // circuit here, so every action-capable circuit is checked the same way.
768
666
  {
@@ -3866,7 +3764,7 @@ function estimateQRDataSize(requestOrUrl, scheme = DEFAULT_SCHEME) {
3866
3764
  * The version. `@aztec/bb.js` must be the WASM build of the same bb the
3867
3765
  * circuits are compiled with -- `BB_VERSION` in circuits/toolchain.env,
3868
3766
  * 4.2.0-aztecnr-rc.2 since the beta.19 toolchain. Versions are not
3869
- * interchangeable: on the Korea Mobile ID age circuit the 2025 nightly wrote
3767
+ * interchangeable: on one of our circuits the 2025 nightly wrote
3870
3768
  * a 16256-byte proof where bb 1.2.0 wrote 16224 for the identical circuit and
3871
3769
  * witness, with the SAME verification key, so the key cannot warn you. A
3872
3770
  * mismatch shows up only as verification failing.
@@ -3968,15 +3866,12 @@ function normalisePublicInputs(publicInputs) {
3968
3866
  *
3969
3867
  * @internal
3970
3868
  */
3971
- async function fetchVerificationKey(circuit, timeoutMs) {
3972
- const cached = vkCache.get(circuit);
3869
+ async function fetchVerificationKey(circuit, variant, timeoutMs) {
3870
+ const cacheKey = `${circuit}/${variant}`;
3871
+ const cached = vkCache.get(cacheKey);
3973
3872
  if (cached && Date.now() - cached.at < VK_CACHE_TTL_MS)
3974
3873
  return cached.vk;
3975
- const vkPath = CIRCUIT_VK_PATHS[circuit];
3976
- if (!vkPath) {
3977
- throw new Error(`No verification key path for circuit '${circuit}'. ` +
3978
- `Add it to CIRCUIT_VK_PATHS, or pass verificationKey directly.`);
3979
- }
3874
+ const vkPath = getCircuitPackage(circuit, variant).vkPath;
3980
3875
  const url = `${CIRCUITS_VK_BASE}/${vkPath}`;
3981
3876
  const controller = new AbortController();
3982
3877
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -3993,7 +3888,7 @@ async function fetchVerificationKey(circuit, timeoutMs) {
3993
3888
  throw new Error(`The verification key at ${url} is ${vk.length} bytes, far too small to be one. ` +
3994
3889
  `The path is probably wrong and something else was served.`);
3995
3890
  }
3996
- vkCache.set(circuit, { vk, at: Date.now() });
3891
+ vkCache.set(cacheKey, { vk, at: Date.now() });
3997
3892
  return vk;
3998
3893
  }
3999
3894
  finally {
@@ -4006,9 +3901,11 @@ async function fetchVerificationKey(circuit, timeoutMs) {
4006
3901
  * Needs no setup from the caller: the verifier ships with this SDK and is
4007
3902
  * loaded on first use.
4008
3903
  *
4009
- * For `oidc_domain_attestation` it first checks the proof's RSA key against the
4010
- * provider's published JWKS (core's `checkOidcProviderKey`); a key the provider
4011
- * does not publish, or a JWKS that cannot be fetched, is `{ valid: false }`.
3904
+ * For `oidc_domain_attestation` it first checks the token's audience, nonce and
3905
+ * time (core's `checkOidcPolicy`, against `options.request` when given) and the
3906
+ * proof's RSA key against the provider's published JWKS (core's
3907
+ * `checkOidcProviderKey`); any refusal, or a JWKS that cannot be fetched, is
3908
+ * `{ valid: false }`.
4012
3909
  *
4013
3910
  * @param circuit - Canonical circuit identifier
4014
3911
  * @param proof - Proof bytes as hex, WITHOUT public inputs (as the relay sends it)
@@ -4031,12 +3928,16 @@ async function verifyProofOffChain(circuit, proof, publicInputs, options = {}) {
4031
3928
  if (!isCircuitId(circuit)) {
4032
3929
  return { valid: false, error: `Unknown circuit '${circuit}'. Known: ${ALL_CIRCUIT_IDS.join(', ')}.` };
4033
3930
  }
3931
+ const signer = resolveSignerVariant(circuit, options.signerVariant, proof);
3932
+ if ('error' in signer) {
3933
+ return { valid: false, error: signer.error };
3934
+ }
4034
3935
  // Before Barretenberg, and for the same reason as on-chain: a proof can be
4035
3936
  // mathematically valid and still mean nothing, because some circuits take
4036
3937
  // their trust anchor as a public input the prover chooses. For OIDC that is
4037
3938
  // the RSA key -- see core's oidcProviderKeys. Checked here so a dApp that
4038
3939
  // verifies off-chain gets the same answer as one that verifies on-chain.
4039
- const issuer = await checkProofIssuer(circuit, publicInputs);
3940
+ const issuer = await checkProofIssuer(circuit, publicInputs, { request: options.request });
4040
3941
  if (!issuer.ok) {
4041
3942
  return { valid: false, error: issuer.error };
4042
3943
  }
@@ -4077,7 +3978,7 @@ async function verifyProofOffChain(circuit, proof, publicInputs, options = {}) {
4077
3978
  // relay can deliver a short value (`0x01`), and the check below must see
4078
3979
  // exactly what the circuit committed to.
4079
3980
  const fields = normalisePublicInputs(publicInputs);
4080
- const vk = options.verificationKey ?? (await fetchVerificationKey(circuit, timeoutMs));
3981
+ const vk = options.verificationKey ?? (await fetchVerificationKey(circuit, signer.variant, timeoutMs));
4081
3982
  const barretenberg = await bb.Barretenberg.new({ threads: 1 });
4082
3983
  api = barretenberg;
4083
3984
  const verifier = new bb.UltraHonkVerifierBackend(barretenberg);
@@ -4111,6 +4012,17 @@ const RELAY_TERMINAL_STATUSES = ['completed', 'error', 'failed', 'cancelled', 'e
4111
4012
  function isTerminalRelayStatus(status) {
4112
4013
  return RELAY_TERMINAL_STATUSES.includes(status);
4113
4014
  }
4015
+ /**
4016
+ * How long `waitForProof()` / `waitForResult()` wait when the caller passes no
4017
+ * `timeoutMs`: 600 000 ms (10 min).
4018
+ *
4019
+ * The relay answers `expired` itself at 540 s, so with this default a dApp
4020
+ * always gets a terminal status to branch on rather than its own timeout. A
4021
+ * heavy proof on an older phone takes a few minutes after the user confirms;
4022
+ * the earlier 300 000 ms default could fire while such a proof was still being
4023
+ * made.
4024
+ */
4025
+ const DEFAULT_RELAY_WAIT_MS = 600000;
4114
4026
 
4115
4027
  /**
4116
4028
  * Proofport SDK - Main class
@@ -4178,6 +4090,13 @@ class ProofportSDK {
4178
4090
  */
4179
4091
  constructor(config = {}) {
4180
4092
  this.pendingRequests = new Map();
4093
+ /**
4094
+ * requestId -> scope of the relay requests this instance created, so that
4095
+ * verifyResponse* can check an OIDC proof's request-bound nonce without the
4096
+ * dApp passing the request back in. Oldest entries go first past
4097
+ * {@link ProofportSDK.MAX_REMEMBERED_RELAY_REQUESTS}.
4098
+ */
4099
+ this.relayRequests = new Map();
4181
4100
  this.signer = null;
4182
4101
  this.socket = null;
4183
4102
  /**
@@ -4254,7 +4173,7 @@ class ProofportSDK {
4254
4173
  const request = {
4255
4174
  requestId: generateRequestId(),
4256
4175
  // The circuit asked for, always. This used to be the literal
4257
- // 'coinbase_attestation', and every circuit that was not country or mDL
4176
+ // 'coinbase_attestation', and every circuit that was not country
4258
4177
  // was routed here -- a GIWA, Dojang or OIDC request silently became a
4259
4178
  // Coinbase KYC request.
4260
4179
  circuit,
@@ -4361,45 +4280,10 @@ class ProofportSDK {
4361
4280
  switch (builder) {
4362
4281
  case 'country':
4363
4282
  return this.createCoinbaseCountryRequest(inputs, options);
4364
- case 'mdl':
4365
- return this.createMdlKrRequest(circuit, inputs, options);
4366
4283
  case 'scoped':
4367
4284
  return this.createScopedRequest(circuit, inputs, options);
4368
4285
  }
4369
4286
  }
4370
- /**
4371
- * @internal
4372
- * Creates a Korea Mobile ID (mDL) proof request for any of the three
4373
- * mDL predicate circuits (ownership / age / region).
4374
- *
4375
- * The mobile app collects the license data on-device; the dapp only
4376
- * supplies the predicate parameters validated here. No wallet signature
4377
- * is required for mDL circuits.
4378
- *
4379
- * @param circuit - 'mdl_kr_ownership' | 'mdl_kr_age' | 'mdl_kr_region'
4380
- * @param inputs - Predicate inputs (see MdlKr*Inputs types)
4381
- * @param options - Request configuration options
4382
- *
4383
- * @throws Error if the predicate inputs fail validation
4384
- */
4385
- createMdlKrRequest(circuit, inputs, options = {}) {
4386
- const mdlError = validateMdlInputs(circuit, inputs);
4387
- if (mdlError) {
4388
- throw new Error(`${mdlError} (circuit: ${circuit})`);
4389
- }
4390
- const request = {
4391
- requestId: generateRequestId(),
4392
- circuit,
4393
- inputs,
4394
- message: options.message,
4395
- dappName: options.dappName,
4396
- dappIcon: options.dappIcon,
4397
- createdAt: Date.now(),
4398
- expiresAt: Date.now() + (options.expiresInMs || DEFAULT_REQUEST_EXPIRY_MS),
4399
- };
4400
- this.pendingRequests.set(request.requestId, request);
4401
- return request;
4402
- }
4403
4287
  // ============ Deep Link Generation ============
4404
4288
  /**
4405
4289
  * @internal
@@ -4739,9 +4623,50 @@ class ProofportSDK {
4739
4623
  * }
4740
4624
  * ```
4741
4625
  */
4742
- async verifyOnChain(circuit, proof, publicInputs, providerOrSigner) {
4626
+ async verifyOnChain(circuit, proof, publicInputs, providerOrSigner, options = {}) {
4743
4627
  const parsedProof = parseProofForOnChain(proof, publicInputs, publicInputs.length);
4744
- return verifyProofOnChain(circuit, parsedProof, providerOrSigner, this.verifierTarget(circuit));
4628
+ return verifyProofOnChain(circuit, parsedProof, providerOrSigner, this.verifierTarget(circuit), undefined, {
4629
+ request: options.request,
4630
+ signerVariant: options.signerVariant,
4631
+ });
4632
+ }
4633
+ /**
4634
+ * The request an `oidc_domain_attestation` response answers: the one the
4635
+ * caller passed, else the relay request this instance created under the
4636
+ * response's requestId. `{ error }` when there is none, or when the two
4637
+ * disagree -- an OIDC proof made with a request-bound nonce is only
4638
+ * meaningful against the request it was bound to, so a response this SDK
4639
+ * cannot place is refused rather than checked without it.
4640
+ */
4641
+ oidcRequestFor(response, explicit) {
4642
+ if (explicit !== undefined) {
4643
+ if (explicit === null || typeof explicit !== 'object' || explicit.requestId !== response.requestId) {
4644
+ return {
4645
+ error: `The request passed in (${JSON.stringify(explicit?.requestId)}) is not the one this response answers (${JSON.stringify(response.requestId)}).`,
4646
+ };
4647
+ }
4648
+ return { request: explicit };
4649
+ }
4650
+ const remembered = this.relayRequests.get(response.requestId);
4651
+ if (!remembered) {
4652
+ return {
4653
+ error: `No request ${JSON.stringify(response.requestId)} was created by this SDK instance, so the OIDC proof's ` +
4654
+ 'nonce cannot be checked against it. Pass { request: { scope, requestId } } -- the scope and relay ' +
4655
+ 'requestId of the request you created.',
4656
+ };
4657
+ }
4658
+ return { request: remembered };
4659
+ }
4660
+ /** @internal Remember a relay request's scope for verifyResponse*. */
4661
+ rememberRelayRequest(requestId, scope) {
4662
+ if (typeof requestId !== 'string' || typeof scope !== 'string')
4663
+ return;
4664
+ this.relayRequests.delete(requestId);
4665
+ this.relayRequests.set(requestId, { scope, requestId });
4666
+ while (this.relayRequests.size > ProofportSDK.MAX_REMEMBERED_RELAY_REQUESTS) {
4667
+ const oldest = this.relayRequests.keys().next().value;
4668
+ this.relayRequests.delete(oldest);
4669
+ }
4745
4670
  }
4746
4671
  /**
4747
4672
  * The verifier for a circuit: the one the dApp configured, otherwise the
@@ -4768,6 +4693,9 @@ class ProofportSDK {
4768
4693
  *
4769
4694
  * @param response - ProofResponse object from parseResponse()
4770
4695
  * @param providerOrSigner - ethers v6 Provider or Signer (defaults to base mainnet public RPC)
4696
+ * @param options.request - For `oidc_domain_attestation`: the `{ scope, requestId }`
4697
+ * the response answers. Not needed for a relay request this SDK instance
4698
+ * created; an OIDC response this instance cannot place is refused without it.
4771
4699
  *
4772
4700
  * @returns Promise resolving to verification result with valid boolean and optional error message
4773
4701
  *
@@ -4794,10 +4722,17 @@ class ProofportSDK {
4794
4722
  * });
4795
4723
  * ```
4796
4724
  */
4797
- async verifyResponseOnChain(response, providerOrSigner) {
4725
+ async verifyResponseOnChain(response, providerOrSigner, options = {}) {
4798
4726
  if (response.status !== 'completed' || !response.proof || !response.publicInputs) {
4799
4727
  return { valid: false, error: 'Invalid or incomplete response' };
4800
4728
  }
4729
+ let request;
4730
+ if (response.circuit === 'oidc_domain_attestation') {
4731
+ const found = this.oidcRequestFor(response, options.request);
4732
+ if ('error' in found)
4733
+ return { valid: false, error: found.error };
4734
+ request = found.request;
4735
+ }
4801
4736
  const parsedProof = parseProofForOnChain(response.proof, response.publicInputs, response.publicInputs.length);
4802
4737
  // The response's verifierAddress and chainId are a claim to check, never
4803
4738
  // the contract to call: whoever wrote the response would otherwise pick
@@ -4806,7 +4741,7 @@ class ProofportSDK {
4806
4741
  return verifyProofOnChain(response.circuit, parsedProof, providerOrSigner, this.verifierTarget(response.circuit), {
4807
4742
  verifierAddress: response.verifierAddress,
4808
4743
  chainId: response.chainId,
4809
- });
4744
+ }, { request, signerVariant: response.signerVariant });
4810
4745
  }
4811
4746
  /**
4812
4747
  * Verifies a proof response off-chain, against the circuit's verification key.
@@ -4836,7 +4771,14 @@ class ProofportSDK {
4836
4771
  if (response.status !== 'completed' || !response.proof || !response.publicInputs) {
4837
4772
  return { valid: false, error: 'Invalid or incomplete response' };
4838
4773
  }
4839
- return verifyProofOffChain(response.circuit, response.proof, response.publicInputs, options);
4774
+ let request;
4775
+ if (response.circuit === 'oidc_domain_attestation') {
4776
+ const found = this.oidcRequestFor(response, options?.request);
4777
+ if ('error' in found)
4778
+ return { valid: false, error: found.error };
4779
+ request = found.request;
4780
+ }
4781
+ return verifyProofOffChain(response.circuit, response.proof, response.publicInputs, { ...options, request, signerVariant: response.signerVariant });
4840
4782
  }
4841
4783
  // ============ Utility Methods ============
4842
4784
  /**
@@ -5194,14 +5136,6 @@ class ProofportSDK {
5194
5136
  if (needsSignature && !this.signer) {
5195
5137
  throw new Error('Signer not set. Call setSigner() first. Wallet signature is required for this circuit.');
5196
5138
  }
5197
- // mDL circuits: validate predicate inputs client-side before hitting the
5198
- // relay, so a bad ageThreshold / targetRegion fails fast with a clear error.
5199
- if (circuit === 'mdl_kr_ownership' || circuit === 'mdl_kr_age' || circuit === 'mdl_kr_region') {
5200
- const mdlError = validateMdlInputs(circuit, inputs);
5201
- if (mdlError) {
5202
- throw new Error(`${mdlError} (circuit: ${circuit})`);
5203
- }
5204
- }
5205
5139
  // The action, per the circuit's entry in CIRCUIT_ACTION_BINDING. Checked
5206
5140
  // here, before a challenge is burned, because the alternative is a refusal
5207
5141
  // from inside the mobile app -- a screen the dapp developer never sees,
@@ -5275,7 +5209,9 @@ class ProofportSDK {
5275
5209
  const error = await response.json().catch(() => ({ error: `HTTP ${response.status}` }));
5276
5210
  throw new Error(error.error || `Relay request failed: HTTP ${response.status}`);
5277
5211
  }
5278
- return await response.json();
5212
+ const created = await response.json();
5213
+ this.rememberRelayRequest(created.requestId, scope);
5214
+ return created;
5279
5215
  }
5280
5216
  /**
5281
5217
  * Polls the relay for proof result status.
@@ -5315,14 +5251,14 @@ class ProofportSDK {
5315
5251
  * @param requestId - The relay-issued request ID
5316
5252
  * @param options - Polling options
5317
5253
  * @param options.intervalMs - Polling interval in milliseconds (default: 2000)
5318
- * @param options.timeoutMs - Maximum polling time in milliseconds (default: 300000 = 5 min)
5254
+ * @param options.timeoutMs - Maximum polling time in milliseconds (default: 600000 = 10 min, `DEFAULT_RELAY_WAIT_MS`)
5319
5255
  * @param options.onStatusChange - Callback when status changes
5320
5256
  * @returns Promise resolving to final RelayProofResult
5321
5257
  * @throws Error if timeout or relay error
5322
5258
  */
5323
5259
  async waitForResult(requestId, options = {}) {
5324
5260
  const interval = options.intervalMs || 2000;
5325
- const timeout = options.timeoutMs || 300000;
5261
+ const timeout = options.timeoutMs || DEFAULT_RELAY_WAIT_MS;
5326
5262
  const startTime = Date.now();
5327
5263
  let lastStatus = '';
5328
5264
  while (Date.now() - startTime < timeout) {
@@ -5434,12 +5370,12 @@ class ProofportSDK {
5434
5370
  *
5435
5371
  * @param requestId - The relay-issued request ID
5436
5372
  * @param options - Configuration options
5437
- * @param options.timeoutMs - Maximum wait time in ms (default: 300000 = 5 min)
5373
+ * @param options.timeoutMs - Maximum wait time in ms (default: 600000 = 10 min, `DEFAULT_RELAY_WAIT_MS`)
5438
5374
  * @param options.onStatusChange - Callback for status updates
5439
5375
  * @returns Promise resolving to final RelayProofResult
5440
5376
  */
5441
5377
  async waitForProof(requestId, options = {}) {
5442
- const timeout = options.timeoutMs || 300000;
5378
+ const timeout = options.timeoutMs || DEFAULT_RELAY_WAIT_MS;
5443
5379
  // Try Socket.IO first
5444
5380
  if (this.relayUrl) {
5445
5381
  try {
@@ -5558,6 +5494,8 @@ class ProofportSDK {
5558
5494
  return extractDomainFromPublicInputs(publicInputs, circuit);
5559
5495
  }
5560
5496
  }
5497
+ /** Bound on {@link relayRequests}; a dApp that verifies later can pass the request explicitly. */
5498
+ ProofportSDK.MAX_REMEMBERED_RELAY_REQUESTS = 1000;
5561
5499
  /**
5562
5500
  * How each circuit's request is built and checked. Keyed by circuit id and
5563
5501
  * exhaustive over it, so a new circuit is a compile error here instead of
@@ -5569,10 +5507,7 @@ ProofportSDK.REQUEST_BUILDERS = {
5569
5507
  oidc_domain_attestation: 'scoped',
5570
5508
  giwa_attestation: 'scoped',
5571
5509
  dojang_playground: 'scoped',
5572
- mdl_kr_ownership: 'mdl',
5573
- mdl_kr_age: 'mdl',
5574
- mdl_kr_region: 'mdl',
5575
5510
  };
5576
5511
 
5577
- export { ProofportSDK, RELAY_TERMINAL_STATUSES, ProofportSDK as default, isTerminalRelayStatus, verifyProofOffChain };
5512
+ export { DEFAULT_RELAY_WAIT_MS, ProofportSDK, RELAY_TERMINAL_STATUSES, ProofportSDK as default, isTerminalRelayStatus, verifyProofOffChain };
5578
5513
  //# sourceMappingURL=index.esm.js.map