Introduce Hidden leaves in ScriptLeaves
Cleanly separate `TapTree` and `NodeInfo`. Fix serde not respecting invariants for several data structures Repurpose some tests from removed taproot builder for taptree
This commit is contained in:
parent
22bc39a143
commit
9affda3012
|
@ -95,6 +95,8 @@ pub enum Error {
|
|||
InvalidLeafVersion,
|
||||
/// Parsing error indicating a taproot error
|
||||
Taproot(&'static str),
|
||||
/// Taproot tree deserilaization error
|
||||
TapTree(crate::taproot::IncompleteBuilder),
|
||||
/// Error related to an xpub key
|
||||
XPubKey(&'static str),
|
||||
/// Error related to PSBT version
|
||||
|
@ -140,6 +142,7 @@ impl fmt::Display for Error {
|
|||
Error::InvalidControlBlock => f.write_str("invalid control block"),
|
||||
Error::InvalidLeafVersion => f.write_str("invalid leaf version"),
|
||||
Error::Taproot(s) => write!(f, "taproot error - {}", s),
|
||||
Error::TapTree(ref e) => write_err!(f, "taproot tree error"; e),
|
||||
Error::XPubKey(s) => write!(f, "xpub key error - {}", s),
|
||||
Error::Version(s) => write!(f, "version error {}", s),
|
||||
Error::PartialDataConsumption => f.write_str("data not consumed entirely when explicitly deserializing"),
|
||||
|
@ -183,6 +186,7 @@ impl std::error::Error for Error {
|
|||
| InvalidControlBlock
|
||||
| InvalidLeafVersion
|
||||
| Taproot(_)
|
||||
| TapTree(_)
|
||||
| XPubKey(_)
|
||||
| Version(_)
|
||||
| PartialDataConsumption=> None,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
//! according to the BIP-174 specification.
|
||||
//!
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::convert::TryInto;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
@ -385,23 +386,22 @@ impl Deserialize for (Vec<TapLeafHash>, KeySource) {
|
|||
|
||||
impl Serialize for TapTree {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
match (self.0.branch().len(), self.0.branch().last()) {
|
||||
(1, Some(Some(root))) => {
|
||||
let mut buf = Vec::new();
|
||||
for leaf_info in root.leaves.iter() {
|
||||
// # Cast Safety:
|
||||
//
|
||||
// TaprootMerkleBranch can only have len atmost 128(TAPROOT_CONTROL_MAX_NODE_COUNT).
|
||||
// safe to cast from usize to u8
|
||||
buf.push(leaf_info.merkle_branch().as_inner().len() as u8);
|
||||
buf.push(leaf_info.leaf_version().to_consensus());
|
||||
leaf_info.script().consensus_encode(&mut buf).expect("Vecs dont err");
|
||||
}
|
||||
buf
|
||||
}
|
||||
// This should be unreachable as we Taptree is already finalized
|
||||
_ => unreachable!(),
|
||||
let script_leaves_iter = self.script_leaves();
|
||||
let mut buf = Vec::with_capacity(script_leaves_iter.len());
|
||||
for leaf_info in script_leaves_iter {
|
||||
// # Panics:
|
||||
//
|
||||
// TapTree only has script leaves. We will remove this expect in future commit.
|
||||
let (script, ver) = leaf_info.leaf().as_script().expect("TapTree only has script leaves");
|
||||
// # Cast Safety:
|
||||
//
|
||||
// TaprootMerkleBranch can only have len atmost 128(TAPROOT_CONTROL_MAX_NODE_COUNT).
|
||||
// safe to cast from usize to u8
|
||||
buf.push(leaf_info.merkle_branch().len() as u8);
|
||||
buf.push(ver.to_consensus());
|
||||
script.consensus_encode(&mut buf).expect("Vecs dont err");
|
||||
}
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -420,11 +420,7 @@ impl Deserialize for TapTree {
|
|||
builder = builder.add_leaf_with_ver(*depth, script, leaf_version)
|
||||
.map_err(|_| Error::Taproot("Tree not in DFS order"))?;
|
||||
}
|
||||
if builder.is_finalizable() && !builder.has_hidden_nodes() {
|
||||
Ok(TapTree(builder))
|
||||
} else {
|
||||
Err(Error::Taproot("Incomplete taproot Tree"))
|
||||
}
|
||||
TapTree::try_from(builder).map_err(Error::TapTree)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -94,17 +94,23 @@ impl TapLeafHash {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<ScriptLeaf> for TapLeafHash {
|
||||
fn from(leaf: ScriptLeaf) -> TapLeafHash { leaf.leaf_hash() }
|
||||
impl From<ScriptLeaf> for TapNodeHash {
|
||||
fn from(leaf: ScriptLeaf) -> TapNodeHash { leaf.node_hash() }
|
||||
}
|
||||
|
||||
impl From<&ScriptLeaf> for TapLeafHash {
|
||||
fn from(leaf: &ScriptLeaf) -> TapLeafHash { leaf.leaf_hash() }
|
||||
impl From<&ScriptLeaf> for TapNodeHash {
|
||||
fn from(leaf: &ScriptLeaf) -> TapNodeHash { leaf.node_hash() }
|
||||
}
|
||||
|
||||
impl TapNodeHash {
|
||||
/// Computes branch hash given two hashes of the nodes underneath it.
|
||||
pub fn from_node_hashes(a: TapNodeHash, b: TapNodeHash) -> TapNodeHash {
|
||||
Self::combine_node_hashes(a, b).0
|
||||
}
|
||||
|
||||
/// Computes branch hash given two hashes of the nodes underneath it and returns
|
||||
/// whether the left node was the one hashed first.
|
||||
fn combine_node_hashes(a: TapNodeHash, b: TapNodeHash) -> (TapNodeHash, bool) {
|
||||
let mut eng = TapNodeHash::engine();
|
||||
if a < b {
|
||||
eng.input(a.as_ref());
|
||||
|
@ -113,7 +119,16 @@ impl TapNodeHash {
|
|||
eng.input(b.as_ref());
|
||||
eng.input(a.as_ref());
|
||||
};
|
||||
TapNodeHash::from_engine(eng)
|
||||
(TapNodeHash::from_engine(eng), a < b)
|
||||
}
|
||||
|
||||
/// Assumes the given 32 byte array as hidden [`TapNodeHash`].
|
||||
///
|
||||
/// Similar to [`TapLeafHash::from_inner`], but explicitly conveys that the
|
||||
/// hash is constructed from a hidden node. This also has better ergonomics
|
||||
/// because it does not require the caller to import the Hash trait.
|
||||
pub fn assume_hidden(hash: [u8; 32]) -> TapNodeHash {
|
||||
TapNodeHash::from_byte_array(hash)
|
||||
}
|
||||
|
||||
/// Computes the [`TapNodeHash`] from a script and a leaf version.
|
||||
|
@ -263,16 +278,27 @@ impl TaprootSpendInfo {
|
|||
// Create as if it is a key spend path with the given merkle root
|
||||
let root_hash = Some(node.hash);
|
||||
let mut info = TaprootSpendInfo::new_key_spend(secp, internal_key, root_hash);
|
||||
|
||||
for leaves in node.leaves {
|
||||
let key = (leaves.script, leaves.ver);
|
||||
let value = leaves.merkle_branch;
|
||||
if let Some(set) = info.script_map.get_mut(&key) {
|
||||
set.insert(value);
|
||||
continue; // NLL fix
|
||||
match leaves.leaf {
|
||||
TapLeaf::Hidden(_) => {
|
||||
// We don't store any information about hidden nodes in TaprootSpendInfo.
|
||||
}
|
||||
TapLeaf::Script(script, ver) => {
|
||||
let key = (script, ver);
|
||||
let value = leaves.merkle_branch;
|
||||
match info.script_map.get_mut(&key) {
|
||||
None => {
|
||||
let mut set = BTreeSet::new();
|
||||
set.insert(value);
|
||||
info.script_map.insert(key, set);
|
||||
},
|
||||
Some(set) => {
|
||||
set.insert(value);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
let mut set = BTreeSet::new();
|
||||
set.insert(value);
|
||||
info.script_map.insert(key, set);
|
||||
}
|
||||
info
|
||||
}
|
||||
|
@ -316,8 +342,6 @@ impl From<&TaprootSpendInfo> for TapTweakHash {
|
|||
/// See Wikipedia for more details on [DFS](https://en.wikipedia.org/wiki/Depth-first_search).
|
||||
// Similar to Taproot Builder in Bitcoin Core.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
|
||||
pub struct TaprootBuilder {
|
||||
// The following doc-comment is from Bitcoin Core, but modified for Rust. It describes the
|
||||
// current state of the builder for a given tree.
|
||||
|
@ -358,6 +382,15 @@ impl TaprootBuilder {
|
|||
/// Creates a new instance of [`TaprootBuilder`].
|
||||
pub fn new() -> Self { TaprootBuilder { branch: vec![] } }
|
||||
|
||||
/// Creates a new instance of [`TaprootBuilder`] with a capacity hint for `size` elements.
|
||||
///
|
||||
/// The size here should be maximum depth of the tree.
|
||||
pub fn with_capacity(size: usize) -> Self {
|
||||
TaprootBuilder {
|
||||
branch: Vec::with_capacity(size),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`TaprootSpendInfo`] from a list of scripts (with default script version) and
|
||||
/// weights of satisfaction for that script.
|
||||
///
|
||||
|
@ -443,6 +476,33 @@ impl TaprootBuilder {
|
|||
/// Checks if the builder has finalized building a tree.
|
||||
pub fn is_finalizable(&self) -> bool { self.branch.len() == 1 && self.branch[0].is_some() }
|
||||
|
||||
/// Converts the builder into a [`NodeInfo`] if the builder is a full tree with possibly
|
||||
/// hidden nodes
|
||||
///
|
||||
/// # Errors:
|
||||
///
|
||||
/// [`IncompleteBuilder::NotFinalized`] if the builder is not finalized. The builder
|
||||
/// can be restored by calling [`IncompleteBuilder::into_builder`]
|
||||
pub fn try_into_node_info(mut self) -> Result<NodeInfo, IncompleteBuilder> {
|
||||
if self.branch().len() != 1 {
|
||||
return Err(IncompleteBuilder::NotFinalized(self));
|
||||
}
|
||||
Ok(self.branch.pop()
|
||||
.expect("length checked above")
|
||||
.expect("invariant guarantees node info exists"))
|
||||
}
|
||||
|
||||
/// Converts the builder into a [`TapTree`] if the builder is a full tree and
|
||||
/// does not contain any hidden nodes
|
||||
pub fn try_into_taptree(self) -> Result<TapTree, IncompleteBuilder> {
|
||||
let node = self.try_into_node_info()?;
|
||||
if node.has_hidden_nodes {
|
||||
// Reconstruct the builder as it was if it has hidden nodes
|
||||
return Err(IncompleteBuilder::HiddenParts(TaprootBuilder { branch: vec![Some(node)] }));
|
||||
}
|
||||
Ok(TapTree(node))
|
||||
}
|
||||
|
||||
/// Checks if the builder has hidden nodes.
|
||||
pub fn has_hidden_nodes(&self) -> bool {
|
||||
self.branch.iter().flatten().any(|node| node.has_hidden_nodes)
|
||||
|
@ -525,29 +585,29 @@ impl Default for TaprootBuilder {
|
|||
/// having hidden branches or not being finalized.
|
||||
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum IncompleteTapTree {
|
||||
pub enum IncompleteBuilder {
|
||||
/// Indicates an attempt to construct a tap tree from a builder containing incomplete branches.
|
||||
NotFinalized(TaprootBuilder),
|
||||
/// Indicates an attempt to construct a tap tree from a builder containing hidden parts.
|
||||
HiddenParts(TaprootBuilder),
|
||||
}
|
||||
|
||||
impl IncompleteTapTree {
|
||||
impl IncompleteBuilder {
|
||||
/// Converts error into the original incomplete [`TaprootBuilder`] instance.
|
||||
pub fn into_builder(self) -> TaprootBuilder {
|
||||
match self {
|
||||
IncompleteTapTree::NotFinalized(builder) | IncompleteTapTree::HiddenParts(builder) =>
|
||||
IncompleteBuilder::NotFinalized(builder) | IncompleteBuilder::HiddenParts(builder) =>
|
||||
builder,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for IncompleteTapTree {
|
||||
impl core::fmt::Display for IncompleteBuilder {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.write_str(match self {
|
||||
IncompleteTapTree::NotFinalized(_) =>
|
||||
IncompleteBuilder::NotFinalized(_) =>
|
||||
"an attempt to construct a tap tree from a builder containing incomplete branches.",
|
||||
IncompleteTapTree::HiddenParts(_) =>
|
||||
IncompleteBuilder::HiddenParts(_) =>
|
||||
"an attempt to construct a tap tree from a builder containing hidden parts.",
|
||||
})
|
||||
}
|
||||
|
@ -555,9 +615,9 @@ impl core::fmt::Display for IncompleteTapTree {
|
|||
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
|
||||
impl std::error::Error for IncompleteTapTree {
|
||||
impl std::error::Error for IncompleteBuilder {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
use self::IncompleteTapTree::*;
|
||||
use self::IncompleteBuilder::*;
|
||||
|
||||
match self {
|
||||
NotFinalized(_) | HiddenParts(_) => None,
|
||||
|
@ -565,57 +625,84 @@ impl std::error::Error for IncompleteTapTree {
|
|||
}
|
||||
}
|
||||
|
||||
/// Taproot Tree representing a finalized [`TaprootBuilder`] (a complete binary tree).
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
|
||||
pub struct TapTree(pub(crate) TaprootBuilder);
|
||||
|
||||
impl PartialEq for TapTree {
|
||||
fn eq(&self, other: &Self) -> bool { self.node_info().hash.eq(&other.node_info().hash) }
|
||||
/// Error happening when [`TapTree`] is constructed from a [`NodeInfo`]
|
||||
/// having hidden branches.
|
||||
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum HiddenNodes {
|
||||
/// Indicates an attempt to construct a tap tree from a builder containing hidden parts.
|
||||
HiddenParts(NodeInfo),
|
||||
}
|
||||
|
||||
impl core::hash::Hash for TapTree {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) { self.node_info().hash(state) }
|
||||
}
|
||||
|
||||
impl Eq for TapTree {}
|
||||
|
||||
impl From<TapTree> for TaprootBuilder {
|
||||
#[inline]
|
||||
fn from(tree: TapTree) -> Self { tree.into_builder() }
|
||||
}
|
||||
|
||||
impl TapTree {
|
||||
/// Gets the inner node info as the builder is finalized.
|
||||
pub fn node_info(&self) -> &NodeInfo {
|
||||
// The builder algorithm invariant guarantees that is_complete builder
|
||||
// have only 1 element in branch and that is not None.
|
||||
// We make sure that we only allow is_complete builders via the from_inner
|
||||
// constructor
|
||||
self.0.branch()[0].as_ref().expect("from_inner only parses is_complete builders")
|
||||
}
|
||||
|
||||
/// Converts self into builder [`TaprootBuilder`]. The builder is guaranteed to be finalized.
|
||||
pub fn into_builder(self) -> TaprootBuilder { self.0 }
|
||||
|
||||
/// Constructs [`TaprootBuilder`] by internally cloning the `self`. The builder is guaranteed
|
||||
/// to be finalized.
|
||||
pub fn to_builder(&self) -> TaprootBuilder { self.0.clone() }
|
||||
|
||||
/// Returns [`TapTreeIter<'_>`] iterator for a taproot script tree, operating in DFS order over
|
||||
/// tree [`ScriptLeaf`]s.
|
||||
pub fn script_leaves(&self) -> TapTreeIter {
|
||||
match (self.0.branch().len(), self.0.branch().last()) {
|
||||
(1, Some(Some(root))) => TapTreeIter { leaf_iter: root.leaves.iter() },
|
||||
// This should be unreachable as we Taptree is already finalized
|
||||
_ => unreachable!("non-finalized tree builder inside TapTree"),
|
||||
impl HiddenNodes {
|
||||
/// Converts error into the original incomplete [`NodeInfo`] instance.
|
||||
pub fn into_node_info(self) -> NodeInfo {
|
||||
match self {
|
||||
HiddenNodes::HiddenParts(node_info) => node_info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for HiddenNodes {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.write_str(match self {
|
||||
HiddenNodes::HiddenParts(_) =>
|
||||
"an attempt to construct a tap tree from a node_info containing hidden parts.",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
|
||||
impl std::error::Error for HiddenNodes {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
use self::HiddenNodes::*;
|
||||
|
||||
match self {
|
||||
HiddenParts(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Taproot Tree representing a complete binary tree without any hidden nodes.
|
||||
///
|
||||
/// This is in contrast to [`NodeInfo`], which allows hidden nodes.
|
||||
/// The implementations for Eq, PartialEq and Hash compare the merkle root of the tree
|
||||
//
|
||||
// This is a bug in BIP370 that does not specify how to share trees with hidden nodes,
|
||||
// for which we need a separate type.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
|
||||
#[cfg_attr(feature = "serde", serde(into = "NodeInfo"))]
|
||||
#[cfg_attr(feature = "serde", serde(try_from = "NodeInfo"))]
|
||||
pub struct TapTree(NodeInfo);
|
||||
|
||||
impl From<TapTree> for NodeInfo {
|
||||
#[inline]
|
||||
fn from(tree: TapTree) -> Self { tree.into_node_info() }
|
||||
}
|
||||
|
||||
impl TapTree {
|
||||
/// Gets the reference to inner [`NodeInfo`] of this tree root.
|
||||
pub fn node_info(&self) -> &NodeInfo {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Gets the inner [`NodeInfo`] of this tree root.
|
||||
pub fn into_node_info(self) -> NodeInfo {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns [`TapTreeIter<'_>`] iterator for a taproot script tree, operating in DFS order over
|
||||
/// tree [`ScriptLeaf`]s.
|
||||
pub fn script_leaves(&self) -> TapTreeIter {
|
||||
TapTreeIter { leaf_iter: self.0.leaves.iter() }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TaprootBuilder> for TapTree {
|
||||
type Error = IncompleteTapTree;
|
||||
type Error = IncompleteBuilder;
|
||||
|
||||
/// Constructs [`TapTree`] from a [`TaprootBuilder`] if it is complete binary tree.
|
||||
///
|
||||
|
@ -624,18 +711,35 @@ impl TryFrom<TaprootBuilder> for TapTree {
|
|||
/// A [`TapTree`] iff the `builder` is complete, otherwise return [`IncompleteBuilder`]
|
||||
/// error with the content of incomplete `builder` instance.
|
||||
fn try_from(builder: TaprootBuilder) -> Result<Self, Self::Error> {
|
||||
if !builder.is_finalizable() {
|
||||
Err(IncompleteTapTree::NotFinalized(builder))
|
||||
} else if builder.has_hidden_nodes() {
|
||||
Err(IncompleteTapTree::HiddenParts(builder))
|
||||
builder.try_into_taptree()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NodeInfo> for TapTree {
|
||||
type Error = HiddenNodes;
|
||||
|
||||
/// Constructs [`TapTree`] from a [`NodeInfo`] if it is complete binary tree.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A [`TapTree`] iff the [`NodeInfo`] has no hidden nodes, otherwise return [`HiddenNodes`]
|
||||
/// error with the content of incomplete [`NodeInfo`] instance.
|
||||
fn try_from(node_info: NodeInfo) -> Result<Self, Self::Error> {
|
||||
if node_info.has_hidden_nodes {
|
||||
Err(HiddenNodes::HiddenParts(node_info))
|
||||
} else {
|
||||
Ok(TapTree(builder))
|
||||
Ok(TapTree(node_info))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator for a taproot script tree, operating in DFS order over leaf depth and
|
||||
/// leaf script pairs.
|
||||
/// This is guaranteed to not contain any hidden nodes.
|
||||
|
||||
// TODO: Enforce this in type system in a later PR. Users should not need to unwrap
|
||||
// as script leaf when there are no hidden nodes.
|
||||
// An idea can be to have the iterator return a tuple (depth, script, version).
|
||||
pub struct TapTreeIter<'tree> {
|
||||
leaf_iter: core::slice::Iter<'tree, ScriptLeaf>,
|
||||
}
|
||||
|
@ -647,7 +751,13 @@ impl<'tree> Iterator for TapTreeIter<'tree> {
|
|||
fn next(&mut self) -> Option<Self::Item> { self.leaf_iter.next() }
|
||||
}
|
||||
|
||||
/// Represents the node information in taproot tree.
|
||||
impl<'tree> ExactSizeIterator for TapTreeIter<'tree> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize { self.leaf_iter.len() }
|
||||
}
|
||||
|
||||
/// Represents the node information in taproot tree. In contrast to [`TapTree`], this
|
||||
/// is allowed to have hidden leaves as children.
|
||||
///
|
||||
/// Helper type used in merkle tree construction allowing one to build sparse merkle trees. The node
|
||||
/// represents part of the tree that has information about all of its descendants.
|
||||
|
@ -655,9 +765,7 @@ impl<'tree> Iterator for TapTreeIter<'tree> {
|
|||
///
|
||||
/// You can use [`TaprootSpendInfo::from_node_info`] to a get a [`TaprootSpendInfo`] from the merkle
|
||||
/// root [`NodeInfo`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
|
||||
#[derive(Debug, Clone, PartialOrd, Ord)]
|
||||
pub struct NodeInfo {
|
||||
/// Merkle hash for this node.
|
||||
pub(crate) hash: TapNodeHash,
|
||||
|
@ -667,6 +775,16 @@ pub struct NodeInfo {
|
|||
pub(crate) has_hidden_nodes: bool,
|
||||
}
|
||||
|
||||
impl PartialEq for NodeInfo {
|
||||
fn eq(&self, other: &Self) -> bool { self.hash.eq(&other.hash) }
|
||||
}
|
||||
|
||||
impl core::hash::Hash for NodeInfo {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) { self.hash.hash(state) }
|
||||
}
|
||||
|
||||
impl Eq for NodeInfo {}
|
||||
|
||||
impl NodeInfo {
|
||||
/// Creates a new [`NodeInfo`] with omitted/hidden info.
|
||||
pub fn new_hidden_node(hash: TapNodeHash) -> Self {
|
||||
|
@ -677,7 +795,7 @@ impl NodeInfo {
|
|||
pub fn new_leaf_with_ver(script: ScriptBuf, ver: LeafVersion) -> Self {
|
||||
Self {
|
||||
hash: TapNodeHash::from_script(&script, ver),
|
||||
leaves: vec![ScriptLeaf::new(script, ver)],
|
||||
leaves: vec![ScriptLeaf::new_script(script, ver)],
|
||||
has_hidden_nodes: false,
|
||||
}
|
||||
}
|
||||
|
@ -685,6 +803,8 @@ impl NodeInfo {
|
|||
/// Combines two [`NodeInfo`] to create a new parent.
|
||||
pub fn combine(a: Self, b: Self) -> Result<Self, TaprootBuilderError> {
|
||||
let mut all_leaves = Vec::with_capacity(a.leaves.len() + b.leaves.len());
|
||||
let (hash, left_first) = TapNodeHash::combine_node_hashes(a.hash, b.hash);
|
||||
let (a, b) = if left_first { (a, b) } else { (b, a) };
|
||||
for mut a_leaf in a.leaves {
|
||||
a_leaf.merkle_branch.push(b.hash)?; // add hashing partner
|
||||
all_leaves.push(a_leaf);
|
||||
|
@ -693,7 +813,6 @@ impl NodeInfo {
|
|||
b_leaf.merkle_branch.push(a.hash)?; // add hashing partner
|
||||
all_leaves.push(b_leaf);
|
||||
}
|
||||
let hash = TapNodeHash::from_node_hashes(a.hash, b.hash);
|
||||
Ok(Self {
|
||||
hash,
|
||||
leaves: all_leaves,
|
||||
|
@ -702,48 +821,185 @@ impl NodeInfo {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TaprootBuilder> for NodeInfo {
|
||||
type Error = IncompleteBuilder;
|
||||
|
||||
fn try_from(builder: TaprootBuilder) -> Result<Self, Self::Error> {
|
||||
builder.try_into_node_info()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
|
||||
impl serde::Serialize for NodeInfo {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
use serde::ser::SerializeSeq;
|
||||
let mut seq = serializer.serialize_seq(Some(self.leaves.len() * 2))?;
|
||||
for tap_leaf in self.leaves.iter() {
|
||||
seq.serialize_element(&tap_leaf.merkle_branch().len())?;
|
||||
seq.serialize_element(&tap_leaf.leaf)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
|
||||
impl<'de> serde::Deserialize<'de> for NodeInfo {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct SeqVisitor;
|
||||
impl<'de> serde::de::Visitor<'de> for SeqVisitor {
|
||||
type Value = NodeInfo;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("Taproot tree in DFS walk order")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let size = seq.size_hint()
|
||||
.map(|x| core::mem::size_of::<usize>()*8 - x.leading_zeros() as usize)
|
||||
.map(|x| x / 2) // Each leaf is serialized as two elements.
|
||||
.unwrap_or(0)
|
||||
.min(TAPROOT_CONTROL_MAX_NODE_COUNT); // no more than 128 nodes
|
||||
let mut builder = TaprootBuilder::with_capacity(size);
|
||||
while let Some(depth) = seq.next_element()? {
|
||||
let tap_leaf : TapLeaf = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| serde::de::Error::custom("Missing tap_leaf"))?;
|
||||
match tap_leaf {
|
||||
TapLeaf::Script(script, ver) => {
|
||||
builder = builder.add_leaf_with_ver(depth, script, ver).map_err(|e| {
|
||||
serde::de::Error::custom(format!("Leaf insertion error: {}", e))
|
||||
})?;
|
||||
},
|
||||
TapLeaf::Hidden(h) => {
|
||||
builder = builder.add_hidden_node(depth, h).map_err(|e| {
|
||||
serde::de::Error::custom(format!("Hidden node insertion error: {}", e))
|
||||
})?;
|
||||
},
|
||||
}
|
||||
}
|
||||
NodeInfo::try_from(builder).map_err(|e| {
|
||||
serde::de::Error::custom(format!("Incomplete taproot tree: {}", e))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_seq(SeqVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
/// Leaf node in a taproot tree. Can be either hidden or known.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
|
||||
pub enum TapLeaf {
|
||||
/// A known script
|
||||
Script(ScriptBuf, LeafVersion),
|
||||
/// Hidden Node with the given leaf hash
|
||||
Hidden(TapNodeHash),
|
||||
}
|
||||
|
||||
impl TapLeaf {
|
||||
/// Obtains the hidden leaf hash if the leaf is hidden.
|
||||
pub fn as_hidden(&self) -> Option<&TapNodeHash> {
|
||||
if let Self::Hidden(v) = self {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtains the script and version if the leaf is known.
|
||||
pub fn as_script(&self) -> Option<(&Script, LeafVersion)> {
|
||||
if let Self::Script(script, ver) = self {
|
||||
Some((script, *ver))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Store information about taproot leaf node.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
|
||||
pub struct ScriptLeaf {
|
||||
/// The underlying script.
|
||||
script: ScriptBuf,
|
||||
/// The leaf version.
|
||||
ver: LeafVersion,
|
||||
/// The [`TapLeaf`]
|
||||
leaf: TapLeaf,
|
||||
/// The merkle proof (hashing partners) to get this node.
|
||||
merkle_branch: TaprootMerkleBranch,
|
||||
}
|
||||
|
||||
impl ScriptLeaf {
|
||||
/// Creates an new [`ScriptLeaf`] from `script` and `ver` and no merkle branch.
|
||||
fn new(script: ScriptBuf, ver: LeafVersion) -> Self {
|
||||
Self { script, ver, merkle_branch: TaprootMerkleBranch(vec![]) }
|
||||
pub fn new_script(script: ScriptBuf, ver: LeafVersion) -> Self {
|
||||
Self { leaf: TapLeaf::Script(script, ver), merkle_branch: TaprootMerkleBranch(vec![]) }
|
||||
}
|
||||
|
||||
/// Creates an new [`ScriptLeaf`] from `hash` and no merkle branch.
|
||||
pub fn new_hidden(hash: TapNodeHash) -> Self {
|
||||
Self { leaf: TapLeaf::Hidden(hash), merkle_branch: TaprootMerkleBranch(vec![]) }
|
||||
}
|
||||
|
||||
/// Returns the depth of this script leaf in the tap tree.
|
||||
#[inline]
|
||||
pub fn depth(&self) -> u8 {
|
||||
// Depth is guarded by TAPROOT_CONTROL_MAX_NODE_COUNT.
|
||||
u8::try_from(self.merkle_branch.0.len()).expect("depth is guaranteed to fit in a u8")
|
||||
u8::try_from(self.merkle_branch().0.len()).expect("depth is guaranteed to fit in a u8")
|
||||
}
|
||||
|
||||
/// Computes a leaf hash for this [`ScriptLeaf`].
|
||||
/// Computes a leaf hash for this [`ScriptLeaf`] if the leaf is known.
|
||||
/// See [`ScriptLeaf::node_hash`] for computing the [`TapNodeHash`] which returns
|
||||
/// the hidden node hash if the node is hidden.
|
||||
/// This [`TapLeafHash`] is useful while signing taproot script spends.
|
||||
#[inline]
|
||||
pub fn leaf_hash(&self) -> TapLeafHash { TapLeafHash::from_script(&self.script, self.ver) }
|
||||
pub fn leaf_hash(&self) -> Option<TapLeafHash> {
|
||||
let (script, ver) = self.leaf.as_script()?;
|
||||
Some(TapLeafHash::from_script(script, ver))
|
||||
}
|
||||
|
||||
/// Returns reference to the leaf script.
|
||||
/// Computes the [`TapNodeHash`] for this [`ScriptLeaf`]. This returns the
|
||||
/// leaf hash if the leaf is known and the hidden node hash if the leaf is
|
||||
/// hidden.
|
||||
/// See also, [`ScriptLeaf::leaf_hash`].
|
||||
#[inline]
|
||||
pub fn script(&self) -> &Script { &self.script }
|
||||
pub fn node_hash(&self) -> TapNodeHash {
|
||||
match self.leaf {
|
||||
TapLeaf::Script(ref script, ver) => {
|
||||
TapLeafHash::from_script(script, ver).into()
|
||||
}
|
||||
TapLeaf::Hidden(ref hash) => *hash,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns leaf version of the script.
|
||||
/// Returns reference to the leaf script if the leaf is known.
|
||||
#[inline]
|
||||
pub fn leaf_version(&self) -> LeafVersion { self.ver }
|
||||
pub fn script(&self) -> Option<&Script> { self.leaf.as_script().map(|x| x.0) }
|
||||
|
||||
/// Returns leaf version of the script if the leaf is known.
|
||||
#[inline]
|
||||
pub fn leaf_version(&self) -> Option<LeafVersion> { self.leaf.as_script().map(|x| x.1) }
|
||||
|
||||
/// Returns reference to the merkle proof (hashing partners) to get this
|
||||
/// node in form of [`TaprootMerkleBranch`].
|
||||
#[inline]
|
||||
pub fn merkle_branch(&self) -> &TaprootMerkleBranch { &self.merkle_branch }
|
||||
|
||||
/// Returns a reference to the leaf of this [`ScriptLeaf`].
|
||||
#[inline]
|
||||
pub fn leaf(&self) -> &TapLeaf {
|
||||
&self.leaf
|
||||
}
|
||||
}
|
||||
|
||||
/// The merkle proof for inclusion of a tree in a taptree hash.
|
||||
|
@ -758,6 +1014,12 @@ impl TaprootMerkleBranch {
|
|||
/// Returns a reference to the inner vector of hashes.
|
||||
pub fn as_inner(&self) -> &[TapNodeHash] { &self.0 }
|
||||
|
||||
/// Returns the number of nodes in this merkle proof.
|
||||
pub fn len(&self) -> usize { self.0.len() }
|
||||
|
||||
/// Checks if this merkle proof is empty.
|
||||
pub fn is_empty(&self) -> bool { self.0.is_empty() }
|
||||
|
||||
/// Decodes bytes from control block.
|
||||
#[deprecated(since = "0.30.0", note = "Use decode instead")]
|
||||
pub fn from_slice(sl: &[u8]) -> Result<Self, TaprootError> {
|
||||
|
@ -1518,6 +1780,35 @@ mod test {
|
|||
let builder = builder.finalize(&secp, internal_key).unwrap_err();
|
||||
let builder = builder.add_leaf(3, e.clone()).unwrap();
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
{
|
||||
let tree = TapTree::try_from(builder.clone()).unwrap();
|
||||
// test roundtrip serialization with serde_test
|
||||
#[rustfmt::skip]
|
||||
assert_tokens(&tree.readable(), &[
|
||||
Token::Seq { len: Some(10) },
|
||||
Token::U64(2), Token::TupleVariant { name: "TapLeaf", variant: "Script", len: 2}, Token::Str("51"), Token::U8(192), Token::TupleVariantEnd,
|
||||
Token::U64(2), Token::TupleVariant { name: "TapLeaf", variant: "Script", len: 2}, Token::Str("52"), Token::U8(192), Token::TupleVariantEnd,
|
||||
Token::U64(3), Token::TupleVariant { name: "TapLeaf", variant: "Script", len: 2}, Token::Str("55"), Token::U8(192), Token::TupleVariantEnd,
|
||||
Token::U64(3), Token::TupleVariant { name: "TapLeaf", variant: "Script", len: 2}, Token::Str("54"), Token::U8(192), Token::TupleVariantEnd,
|
||||
Token::U64(2), Token::TupleVariant { name: "TapLeaf", variant: "Script", len: 2}, Token::Str("53"), Token::U8(192), Token::TupleVariantEnd,
|
||||
Token::SeqEnd,
|
||||
],);
|
||||
|
||||
let node_info = TapTree::try_from(builder.clone()).unwrap().into_node_info();
|
||||
// test roundtrip serialization with serde_test
|
||||
#[rustfmt::skip]
|
||||
assert_tokens(&node_info.readable(), &[
|
||||
Token::Seq { len: Some(10) },
|
||||
Token::U64(2), Token::TupleVariant { name: "TapLeaf", variant: "Script", len: 2}, Token::Str("51"), Token::U8(192), Token::TupleVariantEnd,
|
||||
Token::U64(2), Token::TupleVariant { name: "TapLeaf", variant: "Script", len: 2}, Token::Str("52"), Token::U8(192), Token::TupleVariantEnd,
|
||||
Token::U64(3), Token::TupleVariant { name: "TapLeaf", variant: "Script", len: 2}, Token::Str("55"), Token::U8(192), Token::TupleVariantEnd,
|
||||
Token::U64(3), Token::TupleVariant { name: "TapLeaf", variant: "Script", len: 2}, Token::Str("54"), Token::U8(192), Token::TupleVariantEnd,
|
||||
Token::U64(2), Token::TupleVariant { name: "TapLeaf", variant: "Script", len: 2}, Token::Str("53"), Token::U8(192), Token::TupleVariantEnd,
|
||||
Token::SeqEnd,
|
||||
],);
|
||||
}
|
||||
|
||||
let tree_info = builder.finalize(&secp, internal_key).unwrap();
|
||||
let output_key = tree_info.output_key();
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -23,6 +23,7 @@
|
|||
#![cfg(feature = "serde")]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
|
||||
use bincode::serialize;
|
||||
|
@ -36,7 +37,7 @@ use bitcoin::key::UntweakedPublicKey;
|
|||
use bitcoin::psbt::raw::{self, Key, Pair, ProprietaryKey};
|
||||
use bitcoin::psbt::{Input, Output, Psbt, PsbtSighashType};
|
||||
use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
|
||||
use bitcoin::taproot::{self, ControlBlock, LeafVersion, TaprootBuilder, TaprootSpendInfo};
|
||||
use bitcoin::taproot::{self, ControlBlock, LeafVersion, TaprootBuilder, TaprootSpendInfo, TapTree};
|
||||
use bitcoin::{
|
||||
ecdsa, Address, Block, Network, OutPoint, PrivateKey, PublicKey, ScriptBuf, Sequence, Target,
|
||||
Transaction, TxIn, TxOut, Txid, Work,
|
||||
|
@ -352,13 +353,15 @@ fn serde_regression_taproot_sig() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn serde_regression_taproot_builder() {
|
||||
fn serde_regression_taptree() {
|
||||
let ver = LeafVersion::from_consensus(0).unwrap();
|
||||
let script = ScriptBuf::from(vec![0u8, 1u8, 2u8]);
|
||||
let builder = TaprootBuilder::new().add_leaf_with_ver(1, script, ver).unwrap();
|
||||
let mut builder = TaprootBuilder::new().add_leaf_with_ver(1, script.clone(), ver).unwrap();
|
||||
builder = builder.add_leaf(1, script).unwrap();
|
||||
let tree = TapTree::try_from(builder).unwrap();
|
||||
|
||||
let got = serialize(&builder).unwrap();
|
||||
let want = include_bytes!("data/serde/taproot_builder_bincode") as &[_];
|
||||
let got = serialize(&tree).unwrap();
|
||||
let want = include_bytes!("data/serde/taptree_bincode") as &[_];
|
||||
assert_eq!(got, want)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue