Use the descriptor newtype instead of the raw miniscript Descriptor type
This commit is contained in:
parent
44eb0fad9b
commit
0fd57db8a8
@ -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<DescriptorPublicKey>) -> Option<String> {
|
||||
fn import_descriptor(&self, descriptor: &InheritanceDescriptor) -> Option<String> {
|
||||
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<DescriptorPublicKey>,
|
||||
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<DescriptorPublicKey>,
|
||||
main_descriptor: &InheritanceDescriptor,
|
||||
config_network: bitcoin::Network,
|
||||
) -> Result<(), BitcoindError> {
|
||||
// Check the minimum supported bitcoind version
|
||||
|
||||
@ -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<descriptor::DescriptorPublicKey>,
|
||||
pub main: descriptors::InheritanceDescriptor,
|
||||
}
|
||||
|
||||
/// Information about the daemon
|
||||
|
||||
@ -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<DescriptorPublicKey>,
|
||||
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<DescriptorPublicKey>),
|
||||
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
|
||||
|
||||
@ -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<DescriptorPublicKey>),
|
||||
DescriptorMismatch(InheritanceDescriptor),
|
||||
Rusqlite(rusqlite::Error),
|
||||
}
|
||||
|
||||
@ -79,7 +77,7 @@ impl From<rusqlite::Error> for SqliteDbError {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FreshDbOptions {
|
||||
pub bitcoind_network: bitcoin::Network,
|
||||
pub main_descriptor: Descriptor<DescriptorPublicKey>,
|
||||
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<DescriptorPublicKey>,
|
||||
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::<DescriptorPublicKey>::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::<DescriptorPublicKey>::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.
|
||||
|
||||
@ -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<DescriptorPublicKey>,
|
||||
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::<DescriptorPublicKey>::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)?;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
//
|
||||
// <https://github.com/bitcoin/bitcoin/blob/4a540683ec40393d6369da1a9e02e45614db936d/src/primitives/transaction.h#L87-L89>
|
||||
@ -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<DerivedPublicKey, Self::Err> {
|
||||
// The key is always of the form:
|
||||
// [ fingerprint / index ]<key>
|
||||
|
||||
// 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<descriptor::DescriptorPublicKey>);
|
||||
|
||||
/// 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<DerivedPublicKey>);
|
||||
|
||||
impl fmt::Display for InheritanceDescriptor {
|
||||
@ -210,7 +262,7 @@ impl InheritanceDescriptor {
|
||||
owner_key: descriptor::DescriptorPublicKey,
|
||||
heir_key: descriptor::DescriptorPublicKey,
|
||||
timelock: u32,
|
||||
) -> Result<descriptor::Descriptor<descriptor::DescriptorPublicKey>, DescCreationError> {
|
||||
) -> Result<InheritanceDescriptor, DescCreationError> {
|
||||
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<descriptor::DescriptorPublicKey> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Derive this descriptor at a given index.
|
||||
|
||||
@ -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::<DescriptorPublicKey>::from_str(desc_str).unwrap();
|
||||
let desc = InheritanceDescriptor::from_str(desc_str).unwrap();
|
||||
let config = Config {
|
||||
bitcoin_config,
|
||||
bitcoind_config: Some(bitcoind_config),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user