Ethers 加签 + Solidity 合约验签实现 单元测试 demo
文章目录前言Ethers 加签 Solidity 合约验签实现 单元测试 demo1. 使用场景2. solidity 合约编写验签合约签名通过ethers进行加签3. 单元测试范围4. 源码及测试前言如果您觉得有用的话记得给博主点个赞评论收藏一键三连啊写作不易啊^ _ ^。而且听说点赞的人每天的运气都不会太差实在白嫖的话那欢迎常来啊!!!Ethers 加签 Solidity 合约验签实现 单元测试 demo1. 使用场景在 Web3 开发中签名验证是实现「无链上交易的身份认证、权限校验、消息防篡改」的核心技术无需消耗 Gas 上链即可确认消息由指定钱包地址发出典型适用场景包括去中心化应用DApp的离线身份验证用户无需转账即可证明钱包所有权链下订单 / 指令确认比如 NFT 铸造授权、交易委托防止消息被篡改空投白名单校验通过签名验证用户是否具备白名单资格避免链上遍历的 Gas 消耗跨链 / 跨应用的消息互通通过签名保证消息的真实性和唯一性。2. solidity 合约编写验签合约签名通过ethers进行加签加签逻辑:确定待签名的业务消息如用户地址、白名单标识等将消息转成字节数组做 Keccak256 哈希固定 32 字节防篡改用钱包私钥对哈希后的消息签名生成 65 字节标准签名把「原始消息 签名」传给合约用于验签。验签逻辑:合约接收「原始消息 签名」对消息复刻加签的哈希流程生成相同哈希值用 OpenZeppelin 库解析签名恢复出签名者地址对比恢复地址与业务预期地址如白名单地址一致则验签通过执行后续业务不一致则拒绝。3. 单元测试范围核心用例1验证有效签名能正确恢复出签名者地址 边界用例2验证无效签名无法恢复出正确地址 边界用例3验证修改原始消息后有效签名也会验签失败覆盖消息篡改场景4. 源码及测试合约:// SPDX-License-Identifier: MIT // 开源协议MIT可商用、可修改保留版权声明即可适合合约上线场景 pragma solidity ^0.8.28;// 指定Solidity编译器版本兼容0.8.28及以上同系列版本 //0.8.x版本自带溢出/下溢安全检查无需手动编写校验逻辑 // 导入OpenZeppelin的ECDSA算法库用于签名解析和地址恢复importopenzeppelin/contracts/utils/cryptography/ECDSA.sol;// 导入OpenZeppelin的消息哈希工具库用于生成以太坊标准签名哈希importopenzeppelin/contracts/utils/cryptography/MessageHashUtils.sol;// 签名验证合约核心功能根据原始消息和签名恢复并返回签名者的以太坊地址 contract VerifySignature{// 为bytes32类型挂载ECDSA库的所有方法可直接通过bytes32变量调用如recover using ECDSAforbytes32;// 为bytes32类型挂载MessageHashUtils库的所有方法可直接通过bytes32变量调用如toEthSignedMessageHash using MessageHashUtilsforbytes32;/** * dev 核心验签方法根据原始字符串消息和签名恢复签名者地址 * param str 原始待签名的字符串消息与签名时的原始消息完全一致 * param signature 签名结果由前端/钱包通过私钥对消息签名生成65字节r(32)s(32)v(1) * return 恢复出的签名者以太坊地址若签名无效会返回错误 */functionrecover(string memory str, bytes memory signature)external pure returns(address){//1. 原始消息哈希将字符串转成bytes字节数组再做Keccak256哈希 // 目的将任意长度的字符串转为固定32字节的哈希值适配区块链签名算法要求 bytes32hashkeccak256(bytes(str));//2. 还原签名者地址两步核心操作链式调用 // hash.toEthSignedMessageHash()给原始哈希拼接以太坊标准签名前缀(\x19Ethereum Signed Message:\n32)并二次Keccak256哈希 // → 对齐钱包/前端signMessage的签名规则防止重放攻击 // .recover(signature)从签名中解析r/s/v通过ECDSA算法根据哈希值恢复签名者公钥再推导为以太坊地址returnhash.toEthSignedMessageHash().recover(signature);}}单元测试:import{expect}fromchai;importnomicfoundation/hardhat-chai-matchers;import{ethers}fromhardhat;import{SignerWithAddress}fromnomicfoundation/hardhat-ethers/signers;// 测试加签数据 const TEST_MESSAGEyangzhenyu;// 全局加签方法入参消息签名者返回生成的签名 asyncfunctiongenerateSignature(message: string, signer: SignerWithAddress){//1. 消息转UTF-8字节数组 const messageBytesethers.toUtf8Bytes(message);//2. Keccak256哈希 const messageHashethers.keccak256(messageBytes);//3. 哈希转字节数组适配signMessage接口 const messageHashBytesethers.toBeArray(messageHash);//4. 签名并返回结果returnawait signer.signMessage(messageHashBytes);}describe(VerifySignature,function(){// 声明全局测试变量所有用例复用统一any类型letverifySignature: any;// 签名者账户letsigner: SignerWithAddress;// 另外的账户letotherSigner: SignerWithAddress;// 前置钩子每个用例执行前初始化和你示例的beforeEach保持一致 beforeEach(asyncfunction(){// 获取Hardhat本地测试账户第一个作为签名者[signer, otherSigner]await ethers.getSigners()as SignerWithAddress[];// 获取合约工厂部署合约完全对齐你示例的部署写法 const VerifySignature2Factoryawait ethers.getContractFactory(VerifySignature);verifySignatureawait VerifySignature2Factory.deploy();// 等待合约部署上链生效 await verifySignature.waitForDeployment();});// 核心用例1验证有效签名能正确恢复出签名者地址 it(should recover correct signer address with valid signature, asyncfunction(){// 直接调用抽离的方法生成签名一行搞定 const signatureawait generateSignature(TEST_MESSAGE, signer);// 合约验签 const recoveredAddressawait verifySignature.recover(TEST_MESSAGE, signature);// 断言 expect(recoveredAddress).to.equal(signer.address);});// 边界用例2验证无效签名无法恢复出正确地址 it(should not recover correct signer address with invalid signature, asyncfunction(){// 构造「格式合法但内容无效」的签名用另一个账户签名相同消息 const invalidSignatureawait generateSignature(TEST_MESSAGE, otherSigner);// 合约验签 const recoveredAddressawait verifySignature.recover(TEST_MESSAGE, invalidSignature);// 断言 expect(recoveredAddress).to.not.equal(signer.address);});// 边界用例3验证修改原始消息后有效签名也会验签失败覆盖消息篡改场景 it(should fail when message is tampered with valid signature, asyncfunction(){// 调用抽离方法生成有效签名 const validSignatureawait generateSignature(TEST_MESSAGE, signer);// 篡改原始消息 const tamperedMessageyangzhenyu ;// 合约验签篡改后的消息 const recoveredAddressawait verifySignature.recover(tamperedMessage, validSignature);// 断言 expect(recoveredAddress).to.not.equal(signer.address);});});下面是拿出来的加签方法:// 全局加签方法入参消息签名者返回生成的签名 asyncfunctiongenerateSignature(message: string, signer: SignerWithAddress){//1. 消息转UTF-8字节数组 const messageBytesethers.toUtf8Bytes(message);//2. Keccak256哈希 const messageHashethers.keccak256(messageBytes);//3. 哈希转字节数组适配signMessage接口 const messageHashBytesethers.toBeArray(messageHash);//4. 签名并返回结果returnawait signer.signMessage(messageHashBytes);}测试:测试成功!!!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2426735.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!