{ BDK deep dive }
@danielabrozzoni - MIT Bitcoin Expo 2023
About me
- I'm @danielabrozzoni :)
- Full time developer on the BitcoinDevKit project, supported by Spiral
- Previously worked in Blockstream, Braiins, Revault
{ Bitcoin wallets }
What is a Bitcoin wallet?
- Software used to manage keys and to make transactions on the Bitcoin network
- Acts as a:
- Keychain
- Blockchain monitor
- Transaction creator
Wallet as a keychain
- Bitcoin wallets hold keys
- Sometimes public keys only (watchonly wallets), sometimes private keys as well
- Given the master key (either seed or xprv), need to generate the associated Bitcoin scripts
Wallet as a blockchain monitor
- Periodically check if some coins has been sent to addresses managed by the wallet
Wallet as a transaction creator
- Need to create transactions to let the users spend their money
- Given a list of recipients and a feerate, find suitable inputs and construct the transaction, with an optional change
- This step is called "coin selection"
- Sign the transaction
- Build the satisfying witness
- Broadcast
{ Building }
{ Question: how hard is it to build a Bitcoin wallet from scratch? }
Building a wallet
- Wallet acts as a
- Keychain
- Blockchain monitor
- Transaction creator
Building a keychain
- Usually wallets use a single "master" secret
- Given the master secret, derive public keys from it (see BIP32)
- For each public key, you build the associated script pubkey, which depends on the wallet type the user chose (P2PKH, P2SH, P2WPKH, P2WSH, P2TR, etc)
Building a blockchain monitor
- Not *that* hard
- Just write some code to ask to an external entity for updates
- The "external entity" might be your own Bitcoin Core node, an esplora/electrum server, or Bitcoin Core nodes using the CBF protocol
Building a transaction creator
- Figure out how expensive it is to spend each input
- Coin selection
- Various algorithms on how to select inputs given feerate & recipients
- Not all algorithms are born equal (obviously)
- Most famous are: branch and bound, single random draw, largest first, oldest first
- Encode the transaction
- Sign and construct the satisfying witness
- Broadcast
{ Tools }
{ Building from scratch sounds difficult. Is there something out there that might help us? }
Picking a programming language
- Type safety
- Memory safety
- Network effect
- Cute mascot
rust-bitcoin
- Provides Bitcoin-related primitives
- Transaction, TxOut, TxIn...
- PrivateKey, PublicKey, Script, Address...
- Block, BlockHeader
- Message, Network, Address (IP)
rust-miniscript
- Bitcoin Script is not easy to use, despite being really limited
- Miniscript is a language for writing (a subset of) Bitcoin Scripts in a structured way
- Developer writes a policy, miniscript compiles it into a descriptor
- Descriptor: an engineer-readable syntax for Bitcoin script
rust-miniscript
<A> OP_CHECKSIGVERIFY <B> OP_CHECKSIG
and_v(v:pk(A),pk(B))
and(pk(A),pk(B))
rust-miniscript
- Miniscript enables static analysis on descriptors
- Given a descriptor, it is easy to:
- Build a satisfying witness
- Figure out the cheapest way to spend
- Predict the cost of spending an input
BDK
- A library to seamlessly build Bitcoin wallets
- Makes it possible for non-Bitcoin experts to build Bitcoin wallets
- Builds on top of rust-bitcoin, rust-miniscript
- Highly customizable
- How store data
- How to sync the blockchain
- Written in Rust, with bindings in Java/Kotlin, Python, Swift, and more coming soon :tm:
{ BDK }
{ Where we're at }
Main BDK structures
- `Wallet`
- Created from one or two descriptors
- Provides methods to sync, get the balance, get new addresses, create/sign/broadcast transactions
- `Database`
- Used to cache info about the wallet (utxos, txs)
- Currently supported: Sled, Sqlite, in-memory (useful for testing)
- `Blockchain`
- Used to sync the wallet
- Currently supported: Bitcoin Core RPC, Electrum, Esplora, Compact Block filters
The BDK API (0.28)
let database = SqliteDatabase::new("path...");
let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tpub.../0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tpub.../1/*)"),
Network::Testnet,
database,
)?;
let blockchain = EsploraBlockchain::from("url...", 50 /* stop_gap */);
wallet.sync(&blockchain, SyncOptions::default())?;
let send_to = Address::from_string("bc1q202rwadss5k6phxjazugjp87ncjmt62szj0ylk")?;
let (psbt, _) = {
let mut builder = wallet.build_tx();
builder
.add_recipient(send_to.script_pubkey(), 50_000)
.enable_rbf()
.current_height(786693)
.fee_rate(FeeRate::from_sat_per_vb(5.0));
builder.finish()?
};
println!("Unsigned PSBT: {}", &psbt);
The BDK API (0.28)
- Good news: it works!
- Bad news: it's not perfect
The BDK API - we can do better
- Wallet is not thread safe (not Send/Sync)
- Needs to be enclosed in a Mutex if you want to use it in multithread/async envs
- Some methods keep a lock on the Wallet for longer than necessary (ex: syncing)
- Mobile apps usually cache balance/txs to be able to show them to the user while syncing
The BDK API - we can do better
- Heavy use of traits in the public API
- Rust traits are somewhat like Java interfaces
- Designing the public interface before seeing how people might use it is hard, and leads to frequent API breaks down the road
- The most obvious example of this is the syncing API in BDK
The BDK API - we can do better
- Heavy use of traits in the public API
- Example: Database trait defines what can be stored in the BDK database (`set_utxo`, `set_tx`, etc.)
- Example: WalletSync trait defines how to sync the database against the blockchain, but it only lets you update the whole database in one shot
pub trait WalletSync {
fn wallet_sync<D: BatchDatabase>(
&self,
database: &RefCell<D>,
progress_update: Box<dyn Progress>
) -> Result<(), Error> { ... }
// Other methods omitted for clarity
}
The BDK API - we can do better
- Not modular
- "Ohhh you want to leverage BDK's coin selection and completely ignore the rest? Too bad ¯\_(ツ)_/¯"
- No no-std support
{ BDK }
{ Where we're going }
🎉 BDK 1.0 🎉
Eventually
Coming soon
I really don't know when
Sorry
Working on it
BDK 1.0: what
- Stable API :)
- Improved syncing API
- No more `WalletSync` trait forcing us to sync everything in one shot
- Modularization: divide BDK into multiple rust crates
- Coin selection
- Syncing primitives
- Better support for multi-descriptor wallets
- BIP47? 😏
- no-std support
BDK 1.0: how
- Halt development of bdk version 0
- We release new versions of bdk 0 for critical bug fixes
- Full team is working on bdk 1.0 at the moment
- User feedback is extremely helpful
- Consider trying it out and give feedback
- Much ❤️ to mutiny wallet for already using bdk 1.0!
BDK 1.0: when
- I really don't know
- Soon! (Hopefully)
{ In conclusion }
- Building wallets is hard
- BDK makes it easier
- BDK is cool, but we're making it even cooler