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
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!