Skip to content

Commit dbf7b32

Browse files
committed
primitives: introduce Threshold type
1 parent 30a3b59 commit dbf7b32

File tree

3 files changed

+294
-0
lines changed

3 files changed

+294
-0
lines changed

src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ pub use crate::miniscript::{hash256, Miniscript};
152152
use crate::prelude::*;
153153
pub use crate::primitives::absolute_locktime::{AbsLockTime, AbsLockTimeError};
154154
pub use crate::primitives::relative_locktime::{RelLockTime, RelLockTimeError};
155+
pub use crate::primitives::threshold::{Threshold, ThresholdError};
155156

156157
/// Public key trait which can be converted to Hash type
157158
pub trait MiniscriptKey: Clone + Eq + Ord + fmt::Debug + fmt::Display + hash::Hash {
@@ -492,6 +493,8 @@ pub enum Error {
492493
AbsoluteLockTime(AbsLockTimeError),
493494
/// Invalid absolute locktime
494495
RelativeLockTime(RelLockTimeError),
496+
/// Invalid threshold.
497+
Threshold(ThresholdError),
495498
}
496499

497500
// https://github.com/sipa/miniscript/pull/5 for discussion on this number
@@ -549,6 +552,7 @@ impl fmt::Display for Error {
549552
Error::MultipathDescLenMismatch => write!(f, "At least two BIP389 key expressions in the descriptor contain tuples of derivation indexes of different lengths"),
550553
Error::AbsoluteLockTime(ref e) => e.fmt(f),
551554
Error::RelativeLockTime(ref e) => e.fmt(f),
555+
Error::Threshold(ref e) => e.fmt(f),
552556
}
553557
}
554558
}
@@ -595,6 +599,7 @@ impl error::Error for Error {
595599
PubKeyCtxError(e, _) => Some(e),
596600
AbsoluteLockTime(e) => Some(e),
597601
RelativeLockTime(e) => Some(e),
602+
Threshold(e) => Some(e),
598603
}
599604
}
600605
}

src/primitives/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313
1414
pub mod absolute_locktime;
1515
pub mod relative_locktime;
16+
pub mod threshold;

src/primitives/threshold.rs

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
3+
//! Thresholds
4+
//!
5+
//! Miniscript
6+
7+
#[cfg(all(not(feature = "std"), not(test)))]
8+
use alloc::{vec, vec::Vec};
9+
use core::{cmp, fmt, iter};
10+
#[cfg(any(feature = "std", test))]
11+
use std::vec;
12+
13+
/// Error parsing an absolute locktime.
14+
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
15+
pub struct ThresholdError {
16+
k: usize,
17+
n: usize,
18+
max: Option<usize>,
19+
}
20+
21+
impl fmt::Display for ThresholdError {
22+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
23+
if self.n == 0 {
24+
f.write_str("thresholds in Miniscript must be nonempty")
25+
} else if self.k == 0 {
26+
f.write_str("thresholds in Miniscript must have k > 0")
27+
} else if self.k > self.n {
28+
write!(f, "invalid threshold {}-of-{}; cannot have k > n", self.k, self.n)
29+
} else {
30+
debug_assert!(self.max.is_some());
31+
let max = self.max.unwrap();
32+
debug_assert!(self.n > max);
33+
write!(f, "invalid threshold {}-of-{}; maximum size is {}", self.k, self.n, max)
34+
}
35+
}
36+
}
37+
38+
#[cfg(feature = "std")]
39+
impl std::error::Error for ThresholdError {
40+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
41+
}
42+
43+
/// Structure representing a k-of-n threshold collection of some arbitrary
44+
/// object `T`.
45+
///
46+
/// If the constant parameter `MAX` is nonzero, it represents a cap on the
47+
/// `n` value; if `n` exceeds `MAX` then an error is returned on construction.
48+
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
49+
pub struct Threshold<T, const MAX: usize> {
50+
k: usize,
51+
inner: Vec<T>,
52+
}
53+
54+
impl<T, const MAX: usize> Threshold<T, MAX> {
55+
/// Constructs a threshold directly from a threshold value and collection.
56+
pub fn new(k: usize, inner: Vec<T>) -> Result<Self, ThresholdError> {
57+
if k == 0 || k > inner.len() || (MAX > 0 && inner.len() > MAX) {
58+
Err(ThresholdError { k, n: inner.len(), max: (MAX > 0).then(|| MAX) })
59+
} else {
60+
Ok(Threshold { k, inner })
61+
}
62+
}
63+
64+
/// Constructs a threshold from a threshold value and an iterator that yields collection
65+
/// elements.
66+
pub fn from_iter<I: Iterator<Item = T>>(k: usize, iter: I) -> Result<Self, ThresholdError> {
67+
let min_size = cmp::max(k, iter.size_hint().0);
68+
// Do an early return if our minimum size exceeds the max.
69+
if MAX > 0 && min_size > MAX {
70+
let n = iter.count();
71+
return Err(ThresholdError { k, n, max: (MAX > 0).then(|| MAX) });
72+
}
73+
74+
let mut inner = Vec::with_capacity(min_size);
75+
iter.for_each(|x| inner.push(x));
76+
Self::new(k, inner)
77+
}
78+
79+
/// Constructor for an "or" represented as a 1-of-2 threshold.
80+
pub fn or(left: T, right: T) -> Self {
81+
debug_assert!(MAX == 0 || MAX > 1);
82+
Threshold { k: 1, inner: vec![left, right] }
83+
}
84+
85+
/// Constructor for an "and" represented as a 2-of-2 threshold.
86+
pub fn and(left: T, right: T) -> Self {
87+
debug_assert!(MAX == 0 || MAX > 1);
88+
Threshold { k: 2, inner: vec![left, right] }
89+
}
90+
91+
/// Whether this threshold is a 1-of-n.
92+
pub fn is_or(&self) -> bool { self.k == 1 }
93+
94+
/// Whether this threshold is a n-of-n.
95+
pub fn is_and(&self) -> bool { self.k == self.inner.len() }
96+
97+
/// Changes the type-system-enforced maximum value of the threshold.
98+
pub fn set_maximum<const NEWMAX: usize>(self) -> Result<Threshold<T, NEWMAX>, ThresholdError> {
99+
Threshold::new(self.k, self.inner)
100+
}
101+
102+
/// Forgets the type-system-enforced maximum value of the threshold.
103+
pub fn forget_maximum(self) -> Threshold<T, 0> { Threshold { k: self.k, inner: self.inner } }
104+
105+
/// Constructs a threshold from an existing threshold by applying a mapping function to
106+
/// each individual item.
107+
pub fn map<U, F: FnMut(T) -> U>(self, mapfn: F) -> Threshold<U, MAX> {
108+
Threshold { k: self.k, inner: self.inner.into_iter().map(mapfn).collect() }
109+
}
110+
111+
/// Like [`Self::map`] but takes a reference to the threshold rather than taking ownership.
112+
pub fn map_ref<U, F: FnMut(&T) -> U>(&self, mapfn: F) -> Threshold<U, MAX> {
113+
Threshold { k: self.k, inner: self.inner.iter().map(mapfn).collect() }
114+
}
115+
116+
/// Like [`Self::map`] except that the mapping function may return an error.
117+
pub fn translate<U, F, FuncError>(self, translatefn: F) -> Result<Threshold<U, MAX>, FuncError>
118+
where
119+
F: FnMut(T) -> Result<U, FuncError>,
120+
{
121+
let k = self.k;
122+
self.inner
123+
.into_iter()
124+
.map(translatefn)
125+
.collect::<Result<Vec<_>, _>>()
126+
.map(|inner| Threshold { k, inner })
127+
}
128+
129+
/// Like [`Self::translate`] but takes a reference to the threshold rather than taking ownership.
130+
pub fn translate_ref<U, F, FuncError>(
131+
&self,
132+
translatefn: F,
133+
) -> Result<Threshold<U, MAX>, FuncError>
134+
where
135+
F: FnMut(&T) -> Result<U, FuncError>,
136+
{
137+
let k = self.k;
138+
self.inner
139+
.iter()
140+
.map(translatefn)
141+
.collect::<Result<Vec<_>, _>>()
142+
.map(|inner| Threshold { k, inner })
143+
}
144+
145+
/// Construct a threshold from an existing threshold which has been processed in some way.
146+
///
147+
/// It is a common pattern in this library to transform data structures by
148+
/// running a post-order iterator over them, putting processed elements into
149+
/// a vector to be later referenced by their parents.
150+
///
151+
/// This function encapsulates that pattern by taking the child-index vector of
152+
/// the`PostOrderIterItem`, under consideration, and the vector of processed
153+
/// elements.
154+
pub fn map_from_post_order_iter<U: Clone>(
155+
&self,
156+
child_indices: &[usize],
157+
processed: &[U],
158+
) -> Threshold<U, MAX> {
159+
debug_assert_eq!(
160+
self.inner.len(),
161+
child_indices.len(),
162+
"internal consistency error translating threshold by post-order iterator"
163+
);
164+
let mut processed_inner = Vec::with_capacity(self.inner.len());
165+
processed_inner.extend(child_indices.iter().copied().map(|n| processed[n].clone()));
166+
Threshold { k: self.k, inner: processed_inner }
167+
}
168+
169+
/// Accessor for the number of elements in the threshold.
170+
// non-const because Vec::len is not const
171+
pub fn n(&self) -> usize { self.inner.len() }
172+
173+
/// Accessor for the threshold value.
174+
pub const fn k(&self) -> usize { self.k }
175+
176+
/// Accessor for the underlying data.
177+
pub fn data(&self) -> &[T] { &self.inner }
178+
179+
/// Mutable accessor for the underlying data.
180+
///
181+
/// This returns access to the underlying data as a mutable slice, which allows you
182+
/// to modify individual elements. To change the number of elements, you must
183+
/// destructure the threshold with [`Self::k`] and [`Self::into_data`] and
184+
/// reconstruct it (and on reconstruction, deal with any errors caused by your
185+
/// tinkering with the threshold values).
186+
pub fn data_mut(&mut self) -> &mut [T] { &mut self.inner }
187+
188+
/// Accessor for the underlying data.
189+
pub fn into_data(self) -> Vec<T> { self.inner }
190+
191+
/// Passthrough to an iterator on the underlying vector.
192+
pub fn iter(&self) -> core::slice::Iter<T> { self.inner.iter() }
193+
}
194+
195+
impl<T> Threshold<T, 0> {
196+
/// Constructor for an "or" represented as a 1-of-n threshold.
197+
///
198+
/// # Panics
199+
///
200+
/// Panics if the passed vector is empty.
201+
pub fn or_n(inner: Vec<T>) -> Self {
202+
assert_ne!(inner.len(), 0);
203+
Threshold { k: 1, inner }
204+
}
205+
206+
/// Constructor for an "and" represented as a n-of-n threshold.
207+
///
208+
/// # Panics
209+
///
210+
/// Panics if the passed vector is empty.
211+
pub fn and_n(inner: Vec<T>) -> Self {
212+
assert_ne!(inner.len(), 0);
213+
Threshold { k: inner.len(), inner }
214+
}
215+
}
216+
217+
impl<T, const MAX: usize> iter::IntoIterator for Threshold<T, MAX> {
218+
type Item = T;
219+
type IntoIter = vec::IntoIter<T>;
220+
221+
fn into_iter(self) -> Self::IntoIter { self.inner.into_iter() }
222+
}
223+
224+
impl<T: fmt::Display, const MAX: usize> Threshold<T, MAX> {
225+
/// Produces an object which can [`fmt::Display`] the threshold.
226+
pub fn display<'s>(&'s self, name: &'s str, show_k: bool) -> impl fmt::Display + 's {
227+
ThreshDisplay { name, thresh: self, show_k }
228+
}
229+
}
230+
231+
impl<T: fmt::Debug, const MAX: usize> Threshold<T, MAX> {
232+
/// Produces an object which can [`fmt::Debug`] the threshold.
233+
pub fn debug<'s>(&'s self, name: &'s str, show_k: bool) -> impl fmt::Debug + 's {
234+
ThreshDisplay { name, thresh: self, show_k }
235+
}
236+
}
237+
238+
struct ThreshDisplay<'t, 's, T, const MAX: usize> {
239+
name: &'s str,
240+
thresh: &'t Threshold<T, MAX>,
241+
show_k: bool,
242+
}
243+
244+
impl<'t, 's, T, const MAX: usize> fmt::Display for ThreshDisplay<'t, 's, T, MAX>
245+
where
246+
T: fmt::Display,
247+
{
248+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
249+
use core::fmt::Write;
250+
251+
f.write_str(self.name)?;
252+
f.write_char('(')?;
253+
let inners = if self.show_k {
254+
write!(f, "{}", self.thresh.k)?;
255+
&self.thresh.inner[0..]
256+
} else {
257+
write!(f, "{}", self.thresh.inner[0])?;
258+
&self.thresh.inner[1..]
259+
};
260+
for inner in inners {
261+
write!(f, ",{}", inner)?;
262+
}
263+
f.write_char(')')
264+
}
265+
}
266+
267+
impl<'t, 's, T, const MAX: usize> fmt::Debug for ThreshDisplay<'t, 's, T, MAX>
268+
where
269+
T: fmt::Debug,
270+
{
271+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
272+
use core::fmt::Write;
273+
274+
f.write_str(self.name)?;
275+
f.write_char('(')?;
276+
let inners = if self.show_k {
277+
write!(f, "{}", self.thresh.k)?;
278+
&self.thresh.inner[0..]
279+
} else {
280+
write!(f, "{:?}", self.thresh.inner[0])?;
281+
&self.thresh.inner[1..]
282+
};
283+
for inner in inners {
284+
write!(f, ",{:?}", inner)?;
285+
}
286+
f.write_char(')')
287+
}
288+
}

0 commit comments

Comments
 (0)