Regtest Playground - Performing a Double-Spent and Chain Reorg in bcoin !

Regtest Playground - Performing a Double-Spent and Chain Reorg in bcoin !

A beginner's guide

·

9 min read

In the previous blog, we are done with setting up our bcoin node in regtest mode. In this blog, we will try to play more in this regtest mode and try performing chain reorganisation, and Double-spent attacks.

So, the first question that arises is what the regtest mode is?

Regtest mode

Bitcoin Core provides testing tools designed to let developers test their applications with reduced risks and limitations. One of the modes is the regtest.

Regtest (regression test) mode creates a local private blockchain where you can adjust the parameters to what you want. You usually use this when it is not needed to communicate with other peers and blocks. An example of what you can do with the parameters is created blocks instantly, and you have complete control over the environment.

What is a Double Spent attack?

A double-spend is when the same unit of a digital currency is fraudulently spent more than one time. This is often because digital files can be easily copied. Though it is not possible to "copy" cryptocurrencies, there are certain types of attacks that can allow bad actors to “reverse” a crypto transaction and double-spend a coin.

Double-spending occurs when a blockchain network is disrupted, and cryptocurrency is essentially stolen. The thief would send a copy of the currency transaction to make it look legitimate or might erase the transaction altogether.

What is Chain Reorganisation?

A reorg is a built-in feature of the Bitcoin network designed to deal with the issue of simultaneously mined blocks, and you have a conflict about what chain to follow now. These conflicts, which briefly reorganise the ledger history, are built into the Bitcoin infrastructure. Reorgs are a deliberate feature of blockchain technology, solving the problem of miners in a decentralised system mining the same block simultaneously.

In particular, that “Chain reorganisation is a client-local phenomenon”. An individual client finds out that they are in the wrong chain, reverting and jumping to the correct one.

Now we understand the required terms, So we can move on to performing chain reorg and Double-spent attack on our regtest mode.

Here is what we will do:

  1. Connecting two full nodes in regtest mode.
  2. Performing a chain reorg.
  3. Performing a Double-spent attack.

Connect two full nodes in regtest mode

This will require more than two terminal windows, so it will be better to use tmux with three panes on the same screens.

So, we are going to connect two nodes and watch them exchange some messages. For simplicity, we will use N1 for node1 and N2 for node2.

In terminal 1 run,

$ bcoin --network=regtest --log-level=info --max-inbound=1 --max-outbound=1

We have started our first bcoin node in regtest mode. Now we will start another node in terminal 2.

$ bcoin --network=regtest --prefix=~/.bcoin/regtest-2 --port=10000 -http-port=20000 --wallet-http-port=30000 --log-level=info --max-inbound=1 --max-outbound=1

This will start the second bcoin node with the given ports. Where,

prefix: The data directory (stores databases, logs, and configs).

Port: Port to listen on (default: 8333).

http-port: HTTP port to listen on (default: 8332 for mainnet).

wallet-http-port: wallet HTTP port to listen on (default: 8334 for mainnet).

Now we have both nodes N1 and N2, up and running. Congrats if you followed up here.

Open terminal 3, and run this command.

$ export BCOIN_NETWORK=regtest

Now let us check the info of both nodes.

For node N1,

$ bcoin-cli --network=regtest info

For node N2,

$ bcoin-cli --network=regtest --http-port=2000 info

You will notice that the height and tip of both node matches, as we have not yet mined blocks on either of them.

Let us mine some blocks from node N1.

$ bcoin-cli rpc generatetoaddress 100 `bwallet-cli rpc getnewaddress`

See the command bwallet-cli rpc getnewaddress. It will give you a new address.

Recheck the nodes info, and you will notice that node N1 will have 100 blocks mined and have a UTXO of 100.

Now, we have two nodes running successfully. So, let us connect them.

$ bcoin-cli rpc --http-port=20000 addnode 127.0.0.1 onenrty

The above command will send a connection message from node N2 to N1, and the onentry parameter will try to connect to the given node.

Now recheck both nodes info, they should be synced, and you should be able to observe that the second node syncing those 100 blocks from the first node.

Congrats, you have successfully cleared our first checkpoint out of three.

Now, let's move on to chain reorganisation.

Performing Chain Reorganisation

To perform a chain reorganisation, disconnect the second node from the first:

$ bcoin-cli --http-port=20000 rpc disconnectnode 127.0.0.1

Now, both not are out of sync. Let mine some blocks on both nodes. Generate 2 blocks on node N1 and 1 block on node N2.

$ bcoin-cli rpc generatetoaddress 2 `bwallet-cli rpc getnewaddress`
$ bcoin-cli --http-port=20000 rpc generatetoaddress 1 `bwallet-cli rpc getnewaddress`

Now, if you recheck nodes info again, they should be out of sync. However, this time what we have is a chain split. Both nodes have the same blockchain up to height 100. After that point, the second node has 1 new block, but the first node has 2 totally different blocks. The two chains after height 100 are completely different. What will happen when we connect these two nodes back together again?

$ bcoin-cli rpc --http-port=20000 addnode 127.0.0.1 onenrty

Once you execute this, observe the log from the second node in terminal 2. The actual log file can be found at ~/.bcoin/regtest-2/debug.log if you want to inspect it later, but the same output should be in the stdout of that terminal window / tmux pane.

You should see a warning like this:

[warning] (chain) Heads up: Competing chain at height 101: tip-height=101 competitor-height=101
  tip-hash=397c49bcd2159b7e3cd528ede1981aa8760b758ec2a141ba1c21de97226b307c competitor-hash=4c6f53e2d911aa1d3c2a8f437b37d7fbe39eafdf0df016a1bfdd1f81c5d25df7
  tip-chainwork=204 competitor-chainwork=204 chainwork-diff=0
[debug] (chain) Memory: rss=48mb, js-heap=10/12mb native-heap=36mb
[info] (chain) Block 4c6f53e2d911aa1d3c2a8f437b37d7fbe39eafdf0df016a1bfdd1f81c5d25df7 (101) added to chain (size=280 txs=1 time=6.685683).
[debug] (net) Received full compact block 0e463b7bf53efb2b2dc517196f37efaf58d09d5c8af636e0f99c4577fd538506 (127.0.0.1:48444).
[warning] (chain) WARNING: Reorganizing chain.
[debug] (wallet) Adding block: 101.
[warning] (chain) Chain reorganization:
  old=397c49bcd2159b7e3cd528ede1981aa8760b758ec2a141ba1c21de97226b307c(101)
  new=0e463b7bf53efb2b2dc517196f37efaf58d09d5c8af636e0f99c4577fd538506(102)
[debug] (wallet) Adding block: 102.

This warning says that we have two chains now, one with 101 blocks and the other with 102 blocks, so to avoid further conflict, we are performing a chain reorg in node N2. Now our node N2 is up to date with the longest blockchain.

Complimenti! We have cleared our second checkpoint by performing a successful chain reorg.

Now, let us perform our final task Double-spent attack

Double Spent Attack!

There are several types of double-spend attacks theoretically possible in Bitcoin. Since we have 100% of the mining hash rate in our two-node regtest network, we can easily demonstrate how it works.

Given how we have executed our generatetoaddress commands thus far, the wallet in the first node should have a big balance, and the second node's wallet should be empty:

Let us check wallets balance now, For node N1:

$ bwallet-cli balance
> {
  "account": -1,
  "tx": 102,
  "coin": 102,
  "unconfirmed": 510000000000,
  "confirmed": 510000000000
}

For node N2:


$ bwallet-cli --http-port=30000 balance
> {
  "account": -1,
  "tx": 0,
  "coin": 0,
  "unconfirmed": 0,
  "confirmed": 0
}

Now, what we are gonna do is sending a transaction to the second node N2 from the first node N1, and then double-spend-attack that wallet, effectively reversing the transaction and potentially stealing from that user!

Get an address from the victim's wallet:

$ bwallet-cli --http-port=30000 rpc getnewaddress

Send 150 BTC from the first node to that address:

$ bwallet-cli send <victim's address> 150.00000000 --subtract-fee=true

Now we have a transaction sending 150 BTC from N1 to N2. Let us confirm this transaction by including it in a block.

Confirm that TX is in a block and note the block hash that is returned:

$ bcoin-cli rpc generatetoaddress 1 `bwallet-cli rpc getnewaddress`
> [
  "4965f6fd776265eb23a31f2a3c85eaaefc3a49ac447e47bf6cda274e9f380e2a"
]

At this point, the "victim" (the second node's wallet) appears to have a confirmed balance of around 150 BTC (minus the transaction fee, do you know why?). Here is where we are going to start getting nasty. Notice how all the following commands are only executed to the first ("attacker") node while remaining connected. Some of these steps are a bit hacky to side-step safety features normally built into bcoin.

Undo the last block, un-confirming the 150 BTC transaction:

$ bcoin-cli rpc invalidateblock <block hash from last command>

If you recheck the nodes info right now, you'll notice that the first node is now "behind" by one block -- the invalidation command is local only and does not affect other nodes on the network.

Remove the unconfirmed transaction from the sender's wallet:

Without this step, the wallet will not let us double-spend our coins.

$ bwallet-cli zap --account=default --age=1

Check that the wallet has no "pending" transactions. This would prevent us from double-spending.

$ bwallet-cli pending
> []

OK, let's steal our money back:

$ bwallet-cli send `bwallet-cli rpc getnewaddress` 150.00000000 --subtract-fee=true

The above command will send those 150 BTC to our own address. Now we have a transaction coming to our wallet. Let us confirm it by mining a block.

Now we confirm that transaction once:

$ bcoin-cli rpc generatetoaddress 1 `bwallet-cli rpc getnewaddress`

The second node should log the familiar "Heads up" warning. At this level, we have two chains with the same number of blocks. To successfully perform our trick, we need to add one more block on node1, which will reorg the blockchain of node N2.

Then we reorganize with one more block:

$ bcoin-cli rpc generatetoaddress 1 `bwallet-cli rpc getnewaddress`

WOW, the second node and its wallet really did not like that, huh?


[warning] (chain) WARNING: Reorganizing chain.
[debug] (wallet) Rescan: reverting block 103
[warning] (wallet) Disconnected wallet block 1103b6349e5c30a4e390132f2c3cfb108b52dfa8a1b87a1a2790df066027c458 (tx=1).
[debug] (wallet) Adding block: 103.
[info] (wallet) Incoming transaction for 1 wallets in WalletDB (2416818256d576c9ac8b24b7108ffb96b240e6d577a9745b49491b14f23ad929).
[warning] (wallet) Handling conflicting tx: ba3a87a2cc42327cf7d08bdb33ca8785d301f260dbc432aeb664c65ca241c8a9.
[warning] (wallet) Removed conflict: ba3a87a2cc42327cf7d08bdb33ca8785d301f260dbc432aeb664c65ca241c8a9.
[warning] (chain) Chain reorganization:
  old=1103b6349e5c30a4e390132f2c3cfb108b52dfa8a1b87a1a2790df066027c458(103)
  new=62cab7c55d967b45b765fefabfe5aa99287c0f7c0161bec8c8a0ae33eadd3eab(104)
[debug] (wallet) Adding block: 104.

Now recheck the balances of the two nodes:

For N1:

$ bwallet-cli balance
> {
  "account": -1,
  "tx": 203,
  "coin": 103,
  "unconfirmed": 877500000000,
  "confirmed": 877500000000
}

For N2:

$ bwallet-cli --http-port=30000 balance
{
  "account": -1,
  "tx": 0,
  "coin": 0,
  "unconfirmed": 0,
  "confirmed": 0
}

You should be able to notice that we have stolen sent coins back.

Congrats, you have successfully performed a Double-spent attack in regtest mode.

Conclusion:

In this article, I have shown you some things to play in regtest mode. Firstly, we performed a chain reorg by creating a conflict, i.e. a chain split, and then we tried the supercool task Double-spent attack.

Remember, we were able to perform Double-spent attack because we own a 100% mining rate in regtest mode. For mainnet, if you have more than or equals a 50% mining rate, then this attack is feasible.

Task For you !

Remember, the second node in our regtest network can ALSO generate blocks. Can you STEAL BACK the 150 BTC that was originally sent to the second node?

Thanks for the reading.

References

This post was written at Summer of Bitcoin 2021. Here is the list of references I used.

Special thanks to Matthew Zipkin for being an amazing mentor.