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:
parent
2715a6e777
commit
115f8c043c
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,3 +13,8 @@ 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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// place at end to pick up macros
|
||||||
|
mod global;
|
||||||
|
|
||||||
|
pub use self::global::Global;
|
||||||
|
|
|
@ -9,14 +9,65 @@ pub use self::error::Error;
|
||||||
|
|
||||||
pub mod raw;
|
pub mod raw;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
|
||||||
|
pub mod serialize;
|
||||||
|
|
||||||
mod map;
|
mod map;
|
||||||
pub use self::map::Map;
|
pub use self::map::{Map, Global};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
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 consensus::encode::{deserialize, serialize};
|
||||||
|
use util::psbt::map::Global;
|
||||||
use util::psbt::raw;
|
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]
|
#[test]
|
||||||
fn serialize_then_deserialize_psbtkvpair() {
|
fn serialize_then_deserialize_psbtkvpair() {
|
||||||
let expected = raw::Pair {
|
let expected = raw::Pair {
|
||||||
|
|
|
@ -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);
|
Loading…
Reference in New Issue