Skip to content

Commit d0a4f1a

Browse files
committed
Update lift trait
1 parent 9098986 commit d0a4f1a

File tree

6 files changed

+158
-34
lines changed

6 files changed

+158
-34
lines changed

examples/htlc.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ fn main() {
3939
);
4040

4141
assert_eq!(
42-
format!("{}", htlc_descriptor.lift()),
43-
"or(and(pkh(4377a5acd66dc5cb67148a24818d1e51fa183bd2),pkh(4377a5acd66dc5cb67148a24818d1e51fa183bd2),older(4444)),sha256(1111111111111111111111111111111111111111111111111111111111111111))");
42+
format!("{}", htlc_descriptor.lift().unwrap()),
43+
"or(and(pkh(4377a5acd66dc5cb67148a24818d1e51fa183bd2),pkh(4377a5acd66dc5cb67148a24818d1e51fa183bd2),older(4444)),sha256(1111111111111111111111111111111111111111111111111111111111111111))"
44+
);
4445

4546
assert_eq!(
4647
format!("{:x}", htlc_descriptor.script_pubkey()),

fuzz/fuzz_targets/compile_descriptor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ fn do_test(data: &[u8]) {
1515
// Compile
1616
if let Ok(desc) = pol.compile::<Segwitv0>() {
1717
// Lift
18-
assert_eq!(desc.clone().lift(), pol.clone().lift());
18+
assert_eq!(desc.clone().lift().unwrap(), pol.clone().lift().unwrap());
1919
// Try to roundtrip the output of the compiler
2020
let output = desc.to_string();
2121
if let Ok(desc) = DummyScript::from_str(&output) {

src/miniscript/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,7 @@ mod tests {
659659
keys[4].to_string(),
660660
);
661661

662-
let mut abs = miniscript.lift();
662+
let mut abs = miniscript.lift().unwrap();
663663
assert_eq!(abs.n_keys(), 5);
664664
assert_eq!(abs.minimum_n_keys(), 2);
665665
abs = abs.at_age(10000);

src/policy/compiler.rs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,10 +1205,28 @@ mod tests {
12051205
let policy = DummyPolicy::from_str(s).expect("parse");
12061206
let miniscript: Miniscript<DummyKey, Segwitv0> = policy.compile()?;
12071207

1208-
assert_eq!(policy.lift().sorted(), miniscript.lift().sorted());
1208+
assert_eq!(
1209+
policy.lift().unwrap().sorted(),
1210+
miniscript.lift().unwrap().sorted()
1211+
);
12091212
Ok(())
12101213
}
12111214

1215+
#[test]
1216+
fn compile_timelocks() {
1217+
// artificially create a policy that is problematic and try to compile
1218+
let pol: DummyPolicy = Concrete::And(vec![
1219+
Concrete::Key(DummyKey),
1220+
Concrete::And(vec![Concrete::After(9), Concrete::After(1000_000_000)]),
1221+
]);
1222+
assert!(pol.compile::<Segwitv0>().is_err());
1223+
1224+
// This should compile
1225+
let pol: DummyPolicy =
1226+
DummyPolicy::from_str("and(pk(),or(and(after(9),pk()),and(after(1000000000),pk())))")
1227+
.unwrap();
1228+
assert!(pol.compile::<Segwitv0>().is_ok());
1229+
}
12121230
#[test]
12131231
fn compile_basic() {
12141232
assert!(policy_compile_lift_check("pk()").is_ok());
@@ -1248,7 +1266,10 @@ mod tests {
12481266
best_t(&mut HashMap::new(), &policy, 1.0, None).unwrap();
12491267

12501268
assert_eq!(compilation.cost_1d(1.0, None), 88.0 + 74.109375);
1251-
assert_eq!(policy.lift().sorted(), compilation.ms.lift().sorted());
1269+
assert_eq!(
1270+
policy.lift().unwrap().sorted(),
1271+
compilation.ms.lift().unwrap().sorted()
1272+
);
12521273

12531274
let policy = SPolicy::from_str(
12541275
"and(and(and(or(127@thresh(2,pk(),pk(),thresh(2,or(127@pk(),1@pk()),after(100),or(and(pk(),after(200)),and(pk(),sha256(66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925))),pk())),1@pk()),sha256(66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925)),or(127@pk(),1@after(300))),or(127@after(400),pk()))"
@@ -1257,7 +1278,10 @@ mod tests {
12571278
best_t(&mut HashMap::new(), &policy, 1.0, None).unwrap();
12581279

12591280
assert_eq!(compilation.cost_1d(1.0, None), 437.0 + 299.4003295898438);
1260-
assert_eq!(policy.lift().sorted(), compilation.ms.lift().sorted());
1281+
assert_eq!(
1282+
policy.lift().unwrap().sorted(),
1283+
compilation.ms.lift().unwrap().sorted()
1284+
);
12611285
}
12621286

12631287
#[test]
@@ -1327,7 +1351,7 @@ mod tests {
13271351

13281352
assert_eq!(ms, ms_comp_res);
13291353

1330-
let mut abs = policy.lift();
1354+
let mut abs = policy.lift().unwrap();
13311355
assert_eq!(abs.n_keys(), 8);
13321356
assert_eq!(abs.minimum_n_keys(), 2);
13331357
abs = abs.at_age(10000);

src/policy/concrete.rs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use std::{error, fmt, str};
2222
use super::ENTAILMENT_MAX_TERMINALS;
2323
use errstr;
2424
use expression::{self, FromTree};
25+
use miniscript::types::extra_props::{TimeLockInfo, HEIGHT_TIME_THRESHOLD};
2526
#[cfg(feature = "compiler")]
2627
use miniscript::ScriptContext;
2728
#[cfg(feature = "compiler")]
@@ -31,7 +32,6 @@ use policy::compiler::CompilerError;
3132
#[cfg(feature = "compiler")]
3233
use Miniscript;
3334
use {Error, MiniscriptKey};
34-
3535
/// Concrete policy which corresponds directly to a Miniscript structure,
3636
/// and whose disjunctions are annotated with satisfaction probabilities
3737
/// to assist the compiler
@@ -79,6 +79,9 @@ pub enum PolicyError {
7979
InsufficientArgsforOr,
8080
/// Entailment max terminals exceeded
8181
EntailmentMaxTerminals,
82+
/// lifting error: Cannot lift policies that have
83+
/// a combination of height and timelocks.
84+
HeightTimeLockCombination,
8285
}
8386

8487
impl error::Error for PolicyError {
@@ -115,6 +118,9 @@ impl fmt::Display for PolicyError {
115118
"Policy entailment only supports {} terminals",
116119
ENTAILMENT_MAX_TERMINALS
117120
),
121+
PolicyError::HeightTimeLockCombination => {
122+
f.write_str("Cannot lift policies that have a heightlock and timelock combination")
123+
}
118124
}
119125
}
120126
}
@@ -168,9 +174,66 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
168174
}
169175
}
170176

177+
/// Checks whether the given concrete policy contains a combination of
178+
/// timelocks and heightlocks.
179+
/// Returns an error if there is atleast one satisfaction that contains
180+
/// a combination of hieghtlock and timelock.
181+
pub fn check_timelocks(&self) -> Result<(), PolicyError> {
182+
let timelocks = self.check_timelocks_helper();
183+
if timelocks.contains_combination {
184+
Err(PolicyError::HeightTimeLockCombination)
185+
} else {
186+
Ok(())
187+
}
188+
}
189+
190+
// Checks whether the given concrete policy contains a combination of
191+
// timelocks and heightlocks
192+
fn check_timelocks_helper(&self) -> TimeLockInfo {
193+
// timelocks[csv_h, csv_t, cltv_h, cltv_t, combination]
194+
match *self {
195+
Policy::Key(_)
196+
| Policy::Sha256(_)
197+
| Policy::Hash256(_)
198+
| Policy::Ripemd160(_)
199+
| Policy::Hash160(_) => TimeLockInfo::default(),
200+
Policy::After(t) => TimeLockInfo {
201+
csv_with_height: false,
202+
csv_with_time: false,
203+
cltv_with_height: t < HEIGHT_TIME_THRESHOLD,
204+
cltv_with_time: t >= HEIGHT_TIME_THRESHOLD,
205+
contains_combination: false,
206+
},
207+
Policy::Older(t) => TimeLockInfo {
208+
csv_with_height: t < HEIGHT_TIME_THRESHOLD,
209+
csv_with_time: t >= HEIGHT_TIME_THRESHOLD,
210+
cltv_with_height: false,
211+
cltv_with_time: false,
212+
contains_combination: false,
213+
},
214+
Policy::Threshold(k, ref subs) => {
215+
let iter = subs.iter().map(|sub| sub.check_timelocks_helper());
216+
TimeLockInfo::combine_thresh_timelocks(k, iter)
217+
}
218+
Policy::And(ref subs) => {
219+
let iter = subs.iter().map(|sub| sub.check_timelocks_helper());
220+
TimeLockInfo::combine_thresh_timelocks(subs.len(), iter)
221+
}
222+
Policy::Or(ref subs) => {
223+
let iter = subs
224+
.iter()
225+
.map(|&(ref _p, ref sub)| sub.check_timelocks_helper());
226+
TimeLockInfo::combine_thresh_timelocks(1, iter)
227+
}
228+
}
229+
}
230+
171231
/// This returns whether the given policy is valid or not. It maybe possible that the policy
172232
/// contains Non-two argument `and`, `or` or a `0` arg thresh.
233+
/// Validity condition also checks whether there is a possible satisfaction
234+
/// combination of timelocks and heightlocks
173235
pub fn is_valid(&self) -> Result<(), PolicyError> {
236+
self.check_timelocks()?;
174237
match *self {
175238
Policy::And(ref subs) => {
176239
if subs.len() != 2 {
@@ -362,7 +425,9 @@ where
362425
}
363426

364427
let tree = expression::Tree::from_str(s)?;
365-
FromTree::from_tree(&tree)
428+
let policy: Policy<Pk> = FromTree::from_tree(&tree)?;
429+
policy.check_timelocks()?;
430+
Ok(policy)
366431
}
367432
}
368433

src/policy/mod.rs

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub use self::concrete::Policy as Concrete;
3535
/// Semantic policies are "abstract" policies elsewhere; but we
3636
/// avoid this word because it is a reserved keyword in Rust
3737
pub use self::semantic::Policy as Semantic;
38+
use Error;
3839
use MiniscriptKey;
3940

4041
/// Policy entailment algorithm maximum number of terminals allowed
@@ -46,18 +47,25 @@ const ENTAILMENT_MAX_TERMINALS: usize = 20;
4647
/// `Lift(Concrete) == Concrete -> Miniscript -> Script -> Miniscript -> Semantic`
4748
pub trait Liftable<Pk: MiniscriptKey> {
4849
/// Convert the object into an abstract policy
49-
fn lift(&self) -> Semantic<Pk>;
50+
fn lift(&self) -> Result<Semantic<Pk>, Error>;
5051
}
5152

5253
impl<Pk: MiniscriptKey, Ctx: ScriptContext> Liftable<Pk> for Miniscript<Pk, Ctx> {
53-
fn lift(&self) -> Semantic<Pk> {
54+
fn lift(&self) -> Result<Semantic<Pk>, Error> {
55+
// check whether the root miniscript can have a spending path that is
56+
// a combination of heightlock and timelock
57+
if self.ext.timelock_info.contains_unspendable_path() {
58+
return Err(Error::PolicyError(
59+
concrete::PolicyError::HeightTimeLockCombination,
60+
));
61+
}
5462
self.as_inner().lift()
5563
}
5664
}
5765

5866
impl<Pk: MiniscriptKey, Ctx: ScriptContext> Liftable<Pk> for Terminal<Pk, Ctx> {
59-
fn lift(&self) -> Semantic<Pk> {
60-
match *self {
67+
fn lift(&self) -> Result<Semantic<Pk>, Error> {
68+
let ret = match *self {
6169
Terminal::PkK(ref pk) => Semantic::KeyHash(pk.to_pubkeyhash()),
6270
Terminal::PkH(ref pkh) => Semantic::KeyHash(pkh.clone()),
6371
Terminal::After(t) => Semantic::After(t),
@@ -74,25 +82,27 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Liftable<Pk> for Terminal<Pk, Ctx> {
7482
| Terminal::DupIf(ref sub)
7583
| Terminal::Verify(ref sub)
7684
| Terminal::NonZero(ref sub)
77-
| Terminal::ZeroNotEqual(ref sub) => sub.node.lift(),
85+
| Terminal::ZeroNotEqual(ref sub) => sub.node.lift()?,
7886
Terminal::AndV(ref left, ref right) | Terminal::AndB(ref left, ref right) => {
79-
Semantic::Threshold(2, vec![left.node.lift(), right.node.lift()])
87+
Semantic::Threshold(2, vec![left.node.lift()?, right.node.lift()?])
8088
}
8189
Terminal::AndOr(ref a, ref b, ref c) => Semantic::Threshold(
8290
1,
8391
vec![
84-
Semantic::Threshold(2, vec![a.node.lift(), c.node.lift()]),
85-
b.node.lift(),
92+
Semantic::Threshold(2, vec![a.node.lift()?, c.node.lift()?]),
93+
b.node.lift()?,
8694
],
8795
),
8896
Terminal::OrB(ref left, ref right)
8997
| Terminal::OrD(ref left, ref right)
9098
| Terminal::OrC(ref left, ref right)
9199
| Terminal::OrI(ref left, ref right) => {
92-
Semantic::Threshold(1, vec![left.node.lift(), right.node.lift()])
100+
Semantic::Threshold(1, vec![left.node.lift()?, right.node.lift()?])
93101
}
94102
Terminal::Thresh(k, ref subs) => {
95-
Semantic::Threshold(k, subs.into_iter().map(|s| s.node.lift()).collect())
103+
let semantic_subs: Result<_, Error> =
104+
subs.into_iter().map(|s| s.node.lift()).collect();
105+
Semantic::Threshold(k, semantic_subs?)
96106
}
97107
Terminal::Multi(k, ref keys) => Semantic::Threshold(
98108
k,
@@ -101,32 +111,36 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Liftable<Pk> for Terminal<Pk, Ctx> {
101111
.collect(),
102112
),
103113
}
104-
.normalized()
114+
.normalized();
115+
Ok(ret)
105116
}
106117
}
107118

108119
impl<Pk: MiniscriptKey> Liftable<Pk> for Descriptor<Pk> {
109-
fn lift(&self) -> Semantic<Pk> {
110-
match *self {
111-
Descriptor::Bare(ref d) | Descriptor::Sh(ref d) => d.node.lift(),
112-
Descriptor::Wsh(ref d) | Descriptor::ShWsh(ref d) => d.node.lift(),
120+
fn lift(&self) -> Result<Semantic<Pk>, Error> {
121+
Ok(match *self {
122+
Descriptor::Bare(ref d) | Descriptor::Sh(ref d) => d.node.lift()?,
123+
Descriptor::Wsh(ref d) | Descriptor::ShWsh(ref d) => d.node.lift()?,
113124
Descriptor::Pk(ref p)
114125
| Descriptor::Pkh(ref p)
115126
| Descriptor::Wpkh(ref p)
116127
| Descriptor::ShWpkh(ref p) => Semantic::KeyHash(p.to_pubkeyhash()),
117-
}
128+
})
118129
}
119130
}
120131

121132
impl<Pk: MiniscriptKey> Liftable<Pk> for Semantic<Pk> {
122-
fn lift(&self) -> Semantic<Pk> {
123-
self.clone()
133+
fn lift(&self) -> Result<Semantic<Pk>, Error> {
134+
Ok(self.clone())
124135
}
125136
}
126137

127138
impl<Pk: MiniscriptKey> Liftable<Pk> for Concrete<Pk> {
128-
fn lift(&self) -> Semantic<Pk> {
129-
match *self {
139+
fn lift(&self) -> Result<Semantic<Pk>, Error> {
140+
// do not lift if there is a possible satisfaction
141+
// involving combination of timelocks and heightlocks
142+
self.check_timelocks()?;
143+
let ret = match *self {
130144
Concrete::Key(ref pk) => Semantic::KeyHash(pk.to_pubkeyhash()),
131145
Concrete::After(t) => Semantic::After(t),
132146
Concrete::Older(t) => Semantic::Older(t),
@@ -135,16 +149,21 @@ impl<Pk: MiniscriptKey> Liftable<Pk> for Concrete<Pk> {
135149
Concrete::Ripemd160(h) => Semantic::Ripemd160(h),
136150
Concrete::Hash160(h) => Semantic::Hash160(h),
137151
Concrete::And(ref subs) => {
138-
Semantic::Threshold(subs.len(), subs.iter().map(Liftable::lift).collect())
152+
let semantic_subs: Result<_, Error> = subs.iter().map(Liftable::lift).collect();
153+
Semantic::Threshold(2, semantic_subs?)
139154
}
140155
Concrete::Or(ref subs) => {
141-
Semantic::Threshold(1, subs.iter().map(|&(_, ref sub)| sub.lift()).collect())
156+
let semantic_subs: Result<_, Error> =
157+
subs.iter().map(|&(ref _p, ref sub)| sub.lift()).collect();
158+
Semantic::Threshold(1, semantic_subs?)
142159
}
143160
Concrete::Threshold(k, ref subs) => {
144-
Semantic::Threshold(k, subs.iter().map(Liftable::lift).collect())
161+
let semantic_subs: Result<_, Error> = subs.iter().map(Liftable::lift).collect();
162+
Semantic::Threshold(k, semantic_subs?)
145163
}
146164
}
147-
.normalized()
165+
.normalized();
166+
Ok(ret)
148167
}
149168
}
150169

@@ -169,6 +188,21 @@ mod tests {
169188
assert_eq!(s.to_lowercase(), output.to_lowercase());
170189
}
171190

191+
#[test]
192+
fn test_timelock_validity() {
193+
// only height
194+
assert!(ConcretePol::from_str("after(100)").is_ok());
195+
// only time
196+
assert!(ConcretePol::from_str("after(1000000000)").is_ok());
197+
// disjunction
198+
assert!(ConcretePol::from_str("or(after(1000000000),after(100))").is_ok());
199+
// conjunction
200+
assert!(ConcretePol::from_str("and(after(1000000000),after(100))").is_err());
201+
// thresh with k = 1
202+
assert!(ConcretePol::from_str("thresh(1,pk(),after(1000000000),after(100))").is_ok());
203+
// thresh with k = 2
204+
assert!(ConcretePol::from_str("thresh(2,after(1000000000),after(100),pk())").is_err());
205+
}
172206
#[test]
173207
fn policy_rtt_tests() {
174208
concrete_policy_rtt("pk()");

0 commit comments

Comments
 (0)