Skip to content
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

Taproot descriptor #267

Closed
Closed
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
54 changes: 54 additions & 0 deletions src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ mod bare;
mod segwitv0;
mod sh;
mod sortedmulti;
mod tr;
Copy link
Member

Choose a reason for hiding this comment

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

In 73dd23e,
You should be exposing public utilities from private module tr to the external user. In particular, I think you need to expose TapTree/Tr.

A good exercise might be to write an example (see /examples folder) to see how an external user might use the library.

Copy link
Member

Choose a reason for hiding this comment

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

This is still not addressed

Copy link
Member

Choose a reason for hiding this comment

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

We still need a statement with pub use self::tr::{TapTree} and all other utilities that need to be exposed.


// Descriptor Exports
pub use self::bare::{Bare, Pkh};
pub use self::segwitv0::{Wpkh, Wsh, WshInner};
Expand All @@ -53,6 +55,7 @@ pub use self::sortedmulti::SortedMultiVec;

mod checksum;
mod key;

pub use self::key::{
ConversionError, DescriptorKeyParseError, DescriptorPublicKey, DescriptorSecretKey,
DescriptorSinglePriv, DescriptorSinglePub, DescriptorXKey, InnerXKey, Wildcard,
Expand Down Expand Up @@ -166,6 +169,9 @@ pub enum Descriptor<Pk: MiniscriptKey> {
Sh(Sh<Pk>),
/// Pay-to-Witness-ScriptHash with Segwitv0 context
Wsh(Wsh<Pk>),
// /// Pay-to-Taproot with Segwitv0 context
// /// TODO: Update context to Segwitv1
// Tr(Tr<Pk>)
}

/// Descriptor Type of the descriptor
Expand All @@ -191,6 +197,8 @@ pub enum DescriptorType {
WshSortedMulti,
/// Sh Wsh Sorted Multi
ShWshSortedMulti,
// /// Tr Descriptor
// Tr
}

impl<Pk: MiniscriptKey> Descriptor<Pk> {
Expand Down Expand Up @@ -277,6 +285,12 @@ impl<Pk: MiniscriptKey> Descriptor<Pk> {
Ok(Descriptor::Wsh(Wsh::new_sortedmulti(k, pks)?))
}

// /// Create new tr descriptor
// /// Errors when miniscript exceeds resource limits under Segwitv0 context
// pub fn new_tr(key: Pk, script: Option<tr::TapTree<Pk>>) -> Result<Self, Error> {
// Ok(Descriptor::Tr(Tr::new(key, script)?))
// }

/// Get the [DescriptorType] of [Descriptor]
pub fn desc_type(&self) -> DescriptorType {
match *self {
Expand All @@ -296,6 +310,7 @@ impl<Pk: MiniscriptKey> Descriptor<Pk> {
WshInner::SortedMulti(ref _smv) => DescriptorType::WshSortedMulti,
WshInner::Ms(ref _ms) => DescriptorType::Wsh,
},
// Descriptor::Tr(_) => DescriptorType::Tr,
}
}
}
Expand Down Expand Up @@ -622,6 +637,7 @@ serde_string_impl_pk!(Descriptor, "a script descriptor");
#[cfg(test)]
mod tests {
use super::checksum::desc_checksum;
use super::tr::Tr;
use super::DescriptorTrait;
use bitcoin::blockdata::opcodes::all::{OP_CLTV, OP_CSV};
use bitcoin::blockdata::script::Instruction;
Expand Down Expand Up @@ -1086,6 +1102,44 @@ mod tests {
assert_eq!(check, &Ok(Instruction::Op(OP_CSV)))
}

#[test]
fn tr_roundtrip_key() {
let script = Tr::<DummyKey>::from_str("tr()").unwrap().to_string();
assert_eq!(script, format!("tr()#x4ml3kxd"))
}

#[test]
fn tr_roundtrip_script() {
let descriptor = Tr::<DummyKey>::from_str("tr(,{pk(),pk()})")
.unwrap()
.to_string();

assert_eq!(descriptor, "tr(,{pk(),pk()})#7dqr6v8r")
}

#[test]
fn tr_roundtrip_tree() {
let p1 = "020000000000000000000000000000000000000000000000000000000000000001";
let p2 = "020000000000000000000000000000000000000000000000000000000000000002";
let p3 = "020000000000000000000000000000000000000000000000000000000000000003";
let p4 = "020000000000000000000000000000000000000000000000000000000000000004";
let p5 = "f54a5851e9372b87810a8e60cdd2e7cfd80b6e31";
let descriptor = Tr::<PublicKey>::from_str(&format!(
"tr({},{{pk({}),{{pk({}),or_d(pk({}),pkh({}))}}}})",
p1, p2, p3, p4, p5
))
.unwrap()
.to_string();

assert_eq!(
descriptor,
format!(
"tr({},{{pk({}),{{pk({}),or_d(pk({}),pkh({}))}}}})#fdhmu4fj",
p1, p2, p3, p4, p5
)
)
}

#[test]
fn roundtrip_tests() {
let descriptor = Descriptor::<bitcoin::PublicKey>::from_str("multi");
Expand Down
260 changes: 260 additions & 0 deletions src/descriptor/tr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
// Tapscript

use super::checksum::{desc_checksum, verify_checksum};
use bitcoin::hashes::_export::_core::fmt::Formatter;
use errstr;
use expression::{self, FromTree, Tree};
use miniscript::{limits::TAPROOT_MAX_NODE_COUNT, Miniscript};
use std::cmp::max;
use std::sync::Arc;
use std::{fmt, str::FromStr};
use Segwitv0;
use {Error, MiniscriptKey};

#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum TapTree<Pk: MiniscriptKey> {
Tree(Arc<TapTree<Pk>>, Arc<TapTree<Pk>>),
Leaf(Arc<Miniscript<Pk, Segwitv0>>),
Copy link
Contributor

Choose a reason for hiding this comment

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

@sanket1729: I assume we can't use Segwitv0 here. Neither Tap, since tapscript is only valid for leafs with hash version 0xC0. No idea how to solve this problem without refactoring the whole context system

Copy link
Member

Choose a reason for hiding this comment

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

@dr-orlovsky. This is being done in #278 now

}

#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct Tr<Pk: MiniscriptKey> {
internal_key: Pk,
tree: Option<TapTree<Pk>>,
}

impl<Pk: MiniscriptKey> TapTree<Pk> {
fn taptree_height(&self) -> usize {
match *self {
TapTree::Tree(ref left_tree, ref right_tree) => {
1 + max(left_tree.taptree_height(), right_tree.taptree_height())
}
TapTree::Leaf(_) => 1,
}
}

pub fn to_string_no_checksum(&self) -> String {
match self {
TapTree::Tree(ref left, ref right) => {
format!("{{{},{}}}", *left, *right)
}
TapTree::Leaf(ref script) => format!("{}", *script),
}
}
}

impl<Pk: MiniscriptKey> fmt::Display for TapTree<Pk> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let desc = self.to_string_no_checksum();
write!(f, "{}", &desc)
}
}

impl<Pk: MiniscriptKey> Tr<Pk> {
pub fn new(internal_key: Pk, tree: Option<TapTree<Pk>>) -> Result<Self, Error> {
let nodes = match tree {
Some(ref t) => t.taptree_height(),
None => 0,
};

if nodes <= TAPROOT_MAX_NODE_COUNT {
Ok(Self { internal_key, tree })
Copy link
Member

Choose a reason for hiding this comment

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

If condition is reversed. It should be error if nodes > TAPROOT_MAX_NODE_COUNT

} else {
Err(Error::MaxRecursiveDepthExceeded)
}
}

fn to_string_no_checksum(&self) -> String {
let key = &self.internal_key;
match self.tree {
Some(ref s) => format!("tr({},{})", key, s),
None => format!("tr({})", key),
}
}

pub fn internal_key(&self) -> &Pk {
&self.internal_key
}

pub fn taptree(&self) -> &Option<TapTree<Pk>> {
&self.tree
}
}

impl<Pk> Tr<Pk>
where
Pk: MiniscriptKey + FromStr,
Pk::Hash: FromStr,
<Pk as FromStr>::Err: ToString,
<<Pk as MiniscriptKey>::Hash as FromStr>::Err: ToString,
{
Copy link
Member

Choose a reason for hiding this comment

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

Same comment about the bounds here. Try to separate out the functions that really need the bound into its own impl. For example, the new function Tr::new() should not need this bound.

Copy link
Member

Choose a reason for hiding this comment

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

Also make accessor function for fields in Tr as those are private

pub fn tr_script_path(tree: &Tree) -> Result<TapTree<Pk>, Error> {
match tree {
Tree { name, args } if name.len() > 0 && args.len() == 0 => {
let script = Miniscript::<Pk, Segwitv0>::from_str(name)?;
Ok(TapTree::Leaf(Arc::new(script)))
}
Tree { name, args } if name.len() == 0 && args.len() == 2 => {
let left = Self::tr_script_path(&args[0])?;
let right = Self::tr_script_path(&args[1])?;
Ok(TapTree::Tree(Arc::new(left), Arc::new(right)))
}
_ => {
return Err(Error::Unexpected(
"unknown format for script spending paths while parsing taproot descriptor"
.to_string(),
));
}
}
}
}

impl<Pk: MiniscriptKey> FromTree for Tr<Pk>
where
Pk: MiniscriptKey + FromStr,
Pk::Hash: FromStr,
<Pk as FromStr>::Err: ToString,
<<Pk as MiniscriptKey>::Hash as FromStr>::Err: ToString,
{
fn from_tree(top: &Tree) -> Result<Self, Error> {
if top.name == "tr" {
match top.args.len() {
1 => {
let key = &top.args[0];
if key.args.len() > 0 {
return Err(Error::Unexpected(format!(
"#{} script associated with `key-path` while parsing taproot descriptor",
key.args.len()
)));
}
Ok(Tr {
internal_key: expression::terminal(key, Pk::from_str)?,
tree: None,
})
}
2 => {
let ref key = top.args[0];
if key.args.len() > 0 {
return Err(Error::Unexpected(format!(
"#{} script associated with `key-path` while parsing taproot descriptor",
key.args.len()
)));
}
let ref tree = top.args[1];
let ret = Tr::tr_script_path(tree)?;
Ok(Tr {
internal_key: expression::terminal(key, Pk::from_str)?,
tree: Some(ret),
})
}
_ => {
return Err(Error::Unexpected(format!(
"{}[#{} args] while parsing taproot descriptor",
top.name,
top.args.len()
)));
}
}
} else {
return Err(Error::Unexpected(format!(
"{}[#{} args] while parsing taproot descriptor",
top.name,
top.args.len()
)));
}
}
}

impl<Pk: MiniscriptKey> FromStr for Tr<Pk>
where
Pk: MiniscriptKey + FromStr,
Pk::Hash: FromStr,
<Pk as FromStr>::Err: ToString,
<<Pk as MiniscriptKey>::Hash as FromStr>::Err: ToString,
{
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let desc_str = verify_checksum(s)?;
let top = parse_tr(desc_str)?;
Self::from_tree(&top)
}
}

impl<Pk: MiniscriptKey> fmt::Display for Tr<Pk> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let desc = self.to_string_no_checksum();
let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?;
write!(f, "{}#{}", &desc, &checksum)
}
}

fn parse_tr(s: &str) -> Result<Tree, Error> {
for ch in s.bytes() {
if ch > 0x7f {
return Err(Error::Unprintable(ch));
}
}

let ret = if s.len() > 3 && &s[..3] == "tr(" && s.as_bytes()[s.len() - 1] == b')' {
let rest = &s[3..s.len() - 1];
if !rest.contains(',') {
let internal_key = Tree {
name: rest,
args: vec![],
};
return Ok(Tree {
name: "tr",
args: vec![internal_key],
});
}
// use str::split_once() method to refactor this when compiler version bumps up
let (key, script) = split_once(rest, ',')
.ok_or_else(|| Error::BadDescriptor("invalid taproot descriptor".to_string()))?;

let internal_key = Tree {
name: key,
args: vec![],
};
if script.is_empty() {
return Ok(Tree {
name: "tr",
args: vec![internal_key],
});
}
let (tree, rest) = expression::Tree::from_slice_helper_curly(script, 1)?;
if rest.is_empty() {
Ok(Tree {
name: "tr",
args: vec![internal_key, tree],
})
} else {
Err(errstr(rest))
}
} else {
Err(Error::Unexpected("invalid taproot descriptor".to_string()))
};

return ret;
}

fn split_once(inp: &str, delim: char) -> Option<(&str, &str)> {
let ret = if inp.len() == 0 {
None
} else {
let mut found = inp.len();
for (idx, ch) in inp.chars().enumerate() {
if ch == delim {
found = idx;
break;
}
}
// No comma or trailing comma found
if found >= inp.len() - 1 {
Some((&inp[..], ""))
} else {
Some((&inp[..found], &inp[found + 1..]))
Copy link
Member

Choose a reason for hiding this comment

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

Add a comment that this deals with the case when the , is the last character.

}
};
return ret;
}
Loading