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;
|
use libfuzzer_sys::fuzz_target;
|
||||||
extern crate zcash_script;
|
extern crate zcash_script;
|
||||||
|
|
||||||
|
use zcash_script::interpreter::CallbackTransactionSignatureChecker;
|
||||||
use zcash_script::*;
|
use zcash_script::*;
|
||||||
|
|
||||||
fn missing_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> {
|
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!(|tup: (u32, bool, &[u8], &[u8], u32)| {
|
||||||
// `fuzz_target!` doesn’t support pattern matching in the parameter list.
|
// `fuzz_target!` doesn’t support pattern matching in the parameter list.
|
||||||
let (lock_time, is_final, pub_key, sig, flags) = tup;
|
let (lock_time, is_final, pub_key, sig, flag_bits) = tup;
|
||||||
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
|
let flags = testing::repair_flags(VerificationFlags::from_bits_truncate(flag_bits));
|
||||||
&missing_sighash,
|
let ret = check_verify_callback(
|
||||||
lock_time,
|
&CxxInterpreter {
|
||||||
is_final,
|
sighash: &missing_sighash,
|
||||||
|
lock_time,
|
||||||
|
is_final,
|
||||||
|
},
|
||||||
|
&rust_interpreter(
|
||||||
|
flags,
|
||||||
|
CallbackTransactionSignatureChecker {
|
||||||
|
sighash: &missing_sighash,
|
||||||
|
lock_time: lock_time.into(),
|
||||||
|
is_final,
|
||||||
|
},
|
||||||
|
),
|
||||||
pub_key,
|
pub_key,
|
||||||
sig,
|
sig,
|
||||||
testing::repair_flags(VerificationFlags::from_bits_truncate(flags)),
|
flags,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ret.0,
|
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]
|
#[macro_use]
|
||||||
extern crate enum_primitive;
|
extern crate enum_primitive;
|
||||||
|
|
||||||
mod cxx;
|
pub mod cxx;
|
||||||
mod external;
|
mod external;
|
||||||
mod interpreter;
|
pub mod interpreter;
|
||||||
mod script;
|
mod script;
|
||||||
pub mod script_error;
|
pub mod script_error;
|
||||||
mod zcash_script;
|
mod zcash_script;
|
||||||
|
@ -18,92 +18,76 @@ use std::os::raw::{c_int, c_uint, c_void};
|
||||||
|
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
pub use cxx::*;
|
pub use interpreter::{
|
||||||
pub use interpreter::{HashType, SighashCalculator, SignedOutputs, VerificationFlags};
|
CallbackTransactionSignatureChecker, DefaultStepEvaluator, HashType, SighashCalculator,
|
||||||
|
SignedOutputs, VerificationFlags,
|
||||||
|
};
|
||||||
|
use script_error::ScriptError;
|
||||||
pub use zcash_script::*;
|
pub use zcash_script::*;
|
||||||
|
|
||||||
/// A tag to indicate that the C++ implementation of zcash_script should be used.
|
pub struct CxxInterpreter<'a> {
|
||||||
pub enum CxxInterpreter {}
|
pub sighash: SighashCalculator<'a>,
|
||||||
|
pub lock_time: u32,
|
||||||
|
pub is_final: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl From<cxx::ScriptError> for Error {
|
impl From<cxx::ScriptError> for Error {
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
fn from(err_code: cxx::ScriptError) -> Self {
|
fn from(err_code: cxx::ScriptError) -> Self {
|
||||||
match err_code {
|
match err_code {
|
||||||
ScriptError_t_SCRIPT_ERR_OK => Error::Ok(script_error::ScriptError::Ok),
|
cxx::ScriptError_t_SCRIPT_ERR_OK => Error::Ok(ScriptError::Ok),
|
||||||
ScriptError_t_SCRIPT_ERR_UNKNOWN_ERROR => {
|
cxx::ScriptError_t_SCRIPT_ERR_UNKNOWN_ERROR => Error::Ok(ScriptError::UnknownError),
|
||||||
Error::Ok(script_error::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_EVAL_FALSE => Error::Ok(script_error::ScriptError::EvalFalse),
|
|
||||||
ScriptError_t_SCRIPT_ERR_OP_RETURN => Error::Ok(script_error::ScriptError::OpReturn),
|
|
||||||
|
|
||||||
ScriptError_t_SCRIPT_ERR_SCRIPT_SIZE => {
|
cxx::ScriptError_t_SCRIPT_ERR_SCRIPT_SIZE => Error::Ok(ScriptError::ScriptSize),
|
||||||
Error::Ok(script_error::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),
|
cxx::ScriptError_t_SCRIPT_ERR_CHECKSIGVERIFY => Error::Ok(ScriptError::CheckSigVerify),
|
||||||
ScriptError_t_SCRIPT_ERR_OP_COUNT => Error::Ok(script_error::ScriptError::OpCount),
|
cxx::ScriptError_t_SCRIPT_ERR_NUMEQUALVERIFY => Error::Ok(ScriptError::NumEqualVerify),
|
||||||
ScriptError_t_SCRIPT_ERR_STACK_SIZE => Error::Ok(script_error::ScriptError::StackSize),
|
|
||||||
ScriptError_t_SCRIPT_ERR_SIG_COUNT => Error::Ok(script_error::ScriptError::SigCount),
|
cxx::ScriptError_t_SCRIPT_ERR_BAD_OPCODE => Error::Ok(ScriptError::BadOpcode),
|
||||||
ScriptError_t_SCRIPT_ERR_PUBKEY_COUNT => {
|
cxx::ScriptError_t_SCRIPT_ERR_DISABLED_OPCODE => Error::Ok(ScriptError::DisabledOpcode),
|
||||||
Error::Ok(script_error::ScriptError::PubKeyCount)
|
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),
|
cxx::ScriptError_t_SCRIPT_ERR_NEGATIVE_LOCKTIME => {
|
||||||
ScriptError_t_SCRIPT_ERR_EQUALVERIFY => {
|
Error::Ok(ScriptError::NegativeLockTime)
|
||||||
Error::Ok(script_error::ScriptError::EqualVerify)
|
|
||||||
}
|
}
|
||||||
ScriptError_t_SCRIPT_ERR_CHECKMULTISIGVERIFY => {
|
cxx::ScriptError_t_SCRIPT_ERR_UNSATISFIED_LOCKTIME => {
|
||||||
Error::Ok(script_error::ScriptError::CheckMultisigVerify)
|
Error::Ok(ScriptError::UnsatisfiedLockTime)
|
||||||
}
|
|
||||||
ScriptError_t_SCRIPT_ERR_CHECKSIGVERIFY => {
|
|
||||||
Error::Ok(script_error::ScriptError::CheckSigVerify)
|
|
||||||
}
|
|
||||||
ScriptError_t_SCRIPT_ERR_NUMEQUALVERIFY => {
|
|
||||||
Error::Ok(script_error::ScriptError::NumEqualVerify)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptError_t_SCRIPT_ERR_BAD_OPCODE => Error::Ok(script_error::ScriptError::BadOpcode),
|
cxx::ScriptError_t_SCRIPT_ERR_SIG_HASHTYPE => Error::Ok(ScriptError::SigHashType),
|
||||||
ScriptError_t_SCRIPT_ERR_DISABLED_OPCODE => {
|
cxx::ScriptError_t_SCRIPT_ERR_SIG_DER => Error::Ok(ScriptError::SigDER),
|
||||||
Error::Ok(script_error::ScriptError::DisabledOpcode)
|
cxx::ScriptError_t_SCRIPT_ERR_MINIMALDATA => Error::Ok(ScriptError::MinimalData),
|
||||||
}
|
cxx::ScriptError_t_SCRIPT_ERR_SIG_PUSHONLY => Error::Ok(ScriptError::SigPushOnly),
|
||||||
ScriptError_t_SCRIPT_ERR_INVALID_STACK_OPERATION => {
|
cxx::ScriptError_t_SCRIPT_ERR_SIG_HIGH_S => Error::Ok(ScriptError::SigHighS),
|
||||||
Error::Ok(script_error::ScriptError::InvalidStackOperation)
|
cxx::ScriptError_t_SCRIPT_ERR_SIG_NULLDUMMY => Error::Ok(ScriptError::SigNullDummy),
|
||||||
}
|
cxx::ScriptError_t_SCRIPT_ERR_PUBKEYTYPE => Error::Ok(ScriptError::PubKeyType),
|
||||||
ScriptError_t_SCRIPT_ERR_INVALID_ALTSTACK_OPERATION => {
|
cxx::ScriptError_t_SCRIPT_ERR_CLEANSTACK => Error::Ok(ScriptError::CleanStack),
|
||||||
Error::Ok(script_error::ScriptError::InvalidAltstackOperation)
|
|
||||||
}
|
cxx::ScriptError_t_SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS => {
|
||||||
ScriptError_t_SCRIPT_ERR_UNBALANCED_CONDITIONAL => {
|
Error::Ok(ScriptError::DiscourageUpgradableNOPs)
|
||||||
Error::Ok(script_error::ScriptError::UnbalancedConditional)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptError_t_SCRIPT_ERR_NEGATIVE_LOCKTIME => {
|
cxx::ScriptError_t_SCRIPT_ERR_VERIFY_SCRIPT => Error::VerifyScript,
|
||||||
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,
|
|
||||||
unknown => Error::Unknown(unknown.into()),
|
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.
|
/// 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(
|
fn verify_callback(
|
||||||
sighash: SighashCalculator,
|
&self,
|
||||||
lock_time: u32,
|
|
||||||
is_final: bool,
|
|
||||||
script_pub_key: &[u8],
|
script_pub_key: &[u8],
|
||||||
signature_script: &[u8],
|
signature_script: &[u8],
|
||||||
flags: VerificationFlags,
|
flags: VerificationFlags,
|
||||||
|
@ -156,11 +138,11 @@ impl ZcashScript for CxxInterpreter {
|
||||||
|
|
||||||
// SAFETY: The `script` fields are created from a valid Rust `slice`.
|
// SAFETY: The `script` fields are created from a valid Rust `slice`.
|
||||||
let ret = unsafe {
|
let ret = unsafe {
|
||||||
zcash_script_verify_callback(
|
cxx::zcash_script_verify_callback(
|
||||||
(&sighash as *const SighashCalculator) as *const c_void,
|
(&self.sighash as *const SighashCalculator) as *const c_void,
|
||||||
Some(sighash_callback),
|
Some(sighash_callback),
|
||||||
lock_time.into(),
|
self.lock_time.into(),
|
||||||
if is_final { 1 } else { 0 },
|
if self.is_final { 1 } else { 0 },
|
||||||
script_pub_key.as_ptr(),
|
script_pub_key.as_ptr(),
|
||||||
script_pub_key
|
script_pub_key
|
||||||
.len()
|
.len()
|
||||||
|
@ -185,13 +167,13 @@ impl ZcashScript for CxxInterpreter {
|
||||||
|
|
||||||
/// Returns the number of transparent signature operations in the
|
/// Returns the number of transparent signature operations in the
|
||||||
/// transparent inputs and outputs of this transaction.
|
/// 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
|
script
|
||||||
.len()
|
.len()
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(Error::InvalidScriptSize)
|
.map_err(Error::InvalidScriptSize)
|
||||||
.map(|script_len| unsafe {
|
.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
|
/// 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.
|
/// differ and always returns the C++ result.
|
||||||
fn check_legacy_sigop_count_script<T: ZcashScript, U: ZcashScript>(
|
fn check_legacy_sigop_count_script<T: ZcashScript, U: ZcashScript>(
|
||||||
|
first: &T,
|
||||||
|
second: &U,
|
||||||
script: &[u8],
|
script: &[u8],
|
||||||
) -> (Result<u32, Error>, Result<u32, Error>) {
|
) -> (Result<u32, Error>, Result<u32, Error>) {
|
||||||
(
|
(
|
||||||
T::legacy_sigop_count_script(script),
|
first.legacy_sigop_count_script(script),
|
||||||
U::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
|
/// 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.
|
/// differ and always returns the `T` result.
|
||||||
pub fn check_verify_callback<T: ZcashScript, U: ZcashScript>(
|
pub fn check_verify_callback<T: ZcashScript, U: ZcashScript>(
|
||||||
sighash: SighashCalculator,
|
first: &T,
|
||||||
lock_time: u32,
|
second: &U,
|
||||||
is_final: bool,
|
|
||||||
script_pub_key: &[u8],
|
script_pub_key: &[u8],
|
||||||
script_sig: &[u8],
|
script_sig: &[u8],
|
||||||
flags: VerificationFlags,
|
flags: VerificationFlags,
|
||||||
) -> (Result<(), Error>, Result<(), Error>) {
|
) -> (Result<(), Error>, Result<(), Error>) {
|
||||||
(
|
(
|
||||||
T::verify_callback(
|
first.verify_callback(script_pub_key, script_sig, flags),
|
||||||
sighash,
|
second.verify_callback(script_pub_key, script_sig, flags),
|
||||||
lock_time,
|
|
||||||
is_final,
|
|
||||||
script_pub_key,
|
|
||||||
script_sig,
|
|
||||||
flags,
|
|
||||||
),
|
|
||||||
U::verify_callback(
|
|
||||||
sighash,
|
|
||||||
lock_time,
|
|
||||||
is_final,
|
|
||||||
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 {
|
pub fn normalize_error(err: Error) -> Error {
|
||||||
match err {
|
match err {
|
||||||
Error::Ok(serr) => Error::Ok(match serr {
|
Error::Ok(serr) => Error::Ok(match serr {
|
||||||
script_error::ScriptError::ReadError { .. } => script_error::ScriptError::BadOpcode,
|
ScriptError::ReadError { .. } => ScriptError::BadOpcode,
|
||||||
script_error::ScriptError::ScriptNumError(_) => script_error::ScriptError::UnknownError,
|
ScriptError::ScriptNumError(_) => ScriptError::UnknownError,
|
||||||
_ => serr,
|
_ => serr,
|
||||||
}),
|
}),
|
||||||
_ => err,
|
_ => 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,
|
/// A tag to indicate that both the C++ and Rust implementations of zcash_script should be used,
|
||||||
/// with their results compared.
|
/// 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`)
|
/// 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 and logs a warning if they disagree.
|
||||||
impl ZcashScript for CxxRustComparisonInterpreter {
|
impl<T: ZcashScript, U: ZcashScript> ZcashScript for ComparisonInterpreter<T, U> {
|
||||||
fn legacy_sigop_count_script(script: &[u8]) -> Result<u32, Error> {
|
fn legacy_sigop_count_script(&self, script: &[u8]) -> Result<u32, Error> {
|
||||||
let (cxx, rust) =
|
let (cxx, rust) = check_legacy_sigop_count_script(&self.first, &self.second, script);
|
||||||
check_legacy_sigop_count_script::<CxxInterpreter, RustInterpreter>(script);
|
|
||||||
if rust != cxx {
|
if rust != cxx {
|
||||||
warn!(
|
warn!(
|
||||||
"The Rust Zcash Script interpreter had a different sigop count ({:?}) from the C++ one ({:?}).",
|
"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(
|
fn verify_callback(
|
||||||
sighash: SighashCalculator,
|
&self,
|
||||||
lock_time: u32,
|
|
||||||
is_final: bool,
|
|
||||||
script_pub_key: &[u8],
|
script_pub_key: &[u8],
|
||||||
script_sig: &[u8],
|
script_sig: &[u8],
|
||||||
flags: VerificationFlags,
|
flags: VerificationFlags,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let (cxx, rust) = check_verify_callback::<CxxInterpreter, RustInterpreter>(
|
let (cxx, rust) =
|
||||||
sighash,
|
check_verify_callback(&self.first, &self.second, script_pub_key, script_sig, flags);
|
||||||
lock_time,
|
|
||||||
is_final,
|
|
||||||
script_pub_key,
|
|
||||||
script_sig,
|
|
||||||
flags,
|
|
||||||
);
|
|
||||||
if rust.map_err(normalize_error) != cxx {
|
if rust.map_err(normalize_error) != cxx {
|
||||||
// probably want to distinguish between
|
// probably want to distinguish between
|
||||||
// - one succeeding when the other fails (bad), and
|
// - one succeeding when the other fails (bad), and
|
||||||
|
@ -302,6 +291,10 @@ impl ZcashScript for CxxRustComparisonInterpreter {
|
||||||
#[cfg(any(test, feature = "test-dependencies"))]
|
#[cfg(any(test, feature = "test-dependencies"))]
|
||||||
pub mod testing {
|
pub mod testing {
|
||||||
use super::*;
|
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
|
/// Ensures that flags represent a supported state. This avoids crashes in the C++ code, which
|
||||||
/// break various tests.
|
/// break various tests.
|
||||||
|
@ -317,6 +310,33 @@ pub mod testing {
|
||||||
|
|
||||||
/// A `usize` one larger than the longest allowed script, for testing bounds.
|
/// A `usize` one larger than the longest allowed script, for testing bounds.
|
||||||
pub const OVERFLOW_SCRIPT_SIZE: usize = script::MAX_SCRIPT_SIZE + 1;
|
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)]
|
#[cfg(test)]
|
||||||
|
@ -352,16 +372,26 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_works() {
|
fn it_works() {
|
||||||
let n_lock_time: u32 = 2410374;
|
let lock_time: u32 = 2410374;
|
||||||
let is_final: bool = true;
|
let is_final: bool = true;
|
||||||
let script_pub_key = &SCRIPT_PUBKEY;
|
let script_pub_key = &SCRIPT_PUBKEY;
|
||||||
let script_sig = &SCRIPT_SIG;
|
let script_sig = &SCRIPT_SIG;
|
||||||
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
|
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
|
||||||
|
|
||||||
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
|
let ret = check_verify_callback(
|
||||||
&sighash,
|
&CxxInterpreter {
|
||||||
n_lock_time,
|
sighash: &sighash,
|
||||||
is_final,
|
lock_time,
|
||||||
|
is_final,
|
||||||
|
},
|
||||||
|
&rust_interpreter(
|
||||||
|
flags,
|
||||||
|
CallbackTransactionSignatureChecker {
|
||||||
|
sighash: &sighash,
|
||||||
|
lock_time: lock_time.into(),
|
||||||
|
is_final,
|
||||||
|
},
|
||||||
|
),
|
||||||
script_pub_key,
|
script_pub_key,
|
||||||
script_sig,
|
script_sig,
|
||||||
flags,
|
flags,
|
||||||
|
@ -373,44 +403,63 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_fails_on_invalid_sighash() {
|
fn it_fails_on_invalid_sighash() {
|
||||||
let n_lock_time: u32 = 2410374;
|
let lock_time: u32 = 2410374;
|
||||||
let is_final: bool = true;
|
let is_final: bool = true;
|
||||||
let script_pub_key = &SCRIPT_PUBKEY;
|
let script_pub_key = &SCRIPT_PUBKEY;
|
||||||
let script_sig = &SCRIPT_SIG;
|
let script_sig = &SCRIPT_SIG;
|
||||||
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
|
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
|
||||||
|
let ret = check_verify_callback(
|
||||||
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
|
&CxxInterpreter {
|
||||||
&invalid_sighash,
|
sighash: &invalid_sighash,
|
||||||
n_lock_time,
|
lock_time,
|
||||||
is_final,
|
is_final,
|
||||||
|
},
|
||||||
|
&rust_interpreter(
|
||||||
|
flags,
|
||||||
|
CallbackTransactionSignatureChecker {
|
||||||
|
sighash: &invalid_sighash,
|
||||||
|
lock_time: lock_time.into(),
|
||||||
|
is_final,
|
||||||
|
},
|
||||||
|
),
|
||||||
script_pub_key,
|
script_pub_key,
|
||||||
script_sig,
|
script_sig,
|
||||||
flags,
|
flags,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(ret.0, ret.1.map_err(normalize_error));
|
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]
|
#[test]
|
||||||
fn it_fails_on_missing_sighash() {
|
fn it_fails_on_missing_sighash() {
|
||||||
let n_lock_time: u32 = 2410374;
|
let lock_time: u32 = 2410374;
|
||||||
let is_final: bool = true;
|
let is_final: bool = true;
|
||||||
let script_pub_key = &SCRIPT_PUBKEY;
|
let script_pub_key = &SCRIPT_PUBKEY;
|
||||||
let script_sig = &SCRIPT_SIG;
|
let script_sig = &SCRIPT_SIG;
|
||||||
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
|
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
|
||||||
|
|
||||||
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
|
let ret = check_verify_callback(
|
||||||
&missing_sighash,
|
&CxxInterpreter {
|
||||||
n_lock_time,
|
sighash: &missing_sighash,
|
||||||
is_final,
|
lock_time,
|
||||||
|
is_final,
|
||||||
|
},
|
||||||
|
&rust_interpreter(
|
||||||
|
flags,
|
||||||
|
CallbackTransactionSignatureChecker {
|
||||||
|
sighash: &missing_sighash,
|
||||||
|
lock_time: lock_time.into(),
|
||||||
|
is_final,
|
||||||
|
},
|
||||||
|
),
|
||||||
script_pub_key,
|
script_pub_key,
|
||||||
script_sig,
|
script_sig,
|
||||||
flags,
|
flags,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(ret.0, ret.1.map_err(normalize_error));
|
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! {
|
proptest! {
|
||||||
|
@ -424,15 +473,26 @@ mod tests {
|
||||||
is_final in prop::bool::ANY,
|
is_final in prop::bool::ANY,
|
||||||
pub_key in prop::collection::vec(0..=0xffu8, 0..=OVERFLOW_SCRIPT_SIZE),
|
pub_key in prop::collection::vec(0..=0xffu8, 0..=OVERFLOW_SCRIPT_SIZE),
|
||||||
sig in prop::collection::vec(0..=0xffu8, 1..=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>(
|
let flags = repair_flags(VerificationFlags::from_bits_truncate(flag_bits));
|
||||||
&missing_sighash,
|
let ret = check_verify_callback(
|
||||||
|
&CxxInterpreter {
|
||||||
|
sighash: &sighash,
|
||||||
lock_time,
|
lock_time,
|
||||||
is_final,
|
is_final,
|
||||||
|
},
|
||||||
|
&rust_interpreter(
|
||||||
|
flags,
|
||||||
|
CallbackTransactionSignatureChecker {
|
||||||
|
sighash: &sighash,
|
||||||
|
lock_time: lock_time.into(),
|
||||||
|
is_final,
|
||||||
|
},
|
||||||
|
),
|
||||||
&pub_key[..],
|
&pub_key[..],
|
||||||
&sig[..],
|
&sig[..],
|
||||||
repair_flags(VerificationFlags::from_bits_truncate(flags)),
|
flags,
|
||||||
);
|
);
|
||||||
prop_assert_eq!(ret.0, ret.1.map_err(normalize_error),
|
prop_assert_eq!(ret.0, ret.1.map_err(normalize_error),
|
||||||
"original Rust result: {:?}", ret.1);
|
"original Rust result: {:?}", ret.1);
|
||||||
|
@ -445,18 +505,29 @@ mod tests {
|
||||||
is_final in prop::bool::ANY,
|
is_final in prop::bool::ANY,
|
||||||
pub_key in prop::collection::vec(0..=0xffu8, 0..=OVERFLOW_SCRIPT_SIZE),
|
pub_key in prop::collection::vec(0..=0xffu8, 0..=OVERFLOW_SCRIPT_SIZE),
|
||||||
sig in prop::collection::vec(0..=0x60u8, 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.
|
// Don’t waste test cases on whether or not `SigPushOnly` is set.
|
||||||
(VerificationFlags::all() - VerificationFlags::SigPushOnly).bits()),
|
(VerificationFlags::all() - VerificationFlags::SigPushOnly).bits()),
|
||||||
) {
|
) {
|
||||||
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
|
let flags = repair_flags(VerificationFlags::from_bits_truncate(flag_bits))
|
||||||
&missing_sighash,
|
| VerificationFlags::SigPushOnly;
|
||||||
|
let ret = check_verify_callback(
|
||||||
|
&CxxInterpreter {
|
||||||
|
sighash: &sighash,
|
||||||
lock_time,
|
lock_time,
|
||||||
is_final,
|
is_final,
|
||||||
|
},
|
||||||
|
&rust_interpreter(
|
||||||
|
flags,
|
||||||
|
CallbackTransactionSignatureChecker {
|
||||||
|
sighash: &sighash,
|
||||||
|
lock_time: lock_time.into(),
|
||||||
|
is_final,
|
||||||
|
},
|
||||||
|
),
|
||||||
&pub_key[..],
|
&pub_key[..],
|
||||||
&sig[..],
|
&sig[..],
|
||||||
repair_flags(VerificationFlags::from_bits_truncate(flags))
|
flags,
|
||||||
| VerificationFlags::SigPushOnly,
|
|
||||||
);
|
);
|
||||||
prop_assert_eq!(ret.0, ret.1.map_err(normalize_error),
|
prop_assert_eq!(ret.0, ret.1.map_err(normalize_error),
|
||||||
"original Rust result: {:?}", ret.1);
|
"original Rust result: {:?}", ret.1);
|
||||||
|
|
|
@ -269,6 +269,9 @@ impl From<Operation> for u8 {
|
||||||
pub struct ScriptNum(i64);
|
pub struct ScriptNum(i64);
|
||||||
|
|
||||||
impl ScriptNum {
|
impl ScriptNum {
|
||||||
|
pub const ZERO: ScriptNum = ScriptNum(0);
|
||||||
|
pub const ONE: ScriptNum = ScriptNum(1);
|
||||||
|
|
||||||
const DEFAULT_MAX_NUM_SIZE: usize = 4;
|
const DEFAULT_MAX_NUM_SIZE: usize = 4;
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
|
|
@ -41,9 +41,7 @@ pub trait ZcashScript {
|
||||||
///
|
///
|
||||||
/// Note that script verification failure is indicated by `Err(Error::Ok)`.
|
/// Note that script verification failure is indicated by `Err(Error::Ok)`.
|
||||||
fn verify_callback(
|
fn verify_callback(
|
||||||
sighash_callback: SighashCalculator,
|
&self,
|
||||||
lock_time: u32,
|
|
||||||
is_final: bool,
|
|
||||||
script_pub_key: &[u8],
|
script_pub_key: &[u8],
|
||||||
script_sig: &[u8],
|
script_sig: &[u8],
|
||||||
flags: VerificationFlags,
|
flags: VerificationFlags,
|
||||||
|
@ -51,39 +49,403 @@ pub trait ZcashScript {
|
||||||
|
|
||||||
/// Returns the number of transparent signature operations in the input or
|
/// Returns the number of transparent signature operations in the input or
|
||||||
/// output script pointed to by script.
|
/// 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 fn stepwise_verify<F>(
|
||||||
pub enum RustInterpreter {}
|
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
|
/// Returns the number of transparent signature operations in the
|
||||||
/// transparent inputs and outputs of this transaction.
|
/// 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);
|
let cscript = Script(script);
|
||||||
Ok(cscript.get_sig_op_count(false))
|
Ok(cscript.get_sig_op_count(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_callback(
|
fn verify_callback(
|
||||||
sighash: SighashCalculator,
|
&self,
|
||||||
lock_time: u32,
|
|
||||||
is_final: bool,
|
|
||||||
script_pub_key: &[u8],
|
script_pub_key: &[u8],
|
||||||
script_sig: &[u8],
|
script_sig: &[u8],
|
||||||
flags: VerificationFlags,
|
flags: VerificationFlags,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let lock_time_num = lock_time.into();
|
let mut payload = self.initial_payload.clone();
|
||||||
verify_script(
|
stepwise_verify(
|
||||||
&Script(script_sig),
|
script_pub_key,
|
||||||
&Script(script_pub_key),
|
script_sig,
|
||||||
flags,
|
flags,
|
||||||
&CallbackTransactionSignatureChecker {
|
&mut payload,
|
||||||
sighash,
|
&self.stepper,
|
||||||
lock_time: &lock_time_num,
|
|
||||||
is_final,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.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