From 99f349d32fc15955c04c5be519cfef226a5e80c5 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 22 Feb 2024 01:35:23 +0300 Subject: [PATCH 01/13] [Smart Wallet] Always force deploy on sign/auth, remove legacy signing, always use 1271 --- .changeset/good-ways-listen.md | 6 ++ .../unity-js-bridge/src/thirdweb-bridge.ts | 1 - .../src/evm/connectors/smart-wallet/index.ts | 6 +- .../smart-wallet/lib/erc4337-signer.ts | 24 +++--- .../src/evm/connectors/smart-wallet/types.ts | 2 - .../wallets/src/evm/wallets/smart-wallet.ts | 75 +++++++------------ 6 files changed, 49 insertions(+), 65 deletions(-) create mode 100644 .changeset/good-ways-listen.md diff --git a/.changeset/good-ways-listen.md b/.changeset/good-ways-listen.md new file mode 100644 index 00000000000..e2fbfa62e83 --- /dev/null +++ b/.changeset/good-ways-listen.md @@ -0,0 +1,6 @@ +--- +"@thirdweb-dev/unity-js-bridge": minor +"@thirdweb-dev/wallets": minor +--- + +[Smart Wallet] Always force deploy on sign/auth, remove legacy signing, always use 1271 diff --git a/packages/unity-js-bridge/src/thirdweb-bridge.ts b/packages/unity-js-bridge/src/thirdweb-bridge.ts index 36395b0d730..1789b447bbb 100644 --- a/packages/unity-js-bridge/src/thirdweb-bridge.ts +++ b/packages/unity-js-bridge/src/thirdweb-bridge.ts @@ -244,7 +244,6 @@ class ThirdwebBridge implements TWBridge { paymasterUrl: sdkOptions.smartWalletConfig?.paymasterUrl, // paymasterAPI: sdkOptions.smartWalletConfig?.paymasterAPI, entryPointAddress: sdkOptions.smartWalletConfig?.entryPointAddress, - deployOnSign: sdkOptions.smartWalletConfig?.deployOnSign, }; walletInstance = new SmartWallet(config); break; diff --git a/packages/wallets/src/evm/connectors/smart-wallet/index.ts b/packages/wallets/src/evm/connectors/smart-wallet/index.ts index 120a6ee655d..5466799ad31 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/index.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/index.ts @@ -54,7 +54,6 @@ export class SmartWalletConnector extends Connector { this.config.paymasterUrl || `https://${this.chainId}.bundler.thirdweb.com/v2`; const entryPointAddress = config.entryPointAddress || ENTRYPOINT_ADDRESS; - const deployOnSign = config.deployOnSign ?? true; const localSigner = await params.personalWallet.getSigner(); const providerConfig: ProviderConfig = { chain: config.chain, @@ -70,7 +69,6 @@ export class SmartWalletConnector extends Connector { this.config.secretKey, ), gasless: config.gasless, - deployOnSign: deployOnSign, factoryAddress: config.factoryAddress, accountAddress: params.accountAddress, factoryInfo: config.factoryInfo || this.defaultFactoryInfo(), @@ -316,8 +314,8 @@ export class SmartWalletConnector extends Connector { value: await transaction.getValue(), gasLimit: await transaction.getOverrides().gasLimit, maxFeePerGas: await transaction.getOverrides().maxFeePerGas, - maxPriorityFeePerGas: await transaction.getOverrides() - .maxPriorityFeePerGas, + maxPriorityFeePerGas: + await transaction.getOverrides().maxPriorityFeePerGas, nonce: await transaction.getOverrides().nonce, }, options, diff --git a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts index 8492b92e1ed..fd2ed7a98dc 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts @@ -139,18 +139,18 @@ Code: ${errorCode}`; } async signMessage(message: Bytes | string): Promise { - const isNotDeployed = await this.smartAccountAPI.checkAccountPhantom(); - if (isNotDeployed && this.config.deployOnSign) { - console.log( - "Account contract not deployed yet. Deploying account before signing message", - ); - const tx = await this.sendTransaction({ - to: await this.getAddress(), - data: "0x", - }); - await tx.wait(); - } - + const isNotDeployed = await this.smartAccountAPI.checkAccountPhantom(); + if (isNotDeployed) { + console.log( + "Account contract not deployed yet. Deploying account before signing message", + ); + const tx = await this.sendTransaction({ + to: await this.getAddress(), + data: "0x", + }); + await tx.wait(); + } + return await this.originalSigner.signMessage(message); } diff --git a/packages/wallets/src/evm/connectors/smart-wallet/types.ts b/packages/wallets/src/evm/connectors/smart-wallet/types.ts index 9e0a0981f32..1acf4a26355 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/types.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/types.ts @@ -22,7 +22,6 @@ export type SmartWalletConfig = { paymasterUrl?: string; paymasterAPI?: PaymasterAPI; entryPointAddress?: string; - deployOnSign?: boolean; } & ContractInfoInput & WalletConnectReceiverConfig; @@ -43,7 +42,6 @@ export interface ProviderConfig extends ContractInfo { accountAddress?: string; paymasterAPI: PaymasterAPI; gasless: boolean; - deployOnSign?: boolean; } export type ContractInfoInput = { diff --git a/packages/wallets/src/evm/wallets/smart-wallet.ts b/packages/wallets/src/evm/wallets/smart-wallet.ts index 29aa5608665..2a771d837d1 100644 --- a/packages/wallets/src/evm/wallets/smart-wallet.ts +++ b/packages/wallets/src/evm/wallets/smart-wallet.ts @@ -246,10 +246,10 @@ export class SmartWallet extends AbstractClientWallet< * The entrypoint contract address. Uses v0.6 by default. * * Must be a `string`. - * + * * #### deployOnSign * Whether to deploy the smart wallet when the user signs a message. Defaults to true. - * + * * Must be a `boolean`. * * #### chains @@ -328,54 +328,37 @@ export class SmartWallet extends AbstractClientWallet< const chainId = await erc4337Signer.getChainId(); const address = await connector.getAddress(); - /** - * We first try to sign the EIP-712 typed data i.e. the message mixed with the smart wallet's domain separator. - * If this fails, we fallback to the legacy signing method. - */ - try { - const result = await signTypedDataInternal( - erc4337Signer, - { - name: "Account", - version: "1", - chainId, - verifyingContract: address, - }, - { AccountMessage: [{ name: "message", type: "bytes" }] }, - { - message: utils.defaultAbiCoder.encode( - ["bytes32"], - [utils.hashMessage(message)], - ), - }, - ); - - const isValid = await checkContractWalletSignature( - message as string, - result.signature, - address, + const result = await signTypedDataInternal( + erc4337Signer, + { + name: "Account", + version: "1", chainId, + verifyingContract: address, + }, + { AccountMessage: [{ name: "message", type: "bytes" }] }, + { + message: utils.defaultAbiCoder.encode( + ["bytes32"], + [utils.hashMessage(message)], + ), + }, + ); + + const isValid = await checkContractWalletSignature( + message as string, + result.signature, + address, + chainId, + ); + + if (!isValid) { + throw new Error( + "Unable to verify signature on smart account, please make sure the smart account is deployed and the signature is valid.", ); - - if (!isValid) { - throw new Error("Invalid signature"); - } - - return result.signature; - } catch { - return await this.signMessageLegacy(erc4337Signer, message); } - } - /** - * This is only for for legacy EIP-1271 signature verification - * Sign a message and return the signature - */ - private async signMessageLegacy( - signer: Signer, - message: Bytes | string, - ): Promise { - return await signer.signMessage(message); + return result.signature; } /** From e3efab23486a35c3053a734f5eadd350ab3a675d Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 22 Feb 2024 03:06:56 +0300 Subject: [PATCH 02/13] bring back fallback and use upcoming typehash --- packages/wallets/src/evm/wallets/abstract.ts | 5 +- .../wallets/src/evm/wallets/smart-wallet.ts | 69 +++++++++++-------- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/packages/wallets/src/evm/wallets/abstract.ts b/packages/wallets/src/evm/wallets/abstract.ts index 5a47087dfd2..ea474954e55 100644 --- a/packages/wallets/src/evm/wallets/abstract.ts +++ b/packages/wallets/src/evm/wallets/abstract.ts @@ -45,7 +45,7 @@ const EIP1271_ABI = [ const EIP1271_MAGICVALUE = "0x1626ba7e"; export async function checkContractWalletSignature( - message: string, + hash: string, signature: string, address: string, chainId: number, @@ -66,9 +66,8 @@ export async function checkContractWalletSignature( skipFetchSetup: _skipFetchSetup, }); const walletContract = new Contract(address, EIP1271_ABI, provider); - const _hashMessage = utils.hashMessage(message); try { - const res = await walletContract.isValidSignature(_hashMessage, signature); + const res = await walletContract.isValidSignature(hash, signature); return res === EIP1271_MAGICVALUE; } catch { return false; diff --git a/packages/wallets/src/evm/wallets/smart-wallet.ts b/packages/wallets/src/evm/wallets/smart-wallet.ts index 2a771d837d1..d09a8e04a6e 100644 --- a/packages/wallets/src/evm/wallets/smart-wallet.ts +++ b/packages/wallets/src/evm/wallets/smart-wallet.ts @@ -328,37 +328,52 @@ export class SmartWallet extends AbstractClientWallet< const chainId = await erc4337Signer.getChainId(); const address = await connector.getAddress(); - const result = await signTypedDataInternal( - erc4337Signer, - { - name: "Account", - version: "1", + /** + * We first try to sign the EIP-712 typed data i.e. the message mixed with the smart wallet's domain separator. + * If this fails, we fallback to the legacy signing method. + */ + try { + const hash = utils.hashMessage(message); + const result = await signTypedDataInternal( + erc4337Signer, + { + name: "Account", + version: "1", + chainId, + verifyingContract: address, + }, + { AccountMessage: [{ name: "hash", type: "bytes32" }] }, + { + message: utils.defaultAbiCoder.encode(["bytes32"], [hash]), + }, + ); + + const isValid = await checkContractWalletSignature( + hash, + result.signature, + address, chainId, - verifyingContract: address, - }, - { AccountMessage: [{ name: "message", type: "bytes" }] }, - { - message: utils.defaultAbiCoder.encode( - ["bytes32"], - [utils.hashMessage(message)], - ), - }, - ); - - const isValid = await checkContractWalletSignature( - message as string, - result.signature, - address, - chainId, - ); - - if (!isValid) { - throw new Error( - "Unable to verify signature on smart account, please make sure the smart account is deployed and the signature is valid.", ); + + if (!isValid) { + throw new Error("Invalid signature"); + } + + return result.signature; + } catch { + return await this.signMessageLegacy(erc4337Signer, message); } + } - return result.signature; + /** + * This is only for for legacy EIP-1271 signature verification + * Sign a message and return the signature + */ + private async signMessageLegacy( + signer: Signer, + message: Bytes | string, + ): Promise { + return await signer.signMessage(message); } /** From 03c8a37a99e940390e94b464efa33e353d276086 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Sat, 24 Feb 2024 03:49:04 +0300 Subject: [PATCH 03/13] move signing from smart-wallet to erc4337-signer --- .../smart-wallet/lib/erc4337-signer.ts | 80 ++++++++++++++++++- packages/wallets/src/evm/wallets/abstract.ts | 2 +- .../wallets/src/evm/wallets/smart-wallet.ts | 61 -------------- 3 files changed, 78 insertions(+), 65 deletions(-) diff --git a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts index fd2ed7a98dc..02d10aeb828 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts @@ -1,4 +1,4 @@ -import { ethers, providers, utils } from "ethers"; +import { Contract, ethers, providers, utils } from "ethers"; import { Bytes, Signer } from "ethers"; import { BaseAccountAPI } from "./base-api"; @@ -6,6 +6,11 @@ import type { ERC4337EthersProvider } from "./erc4337-provider"; import { HttpRpcClient } from "./http-rpc-client"; import { hexlifyUserOp, randomNonce } from "./utils"; import { ProviderConfig, UserOpOptions } from "../types"; +import { signTypedDataInternal } from "@thirdweb-dev/sdk"; +import { + checkContractWalletSignature, + chainIdToThirdwebRpc, +} from "../../../wallets/abstract"; export class ERC4337EthersSigner extends Signer { config: ProviderConfig; @@ -138,7 +143,11 @@ Code: ${errorCode}`; return this.address as string; } - async signMessage(message: Bytes | string): Promise { + /** + * Sign a message and return the signature + */ + public async signMessage(message: Bytes | string): Promise { + // Deploy smart wallet if needed const isNotDeployed = await this.smartAccountAPI.checkAccountPhantom(); if (isNotDeployed) { console.log( @@ -151,7 +160,72 @@ Code: ${errorCode}`; await tx.wait(); } - return await this.originalSigner.signMessage(message); + const chainId = await this.getChainId(); + const address = await this.getAddress(); + + /** + * We first try to sign the EIP-712 typed data i.e. the message mixed with the smart wallet's domain separator. + * If this fails, we fallback to the legacy signing method. + */ + try { + const provider = new providers.JsonRpcProvider({ + url: chainIdToThirdwebRpc(chainId), + skipFetchSetup: false, + }); + const walletContract = new Contract( + address, + "function getMessageHash(bytes memory message) public view returns (bytes32)", + provider, + ); + + // if this doesn't fail, it's a post 1271 + typehash account + await walletContract.getMessageHash(message); + + console.log( + "Signing with EIP-712 typed data and verifying with EIP-1271...", + ); + + const hash = utils.hashMessage(message); + const result = await signTypedDataInternal( + this, + { + name: "Account", + version: "1", + chainId, + verifyingContract: address, + }, + { AccountMessage: [{ name: "message", type: "bytes32" }] }, + { + message: utils.defaultAbiCoder.encode(["bytes32"], [hash]), + }, + ); + + const isValid = await checkContractWalletSignature( + hash, + result.signature, + address, + chainId, + ); + + if (!isValid) { + throw new Error("Invalid signature"); + } + + return result.signature; + } catch { + return await this.signMessageLegacy(this, message); + } + } + + /** + * This is only for for legacy EIP-1271 signature verification + * Sign a message and return the signature + */ + private async signMessageLegacy( + signer: Signer, + message: Bytes | string, + ): Promise { + return await signer.signMessage(message); } async signTransaction( diff --git a/packages/wallets/src/evm/wallets/abstract.ts b/packages/wallets/src/evm/wallets/abstract.ts index ea474954e55..8dc8b588fec 100644 --- a/packages/wallets/src/evm/wallets/abstract.ts +++ b/packages/wallets/src/evm/wallets/abstract.ts @@ -15,7 +15,7 @@ import { import { createErc20 } from "../utils/currency"; // TODO improve this -function chainIdToThirdwebRpc(chainId: number, clientId?: string) { +export function chainIdToThirdwebRpc(chainId: number, clientId?: string) { return `https://${chainId}.rpc.thirdweb.com${clientId ? `/${clientId}` : ""}${ typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis ? `?bundleId=${(globalThis as any).APP_BUNDLE_ID as string}` diff --git a/packages/wallets/src/evm/wallets/smart-wallet.ts b/packages/wallets/src/evm/wallets/smart-wallet.ts index d09a8e04a6e..ab6ebfce4e5 100644 --- a/packages/wallets/src/evm/wallets/smart-wallet.ts +++ b/packages/wallets/src/evm/wallets/smart-wallet.ts @@ -1,5 +1,4 @@ import { AbstractClientWallet, WalletOptions } from "./base"; -import { checkContractWalletSignature } from "./abstract"; import type { ConnectParams } from "../interfaces/connector"; import type { SmartWalletConfig, @@ -316,66 +315,6 @@ export class SmartWallet extends AbstractClientWallet< return this.connector?.personalWallet; } - /** - * Sign a message and return the signature - */ - public async signMessage(message: Bytes | string): Promise { - // Deploy smart wallet if needed - const connector = await this.getConnector(); - await connector.deployIfNeeded(); - - const erc4337Signer = await this.getSigner(); - const chainId = await erc4337Signer.getChainId(); - const address = await connector.getAddress(); - - /** - * We first try to sign the EIP-712 typed data i.e. the message mixed with the smart wallet's domain separator. - * If this fails, we fallback to the legacy signing method. - */ - try { - const hash = utils.hashMessage(message); - const result = await signTypedDataInternal( - erc4337Signer, - { - name: "Account", - version: "1", - chainId, - verifyingContract: address, - }, - { AccountMessage: [{ name: "hash", type: "bytes32" }] }, - { - message: utils.defaultAbiCoder.encode(["bytes32"], [hash]), - }, - ); - - const isValid = await checkContractWalletSignature( - hash, - result.signature, - address, - chainId, - ); - - if (!isValid) { - throw new Error("Invalid signature"); - } - - return result.signature; - } catch { - return await this.signMessageLegacy(erc4337Signer, message); - } - } - - /** - * This is only for for legacy EIP-1271 signature verification - * Sign a message and return the signature - */ - private async signMessageLegacy( - signer: Signer, - message: Bytes | string, - ): Promise { - return await signer.signMessage(message); - } - /** * Check whether the connected signer can execute a given transaction using the smart wallet. * @param transaction - The transaction to execute using the smart wallet. From 1a03b33650c3ec5b55497656c8b17cc241f0f191 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Mon, 26 Feb 2024 18:19:20 +0300 Subject: [PATCH 04/13] Update erc4337-signer.ts --- .../src/evm/connectors/smart-wallet/lib/erc4337-signer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts index 02d10aeb828..a907edd6635 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts @@ -194,7 +194,7 @@ Code: ${errorCode}`; chainId, verifyingContract: address, }, - { AccountMessage: [{ name: "message", type: "bytes32" }] }, + { AccountMessage: [{ name: "message", type: "bytes" }] }, { message: utils.defaultAbiCoder.encode(["bytes32"], [hash]), }, From 058e0e00ebb3f7c55667f0ed83ec1d94817e0db9 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Mon, 26 Feb 2024 18:32:12 +0300 Subject: [PATCH 05/13] update getMessageHash sig --- .../evm/connectors/smart-wallet/lib/erc4337-signer.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts index a907edd6635..9f630c007b2 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts @@ -169,23 +169,24 @@ Code: ${errorCode}`; */ try { const provider = new providers.JsonRpcProvider({ - url: chainIdToThirdwebRpc(chainId), + url: chainIdToThirdwebRpc(chainId, this.config.clientId), skipFetchSetup: false, }); const walletContract = new Contract( address, - "function getMessageHash(bytes memory message) public view returns (bytes32)", + "function getMessageHash(bytes32 _hash) public view returns (bytes32)", provider, ); + const hash = utils.hashMessage(message); + // if this doesn't fail, it's a post 1271 + typehash account - await walletContract.getMessageHash(message); + await walletContract.getMessageHash(hash); console.log( "Signing with EIP-712 typed data and verifying with EIP-1271...", ); - const hash = utils.hashMessage(message); const result = await signTypedDataInternal( this, { From edf12c1e1cb0bf09bcdfe8e2fa89e154460adad8 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Mon, 26 Feb 2024 18:43:14 +0300 Subject: [PATCH 06/13] lint --- packages/wallets/src/evm/wallets/smart-wallet.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/wallets/src/evm/wallets/smart-wallet.ts b/packages/wallets/src/evm/wallets/smart-wallet.ts index ab6ebfce4e5..ca0a78cc3cd 100644 --- a/packages/wallets/src/evm/wallets/smart-wallet.ts +++ b/packages/wallets/src/evm/wallets/smart-wallet.ts @@ -15,8 +15,7 @@ import { } from "@thirdweb-dev/sdk"; import { walletIds } from "../constants/walletIds"; import { getValidChainRPCs } from "@thirdweb-dev/chains"; -import { providers, utils, Bytes, Signer } from "ethers"; -import { signTypedDataInternal } from "@thirdweb-dev/sdk"; +import { providers, utils } from "ethers"; // export types and utils for convenience export type * from "../connectors/smart-wallet/types"; From 972c77578b6b8d537c7d5d91bb199f29bdff6a6a Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Mon, 26 Feb 2024 20:38:15 +0300 Subject: [PATCH 07/13] Update erc4337-signer.ts --- .../smart-wallet/lib/erc4337-signer.ts | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts index 9f630c007b2..71bcf594ec2 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts @@ -168,10 +168,11 @@ Code: ${errorCode}`; * If this fails, we fallback to the legacy signing method. */ try { - const provider = new providers.JsonRpcProvider({ - url: chainIdToThirdwebRpc(chainId, this.config.clientId), - skipFetchSetup: false, - }); + const provider = new providers.JsonRpcProvider( + chainIdToThirdwebRpc(chainId, this.config.clientId), + chainId, + ); + const walletContract = new Contract( address, "function getMessageHash(bytes32 _hash) public view returns (bytes32)", @@ -183,10 +184,6 @@ Code: ${errorCode}`; // if this doesn't fail, it's a post 1271 + typehash account await walletContract.getMessageHash(hash); - console.log( - "Signing with EIP-712 typed data and verifying with EIP-1271...", - ); - const result = await signTypedDataInternal( this, { @@ -214,21 +211,13 @@ Code: ${errorCode}`; return result.signature; } catch { - return await this.signMessageLegacy(this, message); + console.log( + "EIP-712 typed data and EIP-1271 verification failed, falling back to legacy signing method...", + ); + return await this.originalSigner.signMessage(message); } } - /** - * This is only for for legacy EIP-1271 signature verification - * Sign a message and return the signature - */ - private async signMessageLegacy( - signer: Signer, - message: Bytes | string, - ): Promise { - return await signer.signMessage(message); - } - async signTransaction( transaction: utils.Deferrable, options?: UserOpOptions, From 7983e8c605bc4ec4c60c6d4d72dfebf32d52d9f5 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Mon, 26 Feb 2024 21:54:28 +0300 Subject: [PATCH 08/13] fix abi --- .../src/evm/connectors/smart-wallet/lib/erc4337-signer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts index 71bcf594ec2..1a89b1f98c5 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts @@ -175,7 +175,9 @@ Code: ${errorCode}`; const walletContract = new Contract( address, - "function getMessageHash(bytes32 _hash) public view returns (bytes32)", + [ + "function getMessageHash(bytes32 _hash) public view returns (bytes32)", + ], provider, ); From 2806f7bddf8b95e0530ae1860253e12ec56b3272 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Mon, 26 Feb 2024 22:12:46 +0300 Subject: [PATCH 09/13] take message to fix auth login, hash inside check --- .../src/evm/connectors/smart-wallet/lib/erc4337-signer.ts | 2 +- packages/wallets/src/evm/wallets/abstract.ts | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts index 1a89b1f98c5..7ce573e4a09 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts @@ -201,7 +201,7 @@ Code: ${errorCode}`; ); const isValid = await checkContractWalletSignature( - hash, + message as string, result.signature, address, chainId, diff --git a/packages/wallets/src/evm/wallets/abstract.ts b/packages/wallets/src/evm/wallets/abstract.ts index 8dc8b588fec..fd37d9be10e 100644 --- a/packages/wallets/src/evm/wallets/abstract.ts +++ b/packages/wallets/src/evm/wallets/abstract.ts @@ -45,7 +45,7 @@ const EIP1271_ABI = [ const EIP1271_MAGICVALUE = "0x1626ba7e"; export async function checkContractWalletSignature( - hash: string, + message: string, signature: string, address: string, chainId: number, @@ -67,7 +67,10 @@ export async function checkContractWalletSignature( }); const walletContract = new Contract(address, EIP1271_ABI, provider); try { - const res = await walletContract.isValidSignature(hash, signature); + const res = await walletContract.isValidSignature( + utils.hashMessage(message), + signature, + ); return res === EIP1271_MAGICVALUE; } catch { return false; From 1f0cb6674304bfbd4b154c9e75f576fbba410dd6 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Tue, 27 Feb 2024 01:32:00 +0300 Subject: [PATCH 10/13] cleanup and always try recover --- .../smart-wallet/lib/erc4337-signer.ts | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts index 7ce573e4a09..c3ea71918c7 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts @@ -162,17 +162,16 @@ Code: ${errorCode}`; const chainId = await this.getChainId(); const address = await this.getAddress(); + const originalMsgHash = utils.hashMessage(message); + + let factorySupports712: boolean; + let signature: string; - /** - * We first try to sign the EIP-712 typed data i.e. the message mixed with the smart wallet's domain separator. - * If this fails, we fallback to the legacy signing method. - */ try { const provider = new providers.JsonRpcProvider( chainIdToThirdwebRpc(chainId, this.config.clientId), chainId, ); - const walletContract = new Contract( address, [ @@ -180,12 +179,14 @@ Code: ${errorCode}`; ], provider, ); + // if this fails it's a pre 712 factory + await walletContract.getMessageHash(originalMsgHash); + factorySupports712 = true; + } catch { + factorySupports712 = false; + } - const hash = utils.hashMessage(message); - - // if this doesn't fail, it's a post 1271 + typehash account - await walletContract.getMessageHash(hash); - + if (factorySupports712) { const result = await signTypedDataInternal( this, { @@ -196,27 +197,27 @@ Code: ${errorCode}`; }, { AccountMessage: [{ name: "message", type: "bytes" }] }, { - message: utils.defaultAbiCoder.encode(["bytes32"], [hash]), + message: utils.defaultAbiCoder.encode(["bytes32"], [originalMsgHash]), }, ); + signature = result.signature; + } else { + signature = await this.originalSigner.signMessage(message); + } - const isValid = await checkContractWalletSignature( - message as string, - result.signature, - address, - chainId, - ); - - if (!isValid) { - throw new Error("Invalid signature"); - } + const isValid = await checkContractWalletSignature( + message as string, + signature, + address, + chainId, + ); - return result.signature; - } catch { - console.log( - "EIP-712 typed data and EIP-1271 verification failed, falling back to legacy signing method...", + if (isValid) { + return signature; + } else { + throw new Error( + "Unable to verify signature on smart account, please make sure the smart account is deployed and the signature is valid.", ); - return await this.originalSigner.signMessage(message); } } From c0c16a8d5afd4677f6ebe6ecaf7c1c06b11da7c5 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Tue, 27 Feb 2024 01:38:36 +0300 Subject: [PATCH 11/13] add tests --- .../test/smart-wallet-integration.test.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/wallets/test/smart-wallet-integration.test.ts b/packages/wallets/test/smart-wallet-integration.test.ts index 77ddee205fe..ef40351dc2c 100644 --- a/packages/wallets/test/smart-wallet-integration.test.ts +++ b/packages/wallets/test/smart-wallet-integration.test.ts @@ -3,6 +3,10 @@ import { SmartWallet } from "../src/evm/wallets/smart-wallet"; import { LocalWallet } from "../src/evm/wallets/local-wallet"; import { Mumbai } from "@thirdweb-dev/chains"; import { ThirdwebSDK, SmartContract } from "@thirdweb-dev/sdk"; +import { checkContractWalletSignature } from "../src/evm/wallets/abstract"; + +require("dotenv-mono").load(); +jest.setTimeout(240_000); let smartWallet: SmartWallet; let smartWalletAddress: string; @@ -93,4 +97,23 @@ describeIf(!!process.env.TW_SECRET_KEY)("SmartWallet core tests", () => { const balance = await contract.erc1155.balance(0); expect(balance.toNumber()).toEqual(7); }); + + it("can sign and verify 1271", async () => { + const message = "0x1234"; + const sig = await smartWallet.signMessage(message); + const isValidV1 = await smartWallet.verifySignature( + message, + sig, + smartWalletAddress, + chain.chainId, + ); + expect(isValidV1).toEqual(true); + const isValidV2 = await checkContractWalletSignature( + message, + sig, + smartWalletAddress, + chain.chainId, + ); + expect(isValidV2).toEqual(true); + }); }); From 9fdf793b97372a4b82c961410d936621000bf56d Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Tue, 27 Feb 2024 01:45:37 +0300 Subject: [PATCH 12/13] parallelize --- .../src/evm/connectors/smart-wallet/lib/erc4337-signer.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts index c3ea71918c7..ff791265ff4 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/lib/erc4337-signer.ts @@ -160,8 +160,10 @@ Code: ${errorCode}`; await tx.wait(); } - const chainId = await this.getChainId(); - const address = await this.getAddress(); + const [chainId, address] = await Promise.all([ + this.getChainId(), + this.getAddress(), + ]); const originalMsgHash = utils.hashMessage(message); let factorySupports712: boolean; From cf6ba08fee735bd3cecdc73630e9f10fe6a15ded Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Tue, 27 Feb 2024 01:47:54 +0300 Subject: [PATCH 13/13] ver --- .changeset/good-ways-listen.md | 4 ++-- packages/unity-js-bridge/src/thirdweb-bridge.ts | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.changeset/good-ways-listen.md b/.changeset/good-ways-listen.md index e2fbfa62e83..f470f97b25c 100644 --- a/.changeset/good-ways-listen.md +++ b/.changeset/good-ways-listen.md @@ -1,6 +1,6 @@ --- "@thirdweb-dev/unity-js-bridge": minor -"@thirdweb-dev/wallets": minor +"@thirdweb-dev/wallets": patch --- -[Smart Wallet] Always force deploy on sign/auth, remove legacy signing, always use 1271 +[Smart Wallet] Always force deploy on sign/auth, use 712 with 1271 where possible diff --git a/packages/unity-js-bridge/src/thirdweb-bridge.ts b/packages/unity-js-bridge/src/thirdweb-bridge.ts index 1789b447bbb..fcd13566d45 100644 --- a/packages/unity-js-bridge/src/thirdweb-bridge.ts +++ b/packages/unity-js-bridge/src/thirdweb-bridge.ts @@ -144,7 +144,7 @@ class ThirdwebBridge implements TWBridge { } (globalThis as any).X_SDK_NAME = "UnitySDK_WebGL"; (globalThis as any).X_SDK_PLATFORM = "unity"; - (globalThis as any).X_SDK_VERSION = "4.6.4"; + (globalThis as any).X_SDK_VERSION = "4.7.0"; (globalThis as any).X_SDK_OS = browser?.os ?? "unknown"; } this.initializedChain = chain; @@ -812,18 +812,19 @@ class ThirdwebBridge implements TWBridge { if (!this.activeWallet) { throw new Error("No wallet connected"); } - try{ + try { const smartWallet = this.activeWallet as SmartWallet; const signer = await smartWallet.getPersonalWallet()?.getSigner(); const res = await signer?.getAddress(); return JSON.stringify({ result: res }, bigNumberReplacer); } catch { - console.debug("Could not find a smart wallet, defaulting to normal signer"); + console.debug( + "Could not find a smart wallet, defaulting to normal signer", + ); const signer = await this.activeWallet.getSigner(); const res = await signer.getAddress(); return JSON.stringify({ result: res }, bigNumberReplacer); } - } public openPopupWindow() {