diff --git a/src/descriptors/keys.rs b/src/descriptors/keys.rs new file mode 100644 index 00000000..29e1d13e --- /dev/null +++ b/src/descriptors/keys.rs @@ -0,0 +1,137 @@ +use miniscript::{ + bitcoin::{ + self, + hashes::{hash160, ripemd160, sha256}, + util::bip32, + }, + hash256, MiniscriptKey, ToPublicKey, +}; + +use std::{error, fmt, str}; + +#[derive(Debug)] +pub enum DescKeyError { + DerivedKeyParsing, +} + +impl std::fmt::Display for DescKeyError { + fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result { + match self { + DescKeyError::DerivedKeyParsing => write!(f, "Parsing derived key"), + } + } +} + +impl error::Error for DescKeyError {} + +/// A public key used in derived descriptors +#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)] +pub struct DerivedPublicKey { + /// Fingerprint of the master xpub and the derivation index used. We don't use a path + /// since we never derive at more than one depth. + pub origin: (bip32::Fingerprint, bip32::DerivationPath), + /// The actual key + pub key: bitcoin::PublicKey, +} + +impl fmt::Display for DerivedPublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (fingerprint, deriv_path) = &self.origin; + + write!(f, "[")?; + for byte in fingerprint.as_bytes().iter() { + write!(f, "{:02x}", byte)?; + } + write!(f, "/{}", deriv_path)?; + write!(f, "]{}", self.key) + } +} + +impl str::FromStr for DerivedPublicKey { + type Err = DescKeyError; + + 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(DescKeyError::DerivedKeyParsing); + } + + // Non-ASCII? + for ch in s.as_bytes() { + if *ch < 20 || *ch > 127 { + return Err(DescKeyError::DerivedKeyParsing); + } + } + + if s.chars().next().expect("Size checked above") != '[' { + return Err(DescKeyError::DerivedKeyParsing); + } + + let mut parts = s[1..].split(']'); + let fg_deriv = parts.next().ok_or(DescKeyError::DerivedKeyParsing)?; + let key_str = parts.next().ok_or(DescKeyError::DerivedKeyParsing)?; + + if fg_deriv.len() < 10 { + return Err(DescKeyError::DerivedKeyParsing); + } + let fingerprint = bip32::Fingerprint::from_str(&fg_deriv[..8]) + .map_err(|_| DescKeyError::DerivedKeyParsing)?; + let deriv_path = bip32::DerivationPath::from_str(&fg_deriv[9..]) + .map_err(|_| DescKeyError::DerivedKeyParsing)?; + if deriv_path.into_iter().any(bip32::ChildNumber::is_hardened) { + return Err(DescKeyError::DerivedKeyParsing); + } + + let key = + bitcoin::PublicKey::from_str(key_str).map_err(|_| DescKeyError::DerivedKeyParsing)?; + + Ok(DerivedPublicKey { + key, + origin: (fingerprint, deriv_path), + }) + } +} + +impl MiniscriptKey for DerivedPublicKey { + type Sha256 = sha256::Hash; + type Hash256 = hash256::Hash; + type Ripemd160 = ripemd160::Hash; + type Hash160 = hash160::Hash; + + fn is_uncompressed(&self) -> bool { + self.key.is_uncompressed() + } + + fn is_x_only_key(&self) -> bool { + false + } + + fn num_der_paths(&self) -> usize { + 0 + } +} + +impl ToPublicKey for DerivedPublicKey { + fn to_public_key(&self) -> bitcoin::PublicKey { + self.key + } + + fn to_sha256(hash: &sha256::Hash) -> sha256::Hash { + *hash + } + + fn to_hash256(hash: &hash256::Hash) -> hash256::Hash { + *hash + } + + fn to_ripemd160(hash: &ripemd160::Hash) -> ripemd160::Hash { + *hash + } + + fn to_hash160(hash: &hash160::Hash) -> hash160::Hash { + *hash + } +} diff --git a/src/descriptors/mod.rs b/src/descriptors/mod.rs index c0d534fa..0981b2a6 100644 --- a/src/descriptors/mod.rs +++ b/src/descriptors/mod.rs @@ -2,18 +2,16 @@ use miniscript::{ bitcoin::{ self, blockdata::transaction::Sequence, - hashes::{hash160, ripemd160, sha256}, secp256k1, util::{ bip32, psbt::{Input as PsbtIn, Psbt}, }, }, - descriptor, hash256, + descriptor, miniscript::{decode::Terminal, Miniscript}, policy::{Liftable, Semantic as SemanticPolicy}, - translate_hash_clone, ForEachKey, MiniscriptKey, ScriptContext, ToPublicKey, TranslatePk, - Translator, + translate_hash_clone, ForEachKey, ScriptContext, TranslatePk, Translator, }; use std::{ @@ -24,6 +22,9 @@ use std::{ use serde::{Deserialize, Serialize}; +pub mod keys; +pub use keys::*; + const WITNESS_FACTOR: usize = 4; // Convert a size in weight units to a size in virtual bytes, rounding up. @@ -40,7 +41,7 @@ pub enum LianaDescError { DuplicateKey(Box), Miniscript(miniscript::Error), IncompatibleDesc, - DerivedKeyParsing, + DescKey(DescKeyError), InvalidMultiThresh(usize), InvalidMultiKeys(usize), /// Different number of PSBT vs tx inputs, etc.. @@ -67,7 +68,7 @@ impl std::fmt::Display for LianaDescError { } Self::Miniscript(e) => write!(f, "Miniscript error: '{}'.", e), Self::IncompatibleDesc => write!(f, "Descriptor is not compatible."), - Self::DerivedKeyParsing => write!(f, "Parsing derived key,"), + Self::DescKey(e) => write!(f, "{}", e), Self::InvalidMultiThresh(thresh) => write!(f, "Invalid threshold value '{}'. The threshold must be > to 0 and <= to the number of keys.", thresh), Self::InvalidMultiKeys(n_keys) => write!(f, "Invalid number of keys '{}'. Between 2 and 20 keys must be given to use multiple keys in a specific path.", n_keys), Self::InsanePsbt => write!(f, "Analyzed PSBT is empty or malformed."), @@ -78,118 +79,6 @@ impl std::fmt::Display for LianaDescError { impl error::Error for LianaDescError {} -/// A public key used in derived descriptors -#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)] -pub struct DerivedPublicKey { - /// Fingerprint of the master xpub and the derivation index used. We don't use a path - /// since we never derive at more than one depth. - pub origin: (bip32::Fingerprint, bip32::DerivationPath), - /// The actual key - pub key: bitcoin::PublicKey, -} - -impl fmt::Display for DerivedPublicKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let (fingerprint, deriv_path) = &self.origin; - - write!(f, "[")?; - for byte in fingerprint.as_bytes().iter() { - write!(f, "{:02x}", byte)?; - } - write!(f, "/{}", deriv_path)?; - write!(f, "]{}", self.key) - } -} - -impl str::FromStr for DerivedPublicKey { - type Err = LianaDescError; - - 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(LianaDescError::DerivedKeyParsing); - } - - // Non-ASCII? - for ch in s.as_bytes() { - if *ch < 20 || *ch > 127 { - return Err(LianaDescError::DerivedKeyParsing); - } - } - - if s.chars().next().expect("Size checked above") != '[' { - return Err(LianaDescError::DerivedKeyParsing); - } - - let mut parts = s[1..].split(']'); - let fg_deriv = parts.next().ok_or(LianaDescError::DerivedKeyParsing)?; - let key_str = parts.next().ok_or(LianaDescError::DerivedKeyParsing)?; - - if fg_deriv.len() < 10 { - return Err(LianaDescError::DerivedKeyParsing); - } - let fingerprint = bip32::Fingerprint::from_str(&fg_deriv[..8]) - .map_err(|_| LianaDescError::DerivedKeyParsing)?; - let deriv_path = bip32::DerivationPath::from_str(&fg_deriv[9..]) - .map_err(|_| LianaDescError::DerivedKeyParsing)?; - if deriv_path.into_iter().any(bip32::ChildNumber::is_hardened) { - return Err(LianaDescError::DerivedKeyParsing); - } - - let key = - bitcoin::PublicKey::from_str(key_str).map_err(|_| LianaDescError::DerivedKeyParsing)?; - - Ok(DerivedPublicKey { - key, - origin: (fingerprint, deriv_path), - }) - } -} - -impl MiniscriptKey for DerivedPublicKey { - type Sha256 = sha256::Hash; - type Hash256 = hash256::Hash; - type Ripemd160 = ripemd160::Hash; - type Hash160 = hash160::Hash; - - fn is_uncompressed(&self) -> bool { - self.key.is_uncompressed() - } - - fn is_x_only_key(&self) -> bool { - false - } - - fn num_der_paths(&self) -> usize { - 0 - } -} - -impl ToPublicKey for DerivedPublicKey { - fn to_public_key(&self) -> bitcoin::PublicKey { - self.key - } - - fn to_sha256(hash: &sha256::Hash) -> sha256::Hash { - *hash - } - - fn to_hash256(hash: &hash256::Hash) -> hash256::Hash { - *hash - } - - fn to_ripemd160(hash: &ripemd160::Hash) -> ripemd160::Hash { - *hash - } - - fn to_hash160(hash: &hash160::Hash) -> hash160::Hash { - *hash - } -} - // We require the locktime to: // - not be disabled // - be in number of blocks