Merge pull request #677 from sanket1729/taptree_utils
Add taproot data structures
This commit is contained in:
commit
425c61674a
|
@ -178,10 +178,10 @@ mod prelude {
|
||||||
pub use std::{string::{String, ToString}, vec::Vec, boxed::Box, borrow::{Cow, ToOwned}, slice, rc, sync};
|
pub use std::{string::{String, ToString}, vec::Vec, boxed::Box, borrow::{Cow, ToOwned}, slice, rc, sync};
|
||||||
|
|
||||||
#[cfg(all(not(feature = "std"), not(test)))]
|
#[cfg(all(not(feature = "std"), not(test)))]
|
||||||
pub use alloc::collections::{BTreeMap, btree_map};
|
pub use alloc::collections::{BTreeMap, BTreeSet, btree_map, BinaryHeap};
|
||||||
|
|
||||||
#[cfg(any(feature = "std", test))]
|
#[cfg(any(feature = "std", test))]
|
||||||
pub use std::collections::{BTreeMap, btree_map};
|
pub use std::collections::{BTreeMap, BTreeSet, btree_map, BinaryHeap};
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub use std::io::sink;
|
pub use std::io::sink;
|
||||||
|
|
|
@ -31,6 +31,8 @@ use {Script, Transaction, TxOut};
|
||||||
|
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
|
|
||||||
|
use super::taproot::LeafVersion;
|
||||||
|
|
||||||
/// Efficiently calculates signature hash message for legacy, segwit and taproot inputs.
|
/// Efficiently calculates signature hash message for legacy, segwit and taproot inputs.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SigHashCache<T: Deref<Target = Transaction>> {
|
pub struct SigHashCache<T: Deref<Target = Transaction>> {
|
||||||
|
@ -88,7 +90,6 @@ pub enum Prevouts<'u> {
|
||||||
All(&'u [TxOut]),
|
All(&'u [TxOut]),
|
||||||
}
|
}
|
||||||
|
|
||||||
const LEAF_VERSION_TAPSCRIPT: u8 = 0xc0;
|
|
||||||
const KEY_VERSION_0: u8 = 0u8;
|
const KEY_VERSION_0: u8 = 0u8;
|
||||||
|
|
||||||
/// Information related to the script path spending
|
/// Information related to the script path spending
|
||||||
|
@ -96,7 +97,7 @@ const KEY_VERSION_0: u8 = 0u8;
|
||||||
pub struct ScriptPath<'s> {
|
pub struct ScriptPath<'s> {
|
||||||
script: &'s Script,
|
script: &'s Script,
|
||||||
code_separator_pos: u32,
|
code_separator_pos: u32,
|
||||||
leaf_version: u8,
|
leaf_version: LeafVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hashtype of an input's signature, encoded in the last byte of the signature
|
/// Hashtype of an input's signature, encoded in the last byte of the signature
|
||||||
|
@ -214,7 +215,7 @@ impl<'u> Prevouts<'u> {
|
||||||
|
|
||||||
impl<'s> ScriptPath<'s> {
|
impl<'s> ScriptPath<'s> {
|
||||||
/// Create a new ScriptPath structure
|
/// Create a new ScriptPath structure
|
||||||
pub fn new(script: &'s Script, code_separator_pos: u32, leaf_version: u8) -> Self {
|
pub fn new(script: &'s Script, code_separator_pos: u32, leaf_version: LeafVersion) -> Self {
|
||||||
ScriptPath {
|
ScriptPath {
|
||||||
script,
|
script,
|
||||||
code_separator_pos,
|
code_separator_pos,
|
||||||
|
@ -223,7 +224,7 @@ impl<'s> ScriptPath<'s> {
|
||||||
}
|
}
|
||||||
/// Create a new ScriptPath structure using default values for `code_separator_pos` and `leaf_version`
|
/// Create a new ScriptPath structure using default values for `code_separator_pos` and `leaf_version`
|
||||||
pub fn with_defaults(script: &'s Script) -> Self {
|
pub fn with_defaults(script: &'s Script) -> Self {
|
||||||
Self::new(script, 0xFFFFFFFFu32, LEAF_VERSION_TAPSCRIPT)
|
Self::new(script, 0xFFFFFFFFu32, LeafVersion::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,7 +400,7 @@ impl<R: Deref<Target = Transaction>> SigHashCache<R> {
|
||||||
}) = script_path
|
}) = script_path
|
||||||
{
|
{
|
||||||
let mut enc = TapLeafHash::engine();
|
let mut enc = TapLeafHash::engine();
|
||||||
leaf_version.consensus_encode(&mut enc)?;
|
leaf_version.as_u8().consensus_encode(&mut enc)?;
|
||||||
script.consensus_encode(&mut enc)?;
|
script.consensus_encode(&mut enc)?;
|
||||||
let hash = TapLeafHash::from_engine(enc);
|
let hash = TapLeafHash::from_engine(enc);
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,19 @@
|
||||||
|
|
||||||
//! Taproot
|
//! Taproot
|
||||||
//!
|
//!
|
||||||
|
use prelude::*;
|
||||||
|
use io;
|
||||||
|
use secp256k1::{self, Secp256k1};
|
||||||
|
|
||||||
use hashes::{sha256, sha256t, Hash};
|
use core::fmt;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use std::error;
|
||||||
|
|
||||||
|
use hashes::{sha256, sha256t, Hash, HashEngine};
|
||||||
|
use schnorr;
|
||||||
|
use Script;
|
||||||
|
|
||||||
|
use consensus::Encodable;
|
||||||
|
|
||||||
/// The SHA-256 midstate value for the TapLeaf hash.
|
/// The SHA-256 midstate value for the TapLeaf hash.
|
||||||
const MIDSTATE_TAPLEAF: [u8; 32] = [
|
const MIDSTATE_TAPLEAF: [u8; 32] = [
|
||||||
|
@ -82,12 +93,812 @@ sha256t_hash_newtype!(TapSighashHash, TapSighashTag, MIDSTATE_TAPSIGHASH, 64,
|
||||||
doc="Taproot-tagged hash for the taproot signature hash", true
|
doc="Taproot-tagged hash for the taproot signature hash", true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
impl TapTweakHash {
|
||||||
|
|
||||||
|
/// Create a new BIP341 [`TapTweakHash`] from key and tweak
|
||||||
|
/// Produces H_taptweak(P||R) where P is internal key and R is the merkle root
|
||||||
|
pub fn from_key_and_tweak(
|
||||||
|
internal_key: schnorr::PublicKey,
|
||||||
|
merkle_root: Option<TapBranchHash>,
|
||||||
|
) -> TapTweakHash {
|
||||||
|
let mut eng = TapTweakHash::engine();
|
||||||
|
// always hash the key
|
||||||
|
eng.input(&internal_key.serialize());
|
||||||
|
if let Some(h) = merkle_root {
|
||||||
|
eng.input(&h);
|
||||||
|
} else {
|
||||||
|
// nothing to hash
|
||||||
|
}
|
||||||
|
TapTweakHash::from_engine(eng)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TapLeafHash {
|
||||||
|
/// function to compute leaf hash from components
|
||||||
|
pub fn from_script(script: &Script, ver: LeafVersion) -> TapLeafHash {
|
||||||
|
let mut eng = TapLeafHash::engine();
|
||||||
|
ver.as_u8()
|
||||||
|
.consensus_encode(&mut eng)
|
||||||
|
.expect("engines don't err");
|
||||||
|
script
|
||||||
|
.consensus_encode(&mut eng)
|
||||||
|
.expect("engines don't err");
|
||||||
|
TapLeafHash::from_engine(eng)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maximum depth of a Taproot Tree Script spend path
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/e826b22da252e0599c61d21c98ff89f366b3120f/src/script/interpreter.h#L229
|
||||||
|
pub const TAPROOT_CONTROL_MAX_NODE_COUNT: usize = 128;
|
||||||
|
/// Size of a taproot control node
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/e826b22da252e0599c61d21c98ff89f366b3120f/src/script/interpreter.h#L228
|
||||||
|
pub const TAPROOT_CONTROL_NODE_SIZE: usize = 32;
|
||||||
|
/// Tapleaf mask for getting the leaf version from first byte of control block
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/e826b22da252e0599c61d21c98ff89f366b3120f/src/script/interpreter.h#L225
|
||||||
|
pub const TAPROOT_LEAF_MASK: u8 = 0xfe;
|
||||||
|
/// Tapscript leaf version
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/e826b22da252e0599c61d21c98ff89f366b3120f/src/script/interpreter.h#L226
|
||||||
|
pub const TAPROOT_LEAF_TAPSCRIPT: u8 = 0xc0;
|
||||||
|
/// Tapscript control base size
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/e826b22da252e0599c61d21c98ff89f366b3120f/src/script/interpreter.h#L227
|
||||||
|
pub const TAPROOT_CONTROL_BASE_SIZE: usize = 33;
|
||||||
|
/// Tapscript control max size
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/e826b22da252e0599c61d21c98ff89f366b3120f/src/script/interpreter.h#L230
|
||||||
|
pub const TAPROOT_CONTROL_MAX_SIZE: usize =
|
||||||
|
TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT;
|
||||||
|
|
||||||
|
// type alias for versioned tap script corresponding merkle proof
|
||||||
|
type ScriptMerkleProofMap = BTreeMap<(Script, LeafVersion), BTreeSet<TaprootMerkleBranch>>;
|
||||||
|
/// Data structure for representing Taproot spending information.
|
||||||
|
/// Taproot output corresponds to a combination of a
|
||||||
|
/// single public key condition (known the internal key), and zero or more
|
||||||
|
/// general conditions encoded in scripts organized in the form of a binary tree.
|
||||||
|
///
|
||||||
|
/// Taproot can be spent be either:
|
||||||
|
/// - Spending using the key path i.e., with secret key corresponding to the output_key
|
||||||
|
/// - By satisfying any of the scripts in the script spent path. Each script can be satisfied by providing
|
||||||
|
/// a witness stack consisting of the script's inputs, plus the script itself and the control block.
|
||||||
|
///
|
||||||
|
/// If one or more of the spending conditions consist of just a single key (after aggregation),
|
||||||
|
/// the most likely one should be made the internal key.
|
||||||
|
/// See [BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki) for more details
|
||||||
|
/// on choosing internal keys for a taproot application
|
||||||
|
///
|
||||||
|
/// Note: This library currently does not support [annex](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-5)
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct TaprootSpendInfo {
|
||||||
|
/// The BIP341 internal key.
|
||||||
|
internal_key: schnorr::PublicKey,
|
||||||
|
/// The Merkle root of the script tree (None if there are no scripts)
|
||||||
|
merkle_root: Option<TapBranchHash>,
|
||||||
|
/// The sign final output pubkey as per BIP 341
|
||||||
|
output_key_parity: bool,
|
||||||
|
/// The tweaked output key
|
||||||
|
output_key: schnorr::PublicKey,
|
||||||
|
/// Map from (script, leaf_version) to (sets of) [`TaprootMerkleBranch`].
|
||||||
|
/// More than one control block for a given script is only possible if it
|
||||||
|
/// appears in multiple branches of the tree. In all cases, keeping one should
|
||||||
|
/// be enough for spending funds, but we keep all of the paths so that
|
||||||
|
/// a full tree can be constructed again from spending data if required.
|
||||||
|
script_map: ScriptMerkleProofMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TaprootSpendInfo {
|
||||||
|
/// Create a new [`TaprootSpendInfo`] from a list of script(with default script version) and
|
||||||
|
/// weights of satisfaction for that script. The weights represent the probability of
|
||||||
|
/// each branch being taken. If probabilities/weights for each condition are known,
|
||||||
|
/// constructing the tree as a Huffman tree is the optimal way to minimize average
|
||||||
|
/// case satisfaction cost. This function takes input an iterator of tuple(u64, &Script)
|
||||||
|
/// where usize represents the satisfaction weights of the branch.
|
||||||
|
/// For example, [(3, S1), (2, S2), (5, S3)] would construct a TapTree that has optimal
|
||||||
|
/// satisfaction weight when probability for S1 is 30%, S2 is 20% and S3 is 50%.
|
||||||
|
///
|
||||||
|
/// # Errors:
|
||||||
|
///
|
||||||
|
/// - When the optimal huffman tree has a depth more than 128
|
||||||
|
/// - If the provided list of script weights is empty
|
||||||
|
/// - If the script weight calculations overflow. This should not happen unless you are
|
||||||
|
/// dealing with numbers close to 2^64.
|
||||||
|
pub fn with_huffman_tree<C, I>(
|
||||||
|
secp: &Secp256k1<C>,
|
||||||
|
internal_key: schnorr::PublicKey,
|
||||||
|
script_weights: I,
|
||||||
|
) -> Result<Self, TaprootBuilderError>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = (u64, Script)>,
|
||||||
|
C: secp256k1::Verification,
|
||||||
|
{
|
||||||
|
let mut node_weights = BinaryHeap::<(u64, NodeInfo)>::new();
|
||||||
|
for (p, leaf) in script_weights {
|
||||||
|
node_weights.push((p, NodeInfo::new_leaf_with_ver(leaf, LeafVersion::default())));
|
||||||
|
}
|
||||||
|
if node_weights.is_empty() {
|
||||||
|
return Err(TaprootBuilderError::IncompleteTree);
|
||||||
|
}
|
||||||
|
while node_weights.len() > 1 {
|
||||||
|
// Combine the last two elements and insert a new node
|
||||||
|
let (p1, s1) = node_weights.pop().expect("len must be at least two");
|
||||||
|
let (p2, s2) = node_weights.pop().expect("len must be at least two");
|
||||||
|
// Insert the sum of first two in the tree as a new node
|
||||||
|
let p = p1.checked_add(p2).ok_or(TaprootBuilderError::ScriptWeightOverflow)?;
|
||||||
|
node_weights.push((p, NodeInfo::combine(s1, s2)?));
|
||||||
|
}
|
||||||
|
// Every iteration of the loop reduces the node_weights.len() by exactly 1
|
||||||
|
// Therefore, the loop will eventually terminate with exactly 1 element
|
||||||
|
debug_assert!(node_weights.len() == 1);
|
||||||
|
let node = node_weights.pop().expect("huffman tree algorithm is broken").1;
|
||||||
|
return Ok(Self::from_node_info(secp, internal_key, node));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new key spend with internal key and proided merkle root.
|
||||||
|
/// Provide [`None`] for merkle_root if there is no script path.
|
||||||
|
///
|
||||||
|
/// *Note*: As per BIP341
|
||||||
|
///
|
||||||
|
/// When the merkle root is [`None`], the output key commits to an unspendable
|
||||||
|
/// script path instead of having no script path. This is achieved by computing
|
||||||
|
/// the output key point as Q = P + int(hashTapTweak(bytes(P)))G.
|
||||||
|
/// See also [`TaprootSpendInfo::tap_tweak`].
|
||||||
|
/// Refer to BIP 341 footnote (Why should the output key always have
|
||||||
|
/// a taproot commitment, even if there is no script path?) for more details
|
||||||
|
///
|
||||||
|
pub fn new_key_spend<C: secp256k1::Verification>(
|
||||||
|
secp: &Secp256k1<C>,
|
||||||
|
internal_key: schnorr::PublicKey,
|
||||||
|
merkle_root: Option<TapBranchHash>,
|
||||||
|
) -> Self {
|
||||||
|
let tweak = TapTweakHash::from_key_and_tweak(internal_key, merkle_root);
|
||||||
|
let mut output_key = internal_key;
|
||||||
|
// # Panics:
|
||||||
|
//
|
||||||
|
// This would return Err if the merkle root hash is the negation of the secret
|
||||||
|
// key corresponding to the internal key.
|
||||||
|
// Because the tweak is derived as specified in BIP341 (hash output of a function),
|
||||||
|
// this is unlikely to occur (1/2^128) in real life usage, it is safe to unwrap this
|
||||||
|
let parity = output_key
|
||||||
|
.tweak_add_assign(&secp, &tweak)
|
||||||
|
.expect("TapTweakHash::from_key_and_tweak is broken");
|
||||||
|
Self {
|
||||||
|
internal_key: internal_key,
|
||||||
|
merkle_root: merkle_root,
|
||||||
|
output_key_parity: parity,
|
||||||
|
output_key: output_key,
|
||||||
|
script_map: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain the tweak and parity used to compute the output_key
|
||||||
|
pub fn tap_tweak(&self) -> TapTweakHash {
|
||||||
|
TapTweakHash::from_key_and_tweak(self.internal_key, self.merkle_root)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain the internal key
|
||||||
|
pub fn internal_key(&self) -> schnorr::PublicKey {
|
||||||
|
self.internal_key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain the merkle root
|
||||||
|
pub fn merkle_root(&self) -> Option<TapBranchHash> {
|
||||||
|
self.merkle_root
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output key(the key used in script pubkey) from Spend data. See also
|
||||||
|
/// [`TaprootSpendInfo::output_key_parity`]
|
||||||
|
pub fn output_key(&self) -> schnorr::PublicKey {
|
||||||
|
self.output_key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parity of the output key. See also [`TaprootSpendInfo::output_key`]
|
||||||
|
pub fn output_key_parity(&self) -> bool {
|
||||||
|
self.output_key_parity
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal function to compute [`TaprootSpendInfo`] from NodeInfo
|
||||||
|
fn from_node_info<C: secp256k1::Verification>(
|
||||||
|
secp: &Secp256k1<C>,
|
||||||
|
internal_key: schnorr::PublicKey,
|
||||||
|
node: NodeInfo,
|
||||||
|
) -> TaprootSpendInfo {
|
||||||
|
// Create as if it is a key spend path with the given merkle root
|
||||||
|
let root_hash = Some(TapBranchHash::from_inner(node.hash.into_inner()));
|
||||||
|
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;
|
||||||
|
match info.script_map.get_mut(&key) {
|
||||||
|
Some(set) => {
|
||||||
|
set.insert(value);
|
||||||
|
continue; // NLL fix
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
let mut set = BTreeSet::new();
|
||||||
|
set.insert(value);
|
||||||
|
info.script_map.insert(key, set);
|
||||||
|
}
|
||||||
|
info
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access the internal script map
|
||||||
|
pub fn as_script_map(&self) -> &ScriptMerkleProofMap {
|
||||||
|
&self.script_map
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain a [`ControlBlock`] for particular script with the given version.
|
||||||
|
/// Returns [`None`] if the script is not contained in the [`TaprootSpendInfo`]
|
||||||
|
/// If there are multiple ControlBlocks possible, this returns the shortest one.
|
||||||
|
pub fn control_block(&self, script_ver: &(Script, LeafVersion)) -> Option<ControlBlock> {
|
||||||
|
let merkle_branch_set = self.script_map.get(script_ver)?;
|
||||||
|
// Choose the smallest one amongst the multiple script maps
|
||||||
|
let smallest = merkle_branch_set
|
||||||
|
.iter()
|
||||||
|
.min_by(|x, y| x.0.len().cmp(&y.0.len()))
|
||||||
|
.expect("Invariant: Script map key must contain non-empty set value");
|
||||||
|
Some(ControlBlock {
|
||||||
|
internal_key: self.internal_key,
|
||||||
|
output_key_parity: self.output_key_parity,
|
||||||
|
leaf_version: LeafVersion::default(),
|
||||||
|
merkle_branch: smallest.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builder for building taproot iteratively. Users can specify tap leaf or omitted/hidden
|
||||||
|
/// branches in a DFS(Depth first search) walk to construct this tree.
|
||||||
|
// Similar to Taproot Builder in bitcoin core
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct TaprootBuilder {
|
||||||
|
// The following doc-comment is from bitcoin core, but modified for rust
|
||||||
|
// The comment below describes the current state of the builder for a given tree.
|
||||||
|
//
|
||||||
|
// For each level in the tree, one NodeInfo object may be present. branch at index 0
|
||||||
|
// is information about the root; further values are for deeper subtrees being
|
||||||
|
// explored.
|
||||||
|
//
|
||||||
|
// During the construction of Taptree, for every right branch taken to
|
||||||
|
// reach the position we're currently working in, there will be a (Some(_))
|
||||||
|
// entry in branch corresponding to the left branch at that level.
|
||||||
|
//
|
||||||
|
// For example, imagine this tree: - N0 -
|
||||||
|
// / \
|
||||||
|
// N1 N2
|
||||||
|
// / \ / \
|
||||||
|
// A B C N3
|
||||||
|
// / \
|
||||||
|
// D E
|
||||||
|
//
|
||||||
|
// Initially, branch is empty. After processing leaf A, it would become
|
||||||
|
// {None, None, A}. When processing leaf B, an entry at level 2 already
|
||||||
|
// exists, and it would thus be combined with it to produce a level 1 one,
|
||||||
|
// resulting in {None, N1}. Adding C and D takes us to {None, N1, C}
|
||||||
|
// and {None, N1, C, D} respectively. When E is processed, it is combined
|
||||||
|
// with D, and then C, and then N1, to produce the root, resulting in {N0}.
|
||||||
|
//
|
||||||
|
// This structure allows processing with just O(log n) overhead if the leaves
|
||||||
|
// are computed on the fly.
|
||||||
|
//
|
||||||
|
// As an invariant, there can never be None entries at the end. There can
|
||||||
|
// also not be more than 128 entries (as that would mean more than 128 levels
|
||||||
|
// in the tree). The depth of newly added entries will always be at least
|
||||||
|
// equal to the current size of branch (otherwise it does not correspond
|
||||||
|
// to a depth-first traversal of a tree). branch is only empty if no entries
|
||||||
|
// have ever be processed. branch having length 1 corresponds to being done.
|
||||||
|
//
|
||||||
|
branch: Vec<Option<NodeInfo>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TaprootBuilder {
|
||||||
|
/// Create a new instance of [`TaprootBuilder`]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
TaprootBuilder { branch: vec![] }
|
||||||
|
}
|
||||||
|
/// Just like [`TaprootBuilder::add_leaf`] but allows to specify script version
|
||||||
|
pub fn add_leaf_with_ver(
|
||||||
|
self,
|
||||||
|
depth: usize,
|
||||||
|
script: Script,
|
||||||
|
ver: LeafVersion,
|
||||||
|
) -> Result<Self, TaprootBuilderError> {
|
||||||
|
let leaf = NodeInfo::new_leaf_with_ver(script, ver);
|
||||||
|
self.insert(leaf, depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a leaf script at a depth `depth` to the builder with default script version.
|
||||||
|
/// This will error if the leave are not provided in a DFS walk order. The depth of the
|
||||||
|
/// root node is 0 and it's immediate child would be at depth 1.
|
||||||
|
/// See [`TaprootBuilder::add_leaf_with_ver`] for adding a leaf with specific version
|
||||||
|
/// See [Wikipedia](https://en.wikipedia.org/wiki/Depth-first_search) for more details
|
||||||
|
pub fn add_leaf(self, depth: usize, script: Script) -> Result<Self, TaprootBuilderError> {
|
||||||
|
self.add_leaf_with_ver(depth, script, LeafVersion::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a hidden/omitted node at a depth `depth` to the builder.
|
||||||
|
/// This will error if the node are not provided in a DFS walk order. The depth of the
|
||||||
|
/// root node is 0 and it's immediate child would be at depth 1.
|
||||||
|
pub fn add_hidden(self, depth: usize, hash: sha256::Hash) -> Result<Self, TaprootBuilderError> {
|
||||||
|
let node = NodeInfo::new_hidden(hash);
|
||||||
|
self.insert(node, depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create [`TaprootSpendInfo`] with the given internal key
|
||||||
|
pub fn finalize<C: secp256k1::Verification>(
|
||||||
|
mut self,
|
||||||
|
secp: &Secp256k1<C>,
|
||||||
|
internal_key: schnorr::PublicKey,
|
||||||
|
) -> Result<TaprootSpendInfo, TaprootBuilderError> {
|
||||||
|
if self.branch.len() > 1 {
|
||||||
|
return Err(TaprootBuilderError::IncompleteTree);
|
||||||
|
}
|
||||||
|
let node = self
|
||||||
|
.branch
|
||||||
|
.pop()
|
||||||
|
.ok_or(TaprootBuilderError::EmptyTree)?
|
||||||
|
.expect("Builder invariant: last element of the branch must be some");
|
||||||
|
Ok(TaprootSpendInfo::from_node_info(secp, internal_key, node))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to insert a leaf at a depth
|
||||||
|
fn insert(mut self, mut node: NodeInfo, mut depth: usize) -> Result<Self, TaprootBuilderError> {
|
||||||
|
// early error on invalid depth. Though this will be checked later
|
||||||
|
// while constructing TaprootMerkelBranch
|
||||||
|
if depth > TAPROOT_CONTROL_MAX_NODE_COUNT {
|
||||||
|
return Err(TaprootBuilderError::InvalidMerkleTreeDepth(depth));
|
||||||
|
}
|
||||||
|
// We cannot insert a leaf at a lower depth while a deeper branch is unfinished. Doing
|
||||||
|
// so would mean the add_leaf/add_hidden invocations do not correspond to a DFS traversal of a
|
||||||
|
// binary tree.
|
||||||
|
if depth + 1 < self.branch.len() {
|
||||||
|
return Err(TaprootBuilderError::NodeNotInDfsOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
while self.branch.len() == depth + 1 {
|
||||||
|
let child = match self.branch.pop() {
|
||||||
|
None => unreachable!("Len of branch checked to be >= 1"),
|
||||||
|
Some(Some(child)) => child,
|
||||||
|
// Needs an explicit push to add the None that we just popped.
|
||||||
|
// Cannot use .last() because of borrow checker issues.
|
||||||
|
Some(None) => {
|
||||||
|
self.branch.push(None);
|
||||||
|
break;
|
||||||
|
} // Cannot combine further
|
||||||
|
};
|
||||||
|
if depth == 0 {
|
||||||
|
// We are trying to combine two nodes at root level.
|
||||||
|
// Can't propagate further up than the root
|
||||||
|
return Err(TaprootBuilderError::OverCompleteTree);
|
||||||
|
}
|
||||||
|
node = NodeInfo::combine(node, child)?;
|
||||||
|
// Propagate to combine nodes at a lower depth
|
||||||
|
depth -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.branch.len() < depth + 1 {
|
||||||
|
// add enough nodes so that we can insert node at depth `depth`
|
||||||
|
let num_extra_nodes = depth + 1 - self.branch.len();
|
||||||
|
self.branch
|
||||||
|
.extend((0..num_extra_nodes).into_iter().map(|_| None));
|
||||||
|
}
|
||||||
|
// Push the last node to the branch
|
||||||
|
self.branch[depth] = Some(node);
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internally used structure to represent the node information in taproot tree
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
struct NodeInfo {
|
||||||
|
/// Merkle Hash for this node
|
||||||
|
hash: sha256::Hash,
|
||||||
|
/// information about leaves inside this node
|
||||||
|
leaves: Vec<LeafInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodeInfo {
|
||||||
|
// Create a new NodeInfo with omitted/hidden info
|
||||||
|
fn new_hidden(hash: sha256::Hash) -> Self {
|
||||||
|
Self {
|
||||||
|
hash: hash,
|
||||||
|
leaves: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new leaf with NodeInfo
|
||||||
|
fn new_leaf_with_ver(script: Script, ver: LeafVersion) -> Self {
|
||||||
|
let leaf = LeafInfo::new(script, ver);
|
||||||
|
Self {
|
||||||
|
hash: leaf.hash(),
|
||||||
|
leaves: vec![leaf],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine two NodeInfo's to create a new parent
|
||||||
|
fn combine(a: Self, b: Self) -> Result<Self, TaprootBuilderError> {
|
||||||
|
let mut all_leaves = Vec::with_capacity(a.leaves.len() + b.leaves.len());
|
||||||
|
for mut a_leaf in a.leaves {
|
||||||
|
a_leaf.merkle_branch.push(b.hash)?; // add hashing partner
|
||||||
|
all_leaves.push(a_leaf);
|
||||||
|
}
|
||||||
|
for mut b_leaf in b.leaves {
|
||||||
|
b_leaf.merkle_branch.push(a.hash)?; // add hashing partner
|
||||||
|
all_leaves.push(b_leaf);
|
||||||
|
}
|
||||||
|
let mut eng = TapBranchHash::engine();
|
||||||
|
if a.hash < b.hash {
|
||||||
|
eng.input(&a.hash);
|
||||||
|
eng.input(&b.hash);
|
||||||
|
} else {
|
||||||
|
eng.input(&b.hash);
|
||||||
|
eng.input(&a.hash);
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
hash: sha256::Hash::from_engine(eng),
|
||||||
|
leaves: all_leaves,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internally used structure to store information about taproot leaf node
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
struct LeafInfo {
|
||||||
|
// The underlying script
|
||||||
|
script: Script,
|
||||||
|
// The leaf version
|
||||||
|
ver: LeafVersion,
|
||||||
|
// The merkle proof(hashing partners) to get this node
|
||||||
|
merkle_branch: TaprootMerkleBranch,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LeafInfo {
|
||||||
|
// Create an instance of Self from Script with default version and no merkle branch
|
||||||
|
fn new(script: Script, ver: LeafVersion) -> Self {
|
||||||
|
Self {
|
||||||
|
script: script,
|
||||||
|
ver: ver,
|
||||||
|
merkle_branch: TaprootMerkleBranch(vec![]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute a leaf hash for the given leaf
|
||||||
|
fn hash(&self) -> sha256::Hash {
|
||||||
|
let leaf_hash = TapLeafHash::from_script(&self.script, self.ver);
|
||||||
|
sha256::Hash::from_inner(leaf_hash.into_inner())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Merkle proof for inclusion of a tree in a taptree hash
|
||||||
|
// The type of hash is sha256::Hash because the vector might contain
|
||||||
|
// both TapBranchHash and TapLeafHash
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
|
pub struct TaprootMerkleBranch(Vec<sha256::Hash>);
|
||||||
|
|
||||||
|
impl TaprootMerkleBranch {
|
||||||
|
/// Obtain a reference to inner
|
||||||
|
pub fn as_inner(&self) -> &[sha256::Hash] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a merkle proof from slice
|
||||||
|
pub fn from_slice(sl: &[u8]) -> Result<Self, TaprootError> {
|
||||||
|
if sl.len() % TAPROOT_CONTROL_NODE_SIZE != 0 {
|
||||||
|
Err(TaprootError::InvalidMerkleBranchSize(sl.len()))
|
||||||
|
} else if sl.len() > TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT {
|
||||||
|
Err(TaprootError::InvalidMerkleTreeDepth(
|
||||||
|
sl.len() / TAPROOT_CONTROL_NODE_SIZE,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
let inner = sl
|
||||||
|
// TODO: Use chunks_exact after MSRV changes to 1.31
|
||||||
|
.chunks(TAPROOT_CONTROL_NODE_SIZE)
|
||||||
|
.map(|chunk| {
|
||||||
|
sha256::Hash::from_slice(chunk)
|
||||||
|
.expect("chunk exact always returns the correct size")
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(TaprootMerkleBranch(inner))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize to a writer. Returns the number of bytes written
|
||||||
|
pub fn encode<Write: io::Write>(&self, mut writer: Write) -> io::Result<usize> {
|
||||||
|
let mut written = 0;
|
||||||
|
for hash in self.0.iter() {
|
||||||
|
written += writer.write(hash)?;
|
||||||
|
}
|
||||||
|
Ok(written)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize self as bytes
|
||||||
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
|
self.0.iter().map(|e| e.as_inner()).flatten().map(|x| *x).collect::<Vec<u8>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal function to append elements to proof
|
||||||
|
fn push(&mut self, h: sha256::Hash) -> Result<(), TaprootBuilderError> {
|
||||||
|
if self.0.len() >= TAPROOT_CONTROL_MAX_NODE_COUNT {
|
||||||
|
Err(TaprootBuilderError::InvalidMerkleTreeDepth(self.0.len()))
|
||||||
|
} else {
|
||||||
|
self.0.push(h);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a MerkleProof from Vec<[`sha256::Hash`]>. Returns an error when
|
||||||
|
/// inner proof len is more than TAPROOT_CONTROL_MAX_NODE_COUNT (128)
|
||||||
|
pub fn from_inner(inner: Vec<sha256::Hash>) -> Result<Self, TaprootError> {
|
||||||
|
if inner.len() > TAPROOT_CONTROL_MAX_NODE_COUNT {
|
||||||
|
Err(TaprootError::InvalidMerkleTreeDepth(inner.len()))
|
||||||
|
} else {
|
||||||
|
Ok(TaprootMerkleBranch(inner))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume Self to get Vec<[`sha256::Hash`]>
|
||||||
|
pub fn into_inner(self) -> Vec<sha256::Hash> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Control Block data structure used in Tapscript satisfaction
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct ControlBlock {
|
||||||
|
/// The tapleaf version,
|
||||||
|
pub leaf_version: LeafVersion,
|
||||||
|
/// The parity of the output key (NOT THE INTERNAL KEY WHICH IS ALWAYS XONLY)
|
||||||
|
pub output_key_parity: bool,
|
||||||
|
/// The internal key
|
||||||
|
pub internal_key: schnorr::PublicKey,
|
||||||
|
/// The merkle proof of a script associated with this leaf
|
||||||
|
pub merkle_branch: TaprootMerkleBranch,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ControlBlock {
|
||||||
|
/// Obtain a ControlBlock from slice. This is an extra witness element
|
||||||
|
/// that provides the proof that taproot script pubkey is correctly computed
|
||||||
|
/// with some specified leaf hash. This is the last element in
|
||||||
|
/// taproot witness when spending a output via script path.
|
||||||
|
///
|
||||||
|
/// # Errors:
|
||||||
|
/// - If the control block size is not of the form 33 + 32m where
|
||||||
|
/// 0 <= m <= 128, InvalidControlBlock is returned
|
||||||
|
pub fn from_slice(sl: &[u8]) -> Result<ControlBlock, TaprootError> {
|
||||||
|
if sl.len() < TAPROOT_CONTROL_BASE_SIZE
|
||||||
|
|| (sl.len() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE != 0
|
||||||
|
{
|
||||||
|
return Err(TaprootError::InvalidControlBlockSize(sl.len()));
|
||||||
|
}
|
||||||
|
let output_key_parity = (sl[0] & 1) == 1;
|
||||||
|
let leaf_version = LeafVersion::from_u8(sl[0] & TAPROOT_LEAF_MASK)?;
|
||||||
|
let internal_key = schnorr::PublicKey::from_slice(&sl[1..TAPROOT_CONTROL_BASE_SIZE])
|
||||||
|
.map_err(TaprootError::InvalidInternalKey)?;
|
||||||
|
let merkle_branch = TaprootMerkleBranch::from_slice(&sl[TAPROOT_CONTROL_BASE_SIZE..])?;
|
||||||
|
Ok(ControlBlock {
|
||||||
|
leaf_version,
|
||||||
|
output_key_parity,
|
||||||
|
internal_key,
|
||||||
|
merkle_branch,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain the size of control block. Faster and more efficient than calling
|
||||||
|
/// serialize() followed by len(). Can be handy for fee estimation
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * self.merkle_branch.as_inner().len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize to a writer. Returns the number of bytes written
|
||||||
|
pub fn encode<Write: io::Write>(&self, mut writer: Write) -> io::Result<usize> {
|
||||||
|
let first_byte: u8 =
|
||||||
|
(if self.output_key_parity { 1 } else { 0 }) | self.leaf_version.as_u8();
|
||||||
|
let mut bytes_written = 0;
|
||||||
|
bytes_written += writer.write(&[first_byte])?;
|
||||||
|
bytes_written += writer.write(&self.internal_key.serialize())?;
|
||||||
|
bytes_written += self.merkle_branch.encode(&mut writer)?;
|
||||||
|
Ok(bytes_written)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize the control block. This would be required when
|
||||||
|
/// using ControlBlock as a witness element while spending an output via
|
||||||
|
/// script path. This serialization does not include the VarInt prefix that would be
|
||||||
|
/// applied when encoding this element as a witness.
|
||||||
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
|
let mut buf = Vec::with_capacity(self.size());
|
||||||
|
self.encode(&mut buf)
|
||||||
|
.expect("writers don't error");
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify that a control block is correct proof for a given output key and script
|
||||||
|
/// This only checks that script is contained inside the taptree described by
|
||||||
|
/// output key, full verification must also execute the script with witness data
|
||||||
|
pub fn verify_taproot_commitment<C: secp256k1::Verification>(
|
||||||
|
&self,
|
||||||
|
secp: &Secp256k1<C>,
|
||||||
|
output_key: &schnorr::PublicKey,
|
||||||
|
script: &Script,
|
||||||
|
) -> bool {
|
||||||
|
// compute the script hash
|
||||||
|
// Initially the curr_hash is the leaf hash
|
||||||
|
let leaf_hash = TapLeafHash::from_script(&script, self.leaf_version);
|
||||||
|
let mut curr_hash = TapBranchHash::from_inner(leaf_hash.into_inner());
|
||||||
|
// Verify the proof
|
||||||
|
for elem in self.merkle_branch.as_inner() {
|
||||||
|
let mut eng = TapBranchHash::engine();
|
||||||
|
if curr_hash.as_inner() < elem.as_inner() {
|
||||||
|
eng.input(&curr_hash);
|
||||||
|
eng.input(elem);
|
||||||
|
} else {
|
||||||
|
eng.input(elem);
|
||||||
|
eng.input(&curr_hash);
|
||||||
|
}
|
||||||
|
// Recalculate the curr hash as parent hash
|
||||||
|
curr_hash = TapBranchHash::from_engine(eng);
|
||||||
|
}
|
||||||
|
// compute the taptweak
|
||||||
|
let tweak = TapTweakHash::from_key_and_tweak(self.internal_key, Some(curr_hash));
|
||||||
|
self.internal_key.tweak_add_check(
|
||||||
|
secp,
|
||||||
|
output_key,
|
||||||
|
self.output_key_parity,
|
||||||
|
tweak.into_inner(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The leaf version for tapleafs
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct LeafVersion(u8);
|
||||||
|
|
||||||
|
impl Default for LeafVersion {
|
||||||
|
fn default() -> Self {
|
||||||
|
LeafVersion(TAPROOT_LEAF_TAPSCRIPT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LeafVersion {
|
||||||
|
/// Obtain LeafVersion from u8, will error when last bit of ver is even or
|
||||||
|
/// when ver is 0x50 (ANNEX_TAG)
|
||||||
|
// Text from BIP341:
|
||||||
|
// In order to support some forms of static analysis that rely on
|
||||||
|
// being able to identify script spends without access to the output being
|
||||||
|
// spent, it is recommended to avoid using any leaf versions that would conflict
|
||||||
|
// with a valid first byte of either a valid P2WPKH pubkey or a valid P2WSH script
|
||||||
|
// (that is, both v and v | 1 should be an undefined, invalid or disabled opcode
|
||||||
|
// or an opcode that is not valid as the first opcode).
|
||||||
|
// The values that comply to this rule are the 32 even values between
|
||||||
|
// 0xc0 and 0xfe and also 0x66, 0x7e, 0x80, 0x84, 0x96, 0x98, 0xba, 0xbc, 0xbe
|
||||||
|
pub fn from_u8(ver: u8) -> Result<Self, TaprootError> {
|
||||||
|
if ver & TAPROOT_LEAF_MASK == ver && ver != 0x50 {
|
||||||
|
Ok(LeafVersion(ver))
|
||||||
|
} else {
|
||||||
|
Err(TaprootError::InvalidTaprootLeafVersion(ver))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the inner version from LeafVersion
|
||||||
|
pub fn as_u8(&self) -> u8 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<u8> for LeafVersion {
|
||||||
|
fn into(self) -> u8 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detailed error type for taproot builder
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum TaprootBuilderError {
|
||||||
|
/// Merkle Tree depth must not be more than 128
|
||||||
|
InvalidMerkleTreeDepth(usize),
|
||||||
|
/// Nodes must be added specified in DFS order
|
||||||
|
NodeNotInDfsOrder,
|
||||||
|
/// Two nodes at depth 0 are not allowed
|
||||||
|
OverCompleteTree,
|
||||||
|
/// Invalid taproot internal key
|
||||||
|
InvalidInternalKey(secp256k1::Error),
|
||||||
|
/// Called finalize on an incomplete tree
|
||||||
|
IncompleteTree,
|
||||||
|
/// Called finalize on a empty tree
|
||||||
|
EmptyTree,
|
||||||
|
/// Script weight overflow
|
||||||
|
ScriptWeightOverflow,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TaprootBuilderError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
TaprootBuilderError::NodeNotInDfsOrder => {
|
||||||
|
write!(f, "add_leaf/add_hidden must be called in DFS walk order",)
|
||||||
|
}
|
||||||
|
TaprootBuilderError::OverCompleteTree => write!(
|
||||||
|
f,
|
||||||
|
"Attempted to create a tree with two nodes at depth 0. There must\
|
||||||
|
only be a exactly one node at depth 0",
|
||||||
|
),
|
||||||
|
TaprootBuilderError::InvalidMerkleTreeDepth(d) => write!(
|
||||||
|
f,
|
||||||
|
"Merkle Tree depth({}) must be less than {}",
|
||||||
|
d, TAPROOT_CONTROL_MAX_NODE_COUNT
|
||||||
|
),
|
||||||
|
TaprootBuilderError::InvalidInternalKey(e) => {
|
||||||
|
write!(f, "Invalid Internal XOnly key : {}", e)
|
||||||
|
}
|
||||||
|
TaprootBuilderError::IncompleteTree => {
|
||||||
|
write!(f, "Called finalize on an incomplete tree")
|
||||||
|
}
|
||||||
|
TaprootBuilderError::EmptyTree => {
|
||||||
|
write!(f, "Called finalize on an empty tree")
|
||||||
|
}
|
||||||
|
TaprootBuilderError::ScriptWeightOverflow => {
|
||||||
|
write!(f, "Script weight overflow in Huffman tree construction")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
|
||||||
|
impl error::Error for TaprootBuilderError {}
|
||||||
|
|
||||||
|
/// Detailed error type for taproot utilities
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum TaprootError {
|
||||||
|
/// Proof size must be a multiple of 32
|
||||||
|
InvalidMerkleBranchSize(usize),
|
||||||
|
/// Merkle Tree depth must not be more than 128
|
||||||
|
InvalidMerkleTreeDepth(usize),
|
||||||
|
/// The last bit of tapleaf version must be zero
|
||||||
|
InvalidTaprootLeafVersion(u8),
|
||||||
|
/// Invalid Control Block Size
|
||||||
|
InvalidControlBlockSize(usize),
|
||||||
|
/// Invalid taproot internal key
|
||||||
|
InvalidInternalKey(secp256k1::Error),
|
||||||
|
/// Empty TapTree
|
||||||
|
EmptyTree,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TaprootError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
TaprootError::InvalidMerkleBranchSize(sz) => write!(
|
||||||
|
f,
|
||||||
|
"Merkle branch size({}) must be a multiple of {}",
|
||||||
|
sz, TAPROOT_CONTROL_NODE_SIZE
|
||||||
|
),
|
||||||
|
TaprootError::InvalidMerkleTreeDepth(d) => write!(
|
||||||
|
f,
|
||||||
|
"Merkle Tree depth({}) must be less than {}",
|
||||||
|
d, TAPROOT_CONTROL_MAX_NODE_COUNT
|
||||||
|
),
|
||||||
|
TaprootError::InvalidTaprootLeafVersion(v) => write!(
|
||||||
|
f,
|
||||||
|
"Leaf version({}) must have the least significant bit 0",
|
||||||
|
v
|
||||||
|
),
|
||||||
|
TaprootError::InvalidControlBlockSize(sz) => write!(
|
||||||
|
f,
|
||||||
|
"Control Block size({}) must be of the form 33 + 32*m where 0 <= m <= {} ",
|
||||||
|
sz, TAPROOT_CONTROL_MAX_NODE_COUNT
|
||||||
|
),
|
||||||
|
// TODO: add source when in MSRV
|
||||||
|
TaprootError::InvalidInternalKey(e) => write!(f, "Invalid Internal XOnly key : {}", e),
|
||||||
|
TaprootError::EmptyTree => write!(f, "Taproot Tree must contain at least one script"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
|
||||||
|
impl error::Error for TaprootError {}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use hashes::hex::ToHex;
|
use hashes::hex::{FromHex, ToHex};
|
||||||
use hashes::sha256t::Tag;
|
use hashes::sha256t::Tag;
|
||||||
use hashes::{sha256, Hash, HashEngine};
|
use hashes::{sha256, Hash, HashEngine};
|
||||||
|
use secp256k1::VerifyOnly;
|
||||||
|
use core::str::FromStr;
|
||||||
|
|
||||||
fn tag_engine(tag_name: &str) -> sha256::HashEngine {
|
fn tag_engine(tag_name: &str) -> sha256::HashEngine {
|
||||||
let mut engine = sha256::Hash::engine();
|
let mut engine = sha256::Hash::engine();
|
||||||
|
@ -169,4 +980,105 @@ mod test {
|
||||||
"cd10c023c300fb9a507dff136370fba1d8a0566667cfafc4099a8803e00dfdc2"
|
"cd10c023c300fb9a507dff136370fba1d8a0566667cfafc4099a8803e00dfdc2"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn _verify_tap_commitments(secp: &Secp256k1<VerifyOnly>, out_spk_hex: &str, script_hex : &str, control_block_hex: &str) {
|
||||||
|
let out_pk = schnorr::PublicKey::from_str(&out_spk_hex[4..]).unwrap();
|
||||||
|
let script = Script::from_hex(script_hex).unwrap();
|
||||||
|
let control_block = ControlBlock::from_slice(&Vec::<u8>::from_hex(control_block_hex).unwrap()).unwrap();
|
||||||
|
assert_eq!(control_block_hex, control_block.serialize().to_hex());
|
||||||
|
assert!(control_block.verify_taproot_commitment(secp, &out_pk, &script));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn control_block_verify() {
|
||||||
|
let secp = Secp256k1::verification_only();
|
||||||
|
// test vectors obtained from printing values in feature_taproot.py from bitcoin core
|
||||||
|
_verify_tap_commitments(&secp, "51205dc8e62b15e0ebdf44751676be35ba32eed2e84608b290d4061bbff136cd7ba9", "6a", "c1a9d6f66cd4b25004f526bfa873e56942f98e8e492bd79ed6532b966104817c2bda584e7d32612381cf88edc1c02e28a296e807c16ad22f591ee113946e48a71e0641e660d1e5392fb79d64838c2b84faf04b7f5f283c9d8bf83e39e177b64372a0cd22eeab7e093873e851e247714eff762d8a30be699ba4456cfe6491b282e193a071350ae099005a5950d74f73ba13077a57bc478007fb0e4d1099ce9cf3d4");
|
||||||
|
_verify_tap_commitments(&secp, "5120e208c869c40d8827101c5ad3238018de0f3f5183d77a0c53d18ac28ddcbcd8ad", "f4", "c0a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f40090ab1f4890d51115998242ebce636efb9ede1b516d9eb8952dc1068e0335306199aaf103cceb41d9bc37ec231aca89b984b5fd3c65977ce764d51033ac65adb4da14e029b1e154a85bfd9139e7aa2720b6070a4ceba8264ca61d5d3ac27aceb9ef4b54cd43c2d1fd5e11b5c2e93cf29b91ea3dc5b832201f02f7473a28c63246");
|
||||||
|
_verify_tap_commitments(&secp, "5120567666e7df90e0450bb608e17c01ed3fbcfa5355a5f8273e34e583bfaa70ce09", "203455139bf238a3067bd72ed77e0ab8db590330f55ed58dba7366b53bf4734279ac", "c1a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f400");
|
||||||
|
_verify_tap_commitments(&secp, "5120580a19e47269414a55eb86d5d0c6c9b371455d9fd2154412a57dec840df99fe1", "6a", "bca0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f40042ba1bd1c63c03ccff60d4c4d53a653f87909eb3358e7fa45c9d805231fb08c933e1f4e0f9d17f591df1419df7d5b7eb5f744f404c5ef9ecdb1b89b18cafa3a816d8b5dba3205f9a9c05f866d91f40d2793a7586d502cb42f46c7a11f66ad4aa");
|
||||||
|
_verify_tap_commitments(&secp, "5120228b94a4806254a38d6efa8a134c28ebc89546209559dfe40b2b0493bafacc5b", "6a50", "c0a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f4009c9aed3dfd11ab0e78bf87ef3bf296269dc4b0f7712140386d6980992bab4b45");
|
||||||
|
_verify_tap_commitments(&secp, "5120567666e7df90e0450bb608e17c01ed3fbcfa5355a5f8273e34e583bfaa70ce09", "203455139bf238a3067bd72ed77e0ab8db590330f55ed58dba7366b53bf4734279ac", "c1a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f400");
|
||||||
|
_verify_tap_commitments(&secp, "5120b0a79103c31fe51eea61d2873bad8a25a310da319d7e7a85f825fa7a00ea3f85", "203455139bf238a3067bd72ed77e0ab8db590330f55ed58dba7366b53bf4734279ad51", "c1a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f400");
|
||||||
|
_verify_tap_commitments(&secp, "5120f2f62e854a0012aeba78cd4ba4a0832447a5262d4c6eb4f1c95c7914b536fc6c", "6a86", "c1a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f4009ad3d30479f0689dbdf59a6b840d60ad485b2effbed1825a75ce19a44e460e09056f60ea686d79cfa4fb79f197b2e905ac857a983be4a5a41a4873e865aa950780c0237de279dc063e67deec46ef8e1bc351bf12c4d67a6d568001faf097e797e6ee620f53cfe0f8acaddf2063c39c3577853bb46d61ffcba5a024c3e1216837");
|
||||||
|
_verify_tap_commitments(&secp, "51202a4772070b49bae68b44315032cdbf9c40c7c2f896781b32b931b73dbfb26d7e", "6af8", "c0a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f4006f183944a14618fc7fe9ceade0f58e43a19d3c3b179ea6c43c29616413b6971c99aaf103cceb41d9bc37ec231aca89b984b5fd3c65977ce764d51033ac65adb4c3462adec78cd04f3cc156bdadec50def99feae0dc6a23664e8a2b0d42d6ca9eb968dfdf46c23af642b2688351904e0a0630e71ffac5bcaba33b9b2c8a7495ec");
|
||||||
|
_verify_tap_commitments(&secp, "5120a32b0b8cfafe0f0f8d5870030ba4d19a8725ad345cb3c8420f86ac4e0dff6207", "4c", "e8a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f400615da7ac8d078e5fc7f4690fc2127ba40f0f97cc070ade5b3a7919783d91ef3f13734aab908ae998e57848a01268fe8217d70bc3ee8ea8ceae158ae964a4b5f3af20b50d7019bf47fde210eee5c52f1cfe71cfca78f2d3e7c1fd828c80351525");
|
||||||
|
_verify_tap_commitments(&secp, "5120b0a79103c31fe51eea61d2873bad8a25a310da319d7e7a85f825fa7a00ea3f85", "203455139bf238a3067bd72ed77e0ab8db590330f55ed58dba7366b53bf4734279ad51", "c1a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f400");
|
||||||
|
_verify_tap_commitments(&secp, "51208678459f1fa0f80e9b89b8ffdcaf46a022bdf60aa45f1fed9a96145edf4ec400", "6a50", "c0a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f4001eff29e1a89e650076b8d3c56302881d09c9df215774ed99993aaed14acd6615");
|
||||||
|
_verify_tap_commitments(&secp, "5120017316303aed02bcdec424c851c9eacbe192b013139bd9634c4e19b3475b06e1", "61", "02a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f40050462265ca552b23cbb4fe021b474313c8cb87d4a18b3f7bdbeb2b418279ba31fc6509d829cd42336f563363cb3538d78758e0876c71e13012eb2b656eb0edb051a2420a840d5c8c6c762abc7410af2c311f606b20ca2ace56a8139f84b1379a");
|
||||||
|
_verify_tap_commitments(&secp, "5120896d4d5d2236e86c6e9320e86d1a7822e652907cbd508360e8c71aefc127c77d", "61", "14a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f4001ab0e9d9a4858a0e69605fe9c5a42d739fbe26fa79650e7074f462b02645f7ea1c91802b298cd91e6b5af57c6a013d93397cd2ecbd5569382cc27becf44ff4fff8960b20f846160c159c58350f6b6072cf1b3daa5185b7a42524fb72cbc252576ae46732b8e31ac24bfa7d72f4c3713e8696f99d8ac6c07e4c820a03f249f144");
|
||||||
|
_verify_tap_commitments(&secp, "512093c7378d96518a75448821c4f7c8f4bae7ce60f804d03d1f0628dd5dd0f5de51", "04ffffffff203455139bf238a3067bd72ed77e0ab8db590330f55ed58dba7366b53bf4734279ba04feffffff87ab", "c1a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f400c9a5cd1f6c8a81f5648e39f9810591df1c9a8f1fe97c92e03ecd7c0c016c951983e05473c6e8238cb4c780ea2ce62552b2a3eee068ceffc00517cd7b97e10dad");
|
||||||
|
_verify_tap_commitments(&secp, "5120b28d75a7179de6feb66b8bb0bfa2b2c739d1a41cf7366a1b393804a844db8a28", "61", "c4a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f400eebc95ded88fb8050094e8dfa958c3be0894eaff0fafae678206b26918d8d7ac47039d40fe34d04b4155df7f1be7f2a49253c7e87812ea9e569e683ac27459e652d6503aa32d64734d00adfee8798b2eed28858abf3bd038e8fa58eb7df4a2d9");
|
||||||
|
_verify_tap_commitments(&secp, "512043e4aa733fc6f43c78a31c2b3c192623acf5cc8c01199ebcc4de88067baca83e", "bd4c", "c1a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f4003f7be6f8848b5bddf332c4d7bd83077f73701e2479f70e02b5730e841234d082b8b41ebea96ffd937715d9faeaa6895e6ef3b22919c554b75df12b3371d328023e443d1df50634ecc1cd169803a1e546f0d44304d8fc5056c408e597fed469b8437d6660eaad3cf72e35ba6e5ff7ddd5e293c1e7e813c871df4f46508e9946ec");
|
||||||
|
_verify_tap_commitments(&secp, "5120ee9aecb28f5f35ce1f8b5ec80275ac0f81bca4a21b29b4632fb4bcbef8823e6a", "2021a5981b13be29c9d4ea179ea44a8b773ea8c02d68f6f6eefd98de20d4bd055fac", "c13359c284c196b6e80f0cf1d93b6a397cf7ee722f0427b705bd954b88ada8838bd2622fd0e104fc50aa763b43c6a792d7d117029983abd687223b4344a9402c618bba7f5fc3fa8a57491f6842acde88c1e675ca35caea3b1a69ee2c2d9b10f615");
|
||||||
|
_verify_tap_commitments(&secp, "5120885274df2252b44764dcef53c21f21154e8488b7e79fafbc96b9ebb22ad0200d", "6a50", "c1a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f4000793597254158918e3369507f2d6fdbef17d18b1028bbb0719450ded0f42c58f");
|
||||||
|
_verify_tap_commitments(&secp, "512066f6f6f91d47674d198a28388e1eb05ec24e6ddbba10f16396b1a80c08675121", "6a50", "c1a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f400fe92aff70a2e8e2a4f34a913b99612468a41e0f8ecaff9a729a173d11013c27e");
|
||||||
|
_verify_tap_commitments(&secp, "5120868ed9307bd4637491ff03e3aa2c216a08fe213cac8b6cedbb9ab31dbfa6512c", "61", "a2a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f400da584e7d32612381cf88edc1c02e28a296e807c16ad22f591ee113946e48a71e46c7eccffefd2d573ec014130e508f0c9963ccebd7830409f7b1b1301725e9fa759d4ef857ec8e0bb42d6d31609d3c7e77de3bfa28c38f93393a6ddbabe819ec560ed4f061fbe742a5fd2a648d5209469420434c8753da3fa7067cc2bb4c172a");
|
||||||
|
_verify_tap_commitments(&secp, "5120c1a00a9baa82888fd7d30291135a7eaa9e9966a5f16db2b10460572f8b108d8d", "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "5ba0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f4007960d7b37dd1361aee34510e77acb4d27ddca17648a17e28475032538c1eb500f5a747f2c0893f79fe153ae918ac3d696de9322aa679aae62051ff5ed83aa502b338bd907346abd4cd9cf06117cb35d55a5a8dd950843522f8de7b5c7fba1804c38b0778d3d76b383f6db6fdf9d6e770da8fffbfa5152c0b8b38129885bcdee6");
|
||||||
|
_verify_tap_commitments(&secp, "5120bb9abeff7286b76dfc61800c548fe2621ff47506e47201a85c543b4a9a96fead", "75203455139bf238a3067bd72ed77e0ab8db590330f55ed58dba7366b53bf47342796ead6ead6ead6ead6ead6ead6ead6ead6ead6ead6ead6ead6eadac", "c0a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f4003eb5cdc419e0a6a800f34583ce750f387be34879c26f4230991bd61da743ad9d34d288e79397b709ac22ad8cc57645d593af3e15b97a876362117177ab2519c000000000000000000000000000000000000000000000000000000000000000007160c3a48c8b17bc3aeaf01db9e0a96ac47a5a9fa329e046856e7765e89c8a93ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff07feb9aa7cd72c78e66a85414cd19289f8b0ab1415013dc2a007666aa9248ec1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001fccc8bea662a9442a94f7ba0643c1d7ee7cc689f3b3506b7c8c99fd3f3b3d7772972dcdf2550cf95b65098aea67f72fef10abdcf1cef9815af8f4c4644b060e0000000000000000000000000000000000000000000000000000000000000000");
|
||||||
|
_verify_tap_commitments(&secp, "5120afddc189ea51094b4cbf463806792e9c8b35dfdc5e01228c78376380d0046b00", "4d09024747703eb9f759ce5ecd839109fecc40974ab16f5173ea390daaa5a78f7abe898165c90990062af998c5dc7989818393158a2c62b7ece727e7f5400d2efd33db8732599f6d1dce6b5b68d2d47317f2de6c9df118f61227f98453225036618aaf058140f2415d134fa69ba041c724ad81387f8c568d12ddc49eb32a71532096181b3f85fd465b8e9a176bb19f45c070baad47a2cc4505414b88c31cb5b0a192b2d2d56c404a37070b04d42c875c4ac351224f5b254f9ad0b820f43cad292d6565f796bf083173e14723f1e543c85a61689ddd5cb6666b240c15c38ce3320bf0c3be9e0322e5ef72366c294d3a2d7e8b8e7db875e7ae814537554f10b91c72b8b413e026bd5d5e917de4b54fa8f43f38771a7f242aa32dcb7ca1b0588dbf54af7ab9455047fbb894cdfdd242166db784276430eb47d4df092a6b8cb160eb982fe7d14a44283bdb4a9861ca65c06fd8b2546cfbfe38bc77f527de1b9bfd2c95a3e283b7b1d1d2b2fa291256a90a7003aefcef47ceabf113865a494af43e96a38b0b00919855eb7722ea2363e0ddfc9c51c08631d01e2a2d56e786b4ff6f1e5d415facc9c2619c285d9ad43001878294157cb025f639fb954271fd1d6173f6bc16535672f6abdd72b0284b4ff3eaf5b7247719d7c39365622610efae6562bef6e08a0b370fba75bb04dbdb90a482d8417e057f8bd021ea6ac32d0d48b08be9f77833b11e5e739960c9837d7583", "c0a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f400ff698adfda0327f188e2ee35f7aecc0f90c9138a350d450648d968c2b5dd7ef94ddd3ec418dc0d03ee4956feb708d838ed2b20e5a193465a6a1467fd3054e1ea141ea4c4c503a6271e19a090e2a69a24282e3be04c4f98720f7a0eb274d9693d13a8e3c139aa625fa2aefd09854570527f9ac545bda1b689719f5cb715612c07");
|
||||||
|
_verify_tap_commitments(&secp, "5120afddc189ea51094b4cbf463806792e9c8b35dfdc5e01228c78376380d0046b00", "83", "c0a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f4007388cda01113397d4cd00bcfbd08fd68c3cfe3a42cbfe3a7651c1d5e6dacf1ad99aaf103cceb41d9bc37ec231aca89b984b5fd3c65977ce764d51033ac65adb4b59764bec92507e4a4c3f01a06f05980163ca10f1c549bfe01f85fa4f109a1295e607f5ed9f1008048474de336f11f67a1fbf2012f58944dede0ab19a3ca81f5");
|
||||||
|
_verify_tap_commitments(&secp, "512093c7378d96518a75448821c4f7c8f4bae7ce60f804d03d1f0628dd5dd0f5de51", "04ffffffff203455139bf238a3067bd72ed77e0ab8db590330f55ed58dba7366b53bf4734279ba04feffffff87ab", "c1a0eb12e60a52614986c623cbb6621dcdba3a47e3be6b37e032b7a11c7b98f400c9a5cd1f6c8a81f5648e39f9810591df1c9a8f1fe97c92e03ecd7c0c016c951983e05473c6e8238cb4c780ea2ce62552b2a3eee068ceffc00517cd7b97e10dad");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build_huffman_tree() {
|
||||||
|
let secp = Secp256k1::verification_only();
|
||||||
|
let internal_key = schnorr::PublicKey::from_str("93c7378d96518a75448821c4f7c8f4bae7ce60f804d03d1f0628dd5dd0f5de51").unwrap();
|
||||||
|
|
||||||
|
let script_weights = vec![
|
||||||
|
(10, Script::from_hex("51").unwrap()), // semantics of script don't matter for this test
|
||||||
|
(20, Script::from_hex("52").unwrap()),
|
||||||
|
(20, Script::from_hex("53").unwrap()),
|
||||||
|
(30, Script::from_hex("54").unwrap()),
|
||||||
|
(20, Script::from_hex("55").unwrap()),
|
||||||
|
];
|
||||||
|
let tree_info = TaprootSpendInfo::with_huffman_tree(&secp, internal_key, script_weights.clone()).unwrap();
|
||||||
|
|
||||||
|
// Obtain the output key
|
||||||
|
let output_key = tree_info.output_key();
|
||||||
|
|
||||||
|
// Try to create and verify a control block from each path
|
||||||
|
for (_weights, script) in script_weights {
|
||||||
|
let ver_script = (script, LeafVersion::default());
|
||||||
|
let ctrl_block = tree_info.control_block(&ver_script).unwrap();
|
||||||
|
assert!(ctrl_block.verify_taproot_commitment(&secp, &output_key, &ver_script.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn taptree_builder() {
|
||||||
|
let secp = Secp256k1::verification_only();
|
||||||
|
let internal_key = schnorr::PublicKey::from_str("93c7378d96518a75448821c4f7c8f4bae7ce60f804d03d1f0628dd5dd0f5de51").unwrap();
|
||||||
|
|
||||||
|
let builder = TaprootBuilder::new();
|
||||||
|
// Create a tree as shown below
|
||||||
|
// For example, imagine this tree:
|
||||||
|
// A, B , C are at depth 2 and D,E are at 3
|
||||||
|
// ....
|
||||||
|
// / \
|
||||||
|
// /\ /\
|
||||||
|
// / \ / \
|
||||||
|
// A B C / \
|
||||||
|
// D E
|
||||||
|
let a = Script::from_hex("51").unwrap();
|
||||||
|
let b = Script::from_hex("52").unwrap();
|
||||||
|
let c = Script::from_hex("53").unwrap();
|
||||||
|
let d = Script::from_hex("54").unwrap();
|
||||||
|
let e = Script::from_hex("55").unwrap();
|
||||||
|
let builder = builder.add_leaf(2, a.clone()).unwrap();
|
||||||
|
let builder = builder.add_leaf(2, b.clone()).unwrap();
|
||||||
|
let builder = builder.add_leaf(2, c.clone()).unwrap();
|
||||||
|
let builder = builder.add_leaf(3, d.clone()).unwrap();
|
||||||
|
let builder = builder.add_leaf(3, e.clone()).unwrap();
|
||||||
|
|
||||||
|
let tree_info = builder.finalize(&secp, internal_key).unwrap();
|
||||||
|
let output_key = tree_info.output_key();
|
||||||
|
|
||||||
|
for script in vec![a, b, c, d, e] {
|
||||||
|
let ver_script = (script, LeafVersion::default());
|
||||||
|
let ctrl_block = tree_info.control_block(&ver_script).unwrap();
|
||||||
|
assert!(ctrl_block.verify_taproot_commitment(&secp, &output_key, &ver_script.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue