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

相关推荐
链上罗主任2 天前
《以太坊十年:从概念验证到模块化架构》
去中心化·区块链·智能合约
开开心心_Every2 天前
Win10/Win11版本一键切换工具
linux·运维·服务器·edge·pdf·web3·共识算法
xdpcxq10292 天前
智能合约 Fuzzing 工具
智能合约
devmoon3 天前
Polkadot Hub 智能合约中的账户体系
web3·区块链·智能合约·polkadot
暴躁小师兄数据学院3 天前
【WEB3.0零基础转行笔记】编程语言篇-第一讲:Go语言基础及环节搭建
笔记·golang·web3·区块链
老蒋每日coding3 天前
Web3 开发入门:用 Ethers.js 玩转以太坊交易与合约
web3·区块链
暴躁小师兄数据学院4 天前
【WEB3.0零基础转行笔记】基础知识篇—第一讲:区块链基础
笔记·web3·区块链
yTfDRlpl5 天前
探索三电平T型逆变器仿真模型:MATLAB Simulink之旅
智能合约
voidmort5 天前
web3中的共识:PBFT、Tendermint 与 DAG 共识
web3·区块链
CertiK6 天前
CertiK登上达沃斯官网,Web3安全进入主流视野
安全·web3