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试用混音版

相关推荐
许强0xq14 小时前
订单流战争:AI、区块链与市场透明度的终极博弈
web3·区块链·智能合约·solidity·dapp
潇楠Web3哨兵17 小时前
Web3多功能监控软件 V10 :从代码层面深度剖析一款商业级双链监控系统的实现艺术!
web3
迷藏4941 天前
**发散创新:基于Solid协议的Web3.0去中心化身份认证系统实战解析**在Web3.
java·python·web3·去中心化·区块链
AI_Claude_code1 天前
ZLibrary访问困境方案三:Web代理与轻量级转发服务的搭建与优化
爬虫·python·web安全·搜索引擎·网络安全·web3·httpx
开开心心_Every2 天前
内存清理软件灵活设置,自动阈值快捷键清
运维·服务器·pdf·web3·电脑·excel·共识算法
好多大米4 天前
W2D3-Foundry 测试
区块链·solidity
木西5 天前
深度复刻 Sky Protocol:基于 OpenZeppelin V5 与 Solidity 0.8.24 的工程实践
web3·智能合约·solidity
OxYGC5 天前
[Web3] 一文读懂区块链中的账本类型
web3·区块链
Joy T8 天前
【Web3】深度解析 NFT 跨链智能合约开发:原生资产与衍生包装合约架构实战
git·架构·web3·区块链·node·智能合约·hardhat
Joy T9 天前
【Web3】智能合约质量保障工程:从单元测试到 Gas 效能优化
单元测试·log4j·web3·智能合约·hardhat