Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 247 additions & 0 deletions bdk-ffi/src/bitcoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::error::{
};
use crate::error::{ParseAmountError, PsbtFinalizeError};
use crate::{impl_from_core_type, impl_hash_like, impl_into_core_type};
use std::collections::HashMap;

use bdk_wallet::bitcoin::address::NetworkChecked;
use bdk_wallet::bitcoin::address::NetworkUnchecked;
Expand All @@ -14,7 +15,9 @@ use bdk_wallet::bitcoin::consensus::encode::serialize;
use bdk_wallet::bitcoin::consensus::Decodable;
use bdk_wallet::bitcoin::hashes::sha256::Hash as BitcoinSha256Hash;
use bdk_wallet::bitcoin::hashes::sha256d::Hash as BitcoinDoubleSha256Hash;
use bdk_wallet::bitcoin::hex::DisplayHex;
use bdk_wallet::bitcoin::io::Cursor;
use bdk_wallet::bitcoin::psbt::Input as BdkInput;
use bdk_wallet::bitcoin::secp256k1::Secp256k1;
use bdk_wallet::bitcoin::Amount as BdkAmount;
use bdk_wallet::bitcoin::BlockHash as BitcoinBlockHash;
Expand Down Expand Up @@ -500,6 +503,244 @@ impl Display for Transaction {
}
}

#[derive(Clone, Debug, uniffi::Record)]
pub struct PartialSig {
pub pubkey: String,
pub signature: Vec<u8>,
}
Comment on lines +507 to +510
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the rust-bitcoin type for this field is a map, I think we could use the uniffi equivalent here instead of a vector of structs. Let me know if you've tried this and it doesn't work for some reason!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are correct. I struggled with trying to use BTreeMap in a record this made me think maps are not just one of the Uniffi compatible type. HashMap is definitely closer and it works. I just checked. I did

pub bip32_derivation: HashMap<String, KeySource>

instead of

pub bip32_derivation: Vec<Bip32Derivation>

where keysource is

#[derive(Clone, Debug, uniffi::Record)]
pub struct KeySource {
    pub fingerprint: String,
    pub path: String,
}

Copy link
Collaborator Author

@ItoroD ItoroD Sep 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about this again, the only thing about collecting these into Hashmap is that we loose the ordering that the BTreeMap gave from the actual rust-bitcoin types. Should we care about the ordering ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I wondered that too. But looking at the fields, I do think the keys identify everything you need, and so the ordering doesn't matter. If anything devs would need an extra line of lookup to find the associated data instead of relying on the ordering, but even then I don't know if that's even how people use the BTreeMap for these fields. My hunch would be to use the HashMap.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, I will update. Thanks.


#[derive(Clone, Debug, uniffi::Record)]
pub struct TapScriptSig {
/// x-only pubkey as hex
pub xonly_pubkey: String,
/// tap leaf hash as hex
pub leaf_hash: String,
/// signature bytes
pub signature: Vec<u8>,
}

#[derive(Clone, Debug, uniffi::Record)]
pub struct TapScriptEntry {
/// control block bytes
pub control_block: String,
/// script (reuse existing `Script` FFI type)
pub script: Arc<Script>,
/// leaf version
pub leaf_version: u8,
}

#[derive(Clone, Debug, uniffi::Record)]
pub struct TapKeyOrigin {
/// x-only pubkey as hex
pub xonly_pubkey: String,
/// leaf hashes as hex strings
pub leaf_hashes: Vec<String>,
/// master key fingerprint
pub fingerprint: String,
/// derivation path segments
pub path: String,
}

#[derive(Clone, Debug, uniffi::Record)]
pub struct KeyValuePair {
/// raw key bytes (for proprietary / unknown keys)
pub key: Vec<u8>,
/// raw value bytes
pub value: Vec<u8>,
/// The type of this PSBT key.
pub type_value: u8,
}

#[derive(Clone, Debug, uniffi::Record)]
pub struct ProprietaryKeyValuePair {
/// Proprietary type prefix used for grouping together keys under some application and avoid namespace collision
pub prefix: Vec<u8>,
/// Custom proprietary subtype
pub subtype: u8,
/// Additional key bytes (like serialized public key data etc)
pub key: Vec<u8>,
/// raw value bytes
pub value: Vec<u8>,
}

#[derive(Clone, Debug, uniffi::Record)]
pub struct KeySource {
pub fingerprint: String,
pub path: String,
}

/// A key-value map for an input of the corresponding index in the unsigned transaction.
#[derive(Clone, Debug, uniffi::Record)]
pub struct Input {
/// The non-witness transaction this input spends from. Should only be
/// `Option::Some` for inputs which spend non-segwit outputs or
/// if it is unknown whether an input spends a segwit output.
pub non_witness_utxo: Option<Arc<Transaction>>,
/// The transaction output this input spends from. Should only be
/// `Option::Some` for inputs which spend segwit outputs,
/// including P2SH embedded ones.
pub witness_utxo: Option<TxOut>,
/// A map from public keys to their corresponding signature as would be
/// pushed to the stack from a scriptSig or witness for a non-taproot inputs.
pub partial_sigs: HashMap<String, Vec<u8>>,
/// The sighash type to be used for this input. Signatures for this input
/// must use the sighash type.
pub sighash_type: Option<String>,
/// The redeem script for this input.
pub redeem_script: Option<Arc<Script>>,
/// The witness script for this input.
pub witness_script: Option<Arc<Script>>,
/// A map from public keys needed to sign this input to their corresponding
/// master key fingerprints and derivation paths.
pub bip32_derivation: HashMap<String, KeySource>,

/// The finalized, fully-constructed scriptSig with signatures and any other
/// scripts necessary for this input to pass validation.
pub final_script_sig: Option<Arc<Script>>,

/// The finalized, fully-constructed scriptWitness with signatures and any
/// other scripts necessary for this input to pass validation.
pub final_script_witness: Option<Vec<Vec<u8>>>,
/// RIPEMD160 hash to preimage map.
pub ripemd160_preimages: HashMap<String, Vec<u8>>,
/// SHA256 hash to preimage map.
pub sha256_preimages: HashMap<String, Vec<u8>>,
/// HASH160 hash to preimage map.
pub hash160_preimages: HashMap<String, Vec<u8>>,
/// HASH256 hash to preimage map.
pub hash256_preimages: HashMap<String, Vec<u8>>,
/// Serialized taproot signature with sighash type for key spend.
pub tap_key_sig: Option<Vec<u8>>,
/// Map of `<xonlypubkey>|<leafhash>` with signature.
pub tap_script_sigs: Vec<TapScriptSig>,
/// Map of Control blocks to Script version pair.
pub tap_scripts: Vec<TapScriptEntry>,
/// Map of tap root x only keys to origin info and leaf hashes contained in it.
pub tap_key_origins: Vec<TapKeyOrigin>,
/// Taproot Internal key.
pub tap_internal_key: Option<String>,
/// Taproot Merkle root.
pub tap_merkle_root: Option<String>,
/// Proprietary key-value pairs for this input.
pub proprietary: Vec<ProprietaryKeyValuePair>,
/// Unknown key-value pairs for this input.
pub unknown: Vec<KeyValuePair>,
}

impl From<&BdkInput> for Input {
fn from(input: &BdkInput) -> Self {
Input {
non_witness_utxo: input
.non_witness_utxo
.as_ref()
.map(|tx| Arc::new(Transaction(tx.clone()))),
witness_utxo: input.witness_utxo.as_ref().map(TxOut::from),
partial_sigs: input
.partial_sigs
.iter()
.map(|(k, v)| (k.to_string(), v.to_vec()))
.collect(),
sighash_type: input.sighash_type.as_ref().map(|s| s.to_string()),
redeem_script: input
.redeem_script
.as_ref()
.map(|s| Arc::new(Script(s.clone()))),
witness_script: input
.witness_script
.as_ref()
.map(|s| Arc::new(Script(s.clone()))),
bip32_derivation: input
.bip32_derivation
.iter()
.map(|(pk, (fingerprint, deriv_path))| {
(
pk.to_string(),
KeySource {
fingerprint: fingerprint.to_string(),
path: deriv_path.to_string(),
},
)
})
.collect(),
final_script_sig: input
.final_script_sig
.as_ref()
.map(|s| Arc::new(Script(s.clone()))),
final_script_witness: input.final_script_witness.as_ref().map(|w| w.to_vec()),
ripemd160_preimages: input
.ripemd160_preimages
.iter()
.map(|(k, v)| (k.to_string(), v.clone()))
.collect(),
sha256_preimages: input
.sha256_preimages
.iter()
.map(|(k, v)| (k.to_string(), v.clone()))
.collect(),
hash160_preimages: input
.hash160_preimages
.iter()
.map(|(k, v)| (k.to_string(), v.clone()))
.collect(),
hash256_preimages: input
.hash256_preimages
.iter()
.map(|(k, v)| (k.to_string(), v.clone()))
.collect(),
tap_key_sig: input.tap_key_sig.as_ref().map(|s| s.serialize().to_vec()),
tap_script_sigs: input
.tap_script_sigs
.iter()
.map(|(k, v)| TapScriptSig {
xonly_pubkey: k.0.to_string(),
leaf_hash: k.1.to_string(),
signature: v.to_vec(),
})
.collect::<Vec<TapScriptSig>>(),
tap_scripts: input
.tap_scripts
.iter()
.map(|(k, v)| TapScriptEntry {
control_block: DisplayHex::to_lower_hex_string(&k.serialize()),
script: Arc::new(v.0.clone().into()),
leaf_version: v.1.to_consensus(),
})
.collect::<Vec<TapScriptEntry>>(),
tap_key_origins: input
.tap_key_origins
.iter()
.map(|(k, v)| TapKeyOrigin {
xonly_pubkey: k.to_string(),
leaf_hashes: v.0.iter().map(|h| h.to_string()).collect(),
fingerprint: v.1 .0.to_string(),
path: v.1 .1.to_string(),
})
.collect::<Vec<TapKeyOrigin>>(),
tap_internal_key: input.tap_internal_key.as_ref().map(|k| k.to_string()),
tap_merkle_root: input.tap_merkle_root.as_ref().map(|k| k.to_string()),
proprietary: input
.proprietary
.iter()
.map(|(k, v)| ProprietaryKeyValuePair {
key: k.to_key().key.clone(),
subtype: k.subtype,
prefix: k.prefix.to_vec(),
value: v.to_vec(),
})
.collect(),
unknown: input
.unknown
.iter()
.map(|(k, v)| KeyValuePair {
key: k.key.clone(),
value: v.to_vec(),
type_value: k.type_value,
})
.collect(),
}
}
}

/// A Partially Signed Transaction.
#[derive(uniffi::Object)]
pub struct Psbt(pub(crate) Mutex<BdkPsbt>);
Expand Down Expand Up @@ -625,6 +866,12 @@ impl Psbt {
let utxo = psbt.spend_utxo(input_index as usize).unwrap();
serde_json::to_string(&utxo).unwrap()
}

/// The corresponding key-value map for each input in the unsigned transaction.
pub fn input(&self) -> Vec<Input> {
let psbt = self.0.lock().unwrap();
psbt.inputs.iter().map(|input| input.into()).collect()
}
}

impl From<BdkPsbt> for Psbt {
Expand Down
Loading
Loading