From 0fd57db8a893ae2621ca5f3cc03c6393c3a65646 Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Thu, 18 Aug 2022 11:39:16 +0200 Subject: [PATCH] Use the descriptor newtype instead of the raw miniscript Descriptor type --- src/bitcoin/d/mod.rs | 10 +++--- src/commands/mod.rs | 20 ++++------- src/config.rs | 23 ++++++------ src/database/sqlite/mod.rs | 47 +++++++++---------------- src/database/sqlite/schema.rs | 11 +++--- src/database/sqlite/utils.rs | 9 ++--- src/descriptors.rs | 66 ++++++++++++++++++++++++++++++++--- src/lib.rs | 5 +-- src/testutils.rs | 3 +- tests/fixtures.py | 2 +- 10 files changed, 112 insertions(+), 84 deletions(-) diff --git a/src/bitcoin/d/mod.rs b/src/bitcoin/d/mod.rs index e2edcca2..2127cdb4 100644 --- a/src/bitcoin/d/mod.rs +++ b/src/bitcoin/d/mod.rs @@ -1,7 +1,7 @@ ///! Implementation of the Bitcoin interface using bitcoind. ///! ///! We use the RPC interface and a watchonly descriptor wallet. -use crate::{bitcoin::BlockChainTip, config}; +use crate::{bitcoin::BlockChainTip, config, descriptors::InheritanceDescriptor}; use std::{collections::HashSet, convert::TryInto, fs, io, str::FromStr, time::Duration}; @@ -10,7 +10,7 @@ use jsonrpc::{ client::Client, simple_http::{self, SimpleHttpTransport}, }; -use miniscript::{bitcoin, Descriptor, DescriptorPublicKey}; +use miniscript::bitcoin; use serde_json::Value as Json; @@ -354,7 +354,7 @@ impl BitcoinD { } // TODO: rescan feature will probably need another timestamp than 'now' - fn import_descriptor(&self, descriptor: &Descriptor) -> Option { + fn import_descriptor(&self, descriptor: &InheritanceDescriptor) -> Option { let descriptors = vec![serde_json::json!({ "desc": descriptor.to_string(), "timestamp": "now", @@ -400,7 +400,7 @@ impl BitcoinD { /// Create the watchonly wallet on bitcoind, and import it the main descriptor. pub fn create_watchonly_wallet( &self, - main_descriptor: &Descriptor, + main_descriptor: &InheritanceDescriptor, ) -> Result<(), BitcoindError> { // Remove any leftover. This can happen if we delete the watchonly wallet but don't restart // bitcoind. @@ -440,7 +440,7 @@ impl BitcoinD { /// Perform various sanity checks on the bitcoind instance. pub fn sanity_check( &self, - main_descriptor: &Descriptor, + main_descriptor: &InheritanceDescriptor, config_network: bitcoin::Network, ) -> Result<(), BitcoindError> { // Check the minimum supported bitcoind version diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 6b1ef3f0..0c6c0e69 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -7,15 +7,11 @@ mod utils; use crate::{ bitcoin::BitcoinInterface, database::{Coin, DatabaseInterface}, - DaemonControl, VERSION, + descriptors, DaemonControl, VERSION, }; use utils::{deser_amount_from_sats, ser_amount}; -use miniscript::{ - bitcoin, - descriptor::{self, DescriptorTrait}, - TranslatePk2, -}; +use miniscript::bitcoin; use serde::{Deserialize, Serialize}; impl DaemonControl { @@ -37,17 +33,13 @@ impl DaemonControl { pub fn get_new_address(&self) -> GetAddressResult { let mut db_conn = self.db.connection(); let index = db_conn.derivation_index(); - // TODO: handle should we wrap around instead of failing? + // TODO: should we wrap around instead of failing? db_conn.increment_derivation_index(&self.secp); let address = self .config .main_descriptor - // TODO: have a descriptor newtype along with a derived descriptor one. - .derive(index.into()) - .translate_pk2(|xpk| xpk.derive_public_key(&self.secp)) - .expect("All pubkeys were derived, no wildcard.") - .address(self.config.bitcoin_config.network) - .expect("It's a wsh() descriptor"); + .derive(index.into(), &self.secp) + .address(self.config.bitcoin_config.network); GetAddressResult { address } } @@ -78,7 +70,7 @@ impl DaemonControl { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GetInfoDescriptors { - pub main: descriptor::Descriptor, + pub main: descriptors::InheritanceDescriptor, } /// Information about the daemon diff --git a/src/config.rs b/src/config.rs index 168746fc..92ef7db2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,10 +1,8 @@ +use crate::descriptors::InheritanceDescriptor; + use std::{net::SocketAddr, path::PathBuf, str::FromStr, time::Duration}; -use miniscript::{ - bitcoin::Network, - descriptor::{Descriptor, DescriptorPublicKey}, - ForEach, ForEachKey, -}; +use miniscript::{bitcoin::Network, DescriptorPublicKey, ForEach, ForEachKey}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; @@ -94,7 +92,7 @@ pub struct Config { deserialize_with = "deserialize_fromstr", serialize_with = "serialize_to_string" )] - pub main_descriptor: Descriptor, + pub main_descriptor: InheritanceDescriptor, /// Settings for the Bitcoin interface pub bitcoin_config: BitcoinConfig, /// Settings specific to bitcoind as the Bitcoin interface @@ -115,7 +113,7 @@ pub enum ConfigError { DatadirNotFound, FileNotFound, ReadingFile(String), - UnexpectedDescriptor(Descriptor), + UnexpectedDescriptor(InheritanceDescriptor), Unexpected(String), } @@ -205,7 +203,7 @@ impl Config { Network::Bitcoin => Network::Bitcoin, _ => Network::Testnet, }; - let unexpected_net = self.main_descriptor.for_each_key(|pkpkh| { + let unexpected_net = self.main_descriptor.as_inner().for_each_key(|pkpkh| { let xpub = match pkpkh { // For DescriptorPublicKey, Pk::Hash == Self. ForEach::Key(xpub) => xpub, @@ -242,7 +240,7 @@ mod tests { data_dir = "/home/wizardsardine/custom/folder/" daemon = false log_level = "debug" - main_descriptor = "wsh(andor(thresh(1,pk(xpub6BaZSKgpaVvibu2k78QsqeDWXp92xLHZxiu1WoqLB9hKhsBf3miBUDX7PJLgSPvkj66ThVHTqdnbXpeu8crXFmDUd4HeM4s4miQS2xsv3Qb/*)),and_v(v:multi(2,03b506a1dbe57b4bf48c95e0c7d417b87dd3b4349d290d2e7e9ba72c912652d80a,0295e7f5d12a2061f1fd2286cefec592dff656a19f55f4f01305d6aa56630880ce),older(4)),thresh(2,pkh(xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU/*),a:pkh(xpub6AaffFGfH6WXfm6pwWzmUMuECQnoLeB3agMKaLyEBZ5ZVfwtnS5VJKqXBt8o5ooCWVy2H87GsZshp7DeKE25eWLyd1Ccuh2ZubQUkgpiVux/*))))#532k8uvf" + main_descriptor = "wsh(andor(pk(tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/*)))#y5wcna2d" [bitcoin_config] network = "bitcoin" @@ -259,7 +257,7 @@ mod tests { data_dir = '/home/wizardsardine/custom/folder/' daemon = false log_level = 'TRACE' - main_descriptor = 'wsh(andor(thresh(1,pk(xpub6BaZSKgpaVvibu2k78QsqeDWXp92xLHZxiu1WoqLB9hKhsBf3miBUDX7PJLgSPvkj66ThVHTqdnbXpeu8crXFmDUd4HeM4s4miQS2xsv3Qb/*)),and_v(v:multi(2,03b506a1dbe57b4bf48c95e0c7d417b87dd3b4349d290d2e7e9ba72c912652d80a,0295e7f5d12a2061f1fd2286cefec592dff656a19f55f4f01305d6aa56630880ce),older(4)),thresh(2,pkh(xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU/*),a:pkh(xpub6AaffFGfH6WXfm6pwWzmUMuECQnoLeB3agMKaLyEBZ5ZVfwtnS5VJKqXBt8o5ooCWVy2H87GsZshp7DeKE25eWLyd1Ccuh2ZubQUkgpiVux/*))))#532k8uvf' + main_descriptor = 'wsh(andor(pk(tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/*)))#y5wcna2d' [bitcoin_config] network = 'bitcoin' @@ -280,8 +278,7 @@ mod tests { log_level = "trace" data_dir = "/home/wizardsardine/custom/folder/" - # The main descriptor semantics aren't checked, yet. - main_descriptor = "wsh(andor(thresh(1,pk(xpub6BaZSKgpaVvibu2k78QsqeDWXp92xLHZxiu1WoqLB9hKhsBf3miBUDX7PJLgSPvkj66ThVHTqdnbXpeu8crXFmDUd4HeM4s4miQS2xsv3Qb/*)),and_v(v:multi(2,03b506a1dbe57b4bf48c95e0c7d417b87dd3b4349d290d2e7e9ba72c912652d80a,0295e7f5d12a2061f1fd2286cefec592dff656a19f55f4f01305d6aa56630880ce),older(4)),thresh(2,pkh(xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU/*),a:pkh(xpub6AaffFGfH6WXfm6pwWzmUMuECQnoLeB3agMKaLyEBZ5ZVfwtnS5VJKqXBt8o5ooCWVy2H87GsZshp7DeKE25eWLyd1Ccuh2ZubQUkgpiVux/*))))#532k88vf" + main_descriptor = "wsh(andor(pk(tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/*)))#y5wcna2e" [bitcoin_config] network = "bitcoin" @@ -301,7 +298,7 @@ mod tests { data_dir = "/home/wizardsardine/custom/folder/" # The main descriptor semantics aren't checked, yet. - main_descriptor = "wsh(andor(thresh(1,pk(xpub6BaZSKgpaVvibu2k78QsqeDWXp92xLHZxiu1WoqLB9hKhsBf3miBUDX7PJLgSPvkj66ThVHTqdnbXpeu8crXFmDUd4HeM4s4miQS2xsv3Qb/*)),and_v(v:multi(2,03b506a1dbe57b4bf48c95e0c7d417b87dd3b4349d290d2e7e9ba72c912652d80a,0295e7f5d12a2061f1fd2286cefec592dff656a19f55f4f01305d6aa56630880ce),older(4)),thresh(2,pkh(xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU/*),a:pkh(xpub6AaffFGfH6WXfm6pwWzmUMuECQnoLeB3agMKaLyEBZ5ZVfwtnS5VJKqXBt8o5ooCWVy2H87GsZshp7DeKE25eWLyd1Ccuh2ZubQUkgpiVux/*))))#532k8uvf" + main_descriptor = "" [bitcoin_config] poll_interval_secs = 18 diff --git a/src/database/sqlite/mod.rs b/src/database/sqlite/mod.rs index 91315ba5..8406ff37 100644 --- a/src/database/sqlite/mod.rs +++ b/src/database/sqlite/mod.rs @@ -18,14 +18,12 @@ use crate::{ }, Coin, }, + descriptors::InheritanceDescriptor, }; use std::{convert::TryInto, fmt, io, path}; -use miniscript::{ - bitcoin::{self, secp256k1}, - Descriptor, DescriptorPublicKey, DescriptorTrait, TranslatePk2, -}; +use miniscript::bitcoin::{self, secp256k1}; const DB_VERSION: i64 = 0; @@ -35,7 +33,7 @@ pub enum SqliteDbError { FileNotFound(path::PathBuf), UnsupportedVersion(i64), InvalidNetwork(bitcoin::Network), - DescriptorMismatch(Descriptor), + DescriptorMismatch(InheritanceDescriptor), Rusqlite(rusqlite::Error), } @@ -79,7 +77,7 @@ impl From for SqliteDbError { #[derive(Debug, Clone)] pub struct FreshDbOptions { pub bitcoind_network: bitcoin::Network, - pub main_descriptor: Descriptor, + pub main_descriptor: InheritanceDescriptor, } #[derive(Debug, Clone)] @@ -118,7 +116,7 @@ impl SqliteDb { pub fn sanity_check( &self, bitcoind_network: bitcoin::Network, - main_descriptor: &Descriptor, + main_descriptor: &InheritanceDescriptor, ) -> Result<(), SqliteDbError> { let mut conn = self.connection()?; @@ -239,11 +237,8 @@ impl SqliteConn { let next_la_index = next_index + LOOK_AHEAD_LIMIT - 1; let next_la_address = db_wallet .main_descriptor - .derive(next_la_index) - .translate_pk2(|xpk| xpk.derive_public_key(secp)) - .expect("All pubkeys were derived, no wildcard.") - .address(network) - .expect("It's a wsh() descriptor"); + .derive(next_la_index.into(), &secp) + .address(network); db_tx .execute( "INSERT INTO addresses (address, derivation_index) VALUES (?1, ?2)", @@ -344,11 +339,10 @@ mod tests { use std::{collections::HashSet, fs, path, str::FromStr}; use bitcoin::{hashes::Hash, util::bip32}; - use miniscript::{DescriptorTrait, TranslatePk2}; fn dummy_options() -> FreshDbOptions { let desc_str = "wsh(andor(pk(tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/*)))#y5wcna2d"; - let main_descriptor = Descriptor::::from_str(desc_str).unwrap(); + let main_descriptor = InheritanceDescriptor::from_str(desc_str).unwrap(); FreshDbOptions { bitcoind_network: bitcoin::Network::Bitcoin, main_descriptor, @@ -396,8 +390,8 @@ mod tests { .to_string() .contains("Database was created for network"); fs::remove_file(&db_path).unwrap(); - let other_desc_str = "wsh(andor(pk(037a27a76ebf33594c785e4fa41607860a960bb5aa3039654297b05bff57e4f9a9),older(10000),pk(0295e7f5d12a2061f1fd2286cefec592dff656a19f55f4f01305d6aa56630880ce)))"; - let other_desc = Descriptor::::from_str(other_desc_str).unwrap(); + let other_desc_str = "wsh(andor(pk(tpubDExU4YLJkyQ9RRbVScQq2brFxWWha7WmAUByPWyaWYwmcTv3Shx8aHp6mVwuE5n4TeM4z5DTWGf2YhNPmXtfvyr8cUDVvA3txdrFnFgNdF7/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/*)))"; + let other_desc = InheritanceDescriptor::from_str(other_desc_str).unwrap(); let db = SqliteDb::new(db_path.clone(), Some(options.clone()), &secp).unwrap(); db.sanity_check(bitcoin::Network::Bitcoin, &other_desc) .unwrap_err() @@ -522,33 +516,24 @@ mod tests { // There is the index for the first index let addr = options .main_descriptor - .derive(0) - .translate_pk2(|xpk| xpk.derive_public_key(&secp)) - .expect("All pubkeys were derived, no wildcard.") - .address(options.bitcoind_network) - .expect("Always a P2WSH address"); + .derive(0.into(), &secp) + .address(options.bitcoind_network); let db_addr = conn.db_address(&addr).unwrap(); assert_eq!(db_addr.derivation_index, 0.into()); // There is the index for the 199th index (look-ahead limit) let addr = options .main_descriptor - .derive(199) - .translate_pk2(|xpk| xpk.derive_public_key(&secp)) - .expect("All pubkeys were derived, no wildcard.") - .address(options.bitcoind_network) - .expect("Always a P2WSH address"); + .derive(199.into(), &secp) + .address(options.bitcoind_network); let db_addr = conn.db_address(&addr).unwrap(); assert_eq!(db_addr.derivation_index, 199.into()); // And not for the 200th one. let addr = options .main_descriptor - .derive(200) - .translate_pk2(|xpk| xpk.derive_public_key(&secp)) - .expect("All pubkeys were derived, no wildcard.") - .address(options.bitcoind_network) - .expect("Always a P2WSH address"); + .derive(200.into(), &secp) + .address(options.bitcoind_network); assert!(conn.db_address(&addr).is_none()); // But if we increment the deposit derivation index, the 200th one will be there. diff --git a/src/database/sqlite/schema.rs b/src/database/sqlite/schema.rs index 1a0718aa..355728fe 100644 --- a/src/database/sqlite/schema.rs +++ b/src/database/sqlite/schema.rs @@ -1,9 +1,8 @@ +use crate::descriptors::InheritanceDescriptor; + use std::{convert::TryFrom, str::FromStr}; -use miniscript::{ - bitcoin::{self, consensus::encode, util::bip32}, - Descriptor, DescriptorPublicKey, -}; +use miniscript::bitcoin::{self, consensus::encode, util::bip32}; pub const SCHEMA: &str = "\ CREATE TABLE version ( @@ -86,7 +85,7 @@ impl TryFrom<&rusqlite::Row<'_>> for DbTip { pub struct DbWallet { pub id: i64, pub timestamp: u32, - pub main_descriptor: Descriptor, + pub main_descriptor: InheritanceDescriptor, pub deposit_derivation_index: bip32::ChildNumber, } @@ -98,7 +97,7 @@ impl TryFrom<&rusqlite::Row<'_>> for DbWallet { let timestamp = row.get(1)?; let desc_str: String = row.get(2)?; - let main_descriptor = Descriptor::::from_str(&desc_str) + let main_descriptor = InheritanceDescriptor::from_str(&desc_str) .expect("Insane database: can't parse deposit descriptor"); let der_idx: u32 = row.get(3)?; diff --git a/src/database/sqlite/utils.rs b/src/database/sqlite/utils.rs index e744ce4b..1b0f07c4 100644 --- a/src/database/sqlite/utils.rs +++ b/src/database/sqlite/utils.rs @@ -2,7 +2,7 @@ use crate::database::sqlite::{schema::SCHEMA, FreshDbOptions, SqliteDbError, DB_ use std::{convert::TryInto, fs, path, time}; -use miniscript::{bitcoin::secp256k1, DescriptorTrait, TranslatePk2}; +use miniscript::bitcoin::secp256k1; pub const LOOK_AHEAD_LIMIT: u32 = 200; @@ -106,11 +106,8 @@ pub fn create_fresh_db( // TODO: have this as a helper in descriptors.rs let address = options .main_descriptor - .derive(index) - .translate_pk2(|xpk| xpk.derive_public_key(secp)) - .expect("All pubkeys were derived, no wildcard.") - .address(options.bitcoind_network) - .expect("Always a P2WSH address"); + .derive(index.into(), secp) + .address(options.bitcoind_network); query += &format!( "INSERT INTO addresses (address, derivation_index) VALUES (\"{}\", {});\n", address, index diff --git a/src/descriptors.rs b/src/descriptors.rs index 811edc46..aa6c1dc2 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -13,6 +13,8 @@ use miniscript::{ use std::{error, fmt, io::Write, str, sync}; +use serde::{Deserialize, Serialize}; + // Flag applied to the nSequence and CSV value before comparing them. // // @@ -24,6 +26,7 @@ pub enum DescCreationError { InvalidKey(descriptor::DescriptorPublicKey), Miniscript(miniscript::Error), IncompatibleDesc, + DerivedKeyParsing, } impl std::fmt::Display for DescCreationError { @@ -35,6 +38,7 @@ impl std::fmt::Display for DescCreationError { } Self::Miniscript(e) => write!(f, "Miniscript error: '{}'.", e), Self::IncompatibleDesc => write!(f, "Descriptor is not compatible."), + Self::DerivedKeyParsing => write!(f, "Parsing derived key,"), } } } @@ -64,6 +68,54 @@ impl fmt::Display for DerivedPublicKey { } } +impl str::FromStr for DerivedPublicKey { + type Err = DescCreationError; + + fn from_str(s: &str) -> Result { + // The key is always of the form: + // [ fingerprint / index ] + + // 1 + 8 + 1 + 1 + 1 + 66 minimum + if s.len() < 78 { + return Err(DescCreationError::DerivedKeyParsing); + } + + // Non-ASCII? + for ch in s.as_bytes() { + if *ch < 20 || *ch > 127 { + return Err(DescCreationError::DerivedKeyParsing); + } + } + + if s.chars().next().expect("Size checked above") != '[' { + return Err(DescCreationError::DerivedKeyParsing); + } + + let mut parts = s[1..].split(']'); + let fg_deriv = parts.next().ok_or(DescCreationError::DerivedKeyParsing)?; + let key_str = parts.next().ok_or(DescCreationError::DerivedKeyParsing)?; + + if fg_deriv.len() < 10 { + return Err(DescCreationError::DerivedKeyParsing); + } + let fingerprint = bip32::Fingerprint::from_str(&fg_deriv[..8]) + .map_err(|_| DescCreationError::DerivedKeyParsing)?; + let deriv_index = bip32::ChildNumber::from_str(&fg_deriv[9..]) + .map_err(|_| DescCreationError::DerivedKeyParsing)?; + if deriv_index.is_hardened() { + return Err(DescCreationError::DerivedKeyParsing); + } + + let key = bitcoin::PublicKey::from_str(&key_str) + .map_err(|_| DescCreationError::DerivedKeyParsing)?; + + Ok(DerivedPublicKey { + key, + origin: (fingerprint, deriv_index), + }) + } +} + impl MiniscriptKey for DerivedPublicKey { // This allows us to be able to derive keys and key source even for PkH s type Hash = Self; @@ -117,11 +169,11 @@ fn is_unhardened_deriv(key: &descriptor::DescriptorPublicKey) -> bool { /// A Miniscript descriptor with a main, unencombered, branch (the main owner of the coins) /// and a timelocked branch (the heir). -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct InheritanceDescriptor(descriptor::Descriptor); /// Derived (containing only raw Bitcoin public keys) version of the inheritance descriptor. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct DerivedInheritanceDescriptor(descriptor::Descriptor); impl fmt::Display for InheritanceDescriptor { @@ -210,7 +262,7 @@ impl InheritanceDescriptor { owner_key: descriptor::DescriptorPublicKey, heir_key: descriptor::DescriptorPublicKey, timelock: u32, - ) -> Result, DescCreationError> { + ) -> Result { csv_check(timelock)?; if let Some(key) = vec![&owner_key, &heir_key] @@ -247,9 +299,13 @@ impl InheritanceDescriptor { miniscript::Segwitv0::check_local_validity(&tl_miniscript) .expect("Miniscript must be sane"); - Ok(descriptor::Descriptor::Wsh( + Ok(InheritanceDescriptor(descriptor::Descriptor::Wsh( descriptor::Wsh::new(tl_miniscript).expect("Must pass sanity checks"), - )) + ))) + } + + pub fn as_inner(&self) -> &descriptor::Descriptor { + &self.0 } /// Derive this descriptor at a given index. diff --git a/src/lib.rs b/src/lib.rs index 5d9ef901..ba4fb6c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -377,10 +377,11 @@ mod tests { use super::*; use crate::{ config::{BitcoinConfig, BitcoindConfig}, + descriptors::InheritanceDescriptor, testutils::*, }; - use miniscript::{bitcoin, Descriptor, DescriptorPublicKey}; + use miniscript::bitcoin; use std::{ fs, io::{BufRead, BufReader, Write}, @@ -588,7 +589,7 @@ mod tests { // Create a dummy config with this bitcoind let desc_str = "wsh(andor(pk(xpub68JJTXc1MWK8KLW4HGLXZBJknja7kDUJuFHnM424LbziEXsfkh1WQCiEjjHw4zLqSUm4rvhgyGkkuRowE9tCJSgt3TQB5J3SKAbZ2SdcKST/*),older(10000),pk(xpub68JJTXc1MWK8PEQozKsRatrUHXKFNkD1Cb1BuQU9Xr5moCv87anqGyXLyUd4KpnDyZgo3gz4aN1r3NiaoweFW8UutBsBbgKHzaD5HkTkifK/*)))#tk6wzexy"; - let desc = Descriptor::::from_str(desc_str).unwrap(); + let desc = InheritanceDescriptor::from_str(desc_str).unwrap(); let config = Config { bitcoin_config, bitcoind_config: Some(bitcoind_config), diff --git a/src/testutils.rs b/src/testutils.rs index 215a8524..d97b1fec 100644 --- a/src/testutils.rs +++ b/src/testutils.rs @@ -177,7 +177,8 @@ impl DummyMinisafe { let owner_key = descriptor::DescriptorPublicKey::from_str("xpub68JJTXc1MWK8KLW4HGLXZBJknja7kDUJuFHnM424LbziEXsfkh1WQCiEjjHw4zLqSUm4rvhgyGkkuRowE9tCJSgt3TQB5J3SKAbZ2SdcKST/*").unwrap(); let heir_key = descriptor::DescriptorPublicKey::from_str("xpub68JJTXc1MWK8PEQozKsRatrUHXKFNkD1Cb1BuQU9Xr5moCv87anqGyXLyUd4KpnDyZgo3gz4aN1r3NiaoweFW8UutBsBbgKHzaD5HkTkifK/*").unwrap(); - let desc = crate::descriptors::InheritanceDescriptor::new(owner_key, heir_key, 10_000).unwrap(); + let desc = + crate::descriptors::InheritanceDescriptor::new(owner_key, heir_key, 10_000).unwrap(); let config = Config { bitcoin_config, bitcoind_config: None, diff --git a/tests/fixtures.py b/tests/fixtures.py index 6a19b44b..dad19e38 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -117,7 +117,7 @@ def minisafed(bitcoind, directory): os.makedirs(datadir, exist_ok=True) bitcoind_cookie = os.path.join(bitcoind.bitcoin_dir, "regtest", ".cookie") - main_desc = "wsh(or_d(pk(tpubD9vQiBdDxYzU1V5D5UUmMTXF9FZC13PuQDs4aiv6rF7UCKQFvtVKZguYakX12C2bt8736ksioxu9Y9Nmp18gj4jDeNJEEqrBPEZXAxe5YcQ/*),and_v(v:pkh(tpubD9vQiBdDxYzU4cVFtApWj4devZrvcfWaPXX1zHdDc7GPfUsDKqGnbhraccfm7BAXgRgUbVQUV2v2o4NitjGEk7hpbuP85kvBrD4ahFDtNBJ/*),older(157680))))" + main_desc = "wsh(or_d(pk(tpubD9vQiBdDxYzU1V5D5UUmMTXF9FZC13PuQDs4aiv6rF7UCKQFvtVKZguYakX12C2bt8736ksioxu9Y9Nmp18gj4jDeNJEEqrBPEZXAxe5YcQ/*),and_v(v:pkh(tpubD9vQiBdDxYzU4cVFtApWj4devZrvcfWaPXX1zHdDc7GPfUsDKqGnbhraccfm7BAXgRgUbVQUV2v2o4NitjGEk7hpbuP85kvBrD4ahFDtNBJ/*),older(65000))))" minisafed = Minisafed( datadir,