Change WalletSource::sign_tx to sign_psbt

This commit is contained in:
benthecarman 2023-12-07 00:49:16 -06:00
parent fcf47a429c
commit 489b408520
No known key found for this signature in database
GPG key ID: D7CC770B81FD22A8
2 changed files with 58 additions and 18 deletions

View file

@ -35,6 +35,7 @@ use bitcoin::{OutPoint, PubkeyHash, Sequence, ScriptBuf, Transaction, TxIn, TxOu
use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR; use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR;
use bitcoin::blockdata::locktime::absolute::LockTime; use bitcoin::blockdata::locktime::absolute::LockTime;
use bitcoin::consensus::Encodable; use bitcoin::consensus::Encodable;
use bitcoin::psbt::PartiallySignedTransaction;
use bitcoin::secp256k1; use bitcoin::secp256k1;
use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::Secp256k1;
use bitcoin::secp256k1::ecdsa::Signature; use bitcoin::secp256k1::ecdsa::Signature;
@ -343,7 +344,10 @@ pub trait CoinSelectionSource {
) -> Result<CoinSelection, ()>; ) -> Result<CoinSelection, ()>;
/// Signs and provides the full witness for all inputs within the transaction known to the /// Signs and provides the full witness for all inputs within the transaction known to the
/// trait (i.e., any provided via [`CoinSelectionSource::select_confirmed_utxos`]). /// trait (i.e., any provided via [`CoinSelectionSource::select_confirmed_utxos`]).
fn sign_tx(&self, tx: Transaction) -> Result<Transaction, ()>; ///
/// If your wallet does not support signing PSBTs you can call `psbt.extract_tx()` to get the
/// unsigned transaction and then sign it with your wallet.
fn sign_psbt(&self, psbt: PartiallySignedTransaction) -> Result<Transaction, ()>;
} }
/// An alternative to [`CoinSelectionSource`] that can be implemented and used along [`Wallet`] to /// An alternative to [`CoinSelectionSource`] that can be implemented and used along [`Wallet`] to
@ -357,7 +361,10 @@ pub trait WalletSource {
/// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within /// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within
/// the transaction known to the wallet (i.e., any provided via /// the transaction known to the wallet (i.e., any provided via
/// [`WalletSource::list_confirmed_utxos`]). /// [`WalletSource::list_confirmed_utxos`]).
fn sign_tx(&self, tx: Transaction) -> Result<Transaction, ()>; ///
/// If your wallet does not support signing PSBTs you can call `psbt.extract_tx()` to get the
/// unsigned transaction and then sign it with your wallet.
fn sign_psbt(&self, psbt: PartiallySignedTransaction) -> Result<Transaction, ()>;
} }
/// A wrapper over [`WalletSource`] that implements [`CoinSelection`] by preferring UTXOs that would /// A wrapper over [`WalletSource`] that implements [`CoinSelection`] by preferring UTXOs that would
@ -504,8 +511,8 @@ where
.or_else(|_| do_coin_selection(true, true)) .or_else(|_| do_coin_selection(true, true))
} }
fn sign_tx(&self, tx: Transaction) -> Result<Transaction, ()> { fn sign_psbt(&self, psbt: PartiallySignedTransaction) -> Result<Transaction, ()> {
self.source.sign_tx(tx) self.source.sign_psbt(psbt)
} }
} }
@ -549,8 +556,8 @@ where
} }
/// Updates a transaction with the result of a successful coin selection attempt. /// Updates a transaction with the result of a successful coin selection attempt.
fn process_coin_selection(&self, tx: &mut Transaction, mut coin_selection: CoinSelection) { fn process_coin_selection(&self, tx: &mut Transaction, coin_selection: &CoinSelection) {
for utxo in coin_selection.confirmed_utxos.drain(..) { for utxo in coin_selection.confirmed_utxos.iter() {
tx.input.push(TxIn { tx.input.push(TxIn {
previous_output: utxo.outpoint, previous_output: utxo.outpoint,
script_sig: ScriptBuf::new(), script_sig: ScriptBuf::new(),
@ -558,7 +565,7 @@ where
witness: Witness::new(), witness: Witness::new(),
}); });
} }
if let Some(change_output) = coin_selection.change_output.take() { if let Some(change_output) = coin_selection.change_output.clone() {
tx.output.push(change_output); tx.output.push(change_output);
} else if tx.output.is_empty() { } else if tx.output.is_empty() {
// We weren't provided a change output, likely because the input set was a perfect // We weren't provided a change output, likely because the input set was a perfect
@ -595,7 +602,7 @@ where
log_debug!(self.logger, "Peforming coin selection for commitment package (commitment and anchor transaction) targeting {} sat/kW", log_debug!(self.logger, "Peforming coin selection for commitment package (commitment and anchor transaction) targeting {} sat/kW",
package_target_feerate_sat_per_1000_weight); package_target_feerate_sat_per_1000_weight);
let coin_selection = self.utxo_source.select_confirmed_utxos( let coin_selection: CoinSelection = self.utxo_source.select_confirmed_utxos(
claim_id, must_spend, &[], package_target_feerate_sat_per_1000_weight, claim_id, must_spend, &[], package_target_feerate_sat_per_1000_weight,
)?; )?;
@ -613,15 +620,29 @@ where
let total_input_amount = must_spend_amount + let total_input_amount = must_spend_amount +
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value).sum::<u64>(); coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value).sum::<u64>();
self.process_coin_selection(&mut anchor_tx, coin_selection); self.process_coin_selection(&mut anchor_tx, &coin_selection);
let anchor_txid = anchor_tx.txid(); let anchor_txid = anchor_tx.txid();
debug_assert_eq!(anchor_tx.output.len(), 1); // construct psbt
let mut anchor_psbt = PartiallySignedTransaction::from_unsigned_tx(anchor_tx).unwrap();
// add witness_utxo to anchor input
anchor_psbt.inputs[0].witness_utxo = Some(anchor_descriptor.previous_utxo());
// add witness_utxo to remaining inputs
for (idx, utxo) in coin_selection.confirmed_utxos.into_iter().enumerate() {
// add 1 to skip the anchor input
let index = idx + 1;
debug_assert_eq!(anchor_psbt.unsigned_tx.input[index].previous_output, utxo.outpoint);
if utxo.output.script_pubkey.is_witness_program() {
anchor_psbt.inputs[index].witness_utxo = Some(utxo.output);
}
}
debug_assert_eq!(anchor_psbt.unsigned_tx.output.len(), 1);
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let unsigned_tx_weight = anchor_tx.weight().to_wu() - (anchor_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT); let unsigned_tx_weight = anchor_psbt.unsigned_tx.weight().to_wu() - (anchor_psbt.unsigned_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
log_debug!(self.logger, "Signing anchor transaction {}", anchor_txid); log_debug!(self.logger, "Signing anchor transaction {}", anchor_txid);
anchor_tx = self.utxo_source.sign_tx(anchor_tx)?; anchor_tx = self.utxo_source.sign_psbt(anchor_psbt)?;
let signer = anchor_descriptor.derive_channel_signer(&self.signer_provider); let signer = anchor_descriptor.derive_channel_signer(&self.signer_provider);
let anchor_sig = signer.sign_holder_anchor_input(&anchor_tx, 0, &self.secp)?; let anchor_sig = signer.sign_holder_anchor_input(&anchor_tx, 0, &self.secp)?;
@ -690,7 +711,7 @@ where
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let must_spend_amount = must_spend.iter().map(|input| input.previous_utxo.value).sum::<u64>(); let must_spend_amount = must_spend.iter().map(|input| input.previous_utxo.value).sum::<u64>();
let coin_selection = self.utxo_source.select_confirmed_utxos( let coin_selection: CoinSelection = self.utxo_source.select_confirmed_utxos(
claim_id, must_spend, &htlc_tx.output, target_feerate_sat_per_1000_weight, claim_id, must_spend, &htlc_tx.output, target_feerate_sat_per_1000_weight,
)?; )?;
@ -701,13 +722,30 @@ where
let total_input_amount = must_spend_amount + let total_input_amount = must_spend_amount +
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value).sum::<u64>(); coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value).sum::<u64>();
self.process_coin_selection(&mut htlc_tx, coin_selection); self.process_coin_selection(&mut htlc_tx, &coin_selection);
// construct psbt
let mut htlc_psbt = PartiallySignedTransaction::from_unsigned_tx(htlc_tx).unwrap();
// add witness_utxo to htlc inputs
for (i, htlc_descriptor) in htlc_descriptors.iter().enumerate() {
debug_assert_eq!(htlc_psbt.unsigned_tx.input[i].previous_output, htlc_descriptor.outpoint());
htlc_psbt.inputs[i].witness_utxo = Some(htlc_descriptor.previous_utxo(&self.secp));
}
// add witness_utxo to remaining inputs
for (idx, utxo) in coin_selection.confirmed_utxos.into_iter().enumerate() {
// offset to skip the htlc inputs
let index = idx + htlc_descriptors.len();
debug_assert_eq!(htlc_psbt.unsigned_tx.input[index].previous_output, utxo.outpoint);
if utxo.output.script_pubkey.is_witness_program() {
htlc_psbt.inputs[index].witness_utxo = Some(utxo.output);
}
}
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let unsigned_tx_weight = htlc_tx.weight().to_wu() - (htlc_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT); let unsigned_tx_weight = htlc_psbt.unsigned_tx.weight().to_wu() - (htlc_psbt.unsigned_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
log_debug!(self.logger, "Signing HTLC transaction {}", htlc_tx.txid()); log_debug!(self.logger, "Signing HTLC transaction {}", htlc_psbt.unsigned_tx.txid());
htlc_tx = self.utxo_source.sign_tx(htlc_tx)?; htlc_tx = self.utxo_source.sign_psbt(htlc_psbt)?;
let mut signers = BTreeMap::new(); let mut signers = BTreeMap::new();
for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() { for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() {

View file

@ -71,6 +71,7 @@ use crate::sign::{InMemorySigner, Recipient, EntropySource, NodeSigner, SignerPr
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use bitcoin::psbt::PartiallySignedTransaction;
use bitcoin::Sequence; use bitcoin::Sequence;
pub fn pubkey(byte: u8) -> PublicKey { pub fn pubkey(byte: u8) -> PublicKey {
@ -1410,7 +1411,8 @@ impl WalletSource for TestWalletSource {
Ok(ScriptBuf::new_p2pkh(&public_key.pubkey_hash())) Ok(ScriptBuf::new_p2pkh(&public_key.pubkey_hash()))
} }
fn sign_tx(&self, mut tx: Transaction) -> Result<Transaction, ()> { fn sign_psbt(&self, psbt: PartiallySignedTransaction) -> Result<Transaction, ()> {
let mut tx = psbt.extract_tx();
let utxos = self.utxos.borrow(); let utxos = self.utxos.borrow();
for i in 0..tx.input.len() { for i in 0..tx.input.len() {
if let Some(utxo) = utxos.iter().find(|utxo| utxo.outpoint == tx.input[i].previous_output) { if let Some(utxo) = utxos.iter().find(|utxo| utxo.outpoint == tx.input[i].previous_output) {