Add various stepwise functionality (#204)

* Move some constants in preparation

This makes some minor changes to constants to facilitate splitting out a
stepwise interpreter.

* Extract a step function from the interpreter

This is generally useful for testing, but specifically, we want to be
able to run this side-by-side with the C++ interpreter, and check that
every bit of our state matches along the way.

This change is as minimal as possible, to avoid divergence until after
it can be compared against C++. E.g., the massive `match opcode {…}`
block that has been moved should only change the dereferencing of
`op_count`.

* Add a `State` struct to make stepping easier

* Expose step interpreter

* Add a stepwise comparison interpreter

The C++ changes aren’t in place yet, so this is currently just an A/A test.

This changes our closures into structs containing a function, because
that’s how we can pass around functions with universally-quantified
lifetimes.

* Make interpreters more flexible

Previously the `ZcashScript` impls didn’t use `self`, so the types were
just tags. However, with the new `StepwiseInterpreter`, they need
`self`.

This also removes `RustInterpreter` in favor of a `rust_interpreter`
function that instantiates an appropriate `StepwiseInterpreter`.

* Add a function for C++/Rust comparison interpreter

* Fix fuzzer

* Clean up `use` in lib.rs

* Fix weird indentation

* Make various fields non-`pub`

* Add a `new` constructor for `Stack`

* Remove incorrect comment

* Appease Clippy

Adds `Default` impls for `Stack` and `State`.

* Rename `State::manual` to `State::from_parts`
This commit is contained in:
Greg Pfeil 2025-04-10 15:03:29 -06:00 committed by GitHub
parent 2afc474338
commit cc157ffdce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1494 additions and 935 deletions

View File

@ -3,6 +3,7 @@
use libfuzzer_sys::fuzz_target;
extern crate zcash_script;
use zcash_script::interpreter::CallbackTransactionSignatureChecker;
use zcash_script::*;
fn missing_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> {
@ -11,14 +12,25 @@ fn missing_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]
fuzz_target!(|tup: (u32, bool, &[u8], &[u8], u32)| {
// `fuzz_target!` doesnt support pattern matching in the parameter list.
let (lock_time, is_final, pub_key, sig, flags) = tup;
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
&missing_sighash,
let (lock_time, is_final, pub_key, sig, flag_bits) = tup;
let flags = testing::repair_flags(VerificationFlags::from_bits_truncate(flag_bits));
let ret = check_verify_callback(
&CxxInterpreter {
sighash: &missing_sighash,
lock_time,
is_final,
},
&rust_interpreter(
flags,
CallbackTransactionSignatureChecker {
sighash: &missing_sighash,
lock_time: lock_time.into(),
is_final,
},
),
pub_key,
sig,
testing::repair_flags(VerificationFlags::from_bits_truncate(flags)),
flags,
);
assert_eq!(
ret.0,

View File

@ -1,4 +1,3 @@
use std::mem::swap;
use std::slice::Iter;
use ripemd::Ripemd160;
@ -144,9 +143,10 @@ pub struct BaseSignatureChecker();
impl SignatureChecker for BaseSignatureChecker {}
#[derive(Copy, Clone)]
pub struct CallbackTransactionSignatureChecker<'a> {
pub sighash: SighashCalculator<'a>,
pub lock_time: &'a ScriptNum,
pub lock_time: ScriptNum,
pub is_final: bool,
}
@ -177,12 +177,16 @@ fn cast_to_bool(vch: &ValType) -> bool {
* Script is a stack machine (like Forth) that evaluates a predicate
* returning a bool indicating valid or not. There are no loops.
*/
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Stack<T>(Vec<T>);
/// Wraps a Vec (or whatever underlying implementation we choose in a way that matches the C++ impl
/// and provides us some decent chaining)
impl<T> Stack<T> {
impl<T: Clone> Stack<T> {
pub fn new() -> Self {
Stack(vec![])
}
fn reverse_index(&self, i: isize) -> Result<usize, ScriptError> {
usize::try_from(-i)
.map(|a| self.0.len() - a)
@ -225,6 +229,17 @@ impl<T> Stack<T> {
self.0.last_mut().ok_or(ScriptError::InvalidStackOperation)
}
pub fn last(&self) -> Result<&T, ScriptError> {
self.0.last().ok_or(ScriptError::InvalidStackOperation)
}
pub fn split_last(&self) -> Result<(&T, Stack<T>), ScriptError> {
self.0
.split_last()
.ok_or(ScriptError::InvalidStackOperation)
.map(|(last, rem)| (last, Stack(rem.to_vec())))
}
pub fn erase(&mut self, start: usize, end: Option<usize>) {
for _ in 0..end.map_or(1, |e| e - start) {
self.0.remove(start);
@ -444,39 +459,82 @@ fn check_minimal_push(data: &[u8], opcode: PushValue) -> bool {
true
}
pub fn eval_script(
stack: &mut Stack<Vec<u8>>,
script: &Script,
flags: VerificationFlags,
checker: &dyn SignatureChecker,
) -> Result<(), ScriptError> {
let bn_zero = ScriptNum::from(0);
let bn_one = ScriptNum::from(1);
let vch_false: ValType = vec![];
let vch_true: ValType = vec![1];
const VCH_FALSE: ValType = Vec::new();
const VCH_TRUE: [u8; 1] = [1];
// There's a limit on how large scripts can be.
if script.0.len() > MAX_SCRIPT_SIZE {
return set_error(ScriptError::ScriptSize);
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct State {
stack: Stack<Vec<u8>>,
altstack: Stack<Vec<u8>>,
// We keep track of how many operations have executed so far to prevent expensive-to-verify
// scripts
op_count: u8,
// This keeps track of the conditional flags at each nesting level during execution. If we're in
// a branch of execution where *any* of these conditionals are false, we ignore opcodes unless
// those opcodes direct control flow (OP_IF, OP_ELSE, etc.).
vexec: Stack<bool>,
}
impl State {
/// Creates a new empty state.
pub fn new() -> Self {
Self::initial(Stack::new())
}
let mut pc = script.0;
/// Creates a state with an initial stack, but other components empty.
pub fn initial(stack: Stack<Vec<u8>>) -> Self {
Self::from_parts(stack, Stack::new(), 0, Stack::new())
}
// We keep track of how many operations have executed so far to prevent
// expensive-to-verify scripts
let mut op_count: u8 = 0;
/// Create an arbitrary state.
pub fn from_parts(
stack: Stack<Vec<u8>>,
altstack: Stack<Vec<u8>>,
op_count: u8,
vexec: Stack<bool>,
) -> Self {
State {
stack,
altstack,
op_count,
vexec,
}
}
pub fn stack(&self) -> &Stack<Vec<u8>> {
&self.stack
}
pub fn altstack(&self) -> &Stack<Vec<u8>> {
&self.altstack
}
pub fn op_count(&self) -> u8 {
self.op_count
}
pub fn vexec(&self) -> &Stack<bool> {
&self.vexec
}
}
/// Run a single step of the interpreter.
///
/// This is useful for testing & debugging, as we can set up the exact state we want in order to
/// trigger some behavior.
pub fn eval_step<'a>(
pc: &'a [u8],
script: &Script,
flags: VerificationFlags,
checker: impl SignatureChecker,
state: &mut State,
) -> Result<&'a [u8], ScriptError> {
let stack = &mut state.stack;
let op_count = &mut state.op_count;
let require_minimal = flags.contains(VerificationFlags::MinimalData);
let vexec = &mut state.vexec;
let altstack = &mut state.altstack;
// This keeps track of the conditional flags at each nesting level
// during execution. If we're in a branch of execution where *any*
// of these conditionals are false, we ignore opcodes unless those
// opcodes direct control flow (OP_IF, OP_ELSE, etc.).
let mut vexec: Stack<bool> = Stack(vec![]);
let mut altstack: Stack<Vec<u8>> = Stack(vec![]);
// Main execution loop
while !pc.is_empty() {
// Are we in an executing branch of the script?
let exec = vexec.iter().all(|value| *value);
@ -484,7 +542,6 @@ pub fn eval_script(
// Read instruction
//
let (opcode, vch_push_value, new_pc) = Script::get_op2(pc)?;
pc = new_pc;
if vch_push_value.len() > MAX_SCRIPT_ELEMENT_SIZE {
return set_error(ScriptError::PushSize);
}
@ -496,8 +553,8 @@ pub fn eval_script(
//
// Push value
//
OP_1NEGATE | OP_1 | OP_2 | OP_3 | OP_4 | OP_5 | OP_6 | OP_7 | OP_8
| OP_9 | OP_10 | OP_11 | OP_12 | OP_13 | OP_14 | OP_15 | OP_16 => {
OP_1NEGATE | OP_1 | OP_2 | OP_3 | OP_4 | OP_5 | OP_6 | OP_7 | OP_8 | OP_9
| OP_10 | OP_11 | OP_12 | OP_13 | OP_14 | OP_15 | OP_16 => {
// ( -- value)
let bn = ScriptNum::from(u8::from(pv)) - u8::from(OP_RESERVED).into();
stack.push_back(bn.getvch());
@ -519,8 +576,8 @@ pub fn eval_script(
}
Opcode::Operation(op) => {
// Note how OP_RESERVED does not count towards the opcode limit.
op_count += 1;
if op_count > 201 {
*op_count += 1;
if *op_count > 201 {
return set_error(ScriptError::OpCount);
}
@ -591,7 +648,7 @@ pub fn eval_script(
// In the rare event that the argument may be < 0 due to
// some arithmetic being done first, you can always use
// 0 MAX CHECKLOCKTIMEVERIFY.
if lock_time < bn_zero {
if lock_time < ScriptNum::ZERO {
return set_error(ScriptError::NegativeLockTime);
}
@ -802,11 +859,8 @@ pub fn eval_script(
if stack.size() < 2 {
return set_error(ScriptError::InvalidStackOperation);
}
let n = u16::try_from(ScriptNum::new(
stack.top(-1)?,
require_minimal,
None,
)?)
let n =
u16::try_from(ScriptNum::new(stack.top(-1)?, require_minimal, None)?)
.map_err(|_| ScriptError::InvalidStackOperation)?;
stack.pop()?;
if usize::from(n) >= stack.size() {
@ -876,11 +930,7 @@ pub fn eval_script(
let equal = vch1 == vch2;
stack.pop()?;
stack.pop()?;
stack.push_back(if equal {
vch_true.clone()
} else {
vch_false.clone()
});
stack.push_back(if equal { VCH_TRUE.to_vec() } else { VCH_FALSE });
if op == OP_EQUALVERIFY {
if equal {
stack.pop()?;
@ -900,16 +950,16 @@ pub fn eval_script(
}
let mut bn = ScriptNum::new(stack.top(-1)?, require_minimal, None)?;
match op {
OP_1ADD => bn = bn + bn_one,
OP_1SUB => bn = bn - bn_one,
OP_1ADD => bn = bn + ScriptNum::ONE,
OP_1SUB => bn = bn - ScriptNum::ONE,
OP_NEGATE => bn = -bn,
OP_ABS => {
if bn < bn_zero {
if bn < ScriptNum::ZERO {
bn = -bn
}
}
OP_NOT => bn = ScriptNum::from(bn == bn_zero),
OP_0NOTEQUAL => bn = ScriptNum::from(bn != bn_zero),
OP_NOT => bn = ScriptNum::from(bn == ScriptNum::ZERO),
OP_0NOTEQUAL => bn = ScriptNum::from(bn != ScriptNum::ZERO),
_ => panic!("invalid opcode"),
}
stack.pop()?;
@ -940,8 +990,12 @@ pub fn eval_script(
OP_SUB => bn1 - bn2,
OP_BOOLAND => ScriptNum::from(bn1 != bn_zero && bn2 != bn_zero),
OP_BOOLOR => ScriptNum::from(bn1 != bn_zero || bn2 != bn_zero),
OP_BOOLAND => {
ScriptNum::from(bn1 != ScriptNum::ZERO && bn2 != ScriptNum::ZERO)
}
OP_BOOLOR => {
ScriptNum::from(bn1 != ScriptNum::ZERO || bn2 != ScriptNum::ZERO)
}
OP_NUMEQUAL => ScriptNum::from(bn1 == bn2),
OP_NUMEQUALVERIFY => ScriptNum::from(bn1 == bn2),
OP_NUMNOTEQUAL => ScriptNum::from(bn1 != bn2),
@ -990,11 +1044,7 @@ pub fn eval_script(
stack.pop()?;
stack.pop()?;
stack.pop()?;
stack.push_back(if value {
vch_true.clone()
} else {
vch_false.clone()
})
stack.push_back(if value { VCH_TRUE.to_vec() } else { VCH_FALSE })
}
//
@ -1040,9 +1090,9 @@ pub fn eval_script(
stack.pop()?;
stack.pop()?;
stack.push_back(if success {
vch_true.clone()
VCH_TRUE.to_vec()
} else {
vch_false.clone()
VCH_FALSE
});
if op == OP_CHECKSIGVERIFY {
if success {
@ -1073,9 +1123,9 @@ pub fn eval_script(
if keys_count > 20 {
return set_error(ScriptError::PubKeyCount);
};
assert!(op_count <= 201);
op_count += keys_count;
if op_count > 201 {
assert!(*op_count <= 201);
*op_count += keys_count;
if *op_count > 201 {
return set_error(ScriptError::OpCount);
};
i += 1;
@ -1153,9 +1203,9 @@ pub fn eval_script(
stack.pop()?;
stack.push_back(if success {
vch_true.clone()
VCH_TRUE.to_vec()
} else {
vch_false.clone()
VCH_FALSE
});
if op == OP_CHECKMULTISIGVERIFY {
@ -1179,13 +1229,69 @@ pub fn eval_script(
if stack.size() + altstack.size() > 1000 {
return set_error(ScriptError::StackSize);
}
set_success(new_pc)
}
pub trait StepFn {
type Payload: Clone;
fn call<'a>(
&self,
pc: &'a [u8],
script: &Script,
state: &mut State,
payload: &mut Self::Payload,
) -> Result<&'a [u8], ScriptError>;
}
/// Produces the default stepper, which carries no payload and runs the script as before.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct DefaultStepEvaluator<C> {
pub flags: VerificationFlags,
pub checker: C,
}
impl<C: SignatureChecker + Copy> StepFn for DefaultStepEvaluator<C> {
type Payload = ();
fn call<'a>(
&self,
pc: &'a [u8],
script: &Script,
state: &mut State,
_payload: &mut (),
) -> Result<&'a [u8], ScriptError> {
eval_step(pc, script, self.flags, self.checker, state)
}
}
pub fn eval_script<F>(
stack: Stack<Vec<u8>>,
script: &Script,
payload: &mut F::Payload,
eval_step: &F,
) -> Result<Stack<Vec<u8>>, ScriptError>
where
F: StepFn,
{
// There's a limit on how large scripts can be.
if script.0.len() > MAX_SCRIPT_SIZE {
return set_error(ScriptError::ScriptSize);
}
if !vexec.empty() {
let mut pc = script.0;
let mut state = State::initial(stack);
// Main execution loop
while !pc.is_empty() {
pc = eval_step.call(pc, script, &mut state, payload)?;
}
if !state.vexec.empty() {
return set_error(ScriptError::UnbalancedConditional);
}
set_success(())
set_success(state.stack)
}
/// All signature hashes are 32 bytes, since they are either:
@ -1234,11 +1340,11 @@ impl SignatureChecker for CallbackTransactionSignatureChecker<'_> {
// We want to compare apples to apples, so fail the script
// unless the type of nLockTime being tested is the same as
// the nLockTime in the transaction.
if *self.lock_time < LOCKTIME_THRESHOLD && *lock_time >= LOCKTIME_THRESHOLD
|| *self.lock_time >= LOCKTIME_THRESHOLD && *lock_time < LOCKTIME_THRESHOLD
if self.lock_time < LOCKTIME_THRESHOLD && *lock_time >= LOCKTIME_THRESHOLD
|| self.lock_time >= LOCKTIME_THRESHOLD && *lock_time < LOCKTIME_THRESHOLD
// Now that we know we're comparing apples-to-apples, the
// comparison is a simple numeric one.
|| lock_time > self.lock_time
|| *lock_time > self.lock_time
{
false
// Finally the nLockTime feature can be disabled and thus
@ -1257,57 +1363,62 @@ impl SignatureChecker for CallbackTransactionSignatureChecker<'_> {
}
}
pub fn verify_script(
pub fn verify_script<F>(
script_sig: &Script,
script_pub_key: &Script,
flags: VerificationFlags,
checker: &dyn SignatureChecker,
) -> Result<(), ScriptError> {
payload: &mut F::Payload,
stepper: &F,
) -> Result<(), ScriptError>
where
F: StepFn,
{
if flags.contains(VerificationFlags::SigPushOnly) && !script_sig.is_push_only() {
return set_error(ScriptError::SigPushOnly);
}
let mut stack = Stack(Vec::new());
let mut stack_copy = Stack(Vec::new());
eval_script(&mut stack, script_sig, flags, checker)?;
if flags.contains(VerificationFlags::P2SH) {
stack_copy = stack.clone()
}
eval_script(&mut stack, script_pub_key, flags, checker)?;
if stack.empty() {
let data_stack = eval_script(Stack::new(), script_sig, payload, stepper)?;
let pub_key_stack = eval_script(data_stack.clone(), script_pub_key, payload, stepper)?;
if pub_key_stack.empty() {
return set_error(ScriptError::EvalFalse);
}
if !cast_to_bool(stack.back()?) {
if !cast_to_bool(pub_key_stack.last()?) {
return set_error(ScriptError::EvalFalse);
}
// Additional validation for spend-to-script-hash transactions:
if flags.contains(VerificationFlags::P2SH) && script_pub_key.is_pay_to_script_hash() {
let result_stack = if flags.contains(VerificationFlags::P2SH)
&& script_pub_key.is_pay_to_script_hash()
{
// script_sig must be literals-only or validation fails
if !script_sig.is_push_only() {
return set_error(ScriptError::SigPushOnly);
};
// Restore stack.
swap(&mut stack, &mut stack_copy);
// stack cannot be empty here, because if it was the
// P2SH HASH <> EQUAL scriptPubKey would be evaluated with
// an empty stack and the `eval_script` above would return false.
assert!(!stack.empty());
assert!(!data_stack.empty());
let pub_key_serialized = stack.back()?.clone();
let pub_key_2 = Script(pub_key_serialized.as_slice());
stack.pop()?;
data_stack
.split_last()
.map_err(|_| ScriptError::InvalidStackOperation)
.and_then(|(pub_key_serialized, remaining_stack)| {
let pub_key_2 = Script(pub_key_serialized);
eval_script(&mut stack, &pub_key_2, flags, checker)?;
if stack.empty() {
eval_script(remaining_stack, &pub_key_2, payload, stepper).and_then(|p2sh_stack| {
if p2sh_stack.empty() {
return set_error(ScriptError::EvalFalse);
}
if !cast_to_bool(stack.back()?) {
if !cast_to_bool(p2sh_stack.last()?) {
return set_error(ScriptError::EvalFalse);
}
}
Ok(p2sh_stack)
})
})?
} else {
pub_key_stack
};
// The CLEANSTACK check is only performed after potential P2SH evaluation,
// as the non-P2SH evaluation of a P2SH script will obviously not result in
@ -1315,7 +1426,7 @@ pub fn verify_script(
if flags.contains(VerificationFlags::CleanStack) {
// Disallow CLEANSTACK without P2SH, because Bitcoin did.
assert!(flags.contains(VerificationFlags::P2SH));
if stack.size() != 1 {
if result_stack.size() != 1 {
return set_error(ScriptError::CleanStack);
}
};

View File

@ -7,9 +7,9 @@
#[macro_use]
extern crate enum_primitive;
mod cxx;
pub mod cxx;
mod external;
mod interpreter;
pub mod interpreter;
mod script;
pub mod script_error;
mod zcash_script;
@ -18,92 +18,76 @@ use std::os::raw::{c_int, c_uint, c_void};
use tracing::warn;
pub use cxx::*;
pub use interpreter::{HashType, SighashCalculator, SignedOutputs, VerificationFlags};
pub use interpreter::{
CallbackTransactionSignatureChecker, DefaultStepEvaluator, HashType, SighashCalculator,
SignedOutputs, VerificationFlags,
};
use script_error::ScriptError;
pub use zcash_script::*;
/// A tag to indicate that the C++ implementation of zcash_script should be used.
pub enum CxxInterpreter {}
pub struct CxxInterpreter<'a> {
pub sighash: SighashCalculator<'a>,
pub lock_time: u32,
pub is_final: bool,
}
impl From<cxx::ScriptError> for Error {
#[allow(non_upper_case_globals)]
fn from(err_code: cxx::ScriptError) -> Self {
match err_code {
ScriptError_t_SCRIPT_ERR_OK => Error::Ok(script_error::ScriptError::Ok),
ScriptError_t_SCRIPT_ERR_UNKNOWN_ERROR => {
Error::Ok(script_error::ScriptError::UnknownError)
}
ScriptError_t_SCRIPT_ERR_EVAL_FALSE => Error::Ok(script_error::ScriptError::EvalFalse),
ScriptError_t_SCRIPT_ERR_OP_RETURN => Error::Ok(script_error::ScriptError::OpReturn),
cxx::ScriptError_t_SCRIPT_ERR_OK => Error::Ok(ScriptError::Ok),
cxx::ScriptError_t_SCRIPT_ERR_UNKNOWN_ERROR => Error::Ok(ScriptError::UnknownError),
cxx::ScriptError_t_SCRIPT_ERR_EVAL_FALSE => Error::Ok(ScriptError::EvalFalse),
cxx::ScriptError_t_SCRIPT_ERR_OP_RETURN => Error::Ok(ScriptError::OpReturn),
ScriptError_t_SCRIPT_ERR_SCRIPT_SIZE => {
Error::Ok(script_error::ScriptError::ScriptSize)
cxx::ScriptError_t_SCRIPT_ERR_SCRIPT_SIZE => Error::Ok(ScriptError::ScriptSize),
cxx::ScriptError_t_SCRIPT_ERR_PUSH_SIZE => Error::Ok(ScriptError::PushSize),
cxx::ScriptError_t_SCRIPT_ERR_OP_COUNT => Error::Ok(ScriptError::OpCount),
cxx::ScriptError_t_SCRIPT_ERR_STACK_SIZE => Error::Ok(ScriptError::StackSize),
cxx::ScriptError_t_SCRIPT_ERR_SIG_COUNT => Error::Ok(ScriptError::SigCount),
cxx::ScriptError_t_SCRIPT_ERR_PUBKEY_COUNT => Error::Ok(ScriptError::PubKeyCount),
cxx::ScriptError_t_SCRIPT_ERR_VERIFY => Error::Ok(ScriptError::Verify),
cxx::ScriptError_t_SCRIPT_ERR_EQUALVERIFY => Error::Ok(ScriptError::EqualVerify),
cxx::ScriptError_t_SCRIPT_ERR_CHECKMULTISIGVERIFY => {
Error::Ok(ScriptError::CheckMultisigVerify)
}
ScriptError_t_SCRIPT_ERR_PUSH_SIZE => Error::Ok(script_error::ScriptError::PushSize),
ScriptError_t_SCRIPT_ERR_OP_COUNT => Error::Ok(script_error::ScriptError::OpCount),
ScriptError_t_SCRIPT_ERR_STACK_SIZE => Error::Ok(script_error::ScriptError::StackSize),
ScriptError_t_SCRIPT_ERR_SIG_COUNT => Error::Ok(script_error::ScriptError::SigCount),
ScriptError_t_SCRIPT_ERR_PUBKEY_COUNT => {
Error::Ok(script_error::ScriptError::PubKeyCount)
cxx::ScriptError_t_SCRIPT_ERR_CHECKSIGVERIFY => Error::Ok(ScriptError::CheckSigVerify),
cxx::ScriptError_t_SCRIPT_ERR_NUMEQUALVERIFY => Error::Ok(ScriptError::NumEqualVerify),
cxx::ScriptError_t_SCRIPT_ERR_BAD_OPCODE => Error::Ok(ScriptError::BadOpcode),
cxx::ScriptError_t_SCRIPT_ERR_DISABLED_OPCODE => Error::Ok(ScriptError::DisabledOpcode),
cxx::ScriptError_t_SCRIPT_ERR_INVALID_STACK_OPERATION => {
Error::Ok(ScriptError::InvalidStackOperation)
}
cxx::ScriptError_t_SCRIPT_ERR_INVALID_ALTSTACK_OPERATION => {
Error::Ok(ScriptError::InvalidAltstackOperation)
}
cxx::ScriptError_t_SCRIPT_ERR_UNBALANCED_CONDITIONAL => {
Error::Ok(ScriptError::UnbalancedConditional)
}
ScriptError_t_SCRIPT_ERR_VERIFY => Error::Ok(script_error::ScriptError::Verify),
ScriptError_t_SCRIPT_ERR_EQUALVERIFY => {
Error::Ok(script_error::ScriptError::EqualVerify)
cxx::ScriptError_t_SCRIPT_ERR_NEGATIVE_LOCKTIME => {
Error::Ok(ScriptError::NegativeLockTime)
}
ScriptError_t_SCRIPT_ERR_CHECKMULTISIGVERIFY => {
Error::Ok(script_error::ScriptError::CheckMultisigVerify)
}
ScriptError_t_SCRIPT_ERR_CHECKSIGVERIFY => {
Error::Ok(script_error::ScriptError::CheckSigVerify)
}
ScriptError_t_SCRIPT_ERR_NUMEQUALVERIFY => {
Error::Ok(script_error::ScriptError::NumEqualVerify)
cxx::ScriptError_t_SCRIPT_ERR_UNSATISFIED_LOCKTIME => {
Error::Ok(ScriptError::UnsatisfiedLockTime)
}
ScriptError_t_SCRIPT_ERR_BAD_OPCODE => Error::Ok(script_error::ScriptError::BadOpcode),
ScriptError_t_SCRIPT_ERR_DISABLED_OPCODE => {
Error::Ok(script_error::ScriptError::DisabledOpcode)
}
ScriptError_t_SCRIPT_ERR_INVALID_STACK_OPERATION => {
Error::Ok(script_error::ScriptError::InvalidStackOperation)
}
ScriptError_t_SCRIPT_ERR_INVALID_ALTSTACK_OPERATION => {
Error::Ok(script_error::ScriptError::InvalidAltstackOperation)
}
ScriptError_t_SCRIPT_ERR_UNBALANCED_CONDITIONAL => {
Error::Ok(script_error::ScriptError::UnbalancedConditional)
cxx::ScriptError_t_SCRIPT_ERR_SIG_HASHTYPE => Error::Ok(ScriptError::SigHashType),
cxx::ScriptError_t_SCRIPT_ERR_SIG_DER => Error::Ok(ScriptError::SigDER),
cxx::ScriptError_t_SCRIPT_ERR_MINIMALDATA => Error::Ok(ScriptError::MinimalData),
cxx::ScriptError_t_SCRIPT_ERR_SIG_PUSHONLY => Error::Ok(ScriptError::SigPushOnly),
cxx::ScriptError_t_SCRIPT_ERR_SIG_HIGH_S => Error::Ok(ScriptError::SigHighS),
cxx::ScriptError_t_SCRIPT_ERR_SIG_NULLDUMMY => Error::Ok(ScriptError::SigNullDummy),
cxx::ScriptError_t_SCRIPT_ERR_PUBKEYTYPE => Error::Ok(ScriptError::PubKeyType),
cxx::ScriptError_t_SCRIPT_ERR_CLEANSTACK => Error::Ok(ScriptError::CleanStack),
cxx::ScriptError_t_SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS => {
Error::Ok(ScriptError::DiscourageUpgradableNOPs)
}
ScriptError_t_SCRIPT_ERR_NEGATIVE_LOCKTIME => {
Error::Ok(script_error::ScriptError::NegativeLockTime)
}
ScriptError_t_SCRIPT_ERR_UNSATISFIED_LOCKTIME => {
Error::Ok(script_error::ScriptError::UnsatisfiedLockTime)
}
ScriptError_t_SCRIPT_ERR_SIG_HASHTYPE => {
Error::Ok(script_error::ScriptError::SigHashType)
}
ScriptError_t_SCRIPT_ERR_SIG_DER => Error::Ok(script_error::ScriptError::SigDER),
ScriptError_t_SCRIPT_ERR_MINIMALDATA => {
Error::Ok(script_error::ScriptError::MinimalData)
}
ScriptError_t_SCRIPT_ERR_SIG_PUSHONLY => {
Error::Ok(script_error::ScriptError::SigPushOnly)
}
ScriptError_t_SCRIPT_ERR_SIG_HIGH_S => Error::Ok(script_error::ScriptError::SigHighS),
ScriptError_t_SCRIPT_ERR_SIG_NULLDUMMY => {
Error::Ok(script_error::ScriptError::SigNullDummy)
}
ScriptError_t_SCRIPT_ERR_PUBKEYTYPE => Error::Ok(script_error::ScriptError::PubKeyType),
ScriptError_t_SCRIPT_ERR_CLEANSTACK => Error::Ok(script_error::ScriptError::CleanStack),
ScriptError_t_SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS => {
Error::Ok(script_error::ScriptError::DiscourageUpgradableNOPs)
}
ScriptError_t_SCRIPT_ERR_VERIFY_SCRIPT => Error::VerifyScript,
cxx::ScriptError_t_SCRIPT_ERR_VERIFY_SCRIPT => Error::VerifyScript,
unknown => Error::Unknown(unknown.into()),
}
}
@ -143,11 +127,9 @@ extern "C" fn sighash_callback(
}
/// This steals a bit of the wrapper code from zebra_script, to provide the API that they want.
impl ZcashScript for CxxInterpreter {
impl<'a> ZcashScript for CxxInterpreter<'a> {
fn verify_callback(
sighash: SighashCalculator,
lock_time: u32,
is_final: bool,
&self,
script_pub_key: &[u8],
signature_script: &[u8],
flags: VerificationFlags,
@ -156,11 +138,11 @@ impl ZcashScript for CxxInterpreter {
// SAFETY: The `script` fields are created from a valid Rust `slice`.
let ret = unsafe {
zcash_script_verify_callback(
(&sighash as *const SighashCalculator) as *const c_void,
cxx::zcash_script_verify_callback(
(&self.sighash as *const SighashCalculator) as *const c_void,
Some(sighash_callback),
lock_time.into(),
if is_final { 1 } else { 0 },
self.lock_time.into(),
if self.is_final { 1 } else { 0 },
script_pub_key.as_ptr(),
script_pub_key
.len()
@ -185,13 +167,13 @@ impl ZcashScript for CxxInterpreter {
/// Returns the number of transparent signature operations in the
/// transparent inputs and outputs of this transaction.
fn legacy_sigop_count_script(script: &[u8]) -> Result<u32, Error> {
fn legacy_sigop_count_script(&self, script: &[u8]) -> Result<u32, Error> {
script
.len()
.try_into()
.map_err(Error::InvalidScriptSize)
.map(|script_len| unsafe {
zcash_script_legacy_sigop_count_script(script.as_ptr(), script_len)
cxx::zcash_script_legacy_sigop_count_script(script.as_ptr(), script_len)
})
}
}
@ -200,11 +182,13 @@ impl ZcashScript for CxxInterpreter {
/// both results. This is more useful for testing than the impl that logs a warning if the results
/// differ and always returns the C++ result.
fn check_legacy_sigop_count_script<T: ZcashScript, U: ZcashScript>(
first: &T,
second: &U,
script: &[u8],
) -> (Result<u32, Error>, Result<u32, Error>) {
(
T::legacy_sigop_count_script(script),
U::legacy_sigop_count_script(script),
first.legacy_sigop_count_script(script),
second.legacy_sigop_count_script(script),
)
}
@ -212,30 +196,15 @@ fn check_legacy_sigop_count_script<T: ZcashScript, U: ZcashScript>(
/// both results. This is more useful for testing than the impl that logs a warning if the results
/// differ and always returns the `T` result.
pub fn check_verify_callback<T: ZcashScript, U: ZcashScript>(
sighash: SighashCalculator,
lock_time: u32,
is_final: bool,
first: &T,
second: &U,
script_pub_key: &[u8],
script_sig: &[u8],
flags: VerificationFlags,
) -> (Result<(), Error>, Result<(), Error>) {
(
T::verify_callback(
sighash,
lock_time,
is_final,
script_pub_key,
script_sig,
flags,
),
U::verify_callback(
sighash,
lock_time,
is_final,
script_pub_key,
script_sig,
flags,
),
first.verify_callback(script_pub_key, script_sig, flags),
second.verify_callback(script_pub_key, script_sig, flags),
)
}
@ -243,8 +212,8 @@ pub fn check_verify_callback<T: ZcashScript, U: ZcashScript>(
pub fn normalize_error(err: Error) -> Error {
match err {
Error::Ok(serr) => Error::Ok(match serr {
script_error::ScriptError::ReadError { .. } => script_error::ScriptError::BadOpcode,
script_error::ScriptError::ScriptNumError(_) => script_error::ScriptError::UnknownError,
ScriptError::ReadError { .. } => ScriptError::BadOpcode,
ScriptError::ScriptNumError(_) => ScriptError::UnknownError,
_ => serr,
}),
_ => err,
@ -253,14 +222,42 @@ pub fn normalize_error(err: Error) -> Error {
/// A tag to indicate that both the C++ and Rust implementations of zcash_script should be used,
/// with their results compared.
pub enum CxxRustComparisonInterpreter {}
pub struct ComparisonInterpreter<T, U> {
first: T,
second: U,
}
pub fn cxx_rust_comparison_interpreter<'a>(
sighash: &'a SighashCalculator<'a>,
lock_time: u32,
is_final: bool,
flags: VerificationFlags,
) -> ComparisonInterpreter<
CxxInterpreter<'a>,
StepwiseInterpreter<DefaultStepEvaluator<CallbackTransactionSignatureChecker<'a>>>,
> {
ComparisonInterpreter {
first: CxxInterpreter {
sighash,
lock_time,
is_final,
},
second: rust_interpreter(
flags,
CallbackTransactionSignatureChecker {
sighash,
lock_time: lock_time.into(),
is_final,
},
),
}
}
/// This implementation is functionally equivalent to the `T` impl, but it also runs a second (`U`)
/// impl and logs a warning if they disagree.
impl ZcashScript for CxxRustComparisonInterpreter {
fn legacy_sigop_count_script(script: &[u8]) -> Result<u32, Error> {
let (cxx, rust) =
check_legacy_sigop_count_script::<CxxInterpreter, RustInterpreter>(script);
impl<T: ZcashScript, U: ZcashScript> ZcashScript for ComparisonInterpreter<T, U> {
fn legacy_sigop_count_script(&self, script: &[u8]) -> Result<u32, Error> {
let (cxx, rust) = check_legacy_sigop_count_script(&self.first, &self.second, script);
if rust != cxx {
warn!(
"The Rust Zcash Script interpreter had a different sigop count ({:?}) from the C++ one ({:?}).",
@ -271,21 +268,13 @@ impl ZcashScript for CxxRustComparisonInterpreter {
}
fn verify_callback(
sighash: SighashCalculator,
lock_time: u32,
is_final: bool,
&self,
script_pub_key: &[u8],
script_sig: &[u8],
flags: VerificationFlags,
) -> Result<(), Error> {
let (cxx, rust) = check_verify_callback::<CxxInterpreter, RustInterpreter>(
sighash,
lock_time,
is_final,
script_pub_key,
script_sig,
flags,
);
let (cxx, rust) =
check_verify_callback(&self.first, &self.second, script_pub_key, script_sig, flags);
if rust.map_err(normalize_error) != cxx {
// probably want to distinguish between
// - one succeeding when the other fails (bad), and
@ -302,6 +291,10 @@ impl ZcashScript for CxxRustComparisonInterpreter {
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use super::*;
use crate::{
interpreter::{State, StepFn},
script::{Operation, Script},
};
/// Ensures that flags represent a supported state. This avoids crashes in the C++ code, which
/// break various tests.
@ -317,6 +310,33 @@ pub mod testing {
/// A `usize` one larger than the longest allowed script, for testing bounds.
pub const OVERFLOW_SCRIPT_SIZE: usize = script::MAX_SCRIPT_SIZE + 1;
/// This is the same as `DefaultStepEvaluator`, except that it skips `OP_EQUAL`, allowing us to
/// test comparison failures.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct BrokenStepEvaluator<T>(pub T);
impl<T: StepFn> StepFn for BrokenStepEvaluator<T> {
type Payload = T::Payload;
fn call<'a>(
&self,
pc: &'a [u8],
script: &Script,
state: &mut State,
payload: &mut T::Payload,
) -> Result<&'a [u8], ScriptError> {
self.0.call(
if pc[0] == Operation::OP_EQUAL.into() {
&pc[1..]
} else {
pc
},
script,
state,
payload,
)
}
}
}
#[cfg(test)]
@ -352,16 +372,26 @@ mod tests {
#[test]
fn it_works() {
let n_lock_time: u32 = 2410374;
let lock_time: u32 = 2410374;
let is_final: bool = true;
let script_pub_key = &SCRIPT_PUBKEY;
let script_sig = &SCRIPT_SIG;
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
&sighash,
n_lock_time,
let ret = check_verify_callback(
&CxxInterpreter {
sighash: &sighash,
lock_time,
is_final,
},
&rust_interpreter(
flags,
CallbackTransactionSignatureChecker {
sighash: &sighash,
lock_time: lock_time.into(),
is_final,
},
),
script_pub_key,
script_sig,
flags,
@ -373,44 +403,63 @@ mod tests {
#[test]
fn it_fails_on_invalid_sighash() {
let n_lock_time: u32 = 2410374;
let lock_time: u32 = 2410374;
let is_final: bool = true;
let script_pub_key = &SCRIPT_PUBKEY;
let script_sig = &SCRIPT_SIG;
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
&invalid_sighash,
n_lock_time,
let ret = check_verify_callback(
&CxxInterpreter {
sighash: &invalid_sighash,
lock_time,
is_final,
},
&rust_interpreter(
flags,
CallbackTransactionSignatureChecker {
sighash: &invalid_sighash,
lock_time: lock_time.into(),
is_final,
},
),
script_pub_key,
script_sig,
flags,
);
assert_eq!(ret.0, ret.1.map_err(normalize_error));
assert_eq!(ret.0, Err(Error::Ok(script_error::ScriptError::EvalFalse)));
assert_eq!(ret.0, Err(Error::Ok(ScriptError::EvalFalse)));
}
#[test]
fn it_fails_on_missing_sighash() {
let n_lock_time: u32 = 2410374;
let lock_time: u32 = 2410374;
let is_final: bool = true;
let script_pub_key = &SCRIPT_PUBKEY;
let script_sig = &SCRIPT_SIG;
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
&missing_sighash,
n_lock_time,
let ret = check_verify_callback(
&CxxInterpreter {
sighash: &missing_sighash,
lock_time,
is_final,
},
&rust_interpreter(
flags,
CallbackTransactionSignatureChecker {
sighash: &missing_sighash,
lock_time: lock_time.into(),
is_final,
},
),
script_pub_key,
script_sig,
flags,
);
assert_eq!(ret.0, ret.1.map_err(normalize_error));
assert_eq!(ret.0, Err(Error::Ok(script_error::ScriptError::EvalFalse)));
assert_eq!(ret.0, Err(Error::Ok(ScriptError::EvalFalse)));
}
proptest! {
@ -424,15 +473,26 @@ mod tests {
is_final in prop::bool::ANY,
pub_key in prop::collection::vec(0..=0xffu8, 0..=OVERFLOW_SCRIPT_SIZE),
sig in prop::collection::vec(0..=0xffu8, 1..=OVERFLOW_SCRIPT_SIZE),
flags in prop::bits::u32::masked(VerificationFlags::all().bits()),
flag_bits in prop::bits::u32::masked(VerificationFlags::all().bits()),
) {
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
&missing_sighash,
let flags = repair_flags(VerificationFlags::from_bits_truncate(flag_bits));
let ret = check_verify_callback(
&CxxInterpreter {
sighash: &sighash,
lock_time,
is_final,
},
&rust_interpreter(
flags,
CallbackTransactionSignatureChecker {
sighash: &sighash,
lock_time: lock_time.into(),
is_final,
},
),
&pub_key[..],
&sig[..],
repair_flags(VerificationFlags::from_bits_truncate(flags)),
flags,
);
prop_assert_eq!(ret.0, ret.1.map_err(normalize_error),
"original Rust result: {:?}", ret.1);
@ -445,18 +505,29 @@ mod tests {
is_final in prop::bool::ANY,
pub_key in prop::collection::vec(0..=0xffu8, 0..=OVERFLOW_SCRIPT_SIZE),
sig in prop::collection::vec(0..=0x60u8, 0..=OVERFLOW_SCRIPT_SIZE),
flags in prop::bits::u32::masked(
flag_bits in prop::bits::u32::masked(
// Dont waste test cases on whether or not `SigPushOnly` is set.
(VerificationFlags::all() - VerificationFlags::SigPushOnly).bits()),
) {
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
&missing_sighash,
let flags = repair_flags(VerificationFlags::from_bits_truncate(flag_bits))
| VerificationFlags::SigPushOnly;
let ret = check_verify_callback(
&CxxInterpreter {
sighash: &sighash,
lock_time,
is_final,
},
&rust_interpreter(
flags,
CallbackTransactionSignatureChecker {
sighash: &sighash,
lock_time: lock_time.into(),
is_final,
},
),
&pub_key[..],
&sig[..],
repair_flags(VerificationFlags::from_bits_truncate(flags))
| VerificationFlags::SigPushOnly,
flags,
);
prop_assert_eq!(ret.0, ret.1.map_err(normalize_error),
"original Rust result: {:?}", ret.1);

View File

@ -269,6 +269,9 @@ impl From<Operation> for u8 {
pub struct ScriptNum(i64);
impl ScriptNum {
pub const ZERO: ScriptNum = ScriptNum(0);
pub const ONE: ScriptNum = ScriptNum(1);
const DEFAULT_MAX_NUM_SIZE: usize = 4;
pub fn new(

View File

@ -41,9 +41,7 @@ pub trait ZcashScript {
///
/// Note that script verification failure is indicated by `Err(Error::Ok)`.
fn verify_callback(
sighash_callback: SighashCalculator,
lock_time: u32,
is_final: bool,
&self,
script_pub_key: &[u8],
script_sig: &[u8],
flags: VerificationFlags,
@ -51,39 +49,403 @@ pub trait ZcashScript {
/// Returns the number of transparent signature operations in the input or
/// output script pointed to by script.
fn legacy_sigop_count_script(script: &[u8]) -> Result<u32, Error>;
fn legacy_sigop_count_script(&self, script: &[u8]) -> Result<u32, Error>;
}
/// A tag to indicate that the Rust implementation of zcash_script should be used.
pub enum RustInterpreter {}
pub fn stepwise_verify<F>(
script_pub_key: &[u8],
script_sig: &[u8],
flags: VerificationFlags,
payload: &mut F::Payload,
stepper: &F,
) -> Result<(), Error>
where
F: StepFn,
{
verify_script(
&Script(script_sig),
&Script(script_pub_key),
flags,
payload,
stepper,
)
.map_err(Error::Ok)
}
impl ZcashScript for RustInterpreter {
/// A payload for comparing the results of two steppers.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StepResults<T, U> {
/// This contains the step-wise states of the steppers as long as they were identical. Its
/// `head` contains the initial state and its `tail` has a 1:1 correspondence to the opcodes
/// (not to the bytes).
pub identical_states: Vec<State>,
/// If the execution matched the entire way, then this contains `None`. If there was a
/// divergence, then this contains `Some` with a pair of `Result`s one representing each
/// steppers outcome at the point at which they diverged.
pub diverging_result: Option<(Result<State, ScriptError>, Result<State, ScriptError>)>,
/// The final payload of the first stepper.
pub payload_l: T,
/// The final payload of the second stepper.
pub payload_r: U,
}
impl<T, U> StepResults<T, U> {
pub fn initial(payload_l: T, payload_r: U) -> Self {
StepResults {
identical_states: vec![],
diverging_result: None,
payload_l,
payload_r,
}
}
}
/// This compares two `ZcashScript` implementations in a deep way checking the entire `State` step
/// by step. Note that this has some tradeoffs: one is performance. Another is that it doesnt run
/// the entire codepath of either implementation. The setup/wrapup code is specific to this
/// definition, but any differences there should be caught very easily by other testing mechanisms
/// (like `check_verify_callback`).
///
/// This returns a very debuggable result. See `StepResults` for details.
pub struct ComparisonStepEvaluator<'a, T, U> {
pub eval_step_l: &'a dyn StepFn<Payload = T>,
pub eval_step_r: &'a dyn StepFn<Payload = U>,
}
impl<'a, T: Clone, U: Clone> StepFn for ComparisonStepEvaluator<'a, T, U> {
type Payload = StepResults<T, U>;
fn call<'b>(
&self,
pc: &'b [u8],
script: &Script,
state: &mut State,
payload: &mut StepResults<T, U>,
) -> Result<&'b [u8], ScriptError> {
let mut right_state = (*state).clone();
let left = self
.eval_step_l
.call(pc, script, state, &mut payload.payload_l);
let right = self
.eval_step_r
.call(pc, script, &mut right_state, &mut payload.payload_r);
match (left, right) {
(Ok(_), Ok(_)) => {
if *state == right_state {
payload.identical_states.push(state.clone());
left
} else {
// In this case, the script hasnt failed, but we stop running
// anything
payload.diverging_result = Some((
left.map(|_| state.clone()),
right.map(|_| right_state.clone()),
));
Err(ScriptError::UnknownError)
}
}
// at least one is `Err`
(_, _) => {
if left != right {
payload.diverging_result = Some((
left.map(|_| state.clone()),
right.map(|_| right_state.clone()),
));
}
left.and(right)
}
}
}
}
pub struct StepwiseInterpreter<F>
where
F: StepFn,
{
initial_payload: F::Payload,
stepper: F,
}
impl<F: StepFn> StepwiseInterpreter<F> {
pub fn new(initial_payload: F::Payload, stepper: F) -> Self {
StepwiseInterpreter {
initial_payload,
stepper,
}
}
}
pub fn rust_interpreter<C: SignatureChecker + Copy>(
flags: VerificationFlags,
checker: C,
) -> StepwiseInterpreter<DefaultStepEvaluator<C>> {
StepwiseInterpreter {
initial_payload: (),
stepper: DefaultStepEvaluator { flags, checker },
}
}
impl<F: StepFn> ZcashScript for StepwiseInterpreter<F> {
/// Returns the number of transparent signature operations in the
/// transparent inputs and outputs of this transaction.
fn legacy_sigop_count_script(script: &[u8]) -> Result<u32, Error> {
fn legacy_sigop_count_script(&self, script: &[u8]) -> Result<u32, Error> {
let cscript = Script(script);
Ok(cscript.get_sig_op_count(false))
}
fn verify_callback(
sighash: SighashCalculator,
lock_time: u32,
is_final: bool,
&self,
script_pub_key: &[u8],
script_sig: &[u8],
flags: VerificationFlags,
) -> Result<(), Error> {
let lock_time_num = lock_time.into();
verify_script(
&Script(script_sig),
&Script(script_pub_key),
let mut payload = self.initial_payload.clone();
stepwise_verify(
script_pub_key,
script_sig,
flags,
&CallbackTransactionSignatureChecker {
sighash,
lock_time: &lock_time_num,
is_final,
},
&mut payload,
&self.stepper,
)
.map_err(Error::Ok)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::*;
use hex::FromHex;
use proptest::prelude::*;
lazy_static::lazy_static! {
pub static ref SCRIPT_PUBKEY: Vec<u8> = <Vec<u8>>::from_hex("a914c117756dcbe144a12a7c33a77cfa81aa5aeeb38187").unwrap();
pub static ref SCRIPT_SIG: Vec<u8> = <Vec<u8>>::from_hex("00483045022100d2ab3e6258fe244fa442cfb38f6cef9ac9a18c54e70b2f508e83fa87e20d040502200eead947521de943831d07a350e45af8e36c2166984a8636f0a8811ff03ed09401473044022013e15d865010c257eef133064ef69a780b4bc7ebe6eda367504e806614f940c3022062fdbc8c2d049f91db2042d6c9771de6f1ef0b3b1fea76c1ab5542e44ed29ed8014c69522103b2cc71d23eb30020a4893982a1e2d352da0d20ee657fa02901c432758909ed8f21029d1e9a9354c0d2aee9ffd0f0cea6c39bbf98c4066cf143115ba2279d0ba7dabe2103e32096b63fd57f3308149d238dcbb24d8d28aad95c0e4e74e3e5e6a11b61bcc453ae").expect("Block bytes are in valid hex representation");
}
fn sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> {
hex::decode("e8c7bdac77f6bb1f3aba2eaa1fada551a9c8b3b5ecd1ef86e6e58a5f1aab952c")
.unwrap()
.as_slice()
.first_chunk::<32>()
.copied()
}
fn invalid_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> {
hex::decode("08c7bdac77f6bb1f3aba2eaa1fada551a9c8b3b5ecd1ef86e6e58a5f1aab952c")
.unwrap()
.as_slice()
.first_chunk::<32>()
.copied()
}
fn missing_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> {
None
}
#[test]
fn it_works() {
let n_lock_time: u32 = 2410374;
let is_final: bool = true;
let script_pub_key = &SCRIPT_PUBKEY;
let script_sig = &SCRIPT_SIG;
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
let checker = CallbackTransactionSignatureChecker {
sighash: &sighash,
lock_time: n_lock_time.into(),
is_final,
};
let rust_stepper = DefaultStepEvaluator { flags, checker };
let stepper = ComparisonStepEvaluator {
eval_step_l: &rust_stepper,
eval_step_r: &rust_stepper,
};
let mut res = StepResults::initial((), ());
let ret = stepwise_verify(script_pub_key, script_sig, flags, &mut res, &stepper);
if res.diverging_result != None {
panic!("invalid result: {:?}", res);
}
assert_eq!(ret, Ok(()));
}
#[test]
fn broken_stepper_causes_divergence() {
let n_lock_time: u32 = 2410374;
let is_final: bool = true;
let script_pub_key = &SCRIPT_PUBKEY;
let script_sig = &SCRIPT_SIG;
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
let checker = CallbackTransactionSignatureChecker {
sighash: &sighash,
lock_time: n_lock_time.into(),
is_final,
};
let rust_stepper = DefaultStepEvaluator { flags, checker };
let broken_stepper = BrokenStepEvaluator(rust_stepper);
let stepper = ComparisonStepEvaluator {
eval_step_l: &rust_stepper,
eval_step_r: &broken_stepper,
};
let mut res = StepResults::initial((), ());
let ret = stepwise_verify(script_pub_key, script_sig, flags, &mut res, &stepper);
// The final return value is from whichever stepper failed.
assert_eq!(
ret,
Err(Error::Ok(ScriptError::ReadError {
expected_bytes: 1,
available_bytes: 0,
}))
);
// `State`s are large, so we just check that there was some progress in lock step, and a
// divergence.
match res {
StepResults {
identical_states,
diverging_result:
Some((
Ok(state),
Err(ScriptError::ReadError {
expected_bytes: 1,
available_bytes: 0,
}),
)),
payload_l: (),
payload_r: (),
} => {
assert!(
identical_states.len() == 6
&& state.stack().size() == 4
&& state.altstack().empty()
&& state.op_count() == 2
&& state.vexec().empty()
);
}
_ => {
panic!("invalid result: {:?}", res);
}
}
}
#[test]
fn it_fails_on_invalid_sighash() {
let n_lock_time: u32 = 2410374;
let is_final: bool = true;
let script_pub_key = &SCRIPT_PUBKEY;
let script_sig = &SCRIPT_SIG;
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
let checker = CallbackTransactionSignatureChecker {
sighash: &invalid_sighash,
lock_time: n_lock_time.into(),
is_final,
};
let rust_stepper = DefaultStepEvaluator { flags, checker };
let stepper = ComparisonStepEvaluator {
eval_step_l: &rust_stepper,
eval_step_r: &rust_stepper,
};
let mut res = StepResults::initial((), ());
let ret = stepwise_verify(script_pub_key, script_sig, flags, &mut res, &stepper);
if res.diverging_result != None {
panic!("mismatched result: {:?}", res);
}
assert_eq!(ret, Err(Error::Ok(ScriptError::EvalFalse)));
}
#[test]
fn it_fails_on_missing_sighash() {
let n_lock_time: u32 = 2410374;
let is_final: bool = true;
let script_pub_key = &SCRIPT_PUBKEY;
let script_sig = &SCRIPT_SIG;
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
let checker = CallbackTransactionSignatureChecker {
sighash: &missing_sighash,
lock_time: n_lock_time.into(),
is_final,
};
let rust_stepper = DefaultStepEvaluator { flags, checker };
let stepper = ComparisonStepEvaluator {
eval_step_l: &rust_stepper,
eval_step_r: &rust_stepper,
};
let mut res = StepResults::initial((), ());
let ret = stepwise_verify(script_pub_key, script_sig, flags, &mut res, &stepper);
if res.diverging_result != None {
panic!("mismatched result: {:?}", res);
}
assert_eq!(ret, Err(Error::Ok(ScriptError::EvalFalse)));
}
proptest! {
// The stepwise comparison tests are significantly slower than the simple comparison tests,
// so run fewer iterations.
#![proptest_config(ProptestConfig {
cases: 2_000, .. ProptestConfig::default()
})]
#[test]
fn test_arbitrary_scripts(
lock_time in prop::num::u32::ANY,
is_final in prop::bool::ANY,
pub_key in prop::collection::vec(0..=0xffu8, 0..=OVERFLOW_SCRIPT_SIZE),
sig in prop::collection::vec(0..=0xffu8, 1..=OVERFLOW_SCRIPT_SIZE),
flags in prop::bits::u32::masked(VerificationFlags::all().bits()),
) {
let checker = CallbackTransactionSignatureChecker {
sighash: &missing_sighash,
lock_time: lock_time.into(),
is_final,
};
let flags = repair_flags(VerificationFlags::from_bits_truncate(flags));
let rust_stepper = DefaultStepEvaluator { flags, checker };
let stepper = ComparisonStepEvaluator {
eval_step_l: &rust_stepper,
eval_step_r: &rust_stepper,
};
let mut res = StepResults::initial((), ());
let _ = stepwise_verify(&pub_key[..], &sig[..], flags, &mut res, &stepper);
if res.diverging_result != None {
panic!("mismatched result: {:?}", res);
}
}
/// Similar to `test_arbitrary_scripts`, but ensures the `sig` only contains pushes.
#[test]
fn test_restricted_sig_scripts(
lock_time in prop::num::u32::ANY,
is_final in prop::bool::ANY,
pub_key in prop::collection::vec(0..=0xffu8, 0..=OVERFLOW_SCRIPT_SIZE),
sig in prop::collection::vec(0..=0x60u8, 0..=OVERFLOW_SCRIPT_SIZE),
flags in prop::bits::u32::masked(
// Dont waste test cases on whether or not `SigPushOnly` is set.
(VerificationFlags::all() - VerificationFlags::SigPushOnly).bits()),
) {
let checker = CallbackTransactionSignatureChecker {
sighash: &missing_sighash,
lock_time: lock_time.into(),
is_final,
};
let flags = repair_flags(VerificationFlags::from_bits_truncate(flags)) | VerificationFlags::SigPushOnly;
let rust_stepper = DefaultStepEvaluator { flags, checker };
let stepper = ComparisonStepEvaluator {
eval_step_l: &rust_stepper,
eval_step_r: &rust_stepper,
};
let mut res = StepResults::initial((), ());
let _ = stepwise_verify(&pub_key[..], &sig[..], flags, &mut res, &stepper);
if res.diverging_result != None {
panic!("mismatched result: {:?}", res);
}
}
}
}