Skip to content

Commit 420f0ff

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

File tree

1 file changed

+266
-3
lines changed

1 file changed

+266
-3
lines changed

bdk-ffi/src/bitcoin.rs

Lines changed: 266 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,249 @@ 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+
}
568+
/// A key-value map for an input of the corresponding index in the unsigned transaction.
569+
#[derive(Clone, Debug, uniffi::Record)]
570+
pub struct Input {
571+
/// The non-witness transaction this input spends from. Should only be
572+
/// `Option::Some` for inputs which spend non-segwit outputs or
573+
/// if it is unknown whether an input spends a segwit output.
574+
pub non_witness_utxo: Option<Arc<Transaction>>,
575+
/// The transaction output this input spends from. Should only be
576+
/// `Option::Some` for inputs which spend segwit outputs,
577+
/// including P2SH embedded ones.
578+
pub witness_utxo: Option<TxOut>,
579+
/// A map from public keys to their corresponding signature as would be
580+
// pushed to the stack from a scriptSig or witness for a non-taproot inputs.
581+
pub partial_sigs: Vec<PartialSig>,
582+
/// The sighash type to be used for this input. Signatures for this input
583+
/// must use the sighash type.
584+
pub sighash_type: Option<String>,
585+
/// The redeem script for this input.
586+
pub redeem_script: Option<Arc<Script>>,
587+
/// The witness script for this input.
588+
pub witness_script: Option<Arc<Script>>,
589+
/// A map from public keys needed to sign this input to their corresponding
590+
/// master key fingerprints and derivation paths.
591+
pub bip32_derivation: Vec<Bip32Derivation>,
592+
593+
/// The finalized, fully-constructed scriptSig with signatures and any other
594+
/// scripts necessary for this input to pass validation.
595+
pub final_script_sig: Option<Arc<Script>>,
596+
597+
/// The finalized, fully-constructed scriptWitness with signatures and any
598+
/// other scripts necessary for this input to pass validation.
599+
pub final_script_witness: Option<Vec<Vec<u8>>>,
600+
/// RIPEMD160 hash to preimage map.
601+
pub ripemd160_preimages: Vec<PreimagePair>,
602+
/// SHA256 hash to preimage map.
603+
pub sha256_preimages: Vec<PreimagePair>,
604+
/// HSAH160 hash to preimage map.
605+
pub hash160_preimages: Vec<PreimagePair>,
606+
/// HAS256 hash to preimage map.
607+
pub hash256_preimages: Vec<PreimagePair>,
608+
/// Serialized taproot signature with sighash type for key spend.
609+
pub tap_key_sig: Option<Vec<u8>>,
610+
/// Map of `<xonlypubkey>|<leafhash>` with signature.
611+
pub tap_script_sigs: Vec<TapScriptSig>,
612+
/// Map of Control blocks to Script version pair.
613+
pub tap_scripts: Vec<TapScriptEntry>,
614+
/// Map of tap root x only keys to origin info and leaf hashes contained in it.
615+
pub tap_key_origins: Vec<TapKeyOrigin>,
616+
/// Taproot Internal key.
617+
pub tap_internal_key: Option<String>,
618+
/// Taproot Merkle root.
619+
pub tap_merkle_root: Option<String>,
620+
/// Proprietary key-value pairs for this input.
621+
pub proprietary: Vec<KeyValuePair>,
622+
/// Unknown key-value pairs for this input.
623+
pub unknown: Vec<KeyValuePair>,
624+
}
625+
626+
impl From<&BdkInput> for Input {
627+
fn from(input: &BdkInput) -> Self {
628+
Input {
629+
non_witness_utxo: input
630+
.non_witness_utxo
631+
.as_ref()
632+
.map(|tx| Arc::new(Transaction(tx.clone()))),
633+
witness_utxo: input.witness_utxo.as_ref().map(TxOut::from),
634+
partial_sigs: input
635+
.partial_sigs
636+
.iter()
637+
.map(|(k, v)| PartialSig {
638+
pubkey: k.to_string(),
639+
signature: v.to_vec(),
640+
})
641+
.collect::<Vec<PartialSig>>(),
642+
sighash_type: input.sighash_type.as_ref().map(|s| s.to_string()),
643+
redeem_script: input
644+
.redeem_script
645+
.as_ref()
646+
.map(|s| Arc::new(Script(s.clone()))),
647+
witness_script: input
648+
.witness_script
649+
.as_ref()
650+
.map(|s| Arc::new(Script(s.clone()))),
651+
bip32_derivation: input
652+
.bip32_derivation
653+
.iter()
654+
.map(|(k, v)| Bip32Derivation {
655+
pubkey: k.to_string(),
656+
fingerprint: v.0.to_string(),
657+
path: v.1.to_string(),
658+
})
659+
.collect::<Vec<Bip32Derivation>>(),
660+
final_script_sig: input
661+
.final_script_sig
662+
.as_ref()
663+
.map(|s| Arc::new(Script(s.clone()))),
664+
final_script_witness: input.final_script_witness.as_ref().map(|w| w.to_vec()),
665+
ripemd160_preimages: input
666+
.ripemd160_preimages
667+
.iter()
668+
.map(|(k, v)| PreimagePair {
669+
hash: k.to_string(),
670+
preimage: v.to_vec(),
671+
})
672+
.collect::<Vec<PreimagePair>>(),
673+
sha256_preimages: input
674+
.sha256_preimages
675+
.iter()
676+
.map(|(k, v)| PreimagePair {
677+
hash: k.to_string(),
678+
preimage: v.to_vec(),
679+
})
680+
.collect::<Vec<PreimagePair>>(),
681+
hash160_preimages: input
682+
.hash160_preimages
683+
.iter()
684+
.map(|(k, v)| PreimagePair {
685+
hash: k.to_string(),
686+
preimage: v.to_vec(),
687+
})
688+
.collect::<Vec<PreimagePair>>(),
689+
hash256_preimages: input
690+
.hash256_preimages
691+
.iter()
692+
.map(|(k, v)| PreimagePair {
693+
hash: k.to_string(),
694+
preimage: v.to_vec(),
695+
})
696+
.collect::<Vec<PreimagePair>>(),
697+
tap_key_sig: input.tap_key_sig.as_ref().map(|s| s.serialize().to_vec()),
698+
tap_script_sigs: input
699+
.tap_script_sigs
700+
.iter()
701+
.map(|(k, v)| TapScriptSig {
702+
xonly_pubkey: k.0.to_string(),
703+
leaf_hash: k.1.to_string(),
704+
signature: v.to_vec(),
705+
})
706+
.collect::<Vec<TapScriptSig>>(),
707+
tap_scripts: input
708+
.tap_scripts
709+
.iter()
710+
.map(|(k, v)| TapScriptEntry {
711+
control_block: DisplayHex::to_lower_hex_string(&k.serialize()),
712+
script: Arc::new(v.0.clone().into()),
713+
leaf_version: v.1.to_consensus(),
714+
})
715+
.collect::<Vec<TapScriptEntry>>(),
716+
tap_key_origins: input
717+
.tap_key_origins
718+
.iter()
719+
.map(|(k, v)| TapKeyOrigin {
720+
xonly_pubkey: k.to_string(),
721+
leaf_hashes: v.0.iter().map(|h| h.to_string()).collect(),
722+
fingerprint: v.1 .0.to_string(),
723+
path: v.1 .1.to_string(),
724+
})
725+
.collect::<Vec<TapKeyOrigin>>(),
726+
tap_internal_key: input.tap_internal_key.as_ref().map(|k| k.to_string()),
727+
tap_merkle_root: input.tap_merkle_root.as_ref().map(|k| k.to_string()),
728+
proprietary: input
729+
.proprietary
730+
.iter()
731+
.map(|(k, v)| KeyValuePair {
732+
key: k.to_key().key.clone(),
733+
value: v.to_vec(),
734+
})
735+
.collect(),
736+
unknown: input
737+
.unknown
738+
.iter()
739+
.map(|(k, v)| KeyValuePair {
740+
key: k.key.clone(),
741+
value: v.to_vec(),
742+
})
743+
.collect(),
744+
}
745+
}
746+
}
747+
503748
/// A Partially Signed Transaction.
504749
#[derive(uniffi::Object)]
505750
pub struct Psbt(pub(crate) Mutex<BdkPsbt>);
@@ -625,6 +870,12 @@ impl Psbt {
625870
let utxo = psbt.spend_utxo(input_index as usize).unwrap();
626871
serde_json::to_string(&utxo).unwrap()
627872
}
873+
874+
/// The corresponding key-value map for each input in the unsigned transaction.
875+
pub fn input(&self) -> Vec<Input> {
876+
let psbt = self.0.lock().unwrap();
877+
psbt.inputs.iter().map(|input| input.into()).collect()
878+
}
628879
}
629880

630881
impl From<BdkPsbt> for Psbt {
@@ -1135,10 +1386,8 @@ mod tests {
11351386

11361387
#[test]
11371388
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();
1389+
let psbt = sample_psbt();
11401390
let psbt_utxo = psbt.spend_utxo(0);
1141-
11421391
println!("Psbt utxo: {:?}", psbt_utxo);
11431392

11441393
assert_eq!(
@@ -1147,4 +1396,18 @@ mod tests {
11471396
"Psbt utxo does not match the expected value"
11481397
);
11491398
}
1399+
1400+
#[test]
1401+
fn test_psbt_input() {
1402+
let psbt = sample_psbt();
1403+
let psbt_inputs = psbt.input();
1404+
println!("Psbt Input: {:?}", psbt_inputs);
1405+
1406+
assert_eq!(psbt_inputs.len(), 1);
1407+
}
1408+
1409+
fn sample_psbt() -> Psbt {
1410+
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())
1411+
.unwrap()
1412+
}
11501413
}

0 commit comments

Comments
 (0)