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