diff --git a/fuzz/fuzz_targets/compare.rs b/fuzz/fuzz_targets/compare.rs index ff2a12438..8980d4c56 100644 --- a/fuzz/fuzz_targets/compare.rs +++ b/fuzz/fuzz_targets/compare.rs @@ -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::( - &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, diff --git a/src/interpreter.rs b/src/interpreter.rs index 148cf055b..db41c2197 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -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(Vec); /// Wraps a Vec (or whatever underlying implementation we choose in a way that matches the C++ impl /// and provides us some decent chaining) -impl Stack { +impl Stack { + pub fn new() -> Self { + Stack(vec![]) + } + fn reverse_index(&self, i: isize) -> Result { usize::try_from(-i) .map(|a| self.0.len() - a) @@ -225,6 +229,17 @@ impl Stack { 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), 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) { for _ in 0..end.map_or(1, |e| e - start) { self.0.remove(start); @@ -444,17 +459,820 @@ fn check_minimal_push(data: &[u8], opcode: PushValue) -> bool { true } -pub fn eval_script( - stack: &mut Stack>, +const VCH_FALSE: ValType = Vec::new(); +const VCH_TRUE: [u8; 1] = [1]; + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct State { + stack: Stack>, + altstack: Stack>, + // 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, +} + +impl State { + /// Creates a new empty state. + pub fn new() -> Self { + Self::initial(Stack::new()) + } + + /// Creates a state with an initial stack, but other components empty. + pub fn initial(stack: Stack>) -> Self { + Self::from_parts(stack, Stack::new(), 0, Stack::new()) + } + + /// Create an arbitrary state. + pub fn from_parts( + stack: Stack>, + altstack: Stack>, + op_count: u8, + vexec: Stack, + ) -> Self { + State { + stack, + altstack, + op_count, + vexec, + } + } + + pub fn stack(&self) -> &Stack> { + &self.stack + } + + pub fn altstack(&self) -> &Stack> { + &self.altstack + } + + pub fn op_count(&self) -> u8 { + self.op_count + } + + pub fn vexec(&self) -> &Stack { + &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: &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]; + 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; + // Are we in an executing branch of the script? + let exec = vexec.iter().all(|value| *value); + + // + // Read instruction + // + let (opcode, vch_push_value, new_pc) = Script::get_op2(pc)?; + if vch_push_value.len() > MAX_SCRIPT_ELEMENT_SIZE { + return set_error(ScriptError::PushSize); + } + + match opcode { + Opcode::PushValue(pv) => { + if exec { + match pv { + // + // 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 => { + // ( -- value) + let bn = ScriptNum::from(u8::from(pv)) - u8::from(OP_RESERVED).into(); + stack.push_back(bn.getvch()); + // The result of these opcodes should always be the minimal way to push the data + // they push, so no need for a CheckMinimalPush here. + } + _ => { + if pv <= OP_PUSHDATA4 { + if require_minimal && !check_minimal_push(vch_push_value, pv) { + return set_error(ScriptError::MinimalData); + } + stack.push_back(vch_push_value.to_vec()); + } else { + return set_error(ScriptError::BadOpcode); + } + } + } + } + } + Opcode::Operation(op) => { + // Note how OP_RESERVED does not count towards the opcode limit. + *op_count += 1; + if *op_count > 201 { + return set_error(ScriptError::OpCount); + } + + if op == OP_CAT + || op == OP_SUBSTR + || op == OP_LEFT + || op == OP_RIGHT + || op == OP_INVERT + || op == OP_AND + || op == OP_OR + || op == OP_XOR + || op == OP_2MUL + || op == OP_2DIV + || op == OP_MUL + || op == OP_DIV + || op == OP_MOD + || op == OP_LSHIFT + || op == OP_RSHIFT + || op == OP_CODESEPARATOR + { + return set_error(ScriptError::DisabledOpcode); // Disabled opcodes. + } + + if exec || (OP_IF <= op && op <= OP_ENDIF) { + match op { + // + // Control + // + OP_NOP => (), + + OP_CHECKLOCKTIMEVERIFY => { + // https://zips.z.cash/protocol/protocol.pdf#bips : + // + // The following BIPs apply starting from the Zcash genesis block, + // i.e. any activation rules or exceptions for particular blocks in + // the Bitcoin block chain are to be ignored: [BIP-16], [BIP-30], + // [BIP-65], [BIP-66]. + // + // So BIP 65, which defines CHECKLOCKTIMEVERIFY, is in practice always + // enabled, and this `if` branch is dead code. In zcashd see + // https://github.com/zcash/zcash/blob/a3435336b0c561799ac6805a27993eca3f9656df/src/main.cpp#L3151 + if !flags.contains(VerificationFlags::CHECKLOCKTIMEVERIFY) { + if flags.contains(VerificationFlags::DiscourageUpgradableNOPs) { + return set_error(ScriptError::DiscourageUpgradableNOPs); + } + } else { + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + + // Note that elsewhere numeric opcodes are limited to + // operands in the range -2**31+1 to 2**31-1, however it is + // legal for opcodes to produce results exceeding that + // range. This limitation is implemented by `ScriptNum`'s + // default 4-byte limit. + // + // If we kept to that limit we'd have a year 2038 problem, + // even though the `lock_time` field in transactions + // themselves is u32 which only becomes meaningless + // after the year 2106. + // + // Thus as a special case we tell `ScriptNum` to accept up + // to 5-byte bignums, which are good until 2**39-1, well + // beyond the 2**32-1 limit of the `lock_time` field itself. + let lock_time = + ScriptNum::new(stack.top(-1)?, require_minimal, Some(5))?; + + // 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 < ScriptNum::ZERO { + return set_error(ScriptError::NegativeLockTime); + } + + // Actually compare the specified lock time with the transaction. + if !checker.check_lock_time(&lock_time) { + return set_error(ScriptError::UnsatisfiedLockTime); + } + } + } + + OP_NOP1 | OP_NOP3 | OP_NOP4 | OP_NOP5 | OP_NOP6 | OP_NOP7 | OP_NOP8 + | OP_NOP9 | OP_NOP10 => { + // Do nothing, though if the caller wants to prevent people from using + // these NOPs (as part of a standard tx rule, for example) they can + // enable `DiscourageUpgradableNOPs` to turn these opcodes into errors. + if flags.contains(VerificationFlags::DiscourageUpgradableNOPs) { + return set_error(ScriptError::DiscourageUpgradableNOPs); + } + } + + OP_IF | OP_NOTIF => { + // if [statements] [else [statements]] endif + let mut value = false; + if exec { + if stack.size() < 1 { + return set_error(ScriptError::UnbalancedConditional); + } + let vch: &ValType = stack.top(-1)?; + value = cast_to_bool(vch); + if op == OP_NOTIF { + value = !value + }; + stack.pop()?; + } + vexec.push_back(value); + } + + OP_ELSE => { + if vexec.empty() { + return set_error(ScriptError::UnbalancedConditional); + } + vexec.back().map(|last| *last = !*last)?; + } + + OP_ENDIF => { + if vexec.empty() { + return set_error(ScriptError::UnbalancedConditional); + } + vexec.pop()?; + } + + OP_VERIFY => { + // (true -- ) or + // (false -- false) and return + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + let value = cast_to_bool(stack.top(-1)?); + if value { + stack.pop()?; + } else { + return set_error(ScriptError::Verify); + } + } + + OP_RETURN => return set_error(ScriptError::OpReturn), + + // + // Stack ops + // + OP_TOALTSTACK => { + if stack.empty() { + return set_error(ScriptError::InvalidStackOperation); + } + altstack.push_back(stack.top(-1)?.clone()); + stack.pop()?; + } + + OP_FROMALTSTACK => { + if altstack.empty() { + return set_error(ScriptError::InvalidAltstackOperation); + } + stack.push_back(altstack.top(-1)?.clone()); + altstack.pop()?; + } + + OP_2DROP => { + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + + stack.pop()?; + stack.pop()?; + } + + OP_2DUP => { + // (x1 x2 -- x1 x2 x1 x2) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch1 = stack.top(-2)?.clone(); + let vch2 = stack.top(-1)?.clone(); + stack.push_back(vch1); + stack.push_back(vch2); + } + + OP_3DUP => { + // (x1 x2 x3 -- x1 x2 x3 x1 x2 x3) + if stack.size() < 3 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch1 = stack.top(-3)?.clone(); + let vch2 = stack.top(-2)?.clone(); + let vch3 = stack.top(-1)?.clone(); + stack.push_back(vch1); + stack.push_back(vch2); + stack.push_back(vch3); + } + + OP_2OVER => { + // (x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2) + if stack.size() < 4 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch1 = stack.top(-4)?.clone(); + let vch2 = stack.top(-3)?.clone(); + stack.push_back(vch1); + stack.push_back(vch2); + } + + OP_2ROT => { + // (x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2) + if stack.size() < 6 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch1 = stack.top(-6)?.clone(); + let vch2 = stack.top(-5)?.clone(); + stack.erase(stack.end() - 6, Some(stack.end() - 4)); + stack.push_back(vch1); + stack.push_back(vch2); + } + + OP_2SWAP => { + // (x1 x2 x3 x4 -- x3 x4 x1 x2) + if stack.size() < 4 { + return set_error(ScriptError::InvalidStackOperation); + } + stack.swap(-4, -2)?; + stack.swap(-3, -1)?; + } + + OP_IFDUP => { + // (x - 0 | x x) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch = stack.top(-1)?; + if cast_to_bool(vch) { + stack.push_back(vch.to_vec()) + } + } + + OP_DEPTH => { + // -- stacksize + let bn = ScriptNum::try_from(stack.size()) + .map_err(|_| ScriptError::StackSize)?; + stack.push_back(bn.getvch()) + } + + OP_DROP => { + // (x -- ) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + stack.pop()?; + } + + OP_DUP => { + // (x -- x x) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + + let vch = stack.top(-1)?; + stack.push_back(vch.clone()); + } + + OP_NIP => { + // (x1 x2 -- x2) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + stack.erase(stack.end() - 2, None); + } + + OP_OVER => { + // (x1 x2 -- x1 x2 x1) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch = stack.top(-2)?; + stack.push_back(vch.clone()); + } + + OP_PICK | OP_ROLL => { + // (xn ... x2 x1 x0 n - xn ... x2 x1 x0 xn) + // (xn ... x2 x1 x0 n - ... x2 x1 x0 xn) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + 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() { + return set_error(ScriptError::InvalidStackOperation); + } + let vch: ValType = stack + .top( + -isize::try_from(n) + .map_err(|_| ScriptError::InvalidStackOperation)? + - 1, + )? + .clone(); + if op == OP_ROLL { + stack.erase(stack.end() - usize::from(n) - 1, None); + } + stack.push_back(vch) + } + + OP_ROT => { + // (x1 x2 x3 -- x2 x3 x1) + // x2 x1 x3 after first swap + // x2 x3 x1 after second swap + if stack.size() < 3 { + return set_error(ScriptError::InvalidStackOperation); + } + stack.swap(-3, -2)?; + stack.swap(-2, -1)?; + } + + OP_SWAP => { + // (x1 x2 -- x2 x1) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + stack.swap(-2, -1)?; + } + + OP_TUCK => { + // (x1 x2 -- x2 x1 x2) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch = stack.top(-1)?.clone(); + stack.insert(stack.end() - 2, vch) + } + + OP_SIZE => { + // (in -- in size) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + let bn = ScriptNum::try_from(stack.top(-1)?.len()) + .expect("stack element size fits in ScriptNum"); + stack.push_back(bn.getvch()) + } + + // + // Bitwise logic + // + OP_EQUAL | OP_EQUALVERIFY => { + // (x1 x2 - bool) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch1 = stack.top(-2)?.clone(); + let vch2 = stack.top(-1)?.clone(); + let equal = vch1 == vch2; + stack.pop()?; + stack.pop()?; + stack.push_back(if equal { VCH_TRUE.to_vec() } else { VCH_FALSE }); + if op == OP_EQUALVERIFY { + if equal { + stack.pop()?; + } else { + return set_error(ScriptError::EqualVerify); + } + } + } + + // + // Numeric + // + OP_1ADD | OP_1SUB | OP_NEGATE | OP_ABS | OP_NOT | OP_0NOTEQUAL => { + // (in -- out) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + let mut bn = ScriptNum::new(stack.top(-1)?, require_minimal, None)?; + match op { + OP_1ADD => bn = bn + ScriptNum::ONE, + OP_1SUB => bn = bn - ScriptNum::ONE, + OP_NEGATE => bn = -bn, + OP_ABS => { + if bn < ScriptNum::ZERO { + bn = -bn + } + } + OP_NOT => bn = ScriptNum::from(bn == ScriptNum::ZERO), + OP_0NOTEQUAL => bn = ScriptNum::from(bn != ScriptNum::ZERO), + _ => panic!("invalid opcode"), + } + stack.pop()?; + stack.push_back(bn.getvch()) + } + + OP_ADD + | OP_SUB + | OP_BOOLAND + | OP_BOOLOR + | OP_NUMEQUAL + | OP_NUMEQUALVERIFY + | OP_NUMNOTEQUAL + | OP_LESSTHAN + | OP_GREATERTHAN + | OP_LESSTHANOREQUAL + | OP_GREATERTHANOREQUAL + | OP_MIN + | OP_MAX => { + // (x1 x2 -- out) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + let bn1 = ScriptNum::new(stack.top(-2)?, require_minimal, None)?; + let bn2 = ScriptNum::new(stack.top(-1)?, require_minimal, None)?; + let bn = match op { + OP_ADD => bn1 + bn2, + + OP_SUB => bn1 - bn2, + + 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), + OP_LESSTHAN => ScriptNum::from(bn1 < bn2), + OP_GREATERTHAN => ScriptNum::from(bn1 > bn2), + OP_LESSTHANOREQUAL => ScriptNum::from(bn1 <= bn2), + OP_GREATERTHANOREQUAL => ScriptNum::from(bn1 >= bn2), + OP_MIN => { + if bn1 < bn2 { + bn1 + } else { + bn2 + } + } + OP_MAX => { + if bn1 > bn2 { + bn1 + } else { + bn2 + } + } + _ => panic!("invalid opcode"), + }; + stack.pop()?; + stack.pop()?; + stack.push_back(bn.getvch()); + + if op == OP_NUMEQUALVERIFY { + if cast_to_bool(stack.top(-1)?) { + stack.pop()?; + } else { + return set_error(ScriptError::NumEqualVerify); + } + } + } + + OP_WITHIN => { + // (x min max -- out) + if stack.size() < 3 { + return set_error(ScriptError::InvalidStackOperation); + } + let bn1 = ScriptNum::new(stack.top(-3)?, require_minimal, None)?; + let bn2 = ScriptNum::new(stack.top(-2)?, require_minimal, None)?; + let bn3 = ScriptNum::new(stack.top(-1)?, require_minimal, None)?; + let value = bn2 <= bn1 && bn1 < bn3; + stack.pop()?; + stack.pop()?; + stack.pop()?; + stack.push_back(if value { VCH_TRUE.to_vec() } else { VCH_FALSE }) + } + + // + // Crypto + // + OP_RIPEMD160 | OP_SHA1 | OP_SHA256 | OP_HASH160 | OP_HASH256 => { + // (in -- hash) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch = stack.top(-1)?; + let mut vch_hash = vec![]; + if op == OP_RIPEMD160 { + vch_hash = Ripemd160::digest(vch).to_vec(); + } else if op == OP_SHA1 { + let mut hasher = Sha1::new(); + hasher.update(vch); + vch_hash = hasher.finalize().to_vec(); + } else if op == OP_SHA256 { + vch_hash = Sha256::digest(vch).to_vec(); + } else if op == OP_HASH160 { + vch_hash = Ripemd160::digest(Sha256::digest(vch)).to_vec(); + } else if op == OP_HASH256 { + vch_hash = Sha256::digest(Sha256::digest(vch)).to_vec(); + } + stack.pop()?; + stack.push_back(vch_hash) + } + + OP_CHECKSIG | OP_CHECKSIGVERIFY => { + // (sig pubkey -- bool) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + + let vch_sig = stack.top(-2)?.clone(); + let vch_pub_key = stack.top(-1)?.clone(); + + check_signature_encoding(&vch_sig, flags)?; + check_pub_key_encoding(&vch_pub_key, flags)?; + let success = checker.check_sig(&vch_sig, &vch_pub_key, script); + + stack.pop()?; + stack.pop()?; + stack.push_back(if success { + VCH_TRUE.to_vec() + } else { + VCH_FALSE + }); + if op == OP_CHECKSIGVERIFY { + if success { + stack.pop()?; + } else { + return set_error(ScriptError::CheckSigVerify); + } + } + } + + OP_CHECKMULTISIG | OP_CHECKMULTISIGVERIFY => { + // ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) + + // NB: This is guaranteed u8-safe, because we are limited to 20 keys and + // 20 signatures, plus a couple other fields. u8 also gives us total + // conversions to the other types we deal with here (`isize` and `i64`). + let mut i: u8 = 1; + if stack.size() < i.into() { + return set_error(ScriptError::InvalidStackOperation); + }; + + let mut keys_count = u8::try_from(ScriptNum::new( + stack.top(-isize::from(i))?, + require_minimal, + None, + )?) + .map_err(|_| ScriptError::PubKeyCount)?; + if keys_count > 20 { + return set_error(ScriptError::PubKeyCount); + }; + assert!(*op_count <= 201); + *op_count += keys_count; + if *op_count > 201 { + return set_error(ScriptError::OpCount); + }; + i += 1; + let mut ikey = i; + i += keys_count; + if stack.size() < i.into() { + return set_error(ScriptError::InvalidStackOperation); + } + + let mut sigs_count = u8::try_from(ScriptNum::new( + stack.top(-isize::from(i))?, + require_minimal, + None, + )?) + .map_err(|_| ScriptError::SigCount)?; + if sigs_count > keys_count { + return set_error(ScriptError::SigCount); + }; + assert!(i <= 22); + i += 1; + let mut isig = i; + i += sigs_count; + if stack.size() < i.into() { + return set_error(ScriptError::InvalidStackOperation); + }; + + let mut success = true; + while success && sigs_count > 0 { + let vch_sig: &ValType = stack.top(-isize::from(isig))?; + let vch_pub_key: &ValType = stack.top(-isize::from(ikey))?; + + // Note how this makes the exact order of pubkey/signature evaluation + // distinguishable by CHECKMULTISIG NOT if the STRICTENC flag is set. + // See the script_(in)valid tests for details. + check_signature_encoding(vch_sig, flags)?; + check_pub_key_encoding(vch_pub_key, flags)?; + + // Check signature + let ok: bool = checker.check_sig(vch_sig, vch_pub_key, script); + + if ok { + isig += 1; + sigs_count -= 1; + } + ikey += 1; + keys_count -= 1; + + // If there are more signatures left than keys left, + // then too many signatures have failed. Exit early, + // without checking any further signatures. + if sigs_count > keys_count { + success = false; + }; + } + + // Clean up stack of actual arguments + for _ in 1..i { + stack.pop()?; + } + + // A bug causes CHECKMULTISIG to consume one extra argument + // whose contents were not checked in any way. + // + // Unfortunately this is a potential source of mutability, + // so optionally verify it is exactly equal to zero prior + // to removing it from the stack. + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + if flags.contains(VerificationFlags::NullDummy) + && !stack.top(-1)?.is_empty() + { + return set_error(ScriptError::SigNullDummy); + } + stack.pop()?; + + stack.push_back(if success { + VCH_TRUE.to_vec() + } else { + VCH_FALSE + }); + + if op == OP_CHECKMULTISIGVERIFY { + if success { + stack.pop()?; + } else { + return set_error(ScriptError::CheckMultisigVerify); + } + } + } + + _ => { + return set_error(ScriptError::BadOpcode); + } + } + } + } + } + + // Size limits + 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 { + pub flags: VerificationFlags, + pub checker: C, +} + +impl StepFn for DefaultStepEvaluator { + 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( + stack: Stack>, + script: &Script, + payload: &mut F::Payload, + eval_step: &F, +) -> Result>, 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); @@ -462,730 +1280,18 @@ pub fn eval_script( let mut pc = script.0; - // We keep track of how many operations have executed so far to prevent - // expensive-to-verify scripts - let mut op_count: u8 = 0; - let require_minimal = flags.contains(VerificationFlags::MinimalData); - - // 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 = Stack(vec![]); - - let mut altstack: Stack> = Stack(vec![]); + let mut state = State::initial(stack); // Main execution loop while !pc.is_empty() { - // Are we in an executing branch of the script? - let exec = vexec.iter().all(|value| *value); - - // - // 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); - } - - match opcode { - Opcode::PushValue(pv) => { - if exec { - match pv { - // - // 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 => { - // ( -- value) - let bn = ScriptNum::from(u8::from(pv)) - u8::from(OP_RESERVED).into(); - stack.push_back(bn.getvch()); - // The result of these opcodes should always be the minimal way to push the data - // they push, so no need for a CheckMinimalPush here. - } - _ => { - if pv <= OP_PUSHDATA4 { - if require_minimal && !check_minimal_push(vch_push_value, pv) { - return set_error(ScriptError::MinimalData); - } - stack.push_back(vch_push_value.to_vec()); - } else { - return set_error(ScriptError::BadOpcode); - } - } - } - } - } - Opcode::Operation(op) => { - // Note how OP_RESERVED does not count towards the opcode limit. - op_count += 1; - if op_count > 201 { - return set_error(ScriptError::OpCount); - } - - if op == OP_CAT - || op == OP_SUBSTR - || op == OP_LEFT - || op == OP_RIGHT - || op == OP_INVERT - || op == OP_AND - || op == OP_OR - || op == OP_XOR - || op == OP_2MUL - || op == OP_2DIV - || op == OP_MUL - || op == OP_DIV - || op == OP_MOD - || op == OP_LSHIFT - || op == OP_RSHIFT - || op == OP_CODESEPARATOR - { - return set_error(ScriptError::DisabledOpcode); // Disabled opcodes. - } - - if exec || (OP_IF <= op && op <= OP_ENDIF) { - match op { - // - // Control - // - OP_NOP => (), - - OP_CHECKLOCKTIMEVERIFY => { - // https://zips.z.cash/protocol/protocol.pdf#bips : - // - // The following BIPs apply starting from the Zcash genesis block, - // i.e. any activation rules or exceptions for particular blocks in - // the Bitcoin block chain are to be ignored: [BIP-16], [BIP-30], - // [BIP-65], [BIP-66]. - // - // So BIP 65, which defines CHECKLOCKTIMEVERIFY, is in practice always - // enabled, and this `if` branch is dead code. In zcashd see - // https://github.com/zcash/zcash/blob/a3435336b0c561799ac6805a27993eca3f9656df/src/main.cpp#L3151 - if !flags.contains(VerificationFlags::CHECKLOCKTIMEVERIFY) { - if flags.contains(VerificationFlags::DiscourageUpgradableNOPs) { - return set_error(ScriptError::DiscourageUpgradableNOPs); - } - } else { - if stack.size() < 1 { - return set_error(ScriptError::InvalidStackOperation); - } - - // Note that elsewhere numeric opcodes are limited to - // operands in the range -2**31+1 to 2**31-1, however it is - // legal for opcodes to produce results exceeding that - // range. This limitation is implemented by `ScriptNum`'s - // default 4-byte limit. - // - // If we kept to that limit we'd have a year 2038 problem, - // even though the `lock_time` field in transactions - // themselves is u32 which only becomes meaningless - // after the year 2106. - // - // Thus as a special case we tell `ScriptNum` to accept up - // to 5-byte bignums, which are good until 2**39-1, well - // beyond the 2**32-1 limit of the `lock_time` field itself. - let lock_time = - ScriptNum::new(stack.top(-1)?, require_minimal, Some(5))?; - - // 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 { - return set_error(ScriptError::NegativeLockTime); - } - - // Actually compare the specified lock time with the transaction. - if !checker.check_lock_time(&lock_time) { - return set_error(ScriptError::UnsatisfiedLockTime); - } - } - } - - OP_NOP1 | OP_NOP3 | OP_NOP4 | OP_NOP5 | OP_NOP6 | OP_NOP7 | OP_NOP8 - | OP_NOP9 | OP_NOP10 => { - // Do nothing, though if the caller wants to prevent people from using - // these NOPs (as part of a standard tx rule, for example) they can - // enable `DiscourageUpgradableNOPs` to turn these opcodes into errors. - if flags.contains(VerificationFlags::DiscourageUpgradableNOPs) { - return set_error(ScriptError::DiscourageUpgradableNOPs); - } - } - - OP_IF | OP_NOTIF => { - // if [statements] [else [statements]] endif - let mut value = false; - if exec { - if stack.size() < 1 { - return set_error(ScriptError::UnbalancedConditional); - } - let vch: &ValType = stack.top(-1)?; - value = cast_to_bool(vch); - if op == OP_NOTIF { - value = !value - }; - stack.pop()?; - } - vexec.push_back(value); - } - - OP_ELSE => { - if vexec.empty() { - return set_error(ScriptError::UnbalancedConditional); - } - vexec.back().map(|last| *last = !*last)?; - } - - OP_ENDIF => { - if vexec.empty() { - return set_error(ScriptError::UnbalancedConditional); - } - vexec.pop()?; - } - - OP_VERIFY => { - // (true -- ) or - // (false -- false) and return - if stack.size() < 1 { - return set_error(ScriptError::InvalidStackOperation); - } - let value = cast_to_bool(stack.top(-1)?); - if value { - stack.pop()?; - } else { - return set_error(ScriptError::Verify); - } - } - - OP_RETURN => return set_error(ScriptError::OpReturn), - - // - // Stack ops - // - OP_TOALTSTACK => { - if stack.empty() { - return set_error(ScriptError::InvalidStackOperation); - } - altstack.push_back(stack.top(-1)?.clone()); - stack.pop()?; - } - - OP_FROMALTSTACK => { - if altstack.empty() { - return set_error(ScriptError::InvalidAltstackOperation); - } - stack.push_back(altstack.top(-1)?.clone()); - altstack.pop()?; - } - - OP_2DROP => { - if stack.size() < 2 { - return set_error(ScriptError::InvalidStackOperation); - } - - stack.pop()?; - stack.pop()?; - } - - OP_2DUP => { - // (x1 x2 -- x1 x2 x1 x2) - if stack.size() < 2 { - return set_error(ScriptError::InvalidStackOperation); - } - let vch1 = stack.top(-2)?.clone(); - let vch2 = stack.top(-1)?.clone(); - stack.push_back(vch1); - stack.push_back(vch2); - } - - OP_3DUP => { - // (x1 x2 x3 -- x1 x2 x3 x1 x2 x3) - if stack.size() < 3 { - return set_error(ScriptError::InvalidStackOperation); - } - let vch1 = stack.top(-3)?.clone(); - let vch2 = stack.top(-2)?.clone(); - let vch3 = stack.top(-1)?.clone(); - stack.push_back(vch1); - stack.push_back(vch2); - stack.push_back(vch3); - } - - OP_2OVER => { - // (x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2) - if stack.size() < 4 { - return set_error(ScriptError::InvalidStackOperation); - } - let vch1 = stack.top(-4)?.clone(); - let vch2 = stack.top(-3)?.clone(); - stack.push_back(vch1); - stack.push_back(vch2); - } - - OP_2ROT => { - // (x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2) - if stack.size() < 6 { - return set_error(ScriptError::InvalidStackOperation); - } - let vch1 = stack.top(-6)?.clone(); - let vch2 = stack.top(-5)?.clone(); - stack.erase(stack.end() - 6, Some(stack.end() - 4)); - stack.push_back(vch1); - stack.push_back(vch2); - } - - OP_2SWAP => { - // (x1 x2 x3 x4 -- x3 x4 x1 x2) - if stack.size() < 4 { - return set_error(ScriptError::InvalidStackOperation); - } - stack.swap(-4, -2)?; - stack.swap(-3, -1)?; - } - - OP_IFDUP => { - // (x - 0 | x x) - if stack.size() < 1 { - return set_error(ScriptError::InvalidStackOperation); - } - let vch = stack.top(-1)?; - if cast_to_bool(vch) { - stack.push_back(vch.to_vec()) - } - } - - OP_DEPTH => { - // -- stacksize - let bn = ScriptNum::try_from(stack.size()) - .map_err(|_| ScriptError::StackSize)?; - stack.push_back(bn.getvch()) - } - - OP_DROP => { - // (x -- ) - if stack.size() < 1 { - return set_error(ScriptError::InvalidStackOperation); - } - stack.pop()?; - } - - OP_DUP => { - // (x -- x x) - if stack.size() < 1 { - return set_error(ScriptError::InvalidStackOperation); - } - - let vch = stack.top(-1)?; - stack.push_back(vch.clone()); - } - - OP_NIP => { - // (x1 x2 -- x2) - if stack.size() < 2 { - return set_error(ScriptError::InvalidStackOperation); - } - stack.erase(stack.end() - 2, None); - } - - OP_OVER => { - // (x1 x2 -- x1 x2 x1) - if stack.size() < 2 { - return set_error(ScriptError::InvalidStackOperation); - } - let vch = stack.top(-2)?; - stack.push_back(vch.clone()); - } - - OP_PICK | OP_ROLL => { - // (xn ... x2 x1 x0 n - xn ... x2 x1 x0 xn) - // (xn ... x2 x1 x0 n - ... x2 x1 x0 xn) - if stack.size() < 2 { - return set_error(ScriptError::InvalidStackOperation); - } - 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() { - return set_error(ScriptError::InvalidStackOperation); - } - let vch: ValType = stack - .top( - -isize::try_from(n) - .map_err(|_| ScriptError::InvalidStackOperation)? - - 1, - )? - .clone(); - if op == OP_ROLL { - stack.erase(stack.end() - usize::from(n) - 1, None); - } - stack.push_back(vch) - } - - OP_ROT => { - // (x1 x2 x3 -- x2 x3 x1) - // x2 x1 x3 after first swap - // x2 x3 x1 after second swap - if stack.size() < 3 { - return set_error(ScriptError::InvalidStackOperation); - } - stack.swap(-3, -2)?; - stack.swap(-2, -1)?; - } - - OP_SWAP => { - // (x1 x2 -- x2 x1) - if stack.size() < 2 { - return set_error(ScriptError::InvalidStackOperation); - } - stack.swap(-2, -1)?; - } - - OP_TUCK => { - // (x1 x2 -- x2 x1 x2) - if stack.size() < 2 { - return set_error(ScriptError::InvalidStackOperation); - } - let vch = stack.top(-1)?.clone(); - stack.insert(stack.end() - 2, vch) - } - - OP_SIZE => { - // (in -- in size) - if stack.size() < 1 { - return set_error(ScriptError::InvalidStackOperation); - } - let bn = ScriptNum::try_from(stack.top(-1)?.len()) - .expect("stack element size fits in ScriptNum"); - stack.push_back(bn.getvch()) - } - - // - // Bitwise logic - // - OP_EQUAL | OP_EQUALVERIFY => { - // (x1 x2 - bool) - if stack.size() < 2 { - return set_error(ScriptError::InvalidStackOperation); - } - let vch1 = stack.top(-2)?.clone(); - let vch2 = stack.top(-1)?.clone(); - let equal = vch1 == vch2; - stack.pop()?; - stack.pop()?; - stack.push_back(if equal { - vch_true.clone() - } else { - vch_false.clone() - }); - if op == OP_EQUALVERIFY { - if equal { - stack.pop()?; - } else { - return set_error(ScriptError::EqualVerify); - } - } - } - - // - // Numeric - // - OP_1ADD | OP_1SUB | OP_NEGATE | OP_ABS | OP_NOT | OP_0NOTEQUAL => { - // (in -- out) - if stack.size() < 1 { - return set_error(ScriptError::InvalidStackOperation); - } - 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_NEGATE => bn = -bn, - OP_ABS => { - if bn < bn_zero { - bn = -bn - } - } - OP_NOT => bn = ScriptNum::from(bn == bn_zero), - OP_0NOTEQUAL => bn = ScriptNum::from(bn != bn_zero), - _ => panic!("invalid opcode"), - } - stack.pop()?; - stack.push_back(bn.getvch()) - } - - OP_ADD - | OP_SUB - | OP_BOOLAND - | OP_BOOLOR - | OP_NUMEQUAL - | OP_NUMEQUALVERIFY - | OP_NUMNOTEQUAL - | OP_LESSTHAN - | OP_GREATERTHAN - | OP_LESSTHANOREQUAL - | OP_GREATERTHANOREQUAL - | OP_MIN - | OP_MAX => { - // (x1 x2 -- out) - if stack.size() < 2 { - return set_error(ScriptError::InvalidStackOperation); - } - let bn1 = ScriptNum::new(stack.top(-2)?, require_minimal, None)?; - let bn2 = ScriptNum::new(stack.top(-1)?, require_minimal, None)?; - let bn = match op { - OP_ADD => bn1 + bn2, - - 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_NUMEQUAL => ScriptNum::from(bn1 == bn2), - OP_NUMEQUALVERIFY => ScriptNum::from(bn1 == bn2), - OP_NUMNOTEQUAL => ScriptNum::from(bn1 != bn2), - OP_LESSTHAN => ScriptNum::from(bn1 < bn2), - OP_GREATERTHAN => ScriptNum::from(bn1 > bn2), - OP_LESSTHANOREQUAL => ScriptNum::from(bn1 <= bn2), - OP_GREATERTHANOREQUAL => ScriptNum::from(bn1 >= bn2), - OP_MIN => { - if bn1 < bn2 { - bn1 - } else { - bn2 - } - } - OP_MAX => { - if bn1 > bn2 { - bn1 - } else { - bn2 - } - } - _ => panic!("invalid opcode"), - }; - stack.pop()?; - stack.pop()?; - stack.push_back(bn.getvch()); - - if op == OP_NUMEQUALVERIFY { - if cast_to_bool(stack.top(-1)?) { - stack.pop()?; - } else { - return set_error(ScriptError::NumEqualVerify); - } - } - } - - OP_WITHIN => { - // (x min max -- out) - if stack.size() < 3 { - return set_error(ScriptError::InvalidStackOperation); - } - let bn1 = ScriptNum::new(stack.top(-3)?, require_minimal, None)?; - let bn2 = ScriptNum::new(stack.top(-2)?, require_minimal, None)?; - let bn3 = ScriptNum::new(stack.top(-1)?, require_minimal, None)?; - let value = bn2 <= bn1 && bn1 < bn3; - stack.pop()?; - stack.pop()?; - stack.pop()?; - stack.push_back(if value { - vch_true.clone() - } else { - vch_false.clone() - }) - } - - // - // Crypto - // - OP_RIPEMD160 | OP_SHA1 | OP_SHA256 | OP_HASH160 | OP_HASH256 => { - // (in -- hash) - if stack.size() < 1 { - return set_error(ScriptError::InvalidStackOperation); - } - let vch = stack.top(-1)?; - let mut vch_hash = vec![]; - if op == OP_RIPEMD160 { - vch_hash = Ripemd160::digest(vch).to_vec(); - } else if op == OP_SHA1 { - let mut hasher = Sha1::new(); - hasher.update(vch); - vch_hash = hasher.finalize().to_vec(); - } else if op == OP_SHA256 { - vch_hash = Sha256::digest(vch).to_vec(); - } else if op == OP_HASH160 { - vch_hash = Ripemd160::digest(Sha256::digest(vch)).to_vec(); - } else if op == OP_HASH256 { - vch_hash = Sha256::digest(Sha256::digest(vch)).to_vec(); - } - stack.pop()?; - stack.push_back(vch_hash) - } - - OP_CHECKSIG | OP_CHECKSIGVERIFY => { - // (sig pubkey -- bool) - if stack.size() < 2 { - return set_error(ScriptError::InvalidStackOperation); - } - - let vch_sig = stack.top(-2)?.clone(); - let vch_pub_key = stack.top(-1)?.clone(); - - check_signature_encoding(&vch_sig, flags)?; - check_pub_key_encoding(&vch_pub_key, flags)?; - let success = checker.check_sig(&vch_sig, &vch_pub_key, script); - - stack.pop()?; - stack.pop()?; - stack.push_back(if success { - vch_true.clone() - } else { - vch_false.clone() - }); - if op == OP_CHECKSIGVERIFY { - if success { - stack.pop()?; - } else { - return set_error(ScriptError::CheckSigVerify); - } - } - } - - OP_CHECKMULTISIG | OP_CHECKMULTISIGVERIFY => { - // ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) - - // NB: This is guaranteed u8-safe, because we are limited to 20 keys and - // 20 signatures, plus a couple other fields. u8 also gives us total - // conversions to the other types we deal with here (`isize` and `i64`). - let mut i: u8 = 1; - if stack.size() < i.into() { - return set_error(ScriptError::InvalidStackOperation); - }; - - let mut keys_count = u8::try_from(ScriptNum::new( - stack.top(-isize::from(i))?, - require_minimal, - None, - )?) - .map_err(|_| ScriptError::PubKeyCount)?; - if keys_count > 20 { - return set_error(ScriptError::PubKeyCount); - }; - assert!(op_count <= 201); - op_count += keys_count; - if op_count > 201 { - return set_error(ScriptError::OpCount); - }; - i += 1; - let mut ikey = i; - i += keys_count; - if stack.size() < i.into() { - return set_error(ScriptError::InvalidStackOperation); - } - - let mut sigs_count = u8::try_from(ScriptNum::new( - stack.top(-isize::from(i))?, - require_minimal, - None, - )?) - .map_err(|_| ScriptError::SigCount)?; - if sigs_count > keys_count { - return set_error(ScriptError::SigCount); - }; - assert!(i <= 22); - i += 1; - let mut isig = i; - i += sigs_count; - if stack.size() < i.into() { - return set_error(ScriptError::InvalidStackOperation); - }; - - let mut success = true; - while success && sigs_count > 0 { - let vch_sig: &ValType = stack.top(-isize::from(isig))?; - let vch_pub_key: &ValType = stack.top(-isize::from(ikey))?; - - // Note how this makes the exact order of pubkey/signature evaluation - // distinguishable by CHECKMULTISIG NOT if the STRICTENC flag is set. - // See the script_(in)valid tests for details. - check_signature_encoding(vch_sig, flags)?; - check_pub_key_encoding(vch_pub_key, flags)?; - - // Check signature - let ok: bool = checker.check_sig(vch_sig, vch_pub_key, script); - - if ok { - isig += 1; - sigs_count -= 1; - } - ikey += 1; - keys_count -= 1; - - // If there are more signatures left than keys left, - // then too many signatures have failed. Exit early, - // without checking any further signatures. - if sigs_count > keys_count { - success = false; - }; - } - - // Clean up stack of actual arguments - for _ in 1..i { - stack.pop()?; - } - - // A bug causes CHECKMULTISIG to consume one extra argument - // whose contents were not checked in any way. - // - // Unfortunately this is a potential source of mutability, - // so optionally verify it is exactly equal to zero prior - // to removing it from the stack. - if stack.size() < 1 { - return set_error(ScriptError::InvalidStackOperation); - } - if flags.contains(VerificationFlags::NullDummy) - && !stack.top(-1)?.is_empty() - { - return set_error(ScriptError::SigNullDummy); - } - stack.pop()?; - - stack.push_back(if success { - vch_true.clone() - } else { - vch_false.clone() - }); - - if op == OP_CHECKMULTISIGVERIFY { - if success { - stack.pop()?; - } else { - return set_error(ScriptError::CheckMultisigVerify); - } - } - } - - _ => { - return set_error(ScriptError::BadOpcode); - } - } - } - } - } - - // Size limits - if stack.size() + altstack.size() > 1000 { - return set_error(ScriptError::StackSize); - } + pc = eval_step.call(pc, script, &mut state, payload)?; } - if !vexec.empty() { + 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( 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() { - return set_error(ScriptError::EvalFalse); - } - if !cast_to_bool(stack.back()?) { - return set_error(ScriptError::EvalFalse); - } - } + 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(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); } }; diff --git a/src/lib.rs b/src/lib.rs index e03dbe302..23633e345 100644 --- a/src/lib.rs +++ b/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 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 { + fn legacy_sigop_count_script(&self, script: &[u8]) -> Result { 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( + first: &T, + second: &U, script: &[u8], ) -> (Result, Result) { ( - 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( /// 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( - 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( 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 { + 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>>, +> { + 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 { - let (cxx, rust) = - check_legacy_sigop_count_script::(script); +impl ZcashScript for ComparisonInterpreter { + fn legacy_sigop_count_script(&self, script: &[u8]) -> Result { + 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::( - 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(pub T); + + impl StepFn for BrokenStepEvaluator { + 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::( - &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::( - &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::( - &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::( - &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::( - &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); diff --git a/src/script.rs b/src/script.rs index ab24e301d..d3f4f4567 100644 --- a/src/script.rs +++ b/src/script.rs @@ -269,6 +269,9 @@ impl From 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( diff --git a/src/zcash_script.rs b/src/zcash_script.rs index 191b92a24..786681126 100644 --- a/src/zcash_script.rs +++ b/src/zcash_script.rs @@ -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; + fn legacy_sigop_count_script(&self, script: &[u8]) -> Result; } -/// A tag to indicate that the Rust implementation of zcash_script should be used. -pub enum RustInterpreter {} +pub fn stepwise_verify( + 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 { + /// 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, + /// 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, Result)>, + /// The final payload of the first stepper. + pub payload_l: T, + /// The final payload of the second stepper. + pub payload_r: U, +} + +impl StepResults { + 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, + pub eval_step_r: &'a dyn StepFn, +} + +impl<'a, T: Clone, U: Clone> StepFn for ComparisonStepEvaluator<'a, T, U> { + type Payload = StepResults; + fn call<'b>( + &self, + pc: &'b [u8], + script: &Script, + state: &mut State, + payload: &mut StepResults, + ) -> 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 +where + F: StepFn, +{ + initial_payload: F::Payload, + stepper: F, +} + +impl StepwiseInterpreter { + pub fn new(initial_payload: F::Payload, stepper: F) -> Self { + StepwiseInterpreter { + initial_payload, + stepper, + } + } +} + +pub fn rust_interpreter( + flags: VerificationFlags, + checker: C, +) -> StepwiseInterpreter> { + StepwiseInterpreter { + initial_payload: (), + stepper: DefaultStepEvaluator { flags, checker }, + } +} + +impl ZcashScript for StepwiseInterpreter { /// Returns the number of transparent signature operations in the /// transparent inputs and outputs of this transaction. - fn legacy_sigop_count_script(script: &[u8]) -> Result { + fn legacy_sigop_count_script(&self, script: &[u8]) -> Result { 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 = >::from_hex("a914c117756dcbe144a12a7c33a77cfa81aa5aeeb38187").unwrap(); + pub static ref SCRIPT_SIG: Vec = >::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); + } + } } }