Skip to content

Commit e3fd002

Browse files
committed
feat: Add Input for PSBT
This allows easier access to the inputs of our PSBT type
1 parent cc7e216 commit e3fd002

File tree

2 files changed

+353
-2
lines changed

2 files changed

+353
-2
lines changed

bdk-ffi/src/bitcoin.rs

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::error::{
44
};
55
use crate::error::{ParseAmountError, PsbtFinalizeError};
66
use crate::{impl_from_core_type, impl_hash_like, impl_into_core_type};
7+
use std::collections::HashMap;
78

89
use bdk_wallet::bitcoin::address::NetworkChecked;
910
use bdk_wallet::bitcoin::address::NetworkUnchecked;
@@ -14,7 +15,9 @@ use bdk_wallet::bitcoin::consensus::encode::serialize;
1415
use bdk_wallet::bitcoin::consensus::Decodable;
1516
use bdk_wallet::bitcoin::hashes::sha256::Hash as BitcoinSha256Hash;
1617
use bdk_wallet::bitcoin::hashes::sha256d::Hash as BitcoinDoubleSha256Hash;
18+
use bdk_wallet::bitcoin::hex::DisplayHex;
1719
use bdk_wallet::bitcoin::io::Cursor;
20+
use bdk_wallet::bitcoin::psbt::Input as BdkInput;
1821
use bdk_wallet::bitcoin::secp256k1::Secp256k1;
1922
use bdk_wallet::bitcoin::Amount as BdkAmount;
2023
use bdk_wallet::bitcoin::BlockHash as BitcoinBlockHash;
@@ -500,6 +503,238 @@ impl Display for Transaction {
500503
}
501504
}
502505

506+
#[derive(Clone, Debug, uniffi::Record)]
507+
pub struct TapScriptSig {
508+
/// x-only pubkey as hex
509+
pub xonly_pubkey: String,
510+
/// tap leaf hash as hex
511+
pub leaf_hash: String,
512+
/// signature bytes
513+
pub signature: Vec<u8>,
514+
}
515+
516+
#[derive(Clone, Debug, uniffi::Record)]
517+
pub struct TapScriptEntry {
518+
/// control block bytes
519+
pub control_block: String,
520+
/// script (reuse existing `Script` FFI type)
521+
pub script: Arc<Script>,
522+
/// leaf version
523+
pub leaf_version: u8,
524+
}
525+
526+
#[derive(Clone, Debug, uniffi::Record)]
527+
pub struct TapKeyOrigin {
528+
/// x-only pubkey as hex
529+
pub xonly_pubkey: String,
530+
/// leaf hashes as hex strings
531+
pub leaf_hashes: Vec<String>,
532+
/// master key fingerprint
533+
pub fingerprint: String,
534+
/// derivation path segments
535+
pub path: String,
536+
}
537+
538+
#[derive(Clone, Debug, uniffi::Record)]
539+
pub struct KeyValuePair {
540+
/// raw key bytes (for proprietary / unknown keys)
541+
pub key: Vec<u8>,
542+
/// raw value bytes
543+
pub value: Vec<u8>,
544+
/// The type of this PSBT key.
545+
pub type_value: u8,
546+
}
547+
548+
#[derive(Clone, Debug, uniffi::Record)]
549+
pub struct ProprietaryKeyValuePair {
550+
/// Proprietary type prefix used for grouping together keys under some application and avoid namespace collision
551+
pub prefix: Vec<u8>,
552+
/// Custom proprietary subtype
553+
pub subtype: u8,
554+
/// Additional key bytes (like serialized public key data etc)
555+
pub key: Vec<u8>,
556+
/// raw value bytes
557+
pub value: Vec<u8>,
558+
}
559+
560+
#[derive(Clone, Debug, uniffi::Record)]
561+
pub struct KeySource {
562+
pub fingerprint: String,
563+
pub path: String,
564+
}
565+
566+
/// A key-value map for an input of the corresponding index in the unsigned transaction.
567+
#[derive(Clone, Debug, uniffi::Record)]
568+
pub struct Input {
569+
/// The non-witness transaction this input spends from. Should only be
570+
/// `Option::Some` for inputs which spend non-segwit outputs or
571+
/// if it is unknown whether an input spends a segwit output.
572+
pub non_witness_utxo: Option<Arc<Transaction>>,
573+
/// The transaction output this input spends from. Should only be
574+
/// `Option::Some` for inputs which spend segwit outputs,
575+
/// including P2SH embedded ones.
576+
pub witness_utxo: Option<TxOut>,
577+
/// A map from public keys to their corresponding signature as would be
578+
/// pushed to the stack from a scriptSig or witness for a non-taproot inputs.
579+
pub partial_sigs: HashMap<String, Vec<u8>>,
580+
/// The sighash type to be used for this input. Signatures for this input
581+
/// must use the sighash type.
582+
pub sighash_type: Option<String>,
583+
/// The redeem script for this input.
584+
pub redeem_script: Option<Arc<Script>>,
585+
/// The witness script for this input.
586+
pub witness_script: Option<Arc<Script>>,
587+
/// A map from public keys needed to sign this input to their corresponding
588+
/// master key fingerprints and derivation paths.
589+
pub bip32_derivation: HashMap<String, KeySource>,
590+
591+
/// The finalized, fully-constructed scriptSig with signatures and any other
592+
/// scripts necessary for this input to pass validation.
593+
pub final_script_sig: Option<Arc<Script>>,
594+
595+
/// The finalized, fully-constructed scriptWitness with signatures and any
596+
/// other scripts necessary for this input to pass validation.
597+
pub final_script_witness: Option<Vec<Vec<u8>>>,
598+
/// RIPEMD160 hash to preimage map.
599+
pub ripemd160_preimages: HashMap<String, Vec<u8>>,
600+
/// SHA256 hash to preimage map.
601+
pub sha256_preimages: HashMap<String, Vec<u8>>,
602+
/// HASH160 hash to preimage map.
603+
pub hash160_preimages: HashMap<String, Vec<u8>>,
604+
/// HASH256 hash to preimage map.
605+
pub hash256_preimages: HashMap<String, Vec<u8>>,
606+
/// Serialized taproot signature with sighash type for key spend.
607+
pub tap_key_sig: Option<Vec<u8>>,
608+
/// Map of `<xonlypubkey>|<leafhash>` with signature.
609+
pub tap_script_sigs: Vec<TapScriptSig>,
610+
/// Map of Control blocks to Script version pair.
611+
pub tap_scripts: Vec<TapScriptEntry>,
612+
/// Map of tap root x only keys to origin info and leaf hashes contained in it.
613+
pub tap_key_origins: Vec<TapKeyOrigin>,
614+
/// Taproot Internal key.
615+
pub tap_internal_key: Option<String>,
616+
/// Taproot Merkle root.
617+
pub tap_merkle_root: Option<String>,
618+
/// Proprietary key-value pairs for this input.
619+
pub proprietary: Vec<ProprietaryKeyValuePair>,
620+
/// Unknown key-value pairs for this input.
621+
pub unknown: Vec<KeyValuePair>,
622+
}
623+
624+
impl From<&BdkInput> for Input {
625+
fn from(input: &BdkInput) -> Self {
626+
Input {
627+
non_witness_utxo: input
628+
.non_witness_utxo
629+
.as_ref()
630+
.map(|tx| Arc::new(Transaction(tx.clone()))),
631+
witness_utxo: input.witness_utxo.as_ref().map(TxOut::from),
632+
partial_sigs: input
633+
.partial_sigs
634+
.iter()
635+
.map(|(k, v)| (k.to_string(), v.to_vec()))
636+
.collect(),
637+
sighash_type: input.sighash_type.as_ref().map(|s| s.to_string()),
638+
redeem_script: input
639+
.redeem_script
640+
.as_ref()
641+
.map(|s| Arc::new(Script(s.clone()))),
642+
witness_script: input
643+
.witness_script
644+
.as_ref()
645+
.map(|s| Arc::new(Script(s.clone()))),
646+
bip32_derivation: input
647+
.bip32_derivation
648+
.iter()
649+
.map(|(pk, (fingerprint, deriv_path))| {
650+
(
651+
pk.to_string(),
652+
KeySource {
653+
fingerprint: fingerprint.to_string(),
654+
path: deriv_path.to_string(),
655+
},
656+
)
657+
})
658+
.collect(),
659+
final_script_sig: input
660+
.final_script_sig
661+
.as_ref()
662+
.map(|s| Arc::new(Script(s.clone()))),
663+
final_script_witness: input.final_script_witness.as_ref().map(|w| w.to_vec()),
664+
ripemd160_preimages: input
665+
.ripemd160_preimages
666+
.iter()
667+
.map(|(k, v)| (k.to_string(), v.clone()))
668+
.collect(),
669+
sha256_preimages: input
670+
.sha256_preimages
671+
.iter()
672+
.map(|(k, v)| (k.to_string(), v.clone()))
673+
.collect(),
674+
hash160_preimages: input
675+
.hash160_preimages
676+
.iter()
677+
.map(|(k, v)| (k.to_string(), v.clone()))
678+
.collect(),
679+
hash256_preimages: input
680+
.hash256_preimages
681+
.iter()
682+
.map(|(k, v)| (k.to_string(), v.clone()))
683+
.collect(),
684+
tap_key_sig: input.tap_key_sig.as_ref().map(|s| s.serialize().to_vec()),
685+
tap_script_sigs: input
686+
.tap_script_sigs
687+
.iter()
688+
.map(|(k, v)| TapScriptSig {
689+
xonly_pubkey: k.0.to_string(),
690+
leaf_hash: k.1.to_string(),
691+
signature: v.to_vec(),
692+
})
693+
.collect::<Vec<TapScriptSig>>(),
694+
tap_scripts: input
695+
.tap_scripts
696+
.iter()
697+
.map(|(k, v)| TapScriptEntry {
698+
control_block: DisplayHex::to_lower_hex_string(&k.serialize()),
699+
script: Arc::new(v.0.clone().into()),
700+
leaf_version: v.1.to_consensus(),
701+
})
702+
.collect::<Vec<TapScriptEntry>>(),
703+
tap_key_origins: input
704+
.tap_key_origins
705+
.iter()
706+
.map(|(k, v)| TapKeyOrigin {
707+
xonly_pubkey: k.to_string(),
708+
leaf_hashes: v.0.iter().map(|h| h.to_string()).collect(),
709+
fingerprint: v.1 .0.to_string(),
710+
path: v.1 .1.to_string(),
711+
})
712+
.collect::<Vec<TapKeyOrigin>>(),
713+
tap_internal_key: input.tap_internal_key.as_ref().map(|k| k.to_string()),
714+
tap_merkle_root: input.tap_merkle_root.as_ref().map(|k| k.to_string()),
715+
proprietary: input
716+
.proprietary
717+
.iter()
718+
.map(|(k, v)| ProprietaryKeyValuePair {
719+
key: k.to_key().key.clone(),
720+
subtype: k.subtype,
721+
prefix: k.prefix.to_vec(),
722+
value: v.to_vec(),
723+
})
724+
.collect(),
725+
unknown: input
726+
.unknown
727+
.iter()
728+
.map(|(k, v)| KeyValuePair {
729+
key: k.key.clone(),
730+
value: v.to_vec(),
731+
type_value: k.type_value,
732+
})
733+
.collect(),
734+
}
735+
}
736+
}
737+
503738
/// A Partially Signed Transaction.
504739
#[derive(uniffi::Object)]
505740
pub struct Psbt(pub(crate) Mutex<BdkPsbt>);
@@ -625,6 +860,12 @@ impl Psbt {
625860
let utxo = psbt.spend_utxo(input_index as usize).unwrap();
626861
serde_json::to_string(&utxo).unwrap()
627862
}
863+
864+
/// The corresponding key-value map for each input in the unsigned transaction.
865+
pub fn input(&self) -> Vec<Input> {
866+
let psbt = self.0.lock().unwrap();
867+
psbt.inputs.iter().map(|input| input.into()).collect()
868+
}
628869
}
629870

630871
impl From<BdkPsbt> for Psbt {

bdk-ffi/src/tests/bitcoin.rs

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::bitcoin::{Address, Network, Psbt};
2+
use bdk_electrum::bdk_core::bitcoin::hex::DisplayHex;
23

34
#[test]
45
fn test_is_valid_for_network() {
@@ -357,8 +358,7 @@ fn test_to_address_data() {
357358

358359
#[test]
359360
fn test_psbt_spend_utxo() {
360-
let psbt = Psbt::new("cHNidP8BAH0CAAAAAXHl8cCbj84lm1v42e54IGI6CQru/nBXwrPE3q2fiGO4AAAAAAD9////Ar4DAAAAAAAAIgAgYw/rnGd4Bifj8s7TaMgR2tal/lq+L1jVv2Sqd1mxMbJEEQAAAAAAABYAFNVpt8vHYUPZNSF6Hu07uP1YeHts4QsAAAABALUCAAAAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////BAJ+CwD/////AkAlAAAAAAAAIgAgQyrnn86L9D3vDiH959KJbPudDHc/bp6nI9E5EBLQD1YAAAAAAAAAACZqJKohqe3i9hw/cdHe/T+pmd+jaVN1XGkGiXmZYrSL69g2l06M+QEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQErQCUAAAAAAAAiACBDKuefzov0Pe8OIf3n0ols+50Mdz9unqcj0TkQEtAPViICAy4V+d/Qff71zzPXxK4FWG5x+wL/Ku93y/LG5p+0rI2xSDBFAiEA9b0OdASAs0P2uhQinjN7QGP5jX/b32LcShBmny8U0RUCIBebxvCDbpchCjqLAhOMjydT80DAzokaalGzV7XVTsbiASICA1tMY+46EgxIHU18bgHnUvAAlAkMq5LfwkpOGZ97sDKRRzBEAiBpmlZwJocNEiKLxexEX0Par6UgG8a89AklTG3/z9AHlAIgQH/ybCvfKJzr2dq0+IyueDebm7FamKIJdzBYWMXRr/wBIgID+aCzK9nclwhbbN7KbIVGUQGLWZsjcaqWPxk9gFeG+FxIMEUCIQDRPBzb0i9vaUmxCcs1yz8uq4tq1mdDAYvvYn3isKEhFAIgfmeTLLzMo0mmQ23ooMnyx6iPceE8xV5CvARuJsd88tEBAQVpUiEDW0xj7joSDEgdTXxuAedS8ACUCQyrkt/CSk4Zn3uwMpEhAy4V+d/Qff71zzPXxK4FWG5x+wL/Ku93y/LG5p+0rI2xIQP5oLMr2dyXCFts3spshUZRAYtZmyNxqpY/GT2AV4b4XFOuIgYDLhX539B9/vXPM9fErgVYbnH7Av8q73fL8sbmn7SsjbEYCapBE1QAAIABAACAAAAAgAAAAAAAAAAAIgYDW0xj7joSDEgdTXxuAedS8ACUCQyrkt/CSk4Zn3uwMpEY2bvrelQAAIABAACAAAAAgAAAAAAAAAAAIgYD+aCzK9nclwhbbN7KbIVGUQGLWZsjcaqWPxk9gFeG+FwYAKVFVFQAAIABAACAAAAAgAAAAAAAAAAAAAEBaVIhA7cr8fTHOPtE+t0zM3iWJvpfPvsNaVyQ0Sar6nIe9tQXIQMm7k7OY+q+Lsge3bVACuSa9r19Js+lNuTtEhehWkpe1iECelHmzmhzDsQTDnApIcnWRz3oFR68UX1ag8jfk/SKuopTriICAnpR5s5ocw7EEw5wKSHJ1kc96BUevFF9WoPI35P0irqKGAClRVRUAACAAQAAgAAAAIABAAAAAAAAACICAybuTs5j6r4uyB7dtUAK5Jr2vX0mz6U25O0SF6FaSl7WGAmqQRNUAACAAQAAgAAAAIABAAAAAAAAACICA7cr8fTHOPtE+t0zM3iWJvpfPvsNaVyQ0Sar6nIe9tQXGNm763pUAACAAQAAgAAAAIABAAAAAAAAAAAA".to_string())
361-
.unwrap();
361+
let psbt = sample_psbt();
362362
let psbt_utxo = psbt.spend_utxo(0);
363363

364364
println!("Psbt utxo: {:?}", psbt_utxo);
@@ -369,3 +369,113 @@ fn test_psbt_spend_utxo() {
369369
"Psbt utxo does not match the expected value"
370370
);
371371
}
372+
373+
#[test]
374+
fn test_psbt_input_length() {
375+
let psbt = sample_psbt2();
376+
let psbt_inputs = psbt.input();
377+
println!("Psbt Input: {:?}", psbt_inputs);
378+
379+
let unknown = &psbt_inputs[0].unknown;
380+
println!("Unknown field in Psbt Input: {:?}", unknown);
381+
assert_eq!(psbt_inputs.len(), 1);
382+
}
383+
384+
#[test]
385+
fn test_psbt_input_partial_sigs() {
386+
let psbt = sample_psbt();
387+
let psbt_inputs = psbt.input();
388+
println!("Psbt Input: {:?}", psbt_inputs);
389+
assert_eq!(psbt_inputs.len(), 1);
390+
391+
let psbt_input = &psbt_inputs[0];
392+
393+
let partial_sigs = &psbt_input.partial_sigs;
394+
let signatures_count = partial_sigs.len();
395+
assert_eq!(signatures_count, 3, "There should be 3 partial signatures");
396+
397+
let public_key = "032e15f9dfd07dfef5cf33d7c4ae05586e71fb02ff2aef77cbf2c6e69fb4ac8db1";
398+
let signature: Option<Vec<u8>> = partial_sigs.get(public_key).cloned();
399+
println!("Signature for pubkey {}: {:?}", public_key, signature);
400+
assert!(
401+
signature.is_some(),
402+
"Signature for the given pubkey should be present"
403+
);
404+
405+
// Convert signature from bytes to hex string (Including last byte 0x01 for sighash type)
406+
let signature_hex = DisplayHex::to_lower_hex_string(&signature.unwrap());
407+
println!("Signature in hex: {}", signature_hex);
408+
409+
let expected_signature_hex = "3045022100f5bd0e740480b343f6ba14229e337b4063f98d7fdbdf62dc4a10669f2f14d1150220179bc6f0836e97210a3a8b02138c8f2753f340c0ce891a6a51b357b5d54ec6e201";
410+
assert_eq!(
411+
signature_hex, expected_signature_hex,
412+
"Signature hex does not match the expected value"
413+
);
414+
}
415+
416+
#[test]
417+
fn test_psbt_input_bip32_derivation() {
418+
let psbt = sample_psbt();
419+
let psbt_inputs = psbt.input();
420+
assert_eq!(psbt_inputs.len(), 1);
421+
422+
let psbt_input = &psbt_inputs[0];
423+
let bip32_derivations = &psbt_input.bip32_derivation;
424+
let derivations_count = bip32_derivations.len();
425+
assert_eq!(derivations_count, 3, "There should be 3 BIP32 derivations");
426+
427+
let public_key = "032e15f9dfd07dfef5cf33d7c4ae05586e71fb02ff2aef77cbf2c6e69fb4ac8db1";
428+
let derivation = bip32_derivations.get(public_key);
429+
println!(
430+
"BIP32 Derivation for pubkey {}: {:?}",
431+
public_key, derivation
432+
);
433+
assert!(
434+
derivation.is_some(),
435+
"BIP32 derivation for the given pubkey should be present"
436+
);
437+
438+
let fingerprint = &derivation.unwrap().fingerprint;
439+
let expected_fingerprint = "09aa4113";
440+
assert_eq!(
441+
fingerprint, &expected_fingerprint,
442+
"Fingerprint does not match the expected value"
443+
);
444+
445+
let derivation_path = &derivation.unwrap().path;
446+
let expected_derivation_path = "84'/1'/0'/0/0";
447+
assert_eq!(
448+
derivation_path, &expected_derivation_path,
449+
"Derivation path does not match the expected value"
450+
);
451+
}
452+
453+
#[test]
454+
fn test_psbt_input_witness_script() {
455+
let psbt = sample_psbt();
456+
let psbt_inputs = psbt.input();
457+
assert_eq!(psbt_inputs.len(), 1);
458+
459+
let psbt_input = &psbt_inputs[0];
460+
let witness_script = psbt_input
461+
.witness_script
462+
.as_ref()
463+
.expect("Witness script should be present");
464+
let byte_witness = witness_script.to_bytes();
465+
let witness_hex = DisplayHex::to_lower_hex_string(&byte_witness);
466+
let expected_witness_hex = "5221035b4c63ee3a120c481d4d7c6e01e752f00094090cab92dfc24a4e199f7bb0329121032e15f9dfd07dfef5cf33d7c4ae05586e71fb02ff2aef77cbf2c6e69fb4ac8db12103f9a0b32bd9dc97085b6cdeca6c854651018b599b2371aa963f193d805786f85c53ae";
467+
assert_eq!(
468+
witness_hex, expected_witness_hex,
469+
"Witness script hex does not match the expected value"
470+
);
471+
}
472+
473+
fn sample_psbt() -> Psbt {
474+
Psbt::new("cHNidP8BAH0CAAAAAXHl8cCbj84lm1v42e54IGI6CQru/nBXwrPE3q2fiGO4AAAAAAD9////Ar4DAAAAAAAAIgAgYw/rnGd4Bifj8s7TaMgR2tal/lq+L1jVv2Sqd1mxMbJEEQAAAAAAABYAFNVpt8vHYUPZNSF6Hu07uP1YeHts4QsAAAABALUCAAAAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////BAJ+CwD/////AkAlAAAAAAAAIgAgQyrnn86L9D3vDiH959KJbPudDHc/bp6nI9E5EBLQD1YAAAAAAAAAACZqJKohqe3i9hw/cdHe/T+pmd+jaVN1XGkGiXmZYrSL69g2l06M+QEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQErQCUAAAAAAAAiACBDKuefzov0Pe8OIf3n0ols+50Mdz9unqcj0TkQEtAPViICAy4V+d/Qff71zzPXxK4FWG5x+wL/Ku93y/LG5p+0rI2xSDBFAiEA9b0OdASAs0P2uhQinjN7QGP5jX/b32LcShBmny8U0RUCIBebxvCDbpchCjqLAhOMjydT80DAzokaalGzV7XVTsbiASICA1tMY+46EgxIHU18bgHnUvAAlAkMq5LfwkpOGZ97sDKRRzBEAiBpmlZwJocNEiKLxexEX0Par6UgG8a89AklTG3/z9AHlAIgQH/ybCvfKJzr2dq0+IyueDebm7FamKIJdzBYWMXRr/wBIgID+aCzK9nclwhbbN7KbIVGUQGLWZsjcaqWPxk9gFeG+FxIMEUCIQDRPBzb0i9vaUmxCcs1yz8uq4tq1mdDAYvvYn3isKEhFAIgfmeTLLzMo0mmQ23ooMnyx6iPceE8xV5CvARuJsd88tEBAQVpUiEDW0xj7joSDEgdTXxuAedS8ACUCQyrkt/CSk4Zn3uwMpEhAy4V+d/Qff71zzPXxK4FWG5x+wL/Ku93y/LG5p+0rI2xIQP5oLMr2dyXCFts3spshUZRAYtZmyNxqpY/GT2AV4b4XFOuIgYDLhX539B9/vXPM9fErgVYbnH7Av8q73fL8sbmn7SsjbEYCapBE1QAAIABAACAAAAAgAAAAAAAAAAAIgYDW0xj7joSDEgdTXxuAedS8ACUCQyrkt/CSk4Zn3uwMpEY2bvrelQAAIABAACAAAAAgAAAAAAAAAAAIgYD+aCzK9nclwhbbN7KbIVGUQGLWZsjcaqWPxk9gFeG+FwYAKVFVFQAAIABAACAAAAAgAAAAAAAAAAAAAEBaVIhA7cr8fTHOPtE+t0zM3iWJvpfPvsNaVyQ0Sar6nIe9tQXIQMm7k7OY+q+Lsge3bVACuSa9r19Js+lNuTtEhehWkpe1iECelHmzmhzDsQTDnApIcnWRz3oFR68UX1ag8jfk/SKuopTriICAnpR5s5ocw7EEw5wKSHJ1kc96BUevFF9WoPI35P0irqKGAClRVRUAACAAQAAgAAAAIABAAAAAAAAACICAybuTs5j6r4uyB7dtUAK5Jr2vX0mz6U25O0SF6FaSl7WGAmqQRNUAACAAQAAgAAAAIABAAAAAAAAACICA7cr8fTHOPtE+t0zM3iWJvpfPvsNaVyQ0Sar6nIe9tQXGNm763pUAACAAQAAgAAAAIABAAAAAAAAAAAA".to_string())
475+
.unwrap()
476+
}
477+
478+
fn sample_psbt2() -> Psbt {
479+
Psbt::new("cHNidP8BAFMBAAAAATkUkZZWjQ4TAMqaOkez2dl2+5yBsfd38qS6x8fkjesmAQAAAAD/////AXL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAAAAAE8BBIiyHgAAAAAAAAAAAIc9/4HAL1JWI/0f5RZ+rDpVoEnePTFLtC7iJ//tN9UIAzmjYBMwFZfa70H75ZOgLMUT0LVVJ+wt8QUOLo/0nIXCDN6tvu8AAACAAQAAABD8BXByZWZ4KnRlc3Rfa2V5AwUGBwMJAAEDAwQFAAEAjwEAAAAAAQGJo8ceq00g4Dcbu6TMaY+ilclGOvouOX+FM8y2L5Vn5QEAAAAXFgAUvhjRUqmwEgOdrz2n3k9TNJ7suYX/////AXL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUAAAAAAQEgcv74TiwAAAAXqRQzlyW6Ie/WKsdTqbzQZ9bHpqOdBYciAgM5iA3JI5S3NV49BDn6KDwx3nWQgS6gEcQkXAZ0poXog0cwRAIgT2fir7dhQtRPrliiSV0zo0GdqibNDbjQTzRStjKJrA8CIBB2Kp+2fpTMXK2QJvbcmf9/Bw9CeNMPvH0Mhp3TjH/nAQEDBIMAAAABBAFRIgYDOYgNySOUtzVePQQ5+ig8Md51kIEuoBHEJFwGdKaF6IMM3q2+7wAAAIABAAAAAQgGAgIBAwEFFQoYn3yLGjhv/o7tkbODDHp7zR53jAIBAiELoShx/uIQ+4YZKR6uoZRYHL0lMeSyN1nSJfaAaSP2MiICAQIVDBXMSeGRy8Ug2RlEYApct3r2qjKRAgECIQ12pWrO2RXSUT3NhMLDeLLoqlzWMrW3HKLyrFsOOmSb2wIBAhD8BXByZWZ4KnRlc3Rfa2V5AwUGBwMJAAEDAwQFACICAzmIDckjlLc1Xj0EOfooPDHedZCBLqARxCRcBnSmheiDDN6tvu8AAACAAQAAABD8BXByZWZ4KnRlc3Rfa2V5AwUGBwMJAAEDAwQFAA==".to_string())
480+
.unwrap()
481+
}

0 commit comments

Comments
 (0)