descriptors: introduce a newtype for derived descriptors

This commit is contained in:
Antoine Poinsot 2022-08-18 11:19:54 +02:00
parent 869d370daa
commit 44eb0fad9b
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304

View File

@ -1,5 +1,6 @@
use miniscript::{ use miniscript::{
descriptor, bitcoin::{self, hashes::hash160, hashes::Hash, secp256k1, util::bip32},
descriptor::{self, DescriptorTrait},
miniscript::{ miniscript::{
decode::Terminal, decode::Terminal,
iter::PkPkh, iter::PkPkh,
@ -7,10 +8,10 @@ use miniscript::{
Miniscript, Miniscript,
}, },
policy::{Liftable, Semantic as SemanticPolicy}, policy::{Liftable, Semantic as SemanticPolicy},
ScriptContext, MiniscriptKey, ScriptContext, ToPublicKey, TranslatePk2,
}; };
use std::{error, fmt, str, sync}; use std::{error, fmt, io::Write, str, sync};
// Flag applied to the nSequence and CSV value before comparing them. // Flag applied to the nSequence and CSV value before comparing them.
// //
@ -40,6 +41,56 @@ impl std::fmt::Display for DescCreationError {
impl error::Error for DescCreationError {} impl error::Error for DescCreationError {}
/// 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::ChildNumber),
/// The actual key
pub key: bitcoin::PublicKey,
}
impl fmt::Display for DerivedPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (fingerprint, deriv_index) = &self.origin;
write!(f, "[")?;
for byte in fingerprint.as_bytes().iter() {
write!(f, "{:02x}", byte)?;
}
write!(f, "/{}", deriv_index)?;
write!(f, "]{}", self.key)
}
}
impl MiniscriptKey for DerivedPublicKey {
// This allows us to be able to derive keys and key source even for PkH s
type Hash = Self;
fn is_uncompressed(&self) -> bool {
self.key.is_uncompressed()
}
fn to_pubkeyhash(&self) -> Self::Hash {
self.clone()
}
}
impl ToPublicKey for DerivedPublicKey {
fn to_public_key(&self) -> bitcoin::PublicKey {
self.key
}
fn hash_to_hash160(derived_key: &Self) -> hash160::Hash {
let mut engine = hash160::Hash::engine();
engine
.write_all(&derived_key.key.key.serialize())
.expect("engines don't error");
hash160::Hash::from_engine(engine)
}
}
// We require the locktime to: // We require the locktime to:
// - not be disabled // - not be disabled
// - be in number of blocks // - be in number of blocks
@ -69,6 +120,10 @@ fn is_unhardened_deriv(key: &descriptor::DescriptorPublicKey) -> bool {
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct InheritanceDescriptor(descriptor::Descriptor<descriptor::DescriptorPublicKey>); pub struct InheritanceDescriptor(descriptor::Descriptor<descriptor::DescriptorPublicKey>);
/// Derived (containing only raw Bitcoin public keys) version of the inheritance descriptor.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DerivedInheritanceDescriptor(descriptor::Descriptor<DerivedPublicKey>);
impl fmt::Display for InheritanceDescriptor { impl fmt::Display for InheritanceDescriptor {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0) write!(f, "{}", self.0)
@ -196,6 +251,42 @@ impl InheritanceDescriptor {
descriptor::Wsh::new(tl_miniscript).expect("Must pass sanity checks"), descriptor::Wsh::new(tl_miniscript).expect("Must pass sanity checks"),
)) ))
} }
/// Derive this descriptor at a given index.
pub fn derive(
&self,
index: bip32::ChildNumber,
secp: &secp256k1::Secp256k1<impl secp256k1::Verification>,
) -> DerivedInheritanceDescriptor {
assert!(index.is_normal());
let desc = self
.0
.derive(index.into())
.translate_pk2(|xpk| {
xpk.derive_public_key(secp).map(|key| {
// FIXME: rust-miniscript will panic if we call
// xpk.master_fingerprint() on a key without origin
let origin = match xpk {
descriptor::DescriptorPublicKey::XPub(..) => {
(xpk.master_fingerprint(), index)
}
_ => unreachable!("All keys are always xpubs"),
};
DerivedPublicKey { key, origin }
})
})
.expect("All pubkeys are derived, no wildcard.");
DerivedInheritanceDescriptor(desc)
}
}
impl DerivedInheritanceDescriptor {
pub fn address(&self, network: bitcoin::Network) -> bitcoin::Address {
self.0
.address(network)
.expect("A P2WSH always has an address")
}
} }
#[cfg(test)] #[cfg(test)]
@ -232,4 +323,17 @@ mod tests {
.unwrap(); .unwrap();
InheritanceDescriptor::new(owner_key.clone(), heir_key, timelock).unwrap_err(); InheritanceDescriptor::new(owner_key.clone(), heir_key, timelock).unwrap_err();
} }
#[test]
fn inheritance_descriptor_derivation() {
let secp = secp256k1::Secp256k1::verification_only();
let desc = InheritanceDescriptor::from_str("wsh(andor(pk(tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/*),older(10000),pk(tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/*)))#y5wcna2d").unwrap();
let der_desc = desc.derive(11.into(), &secp);
assert_eq!(
"bc1qvjzcg25nsxmfccct0txjvljxjwn68htkrw57jqmjhfzvhyd2z4msc74w65",
der_desc.address(bitcoin::Network::Bitcoin).to_string()
);
}
// TODO: test error conditions of deserialization.
} }