From 55d86c62a59bcfe632b866d1b24653219f4c4399 Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Fri, 24 Mar 2023 18:46:25 +0100 Subject: [PATCH] descriptors: use the Miniscript compiler to create a descriptor --- Cargo.toml | 2 +- src/descriptors/analysis.rs | 80 +++++++++++-------------------------- src/descriptors/mod.rs | 2 +- 3 files changed, 25 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 15dd0bf1..c6c4b1b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ daemon = ["libc"] [dependencies] # For managing transactions (it re-exports the bitcoin crate) -miniscript = { git = "https://github.com/darosior/rust-miniscript", branch = "multipath_descriptors_on_9.0", features = ["serde"] } +miniscript = { git = "https://github.com/darosior/rust-miniscript", branch = "multipath_descriptors_on_9.0", features = ["serde", "compiler"] } # Don't reinvent the wheel dirs = "5.0" diff --git a/src/descriptors/analysis.rs b/src/descriptors/analysis.rs index c34ea2e6..1f2f04b0 100644 --- a/src/descriptors/analysis.rs +++ b/src/descriptors/analysis.rs @@ -1,14 +1,14 @@ use miniscript::{ bitcoin::{util::bip32, Sequence}, descriptor, - policy::{Liftable, Semantic as SemanticPolicy}, - Miniscript, ScriptContext, Terminal, + policy::{Concrete as ConcretePolicy, Liftable, Semantic as SemanticPolicy}, + ScriptContext, }; use std::{ collections::{HashMap, HashSet}, convert::TryFrom, - error, fmt, sync, + error, fmt, }; #[derive(Debug)] @@ -288,33 +288,14 @@ impl PathInfo { } } - /// Returns `None` if it is a multisig that does not fit inside a CHECKMULTISIG. - pub fn into_miniscript( - self, - as_hash: bool, - ) -> Option> { + /// Get a Miniscript Policy for this path. + pub fn into_ms_policy(self) -> ConcretePolicy { match self { - PathInfo::Single(key) => Some( - Miniscript::from_ast(Terminal::Check(sync::Arc::from( - Miniscript::from_ast(if as_hash { - Terminal::PkH(key) - } else { - Terminal::PkK(key) - }) - .expect("pk_k is a valid Miniscript"), - ))) - .expect("Well typed"), + PathInfo::Single(key) => ConcretePolicy::Key(key), + PathInfo::Multi(thresh, keys) => ConcretePolicy::Threshold( + thresh, + keys.into_iter().map(ConcretePolicy::Key).collect(), ), - PathInfo::Multi(thresh, keys) => { - if thresh < 1 || keys.len() > 20 || thresh > keys.len() { - None - } else { - Some( - Miniscript::from_ast(Terminal::Multi(thresh, keys)) - .expect("multi is a valid Miniscript"), - ) - } - } } } } @@ -484,36 +465,21 @@ impl LianaPolicy { recovery_path: (timelock, recovery_path), } = self; - // Create the timelocked spending path. If there is a single key we make it a pk_h() in - // order to save on the script size (since we assume the timelocked recovery path will - // seldom be used). - let recovery_timelock = Terminal::Older(Sequence::from_height(timelock)); - let recovery_keys = recovery_path - .into_miniscript(true) - .expect("We check the multisig never overflows in our constructors."); - let recovery_branch = Miniscript::from_ast(Terminal::AndV( - Miniscript::from_ast(Terminal::Verify(recovery_keys.into())) - .expect("Well typed") - .into(), - Miniscript::from_ast(recovery_timelock) - .expect("Well typed") - .into(), - )) - .expect("Well typed"); + // Create the timelocked recovery spending path. + let recovery_timelock = ConcretePolicy::Older(Sequence::from_height(timelock)); + let recovery_keys = recovery_path.into_ms_policy(); + let recovery_branch = ConcretePolicy::And(vec![recovery_keys, recovery_timelock]); - // Combine the timelocked spending path with the simple "primary" path. For the primary key - // we don't use a pkh since it's the one that will likely always be used. - let primary_keys = primary_path - .into_miniscript(false) - .expect("We check the multisig never overflows in our constructors."); - let tl_miniscript = - Miniscript::from_ast(Terminal::OrD(primary_keys.into(), recovery_branch.into())) - .expect("Well typed"); - miniscript::Segwitv0::check_local_validity(&tl_miniscript) - .expect("Miniscript must be sane"); - descriptor::Descriptor::Wsh( - descriptor::Wsh::new(tl_miniscript).expect("Must pass sanity checks"), - ) + // Create the primary spending path and combine both, assuming the recovery path will + // seldom be used. + let primary_keys = primary_path.into_ms_policy(); + let tl_policy = ConcretePolicy::Or(vec![(99, primary_keys), (1, recovery_branch)]); + + let ms = tl_policy + .compile::() + .expect("Compilation must never fail, nothing overflows."); + miniscript::Segwitv0::check_local_validity(&ms).expect("Miniscript must be sane"); + descriptor::Descriptor::Wsh(descriptor::Wsh::new(ms).expect("Must pass sanity checks")) } } diff --git a/src/descriptors/mod.rs b/src/descriptors/mod.rs index b8d169ea..c6767de3 100644 --- a/src/descriptors/mod.rs +++ b/src/descriptors/mod.rs @@ -421,7 +421,7 @@ mod tests { ], ); let policy = LianaPolicy::new(primary_keys, recovery_keys, 26352).unwrap(); - assert_eq!(LianaDescriptor::new(policy).to_string(), "wsh(or_d(multi(3,[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*,[aabb0011/10/4893]xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/<0;1>/*,[abcdef01]xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/<0;1>/*),and_v(v:multi(2,[abcdef01]xpub69cP4Y7S9TWcbSNxmk6CEDBsoaqr3ZEdjHuZcHxEFFKGh569RsJNr2V27XGhsbH9FXgWUEmKXRN7c5wQfq2VPjt31xP9VsYnVUyU8HcVevm/<0;1>/*,[abcdef01]xpub6AA2N8RALRYgLD6jT1iXYCEDkndTeZndMtWPbtNX6sY5dPiLtf2T88ahdxrGXMUPoNadgR86sFhBXWQVgifPzDYbY9ZtwK4gqzx4y5Da1DW/<0;1>/*,[aabb0011/10/4893]xpub6AyxexvxizZJffF153evmfqHcE9MV88fCNCAtP3jQjXJHwrAKri71Tq9jWUkPxj9pja4u6AkCPHY7atgxzSEa2HtDwJfrRWKK4fsfQg4o77/<0;1>/*),older(26352))))#s0zsa6uc"); + assert_eq!(LianaDescriptor::new(policy).to_string(), "wsh(or_d(multi(3,[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*,[aabb0011/10/4893]xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/<0;1>/*,[abcdef01]xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/<0;1>/*),and_v(v:thresh(2,pkh([abcdef01]xpub69cP4Y7S9TWcbSNxmk6CEDBsoaqr3ZEdjHuZcHxEFFKGh569RsJNr2V27XGhsbH9FXgWUEmKXRN7c5wQfq2VPjt31xP9VsYnVUyU8HcVevm/<0;1>/*),a:pkh([abcdef01]xpub6AA2N8RALRYgLD6jT1iXYCEDkndTeZndMtWPbtNX6sY5dPiLtf2T88ahdxrGXMUPoNadgR86sFhBXWQVgifPzDYbY9ZtwK4gqzx4y5Da1DW/<0;1>/*),a:pkh([aabb0011/10/4893]xpub6AyxexvxizZJffF153evmfqHcE9MV88fCNCAtP3jQjXJHwrAKri71Tq9jWUkPxj9pja4u6AkCPHY7atgxzSEa2HtDwJfrRWKK4fsfQg4o77/<0;1>/*)),older(26352))))#hmsqemgr"); // We prevent footguns with timelocks by requiring a u16. Note how the following wouldn't // compile: