{"author":{"address":null,"user":"https://learnblockchain.cn/people/18158"},"content":{"body":"# 前言\r\n\r\n\u003e 本文主要实现EIP721类型化数据签名的智能合约的开发、测试、部署、交互，测试过程：涉及到前端通过ethers库和合约以及钱包的交互；\r\n\r\n# EIP712 类型化数据签名\r\n\r\n**定义**：一种以太坊改进提案，旨在提供一种更高级、更安全的类型化数据签名方法；\r\n\r\n#### 背景与重要性\r\n\r\n*   **链下签名，链上验证**：EIP712将签名过程从链上转移至链下，节省Gas费。这种链下消息签名、链上验证的形式，可以省去多余的approve操作。\r\n*   **提高安全性与可用性**：当支持EIP712的Dapp请求签名时，钱包会展示签名消息的原始数据，用户可以在验证数据符合预期之后签名。这使得用户能够更直观地了解他们正在签名的数据内容，而不是面对一串无法观察的编码。\r\n\r\n#### 工作流程\r\n\r\n1.  定义签名结构体数据类型\r\n2.  定义结构体的hash\r\n3.  定义编码数据\r\n4.  定义Domain Separator\r\n5.  生成摘要hash\r\n6.  验证签名者\r\n\r\n#### 应用场景\r\n\r\n*   **数字资产交易**：验证交易的合法性和完整性。\r\n*   **DApps身份验证**：确保用户身份的合法性。\r\n*   **智能合约的部署和调用**：验证调用者的身份和权限。\r\n\r\n# 合约开发\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.20;\r\nimport {ECDSA} from \"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\";\r\nimport {EIP712} from \"@openzeppelin/contracts/utils/cryptography/EIP712.sol\";\r\nimport \"hardhat/console.sol\";\r\ncontract EIP712Storage is EIP712 {\r\n    bytes32 public constant PERMIT_TYPEHASH =\r\n    keccak256(\"Permit(address owner,uint256 value,uint256 nonce,uint256 deadline)\");\r\n    mapping(address =\u003e uint256) public nonces;\r\n    uint256 private _value;\r\n    constructor() EIP712(\"EIP712Storage\", \"1.0.0\") {}\r\n    function storeData(\r\n        uint256 value,\r\n        uint256 deadline,\r\n        uint8 v,\r\n        bytes32 r,\r\n        bytes32 s\r\n    ) public {\r\n        require(block.timestamp \u003c= deadline, \"EIP712Storage: expired deadline\");\r\n        bytes32 structHash = keccak256(\r\n            abi.encode(\r\n                PERMIT_TYPEHASH,\r\n                msg.sender,\r\n                value,\r\n                nonces[msg.sender]++,\r\n                deadline\r\n            ));\r\n        bytes32 hash = _hashTypedDataV4(structHash);\r\n        address signer = ECDSA.recover(hash, v, r, s);\r\n        require(signer == msg.sender, \"EIP712Storage: invalid signature\");\r\n        _value = value;\r\n    }\r\n    function retrieve() public view returns (uint256) {\r\n        return _value;\r\n    }\r\n    function getDomainSeparator() public view returns (bytes32) {\r\n        return _domainSeparatorV4();\r\n    }\r\n}\r\n    # 部署指令\r\n    # npx hardhat deploy\r\n```\r\n\r\n# 合约本地测试（hardhat）\r\n**说明**：主要测试签名有效、无效、时间过期三种情况的测试\r\n```\r\nconst { ethers } = require(\"hardhat\");\r\nconst { expect } = require(\"chai\");\r\ndescribe(\"EIP712Storage\", function () {\r\n    let EIP712Storage;\r\n    let eip712Storage;\r\n    let owner;\r\n    let addr1;\r\n    let addr2;\r\n    beforeEach(async function () {\r\n         await deployments.fixture([\"EIP712Storage\"]);\r\n        [owner, addr1, addr2] = await ethers.getSigners();\r\n        const EIP712StorageFactory = await deployments.get(\"EIP712Storage\");\r\n        eip712Storage = await ethers.getContractAt(\"EIP712Storage\",EIP712StorageFactory.address);\r\n        //和上面是一样的都是部署合约\r\n        // const EIP712StorageFactory = await ethers.getContractFactory(\"EIP712Storage\");\r\n        // eip712Storage = await EIP712StorageFactory.deploy();\r\n        // await eip712Storage.waitForDeployment();\r\n\r\n    });\r\n    describe(\"Storing with EIP712 signature\", function () {\r\n        it(\"有效签名存储\", async function () {\r\n            const chainId = (await ethers.provider.getNetwork()).chainId;\r\n            // 构建域分隔符\r\n            const domain = {\r\n                name: \"EIP712Storage\",\r\n                version: \"1.0.0\",\r\n                chainId: chainId,\r\n                verifyingContract: await eip712Storage.getAddress()\r\n            };\r\n            // 定义类型\r\n            const types = {\r\n                Permit: [\r\n                    { name: \"owner\", type: \"address\" },\r\n                    { name: \"value\", type: \"uint256\" },\r\n                    { name: \"nonce\", type: \"uint256\" },\r\n                    { name: \"deadline\", type: \"uint256\" }\r\n                ]\r\n            };\r\n            const value = 42;\r\n            const deadline = Math.floor(Date.now() / 1000) + 60 * 60; // 1 hour from now\r\n            const nonce = await eip712Storage.nonces(owner.address);\r\n            // 准备签名数据\r\n            const message = {\r\n                owner: owner.address,\r\n                value: value,\r\n                nonce: nonce,\r\n                deadline: deadline\r\n            };\r\n            // 签名\r\n            const signature = await owner.signTypedData(domain, types, message);\r\n            const { v, r, s } = ethers.Signature.from(signature);\r\n            // 存储数据\r\n            await eip712Storage.storeData(value, deadline, v, r, s);\r\n            // 验证存储的值\r\n            expect(await eip712Storage.retrieve()).to.equal(value);\r\n        });\r\n        it(\"日期过期测试\", async function () {\r\n            const chainId = (await ethers.provider.getNetwork()).chainId;\r\n            const domain = {\r\n                name: \"EIP712Storage\",\r\n                version: \"1.0.0\",\r\n                chainId: chainId,\r\n                verifyingContract: await eip712Storage.getAddress()\r\n            };\r\n            const types = {\r\n                Permit: [\r\n                    { name: \"owner\", type: \"address\" },\r\n                    { name: \"value\", type: \"uint256\" },\r\n                    { name: \"nonce\", type: \"uint256\" },\r\n                    { name: \"deadline\", type: \"uint256\" }\r\n                ]\r\n            }; \r\n            const value = 42;\r\n            const deadline = Math.floor(Date.now() / 1000) - 60; // 1 minute ago\r\n            const nonce = await eip712Storage.nonces(owner.address);\r\n            const message = {\r\n                owner: owner.address,\r\n                value: value,\r\n                nonce: nonce,\r\n                deadline: deadline\r\n            };\r\n            const signature = await owner.signTypedData(domain, types, message);\r\n            const { v, r, s } = ethers.Signature.from(signature);\r\n            await expect(\r\n                eip712Storage.storeData(value, deadline, v, r, s)\r\n            ).to.be.revertedWith(\"EIP712Storage: expired deadline\");\r\n        });\r\n        it(\"签名无效\", async function () {\r\n            const chainId = (await ethers.provider.getNetwork()).chainId;\r\n            const domain = {\r\n                name: \"EIP712Storage\",\r\n                version: \"1.0.0\",\r\n                chainId: chainId,\r\n                verifyingContract: await eip712Storage.getAddress()\r\n            };\r\n            const types = {\r\n                Permit: [\r\n                    { name: \"owner\", type: \"address\" },\r\n                    { name: \"value\", type: \"uint256\" },\r\n                    { name: \"nonce\", type: \"uint256\" },\r\n                    { name: \"deadline\", type: \"uint256\" }\r\n                ]\r\n            };\r\n            const value = 42;\r\n            const deadline = Math.floor(Date.now() / 1000) + 60 * 60;\r\n            const nonce = await eip712Storage.nonces(owner.address);\r\n            const message = {\r\n                owner: owner.address,\r\n                value: value,\r\n                nonce: nonce,\r\n                deadline: deadline\r\n            };\r\n\r\n            // 使用不同的签名者\r\n            const signature = await addr1.signTypedData(domain, types, message)\r\n            const { v, r, s } = ethers.Signature.from(signature);\r\n            await expect(\r\n\r\n                eip712Storage.connect(owner).storeData(value, deadline, v, r, s)\r\n\r\n            ).to.be.revertedWith(\"EIP712Storage: invalid signature\");\r\n\r\n        });\r\n\r\n    });\r\n\r\n});\r\n# 测试指令\r\n# npx hardhat test ./test/xxx.js\r\n```\r\n# 合约部署\r\n```\r\n    module.exports = async function ({getNamedAccounts,deployments}) {\r\n        const firstAccount = (await getNamedAccounts()).firstAccount;\r\n        const {deploy,log} = deployments;\r\n        const EIP712Storage=await deploy(\"EIP712Storage\",{\r\n            contract: \"EIP712Storage\",\r\n            from: firstAccount,\r\n            args: [],\r\n            log: true,\r\n            // waitConfirmations: 1,\r\n        })\r\n        console.log(\"EIP712Storage合约地址\",EIP712Storage.address)\r\n      }\r\n      module.exports.tags = [\"all\", \"EIP712Storage\"]\r\n    # 部署指令\r\n    # npx hardhat deploy\r\n```\r\n# 总结\r\n\r\n以上就是EIP712 类型化数据签名合约的开发、测试、部署全部流程，主要包含了测试了签名有效，无效和时间过期三种情况。","title":"EIP712 类型化数据签名合约一种更安全、先进的签名方式"},"history":null,"timestamp":1740980544,"version":1}