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: ACK55c627715f
sanket1729: ACK55c627715f
. Reviewed range diff with ac0c908 that I previously ACKed Tree-SHA512: 79b329b6e4e60af905e4e00507d6abc558261d921bcf8f5d4ee34dd685322d7a529b18015423da50a388ba6732b7b662a92bc95ad078228cc809254ad010d467
This commit is contained in:
commit
abc242dfe1
|
@ -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)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,7 +264,6 @@ mod tests {
|
|||
#[test]
|
||||
fn trivial_psbt() {
|
||||
let psbt = PartiallySignedTransaction {
|
||||
global: Global {
|
||||
unsigned_tx: Transaction {
|
||||
version: 2,
|
||||
lock_time: 0,
|
||||
|
@ -253,7 +274,7 @@ mod tests {
|
|||
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,7 +447,6 @@ mod tests {
|
|||
)].into_iter().collect();
|
||||
|
||||
let psbt = PartiallySignedTransaction {
|
||||
global: Global {
|
||||
version: 0,
|
||||
xpub: {
|
||||
let xpub: ExtendedPubKey =
|
||||
|
@ -435,7 +462,7 @@ mod tests {
|
|||
},
|
||||
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,7 +592,6 @@ mod tests {
|
|||
#[test]
|
||||
fn valid_vector_1() {
|
||||
let unserialized = PartiallySignedTransaction {
|
||||
global: Global {
|
||||
unsigned_tx: Transaction {
|
||||
version: 2,
|
||||
lock_time: 1257139,
|
||||
|
@ -595,7 +621,7 @@ mod tests {
|
|||
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,7 +824,6 @@ 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,
|
||||
|
@ -828,7 +853,7 @@ mod tests {
|
|||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue