Skip to main content

chains_sdk/bitcoin/
taproot.rs

1//! **BIP-341** — Taproot output construction and script-path spending.
2//!
3//! Implements the full TapTree (Merkelized Abstract Syntax Tree) for organizing
4//! spending conditions, key tweaking, control block construction, and P2TR
5//! address generation from script trees.
6//!
7//! # Example
8//! ```no_run
9//! use chains_sdk::bitcoin::taproot::{TapTree, TapLeaf};
10//!
11//! // Build a TapTree with two spending scripts
12//! let leaf1 = TapLeaf::new(0xC0, vec![0x51]); // OP_TRUE
13//! let leaf2 = TapLeaf::new(0xC0, vec![0x00]); // OP_FALSE
14//! let tree = TapTree::branch(TapTree::leaf(leaf1), TapTree::leaf(leaf2));
15//! let merkle_root = tree.merkle_root();
16//! ```
17
18use crate::crypto::tagged_hash;
19use crate::encoding;
20use crate::error::SignerError;
21
22// ─── TapLeaf ────────────────────────────────────────────────────────
23
24/// A leaf node in a TapTree, containing a script and leaf version.
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct TapLeaf {
27    /// The leaf version (default 0xC0 for Tapscript as defined in BIP-342).
28    pub version: u8,
29    /// The script bytes.
30    pub script: Vec<u8>,
31}
32
33impl TapLeaf {
34    /// Create a new TapLeaf with the given version and script.
35    pub fn new(version: u8, script: Vec<u8>) -> Self {
36        Self { version, script }
37    }
38
39    /// Create a Tapscript leaf (version 0xC0) with the given script.
40    pub fn tapscript(script: Vec<u8>) -> Self {
41        Self {
42            version: 0xC0,
43            script,
44        }
45    }
46
47    /// Compute the leaf hash: `tagged_hash("TapLeaf", version || compact_size(script) || script)`.
48    pub fn leaf_hash(&self) -> [u8; 32] {
49        let mut data = Vec::new();
50        data.push(self.version);
51        encoding::encode_compact_size(&mut data, self.script.len() as u64);
52        data.extend_from_slice(&self.script);
53        tagged_hash(b"TapLeaf", &data)
54    }
55}
56
57// ─── TapTree (Merkle Tree) ──────────────────────────────────────────
58
59/// A node in the Taproot Merkle tree.
60#[derive(Clone, Debug)]
61pub enum TapTree {
62    /// A leaf node containing a script.
63    Leaf(TapLeaf),
64    /// A branch node with two children.
65    Branch(Box<TapTree>, Box<TapTree>),
66}
67
68impl TapTree {
69    /// Create a leaf node.
70    pub fn leaf(tap_leaf: TapLeaf) -> Self {
71        TapTree::Leaf(tap_leaf)
72    }
73
74    /// Create a branch node from two children.
75    pub fn branch(left: TapTree, right: TapTree) -> Self {
76        TapTree::Branch(Box::new(left), Box::new(right))
77    }
78
79    /// Compute the merkle root hash of this tree node.
80    ///
81    /// - Leaf: `tagged_hash("TapLeaf", ...)`
82    /// - Branch: `tagged_hash("TapBranch", sort(left, right))`
83    pub fn merkle_root(&self) -> [u8; 32] {
84        match self {
85            TapTree::Leaf(leaf) => leaf.leaf_hash(),
86            TapTree::Branch(left, right) => {
87                let left_hash = left.merkle_root();
88                let right_hash = right.merkle_root();
89                tap_branch_hash(&left_hash, &right_hash)
90            }
91        }
92    }
93
94    /// Compute the merkle proof (path) for a specific leaf.
95    ///
96    /// Returns `Some(path)` where path is a list of 32-byte hashes,
97    /// or `None` if the leaf is not found in this tree.
98    pub fn merkle_proof(&self, target_leaf: &TapLeaf) -> Option<Vec<[u8; 32]>> {
99        self.find_proof(target_leaf)
100    }
101
102    fn find_proof(&self, target: &TapLeaf) -> Option<Vec<[u8; 32]>> {
103        match self {
104            TapTree::Leaf(leaf) => {
105                if leaf == target {
106                    Some(Vec::new())
107                } else {
108                    None
109                }
110            }
111            TapTree::Branch(left, right) => {
112                // Try left subtree
113                if let Some(mut path) = left.find_proof(target) {
114                    path.push(right.merkle_root());
115                    return Some(path);
116                }
117                // Try right subtree
118                if let Some(mut path) = right.find_proof(target) {
119                    path.push(left.merkle_root());
120                    return Some(path);
121                }
122                None
123            }
124        }
125    }
126
127    /// Count the total number of leaves in this tree.
128    pub fn leaf_count(&self) -> usize {
129        match self {
130            TapTree::Leaf(_) => 1,
131            TapTree::Branch(left, right) => left.leaf_count() + right.leaf_count(),
132        }
133    }
134
135    /// Get the depth of the tree.
136    pub fn depth(&self) -> usize {
137        match self {
138            TapTree::Leaf(_) => 0,
139            TapTree::Branch(left, right) => 1 + left.depth().max(right.depth()),
140        }
141    }
142}
143
144// ─── Branch Hashing ─────────────────────────────────────────────────
145
146/// Compute a TapBranch hash from two child hashes.
147///
148/// Per BIP-341: `tagged_hash("TapBranch", sort(a, b))` — the smaller hash comes first.
149pub fn tap_branch_hash(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
150    let mut data = Vec::with_capacity(64);
151    // Lexicographic sort
152    if a <= b {
153        data.extend_from_slice(a);
154        data.extend_from_slice(b);
155    } else {
156        data.extend_from_slice(b);
157        data.extend_from_slice(a);
158    }
159    tagged_hash(b"TapBranch", &data)
160}
161
162// ─── Key Tweaking ───────────────────────────────────────────────────
163
164/// Tweak an internal public key with a merkle root to produce the output key.
165///
166/// **BIP-341 Algorithm:**
167/// - `t = tagged_hash("TapTweak", internal_key || merkle_root)`
168/// - `Q = P + t*G`
169///
170/// Returns `Ok((tweaked_x_only_key, parity))` where parity indicates if Q has odd y.
171///
172/// If `merkle_root` is `None`, uses key-path-only: `t = tagged_hash("TapTweak", internal_key)`.
173///
174/// Returns `Err` if the internal key is not a valid curve point.
175pub fn taproot_tweak(
176    internal_key: &[u8; 32],
177    merkle_root: Option<&[u8; 32]>,
178) -> Result<([u8; 32], bool), SignerError> {
179    // Compute tweak scalar
180    let mut tweak_data = Vec::with_capacity(64);
181    tweak_data.extend_from_slice(internal_key);
182    if let Some(root) = merkle_root {
183        tweak_data.extend_from_slice(root);
184    }
185    let tweak = tagged_hash(b"TapTweak", &tweak_data);
186
187    // Parse internal key as x-only point (even y assumed)
188    use k256::elliptic_curve::ops::Reduce;
189    use k256::{ProjectivePoint, Scalar};
190
191    let mut pk_sec1 = [0u8; 33];
192    pk_sec1[0] = 0x02; // even y
193    pk_sec1[1..].copy_from_slice(internal_key);
194
195    // Parse the internal key point
196    // GroupEncoding import required for AffinePoint::from_bytes() trait resolution.
197    use k256::elliptic_curve::group::GroupEncoding;
198    use k256::AffinePoint;
199
200    let pk_ct = AffinePoint::from_bytes((&pk_sec1).into());
201    let pk_point: ProjectivePoint =
202        Option::from(pk_ct.map(ProjectivePoint::from)).ok_or_else(|| {
203            SignerError::InvalidPublicKey("taproot internal key is not a valid curve point".into())
204        })?;
205
206    // Compute t * G
207    let t_wide = k256::U256::from_be_slice(&tweak);
208    let t_scalar = <Scalar as Reduce<k256::U256>>::reduce(t_wide);
209    let tweaked = pk_point + ProjectivePoint::GENERATOR * t_scalar;
210
211    // Get the x-only output key
212    use k256::elliptic_curve::sec1::ToEncodedPoint;
213    let tweaked_affine = tweaked.to_affine();
214    let encoded = tweaked_affine.to_encoded_point(true);
215    let bytes = encoded.as_bytes();
216
217    let mut x_only = [0u8; 32];
218    x_only.copy_from_slice(&bytes[1..33]);
219
220    let parity = bytes[0] == 0x03;
221
222    Ok((x_only, parity))
223}
224
225/// Compute the full Taproot output key from an internal key and optional TapTree.
226///
227/// If `tree` is `None`, this produces a key-path-only P2TR output.
228pub fn taproot_output_key(
229    internal_key: &[u8; 32],
230    tree: Option<&TapTree>,
231) -> Result<([u8; 32], bool), SignerError> {
232    let merkle_root = tree.map(|t| t.merkle_root());
233    taproot_tweak(internal_key, merkle_root.as_ref())
234}
235
236// ─── Control Block ──────────────────────────────────────────────────
237
238/// A BIP-341 control block for script-path spending.
239#[derive(Clone, Debug)]
240pub struct ControlBlock {
241    /// The leaf version (high 7 bits) combined with parity bit (low bit).
242    pub leaf_version_and_parity: u8,
243    /// The 32-byte x-only internal public key.
244    pub internal_key: [u8; 32],
245    /// The merkle proof path (0 to 128 hashes of 32 bytes each).
246    pub merkle_path: Vec<[u8; 32]>,
247}
248
249/// Maximum Taproot control block Merkle path depth (BIP-341 consensus limit).
250const MAX_CONTROL_BLOCK_DEPTH: usize = 128;
251
252impl ControlBlock {
253    /// Create a control block for a specific leaf in a tree.
254    ///
255    /// # Arguments
256    /// * `internal_key` - The 32-byte x-only internal public key
257    /// * `tree` - The TapTree
258    /// * `leaf` - The target leaf to create a proof for
259    /// * `output_key_parity` - Whether the output key Q has odd y
260    pub fn new(
261        internal_key: [u8; 32],
262        tree: &TapTree,
263        leaf: &TapLeaf,
264        output_key_parity: bool,
265    ) -> Option<Self> {
266        let merkle_path = tree.merkle_proof(leaf)?;
267        let parity_bit = if output_key_parity { 1u8 } else { 0u8 };
268        Some(ControlBlock {
269            leaf_version_and_parity: (leaf.version & 0xFE) | parity_bit,
270            internal_key,
271            merkle_path,
272        })
273    }
274
275    /// Serialize the control block to bytes.
276    ///
277    /// Format: `control_byte || internal_key (32) || merkle_path (32 * m)`
278    pub fn to_bytes(&self) -> Vec<u8> {
279        let mut bytes = Vec::with_capacity(33 + self.merkle_path.len() * 32);
280        bytes.push(self.leaf_version_and_parity);
281        bytes.extend_from_slice(&self.internal_key);
282        for hash in &self.merkle_path {
283            bytes.extend_from_slice(hash);
284        }
285        bytes
286    }
287
288    /// Parse a control block from bytes.
289    pub fn from_bytes(data: &[u8]) -> Option<Self> {
290        if data.len() < 33 || (data.len() - 33) % 32 != 0 {
291            return None;
292        }
293        let control_byte = data[0];
294        let mut internal_key = [0u8; 32];
295        internal_key.copy_from_slice(&data[1..33]);
296        let path_count = (data.len() - 33) / 32;
297        if path_count > MAX_CONTROL_BLOCK_DEPTH {
298            return None;
299        }
300        let mut merkle_path = Vec::with_capacity(path_count);
301        for i in 0..path_count {
302            let mut hash = [0u8; 32];
303            hash.copy_from_slice(&data[33 + i * 32..33 + (i + 1) * 32]);
304            merkle_path.push(hash);
305        }
306        Some(ControlBlock {
307            leaf_version_and_parity: control_byte,
308            internal_key,
309            merkle_path,
310        })
311    }
312
313    /// Verify the control block against a given output key and leaf.
314    ///
315    /// Reconstructs the merkle root from the proof path and verifies
316    /// that `taproot_tweak(internal_key, merkle_root) == output_key`.
317    pub fn verify(&self, output_key: &[u8; 32], leaf: &TapLeaf) -> bool {
318        let control_leaf_version = self.leaf_version_and_parity & 0xFE;
319        if control_leaf_version != (leaf.version & 0xFE) {
320            return false;
321        }
322
323        // Start with the leaf hash
324        let mut current = leaf.leaf_hash();
325
326        // Walk the merkle path
327        for sibling in &self.merkle_path {
328            current = tap_branch_hash(&current, sibling);
329        }
330
331        // Verify the tweak
332        let Ok((tweaked, parity)) = taproot_tweak(&self.internal_key, Some(&current)) else {
333            return false;
334        };
335        let expected_parity = (self.leaf_version_and_parity & 1) == 1;
336
337        tweaked == *output_key && parity == expected_parity
338    }
339}
340
341// ─── P2TR Address Generation ────────────────────────────────────────
342
343/// Generate a P2TR (Taproot) Bech32m address from an internal key and optional tree.
344///
345/// # Arguments
346/// * `internal_key` - 32-byte x-only internal public key
347/// * `tree` - Optional TapTree for script-path spending
348/// * `hrp` - Human readable part ("bc" for mainnet, "tb" for testnet)
349pub fn taproot_address(
350    internal_key: &[u8; 32],
351    tree: Option<&TapTree>,
352    hrp: &str,
353) -> Result<String, SignerError> {
354    let (output_key, _parity) = taproot_output_key(internal_key, tree)?;
355    encoding::bech32_encode(hrp, 1, &output_key)
356}
357
358// ─── Tests ──────────────────────────────────────────────────────────
359
360#[cfg(test)]
361#[allow(clippy::unwrap_used, clippy::expect_used)]
362mod tests {
363    use super::*;
364
365    #[test]
366    fn test_tap_leaf_hash_deterministic() {
367        let leaf = TapLeaf::tapscript(vec![0x51]); // OP_TRUE
368        let h1 = leaf.leaf_hash();
369        let h2 = leaf.leaf_hash();
370        assert_eq!(h1, h2);
371    }
372
373    #[test]
374    fn test_tap_leaf_hash_different_scripts() {
375        let l1 = TapLeaf::tapscript(vec![0x51]); // OP_TRUE
376        let l2 = TapLeaf::tapscript(vec![0x00]); // OP_FALSE
377        assert_ne!(l1.leaf_hash(), l2.leaf_hash());
378    }
379
380    #[test]
381    fn test_tap_leaf_hash_different_versions() {
382        let l1 = TapLeaf::new(0xC0, vec![0x51]);
383        let l2 = TapLeaf::new(0xC2, vec![0x51]);
384        assert_ne!(l1.leaf_hash(), l2.leaf_hash());
385    }
386
387    #[test]
388    fn test_tap_branch_hash_commutative() {
389        let a = [0xAA; 32];
390        let b = [0xBB; 32];
391        // Branch hash should be the same regardless of order
392        assert_eq!(tap_branch_hash(&a, &b), tap_branch_hash(&b, &a));
393    }
394
395    #[test]
396    fn test_tap_tree_single_leaf() {
397        let leaf = TapLeaf::tapscript(vec![0x51]);
398        let tree = TapTree::leaf(leaf.clone());
399        assert_eq!(tree.leaf_count(), 1);
400        assert_eq!(tree.depth(), 0);
401        assert_eq!(tree.merkle_root(), leaf.leaf_hash());
402    }
403
404    #[test]
405    fn test_tap_tree_two_leaves() {
406        let l1 = TapLeaf::tapscript(vec![0x51]);
407        let l2 = TapLeaf::tapscript(vec![0x00]);
408        let tree = TapTree::branch(TapTree::leaf(l1.clone()), TapTree::leaf(l2.clone()));
409        assert_eq!(tree.leaf_count(), 2);
410        assert_eq!(tree.depth(), 1);
411        let expected = tap_branch_hash(&l1.leaf_hash(), &l2.leaf_hash());
412        assert_eq!(tree.merkle_root(), expected);
413    }
414
415    #[test]
416    fn test_tap_tree_three_leaves() {
417        let l1 = TapLeaf::tapscript(vec![0x51]);
418        let l2 = TapLeaf::tapscript(vec![0x00]);
419        let l3 = TapLeaf::tapscript(vec![0x52]); // OP_2
420        let tree = TapTree::branch(
421            TapTree::leaf(l1),
422            TapTree::branch(TapTree::leaf(l2), TapTree::leaf(l3)),
423        );
424        assert_eq!(tree.leaf_count(), 3);
425        assert_eq!(tree.depth(), 2);
426    }
427
428    #[test]
429    fn test_tap_tree_merkle_proof() {
430        let l1 = TapLeaf::tapscript(vec![0x51]);
431        let l2 = TapLeaf::tapscript(vec![0x00]);
432        let tree = TapTree::branch(TapTree::leaf(l1.clone()), TapTree::leaf(l2.clone()));
433
434        // Proof for l1 should contain l2's hash
435        let proof1 = tree.merkle_proof(&l1).expect("leaf found");
436        assert_eq!(proof1.len(), 1);
437        assert_eq!(proof1[0], l2.leaf_hash());
438
439        // Proof for l2 should contain l1's hash
440        let proof2 = tree.merkle_proof(&l2).expect("leaf found");
441        assert_eq!(proof2.len(), 1);
442        assert_eq!(proof2[0], l1.leaf_hash());
443    }
444
445    #[test]
446    fn test_tap_tree_merkle_proof_not_found() {
447        let l1 = TapLeaf::tapscript(vec![0x51]);
448        let l2 = TapLeaf::tapscript(vec![0x00]);
449        let unknown = TapLeaf::tapscript(vec![0xFF]);
450        let tree = TapTree::branch(TapTree::leaf(l1), TapTree::leaf(l2));
451        assert!(tree.merkle_proof(&unknown).is_none());
452    }
453
454    #[test]
455    fn test_taproot_tweak_key_path_only() {
456        // Key-path-only (no merkle root)
457        let internal_key = [0x01; 32];
458        let result = taproot_tweak(&internal_key, None);
459        // [0x01; 32] is not a valid x-coordinate on secp256k1
460        // Valid keys will return Ok, invalid will return Err
461        match result {
462            Ok((tweaked, _parity)) => {
463                assert_ne!(tweaked, internal_key); // tweaked key should differ
464                assert_ne!(tweaked, [0u8; 32]); // should not be zero
465
466                // Deterministic
467                let (tweaked2, _) = taproot_tweak(&internal_key, None).unwrap();
468                assert_eq!(tweaked, tweaked2);
469            }
470            Err(_) => {
471                // Invalid internal key — this is expected for arbitrary byte patterns
472            }
473        }
474    }
475
476    #[test]
477    fn test_taproot_tweak_with_merkle_root() {
478        // Use a known valid internal key (generator point x-coordinate)
479        let internal_key_hex = "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798";
480        let internal_key_bytes = hex::decode(internal_key_hex).unwrap();
481        let mut internal_key = [0u8; 32];
482        internal_key.copy_from_slice(&internal_key_bytes);
483
484        let merkle_root = [0xAA; 32];
485        let (tweaked1, _) = taproot_tweak(&internal_key, Some(&merkle_root)).unwrap();
486        let (tweaked2, _) = taproot_tweak(&internal_key, None).unwrap();
487        // Different merkle roots should produce different tweaked keys
488        assert_ne!(tweaked1, tweaked2);
489    }
490
491    #[test]
492    fn test_taproot_output_key_with_tree() {
493        let internal_key_hex = "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798";
494        let internal_key_bytes = hex::decode(internal_key_hex).unwrap();
495        let mut internal_key = [0u8; 32];
496        internal_key.copy_from_slice(&internal_key_bytes);
497        let l1 = TapLeaf::tapscript(vec![0x51]);
498        let tree = TapTree::leaf(l1);
499        let (output_key, _parity) = taproot_output_key(&internal_key, Some(&tree)).unwrap();
500        assert_ne!(output_key, [0u8; 32]);
501    }
502
503    #[test]
504    fn test_control_block_serialization() {
505        let l1 = TapLeaf::tapscript(vec![0x51]);
506        let l2 = TapLeaf::tapscript(vec![0x00]);
507        let tree = TapTree::branch(TapTree::leaf(l1.clone()), TapTree::leaf(l2.clone()));
508        let internal_key = [0x01; 32];
509        let (_, parity) = taproot_output_key(&internal_key, Some(&tree)).unwrap();
510
511        let cb = ControlBlock::new(internal_key, &tree, &l1, parity).expect("ok");
512        let bytes = cb.to_bytes();
513
514        // Size: 1 (control byte) + 32 (internal key) + 32 * 1 (one proof hash)
515        assert_eq!(bytes.len(), 65);
516
517        // Round-trip
518        let parsed = ControlBlock::from_bytes(&bytes).expect("valid");
519        assert_eq!(parsed.internal_key, internal_key);
520        assert_eq!(parsed.merkle_path.len(), 1);
521    }
522
523    #[test]
524    fn test_control_block_verify() {
525        let l1 = TapLeaf::tapscript(vec![0x51]);
526        let l2 = TapLeaf::tapscript(vec![0x00]);
527        let tree = TapTree::branch(TapTree::leaf(l1.clone()), TapTree::leaf(l2.clone()));
528
529        // Use a known valid internal key (generator point x-coordinate)
530        let internal_key_hex = "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798";
531        let internal_key_bytes = hex::decode(internal_key_hex).unwrap();
532        let mut internal_key = [0u8; 32];
533        internal_key.copy_from_slice(&internal_key_bytes);
534
535        let (output_key, parity) = taproot_output_key(&internal_key, Some(&tree)).unwrap();
536        let cb = ControlBlock::new(internal_key, &tree, &l1, parity).expect("ok");
537
538        assert!(cb.verify(&output_key, &l1));
539        // Verify with wrong leaf should fail
540        assert!(!cb.verify(&output_key, &l2));
541
542        // Control-byte leaf version must match the provided leaf version.
543        let mut bad_cb = cb.clone();
544        bad_cb.leaf_version_and_parity ^= 0x02; // flip a leaf-version bit
545        assert!(!bad_cb.verify(&output_key, &l1));
546    }
547
548    #[test]
549    fn test_control_block_from_bytes_invalid() {
550        // Too short
551        assert!(ControlBlock::from_bytes(&[0x00; 10]).is_none());
552        // Wrong alignment
553        assert!(ControlBlock::from_bytes(&[0x00; 34]).is_none());
554        // Too deep (BIP-341 max depth is 128)
555        assert!(ControlBlock::from_bytes(&vec![0x00; 33 + 32 * 129]).is_none());
556        // Valid: 33 bytes (no proof)
557        assert!(ControlBlock::from_bytes(&[0x00; 33]).is_some());
558        // Valid: 65 bytes (1 proof hash)
559        assert!(ControlBlock::from_bytes(&[0x00; 65]).is_some());
560    }
561
562    #[test]
563    fn test_taproot_address_key_path_only() {
564        let internal_key_hex = "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798";
565        let internal_key_bytes = hex::decode(internal_key_hex).unwrap();
566        let mut internal_key = [0u8; 32];
567        internal_key.copy_from_slice(&internal_key_bytes);
568
569        let addr = taproot_address(&internal_key, None, "bc").expect("ok");
570        assert!(addr.starts_with("bc1p"));
571    }
572
573    #[test]
574    fn test_taproot_tweak_invalid_key() {
575        // All-zero key is not on the curve
576        let invalid_key = [0u8; 32];
577        let result = taproot_tweak(&invalid_key, None);
578        assert!(result.is_err(), "zeroed key should not be valid");
579    }
580
581    #[test]
582    fn test_taproot_address_with_tree() {
583        let internal_key_hex = "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798";
584        let internal_key_bytes = hex::decode(internal_key_hex).unwrap();
585        let mut internal_key = [0u8; 32];
586        internal_key.copy_from_slice(&internal_key_bytes);
587
588        let leaf = TapLeaf::tapscript(vec![0x51]);
589        let tree = TapTree::leaf(leaf);
590
591        let addr = taproot_address(&internal_key, Some(&tree), "bc").expect("ok");
592        assert!(addr.starts_with("bc1p"));
593
594        // Address with tree should differ from key-path-only
595        let addr_no_tree = taproot_address(&internal_key, None, "bc").expect("ok");
596        assert_ne!(addr, addr_no_tree);
597    }
598
599    #[test]
600    fn test_taproot_address_testnet() {
601        let internal_key_hex = "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798";
602        let internal_key_bytes = hex::decode(internal_key_hex).unwrap();
603        let mut internal_key = [0u8; 32];
604        internal_key.copy_from_slice(&internal_key_bytes);
605        let addr = taproot_address(&internal_key, None, "tb").expect("ok");
606        assert!(addr.starts_with("tb1p"));
607    }
608}