{"content":{"title":"使用 ERC-1271，让 Dapp 兼容智能合约钱包","body":">- 原文链接：https://docs.alchemy.com/docs/how-to-make-your-dapp-compatible-with-smart-contract-wallets\r\n>- 译文出自：[登链翻译计划](https://github.com/lbc-team/Pioneer)\r\n>- 译者：[翻译小组](https://learnblockchain.cn/people/412) ，校对：[Tiny 熊](https://learnblockchain.cn/people/15)\r\n>- 本文永久链接：[learnblockchain.cn/article…](https://learnblockchain.cn/article/7363)\r\n\r\n\r\n学习如何通过实现 ERC-1271 来验证 Dapp 中智能合约钱包的签名。\r\n\r\n在本教程中，我们将讨论智能合约钱包，它们的工作原理以及如何允许它们登录到你的 Dapp 中。我们将涵盖 [EIP-1271](https://eip1271.io/)，该标准定义了一种在账户为智能合约（钱包）时验证签名的方式。我们还将讨论 [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) 作为智能合约钱包的额外上下文。\r\n\r\n## 智能合约钱包和 ERC-4337\r\n\r\n### 智能合约钱包\r\n\r\n智能合约钱包（SCW）是一种建立在区块链网络上的智能合约上的加密货币钱包类型，例如以太坊。与传统的由私钥控制的外部拥有账户（EOA）不同，智能合约钱包提供了额外的功能和安全特性。它们允许进行更复杂的操作、可定制的规则、恢复选项和多签功能。\r\n\r\n**智能合约钱包的关键特性包括：**\r\n\r\n- **可定制性**：用户可以根据自己的需求添加特定功能或修改现有功能。\r\n- **安全性**：这些钱包使用先进的安全功能，如多签访问和恢复机制。\r\n- **可升级性**：智能合约钱包的功能可以更新，而不会丢失存储的资产。\r\n- **可互操作性**：它们可以与区块链网络上的其他智能合约和去中心化应用程序（dApp）进行交互。\r\n\r\n### ERC-4337\r\n\r\n**[ERC-4337](https://learnblockchain.cn/tags/ERC-4337)**，也称为以太坊改进提案（EIP）4337，是以太坊中的账户抽象标准。它旨在通过引入新的交易类型“UserOperation”简化用户体验，该类型将交易验证与执行分离。该标准允许用户使用智能合约钱包与以太坊网络进行交互，而无需直接管理 gas 费用和 nonce 管理。\r\n\r\n**ERC-4337 的关键组件包括：**\r\n\r\n- **UserOperation**：一种封装用户期望操作的新交易类型，包括发送者、有效载荷和与 gas 相关的信息。“UserOperation”将验证和执行步骤分离，从而实现更高效和安全的交易处理。\r\n- **EntryPoint**：作为“UserOperations”的网关的智能合约。它负责验证和执行“UserOperations”，并可以与其他合约（如Paymasters和Factories）进行交互，以促进交易。\r\n- **Paymasters**：这些是可以代表用户赞助交易的智能合约。它们支持各种支付机制，如 [ERC-20](https://learnblockchain.cn/tags/ERC20) 代币，用于 gas 费用，为用户提供更大的灵活性。\r\n- **Factories**：负责创建新智能合约钱包的智能合约。它们使用 [`CREATE2`](https://docs.alchemy.com/docs/create2-an-alternative-to-deriving-contract-addresses) 确保钱包地址是确定性的，并且独立于钱包创建的顺序。这允许用户在本地生成钱包地址，而无需依赖现有用户或执行自定义操作。\r\n- **声誉评分和节流**：为防止滥用和拒绝服务（DoS）攻击，ERC-4337 引入了全局实体的声誉评分和节流机制，如Paymaster和Factories。实体需要抵押一定数量的 ETH，以确保它们的行为不会导致其他“UserOperations”失效。\r\n\r\n总之，**[ERC-4337](https://learnblockchain.cn/tags/ERC-4337)** 旨在通过简化与以太坊网络的交互并通过智能合约钱包引入高级功能，改善用户体验。它将交易验证与执行分离，允许更灵活的 gas 支付选项，并支持无缝账户创建。\r\n\r\n## 合约如何签署消息？\r\n\r\n当用户将他们的钱包连接到应用时，可能会被要求签署一条消息以证明他们的身份。对于外部拥有账户（EOA），用户使用他们的私钥对消息进行签名。验证方可以使用[恢复算法](https://docs.alchemy.com/docs/how-to-verify-a-message-signature-on-ethereum) ，如 `ecrecover`，来确定谁签署了消息。\r\n\r\n智能合约钱包可以生成签名，但它们没有像 EOA 那样的私钥。但是，智能合约本身可提供验证签名的机制。EIP-1271 通过一个标准接口解决了这个问题。\r\n\r\n## EIP-1271\r\n\r\n[EIP-1271](https://eip1271.io/) 是一个用于验证由智能合约钱包（SCW）生成的签名的合约的标准接口。它于 2018 年作为以太坊改进提案（EIP）提出，并已被广泛采用，用于需要签名验证的 [DApp](https://learnblockchain.cn/tags/DApp)。\r\n\r\nEIP-1271 接口定义了一个名为 `isValidSignature` 的函数。该函数接受两个参数：\r\n\r\n- `bytes32 _messageHash`：被签名的消息哈希\r\n- `bytes _signature`：智能合约钱包生成的签名\r\n\r\n该函数返回一个 `bytes4` 值，指示签名是否有效。可能的返回值包括：\r\n\r\n- `0x1626ba7e`：签名有效\r\n- `0xffffffff`：签名无效\r\n\r\n## 钱包验证通常如何工作\r\n\r\n以下是常见的 web3 认证流程的工作方式：\r\n\r\n1. 服务器生成用户必须签署的消息。该消息包含一个随机 nonce，以防止重放攻击。\r\n2. 用户使用他们的私钥对消息的哈希进行签名。\r\n3. 服务器使用 `ecrecover`（ [恢复算法](https://docs.alchemy.com/docs/how-to-verify-a-message-signature-on-ethereum) ）验证签名，如果消息被正确签名，应返回用户的地址。\r\n\r\n但是，这种方法不能直接用于智能合约钱包，因为你不能对 SCW 签名使用 `ecrecover`，因为 SCW 可能具有自定义签名验证逻辑。\r\n\r\n## 如何使智能合约的钱包验证工作\r\n\r\n要使智能合约的钱包验证工作，我们需要修改验证过程。我们应该调用智能合约钱包的 `isValidSignature` 函数来验证签名是否被它批准，正如 EIP-1271 中所定义的那样。\r\n\r\n### 使用 `ethers.js` 的示例脚本\r\n\r\n以下是一个用于在 `ethers.js` 中验证智能合约钱包和 EOA 签名有效性的示例脚本：\r\n\r\nverifySig.js\r\n\r\n```javascript\r\n// importing the required modules from ethers.js\r\nconst { providers, utils, Contract } = require(\"ethers\");\r\n\r\n// importing ABI for interface of ERC1271 so we can call the `isValidSignature` function\r\nconst IERC1271Abi = [{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"addrs\",\"type\":\"address[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"returnData\",\"type\":\"bytes\"}],\"name\":\"LogErr\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"priv\",\"type\":\"bytes32\"}],\"name\":\"LogPrivilegeChanged\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"struct Identity.Transaction[]\",\"name\":\"txns\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"name\":\"execute\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"struct Identity.Transaction[]\",\"name\":\"txns\",\"type\":\"tuple[]\"}],\"name\":\"executeBySelf\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"struct Identity.Transaction[]\",\"name\":\"txns\",\"type\":\"tuple[]\"}],\"name\":\"executeBySender\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"hash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"name\":\"isValidSignature\",\"outputs\":[{\"internalType\":\"bytes4\",\"name\":\"\",\"type\":\"bytes4\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"nonce\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"privileges\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"priv\",\"type\":\"bytes32\"}],\"name\":\"setAddrPrivilege\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceID\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"tipMiner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"tryCatch\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]\r\n\r\n// This is a constant magic value defined in EIP-1271 that's returned when the signature is valid\r\nconst MAGICVALUE = 0x1626ba7e;\r\n\r\n// function to check if a signature is valid\r\nconst isValidSignature = async (signingAddress, message, signature) => {\r\n  const hash = utils.hashMessage(message); // hash the message\r\n  const apiKey = \"demo\" // replace with your Alchemy API key for the network you are verifying the signature for, in this case Polygon Mainnet\r\n  const provider = new providers.JsonRpcProvider(\r\n    `https://polygon-mainnet.g.alchemy.com/v2/${apiKey}`\r\n  ); // get your provider\r\n  const bytecode = await provider.getCode(signingAddress); // get the bytecode\r\n  const isSmartContract = bytecode && utils.hexStripZeros(bytecode) !== \"0x\"; // check if it is a smart contract wallet\r\n\r\n  if (isSmartContract) {\r\n    // verify the message for a decentralized account (contract wallet)\r\n    const contractWallet = new Contract(signingAddress, IERC1271Abi, provider); // make an instance for the contact wallet\r\n    const verification = await contractWallet.isValidSignature(hash, signature); // verify if the signature is valid using the `isValidSignature` function\r\n    console.log(\"Message is verified?\", verification === MAGICVALUE); // log if the signature is valid\r\n    return verification === MAGICVALUE; // return true or false based on if the signature is valid or not\r\n  } else {\r\n    // verify the message for an externally owned account (EOA) using the recovery algorithm\r\n    const sig = ethers.utils.splitSignature(signature);\r\n    const recovered = await contract.verifyHash(hash, sig.v, sig.r, sig.s);\r\n    console.log(\"Message is verified?\", recovered === signingAddress);\r\n    return recovered === signingAddress;\r\n  }\r\n};\r\n\r\nasync function main() {\r\n  let isValid = await isValidSignature(\r\n    \"0x4836a472ab1dd406ecb8d0f933a985541ee3921f\",\r\n    \"0x787177\",\r\n    \"0xc0f8db6019888d87a0afc1299e81ef45d3abce64f63072c8d7a6ef00f5f82c1522958ff110afa98b8c0d23b558376db1d2fbab4944e708f8bf6dc7b977ee07201b00\"\r\n  );\r\n\r\n  console.log(isValid);\r\n}\r\n\r\nmain();\r\n```\r\n\r\n在这里，我们检查一个账户是智能合约钱包还是 EOA，如果是 SCW，我们调用其 `isValidSignature` 函数来验证签名的有效性，否则，我们使用 EOA 的[恢复算法](https://docs.alchemy.com/docs/how-to-verify-a-message-signature-on-ethereum)来验证签名的有效性。\r\n\r\n目前，在 Ethers 的 Github 存储库中有一个 [PR](https://github.com/ethers-io/ethers.js/pull/3904)，用于直接向 Ethers 添加此功能。一旦该 PR 被合并，使用 Ethers 验证 SCW 的签名将变得更加容易。\r\n\r\n## 用于 SCW 签名验证的库\r\n\r\n还有一些库可以使开发人员在智能合约钱包的情况下轻松进行签名验证。这些库的示例包括 [`eip1271-verification-util`](https://github.com/etherspot/eip1271-verification-util) 和 [`signature-validator`](https://github.com/AmbireTech/signature-validator/)。\r\n\r\n\r\n下面是一个示例脚本，描述了如何使用 [eip1271-verification-util](https://github.com/etherspot/eip1271-verification-util) 库验证前端 dapp 中智能合约钱包的签名：\r\n\r\n> 📘 注意\r\n\r\n> 下面给出的代码不能在 nodejs 项目中使用，只能在前端应用程序中使用，因为 eip1271-verification-util 库是专门为 dapps 设计的，用于验证智能合约钱包的签名。\r\n\r\n\r\n\r\n```javascript\r\n// Setup: npm i @etherspot/eip1271-verification-util\r\n// importing ethers\r\nimport ethers from \"ethers\";\r\n\r\n\r\n// importing the `isValidEip1271Signature` function from `eip1271-verification-util`\r\nimport { isValidEip1271Signature } from \"@etherspot/eip1271-verification-util\";\r\n\r\nconst checkSig = async () => {\r\n  // the random message (nonce) that was signed\r\n  const data = \"0x787177\";\r\n\r\n  // defining signer and the rpc url\r\n\tconst signerAddress = '0x4836a472ab1dd406ecb8d0f933a985541ee3921f';\r\n\r\n\t// the rpc url to make requests\r\n\tconst rpcUrl = 'https://polygon-mainnet.g.alchemy.com/v2/demo'\r\n\r\n  // The signature to verify as a hex string\r\n  const signature = '0xc0f8db6019888d87a0afc1299e81ef45d3abce64f63072c8d7a6ef00f5f82c1522958ff110afa98b8c0d23b558376db1d2fbab4944e708f8bf6dc7b977ee07201b00' \r\n\r\n  // Hashed data used for the signature to verify. The dApp will need to pre-compute this as no hashing will occur in the function, and this will be directly used in isValidEip1271Signature\r\n  const hash = ethers.utils.hashMessage(ethers.utils.arrayify(data)); \r\n\r\n  // calling the imported function to verify the signature and passing the required params\r\n  const isValidSig = await isValidEip1271Signature(\r\n    rpcUrl,\r\n    signerAddress,\r\n    hash,\r\n    signature\r\n  );\r\n\r\n\t// logging if the signature is valid or not\r\n  console.log(\"is signature valid:\", isValidSig);\r\n};\r\n```\r\n\r\n如你所见，使用 [`eip1271-verification-util`](https://github.com/etherspot/eip1271-verification-util) 库的`isValidEip1271Signature`函数，你可以验证智能合约钱包的签名。\r\n\r\n## 结论\r\n\r\n\r\n总之，EIP-1271 是一个重要的提案，它为智能合约验证签名定义了一个标准。它允许人们在去中心化应用中使用智能合约钱包。实施 EIP-1271 对于任何希望保持领先地位的 dApp 都至关重要。\r\n\r\n---\r\n\r\n> 本翻译由 [DeCert.me](https://decert.me/) 协助支持， 在 DeCert 构建可信履历，为自己码一个未来。"},"author":{"user":"https://learnblockchain.cn/people/412","address":"0x9e64a306aB319811C5a1270F2CA9f6E1e4857c84"},"history":"bafkreigarw7uh4wtwavjbpck2nogpwpnbcpqmyz6yxa6fxk6p4fczp7wzq","timestamp":1706798765,"version":1}