Merge branch 'master' into fix_wallet_checksum
This commit is contained in:
@@ -1,158 +0,0 @@
|
||||
// Bitcoin Dev Kit
|
||||
// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
|
||||
//
|
||||
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
|
||||
//
|
||||
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
|
||||
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
|
||||
// You may not use this file except in accordance with one or both of these
|
||||
// licenses.
|
||||
|
||||
//! Address validation callbacks
|
||||
//!
|
||||
//! The typical usage of those callbacks is for displaying the newly-generated address on a
|
||||
//! hardware wallet, so that the user can cross-check its correctness.
|
||||
//!
|
||||
//! More generally speaking though, these callbacks can also be used to "do something" every time
|
||||
//! an address is generated, without necessarily checking or validating it.
|
||||
//!
|
||||
//! An address validator can be attached to a [`Wallet`](super::Wallet) by using the
|
||||
//! [`Wallet::add_address_validator`](super::Wallet::add_address_validator) method, and
|
||||
//! whenever a new address is generated (either explicitly by the user with
|
||||
//! [`Wallet::get_address`](super::Wallet::get_address) or internally to create a change
|
||||
//! address) all the attached validators will be polled, in sequence. All of them must complete
|
||||
//! successfully to continue.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```
|
||||
//! # use std::sync::Arc;
|
||||
//! # use bitcoin::*;
|
||||
//! # use bdk::address_validator::*;
|
||||
//! # use bdk::database::*;
|
||||
//! # use bdk::*;
|
||||
//! # use bdk::wallet::AddressIndex::New;
|
||||
//! #[derive(Debug)]
|
||||
//! struct PrintAddressAndContinue;
|
||||
//!
|
||||
//! impl AddressValidator for PrintAddressAndContinue {
|
||||
//! fn validate(
|
||||
//! &self,
|
||||
//! keychain: KeychainKind,
|
||||
//! hd_keypaths: &HdKeyPaths,
|
||||
//! script: &Script
|
||||
//! ) -> Result<(), AddressValidatorError> {
|
||||
//! let address = Address::from_script(script, Network::Testnet)
|
||||
//! .as_ref()
|
||||
//! .map(Address::to_string)
|
||||
//! .unwrap_or(script.to_string());
|
||||
//! println!("New address of type {:?}: {}", keychain, address);
|
||||
//! println!("HD keypaths: {:#?}", hd_keypaths);
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
||||
//! let mut wallet = Wallet::new(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||
//! wallet.add_address_validator(Arc::new(PrintAddressAndContinue));
|
||||
//!
|
||||
//! let address = wallet.get_address(New)?;
|
||||
//! println!("Address: {}", address);
|
||||
//! # Ok::<(), bdk::Error>(())
|
||||
//! ```
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use bitcoin::Script;
|
||||
|
||||
use crate::descriptor::HdKeyPaths;
|
||||
use crate::types::KeychainKind;
|
||||
|
||||
/// Errors that can be returned to fail the validation of an address
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AddressValidatorError {
|
||||
/// User rejected the address
|
||||
UserRejected,
|
||||
/// Network connection error
|
||||
ConnectionError,
|
||||
/// Network request timeout error
|
||||
TimeoutError,
|
||||
/// Invalid script
|
||||
InvalidScript,
|
||||
/// A custom error message
|
||||
Message(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for AddressValidatorError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for AddressValidatorError {}
|
||||
|
||||
/// Trait to build address validators
|
||||
///
|
||||
/// All the address validators attached to a wallet with [`Wallet::add_address_validator`](super::Wallet::add_address_validator) will be polled
|
||||
/// every time an address (external or internal) is generated by the wallet. Errors returned in the
|
||||
/// validator will be propagated up to the original caller that triggered the address generation.
|
||||
///
|
||||
/// For a usage example see [this module](crate::address_validator)'s documentation.
|
||||
#[deprecated = "AddressValidator was rarely used. Address validation can occur outside of BDK"]
|
||||
pub trait AddressValidator: Send + Sync + fmt::Debug {
|
||||
/// Validate or inspect an address
|
||||
fn validate(
|
||||
&self,
|
||||
keychain: KeychainKind,
|
||||
hd_keypaths: &HdKeyPaths,
|
||||
script: &Script,
|
||||
) -> Result<(), AddressValidatorError>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
use crate::wallet::AddressIndex::New;
|
||||
use crate::wallet::{get_funded_wallet, test::get_test_wpkh};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestValidator;
|
||||
#[allow(deprecated)]
|
||||
impl AddressValidator for TestValidator {
|
||||
fn validate(
|
||||
&self,
|
||||
_keychain: KeychainKind,
|
||||
_hd_keypaths: &HdKeyPaths,
|
||||
_script: &bitcoin::Script,
|
||||
) -> Result<(), AddressValidatorError> {
|
||||
Err(AddressValidatorError::InvalidScript)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "InvalidScript")]
|
||||
fn test_address_validator_external() {
|
||||
let (mut wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||
#[allow(deprecated)]
|
||||
wallet.add_address_validator(Arc::new(TestValidator));
|
||||
|
||||
wallet.get_address(New).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "InvalidScript")]
|
||||
fn test_address_validator_internal() {
|
||||
let (mut wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
|
||||
#[allow(deprecated)]
|
||||
wallet.add_address_validator(Arc::new(TestValidator));
|
||||
|
||||
let addr = crate::testutils!(@external descriptors, 10);
|
||||
let mut builder = wallet.build_tx();
|
||||
builder.add_recipient(addr.script_pubkey(), 25_000);
|
||||
builder.finish().unwrap();
|
||||
}
|
||||
}
|
||||
@@ -310,7 +310,7 @@ pub fn decide_change(remaining_amount: u64, fee_rate: FeeRate, drain_script: &Sc
|
||||
let drain_val = remaining_amount.saturating_sub(change_fee);
|
||||
|
||||
if drain_val.is_dust(drain_script) {
|
||||
let dust_threshold = drain_script.dust_value().as_sat();
|
||||
let dust_threshold = drain_script.dust_value().to_sat();
|
||||
Excess::NoChange {
|
||||
dust_threshold,
|
||||
change_fee,
|
||||
@@ -835,7 +835,7 @@ mod test {
|
||||
)
|
||||
.unwrap(),
|
||||
txout: TxOut {
|
||||
value: rng.gen_range(0, 200000000),
|
||||
value: rng.gen_range(0..200000000),
|
||||
script_pubkey: Script::new(),
|
||||
},
|
||||
keychain: KeychainKind::External,
|
||||
@@ -866,7 +866,7 @@ mod test {
|
||||
}
|
||||
|
||||
fn sum_random_utxos(mut rng: &mut StdRng, utxos: &mut Vec<WeightedUtxo>) -> u64 {
|
||||
let utxos_picked_len = rng.gen_range(2, utxos.len() / 2);
|
||||
let utxos_picked_len = rng.gen_range(2..utxos.len() / 2);
|
||||
utxos.shuffle(&mut rng);
|
||||
utxos[..utxos_picked_len]
|
||||
.iter()
|
||||
@@ -1226,6 +1226,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_bnb_coin_selection_required_not_enough() {
|
||||
let utxos = get_test_utxos();
|
||||
let database = MemoryDatabase::default();
|
||||
|
||||
@@ -11,7 +11,40 @@
|
||||
|
||||
//! HWI Signer
|
||||
//!
|
||||
//! This module contains a simple implementation of a Custom signer for rust-hwi
|
||||
//! This module contains HWISigner, an implementation of a [TransactionSigner] to be
|
||||
//! used with hardware wallets.
|
||||
//! ```no_run
|
||||
//! # use bdk::bitcoin::Network;
|
||||
//! # use bdk::database::MemoryDatabase;
|
||||
//! # use bdk::signer::SignerOrdering;
|
||||
//! # use bdk::wallet::hardwaresigner::HWISigner;
|
||||
//! # use bdk::wallet::AddressIndex::New;
|
||||
//! # use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet};
|
||||
//! # use hwi::{types::HWIChain, HWIClient};
|
||||
//! # use std::sync::Arc;
|
||||
//! #
|
||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! let devices = HWIClient::enumerate()?;
|
||||
//! let first_device = devices.first().expect("No devices found!");
|
||||
//! let custom_signer = HWISigner::from_device(first_device, HWIChain::Test)?;
|
||||
//!
|
||||
//! # let mut wallet = Wallet::new(
|
||||
//! # "",
|
||||
//! # None,
|
||||
//! # Network::Testnet,
|
||||
//! # MemoryDatabase::default(),
|
||||
//! # )?;
|
||||
//! #
|
||||
//! // Adding the hardware signer to the BDK wallet
|
||||
//! wallet.add_signer(
|
||||
//! KeychainKind::External,
|
||||
//! SignerOrdering(200),
|
||||
//! Arc::new(custom_signer),
|
||||
//! );
|
||||
//!
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
use bitcoin::psbt::PartiallySignedTransaction;
|
||||
use bitcoin::secp256k1::{All, Secp256k1};
|
||||
|
||||
@@ -24,20 +24,17 @@ use std::sync::Arc;
|
||||
use bitcoin::secp256k1::Secp256k1;
|
||||
|
||||
use bitcoin::consensus::encode::serialize;
|
||||
use bitcoin::util::{psbt, taproot};
|
||||
use bitcoin::util::psbt;
|
||||
use bitcoin::{
|
||||
Address, EcdsaSighashType, Network, OutPoint, SchnorrSighashType, Script, Transaction, TxOut,
|
||||
Txid, Witness,
|
||||
Address, EcdsaSighashType, LockTime, Network, OutPoint, SchnorrSighashType, Script, Sequence,
|
||||
Transaction, TxOut, Txid, Witness,
|
||||
};
|
||||
|
||||
use miniscript::descriptor::DescriptorTrait;
|
||||
use miniscript::psbt::PsbtInputSatisfier;
|
||||
use miniscript::ToPublicKey;
|
||||
use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use log::{debug, error, info, trace};
|
||||
|
||||
pub mod address_validator;
|
||||
pub mod coin_selection;
|
||||
pub mod export;
|
||||
pub mod signer;
|
||||
@@ -54,25 +51,21 @@ pub mod hardwaresigner;
|
||||
|
||||
pub use utils::IsDust;
|
||||
|
||||
#[allow(deprecated)]
|
||||
use address_validator::AddressValidator;
|
||||
use coin_selection::DefaultCoinSelectionAlgorithm;
|
||||
use signer::{SignOptions, SignerOrdering, SignersContainer, TransactionSigner};
|
||||
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
|
||||
use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx};
|
||||
use utils::{check_nsequence_rbf, After, Older, SecpCtx};
|
||||
|
||||
use crate::blockchain::{GetHeight, NoopProgress, Progress, WalletSync};
|
||||
use crate::database::memory::MemoryDatabase;
|
||||
use crate::database::{AnyDatabase, BatchDatabase, BatchOperations, DatabaseUtils, SyncTime};
|
||||
use crate::descriptor::checksum::calc_checksum_bytes_internal;
|
||||
use crate::descriptor::derived::AsDerived;
|
||||
use crate::descriptor::policy::BuildSatisfaction;
|
||||
use crate::descriptor::{
|
||||
calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DerivedDescriptorMeta,
|
||||
DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor,
|
||||
Policy, XKeyUtils,
|
||||
calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta,
|
||||
ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
|
||||
};
|
||||
use crate::error::Error;
|
||||
use crate::error::{Error, MiniscriptPsbtError};
|
||||
use crate::psbt::PsbtUtils;
|
||||
use crate::signer::SignerError;
|
||||
use crate::testutils;
|
||||
@@ -101,9 +94,6 @@ pub struct Wallet<D> {
|
||||
signers: Arc<SignersContainer>,
|
||||
change_signers: Arc<SignersContainer>,
|
||||
|
||||
#[allow(deprecated)]
|
||||
address_validators: Vec<Arc<dyn AddressValidator>>,
|
||||
|
||||
network: Network,
|
||||
|
||||
database: RefCell<D>,
|
||||
@@ -144,7 +134,7 @@ pub enum AddressIndex {
|
||||
|
||||
/// A derived address and the index it was found at
|
||||
/// For convenience this automatically derefs to `Address`
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct AddressInfo {
|
||||
/// Child index of this address
|
||||
pub index: u32,
|
||||
@@ -236,7 +226,6 @@ where
|
||||
change_descriptor,
|
||||
signers,
|
||||
change_signers,
|
||||
address_validators: Vec::new(),
|
||||
network,
|
||||
database: RefCell::new(database),
|
||||
secp,
|
||||
@@ -267,7 +256,7 @@ where
|
||||
|
||||
let address_result = self
|
||||
.get_descriptor_for_keychain(keychain)
|
||||
.as_derived(incremented_index, &self.secp)
|
||||
.at_derivation_index(incremented_index)
|
||||
.address(self.network);
|
||||
|
||||
address_result
|
||||
@@ -286,7 +275,7 @@ where
|
||||
|
||||
let derived_key = self
|
||||
.get_descriptor_for_keychain(keychain)
|
||||
.as_derived(current_index, &self.secp);
|
||||
.at_derivation_index(current_index);
|
||||
|
||||
let script_pubkey = derived_key.script_pubkey();
|
||||
|
||||
@@ -314,7 +303,7 @@ where
|
||||
// Return derived address for the descriptor of given [`KeychainKind`] at a specific index
|
||||
fn peek_address(&self, index: u32, keychain: KeychainKind) -> Result<AddressInfo, Error> {
|
||||
self.get_descriptor_for_keychain(keychain)
|
||||
.as_derived(index, &self.secp)
|
||||
.at_derivation_index(index)
|
||||
.address(self.network)
|
||||
.map(|address| AddressInfo {
|
||||
index,
|
||||
@@ -330,7 +319,7 @@ where
|
||||
self.set_index(keychain, index)?;
|
||||
|
||||
self.get_descriptor_for_keychain(keychain)
|
||||
.as_derived(index, &self.secp)
|
||||
.at_derivation_index(index)
|
||||
.address(self.network)
|
||||
.map(|address| AddressInfo {
|
||||
index,
|
||||
@@ -379,7 +368,7 @@ where
|
||||
/// transaction output scripts.
|
||||
pub fn ensure_addresses_cached(&self, max_addresses: u32) -> Result<bool, Error> {
|
||||
let mut new_addresses_cached = false;
|
||||
let max_address = match self.descriptor.is_deriveable() {
|
||||
let max_address = match self.descriptor.has_wildcard() {
|
||||
false => 0,
|
||||
true => max_addresses,
|
||||
};
|
||||
@@ -396,7 +385,7 @@ where
|
||||
}
|
||||
|
||||
if let Some(change_descriptor) = &self.change_descriptor {
|
||||
let max_address = match change_descriptor.is_deriveable() {
|
||||
let max_address = match change_descriptor.has_wildcard() {
|
||||
false => 0,
|
||||
true => max_addresses,
|
||||
};
|
||||
@@ -565,24 +554,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an address validator
|
||||
///
|
||||
/// See [the `address_validator` module](address_validator) for an example.
|
||||
#[deprecated]
|
||||
#[allow(deprecated)]
|
||||
pub fn add_address_validator(&mut self, validator: Arc<dyn AddressValidator>) {
|
||||
self.address_validators.push(validator);
|
||||
}
|
||||
|
||||
/// Get the address validators
|
||||
///
|
||||
/// See [the `address_validator` module](address_validator).
|
||||
#[deprecated]
|
||||
#[allow(deprecated)]
|
||||
pub fn get_address_validators(&self) -> &[Arc<dyn AddressValidator>] {
|
||||
&self.address_validators
|
||||
}
|
||||
|
||||
/// Start building a transaction.
|
||||
///
|
||||
/// This returns a blank [`TxBuilder`] from which you can specify the parameters for the transaction.
|
||||
@@ -697,10 +668,9 @@ where
|
||||
// We use a match here instead of a map_or_else as it's way more readable :)
|
||||
let current_height = match params.current_height {
|
||||
// If they didn't tell us the current height, we assume it's the latest sync height.
|
||||
None => self
|
||||
.database()
|
||||
.get_sync_time()?
|
||||
.map(|sync_time| sync_time.block_time.height),
|
||||
None => self.database().get_sync_time()?.map(|sync_time| {
|
||||
LockTime::from_height(sync_time.block_time.height).expect("Invalid height")
|
||||
}),
|
||||
h => h,
|
||||
};
|
||||
|
||||
@@ -710,24 +680,33 @@ where
|
||||
// Fee sniping can be partially prevented by setting the timelock
|
||||
// to current_height. If we don't know the current_height,
|
||||
// we default to 0.
|
||||
let fee_sniping_height = current_height.unwrap_or(0);
|
||||
let fee_sniping_height = current_height.unwrap_or(LockTime::ZERO);
|
||||
|
||||
// We choose the biggest between the required nlocktime and the fee sniping
|
||||
// height
|
||||
std::cmp::max(requirements.timelock.unwrap_or(0), fee_sniping_height)
|
||||
match requirements.timelock {
|
||||
// No requirement, just use the fee_sniping_height
|
||||
None => fee_sniping_height,
|
||||
// There's a block-based requirement, but the value is lower than the fee_sniping_height
|
||||
Some(value @ LockTime::Blocks(_)) if value < fee_sniping_height => fee_sniping_height,
|
||||
// There's a time-based requirement or a block-based requirement greater
|
||||
// than the fee_sniping_height use that value
|
||||
Some(value) => value,
|
||||
}
|
||||
}
|
||||
// Specific nLockTime required and we have no constraints, so just set to that value
|
||||
Some(x) if requirements.timelock.is_none() => x,
|
||||
// Specific nLockTime required and it's compatible with the constraints
|
||||
Some(x) if check_nlocktime(x, requirements.timelock.unwrap()) => x,
|
||||
Some(x) if requirements.timelock.unwrap().is_same_unit(x) && x >= requirements.timelock.unwrap() => x,
|
||||
// Invalid nLockTime required
|
||||
Some(x) => return Err(Error::Generic(format!("TxBuilder requested timelock of `{}`, but at least `{}` is required to spend from this script", x, requirements.timelock.unwrap())))
|
||||
Some(x) => return Err(Error::Generic(format!("TxBuilder requested timelock of `{:?}`, but at least `{:?}` is required to spend from this script", x, requirements.timelock.unwrap())))
|
||||
};
|
||||
|
||||
let n_sequence = match (params.rbf, requirements.csv) {
|
||||
// No RBF or CSV but there's an nLockTime, so the nSequence cannot be final
|
||||
(None, None) if lock_time != 0 => 0xFFFFFFFE,
|
||||
(None, None) if lock_time != LockTime::ZERO => Sequence::ENABLE_LOCKTIME_NO_RBF,
|
||||
// No RBF, CSV or nLockTime, make the transaction final
|
||||
(None, None) => 0xFFFFFFFF,
|
||||
(None, None) => Sequence::MAX,
|
||||
|
||||
// No RBF requested, use the value from CSV. Note that this value is by definition
|
||||
// non-final, so even if a timelock is enabled this nSequence is fine, hence why we
|
||||
@@ -735,7 +714,7 @@ where
|
||||
(None, Some(csv)) => csv,
|
||||
|
||||
// RBF with a specific value but that value is too high
|
||||
(Some(tx_builder::RbfValue::Value(rbf)), _) if rbf >= 0xFFFFFFFE => {
|
||||
(Some(tx_builder::RbfValue::Value(rbf)), _) if !rbf.is_rbf() => {
|
||||
return Err(Error::Generic(
|
||||
"Cannot enable RBF with a nSequence >= 0xFFFFFFFE".into(),
|
||||
))
|
||||
@@ -745,7 +724,7 @@ where
|
||||
if !check_nsequence_rbf(rbf, csv) =>
|
||||
{
|
||||
return Err(Error::Generic(format!(
|
||||
"Cannot enable RBF with nSequence `{}` given a required OP_CSV of `{}`",
|
||||
"Cannot enable RBF with nSequence `{:?}` given a required OP_CSV of `{:?}`",
|
||||
rbf, csv
|
||||
)))
|
||||
}
|
||||
@@ -788,7 +767,7 @@ where
|
||||
|
||||
let mut tx = Transaction {
|
||||
version,
|
||||
lock_time,
|
||||
lock_time: lock_time.into(),
|
||||
input: vec![],
|
||||
output: vec![],
|
||||
};
|
||||
@@ -853,7 +832,7 @@ where
|
||||
params.drain_wallet,
|
||||
params.manually_selected_only,
|
||||
params.bumping_fee.is_some(), // we mandate confirmed transactions if we're bumping the fee
|
||||
current_height,
|
||||
current_height.map(LockTime::to_consensus_u32),
|
||||
)?;
|
||||
|
||||
// get drain script
|
||||
@@ -1005,7 +984,11 @@ where
|
||||
Some(tx) => tx,
|
||||
};
|
||||
let mut tx = details.transaction.take().unwrap();
|
||||
if !tx.input.iter().any(|txin| txin.sequence <= 0xFFFFFFFD) {
|
||||
if !tx
|
||||
.input
|
||||
.iter()
|
||||
.any(|txin| txin.sequence.to_consensus_u32() <= 0xFFFFFFFD)
|
||||
{
|
||||
return Err(Error::IrreplaceableTransaction);
|
||||
}
|
||||
|
||||
@@ -1131,8 +1114,9 @@ where
|
||||
psbt: &mut psbt::PartiallySignedTransaction,
|
||||
sign_options: SignOptions,
|
||||
) -> Result<bool, Error> {
|
||||
// this helps us doing our job later
|
||||
self.add_input_hd_keypaths(psbt)?;
|
||||
// This adds all the PSBT metadata for the inputs, which will help us later figure out how
|
||||
// to derive our keys
|
||||
self.update_psbt_with_descriptor(psbt)?;
|
||||
|
||||
// If we aren't allowed to use `witness_utxo`, ensure that every input (except p2tr and finalized ones)
|
||||
// has the `non_witness_utxo`
|
||||
@@ -1333,21 +1317,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn get_descriptor_for_txout(
|
||||
&self,
|
||||
txout: &TxOut,
|
||||
) -> Result<Option<DerivedDescriptor<'_>>, Error> {
|
||||
fn get_descriptor_for_txout(&self, txout: &TxOut) -> Result<Option<DerivedDescriptor>, Error> {
|
||||
Ok(self
|
||||
.database
|
||||
.borrow()
|
||||
.get_path_from_script_pubkey(&txout.script_pubkey)?
|
||||
.map(|(keychain, child)| (self.get_descriptor_for_keychain(keychain), child))
|
||||
.map(|(desc, child)| desc.as_derived(child, &self.secp)))
|
||||
.map(|(desc, child)| desc.at_derivation_index(child)))
|
||||
}
|
||||
|
||||
fn fetch_and_increment_index(&self, keychain: KeychainKind) -> Result<u32, Error> {
|
||||
let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain);
|
||||
let index = match descriptor.is_deriveable() {
|
||||
let index = match descriptor.has_wildcard() {
|
||||
false => 0,
|
||||
true => self.database.borrow_mut().increment_last_index(keychain)?,
|
||||
};
|
||||
@@ -1361,22 +1342,12 @@ where
|
||||
self.cache_addresses(keychain, index, CACHE_ADDR_BATCH_SIZE)?;
|
||||
}
|
||||
|
||||
let derived_descriptor = descriptor.as_derived(index, &self.secp);
|
||||
|
||||
let hd_keypaths = derived_descriptor.get_hd_keypaths(&self.secp);
|
||||
let script = derived_descriptor.script_pubkey();
|
||||
|
||||
for validator in &self.address_validators {
|
||||
#[allow(deprecated)]
|
||||
validator.validate(keychain, &hd_keypaths, &script)?;
|
||||
}
|
||||
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
fn fetch_index(&self, keychain: KeychainKind) -> Result<u32, Error> {
|
||||
let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain);
|
||||
let index = match descriptor.is_deriveable() {
|
||||
let index = match descriptor.has_wildcard() {
|
||||
false => Some(0),
|
||||
true => self.database.borrow_mut().get_last_index(keychain)?,
|
||||
};
|
||||
@@ -1400,7 +1371,7 @@ where
|
||||
mut count: u32,
|
||||
) -> Result<(), Error> {
|
||||
let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain);
|
||||
if !descriptor.is_deriveable() {
|
||||
if !descriptor.has_wildcard() {
|
||||
if from > 0 {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -1413,7 +1384,7 @@ where
|
||||
let start_time = time::Instant::new();
|
||||
for i in from..(from + count) {
|
||||
address_batch.set_script_pubkey(
|
||||
&descriptor.as_derived(i, &self.secp).script_pubkey(),
|
||||
&descriptor.at_derivation_index(i).script_pubkey(),
|
||||
keychain,
|
||||
i,
|
||||
)?;
|
||||
@@ -1617,52 +1588,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// probably redundant but it doesn't hurt...
|
||||
self.add_input_hd_keypaths(&mut psbt)?;
|
||||
|
||||
// add metadata for the outputs
|
||||
for (psbt_output, tx_output) in psbt.outputs.iter_mut().zip(psbt.unsigned_tx.output.iter())
|
||||
{
|
||||
if let Some((keychain, child)) = self
|
||||
.database
|
||||
.borrow()
|
||||
.get_path_from_script_pubkey(&tx_output.script_pubkey)?
|
||||
{
|
||||
let (desc, _) = self._get_descriptor_for_keychain(keychain);
|
||||
let derived_descriptor = desc.as_derived(child, &self.secp);
|
||||
|
||||
if let miniscript::Descriptor::Tr(tr) = &derived_descriptor {
|
||||
let tap_tree = if tr.taptree().is_some() {
|
||||
let mut builder = taproot::TaprootBuilder::new();
|
||||
for (depth, ms) in tr.iter_scripts() {
|
||||
let script = ms.encode();
|
||||
builder = builder.add_leaf(depth, script).expect(
|
||||
"Computing spend data on a valid Tree should always succeed",
|
||||
);
|
||||
}
|
||||
Some(
|
||||
psbt::TapTree::from_builder(builder)
|
||||
.expect("The tree should always be valid"),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
psbt_output.tap_tree = tap_tree;
|
||||
psbt_output
|
||||
.tap_key_origins
|
||||
.append(&mut derived_descriptor.get_tap_key_origins(&self.secp));
|
||||
psbt_output.tap_internal_key = Some(tr.internal_key().to_x_only_pubkey());
|
||||
} else {
|
||||
psbt_output
|
||||
.bip32_derivation
|
||||
.append(&mut derived_descriptor.get_hd_keypaths(&self.secp));
|
||||
}
|
||||
if params.include_output_redeem_witness_script {
|
||||
psbt_output.witness_script = derived_descriptor.psbt_witness_script();
|
||||
psbt_output.redeem_script = derived_descriptor.psbt_redeem_script();
|
||||
};
|
||||
}
|
||||
}
|
||||
self.update_psbt_with_descriptor(&mut psbt)?;
|
||||
|
||||
Ok(psbt)
|
||||
}
|
||||
@@ -1688,29 +1614,11 @@ where
|
||||
};
|
||||
|
||||
let desc = self.get_descriptor_for_keychain(keychain);
|
||||
let derived_descriptor = desc.as_derived(child, &self.secp);
|
||||
let derived_descriptor = desc.at_derivation_index(child);
|
||||
|
||||
if let miniscript::Descriptor::Tr(tr) = &derived_descriptor {
|
||||
psbt_input.tap_key_origins = derived_descriptor.get_tap_key_origins(&self.secp);
|
||||
psbt_input.tap_internal_key = Some(tr.internal_key().to_x_only_pubkey());
|
||||
|
||||
let spend_info = tr.spend_info();
|
||||
psbt_input.tap_merkle_root = spend_info.merkle_root();
|
||||
psbt_input.tap_scripts = spend_info
|
||||
.as_script_map()
|
||||
.keys()
|
||||
.filter_map(|script_ver| {
|
||||
spend_info
|
||||
.control_block(script_ver)
|
||||
.map(|cb| (cb, script_ver.clone()))
|
||||
})
|
||||
.collect();
|
||||
} else {
|
||||
psbt_input.bip32_derivation = derived_descriptor.get_hd_keypaths(&self.secp);
|
||||
}
|
||||
|
||||
psbt_input.redeem_script = derived_descriptor.psbt_redeem_script();
|
||||
psbt_input.witness_script = derived_descriptor.psbt_witness_script();
|
||||
psbt_input
|
||||
.update_with_descriptor_unchecked(&derived_descriptor)
|
||||
.map_err(MiniscriptPsbtError::Conversion)?;
|
||||
|
||||
let prev_output = utxo.outpoint;
|
||||
if let Some(prev_tx) = self.database.borrow().get_raw_tx(&prev_output.txid)? {
|
||||
@@ -1724,38 +1632,47 @@ where
|
||||
Ok(psbt_input)
|
||||
}
|
||||
|
||||
fn add_input_hd_keypaths(
|
||||
fn update_psbt_with_descriptor(
|
||||
&self,
|
||||
psbt: &mut psbt::PartiallySignedTransaction,
|
||||
) -> Result<(), Error> {
|
||||
let mut input_utxos = Vec::with_capacity(psbt.inputs.len());
|
||||
for n in 0..psbt.inputs.len() {
|
||||
input_utxos.push(psbt.get_utxo_for(n).clone());
|
||||
}
|
||||
// We need to borrow `psbt` mutably within the loops, so we have to allocate a vec for all
|
||||
// the input utxos and outputs
|
||||
//
|
||||
// Clippy complains that the collect is not required, but that's wrong
|
||||
#[allow(clippy::needless_collect)]
|
||||
let utxos = (0..psbt.inputs.len())
|
||||
.filter_map(|i| psbt.get_utxo_for(i).map(|utxo| (true, i, utxo)))
|
||||
.chain(
|
||||
psbt.unsigned_tx
|
||||
.output
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, out)| (false, i, out.clone())),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// try to add hd_keypaths if we've already seen the output
|
||||
for (psbt_input, out) in psbt.inputs.iter_mut().zip(input_utxos.iter()) {
|
||||
if let Some(out) = out {
|
||||
if let Some((keychain, child)) = self
|
||||
.database
|
||||
.borrow()
|
||||
.get_path_from_script_pubkey(&out.script_pubkey)?
|
||||
{
|
||||
debug!("Found descriptor {:?}/{}", keychain, child);
|
||||
// Try to figure out the keychain and derivation for every input and output
|
||||
for (is_input, index, out) in utxos.into_iter() {
|
||||
if let Some((keychain, child)) = self
|
||||
.database
|
||||
.borrow()
|
||||
.get_path_from_script_pubkey(&out.script_pubkey)?
|
||||
{
|
||||
debug!(
|
||||
"Found descriptor for input #{} {:?}/{}",
|
||||
index, keychain, child
|
||||
);
|
||||
|
||||
// merge hd_keypaths or tap_key_origins
|
||||
let desc = self.get_descriptor_for_keychain(keychain);
|
||||
if desc.is_taproot() {
|
||||
let mut tap_key_origins = desc
|
||||
.as_derived(child, &self.secp)
|
||||
.get_tap_key_origins(&self.secp);
|
||||
psbt_input.tap_key_origins.append(&mut tap_key_origins);
|
||||
} else {
|
||||
let mut hd_keypaths = desc
|
||||
.as_derived(child, &self.secp)
|
||||
.get_hd_keypaths(&self.secp);
|
||||
psbt_input.bip32_derivation.append(&mut hd_keypaths);
|
||||
}
|
||||
let desc = self.get_descriptor_for_keychain(keychain);
|
||||
let desc = desc.at_derivation_index(child);
|
||||
|
||||
if is_input {
|
||||
psbt.update_input_with_descriptor(index, &desc)
|
||||
.map_err(MiniscriptPsbtError::UtxoUpdate)?;
|
||||
} else {
|
||||
psbt.update_output_with_descriptor(index, &desc)
|
||||
.map_err(MiniscriptPsbtError::OutputUpdate)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1794,12 +1711,12 @@ where
|
||||
|
||||
// We need to ensure descriptor is derivable to fullfil "missing cache", otherwise we will
|
||||
// end up with an infinite loop
|
||||
let is_deriveable = self.descriptor.is_deriveable()
|
||||
let has_wildcard = self.descriptor.has_wildcard()
|
||||
&& (self.change_descriptor.is_none()
|
||||
|| self.change_descriptor.as_ref().unwrap().is_deriveable());
|
||||
|| self.change_descriptor.as_ref().unwrap().has_wildcard());
|
||||
|
||||
// Restrict max rounds in case of faulty "missing cache" implementation by blockchain
|
||||
let max_rounds = if is_deriveable { 100 } else { 1 };
|
||||
let max_rounds = if has_wildcard { 100 } else { 1 };
|
||||
|
||||
for _ in 0..max_rounds {
|
||||
let sync_res =
|
||||
@@ -1934,7 +1851,7 @@ pub fn get_funded_wallet(
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test {
|
||||
use bitcoin::{util::psbt, Network};
|
||||
use bitcoin::{util::psbt, Network, PackedLockTime, Sequence};
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::types::KeychainKind;
|
||||
@@ -2270,7 +2187,7 @@ pub(crate) mod test {
|
||||
|
||||
// Since we never synced the wallet we don't have a last_sync_height
|
||||
// we could use to try to prevent fee sniping. We default to 0.
|
||||
assert_eq!(psbt.unsigned_tx.lock_time, 0);
|
||||
assert_eq!(psbt.unsigned_tx.lock_time, PackedLockTime(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2295,7 +2212,7 @@ pub(crate) mod test {
|
||||
let (psbt, _) = builder.finish().unwrap();
|
||||
|
||||
// current_height will override the last sync height
|
||||
assert_eq!(psbt.unsigned_tx.lock_time, current_height);
|
||||
assert_eq!(psbt.unsigned_tx.lock_time, PackedLockTime(current_height));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2318,7 +2235,10 @@ pub(crate) mod test {
|
||||
let (psbt, _) = builder.finish().unwrap();
|
||||
|
||||
// If there's no current_height we're left with using the last sync height
|
||||
assert_eq!(psbt.unsigned_tx.lock_time, sync_time.block_time.height);
|
||||
assert_eq!(
|
||||
psbt.unsigned_tx.lock_time,
|
||||
PackedLockTime(sync_time.block_time.height)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2329,7 +2249,7 @@ pub(crate) mod test {
|
||||
builder.add_recipient(addr.script_pubkey(), 25_000);
|
||||
let (psbt, _) = builder.finish().unwrap();
|
||||
|
||||
assert_eq!(psbt.unsigned_tx.lock_time, 100_000);
|
||||
assert_eq!(psbt.unsigned_tx.lock_time, PackedLockTime(100_000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2340,13 +2260,13 @@ pub(crate) mod test {
|
||||
builder
|
||||
.add_recipient(addr.script_pubkey(), 25_000)
|
||||
.current_height(630_001)
|
||||
.nlocktime(630_000);
|
||||
.nlocktime(LockTime::from_height(630_000).unwrap());
|
||||
let (psbt, _) = builder.finish().unwrap();
|
||||
|
||||
// When we explicitly specify a nlocktime
|
||||
// we don't try any fee sniping prevention trick
|
||||
// (we ignore the current_height)
|
||||
assert_eq!(psbt.unsigned_tx.lock_time, 630_000);
|
||||
assert_eq!(psbt.unsigned_tx.lock_time, PackedLockTime(630_000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2356,15 +2276,15 @@ pub(crate) mod test {
|
||||
let mut builder = wallet.build_tx();
|
||||
builder
|
||||
.add_recipient(addr.script_pubkey(), 25_000)
|
||||
.nlocktime(630_000);
|
||||
.nlocktime(LockTime::from_height(630_000).unwrap());
|
||||
let (psbt, _) = builder.finish().unwrap();
|
||||
|
||||
assert_eq!(psbt.unsigned_tx.lock_time, 630_000);
|
||||
assert_eq!(psbt.unsigned_tx.lock_time, PackedLockTime(630_000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "TxBuilder requested timelock of `50000`, but at least `100000` is required to spend from this script"
|
||||
expected = "TxBuilder requested timelock of `Blocks(Height(50000))`, but at least `Blocks(Height(100000))` is required to spend from this script"
|
||||
)]
|
||||
fn test_create_tx_custom_locktime_incompatible_with_cltv() {
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
|
||||
@@ -2372,7 +2292,7 @@ pub(crate) mod test {
|
||||
let mut builder = wallet.build_tx();
|
||||
builder
|
||||
.add_recipient(addr.script_pubkey(), 25_000)
|
||||
.nlocktime(50000);
|
||||
.nlocktime(LockTime::from_height(50000).unwrap());
|
||||
builder.finish().unwrap();
|
||||
}
|
||||
|
||||
@@ -2384,7 +2304,7 @@ pub(crate) mod test {
|
||||
builder.add_recipient(addr.script_pubkey(), 25_000);
|
||||
let (psbt, _) = builder.finish().unwrap();
|
||||
|
||||
assert_eq!(psbt.unsigned_tx.input[0].sequence, 6);
|
||||
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2398,12 +2318,12 @@ pub(crate) mod test {
|
||||
let (psbt, _) = builder.finish().unwrap();
|
||||
// When CSV is enabled it takes precedence over the rbf value (unless forced by the user).
|
||||
// It will be set to the OP_CSV value, in this case 6
|
||||
assert_eq!(psbt.unsigned_tx.input[0].sequence, 6);
|
||||
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Cannot enable RBF with nSequence `3` given a required OP_CSV of `6`"
|
||||
expected = "Cannot enable RBF with nSequence `Sequence(3)` given a required OP_CSV of `Sequence(6)`"
|
||||
)]
|
||||
fn test_create_tx_with_custom_rbf_csv() {
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
|
||||
@@ -2411,7 +2331,7 @@ pub(crate) mod test {
|
||||
let mut builder = wallet.build_tx();
|
||||
builder
|
||||
.add_recipient(addr.script_pubkey(), 25_000)
|
||||
.enable_rbf_with_sequence(3);
|
||||
.enable_rbf_with_sequence(Sequence(3));
|
||||
builder.finish().unwrap();
|
||||
}
|
||||
|
||||
@@ -2423,7 +2343,7 @@ pub(crate) mod test {
|
||||
builder.add_recipient(addr.script_pubkey(), 25_000);
|
||||
let (psbt, _) = builder.finish().unwrap();
|
||||
|
||||
assert_eq!(psbt.unsigned_tx.input[0].sequence, 0xFFFFFFFE);
|
||||
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2434,7 +2354,7 @@ pub(crate) mod test {
|
||||
let mut builder = wallet.build_tx();
|
||||
builder
|
||||
.add_recipient(addr.script_pubkey(), 25_000)
|
||||
.enable_rbf_with_sequence(0xFFFFFFFE);
|
||||
.enable_rbf_with_sequence(Sequence(0xFFFFFFFE));
|
||||
builder.finish().unwrap();
|
||||
}
|
||||
|
||||
@@ -2445,10 +2365,10 @@ pub(crate) mod test {
|
||||
let mut builder = wallet.build_tx();
|
||||
builder
|
||||
.add_recipient(addr.script_pubkey(), 25_000)
|
||||
.enable_rbf_with_sequence(0xDEADBEEF);
|
||||
.enable_rbf_with_sequence(Sequence(0xDEADBEEF));
|
||||
let (psbt, _) = builder.finish().unwrap();
|
||||
|
||||
assert_eq!(psbt.unsigned_tx.input[0].sequence, 0xDEADBEEF);
|
||||
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xDEADBEEF));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2475,7 +2395,7 @@ pub(crate) mod test {
|
||||
builder.add_recipient(addr.script_pubkey(), 25_000);
|
||||
let (psbt, _) = builder.finish().unwrap();
|
||||
|
||||
assert_eq!(psbt.unsigned_tx.input[0].sequence, 0xFFFFFFFF);
|
||||
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFF));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2996,7 +2916,7 @@ pub(crate) mod test {
|
||||
.policy_path(path, KeychainKind::External);
|
||||
let (psbt, _) = builder.finish().unwrap();
|
||||
|
||||
assert_eq!(psbt.unsigned_tx.input[0].sequence, 0xFFFFFFFF);
|
||||
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFF));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3015,7 +2935,7 @@ pub(crate) mod test {
|
||||
.policy_path(path, KeychainKind::External);
|
||||
let (psbt, _) = builder.finish().unwrap();
|
||||
|
||||
assert_eq!(psbt.unsigned_tx.input[0].sequence, 144);
|
||||
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(144));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -4868,7 +4788,7 @@ pub(crate) mod test {
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_tr_repeated_key());
|
||||
let addr = wallet.get_address(AddressIndex::New).unwrap();
|
||||
|
||||
let path = vec![("rn4nre9c".to_string(), vec![0])]
|
||||
let path = vec![("e5mmg3xh".to_string(), vec![0])]
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
@@ -4878,48 +4798,50 @@ pub(crate) mod test {
|
||||
.policy_path(path, KeychainKind::External);
|
||||
let (psbt, _) = builder.finish().unwrap();
|
||||
|
||||
let mut input_key_origins = psbt.inputs[0]
|
||||
.tap_key_origins
|
||||
.clone()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
input_key_origins.sort();
|
||||
|
||||
assert_eq!(
|
||||
psbt.inputs[0]
|
||||
.tap_key_origins
|
||||
.clone()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![(
|
||||
from_str!("2b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3"),
|
||||
input_key_origins,
|
||||
vec![
|
||||
(
|
||||
vec![
|
||||
from_str!(
|
||||
"858ad7a7d7f270e2c490c4d6ba00c499e46b18fdd59ea3c2c47d20347110271e"
|
||||
),
|
||||
from_str!(
|
||||
"f6e927ad4492c051fe325894a4f5f14538333b55a35f099876be42009ec8f903"
|
||||
)
|
||||
],
|
||||
(Default::default(), Default::default())
|
||||
from_str!("b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55"),
|
||||
(
|
||||
vec![],
|
||||
(FromStr::from_str("871fd295").unwrap(), vec![].into())
|
||||
)
|
||||
),
|
||||
(
|
||||
from_str!("2b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3"),
|
||||
(
|
||||
vec![
|
||||
from_str!(
|
||||
"858ad7a7d7f270e2c490c4d6ba00c499e46b18fdd59ea3c2c47d20347110271e"
|
||||
),
|
||||
from_str!(
|
||||
"f6e927ad4492c051fe325894a4f5f14538333b55a35f099876be42009ec8f903"
|
||||
),
|
||||
],
|
||||
(FromStr::from_str("ece52657").unwrap(), vec![].into())
|
||||
)
|
||||
)
|
||||
)],
|
||||
],
|
||||
"Wrong input tap_key_origins"
|
||||
);
|
||||
|
||||
let mut output_key_origins = psbt.outputs[0]
|
||||
.tap_key_origins
|
||||
.clone()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
output_key_origins.sort();
|
||||
|
||||
assert_eq!(
|
||||
psbt.outputs[0]
|
||||
.tap_key_origins
|
||||
.clone()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![(
|
||||
from_str!("2b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3"),
|
||||
(
|
||||
vec![
|
||||
from_str!(
|
||||
"858ad7a7d7f270e2c490c4d6ba00c499e46b18fdd59ea3c2c47d20347110271e"
|
||||
),
|
||||
from_str!(
|
||||
"f6e927ad4492c051fe325894a4f5f14538333b55a35f099876be42009ec8f903"
|
||||
)
|
||||
],
|
||||
(Default::default(), Default::default())
|
||||
)
|
||||
)],
|
||||
input_key_origins, output_key_origins,
|
||||
"Wrong output tap_key_origins"
|
||||
);
|
||||
}
|
||||
@@ -5171,7 +5093,7 @@ pub(crate) mod test {
|
||||
#[test]
|
||||
fn test_taproot_script_spend_sign_include_some_leaves() {
|
||||
use crate::signer::TapLeavesOptions;
|
||||
use crate::wallet::taproot::TapLeafHash;
|
||||
use bitcoin::util::taproot::TapLeafHash;
|
||||
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
|
||||
let addr = wallet.get_address(AddressIndex::New).unwrap();
|
||||
@@ -5213,7 +5135,7 @@ pub(crate) mod test {
|
||||
#[test]
|
||||
fn test_taproot_script_spend_sign_exclude_some_leaves() {
|
||||
use crate::signer::TapLeavesOptions;
|
||||
use crate::wallet::taproot::TapLeafHash;
|
||||
use bitcoin::util::taproot::TapLeafHash;
|
||||
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
|
||||
let addr = wallet.get_address(AddressIndex::New).unwrap();
|
||||
@@ -5560,6 +5482,7 @@ pub(crate) mod test {
|
||||
SignOptions {
|
||||
remove_partial_sigs: false,
|
||||
try_finalize: false,
|
||||
allow_grinding: false,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
@@ -5574,6 +5497,7 @@ pub(crate) mod test {
|
||||
&mut psbt,
|
||||
SignOptions {
|
||||
remove_partial_sigs: false,
|
||||
allow_grinding: false,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
@@ -5582,6 +5506,39 @@ pub(crate) mod test {
|
||||
assert_fee_rate!(psbt, details.fee.unwrap_or(0), fee_rate);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fee_rate_sign_grinding_low_r() {
|
||||
// Our goal is to obtain a transaction with a signature with low-R (70 bytes)
|
||||
// by setting the `allow_grinding` signing option as true.
|
||||
// We then check that our fee rate and fee calculation is alright and that our
|
||||
// signature is 70 bytes.
|
||||
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
|
||||
let addr = wallet.get_address(New).unwrap();
|
||||
let fee_rate = FeeRate::from_sat_per_vb(1.0);
|
||||
let mut builder = wallet.build_tx();
|
||||
builder
|
||||
.drain_to(addr.script_pubkey())
|
||||
.drain_wallet()
|
||||
.fee_rate(fee_rate);
|
||||
let (mut psbt, details) = builder.finish().unwrap();
|
||||
|
||||
wallet
|
||||
.sign(
|
||||
&mut psbt,
|
||||
SignOptions {
|
||||
remove_partial_sigs: false,
|
||||
allow_grinding: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let key = psbt.inputs[0].partial_sigs.keys().next().unwrap();
|
||||
let sig_len = psbt.inputs[0].partial_sigs[key].sig.serialize_der().len();
|
||||
assert_eq!(sig_len, 70);
|
||||
assert_fee_rate!(psbt, details.fee.unwrap_or(0), fee_rate);
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-hardware-signer")]
|
||||
#[test]
|
||||
fn test_create_signer() {
|
||||
|
||||
@@ -96,10 +96,10 @@ use bitcoin::{secp256k1, XOnlyPublicKey};
|
||||
use bitcoin::{EcdsaSighashType, PrivateKey, PublicKey, SchnorrSighashType, Script};
|
||||
|
||||
use miniscript::descriptor::{
|
||||
Descriptor, DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorXKey,
|
||||
KeyMap, SinglePubKey,
|
||||
Descriptor, DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, KeyMap, SinglePriv,
|
||||
SinglePubKey,
|
||||
};
|
||||
use miniscript::{Legacy, MiniscriptKey, Segwitv0, Tap};
|
||||
use miniscript::{Legacy, Segwitv0, SigType, Tap, ToPublicKey};
|
||||
|
||||
use super::utils::SecpCtx;
|
||||
use crate::descriptor::{DescriptorMeta, XKeyUtils};
|
||||
@@ -369,11 +369,11 @@ impl InputSigner for SignerWrapper<DescriptorXKey<ExtendedPrivKey>> {
|
||||
|
||||
impl SignerCommon for SignerWrapper<PrivateKey> {
|
||||
fn id(&self, secp: &SecpCtx) -> SignerId {
|
||||
SignerId::from(self.public_key(secp).to_pubkeyhash())
|
||||
SignerId::from(self.public_key(secp).to_pubkeyhash(SigType::Ecdsa))
|
||||
}
|
||||
|
||||
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
|
||||
Some(DescriptorSecretKey::SinglePriv(DescriptorSinglePriv {
|
||||
Some(DescriptorSecretKey::Single(SinglePriv {
|
||||
key: self.signer,
|
||||
origin: None,
|
||||
}))
|
||||
@@ -472,6 +472,7 @@ impl InputSigner for SignerWrapper<PrivateKey> {
|
||||
hash,
|
||||
hash_ty,
|
||||
secp,
|
||||
sign_options.allow_grinding,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -485,9 +486,14 @@ fn sign_psbt_ecdsa(
|
||||
hash: bitcoin::Sighash,
|
||||
hash_ty: EcdsaSighashType,
|
||||
secp: &SecpCtx,
|
||||
allow_grinding: bool,
|
||||
) {
|
||||
let msg = &Message::from_slice(&hash.into_inner()[..]).unwrap();
|
||||
let sig = secp.sign_ecdsa(msg, secret_key);
|
||||
let sig = if allow_grinding {
|
||||
secp.sign_ecdsa_low_r(msg, secret_key)
|
||||
} else {
|
||||
secp.sign_ecdsa(msg, secret_key)
|
||||
};
|
||||
secp.verify_ecdsa(msg, &sig, &pubkey.inner)
|
||||
.expect("invalid or corrupted ecdsa signature");
|
||||
|
||||
@@ -511,13 +517,13 @@ fn sign_psbt_schnorr(
|
||||
let keypair = match leaf_hash {
|
||||
None => keypair
|
||||
.tap_tweak(secp, psbt_input.tap_merkle_root)
|
||||
.into_inner(),
|
||||
.to_inner(),
|
||||
Some(_) => keypair, // no tweak for script spend
|
||||
};
|
||||
|
||||
let msg = &Message::from_slice(&hash.into_inner()[..]).unwrap();
|
||||
let sig = secp.sign_schnorr(msg, &keypair);
|
||||
secp.verify_schnorr(&sig, msg, &XOnlyPublicKey::from_keypair(&keypair))
|
||||
secp.verify_schnorr(&sig, msg, &XOnlyPublicKey::from_keypair(&keypair).0)
|
||||
.expect("invalid or corrupted schnorr signature");
|
||||
|
||||
let final_signature = schnorr::SchnorrSig { sig, hash_ty };
|
||||
@@ -570,7 +576,7 @@ impl SignersContainer {
|
||||
self.0
|
||||
.values()
|
||||
.filter_map(|signer| signer.descriptor_secret_key())
|
||||
.filter_map(|secret| secret.as_public(secp).ok().map(|public| (public, secret)))
|
||||
.filter_map(|secret| secret.to_public(secp).ok().map(|public| (public, secret)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -595,8 +601,13 @@ impl SignersContainer {
|
||||
};
|
||||
|
||||
match secret {
|
||||
DescriptorSecretKey::SinglePriv(private_key) => container.add_external(
|
||||
SignerId::from(private_key.key.public_key(secp).to_pubkeyhash()),
|
||||
DescriptorSecretKey::Single(private_key) => container.add_external(
|
||||
SignerId::from(
|
||||
private_key
|
||||
.key
|
||||
.public_key(secp)
|
||||
.to_pubkeyhash(SigType::Ecdsa),
|
||||
),
|
||||
SignerOrdering::default(),
|
||||
Arc::new(SignerWrapper::new(private_key.key, ctx)),
|
||||
),
|
||||
@@ -718,10 +729,15 @@ pub struct SignOptions {
|
||||
///
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// Customize which taproot script-path leaves the signer should sign.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TapLeavesOptions {
|
||||
/// The signer will sign all the leaves it has a key for.
|
||||
All,
|
||||
@@ -751,6 +767,7 @@ impl Default for SignOptions {
|
||||
try_finalize: true,
|
||||
tap_leaves_options: TapLeavesOptions::default(),
|
||||
sign_with_tap_internal_key: true,
|
||||
allow_grinding: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,9 +42,7 @@ use std::default::Default;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use bitcoin::util::psbt::{self, PartiallySignedTransaction as Psbt};
|
||||
use bitcoin::{OutPoint, Script, Transaction};
|
||||
|
||||
use miniscript::descriptor::DescriptorTrait;
|
||||
use bitcoin::{LockTime, OutPoint, Script, Sequence, Transaction};
|
||||
|
||||
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
|
||||
use crate::{database::BatchDatabase, Error, Utxo, Wallet};
|
||||
@@ -139,7 +137,7 @@ pub(crate) struct TxParams {
|
||||
pub(crate) manually_selected_only: bool,
|
||||
pub(crate) sighash: Option<psbt::PsbtSighashType>,
|
||||
pub(crate) ordering: TxOrdering,
|
||||
pub(crate) locktime: Option<u32>,
|
||||
pub(crate) locktime: Option<LockTime>,
|
||||
pub(crate) rbf: Option<RbfValue>,
|
||||
pub(crate) version: Option<Version>,
|
||||
pub(crate) change_policy: ChangeSpendPolicy,
|
||||
@@ -147,7 +145,7 @@ pub(crate) struct TxParams {
|
||||
pub(crate) add_global_xpubs: bool,
|
||||
pub(crate) include_output_redeem_witness_script: bool,
|
||||
pub(crate) bumping_fee: Option<PreviousFee>,
|
||||
pub(crate) current_height: Option<u32>,
|
||||
pub(crate) current_height: Option<LockTime>,
|
||||
pub(crate) allow_dust: bool,
|
||||
}
|
||||
|
||||
@@ -426,7 +424,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
|
||||
/// Use a specific nLockTime while creating the transaction
|
||||
///
|
||||
/// This can cause conflicts if the wallet's descriptors contain an "after" (OP_CLTV) operator.
|
||||
pub fn nlocktime(&mut self, locktime: u32) -> &mut Self {
|
||||
pub fn nlocktime(&mut self, locktime: LockTime) -> &mut Self {
|
||||
self.params.locktime = Some(locktime);
|
||||
self
|
||||
}
|
||||
@@ -541,7 +539,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
|
||||
///
|
||||
/// If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not
|
||||
/// be a valid nSequence to signal RBF.
|
||||
pub fn enable_rbf_with_sequence(&mut self, nsequence: u32) -> &mut Self {
|
||||
pub fn enable_rbf_with_sequence(&mut self, nsequence: Sequence) -> &mut Self {
|
||||
self.params.rbf = Some(RbfValue::Value(nsequence));
|
||||
self
|
||||
}
|
||||
@@ -558,7 +556,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
|
||||
///
|
||||
/// In both cases, if you don't provide a current height, we use the last sync height.
|
||||
pub fn current_height(&mut self, height: u32) -> &mut Self {
|
||||
self.params.current_height = Some(height);
|
||||
self.params.current_height = Some(LockTime::from_height(height).expect("Invalid height"));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -703,7 +701,7 @@ impl TxOrdering {
|
||||
#[cfg(not(test))]
|
||||
let mut rng = rand::thread_rng();
|
||||
#[cfg(test)]
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(12345);
|
||||
|
||||
tx.output.shuffle(&mut rng);
|
||||
}
|
||||
@@ -736,13 +734,13 @@ impl Default for Version {
|
||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
|
||||
pub(crate) enum RbfValue {
|
||||
Default,
|
||||
Value(u32),
|
||||
Value(Sequence),
|
||||
}
|
||||
|
||||
impl RbfValue {
|
||||
pub(crate) fn get_value(&self) -> u32 {
|
||||
pub(crate) fn get_value(&self) -> Sequence {
|
||||
match self {
|
||||
RbfValue::Default => 0xFFFFFFFD,
|
||||
RbfValue::Default => Sequence::ENABLE_RBF_NO_LOCKTIME,
|
||||
RbfValue::Value(v) => *v,
|
||||
}
|
||||
}
|
||||
@@ -858,10 +856,12 @@ mod test {
|
||||
}
|
||||
|
||||
fn get_test_utxos() -> Vec<LocalUtxo> {
|
||||
use bitcoin::hashes::Hash;
|
||||
|
||||
vec![
|
||||
LocalUtxo {
|
||||
outpoint: OutPoint {
|
||||
txid: Default::default(),
|
||||
txid: bitcoin::Txid::from_inner([0; 32]),
|
||||
vout: 0,
|
||||
},
|
||||
txout: Default::default(),
|
||||
@@ -870,7 +870,7 @@ mod test {
|
||||
},
|
||||
LocalUtxo {
|
||||
outpoint: OutPoint {
|
||||
txid: Default::default(),
|
||||
txid: bitcoin::Txid::from_inner([0; 32]),
|
||||
vout: 1,
|
||||
},
|
||||
txout: Default::default(),
|
||||
|
||||
@@ -9,23 +9,11 @@
|
||||
// You may not use this file except in accordance with one or both of these
|
||||
// licenses.
|
||||
|
||||
use bitcoin::blockdata::script::Script;
|
||||
use bitcoin::secp256k1::{All, Secp256k1};
|
||||
use bitcoin::{LockTime, Script, Sequence};
|
||||
|
||||
use miniscript::{MiniscriptKey, Satisfier, ToPublicKey};
|
||||
|
||||
// MSB of the nSequence. If set there's no consensus-constraint, so it must be disabled when
|
||||
// spending using CSV in order to enforce CSV rules
|
||||
pub(crate) const SEQUENCE_LOCKTIME_DISABLE_FLAG: u32 = 1 << 31;
|
||||
// When nSequence is lower than this flag the timelock is interpreted as block-height-based,
|
||||
// otherwise it's time-based
|
||||
pub(crate) const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = 1 << 22;
|
||||
// Mask for the bits used to express the timelock
|
||||
pub(crate) const SEQUENCE_LOCKTIME_MASK: u32 = 0x0000FFFF;
|
||||
|
||||
// Threshold for nLockTime to be considered a block-height-based timelock rather than time-based
|
||||
pub(crate) const BLOCKS_TIMELOCK_THRESHOLD: u32 = 500000000;
|
||||
|
||||
/// Trait to check if a value is below the dust limit.
|
||||
/// We are performing dust value calculation for a given script public key using rust-bitcoin to
|
||||
/// keep it compatible with network dust rate
|
||||
@@ -38,7 +26,7 @@ pub trait IsDust {
|
||||
|
||||
impl IsDust for u64 {
|
||||
fn is_dust(&self, script: &Script) -> bool {
|
||||
*self < script.dust_value().as_sat()
|
||||
*self < script.dust_value().to_sat()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,19 +44,15 @@ impl After {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_nsequence_rbf(rbf: u32, csv: u32) -> bool {
|
||||
// This flag cannot be set in the nSequence when spending using OP_CSV
|
||||
if rbf & SEQUENCE_LOCKTIME_DISABLE_FLAG != 0 {
|
||||
pub(crate) fn check_nsequence_rbf(rbf: Sequence, csv: Sequence) -> bool {
|
||||
// The RBF value must enable relative timelocks
|
||||
if !rbf.is_relative_lock_time() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mask = SEQUENCE_LOCKTIME_TYPE_FLAG | SEQUENCE_LOCKTIME_MASK;
|
||||
let rbf = rbf & mask;
|
||||
let csv = csv & mask;
|
||||
|
||||
// Both values should be represented in the same unit (either time-based or
|
||||
// block-height based)
|
||||
if (rbf < SEQUENCE_LOCKTIME_TYPE_FLAG) != (csv < SEQUENCE_LOCKTIME_TYPE_FLAG) {
|
||||
if rbf.is_time_locked() != csv.is_time_locked() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -80,24 +64,10 @@ pub(crate) fn check_nsequence_rbf(rbf: u32, csv: u32) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn check_nlocktime(nlocktime: u32, required: u32) -> bool {
|
||||
// Both values should be expressed in the same unit
|
||||
if (nlocktime < BLOCKS_TIMELOCK_THRESHOLD) != (required < BLOCKS_TIMELOCK_THRESHOLD) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The value should be at least `required`
|
||||
if nlocktime < required {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for After {
|
||||
fn check_after(&self, n: u32) -> bool {
|
||||
fn check_after(&self, n: LockTime) -> bool {
|
||||
if let Some(current_height) = self.current_height {
|
||||
current_height >= n
|
||||
current_height >= n.to_consensus_u32()
|
||||
} else {
|
||||
self.assume_height_reached
|
||||
}
|
||||
@@ -125,10 +95,15 @@ impl Older {
|
||||
}
|
||||
|
||||
impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for Older {
|
||||
fn check_older(&self, n: u32) -> bool {
|
||||
fn check_older(&self, n: Sequence) -> bool {
|
||||
if let Some(current_height) = self.current_height {
|
||||
// TODO: test >= / >
|
||||
current_height as u64 >= self.create_height.unwrap_or(0) as u64 + n as u64
|
||||
current_height
|
||||
>= self
|
||||
.create_height
|
||||
.unwrap_or(0)
|
||||
.checked_add(n.to_consensus_u32())
|
||||
.expect("Overflowing addition")
|
||||
} else {
|
||||
self.assume_height_reached
|
||||
}
|
||||
@@ -139,11 +114,12 @@ pub(crate) type SecpCtx = Secp256k1<All>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{
|
||||
check_nlocktime, check_nsequence_rbf, IsDust, BLOCKS_TIMELOCK_THRESHOLD,
|
||||
SEQUENCE_LOCKTIME_TYPE_FLAG,
|
||||
};
|
||||
use crate::bitcoin::Address;
|
||||
// When nSequence is lower than this flag the timelock is interpreted as block-height-based,
|
||||
// otherwise it's time-based
|
||||
pub(crate) const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = 1 << 22;
|
||||
|
||||
use super::{check_nsequence_rbf, IsDust};
|
||||
use crate::bitcoin::{Address, Sequence};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
@@ -165,66 +141,40 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_check_nsequence_rbf_msb_set() {
|
||||
let result = check_nsequence_rbf(0x80000000, 5000);
|
||||
let result = check_nsequence_rbf(Sequence(0x80000000), Sequence(5000));
|
||||
assert!(!result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_nsequence_rbf_lt_csv() {
|
||||
let result = check_nsequence_rbf(4000, 5000);
|
||||
let result = check_nsequence_rbf(Sequence(4000), Sequence(5000));
|
||||
assert!(!result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_nsequence_rbf_different_unit() {
|
||||
let result = check_nsequence_rbf(SEQUENCE_LOCKTIME_TYPE_FLAG + 5000, 5000);
|
||||
let result =
|
||||
check_nsequence_rbf(Sequence(SEQUENCE_LOCKTIME_TYPE_FLAG + 5000), Sequence(5000));
|
||||
assert!(!result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_nsequence_rbf_mask() {
|
||||
let result = check_nsequence_rbf(0x3f + 10_000, 5000);
|
||||
let result = check_nsequence_rbf(Sequence(0x3f + 10_000), Sequence(5000));
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_nsequence_rbf_same_unit_blocks() {
|
||||
let result = check_nsequence_rbf(10_000, 5000);
|
||||
let result = check_nsequence_rbf(Sequence(10_000), Sequence(5000));
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_nsequence_rbf_same_unit_time() {
|
||||
let result = check_nsequence_rbf(
|
||||
SEQUENCE_LOCKTIME_TYPE_FLAG + 10_000,
|
||||
SEQUENCE_LOCKTIME_TYPE_FLAG + 5000,
|
||||
);
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_nlocktime_lt_cltv() {
|
||||
let result = check_nlocktime(4000, 5000);
|
||||
assert!(!result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_nlocktime_different_unit() {
|
||||
let result = check_nlocktime(BLOCKS_TIMELOCK_THRESHOLD + 5000, 5000);
|
||||
assert!(!result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_nlocktime_same_unit_blocks() {
|
||||
let result = check_nlocktime(10_000, 5000);
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_nlocktime_same_unit_time() {
|
||||
let result = check_nlocktime(
|
||||
BLOCKS_TIMELOCK_THRESHOLD + 10_000,
|
||||
BLOCKS_TIMELOCK_THRESHOLD + 5000,
|
||||
Sequence(SEQUENCE_LOCKTIME_TYPE_FLAG + 10_000),
|
||||
Sequence(SEQUENCE_LOCKTIME_TYPE_FLAG + 5000),
|
||||
);
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ use crate::error::Error;
|
||||
/// Depending on the [capabilities](crate::blockchain::Blockchain::get_capabilities) of the
|
||||
/// [`Blockchain`] backend, the method could fail when called with old "historical" transactions or
|
||||
/// with unconfirmed transactions that have been evicted from the backend's memory.
|
||||
///
|
||||
/// [`Blockchain`]: crate::blockchain::Blockchain
|
||||
pub fn verify_tx<D: Database, B: GetTx>(
|
||||
tx: &Transaction,
|
||||
database: &D,
|
||||
|
||||
Reference in New Issue
Block a user