Skip to content

Commit

Permalink
Merge rust-bitcoin#751: descriptor: introduce several Taproot accessors
Browse files Browse the repository at this point in the history
86305f0 ci: clippy: remove a ton of now-elidable lifetimes (Andrew Poelstra)
dd3396e ci: fix new clippy lint related to doccomments (Andrew Poelstra)
998b423 descriptor: introduce several Taproot accessors (Andrew Poelstra)

Pull request description:

  When working with Taproot descriptors you typically need to do an annoying (and hard to discover) `match` statement to get the `Tr` out of the descriptor, and then call accessors on that to get the actual data out.

  Add two new methods to `Descriptor` that directly access the internal key and the taptree. Document that the actual leaves can be obtained by calling `.iter` on the taptree.

  Next, when a user is trying to sign a Taproot branch, they need to obtain a TapLeafHash. We have internal code which does this (which I have pulled into a helper function since there is some room to optimize it there..) but no exposed code, forcing the user to go digging through the rust-bitcoin docs to figure it out (including knowing the standard Taproot leaf version, which is an arcane detail of the sort that Miniscript otherwise hides).

  Add a new method `leaf_hash` on Taproot miniscripts, so that the user can directly obtain the leaf hashes.

  Now you can write e.g.

  ```rust
  for script in trdesc.tap_tree_iter() {
      let leaf_hash = script.leaf_hash();
      // Do whatever you want...
  }
  ```

  vs the previous code which was roughly

  ```rust
  let tr = match trdesc {
      Descriptor::Tr(ref tr) => tr,
      _ => unreachable!("I know this is a Taproot descriptor"),
  };
  // Or tr.tap_tree().unwrap().iter() in case you miss the weirdly-named // Tr::iter_scripts
  for script in tr.iter_scripts() {
      // Hope you know your rust-bitcoin docs by heart, and also that
      // .encode is the way to convert a Miniscript to a Script!
      let leaf_hash = TapLeafHash::from_script(
          LeafVersion::TapScript,
          script.encode(),
      );
  }
  ```

ACKs for top commit:
  sanket1729:
    ACK 86305f0

Tree-SHA512: a6d5ca4d63222e03520f08ab1835d5ad292f6fcd021f517427a56d7d53ecf28c975acd2c8bdd0e2d0b9741cf79d34d352cb775b59bd21600b0fb9daa06aced8d
  • Loading branch information
apoelstra committed Oct 23, 2024
2 parents aa3691d + 86305f0 commit 3a85952
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 24 deletions.
2 changes: 1 addition & 1 deletion src/descriptor/checksum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ impl<'f, 'a> Formatter<'f, 'a> {
}
}

impl<'f, 'a> fmt::Write for Formatter<'f, 'a> {
impl fmt::Write for Formatter<'_, '_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.fmt.write_str(s)?;
self.eng.input(s).map_err(|_| fmt::Error)
Expand Down
40 changes: 37 additions & 3 deletions src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,40 @@ impl<Pk: MiniscriptKey> Descriptor<Pk> {
Ok(Descriptor::Tr(Tr::new(key, script)?))
}

/// For a Taproot descriptor, returns the internal key.
pub fn internal_key(&self) -> Option<&Pk> {
if let Descriptor::Tr(ref tr) = self {
Some(tr.internal_key())
} else {
None
}
}

/// For a Taproot descriptor, returns the [`TapTree`] describing the Taproot tree.
///
/// To obtain the individual leaves of the tree, call [`TapTree::iter`] on the
/// returned value.
pub fn tap_tree(&self) -> Option<&TapTree<Pk>> {
if let Descriptor::Tr(ref tr) = self {
tr.tap_tree().as_ref()
} else {
None
}
}

/// For a Taproot descriptor, returns an iterator over the scripts in the Taptree.
///
/// If the descriptor is not a Taproot descriptor, **or** if the descriptor is a
/// Taproot descriptor containing only a keyspend, returns an empty iterator.
pub fn tap_tree_iter(&self) -> tr::TapTreeIter<Pk> {
if let Descriptor::Tr(ref tr) = self {
if let Some(ref tree) = tr.tap_tree() {
return tree.iter();
}
}
tr::TapTreeIter::empty()
}

/// Get the [DescriptorType] of [Descriptor]
pub fn desc_type(&self) -> DescriptorType {
match *self {
Expand Down Expand Up @@ -698,7 +732,7 @@ impl Descriptor<DescriptorPublicKey> {

struct KeyMapWrapper<'a, C: secp256k1::Signing>(KeyMap, &'a secp256k1::Secp256k1<C>);

impl<'a, C: secp256k1::Signing> Translator<String> for KeyMapWrapper<'a, C> {
impl<C: secp256k1::Signing> Translator<String> for KeyMapWrapper<'_, C> {
type TargetPk = DescriptorPublicKey;
type Error = Error;

Expand Down Expand Up @@ -746,7 +780,7 @@ impl Descriptor<DescriptorPublicKey> {
pub fn to_string_with_secret(&self, key_map: &KeyMap) -> String {
struct KeyMapLookUp<'a>(&'a KeyMap);

impl<'a> Translator<DescriptorPublicKey> for KeyMapLookUp<'a> {
impl Translator<DescriptorPublicKey> for KeyMapLookUp<'_> {
type TargetPk = String;
type Error = core::convert::Infallible;

Expand Down Expand Up @@ -909,7 +943,7 @@ impl Descriptor<DefiniteDescriptorKey> {
) -> Result<Descriptor<bitcoin::PublicKey>, ConversionError> {
struct Derivator<'a, C: secp256k1::Verification>(&'a secp256k1::Secp256k1<C>);

impl<'a, C: secp256k1::Verification> Translator<DefiniteDescriptorKey> for Derivator<'a, C> {
impl<C: secp256k1::Verification> Translator<DefiniteDescriptorKey> for Derivator<'_, C> {
type TargetPk = bitcoin::PublicKey;
type Error = ConversionError;

Expand Down
5 changes: 5 additions & 0 deletions src/descriptor/tr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,11 @@ pub struct TapTreeIter<'a, Pk: MiniscriptKey> {
stack: Vec<(u8, &'a TapTree<Pk>)>,
}

impl<Pk: MiniscriptKey> TapTreeIter<'_, Pk> {
/// Helper function to return an empty iterator from Descriptor::tap_tree_iter.
pub(super) fn empty() -> Self { Self { stack: vec![] } }
}

impl<'a, Pk> Iterator for TapTreeIter<'a, Pk>
where
Pk: MiniscriptKey + 'a,
Expand Down
10 changes: 7 additions & 3 deletions src/miniscript/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> PkIter<'a, Pk, Ctx> {
}
}

impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> Iterator for PkIter<'a, Pk, Ctx> {
impl<Pk: MiniscriptKey, Ctx: ScriptContext> Iterator for PkIter<'_, Pk, Ctx> {
type Item = Pk;

fn next(&mut self) -> Option<Self::Item> {
Expand All @@ -199,22 +199,24 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> Iterator for PkIter<'a, Pk, Ctx>
}
}

// Module is public since it export testcase generation which may be used in
// dependent libraries for their own tasts based on Miniscript AST
/// Module is public since it export testcase generation which may be used in
/// dependent libraries for their own tasts based on Miniscript AST
#[cfg(test)]
pub mod test {
use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash};

use super::Miniscript;
use crate::miniscript::context::Segwitv0;

/// Test case.
pub type TestData = (
Miniscript<bitcoin::PublicKey, Segwitv0>,
Vec<bitcoin::PublicKey>,
Vec<hash160::Hash>,
bool, // Indicates that the top-level contains public key or hashes
);

/// Generate a deterministic list of public keys of the given length.
pub fn gen_secp_pubkeys(n: usize) -> Vec<secp256k1::PublicKey> {
let mut ret = Vec::with_capacity(n);
let secp = secp256k1::Secp256k1::new();
Expand All @@ -233,13 +235,15 @@ pub mod test {
ret
}

/// Generate a deterministic list of Bitcoin public keys of the given length.
pub fn gen_bitcoin_pubkeys(n: usize, compressed: bool) -> Vec<bitcoin::PublicKey> {
gen_secp_pubkeys(n)
.into_iter()
.map(|inner| bitcoin::PublicKey { inner, compressed })
.collect()
}

/// Generate a deterministic list of test cases of the given length.
pub fn gen_testcases() -> Vec<TestData> {
let k = gen_bitcoin_pubkeys(10, true);
let _h: Vec<hash160::Hash> = k
Expand Down
2 changes: 1 addition & 1 deletion src/miniscript/lex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub enum Token<'s> {
Bytes65(&'s [u8]),
}

impl<'s> fmt::Display for Token<'s> {
impl fmt::Display for Token<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Token::Num(n) => write!(f, "#{}", n),
Expand Down
40 changes: 30 additions & 10 deletions src/miniscript/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: CC0-1.0

//! # Abstract Syntax Tree
//! Abstract Syntax Tree
//!
//! Defines a variety of data structures for describing Miniscript, a subset of
//! Bitcoin Script which can be efficiently parsed and serialized from Script,
Expand Down Expand Up @@ -289,16 +289,27 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
Ctx::max_satisfaction_size(self).ok_or(Error::ImpossibleSatisfaction)
}

/// Helper function to produce Taproot leaf hashes
fn leaf_hash_internal(&self) -> TapLeafHash
where
Pk: ToPublicKey,
{
TapLeafHash::from_script(&self.encode(), LeafVersion::TapScript)
}

/// Attempt to produce non-malleable satisfying witness for the
/// witness script represented by the parse tree
pub fn satisfy<S: satisfy::Satisfier<Pk>>(&self, satisfier: S) -> Result<Vec<Vec<u8>>, Error>
where
Pk: ToPublicKey,
{
// Only satisfactions for default versions (0xc0) are allowed.
let leaf_hash = TapLeafHash::from_script(&self.encode(), LeafVersion::TapScript);
let satisfaction =
satisfy::Satisfaction::satisfy(&self.node, &satisfier, self.ty.mall.safe, &leaf_hash);
let satisfaction = satisfy::Satisfaction::satisfy(
&self.node,
&satisfier,
self.ty.mall.safe,
&self.leaf_hash_internal(),
);
self._satisfy(satisfaction)
}

Expand All @@ -311,12 +322,11 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
where
Pk: ToPublicKey,
{
let leaf_hash = TapLeafHash::from_script(&self.encode(), LeafVersion::TapScript);
let satisfaction = satisfy::Satisfaction::satisfy_mall(
&self.node,
&satisfier,
self.ty.mall.safe,
&leaf_hash,
&self.leaf_hash_internal(),
);
self._satisfy(satisfaction)
}
Expand Down Expand Up @@ -344,8 +354,12 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
where
Pk: ToPublicKey,
{
let leaf_hash = TapLeafHash::from_script(&self.encode(), LeafVersion::TapScript);
satisfy::Satisfaction::build_template(&self.node, provider, self.ty.mall.safe, &leaf_hash)
satisfy::Satisfaction::build_template(
&self.node,
provider,
self.ty.mall.safe,
&self.leaf_hash_internal(),
)
}

/// Attempt to produce a malleable witness template given the assets available
Expand All @@ -356,16 +370,22 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
where
Pk: ToPublicKey,
{
let leaf_hash = TapLeafHash::from_script(&self.encode(), LeafVersion::TapScript);
satisfy::Satisfaction::build_template_mall(
&self.node,
provider,
self.ty.mall.safe,
&leaf_hash,
&self.leaf_hash_internal(),
)
}
}

impl Miniscript<<Tap as ScriptContext>::Key, Tap> {
/// Returns the leaf hash used within a Taproot signature for this script.
///
/// Note that this method is only implemented for Taproot Miniscripts.
pub fn leaf_hash(&self) -> TapLeafHash { self.leaf_hash_internal() }
}

impl<Ctx: ScriptContext> Miniscript<Ctx::Key, Ctx> {
/// Attempt to parse an insane(scripts don't clear sanity checks)
/// script into a Miniscript representation.
Expand Down
4 changes: 2 additions & 2 deletions src/miniscript/satisfy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ impl_satisfier_for_map_hash_tapleafhash_to_key_taproot_sig! {
impl Satisfier<Pk> for HashMap<(hash160::Hash, TapLeafHash), (Pk, bitcoin::taproot::Signature)>
}

impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier<Pk>> Satisfier<Pk> for &'a S {
impl<Pk: MiniscriptKey + ToPublicKey, S: Satisfier<Pk>> Satisfier<Pk> for &S {
fn lookup_ecdsa_sig(&self, p: &Pk) -> Option<bitcoin::ecdsa::Signature> {
(**self).lookup_ecdsa_sig(p)
}
Expand Down Expand Up @@ -322,7 +322,7 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier<Pk>> Satisfier<Pk> for &'
fn check_after(&self, n: absolute::LockTime) -> bool { (**self).check_after(n) }
}

impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier<Pk>> Satisfier<Pk> for &'a mut S {
impl<Pk: MiniscriptKey + ToPublicKey, S: Satisfier<Pk>> Satisfier<Pk> for &mut S {
fn lookup_ecdsa_sig(&self, p: &Pk) -> Option<bitcoin::ecdsa::Signature> {
(**self).lookup_ecdsa_sig(p)
}
Expand Down
2 changes: 1 addition & 1 deletion src/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ macro_rules! impl_log_method {
}

#[cfg(feature = "std")]
impl<'a> AssetProvider<DefiniteDescriptorKey> for LoggerAssetProvider<'a> {
impl AssetProvider<DefiniteDescriptorKey> for LoggerAssetProvider<'_> {
impl_log_method!(provider_lookup_ecdsa_sig, pk: &DefiniteDescriptorKey, -> bool);
impl_log_method!(provider_lookup_tap_key_spend_sig, pk: &DefiniteDescriptorKey, -> Option<usize>);
impl_log_method!(provider_lookup_tap_leaf_script_sig, pk: &DefiniteDescriptorKey, leaf_hash: &TapLeafHash, -> Option<usize>);
Expand Down
4 changes: 2 additions & 2 deletions src/primitives/threshold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ struct ThreshDisplay<'t, 's, T, const MAX: usize> {
show_k: bool,
}

impl<'t, 's, T, const MAX: usize> fmt::Display for ThreshDisplay<'t, 's, T, MAX>
impl<T, const MAX: usize> fmt::Display for ThreshDisplay<'_, '_, T, MAX>
where
T: fmt::Display,
{
Expand All @@ -286,7 +286,7 @@ where
}
}

impl<'t, 's, T, const MAX: usize> fmt::Debug for ThreshDisplay<'t, 's, T, MAX>
impl<T, const MAX: usize> fmt::Debug for ThreshDisplay<'_, '_, T, MAX>
where
T: fmt::Debug,
{
Expand Down
2 changes: 1 addition & 1 deletion src/psbt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ impl<'psbt> PsbtInputSatisfier<'psbt> {
pub fn new(psbt: &'psbt Psbt, index: usize) -> Self { Self { psbt, index } }
}

impl<'psbt, Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for PsbtInputSatisfier<'psbt> {
impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for PsbtInputSatisfier<'_> {
fn lookup_tap_key_spend_sig(&self) -> Option<bitcoin::taproot::Signature> {
self.psbt.inputs[self.index].tap_key_sig
}
Expand Down

0 comments on commit 3a85952

Please sign in to comment.