You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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"
    );
}

问题根源

你的签名生成逻辑和合约验证逻辑存在两处核心不匹配:

  1. 参数编码哈希不一致:合约用abi.encodePacked拼接参数后做keccak256哈希,而你用solidityKeccak256(带类型的标准ABI编码)生成哈希,两者原始哈希值完全不同。
  2. 签名前缀缺失:合约对原始哈希做了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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 20:27:13