diff --git a/Cargo.lock b/Cargo.lock index 20cf0ea..9895d45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,9 +21,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" [[package]] name = "askama" @@ -44,7 +44,7 @@ checksum = "87bf87e6e8b47264efa9bde63d6225c6276a52e05e91bf37eaa8afd0032d6b71" dependencies = [ "askama_shared", "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] @@ -66,7 +66,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn", + "syn 1.0.109", "toml", ] @@ -78,13 +78,13 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-trait" -version = "0.1.66" +version = "0.1.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc" +checksum = "86ea188f25f0255d8f92797797c97ebf5631fa88178beb1a46fdf5622c9a00e4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.8", ] [[package]] @@ -162,7 +162,7 @@ checksum = "81c1980e50ae23bb6efa9283ae8679d6ea2c6fa6a99fe62533f65f4a25a1a56c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -333,7 +333,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -645,9 +645,9 @@ dependencies = [ [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" @@ -776,7 +776,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -793,9 +793,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.52" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" +checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" dependencies = [ "unicode-ident", ] @@ -924,7 +924,7 @@ checksum = "bdbda6ac5cd1321e724fa9cee216f3a61885889b896f073b8f82322789c5250e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -969,22 +969,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.155" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71f2b4817415c6d4210bfe1c7bfcf4801b2d904cb4d0e1a8fdb651013c9e86b8" +checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.155" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d071a94a3fac4aff69d023a7f411e33f40f3483f8c5190b1953822b6b76d7630" +checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.8", ] [[package]] @@ -1075,6 +1075,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -1092,22 +1103,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.8", ] [[package]] @@ -1130,7 +1141,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1153,9 +1164,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524b68aca1d05e03fdf03fcdce2c6c94b6daf6d16861ddaa7e4f2b6638a9052c" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" @@ -1229,7 +1240,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03de61393a42b4ad4984a3763c0600594ac3e57e5aaa1d05cede933958987c03" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1261,7 +1272,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn", + "syn 1.0.109", "toml", "uniffi_build", "uniffi_meta", @@ -1368,7 +1379,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -1390,7 +1401,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/api-docs/kotlin/src/main/kotlin/org/bitcoindevkit/bdk.kt b/api-docs/kotlin/src/main/kotlin/org/bitcoindevkit/bdk.kt index b96530c..a4ddc15 100644 --- a/api-docs/kotlin/src/main/kotlin/org/bitcoindevkit/bdk.kt +++ b/api-docs/kotlin/src/main/kotlin/org/bitcoindevkit/bdk.kt @@ -487,8 +487,20 @@ class Wallet( /** Return the wallet's balance, across different categories. See [Balance] for the categories. Note that this method only operates on the internal database, which first needs to be [Wallet.sync] manually. */ fun getBalance(): Balance {} - /** Sign a transaction with all the wallet’s signers. */ - fun sign(psbt: PartiallySignedTransaction): Boolean {} + /** + * Sign a transaction with all the wallet's signers, in the order specified by every signer's + * `SignerOrdering`. + * + * The `SignOptions` can be used to tweak the behavior of the software signers, and the way + * the transaction is finalized at the end. Note that it can't be guaranteed that *every* + * signers will follow the options, but the "software signers" (WIF keys and `xprv`) defined + * in this library will. + * + * @param psbt PSBT to be signed + * @param signOptions signing options + * @return true if the PSBT was finalized, or false otherwise + */ + fun sign(psbt: PartiallySignedTransaction, signOptions: SignOptions?): Boolean {} /** Return the list of transactions made and received by the wallet. Note that this method only operate on the internal database, which first needs to be [Wallet.sync] manually. */ fun listTransactions(includeRaw: Boolean): List {} @@ -596,6 +608,29 @@ class TxBuilder() { fun finish(wallet: Wallet): TxBuilderResult {} } +/** + * Options for a software signer. + * + * Adjust the behavior of our software signers and the way a transaction is finalized. + * + * @property trustWitnessUtxo Whether the signer should trust the `witness_utxo`, if the `non_witness_utxo` hasn't been provided. Defaults to `false`. + * @property assumeHeight Whether the wallet should assume a specific height has been reached when trying to finalize a transaction. + * @property allowAllSighashes Whether the signer should use the sighash_type set in the PSBT when signing, no matter what its value is. Defaults to `false`. + * @property removePartialSigs Whether to remove partial signatures from the PSBT inputs while finalizing PSBT. Defaults to `true`. + * @property tryFinalize Whether to try finalizing the PSBT after the inputs are signed. Defaults to `true`. + * @property signWithTapInternalKey Whether we should try to sign a taproot transaction with the taproot internal key or not. This option is ignored if we're signing a non-taproot PSBT. Defaults to `true`. + * @property allowGrinding Whether we should grind ECDSA signature to ensure signing with low r or not. Defaults to `true`. + */ +data class SignOptions ( + var trustWitnessUtxo: Boolean, + var assumeHeight: UInt?, + var allowAllSighashes: Boolean, + var removePartialSigs: Boolean, + var tryFinalize: Boolean, + var signWithTapInternalKey: Boolean, + var allowGrinding: Boolean +) + /** * A object holding a ScriptPubKey and an amount. * diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index a000ecb..c0fa8aa 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -230,7 +230,7 @@ interface Wallet { Balance get_balance(); [Throws=BdkError] - boolean sign([ByRef] PartiallySignedTransaction psbt); + boolean sign([ByRef] PartiallySignedTransaction psbt, SignOptions? sign_options); [Throws=BdkError] sequence list_transactions(boolean include_raw); @@ -251,6 +251,16 @@ interface FeeRate { float as_sat_per_vb(); }; +dictionary SignOptions { + boolean trust_witness_utxo; + u32? assume_height; + boolean allow_all_sighashes; + boolean remove_partial_sigs; + boolean try_finalize; + boolean sign_with_tap_internal_key; + boolean allow_grinding; +}; + interface Transaction { [Throws=BdkError] constructor(sequence transaction_bytes); diff --git a/bdk-ffi/src/lib.rs b/bdk-ffi/src/lib.rs index 1aecf49..92c8230 100644 --- a/bdk-ffi/src/lib.rs +++ b/bdk-ffi/src/lib.rs @@ -13,6 +13,7 @@ use crate::descriptor::Descriptor; use crate::keys::DerivationPath; use crate::keys::{DescriptorPublicKey, DescriptorSecretKey, Mnemonic}; use crate::psbt::PartiallySignedTransaction; +use crate::wallet::SignOptions; use crate::wallet::{BumpFeeTxBuilder, TxBuilder, Wallet}; use bdk::bitcoin::blockdata::script::Script as BdkScript; use bdk::bitcoin::blockdata::transaction::TxIn as BdkTxIn; diff --git a/bdk-ffi/src/wallet.rs b/bdk-ffi/src/wallet.rs index 1042f06..0eafeee 100644 --- a/bdk-ffi/src/wallet.rs +++ b/bdk-ffi/src/wallet.rs @@ -4,8 +4,8 @@ use bdk::database::any::AnyDatabase; use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase}; use bdk::wallet::tx_builder::ChangeSpendPolicy; use bdk::{ - FeeRate, LocalUtxo as BdkLocalUtxo, SignOptions, SyncOptions as BdkSyncOptions, - Wallet as BdkWallet, + FeeRate, LocalUtxo as BdkLocalUtxo, SignOptions as BdkSignOptions, + SyncOptions as BdkSyncOptions, Wallet as BdkWallet, }; use std::collections::HashSet; use std::ops::Deref; @@ -113,10 +113,24 @@ impl Wallet { self.get_wallet().get_balance().map(|b| b.into()) } - /// Sign a transaction with all the wallet’s signers. - pub(crate) fn sign(&self, psbt: &PartiallySignedTransaction) -> Result { + /// Sign a transaction with all the wallet's signers, in the order specified by every signer's + /// [`SignerOrdering`]. This function returns the `Result` type with an encapsulated `bool` that + /// has the value true if the PSBT was finalized, or false otherwise. + /// + /// The [`SignOptions`] can be used to tweak the behavior of the software signers, and the way + /// the transaction is finalized at the end. Note that it can't be guaranteed that *every* + /// signers will follow the options, but the "software signers" (WIF keys and `xprv`) defined + /// in this library will. + pub(crate) fn sign( + &self, + psbt: &PartiallySignedTransaction, + sign_options: Option, + ) -> Result { let mut psbt = psbt.internal.lock().unwrap(); - self.get_wallet().sign(&mut psbt, SignOptions::default()) + self.get_wallet().sign( + &mut psbt, + sign_options.map(SignOptions::into).unwrap_or_default(), + ) } /// Return the list of transactions made and received by the wallet. Note that this method only operate on the internal database, which first needs to be [Wallet.sync] manually. @@ -139,6 +153,81 @@ impl Wallet { } } +/// Options for a software signer +/// +/// Adjust the behavior of our software signers and the way a transaction is finalized +#[derive(Debug, Clone, Default)] +pub struct SignOptions { + /// Whether the signer should trust the `witness_utxo`, if the `non_witness_utxo` hasn't been + /// provided + /// + /// Defaults to `false` to mitigate the "SegWit bug" which should trick the wallet into + /// paying a fee larger than expected. + /// + /// Some wallets, especially if relatively old, might not provide the `non_witness_utxo` for + /// SegWit transactions in the PSBT they generate: in those cases setting this to `true` + /// should correctly produce a signature, at the expense of an increased trust in the creator + /// of the PSBT. + /// + /// For more details see: + pub trust_witness_utxo: bool, + + /// Whether the wallet should assume a specific height has been reached when trying to finalize + /// a transaction + /// + /// The wallet will only "use" a timelock to satisfy the spending policy of an input if the + /// timelock height has already been reached. This option allows overriding the "current height" to let the + /// wallet use timelocks in the future to spend a coin. + pub assume_height: Option, + + /// Whether the signer should use the `sighash_type` set in the PSBT when signing, no matter + /// what its value is + /// + /// Defaults to `false` which will only allow signing using `SIGHASH_ALL`. + pub allow_all_sighashes: bool, + + /// Whether to remove partial signatures from the PSBT inputs while finalizing PSBT. + /// + /// Defaults to `true` which will remove partial signatures during finalization. + pub remove_partial_sigs: bool, + + /// Whether to try finalizing the PSBT after the inputs are signed. + /// + /// Defaults to `true` which will try finalizing PSBT after inputs are signed. + pub try_finalize: bool, + + // Specifies which Taproot script-spend leaves we should sign for. This option is + // ignored if we're signing a non-taproot PSBT. + // + // Defaults to All, i.e., the wallet will sign all the leaves it has a key for. + // TODO pub tap_leaves_options: TapLeavesOptions, + /// Whether we should try to sign a taproot transaction with the taproot internal key + /// or not. This option is ignored if we're signing a non-taproot PSBT. + /// + /// Defaults to `true`, i.e., we always try to sign with the taproot internal key. + pub sign_with_tap_internal_key: bool, + + /// Whether we should grind ECDSA signature to ensure signing with low r + /// or not. + /// Defaults to `true`, i.e., we always grind ECDSA signature to sign with low r. + pub allow_grinding: bool, +} + +impl From for BdkSignOptions { + fn from(sign_options: SignOptions) -> Self { + BdkSignOptions { + trust_witness_utxo: sign_options.trust_witness_utxo, + assume_height: sign_options.assume_height, + allow_all_sighashes: sign_options.allow_all_sighashes, + remove_partial_sigs: sign_options.remove_partial_sigs, + try_finalize: sign_options.try_finalize, + tap_leaves_options: Default::default(), + sign_with_tap_internal_key: sign_options.sign_with_tap_internal_key, + allow_grinding: sign_options.allow_grinding, + } + } +} + /// A transaction builder. /// After creating the TxBuilder, you set options on it until finally calling finish to consume the builder and generate the transaction. /// Each method on the TxBuilder returns an instance of a new TxBuilder with the option set/added.