Building an airgapped bitcoin node with NNCP

I have been playing with NNCP recently, it’s a modern take on UUCP, which stands for Unix-to-Unix copy. UUCP was used before the internet for syncing mail between machines. You would use your phone line and modem to literally dial another machine, sync mail and perform commands, and then hang up.


UUCP made sense in a world where things weren’t always connected and online. There are still situations where you might not want to be always online. The internet is a distracting place, more and more people are finding the urge to disconnect. Connectivity isn’t always the best in remote places of the world. Bandwidth might be poor, especially with satellite or radio links.

What if we designed our protocols to be delay and low-bandwidth tolerant? What if you could fire and forget instant messages, emails, bitcoin transactions, etc, while you are offline?

NNCP makes all of this easy! You can pipe data into it, the data gets saved into a local queue, ready to be synced with other nodes. Here are some of the use cases listed on the NNCP website:

NNCP Use cases

I wanted to see if I could use NNCP to create an airgapped, offline bitcoin node that could still stay in sync with the bitcoin network. NNCP made this super easy.

First of all, why would you want an airgapped bitcoin node? For the truly paranoid, having private keys attached to a general purpose computer that can connect to the internet is pretty sketchy. If at any point your computer gets compromised by malware or hackers, then say goodbye to your money. These days I just use a hardware wallet, which is like a small airgapped node, but if you’re in a pinch and have a spare laptop lying around, then an airgapped computer will work just as well.

One issue with an airgapped node is, how do you stay in sync with the network? Your bitcoin node builds the current state of the ledger by appending blocks to what Satoshi originally called a “timechain”. Bitcoiners use the term timechain these days to distinguish from all of the cargo-cult “blockchain” shitcoinery that has spawned since Satoshi’s original invention. If you don’t have the latest state of the ledger, stored in something called the UTXO set, then your client might not show you your true balance. One of your UTXOs might have already been spent. If you’re not in sync with network, there’s no way to know if a transaction you’re creating is even valid! You could be double-spending an already spent coin!

So having the latest UTXO state is pretty important when spending your bitcoin, but how do we get a fully synced bitcoin node on an airgapped computer? We will be using NNCP to do this course!

To start, you need a trusted node to sync from. Luckily I already had a full archival node on my local network so I started with that. Next to my full archival node I created a new bitcoin node with pruning turned on. Once my pruned node fully synced, it’s block and utxo state was small enough that I could copy it to a usb drive. This is the initial data I used to bootstrap my airgapped node.

Now, here’s where NNCP comes in. We want to incrementally transfer blocks to our airgapped node. On our online node, we run a single command:

for i in $(seq 708560 $(bitcoin-cli getblockcount)); do
  bitcoin-cli getblock $(bitcoin-cli getblockhash $i) 0 |
  xxd -r -p |
  nncp-exec airgapnode block; done

where 708560 is the last height synced on the airgapped node. Each call to nncp-exec queues a block to be processed on the airgapped side. nncp-exec takes a HANDLER argument, in this case called block. The HANDLER is an identifier that will tell our airgapped node what command to run for each block.

When we are ready to sync to our node, we call nncp-bundle:

$ nncp-bundle -tx -delete airgapnode > /usb/block-packets

This commands packages up our block packets into a file that we can store on a USB drive to sneakernet to our airgapped node. Once we plug our USB into our airgapped node, we run nncp-bundle to receive these packets:

$ nncp-bundle -rx < /usb/block-packets

This copies the block packets into our local spool, ready for processing. Before we process the packets, we need to set up our block handler called blocksubmit:

#!/usr/bin/env bash
set -eou pipefail

res=$(xxd -p | tr -d '\n' | bitcoin-cli -stdin submitblock)

# don't drop the block packet if it's out of order
if [ $res == "prev-blk-not-found" ]; then
    exit 42

This script takes a raw block as input, converts it to hex, and passes it to bitcoin’s submitblock rpc.

Now all we have to do is run nncp-toss. This processes our sneakernet packets and runs the blocksubmit handler for each nncp-exec we originally called:

$ nncp-toss
2021-11-07T02:47:21Z Got exec from monad to block (322 KiB)
2021-11-07T02:47:21Z Got exec from monad to block (773 KiB)
2021-11-07T02:47:21Z Got exec from monad to block (1.1 MiB)
2021-11-07T02:47:21Z Got exec from monad to block (1.1 MiB)
2021-11-07T02:47:21Z Got exec from monad to block (1.1 MiB)

and we’re done, our offline bitcoin node is now up to date with the bitcoin network!

I’m interested to see what else I can use NNCP for. It’s just a general tool for building store-and-forward protocols. Next I’m thinking about converting my email setup to use NNCP, instead of relying on the buggy mail syncing tools I’m using now.

One day I imagine a world where I can do most day-to-day tasks seamlessly offline. NNCP is one step toward this future. I can’t imagine not using tools like this in low bandwidth meshnets, space, and perhaps even between planets. We might have to start thinking about these things sooner or later!