Skip to content

Commit d97a9a0

Browse files
committed
Merge rust-bitcoin/rust-miniscript#419: Fix bug in absolute timelock usage
42e12168f4c4bcb6f521016930fa6a92289631fd Unit test mixed up absolute timelocks (Tobin C. Harding) e4eb285400c417cf9e7255b0e7911649afb201ee Add a minimal timelock module (Tobin C. Harding) 0b62f5e212e8d5d33d4753db9168d3817f2f802a Fix incorrect timelock docs (Tobin C. Harding) Pull request description: Currently if we mix up height/time absolute timelocks when filtering policies the result is incorrect (to the best of my understanding). Add a `timelock` module that includes a single public function for checking if absolute timelocks are the same unit. Use the new function to fix a bug in policy filtering. - Patch 1 is a docs bug fix - Patch 2 is the bug fix - Patch 3 is a unit test patch that fails if its moved before patch 2. Please review the unit test carefully to make sure I'm not confused. ## Note There is [ongoing discussion](rust-bitcoin/rust-bitcoin#994) around trying to design a suitable timelock API. This PR is an attempt to make some forward progress by taking baby steps _and_ making objective improvements. I decided to do it here in miniscript because it will be easier to iterate on and more obvious when there are concrete usage examples along with each change. cc dpc ACKs for top commit: apoelstra: ACK 42e12168f4c4bcb6f521016930fa6a92289631fd Tree-SHA512: 93a55c550a903477cf20f50873d7b778bc36c1eaaf9a31c247d46d931f46100296d3fc3c89cf34ad6b029a7a14f6ae20d8208f22f4536fffe23cd5dab5fa674b
2 parents ade3afa + fc96b8e commit d97a9a0

File tree

4 files changed

+49
-4
lines changed

4 files changed

+49
-4
lines changed

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ pub mod interpreter;
104104
pub mod miniscript;
105105
pub mod policy;
106106
pub mod psbt;
107+
pub mod timelock;
107108

108109
mod util;
109110

src/miniscript/types/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,13 +298,13 @@ pub trait Property: Sized {
298298
/// Type property of a timelock
299299
fn from_time(t: u32) -> Self;
300300

301-
/// Type property of a relative timelock. Default implementation simply
301+
/// Type property of an absolute timelock. Default implementation simply
302302
/// passes through to `from_time`
303303
fn from_after(t: u32) -> Self {
304304
Self::from_time(t)
305305
}
306306

307-
/// Type property of an absolute timelock. Default implementation simply
307+
/// Type property of a relative timelock. Default implementation simply
308308
/// passes through to `from_time`
309309
fn from_older(t: u32) -> Self {
310310
Self::from_time(t)

src/policy/semantic.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d};
2222

2323
use super::concrete::PolicyError;
2424
use super::ENTAILMENT_MAX_TERMINALS;
25-
use crate::{errstr, expression, Error, ForEach, ForEachKey, MiniscriptKey};
25+
use crate::{errstr, expression, timelock, Error, ForEach, ForEachKey, MiniscriptKey};
2626

2727
/// Abstract policy which corresponds to the semantics of a Miniscript
2828
/// and which allows complex forms of analysis, e.g. filtering and
@@ -543,7 +543,9 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
543543
pub fn at_height(mut self, time: u32) -> Policy<Pk> {
544544
self = match self {
545545
Policy::After(t) => {
546-
if t > time {
546+
if !timelock::absolute_timelocks_are_same_unit(t, time) {
547+
Policy::Unsatisfiable
548+
} else if t > time {
547549
Policy::Unsatisfiable
548550
} else {
549551
Policy::After(t)
@@ -762,6 +764,7 @@ mod tests {
762764
assert_eq!(policy.n_keys(), 0);
763765
assert_eq!(policy.minimum_n_keys(), Some(0));
764766

767+
// Block height 1000.
765768
let policy = StringPolicy::from_str("after(1000)").unwrap();
766769
assert_eq!(policy, Policy::After(1000));
767770
assert_eq!(policy.absolute_timelocks(), vec![1000]);
@@ -770,6 +773,26 @@ mod tests {
770773
assert_eq!(policy.clone().at_height(999), Policy::Unsatisfiable);
771774
assert_eq!(policy.clone().at_height(1000), policy.clone());
772775
assert_eq!(policy.clone().at_height(10000), policy.clone());
776+
// Pass a UNIX timestamp to at_height while policy uses a block height.
777+
assert_eq!(policy.clone().at_height(500_000_001), Policy::Unsatisfiable);
778+
assert_eq!(policy.n_keys(), 0);
779+
assert_eq!(policy.minimum_n_keys(), Some(0));
780+
781+
// UNIX timestamp of 10 seconds after the epoch.
782+
let policy = StringPolicy::from_str("after(500000010)").unwrap();
783+
assert_eq!(policy, Policy::After(500_000_010));
784+
assert_eq!(policy.absolute_timelocks(), vec![500_000_010]);
785+
assert_eq!(policy.relative_timelocks(), vec![]);
786+
// Pass a block height to at_height while policy uses a UNIX timestapm.
787+
assert_eq!(policy.clone().at_height(0), Policy::Unsatisfiable);
788+
assert_eq!(policy.clone().at_height(999), Policy::Unsatisfiable);
789+
assert_eq!(policy.clone().at_height(1000), Policy::Unsatisfiable);
790+
assert_eq!(policy.clone().at_height(10000), Policy::Unsatisfiable);
791+
// And now pass a UNIX timestamp to at_height while policy also uses a timestamp.
792+
assert_eq!(policy.clone().at_height(500_000_000), Policy::Unsatisfiable);
793+
assert_eq!(policy.clone().at_height(500_000_001), Policy::Unsatisfiable);
794+
assert_eq!(policy.clone().at_height(500_000_010), policy.clone());
795+
assert_eq!(policy.clone().at_height(500_000_012), policy.clone());
773796
assert_eq!(policy.n_keys(), 0);
774797
assert_eq!(policy.minimum_n_keys(), Some(0));
775798
}

src/timelock.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//! Various functions for manipulating Bitcoin timelocks.
2+
3+
use crate::miniscript::limits::LOCKTIME_THRESHOLD;
4+
5+
/// Returns true if `a` and `b` are the same unit i.e., both are block heights or both are UNIX
6+
/// timestamps. `a` and `b` are nLockTime values.
7+
pub fn absolute_timelocks_are_same_unit(a: u32, b: u32) -> bool {
8+
n_lock_time_is_block_height(a) == n_lock_time_is_block_height(b)
9+
}
10+
11+
// https://github.com/bitcoin/bitcoin/blob/9ccaee1d5e2e4b79b0a7c29aadb41b97e4741332/src/script/script.h#L39
12+
13+
/// Returns true if nLockTime `n` is to be interpreted as a block height.
14+
pub fn n_lock_time_is_block_height(n: u32) -> bool {
15+
n < LOCKTIME_THRESHOLD
16+
}
17+
18+
/// Returns true if nLockTime `n` is to be interpreted as a UNIX timestamp.
19+
pub fn n_lock_time_is_timestamp(n: u32) -> bool {
20+
n >= LOCKTIME_THRESHOLD
21+
}

0 commit comments

Comments
 (0)