Skip to content

Commit 360afcd

Browse files
committed
Do not allow uncompressed pubkey in wpkh(KEY) output descriptor
> wpkh(KEY) (not inside wsh): P2WPKH output for the given compressed pubkey. https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md
1 parent 7248f32 commit 360afcd

File tree

5 files changed

+50
-22
lines changed

5 files changed

+50
-22
lines changed

src/descriptor/mod.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -388,15 +388,15 @@ where
388388
/// Parse an expression tree into a descriptor
389389
fn from_tree(top: &expression::Tree) -> Result<Descriptor<Pk>, Error> {
390390
match (top.name, top.args.len() as u32) {
391-
("pk", 1) => {
392-
expression::terminal(&top.args[0], |pk| Pk::from_str(pk).map(Descriptor::Pk))
393-
}
394-
("pkh", 1) => {
395-
expression::terminal(&top.args[0], |pk| Pk::from_str(pk).map(Descriptor::Pkh))
396-
}
397-
("wpkh", 1) => {
398-
expression::terminal(&top.args[0], |pk| Pk::from_str(pk).map(Descriptor::Wpkh))
399-
}
391+
("pk", 1) => expression::terminal(&top.args[0], |pk| {
392+
MiniscriptKey::from_str(pk, false).map(Descriptor::Pk)
393+
}),
394+
("pkh", 1) => expression::terminal(&top.args[0], |pk| {
395+
MiniscriptKey::from_str(pk, false).map(Descriptor::Pkh)
396+
}),
397+
("wpkh", 1) => expression::terminal(&top.args[0], |pk| {
398+
MiniscriptKey::from_str(pk, true).map(Descriptor::Wpkh)
399+
}),
400400
("sh", 1) => {
401401
let newtop = &top.args[0];
402402
match (newtop.name, newtop.args.len()) {
@@ -405,7 +405,7 @@ where
405405
Ok(Descriptor::ShWsh(sub))
406406
}
407407
("wpkh", 1) => expression::terminal(&newtop.args[0], |pk| {
408-
Pk::from_str(pk).map(Descriptor::ShWpkh)
408+
str::FromStr::from_str(pk).map(Descriptor::ShWpkh)
409409
}),
410410
_ => {
411411
let sub = Miniscript::from_tree(&top.args[0])?;
@@ -618,6 +618,16 @@ mod tests {
618618
"bc1qsn57m9drscflq5nl76z6ny52hck5w4x5wqd9yt"
619619
);
620620

621+
// Only compressed pubkeys are allowed in wpkh(KEY)
622+
assert_eq!(
623+
StdDescriptor::from_str(
624+
"wpkh(\
625+
044f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa385b6b1b8ead809ca67454d9683fcf2ba03456d6fe2c4abe2b07f0fbdbb2f1c1\
626+
)",
627+
).unwrap_err().to_string(),
628+
"unexpected «base58 error: Uncompressed pubkeys are not allowed»"
629+
);
630+
621631
let shwpkh = StdDescriptor::from_str(
622632
"sh(wpkh(\
623633
020000000000000000000000000000000000000000000000000000000000000002\

src/lib.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ use std::{error, fmt, hash, str};
102102

103103
use bitcoin::blockdata::{opcodes, script};
104104
use bitcoin::hashes::{hash160, sha256, Hash};
105+
use bitcoin::util::base58;
106+
use bitcoin::util::key::Error::Base58;
105107

106108
pub use descriptor::{Descriptor, SatisfiedConstraints};
107109
pub use miniscript::decode::Terminal;
@@ -116,6 +118,10 @@ pub trait MiniscriptKey:
116118

117119
///Converts an object to PublicHash
118120
fn to_pubkeyhash(&self) -> Self::Hash;
121+
122+
fn from_str(s: &str, _compressed_only: bool) -> Result<Self, Self::Err> {
123+
str::FromStr::from_str(s)
124+
}
119125
}
120126

121127
impl MiniscriptKey for bitcoin::PublicKey {
@@ -126,6 +132,17 @@ impl MiniscriptKey for bitcoin::PublicKey {
126132
self.write_into(&mut engine);
127133
hash160::Hash::from_engine(engine)
128134
}
135+
136+
fn from_str(s: &str, compressed_only: bool) -> Result<Self, Self::Err> {
137+
let r: bitcoin::PublicKey = str::FromStr::from_str(s)?;
138+
if compressed_only && !r.compressed {
139+
Err(Base58(base58::Error::Other(
140+
"Uncompressed public keys not allowed in Segwit descriptors".to_owned(),
141+
)))
142+
} else {
143+
Ok(r)
144+
}
145+
}
129146
}
130147

131148
impl MiniscriptKey for String {
@@ -207,10 +224,8 @@ impl fmt::Display for DummyKey {
207224

208225
impl ToPublicKey for DummyKey {
209226
fn to_public_key(&self) -> bitcoin::PublicKey {
210-
bitcoin::PublicKey::from_str(
211-
"0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352",
212-
)
213-
.unwrap()
227+
str::FromStr::from_str("0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352")
228+
.unwrap()
214229
}
215230

216231
fn hash_to_hash160(_: &DummyKeyHash) -> hash160::Hash {

src/miniscript/astelem.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -364,9 +364,9 @@ where
364364
}
365365
}
366366
let mut unwrapped = match (frag_name, top.args.len()) {
367-
("pk_k", 1) => {
368-
expression::terminal(&top.args[0], |x| Pk::from_str(x).map(Terminal::PkK))
369-
}
367+
("pk_k", 1) => expression::terminal(&top.args[0], |x| {
368+
str::FromStr::from_str(x).map(Terminal::PkK)
369+
}),
370370
("pk_h", 1) => {
371371
expression::terminal(&top.args[0], |x| Pk::Hash::from_str(x).map(Terminal::PkH))
372372
}
@@ -458,7 +458,7 @@ where
458458

459459
let pks: Result<Vec<Pk>, _> = top.args[1..]
460460
.iter()
461-
.map(|sub| expression::terminal(sub, Pk::from_str))
461+
.map(|sub| expression::terminal(sub, str::FromStr::from_str))
462462
.collect();
463463

464464
pks.map(|pks| Terminal::Multi(k, pks))

src/miniscript/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -498,10 +498,11 @@ mod tests {
498498

499499
#[test]
500500
fn basic() {
501-
let pk = bitcoin::PublicKey::from_str(
501+
let pk = MiniscriptKey::from_str(
502502
"\
503503
020202020202020202020202020202020202020202020202020202020202020202\
504504
",
505+
false,
505506
)
506507
.unwrap();
507508
let hash = hash160::Hash::from_inner([17; 20]);
@@ -586,15 +587,15 @@ mod tests {
586587

587588
string_rtt(
588589
script,
589-
"[B/onduesm]c:[K/onduesm]pk_k(PublicKey { compressed: true, key: PublicKey(aa4c32e50fb34a95a372940ae3654b692ea35294748c3dd2c08b29f87ba9288c8294efcb73dc719e45b91c45f084e77aebc07c1ff3ed8f37935130a36304a340) })",
590+
"[B/onduesm]c:[K/onduesm]pk_k(PublicKey { compressed: true, key: PublicKey(aa4c32e50fb34a95a372940ae3654b692ea35294748c3dd2c08b29f87ba9288c8294efcb73dc719e45b91c45f084e77aebc07c1ff3ed8f37935130a36304a340) })",
590591
"pk(028c28a97bf8298bc0d23d8c749452a32e694b65e30a9472a3954ab30fe5324caa)"
591592
);
592593

593594
let script: Miniscript<bitcoin::PublicKey> = ms_str!("pk({})", pubkey.to_string());
594595

595596
string_rtt(
596597
script,
597-
"[B/onduesm]c:[K/onduesm]pk_k(PublicKey { compressed: true, key: PublicKey(aa4c32e50fb34a95a372940ae3654b692ea35294748c3dd2c08b29f87ba9288c8294efcb73dc719e45b91c45f084e77aebc07c1ff3ed8f37935130a36304a340) })",
598+
"[B/onduesm]c:[K/onduesm]pk_k(PublicKey { compressed: true, key: PublicKey(aa4c32e50fb34a95a372940ae3654b692ea35294748c3dd2c08b29f87ba9288c8294efcb73dc719e45b91c45f084e77aebc07c1ff3ed8f37935130a36304a340) })",
598599
"pk(028c28a97bf8298bc0d23d8c749452a32e694b65e30a9472a3954ab30fe5324caa)"
599600
);
600601
}

src/policy/concrete.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,9 @@ where
381381
}
382382
}
383383
match (frag_name, top.args.len() as u32) {
384-
("pk", 1) => expression::terminal(&top.args[0], |pk| Pk::from_str(pk).map(Policy::Key)),
384+
("pk", 1) => expression::terminal(&top.args[0], |pk| {
385+
str::FromStr::from_str(pk).map(Policy::Key)
386+
}),
385387
("after", 1) => expression::terminal(&top.args[0], |x| {
386388
expression::parse_num(x).map(Policy::After)
387389
}),

0 commit comments

Comments
 (0)