|
| 1 | +#![allow(unused)] |
| 2 | +use std::str::FromStr; |
| 3 | + |
| 4 | +use bdk_wallet::bitcoin::bip32::Xpriv; |
| 5 | +use bdk_wallet::bitcoin::hashes::Hash; |
| 6 | +use bdk_wallet::bitcoin::key::Secp256k1; |
| 7 | + |
| 8 | +use bdk_wallet::bitcoin::{ |
| 9 | + self, psbt, Address, Network, OutPoint, Psbt, Script, Sequence, Transaction, TxIn, TxOut, Txid, |
| 10 | +}; |
| 11 | + |
| 12 | +use bdk_wallet::keys::DescriptorPublicKey; |
| 13 | +use bdk_wallet::miniscript::plan::Assets; |
| 14 | +use bdk_wallet::miniscript::policy::Concrete; |
| 15 | +use bdk_wallet::miniscript::psbt::PsbtExt; |
| 16 | +use bdk_wallet::miniscript::{DefiniteDescriptorKey, Descriptor}; |
| 17 | +use bdk_wallet::{KeychainKind, Wallet}; |
| 18 | +use bitcoin::{absolute, transaction, Amount}; |
| 19 | + |
| 20 | +// Using a descriptor and spending plans with BDK wallets. |
| 21 | +// |
| 22 | +// Consider the basic flow of using a descriptor. The steps are: |
| 23 | +// 1. Set up the Descriptor |
| 24 | +// 2. Deposit sats into the descriptor |
| 25 | +// 3. Get the previous output and witness from the deposit transaction |
| 26 | +// 4. Set up a psbt to spend the deposited funds |
| 27 | +// 5. If there are multiple spend paths, use the `plan` module to format the psbt properly |
| 28 | +// 6. Sign the spending psbt |
| 29 | +// 7. Finalize the psbt. At this point, miniscript will check whether the transaction |
| 30 | +// satisfies the descriptor, and will notify you if it doesn't. |
| 31 | +// 8. If desired, extract the transaction from the psbt and broadcast it. |
| 32 | +fn main() { |
| 33 | + // In order to try out descriptors, let's define a Bitcoin vault with two spend paths. |
| 34 | + // |
| 35 | + // The vault works like this: |
| 36 | + // |
| 37 | + // A. If you have the `unvault_key`, you can spend the funds, but only *after* a specified block height |
| 38 | + // B. If you have the `emergency_key`, presumably kept in deep cold storage, you can spend at any time. |
| 39 | + |
| 40 | + // Let's set up some wallets so we have keys to work with. |
| 41 | + |
| 42 | + // Regular unvault spend path keys + blockheight. You can use wallet descriptors like this, |
| 43 | + // or you could potentially use a mnemonic and derive from that. See the `mnemonic_to_descriptors.rs` |
| 44 | + // example if you want to do that. |
| 45 | + let unvault_tprv = "tprv8ZgxMBicQKsPdKyH699thnjrFcmJMrUUoaNZvHYxxqvhySPhAYZpmxtR39u5QAYnhtYSfMBuBBH6pGuSgmoK3NpfNDU3RAbrVpcbpLmz5ot"; |
| 46 | + let unvault_pk = "02e7c62fd3a65abdc7ff233fba5637f89c9eaba7fe6baaf15ca99d81e0f5145bf8"; |
| 47 | + let after = 1311208; |
| 48 | + |
| 49 | + // Emergency path keys |
| 50 | + let emergency_tprv = "tprv8ZgxMBicQKsPekKEvzvCnK7qe5r6ausugHDyrPeX9TLQ4oADSYLWtA4m3XsEMmUZEbVaeJtuZimakomLkecLTMwerVJKpAZFtXoo7DYb84B"; |
| 51 | + let emergency_pk = "033b4ac89f5d83de29af72d8b99963c4dbd416fa7c8a8aee6b4761f8f85e588f80"; |
| 52 | + |
| 53 | + // Make a wallet for the unvault user |
| 54 | + let unvault_desc = format!("wpkh({unvault_tprv}/84'/1'/0'/0/*)"); |
| 55 | + let unvault_change_desc = format!("wpkh({unvault_tprv}/84'/1'/0'/1/*)"); |
| 56 | + let mut unvault_wallet = Wallet::create(unvault_desc, unvault_change_desc) |
| 57 | + .network(Network::Testnet) |
| 58 | + .create_wallet_no_persist() |
| 59 | + .expect("couldn't create unvault_wallet"); |
| 60 | + |
| 61 | + // Make a wallet for the emergency user |
| 62 | + let emergency_desc = format!("wpkh({emergency_tprv}/84'/1'/0'/0/*)"); |
| 63 | + let emergency_change_desc = format!("wpkh({emergency_tprv}/84'/1'/0'/1/*)"); |
| 64 | + let mut emergency_wallet = Wallet::create(emergency_desc, emergency_change_desc) |
| 65 | + .network(Network::Testnet) |
| 66 | + .create_wallet_no_persist() |
| 67 | + .expect("couldn't create emergency_wallet"); |
| 68 | + |
| 69 | + // 1. Set up the Descriptor |
| 70 | + |
| 71 | + // The following string defines a miniscript vault policy with two possible spend paths (`or`): |
| 72 | + // * spend at any time with the `emergency_pk` |
| 73 | + // * spend `after` the timelock with the `unvault_pk` |
| 74 | + let policy_str = format!("or(pk({emergency_pk}),and(pk({unvault_pk}),after({after})))"); |
| 75 | + |
| 76 | + // Refer to `examples/compiler.rs` for compiling a miniscript descriptor from a policy string. |
| 77 | + let desc_str = "wsh(or_d(pk(033b4ac89f5d83de29af72d8b99963c4dbd416fa7c8a8aee6b4761f8f85e588f80),and_v(v:pk(02e7c62fd3a65abdc7ff233fba5637f89c9eaba7fe6baaf15ca99d81e0f5145bf8),after(1311208))))#9xvht4sc"; |
| 78 | + println!("The vault descriptor is: {}\n", desc_str); |
| 79 | + |
| 80 | + // Alternately, we can make a wallet for our vault and get its address: |
| 81 | + let mut vault = Wallet::create_single(desc_str) |
| 82 | + .network(Network::Testnet) |
| 83 | + .create_wallet_no_persist() |
| 84 | + .unwrap(); |
| 85 | + let vault_address = vault.peek_address(KeychainKind::External, 0).address; |
| 86 | + println!("The vault address is {:?}", vault_address); |
| 87 | + let vault_descriptor = vault.public_descriptor(KeychainKind::External).clone(); |
| 88 | + let definite_descriptor = vault_descriptor.at_derivation_index(0).unwrap(); |
| 89 | + |
| 90 | + // We don't need to broadcast the funding transaction in this tutorial - |
| 91 | + // having it locally is good enough to get the information we need, and it saves |
| 92 | + // messing around with faucets etc. |
| 93 | + |
| 94 | + // Fund the vault by inserting a transaction: |
| 95 | + let witness_utxo = TxOut { |
| 96 | + value: Amount::from_sat(76_000), |
| 97 | + script_pubkey: vault_address.script_pubkey(), |
| 98 | + }; |
| 99 | + let tx = Transaction { |
| 100 | + output: vec![witness_utxo.clone()], |
| 101 | + ..blank_transaction() |
| 102 | + }; |
| 103 | + |
| 104 | + let previous_output = deposit_transaction(&mut vault, tx); |
| 105 | + println!("Vault balance: {}", vault.balance().total()); |
| 106 | + |
| 107 | + // 3. Get the previous output and txout from the deposit transaction. In a real application |
| 108 | + // you would get this from the blockchain if you didn't make the deposit_tx. |
| 109 | + println!("The deposit transaction's outpoint was {}", previous_output); |
| 110 | + |
| 111 | + // 4. Set up a psbt to spend the deposited funds |
| 112 | + println!("Setting up a psbt for the emergency spend path"); |
| 113 | + let emergency_spend = blank_transaction(); |
| 114 | + let mut psbt = |
| 115 | + Psbt::from_unsigned_tx(emergency_spend).expect("couldn't create psbt from emergency_spend"); |
| 116 | + |
| 117 | + // Format an input containing the previous output |
| 118 | + let txin = TxIn { |
| 119 | + previous_output, |
| 120 | + ..Default::default() |
| 121 | + }; |
| 122 | + |
| 123 | + // Format an output which spends some of the funds in the vault |
| 124 | + let txout1 = TxOut { |
| 125 | + script_pubkey: emergency_wallet |
| 126 | + .next_unused_address(KeychainKind::External) |
| 127 | + .script_pubkey(), |
| 128 | + value: Amount::from_sat(750), |
| 129 | + }; |
| 130 | + |
| 131 | + // Leave some sats aside for fees |
| 132 | + let fees = Amount::from_sat(500); |
| 133 | + |
| 134 | + // Calculate the change amount (total balance minus the amount sent in txout1) |
| 135 | + let change_amount = emergency_wallet |
| 136 | + .balance() |
| 137 | + .confirmed |
| 138 | + .checked_sub(txout1.value) |
| 139 | + .expect("failed to generate change amount") |
| 140 | + .checked_sub(fees) |
| 141 | + .expect("couldn't subtract fee amount"); |
| 142 | + |
| 143 | + // Change output |
| 144 | + let txout2 = TxOut { |
| 145 | + script_pubkey: emergency_wallet |
| 146 | + .next_unused_address(KeychainKind::Internal) |
| 147 | + .script_pubkey(), |
| 148 | + value: change_amount, |
| 149 | + }; |
| 150 | + |
| 151 | + // Add the TxIn and TxOut to the transaction we're working on |
| 152 | + psbt.unsigned_tx.input.push(txin); |
| 153 | + psbt.unsigned_tx.output.push(txout1); |
| 154 | + psbt.unsigned_tx.output.push(txout2); |
| 155 | + |
| 156 | + // 5. If there are multiple spend paths, use the `plan` module to format the psbt properly |
| 157 | + |
| 158 | + // Our vault happens to have two spend paths, and the miniscript satisfier will freak out |
| 159 | + // if we don't tell it which path we're formatting this transaction for. It's like a |
| 160 | + // compile-time check vs a runtime check. |
| 161 | + // |
| 162 | + // In order to tell it whether we are trying for the unvault + timelock spend path, |
| 163 | + // or the emergency spend path, we can use the `plan` module from `rust-miniscript`. |
| 164 | + // |
| 165 | + // The plan module says: "given x assets, can I satisfy the |
| 166 | + // miniscript descriptor y?". It can also automatically update the psbt |
| 167 | + // with the information. When the psbt is finalized, miniscript will check |
| 168 | + // whether the formatted transaction can satisfy the descriptor or not. |
| 169 | + |
| 170 | + // Let's try using the plan module on the emergency spend path. |
| 171 | + |
| 172 | + // First we define our emergency key as a possible asset we can use in the plan |
| 173 | + // to attempt to satisfy the descriptor. |
| 174 | + println!("Adding a spending plan to the emergency spend psbt"); |
| 175 | + let emergency_key_asset = DescriptorPublicKey::from_str(emergency_pk).unwrap(); |
| 176 | + |
| 177 | + // Then we add the emergency key to our list of plan assets. If we had more than one |
| 178 | + // asset (e.g. multiple keys, timelocks, etc) in the descriptor branch we are trying |
| 179 | + // to spend on, we would define and add multiple assets. |
| 180 | + let assets = Assets::new().add(emergency_key_asset); |
| 181 | + |
| 182 | + // Automatically generate a plan for spending the descriptor |
| 183 | + let emergency_plan = definite_descriptor |
| 184 | + .clone() |
| 185 | + .plan(&assets) |
| 186 | + .expect("couldn't create emergency plan"); |
| 187 | + |
| 188 | + // Create an input where we can put the plan data |
| 189 | + // Add the witness_utxo from the deposit transaction to the input |
| 190 | + let mut input = psbt::Input { |
| 191 | + witness_utxo: Some(witness_utxo.clone()), |
| 192 | + ..Default::default() |
| 193 | + }; |
| 194 | + |
| 195 | + // Update the input with the generated plan |
| 196 | + println!("Update the emergency spend psbt with spend plan"); |
| 197 | + emergency_plan.update_psbt_input(&mut input); |
| 198 | + |
| 199 | + // Push the input to the PSBT |
| 200 | + psbt.inputs.push(input); |
| 201 | + |
| 202 | + // Add a default output to the PSBT |
| 203 | + psbt.outputs.push(psbt::Output::default()); |
| 204 | + |
| 205 | + // 6. Sign the spending psbt |
| 206 | + |
| 207 | + // At this point, we have a PSBT that is ready to be signed. |
| 208 | + // It contains public data in its inputs, and data which needs to be signed |
| 209 | + // in its `unsigned_tx.{input, output}s` |
| 210 | + |
| 211 | + // Sign the psbt |
| 212 | + println!("Signing emergency spend psbt"); |
| 213 | + let secp = Secp256k1::new(); |
| 214 | + let emergency_key = Xpriv::from_str(emergency_tprv).expect("couldn't create emergency key"); |
| 215 | + psbt.sign(&emergency_key, &secp) |
| 216 | + .expect("failed to sign emergency spend psbt"); |
| 217 | + |
| 218 | + // 7. Finalize the psbt. At this point, miniscript will check whether the transaction |
| 219 | + // satisfies the descriptor, and will notify you if it doesn't. |
| 220 | + |
| 221 | + psbt.finalize_mut(&secp) |
| 222 | + .expect("problem finalizing emergency psbt"); |
| 223 | + println!("Finalized emergency spend psbt"); |
| 224 | + |
| 225 | + // 8. If desired, extract the transaction from the psbt and broadcast it. We won't do this |
| 226 | + // here as it saves messing around with faucets, wallets, etc. |
| 227 | + let _my_emergency_spend_tx = psbt.extract_tx().expect("failed to extract emergency tx"); |
| 228 | + |
| 229 | + println!("==================================================="); |
| 230 | + |
| 231 | + // Let's now try the same thing with the unvault transaction. We just need to make a new |
| 232 | + // plan, sign a new spending psbt, and finalize it. |
| 233 | + |
| 234 | + // Build a spend transaction the unvault key path |
| 235 | + println!("Setting up a psbt for the unvault spend path"); |
| 236 | + let timelock = absolute::LockTime::from_height(after).expect("couldn't format locktime"); |
| 237 | + let unvault_spend_transaction = blank_transaction_with(timelock); |
| 238 | + let mut psbt = Psbt::from_unsigned_tx(unvault_spend_transaction) |
| 239 | + .expect("couldn't create psbt from unvault_spend_transaction"); |
| 240 | + |
| 241 | + // Format an input containing the previous output |
| 242 | + let txin = TxIn { |
| 243 | + previous_output, |
| 244 | + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, // disables relative timelock |
| 245 | + ..Default::default() |
| 246 | + }; |
| 247 | + |
| 248 | + // Format an output which spends some of the funds in the vault. |
| 249 | + let txout = TxOut { |
| 250 | + script_pubkey: unvault_wallet |
| 251 | + .next_unused_address(KeychainKind::External) |
| 252 | + .script_pubkey(), |
| 253 | + value: Amount::from_sat(750), |
| 254 | + }; |
| 255 | + |
| 256 | + // Add the TxIn and TxOut to the transaction we're working on |
| 257 | + psbt.unsigned_tx.input.push(txin); |
| 258 | + psbt.unsigned_tx.output.push(txout); |
| 259 | + |
| 260 | + // Let's try using the Plan module, this time with two assets: the unvault_key |
| 261 | + // and our `after` timelock. |
| 262 | + println!("Adding a spending plan to the unvault spend psbt"); |
| 263 | + let unvault_key_asset = DescriptorPublicKey::from_str(unvault_pk).unwrap(); |
| 264 | + let timelock = absolute::LockTime::from_height(after).expect("couldn't format locktime"); |
| 265 | + let unvault_assets = Assets::new().add(unvault_key_asset).after(timelock); |
| 266 | + |
| 267 | + // Automatically generate a plan for spending the descriptor, using the assets in our plan |
| 268 | + let unvault_plan = definite_descriptor |
| 269 | + .clone() |
| 270 | + .plan(&unvault_assets) |
| 271 | + .expect("couldn't create plan"); |
| 272 | + |
| 273 | + // Create an input where we can put the plan data |
| 274 | + // Add the witness_utxo from the deposit transaction to the input |
| 275 | + let mut input = psbt::Input { |
| 276 | + witness_utxo: Some(witness_utxo.clone()), |
| 277 | + ..Default::default() |
| 278 | + }; |
| 279 | + |
| 280 | + // Update the input with the generated plan |
| 281 | + println!("Update the unvault spend psbt with spend plan"); |
| 282 | + unvault_plan.update_psbt_input(&mut input); |
| 283 | + |
| 284 | + // Push the input to the PSBT |
| 285 | + psbt.inputs.push(input); |
| 286 | + |
| 287 | + // Add a default output to the PSBT |
| 288 | + psbt.outputs.push(psbt::Output::default()); |
| 289 | + |
| 290 | + // Sign it |
| 291 | + println!("Signing unvault spend psbt"); |
| 292 | + let secp = Secp256k1::new(); |
| 293 | + let unvault_key = Xpriv::from_str(unvault_tprv).unwrap(); |
| 294 | + psbt.sign(&unvault_key, &secp) |
| 295 | + .expect("failed to sign unvault psbt"); |
| 296 | + |
| 297 | + // Finalize the psbt. Miniscript satisfier checks are run at this point, |
| 298 | + // and if your transaction doesn't satisfy the descriptor, this will error. |
| 299 | + psbt.finalize_mut(&secp) |
| 300 | + .expect("problem finalizing unvault psbt"); |
| 301 | + println!("Finalized unvault spend psbt"); |
| 302 | + |
| 303 | + // Once again, we could broadcast the transaction if we wanted to |
| 304 | + // spend using the unvault path. Spend attempts will fail until |
| 305 | + // after the absolute block height defined in the timelock. |
| 306 | + let _my_unvault_tx = psbt.extract_tx().expect("failed to extract unvault tx"); |
| 307 | + |
| 308 | + println!("Congratulations, you've just used a miniscript descriptor with a BDK wallet!"); |
| 309 | + println!("Read the code comments for a more detailed look at what happened.") |
| 310 | +} |
| 311 | + |
| 312 | +fn blank_transaction() -> bitcoin::Transaction { |
| 313 | + blank_transaction_with(absolute::LockTime::ZERO) |
| 314 | +} |
| 315 | + |
| 316 | +fn blank_transaction_with(lock_time: absolute::LockTime) -> bitcoin::Transaction { |
| 317 | + bitcoin::Transaction { |
| 318 | + version: transaction::Version::TWO, |
| 319 | + lock_time, |
| 320 | + input: vec![], |
| 321 | + output: vec![], |
| 322 | + } |
| 323 | +} |
| 324 | + |
| 325 | +fn deposit_transaction(wallet: &mut Wallet, tx: Transaction) -> OutPoint { |
| 326 | + use bdk_chain::{ConfirmationBlockTime, TxGraph}; |
| 327 | + use bdk_wallet::Update; |
| 328 | + |
| 329 | + let txid = tx.compute_txid(); |
| 330 | + let vout = 0; |
| 331 | + let mut graph = TxGraph::<ConfirmationBlockTime>::new([tx]); |
| 332 | + let _ = graph.insert_seen_at(txid, 42); |
| 333 | + wallet |
| 334 | + .apply_update(Update { |
| 335 | + graph, |
| 336 | + ..Default::default() |
| 337 | + }) |
| 338 | + .unwrap(); |
| 339 | + |
| 340 | + OutPoint { txid, vout } |
| 341 | +} |
0 commit comments