Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose the add_foreign_utxo method on TxBuilder type #358

Closed
135 changes: 135 additions & 0 deletions api-docs/kotlin/src/main/kotlin/org/bitcoindevkit/bdk.kt
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,9 @@ class Wallet(

/** Return the list of unspent outputs of this wallet. Note that this method only operates on the internal database, which first needs to be [Wallet.sync] manually. */
fun listUnspent(): List<LocalUtxo> {}

/** Get the corresponding PSBT Input for a LocalUtxo. */
fun getPsbtInput(utxo: LocalUtxo, sighashType: PsbtSighashType?, onlyWitnessUtxo: Boolean): Input {}
}

/**
Expand Down Expand Up @@ -557,6 +560,43 @@ class TxBuilder() {
/** Add an outpoint to the internal list of UTXOs that must be spent. These have priority over the "unspendable" utxos, meaning that if a utxo is present both in the "utxos" and the "unspendable" list, it will be spent. */
fun addUtxo(outpoint: OutPoint): TxBuilder {}

/**
* Add a foreign UTXO i.e. a UTXO not owned by this wallet.
* At a minimum to add a foreign UTXO we need:
* outpoint: To add it to the raw transaction.
* psbt_input: To know the value.
* satisfaction_weight: To know how much weight/vbytes the input will add to the transaction for fee calculation.
*
* There are several security concerns about adding foreign UTXOs that application developers should consider.
* First, how do you know the value of the input is correct? If a non_witness_utxo is provided in the
* psbt_input then this method implicitly verifies the value by checking it against the transaction.
* If only a witness_utxo is provided then this method does not verify the value but just takes it as a
* given – it is up to you to check that whoever sent you the input_psbt was not lying!
*
* Secondly, you must somehow provide satisfaction_weight of the input. Depending on your application
* it may be important that this be known precisely. If not, a malicious counterparty may fool you into putting in
* a value that is too low, giving the transaction a lower than expected feerate. They could also fool you
* into putting a value that is too high causing you to pay a fee that is too high. The party who is broadcasting
* the transaction can of course check the real input weight matches the expected weight prior to broadcasting.
*
* To guarantee the satisfaction_weight is correct, you can require the party providing the psbt_input provide
* a miniscript descriptor for the input so you can check it against the script_pubkey and then ask it for the
* max_satisfaction_weight.
*
* Errors
* This method returns errors in the following circumstances:
* The psbt_input does not contain a witness_utxo or non_witness_utxo.
* The data in non_witness_utxo does not match what is in outpoint.
*
* Note unless you set only_witness_utxo any non-taproot psbt_input you pass to this method must
* have non_witness_utxo set otherwise you will get an error when finish is called.
*
* @param outpoint The outpoint of the UTXO to add.
* @param input The PSBT input that contains the value of the UTXO.
* @param satisfactionWeight how much weight/vbytes the input will add to the transaction for fee calculation.
*/
fun addForeignUtxo(outpoint: OutPoint, input: Input, satisfactionWeight: ULong): TxBuilder {}

/**
* Add the list of outpoints to the internal list of UTXOs that must be spent. If an error
* occurs while adding any of the UTXOs then none of them are added and the error is returned.
Expand Down Expand Up @@ -715,6 +755,72 @@ class DescriptorSecretKey(network: Network, mnemonic: Mnemonic, password: String
fun asString(): String {}
}

/**
* A Signature hash type for the corresponding input. As of taproot upgrade, the signature hash
* type can be either [`EcdsaSighashType`] or [`SchnorrSighashType`] but it is not possible to know
* directly which signature hash type the user is dealing with. Therefore, the user is responsible
* for converting to/from [`PsbtSighashType`] from/to the desired signature hash type they need.
*
*/
class PsbtSighashType() {

companion object {
fun `fromEcdsa`(`ecdsaHashTy`: EcdsaSighashType): PsbtSighashType
fun `fromSchnorr`(`schnorrHashTy`: SchnorrSighashType): PsbtSighashType
}
}

/**
* Hashtype of an input's signature, encoded in the last byte of the signature.
* Fixed values so they can be cast as integer types for encoding (see also
* `SchnorrSighashType`).
*/
enum class EcdsaSighashType {
/** 0x1: Sign all outputs. */
ALL,
/** 0x2: Sign no outputs --- anyone can choose the destination. */
NONE,
/**
* 0x3: Sign the output whose index matches this input's index. If none exists,
* sign the hash `0000000000000000000000000000000000000000000000000000000000000001`.
* (This rule is probably an unintentional C++ism, but it's consensus so we have
* to follow it.)
*/
SINGLE,
/** 0x81: Sign all outputs but only this input. */
ALL_PLUS_ANYONE_CAN_PAY,
/** 0x82: Sign no outputs and only this input. */
NONE_PLUS_ANYONE_CAN_PAY,
/** 0x83: Sign one output and only this input (see `Single` for what "one output" means). */
SINGLE_PLUS_ANYONE_CAN_PAY;
}

/**
* Hashtype of an input's signature, encoded in the last byte of the signature.
* Fixed values so they can be cast as integer types for encoding.
*/
enum class SchnorrSighashType {
/** 0x0: Used when not explicitly specified, defaults to [`SchnorrSighashType::All`] */
DEFAULT,
/** 0x1: Sign all outputs. */
ALL,
/** 0x2: Sign no outputs --- anyone can choose the destination. */
NONE,
/**
* 0x3: Sign the output whose index matches this input's index. If none exists,
* sign the hash `0000000000000000000000000000000000000000000000000000000000000001`.
* (This rule is probably an unintentional C++ism, but it's consensus so we have
* to follow it.)
*/
SINGLE,
/** 0x81: Sign all outputs but only this input. */
ALL_PLUS_ANYONE_CAN_PAY,
/** 0x82: Sign no outputs and only this input. */
NONE_PLUS_ANYONE_CAN_PAY,
/** 0x83: Sign one output and only this input (see `Single` for what "one output" means). */
SINGLE_PLUS_ANYONE_CAN_PAY;
}

/**
* An extended public key.
*
Expand Down Expand Up @@ -785,6 +891,21 @@ class Descriptor(descriptor: String, network: Network) {
*/
fun newBip84Public(publicKey: DescriptorPublicKey, fingerprint: String, keychain: KeychainKind, network: Network) {}

/**
* Computes an upper bound on the weight of a satisfying witness to the
* transaction.
*
* Assumes all ec-signatures are 73 bytes, including push opcode and
* sighash suffix. Includes the weight of the VarInts encoding the
* scriptSig and witness stack length.
*
* # Errors
* When the descriptor is impossible to satisfy (ex: sh(OP_FALSE)).
*
* @return max satisfaction weight
*/
fun maxSatisfactionWeight(): UInt {}

/** Return the public version of the output descriptor. */
fun asString(): String {}

Expand Down Expand Up @@ -895,6 +1016,20 @@ enum class WitnessVersion {
V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16
}

/**
* A key-value map for an input of the corresponding index in the unsigned transaction.
*
* @constructor Create a new PSBT Input from a JSON String.
*/
class Input(inputJson: String) {

/**
* Serialize the PSBT Input data structure as a JSON String.
*
*/
fun jsonSerialize(): String;
}

/**
* Mnemonic phrases are a human-readable version of the private keys. Supported number of words are 12, 15, 18, 21 and 24.
*
Expand Down
44 changes: 43 additions & 1 deletion bdk-ffi/src/bdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ interface Wallet {
[Throws=BdkError]
sequence<LocalUtxo> list_unspent();

[Throws=BdkError]
Input get_psbt_input(LocalUtxo utxo, PsbtSighashType? sighash_type, boolean only_witness_utxo);

[Throws=BdkError]
sequence<TransactionDetails> list_transactions(boolean include_raw);

Expand Down Expand Up @@ -314,6 +317,40 @@ interface PartiallySignedTransaction {
string json_serialize();
};

interface Input {
[Name=from_json,Throws=BdkError]
constructor(string input_json);

string json_serialize();
};

interface PsbtSighashType {
[Name=from_ecdsa]
constructor(EcdsaSighashType ecdsa_hash_ty);

[Name=from_schnorr]
constructor(SchnorrSighashType schnorr_hash_ty);
};

enum EcdsaSighashType {
"All",
"None",
"Single",
"AllPlusAnyoneCanPay",
"NonePlusAnyoneCanPay",
"SinglePlusAnyoneCanPay",
};

enum SchnorrSighashType {
"Default",
"All",
"None",
"Single",
"AllPlusAnyoneCanPay",
"NonePlusAnyoneCanPay",
"SinglePlusAnyoneCanPay",
};

dictionary TxBuilderResult {
PartiallySignedTransaction psbt;
TransactionDetails transaction_details;
Expand All @@ -326,9 +363,11 @@ interface TxBuilder {

TxBuilder add_unspendable(OutPoint unspendable);

TxBuilder add_utxos(sequence<OutPoint> outpoints);

TxBuilder add_utxo(OutPoint outpoint);

TxBuilder add_utxos(sequence<OutPoint> outpoints);
TxBuilder add_foreign_utxo(OutPoint outpoint, Input psbt_input, u64 satisfaction_weight);

TxBuilder do_not_spend_change();

Expand Down Expand Up @@ -442,6 +481,9 @@ interface Descriptor {
[Name=new_bip84_public]
constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);

[Throws=BdkError]
u64 max_satisfaction_weight();

string as_string();

string as_string_private();
Expand Down
17 changes: 17 additions & 0 deletions bdk-ffi/src/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use bdk::template::{
Bip44, Bip44Public, Bip49, Bip49Public, Bip84, Bip84Public, DescriptorTemplate,
};
use bdk::KeychainKind;
use std::convert::TryFrom;
use std::ops::Deref;
use std::str::FromStr;
use std::sync::Arc;
Expand Down Expand Up @@ -183,6 +184,22 @@ impl Descriptor {
}
}

/// Computes an upper bound on the weight of a satisfying witness to the
/// transaction.
///
/// Assumes all ec-signatures are 73 bytes, including push opcode and
/// sighash suffix. Includes the weight of the VarInts encoding the
/// scriptSig and witness stack length.
///
/// # Errors
/// When the descriptor is impossible to satisfy (ex: sh(OP_FALSE)).
pub(crate) fn max_satisfaction_weight(&self) -> Result<u64, BdkError> {
self.extended_descriptor
.max_satisfaction_weight()
.map(|w| u64::try_from(w).unwrap())
.map_err(BdkError::Miniscript)
}

pub(crate) fn as_string_private(&self) -> String {
let descriptor = &self.extended_descriptor;
let key_map = &self.key_map;
Expand Down
32 changes: 32 additions & 0 deletions bdk-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use crate::database::DatabaseConfig;
use crate::descriptor::Descriptor;
use crate::keys::DerivationPath;
use crate::keys::{DescriptorPublicKey, DescriptorSecretKey, Mnemonic};
use crate::psbt::Input;
use crate::psbt::PartiallySignedTransaction;
use crate::psbt::PsbtSighashType;
use crate::wallet::SignOptions;
use crate::wallet::{BumpFeeTxBuilder, TxBuilder, Wallet};
use bdk::bitcoin::blockdata::script::Script as BdkScript;
Expand All @@ -24,6 +26,7 @@ use bdk::bitcoin::util::address::{Payload as BdkPayload, WitnessVersion};
use bdk::bitcoin::{
Address as BdkAddress, Network, OutPoint as BdkOutPoint, Transaction as BdkTransaction, Txid,
};
use bdk::bitcoin::{EcdsaSighashType, SchnorrSighashType};
use bdk::blockchain::Progress as BdkProgress;
use bdk::database::any::{SledDbConfiguration, SqliteDbConfiguration};
use bdk::keys::bip39::WordCount;
Expand Down Expand Up @@ -161,6 +164,15 @@ impl From<&OutPoint> for BdkOutPoint {
}
}

impl From<OutPoint> for BdkOutPoint {
fn from(outpoint: OutPoint) -> Self {
BdkOutPoint {
txid: Txid::from_str(&outpoint.txid).unwrap(),
vout: outpoint.vout,
}
}
}

pub struct Balance {
// All coinbase outputs not yet matured
pub immature: u64,
Expand Down Expand Up @@ -209,6 +221,15 @@ impl From<&BdkTxOut> for TxOut {
}
}

impl From<TxOut> for BdkTxOut {
fn from(tx_out: TxOut) -> Self {
BdkTxOut {
value: tx_out.value,
script_pubkey: tx_out.script_pubkey.script.clone(),
}
}
}

pub struct LocalUtxo {
outpoint: OutPoint,
txout: TxOut,
Expand All @@ -235,6 +256,17 @@ impl From<BdkLocalUtxo> for LocalUtxo {
}
}

impl From<LocalUtxo> for BdkLocalUtxo {
fn from(local_utxo: LocalUtxo) -> Self {
BdkLocalUtxo {
outpoint: local_utxo.outpoint.into(),
txout: local_utxo.txout.into(),
keychain: local_utxo.keychain,
is_spent: local_utxo.is_spent,
}
}
}

/// Trait that logs at level INFO every update received (if any).
pub trait Progress: Send + Sync + 'static {
/// Send a new progress update. The progress value should be in the range 0.0 - 100.0, and the message value is an
Expand Down
Loading