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
4 changes: 2 additions & 2 deletions bdk-ffi/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion bdk-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ name = "uniffi-bindgen"
path = "uniffi-bindgen.rs"

[dependencies]
bdk_wallet = { version = "=3.0.0", features = ["all-keys", "keys-bip39", "rusqlite"] }
bdk_wallet = { version = "=3.1.0", features = ["all-keys", "keys-bip39", "rusqlite"] }
bdk_esplora = { version = "0.22.2", default-features = false, features = ["std", "blocking", "blocking-https-rustls"] }
bdk_electrum = { version = "0.24.0", default-features = false, features = ["use-rustls-ring"] }
bdk_kyoto = { version = "0.17.0" }
Expand Down
1 change: 1 addition & 0 deletions bdk-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod esplora;
mod keys;
mod kyoto;
mod macros;
mod signer;
mod store;
mod tx_builder;
mod types;
Expand Down
58 changes: 58 additions & 0 deletions bdk-ffi/src/signer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use crate::descriptor::Descriptor;

use bdk_wallet::bitcoin::key::Secp256k1;
use bdk_wallet::signer::SignersContainer as BdkSignersContainer;

use std::sync::Arc;

/// Container for multiple signers.
#[derive(Debug, uniffi::Object)]
pub struct SignersContainer {
pub(crate) inner: BdkSignersContainer,
}

#[uniffi::export]
impl SignersContainer {
/// Build a new signer container from a descriptor's key map.
///
/// Also looks at the same descriptor to determine the `SignerContext` to attach to the signers.
#[uniffi::constructor]
pub fn from_descriptor(descriptor: Arc<Descriptor>) -> Self {
Self::from_descriptor_with_context(Arc::clone(&descriptor), descriptor)
}

/// Build a new signer container from a signer descriptor's key map.
///
/// Also looks at the corresponding descriptor to determine the `SignerContext` to attach to
/// the signers.
#[uniffi::constructor]
pub fn from_descriptor_with_context(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd like to make sure I understand well the use case(s) for this constructor. Can we add a test showcasing this?

signer_descriptor: Arc<Descriptor>,
context_descriptor: Arc<Descriptor>,
) -> Self {
let secp = Secp256k1::new();
let inner = BdkSignersContainer::build(
signer_descriptor.key_map.clone(),
&context_descriptor.extended_descriptor,
&secp,
);

Self { inner }
}

/// Returns the number of signers in the container.
pub fn len(&self) -> u64 {
self.inner.signers().len() as u64

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What does this length represent? A reminder to myself to look into it.

}

/// Returns true when the container has no signers.
pub fn is_empty(&self) -> bool {
self.inner.signers().is_empty()
}
}

impl From<BdkSignersContainer> for SignersContainer {
fn from(inner: BdkSignersContainer) -> Self {
Self { inner }
}
}
96 changes: 94 additions & 2 deletions bdk-ffi/src/tests/tx_builder.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use crate::bitcoin::{Amount, Input, Network, NetworkKind, OutPoint, Script, TxOut};
use crate::bitcoin::{Amount, Input, Network, NetworkKind, OutPoint, Script, Transaction, TxOut};
use crate::descriptor::Descriptor;
use crate::error::SighashParseError;
use crate::error::{AddForeignUtxoError, SighashParseError};
use crate::esplora::EsploraClient;
use crate::store::Persister;
use crate::tx_builder::TxBuilder;
use crate::types::FullScanScriptInspector;
use crate::wallet::Wallet;

use bdk_wallet::bitcoin::hashes::hex::FromHex;
use bdk_wallet::bitcoin::{
absolute, consensus::serialize, transaction, Amount as BdkAmount, ScriptBuf as BdkScriptBuf,
Transaction as BdkTransaction, TxIn as BdkTxIn, TxOut as BdkTxOut,
};

use std::collections::HashMap;
use std::sync::Arc;
Expand Down Expand Up @@ -300,6 +304,94 @@ fn test_add_foreign_utxo_missing_witness_data() {
);
}

#[test]
fn test_add_foreign_utxo_validates_non_witness_utxo_when_witness_utxo_is_present() {
let outpoint = OutPoint {
txid: Arc::new(
crate::bitcoin::Txid::from_string(
"5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456".to_string(),
)
.unwrap(),
),
vout: 0,
};

let witness_utxo = TxOut {
value: Arc::new(Amount::from_sat(50_000)),
script_pubkey: Arc::new(Script::new(
Vec::from_hex("0014d85c2b71d0060b09c9886aeb815e50991dda124d").unwrap(),
)),
};

let non_witness_tx = BdkTransaction {
version: transaction::Version::TWO,
lock_time: absolute::LockTime::ZERO,
input: vec![BdkTxIn::default()],
output: vec![BdkTxOut {
value: BdkAmount::from_sat(50_000),
script_pubkey: BdkScriptBuf::new(),
}],
};
let non_witness_utxo = Arc::new(Transaction::new(serialize(&non_witness_tx)).unwrap());

let psbt_input = Input {
non_witness_utxo: Some(non_witness_utxo.clone()),
witness_utxo: Some(witness_utxo.clone()),
partial_sigs: HashMap::new(),
sighash_type: None,
redeem_script: None,
witness_script: None,
bip32_derivation: HashMap::new(),
final_script_sig: None,
final_script_witness: None,
ripemd160_preimages: HashMap::new(),
sha256_preimages: HashMap::new(),
hash160_preimages: HashMap::new(),
hash256_preimages: HashMap::new(),
tap_key_sig: None,
tap_script_sigs: HashMap::new(),
tap_scripts: HashMap::new(),
tap_key_origins: HashMap::new(),
tap_internal_key: None,
tap_merkle_root: None,
proprietary: HashMap::new(),
unknown: HashMap::new(),
};

let result = TxBuilder::new().add_foreign_utxo(outpoint.clone(), psbt_input, 68);

assert!(matches!(result, Err(AddForeignUtxoError::InvalidTxid)));

let psbt_input_with_sequence = Input {
non_witness_utxo: Some(non_witness_utxo),
witness_utxo: Some(witness_utxo),
partial_sigs: HashMap::new(),
sighash_type: None,
redeem_script: None,
witness_script: None,
bip32_derivation: HashMap::new(),
final_script_sig: None,
final_script_witness: None,
ripemd160_preimages: HashMap::new(),
sha256_preimages: HashMap::new(),
hash160_preimages: HashMap::new(),
hash256_preimages: HashMap::new(),
tap_key_sig: None,
tap_script_sigs: HashMap::new(),
tap_scripts: HashMap::new(),
tap_key_origins: HashMap::new(),
tap_internal_key: None,
tap_merkle_root: None,
proprietary: HashMap::new(),
unknown: HashMap::new(),
};

let result =
TxBuilder::new().add_foreign_utxo_with_sequence(outpoint, psbt_input_with_sequence, 68, 0);

assert!(matches!(result, Err(AddForeignUtxoError::InvalidTxid)));
}

#[test]
#[ignore = "requires live MutinyNet Esplora access"]
fn test_add_foreign_utxo_with_witness_utxo_succeeds() {
Expand Down
Loading
Loading