@zkproofport-app/sdk 0.5.0-beta.4 → 0.5.0-beta.6
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/README.md +59 -28
- package/dist/ProofportSDK.d.ts +24 -1
- package/dist/constants.d.ts +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +109 -23
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +111 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +109 -23
- package/dist/index.mjs.map +1 -1
- package/dist/offChainVerifier.d.ts +8 -1
- package/dist/types.d.ts +7 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -177,6 +177,7 @@ Proves that a user has completed Coinbase KYC identity verification without reve
|
|
|
177
177
|
| `scope` | `string` | Yes | Application-specific identifier (e.g., your domain). Ensures proof uniqueness per app. |
|
|
178
178
|
| `userAddress` | `string` | No | Address to prove the attestation for. Omit it and the mobile app asks the user to connect a wallet. |
|
|
179
179
|
| `rawTransaction` | `string` | No | Pre-fetched attestation transaction data. Omit it and the app fetches the attestation itself. |
|
|
180
|
+
| `action` | `TypedAction` | No | One EIP-712 action for the wallet to sign and the proof to carry. Without one the wallet signs the request's signal hash. See [Which circuits take an action](#which-circuits-take-an-action). |
|
|
180
181
|
|
|
181
182
|
```typescript
|
|
182
183
|
const relay = await sdk.createRelayRequest('coinbase_attestation', {
|
|
@@ -184,15 +185,19 @@ const relay = await sdk.createRelayRequest('coinbase_attestation', {
|
|
|
184
185
|
});
|
|
185
186
|
```
|
|
186
187
|
|
|
188
|
+
Its nullifier is `keccak(keccak(wallet ++ keccak("coinbase_attestation")) ++ scope)`:
|
|
189
|
+
one wallet in one scope has exactly one nullifier.
|
|
190
|
+
|
|
187
191
|
### `coinbase_country_attestation`
|
|
188
192
|
|
|
189
|
-
Proves a user's country based on Coinbase verification, supporting inclusion and exclusion checks, without revealing the actual country. Requires a wallet signer.
|
|
193
|
+
Proves a user's country based on Coinbase verification, supporting inclusion and exclusion checks, without revealing the actual country. Requires a wallet signer. Like Coinbase KYC, it can authorize one EIP-712 action, and the attestation is confirmed unrevoked — for the very country being proved — right before proving (`attested_at`, checked against your clock).
|
|
190
194
|
|
|
191
195
|
| Field | Type | Required | Description |
|
|
192
196
|
|-------|------|----------|-------------|
|
|
193
197
|
| `scope` | `string` | Yes | Application-specific identifier |
|
|
194
|
-
| `countryList` | `string[]` | Yes | ISO 3166-1 alpha-2 country codes (e.g., `['US', 'KR']`) |
|
|
198
|
+
| `countryList` | `string[]` | Yes | 1 to 10 upper-case ISO 3166-1 alpha-2 country codes (e.g., `['US', 'KR']`); anything else is refused when the request is created |
|
|
195
199
|
| `isIncluded` | `boolean` | Yes | `true` = prove user IS from listed countries; `false` = prove user is NOT |
|
|
200
|
+
| `action` | `TypedAction` | No | An EIP-712 action the wallet signs instead of the request's signal hash |
|
|
196
201
|
| `userAddress` | `string` | No | Address to prove the attestation for (optional, as above) |
|
|
197
202
|
| `rawTransaction` | `string` | No | Pre-fetched attestation transaction data (optional, as above) |
|
|
198
203
|
|
|
@@ -204,6 +209,24 @@ const relay = await sdk.createRelayRequest('coinbase_country_attestation', {
|
|
|
204
209
|
});
|
|
205
210
|
```
|
|
206
211
|
|
|
212
|
+
Its nullifier is `keccak(keccak(wallet ++ keccak("coinbase_country_attestation")) ++ scope)`.
|
|
213
|
+
|
|
214
|
+
The list and the direction are public inputs the prover chooses; the wallet's
|
|
215
|
+
signature does not cover them. `verifyResponseOnChain` and
|
|
216
|
+
`verifyResponseOffChain` therefore compare them with the request this SDK
|
|
217
|
+
instance created (relay or deep link) and refuse a proof made against any other
|
|
218
|
+
list. A response the instance cannot place is refused; pass the request's
|
|
219
|
+
condition explicitly in that case:
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
await sdk.verifyResponseOnChain(response, provider, {
|
|
223
|
+
countryCondition: { countryList: ['US', 'KR'], isIncluded: true },
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`checkCountryListMatches(publicInputs, { countryList, isIncluded })` is the same
|
|
228
|
+
check on its own (codes compared as a set, order does not matter).
|
|
229
|
+
|
|
207
230
|
### `oidc_domain_attestation`
|
|
208
231
|
|
|
209
232
|
Prove email domain affiliation via OIDC Sign-In. The mobile app handles authentication and proof generation entirely on-device — the user's email is never revealed. No wallet signer is required.
|
|
@@ -263,7 +286,7 @@ const { deepLink } = await sdk.createRelayRequest(
|
|
|
263
286
|
**An action is optional here.** Send one
|
|
264
287
|
and the wallet signs EIP-712 typed data, so the person reads named fields and
|
|
265
288
|
the proof carries what they approved. Send none and the wallet signs the
|
|
266
|
-
request's signal hash
|
|
289
|
+
request's signal hash. Coinbase KYC branches the same way. The circuit itself
|
|
267
290
|
branches, and it refuses a request that tries to be both: with an action the
|
|
268
291
|
signal hash must be empty, without one the EIP-712 pair must be.
|
|
269
292
|
|
|
@@ -284,8 +307,8 @@ await sdk.createRelayRequest(
|
|
|
284
307
|
```
|
|
285
308
|
|
|
286
309
|
Its public inputs are `signal_hash`, `domain_separator`, `action_hash`,
|
|
287
|
-
`signer_list_merkle_root`, `scope`, `nullifier
|
|
288
|
-
|
|
310
|
+
`signer_list_merkle_root`, `scope`, `nullifier`: Coinbase KYC's order without
|
|
311
|
+
its final `attested_at`.
|
|
289
312
|
|
|
290
313
|
One more difference worth knowing if you count people: its nullifier is
|
|
291
314
|
`keccak(keccak(wallet ++ keccak("giwa_attestation")) ++ scope)`. The wallet and
|
|
@@ -329,14 +352,16 @@ circuit names are special:
|
|
|
329
352
|
|
|
330
353
|
| What the circuit does with an action | Circuits |
|
|
331
354
|
|---|---|
|
|
332
|
-
| Signs it when present, signs the signal hash when not | `giwa_attestation`, `dojang_playground` |
|
|
333
|
-
| Has no action inputs — sending one is refused, not ignored |
|
|
355
|
+
| Signs it when present, signs the signal hash when not | `coinbase_attestation`, `coinbase_country_attestation`, `giwa_attestation`, `dojang_playground` |
|
|
356
|
+
| Has no action inputs — sending one is refused, not ignored | OIDC domain |
|
|
334
357
|
|
|
335
358
|
```typescript
|
|
336
359
|
import { circuitActionBinding } from '@zkproofport-app/sdk';
|
|
337
360
|
|
|
338
361
|
circuitActionBinding('giwa_attestation'); // 'optional'
|
|
339
|
-
circuitActionBinding('coinbase_attestation'); // '
|
|
362
|
+
circuitActionBinding('coinbase_attestation'); // 'optional'
|
|
363
|
+
circuitActionBinding('coinbase_country_attestation'); // 'optional'
|
|
364
|
+
circuitActionBinding('oidc_domain_attestation'); // 'none'
|
|
340
365
|
```
|
|
341
366
|
|
|
342
367
|
Sending an action to a circuit that cannot prove one is an error rather than a
|
|
@@ -615,7 +640,7 @@ When you are done, `sdk.disconnect()` closes any open socket.
|
|
|
615
640
|
status: 'completed',
|
|
616
641
|
circuit: 'coinbase_attestation',
|
|
617
642
|
proof: '0x1f8b0a…', // proof bytes, opaque
|
|
618
|
-
publicInputs: ['0x00…2f', '0x00…a1', …], // ordered —
|
|
643
|
+
publicInputs: ['0x00…2f', '0x00…a1', …], // ordered — 193 entries for this circuit
|
|
619
644
|
verifierAddress: '0x…', // the verifier the app reports (checked, not trusted)
|
|
620
645
|
chainId: …, // the network the app reports (checked, not trusted)
|
|
621
646
|
deepLink: 'zkproofport://proof-request?data=…',
|
|
@@ -729,7 +754,7 @@ For OIDC domain proofs the SDK first checks what the proof says about the sign-i
|
|
|
729
754
|
|
|
730
755
|
The SDK also checks that the proof was made with a signing key Google or Microsoft actually publishes. That key is part of the proof's public inputs, so a proof made with a self-generated key would otherwise pass the contract. The SDK fetches the provider's published keys, caches them as the provider's own cache headers allow, and returns `{ valid: false, error }` when the key is not among them or the keys cannot be fetched. Every verify method does this; there is nothing to call separately.
|
|
731
756
|
|
|
732
|
-
For Coinbase KYC, Coinbase country and GIWA proofs the SDK checks that the attestation's signer belongs to the published signer set: the proof carries the root of the signer list it was made against, and a proof made against any other list is refused with `{ valid: false, error }`. This too runs in every verify method.
|
|
757
|
+
For Coinbase KYC, Coinbase country and GIWA proofs the SDK checks that the attestation's signer belongs to the published signer set: the proof carries the root of the signer list it was made against, and a proof made against any other list is refused with `{ valid: false, error }`. A Coinbase KYC proof also names the time its attestation was confirmed unrevoked; it must be within the last 15 minutes (and not more than 5 minutes ahead of your clock), so verify a Coinbase KYC proof when you receive it rather than re-verifying a stored one later. This too runs in every verify method.
|
|
733
758
|
|
|
734
759
|
The one limit: a proof made with a key the provider has already withdrawn, which this process never saw while it was published, cannot be checked and is refused. Keys seen while published stay acceptable for 1 hour after they were last listed.
|
|
735
760
|
|
|
@@ -885,7 +910,7 @@ const domain = extractDomainFromPublicInputs(publicInputs, 'oidc_domain_attestat
|
|
|
885
910
|
// domain: 'example.com', or null if the circuit doesn't match or inputs are insufficient
|
|
886
911
|
```
|
|
887
912
|
|
|
888
|
-
|
|
913
|
+
The circuit argument is required: the layout differs per circuit, and an omitted or unknown circuit id throws an error naming the known ones.
|
|
889
914
|
|
|
890
915
|
## Complete Example
|
|
891
916
|
|
|
@@ -1011,7 +1036,7 @@ import type {
|
|
|
1011
1036
|
| `CircuitType` | Alias of `CircuitId`. Kept as the name used throughout `ProofRequest`, `ProofResponse` and the SDK methods |
|
|
1012
1037
|
| `ProofRequestStatus` | `'pending' \| 'completed' \| 'error' \| 'cancelled'` — the status on a `ProofResponse` |
|
|
1013
1038
|
| `CoinbaseKycInputs` | Inputs for `coinbase_attestation`: `{ scope, userAddress?, rawTransaction? }` |
|
|
1014
|
-
| `CoinbaseCountryInputs` | Inputs for `coinbase_country_attestation`: `{ scope, countryList, isIncluded, userAddress?, rawTransaction? }` |
|
|
1039
|
+
| `CoinbaseCountryInputs` | Inputs for `coinbase_country_attestation`: `{ scope, countryList, isIncluded, action?, userAddress?, rawTransaction? }` |
|
|
1015
1040
|
| `OidcDomainInputs` | Inputs for `oidc_domain_attestation`: `{ domain, scope, provider? }` |
|
|
1016
1041
|
| `ActionBoundInputs` | Inputs for the circuits that can bind an EIP-712 action: `{ scope, action?, userAddress?, rawTransaction? }`. Whether the action is required is the circuit's property, in `CIRCUIT_ACTION_BINDING` — optional for `giwa_attestation` and `dojang_playground` |
|
|
1017
1042
|
| `GiwaAttestationInputs` | Alias of `ActionBoundInputs`, for `giwa_attestation` |
|
|
@@ -1055,7 +1080,7 @@ import {
|
|
|
1055
1080
|
Two rules hold for every table below:
|
|
1056
1081
|
|
|
1057
1082
|
- **Every number is an index into `publicInputs`, and `_END` is inclusive.** Slice with `slice(START, END + 1)`.
|
|
1058
|
-
- **One entry is one field element, not one 32-byte value.** A `bytes32` such as a scope or a nullifier is spread over 32 consecutive entries, each holding a single byte in its low byte: `publicInputs[
|
|
1083
|
+
- **One entry is one field element, not one 32-byte value.** A `bytes32` such as a scope or a nullifier is spread over 32 consecutive entries, each holding a single byte in its low byte: `publicInputs[128] === '0x00…2f'` is the byte `0x2f`. Rebuild it by concatenating those 32 low bytes. The one exception is the OIDC RSA modulus, whose 18 entries each hold a 128-bit limb.
|
|
1059
1084
|
|
|
1060
1085
|
```typescript
|
|
1061
1086
|
const L = COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT;
|
|
@@ -1067,28 +1092,34 @@ const toBytes32 = (fields: string[]): string =>
|
|
|
1067
1092
|
const signalHash = toBytes32(publicInputs.slice(L.SIGNAL_HASH_START, L.SIGNAL_HASH_END + 1));
|
|
1068
1093
|
```
|
|
1069
1094
|
|
|
1070
|
-
### `coinbase_attestation` —
|
|
1095
|
+
### `coinbase_attestation` — 193 entries
|
|
1071
1096
|
|
|
1072
1097
|
| Index | Constants | Circuit field | What it is |
|
|
1073
1098
|
|-------|-----------|---------------|------------|
|
|
1074
|
-
| 0–31 | `SIGNAL_HASH_START` / `_END` | `signal_hash` | `bytes32` anti-replay challenge for the request |
|
|
1075
|
-
| 32–63 | `
|
|
1076
|
-
| 64–95 | `
|
|
1077
|
-
| 96–127 | `
|
|
1099
|
+
| 0–31 | `SIGNAL_HASH_START` / `_END` | `signal_hash` | `bytes32` anti-replay challenge for the request; zero when the wallet signed an EIP-712 action |
|
|
1100
|
+
| 32–63 | `DOMAIN_SEPARATOR_START` / `_END` | `domain_separator` | EIP-712 domain separator of the signed action; zero without one |
|
|
1101
|
+
| 64–95 | `ACTION_HASH_START` / `_END` | `action_hash` | EIP-712 struct hash of the signed action; zero without one |
|
|
1102
|
+
| 96–127 | `MERKLE_ROOT_START` / `_END` | `signer_list_merkle_root` | `bytes32` root of the Coinbase attestation-signer list the proof was checked against |
|
|
1103
|
+
| 128–159 | `SCOPE_START` / `_END` | `scope` | `bytes32` — `keccak256` of your scope string. Compare it (Step 8) |
|
|
1104
|
+
| 160–191 | `NULLIFIER_START` / `_END` | `nullifier` | `bytes32`, unique per wallet and scope |
|
|
1105
|
+
| 192 | `ATTESTED_AT` | `attested_at` | Unix seconds at which the attestation was confirmed unrevoked; checked against your clock by every verify method |
|
|
1078
1106
|
|
|
1079
|
-
### `coinbase_country_attestation` —
|
|
1107
|
+
### `coinbase_country_attestation` — 215 entries
|
|
1080
1108
|
|
|
1081
1109
|
| Index | Constants | Circuit field | What it is |
|
|
1082
1110
|
|-------|-----------|---------------|------------|
|
|
1083
|
-
| 0–31 | `SIGNAL_HASH_START` / `_END` | `signal_hash` | `bytes32` anti-replay challenge |
|
|
1084
|
-
| 32–63 | `
|
|
1085
|
-
| 64–
|
|
1086
|
-
|
|
|
1087
|
-
|
|
|
1088
|
-
|
|
|
1089
|
-
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1111
|
+
| 0–31 | `SIGNAL_HASH_START` / `_END` | `signal_hash` | `bytes32` anti-replay challenge; zero when the wallet signed an EIP-712 action |
|
|
1112
|
+
| 32–63 | `DOMAIN_SEPARATOR_START` / `_END` | `domain_separator` | EIP-712 domain separator of the signed action; zero without one |
|
|
1113
|
+
| 64–95 | `ACTION_HASH_START` / `_END` | `action_hash` | EIP-712 struct hash of the signed action; zero without one |
|
|
1114
|
+
| 96–127 | `MERKLE_ROOT_START` / `_END` | `signer_list_merkle_root` | `bytes32` signer-list root |
|
|
1115
|
+
| 128–147 | `COUNTRY_LIST_START` / `_END` | `country_list` | 10 slots of 2 ASCII bytes, one byte per entry — entries 128–129 are the first code, 130–131 the second, and so on, in the order you sent them. Only the first `country_list_length` slots are meaningful; the rest are zero |
|
|
1116
|
+
| 148 | `COUNTRY_LIST_LENGTH` | `country_list_length` | How many of those 10 slots count |
|
|
1117
|
+
| 149 | `IS_INCLUDED` | `is_included` | `1` = the user **is** in the list, `0` = the user is **not** |
|
|
1118
|
+
| 150–181 | `SCOPE_START` / `_END` | `scope` | `bytes32` scope hash |
|
|
1119
|
+
| 182–213 | `NULLIFIER_START` / `_END` | `nullifier` | `bytes32`, unique per wallet and scope |
|
|
1120
|
+
| 214 | `ATTESTED_AT` | `attested_at` | Unix seconds at which the attestation was confirmed unrevoked; checked against your clock by every verify method |
|
|
1121
|
+
|
|
1122
|
+
The country list and the `is_included` flag are public inputs, which means the proof is only as strong as the list it was proved against. The `verifyResponse*` methods compare them with your request (above); if you verify through another path, call `checkCountryListMatches` or read indices 128–149 back yourself — a valid proof against a different list is still a valid proof.
|
|
1092
1123
|
|
|
1093
1124
|
### `oidc_domain_attestation` — 214 entries
|
|
1094
1125
|
|
package/dist/ProofportSDK.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { ProofRequest, ProofResponse, CircuitType, CircuitInputs, ProofportConfig, QRCodeOptions, ChallengeResponse, WalletSigner, RelayProofRequest, RelayProofResult } from './types';
|
|
5
5
|
import { type OffChainVerifyOptions } from './offChainVerifier';
|
|
6
|
-
import { type OidcRequestContext } from './verifier';
|
|
6
|
+
import { type CountryCondition, type OidcRequestContext } from './verifier';
|
|
7
7
|
import type { SDKEnvironment } from './types';
|
|
8
8
|
/**
|
|
9
9
|
* Main SDK class for interacting with the ZKProofport mobile app.
|
|
@@ -51,6 +51,12 @@ export declare class ProofportSDK {
|
|
|
51
51
|
private relayRequests;
|
|
52
52
|
/** Bound on {@link relayRequests}; a dApp that verifies later can pass the request explicitly. */
|
|
53
53
|
static readonly MAX_REMEMBERED_RELAY_REQUESTS = 1000;
|
|
54
|
+
/**
|
|
55
|
+
* requestId -> the country list and direction of the Coinbase Country relay
|
|
56
|
+
* requests this instance created, so verifyResponse* can hold the proof to
|
|
57
|
+
* them. Bounded like {@link relayRequests}.
|
|
58
|
+
*/
|
|
59
|
+
private countryRequests;
|
|
54
60
|
private signer;
|
|
55
61
|
private relayUrl;
|
|
56
62
|
private socket;
|
|
@@ -494,6 +500,7 @@ export declare class ProofportSDK {
|
|
|
494
500
|
verifyOnChain(circuit: CircuitType, proof: string, publicInputs: string[], providerOrSigner?: any, options?: {
|
|
495
501
|
request?: OidcRequestContext;
|
|
496
502
|
signerVariant?: string;
|
|
503
|
+
countryCondition?: CountryCondition;
|
|
497
504
|
}): Promise<{
|
|
498
505
|
valid: boolean;
|
|
499
506
|
error?: string;
|
|
@@ -509,6 +516,17 @@ export declare class ProofportSDK {
|
|
|
509
516
|
private oidcRequestFor;
|
|
510
517
|
/** @internal Remember a relay request's scope for verifyResponse*. */
|
|
511
518
|
private rememberRelayRequest;
|
|
519
|
+
/**
|
|
520
|
+
* The country list and direction a `coinbase_country_attestation` response
|
|
521
|
+
* must have been proved against: the one the caller passed, else the relay
|
|
522
|
+
* request this instance created under the response's requestId, else the
|
|
523
|
+
* deep-link request still pending under it. `{ error }` when there is none:
|
|
524
|
+
* the proof's list is the prover's choice, so a response this SDK cannot
|
|
525
|
+
* place is refused rather than accepted for whatever list it carries.
|
|
526
|
+
*/
|
|
527
|
+
private countryConditionFor;
|
|
528
|
+
/** @internal Remember a Coinbase Country relay request's list and direction for verifyResponse*. */
|
|
529
|
+
private rememberCountryRequest;
|
|
512
530
|
/**
|
|
513
531
|
* The verifier for a circuit: the one the dApp configured, otherwise the
|
|
514
532
|
* pinned deployment on the chain this SDK's environment verifies that
|
|
@@ -527,6 +545,10 @@ export declare class ProofportSDK {
|
|
|
527
545
|
* @param options.request - For `oidc_domain_attestation`: the `{ scope, requestId }`
|
|
528
546
|
* the response answers. Not needed for a relay request this SDK instance
|
|
529
547
|
* created; an OIDC response this instance cannot place is refused without it.
|
|
548
|
+
* @param options.countryCondition - For `coinbase_country_attestation`: the
|
|
549
|
+
* `{ countryList, isIncluded }` the request asked for. Not needed for a
|
|
550
|
+
* request this SDK instance created (relay or deep link); a Country response
|
|
551
|
+
* this instance cannot place is refused without it.
|
|
530
552
|
*
|
|
531
553
|
* @returns Promise resolving to verification result with valid boolean and optional error message
|
|
532
554
|
*
|
|
@@ -555,6 +577,7 @@ export declare class ProofportSDK {
|
|
|
555
577
|
*/
|
|
556
578
|
verifyResponseOnChain(response: ProofResponse, providerOrSigner?: any, options?: {
|
|
557
579
|
request?: OidcRequestContext;
|
|
580
|
+
countryCondition?: CountryCondition;
|
|
558
581
|
}): Promise<{
|
|
559
582
|
valid: boolean;
|
|
560
583
|
error?: string;
|
package/dist/constants.d.ts
CHANGED
|
@@ -83,7 +83,7 @@ export declare const DEEP_LINK_HOSTS: {
|
|
|
83
83
|
* ```typescript
|
|
84
84
|
* const metadata = CIRCUIT_METADATA['coinbase_attestation'];
|
|
85
85
|
* console.log(metadata.name); // "Coinbase KYC"
|
|
86
|
-
* console.log(metadata.publicInputsCount); //
|
|
86
|
+
* console.log(metadata.publicInputsCount); // 193
|
|
87
87
|
* ```
|
|
88
88
|
*/
|
|
89
89
|
export declare const CIRCUIT_METADATA: Record<CircuitType, {
|
|
@@ -123,4 +123,4 @@ export declare const DEFAULT_REQUEST_EXPIRY_MS: number;
|
|
|
123
123
|
* ```
|
|
124
124
|
*/
|
|
125
125
|
export declare const MAX_QR_DATA_SIZE = 2953;
|
|
126
|
-
export { VERIFIER_ABI, RPC_ENDPOINTS, COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT, OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT, } from '@zkproofport-app/core';
|
|
126
|
+
export { VERIFIER_ABI, RPC_ENDPOINTS, COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_COUNT, OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT, } from '@zkproofport-app/core';
|
package/dist/index.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export { PINNED_VERIFIERS, PINNED_VARIANT_VERIFIERS, getPinnedVerifier, getPinne
|
|
|
34
34
|
export type { PinnedVerifier } from './verifier';
|
|
35
35
|
export { checkOidcPolicy, deriveOidcNonceBinding, oidcAudienceHash, OIDC_ALLOWED_AUDIENCES, OIDC_NONCE_DOMAIN, OIDC_NOW_MAX_AGE_SECONDS, OIDC_NOW_MAX_FUTURE_SECONDS, OIDC_V1_PUBLIC_INPUT_COUNT, OIDC_V2_PUBLIC_INPUT_COUNT, } from './verifier';
|
|
36
36
|
export type { OidcRequestContext, OidcAudience, OidcNonceMode } from './verifier';
|
|
37
|
-
export { COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT, OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT, } from './constants';
|
|
37
|
+
export { COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_COUNT, OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT, } from './constants';
|
|
38
38
|
export type { CircuitId, CircuitSupportStatus, SignerVariant, CircuitPackage } from './circuits';
|
|
39
39
|
export { CIRCUIT_ACTION_BINDING, circuitActionBinding, } from './circuits';
|
|
40
40
|
export type { CircuitActionBinding } from './circuits';
|
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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, 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';
|
|
1
|
+
import { countryListError, checkProofIssuer, parseProofForOnChain, verifyProofOnChain, getPinnedVerifier, extractScopeFromPublicInputs, extractNullifierFromPublicInputs, extractDomainFromPublicInputs } from '@zkproofport-app/core';
|
|
2
|
+
export { COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_COUNT, 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
3
|
import { isCircuitId, ALL_CIRCUIT_IDS, circuitActionBinding, resolveSignerVariant, getCircuitPackage, CIRCUIT_NEEDS_WALLET_SIGNATURE } from '@zkproofport-app/core/circuits';
|
|
4
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';
|
|
@@ -90,27 +90,32 @@ const DEEP_LINK_HOSTS = {
|
|
|
90
90
|
* ```typescript
|
|
91
91
|
* const metadata = CIRCUIT_METADATA['coinbase_attestation'];
|
|
92
92
|
* console.log(metadata.name); // "Coinbase KYC"
|
|
93
|
-
* console.log(metadata.publicInputsCount); //
|
|
93
|
+
* console.log(metadata.publicInputsCount); // 193
|
|
94
94
|
* ```
|
|
95
95
|
*/
|
|
96
96
|
const CIRCUIT_METADATA = {
|
|
97
97
|
coinbase_attestation: {
|
|
98
98
|
name: 'Coinbase KYC',
|
|
99
|
-
description: 'Prove Coinbase identity verification',
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
description: 'Prove Coinbase identity verification, optionally authorizing one EIP-712 action',
|
|
100
|
+
// Coinbase KYC v2 (coinbase-attestation-v2/src/main.nr): GIWA's six
|
|
101
|
+
// `pub [u8; 32]` in its order, then `attested_at: pub u64` = 193 fields.
|
|
102
|
+
// signal_hash 0-31, domain_separator 32-63, action_hash 64-95,
|
|
103
|
+
// signer_list_merkle_root 96-127, scope 128-159, nullifier 160-191,
|
|
104
|
+
// attested_at 192. v1's 128 fields are refused on every verify path.
|
|
105
|
+
publicInputsCount: 193,
|
|
106
|
+
publicInputNames: ['signal_hash', 'domain_separator', 'action_hash', 'signer_list_merkle_root', 'scope', 'nullifier', 'attested_at'],
|
|
106
107
|
},
|
|
107
108
|
coinbase_country_attestation: {
|
|
108
109
|
name: 'Coinbase Country',
|
|
109
|
-
description: 'Prove Coinbase country verification',
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
description: 'Prove Coinbase country verification, optionally authorizing one EIP-712 action',
|
|
111
|
+
// Coinbase Country v2 (coinbase-country-attestation-v2/src/main.nr):
|
|
112
|
+
// signal_hash 0-31, domain_separator 32-63, action_hash 64-95,
|
|
113
|
+
// signer_list_merkle_root 96-127, country_list 128-147 (10 x 2 bytes),
|
|
114
|
+
// country_list_length 148, is_included 149, scope 150-181,
|
|
115
|
+
// nullifier 182-213, attested_at 214 = 215 fields. v1's 150 fields are
|
|
116
|
+
// refused on every verify path.
|
|
117
|
+
publicInputsCount: 215,
|
|
118
|
+
publicInputNames: ['signal_hash', 'domain_separator', 'action_hash', 'signer_list_merkle_root', 'country_list', 'country_list_length', 'is_included', 'scope', 'nullifier', 'attested_at'],
|
|
114
119
|
},
|
|
115
120
|
oidc_domain_attestation: {
|
|
116
121
|
name: 'OIDC Domain',
|
|
@@ -639,11 +644,9 @@ function validateProofRequest(request) {
|
|
|
639
644
|
if (inputs.userAddress && !/^0x[a-fA-F0-9]{40}$/.test(inputs.userAddress)) {
|
|
640
645
|
return { valid: false, error: 'Invalid userAddress format' };
|
|
641
646
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
if (!inputs.countryList.every(c => typeof c === 'string')) {
|
|
646
|
-
return { valid: false, error: 'countryList must contain only strings' };
|
|
647
|
+
const listError = countryListError(inputs.countryList);
|
|
648
|
+
if (listError) {
|
|
649
|
+
return { valid: false, error: listError };
|
|
647
650
|
}
|
|
648
651
|
if (typeof inputs.isIncluded !== 'boolean') {
|
|
649
652
|
return { valid: false, error: 'isIncluded is required and must be a boolean' };
|
|
@@ -3937,7 +3940,7 @@ async function verifyProofOffChain(circuit, proof, publicInputs, options = {}) {
|
|
|
3937
3940
|
// their trust anchor as a public input the prover chooses. For OIDC that is
|
|
3938
3941
|
// the RSA key -- see core's oidcProviderKeys. Checked here so a dApp that
|
|
3939
3942
|
// verifies off-chain gets the same answer as one that verifies on-chain.
|
|
3940
|
-
const issuer = await checkProofIssuer(circuit, publicInputs, { request: options.request });
|
|
3943
|
+
const issuer = await checkProofIssuer(circuit, publicInputs, { request: options.request, countryCondition: options.countryCondition });
|
|
3941
3944
|
if (!issuer.ok) {
|
|
3942
3945
|
return { valid: false, error: issuer.error };
|
|
3943
3946
|
}
|
|
@@ -4097,6 +4100,12 @@ class ProofportSDK {
|
|
|
4097
4100
|
* {@link ProofportSDK.MAX_REMEMBERED_RELAY_REQUESTS}.
|
|
4098
4101
|
*/
|
|
4099
4102
|
this.relayRequests = new Map();
|
|
4103
|
+
/**
|
|
4104
|
+
* requestId -> the country list and direction of the Coinbase Country relay
|
|
4105
|
+
* requests this instance created, so verifyResponse* can hold the proof to
|
|
4106
|
+
* them. Bounded like {@link relayRequests}.
|
|
4107
|
+
*/
|
|
4108
|
+
this.countryRequests = new Map();
|
|
4100
4109
|
this.signer = null;
|
|
4101
4110
|
this.socket = null;
|
|
4102
4111
|
/**
|
|
@@ -4228,6 +4237,15 @@ class ProofportSDK {
|
|
|
4228
4237
|
if (typeof inputs.isIncluded !== 'boolean') {
|
|
4229
4238
|
throw new Error('isIncluded is required for coinbase_country_attestation circuit');
|
|
4230
4239
|
}
|
|
4240
|
+
const listError = countryListError(inputs.countryList);
|
|
4241
|
+
if (listError)
|
|
4242
|
+
throw new Error(`${listError} (circuit: coinbase_country_attestation)`);
|
|
4243
|
+
// Coinbase Country v2 binds an action or none, like Coinbase KYC v2.
|
|
4244
|
+
if (inputs.action !== undefined) {
|
|
4245
|
+
const actionError = validateTypedAction(inputs.action);
|
|
4246
|
+
if (actionError)
|
|
4247
|
+
throw new Error(`${actionError} (circuit: coinbase_country_attestation)`);
|
|
4248
|
+
}
|
|
4231
4249
|
const request = {
|
|
4232
4250
|
requestId: generateRequestId(),
|
|
4233
4251
|
circuit: 'coinbase_country_attestation',
|
|
@@ -4628,6 +4646,7 @@ class ProofportSDK {
|
|
|
4628
4646
|
return verifyProofOnChain(circuit, parsedProof, providerOrSigner, this.verifierTarget(circuit), undefined, {
|
|
4629
4647
|
request: options.request,
|
|
4630
4648
|
signerVariant: options.signerVariant,
|
|
4649
|
+
countryCondition: options.countryCondition,
|
|
4631
4650
|
});
|
|
4632
4651
|
}
|
|
4633
4652
|
/**
|
|
@@ -4668,6 +4687,43 @@ class ProofportSDK {
|
|
|
4668
4687
|
this.relayRequests.delete(oldest);
|
|
4669
4688
|
}
|
|
4670
4689
|
}
|
|
4690
|
+
/**
|
|
4691
|
+
* The country list and direction a `coinbase_country_attestation` response
|
|
4692
|
+
* must have been proved against: the one the caller passed, else the relay
|
|
4693
|
+
* request this instance created under the response's requestId, else the
|
|
4694
|
+
* deep-link request still pending under it. `{ error }` when there is none:
|
|
4695
|
+
* the proof's list is the prover's choice, so a response this SDK cannot
|
|
4696
|
+
* place is refused rather than accepted for whatever list it carries.
|
|
4697
|
+
*/
|
|
4698
|
+
countryConditionFor(response, explicit) {
|
|
4699
|
+
if (explicit !== undefined)
|
|
4700
|
+
return { condition: explicit };
|
|
4701
|
+
const remembered = this.countryRequests.get(response.requestId);
|
|
4702
|
+
if (remembered)
|
|
4703
|
+
return { condition: remembered };
|
|
4704
|
+
const pending = this.pendingRequests.get(response.requestId);
|
|
4705
|
+
const inputs = pending?.circuit === 'coinbase_country_attestation' ? pending.inputs : undefined;
|
|
4706
|
+
if (inputs && Array.isArray(inputs.countryList) && typeof inputs.isIncluded === 'boolean') {
|
|
4707
|
+
return { condition: { countryList: [...inputs.countryList], isIncluded: inputs.isIncluded } };
|
|
4708
|
+
}
|
|
4709
|
+
return {
|
|
4710
|
+
error: `No Coinbase Country request ${JSON.stringify(response.requestId)} was created by this SDK instance, so the ` +
|
|
4711
|
+
"proof's country list cannot be checked against what was asked. Pass { countryCondition: { countryList, " +
|
|
4712
|
+
'isIncluded } } -- the list and direction of the request you created.',
|
|
4713
|
+
};
|
|
4714
|
+
}
|
|
4715
|
+
/** @internal Remember a Coinbase Country relay request's list and direction for verifyResponse*. */
|
|
4716
|
+
rememberCountryRequest(requestId, inputs) {
|
|
4717
|
+
const i = inputs;
|
|
4718
|
+
if (typeof requestId !== 'string' || !i || !Array.isArray(i.countryList) || typeof i.isIncluded !== 'boolean')
|
|
4719
|
+
return;
|
|
4720
|
+
this.countryRequests.delete(requestId);
|
|
4721
|
+
this.countryRequests.set(requestId, { countryList: [...i.countryList], isIncluded: i.isIncluded });
|
|
4722
|
+
while (this.countryRequests.size > ProofportSDK.MAX_REMEMBERED_RELAY_REQUESTS) {
|
|
4723
|
+
const oldest = this.countryRequests.keys().next().value;
|
|
4724
|
+
this.countryRequests.delete(oldest);
|
|
4725
|
+
}
|
|
4726
|
+
}
|
|
4671
4727
|
/**
|
|
4672
4728
|
* The verifier for a circuit: the one the dApp configured, otherwise the
|
|
4673
4729
|
* pinned deployment on the chain this SDK's environment verifies that
|
|
@@ -4696,6 +4752,10 @@ class ProofportSDK {
|
|
|
4696
4752
|
* @param options.request - For `oidc_domain_attestation`: the `{ scope, requestId }`
|
|
4697
4753
|
* the response answers. Not needed for a relay request this SDK instance
|
|
4698
4754
|
* created; an OIDC response this instance cannot place is refused without it.
|
|
4755
|
+
* @param options.countryCondition - For `coinbase_country_attestation`: the
|
|
4756
|
+
* `{ countryList, isIncluded }` the request asked for. Not needed for a
|
|
4757
|
+
* request this SDK instance created (relay or deep link); a Country response
|
|
4758
|
+
* this instance cannot place is refused without it.
|
|
4699
4759
|
*
|
|
4700
4760
|
* @returns Promise resolving to verification result with valid boolean and optional error message
|
|
4701
4761
|
*
|
|
@@ -4733,6 +4793,13 @@ class ProofportSDK {
|
|
|
4733
4793
|
return { valid: false, error: found.error };
|
|
4734
4794
|
request = found.request;
|
|
4735
4795
|
}
|
|
4796
|
+
let countryCondition;
|
|
4797
|
+
if (response.circuit === 'coinbase_country_attestation') {
|
|
4798
|
+
const found = this.countryConditionFor(response, options.countryCondition);
|
|
4799
|
+
if ('error' in found)
|
|
4800
|
+
return { valid: false, error: found.error };
|
|
4801
|
+
countryCondition = found.condition;
|
|
4802
|
+
}
|
|
4736
4803
|
const parsedProof = parseProofForOnChain(response.proof, response.publicInputs, response.publicInputs.length);
|
|
4737
4804
|
// The response's verifierAddress and chainId are a claim to check, never
|
|
4738
4805
|
// the contract to call: whoever wrote the response would otherwise pick
|
|
@@ -4741,7 +4808,7 @@ class ProofportSDK {
|
|
|
4741
4808
|
return verifyProofOnChain(response.circuit, parsedProof, providerOrSigner, this.verifierTarget(response.circuit), {
|
|
4742
4809
|
verifierAddress: response.verifierAddress,
|
|
4743
4810
|
chainId: response.chainId,
|
|
4744
|
-
}, { request, signerVariant: response.signerVariant });
|
|
4811
|
+
}, { request, signerVariant: response.signerVariant, countryCondition });
|
|
4745
4812
|
}
|
|
4746
4813
|
/**
|
|
4747
4814
|
* Verifies a proof response off-chain, against the circuit's verification key.
|
|
@@ -4778,7 +4845,14 @@ class ProofportSDK {
|
|
|
4778
4845
|
return { valid: false, error: found.error };
|
|
4779
4846
|
request = found.request;
|
|
4780
4847
|
}
|
|
4781
|
-
|
|
4848
|
+
let countryCondition;
|
|
4849
|
+
if (response.circuit === 'coinbase_country_attestation') {
|
|
4850
|
+
const found = this.countryConditionFor(response, options?.countryCondition);
|
|
4851
|
+
if ('error' in found)
|
|
4852
|
+
return { valid: false, error: found.error };
|
|
4853
|
+
countryCondition = found.condition;
|
|
4854
|
+
}
|
|
4855
|
+
return verifyProofOffChain(response.circuit, response.proof, response.publicInputs, { ...options, request, countryCondition, signerVariant: response.signerVariant });
|
|
4782
4856
|
}
|
|
4783
4857
|
// ============ Utility Methods ============
|
|
4784
4858
|
/**
|
|
@@ -5156,6 +5230,16 @@ class ProofportSDK {
|
|
|
5156
5230
|
}
|
|
5157
5231
|
}
|
|
5158
5232
|
}
|
|
5233
|
+
// A Coinbase Country list the circuit would refuse is refused here, before a
|
|
5234
|
+
// challenge is burned, as createProofRequest does.
|
|
5235
|
+
if (circuit === 'coinbase_country_attestation') {
|
|
5236
|
+
const c = inputs;
|
|
5237
|
+
const listError = countryListError(c.countryList);
|
|
5238
|
+
if (listError)
|
|
5239
|
+
throw new Error(`${listError} (circuit: ${circuit})`);
|
|
5240
|
+
if (typeof c.isIncluded !== 'boolean')
|
|
5241
|
+
throw new Error('isIncluded is required for coinbase_country_attestation circuit');
|
|
5242
|
+
}
|
|
5159
5243
|
// Return target: validated before we burn a challenge, so a typo surfaces
|
|
5160
5244
|
// as a local error instead of a relay 400 on the second round trip.
|
|
5161
5245
|
if (options.returnScheme !== undefined) {
|
|
@@ -5211,6 +5295,8 @@ class ProofportSDK {
|
|
|
5211
5295
|
}
|
|
5212
5296
|
const created = await response.json();
|
|
5213
5297
|
this.rememberRelayRequest(created.requestId, scope);
|
|
5298
|
+
if (circuit === 'coinbase_country_attestation')
|
|
5299
|
+
this.rememberCountryRequest(created.requestId, inputs);
|
|
5214
5300
|
return created;
|
|
5215
5301
|
}
|
|
5216
5302
|
/**
|