@zkpassport/sdk 0.3.4 → 0.4.1
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/cjs/assets/abi/ZKPassportVerifier.json +56 -14
- package/dist/cjs/index.d.ts +19 -7
- package/dist/cjs/index.js +192 -104
- package/dist/esm/assets/abi/ZKPassportVerifier.json +56 -14
- package/dist/esm/index.d.ts +19 -7
- package/dist/esm/index.js +193 -105
- package/package.json +3 -2
- package/src/assets/abi/ZKPassportVerifier.json +56 -14
- package/src/index.ts +221 -120
- package/src/encryption.ts +0 -45
- package/src/json-rpc.ts +0 -61
- package/src/mobile.ts +0 -186
- package/src/websocket.ts +0 -16
package/src/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomBytes } from "crypto"
|
|
2
1
|
import { Alpha3Code, getAlpha3Code, registerLocale } from "i18n-iso-countries"
|
|
3
2
|
import {
|
|
4
3
|
type DisclosableIDCredential,
|
|
@@ -57,11 +56,14 @@ import {
|
|
|
57
56
|
ProofData,
|
|
58
57
|
getScopeFromOuterProof,
|
|
59
58
|
getSubscopeFromOuterProof,
|
|
59
|
+
getServiceScopeHash,
|
|
60
|
+
BoundData,
|
|
61
|
+
BindCommittedInputs,
|
|
62
|
+
getBindEVMParameterCommitment,
|
|
63
|
+
getBindParameterCommitment,
|
|
64
|
+
formatBoundData,
|
|
60
65
|
} from "@zkpassport/utils"
|
|
61
66
|
import { bytesToHex } from "@noble/ciphers/utils"
|
|
62
|
-
import { getWebSocketClient, WebSocketClient } from "./websocket"
|
|
63
|
-
import { createEncryptedJsonRpcRequest } from "./json-rpc"
|
|
64
|
-
import { decrypt, generateECDHKeyPair, getSharedSecret } from "./encryption"
|
|
65
67
|
import { noLogger as logger } from "./logger"
|
|
66
68
|
import { inflate } from "pako"
|
|
67
69
|
import i18en from "i18n-iso-countries/langs/en.json"
|
|
@@ -70,6 +72,7 @@ import { sha256 } from "@noble/hashes/sha2"
|
|
|
70
72
|
import { hexToBytes } from "@noble/hashes/utils"
|
|
71
73
|
import ZKPassportVerifierAbi from "./assets/abi/ZKPassportVerifier.json"
|
|
72
74
|
import { RegistryClient } from "@zkpassport/registry"
|
|
75
|
+
import { Bridge, BridgeInterface } from "@obsidion/bridge"
|
|
73
76
|
|
|
74
77
|
const DEFAULT_DATE_VALUE = new Date(1111, 10, 11)
|
|
75
78
|
|
|
@@ -94,7 +97,8 @@ export type QueryResultErrors = {
|
|
|
94
97
|
| "sig_check_id_data"
|
|
95
98
|
| "data_check_integrity"
|
|
96
99
|
| "outer"
|
|
97
|
-
| "disclose"
|
|
100
|
+
| "disclose"
|
|
101
|
+
| "bind"]: {
|
|
98
102
|
disclose?: QueryResultError<string | number | Date>
|
|
99
103
|
gte?: QueryResultError<number | Date>
|
|
100
104
|
lte?: QueryResultError<number | Date>
|
|
@@ -124,6 +128,24 @@ export type SolidityVerifierParameters = {
|
|
|
124
128
|
|
|
125
129
|
export type EVMChain = "ethereum_sepolia" | "local_anvil"
|
|
126
130
|
|
|
131
|
+
function getChainIdFromEVMChain(chain: EVMChain): number {
|
|
132
|
+
if (chain === "ethereum_sepolia") {
|
|
133
|
+
return 11155111
|
|
134
|
+
} else if (chain === "local_anvil") {
|
|
135
|
+
return 31337
|
|
136
|
+
}
|
|
137
|
+
throw new Error(`Unsupported chain: ${chain}`)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getEVMChainFromChainId(chainId: number): EVMChain {
|
|
141
|
+
if (chainId === 11155111) {
|
|
142
|
+
return "ethereum_sepolia"
|
|
143
|
+
} else if (chainId === 31337) {
|
|
144
|
+
return "local_anvil"
|
|
145
|
+
}
|
|
146
|
+
throw new Error(`Unsupported chain ID: ${chainId}`)
|
|
147
|
+
}
|
|
148
|
+
|
|
127
149
|
registerLocale(i18en)
|
|
128
150
|
|
|
129
151
|
function hasRequestedAccessToField(credentialsRequest: Query, field: IDCredential): boolean {
|
|
@@ -330,6 +352,12 @@ export type QueryBuilder = {
|
|
|
330
352
|
* @param key The attribute to disclose.
|
|
331
353
|
*/
|
|
332
354
|
disclose: (key: DisclosableIDCredential) => QueryBuilder
|
|
355
|
+
/**
|
|
356
|
+
* Binds a value to the request.
|
|
357
|
+
* @param key The key of the value to bind.
|
|
358
|
+
* @param value The value to bind the request to.
|
|
359
|
+
*/
|
|
360
|
+
bind: (key: keyof BoundData, value: BoundData[keyof BoundData]) => QueryBuilder
|
|
333
361
|
/**
|
|
334
362
|
* Builds the request.
|
|
335
363
|
*
|
|
@@ -351,13 +379,12 @@ export class ZKPassport {
|
|
|
351
379
|
devMode: boolean
|
|
352
380
|
}
|
|
353
381
|
> = {}
|
|
354
|
-
private
|
|
355
|
-
private
|
|
356
|
-
private topicToSharedSecret: Record<string, Uint8Array> = {}
|
|
382
|
+
private topicToPublicKey: Record<string, string> = {}
|
|
383
|
+
private topicToBridge: Record<string, BridgeInterface> = {}
|
|
357
384
|
private topicToRequestReceived: Record<string, boolean> = {}
|
|
358
385
|
private topicToService: Record<
|
|
359
386
|
string,
|
|
360
|
-
{ name: string; logo: string; purpose: string; scope?: string }
|
|
387
|
+
{ name: string; logo: string; purpose: string; scope?: string; chainId?: number }
|
|
361
388
|
> = {}
|
|
362
389
|
private topicToProofs: Record<string, Array<ProofResult>> = {}
|
|
363
390
|
private topicToExpectedProofCount: Record<string, number> = {}
|
|
@@ -400,6 +427,9 @@ export class ZKPassport {
|
|
|
400
427
|
queryResult: result,
|
|
401
428
|
validity: this.topicToLocalConfig[topic]?.validity,
|
|
402
429
|
scope: this.topicToService[topic]?.scope,
|
|
430
|
+
evmChain: this.topicToService[topic]?.chainId
|
|
431
|
+
? getEVMChainFromChainId(this.topicToService[topic]?.chainId)
|
|
432
|
+
: undefined,
|
|
403
433
|
devMode: this.topicToLocalConfig[topic]?.devMode,
|
|
404
434
|
})
|
|
405
435
|
delete this.topicToProofs[topic]
|
|
@@ -485,6 +515,9 @@ export class ZKPassport {
|
|
|
485
515
|
}
|
|
486
516
|
}
|
|
487
517
|
}
|
|
518
|
+
if ((this.topicToConfig[topic] as Query).bind) {
|
|
519
|
+
neededCircuits.push("bind")
|
|
520
|
+
}
|
|
488
521
|
// From the circuits needed, determine the expected proof count
|
|
489
522
|
// There are at least 4 proofs, 3 base proofs and 1 disclosure proof minimum
|
|
490
523
|
// Each separate needed circuit adds 1 disclosure proof
|
|
@@ -498,11 +531,7 @@ export class ZKPassport {
|
|
|
498
531
|
* @param request The request.
|
|
499
532
|
* @param outerRequest The outer request.
|
|
500
533
|
*/
|
|
501
|
-
private async handleEncryptedMessage(
|
|
502
|
-
topic: string,
|
|
503
|
-
request: JsonRpcRequest,
|
|
504
|
-
outerRequest: JsonRpcRequest,
|
|
505
|
-
) {
|
|
534
|
+
private async handleEncryptedMessage(topic: string, request: JsonRpcRequest) {
|
|
506
535
|
logger.debug("Received encrypted message:", request)
|
|
507
536
|
if (request.method === "accept") {
|
|
508
537
|
logger.debug(`User accepted the request and is generating a proof`)
|
|
@@ -512,31 +541,9 @@ export class ZKPassport {
|
|
|
512
541
|
await Promise.all(this.onRejectCallbacks[topic].map((callback) => callback()))
|
|
513
542
|
} else if (request.method === "proof") {
|
|
514
543
|
logger.debug(`User generated proof`)
|
|
515
|
-
|
|
516
|
-
const bytesProof = Buffer.from(request.params.proof, "base64")
|
|
517
|
-
const bytesCommittedInputs = request.params.committedInputs
|
|
518
|
-
? Buffer.from(request.params.committedInputs, "base64")
|
|
519
|
-
: null
|
|
520
|
-
const uncompressedProof = inflate(bytesProof)
|
|
521
|
-
const uncompressedCommittedInputs = bytesCommittedInputs
|
|
522
|
-
? inflate(bytesCommittedInputs)
|
|
523
|
-
: null
|
|
524
|
-
// The gzip lib in the app compress the proof as ASCII
|
|
525
|
-
// and since the app passes the proof as a hex string, we can
|
|
526
|
-
// just decode the bytes as hex characters using the TextDecoder
|
|
527
|
-
const hexProof = new TextDecoder().decode(uncompressedProof)
|
|
528
|
-
const processedProof: ProofResult = {
|
|
529
|
-
proof: hexProof,
|
|
530
|
-
vkeyHash: request.params.vkeyHash,
|
|
531
|
-
name: request.params.name,
|
|
532
|
-
version: request.params.version,
|
|
533
|
-
committedInputs: uncompressedCommittedInputs
|
|
534
|
-
? JSON.parse(new TextDecoder().decode(uncompressedCommittedInputs))
|
|
535
|
-
: undefined,
|
|
536
|
-
}
|
|
537
|
-
this.topicToProofs[topic].push(processedProof)
|
|
544
|
+
this.topicToProofs[topic].push(request.params)
|
|
538
545
|
await Promise.all(
|
|
539
|
-
this.onProofGeneratedCallbacks[topic].map((callback) => callback(
|
|
546
|
+
this.onProofGeneratedCallbacks[topic].map((callback) => callback(request.params)),
|
|
540
547
|
)
|
|
541
548
|
// If the results were received before all the proofs were generated,
|
|
542
549
|
// we can handle the result now
|
|
@@ -644,6 +651,13 @@ export class ZKPassport {
|
|
|
644
651
|
}
|
|
645
652
|
return this.getZkPassportRequest(topic)
|
|
646
653
|
},
|
|
654
|
+
bind: (key: keyof BoundData, value: BoundData[keyof BoundData]) => {
|
|
655
|
+
this.topicToConfig[topic].bind = {
|
|
656
|
+
...this.topicToConfig[topic].bind,
|
|
657
|
+
[key]: value,
|
|
658
|
+
}
|
|
659
|
+
return this.getZkPassportRequest(topic)
|
|
660
|
+
},
|
|
647
661
|
done: () => {
|
|
648
662
|
const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[topic])).toString(
|
|
649
663
|
"base64",
|
|
@@ -651,7 +665,7 @@ export class ZKPassport {
|
|
|
651
665
|
const base64Service = Buffer.from(JSON.stringify(this.topicToService[topic])).toString(
|
|
652
666
|
"base64",
|
|
653
667
|
)
|
|
654
|
-
const pubkey =
|
|
668
|
+
const pubkey = this.topicToPublicKey[topic]
|
|
655
669
|
this.setExpectedProofCount(topic)
|
|
656
670
|
return {
|
|
657
671
|
url: `https://zkpassport.id/r?d=${this.domain}&t=${topic}&c=${base64Config}&s=${base64Service}&p=${pubkey}&m=${this.topicToLocalConfig[topic].mode}`,
|
|
@@ -675,7 +689,7 @@ export class ZKPassport {
|
|
|
675
689
|
onReject: (callback: () => void) => this.onRejectCallbacks[topic].push(callback),
|
|
676
690
|
onError: (callback: (error: string) => void) =>
|
|
677
691
|
this.onErrorCallbacks[topic].push(callback),
|
|
678
|
-
isBridgeConnected: () => this.
|
|
692
|
+
isBridgeConnected: () => this.topicToBridge[topic].isBridgeConnected(),
|
|
679
693
|
requestReceived: () => this.topicToRequestReceived[topic] === true,
|
|
680
694
|
}
|
|
681
695
|
},
|
|
@@ -690,6 +704,7 @@ export class ZKPassport {
|
|
|
690
704
|
* @param scope Scope this request to a specific use case
|
|
691
705
|
* @param validity How many days ago should have the ID been last scanned by the user?
|
|
692
706
|
* @param devMode Whether to enable dev mode. This will allow you to verify mock proofs (i.e. from ZKR)
|
|
707
|
+
* @param evmChain The EVM chain to use for the request (if using the proof onchain)
|
|
693
708
|
* @returns The query builder object.
|
|
694
709
|
*/
|
|
695
710
|
public async request({
|
|
@@ -698,6 +713,7 @@ export class ZKPassport {
|
|
|
698
713
|
purpose,
|
|
699
714
|
scope,
|
|
700
715
|
mode,
|
|
716
|
+
evmChain,
|
|
701
717
|
validity,
|
|
702
718
|
devMode,
|
|
703
719
|
topicOverride,
|
|
@@ -708,21 +724,27 @@ export class ZKPassport {
|
|
|
708
724
|
purpose: string
|
|
709
725
|
scope?: string
|
|
710
726
|
mode?: ProofMode
|
|
727
|
+
evmChain?: EVMChain
|
|
711
728
|
validity?: number
|
|
712
729
|
devMode?: boolean
|
|
713
730
|
topicOverride?: string
|
|
714
731
|
keyPairOverride?: { privateKey: Uint8Array; publicKey: Uint8Array }
|
|
715
732
|
}): Promise<QueryBuilder> {
|
|
716
|
-
const
|
|
733
|
+
const bridge = await Bridge.create({
|
|
734
|
+
keyPair: keyPairOverride,
|
|
735
|
+
bridgeId: topicOverride,
|
|
736
|
+
})
|
|
717
737
|
|
|
718
|
-
const
|
|
719
|
-
this.topicToKeyPair[topic] = {
|
|
720
|
-
privateKey: keyPair.privateKey,
|
|
721
|
-
publicKey: keyPair.publicKey,
|
|
722
|
-
}
|
|
738
|
+
const topic = bridge.connection.getBridgeId()
|
|
723
739
|
|
|
724
740
|
this.topicToConfig[topic] = {}
|
|
725
|
-
this.topicToService[topic] = {
|
|
741
|
+
this.topicToService[topic] = {
|
|
742
|
+
name,
|
|
743
|
+
logo,
|
|
744
|
+
purpose,
|
|
745
|
+
scope,
|
|
746
|
+
chainId: evmChain ? getChainIdFromEVMChain(evmChain) : undefined,
|
|
747
|
+
}
|
|
726
748
|
this.topicToProofs[topic] = []
|
|
727
749
|
this.topicToExpectedProofCount[topic] = 0
|
|
728
750
|
this.topicToLocalConfig[topic] = {
|
|
@@ -740,68 +762,22 @@ export class ZKPassport {
|
|
|
740
762
|
this.onRejectCallbacks[topic] = []
|
|
741
763
|
this.onErrorCallbacks[topic] = []
|
|
742
764
|
|
|
743
|
-
|
|
744
|
-
this.topicToWebSocketClient[topic] = wsClient
|
|
745
|
-
wsClient.onopen = async () => {
|
|
746
|
-
logger.info("[frontend] WebSocket connection established")
|
|
747
|
-
await Promise.all(this.onBridgeConnectCallbacks[topic].map((callback) => callback()))
|
|
748
|
-
}
|
|
749
|
-
wsClient.addEventListener("message", async (event: any) => {
|
|
750
|
-
logger.debug("[frontend] Received message:", event.data)
|
|
751
|
-
try {
|
|
752
|
-
const data: JsonRpcRequest = JSON.parse(event.data)
|
|
753
|
-
// Handshake happens when the mobile app scans the QR code and connects to the bridge
|
|
754
|
-
if (data.method === "handshake") {
|
|
755
|
-
logger.debug("[frontend] Received handshake:", event.data)
|
|
756
|
-
|
|
757
|
-
this.topicToRequestReceived[topic] = true
|
|
758
|
-
this.topicToSharedSecret[topic] = await getSharedSecret(
|
|
759
|
-
bytesToHex(keyPair.privateKey),
|
|
760
|
-
data.params.pubkey,
|
|
761
|
-
)
|
|
762
|
-
logger.debug(
|
|
763
|
-
"[frontend] Shared secret:",
|
|
764
|
-
Buffer.from(this.topicToSharedSecret[topic]).toString("hex"),
|
|
765
|
-
)
|
|
766
|
-
|
|
767
|
-
const encryptedMessage = await createEncryptedJsonRpcRequest(
|
|
768
|
-
"hello",
|
|
769
|
-
null,
|
|
770
|
-
this.topicToSharedSecret[topic],
|
|
771
|
-
topic,
|
|
772
|
-
)
|
|
773
|
-
logger.debug("[frontend] Sending encrypted message:", encryptedMessage)
|
|
774
|
-
wsClient.send(JSON.stringify(encryptedMessage))
|
|
775
|
-
|
|
776
|
-
await Promise.all(this.onRequestReceivedCallbacks[topic].map((callback) => callback()))
|
|
777
|
-
return
|
|
778
|
-
}
|
|
765
|
+
this.topicToPublicKey[topic] = bridge.getPublicKey()
|
|
779
766
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
} catch (error) {
|
|
794
|
-
logger.error("[frontend] Error decrypting message:", error)
|
|
795
|
-
}
|
|
796
|
-
return
|
|
797
|
-
}
|
|
798
|
-
} catch (error) {
|
|
799
|
-
logger.error("[frontend] Error:", error)
|
|
800
|
-
}
|
|
767
|
+
this.topicToBridge[topic] = bridge
|
|
768
|
+
bridge.onConnect(async (reconnection: boolean) => {
|
|
769
|
+
logger.debug("Bridge connected")
|
|
770
|
+
logger.debug("Is reconnection:", reconnection)
|
|
771
|
+
await Promise.all(this.onBridgeConnectCallbacks[topic].map((callback) => callback()))
|
|
772
|
+
})
|
|
773
|
+
bridge.onSecureChannelEstablished(async () => {
|
|
774
|
+
logger.debug("Secure channel established")
|
|
775
|
+
await Promise.all(this.onRequestReceivedCallbacks[topic].map((callback) => callback()))
|
|
776
|
+
})
|
|
777
|
+
bridge.onSecureMessage(async (message: any) => {
|
|
778
|
+
logger.debug("Received message:", message)
|
|
779
|
+
this.handleEncryptedMessage(topic, message)
|
|
801
780
|
})
|
|
802
|
-
wsClient.onerror = (error: Event) => {
|
|
803
|
-
logger.error("[frontend] WebSocket error:", error)
|
|
804
|
-
}
|
|
805
781
|
return this.getZkPassportRequest(topic)
|
|
806
782
|
}
|
|
807
783
|
|
|
@@ -823,6 +799,7 @@ export class ZKPassport {
|
|
|
823
799
|
fullname: {},
|
|
824
800
|
document_number: {},
|
|
825
801
|
outer: {},
|
|
802
|
+
bind: {},
|
|
826
803
|
}
|
|
827
804
|
let isCorrect = true
|
|
828
805
|
// We can't be certain that the disclosed data is for a passport or an ID card
|
|
@@ -1184,6 +1161,7 @@ export class ZKPassport {
|
|
|
1184
1161
|
fullname: {},
|
|
1185
1162
|
document_number: {},
|
|
1186
1163
|
outer: {},
|
|
1164
|
+
bind: {},
|
|
1187
1165
|
}
|
|
1188
1166
|
let isCorrect = true
|
|
1189
1167
|
const currentTime = new Date()
|
|
@@ -1317,6 +1295,7 @@ export class ZKPassport {
|
|
|
1317
1295
|
fullname: {},
|
|
1318
1296
|
document_number: {},
|
|
1319
1297
|
outer: {},
|
|
1298
|
+
bind: {},
|
|
1320
1299
|
}
|
|
1321
1300
|
let isCorrect = true
|
|
1322
1301
|
const currentTime = new Date()
|
|
@@ -1445,6 +1424,7 @@ export class ZKPassport {
|
|
|
1445
1424
|
fullname: {},
|
|
1446
1425
|
document_number: {},
|
|
1447
1426
|
outer: {},
|
|
1427
|
+
bind: {},
|
|
1448
1428
|
}
|
|
1449
1429
|
let isCorrect = true
|
|
1450
1430
|
const currentTime = new Date()
|
|
@@ -1573,6 +1553,7 @@ export class ZKPassport {
|
|
|
1573
1553
|
fullname: {},
|
|
1574
1554
|
document_number: {},
|
|
1575
1555
|
outer: {},
|
|
1556
|
+
bind: {},
|
|
1576
1557
|
}
|
|
1577
1558
|
let isCorrect = true
|
|
1578
1559
|
if (
|
|
@@ -1637,6 +1618,7 @@ export class ZKPassport {
|
|
|
1637
1618
|
fullname: {},
|
|
1638
1619
|
document_number: {},
|
|
1639
1620
|
outer: {},
|
|
1621
|
+
bind: {},
|
|
1640
1622
|
}
|
|
1641
1623
|
let isCorrect = true
|
|
1642
1624
|
|
|
@@ -1699,6 +1681,7 @@ export class ZKPassport {
|
|
|
1699
1681
|
fullname: {},
|
|
1700
1682
|
document_number: {},
|
|
1701
1683
|
outer: {},
|
|
1684
|
+
bind: {},
|
|
1702
1685
|
}
|
|
1703
1686
|
let isCorrect = true
|
|
1704
1687
|
if (
|
|
@@ -1746,6 +1729,7 @@ export class ZKPassport {
|
|
|
1746
1729
|
fullname: {},
|
|
1747
1730
|
document_number: {},
|
|
1748
1731
|
outer: {},
|
|
1732
|
+
bind: {},
|
|
1749
1733
|
}
|
|
1750
1734
|
let isCorrect = true
|
|
1751
1735
|
|
|
@@ -1780,13 +1764,17 @@ export class ZKPassport {
|
|
|
1780
1764
|
queryResultErrors: QueryResultErrors,
|
|
1781
1765
|
key: string,
|
|
1782
1766
|
scope?: string,
|
|
1767
|
+
chainId?: number,
|
|
1783
1768
|
) {
|
|
1784
1769
|
let isCorrect = true
|
|
1785
|
-
if (
|
|
1770
|
+
if (
|
|
1771
|
+
this.domain &&
|
|
1772
|
+
getServiceScopeHash(this.domain, chainId) !== BigInt(proofData.publicInputs[1])
|
|
1773
|
+
) {
|
|
1786
1774
|
console.warn("The proof comes from a different domain than the one expected")
|
|
1787
1775
|
isCorrect = false
|
|
1788
1776
|
queryResultErrors[key as keyof QueryResultErrors].scope = {
|
|
1789
|
-
expected: `Scope: ${
|
|
1777
|
+
expected: `Scope: ${getServiceScopeHash(this.domain, chainId).toString()}`,
|
|
1790
1778
|
received: `Scope: ${BigInt(proofData.publicInputs[1]).toString()}`,
|
|
1791
1779
|
message: "The proof comes from a different domain than the one expected",
|
|
1792
1780
|
}
|
|
@@ -1835,11 +1823,63 @@ export class ZKPassport {
|
|
|
1835
1823
|
return { isCorrect, queryResultErrors }
|
|
1836
1824
|
}
|
|
1837
1825
|
|
|
1826
|
+
private checkBindPublicInputs(queryResult: QueryResult, boundData: BoundData) {
|
|
1827
|
+
const queryResultErrors: QueryResultErrors = {
|
|
1828
|
+
sig_check_dsc: {},
|
|
1829
|
+
sig_check_id_data: {},
|
|
1830
|
+
data_check_integrity: {},
|
|
1831
|
+
disclose: {},
|
|
1832
|
+
age: {},
|
|
1833
|
+
birthdate: {},
|
|
1834
|
+
expiry_date: {},
|
|
1835
|
+
document_type: {},
|
|
1836
|
+
issuing_country: {},
|
|
1837
|
+
gender: {},
|
|
1838
|
+
nationality: {},
|
|
1839
|
+
firstname: {},
|
|
1840
|
+
lastname: {},
|
|
1841
|
+
fullname: {},
|
|
1842
|
+
document_number: {},
|
|
1843
|
+
outer: {},
|
|
1844
|
+
bind: {},
|
|
1845
|
+
}
|
|
1846
|
+
let isCorrect = true
|
|
1847
|
+
|
|
1848
|
+
if (queryResult.bind) {
|
|
1849
|
+
if (
|
|
1850
|
+
queryResult.bind.user_address?.toLowerCase().replace("0x", "") !==
|
|
1851
|
+
boundData.user_address?.toLowerCase().replace("0x", "")
|
|
1852
|
+
) {
|
|
1853
|
+
console.warn("Bound user address does not match the one from the query results")
|
|
1854
|
+
isCorrect = false
|
|
1855
|
+
queryResultErrors.bind.eq = {
|
|
1856
|
+
expected: queryResult.bind.user_address,
|
|
1857
|
+
received: boundData.user_address,
|
|
1858
|
+
message: "Bound user address does not match the one from the query results",
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
if (
|
|
1862
|
+
queryResult.bind.custom_data?.trim().toLowerCase() !==
|
|
1863
|
+
boundData.custom_data?.trim().toLowerCase()
|
|
1864
|
+
) {
|
|
1865
|
+
console.warn("Bound custom data does not match the one from the query results")
|
|
1866
|
+
isCorrect = false
|
|
1867
|
+
queryResultErrors.bind.eq = {
|
|
1868
|
+
expected: queryResult.bind.custom_data,
|
|
1869
|
+
received: boundData.custom_data,
|
|
1870
|
+
message: "Bound custom data does not match the one from the query results",
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
return { isCorrect, queryResultErrors }
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1838
1877
|
private async checkPublicInputs(
|
|
1839
1878
|
proofs: Array<ProofResult>,
|
|
1840
1879
|
queryResult: QueryResult,
|
|
1841
1880
|
validity?: number,
|
|
1842
1881
|
scope?: string,
|
|
1882
|
+
chainId?: number,
|
|
1843
1883
|
) {
|
|
1844
1884
|
let commitmentIn: bigint | undefined
|
|
1845
1885
|
let commitmentOut: bigint | undefined
|
|
@@ -1872,6 +1912,7 @@ export class ZKPassport {
|
|
|
1872
1912
|
fullname: {},
|
|
1873
1913
|
document_number: {},
|
|
1874
1914
|
outer: {},
|
|
1915
|
+
bind: {},
|
|
1875
1916
|
}
|
|
1876
1917
|
|
|
1877
1918
|
// Since the order is important for the commitments, we need to sort the proofs
|
|
@@ -1889,6 +1930,7 @@ export class ZKPassport {
|
|
|
1889
1930
|
"inclusion_check_nationality",
|
|
1890
1931
|
"exclusion_check_issuing_country",
|
|
1891
1932
|
"inclusion_check_issuing_country",
|
|
1933
|
+
"bind",
|
|
1892
1934
|
]
|
|
1893
1935
|
const getIndex = (proof: ProofResult) => {
|
|
1894
1936
|
const name = proof.name || ""
|
|
@@ -1945,11 +1987,14 @@ export class ZKPassport {
|
|
|
1945
1987
|
message: "The proof does not verify all the requested conditions and information",
|
|
1946
1988
|
}
|
|
1947
1989
|
}
|
|
1948
|
-
if (
|
|
1990
|
+
if (
|
|
1991
|
+
this.domain &&
|
|
1992
|
+
getServiceScopeHash(this.domain, chainId) !== getScopeFromOuterProof(proofData)
|
|
1993
|
+
) {
|
|
1949
1994
|
console.warn("The proof comes from a different domain than the one expected")
|
|
1950
1995
|
isCorrect = false
|
|
1951
1996
|
queryResultErrors.outer.scope = {
|
|
1952
|
-
expected: `Scope: ${
|
|
1997
|
+
expected: `Scope: ${getServiceScopeHash(this.domain, chainId).toString()}`,
|
|
1953
1998
|
received: `Scope: ${getScopeFromOuterProof(proofData).toString()}`,
|
|
1954
1999
|
message: "The proof comes from a different domain than the one expected",
|
|
1955
2000
|
}
|
|
@@ -2205,6 +2250,27 @@ export class ZKPassport {
|
|
|
2205
2250
|
...queryResultErrors,
|
|
2206
2251
|
...queryResultErrorsIssuingCountryExclusion,
|
|
2207
2252
|
}
|
|
2253
|
+
} else if (!!committedInputs?.bind) {
|
|
2254
|
+
const bindCommittedInputs = committedInputs?.bind as BindCommittedInputs
|
|
2255
|
+
const bindParameterCommitment = isForEVM
|
|
2256
|
+
? await getBindEVMParameterCommitment(formatBoundData(bindCommittedInputs.data))
|
|
2257
|
+
: await getBindParameterCommitment(formatBoundData(bindCommittedInputs.data))
|
|
2258
|
+
if (!paramCommitments.includes(bindParameterCommitment)) {
|
|
2259
|
+
console.warn("This proof does not verify the bound data")
|
|
2260
|
+
isCorrect = false
|
|
2261
|
+
queryResultErrors.bind.commitment = {
|
|
2262
|
+
expected: `Bind parameter commitment: ${bindParameterCommitment.toString()}`,
|
|
2263
|
+
received: `Parameter commitments included: ${paramCommitments.join(", ")}`,
|
|
2264
|
+
message: "This proof does not verify the bound data",
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
const { isCorrect: isCorrectBind, queryResultErrors: queryResultErrorsBind } =
|
|
2268
|
+
this.checkBindPublicInputs(queryResult, bindCommittedInputs.data)
|
|
2269
|
+
isCorrect = isCorrect && isCorrectBind
|
|
2270
|
+
queryResultErrors = {
|
|
2271
|
+
...queryResultErrors,
|
|
2272
|
+
...queryResultErrorsBind,
|
|
2273
|
+
}
|
|
2208
2274
|
}
|
|
2209
2275
|
uniqueIdentifier = getNullifierFromOuterProof(proofData).toString(10)
|
|
2210
2276
|
} else if (proof.name?.startsWith("sig_check_dsc")) {
|
|
@@ -2636,6 +2702,29 @@ export class ZKPassport {
|
|
|
2636
2702
|
...queryResultErrorsScope,
|
|
2637
2703
|
}
|
|
2638
2704
|
uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
|
|
2705
|
+
} else if (proof.name === "bind") {
|
|
2706
|
+
const bindCommittedInputs = proof.committedInputs?.bind as BindCommittedInputs
|
|
2707
|
+
const paramCommittment = getParameterCommitmentFromDisclosureProof(proofData)
|
|
2708
|
+
const calculatedParamCommitment = await getBindParameterCommitment(
|
|
2709
|
+
formatBoundData(bindCommittedInputs.data),
|
|
2710
|
+
)
|
|
2711
|
+
if (paramCommittment !== calculatedParamCommitment) {
|
|
2712
|
+
console.warn("The bound data does not match the one from the proof")
|
|
2713
|
+
isCorrect = false
|
|
2714
|
+
queryResultErrors.bind.commitment = {
|
|
2715
|
+
expected: `Commitment: ${calculatedParamCommitment}`,
|
|
2716
|
+
received: `Commitment: ${paramCommittment}`,
|
|
2717
|
+
message: "The bound data does not match the one from the proof",
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
const { isCorrect: isCorrectBind, queryResultErrors: queryResultErrorsBind } =
|
|
2721
|
+
this.checkBindPublicInputs(queryResult, bindCommittedInputs.data)
|
|
2722
|
+
isCorrect = isCorrect && isCorrectBind
|
|
2723
|
+
queryResultErrors = {
|
|
2724
|
+
...queryResultErrors,
|
|
2725
|
+
...queryResultErrorsBind,
|
|
2726
|
+
}
|
|
2727
|
+
uniqueIdentifier = getNullifierFromDisclosureProof(proofData).toString(10)
|
|
2639
2728
|
}
|
|
2640
2729
|
}
|
|
2641
2730
|
return { isCorrect, uniqueIdentifier, queryResultErrors }
|
|
@@ -2646,6 +2735,9 @@ export class ZKPassport {
|
|
|
2646
2735
|
* @param proofs The proofs to verify.
|
|
2647
2736
|
* @param queryResult The query result to verify against
|
|
2648
2737
|
* @param validity How many days ago should have the ID been last scanned by the user?
|
|
2738
|
+
* @param scope Scope this request to a specific use case
|
|
2739
|
+
* @param evmChain The EVM chain to use for the verification (if using the proof onchain)
|
|
2740
|
+
* @param devMode Whether to enable dev mode. This will allow you to verify mock proofs (i.e. from ZKR)
|
|
2649
2741
|
* @returns An object containing the unique identifier associated to the user
|
|
2650
2742
|
* and a boolean indicating whether the proofs were successfully verified.
|
|
2651
2743
|
*/
|
|
@@ -2654,12 +2746,14 @@ export class ZKPassport {
|
|
|
2654
2746
|
queryResult,
|
|
2655
2747
|
validity,
|
|
2656
2748
|
scope,
|
|
2749
|
+
evmChain,
|
|
2657
2750
|
devMode = false,
|
|
2658
2751
|
}: {
|
|
2659
2752
|
proofs: Array<ProofResult>
|
|
2660
2753
|
queryResult: QueryResult
|
|
2661
2754
|
validity?: number
|
|
2662
2755
|
scope?: string
|
|
2756
|
+
evmChain?: EVMChain
|
|
2663
2757
|
devMode?: boolean
|
|
2664
2758
|
}): Promise<{
|
|
2665
2759
|
uniqueIdentifier: string | undefined
|
|
@@ -2692,11 +2786,12 @@ export class ZKPassport {
|
|
|
2692
2786
|
let verified = true
|
|
2693
2787
|
let uniqueIdentifier: string | undefined
|
|
2694
2788
|
let queryResultErrors: QueryResultErrors | undefined
|
|
2789
|
+
const chainId = evmChain ? getChainIdFromEVMChain(evmChain) : undefined
|
|
2695
2790
|
const {
|
|
2696
2791
|
isCorrect,
|
|
2697
2792
|
uniqueIdentifier: uniqueIdentifierFromPublicInputs,
|
|
2698
2793
|
queryResultErrors: queryResultErrorsFromPublicInputs,
|
|
2699
|
-
} = await this.checkPublicInputs(proofs, formattedResult, validity, scope)
|
|
2794
|
+
} = await this.checkPublicInputs(proofs, formattedResult, validity, scope, chainId)
|
|
2700
2795
|
uniqueIdentifier = uniqueIdentifierFromPublicInputs
|
|
2701
2796
|
verified = isCorrect
|
|
2702
2797
|
queryResultErrors = isCorrect ? undefined : queryResultErrorsFromPublicInputs
|
|
@@ -2790,7 +2885,7 @@ export class ZKPassport {
|
|
|
2790
2885
|
if (network === "ethereum_sepolia") {
|
|
2791
2886
|
return {
|
|
2792
2887
|
...baseConfig,
|
|
2793
|
-
address: "
|
|
2888
|
+
address: "0x5e4B11F7B7995F5Cee0134692a422b045091112F",
|
|
2794
2889
|
}
|
|
2795
2890
|
} else if (network === "local_anvil") {
|
|
2796
2891
|
return {
|
|
@@ -2901,6 +2996,13 @@ export class ZKPassport {
|
|
|
2901
2996
|
ProofType.DISCLOSE.toString(16).padStart(2, "0") +
|
|
2902
2997
|
value.discloseMask.map((x) => x.toString(16).padStart(2, "0")).join("") +
|
|
2903
2998
|
value.disclosedBytes.map((x) => x.toString(16).padStart(2, "0")).join("")
|
|
2999
|
+
} else if (circuitName === "bind_evm") {
|
|
3000
|
+
const value = proof.committedInputs[circuitName] as BindCommittedInputs
|
|
3001
|
+
compressedCommittedInputs =
|
|
3002
|
+
ProofType.BIND.toString(16).padStart(2, "0") +
|
|
3003
|
+
rightPadArrayWithZeros(formatBoundData(value.data), 500)
|
|
3004
|
+
.map((x) => x.toString(16).padStart(2, "0"))
|
|
3005
|
+
.join("")
|
|
2904
3006
|
} else {
|
|
2905
3007
|
throw new Error(`Unsupported circuit for EVM verification: ${circuitName}`)
|
|
2906
3008
|
}
|
|
@@ -2959,7 +3061,7 @@ export class ZKPassport {
|
|
|
2959
3061
|
* @returns The URL of the request.
|
|
2960
3062
|
*/
|
|
2961
3063
|
public getUrl(requestId: string) {
|
|
2962
|
-
const pubkey =
|
|
3064
|
+
const pubkey = this.topicToPublicKey[requestId]
|
|
2963
3065
|
const base64Config = Buffer.from(JSON.stringify(this.topicToConfig[requestId])).toString(
|
|
2964
3066
|
"base64",
|
|
2965
3067
|
)
|
|
@@ -2974,14 +3076,13 @@ export class ZKPassport {
|
|
|
2974
3076
|
* @param requestId The request ID.
|
|
2975
3077
|
*/
|
|
2976
3078
|
public cancelRequest(requestId: string) {
|
|
2977
|
-
if (this.
|
|
2978
|
-
this.
|
|
2979
|
-
delete this.
|
|
3079
|
+
if (this.topicToBridge[requestId]) {
|
|
3080
|
+
this.topicToBridge[requestId].close()
|
|
3081
|
+
delete this.topicToBridge[requestId]
|
|
2980
3082
|
}
|
|
2981
|
-
delete this.
|
|
3083
|
+
delete this.topicToPublicKey[requestId]
|
|
2982
3084
|
delete this.topicToConfig[requestId]
|
|
2983
3085
|
delete this.topicToLocalConfig[requestId]
|
|
2984
|
-
delete this.topicToSharedSecret[requestId]
|
|
2985
3086
|
delete this.topicToProofs[requestId]
|
|
2986
3087
|
delete this.topicToExpectedProofCount[requestId]
|
|
2987
3088
|
delete this.topicToFailedProofCount[requestId]
|
|
@@ -2998,7 +3099,7 @@ export class ZKPassport {
|
|
|
2998
3099
|
* @notice Clears all requests.
|
|
2999
3100
|
*/
|
|
3000
3101
|
public clearAllRequests() {
|
|
3001
|
-
for (const requestId in this.
|
|
3102
|
+
for (const requestId in this.topicToBridge) {
|
|
3002
3103
|
this.cancelRequest(requestId)
|
|
3003
3104
|
}
|
|
3004
3105
|
}
|