首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >当尝试使用支持ERC20的EIP712令牌的“许可”特性时,会遇到“无效签名错误”

当尝试使用支持ERC20的EIP712令牌的“许可”特性时,会遇到“无效签名错误”
EN

Ethereum用户
提问于 2021-02-13 04:48:15
回答 1查看 6.3K关注 0票数 5

我目前正在编写一些可靠的智能契约,并通过web3与它们进行交互。前提是令牌可以存放在池中,然后可以在稍后的日期提取。

存款运行良好--目前,应用程序要求用户“批准”该应用程序代表他们使用令牌,并将其存入池中。下面是处理将令牌存放到池中的相关智能契约代码

代码语言:javascript
复制
function deposit(
    address token,
    uint tokenAmount,
    bytes32 request,
    address to,
    uint deadline
) external virtual override ensure(deadline) returns (uint amount, uint liquidity) {
    address pool = ISafepayFactory(factory).getPool(token);
    require(pool != address(0), 'SFPY_ROUTER: UNSUPPORTED POOL');
    TransferHelper.safeTransferFrom(token, msg.sender, pool, tokenAmount);
    liquidity = ISafepayPool(pool).mint(to);
    amount = tokenAmount;
    emit Deposit(msg.sender, to, token, request, amount);
}

这假设已经为该令牌预先创建了一个池。一旦令牌被转移,池“造币厂”的流动性令牌就会传递给用户。

当用户想要撤回这些令牌时,应用程序首先要求用户签署一条“许可”消息(目前正在使用MetaMask)。这是相关的JS代码

等待poolContract.nonces(帐户)

代码语言:javascript
复制
const EIP712Domain = [
  { name: 'name', type: 'string' },
  { name: 'version', type: 'string' },
  { name: 'chainId', type: 'uint256' },
  { name: 'verifyingContract', type: 'address' }
]
const domain = {
  name: 'Safepay LP Token',
  version: '1',
  chainId: chainId,
  verifyingContract: pool.liquidityToken.address
}
const Permit = [
  { name: 'owner', type: 'address' },
  { name: 'spender', type: 'address' },
  { name: 'value', type: 'uint256' },
  { name: 'nonce', type: 'uint256' },
  { name: 'deadline', type: 'uint256' }
]
const message = {
  owner: account,
  spender: ROUTER_ADDRESS,
  value: liquidityAmount.raw.toString(),
  nonce: nonce.toHexString(),
  deadline: deadline.toNumber()
}
const data = JSON.stringify({
  types: {
    EIP712Domain,
    Permit
  },
  domain,
  primaryType: 'Permit',
  message
})

library
  .send('eth_signTypedData_v4', [account, data])
  .then(sig => {
    console.log(sig)
    const recovered = recoverTypedSignature_v4({
      data: JSON.parse(data),
      sig,
    })
    // Weirdly the address is recovered correctly here.
    console.log(getAddress(recovered) === getAddress(account))
  
    return splitSignature(sig)
  })
  .then(signature => {
    setSignatureData({
      v: signature.v,
      r: signature.r,
      s: signature.s,
      deadline: deadline.toNumber()
    })
  })
  .catch(error => {
    console.log(error)
    // for all errors other than 4001 (EIP-1193 user rejected request), fall back to manual approve
    if (error?.code !== 4001) {
      approveCallback()
    }
  })

在签署消息后,该应用程序在带有签名参数的路由器智能合同上调用了一个“估计气体”函数。这是withdrawWithPermit函数

代码语言:javascript
复制
function withdrawWithPermit(
  address token,
  uint liquidity,
  uint amountMin,
  address to,
  uint deadline,
  bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amount) {
    address pool = ISafepayFactory(factory).getPool(token);
    require(pool != address(0), 'SFPY_ROUTER: UNSUPPORTED POOL');
    uint value = approveMax ? uint(-1) : liquidity;
    ISafepayPool(pool).permit(msg.sender, address(this), value, deadline, v, r, s);
    amount = withdraw(token, liquidity, amountMin, to, deadline);
}

“池”合同继承自ERC20合同,并具有一些额外的功能,以“薄荷”和“烧掉”流动性令牌。但这里是游泳池的相关“许可”功能

代码语言:javascript
复制
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
    require(deadline >= block.timestamp, 'SFPY: EXPIRED');

    bytes32 hashStruct = keccak256(
      abi.encode(
        PERMIT_TYPEHASH,
        owner,
        spender,
        value,
        nonces[owner]++,
        deadline
      )
    );

    bytes32 digest = keccak256(
      abi.encodePacked(
          '\x19\x01',
          DOMAIN_SEPARATOR,
          hashStruct
      )
    );
    
    address recoveredAddress = ecrecover(digest, v, r, s);
    require(recoveredAddress != address(0) && recoveredAddress == owner, 'SFPY: INVALID_SIGNATURE');
    _approve(owner, spender, value);
}

这是PERMIT_TYPEHASH

代码语言:javascript
复制
bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

和DOMAIN_SEPARATOR

代码语言:javascript
复制
DOMAIN_SEPARATOR = keccak256(
   abi.encode(
        keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
        keccak256(bytes(name)),
        keccak256(bytes('1')),
        chainId,
        address(this)
   )
);

(为非常长的上下文表示歉意,但如果你已经做到了这一点,这就是乐趣的起点)

每当应用程序(通过MetaMask)向该函数发出请求时,智能契约就会抛出SFPY: EXPIRED错误或SFPY: INVALID_SIGNATURE错误。

INVALID_SIGNATURE很有趣,因为在提出请求之前,应用程序能够正确恢复签名者的地址(如JS片段所示)。

然而,对智能契约的调用总是失败的。我假设DOMAIN_SEPARATOR、PERMIT_TYPEHASH或其他方面都有错误,但我希望有人能帮上忙!

乐意添加更多的代码片段,屏幕截图,视需要。

(示例错误屏幕截图附后)

EN

回答 1

Ethereum用户

发布于 2021-02-14 19:24:00

经过大量的调试,我终于找到了这个问题。原来,chainid OPCODE在Ganche中不能正常工作(如果您使用他们的UI,修复程序已经在ganache-cli项目中发布)。

缺少正确的功能链会导致DOMAIN_TYPEHASH计算错误,从而阻止ecrecover从签名中生成正确的地址。

您可以跟踪这些问题。

链ID操作码:硬编码在1 #1643

伊斯坦布尔chainId操作码返回坏值#515

希望这对其他人有帮助

票数 6
EN
页面原文内容由Ethereum提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://ethereum.stackexchange.com/questions/93440

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档