Add PSBT output data key-value map type

- Implement psbt::Map trait for psbt::Output
- Add (en)decoding logic for psbt::Output

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

- Add macro for merging fields for PSBT key-value maps
- Add macro for implementing decoding logic for PSBT key-value maps
- Add convenience macro for implementing both encoding and decoding
  logic for PSBT key-value maps
- Add macro for inserting raw PSBT key-value pairs into PSBT key-value
  maps
- Add macro for getting raw PSBT key-value pairs from PSBT key-value
  maps
This commit is contained in:
Carl Dong 2018-09-08 21:20:29 -07:00
parent 115f8c043c
commit 9c08dbae47
6 changed files with 323 additions and 4 deletions

View File

@ -518,6 +518,7 @@ impl_array!(8);
impl_array!(12);
impl_array!(16);
impl_array!(32);
impl_array!(33);
impl<S: Encoder, T: Encodable<S>> Encodable<S> for [T] {
#[inline]

View File

@ -1,3 +1,11 @@
macro_rules! merge {
($thing:ident, $slf:ident, $other:ident) => {
if let (&None, Some($thing)) = (&$slf.$thing, $other.$thing) {
$slf.$thing = Some($thing);
}
};
}
macro_rules! impl_psbt_de_serialize {
($thing:ty) => {
impl_psbt_serialize!($thing);
@ -38,3 +46,86 @@ macro_rules! impl_psbtmap_consensus_encoding {
}
};
}
macro_rules! impl_psbtmap_consensus_decoding {
($thing:ty) => {
impl<D: ::consensus::encode::Decoder> ::consensus::encode::Decodable<D> for $thing {
fn consensus_decode(d: &mut D) -> Result<Self, ::consensus::encode::Error> {
let mut rv: Self = ::std::default::Default::default();
loop {
match ::consensus::encode::Decodable::consensus_decode(d) {
Ok(pair) => ::util::psbt::Map::insert_pair(&mut rv, pair)?,
Err(::consensus::encode::Error::Psbt(::util::psbt::Error::NoMorePairs)) => return Ok(rv),
Err(e) => return Err(e),
}
}
}
}
};
}
macro_rules! impl_psbtmap_consensus_enc_dec_oding {
($thing:ty) => {
impl_psbtmap_consensus_decoding!($thing);
impl_psbtmap_consensus_encoding!($thing);
};
}
#[cfg_attr(rustfmt, rustfmt_skip)]
macro_rules! impl_psbt_insert_pair {
($slf:ident.$unkeyed_name:ident <= <$raw_key:ident: _>|<$raw_value:ident: $unkeyed_value_type:ty>) => {
if $raw_key.key.is_empty() {
if let None = $slf.$unkeyed_name {
let val: $unkeyed_value_type = ::util::psbt::serialize::Deserialize::deserialize(&$raw_value)?;
$slf.$unkeyed_name = Some(val)
} else {
return Err(::util::psbt::Error::DuplicateKey($raw_key).into());
}
} else {
return Err(::util::psbt::Error::InvalidKey($raw_key).into());
}
};
($slf:ident.$keyed_name:ident <= <$raw_key:ident: $keyed_key_type:ty>|<$raw_value:ident: $keyed_value_type:ty>) => {
if !$raw_key.key.is_empty() {
let key_val: $keyed_key_type = ::util::psbt::serialize::Deserialize::deserialize(&$raw_key.key)?;
if $slf.$keyed_name.contains_key(&key_val) {
return Err(::util::psbt::Error::DuplicateKey($raw_key).into());
} else {
let val: $keyed_value_type = ::util::psbt::serialize::Deserialize::deserialize(&$raw_value)?;
$slf.$keyed_name.insert(key_val, val);
}
} else {
return Err(::util::psbt::Error::InvalidKey($raw_key).into());
}
};
}
#[cfg_attr(rustfmt, rustfmt_skip)]
macro_rules! impl_psbt_get_pair {
($rv:ident.push($slf:ident.$unkeyed_name:ident as <$unkeyed_typeval:expr, _>|<$unkeyed_value_type:ty>)) => {
if let Some(ref $unkeyed_name) = $slf.$unkeyed_name {
$rv.push(::util::psbt::raw::Pair {
key: ::util::psbt::raw::Key {
type_value: $unkeyed_typeval,
key: vec![],
},
value: ::util::psbt::serialize::Serialize::serialize($unkeyed_name),
});
}
};
($rv:ident.push($slf:ident.$keyed_name:ident as <$keyed_typeval:expr, $keyed_key_type:ty>|<$keyed_value_type:ty>)) => {
for (key, val) in &$slf.$keyed_name {
$rv.push(::util::psbt::raw::Pair {
key: ::util::psbt::raw::Key {
type_value: $keyed_typeval,
key: ::util::psbt::serialize::Serialize::serialize(key),
},
value: ::util::psbt::serialize::Serialize::serialize(val),
});
}
};
}

View File

@ -16,5 +16,7 @@ pub trait Map {
// place at end to pick up macros
mod global;
mod output;
pub use self::global::Global;
pub use self::output::Output;

View File

@ -0,0 +1,98 @@
use std::collections::HashMap;
use blockdata::script::Script;
use consensus::encode;
use util::bip32::{DerivationPath, Fingerprint};
use util::key::PublicKey;
use util::psbt;
use util::psbt::map::Map;
use util::psbt::raw;
use util::psbt::Error;
/// A key-value map for an output of the corresponding index in the unsigned
/// transaction.
#[derive(Clone, Default, Debug, PartialEq)]
pub struct Output {
/// The redeem script for this output.
pub redeem_script: Option<Script>,
/// The witness script for this output.
pub witness_script: Option<Script>,
/// A map from public keys needed to spend this output to their
/// corresponding master key fingerprints and derivation paths.
pub hd_keypaths: HashMap<PublicKey, (Fingerprint, DerivationPath)>,
/// Unknown key-value pairs for this output.
pub unknown: HashMap<raw::Key, Vec<u8>>,
}
impl Map for Output {
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 => {
impl_psbt_insert_pair! {
self.redeem_script <= <raw_key: _>|<raw_value: Script>
}
}
1u8 => {
impl_psbt_insert_pair! {
self.witness_script <= <raw_key: _>|<raw_value: Script>
}
}
2u8 => {
impl_psbt_insert_pair! {
self.hd_keypaths <= <raw_key: PublicKey>|<raw_value: (Fingerprint, DerivationPath)>
}
}
_ => {
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();
impl_psbt_get_pair! {
rv.push(self.redeem_script as <0u8, _>|<Script>)
}
impl_psbt_get_pair! {
rv.push(self.witness_script as <1u8, _>|<Script>)
}
impl_psbt_get_pair! {
rv.push(self.hd_keypaths as <2u8, PublicKey>|<(Fingerprint, DerivationPath)>)
}
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> {
self.hd_keypaths.extend(other.hd_keypaths);
self.unknown.extend(other.unknown);
merge!(redeem_script, self, other);
merge!(witness_script, self, other);
Ok(())
}
}
impl_psbtmap_consensus_enc_dec_oding!(Output);

View File

@ -15,19 +15,72 @@ mod macros;
pub mod serialize;
mod map;
pub use self::map::{Map, Global};
pub use self::map::{Map, Global, Output};
#[cfg(test)]
mod tests {
use bitcoin_hashes::hex::FromHex;
use bitcoin_hashes::sha256d;
use std::collections::HashMap;
use hex::decode as hex_decode;
use secp256k1::Secp256k1;
use blockdata::script::Script;
use blockdata::transaction::{Transaction, TxIn, TxOut, OutPoint};
use consensus::encode::{deserialize, serialize};
use util::psbt::map::Global;
use network::constants::Network::Bitcoin;
use consensus::encode::{deserialize, serialize, serialize_hex};
use util::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint};
use util::key::PublicKey;
use util::psbt::map::{Global, Output};
use util::psbt::raw;
#[test]
fn serialize_then_deserialize_output() {
let secp = &Secp256k1::new();
let seed = hex_decode("000102030405060708090a0b0c0d0e0f").unwrap();
let mut hd_keypaths: HashMap<PublicKey, (Fingerprint, DerivationPath)> = Default::default();
let mut sk: ExtendedPrivKey = ExtendedPrivKey::new_master(Bitcoin, &seed).unwrap();
let fprint: Fingerprint = sk.fingerprint(&secp);
let dpath: Vec<ChildNumber> = vec![
ChildNumber::from_normal_idx(0).unwrap(),
ChildNumber::from_normal_idx(1).unwrap(),
ChildNumber::from_normal_idx(2).unwrap(),
ChildNumber::from_normal_idx(4).unwrap(),
ChildNumber::from_normal_idx(42).unwrap(),
ChildNumber::from_hardened_idx(69).unwrap(),
ChildNumber::from_normal_idx(420).unwrap(),
ChildNumber::from_normal_idx(31337).unwrap(),
];
sk = sk.derive_priv(secp, &dpath).unwrap();
let pk: ExtendedPubKey = ExtendedPubKey::from_private(&secp, &sk);
hd_keypaths.insert(pk.public_key, (fprint, dpath.into()));
let expected: Output = Output {
redeem_script: Some(hex_script!(
"76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac"
)),
witness_script: Some(hex_script!(
"a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"
)),
hd_keypaths: hd_keypaths,
..Default::default()
};
let actual: Output = deserialize(&serialize(&expected)).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn serialize_then_deserialize_global() {
let expected = Global {

View File

@ -3,8 +3,14 @@
//! Defines traits used for (de)serializing PSBT values into/from raw
//! bytes in PSBT key-value pairs.
use std::io::{self, Cursor};
use blockdata::script::Script;
use blockdata::transaction::Transaction;
use consensus::encode;
use consensus::encode::{self, serialize, Decodable};
use util::bip32::{ChildNumber, DerivationPath, Fingerprint};
use util::key::PublicKey;
use util::psbt;
/// A trait for serializing a value as raw data for insertion into PSBT
/// key-value pairs.
@ -20,3 +26,71 @@ pub trait Deserialize: Sized {
}
impl_psbt_de_serialize!(Transaction);
impl Serialize for Script {
fn serialize(&self) -> Vec<u8> {
self.to_bytes()
}
}
impl Deserialize for Script {
fn deserialize(bytes: &[u8]) -> Result<Self, encode::Error> {
Ok(Self::from(bytes.to_vec()))
}
}
impl Serialize for PublicKey {
fn serialize(&self) -> Vec<u8> {
let mut buf = Vec::new();
self.write_into(&mut buf);
buf
}
}
impl Deserialize for PublicKey {
fn deserialize(bytes: &[u8]) -> Result<Self, encode::Error> {
PublicKey::from_slice(bytes)
.map_err(|_| encode::Error::ParseFailed("invalid public key"))
}
}
impl Serialize for (Fingerprint, DerivationPath) {
fn serialize(&self) -> Vec<u8> {
let mut rv: Vec<u8> = Vec::with_capacity(4 + 4 * (self.1).as_ref().len());
rv.append(&mut self.0.to_bytes().to_vec());
for cnum in self.1.into_iter() {
rv.append(&mut serialize(&u32::from(*cnum)))
}
rv
}
}
impl Deserialize for (Fingerprint, DerivationPath) {
fn deserialize(bytes: &[u8]) -> Result<Self, encode::Error> {
if bytes.len() < 4 {
return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into())
}
let fprint: Fingerprint = Fingerprint::from(&bytes[0..4]);
let mut dpath: Vec<ChildNumber> = Default::default();
let d = &mut Cursor::new(&bytes[4..]);
loop {
match Decodable::consensus_decode(d) {
Ok(index) => {
dpath.push(<ChildNumber as From<u32>>::from(index));
if d.position() == (bytes.len() - 4) as u64 {
break;
}
},
Err(e) => return Err(e),
}
}
Ok((fprint, dpath.into()))
}
}