Skip to content

Commit 8b2e470

Browse files
committed
feat: Expose psbt output
1 parent 110703d commit 8b2e470

File tree

2 files changed

+175
-0
lines changed

2 files changed

+175
-0
lines changed

bdk-ffi/src/bitcoin.rs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use bdk_wallet::bitcoin::hashes::sha256::Hash as BitcoinSha256Hash;
1919
use bdk_wallet::bitcoin::hashes::sha256d::Hash as BitcoinDoubleSha256Hash;
2020
use bdk_wallet::bitcoin::io::Cursor;
2121
use bdk_wallet::bitcoin::psbt::Input as BdkInput;
22+
use bdk_wallet::bitcoin::psbt::Output as BdkOutput;
2223
use bdk_wallet::bitcoin::secp256k1::Secp256k1;
2324
use bdk_wallet::bitcoin::Amount as BdkAmount;
2425
use bdk_wallet::bitcoin::BlockHash as BitcoinBlockHash;
@@ -767,6 +768,165 @@ impl From<&BdkInput> for Input {
767768
}
768769
}
769770

771+
#[derive(Clone, Debug, uniffi::Enum)]
772+
pub enum TapLeaf {
773+
/// A known script with its version
774+
Script {
775+
script: Arc<Script>,
776+
version: u8,
777+
},
778+
/// Hidden node with the given node hash
779+
Hidden {
780+
hash: String,
781+
},
782+
}
783+
784+
#[derive(Clone, Debug, uniffi::Record)]
785+
pub struct LeafNode {
786+
/// The tap leaf (script or hidden)
787+
pub leaf: TapLeaf,
788+
/// The merkle proof (hashing partners) to get this node as hex strings
789+
pub merkle_branch: Vec<String>,
790+
}
791+
792+
#[derive(Clone, Debug, uniffi::Record)]
793+
pub struct TapTree {
794+
/// TapTree merkle root (TapNodeHash) as hex string
795+
pub hash: String,
796+
/// Whether the tree contains hidden nodes
797+
pub has_hidden_nodes: bool,
798+
/// Leaf nodes in this tree
799+
pub leaves: Vec<LeafNode>,
800+
}
801+
802+
#[derive(Clone, Debug, uniffi::Record)]
803+
pub struct Output {
804+
/// The redeem script for this output.
805+
pub redeem_script: Option<Arc<Script>>,
806+
/// The witness script for this output.
807+
pub witness_script: Option<Arc<Script>>,
808+
/// Map of public keys needed to spend this output to their corresponding
809+
/// master key fingerprints and derivation paths.
810+
pub bip32_derivation: HashMap<String, KeySource>,
811+
/// Taproot Internal key.
812+
pub tap_internal_key: Option<String>,
813+
/// Taproot Output tree (structured record).
814+
pub tap_tree: Option<TapTree>,
815+
/// Map of tap root x only keys to origin info and leaf hashes contained in it.
816+
pub tap_key_origins: HashMap<String, TapKeyOrigin>,
817+
/// Proprietary key-value pairs for this output.
818+
pub proprietary: HashMap<ProprietaryKey, Vec<u8>>,
819+
/// Unknown key-value pairs for this output.
820+
pub unknown: HashMap<Key, Vec<u8>>,
821+
}
822+
823+
impl From<&BdkOutput> for Output {
824+
fn from(output: &BdkOutput) -> Self {
825+
Output {
826+
redeem_script: output
827+
.redeem_script
828+
.as_ref()
829+
.map(|s| Arc::new(Script(s.clone()))),
830+
witness_script: output
831+
.witness_script
832+
.as_ref()
833+
.map(|s| Arc::new(Script(s.clone()))),
834+
bip32_derivation: output
835+
.bip32_derivation
836+
.iter()
837+
.map(|(pk, (fingerprint, deriv_path))| {
838+
(
839+
pk.to_string(),
840+
KeySource {
841+
fingerprint: fingerprint.to_string(),
842+
path: deriv_path.to_string(),
843+
},
844+
)
845+
})
846+
.collect(),
847+
tap_internal_key: output.tap_internal_key.as_ref().map(|k| k.to_string()),
848+
tap_tree: output.tap_tree.as_ref().map(|t| {
849+
let node_info = t.node_info();
850+
let leaves = node_info
851+
.leaf_nodes()
852+
.map(|leaf_node| {
853+
let leaf = match leaf_node.leaf() {
854+
bdk_wallet::bitcoin::taproot::TapLeaf::Script(script, ver) => {
855+
TapLeaf::Script {
856+
script: Arc::new(Script(script.clone())),
857+
version: ver.to_consensus(),
858+
}
859+
}
860+
bdk_wallet::bitcoin::taproot::TapLeaf::Hidden(hash) => {
861+
TapLeaf::Hidden {
862+
hash: hash.to_string(),
863+
}
864+
}
865+
};
866+
867+
LeafNode {
868+
leaf,
869+
merkle_branch: leaf_node
870+
.merkle_branch()
871+
.iter()
872+
.map(|h| h.to_string())
873+
.collect(),
874+
}
875+
})
876+
.collect();
877+
878+
TapTree {
879+
hash: node_info.node_hash().to_string(),
880+
has_hidden_nodes: false,
881+
leaves,
882+
}
883+
}),
884+
tap_key_origins: output
885+
.tap_key_origins
886+
.iter()
887+
.map(|(k, v)| {
888+
let key = k.to_string();
889+
let value = TapKeyOrigin {
890+
tap_leaf_hashes: v.0.iter().map(|h| h.to_string()).collect(),
891+
key_source: KeySource {
892+
fingerprint: v.1 .0.to_string(),
893+
path: v.1 .1.to_string(),
894+
},
895+
};
896+
(key, value)
897+
})
898+
.collect(),
899+
proprietary: output
900+
.proprietary
901+
.iter()
902+
.map(|(k, v)| {
903+
(
904+
ProprietaryKey {
905+
prefix: k.prefix.clone(),
906+
subtype: k.subtype,
907+
key: k.key.clone(),
908+
},
909+
v.to_vec(),
910+
)
911+
})
912+
.collect(),
913+
unknown: output
914+
.unknown
915+
.iter()
916+
.map(|(k, v)| {
917+
(
918+
Key {
919+
key: k.key.clone(),
920+
type_value: k.type_value,
921+
},
922+
v.to_vec(),
923+
)
924+
})
925+
.collect(),
926+
}
927+
}
928+
}
929+
770930
/// A Partially Signed Transaction.
771931
#[derive(uniffi::Object)]
772932
pub struct Psbt(pub(crate) Mutex<BdkPsbt>);
@@ -898,6 +1058,12 @@ impl Psbt {
8981058
let psbt = self.0.lock().unwrap();
8991059
psbt.inputs.iter().map(|input| input.into()).collect()
9001060
}
1061+
1062+
/// The corresponding key-value map for each output in the unsigned transaction.
1063+
pub fn output(&self) -> Vec<Output> {
1064+
let psbt = self.0.lock().unwrap();
1065+
psbt.outputs.iter().map(|o| o.into()).collect()
1066+
}
9011067
}
9021068

9031069
impl From<BdkPsbt> for Psbt {

bdk-ffi/src/tests/bitcoin.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,15 @@ fn test_psbt_input_proprietary() {
524524
);
525525
}
526526

527+
#[test]
528+
fn test_psbt_output_length() {
529+
let psbt = sample_psbt();
530+
let psbt_outputs = psbt.output();
531+
println!("Psbt Output: {:?}", psbt_outputs);
532+
533+
assert_eq!(psbt_outputs.len(), 2);
534+
}
535+
527536
fn sample_psbt() -> Psbt {
528537
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())
529538
.unwrap()

0 commit comments

Comments
 (0)