@zkproofport-app/sdk 0.5.0-beta.5 → 0.5.0-beta.7

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.js CHANGED
@@ -109,11 +109,15 @@ const CIRCUIT_METADATA = {
109
109
  },
110
110
  coinbase_country_attestation: {
111
111
  name: 'Coinbase Country',
112
- description: 'Prove Coinbase country verification',
113
- // 32 + 32 + (10 x 2) + 1 + 1 + 32 + 32 = 150, counted off
114
- // coinbase-country-attestation/src/main.nr. Read 14 until 2026-09-04.
115
- publicInputsCount: 150,
116
- publicInputNames: ['signal_hash', 'signer_list_merkle_root', 'country_list', 'country_list_length', 'is_included'],
112
+ description: 'Prove Coinbase country verification, optionally authorizing one EIP-712 action',
113
+ // Coinbase Country v2 (coinbase-country-attestation-v2/src/main.nr):
114
+ // signal_hash 0-31, domain_separator 32-63, action_hash 64-95,
115
+ // signer_list_merkle_root 96-127, country_list 128-147 (10 x 2 bytes),
116
+ // country_list_length 148, is_included 149, scope 150-181,
117
+ // nullifier 182-213, attested_at 214 = 215 fields. v1's 150 fields are
118
+ // refused on every verify path.
119
+ publicInputsCount: 215,
120
+ publicInputNames: ['signal_hash', 'domain_separator', 'action_hash', 'signer_list_merkle_root', 'country_list', 'country_list_length', 'is_included', 'scope', 'nullifier', 'attested_at'],
117
121
  },
118
122
  oidc_domain_attestation: {
119
123
  name: 'OIDC Domain',
@@ -642,11 +646,9 @@ function validateProofRequest(request) {
642
646
  if (inputs.userAddress && !/^0x[a-fA-F0-9]{40}$/.test(inputs.userAddress)) {
643
647
  return { valid: false, error: 'Invalid userAddress format' };
644
648
  }
645
- if (!inputs.countryList || !Array.isArray(inputs.countryList) || inputs.countryList.length === 0) {
646
- return { valid: false, error: 'countryList is required and must be a non-empty array' };
647
- }
648
- if (!inputs.countryList.every(c => typeof c === 'string')) {
649
- return { valid: false, error: 'countryList must contain only strings' };
649
+ const listError = core.countryListError(inputs.countryList);
650
+ if (listError) {
651
+ return { valid: false, error: listError };
650
652
  }
651
653
  if (typeof inputs.isIncluded !== 'boolean') {
652
654
  return { valid: false, error: 'isIncluded is required and must be a boolean' };
@@ -1073,7 +1075,7 @@ async function verifyProofOffChain(circuit, proof, publicInputs, options = {}) {
1073
1075
  // their trust anchor as a public input the prover chooses. For OIDC that is
1074
1076
  // the RSA key -- see core's oidcProviderKeys. Checked here so a dApp that
1075
1077
  // verifies off-chain gets the same answer as one that verifies on-chain.
1076
- const issuer = await core.checkProofIssuer(circuit, publicInputs, { request: options.request });
1078
+ const issuer = await core.checkProofIssuer(circuit, publicInputs, { request: options.request, countryCondition: options.countryCondition });
1077
1079
  if (!issuer.ok) {
1078
1080
  return { valid: false, error: issuer.error };
1079
1081
  }
@@ -1233,6 +1235,12 @@ class ProofportSDK {
1233
1235
  * {@link ProofportSDK.MAX_REMEMBERED_RELAY_REQUESTS}.
1234
1236
  */
1235
1237
  this.relayRequests = new Map();
1238
+ /**
1239
+ * requestId -> the country list and direction of the Coinbase Country relay
1240
+ * requests this instance created, so verifyResponse* can hold the proof to
1241
+ * them. Bounded like {@link relayRequests}.
1242
+ */
1243
+ this.countryRequests = new Map();
1236
1244
  this.signer = null;
1237
1245
  this.socket = null;
1238
1246
  /**
@@ -1364,6 +1372,15 @@ class ProofportSDK {
1364
1372
  if (typeof inputs.isIncluded !== 'boolean') {
1365
1373
  throw new Error('isIncluded is required for coinbase_country_attestation circuit');
1366
1374
  }
1375
+ const listError = core.countryListError(inputs.countryList);
1376
+ if (listError)
1377
+ throw new Error(`${listError} (circuit: coinbase_country_attestation)`);
1378
+ // Coinbase Country v2 binds an action or none, like Coinbase KYC v2.
1379
+ if (inputs.action !== undefined) {
1380
+ const actionError = typedAction.validateTypedAction(inputs.action);
1381
+ if (actionError)
1382
+ throw new Error(`${actionError} (circuit: coinbase_country_attestation)`);
1383
+ }
1367
1384
  const request = {
1368
1385
  requestId: generateRequestId(),
1369
1386
  circuit: 'coinbase_country_attestation',
@@ -1764,6 +1781,7 @@ class ProofportSDK {
1764
1781
  return core.verifyProofOnChain(circuit, parsedProof, providerOrSigner, this.verifierTarget(circuit), undefined, {
1765
1782
  request: options.request,
1766
1783
  signerVariant: options.signerVariant,
1784
+ countryCondition: options.countryCondition,
1767
1785
  });
1768
1786
  }
1769
1787
  /**
@@ -1804,6 +1822,43 @@ class ProofportSDK {
1804
1822
  this.relayRequests.delete(oldest);
1805
1823
  }
1806
1824
  }
1825
+ /**
1826
+ * The country list and direction a `coinbase_country_attestation` response
1827
+ * must have been proved against: the one the caller passed, else the relay
1828
+ * request this instance created under the response's requestId, else the
1829
+ * deep-link request still pending under it. `{ error }` when there is none:
1830
+ * the proof's list is the prover's choice, so a response this SDK cannot
1831
+ * place is refused rather than accepted for whatever list it carries.
1832
+ */
1833
+ countryConditionFor(response, explicit) {
1834
+ if (explicit !== undefined)
1835
+ return { condition: explicit };
1836
+ const remembered = this.countryRequests.get(response.requestId);
1837
+ if (remembered)
1838
+ return { condition: remembered };
1839
+ const pending = this.pendingRequests.get(response.requestId);
1840
+ const inputs = pending?.circuit === 'coinbase_country_attestation' ? pending.inputs : undefined;
1841
+ if (inputs && Array.isArray(inputs.countryList) && typeof inputs.isIncluded === 'boolean') {
1842
+ return { condition: { countryList: [...inputs.countryList], isIncluded: inputs.isIncluded } };
1843
+ }
1844
+ return {
1845
+ error: `No Coinbase Country request ${JSON.stringify(response.requestId)} was created by this SDK instance, so the ` +
1846
+ "proof's country list cannot be checked against what was asked. Pass { countryCondition: { countryList, " +
1847
+ 'isIncluded } } -- the list and direction of the request you created.',
1848
+ };
1849
+ }
1850
+ /** @internal Remember a Coinbase Country relay request's list and direction for verifyResponse*. */
1851
+ rememberCountryRequest(requestId, inputs) {
1852
+ const i = inputs;
1853
+ if (typeof requestId !== 'string' || !i || !Array.isArray(i.countryList) || typeof i.isIncluded !== 'boolean')
1854
+ return;
1855
+ this.countryRequests.delete(requestId);
1856
+ this.countryRequests.set(requestId, { countryList: [...i.countryList], isIncluded: i.isIncluded });
1857
+ while (this.countryRequests.size > ProofportSDK.MAX_REMEMBERED_RELAY_REQUESTS) {
1858
+ const oldest = this.countryRequests.keys().next().value;
1859
+ this.countryRequests.delete(oldest);
1860
+ }
1861
+ }
1807
1862
  /**
1808
1863
  * The verifier for a circuit: the one the dApp configured, otherwise the
1809
1864
  * pinned deployment on the chain this SDK's environment verifies that
@@ -1832,6 +1887,10 @@ class ProofportSDK {
1832
1887
  * @param options.request - For `oidc_domain_attestation`: the `{ scope, requestId }`
1833
1888
  * the response answers. Not needed for a relay request this SDK instance
1834
1889
  * created; an OIDC response this instance cannot place is refused without it.
1890
+ * @param options.countryCondition - For `coinbase_country_attestation`: the
1891
+ * `{ countryList, isIncluded }` the request asked for. Not needed for a
1892
+ * request this SDK instance created (relay or deep link); a Country response
1893
+ * this instance cannot place is refused without it.
1835
1894
  *
1836
1895
  * @returns Promise resolving to verification result with valid boolean and optional error message
1837
1896
  *
@@ -1869,6 +1928,13 @@ class ProofportSDK {
1869
1928
  return { valid: false, error: found.error };
1870
1929
  request = found.request;
1871
1930
  }
1931
+ let countryCondition;
1932
+ if (response.circuit === 'coinbase_country_attestation') {
1933
+ const found = this.countryConditionFor(response, options.countryCondition);
1934
+ if ('error' in found)
1935
+ return { valid: false, error: found.error };
1936
+ countryCondition = found.condition;
1937
+ }
1872
1938
  const parsedProof = core.parseProofForOnChain(response.proof, response.publicInputs, response.publicInputs.length);
1873
1939
  // The response's verifierAddress and chainId are a claim to check, never
1874
1940
  // the contract to call: whoever wrote the response would otherwise pick
@@ -1877,7 +1943,7 @@ class ProofportSDK {
1877
1943
  return core.verifyProofOnChain(response.circuit, parsedProof, providerOrSigner, this.verifierTarget(response.circuit), {
1878
1944
  verifierAddress: response.verifierAddress,
1879
1945
  chainId: response.chainId,
1880
- }, { request, signerVariant: response.signerVariant });
1946
+ }, { request, signerVariant: response.signerVariant, countryCondition });
1881
1947
  }
1882
1948
  /**
1883
1949
  * Verifies a proof response off-chain, against the circuit's verification key.
@@ -1914,7 +1980,14 @@ class ProofportSDK {
1914
1980
  return { valid: false, error: found.error };
1915
1981
  request = found.request;
1916
1982
  }
1917
- return verifyProofOffChain(response.circuit, response.proof, response.publicInputs, { ...options, request, signerVariant: response.signerVariant });
1983
+ let countryCondition;
1984
+ if (response.circuit === 'coinbase_country_attestation') {
1985
+ const found = this.countryConditionFor(response, options?.countryCondition);
1986
+ if ('error' in found)
1987
+ return { valid: false, error: found.error };
1988
+ countryCondition = found.condition;
1989
+ }
1990
+ return verifyProofOffChain(response.circuit, response.proof, response.publicInputs, { ...options, request, countryCondition, signerVariant: response.signerVariant });
1918
1991
  }
1919
1992
  // ============ Utility Methods ============
1920
1993
  /**
@@ -2292,6 +2365,16 @@ class ProofportSDK {
2292
2365
  }
2293
2366
  }
2294
2367
  }
2368
+ // A Coinbase Country list the circuit would refuse is refused here, before a
2369
+ // challenge is burned, as createProofRequest does.
2370
+ if (circuit === 'coinbase_country_attestation') {
2371
+ const c = inputs;
2372
+ const listError = core.countryListError(c.countryList);
2373
+ if (listError)
2374
+ throw new Error(`${listError} (circuit: ${circuit})`);
2375
+ if (typeof c.isIncluded !== 'boolean')
2376
+ throw new Error('isIncluded is required for coinbase_country_attestation circuit');
2377
+ }
2295
2378
  // Return target: validated before we burn a challenge, so a typo surfaces
2296
2379
  // as a local error instead of a relay 400 on the second round trip.
2297
2380
  if (options.returnScheme !== undefined) {
@@ -2347,6 +2430,8 @@ class ProofportSDK {
2347
2430
  }
2348
2431
  const created = await response.json();
2349
2432
  this.rememberRelayRequest(created.requestId, scope);
2433
+ if (circuit === 'coinbase_country_attestation')
2434
+ this.rememberCountryRequest(created.requestId, inputs);
2350
2435
  return created;
2351
2436
  }
2352
2437
  /**
@@ -2649,6 +2734,10 @@ Object.defineProperty(exports, "COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT", {
2649
2734
  enumerable: true,
2650
2735
  get: function () { return core.COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT; }
2651
2736
  });
2737
+ Object.defineProperty(exports, "COINBASE_COUNTRY_PUBLIC_INPUT_COUNT", {
2738
+ enumerable: true,
2739
+ get: function () { return core.COINBASE_COUNTRY_PUBLIC_INPUT_COUNT; }
2740
+ });
2652
2741
  Object.defineProperty(exports, "COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT", {
2653
2742
  enumerable: true,
2654
2743
  get: function () { return core.COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT; }