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:
parent
2afc474338
commit
cc157ffdce
|
@ -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!` doesn’t 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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
361
src/lib.rs
361
src/lib.rs
|
@ -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(
|
||||
// Don’t 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);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
/// stepper’s 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 doesn’t 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 hasn’t 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(
|
||||
// Don’t 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue