@zkproofport-app/sdk 0.2.15 → 0.3.0
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 +70 -2
- package/dist/ProofportSDK.d.ts +1 -1
- package/dist/circuits.d.ts +45 -3
- package/dist/circuits.esm.js +58 -10
- package/dist/circuits.esm.js.map +1 -1
- package/dist/circuits.js +59 -9
- package/dist/circuits.js.map +1 -1
- package/dist/circuits.mjs +58 -10
- package/dist/circuits.mjs.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.esm.js +472 -290
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +474 -289
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +472 -290
- package/dist/index.mjs.map +1 -1
- package/dist/typedAction.d.ts +29 -0
- package/dist/typedAction.esm.js +72 -0
- package/dist/typedAction.esm.js.map +1 -0
- package/dist/typedAction.js +75 -0
- package/dist/typedAction.js.map +1 -0
- package/dist/typedAction.mjs +72 -0
- package/dist/typedAction.mjs.map +1 -0
- package/dist/types.d.ts +62 -1
- package/dist/verifier.d.ts +9 -6
- package/package.json +8 -2
package/dist/index.js
CHANGED
|
@@ -84,6 +84,16 @@ const CIRCUIT_METADATA = {
|
|
|
84
84
|
publicInputsCount: 148,
|
|
85
85
|
publicInputNames: ['pubkey_modulus_limbs', 'domain', 'scope', 'nullifier', 'provider'],
|
|
86
86
|
},
|
|
87
|
+
arc_eligibility: {
|
|
88
|
+
name: 'Arc Eligibility',
|
|
89
|
+
description: 'Prove eligibility and authorize one EIP-712 action, for an Arc gate',
|
|
90
|
+
// 6 x `pub [u8; 32]` in arc-eligibility/src/main.nr, flattened to bytes.
|
|
91
|
+
// Read off the compiled ABI rather than counted: signal_hash 0-31,
|
|
92
|
+
// domain_separator 32-63, action_hash 64-95, signer_list_merkle_root
|
|
93
|
+
// 96-127, scope 128-159, nullifier 160-191.
|
|
94
|
+
publicInputsCount: 192,
|
|
95
|
+
publicInputNames: ['signal_hash', 'domain_separator', 'action_hash', 'signer_list_merkle_root'],
|
|
96
|
+
},
|
|
87
97
|
// Planned circuit — the identifier is reserved and the circuit exists, but it
|
|
88
98
|
// is not officially supported yet. See CIRCUIT_SUPPORT_STATUS in ./circuits.
|
|
89
99
|
// Forked from coinbase_attestation and shares its four public inputs, so the
|
|
@@ -323,6 +333,292 @@ const OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT = {
|
|
|
323
333
|
DOMAIN_END: 82,
|
|
324
334
|
};
|
|
325
335
|
|
|
336
|
+
/**
|
|
337
|
+
* Canonical ZKProofport circuit identifiers.
|
|
338
|
+
*
|
|
339
|
+
* This module is the single source of truth for circuit IDs. Every other layer
|
|
340
|
+
* — the mobile app, the demo, the relay, server-side provers — reads its list
|
|
341
|
+
* from here instead of keeping a private copy that silently drifts.
|
|
342
|
+
*
|
|
343
|
+
* It has **no imports and no dependencies**, so it can be pulled in on its own:
|
|
344
|
+
*
|
|
345
|
+
* ```typescript
|
|
346
|
+
* import { CIRCUIT_IDS, isCircuitId } from '@zkproofport-app/sdk/circuits';
|
|
347
|
+
* ```
|
|
348
|
+
*
|
|
349
|
+
* The same names are also re-exported from the package root
|
|
350
|
+
* (`@zkproofport-app/sdk`) for callers that already import the SDK.
|
|
351
|
+
*
|
|
352
|
+
* ## Identifiers are canonical and case-sensitive
|
|
353
|
+
*
|
|
354
|
+
* Each ID is the circuit's `name` in its `Nargo.toml`, verbatim. The format is
|
|
355
|
+
* lowercase with underscores. Hyphenated spellings (`coinbase-kyc`,
|
|
356
|
+
* `mdl-kr-age`) are directory or UI route names in other repositories — they
|
|
357
|
+
* are **not** circuit IDs, and passing one produces a nullifier mismatch or a
|
|
358
|
+
* failed on-chain lookup rather than a clear error.
|
|
359
|
+
*/
|
|
360
|
+
/**
|
|
361
|
+
* Canonical circuit identifiers, keyed by a stable constant name.
|
|
362
|
+
*
|
|
363
|
+
* Prefer these constants over string literals so a rename is a compile error
|
|
364
|
+
* rather than a runtime surprise.
|
|
365
|
+
*
|
|
366
|
+
* @example
|
|
367
|
+
* ```typescript
|
|
368
|
+
* import { CIRCUIT_IDS } from '@zkproofport-app/sdk/circuits';
|
|
369
|
+
*
|
|
370
|
+
* const relay = await sdk.createRelayRequest(CIRCUIT_IDS.COINBASE_ATTESTATION, {
|
|
371
|
+
* scope: 'myapp.com',
|
|
372
|
+
* });
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
const CIRCUIT_IDS = Object.freeze({
|
|
376
|
+
/** Coinbase KYC attestation. Officially supported. */
|
|
377
|
+
COINBASE_ATTESTATION: 'coinbase_attestation',
|
|
378
|
+
/** Coinbase country attestation (inclusion / exclusion). Officially supported. */
|
|
379
|
+
COINBASE_COUNTRY_ATTESTATION: 'coinbase_country_attestation',
|
|
380
|
+
/** OIDC email-domain attestation. Officially supported. */
|
|
381
|
+
OIDC_DOMAIN_ATTESTATION: 'oidc_domain_attestation',
|
|
382
|
+
/** Arc eligibility: one EIP-712 action, authorized. Officially supported. */
|
|
383
|
+
ARC_ELIGIBILITY: 'arc_eligibility',
|
|
384
|
+
/** GIWA attestation. Planned — not officially supported yet. */
|
|
385
|
+
GIWA_ATTESTATION: 'giwa_attestation',
|
|
386
|
+
/** Korea Mobile ID ownership. Planned — not officially supported yet. */
|
|
387
|
+
MDL_KR_OWNERSHIP: 'mdl_kr_ownership',
|
|
388
|
+
/** Korea Mobile ID age threshold. Planned — not officially supported yet. */
|
|
389
|
+
MDL_KR_AGE: 'mdl_kr_age',
|
|
390
|
+
/** Korea Mobile ID si/do region. Planned — not officially supported yet. */
|
|
391
|
+
MDL_KR_REGION: 'mdl_kr_region',
|
|
392
|
+
});
|
|
393
|
+
/**
|
|
394
|
+
* Support status for every circuit.
|
|
395
|
+
*
|
|
396
|
+
* The record is exhaustive over {@link CircuitId}: adding an identifier without
|
|
397
|
+
* giving it a status is a compile error, so this list cannot drift from
|
|
398
|
+
* {@link CIRCUIT_IDS}.
|
|
399
|
+
*
|
|
400
|
+
* @example
|
|
401
|
+
* ```typescript
|
|
402
|
+
* import { CIRCUIT_SUPPORT_STATUS } from '@zkproofport-app/sdk/circuits';
|
|
403
|
+
*
|
|
404
|
+
* CIRCUIT_SUPPORT_STATUS.coinbase_attestation; // 'supported'
|
|
405
|
+
* CIRCUIT_SUPPORT_STATUS.mdl_kr_age; // 'planned'
|
|
406
|
+
* ```
|
|
407
|
+
*/
|
|
408
|
+
const CIRCUIT_SUPPORT_STATUS = Object.freeze({
|
|
409
|
+
coinbase_attestation: 'supported',
|
|
410
|
+
coinbase_country_attestation: 'supported',
|
|
411
|
+
oidc_domain_attestation: 'supported',
|
|
412
|
+
arc_eligibility: 'experimental',
|
|
413
|
+
giwa_attestation: 'planned',
|
|
414
|
+
mdl_kr_ownership: 'planned',
|
|
415
|
+
mdl_kr_age: 'planned',
|
|
416
|
+
mdl_kr_region: 'planned',
|
|
417
|
+
});
|
|
418
|
+
/**
|
|
419
|
+
* Where each circuit's verification key FILE lives inside the
|
|
420
|
+
* `zkproofport/circuits` repository, relative to its root.
|
|
421
|
+
*
|
|
422
|
+
* Off-chain verification needs the verification key and nothing else — no
|
|
423
|
+
* circuit bytecode, no witness. The key is public by nature: it is what anyone
|
|
424
|
+
* checking a proof must already have, and the Solidity verifiers deployed
|
|
425
|
+
* on-chain embed the same key.
|
|
426
|
+
*
|
|
427
|
+
* NOTE THE TRAILING `/vk` TWICE. In that repository `target/vk` is a DIRECTORY
|
|
428
|
+
* holding `vk`, `vk_hash` and `SHA256SUMS`; the key itself is `target/vk/vk`.
|
|
429
|
+
* These paths were first copied here from the mobile app, which keeps the
|
|
430
|
+
* directory and the filename as two fields and joins them — so taking only the
|
|
431
|
+
* directory produced a URL that answers 404, and the first browser to try
|
|
432
|
+
* off-chain verification said "Could not fetch the verification key (HTTP 404)".
|
|
433
|
+
*
|
|
434
|
+
* Exhaustive over {@link CircuitId} on purpose. A new circuit whose key path is
|
|
435
|
+
* not listed here fails to compile rather than failing at verification time,
|
|
436
|
+
* where the only symptom would be a proof that cannot be checked.
|
|
437
|
+
*/
|
|
438
|
+
const CIRCUIT_VK_PATHS = Object.freeze({
|
|
439
|
+
coinbase_attestation: 'coinbase-attestation/target/vk/vk',
|
|
440
|
+
coinbase_country_attestation: 'coinbase-country-attestation/target/vk/vk',
|
|
441
|
+
oidc_domain_attestation: 'oidc-domain-attestation/target/vk/vk',
|
|
442
|
+
arc_eligibility: 'arc-eligibility/target/vk/vk',
|
|
443
|
+
giwa_attestation: 'giwa-attestation/target/vk/vk',
|
|
444
|
+
mdl_kr_ownership: 'mdl/kr-ownership/target/vk/vk',
|
|
445
|
+
mdl_kr_age: 'mdl/kr-age/target/vk/vk',
|
|
446
|
+
mdl_kr_region: 'mdl/kr-region/target/vk/vk',
|
|
447
|
+
});
|
|
448
|
+
/**
|
|
449
|
+
* Every canonical circuit identifier, in declaration order.
|
|
450
|
+
*
|
|
451
|
+
* Includes circuits that are still `planned`. Use {@link SUPPORTED_CIRCUIT_IDS}
|
|
452
|
+
* when you only want the ones that are officially supported today.
|
|
453
|
+
*/
|
|
454
|
+
const ALL_CIRCUIT_IDS = Object.freeze(Object.values(CIRCUIT_IDS));
|
|
455
|
+
/**
|
|
456
|
+
* Circuits that are officially supported today: the two Coinbase circuits and
|
|
457
|
+
* OIDC domain attestation.
|
|
458
|
+
*
|
|
459
|
+
* Derived from {@link CIRCUIT_SUPPORT_STATUS}, so it cannot fall out of sync.
|
|
460
|
+
*/
|
|
461
|
+
const SUPPORTED_CIRCUIT_IDS = Object.freeze(ALL_CIRCUIT_IDS.filter((id) => CIRCUIT_SUPPORT_STATUS[id] === 'supported'));
|
|
462
|
+
/**
|
|
463
|
+
* Circuits whose identifiers are reserved but which are not officially
|
|
464
|
+
* supported yet: GIWA attestation and the three Korea Mobile ID circuits.
|
|
465
|
+
*
|
|
466
|
+
* Derived from {@link CIRCUIT_SUPPORT_STATUS}, so it cannot fall out of sync.
|
|
467
|
+
*/
|
|
468
|
+
/**
|
|
469
|
+
* Circuits you can prove today, on a testnet, with everything subject to change.
|
|
470
|
+
*
|
|
471
|
+
* `arc_eligibility` is the reason this status exists. Calling it `planned`
|
|
472
|
+
* would be wrong — the circuit compiles, the verifier is deployed and a proof
|
|
473
|
+
* verifies against it — and calling it `supported` would be worse, because the
|
|
474
|
+
* only deployment is Arc Testnet, Circle has not published a mainnet chain id,
|
|
475
|
+
* and the public-input layout is still open. Neither existing word was true, so
|
|
476
|
+
* the honest move was a third word rather than picking the closer lie.
|
|
477
|
+
*/
|
|
478
|
+
const EXPERIMENTAL_CIRCUIT_IDS = Object.freeze(ALL_CIRCUIT_IDS.filter(id => CIRCUIT_SUPPORT_STATUS[id] === 'experimental'));
|
|
479
|
+
const PLANNED_CIRCUIT_IDS = Object.freeze(ALL_CIRCUIT_IDS.filter((id) => CIRCUIT_SUPPORT_STATUS[id] === 'planned'));
|
|
480
|
+
/**
|
|
481
|
+
* Narrows an unknown value to a canonical circuit identifier.
|
|
482
|
+
*
|
|
483
|
+
* Returns `false` for hyphenated route names such as `'coinbase-kyc'`, which
|
|
484
|
+
* are not circuit IDs.
|
|
485
|
+
*
|
|
486
|
+
* @example
|
|
487
|
+
* ```typescript
|
|
488
|
+
* isCircuitId('coinbase_attestation'); // true
|
|
489
|
+
* isCircuitId('coinbase-kyc'); // false
|
|
490
|
+
* ```
|
|
491
|
+
*/
|
|
492
|
+
function isCircuitId(value) {
|
|
493
|
+
return typeof value === 'string' && ALL_CIRCUIT_IDS.includes(value);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Narrows an unknown value to a circuit identifier that is officially supported
|
|
497
|
+
* today. Planned circuits return `false`.
|
|
498
|
+
*
|
|
499
|
+
* @example
|
|
500
|
+
* ```typescript
|
|
501
|
+
* isSupportedCircuitId('oidc_domain_attestation'); // true
|
|
502
|
+
* isSupportedCircuitId('mdl_kr_age'); // false — planned
|
|
503
|
+
* ```
|
|
504
|
+
*/
|
|
505
|
+
function isSupportedCircuitId(value) {
|
|
506
|
+
return isCircuitId(value) && CIRCUIT_SUPPORT_STATUS[value] === 'supported';
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Returns the support status of a circuit.
|
|
510
|
+
*
|
|
511
|
+
* @param circuit - A canonical circuit identifier.
|
|
512
|
+
* @throws Error when the value is not a canonical circuit identifier.
|
|
513
|
+
*
|
|
514
|
+
* @example
|
|
515
|
+
* ```typescript
|
|
516
|
+
* getCircuitSupportStatus('giwa_attestation'); // 'planned'
|
|
517
|
+
* ```
|
|
518
|
+
*/
|
|
519
|
+
function getCircuitSupportStatus(circuit) {
|
|
520
|
+
if (!isCircuitId(circuit)) {
|
|
521
|
+
throw new Error(`Unknown circuit '${String(circuit)}'. Expected one of: ${ALL_CIRCUIT_IDS.join(', ')}`);
|
|
522
|
+
}
|
|
523
|
+
return CIRCUIT_SUPPORT_STATUS[circuit];
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Whether a circuit's relay request must carry a wallet signature.
|
|
527
|
+
*
|
|
528
|
+
* The SDK checks this to fail fast — "Signer not set" locally beats a 401 from
|
|
529
|
+
* the relay two round trips later, with nothing saying which circuit needed
|
|
530
|
+
* what. The relay declares its OWN copy on purpose and does not import this
|
|
531
|
+
* one: it is a security boundary, and an auth policy that a published package
|
|
532
|
+
* can change is not a policy. A test in the relay asserts the two agree.
|
|
533
|
+
*
|
|
534
|
+
* `satisfies` and not an annotation, so a circuit added above is a compile
|
|
535
|
+
* error here until somebody decides which it is.
|
|
536
|
+
*/
|
|
537
|
+
const CIRCUIT_NEEDS_WALLET_SIGNATURE = {
|
|
538
|
+
coinbase_attestation: true,
|
|
539
|
+
coinbase_country_attestation: true,
|
|
540
|
+
// Same Coinbase-attested wallet as above; what changes is that it signs an
|
|
541
|
+
// EIP-712 action rather than signal_hash.
|
|
542
|
+
arc_eligibility: true,
|
|
543
|
+
// The signature is inside the OIDC identity token, checked by the prover.
|
|
544
|
+
oidc_domain_attestation: false,
|
|
545
|
+
// Web2 (OmniOne CX) flows with no wallet binding at all.
|
|
546
|
+
mdl_kr_ownership: false,
|
|
547
|
+
mdl_kr_age: false,
|
|
548
|
+
mdl_kr_region: false,
|
|
549
|
+
giwa_attestation: false,
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
553
|
+
/**
|
|
554
|
+
* The reason the value is not a usable action, or `null` when it is one.
|
|
555
|
+
*
|
|
556
|
+
* A string rather than a boolean because every caller shows it to somebody —
|
|
557
|
+
* a dapp developer reading a validation error, or a person looking at the
|
|
558
|
+
* mobile app's demo screen.
|
|
559
|
+
*/
|
|
560
|
+
function validateTypedAction(value) {
|
|
561
|
+
if (value === undefined || value === null) {
|
|
562
|
+
return 'action is required for arc_eligibility. The circuit proves that a wallet authorised ONE EIP-712 action; there is nothing to prove without it.';
|
|
563
|
+
}
|
|
564
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
565
|
+
return 'action must be an object.';
|
|
566
|
+
}
|
|
567
|
+
const a = value;
|
|
568
|
+
const d = a.domain;
|
|
569
|
+
if (!d || typeof d !== 'object') {
|
|
570
|
+
return 'action.domain is required.';
|
|
571
|
+
}
|
|
572
|
+
for (const k of ['name', 'version', 'verifyingContract']) {
|
|
573
|
+
if (typeof d[k] !== 'string' || !d[k]) {
|
|
574
|
+
return `action.domain.${k} must be a non-empty string.`;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (typeof d.chainId !== 'number' || !Number.isInteger(d.chainId)) {
|
|
578
|
+
return 'action.domain.chainId must be an integer.';
|
|
579
|
+
}
|
|
580
|
+
if (!ADDRESS.test(d.verifyingContract)) {
|
|
581
|
+
return 'action.domain.verifyingContract must be a 20-byte hex address.';
|
|
582
|
+
}
|
|
583
|
+
if (!a.types || typeof a.types !== 'object' || Array.isArray(a.types)) {
|
|
584
|
+
return 'action.types is required.';
|
|
585
|
+
}
|
|
586
|
+
if (typeof a.primaryType !== 'string' || !a.primaryType) {
|
|
587
|
+
return 'action.primaryType is required.';
|
|
588
|
+
}
|
|
589
|
+
if (!(a.primaryType in a.types)) {
|
|
590
|
+
return `action.primaryType '${a.primaryType}' is not one of the structs in action.types.`;
|
|
591
|
+
}
|
|
592
|
+
if (!a.message || typeof a.message !== 'object' || Array.isArray(a.message)) {
|
|
593
|
+
return 'action.message is required.';
|
|
594
|
+
}
|
|
595
|
+
// Every field the struct declares must be present, or the wallet signs a
|
|
596
|
+
// different message than the one the caller described.
|
|
597
|
+
const fields = a.types[a.primaryType];
|
|
598
|
+
if (!Array.isArray(fields) || fields.length === 0) {
|
|
599
|
+
return `action.types.${a.primaryType} declares no fields.`;
|
|
600
|
+
}
|
|
601
|
+
const missing = fields
|
|
602
|
+
.map(f => f?.name)
|
|
603
|
+
.filter(n => typeof n === 'string' && !(n in a.message));
|
|
604
|
+
if (missing.length) {
|
|
605
|
+
return `action.message is missing: ${missing.join(', ')}`;
|
|
606
|
+
}
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
/** The action, or the reason it is not one. For text a person typed. */
|
|
610
|
+
function parseTypedAction(text) {
|
|
611
|
+
let raw;
|
|
612
|
+
try {
|
|
613
|
+
raw = JSON.parse(text);
|
|
614
|
+
}
|
|
615
|
+
catch (e) {
|
|
616
|
+
return { error: `Not valid JSON: ${e.message}` };
|
|
617
|
+
}
|
|
618
|
+
const error = validateTypedAction(raw);
|
|
619
|
+
return error ? { error } : { action: raw };
|
|
620
|
+
}
|
|
621
|
+
|
|
326
622
|
/**
|
|
327
623
|
* Deep Link utilities for ZKProofport SDK
|
|
328
624
|
*/
|
|
@@ -501,9 +797,22 @@ function parseProofResponseUrl(url) {
|
|
|
501
797
|
if (!requestId || !status) {
|
|
502
798
|
return null;
|
|
503
799
|
}
|
|
800
|
+
// A missing or unrecognised circuit is a REJECTION, not a default.
|
|
801
|
+
//
|
|
802
|
+
// This used to fall back to `coinbase_attestation`. A callback carrying a
|
|
803
|
+
// country or OIDC proof, or a circuit this build has never heard of, was
|
|
804
|
+
// then handed to the caller labelled Coinbase KYC -- the parse succeeded,
|
|
805
|
+
// the label was wrong, and nothing downstream could tell. Whoever reads
|
|
806
|
+
// `response.circuit` decides which verifier and which public-input layout
|
|
807
|
+
// to use, so the wrong label is a proof checked against another circuit's
|
|
808
|
+
// bytes.
|
|
809
|
+
const circuit = urlObj.searchParams.get('circuit');
|
|
810
|
+
if (!circuit || !isCircuitId(circuit)) {
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
504
813
|
const response = {
|
|
505
814
|
requestId,
|
|
506
|
-
circuit
|
|
815
|
+
circuit,
|
|
507
816
|
status,
|
|
508
817
|
};
|
|
509
818
|
if (status === 'completed') {
|
|
@@ -805,15 +1114,15 @@ function validateProofRequest(request) {
|
|
|
805
1114
|
if (!request.circuit) {
|
|
806
1115
|
return { valid: false, error: 'Missing circuit type' };
|
|
807
1116
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1117
|
+
// The list is the SDK's own, not a copy. The copy that used to sit here
|
|
1118
|
+
// named six circuits and had missed two — `giwa_attestation` and
|
|
1119
|
+
// `arc_eligibility` — so a link for either was refused as an invalid type
|
|
1120
|
+
// while every other layer accepted it.
|
|
1121
|
+
if (!isCircuitId(request.circuit)) {
|
|
1122
|
+
return {
|
|
1123
|
+
valid: false,
|
|
1124
|
+
error: `Invalid circuit type: ${request.circuit}. Expected one of: ${ALL_CIRCUIT_IDS.join(', ')}`,
|
|
1125
|
+
};
|
|
817
1126
|
}
|
|
818
1127
|
if (!request.callbackUrl) {
|
|
819
1128
|
return { valid: false, error: 'Missing callbackUrl' };
|
|
@@ -862,6 +1171,16 @@ function validateProofRequest(request) {
|
|
|
862
1171
|
return { valid: false, error: mdlError };
|
|
863
1172
|
}
|
|
864
1173
|
}
|
|
1174
|
+
else if (request.circuit === 'arc_eligibility') {
|
|
1175
|
+
const inputs = request.inputs;
|
|
1176
|
+
if (inputs.userAddress && !/^0x[a-fA-F0-9]{40}$/.test(inputs.userAddress)) {
|
|
1177
|
+
return { valid: false, error: 'Invalid userAddress format' };
|
|
1178
|
+
}
|
|
1179
|
+
const actionError = validateTypedAction(inputs.action);
|
|
1180
|
+
if (actionError) {
|
|
1181
|
+
return { valid: false, error: actionError };
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
865
1184
|
// Check expiry
|
|
866
1185
|
if (request.expiresAt && Date.now() > request.expiresAt) {
|
|
867
1186
|
return { valid: false, error: 'Request has expired' };
|
|
@@ -1145,7 +1464,7 @@ function getDefaultProvider(chainId) {
|
|
|
1145
1464
|
* This function resolves the verifier contract from SDK config or proof response,
|
|
1146
1465
|
* connects to the blockchain, and calls the verify() method with the proof and public inputs.
|
|
1147
1466
|
*
|
|
1148
|
-
* @param circuit - The canonical circuit identifier
|
|
1467
|
+
* @param circuit - The canonical circuit identifier. REQUIRED.
|
|
1149
1468
|
* @param parsedProof - Parsed proof object containing proofHex and publicInputsHex
|
|
1150
1469
|
* @param providerOrSigner - Optional ethers.js Provider or Signer instance. If not provided, uses default RPC for the chain
|
|
1151
1470
|
* @param customVerifier - Optional custom verifier contract config (takes priority over responseVerifier)
|
|
@@ -1243,7 +1562,7 @@ function requireVerifier(circuit, verifier) {
|
|
|
1243
1562
|
/**
|
|
1244
1563
|
* Get verifier contract address for a circuit.
|
|
1245
1564
|
*
|
|
1246
|
-
* @param circuit - The canonical circuit identifier
|
|
1565
|
+
* @param circuit - The canonical circuit identifier. REQUIRED.
|
|
1247
1566
|
* @param customVerifier - Optional custom verifier contract config
|
|
1248
1567
|
* @returns Verifier contract address as hex string
|
|
1249
1568
|
* @throws Error if no verifier is configured for the circuit
|
|
@@ -1260,7 +1579,7 @@ function getVerifierAddress(circuit, customVerifier) {
|
|
|
1260
1579
|
/**
|
|
1261
1580
|
* Get chain ID for a circuit's verifier contract.
|
|
1262
1581
|
*
|
|
1263
|
-
* @param circuit - The canonical circuit identifier
|
|
1582
|
+
* @param circuit - The canonical circuit identifier. REQUIRED.
|
|
1264
1583
|
* @param customVerifier - Optional custom verifier contract config
|
|
1265
1584
|
* @returns Chain ID number (e.g., 11155111 for Sepolia, 84532 for Base Sepolia)
|
|
1266
1585
|
* @throws Error if no verifier is configured for the circuit
|
|
@@ -1274,6 +1593,55 @@ function getVerifierAddress(circuit, customVerifier) {
|
|
|
1274
1593
|
function getVerifierChainId(circuit, customVerifier) {
|
|
1275
1594
|
return requireVerifier(circuit, customVerifier).chainId;
|
|
1276
1595
|
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Where `scope` and `nullifier` sit in each circuit's public inputs.
|
|
1598
|
+
*
|
|
1599
|
+
* Keyed by `CircuitId`, which is the point: adding a circuit to the SDK makes
|
|
1600
|
+
* this object a compile error until somebody opens that circuit's `fn main`
|
|
1601
|
+
* and writes its offsets down. Nothing here can be inferred from the proof —
|
|
1602
|
+
* the public inputs are a flat array of bytes with no framing.
|
|
1603
|
+
*
|
|
1604
|
+
* It replaced an `if / else if / else` chain whose final `else` handed every
|
|
1605
|
+
* unrecognised circuit — and every call that named no circuit at all —
|
|
1606
|
+
* Coinbase's offsets. For `arc_eligibility` that is not a near miss: its
|
|
1607
|
+
* public inputs are signal_hash, domain_separator, action_hash, merkle root,
|
|
1608
|
+
* scope, nullifier, so the nullifier slot under Coinbase's offsets holds the
|
|
1609
|
+
* MERKLE ROOT, a value identical for every user. Duplicate detection built on
|
|
1610
|
+
* it would report one person.
|
|
1611
|
+
*/
|
|
1612
|
+
const PUBLIC_INPUT_OFFSETS = Object.freeze({
|
|
1613
|
+
// signal_hash, signer_list_merkle_root, scope, nullifier
|
|
1614
|
+
coinbase_attestation: { scope: [64, 95], nullifier: [96, 127] },
|
|
1615
|
+
// Same four fields in the same order as Coinbase.
|
|
1616
|
+
giwa_attestation: { scope: [64, 95], nullifier: [96, 127] },
|
|
1617
|
+
// country_list, country_list_length and is_included sit before scope.
|
|
1618
|
+
coinbase_country_attestation: { scope: [86, 117], nullifier: [118, 149] },
|
|
1619
|
+
// pubkey_modulus_limbs and the bounded domain sit before scope.
|
|
1620
|
+
oidc_domain_attestation: { scope: [83, 114], nullifier: [115, 146] },
|
|
1621
|
+
// signal_hash, domain_separator, action_hash, merkle root, then the pair.
|
|
1622
|
+
arc_eligibility: { scope: [128, 159], nullifier: [160, 191] },
|
|
1623
|
+
// The Korea mDL circuits open with the pair; signal_hash is commented out.
|
|
1624
|
+
mdl_kr_ownership: { scope: [0, 31], nullifier: [32, 63] },
|
|
1625
|
+
mdl_kr_age: { scope: [0, 31], nullifier: [32, 63] },
|
|
1626
|
+
mdl_kr_region: { scope: [0, 31], nullifier: [32, 63] },
|
|
1627
|
+
});
|
|
1628
|
+
/**
|
|
1629
|
+
* The offsets for a circuit, or an error naming what was asked for.
|
|
1630
|
+
*
|
|
1631
|
+
* Throwing is the whole point. A caller that omits the circuit, or names one
|
|
1632
|
+
* this SDK has never heard of, is asking a question with no answer; returning
|
|
1633
|
+
* a guess produced a scope and a nullifier that belong to different bytes and
|
|
1634
|
+
* looked exactly like real ones.
|
|
1635
|
+
*/
|
|
1636
|
+
function offsetsFor(circuit) {
|
|
1637
|
+
if (circuit === undefined) {
|
|
1638
|
+
throw new Error(`A circuit id is required to read public inputs: the layout differs per circuit and cannot be inferred from the proof. Pass one of: ${ALL_CIRCUIT_IDS.join(', ')}`);
|
|
1639
|
+
}
|
|
1640
|
+
if (!isCircuitId(circuit)) {
|
|
1641
|
+
throw new Error(`Unknown circuit '${circuit}'. Expected one of: ${ALL_CIRCUIT_IDS.join(', ')}`);
|
|
1642
|
+
}
|
|
1643
|
+
return PUBLIC_INPUT_OFFSETS[circuit];
|
|
1644
|
+
}
|
|
1277
1645
|
/**
|
|
1278
1646
|
* Extract scope value from public inputs array.
|
|
1279
1647
|
*
|
|
@@ -1291,24 +1659,7 @@ function getVerifierChainId(circuit, customVerifier) {
|
|
|
1291
1659
|
* ```
|
|
1292
1660
|
*/
|
|
1293
1661
|
function extractScopeFromPublicInputs(publicInputsHex, circuit) {
|
|
1294
|
-
|
|
1295
|
-
if (circuit === 'coinbase_country_attestation') {
|
|
1296
|
-
start = 86;
|
|
1297
|
-
end = 117;
|
|
1298
|
-
}
|
|
1299
|
-
else if (circuit === 'oidc_domain_attestation') {
|
|
1300
|
-
start = 83;
|
|
1301
|
-
end = 114;
|
|
1302
|
-
}
|
|
1303
|
-
else if (circuit !== undefined && circuit.startsWith('mdl_kr_')) {
|
|
1304
|
-
// All Korea mDL circuits: scope [0..31], nullifier_value [32..63]
|
|
1305
|
-
start = 0;
|
|
1306
|
-
end = 31;
|
|
1307
|
-
}
|
|
1308
|
-
else {
|
|
1309
|
-
start = 64;
|
|
1310
|
-
end = 95;
|
|
1311
|
-
}
|
|
1662
|
+
const [start, end] = offsetsFor(circuit).scope;
|
|
1312
1663
|
if (publicInputsHex.length <= end)
|
|
1313
1664
|
return null;
|
|
1314
1665
|
const scopeFields = publicInputsHex.slice(start, end + 1);
|
|
@@ -1323,7 +1674,10 @@ function extractScopeFromPublicInputs(publicInputsHex, circuit) {
|
|
|
1323
1674
|
* detection without revealing the wallet address.
|
|
1324
1675
|
*
|
|
1325
1676
|
* @param publicInputsHex - Array of hex-encoded field elements
|
|
1326
|
-
* @param circuit - Circuit
|
|
1677
|
+
* @param circuit - Circuit id. REQUIRED: the layout differs per circuit and
|
|
1678
|
+
* cannot be inferred from the proof. It used to default to
|
|
1679
|
+
* `coinbase_attestation`, which returned the right-shaped bytes read from
|
|
1680
|
+
* the wrong place for every other circuit.
|
|
1327
1681
|
* @returns Nullifier as hex string (bytes32), or null if publicInputs too short
|
|
1328
1682
|
*
|
|
1329
1683
|
* @example
|
|
@@ -1333,24 +1687,7 @@ function extractScopeFromPublicInputs(publicInputsHex, circuit) {
|
|
|
1333
1687
|
* ```
|
|
1334
1688
|
*/
|
|
1335
1689
|
function extractNullifierFromPublicInputs(publicInputsHex, circuit) {
|
|
1336
|
-
|
|
1337
|
-
if (circuit === 'coinbase_country_attestation') {
|
|
1338
|
-
start = 118;
|
|
1339
|
-
end = 149;
|
|
1340
|
-
}
|
|
1341
|
-
else if (circuit === 'oidc_domain_attestation') {
|
|
1342
|
-
start = 115;
|
|
1343
|
-
end = 146;
|
|
1344
|
-
}
|
|
1345
|
-
else if (circuit !== undefined && circuit.startsWith('mdl_kr_')) {
|
|
1346
|
-
// All Korea mDL circuits: scope [0..31], nullifier_value [32..63]
|
|
1347
|
-
start = 32;
|
|
1348
|
-
end = 63;
|
|
1349
|
-
}
|
|
1350
|
-
else {
|
|
1351
|
-
start = 96;
|
|
1352
|
-
end = 127;
|
|
1353
|
-
}
|
|
1690
|
+
const [start, end] = offsetsFor(circuit).nullifier;
|
|
1354
1691
|
if (publicInputsHex.length <= end)
|
|
1355
1692
|
return null;
|
|
1356
1693
|
const nullifierFields = publicInputsHex.slice(start, end + 1);
|
|
@@ -1400,174 +1737,6 @@ function reconstructBytes32FromFields(fields) {
|
|
|
1400
1737
|
return '0x' + bytes;
|
|
1401
1738
|
}
|
|
1402
1739
|
|
|
1403
|
-
/**
|
|
1404
|
-
* Canonical ZKProofport circuit identifiers.
|
|
1405
|
-
*
|
|
1406
|
-
* This module is the single source of truth for circuit IDs. Every other layer
|
|
1407
|
-
* — the mobile app, the demo, the relay, server-side provers — reads its list
|
|
1408
|
-
* from here instead of keeping a private copy that silently drifts.
|
|
1409
|
-
*
|
|
1410
|
-
* It has **no imports and no dependencies**, so it can be pulled in on its own:
|
|
1411
|
-
*
|
|
1412
|
-
* ```typescript
|
|
1413
|
-
* import { CIRCUIT_IDS, isCircuitId } from '@zkproofport-app/sdk/circuits';
|
|
1414
|
-
* ```
|
|
1415
|
-
*
|
|
1416
|
-
* The same names are also re-exported from the package root
|
|
1417
|
-
* (`@zkproofport-app/sdk`) for callers that already import the SDK.
|
|
1418
|
-
*
|
|
1419
|
-
* ## Identifiers are canonical and case-sensitive
|
|
1420
|
-
*
|
|
1421
|
-
* Each ID is the circuit's `name` in its `Nargo.toml`, verbatim. The format is
|
|
1422
|
-
* lowercase with underscores. Hyphenated spellings (`coinbase-kyc`,
|
|
1423
|
-
* `mdl-kr-age`) are directory or UI route names in other repositories — they
|
|
1424
|
-
* are **not** circuit IDs, and passing one produces a nullifier mismatch or a
|
|
1425
|
-
* failed on-chain lookup rather than a clear error.
|
|
1426
|
-
*/
|
|
1427
|
-
/**
|
|
1428
|
-
* Canonical circuit identifiers, keyed by a stable constant name.
|
|
1429
|
-
*
|
|
1430
|
-
* Prefer these constants over string literals so a rename is a compile error
|
|
1431
|
-
* rather than a runtime surprise.
|
|
1432
|
-
*
|
|
1433
|
-
* @example
|
|
1434
|
-
* ```typescript
|
|
1435
|
-
* import { CIRCUIT_IDS } from '@zkproofport-app/sdk/circuits';
|
|
1436
|
-
*
|
|
1437
|
-
* const relay = await sdk.createRelayRequest(CIRCUIT_IDS.COINBASE_ATTESTATION, {
|
|
1438
|
-
* scope: 'myapp.com',
|
|
1439
|
-
* });
|
|
1440
|
-
* ```
|
|
1441
|
-
*/
|
|
1442
|
-
const CIRCUIT_IDS = Object.freeze({
|
|
1443
|
-
/** Coinbase KYC attestation. Officially supported. */
|
|
1444
|
-
COINBASE_ATTESTATION: 'coinbase_attestation',
|
|
1445
|
-
/** Coinbase country attestation (inclusion / exclusion). Officially supported. */
|
|
1446
|
-
COINBASE_COUNTRY_ATTESTATION: 'coinbase_country_attestation',
|
|
1447
|
-
/** OIDC email-domain attestation. Officially supported. */
|
|
1448
|
-
OIDC_DOMAIN_ATTESTATION: 'oidc_domain_attestation',
|
|
1449
|
-
/** GIWA attestation. Planned — not officially supported yet. */
|
|
1450
|
-
GIWA_ATTESTATION: 'giwa_attestation',
|
|
1451
|
-
/** Korea Mobile ID ownership. Planned — not officially supported yet. */
|
|
1452
|
-
MDL_KR_OWNERSHIP: 'mdl_kr_ownership',
|
|
1453
|
-
/** Korea Mobile ID age threshold. Planned — not officially supported yet. */
|
|
1454
|
-
MDL_KR_AGE: 'mdl_kr_age',
|
|
1455
|
-
/** Korea Mobile ID si/do region. Planned — not officially supported yet. */
|
|
1456
|
-
MDL_KR_REGION: 'mdl_kr_region',
|
|
1457
|
-
});
|
|
1458
|
-
/**
|
|
1459
|
-
* Support status for every circuit.
|
|
1460
|
-
*
|
|
1461
|
-
* The record is exhaustive over {@link CircuitId}: adding an identifier without
|
|
1462
|
-
* giving it a status is a compile error, so this list cannot drift from
|
|
1463
|
-
* {@link CIRCUIT_IDS}.
|
|
1464
|
-
*
|
|
1465
|
-
* @example
|
|
1466
|
-
* ```typescript
|
|
1467
|
-
* import { CIRCUIT_SUPPORT_STATUS } from '@zkproofport-app/sdk/circuits';
|
|
1468
|
-
*
|
|
1469
|
-
* CIRCUIT_SUPPORT_STATUS.coinbase_attestation; // 'supported'
|
|
1470
|
-
* CIRCUIT_SUPPORT_STATUS.mdl_kr_age; // 'planned'
|
|
1471
|
-
* ```
|
|
1472
|
-
*/
|
|
1473
|
-
const CIRCUIT_SUPPORT_STATUS = Object.freeze({
|
|
1474
|
-
coinbase_attestation: 'supported',
|
|
1475
|
-
coinbase_country_attestation: 'supported',
|
|
1476
|
-
oidc_domain_attestation: 'supported',
|
|
1477
|
-
giwa_attestation: 'planned',
|
|
1478
|
-
mdl_kr_ownership: 'planned',
|
|
1479
|
-
mdl_kr_age: 'planned',
|
|
1480
|
-
mdl_kr_region: 'planned',
|
|
1481
|
-
});
|
|
1482
|
-
/**
|
|
1483
|
-
* Where each circuit's verification key lives inside the `zkproofport/circuits`
|
|
1484
|
-
* repository, relative to its root.
|
|
1485
|
-
*
|
|
1486
|
-
* Off-chain verification needs the verification key and nothing else — no
|
|
1487
|
-
* circuit bytecode, no witness. The key is public by nature: it is what anyone
|
|
1488
|
-
* checking a proof must already have, and the Solidity verifiers deployed
|
|
1489
|
-
* on-chain embed the same key.
|
|
1490
|
-
*
|
|
1491
|
-
* Exhaustive over {@link CircuitId} on purpose. A new circuit whose key path is
|
|
1492
|
-
* not listed here fails to compile rather than failing at verification time,
|
|
1493
|
-
* where the only symptom would be a proof that cannot be checked.
|
|
1494
|
-
*/
|
|
1495
|
-
const CIRCUIT_VK_PATHS = Object.freeze({
|
|
1496
|
-
coinbase_attestation: 'coinbase-attestation/target/vk',
|
|
1497
|
-
coinbase_country_attestation: 'coinbase-country-attestation/target/vk',
|
|
1498
|
-
oidc_domain_attestation: 'oidc-domain-attestation/target/vk',
|
|
1499
|
-
giwa_attestation: 'giwa-attestation/target/vk',
|
|
1500
|
-
mdl_kr_ownership: 'mdl/kr-ownership/target/vk',
|
|
1501
|
-
mdl_kr_age: 'mdl/kr-age/target/vk',
|
|
1502
|
-
mdl_kr_region: 'mdl/kr-region/target/vk',
|
|
1503
|
-
});
|
|
1504
|
-
/**
|
|
1505
|
-
* Every canonical circuit identifier, in declaration order.
|
|
1506
|
-
*
|
|
1507
|
-
* Includes circuits that are still `planned`. Use {@link SUPPORTED_CIRCUIT_IDS}
|
|
1508
|
-
* when you only want the ones that are officially supported today.
|
|
1509
|
-
*/
|
|
1510
|
-
const ALL_CIRCUIT_IDS = Object.freeze(Object.values(CIRCUIT_IDS));
|
|
1511
|
-
/**
|
|
1512
|
-
* Circuits that are officially supported today: the two Coinbase circuits and
|
|
1513
|
-
* OIDC domain attestation.
|
|
1514
|
-
*
|
|
1515
|
-
* Derived from {@link CIRCUIT_SUPPORT_STATUS}, so it cannot fall out of sync.
|
|
1516
|
-
*/
|
|
1517
|
-
const SUPPORTED_CIRCUIT_IDS = Object.freeze(ALL_CIRCUIT_IDS.filter((id) => CIRCUIT_SUPPORT_STATUS[id] === 'supported'));
|
|
1518
|
-
/**
|
|
1519
|
-
* Circuits whose identifiers are reserved but which are not officially
|
|
1520
|
-
* supported yet: GIWA attestation and the three Korea Mobile ID circuits.
|
|
1521
|
-
*
|
|
1522
|
-
* Derived from {@link CIRCUIT_SUPPORT_STATUS}, so it cannot fall out of sync.
|
|
1523
|
-
*/
|
|
1524
|
-
const PLANNED_CIRCUIT_IDS = Object.freeze(ALL_CIRCUIT_IDS.filter((id) => CIRCUIT_SUPPORT_STATUS[id] === 'planned'));
|
|
1525
|
-
/**
|
|
1526
|
-
* Narrows an unknown value to a canonical circuit identifier.
|
|
1527
|
-
*
|
|
1528
|
-
* Returns `false` for hyphenated route names such as `'coinbase-kyc'`, which
|
|
1529
|
-
* are not circuit IDs.
|
|
1530
|
-
*
|
|
1531
|
-
* @example
|
|
1532
|
-
* ```typescript
|
|
1533
|
-
* isCircuitId('coinbase_attestation'); // true
|
|
1534
|
-
* isCircuitId('coinbase-kyc'); // false
|
|
1535
|
-
* ```
|
|
1536
|
-
*/
|
|
1537
|
-
function isCircuitId(value) {
|
|
1538
|
-
return typeof value === 'string' && ALL_CIRCUIT_IDS.includes(value);
|
|
1539
|
-
}
|
|
1540
|
-
/**
|
|
1541
|
-
* Narrows an unknown value to a circuit identifier that is officially supported
|
|
1542
|
-
* today. Planned circuits return `false`.
|
|
1543
|
-
*
|
|
1544
|
-
* @example
|
|
1545
|
-
* ```typescript
|
|
1546
|
-
* isSupportedCircuitId('oidc_domain_attestation'); // true
|
|
1547
|
-
* isSupportedCircuitId('mdl_kr_age'); // false — planned
|
|
1548
|
-
* ```
|
|
1549
|
-
*/
|
|
1550
|
-
function isSupportedCircuitId(value) {
|
|
1551
|
-
return isCircuitId(value) && CIRCUIT_SUPPORT_STATUS[value] === 'supported';
|
|
1552
|
-
}
|
|
1553
|
-
/**
|
|
1554
|
-
* Returns the support status of a circuit.
|
|
1555
|
-
*
|
|
1556
|
-
* @param circuit - A canonical circuit identifier.
|
|
1557
|
-
* @throws Error when the value is not a canonical circuit identifier.
|
|
1558
|
-
*
|
|
1559
|
-
* @example
|
|
1560
|
-
* ```typescript
|
|
1561
|
-
* getCircuitSupportStatus('giwa_attestation'); // 'planned'
|
|
1562
|
-
* ```
|
|
1563
|
-
*/
|
|
1564
|
-
function getCircuitSupportStatus(circuit) {
|
|
1565
|
-
if (!isCircuitId(circuit)) {
|
|
1566
|
-
throw new Error(`Unknown circuit '${String(circuit)}'. Expected one of: ${ALL_CIRCUIT_IDS.join(', ')}`);
|
|
1567
|
-
}
|
|
1568
|
-
return CIRCUIT_SUPPORT_STATUS[circuit];
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
1740
|
/**
|
|
1572
1741
|
* Off-chain proof verification — checking a proof against its verification key,
|
|
1573
1742
|
* with no chain, no RPC, and no gas.
|
|
@@ -2765,11 +2934,83 @@ class ProofportSDK {
|
|
|
2765
2934
|
}
|
|
2766
2935
|
return await response.json();
|
|
2767
2936
|
}
|
|
2937
|
+
/**
|
|
2938
|
+
* Creates a proof request through the relay server.
|
|
2939
|
+
*
|
|
2940
|
+
* This is the recommended way to create proof requests. The relay server:
|
|
2941
|
+
* - Issues a server-side requestId (validated by the mobile app)
|
|
2942
|
+
* - Tracks request status in Redis
|
|
2943
|
+
* - Builds the deep link with relay callback URL
|
|
2944
|
+
* - Stores inputs hash for deep link integrity verification
|
|
2945
|
+
*
|
|
2946
|
+
* @param circuit - Circuit type identifier
|
|
2947
|
+
* @param inputs - Circuit-specific inputs
|
|
2948
|
+
* @param options - Request options (message, dappName, dappIcon, nonce, returnScheme)
|
|
2949
|
+
* @param options.returnScheme - Which app the ZKProofport app should switch back
|
|
2950
|
+
* to once it has finished. Put **your own** deep link scheme here — the bare
|
|
2951
|
+
* scheme your app registers, e.g. `'mydapp://'`. Only a native app has an
|
|
2952
|
+
* answer to this question; a web app omits the option entirely, because
|
|
2953
|
+
* opening its https origin would launch a new browser tab on a fresh page
|
|
2954
|
+
* rather than return the user to the tab they started from.
|
|
2955
|
+
*
|
|
2956
|
+
* This is intentionally **not** a URL and not a return address: no path, query
|
|
2957
|
+
* string or fragment is accepted, and the ZKProofport app never sends the proof
|
|
2958
|
+
* there. The proof still comes back to you the same way it always did — over the
|
|
2959
|
+
* relay, via `waitForProof()` / `waitForResult()`. `returnScheme` only answers
|
|
2960
|
+
* "which app should come back to the foreground", exactly like the
|
|
2961
|
+
* `redirect.native` value a wallet uses to hand control back after signing.
|
|
2962
|
+
*
|
|
2963
|
+
* **Omit it and the SDK decides for you.** When the request is created from a
|
|
2964
|
+
* page running in Chrome for iOS (`CriOS`) or Firefox for iOS (`FxiOS`), the
|
|
2965
|
+
* SDK fills in `'googlechrome://'` or `'firefox://'` by itself: opening either
|
|
2966
|
+
* scheme bare brings that browser forward without navigating, so the user lands
|
|
2967
|
+
* back on the tab they were already reading. Every other browser sends nothing,
|
|
2968
|
+
* because nothing safe exists to send — Safari has no such scheme, and Brave
|
|
2969
|
+
* and Arc are indistinguishable from Safari on iOS, so guessing would eject the
|
|
2970
|
+
* user into the wrong browser.
|
|
2971
|
+
* On Android nothing is needed at all: the ZKProofport app brings itself to the
|
|
2972
|
+
* back and the browser resumes exactly as it was. When no target is sent and
|
|
2973
|
+
* none can be inferred, the ZKProofport app tells the user the proof was
|
|
2974
|
+
* delivered and lets them switch back themselves.
|
|
2975
|
+
*
|
|
2976
|
+
* There is no auto-switch on proof failure; only on success and on the user
|
|
2977
|
+
* declining the request.
|
|
2978
|
+
*
|
|
2979
|
+
* Rejected with an error before the request is sent: empty or whitespace values,
|
|
2980
|
+
* anything longer than 128 characters, values carrying a host/path/query/fragment
|
|
2981
|
+
* (including https origins such as `'https://myapp.com'`), and the `http:`,
|
|
2982
|
+
* `file:`, `data:`, `javascript:`, `intent:`, `tel:`, `sms:`, `mailto:` families.
|
|
2983
|
+
* @returns Promise resolving to RelayProofRequest with requestId, deepLink, pollUrl
|
|
2984
|
+
* @throws Error if signer not set, returnScheme is malformed, or the relay request fails
|
|
2985
|
+
*
|
|
2986
|
+
* @example
|
|
2987
|
+
* ```typescript
|
|
2988
|
+
* const sdk = ProofportSDK.create();
|
|
2989
|
+
* sdk.setSigner(signer);
|
|
2990
|
+
*
|
|
2991
|
+
* const relay = await sdk.createRelayRequest('coinbase_attestation', {
|
|
2992
|
+
* scope: 'myapp.com'
|
|
2993
|
+
* }, { dappName: 'My DApp', returnScheme: 'mydapp://' });
|
|
2994
|
+
*
|
|
2995
|
+
* // Generate QR code from relay deep link
|
|
2996
|
+
* const qr = await sdk.generateQRCode(relay.deepLink);
|
|
2997
|
+
*
|
|
2998
|
+
* // Wait for proof (WebSocket primary, polling fallback)
|
|
2999
|
+
* const result = await sdk.waitForProof(relay.requestId);
|
|
3000
|
+
* ```
|
|
3001
|
+
*/
|
|
3002
|
+
// Which circuits need a wallet signature, from the one record in
|
|
3003
|
+
// src/circuits.ts. The typed-out pair that used to sit here had missed
|
|
3004
|
+
// arc_eligibility, so a dapp calling createRelayRequest() for it with no
|
|
3005
|
+
// signer got no local error and a relay 401 two round trips later.
|
|
3006
|
+
static needsWalletSignature(circuit) {
|
|
3007
|
+
return CIRCUIT_NEEDS_WALLET_SIGNATURE[circuit] === true;
|
|
3008
|
+
}
|
|
2768
3009
|
async createRelayRequest(circuit, inputs, options = {}) {
|
|
2769
3010
|
if (!this.relayUrl) {
|
|
2770
3011
|
throw new Error('relayUrl is required. Set it in ProofportSDK config.');
|
|
2771
3012
|
}
|
|
2772
|
-
const needsSignature = ProofportSDK.
|
|
3013
|
+
const needsSignature = ProofportSDK.needsWalletSignature(circuit);
|
|
2773
3014
|
if (needsSignature && !this.signer) {
|
|
2774
3015
|
throw new Error('Signer not set. Call setSigner() first. Wallet signature is required for this circuit.');
|
|
2775
3016
|
}
|
|
@@ -2781,6 +3022,17 @@ class ProofportSDK {
|
|
|
2781
3022
|
throw new Error(`${mdlError} (circuit: ${circuit})`);
|
|
2782
3023
|
}
|
|
2783
3024
|
}
|
|
3025
|
+
// arc_eligibility: the action is what the proof binds to, so a request
|
|
3026
|
+
// without a usable one cannot be honoured. Checked here, before a
|
|
3027
|
+
// challenge is burned, because the alternative is a refusal from inside
|
|
3028
|
+
// the mobile app -- a screen the dapp developer never sees, reached after
|
|
3029
|
+
// the person has already scanned a QR code.
|
|
3030
|
+
if (circuit === 'arc_eligibility') {
|
|
3031
|
+
const actionError = validateTypedAction(inputs.action);
|
|
3032
|
+
if (actionError) {
|
|
3033
|
+
throw new Error(`${actionError} (circuit: ${circuit})`);
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
2784
3036
|
// Return target: validated before we burn a challenge, so a typo surfaces
|
|
2785
3037
|
// as a local error instead of a relay 400 on the second round trip.
|
|
2786
3038
|
if (options.returnScheme !== undefined) {
|
|
@@ -3105,76 +3357,6 @@ class ProofportSDK {
|
|
|
3105
3357
|
return extractDomainFromPublicInputs(publicInputs, circuit);
|
|
3106
3358
|
}
|
|
3107
3359
|
}
|
|
3108
|
-
/**
|
|
3109
|
-
* Creates a proof request through the relay server.
|
|
3110
|
-
*
|
|
3111
|
-
* This is the recommended way to create proof requests. The relay server:
|
|
3112
|
-
* - Issues a server-side requestId (validated by the mobile app)
|
|
3113
|
-
* - Tracks request status in Redis
|
|
3114
|
-
* - Builds the deep link with relay callback URL
|
|
3115
|
-
* - Stores inputs hash for deep link integrity verification
|
|
3116
|
-
*
|
|
3117
|
-
* @param circuit - Circuit type identifier
|
|
3118
|
-
* @param inputs - Circuit-specific inputs
|
|
3119
|
-
* @param options - Request options (message, dappName, dappIcon, nonce, returnScheme)
|
|
3120
|
-
* @param options.returnScheme - Which app the ZKProofport app should switch back
|
|
3121
|
-
* to once it has finished. Put **your own** deep link scheme here — the bare
|
|
3122
|
-
* scheme your app registers, e.g. `'mydapp://'`. Only a native app has an
|
|
3123
|
-
* answer to this question; a web app omits the option entirely, because
|
|
3124
|
-
* opening its https origin would launch a new browser tab on a fresh page
|
|
3125
|
-
* rather than return the user to the tab they started from.
|
|
3126
|
-
*
|
|
3127
|
-
* This is intentionally **not** a URL and not a return address: no path, query
|
|
3128
|
-
* string or fragment is accepted, and the ZKProofport app never sends the proof
|
|
3129
|
-
* there. The proof still comes back to you the same way it always did — over the
|
|
3130
|
-
* relay, via `waitForProof()` / `waitForResult()`. `returnScheme` only answers
|
|
3131
|
-
* "which app should come back to the foreground", exactly like the
|
|
3132
|
-
* `redirect.native` value a wallet uses to hand control back after signing.
|
|
3133
|
-
*
|
|
3134
|
-
* **Omit it and the SDK decides for you.** When the request is created from a
|
|
3135
|
-
* page running in Chrome for iOS (`CriOS`) or Firefox for iOS (`FxiOS`), the
|
|
3136
|
-
* SDK fills in `'googlechrome://'` or `'firefox://'` by itself: opening either
|
|
3137
|
-
* scheme bare brings that browser forward without navigating, so the user lands
|
|
3138
|
-
* back on the tab they were already reading. Every other browser sends nothing,
|
|
3139
|
-
* because nothing safe exists to send — Safari has no such scheme, and Brave
|
|
3140
|
-
* and Arc are indistinguishable from Safari on iOS, so guessing would eject the
|
|
3141
|
-
* user into the wrong browser.
|
|
3142
|
-
* On Android nothing is needed at all: the ZKProofport app brings itself to the
|
|
3143
|
-
* back and the browser resumes exactly as it was. When no target is sent and
|
|
3144
|
-
* none can be inferred, the ZKProofport app tells the user the proof was
|
|
3145
|
-
* delivered and lets them switch back themselves.
|
|
3146
|
-
*
|
|
3147
|
-
* There is no auto-switch on proof failure; only on success and on the user
|
|
3148
|
-
* declining the request.
|
|
3149
|
-
*
|
|
3150
|
-
* Rejected with an error before the request is sent: empty or whitespace values,
|
|
3151
|
-
* anything longer than 128 characters, values carrying a host/path/query/fragment
|
|
3152
|
-
* (including https origins such as `'https://myapp.com'`), and the `http:`,
|
|
3153
|
-
* `file:`, `data:`, `javascript:`, `intent:`, `tel:`, `sms:`, `mailto:` families.
|
|
3154
|
-
* @returns Promise resolving to RelayProofRequest with requestId, deepLink, pollUrl
|
|
3155
|
-
* @throws Error if signer not set, returnScheme is malformed, or the relay request fails
|
|
3156
|
-
*
|
|
3157
|
-
* @example
|
|
3158
|
-
* ```typescript
|
|
3159
|
-
* const sdk = ProofportSDK.create();
|
|
3160
|
-
* sdk.setSigner(signer);
|
|
3161
|
-
*
|
|
3162
|
-
* const relay = await sdk.createRelayRequest('coinbase_attestation', {
|
|
3163
|
-
* scope: 'myapp.com'
|
|
3164
|
-
* }, { dappName: 'My DApp', returnScheme: 'mydapp://' });
|
|
3165
|
-
*
|
|
3166
|
-
* // Generate QR code from relay deep link
|
|
3167
|
-
* const qr = await sdk.generateQRCode(relay.deepLink);
|
|
3168
|
-
*
|
|
3169
|
-
* // Wait for proof (WebSocket primary, polling fallback)
|
|
3170
|
-
* const result = await sdk.waitForProof(relay.requestId);
|
|
3171
|
-
* ```
|
|
3172
|
-
*/
|
|
3173
|
-
// Circuits that require wallet signature (used as circuit input)
|
|
3174
|
-
ProofportSDK.WALLET_SIGNATURE_CIRCUITS = [
|
|
3175
|
-
'coinbase_attestation',
|
|
3176
|
-
'coinbase_country_attestation',
|
|
3177
|
-
];
|
|
3178
3360
|
|
|
3179
3361
|
exports.ALL_CIRCUIT_IDS = ALL_CIRCUIT_IDS;
|
|
3180
3362
|
exports.CIRCUIT_IDS = CIRCUIT_IDS;
|
|
@@ -3182,6 +3364,7 @@ exports.CIRCUIT_SUPPORT_STATUS = CIRCUIT_SUPPORT_STATUS;
|
|
|
3182
3364
|
exports.CIRCUIT_VK_PATHS = CIRCUIT_VK_PATHS;
|
|
3183
3365
|
exports.COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT = COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT;
|
|
3184
3366
|
exports.COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT = COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT;
|
|
3367
|
+
exports.EXPERIMENTAL_CIRCUIT_IDS = EXPERIMENTAL_CIRCUIT_IDS;
|
|
3185
3368
|
exports.MDL_KR_PUBLIC_INPUT_LAYOUT = MDL_KR_PUBLIC_INPUT_LAYOUT;
|
|
3186
3369
|
exports.OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT = OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT;
|
|
3187
3370
|
exports.PLANNED_CIRCUIT_IDS = PLANNED_CIRCUIT_IDS;
|
|
@@ -3194,5 +3377,7 @@ exports.extractScopeFromPublicInputs = extractScopeFromPublicInputs;
|
|
|
3194
3377
|
exports.getCircuitSupportStatus = getCircuitSupportStatus;
|
|
3195
3378
|
exports.isCircuitId = isCircuitId;
|
|
3196
3379
|
exports.isSupportedCircuitId = isSupportedCircuitId;
|
|
3380
|
+
exports.parseTypedAction = parseTypedAction;
|
|
3381
|
+
exports.validateTypedAction = validateTypedAction;
|
|
3197
3382
|
exports.verifyProofOffChain = verifyProofOffChain;
|
|
3198
3383
|
//# sourceMappingURL=index.js.map
|