Skip to content

Commit f1fa7be

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

File tree

1 file changed

+284
-3
lines changed

1 file changed

+284
-3
lines changed

bdk-ffi/src/bitcoin.rs

Lines changed: 284 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ use bdk_wallet::bitcoin::consensus::encode::serialize;
1414
use bdk_wallet::bitcoin::consensus::Decodable;
1515
use bdk_wallet::bitcoin::hashes::sha256::Hash as BitcoinSha256Hash;
1616
use bdk_wallet::bitcoin::hashes::sha256d::Hash as BitcoinDoubleSha256Hash;
17+
use bdk_wallet::bitcoin::hex::DisplayHex;
1718
use bdk_wallet::bitcoin::io::Cursor;
19+
use bdk_wallet::bitcoin::psbt::Input as BdkInput;
1820
use bdk_wallet::bitcoin::secp256k1::Secp256k1;
1921
use bdk_wallet::bitcoin::Amount as BdkAmount;
2022
use bdk_wallet::bitcoin::BlockHash as BitcoinBlockHash;
@@ -500,6 +502,267 @@ impl Display for Transaction {
500502
}
501503
}
502504

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

630899
impl From<BdkPsbt> for Psbt {
@@ -1135,10 +1404,8 @@ mod tests {
11351404

11361405
#[test]
11371406
fn test_psbt_spend_utxo() {
1138-
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())
1139-
.unwrap();
1407+
let psbt = sample_psbt();
11401408
let psbt_utxo = psbt.spend_utxo(0);
1141-
11421409
println!("Psbt utxo: {:?}", psbt_utxo);
11431410

11441411
assert_eq!(
@@ -1147,4 +1414,18 @@ mod tests {
11471414
"Psbt utxo does not match the expected value"
11481415
);
11491416
}
1417+
1418+
#[test]
1419+
fn test_psbt_input() {
1420+
let psbt = sample_psbt();
1421+
let psbt_inputs = psbt.input();
1422+
println!("Psbt Input: {:?}", psbt_inputs);
1423+
1424+
assert_eq!(psbt_inputs.len(), 1);
1425+
}
1426+
1427+
fn sample_psbt() -> Psbt {
1428+
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())
1429+
.unwrap()
1430+
}
11501431
}

0 commit comments

Comments
 (0)