De/serialize Psbt without consensus traits
This commit is contained in:
parent
5b10e6cf0c
commit
1b7b08aa5d
|
@ -1,14 +1,14 @@
|
|||
extern crate bitcoin;
|
||||
|
||||
fn do_test(data: &[u8]) {
|
||||
let psbt: Result<bitcoin::psbt::PartiallySignedTransaction, _> = bitcoin::consensus::encode::deserialize(data);
|
||||
let psbt: Result<bitcoin::psbt::PartiallySignedTransaction, _> = 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! hex_psbt {
|
||||
($s:expr) => { $crate::consensus::deserialize::<$crate::psbt::PartiallySignedTransaction>(&<$crate::prelude::Vec<u8> as $crate::hashes::hex::FromHex>::from_hex($s).unwrap()) };
|
||||
($s:expr) => { <$crate::psbt::PartiallySignedTransaction>::deserialize(&<$crate::prelude::Vec<u8> as $crate::hashes::hex::FromHex>::from_hex($s).unwrap()) };
|
||||
}
|
||||
|
||||
macro_rules! combine {
|
||||
|
|
|
@ -19,7 +19,6 @@ pub(super) trait Map {
|
|||
/// Attempt to get all key-value pairs.
|
||||
fn get_pairs(&self) -> Result<Vec<raw::Pair>, io::Error>;
|
||||
|
||||
/// Encodes map data with bitcoin consensus encoding.
|
||||
fn consensus_encode_map<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
|
||||
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<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
|
||||
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)?)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Self, Self::Err> {
|
||||
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<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
|
||||
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: io::Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
|
||||
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<Input> = {
|
||||
let inputs_len: usize = global.unsigned_tx.input.len();
|
||||
|
||||
let mut inputs: Vec<Input> = Vec::with_capacity(inputs_len);
|
||||
|
||||
for _ in 0..inputs_len {
|
||||
inputs.push(Decodable::consensus_decode(r)?);
|
||||
}
|
||||
|
||||
inputs
|
||||
};
|
||||
|
||||
let outputs: Vec<Output> = {
|
||||
let outputs_len: usize = global.unsigned_tx.output.len();
|
||||
|
||||
let mut outputs: Vec<Output> = 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::<PartiallySignedTransaction>(&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<PartiallySignedTransaction, _> = hex_psbt!(&serialize_hex(&unserialized));
|
||||
let rtt: Result<PartiallySignedTransaction, _> = 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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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<u8>;
|
||||
}
|
||||
|
||||
/// 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<Self, encode::Error>;
|
||||
}
|
||||
|
||||
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<u8> {
|
||||
let mut buf: Vec<u8> = 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<Self, encode::Error> {
|
||||
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<Input> = {
|
||||
let inputs_len: usize = (&global.unsigned_tx.input).len();
|
||||
|
||||
let mut inputs: Vec<Input> = Vec::with_capacity(inputs_len);
|
||||
|
||||
for _ in 0..inputs_len {
|
||||
inputs.push(Decodable::consensus_decode(&mut d)?);
|
||||
}
|
||||
|
||||
inputs
|
||||
};
|
||||
|
||||
let outputs: Vec<Output> = {
|
||||
let outputs_len: usize = (&global.unsigned_tx.output).len();
|
||||
|
||||
let mut outputs: Vec<Output> = 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ macro_rules! hex_script {
|
|||
|
||||
macro_rules! hex_psbt {
|
||||
($s:expr) => {
|
||||
deserialize::<Psbt>(&<Vec<u8> as FromHex>::from_hex($s).unwrap())
|
||||
Psbt::deserialize(&<Vec<u8> 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");
|
||||
|
||||
|
|
Loading…
Reference in New Issue