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,
|
||||
lock_time,
|
||||
is_final,
|
||||
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,
|
||||
|
|
1631
src/interpreter.rs
1631
src/interpreter.rs
File diff suppressed because it is too large
Load Diff
367
src/lib.rs
367
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,
|
||||
is_final,
|
||||
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,
|
||||
is_final,
|
||||
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,
|
||||
is_final,
|
||||
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