Merge branch 'release/0.4.0'
This commit is contained in:
commit
5e352489a0
10
CHANGELOG.md
10
CHANGELOG.md
@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [v0.4.0] - [v0.3.0]
|
||||||
|
|
||||||
### Keys
|
### Keys
|
||||||
#### Changed
|
#### Changed
|
||||||
- Renamed `DerivableKey::add_metadata()` to `DerivableKey::into_descriptor_key()`
|
- Renamed `DerivableKey::add_metadata()` to `DerivableKey::into_descriptor_key()`
|
||||||
@ -16,13 +18,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
#### Removed
|
#### Removed
|
||||||
- Removed the `parse_descriptor` example, since it wasn't demostrating any bdk-specific API anymore.
|
- Removed the `parse_descriptor` example, since it wasn't demonstrating any bdk-specific API anymore.
|
||||||
#### Changed
|
#### Changed
|
||||||
- Updated `bitcoin` to `0.26`, `miniscript` to `5.1` and `electrum-client` to `0.6`
|
- Updated `bitcoin` to `0.26`, `miniscript` to `5.1` and `electrum-client` to `0.6`
|
||||||
#### Added
|
#### Added
|
||||||
- Added support for the `signet` network (issue #62)
|
- Added support for the `signet` network (issue #62)
|
||||||
|
|
||||||
#### Added
|
|
||||||
- Added a function to get the version of BDK at runtime
|
- Added a function to get the version of BDK at runtime
|
||||||
|
|
||||||
### Wallet
|
### Wallet
|
||||||
@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Policy
|
### Policy
|
||||||
#### Changed
|
#### Changed
|
||||||
- Removed unneeded `Result<(), PolicyError>` return type for `Satisfaction::finalize()`
|
- Removed unneeded `Result<(), PolicyError>` return type for `Satisfaction::finalize()`
|
||||||
|
- Removed the `TooManyItemsSelected` policy error (see commit message for more details)
|
||||||
|
|
||||||
## [v0.3.0] - [v0.2.0]
|
## [v0.3.0] - [v0.2.0]
|
||||||
|
|
||||||
@ -269,7 +270,8 @@ final transaction is created by calling `finish` on the builder.
|
|||||||
- Use `MemoryDatabase` in the compiler example
|
- Use `MemoryDatabase` in the compiler example
|
||||||
- Make the REPL return JSON
|
- Make the REPL return JSON
|
||||||
|
|
||||||
[unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.2.0...HEAD
|
[unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.4.0...HEAD
|
||||||
[0.1.0-beta.1]: https://github.com/bitcoindevkit/bdk/compare/96c87ea5...0.1.0-beta.1
|
[0.1.0-beta.1]: https://github.com/bitcoindevkit/bdk/compare/96c87ea5...0.1.0-beta.1
|
||||||
[v0.2.0]: https://github.com/bitcoindevkit/bdk/compare/0.1.0-beta.1...v0.2.0
|
[v0.2.0]: https://github.com/bitcoindevkit/bdk/compare/0.1.0-beta.1...v0.2.0
|
||||||
[v0.3.0]: https://github.com/bitcoindevkit/bdk/compare/v0.2.0...v0.3.0
|
[v0.3.0]: https://github.com/bitcoindevkit/bdk/compare/v0.2.0...v0.3.0
|
||||||
|
[v0.4.0]: https://github.com/bitcoindevkit/bdk/compare/v0.3.0...v0.4.0
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk"
|
name = "bdk"
|
||||||
version = "0.3.1-dev"
|
version = "0.4.1-dev"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
|
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
|
||||||
homepage = "https://bitcoindevkit.org"
|
homepage = "https://bitcoindevkit.org"
|
||||||
@ -12,7 +12,7 @@ readme = "README.md"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bdk-macros = { path = "./macros" }
|
bdk-macros = "^0.3"
|
||||||
log = "^0.4"
|
log = "^0.4"
|
||||||
miniscript = "5.1"
|
miniscript = "5.1"
|
||||||
bitcoin = { version = "^0.26", features = ["use-serde"] }
|
bitcoin = { version = "^0.26", features = ["use-serde"] }
|
||||||
@ -59,8 +59,8 @@ test-electrum = ["electrum"]
|
|||||||
test-md-docs = ["electrum"]
|
test-md-docs = ["electrum"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bdk-testutils = { path = "./testutils" }
|
bdk-testutils = "^0.3"
|
||||||
bdk-testutils-macros = { path = "./testutils-macros" }
|
bdk-testutils-macros = "^0.3"
|
||||||
serial_test = "0.4"
|
serial_test = "0.4"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk-macros"
|
name = "bdk-macros"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
authors = ["Alekos Filini <alekos.filini@gmail.com>"]
|
authors = ["Alekos Filini <alekos.filini@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
homepage = "https://bitcoindevkit.org"
|
homepage = "https://bitcoindevkit.org"
|
||||||
|
@ -31,6 +31,8 @@ pub enum Error {
|
|||||||
InvalidHDKeyPath,
|
InvalidHDKeyPath,
|
||||||
/// The provided descriptor doesn't match its checksum
|
/// The provided descriptor doesn't match its checksum
|
||||||
InvalidDescriptorChecksum,
|
InvalidDescriptorChecksum,
|
||||||
|
/// The descriptor contains hardened derivation steps on public extended keys
|
||||||
|
HardenedDerivationXpub,
|
||||||
|
|
||||||
/// Error thrown while working with [`keys`](crate::keys)
|
/// Error thrown while working with [`keys`](crate::keys)
|
||||||
Key(crate::keys::KeyError),
|
Key(crate::keys::KeyError),
|
||||||
|
@ -198,6 +198,36 @@ impl IntoWalletDescriptor for DescriptorTemplateOut {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wrapper for `IntoWalletDescriptor` that performs additional checks on the keys contained in the
|
||||||
|
/// descriptor
|
||||||
|
pub(crate) fn into_wallet_descriptor_checked<T: IntoWalletDescriptor>(
|
||||||
|
inner: T,
|
||||||
|
secp: &SecpCtx,
|
||||||
|
network: Network,
|
||||||
|
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
|
||||||
|
let (descriptor, keymap) = inner.into_wallet_descriptor(secp, network)?;
|
||||||
|
|
||||||
|
// Ensure the keys don't contain any hardened derivation steps or hardened wildcards
|
||||||
|
let descriptor_contains_hardened_steps = descriptor.for_any_key(|k| {
|
||||||
|
if let DescriptorPublicKey::XPub(DescriptorXKey {
|
||||||
|
derivation_path,
|
||||||
|
wildcard,
|
||||||
|
..
|
||||||
|
}) = k.as_key()
|
||||||
|
{
|
||||||
|
return *wildcard == Wildcard::Hardened
|
||||||
|
|| derivation_path.into_iter().any(ChildNumber::is_hardened);
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
});
|
||||||
|
if descriptor_contains_hardened_steps {
|
||||||
|
return Err(DescriptorError::HardenedDerivationXpub);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((descriptor, keymap))
|
||||||
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// Used internally mainly by the `descriptor!()` and `fragment!()` macros
|
/// Used internally mainly by the `descriptor!()` and `fragment!()` macros
|
||||||
pub trait CheckMiniscript<Ctx: miniscript::ScriptContext> {
|
pub trait CheckMiniscript<Ctx: miniscript::ScriptContext> {
|
||||||
@ -740,4 +770,18 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(wallet_desc, wallet_desc2)
|
assert_eq!(wallet_desc, wallet_desc2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_into_wallet_descriptor_checked() {
|
||||||
|
let secp = Secp256k1::new();
|
||||||
|
|
||||||
|
let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)";
|
||||||
|
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(
|
||||||
|
result.unwrap_err(),
|
||||||
|
DescriptorError::HardenedDerivationXpub
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
//! # Ok::<(), bdk::Error>(())
|
//! # Ok::<(), bdk::Error>(())
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use std::cmp::{max, Ordering};
|
use std::cmp::max;
|
||||||
use std::collections::{BTreeMap, HashSet, VecDeque};
|
use std::collections::{BTreeMap, HashSet, VecDeque};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
@ -506,13 +506,11 @@ impl Condition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Errors that can happen while extracting and manipulating policies
|
/// Errors that can happen while extracting and manipulating policies
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum PolicyError {
|
pub enum PolicyError {
|
||||||
/// Not enough items are selected to satisfy a [`SatisfiableItem::Thresh`]
|
/// Not enough items are selected to satisfy a [`SatisfiableItem::Thresh`] or a [`SatisfiableItem::Multisig`]
|
||||||
NotEnoughItemsSelected(String),
|
NotEnoughItemsSelected(String),
|
||||||
/// Too many items are selected to satisfy a [`SatisfiableItem::Thresh`]
|
/// Index out of range for an item to satisfy a [`SatisfiableItem::Thresh`] or a [`SatisfiableItem::Multisig`]
|
||||||
TooManyItemsSelected(String),
|
|
||||||
/// Index out of range for an item to satisfy a [`SatisfiableItem::Thresh`]
|
|
||||||
IndexOutOfRange(usize),
|
IndexOutOfRange(usize),
|
||||||
/// Can not add to an item that is [`Satisfaction::None`] or [`Satisfaction::Complete`]
|
/// Can not add to an item that is [`Satisfaction::None`] or [`Satisfaction::Complete`]
|
||||||
AddOnLeaf,
|
AddOnLeaf,
|
||||||
@ -644,10 +642,10 @@ impl Policy {
|
|||||||
SatisfiableItem::Thresh { items, threshold } if items.len() == *threshold => {
|
SatisfiableItem::Thresh { items, threshold } if items.len() == *threshold => {
|
||||||
(0..*threshold).collect()
|
(0..*threshold).collect()
|
||||||
}
|
}
|
||||||
|
SatisfiableItem::Multisig { keys, .. } => (0..keys.len()).collect(),
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
};
|
};
|
||||||
let selected = match path.get(&self.id) {
|
let selected = match path.get(&self.id) {
|
||||||
_ if !default.is_empty() => &default,
|
|
||||||
Some(arr) => arr,
|
Some(arr) => arr,
|
||||||
_ => &default,
|
_ => &default,
|
||||||
};
|
};
|
||||||
@ -668,14 +666,8 @@ impl Policy {
|
|||||||
// if we have something, make sure we have enough items. note that the user can set
|
// if we have something, make sure we have enough items. note that the user can set
|
||||||
// an empty value for this step in case of n-of-n, because `selected` is set to all
|
// an empty value for this step in case of n-of-n, because `selected` is set to all
|
||||||
// the elements above
|
// the elements above
|
||||||
match selected.len().cmp(threshold) {
|
if selected.len() < *threshold {
|
||||||
Ordering::Less => {
|
return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
|
||||||
return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()))
|
|
||||||
}
|
|
||||||
Ordering::Greater => {
|
|
||||||
return Err(PolicyError::TooManyItemsSelected(self.id.clone()))
|
|
||||||
}
|
|
||||||
Ordering::Equal => (),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the selected items, see if there are conflicting requirements
|
// check the selected items, see if there are conflicting requirements
|
||||||
@ -690,7 +682,16 @@ impl Policy {
|
|||||||
|
|
||||||
Ok(requirements)
|
Ok(requirements)
|
||||||
}
|
}
|
||||||
_ if !selected.is_empty() => Err(PolicyError::TooManyItemsSelected(self.id.clone())),
|
SatisfiableItem::Multisig { keys, threshold } => {
|
||||||
|
if selected.len() < *threshold {
|
||||||
|
return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
|
||||||
|
}
|
||||||
|
if let Some(item) = selected.iter().find(|i| **i >= keys.len()) {
|
||||||
|
return Err(PolicyError::IndexOutOfRange(*item));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Condition::default())
|
||||||
|
}
|
||||||
SatisfiableItem::AbsoluteTimelock { value } => Ok(Condition {
|
SatisfiableItem::AbsoluteTimelock { value } => Ok(Condition {
|
||||||
csv: None,
|
csv: None,
|
||||||
timelock: Some(*value),
|
timelock: Some(*value),
|
||||||
@ -1257,4 +1258,50 @@ mod test {
|
|||||||
//
|
//
|
||||||
// // TODO how should this merge timelocks?
|
// // TODO how should this merge timelocks?
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_condition_multisig() {
|
||||||
|
let secp = Secp256k1::gen_new();
|
||||||
|
|
||||||
|
let (_, pk0, _) = setup_keys(TPRV0_STR);
|
||||||
|
let (_, pk1, _) = setup_keys(TPRV1_STR);
|
||||||
|
|
||||||
|
let desc = descriptor!(wsh(multi(1, pk0, pk1))).unwrap();
|
||||||
|
let (wallet_desc, keymap) = desc
|
||||||
|
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||||
|
.unwrap();
|
||||||
|
let signers = keymap.into();
|
||||||
|
|
||||||
|
let policy = wallet_desc
|
||||||
|
.extract_policy(&signers, &secp)
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// no args, choose the default
|
||||||
|
let no_args = policy.get_condition(&vec![].into_iter().collect());
|
||||||
|
assert_eq!(no_args, Ok(Condition::default()));
|
||||||
|
|
||||||
|
// enough args
|
||||||
|
let eq_thresh =
|
||||||
|
policy.get_condition(&vec![(policy.id.clone(), vec![0])].into_iter().collect());
|
||||||
|
assert_eq!(eq_thresh, Ok(Condition::default()));
|
||||||
|
|
||||||
|
// more args, it doesn't really change anything
|
||||||
|
let gt_thresh =
|
||||||
|
policy.get_condition(&vec![(policy.id.clone(), vec![0, 1])].into_iter().collect());
|
||||||
|
assert_eq!(gt_thresh, Ok(Condition::default()));
|
||||||
|
|
||||||
|
// not enough args, error
|
||||||
|
let lt_thresh =
|
||||||
|
policy.get_condition(&vec![(policy.id.clone(), vec![])].into_iter().collect());
|
||||||
|
assert_eq!(
|
||||||
|
lt_thresh,
|
||||||
|
Err(PolicyError::NotEnoughItemsSelected(policy.id.clone()))
|
||||||
|
);
|
||||||
|
|
||||||
|
// index out of range
|
||||||
|
let out_of_range =
|
||||||
|
policy.get_condition(&vec![(policy.id.clone(), vec![5])].into_iter().collect());
|
||||||
|
assert_eq!(out_of_range, Err(PolicyError::IndexOutOfRange(5)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
//! interact with the bitcoin P2P network.
|
//! interact with the bitcoin P2P network.
|
||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ```toml
|
||||||
//! bdk = "0.3.0"
|
//! bdk = "0.4.0"
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Sync the balance of a descriptor
|
//! ## Sync the balance of a descriptor
|
||||||
|
@ -66,8 +66,9 @@ use crate::blockchain::{Blockchain, Progress};
|
|||||||
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
|
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
|
||||||
use crate::descriptor::derived::AsDerived;
|
use crate::descriptor::derived::AsDerived;
|
||||||
use crate::descriptor::{
|
use crate::descriptor::{
|
||||||
get_checksum, DerivedDescriptor, DerivedDescriptorMeta, DescriptorMeta, DescriptorScripts,
|
get_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DerivedDescriptorMeta,
|
||||||
ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
|
DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor,
|
||||||
|
Policy, XKeyUtils,
|
||||||
};
|
};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::psbt::PSBTUtils;
|
use crate::psbt::PSBTUtils;
|
||||||
@ -134,7 +135,7 @@ where
|
|||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
|
|
||||||
let (descriptor, keymap) = descriptor.into_wallet_descriptor(&secp, network)?;
|
let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)?;
|
||||||
database.check_descriptor_checksum(
|
database.check_descriptor_checksum(
|
||||||
KeychainKind::External,
|
KeychainKind::External,
|
||||||
get_checksum(&descriptor.to_string())?.as_bytes(),
|
get_checksum(&descriptor.to_string())?.as_bytes(),
|
||||||
@ -143,7 +144,7 @@ where
|
|||||||
let (change_descriptor, change_signers) = match change_descriptor {
|
let (change_descriptor, change_signers) = match change_descriptor {
|
||||||
Some(desc) => {
|
Some(desc) => {
|
||||||
let (change_descriptor, change_keymap) =
|
let (change_descriptor, change_keymap) =
|
||||||
desc.into_wallet_descriptor(&secp, network)?;
|
into_wallet_descriptor_checked(desc, &secp, network)?;
|
||||||
database.check_descriptor_checksum(
|
database.check_descriptor_checksum(
|
||||||
KeychainKind::Internal,
|
KeychainKind::Internal,
|
||||||
get_checksum(&change_descriptor.to_string())?.as_bytes(),
|
get_checksum(&change_descriptor.to_string())?.as_bytes(),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk-testutils-macros"
|
name = "bdk-testutils-macros"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
authors = ["Alekos Filini <alekos.filini@gmail.com>"]
|
authors = ["Alekos Filini <alekos.filini@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
homepage = "https://bitcoindevkit.org"
|
homepage = "https://bitcoindevkit.org"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk-testutils"
|
name = "bdk-testutils"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
authors = ["Alekos Filini <alekos.filini@gmail.com>"]
|
authors = ["Alekos Filini <alekos.filini@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
homepage = "https://bitcoindevkit.org"
|
homepage = "https://bitcoindevkit.org"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user