diff --git a/bitcoin/fuzz/fuzz_targets/deserialize_psbt.rs b/bitcoin/fuzz/fuzz_targets/deserialize_psbt.rs index 7556679b..284c8655 100644 --- a/bitcoin/fuzz/fuzz_targets/deserialize_psbt.rs +++ b/bitcoin/fuzz/fuzz_targets/deserialize_psbt.rs @@ -1,14 +1,14 @@ extern crate bitcoin; fn do_test(data: &[u8]) { - let psbt: Result = bitcoin::consensus::encode::deserialize(data); + let psbt: Result = bitcoin::psbt::Psbt::deserialize(data); match psbt { Err(_) => {}, Ok(psbt) => { - let ser = bitcoin::consensus::encode::serialize(&psbt); - let deser: bitcoin::psbt::PartiallySignedTransaction = bitcoin::consensus::encode::deserialize(&ser).unwrap(); + let ser = bitcoin::psbt::Psbt::serialize(&psbt); + let deser = bitcoin::psbt::Psbt::deserialize(&ser).unwrap(); // Since the fuzz data could order psbt fields differently, we compare to our deser/ser instead of data - assert_eq!(ser, bitcoin::consensus::encode::serialize(&deser)); + assert_eq!(ser, bitcoin::psbt::Psbt::serialize(&deser)); } } } diff --git a/bitcoin/src/psbt/macros.rs b/bitcoin/src/psbt/macros.rs index ddfce8aa..e6b7e791 100644 --- a/bitcoin/src/psbt/macros.rs +++ b/bitcoin/src/psbt/macros.rs @@ -2,7 +2,7 @@ #[allow(unused_macros)] macro_rules! hex_psbt { - ($s:expr) => { $crate::consensus::deserialize::<$crate::psbt::PartiallySignedTransaction>(&<$crate::prelude::Vec as $crate::hashes::hex::FromHex>::from_hex($s).unwrap()) }; + ($s:expr) => { <$crate::psbt::PartiallySignedTransaction>::deserialize(&<$crate::prelude::Vec as $crate::hashes::hex::FromHex>::from_hex($s).unwrap()) }; } macro_rules! combine { diff --git a/bitcoin/src/psbt/map/mod.rs b/bitcoin/src/psbt/map/mod.rs index d22dc226..4357b688 100644 --- a/bitcoin/src/psbt/map/mod.rs +++ b/bitcoin/src/psbt/map/mod.rs @@ -19,7 +19,6 @@ pub(super) trait Map { /// Attempt to get all key-value pairs. fn get_pairs(&self) -> Result, io::Error>; - /// Encodes map data with bitcoin consensus encoding. fn consensus_encode_map(&self, w: &mut W) -> Result { let mut len = 0; for pair in Map::get_pairs(self)? { @@ -28,4 +27,15 @@ pub(super) trait Map { Ok(len + encode::Encodable::consensus_encode(&0x00_u8, w)?) } + + /// Encodes map data with bitcoin consensus encoding. + /// Serialize Psbt map data according to BIP-174 specification. + fn serialize_map(&self, w: &mut W) -> Result { + let mut len = 0; + for pair in Map::get_pairs(self)? { + len += encode::Encodable::consensus_encode(&pair, w)?; + } + + Ok(len + encode::Encodable::consensus_encode(&0x00_u8, w)?) + } } diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index f1d18f73..2e4629b9 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -17,11 +17,9 @@ use secp256k1::{Message, Secp256k1, Signing}; use bitcoin_internals::write_err; use crate::{prelude::*, Amount}; -use crate::io; use crate::blockdata::script::ScriptBuf; use crate::blockdata::transaction::{Transaction, TxOut}; -use crate::consensus::{encode, Encodable, Decodable}; use crate::bip32::{self, ExtendedPrivKey, ExtendedPubKey, KeySource}; use crate::crypto::ecdsa; use crate::crypto::key::{PublicKey, PrivateKey}; @@ -39,7 +37,6 @@ pub use self::error::Error; mod map; pub use self::map::{Input, Output, TapTree, PsbtSighashType, IncompleteTapTree}; -use self::map::Map; /// Partially signed transaction, commonly referred to as a PSBT. pub type Psbt = PartiallySignedTransaction; @@ -751,7 +748,7 @@ mod display_from_str { use super::PartiallySignedTransaction; use core::fmt::{Display, Formatter, self}; use core::str::FromStr; - use crate::consensus::encode::{Error, self}; + use crate::consensus::encode::Error; use base64::display::Base64Display; use bitcoin_internals::write_err; @@ -793,7 +790,7 @@ mod display_from_str { #[cfg_attr(docsrs, doc(cfg(feature = "base64")))] impl Display for PartiallySignedTransaction { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}", Base64Display::with_config(&encode::serialize(self), base64::STANDARD)) + write!(f, "{}", Base64Display::with_config(&self.serialize(), base64::STANDARD)) } } @@ -803,7 +800,7 @@ mod display_from_str { fn from_str(s: &str) -> Result { let data = base64::decode(s).map_err(PsbtParseError::Base64Encoding)?; - encode::deserialize(&data).map_err(PsbtParseError::PsbtEncoding) + PartiallySignedTransaction::deserialize(&data).map_err(PsbtParseError::PsbtEncoding) } } } @@ -811,72 +808,6 @@ mod display_from_str { #[cfg_attr(docsrs, doc(cfg(feature = "base64")))] pub use self::display_from_str::PsbtParseError; -impl Encodable for PartiallySignedTransaction { - fn consensus_encode(&self, w: &mut W) -> Result { - let mut len = 0; - len += b"psbt".consensus_encode(w)?; - - len += 0xff_u8.consensus_encode(w)?; - - len += self.consensus_encode_map(w)?; - - for i in &self.inputs { - len += i.consensus_encode(w)?; - } - - for i in &self.outputs { - len += i.consensus_encode(w)?; - } - - Ok(len) - } -} - -impl Decodable for PartiallySignedTransaction { - fn consensus_decode_from_finite_reader(r: &mut R) -> Result { - let magic: [u8; 4] = Decodable::consensus_decode(r)?; - - if *b"psbt" != magic { - return Err(Error::InvalidMagic.into()); - } - - if 0xff_u8 != u8::consensus_decode(r)? { - return Err(Error::InvalidSeparator.into()); - } - - let mut global = PartiallySignedTransaction::consensus_decode_global(r)?; - global.unsigned_tx_checks()?; - - 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(r)?); - } - - 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(r)?); - } - - outputs - }; - - global.inputs = inputs; - global.outputs = outputs; - Ok(global) - } -} - #[cfg(test)] mod tests { use super::*; @@ -893,7 +824,7 @@ mod tests { use crate::blockdata::script::ScriptBuf; use crate::blockdata::transaction::{Transaction, TxIn, TxOut, OutPoint, Sequence}; use crate::network::constants::Network::Bitcoin; - use crate::consensus::encode::{deserialize, serialize, serialize_hex}; + use crate::consensus::encode::{deserialize, serialize}; use crate::bip32::{ChildNumber, ExtendedPrivKey, ExtendedPubKey, KeySource}; use crate::psbt::map::{Output, Input}; use crate::psbt::raw; @@ -919,7 +850,7 @@ mod tests { inputs: vec![], outputs: vec![], }; - assert_eq!(serialize_hex(&psbt), "70736274ff01000a0200000000000000000000"); + assert_eq!(psbt.serialize_hex(), "70736274ff01000a0200000000000000000000"); } #[test] @@ -1012,8 +943,7 @@ mod tests { outputs: vec![Output::default(), Output::default()], }; - let actual: PartiallySignedTransaction = deserialize(&serialize(&expected)).unwrap(); - + let actual: Psbt = Psbt::deserialize(&Psbt::serialize(&expected)).unwrap(); assert_eq!(expected, actual); } @@ -1036,7 +966,7 @@ mod tests { fn deserialize_and_serialize_psbt_with_two_partial_sigs() { let hex = "70736274ff0100890200000001207ae985d787dfe6143d5c58fad79cc7105e0e799fcf033b7f2ba17e62d7b3200000000000ffffffff02563d03000000000022002019899534b9a011043c0dd57c3ff9a381c3522c5f27c6a42319085b56ca543a1d6adc020000000000220020618b47a07ebecca4e156edb1b9ea7c24bdee0139fc049237965ffdaf56d5ee73000000000001012b801a0600000000002200201148e93e9315e37dbed2121be5239257af35adc03ffdfc5d914b083afa44dab82202025fe7371376d53cf8a2783917c28bf30bd690b0a4d4a207690093ca2b920ee076473044022007e06b362e89912abd4661f47945430739b006a85d1b2a16c01dc1a4bd07acab022061576d7aa834988b7ab94ef21d8eebd996ea59ea20529a19b15f0c9cebe3d8ac01220202b3fe93530020a8294f0e527e33fbdff184f047eb6b5a1558a352f62c29972f8a473044022002787f926d6817504431ee281183b8119b6845bfaa6befae45e13b6d430c9d2f02202859f149a6cd26ae2f03a107e7f33c7d91730dade305fe077bae677b5d44952a01010547522102b3fe93530020a8294f0e527e33fbdff184f047eb6b5a1558a352f62c29972f8a21025fe7371376d53cf8a2783917c28bf30bd690b0a4d4a207690093ca2b920ee07652ae0001014752210283ef76537f2d58ae3aa3a4bd8ae41c3f230ccadffb1a0bd3ca504d871cff05e7210353d79cc0cb1396f4ce278d005f16d948e02a6aec9ed1109f13747ecb1507b37b52ae00010147522102b3937241777b6665e0d694e52f9c1b188433641df852da6fc42187b5d8a368a321034cdd474f01cc5aa7ff834ad8bcc882a87e854affc775486bc2a9f62e8f49bd7852ae00"; let psbt: PartiallySignedTransaction = hex_psbt!(hex).unwrap(); - assert_eq!(hex, serialize_hex(&psbt)); + assert_eq!(hex, psbt.serialize_hex()); } #[cfg(feature = "serde")] @@ -1147,7 +1077,6 @@ mod tests { use crate::blockdata::script::ScriptBuf; use crate::blockdata::transaction::{Transaction, TxIn, TxOut, OutPoint, Sequence}; - use crate::consensus::encode::serialize_hex; use crate::blockdata::locktime::absolute; use crate::psbt::map::{Map, Input, Output}; use crate::psbt::{raw, PartiallySignedTransaction, Error}; @@ -1322,7 +1251,7 @@ mod tests { let base16str = "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000"; - assert_eq!(serialize_hex(&unserialized), base16str); + assert_eq!(unserialized.serialize_hex(), base16str); assert_eq!(unserialized, hex_psbt!(base16str).unwrap()); #[cfg(feature = "base64")] { @@ -1452,7 +1381,6 @@ mod tests { mod bip_371_vectors { use super::*; - use super::serialize; #[test] fn invalid_vectors() { @@ -1482,8 +1410,8 @@ mod tests { } fn rtt_psbt(psbt: PartiallySignedTransaction) { - let enc = serialize(&psbt); - let psbt2 = deserialize::(&enc).unwrap(); + let enc = Psbt::serialize(&psbt); + let psbt2 = Psbt::deserialize(&enc).unwrap(); assert_eq!(psbt, psbt2); } @@ -1634,7 +1562,7 @@ mod tests { unserialized.inputs[0].hash160_preimages = hash160_preimages; unserialized.inputs[0].sha256_preimages = sha256_preimages; - let rtt: PartiallySignedTransaction = hex_psbt!(&serialize_hex(&unserialized)).unwrap(); + let rtt: PartiallySignedTransaction = hex_psbt!(&unserialized.serialize_hex()).unwrap(); assert_eq!(rtt, unserialized); // Now add an ripemd160 with incorrect preimage @@ -1643,7 +1571,7 @@ mod tests { unserialized.inputs[0].ripemd160_preimages = ripemd160_preimages; // Now the roundtrip should fail as the preimage is incorrect. - let rtt: Result = hex_psbt!(&serialize_hex(&unserialized)); + let rtt: Result = hex_psbt!(&unserialized.serialize_hex()); assert!(rtt.is_err()); } @@ -1656,7 +1584,7 @@ mod tests { key: b"test".to_vec(), }, b"test".to_vec()); assert!(!psbt.proprietary.is_empty()); - let rtt: PartiallySignedTransaction = hex_psbt!(&serialize_hex(&psbt)).unwrap(); + let rtt: PartiallySignedTransaction = hex_psbt!(&psbt.serialize_hex()).unwrap(); assert!(!rtt.proprietary.is_empty()); } diff --git a/bitcoin/src/psbt/serialize.rs b/bitcoin/src/psbt/serialize.rs index 0fd909a7..76b7ad28 100644 --- a/bitcoin/src/psbt/serialize.rs +++ b/bitcoin/src/psbt/serialize.rs @@ -2,8 +2,8 @@ //! PSBT serialization. //! -//! Defines traits used for (de)serializing PSBT values into/from raw -//! bytes from/as PSBT key-value pairs. +//! Traits to serialize PSBT values to and from raw bytes +//! according to the BIP-174 specification. //! use crate::prelude::*; @@ -18,26 +18,100 @@ use secp256k1::{self, XOnlyPublicKey}; use crate::bip32::{ChildNumber, Fingerprint, KeySource}; use crate::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; use crate::crypto::{ecdsa, schnorr}; -use crate::psbt; +use crate::psbt::{self, Error, PartiallySignedTransaction}; use crate::taproot::{TapBranchHash, TapLeafHash, ControlBlock, LeafVersion}; use crate::crypto::key::PublicKey; -use super::map::{TapTree, PsbtSighashType}; +use super::map::{Map, Input, Output, TapTree, PsbtSighashType}; use crate::taproot::TaprootBuilder; /// A trait for serializing a value as raw data for insertion into PSBT -/// key-value pairs. +/// key-value maps. 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. +/// A trait for deserializing a value from raw data in PSBT key-value maps. pub trait Deserialize: Sized { /// Deserialize a value from raw data. fn deserialize(bytes: &[u8]) -> Result; } +impl PartiallySignedTransaction { + /// Serialize a value as bytes in hex. + pub fn serialize_hex(&self) -> String { + bitcoin_hashes::hex::ToHex::to_hex(&self.serialize()[..]) + } + + /// Serialize a value as raw binary data. + pub fn serialize(&self) -> Vec { + let mut buf: Vec = Vec::new(); + + buf.extend(b"psbt"); + + buf.push(0xff_u8); + + self.serialize_map(&mut buf).unwrap(); + + for i in &self.inputs { + i.consensus_encode(&mut buf).unwrap(); + } + + for i in &self.outputs { + i.consensus_encode(&mut buf).unwrap(); + } + + buf + } + + + /// Deserialize a value from raw binary data. + pub fn deserialize(bytes: &[u8]) -> Result { + const MAGIC_BYTES: &[u8] = b"psbt"; + if bytes.get(0..MAGIC_BYTES.len()) != Some(MAGIC_BYTES) { + return Err(Error::InvalidMagic.into()); + } + + const PSBT_SERPARATOR: u8 = 0xff_u8; + if bytes.get(MAGIC_BYTES.len()) != Some(&PSBT_SERPARATOR) { + return Err(Error::InvalidSeparator.into()); + } + + let mut d = bytes.get(5..).ok_or(Error::NoMorePairs)?; + + let mut global = PartiallySignedTransaction::consensus_decode_global(&mut d)?; + global.unsigned_tx_checks()?; + + 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(&mut 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(&mut d)?); + } + + outputs + }; + + global.inputs = inputs; + global.outputs = outputs; + Ok(global) + } +} impl_psbt_de_serialize!(Transaction); impl_psbt_de_serialize!(TxOut); impl_psbt_de_serialize!(Witness); @@ -400,4 +474,11 @@ mod tests { let sighash = PsbtSighashType::deserialize(&non_standard_sighash); assert!(sighash.is_ok()) } + + #[test] + #[should_panic(expected = "InvalidMagic")] + fn invalid_vector_1() { + let hex_psbt = b"0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300"; + PartiallySignedTransaction::deserialize(hex_psbt).unwrap(); + } } diff --git a/bitcoin/tests/psbt.rs b/bitcoin/tests/psbt.rs index d14bea47..9fdc0578 100644 --- a/bitcoin/tests/psbt.rs +++ b/bitcoin/tests/psbt.rs @@ -26,7 +26,7 @@ macro_rules! hex_script { macro_rules! hex_psbt { ($s:expr) => { - deserialize::(& as FromHex>::from_hex($s).unwrap()) + Psbt::deserialize(& as FromHex>::from_hex($s).unwrap()) }; } @@ -397,10 +397,10 @@ fn combine_lexicographically() { let expected_psbt = hex_psbt!(expected_psbt_hex).unwrap(); let v = Vec::from_hex(psbt_1_hex).unwrap(); - let mut psbt_1: Psbt = deserialize(&v).expect("failed to deserialize psbt 1"); + let mut psbt_1 = Psbt::deserialize(&v).expect("failed to deserialize psbt 1"); let v = Vec::from_hex(psbt_2_hex).unwrap(); - let psbt_2: Psbt = deserialize(&v).expect("failed to deserialize psbt 2"); + let psbt_2 = Psbt::deserialize(&v).expect("failed to deserialize psbt 2"); psbt_1.combine(psbt_2).expect("failed to combine PSBTs");