Skip to content

Add PsbtInputExt::update_with_descriptor #339

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions integration_test/src/test_desc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use bitcoin::util::{psbt, sighash};
use bitcoin::{self, Amount, OutPoint, SchnorrSig, Script, Transaction, TxIn, TxOut, Txid};
use bitcoincore_rpc::{json, Client, RpcApi};
use miniscript::miniscript::iter;
use miniscript::psbt::PsbtExt;
use miniscript::psbt::{PsbtInputExt, PsbtExt};
use miniscript::{Descriptor, DescriptorTrait, Miniscript, ToPublicKey};
use miniscript::{MiniscriptKey, ScriptContext};
use std::collections::BTreeMap;
Expand Down Expand Up @@ -109,10 +109,10 @@ pub fn test_desc_satisfy(cl: &Client, testdata: &TestData, desc: &str) -> Witnes
script_pubkey: addr.script_pubkey(),
});
let mut input = psbt::Input::default();
input.update_with_descriptor_unchecked(&desc).unwrap();
input.witness_utxo = Some(witness_utxo.clone());
psbt.inputs.push(input);
psbt.outputs.push(psbt::Output::default());
psbt.update_desc(0, &desc, None).unwrap();

// --------------------------------------------
// Sign the transactions with all keys
Expand Down
70 changes: 70 additions & 0 deletions src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
//! these with BIP32 paths, pay-to-contract instructions, etc.
//!

use std::ops::Range;
use std::{collections::HashMap, sync::Arc};
use std::{
fmt,
str::{self, FromStr},
};

use bitcoin::blockdata::witness::Witness;
use bitcoin::util::address::WitnessVersion;
use bitcoin::{self, secp256k1, Script};

use self::checksum::verify_checksum;
Expand Down Expand Up @@ -259,6 +261,22 @@ pub enum DescriptorType {
Tr,
}

impl DescriptorType {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also add a is_taproot() method, even though it will be unused here in miniscript. I'm sure somebody will find that useful!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe segwit_version(&self) -> Option<u8> would be the most general?

/// Returns the segwit version implied by the descriptor type.
///
/// This will return `Some(WitnessVersion::V0)` whether it is "native" segwitv0 or "wrapped" p2sh segwit.
pub fn segwit_version(&self) -> Option<WitnessVersion> {
use self::DescriptorType::*;
match self {
Tr => Some(WitnessVersion::V1),
Wpkh | ShWpkh | Wsh | ShWsh | ShWshSortedMulti | WshSortedMulti => {
Some(WitnessVersion::V0)
}
Bare | Sh | Pkh | ShSortedMulti => None,
}
}
}

impl<Pk: MiniscriptKey> Descriptor<Pk> {
// Keys

Expand Down Expand Up @@ -749,6 +767,31 @@ impl Descriptor<DescriptorPublicKey> {

descriptor.to_string()
}

/// Utility method for deriving the descriptor at each index in a range to find one matching
/// `script_pubkey`.
///
/// If it finds a match then it returns the index it was derived at and the concrete
/// descriptor at that index. If the descriptor is non-derivable then it will simply check the
/// script pubkey against the descriptor and return it if it matches (in this case the index
/// returned will be meaningless).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, TIL we derive at an index not from an index. Cheers.

pub fn find_derivation_index_for_spk<C: secp256k1::Verification>(
&self,
secp: &secp256k1::Secp256k1<C>,
script_pubkey: &Script,
range: Range<u32>,
) -> Result<Option<(u32, Descriptor<bitcoin::PublicKey>)>, ConversionError> {
let range = if self.is_deriveable() { range } else { 0..1 };

for i in range {
let concrete = self.derived_descriptor(&secp, i)?;
if &concrete.script_pubkey() == script_pubkey {
return Ok(Some((i, concrete)));
}
}

Ok(None)
}
}

impl<Pk> expression::FromTree for Descriptor<Pk>
Expand Down Expand Up @@ -1736,4 +1779,31 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))";
Descriptor::<DescriptorPublicKey>::from_str(&format!("wsh(pk({}))", x_only_key))
.unwrap_err();
}

#[test]
fn test_find_derivation_index_for_spk() {
let secp = secp256k1::Secp256k1::verification_only();
let descriptor = Descriptor::from_str("tr([73c5da0a/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/*)").unwrap();
let script_at_0_1 = Script::from_str(
"5120a82f29944d65b86ae6b5e5cc75e294ead6c59391a1edc5e016e3498c67fc7bbb",
)
.unwrap();
let expected_concrete = Descriptor::from_str(
"tr(0283dfe85a3151d2517290da461fe2815591ef69f2b18a2ce63f01697a8b313145)",
)
.unwrap();

assert_eq!(
descriptor.find_derivation_index_for_spk(&secp, &script_at_0_1, 0..1),
Ok(None)
);
assert_eq!(
descriptor.find_derivation_index_for_spk(&secp, &script_at_0_1, 0..2),
Ok(Some((1, expected_concrete.clone())))
);
assert_eq!(
descriptor.find_derivation_index_for_spk(&secp, &script_at_0_1, 0..10),
Ok(Some((1, expected_concrete)))
);
}
}
Loading