[dependencies]
bdk-macros = "^0.6"
log = "^0.4"
-miniscript = { version = "7.0", features = ["use-serde"] }
-bitcoin = { version = "0.28.1", features = ["use-serde", "base64", "rand"] }
+miniscript = { version = "8.0", features = ["serde"] }
+bitcoin = { version = "0.29.1", features = ["serde", "base64", "rand"] }
serde = { version = "^1.0", features = ["derive"] }
serde_json = { version = "^1.0" }
rand = "^0.8"
# Optional dependencies
sled = { version = "0.34", optional = true }
-electrum-client = { version = "0.11", optional = true }
-esplora-client = { version = "0.1.1", default-features = false, optional = true }
+electrum-client = { version = "0.12", optional = true }
+esplora-client = { version = "0.2", default-features = false, optional = true }
rusqlite = { version = "0.27.0", optional = true }
ahash = { version = "0.7.6", optional = true }
futures = { version = "0.3", optional = true }
rocksdb = { version = "0.14", default-features = false, features = ["snappy"], optional = true }
cc = { version = ">=1.0.64", optional = true }
socks = { version = "0.3", optional = true }
-hwi = { version = "0.2.3", optional = true }
+hwi = { version = "0.3.0", optional = true }
bip39 = { version = "1.0.1", optional = true }
bitcoinconsensus = { version = "0.19.0-3", optional = true }
# Needed by bdk_blockchain_tests macro and the `rpc` feature
-bitcoincore-rpc = { version = "0.15", optional = true }
+bitcoincore-rpc = { version = "0.16", optional = true }
# Platform-specific dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
[dev-dependencies]
lazy_static = "1.4"
env_logger = "0.7"
-electrsd = "0.20"
+electrsd = "0.21"
+# Move back to importing from rust-bitcoin once https://github.com/rust-bitcoin/rust-bitcoin/pull/1342 is released
+base64 = "^0.13"
[[example]]
name = "compact_filters_balance"
use bdk::electrum_client::Client;
use bdk::wallet::AddressIndex::New;
-use bitcoin::base64;
+use base64;
use bitcoin::consensus::serialize;
fn main() -> Result<(), bdk::Error> {
```rust,no_run
use bdk::{Wallet, SignOptions, database::MemoryDatabase};
-use bitcoin::base64;
+use base64;
use bitcoin::consensus::deserialize;
fn main() -> Result<(), bdk::Error> {
let internal_secret_xkey = DescriptorSecretKey::from_str("[e9824965/84'/1'/0']tprv8fvem7qWxY3SGCQczQpRpqTKg455wf1zgixn6MZ4ze8gRfHjov5gXBQTadNfDgqs9ERbZZ3Bi1PNYrCCusFLucT39K525MWLpeURjHwUsfX/1/*").unwrap();
let secp = Secp256k1::new();
- let external_public_xkey = external_secret_xkey.as_public(&secp).unwrap();
- let internal_public_xkey = internal_secret_xkey.as_public(&secp).unwrap();
+ let external_public_xkey = external_secret_xkey.to_public(&secp).unwrap();
+ let internal_public_xkey = internal_secret_xkey.to_public(&secp).unwrap();
let signing_external_descriptor = descriptor!(wpkh(external_secret_xkey)).unwrap();
let signing_internal_descriptor = descriptor!(wpkh(internal_secret_xkey)).unwrap();
/// );
/// # }
/// ```
-#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)]
+#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AnyBlockchainConfig {
#[cfg(feature = "electrum")]
}
/// Data to connect to a Bitcoin P2P peer
-#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)]
+#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq)]
pub struct BitcoinPeerConfig {
/// Peer address such as 127.0.0.1:18333
pub address: String,
}
/// Configuration for a [`CompactFiltersBlockchain`]
-#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)]
+#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq)]
pub struct CompactFiltersBlockchainConfig {
/// List of peers to try to connect to for asking headers and filters
pub peers: Vec<BitcoinPeerConfig>,
/// Look-up a transaction in the mempool given an [`Inventory`] request
pub fn get_tx(&self, inventory: &Inventory) -> Option<Transaction> {
let identifer = match inventory {
- Inventory::Error | Inventory::Block(_) | Inventory::WitnessBlock(_) => return None,
+ Inventory::Error
+ | Inventory::Block(_)
+ | Inventory::WitnessBlock(_)
+ | Inventory::CompactBlock(_) => return None,
Inventory::Transaction(txid) => TxIdentifier::Txid(*txid),
Inventory::WitnessTransaction(txid) => TxIdentifier::Txid(*txid),
Inventory::WTx(wtxid) => TxIdentifier::Wtxid(*wtxid),
}
impl Encodable for BundleStatus {
- fn consensus_encode<W: Write>(&self, mut e: W) -> Result<usize, std::io::Error> {
+ fn consensus_encode<W: Write + ?Sized>(&self, e: &mut W) -> Result<usize, std::io::Error> {
let mut written = 0;
match self {
BundleStatus::Init => {
- written += 0x00u8.consensus_encode(&mut e)?;
+ written += 0x00u8.consensus_encode(e)?;
}
BundleStatus::CfHeaders { cf_headers } => {
- written += 0x01u8.consensus_encode(&mut e)?;
- written += VarInt(cf_headers.len() as u64).consensus_encode(&mut e)?;
+ written += 0x01u8.consensus_encode(e)?;
+ written += VarInt(cf_headers.len() as u64).consensus_encode(e)?;
for header in cf_headers {
- written += header.consensus_encode(&mut e)?;
+ written += header.consensus_encode(e)?;
}
}
BundleStatus::CFilters { cf_filters } => {
- written += 0x02u8.consensus_encode(&mut e)?;
- written += VarInt(cf_filters.len() as u64).consensus_encode(&mut e)?;
+ written += 0x02u8.consensus_encode(e)?;
+ written += VarInt(cf_filters.len() as u64).consensus_encode(e)?;
for filter in cf_filters {
- written += filter.consensus_encode(&mut e)?;
+ written += filter.consensus_encode(e)?;
}
}
BundleStatus::Processed { cf_filters } => {
- written += 0x03u8.consensus_encode(&mut e)?;
- written += VarInt(cf_filters.len() as u64).consensus_encode(&mut e)?;
+ written += 0x03u8.consensus_encode(e)?;
+ written += VarInt(cf_filters.len() as u64).consensus_encode(e)?;
for filter in cf_filters {
- written += filter.consensus_encode(&mut e)?;
+ written += filter.consensus_encode(e)?;
}
}
BundleStatus::Pruned => {
- written += 0x04u8.consensus_encode(&mut e)?;
+ written += 0x04u8.consensus_encode(e)?;
}
BundleStatus::Tip { cf_filters } => {
- written += 0x05u8.consensus_encode(&mut e)?;
- written += VarInt(cf_filters.len() as u64).consensus_encode(&mut e)?;
+ written += 0x05u8.consensus_encode(e)?;
+ written += VarInt(cf_filters.len() as u64).consensus_encode(e)?;
for filter in cf_filters {
- written += filter.consensus_encode(&mut e)?;
+ written += filter.consensus_encode(e)?;
}
}
}
}
impl Decodable for BundleStatus {
- fn consensus_decode<D: Read>(mut d: D) -> Result<Self, bitcoin::consensus::encode::Error> {
- let byte_type = u8::consensus_decode(&mut d)?;
+ fn consensus_decode<D: Read + ?Sized>(
+ d: &mut D,
+ ) -> Result<Self, bitcoin::consensus::encode::Error> {
+ let byte_type = u8::consensus_decode(d)?;
match byte_type {
0x00 => Ok(BundleStatus::Init),
0x01 => {
- let num = VarInt::consensus_decode(&mut d)?;
+ let num = VarInt::consensus_decode(d)?;
let num = num.0 as usize;
let mut cf_headers = Vec::with_capacity(num);
for _ in 0..num {
- cf_headers.push(FilterHeader::consensus_decode(&mut d)?);
+ cf_headers.push(FilterHeader::consensus_decode(d)?);
}
Ok(BundleStatus::CfHeaders { cf_headers })
}
0x02 => {
- let num = VarInt::consensus_decode(&mut d)?;
+ let num = VarInt::consensus_decode(d)?;
let num = num.0 as usize;
let mut cf_filters = Vec::with_capacity(num);
for _ in 0..num {
- cf_filters.push(Vec::<u8>::consensus_decode(&mut d)?);
+ cf_filters.push(Vec::<u8>::consensus_decode(d)?);
}
Ok(BundleStatus::CFilters { cf_filters })
}
0x03 => {
- let num = VarInt::consensus_decode(&mut d)?;
+ let num = VarInt::consensus_decode(d)?;
let num = num.0 as usize;
let mut cf_filters = Vec::with_capacity(num);
for _ in 0..num {
- cf_filters.push(Vec::<u8>::consensus_decode(&mut d)?);
+ cf_filters.push(Vec::<u8>::consensus_decode(d)?);
}
Ok(BundleStatus::Processed { cf_filters })
}
0x04 => Ok(BundleStatus::Pruned),
0x05 => {
- let num = VarInt::consensus_decode(&mut d)?;
+ let num = VarInt::consensus_decode(d)?;
let num = num.0 as usize;
let mut cf_filters = Vec::with_capacity(num);
for _ in 0..num {
- cf_filters.push(Vec::<u8>::consensus_decode(&mut d)?);
+ cf_filters.push(Vec::<u8>::consensus_decode(d)?);
}
Ok(BundleStatus::Tip { cf_filters })
}
pub fn start_snapshot(&self, from: usize) -> Result<ChainStore<Snapshot>, CompactFiltersError> {
- let new_cf_name: String = thread_rng().sample_iter(&Alphanumeric).take(16).collect();
+ let new_cf_name: String = thread_rng()
+ .sample_iter(&Alphanumeric)
+ .map(|byte| byte as char)
+ .take(16)
+ .collect();
let new_cf_name = format!("_headers:{}", new_cf_name);
let mut write_store = self.store.write().unwrap();
&first_key,
(
BundleStatus::Init,
- filter.filter_header(&FilterHeader::from_hash(Default::default())),
+ filter.filter_header(&FilterHeader::from_hash(Hash::all_zeros())),
)
.serialize(),
)?;
use std::time::Duration;
use bitcoin::hash_types::{BlockHash, FilterHeader};
+use bitcoin::hashes::Hash;
use bitcoin::network::message::NetworkMessage;
use bitcoin::network::message_blockdata::GetHeadersMessage;
use bitcoin::util::bip158::BlockFilter;
peer.send(NetworkMessage::GetHeaders(GetHeadersMessage::new(
locators_vec,
- Default::default(),
+ Hash::all_zeros(),
)))?;
let (mut snapshot, mut last_hash) = if let NetworkMessage::Headers(headers) = peer
.recv("headers", Some(Duration::from_secs(TIMEOUT_SECS)))?
while sync_height < peer.get_version().start_height as usize {
peer.send(NetworkMessage::GetHeaders(GetHeadersMessage::new(
vec![last_hash],
- Default::default(),
+ Hash::all_zeros(),
)))?;
if let NetworkMessage::Headers(headers) = peer
.recv("headers", Some(Duration::from_secs(TIMEOUT_SECS)))?
}
/// Configuration for an [`ElectrumBlockchain`]
-#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)]
+#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq)]
pub struct ElectrumBlockchainConfig {
/// URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with `ssl://` or `tcp://` and include a port
///
#[maybe_async]
impl GetBlockHash for EsploraBlockchain {
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
- let block_header = await_or_block!(self.url_client.get_header(height as u32))?;
- Ok(block_header.block_hash())
+ Ok(await_or_block!(self
+ .url_client
+ .get_block_hash(height as u32))?)
}
}
impl GetBlockHash for EsploraBlockchain {
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
- let block_header = self.url_client.get_header(height as u32)?;
- Ok(block_header.block_hash())
+ Ok(self.url_client.get_block_hash(height as u32)?)
}
}
pub use self::blocking::*;
/// Configuration for an [`EsploraBlockchain`]
-#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)]
+#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq)]
pub struct EsploraBlockchainConfig {
/// Base URL of the esplora service
///
}
/// RpcBlockchain configuration options
-#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct RpcConfig {
/// The bitcoin node url
pub url: String,
/// In general, BDK tries to sync `scriptPubKey`s cached in [`crate::database::Database`] with
/// `scriptPubKey`s imported in the Bitcoin Core Wallet. These parameters are used for determining
/// how the `importdescriptors` RPC calls are to be made.
-#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct RpcSyncParams {
/// The minimum number of scripts to scan for on initial sync.
pub start_script_count: usize,
.estimate_smart_fee(target as u16, None)?
.fee_rate
.ok_or(Error::FeeRateUnavailable)?
- .as_sat() as f64;
+ .to_sat() as f64;
Ok(FeeRate::from_sat_per_vb((sat_per_kb / 1000f64) as f32))
}
updated = true;
TransactionDetails {
txid: tx_res.info.txid,
- ..Default::default()
+ transaction: None,
+
+ received: 0,
+ sent: 0,
+ fee: None,
+ confirmation_time: None,
}
});
// update fee (if needed)
if let (None, Some(new_fee)) = (db_tx.fee, tx_res.detail.fee) {
updated = true;
- db_tx.fee = Some(new_fee.as_sat().unsigned_abs());
+ db_tx.fee = Some(new_fee.to_sat().unsigned_abs());
}
// update confirmation time (if needed)
LocalUtxo {
outpoint: OutPoint::new(entry.txid, entry.vout),
txout: TxOut {
- value: entry.amount.as_sat(),
+ value: entry.amount.to_sat(),
script_pubkey: entry.script_pub_key,
},
keychain,
mod test {
use super::*;
use crate::{
- descriptor::{into_wallet_descriptor_checked, AsDerived},
- testutils::blockchain_tests::TestClient,
+ descriptor::into_wallet_descriptor_checked, testutils::blockchain_tests::TestClient,
wallet::utils::SecpCtx,
};
use bitcoin::{Address, Network};
use bitcoincore_rpc::RpcApi;
use log::LevelFilter;
- use miniscript::DescriptorTrait;
crate::bdk_blockchain_tests! {
fn test_instance(test_client: &TestClient) -> RpcBlockchain {
// generate scripts (1 tx per script)
let scripts = (0..TX_COUNT)
- .map(|index| desc.as_derived(index, &secp).script_pubkey())
+ .map(|index| desc.at_derivation_index(index).script_pubkey())
.collect::<Vec<_>>();
// import scripts and wait
}
let tx = $crate::bitcoin::Transaction {
version: 1,
- lock_time: 0,
+ lock_time: bitcoin::PackedLockTime(0),
input,
output: tx_meta
.output
+++ /dev/null
-// 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.
-
-//! Derived descriptor keys
-//!
-//! The [`DerivedDescriptorKey`] type is a wrapper over the standard [`DescriptorPublicKey`] which
-//! guarantees that all the extended keys have a fixed derivation path, i.e. all the wildcards have
-//! been replaced by actual derivation indexes.
-//!
-//! The [`AsDerived`] trait provides a quick way to derive descriptors to obtain a
-//! `Descriptor<DerivedDescriptorKey>` type. This, in turn, can be used to derive public
-//! keys for arbitrary derivation indexes.
-//!
-//! Combining this with [`Wallet::get_signers`], secret keys can also be derived.
-//!
-//! # Example
-//!
-//! ```
-//! # use std::str::FromStr;
-//! # use bitcoin::secp256k1::Secp256k1;
-//! use bdk::descriptor::{AsDerived, DescriptorPublicKey};
-//! use bdk::miniscript::{ToPublicKey, TranslatePk, MiniscriptKey};
-//!
-//! let secp = Secp256k1::gen_new();
-//!
-//! let key = DescriptorPublicKey::from_str("[aa600a45/84'/0'/0']tpubDCbDXFKoLTQp44wQuC12JgSn5g9CWGjZdpBHeTqyypZ4VvgYjTJmK9CkyR5bFvG9f4PutvwmvpYCLkFx2rpx25hiMs4sUgxJveW8ZzSAVAc/0/*")?;
-//! let (descriptor, _, _) = bdk::descriptor!(wpkh(key))?;
-//!
-//! // derived: wpkh([aa600a45/84'/0'/0']tpubDCbDXFKoLTQp44wQuC12JgSn5g9CWGjZdpBHeTqyypZ4VvgYjTJmK9CkyR5bFvG9f4PutvwmvpYCLkFx2rpx25hiMs4sUgxJveW8ZzSAVAc/0/42)#3ladd0t2
-//! let derived = descriptor.as_derived(42, &secp);
-//! println!("derived: {}", derived);
-//!
-//! // with_pks: wpkh(02373ecb54c5e83bd7e0d40adf78b65efaf12fafb13571f0261fc90364eee22e1e)#p4jjgvll
-//! let with_pks = derived.translate_pk_infallible(|pk| pk.to_public_key(), |pkh| pkh.to_public_key().to_pubkeyhash());
-//! println!("with_pks: {}", with_pks);
-//! # Ok::<(), Box<dyn std::error::Error>>(())
-//! ```
-//!
-//! [`Wallet::get_signers`]: crate::wallet::Wallet::get_signers
-
-use std::cmp::Ordering;
-use std::fmt;
-use std::hash::{Hash, Hasher};
-use std::ops::Deref;
-
-use bitcoin::hashes::hash160;
-use bitcoin::{PublicKey, XOnlyPublicKey};
-
-use miniscript::descriptor::{DescriptorSinglePub, SinglePubKey, Wildcard};
-use miniscript::{Descriptor, DescriptorPublicKey, MiniscriptKey, ToPublicKey, TranslatePk};
-
-use crate::wallet::utils::SecpCtx;
-
-/// Extended [`DescriptorPublicKey`] that has been derived
-///
-/// Derived keys are guaranteed to never contain wildcards of any kind
-#[derive(Debug, Clone)]
-pub struct DerivedDescriptorKey<'s>(DescriptorPublicKey, &'s SecpCtx);
-
-impl<'s> DerivedDescriptorKey<'s> {
- /// Construct a new derived key
- ///
- /// Panics if the key is wildcard
- pub fn new(key: DescriptorPublicKey, secp: &'s SecpCtx) -> DerivedDescriptorKey<'s> {
- if let DescriptorPublicKey::XPub(xpub) = &key {
- assert!(xpub.wildcard == Wildcard::None)
- }
-
- DerivedDescriptorKey(key, secp)
- }
-}
-
-impl<'s> Deref for DerivedDescriptorKey<'s> {
- type Target = DescriptorPublicKey;
-
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
-
-impl<'s> PartialEq for DerivedDescriptorKey<'s> {
- fn eq(&self, other: &Self) -> bool {
- self.0 == other.0
- }
-}
-
-impl<'s> Eq for DerivedDescriptorKey<'s> {}
-
-impl<'s> PartialOrd for DerivedDescriptorKey<'s> {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- self.0.partial_cmp(&other.0)
- }
-}
-
-impl<'s> Ord for DerivedDescriptorKey<'s> {
- fn cmp(&self, other: &Self) -> Ordering {
- self.0.cmp(&other.0)
- }
-}
-
-impl<'s> fmt::Display for DerivedDescriptorKey<'s> {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- self.0.fmt(f)
- }
-}
-
-impl<'s> Hash for DerivedDescriptorKey<'s> {
- fn hash<H: Hasher>(&self, state: &mut H) {
- self.0.hash(state);
- }
-}
-
-impl<'s> MiniscriptKey for DerivedDescriptorKey<'s> {
- type Hash = Self;
-
- fn to_pubkeyhash(&self) -> Self::Hash {
- DerivedDescriptorKey(self.0.to_pubkeyhash(), self.1)
- }
-
- fn is_uncompressed(&self) -> bool {
- self.0.is_uncompressed()
- }
-}
-
-impl<'s> ToPublicKey for DerivedDescriptorKey<'s> {
- fn to_public_key(&self) -> PublicKey {
- match &self.0 {
- DescriptorPublicKey::SinglePub(DescriptorSinglePub {
- key: SinglePubKey::XOnly(_),
- ..
- }) => panic!("Found x-only public key in non-tr descriptor"),
- DescriptorPublicKey::SinglePub(DescriptorSinglePub {
- key: SinglePubKey::FullKey(ref pk),
- ..
- }) => *pk,
- DescriptorPublicKey::XPub(ref xpub) => PublicKey::new(
- xpub.xkey
- .derive_pub(self.1, &xpub.derivation_path)
- .expect("Shouldn't fail, only normal derivations")
- .public_key,
- ),
- }
- }
-
- fn to_x_only_pubkey(&self) -> XOnlyPublicKey {
- match &self.0 {
- DescriptorPublicKey::SinglePub(DescriptorSinglePub {
- key: SinglePubKey::XOnly(ref pk),
- ..
- }) => *pk,
- DescriptorPublicKey::SinglePub(DescriptorSinglePub {
- key: SinglePubKey::FullKey(ref pk),
- ..
- }) => XOnlyPublicKey::from(pk.inner),
- DescriptorPublicKey::XPub(ref xpub) => XOnlyPublicKey::from(
- xpub.xkey
- .derive_pub(self.1, &xpub.derivation_path)
- .expect("Shouldn't fail, only normal derivations")
- .public_key,
- ),
- }
- }
-
- fn hash_to_hash160(hash: &Self::Hash) -> hash160::Hash {
- hash.to_public_key().to_pubkeyhash()
- }
-}
-
-/// Utilities to derive descriptors
-///
-/// Check out the [module level] documentation for more.
-///
-/// [module level]: crate::descriptor::derived
-pub trait AsDerived {
- /// Derive a descriptor and transform all of its keys to `DerivedDescriptorKey`
- fn as_derived<'s>(&self, index: u32, secp: &'s SecpCtx)
- -> Descriptor<DerivedDescriptorKey<'s>>;
-
- /// Transform the keys into `DerivedDescriptorKey`.
- ///
- /// Panics if the descriptor is not "fixed", i.e. if it's derivable
- fn as_derived_fixed<'s>(&self, secp: &'s SecpCtx) -> Descriptor<DerivedDescriptorKey<'s>>;
-}
-
-impl AsDerived for Descriptor<DescriptorPublicKey> {
- fn as_derived<'s>(
- &self,
- index: u32,
- secp: &'s SecpCtx,
- ) -> Descriptor<DerivedDescriptorKey<'s>> {
- self.derive(index).translate_pk_infallible(
- |key| DerivedDescriptorKey::new(key.clone(), secp),
- |key| DerivedDescriptorKey::new(key.clone(), secp),
- )
- }
-
- fn as_derived_fixed<'s>(&self, secp: &'s SecpCtx) -> Descriptor<DerivedDescriptorKey<'s>> {
- assert!(!self.is_deriveable());
-
- self.as_derived(0, secp)
- }
-}
$crate::keys::make_pkh($key, &secp)
});
( after ( $value:expr ) ) => ({
- $crate::impl_leaf_opcode_value!(After, $value)
+ $crate::impl_leaf_opcode_value!(After, $crate::bitcoin::PackedLockTime($value)) // TODO!! https://github.com/rust-bitcoin/rust-bitcoin/issues/1302
});
( older ( $value:expr ) ) => ({
- $crate::impl_leaf_opcode_value!(Older, $value)
+ $crate::impl_leaf_opcode_value!(Older, $crate::bitcoin::Sequence($value)) // TODO!!
});
( sha256 ( $hash:expr ) ) => ({
$crate::impl_leaf_opcode_value!(Sha256, $hash)
mod test {
use bitcoin::hashes::hex::ToHex;
use bitcoin::secp256k1::Secp256k1;
- use miniscript::descriptor::{DescriptorPublicKey, DescriptorTrait, KeyMap};
+ use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
use miniscript::{Descriptor, Legacy, Segwitv0};
use std::str::FromStr;
use bitcoin::util::bip32;
use bitcoin::PrivateKey;
- use crate::descriptor::derived::AsDerived;
-
// test the descriptor!() macro
// verify descriptor generates expected script(s) (if bare or pk) or address(es)
is_fixed: bool,
expected: &[&str],
) {
- let secp = Secp256k1::new();
-
let (desc, _key_map, _networks) = desc.unwrap();
assert_eq!(desc.is_witness(), is_witness);
- assert_eq!(!desc.is_deriveable(), is_fixed);
+ assert_eq!(!desc.has_wildcard(), is_fixed);
for i in 0..expected.len() {
let index = i as u32;
- let child_desc = if !desc.is_deriveable() {
- desc.as_derived_fixed(&secp)
+ let child_desc = if !desc.has_wildcard() {
+ desc.at_derivation_index(0)
} else {
- desc.as_derived(index, &secp)
+ desc.at_derivation_index(index)
};
let address = child_desc.address(Regtest);
if let Ok(address) = address {
//! from [`miniscript`].
use std::collections::BTreeMap;
-use std::ops::Deref;
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource};
use bitcoin::util::{psbt, taproot};
use bitcoin::{secp256k1, PublicKey, XOnlyPublicKey};
-use bitcoin::{Network, Script, TxOut};
+use bitcoin::{Network, TxOut};
-use miniscript::descriptor::{DescriptorType, InnerXKey, SinglePubKey};
+use miniscript::descriptor::{DefiniteDescriptorKey, DescriptorType, InnerXKey, SinglePubKey};
pub use miniscript::{
descriptor::DescriptorXKey, descriptor::KeyMap, descriptor::Wildcard, Descriptor,
DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
};
-use miniscript::{DescriptorTrait, ForEachKey, TranslatePk};
+use miniscript::{ForEachKey, MiniscriptKey, TranslatePk};
use crate::descriptor::policy::BuildSatisfaction;
pub mod checksum;
-pub mod derived;
#[doc(hidden)]
pub mod dsl;
pub mod error;
pub mod template;
pub use self::checksum::get_checksum;
-pub use self::derived::{AsDerived, DerivedDescriptorKey};
pub use self::error::Error as DescriptorError;
pub use self::policy::Policy;
use self::template::DescriptorTemplateOut;
pub type ExtendedDescriptor = Descriptor<DescriptorPublicKey>;
/// Alias for a [`Descriptor`] that contains extended **derived** keys
-pub type DerivedDescriptor<'s> = Descriptor<DerivedDescriptorKey<'s>>;
+pub type DerivedDescriptor = Descriptor<DefiniteDescriptorKey>;
/// Alias for the type of maps that represent derivation paths in a [`psbt::Input`] or
/// [`psbt::Output`]
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
use crate::keys::DescriptorKey;
- let check_key = |pk: &DescriptorPublicKey| {
- let (pk, _, networks) = if self.0.is_witness() {
- let descriptor_key: DescriptorKey<miniscript::Segwitv0> =
- pk.clone().into_descriptor_key()?;
- descriptor_key.extract(secp)?
- } else {
- let descriptor_key: DescriptorKey<miniscript::Legacy> =
- pk.clone().into_descriptor_key()?;
- descriptor_key.extract(secp)?
- };
-
- if networks.contains(&network) {
- Ok(pk)
- } else {
- Err(DescriptorError::Key(KeyError::InvalidNetwork))
+ struct Translator<'s, 'd> {
+ secp: &'s SecpCtx,
+ descriptor: &'d ExtendedDescriptor,
+ network: Network,
+ }
+
+ impl<'s, 'd>
+ miniscript::Translator<DescriptorPublicKey, miniscript::DummyKey, DescriptorError>
+ for Translator<'s, 'd>
+ {
+ fn pk(
+ &mut self,
+ pk: &DescriptorPublicKey,
+ ) -> Result<miniscript::DummyKey, DescriptorError> {
+ let secp = &self.secp;
+
+ let (_, _, networks) = if self.descriptor.is_taproot() {
+ let descriptor_key: DescriptorKey<miniscript::Tap> =
+ pk.clone().into_descriptor_key()?;
+ descriptor_key.extract(secp)?
+ } else if self.descriptor.is_witness() {
+ let descriptor_key: DescriptorKey<miniscript::Segwitv0> =
+ pk.clone().into_descriptor_key()?;
+ descriptor_key.extract(secp)?
+ } else {
+ let descriptor_key: DescriptorKey<miniscript::Legacy> =
+ pk.clone().into_descriptor_key()?;
+ descriptor_key.extract(secp)?
+ };
+
+ if networks.contains(&self.network) {
+ Ok(miniscript::DummyKey)
+ } else {
+ Err(DescriptorError::Key(KeyError::InvalidNetwork))
+ }
}
- };
+ fn sha256(
+ &mut self,
+ _sha256: &<DescriptorPublicKey as MiniscriptKey>::Sha256,
+ ) -> Result<miniscript::DummySha256Hash, DescriptorError> {
+ Ok(Default::default())
+ }
+ fn hash256(
+ &mut self,
+ _hash256: &<DescriptorPublicKey as MiniscriptKey>::Hash256,
+ ) -> Result<miniscript::DummyHash256Hash, DescriptorError> {
+ Ok(Default::default())
+ }
+ fn ripemd160(
+ &mut self,
+ _ripemd160: &<DescriptorPublicKey as MiniscriptKey>::Ripemd160,
+ ) -> Result<miniscript::DummyRipemd160Hash, DescriptorError> {
+ Ok(Default::default())
+ }
+ fn hash160(
+ &mut self,
+ _hash160: &<DescriptorPublicKey as MiniscriptKey>::Hash160,
+ ) -> Result<miniscript::DummyHash160Hash, DescriptorError> {
+ Ok(Default::default())
+ }
+ }
// check the network for the keys
- let translated = self.0.translate_pk(check_key, check_key)?;
+ self.0.translate_pk(&mut Translator {
+ secp,
+ network,
+ descriptor: &self.0,
+ })?;
- Ok((translated, self.1))
+ Ok(self)
}
}
_secp: &SecpCtx,
network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
- let valid_networks = &self.2;
+ struct Translator {
+ network: Network,
+ }
- let fix_key = |pk: &DescriptorPublicKey| {
- if valid_networks.contains(&network) {
+ impl miniscript::Translator<DescriptorPublicKey, DescriptorPublicKey, DescriptorError>
+ for Translator
+ {
+ fn pk(
+ &mut self,
+ pk: &DescriptorPublicKey,
+ ) -> Result<DescriptorPublicKey, DescriptorError> {
// workaround for xpubs generated by other key types, like bip39: since when the
// conversion is made one network has to be chosen, what we generally choose
// "mainnet", but then override the set of valid networks to specify that all of
let pk = match pk {
DescriptorPublicKey::XPub(ref xpub) => {
let mut xpub = xpub.clone();
- xpub.xkey.network = network;
+ xpub.xkey.network = self.network;
DescriptorPublicKey::XPub(xpub)
}
};
Ok(pk)
- } else {
- Err(DescriptorError::Key(KeyError::InvalidNetwork))
}
- };
+ miniscript::translate_hash_clone!(
+ DescriptorPublicKey,
+ DescriptorPublicKey,
+ DescriptorError
+ );
+ }
+
+ if !self.2.contains(&network) {
+ return Err(DescriptorError::Key(KeyError::InvalidNetwork));
+ }
// fixup the network for keys that need it
- let translated = self.0.translate_pk(fix_key, fix_key)?;
+ let translated = self.0.translate_pk(&mut Translator { network })?;
Ok((translated, self.1))
}
derivation_path,
wildcard,
..
- }) = k.as_key()
+ }) = k
{
return *wildcard == Wildcard::Hardened
|| derivation_path.into_iter().any(ChildNumber::is_hardened);
}
}
-pub(crate) trait DerivedDescriptorMeta {
- fn get_hd_keypaths(&self, secp: &SecpCtx) -> HdKeyPaths;
- fn get_tap_key_origins(&self, secp: &SecpCtx) -> TapKeyOrigins;
-}
-
pub(crate) trait DescriptorMeta {
fn is_witness(&self) -> bool;
fn is_taproot(&self) -> bool;
&self,
hd_keypaths: &HdKeyPaths,
secp: &'s SecpCtx,
- ) -> Option<DerivedDescriptor<'s>>;
+ ) -> Option<DerivedDescriptor>;
fn derive_from_tap_key_origins<'s>(
&self,
tap_key_origins: &TapKeyOrigins,
secp: &'s SecpCtx,
- ) -> Option<DerivedDescriptor<'s>>;
+ ) -> Option<DerivedDescriptor>;
fn derive_from_psbt_key_origins<'s>(
&self,
key_origins: BTreeMap<Fingerprint, (&DerivationPath, SinglePubKey)>,
secp: &'s SecpCtx,
- ) -> Option<DerivedDescriptor<'s>>;
+ ) -> Option<DerivedDescriptor>;
fn derive_from_psbt_input<'s>(
&self,
psbt_input: &psbt::Input,
utxo: Option<TxOut>,
secp: &'s SecpCtx,
- ) -> Option<DerivedDescriptor<'s>>;
-}
-
-pub(crate) trait DescriptorScripts {
- fn psbt_redeem_script(&self) -> Option<Script>;
- fn psbt_witness_script(&self) -> Option<Script>;
-}
-
-impl<'s> DescriptorScripts for DerivedDescriptor<'s> {
- fn psbt_redeem_script(&self) -> Option<Script> {
- match self.desc_type() {
- DescriptorType::ShWpkh => Some(self.explicit_script().unwrap()),
- DescriptorType::ShWsh => Some(self.explicit_script().unwrap().to_v0_p2wsh()),
- DescriptorType::Sh => Some(self.explicit_script().unwrap()),
- DescriptorType::Bare => Some(self.explicit_script().unwrap()),
- DescriptorType::ShSortedMulti => Some(self.explicit_script().unwrap()),
- DescriptorType::ShWshSortedMulti => Some(self.explicit_script().unwrap().to_v0_p2wsh()),
- DescriptorType::Pkh
- | DescriptorType::Wpkh
- | DescriptorType::Tr
- | DescriptorType::Wsh
- | DescriptorType::WshSortedMulti => None,
- }
- }
-
- fn psbt_witness_script(&self) -> Option<Script> {
- match self.desc_type() {
- DescriptorType::Wsh => Some(self.explicit_script().unwrap()),
- DescriptorType::ShWsh => Some(self.explicit_script().unwrap()),
- DescriptorType::WshSortedMulti | DescriptorType::ShWshSortedMulti => {
- Some(self.explicit_script().unwrap())
- }
- DescriptorType::Bare
- | DescriptorType::Sh
- | DescriptorType::Pkh
- | DescriptorType::Wpkh
- | DescriptorType::ShSortedMulti
- | DescriptorType::Tr
- | DescriptorType::ShWpkh => None,
- }
- }
+ ) -> Option<DerivedDescriptor>;
}
impl DescriptorMeta for ExtendedDescriptor {
let mut answer = Vec::new();
self.for_each_key(|pk| {
- if let DescriptorPublicKey::XPub(xpub) = pk.as_key() {
+ if let DescriptorPublicKey::XPub(xpub) = pk {
answer.push(xpub.clone());
}
&self,
key_origins: BTreeMap<Fingerprint, (&DerivationPath, SinglePubKey)>,
secp: &'s SecpCtx,
- ) -> Option<DerivedDescriptor<'s>> {
+ ) -> Option<DerivedDescriptor> {
// Ensure that deriving `xpub` with `path` yields `expected`
let verify_key = |xpub: &DescriptorXKey<ExtendedPubKey>,
path: &DerivationPath,
// using `for_any_key` should make this stop as soon as we return `true`
self.for_any_key(|key| {
- if let DescriptorPublicKey::XPub(xpub) = key.as_key().deref() {
+ if let DescriptorPublicKey::XPub(xpub) = key {
// Check if the key matches one entry in our `key_origins`. If it does, `matches()` will
// return the "prefix" that matched, so we remove that prefix from the full path
// found in `key_origins` and save it in `derive_path`. We expect this to be a derivation
false
});
- path_found.map(|path| self.as_derived(path, secp))
+ path_found.map(|path| self.at_derivation_index(path))
}
fn derive_from_hd_keypaths<'s>(
&self,
hd_keypaths: &HdKeyPaths,
secp: &'s SecpCtx,
- ) -> Option<DerivedDescriptor<'s>> {
+ ) -> Option<DerivedDescriptor> {
// "Convert" an hd_keypaths map to the format required by `derive_from_psbt_key_origins`
let key_origins = hd_keypaths
.iter()
&self,
tap_key_origins: &TapKeyOrigins,
secp: &'s SecpCtx,
- ) -> Option<DerivedDescriptor<'s>> {
+ ) -> Option<DerivedDescriptor> {
// "Convert" a tap_key_origins map to the format required by `derive_from_psbt_key_origins`
let key_origins = tap_key_origins
.iter()
psbt_input: &psbt::Input,
utxo: Option<TxOut>,
secp: &'s SecpCtx,
- ) -> Option<DerivedDescriptor<'s>> {
+ ) -> Option<DerivedDescriptor> {
if let Some(derived) = self.derive_from_hd_keypaths(&psbt_input.bip32_derivation, secp) {
return Some(derived);
}
if let Some(derived) = self.derive_from_tap_key_origins(&psbt_input.tap_key_origins, secp) {
return Some(derived);
}
- if self.is_deriveable() {
+ if self.has_wildcard() {
// We can't try to bruteforce the derivation index, exit here
return None;
}
- let descriptor = self.as_derived_fixed(secp);
+ let descriptor = self.at_derivation_index(0);
match descriptor.desc_type() {
// TODO: add pk() here
DescriptorType::Pkh
}
}
-impl<'s> DerivedDescriptorMeta for DerivedDescriptor<'s> {
- fn get_hd_keypaths(&self, secp: &SecpCtx) -> HdKeyPaths {
- let mut answer = BTreeMap::new();
- self.for_each_key(|key| {
- if let DescriptorPublicKey::XPub(xpub) = key.as_key().deref() {
- let derived_pubkey = xpub
- .xkey
- .derive_pub(secp, &xpub.derivation_path)
- .expect("Derivation can't fail");
-
- answer.insert(
- derived_pubkey.public_key,
- (xpub.root_fingerprint(secp), xpub.full_path(&[])),
- );
- }
-
- true
- });
-
- answer
- }
-
- fn get_tap_key_origins(&self, secp: &SecpCtx) -> TapKeyOrigins {
- use miniscript::ToPublicKey;
-
- let mut answer = BTreeMap::new();
- let mut insert_path = |pk: &DerivedDescriptorKey<'_>, lh| {
- let key_origin = match pk.deref() {
- DescriptorPublicKey::XPub(xpub) => {
- Some((xpub.root_fingerprint(secp), xpub.full_path(&[])))
- }
- DescriptorPublicKey::SinglePub(_) => None,
- };
-
- // If this is the internal key, we only insert the key origin if it's not None.
- // For keys found in the tap tree we always insert a key origin (because the signer
- // looks for it to know which leaves to sign for), even though it may be None
- match (lh, key_origin) {
- (None, Some(ko)) => {
- answer
- .entry(pk.to_x_only_pubkey())
- .or_insert_with(|| (vec![], ko));
- }
- (Some(lh), origin) => {
- answer
- .entry(pk.to_x_only_pubkey())
- .or_insert_with(|| (vec![], origin.unwrap_or_default()))
- .0
- .push(lh);
- }
- _ => {}
- }
- };
-
- if let Descriptor::Tr(tr) = &self {
- // Internal key first, then iterate the scripts
- insert_path(tr.internal_key(), None);
-
- for (_, ms) in tr.iter_scripts() {
- // Assume always the same leaf version
- let leaf_hash = taproot::TapLeafHash::from_script(
- &ms.encode(),
- taproot::LeafVersion::TapScript,
- );
-
- for key in ms.iter_pk_pkh() {
- let key = match key {
- miniscript::miniscript::iter::PkPkh::PlainPubkey(pk) => pk,
- miniscript::miniscript::iter::PkPkh::HashedPubkey(pk) => pk,
- };
-
- insert_path(&key, Some(leaf_hash));
- }
- }
- }
-
- answer
- }
-}
-
#[cfg(test)]
mod test {
use std::str::FromStr;
#[test]
fn test_sh_wsh_sortedmulti_redeemscript() {
- use super::{AsDerived, DescriptorScripts};
+ use miniscript::psbt::PsbtInputExt;
let secp = Secp256k1::new();
let (descriptor, _) =
into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet).unwrap();
- let descriptor = descriptor.as_derived(0, &secp);
+ let descriptor = descriptor.at_derivation_index(0);
let script = Script::from_str("5321022f533b667e2ea3b36e21961c9fe9dca340fbe0af5210173a83ae0337ab20a57621026bb53a98e810bd0ee61a0ed1164ba6c024786d76554e793e202dc6ce9c78c4ea2102d5b8a7d66a41ffdb6f4c53d61994022e886b4f45001fb158b95c9164d45f8ca3210324b75eead2c1f9c60e8adeb5e7009fec7a29afcdb30d829d82d09562fe8bae8521032d34f8932200833487bd294aa219dcbe000b9f9b3d824799541430009f0fa55121037468f8ea99b6c64788398b5ad25480cad08f4b0d65be54ce3a55fd206b5ae4722103f72d3d96663b0ea99b0aeb0d7f273cab11a8de37885f1dddc8d9112adb87169357ae").unwrap();
- assert_eq!(descriptor.psbt_redeem_script(), Some(script.to_v0_p2wsh()));
- assert_eq!(descriptor.psbt_witness_script(), Some(script));
+ let mut psbt_input = psbt::Input::default();
+ psbt_input
+ .update_with_descriptor_unchecked(&descriptor)
+ .unwrap();
+
+ assert_eq!(psbt_input.redeem_script, Some(script.to_v0_p2wsh()));
+ assert_eq!(psbt_input.witness_script, Some(script));
}
}
use serde::ser::SerializeMap;
use serde::{Serialize, Serializer};
-use bitcoin::hashes::*;
+use bitcoin::hashes::{hash160, ripemd160, sha256};
use bitcoin::util::bip32::Fingerprint;
-use bitcoin::{PublicKey, XOnlyPublicKey};
+use bitcoin::{LockTime, PublicKey, Sequence, XOnlyPublicKey};
use miniscript::descriptor::{
- DescriptorPublicKey, DescriptorSinglePub, ShInner, SinglePubKey, SortedMultiVec, WshInner,
+ DescriptorPublicKey, ShInner, SinglePub, SinglePubKey, SortedMultiVec, WshInner,
+};
+use miniscript::hash256;
+use miniscript::{
+ Descriptor, Miniscript, Satisfier, ScriptContext, SigType, Terminal, ToPublicKey,
};
-use miniscript::{Descriptor, Miniscript, MiniscriptKey, Satisfier, ScriptContext, Terminal};
#[allow(unused_imports)]
use log::{debug, error, info, trace};
use crate::descriptor::ExtractPolicy;
use crate::keys::ExtScriptContext;
use crate::wallet::signer::{SignerId, SignersContainer};
-use crate::wallet::utils::{self, After, Older, SecpCtx};
+use crate::wallet::utils::{After, Older, SecpCtx};
use super::checksum::get_checksum;
use super::error::Error;
impl PkOrF {
fn from_key(k: &DescriptorPublicKey, secp: &SecpCtx) -> Self {
match k {
- DescriptorPublicKey::SinglePub(DescriptorSinglePub {
+ DescriptorPublicKey::Single(SinglePub {
key: SinglePubKey::FullKey(pk),
..
}) => PkOrF::Pubkey(*pk),
- DescriptorPublicKey::SinglePub(DescriptorSinglePub {
+ DescriptorPublicKey::Single(SinglePub {
key: SinglePubKey::XOnly(pk),
..
}) => PkOrF::XOnlyPubkey(*pk),
/// Double SHA256 preimage hash
Hash256Preimage {
/// The digest value
- hash: sha256d::Hash,
+ hash: hash256::Hash,
},
/// RIPEMD160 preimage hash
Ripemd160Preimage {
},
/// Absolute timeclock timestamp
AbsoluteTimelock {
- /// The timestamp value
- value: u32,
+ /// The timelock value
+ value: LockTime,
},
/// Relative timelock locktime
RelativeTimelock {
- /// The locktime value
- value: u32,
+ /// The timelock value
+ value: Sequence,
},
/// Multi-signature public keys with threshold count
Multisig {
}
/// An extra condition that must be satisfied but that is out of control of the user
-#[derive(Hash, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Serialize)]
+/// TODO: use `bitcoin::LockTime` and `bitcoin::Sequence`
+#[derive(Hash, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Default, Serialize)]
pub struct Condition {
/// Optional CheckSequenceVerify condition
#[serde(skip_serializing_if = "Option::is_none")]
- pub csv: Option<u32>,
+ pub csv: Option<Sequence>,
/// Optional timelock condition
#[serde(skip_serializing_if = "Option::is_none")]
- pub timelock: Option<u32>,
+ pub timelock: Option<LockTime>,
}
impl Condition {
- fn merge_nlocktime(a: u32, b: u32) -> Result<u32, PolicyError> {
- if (a < utils::BLOCKS_TIMELOCK_THRESHOLD) != (b < utils::BLOCKS_TIMELOCK_THRESHOLD) {
+ fn merge_nlocktime(a: LockTime, b: LockTime) -> Result<LockTime, PolicyError> {
+ if !a.is_same_unit(b) {
Err(PolicyError::MixedTimelockUnits)
+ } else if a > b {
+ Ok(a)
} else {
- Ok(max(a, b))
+ Ok(b)
}
}
- fn merge_nsequence(a: u32, b: u32) -> Result<u32, PolicyError> {
- let mask = utils::SEQUENCE_LOCKTIME_TYPE_FLAG | utils::SEQUENCE_LOCKTIME_MASK;
-
- let a = a & mask;
- let b = b & mask;
-
- if (a < utils::SEQUENCE_LOCKTIME_TYPE_FLAG) != (b < utils::SEQUENCE_LOCKTIME_TYPE_FLAG) {
+ fn merge_nsequence(a: Sequence, b: Sequence) -> Result<Sequence, PolicyError> {
+ if a.is_time_locked() != b.is_time_locked() {
Err(PolicyError::MixedTimelockUnits)
} else {
Ok(max(a, b))
}
fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
+ // For consistency we always compute the key hash in "ecdsa" form (with the leading sign
+ // prefix) even if we are in a taproot descriptor. We just want some kind of unique identifier
+ // for a key, so it doesn't really matter how the identifier is computed.
match key {
- DescriptorPublicKey::SinglePub(DescriptorSinglePub {
+ DescriptorPublicKey::Single(SinglePub {
key: SinglePubKey::FullKey(pk),
..
- }) => pk.to_pubkeyhash().into(),
- DescriptorPublicKey::SinglePub(DescriptorSinglePub {
+ }) => pk.to_pubkeyhash(SigType::Ecdsa).into(),
+ DescriptorPublicKey::Single(SinglePub {
key: SinglePubKey::XOnly(pk),
..
- }) => pk.to_pubkeyhash().into(),
+ }) => pk.to_pubkeyhash(SigType::Ecdsa).into(),
DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint(secp).into(),
}
}
) -> bool {
//TODO check signature validity
psbt.inputs.iter().all(|input| match key {
- DescriptorPublicKey::SinglePub(DescriptorSinglePub { key, .. }) => check(input, key),
+ DescriptorPublicKey::Single(SinglePub { key, .. }) => check(input, key),
DescriptorPublicKey::XPub(xpub) => {
//TODO check actual derivation matches
match extract(input, xpub.root_fingerprint(secp)) {
Some(Ctx::make_signature(pubkey_hash, signers, build_sat, secp))
}
Terminal::After(value) => {
- let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into();
+ let mut policy: Policy = SatisfiableItem::AbsoluteTimelock {
+ value: value.into(),
+ }
+ .into();
policy.contribution = Satisfaction::Complete {
condition: Condition {
- timelock: Some(*value),
+ timelock: Some(value.into()),
csv: None,
},
};
} = build_sat
{
let after = After::new(Some(current_height), false);
- let after_sat = Satisfier::<bitcoin::PublicKey>::check_after(&after, *value);
- let inputs_sat = psbt_inputs_sat(psbt)
- .all(|sat| Satisfier::<bitcoin::PublicKey>::check_after(&sat, *value));
+ let after_sat =
+ Satisfier::<bitcoin::PublicKey>::check_after(&after, value.into());
+ let inputs_sat = psbt_inputs_sat(psbt).all(|sat| {
+ Satisfier::<bitcoin::PublicKey>::check_after(&sat, value.into())
+ });
if after_sat && inputs_sat {
policy.satisfaction = policy.contribution.clone();
}
Policy::make_thresh(mapped, threshold)?
}
+
+ // Unsupported
+ Terminal::RawPkH(_) => None,
})
}
}
use crate::descriptor::{ExtractPolicy, IntoWalletDescriptor};
use super::*;
- use crate::descriptor::derived::AsDerived;
use crate::descriptor::policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh};
use crate::keys::{DescriptorKey, IntoDescriptorKey};
use crate::wallet::signer::SignersContainer;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::util::bip32;
use bitcoin::Network;
- use miniscript::DescriptorTrait;
use std::str::FromStr;
use std::sync::Arc;
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
- let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
- let policy = single_key
+ let policy = wallet_desc
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap()
.unwrap();
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
- let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
- let policy = single_key
+ let policy = wallet_desc
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap()
.unwrap();
- assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
+ assert!(matches!(policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == fingerprint));
assert!(
- matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
+ matches!(policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
);
}
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
- let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
- let policy = single_key
+ let policy = wallet_desc
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap()
.unwrap();
assert!(
- matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
+ matches!(policy.item, Multisig { keys, threshold } if threshold == 1
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
);
assert!(
- matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
- && m == &1
+ matches!(policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == 2
+ && m == 1
&& items.len() == 2
&& conditions.contains_key(&vec![0])
&& conditions.contains_key(&vec![1])
&& m == &2
&& items.len() == 3
&& conditions.get(&vec![0,1]).unwrap().iter().next().unwrap().csv.is_none()
- && conditions.get(&vec![0,2]).unwrap().iter().next().unwrap().csv == Some(sequence)
- && conditions.get(&vec![1,2]).unwrap().iter().next().unwrap().csv == Some(sequence)
+ && conditions.get(&vec![0,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
+ && conditions.get(&vec![1,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
)
);
}
.unwrap();
let addr = wallet_desc
- .as_derived(0, &secp)
+ .at_derivation_index(0)
.address(Network::Testnet)
.unwrap();
assert_eq!(
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
let addr = wallet_desc
- .as_derived(0, &secp)
+ .at_derivation_index(0)
.address(Network::Testnet)
.unwrap();
assert_eq!(
use std::str::FromStr;
use super::*;
- use crate::descriptor::derived::AsDerived;
use crate::descriptor::{DescriptorError, DescriptorMeta};
use crate::keys::ValidNetworks;
use bitcoin::network::constants::Network::Regtest;
- use bitcoin::secp256k1::Secp256k1;
- use miniscript::descriptor::{DescriptorPublicKey, DescriptorTrait, KeyMap};
+ use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
use miniscript::Descriptor;
// BIP44 `pkh(key/44'/{0,1}'/0'/{0,1}/*)`
is_fixed: bool,
expected: &[&str],
) {
- let secp = Secp256k1::new();
-
let (desc, _key_map, _networks) = desc.unwrap();
assert_eq!(desc.is_witness(), is_witness);
- assert_eq!(!desc.is_deriveable(), is_fixed);
+ assert_eq!(!desc.has_wildcard(), is_fixed);
for i in 0..expected.len() {
let index = i as u32;
- let child_desc = if !desc.is_deriveable() {
- desc.as_derived_fixed(&secp)
+ let child_desc = if !desc.has_wildcard() {
+ desc.at_derivation_index(0)
} else {
- desc.as_derived(index, &secp)
+ desc.at_derivation_index(index)
};
let address = child_desc.address(Regtest).unwrap();
assert_eq!(address.to_string(), *expected.get(i).unwrap());
Encode(bitcoin::consensus::encode::Error),
/// Miniscript error
Miniscript(miniscript::Error),
+ /// Miniscript PSBT error
+ MiniscriptPsbt(MiniscriptPsbtError),
/// BIP32 error
Bip32(bitcoin::util::bip32::Error),
/// An ECDSA error
Rusqlite(rusqlite::Error),
}
+/// Errors returned by miniscript when updating inconsistent PSBTs
+#[derive(Debug, Clone)]
+pub enum MiniscriptPsbtError {
+ Conversion(miniscript::descriptor::ConversionError),
+ UtxoUpdate(miniscript::psbt::UtxoUpdateError),
+ OutputUpdate(miniscript::psbt::OutputUpdateError),
+}
+
/// Represents the last failed [`crate::blockchain::WalletSync`] sync attempt in which we were short
/// on cached `scriptPubKey`s.
#[derive(Debug)]
impl_error!(bitcoin::consensus::encode::Error, Encode);
impl_error!(miniscript::Error, Miniscript);
+impl_error!(MiniscriptPsbtError, MiniscriptPsbt);
impl_error!(bitcoin::util::bip32::Error, Bip32);
impl_error!(bitcoin::secp256k1::Error, Secp256k1);
impl_error!(serde_json::Error, Json);
use miniscript::descriptor::{Descriptor, DescriptorXKey, Wildcard};
pub use miniscript::descriptor::{
- DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorSinglePub, KeyMap,
- SinglePubKey, SortedMultiVec,
+ DescriptorPublicKey, DescriptorSecretKey, KeyMap, SinglePriv, SinglePub, SinglePubKey,
+ SortedMultiVec,
};
pub use miniscript::ScriptContext;
use miniscript::{Miniscript, Terminal};
let mut key_map = KeyMap::with_capacity(1);
let public = secret
- .as_public(secp)
+ .to_public(secp)
.map_err(|e| miniscript::Error::Unexpected(e.to_string()))?;
key_map.insert(public.clone(), secret);
/// use bdk::bitcoin::PublicKey;
///
/// use bdk::keys::{
-/// mainnet_network, DescriptorKey, DescriptorPublicKey, DescriptorSinglePub,
-/// IntoDescriptorKey, KeyError, ScriptContext, SinglePubKey,
+/// mainnet_network, DescriptorKey, DescriptorPublicKey, IntoDescriptorKey, KeyError,
+/// ScriptContext, SinglePub, SinglePubKey,
/// };
///
/// pub struct MyKeyType {
/// impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for MyKeyType {
/// fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
/// Ok(DescriptorKey::from_public(
-/// DescriptorPublicKey::SinglePub(DescriptorSinglePub {
+/// DescriptorPublicKey::Single(SinglePub {
/// origin: None,
/// key: SinglePubKey::FullKey(self.pubkey),
/// }),
impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for DescriptorPublicKey {
fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
let networks = match self {
- DescriptorPublicKey::SinglePub(_) => any_network(),
+ DescriptorPublicKey::Single(_) => any_network(),
DescriptorPublicKey::XPub(DescriptorXKey { xkey, .. })
if xkey.network == Network::Bitcoin =>
{
impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for PublicKey {
fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
- DescriptorPublicKey::SinglePub(DescriptorSinglePub {
+ DescriptorPublicKey::Single(SinglePub {
key: SinglePubKey::FullKey(self),
origin: None,
})
impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for XOnlyPublicKey {
fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
- DescriptorPublicKey::SinglePub(DescriptorSinglePub {
+ DescriptorPublicKey::Single(SinglePub {
key: SinglePubKey::XOnly(self),
origin: None,
})
impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for DescriptorSecretKey {
fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
let networks = match &self {
- DescriptorSecretKey::SinglePriv(sk) if sk.key.network == Network::Bitcoin => {
+ DescriptorSecretKey::Single(sk) if sk.key.network == Network::Bitcoin => {
mainnet_network()
}
DescriptorSecretKey::XPrv(DescriptorXKey { xkey, .. })
impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for PrivateKey {
fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
- DescriptorSecretKey::SinglePriv(DescriptorSinglePriv {
+ DescriptorSecretKey::Single(SinglePriv {
key: self,
origin: None,
})
+// Bitcoin Dev Kit
+//
+// 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.
+
use crate::testutils::TestIncomingTx;
use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::hashes::hex::{FromHex, ToHex};
use bitcoin::hashes::sha256d;
-use bitcoin::{Address, Amount, Script, Transaction, Txid, Witness};
+use bitcoin::{Address, Amount, PackedLockTime, Script, Sequence, Transaction, Txid, Witness};
pub use bitcoincore_rpc::bitcoincore_rpc_json::AddressType;
pub use bitcoincore_rpc::{Auth, Client as RpcClient, RpcApi};
use core::str::FromStr;
if let Some(true) = meta_tx.replaceable {
// for some reason core doesn't set this field right
for input in &mut tx.input {
- input.sequence = 0xFFFFFFFD;
+ input.sequence = Sequence(0xFFFFFFFD);
}
}
use bitcoin::blockdata::script::Builder;
use bitcoin::blockdata::transaction::{OutPoint, TxIn, TxOut};
use bitcoin::hash_types::{BlockHash, TxMerkleNode};
+ use bitcoin::hashes::Hash;
let block_template: serde_json::Value = self
.call("getblocktemplate", &[json!({"rules": ["segwit"]})])
block_template["previousblockhash"].as_str().unwrap(),
)
.unwrap(),
- merkle_root: TxMerkleNode::default(),
+ merkle_root: TxMerkleNode::all_zeros(),
time: block_template["curtime"].as_u64().unwrap() as u32,
bits: u32::from_str_radix(block_template["bits"].as_str().unwrap(), 16).unwrap(),
nonce: 0,
debug!("header: {:#?}", header);
let height = block_template["height"].as_u64().unwrap() as i64;
- let witness_reserved_value: Vec<u8> = sha256d::Hash::default().as_ref().into();
+ let witness_reserved_value: Vec<u8> = sha256d::Hash::all_zeros().as_ref().into();
// burn block subsidy and fees, not a big deal
let mut coinbase_tx = Transaction {
version: 1,
- lock_time: 0,
+ lock_time: PackedLockTime(0),
input: vec![TxIn {
previous_output: OutPoint::null(),
script_sig: Builder::new().push_int(height).into_script(),
- sequence: 0xFFFFFFFF,
+ sequence: Sequence(0xFFFFFFFF),
witness: Witness::from_vec(vec![witness_reserved_value]),
}],
output: vec![],
// 5. Verify 25_000 sats are received by test bitcoind node taproot wallet
let taproot_balance = taproot_wallet_client.get_balance(None, None).unwrap();
- assert_eq!(taproot_balance.as_sat(), 25_000, "node has incorrect taproot wallet balance");
+ assert_eq!(taproot_balance.to_sat(), 25_000, "node has incorrect taproot wallet balance");
}
#[test]
macro_rules! testutils {
( @external $descriptors:expr, $child:expr ) => ({
use $crate::bitcoin::secp256k1::Secp256k1;
- use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
-
- use $crate::descriptor::AsDerived;
+ use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
let secp = Secp256k1::new();
let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.0).expect("Failed to parse descriptor in `testutils!(@external)`").0;
- parsed.as_derived($child, &secp).address(bitcoin::Network::Regtest).expect("No address form")
+ parsed.at_derivation_index($child).address(bitcoin::Network::Regtest).expect("No address form")
});
( @internal $descriptors:expr, $child:expr ) => ({
use $crate::bitcoin::secp256k1::Secp256k1;
- use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
-
- use $crate::descriptor::AsDerived;
+ use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
let secp = Secp256k1::new();
let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0;
- parsed.as_derived($child, &secp).address($crate::bitcoin::Network::Regtest).expect("No address form")
+ parsed.at_derivation_index($child).address($crate::bitcoin::Network::Regtest).expect("No address form")
});
( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) });
( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) });
( @descriptors ( $external_descriptor:expr ) $( ( $internal_descriptor:expr ) )? $( ( @keys $( $keys:tt )* ) )* ) => ({
use std::str::FromStr;
use std::collections::HashMap;
+ use std::convert::Infallible;
+
use $crate::miniscript::descriptor::Descriptor;
use $crate::miniscript::TranslatePk;
+ struct Translator {
+ keys: HashMap<&'static str, (String, Option<String>, Option<String>)>,
+ is_internal: bool,
+ }
+
+ impl $crate::miniscript::Translator<String, String, Infallible> for Translator {
+ fn pk(&mut self, pk: &String) -> Result<String, Infallible> {
+ match self.keys.get(pk.as_str()) {
+ Some((key, ext_path, int_path)) => {
+ let path = if self.is_internal { int_path } else { ext_path };
+ Ok(format!("{}{}", key, path.clone().unwrap_or_default()))
+ }
+ None => Ok(pk.clone()),
+ }
+ }
+ fn sha256(&mut self, sha256: &String) -> Result<String, Infallible> { Ok(sha256.clone()) }
+ fn hash256(&mut self, hash256: &String) -> Result<String, Infallible> { Ok(hash256.clone()) }
+ fn ripemd160(&mut self, ripemd160: &String) -> Result<String, Infallible> { Ok(ripemd160.clone()) }
+ fn hash160(&mut self, hash160: &String) -> Result<String, Infallible> { Ok(hash160.clone()) }
+ }
+
#[allow(unused_assignments, unused_mut)]
- let mut keys: HashMap<&'static str, (String, Option<String>, Option<String>)> = HashMap::new();
+ let mut keys = HashMap::new();
$(
keys = testutils!{ @keys $( $keys )* };
)*
- let external: Descriptor<String> = FromStr::from_str($external_descriptor).unwrap();
- let external: Descriptor<String> = external.translate_pk_infallible::<_, _>(|k| {
- if let Some((key, ext_path, _)) = keys.get(&k.as_str()) {
- format!("{}{}", key, ext_path.as_ref().unwrap_or(&"".into()))
- } else {
- k.clone()
- }
- }, |kh| {
- if let Some((key, ext_path, _)) = keys.get(&kh.as_str()) {
- format!("{}{}", key, ext_path.as_ref().unwrap_or(&"".into()))
- } else {
- kh.clone()
- }
+ let mut translator = Translator { keys, is_internal: false };
- });
+ let external: Descriptor<String> = FromStr::from_str($external_descriptor).unwrap();
+ let external = external.translate_pk(&mut translator).expect("Infallible conversion");
let external = external.to_string();
- let internal = None::<String>$(.or({
- let string_internal: Descriptor<String> = FromStr::from_str($internal_descriptor).unwrap();
+ translator.is_internal = true;
- let string_internal: Descriptor<String> = string_internal.translate_pk_infallible::<_, _>(|k| {
- if let Some((key, _, int_path)) = keys.get(&k.as_str()) {
- format!("{}{}", key, int_path.as_ref().unwrap_or(&"".into()))
- } else {
- k.clone()
- }
- }, |kh| {
- if let Some((key, _, int_path)) = keys.get(&kh.as_str()) {
- format!("{}{}", key, int_path.as_ref().unwrap_or(&"".into()))
- } else {
- kh.clone()
- }
- });
- Some(string_internal.to_string())
+ let internal = None::<String>$(.or({
+ let internal: Descriptor<String> = FromStr::from_str($internal_descriptor).unwrap();
+ let internal = internal.translate_pk(&mut translator).expect("Infallible conversion");
+ Some(internal.to_string())
}))?;
(external, internal)
}
/// A [`Utxo`] with its `satisfaction_weight`.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WeightedUtxo {
/// The weight of the witness data and `scriptSig` expressed in [weight units]. This is used to
/// properly maintain the feerate when adding this input to a transaction during coin selection.
pub utxo: Utxo,
}
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Eq)]
/// An unspent transaction output (UTXO).
pub enum Utxo {
/// A UTXO owned by the local wallet.
}
/// A wallet transaction
-#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct TransactionDetails {
/// Optional transaction
pub transaction: Option<Transaction>,
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,
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};
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::derived::AsDerived;
use crate::descriptor::policy::BuildSatisfaction;
use crate::descriptor::{
- get_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DerivedDescriptorMeta,
- DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor,
- Policy, XKeyUtils,
+ get_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;
/// 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,
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
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();
// 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,
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,
/// 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,
};
}
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,
};
// 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,
};
// 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
(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(),
))
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
)))
}
let mut tx = Transaction {
version,
- lock_time,
+ lock_time: lock_time.into(),
input: vec![],
output: vec![],
};
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
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);
}
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`
}
}
- 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)?,
};
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)?,
};
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(());
}
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,
)?;
}
}
- // 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)
}
};
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 {
- 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);
- }
+ let derived_descriptor = desc.at_derivation_index(child);
- 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)? {
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);
-
- // 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);
- }
+ // 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
+ );
+
+ 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)?;
}
}
}
// 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 =
#[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;
// 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]
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]
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]
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]
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]
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());
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();
}
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]
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());
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();
}
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]
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();
}
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]
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]
.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]
.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]
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();
.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"
);
}
#[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();
#[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();
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};
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,
}))
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 };
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()
}
};
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)),
),
}
/// 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,
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};
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,
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,
}
/// 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
}
///
/// 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
}
///
/// 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
}
#[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,
}
}
}
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(),
},
LocalUtxo {
outpoint: OutPoint {
- txid: Default::default(),
+ txid: bitcoin::Txid::from_inner([0; 32]),
vout: 1,
},
txout: Default::default(),
// 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
impl IsDust for u64 {
fn is_dust(&self, script: &Script) -> bool {
- *self < script.dust_value().as_sat()
+ *self < script.dust_value().to_sat()
}
}
}
}
-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;
}
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
}
}
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
}
#[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]
#[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);
}