How to perform a 51% attack

It’s all about forks

A little click-bait title to surf on the hype produced by the famous consensus attack, this propaganda which would implicitly suggest that Bitcoin is not so secure… In this post I will talk about the infamous (but not well known) so-called 51% attack. I will start by describing what is it, under which conditions it can be performed, then how to do it, and finally what I exactly did to attack the Insacoin network and rewrite the whole history since the Genesis block.

51% : The infamous

(Yes I just made a reference to Mobb Deep in an IT post, so what ?)

If 2017 has been the ICO year, then 2018 has been the 51’s one. With a lot of networks being attacked (the most famous being, I think, Bitcoin Private) and a lot of news spreading the word that, to “take the control of Bitcoin” (yes I have read that) you “just have to have more than 51% of the hashpower”. It’s of course an insanity but the fear, the uncertainty (and moreover the misconception), and the doubt can be propagated very quickly. They dared to use the word “just” when there are more hashes calculated on the Bitcoin network every second than there are meters separating us from the sun (source: hashrate exa sun-moon distance).

What a 51% attack is not about

  • No creation of coins out of thin air.
  • No stolen coin from an address.

What a 51% attack is about

  • Double-spent transaction(s).
  • Censorship possibility. You can choose to not include some transactions.

What is a 51% attack

It consists of forking the block chain, which means changing the history. This is a fork imposed to the network, because of the consensus rules enforced by every nodes operating on it and is called 51% because there is chance that an attacker can impose a fork to the network if he possesses more hashpower than the rest of the network (which means having >50% of the total hashpower), otherwise the odds of achieving it decrease exponentially.

We can then wonder why the nodes enforce this consensus rule which can make them being imposed a chain from an attacker, and Satoshi already answered the question in the Bitcoin whitepaper :

Nodes always consider the longest chain to be the correct one and will keep working on extending it. If two nodes broadcast different versions of the next block simultaneously, some nodes may receive one or the other first. In that case, they work on the first one they received, but save the other branch in case it becomes longer. The tie will be broken when the next proof-of-work is found and one branch becomes longer; the nodes that were working on the other branch will then switch to the longer one.

Without this rule, fork would occur frequently on the peer-to-peer network that Bitcoin is, and a consistency in the history could not be found without an authority or a consensus rule (to choose which block will be added to the chain).

What is the scope of such an attack

Depending on the hash power the attacker possesses, he can rewrite one or more blocks in the past. If he generates valid blocks faster than the rest of the network does he can then make his chain accepted by the network, transactions that have been valid for some time in rewritten blocks becoming then obsolete. A possible attack could be :

  • Mallory (the attacker) starts generating blocks on the block chain at height n. When he finds a block he just goes on generating blocks on top of it without advertising the network he found a solution to the PoW challenge.
  • Mallory goes to a shop and buys an apple from Alice, paying 2 bitcoins. The transaction is included in block n+1. Alice waits for another confirmation (an apple is not worthless) and then Mallory leaves. Bitcoin network’s block chain is at height n+2.
  • Mallory comes back to its home, network is at height n+3. Fortunately Mallory has a lot of hashpower and he generated 4 empty blocks (thus without including the transaction he made to the shop) with the same difficulty than the network, the height of his block chain is n+4.
  • Mallory broadcasts his chain and the nodes are forced to accept it. Mallory has still his 2 bitcoins and the apple from the shop.

The same scheme can be followed with an exchange, and that’s why they wait a lot of confirmations when you deposit before you can use your funds.

How to perform the attack (in theory)

Let’s dive a little deeper and inspect what should be done. To recap we should construct a chain on our own, broadcast it to the other nodes and make them accept it. The last part is the important one : under which conditions the blockchain we made will be accepted by other nodes ?

To answer this question we don’t have too many choices : since the Bitcoin protocol does not have a specification but a reference implementation, we need to checkout what will be done by a node when receiving our chain. This means inspecting the bitcoin-core code.

The answer is : the client (/node) will compare the total work (nChainWork ) of a block, using the CBlockIndexWorkComparator structure (permalink to the comparator, permalink to the structure of a block) :

struct CBlockIndexWorkComparator
{
bool operator()(const CBlockIndex *pa, const CBlockIndex *pb) const {
// First sort by most total work, ...
if (pa->nChainWork > pb->nChainWork) return false;
if (pa->nChainWork < pb->nChainWork) return true;

// ... then by earliest time received, ...
if (pa->nSequenceId < pb->nSequenceId) return false;
if (pa->nSequenceId > pb->nSequenceId) return true;

// Use pointer address as tie breaker (should only happen with blocks
// loaded from disk, as those all have id 0).
if (pa < pb) return false;
if (pa > pb) return true;

// Identical blocks.
return false;
}
};

The two last conditions, are just here in (the unlikely) case the hashpower of the two blocks is equal. The thing that real matter is the chainwork : the approximate number of attempts necessary to find a solution for the PoW.

It means that this is not actually the longest chain but the chain with the more work that is accepted : you can impose the network a shorter chain if you mined blocks with a higher difficulty (this what I actually did on Insacoin).

How to perform the attack (in practice)

Because coding the whole process of forming the chain and broadcasting it was both long and error-prone, I took the original Insacoin client and modified it to make kind-of an attacker client. I’m not a master C++ programmer and everything I did was very hacky. However, as a request and in order to give a tutorial here is the description of my modifications :

I added a startup parameter, -dontbroadcastblocks which when specified, would stop the exchange of blocks with the network :

doNotBroadcastBlocks = GetBoolArg("-dontbroadcastblocks", false);

in init.cpp. I added

extern bool doNotBroadcastBlocks; # .h
bool doNotBroadcastBlocks = false; # .cpp

in main.h and main.cpp (because this is where are written the protocol messages in insacoin-core. In the latest bitcoin-core version, they are in net_processing.cpp) in order to check if the parameter has been passed to the daemon.

Then I made the code of some messages of the protocol to be executed only if the option was not passed, these messages are :

I then disabled the mining and getblocktemplate limitation on the number of peers in miner.cpp (this could have been done another/proper way) and rpcmining.cpp. Then I set up a pool in order to branch a L3 ASIC to mine a new chain. I chose the unmaintained but functional UNOMP (my repo, official repo). If you don’t want to mine at a high difficulty, it is not necessary to setup a pool.

I then compiled the client and directly started it with -dontbroadcastblocks so that I start rewriting blocks since the genesis, but you can sync some blocks before forking : this means starting the client without the parameter, stopping it at the desired height for the fork, restarting it with the parameter -dontbroadcastblocks and start mining.

I finally launched the pool, another member of Crypto Lyon connected his L3 miner and we waited for about 2 days. If you do it, here the CLI command getinfo should return an increasing number of blocks without any connected peer.

We finally had more chainwork, which you can checkout in the logs (by default in ~/.insacoin/debug.log) :

2019-01-21 15:25:07 UpdateTip: new best=699980fa88026fb1633d63f5b316de17f88cb7b4ef8f5f259bc59f4bc6f2eae5  height=24717  log2_work=36.723465  tx=24746  date=2018-11-27 02:14:43 progress=0.997760  cache=2638

log2_work being the actual chainwork. I then stopped the daemon and restarted it without the -dontbroadcastblocks parameter :

insacoin-cli stop
insacoind -daemon

The client connected to other nodes and after a few minutes, we could see in the block explorer that we successfully forked the chain.

Conclusion

About Bitcoin

On the Bitcoin network, imposed forks of 1 block occur frequently (because of network latency) and it is not an attack. An imposed fork of 2 blocks would requires to mine 2 blocks while the whole network generates 0, or 3 blocks while the whole networks generates just 1 : it means having three times the hashpower of the whole Bitcoin network, or 75% of the total hashpower. A the current hashrate it would means being able to generate 0.75*40 =30 exahashes per second, or 6 times the total hashrate of the biggest Bitcoin pool : BTC.com. Even with that hashrate, you would not be able to double spend an exchange which waits for at least 3 (and on average 5) confirmations.

About the how-to

No shitty “don’t do this at home”-like sentence to conclude my post. Please do this at home, please do this better than I did : learn about the protocol, about what you are doing, why you are doing so.

Links