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..3f752ca3e 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, @@ -10,7 +11,7 @@ import { TRUSTED_VERIFY_URLS, VERIFY_CONTEXT, VERIFY_SERVER, - VERIFY_SERVER_V2, + VERIFY_SERVER_V3, } from "../constants"; import { IKeyValueStorage } from "@walletconnect/keyvaluestorage"; @@ -29,7 +30,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,40 +63,30 @@ 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 attestationJwt = await new Promise((resolve) => { + const abortTimeout = this.startAbortTimer(ONE_SECOND * 5); + const attestationJwt = await new Promise((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, { - signal: this.abortController.signal, - }); + this.abortController.signal.addEventListener("abort", abortListener); const iframe = document.createElement("iframe"); - iframe.srcdoc = src; + 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); 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); @@ -116,14 +107,15 @@ 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 decoded = decodeJWT(attestationId) as unknown as { payload: JwkPayload }; + if (decoded.payload.id !== encryptedId) return; const validation = await this.isValidJwtAttestation(attestationId); if (validation) return validation; } @@ -165,9 +157,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); 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,