Skip to main content

kaccy_bitcoin/
taproot_psbt.rs

1//! BIP 371 — Taproot fields for PSBT.
2//!
3//! Adds Taproot-specific key-value fields to Partially Signed Bitcoin
4//! Transactions for key-path signing, script-path signing, and Taproot
5//! BIP 32 derivation paths.
6//!
7//! The new fields defined by BIP 371 cover:
8//!
9//! | Field | Key type | Description |
10//! |---|---|---|
11//! | `PSBT_IN_TAP_KEY_SIG` | `0x13` | Schnorr key-path signature |
12//! | `PSBT_IN_TAP_SCRIPT_SIG` | `0x14` | Schnorr script-path signature |
13//! | `PSBT_IN_TAP_LEAF_SCRIPT` | `0x15` | Script + control block |
14//! | `PSBT_IN_TAP_BIP32_DERIVATION` | `0x16` | x-only pubkey derivation |
15//! | `PSBT_IN_TAP_INTERNAL_KEY` | `0x17` | x-only internal key |
16//! | `PSBT_IN_TAP_MERKLE_ROOT` | `0x18` | Taproot Merkle root |
17//! | `PSBT_OUT_TAP_INTERNAL_KEY` | `0x05` | x-only output internal key |
18//! | `PSBT_OUT_TAP_TREE` | `0x06` | Tapscript tree |
19//! | `PSBT_OUT_TAP_BIP32_DERIVATION` | `0x07` | x-only output key derivation |
20//!
21//! # References
22//! - [BIP 371](https://github.com/bitcoin/bips/blob/master/bip-0371.mediawiki)
23//! - [BIP 341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki)
24
25use serde::{Deserialize, Serialize};
26use thiserror::Error;
27
28// ──────────────────────────────────────────────────────────────────────────────
29// Errors
30// ──────────────────────────────────────────────────────────────────────────────
31
32/// Errors specific to BIP 371 Taproot PSBT fields.
33#[derive(Error, Debug, Clone, PartialEq, Eq)]
34pub enum TaprootPsbtError {
35    /// An invalid key path was supplied.
36    #[error("invalid key path: {0}")]
37    InvalidKeyPath(String),
38
39    /// A leaf script is malformed.
40    #[error("invalid leaf script: {0}")]
41    InvalidLeafScript(String),
42
43    /// A control block is malformed.
44    #[error("invalid control block: {0}")]
45    InvalidControlBlock(String),
46
47    /// A Schnorr signature is malformed.
48    #[error("invalid Taproot signature: {0}")]
49    InvalidSignature(String),
50
51    /// An x-only internal key is malformed.
52    #[error("invalid internal key: {0}")]
53    InvalidInternalKey(String),
54
55    /// A Taproot Merkle root is malformed.
56    #[error("invalid Merkle root: {0}")]
57    InvalidMerkleRoot(String),
58
59    /// A BIP 32 derivation path is malformed.
60    #[error("invalid derivation path: {0}")]
61    InvalidDerivationPath(String),
62
63    /// An input index is out of range.
64    #[error("input index {0} out of range")]
65    InputIndexOutOfRange(usize),
66
67    /// An output index is out of range.
68    #[error("output index {0} out of range")]
69    OutputIndexOutOfRange(usize),
70}
71
72// ──────────────────────────────────────────────────────────────────────────────
73// Private helpers
74// ──────────────────────────────────────────────────────────────────────────────
75
76/// Validate that `hex_str` encodes exactly `expected_bytes` bytes.
77fn validate_hex_length(
78    hex_str: &str,
79    expected_bytes: usize,
80    field: &str,
81) -> Result<(), TaprootPsbtError> {
82    let expected_chars = expected_bytes * 2;
83    if hex_str.len() != expected_chars {
84        return Err(TaprootPsbtError::InvalidInternalKey(format!(
85            "{field}: expected {expected_bytes} bytes ({expected_chars} hex chars), got {} chars",
86            hex_str.len()
87        )));
88    }
89    if !hex_str.chars().all(|c| c.is_ascii_hexdigit()) {
90        return Err(TaprootPsbtError::InvalidInternalKey(format!(
91            "{field}: contains non-hex characters"
92        )));
93    }
94    Ok(())
95}
96
97/// Validate a hex string of exactly `expected_bytes` bytes, returning a
98/// domain-specific error variant wrapped in the closure `make_err`.
99fn validate_hex_with<E, F>(hex_str: &str, expected_bytes: usize, make_err: F) -> Result<(), E>
100where
101    F: FnOnce(String) -> E,
102{
103    let expected_chars = expected_bytes * 2;
104    if hex_str.len() != expected_chars {
105        return Err(make_err(format!(
106            "expected {expected_bytes} bytes ({expected_chars} hex chars), got {} chars",
107            hex_str.len()
108        )));
109    }
110    if !hex_str.chars().all(|c| c.is_ascii_hexdigit()) {
111        return Err(make_err("contains non-hex characters".to_string()));
112    }
113    Ok(())
114}
115
116// ──────────────────────────────────────────────────────────────────────────────
117// LeafVersion
118// ──────────────────────────────────────────────────────────────────────────────
119
120/// Tapscript leaf version as defined in BIP 341.
121///
122/// The only standardised version is `0xC0` (TapScript).  Future soft-forks
123/// may introduce additional versions; those are represented by [`Future`].
124///
125/// [`Future`]: LeafVersion::Future
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
127pub enum LeafVersion {
128    /// Standard Tapscript (`0xC0`).
129    #[default]
130    TapScript,
131    /// A future or non-standard leaf version.
132    Future(u8),
133}
134
135impl LeafVersion {
136    /// Encode the leaf version as a single byte.
137    pub fn to_byte(&self) -> u8 {
138        match self {
139            Self::TapScript => 0xC0,
140            Self::Future(b) => *b,
141        }
142    }
143
144    /// Decode a leaf version from a single byte.
145    pub fn from_byte(b: u8) -> Self {
146        if b == 0xC0 {
147            Self::TapScript
148        } else {
149            Self::Future(b)
150        }
151    }
152}
153
154// ──────────────────────────────────────────────────────────────────────────────
155// TapLeafScript
156// ──────────────────────────────────────────────────────────────────────────────
157
158/// A Tapscript leaf (`PSBT_IN_TAP_LEAF_SCRIPT`).
159///
160/// Stores the raw script bytes together with the control block that proves
161/// membership in the Taproot commitment tree.
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct TapLeafScript {
164    /// Tapscript leaf version.
165    pub leaf_version: LeafVersion,
166
167    /// Raw script bytes.
168    pub script: Vec<u8>,
169
170    /// Control block bytes (Merkle proof).
171    ///
172    /// The control block is at least 33 bytes (1-byte version/parity + 32-byte
173    /// internal key), followed by zero or more 32-byte Merkle proof elements.
174    pub control_block: Vec<u8>,
175}
176
177impl TapLeafScript {
178    /// Construct a new leaf script with an empty control block.
179    pub fn new(script: Vec<u8>, version: LeafVersion) -> Self {
180        Self {
181            leaf_version: version,
182            script,
183            control_block: Vec::new(),
184        }
185    }
186
187    /// Return the script as a lowercase hex string.
188    pub fn script_hex(&self) -> String {
189        self.script
190            .iter()
191            .map(|b| format!("{b:02x}"))
192            .collect::<String>()
193    }
194
195    /// Return the control block as a lowercase hex string.
196    pub fn control_block_hex(&self) -> String {
197        self.control_block
198            .iter()
199            .map(|b| format!("{b:02x}"))
200            .collect::<String>()
201    }
202
203    /// Number of 32-byte Merkle proof elements in the control block.
204    ///
205    /// The minimum well-formed control block is 33 bytes (version byte +
206    /// 32-byte internal key).  Each additional 32 bytes is one proof element.
207    /// Returns `0` if the control block is empty or shorter than 33 bytes.
208    pub fn proof_len(&self) -> usize {
209        if self.control_block.len() < 33 {
210            return 0;
211        }
212        (self.control_block.len() - 33) / 32
213    }
214}
215
216// ──────────────────────────────────────────────────────────────────────────────
217// TapBip32Derivation
218// ──────────────────────────────────────────────────────────────────────────────
219
220/// BIP 32 derivation record for a Taproot x-only public key
221/// (`PSBT_IN_TAP_BIP32_DERIVATION` / `PSBT_OUT_TAP_BIP32_DERIVATION`).
222#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct TapBip32Derivation {
224    /// 32-byte x-only public key as lowercase hex (64 chars).
225    pub x_only_pubkey: String,
226
227    /// 4-byte master key fingerprint as lowercase hex (8 chars).
228    pub master_fingerprint: String,
229
230    /// BIP 32 derivation path, e.g. `"m/86'/0'/0'/0/0"`.
231    pub derivation_path: String,
232
233    /// Tapleaf hashes that this key signs for.  An empty vector indicates
234    /// key-path spending.
235    pub leaf_hashes: Vec<String>,
236}
237
238impl TapBip32Derivation {
239    /// Construct a derivation record.
240    pub fn new(x_only_pubkey: String, master_fingerprint: String, derivation_path: String) -> Self {
241        Self {
242            x_only_pubkey,
243            master_fingerprint,
244            derivation_path,
245            leaf_hashes: Vec::new(),
246        }
247    }
248
249    /// Returns `true` when this key is used for key-path spending (no leaf
250    /// hashes specified).
251    pub fn is_key_path(&self) -> bool {
252        self.leaf_hashes.is_empty()
253    }
254
255    /// Validate the derivation record.
256    pub fn validate(&self) -> Result<(), TaprootPsbtError> {
257        // x-only pubkey: 32 bytes = 64 hex chars.
258        validate_hex_with(&self.x_only_pubkey, 32, |msg| {
259            TaprootPsbtError::InvalidKeyPath(format!("x_only_pubkey: {msg}"))
260        })?;
261
262        // Master fingerprint: 4 bytes = 8 hex chars.
263        validate_hex_with(&self.master_fingerprint, 4, |msg| {
264            TaprootPsbtError::InvalidDerivationPath(format!("master_fingerprint: {msg}"))
265        })?;
266
267        if self.derivation_path.is_empty() {
268            return Err(TaprootPsbtError::InvalidDerivationPath(
269                "derivation path is empty".to_string(),
270            ));
271        }
272
273        // Validate each leaf hash is 32 bytes.
274        for hash in &self.leaf_hashes {
275            validate_hex_with(hash, 32, |msg| {
276                TaprootPsbtError::InvalidKeyPath(format!("leaf_hash: {msg}"))
277            })?;
278        }
279
280        Ok(())
281    }
282}
283
284// ──────────────────────────────────────────────────────────────────────────────
285// TaprootInputFields
286// ──────────────────────────────────────────────────────────────────────────────
287
288/// BIP 371 Taproot fields for a single PSBT input.
289#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct TaprootInputFields {
291    /// Schnorr signature for key-path spending (`PSBT_IN_TAP_KEY_SIG`).
292    ///
293    /// Must be 64 bytes (without sighash type) or 65 bytes (with sighash type).
294    pub tap_key_sig: Option<String>,
295
296    /// Script-path Schnorr signatures: `(control_block_hex, sig_hex)` pairs
297    /// (`PSBT_IN_TAP_SCRIPT_SIG`).
298    pub tap_script_sigs: Vec<(String, String)>,
299
300    /// Tapscript leaves in the spend path (`PSBT_IN_TAP_LEAF_SCRIPT`).
301    pub tap_leaf_scripts: Vec<TapLeafScript>,
302
303    /// BIP 32 derivation paths for Taproot keys
304    /// (`PSBT_IN_TAP_BIP32_DERIVATION`).
305    pub tap_bip32_derivations: Vec<TapBip32Derivation>,
306
307    /// x-only internal key (`PSBT_IN_TAP_INTERNAL_KEY`).
308    pub tap_internal_key: Option<String>,
309
310    /// Taproot Merkle root (`PSBT_IN_TAP_MERKLE_ROOT`).
311    ///
312    /// `None` indicates a key-path-only output (no script tree).
313    pub tap_merkle_root: Option<String>,
314}
315
316impl TaprootInputFields {
317    /// Construct an empty set of Taproot input fields.
318    pub fn new() -> Self {
319        Self {
320            tap_key_sig: None,
321            tap_script_sigs: Vec::new(),
322            tap_leaf_scripts: Vec::new(),
323            tap_bip32_derivations: Vec::new(),
324            tap_internal_key: None,
325            tap_merkle_root: None,
326        }
327    }
328
329    /// Attach a key-path Schnorr signature.
330    ///
331    /// The signature must be exactly 64 bytes (without explicit sighash type)
332    /// or 65 bytes (with an appended sighash type byte).
333    pub fn with_key_sig(mut self, sig: String) -> Result<Self, TaprootPsbtError> {
334        let is_valid = sig.len() == 128 /* 64 bytes */ || sig.len() == 130 /* 65 bytes */;
335        if !is_valid || !sig.chars().all(|c| c.is_ascii_hexdigit()) {
336            return Err(TaprootPsbtError::InvalidSignature(format!(
337                "tap_key_sig must be 64 or 65 bytes (128 or 130 hex chars), got {} chars",
338                sig.len()
339            )));
340        }
341        self.tap_key_sig = Some(sig);
342        Ok(self)
343    }
344
345    /// Attach the x-only internal key.
346    ///
347    /// Must be exactly 32 bytes (64 hex chars).
348    pub fn with_internal_key(mut self, key: String) -> Result<Self, TaprootPsbtError> {
349        validate_hex_length(&key, 32, "tap_internal_key").map_err(|_| {
350            TaprootPsbtError::InvalidInternalKey(format!(
351                "tap_internal_key must be 32 bytes (64 hex chars), got {} chars",
352                key.len()
353            ))
354        })?;
355        self.tap_internal_key = Some(key);
356        Ok(self)
357    }
358
359    /// Attach the Taproot Merkle root.
360    ///
361    /// Must be exactly 32 bytes (64 hex chars).
362    pub fn with_merkle_root(mut self, root: String) -> Result<Self, TaprootPsbtError> {
363        validate_hex_with(&root, 32, TaprootPsbtError::InvalidMerkleRoot)?;
364        self.tap_merkle_root = Some(root);
365        Ok(self)
366    }
367
368    /// Append a Tapscript leaf.
369    pub fn add_leaf_script(&mut self, leaf: TapLeafScript) {
370        self.tap_leaf_scripts.push(leaf);
371    }
372
373    /// Append a BIP 32 derivation record.
374    pub fn add_bip32_derivation(&mut self, deriv: TapBip32Derivation) {
375        self.tap_bip32_derivations.push(deriv);
376    }
377
378    /// Returns `true` when a key-path Schnorr signature is present.
379    pub fn is_key_path_signed(&self) -> bool {
380        self.tap_key_sig.is_some()
381    }
382
383    /// Returns `true` when at least one script-path signature is present.
384    pub fn is_script_path_signed(&self) -> bool {
385        !self.tap_script_sigs.is_empty()
386    }
387
388    /// Returns `true` when the input is finalised via either key-path or
389    /// script-path signing.
390    pub fn is_finalized(&self) -> bool {
391        self.is_key_path_signed() || self.is_script_path_signed()
392    }
393
394    /// Validate all Taproot fields.
395    pub fn validate(&self) -> Result<(), TaprootPsbtError> {
396        if let Some(ref sig) = self.tap_key_sig {
397            let valid = (sig.len() == 128 || sig.len() == 130)
398                && sig.chars().all(|c| c.is_ascii_hexdigit());
399            if !valid {
400                return Err(TaprootPsbtError::InvalidSignature(format!(
401                    "tap_key_sig: expected 64 or 65 bytes, got {} hex chars",
402                    sig.len()
403                )));
404            }
405        }
406
407        if let Some(ref key) = self.tap_internal_key {
408            validate_hex_length(key, 32, "tap_internal_key").map_err(|_| {
409                TaprootPsbtError::InvalidInternalKey(format!(
410                    "tap_internal_key must be 32 bytes (64 hex chars), got {} chars",
411                    key.len()
412                ))
413            })?;
414        }
415
416        if let Some(ref root) = self.tap_merkle_root {
417            validate_hex_with(root, 32, TaprootPsbtError::InvalidMerkleRoot)?;
418        }
419
420        for (cb, sig) in &self.tap_script_sigs {
421            // Control block: minimum 33 bytes, multiples of 32 thereafter.
422            if cb.len() < 66 || cb.len() % 2 != 0 || !cb.chars().all(|c| c.is_ascii_hexdigit()) {
423                return Err(TaprootPsbtError::InvalidControlBlock(format!(
424                    "script sig control block is malformed (len {})",
425                    cb.len()
426                )));
427            }
428            let sig_valid = (sig.len() == 128 || sig.len() == 130)
429                && sig.chars().all(|c| c.is_ascii_hexdigit());
430            if !sig_valid {
431                return Err(TaprootPsbtError::InvalidSignature(format!(
432                    "script-path sig must be 64 or 65 bytes, got {} hex chars",
433                    sig.len()
434                )));
435            }
436        }
437
438        for deriv in &self.tap_bip32_derivations {
439            deriv.validate()?;
440        }
441
442        Ok(())
443    }
444}
445
446impl Default for TaprootInputFields {
447    fn default() -> Self {
448        Self::new()
449    }
450}
451
452// ──────────────────────────────────────────────────────────────────────────────
453// TaprootOutputFields
454// ──────────────────────────────────────────────────────────────────────────────
455
456/// BIP 371 Taproot fields for a single PSBT output.
457#[derive(Debug, Clone, Serialize, Deserialize)]
458pub struct TaprootOutputFields {
459    /// x-only internal key (`PSBT_OUT_TAP_INTERNAL_KEY`).
460    pub tap_internal_key: Option<String>,
461
462    /// Tapscript tree (`PSBT_OUT_TAP_TREE`).
463    pub tap_tree: Vec<TapLeafScript>,
464
465    /// BIP 32 derivation paths for x-only output keys
466    /// (`PSBT_OUT_TAP_BIP32_DERIVATION`).
467    pub tap_bip32_derivations: Vec<TapBip32Derivation>,
468}
469
470impl TaprootOutputFields {
471    /// Construct an empty set of Taproot output fields.
472    pub fn new() -> Self {
473        Self {
474            tap_internal_key: None,
475            tap_tree: Vec::new(),
476            tap_bip32_derivations: Vec::new(),
477        }
478    }
479
480    /// Attach the x-only internal key.
481    ///
482    /// Must be exactly 32 bytes (64 hex chars).
483    pub fn with_internal_key(mut self, key: String) -> Result<Self, TaprootPsbtError> {
484        validate_hex_with(&key, 32, |msg| {
485            TaprootPsbtError::InvalidInternalKey(format!("tap_internal_key: {msg}"))
486        })?;
487        self.tap_internal_key = Some(key);
488        Ok(self)
489    }
490
491    /// Append a Tapscript tree leaf.
492    pub fn add_tree_leaf(&mut self, leaf: TapLeafScript) {
493        self.tap_tree.push(leaf);
494    }
495
496    /// Returns `true` when an internal key is set, indicating this is a Taproot
497    /// output.
498    pub fn is_taproot_output(&self) -> bool {
499        self.tap_internal_key.is_some()
500    }
501
502    /// Validate all Taproot output fields.
503    pub fn validate(&self) -> Result<(), TaprootPsbtError> {
504        if let Some(ref key) = self.tap_internal_key {
505            validate_hex_with(key, 32, |msg| {
506                TaprootPsbtError::InvalidInternalKey(format!("tap_internal_key: {msg}"))
507            })?;
508        }
509
510        for deriv in &self.tap_bip32_derivations {
511            deriv.validate()?;
512        }
513
514        Ok(())
515    }
516}
517
518impl Default for TaprootOutputFields {
519    fn default() -> Self {
520        Self::new()
521    }
522}
523
524// ──────────────────────────────────────────────────────────────────────────────
525// TaprootPsbt
526// ──────────────────────────────────────────────────────────────────────────────
527
528/// A PSBT with BIP 371 Taproot fields attached.
529///
530/// The base PSBT data is stored as a hex-encoded byte string.  The
531/// `input_fields` and `output_fields` vectors are indexed to match the inputs
532/// and outputs of the base PSBT.
533#[derive(Debug, Clone, Serialize, Deserialize)]
534pub struct TaprootPsbt {
535    /// The serialised base PSBT, hex-encoded.
536    pub base_psbt_hex: String,
537
538    /// Taproot fields for each input, indexed to match the base PSBT inputs.
539    pub input_fields: Vec<TaprootInputFields>,
540
541    /// Taproot fields for each output, indexed to match the base PSBT outputs.
542    pub output_fields: Vec<TaprootOutputFields>,
543}
544
545impl TaprootPsbt {
546    /// Wrap an existing hex-encoded PSBT.
547    pub fn new(psbt_hex: String) -> Self {
548        Self {
549            base_psbt_hex: psbt_hex,
550            input_fields: Vec::new(),
551            output_fields: Vec::new(),
552        }
553    }
554
555    /// Number of input Taproot field records currently attached.
556    pub fn input_count(&self) -> usize {
557        self.input_fields.len()
558    }
559
560    /// Number of output Taproot field records currently attached.
561    pub fn output_count(&self) -> usize {
562        self.output_fields.len()
563    }
564
565    /// Returns `true` when every attached input field set is finalised.
566    ///
567    /// Returns `false` when no input fields are attached.
568    pub fn is_complete(&self) -> bool {
569        if self.input_fields.is_empty() {
570            return false;
571        }
572        self.input_fields.iter().all(|f| f.is_finalized())
573    }
574
575    /// Return a reference to the Taproot fields for input `index`, or `None`
576    /// if the index is out of range.
577    pub fn get_input_fields(&self, index: usize) -> Option<&TaprootInputFields> {
578        self.input_fields.get(index)
579    }
580
581    /// Return a reference to the Taproot fields for output `index`, or `None`
582    /// if the index is out of range.
583    pub fn get_output_fields(&self, index: usize) -> Option<&TaprootOutputFields> {
584        self.output_fields.get(index)
585    }
586
587    /// Attach or replace the Taproot fields for input at `index`.
588    ///
589    /// If `index` is beyond the current length of `input_fields`, the vector
590    /// is extended with default (empty) records to fill the gap.
591    pub fn add_input_taproot_fields(
592        &mut self,
593        index: usize,
594        fields: TaprootInputFields,
595    ) -> Result<(), TaprootPsbtError> {
596        // Extend with defaults if needed.
597        while self.input_fields.len() <= index {
598            self.input_fields.push(TaprootInputFields::new());
599        }
600        self.input_fields[index] = fields;
601        Ok(())
602    }
603
604    /// Attach or replace the Taproot fields for output at `index`.
605    ///
606    /// If `index` is beyond the current length of `output_fields`, the vector
607    /// is extended with default (empty) records to fill the gap.
608    pub fn add_output_taproot_fields(
609        &mut self,
610        index: usize,
611        fields: TaprootOutputFields,
612    ) -> Result<(), TaprootPsbtError> {
613        while self.output_fields.len() <= index {
614            self.output_fields.push(TaprootOutputFields::new());
615        }
616        self.output_fields[index] = fields;
617        Ok(())
618    }
619
620    /// Validate all attached input and output Taproot fields.
621    pub fn validate_all(&self) -> Result<(), TaprootPsbtError> {
622        for fields in &self.input_fields {
623            fields.validate()?;
624        }
625        for fields in &self.output_fields {
626            fields.validate()?;
627        }
628        Ok(())
629    }
630}
631
632// ──────────────────────────────────────────────────────────────────────────────
633// TaprootPsbtBuilder
634// ──────────────────────────────────────────────────────────────────────────────
635
636/// Fluent builder for [`TaprootPsbt`].
637///
638/// # Example
639///
640/// ```rust
641/// use kaccy_bitcoin::taproot_psbt::{
642///     TaprootPsbtBuilder, TaprootInputFields, TaprootOutputFields,
643/// };
644///
645/// let psbt = TaprootPsbtBuilder::new("deadbeef".to_string())
646///     .add_input_fields(TaprootInputFields::new())
647///     .add_output_fields(TaprootOutputFields::new())
648///     .build();
649///
650/// assert_eq!(psbt.input_count(), 1);
651/// assert_eq!(psbt.output_count(), 1);
652/// ```
653#[derive(Debug)]
654pub struct TaprootPsbtBuilder {
655    psbt_hex: String,
656    input_fields: Vec<TaprootInputFields>,
657    output_fields: Vec<TaprootOutputFields>,
658}
659
660impl TaprootPsbtBuilder {
661    /// Create a new builder wrapping the given hex-encoded PSBT.
662    pub fn new(psbt_hex: String) -> Self {
663        Self {
664            psbt_hex,
665            input_fields: Vec::new(),
666            output_fields: Vec::new(),
667        }
668    }
669
670    /// Append input Taproot fields.
671    #[must_use]
672    pub fn add_input_fields(mut self, fields: TaprootInputFields) -> Self {
673        self.input_fields.push(fields);
674        self
675    }
676
677    /// Append output Taproot fields.
678    #[must_use]
679    pub fn add_output_fields(mut self, fields: TaprootOutputFields) -> Self {
680        self.output_fields.push(fields);
681        self
682    }
683
684    /// Consume the builder and produce a [`TaprootPsbt`].
685    pub fn build(self) -> TaprootPsbt {
686        TaprootPsbt {
687            base_psbt_hex: self.psbt_hex,
688            input_fields: self.input_fields,
689            output_fields: self.output_fields,
690        }
691    }
692}
693
694// ──────────────────────────────────────────────────────────────────────────────
695// Tests
696// ──────────────────────────────────────────────────────────────────────────────
697
698#[cfg(test)]
699mod tests {
700    use super::*;
701
702    // 32-byte x-only pubkey hex (64 chars).
703    fn xonly_key() -> String {
704        "b2c9c8b6a5d3e1f0b2c9c8b6a5d3e1f0b2c9c8b6a5d3e1f0b2c9c8b6a5d3e1f0".to_string()
705    }
706
707    // 64-byte Schnorr signature (128 hex chars).
708    fn schnorr_sig_64() -> String {
709        "aa".repeat(64)
710    }
711
712    // 65-byte Schnorr signature (130 hex chars).
713    fn schnorr_sig_65() -> String {
714        format!("{}01", "bb".repeat(64))
715    }
716
717    // Minimal control block (33 bytes = 66 hex chars).
718    fn minimal_control_block() -> String {
719        "c0".to_string() + &"ab".repeat(32)
720    }
721
722    #[test]
723    fn test_leaf_version_tapscript() {
724        let v = LeafVersion::TapScript;
725        assert_eq!(v.to_byte(), 0xC0);
726        assert_eq!(LeafVersion::from_byte(0xC0), LeafVersion::TapScript);
727    }
728
729    #[test]
730    fn test_leaf_version_future() {
731        let v = LeafVersion::Future(0xC2);
732        assert_eq!(v.to_byte(), 0xC2);
733        assert_eq!(LeafVersion::from_byte(0xC2), LeafVersion::Future(0xC2));
734    }
735
736    #[test]
737    fn test_tap_leaf_script_new() {
738        let script = vec![0x51, 0x20]; // OP_1 OP_32
739        let leaf = TapLeafScript::new(script.clone(), LeafVersion::TapScript);
740        assert_eq!(leaf.leaf_version, LeafVersion::TapScript);
741        assert_eq!(leaf.script, script);
742        assert!(leaf.control_block.is_empty());
743        assert_eq!(leaf.script_hex(), "5120");
744    }
745
746    #[test]
747    fn test_tap_leaf_script_proof_len() {
748        let mut leaf = TapLeafScript::new(vec![], LeafVersion::TapScript);
749
750        // Empty control block → 0.
751        assert_eq!(leaf.proof_len(), 0);
752
753        // Control block with exactly 33 bytes → 0 proof elements.
754        leaf.control_block = vec![0xc0; 33];
755        assert_eq!(leaf.proof_len(), 0);
756
757        // 33 + 32 = 65 bytes → 1 proof element.
758        leaf.control_block = vec![0xc0; 65];
759        assert_eq!(leaf.proof_len(), 1);
760
761        // 33 + 64 = 97 bytes → 2 proof elements.
762        leaf.control_block = vec![0xc0; 97];
763        assert_eq!(leaf.proof_len(), 2);
764    }
765
766    #[test]
767    fn test_taproot_input_fields_default() {
768        let fields = TaprootInputFields::default();
769        assert!(fields.tap_key_sig.is_none());
770        assert!(fields.tap_internal_key.is_none());
771        assert!(fields.tap_merkle_root.is_none());
772        assert!(fields.tap_script_sigs.is_empty());
773        assert!(fields.tap_leaf_scripts.is_empty());
774        assert!(fields.tap_bip32_derivations.is_empty());
775        assert!(!fields.is_finalized());
776    }
777
778    #[test]
779    fn test_taproot_input_key_path_signed() {
780        let fields = TaprootInputFields::new()
781            .with_key_sig(schnorr_sig_64())
782            .expect("64-byte sig should be accepted");
783
784        assert!(fields.is_key_path_signed());
785        assert!(!fields.is_script_path_signed());
786        assert!(fields.is_finalized());
787        assert!(fields.validate().is_ok());
788    }
789
790    #[test]
791    fn test_taproot_input_key_sig_65_bytes() {
792        let fields = TaprootInputFields::new()
793            .with_key_sig(schnorr_sig_65())
794            .expect("65-byte sig should be accepted");
795
796        assert!(fields.is_key_path_signed());
797        assert!(fields.validate().is_ok());
798    }
799
800    #[test]
801    fn test_taproot_input_key_sig_invalid_length() {
802        // 63 bytes = 126 hex chars — invalid.
803        let bad_sig = "cc".repeat(63);
804        let result = TaprootInputFields::new().with_key_sig(bad_sig);
805        assert!(
806            matches!(result, Err(TaprootPsbtError::InvalidSignature(_))),
807            "expected InvalidSignature"
808        );
809    }
810
811    #[test]
812    fn test_taproot_input_internal_key_valid() {
813        let fields = TaprootInputFields::new()
814            .with_internal_key(xonly_key())
815            .expect("valid 32-byte key");
816
817        assert!(fields.tap_internal_key.is_some());
818        assert!(fields.validate().is_ok());
819    }
820
821    #[test]
822    fn test_taproot_input_internal_key_invalid() {
823        let short_key = "aabb".to_string(); // only 2 bytes
824        let result = TaprootInputFields::new().with_internal_key(short_key);
825        assert!(
826            matches!(result, Err(TaprootPsbtError::InvalidInternalKey(_))),
827            "expected InvalidInternalKey"
828        );
829    }
830
831    #[test]
832    fn test_taproot_output_fields() {
833        let fields = TaprootOutputFields::new()
834            .with_internal_key(xonly_key())
835            .expect("valid key");
836
837        assert!(fields.is_taproot_output());
838        assert!(fields.validate().is_ok());
839    }
840
841    #[test]
842    fn test_taproot_output_fields_default_not_taproot() {
843        let fields = TaprootOutputFields::default();
844        assert!(!fields.is_taproot_output());
845        assert!(fields.validate().is_ok());
846    }
847
848    #[test]
849    fn test_taproot_psbt_new() {
850        let psbt = TaprootPsbt::new("deadbeef".to_string());
851        assert_eq!(psbt.base_psbt_hex, "deadbeef");
852        assert_eq!(psbt.input_count(), 0);
853        assert_eq!(psbt.output_count(), 0);
854        assert!(!psbt.is_complete());
855    }
856
857    #[test]
858    fn test_taproot_psbt_add_input_fields() {
859        let mut psbt = TaprootPsbt::new("deadbeef".to_string());
860
861        let fields = TaprootInputFields::new()
862            .with_key_sig(schnorr_sig_64())
863            .expect("valid sig");
864
865        psbt.add_input_taproot_fields(0, fields).unwrap();
866        assert_eq!(psbt.input_count(), 1);
867
868        // is_complete() should be true since the one input is finalised.
869        assert!(psbt.is_complete());
870
871        // get_input_fields
872        let retrieved = psbt.get_input_fields(0);
873        assert!(retrieved.is_some());
874        assert!(retrieved.unwrap().is_key_path_signed());
875
876        // Out-of-range returns None.
877        assert!(psbt.get_input_fields(99).is_none());
878    }
879
880    #[test]
881    fn test_taproot_psbt_add_output_fields_sparse() {
882        let mut psbt = TaprootPsbt::new("cafebabe".to_string());
883
884        // Insert at index 2 — should extend the vector.
885        let fields = TaprootOutputFields::new()
886            .with_internal_key(xonly_key())
887            .expect("valid key");
888
889        psbt.add_output_taproot_fields(2, fields).unwrap();
890        assert_eq!(psbt.output_count(), 3); // indices 0, 1 are defaults
891        assert!(psbt.get_output_fields(2).unwrap().is_taproot_output());
892        assert!(!psbt.get_output_fields(0).unwrap().is_taproot_output());
893    }
894
895    #[test]
896    fn test_bip32_derivation() {
897        let deriv = TapBip32Derivation::new(
898            xonly_key(),
899            "deadbeef".to_string(), // 4-byte fingerprint
900            "m/86'/0'/0'/0/0".to_string(),
901        );
902
903        assert!(deriv.is_key_path());
904        assert!(deriv.validate().is_ok());
905    }
906
907    #[test]
908    fn test_bip32_derivation_invalid_fingerprint() {
909        let deriv = TapBip32Derivation::new(
910            xonly_key(),
911            "dead".to_string(), // only 2 bytes — should fail
912            "m/86'/0'/0'/0/0".to_string(),
913        );
914        let result = deriv.validate();
915        assert!(
916            matches!(result, Err(TaprootPsbtError::InvalidDerivationPath(_))),
917            "expected InvalidDerivationPath for bad fingerprint, got {result:?}"
918        );
919    }
920
921    #[test]
922    fn test_taproot_psbt_builder() {
923        let input_fields = TaprootInputFields::new()
924            .with_key_sig(schnorr_sig_64())
925            .expect("valid sig");
926
927        let output_fields = TaprootOutputFields::new()
928            .with_internal_key(xonly_key())
929            .expect("valid key");
930
931        let psbt = TaprootPsbtBuilder::new("aabbccdd".to_string())
932            .add_input_fields(input_fields)
933            .add_output_fields(output_fields)
934            .build();
935
936        assert_eq!(psbt.base_psbt_hex, "aabbccdd");
937        assert_eq!(psbt.input_count(), 1);
938        assert_eq!(psbt.output_count(), 1);
939        assert!(psbt.is_complete());
940        assert!(psbt.validate_all().is_ok());
941    }
942
943    #[test]
944    fn test_validate_all_with_script_path_sigs() {
945        let mut input_fields = TaprootInputFields::new();
946        // Add a script-path sig with a minimal control block and valid sig.
947        input_fields
948            .tap_script_sigs
949            .push((minimal_control_block(), schnorr_sig_64()));
950
951        let psbt = TaprootPsbtBuilder::new("ff".to_string())
952            .add_input_fields(input_fields)
953            .build();
954
955        assert!(psbt.is_complete());
956        assert!(psbt.validate_all().is_ok());
957    }
958
959    #[test]
960    fn test_merkle_root_validation() {
961        // Exactly 64 hex chars = 32 bytes (32 * "ab").
962        let valid_root = "ab".repeat(32);
963        let fields = TaprootInputFields::new()
964            .with_merkle_root(valid_root)
965            .expect("valid 32-byte merkle root");
966
967        assert!(fields.tap_merkle_root.is_some());
968        assert!(fields.validate().is_ok());
969
970        // Short merkle root should fail.
971        let result = TaprootInputFields::new().with_merkle_root("deadbeef".to_string()); // only 4 bytes
972        assert!(
973            matches!(result, Err(TaprootPsbtError::InvalidMerkleRoot(_))),
974            "expected InvalidMerkleRoot for short value"
975        );
976    }
977
978    #[test]
979    fn test_tap_leaf_script_add_and_retrieve() {
980        let mut input_fields = TaprootInputFields::new();
981
982        let leaf1 = TapLeafScript::new(vec![0x51], LeafVersion::TapScript);
983        let leaf2 = TapLeafScript::new(vec![0x52], LeafVersion::Future(0xC2));
984
985        input_fields.add_leaf_script(leaf1);
986        input_fields.add_leaf_script(leaf2);
987
988        assert_eq!(input_fields.tap_leaf_scripts.len(), 2);
989        assert_eq!(input_fields.tap_leaf_scripts[0].script_hex(), "51");
990        assert_eq!(
991            input_fields.tap_leaf_scripts[1].leaf_version,
992            LeafVersion::Future(0xC2)
993        );
994    }
995}