Add PSBT global data key-value map type

- Implement psbt::Map trait for psbt::Global
- Add converting constructor logic from Transaction for psbt::Global
- Add (en)decoding logic for psbt::Global
  - Always deserialize unsigned_tx as non-witness

- Add trait for PSBT (de)serialization
- Implement PSBT (de)serialization trait for relevant psbt::Global types

- Add macros for consensus::encode-backed PSBT (de)serialization
  implementations
- Add macro for implementing encoding logic for PSBT key-value maps
This commit is contained in:
Carl Dong 2018-09-08 21:07:07 -07:00
parent 2715a6e777
commit 115f8c043c
5 changed files with 288 additions and 1 deletions

40
src/util/psbt/macros.rs Normal file
View File

@ -0,0 +1,40 @@
macro_rules! impl_psbt_de_serialize {
($thing:ty) => {
impl_psbt_serialize!($thing);
impl_psbt_deserialize!($thing);
};
}
macro_rules! impl_psbt_deserialize {
($thing:ty) => {
impl ::util::psbt::serialize::Deserialize for $thing {
fn deserialize(bytes: &[u8]) -> Result<Self, ::consensus::encode::Error> {
::consensus::encode::deserialize(&bytes[..])
}
}
};
}
macro_rules! impl_psbt_serialize {
($thing:ty) => {
impl ::util::psbt::serialize::Serialize for $thing {
fn serialize(&self) -> Vec<u8> {
::consensus::encode::serialize(self)
}
}
};
}
macro_rules! impl_psbtmap_consensus_encoding {
($thing:ty) => {
impl<S: ::consensus::encode::Encoder> ::consensus::encode::Encodable<S> for $thing {
fn consensus_encode(&self, s: &mut S) -> Result<(), ::consensus::encode::Error> {
for pair in ::util::psbt::Map::get_pairs(self)? {
::consensus::encode::Encodable::consensus_encode(&pair, s)?
}
::consensus::encode::Encodable::consensus_encode(&0x00_u8, s)
}
}
};
}

169
src/util/psbt/map/global.rs Normal file
View File

@ -0,0 +1,169 @@
use std::collections::HashMap;
use std::io::Cursor;
use blockdata::transaction::Transaction;
use consensus::encode::{self, Encodable, Decodable, Decoder};
use util::psbt::map::Map;
use util::psbt::raw;
use util::psbt;
use util::psbt::Error;
/// A key-value map for global data.
#[derive(Clone, Debug, PartialEq)]
pub struct Global {
/// The unsigned transaction, scriptSigs and witnesses for each input must be
/// empty.
pub unsigned_tx: Transaction,
/// Unknown global key-value pairs.
pub unknown: HashMap<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,
unknown: Default::default(),
})
}
}
impl Map for Global {
fn insert_pair(&mut self, pair: raw::Pair) -> Result<(), encode::Error> {
let raw::Pair {
key: raw_key,
value: raw_value,
} = pair;
match raw_key.type_value {
0u8 => {
return Err(Error::DuplicateKey(raw_key).into());
}
_ => {
if self.unknown.contains_key(&raw_key) {
return Err(Error::DuplicateKey(raw_key).into());
} else {
self.unknown.insert(raw_key, raw_value);
}
}
}
Ok(())
}
fn get_pairs(&self) -> Result<Vec<raw::Pair>, encode::Error> {
let mut rv: Vec<raw::Pair> = Default::default();
rv.push(raw::Pair {
key: raw::Key {
type_value: 0u8,
key: vec![],
},
value: {
// Manually serialized to ensure 0-input txs are serialized
// without witnesses.
let mut ret = Vec::new();
self.unsigned_tx.version.consensus_encode(&mut ret)?;
self.unsigned_tx.input.consensus_encode(&mut ret)?;
self.unsigned_tx.output.consensus_encode(&mut ret)?;
self.unsigned_tx.lock_time.consensus_encode(&mut ret)?;
ret
},
});
for (key, value) in self.unknown.iter() {
rv.push(raw::Pair {
key: key.clone(),
value: value.clone(),
});
}
Ok(rv)
}
fn merge(&mut self, other: Self) -> Result<(), psbt::Error> {
if self.unsigned_tx != other.unsigned_tx {
return Err(psbt::Error::UnexpectedUnsignedTx {
expected: self.unsigned_tx.clone(),
actual: other.unsigned_tx,
});
}
self.unknown.extend(other.unknown);
Ok(())
}
}
impl_psbtmap_consensus_encoding!(Global);
impl<D: Decoder> Decodable<D> for Global {
fn consensus_decode(d: &mut D) -> Result<Self, encode::Error> {
let mut tx: Option<Transaction> = None;
let mut unknowns: HashMap<raw::Key, Vec<u8>> = Default::default();
loop {
match raw::Pair::consensus_decode(d) {
Ok(pair) => {
match pair.key.type_value {
0u8 => {
// key has to be empty
if pair.key.key.is_empty() {
// there can only be one unsigned transaction
if tx.is_none() {
let vlen: usize = pair.value.len();
let mut decoder = Cursor::new(pair.value);
// Manually deserialized to ensure 0-input
// txs without witnesses are deserialized
// properly.
tx = Some(Transaction {
version: Decodable::consensus_decode(&mut decoder)?,
input: Decodable::consensus_decode(&mut decoder)?,
output: Decodable::consensus_decode(&mut decoder)?,
lock_time: Decodable::consensus_decode(&mut decoder)?,
});
if decoder.position() != vlen as u64 {
return Err(encode::Error::ParseFailed("data not consumed entirely when explicitly deserializing"))
}
} else {
return Err(Error::DuplicateKey(pair.key).into())
}
} else {
return Err(Error::InvalidKey(pair.key).into())
}
}
_ => {
if unknowns.contains_key(&pair.key) {
return Err(Error::DuplicateKey(pair.key).into());
} else {
unknowns.insert(pair.key, pair.value);
}
}
}
}
Err(::consensus::encode::Error::Psbt(::util::psbt::Error::NoMorePairs)) => break,
Err(e) => return Err(e),
}
}
if let Some(tx) = tx {
let mut rv: Global = Global::from_unsigned_tx(tx)?;
rv.unknown = unknowns;
Ok(rv)
} else {
Err(Error::MustHaveUnsignedTx.into())
}
}
}

View File

@ -13,3 +13,8 @@ pub trait Map {
/// Attempt to merge with another key-value map of the same type.
fn merge(&mut self, other: Self) -> Result<(), psbt::Error>;
}
// place at end to pick up macros
mod global;
pub use self::global::Global;

View File

@ -9,14 +9,65 @@ pub use self::error::Error;
pub mod raw;
#[macro_use]
mod macros;
pub mod serialize;
mod map;
pub use self::map::Map;
pub use self::map::{Map, Global};
#[cfg(test)]
mod tests {
use bitcoin_hashes::hex::FromHex;
use bitcoin_hashes::sha256d;
use blockdata::script::Script;
use blockdata::transaction::{Transaction, TxIn, TxOut, OutPoint};
use consensus::encode::{deserialize, serialize};
use util::psbt::map::Global;
use util::psbt::raw;
#[test]
fn serialize_then_deserialize_global() {
let expected = Global {
unsigned_tx: Transaction {
version: 2,
lock_time: 1257139,
input: vec![TxIn {
previous_output: OutPoint {
txid: sha256d::Hash::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"
),
},
],
},
unknown: Default::default(),
};
let actual: Global = deserialize(&serialize(&expected)).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn serialize_then_deserialize_psbtkvpair() {
let expected = raw::Pair {

View File

@ -0,0 +1,22 @@
//! # PSBT Serialization
//!
//! Defines traits used for (de)serializing PSBT values into/from raw
//! bytes in PSBT key-value pairs.
use blockdata::transaction::Transaction;
use consensus::encode;
/// A trait for serializing a value as raw data for insertion into PSBT
/// key-value pairs.
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.
pub trait Deserialize: Sized {
/// Deserialize a value from raw data.
fn deserialize(bytes: &[u8]) -> Result<Self, encode::Error>;
}
impl_psbt_de_serialize!(Transaction);