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.
|
||||
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;
|
||||
|
||||
#[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 {
|
||||
|
|
|
@ -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