diff --git a/fuzz/fuzz_targets/descriptor_parse.rs b/fuzz/fuzz_targets/descriptor_parse.rs index 9d7563a5..8a3b095d 100644 --- a/fuzz/fuzz_targets/descriptor_parse.rs +++ b/fuzz/fuzz_targets/descriptor_parse.rs @@ -67,10 +67,12 @@ fuzz_target!(|data: &[u8]| { desc.receive_descriptor().derive(der_index, &SECP256K1), desc.change_descriptor().derive(der_index, &SECP256K1), ]; + let mut psbt_in = Default::default(); + let mut psbt_out = Default::default(); for desc in der_descs { desc.address(Network::Bitcoin); desc.script_pubkey(); - desc.witness_script(); - desc.bip32_derivations(); + desc.update_psbt_in(&mut psbt_in); + desc.update_change_psbt_out(&mut psbt_out); } }); diff --git a/fuzz/fuzz_targets/descriptors.rs b/fuzz/fuzz_targets/descriptors.rs index 61eaeca2..4fae8129 100644 --- a/fuzz/fuzz_targets/descriptors.rs +++ b/fuzz/fuzz_targets/descriptors.rs @@ -154,11 +154,13 @@ fuzz_target!(|config: Config| { desc.receive_descriptor().derive(der_index, &SECP256K1), desc.change_descriptor().derive(der_index, &SECP256K1), ]; + let mut psbt_in = Default::default(); + let mut psbt_out = Default::default(); for desc in &der_descs { desc.address(Network::Bitcoin); desc.script_pubkey(); - desc.witness_script(); - desc.bip32_derivations(); + desc.update_psbt_in(&mut psbt_in); + desc.update_change_psbt_out(&mut psbt_out); } // Exercise the methods gathering information from a PSBT. TODO: get more useful PSBTs. @@ -172,12 +174,11 @@ fuzz_target!(|config: Config| { // for outputs. let rec_desc = &der_descs[0]; for psbt_in in psbt.inputs.iter_mut() { - psbt_in.witness_script = Some(rec_desc.witness_script()); - psbt_in.bip32_derivation = rec_desc.bip32_derivations(); + rec_desc.update_psbt_in(psbt_in); } let change_desc = &der_descs[1]; for psbt_out in psbt.outputs.iter_mut() { - psbt_out.bip32_derivation = change_desc.bip32_derivations(); + change_desc.update_change_psbt_out(psbt_out); } // Now get the spend info again with these info. diff --git a/src/descriptors/mod.rs b/src/descriptors/mod.rs index 2d249b54..dfe2806e 100644 --- a/src/descriptors/mod.rs +++ b/src/descriptors/mod.rs @@ -2,7 +2,7 @@ use miniscript::{ bitcoin::{ self, bip32, constants::WITNESS_SCALE_FACTOR, - psbt::{Input as PsbtIn, Psbt}, + psbt::{Input as PsbtIn, Output as PsbtOut, Psbt}, secp256k1, }, descriptor, translate_hash_clone, ForEachKey, TranslatePk, Translator, @@ -519,11 +519,11 @@ impl DerivedSinglePathLianaDesc { self.0.script_pubkey() } - pub fn witness_script(&self) -> bitcoin::ScriptBuf { + fn witness_script(&self) -> bitcoin::ScriptBuf { self.0.explicit_script().expect("Not a Taproot descriptor") } - pub fn bip32_derivations(&self) -> Bip32Deriv { + fn bip32_derivations(&self) -> Bip32Deriv { let ms = match self.0 { descriptor::Descriptor::Wsh(ref wsh) => match wsh.as_inner() { descriptor::WshInner::Ms(ms) => ms, @@ -539,6 +539,18 @@ impl DerivedSinglePathLianaDesc { .map(|k| (k.key.inner, (k.origin.0, k.origin.1))) .collect() } + + /// Update the PSBT input information with data from this derived descriptor. + pub fn update_psbt_in(&self, psbtin: &mut PsbtIn) { + psbtin.bip32_derivation = self.bip32_derivations(); + psbtin.witness_script = Some(self.witness_script()); + } + + /// Update the info of a PSBT output for a change output with data from this derived + /// descriptor. + pub fn update_change_psbt_out(&self, psbtout: &mut PsbtOut) { + psbtout.bip32_derivation = self.bip32_derivations(); + } } #[cfg(test)] @@ -833,8 +845,10 @@ mod tests { // Sanity check we can call the methods on the derived desc der_desc.script_pubkey(); - der_desc.witness_script(); - assert!(!der_desc.bip32_derivations().is_empty()); + let mut psbt_in = PsbtIn::default(); + der_desc.update_psbt_in(&mut psbt_in); + assert!(psbt_in.witness_script.is_some()); + assert!(!psbt_in.bip32_derivation.is_empty()); } #[test] diff --git a/src/signer.rs b/src/signer.rs index e074e233..9565d998 100644 --- a/src/signer.rs +++ b/src/signer.rs @@ -427,6 +427,12 @@ mod tests { // Create a dummy PSBT spending a coin from this descriptor with a single input and single // (external) output. We'll be modifying it as we go. let spent_coin_desc = desc.receive_descriptor().derive(42.into(), &secp); + let mut psbt_in = PsbtIn::default(); + spent_coin_desc.update_psbt_in(&mut psbt_in); + psbt_in.witness_utxo = Some(bitcoin::TxOut { + value: Amount::from_sat(19_000), + script_pubkey: spent_coin_desc.script_pubkey(), + }); let mut dummy_psbt = Psbt { unsigned_tx: bitcoin::Transaction { version: bitcoin::transaction::Version::TWO, @@ -453,15 +459,7 @@ mod tests { xpub: BTreeMap::new(), proprietary: BTreeMap::new(), unknown: BTreeMap::new(), - inputs: vec![PsbtIn { - witness_script: Some(spent_coin_desc.witness_script()), - bip32_derivation: spent_coin_desc.bip32_derivations(), - witness_utxo: Some(bitcoin::TxOut { - value: Amount::from_sat(19_000), - script_pubkey: spent_coin_desc.script_pubkey(), - }), - ..PsbtIn::default() - }], + inputs: vec![psbt_in], outputs: Vec::new(), }; @@ -492,15 +490,13 @@ mod tests { // We can add another input to the PSBT. If we don't attach also another transaction input // it will fail. let other_spent_coin_desc = desc.receive_descriptor().derive(84.into(), &secp); - dummy_psbt.inputs.push(PsbtIn { - witness_script: Some(other_spent_coin_desc.witness_script()), - bip32_derivation: other_spent_coin_desc.bip32_derivations(), - witness_utxo: Some(bitcoin::TxOut { - value: Amount::from_sat(19_000), - script_pubkey: other_spent_coin_desc.script_pubkey(), - }), - ..PsbtIn::default() + let mut psbt_in = PsbtIn::default(); + other_spent_coin_desc.update_psbt_in(&mut psbt_in); + psbt_in.witness_utxo = Some(bitcoin::TxOut { + value: Amount::from_sat(19_000), + script_pubkey: other_spent_coin_desc.script_pubkey(), }); + dummy_psbt.inputs.push(psbt_in); let psbt = dummy_psbt.clone(); assert!(prim_signer_a .sign_psbt(psbt, &secp) diff --git a/src/spend.rs b/src/spend.rs index d545375e..555ec621 100644 --- a/src/spend.rs +++ b/src/spend.rs @@ -649,20 +649,17 @@ pub fn create_spend( }); // If it's an address of ours, signal it as change to signing devices by adding the // BIP32 derivation path to the PSBT output. - let bip32_derivation = if let Some(AddrInfo { index, is_change }) = address.info { + let mut psbt_out = PsbtOut::default(); + if let Some(AddrInfo { index, is_change }) = address.info { let desc = if is_change { main_descriptor.change_descriptor() } else { main_descriptor.receive_descriptor() }; - desc.derive(index, secp).bip32_derivations() - } else { - Default::default() - }; - psbt_outs.push(PsbtOut { - bip32_derivation, - ..PsbtOut::default() - }); + desc.derive(index, secp) + .update_change_psbt_out(&mut psbt_out) + } + psbt_outs.push(psbt_out); } assert_eq!(tx.output.is_empty(), is_self_send); @@ -718,24 +715,21 @@ pub fn create_spend( // If the change address is ours, tell the signers by setting the BIP32 derivations in the // PSBT output. - let bip32_derivation = if let Some(AddrInfo { index, is_change }) = change_addr.info { + let mut psbt_out = PsbtOut::default(); + if let Some(AddrInfo { index, is_change }) = change_addr.info { let desc = if is_change { main_descriptor.change_descriptor() } else { main_descriptor.receive_descriptor() }; - desc.derive(index, secp).bip32_derivations() - } else { - Default::default() - }; + desc.derive(index, secp) + .update_change_psbt_out(&mut psbt_out); + } // TODO: shuffle once we have Taproot change_txo.value = change_amount; tx.output.push(change_txo); - psbt_outs.push(PsbtOut { - bip32_derivation, - ..PsbtOut::default() - }); + psbt_outs.push(psbt_out); } else if max_change_amount.to_sat() > 0 { warnings.push(CreateSpendWarning::ChangeAddedToFee( max_change_amount.to_sat(), @@ -762,21 +756,15 @@ pub fn create_spend( }); // Populate the PSBT input with the information needed by signers. + let mut psbt_in = PsbtIn::default(); let coin_desc = derived_desc(secp, main_descriptor, cand); - let witness_script = Some(coin_desc.witness_script()); - let witness_utxo = Some(bitcoin::TxOut { + coin_desc.update_psbt_in(&mut psbt_in); + psbt_in.witness_utxo = Some(bitcoin::TxOut { value: cand.amount, script_pubkey: coin_desc.script_pubkey(), }); - let non_witness_utxo = tx_getter.get_tx(&cand.outpoint.txid); - let bip32_derivation = coin_desc.bip32_derivations(); - psbt_ins.push(PsbtIn { - witness_script, - witness_utxo, - bip32_derivation, - non_witness_utxo, - ..PsbtIn::default() - }); + psbt_in.non_witness_utxo = tx_getter.get_tx(&cand.outpoint.txid); + psbt_ins.push(psbt_in); } // Finally, create the PSBT with all inputs and outputs, sanity check it and return it.