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,
|
||||
/// 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
|
||||
/// Attempting to combine with a PSBT describing a different unsigned
|
||||
/// transaction.
|
||||
UnexpectedUnsignedTx {
|
||||
/// Expected
|
||||
|
@ -74,9 +74,9 @@ pub enum Error {
|
|||
/// Hash value
|
||||
hash: Box<[u8]>,
|
||||
},
|
||||
/// Conflicting data during merge procedure:
|
||||
/// Conflicting data during combine procedure:
|
||||
/// global extended public key has inconsistent key sources
|
||||
MergeInconsistentKeySources(ExtendedPubKey),
|
||||
CombineInconsistentKeySources(ExtendedPubKey),
|
||||
/// Serialization error in bitcoin consensus-encoded structures
|
||||
ConsensusEncoding,
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ impl fmt::Display for Error {
|
|||
// directly using debug forms of psbthash enums
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()) };
|
||||
}
|
||||
|
||||
macro_rules! merge {
|
||||
macro_rules! combine {
|
||||
($thing:ident, $slf:ident, $other:ident) => {
|
||||
if let (&None, Some($thing)) = (&$slf.$thing, $other.$thing) {
|
||||
$slf.$thing = Some($thing);
|
||||
|
|
|
@ -15,14 +15,12 @@
|
|||
use prelude::*;
|
||||
|
||||
use io::{self, Cursor, Read};
|
||||
use core::cmp;
|
||||
|
||||
use blockdata::transaction::Transaction;
|
||||
use consensus::{encode, Encodable, Decodable};
|
||||
use consensus::encode::MAX_VEC_SIZE;
|
||||
use util::psbt::map::Map;
|
||||
use util::psbt::{raw, PartiallySignedTransaction};
|
||||
use util::psbt;
|
||||
use util::psbt::Error;
|
||||
use util::endian::u32_to_array_le;
|
||||
use util::bip32::{ExtendedPubKey, Fingerprint, DerivationPath, ChildNumber};
|
||||
|
@ -99,69 +97,6 @@ impl Map for PartiallySignedTransaction {
|
|||
|
||||
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 {
|
||||
|
|
|
@ -304,6 +304,36 @@ impl Input {
|
|||
|
||||
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 {
|
||||
|
@ -401,37 +431,6 @@ impl Map for Input {
|
|||
|
||||
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);
|
||||
|
|
|
@ -17,7 +17,6 @@ use prelude::*;
|
|||
use io;
|
||||
|
||||
use consensus::encode;
|
||||
use util::psbt;
|
||||
use util::psbt::raw;
|
||||
|
||||
mod global;
|
||||
|
@ -32,9 +31,6 @@ pub(super) trait Map {
|
|||
/// Attempt to get all key-value pairs.
|
||||
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.
|
||||
fn consensus_encode_map<S: io::Write>(
|
||||
&self,
|
||||
|
|
|
@ -21,7 +21,6 @@ use consensus::encode;
|
|||
use secp256k1::XOnlyPublicKey;
|
||||
use util::bip32::KeySource;
|
||||
use secp256k1;
|
||||
use util::psbt;
|
||||
use util::psbt::map::Map;
|
||||
use util::psbt::raw;
|
||||
use util::psbt::Error;
|
||||
|
@ -177,6 +176,19 @@ impl Output {
|
|||
|
||||
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 {
|
||||
|
@ -223,20 +235,6 @@ impl Map for Output {
|
|||
|
||||
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);
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
//! except we define PSBTs containing non-standard SigHash types as invalid.
|
||||
//!
|
||||
|
||||
use core::cmp;
|
||||
|
||||
use blockdata::script::Script;
|
||||
use blockdata::transaction::Transaction;
|
||||
use consensus::{encode, Encodable, Decodable};
|
||||
|
@ -117,6 +119,70 @@ impl PartiallySignedTransaction {
|
|||
|
||||
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")]
|
||||
|
@ -243,7 +309,7 @@ impl Decodable for PartiallySignedTransaction {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PartiallySignedTransaction;
|
||||
use super::*;
|
||||
|
||||
use hashes::hex::FromHex;
|
||||
use hashes::{sha256, hash160, Hash, ripemd160};
|
||||
|
@ -1031,4 +1097,28 @@ mod tests {
|
|||
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