@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/src/encryption.ts DELETED
@@ -1,45 +0,0 @@
1
- import { gcm } from "@noble/ciphers/aes"
2
- import { utf8ToBytes } from "@noble/ciphers/utils"
3
-
4
- async function sha256Truncate(topic: string): Promise<Uint8Array> {
5
- const encoder = new TextEncoder()
6
- const data = encoder.encode(topic)
7
- const hashBuffer = await crypto.subtle.digest("SHA-256", data)
8
- const fullHashArray = new Uint8Array(hashBuffer)
9
- const truncatedHashArray = fullHashArray.slice(0, 12)
10
- return truncatedHashArray
11
- }
12
-
13
- export async function generateECDHKeyPair() {
14
- const secp256k1 = await import("@noble/secp256k1")
15
- const privKey = secp256k1.utils.randomPrivateKey()
16
- const pubKey = secp256k1.getPublicKey(privKey)
17
- return {
18
- privateKey: privKey,
19
- publicKey: pubKey,
20
- }
21
- }
22
-
23
- export async function getSharedSecret(privateKey: string, publicKey: string) {
24
- const secp256k1 = await import("@noble/secp256k1")
25
- const sharedSecret = secp256k1.getSharedSecret(privateKey, publicKey)
26
- return sharedSecret.slice(0, 32)
27
- }
28
-
29
- export async function encrypt(message: string, sharedSecret: Uint8Array, topic: string) {
30
- // Nonce must be 12 bytes
31
- const nonce = await sha256Truncate(topic)
32
- const aes = gcm(sharedSecret, nonce)
33
- const data = utf8ToBytes(message)
34
- const ciphertext = aes.encrypt(data)
35
- return ciphertext
36
- }
37
-
38
- export async function decrypt(ciphertext: Uint8Array, sharedSecret: Uint8Array, topic: string) {
39
- // Nonce must be 12 bytes
40
- const nonce = await sha256Truncate(topic)
41
- const aes = gcm(sharedSecret, nonce)
42
- const data = aes.decrypt(ciphertext)
43
- const dataString = new TextDecoder().decode(data)
44
- return dataString
45
- }
package/src/json-rpc.ts DELETED
@@ -1,61 +0,0 @@
1
- import { randomBytes } from "crypto"
2
- import type { JsonRpcRequest, JsonRpcResponse } from "@zkpassport/utils"
3
- import { encrypt } from "./encryption"
4
- import { WebSocketClient } from "./websocket"
5
- import { noLogger as logger } from "./logger"
6
-
7
- export function createJsonRpcRequest(method: string, params: any): JsonRpcRequest {
8
- return {
9
- jsonrpc: "2.0",
10
- id: randomBytes(16).toString("hex"),
11
- method,
12
- params,
13
- }
14
- }
15
-
16
- export async function createEncryptedJsonRpcRequest(
17
- method: string,
18
- params: any,
19
- sharedSecret: Uint8Array,
20
- topic: string,
21
- ): Promise<JsonRpcRequest> {
22
- const encryptedMessage = await encrypt(
23
- JSON.stringify({ method, params: params || {} }),
24
- sharedSecret,
25
- topic,
26
- )
27
- return createJsonRpcRequest("encryptedMessage", {
28
- payload: Buffer.from(encryptedMessage).toString("base64"),
29
- })
30
- }
31
-
32
- export async function sendEncryptedJsonRpcRequest(
33
- method: string,
34
- params: any,
35
- sharedSecret: Uint8Array,
36
- topic: string,
37
- wsClient: WebSocketClient,
38
- ): Promise<boolean> {
39
- try {
40
- const message = { method, params: params || {} }
41
- const encryptedMessage = await encrypt(JSON.stringify(message), sharedSecret, topic)
42
- const request = createJsonRpcRequest("encryptedMessage", {
43
- payload: Buffer.from(encryptedMessage).toString("base64"),
44
- })
45
- logger.debug("Sending encrypted message (original):", message)
46
- logger.debug("Sending encrypted message (encrypted):", request)
47
- wsClient.send(JSON.stringify(request))
48
- return true
49
- } catch (error) {
50
- logger.error("Error sending encrypted message:", error)
51
- return false
52
- }
53
- }
54
-
55
- export function createJsonRpcResponse(id: string, result: any): JsonRpcResponse {
56
- return {
57
- jsonrpc: "2.0",
58
- id,
59
- result,
60
- }
61
- }
package/src/mobile.ts DELETED
@@ -1,186 +0,0 @@
1
- import { bytesToHex } from "@noble/ciphers/utils"
2
- import { getWebSocketClient, WebSocketClient } from "./websocket"
3
- import { sendEncryptedJsonRpcRequest } from "./json-rpc"
4
- import { decrypt, generateECDHKeyPair, getSharedSecret } from "./encryption"
5
- import type { JsonRpcRequest } from "@zkpassport/utils"
6
- import { noLogger as logger } from "./logger"
7
-
8
- export class ZkPassportProver {
9
- private domain?: string
10
- private topicToKeyPair: Record<string, { privateKey: Uint8Array; publicKey: Uint8Array }> = {}
11
- private topicToWebSocketClient: Record<string, WebSocketClient> = {}
12
- private topicToRemoteDomainVerified: Record<string, boolean> = {}
13
- private topicToSharedSecret: Record<string, Uint8Array> = {}
14
- private topicToRemotePublicKey: Record<string, Uint8Array> = {}
15
-
16
- private onDomainVerifiedCallbacks: Record<string, Array<() => void>> = {}
17
- private onBridgeConnectCallbacks: Record<string, Array<() => void>> = {}
18
- private onWebsiteDomainVerifyFailureCallbacks: Record<string, Array<() => void>> = {}
19
-
20
- constructor() {}
21
-
22
- /**
23
- * @notice Handle an encrypted message.
24
- * @param request The request.
25
- * @param outerRequest The outer request.
26
- */
27
- private async handleEncryptedMessage(
28
- topic: string,
29
- request: JsonRpcRequest,
30
- outerRequest: JsonRpcRequest,
31
- ) {
32
- logger.debug("Received encrypted message:", request)
33
- if (request.method === "hello") {
34
- logger.info(`Successfully verified origin domain name: ${outerRequest.origin}`)
35
- this.topicToRemoteDomainVerified[topic] = true
36
- await Promise.all(this.onDomainVerifiedCallbacks[topic].map((callback) => callback()))
37
- } else if (request.method === "closed_page") {
38
- // TODO: Implement
39
- }
40
- }
41
-
42
- /**
43
- * @notice Scan a credentirequest QR code.
44
- * @returns
45
- */
46
- public async scan(
47
- url: string,
48
- {
49
- keyPairOverride,
50
- }: {
51
- keyPairOverride?: { privateKey: Uint8Array; publicKey: Uint8Array }
52
- } = {},
53
- ) {
54
- const parsedUrl = new URL(url)
55
- const domain = parsedUrl.searchParams.get("d")
56
- const topic = parsedUrl.searchParams.get("t")
57
- const pubkeyHex = parsedUrl.searchParams.get("p")
58
-
59
- if (!domain || !topic || !pubkeyHex) {
60
- throw new Error("Invalid URL: missing required parameters")
61
- }
62
-
63
- const pubkey = new Uint8Array(Buffer.from(pubkeyHex, "hex"))
64
-
65
- this.domain = domain
66
- const keyPair = keyPairOverride || (await generateECDHKeyPair())
67
-
68
- this.topicToKeyPair[topic] = {
69
- privateKey: keyPair.privateKey,
70
- publicKey: keyPair.publicKey,
71
- }
72
- this.topicToRemotePublicKey[topic] = pubkey
73
- this.topicToSharedSecret[topic] = await getSharedSecret(
74
- bytesToHex(keyPair.privateKey),
75
- bytesToHex(pubkey),
76
- )
77
- this.topicToRemoteDomainVerified[topic] = false
78
- this.onDomainVerifiedCallbacks[topic] = []
79
- this.onBridgeConnectCallbacks[topic] = []
80
-
81
- // Set up WebSocket connection
82
- const wsClient = getWebSocketClient(
83
- `wss://bridge.zkpassport.id?topic=${topic}&pubkey=${bytesToHex(keyPair.publicKey)}`,
84
- )
85
- this.topicToWebSocketClient[topic] = wsClient
86
-
87
- wsClient.onopen = async () => {
88
- logger.info("[mobile] WebSocket connection established")
89
- await Promise.all(this.onBridgeConnectCallbacks[topic].map((callback) => callback()))
90
- // Server sends handshake automatically (when it sees a pubkey in websocket URI)
91
- // wsClient.send(
92
- // JSON.stringify(
93
- // createJsonRpcRequest('handshake', {
94
- // pubkey: bytesToHex(keyPair.publicKey),
95
- // }),
96
- // ),
97
- // )
98
- }
99
-
100
- wsClient.addEventListener("message", async (event: any) => {
101
- logger.info("[mobile] Received message:", event.data)
102
-
103
- try {
104
- const data: JsonRpcRequest = JSON.parse(event.data)
105
- const originDomain = data.origin ? new URL(data.origin).hostname : undefined
106
- // Origin domain must match domain in QR code
107
- if (originDomain !== this.domain) {
108
- logger.warn(
109
- `[mobile] Origin does not match domain in QR code. Expected ${this.domain} but got ${originDomain}`,
110
- )
111
- return
112
- }
113
-
114
- if (data.method === "encryptedMessage") {
115
- // Decode the payload from base64 to Uint8Array
116
- const payload = new Uint8Array(
117
- atob(data.params.payload)
118
- .split("")
119
- .map((c) => c.charCodeAt(0)),
120
- )
121
- try {
122
- // Decrypt the payload using the shared secret
123
- const decrypted = await decrypt(payload, this.topicToSharedSecret[topic], topic)
124
- const decryptedJson: JsonRpcRequest = JSON.parse(decrypted)
125
- await this.handleEncryptedMessage(topic, decryptedJson, data)
126
- } catch (error) {
127
- logger.error("[mobile] Error decrypting message:", error)
128
- }
129
- }
130
- } catch (error) {
131
- logger.error("[mobile] Error:", error)
132
- }
133
- })
134
-
135
- wsClient.onerror = (error: Event) => {
136
- logger.error("[mobile] WebSocket error:", error)
137
- }
138
-
139
- return {
140
- domain: this.domain,
141
- requestId: topic,
142
- isBridgeConnected: () => this.topicToWebSocketClient[topic].readyState === WebSocket.OPEN,
143
- isDomainVerified: () => this.topicToRemoteDomainVerified[topic] === true,
144
- onDomainVerified: (callback: () => void) =>
145
- this.onDomainVerifiedCallbacks[topic].push(callback),
146
- onBridgeConnect: (callback: () => void) =>
147
- this.onBridgeConnectCallbacks[topic].push(callback),
148
- notifyReject: async () => {
149
- await sendEncryptedJsonRpcRequest(
150
- "reject",
151
- null,
152
- this.topicToSharedSecret[topic],
153
- topic,
154
- this.topicToWebSocketClient[topic],
155
- )
156
- },
157
- notifyAccept: async () => {
158
- await sendEncryptedJsonRpcRequest(
159
- "accept",
160
- null,
161
- this.topicToSharedSecret[topic],
162
- topic,
163
- this.topicToWebSocketClient[topic],
164
- )
165
- },
166
- notifyDone: async (proof: any) => {
167
- await sendEncryptedJsonRpcRequest(
168
- "done",
169
- { proof },
170
- this.topicToSharedSecret[topic],
171
- topic,
172
- this.topicToWebSocketClient[topic],
173
- )
174
- },
175
- notifyError: async (error: string) => {
176
- await sendEncryptedJsonRpcRequest(
177
- "error",
178
- { error },
179
- this.topicToSharedSecret[topic],
180
- topic,
181
- this.topicToWebSocketClient[topic],
182
- )
183
- },
184
- }
185
- }
186
- }
package/src/websocket.ts DELETED
@@ -1,16 +0,0 @@
1
- export function getWebSocketClient(url: string, origin?: string) {
2
- if (typeof window !== "undefined" && window.WebSocket) {
3
- // Browser environment
4
- return new WebSocket(url)
5
- } else {
6
- // Node.js environment
7
- const WebSocket = require("ws")
8
- return new WebSocket(url, {
9
- headers: {
10
- Origin: origin || "nodejs",
11
- },
12
- }) as import("ws").WebSocket
13
- }
14
- }
15
-
16
- export type WebSocketClient = ReturnType<typeof getWebSocketClient>