Ethereum security
Ethereum in one slide
Copyright By PowCoder代写 加微信 powcoder
● States S = a map from addresses to state
● Inputs I (transactions)
● Transition f:
○ validate signature
○ run to.code(from, data, value, startgas, gasprice)
● Start state: ∅
The full* Ethereum blockchain structure
prev height nonce difficulty
state root
miner extra
transaction root receipt root
The full* Ethereum blockchain structure
prev height nonce difficulty
state root
miner extra
transaction root receipt root
The full* Ethereum blockchain structure
prev height nonce difficulty
state root
miner extra
transaction root receipt root
final state
log output
Ethereum addresses can be accounts or contracts
H(pub_key)
H(creator, nonce)
Merkle storage root
ETH balance
#transaction sent
Three* types of transaction in Ethereum
Three* types of transaction in Ethereum
transfer_val
Three* types of transaction in Ethereum
transfer_val
transfer_val
Security by example: Rock-paper-scissors
Warmup: Rock-Paper-Scissors in Ethereum
1. function add_player() payable; Takes player’s deposit of 1 ETH.
2. function input(uint choice); Records player’s choice (0 or 1 or 2)
3. function check_winner(); Decides who wins, pays the winner
Problem: Front Running
Rock! Paper!
Seconds go by….
Solution attempt #1: post hash of move first
H0 = hash(“Rock” )
H1 = hash(“Scissors” )
Commit to H1
Commit to H0
Commitment Deadline
Reveal: “Rock”
Reveal: “Scissors”
Problem: enumeration of possible moves
H0 = hash( “Rock” )
commit(H0)
H1 = hash(“Paper” )
Check if H0 in {H(“Rock”), H(“Scissors”),
Commit to H1 H(“Paper”)}
Front-Running generally avoided with commitments
nonce0 ← random()
H0 = hash(nonce0, “Rock” )
nonce1 ← random()
H1 = hash(once1, “Scissors” )
Commit to H1
Commit to H0
Commitment Deadline
Reveal: nonce0, “Rock”
nonce1, “Scissors”
Cryptographic commitments are a useful tool
● General structure:
○ c = Commit(x, r)
○ VerifyOpening(x, c, r) →yes/no
● Hash-based commitments: Commit(x, r) = H(x || r)
○ Simple hash is sufficient only if x has high min-entropy
● Pedersen commitments: Commit(x, r) = gxhr
○ Addition, proofs of equality, proofs of knowledge, range proofs…
Avoid Front-Running with Commitments
nonce0 ← Random String
H0 = hash( nonce0, “Rock” )
nonce1 ← Random String
H1 = hash( nonce1, “Scissors” )
Commit to H1
Commitment Deadline
Commit to H0
nonce0, “Rock”
nonce1, “Scissors”
What remaining problems are there?
Remaining problem: what if one player drops out?
H0 = hash(
Commitment Deadline
nonce0, “Rock” ) Commit to H0
nonce0 ← Random String
nonce1 ← Random String
H1 = hash( nonce1, “Scissors” )
Commit to H1
nonce0, “Rock”
Hmmmm I’m screwed if I reveal…
Need a second deadline to open commitments
H0 = hash(
Commitment Deadline
nonce0, “Rock” ) Commit to H0
nonce0 ← Random String
nonce1 ← Random String
H1 = hash( nonce1, “Scissors” )
Commit to H1
nonce0, “Rock”
nonce1, “Scissors”
Reveal Deadline
Several ways to handle aborts
● Player who aborted loses by default
● Fidelity bonds-bond is captured if any player aborts
● Cryptographic solutions
○ Opening escrowed with third party(ies)
○ Timed commitments
Commit-reveal with penalties nonce0 ← Random String
nonce1 ← Random String
H1 = hash( nonce1, “Scissors” )
Commit to H1
nonce1, “Scissors”
H0 = hash(
Commitment Deadline
nonce0, “Rock” ) Commit to H0
nonce0, “Rock”
Reveal Deadline
Auditing smart-contracts is difficult but crucial!
struct Player {
uint choice;
uint addr; }
function add_player() payable { assert(num_players < 2); assert(msg.value >= 2000 szabo); reward += msg.value; player[num_players].addr = msg.sender; num_players++;
function input(uint choice, uint idx) {
assert(msg.sender == player[idx].addr);
player[idx].choice = choice;
uint num_players = 0;
uint reward = 0;
mapping (uint => Player) player;
function check_winner() returns(int) { var p0_choice = player[0].choice; var p1_choice = player[1].choice; if (p0_choice – p1_choice % 3 == 1)
// Player 0 wins
player[0].addr.send(reward);
if (p0_choice – p1_choice % 3 == 2)
// Player 1 wins
player[1].addr.send(reward);
player[0].addr.send(reward/2);
player[1].addr.send(reward/2);
Can you spot the bug here?
contract Bank {
mapping (address => uint) balances;
function withdraw() public{
uint to_send = balances[tx.origin];
balances[tx.origin] = 0;
payable(tx.origin).transfer(to_send);
Inter-contract calls
Three levels of contract call in Ethereum Original message:
Results of a call to C:
DELEGATE CALL
msg.sender
x’≤ B.balance
(as specified)
s’ ≤ gas remaining
storage updated
Solidity syntax for calling other contracts
● a.send(x) sends x to address a
○ returns 0 if this fails due to call stack
● foo.call.value(3).gas(20764)( bytes4(sha3(“bar()”)));
○ also callcode, delegatecall
○ default is 0 value, all available gas
● new constructor deploys a new contract ○ Careful, it’s expensive!
Smart contracts code is fixed forever. Calls required to update functionality
Callers can choose how much gas to send
function b(): 5 assert msg.gas == 10 y = C.c.gas(5)()
assert(y == 0);
// out of gas
return “Hello”
function a(): 10 assert(msg.gas == 100);
x = B.b.gas(10)()
return x + “ World!”
function c():
assert(msg.gas == 5
while (true) {
return “Bonjour”
“Hello World!”
Out of gas
Subtleties to contract calls
● Data: unlimited params/return values
○ Direct mapped to memory address + size
● Exceptions: out of gas, bad jump, etc.
○ No state changes persisted
○ Control returns to caller
● Call stack limit: 1024
○ Calls from 1024th frame will fail
Many idioms for calling functions
100 Amount in Ether 25 Amount in gas 55 Data argument
all of it all of it
Exception behavior
returns 0 returns 0 returns 0 returns 0
returns 0 returns 0 exception exception
send(recipient, 100) send(25, recipient, 100) recipient.foo(55) recipient.foo(55, gas=25)
recipient.send(100) recipient.call{value: 100, gas:25}() recipient.foo{value:100}(55) recipient.foo(55)
The confused deputy problem
contract Bank {
mapping (address => uint) balances;
function withdraw() public{
uint to_send = balances[tx.origin];
balances[tx.origin] = 0;
payable(tx.origin).transfer(to_send);
The confused deputy problem
function a(): Caller A B.foo();
function foo(): … Bank.withdrawy()
A’s money is withdrawn. Fortunately it is sent to A in this case. But A didn’t really authorize this…
contract Bank {
mapping (address => uint) balances;
function withdraw() public{
uint to_send = balances[tx.origin];
balances[tx.origin] = 0;
payable(tx.origin).transfer(to_send);
Unchecked send and other problems
The E therpot Story
August 26, 2015
How Etherpot Works
1. Each round lasts 1 day. (Everything is reset after a round.)
2. Users deposit money to purchase Tickets at a fixed price.
Each “Subpot” holds a fixed number of tickets.
3. After the round ends, the next N block hashes are used as random seeds to determine the winners of N subpots.
(1 block for each subpot)
Block hashes used as random seeds
Bugs in Etherpot
Within days, hundreds of dollars of Eth paid out to the wrong recipient.
Multiple more bugs were found.
Call stack hazard:
Maximum call stack depth is 1023
Suppose we call A.recurse(0). Does Alice get 100?
Contract A:
Stack depth = 1023
Contract B:
Stack depth = 0 Stack depth = 1
Stack depth = 1022
Contract C:
function() {
Alice.send(100);
function recurse(int i) {
if (i == 1022)
return B.b() + “World”;
else recurse(i+1);
function b() {
C.send(100);
return “Hello “;
return OK; }
A.recurse(0) A.recurse(1)
…. A.recurse(1022)
Returns “Hello World!”
https://gist.github.com/amiller/665cc46970f2c0684d2a
The Callstack hazard in Etherpot ….
Attack Contract:
function recurse(int i) {
if (i == 1022)
Etherpot.cash(r,idx)
else recurse(i+1);
return OK;
Result: attacker can destroy all the funds in the contract
General problem: unchecked errors (particularly send) contract Bank {
mapping (address => uint) balances;
function withdraw() public{
payable(msg.sender).send(to_send);
balances[msg.sender] = 0;
send does not throw an exception if it fails
● Failure indicated by a return value of zero
○ But many programmers failed to check this
● Result: send() was deprecated in favor of transfer()
○ transfer throws an exception on failure
● Modern Solidity also deprecates transfer()…
○ For different reasons
Other unchecked errors: King of the Ether throne
Call stack hazard:
Exceptions are not propagated (for default function)
Example: an exception caused by Out of Gas
Contract A:
Contract Wallet:
function() { // handle payment
// Do more than 2300 // gas worth of work
function payWinnings() {
Winner.send();
selfdestruct;
Result: If you played King of using a “Wallet Contract”, your winnings would be destroyed forever.
Minimum amount of gas (2300)
Out of gas
EtherPot’s incentive mechanism
EtherPot’s incentive mechanism
● Can miners influence the outcome of lottery? ○ Yes – by withholding blocks
● Solution: “subpots” smaller than block reward
● Problem: GHOST
○ Withheld blocks can still get 88% reward
Secure randomness generation is very difficult ● Bad (but cheap) solution: use block hashes
○ Has been manipulated in practice
● Better solution: have participants commit/reveal
○ Need protocol to incentivize revealing!
● Best: use a dedicated randomness beacon ○ Will be discussed in future lectures
EtherPot didn’t even use block hashes correctly
return uint(block.blockhash(blockIndex));
Index of the block that determines lottery outcome
Hash of that block
Parity: a tale of two bugs
Bug #1: Parity used an indirect constructor
constructor(…) {
initWallet(…);
function initWallet(address[] _owners, uint _required, uint _daylimit){
initDaylimit(_daylimit);
initMultiowned(_owners, _required);
initWallet is internal, so can’t be called by the outside… right?
Bug #1: Parity left an open-dispatch function
function() payable {
// just being sent some cash?
if (msg.value > 0)
Deposit(msg.sender, msg.value);
else if (msg.data.length > 0)
_walletLibrary.delegatecall(msg.data);
this function allows anybody to call anything!
Attacker combined both to take over many wallets
Bug #2: Parity was meant to only be a library
address Parity;
function a():
Parity.delegatecall(…);
delegetcall() means the code executes in B’s storage space
contract Parity {
mapping (address => uint) balances;
function transfer(…) public{
Bug #2.1: Some clients used the Parity directly
address Parity;
function a():
Parity.call(…);
contract Parity {
mapping (address => uint) balances;
function transfer(…) public{
Bug #2: Parity’s contract was left uninitialized
contract Parity {
// throw unless the contract is not yet initialized.
modifier only_uninitialized {
if (m_numOwners > 0)
// constructor — just pass on the owner array to the multiowned and
function initWallet(address[] _owners) only_uninitialized
shouldn’t matter because it’s not meant to be used directly?
Bug #2: Parity’s contract featured a kill() function
contract Parity {
// self-destruct the contract
function kill() only_owners
Bug #2: Library contract was killed in November 2017
Bug #2: Thousands of reliant contracts no longer work!
address Parity;
function withdraw():
Parity.delegatecall(…);
Funds frozen in place permanently
contract Parity {
mapping (address => uint) balances;
function transfer(…) public{
Bug #2: Frozen parity funds have never been recovered
Parity lesson: even the experts can get it wrong
Ethereum co-founder
Parity co-founder
How can we build high-assurance smart-contracts?
● Language design
○ Solidity has made many changes to reduce bugs
● Formal verification
○ Requires a spec to check against!
● Bug-finding tools
○ Not guaranteed to be complete, but helpful
● Human-driven code audits
Re-entrancy
Re-entrancy hazards in Contract:
public int totalReceived = 0;
function recv() {
EventMoneyReceived(msg.value);
totalReceived += msg.value;
function doWithdraw() {
A.withdraw();
Balance:0100
function withdraw()
only(callee) {
if (balance > 0)
balance = 0;
Contract A:
public address callee;
public int balance =
callee.recv.value(balance)();
Done! Callee withdraws 100.
Re-entrancy hazards in Ethereum
23100 balances[attacker] -01-10200
Contract A:
mapping (address => int64) balances;
function withdraw(uint x) {
if (balances[msg.sender] >= x)
callee.recv{value:balance}();
balances[msg.sender] -= x;
Balance 132000
Attacker Contract
function startAttack() {
A.withdraw(100);
function recv() {
if (counter == 2) return;
Counter += 1;
A.withdraw(100);
Fixes to re-entrancy
● only use send()/transfer() to limit gas ○ Now deprecated
● Use modifiers to lock functions
● Checks/effects/interaction ordering
Limiting gas available to the callee
● send()/transfer() send 2300 gas by default ○ Not enough to do another call… today
● Call the callee’s fallback()/receive() function
● Not guaranteed to be future proof-gas costs change!
Using modifiers to lock a function
bool reentrantLock;
modifier noReentrancy{
if (!reentrantLock){
reentrantLock = true;
reentrantLock = false;
The “Checks / Effects / Interactions” paradigm
A Best Practice guideline for safe smart contract behavior
When receiving a message, do the following in order:
Contract A:
public address callee;
public int balance = 100;
function withdraw()
only(callee) {
if (balance <= 0) return;
var toSend = balance;
1. Perform all input validation and checks on current state. Discard the message if validation fails.
2. Update local state.
3. Finally, pass on interactions to trigger other contracts.
balance = 0;
callee.recv.value(toSend)();
a Blockchain + IoT company
Example use case:
1. AirBnB user submits payment to the Ethereum blockchain
2. Slock Home Server (Ethereum client) receives the transaction
3. Power switch connected to Home Server receives “unlock” command, unlocks the door
slock.it built The DAO as a custom fundraising tool
“DAO”: Decentralized Autonomous Organization (coined by Vitalik in 2013) Built by slock.it to raise funds for their company
Main idea: A decentralized hedge fund
Investors contribute funds, receive ownership “tokens”
Investors jointly decide how to spend funds, by voting in proportion to tokens Many additional mechanisms:
“Splitting” to prevent hostile takeover Reward disbursing
Raised ~150 million dollars in ~ 1 month
The attacker built a contract to drain the DAO
Attacker contract calls “withdraw” again before returning
Timeline and Aftermath of The DAO
- June 12: slock.it developers announce that the bug is found, but no funds at risk - June 17 (Morning): attacker drains 1⁄3 of the DAO’s Ether ($50M) over 24 hrs
Attacker’s funds were trapped in a subcontract for 40 days (July 27)
- June 17 (Evening): Eth Foundation proposes a “Soft Fork” to freeze the funds - June 28: Cornell freshmen identify a flaw in the Soft Fork Proposal
- July 15 (Morning): Eth Foundation proposes a “Hard Fork” to recover funds
- July 15 (Evening): “Ethereum Classic” manifesto published on github
- July 19: “Hard Fork” moves funds from attacker’s contract to recovery contract
Ethereum Classic blockchain survives and is traded on exchanges Both Ethereum and Ethereum Classic are both around, reached new peaks
Why can’t we soft-fork to prevent theft?
Add a new rule to Ethereum:
If a transaction moves the attacker’s money, it is invalid
function slow_invalid(byte[] input, int x, byte[target] target){
while(sha3(x) != target)
send(sha3(x) ^ input);
Reentrancy was known before the DAO
2014: Forum post on re-entrancy hazards - Suggested mitigations at the language level
Reentrancy was known before the DAO
2014: Forum post on re-entrancy hazards - Suggested mitigations at the language level
2015: ETH-commissioned report on EVM security - Official ETH examples (crowdfund.se) also exhibit this flaw
(they happen not to be exploitable, but without showing why)
“the refund callback could make a new donation, triggering another refund cycle, potentially double-refunding the earlier contributions, or failing to refund later ones”
2016: The DAO happens anyway
Solution today: implement ERC20
Should Ethereum hard fork?
History of Smart contracts
“Smart contracts” conceptualized by Szabo in 1994
A smart contract is a computerized transaction protocol that executes the terms of a contract. The general objectives are to satisfy common contractual conditions (such as payment terms, liens, confidentiality, and even enforcement), minimize exceptions both malicious and accidental, and minimize the need for trusted intermediaries. Related economic goals include lowering fraud loss, arbitrations and enforcement costs, and other transaction costs.
- “The Idea of Smart Contracts”
A “dumb contract” example: pay for a hash pre-image
Alice will reveal to Bob a value x such that SHA-256(x) = 0x2a...
In exchange, Bob will pay US$10.
If Alice does not reveal by July 1, 2017, then she will pay a penalty of US$1 per day that she is late, up to US$100.
Traditional contracts vs. smart contracts
Traditional
specification
dispute resolution
nullification
Traditional contracts vs. smart contracts
Traditional
specification
Natural language + “legalese”
Signatures
Digital signatures
dispute resolution
Judges, arbitrators
Decentralized platform
nullification
As specified
Trusted third party
程序代写 CS代考 加微信: powcoder QQ: 1823890830 Email: powcoder@163.com