diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index ab30b4dcf..c96b853b0 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -388,15 +388,15 @@ where /// Parse an expression tree into a descriptor fn from_tree(top: &expression::Tree) -> Result, Error> { match (top.name, top.args.len() as u32) { - ("pk", 1) => { - expression::terminal(&top.args[0], |pk| Pk::from_str(pk).map(Descriptor::Pk)) - } - ("pkh", 1) => { - expression::terminal(&top.args[0], |pk| Pk::from_str(pk).map(Descriptor::Pkh)) - } - ("wpkh", 1) => { - expression::terminal(&top.args[0], |pk| Pk::from_str(pk).map(Descriptor::Wpkh)) - } + ("pk", 1) => expression::terminal(&top.args[0], |pk| { + MiniscriptKey::from_str(pk, false).map(Descriptor::Pk) + }), + ("pkh", 1) => expression::terminal(&top.args[0], |pk| { + MiniscriptKey::from_str(pk, false).map(Descriptor::Pkh) + }), + ("wpkh", 1) => expression::terminal(&top.args[0], |pk| { + MiniscriptKey::from_str(pk, true).map(Descriptor::Wpkh) + }), ("sh", 1) => { let newtop = &top.args[0]; match (newtop.name, newtop.args.len()) { @@ -405,7 +405,7 @@ where Ok(Descriptor::ShWsh(sub)) } ("wpkh", 1) => expression::terminal(&newtop.args[0], |pk| { - Pk::from_str(pk).map(Descriptor::ShWpkh) + MiniscriptKey::from_str(pk, true).map(Descriptor::ShWpkh) }), _ => { let sub = Miniscript::from_tree(&top.args[0])?; @@ -618,6 +618,16 @@ mod tests { "bc1qsn57m9drscflq5nl76z6ny52hck5w4x5wqd9yt" ); + // Only compressed pubkeys are allowed in wpkh(KEY) + assert_eq!( + StdDescriptor::from_str( + "wpkh(\ + 044f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa385b6b1b8ead809ca67454d9683fcf2ba03456d6fe2c4abe2b07f0fbdbb2f1c1\ + )", + ).unwrap_err().to_string(), + "unexpected «base58 error: Uncompressed public keys not allowed in Segwit descriptors»" + ); + let shwpkh = StdDescriptor::from_str( "sh(wpkh(\ 020000000000000000000000000000000000000000000000000000000000000002\ diff --git a/src/lib.rs b/src/lib.rs index bd8d10465..ded277fa0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,6 +102,8 @@ use std::{error, fmt, hash, str}; use bitcoin::blockdata::{opcodes, script}; use bitcoin::hashes::{hash160, sha256, Hash}; +use bitcoin::util::base58; +use bitcoin::util::key::Error::Base58; pub use descriptor::{Descriptor, SatisfiedConstraints}; pub use miniscript::decode::Terminal; @@ -116,6 +118,10 @@ pub trait MiniscriptKey: ///Converts an object to PublicHash fn to_pubkeyhash(&self) -> Self::Hash; + + fn from_str(s: &str, _compressed_only: bool) -> Result { + str::FromStr::from_str(s) + } } impl MiniscriptKey for bitcoin::PublicKey { @@ -126,6 +132,17 @@ impl MiniscriptKey for bitcoin::PublicKey { self.write_into(&mut engine); hash160::Hash::from_engine(engine) } + + fn from_str(s: &str, compressed_only: bool) -> Result { + let r: bitcoin::PublicKey = str::FromStr::from_str(s)?; + if compressed_only && !r.compressed { + Err(Base58(base58::Error::Other( + "Uncompressed public keys not allowed in Segwit descriptors".to_owned(), + ))) + } else { + Ok(r) + } + } } impl MiniscriptKey for String { @@ -207,10 +224,8 @@ impl fmt::Display for DummyKey { impl ToPublicKey for DummyKey { fn to_public_key(&self) -> bitcoin::PublicKey { - bitcoin::PublicKey::from_str( - "0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352", - ) - .unwrap() + str::FromStr::from_str("0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352") + .unwrap() } fn hash_to_hash160(_: &DummyKeyHash) -> hash160::Hash { diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index d85ec2662..3a809edb7 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -364,9 +364,9 @@ where } } let mut unwrapped = match (frag_name, top.args.len()) { - ("pk_k", 1) => { - expression::terminal(&top.args[0], |x| Pk::from_str(x).map(Terminal::PkK)) - } + ("pk_k", 1) => expression::terminal(&top.args[0], |x| { + MiniscriptKey::from_str(x, false).map(Terminal::PkK) + }), ("pk_h", 1) => { expression::terminal(&top.args[0], |x| Pk::Hash::from_str(x).map(Terminal::PkH)) } @@ -458,7 +458,7 @@ where let pks: Result, _> = top.args[1..] .iter() - .map(|sub| expression::terminal(sub, Pk::from_str)) + .map(|sub| expression::terminal(sub, str::FromStr::from_str)) .collect(); pks.map(|pks| Terminal::Multi(k, pks)) diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index 41efa1450..8a3eb1004 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -498,10 +498,11 @@ mod tests { #[test] fn basic() { - let pk = bitcoin::PublicKey::from_str( + let pk = MiniscriptKey::from_str( "\ 020202020202020202020202020202020202020202020202020202020202020202\ ", + false, ) .unwrap(); let hash = hash160::Hash::from_inner([17; 20]); @@ -586,7 +587,7 @@ mod tests { string_rtt( script, - "[B/onduesm]c:[K/onduesm]pk_k(PublicKey { compressed: true, key: PublicKey(aa4c32e50fb34a95a372940ae3654b692ea35294748c3dd2c08b29f87ba9288c8294efcb73dc719e45b91c45f084e77aebc07c1ff3ed8f37935130a36304a340) })", + "[B/onduesm]c:[K/onduesm]pk_k(PublicKey { compressed: true, key: PublicKey(aa4c32e50fb34a95a372940ae3654b692ea35294748c3dd2c08b29f87ba9288c8294efcb73dc719e45b91c45f084e77aebc07c1ff3ed8f37935130a36304a340) })", "pk(028c28a97bf8298bc0d23d8c749452a32e694b65e30a9472a3954ab30fe5324caa)" ); @@ -594,7 +595,7 @@ mod tests { string_rtt( script, - "[B/onduesm]c:[K/onduesm]pk_k(PublicKey { compressed: true, key: PublicKey(aa4c32e50fb34a95a372940ae3654b692ea35294748c3dd2c08b29f87ba9288c8294efcb73dc719e45b91c45f084e77aebc07c1ff3ed8f37935130a36304a340) })", + "[B/onduesm]c:[K/onduesm]pk_k(PublicKey { compressed: true, key: PublicKey(aa4c32e50fb34a95a372940ae3654b692ea35294748c3dd2c08b29f87ba9288c8294efcb73dc719e45b91c45f084e77aebc07c1ff3ed8f37935130a36304a340) })", "pk(028c28a97bf8298bc0d23d8c749452a32e694b65e30a9472a3954ab30fe5324caa)" ); } diff --git a/src/policy/concrete.rs b/src/policy/concrete.rs index a7501b67e..aed322d98 100644 --- a/src/policy/concrete.rs +++ b/src/policy/concrete.rs @@ -381,7 +381,9 @@ where } } match (frag_name, top.args.len() as u32) { - ("pk", 1) => expression::terminal(&top.args[0], |pk| Pk::from_str(pk).map(Policy::Key)), + ("pk", 1) => expression::terminal(&top.args[0], |pk| { + MiniscriptKey::from_str(pk, false).map(Policy::Key) + }), ("after", 1) => expression::terminal(&top.args[0], |x| { expression::parse_num(x).map(Policy::After) }),