Skip to content

Commit 1167c84

Browse files
committed
feat: Expose psbt output
1 parent 110703d commit 1167c84

File tree

2 files changed

+193
-0
lines changed

2 files changed

+193
-0
lines changed

bdk-ffi/src/bitcoin.rs

Lines changed: 184 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,183 @@ impl From<&BdkInput> for Input {
767768
}
768769
}
769770

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

9031087
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)