3分钟Solidity: 11.11 抢先交易Front Running

欢迎订阅专栏3分钟Solidity--智能合约--Web3区块链技术必学

如需获取本内容的最新版本,请参见 Cyfrin.io 上的Front Running(代码示例)

漏洞

交易在被挖出之前需要一定时间。攻击者可以监视交易池并发送一笔交易,使其在原始交易之前被打包进区块。这种机制可能被滥用,从而按照攻击者的利益重新排序交易。

typescript 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

/*
爱丽丝设计了一个猜谜游戏。
如果你能找到哈希值匹配目标的正确字符串,就能赢得10个以太币。让我们看看这个合约如何容易受到抢先交易的攻击。
*/

/*
1.  Alice 用 10 个以太币部署了 FindThisHash。
2.  Bob 找到了能哈希到目标哈希值的正确字符串("Ethereum")。
3.  Bob 调用 solve("Ethereum"),并将 gas 价格设置为 15 gwei。
4.  Eve 正在监视交易池,等待答案被提交。
5.  Eve 看到了 Bob 的答案,并以比 Bob 更高的 gas 价格(100 gwei)调用 solve("Ethereum")。
6.  Eve 的交易比 Bob 的交易先被挖出。Eve 赢得了 10 个以太币的奖励。

发生了什么?
交易在被挖出之前需要一些时间。
尚未被挖出的交易会被放入交易池中。
通常,gas 价格更高的交易会先被挖出。
攻击者可以从交易池中获取答案,然后发送一笔 gas 价格更高的交易,这样他们的交易就会比原始交易先被打包进区块。
*/

contract FindThisHash {
    bytes32 public constant hash =
        0x564ccaf7594d66b1eaaea24fe01f0585bf52ee70852af4eac0cc4b04711cd0e2;

    constructor() payable {}

    function solve(string memory solution) public {
        require(
            hash == keccak256(abi.encodePacked(solution)), "Incorrect answer"
        );

        (bool sent,) = msg.sender.call{value: 10 ether}("");
        require(sent, "Failed to send Ether");
    }
}

预防性技术

承诺-揭示方案

承诺方案是一种密码学算法,用于让某人能够承诺一个值,同时将其隐藏起来不让其他人知道,并具备在之后揭示该值的能力。承诺方案中的值具有约束力,意味着一旦承诺就无法更改。该方案分为两个阶段:承诺阶段,即选择并指定一个值;揭示阶段,即揭示并验证该值。

ini 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

/*
   现在让我们看看如何使用提交揭示方案来防止抢先交易。
*/

/*
1.  爱丽丝用10个以太币部署了SecuredFindThisHash合约。
2.  鲍勃找到了能哈希出目标哈希值的正确字符串("Ethereum")。
3.  接着鲍勃计算出keccak256(小写地址+答案+密钥)的哈希值。
    地址是他小写的钱包地址,答案是"Ethereum",密钥是仅鲍勃知道的密码(如"mysecret"),用于提交和揭示答案。
    keccak256("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266Ethereummysecret") = '0xf95b1dd61edc3bd962cdea3987c6f55bcb714a02a2c3eb73bd960d6b4387fc36'
4.  鲍勃调用commitSolution("0xf95b1dd61edc3bd962cdea3987c6f55bcb714a02a2c3eb73bd960d6b4387fc36"),
    以15 gwei的gas价格提交计算出的答案哈希。
5.  伊芙正在监视交易池等待答案提交。
6.  伊芙看到鲍勃的答案后,也调用commitSolution("0xf95b1dd61edc3bd962cdea3987c6f55bcb714a02a2c3eb73bd960d6b4387fc36"),
    并设置比鲍勃更高的gas价格(100 gwei)。
7.  伊芙的交易先于鲍勃的交易被打包,但她尚未获得奖励。
    她需要调用revealSolution()并提供准确的密钥和答案,因此她继续监视交易池,准备像之前那样抢跑鲍勃。
8.  接着鲍勃调用revealSolution("Ethereum", "mysecret"),gas价格设为15 gwei。
9.  假设监视交易池的伊芙发现了鲍勃揭示答案的交易,她也调用
    revealSolution("Ethereum", "mysecret"),并设置比鲍勃更高的gas价格(100 gwei)。
10.  这次伊芙的揭示交易再次先于鲍勃的交易被打包,但她会因"哈希不匹配"错误被回滚。
    因为revealSolution()函数会通过keccak256(发送者地址+答案+密钥)校验哈希值,所以这次伊芙未能赢得奖励。
11.  但鲍勃的revealSolution("Ethereum", "mysecret")通过了哈希校验,成功获得了10个以太币的奖励。
*/

contract SecuredFindThisHash {
    // 结构体用于存储提交详细信息
    struct Commit {
        bytes32 solutionHash;
        uint256 commitTime;
        bool revealed;
    }

    // 需要被解决的哈希值
    bytes32 public hash =
        0x564ccaf7594d66b1eaaea24fe01f0585bf52ee70852af4eac0cc4b04711cd0e2;

    // 获奖者地址
    address public winner;

    // 奖励价格
    uint256 public reward;

    // 游戏状态
    bool public ended;

    // 将提交详情与地址映射存储
    mapping(address => Commit) commits;

    // 检查游戏是否处于活动状态的装饰器
    modifier gameActive() {
        require(!ended, "Already ended");
        _;
    }

    constructor() payable {
        reward = msg.value;
    }

    /* 
       提交函数用于存储使用keccak256(小写地址+解决方案+密钥)计算的哈希值。
       用户只能提交一次,且游戏必须处于活动状态。
    */
    function commitSolution(bytes32 _solutionHash) public gameActive {
        Commit storage commit = commits[msg.sender];
        require(commit.commitTime == 0, "Already committed");
        commit.solutionHash = _solutionHash;
        commit.commitTime = block.timestamp;
        commit.revealed = false;
    }

    /* 
        获取提交详情的函数。它返回一个元组(solutionHash、commitTime、revealStatus);
        用户只有在游戏处于活动状态且已提交solutionHash时才能获取解决方案
    */
    function getMySolution()
        public
        view
        gameActive
        returns (bytes32, uint256, bool)
    {
        Commit storage commit = commits[msg.sender];
        require(commit.commitTime != 0, "Not committed yet");
        return (commit.solutionHash, commit.commitTime, commit.revealed);
    }
    /* 
        功能:揭示提交并获得奖励。
        用户只有在游戏处于活跃状态、且在当前区块之前已提交过solutionHash但尚未揭示的情况下,才能获取揭示的解决方案。
        该功能会生成一个keccak256(msg.sender + solution + secret)哈希值,并与之前提交的哈希值进行比对。
        由于msg.sender不同,假设提交已被包含在链上,抢先交易者将无法通过此验证。
        接着使用keccak256(solution)检查实际解决方案,如果匹配,则宣布获胜者,
        游戏结束并将奖励金额发送给获胜者。
    */

    function revealSolution(string memory _solution, string memory _secret)
        public
        gameActive
    {
        Commit storage commit = commits[msg.sender];
        require(commit.commitTime != 0, "Not committed yet");
        require(
            commit.commitTime < block.timestamp,
            "Cannot reveal in the same block"
        );
        require(!commit.revealed, "Already committed and revealed");

        bytes32 solutionHash =
            keccak256(abi.encodePacked(msg.sender, _solution, _secret));
        require(solutionHash == commit.solutionHash, "Hash doesn't match");

        require(
            keccak256(abi.encodePacked(_solution)) == hash, "Incorrect answer"
        );

        winner = msg.sender;
        ended = true;

        (bool sent,) = payable(msg.sender).call{value: reward}("");
        if (!sent) {
            winner = address(0);
            ended = false;
            revert("Failed to send ether.");
        }
    }
}

Try on Remix试用混音版

相关推荐
下海fallsea2 小时前
耐克要数字,不要故事
web3
DICOM医学影像18 小时前
3. go语言从零实现以太坊客户端 - 查询合约中账户余额
golang·区块链·智能合约·solidity·以太坊·web3.0
Rockbean1 天前
3分钟Solidity: 11.10 蜜罐
web3·智能合约·solidity
CryptoRzz2 天前
印度尼西亚(IDX)股票数据对接开发
java·后端·websocket·web3·区块链
DICOM医学影像2 天前
1. go语言从零实现以太坊客户端-JSON-RPC
golang·区块链·solidity·以太坊·web3.0·json-rpc·erc20
WebGISer_白茶乌龙桃2 天前
Cesium实现“悬浮岛”式,三维立体的行政区划
javascript·vue.js·3d·web3·html5·webgl
电报号dapp1195 天前
钱包开发:在虚无中为数字自我筑巢
游戏·去中心化·区块链·智能合约
Rockbean5 天前
3分钟Solidity: 11.1 重入攻击
web3·智能合约·solidity
Rockbean5 天前
3分钟Solidity: 10.6 时间锁定
web3·智能合约·solidity