Skip to content

Commit b68bf13

Browse files
committed
Merge #654: Several locktime improvements
422b83c interpreter: use `relative::LockTime` for constraints from Miniscript (Andrew Poelstra) fc42e48 miniscript: use RelLockTime in Terminal (Andrew Poelstra) c490f49 policy: use `RelLockTime` in semantic and concrete policies (Andrew Poelstra) a7a1b9a miniscript: use RelLockTime in `Satisfaction` (Andrew Poelstra) fc6bfa8 introduce RelLockTime type (Andrew Poelstra) 637720a plan/satisfy: use relative::LockTime instead of Sequence to indicate relative locktimes (Andrew Poelstra) bd6ef65 interpreter: rename "age" to "sequence" (Andrew Poelstra) 950b793 AbsLockTime: move to module, consolidate error checking (Andrew Poelstra) 60d7c98 types: drop unused "strongness" errors (Andrew Poelstra) 2f66778 types: replace closure with iterator (Andrew Poelstra) Pull request description: This PR: * Changes user-facing "check locktime" functions to use `bitcoin::relative::LockTime` rather than `Sequence` numbers. * Introduces a new `RelLockTime` type analogous to `AbsLockTime` which is used to provide a locktime type that implements `Ord`. * In both types, validate that the locktimes are legal for use in Miniscript during creation, rather than during typechecking or validity checks. * In many places, replace manual code with the new `is_implied_by` method from rust-bitcoin. This might be a little hard to review because it messes with locktime code which is so easy to get backward. But it appears that there are pretty extensive unit tests which caught several iterations of this where I did get them backward. Going forward things should be much easier to maintain because we have stronger types and an encapsulated `is_implied_by` method. The next PR will do a similar thing with thresholds to validate the `0 < k <= n` invariant when thresholds are created, avoiding the repeated and inconsistent post-hoc error checking logic. Fixes #270 ACKs for top commit: sanket1729: ACK 422b83c Tree-SHA512: 42a749a405c142c7ec468b2fd2e8c3c559c31e6eacf72a351c4dd24887385fa74d8128982a70cef69489c4ef72d6247860e1cd96e52ec83a00bdee0e3359d9b8
2 parents 22dad5e + 422b83c commit b68bf13

22 files changed

+516
-453
lines changed

src/descriptor/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ impl Descriptor<DefiniteDescriptorKey> {
512512
descriptor: self,
513513
template: stack,
514514
absolute_timelock: satisfaction.absolute_timelock.map(Into::into),
515-
relative_timelock: satisfaction.relative_timelock,
515+
relative_timelock: satisfaction.relative_timelock.map(Into::into),
516516
})
517517
} else {
518518
Err(self)
@@ -540,7 +540,8 @@ impl Descriptor<DefiniteDescriptorKey> {
540540
descriptor: self,
541541
template: stack,
542542
absolute_timelock: satisfaction.absolute_timelock.map(Into::into),
543-
relative_timelock: satisfaction.relative_timelock,
543+
// unwrap to be removed in a later commit
544+
relative_timelock: satisfaction.relative_timelock.map(Into::into),
544545
})
545546
} else {
546547
Err(self)

src/interpreter/error.rs

+16-10
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use bitcoin::hashes::hash160;
99
use bitcoin::hex::DisplayHex;
1010
#[cfg(not(test))] // https://github.com/rust-lang/rust/issues/121684
1111
use bitcoin::secp256k1;
12-
use bitcoin::taproot;
12+
use bitcoin::{absolute, relative, taproot};
1313

1414
use super::BitcoinKey;
1515
use crate::prelude::*;
@@ -18,9 +18,9 @@ use crate::prelude::*;
1818
#[derive(Debug)]
1919
pub enum Error {
2020
/// Could not satisfy, absolute locktime not met
21-
AbsoluteLocktimeNotMet(u32),
21+
AbsoluteLockTimeNotMet(absolute::LockTime),
2222
/// Could not satisfy, lock time values are different units
23-
AbsoluteLocktimeComparisonInvalid(u32, u32),
23+
AbsoluteLockTimeComparisonInvalid(absolute::LockTime, absolute::LockTime),
2424
/// Cannot Infer a taproot descriptor
2525
/// Key spends cannot infer the internal key of the descriptor
2626
/// Inferring script spends is possible, but is hidden nodes are currently
@@ -85,7 +85,9 @@ pub enum Error {
8585
/// Parse Error while parsing a `stack::Element::Push` as a XOnlyPublicKey (32 bytes)
8686
XOnlyPublicKeyParseError,
8787
/// Could not satisfy, relative locktime not met
88-
RelativeLocktimeNotMet(u32),
88+
RelativeLockTimeNotMet(relative::LockTime),
89+
/// Could not satisfy, the sequence number on the tx input had the disable flag set.
90+
RelativeLockTimeDisabled(relative::LockTime),
8991
/// Forward-secp related errors
9092
Secp(secp256k1::Error),
9193
/// Miniscript requires the entire top level script to be satisfied.
@@ -116,10 +118,10 @@ pub enum Error {
116118
impl fmt::Display for Error {
117119
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118120
match *self {
119-
Error::AbsoluteLocktimeNotMet(n) => {
121+
Error::AbsoluteLockTimeNotMet(n) => {
120122
write!(f, "required absolute locktime CLTV of {} blocks, not met", n)
121123
}
122-
Error::AbsoluteLocktimeComparisonInvalid(n, lock_time) => write!(
124+
Error::AbsoluteLockTimeComparisonInvalid(n, lock_time) => write!(
123125
f,
124126
"could not satisfy, lock time values are different units n: {} lock_time: {}",
125127
n, lock_time
@@ -159,9 +161,12 @@ impl fmt::Display for Error {
159161
Error::PkHashVerifyFail(ref hash) => write!(f, "Pubkey Hash check failed {}", hash),
160162
Error::PubkeyParseError => f.write_str("could not parse pubkey"),
161163
Error::XOnlyPublicKeyParseError => f.write_str("could not parse x-only pubkey"),
162-
Error::RelativeLocktimeNotMet(n) => {
164+
Error::RelativeLockTimeNotMet(n) => {
163165
write!(f, "required relative locktime CSV of {} blocks, not met", n)
164166
}
167+
Error::RelativeLockTimeDisabled(n) => {
168+
write!(f, "required relative locktime CSV of {} blocks, but tx sequence number has disable-flag set", n)
169+
}
165170
Error::ScriptSatisfactionError => f.write_str("Top level script must be satisfied"),
166171
Error::Secp(ref e) => fmt::Display::fmt(e, f),
167172
Error::SchnorrSig(ref s) => write!(f, "Schnorr sig error: {}", s),
@@ -188,8 +193,8 @@ impl error::Error for Error {
188193
use self::Error::*;
189194

190195
match self {
191-
AbsoluteLocktimeNotMet(_)
192-
| AbsoluteLocktimeComparisonInvalid(_, _)
196+
AbsoluteLockTimeNotMet(_)
197+
| AbsoluteLockTimeComparisonInvalid(_, _)
193198
| CannotInferTrDescriptors
194199
| ControlBlockVerificationError
195200
| CouldNotEvaluate
@@ -212,7 +217,8 @@ impl error::Error for Error {
212217
| XOnlyPublicKeyParseError
213218
| PkEvaluationError(_)
214219
| PkHashVerifyFail(_)
215-
| RelativeLocktimeNotMet(_)
220+
| RelativeLockTimeNotMet(_)
221+
| RelativeLockTimeDisabled(_)
216222
| ScriptSatisfactionError
217223
| TapAnnexUnsupported
218224
| UncompressedPubkey

src/interpreter/mod.rs

+12-10
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use core::fmt;
1212
use core::str::FromStr;
1313

1414
use bitcoin::hashes::{hash160, ripemd160, sha256, Hash};
15-
use bitcoin::{absolute, secp256k1, sighash, taproot, Sequence, TxOut, Witness};
15+
use bitcoin::{absolute, relative, secp256k1, sighash, taproot, Sequence, TxOut, Witness};
1616

1717
use crate::miniscript::context::{NoChecks, SigType};
1818
use crate::miniscript::ScriptContext;
@@ -35,7 +35,7 @@ pub struct Interpreter<'txin> {
3535
/// For non-Taproot spends, the scriptCode; for Taproot script-spends, this
3636
/// is the leaf script; for key-spends it is `None`.
3737
script_code: Option<bitcoin::ScriptBuf>,
38-
age: Sequence,
38+
sequence: Sequence,
3939
lock_time: absolute::LockTime,
4040
}
4141

@@ -136,11 +136,11 @@ impl<'txin> Interpreter<'txin> {
136136
spk: &bitcoin::ScriptBuf,
137137
script_sig: &'txin bitcoin::Script,
138138
witness: &'txin Witness,
139-
age: Sequence, // CSV, relative lock time.
139+
sequence: Sequence, // CSV, relative lock time.
140140
lock_time: absolute::LockTime, // CLTV, absolute lock time.
141141
) -> Result<Self, Error> {
142142
let (inner, stack, script_code) = inner::from_txdata(spk, script_sig, witness)?;
143-
Ok(Interpreter { inner, stack, script_code, age, lock_time })
143+
Ok(Interpreter { inner, stack, script_code, sequence, lock_time })
144144
}
145145

146146
/// Same as [`Interpreter::iter`], but allows for a custom verification function.
@@ -165,7 +165,7 @@ impl<'txin> Interpreter<'txin> {
165165
// Cloning the references to elements of stack should be fine as it allows
166166
// call interpreter.iter() without mutating interpreter
167167
stack: self.stack.clone(),
168-
age: self.age,
168+
sequence: self.sequence,
169169
lock_time: self.lock_time,
170170
has_errored: false,
171171
sig_type: self.sig_type(),
@@ -468,7 +468,7 @@ pub enum SatisfiedConstraint {
468468
///Relative Timelock for CSV.
469469
RelativeTimelock {
470470
/// The value of RelativeTimelock
471-
n: Sequence,
471+
n: relative::LockTime,
472472
},
473473
///Absolute Timelock for CLTV.
474474
AbsoluteTimelock {
@@ -508,7 +508,7 @@ pub struct Iter<'intp, 'txin: 'intp> {
508508
public_key: Option<&'intp BitcoinKey>,
509509
state: Vec<NodeEvaluationState<'intp>>,
510510
stack: Stack<'txin>,
511-
age: Sequence,
511+
sequence: Sequence,
512512
lock_time: absolute::LockTime,
513513
has_errored: bool,
514514
sig_type: SigType,
@@ -608,7 +608,7 @@ where
608608
Terminal::Older(ref n) => {
609609
debug_assert_eq!(node_state.n_evaluated, 0);
610610
debug_assert_eq!(node_state.n_satisfied, 0);
611-
let res = self.stack.evaluate_older(n, self.age);
611+
let res = self.stack.evaluate_older(&(*n).into(), self.sequence);
612612
if res.is_some() {
613613
return res;
614614
}
@@ -1110,7 +1110,7 @@ mod tests {
11101110
stack,
11111111
public_key: None,
11121112
state: vec![NodeEvaluationState { node: ms, n_evaluated: 0, n_satisfied: 0 }],
1113-
age: Sequence::from_height(1002),
1113+
sequence: Sequence::from_height(1002),
11141114
lock_time: absolute::LockTime::from_height(1002).unwrap(),
11151115
has_errored: false,
11161116
sig_type: SigType::Ecdsa,
@@ -1182,7 +1182,9 @@ mod tests {
11821182
let older_satisfied: Result<Vec<SatisfiedConstraint>, Error> = constraints.collect();
11831183
assert_eq!(
11841184
older_satisfied.unwrap(),
1185-
vec![SatisfiedConstraint::RelativeTimelock { n: Sequence::from_height(1000) }]
1185+
vec![SatisfiedConstraint::RelativeTimelock {
1186+
n: crate::RelLockTime::from_height(1000).into()
1187+
}]
11861188
);
11871189

11881190
//Check Sha256

src/interpreter/stack.rs

+14-14
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
use bitcoin::blockdata::{opcodes, script};
77
use bitcoin::hashes::{hash160, ripemd160, sha256, Hash};
8-
use bitcoin::{absolute, Sequence};
8+
use bitcoin::{absolute, relative, Sequence};
99

1010
use super::error::PkEvalErrInner;
1111
use super::{verify_sersig, BitcoinKey, Error, HashLockType, KeySigPair, SatisfiedConstraint};
@@ -212,19 +212,14 @@ impl<'txin> Stack<'txin> {
212212
let is_satisfied = match (*n, lock_time) {
213213
(Blocks(n), Blocks(lock_time)) => n <= lock_time,
214214
(Seconds(n), Seconds(lock_time)) => n <= lock_time,
215-
_ => {
216-
return Some(Err(Error::AbsoluteLocktimeComparisonInvalid(
217-
n.to_consensus_u32(),
218-
lock_time.to_consensus_u32(),
219-
)))
220-
}
215+
_ => return Some(Err(Error::AbsoluteLockTimeComparisonInvalid(*n, lock_time))),
221216
};
222217

223218
if is_satisfied {
224219
self.push(Element::Satisfied);
225220
Some(Ok(SatisfiedConstraint::AbsoluteTimelock { n: *n }))
226221
} else {
227-
Some(Err(Error::AbsoluteLocktimeNotMet(n.to_consensus_u32())))
222+
Some(Err(Error::AbsoluteLockTimeNotMet(*n)))
228223
}
229224
}
230225

@@ -236,14 +231,19 @@ impl<'txin> Stack<'txin> {
236231
/// booleans
237232
pub(super) fn evaluate_older(
238233
&mut self,
239-
n: &Sequence,
240-
age: Sequence,
234+
n: &relative::LockTime,
235+
sequence: Sequence,
241236
) -> Option<Result<SatisfiedConstraint, Error>> {
242-
if age >= *n {
243-
self.push(Element::Satisfied);
244-
Some(Ok(SatisfiedConstraint::RelativeTimelock { n: *n }))
237+
if let Some(tx_locktime) = sequence.to_relative_lock_time() {
238+
if n.is_implied_by(tx_locktime) {
239+
self.push(Element::Satisfied);
240+
Some(Ok(SatisfiedConstraint::RelativeTimelock { n: *n }))
241+
} else {
242+
Some(Err(Error::RelativeLockTimeNotMet(*n)))
243+
}
245244
} else {
246-
Some(Err(Error::RelativeLocktimeNotMet(n.to_consensus_u32())))
245+
// BIP 112: if the tx locktime has the disable flag set, fail CSV.
246+
Some(Err(Error::RelativeLockTimeDisabled(*n)))
247247
}
248248
}
249249

src/lib.rs

+13-47
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
#![deny(missing_docs)]
8888
// Clippy lints that we have disabled
8989
#![allow(clippy::iter_kv_map)] // https://github.com/rust-lang/rust-clippy/issues/11752
90+
#![allow(clippy::manual_range_contains)] // I hate this lint -asp
9091

9192
#[cfg(target_pointer_width = "16")]
9293
compile_error!(
@@ -125,19 +126,19 @@ pub mod iter;
125126
pub mod miniscript;
126127
pub mod plan;
127128
pub mod policy;
129+
mod primitives;
128130
pub mod psbt;
129131

130132
#[cfg(test)]
131133
mod test_utils;
132134
mod util;
133135

134-
use core::{cmp, fmt, hash, str};
136+
use core::{fmt, hash, str};
135137
#[cfg(feature = "std")]
136138
use std::error;
137139

138140
use bitcoin::hashes::{hash160, ripemd160, sha256, Hash};
139141
use bitcoin::hex::DisplayHex;
140-
use bitcoin::locktime::absolute;
141142
use bitcoin::{script, Opcode};
142143

143144
pub use crate::blanket_traits::FromStrKey;
@@ -149,6 +150,8 @@ pub use crate::miniscript::decode::Terminal;
149150
pub use crate::miniscript::satisfy::{Preimage32, Satisfier};
150151
pub use crate::miniscript::{hash256, Miniscript};
151152
use crate::prelude::*;
153+
pub use crate::primitives::absolute_locktime::{AbsLockTime, AbsLockTimeError};
154+
pub use crate::primitives::relative_locktime::{RelLockTime, RelLockTimeError};
152155

153156
/// Public key trait which can be converted to Hash type
154157
pub trait MiniscriptKey: Clone + Eq + Ord + fmt::Debug + fmt::Display + hash::Hash {
@@ -501,6 +504,10 @@ pub enum Error {
501504
/// At least two BIP389 key expressions in the descriptor contain tuples of
502505
/// derivation indexes of different lengths.
503506
MultipathDescLenMismatch,
507+
/// Invalid absolute locktime
508+
AbsoluteLockTime(AbsLockTimeError),
509+
/// Invalid absolute locktime
510+
RelativeLockTime(RelLockTimeError),
504511
}
505512

506513
// https://github.com/sipa/miniscript/pull/5 for discussion on this number
@@ -576,6 +583,8 @@ impl fmt::Display for Error {
576583
Error::TrNoScriptCode => write!(f, "No script code for Tr descriptors"),
577584
Error::TrNoExplicitScript => write!(f, "No script code for Tr descriptors"),
578585
Error::MultipathDescLenMismatch => write!(f, "At least two BIP389 key expressions in the descriptor contain tuples of derivation indexes of different lengths"),
586+
Error::AbsoluteLockTime(ref e) => e.fmt(f),
587+
Error::RelativeLockTime(ref e) => e.fmt(f),
579588
}
580589
}
581590
}
@@ -628,6 +637,8 @@ impl error::Error for Error {
628637
ContextError(e) => Some(e),
629638
AnalysisError(e) => Some(e),
630639
PubKeyCtxError(e, _) => Some(e),
640+
AbsoluteLockTime(e) => Some(e),
641+
RelativeLockTime(e) => Some(e),
631642
}
632643
}
633644
}
@@ -711,51 +722,6 @@ fn hex_script(s: &str) -> bitcoin::ScriptBuf {
711722
bitcoin::ScriptBuf::from(v)
712723
}
713724

714-
/// An absolute locktime that implements `Ord`.
715-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
716-
pub struct AbsLockTime(absolute::LockTime);
717-
718-
impl AbsLockTime {
719-
/// Constructs an `AbsLockTime` from an nLockTime value or the argument to OP_CHEKCLOCKTIMEVERIFY.
720-
pub fn from_consensus(n: u32) -> Self { Self(absolute::LockTime::from_consensus(n)) }
721-
722-
/// Returns the inner `u32` value. This is the value used when creating this `LockTime`
723-
/// i.e., `n OP_CHECKLOCKTIMEVERIFY` or nLockTime.
724-
///
725-
/// This calls through to `absolute::LockTime::to_consensus_u32()` and the same usage warnings
726-
/// apply.
727-
pub fn to_consensus_u32(self) -> u32 { self.0.to_consensus_u32() }
728-
729-
/// Returns the inner `u32` value.
730-
///
731-
/// Equivalent to `AbsLockTime::to_consensus_u32()`.
732-
pub fn to_u32(self) -> u32 { self.to_consensus_u32() }
733-
}
734-
735-
impl From<absolute::LockTime> for AbsLockTime {
736-
fn from(lock_time: absolute::LockTime) -> Self { Self(lock_time) }
737-
}
738-
739-
impl From<AbsLockTime> for absolute::LockTime {
740-
fn from(lock_time: AbsLockTime) -> absolute::LockTime { lock_time.0 }
741-
}
742-
743-
impl cmp::PartialOrd for AbsLockTime {
744-
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { Some(self.cmp(other)) }
745-
}
746-
747-
impl cmp::Ord for AbsLockTime {
748-
fn cmp(&self, other: &Self) -> cmp::Ordering {
749-
let this = self.0.to_consensus_u32();
750-
let that = other.0.to_consensus_u32();
751-
this.cmp(&that)
752-
}
753-
}
754-
755-
impl fmt::Display for AbsLockTime {
756-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
757-
}
758-
759725
#[cfg(test)]
760726
mod tests {
761727
use core::str::FromStr;

0 commit comments

Comments
 (0)