Skip to content

Commit f83eb64

Browse files
committed
Merge #660: Introduce Threshold type and use it in SortedMulti as a preview
50bf404 policy: use new Threshold type in semantic policy (Andrew Poelstra) 5964ec3 sortedmulti: use new Threshold type internally (Andrew Poelstra) 36ea5cc expression: add method to validate and construct a threshold (Andrew Poelstra) 452615b rename expression.rs to expression/mod.rs (Andrew Poelstra) dbf7b32 primitives: introduce Threshold type (Andrew Poelstra) 30a3b59 psbt: fix "redundant target" doc warning (Andrew Poelstra) Pull request description: I have a big branch which uses this type throughout the codebase. I split it up into pieces (separate commit for its use in each policy type, for its use in multi/multi_a, for its use in Terminal::thresh, etc) but it's still a pretty big diff. Despite the size of the diff I think this is definitely worth doing -- in my current branch we have eliminated a ton of error paths, including a ton of badly-typed ones (grepping the codebase for `errstr` shows I've reduced it from 30 instances to 18). This PR simply introduces the new `Threshold` type and uses it in `SortedMultiVec` to show how the API works. Later PRs will do the more invasive stuff. ACKs for top commit: tcharding: ACK 50bf404 sanket1729: ACK 50bf404. Tree-SHA512: 084034f43a66cb6e73446549aad1e1a01f7f0067e7ab3b401f8d819f7375f7a0f6b695c013e3917f550d07b579dcd8ca21adf6dd854bb82c824911e8d1ead152
2 parents a548edd + 50bf404 commit f83eb64

File tree

10 files changed

+623
-252
lines changed

10 files changed

+623
-252
lines changed

src/descriptor/mod.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -1042,18 +1042,17 @@ mod tests {
10421042
StdDescriptor::from_str("(\u{7f}()3").unwrap_err();
10431043
StdDescriptor::from_str("pk()").unwrap_err();
10441044
StdDescriptor::from_str("nl:0").unwrap_err(); //issue 63
1045-
let compressed_pk = String::from("");
10461045
assert_eq!(
10471046
StdDescriptor::from_str("sh(sortedmulti)")
10481047
.unwrap_err()
10491048
.to_string(),
1050-
"unexpected «no arguments given for sortedmulti»"
1049+
"expected threshold, found terminal",
10511050
); //issue 202
10521051
assert_eq!(
1053-
StdDescriptor::from_str(&format!("sh(sortedmulti(2,{}))", compressed_pk))
1052+
StdDescriptor::from_str(&format!("sh(sortedmulti(2,{}))", &TEST_PK[3..69]))
10541053
.unwrap_err()
10551054
.to_string(),
1056-
"unexpected «higher threshold than there were keys in sortedmulti»"
1055+
"invalid threshold 2-of-1; cannot have k > n",
10571056
); //issue 202
10581057

10591058
StdDescriptor::from_str(TEST_PK).unwrap();

src/descriptor/sortedmulti.rs

+61-59
Original file line numberDiff line numberDiff line change
@@ -19,62 +19,55 @@ use crate::plan::AssetProvider;
1919
use crate::prelude::*;
2020
use crate::sync::Arc;
2121
use crate::{
22-
errstr, expression, policy, script_num_size, Error, ForEachKey, Miniscript, MiniscriptKey,
23-
Satisfier, ToPublicKey, TranslateErr, Translator,
22+
expression, policy, script_num_size, Error, ForEachKey, Miniscript, MiniscriptKey, Satisfier,
23+
Threshold, ToPublicKey, TranslateErr, Translator,
2424
};
2525

2626
/// Contents of a "sortedmulti" descriptor
2727
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2828
pub struct SortedMultiVec<Pk: MiniscriptKey, Ctx: ScriptContext> {
29-
/// signatures required
30-
pub k: usize,
31-
/// public keys inside sorted Multi
32-
pub pks: Vec<Pk>,
29+
inner: Threshold<Pk, MAX_PUBKEYS_PER_MULTISIG>,
3330
/// The current ScriptContext for sortedmulti
34-
pub(crate) phantom: PhantomData<Ctx>,
31+
phantom: PhantomData<Ctx>,
3532
}
3633

3734
impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
38-
/// Create a new instance of `SortedMultiVec` given a list of keys and the threshold
39-
///
40-
/// Internally checks all the applicable size limits and pubkey types limitations according to the current `Ctx`.
41-
pub fn new(k: usize, pks: Vec<Pk>) -> Result<Self, Error> {
42-
// A sortedmulti() is only defined for <= 20 keys (it maps to CHECKMULTISIG)
43-
if pks.len() > MAX_PUBKEYS_PER_MULTISIG {
44-
return Err(Error::BadDescriptor("Too many public keys".to_string()));
45-
}
46-
35+
fn constructor_check(&self) -> Result<(), Error> {
4736
// Check the limits before creating a new SortedMultiVec
4837
// For example, under p2sh context the scriptlen can only be
4938
// upto 520 bytes.
50-
let term: Terminal<Pk, Ctx> = Terminal::Multi(k, pks.clone());
39+
let term: Terminal<Pk, Ctx> = Terminal::Multi(self.inner.k(), self.inner.data().to_owned());
5140
let ms = Miniscript::from_ast(term)?;
52-
5341
// This would check all the consensus rules for p2sh/p2wsh and
5442
// even tapscript in future
55-
Ctx::check_local_validity(&ms)?;
43+
Ctx::check_local_validity(&ms).map_err(From::from)
44+
}
5645

57-
Ok(Self { k, pks, phantom: PhantomData })
46+
/// Create a new instance of `SortedMultiVec` given a list of keys and the threshold
47+
///
48+
/// Internally checks all the applicable size limits and pubkey types limitations according to the current `Ctx`.
49+
pub fn new(k: usize, pks: Vec<Pk>) -> Result<Self, Error> {
50+
let ret =
51+
Self { inner: Threshold::new(k, pks).map_err(Error::Threshold)?, phantom: PhantomData };
52+
ret.constructor_check()?;
53+
Ok(ret)
5854
}
55+
5956
/// Parse an expression tree into a SortedMultiVec
6057
pub fn from_tree(tree: &expression::Tree) -> Result<Self, Error>
6158
where
6259
Pk: FromStr,
6360
<Pk as FromStr>::Err: fmt::Display,
6461
{
65-
if tree.args.is_empty() {
66-
return Err(errstr("no arguments given for sortedmulti"));
67-
}
68-
let k = expression::parse_num(tree.args[0].name)?;
69-
if k > (tree.args.len() - 1) as u32 {
70-
return Err(errstr("higher threshold than there were keys in sortedmulti"));
71-
}
72-
let pks: Result<Vec<Pk>, _> = tree.args[1..]
73-
.iter()
74-
.map(|sub| expression::terminal(sub, Pk::from_str))
75-
.collect();
76-
77-
pks.map(|pks| SortedMultiVec::new(k as usize, pks))?
62+
let ret = Self {
63+
inner: tree
64+
.to_null_threshold()
65+
.map_err(Error::ParseThreshold)?
66+
.translate_by_index(|i| expression::terminal(&tree.args[i + 1], Pk::from_str))?,
67+
phantom: PhantomData,
68+
};
69+
ret.constructor_check()?;
70+
Ok(ret)
7871
}
7972

8073
/// This will panic if fpk returns an uncompressed key when
@@ -88,23 +81,39 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
8881
T: Translator<Pk, Q, FuncError>,
8982
Q: MiniscriptKey,
9083
{
91-
let pks: Result<Vec<Q>, _> = self.pks.iter().map(|pk| t.pk(pk)).collect();
92-
let res = SortedMultiVec::new(self.k, pks?).map_err(TranslateErr::OuterError)?;
93-
Ok(res)
84+
let ret = SortedMultiVec {
85+
inner: self.inner.translate_ref(|pk| t.pk(pk))?,
86+
phantom: PhantomData,
87+
};
88+
ret.constructor_check().map_err(TranslateErr::OuterError)?;
89+
Ok(ret)
9490
}
91+
92+
/// The threshold value for the multisig.
93+
pub fn k(&self) -> usize { self.inner.k() }
94+
95+
/// The number of keys in the multisig.
96+
pub fn n(&self) -> usize { self.inner.n() }
97+
98+
/// Accessor for the public keys in the multisig.
99+
///
100+
/// The keys in this structure might **not** be sorted. In general, they cannot be
101+
/// sorted until they are converted to consensus-encoded public keys, which may not
102+
/// be possible (for example for BIP32 paths with unfilled wildcards).
103+
pub fn pks(&self) -> &[Pk] { self.inner.data() }
95104
}
96105

97106
impl<Pk: MiniscriptKey, Ctx: ScriptContext> ForEachKey<Pk> for SortedMultiVec<Pk, Ctx> {
98107
fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, pred: F) -> bool {
99-
self.pks.iter().all(pred)
108+
self.pks().iter().all(pred)
100109
}
101110
}
102111

103112
impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
104113
/// utility function to sanity a sorted multi vec
105114
pub fn sanity_check(&self) -> Result<(), Error> {
106115
let ms: Miniscript<Pk, Ctx> =
107-
Miniscript::from_ast(Terminal::Multi(self.k, self.pks.clone()))
116+
Miniscript::from_ast(Terminal::Multi(self.k(), self.pks().to_owned()))
108117
.expect("Must typecheck");
109118
// '?' for doing From conversion
110119
ms.sanity_check()?;
@@ -118,7 +127,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
118127
where
119128
Pk: ToPublicKey,
120129
{
121-
let mut pks = self.pks.clone();
130+
let mut pks = self.pks().to_owned();
122131
// Sort pubkeys lexicographically according to BIP 67
123132
pks.sort_by(|a, b| {
124133
a.to_public_key()
@@ -127,7 +136,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
127136
.partial_cmp(&b.to_public_key().inner.serialize())
128137
.unwrap()
129138
});
130-
Terminal::Multi(self.k, pks)
139+
Terminal::Multi(self.k(), pks)
131140
}
132141

133142
/// Encode as a Bitcoin script
@@ -169,10 +178,10 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
169178
/// to instead call the corresponding function on a `Descriptor`, which
170179
/// will handle the segwit/non-segwit technicalities for you.
171180
pub fn script_size(&self) -> usize {
172-
script_num_size(self.k)
181+
script_num_size(self.k())
173182
+ 1
174-
+ script_num_size(self.pks.len())
175-
+ self.pks.iter().map(|pk| Ctx::pk_len(pk)).sum::<usize>()
183+
+ script_num_size(self.n())
184+
+ self.pks().iter().map(|pk| Ctx::pk_len(pk)).sum::<usize>()
176185
}
177186

178187
/// Maximum number of witness elements used to satisfy the Miniscript
@@ -183,7 +192,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
183192
/// This function may panic on malformed `Miniscript` objects which do
184193
/// not correspond to semantically sane Scripts. (Such scripts should be
185194
/// rejected at parse time. Any exceptions are bugs.)
186-
pub fn max_satisfaction_witness_elements(&self) -> usize { 2 + self.k }
195+
pub fn max_satisfaction_witness_elements(&self) -> usize { 2 + self.k() }
187196

188197
/// Maximum size, in bytes, of a satisfying witness.
189198
/// In general, it is not recommended to use this function directly, but
@@ -193,19 +202,16 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
193202
/// All signatures are assumed to be 73 bytes in size, including the
194203
/// length prefix (segwit) or push opcode (pre-segwit) and sighash
195204
/// postfix.
196-
pub fn max_satisfaction_size(&self) -> usize { 1 + 73 * self.k }
205+
pub fn max_satisfaction_size(&self) -> usize { 1 + 73 * self.k() }
197206
}
198207

199208
impl<Pk: MiniscriptKey, Ctx: ScriptContext> policy::Liftable<Pk> for SortedMultiVec<Pk, Ctx> {
200209
fn lift(&self) -> Result<policy::semantic::Policy<Pk>, Error> {
201-
let ret = policy::semantic::Policy::Thresh(
202-
self.k,
203-
self.pks
204-
.iter()
205-
.map(|k| Arc::new(policy::semantic::Policy::Key(k.clone())))
206-
.collect(),
207-
);
208-
Ok(ret)
210+
Ok(policy::semantic::Policy::Thresh(
211+
self.inner
212+
.map_ref(|pk| Arc::new(policy::semantic::Policy::Key(pk.clone())))
213+
.forget_maximum(),
214+
))
209215
}
210216
}
211217

@@ -215,11 +221,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> fmt::Debug for SortedMultiVec<Pk, Ct
215221

216222
impl<Pk: MiniscriptKey, Ctx: ScriptContext> fmt::Display for SortedMultiVec<Pk, Ctx> {
217223
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
218-
write!(f, "sortedmulti({}", self.k)?;
219-
for k in &self.pks {
220-
write!(f, ",{}", k)?;
221-
}
222-
f.write_str(")")
224+
fmt::Display::fmt(&self.inner.display("sortedmulti", true), f)
223225
}
224226
}
225227

@@ -249,7 +251,7 @@ mod tests {
249251
let error = res.expect_err("constructor should err");
250252

251253
match error {
252-
Error::BadDescriptor(_) => {} // ok
254+
Error::Threshold(_) => {} // ok
253255
other => panic!("unexpected error: {:?}", other),
254256
}
255257
}

src/descriptor/tr.rs

+6-10
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use crate::policy::Liftable;
2323
use crate::prelude::*;
2424
use crate::util::{varint_len, witness_size};
2525
use crate::{
26-
errstr, Error, ForEachKey, FromStrKey, MiniscriptKey, Satisfier, ScriptContext, Tap,
26+
errstr, Error, ForEachKey, FromStrKey, MiniscriptKey, Satisfier, ScriptContext, Tap, Threshold,
2727
ToPublicKey, TranslateErr, TranslatePk, Translator,
2828
};
2929

@@ -620,8 +620,7 @@ impl<Pk: MiniscriptKey> Liftable<Pk> for TapTree<Pk> {
620620
fn lift_helper<Pk: MiniscriptKey>(s: &TapTree<Pk>) -> Result<Policy<Pk>, Error> {
621621
match *s {
622622
TapTree::Tree { ref left, ref right, height: _ } => Ok(Policy::Thresh(
623-
1,
624-
vec![Arc::new(lift_helper(left)?), Arc::new(lift_helper(right)?)],
623+
Threshold::or(Arc::new(lift_helper(left)?), Arc::new(lift_helper(right)?)),
625624
)),
626625
TapTree::Leaf(ref leaf) => leaf.lift(),
627626
}
@@ -635,13 +634,10 @@ impl<Pk: MiniscriptKey> Liftable<Pk> for TapTree<Pk> {
635634
impl<Pk: MiniscriptKey> Liftable<Pk> for Tr<Pk> {
636635
fn lift(&self) -> Result<Policy<Pk>, Error> {
637636
match &self.tree {
638-
Some(root) => Ok(Policy::Thresh(
639-
1,
640-
vec![
641-
Arc::new(Policy::Key(self.internal_key.clone())),
642-
Arc::new(root.lift()?),
643-
],
644-
)),
637+
Some(root) => Ok(Policy::Thresh(Threshold::or(
638+
Arc::new(Policy::Key(self.internal_key.clone())),
639+
Arc::new(root.lift()?),
640+
))),
645641
None => Ok(Policy::Key(self.internal_key.clone())),
646642
}
647643
}

src/expression/error.rs

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
3+
//! Expression-related errors
4+
5+
use core::fmt;
6+
7+
use crate::prelude::*;
8+
use crate::ThresholdError;
9+
10+
/// Error parsing a threshold expression.
11+
#[derive(Clone, Debug, PartialEq, Eq)]
12+
pub enum ParseThresholdError {
13+
/// Expression had no children, not even a threshold value.
14+
NoChildren,
15+
/// The threshold value appeared to be a sub-expression rather than a number.
16+
KNotTerminal,
17+
/// Failed to parse the threshold value.
18+
// FIXME this should be a more specific type. Will be handled in a later PR
19+
// that rewrites the expression parsing logic.
20+
ParseK(String),
21+
/// Threshold parameters were invalid.
22+
Threshold(ThresholdError),
23+
}
24+
25+
impl fmt::Display for ParseThresholdError {
26+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27+
use ParseThresholdError::*;
28+
29+
match *self {
30+
NoChildren => f.write_str("expected threshold, found terminal"),
31+
KNotTerminal => f.write_str("expected positive integer, found expression"),
32+
ParseK(ref x) => write!(f, "failed to parse threshold value {}", x),
33+
Threshold(ref e) => e.fmt(f),
34+
}
35+
}
36+
}
37+
38+
#[cfg(feature = "std")]
39+
impl std::error::Error for ParseThresholdError {
40+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
41+
use ParseThresholdError::*;
42+
43+
match *self {
44+
NoChildren => None,
45+
KNotTerminal => None,
46+
ParseK(..) => None,
47+
Threshold(ref e) => Some(e),
48+
}
49+
}
50+
}

src/expression.rs src/expression/mod.rs

+34-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22

33
//! # Function-like Expression Language
44
//!
5+
6+
mod error;
7+
58
use core::fmt;
69
use core::str::FromStr;
710

11+
pub use self::error::ParseThresholdError;
812
use crate::prelude::*;
9-
use crate::{errstr, Error, MAX_RECURSION_DEPTH};
13+
use crate::{errstr, Error, Threshold, MAX_RECURSION_DEPTH};
1014

1115
/// Allowed characters are descriptor strings.
1216
pub const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
@@ -185,6 +189,35 @@ impl<'a> Tree<'a> {
185189
Err(errstr(rem))
186190
}
187191
}
192+
193+
/// Parses an expression tree as a threshold (a term with at least one child,
194+
/// the first of which is a positive integer k).
195+
///
196+
/// This sanity-checks that the threshold is well-formed (begins with a valid
197+
/// threshold value, etc.) but does not parse the children of the threshold.
198+
/// Instead it returns a threshold holding the empty type `()`, which is
199+
/// constructed without any allocations, and expects the caller to convert
200+
/// this to the "real" threshold type by calling [`Threshold::translate`].
201+
///
202+
/// (An alternate API which does the conversion inline turned out to be
203+
/// too messy; it needs to take a closure, have multiple generic parameters,
204+
/// and be able to return multiple error types.)
205+
pub fn to_null_threshold<const MAX: usize>(
206+
&self,
207+
) -> Result<Threshold<(), MAX>, ParseThresholdError> {
208+
// First, special case "no arguments" so we can index the first argument without panics.
209+
if self.args.is_empty() {
210+
return Err(ParseThresholdError::NoChildren);
211+
}
212+
213+
if !self.args[0].args.is_empty() {
214+
return Err(ParseThresholdError::KNotTerminal);
215+
}
216+
217+
let k = parse_num(self.args[0].name)
218+
.map_err(|e| ParseThresholdError::ParseK(e.to_string()))? as usize;
219+
Threshold::new(k, vec![(); self.args.len() - 1]).map_err(ParseThresholdError::Threshold)
220+
}
188221
}
189222

190223
/// Filter out non-ASCII because we byte-index strings all over the

0 commit comments

Comments
 (0)