Merge rust-bitcoin/rust-bitcoin#654: Making globals part of PSBT struct. Closes #652

55c627715f Moving globals into PSBT struct (Dr Maxim Orlovsky)

Pull request description:

  I took the most non-invasive approach to reduce diff size. Many parts of the code can be improved in style or further refactored (like some functions are not necessary and can be just moved to be part of other functions), but I'd prefer to do that as a separate PR once this will be merged.

  My approach with this PR:
  1. Remove `Global` struct by moving its fields right into `PartiallySignedTransaction` - but keep the `util/psbt/map/global.rs` file with all its logic
  2. Keep existing `Map for Global` implementation in the same file, but just change it to `Map for PartiallySignedTransaction`
  3. With serialization, convert `Global` deserialization into crate-private function and use it from `PartiallySignedTransaction` deserialization
  4. Refactor the tests and imports as required to get the thing compile and pass tests

  The refactoring will be followed by PR(s) adding support for Taproot

ACKs for top commit:
  apoelstra:
    ACK 55c627715f
  sanket1729:
    ACK 55c627715f . Reviewed range diff with ac0c908 that I previously ACKed

Tree-SHA512: 79b329b6e4e60af905e4e00507d6abc558261d921bcf8f5d4ee34dd685322d7a529b18015423da50a388ba6732b7b662a92bc95ad078228cc809254ad010d467
This commit is contained in:
sanket1729 2021-11-12 11:50:22 -08:00
commit abc242dfe1
No known key found for this signature in database
GPG Key ID: 648FFB183E0870A2
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 {
fn consensus_encode<S: $crate::io::Write>(
&self,
mut s: S,
s: S,
) -> Result<usize, $crate::io::Error> {
let mut len = 0;
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)?)
self.consensus_encode_map(s)
}
}
};

View File

@ -21,11 +21,11 @@ use blockdata::transaction::Transaction;
use consensus::{encode, Encodable, Decodable};
use consensus::encode::MAX_VEC_SIZE;
use util::psbt::map::Map;
use util::psbt::raw;
use util::psbt::{raw, PartiallySignedTransaction};
use util::psbt;
use util::psbt::Error;
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
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
const PSBT_GLOBAL_PROPRIETARY: u8 = 0xFC;
/// A key-value map for global data.
#[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 {
impl Map for PartiallySignedTransaction {
fn insert_pair(&mut self, pair: raw::Pair) -> Result<(), encode::Error> {
let raw::Pair {
key: raw_key,
@ -220,14 +177,21 @@ impl Map for Global {
self.proprietary.extend(other.proprietary);
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(())
}
}
impl_psbtmap_consensus_encoding!(Global);
impl Decodable for Global {
fn consensus_decode<D: io::Read>(d: D) -> Result<Self, encode::Error> {
impl PartiallySignedTransaction {
pub(crate) fn consensus_decode_global<D: io::Read>(d: D) -> Result<Self, encode::Error> {
let mut d = d.take(MAX_VEC_SIZE as u64);
let mut tx: Option<Transaction> = None;
let mut version: Option<u32> = None;
@ -334,12 +298,15 @@ impl Decodable for Global {
}
if let Some(tx) = tx {
let mut rv: Global = Global::from_unsigned_tx(tx)?;
rv.version = version.unwrap_or(0);
rv.xpub = xpub_map;
rv.unknown = unknowns;
rv.proprietary = proprietary;
Ok(rv)
Ok(PartiallySignedTransaction {
unsigned_tx: tx,
version: version.unwrap_or(0),
xpub: xpub_map,
proprietary,
unknown: unknowns,
inputs: vec![],
outputs: vec![]
})
} else {
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.
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
@ -37,6 +53,5 @@ mod global;
mod input;
mod output;
pub use self::global::Global;
pub use self::input::Input;
pub use self::output::Output;

View File

@ -38,14 +38,29 @@ mod macros;
pub mod serialize;
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.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PartiallySignedTransaction {
/// The key-value pairs for all global data.
pub global: 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>>,
/// The corresponding key-value map for each input in the unsigned
/// transaction.
pub inputs: Vec<Input>,
@ -55,20 +70,43 @@ pub struct 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
/// if not unsigned
pub fn from_unsigned_tx(tx: Transaction) -> Result<Self, self::Error> {
Ok(PartiallySignedTransaction {
pub fn from_unsigned_tx(tx: Transaction) -> Result<Self, Error> {
let psbt = PartiallySignedTransaction {
inputs: vec![Default::default(); tx.input.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
/// the available signature information in place.
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()) {
vin.script_sig = psbtin.final_script_sig.unwrap_or_else(Script::new);
@ -77,21 +115,6 @@ impl PartiallySignedTransaction {
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")]
@ -156,7 +179,7 @@ impl Encodable for PartiallySignedTransaction {
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 {
len += i.consensus_encode(&mut s)?;
@ -183,7 +206,8 @@ impl Decodable for PartiallySignedTransaction {
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_len: usize = (&global.unsigned_tx.input).len();
@ -209,11 +233,9 @@ impl Decodable for PartiallySignedTransaction {
outputs
};
Ok(PartiallySignedTransaction {
global: global,
inputs: inputs,
outputs: outputs,
})
global.inputs = inputs;
global.outputs = outputs;
Ok(global)
}
}
@ -232,7 +254,7 @@ mod tests {
use consensus::encode::{deserialize, serialize, serialize_hex};
use util::bip32::{ChildNumber, ExtendedPrivKey, ExtendedPubKey, Fingerprint, KeySource};
use util::ecdsa::PublicKey;
use util::psbt::map::{Global, Output, Input};
use util::psbt::map::{Output, Input};
use util::psbt::raw;
use super::PartiallySignedTransaction;
@ -242,18 +264,17 @@ mod tests {
#[test]
fn trivial_psbt() {
let psbt = PartiallySignedTransaction {
global: Global {
unsigned_tx: Transaction {
version: 2,
lock_time: 0,
input: vec![],
output: vec![],
},
xpub: Default::default(),
version: 0,
proprietary: BTreeMap::new(),
unknown: BTreeMap::new(),
unsigned_tx: Transaction {
version: 2,
lock_time: 0,
input: vec![],
output: vec![],
},
xpub: Default::default(),
version: 0,
proprietary: BTreeMap::new(),
unknown: BTreeMap::new(),
inputs: vec![],
outputs: vec![],
};
@ -309,7 +330,7 @@ mod tests {
#[test]
fn serialize_then_deserialize_global() {
let expected = Global {
let expected = PartiallySignedTransaction {
unsigned_tx: Transaction {
version: 2,
lock_time: 1257139,
@ -343,9 +364,16 @@ mod tests {
version: 0,
proprietary: 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);
}
@ -419,23 +447,22 @@ mod tests {
)].into_iter().collect();
let psbt = PartiallySignedTransaction {
global: Global {
version: 0,
xpub: {
let xpub: ExtendedPubKey =
"xpub661MyMwAqRbcGoRVtwfvzZsq2VBJR1LAHfQstHUoxqDorV89vRoMxUZ27kLrraAj6MPi\
QfrDb27gigC1VS1dBXi5jGpxmMeBXEkKkcXUTg4".parse().unwrap();
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(),
version: 0,
xpub: {
let xpub: ExtendedPubKey =
"xpub661MyMwAqRbcGoRVtwfvzZsq2VBJR1LAHfQstHUoxqDorV89vRoMxUZ27kLrraAj6MPi\
QfrDb27gigC1VS1dBXi5jGpxmMeBXEkKkcXUTg4".parse().unwrap();
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(),
inputs: vec![Input {
non_witness_utxo: Some(tx),
witness_utxo: Some(TxOut {
@ -481,7 +508,7 @@ mod tests {
use blockdata::script::Script;
use blockdata::transaction::{SigHashType, Transaction, TxIn, TxOut, OutPoint};
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::{PartiallySignedTransaction, Error};
use std::collections::BTreeMap;
@ -565,37 +592,36 @@ mod tests {
#[test]
fn valid_vector_1() {
let unserialized = PartiallySignedTransaction {
global: Global {
unsigned_tx: Transaction {
version: 2,
lock_time: 1257139,
input: vec![TxIn {
previous_output: OutPoint {
txid: Txid::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"),
},
],
},
xpub: Default::default(),
version: 0,
proprietary: BTreeMap::new(),
unknown: BTreeMap::new(),
unsigned_tx: Transaction {
version: 2,
lock_time: 1257139,
input: vec![TxIn {
previous_output: OutPoint {
txid: Txid::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"),
},
],
},
xpub: Default::default(),
version: 0,
proprietary: BTreeMap::new(),
unknown: BTreeMap::new(),
inputs: vec![Input {
non_witness_utxo: Some(Transaction {
version: 1,
@ -695,7 +721,7 @@ mod tests {
assert_eq!(psbt.inputs.len(), 1);
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();
assert_eq!(tx_input.previous_output.txid, psbt_non_witness_utxo.txid());
@ -763,7 +789,7 @@ mod tests {
assert_eq!(psbt.inputs.len(), 1);
assert_eq!(psbt.outputs.len(), 1);
let tx = &psbt.global.unsigned_tx;
let tx = &psbt.unsigned_tx;
assert_eq!(
tx.txid(),
Txid::from_hex(
@ -798,37 +824,36 @@ mod tests {
// same vector as valid_vector_1 from BIPs with added
let mut unserialized = PartiallySignedTransaction {
global: Global {
unsigned_tx: Transaction {
version: 2,
lock_time: 1257139,
input: vec![TxIn {
previous_output: OutPoint {
txid: Txid::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"),
},
],
},
version: 0,
xpub: Default::default(),
proprietary: Default::default(),
unknown: BTreeMap::new(),
unsigned_tx: Transaction {
version: 2,
lock_time: 1257139,
input: vec![TxIn {
previous_output: OutPoint {
txid: Txid::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"),
},
],
},
version: 0,
xpub: Default::default(),
proprietary: Default::default(),
unknown: BTreeMap::new(),
inputs: vec![Input {
non_witness_utxo: Some(Transaction {
version: 1,
@ -902,14 +927,14 @@ mod tests {
#[test]
fn serialize_and_deserialize_proprietary() {
let mut psbt: PartiallySignedTransaction = hex_psbt!("70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac000000000001076a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa882920001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000").unwrap();
psbt.global.proprietary.insert(ProprietaryKey {
psbt.proprietary.insert(ProprietaryKey {
prefix: b"test".to_vec(),
subtype: 0u8,
key: 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();
assert!(!rtt.global.proprietary.is_empty());
assert!(!rtt.proprietary.is_empty());
}
}