Skip to main content

blvm_sdk/governance/
psbt.rs

1//! BIP174: Partially Signed Bitcoin Transaction (PSBT)
2//!
3//! Specification: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
4//!
5//! PSBT format enables multi-party transaction signing without exposing private keys.
6//! Critical for hardware wallet support and transaction coordination.
7
8use crate::governance::error::{GovernanceError, GovernanceResult};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12/// Raw PSBT map (BIP174 key-value pairs as byte blobs).
13type PsbtRawMap = HashMap<Vec<u8>, Vec<u8>>;
14
15/// PSBT magic bytes: 0x70736274 ("psbt")
16pub const PSBT_MAGIC: [u8; 4] = [0x70, 0x73, 0x62, 0x74];
17
18/// PSBT separator: 0xff
19pub const PSBT_SEPARATOR: u8 = 0xff;
20
21/// PSBT global map key types
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum PsbtGlobalKey {
24    /// Unsigned transaction (required)
25    UnsignedTx = 0x00,
26    /// Extended public key (BIP32)
27    Xpub = 0x01,
28    /// Version number
29    Version = 0xfb,
30    /// Proprietary data
31    Proprietary = 0xfc,
32}
33
34/// PSBT input map key types
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub enum PsbtInputKey {
37    /// Non-witness UTXO
38    NonWitnessUtxo = 0x00,
39    /// Witness UTXO
40    WitnessUtxo = 0x01,
41    /// Partial signature
42    PartialSig = 0x02,
43    /// Sighash type
44    SighashType = 0x03,
45    /// Redeem script
46    RedeemScript = 0x04,
47    /// Witness script
48    WitnessScript = 0x05,
49    /// BIP32 derivation path
50    Bip32Derivation = 0x06,
51    /// Final script sig
52    FinalScriptSig = 0x07,
53    /// Final script witness
54    FinalScriptWitness = 0x08,
55    /// Proprietary data
56    Proprietary = 0xfc,
57}
58
59/// PSBT output map key types
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
61pub enum PsbtOutputKey {
62    /// Redeem script
63    RedeemScript = 0x00,
64    /// Witness script
65    WitnessScript = 0x01,
66    /// BIP32 derivation path
67    Bip32Derivation = 0x02,
68    /// Proprietary data
69    Proprietary = 0xfc,
70}
71
72/// BIP32 derivation path entry
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74pub struct Bip32Derivation {
75    /// Public key (33 bytes compressed or 65 bytes uncompressed)
76    pub pubkey: Vec<u8>,
77    /// Derivation path
78    pub path: Vec<u32>,
79    /// Master key fingerprint (4 bytes)
80    pub master_fingerprint: [u8; 4],
81}
82
83/// Partial signature entry
84#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct PartialSignature {
86    /// Public key
87    pub pubkey: Vec<u8>,
88    /// Signature
89    pub signature: Vec<u8>,
90}
91
92/// Sighash type
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum SighashType {
95    /// SIGHASH_ALL
96    All = 0x01,
97    /// SIGHASH_NONE
98    None = 0x02,
99    /// SIGHASH_SINGLE
100    Single = 0x03,
101    /// SIGHASH_ALL | SIGHASH_ANYONECANPAY
102    AllAnyoneCanPay = 0x81,
103    /// SIGHASH_NONE | SIGHASH_ANYONECANPAY
104    NoneAnyoneCanPay = 0x82,
105    /// SIGHASH_SINGLE | SIGHASH_ANYONECANPAY
106    SingleAnyoneCanPay = 0x83,
107}
108
109impl SighashType {
110    /// Parse sighash type from byte
111    pub fn from_byte(byte: u8) -> Option<Self> {
112        match byte {
113            0x01 => Some(SighashType::All),
114            0x02 => Some(SighashType::None),
115            0x03 => Some(SighashType::Single),
116            0x81 => Some(SighashType::AllAnyoneCanPay),
117            0x82 => Some(SighashType::NoneAnyoneCanPay),
118            0x83 => Some(SighashType::SingleAnyoneCanPay),
119            _ => None,
120        }
121    }
122
123    /// Get byte representation
124    pub fn to_byte(self) -> u8 {
125        self as u8
126    }
127}
128
129/// Partially Signed Bitcoin Transaction
130#[derive(Debug, Clone, PartialEq, Eq)]
131pub struct PartiallySignedTransaction {
132    /// Global map (unsigned transaction, xpubs, etc.)
133    pub global: HashMap<Vec<u8>, Vec<u8>>,
134    /// Input maps (one per input)
135    pub inputs: Vec<HashMap<Vec<u8>, Vec<u8>>>,
136    /// Output maps (one per output)
137    pub outputs: Vec<HashMap<Vec<u8>, Vec<u8>>>,
138    /// Version (default: 0)
139    pub version: u8,
140}
141
142impl PartiallySignedTransaction {
143    /// Create a new PSBT from an unsigned transaction
144    pub fn new(unsigned_tx: &[u8]) -> GovernanceResult<Self> {
145        let mut global = HashMap::new();
146        global.insert(vec![PsbtGlobalKey::UnsignedTx as u8], unsigned_tx.to_vec());
147        global.insert(vec![PsbtGlobalKey::Version as u8], vec![0x00]); // Version 0
148
149        Ok(PartiallySignedTransaction {
150            global,
151            inputs: Vec::new(),
152            outputs: Vec::new(),
153            version: 0,
154        })
155    }
156
157    /// Add input data
158    pub fn add_input_data(
159        &mut self,
160        input_index: usize,
161        key: Vec<u8>,
162        value: Vec<u8>,
163    ) -> GovernanceResult<()> {
164        if input_index >= self.inputs.len() {
165            // Extend inputs vector if needed
166            while self.inputs.len() <= input_index {
167                self.inputs.push(HashMap::new());
168            }
169        }
170        self.inputs[input_index].insert(key, value);
171        Ok(())
172    }
173
174    /// Add output data
175    pub fn add_output_data(
176        &mut self,
177        output_index: usize,
178        key: Vec<u8>,
179        value: Vec<u8>,
180    ) -> GovernanceResult<()> {
181        if output_index >= self.outputs.len() {
182            // Extend outputs vector if needed
183            while self.outputs.len() <= output_index {
184                self.outputs.push(HashMap::new());
185            }
186        }
187        self.outputs[output_index].insert(key, value);
188        Ok(())
189    }
190
191    /// Add partial signature to an input
192    pub fn add_partial_signature(
193        &mut self,
194        input_index: usize,
195        pubkey: Vec<u8>,
196        signature: Vec<u8>,
197    ) -> GovernanceResult<()> {
198        // Format: <pubkey_len><pubkey><sig_len><signature>
199        let mut key = vec![PsbtInputKey::PartialSig as u8];
200        key.extend_from_slice(&pubkey);
201
202        let mut value = Vec::with_capacity(1 + signature.len());
203        value.push(signature.len() as u8);
204        value.extend_from_slice(&signature);
205
206        self.add_input_data(input_index, key, value)
207    }
208
209    /// Add BIP32 derivation path to an input
210    pub fn add_bip32_derivation(
211        &mut self,
212        input_index: usize,
213        pubkey: Vec<u8>,
214        derivation: Bip32Derivation,
215    ) -> GovernanceResult<()> {
216        let mut key = vec![PsbtInputKey::Bip32Derivation as u8];
217        key.extend_from_slice(&pubkey);
218
219        // Serialize derivation: <master_fp(4)><path_len><path>
220        let mut value = Vec::new();
221        value.extend_from_slice(&derivation.master_fingerprint);
222        value.push(derivation.path.len() as u8);
223        for &index in &derivation.path {
224            value.extend_from_slice(&index.to_be_bytes());
225        }
226
227        self.add_input_data(input_index, key, value)
228    }
229
230    /// Set sighash type for an input
231    pub fn set_sighash_type(
232        &mut self,
233        input_index: usize,
234        sighash_type: SighashType,
235    ) -> GovernanceResult<()> {
236        let key = vec![PsbtInputKey::SighashType as u8];
237        let value = vec![sighash_type.to_byte()];
238        self.add_input_data(input_index, key, value)
239    }
240
241    /// Check if PSBT is finalized (all inputs have final script sig/witness)
242    pub fn is_finalized(&self) -> bool {
243        for input_map in &self.inputs {
244            let has_final_sig = input_map.contains_key(&vec![PsbtInputKey::FinalScriptSig as u8]);
245            let has_final_witness =
246                input_map.contains_key(&vec![PsbtInputKey::FinalScriptWitness as u8]);
247
248            if !has_final_sig && !has_final_witness {
249                return false;
250            }
251        }
252        true
253    }
254
255    /// Extract final transaction (throws error if not finalized)
256    pub fn extract_transaction(&self) -> GovernanceResult<Vec<u8>> {
257        if !self.is_finalized() {
258            return Err(GovernanceError::InvalidInput(
259                "PSBT is not finalized".to_string(),
260            ));
261        }
262
263        // Get unsigned transaction from global map
264        let unsigned_tx_key = vec![PsbtGlobalKey::UnsignedTx as u8];
265        let unsigned_tx = self.global.get(&unsigned_tx_key).ok_or_else(|| {
266            GovernanceError::InvalidInput("Missing unsigned transaction".to_string())
267        })?;
268
269        // Build final transaction by combining unsigned tx with final scripts
270        // This is a simplified version - full implementation would parse transaction
271        // and insert final script sig/witness data
272
273        Ok(unsigned_tx.clone())
274    }
275
276    /// Serialize PSBT to bytes
277    pub fn serialize(&self) -> GovernanceResult<Vec<u8>> {
278        let mut result = Vec::new();
279
280        // Magic bytes
281        result.extend_from_slice(&PSBT_MAGIC);
282        result.push(PSBT_SEPARATOR);
283
284        // Global map
285        serialize_map(&mut result, &self.global)?;
286
287        // Separator between global and inputs
288        result.push(PSBT_SEPARATOR);
289
290        // Input maps
291        for input_map in &self.inputs {
292            serialize_map(&mut result, input_map)?;
293            result.push(PSBT_SEPARATOR);
294        }
295
296        // Output maps
297        for output_map in &self.outputs {
298            serialize_map(&mut result, output_map)?;
299            result.push(PSBT_SEPARATOR);
300        }
301
302        Ok(result)
303    }
304
305    /// Deserialize PSBT from bytes
306    pub fn deserialize(data: &[u8]) -> GovernanceResult<Self> {
307        if data.len() < 5 || data[..4] != PSBT_MAGIC || data[4] != PSBT_SEPARATOR {
308            return Err(GovernanceError::InvalidInput(
309                "Invalid PSBT magic bytes".to_string(),
310            ));
311        }
312
313        let mut offset = 5;
314
315        // Parse global map
316        let (global, new_offset) = deserialize_map(&data[offset..])?;
317        offset += new_offset;
318
319        // Skip separator
320        if offset >= data.len() || data[offset] != PSBT_SEPARATOR {
321            return Err(GovernanceError::InvalidInput(
322                "Missing separator after global map".to_string(),
323            ));
324        }
325        offset += 1;
326
327        // Parse input maps
328        let mut inputs = Vec::new();
329        // Determine number of inputs from unsigned transaction
330        // For now, parse until we hit output separator or end
331        while offset < data.len() && data[offset] != PSBT_SEPARATOR {
332            let (input_map, new_offset) = deserialize_map(&data[offset..])?;
333            inputs.push(input_map);
334            offset += new_offset;
335
336            // Skip separator
337            if offset < data.len() && data[offset] == PSBT_SEPARATOR {
338                offset += 1;
339                break; // Separator indicates start of outputs
340            }
341        }
342
343        // Parse output maps
344        let mut outputs = Vec::new();
345        while offset < data.len() {
346            if data[offset] == PSBT_SEPARATOR && offset + 1 >= data.len() {
347                break; // Final separator
348            }
349            let (output_map, new_offset) = deserialize_map(&data[offset..])?;
350            outputs.push(output_map);
351            offset += new_offset;
352
353            if offset < data.len() && data[offset] == PSBT_SEPARATOR {
354                offset += 1;
355            }
356        }
357
358        // Extract version
359        let version_key = vec![PsbtGlobalKey::Version as u8];
360        let version = global
361            .get(&version_key)
362            .and_then(|v| v.first().copied())
363            .unwrap_or(0);
364
365        Ok(PartiallySignedTransaction {
366            global,
367            inputs,
368            outputs,
369            version,
370        })
371    }
372}
373
374/// Serialize a key-value map (CompactSize encoding)
375fn serialize_map(result: &mut Vec<u8>, map: &HashMap<Vec<u8>, Vec<u8>>) -> GovernanceResult<()> {
376    for (key, value) in map {
377        // Key length (compact size)
378        write_compact_size(result, key.len())?;
379        result.extend_from_slice(key);
380
381        // Value length (compact size)
382        write_compact_size(result, value.len())?;
383        result.extend_from_slice(value);
384    }
385
386    // End marker: 0x00
387    result.push(0x00);
388
389    Ok(())
390}
391
392/// Deserialize a key-value map
393fn deserialize_map(data: &[u8]) -> GovernanceResult<(PsbtRawMap, usize)> {
394    let mut map = HashMap::new();
395    let mut offset = 0;
396
397    while offset < data.len() {
398        // Check for end marker
399        if data[offset] == 0x00 {
400            offset += 1;
401            break;
402        }
403
404        // Read key (S-013: BIP174-style limits to prevent OOM)
405        const MAX_PSBT_KEY_LEN: usize = 520;
406        const MAX_PSBT_VALUE_LEN: usize = 520_000;
407        let (key_len, len_offset) = read_compact_size(&data[offset..])?;
408        offset += len_offset;
409        if key_len > MAX_PSBT_KEY_LEN {
410            return Err(GovernanceError::InvalidInput(format!(
411                "PSBT key too long: {key_len} bytes (max: {MAX_PSBT_KEY_LEN})"
412            )));
413        }
414
415        if offset + key_len > data.len() {
416            return Err(GovernanceError::InvalidInput(
417                "Invalid key length".to_string(),
418            ));
419        }
420        let key = data[offset..offset + key_len].to_vec();
421        offset += key_len;
422
423        // Read value
424        let (value_len, len_offset) = read_compact_size(&data[offset..])?;
425        offset += len_offset;
426        if value_len > MAX_PSBT_VALUE_LEN {
427            return Err(GovernanceError::InvalidInput(format!(
428                "PSBT value too long: {value_len} bytes (max: {MAX_PSBT_VALUE_LEN})"
429            )));
430        }
431
432        if offset + value_len > data.len() {
433            return Err(GovernanceError::InvalidInput(
434                "Invalid value length".to_string(),
435            ));
436        }
437        let value = data[offset..offset + value_len].to_vec();
438        offset += value_len;
439
440        map.insert(key, value);
441    }
442
443    Ok((map, offset))
444}
445
446/// Write compact size (VarInt encoding)
447fn write_compact_size(result: &mut Vec<u8>, size: usize) -> GovernanceResult<()> {
448    if size < 0xfd {
449        result.push(size as u8);
450    } else if size <= 0xffff {
451        result.push(0xfd);
452        result.extend_from_slice(&(size as u16).to_le_bytes());
453    } else if size <= 0xffffffff {
454        result.push(0xfe);
455        result.extend_from_slice(&(size as u32).to_le_bytes());
456    } else {
457        result.push(0xff);
458        result.extend_from_slice(&(size as u64).to_le_bytes());
459    }
460    Ok(())
461}
462
463/// Read compact size (VarInt decoding)
464fn read_compact_size(data: &[u8]) -> GovernanceResult<(usize, usize)> {
465    if data.is_empty() {
466        return Err(GovernanceError::InvalidInput(
467            "Unexpected end of data".to_string(),
468        ));
469    }
470
471    match data[0] {
472        n if n < 0xfd => Ok((n as usize, 1)),
473        0xfd => {
474            if data.len() < 3 {
475                return Err(GovernanceError::InvalidInput(
476                    "Invalid compact size".to_string(),
477                ));
478            }
479            let value = u16::from_le_bytes([data[1], data[2]]) as usize;
480            Ok((value, 3))
481        }
482        0xfe => {
483            if data.len() < 5 {
484                return Err(GovernanceError::InvalidInput(
485                    "Invalid compact size".to_string(),
486                ));
487            }
488            let value = u32::from_le_bytes([data[1], data[2], data[3], data[4]]) as usize;
489            Ok((value, 5))
490        }
491        0xff => {
492            if data.len() < 9 {
493                return Err(GovernanceError::InvalidInput(
494                    "Invalid compact size".to_string(),
495                ));
496            }
497            let value = u64::from_le_bytes([
498                data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
499            ]) as usize;
500            Ok((value, 9))
501        }
502        _ => Err(GovernanceError::InvalidInput(
503            "Invalid compact size marker".to_string(),
504        )),
505    }
506}
507
508#[cfg(test)]
509mod tests {
510    use super::*;
511
512    #[test]
513    fn test_psbt_creation() {
514        let unsigned_tx = vec![0x01, 0x00, 0x00, 0x00]; // Dummy transaction
515        let psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();
516
517        assert_eq!(psbt.version, 0);
518        assert!(psbt
519            .global
520            .contains_key(&vec![PsbtGlobalKey::UnsignedTx as u8]));
521    }
522
523    #[test]
524    fn test_serialize_deserialize() {
525        let unsigned_tx = vec![0x01, 0x00, 0x00, 0x00];
526        let mut psbt = PartiallySignedTransaction::new(&unsigned_tx).unwrap();
527
528        // Add some data
529        psbt.add_partial_signature(0, vec![0x02; 33], vec![0x30; 72])
530            .unwrap();
531
532        let serialized = psbt.serialize().unwrap();
533        let deserialized = PartiallySignedTransaction::deserialize(&serialized).unwrap();
534
535        assert_eq!(psbt.global, deserialized.global);
536    }
537
538    #[test]
539    fn test_compact_size_encoding() {
540        let mut result = Vec::new();
541        write_compact_size(&mut result, 253).unwrap();
542        assert_eq!(result[0], 0xfd);
543
544        let (value, offset) = read_compact_size(&result).unwrap();
545        assert_eq!(value, 253);
546        assert_eq!(offset, 3);
547    }
548}