descriptor: encapsulate PSBT in/out information update

It'll make it easier to switch to update Taproot info.
This commit is contained in:
Antoine Poinsot 2024-01-25 16:05:30 +01:00
parent f17092375e
commit 85fdc40366
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
5 changed files with 59 additions and 58 deletions

View File

@ -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);
}
});

View File

@ -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.

View File

@ -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]

View File

@ -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)

View File

@ -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.