Skip to main content

chains_sdk/bitcoin/psbt/
v2.rs

1//! **PSBT v2** — BIP-370 Constructor-based PSBT.
2//!
3//! PSBTv2 separates the global unsigned transaction into per-input/output
4//! fields, enabling interactive construction (CoinJoin, Payjoin) where
5//! inputs and outputs can be added independently.
6//!
7//! # Key Differences from v0
8//! - No `PSBT_GLOBAL_UNSIGNED_TX` — inputs/outputs use per-map fields
9//! - `PSBT_GLOBAL_TX_VERSION`, `PSBT_GLOBAL_FALLBACK_LOCKTIME`
10//! - `PSBT_GLOBAL_INPUT_COUNT`, `PSBT_GLOBAL_OUTPUT_COUNT`
11//! - `PSBT_GLOBAL_TX_MODIFIABLE` flag for interactive construction
12//! - Per-input: `PREVIOUS_TXID`, `OUTPUT_INDEX`, `SEQUENCE`
13//! - Per-output: `AMOUNT`, `SCRIPT`
14
15use crate::encoding;
16use crate::error::SignerError;
17use std::collections::HashSet;
18
19// ═══════════════════════════════════════════════════════════════════
20// Constants — Global Key Types (BIP-370)
21// ═══════════════════════════════════════════════════════════════════
22
23/// PSBT v2 global key types.
24pub mod global_key {
25    /// Transaction version (4-byte LE uint32).
26    pub const TX_VERSION: u8 = 0x02;
27    /// Fallback locktime (4-byte LE uint32).
28    pub const FALLBACK_LOCKTIME: u8 = 0x03;
29    /// Number of inputs (compact-size uint).
30    pub const INPUT_COUNT: u8 = 0x04;
31    /// Number of outputs (compact-size uint).
32    pub const OUTPUT_COUNT: u8 = 0x05;
33    /// Modifiable flags (1 byte).
34    pub const TX_MODIFIABLE: u8 = 0x06;
35    /// PSBT version (4-byte LE uint32, must be 2).
36    pub const VERSION: u8 = 0xFB;
37}
38
39/// PSBT v2 per-input key types.
40pub mod input_key {
41    /// Previous txid (32 bytes, reversed).
42    pub const PREVIOUS_TXID: u8 = 0x0E;
43    /// Output index (4-byte LE uint32).
44    pub const OUTPUT_INDEX: u8 = 0x0F;
45    /// Sequence number (4-byte LE uint32).
46    pub const SEQUENCE: u8 = 0x10;
47    /// Required time-based locktime.
48    pub const REQUIRED_TIME_LOCKTIME: u8 = 0x11;
49    /// Required height-based locktime.
50    pub const REQUIRED_HEIGHT_LOCKTIME: u8 = 0x12;
51    // v0-compatible keys reused in v2:
52    /// Non-witness UTXO.
53    pub const NON_WITNESS_UTXO: u8 = 0x00;
54    /// Witness UTXO.
55    pub const WITNESS_UTXO: u8 = 0x01;
56    /// Partial signature.
57    pub const PARTIAL_SIG: u8 = 0x02;
58    /// Sighash type.
59    pub const SIGHASH_TYPE: u8 = 0x03;
60}
61
62/// PSBT v2 per-output key types.
63pub mod output_key {
64    /// Output amount (8-byte LE int64).
65    pub const AMOUNT: u8 = 0x03;
66    /// Output script (variable length).
67    pub const SCRIPT: u8 = 0x04;
68    // v0-compatible keys:
69    /// Redeem script.
70    pub const REDEEM_SCRIPT: u8 = 0x00;
71    /// Witness script.
72    pub const WITNESS_SCRIPT: u8 = 0x01;
73}
74
75// ═══════════════════════════════════════════════════════════════════
76// Modifiable Flags
77// ═══════════════════════════════════════════════════════════════════
78
79/// Flags indicating which parts of the PSBT can be modified.
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub struct ModifiableFlags(u8);
82
83impl ModifiableFlags {
84    /// No modifications allowed.
85    pub const NONE: Self = Self(0);
86    /// Inputs can be added/removed.
87    pub const INPUTS_MODIFIABLE: Self = Self(0x01);
88    /// Outputs can be added/removed.
89    pub const OUTPUTS_MODIFIABLE: Self = Self(0x02);
90    /// Sighash type can include ANYONECANPAY.
91    pub const HAS_SIGHASH_SINGLE: Self = Self(0x04);
92
93    /// Create flags from raw byte.
94    #[must_use]
95    pub const fn from_byte(b: u8) -> Self {
96        Self(b)
97    }
98
99    /// Get the raw byte.
100    #[must_use]
101    pub const fn to_byte(self) -> u8 {
102        self.0
103    }
104
105    /// Check if inputs are modifiable.
106    #[must_use]
107    pub const fn inputs_modifiable(self) -> bool {
108        self.0 & 0x01 != 0
109    }
110
111    /// Check if outputs are modifiable.
112    #[must_use]
113    pub const fn outputs_modifiable(self) -> bool {
114        self.0 & 0x02 != 0
115    }
116
117    /// Combine two flag sets.
118    #[must_use]
119    pub const fn union(self, other: Self) -> Self {
120        Self(self.0 | other.0)
121    }
122}
123
124// ═══════════════════════════════════════════════════════════════════
125// PSBT v2 Input
126// ═══════════════════════════════════════════════════════════════════
127
128/// A PSBTv2 input with explicit fields.
129#[derive(Debug, Clone)]
130pub struct PsbtV2Input {
131    /// Previous transaction ID (32 bytes, internal byte order).
132    pub previous_txid: [u8; 32],
133    /// Output index being spent.
134    pub output_index: u32,
135    /// Sequence number (default: 0xFFFFFFFF).
136    pub sequence: u32,
137    /// Required time-based locktime for this input.
138    pub required_time_locktime: Option<u32>,
139    /// Required height-based locktime for this input.
140    pub required_height_locktime: Option<u32>,
141    /// Additional key-value pairs (witness UTXO, sigs, etc.).
142    pub extra: Vec<(Vec<u8>, Vec<u8>)>,
143}
144
145impl PsbtV2Input {
146    /// Create a new input referencing a specific UTXO.
147    #[must_use]
148    pub fn new(previous_txid: [u8; 32], output_index: u32) -> Self {
149        Self {
150            previous_txid,
151            output_index,
152            sequence: 0xFFFFFFFF,
153            required_time_locktime: None,
154            required_height_locktime: None,
155            extra: Vec::new(),
156        }
157    }
158
159    /// Set the sequence number.
160    #[must_use]
161    pub fn with_sequence(mut self, sequence: u32) -> Self {
162        self.sequence = sequence;
163        self
164    }
165
166    /// Set a witness UTXO for this input.
167    pub fn set_witness_utxo(&mut self, amount: u64, script_pubkey: &[u8]) {
168        let mut value = Vec::with_capacity(8 + script_pubkey.len() + 9);
169        value.extend_from_slice(&amount.to_le_bytes());
170        // CompactSize for script length
171        encoding::encode_compact_size(&mut value, script_pubkey.len() as u64);
172        value.extend_from_slice(script_pubkey);
173        self.extra.push((vec![input_key::WITNESS_UTXO], value));
174    }
175
176    /// Serialize this input map to PSBTv2 format.
177    pub fn serialize(&self) -> Vec<u8> {
178        let mut buf = Vec::new();
179
180        // PREVIOUS_TXID
181        write_kv(&mut buf, &[input_key::PREVIOUS_TXID], &self.previous_txid);
182        // OUTPUT_INDEX
183        write_kv(
184            &mut buf,
185            &[input_key::OUTPUT_INDEX],
186            &self.output_index.to_le_bytes(),
187        );
188        // SEQUENCE
189        write_kv(
190            &mut buf,
191            &[input_key::SEQUENCE],
192            &self.sequence.to_le_bytes(),
193        );
194
195        // Optional timelocks
196        if let Some(t) = self.required_time_locktime {
197            write_kv(
198                &mut buf,
199                &[input_key::REQUIRED_TIME_LOCKTIME],
200                &t.to_le_bytes(),
201            );
202        }
203        if let Some(h) = self.required_height_locktime {
204            write_kv(
205                &mut buf,
206                &[input_key::REQUIRED_HEIGHT_LOCKTIME],
207                &h.to_le_bytes(),
208            );
209        }
210
211        // Extra KVs
212        for (k, v) in &self.extra {
213            write_kv(&mut buf, k, v);
214        }
215
216        // Terminator
217        buf.push(0x00);
218        buf
219    }
220}
221
222// ═══════════════════════════════════════════════════════════════════
223// PSBT v2 Output
224// ═══════════════════════════════════════════════════════════════════
225
226/// A PSBTv2 output with explicit fields.
227#[derive(Debug, Clone)]
228pub struct PsbtV2Output {
229    /// Output amount in satoshis.
230    pub amount: u64,
231    /// Output scriptPubKey.
232    pub script: Vec<u8>,
233    /// Additional key-value pairs.
234    pub extra: Vec<(Vec<u8>, Vec<u8>)>,
235}
236
237impl PsbtV2Output {
238    /// Create a new output.
239    #[must_use]
240    pub fn new(amount: u64, script: Vec<u8>) -> Self {
241        Self {
242            amount,
243            script,
244            extra: Vec::new(),
245        }
246    }
247
248    /// Serialize this output map to PSBTv2 format.
249    pub fn serialize(&self) -> Vec<u8> {
250        let mut buf = Vec::new();
251
252        // AMOUNT
253        write_kv(&mut buf, &[output_key::AMOUNT], &self.amount.to_le_bytes());
254        // SCRIPT
255        write_kv(&mut buf, &[output_key::SCRIPT], &self.script);
256
257        // Extra KVs
258        for (k, v) in &self.extra {
259            write_kv(&mut buf, k, v);
260        }
261
262        // Terminator
263        buf.push(0x00);
264        buf
265    }
266}
267
268// ═══════════════════════════════════════════════════════════════════
269// PSBT v2 Constructor
270// ═══════════════════════════════════════════════════════════════════
271
272/// A PSBTv2 (BIP-370) container.
273#[derive(Debug, Clone)]
274pub struct PsbtV2 {
275    /// Transaction version (typically 2).
276    pub tx_version: u32,
277    /// Fallback locktime.
278    pub fallback_locktime: u32,
279    /// Modifiable flags.
280    pub modifiable: ModifiableFlags,
281    /// Per-input data.
282    pub inputs: Vec<PsbtV2Input>,
283    /// Per-output data.
284    pub outputs: Vec<PsbtV2Output>,
285    /// Additional global key-value pairs.
286    pub global_extra: Vec<(Vec<u8>, Vec<u8>)>,
287}
288
289impl PsbtV2 {
290    /// Create a new PSBTv2 with default settings.
291    #[must_use]
292    pub fn new() -> Self {
293        Self {
294            tx_version: 2,
295            fallback_locktime: 0,
296            modifiable: ModifiableFlags::NONE,
297            inputs: Vec::new(),
298            outputs: Vec::new(),
299            global_extra: Vec::new(),
300        }
301    }
302
303    /// Create a PSBTv2 for interactive construction (CoinJoin/Payjoin).
304    ///
305    /// Sets inputs and outputs as modifiable.
306    #[must_use]
307    pub fn new_interactive() -> Self {
308        Self {
309            modifiable: ModifiableFlags::INPUTS_MODIFIABLE
310                .union(ModifiableFlags::OUTPUTS_MODIFIABLE),
311            ..Self::new()
312        }
313    }
314
315    /// Add an input. Returns the input index.
316    pub fn add_input(&mut self, input: PsbtV2Input) -> usize {
317        self.inputs.push(input);
318        self.inputs.len() - 1
319    }
320
321    /// Add an output. Returns the output index.
322    pub fn add_output(&mut self, output: PsbtV2Output) -> usize {
323        self.outputs.push(output);
324        self.outputs.len() - 1
325    }
326
327    /// Compute the effective locktime.
328    ///
329    /// Per BIP-370: uses the maximum of all required timelocks,
330    /// falling back to the global fallback_locktime.
331    #[must_use]
332    pub fn computed_locktime(&self) -> u32 {
333        let mut max_time: Option<u32> = None;
334        let mut max_height: Option<u32> = None;
335
336        for input in &self.inputs {
337            if let Some(t) = input.required_time_locktime {
338                max_time = Some(max_time.map_or(t, |m: u32| m.max(t)));
339            }
340            if let Some(h) = input.required_height_locktime {
341                max_height = Some(max_height.map_or(h, |m: u32| m.max(h)));
342            }
343        }
344
345        // Height-based takes priority if both present
346        if let Some(h) = max_height {
347            return h;
348        }
349        if let Some(t) = max_time {
350            return t;
351        }
352
353        self.fallback_locktime
354    }
355
356    /// Serialize to PSBTv2 binary format.
357    ///
358    /// Format: `magic || 0xFF || global_map || input_maps... || output_maps...`
359    pub fn serialize(&self) -> Vec<u8> {
360        let mut buf = Vec::new();
361
362        // Magic bytes
363        buf.extend_from_slice(b"psbt\xFF");
364
365        // ─── Global Map ─────────────────────────────────────────
366        // Version = 2
367        write_kv(&mut buf, &[global_key::VERSION], &2u32.to_le_bytes());
368        // TX_VERSION
369        write_kv(
370            &mut buf,
371            &[global_key::TX_VERSION],
372            &self.tx_version.to_le_bytes(),
373        );
374        // FALLBACK_LOCKTIME
375        write_kv(
376            &mut buf,
377            &[global_key::FALLBACK_LOCKTIME],
378            &self.fallback_locktime.to_le_bytes(),
379        );
380        // INPUT_COUNT
381        write_kv(
382            &mut buf,
383            &[global_key::INPUT_COUNT],
384            &compact_size(self.inputs.len()),
385        );
386        // OUTPUT_COUNT
387        write_kv(
388            &mut buf,
389            &[global_key::OUTPUT_COUNT],
390            &compact_size(self.outputs.len()),
391        );
392        // TX_MODIFIABLE
393        if self.modifiable.to_byte() != 0 {
394            write_kv(
395                &mut buf,
396                &[global_key::TX_MODIFIABLE],
397                &[self.modifiable.to_byte()],
398            );
399        }
400
401        // Extra global KVs
402        for (k, v) in &self.global_extra {
403            write_kv(&mut buf, k, v);
404        }
405
406        // Global map terminator
407        buf.push(0x00);
408
409        // ─── Input Maps ─────────────────────────────────────────
410        for input in &self.inputs {
411            buf.extend_from_slice(&input.serialize());
412        }
413
414        // ─── Output Maps ────────────────────────────────────────
415        for output in &self.outputs {
416            buf.extend_from_slice(&output.serialize());
417        }
418
419        buf
420    }
421
422    /// Deserialize a PSBTv2 from binary format.
423    pub fn deserialize(data: &[u8]) -> Result<Self, SignerError> {
424        if data.len() < 5 || &data[0..5] != b"psbt\xFF" {
425            return Err(SignerError::ParseError("invalid PSBT magic".into()));
426        }
427
428        let mut pos = 5;
429        let mut psbt = PsbtV2::new();
430        let mut input_count: Option<usize> = None;
431        let mut output_count: Option<usize> = None;
432        let mut found_version = false;
433        let mut found_tx_version = false;
434        let mut found_global_terminator = false;
435        let mut seen_global_keys: HashSet<Vec<u8>> = HashSet::new();
436
437        // Parse global map
438        while pos < data.len() {
439            if data[pos] == 0x00 {
440                pos += 1;
441                found_global_terminator = true;
442                break;
443            }
444
445            let (key, val, consumed) = read_kv(&data[pos..])?;
446            pos += consumed;
447
448            if !seen_global_keys.insert(key.clone()) {
449                return Err(SignerError::ParseError(
450                    "PSBTv2: duplicate key in global map".into(),
451                ));
452            }
453
454            if key.len() == 1 {
455                match key[0] {
456                    global_key::VERSION => {
457                        if val.len() != 4 {
458                            return Err(SignerError::ParseError(
459                                "PSBTv2: version must be 4 bytes".into(),
460                            ));
461                        }
462                        let v = u32::from_le_bytes([val[0], val[1], val[2], val[3]]);
463                        if v != 2 {
464                            return Err(SignerError::ParseError(format!(
465                                "expected PSBT version 2, got {v}"
466                            )));
467                        }
468                        found_version = true;
469                    }
470                    global_key::TX_VERSION => {
471                        if val.len() != 4 {
472                            return Err(SignerError::ParseError(
473                                "PSBTv2: tx version must be 4 bytes".into(),
474                            ));
475                        }
476                        psbt.tx_version = u32::from_le_bytes([val[0], val[1], val[2], val[3]]);
477                        found_tx_version = true;
478                    }
479                    global_key::FALLBACK_LOCKTIME => {
480                        if val.len() != 4 {
481                            return Err(SignerError::ParseError(
482                                "PSBTv2: fallback locktime must be 4 bytes".into(),
483                            ));
484                        }
485                        psbt.fallback_locktime =
486                            u32::from_le_bytes([val[0], val[1], val[2], val[3]]);
487                    }
488                    global_key::INPUT_COUNT => {
489                        input_count = Some(read_compact_size(&val)?);
490                    }
491                    global_key::OUTPUT_COUNT => {
492                        output_count = Some(read_compact_size(&val)?);
493                    }
494                    global_key::TX_MODIFIABLE => {
495                        if val.len() != 1 {
496                            return Err(SignerError::ParseError(
497                                "PSBTv2: tx_modifiable must be 1 byte".into(),
498                            ));
499                        }
500                        psbt.modifiable = ModifiableFlags::from_byte(val[0]);
501                    }
502                    _ => {
503                        psbt.global_extra.push((key, val));
504                    }
505                }
506            } else {
507                psbt.global_extra.push((key, val));
508            }
509        }
510
511        if !found_global_terminator {
512            return Err(SignerError::ParseError(
513                "PSBTv2: unterminated global map".into(),
514            ));
515        }
516
517        if !found_version {
518            return Err(SignerError::ParseError("missing PSBT version".into()));
519        }
520        if !found_tx_version {
521            return Err(SignerError::ParseError("missing tx version".into()));
522        }
523
524        let n_inputs =
525            input_count.ok_or_else(|| SignerError::ParseError("missing input count".into()))?;
526        let n_outputs =
527            output_count.ok_or_else(|| SignerError::ParseError("missing output count".into()))?;
528
529        // Parse input maps
530        for i in 0..n_inputs {
531            let mut input = PsbtV2Input::new([0; 32], 0);
532            let mut has_previous_txid = false;
533            let mut has_output_index = false;
534            let mut found_terminator = false;
535            let mut seen_input_keys: HashSet<Vec<u8>> = HashSet::new();
536            while pos < data.len() {
537                if data[pos] == 0x00 {
538                    pos += 1;
539                    found_terminator = true;
540                    break;
541                }
542                let (key, val, consumed) = read_kv(&data[pos..])?;
543                pos += consumed;
544
545                if !seen_input_keys.insert(key.clone()) {
546                    return Err(SignerError::ParseError(format!(
547                        "PSBTv2: duplicate key in input map {i}"
548                    )));
549                }
550
551                if key.len() == 1 {
552                    match key[0] {
553                        input_key::PREVIOUS_TXID => {
554                            if has_previous_txid {
555                                return Err(SignerError::ParseError(format!(
556                                    "PSBTv2: duplicate input previous_txid in map {i}",
557                                )));
558                            }
559                            if val.len() != 32 {
560                                return Err(SignerError::ParseError(format!(
561                                    "PSBTv2: input {i} previous_txid must be 32 bytes, got {}",
562                                    val.len()
563                                )));
564                            }
565                            input.previous_txid.copy_from_slice(&val);
566                            has_previous_txid = true;
567                        }
568                        input_key::OUTPUT_INDEX => {
569                            if has_output_index {
570                                return Err(SignerError::ParseError(format!(
571                                    "PSBTv2: duplicate input output_index in map {i}",
572                                )));
573                            }
574                            if val.len() != 4 {
575                                return Err(SignerError::ParseError(format!(
576                                    "PSBTv2: input {i} output_index must be 4 bytes, got {}",
577                                    val.len()
578                                )));
579                            }
580                            input.output_index =
581                                u32::from_le_bytes([val[0], val[1], val[2], val[3]]);
582                            has_output_index = true;
583                        }
584                        input_key::SEQUENCE => {
585                            if val.len() != 4 {
586                                return Err(SignerError::ParseError(format!(
587                                    "PSBTv2: input {i} sequence must be 4 bytes, got {}",
588                                    val.len()
589                                )));
590                            }
591                            input.sequence = u32::from_le_bytes([val[0], val[1], val[2], val[3]]);
592                        }
593                        input_key::REQUIRED_TIME_LOCKTIME => {
594                            if input.required_time_locktime.is_some() {
595                                return Err(SignerError::ParseError(format!(
596                                    "PSBTv2: duplicate input required_time_locktime in map {i}",
597                                )));
598                            }
599                            if val.len() != 4 {
600                                return Err(SignerError::ParseError(format!(
601                                    "PSBTv2: input {i} required_time_locktime must be 4 bytes, got {}",
602                                    val.len()
603                                )));
604                            }
605                            input.required_time_locktime =
606                                Some(u32::from_le_bytes([val[0], val[1], val[2], val[3]]));
607                        }
608                        input_key::REQUIRED_HEIGHT_LOCKTIME => {
609                            if input.required_height_locktime.is_some() {
610                                return Err(SignerError::ParseError(format!(
611                                    "PSBTv2: duplicate input required_height_locktime in map {i}",
612                                )));
613                            }
614                            if val.len() != 4 {
615                                return Err(SignerError::ParseError(format!(
616                                    "PSBTv2: input {i} required_height_locktime must be 4 bytes, got {}",
617                                    val.len()
618                                )));
619                            }
620                            input.required_height_locktime =
621                                Some(u32::from_le_bytes([val[0], val[1], val[2], val[3]]));
622                        }
623                        _ => {
624                            input.extra.push((key, val));
625                        }
626                    }
627                } else {
628                    input.extra.push((key, val));
629                }
630            }
631            if !found_terminator {
632                return Err(SignerError::ParseError(format!(
633                    "PSBTv2: unterminated input map {i}"
634                )));
635            }
636            if !has_previous_txid {
637                return Err(SignerError::ParseError(format!(
638                    "PSBTv2: missing PREVIOUS_TXID in input map {i}",
639                )));
640            }
641            if !has_output_index {
642                return Err(SignerError::ParseError(format!(
643                    "PSBTv2: missing OUTPUT_INDEX in input map {i}",
644                )));
645            }
646            psbt.inputs.push(input);
647        }
648
649        // Parse output maps
650        for i in 0..n_outputs {
651            let mut output = PsbtV2Output::new(0, Vec::new());
652            let mut has_amount = false;
653            let mut has_script = false;
654            let mut found_terminator = false;
655            let mut seen_output_keys: HashSet<Vec<u8>> = HashSet::new();
656            while pos < data.len() {
657                if data[pos] == 0x00 {
658                    pos += 1;
659                    found_terminator = true;
660                    break;
661                }
662                let (key, val, consumed) = read_kv(&data[pos..])?;
663                pos += consumed;
664
665                if !seen_output_keys.insert(key.clone()) {
666                    return Err(SignerError::ParseError(format!(
667                        "PSBTv2: duplicate key in output map {i}"
668                    )));
669                }
670
671                if key.len() == 1 {
672                    match key[0] {
673                        output_key::AMOUNT => {
674                            if has_amount {
675                                return Err(SignerError::ParseError(format!(
676                                    "PSBTv2: duplicate output amount in map {i}",
677                                )));
678                            }
679                            if val.len() != 8 {
680                                return Err(SignerError::ParseError(format!(
681                                    "PSBTv2: output {i} amount must be 8 bytes, got {}",
682                                    val.len()
683                                )));
684                            }
685                            output.amount = u64::from_le_bytes([
686                                val[0], val[1], val[2], val[3], val[4], val[5], val[6], val[7],
687                            ]);
688                            has_amount = true;
689                        }
690                        output_key::SCRIPT => {
691                            if has_script {
692                                return Err(SignerError::ParseError(format!(
693                                    "PSBTv2: duplicate output script in map {i}",
694                                )));
695                            }
696                            output.script = val;
697                            has_script = true;
698                        }
699                        _ => {
700                            output.extra.push((key, val));
701                        }
702                    }
703                } else {
704                    output.extra.push((key, val));
705                }
706            }
707            if !found_terminator {
708                return Err(SignerError::ParseError(format!(
709                    "PSBTv2: unterminated output map {i}"
710                )));
711            }
712            if !has_amount {
713                return Err(SignerError::ParseError(format!(
714                    "PSBTv2: missing AMOUNT in output map {i}",
715                )));
716            }
717            if !has_script {
718                return Err(SignerError::ParseError(format!(
719                    "PSBTv2: missing SCRIPT in output map {i}",
720                )));
721            }
722            psbt.outputs.push(output);
723        }
724
725        // Reject trailing bytes
726        if pos != data.len() {
727            return Err(SignerError::ParseError(format!(
728                "PSBTv2: {} trailing bytes",
729                data.len() - pos
730            )));
731        }
732
733        Ok(psbt)
734    }
735}
736
737impl Default for PsbtV2 {
738    fn default() -> Self {
739        Self::new()
740    }
741}
742
743// ═══════════════════════════════════════════════════════════════════
744// Helpers
745// ═══════════════════════════════════════════════════════════════════
746
747/// Write a PSBT key-value pair: `compact_size(key_len) || key || compact_size(val_len) || val`
748fn write_kv(buf: &mut Vec<u8>, key: &[u8], value: &[u8]) {
749    buf.extend_from_slice(&compact_size(key.len()));
750    buf.extend_from_slice(key);
751    buf.extend_from_slice(&compact_size(value.len()));
752    buf.extend_from_slice(value);
753}
754
755/// Read a PSBT key-value pair. Returns (key, value, bytes_consumed).
756fn read_kv(data: &[u8]) -> Result<(Vec<u8>, Vec<u8>, usize), SignerError> {
757    let mut pos = 0;
758
759    // Key length
760    let (key_len, consumed) = read_compact_size_at(data, pos)?;
761    pos = pos
762        .checked_add(consumed)
763        .ok_or_else(|| SignerError::ParseError("PSBT key length offset overflow".into()))?;
764    let key_end = pos
765        .checked_add(key_len)
766        .ok_or_else(|| SignerError::ParseError("PSBT key length overflow".into()))?;
767    if key_end > data.len() {
768        return Err(SignerError::ParseError("truncated PSBT key".into()));
769    }
770    let key = data[pos..key_end].to_vec();
771    pos = key_end;
772
773    // Value length
774    let (val_len, consumed) = read_compact_size_at(data, pos)?;
775    pos = pos
776        .checked_add(consumed)
777        .ok_or_else(|| SignerError::ParseError("PSBT value length offset overflow".into()))?;
778    let val_end = pos
779        .checked_add(val_len)
780        .ok_or_else(|| SignerError::ParseError("PSBT value length overflow".into()))?;
781    if val_end > data.len() {
782        return Err(SignerError::ParseError("truncated PSBT value".into()));
783    }
784    let value = data[pos..val_end].to_vec();
785    pos = val_end;
786
787    Ok((key, value, pos))
788}
789
790/// Encode a compact size integer.
791fn compact_size(n: usize) -> Vec<u8> {
792    if n < 0xFD {
793        vec![n as u8]
794    } else if n <= 0xFFFF {
795        let mut v = vec![0xFD];
796        v.extend_from_slice(&(n as u16).to_le_bytes());
797        v
798    } else if n <= 0xFFFF_FFFF {
799        let mut v = vec![0xFE];
800        v.extend_from_slice(&(n as u32).to_le_bytes());
801        v
802    } else {
803        let mut v = vec![0xFF];
804        v.extend_from_slice(&(n as u64).to_le_bytes());
805        v
806    }
807}
808
809/// Read a compact_size from a serialized value.
810fn read_compact_size(data: &[u8]) -> Result<usize, SignerError> {
811    let (value, consumed) = decode_compact_size_with_consumed(data)?;
812    if consumed != data.len() {
813        return Err(SignerError::ParseError(
814            "compact_size: trailing bytes".into(),
815        ));
816    }
817    Ok(value)
818}
819
820/// Read a compact_size at a position. Returns (value, bytes_consumed).
821fn read_compact_size_at(data: &[u8], pos: usize) -> Result<(usize, usize), SignerError> {
822    if pos >= data.len() {
823        return Err(SignerError::ParseError("unexpected end of PSBT".into()));
824    }
825    decode_compact_size_with_consumed(&data[pos..])
826}
827
828fn decode_compact_size_with_consumed(data: &[u8]) -> Result<(usize, usize), SignerError> {
829    let mut offset = 0usize;
830    let value = encoding::read_compact_size(data, &mut offset)
831        .map_err(|e| SignerError::ParseError(format!("compact_size: {e}")))?;
832    let value = usize::try_from(value).map_err(|_| {
833        SignerError::ParseError("compact_size: value exceeds platform usize".into())
834    })?;
835    Ok((value, offset))
836}
837
838// ═══════════════════════════════════════════════════════════════════
839// Tests
840// ═══════════════════════════════════════════════════════════════════
841
842#[cfg(test)]
843#[allow(clippy::unwrap_used, clippy::expect_used)]
844mod tests {
845    use super::*;
846
847    fn minimal_psbt_with_counts(input_count: usize, output_count: usize) -> Vec<u8> {
848        let mut data = Vec::new();
849        data.extend_from_slice(b"psbt\xFF");
850        write_kv(&mut data, &[global_key::VERSION], &2u32.to_le_bytes());
851        write_kv(&mut data, &[global_key::TX_VERSION], &2u32.to_le_bytes());
852        write_kv(
853            &mut data,
854            &[global_key::FALLBACK_LOCKTIME],
855            &0u32.to_le_bytes(),
856        );
857        write_kv(
858            &mut data,
859            &[global_key::INPUT_COUNT],
860            &compact_size(input_count),
861        );
862        write_kv(
863            &mut data,
864            &[global_key::OUTPUT_COUNT],
865            &compact_size(output_count),
866        );
867        data.push(0x00);
868        data
869    }
870
871    // ─── Construction ────────────────────────────────────────────
872
873    #[test]
874    fn test_psbtv2_new_defaults() {
875        let psbt = PsbtV2::new();
876        assert_eq!(psbt.tx_version, 2);
877        assert_eq!(psbt.fallback_locktime, 0);
878        assert_eq!(psbt.modifiable.to_byte(), 0);
879        assert!(psbt.inputs.is_empty());
880        assert!(psbt.outputs.is_empty());
881    }
882
883    #[test]
884    fn test_psbtv2_interactive() {
885        let psbt = PsbtV2::new_interactive();
886        assert!(psbt.modifiable.inputs_modifiable());
887        assert!(psbt.modifiable.outputs_modifiable());
888    }
889
890    #[test]
891    fn test_add_input() {
892        let mut psbt = PsbtV2::new();
893        let idx = psbt.add_input(PsbtV2Input::new([0xAA; 32], 0));
894        assert_eq!(idx, 0);
895        assert_eq!(psbt.inputs.len(), 1);
896        assert_eq!(psbt.inputs[0].previous_txid, [0xAA; 32]);
897    }
898
899    #[test]
900    fn test_add_multiple_inputs() {
901        let mut psbt = PsbtV2::new();
902        let i0 = psbt.add_input(PsbtV2Input::new([0x01; 32], 0));
903        let i1 = psbt.add_input(PsbtV2Input::new([0x02; 32], 1));
904        let i2 = psbt.add_input(PsbtV2Input::new([0x03; 32], 2));
905        assert_eq!(i0, 0);
906        assert_eq!(i1, 1);
907        assert_eq!(i2, 2);
908        assert_eq!(psbt.inputs[2].output_index, 2);
909    }
910
911    #[test]
912    fn test_add_output() {
913        let mut psbt = PsbtV2::new();
914        let idx = psbt.add_output(PsbtV2Output::new(50_000, vec![0x00, 0x14]));
915        assert_eq!(idx, 0);
916        assert_eq!(psbt.outputs.len(), 1);
917        assert_eq!(psbt.outputs[0].amount, 50_000);
918    }
919
920    #[test]
921    fn test_input_default_sequence() {
922        let input = PsbtV2Input::new([0; 32], 0);
923        assert_eq!(input.sequence, 0xFFFFFFFF);
924    }
925
926    #[test]
927    fn test_input_with_sequence() {
928        let input = PsbtV2Input::new([0; 32], 0).with_sequence(0xFFFFFFFD);
929        assert_eq!(input.sequence, 0xFFFFFFFD);
930    }
931
932    #[test]
933    fn test_input_rbf_sequence() {
934        // RBF = 0xFFFFFFFD (enables opt-in Replace-By-Fee)
935        let input = PsbtV2Input::new([0; 32], 0).with_sequence(0xFFFFFFFD);
936        assert_eq!(input.sequence, 0xFFFFFFFD);
937        assert_ne!(input.sequence, 0xFFFFFFFF);
938    }
939
940    // ─── Witness UTXO ────────────────────────────────────────────
941
942    #[test]
943    fn test_set_witness_utxo() {
944        let mut input = PsbtV2Input::new([0; 32], 0);
945        let script = vec![0x00, 0x14, 0xAA, 0xBB];
946        input.set_witness_utxo(100_000, &script);
947        assert_eq!(input.extra.len(), 1);
948        assert_eq!(input.extra[0].0, vec![input_key::WITNESS_UTXO]);
949    }
950
951    #[test]
952    fn test_witness_utxo_encoding() {
953        let mut input = PsbtV2Input::new([0; 32], 0);
954        let script = vec![0x00, 0x14, 0xAA, 0xBB];
955        input.set_witness_utxo(100_000, &script);
956        let value = &input.extra[0].1;
957        // First 8 bytes: amount LE
958        let amount = u64::from_le_bytes(value[0..8].try_into().unwrap());
959        assert_eq!(amount, 100_000);
960        // Then compact size + script
961        assert_eq!(value[8], 4); // script length
962        assert_eq!(&value[9..13], &script[..]);
963    }
964
965    #[test]
966    fn test_witness_utxo_encoding_large_script_uses_compact_size() {
967        let mut input = PsbtV2Input::new([0; 32], 0);
968        let script = vec![0xAB; 300];
969        input.set_witness_utxo(100_000, &script);
970        let value = &input.extra[0].1;
971
972        // 8-byte amount + compact-size(300) = 0xFD, 0x2C, 0x01
973        assert_eq!(value[8], 0xFD);
974        assert_eq!(value[9], 0x2C);
975        assert_eq!(value[10], 0x01);
976        assert_eq!(&value[11..], &script[..]);
977    }
978
979    // ─── Computed Locktime ───────────────────────────────────────
980
981    #[test]
982    fn test_computed_locktime_fallback() {
983        let mut psbt = PsbtV2::new();
984        psbt.fallback_locktime = 800_000;
985        assert_eq!(psbt.computed_locktime(), 800_000);
986    }
987
988    #[test]
989    fn test_computed_locktime_no_inputs() {
990        let psbt = PsbtV2::new();
991        assert_eq!(psbt.computed_locktime(), 0);
992    }
993
994    #[test]
995    fn test_computed_locktime_height_priority() {
996        let mut psbt = PsbtV2::new();
997        let mut i1 = PsbtV2Input::new([0; 32], 0);
998        i1.required_time_locktime = Some(1_700_000_000);
999        i1.required_height_locktime = Some(800_000);
1000        psbt.add_input(i1);
1001        assert_eq!(psbt.computed_locktime(), 800_000);
1002    }
1003
1004    #[test]
1005    fn test_computed_locktime_time_only() {
1006        let mut psbt = PsbtV2::new();
1007        let mut i1 = PsbtV2Input::new([0; 32], 0);
1008        i1.required_time_locktime = Some(1_700_000_000);
1009        psbt.add_input(i1);
1010        assert_eq!(psbt.computed_locktime(), 1_700_000_000);
1011    }
1012
1013    #[test]
1014    fn test_computed_locktime_max_across_inputs() {
1015        let mut psbt = PsbtV2::new();
1016        let mut i1 = PsbtV2Input::new([0; 32], 0);
1017        i1.required_height_locktime = Some(100_000);
1018        let mut i2 = PsbtV2Input::new([1; 32], 0);
1019        i2.required_height_locktime = Some(200_000);
1020        psbt.add_input(i1);
1021        psbt.add_input(i2);
1022        assert_eq!(psbt.computed_locktime(), 200_000);
1023    }
1024
1025    #[test]
1026    fn test_computed_locktime_max_time_across_inputs() {
1027        let mut psbt = PsbtV2::new();
1028        let mut i1 = PsbtV2Input::new([0; 32], 0);
1029        i1.required_time_locktime = Some(1_600_000_000);
1030        let mut i2 = PsbtV2Input::new([1; 32], 0);
1031        i2.required_time_locktime = Some(1_700_000_000);
1032        psbt.add_input(i1);
1033        psbt.add_input(i2);
1034        assert_eq!(psbt.computed_locktime(), 1_700_000_000);
1035    }
1036
1037    #[test]
1038    fn test_computed_locktime_inputs_without_timelocks() {
1039        let mut psbt = PsbtV2::new();
1040        psbt.fallback_locktime = 500_000;
1041        psbt.add_input(PsbtV2Input::new([0; 32], 0));
1042        psbt.add_input(PsbtV2Input::new([1; 32], 0));
1043        // No timelocks set → falls back
1044        assert_eq!(psbt.computed_locktime(), 500_000);
1045    }
1046
1047    // ─── Modifiable Flags ────────────────────────────────────────
1048
1049    #[test]
1050    fn test_modifiable_flags_none() {
1051        let f = ModifiableFlags::NONE;
1052        assert!(!f.inputs_modifiable());
1053        assert!(!f.outputs_modifiable());
1054    }
1055
1056    #[test]
1057    fn test_modifiable_flags_inputs_only() {
1058        let f = ModifiableFlags::INPUTS_MODIFIABLE;
1059        assert!(f.inputs_modifiable());
1060        assert!(!f.outputs_modifiable());
1061    }
1062
1063    #[test]
1064    fn test_modifiable_flags_outputs_only() {
1065        let f = ModifiableFlags::OUTPUTS_MODIFIABLE;
1066        assert!(!f.inputs_modifiable());
1067        assert!(f.outputs_modifiable());
1068    }
1069
1070    #[test]
1071    fn test_modifiable_flags_union() {
1072        let f = ModifiableFlags::INPUTS_MODIFIABLE.union(ModifiableFlags::OUTPUTS_MODIFIABLE);
1073        assert!(f.inputs_modifiable());
1074        assert!(f.outputs_modifiable());
1075        assert_eq!(f.to_byte(), 0x03);
1076    }
1077
1078    #[test]
1079    fn test_modifiable_flags_sighash_single() {
1080        let f = ModifiableFlags::HAS_SIGHASH_SINGLE;
1081        assert!(!f.inputs_modifiable());
1082        assert!(!f.outputs_modifiable());
1083        assert_eq!(f.to_byte(), 0x04);
1084    }
1085
1086    #[test]
1087    fn test_modifiable_flags_all() {
1088        let f = ModifiableFlags::INPUTS_MODIFIABLE
1089            .union(ModifiableFlags::OUTPUTS_MODIFIABLE)
1090            .union(ModifiableFlags::HAS_SIGHASH_SINGLE);
1091        assert_eq!(f.to_byte(), 0x07);
1092    }
1093
1094    #[test]
1095    fn test_modifiable_flags_from_byte() {
1096        let f = ModifiableFlags::from_byte(0xFF);
1097        assert!(f.inputs_modifiable());
1098        assert!(f.outputs_modifiable());
1099        assert_eq!(f.to_byte(), 0xFF);
1100    }
1101
1102    // ─── Serialization Round-Trip ────────────────────────────────
1103
1104    #[test]
1105    fn test_serialize_roundtrip() {
1106        let mut psbt = PsbtV2::new();
1107        psbt.tx_version = 2;
1108        psbt.fallback_locktime = 800_000;
1109        psbt.modifiable = ModifiableFlags::INPUTS_MODIFIABLE;
1110
1111        let input = PsbtV2Input::new([0xBB; 32], 1).with_sequence(0xFFFFFFFD);
1112        psbt.add_input(input);
1113
1114        let output = PsbtV2Output::new(50_000, vec![0xCC; 20]);
1115        psbt.add_output(output);
1116
1117        let serialized = psbt.serialize();
1118        let deserialized = PsbtV2::deserialize(&serialized).unwrap();
1119
1120        assert_eq!(deserialized.tx_version, 2);
1121        assert_eq!(deserialized.fallback_locktime, 800_000);
1122        assert!(deserialized.modifiable.inputs_modifiable());
1123        assert!(!deserialized.modifiable.outputs_modifiable());
1124        assert_eq!(deserialized.inputs.len(), 1);
1125        assert_eq!(deserialized.outputs.len(), 1);
1126        assert_eq!(deserialized.inputs[0].previous_txid, [0xBB; 32]);
1127        assert_eq!(deserialized.inputs[0].output_index, 1);
1128        assert_eq!(deserialized.inputs[0].sequence, 0xFFFFFFFD);
1129        assert_eq!(deserialized.outputs[0].amount, 50_000);
1130    }
1131
1132    #[test]
1133    fn test_serialize_empty_psbt_roundtrip() {
1134        let psbt = PsbtV2::new();
1135        let data = psbt.serialize();
1136        let rt = PsbtV2::deserialize(&data).unwrap();
1137        assert_eq!(rt.tx_version, 2);
1138        assert_eq!(rt.inputs.len(), 0);
1139        assert_eq!(rt.outputs.len(), 0);
1140    }
1141
1142    #[test]
1143    fn test_serialize_starts_with_magic() {
1144        let psbt = PsbtV2::new();
1145        let data = psbt.serialize();
1146        assert_eq!(&data[0..5], b"psbt\xFF");
1147    }
1148
1149    #[test]
1150    fn test_serialize_output_script_preserved() {
1151        let script = vec![0x76, 0xA9, 0x14, 0xAA, 0xBB, 0xCC];
1152        let mut psbt = PsbtV2::new();
1153        psbt.add_output(PsbtV2Output::new(1_000_000, script.clone()));
1154        let data = psbt.serialize();
1155        let rt = PsbtV2::deserialize(&data).unwrap();
1156        assert_eq!(rt.outputs[0].script, script);
1157    }
1158
1159    #[test]
1160    fn test_serialize_large_amount() {
1161        let mut psbt = PsbtV2::new();
1162        psbt.add_output(PsbtV2Output::new(21_000_000 * 100_000_000, vec![0x00]));
1163        let data = psbt.serialize();
1164        let rt = PsbtV2::deserialize(&data).unwrap();
1165        assert_eq!(rt.outputs[0].amount, 21_000_000 * 100_000_000);
1166    }
1167
1168    #[test]
1169    fn test_serialize_roundtrip_with_timelocks() {
1170        let mut psbt = PsbtV2::new();
1171        let mut input = PsbtV2Input::new([0xAA; 32], 0);
1172        input.required_time_locktime = Some(1_700_000_000);
1173        input.required_height_locktime = Some(800_000);
1174        psbt.add_input(input);
1175
1176        let data = psbt.serialize();
1177        let rt = PsbtV2::deserialize(&data).unwrap();
1178        assert_eq!(rt.inputs[0].required_time_locktime, Some(1_700_000_000));
1179        assert_eq!(rt.inputs[0].required_height_locktime, Some(800_000));
1180    }
1181
1182    // ─── Deserialization Errors ──────────────────────────────────
1183
1184    #[test]
1185    fn test_deserialize_invalid_magic() {
1186        let result = PsbtV2::deserialize(b"not_a_psbt");
1187        assert!(result.is_err());
1188    }
1189
1190    #[test]
1191    fn test_deserialize_too_short() {
1192        let result = PsbtV2::deserialize(b"psbt");
1193        assert!(result.is_err());
1194    }
1195
1196    #[test]
1197    fn test_deserialize_empty() {
1198        let result = PsbtV2::deserialize(b"");
1199        assert!(result.is_err());
1200    }
1201
1202    #[test]
1203    fn test_deserialize_wrong_version() {
1204        // Construct a valid-looking PSBT but with version=1
1205        let psbt = PsbtV2::new();
1206        let data = psbt.serialize();
1207        // Manually patch the version to 1
1208        let mut patched = data.clone();
1209        // Find version KV and change value
1210        // Version is first KV after magic: key_len=1, key=0xFB, val_len=4, val=02000000
1211        // Position: 5 (magic) + 1 (key_len) + 1(key) + 1(val_len) = 8, then 4 bytes value
1212        patched[8] = 1; // change version to 1
1213        let result = PsbtV2::deserialize(&patched);
1214        assert!(result.is_err());
1215    }
1216
1217    #[test]
1218    fn test_deserialize_rejects_tx_modifiable_invalid_len() {
1219        let mut data = Vec::new();
1220        data.extend_from_slice(b"psbt\xFF");
1221        write_kv(&mut data, &[global_key::VERSION], &2u32.to_le_bytes());
1222        write_kv(&mut data, &[global_key::TX_VERSION], &2u32.to_le_bytes());
1223        write_kv(
1224            &mut data,
1225            &[global_key::FALLBACK_LOCKTIME],
1226            &0u32.to_le_bytes(),
1227        );
1228        write_kv(&mut data, &[global_key::INPUT_COUNT], &compact_size(0));
1229        write_kv(&mut data, &[global_key::OUTPUT_COUNT], &compact_size(0));
1230        write_kv(&mut data, &[global_key::TX_MODIFIABLE], &[0x01, 0x02]); // invalid length
1231        data.push(0x00); // end global map
1232
1233        let result = PsbtV2::deserialize(&data);
1234        assert!(result.is_err());
1235    }
1236
1237    #[test]
1238    fn test_deserialize_rejects_missing_tx_version() {
1239        let mut data = Vec::new();
1240        data.extend_from_slice(b"psbt\xFF");
1241        write_kv(&mut data, &[global_key::VERSION], &2u32.to_le_bytes());
1242        write_kv(
1243            &mut data,
1244            &[global_key::FALLBACK_LOCKTIME],
1245            &0u32.to_le_bytes(),
1246        );
1247        write_kv(&mut data, &[global_key::INPUT_COUNT], &compact_size(0));
1248        write_kv(&mut data, &[global_key::OUTPUT_COUNT], &compact_size(0));
1249        data.push(0x00); // end global map
1250
1251        let result = PsbtV2::deserialize(&data);
1252        assert!(result.is_err());
1253    }
1254
1255    #[test]
1256    fn test_deserialize_rejects_duplicate_global_key() {
1257        let mut data = Vec::new();
1258        data.extend_from_slice(b"psbt\xFF");
1259        write_kv(&mut data, &[global_key::VERSION], &2u32.to_le_bytes());
1260        write_kv(&mut data, &[global_key::TX_VERSION], &2u32.to_le_bytes());
1261        write_kv(&mut data, &[global_key::TX_VERSION], &2u32.to_le_bytes()); // duplicate
1262        write_kv(
1263            &mut data,
1264            &[global_key::FALLBACK_LOCKTIME],
1265            &0u32.to_le_bytes(),
1266        );
1267        write_kv(&mut data, &[global_key::INPUT_COUNT], &compact_size(0));
1268        write_kv(&mut data, &[global_key::OUTPUT_COUNT], &compact_size(0));
1269        data.push(0x00);
1270
1271        let result = PsbtV2::deserialize(&data);
1272        assert!(result.is_err());
1273    }
1274
1275    #[test]
1276    fn test_deserialize_rejects_unterminated_global_map() {
1277        let mut data = Vec::new();
1278        data.extend_from_slice(b"psbt\xFF");
1279        write_kv(&mut data, &[global_key::VERSION], &2u32.to_le_bytes());
1280        write_kv(&mut data, &[global_key::TX_VERSION], &2u32.to_le_bytes());
1281        write_kv(
1282            &mut data,
1283            &[global_key::FALLBACK_LOCKTIME],
1284            &0u32.to_le_bytes(),
1285        );
1286        write_kv(&mut data, &[global_key::INPUT_COUNT], &compact_size(0));
1287        write_kv(&mut data, &[global_key::OUTPUT_COUNT], &compact_size(0));
1288        // No 0x00 global-map terminator.
1289        assert!(PsbtV2::deserialize(&data).is_err());
1290    }
1291
1292    #[test]
1293    fn test_deserialize_rejects_input_missing_previous_txid() {
1294        let mut data = minimal_psbt_with_counts(1, 0);
1295        write_kv(&mut data, &[input_key::OUTPUT_INDEX], &0u32.to_le_bytes());
1296        data.push(0x00);
1297
1298        let result = PsbtV2::deserialize(&data);
1299        assert!(result.is_err());
1300    }
1301
1302    #[test]
1303    fn test_deserialize_rejects_input_missing_output_index() {
1304        let mut data = minimal_psbt_with_counts(1, 0);
1305        write_kv(&mut data, &[input_key::PREVIOUS_TXID], &[0xAA; 32]);
1306        data.push(0x00);
1307
1308        let result = PsbtV2::deserialize(&data);
1309        assert!(result.is_err());
1310    }
1311
1312    #[test]
1313    fn test_deserialize_rejects_duplicate_input_key() {
1314        let mut data = minimal_psbt_with_counts(1, 0);
1315        write_kv(&mut data, &[input_key::PREVIOUS_TXID], &[0xAA; 32]);
1316        write_kv(&mut data, &[input_key::PREVIOUS_TXID], &[0xBB; 32]); // duplicate
1317        write_kv(&mut data, &[input_key::OUTPUT_INDEX], &0u32.to_le_bytes());
1318        data.push(0x00);
1319
1320        let result = PsbtV2::deserialize(&data);
1321        assert!(result.is_err());
1322    }
1323
1324    #[test]
1325    fn test_deserialize_rejects_output_missing_amount() {
1326        let mut data = minimal_psbt_with_counts(0, 1);
1327        write_kv(&mut data, &[output_key::SCRIPT], &[0x51]);
1328        data.push(0x00);
1329
1330        let result = PsbtV2::deserialize(&data);
1331        assert!(result.is_err());
1332    }
1333
1334    #[test]
1335    fn test_deserialize_rejects_output_missing_script() {
1336        let mut data = minimal_psbt_with_counts(0, 1);
1337        write_kv(&mut data, &[output_key::AMOUNT], &50_000u64.to_le_bytes());
1338        data.push(0x00);
1339
1340        let result = PsbtV2::deserialize(&data);
1341        assert!(result.is_err());
1342    }
1343
1344    #[test]
1345    fn test_deserialize_rejects_duplicate_output_key() {
1346        let mut data = minimal_psbt_with_counts(0, 1);
1347        write_kv(&mut data, &[output_key::AMOUNT], &50_000u64.to_le_bytes());
1348        write_kv(&mut data, &[output_key::AMOUNT], &60_000u64.to_le_bytes()); // duplicate
1349        write_kv(&mut data, &[output_key::SCRIPT], &[0x51]);
1350        data.push(0x00);
1351
1352        let result = PsbtV2::deserialize(&data);
1353        assert!(result.is_err());
1354    }
1355
1356    // ─── Multiple Inputs/Outputs ─────────────────────────────────
1357
1358    #[test]
1359    fn test_roundtrip_multiple_io() {
1360        let mut psbt = PsbtV2::new();
1361        for i in 0..3u8 {
1362            psbt.add_input(PsbtV2Input::new([i; 32], i as u32));
1363            psbt.add_output(PsbtV2Output::new(
1364                (i as u64 + 1) * 10_000,
1365                vec![0x00, 0x14, i],
1366            ));
1367        }
1368
1369        let data = psbt.serialize();
1370        let rt = PsbtV2::deserialize(&data).unwrap();
1371        assert_eq!(rt.inputs.len(), 3);
1372        assert_eq!(rt.outputs.len(), 3);
1373        assert_eq!(rt.inputs[2].previous_txid, [2; 32]);
1374        assert_eq!(rt.outputs[1].amount, 20_000);
1375    }
1376
1377    #[test]
1378    fn test_roundtrip_10_inputs() {
1379        let mut psbt = PsbtV2::new();
1380        for i in 0..10u8 {
1381            psbt.add_input(PsbtV2Input::new([i; 32], i as u32));
1382        }
1383        psbt.add_output(PsbtV2Output::new(1_000_000, vec![0x00]));
1384
1385        let data = psbt.serialize();
1386        let rt = PsbtV2::deserialize(&data).unwrap();
1387        assert_eq!(rt.inputs.len(), 10);
1388        for i in 0..10u8 {
1389            assert_eq!(rt.inputs[i as usize].previous_txid, [i; 32]);
1390            assert_eq!(rt.inputs[i as usize].output_index, i as u32);
1391        }
1392    }
1393
1394    #[test]
1395    fn test_roundtrip_asymmetric_io() {
1396        // 5 inputs, 2 outputs
1397        let mut psbt = PsbtV2::new();
1398        for i in 0..5u8 {
1399            psbt.add_input(PsbtV2Input::new([i; 32], 0));
1400        }
1401        for i in 0..2u8 {
1402            psbt.add_output(PsbtV2Output::new(50_000, vec![i]));
1403        }
1404
1405        let data = psbt.serialize();
1406        let rt = PsbtV2::deserialize(&data).unwrap();
1407        assert_eq!(rt.inputs.len(), 5);
1408        assert_eq!(rt.outputs.len(), 2);
1409    }
1410
1411    // ─── Compact Size ────────────────────────────────────────────
1412
1413    #[test]
1414    fn test_compact_size_small() {
1415        assert_eq!(compact_size(0), vec![0]);
1416        assert_eq!(compact_size(252), vec![252]);
1417    }
1418
1419    #[test]
1420    fn test_compact_size_boundary_253() {
1421        // 253 triggers 0xFD prefix
1422        let cs = compact_size(253);
1423        assert_eq!(cs[0], 0xFD);
1424        assert_eq!(u16::from_le_bytes([cs[1], cs[2]]), 253);
1425    }
1426
1427    #[test]
1428    fn test_compact_size_medium() {
1429        let cs = compact_size(300);
1430        assert_eq!(cs[0], 0xFD);
1431        assert_eq!(u16::from_le_bytes([cs[1], cs[2]]), 300);
1432    }
1433
1434    #[test]
1435    fn test_compact_size_max_u16() {
1436        let cs = compact_size(65535);
1437        assert_eq!(cs[0], 0xFD);
1438        assert_eq!(u16::from_le_bytes([cs[1], cs[2]]), 65535);
1439    }
1440
1441    #[test]
1442    fn test_compact_size_large() {
1443        let cs = compact_size(70000);
1444        assert_eq!(cs[0], 0xFE);
1445        assert_eq!(u32::from_le_bytes([cs[1], cs[2], cs[3], cs[4]]), 70000);
1446    }
1447
1448    #[test]
1449    #[cfg(target_pointer_width = "64")]
1450    fn test_compact_size_very_large_uses_u64_variant() {
1451        let n = 0x1_0000_0000usize;
1452        let cs = compact_size(n);
1453        assert_eq!(cs[0], 0xFF);
1454        assert_eq!(
1455            u64::from_le_bytes([cs[1], cs[2], cs[3], cs[4], cs[5], cs[6], cs[7], cs[8]]),
1456            n as u64
1457        );
1458    }
1459
1460    // ─── Read Compact Size ───────────────────────────────────────
1461
1462    #[test]
1463    fn test_read_compact_size_small() {
1464        assert_eq!(read_compact_size(&[42]).unwrap(), 42);
1465        assert_eq!(read_compact_size(&[0]).unwrap(), 0);
1466        assert_eq!(read_compact_size(&[252]).unwrap(), 252);
1467    }
1468
1469    #[test]
1470    fn test_read_compact_size_medium() {
1471        let data = [0xFD, 0x00, 0x01]; // 256
1472        assert_eq!(read_compact_size(&data).unwrap(), 256);
1473    }
1474
1475    #[test]
1476    fn test_read_compact_size_rejects_non_canonical() {
1477        // 252 must use single-byte encoding, not 0xFD form
1478        let data = [0xFD, 0xFC, 0x00];
1479        assert!(read_compact_size(&data).is_err());
1480    }
1481
1482    #[test]
1483    fn test_read_compact_size_rejects_non_canonical_u32_form() {
1484        // 65535 must use 0xFD form, not 0xFE form.
1485        let data = [0xFE, 0xFF, 0xFF, 0x00, 0x00];
1486        assert!(read_compact_size(&data).is_err());
1487    }
1488
1489    #[test]
1490    fn test_read_compact_size_rejects_non_canonical_u64_form() {
1491        // 0xFFFFFFFF must use 0xFE form, not 0xFF form.
1492        let data = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00];
1493        assert!(read_compact_size(&data).is_err());
1494    }
1495
1496    #[test]
1497    fn test_read_compact_size_rejects_trailing_bytes() {
1498        let data = [0x01, 0x00];
1499        assert!(read_compact_size(&data).is_err());
1500    }
1501
1502    #[test]
1503    fn test_read_compact_size_empty() {
1504        assert!(read_compact_size(&[]).is_err());
1505    }
1506
1507    #[test]
1508    fn test_read_compact_size_at_reports_consumed_bytes() {
1509        let data = [0xFD, 0x00, 0x01, 0xAA];
1510        let (v1, c1) = read_compact_size_at(&data, 0).unwrap();
1511        assert_eq!(v1, 256);
1512        assert_eq!(c1, 3);
1513        let (v2, c2) = read_compact_size_at(&data, 3).unwrap();
1514        assert_eq!(v2, 0xAA);
1515        assert_eq!(c2, 1);
1516    }
1517
1518    #[test]
1519    fn test_read_compact_size_at_rejects_non_canonical() {
1520        let data = [0xFE, 0x01, 0x00, 0x00, 0x00];
1521        assert!(read_compact_size_at(&data, 0).is_err());
1522    }
1523
1524    // ─── KV Helpers ──────────────────────────────────────────────
1525
1526    #[test]
1527    fn test_write_kv_roundtrip() {
1528        let mut buf = Vec::new();
1529        write_kv(&mut buf, &[0x42], &[0xAA, 0xBB, 0xCC]);
1530        let (key, val, consumed) = read_kv(&buf).unwrap();
1531        assert_eq!(key, vec![0x42]);
1532        assert_eq!(val, vec![0xAA, 0xBB, 0xCC]);
1533        assert_eq!(consumed, buf.len());
1534    }
1535
1536    #[test]
1537    fn test_write_kv_empty_value() {
1538        let mut buf = Vec::new();
1539        write_kv(&mut buf, &[0x01], &[]);
1540        let (key, val, consumed) = read_kv(&buf).unwrap();
1541        assert_eq!(key, vec![0x01]);
1542        assert!(val.is_empty());
1543        assert_eq!(consumed, buf.len());
1544    }
1545
1546    // ─── Input Serialization ────────────────────────────────────
1547
1548    #[test]
1549    fn test_input_serialize_basic() {
1550        let input = PsbtV2Input::new([0xAA; 32], 5);
1551        let data = input.serialize();
1552        // Should end with terminator 0x00
1553        assert_eq!(data[data.len() - 1], 0x00);
1554    }
1555
1556    #[test]
1557    fn test_input_serialize_with_witness_utxo() {
1558        let mut input = PsbtV2Input::new([0; 32], 0);
1559        input.set_witness_utxo(50_000, &[0x00, 0x14]);
1560        let data = input.serialize();
1561        assert!(data.len() > 40); // must be bigger than just basic fields
1562    }
1563
1564    // ─── Output Serialization ───────────────────────────────────
1565
1566    #[test]
1567    fn test_output_serialize_basic() {
1568        let output = PsbtV2Output::new(100_000, vec![0xAA, 0xBB]);
1569        let data = output.serialize();
1570        assert_eq!(data[data.len() - 1], 0x00);
1571    }
1572
1573    // ─── Default Trait ───────────────────────────────────────────
1574
1575    #[test]
1576    fn test_default_trait() {
1577        let psbt = PsbtV2::default();
1578        assert_eq!(psbt.tx_version, 2);
1579    }
1580
1581    // ─── Global Key Constants ────────────────────────────────────
1582
1583    #[test]
1584    fn test_global_key_values() {
1585        assert_eq!(global_key::TX_VERSION, 0x02);
1586        assert_eq!(global_key::FALLBACK_LOCKTIME, 0x03);
1587        assert_eq!(global_key::INPUT_COUNT, 0x04);
1588        assert_eq!(global_key::OUTPUT_COUNT, 0x05);
1589        assert_eq!(global_key::TX_MODIFIABLE, 0x06);
1590        assert_eq!(global_key::VERSION, 0xFB);
1591    }
1592
1593    #[test]
1594    fn test_input_key_values() {
1595        assert_eq!(input_key::PREVIOUS_TXID, 0x0E);
1596        assert_eq!(input_key::OUTPUT_INDEX, 0x0F);
1597        assert_eq!(input_key::SEQUENCE, 0x10);
1598        assert_eq!(input_key::REQUIRED_TIME_LOCKTIME, 0x11);
1599        assert_eq!(input_key::REQUIRED_HEIGHT_LOCKTIME, 0x12);
1600        assert_eq!(input_key::NON_WITNESS_UTXO, 0x00);
1601        assert_eq!(input_key::WITNESS_UTXO, 0x01);
1602    }
1603
1604    #[test]
1605    fn test_output_key_values() {
1606        assert_eq!(output_key::AMOUNT, 0x03);
1607        assert_eq!(output_key::SCRIPT, 0x04);
1608        assert_eq!(output_key::REDEEM_SCRIPT, 0x00);
1609        assert_eq!(output_key::WITNESS_SCRIPT, 0x01);
1610    }
1611}