Moving globals into PSBT struct

This commit is contained in:
Dr Maxim Orlovsky 2021-09-15 07:35:07 +02:00
parent 57d7baf05b
commit 55c627715f
No known key found for this signature in database
GPG Key ID: FFC0250947E5C6F7
4 changed files with 192 additions and 193 deletions

View File

@ -57,17 +57,9 @@ macro_rules! impl_psbtmap_consensus_encoding {
impl $crate::consensus::Encodable for $thing { impl $crate::consensus::Encodable for $thing {
fn consensus_encode<S: $crate::io::Write>( fn consensus_encode<S: $crate::io::Write>(
&self, &self,
mut s: S, s: S,
) -> Result<usize, $crate::io::Error> { ) -> Result<usize, $crate::io::Error> {
let mut len = 0; self.consensus_encode_map(s)
for pair in $crate::util::psbt::Map::get_pairs(self)? {
len += $crate::consensus::Encodable::consensus_encode(
&pair,
&mut s,
)?;
}
Ok(len + $crate::consensus::Encodable::consensus_encode(&0x00_u8, s)?)
} }
} }
}; };

View File

@ -21,11 +21,11 @@ use blockdata::transaction::Transaction;
use consensus::{encode, Encodable, Decodable}; use consensus::{encode, Encodable, Decodable};
use consensus::encode::MAX_VEC_SIZE; use consensus::encode::MAX_VEC_SIZE;
use util::psbt::map::Map; use util::psbt::map::Map;
use util::psbt::raw; use util::psbt::{raw, PartiallySignedTransaction};
use util::psbt; use util::psbt;
use util::psbt::Error; use util::psbt::Error;
use util::endian::u32_to_array_le; use util::endian::u32_to_array_le;
use util::bip32::{ExtendedPubKey, KeySource, Fingerprint, DerivationPath, ChildNumber}; use util::bip32::{ExtendedPubKey, Fingerprint, DerivationPath, ChildNumber};
/// Type: Unsigned Transaction PSBT_GLOBAL_UNSIGNED_TX = 0x00 /// Type: Unsigned Transaction PSBT_GLOBAL_UNSIGNED_TX = 0x00
const PSBT_GLOBAL_UNSIGNED_TX: u8 = 0x00; const PSBT_GLOBAL_UNSIGNED_TX: u8 = 0x00;
@ -36,50 +36,7 @@ const PSBT_GLOBAL_VERSION: u8 = 0xFB;
/// Type: Proprietary Use Type PSBT_GLOBAL_PROPRIETARY = 0xFC /// Type: Proprietary Use Type PSBT_GLOBAL_PROPRIETARY = 0xFC
const PSBT_GLOBAL_PROPRIETARY: u8 = 0xFC; const PSBT_GLOBAL_PROPRIETARY: u8 = 0xFC;
/// A key-value map for global data. impl Map for PartiallySignedTransaction {
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Global {
/// The unsigned transaction, scriptSigs and witnesses for each input must be
/// empty.
pub unsigned_tx: Transaction,
/// The version number of this PSBT. If omitted, the version number is 0.
pub version: u32,
/// A global map from extended public keys to the used key fingerprint and
/// derivation path as defined by BIP 32
pub xpub: BTreeMap<ExtendedPubKey, KeySource>,
/// Global proprietary key-value pairs.
#[cfg_attr(feature = "serde", serde(with = "::serde_utils::btreemap_as_seq_byte_values"))]
pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>,
/// Unknown global key-value pairs.
#[cfg_attr(feature = "serde", serde(with = "::serde_utils::btreemap_as_seq_byte_values"))]
pub unknown: BTreeMap<raw::Key, Vec<u8>>,
}
impl Global {
/// Create a Global from an unsigned transaction, error if not unsigned
pub fn from_unsigned_tx(tx: Transaction) -> Result<Self, psbt::Error> {
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,
xpub: Default::default(),
version: 0,
proprietary: Default::default(),
unknown: Default::default(),
})
}
}
impl Map for Global {
fn insert_pair(&mut self, pair: raw::Pair) -> Result<(), encode::Error> { fn insert_pair(&mut self, pair: raw::Pair) -> Result<(), encode::Error> {
let raw::Pair { let raw::Pair {
key: raw_key, key: raw_key,
@ -220,14 +177,21 @@ impl Map for Global {
self.proprietary.extend(other.proprietary); self.proprietary.extend(other.proprietary);
self.unknown.extend(other.unknown); self.unknown.extend(other.unknown);
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(()) Ok(())
} }
} }
impl_psbtmap_consensus_encoding!(Global); impl PartiallySignedTransaction {
pub(crate) fn consensus_decode_global<D: io::Read>(d: D) -> Result<Self, encode::Error> {
impl Decodable for Global {
fn consensus_decode<D: io::Read>(d: D) -> Result<Self, encode::Error> {
let mut d = d.take(MAX_VEC_SIZE as u64); let mut d = d.take(MAX_VEC_SIZE as u64);
let mut tx: Option<Transaction> = None; let mut tx: Option<Transaction> = None;
let mut version: Option<u32> = None; let mut version: Option<u32> = None;
@ -334,12 +298,15 @@ impl Decodable for Global {
} }
if let Some(tx) = tx { if let Some(tx) = tx {
let mut rv: Global = Global::from_unsigned_tx(tx)?; Ok(PartiallySignedTransaction {
rv.version = version.unwrap_or(0); unsigned_tx: tx,
rv.xpub = xpub_map; version: version.unwrap_or(0),
rv.unknown = unknowns; xpub: xpub_map,
rv.proprietary = proprietary; proprietary,
Ok(rv) unknown: unknowns,
inputs: vec![],
outputs: vec![]
})
} else { } else {
Err(Error::MustHaveUnsignedTx.into()) Err(Error::MustHaveUnsignedTx.into())
} }

View File

@ -30,6 +30,22 @@ pub trait Map {
/// Attempt to merge with another key-value map of the same type. /// Attempt to merge with another key-value map of the same type.
fn merge(&mut self, other: Self) -> Result<(), psbt::Error>; fn merge(&mut self, other: Self) -> Result<(), psbt::Error>;
/// Encodes map data with bitcoin consensus encoding
fn consensus_encode_map<S: io::Write>(
&self,
mut s: S,
) -> Result<usize, io::Error> {
let mut len = 0;
for pair in Map::get_pairs(self)? {
len += encode::Encodable::consensus_encode(
&pair,
&mut s,
)?;
}
Ok(len + encode::Encodable::consensus_encode(&0x00_u8, s)?)
}
} }
// place at end to pick up macros // place at end to pick up macros
@ -37,6 +53,5 @@ mod global;
mod input; mod input;
mod output; mod output;
pub use self::global::Global;
pub use self::input::Input; pub use self::input::Input;
pub use self::output::Output; pub use self::output::Output;

View File

@ -38,14 +38,29 @@ mod macros;
pub mod serialize; pub mod serialize;
mod map; mod map;
pub use self::map::{Map, Global, Input, Output}; pub use self::map::{Map, Input, Output};
use util::bip32::{ExtendedPubKey, KeySource};
/// A Partially Signed Transaction. /// A Partially Signed Transaction.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PartiallySignedTransaction { pub struct PartiallySignedTransaction {
/// The key-value pairs for all global data. /// The unsigned transaction, scriptSigs and witnesses for each input must be
pub global: Global, /// empty.
pub unsigned_tx: Transaction,
/// The version number of this PSBT. If omitted, the version number is 0.
pub version: u32,
/// A global map from extended public keys to the used key fingerprint and
/// derivation path as defined by BIP 32
pub xpub: BTreeMap<ExtendedPubKey, KeySource>,
/// Global proprietary key-value pairs.
#[cfg_attr(feature = "serde", serde(with = "::serde_utils::btreemap_as_seq_byte_values"))]
pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>,
/// Unknown global key-value pairs.
#[cfg_attr(feature = "serde", serde(with = "::serde_utils::btreemap_as_seq_byte_values"))]
pub unknown: BTreeMap<raw::Key, Vec<u8>>,
/// The corresponding key-value map for each input in the unsigned /// The corresponding key-value map for each input in the unsigned
/// transaction. /// transaction.
pub inputs: Vec<Input>, pub inputs: Vec<Input>,
@ -55,20 +70,43 @@ pub struct PartiallySignedTransaction {
} }
impl PartiallySignedTransaction { impl PartiallySignedTransaction {
/// Checks that unsigned transaction does not have scriptSig's or witness
/// data
fn unsigned_tx_checks(&self) -> Result<(), Error> {
for txin in &self.unsigned_tx.input {
if !txin.script_sig.is_empty() {
return Err(Error::UnsignedTxHasScriptSigs);
}
if !txin.witness.is_empty() {
return Err(Error::UnsignedTxHasScriptWitnesses);
}
}
Ok(())
}
/// Create a PartiallySignedTransaction from an unsigned transaction, error /// Create a PartiallySignedTransaction from an unsigned transaction, error
/// if not unsigned /// if not unsigned
pub fn from_unsigned_tx(tx: Transaction) -> Result<Self, self::Error> { pub fn from_unsigned_tx(tx: Transaction) -> Result<Self, Error> {
Ok(PartiallySignedTransaction { let psbt = PartiallySignedTransaction {
inputs: vec![Default::default(); tx.input.len()], inputs: vec![Default::default(); tx.input.len()],
outputs: vec![Default::default(); tx.output.len()], outputs: vec![Default::default(); tx.output.len()],
global: Global::from_unsigned_tx(tx)?,
}) unsigned_tx: tx,
xpub: Default::default(),
version: 0,
proprietary: Default::default(),
unknown: Default::default(),
};
psbt.unsigned_tx_checks()?;
Ok(psbt)
} }
/// Extract the Transaction from a PartiallySignedTransaction by filling in /// Extract the Transaction from a PartiallySignedTransaction by filling in
/// the available signature information in place. /// the available signature information in place.
pub fn extract_tx(self) -> Transaction { pub fn extract_tx(self) -> Transaction {
let mut tx: Transaction = self.global.unsigned_tx; let mut tx: Transaction = self.unsigned_tx;
for (vin, psbtin) in tx.input.iter_mut().zip(self.inputs.into_iter()) { 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.script_sig = psbtin.final_script_sig.unwrap_or_else(Script::new);
@ -77,21 +115,6 @@ impl PartiallySignedTransaction {
tx 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(())
}
} }
#[cfg(feature = "base64")] #[cfg(feature = "base64")]
@ -151,7 +174,7 @@ impl Encodable for PartiallySignedTransaction {
len += 0xff_u8.consensus_encode(&mut s)?; len += 0xff_u8.consensus_encode(&mut s)?;
len += self.global.consensus_encode(&mut s)?; len += self.consensus_encode_map(&mut s)?;
for i in &self.inputs { for i in &self.inputs {
len += i.consensus_encode(&mut s)?; len += i.consensus_encode(&mut s)?;
@ -178,7 +201,8 @@ impl Decodable for PartiallySignedTransaction {
return Err(Error::InvalidSeparator.into()); return Err(Error::InvalidSeparator.into());
} }
let global: Global = Decodable::consensus_decode(&mut d)?; let mut global = PartiallySignedTransaction::consensus_decode_global(&mut d)?;
global.unsigned_tx_checks()?;
let inputs: Vec<Input> = { let inputs: Vec<Input> = {
let inputs_len: usize = (&global.unsigned_tx.input).len(); let inputs_len: usize = (&global.unsigned_tx.input).len();
@ -204,11 +228,9 @@ impl Decodable for PartiallySignedTransaction {
outputs outputs
}; };
Ok(PartiallySignedTransaction { global.inputs = inputs;
global: global, global.outputs = outputs;
inputs: inputs, Ok(global)
outputs: outputs,
})
} }
} }
@ -227,7 +249,7 @@ mod tests {
use consensus::encode::{deserialize, serialize, serialize_hex}; use consensus::encode::{deserialize, serialize, serialize_hex};
use util::bip32::{ChildNumber, ExtendedPrivKey, ExtendedPubKey, Fingerprint, KeySource}; use util::bip32::{ChildNumber, ExtendedPrivKey, ExtendedPubKey, Fingerprint, KeySource};
use util::ecdsa::PublicKey; use util::ecdsa::PublicKey;
use util::psbt::map::{Global, Output, Input}; use util::psbt::map::{Output, Input};
use util::psbt::raw; use util::psbt::raw;
use super::PartiallySignedTransaction; use super::PartiallySignedTransaction;
@ -237,18 +259,17 @@ mod tests {
#[test] #[test]
fn trivial_psbt() { fn trivial_psbt() {
let psbt = PartiallySignedTransaction { let psbt = PartiallySignedTransaction {
global: Global { unsigned_tx: Transaction {
unsigned_tx: Transaction { version: 2,
version: 2, lock_time: 0,
lock_time: 0, input: vec![],
input: vec![], output: vec![],
output: vec![],
},
xpub: Default::default(),
version: 0,
proprietary: BTreeMap::new(),
unknown: BTreeMap::new(),
}, },
xpub: Default::default(),
version: 0,
proprietary: BTreeMap::new(),
unknown: BTreeMap::new(),
inputs: vec![], inputs: vec![],
outputs: vec![], outputs: vec![],
}; };
@ -304,7 +325,7 @@ mod tests {
#[test] #[test]
fn serialize_then_deserialize_global() { fn serialize_then_deserialize_global() {
let expected = Global { let expected = PartiallySignedTransaction {
unsigned_tx: Transaction { unsigned_tx: Transaction {
version: 2, version: 2,
lock_time: 1257139, lock_time: 1257139,
@ -338,9 +359,16 @@ mod tests {
version: 0, version: 0,
proprietary: Default::default(), proprietary: Default::default(),
unknown: Default::default(), unknown: Default::default(),
inputs: vec![
Input::default(),
],
outputs: vec![
Output::default(),
Output::default()
]
}; };
let actual: Global = deserialize(&serialize(&expected)).unwrap(); let actual: PartiallySignedTransaction = deserialize(&serialize(&expected)).unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }
@ -414,23 +442,22 @@ mod tests {
)].into_iter().collect(); )].into_iter().collect();
let psbt = PartiallySignedTransaction { let psbt = PartiallySignedTransaction {
global: Global { version: 0,
version: 0, xpub: {
xpub: { let xpub: ExtendedPubKey =
let xpub: ExtendedPubKey = "xpub661MyMwAqRbcGoRVtwfvzZsq2VBJR1LAHfQstHUoxqDorV89vRoMxUZ27kLrraAj6MPi\
"xpub661MyMwAqRbcGoRVtwfvzZsq2VBJR1LAHfQstHUoxqDorV89vRoMxUZ27kLrraAj6MPi\ QfrDb27gigC1VS1dBXi5jGpxmMeBXEkKkcXUTg4".parse().unwrap();
QfrDb27gigC1VS1dBXi5jGpxmMeBXEkKkcXUTg4".parse().unwrap(); vec![(xpub, key_source.clone())].into_iter().collect()
vec![(xpub, key_source.clone())].into_iter().collect()
},
unsigned_tx: {
let mut unsigned = tx.clone();
unsigned.input[0].script_sig = Script::new();
unsigned.input[0].witness = Vec::new();
unsigned
},
proprietary: proprietary.clone(),
unknown: unknown.clone(),
}, },
unsigned_tx: {
let mut unsigned = tx.clone();
unsigned.input[0].script_sig = Script::new();
unsigned.input[0].witness = Vec::new();
unsigned
},
proprietary: proprietary.clone(),
unknown: unknown.clone(),
inputs: vec![Input { inputs: vec![Input {
non_witness_utxo: Some(tx), non_witness_utxo: Some(tx),
witness_utxo: Some(TxOut { witness_utxo: Some(TxOut {
@ -476,7 +503,7 @@ mod tests {
use blockdata::script::Script; use blockdata::script::Script;
use blockdata::transaction::{SigHashType, Transaction, TxIn, TxOut, OutPoint}; use blockdata::transaction::{SigHashType, Transaction, TxIn, TxOut, OutPoint};
use consensus::encode::serialize_hex; use consensus::encode::serialize_hex;
use util::psbt::map::{Map, Global, Input, Output}; use util::psbt::map::{Map, Input, Output};
use util::psbt::raw; use util::psbt::raw;
use util::psbt::{PartiallySignedTransaction, Error}; use util::psbt::{PartiallySignedTransaction, Error};
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -560,37 +587,36 @@ mod tests {
#[test] #[test]
fn valid_vector_1() { fn valid_vector_1() {
let unserialized = PartiallySignedTransaction { let unserialized = PartiallySignedTransaction {
global: Global { unsigned_tx: Transaction {
unsigned_tx: Transaction { version: 2,
version: 2, lock_time: 1257139,
lock_time: 1257139, input: vec![TxIn {
input: vec![TxIn { previous_output: OutPoint {
previous_output: OutPoint { txid: Txid::from_hex(
txid: Txid::from_hex( "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126",
"f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126", ).unwrap(),
).unwrap(), vout: 0,
vout: 0, },
}, script_sig: Script::new(),
script_sig: Script::new(), sequence: 4294967294,
sequence: 4294967294, witness: vec![],
witness: vec![], }],
}], output: vec![
output: vec![ TxOut {
TxOut { value: 99999699,
value: 99999699, script_pubkey: hex_script!("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac"),
script_pubkey: hex_script!("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac"), },
}, TxOut {
TxOut { value: 100000000,
value: 100000000, script_pubkey: hex_script!("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"),
script_pubkey: hex_script!("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"), },
}, ],
],
},
xpub: Default::default(),
version: 0,
proprietary: BTreeMap::new(),
unknown: BTreeMap::new(),
}, },
xpub: Default::default(),
version: 0,
proprietary: BTreeMap::new(),
unknown: BTreeMap::new(),
inputs: vec![Input { inputs: vec![Input {
non_witness_utxo: Some(Transaction { non_witness_utxo: Some(Transaction {
version: 1, version: 1,
@ -690,7 +716,7 @@ mod tests {
assert_eq!(psbt.inputs.len(), 1); assert_eq!(psbt.inputs.len(), 1);
assert_eq!(psbt.outputs.len(), 2); assert_eq!(psbt.outputs.len(), 2);
let tx_input = &psbt.global.unsigned_tx.input[0]; let tx_input = &psbt.unsigned_tx.input[0];
let psbt_non_witness_utxo = (&psbt.inputs[0].non_witness_utxo).as_ref().unwrap(); let psbt_non_witness_utxo = (&psbt.inputs[0].non_witness_utxo).as_ref().unwrap();
assert_eq!(tx_input.previous_output.txid, psbt_non_witness_utxo.txid()); assert_eq!(tx_input.previous_output.txid, psbt_non_witness_utxo.txid());
@ -758,7 +784,7 @@ mod tests {
assert_eq!(psbt.inputs.len(), 1); assert_eq!(psbt.inputs.len(), 1);
assert_eq!(psbt.outputs.len(), 1); assert_eq!(psbt.outputs.len(), 1);
let tx = &psbt.global.unsigned_tx; let tx = &psbt.unsigned_tx;
assert_eq!( assert_eq!(
tx.txid(), tx.txid(),
Txid::from_hex( Txid::from_hex(
@ -793,37 +819,36 @@ mod tests {
// same vector as valid_vector_1 from BIPs with added // same vector as valid_vector_1 from BIPs with added
let mut unserialized = PartiallySignedTransaction { let mut unserialized = PartiallySignedTransaction {
global: Global { unsigned_tx: Transaction {
unsigned_tx: Transaction { version: 2,
version: 2, lock_time: 1257139,
lock_time: 1257139, input: vec![TxIn {
input: vec![TxIn { previous_output: OutPoint {
previous_output: OutPoint { txid: Txid::from_hex(
txid: Txid::from_hex( "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126",
"f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126", ).unwrap(),
).unwrap(), vout: 0,
vout: 0, },
}, script_sig: Script::new(),
script_sig: Script::new(), sequence: 4294967294,
sequence: 4294967294, witness: vec![],
witness: vec![], }],
}], output: vec![
output: vec![ TxOut {
TxOut { value: 99999699,
value: 99999699, script_pubkey: hex_script!("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac"),
script_pubkey: hex_script!("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac"), },
}, TxOut {
TxOut { value: 100000000,
value: 100000000, script_pubkey: hex_script!("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"),
script_pubkey: hex_script!("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"), },
}, ],
],
},
version: 0,
xpub: Default::default(),
proprietary: Default::default(),
unknown: BTreeMap::new(),
}, },
version: 0,
xpub: Default::default(),
proprietary: Default::default(),
unknown: BTreeMap::new(),
inputs: vec![Input { inputs: vec![Input {
non_witness_utxo: Some(Transaction { non_witness_utxo: Some(Transaction {
version: 1, version: 1,
@ -897,14 +922,14 @@ mod tests {
#[test] #[test]
fn serialize_and_deserialize_proprietary() { fn serialize_and_deserialize_proprietary() {
let mut psbt: PartiallySignedTransaction = hex_psbt!("70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac000000000001076a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa882920001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000").unwrap(); let mut psbt: PartiallySignedTransaction = hex_psbt!("70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac000000000001076a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa882920001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000").unwrap();
psbt.global.proprietary.insert(ProprietaryKey { psbt.proprietary.insert(ProprietaryKey {
prefix: b"test".to_vec(), prefix: b"test".to_vec(),
subtype: 0u8, subtype: 0u8,
key: b"test".to_vec(), key: b"test".to_vec(),
}, b"test".to_vec()); }, b"test".to_vec());
assert!(!psbt.global.proprietary.is_empty()); assert!(!psbt.proprietary.is_empty());
let rtt : PartiallySignedTransaction = hex_psbt!(&serialize_hex(&psbt)).unwrap(); let rtt : PartiallySignedTransaction = hex_psbt!(&serialize_hex(&psbt)).unwrap();
assert!(!rtt.global.proprietary.is_empty()); assert!(!rtt.proprietary.is_empty());
} }
} }