From 4fa39c4a3ec0bfdadd7b27f8c86a2b41f2123905 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Tue, 7 Aug 2018 23:36:51 -0700 Subject: [PATCH 01/10] Add PSBT-specific Error data type - Implement psbt::Error data type - Implement conversion from psbt::Error to util::Error - Create util::psbt module - Create non-public util::psbt::error module --- src/consensus/encode.rs | 13 ++++++++ src/util/mod.rs | 1 + src/util/psbt/error.rs | 72 +++++++++++++++++++++++++++++++++++++++++ src/util/psbt/mod.rs | 8 +++++ 4 files changed, 94 insertions(+) create mode 100644 src/util/psbt/error.rs create mode 100644 src/util/psbt/mod.rs diff --git a/src/consensus/encode.rs b/src/consensus/encode.rs index 4a5fd54..608ee1d 100644 --- a/src/consensus/encode.rs +++ b/src/consensus/encode.rs @@ -45,6 +45,7 @@ use bitcoin_hashes::{sha256d, Hash as HashTrait}; use secp256k1; use util::base58; +use util::psbt; /// Encoding error #[derive(Debug)] @@ -59,6 +60,8 @@ pub enum Error { ByteOrder(io::Error), /// secp-related error Secp256k1(secp256k1::Error), + /// PSBT-related error + Psbt(psbt::Error), /// Network magic was not expected UnexpectedNetworkMagic { /// The expected network magic @@ -102,6 +105,7 @@ impl fmt::Display for Error { Error::Bech32(ref e) => fmt::Display::fmt(e, f), Error::ByteOrder(ref e) => fmt::Display::fmt(e, f), Error::Secp256k1(ref e) => fmt::Display::fmt(e, f), + Error::Psbt(ref e) => fmt::Display::fmt(e, f), Error::UnexpectedNetworkMagic { expected: ref e, actual: ref a } => write!(f, "{}: expected {}, actual {}", error::Error::description(self), e, a), Error::OversizedVectorAllocation { requested: ref r, max: ref m } => write!(f, "{}: requested {}, maximum {}", error::Error::description(self), r, m), Error::InvalidChecksum { expected: ref e, actual: ref a } => write!(f, "{}: expected {}, actual {}", error::Error::description(self), hex_encode(e), hex_encode(a)), @@ -123,6 +127,7 @@ impl error::Error for Error { Error::Bech32(ref e) => Some(e), Error::ByteOrder(ref e) => Some(e), Error::Secp256k1(ref e) => Some(e), + Error::Psbt(ref e) => Some(e), Error::UnexpectedNetworkMagic { .. } | Error::OversizedVectorAllocation { .. } | Error::InvalidChecksum { .. } @@ -142,6 +147,7 @@ impl error::Error for Error { Error::Bech32(ref e) => e.description(), Error::ByteOrder(ref e) => e.description(), Error::Secp256k1(ref e) => e.description(), + Error::Psbt(ref e) => e.description(), Error::UnexpectedNetworkMagic { .. } => "unexpected network magic", Error::OversizedVectorAllocation { .. } => "allocation of oversized vector requested", Error::InvalidChecksum { .. } => "invalid checksum", @@ -183,6 +189,13 @@ impl From for Error { } } +#[doc(hidden)] +impl From for Error { + fn from(e: psbt::Error) -> Error { + Error::Psbt(e) + } +} + /// Encode an object into a vector pub fn serialize(data: &T) -> Vec where T: Encodable>>, diff --git a/src/util/mod.rs b/src/util/mod.rs index afad43b..c76517a 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -25,6 +25,7 @@ pub mod contracthash; pub mod decimal; pub mod hash; pub mod misc; +pub mod psbt; pub mod uint; use std::{error, fmt}; diff --git a/src/util/psbt/error.rs b/src/util/psbt/error.rs new file mode 100644 index 0000000..34095e2 --- /dev/null +++ b/src/util/psbt/error.rs @@ -0,0 +1,72 @@ +use std::error; +use std::fmt; + +use blockdata::transaction::Transaction; + +/// Ways that a Partially Signed Transaction might fail. +#[derive(Debug)] +pub enum Error { + /// Magic bytes for a PSBT must be the ASCII for "psbt" serialized in most + /// significant byte order. + InvalidMagic, + /// The separator for a PSBT must be `0xff`. + InvalidSeparator, + /// Known keys must be according to spec. + InvalidKey, + /// Keys within key-value map should never be duplicated. + DuplicateKey, + /// The scriptSigs for the unsigned transaction must be empty. + UnsignedTxHasScriptSigs, + /// The scriptWitnesses for the unsigned transaction must be empty. + UnsignedTxHasScriptWitnesses, + /// A PSBT must have an unsigned transaction. + MustHaveUnsignedTx, + /// Signals that there are no more key-value pairs in a key-value map. + NoMorePairs, + /// Attempting to merge with a PSBT describing a different unsigned + /// transaction. + UnexpectedUnsignedTx { + /// Expected + expected: Transaction, + /// Actual + actual: Transaction, + }, + /// Unable to parse as a standard SigHash type. + NonStandardSigHashType(u32), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::UnexpectedUnsignedTx { expected: ref e, actual: ref a } => write!(f, "{}: expected {}, actual {}", error::Error::description(self), e.txid(), a.txid()), + Error::NonStandardSigHashType(ref sht) => write!(f, "{}: {}", error::Error::description(self), sht), + Error::InvalidMagic + | Error::InvalidSeparator + | Error::InvalidKey + | Error::DuplicateKey + | Error::UnsignedTxHasScriptSigs + | Error::UnsignedTxHasScriptWitnesses + | Error::MustHaveUnsignedTx + | Error::NoMorePairs => f.write_str(error::Error::description(self)) + } + } +} + +impl error::Error for Error { + fn description(&self) -> &str { + match *self { + Error::InvalidMagic => "invalid magic", + Error::InvalidSeparator => "invalid separator", + Error::InvalidKey => "invalid key", + Error::DuplicateKey => "duplicate key", + Error::UnsignedTxHasScriptSigs => "the unsigned transaction has script sigs", + Error::UnsignedTxHasScriptWitnesses => "the unsigned transaction has script witnesses", + Error::MustHaveUnsignedTx => { + "partially signed transactions must have an unsigned transaction" + } + Error::NoMorePairs => "no more key-value pairs for this psbt map", + Error::UnexpectedUnsignedTx { .. } => "different unsigned transaction", + Error::NonStandardSigHashType(..) => "non-standard sighash type", + } + } +} diff --git a/src/util/psbt/mod.rs b/src/util/psbt/mod.rs new file mode 100644 index 0000000..f0438e1 --- /dev/null +++ b/src/util/psbt/mod.rs @@ -0,0 +1,8 @@ +//! # Partially Signed Transactions +//! +//! Implementation of BIP174 Partially Signed Bitcoin Transaction Format as +//! defined at https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki +//! except we define PSBTs containing non-standard SigHash types as invalid. + +mod error; +pub use self::error::Error; From 528e39334c134dea6e0781ae86dded8d3cc90b30 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Fri, 10 Aug 2018 08:28:48 -0700 Subject: [PATCH 02/10] Add data types for raw PSBT key-value pairs - Add (en)decoding logic for said data types --- src/util/psbt/error.rs | 13 +++--- src/util/psbt/mod.rs | 23 +++++++++++ src/util/psbt/raw.rs | 94 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 src/util/psbt/raw.rs diff --git a/src/util/psbt/error.rs b/src/util/psbt/error.rs index 34095e2..bb5cf90 100644 --- a/src/util/psbt/error.rs +++ b/src/util/psbt/error.rs @@ -2,6 +2,7 @@ use std::error; use std::fmt; use blockdata::transaction::Transaction; +use util::psbt::raw; /// Ways that a Partially Signed Transaction might fail. #[derive(Debug)] @@ -12,9 +13,9 @@ pub enum Error { /// The separator for a PSBT must be `0xff`. InvalidSeparator, /// Known keys must be according to spec. - InvalidKey, + InvalidKey(raw::Key), /// Keys within key-value map should never be duplicated. - DuplicateKey, + DuplicateKey(raw::Key), /// The scriptSigs for the unsigned transaction must be empty. UnsignedTxHasScriptSigs, /// The scriptWitnesses for the unsigned transaction must be empty. @@ -38,12 +39,12 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { + Error::InvalidKey(ref rkey) => write!(f, "{}: {}", error::Error::description(self), rkey), + Error::DuplicateKey(ref rkey) => write!(f, "{}: {}", error::Error::description(self), rkey), Error::UnexpectedUnsignedTx { expected: ref e, actual: ref a } => write!(f, "{}: expected {}, actual {}", error::Error::description(self), e.txid(), a.txid()), Error::NonStandardSigHashType(ref sht) => write!(f, "{}: {}", error::Error::description(self), sht), Error::InvalidMagic | Error::InvalidSeparator - | Error::InvalidKey - | Error::DuplicateKey | Error::UnsignedTxHasScriptSigs | Error::UnsignedTxHasScriptWitnesses | Error::MustHaveUnsignedTx @@ -57,8 +58,8 @@ impl error::Error for Error { match *self { Error::InvalidMagic => "invalid magic", Error::InvalidSeparator => "invalid separator", - Error::InvalidKey => "invalid key", - Error::DuplicateKey => "duplicate key", + Error::InvalidKey(..) => "invalid key", + Error::DuplicateKey(..) => "duplicate key", Error::UnsignedTxHasScriptSigs => "the unsigned transaction has script sigs", Error::UnsignedTxHasScriptWitnesses => "the unsigned transaction has script witnesses", Error::MustHaveUnsignedTx => { diff --git a/src/util/psbt/mod.rs b/src/util/psbt/mod.rs index f0438e1..421c7cb 100644 --- a/src/util/psbt/mod.rs +++ b/src/util/psbt/mod.rs @@ -6,3 +6,26 @@ mod error; pub use self::error::Error; + +pub mod raw; + +#[cfg(test)] +mod tests { + use consensus::encode::{deserialize, serialize}; + use util::psbt::raw; + + #[test] + fn serialize_then_deserialize_psbtkvpair() { + let expected = raw::Pair { + key: raw::Key { + type_value: 0u8, + key: vec![42u8, 69u8], + }, + value: vec![69u8, 42u8, 4u8], + }; + + let actual: raw::Pair = deserialize(&serialize(&expected)).unwrap(); + + assert_eq!(expected, actual); + } +} diff --git a/src/util/psbt/raw.rs b/src/util/psbt/raw.rs new file mode 100644 index 0000000..04abc70 --- /dev/null +++ b/src/util/psbt/raw.rs @@ -0,0 +1,94 @@ +//! # Raw PSBT Key-Value Pairs +//! +//! Raw PSBT key-value pairs as defined at +//! https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki. + +use std::fmt; + +use consensus::encode::{Decodable, Encodable, VarInt, MAX_VEC_SIZE}; +use consensus::encode::{self, Decoder, Encoder}; +use util::psbt::Error; + +/// A PSBT key in its raw byte form. +#[derive(Debug, PartialEq, Hash, Eq, Clone)] +pub struct Key { + /// The type of this PSBT key. + pub type_value: u8, + /// The key itself in raw byte form. + pub key: Vec, +} + +/// A PSBT key-value pair in its raw byte form. +#[derive(Debug, PartialEq)] +pub struct Pair { + /// The key of this key-value pair. + pub key: Key, + /// The value of this key-value pair in raw byte form. + pub value: Vec, +} + +impl fmt::Display for Key { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use hex; + + write!(f, "type: {:#x}, key: {}", self.type_value, hex::encode(&self.key)) + } +} + +impl Decodable for Key { + fn consensus_decode(d: &mut D) -> Result { + let VarInt(byte_size): VarInt = Decodable::consensus_decode(d)?; + + if byte_size == 0 { + return Err(Error::NoMorePairs.into()); + } + + let key_byte_size: u64 = byte_size - 1; + + if key_byte_size > MAX_VEC_SIZE as u64 { + return Err(encode::Error::OversizedVectorAllocation { requested: key_byte_size as usize, max: MAX_VEC_SIZE } ) + } + + let type_value: u8 = Decodable::consensus_decode(d)?; + + let mut key = Vec::with_capacity(key_byte_size as usize); + for _ in 0..key_byte_size { + key.push(Decodable::consensus_decode(d)?); + } + + Ok(Key { + type_value: type_value, + key: key, + }) + } +} + +impl Encodable for Key { + fn consensus_encode(&self, s: &mut S) -> Result<(), encode::Error> { + VarInt((self.key.len() + 1) as u64).consensus_encode(s)?; + + self.type_value.consensus_encode(s)?; + + for key in &self.key { + key.consensus_encode(s)? + } + + Ok(()) + } +} + +impl Encodable for Pair { + fn consensus_encode(&self, s: &mut S) -> Result<(), encode::Error> { + self.key.consensus_encode(s)?; + self.value.consensus_encode(s) + } +} + +impl Decodable for Pair { + fn consensus_decode(d: &mut D) -> Result { + Ok(Pair { + key: Decodable::consensus_decode(d)?, + value: Decodable::consensus_decode(d)?, + }) + } +} From 2715a6e7779390390d45ae8ce87d0ced8985802f Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Sat, 8 Sep 2018 20:50:45 -0700 Subject: [PATCH 03/10] Add trait for PSBT key-value maps --- src/util/psbt/map/mod.rs | 15 +++++++++++++++ src/util/psbt/mod.rs | 3 +++ 2 files changed, 18 insertions(+) create mode 100644 src/util/psbt/map/mod.rs diff --git a/src/util/psbt/map/mod.rs b/src/util/psbt/map/mod.rs new file mode 100644 index 0000000..621b865 --- /dev/null +++ b/src/util/psbt/map/mod.rs @@ -0,0 +1,15 @@ +use consensus::encode; +use util::psbt; +use util::psbt::raw; + +/// A trait that describes a PSBT key-value map. +pub trait Map { + /// Attempt to insert a key-value pair. + fn insert_pair(&mut self, pair: raw::Pair) -> Result<(), encode::Error>; + + /// Attempt to get all key-value pairs. + fn get_pairs(&self) -> Result, encode::Error>; + + /// Attempt to merge with another key-value map of the same type. + fn merge(&mut self, other: Self) -> Result<(), psbt::Error>; +} diff --git a/src/util/psbt/mod.rs b/src/util/psbt/mod.rs index 421c7cb..7d87d59 100644 --- a/src/util/psbt/mod.rs +++ b/src/util/psbt/mod.rs @@ -9,6 +9,9 @@ pub use self::error::Error; pub mod raw; +mod map; +pub use self::map::Map; + #[cfg(test)] mod tests { use consensus::encode::{deserialize, serialize}; From 115f8c043c2f86cb1a8a074dbed1bb525dc9f353 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Sat, 8 Sep 2018 21:07:07 -0700 Subject: [PATCH 04/10] Add PSBT global data key-value map type - Implement psbt::Map trait for psbt::Global - Add converting constructor logic from Transaction for psbt::Global - Add (en)decoding logic for psbt::Global - Always deserialize unsigned_tx as non-witness - Add trait for PSBT (de)serialization - Implement PSBT (de)serialization trait for relevant psbt::Global types - Add macros for consensus::encode-backed PSBT (de)serialization implementations - Add macro for implementing encoding logic for PSBT key-value maps --- src/util/psbt/macros.rs | 40 +++++++++ src/util/psbt/map/global.rs | 169 ++++++++++++++++++++++++++++++++++++ src/util/psbt/map/mod.rs | 5 ++ src/util/psbt/mod.rs | 53 ++++++++++- src/util/psbt/serialize.rs | 22 +++++ 5 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 src/util/psbt/macros.rs create mode 100644 src/util/psbt/map/global.rs create mode 100644 src/util/psbt/serialize.rs diff --git a/src/util/psbt/macros.rs b/src/util/psbt/macros.rs new file mode 100644 index 0000000..628d8ba --- /dev/null +++ b/src/util/psbt/macros.rs @@ -0,0 +1,40 @@ +macro_rules! impl_psbt_de_serialize { + ($thing:ty) => { + impl_psbt_serialize!($thing); + impl_psbt_deserialize!($thing); + }; +} + +macro_rules! impl_psbt_deserialize { + ($thing:ty) => { + impl ::util::psbt::serialize::Deserialize for $thing { + fn deserialize(bytes: &[u8]) -> Result { + ::consensus::encode::deserialize(&bytes[..]) + } + } + }; +} + +macro_rules! impl_psbt_serialize { + ($thing:ty) => { + impl ::util::psbt::serialize::Serialize for $thing { + fn serialize(&self) -> Vec { + ::consensus::encode::serialize(self) + } + } + }; +} + +macro_rules! impl_psbtmap_consensus_encoding { + ($thing:ty) => { + impl ::consensus::encode::Encodable for $thing { + fn consensus_encode(&self, s: &mut S) -> Result<(), ::consensus::encode::Error> { + for pair in ::util::psbt::Map::get_pairs(self)? { + ::consensus::encode::Encodable::consensus_encode(&pair, s)? + } + + ::consensus::encode::Encodable::consensus_encode(&0x00_u8, s) + } + } + }; +} diff --git a/src/util/psbt/map/global.rs b/src/util/psbt/map/global.rs new file mode 100644 index 0000000..f95f416 --- /dev/null +++ b/src/util/psbt/map/global.rs @@ -0,0 +1,169 @@ +use std::collections::HashMap; +use std::io::Cursor; + +use blockdata::transaction::Transaction; +use consensus::encode::{self, Encodable, Decodable, Decoder}; +use util::psbt::map::Map; +use util::psbt::raw; +use util::psbt; +use util::psbt::Error; + +/// A key-value map for global data. +#[derive(Clone, Debug, PartialEq)] +pub struct Global { + /// The unsigned transaction, scriptSigs and witnesses for each input must be + /// empty. + pub unsigned_tx: Transaction, + /// Unknown global key-value pairs. + pub unknown: HashMap>, +} + +impl Global { + /// Create a Global from an unsigned transaction, error if not unsigned + pub fn from_unsigned_tx(tx: Transaction) -> Result { + for txin in &tx.input { + if !txin.script_sig.is_empty() { + return Err(Error::UnsignedTxHasScriptSigs); + } + + if !txin.witness.is_empty() { + return Err(Error::UnsignedTxHasScriptWitnesses); + } + } + + Ok(Global { + unsigned_tx: tx, + unknown: Default::default(), + }) + } +} + +impl Map for Global { + fn insert_pair(&mut self, pair: raw::Pair) -> Result<(), encode::Error> { + let raw::Pair { + key: raw_key, + value: raw_value, + } = pair; + + match raw_key.type_value { + 0u8 => { + return Err(Error::DuplicateKey(raw_key).into()); + } + _ => { + if self.unknown.contains_key(&raw_key) { + return Err(Error::DuplicateKey(raw_key).into()); + } else { + self.unknown.insert(raw_key, raw_value); + } + } + } + + Ok(()) + } + + fn get_pairs(&self) -> Result, encode::Error> { + let mut rv: Vec = Default::default(); + + rv.push(raw::Pair { + key: raw::Key { + type_value: 0u8, + key: vec![], + }, + value: { + // Manually serialized to ensure 0-input txs are serialized + // without witnesses. + let mut ret = Vec::new(); + self.unsigned_tx.version.consensus_encode(&mut ret)?; + self.unsigned_tx.input.consensus_encode(&mut ret)?; + self.unsigned_tx.output.consensus_encode(&mut ret)?; + self.unsigned_tx.lock_time.consensus_encode(&mut ret)?; + ret + }, + }); + + for (key, value) in self.unknown.iter() { + rv.push(raw::Pair { + key: key.clone(), + value: value.clone(), + }); + } + + Ok(rv) + } + + fn merge(&mut self, other: Self) -> Result<(), psbt::Error> { + if self.unsigned_tx != other.unsigned_tx { + return Err(psbt::Error::UnexpectedUnsignedTx { + expected: self.unsigned_tx.clone(), + actual: other.unsigned_tx, + }); + } + + self.unknown.extend(other.unknown); + Ok(()) + } +} + +impl_psbtmap_consensus_encoding!(Global); + +impl Decodable for Global { + fn consensus_decode(d: &mut D) -> Result { + + let mut tx: Option = None; + let mut unknowns: HashMap> = Default::default(); + + loop { + match raw::Pair::consensus_decode(d) { + Ok(pair) => { + match pair.key.type_value { + 0u8 => { + // key has to be empty + if pair.key.key.is_empty() { + // there can only be one unsigned transaction + if tx.is_none() { + let vlen: usize = pair.value.len(); + let mut decoder = Cursor::new(pair.value); + + // Manually deserialized to ensure 0-input + // txs without witnesses are deserialized + // properly. + tx = Some(Transaction { + version: Decodable::consensus_decode(&mut decoder)?, + input: Decodable::consensus_decode(&mut decoder)?, + output: Decodable::consensus_decode(&mut decoder)?, + lock_time: Decodable::consensus_decode(&mut decoder)?, + }); + + if decoder.position() != vlen as u64 { + return Err(encode::Error::ParseFailed("data not consumed entirely when explicitly deserializing")) + } + } else { + return Err(Error::DuplicateKey(pair.key).into()) + } + } else { + return Err(Error::InvalidKey(pair.key).into()) + } + } + _ => { + if unknowns.contains_key(&pair.key) { + return Err(Error::DuplicateKey(pair.key).into()); + } else { + unknowns.insert(pair.key, pair.value); + } + } + } + } + Err(::consensus::encode::Error::Psbt(::util::psbt::Error::NoMorePairs)) => break, + Err(e) => return Err(e), + } + } + + if let Some(tx) = tx { + let mut rv: Global = Global::from_unsigned_tx(tx)?; + rv.unknown = unknowns; + Ok(rv) + } else { + Err(Error::MustHaveUnsignedTx.into()) + } + } +} diff --git a/src/util/psbt/map/mod.rs b/src/util/psbt/map/mod.rs index 621b865..b353e42 100644 --- a/src/util/psbt/map/mod.rs +++ b/src/util/psbt/map/mod.rs @@ -13,3 +13,8 @@ pub trait Map { /// Attempt to merge with another key-value map of the same type. fn merge(&mut self, other: Self) -> Result<(), psbt::Error>; } + +// place at end to pick up macros +mod global; + +pub use self::global::Global; diff --git a/src/util/psbt/mod.rs b/src/util/psbt/mod.rs index 7d87d59..369f1b1 100644 --- a/src/util/psbt/mod.rs +++ b/src/util/psbt/mod.rs @@ -9,14 +9,65 @@ pub use self::error::Error; pub mod raw; +#[macro_use] +mod macros; + +pub mod serialize; + mod map; -pub use self::map::Map; +pub use self::map::{Map, Global}; #[cfg(test)] mod tests { + use bitcoin_hashes::hex::FromHex; + use bitcoin_hashes::sha256d; + + use blockdata::script::Script; + use blockdata::transaction::{Transaction, TxIn, TxOut, OutPoint}; use consensus::encode::{deserialize, serialize}; + use util::psbt::map::Global; use util::psbt::raw; + #[test] + fn serialize_then_deserialize_global() { + let expected = Global { + unsigned_tx: Transaction { + version: 2, + lock_time: 1257139, + input: vec![TxIn { + previous_output: OutPoint { + txid: sha256d::Hash::from_hex( + "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126", + ).unwrap(), + vout: 0, + }, + script_sig: Script::new(), + sequence: 4294967294, + witness: vec![], + }], + output: vec![ + TxOut { + value: 99999699, + script_pubkey: hex_script!( + "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac" + ), + }, + TxOut { + value: 100000000, + script_pubkey: hex_script!( + "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" + ), + }, + ], + }, + unknown: Default::default(), + }; + + let actual: Global = deserialize(&serialize(&expected)).unwrap(); + + assert_eq!(expected, actual); + } + #[test] fn serialize_then_deserialize_psbtkvpair() { let expected = raw::Pair { diff --git a/src/util/psbt/serialize.rs b/src/util/psbt/serialize.rs new file mode 100644 index 0000000..165a88b --- /dev/null +++ b/src/util/psbt/serialize.rs @@ -0,0 +1,22 @@ +//! # PSBT Serialization +//! +//! Defines traits used for (de)serializing PSBT values into/from raw +//! bytes in PSBT key-value pairs. + +use blockdata::transaction::Transaction; +use consensus::encode; + +/// A trait for serializing a value as raw data for insertion into PSBT +/// key-value pairs. +pub trait Serialize { + /// Serialize a value as raw data. + fn serialize(&self) -> Vec; +} + +/// A trait for deserializing a value from raw data in PSBT key-value pairs. +pub trait Deserialize: Sized { + /// Deserialize a value from raw data. + fn deserialize(bytes: &[u8]) -> Result; +} + +impl_psbt_de_serialize!(Transaction); From 9c08dbae475292c10e597c4a8d0e07cd8290c128 Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Sat, 8 Sep 2018 21:20:29 -0700 Subject: [PATCH 05/10] Add PSBT output data key-value map type - Implement psbt::Map trait for psbt::Output - Add (en)decoding logic for psbt::Output - Implement PSBT (de)serialization trait for relevant psbt::Output types - Add macro for merging fields for PSBT key-value maps - Add macro for implementing decoding logic for PSBT key-value maps - Add convenience macro for implementing both encoding and decoding logic for PSBT key-value maps - Add macro for inserting raw PSBT key-value pairs into PSBT key-value maps - Add macro for getting raw PSBT key-value pairs from PSBT key-value maps --- src/consensus/encode.rs | 1 + src/util/psbt/macros.rs | 91 ++++++++++++++++++++++++++++++++++ src/util/psbt/map/mod.rs | 2 + src/util/psbt/map/output.rs | 98 +++++++++++++++++++++++++++++++++++++ src/util/psbt/mod.rs | 59 ++++++++++++++++++++-- src/util/psbt/serialize.rs | 76 +++++++++++++++++++++++++++- 6 files changed, 323 insertions(+), 4 deletions(-) create mode 100644 src/util/psbt/map/output.rs diff --git a/src/consensus/encode.rs b/src/consensus/encode.rs index 608ee1d..de01451 100644 --- a/src/consensus/encode.rs +++ b/src/consensus/encode.rs @@ -518,6 +518,7 @@ impl_array!(8); impl_array!(12); impl_array!(16); impl_array!(32); +impl_array!(33); impl> Encodable for [T] { #[inline] diff --git a/src/util/psbt/macros.rs b/src/util/psbt/macros.rs index 628d8ba..a36fbd0 100644 --- a/src/util/psbt/macros.rs +++ b/src/util/psbt/macros.rs @@ -1,3 +1,11 @@ +macro_rules! merge { + ($thing:ident, $slf:ident, $other:ident) => { + if let (&None, Some($thing)) = (&$slf.$thing, $other.$thing) { + $slf.$thing = Some($thing); + } + }; +} + macro_rules! impl_psbt_de_serialize { ($thing:ty) => { impl_psbt_serialize!($thing); @@ -38,3 +46,86 @@ macro_rules! impl_psbtmap_consensus_encoding { } }; } + +macro_rules! impl_psbtmap_consensus_decoding { + ($thing:ty) => { + impl ::consensus::encode::Decodable for $thing { + fn consensus_decode(d: &mut D) -> Result { + let mut rv: Self = ::std::default::Default::default(); + + loop { + match ::consensus::encode::Decodable::consensus_decode(d) { + Ok(pair) => ::util::psbt::Map::insert_pair(&mut rv, pair)?, + Err(::consensus::encode::Error::Psbt(::util::psbt::Error::NoMorePairs)) => return Ok(rv), + Err(e) => return Err(e), + } + } + } + } + }; +} + +macro_rules! impl_psbtmap_consensus_enc_dec_oding { + ($thing:ty) => { + impl_psbtmap_consensus_decoding!($thing); + impl_psbtmap_consensus_encoding!($thing); + }; +} + +#[cfg_attr(rustfmt, rustfmt_skip)] +macro_rules! impl_psbt_insert_pair { + ($slf:ident.$unkeyed_name:ident <= <$raw_key:ident: _>|<$raw_value:ident: $unkeyed_value_type:ty>) => { + if $raw_key.key.is_empty() { + if let None = $slf.$unkeyed_name { + let val: $unkeyed_value_type = ::util::psbt::serialize::Deserialize::deserialize(&$raw_value)?; + + $slf.$unkeyed_name = Some(val) + } else { + return Err(::util::psbt::Error::DuplicateKey($raw_key).into()); + } + } else { + return Err(::util::psbt::Error::InvalidKey($raw_key).into()); + } + }; + ($slf:ident.$keyed_name:ident <= <$raw_key:ident: $keyed_key_type:ty>|<$raw_value:ident: $keyed_value_type:ty>) => { + if !$raw_key.key.is_empty() { + let key_val: $keyed_key_type = ::util::psbt::serialize::Deserialize::deserialize(&$raw_key.key)?; + + if $slf.$keyed_name.contains_key(&key_val) { + return Err(::util::psbt::Error::DuplicateKey($raw_key).into()); + } else { + let val: $keyed_value_type = ::util::psbt::serialize::Deserialize::deserialize(&$raw_value)?; + + $slf.$keyed_name.insert(key_val, val); + } + } else { + return Err(::util::psbt::Error::InvalidKey($raw_key).into()); + } + }; +} + +#[cfg_attr(rustfmt, rustfmt_skip)] +macro_rules! impl_psbt_get_pair { + ($rv:ident.push($slf:ident.$unkeyed_name:ident as <$unkeyed_typeval:expr, _>|<$unkeyed_value_type:ty>)) => { + if let Some(ref $unkeyed_name) = $slf.$unkeyed_name { + $rv.push(::util::psbt::raw::Pair { + key: ::util::psbt::raw::Key { + type_value: $unkeyed_typeval, + key: vec![], + }, + value: ::util::psbt::serialize::Serialize::serialize($unkeyed_name), + }); + } + }; + ($rv:ident.push($slf:ident.$keyed_name:ident as <$keyed_typeval:expr, $keyed_key_type:ty>|<$keyed_value_type:ty>)) => { + for (key, val) in &$slf.$keyed_name { + $rv.push(::util::psbt::raw::Pair { + key: ::util::psbt::raw::Key { + type_value: $keyed_typeval, + key: ::util::psbt::serialize::Serialize::serialize(key), + }, + value: ::util::psbt::serialize::Serialize::serialize(val), + }); + } + }; +} diff --git a/src/util/psbt/map/mod.rs b/src/util/psbt/map/mod.rs index b353e42..6ad915c 100644 --- a/src/util/psbt/map/mod.rs +++ b/src/util/psbt/map/mod.rs @@ -16,5 +16,7 @@ pub trait Map { // place at end to pick up macros mod global; +mod output; pub use self::global::Global; +pub use self::output::Output; diff --git a/src/util/psbt/map/output.rs b/src/util/psbt/map/output.rs new file mode 100644 index 0000000..6dd2338 --- /dev/null +++ b/src/util/psbt/map/output.rs @@ -0,0 +1,98 @@ +use std::collections::HashMap; + +use blockdata::script::Script; +use consensus::encode; +use util::bip32::{DerivationPath, Fingerprint}; +use util::key::PublicKey; +use util::psbt; +use util::psbt::map::Map; +use util::psbt::raw; +use util::psbt::Error; + +/// A key-value map for an output of the corresponding index in the unsigned +/// transaction. +#[derive(Clone, Default, Debug, PartialEq)] +pub struct Output { + /// The redeem script for this output. + pub redeem_script: Option