Skip to content

Commit

Permalink
Use new bitcoin::timelock module
Browse files Browse the repository at this point in the history
Use the new timelock module (open PR) from `rust-bitcoin`.

This patch is an example of consuming the new timelock API.
  • Loading branch information
tcharding committed May 16, 2022
1 parent 85cd0d8 commit 726b086
Show file tree
Hide file tree
Showing 17 changed files with 202 additions and 198 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Expand Up @@ -19,7 +19,7 @@ use-serde = ["serde", "bitcoin/use-serde"]
rand = ["bitcoin/rand"]

[dependencies]
bitcoin = "0.28.0"
bitcoin = { git = "https://github.com/tcharding/rust-bitcoin", branch = "05-17-timelock" }
serde = { version = "1.0", optional = true}

[[example]]
Expand Down
10 changes: 7 additions & 3 deletions src/interpreter/error.rs
Expand Up @@ -16,6 +16,7 @@ use std::{error, fmt};

use bitcoin::hashes::hash160;
use bitcoin::hashes::hex::ToHex;
use bitcoin::timelock;
use bitcoin::util::taproot;
use bitcoin::{self, secp256k1};

Expand All @@ -25,7 +26,7 @@ use super::BitcoinKey;
#[derive(Debug)]
pub enum Error {
/// Could not satisfy, absolute locktime not met
AbsoluteLocktimeNotMet(u32),
AbsoluteLocktimeNotMet(timelock::Abs),
/// Cannot Infer a taproot descriptor
/// Key spends cannot infer the internal key of the descriptor
/// Inferring script spends is possible, but is hidden nodes are currently
Expand Down Expand Up @@ -90,7 +91,7 @@ pub enum Error {
/// Parse Error while parsing a `stack::Element::Push` as a XOnlyPublicKey (32 bytes)
XOnlyPublicKeyParseError,
/// Could not satisfy, relative locktime not met
RelativeLocktimeNotMet(u32),
RelativeLocktimeNotMet(timelock::Rel),
/// Forward-secp related errors
Secp(secp256k1::Error),
/// Miniscript requires the entire top level script to be satisfied.
Expand All @@ -101,6 +102,8 @@ pub enum Error {
SighashError(bitcoin::util::sighash::Error),
/// Taproot Annex Unsupported
TapAnnexUnsupported,
/// Invalid comparison of timelocks.
TimelockComparisonInvalid(timelock::Error),
/// An uncompressed public key was encountered in a context where it is
/// disallowed (e.g. in a Segwit script or p2wpkh output)
UncompressedPubkey,
Expand Down Expand Up @@ -196,8 +199,9 @@ impl fmt::Display for Error {
Error::AbsoluteLocktimeNotMet(n) => write!(
f,
"required absolute locktime CLTV of {} blocks, not met",
n
n,
),
Error::TimelockComparisonInvalid(e) => write!(f, "timelock comparison error: {:?}", e),
Error::CannotInferTrDescriptors => write!(f, "Cannot infer taproot descriptors"),
Error::ControlBlockParse(ref e) => write!(f, "Control block parse error {}", e),
Error::ControlBlockVerificationError => {
Expand Down
13 changes: 7 additions & 6 deletions src/interpreter/mod.rs
Expand Up @@ -25,6 +25,7 @@ use std::str::FromStr;

use bitcoin::blockdata::witness::Witness;
use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d};
use bitcoin::timelock;
use bitcoin::util::{sighash, taproot};
use bitcoin::{self, secp256k1, TxOut};

Expand Down Expand Up @@ -486,13 +487,13 @@ pub enum SatisfiedConstraint {
},
///Relative Timelock for CSV.
RelativeTimelock {
/// The value of RelativeTimelock
time: u32,
/// The relative timelock.
time: timelock::Rel,
},
///Absolute Timelock for CLTV.
AbsoluteTimelock {
/// The value of Absolute timelock
time: u32,
/// The absolute timelock.
time: timelock::Abs,
},
}

Expand Down Expand Up @@ -1197,7 +1198,7 @@ mod tests {
let after_satisfied: Result<Vec<SatisfiedConstraint>, Error> = constraints.collect();
assert_eq!(
after_satisfied.unwrap(),
vec![SatisfiedConstraint::AbsoluteTimelock { time: 1000 }]
vec![SatisfiedConstraint::AbsoluteTimelock { time: 1000.into() }]
);

//Check Older
Expand All @@ -1207,7 +1208,7 @@ mod tests {
let older_satisfied: Result<Vec<SatisfiedConstraint>, Error> = constraints.collect();
assert_eq!(
older_satisfied.unwrap(),
vec![SatisfiedConstraint::RelativeTimelock { time: 1000 }]
vec![SatisfiedConstraint::RelativeTimelock { time: 1000.into() }],
);

//Check Sha256
Expand Down
29 changes: 17 additions & 12 deletions src/interpreter/stack.rs
Expand Up @@ -17,6 +17,7 @@
use bitcoin;
use bitcoin::blockdata::{opcodes, script};
use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash};
use bitcoin::timelock;

use super::error::PkEvalErrInner;
use super::{
Expand Down Expand Up @@ -230,14 +231,16 @@ impl<'txin> Stack<'txin> {
/// booleans
pub(super) fn evaluate_after(
&mut self,
n: &u32,
n: &timelock::Abs,
age: u32,
) -> Option<Result<SatisfiedConstraint, Error>> {
if age >= *n {
self.push(Element::Satisfied);
Some(Ok(SatisfiedConstraint::AbsoluteTimelock { time: *n }))
} else {
Some(Err(Error::AbsoluteLocktimeNotMet(*n)))
match n.is_expired(age) {
Ok(true) => {
self.push(Element::Satisfied);
Some(Ok(SatisfiedConstraint::AbsoluteTimelock { time: *n }))
}
Ok(false) => Some(Err(Error::AbsoluteLocktimeNotMet(*n))),
Err(e) => Some(Err(Error::TimelockComparisonInvalid(e))),
}
}

Expand All @@ -249,14 +252,16 @@ impl<'txin> Stack<'txin> {
/// booleans
pub(super) fn evaluate_older(
&mut self,
n: &u32,
n: &timelock::Rel,
height: u32,
) -> Option<Result<SatisfiedConstraint, Error>> {
if height >= *n {
self.push(Element::Satisfied);
Some(Ok(SatisfiedConstraint::RelativeTimelock { time: *n }))
} else {
Some(Err(Error::RelativeLocktimeNotMet(*n)))
match n.is_expired(height) {
Ok(true) => {
self.push(Element::Satisfied);
Some(Ok(SatisfiedConstraint::RelativeTimelock { time: *n }))
}
Ok(false) => Some(Err(Error::RelativeLocktimeNotMet(*n))),
Err(e) => Some(Err(Error::TimelockComparisonInvalid(e))),
}
}

Expand Down
14 changes: 8 additions & 6 deletions src/miniscript/astelem.rs
Expand Up @@ -491,10 +491,10 @@ where
expression::terminal(&top.args[0], |x| Pk::Hash::from_str(x).map(Terminal::PkH))
}
("after", 1) => expression::terminal(&top.args[0], |x| {
expression::parse_num(x).map(Terminal::After)
expression::parse_num(x).map(|x| Terminal::After(x.into()))
}),
("older", 1) => expression::terminal(&top.args[0], |x| {
expression::parse_num(x).map(Terminal::Older)
expression::parse_num(x).map(|x| Terminal::Older(x.into()))
}),
("sha256", 1) => expression::terminal(&top.args[0], |x| {
sha256::Hash::from_hex(x).map(Terminal::Sha256)
Expand Down Expand Up @@ -653,9 +653,11 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
.push_slice(&Pk::hash_to_hash160(hash)[..])
.push_opcode(opcodes::all::OP_EQUALVERIFY),
Terminal::After(t) => builder
.push_int(t as i64)
.push_int(t.to_u32() as i64)
.push_opcode(opcodes::all::OP_CLTV),
Terminal::Older(t) => builder.push_int(t as i64).push_opcode(opcodes::all::OP_CSV),
Terminal::Older(t) => builder
.push_int(t.to_u32() as i64)
.push_opcode(opcodes::all::OP_CSV),
Terminal::Sha256(h) => builder
.push_opcode(opcodes::all::OP_SIZE)
.push_int(32)
Expand Down Expand Up @@ -788,8 +790,8 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
match *self {
Terminal::PkK(ref pk) => Ctx::pk_len(pk),
Terminal::PkH(..) => 24,
Terminal::After(n) => script_num_size(n as usize) + 1,
Terminal::Older(n) => script_num_size(n as usize) + 1,
Terminal::After(n) => script_num_size(n.to_u32() as usize) + 1,
Terminal::Older(n) => script_num_size(n.to_u32() as usize) + 1,
Terminal::Sha256(..) => 33 + 6,
Terminal::Hash256(..) => 33 + 6,
Terminal::Ripemd160(..) => 21 + 6,
Expand Down
9 changes: 5 additions & 4 deletions src/miniscript/decode.rs
Expand Up @@ -23,6 +23,7 @@ use std::{error, fmt};

use bitcoin::blockdata::constants::MAX_BLOCK_WEIGHT;
use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash};
use bitcoin::timelock;

use crate::miniscript::lex::{Token as Tk, TokenIter};
use crate::miniscript::limits::MAX_PUBKEYS_PER_MULTISIG;
Expand Down Expand Up @@ -133,9 +134,9 @@ pub enum Terminal<Pk: MiniscriptKey, Ctx: ScriptContext> {
PkH(Pk::Hash),
// timelocks
/// `n CHECKLOCKTIMEVERIFY`
After(u32),
After(timelock::Abs),
/// `n CHECKSEQUENCEVERIFY`
Older(u32),
Older(timelock::Rel),
// hashlocks
/// `SIZE 32 EQUALVERIFY SHA256 <hash> EQUAL`
Sha256(sha256::Hash),
Expand Down Expand Up @@ -386,9 +387,9 @@ pub fn parse<Ctx: ScriptContext>(
},
// timelocks
Tk::CheckSequenceVerify, Tk::Num(n)
=> term.reduce0(Terminal::Older(n))?,
=> term.reduce0(Terminal::Older(n.into()))?,
Tk::CheckLockTimeVerify, Tk::Num(n)
=> term.reduce0(Terminal::After(n))?,
=> term.reduce0(Terminal::After(n.into()))?,
// hashlocks
Tk::Equal => match_token!(
tokens,
Expand Down
20 changes: 0 additions & 20 deletions src/miniscript/limits.rs
Expand Up @@ -14,26 +14,6 @@ pub const MAX_SCRIPT_SIZE: usize = 10_000;
/// Maximum script size allowed by standardness rules
// https://github.com/bitcoin/bitcoin/blob/283a73d7eaea2907a6f7f800f529a0d6db53d7a6/src/policy/policy.h#L44
pub const MAX_STANDARD_P2WSH_SCRIPT_SIZE: usize = 3600;
/// The Threshold for deciding whether `nLockTime` is interpreted as
/// time or height.
// https://github.com/bitcoin/bitcoin/blob/9ccaee1d5e2e4b79b0a7c29aadb41b97e4741332/src/script/script.h#L39
pub const LOCKTIME_THRESHOLD: u32 = 500_000_000;

/// Bit flag for deciding whether sequence number is
/// interpreted as height or time
/* If nSequence encodes a relative lock-time and this flag
* is set, the relative lock-time has units of 512 seconds,
* otherwise it specifies blocks with a granularity of 1. */
// https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki
pub const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = 1 << 22;

/// Disable flag for sequence locktime
/* Below flags apply in the context of BIP 68*/
/* If this flag set, nSequence is NOT interpreted as a
* relative lock-time. For future soft-fork compatibility*/
// https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki
pub const SEQUENCE_LOCKTIME_DISABLE_FLAG: u32 = 1 << 31;

/// Maximum script element size allowed by consensus rules
// https://github.com/bitcoin/bitcoin/blob/42b66a6b814bca130a9ccf0a3f747cf33d628232/src/script/script.h#L23
pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520;
Expand Down
60 changes: 19 additions & 41 deletions src/miniscript/satisfy.rs
Expand Up @@ -25,11 +25,9 @@ use std::{cmp, i64, mem};
use bitcoin;
use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d};
use bitcoin::secp256k1::XOnlyPublicKey;
use bitcoin::timelock;
use bitcoin::util::taproot::{ControlBlock, LeafVersion, TapLeafHash};

use crate::miniscript::limits::{
LOCKTIME_THRESHOLD, SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_LOCKTIME_TYPE_FLAG,
};
use crate::util::witness_size;
use crate::{Miniscript, MiniscriptKey, ScriptContext, Terminal, ToPublicKey};

Expand Down Expand Up @@ -110,56 +108,36 @@ pub trait Satisfier<Pk: MiniscriptKey + ToPublicKey> {
}

/// Assert whether an relative locktime is satisfied
fn check_older(&self, _: u32) -> bool {
fn check_older(&self, _: timelock::Rel) -> bool {
false
}

/// Assert whether a absolute locktime is satisfied
fn check_after(&self, _: u32) -> bool {
fn check_after(&self, _: timelock::Abs) -> bool {
false
}
}

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

/// Newtype around `u32` which implements `Satisfier` using `n` as an
/// relative locktime
pub struct Older(pub u32);

impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for Older {
fn check_older(&self, n: u32) -> bool {
if self.0 & SEQUENCE_LOCKTIME_DISABLE_FLAG != 0 {
impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for timelock::Rel {
fn check_older(&self, n: timelock::Rel) -> bool {
if self.is_disabled() {
return true;
}

/* If nSequence encodes a relative lock-time, this mask is
* applied to extract that lock-time from the sequence field. */
const SEQUENCE_LOCKTIME_MASK: u32 = 0x0000ffff;

let mask = SEQUENCE_LOCKTIME_MASK | SEQUENCE_LOCKTIME_TYPE_FLAG;
let masked_n = n & mask;
let masked_seq = self.0 & mask;
if masked_n < SEQUENCE_LOCKTIME_TYPE_FLAG && masked_seq >= SEQUENCE_LOCKTIME_TYPE_FLAG {
false
} else {
masked_n <= masked_seq
if !self.is_same_type(n) {
return false;
}

n.value() <= self.value()
}
}

/// Newtype around `u32` which implements `Satisfier` using `n` as an
/// absolute locktime
pub struct After(pub u32);

impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for After {
fn check_after(&self, n: u32) -> bool {
// if n > self.0; we will be returning false anyways
if n < LOCKTIME_THRESHOLD && self.0 >= LOCKTIME_THRESHOLD {
false
} else {
n <= self.0
}
impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for timelock::Abs {
fn check_after(&self, n: timelock::Abs) -> bool {
n.is_expired(self.to_u32()).ok().unwrap_or(false)
}
}

Expand Down Expand Up @@ -273,11 +251,11 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier<Pk>> Satisfier<Pk> for &'
(**self).lookup_hash160(h)
}

fn check_older(&self, t: u32) -> bool {
fn check_older(&self, t: timelock::Rel) -> bool {
(**self).check_older(t)
}

fn check_after(&self, t: u32) -> bool {
fn check_after(&self, t: timelock::Abs) -> bool {
(**self).check_after(t)
}
}
Expand Down Expand Up @@ -335,11 +313,11 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier<Pk>> Satisfier<Pk> for &'
(**self).lookup_hash160(h)
}

fn check_older(&self, t: u32) -> bool {
fn check_older(&self, t: timelock::Rel) -> bool {
(**self).check_older(t)
}

fn check_after(&self, t: u32) -> bool {
fn check_after(&self, t: timelock::Abs) -> bool {
(**self).check_after(t)
}
}
Expand Down Expand Up @@ -473,7 +451,7 @@ macro_rules! impl_tuple_satisfier {
None
}

fn check_older(&self, n: u32) -> bool {
fn check_older(&self, n: timelock::Rel) -> bool {
let &($(ref $ty,)*) = self;
$(
if $ty.check_older(n) {
Expand All @@ -483,7 +461,7 @@ macro_rules! impl_tuple_satisfier {
false
}

fn check_after(&self, n: u32) -> bool {
fn check_after(&self, n: timelock::Abs) -> bool {
let &($(ref $ty,)*) = self;
$(
if $ty.check_after(n) {
Expand Down
6 changes: 6 additions & 0 deletions src/miniscript/types/correctness.rs
Expand Up @@ -14,6 +14,8 @@

//! Correctness/Soundness type properties

use bitcoin::timelock;

use super::{ErrorKind, Property};

/// Basic type representing where the fragment can go
Expand Down Expand Up @@ -187,6 +189,10 @@ impl Property for Correctness {
}
}

fn from_older(_: timelock::Rel) -> Self {
Self::from_time(0) // arg is not used.
}

fn cast_alt(self) -> Result<Self, ErrorKind> {
Ok(Correctness {
base: match self.base {
Expand Down

0 comments on commit 726b086

Please sign in to comment.