From 62a8a7020c2f950ece61c49b4b324c65849f7efd Mon Sep 17 00:00:00 2001 From: Dave Hrycyszyn Date: Thu, 25 Jul 2024 12:34:26 +0100 Subject: [PATCH] Experimenting with two-party PSBT signing. --- side-node/src/bitcoin/clients/esplora.rs | 40 ++++++++++++-- side-node/src/bitcoin/driver.rs | 69 ++++++++++++++++-------- 2 files changed, 84 insertions(+), 25 deletions(-) diff --git a/side-node/src/bitcoin/clients/esplora.rs b/side-node/src/bitcoin/clients/esplora.rs index d8162c7..a0e3b99 100644 --- a/side-node/src/bitcoin/clients/esplora.rs +++ b/side-node/src/bitcoin/clients/esplora.rs @@ -39,17 +39,45 @@ impl EsploraWallet { recipient: Address, amount: Amount, ) -> Result { - let mut tx_builder = self.wallet.build_tx(); + let mut tx_builder = self.build_tx()?; tx_builder .add_recipient(recipient.script_pubkey(), amount) .enable_rbf(); let mut psbt = tx_builder.finish()?; - let finalized = self.wallet.sign(&mut psbt, SignOptions::default())?; - assert!(finalized); - let tx = psbt.extract_tx()?; + let tx = self.sign(&mut psbt, true)?.extract_tx()?; Ok(tx) } + pub(crate) fn build_tx( + &mut self, + ) -> Result< + bdk_wallet::TxBuilder, + anyhow::Error, + > { + Ok(self.wallet.build_tx()) + } + + pub(crate) fn sign( + &self, + psbt: &mut bitcoin::Psbt, + finalize: bool, + ) -> Result { + tracing::info!("{} signing PSBT", self.name); + + let options = SignOptions { + try_finalize: finalize, + ..Default::default() + }; + + let finalized = self.wallet.sign(psbt, options)?; + + // make sure the PSBT is finalized if we asked for it + if finalize { + assert!(finalized) + } + Ok(psbt.to_owned()) + } + /// Syncs the wallet with the latest state of the Bitcoin blockchain pub(crate) async fn sync(&mut self) -> Result<(), anyhow::Error> { tracing::info!("{} full scan sync start", self.name); @@ -105,6 +133,10 @@ impl EsploraWallet { ); self.client.broadcast(tx).await } + + pub(crate) fn wallet(&mut self) -> &Wallet { + &self.wallet + } } /// Creates a Bitcoin descriptor wallet with the mnemonic in the given user directory. diff --git a/side-node/src/bitcoin/driver.rs b/side-node/src/bitcoin/driver.rs index a6d9f3b..5171b2a 100644 --- a/side-node/src/bitcoin/driver.rs +++ b/side-node/src/bitcoin/driver.rs @@ -4,25 +4,13 @@ use tracing_subscriber::filter::EnvFilter; use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::{fmt, layer::SubscriberExt}; +use super::clients::esplora::EsploraWallet; + pub(crate) async fn simple_transfer() -> Result<(), anyhow::Error> { - tracing_setup(); - - let mut dave = clients::esplora::create_wallet("dave", Network::Signet)?; - let mut sammy = clients::esplora::create_wallet("sammy", Network::Signet)?; - - let _next_address = dave.next_unused_address()?; - - let _ = dave.balance(); - dave.sync().await?; - let _ = dave.balance(); - - let _sammy = sammy.balance(); - sammy.sync().await?; - let _ = sammy.balance(); - + let (mut dave, mut sammy) = setup().await?; let send_amount = Amount::from_sat(500); - ensure_enough_sats(dave, send_amount); + ensure_enough_sats(&dave, send_amount); let sammy_address = sammy.next_unused_address()?.address; let tx = dave.build_and_sign_send_tx(sammy_address, send_amount)?; @@ -31,8 +19,21 @@ pub(crate) async fn simple_transfer() -> Result<(), anyhow::Error> { Ok(()) } +async fn setup() -> Result<(EsploraWallet, EsploraWallet), anyhow::Error> { + tracing_setup(); + let mut dave = clients::esplora::create_wallet("dave", Network::Signet)?; + let mut sammy = clients::esplora::create_wallet("sammy", Network::Signet)?; + let _ = dave.balance(); + dave.sync().await?; + let _ = dave.balance(); + let _sammy = sammy.balance(); + sammy.sync().await?; + let _ = sammy.balance(); + Ok((dave, sammy)) +} + /// Exit if the wallet does not have enough sats to send. -fn ensure_enough_sats(wallet: EsploraWallet, send_amount: bitcoin::Amount) -> _ { +fn ensure_enough_sats(wallet: &EsploraWallet, send_amount: bitcoin::Amount) { if wallet.balance().total() < send_amount { tracing::error!( "Please send at least {} sats to the receiving address. Exiting.", @@ -43,12 +44,38 @@ fn ensure_enough_sats(wallet: EsploraWallet, send_amount: bitcoin::Amount) -> _ } pub(crate) async fn htlc() -> anyhow::Result<()> { - // dave will be our sender - let mut dave = clients::esplora::create_wallet("dave", Network::Signet)?; + let (mut dave, mut sammy) = setup().await?; - // sammy will be our liquidity router - let mut sammy = clients::esplora::create_wallet("sammy", Network::Signet)?; + // format a new commitment transaction like in Lightning + let mut commitment_builder = dave.build_tx()?; + let amount = Amount::from_sat(500); + let recipient = sammy.next_unused_address()?.script_pubkey(); + commitment_builder.add_recipient(recipient, amount); + commitment_builder.enable_rbf(); + let psbt = commitment_builder + .finish() + .expect("unable to build commitment"); + // sign the commitment transaction + let mut dave_psbt = dave.sign(&mut psbt.clone(), false)?; + let sammy_psbt = sammy.sign(&mut psbt.clone(), false)?; + + dave_psbt + .combine(sammy_psbt) + .expect("problem combining bitcoin PSBTs"); // these guys love mutability + + let finalized = dave + .wallet() + .finalize_psbt(&mut dave_psbt, bdk_wallet::SignOptions::default()) + .expect("couldn't finalize"); + + assert!(finalized); + let tx = dave_psbt.extract_tx()?; + + let _ = dave.broadcast(&tx).await.expect("couldn't broadcast"); + + let _ = sammy.sync(); + sammy.balance(); Ok(()) }