Skip to content

Commit

Permalink
WIP: Demonstrate the new timelock APIs
Browse files Browse the repository at this point in the history
We are currently working on two new timelock APIs

- `LockTime`: rust-bitcoin/rust-bitcoin#994
- `Sequence`: rust-bitcoin/rust-bitcoin#1093

This PR demonstrates how we would use those APIs here in miniscript.

Includes improvements that are not yet in 1093. To test this out locally
you can grab the `WIP-sequence-and-lock-time` branch from my tree.
You'll have to have `rust-bitcoincore-rpc` locally and patch it to use
local `rust-bitcoin`.

This PR is not intended as a merge candidate.
  • Loading branch information
tcharding committed Jul 13, 2022
1 parent 5de4fdd commit ffbf56a
Show file tree
Hide file tree
Showing 23 changed files with 358 additions and 269 deletions.
6 changes: 3 additions & 3 deletions examples/sign_multisig.rs
Expand Up @@ -18,7 +18,7 @@ use std::collections::HashMap;
use std::str::FromStr;

use bitcoin::blockdata::witness::Witness;
use bitcoin::secp256k1;
use bitcoin::{secp256k1, LockTime, Sequence};

fn main() {
let mut tx = spending_transaction();
Expand Down Expand Up @@ -91,11 +91,11 @@ fn main() {
fn spending_transaction() -> bitcoin::Transaction {
bitcoin::Transaction {
version: 2,
lock_time: 0,
lock_time: LockTime::ZERO,
input: vec![bitcoin::TxIn {
previous_output: Default::default(),
script_sig: bitcoin::Script::new(),
sequence: 0xffffffff,
sequence: Sequence::MAX,
witness: Witness::default(),
}],
output: vec![bitcoin::TxOut {
Expand Down
5 changes: 3 additions & 2 deletions examples/verify_tx.rs
Expand Up @@ -19,6 +19,7 @@ use std::str::FromStr;
use bitcoin::consensus::Decodable;
use bitcoin::secp256k1::{self, Secp256k1};
use bitcoin::util::sighash;
use bitcoin::{LockTime, Sequence};
use miniscript::interpreter::KeySigPair;

fn main() {
Expand All @@ -33,8 +34,8 @@ fn main() {
&spk_input_1,
&tx.input[0].script_sig,
&tx.input[0].witness,
0,
0,
Sequence::ZERO,
LockTime::ZERO,
)
.unwrap();

Expand Down
20 changes: 10 additions & 10 deletions src/descriptor/mod.rs
Expand Up @@ -815,7 +815,7 @@ mod tests {
use bitcoin::hashes::hex::{FromHex, ToHex};
use bitcoin::hashes::{hash160, sha256};
use bitcoin::util::bip32;
use bitcoin::{self, secp256k1, EcdsaSighashType, PublicKey};
use bitcoin::{self, secp256k1, EcdsaSighashType, PublicKey, Sequence};

use super::checksum::desc_checksum;
use super::tr::Tr;
Expand Down Expand Up @@ -1110,7 +1110,7 @@ mod tests {
let mut txin = bitcoin::TxIn {
previous_output: bitcoin::OutPoint::default(),
script_sig: bitcoin::Script::new(),
sequence: 100,
sequence: Sequence::from_height(100),
witness: Witness::default(),
};
let bare = Descriptor::new_bare(ms.clone()).unwrap();
Expand All @@ -1121,7 +1121,7 @@ mod tests {
bitcoin::TxIn {
previous_output: bitcoin::OutPoint::default(),
script_sig: script::Builder::new().push_slice(&sigser[..]).into_script(),
sequence: 100,
sequence: Sequence::from_height(100),
witness: Witness::default(),
}
);
Expand All @@ -1137,7 +1137,7 @@ mod tests {
.push_slice(&sigser[..])
.push_key(&pk)
.into_script(),
sequence: 100,
sequence: Sequence::from_height(100),
witness: Witness::default(),
}
);
Expand All @@ -1150,7 +1150,7 @@ mod tests {
bitcoin::TxIn {
previous_output: bitcoin::OutPoint::default(),
script_sig: bitcoin::Script::new(),
sequence: 100,
sequence: Sequence::from_height(100),
witness: Witness::from_vec(vec![sigser.clone(), pk.to_bytes(),]),
}
);
Expand All @@ -1171,7 +1171,7 @@ mod tests {
script_sig: script::Builder::new()
.push_slice(&redeem_script[..])
.into_script(),
sequence: 100,
sequence: Sequence::from_height(100),
witness: Witness::from_vec(vec![sigser.clone(), pk.to_bytes(),]),
}
);
Expand All @@ -1193,7 +1193,7 @@ mod tests {
.push_slice(&sigser[..])
.push_slice(&ms.encode()[..])
.into_script(),
sequence: 100,
sequence: Sequence::from_height(100),
witness: Witness::default(),
}
);
Expand All @@ -1208,7 +1208,7 @@ mod tests {
bitcoin::TxIn {
previous_output: bitcoin::OutPoint::default(),
script_sig: bitcoin::Script::new(),
sequence: 100,
sequence: Sequence::from_height(100),
witness: Witness::from_vec(vec![sigser.clone(), ms.encode().into_bytes(),]),
}
);
Expand All @@ -1223,7 +1223,7 @@ mod tests {
script_sig: script::Builder::new()
.push_slice(&ms.encode().to_v0_p2wsh()[..])
.into_script(),
sequence: 100,
sequence: Sequence::from_height(100),
witness: Witness::from_vec(vec![sigser.clone(), ms.encode().into_bytes(),]),
}
);
Expand Down Expand Up @@ -1356,7 +1356,7 @@ mod tests {
let mut txin = bitcoin::TxIn {
previous_output: bitcoin::OutPoint::default(),
script_sig: bitcoin::Script::new(),
sequence: 0,
sequence: Sequence::ZERO,
witness: Witness::default(),
};
let satisfier = {
Expand Down
2 changes: 1 addition & 1 deletion src/descriptor/tr.rs
Expand Up @@ -257,7 +257,7 @@ impl<Pk: MiniscriptKey> Tr<Pk> {
TaprootBuilderError::EmptyTree => {
unreachable!("Taptree is a well formed tree with atleast 1 element")
}
_ => unreachable!("non_exhaustive catchall")
_ => unreachable!("non_exhaustive catchall"),
},
}
};
Expand Down
4 changes: 4 additions & 0 deletions src/interpreter/error.rs
Expand Up @@ -29,6 +29,8 @@ use crate::prelude::*;
pub enum Error {
/// Could not satisfy, absolute locktime not met
AbsoluteLocktimeNotMet(u32),
/// Could not satisfy, lock time values are different units
AbsoluteLocktimeComparisonInvalid(u32, u32),
/// 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 @@ -187,6 +189,7 @@ impl fmt::Display for Error {
Error::VerifyFailed => {
f.write_str("Expected Satisfied Boolean at stack top for VERIFY")
}
_ => f.write_str("Unknown error, non_exhaustive catch all"),
}
}
}
Expand Down Expand Up @@ -234,6 +237,7 @@ impl error::Error for Error {
Secp(e) => Some(e),
SchnorrSig(e) => Some(e),
SighashError(e) => Some(e),
_ => None, // non_exhaustive catch all.
}
}
}
Expand Down
32 changes: 18 additions & 14 deletions src/interpreter/mod.rs
Expand Up @@ -25,7 +25,7 @@ use core::str::FromStr;
use bitcoin::blockdata::witness::Witness;
use bitcoin::hashes::{hash160, ripemd160, sha256};
use bitcoin::util::{sighash, taproot};
use bitcoin::{self, secp256k1, TxOut};
use bitcoin::{self, secp256k1, LockTime, Sequence, TxOut};

use crate::miniscript::context::NoChecks;
use crate::miniscript::ScriptContext;
Expand All @@ -48,8 +48,8 @@ pub struct Interpreter<'txin> {
/// For non-Taproot spends, the scriptCode; for Taproot script-spends, this
/// is the leaf script; for key-spends it is `None`.
script_code: Option<bitcoin::Script>,
age: u32,
lock_time: u32,
age: Sequence,
lock_time: LockTime,
}

// A type representing functions for checking signatures that accept both
Expand Down Expand Up @@ -173,8 +173,8 @@ impl<'txin> Interpreter<'txin> {
spk: &bitcoin::Script,
script_sig: &'txin bitcoin::Script,
witness: &'txin Witness,
age: u32, // CSV, relative lock time.
lock_time: u32, // CLTV, absolute lock time.
age: Sequence, // CSV, relative lock time.
lock_time: LockTime, // CLTV, absolute lock time.
) -> Result<Self, Error> {
let (inner, stack, script_code) = inner::from_txdata(spk, script_sig, witness)?;
Ok(Interpreter {
Expand Down Expand Up @@ -491,12 +491,12 @@ pub enum SatisfiedConstraint {
///Relative Timelock for CSV.
RelativeTimelock {
/// The value of RelativeTimelock
time: u32,
n: Sequence,
},
///Absolute Timelock for CLTV.
AbsoluteTimelock {
/// The value of Absolute timelock
time: u32,
n: LockTime,
},
}

Expand Down Expand Up @@ -531,8 +531,8 @@ pub struct Iter<'intp, 'txin: 'intp> {
public_key: Option<&'intp BitcoinKey>,
state: Vec<NodeEvaluationState<'intp>>,
stack: Stack<'txin>,
age: u32,
lock_time: u32,
age: Sequence,
lock_time: LockTime,
has_errored: bool,
}

Expand Down Expand Up @@ -619,7 +619,7 @@ where
Terminal::After(ref n) => {
debug_assert_eq!(node_state.n_evaluated, 0);
debug_assert_eq!(node_state.n_satisfied, 0);
let res = self.stack.evaluate_after(n, self.lock_time);
let res = self.stack.evaluate_after(&n.into(), self.lock_time);
if res.is_some() {
return res;
}
Expand Down Expand Up @@ -1144,8 +1144,8 @@ mod tests {
n_evaluated: 0,
n_satisfied: 0,
}],
age: 1002,
lock_time: 1002,
age: Sequence::from_height(1002),
lock_time: LockTime::from_height(1002).unwrap(),
has_errored: false,
}
}
Expand Down Expand Up @@ -1208,7 +1208,9 @@ mod tests {
let after_satisfied: Result<Vec<SatisfiedConstraint>, Error> = constraints.collect();
assert_eq!(
after_satisfied.unwrap(),
vec![SatisfiedConstraint::AbsoluteTimelock { time: 1000 }]
vec![SatisfiedConstraint::AbsoluteTimelock {
n: LockTime::from_height(1000).unwrap()
}]
);

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

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

use super::error::PkEvalErrInner;
use super::{
Expand Down Expand Up @@ -230,14 +231,27 @@ impl<'txin> Stack<'txin> {
/// booleans
pub(super) fn evaluate_after(
&mut self,
n: &u32,
lock_time: u32,
n: &LockTime,
lock_time: LockTime,
) -> Option<Result<SatisfiedConstraint, Error>> {
if lock_time >= *n {
use LockTime::*;

let is_satisfied = match (*n, lock_time) {
(Blocks(n), Blocks(lock_time)) => n <= lock_time,
(Seconds(n), Seconds(lock_time)) => n <= lock_time,
_ => {
return Some(Err(Error::AbsoluteLocktimeComparisonInvalid(
n.to_consensus_u32(),
lock_time.to_consensus_u32(),
)))
}
};

if is_satisfied {
self.push(Element::Satisfied);
Some(Ok(SatisfiedConstraint::AbsoluteTimelock { time: *n }))
Some(Ok(SatisfiedConstraint::AbsoluteTimelock { n: *n }))
} else {
Some(Err(Error::AbsoluteLocktimeNotMet(*n)))
Some(Err(Error::AbsoluteLocktimeNotMet(n.to_consensus_u32())))
}
}

Expand All @@ -249,14 +263,14 @@ impl<'txin> Stack<'txin> {
/// booleans
pub(super) fn evaluate_older(
&mut self,
n: &u32,
age: u32,
n: &Sequence,
age: Sequence,
) -> Option<Result<SatisfiedConstraint, Error>> {
if age >= *n {
self.push(Element::Satisfied);
Some(Ok(SatisfiedConstraint::RelativeTimelock { time: *n }))
Some(Ok(SatisfiedConstraint::RelativeTimelock { n: *n }))
} else {
Some(Err(Error::RelativeLocktimeNotMet(*n)))
Some(Err(Error::RelativeLocktimeNotMet(n.to_consensus_u32())))
}
}

Expand Down
1 change: 0 additions & 1 deletion src/lib.rs
Expand Up @@ -121,7 +121,6 @@ pub mod interpreter;
pub mod miniscript;
pub mod policy;
pub mod psbt;
pub mod timelock;

#[cfg(test)]
mod test_utils;
Expand Down
15 changes: 9 additions & 6 deletions src/miniscript/astelem.rs
Expand Up @@ -23,6 +23,7 @@ use core::fmt;
use core::str::FromStr;

use bitcoin::blockdata::{opcodes, script};
use bitcoin::{PackedLockTime, Sequence};
use sync::Arc;

use crate::miniscript::context::SigType;
Expand Down Expand Up @@ -459,10 +460,10 @@ impl_from_tree!(
}
("pk_h", 1) => expression::terminal(&top.args[0], |x| Pk::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(PackedLockTime::from(x)))
}),
("older", 1) => expression::terminal(&top.args[0], |x| {
expression::parse_num(x).map(Terminal::Older)
expression::parse_num(x).map(|x| Terminal::Older(Sequence::from_consensus(x)))
}),
("sha256", 1) => expression::terminal(&top.args[0], |x| {
Pk::Sha256::from_str(x).map(Terminal::Sha256)
Expand Down Expand Up @@ -620,9 +621,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.0.into())
.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_consensus_u32().into())
.push_opcode(opcodes::all::OP_CSV),
Terminal::Sha256(ref h) => builder
.push_opcode(opcodes::all::OP_SIZE)
.push_int(32)
Expand Down Expand Up @@ -755,8 +758,8 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
match *self {
Terminal::PkK(ref pk) => Ctx::pk_len(pk),
Terminal::PkH(..) | Terminal::RawPkH(..) => 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.0 as usize) + 1,
Terminal::Older(n) => script_num_size(n.to_consensus_u32() as usize) + 1,
Terminal::Sha256(..) => 33 + 6,
Terminal::Hash256(..) => 33 + 6,
Terminal::Ripemd160(..) => 21 + 6,
Expand Down

0 comments on commit ffbf56a

Please sign in to comment.