Swift Bitcoin - Full node library

Hi everyone! I would like to share with you a project that I have been working on for the last couple of years:

It's called Swift Bitcoin and the idea behind it is simple: to implement as much of the Bitcoin Protocol as posible using purely Swift with minimal dependencies.

It's similar to how Rust, Go and others also have their own node implementation and libraries. I wanted to offer a Swift-native API that's also performant enough to be integrated in all types of projects: apps, server, embedded.

While still very much a work in progress I managed to cover a big chunk of functionality including the most important BIPs and their test vectors. I also borrowed test data (and inspiration) from the reference Bitcoin Core project.

For the networking part I'm using SwiftNIO and for all the cryptography bits I'm using both Swift Crypto and a fork of libsecp256k1 adapted for Swift Package Manager. Also leveraging structured concurrency, actors, Swift Testing and many more of the latest language/ecosystem features.

The package is organized in 6 library products: BitcoinCrypto, Base, Wallet, Blockchain, Transport, RPC (plus the Bitcoin umbrella module).

As well as 2 command line tools:
bcnode or BitcoinNode – which spins up a Bitcoin RPC/P2P service.
bcutil or BitcoinUtility – which performs offline operations like signing a transaction in addition to connecting and controlling running node instances.

There's a Getting Started guide to get a feel of what using the framework feels like. I also keep a blog with updates.

The current status is: signing transaction, deriving keys, generating addresses, running scripts, verifying inputs, segwit/taproot, mining blocks and sync'ing a regtest node are all more-or-less working.

But help is very much needed and the project is mature enough to start opening up to contributors. So if you share a similar passion for Swift and Bitcoin please reach out! Writing unit tests for the wire protocol is just one example of the exciting tasks that are part of this crazy endeavor :smiley:

Apologies for the long introduction and I hope to hear from some of you soon!

Here's some sample code:

// Generate a secret key, corresponding public key, hash and address.
let secretKey = SecretKey()
let publicKey = secretKey.publicKey
let address = BitcoinAddress(publicKey)

// # Prepare the Bitcoin service.

// Instantiate a fresh Bitcoin service (regtest).
let service = BitcoinService()

// Create the genesis block.
await service.createGenesisBlock()

// Mine 100 blocks so block 1's coinbase output reaches maturity.
for _ in 0 ..< 100 {
    await service.generateTo(publicKey)
}

// # Prepare our transaction.

// Grab block 1's coinbase transaction and output.
let fundingTx = await service.transactions[1][0]
let prevout = fundingTx.outputs[0]

// Create a new transaction spending from the previous transaction's outpoint.
let unsignedInput = TransactionInput(outpoint: fundingTx.outpoint(0))

// Specify the transaction's output. We'll leave 1000 sats on the table to tip miners. We'll re-use the origin address for simplicity.
let spendingTx = BitcoinTransaction(
    inputs: [unsignedInput],
    outputs: [address.output(100)])

// # We now need to sign the transaction using our secret key.

let signer = TransactionSigner(transaction: spendingTx, prevouts: [prevout])
let signedTx = signer.sign(input: 0, with: secretKey)

// # We can verify that the transaction was signed correctly.

// Make sure the transaction was signed correctly by verifying the scripts.
let isVerified = signedTx.verifyScript(prevouts: [prevout])

#expect(isVerified)
// Yay! Our transaction is valid.

// # Now we're ready to submit our signed transaction to the mempool.

// Submit the signed transaction to the mempool.
try await service.addTransaction(signedTx)

// The mempool should now contain our transaction.
let mempoolBefore = await service.mempool.count
#expect(mempoolBefore == 1)

// # After confirming the transaction was accepted we can mine a block and get it confirmed.

// Let's mine another block to confirm our transaction.

// In this case we can re-use the address we created before.
let publicKeyHash = Data(Hash160.hash(data: publicKey.data))

// Minde to the public key hash
await service.generateTo(publicKeyHash)

// The mempool should now be empty.
let mempoolAfter = await service.mempool.count
#expect(mempoolAfter == 0)

// # Finally let's make sure the transaction was confirmed in a block.

let blocks = await service.headers.count
#expect(blocks == 102)

let lastBlock = await service.transactions.last!
// Verify our transaction was confirmed in a block.

#expect(lastBlock[1] == signedTx)
// Our transaction is now confirmed in the blockchain!
7 Likes

Nice. I have no interest in Bitcoin or blockchain, but it sounds like you have a reasonable amount of the official C++ node software reimplemented in Swift.

How has it been for you writing it in Swift with all its new features, and how does your node version benchmark against the official C++ version?

I've been focusing mainly on code readability, a solid architecture and clear APIs. A good example of this is how the wallet code (addresses, input signing, mnemonic seeds and key derivation) is completely isolated from the base protocol which involves the script interpreter, transaction hashing and verification. Same way the networking code is completely separate from the blockchain.

In that regard I see a big advantage to the monolithic C++ implementation. Performance-wise I don't think we are ready for any kind of benchmark. There's still many features missing from Swift Bitcoin before we can begin to apply some optimizations.

In terms of leveraging Swift-specific technologies I'd say it's more evident at the communications layer where Swift NIO, ServiceLifecycle, Structured Concurrency and actors blend quite nicely.

1 Like

OK, can you say more about your subjective experience of writing such a project in Swift as opposed to C++ or whatever you used before? For example, there are regular complaints in this forum about structured concurrency and recently a thread about Swift Crypto, just wondering how you felt about using those and other Swift features and projects to build your project.

Sure, but it would be interesting to hear some anecdotal data on what the baseline difference in performance is now, before you even really start optimizing it.

1 Like

Hey! Give my lib K1 a look, a swift wrapper around libsecp256k1, with goal of being close to swift-crypto API.

1 Like

Wow K1 looks fantastic, thank you!! I love that you followed SwiftCrypto conventions for the API. Very elegant indeed!!

There's a BitcoinCrypto module in Swift Bitcoin that exposes some related cryptography elements like the Hash160 – aka BITCOIN160 – hash function and it does so sort of mimicking Swift Crypto APIs also… but I wanted to keep the offering very tightly coupled with what is needed to build a bitcoin client on top of it.

For instance recoverable signatures in Bitcoin are only used in the original message signing protocol… so there's an interface for doing just that. But I wanted to discourage using compact signatures for signing transaction inputs because that would kinda break the system.

Also Bitcoin has some nuanced use of libsecp256k1… for instance with the lax parsing of signatures. Or by allowing null public keys and signatures in certain cases while executing SCRIPT. This makes Swift Bitcoin types like PublicKey and Signature very poorly suited to build more general solutions, including other cryptocurrencies.

On the subject of performance I think it's a great idea to start measuring early so thank you for that suggestion!!

I've been struggling to keep up with regular unit tests which I even migrated to Swift Testing… so that took a long time. But it would be cool to follow the same approach and port some of the benchmarks from Bitcoin Core to make comparisons easier.

Even at this early stage when all we have is an unstable regtest and in-memory only persistence I think it can be so useful so I added a GitHub milestone to try and get this going.

Regarding the use of Structured Concurrency it was definitely a steep learning curve for me but in the end I was convinced it's the right way to implement both the wire protocol and the RPC interpreter. Once you start playing nice with the architecture it becomes easier.

To this day I couldn't find a way to spin up an infinite number of peer-to-peer clients and have them be attached to the task hierarchy. But I solved it pragmatically be having a pool of a fixed number of clients which remain idle until needed.

As for Swift Crypto I have no complains personally. It has so far behaved nicely in both linux and xOS although I wouldn't know about performance. That being said most of the crypto functions consumed by Swift Bitcoin really come from the secp256k1 C library which is highly optimized.

1 Like