Skip to content

Commit 779a715

Browse files
committed
Use timelocks instead of u32
Add a new `timelock` module and implement after/older in terms of the new `Abs`/`Rel` timelocks respectively.
1 parent 85cd0d8 commit 779a715

File tree

18 files changed

+464
-172
lines changed

18 files changed

+464
-172
lines changed

src/interpreter/error.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ use bitcoin::hashes::hex::ToHex;
1919
use bitcoin::util::taproot;
2020
use bitcoin::{self, secp256k1};
2121

22+
use crate::miniscript::timelock;
23+
2224
use super::BitcoinKey;
2325

2426
/// Detailed Error type for Interpreter
2527
#[derive(Debug)]
2628
pub enum Error {
2729
/// Could not satisfy, absolute locktime not met
28-
AbsoluteLocktimeNotMet(u32),
30+
AbsoluteLocktimeNotMet(timelock::Abs),
2931
/// Cannot Infer a taproot descriptor
3032
/// Key spends cannot infer the internal key of the descriptor
3133
/// Inferring script spends is possible, but is hidden nodes are currently
@@ -90,7 +92,7 @@ pub enum Error {
9092
/// Parse Error while parsing a `stack::Element::Push` as a XOnlyPublicKey (32 bytes)
9193
XOnlyPublicKeyParseError,
9294
/// Could not satisfy, relative locktime not met
93-
RelativeLocktimeNotMet(u32),
95+
RelativeLocktimeNotMet(timelock::Rel),
9496
/// Forward-secp related errors
9597
Secp(secp256k1::Error),
9698
/// Miniscript requires the entire top level script to be satisfied.
@@ -101,6 +103,8 @@ pub enum Error {
101103
SighashError(bitcoin::util::sighash::Error),
102104
/// Taproot Annex Unsupported
103105
TapAnnexUnsupported,
106+
/// Invalid comparison of timelocks.
107+
TimelockComparisonInvalid(timelock::Error),
104108
/// An uncompressed public key was encountered in a context where it is
105109
/// disallowed (e.g. in a Segwit script or p2wpkh output)
106110
UncompressedPubkey,
@@ -196,8 +200,9 @@ impl fmt::Display for Error {
196200
Error::AbsoluteLocktimeNotMet(n) => write!(
197201
f,
198202
"required absolute locktime CLTV of {} blocks, not met",
199-
n
203+
n,
200204
),
205+
Error::TimelockComparisonInvalid(e) => write!(f, "timelock comparison error: {:?}", e),
201206
Error::CannotInferTrDescriptors => write!(f, "Cannot infer taproot descriptors"),
202207
Error::ControlBlockParse(ref e) => write!(f, "Control block parse error {}", e),
203208
Error::ControlBlockVerificationError => {

src/interpreter/mod.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use bitcoin::util::{sighash, taproot};
2929
use bitcoin::{self, secp256k1, TxOut};
3030

3131
use crate::miniscript::context::NoChecks;
32-
use crate::miniscript::ScriptContext;
32+
use crate::miniscript::{timelock, ScriptContext};
3333
use crate::{Descriptor, Miniscript, Terminal, ToPublicKey};
3434

3535
mod error;
@@ -486,13 +486,13 @@ pub enum SatisfiedConstraint {
486486
},
487487
///Relative Timelock for CSV.
488488
RelativeTimelock {
489-
/// The value of RelativeTimelock
490-
time: u32,
489+
/// The relative timelock.
490+
time: timelock::Rel,
491491
},
492492
///Absolute Timelock for CLTV.
493493
AbsoluteTimelock {
494-
/// The value of Absolute timelock
495-
time: u32,
494+
/// The absolute timelock.
495+
time: timelock::Abs,
496496
},
497497
}
498498

@@ -602,15 +602,15 @@ where
602602
return res;
603603
}
604604
}
605-
Terminal::After(ref n) => {
605+
Terminal::After(n) => {
606606
debug_assert_eq!(node_state.n_evaluated, 0);
607607
debug_assert_eq!(node_state.n_satisfied, 0);
608608
let res = self.stack.evaluate_after(n, self.age);
609609
if res.is_some() {
610610
return res;
611611
}
612612
}
613-
Terminal::Older(ref n) => {
613+
Terminal::Older(n) => {
614614
debug_assert_eq!(node_state.n_evaluated, 0);
615615
debug_assert_eq!(node_state.n_satisfied, 0);
616616
let res = self.stack.evaluate_older(n, self.height);
@@ -1197,7 +1197,7 @@ mod tests {
11971197
let after_satisfied: Result<Vec<SatisfiedConstraint>, Error> = constraints.collect();
11981198
assert_eq!(
11991199
after_satisfied.unwrap(),
1200-
vec![SatisfiedConstraint::AbsoluteTimelock { time: 1000 }]
1200+
vec![SatisfiedConstraint::AbsoluteTimelock { time: 1000.into() }]
12011201
);
12021202

12031203
//Check Older
@@ -1207,7 +1207,7 @@ mod tests {
12071207
let older_satisfied: Result<Vec<SatisfiedConstraint>, Error> = constraints.collect();
12081208
assert_eq!(
12091209
older_satisfied.unwrap(),
1210-
vec![SatisfiedConstraint::RelativeTimelock { time: 1000 }]
1210+
vec![SatisfiedConstraint::RelativeTimelock { time: 1000.into() }],
12111211
);
12121212

12131213
//Check Sha256

src/interpreter/stack.rs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use super::error::PkEvalErrInner;
2222
use super::{
2323
verify_sersig, BitcoinKey, Error, HashLockType, KeySigPair, SatisfiedConstraint, TypedHash160,
2424
};
25+
use crate::miniscript::timelock;
2526

2627
/// Definition of Stack Element of the Stack used for interpretation of Miniscript.
2728
/// All stack elements with vec![] go to Dissatisfied and vec![1] are marked to Satisfied.
@@ -230,14 +231,16 @@ impl<'txin> Stack<'txin> {
230231
/// booleans
231232
pub(super) fn evaluate_after(
232233
&mut self,
233-
n: &u32,
234+
n: timelock::Abs,
234235
age: u32,
235236
) -> Option<Result<SatisfiedConstraint, Error>> {
236-
if age >= *n {
237-
self.push(Element::Satisfied);
238-
Some(Ok(SatisfiedConstraint::AbsoluteTimelock { time: *n }))
239-
} else {
240-
Some(Err(Error::AbsoluteLocktimeNotMet(*n)))
237+
match n.is_expired(age) {
238+
Ok(true) => {
239+
self.push(Element::Satisfied);
240+
Some(Ok(SatisfiedConstraint::AbsoluteTimelock { time: n }))
241+
}
242+
Ok(false) => Some(Err(Error::AbsoluteLocktimeNotMet(n))),
243+
Err(e) => Some(Err(Error::TimelockComparisonInvalid(e))),
241244
}
242245
}
243246

@@ -249,14 +252,16 @@ impl<'txin> Stack<'txin> {
249252
/// booleans
250253
pub(super) fn evaluate_older(
251254
&mut self,
252-
n: &u32,
255+
n: timelock::Rel,
253256
height: u32,
254257
) -> Option<Result<SatisfiedConstraint, Error>> {
255-
if height >= *n {
256-
self.push(Element::Satisfied);
257-
Some(Ok(SatisfiedConstraint::RelativeTimelock { time: *n }))
258-
} else {
259-
Some(Err(Error::RelativeLocktimeNotMet(*n)))
258+
match n.is_expired(height) {
259+
Ok(true) => {
260+
self.push(Element::Satisfied);
261+
Some(Ok(SatisfiedConstraint::RelativeTimelock { time: n }))
262+
}
263+
Ok(false) => Some(Err(Error::RelativeLocktimeNotMet(n))),
264+
Err(e) => Some(Err(Error::TimelockComparisonInvalid(e))),
260265
}
261266
}
262267

src/miniscript/astelem.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash};
2929

3030
use crate::miniscript::context::SigType;
3131
use crate::miniscript::types::{self, Property};
32-
use crate::miniscript::ScriptContext;
32+
use crate::miniscript::{timelock, ScriptContext};
3333
use crate::util::MsKeyBuilder;
3434
use crate::{
3535
errstr, expression, script_num_size, Error, ForEach, ForEachKey, Miniscript, MiniscriptKey,
@@ -491,10 +491,10 @@ where
491491
expression::terminal(&top.args[0], |x| Pk::Hash::from_str(x).map(Terminal::PkH))
492492
}
493493
("after", 1) => expression::terminal(&top.args[0], |x| {
494-
expression::parse_num(x).map(Terminal::After)
494+
expression::parse_num(x).map(|x| Terminal::After(timelock::Abs::from(x)))
495495
}),
496496
("older", 1) => expression::terminal(&top.args[0], |x| {
497-
expression::parse_num(x).map(Terminal::Older)
497+
expression::parse_num(x).map(|x| Terminal::Older(timelock::Rel::from(x)))
498498
}),
499499
("sha256", 1) => expression::terminal(&top.args[0], |x| {
500500
sha256::Hash::from_hex(x).map(Terminal::Sha256)
@@ -653,9 +653,11 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
653653
.push_slice(&Pk::hash_to_hash160(hash)[..])
654654
.push_opcode(opcodes::all::OP_EQUALVERIFY),
655655
Terminal::After(t) => builder
656-
.push_int(t as i64)
656+
.push_int(t.to_u32() as i64)
657657
.push_opcode(opcodes::all::OP_CLTV),
658-
Terminal::Older(t) => builder.push_int(t as i64).push_opcode(opcodes::all::OP_CSV),
658+
Terminal::Older(t) => builder
659+
.push_int(t.n_sequence() as i64)
660+
.push_opcode(opcodes::all::OP_CSV),
659661
Terminal::Sha256(h) => builder
660662
.push_opcode(opcodes::all::OP_SIZE)
661663
.push_int(32)
@@ -788,8 +790,8 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
788790
match *self {
789791
Terminal::PkK(ref pk) => Ctx::pk_len(pk),
790792
Terminal::PkH(..) => 24,
791-
Terminal::After(n) => script_num_size(n as usize) + 1,
792-
Terminal::Older(n) => script_num_size(n as usize) + 1,
793+
Terminal::After(n) => script_num_size(n.to_u32() as usize) + 1,
794+
Terminal::Older(n) => script_num_size(n.n_sequence() as usize) + 1,
793795
Terminal::Sha256(..) => 33 + 6,
794796
Terminal::Hash256(..) => 33 + 6,
795797
Terminal::Ripemd160(..) => 21 + 6,

src/miniscript/decode.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use crate::miniscript::lex::{Token as Tk, TokenIter};
2828
use crate::miniscript::limits::MAX_PUBKEYS_PER_MULTISIG;
2929
use crate::miniscript::types::extra_props::ExtData;
3030
use crate::miniscript::types::{Property, Type};
31-
use crate::miniscript::ScriptContext;
31+
use crate::miniscript::{timelock, ScriptContext};
3232
use crate::{bitcoin, Error, Miniscript, MiniscriptKey, ToPublicKey};
3333

3434
fn return_none<T>(_: usize) -> Option<T> {
@@ -133,9 +133,9 @@ pub enum Terminal<Pk: MiniscriptKey, Ctx: ScriptContext> {
133133
PkH(Pk::Hash),
134134
// timelocks
135135
/// `n CHECKLOCKTIMEVERIFY`
136-
After(u32),
136+
After(timelock::Abs),
137137
/// `n CHECKSEQUENCEVERIFY`
138-
Older(u32),
138+
Older(timelock::Rel),
139139
// hashlocks
140140
/// `SIZE 32 EQUALVERIFY SHA256 <hash> EQUAL`
141141
Sha256(sha256::Hash),
@@ -386,9 +386,9 @@ pub fn parse<Ctx: ScriptContext>(
386386
},
387387
// timelocks
388388
Tk::CheckSequenceVerify, Tk::Num(n)
389-
=> term.reduce0(Terminal::Older(n))?,
389+
=> term.reduce0(Terminal::Older(timelock::Rel::from(n)))?,
390390
Tk::CheckLockTimeVerify, Tk::Num(n)
391-
=> term.reduce0(Terminal::After(n))?,
391+
=> term.reduce0(Terminal::After(timelock::Abs::from(n)))?,
392392
// hashlocks
393393
Tk::Equal => match_token!(
394394
tokens,

src/miniscript/limits.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ pub const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = 1 << 22;
3434
// https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki
3535
pub const SEQUENCE_LOCKTIME_DISABLE_FLAG: u32 = 1 << 31;
3636

37+
/// Granularity for time-based relative lock-time is fixed at 512 seconds, equivalent to 2^9.
38+
// https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki
39+
pub const SEQUENCE_LOCKTIME_GRANULARITY: u32 = 9;
40+
3741
/// Maximum script element size allowed by consensus rules
3842
// https://github.com/bitcoin/bitcoin/blob/42b66a6b814bca130a9ccf0a3f747cf33d628232/src/script/script.h#L23
3943
pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520;

src/miniscript/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub mod iter;
4040
pub mod lex;
4141
pub mod limits;
4242
pub mod satisfy;
43+
pub mod timelock;
4344
pub mod types;
4445

4546
use std::cmp;

src/miniscript/satisfy.rs

Lines changed: 22 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d};
2727
use bitcoin::secp256k1::XOnlyPublicKey;
2828
use bitcoin::util::taproot::{ControlBlock, LeafVersion, TapLeafHash};
2929

30-
use crate::miniscript::limits::{
31-
LOCKTIME_THRESHOLD, SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_LOCKTIME_TYPE_FLAG,
32-
};
30+
use crate::miniscript::timelock;
3331
use crate::util::witness_size;
3432
use crate::{Miniscript, MiniscriptKey, ScriptContext, Terminal, ToPublicKey};
3533

@@ -110,56 +108,40 @@ pub trait Satisfier<Pk: MiniscriptKey + ToPublicKey> {
110108
}
111109

112110
/// Assert whether an relative locktime is satisfied
113-
fn check_older(&self, _: u32) -> bool {
111+
fn check_older(&self, _: timelock::Rel) -> bool {
114112
false
115113
}
116114

117115
/// Assert whether a absolute locktime is satisfied
118-
fn check_after(&self, _: u32) -> bool {
116+
fn check_after(&self, _: timelock::Abs) -> bool {
119117
false
120118
}
121119
}
122120

123121
// Allow use of `()` as a "no conditions available" satisfier
124122
impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for () {}
125123

126-
/// Newtype around `u32` which implements `Satisfier` using `n` as an
127-
/// relative locktime
128-
pub struct Older(pub u32);
129-
130-
impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for Older {
131-
fn check_older(&self, n: u32) -> bool {
132-
if self.0 & SEQUENCE_LOCKTIME_DISABLE_FLAG != 0 {
124+
impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for timelock::Rel {
125+
fn check_older(&self, n: timelock::Rel) -> bool {
126+
if self.is_disabled() {
133127
return true;
134128
}
135129

136-
/* If nSequence encodes a relative lock-time, this mask is
137-
* applied to extract that lock-time from the sequence field. */
138-
const SEQUENCE_LOCKTIME_MASK: u32 = 0x0000ffff;
139-
140-
let mask = SEQUENCE_LOCKTIME_MASK | SEQUENCE_LOCKTIME_TYPE_FLAG;
141-
let masked_n = n & mask;
142-
let masked_seq = self.0 & mask;
143-
if masked_n < SEQUENCE_LOCKTIME_TYPE_FLAG && masked_seq >= SEQUENCE_LOCKTIME_TYPE_FLAG {
144-
false
145-
} else {
146-
masked_n <= masked_seq
130+
if !self.is_same_type(n) {
131+
return false;
147132
}
133+
134+
n.value() <= self.value()
148135
}
149136
}
150137

151-
/// Newtype around `u32` which implements `Satisfier` using `n` as an
152-
/// absolute locktime
153-
pub struct After(pub u32);
154-
155-
impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for After {
156-
fn check_after(&self, n: u32) -> bool {
157-
// if n > self.0; we will be returning false anyways
158-
if n < LOCKTIME_THRESHOLD && self.0 >= LOCKTIME_THRESHOLD {
159-
false
160-
} else {
161-
n <= self.0
138+
impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for timelock::Abs {
139+
fn check_after(&self, n: timelock::Abs) -> bool {
140+
if !self.is_same_type(n) {
141+
return false;
162142
}
143+
144+
n.to_u32() <= self.to_u32()
163145
}
164146
}
165147

@@ -273,11 +255,11 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier<Pk>> Satisfier<Pk> for &'
273255
(**self).lookup_hash160(h)
274256
}
275257

276-
fn check_older(&self, t: u32) -> bool {
258+
fn check_older(&self, t: timelock::Rel) -> bool {
277259
(**self).check_older(t)
278260
}
279261

280-
fn check_after(&self, t: u32) -> bool {
262+
fn check_after(&self, t: timelock::Abs) -> bool {
281263
(**self).check_after(t)
282264
}
283265
}
@@ -335,11 +317,11 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier<Pk>> Satisfier<Pk> for &'
335317
(**self).lookup_hash160(h)
336318
}
337319

338-
fn check_older(&self, t: u32) -> bool {
320+
fn check_older(&self, t: timelock::Rel) -> bool {
339321
(**self).check_older(t)
340322
}
341323

342-
fn check_after(&self, t: u32) -> bool {
324+
fn check_after(&self, t: timelock::Abs) -> bool {
343325
(**self).check_after(t)
344326
}
345327
}
@@ -473,7 +455,7 @@ macro_rules! impl_tuple_satisfier {
473455
None
474456
}
475457

476-
fn check_older(&self, n: u32) -> bool {
458+
fn check_older(&self, n: timelock::Rel) -> bool {
477459
let &($(ref $ty,)*) = self;
478460
$(
479461
if $ty.check_older(n) {
@@ -483,7 +465,7 @@ macro_rules! impl_tuple_satisfier {
483465
false
484466
}
485467

486-
fn check_after(&self, n: u32) -> bool {
468+
fn check_after(&self, n: timelock::Abs) -> bool {
487469
let &($(ref $ty,)*) = self;
488470
$(
489471
if $ty.check_after(n) {

0 commit comments

Comments
 (0)