descriptors: introduce a newtype for derived descriptors
This commit is contained in:
parent
869d370daa
commit
44eb0fad9b
@ -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.
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user