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 {
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")]
@ -151,7 +174,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)?;
@ -178,7 +201,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();
@ -204,11 +228,9 @@ impl Decodable for PartiallySignedTransaction {
outputs
};
Ok(PartiallySignedTransaction {
global: global,
inputs: inputs,
outputs: outputs,
})
global.inputs = inputs;
global.outputs = outputs;
Ok(global)
}
}
@ -227,7 +249,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;
@ -237,18 +259,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![],
};
@ -304,7 +325,7 @@ mod tests {
#[test]
fn serialize_then_deserialize_global() {
let expected = Global {
let expected = PartiallySignedTransaction {
unsigned_tx: Transaction {
version: 2,
lock_time: 1257139,
@ -338,9 +359,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);
}
@ -414,23 +442,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 {
@ -476,7 +503,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;
@ -560,37 +587,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,
@ -690,7 +716,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());
@ -758,7 +784,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(
@ -793,37 +819,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,
@ -897,14 +922,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());
}
}