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:
parent
115f8c043c
commit
9c08dbae47
|
@ -518,6 +518,7 @@ impl_array!(8);
|
||||||
impl_array!(12);
|
impl_array!(12);
|
||||||
impl_array!(16);
|
impl_array!(16);
|
||||||
impl_array!(32);
|
impl_array!(32);
|
||||||
|
impl_array!(33);
|
||||||
|
|
||||||
impl<S: Encoder, T: Encodable<S>> Encodable<S> for [T] {
|
impl<S: Encoder, T: Encodable<S>> Encodable<S> for [T] {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -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 {
|
macro_rules! impl_psbt_de_serialize {
|
||||||
($thing:ty) => {
|
($thing:ty) => {
|
||||||
impl_psbt_serialize!($thing);
|
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),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -16,5 +16,7 @@ pub trait Map {
|
||||||
|
|
||||||
// place at end to pick up macros
|
// place at end to pick up macros
|
||||||
mod global;
|
mod global;
|
||||||
|
mod output;
|
||||||
|
|
||||||
pub use self::global::Global;
|
pub use self::global::Global;
|
||||||
|
pub use self::output::Output;
|
||||||
|
|
|
@ -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);
|
|
@ -15,19 +15,72 @@ mod macros;
|
||||||
pub mod serialize;
|
pub mod serialize;
|
||||||
|
|
||||||
mod map;
|
mod map;
|
||||||
pub use self::map::{Map, Global};
|
pub use self::map::{Map, Global, Output};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use bitcoin_hashes::hex::FromHex;
|
use bitcoin_hashes::hex::FromHex;
|
||||||
use bitcoin_hashes::sha256d;
|
use bitcoin_hashes::sha256d;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use hex::decode as hex_decode;
|
||||||
|
|
||||||
|
use secp256k1::Secp256k1;
|
||||||
|
|
||||||
use blockdata::script::Script;
|
use blockdata::script::Script;
|
||||||
use blockdata::transaction::{Transaction, TxIn, TxOut, OutPoint};
|
use blockdata::transaction::{Transaction, TxIn, TxOut, OutPoint};
|
||||||
use consensus::encode::{deserialize, serialize};
|
use network::constants::Network::Bitcoin;
|
||||||
use util::psbt::map::Global;
|
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;
|
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]
|
#[test]
|
||||||
fn serialize_then_deserialize_global() {
|
fn serialize_then_deserialize_global() {
|
||||||
let expected = Global {
|
let expected = Global {
|
||||||
|
|
|
@ -3,8 +3,14 @@
|
||||||
//! Defines traits used for (de)serializing PSBT values into/from raw
|
//! Defines traits used for (de)serializing PSBT values into/from raw
|
||||||
//! bytes in PSBT key-value pairs.
|
//! bytes in PSBT key-value pairs.
|
||||||
|
|
||||||
|
use std::io::{self, Cursor};
|
||||||
|
|
||||||
|
use blockdata::script::Script;
|
||||||
use blockdata::transaction::Transaction;
|
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
|
/// A trait for serializing a value as raw data for insertion into PSBT
|
||||||
/// key-value pairs.
|
/// key-value pairs.
|
||||||
|
@ -20,3 +26,71 @@ pub trait Deserialize: Sized {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_psbt_de_serialize!(Transaction);
|
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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue