From 39fd567b5626e2f7fb8e264d9da4e6865093505a Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Fri, 10 Aug 2018 13:31:58 -0700 Subject: [PATCH] Add Partially Signed Transaction type - Add merging logic for PartiallySignedTransactions - Add (en)decoding logic for PartiallySignedTransaction - Add converting constructor logic from Transaction for PartiallySignedTransaction - Add extracting constructor logic from PartiallySignedTransaction for Transaction Squashed in fixes from stevenroose - Prevent PSBT::extract_tx from panicking - Make PartiallySignedTransaction fields public --- src/util/psbt/mod.rs | 146 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/src/util/psbt/mod.rs b/src/util/psbt/mod.rs index 0b8fbbd1..dd49ba4d 100644 --- a/src/util/psbt/mod.rs +++ b/src/util/psbt/mod.rs @@ -4,6 +4,10 @@ //! defined at https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki //! except we define PSBTs containing non-standard SigHash types as invalid. +use blockdata::script::Script; +use blockdata::transaction::Transaction; +use consensus::encode::{self, Encodable, Decodable, Encoder, Decoder}; + mod error; pub use self::error::Error; @@ -17,6 +21,125 @@ pub mod serialize; mod map; pub use self::map::{Map, Global, Input, Output}; +/// A Partially Signed Transaction. +#[derive(Debug, Clone, PartialEq)] +pub struct PartiallySignedTransaction { + /// The key-value pairs for all global data. + pub global: Global, + /// The corresponding key-value map for each input in the unsigned + /// transaction. + pub inputs: Vec, + /// The corresponding key-value map for each output in the unsigned + /// transaction. + pub outputs: Vec, +} + +impl PartiallySignedTransaction { + /// Create a PartiallySignedTransaction from an unsigned transaction, error + /// if not unsigned + pub fn from_unsigned_tx(tx: Transaction) -> Result { + Ok(PartiallySignedTransaction { + inputs: vec![Default::default(); tx.input.len()], + outputs: vec![Default::default(); tx.output.len()], + global: Global::from_unsigned_tx(tx)?, + }) + } + + /// Extract the Transaction from a PartiallySignedTransaction by filling in + /// the available signature information in place. + pub fn extract_tx(self) -> Transaction { + let mut tx: Transaction = self.global.unsigned_tx; + + for (vin, psbtin) in tx.input.iter_mut().zip(self.inputs.into_iter()) { + vin.script_sig = psbtin.final_script_sig.unwrap_or_else(|| Script::new()); + vin.witness = psbtin.final_script_witness.unwrap_or_else(|| Vec::new()); + } + + tx + } + + /// Attempt to merge with another `PartiallySignedTransaction`. + pub fn merge(&mut self, other: Self) -> Result<(), self::Error> { + self.global.merge(other.global)?; + + for (self_input, other_input) in self.inputs.iter_mut().zip(other.inputs.into_iter()) { + self_input.merge(other_input)?; + } + + for (self_output, other_output) in self.outputs.iter_mut().zip(other.outputs.into_iter()) { + self_output.merge(other_output)?; + } + + Ok(()) + } +} + +impl Encodable for PartiallySignedTransaction { + fn consensus_encode(&self, s: &mut S) -> Result<(), encode::Error> { + b"psbt".consensus_encode(s)?; + + 0xff_u8.consensus_encode(s)?; + + self.global.consensus_encode(s)?; + + for i in &self.inputs { + i.consensus_encode(s)?; + } + + for i in &self.outputs { + i.consensus_encode(s)?; + } + + Ok(()) + } +} + +impl Decodable for PartiallySignedTransaction { + fn consensus_decode(d: &mut D) -> Result { + let magic: [u8; 4] = Decodable::consensus_decode(d)?; + + if *b"psbt" != magic { + return Err(Error::InvalidMagic.into()); + } + + if 0xff_u8 != Decodable::consensus_decode(d)? { + return Err(Error::InvalidSeparator.into()); + } + + let global: Global = Decodable::consensus_decode(d)?; + + let inputs: Vec = { + let inputs_len: usize = (&global.unsigned_tx.input).len(); + + let mut inputs: Vec = Vec::with_capacity(inputs_len); + + for _ in 0..inputs_len { + inputs.push(Decodable::consensus_decode(d)?); + } + + inputs + }; + + let outputs: Vec = { + let outputs_len: usize = (&global.unsigned_tx.output).len(); + + let mut outputs: Vec = Vec::with_capacity(outputs_len); + + for _ in 0..outputs_len { + outputs.push(Decodable::consensus_decode(d)?); + } + + outputs + }; + + Ok(PartiallySignedTransaction { + global: global, + inputs: inputs, + outputs: outputs, + }) + } +} + #[cfg(test)] mod tests { use bitcoin_hashes::hex::FromHex; @@ -37,6 +160,29 @@ mod tests { use util::psbt::map::{Global, Output}; use util::psbt::raw; + use super::PartiallySignedTransaction; + + #[test] + fn trivial_psbt() { + let psbt = PartiallySignedTransaction { + global: Global { + unsigned_tx: Transaction { + version: 2, + lock_time: 0, + input: vec![], + output: vec![], + }, + unknown: HashMap::new(), + }, + inputs: vec![], + outputs: vec![], + }; + assert_eq!( + serialize_hex(&psbt), + "70736274ff01000a0200000000000000000000" + ); + } + #[test] fn serialize_then_deserialize_output() { let secp = &Secp256k1::new();