Solidity智能合约无效签名:如何生成Lending函数签名?
签名无效问题:Ethers.js调用合约lending函数失败排查
我调用某BSC智能合约的lending函数,用Ethers.js编写了集成代码,但交易返回无效签名错误。以下是我的代码,求指导如何生成正确的签名用于lending调用。
我的Ethers.js代码
const contractAddress = "0x1C4E9F87c7F2bCd80c89A1999d776461d41545b9"; const provider = new ethers.providers.JsonRpcProvider( "https://rpc.ankr.com/bsc" ); const signer = new ethers.Wallet(privateKey, provider); const contract = new ethers.Contract(contractAddress, ABI, signer); const lending = async () => { const id = 992581; const tokenId = 16587857756452; const price = 500000000000; const expiredAt = Date.now() + 60; const messageHash = ethers.utils.solidityKeccak256( ["uint256", "uint256", "uint256", "uint256"], [id, tokenId, price, expiredAt] ); const signingKey = new SigningKey("0x" + privateKey); const signature = signingKey.signDigest(messageHash); const signatureString = ethers.utils.joinSignature(signature); const tx = await contract.lending( id, tokenId, price, expiredAt, signatureString, { gasLimit: 50000, } ); const receipt = await tx.wait(); console.log(receipt); };
合约签名验证代码(Solidity)
function verifyLendingSig( uint256 _id, uint256 _tokenId, uint256 _price, uint256 _expiredAt, bytes calldata _signature ) public view { bytes32 criteriaMessageHash = keccak256( abi.encodePacked(_id, _tokenId, _price, _expiredAt) ); bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash( criteriaMessageHash ); require( ECDSA.recover(ethSignedMessageHash, _signature) == signer, "invalid signature" ); }
问题根源
你的签名生成逻辑和合约验证逻辑存在两处核心不匹配:
- 参数编码哈希不一致:合约用
abi.encodePacked拼接参数后做keccak256哈希,而你用solidityKeccak256(带类型的标准ABI编码)生成哈希,两者原始哈希值完全不同。 - 签名前缀缺失:合约对原始哈希做了
ECDSA.toEthSignedMessageHash处理(自动添加以太坊签名前缀"\x19Ethereum Signed Message:\n32"),但你直接对原始哈希签名,未添加该前缀,导致签名无法通过验证。
修正后的Ethers.js签名生成代码
把原代码中生成签名的部分替换为以下逻辑:
const lending = async () => { const id = 992581; const tokenId = 16587857756452; const price = 500000000000; const expiredAt = Date.now() + 60; // 1. 对齐合约的参数编码:模拟abi.encodePacked拼接uint256参数 const paramBytes = ethers.utils.concat([ ethers.utils.zeroPad(ethers.utils.hexlify(id), 32), ethers.utils.zeroPad(ethers.utils.hexlify(tokenId), 32), ethers.utils.zeroPad(ethers.utils.hexlify(price), 32), ethers.utils.zeroPad(ethers.utils.hexlify(expiredAt), 32) ]); // 2. 生成合约中一致的criteriaMessageHash const criteriaMessageHash = ethers.utils.keccak256(paramBytes); // 3. 用signer签名,自动添加以太坊签名前缀,匹配合约的ECDSA.toEthSignedMessageHash const signatureString = await signer.signMessage(ethers.utils.arrayify(criteriaMessageHash)); // 后续调用合约逻辑不变 const tx = await contract.lending( id, tokenId, price, expiredAt, signatureString, { gasLimit: 50000, } ); const receipt = await tx.wait(); console.log(receipt); };
关键修正点说明
- 编码对齐:用
ethers.utils.concat配合zeroPad模拟Solidity的abi.encodePacked行为(对uint256类型,每个参数补为32字节后直接拼接),确保生成的参数字节和合约完全一致。 - 哈希对齐:对拼接后的字节做keccak256哈希,得到和合约中
criteriaMessageHash完全相同的值。 - 签名前缀对齐:
signer.signMessage会自动添加以太坊标准签名前缀,生成的签名可直接被合约的ECDSA.recover方法验证通过。 - 简化签名流程:直接使用
signer对象签名,避免手动处理SigningKey时可能出现的privateKey格式错误(比如重复添加0x)。
内容的提问来源于stack exchange,提问作者junis087678
相关产品推荐
相关产品推荐

