Merge pull request #103 from dongcarl/psbt
BIP174 (de)serialization support
This commit is contained in:
commit
67e4671457
|
@ -47,3 +47,7 @@ path = "fuzz_targets/deserialize_udecimal.rs"
|
|||
[[bin]]
|
||||
name = "outpoint_string"
|
||||
path = "fuzz_targets/outpoint_string.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "deserialize_psbt"
|
||||
path = "fuzz_targets/deserialize_psbt.rs"
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
extern crate bitcoin;
|
||||
|
||||
fn do_test(data: &[u8]) {
|
||||
let _: Result<bitcoin::util::psbt::PartiallySignedTransaction, _> = bitcoin::consensus::encode::deserialize(data);
|
||||
}
|
||||
|
||||
#[cfg(feature = "afl")]
|
||||
#[macro_use] extern crate afl;
|
||||
#[cfg(feature = "afl")]
|
||||
fn main() {
|
||||
fuzz!(|data| {
|
||||
do_test(&data);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "honggfuzz")]
|
||||
#[macro_use] extern crate honggfuzz;
|
||||
#[cfg(feature = "honggfuzz")]
|
||||
fn main() {
|
||||
loop {
|
||||
fuzz!(|data| {
|
||||
do_test(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn extend_vec_from_hex(hex: &str, out: &mut Vec<u8>) {
|
||||
let mut b = 0;
|
||||
for (idx, c) in hex.as_bytes().iter().enumerate() {
|
||||
b <<= 4;
|
||||
match *c {
|
||||
b'A'...b'F' => b |= c - b'A' + 10,
|
||||
b'a'...b'f' => b |= c - b'a' + 10,
|
||||
b'0'...b'9' => b |= c - b'0',
|
||||
_ => panic!("Bad hex"),
|
||||
}
|
||||
if (idx & 1) == 1 {
|
||||
out.push(b);
|
||||
b = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_crash() {
|
||||
let mut a = Vec::new();
|
||||
extend_vec_from_hex("00", &mut a);
|
||||
super::do_test(&a);
|
||||
}
|
||||
}
|
|
@ -45,6 +45,7 @@ use bitcoin_hashes::{sha256d, Hash as HashTrait};
|
|||
use secp256k1;
|
||||
|
||||
use util::base58;
|
||||
use util::psbt;
|
||||
|
||||
/// Encoding error
|
||||
#[derive(Debug)]
|
||||
|
@ -59,6 +60,8 @@ pub enum Error {
|
|||
ByteOrder(io::Error),
|
||||
/// secp-related error
|
||||
Secp256k1(secp256k1::Error),
|
||||
/// PSBT-related error
|
||||
Psbt(psbt::Error),
|
||||
/// Network magic was not expected
|
||||
UnexpectedNetworkMagic {
|
||||
/// The expected network magic
|
||||
|
@ -102,6 +105,7 @@ impl fmt::Display for Error {
|
|||
Error::Bech32(ref e) => fmt::Display::fmt(e, f),
|
||||
Error::ByteOrder(ref e) => fmt::Display::fmt(e, f),
|
||||
Error::Secp256k1(ref e) => fmt::Display::fmt(e, f),
|
||||
Error::Psbt(ref e) => fmt::Display::fmt(e, f),
|
||||
Error::UnexpectedNetworkMagic { expected: ref e, actual: ref a } => write!(f, "{}: expected {}, actual {}", error::Error::description(self), e, a),
|
||||
Error::OversizedVectorAllocation { requested: ref r, max: ref m } => write!(f, "{}: requested {}, maximum {}", error::Error::description(self), r, m),
|
||||
Error::InvalidChecksum { expected: ref e, actual: ref a } => write!(f, "{}: expected {}, actual {}", error::Error::description(self), hex_encode(e), hex_encode(a)),
|
||||
|
@ -123,6 +127,7 @@ impl error::Error for Error {
|
|||
Error::Bech32(ref e) => Some(e),
|
||||
Error::ByteOrder(ref e) => Some(e),
|
||||
Error::Secp256k1(ref e) => Some(e),
|
||||
Error::Psbt(ref e) => Some(e),
|
||||
Error::UnexpectedNetworkMagic { .. }
|
||||
| Error::OversizedVectorAllocation { .. }
|
||||
| Error::InvalidChecksum { .. }
|
||||
|
@ -142,6 +147,7 @@ impl error::Error for Error {
|
|||
Error::Bech32(ref e) => e.description(),
|
||||
Error::ByteOrder(ref e) => e.description(),
|
||||
Error::Secp256k1(ref e) => e.description(),
|
||||
Error::Psbt(ref e) => e.description(),
|
||||
Error::UnexpectedNetworkMagic { .. } => "unexpected network magic",
|
||||
Error::OversizedVectorAllocation { .. } => "allocation of oversized vector requested",
|
||||
Error::InvalidChecksum { .. } => "invalid checksum",
|
||||
|
@ -183,6 +189,13 @@ impl From<io::Error> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl From<psbt::Error> for Error {
|
||||
fn from(e: psbt::Error) -> Error {
|
||||
Error::Psbt(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode an object into a vector
|
||||
pub fn serialize<T: ?Sized>(data: &T) -> Vec<u8>
|
||||
where T: Encodable<Cursor<Vec<u8>>>,
|
||||
|
@ -505,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]
|
||||
|
|
|
@ -25,6 +25,7 @@ pub mod contracthash;
|
|||
pub mod decimal;
|
||||
pub mod hash;
|
||||
pub mod misc;
|
||||
pub mod psbt;
|
||||
pub mod uint;
|
||||
|
||||
use std::{error, fmt};
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
// Rust Bitcoin Library
|
||||
// Written by
|
||||
// The Rust Bitcoin developers
|
||||
//
|
||||
// To the extent possible under law, the author(s) have dedicated all
|
||||
// copyright and related and neighboring rights to this software to
|
||||
// the public domain worldwide. This software is distributed without
|
||||
// any warranty.
|
||||
//
|
||||
// You should have received a copy of the CC0 Public Domain Dedication
|
||||
// along with this software.
|
||||
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
//
|
||||
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
|
||||
use blockdata::transaction::Transaction;
|
||||
use util::psbt::raw;
|
||||
|
||||
/// Ways that a Partially Signed Transaction might fail.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Magic bytes for a PSBT must be the ASCII for "psbt" serialized in most
|
||||
/// significant byte order.
|
||||
InvalidMagic,
|
||||
/// The separator for a PSBT must be `0xff`.
|
||||
InvalidSeparator,
|
||||
/// Known keys must be according to spec.
|
||||
InvalidKey(raw::Key),
|
||||
/// Keys within key-value map should never be duplicated.
|
||||
DuplicateKey(raw::Key),
|
||||
/// The scriptSigs for the unsigned transaction must be empty.
|
||||
UnsignedTxHasScriptSigs,
|
||||
/// The scriptWitnesses for the unsigned transaction must be empty.
|
||||
UnsignedTxHasScriptWitnesses,
|
||||
/// A PSBT must have an unsigned transaction.
|
||||
MustHaveUnsignedTx,
|
||||
/// Signals that there are no more key-value pairs in a key-value map.
|
||||
NoMorePairs,
|
||||
/// Attempting to merge with a PSBT describing a different unsigned
|
||||
/// transaction.
|
||||
UnexpectedUnsignedTx {
|
||||
/// Expected
|
||||
expected: Transaction,
|
||||
/// Actual
|
||||
actual: Transaction,
|
||||
},
|
||||
/// Unable to parse as a standard SigHash type.
|
||||
NonStandardSigHashType(u32),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::InvalidKey(ref rkey) => write!(f, "{}: {}", error::Error::description(self), rkey),
|
||||
Error::DuplicateKey(ref rkey) => write!(f, "{}: {}", error::Error::description(self), rkey),
|
||||
Error::UnexpectedUnsignedTx { expected: ref e, actual: ref a } => write!(f, "{}: expected {}, actual {}", error::Error::description(self), e.txid(), a.txid()),
|
||||
Error::NonStandardSigHashType(ref sht) => write!(f, "{}: {}", error::Error::description(self), sht),
|
||||
Error::InvalidMagic
|
||||
| Error::InvalidSeparator
|
||||
| Error::UnsignedTxHasScriptSigs
|
||||
| Error::UnsignedTxHasScriptWitnesses
|
||||
| Error::MustHaveUnsignedTx
|
||||
| Error::NoMorePairs => f.write_str(error::Error::description(self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Error::InvalidMagic => "invalid magic",
|
||||
Error::InvalidSeparator => "invalid separator",
|
||||
Error::InvalidKey(..) => "invalid key",
|
||||
Error::DuplicateKey(..) => "duplicate key",
|
||||
Error::UnsignedTxHasScriptSigs => "the unsigned transaction has script sigs",
|
||||
Error::UnsignedTxHasScriptWitnesses => "the unsigned transaction has script witnesses",
|
||||
Error::MustHaveUnsignedTx => {
|
||||
"partially signed transactions must have an unsigned transaction"
|
||||
}
|
||||
Error::NoMorePairs => "no more key-value pairs for this psbt map",
|
||||
Error::UnexpectedUnsignedTx { .. } => "different unsigned transaction",
|
||||
Error::NonStandardSigHashType(..) => "non-standard sighash type",
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
// Rust Bitcoin Library
|
||||
// Written by
|
||||
// The Rust Bitcoin developers
|
||||
//
|
||||
// To the extent possible under law, the author(s) have dedicated all
|
||||
// copyright and related and neighboring rights to this software to
|
||||
// the public domain worldwide. This software is distributed without
|
||||
// any warranty.
|
||||
//
|
||||
// You should have received a copy of the CC0 Public Domain Dedication
|
||||
// along with this software.
|
||||
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
//
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! hex_psbt {
|
||||
($s:expr) => { ::consensus::encode::deserialize(&::hex::decode($s).unwrap()) };
|
||||
}
|
||||
|
||||
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);
|
||||
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)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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),
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
// Rust Bitcoin Library
|
||||
// Written by
|
||||
// The Rust Bitcoin developers
|
||||
//
|
||||
// To the extent possible under law, the author(s) have dedicated all
|
||||
// copyright and related and neighboring rights to this software to
|
||||
// the public domain worldwide. This software is distributed without
|
||||
// any warranty.
|
||||
//
|
||||
// You should have received a copy of the CC0 Public Domain Dedication
|
||||
// along with this software.
|
||||
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
//
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
// Rust Bitcoin Library
|
||||
// Written by
|
||||
// The Rust Bitcoin developers
|
||||
//
|
||||
// To the extent possible under law, the author(s) have dedicated all
|
||||
// copyright and related and neighboring rights to this software to
|
||||
// the public domain worldwide. This software is distributed without
|
||||
// any warranty.
|
||||
//
|
||||
// You should have received a copy of the CC0 Public Domain Dedication
|
||||
// along with this software.
|
||||
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
//
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use blockdata::script::Script;
|
||||
use blockdata::transaction::{SigHashType, Transaction, TxOut};
|
||||
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 input of the corresponding index in the unsigned
|
||||
/// transaction.
|
||||
#[derive(Clone, Default, Debug, PartialEq)]
|
||||
pub struct Input {
|
||||
/// The non-witness transaction this input spends from. Should only be
|
||||
/// [std::option::Option::Some] for inputs which spend non-segwit outputs or
|
||||
/// if it is unknown whether an input spends a segwit output.
|
||||
pub non_witness_utxo: Option<Transaction>,
|
||||
/// The transaction output this input spends from. Should only be
|
||||
/// [std::option::Option::Some] for inputs which spend segwit outputs,
|
||||
/// including P2SH embedded ones.
|
||||
pub witness_utxo: Option<TxOut>,
|
||||
/// A map from public keys to their corresponding signature as would be
|
||||
/// pushed to the stack from a scriptSig or witness.
|
||||
pub partial_sigs: HashMap<PublicKey, Vec<u8>>,
|
||||
/// The sighash type to be used for this input. Signatures for this input
|
||||
/// must use the sighash type.
|
||||
pub sighash_type: Option<SigHashType>,
|
||||
/// The redeem script for this input.
|
||||
pub redeem_script: Option<Script>,
|
||||
/// The witness script for this input.
|
||||
pub witness_script: Option<Script>,
|
||||
/// A map from public keys needed to sign this input to their corresponding
|
||||
/// master key fingerprints and derivation paths.
|
||||
pub hd_keypaths: HashMap<PublicKey, (Fingerprint, DerivationPath)>,
|
||||
/// The finalized, fully-constructed scriptSig with signatures and any other
|
||||
/// scripts necessary for this input to pass validation.
|
||||
pub final_script_sig: Option<Script>,
|
||||
/// The finalized, fully-constructed scriptWitness with signatures and any
|
||||
/// other scripts necessary for this input to pass validation.
|
||||
pub final_script_witness: Option<Vec<Vec<u8>>>,
|
||||
/// Unknown key-value pairs for this input.
|
||||
pub unknown: HashMap<raw::Key, Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Map for Input {
|
||||
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.non_witness_utxo <= <raw_key: _>|<raw_value: Transaction>
|
||||
}
|
||||
}
|
||||
1u8 => {
|
||||
impl_psbt_insert_pair! {
|
||||
self.witness_utxo <= <raw_key: _>|<raw_value: TxOut>
|
||||
}
|
||||
}
|
||||
3u8 => {
|
||||
impl_psbt_insert_pair! {
|
||||
self.sighash_type <= <raw_key: _>|<raw_value: SigHashType>
|
||||
}
|
||||
}
|
||||
4u8 => {
|
||||
impl_psbt_insert_pair! {
|
||||
self.redeem_script <= <raw_key: _>|<raw_value: Script>
|
||||
}
|
||||
}
|
||||
5u8 => {
|
||||
impl_psbt_insert_pair! {
|
||||
self.witness_script <= <raw_key: _>|<raw_value: Script>
|
||||
}
|
||||
}
|
||||
7u8 => {
|
||||
impl_psbt_insert_pair! {
|
||||
self.final_script_sig <= <raw_key: _>|<raw_value: Script>
|
||||
}
|
||||
}
|
||||
8u8 => {
|
||||
impl_psbt_insert_pair! {
|
||||
self.final_script_witness <= <raw_key: _>|<raw_value: Vec<Vec<u8>>>
|
||||
}
|
||||
}
|
||||
2u8 => {
|
||||
impl_psbt_insert_pair! {
|
||||
self.partial_sigs <= <raw_key: PublicKey>|<raw_value: Vec<u8>>
|
||||
}
|
||||
}
|
||||
6u8 => {
|
||||
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.non_witness_utxo as <0u8, _>|<Transaction>)
|
||||
}
|
||||
|
||||
impl_psbt_get_pair! {
|
||||
rv.push(self.witness_utxo as <1u8, _>|<TxOut>)
|
||||
}
|
||||
|
||||
impl_psbt_get_pair! {
|
||||
rv.push(self.partial_sigs as <2u8, PublicKey>|<Vec<u8>>)
|
||||
}
|
||||
|
||||
impl_psbt_get_pair! {
|
||||
rv.push(self.sighash_type as <3u8, _>|<SigHashType>)
|
||||
}
|
||||
|
||||
impl_psbt_get_pair! {
|
||||
rv.push(self.redeem_script as <4u8, _>|<Script>)
|
||||
}
|
||||
|
||||
impl_psbt_get_pair! {
|
||||
rv.push(self.witness_script as <5u8, _>|<Script>)
|
||||
}
|
||||
|
||||
impl_psbt_get_pair! {
|
||||
rv.push(self.hd_keypaths as <6u8, PublicKey>|<(Fingerprint, DerivationPath)>)
|
||||
}
|
||||
|
||||
impl_psbt_get_pair! {
|
||||
rv.push(self.final_script_sig as <7u8, _>|<Script>)
|
||||
}
|
||||
|
||||
impl_psbt_get_pair! {
|
||||
rv.push(self.final_script_witness as <8u8, _>|<Script>)
|
||||
}
|
||||
|
||||
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> {
|
||||
merge!(non_witness_utxo, self, other);
|
||||
|
||||
if let (&None, Some(witness_utxo)) = (&self.witness_utxo, other.witness_utxo) {
|
||||
self.witness_utxo = Some(witness_utxo);
|
||||
self.non_witness_utxo = None; // Clear out any non-witness UTXO when we set a witness one
|
||||
}
|
||||
|
||||
self.partial_sigs.extend(other.partial_sigs);
|
||||
self.hd_keypaths.extend(other.hd_keypaths);
|
||||
self.unknown.extend(other.unknown);
|
||||
|
||||
merge!(redeem_script, self, other);
|
||||
merge!(witness_script, self, other);
|
||||
merge!(final_script_sig, self, other);
|
||||
merge!(final_script_witness, self, other);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl_psbtmap_consensus_enc_dec_oding!(Input);
|
|
@ -0,0 +1,38 @@
|
|||
// Rust Bitcoin Library
|
||||
// Written by
|
||||
// The Rust Bitcoin developers
|
||||
//
|
||||
// To the extent possible under law, the author(s) have dedicated all
|
||||
// copyright and related and neighboring rights to this software to
|
||||
// the public domain worldwide. This software is distributed without
|
||||
// any warranty.
|
||||
//
|
||||
// You should have received a copy of the CC0 Public Domain Dedication
|
||||
// along with this software.
|
||||
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
//
|
||||
|
||||
use consensus::encode;
|
||||
use util::psbt;
|
||||
use util::psbt::raw;
|
||||
|
||||
/// A trait that describes a PSBT key-value map.
|
||||
pub trait Map {
|
||||
/// Attempt to insert a key-value pair.
|
||||
fn insert_pair(&mut self, pair: raw::Pair) -> Result<(), encode::Error>;
|
||||
|
||||
/// Attempt to get all key-value pairs.
|
||||
fn get_pairs(&self) -> Result<Vec<raw::Pair>, encode::Error>;
|
||||
|
||||
/// 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;
|
||||
mod input;
|
||||
mod output;
|
||||
|
||||
pub use self::global::Global;
|
||||
pub use self::input::Input;
|
||||
pub use self::output::Output;
|
|
@ -0,0 +1,112 @@
|
|||
// Rust Bitcoin Library
|
||||
// Written by
|
||||
// The Rust Bitcoin developers
|
||||
//
|
||||
// To the extent possible under law, the author(s) have dedicated all
|
||||
// copyright and related and neighboring rights to this software to
|
||||
// the public domain worldwide. This software is distributed without
|
||||
// any warranty.
|
||||
//
|
||||
// You should have received a copy of the CC0 Public Domain Dedication
|
||||
// along with this software.
|
||||
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
//
|
||||
|
||||
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);
|
|
@ -0,0 +1,555 @@
|
|||
// Rust Bitcoin Library
|
||||
// Written by
|
||||
// The Rust Bitcoin developers
|
||||
//
|
||||
// To the extent possible under law, the author(s) have dedicated all
|
||||
// copyright and related and neighboring rights to this software to
|
||||
// the public domain worldwide. This software is distributed without
|
||||
// any warranty.
|
||||
//
|
||||
// You should have received a copy of the CC0 Public Domain Dedication
|
||||
// along with this software.
|
||||
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
//
|
||||
|
||||
//! # Partially Signed Transactions
|
||||
//!
|
||||
//! Implementation of BIP174 Partially Signed Bitcoin Transaction Format as
|
||||
//! defined at https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
|
||||
//! except we define PSBTs containing non-standard SigHash types as invalid.
|
||||
|
||||
use blockdata::script::Script;
|
||||
use blockdata::transaction::Transaction;
|
||||
use consensus::encode::{self, Encodable, Decodable, Encoder, Decoder};
|
||||
|
||||
mod error;
|
||||
pub use self::error::Error;
|
||||
|
||||
pub mod raw;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
pub mod serialize;
|
||||
|
||||
mod map;
|
||||
pub use self::map::{Map, Global, Input, Output};
|
||||
|
||||
/// A Partially Signed Transaction.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PartiallySignedTransaction {
|
||||
/// The key-value pairs for all global data.
|
||||
pub global: Global,
|
||||
/// The corresponding key-value map for each input in the unsigned
|
||||
/// transaction.
|
||||
pub inputs: Vec<Input>,
|
||||
/// The corresponding key-value map for each output in the unsigned
|
||||
/// transaction.
|
||||
pub outputs: Vec<Output>,
|
||||
}
|
||||
|
||||
impl PartiallySignedTransaction {
|
||||
/// Create a PartiallySignedTransaction from an unsigned transaction, error
|
||||
/// if not unsigned
|
||||
pub fn from_unsigned_tx(tx: Transaction) -> Result<Self, encode::Error> {
|
||||
Ok(PartiallySignedTransaction {
|
||||
inputs: vec![Default::default(); tx.input.len()],
|
||||
outputs: vec![Default::default(); tx.output.len()],
|
||||
global: Global::from_unsigned_tx(tx)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Extract the Transaction from a PartiallySignedTransaction by filling in
|
||||
/// the available signature information in place.
|
||||
pub fn extract_tx(self) -> Transaction {
|
||||
let mut tx: Transaction = self.global.unsigned_tx;
|
||||
|
||||
for (vin, psbtin) in tx.input.iter_mut().zip(self.inputs.into_iter()) {
|
||||
vin.script_sig = psbtin.final_script_sig.unwrap_or_else(|| Script::new());
|
||||
vin.witness = psbtin.final_script_witness.unwrap_or_else(|| Vec::new());
|
||||
}
|
||||
|
||||
tx
|
||||
}
|
||||
|
||||
/// Attempt to merge with another `PartiallySignedTransaction`.
|
||||
pub fn merge(&mut self, other: Self) -> Result<(), self::Error> {
|
||||
self.global.merge(other.global)?;
|
||||
|
||||
for (self_input, other_input) in self.inputs.iter_mut().zip(other.inputs.into_iter()) {
|
||||
self_input.merge(other_input)?;
|
||||
}
|
||||
|
||||
for (self_output, other_output) in self.outputs.iter_mut().zip(other.outputs.into_iter()) {
|
||||
self_output.merge(other_output)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Encoder> Encodable<S> for PartiallySignedTransaction {
|
||||
fn consensus_encode(&self, s: &mut S) -> Result<(), encode::Error> {
|
||||
b"psbt".consensus_encode(s)?;
|
||||
|
||||
0xff_u8.consensus_encode(s)?;
|
||||
|
||||
self.global.consensus_encode(s)?;
|
||||
|
||||
for i in &self.inputs {
|
||||
i.consensus_encode(s)?;
|
||||
}
|
||||
|
||||
for i in &self.outputs {
|
||||
i.consensus_encode(s)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Decoder> Decodable<D> for PartiallySignedTransaction {
|
||||
fn consensus_decode(d: &mut D) -> Result<Self, encode::Error> {
|
||||
let magic: [u8; 4] = Decodable::consensus_decode(d)?;
|
||||
|
||||
if *b"psbt" != magic {
|
||||
return Err(Error::InvalidMagic.into());
|
||||
}
|
||||
|
||||
if 0xff_u8 != Decodable::consensus_decode(d)? {
|
||||
return Err(Error::InvalidSeparator.into());
|
||||
}
|
||||
|
||||
let global: Global = Decodable::consensus_decode(d)?;
|
||||
|
||||
let inputs: Vec<Input> = {
|
||||
let inputs_len: usize = (&global.unsigned_tx.input).len();
|
||||
|
||||
let mut inputs: Vec<Input> = Vec::with_capacity(inputs_len);
|
||||
|
||||
for _ in 0..inputs_len {
|
||||
inputs.push(Decodable::consensus_decode(d)?);
|
||||
}
|
||||
|
||||
inputs
|
||||
};
|
||||
|
||||
let outputs: Vec<Output> = {
|
||||
let outputs_len: usize = (&global.unsigned_tx.output).len();
|
||||
|
||||
let mut outputs: Vec<Output> = Vec::with_capacity(outputs_len);
|
||||
|
||||
for _ in 0..outputs_len {
|
||||
outputs.push(Decodable::consensus_decode(d)?);
|
||||
}
|
||||
|
||||
outputs
|
||||
};
|
||||
|
||||
Ok(PartiallySignedTransaction {
|
||||
global: global,
|
||||
inputs: inputs,
|
||||
outputs: outputs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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 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;
|
||||
|
||||
use super::PartiallySignedTransaction;
|
||||
|
||||
#[test]
|
||||
fn trivial_psbt() {
|
||||
let psbt = PartiallySignedTransaction {
|
||||
global: Global {
|
||||
unsigned_tx: Transaction {
|
||||
version: 2,
|
||||
lock_time: 0,
|
||||
input: vec![],
|
||||
output: vec![],
|
||||
},
|
||||
unknown: HashMap::new(),
|
||||
},
|
||||
inputs: vec![],
|
||||
outputs: vec![],
|
||||
};
|
||||
assert_eq!(
|
||||
serialize_hex(&psbt),
|
||||
"70736274ff01000a0200000000000000000000"
|
||||
);
|
||||
}
|
||||
|
||||
#[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 {
|
||||
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 {
|
||||
key: raw::Key {
|
||||
type_value: 0u8,
|
||||
key: vec![42u8, 69u8],
|
||||
},
|
||||
value: vec![69u8, 42u8, 4u8],
|
||||
};
|
||||
|
||||
let actual: raw::Pair = deserialize(&serialize(&expected)).unwrap();
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
mod bip_vectors {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use hex::decode as hex_decode;
|
||||
|
||||
use bitcoin_hashes::hex::FromHex;
|
||||
use bitcoin_hashes::sha256d;
|
||||
|
||||
use blockdata::script::Script;
|
||||
use blockdata::transaction::{SigHashType, Transaction, TxIn, TxOut, OutPoint};
|
||||
use consensus::encode::serialize_hex;
|
||||
use util::psbt::map::{Map, Global, Input, Output};
|
||||
use util::psbt::raw;
|
||||
use util::psbt::PartiallySignedTransaction;
|
||||
|
||||
#[test]
|
||||
fn invalid_vector_1() {
|
||||
let psbt: Result<PartiallySignedTransaction, _> = hex_psbt!("0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300");
|
||||
assert!(psbt.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_vector_2() {
|
||||
let psbt: Result<PartiallySignedTransaction, _> = hex_psbt!("70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000");
|
||||
assert!(psbt.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_vector_3() {
|
||||
let psbt: Result<PartiallySignedTransaction, _> = hex_psbt!("70736274ff0100fd0a010200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be4000000006a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa88292feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000");
|
||||
assert!(psbt.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_vector_4() {
|
||||
let psbt: Result<PartiallySignedTransaction, _> = hex_psbt!("70736274ff000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000");
|
||||
assert!(psbt.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_vector_5() {
|
||||
let psbt: Result<PartiallySignedTransaction, _> = hex_psbt!("70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000000");
|
||||
assert!(psbt.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_vector_1() {
|
||||
let unserialized = PartiallySignedTransaction {
|
||||
global: 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: HashMap::new(),
|
||||
},
|
||||
inputs: vec![Input {
|
||||
non_witness_utxo: Some(Transaction {
|
||||
version: 1,
|
||||
lock_time: 0,
|
||||
input: vec![TxIn {
|
||||
previous_output: OutPoint {
|
||||
txid: sha256d::Hash::from_hex(
|
||||
"e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389",
|
||||
).unwrap(),
|
||||
vout: 1,
|
||||
},
|
||||
script_sig: hex_script!("160014be18d152a9b012039daf3da7de4f53349eecb985"),
|
||||
sequence: 4294967295,
|
||||
witness: vec![
|
||||
hex_decode("304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01").unwrap(),
|
||||
hex_decode("03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105").unwrap(),
|
||||
],
|
||||
},
|
||||
TxIn {
|
||||
previous_output: OutPoint {
|
||||
txid: sha256d::Hash::from_hex(
|
||||
"b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886",
|
||||
).unwrap(),
|
||||
vout: 1,
|
||||
},
|
||||
script_sig: hex_script!("160014fe3e9ef1a745e974d902c4355943abcb34bd5353"),
|
||||
sequence: 4294967295,
|
||||
witness: vec![
|
||||
hex_decode("3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01").unwrap(),
|
||||
hex_decode("0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3").unwrap(),
|
||||
],
|
||||
}],
|
||||
output: vec![
|
||||
TxOut {
|
||||
value: 200000000,
|
||||
script_pubkey: hex_script!("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac"),
|
||||
},
|
||||
TxOut {
|
||||
value: 190303501938,
|
||||
script_pubkey: hex_script!("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587"),
|
||||
},
|
||||
],
|
||||
}),
|
||||
..Default::default()
|
||||
},],
|
||||
outputs: vec![
|
||||
Output {
|
||||
..Default::default()
|
||||
},
|
||||
Output {
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let serialized = "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000";
|
||||
|
||||
assert_eq!(serialize_hex(&unserialized), serialized);
|
||||
assert_eq!(unserialized, hex_psbt!(serialized).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_vector_2() {
|
||||
let psbt: PartiallySignedTransaction = hex_psbt!("70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac000000000001076a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa882920001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000").unwrap();
|
||||
|
||||
assert_eq!(psbt.inputs.len(), 2);
|
||||
assert_eq!(psbt.outputs.len(), 2);
|
||||
|
||||
assert!(&psbt.inputs[0].final_script_sig.is_some());
|
||||
|
||||
let redeem_script: &Script = &psbt.inputs[1].redeem_script.as_ref().unwrap();
|
||||
let expected_out = hex_script!("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787");
|
||||
|
||||
assert!(redeem_script.is_v0_p2wpkh());
|
||||
assert_eq!(
|
||||
redeem_script.to_p2sh(),
|
||||
psbt.inputs[1].witness_utxo.as_ref().unwrap().script_pubkey
|
||||
);
|
||||
assert_eq!(redeem_script.to_p2sh(), expected_out);
|
||||
|
||||
for output in psbt.outputs {
|
||||
assert_eq!(output.get_pairs().unwrap().len(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_vector_3() {
|
||||
let psbt: PartiallySignedTransaction = hex_psbt!("70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001030401000000000000").unwrap();
|
||||
|
||||
assert_eq!(psbt.inputs.len(), 1);
|
||||
assert_eq!(psbt.outputs.len(), 2);
|
||||
|
||||
let tx_input = &psbt.global.unsigned_tx.input[0];
|
||||
let psbt_non_witness_utxo = (&psbt.inputs[0].non_witness_utxo).as_ref().unwrap();
|
||||
|
||||
assert_eq!(tx_input.previous_output.txid, psbt_non_witness_utxo.txid());
|
||||
assert!(
|
||||
psbt_non_witness_utxo.output[tx_input.previous_output.vout as usize]
|
||||
.script_pubkey
|
||||
.is_p2pkh()
|
||||
);
|
||||
assert_eq!(
|
||||
(&psbt.inputs[0].sighash_type).as_ref().unwrap(),
|
||||
&SigHashType::All
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_vector_4() {
|
||||
let psbt: PartiallySignedTransaction = hex_psbt!("70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000100df0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e13000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb8230800220202ead596687ca806043edc3de116cdf29d5e9257c196cd055cf698c8d02bf24e9910b4a6ba670000008000000080020000800022020394f62be9df19952c5587768aeb7698061ad2c4a25c894f47d8c162b4d7213d0510b4a6ba6700000080010000800200008000").unwrap();
|
||||
|
||||
assert_eq!(psbt.inputs.len(), 2);
|
||||
assert_eq!(psbt.outputs.len(), 2);
|
||||
|
||||
assert!(&psbt.inputs[0].final_script_sig.is_none());
|
||||
assert!(&psbt.inputs[1].final_script_sig.is_none());
|
||||
|
||||
let redeem_script: &Script = &psbt.inputs[1].redeem_script.as_ref().unwrap();
|
||||
let expected_out = hex_script!("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787");
|
||||
|
||||
assert!(redeem_script.is_v0_p2wpkh());
|
||||
assert_eq!(
|
||||
redeem_script.to_p2sh(),
|
||||
psbt.inputs[1].witness_utxo.as_ref().unwrap().script_pubkey
|
||||
);
|
||||
assert_eq!(redeem_script.to_p2sh(), expected_out);
|
||||
|
||||
for output in psbt.outputs {
|
||||
assert!(output.get_pairs().unwrap().len() > 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_vector_5() {
|
||||
let psbt: PartiallySignedTransaction = hex_psbt!("70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000").unwrap();
|
||||
|
||||
assert_eq!(psbt.inputs.len(), 1);
|
||||
assert_eq!(psbt.outputs.len(), 1);
|
||||
|
||||
assert!(&psbt.inputs[0].final_script_sig.is_none());
|
||||
|
||||
let redeem_script: &Script = &psbt.inputs[0].redeem_script.as_ref().unwrap();
|
||||
let expected_out = hex_script!("a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87");
|
||||
|
||||
assert!(redeem_script.is_v0_p2wsh());
|
||||
assert_eq!(
|
||||
redeem_script.to_p2sh(),
|
||||
psbt.inputs[0].witness_utxo.as_ref().unwrap().script_pubkey
|
||||
);
|
||||
|
||||
assert_eq!(redeem_script.to_p2sh(), expected_out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_vector_6() {
|
||||
let psbt: PartiallySignedTransaction = hex_psbt!("70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0000").unwrap();
|
||||
|
||||
assert_eq!(psbt.inputs.len(), 1);
|
||||
assert_eq!(psbt.outputs.len(), 1);
|
||||
|
||||
let tx = &psbt.global.unsigned_tx;
|
||||
assert_eq!(
|
||||
tx.txid(),
|
||||
sha256d::Hash::from_hex(
|
||||
"75c5c9665a570569ad77dd1279e6fd4628a093c4dcbf8d41532614044c14c115"
|
||||
).unwrap()
|
||||
);
|
||||
|
||||
let mut unknown: HashMap<raw::Key, Vec<u8>> = HashMap::new();
|
||||
let key: raw::Key = raw::Key {
|
||||
type_value: 0x0fu8,
|
||||
key: hex_decode("010203040506070809").unwrap(),
|
||||
};
|
||||
let value: Vec<u8> = hex_decode("0102030405060708090a0b0c0d0e0f").unwrap();
|
||||
|
||||
unknown.insert(key, value);
|
||||
|
||||
assert_eq!(psbt.inputs[0].unknown, unknown)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
// Rust Bitcoin Library
|
||||
// Written by
|
||||
// The Rust Bitcoin developers
|
||||
//
|
||||
// To the extent possible under law, the author(s) have dedicated all
|
||||
// copyright and related and neighboring rights to this software to
|
||||
// the public domain worldwide. This software is distributed without
|
||||
// any warranty.
|
||||
//
|
||||
// You should have received a copy of the CC0 Public Domain Dedication
|
||||
// along with this software.
|
||||
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
//
|
||||
|
||||
//! # Raw PSBT Key-Value Pairs
|
||||
//!
|
||||
//! Raw PSBT key-value pairs as defined at
|
||||
//! https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use consensus::encode::{Decodable, Encodable, VarInt, MAX_VEC_SIZE};
|
||||
use consensus::encode::{self, Decoder, Encoder};
|
||||
use util::psbt::Error;
|
||||
|
||||
/// A PSBT key in its raw byte form.
|
||||
#[derive(Debug, PartialEq, Hash, Eq, Clone)]
|
||||
pub struct Key {
|
||||
/// The type of this PSBT key.
|
||||
pub type_value: u8,
|
||||
/// The key itself in raw byte form.
|
||||
pub key: Vec<u8>,
|
||||
}
|
||||
|
||||
/// A PSBT key-value pair in its raw byte form.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Pair {
|
||||
/// The key of this key-value pair.
|
||||
pub key: Key,
|
||||
/// The value of this key-value pair in raw byte form.
|
||||
pub value: Vec<u8>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Key {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use hex;
|
||||
|
||||
write!(f, "type: {:#x}, key: {}", self.type_value, hex::encode(&self.key))
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Decoder> Decodable<D> for Key {
|
||||
fn consensus_decode(d: &mut D) -> Result<Self, encode::Error> {
|
||||
let VarInt(byte_size): VarInt = Decodable::consensus_decode(d)?;
|
||||
|
||||
if byte_size == 0 {
|
||||
return Err(Error::NoMorePairs.into());
|
||||
}
|
||||
|
||||
let key_byte_size: u64 = byte_size - 1;
|
||||
|
||||
if key_byte_size > MAX_VEC_SIZE as u64 {
|
||||
return Err(encode::Error::OversizedVectorAllocation { requested: key_byte_size as usize, max: MAX_VEC_SIZE } )
|
||||
}
|
||||
|
||||
let type_value: u8 = Decodable::consensus_decode(d)?;
|
||||
|
||||
let mut key = Vec::with_capacity(key_byte_size as usize);
|
||||
for _ in 0..key_byte_size {
|
||||
key.push(Decodable::consensus_decode(d)?);
|
||||
}
|
||||
|
||||
Ok(Key {
|
||||
type_value: type_value,
|
||||
key: key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Encoder> Encodable<S> for Key {
|
||||
fn consensus_encode(&self, s: &mut S) -> Result<(), encode::Error> {
|
||||
VarInt((self.key.len() + 1) as u64).consensus_encode(s)?;
|
||||
|
||||
self.type_value.consensus_encode(s)?;
|
||||
|
||||
for key in &self.key {
|
||||
key.consensus_encode(s)?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Encoder> Encodable<S> for Pair {
|
||||
fn consensus_encode(&self, s: &mut S) -> Result<(), encode::Error> {
|
||||
self.key.consensus_encode(s)?;
|
||||
self.value.consensus_encode(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Decoder> Decodable<D> for Pair {
|
||||
fn consensus_decode(d: &mut D) -> Result<Self, encode::Error> {
|
||||
Ok(Pair {
|
||||
key: Decodable::consensus_decode(d)?,
|
||||
value: Decodable::consensus_decode(d)?,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
// Rust Bitcoin Library
|
||||
// Written by
|
||||
// The Rust Bitcoin developers
|
||||
//
|
||||
// To the extent possible under law, the author(s) have dedicated all
|
||||
// copyright and related and neighboring rights to this software to
|
||||
// the public domain worldwide. This software is distributed without
|
||||
// any warranty.
|
||||
//
|
||||
// You should have received a copy of the CC0 Public Domain Dedication
|
||||
// along with this software.
|
||||
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
//
|
||||
|
||||
//! # PSBT Serialization
|
||||
//!
|
||||
//! 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::{SigHashType, Transaction, TxOut};
|
||||
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.
|
||||
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);
|
||||
impl_psbt_de_serialize!(TxOut);
|
||||
impl_psbt_de_serialize!(Vec<Vec<u8>>); // scriptWitness
|
||||
|
||||
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()))
|
||||
}
|
||||
}
|
||||
|
||||
// partial sigs
|
||||
impl Serialize for Vec<u8> {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for Vec<u8> {
|
||||
fn deserialize(bytes: &[u8]) -> Result<Self, encode::Error> {
|
||||
Ok(bytes.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for SigHashType {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
serialize(&self.as_u32())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for SigHashType {
|
||||
fn deserialize(bytes: &[u8]) -> Result<Self, encode::Error> {
|
||||
let raw: u32 = encode::deserialize(bytes)?;
|
||||
let rv: SigHashType = SigHashType::from_u32(raw);
|
||||
|
||||
if rv.as_u32() == raw {
|
||||
Ok(rv)
|
||||
} else {
|
||||
Err(psbt::Error::NonStandardSigHashType(raw).into())
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue