Skip to content

Commit 6cd1fb6

Browse files
committed
Merge #305: Update Psbt APIs
b3f4c18 Correct redeem script/script code caluclation (sanket1729) e9b53e9 Update psbt finalize_* APIs to consume self (sanket1729) 472f803 Update Psbt APIs with _mut and _input support (sanket1729) daa80b7 Update finalize API to return vector of errors (sanket1729) 68cccb3 Add support for finalize input (sanket1729) 5fa86b2 Move input sighash type match checks into sanity_check (sanket1729) 49fe1ca Introduce PsbtExt trait (sanket1729) 0c64a70 Add more auto-derives to conversion error (sanket1729) 30fa409 Remove local function script_is_v1_p2tr (sanket1729) Pull request description: Based on #301 ACKs for top commit: apoelstra: ACK b3f4c18 Tree-SHA512: eaee7b50357c8dad94f11baf6f7116fe982c17b284f3d10f64d85cf4cd91b1c696d46f50b2f8b223dd5abd6d1243e77fa1f57f19a4253c78df747f90b423e3d9
2 parents 5aafba8 + b3f4c18 commit 6cd1fb6

File tree

6 files changed

+698
-138
lines changed

6 files changed

+698
-138
lines changed

examples/psbt.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ extern crate miniscript;
33

44
use bitcoin::consensus::encode::deserialize;
55
use bitcoin::hashes::hex::FromHex;
6-
7-
use miniscript::psbt::{extract, finalize};
6+
use miniscript::psbt::PsbtExt;
87

98
fn main() {
109
// Test vectors from BIP 174
@@ -18,13 +17,13 @@ fn main() {
1817
let secp = bitcoin::secp256k1::Secp256k1::verification_only();
1918
// Assuming all partial sigs are filled in.
2019
// Construct a generic finalizer
21-
finalize(&mut psbt, &secp).unwrap();
20+
psbt.finalize_mut(&secp).unwrap();
2221
// println!("{:?}", psbt);
2322

2423
assert_eq!(psbt, expected_finalized_psbt);
2524

2625
// Extract the transaction from the psbt
27-
let tx = extract(&psbt, &secp).unwrap();
26+
let tx = psbt.extract(&secp).unwrap();
2827

2928
let expected: bitcoin::Transaction = deserialize(&Vec::<u8>::from_hex("0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000").unwrap()).unwrap();
3029
// println!("{:?}", tx);

integration_test/src/main.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use bitcoin::util::psbt::PartiallySignedTransaction as Psbt;
1616
use bitcoin::{Amount, OutPoint, Transaction, TxIn, TxOut, Txid};
1717
mod read_file;
1818
use miniscript::miniscript::iter;
19+
use miniscript::psbt::PsbtExt;
1920
use miniscript::DescriptorTrait;
2021
use miniscript::MiniscriptKey;
2122
use miniscript::{Miniscript, Segwitv0};
@@ -196,7 +197,9 @@ fn main() {
196197
let amt = btc(1).as_sat();
197198
let mut sighash_cache = bitcoin::util::sighash::SigHashCache::new(&psbts[i].unsigned_tx);
198199
let sighash_ty = bitcoin::EcdsaSigHashType::All;
199-
let sighash = sighash_cache.segwit_signature_hash(0, &ms.encode(), amt, sighash_ty).unwrap();
200+
let sighash = sighash_cache
201+
.segwit_signature_hash(0, &ms.encode(), amt, sighash_ty)
202+
.unwrap();
200203

201204
// requires both signing and verification because we check the tx
202205
// after we psbt extract it
@@ -207,7 +210,13 @@ fn main() {
207210
for sk in sks_reqd {
208211
let sig = secp.sign_ecdsa(&msg, &sk);
209212
let pk = pks[sks.iter().position(|&x| x == sk).unwrap()];
210-
psbts[i].inputs[0].partial_sigs.insert(pk.inner, bitcoin::EcdsaSig { sig, hash_ty: sighash_ty });
213+
psbts[i].inputs[0].partial_sigs.insert(
214+
pk.inner,
215+
bitcoin::EcdsaSig {
216+
sig,
217+
hash_ty: sighash_ty,
218+
},
219+
);
211220
}
212221
// Add the hash preimages to the psbt
213222
psbts[i].inputs[0].sha256_preimages.insert(
@@ -229,11 +238,11 @@ fn main() {
229238
);
230239
// Finalize the transaction using psbt
231240
// Let miniscript do it's magic!
232-
if let Err(e) = miniscript::psbt::finalize_mall(&mut psbts[i], &secp) {
241+
if let Err(e) = psbts[i].finalize_mall_mut(&secp) {
233242
// All miniscripts should satisfy
234-
panic!("Could not satisfy: error{} ms:{} at ind:{}", e, ms, i);
243+
panic!("Could not satisfy: error{} ms:{} at ind:{}", e[0], ms, i);
235244
} else {
236-
let tx = miniscript::psbt::extract(&psbts[i], &secp).unwrap();
245+
let tx = psbts[i].extract(&secp).unwrap();
237246

238247
// Send the transactions to bitcoin node for mining.
239248
// Regtest mode has standardness checks

src/descriptor/key.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ impl FromStr for DescriptorPublicKey {
350350
}
351351

352352
/// Descriptor key conversion error
353-
#[derive(Debug, PartialEq, Clone, Copy)]
353+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
354354
pub enum ConversionError {
355355
/// Attempted to convert a key with a wildcard to a bitcoin public key
356356
Wildcard,

src/psbt/finalizer.rs

Lines changed: 83 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@
2020
//!
2121
2222
use bitcoin::util::sighash::Prevouts;
23-
use util::{script_is_v1_tr, witness_size};
23+
use util::witness_size;
2424

2525
use super::{sanity_check, Psbt};
2626
use super::{Error, InputError, PsbtInputSatisfier};
2727
use bitcoin::blockdata::witness::Witness;
2828
use bitcoin::secp256k1::{self, Secp256k1};
2929
use bitcoin::util::key::XOnlyPublicKey;
3030
use bitcoin::util::taproot::LeafVersion;
31-
use bitcoin::{self, EcdsaSigHashType, PublicKey, Script};
31+
use bitcoin::{self, PublicKey, Script};
3232
use descriptor::DescriptorTrait;
3333
use interpreter;
3434
use Descriptor;
@@ -45,7 +45,7 @@ fn construct_tap_witness(
4545
sat: &PsbtInputSatisfier,
4646
allow_mall: bool,
4747
) -> Result<Vec<Vec<u8>>, InputError> {
48-
assert!(script_is_v1_tr(&spk));
48+
assert!(spk.is_v1_p2tr());
4949

5050
// try the script spend path first
5151
if let Some(sig) =
@@ -97,12 +97,12 @@ fn construct_tap_witness(
9797
}
9898

9999
// Get the scriptpubkey for the psbt input
100-
fn get_scriptpubkey(psbt: &Psbt, index: usize) -> Result<&Script, InputError> {
100+
pub(super) fn get_scriptpubkey(psbt: &Psbt, index: usize) -> Result<&Script, InputError> {
101101
get_utxo(psbt, index).map(|utxo| &utxo.script_pubkey)
102102
}
103103

104104
// Get the spending utxo for this psbt input
105-
fn get_utxo(psbt: &Psbt, index: usize) -> Result<&bitcoin::TxOut, InputError> {
105+
pub(super) fn get_utxo(psbt: &Psbt, index: usize) -> Result<&bitcoin::TxOut, InputError> {
106106
let inp = &psbt.inputs[index];
107107
let utxo = if let Some(ref witness_utxo) = inp.witness_utxo {
108108
&witness_utxo
@@ -116,7 +116,7 @@ fn get_utxo(psbt: &Psbt, index: usize) -> Result<&bitcoin::TxOut, InputError> {
116116
}
117117

118118
/// Get the Prevouts for the psbt
119-
fn prevouts<'a>(psbt: &'a Psbt) -> Result<Vec<bitcoin::TxOut>, super::Error> {
119+
pub(super) fn prevouts<'a>(psbt: &'a Psbt) -> Result<Vec<bitcoin::TxOut>, super::Error> {
120120
let mut utxos = vec![];
121121
for i in 0..psbt.inputs.len() {
122122
let utxo_ref = get_utxo(psbt, i).map_err(|e| Error::InputError(e, i))?;
@@ -284,31 +284,43 @@ pub fn interpreter_check<C: secp256k1::Verification>(
284284
) -> Result<(), Error> {
285285
let utxos = prevouts(&psbt)?;
286286
let utxos = &Prevouts::All(&utxos);
287-
for (index, input) in psbt.inputs.iter().enumerate() {
288-
let spk = get_scriptpubkey(psbt, index).map_err(|e| Error::InputError(e, index))?;
289-
let empty_script_sig = Script::new();
290-
let empty_witness = Witness::default();
291-
let script_sig = input.final_script_sig.as_ref().unwrap_or(&empty_script_sig);
292-
let witness = input
293-
.final_script_witness
294-
.as_ref()
295-
.map(|wit_slice| Witness::from_vec(wit_slice.to_vec())) // TODO: Update rust-bitcoin psbt API to use witness
296-
.unwrap_or(empty_witness);
287+
for (index, _input) in psbt.inputs.iter().enumerate() {
288+
interpreter_inp_check(psbt, secp, index, utxos)?;
289+
}
290+
Ok(())
291+
}
297292

298-
// Now look at all the satisfied constraints. If everything is filled in
299-
// corrected, there should be no errors
300-
// Interpreter check
301-
{
302-
let cltv = psbt.unsigned_tx.lock_time;
303-
let csv = psbt.unsigned_tx.input[index].sequence;
304-
let interpreter =
305-
interpreter::Interpreter::from_txdata(spk, &script_sig, &witness, cltv, csv)
306-
.map_err(|e| Error::InputError(InputError::Interpreter(e), index))?;
307-
let iter = interpreter.iter(secp, &psbt.unsigned_tx, index, &utxos);
308-
if let Some(error) = iter.filter_map(Result::err).next() {
309-
return Err(Error::InputError(InputError::Interpreter(error), index));
310-
};
311-
}
293+
// Run the miniscript interpreter on a single psbt input
294+
fn interpreter_inp_check<C: secp256k1::Verification>(
295+
psbt: &Psbt,
296+
secp: &Secp256k1<C>,
297+
index: usize,
298+
utxos: &Prevouts,
299+
) -> Result<(), Error> {
300+
let input = &psbt.inputs[index];
301+
let spk = get_scriptpubkey(psbt, index).map_err(|e| Error::InputError(e, index))?;
302+
let empty_script_sig = Script::new();
303+
let empty_witness = Witness::default();
304+
let script_sig = input.final_script_sig.as_ref().unwrap_or(&empty_script_sig);
305+
let witness = input
306+
.final_script_witness
307+
.as_ref()
308+
.map(|wit_slice| Witness::from_vec(wit_slice.to_vec())) // TODO: Update rust-bitcoin psbt API to use witness
309+
.unwrap_or(empty_witness);
310+
311+
// Now look at all the satisfied constraints. If everything is filled in
312+
// corrected, there should be no errors
313+
// Interpreter check
314+
{
315+
let cltv = psbt.unsigned_tx.lock_time;
316+
let csv = psbt.unsigned_tx.input[index].sequence;
317+
let interpreter =
318+
interpreter::Interpreter::from_txdata(spk, &script_sig, &witness, cltv, csv)
319+
.map_err(|e| Error::InputError(InputError::Interpreter(e), index))?;
320+
let iter = interpreter.iter(secp, &psbt.unsigned_tx, index, &utxos);
321+
if let Some(error) = iter.filter_map(Result::err).next() {
322+
return Err(Error::InputError(InputError::Interpreter(error), index));
323+
};
312324
}
313325
Ok(())
314326
}
@@ -322,6 +334,7 @@ pub fn interpreter_check<C: secp256k1::Verification>(
322334
/// finalized psbt which involves checking the signatures/ preimages/timelocks.
323335
/// The functions fails it is not possible to satisfy any of the inputs non-malleably
324336
/// See [finalize_mall] if you want to allow malleable satisfactions
337+
#[deprecated(since = "7.0", note = "Please use PsbtExt::finalize instead")]
325338
pub fn finalize<C: secp256k1::Verification>(
326339
psbt: &mut Psbt,
327340
secp: &Secp256k1<C>,
@@ -344,64 +357,45 @@ pub fn finalize_helper<C: secp256k1::Verification>(
344357
) -> Result<(), super::Error> {
345358
sanity_check(psbt)?;
346359

347-
// Check well-formedness of input data
348-
for (n, input) in psbt.inputs.iter().enumerate() {
349-
// TODO: fix this after https://github.com/rust-bitcoin/rust-bitcoin/issues/838
350-
let target_ecdsa_sighash_ty = match input.sighash_type {
351-
Some(psbt_hash_ty) => psbt_hash_ty
352-
.ecdsa_hash_ty()
353-
.map_err(|e| Error::InputError(InputError::NonStandardSigHashType(e), n))?,
354-
None => EcdsaSigHashType::All,
355-
};
356-
for (key, ecdsa_sig) in &input.partial_sigs {
357-
let flag = bitcoin::EcdsaSigHashType::from_u32_standard(ecdsa_sig.hash_ty as u32)
358-
.map_err(|_| {
359-
super::Error::InputError(
360-
InputError::Interpreter(interpreter::Error::NonStandardSigHash(
361-
ecdsa_sig.to_vec(),
362-
)),
363-
n,
364-
)
365-
})?;
366-
if target_ecdsa_sighash_ty != flag {
367-
return Err(Error::InputError(
368-
InputError::WrongSigHashFlag {
369-
required: target_ecdsa_sighash_ty,
370-
got: flag,
371-
pubkey: bitcoin::PublicKey::new(*key),
372-
},
373-
n,
374-
));
375-
}
376-
// Signatures are well-formed in psbt partial sigs
377-
}
378-
}
379-
380360
// Actually construct the witnesses
381361
for index in 0..psbt.inputs.len() {
382-
let (witness, script_sig) = {
383-
let spk = get_scriptpubkey(psbt, index).map_err(|e| Error::InputError(e, index))?;
384-
let sat = PsbtInputSatisfier::new(&psbt, index);
362+
finalize_input(psbt, index, secp, allow_mall)?;
363+
}
364+
// Interpreter is already run inside finalize_input for each input
365+
Ok(())
366+
}
385367

386-
if script_is_v1_tr(spk) {
387-
// Deal with tr case separately, unfortunately we cannot infer the full descriptor for Tr
388-
let wit = construct_tap_witness(spk, &sat, allow_mall)
389-
.map_err(|e| Error::InputError(e, index))?;
390-
(wit, Script::new())
391-
} else {
392-
// Get a descriptor for this input.
393-
let desc = get_descriptor(&psbt, index).map_err(|e| Error::InputError(e, index))?;
368+
pub(super) fn finalize_input<C: secp256k1::Verification>(
369+
psbt: &mut Psbt,
370+
index: usize,
371+
secp: &Secp256k1<C>,
372+
allow_mall: bool,
373+
) -> Result<(), super::Error> {
374+
let (witness, script_sig) = {
375+
let spk = get_scriptpubkey(psbt, index).map_err(|e| Error::InputError(e, index))?;
376+
let sat = PsbtInputSatisfier::new(&psbt, index);
394377

395-
//generate the satisfaction witness and scriptsig
396-
if !allow_mall {
397-
desc.get_satisfaction(PsbtInputSatisfier::new(&psbt, index))
398-
} else {
399-
desc.get_satisfaction_mall(PsbtInputSatisfier::new(&psbt, index))
400-
}
401-
.map_err(|e| Error::InputError(InputError::MiniscriptError(e), index))?
378+
if spk.is_v1_p2tr() {
379+
// Deal with tr case separately, unfortunately we cannot infer the full descriptor for Tr
380+
let wit = construct_tap_witness(spk, &sat, allow_mall)
381+
.map_err(|e| Error::InputError(e, index))?;
382+
(wit, Script::new())
383+
} else {
384+
// Get a descriptor for this input.
385+
let desc = get_descriptor(&psbt, index).map_err(|e| Error::InputError(e, index))?;
386+
387+
//generate the satisfaction witness and scriptsig
388+
let sat = PsbtInputSatisfier::new(&psbt, index);
389+
if !allow_mall {
390+
desc.get_satisfaction(sat)
391+
} else {
392+
desc.get_satisfaction_mall(sat)
402393
}
403-
};
394+
.map_err(|e| Error::InputError(InputError::MiniscriptError(e), index))?
395+
}
396+
};
404397

398+
{
405399
let input = &mut psbt.inputs[index];
406400
//Fill in the satisfactions
407401
input.final_script_sig = if script_sig.is_empty() {
@@ -434,16 +428,17 @@ pub fn finalize_helper<C: secp256k1::Verification>(
434428
input.tap_internal_key = None; // x017
435429
input.tap_merkle_root = None; // 0x018
436430
}
437-
// Double check everything with the interpreter
438-
// This only checks whether the script will be executed
439-
// correctly by the bitcoin interpreter under the current
440-
// psbt context.
441-
interpreter_check(&psbt, secp)?;
431+
let utxos = prevouts(&psbt)?;
432+
let utxos = &Prevouts::All(&utxos);
433+
interpreter_inp_check(psbt, secp, index, utxos)?;
434+
442435
Ok(())
443436
}
444437

445438
#[cfg(test)]
446439
mod tests {
440+
use psbt::PsbtExt;
441+
447442
use super::*;
448443

449444
use bitcoin::consensus::encode::deserialize;
@@ -454,7 +449,7 @@ mod tests {
454449
let mut psbt: bitcoin::util::psbt::PartiallySignedTransaction = deserialize(&Vec::<u8>::from_hex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f012202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000").unwrap()).unwrap();
455450

456451
let secp = Secp256k1::verification_only();
457-
finalize(&mut psbt, &secp).unwrap();
452+
psbt.finalize_mut(&secp).unwrap();
458453

459454
let expected: bitcoin::util::psbt::PartiallySignedTransaction = deserialize(&Vec::<u8>::from_hex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000").unwrap()).unwrap();
460455
assert_eq!(psbt, expected);

0 commit comments

Comments
 (0)