Separate merge logic out of Map trait
Recently we (*cough* Tobin) made the `Map` trait private and neglected to add a public API for combining together two PSBTs. Doing so broke the `psbt` module. Pull the merge logic out of the `Map` trait and put it in methods on each individual type (`Input`, `Output`, `PartiallySignedTransaction`). Doing so allows for simplification of return types since combining inputs/outputs never errors. Use the term 'combine' instead of 'merge' since that is the term used in BIP 174.
This commit is contained in:
parent
1871c3ada9
commit
5e2449922d
|
@ -53,7 +53,7 @@ pub enum Error {
|
||||||
MustHaveUnsignedTx,
|
MustHaveUnsignedTx,
|
||||||
/// Signals that there are no more key-value pairs in a key-value map.
|
/// Signals that there are no more key-value pairs in a key-value map.
|
||||||
NoMorePairs,
|
NoMorePairs,
|
||||||
/// Attempting to merge with a PSBT describing a different unsigned
|
/// Attempting to combine with a PSBT describing a different unsigned
|
||||||
/// transaction.
|
/// transaction.
|
||||||
UnexpectedUnsignedTx {
|
UnexpectedUnsignedTx {
|
||||||
/// Expected
|
/// Expected
|
||||||
|
@ -74,9 +74,9 @@ pub enum Error {
|
||||||
/// Hash value
|
/// Hash value
|
||||||
hash: Box<[u8]>,
|
hash: Box<[u8]>,
|
||||||
},
|
},
|
||||||
/// Conflicting data during merge procedure:
|
/// Conflicting data during combine procedure:
|
||||||
/// global extended public key has inconsistent key sources
|
/// global extended public key has inconsistent key sources
|
||||||
MergeInconsistentKeySources(ExtendedPubKey),
|
CombineInconsistentKeySources(ExtendedPubKey),
|
||||||
/// Serialization error in bitcoin consensus-encoded structures
|
/// Serialization error in bitcoin consensus-encoded structures
|
||||||
ConsensusEncoding,
|
ConsensusEncoding,
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ impl fmt::Display for Error {
|
||||||
// directly using debug forms of psbthash enums
|
// directly using debug forms of psbthash enums
|
||||||
write!(f, "Preimage {:?} does not match {:?} hash {:?}", preimage, hash_type, hash )
|
write!(f, "Preimage {:?} does not match {:?} hash {:?}", preimage, hash_type, hash )
|
||||||
}
|
}
|
||||||
Error::MergeInconsistentKeySources(ref s) => { write!(f, "merge conflict: {}", s) }
|
Error::CombineInconsistentKeySources(ref s) => { write!(f, "combine conflict: {}", s) }
|
||||||
Error::ConsensusEncoding => f.write_str("bitcoin consensus or BIP-174 encoding error"),
|
Error::ConsensusEncoding => f.write_str("bitcoin consensus or BIP-174 encoding error"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ macro_rules! hex_psbt {
|
||||||
($s:expr) => { $crate::consensus::deserialize::<$crate::util::psbt::PartiallySignedTransaction>(&<$crate::prelude::Vec<u8> as $crate::hashes::hex::FromHex>::from_hex($s).unwrap()) };
|
($s:expr) => { $crate::consensus::deserialize::<$crate::util::psbt::PartiallySignedTransaction>(&<$crate::prelude::Vec<u8> as $crate::hashes::hex::FromHex>::from_hex($s).unwrap()) };
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! merge {
|
macro_rules! combine {
|
||||||
($thing:ident, $slf:ident, $other:ident) => {
|
($thing:ident, $slf:ident, $other:ident) => {
|
||||||
if let (&None, Some($thing)) = (&$slf.$thing, $other.$thing) {
|
if let (&None, Some($thing)) = (&$slf.$thing, $other.$thing) {
|
||||||
$slf.$thing = Some($thing);
|
$slf.$thing = Some($thing);
|
||||||
|
|
|
@ -15,14 +15,12 @@
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
|
|
||||||
use io::{self, Cursor, Read};
|
use io::{self, Cursor, Read};
|
||||||
use core::cmp;
|
|
||||||
|
|
||||||
use blockdata::transaction::Transaction;
|
use blockdata::transaction::Transaction;
|
||||||
use consensus::{encode, Encodable, Decodable};
|
use consensus::{encode, Encodable, Decodable};
|
||||||
use consensus::encode::MAX_VEC_SIZE;
|
use consensus::encode::MAX_VEC_SIZE;
|
||||||
use util::psbt::map::Map;
|
use util::psbt::map::Map;
|
||||||
use util::psbt::{raw, PartiallySignedTransaction};
|
use util::psbt::{raw, PartiallySignedTransaction};
|
||||||
use util::psbt;
|
|
||||||
use util::psbt::Error;
|
use util::psbt::Error;
|
||||||
use util::endian::u32_to_array_le;
|
use util::endian::u32_to_array_le;
|
||||||
use util::bip32::{ExtendedPubKey, Fingerprint, DerivationPath, ChildNumber};
|
use util::bip32::{ExtendedPubKey, Fingerprint, DerivationPath, ChildNumber};
|
||||||
|
@ -99,69 +97,6 @@ impl Map for PartiallySignedTransaction {
|
||||||
|
|
||||||
Ok(rv)
|
Ok(rv)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep in mind that according to BIP 174 this function must be commutative, i.e.
|
|
||||||
// A.merge(B) == B.merge(A)
|
|
||||||
fn merge(&mut self, other: Self) -> Result<(), psbt::Error> {
|
|
||||||
if self.unsigned_tx != other.unsigned_tx {
|
|
||||||
return Err(psbt::Error::UnexpectedUnsignedTx {
|
|
||||||
expected: Box::new(self.unsigned_tx.clone()),
|
|
||||||
actual: Box::new(other.unsigned_tx),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// BIP 174: The Combiner must remove any duplicate key-value pairs, in accordance with
|
|
||||||
// the specification. It can pick arbitrarily when conflicts occur.
|
|
||||||
|
|
||||||
// Keeping the highest version
|
|
||||||
self.version = cmp::max(self.version, other.version);
|
|
||||||
|
|
||||||
// Merging xpubs
|
|
||||||
for (xpub, (fingerprint1, derivation1)) in other.xpub {
|
|
||||||
match self.xpub.entry(xpub) {
|
|
||||||
btree_map::Entry::Vacant(entry) => {
|
|
||||||
entry.insert((fingerprint1, derivation1));
|
|
||||||
},
|
|
||||||
btree_map::Entry::Occupied(mut entry) => {
|
|
||||||
// Here in case of the conflict we select the version with algorithm:
|
|
||||||
// 1) if everything is equal we do nothing
|
|
||||||
// 2) report an error if
|
|
||||||
// - derivation paths are equal and fingerprints are not
|
|
||||||
// - derivation paths are of the same length, but not equal
|
|
||||||
// - derivation paths has different length, but the shorter one
|
|
||||||
// is not the strict suffix of the longer one
|
|
||||||
// 3) choose longest derivation otherwise
|
|
||||||
|
|
||||||
let (fingerprint2, derivation2) = entry.get().clone();
|
|
||||||
|
|
||||||
if (derivation1 == derivation2 && fingerprint1 == fingerprint2) ||
|
|
||||||
(derivation1.len() < derivation2.len() && derivation1[..] == derivation2[derivation2.len() - derivation1.len()..])
|
|
||||||
{
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
else if derivation2[..] == derivation1[derivation1.len() - derivation2.len()..]
|
|
||||||
{
|
|
||||||
entry.insert((fingerprint1, derivation1));
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return Err(psbt::Error::MergeInconsistentKeySources(xpub));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.proprietary.extend(other.proprietary);
|
|
||||||
self.unknown.extend(other.unknown);
|
|
||||||
|
|
||||||
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 PartiallySignedTransaction {
|
impl PartiallySignedTransaction {
|
||||||
|
|
|
@ -304,6 +304,36 @@ impl Input {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Combines this [`Input`] with `other` `Input` (as described by BIP 174).
|
||||||
|
pub fn combine(&mut self, other: Self) {
|
||||||
|
combine!(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.bip32_derivation.extend(other.bip32_derivation);
|
||||||
|
self.ripemd160_preimages.extend(other.ripemd160_preimages);
|
||||||
|
self.sha256_preimages.extend(other.sha256_preimages);
|
||||||
|
self.hash160_preimages.extend(other.hash160_preimages);
|
||||||
|
self.hash256_preimages.extend(other.hash256_preimages);
|
||||||
|
self.tap_script_sigs.extend(other.tap_script_sigs);
|
||||||
|
self.tap_scripts.extend(other.tap_scripts);
|
||||||
|
self.tap_key_origins.extend(other.tap_key_origins);
|
||||||
|
self.proprietary.extend(other.proprietary);
|
||||||
|
self.unknown.extend(other.unknown);
|
||||||
|
|
||||||
|
combine!(redeem_script, self, other);
|
||||||
|
combine!(witness_script, self, other);
|
||||||
|
combine!(final_script_sig, self, other);
|
||||||
|
combine!(final_script_witness, self, other);
|
||||||
|
combine!(tap_key_sig, self, other);
|
||||||
|
combine!(tap_internal_key, self, other);
|
||||||
|
combine!(tap_merkle_root, self, other);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Map for Input {
|
impl Map for Input {
|
||||||
|
@ -401,37 +431,6 @@ impl Map for Input {
|
||||||
|
|
||||||
Ok(rv)
|
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.bip32_derivation.extend(other.bip32_derivation);
|
|
||||||
self.ripemd160_preimages.extend(other.ripemd160_preimages);
|
|
||||||
self.sha256_preimages.extend(other.sha256_preimages);
|
|
||||||
self.hash160_preimages.extend(other.hash160_preimages);
|
|
||||||
self.hash256_preimages.extend(other.hash256_preimages);
|
|
||||||
self.tap_script_sigs.extend(other.tap_script_sigs);
|
|
||||||
self.tap_scripts.extend(other.tap_scripts);
|
|
||||||
self.tap_key_origins.extend(other.tap_key_origins);
|
|
||||||
self.proprietary.extend(other.proprietary);
|
|
||||||
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);
|
|
||||||
merge!(tap_key_sig, self, other);
|
|
||||||
merge!(tap_internal_key, self, other);
|
|
||||||
merge!(tap_merkle_root, self, other);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_psbtmap_consensus_enc_dec_oding!(Input);
|
impl_psbtmap_consensus_enc_dec_oding!(Input);
|
||||||
|
|
|
@ -17,7 +17,6 @@ use prelude::*;
|
||||||
use io;
|
use io;
|
||||||
|
|
||||||
use consensus::encode;
|
use consensus::encode;
|
||||||
use util::psbt;
|
|
||||||
use util::psbt::raw;
|
use util::psbt::raw;
|
||||||
|
|
||||||
mod global;
|
mod global;
|
||||||
|
@ -32,9 +31,6 @@ pub(super) trait Map {
|
||||||
/// Attempt to get all key-value pairs.
|
/// Attempt to get all key-value pairs.
|
||||||
fn get_pairs(&self) -> Result<Vec<raw::Pair>, io::Error>;
|
fn get_pairs(&self) -> Result<Vec<raw::Pair>, io::Error>;
|
||||||
|
|
||||||
/// Attempt to merge with another key-value map of the same type.
|
|
||||||
fn merge(&mut self, other: Self) -> Result<(), psbt::Error>;
|
|
||||||
|
|
||||||
/// Encodes map data with bitcoin consensus encoding.
|
/// Encodes map data with bitcoin consensus encoding.
|
||||||
fn consensus_encode_map<S: io::Write>(
|
fn consensus_encode_map<S: io::Write>(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -21,7 +21,6 @@ use consensus::encode;
|
||||||
use secp256k1::XOnlyPublicKey;
|
use secp256k1::XOnlyPublicKey;
|
||||||
use util::bip32::KeySource;
|
use util::bip32::KeySource;
|
||||||
use secp256k1;
|
use secp256k1;
|
||||||
use util::psbt;
|
|
||||||
use util::psbt::map::Map;
|
use util::psbt::map::Map;
|
||||||
use util::psbt::raw;
|
use util::psbt::raw;
|
||||||
use util::psbt::Error;
|
use util::psbt::Error;
|
||||||
|
@ -177,6 +176,19 @@ impl Output {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Combines this [`Output`] with `other` `Output` (as described by BIP 174).
|
||||||
|
pub fn combine(&mut self, other: Self) {
|
||||||
|
self.bip32_derivation.extend(other.bip32_derivation);
|
||||||
|
self.proprietary.extend(other.proprietary);
|
||||||
|
self.unknown.extend(other.unknown);
|
||||||
|
self.tap_key_origins.extend(other.tap_key_origins);
|
||||||
|
|
||||||
|
combine!(redeem_script, self, other);
|
||||||
|
combine!(witness_script, self, other);
|
||||||
|
combine!(tap_internal_key, self, other);
|
||||||
|
combine!(tap_tree, self, other);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Map for Output {
|
impl Map for Output {
|
||||||
|
@ -223,20 +235,6 @@ impl Map for Output {
|
||||||
|
|
||||||
Ok(rv)
|
Ok(rv)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge(&mut self, other: Self) -> Result<(), psbt::Error> {
|
|
||||||
self.bip32_derivation.extend(other.bip32_derivation);
|
|
||||||
self.proprietary.extend(other.proprietary);
|
|
||||||
self.unknown.extend(other.unknown);
|
|
||||||
self.tap_key_origins.extend(other.tap_key_origins);
|
|
||||||
|
|
||||||
merge!(redeem_script, self, other);
|
|
||||||
merge!(witness_script, self, other);
|
|
||||||
merge!(tap_internal_key, self, other);
|
|
||||||
merge!(tap_tree, self, other);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_psbtmap_consensus_enc_dec_oding!(Output);
|
impl_psbtmap_consensus_enc_dec_oding!(Output);
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
//! except we define PSBTs containing non-standard SigHash types as invalid.
|
//! except we define PSBTs containing non-standard SigHash types as invalid.
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
use core::cmp;
|
||||||
|
|
||||||
use blockdata::script::Script;
|
use blockdata::script::Script;
|
||||||
use blockdata::transaction::Transaction;
|
use blockdata::transaction::Transaction;
|
||||||
use consensus::{encode, Encodable, Decodable};
|
use consensus::{encode, Encodable, Decodable};
|
||||||
|
@ -117,6 +119,70 @@ impl PartiallySignedTransaction {
|
||||||
|
|
||||||
tx
|
tx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Combines this [`PartiallySignedTransaction`] with `other` PSBT as described by BIP 174.
|
||||||
|
///
|
||||||
|
/// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)`
|
||||||
|
pub fn combine(&mut self, other: Self) -> Result<(), Error> {
|
||||||
|
if self.unsigned_tx != other.unsigned_tx {
|
||||||
|
return Err(Error::UnexpectedUnsignedTx {
|
||||||
|
expected: Box::new(self.unsigned_tx.clone()),
|
||||||
|
actual: Box::new(other.unsigned_tx),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// BIP 174: The Combiner must remove any duplicate key-value pairs, in accordance with
|
||||||
|
// the specification. It can pick arbitrarily when conflicts occur.
|
||||||
|
|
||||||
|
// Keeping the highest version
|
||||||
|
self.version = cmp::max(self.version, other.version);
|
||||||
|
|
||||||
|
// Merging xpubs
|
||||||
|
for (xpub, (fingerprint1, derivation1)) in other.xpub {
|
||||||
|
match self.xpub.entry(xpub) {
|
||||||
|
btree_map::Entry::Vacant(entry) => {
|
||||||
|
entry.insert((fingerprint1, derivation1));
|
||||||
|
},
|
||||||
|
btree_map::Entry::Occupied(mut entry) => {
|
||||||
|
// Here in case of the conflict we select the version with algorithm:
|
||||||
|
// 1) if everything is equal we do nothing
|
||||||
|
// 2) report an error if
|
||||||
|
// - derivation paths are equal and fingerprints are not
|
||||||
|
// - derivation paths are of the same length, but not equal
|
||||||
|
// - derivation paths has different length, but the shorter one
|
||||||
|
// is not the strict suffix of the longer one
|
||||||
|
// 3) choose longest derivation otherwise
|
||||||
|
|
||||||
|
let (fingerprint2, derivation2) = entry.get().clone();
|
||||||
|
|
||||||
|
if (derivation1 == derivation2 && fingerprint1 == fingerprint2) ||
|
||||||
|
(derivation1.len() < derivation2.len() && derivation1[..] == derivation2[derivation2.len() - derivation1.len()..])
|
||||||
|
{
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
else if derivation2[..] == derivation1[derivation1.len() - derivation2.len()..]
|
||||||
|
{
|
||||||
|
entry.insert((fingerprint1, derivation1));
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return Err(Error::CombineInconsistentKeySources(xpub));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.proprietary.extend(other.proprietary);
|
||||||
|
self.unknown.extend(other.unknown);
|
||||||
|
|
||||||
|
for (self_input, other_input) in self.inputs.iter_mut().zip(other.inputs.into_iter()) {
|
||||||
|
self_input.combine(other_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (self_output, other_output) in self.outputs.iter_mut().zip(other.outputs.into_iter()) {
|
||||||
|
self_output.combine(other_output);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "base64")]
|
#[cfg(feature = "base64")]
|
||||||
|
@ -243,7 +309,7 @@ impl Decodable for PartiallySignedTransaction {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::PartiallySignedTransaction;
|
use super::*;
|
||||||
|
|
||||||
use hashes::hex::FromHex;
|
use hashes::hex::FromHex;
|
||||||
use hashes::{sha256, hash160, Hash, ripemd160};
|
use hashes::{sha256, hash160, Hash, ripemd160};
|
||||||
|
@ -1031,4 +1097,28 @@ mod tests {
|
||||||
assert!(!rtt.proprietary.is_empty());
|
assert!(!rtt.proprietary.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PSBTs taken from BIP 174 test vectors.
|
||||||
|
#[test]
|
||||||
|
fn combine_psbts() {
|
||||||
|
let mut psbt1 = hex_psbt!(include_str!("../../../test_data/psbt1.hex")).unwrap();
|
||||||
|
let psbt2 = hex_psbt!(include_str!("../../../test_data/psbt2.hex")).unwrap();
|
||||||
|
let psbt_combined = hex_psbt!(include_str!("../../../test_data/psbt2.hex")).unwrap();
|
||||||
|
|
||||||
|
psbt1.combine(psbt2).expect("psbt combine to succeed");
|
||||||
|
assert_eq!(psbt1, psbt_combined);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn combine_psbts_commutative() {
|
||||||
|
let mut psbt1 = hex_psbt!(include_str!("../../../test_data/psbt1.hex")).unwrap();
|
||||||
|
let mut psbt2 = hex_psbt!(include_str!("../../../test_data/psbt2.hex")).unwrap();
|
||||||
|
|
||||||
|
let psbt1_clone = psbt1.clone();
|
||||||
|
let psbt2_clone = psbt2.clone();
|
||||||
|
|
||||||
|
psbt1.combine(psbt2_clone).expect("psbt1 combine to succeed");
|
||||||
|
psbt2.combine(psbt1_clone).expect("psbt2 combine to succeed");
|
||||||
|
|
||||||
|
assert_eq!(psbt1, psbt2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000
|
|
@ -0,0 +1 @@
|
||||||
|
70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f012202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000
|
|
@ -0,0 +1 @@
|
||||||
|
70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f012202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000
|
Loading…
Reference in New Issue