From f4ee26835a36337655799ce3671eb288d4d1ccf6 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Sun, 30 Jun 2024 15:36:25 +0300 Subject: [PATCH 01/61] chore: partially dedupe fetch implementations and use native fetch instead --- package-lock.json | 131 +------------------------- packages/core/package.json | 4 +- packages/core/src/controllers/echo.ts | 1 - packages/core/test/verify.spec.ts | 1 - 4 files changed, 3 insertions(+), 134 deletions(-) diff --git a/package-lock.json b/package-lock.json index d2ac8de1d..5fa208d2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10462,14 +10462,6 @@ "node": ">=0.10" } }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", @@ -14172,28 +14164,6 @@ "bser": "2.1.1" } }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -14583,17 +14553,6 @@ "dev": true, "license": "MIT" }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "dev": true, - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/forwarded": { "version": "0.2.0", "dev": true, @@ -20575,24 +20534,6 @@ "node": ">= 0.10.5" } }, - "node_modules/node-domexception": { - "version": "1.0.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, "node_modules/node-environment-flags": { "version": "1.0.6", "dev": true, @@ -26599,14 +26540,6 @@ "defaults": "^1.0.3" } }, - "node_modules/web-streams-polyfill": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/web3": { "version": "1.7.5", "dev": true, @@ -27484,31 +27417,11 @@ "@walletconnect/types": "2.13.3", "@walletconnect/utils": "2.13.3", "events": "3.3.0", - "isomorphic-unfetch": "3.1.0", "lodash.isequal": "4.5.0", "uint8arrays": "3.1.0" }, "devDependencies": { - "@types/lodash.isequal": "4.5.6", - "node-fetch": "3.3.0" - } - }, - "packages/core/node_modules/node-fetch": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.0.tgz", - "integrity": "sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==", - "dev": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" + "@types/lodash.isequal": "4.5.6" } }, "packages/core/node_modules/uint8arrays": { @@ -27609,7 +27522,7 @@ }, "packages/web3wallet": { "name": "@walletconnect/web3wallet", - "version": "2.13.3", + "version": "1.12.3", "license": "Apache-2.0", "dependencies": { "@walletconnect/auth-client": "2.1.2", @@ -33641,23 +33554,10 @@ "@walletconnect/types": "2.13.3", "@walletconnect/utils": "2.13.3", "events": "3.3.0", - "isomorphic-unfetch": "3.1.0", "lodash.isequal": "4.5.0", - "node-fetch": "3.3.0", "uint8arrays": "3.1.0" }, "dependencies": { - "node-fetch": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.0.tgz", - "integrity": "sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==", - "dev": true, - "requires": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - } - }, "uint8arrays": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.0.tgz", @@ -35961,10 +35861,6 @@ "assert-plus": "^1.0.0" } }, - "data-uri-to-buffer": { - "version": "4.0.1", - "dev": true - }, "dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", @@ -38424,14 +38320,6 @@ "bser": "2.1.1" } }, - "fetch-blob": { - "version": "3.2.0", - "dev": true, - "requires": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - } - }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -38712,13 +38600,6 @@ "version": "1.7.1", "dev": true }, - "formdata-polyfill": { - "version": "4.0.10", - "dev": true, - "requires": { - "fetch-blob": "^3.1.2" - } - }, "forwarded": { "version": "0.2.0", "dev": true @@ -43156,10 +43037,6 @@ "minimatch": "^3.0.2" } }, - "node-domexception": { - "version": "1.0.0", - "dev": true - }, "node-environment-flags": { "version": "1.0.6", "dev": true, @@ -47491,10 +47368,6 @@ "defaults": "^1.0.3" } }, - "web-streams-polyfill": { - "version": "3.2.1", - "dev": true - }, "web3": { "version": "1.7.5", "dev": true, diff --git a/packages/core/package.json b/packages/core/package.json index 327a40df7..c6bee32de 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -45,12 +45,10 @@ "@walletconnect/types": "2.13.3", "@walletconnect/utils": "2.13.3", "events": "3.3.0", - "isomorphic-unfetch": "3.1.0", "lodash.isequal": "4.5.0", "uint8arrays": "3.1.0" }, "devDependencies": { - "@types/lodash.isequal": "4.5.6", - "node-fetch": "3.3.0" + "@types/lodash.isequal": "4.5.6" } } diff --git a/packages/core/src/controllers/echo.ts b/packages/core/src/controllers/echo.ts index ccf86f7b3..b469f227f 100644 --- a/packages/core/src/controllers/echo.ts +++ b/packages/core/src/controllers/echo.ts @@ -1,7 +1,6 @@ import { generateChildLogger, Logger } from "@walletconnect/logger"; import { IEchoClient } from "@walletconnect/types"; import { ECHO_CONTEXT, ECHO_URL } from "../constants"; -import fetch from "isomorphic-unfetch"; export class EchoClient extends IEchoClient { public readonly context = ECHO_CONTEXT; diff --git a/packages/core/test/verify.spec.ts b/packages/core/test/verify.spec.ts index af8933ecb..8b5f711a4 100644 --- a/packages/core/test/verify.spec.ts +++ b/packages/core/test/verify.spec.ts @@ -1,5 +1,4 @@ import { expect, describe, it } from "vitest"; -import fetch from "node-fetch"; import { hashMessage } from "@walletconnect/utils"; import { Core, VERIFY_SERVER } from "../src"; From c5d18b1368ac493cdfea23fbb058faae4b9782b2 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Sun, 30 Jun 2024 15:39:31 +0300 Subject: [PATCH 02/61] chore: require node 18.x --- packages/core/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/core/package.json b/packages/core/package.json index c6bee32de..03ad479f4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -50,5 +50,8 @@ }, "devDependencies": { "@types/lodash.isequal": "4.5.6" + }, + "engines": { + "node": ">=18" } } From 10d464052d010d3c08e6693f14df5889b1883112 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Thu, 11 Jul 2024 16:11:17 +0300 Subject: [PATCH 03/61] chore: events sdk progress --- package-lock.json | 2 +- packages/core/src/constants/events.ts | 75 +++++++++++++++++++++++++ packages/core/src/controllers/events.ts | 46 +++++++++++++++ packages/core/src/controllers/index.ts | 1 + packages/core/src/core.ts | 3 + packages/types/src/core/core.ts | 4 +- packages/types/src/core/events.ts | 39 +++++++++++++ packages/types/src/core/index.ts | 1 + 8 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/constants/events.ts create mode 100644 packages/core/src/controllers/events.ts create mode 100644 packages/types/src/core/events.ts diff --git a/package-lock.json b/package-lock.json index d2ac8de1d..6521faaa1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27609,7 +27609,7 @@ }, "packages/web3wallet": { "name": "@walletconnect/web3wallet", - "version": "2.13.3", + "version": "1.12.3", "license": "Apache-2.0", "dependencies": { "@walletconnect/auth-client": "2.1.2", diff --git a/packages/core/src/constants/events.ts b/packages/core/src/constants/events.ts new file mode 100644 index 000000000..d0546b698 --- /dev/null +++ b/packages/core/src/constants/events.ts @@ -0,0 +1,75 @@ +export const EVENT_CLIENT_CONTEXT = "event-client"; + +export const EVENT_CLIENT_PAIRING_TRACES = { + pairing_started: "pairing_started", + pairing_uri_validation_success: "pairing_uri_validation_success", + pairing_uri_not_expired: "pairing_uri_not_expired", + store_new_pairing: "store_new_pairing", + subscribing_pairing_topic: "subscribing_pairing_topic", + subscribe_pairing_topic_success: "subscribe_pairing_topic_success", + existing_pairing: "existing_pairing", + pairing_not_expired: "pairing_not_expired", + emit_inactive_pairing: "emit_inactive_pairing", + emit_session_proposal: "emit_session_proposal", + subscribing_to_pairing_topic: "subscribing_to_pairing_topic", +}; + +export const EVENT_CLIENT_PAIRING_ERRORS = { + no_wss_connection: "no_wss_connection", + no_internet_connection: "no_internet_connection", + malformed_pairing_uri: "malformed_pairing_uri", + active_pairing_already_exists: "active_pairing_already_exists", + subscribe_pairing_topic_failure: "subscribe_pairing_topic_failure", + pairing_expired: "pairing_expired", + proposal_expired: "proposal_expired", +}; + +export const EVENT_CLIENT_SESSION_TRACES = { + session_approve_started: "session_approve_started", + proposal_not_expired: "proposal_not_expired", + session_namespaces_validation_success: "session_namespaces_validation_success", + create_session_topic: "create_session_topic", + subscribing_session_topic: "subscribing_session_topic", + subscribe_session_topic_success: "subscribe_session_topic_success", + publishing_session_approve: "publishing_session_approve", + session_approve_publish_success: "session_approve_publish_success", + store_session: "store_session", + publishing_session_settle: "publishing_session_settle", + session_settle_publish_success: "session_settle_publish_success", +}; + +export const EVENT_CLIENT_SESSION_ERRORS = { + no_internet_connection: "no_internet_connection", + no_wss_connection: "no_wss_connection", + proposal_expired: "proposal_expired", + subscribe_session_topic_failure: "subscribe_session_topic_failure", + session_approve_publish_failure: "session_approve_publish_failure", + session_settle_publish_failure: "session_settle_publish_failure", + session_approve_namespace_validation_failure: "session_approve_namespace_validation_failure", +}; + +export const EVENT_CLIENT_AUTHENTICATE_TRACES = { + authenticated_session_approve_started: "authenticated_session_approve_started", + authenticated_session_not_expired: "authenticated_session_not_expired", + chains_caip2_compliant: "chains_caip2_compliant", + chains_evm_compliant: "chains_evm_compliant", + create_authenticated_session_topic: "create_authenticated_session_topic", + cacaos_verified: "cacaos_verified", + store_authenticated_session: "store_authenticated_session", + subscribing_authenticated_session_topic: "subscribing_authenticated_session_topic", + subscribe_authenticated_session_topic_success: "subscribe_authenticated_session_topic_success", + publishing_authenticated_session_approve: "publishing_authenticated_session_approve", + authenticated_session_approve_publish_success: "authenticated_session_approve_publish_success", +}; + +export const EVENT_CLIENT_AUTHENTICATE_ERRORS = { + no_internet_connection: "no_internet_connection", + no_wss_connection: "no_wss_connection", + missing_session_authenticate_request: "missing_session_authenticate_request", + session_authenticate_request_expired: "session_authenticate_request_expired", + chains_caip2_compliant_failure: "chains_caip2_compliant_failure", + chains_evm_compliant_failure: "chains_evm_compliant_failure", + invalid_cacao: "invalid_cacao", + subscribe_authenticated_session_topic_failure: "subscribe_authenticated_session_topic_failure", + authenticated_session_approve_publish_failure: "authenticated_session_approve_publish_failure", +}; diff --git a/packages/core/src/controllers/events.ts b/packages/core/src/controllers/events.ts new file mode 100644 index 000000000..fbe0f4750 --- /dev/null +++ b/packages/core/src/controllers/events.ts @@ -0,0 +1,46 @@ +import { generateChildLogger, Logger } from "@walletconnect/logger"; +import { ICore, IEventClient, EventClientTypes } from "@walletconnect/types"; + +export class EventClient extends IEventClient { + public context = "EventsClient"; + private events: EventClientTypes.Event[] = []; + + constructor(public core: ICore, public logger: Logger) { + super(core, logger); + this.logger = generateChildLogger(logger, this.context); + } + + public createEvent: IEventClient["createEvent"] = async (params) => { + const { + event = "ERROR", + type, + properties: { topic, trace }, + } = params; + const eventId = this.uuidv4(); + const bundleId = this.core.context; + const timestamp = Date.now(); + const props = { + event, + type, + properties: { + topic, + trace, + }, + }; + this.events.push({ eventId, bundleId, timestamp, props }); + return { eventId, bundleId, timestamp, props }; + }; + + private uuidv4() { + if (crypto?.randomUUID) { + return crypto.randomUUID(); + } + + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/gu, (c) => { + const r = (Math.random() * 16) | 0; + const v = c === "x" ? r : (r & 0x3) | 0x8; + + return v.toString(16); + }); + } +} diff --git a/packages/core/src/controllers/index.ts b/packages/core/src/controllers/index.ts index 57ac377a4..127116ddd 100644 --- a/packages/core/src/controllers/index.ts +++ b/packages/core/src/controllers/index.ts @@ -9,3 +9,4 @@ export * from "./history"; export * from "./expirer"; export * from "./verify"; export * from "./echo"; +export * from "./events"; diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index a676a7c75..254297280 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -19,6 +19,7 @@ import { Expirer, Verify, EchoClient, + EventClient, } from "./controllers"; import { CORE_CONTEXT, @@ -49,6 +50,7 @@ export class Core extends ICore { public pairing: ICore["pairing"]; public verify: ICore["verify"]; public echoClient: ICore["echoClient"]; + public eventClient: ICore["eventClient"]; private initialized = false; private logChunkController: ChunkLoggerController | null; @@ -110,6 +112,7 @@ export class Core extends ICore { this.pairing = new Pairing(this, this.logger); this.verify = new Verify(this.projectId || "", this.logger); this.echoClient = new EchoClient(this.projectId || "", this.logger); + this.eventClient = new EventClient(this, this.logger); } get context() { diff --git a/packages/types/src/core/core.ts b/packages/types/src/core/core.ts index 06bd152a6..bd98a6a91 100644 --- a/packages/types/src/core/core.ts +++ b/packages/types/src/core/core.ts @@ -11,6 +11,7 @@ import { IPairing } from "./pairing"; import { Logger } from "@walletconnect/logger"; import { IVerify } from "./verify"; import { IEchoClient } from "./echo"; +import { IEventClient } from "./events"; export declare namespace CoreTypes { interface Options { projectId?: string; @@ -57,7 +58,8 @@ export abstract class ICore extends IEvents { public abstract pairing: IPairing; public abstract verify: IVerify; public abstract echoClient: IEchoClient; - + public abstract eventClient: IEventClient; + constructor(public opts?: CoreTypes.Options) { super(); } diff --git a/packages/types/src/core/events.ts b/packages/types/src/core/events.ts new file mode 100644 index 000000000..00e1cca89 --- /dev/null +++ b/packages/types/src/core/events.ts @@ -0,0 +1,39 @@ +import { Logger } from "@walletconnect/logger"; +import { ICore } from "./core"; + +export declare namespace EventClientTypes { + export interface Event { + eventId: string; + bundleId: string; + timestamp: number; + props: Props; + } + + export interface Props { + event: string; + type: string; + properties: Properties; + } + + export interface Properties { + topic: string; + trace: Trace; + } + + export type Trace = string[]; +} + +export abstract class IEventClient { + public abstract readonly context: string; + + constructor(public core: ICore, public logger: Logger) {} + + public abstract createEvent(params: { + event?: "ERROR"; + type: string; + properties: { + topic: string; + trace: EventClientTypes.Trace; + }; + }): Promise; +} diff --git a/packages/types/src/core/index.ts b/packages/types/src/core/index.ts index 08aa7e323..210ebeb9c 100644 --- a/packages/types/src/core/index.ts +++ b/packages/types/src/core/index.ts @@ -11,3 +11,4 @@ export * from "./expirer"; export * from "./pairing"; export * from "./verify"; export * from "./echo"; +export * from "./events"; \ No newline at end of file From 2ba4208c9120e2be7014cdeb796a96214d9f11bb Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Tue, 16 Jul 2024 13:46:18 +0300 Subject: [PATCH 04/61] feat: events sdk --- packages/core/src/constants/events.ts | 12 ++ packages/core/src/constants/index.ts | 1 + packages/core/src/controllers/events.ts | 190 ++++++++++++++++-- packages/core/src/controllers/pairing.ts | 59 +++++- packages/core/src/core.ts | 2 +- .../sign-client/src/controllers/engine.ts | 176 ++++++++++++++-- packages/types/src/core/core.ts | 3 +- packages/types/src/core/events.ts | 15 +- packages/types/src/core/index.ts | 2 +- packages/utils/src/misc.ts | 13 ++ .../ethereum-provider/src/EthereumProvider.ts | 12 +- .../src/UniversalProvider.ts | 3 + .../universal-provider/src/types/misc.ts | 2 +- 13 files changed, 440 insertions(+), 50 deletions(-) diff --git a/packages/core/src/constants/events.ts b/packages/core/src/constants/events.ts index d0546b698..24e14f292 100644 --- a/packages/core/src/constants/events.ts +++ b/packages/core/src/constants/events.ts @@ -22,6 +22,7 @@ export const EVENT_CLIENT_PAIRING_ERRORS = { subscribe_pairing_topic_failure: "subscribe_pairing_topic_failure", pairing_expired: "pairing_expired", proposal_expired: "proposal_expired", + proposal_listener_not_found: "proposal_listener_not_found", }; export const EVENT_CLIENT_SESSION_TRACES = { @@ -46,6 +47,7 @@ export const EVENT_CLIENT_SESSION_ERRORS = { session_approve_publish_failure: "session_approve_publish_failure", session_settle_publish_failure: "session_settle_publish_failure", session_approve_namespace_validation_failure: "session_approve_namespace_validation_failure", + proposal_not_found: "proposal_not_found", }; export const EVENT_CLIENT_AUTHENTICATE_TRACES = { @@ -72,4 +74,14 @@ export const EVENT_CLIENT_AUTHENTICATE_ERRORS = { invalid_cacao: "invalid_cacao", subscribe_authenticated_session_topic_failure: "subscribe_authenticated_session_topic_failure", authenticated_session_approve_publish_failure: "authenticated_session_approve_publish_failure", + authenticated_session_pending_request_not_found: + "authenticated_session_pending_request_not_found", }; + +export const EVENTS_STORAGE_VERSION = 0.1; + +export const EVENTS_STORAGE_CONTEXT = "event-client"; + +export const EVENTS_STORAGE_CLEANUP_INTERVAL = 86400; + +export const EVENTS_CLIENT_API_URL = "https://pulse.walletconnect.com/batch"; diff --git a/packages/core/src/constants/index.ts b/packages/core/src/constants/index.ts index 937b946ce..20fd81d5f 100644 --- a/packages/core/src/constants/index.ts +++ b/packages/core/src/constants/index.ts @@ -11,3 +11,4 @@ export * from "./history"; export * from "./expirer"; export * from "./verify"; export * from "./echo"; +export * from "./events"; diff --git a/packages/core/src/controllers/events.ts b/packages/core/src/controllers/events.ts index fbe0f4750..bd41fdc57 100644 --- a/packages/core/src/controllers/events.ts +++ b/packages/core/src/controllers/events.ts @@ -1,23 +1,68 @@ import { generateChildLogger, Logger } from "@walletconnect/logger"; import { ICore, IEventClient, EventClientTypes } from "@walletconnect/types"; +import { uuidv4 } from "@walletconnect/utils"; +import { + CORE_STORAGE_PREFIX, + EVENTS_CLIENT_API_URL, + EVENTS_STORAGE_CLEANUP_INTERVAL, + EVENTS_STORAGE_CONTEXT, + EVENTS_STORAGE_VERSION, + RELAYER_SDK_VERSION, +} from "../constants"; +import { HEARTBEAT_EVENTS } from "@walletconnect/heartbeat"; +import { fromMiliseconds } from "@walletconnect/time"; export class EventClient extends IEventClient { - public context = "EventsClient"; - private events: EventClientTypes.Event[] = []; + public readonly context = EVENTS_STORAGE_CONTEXT; + private readonly storagePrefix = CORE_STORAGE_PREFIX; + private readonly storageVersion = EVENTS_STORAGE_VERSION; - constructor(public core: ICore, public logger: Logger) { - super(core, logger); + private events = new Map(); + private toPersist = false; + constructor(public core: ICore, public logger: Logger, telemetryEnabled = true) { + super(core, logger, telemetryEnabled); this.logger = generateChildLogger(logger, this.context); + if (telemetryEnabled) { + this.restore().then(async () => { + await this.submit(); + this.setEventListeners(); + }); + } else { + // overrite any persisted events with empty array + this.persist(); + } + } + + private setEventListeners = () => { + this.core.heartbeat.on(HEARTBEAT_EVENTS.pulse, async () => { + if (this.toPersist) await this.persist(); + // cleanup events older than EVENTS_STORAGE_CLEANUP_INTERVAL + this.events.forEach((event) => { + if ( + fromMiliseconds(Date.now()) - fromMiliseconds(event.timestamp) > + EVENTS_STORAGE_CLEANUP_INTERVAL + ) { + this.events.delete(event.eventId); + this.toPersist = true; + } + }); + }); + }; + + get storageKey() { + return ( + this.storagePrefix + this.storageVersion + this.core.customStoragePrefix + "//" + this.context + ); } - public createEvent: IEventClient["createEvent"] = async (params) => { + public createEvent: IEventClient["createEvent"] = (params) => { const { event = "ERROR", - type, + type = "", properties: { topic, trace }, } = params; - const eventId = this.uuidv4(); - const bundleId = this.core.context; + const eventId = uuidv4(); + const bundleId = this.core.projectId || ""; const timestamp = Date.now(); const props = { event, @@ -27,20 +72,129 @@ export class EventClient extends IEventClient { trace, }, }; - this.events.push({ eventId, bundleId, timestamp, props }); - return { eventId, bundleId, timestamp, props }; + const eventObj = { + eventId, + bundleId, + timestamp, + props, + ...this.setMethods(eventId), + }; + + if (this.telemetryEnabled) { + this.events.set(eventId, eventObj); + this.toPersist = true; + } + + return eventObj; }; - private uuidv4() { - if (crypto?.randomUUID) { - return crypto.randomUUID(); + private setMethods = (eventId: string) => { + return { + addTrace: (trace: string) => this.addTrace(eventId, trace), + setError: (errorType: string) => this.setError(eventId, errorType), + }; + }; + + public getEvent: IEventClient["getEvent"] = (params) => { + const { eventId, topic } = params; + if (eventId) { + return this.events.get(eventId); } + const event = Array.from(this.events.values()).find( + (event) => event.props.properties.topic === topic, + ); + + if (!event) return; - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/gu, (c) => { - const r = (Math.random() * 16) | 0; - const v = c === "x" ? r : (r & 0x3) | 0x8; + return { + ...event, + ...this.setMethods(event.eventId), + }; + }; + + public deleteEvent: IEventClient["deleteEvent"] = (params) => { + const { eventId } = params; + this.events.delete(eventId); + this.toPersist = true; + }; - return v.toString(16); + private addTrace = async (eventId: string, trace: string) => { + const event = this.events.get(eventId); + if (!event) return; + return await new Promise((resolve) => { + event.props.properties.trace.push(trace); + this.events.set(eventId, event); + this.toPersist = true; + resolve(); }); - } + }; + + private setError = async (eventId: string, errorType: string) => { + const event = this.events.get(eventId); + if (!event) return; + return await new Promise((resolve) => { + event.props.type = errorType; + event.timestamp = Date.now(); + this.events.set(eventId, event); + this.toPersist = true; + resolve(); + }); + }; + + private persist = async () => { + await this.core.storage.setItem(this.storageKey, Array.from(this.events.values())); + this.toPersist = false; + }; + + private restore = async () => { + try { + const events = + (await this.core.storage.getItem(this.storageKey)) || []; + if (!events.length) return; + events.forEach((event) => { + this.events.set(event.eventId, { + ...event, + ...this.setMethods(event.eventId), + }); + }); + } catch (error) { + this.logger.warn(error); + } + }; + + private submit = async () => { + if (!this.telemetryEnabled) return; + + if (this.events.size === 0) return; + + const eventsToSend = []; + // exclude events without type as they can be considered `in progress` + for (const [_, event] of this.events) { + if (event.props.type) { + eventsToSend.push(event); + } + } + + if (eventsToSend.length === 0) return; + + try { + const response = await fetch(EVENTS_CLIENT_API_URL, { + method: "POST", + body: JSON.stringify(eventsToSend), + headers: { + "x-project-id": `${this.core.projectId}`, + "x-sdk-type": "events_sdk", + "x-sdk-version": `js-${RELAYER_SDK_VERSION}`, + }, + }); + if (response.ok) { + for (const event of eventsToSend) { + this.events.delete(event.eventId); + this.toPersist = true; + } + } + } catch (error) { + this.logger.warn(error); + } + }; } diff --git a/packages/core/src/controllers/pairing.ts b/packages/core/src/controllers/pairing.ts index 71e9921cd..28c0fd46b 100644 --- a/packages/core/src/controllers/pairing.ts +++ b/packages/core/src/controllers/pairing.ts @@ -8,6 +8,7 @@ import { RelayerTypes, PairingJsonRpcTypes, ExpirerTypes, + EventClientTypes, } from "@walletconnect/types"; import { getInternalError, @@ -45,6 +46,8 @@ import { RELAYER_EVENTS, EXPIRER_EVENTS, PAIRING_EVENTS, + EVENT_CLIENT_PAIRING_TRACES, + EVENT_CLIENT_PAIRING_ERRORS, } from "../constants"; import { Store } from "../controllers/store"; @@ -111,15 +114,38 @@ export class Pairing implements IPairing { public pair: IPairing["pair"] = async (params) => { this.isInitialized(); - this.isValidPair(params); + + const event = this.core.eventClient.createEvent({ + properties: { + topic: params?.uri, + trace: [EVENT_CLIENT_PAIRING_TRACES.pairing_started], + }, + }); + + try { + this.isValidPair(params, event); + } catch (error) { + event.setError(EVENT_CLIENT_PAIRING_ERRORS.malformed_pairing_uri); + throw error; + } + const { topic, symKey, relay, expiryTimestamp, methods } = parseUri(params.uri); + + event.props.properties.topic = topic; + event.addTrace(EVENT_CLIENT_PAIRING_TRACES.pairing_uri_validation_success); + event.addTrace(EVENT_CLIENT_PAIRING_TRACES.pairing_uri_not_expired); + let existingPairing; if (this.pairings.keys.includes(topic)) { existingPairing = this.pairings.get(topic); + event.addTrace(EVENT_CLIENT_PAIRING_TRACES.existing_pairing); if (existingPairing.active) { + event.setError(EVENT_CLIENT_PAIRING_ERRORS.active_pairing_already_exists); throw new Error( `Pairing already exists: ${topic}. Please try again with a new connection URI.`, ); + } else { + event.addTrace(EVENT_CLIENT_PAIRING_TRACES.pairing_not_expired); } } @@ -128,17 +154,37 @@ export class Pairing implements IPairing { this.core.expirer.set(topic, expiry); await this.pairings.set(topic, pairing); + event.addTrace(EVENT_CLIENT_PAIRING_TRACES.store_new_pairing); + if (params.activatePairing) { await this.activate({ topic }); } this.events.emit(PAIRING_EVENTS.create, pairing); + event.addTrace(EVENT_CLIENT_PAIRING_TRACES.emit_inactive_pairing); + // avoid overwriting keychain pairing already exists if (!this.core.crypto.keychain.has(topic)) { await this.core.crypto.setSymKey(symKey, topic); } - await this.core.relayer.subscribe(topic, { relay }); + event.addTrace(EVENT_CLIENT_PAIRING_TRACES.subscribing_pairing_topic); + + try { + await this.core.relayer.confirmOnlineStateOrThrow(); + } catch (error) { + event.setError(EVENT_CLIENT_PAIRING_ERRORS.no_internet_connection); + } + + try { + await this.core.relayer.subscribe(topic, { relay }); + } catch (error) { + event.setError(EVENT_CLIENT_PAIRING_ERRORS.subscribe_pairing_topic_failure); + throw error; + } + + event.addTrace(EVENT_CLIENT_PAIRING_TRACES.subscribe_pairing_topic_success); + return pairing; }; @@ -380,27 +426,32 @@ export class Pairing implements IPairing { // ---------- Validation Helpers ----------------------------------- // - private isValidPair = (params: { uri: string }) => { + private isValidPair = (params: { uri: string }, event: EventClientTypes.Event) => { if (!isValidParams(params)) { const { message } = getInternalError("MISSING_OR_INVALID", `pair() params: ${params}`); + event.setError(EVENT_CLIENT_PAIRING_ERRORS.malformed_pairing_uri); throw new Error(message); } if (!isValidUrl(params.uri)) { const { message } = getInternalError("MISSING_OR_INVALID", `pair() uri: ${params.uri}`); + event.setError(EVENT_CLIENT_PAIRING_ERRORS.malformed_pairing_uri); throw new Error(message); } - const uri = parseUri(params.uri); + const uri = parseUri(params?.uri); if (!uri?.relay?.protocol) { const { message } = getInternalError("MISSING_OR_INVALID", `pair() uri#relay-protocol`); + event.setError(EVENT_CLIENT_PAIRING_ERRORS.malformed_pairing_uri); throw new Error(message); } if (!uri?.symKey) { const { message } = getInternalError("MISSING_OR_INVALID", `pair() uri#symKey`); + event.setError(EVENT_CLIENT_PAIRING_ERRORS.malformed_pairing_uri); throw new Error(message); } if (uri?.expiryTimestamp) { const expiration = toMiliseconds(uri?.expiryTimestamp); if (expiration < Date.now()) { + event.setError(EVENT_CLIENT_PAIRING_ERRORS.pairing_expired); const { message } = getInternalError( "EXPIRED", `pair() URI has expired. Please try again with a new connection URI.`, diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 254297280..c36ef38c6 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -112,7 +112,7 @@ export class Core extends ICore { this.pairing = new Pairing(this, this.logger); this.verify = new Verify(this.projectId || "", this.logger); this.echoClient = new EchoClient(this.projectId || "", this.logger); - this.eventClient = new EventClient(this, this.logger); + this.eventClient = new EventClient(this, this.logger, opts?.telemetryEnabled); } get context() { diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index f63b94b5d..641ec9d57 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -1,4 +1,10 @@ import { + EVENT_CLIENT_AUTHENTICATE_ERRORS, + EVENT_CLIENT_AUTHENTICATE_TRACES, + EVENT_CLIENT_PAIRING_ERRORS, + EVENT_CLIENT_PAIRING_TRACES, + EVENT_CLIENT_SESSION_ERRORS, + EVENT_CLIENT_SESSION_TRACES, EXPIRER_EVENTS, PAIRING_EVENTS, RELAYER_DEFAULT_PROTOCOL, @@ -35,6 +41,7 @@ import { SessionTypes, PairingTypes, AuthTypes, + EventClientTypes, } from "@walletconnect/types"; import { calcExpiry, @@ -255,24 +262,59 @@ export class Engine extends IEngine { }; public approve: IEngine["approve"] = async (params) => { - await this.isInitialized(); + const configEvent = this.client.core.eventClient.createEvent({ + properties: { + topic: params.id.toString(), + trace: [EVENT_CLIENT_SESSION_TRACES.session_approve_started], + }, + }); try { - await this.isValidApprove(params); + await this.isInitialized(); } catch (error) { - this.client.logger.error("approve() -> isValidApprove() failed"); + configEvent.setError(EVENT_CLIENT_SESSION_ERRORS.no_internet_connection); throw error; } + const { id, relayProtocol, namespaces, sessionProperties, sessionConfig } = params; let proposal; try { proposal = this.client.proposal.get(id); } catch (error) { this.client.logger.error(`approve() -> proposal.get(${id}) failed`); + configEvent.setError(EVENT_CLIENT_SESSION_ERRORS.proposal_not_found); + throw error; + } + + try { + await this.isValidApprove(params); + } catch (error) { + this.client.logger.error("approve() -> isValidApprove() failed"); + configEvent.setError( + EVENT_CLIENT_SESSION_ERRORS.session_approve_namespace_validation_failure, + ); throw error; } + this.client.core.eventClient.deleteEvent({ eventId: configEvent.eventId }); + const { pairingTopic, proposer, requiredNamespaces, optionalNamespaces } = proposal; + let event = this.client.core.eventClient?.getEvent({ + topic: pairingTopic, + }) as EventClientTypes.Event; + if (!event) { + event = this.client.core.eventClient?.createEvent({ + type: EVENT_CLIENT_SESSION_TRACES.session_approve_started, + properties: { + topic: pairingTopic, + trace: [ + EVENT_CLIENT_SESSION_TRACES.session_approve_started, + EVENT_CLIENT_SESSION_TRACES.session_namespaces_validation_success, + ], + }, + }); + } + const selfPublicKey = await this.client.core.crypto.generateKeyPair(); const peerPublicKey = proposer.publicKey; const sessionTopic = await this.client.core.crypto.generateSharedKey( @@ -287,7 +329,18 @@ export class Engine extends IEngine { ...(sessionProperties && { sessionProperties }), ...(sessionConfig && { sessionConfig }), }; - await this.client.core.relayer.subscribe(sessionTopic); + + event.addTrace(EVENT_CLIENT_SESSION_TRACES.subscribing_session_topic); + + try { + await this.client.core.relayer.subscribe(sessionTopic); + } catch (error) { + event.setError(EVENT_CLIENT_SESSION_ERRORS.subscribe_session_topic_failure); + throw error; + } + + event.addTrace(EVENT_CLIENT_SESSION_TRACES.subscribe_session_topic_success); + const session = { ...sessionSettle, topic: sessionTopic, @@ -303,7 +356,12 @@ export class Engine extends IEngine { controller: selfPublicKey, }; await this.client.session.set(sessionTopic, session); + + event.addTrace(EVENT_CLIENT_SESSION_TRACES.store_session); + try { + event.addTrace(EVENT_CLIENT_SESSION_TRACES.publishing_session_approve); + await this.sendResult<"wc_sessionPropose">({ id, topic: pairingTopic, @@ -314,13 +372,25 @@ export class Engine extends IEngine { responderPublicKey: selfPublicKey, }, throwOnFailedPublish: true, + }).catch((error) => { + event?.setError(EVENT_CLIENT_SESSION_ERRORS.session_approve_publish_failure); + throw error; }); + + event.addTrace(EVENT_CLIENT_SESSION_TRACES.session_approve_publish_success); + event.addTrace(EVENT_CLIENT_SESSION_TRACES.publishing_session_settle); + await this.sendRequest({ topic: sessionTopic, method: "wc_sessionSettle", params: sessionSettle, throwOnFailedPublish: true, + }).catch((error) => { + event?.setError(EVENT_CLIENT_SESSION_ERRORS.session_settle_publish_failure); + throw error; }); + + event.addTrace(EVENT_CLIENT_SESSION_TRACES.session_settle_publish_success); } catch (error) { this.client.logger.error(error); // if the publish fails, delete the session and throw an error @@ -329,6 +399,8 @@ export class Engine extends IEngine { throw error; } + this.client.core.eventClient.deleteEvent({ eventId: event.eventId }); + await this.client.core.pairing.updateMetadata({ topic: pairingTopic, metadata: proposer.metadata, @@ -877,13 +949,28 @@ export class Engine extends IEngine { public approveSessionAuthenticate: IEngine["approveSessionAuthenticate"] = async ( sessionAuthenticateResponseParams, ) => { - this.isInitialized(); - const { id, auths } = sessionAuthenticateResponseParams; + const event = this.client.core.eventClient.createEvent({ + properties: { + topic: id.toString(), + trace: [EVENT_CLIENT_AUTHENTICATE_TRACES.authenticated_session_approve_started], + }, + }); + + try { + this.isInitialized(); + } catch (error) { + event.setError(EVENT_CLIENT_AUTHENTICATE_ERRORS.no_internet_connection); + throw error; + } + const pendingRequest = this.getPendingAuthRequest(id); if (!pendingRequest) { + event.setError( + EVENT_CLIENT_AUTHENTICATE_ERRORS.authenticated_session_pending_request_not_found, + ); throw new Error(`Could not find pending auth request with id ${id}`); } @@ -902,6 +989,8 @@ export class Engine extends IEngine { for (const cacao of auths) { const isValid = await validateSignedCacao({ cacao, projectId: this.client.core.projectId }); if (!isValid) { + event.setError(EVENT_CLIENT_AUTHENTICATE_ERRORS.invalid_cacao); + const invalidErr = getSdkError( "SESSION_SETTLEMENT_FAILED", "Signature verification failed", @@ -917,6 +1006,8 @@ export class Engine extends IEngine { throw new Error(invalidErr.message); } + event.addTrace(EVENT_CLIENT_AUTHENTICATE_TRACES.cacaos_verified); + const { p: payload } = cacao; const recap = getRecapFromResources(payload.resources); @@ -939,6 +1030,9 @@ export class Engine extends IEngine { senderPublicKey, receiverPublicKey, ); + + event.addTrace(EVENT_CLIENT_AUTHENTICATE_TRACES.create_authenticated_session_topic); + let session: SessionTypes.Struct | undefined; if (approvedMethods?.length > 0) { session = { @@ -965,29 +1059,59 @@ export class Engine extends IEngine { ), }; - await this.client.core.relayer.subscribe(sessionTopic); + event.addTrace(EVENT_CLIENT_AUTHENTICATE_TRACES.subscribing_authenticated_session_topic); + + try { + await this.client.core.relayer.subscribe(sessionTopic); + } catch (error) { + event.setError( + EVENT_CLIENT_AUTHENTICATE_ERRORS.subscribe_authenticated_session_topic_failure, + ); + throw error; + } + + event.addTrace( + EVENT_CLIENT_AUTHENTICATE_TRACES.subscribe_authenticated_session_topic_success, + ); + await this.client.session.set(sessionTopic, session); + + event.addTrace(EVENT_CLIENT_AUTHENTICATE_TRACES.store_authenticated_session); + await this.client.core.pairing.updateMetadata({ topic: pendingRequest.pairingTopic, metadata: pendingRequest.requester.metadata, }); } - await this.sendResult<"wc_sessionAuthenticate">({ - topic: responseTopic, - id, - result: { - cacaos: auths, - responder: { - publicKey: senderPublicKey, - metadata: this.client.metadata, + event.addTrace(EVENT_CLIENT_AUTHENTICATE_TRACES.publishing_authenticated_session_approve); + + try { + await this.sendResult<"wc_sessionAuthenticate">({ + topic: responseTopic, + id, + result: { + cacaos: auths, + responder: { + publicKey: senderPublicKey, + metadata: this.client.metadata, + }, }, - }, - encodeOpts, - throwOnFailedPublish: true, - }); + encodeOpts, + throwOnFailedPublish: true, + }); + } catch (error) { + event.setError( + EVENT_CLIENT_AUTHENTICATE_ERRORS.authenticated_session_approve_publish_failure, + ); + throw error; + } + await this.client.auth.requests.delete(id, { message: "fulfilled", code: 0 }); await this.client.core.pairing.activate({ topic: pendingRequest.pairingTopic }); + + this.client.core.eventClient.deleteEvent({ eventId: event.eventId }); + return { session }; }; @@ -1107,6 +1231,13 @@ export class Engine extends IEngine { }; private deleteProposal: EnginePrivate["deleteProposal"] = async (id, expirerHasDeleted) => { + if (expirerHasDeleted) { + try { + const proposal = this.client.proposal.get(id); + const event = this.client.core.eventClient.getEvent({ topic: proposal.pairingTopic }); + event?.setError(EVENT_CLIENT_SESSION_ERRORS.proposal_expired); + } catch (error) {} + } await Promise.all([ this.client.proposal.delete(id, getSdkError("USER_DISCONNECTED")), expirerHasDeleted ? Promise.resolve() : this.client.core.expirer.del(id), @@ -1460,6 +1591,7 @@ export class Engine extends IEngine { ) => { const { params, id } = payload; try { + const event = this.client.core.eventClient.getEvent({ topic }); this.isValidConnect({ ...payload.params }); const expiryTimestamp = params.expiryTimestamp || calcExpiry(ENGINE_RPC_OPTS.wc_sessionPropose.req.ttl); @@ -1467,6 +1599,12 @@ export class Engine extends IEngine { await this.setProposal(id, proposal); const hash = hashMessage(JSON.stringify(payload)); const verifyContext = await this.getVerifyContext(hash, proposal.proposer.metadata); + + if (this.client.events.listenerCount("session_proposal") === 0) { + console.warn("No listener for session_proposal event"); + event?.setError(EVENT_CLIENT_PAIRING_ERRORS.proposal_listener_not_found); + } + event?.addTrace(EVENT_CLIENT_PAIRING_TRACES.emit_session_proposal); this.client.events.emit("session_proposal", { id, params: proposal, verifyContext }); } catch (err: any) { await this.sendError({ diff --git a/packages/types/src/core/core.ts b/packages/types/src/core/core.ts index bd98a6a91..9b93f28e7 100644 --- a/packages/types/src/core/core.ts +++ b/packages/types/src/core/core.ts @@ -23,6 +23,7 @@ export declare namespace CoreTypes { storageOptions?: KeyValueStorageOptions; maxLogBlobSizeInBytes?: number; customStoragePrefix?: string; + telemetryEnabled?: boolean; } interface Metadata { @@ -59,7 +60,7 @@ export abstract class ICore extends IEvents { public abstract verify: IVerify; public abstract echoClient: IEchoClient; public abstract eventClient: IEventClient; - + constructor(public opts?: CoreTypes.Options) { super(); } diff --git a/packages/types/src/core/events.ts b/packages/types/src/core/events.ts index 00e1cca89..271c01153 100644 --- a/packages/types/src/core/events.ts +++ b/packages/types/src/core/events.ts @@ -7,6 +7,8 @@ export declare namespace EventClientTypes { bundleId: string; timestamp: number; props: Props; + addTrace: (trace: string) => void; + setError: (error: string) => void; } export interface Props { @@ -26,14 +28,21 @@ export declare namespace EventClientTypes { export abstract class IEventClient { public abstract readonly context: string; - constructor(public core: ICore, public logger: Logger) {} + constructor(public core: ICore, public logger: Logger, public telemetryEnabled: boolean) {} public abstract createEvent(params: { event?: "ERROR"; - type: string; + type?: string; properties: { topic: string; trace: EventClientTypes.Trace; }; - }): Promise; + }): EventClientTypes.Event; + + public abstract getEvent(params: { + eventId?: string; + topic?: string; + }): EventClientTypes.Event | undefined; + + public abstract deleteEvent(params: { eventId: string }): void; } diff --git a/packages/types/src/core/index.ts b/packages/types/src/core/index.ts index 210ebeb9c..513fe09cc 100644 --- a/packages/types/src/core/index.ts +++ b/packages/types/src/core/index.ts @@ -11,4 +11,4 @@ export * from "./expirer"; export * from "./pairing"; export * from "./verify"; export * from "./echo"; -export * from "./events"; \ No newline at end of file +export * from "./events"; diff --git a/packages/utils/src/misc.ts b/packages/utils/src/misc.ts index da194ec65..28ed07815 100644 --- a/packages/utils/src/misc.ts +++ b/packages/utils/src/misc.ts @@ -414,3 +414,16 @@ export async function getDeepLink(store: IKeyValueStorage, key: string) { export function getCommonValuesInArrays(arr1: T[], arr2: T[]): T[] { return arr1.filter((value) => arr2.includes(value)); } + +export function uuidv4() { + if (crypto?.randomUUID) { + return crypto.randomUUID(); + } + + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/gu, (c) => { + const r = (Math.random() * 16) | 0; + const v = c === "x" ? r : (r & 0x3) | 0x8; + + return v.toString(16); + }); +} diff --git a/providers/ethereum-provider/src/EthereumProvider.ts b/providers/ethereum-provider/src/EthereumProvider.ts index 7d285ca7d..eea76c2d2 100644 --- a/providers/ethereum-provider/src/EthereumProvider.ts +++ b/providers/ethereum-provider/src/EthereumProvider.ts @@ -8,7 +8,12 @@ import { RequestArguments, QrModalOptions, } from "./types"; -import { Metadata, Namespace, UniversalProvider } from "@walletconnect/universal-provider"; +import { + Metadata, + Namespace, + UniversalProvider, + UniversalProviderOpts, +} from "@walletconnect/universal-provider"; import { AuthTypes, SessionTypes, SignClientTypes } from "@walletconnect/types"; import { JsonRpcResult } from "@walletconnect/jsonrpc-types"; import { @@ -219,7 +224,8 @@ export type EthereumProviderOptions = { disableProviderPing?: boolean; relayUrl?: string; storageOptions?: KeyValueStorageOptions; -} & ChainsProps; +} & ChainsProps & + UniversalProviderOpts; export class EthereumProvider implements IEthereumProvider { public events = new EventEmitter(); @@ -569,6 +575,8 @@ export class EthereumProvider implements IEthereumProvider { disableProviderPing: opts.disableProviderPing, relayUrl: opts.relayUrl, storageOptions: opts.storageOptions, + customStoragePrefix: opts.customStoragePrefix, + telemetryEnabled: opts.telemetryEnabled, }); this.registerEventListeners(); await this.loadPersistedSession(); diff --git a/providers/universal-provider/src/UniversalProvider.ts b/providers/universal-provider/src/UniversalProvider.ts index e09088c96..d003e2799 100644 --- a/providers/universal-provider/src/UniversalProvider.ts +++ b/providers/universal-provider/src/UniversalProvider.ts @@ -282,6 +282,7 @@ export class UniversalProvider implements IUniversalProvider { this.client = this.providerOpts.client || (await SignClient.init({ + core: this.providerOpts.core, logger: this.providerOpts.logger || LOGGER, relayUrl: this.providerOpts.relayUrl || RELAY_URL, projectId: this.providerOpts.projectId, @@ -289,6 +290,8 @@ export class UniversalProvider implements IUniversalProvider { storageOptions: this.providerOpts.storageOptions, storage: this.providerOpts.storage, name: this.providerOpts.name, + customStoragePrefix: this.providerOpts.customStoragePrefix, + telemetryEnabled: this.providerOpts.telemetryEnabled, })); this.logger.trace(`SignClient Initialized`); diff --git a/providers/universal-provider/src/types/misc.ts b/providers/universal-provider/src/types/misc.ts index 65b8d126e..a414c021b 100644 --- a/providers/universal-provider/src/types/misc.ts +++ b/providers/universal-provider/src/types/misc.ts @@ -6,7 +6,7 @@ import { IEvents } from "@walletconnect/events"; import { Logger } from "@walletconnect/logger"; import { IProvider } from "./providers"; -export interface UniversalProviderOpts { +export interface UniversalProviderOpts extends SignClientTypes.Options { projectId?: string; metadata?: Metadata; logger?: string | Logger; From 6e08f09a21441cc500e0b87b9d64373a8359e4dd Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Tue, 16 Jul 2024 13:46:26 +0300 Subject: [PATCH 05/61] feat: tests --- packages/core/test/events.spec.ts | 196 +++++++++++++++++++ packages/sign-client/test/sdk/client.spec.ts | 121 +++++++++++- 2 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 packages/core/test/events.spec.ts diff --git a/packages/core/test/events.spec.ts b/packages/core/test/events.spec.ts new file mode 100644 index 000000000..c5928f709 --- /dev/null +++ b/packages/core/test/events.spec.ts @@ -0,0 +1,196 @@ +import { expect, describe, it } from "vitest"; +import Core, { + EVENTS_STORAGE_CLEANUP_INTERVAL, + EVENT_CLIENT_CONTEXT, + EVENT_CLIENT_PAIRING_ERRORS, +} from "../src"; +import { TEST_CORE_OPTIONS } from "./shared"; +import { toMiliseconds } from "@walletconnect/time"; + +describe("Events Client", () => { + it("Init events client", async () => { + const core = new Core(TEST_CORE_OPTIONS); + await core.start(); + expect(core.eventClient).toBeDefined(); + expect(core.eventClient.context).toBe(EVENT_CLIENT_CONTEXT); + expect(core.eventClient.core).toBe(core); + // @ts-expect-error - accessing private properties for testing + expect(core.eventClient.events.size).toBe(0); + }); + it("should create event", async () => { + const core = new Core(TEST_CORE_OPTIONS); + await core.start(); + const type = EVENT_CLIENT_PAIRING_ERRORS.active_pairing_already_exists; + const topic = "test topic"; + const trace = ["test trace", "test trace 2"]; + const eventType = "ERROR"; + const event = core.eventClient.createEvent({ + event: eventType, + type, + properties: { + topic, + trace, + }, + }); + expect(event).toBeDefined(); + expect(event.props.event).toBe(eventType); + expect(event.props.type).toBe(type); + expect(event.props.properties.topic).toBe(topic); + expect(event.props.properties.trace).toBe(trace); + // @ts-expect-error - accessing private properties for testing + expect(core.eventClient.events.size).toBe(1); + }); + + it("should create multiple events", async () => { + const core = new Core(TEST_CORE_OPTIONS); + await core.start(); + const type = EVENT_CLIENT_PAIRING_ERRORS.active_pairing_already_exists; + const eventsToCreate = 10; + for (let i = 0; i < eventsToCreate; i++) { + const topic = "test topic"; + const trace = ["test trace", "test trace 2"]; + const eventType = "ERROR"; + const event = core.eventClient.createEvent({ + event: eventType, + type, + properties: { + topic, + trace, + }, + }); + expect(event).toBeDefined(); + expect(event.props.event).toBe(eventType); + expect(event.props.type).toBe(type); + expect(event.props.properties.topic).toBe(topic); + expect(event.props.properties.trace).toBe(trace); + } + // @ts-expect-error - accessing private properties for testing + expect(core.eventClient.events.size).toBe(eventsToCreate); + }); + it("should create & delete event", async () => { + const core = new Core(TEST_CORE_OPTIONS); + await core.start(); + const type = EVENT_CLIENT_PAIRING_ERRORS.active_pairing_already_exists; + const topic = "test topic"; + const trace = ["test trace", "test trace 2"]; + const eventType = "ERROR"; + const event = core.eventClient.createEvent({ + event: eventType, + type, + properties: { + topic, + trace, + }, + }); + expect(event).toBeDefined(); + expect(event.props.event).toBe(eventType); + expect(event.props.type).toBe(type); + expect(event.props.properties.topic).toBe(topic); + expect(event.props.properties.trace).toBe(trace); + // @ts-expect-error - accessing private properties for testing + expect(core.eventClient.events.size).toBe(1); + + core.eventClient.deleteEvent({ eventId: event.eventId }); + + // @ts-expect-error - accessing private properties for testing + expect(core.eventClient.events.size).toBe(0); + }); + it("should add trace", async () => { + const core = new Core(TEST_CORE_OPTIONS); + await core.start(); + const type = EVENT_CLIENT_PAIRING_ERRORS.active_pairing_already_exists; + const topic = "test topic"; + const trace = ["test trace", "test trace 2"]; + const eventType = "ERROR"; + const event = core.eventClient.createEvent({ + event: eventType, + type, + properties: { + topic, + trace, + }, + }); + expect(event).toBeDefined(); + expect(event.props.event).toBe(eventType); + expect(event.props.type).toBe(type); + expect(event.props.properties.topic).toBe(topic); + expect(event.props.properties.trace).toBe(trace); + expect(event.addTrace).to.exist; + expect(event.setError).to.exist; + + const additionalTrace = ["test trace 3", "test trace 4"]; + const additionlTraceLenght = additionalTrace.length; + const defaultTraceLength = trace.length; + event.addTrace(additionalTrace[0]); + event.addTrace(additionalTrace[1]); + expect(event.props.properties.trace.length).toEqual(defaultTraceLength + additionlTraceLenght); + expect(event.props.properties.trace).toContain(additionalTrace[0]); + expect(event.props.properties.trace).toContain(additionalTrace[1]); + }); + it("should set error type", async () => { + const core = new Core(TEST_CORE_OPTIONS); + await core.start(); + const topic = "test topic"; + const trace = ["test trace", "test trace 2"]; + const eventType = "ERROR"; + const event = core.eventClient.createEvent({ + event: eventType, + properties: { + topic, + trace, + }, + }); + expect(event).toBeDefined(); + expect(event.props.event).toBe(eventType); + expect(event.props.type).toBe(""); + expect(event.props.properties.topic).toBe(topic); + expect(event.props.properties.trace).toBe(trace); + expect(event.addTrace).to.exist; + expect(event.setError).to.exist; + + event.setError(EVENT_CLIENT_PAIRING_ERRORS.active_pairing_already_exists); + + expect(event.props.type).toBe(EVENT_CLIENT_PAIRING_ERRORS.active_pairing_already_exists); + }); + it("should clean up old events", async () => { + const core = new Core(TEST_CORE_OPTIONS); + await core.start(); + const type = EVENT_CLIENT_PAIRING_ERRORS.active_pairing_already_exists; + const topic = "test topic"; + const trace = ["test trace", "test trace 2"]; + const eventType = "ERROR"; + const event = core.eventClient.createEvent({ + event: eventType, + type, + properties: { + topic, + trace, + }, + }); + + event.timestamp = Date.now() - toMiliseconds(EVENTS_STORAGE_CLEANUP_INTERVAL); + // @ts-expect-error - accessing private properties + expect(core.eventClient.events.size).toBe(1); + await new Promise((resolve) => setTimeout(resolve, 5000)); + // @ts-expect-error - accessing private properties + expect(core.eventClient.events.size).toBe(0); + }); + it("should not store events when telemetry is disabled", async () => { + const core = new Core({ ...TEST_CORE_OPTIONS, telemetryEnabled: false }); + await core.start(); + const type = EVENT_CLIENT_PAIRING_ERRORS.active_pairing_already_exists; + const topic = "test topic"; + const trace = ["test trace", "test trace 2"]; + const eventType = "ERROR"; + core.eventClient.createEvent({ + event: eventType, + type, + properties: { + topic, + trace, + }, + }); + // @ts-expect-error - accessing private properties + expect(core.eventClient.events.size).toBe(0); + }); +}); diff --git a/packages/sign-client/test/sdk/client.spec.ts b/packages/sign-client/test/sdk/client.spec.ts index bbcd20ddf..7a14d871d 100644 --- a/packages/sign-client/test/sdk/client.spec.ts +++ b/packages/sign-client/test/sdk/client.spec.ts @@ -32,7 +32,12 @@ import { initTwoPairedClients, TEST_CONNECT_PARAMS, } from "../shared"; -import { RELAYER_EVENTS } from "@walletconnect/core"; +import { + EVENT_CLIENT_PAIRING_ERRORS, + EVENT_CLIENT_PAIRING_TRACES, + EVENT_CLIENT_SESSION_ERRORS, + RELAYER_EVENTS, +} from "@walletconnect/core"; describe("Sign Client Integration", () => { it("init", async () => { @@ -878,4 +883,118 @@ describe("Sign Client Integration", () => { await deleteClients(clients); }); }); + describe("Events Client", () => { + it("should create event during pairing flow", async () => { + const clients = await initTwoClients(); + const { uri } = await clients.A.connect({}); + if (!uri) throw new Error("URI is undefined"); + await clients.B.pair({ uri }); + const { topic } = parseUri(uri); + expect(clients.B.core.eventClient.events.size).to.eq(1); + const event = clients.B.core.eventClient.getEvent({ topic }); + if (!event) throw new Error("Event is undefined"); + expect(event).to.exist; + expect(event.props.event).to.eq("ERROR"); + expect(event.props.type).to.eq(""); // there is no type yet as no error has happened + expect(event.props.properties.topic).to.eq(topic); + expect(event.props.properties.trace).to.exist; + expect(event.props.properties.trace.length).to.toBeGreaterThan(0); + + await new Promise((resolve) => { + clients.B.once("session_proposal", (params) => { + resolve(); + }); + }); + + expect(event.props.properties.trace).to.include( + EVENT_CLIENT_PAIRING_TRACES.emit_session_proposal, + ); + + vi.useFakeTimers({ shouldAdvanceTime: true }); + vi.setSystemTime(Date.now() + 60_000 * 6); + await throttle(5_000); + + expect(event.props.type).to.eq(EVENT_CLIENT_PAIRING_ERRORS.proposal_expired); + + vi.useRealTimers(); + + await deleteClients(clients); + }); + it("should set missing event listener error type", async () => { + const clients = await initTwoClients(); + const { uri } = await clients.A.connect({}); + if (!uri) throw new Error("URI is undefined"); + await clients.B.pair({ uri }); + const { topic } = parseUri(uri); + expect(clients.B.core.eventClient.events.size).to.eq(1); + const event = clients.B.core.eventClient.getEvent({ topic }); + if (!event) throw new Error("Event is undefined"); + expect(event).to.exist; + expect(event.props.event).to.eq("ERROR"); + expect(event.props.type).to.eq(""); // there is no type yet as no error has happened + expect(event.props.properties.topic).to.eq(topic); + expect(event.props.properties.trace).to.exist; + expect(event.props.properties.trace.length).to.toBeGreaterThan(0); + + // wait for the proposal to be received + await throttle(5_000); + + expect(event.props.type).to.eq(EVENT_CLIENT_PAIRING_ERRORS.proposal_listener_not_found); + + await deleteClients(clients); + }); + it("should create event during approve session flow when proposal is not found", async () => { + const wallet = await SignClient.init({ + ...TEST_SIGN_CLIENT_OPTIONS, + name: "wallet", + metadata: TEST_WALLET_METADATA, + }); + + await expect(wallet.approve({ id: 123, namespaces: TEST_NAMESPACES })).rejects.toThrowError(); + expect(wallet.core.eventClient.events.size).to.eq(1); + const event = wallet.core.eventClient.getEvent({ topic: "123" }); + if (!event) throw new Error("Event is undefined"); + expect(event).to.exist; + expect(event.props.event).to.eq("ERROR"); + expect(event.props.type).to.eq(EVENT_CLIENT_SESSION_ERRORS.proposal_not_found); + expect(event.props.properties.topic).to.eq("123"); + expect(event.props.properties.trace).to.exist; + expect(event.props.properties.trace.length).to.toBeGreaterThan(0); + await deleteClients({ A: wallet, B: undefined }); + }); + it("should create event during approve session flow and delete it on successful approve", async () => { + const clients = await initTwoClients(); + const { uri } = await clients.A.connect({}); + if (!uri) throw new Error("URI is undefined"); + await clients.B.pair({ uri }); + const { topic } = parseUri(uri); + expect(clients.B.core.eventClient.events.size).to.eq(1); + const event = clients.B.core.eventClient.getEvent({ topic }); + if (!event) throw new Error("Event is undefined"); + expect(event).to.exist; + expect(event.props.event).to.eq("ERROR"); + expect(event.props.type).to.eq(""); // there is no type yet as no error has happened + expect(event.props.properties.topic).to.eq(topic); + expect(event.props.properties.trace).to.exist; + expect(event.props.properties.trace.length).to.toBeGreaterThan(0); + + await new Promise((resolve) => { + clients.B.once("session_proposal", async (params) => { + // confirm the emit_session_proposal trace + expect(event.props.properties.trace).to.include( + EVENT_CLIENT_PAIRING_TRACES.emit_session_proposal, + ); + await clients.B.approve({ id: params.id, namespaces: TEST_NAMESPACES }); + resolve(); + }); + }); + + await throttle(2_000); + + // the event should be deleted + expect(clients.B.core.eventClient.events.size).to.eq(0); + + await deleteClients(clients); + }); + }); }); From ff96460c4a1eb2f739c206d2c59b0de790ee9772 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Tue, 16 Jul 2024 13:49:52 +0300 Subject: [PATCH 06/61] refactor: cleanup --- packages/core/src/controllers/events.ts | 48 ++++++++++++------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/packages/core/src/controllers/events.ts b/packages/core/src/controllers/events.ts index bd41fdc57..8fdc1e304 100644 --- a/packages/core/src/controllers/events.ts +++ b/packages/core/src/controllers/events.ts @@ -28,27 +28,11 @@ export class EventClient extends IEventClient { this.setEventListeners(); }); } else { - // overrite any persisted events with empty array + // overwrite any persisted events with an empty array this.persist(); } } - private setEventListeners = () => { - this.core.heartbeat.on(HEARTBEAT_EVENTS.pulse, async () => { - if (this.toPersist) await this.persist(); - // cleanup events older than EVENTS_STORAGE_CLEANUP_INTERVAL - this.events.forEach((event) => { - if ( - fromMiliseconds(Date.now()) - fromMiliseconds(event.timestamp) > - EVENTS_STORAGE_CLEANUP_INTERVAL - ) { - this.events.delete(event.eventId); - this.toPersist = true; - } - }); - }); - }; - get storageKey() { return ( this.storagePrefix + this.storageVersion + this.core.customStoragePrefix + "//" + this.context @@ -88,13 +72,6 @@ export class EventClient extends IEventClient { return eventObj; }; - private setMethods = (eventId: string) => { - return { - addTrace: (trace: string) => this.addTrace(eventId, trace), - setError: (errorType: string) => this.setError(eventId, errorType), - }; - }; - public getEvent: IEventClient["getEvent"] = (params) => { const { eventId, topic } = params; if (eventId) { @@ -118,6 +95,29 @@ export class EventClient extends IEventClient { this.toPersist = true; }; + private setEventListeners = () => { + this.core.heartbeat.on(HEARTBEAT_EVENTS.pulse, async () => { + if (this.toPersist) await this.persist(); + // cleanup events older than EVENTS_STORAGE_CLEANUP_INTERVAL + this.events.forEach((event) => { + if ( + fromMiliseconds(Date.now()) - fromMiliseconds(event.timestamp) > + EVENTS_STORAGE_CLEANUP_INTERVAL + ) { + this.events.delete(event.eventId); + this.toPersist = true; + } + }); + }); + }; + + private setMethods = (eventId: string) => { + return { + addTrace: (trace: string) => this.addTrace(eventId, trace), + setError: (errorType: string) => this.setError(eventId, errorType), + }; + }; + private addTrace = async (eventId: string, trace: string) => { const event = this.events.get(eventId); if (!event) return; From 8c2b7e9612576a81ed99cde91a408f300bc00b8d Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Tue, 16 Jul 2024 14:42:50 +0300 Subject: [PATCH 07/61] fix: crypto in node 18 --- packages/utils/src/misc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utils/src/misc.ts b/packages/utils/src/misc.ts index 28ed07815..725f33d12 100644 --- a/packages/utils/src/misc.ts +++ b/packages/utils/src/misc.ts @@ -416,7 +416,7 @@ export function getCommonValuesInArrays(arr1: T[] } export function uuidv4() { - if (crypto?.randomUUID) { + if (typeof crypto !== "undefined" && crypto?.randomUUID) { return crypto.randomUUID(); } From 90cd4fae45675dad3429ade837de03bf6004488b Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Tue, 16 Jul 2024 14:57:53 +0300 Subject: [PATCH 08/61] fix: validation order --- .../sign-client/src/controllers/engine.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index 641ec9d57..4a586db4d 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -264,7 +264,7 @@ export class Engine extends IEngine { public approve: IEngine["approve"] = async (params) => { const configEvent = this.client.core.eventClient.createEvent({ properties: { - topic: params.id.toString(), + topic: params?.id?.toString(), trace: [EVENT_CLIENT_SESSION_TRACES.session_approve_started], }, }); @@ -275,16 +275,6 @@ export class Engine extends IEngine { throw error; } - const { id, relayProtocol, namespaces, sessionProperties, sessionConfig } = params; - let proposal; - try { - proposal = this.client.proposal.get(id); - } catch (error) { - this.client.logger.error(`approve() -> proposal.get(${id}) failed`); - configEvent.setError(EVENT_CLIENT_SESSION_ERRORS.proposal_not_found); - throw error; - } - try { await this.isValidApprove(params); } catch (error) { @@ -295,6 +285,16 @@ export class Engine extends IEngine { throw error; } + const { id, relayProtocol, namespaces, sessionProperties, sessionConfig } = params; + let proposal; + try { + proposal = this.client.proposal.get(id); + } catch (error) { + this.client.logger.error(`approve() -> proposal.get(${id}) failed`); + configEvent.setError(EVENT_CLIENT_SESSION_ERRORS.proposal_not_found); + throw error; + } + this.client.core.eventClient.deleteEvent({ eventId: configEvent.eventId }); const { pairingTopic, proposer, requiredNamespaces, optionalNamespaces } = proposal; From 0c970b9933b4e9335fc7fea2d83583703a9ba5fc Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Tue, 16 Jul 2024 16:43:21 +0300 Subject: [PATCH 09/61] fix: error smg on missing proposal --- packages/sign-client/src/controllers/engine.ts | 18 +++++++++--------- .../sign-client/test/sdk/validation.spec.ts | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index 4a586db4d..b8649cc78 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -274,7 +274,13 @@ export class Engine extends IEngine { configEvent.setError(EVENT_CLIENT_SESSION_ERRORS.no_internet_connection); throw error; } - + try { + await this.isValidProposalId(params?.id); + } catch (error) { + this.client.logger.error(`approve() -> proposal.get(${params?.id}) failed`); + configEvent.setError(EVENT_CLIENT_SESSION_ERRORS.proposal_not_found); + throw error; + } try { await this.isValidApprove(params); } catch (error) { @@ -286,14 +292,8 @@ export class Engine extends IEngine { } const { id, relayProtocol, namespaces, sessionProperties, sessionConfig } = params; - let proposal; - try { - proposal = this.client.proposal.get(id); - } catch (error) { - this.client.logger.error(`approve() -> proposal.get(${id}) failed`); - configEvent.setError(EVENT_CLIENT_SESSION_ERRORS.proposal_not_found); - throw error; - } + + const proposal = this.client.proposal.get(id); this.client.core.eventClient.deleteEvent({ eventId: configEvent.eventId }); diff --git a/packages/sign-client/test/sdk/validation.spec.ts b/packages/sign-client/test/sdk/validation.spec.ts index 938282d52..4cd5f3ee5 100644 --- a/packages/sign-client/test/sdk/validation.spec.ts +++ b/packages/sign-client/test/sdk/validation.spec.ts @@ -125,7 +125,7 @@ describe("Sign Client Validation", () => { describe("approve", () => { it("throws when no params are passed", async () => { await expect(clients.A.approve()).rejects.toThrowError( - "Missing or invalid. approve() params: undefined", + "Missing or invalid. proposal id should be a number: undefined", ); }); From 5331f390bb5477256d249ba11bd0155da965b897 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Tue, 16 Jul 2024 16:56:57 +0300 Subject: [PATCH 10/61] fix: adds `shouldClearNativeTimers: true` --- packages/sign-client/test/sdk/client.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sign-client/test/sdk/client.spec.ts b/packages/sign-client/test/sdk/client.spec.ts index 7a14d871d..30e16b9ef 100644 --- a/packages/sign-client/test/sdk/client.spec.ts +++ b/packages/sign-client/test/sdk/client.spec.ts @@ -911,6 +911,7 @@ describe("Sign Client Integration", () => { ); vi.useFakeTimers({ shouldAdvanceTime: true }); + vi.useFakeTimers({ shouldAdvanceTime: true, shouldClearNativeTimers: true }); vi.setSystemTime(Date.now() + 60_000 * 6); await throttle(5_000); From 951f99306c1a9465353d9281ebbf01d0f3580c23 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Tue, 30 Jul 2024 14:57:32 +0300 Subject: [PATCH 11/61] wip: verify v2 - submit attestation --- packages/core/src/constants/verify.ts | 1 + packages/core/src/controllers/verify.ts | 190 +++++++++--------- packages/sign-client/src/client.ts | 1 - .../sign-client/src/controllers/engine.ts | 10 +- packages/types/src/core/verify.ts | 4 +- 5 files changed, 103 insertions(+), 103 deletions(-) diff --git a/packages/core/src/constants/verify.ts b/packages/core/src/constants/verify.ts index 479fe969d..511150b29 100644 --- a/packages/core/src/constants/verify.ts +++ b/packages/core/src/constants/verify.ts @@ -3,5 +3,6 @@ export const VERIFY_CONTEXT = "verify-api"; const VERIFY_SERVER_COM = "https://verify.walletconnect.com"; const VERIFY_SERVER_ORG = "https://verify.walletconnect.org"; export const VERIFY_SERVER = VERIFY_SERVER_ORG; +export const VERIFY_SERVER_V2 = "https://verify.walletconnect.com/v2"; export const TRUSTED_VERIFY_URLS = [VERIFY_SERVER_COM, VERIFY_SERVER_ORG]; diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index a710b0e2b..cda5b089e 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -3,7 +3,7 @@ import { IVerify } from "@walletconnect/types"; import { isBrowser, isNode, isReactNative } from "@walletconnect/utils"; import { FIVE_SECONDS, ONE_SECOND, toMiliseconds } from "@walletconnect/time"; -import { TRUSTED_VERIFY_URLS, VERIFY_CONTEXT, VERIFY_SERVER } from "../constants"; +import { TRUSTED_VERIFY_URLS, VERIFY_CONTEXT, VERIFY_SERVER, VERIFY_SERVER_V2 } from "../constants"; export class Verify extends IVerify { public name = VERIFY_CONTEXT; @@ -17,6 +17,7 @@ export class Verify extends IVerify { // flag to disable verify when the iframe fails to load on main & fallback urls. // this means Verify API is not enabled for the current projectId and there's no point in trying to initialize it again. private verifyDisabled = false; + private verifyUrlV2 = VERIFY_SERVER_V2; constructor(public projectId: string, public logger: Logger) { super(projectId, logger); @@ -24,38 +25,44 @@ export class Verify extends IVerify { this.verifyUrl = VERIFY_SERVER; this.abortController = new AbortController(); this.isDevEnv = isNode() && process.env.IS_VITEST; + console.log("Verify v2 init", this.verifyUrlV2); } - public init: IVerify["init"] = async (params) => { - if (this.verifyDisabled) return; - - // ignore on non browser environments - if (isReactNative() || !isBrowser()) return; - - const verifyUrl = this.getVerifyUrl(params?.verifyUrl); - // if init is called again with a different url, remove the iframe and start over - if (this.verifyUrl !== verifyUrl) { - this.removeIframe(); - } - this.verifyUrl = verifyUrl; - - try { - await this.createIframe(); - } catch (error) { - this.logger.info(`Verify iframe failed to load: ${this.verifyUrl}`); - this.logger.info(error); - // if the iframe fails to load, disable verify - this.verifyDisabled = true; - } - }; - public register: IVerify["register"] = async (params) => { - if (!this.initialized) { - this.addToQueue(params.attestationId); - await this.init(); - } else { - this.sendPost(params.attestationId); - } + console.log("register", params); + const { id, decryptedId } = params; + const url = `${this.verifyUrlV2}/attestation?projectId=${this.projectId}`; + const response = await fetch(url, { + method: "POST", + body: JSON.stringify({ id, decryptedId }), + headers: { + origin: "https://8951-78-130-198-143.ngrok-free.app/", + }, + }); + const { srcdoc } = { srcdoc: "" }; + + console.log("srcdoc", await response.json()); + const attestatiatonJwt = await new Promise((resolve) => { + const iframe = document.createElement("iframe"); + iframe.srcdoc = srcdoc; + iframe.style.display = "none"; + const listener = (event: MessageEvent) => { + console.log("message event received", event); + if (event.origin === "verify.walletconnect.com") { + const data = JSON.parse(event.data); + if (data.type === "verify_attestation") { + // best-practice field + window.removeEventListener("message", listener); + document.body.removeChild(iframe); + console.log("attestation", data.attestation); + resolve(data.attestation); + } + } + }; + document.body.appendChild(iframe); + window.addEventListener("message", listener); + }); + console.log("attestatiatonJwt", attestatiatonJwt); }; public resolve: IVerify["resolve"] = async (params) => { @@ -80,77 +87,70 @@ export class Verify extends IVerify { return result.status === 200 ? await result.json() : undefined; }; - private addToQueue = (attestationId: string) => { - this.queue.push(attestationId); - }; - - private processQueue = () => { - if (this.queue.length === 0) return; - this.queue.forEach((attestationId) => this.sendPost(attestationId)); - this.queue = []; - }; - - private sendPost = (attestationId: string) => { - try { - if (!this.iframe) return; - this.iframe.contentWindow?.postMessage(attestationId, "*"); // setting targetOrigin to "*" fixes the `Failed to execute 'postMessage' on 'DOMWindow': The target origin provided...` while the iframe is still loading - this.logger.info(`postMessage sent: ${attestationId} ${this.verifyUrl}`); - } catch (e) {} - }; - - private createIframe = async () => { - let iframeOnLoadResolve: () => void; - const onMessage = (event: MessageEvent) => { - if (event.data === "verify_ready") { - this.onInit(); - window.removeEventListener("message", onMessage); - iframeOnLoadResolve(); - } - }; - await Promise.race([ - new Promise((resolve) => { - const existingIframe = document.getElementById(VERIFY_CONTEXT); - if (existingIframe) { - this.iframe = existingIframe as HTMLIFrameElement; - this.onInit(); - return resolve(); - } - - window.addEventListener("message", onMessage); - const iframe = document.createElement("iframe"); - iframe.id = VERIFY_CONTEXT; - iframe.src = `${this.verifyUrl}/${this.projectId}`; - iframe.style.display = "none"; - document.body.append(iframe); - this.iframe = iframe; - iframeOnLoadResolve = resolve; - }), - new Promise((_, reject) => - setTimeout(() => { - window.removeEventListener("message", onMessage); - reject("verify iframe load timeout"); - }, toMiliseconds(FIVE_SECONDS)), - ), - ]); - }; - - private onInit = () => { - this.initialized = true; - this.processQueue(); - }; + // private addToQueue = (attestationId: string) => { + // this.queue.push(attestationId); + // }; + + // private processQueue = () => { + // if (this.queue.length === 0) return; + // this.queue.forEach((attestationId) => this.sendPost(attestationId)); + // this.queue = []; + // }; + + // private sendPost = (attestationId: string) => { + // try { + // if (!this.iframe) return; + // this.iframe.contentWindow?.postMessage(attestationId, "*"); // setting targetOrigin to "*" fixes the `Failed to execute 'postMessage' on 'DOMWindow': The target origin provided...` while the iframe is still loading + // this.logger.info(`postMessage sent: ${attestationId} ${this.verifyUrl}`); + // } catch (e) {} + // }; + + // private createIframe = async () => { + // let iframeOnLoadResolve: () => void; + // const onMessage = (event: MessageEvent) => { + // if (event.data === "verify_ready") { + // this.onInit(); + // window.removeEventListener("message", onMessage); + // iframeOnLoadResolve(); + // } + // }; + // await Promise.race([ + // new Promise((resolve) => { + // const existingIframe = document.getElementById(VERIFY_CONTEXT); + // if (existingIframe) { + // this.iframe = existingIframe as HTMLIFrameElement; + // this.onInit(); + // return resolve(); + // } + + // window.addEventListener("message", onMessage); + // const iframe = document.createElement("iframe"); + // iframe.id = VERIFY_CONTEXT; + // iframe.src = `${this.verifyUrl}/${this.projectId}`; + // iframe.style.display = "none"; + // document.body.append(iframe); + // this.iframe = iframe; + // iframeOnLoadResolve = resolve; + // }), + // new Promise((_, reject) => + // setTimeout(() => { + // window.removeEventListener("message", onMessage); + // reject("verify iframe load timeout"); + // }, toMiliseconds(FIVE_SECONDS)), + // ), + // ]); + // }; + + // private onInit = () => { + // this.initialized = true; + // this.processQueue(); + // }; private startAbortTimer(timer: number) { this.abortController = new AbortController(); return setTimeout(() => this.abortController.abort(), toMiliseconds(timer)); } - private removeIframe = () => { - if (!this.iframe) return; - this.iframe.remove(); - this.iframe = undefined; - this.initialized = false; - }; - private getVerifyUrl = (verifyUrl?: string) => { let url = verifyUrl || VERIFY_SERVER; if (!TRUSTED_VERIFY_URLS.includes(url)) { diff --git a/packages/sign-client/src/client.ts b/packages/sign-client/src/client.ts index 02f46dda7..fb6427f6a 100644 --- a/packages/sign-client/src/client.ts +++ b/packages/sign-client/src/client.ts @@ -251,7 +251,6 @@ export class SignClient extends ISignClient { await this.pendingRequest.init(); await this.engine.init(); await this.auth.init(); - this.core.verify.init({ verifyUrl: this.metadata.verifyUrl }); this.logger.info(`SignClient Initialization Success`); this.engine.processRelayMessageCache(); } catch (error: any) { diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index 01563ae6e..2577462e9 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -1181,10 +1181,7 @@ export class Engine extends IEngine { private sendRequest: EnginePrivate["sendRequest"] = async (args) => { const { topic, method, params, expiry, relayRpcId, clientRpcId, throwOnFailedPublish } = args; const payload = formatJsonRpcRequest(method, params, clientRpcId); - if (isBrowser() && METHODS_TO_VERIFY.includes(method)) { - const hash = hashMessage(JSON.stringify(payload)); - this.client.core.verify.register({ attestationId: hash }); - } + let message; try { message = await this.client.core.crypto.encode(topic, payload); @@ -1193,6 +1190,11 @@ export class Engine extends IEngine { this.client.logger.error(`sendRequest() -> core.crypto.encode() for topic ${topic} failed`); throw error; } + if (METHODS_TO_VERIFY.includes(method)) { + const decryptedId = hashMessage(JSON.stringify(payload)); + const id = hashMessage(message); + this.client.core.verify.register({ id, decryptedId }); + } const opts = ENGINE_RPC_OPTS[method].req; if (expiry) opts.ttl = expiry; if (relayRpcId) opts.id = relayRpcId; diff --git a/packages/types/src/core/verify.ts b/packages/types/src/core/verify.ts index 55a0327de..8adf31108 100644 --- a/packages/types/src/core/verify.ts +++ b/packages/types/src/core/verify.ts @@ -16,9 +16,7 @@ export abstract class IVerify { constructor(public projectId: string, public logger: Logger) {} - public abstract init(params?: { verifyUrl?: string }): Promise; - - public abstract register(params: { attestationId: string }): Promise; + public abstract register(params: { id: string; decryptedId: string }): Promise; public abstract resolve(params: { attestationId: string; From be88bb9d2f7d9cf74bc6957c35c0fb873cff8cfe Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Tue, 30 Jul 2024 15:26:11 +0300 Subject: [PATCH 12/61] fix: avoid deeplinking if document isn't in focus --- packages/utils/src/misc.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/utils/src/misc.ts b/packages/utils/src/misc.ts index da194ec65..fb177b215 100644 --- a/packages/utils/src/misc.ts +++ b/packages/utils/src/misc.ts @@ -379,6 +379,11 @@ export async function handleDeeplinkRedirect({ const env = getEnvironment(); if (env === ENV_MAP.browser) { + if (!getDocument()?.hasFocus()) { + console.warn("Document does not have focus, skipping deeplink."); + return; + } + if (link.startsWith("https://") || link.startsWith("http://")) { window.open(link, "_blank", "noreferrer noopener"); } else { From 059613149d745647cf9c406af019620e0cf2a88d Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Wed, 31 Jul 2024 11:41:57 +0300 Subject: [PATCH 13/61] chore: sents `attestation` with publish payload --- packages/core/src/controllers/publisher.ts | 7 ++++-- packages/core/src/controllers/relayer.ts | 1 + packages/core/src/controllers/verify.ts | 23 ++++++++++--------- .../sign-client/src/controllers/engine.ts | 5 ++-- packages/types/src/core/relayer.ts | 1 + packages/types/src/core/verify.ts | 2 +- 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/core/src/controllers/publisher.ts b/packages/core/src/controllers/publisher.ts index 4583d1d6b..91ec2b74a 100644 --- a/packages/core/src/controllers/publisher.ts +++ b/packages/core/src/controllers/publisher.ts @@ -63,8 +63,8 @@ export class Publisher extends IPublisher { this.logger.trace({ id, attempts }, `publisher.publish - attempt ${attempts}`); const publish = await createExpiringPromise( - this.rpcPublish(topic, message, ttl, relay, prompt, tag, id).catch((e) => - this.logger.warn(e), + this.rpcPublish(topic, message, ttl, relay, prompt, tag, id, opts?.attestation).catch( + (e) => this.logger.warn(e), ), this.publishTimeout, failedPublishMessage, @@ -121,6 +121,7 @@ export class Publisher extends IPublisher { prompt?: boolean, tag?: number, id?: number, + attestation?: string, ) { const api = getRelayProtocolApi(relay.protocol); const request: RequestArguments = { @@ -131,9 +132,11 @@ export class Publisher extends IPublisher { ttl, prompt, tag, + attestation, }, id, }; + console.log("rpc publish request", request); if (isUndefined(request.params?.prompt)) delete request.params?.prompt; if (isUndefined(request.params?.tag)) delete request.params?.tag; this.logger.debug(`Outgoing Relay Payload`); diff --git a/packages/core/src/controllers/relayer.ts b/packages/core/src/controllers/relayer.ts index dbf8967bd..e1d050853 100644 --- a/packages/core/src/controllers/relayer.ts +++ b/packages/core/src/controllers/relayer.ts @@ -444,6 +444,7 @@ export class Relayer extends IRelayer { private async onProviderPayload(payload: JsonRpcPayload) { this.logger.debug(`Incoming Relay Payload`); this.logger.trace({ type: "payload", direction: "incoming", payload }); + console.log("onProviderPayload", payload); if (isJsonRpcRequest(payload)) { if (!payload.method.endsWith(RELAYER_SUBSCRIBER_SUFFIX)) return; const event = (payload as JsonRpcRequest).params; diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index cda5b089e..5be7dbb7e 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -39,30 +39,31 @@ export class Verify extends IVerify { origin: "https://8951-78-130-198-143.ngrok-free.app/", }, }); - const { srcdoc } = { srcdoc: "" }; + const { srcdoc } = await response.json(); - console.log("srcdoc", await response.json()); + console.log("srcdoc", srcdoc); const attestatiatonJwt = await new Promise((resolve) => { const iframe = document.createElement("iframe"); iframe.srcdoc = srcdoc; + iframe.src = "https://verify.walletconnect.com"; iframe.style.display = "none"; const listener = (event: MessageEvent) => { console.log("message event received", event); - if (event.origin === "verify.walletconnect.com") { - const data = JSON.parse(event.data); - if (data.type === "verify_attestation") { - // best-practice field - window.removeEventListener("message", listener); - document.body.removeChild(iframe); - console.log("attestation", data.attestation); - resolve(data.attestation); - } + if (!event.data) return; + const data = JSON.parse(event.data); + if (data.type === "verify_attestation") { + // best-practice field + // window.removeEventListener("message", listener); + // document.body.removeChild(iframe); + console.log("attestation", data.attestation); + resolve(data.attestation); } }; document.body.appendChild(iframe); window.addEventListener("message", listener); }); console.log("attestatiatonJwt", attestatiatonJwt); + return attestatiatonJwt as string; }; public resolve: IVerify["resolve"] = async (params) => { diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index 2577462e9..9b04b2699 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -61,7 +61,6 @@ import { isValidRequest, isValidRequestExpiry, hashMessage, - isBrowser, isValidRequiredNamespaces, isValidResponse, isValidString, @@ -1190,12 +1189,14 @@ export class Engine extends IEngine { this.client.logger.error(`sendRequest() -> core.crypto.encode() for topic ${topic} failed`); throw error; } + let attestation; if (METHODS_TO_VERIFY.includes(method)) { const decryptedId = hashMessage(JSON.stringify(payload)); const id = hashMessage(message); - this.client.core.verify.register({ id, decryptedId }); + attestation = await this.client.core.verify.register({ id, decryptedId }); } const opts = ENGINE_RPC_OPTS[method].req; + opts.attestation = attestation; if (expiry) opts.ttl = expiry; if (relayRpcId) opts.id = relayRpcId; this.client.core.history.set(topic, payload); diff --git a/packages/types/src/core/relayer.ts b/packages/types/src/core/relayer.ts index 316d12fbb..bc34f5ce8 100644 --- a/packages/types/src/core/relayer.ts +++ b/packages/types/src/core/relayer.ts @@ -21,6 +21,7 @@ export declare namespace RelayerTypes { internal?: { throwOnFailedPublish?: boolean; }; + attestation?: string } export interface SubscribeOptions { diff --git a/packages/types/src/core/verify.ts b/packages/types/src/core/verify.ts index 8adf31108..e96f84b6d 100644 --- a/packages/types/src/core/verify.ts +++ b/packages/types/src/core/verify.ts @@ -16,7 +16,7 @@ export abstract class IVerify { constructor(public projectId: string, public logger: Logger) {} - public abstract register(params: { id: string; decryptedId: string }): Promise; + public abstract register(params: { id: string; decryptedId: string }): Promise; public abstract resolve(params: { attestationId: string; From d1966d413e749a98633658e86093942dc7556a22 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Thu, 1 Aug 2024 11:46:08 +0300 Subject: [PATCH 14/61] feat: verify v2 --- packages/core/src/controllers/publisher.ts | 14 +- packages/core/src/controllers/relayer.ts | 5 +- packages/core/src/controllers/verify.ts | 217 +++++++++++------- packages/core/src/core.ts | 2 +- .../sign-client/src/controllers/engine.ts | 85 ++++--- packages/types/src/core/publisher.ts | 2 +- packages/types/src/core/relayer.ts | 1 + packages/types/src/core/verify.ts | 11 +- packages/types/src/sign-client/engine.ts | 4 + packages/utils/src/crypto.ts | 41 ++++ 10 files changed, 258 insertions(+), 124 deletions(-) diff --git a/packages/core/src/controllers/publisher.ts b/packages/core/src/controllers/publisher.ts index 91ec2b74a..064fe4b40 100644 --- a/packages/core/src/controllers/publisher.ts +++ b/packages/core/src/controllers/publisher.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import { HEARTBEAT_EVENTS } from "@walletconnect/heartbeat"; import { JsonRpcPayload, RequestArguments } from "@walletconnect/jsonrpc-types"; import { generateChildLogger, getLoggerContext, Logger } from "@walletconnect/logger"; @@ -44,7 +45,18 @@ export class Publisher extends IPublisher { const prompt = opts?.prompt || false; const tag = opts?.tag || 0; const id = opts?.id || (getBigIntRpcId().toString() as any); - const params = { topic, message, opts: { ttl, relay, prompt, tag, id } }; + const params = { + topic, + message, + opts: { + ttl, + relay, + prompt, + tag, + id, + attestation: opts?.attestation, + }, + }; const failedPublishMessage = `Failed to publish payload, please try again. id:${id} tag:${tag}`; const startPublish = Date.now(); let result; diff --git a/packages/core/src/controllers/relayer.ts b/packages/core/src/controllers/relayer.ts index e1d050853..208ba3f6c 100644 --- a/packages/core/src/controllers/relayer.ts +++ b/packages/core/src/controllers/relayer.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import { EventEmitter } from "events"; import { JsonRpcProvider } from "@walletconnect/jsonrpc-provider"; import { @@ -448,8 +449,8 @@ export class Relayer extends IRelayer { if (isJsonRpcRequest(payload)) { if (!payload.method.endsWith(RELAYER_SUBSCRIBER_SUFFIX)) return; const event = (payload as JsonRpcRequest).params; - const { topic, message, publishedAt } = event.data; - const messageEvent: RelayerTypes.MessageEvent = { topic, message, publishedAt }; + const { topic, message, publishedAt, attestation } = event.data; + const messageEvent: RelayerTypes.MessageEvent = { topic, message, publishedAt, attestation }; this.logger.debug(`Emitting Relayer Payload`); this.logger.trace({ type: "event", event: event.id, ...messageEvent }); this.events.emit(event.id, messageEvent); diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index 5be7dbb7e..8504aa71b 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -1,50 +1,86 @@ +/* eslint-disable no-console */ import { generateChildLogger, getLoggerContext, Logger } from "@walletconnect/logger"; import { IVerify } from "@walletconnect/types"; -import { isBrowser, isNode, isReactNative } from "@walletconnect/utils"; +import { + getCryptoKeyFromKeyData, + isNode, + P256KeyDataType, + verifyP256Jwt, +} from "@walletconnect/utils"; import { FIVE_SECONDS, ONE_SECOND, toMiliseconds } from "@walletconnect/time"; import { TRUSTED_VERIFY_URLS, VERIFY_CONTEXT, VERIFY_SERVER, VERIFY_SERVER_V2 } from "../constants"; +import { IKeyValueStorage } from "@walletconnect/keyvaluestorage"; +type jwk = { + publicKey: P256KeyDataType; + expiresAt: number; +}; export class Verify extends IVerify { public name = VERIFY_CONTEXT; - private verifyUrl: string; - private iframe?: HTMLIFrameElement; - private initialized = false; private abortController: AbortController; private isDevEnv; - // the queue is only used during the loading phase of the iframe to ensure all attestations are posted - private queue: string[] = []; - // flag to disable verify when the iframe fails to load on main & fallback urls. - // this means Verify API is not enabled for the current projectId and there's no point in trying to initialize it again. - private verifyDisabled = false; private verifyUrlV2 = VERIFY_SERVER_V2; + private publicKey?: jwk; - constructor(public projectId: string, public logger: Logger) { - super(projectId, logger); + constructor(public projectId: string, public logger: Logger, public store: IKeyValueStorage) { + super(projectId, logger, store); this.logger = generateChildLogger(logger, this.name); - this.verifyUrl = VERIFY_SERVER; this.abortController = new AbortController(); this.isDevEnv = isNode() && process.env.IS_VITEST; console.log("Verify v2 init", this.verifyUrlV2); + this.init(); } + get storeKey(): string { + return `verify:public:key`; + } + + public init = async () => { + this.publicKey = await this.store.getItem(this.storeKey); + console.log("persistedKey", this.publicKey); + if (this.publicKey && toMiliseconds(this.publicKey?.expiresAt) < Date.now()) { + console.log("public key expired"); + await this.removePublicKey(); + } + if (this.publicKey) return; + const key = await this.fetchPublicKey(); + console.log("public key", key); + await this.persistPublicKey(key); + }; + public register: IVerify["register"] = async (params) => { console.log("register", params); const { id, decryptedId } = params; const url = `${this.verifyUrlV2}/attestation?projectId=${this.projectId}`; - const response = await fetch(url, { - method: "POST", - body: JSON.stringify({ id, decryptedId }), - headers: { - origin: "https://8951-78-130-198-143.ngrok-free.app/", - }, - }); - const { srcdoc } = await response.json(); + let src = ""; + try { + const response = await fetch(url, { + method: "POST", + body: JSON.stringify({ id, decryptedId }), + headers: { + origin: "https://8951-78-130-198-143.ngrok-free.app/", + }, + }); + const { srcdoc } = await response.json(); + src = srcdoc; + } catch (e) { + console.error("error", e); + return; + } + console.log("srcdoc", src); - console.log("srcdoc", srcdoc); + const abortTimeout = this.startAbortTimer(ONE_SECOND * 2); const attestatiatonJwt = await new Promise((resolve) => { + const abortListener = () => { + document.body.removeChild(iframe); + window.removeEventListener("message", listener); + this.abortController.signal.removeEventListener("abort", abortListener); + }; + + this.abortController.signal.addEventListener("abort", abortListener); const iframe = document.createElement("iframe"); - iframe.srcdoc = srcdoc; + iframe.srcdoc = src; iframe.src = "https://verify.walletconnect.com"; iframe.style.display = "none"; const listener = (event: MessageEvent) => { @@ -53,10 +89,12 @@ export class Verify extends IVerify { const data = JSON.parse(event.data); if (data.type === "verify_attestation") { // best-practice field - // window.removeEventListener("message", listener); - // document.body.removeChild(iframe); + clearInterval(abortTimeout); + window.removeEventListener("message", listener); + document.body.removeChild(iframe); + this.abortController.signal.removeEventListener("abort", abortListener); console.log("attestation", data.attestation); - resolve(data.attestation); + resolve(data.attestation === null ? "" : data.attestation); } }; document.body.appendChild(iframe); @@ -68,9 +106,35 @@ export class Verify extends IVerify { public resolve: IVerify["resolve"] = async (params) => { if (this.isDevEnv) return ""; + const { attestationId, hash } = params; + + console.log("resolve attestation", params); + if (attestationId === "") { + console.log("resolve: attestationId is empty string"); + return; + } + + if (attestationId) { + const data = await this.isValidJwtAttestation(attestationId); + console.log("resolve data", data); + + if (data?.hasExpired) { + console.log("resolve: jwt attestation expired"); + return; + } + + if (data?.valid) { + return { + origin: data.payload.origin, + isScam: data.payload.isScam, + }; + } + } + if (!hash) return; + console.log("resolve hash", hash); const verifyUrl = this.getVerifyUrl(params?.verifyUrl); - return this.fetchAttestation(params.attestationId, verifyUrl); + return this.fetchAttestation(hash, verifyUrl); }; get context(): string { @@ -88,65 +152,6 @@ export class Verify extends IVerify { return result.status === 200 ? await result.json() : undefined; }; - // private addToQueue = (attestationId: string) => { - // this.queue.push(attestationId); - // }; - - // private processQueue = () => { - // if (this.queue.length === 0) return; - // this.queue.forEach((attestationId) => this.sendPost(attestationId)); - // this.queue = []; - // }; - - // private sendPost = (attestationId: string) => { - // try { - // if (!this.iframe) return; - // this.iframe.contentWindow?.postMessage(attestationId, "*"); // setting targetOrigin to "*" fixes the `Failed to execute 'postMessage' on 'DOMWindow': The target origin provided...` while the iframe is still loading - // this.logger.info(`postMessage sent: ${attestationId} ${this.verifyUrl}`); - // } catch (e) {} - // }; - - // private createIframe = async () => { - // let iframeOnLoadResolve: () => void; - // const onMessage = (event: MessageEvent) => { - // if (event.data === "verify_ready") { - // this.onInit(); - // window.removeEventListener("message", onMessage); - // iframeOnLoadResolve(); - // } - // }; - // await Promise.race([ - // new Promise((resolve) => { - // const existingIframe = document.getElementById(VERIFY_CONTEXT); - // if (existingIframe) { - // this.iframe = existingIframe as HTMLIFrameElement; - // this.onInit(); - // return resolve(); - // } - - // window.addEventListener("message", onMessage); - // const iframe = document.createElement("iframe"); - // iframe.id = VERIFY_CONTEXT; - // iframe.src = `${this.verifyUrl}/${this.projectId}`; - // iframe.style.display = "none"; - // document.body.append(iframe); - // this.iframe = iframe; - // iframeOnLoadResolve = resolve; - // }), - // new Promise((_, reject) => - // setTimeout(() => { - // window.removeEventListener("message", onMessage); - // reject("verify iframe load timeout"); - // }, toMiliseconds(FIVE_SECONDS)), - // ), - // ]); - // }; - - // private onInit = () => { - // this.initialized = true; - // this.processQueue(); - // }; - private startAbortTimer(timer: number) { this.abortController = new AbortController(); return setTimeout(() => this.abortController.abort(), toMiliseconds(timer)); @@ -162,4 +167,46 @@ export class Verify extends IVerify { } return url; }; + + private fetchPublicKey = async () => { + this.logger.info(`fetching public key from: ${this.verifyUrlV2}`); + const timeout = this.startAbortTimer(FIVE_SECONDS); + const result = await fetch(`${this.verifyUrlV2}/public-key`, { + signal: this.abortController.signal, + }); + clearTimeout(timeout); + return (await result.json()) as jwk; + }; + + private persistPublicKey = async (publicKey: jwk) => { + console.log(`persisting public key to local storage`, publicKey); + await this.store.setItem(this.storeKey, publicKey); + this.publicKey = publicKey; + }; + + private removePublicKey = async () => { + console.log(`removing public key from local storage`); + await this.store.removeItem(this.storeKey); + this.publicKey = undefined; + }; + + private isValidJwtAttestation = async (attestation: string) => { + if (!this.publicKey) { + console.log("public key not found"); + return; + } + const cryptoKey = await getCryptoKeyFromKeyData(this.publicKey.publicKey); + const result = await verifyP256Jwt<{ + exp: number; + id: string; + origin: string; + isScam: boolean; + }>(attestation, cryptoKey); + + return { + valid: result.verified, + hasExpired: toMiliseconds(result.payload.payload.exp) < Date.now(), + payload: result.payload.payload, + }; + }; } diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index a676a7c75..ffacd3c90 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -108,7 +108,7 @@ export class Core extends ICore { projectId: this.projectId, }); this.pairing = new Pairing(this, this.logger); - this.verify = new Verify(this.projectId || "", this.logger); + this.verify = new Verify(this.projectId || "", this.logger, this.storage); this.echoClient = new EchoClient(this.projectId || "", this.logger); } diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index 9b04b2699..67ebaaab2 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import { EXPIRER_EVENTS, PAIRING_EVENTS, @@ -1189,7 +1190,8 @@ export class Engine extends IEngine { this.client.logger.error(`sendRequest() -> core.crypto.encode() for topic ${topic} failed`); throw error; } - let attestation; + + let attestation: string | undefined; if (METHODS_TO_VERIFY.includes(method)) { const decryptedId = hashMessage(JSON.stringify(payload)); const id = hashMessage(message); @@ -1302,6 +1304,7 @@ export class Engine extends IEngine { private registerRelayerEvents() { this.client.core.relayer.on(RELAYER_EVENTS.message, (event: RelayerTypes.MessageEvent) => { + console.log("Relayer message", event); // capture any messages that arrive before the client is initialized so we can process them after initialization is complete if (!this.initialized || this.relayMessageCache.length > 0) { this.relayMessageCache.push(event); @@ -1312,7 +1315,7 @@ export class Engine extends IEngine { } private async onRelayMessage(event: RelayerTypes.MessageEvent) { - const { topic, message } = event; + const { topic, message, attestation } = event; // Retrieve the public key (if defined) to decrypt possible `auth_request` response const { publicKey } = this.client.auth.authKeys.keys.includes(AUTH_PUBLIC_KEY_NAME) @@ -1326,7 +1329,7 @@ export class Engine extends IEngine { try { if (isJsonRpcRequest(payload)) { this.client.core.history.set(topic, payload); - this.onRelayEventRequest({ topic, payload }); + this.onRelayEventRequest({ topic, payload, attestation }); } else if (isJsonRpcResponse(payload)) { await this.client.core.history.resolve(payload); await this.onRelayEventResponse({ topic, payload }); @@ -1369,7 +1372,7 @@ export class Engine extends IEngine { }; private processRequest: EnginePrivate["onRelayEventRequest"] = async (event) => { - const { topic, payload } = event; + const { topic, payload, attestation } = event; const reqMethod = payload.method as JsonRpcTypes.WcMethod; if (this.shouldIgnorePairingRequest({ topic, requestMethod: reqMethod })) { @@ -1378,7 +1381,7 @@ export class Engine extends IEngine { switch (reqMethod) { case "wc_sessionPropose": - return await this.onSessionProposeRequest(topic, payload); + return await this.onSessionProposeRequest(topic, payload, attestation); case "wc_sessionSettle": return await this.onSessionSettleRequest(topic, payload); case "wc_sessionUpdate": @@ -1390,11 +1393,11 @@ export class Engine extends IEngine { case "wc_sessionDelete": return await this.onSessionDeleteRequest(topic, payload); case "wc_sessionRequest": - return await this.onSessionRequest(topic, payload); + return await this.onSessionRequest(topic, payload, attestation); case "wc_sessionEvent": return await this.onSessionEventRequest(topic, payload); case "wc_sessionAuthenticate": - return await this.onSessionAuthenticateRequest(topic, payload); + return await this.onSessionAuthenticateRequest(topic, payload, attestation); default: return this.client.logger.info(`Unsupported request method ${reqMethod}`); } @@ -1457,6 +1460,7 @@ export class Engine extends IEngine { private onSessionProposeRequest: EnginePrivate["onSessionProposeRequest"] = async ( topic, payload, + attestation, ) => { const { params, id } = payload; try { @@ -1465,8 +1469,12 @@ export class Engine extends IEngine { params.expiryTimestamp || calcExpiry(ENGINE_RPC_OPTS.wc_sessionPropose.req.ttl); const proposal = { id, pairingTopic: topic, expiryTimestamp, ...params }; await this.setProposal(id, proposal); - const hash = hashMessage(JSON.stringify(payload)); - const verifyContext = await this.getVerifyContext(hash, proposal.proposer.metadata); + // if attestation === "" means verify is disabled + const verifyContext = await this.getVerifyContext({ + attestationId: attestation, + hash: hashMessage(JSON.stringify(payload)), + metadata: proposal.proposer.metadata, + }); this.client.events.emit("session_proposal", { id, params: proposal, verifyContext }); } catch (err: any) { await this.sendError({ @@ -1761,15 +1769,20 @@ export class Engine extends IEngine { } }; - private onSessionRequest: EnginePrivate["onSessionRequest"] = async (topic, payload) => { + private onSessionRequest: EnginePrivate["onSessionRequest"] = async ( + topic, + payload, + attestation, + ) => { const { id, params } = payload; try { await this.isValidRequest({ topic, ...params }); - const hash = hashMessage( - JSON.stringify(formatJsonRpcRequest("wc_sessionRequest", params, id)), - ); const session = this.client.session.get(topic); - const verifyContext = await this.getVerifyContext(hash, session.peer.metadata); + const verifyContext = await this.getVerifyContext({ + attestationId: attestation, + hash: hashMessage(JSON.stringify(formatJsonRpcRequest("wc_sessionRequest", params, id))), + metadata: session.peer.metadata, + }); const request = { id, topic, @@ -1861,11 +1874,15 @@ export class Engine extends IEngine { private onSessionAuthenticateRequest: EnginePrivate["onSessionAuthenticateRequest"] = async ( topic, payload, + attestation, ) => { try { const { requester, authPayload, expiryTimestamp } = payload.params; - const hash = hashMessage(JSON.stringify(payload)); - const verifyContext = await this.getVerifyContext(hash, this.client.metadata); + const verifyContext = await this.getVerifyContext({ + attestationId: attestation, + hash: hashMessage(JSON.stringify(payload)), + metadata: this.client.metadata, + }); const pendingRequest = { requester, pairingTopic: topic, @@ -2411,7 +2428,12 @@ export class Engine extends IEngine { } }; - private getVerifyContext = async (hash: string, metadata: CoreTypes.Metadata) => { + private getVerifyContext = async (params: { + attestationId?: string; + hash?: string; + metadata: CoreTypes.Metadata; + }) => { + const { attestationId, hash, metadata } = params; const context: Verify.Context = { verified: { verifyUrl: metadata.verifyUrl || VERIFY_SERVER, @@ -2420,20 +2442,21 @@ export class Engine extends IEngine { }, }; - try { - const result = await this.client.core.verify.resolve({ - attestationId: hash, - verifyUrl: metadata.verifyUrl, - }); - if (result) { - context.verified.origin = result.origin; - context.verified.isScam = result.isScam; - context.verified.validation = - result.origin === new URL(metadata.url).origin ? "VALID" : "INVALID"; - } - } catch (e) { - this.client.logger.info(e); - } + // try { + const result = await this.client.core.verify.resolve({ + attestationId, + hash, + verifyUrl: metadata.verifyUrl, + }); + if (result) { + context.verified.origin = result.origin; + context.verified.isScam = result.isScam; + context.verified.validation = + result.origin === new URL(metadata.url).origin ? "VALID" : "INVALID"; + } + // } catch (e) { + // this.client.logger.info(e); + // } this.client.logger.info(`Verify context: ${JSON.stringify(context)}`); return context; diff --git a/packages/types/src/core/publisher.ts b/packages/types/src/core/publisher.ts index 12df303d8..6d825caca 100644 --- a/packages/types/src/core/publisher.ts +++ b/packages/types/src/core/publisher.ts @@ -7,7 +7,7 @@ export declare namespace PublisherTypes { export interface Params { topic: string; message: string; - opts: Omit, "internal">; + opts: Omit; } } diff --git a/packages/types/src/core/relayer.ts b/packages/types/src/core/relayer.ts index bc34f5ce8..0cc0c4070 100644 --- a/packages/types/src/core/relayer.ts +++ b/packages/types/src/core/relayer.ts @@ -44,6 +44,7 @@ export declare namespace RelayerTypes { topic: string; message: string; publishedAt: number; + attestation?: string; } export interface RpcUrlParams { diff --git a/packages/types/src/core/verify.ts b/packages/types/src/core/verify.ts index e96f84b6d..1275e6842 100644 --- a/packages/types/src/core/verify.ts +++ b/packages/types/src/core/verify.ts @@ -1,4 +1,5 @@ import { Logger } from "@walletconnect/logger"; +import { IKeyValueStorage } from "@walletconnect/keyvaluestorage"; export declare namespace Verify { export interface Context { @@ -14,12 +15,16 @@ export declare namespace Verify { export abstract class IVerify { public abstract readonly context: string; - constructor(public projectId: string, public logger: Logger) {} + constructor(public projectId: string, public logger: Logger, public store: IKeyValueStorage) {} - public abstract register(params: { id: string; decryptedId: string }): Promise; + public abstract register(params: { + id: string; + decryptedId: string; + }): Promise; public abstract resolve(params: { - attestationId: string; + attestationId?: string; + hash?: string; verifyUrl?: string; }): Promise<{ origin: string; isScam?: boolean }>; } diff --git a/packages/types/src/sign-client/engine.ts b/packages/types/src/sign-client/engine.ts index 5dac5a130..c2f6c6986 100644 --- a/packages/types/src/sign-client/engine.ts +++ b/packages/types/src/sign-client/engine.ts @@ -52,6 +52,7 @@ export declare namespace EngineTypes { interface EventCallback { topic: string; payload: T; + attestation?: string; } interface ConnectParams { @@ -250,6 +251,7 @@ export interface EnginePrivate { onSessionProposeRequest( topic: string, payload: JsonRpcRequest, + attestation?: string, ): Promise; onSessionProposeResponse( @@ -305,6 +307,7 @@ export interface EnginePrivate { onSessionRequest( topic: string, payload: JsonRpcRequest, + attestation?: string, ): Promise; onSessionRequestResponse( @@ -320,6 +323,7 @@ export interface EnginePrivate { onSessionAuthenticateRequest( topic: string, payload: JsonRpcRequest, + attestation?: string, ): Promise; onSessionAuthenticateResponse( diff --git a/packages/utils/src/crypto.ts b/packages/utils/src/crypto.ts index d8ea8999e..c142eb762 100644 --- a/packages/utils/src/crypto.ts +++ b/packages/utils/src/crypto.ts @@ -5,6 +5,8 @@ import { hash, SHA256 } from "@stablelib/sha256"; import * as x25519 from "@stablelib/x25519"; import { CryptoTypes } from "@walletconnect/types"; import { concat, fromString, toString } from "uint8arrays"; +import { decodeJWT } from "@walletconnect/relay-auth"; +import { getSubtleCrypto } from "@walletconnect/environment"; export const BASE10 = "base10"; export const BASE16 = "base16"; @@ -14,6 +16,15 @@ export const UTF8 = "utf8"; export const TYPE_0 = 0; export const TYPE_1 = 1; +export type P256KeyDataType = { + crv: "P-256"; + ext: true; + key_ops: ["verify"]; + kty: string; + x: string; + y: string; +}; + const ZERO_INDEX = 0; const TYPE_LENGTH = 1; const IV_LENGTH = 12; @@ -160,3 +171,33 @@ export function isTypeOneEnvelope( typeof result.receiverPublicKey === "string" ); } + +export function getCryptoKeyFromKeyData(keyData: P256KeyDataType): Promise { + return getSubtleCrypto().importKey( + "jwk", + keyData, + { name: "ECDSA", namedCurve: keyData.crv }, + keyData.ext, + keyData.key_ops, + ); +} + +export async function verifyP256Jwt(token: string, publicKey: CryptoKey) { + const payload = decodeJWT(token) as unknown as { + payload: T; + signature: ArrayBuffer; + data: ArrayBuffer; + }; + const alg = { + name: "ECDSA", + hash: { + name: "SHA-256", + }, + namedCurve: "P-256", + }; + const verified = await getSubtleCrypto().verify(alg, publicKey, payload.signature, payload.data); + return { + payload, + verified, + }; +} From f755ef627a80909539fd9371431dea4f4a72c3cb Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Tue, 6 Aug 2024 10:31:55 +0300 Subject: [PATCH 15/61] refactor: uses getDocument() --- packages/core/src/controllers/verify.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index 8504aa71b..c855cdaa1 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -3,11 +3,13 @@ import { generateChildLogger, getLoggerContext, Logger } from "@walletconnect/lo import { IVerify } from "@walletconnect/types"; import { getCryptoKeyFromKeyData, + isBrowser, isNode, P256KeyDataType, verifyP256Jwt, } from "@walletconnect/utils"; import { FIVE_SECONDS, ONE_SECOND, toMiliseconds } from "@walletconnect/time"; +import { getDocument } from "@walletconnect/window-getters"; import { TRUSTED_VERIFY_URLS, VERIFY_CONTEXT, VERIFY_SERVER, VERIFY_SERVER_V2 } from "../constants"; import { IKeyValueStorage } from "@walletconnect/keyvaluestorage"; @@ -37,6 +39,7 @@ export class Verify extends IVerify { } public init = async () => { + if (!isBrowser()) return; this.publicKey = await this.store.getItem(this.storeKey); console.log("persistedKey", this.publicKey); if (this.publicKey && toMiliseconds(this.publicKey?.expiresAt) < Date.now()) { @@ -50,6 +53,7 @@ export class Verify extends IVerify { }; public register: IVerify["register"] = async (params) => { + if (!isBrowser()) return; console.log("register", params); const { id, decryptedId } = params; const url = `${this.verifyUrlV2}/attestation?projectId=${this.projectId}`; @@ -69,7 +73,7 @@ export class Verify extends IVerify { return; } console.log("srcdoc", src); - + const document = getDocument() as Document; const abortTimeout = this.startAbortTimer(ONE_SECOND * 2); const attestatiatonJwt = await new Promise((resolve) => { const abortListener = () => { From 30df459b7c86be36863ecbf6a2d265792ac9c591 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Tue, 6 Aug 2024 10:33:53 +0300 Subject: [PATCH 16/61] refactor: publishes session settle request before the session propose response --- packages/sign-client/src/controllers/engine.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index 01563ae6e..d63293f8c 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -304,6 +304,12 @@ export class Engine extends IEngine { }; await this.client.session.set(sessionTopic, session); try { + await this.sendRequest({ + topic: sessionTopic, + method: "wc_sessionSettle", + params: sessionSettle, + throwOnFailedPublish: true, + }); await this.sendResult<"wc_sessionPropose">({ id, topic: pairingTopic, @@ -315,12 +321,6 @@ export class Engine extends IEngine { }, throwOnFailedPublish: true, }); - await this.sendRequest({ - topic: sessionTopic, - method: "wc_sessionSettle", - params: sessionSettle, - throwOnFailedPublish: true, - }); } catch (error) { this.client.logger.error(error); // if the publish fails, delete the session and throw an error From 033591e3aa5da04f54dfd78f184fbdbbeb54455f Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Tue, 6 Aug 2024 14:31:28 -0400 Subject: [PATCH 17/61] chore: change handshake canary to only measure 1 client initialization --- packages/sign-client/test/canary/canary.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sign-client/test/canary/canary.spec.ts b/packages/sign-client/test/canary/canary.spec.ts index 63ffbfdd7..276406da1 100644 --- a/packages/sign-client/test/canary/canary.spec.ts +++ b/packages/sign-client/test/canary/canary.spec.ts @@ -30,12 +30,12 @@ describe("Canary", () => { const A = await SignClient.init({ ...TEST_SIGN_CLIENT_OPTIONS_A, }); + const handshakeLatencyMs = Date.now() - start; const B = await SignClient.init({ ...TEST_SIGN_CLIENT_OPTIONS_B, }); const clients = { A, B }; - const handshakeLatencyMs = Date.now() - start; log( `Clients initialized (relay '${TEST_RELAY_URL}'), client ids: A:'${await clients.A.core.crypto.getClientId()}';B:'${await clients.B.core.crypto.getClientId()}'`, ); From b353122d92c78316d8ff55314699a86af9dc11d3 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 9 Aug 2024 10:15:53 +0300 Subject: [PATCH 18/61] refactor: emits session connect so session can be completed and then sends session settle response to reduce connect latency --- packages/sign-client/src/controllers/engine.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index 01563ae6e..e0491fa58 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -1555,18 +1555,18 @@ export class Engine extends IEngine { ...(sessionProperties && { sessionProperties }), ...(sessionConfig && { sessionConfig }), }; - await this.sendResult<"wc_sessionSettle">({ - id: payload.id, - topic, - result: true, - throwOnFailedPublish: true, - }); const target = engineEvent("session_connect"); const listeners = this.events.listenerCount(target); if (listeners === 0) { throw new Error(`emitting ${target} without any listeners 997`); } this.events.emit(engineEvent("session_connect"), { session }); + await this.sendResult<"wc_sessionSettle">({ + id: payload.id, + topic, + result: true, + throwOnFailedPublish: true, + }); } catch (err: any) { await this.sendError({ id, From 318a0e2f221ba20ddb5e4927f72f4d16c756d516 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 9 Aug 2024 11:11:40 +0300 Subject: [PATCH 19/61] refactor: makes verify v2 public key being fetched only when attempted to be used and to fetch new public key if validation fails with the current stored key --- packages/core/src/constants/verify.ts | 2 +- packages/core/src/controllers/verify.ts | 87 +++++++++++-------- .../sign-client/src/controllers/engine.ts | 1 - 3 files changed, 52 insertions(+), 38 deletions(-) diff --git a/packages/core/src/constants/verify.ts b/packages/core/src/constants/verify.ts index 511150b29..08ff1b0a3 100644 --- a/packages/core/src/constants/verify.ts +++ b/packages/core/src/constants/verify.ts @@ -3,6 +3,6 @@ export const VERIFY_CONTEXT = "verify-api"; const VERIFY_SERVER_COM = "https://verify.walletconnect.com"; const VERIFY_SERVER_ORG = "https://verify.walletconnect.org"; export const VERIFY_SERVER = VERIFY_SERVER_ORG; -export const VERIFY_SERVER_V2 = "https://verify.walletconnect.com/v2"; +export const VERIFY_SERVER_V2 = "https://verify.walletconnect.org/v2"; export const TRUSTED_VERIFY_URLS = [VERIFY_SERVER_COM, VERIFY_SERVER_ORG]; diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index c855cdaa1..4f6fb891c 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -39,17 +39,12 @@ export class Verify extends IVerify { } public init = async () => { - if (!isBrowser()) return; this.publicKey = await this.store.getItem(this.storeKey); console.log("persistedKey", this.publicKey); if (this.publicKey && toMiliseconds(this.publicKey?.expiresAt) < Date.now()) { console.log("public key expired"); await this.removePublicKey(); } - if (this.publicKey) return; - const key = await this.fetchPublicKey(); - console.log("public key", key); - await this.persistPublicKey(key); }; public register: IVerify["register"] = async (params) => { @@ -62,9 +57,6 @@ export class Verify extends IVerify { const response = await fetch(url, { method: "POST", body: JSON.stringify({ id, decryptedId }), - headers: { - origin: "https://8951-78-130-198-143.ngrok-free.app/", - }, }); const { srcdoc } = await response.json(); src = srcdoc; @@ -78,23 +70,20 @@ export class Verify extends IVerify { const attestatiatonJwt = await new Promise((resolve) => { const abortListener = () => { document.body.removeChild(iframe); - window.removeEventListener("message", listener); - this.abortController.signal.removeEventListener("abort", abortListener); }; - this.abortController.signal.addEventListener("abort", abortListener); + this.abortController.signal.addEventListener("abort", abortListener, { + signal: this.abortController.signal, + }); const iframe = document.createElement("iframe"); iframe.srcdoc = src; - iframe.src = "https://verify.walletconnect.com"; iframe.style.display = "none"; const listener = (event: MessageEvent) => { console.log("message event received", event); if (!event.data) return; const data = JSON.parse(event.data); if (data.type === "verify_attestation") { - // best-practice field clearInterval(abortTimeout); - window.removeEventListener("message", listener); document.body.removeChild(iframe); this.abortController.signal.removeEventListener("abort", abortListener); console.log("attestation", data.attestation); @@ -102,7 +91,7 @@ export class Verify extends IVerify { } }; document.body.appendChild(iframe); - window.addEventListener("message", listener); + window.addEventListener("message", listener, { signal: this.abortController.signal }); }); console.log("attestatiatonJwt", attestatiatonJwt); return attestatiatonJwt as string; @@ -115,25 +104,14 @@ export class Verify extends IVerify { console.log("resolve attestation", params); if (attestationId === "") { - console.log("resolve: attestationId is empty string"); + console.log("AttestationId is empty, skipping resolve"); return; } if (attestationId) { - const data = await this.isValidJwtAttestation(attestationId); - console.log("resolve data", data); - - if (data?.hasExpired) { - console.log("resolve: jwt attestation expired"); - return; - } - - if (data?.valid) { - return { - origin: data.payload.origin, - isScam: data.payload.isScam, - }; - } + const validation = await this.isValidJwtAttestation(attestationId); + console.log("resolve validation", validation); + if (validation) return validation; } if (!hash) return; console.log("resolve hash", hash); @@ -195,22 +173,59 @@ export class Verify extends IVerify { }; private isValidJwtAttestation = async (attestation: string) => { - if (!this.publicKey) { - console.log("public key not found"); - return; + const key = await this.getPublicKey(); + try { + const validation = await this.validateAttestation(attestation, key); + return validation; + } catch (e) { + console.error("error validating attestation", e); + } + const newKey = await this.fetchAndPersistPublicKey(); + try { + const validation = await this.validateAttestation(attestation, newKey); + return validation; + } catch (e) { + console.error("error validating attestation", e); } - const cryptoKey = await getCryptoKeyFromKeyData(this.publicKey.publicKey); + return undefined; + }; + + private getPublicKey = async () => { + if (this.publicKey) return this.publicKey; + return await this.fetchAndPersistPublicKey(); + }; + + private fetchAndPersistPublicKey = async () => { + const key = await this.fetchPublicKey(); + console.log("public key", key); + await this.persistPublicKey(key); + return key; + }; + + private validateAttestation = async (attestation: string, key: jwk) => { + const cryptoKey = await getCryptoKeyFromKeyData(key?.publicKey); const result = await verifyP256Jwt<{ exp: number; id: string; origin: string; isScam: boolean; }>(attestation, cryptoKey); - - return { + console.log("validateAttestation result", result); + const validation = { valid: result.verified, hasExpired: toMiliseconds(result.payload.payload.exp) < Date.now(), payload: result.payload.payload, }; + if (validation.hasExpired) { + console.log("resolve: jwt attestation expired"); + throw new Error("JWT attestation expired"); + } + + return validation.valid + ? { + origin: validation.payload.origin, + isScam: validation.payload.isScam, + } + : undefined; }; } diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index 67ebaaab2..23109028d 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -1469,7 +1469,6 @@ export class Engine extends IEngine { params.expiryTimestamp || calcExpiry(ENGINE_RPC_OPTS.wc_sessionPropose.req.ttl); const proposal = { id, pairingTopic: topic, expiryTimestamp, ...params }; await this.setProposal(id, proposal); - // if attestation === "" means verify is disabled const verifyContext = await this.getVerifyContext({ attestationId: attestation, hash: hashMessage(JSON.stringify(payload)), From d11901e693b2580b4d625ed1a8988aee03438db1 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 9 Aug 2024 12:29:09 +0300 Subject: [PATCH 20/61] chore: canary relay api --- package-lock.json | 24 ++++++++++++------------ packages/core/package.json | 2 +- packages/sign-client/package.json | 2 +- packages/utils/package.json | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 49369419f..5062d9b0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7943,9 +7943,9 @@ "link": true }, "node_modules/@walletconnect/relay-api": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@walletconnect/relay-api/-/relay-api-1.0.10.tgz", - "integrity": "sha512-tqrdd4zU9VBNqUaXXQASaexklv6A54yEyQQEXYOCr+Jz8Ket0dmPBDyg19LVSNUN2cipAghQc45/KVmfFJ0cYw==", + "version": "1.0.11-canary.0", + "resolved": "https://registry.npmjs.org/@walletconnect/relay-api/-/relay-api-1.0.11-canary.0.tgz", + "integrity": "sha512-PMtLs+QwN0dFQWEuAvXJy1B58Rfw785eAl9ByrD9F0jnbtQmvIonmGJMRvnCf2YwwCO8tPPM7tCSIDwy2KaAdQ==", "dependencies": { "@walletconnect/jsonrpc-types": "^1.0.2" } @@ -27477,7 +27477,7 @@ "@walletconnect/jsonrpc-ws-connection": "1.0.14", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", - "@walletconnect/relay-api": "1.0.10", + "@walletconnect/relay-api": "1.0.11-canary.0", "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", @@ -27559,7 +27559,7 @@ "@aws-sdk/client-cloudwatch": "3.450.0", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-ws-connection": "1.0.14", - "@walletconnect/relay-api": "1.0.10" + "@walletconnect/relay-api": "1.0.11-canary.0" } }, "packages/types": { @@ -27585,7 +27585,7 @@ "@stablelib/random": "1.0.2", "@stablelib/sha256": "1.0.1", "@stablelib/x25519": "1.0.3", - "@walletconnect/relay-api": "1.0.10", + "@walletconnect/relay-api": "1.0.11-canary.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.14.0", @@ -33634,7 +33634,7 @@ "@walletconnect/jsonrpc-ws-connection": "1.0.14", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", - "@walletconnect/relay-api": "1.0.10", + "@walletconnect/relay-api": "1.0.11-canary.0", "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", @@ -33841,9 +33841,9 @@ } }, "@walletconnect/relay-api": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@walletconnect/relay-api/-/relay-api-1.0.10.tgz", - "integrity": "sha512-tqrdd4zU9VBNqUaXXQASaexklv6A54yEyQQEXYOCr+Jz8Ket0dmPBDyg19LVSNUN2cipAghQc45/KVmfFJ0cYw==", + "version": "1.0.11-canary.0", + "resolved": "https://registry.npmjs.org/@walletconnect/relay-api/-/relay-api-1.0.11-canary.0.tgz", + "integrity": "sha512-PMtLs+QwN0dFQWEuAvXJy1B58Rfw785eAl9ByrD9F0jnbtQmvIonmGJMRvnCf2YwwCO8tPPM7tCSIDwy2KaAdQ==", "requires": { "@walletconnect/jsonrpc-types": "^1.0.2" } @@ -33886,7 +33886,7 @@ "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/jsonrpc-ws-connection": "1.0.14", "@walletconnect/logger": "2.1.2", - "@walletconnect/relay-api": "1.0.10", + "@walletconnect/relay-api": "1.0.11-canary.0", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.14.0", "@walletconnect/utils": "2.14.0", @@ -34133,7 +34133,7 @@ "@stablelib/sha256": "1.0.1", "@stablelib/x25519": "1.0.3", "@types/lodash.isequal": "4.5.6", - "@walletconnect/relay-api": "1.0.10", + "@walletconnect/relay-api": "1.0.11-canary.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.14.0", diff --git a/packages/core/package.json b/packages/core/package.json index 97a880b92..ce7532a79 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -38,7 +38,7 @@ "@walletconnect/jsonrpc-ws-connection": "1.0.14", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", - "@walletconnect/relay-api": "1.0.10", + "@walletconnect/relay-api": "1.0.11-canary.0", "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", diff --git a/packages/sign-client/package.json b/packages/sign-client/package.json index 3b22b342a..ddd88fd6f 100644 --- a/packages/sign-client/package.json +++ b/packages/sign-client/package.json @@ -52,6 +52,6 @@ "@aws-sdk/client-cloudwatch": "3.450.0", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-ws-connection": "1.0.14", - "@walletconnect/relay-api": "1.0.10" + "@walletconnect/relay-api": "1.0.11-canary.0" } } diff --git a/packages/utils/package.json b/packages/utils/package.json index 7dacfe719..cd1b52e0b 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -36,7 +36,7 @@ "@stablelib/random": "1.0.2", "@stablelib/sha256": "1.0.1", "@stablelib/x25519": "1.0.3", - "@walletconnect/relay-api": "1.0.10", + "@walletconnect/relay-api": "1.0.11-canary.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.14.0", From 18d5f6b59d5ae451c4f2b37becd42258edf3be3a Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 9 Aug 2024 12:31:29 +0300 Subject: [PATCH 21/61] chore: prettier --- packages/types/src/core/relayer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/core/relayer.ts b/packages/types/src/core/relayer.ts index 0cc0c4070..0d67d84e5 100644 --- a/packages/types/src/core/relayer.ts +++ b/packages/types/src/core/relayer.ts @@ -21,7 +21,7 @@ export declare namespace RelayerTypes { internal?: { throwOnFailedPublish?: boolean; }; - attestation?: string + attestation?: string; } export interface SubscribeOptions { From 6b5a70aed757c3499f35ef51f2af787a986b16d5 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 9 Aug 2024 12:41:29 +0300 Subject: [PATCH 22/61] fix: adds new `attestation` field to publisher tests --- packages/core/test/publisher.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/test/publisher.spec.ts b/packages/core/test/publisher.spec.ts index aaa994a91..2984c6e55 100644 --- a/packages/core/test/publisher.spec.ts +++ b/packages/core/test/publisher.spec.ts @@ -175,6 +175,7 @@ describe("Publisher", () => { prompt: false, ttl: PUBLISHER_DEFAULT_TTL, tag: 0, + attestation: undefined, }, id, }); @@ -192,6 +193,7 @@ describe("Publisher", () => { prompt: opts.prompt, ttl: opts.ttl, tag: opts.tag, + attestation: undefined, }, id: opts.id, }); From e0d979e0f87c5a8f8f20f2a20e39b39c50b7a395 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 9 Aug 2024 12:52:18 +0300 Subject: [PATCH 23/61] fix: relayer test with new `attestation` param --- packages/core/test/relayer.spec.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/test/relayer.spec.ts b/packages/core/test/relayer.spec.ts index b81be822c..2bceebc06 100644 --- a/packages/core/test/relayer.spec.ts +++ b/packages/core/test/relayer.spec.ts @@ -222,7 +222,12 @@ describe("Relayer", () => { method: "mock" + RELAYER_SUBSCRIBER_SUFFIX, params: { id: "abc123", - data: { topic: "ababab", message: "deadbeef", publishedAt: 1677151760537 }, + data: { + topic: "ababab", + message: "deadbeef", + publishedAt: 1677151760537, + attestation: undefined, + }, }, }; @@ -250,6 +255,7 @@ describe("Relayer", () => { topic: validPayload.params.data.topic, message: validPayload.params.data.message, publishedAt: validPayload.params.data.publishedAt, + attestation: validPayload.params.data.attestation, }), ).to.be.true; }); From 730bca3c1add2bead8be129a98ed850944db86f9 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 9 Aug 2024 13:09:34 +0300 Subject: [PATCH 24/61] chore: updates relay-api to stable --- package-lock.json | 24 ++++++++++++------------ packages/core/package.json | 2 +- packages/sign-client/package.json | 2 +- packages/utils/package.json | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5062d9b0d..a7b1e6a7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7943,9 +7943,9 @@ "link": true }, "node_modules/@walletconnect/relay-api": { - "version": "1.0.11-canary.0", - "resolved": "https://registry.npmjs.org/@walletconnect/relay-api/-/relay-api-1.0.11-canary.0.tgz", - "integrity": "sha512-PMtLs+QwN0dFQWEuAvXJy1B58Rfw785eAl9ByrD9F0jnbtQmvIonmGJMRvnCf2YwwCO8tPPM7tCSIDwy2KaAdQ==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@walletconnect/relay-api/-/relay-api-1.0.11.tgz", + "integrity": "sha512-tLPErkze/HmC9aCmdZOhtVmYZq1wKfWTJtygQHoWtgg722Jd4homo54Cs4ak2RUFUZIGO2RsOpIcWipaua5D5Q==", "dependencies": { "@walletconnect/jsonrpc-types": "^1.0.2" } @@ -27477,7 +27477,7 @@ "@walletconnect/jsonrpc-ws-connection": "1.0.14", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", - "@walletconnect/relay-api": "1.0.11-canary.0", + "@walletconnect/relay-api": "1.0.11", "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", @@ -27559,7 +27559,7 @@ "@aws-sdk/client-cloudwatch": "3.450.0", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-ws-connection": "1.0.14", - "@walletconnect/relay-api": "1.0.11-canary.0" + "@walletconnect/relay-api": "1.0.11" } }, "packages/types": { @@ -27585,7 +27585,7 @@ "@stablelib/random": "1.0.2", "@stablelib/sha256": "1.0.1", "@stablelib/x25519": "1.0.3", - "@walletconnect/relay-api": "1.0.11-canary.0", + "@walletconnect/relay-api": "1.0.11", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.14.0", @@ -33634,7 +33634,7 @@ "@walletconnect/jsonrpc-ws-connection": "1.0.14", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", - "@walletconnect/relay-api": "1.0.11-canary.0", + "@walletconnect/relay-api": "1.0.11", "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", @@ -33841,9 +33841,9 @@ } }, "@walletconnect/relay-api": { - "version": "1.0.11-canary.0", - "resolved": "https://registry.npmjs.org/@walletconnect/relay-api/-/relay-api-1.0.11-canary.0.tgz", - "integrity": "sha512-PMtLs+QwN0dFQWEuAvXJy1B58Rfw785eAl9ByrD9F0jnbtQmvIonmGJMRvnCf2YwwCO8tPPM7tCSIDwy2KaAdQ==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@walletconnect/relay-api/-/relay-api-1.0.11.tgz", + "integrity": "sha512-tLPErkze/HmC9aCmdZOhtVmYZq1wKfWTJtygQHoWtgg722Jd4homo54Cs4ak2RUFUZIGO2RsOpIcWipaua5D5Q==", "requires": { "@walletconnect/jsonrpc-types": "^1.0.2" } @@ -33886,7 +33886,7 @@ "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/jsonrpc-ws-connection": "1.0.14", "@walletconnect/logger": "2.1.2", - "@walletconnect/relay-api": "1.0.11-canary.0", + "@walletconnect/relay-api": "1.0.11", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.14.0", "@walletconnect/utils": "2.14.0", @@ -34133,7 +34133,7 @@ "@stablelib/sha256": "1.0.1", "@stablelib/x25519": "1.0.3", "@types/lodash.isequal": "4.5.6", - "@walletconnect/relay-api": "1.0.11-canary.0", + "@walletconnect/relay-api": "1.0.11", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.14.0", diff --git a/packages/core/package.json b/packages/core/package.json index ce7532a79..4bc0ba36c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -38,7 +38,7 @@ "@walletconnect/jsonrpc-ws-connection": "1.0.14", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", - "@walletconnect/relay-api": "1.0.11-canary.0", + "@walletconnect/relay-api": "1.0.11", "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", diff --git a/packages/sign-client/package.json b/packages/sign-client/package.json index ddd88fd6f..91cf2c0db 100644 --- a/packages/sign-client/package.json +++ b/packages/sign-client/package.json @@ -52,6 +52,6 @@ "@aws-sdk/client-cloudwatch": "3.450.0", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-ws-connection": "1.0.14", - "@walletconnect/relay-api": "1.0.11-canary.0" + "@walletconnect/relay-api": "1.0.11" } } diff --git a/packages/utils/package.json b/packages/utils/package.json index cd1b52e0b..30573249c 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -36,7 +36,7 @@ "@stablelib/random": "1.0.2", "@stablelib/sha256": "1.0.1", "@stablelib/x25519": "1.0.3", - "@walletconnect/relay-api": "1.0.11-canary.0", + "@walletconnect/relay-api": "1.0.11", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.14.0", From 5210e5af1a48af2d8e2f54cb7fc6c734741d4b77 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 9 Aug 2024 13:31:10 +0300 Subject: [PATCH 25/61] feat: adds test --- packages/utils/test/crypto.spec.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/utils/test/crypto.spec.ts b/packages/utils/test/crypto.spec.ts index de2e1167a..ddd0820f2 100644 --- a/packages/utils/test/crypto.spec.ts +++ b/packages/utils/test/crypto.spec.ts @@ -14,6 +14,10 @@ import { validateDecoding, isTypeOneEnvelope, generateRandomBytes32, + verifyJwt, + verifyP256Jwt, + getCryptoKeyFromKeyData, + P256KeyDataType, } from "../src"; import { TEST_KEY_PAIRS, TEST_SHARED_KEY, TEST_HASHED_KEY, TEST_SYM_KEY } from "./shared"; @@ -108,4 +112,28 @@ describe("Crypto", () => { it("calls generateRandomBytes32", () => { expect(generateRandomBytes32()).toBeTruthy(); }); + it("should validate verify v2 jwt", async () => { + const jwt = + "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjI0MjE3NDAsImlkIjoiNTk5NzQ1ZjJkMTJlNjIxNTFlZDg4ZmMyOWFiYTBkMjVmZWJlYWFlMjliNzU4ZDdmMGNhZGRhOTg0YzkwZjI4YSIsIm9yaWdpbiI6Imh0dHBzOi8vODk1MS03OC0xMzAtMTk4LTE0My5uZ3Jvay1mcmVlLmFwcCIsImlzU2NhbSI6bnVsbH0._pCu1gaZcEo4yjgyDwQZFXS8Q_SA4xdH51dji3bJjpviW73ieEHxWg4zg0Uc0W1Q62AZWW4o-W5id4yZy88dTw"; + const publicKey = { + publicKey: { + crv: "P-256", + ext: true, + key_ops: ["verify"], + kty: "EC", + x: "CbL4DOYOb1ntd-8OmExO-oS0DWCMC00DntrymJoB8tk", + y: "KTFwjHtQxGTDR91VsOypcdBfvbo6sAMj5p4Wb-9hRA0", + }, + expiresAt: 1725091080, + }; + const keyData = await getCryptoKeyFromKeyData(publicKey.publicKey as P256KeyDataType); + const result = await verifyP256Jwt(jwt, keyData); + expect(result).to.exist; + expect(result.verified).to.exist; + expect(result.verified).to.be.true; + expect(result.payload).to.exist; + expect(result.payload.payload).to.exist; + expect(result.payload.data).to.exist; + expect(result.payload.signature).to.exist; + }); }); From 3ddbfc7a5b73e56fcab93f2edace12e8c9fd229f Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 9 Aug 2024 13:39:04 +0300 Subject: [PATCH 26/61] chore: clean up logs --- packages/core/src/controllers/publisher.ts | 2 -- packages/core/src/controllers/relayer.ts | 2 -- packages/core/src/controllers/verify.ts | 35 ++++++------------- .../sign-client/src/controllers/engine.ts | 32 ++++++++--------- 4 files changed, 26 insertions(+), 45 deletions(-) diff --git a/packages/core/src/controllers/publisher.ts b/packages/core/src/controllers/publisher.ts index 064fe4b40..980a85a7c 100644 --- a/packages/core/src/controllers/publisher.ts +++ b/packages/core/src/controllers/publisher.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import { HEARTBEAT_EVENTS } from "@walletconnect/heartbeat"; import { JsonRpcPayload, RequestArguments } from "@walletconnect/jsonrpc-types"; import { generateChildLogger, getLoggerContext, Logger } from "@walletconnect/logger"; @@ -148,7 +147,6 @@ export class Publisher extends IPublisher { }, id, }; - console.log("rpc publish request", request); if (isUndefined(request.params?.prompt)) delete request.params?.prompt; if (isUndefined(request.params?.tag)) delete request.params?.tag; this.logger.debug(`Outgoing Relay Payload`); diff --git a/packages/core/src/controllers/relayer.ts b/packages/core/src/controllers/relayer.ts index 208ba3f6c..5195314b3 100644 --- a/packages/core/src/controllers/relayer.ts +++ b/packages/core/src/controllers/relayer.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import { EventEmitter } from "events"; import { JsonRpcProvider } from "@walletconnect/jsonrpc-provider"; import { @@ -445,7 +444,6 @@ export class Relayer extends IRelayer { private async onProviderPayload(payload: JsonRpcPayload) { this.logger.debug(`Incoming Relay Payload`); this.logger.trace({ type: "payload", direction: "incoming", payload }); - console.log("onProviderPayload", payload); if (isJsonRpcRequest(payload)) { if (!payload.method.endsWith(RELAYER_SUBSCRIBER_SUFFIX)) return; const event = (payload as JsonRpcRequest).params; diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index 4f6fb891c..09cb54439 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import { generateChildLogger, getLoggerContext, Logger } from "@walletconnect/logger"; import { IVerify } from "@walletconnect/types"; import { @@ -30,7 +29,6 @@ export class Verify extends IVerify { this.logger = generateChildLogger(logger, this.name); this.abortController = new AbortController(); this.isDevEnv = isNode() && process.env.IS_VITEST; - console.log("Verify v2 init", this.verifyUrlV2); this.init(); } @@ -40,16 +38,14 @@ export class Verify extends IVerify { public init = async () => { this.publicKey = await this.store.getItem(this.storeKey); - console.log("persistedKey", this.publicKey); if (this.publicKey && toMiliseconds(this.publicKey?.expiresAt) < Date.now()) { - console.log("public key expired"); + this.logger.debug("verify v2 public key expired"); await this.removePublicKey(); } }; public register: IVerify["register"] = async (params) => { if (!isBrowser()) return; - console.log("register", params); const { id, decryptedId } = params; const url = `${this.verifyUrlV2}/attestation?projectId=${this.projectId}`; let src = ""; @@ -60,11 +56,10 @@ export class Verify extends IVerify { }); const { srcdoc } = await response.json(); src = srcdoc; + this.logger.debug("srcdoc fetched", src); } catch (e) { - console.error("error", e); return; } - console.log("srcdoc", src); const document = getDocument() as Document; const abortTimeout = this.startAbortTimer(ONE_SECOND * 2); const attestatiatonJwt = await new Promise((resolve) => { @@ -79,21 +74,19 @@ export class Verify extends IVerify { iframe.srcdoc = src; iframe.style.display = "none"; const listener = (event: MessageEvent) => { - console.log("message event received", event); if (!event.data) return; const data = JSON.parse(event.data); if (data.type === "verify_attestation") { clearInterval(abortTimeout); document.body.removeChild(iframe); this.abortController.signal.removeEventListener("abort", abortListener); - console.log("attestation", data.attestation); resolve(data.attestation === null ? "" : data.attestation); } }; document.body.appendChild(iframe); window.addEventListener("message", listener, { signal: this.abortController.signal }); }); - console.log("attestatiatonJwt", attestatiatonJwt); + this.logger.debug("jwt attestation", attestatiatonJwt); return attestatiatonJwt as string; }; @@ -101,20 +94,16 @@ export class Verify extends IVerify { if (this.isDevEnv) return ""; const { attestationId, hash } = params; - console.log("resolve attestation", params); - if (attestationId === "") { - console.log("AttestationId is empty, skipping resolve"); + this.logger.debug("resolve: attestationId is empty, skipping"); return; } if (attestationId) { const validation = await this.isValidJwtAttestation(attestationId); - console.log("resolve validation", validation); if (validation) return validation; } if (!hash) return; - console.log("resolve hash", hash); const verifyUrl = this.getVerifyUrl(params?.verifyUrl); return this.fetchAttestation(hash, verifyUrl); }; @@ -124,7 +113,7 @@ export class Verify extends IVerify { } private fetchAttestation = async (attestationId: string, url: string) => { - this.logger.info(`resolving attestation: ${attestationId} from url: ${url}`); + this.logger.debug(`resolving attestation: ${attestationId} from url: ${url}`); // set artificial timeout to prevent hanging const timeout = this.startAbortTimer(ONE_SECOND * 5); const result = await fetch(`${url}/attestation/${attestationId}`, { @@ -151,7 +140,7 @@ export class Verify extends IVerify { }; private fetchPublicKey = async () => { - this.logger.info(`fetching public key from: ${this.verifyUrlV2}`); + this.logger.debug(`fetching public key from: ${this.verifyUrlV2}`); const timeout = this.startAbortTimer(FIVE_SECONDS); const result = await fetch(`${this.verifyUrlV2}/public-key`, { signal: this.abortController.signal, @@ -161,13 +150,13 @@ export class Verify extends IVerify { }; private persistPublicKey = async (publicKey: jwk) => { - console.log(`persisting public key to local storage`, publicKey); + this.logger.debug(`persisting public key to local storage`, publicKey); await this.store.setItem(this.storeKey, publicKey); this.publicKey = publicKey; }; private removePublicKey = async () => { - console.log(`removing public key from local storage`); + this.logger.debug(`removing verify v2 public key from storage`); await this.store.removeItem(this.storeKey); this.publicKey = undefined; }; @@ -178,14 +167,14 @@ export class Verify extends IVerify { const validation = await this.validateAttestation(attestation, key); return validation; } catch (e) { - console.error("error validating attestation", e); + this.logger.warn(e, "error validating attestation"); } const newKey = await this.fetchAndPersistPublicKey(); try { const validation = await this.validateAttestation(attestation, newKey); return validation; } catch (e) { - console.error("error validating attestation", e); + this.logger.warn(e, "error validating attestation"); } return undefined; }; @@ -197,7 +186,6 @@ export class Verify extends IVerify { private fetchAndPersistPublicKey = async () => { const key = await this.fetchPublicKey(); - console.log("public key", key); await this.persistPublicKey(key); return key; }; @@ -210,14 +198,13 @@ export class Verify extends IVerify { origin: string; isScam: boolean; }>(attestation, cryptoKey); - console.log("validateAttestation result", result); const validation = { valid: result.verified, hasExpired: toMiliseconds(result.payload.payload.exp) < Date.now(), payload: result.payload.payload, }; if (validation.hasExpired) { - console.log("resolve: jwt attestation expired"); + this.logger.warn("resolve: jwt attestation expired"); throw new Error("JWT attestation expired"); } diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index 23109028d..b37073c05 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import { EXPIRER_EVENTS, PAIRING_EVENTS, @@ -1304,7 +1303,6 @@ export class Engine extends IEngine { private registerRelayerEvents() { this.client.core.relayer.on(RELAYER_EVENTS.message, (event: RelayerTypes.MessageEvent) => { - console.log("Relayer message", event); // capture any messages that arrive before the client is initialized so we can process them after initialization is complete if (!this.initialized || this.relayMessageCache.length > 0) { this.relayMessageCache.push(event); @@ -2441,23 +2439,23 @@ export class Engine extends IEngine { }, }; - // try { - const result = await this.client.core.verify.resolve({ - attestationId, - hash, - verifyUrl: metadata.verifyUrl, - }); - if (result) { - context.verified.origin = result.origin; - context.verified.isScam = result.isScam; - context.verified.validation = - result.origin === new URL(metadata.url).origin ? "VALID" : "INVALID"; + try { + const result = await this.client.core.verify.resolve({ + attestationId, + hash, + verifyUrl: metadata.verifyUrl, + }); + if (result) { + context.verified.origin = result.origin; + context.verified.isScam = result.isScam; + context.verified.validation = + result.origin === new URL(metadata.url).origin ? "VALID" : "INVALID"; + } + } catch (e) { + this.client.logger.warn(e); } - // } catch (e) { - // this.client.logger.info(e); - // } - this.client.logger.info(`Verify context: ${JSON.stringify(context)}`); + this.client.logger.debug(`Verify context: ${JSON.stringify(context)}`); return context; }; From 372a42e036b818432539b9e6ef92dd96b86e659e Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 9 Aug 2024 13:44:18 +0300 Subject: [PATCH 27/61] chore: bump node to 20 --- .github/workflows/pr_checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr_checks.yml b/.github/workflows/pr_checks.yml index 06772b581..87ab84ea1 100644 --- a/.github/workflows/pr_checks.yml +++ b/.github/workflows/pr_checks.yml @@ -16,7 +16,7 @@ jobs: - name: setup-node uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 20.x cache: "npm" cache-dependency-path: "**/package-lock.json" - name: install From 9659018e358668bfd9366f53cdd78ec8cde91eb5 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 9 Aug 2024 14:04:13 +0300 Subject: [PATCH 28/61] fix: logging & rm event listener --- packages/core/src/controllers/verify.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index 09cb54439..b74bdc5d3 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -80,6 +80,7 @@ export class Verify extends IVerify { clearInterval(abortTimeout); document.body.removeChild(iframe); this.abortController.signal.removeEventListener("abort", abortListener); + window.removeEventListener("message", listener); resolve(data.attestation === null ? "" : data.attestation); } }; @@ -167,14 +168,16 @@ export class Verify extends IVerify { const validation = await this.validateAttestation(attestation, key); return validation; } catch (e) { - this.logger.warn(e, "error validating attestation"); + this.logger.warn(e); + this.logger.warn("error validating attestation"); } const newKey = await this.fetchAndPersistPublicKey(); try { const validation = await this.validateAttestation(attestation, newKey); return validation; } catch (e) { - this.logger.warn(e, "error validating attestation"); + this.logger.warn(e); + this.logger.warn("error validating attestation"); } return undefined; }; From faeed6da1af48a4d5a694e277acb880c3d4655e5 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 9 Aug 2024 14:04:28 +0300 Subject: [PATCH 29/61] fix: subtle crypto in node --- packages/utils/src/crypto.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/crypto.ts b/packages/utils/src/crypto.ts index c142eb762..d8603f946 100644 --- a/packages/utils/src/crypto.ts +++ b/packages/utils/src/crypto.ts @@ -6,6 +6,7 @@ import * as x25519 from "@stablelib/x25519"; import { CryptoTypes } from "@walletconnect/types"; import { concat, fromString, toString } from "uint8arrays"; import { decodeJWT } from "@walletconnect/relay-auth"; +import { webcrypto } from "crypto"; import { getSubtleCrypto } from "@walletconnect/environment"; export const BASE10 = "base10"; @@ -173,7 +174,7 @@ export function isTypeOneEnvelope( } export function getCryptoKeyFromKeyData(keyData: P256KeyDataType): Promise { - return getSubtleCrypto().importKey( + return getSubtle().importKey( "jwk", keyData, { name: "ECDSA", namedCurve: keyData.crv }, @@ -195,9 +196,13 @@ export async function verifyP256Jwt(token: string, publicKey: CryptoKey) { }, namedCurve: "P-256", }; - const verified = await getSubtleCrypto().verify(alg, publicKey, payload.signature, payload.data); + const verified = await getSubtle().verify(alg, publicKey, payload.signature, payload.data); return { payload, verified, }; } + +export function getSubtle() { + return getSubtleCrypto() || webcrypto.subtle; +} From 82e5cd4b99efb088d6207a12466c36f4314990a8 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Mon, 12 Aug 2024 15:21:41 +0300 Subject: [PATCH 30/61] refactor: log rejections & unsubscribe on abortController --- packages/core/src/controllers/verify.ts | 63 ++++++++++++++----------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index b74bdc5d3..1f2a4973e 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -58,37 +58,44 @@ export class Verify extends IVerify { src = srcdoc; this.logger.debug("srcdoc fetched", src); } catch (e) { + this.logger.warn(e); return; } - const document = getDocument() as Document; - const abortTimeout = this.startAbortTimer(ONE_SECOND * 2); - const attestatiatonJwt = await new Promise((resolve) => { - const abortListener = () => { - document.body.removeChild(iframe); - }; - - this.abortController.signal.addEventListener("abort", abortListener, { - signal: this.abortController.signal, - }); - const iframe = document.createElement("iframe"); - iframe.srcdoc = src; - iframe.style.display = "none"; - const listener = (event: MessageEvent) => { - if (!event.data) return; - const data = JSON.parse(event.data); - if (data.type === "verify_attestation") { - clearInterval(abortTimeout); - document.body.removeChild(iframe); - this.abortController.signal.removeEventListener("abort", abortListener); + try { + const document = getDocument() as Document; + const abortTimeout = this.startAbortTimer(ONE_SECOND * 3); + const attestatiatonJwt = await new Promise((resolve) => { + const abortListener = () => { window.removeEventListener("message", listener); - resolve(data.attestation === null ? "" : data.attestation); - } - }; - document.body.appendChild(iframe); - window.addEventListener("message", listener, { signal: this.abortController.signal }); - }); - this.logger.debug("jwt attestation", attestatiatonJwt); - return attestatiatonJwt as string; + document.body.removeChild(iframe); + throw new Error("attestation aborted"); + }; + this.abortController.signal.addEventListener("abort", abortListener, { + signal: this.abortController.signal, + }); + const iframe = document.createElement("iframe"); + iframe.srcdoc = src; + iframe.style.display = "none"; + const listener = (event: MessageEvent) => { + if (!event.data) return; + const data = JSON.parse(event.data); + if (data.type === "verify_attestation") { + clearInterval(abortTimeout); + document.body.removeChild(iframe); + this.abortController.signal.removeEventListener("abort", abortListener); + window.removeEventListener("message", listener); + resolve(data.attestation === null ? "" : data.attestation); + } + }; + document.body.appendChild(iframe); + window.addEventListener("message", listener, { signal: this.abortController.signal }); + }); + this.logger.debug("jwt attestation", attestatiatonJwt); + return attestatiatonJwt as string; + } catch (e) { + this.logger.warn(e); + } + return null; }; public resolve: IVerify["resolve"] = async (params) => { From 0ef5ee2d0b6634ec0518728b720bd1f0ec302aa2 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Mon, 12 Aug 2024 15:33:53 +0300 Subject: [PATCH 31/61] chore: handle invalid keys --- packages/core/src/controllers/verify.ts | 37 +++++++++++++++++-------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index 1f2a4973e..e0d0b4577 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -42,6 +42,9 @@ export class Verify extends IVerify { this.logger.debug("verify v2 public key expired"); await this.removePublicKey(); } + if (!this.publicKey) { + await this.fetchAndPersistPublicKey(); + } }; public register: IVerify["register"] = async (params) => { @@ -95,7 +98,7 @@ export class Verify extends IVerify { } catch (e) { this.logger.warn(e); } - return null; + return ""; }; public resolve: IVerify["resolve"] = async (params) => { @@ -148,13 +151,18 @@ export class Verify extends IVerify { }; private fetchPublicKey = async () => { - this.logger.debug(`fetching public key from: ${this.verifyUrlV2}`); - const timeout = this.startAbortTimer(FIVE_SECONDS); - const result = await fetch(`${this.verifyUrlV2}/public-key`, { - signal: this.abortController.signal, - }); - clearTimeout(timeout); - return (await result.json()) as jwk; + try { + this.logger.debug(`fetching public key from: ${this.verifyUrlV2}`); + const timeout = this.startAbortTimer(FIVE_SECONDS); + const result = await fetch(`${this.verifyUrlV2}/public-key`, { + signal: this.abortController.signal, + }); + clearTimeout(timeout); + return (await result.json()) as jwk; + } catch (e) { + this.logger.warn(e); + } + return undefined; }; private persistPublicKey = async (publicKey: jwk) => { @@ -172,16 +180,20 @@ export class Verify extends IVerify { private isValidJwtAttestation = async (attestation: string) => { const key = await this.getPublicKey(); try { - const validation = await this.validateAttestation(attestation, key); - return validation; + if (key) { + const validation = await this.validateAttestation(attestation, key); + return validation; + } } catch (e) { this.logger.warn(e); this.logger.warn("error validating attestation"); } const newKey = await this.fetchAndPersistPublicKey(); try { - const validation = await this.validateAttestation(attestation, newKey); - return validation; + if (newKey) { + const validation = await this.validateAttestation(attestation, newKey); + return validation; + } } catch (e) { this.logger.warn(e); this.logger.warn("error validating attestation"); @@ -196,6 +208,7 @@ export class Verify extends IVerify { private fetchAndPersistPublicKey = async () => { const key = await this.fetchPublicKey(); + if (!key) return; await this.persistPublicKey(key); return key; }; From 24614bd35175aff8df95ab5ff3b181e199a58f20 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Mon, 12 Aug 2024 16:38:13 +0300 Subject: [PATCH 32/61] chore: fixes typo --- packages/core/src/controllers/verify.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index e0d0b4577..3f9a22f6a 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -67,7 +67,7 @@ export class Verify extends IVerify { try { const document = getDocument() as Document; const abortTimeout = this.startAbortTimer(ONE_SECOND * 3); - const attestatiatonJwt = await new Promise((resolve) => { + const attestationJwt = await new Promise((resolve) => { const abortListener = () => { window.removeEventListener("message", listener); document.body.removeChild(iframe); @@ -93,8 +93,8 @@ export class Verify extends IVerify { document.body.appendChild(iframe); window.addEventListener("message", listener, { signal: this.abortController.signal }); }); - this.logger.debug("jwt attestation", attestatiatonJwt); - return attestatiatonJwt as string; + this.logger.debug("jwt attestation", attestationJwt); + return attestationJwt as string; } catch (e) { this.logger.warn(e); } From fb3dceb5b0b451d1804146b43d9b76a792d6f5f4 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Wed, 14 Aug 2024 14:17:52 +0300 Subject: [PATCH 33/61] feat: implements `ecdsa` signature validation without relying on `node:crypto` or `crypto.subtle` --- package-lock.json | 235 +++++++++++++++++++----- package.json | 1 + packages/core/src/controllers/verify.ts | 33 ++-- packages/utils/package.json | 2 + packages/utils/src/crypto.ts | 91 ++++++--- packages/utils/test/crypto.spec.ts | 36 ++-- rollup.config.js | 2 + 7 files changed, 290 insertions(+), 110 deletions(-) diff --git a/package-lock.json b/package-lock.json index a7b1e6a7d..528c2f2b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ ], "devDependencies": { "@rollup/plugin-commonjs": "22.0.2", + "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "13.3.0", "@types/node": "18.7.3", "@types/sinon": "10.0.13", @@ -6372,6 +6373,54 @@ "rollup": "^2.68.0" } }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json/node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json/node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, "node_modules/@rollup/plugin-node-resolve": { "version": "13.3.0", "dev": true, @@ -7421,6 +7470,15 @@ "@types/chai": "*" } }, + "node_modules/@types/elliptic": { + "version": "6.4.18", + "resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.18.tgz", + "integrity": "sha512-UseG6H5vjRiNpQvrhy4VF/JXdA3V/Fp5amvveaL+fs28BZ6xIKJBPnUPRlEaZpysD9MbpfaLi8lbl7PGUAkpWw==", + "dev": true, + "dependencies": { + "@types/bn.js": "*" + } + }, "node_modules/@types/estree": { "version": "0.0.39", "dev": true, @@ -27467,7 +27525,7 @@ }, "packages/core": { "name": "@walletconnect/core", - "version": "2.14.0", + "version": "2.14.0-canary.5", "license": "Apache-2.0", "dependencies": { "@walletconnect/heartbeat": "1.2.2", @@ -27481,8 +27539,8 @@ "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.14.0", - "@walletconnect/utils": "2.14.0", + "@walletconnect/types": "2.14.0-canary.5", + "@walletconnect/utils": "2.14.0-canary.5", "events": "3.3.0", "isomorphic-unfetch": "3.1.0", "lodash.isequal": "4.5.0", @@ -27521,7 +27579,7 @@ }, "packages/react-native-compat": { "name": "@walletconnect/react-native-compat", - "version": "2.14.0", + "version": "2.14.0-canary.5", "license": "Apache-2.0", "dependencies": { "events": "3.3.0", @@ -27542,17 +27600,17 @@ }, "packages/sign-client": { "name": "@walletconnect/sign-client", - "version": "2.14.0", + "version": "2.14.0-canary.5", "license": "Apache-2.0", "dependencies": { - "@walletconnect/core": "2.14.0", + "@walletconnect/core": "2.14.0-canary.5", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.14.0", - "@walletconnect/utils": "2.14.0", + "@walletconnect/types": "2.14.0-canary.5", + "@walletconnect/utils": "2.14.0-canary.5", "events": "3.3.0" }, "devDependencies": { @@ -27564,7 +27622,7 @@ }, "packages/types": { "name": "@walletconnect/types", - "version": "2.14.0", + "version": "2.14.0-canary.5", "license": "Apache-2.0", "dependencies": { "@walletconnect/events": "1.0.1", @@ -27577,7 +27635,7 @@ }, "packages/utils": { "name": "@walletconnect/utils", - "version": "2.14.0", + "version": "2.14.0-canary.5", "license": "Apache-2.0", "dependencies": { "@stablelib/chacha20poly1305": "1.0.1", @@ -27588,17 +27646,38 @@ "@walletconnect/relay-api": "1.0.11", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.14.0", + "@walletconnect/types": "2.14.0-canary.5", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", + "elliptic": "^6.5.7", "query-string": "7.1.3", "uint8arrays": "3.1.0" }, "devDependencies": { + "@types/elliptic": "^6.4.18", "@types/lodash.isequal": "4.5.6" } }, + "packages/utils/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "packages/utils/node_modules/elliptic": { + "version": "6.5.7", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", + "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "packages/utils/node_modules/uint8arrays": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.0.tgz", @@ -27609,17 +27688,17 @@ }, "packages/web3wallet": { "name": "@walletconnect/web3wallet", - "version": "1.13.0", + "version": "2.14.0-canary.5", "license": "Apache-2.0", "dependencies": { "@walletconnect/auth-client": "2.1.2", - "@walletconnect/core": "2.14.0", + "@walletconnect/core": "2.14.0-canary.5", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.14.0", - "@walletconnect/types": "2.14.0", - "@walletconnect/utils": "2.14.0" + "@walletconnect/sign-client": "2.14.0-canary.5", + "@walletconnect/types": "2.14.0-canary.5", + "@walletconnect/utils": "2.14.0-canary.5" }, "devDependencies": { "@ethersproject/wallet": "5.7.0" @@ -27627,7 +27706,7 @@ }, "providers/ethereum-provider": { "name": "@walletconnect/ethereum-provider", - "version": "2.14.0", + "version": "2.14.0-canary.5", "license": "Apache-2.0", "dependencies": { "@walletconnect/jsonrpc-http-connection": "1.0.8", @@ -27635,10 +27714,10 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/modal": "2.6.2", - "@walletconnect/sign-client": "2.14.0", - "@walletconnect/types": "2.14.0", - "@walletconnect/universal-provider": "2.14.0", - "@walletconnect/utils": "2.14.0", + "@walletconnect/sign-client": "2.14.0-canary.5", + "@walletconnect/types": "2.14.0-canary.5", + "@walletconnect/universal-provider": "2.14.0-canary.5", + "@walletconnect/utils": "2.14.0-canary.5", "events": "3.3.0" }, "devDependencies": { @@ -27659,14 +27738,14 @@ }, "providers/signer-connection": { "name": "@walletconnect/signer-connection", - "version": "2.14.0", + "version": "2.14.0-canary.5", "license": "Apache-2.0", "dependencies": { "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/sign-client": "2.14.0", - "@walletconnect/types": "2.14.0", - "@walletconnect/utils": "2.14.0", + "@walletconnect/sign-client": "2.14.0-canary.5", + "@walletconnect/types": "2.14.0-canary.5", + "@walletconnect/utils": "2.14.0-canary.5", "events": "3.3.0", "uint8arrays": "3.1.0" } @@ -27681,7 +27760,7 @@ }, "providers/universal-provider": { "name": "@walletconnect/universal-provider", - "version": "2.14.0", + "version": "2.14.0-canary.5", "license": "Apache-2.0", "dependencies": { "@walletconnect/jsonrpc-http-connection": "1.0.8", @@ -27689,9 +27768,9 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.14.0", - "@walletconnect/types": "2.14.0", - "@walletconnect/utils": "2.14.0", + "@walletconnect/sign-client": "2.14.0-canary.5", + "@walletconnect/types": "2.14.0-canary.5", + "@walletconnect/utils": "2.14.0-canary.5", "events": "3.3.0" }, "devDependencies": { @@ -32521,6 +32600,34 @@ "resolve": "^1.17.0" } }, + "@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.1.0" + }, + "dependencies": { + "@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + } + }, + "@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + } + } + }, "@rollup/plugin-node-resolve": { "version": "13.3.0", "dev": true, @@ -33372,6 +33479,15 @@ "@types/chai": "*" } }, + "@types/elliptic": { + "version": "6.4.18", + "resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.18.tgz", + "integrity": "sha512-UseG6H5vjRiNpQvrhy4VF/JXdA3V/Fp5amvveaL+fs28BZ6xIKJBPnUPRlEaZpysD9MbpfaLi8lbl7PGUAkpWw==", + "dev": true, + "requires": { + "@types/bn.js": "*" + } + }, "@types/estree": { "version": "0.0.39", "dev": true @@ -33638,8 +33754,8 @@ "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.14.0", - "@walletconnect/utils": "2.14.0", + "@walletconnect/types": "2.14.0-canary.5", + "@walletconnect/utils": "2.14.0-canary.5", "events": "3.3.0", "isomorphic-unfetch": "3.1.0", "lodash.isequal": "4.5.0", @@ -33687,10 +33803,10 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/modal": "2.6.2", - "@walletconnect/sign-client": "2.14.0", - "@walletconnect/types": "2.14.0", - "@walletconnect/universal-provider": "2.14.0", - "@walletconnect/utils": "2.14.0", + "@walletconnect/sign-client": "2.14.0-canary.5", + "@walletconnect/types": "2.14.0-canary.5", + "@walletconnect/universal-provider": "2.14.0-canary.5", + "@walletconnect/utils": "2.14.0-canary.5", "ethereum-test-network": "0.1.6", "ethers": "5.6.9", "events": "3.3.0", @@ -33879,7 +33995,7 @@ "version": "file:packages/sign-client", "requires": { "@aws-sdk/client-cloudwatch": "3.450.0", - "@walletconnect/core": "2.14.0", + "@walletconnect/core": "2.14.0-canary.5", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-provider": "1.0.14", @@ -33888,8 +34004,8 @@ "@walletconnect/logger": "2.1.2", "@walletconnect/relay-api": "1.0.11", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.14.0", - "@walletconnect/utils": "2.14.0", + "@walletconnect/types": "2.14.0-canary.5", + "@walletconnect/utils": "2.14.0-canary.5", "events": "3.3.0" } }, @@ -33898,9 +34014,9 @@ "requires": { "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/sign-client": "2.14.0", - "@walletconnect/types": "2.14.0", - "@walletconnect/utils": "2.14.0", + "@walletconnect/sign-client": "2.14.0-canary.5", + "@walletconnect/types": "2.14.0-canary.5", + "@walletconnect/utils": "2.14.0-canary.5", "events": "3.3.0", "uint8arrays": "3.1.0" }, @@ -33945,9 +34061,9 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.14.0", - "@walletconnect/types": "2.14.0", - "@walletconnect/utils": "2.14.0", + "@walletconnect/sign-client": "2.14.0-canary.5", + "@walletconnect/types": "2.14.0-canary.5", + "@walletconnect/utils": "2.14.0-canary.5", "cosmos-wallet": "1.2.0", "ethereum-test-network": "0.1.6", "ethers": "5.7.0", @@ -34132,18 +34248,39 @@ "@stablelib/random": "1.0.2", "@stablelib/sha256": "1.0.1", "@stablelib/x25519": "1.0.3", + "@types/elliptic": "^6.4.18", "@types/lodash.isequal": "4.5.6", "@walletconnect/relay-api": "1.0.11", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.14.0", + "@walletconnect/types": "2.14.0-canary.5", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", + "elliptic": "^6.5.7", "query-string": "7.1.3", "uint8arrays": "3.1.0" }, "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "elliptic": { + "version": "6.5.7", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", + "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "uint8arrays": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.0.tgz", @@ -34159,13 +34296,13 @@ "requires": { "@ethersproject/wallet": "5.7.0", "@walletconnect/auth-client": "2.1.2", - "@walletconnect/core": "2.14.0", + "@walletconnect/core": "2.14.0-canary.5", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.14.0", - "@walletconnect/types": "2.14.0", - "@walletconnect/utils": "2.14.0" + "@walletconnect/sign-client": "2.14.0-canary.5", + "@walletconnect/types": "2.14.0-canary.5", + "@walletconnect/utils": "2.14.0-canary.5" } }, "@walletconnect/window-getters": { diff --git a/package.json b/package.json index 0d53dd87c..658f4d83e 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ }, "devDependencies": { "@rollup/plugin-commonjs": "22.0.2", + "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "13.3.0", "@types/node": "18.7.3", "@types/sinon": "10.0.13", diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index 3f9a22f6a..bcf87ff94 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -1,12 +1,6 @@ import { generateChildLogger, getLoggerContext, Logger } from "@walletconnect/logger"; import { IVerify } from "@walletconnect/types"; -import { - getCryptoKeyFromKeyData, - isBrowser, - isNode, - P256KeyDataType, - verifyP256Jwt, -} from "@walletconnect/utils"; +import { isBrowser, isNode, P256KeyDataType, verifyP256Jwt } from "@walletconnect/utils"; import { FIVE_SECONDS, ONE_SECOND, toMiliseconds } from "@walletconnect/time"; import { getDocument } from "@walletconnect/window-getters"; @@ -185,7 +179,7 @@ export class Verify extends IVerify { return validation; } } catch (e) { - this.logger.warn(e); + console.error(e); this.logger.warn("error validating attestation"); } const newKey = await this.fetchAndPersistPublicKey(); @@ -214,28 +208,29 @@ export class Verify extends IVerify { }; private validateAttestation = async (attestation: string, key: jwk) => { - const cryptoKey = await getCryptoKeyFromKeyData(key?.publicKey); + console.log("cryptoKey", key); const result = await verifyP256Jwt<{ exp: number; id: string; origin: string; isScam: boolean; - }>(attestation, cryptoKey); + isVerified: boolean; + }>(attestation, key.publicKey); + console.log("decoded result", result); const validation = { - valid: result.verified, - hasExpired: toMiliseconds(result.payload.payload.exp) < Date.now(), - payload: result.payload.payload, + valid: true, + hasExpired: toMiliseconds(result.exp) < Date.now(), + payload: result, }; + if (validation.hasExpired) { this.logger.warn("resolve: jwt attestation expired"); throw new Error("JWT attestation expired"); } - return validation.valid - ? { - origin: validation.payload.origin, - isScam: validation.payload.isScam, - } - : undefined; + return { + origin: validation.payload.origin, + isScam: validation.payload.isScam, + }; }; } diff --git a/packages/utils/package.json b/packages/utils/package.json index 30573249c..5f9133c5f 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -43,10 +43,12 @@ "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", + "elliptic": "^6.5.7", "query-string": "7.1.3", "uint8arrays": "3.1.0" }, "devDependencies": { + "@types/elliptic": "^6.4.18", "@types/lodash.isequal": "4.5.6" } } diff --git a/packages/utils/src/crypto.ts b/packages/utils/src/crypto.ts index d8603f946..8adecb32c 100644 --- a/packages/utils/src/crypto.ts +++ b/packages/utils/src/crypto.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import { ChaCha20Poly1305 } from "@stablelib/chacha20poly1305"; import { HKDF } from "@stablelib/hkdf"; import { randomBytes } from "@stablelib/random"; @@ -5,9 +6,8 @@ import { hash, SHA256 } from "@stablelib/sha256"; import * as x25519 from "@stablelib/x25519"; import { CryptoTypes } from "@walletconnect/types"; import { concat, fromString, toString } from "uint8arrays"; +import { ec as EC } from "elliptic"; import { decodeJWT } from "@walletconnect/relay-auth"; -import { webcrypto } from "crypto"; -import { getSubtleCrypto } from "@walletconnect/environment"; export const BASE10 = "base10"; export const BASE16 = "base16"; @@ -173,36 +173,69 @@ export function isTypeOneEnvelope( ); } -export function getCryptoKeyFromKeyData(keyData: P256KeyDataType): Promise { - return getSubtle().importKey( - "jwk", - keyData, - { name: "ECDSA", namedCurve: keyData.crv }, - keyData.ext, - keyData.key_ops, +export async function getCryptoKeyFromKeyData(keyData: P256KeyDataType): Promise { + const ec = new EC("p256"); + const key = ec.keyFromPublic( + { + x: Buffer.from(keyData.x, "base64").toString("hex"), + y: Buffer.from(keyData.y, "base64").toString("hex"), + }, + "hex", ); + return key; } -export async function verifyP256Jwt(token: string, publicKey: CryptoKey) { - const payload = decodeJWT(token) as unknown as { - payload: T; - signature: ArrayBuffer; - data: ArrayBuffer; - }; - const alg = { - name: "ECDSA", - hash: { - name: "SHA-256", - }, - namedCurve: "P-256", - }; - const verified = await getSubtle().verify(alg, publicKey, payload.signature, payload.data); - return { - payload, - verified, - }; +// Utility function to decode Base64 URL +function base64UrlToBase64(base64Url: string) { + let base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); + const padding = base64.length % 4; + if (padding > 0) { + base64 += "=".repeat(4 - padding); + } + return base64; +} + +function base64UrlDecode(base64Url: string) { + return Buffer.from(base64UrlToBase64(base64Url), "base64"); } -export function getSubtle() { - return getSubtleCrypto() || webcrypto.subtle; +export async function verifyP256Jwt(token: string, keyData: P256KeyDataType) { + console.log("verifying...", token, keyData); + + const [headerBase64Url, payloadBase64Url, signatureBase64Url] = token.split("."); + + // Decode the signature + const signatureBuffer = base64UrlDecode(signatureBase64Url); + + // Check if signature length is correct (64 bytes for P-256) + if (signatureBuffer.length !== 64) { + throw new Error("Invalid signature length"); + } + + // Extract r and s from the signature + const r = signatureBuffer.slice(0, 32).toString("hex"); + const s = signatureBuffer.slice(32, 64).toString("hex"); + + // Create the signing input + const signingInput = `${headerBase64Url}.${payloadBase64Url}`; + + const sha256 = new SHA256(); + const buffer = sha256.update(Buffer.from(signingInput)).digest(); + + const key = await getCryptoKeyFromKeyData(keyData); + + // Convert the hash to hex format + const hashHex = Buffer.from(buffer).toString("hex"); + + // Verify the signature + const isValid = key.verify(hashHex, { r, s }); + + if (!isValid) { + throw new Error("Invalid signature"); + } + const data = decodeJWT(token) as unknown as { payload: T }; + return { + ...data.payload, + isVerified: isValid, + }; } diff --git a/packages/utils/test/crypto.spec.ts b/packages/utils/test/crypto.spec.ts index ddd0820f2..ec3efc428 100644 --- a/packages/utils/test/crypto.spec.ts +++ b/packages/utils/test/crypto.spec.ts @@ -1,6 +1,9 @@ import { expect, describe, it } from "vitest"; import { toString } from "uint8arrays"; import { safeJsonStringify } from "@walletconnect/safe-json"; +import { SHA256 } from "@stablelib/sha256"; +import { Buffer } from "buffer"; +import elliptic from "elliptic"; import { BASE16, @@ -14,7 +17,6 @@ import { validateDecoding, isTypeOneEnvelope, generateRandomBytes32, - verifyJwt, verifyP256Jwt, getCryptoKeyFromKeyData, P256KeyDataType, @@ -112,9 +114,10 @@ describe("Crypto", () => { it("calls generateRandomBytes32", () => { expect(generateRandomBytes32()).toBeTruthy(); }); - it("should validate verify v2 jwt", async () => { - const jwt = - "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjI0MjE3NDAsImlkIjoiNTk5NzQ1ZjJkMTJlNjIxNTFlZDg4ZmMyOWFiYTBkMjVmZWJlYWFlMjliNzU4ZDdmMGNhZGRhOTg0YzkwZjI4YSIsIm9yaWdpbiI6Imh0dHBzOi8vODk1MS03OC0xMzAtMTk4LTE0My5uZ3Jvay1mcmVlLmFwcCIsImlzU2NhbSI6bnVsbH0._pCu1gaZcEo4yjgyDwQZFXS8Q_SA4xdH51dji3bJjpviW73ieEHxWg4zg0Uc0W1Q62AZWW4o-W5id4yZy88dTw"; + it.only("should validate verify v2 jwt", async () => { + const token = + "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjM2MzI1MDQsImlkIjoiMDkxN2YzMzk0YTdmMzkyZTg3ZTM1ZjM4OTg2OWU2NDEzZjkyNTBlMGIxZTE4YjUzMDhkNzBhM2VjOTJjZDQ3OCIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImlzU2NhbSI6bnVsbCwiaXNWZXJpZmllZCI6ZmFsc2V9.RehA28c0Ae8D_ixvGS8uG9J9eTJtpGfaC_7kNE9ZNAVFREWBY6Dl_SXc0_E0RSvYkHpupfmXlmjenuDqNcyoeg"; + const publicKey = { publicKey: { crv: "P-256", @@ -124,16 +127,23 @@ describe("Crypto", () => { x: "CbL4DOYOb1ntd-8OmExO-oS0DWCMC00DntrymJoB8tk", y: "KTFwjHtQxGTDR91VsOypcdBfvbo6sAMj5p4Wb-9hRA0", }, - expiresAt: 1725091080, + expiresAt: 1726209328, }; - const keyData = await getCryptoKeyFromKeyData(publicKey.publicKey as P256KeyDataType); - const result = await verifyP256Jwt(jwt, keyData); + + const result = await verifyP256Jwt<{ + exp: number; + id: string; + origin: string; + isScam: boolean; + isVerified: true; + }>(token, publicKey.publicKey); + console.log("result", result); + expect(result).to.exist; expect(result).to.exist; - expect(result.verified).to.exist; - expect(result.verified).to.be.true; - expect(result.payload).to.exist; - expect(result.payload.payload).to.exist; - expect(result.payload.data).to.exist; - expect(result.payload.signature).to.exist; + expect(result.isVerified).to.be.true; + expect(result.exp).to.exist; + expect(result.origin).to.exist; + expect(result.isScam).to.be.null; + await new Promise((resolve) => setTimeout(resolve, 1000)); }); }); diff --git a/rollup.config.js b/rollup.config.js index 83662e0bb..302e57c6c 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,10 +1,12 @@ import esbuild from "rollup-plugin-esbuild"; import { nodeResolve } from "@rollup/plugin-node-resolve"; import commonjs from "@rollup/plugin-commonjs"; +import json from "@rollup/plugin-json"; const input = "./src/index.ts"; const plugins = [ nodeResolve({ preferBuiltins: false, browser: true }), + json(), commonjs(), esbuild({ minify: true, From 0c4df5caa657b3acf3a53feab9e25f7cb271d92a Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Thu, 15 Aug 2024 11:46:27 +0300 Subject: [PATCH 34/61] chore: cleanup --- packages/core/src/controllers/verify.ts | 77 +++++++++++++++---------- packages/core/src/core.ts | 2 +- packages/types/src/core/verify.ts | 3 +- packages/utils/src/cacao.ts | 1 - packages/utils/src/crypto.ts | 10 +--- 5 files changed, 54 insertions(+), 39 deletions(-) diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index bcf87ff94..925a47395 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -1,25 +1,42 @@ import { generateChildLogger, getLoggerContext, Logger } from "@walletconnect/logger"; -import { IVerify } from "@walletconnect/types"; +import { ICore, IVerify } from "@walletconnect/types"; import { isBrowser, isNode, P256KeyDataType, verifyP256Jwt } from "@walletconnect/utils"; import { FIVE_SECONDS, ONE_SECOND, toMiliseconds } from "@walletconnect/time"; import { getDocument } from "@walletconnect/window-getters"; -import { TRUSTED_VERIFY_URLS, VERIFY_CONTEXT, VERIFY_SERVER, VERIFY_SERVER_V2 } from "../constants"; +import { + CORE_STORAGE_PREFIX, + CORE_VERSION, + TRUSTED_VERIFY_URLS, + VERIFY_CONTEXT, + VERIFY_SERVER, + VERIFY_SERVER_V2, +} from "../constants"; import { IKeyValueStorage } from "@walletconnect/keyvaluestorage"; -type jwk = { +type Jwk = { publicKey: P256KeyDataType; expiresAt: number; }; +type JwkPayload = { + exp: number; + id: string; + origin: string; + isScam: boolean; + isVerified: boolean; +}; export class Verify extends IVerify { public name = VERIFY_CONTEXT; private abortController: AbortController; private isDevEnv; private verifyUrlV2 = VERIFY_SERVER_V2; - private publicKey?: jwk; + private storagePrefix = CORE_STORAGE_PREFIX; + private version = CORE_VERSION; + private publicKey?: Jwk; + private fetchPromise?: Promise; - constructor(public projectId: string, public logger: Logger, public store: IKeyValueStorage) { - super(projectId, logger, store); + constructor(public core: ICore, public logger: Logger, public store: IKeyValueStorage) { + super(core, logger, store); this.logger = generateChildLogger(logger, this.name); this.abortController = new AbortController(); this.isDevEnv = isNode() && process.env.IS_VITEST; @@ -27,7 +44,9 @@ export class Verify extends IVerify { } get storeKey(): string { - return `verify:public:key`; + return ( + this.storagePrefix + this.version + this.core.customStoragePrefix + "//" + `verify:public:key` + ); } public init = async () => { @@ -44,7 +63,7 @@ export class Verify extends IVerify { public register: IVerify["register"] = async (params) => { if (!isBrowser()) return; const { id, decryptedId } = params; - const url = `${this.verifyUrlV2}/attestation?projectId=${this.projectId}`; + const url = `${this.verifyUrlV2}/attestation?projectId=${this.core.projectId}`; let src = ""; try { const response = await fetch(url, { @@ -121,7 +140,7 @@ export class Verify extends IVerify { this.logger.debug(`resolving attestation: ${attestationId} from url: ${url}`); // set artificial timeout to prevent hanging const timeout = this.startAbortTimer(ONE_SECOND * 5); - const result = await fetch(`${url}/attestation/${attestationId}`, { + const result = await fetch(`${url}/attestation/${attestationId}?v2Supported=true`, { signal: this.abortController.signal, }); clearTimeout(timeout); @@ -152,14 +171,14 @@ export class Verify extends IVerify { signal: this.abortController.signal, }); clearTimeout(timeout); - return (await result.json()) as jwk; + return (await result.json()) as Jwk; } catch (e) { this.logger.warn(e); } return undefined; }; - private persistPublicKey = async (publicKey: jwk) => { + private persistPublicKey = async (publicKey: Jwk) => { this.logger.debug(`persisting public key to local storage`, publicKey); await this.store.setItem(this.storeKey, publicKey); this.publicKey = publicKey; @@ -175,21 +194,21 @@ export class Verify extends IVerify { const key = await this.getPublicKey(); try { if (key) { - const validation = await this.validateAttestation(attestation, key); + const validation = this.validateAttestation(attestation, key); return validation; } } catch (e) { - console.error(e); + this.logger.error(e); this.logger.warn("error validating attestation"); } const newKey = await this.fetchAndPersistPublicKey(); try { if (newKey) { - const validation = await this.validateAttestation(attestation, newKey); + const validation = this.validateAttestation(attestation, newKey); return validation; } } catch (e) { - this.logger.warn(e); + this.logger.error(e); this.logger.warn("error validating attestation"); } return undefined; @@ -201,24 +220,24 @@ export class Verify extends IVerify { }; private fetchAndPersistPublicKey = async () => { - const key = await this.fetchPublicKey(); - if (!key) return; - await this.persistPublicKey(key); + if (this.fetchPromise) { + await this.fetchPromise; + return this.publicKey; + } + this.fetchPromise = new Promise(async (resolve) => { + const key = await this.fetchPublicKey(); + if (!key) return; + await this.persistPublicKey(key); + resolve(key); + }); + const key = await this.fetchPromise; + this.fetchPromise = undefined; return key; }; - private validateAttestation = async (attestation: string, key: jwk) => { - console.log("cryptoKey", key); - const result = await verifyP256Jwt<{ - exp: number; - id: string; - origin: string; - isScam: boolean; - isVerified: boolean; - }>(attestation, key.publicKey); - console.log("decoded result", result); + private validateAttestation = (attestation: string, key: Jwk) => { + const result = verifyP256Jwt(attestation, key.publicKey); const validation = { - valid: true, hasExpired: toMiliseconds(result.exp) < Date.now(), payload: result, }; diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index ffacd3c90..1afc9cffa 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -108,7 +108,7 @@ export class Core extends ICore { projectId: this.projectId, }); this.pairing = new Pairing(this, this.logger); - this.verify = new Verify(this.projectId || "", this.logger, this.storage); + this.verify = new Verify(this, this.logger, this.storage); this.echoClient = new EchoClient(this.projectId || "", this.logger); } diff --git a/packages/types/src/core/verify.ts b/packages/types/src/core/verify.ts index 1275e6842..ca13f5380 100644 --- a/packages/types/src/core/verify.ts +++ b/packages/types/src/core/verify.ts @@ -1,5 +1,6 @@ import { Logger } from "@walletconnect/logger"; import { IKeyValueStorage } from "@walletconnect/keyvaluestorage"; +import { ICore } from "./core"; export declare namespace Verify { export interface Context { @@ -15,7 +16,7 @@ export declare namespace Verify { export abstract class IVerify { public abstract readonly context: string; - constructor(public projectId: string, public logger: Logger, public store: IKeyValueStorage) {} + constructor(public core: ICore, public logger: Logger, public store: IKeyValueStorage) {} public abstract register(params: { id: string; diff --git a/packages/utils/src/cacao.ts b/packages/utils/src/cacao.ts index dffdf35b8..101872715 100644 --- a/packages/utils/src/cacao.ts +++ b/packages/utils/src/cacao.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import { AuthTypes } from "@walletconnect/types"; import { getCommonValuesInArrays } from "./misc"; import { verifySignature } from "./signatures"; diff --git a/packages/utils/src/crypto.ts b/packages/utils/src/crypto.ts index 8adecb32c..2a5a00098 100644 --- a/packages/utils/src/crypto.ts +++ b/packages/utils/src/crypto.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import { ChaCha20Poly1305 } from "@stablelib/chacha20poly1305"; import { HKDF } from "@stablelib/hkdf"; import { randomBytes } from "@stablelib/random"; @@ -173,7 +172,7 @@ export function isTypeOneEnvelope( ); } -export async function getCryptoKeyFromKeyData(keyData: P256KeyDataType): Promise { +export function getCryptoKeyFromKeyData(keyData: P256KeyDataType): EC.KeyPair { const ec = new EC("p256"); const key = ec.keyFromPublic( { @@ -185,7 +184,6 @@ export async function getCryptoKeyFromKeyData(keyData: P256KeyDataType): Promise return key; } -// Utility function to decode Base64 URL function base64UrlToBase64(base64Url: string) { let base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); const padding = base64.length % 4; @@ -199,9 +197,7 @@ function base64UrlDecode(base64Url: string) { return Buffer.from(base64UrlToBase64(base64Url), "base64"); } -export async function verifyP256Jwt(token: string, keyData: P256KeyDataType) { - console.log("verifying...", token, keyData); - +export function verifyP256Jwt(token: string, keyData: P256KeyDataType) { const [headerBase64Url, payloadBase64Url, signatureBase64Url] = token.split("."); // Decode the signature @@ -222,7 +218,7 @@ export async function verifyP256Jwt(token: string, keyData: P256KeyDataType) const sha256 = new SHA256(); const buffer = sha256.update(Buffer.from(signingInput)).digest(); - const key = await getCryptoKeyFromKeyData(keyData); + const key = getCryptoKeyFromKeyData(keyData); // Convert the hash to hex format const hashHex = Buffer.from(buffer).toString("hex"); From 29f8df8080454dfb921a4a55cd1395448528d44e Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Thu, 15 Aug 2024 11:52:12 +0300 Subject: [PATCH 35/61] feat: adds unhappy path tests --- packages/utils/test/crypto.spec.ts | 56 ++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/packages/utils/test/crypto.spec.ts b/packages/utils/test/crypto.spec.ts index ec3efc428..8b7efeed4 100644 --- a/packages/utils/test/crypto.spec.ts +++ b/packages/utils/test/crypto.spec.ts @@ -114,7 +114,7 @@ describe("Crypto", () => { it("calls generateRandomBytes32", () => { expect(generateRandomBytes32()).toBeTruthy(); }); - it.only("should validate verify v2 jwt", async () => { + it("should validate verify v2 jwt", async () => { const token = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjM2MzI1MDQsImlkIjoiMDkxN2YzMzk0YTdmMzkyZTg3ZTM1ZjM4OTg2OWU2NDEzZjkyNTBlMGIxZTE4YjUzMDhkNzBhM2VjOTJjZDQ3OCIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImlzU2NhbSI6bnVsbCwiaXNWZXJpZmllZCI6ZmFsc2V9.RehA28c0Ae8D_ixvGS8uG9J9eTJtpGfaC_7kNE9ZNAVFREWBY6Dl_SXc0_E0RSvYkHpupfmXlmjenuDqNcyoeg"; @@ -130,7 +130,7 @@ describe("Crypto", () => { expiresAt: 1726209328, }; - const result = await verifyP256Jwt<{ + const result = verifyP256Jwt<{ exp: number; id: string; origin: string; @@ -146,4 +146,56 @@ describe("Crypto", () => { expect(result.isScam).to.be.null; await new Promise((resolve) => setTimeout(resolve, 1000)); }); + it("should fail to validate invalid jwt with public key", async () => { + const token = + "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.oiMDkxN2YzMzk0YTdmMzkyZTg3ZTM1ZjM4OTg2OWU2NDEzZjkyNTBlMGIxZTE4YjUzMDhkNzBhM2VjOTJjZDQ3OCIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImlzU2NhbSI6bnVsbCwiaXNWZXJpZmllZCI6ZmFsc2V9.RehA28c0Ae8D_ixvGS8uG9J9eTJtpGfaC_7kNE9ZNAVFREWBY6Dl_SXc0_E0RSvYkHpupfmXlmjenuDqNcyoeg"; + + const publicKey = { + publicKey: { + crv: "P-256", + ext: true, + key_ops: ["verify"], + kty: "EC", + x: "CbL4DOYOb1ntd-8OmExO-oS0DWCMC00DntrymJoB8tk", + y: "KTFwjHtQxGTDR91VsOypcdBfvbo6sAMj5p4Wb-9hRA0", + }, + expiresAt: 1726209328, + }; + + expect(() => + verifyP256Jwt<{ + exp: number; + id: string; + origin: string; + isScam: boolean; + isVerified: true; + }>(token, publicKey.publicKey), + ).to.throw(); + }); + it("should fail to validate validate verify v2 jwt with invalid public key", async () => { + const token = + "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjM2MzI1MDQsImlkIjoiMDkxN2YzMzk0YTdmMzkyZTg3ZTM1ZjM4OTg2OWU2NDEzZjkyNTBlMGIxZTE4YjUzMDhkNzBhM2VjOTJjZDQ3OCIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImlzU2NhbSI6bnVsbCwiaXNWZXJpZmllZCI6ZmFsc2V9.RehA28c0Ae8D_ixvGS8uG9J9eTJtpGfaC_7kNE9ZNAVFREWBY6Dl_SXc0_E0RSvYkHpupfmXlmjenuDqNcyoeg"; + + const publicKey = { + publicKey: { + crv: "P-256", + ext: true, + key_ops: ["verify"], + kty: "EC", + x: "CbL4DOYOb1ntd-8OmExO-oS0DWCMC00Dn", + y: "KTFwjHtQxGTDR91VsOypcdBfvbo6sAMj5p4Wb-9hRA0", + }, + expiresAt: 1726209328, + }; + + expect(() => + verifyP256Jwt<{ + exp: number; + id: string; + origin: string; + isScam: boolean; + isVerified: true; + }>(token, publicKey.publicKey), + ).to.throw(); + }); }); From 1c229bd7d15f0c465b9fd5ee40d3617830c9b725 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Thu, 15 Aug 2024 11:53:50 +0300 Subject: [PATCH 36/61] chore: uses const --- packages/core/src/constants/verify.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/constants/verify.ts b/packages/core/src/constants/verify.ts index 08ff1b0a3..77049537a 100644 --- a/packages/core/src/constants/verify.ts +++ b/packages/core/src/constants/verify.ts @@ -3,6 +3,6 @@ export const VERIFY_CONTEXT = "verify-api"; const VERIFY_SERVER_COM = "https://verify.walletconnect.com"; const VERIFY_SERVER_ORG = "https://verify.walletconnect.org"; export const VERIFY_SERVER = VERIFY_SERVER_ORG; -export const VERIFY_SERVER_V2 = "https://verify.walletconnect.org/v2"; +export const VERIFY_SERVER_V2 = `${VERIFY_SERVER}/v2`; export const TRUSTED_VERIFY_URLS = [VERIFY_SERVER_COM, VERIFY_SERVER_ORG]; From 9f48275de736d5b69069cc10a7f3e866175ba9a7 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Thu, 15 Aug 2024 13:41:10 +0300 Subject: [PATCH 37/61] chore: prep for `2.15.0` release --- lerna.json | 2 +- package-lock.json | 98 +++++++++++------------ packages/core/package.json | 6 +- packages/core/src/constants/relayer.ts | 2 +- packages/react-native-compat/package.json | 2 +- packages/sign-client/package.json | 8 +- packages/types/package.json | 2 +- packages/utils/package.json | 4 +- packages/web3wallet/package.json | 10 +-- providers/ethereum-provider/package.json | 10 +-- providers/signer-connection/package.json | 8 +- providers/universal-provider/package.json | 8 +- 12 files changed, 80 insertions(+), 80 deletions(-) diff --git a/lerna.json b/lerna.json index 840e083a0..75af9a89b 100644 --- a/lerna.json +++ b/lerna.json @@ -4,5 +4,5 @@ "packages/*", "providers/*" ], - "version": "2.14.0" + "version": "2.15.0" } diff --git a/package-lock.json b/package-lock.json index efde279d9..30eeabeff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27458,7 +27458,7 @@ }, "packages/core": { "name": "@walletconnect/core", - "version": "2.14.0-canary.5", + "version": "2.15.0", "license": "Apache-2.0", "dependencies": { "@walletconnect/heartbeat": "1.2.2", @@ -27472,8 +27472,8 @@ "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.14.0-canary.5", - "@walletconnect/utils": "2.14.0-canary.5", + "@walletconnect/types": "2.15.0", + "@walletconnect/utils": "2.15.0", "events": "3.3.0", "lodash.isequal": "4.5.0", "uint8arrays": "3.1.0" @@ -27495,7 +27495,7 @@ }, "packages/react-native-compat": { "name": "@walletconnect/react-native-compat", - "version": "2.14.0-canary.5", + "version": "2.15.0", "license": "Apache-2.0", "dependencies": { "events": "3.3.0", @@ -27516,17 +27516,17 @@ }, "packages/sign-client": { "name": "@walletconnect/sign-client", - "version": "2.14.0-canary.5", + "version": "2.15.0", "license": "Apache-2.0", "dependencies": { - "@walletconnect/core": "2.14.0-canary.5", + "@walletconnect/core": "2.15.0", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.14.0-canary.5", - "@walletconnect/utils": "2.14.0-canary.5", + "@walletconnect/types": "2.15.0", + "@walletconnect/utils": "2.15.0", "events": "3.3.0" }, "devDependencies": { @@ -27538,7 +27538,7 @@ }, "packages/types": { "name": "@walletconnect/types", - "version": "2.14.0-canary.5", + "version": "2.15.0", "license": "Apache-2.0", "dependencies": { "@walletconnect/events": "1.0.1", @@ -27551,7 +27551,7 @@ }, "packages/utils": { "name": "@walletconnect/utils", - "version": "2.14.0-canary.5", + "version": "2.15.0", "license": "Apache-2.0", "dependencies": { "@stablelib/chacha20poly1305": "1.0.1", @@ -27562,7 +27562,7 @@ "@walletconnect/relay-api": "1.0.11", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.14.0-canary.5", + "@walletconnect/types": "2.15.0", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", @@ -27604,17 +27604,17 @@ }, "packages/web3wallet": { "name": "@walletconnect/web3wallet", - "version": "2.14.0-canary.5", + "version": "1.14.0", "license": "Apache-2.0", "dependencies": { "@walletconnect/auth-client": "2.1.2", - "@walletconnect/core": "2.14.0-canary.5", + "@walletconnect/core": "2.15.0", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.14.0-canary.5", - "@walletconnect/types": "2.14.0-canary.5", - "@walletconnect/utils": "2.14.0-canary.5" + "@walletconnect/sign-client": "2.15.0", + "@walletconnect/types": "2.15.0", + "@walletconnect/utils": "2.15.0" }, "devDependencies": { "@ethersproject/wallet": "5.7.0" @@ -27622,7 +27622,7 @@ }, "providers/ethereum-provider": { "name": "@walletconnect/ethereum-provider", - "version": "2.14.0-canary.5", + "version": "2.15.0", "license": "Apache-2.0", "dependencies": { "@walletconnect/jsonrpc-http-connection": "1.0.8", @@ -27630,10 +27630,10 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/modal": "2.6.2", - "@walletconnect/sign-client": "2.14.0-canary.5", - "@walletconnect/types": "2.14.0-canary.5", - "@walletconnect/universal-provider": "2.14.0-canary.5", - "@walletconnect/utils": "2.14.0-canary.5", + "@walletconnect/sign-client": "2.15.0", + "@walletconnect/types": "2.15.0", + "@walletconnect/universal-provider": "2.15.0", + "@walletconnect/utils": "2.15.0", "events": "3.3.0" }, "devDependencies": { @@ -27654,14 +27654,14 @@ }, "providers/signer-connection": { "name": "@walletconnect/signer-connection", - "version": "2.14.0-canary.5", + "version": "2.15.0", "license": "Apache-2.0", "dependencies": { "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/sign-client": "2.14.0-canary.5", - "@walletconnect/types": "2.14.0-canary.5", - "@walletconnect/utils": "2.14.0-canary.5", + "@walletconnect/sign-client": "2.15.0", + "@walletconnect/types": "2.15.0", + "@walletconnect/utils": "2.15.0", "events": "3.3.0", "uint8arrays": "3.1.0" } @@ -27676,7 +27676,7 @@ }, "providers/universal-provider": { "name": "@walletconnect/universal-provider", - "version": "2.14.0-canary.5", + "version": "2.15.0", "license": "Apache-2.0", "dependencies": { "@walletconnect/jsonrpc-http-connection": "1.0.8", @@ -27684,9 +27684,9 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.14.0-canary.5", - "@walletconnect/types": "2.14.0-canary.5", - "@walletconnect/utils": "2.14.0-canary.5", + "@walletconnect/sign-client": "2.15.0", + "@walletconnect/types": "2.15.0", + "@walletconnect/utils": "2.15.0", "events": "3.3.0" }, "devDependencies": { @@ -33670,8 +33670,8 @@ "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.14.0-canary.5", - "@walletconnect/utils": "2.14.0-canary.5", + "@walletconnect/types": "2.15.0", + "@walletconnect/utils": "2.15.0", "events": "3.3.0", "lodash.isequal": "4.5.0", "uint8arrays": "3.1.0" @@ -33706,10 +33706,10 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/modal": "2.6.2", - "@walletconnect/sign-client": "2.14.0-canary.5", - "@walletconnect/types": "2.14.0-canary.5", - "@walletconnect/universal-provider": "2.14.0-canary.5", - "@walletconnect/utils": "2.14.0-canary.5", + "@walletconnect/sign-client": "2.15.0", + "@walletconnect/types": "2.15.0", + "@walletconnect/universal-provider": "2.15.0", + "@walletconnect/utils": "2.15.0", "ethereum-test-network": "0.1.6", "ethers": "5.6.9", "events": "3.3.0", @@ -33898,7 +33898,7 @@ "version": "file:packages/sign-client", "requires": { "@aws-sdk/client-cloudwatch": "3.450.0", - "@walletconnect/core": "2.14.0-canary.5", + "@walletconnect/core": "2.15.0", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-provider": "1.0.14", @@ -33907,8 +33907,8 @@ "@walletconnect/logger": "2.1.2", "@walletconnect/relay-api": "1.0.11", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.14.0-canary.5", - "@walletconnect/utils": "2.14.0-canary.5", + "@walletconnect/types": "2.15.0", + "@walletconnect/utils": "2.15.0", "events": "3.3.0" } }, @@ -33917,9 +33917,9 @@ "requires": { "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/sign-client": "2.14.0-canary.5", - "@walletconnect/types": "2.14.0-canary.5", - "@walletconnect/utils": "2.14.0-canary.5", + "@walletconnect/sign-client": "2.15.0", + "@walletconnect/types": "2.15.0", + "@walletconnect/utils": "2.15.0", "events": "3.3.0", "uint8arrays": "3.1.0" }, @@ -33964,9 +33964,9 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.14.0-canary.5", - "@walletconnect/types": "2.14.0-canary.5", - "@walletconnect/utils": "2.14.0-canary.5", + "@walletconnect/sign-client": "2.15.0", + "@walletconnect/types": "2.15.0", + "@walletconnect/utils": "2.15.0", "cosmos-wallet": "1.2.0", "ethereum-test-network": "0.1.6", "ethers": "5.7.0", @@ -34156,7 +34156,7 @@ "@walletconnect/relay-api": "1.0.11", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.14.0-canary.5", + "@walletconnect/types": "2.15.0", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", @@ -34199,13 +34199,13 @@ "requires": { "@ethersproject/wallet": "5.7.0", "@walletconnect/auth-client": "2.1.2", - "@walletconnect/core": "2.14.0-canary.5", + "@walletconnect/core": "2.15.0", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.14.0-canary.5", - "@walletconnect/types": "2.14.0-canary.5", - "@walletconnect/utils": "2.14.0-canary.5" + "@walletconnect/sign-client": "2.15.0", + "@walletconnect/types": "2.15.0", + "@walletconnect/utils": "2.15.0" } }, "@walletconnect/window-getters": { diff --git a/packages/core/package.json b/packages/core/package.json index c5f0f4135..500a05a0e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/core", "description": "Core for WalletConnect Protocol", - "version": "2.14.0", + "version": "2.15.0", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", @@ -42,8 +42,8 @@ "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.14.0", - "@walletconnect/utils": "2.14.0", + "@walletconnect/types": "2.15.0", + "@walletconnect/utils": "2.15.0", "events": "3.3.0", "lodash.isequal": "4.5.0", "uint8arrays": "3.1.0" diff --git a/packages/core/src/constants/relayer.ts b/packages/core/src/constants/relayer.ts index 34ebabc97..cfb2e24aa 100644 --- a/packages/core/src/constants/relayer.ts +++ b/packages/core/src/constants/relayer.ts @@ -34,7 +34,7 @@ export const RELAYER_STORAGE_OPTIONS = { // Updated automatically via `new-version` npm script. -export const RELAYER_SDK_VERSION = "2.14.0"; +export const RELAYER_SDK_VERSION = "2.15.0"; // delay to wait before closing the transport connection after init if not active export const RELAYER_TRANSPORT_CUTOFF = 10_000; diff --git a/packages/react-native-compat/package.json b/packages/react-native-compat/package.json index 4e1122ba8..2efd8d9ae 100644 --- a/packages/react-native-compat/package.json +++ b/packages/react-native-compat/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/react-native-compat", "description": "Shims for WalletConnect Protocol in React Native Projects", - "version": "2.14.0", + "version": "2.15.0", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", diff --git a/packages/sign-client/package.json b/packages/sign-client/package.json index 91cf2c0db..c4e536aae 100644 --- a/packages/sign-client/package.json +++ b/packages/sign-client/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/sign-client", "description": "Sign Client for WalletConnect Protocol", - "version": "2.14.0", + "version": "2.15.0", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", @@ -38,14 +38,14 @@ "prettier": "prettier --check '{src,test}/**/*.{js,ts,jsx,tsx}'" }, "dependencies": { - "@walletconnect/core": "2.14.0", + "@walletconnect/core": "2.15.0", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.14.0", - "@walletconnect/utils": "2.14.0", + "@walletconnect/types": "2.15.0", + "@walletconnect/utils": "2.15.0", "events": "3.3.0" }, "devDependencies": { diff --git a/packages/types/package.json b/packages/types/package.json index 54ed54b9e..0f43ef6bf 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/types", "description": "Typings for WalletConnect Protocol", - "version": "2.14.0", + "version": "2.15.0", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", diff --git a/packages/utils/package.json b/packages/utils/package.json index 5f9133c5f..dd9999c69 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/utils", "description": "Utilities for WalletConnect Protocol", - "version": "2.14.0", + "version": "2.15.0", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", @@ -39,7 +39,7 @@ "@walletconnect/relay-api": "1.0.11", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.14.0", + "@walletconnect/types": "2.15.0", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", diff --git a/packages/web3wallet/package.json b/packages/web3wallet/package.json index bb4551e8a..2af9b271a 100644 --- a/packages/web3wallet/package.json +++ b/packages/web3wallet/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/web3wallet", "description": "Web3Wallet for WalletConnect Protocol", - "version": "1.13.0", + "version": "1.14.0", "private": true, "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", @@ -30,13 +30,13 @@ }, "dependencies": { "@walletconnect/auth-client": "2.1.2", - "@walletconnect/core": "2.14.0", + "@walletconnect/core": "2.15.0", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.14.0", - "@walletconnect/types": "2.14.0", - "@walletconnect/utils": "2.14.0" + "@walletconnect/sign-client": "2.15.0", + "@walletconnect/types": "2.15.0", + "@walletconnect/utils": "2.15.0" }, "devDependencies": { "@ethersproject/wallet": "5.7.0" diff --git a/providers/ethereum-provider/package.json b/providers/ethereum-provider/package.json index dd5661e43..c8ad55292 100644 --- a/providers/ethereum-provider/package.json +++ b/providers/ethereum-provider/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/ethereum-provider", "description": "Ethereum Provider for WalletConnect Protocol", - "version": "2.14.0", + "version": "2.15.0", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "repository": { @@ -48,10 +48,10 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/modal": "2.6.2", - "@walletconnect/sign-client": "2.14.0", - "@walletconnect/types": "2.14.0", - "@walletconnect/universal-provider": "2.14.0", - "@walletconnect/utils": "2.14.0", + "@walletconnect/sign-client": "2.15.0", + "@walletconnect/types": "2.15.0", + "@walletconnect/universal-provider": "2.15.0", + "@walletconnect/utils": "2.15.0", "events": "3.3.0" }, "devDependencies": { diff --git a/providers/signer-connection/package.json b/providers/signer-connection/package.json index 3896ca6bd..a84b2b1f4 100644 --- a/providers/signer-connection/package.json +++ b/providers/signer-connection/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/signer-connection", "description": "Signer Connection for WalletConnect Protocol", - "version": "2.14.0", + "version": "2.15.0", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", @@ -39,9 +39,9 @@ "dependencies": { "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/sign-client": "2.14.0", - "@walletconnect/types": "2.14.0", - "@walletconnect/utils": "2.14.0", + "@walletconnect/sign-client": "2.15.0", + "@walletconnect/types": "2.15.0", + "@walletconnect/utils": "2.15.0", "events": "3.3.0", "uint8arrays": "3.1.0" } diff --git a/providers/universal-provider/package.json b/providers/universal-provider/package.json index 6b89bc23a..fd7e22dcf 100644 --- a/providers/universal-provider/package.json +++ b/providers/universal-provider/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/universal-provider", "description": "Universal Provider for WalletConnect Protocol", - "version": "2.14.0", + "version": "2.15.0", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "repository": { @@ -45,9 +45,9 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.14.0", - "@walletconnect/types": "2.14.0", - "@walletconnect/utils": "2.14.0", + "@walletconnect/sign-client": "2.15.0", + "@walletconnect/types": "2.15.0", + "@walletconnect/utils": "2.15.0", "events": "3.3.0" }, "devDependencies": { From f3a836a938c4ecb608168b3b37d35853f9bec34c Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Mon, 19 Aug 2024 18:31:35 -0400 Subject: [PATCH 38/61] fix: Verify V3 --- packages/core/src/constants/verify.ts | 2 +- packages/core/src/controllers/verify.ts | 28 +++++++------------------ 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/packages/core/src/constants/verify.ts b/packages/core/src/constants/verify.ts index 77049537a..1c94a1c35 100644 --- a/packages/core/src/constants/verify.ts +++ b/packages/core/src/constants/verify.ts @@ -3,6 +3,6 @@ export const VERIFY_CONTEXT = "verify-api"; const VERIFY_SERVER_COM = "https://verify.walletconnect.com"; const VERIFY_SERVER_ORG = "https://verify.walletconnect.org"; export const VERIFY_SERVER = VERIFY_SERVER_ORG; -export const VERIFY_SERVER_V2 = `${VERIFY_SERVER}/v2`; +export const VERIFY_SERVER_V3 = `${VERIFY_SERVER}/v3`; export const TRUSTED_VERIFY_URLS = [VERIFY_SERVER_COM, VERIFY_SERVER_ORG]; diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index 925a47395..a83b80a0b 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -10,7 +10,7 @@ import { TRUSTED_VERIFY_URLS, VERIFY_CONTEXT, VERIFY_SERVER, - VERIFY_SERVER_V2, + VERIFY_SERVER_V3, } from "../constants"; import { IKeyValueStorage } from "@walletconnect/keyvaluestorage"; @@ -29,7 +29,7 @@ export class Verify extends IVerify { public name = VERIFY_CONTEXT; private abortController: AbortController; private isDevEnv; - private verifyUrlV2 = VERIFY_SERVER_V2; + private verifyUrlV3 = VERIFY_SERVER_V3; private storagePrefix = CORE_STORAGE_PREFIX; private version = CORE_VERSION; private publicKey?: Jwk; @@ -62,24 +62,12 @@ export class Verify extends IVerify { public register: IVerify["register"] = async (params) => { if (!isBrowser()) return; + const origin = window.location.origin; const { id, decryptedId } = params; - const url = `${this.verifyUrlV2}/attestation?projectId=${this.core.projectId}`; - let src = ""; - try { - const response = await fetch(url, { - method: "POST", - body: JSON.stringify({ id, decryptedId }), - }); - const { srcdoc } = await response.json(); - src = srcdoc; - this.logger.debug("srcdoc fetched", src); - } catch (e) { - this.logger.warn(e); - return; - } + const src = `${this.verifyUrlV3}/attestation?projectId=${this.core.projectId}&origin=${origin}&id=${id}&decryptedId=${decryptedId}`; try { const document = getDocument() as Document; - const abortTimeout = this.startAbortTimer(ONE_SECOND * 3); + const abortTimeout = this.startAbortTimer(ONE_SECOND * 5); const attestationJwt = await new Promise((resolve) => { const abortListener = () => { window.removeEventListener("message", listener); @@ -90,7 +78,7 @@ export class Verify extends IVerify { signal: this.abortController.signal, }); const iframe = document.createElement("iframe"); - iframe.srcdoc = src; + iframe.src = src; iframe.style.display = "none"; const listener = (event: MessageEvent) => { if (!event.data) return; @@ -165,9 +153,9 @@ export class Verify extends IVerify { private fetchPublicKey = async () => { try { - this.logger.debug(`fetching public key from: ${this.verifyUrlV2}`); + this.logger.debug(`fetching public key from: ${this.verifyUrlV3}`); const timeout = this.startAbortTimer(FIVE_SECONDS); - const result = await fetch(`${this.verifyUrlV2}/public-key`, { + const result = await fetch(`${this.verifyUrlV3}/public-key`, { signal: this.abortController.signal, }); clearTimeout(timeout); From 328cdee75cf6931a760ffa18044dbdf1991b3068 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Tue, 20 Aug 2024 16:22:21 +0300 Subject: [PATCH 39/61] chore: reverts verify v2 --- package-lock.json | 137 -------- package.json | 1 - packages/core/src/constants/verify.ts | 1 - packages/core/src/controllers/verify.ts | 309 ++++++------------ packages/core/src/core.ts | 2 +- packages/sign-client/src/client.ts | 1 + .../sign-client/src/controllers/engine.ts | 66 ++-- packages/types/src/core/verify.ts | 14 +- packages/utils/package.json | 2 - packages/utils/src/crypto.ts | 66 ---- packages/utils/test/crypto.spec.ts | 90 ----- rollup.config.js | 2 - 12 files changed, 138 insertions(+), 553 deletions(-) diff --git a/package-lock.json b/package-lock.json index 30eeabeff..267fb2489 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,6 @@ ], "devDependencies": { "@rollup/plugin-commonjs": "22.0.2", - "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "13.3.0", "@types/node": "18.7.3", "@types/sinon": "10.0.13", @@ -6373,54 +6372,6 @@ "rollup": "^2.68.0" } }, - "node_modules/@rollup/plugin-json": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", - "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^5.1.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-json/node_modules/@rollup/pluginutils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", - "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-json/node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, "node_modules/@rollup/plugin-node-resolve": { "version": "13.3.0", "dev": true, @@ -7470,15 +7421,6 @@ "@types/chai": "*" } }, - "node_modules/@types/elliptic": { - "version": "6.4.18", - "resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.18.tgz", - "integrity": "sha512-UseG6H5vjRiNpQvrhy4VF/JXdA3V/Fp5amvveaL+fs28BZ6xIKJBPnUPRlEaZpysD9MbpfaLi8lbl7PGUAkpWw==", - "dev": true, - "dependencies": { - "@types/bn.js": "*" - } - }, "node_modules/@types/estree": { "version": "0.0.39", "dev": true, @@ -27566,34 +27508,13 @@ "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", - "elliptic": "^6.5.7", "query-string": "7.1.3", "uint8arrays": "3.1.0" }, "devDependencies": { - "@types/elliptic": "^6.4.18", "@types/lodash.isequal": "4.5.6" } }, - "packages/utils/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "packages/utils/node_modules/elliptic": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", - "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "packages/utils/node_modules/uint8arrays": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.0.tgz", @@ -32516,34 +32437,6 @@ "resolve": "^1.17.0" } }, - "@rollup/plugin-json": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", - "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^5.1.0" - }, - "dependencies": { - "@rollup/pluginutils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", - "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", - "dev": true, - "requires": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - } - }, - "@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - } - } - }, "@rollup/plugin-node-resolve": { "version": "13.3.0", "dev": true, @@ -33395,15 +33288,6 @@ "@types/chai": "*" } }, - "@types/elliptic": { - "version": "6.4.18", - "resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.18.tgz", - "integrity": "sha512-UseG6H5vjRiNpQvrhy4VF/JXdA3V/Fp5amvveaL+fs28BZ6xIKJBPnUPRlEaZpysD9MbpfaLi8lbl7PGUAkpWw==", - "dev": true, - "requires": { - "@types/bn.js": "*" - } - }, "@types/estree": { "version": "0.0.39", "dev": true @@ -34151,7 +34035,6 @@ "@stablelib/random": "1.0.2", "@stablelib/sha256": "1.0.1", "@stablelib/x25519": "1.0.3", - "@types/elliptic": "^6.4.18", "@types/lodash.isequal": "4.5.6", "@walletconnect/relay-api": "1.0.11", "@walletconnect/safe-json": "1.0.2", @@ -34160,30 +34043,10 @@ "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", - "elliptic": "^6.5.7", "query-string": "7.1.3", "uint8arrays": "3.1.0" }, "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "elliptic": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", - "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "uint8arrays": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.0.tgz", diff --git a/package.json b/package.json index 658f4d83e..0d53dd87c 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,6 @@ }, "devDependencies": { "@rollup/plugin-commonjs": "22.0.2", - "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "13.3.0", "@types/node": "18.7.3", "@types/sinon": "10.0.13", diff --git a/packages/core/src/constants/verify.ts b/packages/core/src/constants/verify.ts index 77049537a..479fe969d 100644 --- a/packages/core/src/constants/verify.ts +++ b/packages/core/src/constants/verify.ts @@ -3,6 +3,5 @@ export const VERIFY_CONTEXT = "verify-api"; const VERIFY_SERVER_COM = "https://verify.walletconnect.com"; const VERIFY_SERVER_ORG = "https://verify.walletconnect.org"; export const VERIFY_SERVER = VERIFY_SERVER_ORG; -export const VERIFY_SERVER_V2 = `${VERIFY_SERVER}/v2`; export const TRUSTED_VERIFY_URLS = [VERIFY_SERVER_COM, VERIFY_SERVER_ORG]; diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index 925a47395..a710b0e2b 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -1,135 +1,68 @@ import { generateChildLogger, getLoggerContext, Logger } from "@walletconnect/logger"; -import { ICore, IVerify } from "@walletconnect/types"; -import { isBrowser, isNode, P256KeyDataType, verifyP256Jwt } from "@walletconnect/utils"; +import { IVerify } from "@walletconnect/types"; +import { isBrowser, isNode, isReactNative } from "@walletconnect/utils"; import { FIVE_SECONDS, ONE_SECOND, toMiliseconds } from "@walletconnect/time"; -import { getDocument } from "@walletconnect/window-getters"; - -import { - CORE_STORAGE_PREFIX, - CORE_VERSION, - TRUSTED_VERIFY_URLS, - VERIFY_CONTEXT, - VERIFY_SERVER, - VERIFY_SERVER_V2, -} from "../constants"; -import { IKeyValueStorage } from "@walletconnect/keyvaluestorage"; - -type Jwk = { - publicKey: P256KeyDataType; - expiresAt: number; -}; -type JwkPayload = { - exp: number; - id: string; - origin: string; - isScam: boolean; - isVerified: boolean; -}; + +import { TRUSTED_VERIFY_URLS, VERIFY_CONTEXT, VERIFY_SERVER } from "../constants"; + export class Verify extends IVerify { public name = VERIFY_CONTEXT; + private verifyUrl: string; + private iframe?: HTMLIFrameElement; + private initialized = false; private abortController: AbortController; private isDevEnv; - private verifyUrlV2 = VERIFY_SERVER_V2; - private storagePrefix = CORE_STORAGE_PREFIX; - private version = CORE_VERSION; - private publicKey?: Jwk; - private fetchPromise?: Promise; - - constructor(public core: ICore, public logger: Logger, public store: IKeyValueStorage) { - super(core, logger, store); + // the queue is only used during the loading phase of the iframe to ensure all attestations are posted + private queue: string[] = []; + // flag to disable verify when the iframe fails to load on main & fallback urls. + // this means Verify API is not enabled for the current projectId and there's no point in trying to initialize it again. + private verifyDisabled = false; + + constructor(public projectId: string, public logger: Logger) { + super(projectId, logger); this.logger = generateChildLogger(logger, this.name); + this.verifyUrl = VERIFY_SERVER; this.abortController = new AbortController(); this.isDevEnv = isNode() && process.env.IS_VITEST; - this.init(); } - get storeKey(): string { - return ( - this.storagePrefix + this.version + this.core.customStoragePrefix + "//" + `verify:public:key` - ); - } + public init: IVerify["init"] = async (params) => { + if (this.verifyDisabled) return; + + // ignore on non browser environments + if (isReactNative() || !isBrowser()) return; - public init = async () => { - this.publicKey = await this.store.getItem(this.storeKey); - if (this.publicKey && toMiliseconds(this.publicKey?.expiresAt) < Date.now()) { - this.logger.debug("verify v2 public key expired"); - await this.removePublicKey(); + const verifyUrl = this.getVerifyUrl(params?.verifyUrl); + // if init is called again with a different url, remove the iframe and start over + if (this.verifyUrl !== verifyUrl) { + this.removeIframe(); } - if (!this.publicKey) { - await this.fetchAndPersistPublicKey(); + this.verifyUrl = verifyUrl; + + try { + await this.createIframe(); + } catch (error) { + this.logger.info(`Verify iframe failed to load: ${this.verifyUrl}`); + this.logger.info(error); + // if the iframe fails to load, disable verify + this.verifyDisabled = true; } }; public register: IVerify["register"] = async (params) => { - if (!isBrowser()) return; - const { id, decryptedId } = params; - const url = `${this.verifyUrlV2}/attestation?projectId=${this.core.projectId}`; - let src = ""; - try { - const response = await fetch(url, { - method: "POST", - body: JSON.stringify({ id, decryptedId }), - }); - const { srcdoc } = await response.json(); - src = srcdoc; - this.logger.debug("srcdoc fetched", src); - } catch (e) { - this.logger.warn(e); - return; + if (!this.initialized) { + this.addToQueue(params.attestationId); + await this.init(); + } else { + this.sendPost(params.attestationId); } - try { - const document = getDocument() as Document; - const abortTimeout = this.startAbortTimer(ONE_SECOND * 3); - const attestationJwt = await new Promise((resolve) => { - const abortListener = () => { - window.removeEventListener("message", listener); - document.body.removeChild(iframe); - throw new Error("attestation aborted"); - }; - this.abortController.signal.addEventListener("abort", abortListener, { - signal: this.abortController.signal, - }); - const iframe = document.createElement("iframe"); - iframe.srcdoc = src; - iframe.style.display = "none"; - const listener = (event: MessageEvent) => { - if (!event.data) return; - const data = JSON.parse(event.data); - if (data.type === "verify_attestation") { - clearInterval(abortTimeout); - document.body.removeChild(iframe); - this.abortController.signal.removeEventListener("abort", abortListener); - window.removeEventListener("message", listener); - resolve(data.attestation === null ? "" : data.attestation); - } - }; - document.body.appendChild(iframe); - window.addEventListener("message", listener, { signal: this.abortController.signal }); - }); - this.logger.debug("jwt attestation", attestationJwt); - return attestationJwt as string; - } catch (e) { - this.logger.warn(e); - } - return ""; }; public resolve: IVerify["resolve"] = async (params) => { if (this.isDevEnv) return ""; - const { attestationId, hash } = params; - if (attestationId === "") { - this.logger.debug("resolve: attestationId is empty, skipping"); - return; - } - - if (attestationId) { - const validation = await this.isValidJwtAttestation(attestationId); - if (validation) return validation; - } - if (!hash) return; const verifyUrl = this.getVerifyUrl(params?.verifyUrl); - return this.fetchAttestation(hash, verifyUrl); + return this.fetchAttestation(params.attestationId, verifyUrl); }; get context(): string { @@ -137,119 +70,95 @@ export class Verify extends IVerify { } private fetchAttestation = async (attestationId: string, url: string) => { - this.logger.debug(`resolving attestation: ${attestationId} from url: ${url}`); + this.logger.info(`resolving attestation: ${attestationId} from url: ${url}`); // set artificial timeout to prevent hanging const timeout = this.startAbortTimer(ONE_SECOND * 5); - const result = await fetch(`${url}/attestation/${attestationId}?v2Supported=true`, { + const result = await fetch(`${url}/attestation/${attestationId}`, { signal: this.abortController.signal, }); clearTimeout(timeout); return result.status === 200 ? await result.json() : undefined; }; - private startAbortTimer(timer: number) { - this.abortController = new AbortController(); - return setTimeout(() => this.abortController.abort(), toMiliseconds(timer)); - } - - private getVerifyUrl = (verifyUrl?: string) => { - let url = verifyUrl || VERIFY_SERVER; - if (!TRUSTED_VERIFY_URLS.includes(url)) { - this.logger.info( - `verify url: ${url}, not included in trusted list, assigning default: ${VERIFY_SERVER}`, - ); - url = VERIFY_SERVER; - } - return url; + private addToQueue = (attestationId: string) => { + this.queue.push(attestationId); }; - private fetchPublicKey = async () => { - try { - this.logger.debug(`fetching public key from: ${this.verifyUrlV2}`); - const timeout = this.startAbortTimer(FIVE_SECONDS); - const result = await fetch(`${this.verifyUrlV2}/public-key`, { - signal: this.abortController.signal, - }); - clearTimeout(timeout); - return (await result.json()) as Jwk; - } catch (e) { - this.logger.warn(e); - } - return undefined; + private processQueue = () => { + if (this.queue.length === 0) return; + this.queue.forEach((attestationId) => this.sendPost(attestationId)); + this.queue = []; }; - private persistPublicKey = async (publicKey: Jwk) => { - this.logger.debug(`persisting public key to local storage`, publicKey); - await this.store.setItem(this.storeKey, publicKey); - this.publicKey = publicKey; - }; - - private removePublicKey = async () => { - this.logger.debug(`removing verify v2 public key from storage`); - await this.store.removeItem(this.storeKey); - this.publicKey = undefined; + private sendPost = (attestationId: string) => { + try { + if (!this.iframe) return; + this.iframe.contentWindow?.postMessage(attestationId, "*"); // setting targetOrigin to "*" fixes the `Failed to execute 'postMessage' on 'DOMWindow': The target origin provided...` while the iframe is still loading + this.logger.info(`postMessage sent: ${attestationId} ${this.verifyUrl}`); + } catch (e) {} }; - private isValidJwtAttestation = async (attestation: string) => { - const key = await this.getPublicKey(); - try { - if (key) { - const validation = this.validateAttestation(attestation, key); - return validation; + private createIframe = async () => { + let iframeOnLoadResolve: () => void; + const onMessage = (event: MessageEvent) => { + if (event.data === "verify_ready") { + this.onInit(); + window.removeEventListener("message", onMessage); + iframeOnLoadResolve(); } - } catch (e) { - this.logger.error(e); - this.logger.warn("error validating attestation"); - } - const newKey = await this.fetchAndPersistPublicKey(); - try { - if (newKey) { - const validation = this.validateAttestation(attestation, newKey); - return validation; - } - } catch (e) { - this.logger.error(e); - this.logger.warn("error validating attestation"); - } - return undefined; + }; + await Promise.race([ + new Promise((resolve) => { + const existingIframe = document.getElementById(VERIFY_CONTEXT); + if (existingIframe) { + this.iframe = existingIframe as HTMLIFrameElement; + this.onInit(); + return resolve(); + } + + window.addEventListener("message", onMessage); + const iframe = document.createElement("iframe"); + iframe.id = VERIFY_CONTEXT; + iframe.src = `${this.verifyUrl}/${this.projectId}`; + iframe.style.display = "none"; + document.body.append(iframe); + this.iframe = iframe; + iframeOnLoadResolve = resolve; + }), + new Promise((_, reject) => + setTimeout(() => { + window.removeEventListener("message", onMessage); + reject("verify iframe load timeout"); + }, toMiliseconds(FIVE_SECONDS)), + ), + ]); }; - private getPublicKey = async () => { - if (this.publicKey) return this.publicKey; - return await this.fetchAndPersistPublicKey(); + private onInit = () => { + this.initialized = true; + this.processQueue(); }; - private fetchAndPersistPublicKey = async () => { - if (this.fetchPromise) { - await this.fetchPromise; - return this.publicKey; - } - this.fetchPromise = new Promise(async (resolve) => { - const key = await this.fetchPublicKey(); - if (!key) return; - await this.persistPublicKey(key); - resolve(key); - }); - const key = await this.fetchPromise; - this.fetchPromise = undefined; - return key; - }; + private startAbortTimer(timer: number) { + this.abortController = new AbortController(); + return setTimeout(() => this.abortController.abort(), toMiliseconds(timer)); + } - private validateAttestation = (attestation: string, key: Jwk) => { - const result = verifyP256Jwt(attestation, key.publicKey); - const validation = { - hasExpired: toMiliseconds(result.exp) < Date.now(), - payload: result, - }; + private removeIframe = () => { + if (!this.iframe) return; + this.iframe.remove(); + this.iframe = undefined; + this.initialized = false; + }; - if (validation.hasExpired) { - this.logger.warn("resolve: jwt attestation expired"); - throw new Error("JWT attestation expired"); + private getVerifyUrl = (verifyUrl?: string) => { + let url = verifyUrl || VERIFY_SERVER; + if (!TRUSTED_VERIFY_URLS.includes(url)) { + this.logger.info( + `verify url: ${url}, not included in trusted list, assigning default: ${VERIFY_SERVER}`, + ); + url = VERIFY_SERVER; } - - return { - origin: validation.payload.origin, - isScam: validation.payload.isScam, - }; + return url; }; } diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 1afc9cffa..a676a7c75 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -108,7 +108,7 @@ export class Core extends ICore { projectId: this.projectId, }); this.pairing = new Pairing(this, this.logger); - this.verify = new Verify(this, this.logger, this.storage); + this.verify = new Verify(this.projectId || "", this.logger); this.echoClient = new EchoClient(this.projectId || "", this.logger); } diff --git a/packages/sign-client/src/client.ts b/packages/sign-client/src/client.ts index fb6427f6a..02f46dda7 100644 --- a/packages/sign-client/src/client.ts +++ b/packages/sign-client/src/client.ts @@ -251,6 +251,7 @@ export class SignClient extends ISignClient { await this.pendingRequest.init(); await this.engine.init(); await this.auth.init(); + this.core.verify.init({ verifyUrl: this.metadata.verifyUrl }); this.logger.info(`SignClient Initialization Success`); this.engine.processRelayMessageCache(); } catch (error: any) { diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index 862304f3e..3974ffb6a 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -81,6 +81,7 @@ import { validateSignedCacao, getNamespacedDidChainId, parseChainId, + isBrowser, } from "@walletconnect/utils"; import EventEmmiter from "events"; import { @@ -1180,7 +1181,10 @@ export class Engine extends IEngine { private sendRequest: EnginePrivate["sendRequest"] = async (args) => { const { topic, method, params, expiry, relayRpcId, clientRpcId, throwOnFailedPublish } = args; const payload = formatJsonRpcRequest(method, params, clientRpcId); - + if (isBrowser() && METHODS_TO_VERIFY.includes(method)) { + const hash = hashMessage(JSON.stringify(payload)); + this.client.core.verify.register({ attestationId: hash }); + } let message; try { message = await this.client.core.crypto.encode(topic, payload); @@ -1190,14 +1194,7 @@ export class Engine extends IEngine { throw error; } - let attestation: string | undefined; - if (METHODS_TO_VERIFY.includes(method)) { - const decryptedId = hashMessage(JSON.stringify(payload)); - const id = hashMessage(message); - attestation = await this.client.core.verify.register({ id, decryptedId }); - } const opts = ENGINE_RPC_OPTS[method].req; - opts.attestation = attestation; if (expiry) opts.ttl = expiry; if (relayRpcId) opts.id = relayRpcId; this.client.core.history.set(topic, payload); @@ -1370,7 +1367,7 @@ export class Engine extends IEngine { }; private processRequest: EnginePrivate["onRelayEventRequest"] = async (event) => { - const { topic, payload, attestation } = event; + const { topic, payload } = event; const reqMethod = payload.method as JsonRpcTypes.WcMethod; if (this.shouldIgnorePairingRequest({ topic, requestMethod: reqMethod })) { @@ -1379,7 +1376,7 @@ export class Engine extends IEngine { switch (reqMethod) { case "wc_sessionPropose": - return await this.onSessionProposeRequest(topic, payload, attestation); + return await this.onSessionProposeRequest(topic, payload); case "wc_sessionSettle": return await this.onSessionSettleRequest(topic, payload); case "wc_sessionUpdate": @@ -1391,11 +1388,11 @@ export class Engine extends IEngine { case "wc_sessionDelete": return await this.onSessionDeleteRequest(topic, payload); case "wc_sessionRequest": - return await this.onSessionRequest(topic, payload, attestation); + return await this.onSessionRequest(topic, payload); case "wc_sessionEvent": return await this.onSessionEventRequest(topic, payload); case "wc_sessionAuthenticate": - return await this.onSessionAuthenticateRequest(topic, payload, attestation); + return await this.onSessionAuthenticateRequest(topic, payload); default: return this.client.logger.info(`Unsupported request method ${reqMethod}`); } @@ -1458,7 +1455,6 @@ export class Engine extends IEngine { private onSessionProposeRequest: EnginePrivate["onSessionProposeRequest"] = async ( topic, payload, - attestation, ) => { const { params, id } = payload; try { @@ -1467,11 +1463,8 @@ export class Engine extends IEngine { params.expiryTimestamp || calcExpiry(ENGINE_RPC_OPTS.wc_sessionPropose.req.ttl); const proposal = { id, pairingTopic: topic, expiryTimestamp, ...params }; await this.setProposal(id, proposal); - const verifyContext = await this.getVerifyContext({ - attestationId: attestation, - hash: hashMessage(JSON.stringify(payload)), - metadata: proposal.proposer.metadata, - }); + const hash = hashMessage(JSON.stringify(payload)); + const verifyContext = await this.getVerifyContext(hash, proposal.proposer.metadata); this.client.events.emit("session_proposal", { id, params: proposal, verifyContext }); } catch (err: any) { await this.sendError({ @@ -1766,20 +1759,15 @@ export class Engine extends IEngine { } }; - private onSessionRequest: EnginePrivate["onSessionRequest"] = async ( - topic, - payload, - attestation, - ) => { + private onSessionRequest: EnginePrivate["onSessionRequest"] = async (topic, payload) => { const { id, params } = payload; try { await this.isValidRequest({ topic, ...params }); + const hash = hashMessage( + JSON.stringify(formatJsonRpcRequest("wc_sessionRequest", params, id)), + ); const session = this.client.session.get(topic); - const verifyContext = await this.getVerifyContext({ - attestationId: attestation, - hash: hashMessage(JSON.stringify(formatJsonRpcRequest("wc_sessionRequest", params, id))), - metadata: session.peer.metadata, - }); + const verifyContext = await this.getVerifyContext(hash, session.peer.metadata); const request = { id, topic, @@ -1871,15 +1859,11 @@ export class Engine extends IEngine { private onSessionAuthenticateRequest: EnginePrivate["onSessionAuthenticateRequest"] = async ( topic, payload, - attestation, ) => { try { const { requester, authPayload, expiryTimestamp } = payload.params; - const verifyContext = await this.getVerifyContext({ - attestationId: attestation, - hash: hashMessage(JSON.stringify(payload)), - metadata: this.client.metadata, - }); + const hash = hashMessage(JSON.stringify(payload)); + const verifyContext = await this.getVerifyContext(hash, this.client.metadata); const pendingRequest = { requester, pairingTopic: topic, @@ -2425,12 +2409,7 @@ export class Engine extends IEngine { } }; - private getVerifyContext = async (params: { - attestationId?: string; - hash?: string; - metadata: CoreTypes.Metadata; - }) => { - const { attestationId, hash, metadata } = params; + private getVerifyContext = async (hash: string, metadata: CoreTypes.Metadata) => { const context: Verify.Context = { verified: { verifyUrl: metadata.verifyUrl || VERIFY_SERVER, @@ -2441,8 +2420,7 @@ export class Engine extends IEngine { try { const result = await this.client.core.verify.resolve({ - attestationId, - hash, + attestationId: hash, verifyUrl: metadata.verifyUrl, }); if (result) { @@ -2452,10 +2430,10 @@ export class Engine extends IEngine { result.origin === new URL(metadata.url).origin ? "VALID" : "INVALID"; } } catch (e) { - this.client.logger.warn(e); + this.client.logger.info(e); } - this.client.logger.debug(`Verify context: ${JSON.stringify(context)}`); + this.client.logger.info(`Verify context: ${JSON.stringify(context)}`); return context; }; diff --git a/packages/types/src/core/verify.ts b/packages/types/src/core/verify.ts index ca13f5380..55a0327de 100644 --- a/packages/types/src/core/verify.ts +++ b/packages/types/src/core/verify.ts @@ -1,6 +1,4 @@ import { Logger } from "@walletconnect/logger"; -import { IKeyValueStorage } from "@walletconnect/keyvaluestorage"; -import { ICore } from "./core"; export declare namespace Verify { export interface Context { @@ -16,16 +14,14 @@ export declare namespace Verify { export abstract class IVerify { public abstract readonly context: string; - constructor(public core: ICore, public logger: Logger, public store: IKeyValueStorage) {} + constructor(public projectId: string, public logger: Logger) {} - public abstract register(params: { - id: string; - decryptedId: string; - }): Promise; + public abstract init(params?: { verifyUrl?: string }): Promise; + + public abstract register(params: { attestationId: string }): Promise; public abstract resolve(params: { - attestationId?: string; - hash?: string; + attestationId: string; verifyUrl?: string; }): Promise<{ origin: string; isScam?: boolean }>; } diff --git a/packages/utils/package.json b/packages/utils/package.json index dd9999c69..0b141dcca 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -43,12 +43,10 @@ "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", - "elliptic": "^6.5.7", "query-string": "7.1.3", "uint8arrays": "3.1.0" }, "devDependencies": { - "@types/elliptic": "^6.4.18", "@types/lodash.isequal": "4.5.6" } } diff --git a/packages/utils/src/crypto.ts b/packages/utils/src/crypto.ts index 2a5a00098..5c1464f0c 100644 --- a/packages/utils/src/crypto.ts +++ b/packages/utils/src/crypto.ts @@ -5,8 +5,6 @@ import { hash, SHA256 } from "@stablelib/sha256"; import * as x25519 from "@stablelib/x25519"; import { CryptoTypes } from "@walletconnect/types"; import { concat, fromString, toString } from "uint8arrays"; -import { ec as EC } from "elliptic"; -import { decodeJWT } from "@walletconnect/relay-auth"; export const BASE10 = "base10"; export const BASE16 = "base16"; @@ -171,67 +169,3 @@ export function isTypeOneEnvelope( typeof result.receiverPublicKey === "string" ); } - -export function getCryptoKeyFromKeyData(keyData: P256KeyDataType): EC.KeyPair { - const ec = new EC("p256"); - const key = ec.keyFromPublic( - { - x: Buffer.from(keyData.x, "base64").toString("hex"), - y: Buffer.from(keyData.y, "base64").toString("hex"), - }, - "hex", - ); - return key; -} - -function base64UrlToBase64(base64Url: string) { - let base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); - const padding = base64.length % 4; - if (padding > 0) { - base64 += "=".repeat(4 - padding); - } - return base64; -} - -function base64UrlDecode(base64Url: string) { - return Buffer.from(base64UrlToBase64(base64Url), "base64"); -} - -export function verifyP256Jwt(token: string, keyData: P256KeyDataType) { - const [headerBase64Url, payloadBase64Url, signatureBase64Url] = token.split("."); - - // Decode the signature - const signatureBuffer = base64UrlDecode(signatureBase64Url); - - // Check if signature length is correct (64 bytes for P-256) - if (signatureBuffer.length !== 64) { - throw new Error("Invalid signature length"); - } - - // Extract r and s from the signature - const r = signatureBuffer.slice(0, 32).toString("hex"); - const s = signatureBuffer.slice(32, 64).toString("hex"); - - // Create the signing input - const signingInput = `${headerBase64Url}.${payloadBase64Url}`; - - const sha256 = new SHA256(); - const buffer = sha256.update(Buffer.from(signingInput)).digest(); - - const key = getCryptoKeyFromKeyData(keyData); - - // Convert the hash to hex format - const hashHex = Buffer.from(buffer).toString("hex"); - - // Verify the signature - const isValid = key.verify(hashHex, { r, s }); - - if (!isValid) { - throw new Error("Invalid signature"); - } - const data = decodeJWT(token) as unknown as { payload: T }; - return { - ...data.payload, - isVerified: isValid, - }; -} diff --git a/packages/utils/test/crypto.spec.ts b/packages/utils/test/crypto.spec.ts index 8b7efeed4..de2e1167a 100644 --- a/packages/utils/test/crypto.spec.ts +++ b/packages/utils/test/crypto.spec.ts @@ -1,9 +1,6 @@ import { expect, describe, it } from "vitest"; import { toString } from "uint8arrays"; import { safeJsonStringify } from "@walletconnect/safe-json"; -import { SHA256 } from "@stablelib/sha256"; -import { Buffer } from "buffer"; -import elliptic from "elliptic"; import { BASE16, @@ -17,9 +14,6 @@ import { validateDecoding, isTypeOneEnvelope, generateRandomBytes32, - verifyP256Jwt, - getCryptoKeyFromKeyData, - P256KeyDataType, } from "../src"; import { TEST_KEY_PAIRS, TEST_SHARED_KEY, TEST_HASHED_KEY, TEST_SYM_KEY } from "./shared"; @@ -114,88 +108,4 @@ describe("Crypto", () => { it("calls generateRandomBytes32", () => { expect(generateRandomBytes32()).toBeTruthy(); }); - it("should validate verify v2 jwt", async () => { - const token = - "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjM2MzI1MDQsImlkIjoiMDkxN2YzMzk0YTdmMzkyZTg3ZTM1ZjM4OTg2OWU2NDEzZjkyNTBlMGIxZTE4YjUzMDhkNzBhM2VjOTJjZDQ3OCIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImlzU2NhbSI6bnVsbCwiaXNWZXJpZmllZCI6ZmFsc2V9.RehA28c0Ae8D_ixvGS8uG9J9eTJtpGfaC_7kNE9ZNAVFREWBY6Dl_SXc0_E0RSvYkHpupfmXlmjenuDqNcyoeg"; - - const publicKey = { - publicKey: { - crv: "P-256", - ext: true, - key_ops: ["verify"], - kty: "EC", - x: "CbL4DOYOb1ntd-8OmExO-oS0DWCMC00DntrymJoB8tk", - y: "KTFwjHtQxGTDR91VsOypcdBfvbo6sAMj5p4Wb-9hRA0", - }, - expiresAt: 1726209328, - }; - - const result = verifyP256Jwt<{ - exp: number; - id: string; - origin: string; - isScam: boolean; - isVerified: true; - }>(token, publicKey.publicKey); - console.log("result", result); - expect(result).to.exist; - expect(result).to.exist; - expect(result.isVerified).to.be.true; - expect(result.exp).to.exist; - expect(result.origin).to.exist; - expect(result.isScam).to.be.null; - await new Promise((resolve) => setTimeout(resolve, 1000)); - }); - it("should fail to validate invalid jwt with public key", async () => { - const token = - "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.oiMDkxN2YzMzk0YTdmMzkyZTg3ZTM1ZjM4OTg2OWU2NDEzZjkyNTBlMGIxZTE4YjUzMDhkNzBhM2VjOTJjZDQ3OCIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImlzU2NhbSI6bnVsbCwiaXNWZXJpZmllZCI6ZmFsc2V9.RehA28c0Ae8D_ixvGS8uG9J9eTJtpGfaC_7kNE9ZNAVFREWBY6Dl_SXc0_E0RSvYkHpupfmXlmjenuDqNcyoeg"; - - const publicKey = { - publicKey: { - crv: "P-256", - ext: true, - key_ops: ["verify"], - kty: "EC", - x: "CbL4DOYOb1ntd-8OmExO-oS0DWCMC00DntrymJoB8tk", - y: "KTFwjHtQxGTDR91VsOypcdBfvbo6sAMj5p4Wb-9hRA0", - }, - expiresAt: 1726209328, - }; - - expect(() => - verifyP256Jwt<{ - exp: number; - id: string; - origin: string; - isScam: boolean; - isVerified: true; - }>(token, publicKey.publicKey), - ).to.throw(); - }); - it("should fail to validate validate verify v2 jwt with invalid public key", async () => { - const token = - "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjM2MzI1MDQsImlkIjoiMDkxN2YzMzk0YTdmMzkyZTg3ZTM1ZjM4OTg2OWU2NDEzZjkyNTBlMGIxZTE4YjUzMDhkNzBhM2VjOTJjZDQ3OCIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImlzU2NhbSI6bnVsbCwiaXNWZXJpZmllZCI6ZmFsc2V9.RehA28c0Ae8D_ixvGS8uG9J9eTJtpGfaC_7kNE9ZNAVFREWBY6Dl_SXc0_E0RSvYkHpupfmXlmjenuDqNcyoeg"; - - const publicKey = { - publicKey: { - crv: "P-256", - ext: true, - key_ops: ["verify"], - kty: "EC", - x: "CbL4DOYOb1ntd-8OmExO-oS0DWCMC00Dn", - y: "KTFwjHtQxGTDR91VsOypcdBfvbo6sAMj5p4Wb-9hRA0", - }, - expiresAt: 1726209328, - }; - - expect(() => - verifyP256Jwt<{ - exp: number; - id: string; - origin: string; - isScam: boolean; - isVerified: true; - }>(token, publicKey.publicKey), - ).to.throw(); - }); }); diff --git a/rollup.config.js b/rollup.config.js index 302e57c6c..83662e0bb 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,12 +1,10 @@ import esbuild from "rollup-plugin-esbuild"; import { nodeResolve } from "@rollup/plugin-node-resolve"; import commonjs from "@rollup/plugin-commonjs"; -import json from "@rollup/plugin-json"; const input = "./src/index.ts"; const plugins = [ nodeResolve({ preferBuiltins: false, browser: true }), - json(), commonjs(), esbuild({ minify: true, From 8b1cbb658fa8500417da83689f4109c35e36f93d Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Tue, 20 Aug 2024 16:41:07 +0300 Subject: [PATCH 40/61] chore: prep for 2.15.1 release --- lerna.json | 2 +- package-lock.json | 98 +++++++++++------------ packages/core/package.json | 6 +- packages/core/src/constants/relayer.ts | 2 +- packages/react-native-compat/package.json | 2 +- packages/sign-client/package.json | 8 +- packages/types/package.json | 2 +- packages/utils/package.json | 4 +- packages/web3wallet/package.json | 10 +-- providers/ethereum-provider/package.json | 10 +-- providers/signer-connection/package.json | 8 +- providers/universal-provider/package.json | 8 +- 12 files changed, 80 insertions(+), 80 deletions(-) diff --git a/lerna.json b/lerna.json index 75af9a89b..3280e1775 100644 --- a/lerna.json +++ b/lerna.json @@ -4,5 +4,5 @@ "packages/*", "providers/*" ], - "version": "2.15.0" + "version": "2.15.1" } diff --git a/package-lock.json b/package-lock.json index 267fb2489..01c4f5d3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27400,7 +27400,7 @@ }, "packages/core": { "name": "@walletconnect/core", - "version": "2.15.0", + "version": "2.15.1", "license": "Apache-2.0", "dependencies": { "@walletconnect/heartbeat": "1.2.2", @@ -27414,8 +27414,8 @@ "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.0", - "@walletconnect/utils": "2.15.0", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1", "events": "3.3.0", "lodash.isequal": "4.5.0", "uint8arrays": "3.1.0" @@ -27437,7 +27437,7 @@ }, "packages/react-native-compat": { "name": "@walletconnect/react-native-compat", - "version": "2.15.0", + "version": "2.15.1", "license": "Apache-2.0", "dependencies": { "events": "3.3.0", @@ -27458,17 +27458,17 @@ }, "packages/sign-client": { "name": "@walletconnect/sign-client", - "version": "2.15.0", + "version": "2.15.1", "license": "Apache-2.0", "dependencies": { - "@walletconnect/core": "2.15.0", + "@walletconnect/core": "2.15.1", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.0", - "@walletconnect/utils": "2.15.0", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1", "events": "3.3.0" }, "devDependencies": { @@ -27480,7 +27480,7 @@ }, "packages/types": { "name": "@walletconnect/types", - "version": "2.15.0", + "version": "2.15.1", "license": "Apache-2.0", "dependencies": { "@walletconnect/events": "1.0.1", @@ -27493,7 +27493,7 @@ }, "packages/utils": { "name": "@walletconnect/utils", - "version": "2.15.0", + "version": "2.15.1", "license": "Apache-2.0", "dependencies": { "@stablelib/chacha20poly1305": "1.0.1", @@ -27504,7 +27504,7 @@ "@walletconnect/relay-api": "1.0.11", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.0", + "@walletconnect/types": "2.15.1", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", @@ -27525,17 +27525,17 @@ }, "packages/web3wallet": { "name": "@walletconnect/web3wallet", - "version": "1.14.0", + "version": "1.14.1", "license": "Apache-2.0", "dependencies": { "@walletconnect/auth-client": "2.1.2", - "@walletconnect/core": "2.15.0", + "@walletconnect/core": "2.15.1", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.15.0", - "@walletconnect/types": "2.15.0", - "@walletconnect/utils": "2.15.0" + "@walletconnect/sign-client": "2.15.1", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1" }, "devDependencies": { "@ethersproject/wallet": "5.7.0" @@ -27543,7 +27543,7 @@ }, "providers/ethereum-provider": { "name": "@walletconnect/ethereum-provider", - "version": "2.15.0", + "version": "2.15.1", "license": "Apache-2.0", "dependencies": { "@walletconnect/jsonrpc-http-connection": "1.0.8", @@ -27551,10 +27551,10 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/modal": "2.6.2", - "@walletconnect/sign-client": "2.15.0", - "@walletconnect/types": "2.15.0", - "@walletconnect/universal-provider": "2.15.0", - "@walletconnect/utils": "2.15.0", + "@walletconnect/sign-client": "2.15.1", + "@walletconnect/types": "2.15.1", + "@walletconnect/universal-provider": "2.15.1", + "@walletconnect/utils": "2.15.1", "events": "3.3.0" }, "devDependencies": { @@ -27575,14 +27575,14 @@ }, "providers/signer-connection": { "name": "@walletconnect/signer-connection", - "version": "2.15.0", + "version": "2.15.1", "license": "Apache-2.0", "dependencies": { "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/sign-client": "2.15.0", - "@walletconnect/types": "2.15.0", - "@walletconnect/utils": "2.15.0", + "@walletconnect/sign-client": "2.15.1", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1", "events": "3.3.0", "uint8arrays": "3.1.0" } @@ -27597,7 +27597,7 @@ }, "providers/universal-provider": { "name": "@walletconnect/universal-provider", - "version": "2.15.0", + "version": "2.15.1", "license": "Apache-2.0", "dependencies": { "@walletconnect/jsonrpc-http-connection": "1.0.8", @@ -27605,9 +27605,9 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.15.0", - "@walletconnect/types": "2.15.0", - "@walletconnect/utils": "2.15.0", + "@walletconnect/sign-client": "2.15.1", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1", "events": "3.3.0" }, "devDependencies": { @@ -33554,8 +33554,8 @@ "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.0", - "@walletconnect/utils": "2.15.0", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1", "events": "3.3.0", "lodash.isequal": "4.5.0", "uint8arrays": "3.1.0" @@ -33590,10 +33590,10 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/modal": "2.6.2", - "@walletconnect/sign-client": "2.15.0", - "@walletconnect/types": "2.15.0", - "@walletconnect/universal-provider": "2.15.0", - "@walletconnect/utils": "2.15.0", + "@walletconnect/sign-client": "2.15.1", + "@walletconnect/types": "2.15.1", + "@walletconnect/universal-provider": "2.15.1", + "@walletconnect/utils": "2.15.1", "ethereum-test-network": "0.1.6", "ethers": "5.6.9", "events": "3.3.0", @@ -33782,7 +33782,7 @@ "version": "file:packages/sign-client", "requires": { "@aws-sdk/client-cloudwatch": "3.450.0", - "@walletconnect/core": "2.15.0", + "@walletconnect/core": "2.15.1", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-provider": "1.0.14", @@ -33791,8 +33791,8 @@ "@walletconnect/logger": "2.1.2", "@walletconnect/relay-api": "1.0.11", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.0", - "@walletconnect/utils": "2.15.0", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1", "events": "3.3.0" } }, @@ -33801,9 +33801,9 @@ "requires": { "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/sign-client": "2.15.0", - "@walletconnect/types": "2.15.0", - "@walletconnect/utils": "2.15.0", + "@walletconnect/sign-client": "2.15.1", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1", "events": "3.3.0", "uint8arrays": "3.1.0" }, @@ -33848,9 +33848,9 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.15.0", - "@walletconnect/types": "2.15.0", - "@walletconnect/utils": "2.15.0", + "@walletconnect/sign-client": "2.15.1", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1", "cosmos-wallet": "1.2.0", "ethereum-test-network": "0.1.6", "ethers": "5.7.0", @@ -34039,7 +34039,7 @@ "@walletconnect/relay-api": "1.0.11", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.0", + "@walletconnect/types": "2.15.1", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", @@ -34062,13 +34062,13 @@ "requires": { "@ethersproject/wallet": "5.7.0", "@walletconnect/auth-client": "2.1.2", - "@walletconnect/core": "2.15.0", + "@walletconnect/core": "2.15.1", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.15.0", - "@walletconnect/types": "2.15.0", - "@walletconnect/utils": "2.15.0" + "@walletconnect/sign-client": "2.15.1", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1" } }, "@walletconnect/window-getters": { diff --git a/packages/core/package.json b/packages/core/package.json index 500a05a0e..7dea8c5b4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/core", "description": "Core for WalletConnect Protocol", - "version": "2.15.0", + "version": "2.15.1", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", @@ -42,8 +42,8 @@ "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.0", - "@walletconnect/utils": "2.15.0", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1", "events": "3.3.0", "lodash.isequal": "4.5.0", "uint8arrays": "3.1.0" diff --git a/packages/core/src/constants/relayer.ts b/packages/core/src/constants/relayer.ts index cfb2e24aa..d0e4b5b51 100644 --- a/packages/core/src/constants/relayer.ts +++ b/packages/core/src/constants/relayer.ts @@ -34,7 +34,7 @@ export const RELAYER_STORAGE_OPTIONS = { // Updated automatically via `new-version` npm script. -export const RELAYER_SDK_VERSION = "2.15.0"; +export const RELAYER_SDK_VERSION = "2.15.1"; // delay to wait before closing the transport connection after init if not active export const RELAYER_TRANSPORT_CUTOFF = 10_000; diff --git a/packages/react-native-compat/package.json b/packages/react-native-compat/package.json index 2efd8d9ae..b1908fc9e 100644 --- a/packages/react-native-compat/package.json +++ b/packages/react-native-compat/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/react-native-compat", "description": "Shims for WalletConnect Protocol in React Native Projects", - "version": "2.15.0", + "version": "2.15.1", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", diff --git a/packages/sign-client/package.json b/packages/sign-client/package.json index c4e536aae..d2d22d372 100644 --- a/packages/sign-client/package.json +++ b/packages/sign-client/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/sign-client", "description": "Sign Client for WalletConnect Protocol", - "version": "2.15.0", + "version": "2.15.1", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", @@ -38,14 +38,14 @@ "prettier": "prettier --check '{src,test}/**/*.{js,ts,jsx,tsx}'" }, "dependencies": { - "@walletconnect/core": "2.15.0", + "@walletconnect/core": "2.15.1", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.0", - "@walletconnect/utils": "2.15.0", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1", "events": "3.3.0" }, "devDependencies": { diff --git a/packages/types/package.json b/packages/types/package.json index 0f43ef6bf..73eba0027 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/types", "description": "Typings for WalletConnect Protocol", - "version": "2.15.0", + "version": "2.15.1", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", diff --git a/packages/utils/package.json b/packages/utils/package.json index 0b141dcca..d78256c1d 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/utils", "description": "Utilities for WalletConnect Protocol", - "version": "2.15.0", + "version": "2.15.1", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", @@ -39,7 +39,7 @@ "@walletconnect/relay-api": "1.0.11", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.0", + "@walletconnect/types": "2.15.1", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", diff --git a/packages/web3wallet/package.json b/packages/web3wallet/package.json index 2af9b271a..537f1d21b 100644 --- a/packages/web3wallet/package.json +++ b/packages/web3wallet/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/web3wallet", "description": "Web3Wallet for WalletConnect Protocol", - "version": "1.14.0", + "version": "1.14.1", "private": true, "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", @@ -30,13 +30,13 @@ }, "dependencies": { "@walletconnect/auth-client": "2.1.2", - "@walletconnect/core": "2.15.0", + "@walletconnect/core": "2.15.1", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.15.0", - "@walletconnect/types": "2.15.0", - "@walletconnect/utils": "2.15.0" + "@walletconnect/sign-client": "2.15.1", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1" }, "devDependencies": { "@ethersproject/wallet": "5.7.0" diff --git a/providers/ethereum-provider/package.json b/providers/ethereum-provider/package.json index c8ad55292..5a2d0ea73 100644 --- a/providers/ethereum-provider/package.json +++ b/providers/ethereum-provider/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/ethereum-provider", "description": "Ethereum Provider for WalletConnect Protocol", - "version": "2.15.0", + "version": "2.15.1", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "repository": { @@ -48,10 +48,10 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/modal": "2.6.2", - "@walletconnect/sign-client": "2.15.0", - "@walletconnect/types": "2.15.0", - "@walletconnect/universal-provider": "2.15.0", - "@walletconnect/utils": "2.15.0", + "@walletconnect/sign-client": "2.15.1", + "@walletconnect/types": "2.15.1", + "@walletconnect/universal-provider": "2.15.1", + "@walletconnect/utils": "2.15.1", "events": "3.3.0" }, "devDependencies": { diff --git a/providers/signer-connection/package.json b/providers/signer-connection/package.json index a84b2b1f4..f4cd67674 100644 --- a/providers/signer-connection/package.json +++ b/providers/signer-connection/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/signer-connection", "description": "Signer Connection for WalletConnect Protocol", - "version": "2.15.0", + "version": "2.15.1", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", @@ -39,9 +39,9 @@ "dependencies": { "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/sign-client": "2.15.0", - "@walletconnect/types": "2.15.0", - "@walletconnect/utils": "2.15.0", + "@walletconnect/sign-client": "2.15.1", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1", "events": "3.3.0", "uint8arrays": "3.1.0" } diff --git a/providers/universal-provider/package.json b/providers/universal-provider/package.json index fd7e22dcf..d942c6f93 100644 --- a/providers/universal-provider/package.json +++ b/providers/universal-provider/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/universal-provider", "description": "Universal Provider for WalletConnect Protocol", - "version": "2.15.0", + "version": "2.15.1", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "repository": { @@ -45,9 +45,9 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.15.0", - "@walletconnect/types": "2.15.0", - "@walletconnect/utils": "2.15.0", + "@walletconnect/sign-client": "2.15.1", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1", "events": "3.3.0" }, "devDependencies": { From fdf866754d86bc574d8006f83e20d4779e61c73f Mon Sep 17 00:00:00 2001 From: Gancho Radkov <43912948+ganchoradkov@users.noreply.github.com> Date: Tue, 20 Aug 2024 17:09:15 +0300 Subject: [PATCH 41/61] Revert "chore: reverts verify v2" --- package-lock.json | 137 ++++++++ package.json | 1 + packages/core/src/constants/verify.ts | 1 + packages/core/src/controllers/verify.ts | 309 ++++++++++++------ packages/core/src/core.ts | 2 +- packages/sign-client/src/client.ts | 1 - .../sign-client/src/controllers/engine.ts | 66 ++-- packages/types/src/core/verify.ts | 14 +- packages/utils/package.json | 2 + packages/utils/src/crypto.ts | 66 ++++ packages/utils/test/crypto.spec.ts | 90 +++++ rollup.config.js | 2 + 12 files changed, 553 insertions(+), 138 deletions(-) diff --git a/package-lock.json b/package-lock.json index 01c4f5d3f..a08b50e5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ ], "devDependencies": { "@rollup/plugin-commonjs": "22.0.2", + "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "13.3.0", "@types/node": "18.7.3", "@types/sinon": "10.0.13", @@ -6372,6 +6373,54 @@ "rollup": "^2.68.0" } }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json/node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json/node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, "node_modules/@rollup/plugin-node-resolve": { "version": "13.3.0", "dev": true, @@ -7421,6 +7470,15 @@ "@types/chai": "*" } }, + "node_modules/@types/elliptic": { + "version": "6.4.18", + "resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.18.tgz", + "integrity": "sha512-UseG6H5vjRiNpQvrhy4VF/JXdA3V/Fp5amvveaL+fs28BZ6xIKJBPnUPRlEaZpysD9MbpfaLi8lbl7PGUAkpWw==", + "dev": true, + "dependencies": { + "@types/bn.js": "*" + } + }, "node_modules/@types/estree": { "version": "0.0.39", "dev": true, @@ -27508,13 +27566,34 @@ "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", + "elliptic": "^6.5.7", "query-string": "7.1.3", "uint8arrays": "3.1.0" }, "devDependencies": { + "@types/elliptic": "^6.4.18", "@types/lodash.isequal": "4.5.6" } }, + "packages/utils/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "packages/utils/node_modules/elliptic": { + "version": "6.5.7", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", + "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "packages/utils/node_modules/uint8arrays": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.0.tgz", @@ -32437,6 +32516,34 @@ "resolve": "^1.17.0" } }, + "@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.1.0" + }, + "dependencies": { + "@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + } + }, + "@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + } + } + }, "@rollup/plugin-node-resolve": { "version": "13.3.0", "dev": true, @@ -33288,6 +33395,15 @@ "@types/chai": "*" } }, + "@types/elliptic": { + "version": "6.4.18", + "resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.18.tgz", + "integrity": "sha512-UseG6H5vjRiNpQvrhy4VF/JXdA3V/Fp5amvveaL+fs28BZ6xIKJBPnUPRlEaZpysD9MbpfaLi8lbl7PGUAkpWw==", + "dev": true, + "requires": { + "@types/bn.js": "*" + } + }, "@types/estree": { "version": "0.0.39", "dev": true @@ -34035,6 +34151,7 @@ "@stablelib/random": "1.0.2", "@stablelib/sha256": "1.0.1", "@stablelib/x25519": "1.0.3", + "@types/elliptic": "^6.4.18", "@types/lodash.isequal": "4.5.6", "@walletconnect/relay-api": "1.0.11", "@walletconnect/safe-json": "1.0.2", @@ -34043,10 +34160,30 @@ "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", + "elliptic": "^6.5.7", "query-string": "7.1.3", "uint8arrays": "3.1.0" }, "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "elliptic": { + "version": "6.5.7", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", + "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "uint8arrays": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.0.tgz", diff --git a/package.json b/package.json index 0d53dd87c..658f4d83e 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ }, "devDependencies": { "@rollup/plugin-commonjs": "22.0.2", + "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "13.3.0", "@types/node": "18.7.3", "@types/sinon": "10.0.13", diff --git a/packages/core/src/constants/verify.ts b/packages/core/src/constants/verify.ts index 479fe969d..77049537a 100644 --- a/packages/core/src/constants/verify.ts +++ b/packages/core/src/constants/verify.ts @@ -3,5 +3,6 @@ export const VERIFY_CONTEXT = "verify-api"; const VERIFY_SERVER_COM = "https://verify.walletconnect.com"; const VERIFY_SERVER_ORG = "https://verify.walletconnect.org"; export const VERIFY_SERVER = VERIFY_SERVER_ORG; +export const VERIFY_SERVER_V2 = `${VERIFY_SERVER}/v2`; export const TRUSTED_VERIFY_URLS = [VERIFY_SERVER_COM, VERIFY_SERVER_ORG]; diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index a710b0e2b..925a47395 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -1,68 +1,135 @@ import { generateChildLogger, getLoggerContext, Logger } from "@walletconnect/logger"; -import { IVerify } from "@walletconnect/types"; -import { isBrowser, isNode, isReactNative } from "@walletconnect/utils"; +import { ICore, IVerify } from "@walletconnect/types"; +import { isBrowser, isNode, P256KeyDataType, verifyP256Jwt } from "@walletconnect/utils"; import { FIVE_SECONDS, ONE_SECOND, toMiliseconds } from "@walletconnect/time"; - -import { TRUSTED_VERIFY_URLS, VERIFY_CONTEXT, VERIFY_SERVER } from "../constants"; - +import { getDocument } from "@walletconnect/window-getters"; + +import { + CORE_STORAGE_PREFIX, + CORE_VERSION, + TRUSTED_VERIFY_URLS, + VERIFY_CONTEXT, + VERIFY_SERVER, + VERIFY_SERVER_V2, +} from "../constants"; +import { IKeyValueStorage } from "@walletconnect/keyvaluestorage"; + +type Jwk = { + publicKey: P256KeyDataType; + expiresAt: number; +}; +type JwkPayload = { + exp: number; + id: string; + origin: string; + isScam: boolean; + isVerified: boolean; +}; export class Verify extends IVerify { public name = VERIFY_CONTEXT; - private verifyUrl: string; - private iframe?: HTMLIFrameElement; - private initialized = false; private abortController: AbortController; private isDevEnv; - // the queue is only used during the loading phase of the iframe to ensure all attestations are posted - private queue: string[] = []; - // flag to disable verify when the iframe fails to load on main & fallback urls. - // this means Verify API is not enabled for the current projectId and there's no point in trying to initialize it again. - private verifyDisabled = false; - - constructor(public projectId: string, public logger: Logger) { - super(projectId, logger); + private verifyUrlV2 = VERIFY_SERVER_V2; + private storagePrefix = CORE_STORAGE_PREFIX; + private version = CORE_VERSION; + private publicKey?: Jwk; + private fetchPromise?: Promise; + + constructor(public core: ICore, public logger: Logger, public store: IKeyValueStorage) { + super(core, logger, store); this.logger = generateChildLogger(logger, this.name); - this.verifyUrl = VERIFY_SERVER; this.abortController = new AbortController(); this.isDevEnv = isNode() && process.env.IS_VITEST; + this.init(); } - public init: IVerify["init"] = async (params) => { - if (this.verifyDisabled) return; - - // ignore on non browser environments - if (isReactNative() || !isBrowser()) return; + get storeKey(): string { + return ( + this.storagePrefix + this.version + this.core.customStoragePrefix + "//" + `verify:public:key` + ); + } - const verifyUrl = this.getVerifyUrl(params?.verifyUrl); - // if init is called again with a different url, remove the iframe and start over - if (this.verifyUrl !== verifyUrl) { - this.removeIframe(); + public init = async () => { + this.publicKey = await this.store.getItem(this.storeKey); + if (this.publicKey && toMiliseconds(this.publicKey?.expiresAt) < Date.now()) { + this.logger.debug("verify v2 public key expired"); + await this.removePublicKey(); } - this.verifyUrl = verifyUrl; - - try { - await this.createIframe(); - } catch (error) { - this.logger.info(`Verify iframe failed to load: ${this.verifyUrl}`); - this.logger.info(error); - // if the iframe fails to load, disable verify - this.verifyDisabled = true; + if (!this.publicKey) { + await this.fetchAndPersistPublicKey(); } }; public register: IVerify["register"] = async (params) => { - if (!this.initialized) { - this.addToQueue(params.attestationId); - await this.init(); - } else { - this.sendPost(params.attestationId); + if (!isBrowser()) return; + const { id, decryptedId } = params; + const url = `${this.verifyUrlV2}/attestation?projectId=${this.core.projectId}`; + let src = ""; + try { + const response = await fetch(url, { + method: "POST", + body: JSON.stringify({ id, decryptedId }), + }); + const { srcdoc } = await response.json(); + src = srcdoc; + this.logger.debug("srcdoc fetched", src); + } catch (e) { + this.logger.warn(e); + return; } + try { + const document = getDocument() as Document; + const abortTimeout = this.startAbortTimer(ONE_SECOND * 3); + const attestationJwt = await new Promise((resolve) => { + const abortListener = () => { + window.removeEventListener("message", listener); + document.body.removeChild(iframe); + throw new Error("attestation aborted"); + }; + this.abortController.signal.addEventListener("abort", abortListener, { + signal: this.abortController.signal, + }); + const iframe = document.createElement("iframe"); + iframe.srcdoc = src; + iframe.style.display = "none"; + const listener = (event: MessageEvent) => { + if (!event.data) return; + const data = JSON.parse(event.data); + if (data.type === "verify_attestation") { + clearInterval(abortTimeout); + document.body.removeChild(iframe); + this.abortController.signal.removeEventListener("abort", abortListener); + window.removeEventListener("message", listener); + resolve(data.attestation === null ? "" : data.attestation); + } + }; + document.body.appendChild(iframe); + window.addEventListener("message", listener, { signal: this.abortController.signal }); + }); + this.logger.debug("jwt attestation", attestationJwt); + return attestationJwt as string; + } catch (e) { + this.logger.warn(e); + } + return ""; }; public resolve: IVerify["resolve"] = async (params) => { if (this.isDevEnv) return ""; + const { attestationId, hash } = params; + if (attestationId === "") { + this.logger.debug("resolve: attestationId is empty, skipping"); + return; + } + + if (attestationId) { + const validation = await this.isValidJwtAttestation(attestationId); + if (validation) return validation; + } + if (!hash) return; const verifyUrl = this.getVerifyUrl(params?.verifyUrl); - return this.fetchAttestation(params.attestationId, verifyUrl); + return this.fetchAttestation(hash, verifyUrl); }; get context(): string { @@ -70,87 +137,21 @@ export class Verify extends IVerify { } private fetchAttestation = async (attestationId: string, url: string) => { - this.logger.info(`resolving attestation: ${attestationId} from url: ${url}`); + this.logger.debug(`resolving attestation: ${attestationId} from url: ${url}`); // set artificial timeout to prevent hanging const timeout = this.startAbortTimer(ONE_SECOND * 5); - const result = await fetch(`${url}/attestation/${attestationId}`, { + const result = await fetch(`${url}/attestation/${attestationId}?v2Supported=true`, { signal: this.abortController.signal, }); clearTimeout(timeout); return result.status === 200 ? await result.json() : undefined; }; - private addToQueue = (attestationId: string) => { - this.queue.push(attestationId); - }; - - private processQueue = () => { - if (this.queue.length === 0) return; - this.queue.forEach((attestationId) => this.sendPost(attestationId)); - this.queue = []; - }; - - private sendPost = (attestationId: string) => { - try { - if (!this.iframe) return; - this.iframe.contentWindow?.postMessage(attestationId, "*"); // setting targetOrigin to "*" fixes the `Failed to execute 'postMessage' on 'DOMWindow': The target origin provided...` while the iframe is still loading - this.logger.info(`postMessage sent: ${attestationId} ${this.verifyUrl}`); - } catch (e) {} - }; - - private createIframe = async () => { - let iframeOnLoadResolve: () => void; - const onMessage = (event: MessageEvent) => { - if (event.data === "verify_ready") { - this.onInit(); - window.removeEventListener("message", onMessage); - iframeOnLoadResolve(); - } - }; - await Promise.race([ - new Promise((resolve) => { - const existingIframe = document.getElementById(VERIFY_CONTEXT); - if (existingIframe) { - this.iframe = existingIframe as HTMLIFrameElement; - this.onInit(); - return resolve(); - } - - window.addEventListener("message", onMessage); - const iframe = document.createElement("iframe"); - iframe.id = VERIFY_CONTEXT; - iframe.src = `${this.verifyUrl}/${this.projectId}`; - iframe.style.display = "none"; - document.body.append(iframe); - this.iframe = iframe; - iframeOnLoadResolve = resolve; - }), - new Promise((_, reject) => - setTimeout(() => { - window.removeEventListener("message", onMessage); - reject("verify iframe load timeout"); - }, toMiliseconds(FIVE_SECONDS)), - ), - ]); - }; - - private onInit = () => { - this.initialized = true; - this.processQueue(); - }; - private startAbortTimer(timer: number) { this.abortController = new AbortController(); return setTimeout(() => this.abortController.abort(), toMiliseconds(timer)); } - private removeIframe = () => { - if (!this.iframe) return; - this.iframe.remove(); - this.iframe = undefined; - this.initialized = false; - }; - private getVerifyUrl = (verifyUrl?: string) => { let url = verifyUrl || VERIFY_SERVER; if (!TRUSTED_VERIFY_URLS.includes(url)) { @@ -161,4 +162,94 @@ export class Verify extends IVerify { } return url; }; + + private fetchPublicKey = async () => { + try { + this.logger.debug(`fetching public key from: ${this.verifyUrlV2}`); + const timeout = this.startAbortTimer(FIVE_SECONDS); + const result = await fetch(`${this.verifyUrlV2}/public-key`, { + signal: this.abortController.signal, + }); + clearTimeout(timeout); + return (await result.json()) as Jwk; + } catch (e) { + this.logger.warn(e); + } + return undefined; + }; + + private persistPublicKey = async (publicKey: Jwk) => { + this.logger.debug(`persisting public key to local storage`, publicKey); + await this.store.setItem(this.storeKey, publicKey); + this.publicKey = publicKey; + }; + + private removePublicKey = async () => { + this.logger.debug(`removing verify v2 public key from storage`); + await this.store.removeItem(this.storeKey); + this.publicKey = undefined; + }; + + private isValidJwtAttestation = async (attestation: string) => { + const key = await this.getPublicKey(); + try { + if (key) { + const validation = this.validateAttestation(attestation, key); + return validation; + } + } catch (e) { + this.logger.error(e); + this.logger.warn("error validating attestation"); + } + const newKey = await this.fetchAndPersistPublicKey(); + try { + if (newKey) { + const validation = this.validateAttestation(attestation, newKey); + return validation; + } + } catch (e) { + this.logger.error(e); + this.logger.warn("error validating attestation"); + } + return undefined; + }; + + private getPublicKey = async () => { + if (this.publicKey) return this.publicKey; + return await this.fetchAndPersistPublicKey(); + }; + + private fetchAndPersistPublicKey = async () => { + if (this.fetchPromise) { + await this.fetchPromise; + return this.publicKey; + } + this.fetchPromise = new Promise(async (resolve) => { + const key = await this.fetchPublicKey(); + if (!key) return; + await this.persistPublicKey(key); + resolve(key); + }); + const key = await this.fetchPromise; + this.fetchPromise = undefined; + return key; + }; + + private validateAttestation = (attestation: string, key: Jwk) => { + const result = verifyP256Jwt(attestation, key.publicKey); + const validation = { + hasExpired: toMiliseconds(result.exp) < Date.now(), + payload: result, + }; + + if (validation.hasExpired) { + this.logger.warn("resolve: jwt attestation expired"); + throw new Error("JWT attestation expired"); + } + + return { + origin: validation.payload.origin, + isScam: validation.payload.isScam, + }; + }; } diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index a676a7c75..1afc9cffa 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -108,7 +108,7 @@ export class Core extends ICore { projectId: this.projectId, }); this.pairing = new Pairing(this, this.logger); - this.verify = new Verify(this.projectId || "", this.logger); + this.verify = new Verify(this, this.logger, this.storage); this.echoClient = new EchoClient(this.projectId || "", this.logger); } diff --git a/packages/sign-client/src/client.ts b/packages/sign-client/src/client.ts index 02f46dda7..fb6427f6a 100644 --- a/packages/sign-client/src/client.ts +++ b/packages/sign-client/src/client.ts @@ -251,7 +251,6 @@ export class SignClient extends ISignClient { await this.pendingRequest.init(); await this.engine.init(); await this.auth.init(); - this.core.verify.init({ verifyUrl: this.metadata.verifyUrl }); this.logger.info(`SignClient Initialization Success`); this.engine.processRelayMessageCache(); } catch (error: any) { diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index 3974ffb6a..862304f3e 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -81,7 +81,6 @@ import { validateSignedCacao, getNamespacedDidChainId, parseChainId, - isBrowser, } from "@walletconnect/utils"; import EventEmmiter from "events"; import { @@ -1181,10 +1180,7 @@ export class Engine extends IEngine { private sendRequest: EnginePrivate["sendRequest"] = async (args) => { const { topic, method, params, expiry, relayRpcId, clientRpcId, throwOnFailedPublish } = args; const payload = formatJsonRpcRequest(method, params, clientRpcId); - if (isBrowser() && METHODS_TO_VERIFY.includes(method)) { - const hash = hashMessage(JSON.stringify(payload)); - this.client.core.verify.register({ attestationId: hash }); - } + let message; try { message = await this.client.core.crypto.encode(topic, payload); @@ -1194,7 +1190,14 @@ export class Engine extends IEngine { throw error; } + let attestation: string | undefined; + if (METHODS_TO_VERIFY.includes(method)) { + const decryptedId = hashMessage(JSON.stringify(payload)); + const id = hashMessage(message); + attestation = await this.client.core.verify.register({ id, decryptedId }); + } const opts = ENGINE_RPC_OPTS[method].req; + opts.attestation = attestation; if (expiry) opts.ttl = expiry; if (relayRpcId) opts.id = relayRpcId; this.client.core.history.set(topic, payload); @@ -1367,7 +1370,7 @@ export class Engine extends IEngine { }; private processRequest: EnginePrivate["onRelayEventRequest"] = async (event) => { - const { topic, payload } = event; + const { topic, payload, attestation } = event; const reqMethod = payload.method as JsonRpcTypes.WcMethod; if (this.shouldIgnorePairingRequest({ topic, requestMethod: reqMethod })) { @@ -1376,7 +1379,7 @@ export class Engine extends IEngine { switch (reqMethod) { case "wc_sessionPropose": - return await this.onSessionProposeRequest(topic, payload); + return await this.onSessionProposeRequest(topic, payload, attestation); case "wc_sessionSettle": return await this.onSessionSettleRequest(topic, payload); case "wc_sessionUpdate": @@ -1388,11 +1391,11 @@ export class Engine extends IEngine { case "wc_sessionDelete": return await this.onSessionDeleteRequest(topic, payload); case "wc_sessionRequest": - return await this.onSessionRequest(topic, payload); + return await this.onSessionRequest(topic, payload, attestation); case "wc_sessionEvent": return await this.onSessionEventRequest(topic, payload); case "wc_sessionAuthenticate": - return await this.onSessionAuthenticateRequest(topic, payload); + return await this.onSessionAuthenticateRequest(topic, payload, attestation); default: return this.client.logger.info(`Unsupported request method ${reqMethod}`); } @@ -1455,6 +1458,7 @@ export class Engine extends IEngine { private onSessionProposeRequest: EnginePrivate["onSessionProposeRequest"] = async ( topic, payload, + attestation, ) => { const { params, id } = payload; try { @@ -1463,8 +1467,11 @@ export class Engine extends IEngine { params.expiryTimestamp || calcExpiry(ENGINE_RPC_OPTS.wc_sessionPropose.req.ttl); const proposal = { id, pairingTopic: topic, expiryTimestamp, ...params }; await this.setProposal(id, proposal); - const hash = hashMessage(JSON.stringify(payload)); - const verifyContext = await this.getVerifyContext(hash, proposal.proposer.metadata); + const verifyContext = await this.getVerifyContext({ + attestationId: attestation, + hash: hashMessage(JSON.stringify(payload)), + metadata: proposal.proposer.metadata, + }); this.client.events.emit("session_proposal", { id, params: proposal, verifyContext }); } catch (err: any) { await this.sendError({ @@ -1759,15 +1766,20 @@ export class Engine extends IEngine { } }; - private onSessionRequest: EnginePrivate["onSessionRequest"] = async (topic, payload) => { + private onSessionRequest: EnginePrivate["onSessionRequest"] = async ( + topic, + payload, + attestation, + ) => { const { id, params } = payload; try { await this.isValidRequest({ topic, ...params }); - const hash = hashMessage( - JSON.stringify(formatJsonRpcRequest("wc_sessionRequest", params, id)), - ); const session = this.client.session.get(topic); - const verifyContext = await this.getVerifyContext(hash, session.peer.metadata); + const verifyContext = await this.getVerifyContext({ + attestationId: attestation, + hash: hashMessage(JSON.stringify(formatJsonRpcRequest("wc_sessionRequest", params, id))), + metadata: session.peer.metadata, + }); const request = { id, topic, @@ -1859,11 +1871,15 @@ export class Engine extends IEngine { private onSessionAuthenticateRequest: EnginePrivate["onSessionAuthenticateRequest"] = async ( topic, payload, + attestation, ) => { try { const { requester, authPayload, expiryTimestamp } = payload.params; - const hash = hashMessage(JSON.stringify(payload)); - const verifyContext = await this.getVerifyContext(hash, this.client.metadata); + const verifyContext = await this.getVerifyContext({ + attestationId: attestation, + hash: hashMessage(JSON.stringify(payload)), + metadata: this.client.metadata, + }); const pendingRequest = { requester, pairingTopic: topic, @@ -2409,7 +2425,12 @@ export class Engine extends IEngine { } }; - private getVerifyContext = async (hash: string, metadata: CoreTypes.Metadata) => { + private getVerifyContext = async (params: { + attestationId?: string; + hash?: string; + metadata: CoreTypes.Metadata; + }) => { + const { attestationId, hash, metadata } = params; const context: Verify.Context = { verified: { verifyUrl: metadata.verifyUrl || VERIFY_SERVER, @@ -2420,7 +2441,8 @@ export class Engine extends IEngine { try { const result = await this.client.core.verify.resolve({ - attestationId: hash, + attestationId, + hash, verifyUrl: metadata.verifyUrl, }); if (result) { @@ -2430,10 +2452,10 @@ export class Engine extends IEngine { result.origin === new URL(metadata.url).origin ? "VALID" : "INVALID"; } } catch (e) { - this.client.logger.info(e); + this.client.logger.warn(e); } - this.client.logger.info(`Verify context: ${JSON.stringify(context)}`); + this.client.logger.debug(`Verify context: ${JSON.stringify(context)}`); return context; }; diff --git a/packages/types/src/core/verify.ts b/packages/types/src/core/verify.ts index 55a0327de..ca13f5380 100644 --- a/packages/types/src/core/verify.ts +++ b/packages/types/src/core/verify.ts @@ -1,4 +1,6 @@ import { Logger } from "@walletconnect/logger"; +import { IKeyValueStorage } from "@walletconnect/keyvaluestorage"; +import { ICore } from "./core"; export declare namespace Verify { export interface Context { @@ -14,14 +16,16 @@ export declare namespace Verify { export abstract class IVerify { public abstract readonly context: string; - constructor(public projectId: string, public logger: Logger) {} + constructor(public core: ICore, public logger: Logger, public store: IKeyValueStorage) {} - public abstract init(params?: { verifyUrl?: string }): Promise; - - public abstract register(params: { attestationId: string }): Promise; + public abstract register(params: { + id: string; + decryptedId: string; + }): Promise; public abstract resolve(params: { - attestationId: string; + attestationId?: string; + hash?: string; verifyUrl?: string; }): Promise<{ origin: string; isScam?: boolean }>; } diff --git a/packages/utils/package.json b/packages/utils/package.json index d78256c1d..bcba44687 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -43,10 +43,12 @@ "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", + "elliptic": "^6.5.7", "query-string": "7.1.3", "uint8arrays": "3.1.0" }, "devDependencies": { + "@types/elliptic": "^6.4.18", "@types/lodash.isequal": "4.5.6" } } diff --git a/packages/utils/src/crypto.ts b/packages/utils/src/crypto.ts index 5c1464f0c..2a5a00098 100644 --- a/packages/utils/src/crypto.ts +++ b/packages/utils/src/crypto.ts @@ -5,6 +5,8 @@ import { hash, SHA256 } from "@stablelib/sha256"; import * as x25519 from "@stablelib/x25519"; import { CryptoTypes } from "@walletconnect/types"; import { concat, fromString, toString } from "uint8arrays"; +import { ec as EC } from "elliptic"; +import { decodeJWT } from "@walletconnect/relay-auth"; export const BASE10 = "base10"; export const BASE16 = "base16"; @@ -169,3 +171,67 @@ export function isTypeOneEnvelope( typeof result.receiverPublicKey === "string" ); } + +export function getCryptoKeyFromKeyData(keyData: P256KeyDataType): EC.KeyPair { + const ec = new EC("p256"); + const key = ec.keyFromPublic( + { + x: Buffer.from(keyData.x, "base64").toString("hex"), + y: Buffer.from(keyData.y, "base64").toString("hex"), + }, + "hex", + ); + return key; +} + +function base64UrlToBase64(base64Url: string) { + let base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); + const padding = base64.length % 4; + if (padding > 0) { + base64 += "=".repeat(4 - padding); + } + return base64; +} + +function base64UrlDecode(base64Url: string) { + return Buffer.from(base64UrlToBase64(base64Url), "base64"); +} + +export function verifyP256Jwt(token: string, keyData: P256KeyDataType) { + const [headerBase64Url, payloadBase64Url, signatureBase64Url] = token.split("."); + + // Decode the signature + const signatureBuffer = base64UrlDecode(signatureBase64Url); + + // Check if signature length is correct (64 bytes for P-256) + if (signatureBuffer.length !== 64) { + throw new Error("Invalid signature length"); + } + + // Extract r and s from the signature + const r = signatureBuffer.slice(0, 32).toString("hex"); + const s = signatureBuffer.slice(32, 64).toString("hex"); + + // Create the signing input + const signingInput = `${headerBase64Url}.${payloadBase64Url}`; + + const sha256 = new SHA256(); + const buffer = sha256.update(Buffer.from(signingInput)).digest(); + + const key = getCryptoKeyFromKeyData(keyData); + + // Convert the hash to hex format + const hashHex = Buffer.from(buffer).toString("hex"); + + // Verify the signature + const isValid = key.verify(hashHex, { r, s }); + + if (!isValid) { + throw new Error("Invalid signature"); + } + const data = decodeJWT(token) as unknown as { payload: T }; + return { + ...data.payload, + isVerified: isValid, + }; +} diff --git a/packages/utils/test/crypto.spec.ts b/packages/utils/test/crypto.spec.ts index de2e1167a..8b7efeed4 100644 --- a/packages/utils/test/crypto.spec.ts +++ b/packages/utils/test/crypto.spec.ts @@ -1,6 +1,9 @@ import { expect, describe, it } from "vitest"; import { toString } from "uint8arrays"; import { safeJsonStringify } from "@walletconnect/safe-json"; +import { SHA256 } from "@stablelib/sha256"; +import { Buffer } from "buffer"; +import elliptic from "elliptic"; import { BASE16, @@ -14,6 +17,9 @@ import { validateDecoding, isTypeOneEnvelope, generateRandomBytes32, + verifyP256Jwt, + getCryptoKeyFromKeyData, + P256KeyDataType, } from "../src"; import { TEST_KEY_PAIRS, TEST_SHARED_KEY, TEST_HASHED_KEY, TEST_SYM_KEY } from "./shared"; @@ -108,4 +114,88 @@ describe("Crypto", () => { it("calls generateRandomBytes32", () => { expect(generateRandomBytes32()).toBeTruthy(); }); + it("should validate verify v2 jwt", async () => { + const token = + "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjM2MzI1MDQsImlkIjoiMDkxN2YzMzk0YTdmMzkyZTg3ZTM1ZjM4OTg2OWU2NDEzZjkyNTBlMGIxZTE4YjUzMDhkNzBhM2VjOTJjZDQ3OCIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImlzU2NhbSI6bnVsbCwiaXNWZXJpZmllZCI6ZmFsc2V9.RehA28c0Ae8D_ixvGS8uG9J9eTJtpGfaC_7kNE9ZNAVFREWBY6Dl_SXc0_E0RSvYkHpupfmXlmjenuDqNcyoeg"; + + const publicKey = { + publicKey: { + crv: "P-256", + ext: true, + key_ops: ["verify"], + kty: "EC", + x: "CbL4DOYOb1ntd-8OmExO-oS0DWCMC00DntrymJoB8tk", + y: "KTFwjHtQxGTDR91VsOypcdBfvbo6sAMj5p4Wb-9hRA0", + }, + expiresAt: 1726209328, + }; + + const result = verifyP256Jwt<{ + exp: number; + id: string; + origin: string; + isScam: boolean; + isVerified: true; + }>(token, publicKey.publicKey); + console.log("result", result); + expect(result).to.exist; + expect(result).to.exist; + expect(result.isVerified).to.be.true; + expect(result.exp).to.exist; + expect(result.origin).to.exist; + expect(result.isScam).to.be.null; + await new Promise((resolve) => setTimeout(resolve, 1000)); + }); + it("should fail to validate invalid jwt with public key", async () => { + const token = + "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.oiMDkxN2YzMzk0YTdmMzkyZTg3ZTM1ZjM4OTg2OWU2NDEzZjkyNTBlMGIxZTE4YjUzMDhkNzBhM2VjOTJjZDQ3OCIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImlzU2NhbSI6bnVsbCwiaXNWZXJpZmllZCI6ZmFsc2V9.RehA28c0Ae8D_ixvGS8uG9J9eTJtpGfaC_7kNE9ZNAVFREWBY6Dl_SXc0_E0RSvYkHpupfmXlmjenuDqNcyoeg"; + + const publicKey = { + publicKey: { + crv: "P-256", + ext: true, + key_ops: ["verify"], + kty: "EC", + x: "CbL4DOYOb1ntd-8OmExO-oS0DWCMC00DntrymJoB8tk", + y: "KTFwjHtQxGTDR91VsOypcdBfvbo6sAMj5p4Wb-9hRA0", + }, + expiresAt: 1726209328, + }; + + expect(() => + verifyP256Jwt<{ + exp: number; + id: string; + origin: string; + isScam: boolean; + isVerified: true; + }>(token, publicKey.publicKey), + ).to.throw(); + }); + it("should fail to validate validate verify v2 jwt with invalid public key", async () => { + const token = + "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjM2MzI1MDQsImlkIjoiMDkxN2YzMzk0YTdmMzkyZTg3ZTM1ZjM4OTg2OWU2NDEzZjkyNTBlMGIxZTE4YjUzMDhkNzBhM2VjOTJjZDQ3OCIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImlzU2NhbSI6bnVsbCwiaXNWZXJpZmllZCI6ZmFsc2V9.RehA28c0Ae8D_ixvGS8uG9J9eTJtpGfaC_7kNE9ZNAVFREWBY6Dl_SXc0_E0RSvYkHpupfmXlmjenuDqNcyoeg"; + + const publicKey = { + publicKey: { + crv: "P-256", + ext: true, + key_ops: ["verify"], + kty: "EC", + x: "CbL4DOYOb1ntd-8OmExO-oS0DWCMC00Dn", + y: "KTFwjHtQxGTDR91VsOypcdBfvbo6sAMj5p4Wb-9hRA0", + }, + expiresAt: 1726209328, + }; + + expect(() => + verifyP256Jwt<{ + exp: number; + id: string; + origin: string; + isScam: boolean; + isVerified: true; + }>(token, publicKey.publicKey), + ).to.throw(); + }); }); diff --git a/rollup.config.js b/rollup.config.js index 83662e0bb..302e57c6c 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,10 +1,12 @@ import esbuild from "rollup-plugin-esbuild"; import { nodeResolve } from "@rollup/plugin-node-resolve"; import commonjs from "@rollup/plugin-commonjs"; +import json from "@rollup/plugin-json"; const input = "./src/index.ts"; const plugins = [ nodeResolve({ preferBuiltins: false, browser: true }), + json(), commonjs(), esbuild({ minify: true, From 34b84ec35656ce2961a83013ff8dfcd55de0ad33 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Wed, 21 Aug 2024 14:16:51 +0300 Subject: [PATCH 42/61] fix: adds missing relay-auth package --- package-lock.json | 2 ++ packages/utils/package.json | 1 + 2 files changed, 3 insertions(+) diff --git a/package-lock.json b/package-lock.json index a08b50e5d..b8f79e30e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27560,6 +27560,7 @@ "@stablelib/sha256": "1.0.1", "@stablelib/x25519": "1.0.3", "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.15.1", @@ -34154,6 +34155,7 @@ "@types/elliptic": "^6.4.18", "@types/lodash.isequal": "4.5.6", "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.15.1", diff --git a/packages/utils/package.json b/packages/utils/package.json index bcba44687..8e6be8d4b 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -37,6 +37,7 @@ "@stablelib/sha256": "1.0.1", "@stablelib/x25519": "1.0.3", "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.15.1", From 1a7dc286e31c105baaf7e3590adcba82e53bd31f Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Wed, 21 Aug 2024 14:17:12 +0300 Subject: [PATCH 43/61] chore: rm unused imports --- packages/utils/test/crypto.spec.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/utils/test/crypto.spec.ts b/packages/utils/test/crypto.spec.ts index 8b7efeed4..b9941b795 100644 --- a/packages/utils/test/crypto.spec.ts +++ b/packages/utils/test/crypto.spec.ts @@ -1,9 +1,6 @@ import { expect, describe, it } from "vitest"; import { toString } from "uint8arrays"; import { safeJsonStringify } from "@walletconnect/safe-json"; -import { SHA256 } from "@stablelib/sha256"; -import { Buffer } from "buffer"; -import elliptic from "elliptic"; import { BASE16, @@ -18,8 +15,6 @@ import { isTypeOneEnvelope, generateRandomBytes32, verifyP256Jwt, - getCryptoKeyFromKeyData, - P256KeyDataType, } from "../src"; import { TEST_KEY_PAIRS, TEST_SHARED_KEY, TEST_HASHED_KEY, TEST_SYM_KEY } from "./shared"; From 7ff0fcb95e367e3a815d3ff63964ebeacd2425a7 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 21 Aug 2024 18:00:37 -0700 Subject: [PATCH 44/61] fix: abort controller aborting itself --- packages/core/src/controllers/verify.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index a83b80a0b..bf4de80c6 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -74,9 +74,7 @@ export class Verify extends IVerify { document.body.removeChild(iframe); throw new Error("attestation aborted"); }; - this.abortController.signal.addEventListener("abort", abortListener, { - signal: this.abortController.signal, - }); + this.abortController.signal.addEventListener("abort", abortListener); const iframe = document.createElement("iframe"); iframe.src = src; iframe.style.display = "none"; From 259b8593ab66dcf9522e37669d4918f3bf61bd94 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Thu, 22 Aug 2024 11:54:02 +0300 Subject: [PATCH 45/61] chore: sets `trace` level logging on canary --- packages/sign-client/test/canary/canary.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/sign-client/test/canary/canary.spec.ts b/packages/sign-client/test/canary/canary.spec.ts index 276406da1..70fbc18f8 100644 --- a/packages/sign-client/test/canary/canary.spec.ts +++ b/packages/sign-client/test/canary/canary.spec.ts @@ -16,7 +16,7 @@ import { SignClient } from "../../src"; const environment = process.env.ENVIRONMENT || "dev"; const region = process.env.REGION || "unknown"; - +const logger = "trace"; const log = (log: string) => { // eslint-disable-next-line no-console console.log(log); @@ -29,11 +29,13 @@ describe("Canary", () => { const start = Date.now(); const A = await SignClient.init({ ...TEST_SIGN_CLIENT_OPTIONS_A, + logger, }); const handshakeLatencyMs = Date.now() - start; const B = await SignClient.init({ ...TEST_SIGN_CLIENT_OPTIONS_B, + logger, }); const clients = { A, B }; log( From 7e88ee88b2851813909e01abc681a044ca28ba7f Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Thu, 22 Aug 2024 14:27:13 +0300 Subject: [PATCH 46/61] refactor: renames variable and removes promises --- packages/core/src/controllers/events.ts | 52 +++++++++++-------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/packages/core/src/controllers/events.ts b/packages/core/src/controllers/events.ts index 8fdc1e304..fd073c832 100644 --- a/packages/core/src/controllers/events.ts +++ b/packages/core/src/controllers/events.ts @@ -18,7 +18,7 @@ export class EventClient extends IEventClient { private readonly storageVersion = EVENTS_STORAGE_VERSION; private events = new Map(); - private toPersist = false; + private shouldPersist = false; constructor(public core: ICore, public logger: Logger, telemetryEnabled = true) { super(core, logger, telemetryEnabled); this.logger = generateChildLogger(logger, this.context); @@ -66,7 +66,7 @@ export class EventClient extends IEventClient { if (this.telemetryEnabled) { this.events.set(eventId, eventObj); - this.toPersist = true; + this.shouldPersist = true; } return eventObj; @@ -92,12 +92,12 @@ export class EventClient extends IEventClient { public deleteEvent: IEventClient["deleteEvent"] = (params) => { const { eventId } = params; this.events.delete(eventId); - this.toPersist = true; + this.shouldPersist = true; }; private setEventListeners = () => { this.core.heartbeat.on(HEARTBEAT_EVENTS.pulse, async () => { - if (this.toPersist) await this.persist(); + if (this.shouldPersist) await this.persist(); // cleanup events older than EVENTS_STORAGE_CLEANUP_INTERVAL this.events.forEach((event) => { if ( @@ -105,7 +105,7 @@ export class EventClient extends IEventClient { EVENTS_STORAGE_CLEANUP_INTERVAL ) { this.events.delete(event.eventId); - this.toPersist = true; + this.shouldPersist = true; } }); }); @@ -118,32 +118,26 @@ export class EventClient extends IEventClient { }; }; - private addTrace = async (eventId: string, trace: string) => { + private addTrace = (eventId: string, trace: string) => { const event = this.events.get(eventId); if (!event) return; - return await new Promise((resolve) => { - event.props.properties.trace.push(trace); - this.events.set(eventId, event); - this.toPersist = true; - resolve(); - }); + event.props.properties.trace.push(trace); + this.events.set(eventId, event); + this.shouldPersist = true; }; - private setError = async (eventId: string, errorType: string) => { + private setError = (eventId: string, errorType: string) => { const event = this.events.get(eventId); if (!event) return; - return await new Promise((resolve) => { - event.props.type = errorType; - event.timestamp = Date.now(); - this.events.set(eventId, event); - this.toPersist = true; - resolve(); - }); + event.props.type = errorType; + event.timestamp = Date.now(); + this.events.set(eventId, event); + this.shouldPersist = true; }; private persist = async () => { await this.core.storage.setItem(this.storageKey, Array.from(this.events.values())); - this.toPersist = false; + this.shouldPersist = false; }; private restore = async () => { @@ -178,19 +172,17 @@ export class EventClient extends IEventClient { if (eventsToSend.length === 0) return; try { - const response = await fetch(EVENTS_CLIENT_API_URL, { - method: "POST", - body: JSON.stringify(eventsToSend), - headers: { - "x-project-id": `${this.core.projectId}`, - "x-sdk-type": "events_sdk", - "x-sdk-version": `js-${RELAYER_SDK_VERSION}`, + const response = await fetch( + `${EVENTS_CLIENT_API_URL}?projectId=${this.core.projectId}&st=events_sdk&sv=js-${RELAYER_SDK_VERSION}`, + { + method: "POST", + body: JSON.stringify(eventsToSend), }, - }); + ); if (response.ok) { for (const event of eventsToSend) { this.events.delete(event.eventId); - this.toPersist = true; + this.shouldPersist = true; } } } catch (error) { From 21f47756aa32871905c3b655200822f8251952e0 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Thu, 22 Aug 2024 14:27:22 +0300 Subject: [PATCH 47/61] fix: incorrect merge --- .../sign-client/src/controllers/engine.ts | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index 9d9f2bf15..8f519dc8c 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -360,15 +360,20 @@ export class Engine extends IEngine { event.addTrace(EVENT_CLIENT_SESSION_TRACES.store_session); try { - event.addTrace(EVENT_CLIENT_SESSION_TRACES.publishing_session_approve); - + event.addTrace(EVENT_CLIENT_SESSION_TRACES.publishing_session_settle); await this.sendRequest({ topic: sessionTopic, method: "wc_sessionSettle", params: sessionSettle, throwOnFailedPublish: true, + }).catch((error) => { + event?.setError(EVENT_CLIENT_SESSION_ERRORS.session_settle_publish_failure); + throw error; }); + event.addTrace(EVENT_CLIENT_SESSION_TRACES.session_settle_publish_success); + + event.addTrace(EVENT_CLIENT_SESSION_TRACES.publishing_session_approve); await this.sendResult<"wc_sessionPropose">({ id, topic: pairingTopic, @@ -385,19 +390,6 @@ export class Engine extends IEngine { }); event.addTrace(EVENT_CLIENT_SESSION_TRACES.session_approve_publish_success); - event.addTrace(EVENT_CLIENT_SESSION_TRACES.publishing_session_settle); - - await this.sendRequest({ - topic: sessionTopic, - method: "wc_sessionSettle", - params: sessionSettle, - throwOnFailedPublish: true, - }).catch((error) => { - event?.setError(EVENT_CLIENT_SESSION_ERRORS.session_settle_publish_failure); - throw error; - }); - - event.addTrace(EVENT_CLIENT_SESSION_TRACES.session_settle_publish_success); } catch (error) { this.client.logger.error(error); // if the publish fails, delete the session and throw an error @@ -807,7 +799,6 @@ export class Engine extends IEngine { // delete this auth request on response // we're using payload from the wallet to establish the session so we don't need to keep this around await this.deletePendingAuthRequest(id, { message: "fulfilled", code: 0 }); - if (payload.error) { // wallets that do not support wc_sessionAuthenticate will return an error // we should not reject the promise in this case as the fallback session proposal will be used @@ -906,7 +897,6 @@ export class Engine extends IEngine { // set the ids for both requests const id = payloadId(); const fallbackId = payloadId(); - // subscribe to response events this.events.once<"session_connect">(engineEvent("session_connect"), onSessionConnect); this.events.once(engineEvent("session_request", id), onAuthenticate); From 98be49e4a010f8d33f765f3649d38ee4b47ce8fc Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 23 Aug 2024 09:38:33 +0300 Subject: [PATCH 48/61] refactor: updates logger level to env variable --- packages/sign-client/test/canary/canary.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sign-client/test/canary/canary.spec.ts b/packages/sign-client/test/canary/canary.spec.ts index 70fbc18f8..a9aecf92b 100644 --- a/packages/sign-client/test/canary/canary.spec.ts +++ b/packages/sign-client/test/canary/canary.spec.ts @@ -16,7 +16,7 @@ import { SignClient } from "../../src"; const environment = process.env.ENVIRONMENT || "dev"; const region = process.env.REGION || "unknown"; -const logger = "trace"; +const logger = process.env.LOGGER || "error"; const log = (log: string) => { // eslint-disable-next-line no-console console.log(log); From b138715223dd5832ea34d3c53e27d7d8900102a9 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 23 Aug 2024 11:36:50 +0300 Subject: [PATCH 49/61] fix: validates jwt id against encrypted id of the particular request --- packages/core/src/controllers/verify.ts | 14 +++-- .../sign-client/src/controllers/engine.ts | 54 +++++++++++-------- packages/types/src/core/verify.ts | 1 + packages/types/src/sign-client/engine.ts | 34 ++++++------ 4 files changed, 61 insertions(+), 42 deletions(-) diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index bf4de80c6..f788bbbac 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -3,6 +3,7 @@ import { ICore, IVerify } from "@walletconnect/types"; import { isBrowser, isNode, P256KeyDataType, verifyP256Jwt } from "@walletconnect/utils"; import { FIVE_SECONDS, ONE_SECOND, toMiliseconds } from "@walletconnect/time"; import { getDocument } from "@walletconnect/window-getters"; +import { decodeJWT } from "@walletconnect/relay-auth"; import { CORE_STORAGE_PREFIX, @@ -82,6 +83,9 @@ export class Verify extends IVerify { if (!event.data) return; const data = JSON.parse(event.data); if (data.type === "verify_attestation") { + const decoded = decodeJWT(data.attestation) as unknown as { payload: JwkPayload }; + if (decoded.payload.id !== id) return; + clearInterval(abortTimeout); document.body.removeChild(iframe); this.abortController.signal.removeEventListener("abort", abortListener); @@ -102,16 +106,18 @@ export class Verify extends IVerify { public resolve: IVerify["resolve"] = async (params) => { if (this.isDevEnv) return ""; - const { attestationId, hash } = params; - + const { attestationId, hash, encryptedId } = params; if (attestationId === "") { this.logger.debug("resolve: attestationId is empty, skipping"); return; } if (attestationId) { - const validation = await this.isValidJwtAttestation(attestationId); - if (validation) return validation; + const decoded = decodeJWT(attestationId) as unknown as { payload: JwkPayload }; + if (decoded.payload.id === encryptedId) { + const validation = await this.isValidJwtAttestation(attestationId); + if (validation) return validation; + } } if (!hash) return; const verifyUrl = this.getVerifyUrl(params?.verifyUrl); diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index 862304f3e..b8e22c853 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -1327,7 +1327,12 @@ export class Engine extends IEngine { try { if (isJsonRpcRequest(payload)) { this.client.core.history.set(topic, payload); - this.onRelayEventRequest({ topic, payload, attestation }); + this.onRelayEventRequest({ + topic, + payload, + attestation, + encryptedId: hashMessage(message), + }); } else if (isJsonRpcResponse(payload)) { await this.client.core.history.resolve(payload); await this.onRelayEventResponse({ topic, payload }); @@ -1370,7 +1375,7 @@ export class Engine extends IEngine { }; private processRequest: EnginePrivate["onRelayEventRequest"] = async (event) => { - const { topic, payload, attestation } = event; + const { topic, payload, attestation, encryptedId } = event; const reqMethod = payload.method as JsonRpcTypes.WcMethod; if (this.shouldIgnorePairingRequest({ topic, requestMethod: reqMethod })) { @@ -1379,7 +1384,7 @@ export class Engine extends IEngine { switch (reqMethod) { case "wc_sessionPropose": - return await this.onSessionProposeRequest(topic, payload, attestation); + return await this.onSessionProposeRequest({ topic, payload, attestation, encryptedId }); case "wc_sessionSettle": return await this.onSessionSettleRequest(topic, payload); case "wc_sessionUpdate": @@ -1391,11 +1396,16 @@ export class Engine extends IEngine { case "wc_sessionDelete": return await this.onSessionDeleteRequest(topic, payload); case "wc_sessionRequest": - return await this.onSessionRequest(topic, payload, attestation); + return await this.onSessionRequest({ topic, payload, attestation, encryptedId }); case "wc_sessionEvent": return await this.onSessionEventRequest(topic, payload); case "wc_sessionAuthenticate": - return await this.onSessionAuthenticateRequest(topic, payload, attestation); + return await this.onSessionAuthenticateRequest({ + topic, + payload, + attestation, + encryptedId, + }); default: return this.client.logger.info(`Unsupported request method ${reqMethod}`); } @@ -1455,11 +1465,8 @@ export class Engine extends IEngine { // ---------- Relay Events Handlers --------------------------------- // - private onSessionProposeRequest: EnginePrivate["onSessionProposeRequest"] = async ( - topic, - payload, - attestation, - ) => { + private onSessionProposeRequest: EnginePrivate["onSessionProposeRequest"] = async (args) => { + const { topic, payload, attestation, encryptedId } = args; const { params, id } = payload; try { this.isValidConnect({ ...payload.params }); @@ -1470,6 +1477,7 @@ export class Engine extends IEngine { const verifyContext = await this.getVerifyContext({ attestationId: attestation, hash: hashMessage(JSON.stringify(payload)), + encryptedId, metadata: proposal.proposer.metadata, }); this.client.events.emit("session_proposal", { id, params: proposal, verifyContext }); @@ -1766,11 +1774,8 @@ export class Engine extends IEngine { } }; - private onSessionRequest: EnginePrivate["onSessionRequest"] = async ( - topic, - payload, - attestation, - ) => { + private onSessionRequest: EnginePrivate["onSessionRequest"] = async (args) => { + const { topic, payload, attestation, encryptedId } = args; const { id, params } = payload; try { await this.isValidRequest({ topic, ...params }); @@ -1778,6 +1783,7 @@ export class Engine extends IEngine { const verifyContext = await this.getVerifyContext({ attestationId: attestation, hash: hashMessage(JSON.stringify(formatJsonRpcRequest("wc_sessionRequest", params, id))), + encryptedId, metadata: session.peer.metadata, }); const request = { @@ -1869,15 +1875,15 @@ export class Engine extends IEngine { }; private onSessionAuthenticateRequest: EnginePrivate["onSessionAuthenticateRequest"] = async ( - topic, - payload, - attestation, + args, ) => { + const { topic, payload, attestation, encryptedId } = args; try { const { requester, authPayload, expiryTimestamp } = payload.params; const verifyContext = await this.getVerifyContext({ attestationId: attestation, hash: hashMessage(JSON.stringify(payload)), + encryptedId, metadata: this.client.metadata, }); const pendingRequest = { @@ -2028,9 +2034,9 @@ export class Engine extends IEngine { const proposals = this.client.proposal.getAll(); const proposal = proposals.find((p) => p.pairingTopic === pairing.topic); if (!proposal) return; - this.onSessionProposeRequest( - pairing.topic, - formatJsonRpcRequest( + this.onSessionProposeRequest({ + topic: pairing.topic, + payload: formatJsonRpcRequest( "wc_sessionPropose", { requiredNamespaces: proposal.requiredNamespaces, @@ -2041,7 +2047,7 @@ export class Engine extends IEngine { }, proposal.id, ), - ); + }); }; // ---------- Validation Helpers ------------------------------------ // @@ -2428,9 +2434,10 @@ export class Engine extends IEngine { private getVerifyContext = async (params: { attestationId?: string; hash?: string; + encryptedId?: string; metadata: CoreTypes.Metadata; }) => { - const { attestationId, hash, metadata } = params; + const { attestationId, hash, encryptedId, metadata } = params; const context: Verify.Context = { verified: { verifyUrl: metadata.verifyUrl || VERIFY_SERVER, @@ -2443,6 +2450,7 @@ export class Engine extends IEngine { const result = await this.client.core.verify.resolve({ attestationId, hash, + encryptedId, verifyUrl: metadata.verifyUrl, }); if (result) { diff --git a/packages/types/src/core/verify.ts b/packages/types/src/core/verify.ts index ca13f5380..17a28e6ac 100644 --- a/packages/types/src/core/verify.ts +++ b/packages/types/src/core/verify.ts @@ -26,6 +26,7 @@ export abstract class IVerify { public abstract resolve(params: { attestationId?: string; hash?: string; + encryptedId?: string; verifyUrl?: string; }): Promise<{ origin: string; isScam?: boolean }>; } diff --git a/packages/types/src/sign-client/engine.ts b/packages/types/src/sign-client/engine.ts index c2f6c6986..5f1638b91 100644 --- a/packages/types/src/sign-client/engine.ts +++ b/packages/types/src/sign-client/engine.ts @@ -53,6 +53,7 @@ export declare namespace EngineTypes { topic: string; payload: T; attestation?: string; + encryptedId?: string; } interface ConnectParams { @@ -248,11 +249,12 @@ export interface EnginePrivate { cleanup(): Promise; - onSessionProposeRequest( - topic: string, - payload: JsonRpcRequest, - attestation?: string, - ): Promise; + onSessionProposeRequest(params: { + topic: string; + payload: JsonRpcRequest; + attestation?: string; + encryptedId?: string; + }): Promise; onSessionProposeResponse( topic: string, @@ -304,11 +306,12 @@ export interface EnginePrivate { payload: JsonRpcRequest, ): Promise; - onSessionRequest( - topic: string, - payload: JsonRpcRequest, - attestation?: string, - ): Promise; + onSessionRequest(params: { + topic: string; + payload: JsonRpcRequest; + attestation?: string; + encryptedId?: string; + }): Promise; onSessionRequestResponse( topic: string, @@ -320,11 +323,12 @@ export interface EnginePrivate { payload: JsonRpcRequest, ): Promise; - onSessionAuthenticateRequest( - topic: string, - payload: JsonRpcRequest, - attestation?: string, - ): Promise; + onSessionAuthenticateRequest(params: { + topic: string; + payload: JsonRpcRequest; + attestation?: string; + encryptedId?: string; + }): Promise; onSessionAuthenticateResponse( topic: string, From 5d9302739b61005c3075bea329ca2adc1239226e Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 23 Aug 2024 15:08:47 +0300 Subject: [PATCH 50/61] chore: avoid setting the same error twice --- packages/core/src/controllers/pairing.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/core/src/controllers/pairing.ts b/packages/core/src/controllers/pairing.ts index 28c0fd46b..cd7560735 100644 --- a/packages/core/src/controllers/pairing.ts +++ b/packages/core/src/controllers/pairing.ts @@ -122,12 +122,7 @@ export class Pairing implements IPairing { }, }); - try { - this.isValidPair(params, event); - } catch (error) { - event.setError(EVENT_CLIENT_PAIRING_ERRORS.malformed_pairing_uri); - throw error; - } + this.isValidPair(params, event); const { topic, symKey, relay, expiryTimestamp, methods } = parseUri(params.uri); From 38ca447dff5e3b5cbdaa140eea3d0c457714865f Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Mon, 26 Aug 2024 11:31:02 +0300 Subject: [PATCH 51/61] refactor: avoids fallbacking to v1 verify if jwt payload id doesn't match --- packages/core/src/controllers/verify.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index f788bbbac..4a324a0a6 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -114,10 +114,9 @@ export class Verify extends IVerify { if (attestationId) { const decoded = decodeJWT(attestationId) as unknown as { payload: JwkPayload }; - if (decoded.payload.id === encryptedId) { - const validation = await this.isValidJwtAttestation(attestationId); - if (validation) return validation; - } + if (decoded.payload.id !== encryptedId) return; + const validation = await this.isValidJwtAttestation(attestationId); + if (validation) return validation; } if (!hash) return; const verifyUrl = this.getVerifyUrl(params?.verifyUrl); From 47e3471d815aab083bd81e0ea4c75a0500e776ff Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Mon, 26 Aug 2024 09:12:53 -0700 Subject: [PATCH 52/61] chore: abort verify on iframe error --- packages/core/src/controllers/verify.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index 4a324a0a6..850b84dbd 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -79,6 +79,7 @@ export class Verify extends IVerify { const iframe = document.createElement("iframe"); iframe.src = src; iframe.style.display = "none"; + iframe.addEventListener("error", abortListener, { signal: this.abortController.signal }); const listener = (event: MessageEvent) => { if (!event.data) return; const data = JSON.parse(event.data); From 56fbd6648974e80d637629bef30c07cb5b540a39 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Mon, 26 Aug 2024 20:17:18 +0300 Subject: [PATCH 53/61] fix: reject when abort controller signals instead of throw an error --- packages/core/src/controllers/verify.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index 4a324a0a6..1032dba86 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -69,11 +69,11 @@ export class Verify extends IVerify { try { const document = getDocument() as Document; const abortTimeout = this.startAbortTimer(ONE_SECOND * 5); - const attestationJwt = await new Promise((resolve) => { + const attestationJwt = await new Promise(async (resolve, reject) => { const abortListener = () => { window.removeEventListener("message", listener); document.body.removeChild(iframe); - throw new Error("attestation aborted"); + reject("attestation aborted"); }; this.abortController.signal.addEventListener("abort", abortListener); const iframe = document.createElement("iframe"); From bc41e19c54d2d9d0a1a8f4badc193cb9c8413f1b Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Mon, 26 Aug 2024 20:19:51 +0300 Subject: [PATCH 54/61] chore: rm redundant await --- packages/core/src/controllers/verify.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index 9f266e79f..3f752ca3e 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -69,7 +69,7 @@ export class Verify extends IVerify { try { const document = getDocument() as Document; const abortTimeout = this.startAbortTimer(ONE_SECOND * 5); - const attestationJwt = await new Promise(async (resolve, reject) => { + const attestationJwt = await new Promise((resolve, reject) => { const abortListener = () => { window.removeEventListener("message", listener); document.body.removeChild(iframe); From 7aea2dbf3841aeba2baa64e05d8673aaa6f7bbdd Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Tue, 27 Aug 2024 14:32:14 +0300 Subject: [PATCH 55/61] chore: doesn't init verify api in dev mode --- packages/core/src/controllers/verify.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index 3f752ca3e..b1c90ea7e 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -51,6 +51,7 @@ export class Verify extends IVerify { } public init = async () => { + if (this.isDevEnv) return; this.publicKey = await this.store.getItem(this.storeKey); if (this.publicKey && toMiliseconds(this.publicKey?.expiresAt) < Date.now()) { this.logger.debug("verify v2 public key expired"); @@ -62,7 +63,7 @@ export class Verify extends IVerify { }; public register: IVerify["register"] = async (params) => { - if (!isBrowser()) return; + if (!isBrowser() || this.isDevEnv) return; const origin = window.location.origin; const { id, decryptedId } = params; const src = `${this.verifyUrlV3}/attestation?projectId=${this.core.projectId}&origin=${origin}&id=${id}&decryptedId=${decryptedId}`; From 69d2c5d6a81575ad67080c083171c1609219ad76 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Aug 2024 21:58:54 +0200 Subject: [PATCH 56/61] fix(deps): update dependency elliptic to v6.5.7 [security] (#5275) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 172 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 128 insertions(+), 44 deletions(-) diff --git a/package-lock.json b/package-lock.json index b8f79e30e..27f641058 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3638,6 +3638,25 @@ "hash.js": "1.1.7" } }, + "node_modules/@ethersproject/signing-key/node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/@ethersproject/signing-key/node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, "node_modules/@ethersproject/solidity": { "version": "5.6.1", "dev": true, @@ -10955,8 +10974,9 @@ "peer": true }, "node_modules/elliptic": { - "version": "6.5.4", - "license": "MIT", + "version": "6.5.7", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", + "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -10969,7 +10989,8 @@ }, "node_modules/elliptic/node_modules/bn.js": { "version": "4.12.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -12709,6 +12730,21 @@ "dev": true, "license": "MIT" }, + "node_modules/ethereum-test-network/node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/ethereum-test-network/node_modules/ethers": { "version": "5.3.1", "dev": true, @@ -13679,6 +13715,27 @@ "@ethersproject/strings": "^5.6.1" } }, + "node_modules/ethers/node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/ethers/node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, "node_modules/ethjs-unit": { "version": "0.1.6", "dev": true, @@ -27576,25 +27633,6 @@ "@types/lodash.isequal": "4.5.6" } }, - "packages/utils/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "packages/utils/node_modules/elliptic": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", - "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "packages/utils/node_modules/uint8arrays": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.0.tgz", @@ -30532,6 +30570,29 @@ "bn.js": "^5.2.1", "elliptic": "6.5.4", "hash.js": "1.1.7" + }, + "dependencies": { + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + } + } + } } }, "@ethersproject/solidity": { @@ -34162,30 +34223,11 @@ "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", - "elliptic": "^6.5.7", + "elliptic": "6.5.7", "query-string": "7.1.3", "uint8arrays": "3.1.0" }, "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "elliptic": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", - "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "uint8arrays": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.0.tgz", @@ -36308,7 +36350,9 @@ "peer": true }, "elliptic": { - "version": "6.5.4", + "version": "6.5.7", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", + "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", "requires": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -36320,7 +36364,9 @@ }, "dependencies": { "bn.js": { - "version": "4.12.0" + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } }, @@ -37395,6 +37441,21 @@ "version": "4.12.0", "dev": true }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "ethers": { "version": "5.3.1", "dev": true, @@ -38030,6 +38091,29 @@ "@ethersproject/properties": "^5.6.0", "@ethersproject/strings": "^5.6.1" } + }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } } } }, From dbbf7c24e48166a221222bfaaee7e636aa3089b5 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Wed, 28 Aug 2024 14:18:10 +0300 Subject: [PATCH 57/61] fix: fixes bug where relayer could go into reconnection loop when multiple disconnect events are emitted at the same time --- packages/core/src/controllers/relayer.ts | 15 +++++++++++---- packages/core/test/relayer.spec.ts | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/core/src/controllers/relayer.ts b/packages/core/src/controllers/relayer.ts index 5195314b3..d8b5980fe 100644 --- a/packages/core/src/controllers/relayer.ts +++ b/packages/core/src/controllers/relayer.ts @@ -93,6 +93,7 @@ export class Relayer extends IRelayer { * meaning if we don't receive a message in 30 seconds, the connection can be considered dead */ private heartBeatTimeout = toMiliseconds(THIRTY_SECONDS + ONE_SECOND); + private reconnectTimeout: NodeJS.Timeout | undefined; constructor(opts: RelayerOptions) { super(opts); @@ -295,9 +296,14 @@ export class Relayer extends IRelayer { this.provider.connect(), toMiliseconds(ONE_MINUTE), `Socket stalled when trying to connect to ${this.relayUrl}`, - ).catch((e) => { - reject(e); - }); + ) + .catch((e) => { + reject(e); + }) + .finally(() => { + clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = undefined; + }); this.subscriber.start().catch((error) => { this.logger.error(error); this.onDisconnectHandler(); @@ -536,7 +542,8 @@ export class Relayer extends IRelayer { this.events.emit(RELAYER_EVENTS.disconnect); this.connectionAttemptInProgress = false; if (this.transportExplicitlyClosed) return; - setTimeout(async () => { + if (this.reconnectTimeout) return; + this.reconnectTimeout = setTimeout(async () => { await this.transportOpen().catch((error) => this.logger.error(error)); }, toMiliseconds(RELAYER_RECONNECT_TIMEOUT)); } diff --git a/packages/core/test/relayer.spec.ts b/packages/core/test/relayer.spec.ts index 2bceebc06..50a04fc5b 100644 --- a/packages/core/test/relayer.spec.ts +++ b/packages/core/test/relayer.spec.ts @@ -283,6 +283,23 @@ describe("Relayer", () => { // the identifier should be the same expect(relayer.core.crypto.randomSessionIdentifier).to.eq(randomSessionIdentifier); }); + it("should connect once regardless of the number of disconnect events", async () => { + const disconnectsToEmit = 10; + let disconnectsReceived = 0; + let connectReceived = 0; + relayer.on(RELAYER_EVENTS.connect, () => { + connectReceived++; + }); + relayer.on(RELAYER_EVENTS.disconnect, () => { + disconnectsReceived++; + }); + await Promise.all( + Array.from(Array(disconnectsToEmit).keys()).map(() => relayer.onDisconnectHandler()), + ); + await throttle(1000); + expect(connectReceived).to.eq(1); + expect(disconnectsReceived).to.eq(disconnectsToEmit); + }); it("should close transport 10 seconds after init if NOT active", async () => { relayer = new Relayer({ From 3b2c6057bc6baa3be726af77974c54871a1d7685 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 30 Aug 2024 09:57:22 +0300 Subject: [PATCH 58/61] chore: prep for `2.15.2` release --- lerna.json | 2 +- package-lock.json | 100 +++++++++++----------- packages/core/package.json | 6 +- packages/core/src/constants/relayer.ts | 2 +- packages/react-native-compat/package.json | 2 +- packages/sign-client/package.json | 8 +- packages/types/package.json | 2 +- packages/utils/package.json | 4 +- packages/web3wallet/package.json | 10 +-- providers/ethereum-provider/package.json | 10 +-- providers/signer-connection/package.json | 8 +- providers/universal-provider/package.json | 8 +- 12 files changed, 81 insertions(+), 81 deletions(-) diff --git a/lerna.json b/lerna.json index 3280e1775..4138ff399 100644 --- a/lerna.json +++ b/lerna.json @@ -4,5 +4,5 @@ "packages/*", "providers/*" ], - "version": "2.15.1" + "version": "2.15.2" } diff --git a/package-lock.json b/package-lock.json index 27f641058..9acba3035 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27515,7 +27515,7 @@ }, "packages/core": { "name": "@walletconnect/core", - "version": "2.15.1", + "version": "2.15.2", "license": "Apache-2.0", "dependencies": { "@walletconnect/heartbeat": "1.2.2", @@ -27529,8 +27529,8 @@ "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1", + "@walletconnect/types": "2.15.2", + "@walletconnect/utils": "2.15.2", "events": "3.3.0", "lodash.isequal": "4.5.0", "uint8arrays": "3.1.0" @@ -27552,7 +27552,7 @@ }, "packages/react-native-compat": { "name": "@walletconnect/react-native-compat", - "version": "2.15.1", + "version": "2.15.2", "license": "Apache-2.0", "dependencies": { "events": "3.3.0", @@ -27573,17 +27573,17 @@ }, "packages/sign-client": { "name": "@walletconnect/sign-client", - "version": "2.15.1", + "version": "2.15.2", "license": "Apache-2.0", "dependencies": { - "@walletconnect/core": "2.15.1", + "@walletconnect/core": "2.15.2", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1", + "@walletconnect/types": "2.15.2", + "@walletconnect/utils": "2.15.2", "events": "3.3.0" }, "devDependencies": { @@ -27595,7 +27595,7 @@ }, "packages/types": { "name": "@walletconnect/types", - "version": "2.15.1", + "version": "2.15.2", "license": "Apache-2.0", "dependencies": { "@walletconnect/events": "1.0.1", @@ -27608,7 +27608,7 @@ }, "packages/utils": { "name": "@walletconnect/utils", - "version": "2.15.1", + "version": "2.15.2", "license": "Apache-2.0", "dependencies": { "@stablelib/chacha20poly1305": "1.0.1", @@ -27620,7 +27620,7 @@ "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.1", + "@walletconnect/types": "2.15.2", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", @@ -27643,17 +27643,17 @@ }, "packages/web3wallet": { "name": "@walletconnect/web3wallet", - "version": "1.14.1", + "version": "1.14.2", "license": "Apache-2.0", "dependencies": { "@walletconnect/auth-client": "2.1.2", - "@walletconnect/core": "2.15.1", + "@walletconnect/core": "2.15.2", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.15.1", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1" + "@walletconnect/sign-client": "2.15.2", + "@walletconnect/types": "2.15.2", + "@walletconnect/utils": "2.15.2" }, "devDependencies": { "@ethersproject/wallet": "5.7.0" @@ -27661,7 +27661,7 @@ }, "providers/ethereum-provider": { "name": "@walletconnect/ethereum-provider", - "version": "2.15.1", + "version": "2.15.2", "license": "Apache-2.0", "dependencies": { "@walletconnect/jsonrpc-http-connection": "1.0.8", @@ -27669,10 +27669,10 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/modal": "2.6.2", - "@walletconnect/sign-client": "2.15.1", - "@walletconnect/types": "2.15.1", - "@walletconnect/universal-provider": "2.15.1", - "@walletconnect/utils": "2.15.1", + "@walletconnect/sign-client": "2.15.2", + "@walletconnect/types": "2.15.2", + "@walletconnect/universal-provider": "2.15.2", + "@walletconnect/utils": "2.15.2", "events": "3.3.0" }, "devDependencies": { @@ -27693,14 +27693,14 @@ }, "providers/signer-connection": { "name": "@walletconnect/signer-connection", - "version": "2.15.1", + "version": "2.15.2", "license": "Apache-2.0", "dependencies": { "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/sign-client": "2.15.1", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1", + "@walletconnect/sign-client": "2.15.2", + "@walletconnect/types": "2.15.2", + "@walletconnect/utils": "2.15.2", "events": "3.3.0", "uint8arrays": "3.1.0" } @@ -27715,7 +27715,7 @@ }, "providers/universal-provider": { "name": "@walletconnect/universal-provider", - "version": "2.15.1", + "version": "2.15.2", "license": "Apache-2.0", "dependencies": { "@walletconnect/jsonrpc-http-connection": "1.0.8", @@ -27723,9 +27723,9 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.15.1", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1", + "@walletconnect/sign-client": "2.15.2", + "@walletconnect/types": "2.15.2", + "@walletconnect/utils": "2.15.2", "events": "3.3.0" }, "devDependencies": { @@ -33732,8 +33732,8 @@ "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1", + "@walletconnect/types": "2.15.2", + "@walletconnect/utils": "2.15.2", "events": "3.3.0", "lodash.isequal": "4.5.0", "uint8arrays": "3.1.0" @@ -33768,10 +33768,10 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/modal": "2.6.2", - "@walletconnect/sign-client": "2.15.1", - "@walletconnect/types": "2.15.1", - "@walletconnect/universal-provider": "2.15.1", - "@walletconnect/utils": "2.15.1", + "@walletconnect/sign-client": "2.15.2", + "@walletconnect/types": "2.15.2", + "@walletconnect/universal-provider": "2.15.2", + "@walletconnect/utils": "2.15.2", "ethereum-test-network": "0.1.6", "ethers": "5.6.9", "events": "3.3.0", @@ -33960,7 +33960,7 @@ "version": "file:packages/sign-client", "requires": { "@aws-sdk/client-cloudwatch": "3.450.0", - "@walletconnect/core": "2.15.1", + "@walletconnect/core": "2.15.2", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-provider": "1.0.14", @@ -33969,8 +33969,8 @@ "@walletconnect/logger": "2.1.2", "@walletconnect/relay-api": "1.0.11", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1", + "@walletconnect/types": "2.15.2", + "@walletconnect/utils": "2.15.2", "events": "3.3.0" } }, @@ -33979,9 +33979,9 @@ "requires": { "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/sign-client": "2.15.1", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1", + "@walletconnect/sign-client": "2.15.2", + "@walletconnect/types": "2.15.2", + "@walletconnect/utils": "2.15.2", "events": "3.3.0", "uint8arrays": "3.1.0" }, @@ -34026,9 +34026,9 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.15.1", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1", + "@walletconnect/sign-client": "2.15.2", + "@walletconnect/types": "2.15.2", + "@walletconnect/utils": "2.15.2", "cosmos-wallet": "1.2.0", "ethereum-test-network": "0.1.6", "ethers": "5.7.0", @@ -34219,11 +34219,11 @@ "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.1", + "@walletconnect/types": "2.15.2", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", - "elliptic": "6.5.7", + "elliptic": "^6.5.7", "query-string": "7.1.3", "uint8arrays": "3.1.0" }, @@ -34243,13 +34243,13 @@ "requires": { "@ethersproject/wallet": "5.7.0", "@walletconnect/auth-client": "2.1.2", - "@walletconnect/core": "2.15.1", + "@walletconnect/core": "2.15.2", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.15.1", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1" + "@walletconnect/sign-client": "2.15.2", + "@walletconnect/types": "2.15.2", + "@walletconnect/utils": "2.15.2" } }, "@walletconnect/window-getters": { diff --git a/packages/core/package.json b/packages/core/package.json index 7dea8c5b4..e90c7e866 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/core", "description": "Core for WalletConnect Protocol", - "version": "2.15.1", + "version": "2.15.2", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", @@ -42,8 +42,8 @@ "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1", + "@walletconnect/types": "2.15.2", + "@walletconnect/utils": "2.15.2", "events": "3.3.0", "lodash.isequal": "4.5.0", "uint8arrays": "3.1.0" diff --git a/packages/core/src/constants/relayer.ts b/packages/core/src/constants/relayer.ts index d0e4b5b51..9a80e32c1 100644 --- a/packages/core/src/constants/relayer.ts +++ b/packages/core/src/constants/relayer.ts @@ -34,7 +34,7 @@ export const RELAYER_STORAGE_OPTIONS = { // Updated automatically via `new-version` npm script. -export const RELAYER_SDK_VERSION = "2.15.1"; +export const RELAYER_SDK_VERSION = "2.15.2"; // delay to wait before closing the transport connection after init if not active export const RELAYER_TRANSPORT_CUTOFF = 10_000; diff --git a/packages/react-native-compat/package.json b/packages/react-native-compat/package.json index b1908fc9e..6b1db6a5b 100644 --- a/packages/react-native-compat/package.json +++ b/packages/react-native-compat/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/react-native-compat", "description": "Shims for WalletConnect Protocol in React Native Projects", - "version": "2.15.1", + "version": "2.15.2", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", diff --git a/packages/sign-client/package.json b/packages/sign-client/package.json index d2d22d372..fe7cb99b5 100644 --- a/packages/sign-client/package.json +++ b/packages/sign-client/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/sign-client", "description": "Sign Client for WalletConnect Protocol", - "version": "2.15.1", + "version": "2.15.2", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", @@ -38,14 +38,14 @@ "prettier": "prettier --check '{src,test}/**/*.{js,ts,jsx,tsx}'" }, "dependencies": { - "@walletconnect/core": "2.15.1", + "@walletconnect/core": "2.15.2", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1", + "@walletconnect/types": "2.15.2", + "@walletconnect/utils": "2.15.2", "events": "3.3.0" }, "devDependencies": { diff --git a/packages/types/package.json b/packages/types/package.json index 73eba0027..fb77137d7 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/types", "description": "Typings for WalletConnect Protocol", - "version": "2.15.1", + "version": "2.15.2", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", diff --git a/packages/utils/package.json b/packages/utils/package.json index 8e6be8d4b..d638eebde 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/utils", "description": "Utilities for WalletConnect Protocol", - "version": "2.15.1", + "version": "2.15.2", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", @@ -40,7 +40,7 @@ "@walletconnect/relay-auth": "1.0.4", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.15.1", + "@walletconnect/types": "2.15.2", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", diff --git a/packages/web3wallet/package.json b/packages/web3wallet/package.json index 537f1d21b..0a51a9d47 100644 --- a/packages/web3wallet/package.json +++ b/packages/web3wallet/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/web3wallet", "description": "Web3Wallet for WalletConnect Protocol", - "version": "1.14.1", + "version": "1.14.2", "private": true, "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", @@ -30,13 +30,13 @@ }, "dependencies": { "@walletconnect/auth-client": "2.1.2", - "@walletconnect/core": "2.15.1", + "@walletconnect/core": "2.15.2", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.15.1", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1" + "@walletconnect/sign-client": "2.15.2", + "@walletconnect/types": "2.15.2", + "@walletconnect/utils": "2.15.2" }, "devDependencies": { "@ethersproject/wallet": "5.7.0" diff --git a/providers/ethereum-provider/package.json b/providers/ethereum-provider/package.json index 5a2d0ea73..99d1f4b8c 100644 --- a/providers/ethereum-provider/package.json +++ b/providers/ethereum-provider/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/ethereum-provider", "description": "Ethereum Provider for WalletConnect Protocol", - "version": "2.15.1", + "version": "2.15.2", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "repository": { @@ -48,10 +48,10 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/modal": "2.6.2", - "@walletconnect/sign-client": "2.15.1", - "@walletconnect/types": "2.15.1", - "@walletconnect/universal-provider": "2.15.1", - "@walletconnect/utils": "2.15.1", + "@walletconnect/sign-client": "2.15.2", + "@walletconnect/types": "2.15.2", + "@walletconnect/universal-provider": "2.15.2", + "@walletconnect/utils": "2.15.2", "events": "3.3.0" }, "devDependencies": { diff --git a/providers/signer-connection/package.json b/providers/signer-connection/package.json index f4cd67674..55ed5467a 100644 --- a/providers/signer-connection/package.json +++ b/providers/signer-connection/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/signer-connection", "description": "Signer Connection for WalletConnect Protocol", - "version": "2.15.1", + "version": "2.15.2", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "license": "Apache-2.0", @@ -39,9 +39,9 @@ "dependencies": { "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/sign-client": "2.15.1", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1", + "@walletconnect/sign-client": "2.15.2", + "@walletconnect/types": "2.15.2", + "@walletconnect/utils": "2.15.2", "events": "3.3.0", "uint8arrays": "3.1.0" } diff --git a/providers/universal-provider/package.json b/providers/universal-provider/package.json index d942c6f93..1d4ec2bef 100644 --- a/providers/universal-provider/package.json +++ b/providers/universal-provider/package.json @@ -1,7 +1,7 @@ { "name": "@walletconnect/universal-provider", "description": "Universal Provider for WalletConnect Protocol", - "version": "2.15.1", + "version": "2.15.2", "author": "WalletConnect, Inc. ", "homepage": "https://github.com/walletconnect/walletconnect-monorepo/", "repository": { @@ -45,9 +45,9 @@ "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.15.1", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1", + "@walletconnect/sign-client": "2.15.2", + "@walletconnect/types": "2.15.2", + "@walletconnect/utils": "2.15.2", "events": "3.3.0" }, "devDependencies": { From cf8de56d6cd765759b4b215a6a748e0f3cb1138a Mon Sep 17 00:00:00 2001 From: Alfreedom <00tango.bromine@icloud.com> Date: Tue, 3 Sep 2024 11:38:00 +0200 Subject: [PATCH 59/61] Fix getVerifyContext when onSessionAuthenticateRequest --- packages/sign-client/src/controllers/engine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index 5adc519fe..6ea0b45ce 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -2021,7 +2021,7 @@ export class Engine extends IEngine { attestationId: attestation, hash: hashMessage(JSON.stringify(payload)), encryptedId, - metadata: this.client.metadata, + metadata: requester.metadata, }); const pendingRequest = { requester, From b15dbb65214404dd4483215a5b2cc341165223df Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Wed, 4 Sep 2024 12:33:58 +0300 Subject: [PATCH 60/61] fix: enforces origin url to be verified to produce verify context --- packages/core/src/controllers/verify.ts | 9 ++++++++- packages/utils/src/crypto.ts | 5 +---- packages/utils/test/crypto.spec.ts | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index b1c90ea7e..1ec135f62 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -118,7 +118,13 @@ export class Verify extends IVerify { const decoded = decodeJWT(attestationId) as unknown as { payload: JwkPayload }; if (decoded.payload.id !== encryptedId) return; const validation = await this.isValidJwtAttestation(attestationId); - if (validation) return validation; + if (validation) { + if (!validation.isVerified) { + this.logger.warn("resolve: jwt attestation: origin url not verified"); + return; + } + return validation; + } } if (!hash) return; const verifyUrl = this.getVerifyUrl(params?.verifyUrl); @@ -243,6 +249,7 @@ export class Verify extends IVerify { return { origin: validation.payload.origin, isScam: validation.payload.isScam, + isVerified: validation.payload.isVerified, }; }; } diff --git a/packages/utils/src/crypto.ts b/packages/utils/src/crypto.ts index 2a5a00098..cbc61f478 100644 --- a/packages/utils/src/crypto.ts +++ b/packages/utils/src/crypto.ts @@ -230,8 +230,5 @@ export function verifyP256Jwt(token: string, keyData: P256KeyDataType) { throw new Error("Invalid signature"); } const data = decodeJWT(token) as unknown as { payload: T }; - return { - ...data.payload, - isVerified: isValid, - }; + return data.payload; } diff --git a/packages/utils/test/crypto.spec.ts b/packages/utils/test/crypto.spec.ts index b9941b795..30e71e8d4 100644 --- a/packages/utils/test/crypto.spec.ts +++ b/packages/utils/test/crypto.spec.ts @@ -135,7 +135,7 @@ describe("Crypto", () => { console.log("result", result); expect(result).to.exist; expect(result).to.exist; - expect(result.isVerified).to.be.true; + expect(result.isVerified).to.be.false; expect(result.exp).to.exist; expect(result.origin).to.exist; expect(result.isScam).to.be.null; From cdca21e306b474d9eebb3356e2d148559fb7667e Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Thu, 5 Sep 2024 11:10:04 +0300 Subject: [PATCH 61/61] refactor: doesn't fetch public key on init --- packages/core/src/controllers/verify.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/core/src/controllers/verify.ts b/packages/core/src/controllers/verify.ts index 1ec135f62..e748be805 100644 --- a/packages/core/src/controllers/verify.ts +++ b/packages/core/src/controllers/verify.ts @@ -57,9 +57,6 @@ export class Verify extends IVerify { this.logger.debug("verify v2 public key expired"); await this.removePublicKey(); } - if (!this.publicKey) { - await this.fetchAndPersistPublicKey(); - } }; public register: IVerify["register"] = async (params) => {