Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Verify V3 #5285

Merged
merged 8 commits into from
Aug 27, 2024
2 changes: 1 addition & 1 deletion packages/core/src/constants/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
46 changes: 19 additions & 27 deletions packages/core/src/controllers/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ 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,
CORE_VERSION,
TRUSTED_VERIFY_URLS,
VERIFY_CONTEXT,
VERIFY_SERVER,
VERIFY_SERVER_V2,
VERIFY_SERVER_V3,
} from "../constants";
import { IKeyValueStorage } from "@walletconnect/keyvaluestorage";

Expand All @@ -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;
Expand Down Expand Up @@ -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);
chris13524 marked this conversation as resolved.
Show resolved Hide resolved
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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down
54 changes: 31 additions & 23 deletions packages/sign-client/src/controllers/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down Expand Up @@ -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 })) {
Expand All @@ -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":
Expand All @@ -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}`);
}
Expand Down Expand Up @@ -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 });
Expand All @@ -1470,6 +1477,7 @@ export class Engine extends IEngine {
const verifyContext = await this.getVerifyContext({
attestationId: attestation,
hash: hashMessage(JSON.stringify(payload)),
encryptedId,
Comment on lines 1478 to +1480
Copy link
Member Author

@chris13524 chris13524 Aug 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ganchoradkov isn't one of these (attestationId, hash, encryptedId) redundant with the other?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope because

  • attestationId = the jwt
  • hash = the unecnrypted payload used for verify v1
  • encryptedId = hash of the encrypted message used for verify v2

encryptedId is used for the check against jwt.payload.id

metadata: proposal.proposer.metadata,
});
this.client.events.emit("session_proposal", { id, params: proposal, verifyContext });
Expand Down Expand Up @@ -1766,18 +1774,16 @@ 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 });
const session = this.client.session.get(topic);
const verifyContext = await this.getVerifyContext({
attestationId: attestation,
hash: hashMessage(JSON.stringify(formatJsonRpcRequest("wc_sessionRequest", params, id))),
encryptedId,
metadata: session.peer.metadata,
});
const request = {
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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,
Expand All @@ -2041,7 +2047,7 @@ export class Engine extends IEngine {
},
proposal.id,
),
);
});
};

// ---------- Validation Helpers ------------------------------------ //
Expand Down Expand Up @@ -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,
Expand All @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/core/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }>;
}
34 changes: 19 additions & 15 deletions packages/types/src/sign-client/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export declare namespace EngineTypes {
topic: string;
payload: T;
attestation?: string;
encryptedId?: string;
}

interface ConnectParams {
Expand Down Expand Up @@ -248,11 +249,12 @@ export interface EnginePrivate {

cleanup(): Promise<void>;

onSessionProposeRequest(
topic: string,
payload: JsonRpcRequest<JsonRpcTypes.RequestParams["wc_sessionPropose"]>,
attestation?: string,
): Promise<void>;
onSessionProposeRequest(params: {
topic: string;
payload: JsonRpcRequest<JsonRpcTypes.RequestParams["wc_sessionPropose"]>;
attestation?: string;
encryptedId?: string;
}): Promise<void>;

onSessionProposeResponse(
topic: string,
Expand Down Expand Up @@ -304,11 +306,12 @@ export interface EnginePrivate {
payload: JsonRpcRequest<JsonRpcTypes.RequestParams["wc_sessionDelete"]>,
): Promise<void>;

onSessionRequest(
topic: string,
payload: JsonRpcRequest<JsonRpcTypes.RequestParams["wc_sessionRequest"]>,
attestation?: string,
): Promise<void>;
onSessionRequest(params: {
topic: string;
payload: JsonRpcRequest<JsonRpcTypes.RequestParams["wc_sessionRequest"]>;
attestation?: string;
encryptedId?: string;
}): Promise<void>;

onSessionRequestResponse(
topic: string,
Expand All @@ -320,11 +323,12 @@ export interface EnginePrivate {
payload: JsonRpcRequest<JsonRpcTypes.RequestParams["wc_sessionEvent"]>,
): Promise<void>;

onSessionAuthenticateRequest(
topic: string,
payload: JsonRpcRequest<JsonRpcTypes.RequestParams["wc_sessionAuthenticate"]>,
attestation?: string,
): Promise<void>;
onSessionAuthenticateRequest(params: {
topic: string;
payload: JsonRpcRequest<JsonRpcTypes.RequestParams["wc_sessionAuthenticate"]>;
attestation?: string;
encryptedId?: string;
}): Promise<void>;

onSessionAuthenticateResponse(
topic: string,
Expand Down