跳到主要内容

签名重放攻击

签名重放攻击

The signature replay attack that led to the theft of 20 million $OP tokens from Wintermute exposes critical vulnerabilities in the management of digital signatures on blockchain platforms. This incident was primarily triggered by a signature replay but was compounded by an additional transactional error with Optimism, a Layer-2 Ethereum scaling solution. In this case, the tokens were mistakenly sent to a multisignature wallet address that had not yet been properly initialized on the Optimism network.

For a more detailed report on this incident, you can read further on Blockworks and Decrypt:

数字签名在区块链技术中发挥着至关重要的作用,用于识别数据的签名者并确保数据的完整性。 When users initiate transactions, they use their private keys to sign them. This allows anyone to verify that the transactions truly originate from the stated accounts. 此外,智能合约经常使用ECDSA算法在执行关键功能(如代币铸造或转账)之前验证链下签名。

通常有两种常见的签名重放攻击类型:

  1. 标准重放攻击: 一次性签名被反复利用,导致对NBA的“The Association”系列NFT进行了数千次未经授权的铸造。 有关此签名重放攻击的详细信息,请参阅Decrypt的报告: NBA在以太坊NFT发售中出现问题,'The Association'遭受攻击 - Decrypt.

该文章涵盖了在NFT发布过程中被利用的安全漏洞,导致了数千次未经授权的铸造。

  1. Cross-chain Replay: A signature intended for use on one blockchain is reused on another. This type of attack was exploited in the theft of 20 million $OP tokens from Wintermute.

易受签名重放攻击的合约示例

下面的SigAuth合约是一个ERC20代币合约,该合约包含一个可被签名重放攻击利用的铸造函数。 它使用链下签名来允许白名单地址铸造指定数量的代币。 The contract stores a signer address to verify the authenticity of signatures.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

contract SigAuth is ERC20, Ownable {
address public authorizedSigner;

constructor() ERC20("SignatureToken", "SIGT") {
authorizedSigner = msg.sender;
}

function vulnerableMint(address recipient, uint amount, bytes memory signature) public {
bytes32 messageHash = keccak256(abi.encodePacked(recipient, amount));
bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(messageHash);
require(ECDSA.recover(ethSignedMessageHash, signature) == authorizedSigner, "Invalid signature!");
_mint(recipient, amount);
}

// Other helper functions...
}

Note: The vulnerableMint() function does not check if the signature has been used before, allowing the same signature to be reused for unlimited token minting.

预防措施

To prevent signature replay attacks, you can use the following methods:

  1. Tracking Used Signatures: Record signatures that have been used for operations like token minting to prevent their reuse.

    // Record the minted addresses
    mapping(address => bool) public alreadyMinted;

    function secureMint(address recipient, uint amount, bytes memory signature) public {
    require(!alreadyMinted[recipient], "Tokens already minted for this address");
    bytes32 messageHash = keccak256(abi.encodePacked(recipient, amount));
    bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(messageHash);
    require(ECDSA.recover(ethSignedMessageHash, signature) == authorizedSigner, "Invalid signature!");
    alreadyMinted[recipient] = true;
    _mint(recipient, amount);
    }
  2. 包含Nonce和ChainID: 在签名消息中加入一个nonce ( 随每笔交易递增) 和chainID以防止标准重放攻击和跨链重放攻击。

    uint public nonce = 0;

    function nonceMint(address recipient, uint amount, bytes memory signature) public {
    bytes32 messageHash = keccak256(abi.encodePacked(recipient, amount, nonce, block.chainid));
    bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(messageHash);
    require(ECDSA.recover(ethSignedMessageHash, signature) == authorizedSigner, "Invalid signature!");
    _mint(recipient, amount);
    nonce++;
    }
  3. 使用EIP-712处理结构化数据:实施EIP-712,以创建更安全和结构化的数据签名体验,有助于防止在不同上下文和合约中的签名重放攻击。

    // Utilizing EIP-712 to create a typed structured data signature
    using EIP712 for bytes32;

    function eip712Mint(address recipient, uint amount, bytes memory signature) public {
    bytes32 structHash = keccak256(abi.encode(
    keccak256("Mint(address recipient,uint256 amount,uint256 nonce)"),
    recipient,
    amount,
    nonce
    ));
    bytes32 digest = _hashTypedDataV4(structHash);
    require(ECDSA.recover(digest, signature) == authorizedSigner, "Invalid EIP-712 signature!");
    _mint(recipient, amount);
    nonce++;
    }

您可以在 OpenZeppelin的EIP-712 文档中找到更详细的执行指南和工具。

4.实施CIP-23以提高跨链操作的安全性: CIP-23 是以太坊 EIP-712 在Conflux core space的改编版本, 旨在增强跨链操作的安全性。 它引入了特定措施来防止重放攻击,确保对于EVM兼容链的签名不能被重放到Conflux core space,反之亦然。

更多信息和详细准则可在 Conflux CIP-23找到。