diff --git a/Cargo.lock b/Cargo.lock index 4040876ab..d38fccffb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -419,4 +419,5 @@ dependencies = [ "cc", "hex", "lazy_static", + "log", ] diff --git a/Cargo.toml b/Cargo.toml index a4b3bf1eb..30c63dee3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ include = [ "/README.md", "build.rs", "src/*.rs", + "src/*/*.rs", "/depend/check_uint128_t.c", "/depend/zcash/src/amount.cpp", "/depend/zcash/src/amount.h", @@ -62,6 +63,7 @@ rust-interpreter = [] [dependencies] bitflags = "2.5" +log = "0.4" [build-dependencies] # The `bindgen` dependency should automatically upgrade to match the version used by zebra-state's `rocksdb` dependency in: diff --git a/src/external/mod.rs b/src/external/mod.rs new file mode 100644 index 000000000..8a5830ce9 --- /dev/null +++ b/src/external/mod.rs @@ -0,0 +1,4 @@ +//! Modules that we use from Zcash, but that are outside the script directory. + +pub mod pubkey; +pub mod uint256; diff --git a/src/external/pubkey.rs b/src/external/pubkey.rs new file mode 100644 index 000000000..e3ccbb564 --- /dev/null +++ b/src/external/pubkey.rs @@ -0,0 +1,23 @@ +use super::uint256::*; + +/// FIXME: `PUBLIC_KEY_SIZE` is meant to be an upper bound, it seems. Maybe parameterize the type +/// over the size. +pub struct PubKey<'a>(pub &'a [u8]); + +impl PubKey<'_> { + pub const PUBLIC_KEY_SIZE: usize = 65; + pub const COMPRESSED_PUBLIC_KEY_SIZE: usize = 33; + + /// Check syntactic correctness. + /// + /// Note that this is consensus critical as CheckSig() calls it! + pub fn is_valid(&self) -> bool { + self.0.len() > 0 + } + + /// Verify a DER signature (~72 bytes). + /// If this public key is not fully valid, the return value will be false. + pub fn verify(&self, hash: &UInt256, vch_sig: &[u8]) -> bool { + todo!() + } +} diff --git a/src/external/uint256.rs b/src/external/uint256.rs new file mode 100644 index 000000000..fb3c0e01d --- /dev/null +++ b/src/external/uint256.rs @@ -0,0 +1,2 @@ +/// FIXME: This probably needs to be an actually separate type somewhere. +pub type UInt256 = [u8; 32]; diff --git a/src/interpreter.rs b/src/interpreter.rs index ffa302fe6..63249dd66 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,3 +1,7 @@ +use std::slice::Iter; + +use super::external::pubkey::PubKey; +use super::external::uint256::UInt256; use super::script::*; use super::script_error::*; @@ -73,16 +77,141 @@ bitflags::bitflags! { } } +/// All signature hashes are 32 bits, since they are necessarily produced by SHA256. +pub const SIGHASH_SIZE: usize = 32; + +/// A function which is called to obtain the sighash. +/// - script_code: the scriptCode being validated. Note that this not always +/// matches script_sig, i.e. for P2SH. +/// - hash_type: the hash type being used. +/// +/// The `extern "C"` function that calls this doesn’t give much opportunity for rich failure +/// reporting, but returning `None` indicates _some_ failure to produce the desired hash. +/// +/// TODO: Can we get the “32” from somewhere rather than hardcoding it? +pub type SighashCalculator<'a> = &'a dyn Fn(&[u8], HashType) -> Option<[u8; SIGHASH_SIZE]>; + +#[derive(Clone, Debug, 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 { + pub fn top(&self, i: isize) -> Result<&T, ScriptError> { + self.0 + .get(self.0.len() - (-i) as usize) + .ok_or(ScriptError::InvalidStackOperation) + } + + pub fn pop(&mut self) -> Result { + self.0.pop().ok_or(ScriptError::InvalidStackOperation) + } + + pub fn push_back(&mut self, value: T) { + self.0.push(value) + } + + pub fn empty(&self) -> bool { + self.0.is_empty() + } + + pub fn size(&self) -> usize { + self.0.len() + } + + pub fn iter(&self) -> Iter<'_, T> { + self.0.iter() + } + + pub fn back(&mut self) -> Option<&mut T> { + self.0.last_mut() + } +} + +pub trait BaseSignatureChecker { + fn check_sig(&self, script_sig: &Vec, vch_pub_key: &Vec, script_code: &Script) -> bool { + false + } + + fn check_lock_time(&self, n_lock_time: i64) -> bool { + false + } +} + type ValType = Vec; -fn check_minimal_push(data: &ValType, opcode: Opcode) -> bool { +pub struct CallbackTransactionSignatureChecker<'a> { + pub sighash: SighashCalculator<'a>, + pub n_lock_time: i64, + pub is_final: bool, +} + +impl CallbackTransactionSignatureChecker<'_> { + pub fn verify_signature(vch_sig: &Vec, pubkey: &PubKey, sighash: &UInt256) -> bool { + pubkey.verify(sighash, vch_sig) + } +} + +impl BaseSignatureChecker for CallbackTransactionSignatureChecker<'_> { + fn check_sig(&self, vch_sig_in: &Vec, vch_pub_key: &Vec, script_code: &Script) -> bool { + todo!() + } + + fn check_lock_time(&self, n_lock_time: i64) -> bool { + // There are two times of nLockTime: lock-by-blockheight + // and lock-by-blocktime, distinguished by whether + // nLockTime < LOCKTIME_THRESHOLD. + // + // 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.n_lock_time < LOCKTIME_THRESHOLD && n_lock_time < LOCKTIME_THRESHOLD) + || (self.n_lock_time >= LOCKTIME_THRESHOLD && n_lock_time >= LOCKTIME_THRESHOLD)) + { + false + // Now that we know we're comparing apples-to-apples, the + // comparison is a simple numeric one. + } else if n_lock_time > self.n_lock_time { + false + // Finally the nLockTime feature can be disabled and thus + // CHECKLOCKTIMEVERIFY bypassed if every txin has been + // finalized by setting nSequence to maxint. The + // transaction would be allowed into the blockchain, making + // the opcode ineffective. + // + // Testing if this vin is not final is sufficient to + // prevent this condition. Alternatively we could test all + // inputs, but testing just this input minimizes the data + // required to prove correct CHECKLOCKTIMEVERIFY execution. + } else if self.is_final { + false + } else { + true + } + } + + // FIXME: Replace the above logic with this, which makes more sense (but preserve a lot of the + // comments). + // !self.is_final + // && (self.n_lock_time < LOCKTIME_THRESHOLD && n_lock_time < LOCKTIME_THRESHOLD) || + // (self.n_lock_time >= LOCKTIME_THRESHOLD && n_lock_time >= LOCKTIME_THRESHOLD)) + // && n_lock_time <= self.n_lock_time +} + +fn cast_to_bool(vch: &ValType) -> bool { + // FIXME: Doesn’t handle negative zero + vch.iter().fold(false, |acc, vchi| acc || *vchi != 0) +} + +fn check_minimal_push(data: &[u8], raw_opcode: u8) -> bool { todo!() } pub fn eval_script( - stack: &mut Vec>, + stack: &mut Stack>, script: &Script, - flags: &VerificationFlags, + flags: VerificationFlags, + checker: &dyn BaseSignatureChecker, ) -> Result<(), ScriptError> { // There's a limit on how large scripts can be. if script.0.len() > MAX_SCRIPT_SIZE { @@ -100,9 +229,9 @@ pub fn eval_script( // 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 exec: Vec = vec![]; + let mut exec: Stack = Stack(vec![]); - let mut altstack: Vec> = vec![]; + let mut altstack: Stack> = Stack(vec![]); // Main execution loop while !script.0.is_empty() { @@ -130,7 +259,7 @@ pub fn eval_script( return Err(ScriptError::MinimalData); } - stack.push(vch_push_value.clone()); + stack.push_back(vch_push_value.clone()); } } Operation::Constant(value) => { @@ -207,29 +336,29 @@ pub fn eval_script( Opcode::OP_IF | Opcode::OP_NOTIF => { let mut value = false; if executing { - if stack.is_empty() { + if stack.empty() { return Err(ScriptError::UnbalancedConditional); } todo!() } - exec.push(value); + exec.push_back(value); } Opcode::OP_ELSE => { - if exec.is_empty() { + if exec.empty() { return Err(ScriptError::UnbalancedConditional); } - exec.last_mut().map(|last| *last = !*last); + exec.back().map(|last| *last = !*last); } Opcode::OP_ENDIF => { - if exec.is_empty() { + if exec.empty() { return Err(ScriptError::UnbalancedConditional); } exec.pop(); } Opcode::OP_VERIFY => { - if stack.is_empty() { + if stack.empty() { return Err(ScriptError::InvalidStackOperation); } @@ -239,21 +368,21 @@ pub fn eval_script( } Opcode::OP_RETURN => return Err(ScriptError::OpReturn), Opcode::OP_TOALTSTACK => { - if stack.is_empty() { + if stack.empty() { return Err(ScriptError::InvalidStackOperation); } - altstack.push(stack.pop().unwrap()); + altstack.push_back(stack.pop().unwrap()); } Opcode::OP_FROMALTSTACK => { - if altstack.is_empty() { + if altstack.empty() { return Err(ScriptError::InvalidStackOperation); } - stack.push(altstack.pop().unwrap()); + stack.push_back(altstack.pop().unwrap()); } Opcode::OP_2DROP => { - if stack.len() < 2 { + if stack.size() < 2 { return Err(ScriptError::InvalidStackOperation); } @@ -261,34 +390,34 @@ pub fn eval_script( stack.pop(); } Opcode::OP_2DUP => { - if stack.len() < 2 { + if stack.size() < 2 { return Err(ScriptError::InvalidStackOperation); } let b = stack.pop().unwrap(); let a = stack.pop().unwrap(); - stack.push(a.clone()); - stack.push(b.clone()); - stack.push(a); - stack.push(b); + stack.push_back(a.clone()); + stack.push_back(b.clone()); + stack.push_back(a); + stack.push_back(b); } Opcode::OP_3DUP => { - if stack.len() < 3 { + if stack.size() < 3 { return Err(ScriptError::InvalidStackOperation); } let c = stack.pop().unwrap(); let b = stack.pop().unwrap(); let a = stack.pop().unwrap(); - stack.push(a.clone()); - stack.push(b.clone()); - stack.push(c.clone()); - stack.push(a); - stack.push(b); - stack.push(c); + stack.push_back(a.clone()); + stack.push_back(b.clone()); + stack.push_back(c.clone()); + stack.push_back(a); + stack.push_back(b); + stack.push_back(c); } Opcode::OP_2OVER => { - if stack.len() < 4 { + if stack.size() < 4 { return Err(ScriptError::InvalidStackOperation); } @@ -296,15 +425,15 @@ pub fn eval_script( let c = stack.pop().unwrap(); let b = stack.pop().unwrap(); let a = stack.pop().unwrap(); - stack.push(a.clone()); - stack.push(b.clone()); - stack.push(c); - stack.push(d); - stack.push(a); - stack.push(b); + stack.push_back(a.clone()); + stack.push_back(b.clone()); + stack.push_back(c); + stack.push_back(d); + stack.push_back(a); + stack.push_back(b); } Opcode::OP_2ROT => { - if stack.len() < 6 { + if stack.size() < 6 { return Err(ScriptError::InvalidStackOperation); } @@ -314,15 +443,15 @@ pub fn eval_script( let c = stack.pop().unwrap(); let b = stack.pop().unwrap(); let a = stack.pop().unwrap(); - stack.push(c); - stack.push(d); - stack.push(e); - stack.push(f); - stack.push(a); - stack.push(b); + stack.push_back(c); + stack.push_back(d); + stack.push_back(e); + stack.push_back(f); + stack.push_back(a); + stack.push_back(b); } Opcode::OP_2SWAP => { - if stack.len() < 4 { + if stack.size() < 4 { return Err(ScriptError::InvalidStackOperation); } @@ -330,13 +459,13 @@ pub fn eval_script( let c = stack.pop().unwrap(); let b = stack.pop().unwrap(); let a = stack.pop().unwrap(); - stack.push(c); - stack.push(d); - stack.push(a); - stack.push(b); + stack.push_back(c); + stack.push_back(d); + stack.push_back(a); + stack.push_back(b); } Opcode::OP_IFDUP => { - if stack.is_empty() { + if stack.empty() { return Err(ScriptError::InvalidStackOperation); } @@ -346,40 +475,40 @@ pub fn eval_script( todo!() } Opcode::OP_DROP => { - if stack.is_empty() { + if stack.empty() { return Err(ScriptError::InvalidStackOperation); } stack.pop(); } Opcode::OP_DUP => { - if stack.is_empty() { + if stack.empty() { return Err(ScriptError::InvalidStackOperation); } let a = stack.pop().unwrap(); - stack.push(a.clone()); - stack.push(a); + stack.push_back(a.clone()); + stack.push_back(a); } Opcode::OP_NIP => { - if stack.len() < 2 { + if stack.size() < 2 { return Err(ScriptError::InvalidStackOperation); } let b = stack.pop().unwrap(); stack.pop(); - stack.push(b); + stack.push_back(b); } Opcode::OP_OVER => { - if stack.len() < 2 { + if stack.size() < 2 { return Err(ScriptError::InvalidStackOperation); } let b = stack.pop().unwrap(); let a = stack.pop().unwrap(); - stack.push(a.clone()); - stack.push(b); - stack.push(a); + stack.push_back(a.clone()); + stack.push_back(b); + stack.push_back(a); } Opcode::OP_PICK | Opcode::OP_ROLL => { todo!() @@ -388,31 +517,31 @@ pub fn eval_script( todo!() } Opcode::OP_SWAP => { - if stack.len() < 2 { + if stack.size() < 2 { return Err(ScriptError::InvalidStackOperation); } let b = stack.pop().unwrap(); let a = stack.pop().unwrap(); - stack.push(b); - stack.push(a); + stack.push_back(b); + stack.push_back(a); } Opcode::OP_TUCK => { - if stack.len() < 2 { + if stack.size() < 2 { return Err(ScriptError::InvalidStackOperation); } let b = stack.pop().unwrap(); let a = stack.pop().unwrap(); - stack.push(b.clone()); - stack.push(a); - stack.push(b); + stack.push_back(b.clone()); + stack.push_back(a); + stack.push_back(b); } Opcode::OP_SIZE => { todo!() } Opcode::OP_EQUAL | Opcode::OP_EQUALVERIFY => { - if stack.len() < 2 { + if stack.size() < 2 { return Err(ScriptError::InvalidStackOperation); } @@ -449,7 +578,28 @@ pub fn eval_script( | Opcode::OP_SHA256 | Opcode::OP_HASH160 | Opcode::OP_HASH256 => { - todo!() + if let Ok(vch) = stack.top(-1) { + let vch_hash: ValType = vec![if opcode == Opcode::OP_RIPEMD160 + || opcode == Opcode::OP_SHA1 + || opcode == Opcode::OP_HASH160 + { + 20 + } else { + 32 + }]; + match opcode { + Opcode::OP_RIPEMD160 => todo!(), + Opcode::OP_SHA1 => todo!(), + Opcode::OP_SHA256 => todo!(), + Opcode::OP_HASH160 => todo!(), + Opcode::OP_HASH256 => todo!(), + _ => (), + }; + stack.pop(); + stack.push_back(vch_hash); + } else { + return Err(ScriptError::InvalidStackOperation); + } } Opcode::OP_CHECKSIG | Opcode::OP_CHECKSIGVERIFY => { todo!() @@ -465,14 +615,75 @@ pub fn eval_script( // There's a limit to how many items can be added to the stack and // alt stack. This limit is enforced upon finishing the execution of // an opcode. - if stack.len() + altstack.len() > 1000 { + if stack.size() + altstack.size() > 1000 { return Err(ScriptError::StackSize); } } - if exec.is_empty() { + if exec.empty() { Ok(()) } else { Err(ScriptError::UnbalancedConditional) } } + +pub fn verify_script( + script_sig: &Script, + script_pub_key: &Script, + flags: VerificationFlags, + checker: &dyn BaseSignatureChecker, +) -> Result<(), ScriptError> { + if flags.contains(VerificationFlags::SigPushOnly) && !script_sig.is_push_only() { + Err(ScriptError::SigPushOnly) + } else { + let mut stack = Stack(Vec::new()); + let mut stack_copy = Stack(Vec::new()); + eval_script(&mut stack, script_sig, flags, checker) + .and({ + if flags.contains(VerificationFlags::P2SH) { + stack_copy = stack.clone() + }; + eval_script(&mut stack, script_pub_key, flags, checker) + }) + .and(if stack.back().map_or(false, |b| cast_to_bool(&b)) { + Err(ScriptError::EvalFalse) + } else { + Ok(()) + }) + .and( + // Additional validation for spend-to-script-hash transactions: + 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() { + Err(ScriptError::SigPushOnly) + } else { + // Restore stack. + stack = stack_copy; + + if let Ok(pub_key_serialized) = stack.pop() { + let pub_key_2 = Script(&pub_key_serialized[..]); + + eval_script(&mut stack, &pub_key_2, flags, checker).and( + if stack.back().map_or(false, |b| cast_to_bool(&b)) { + Err(ScriptError::EvalFalse) + } else { + Ok(()) + }, + ) + } else { + // stack cannot be empty here, because if it was the + // P2SH HASH <> EQUAL scriptPubKey would be evaluated with + // an empty stack and the EvalScript above would return false. + // + // NB: This is different behavior from the C++ implementation, which + // panics here. + Err(ScriptError::StackSize) + } + } + } else { + Ok(()) + }, + ) + } +} diff --git a/src/lib.rs b/src/lib.rs index 71bbb32a7..db04001ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,17 +5,20 @@ #![allow(unsafe_code)] mod cxx; -pub use cxx::*; - +mod external; mod interpreter; -pub use interpreter::{HashType, VerificationFlags}; mod script; mod script_error; mod zcash_script; -pub use zcash_script::*; use std::os::raw::{c_int, c_uint, c_void}; +use log::warn; + +pub use cxx::*; +pub use interpreter::{HashType, SighashCalculator, VerificationFlags}; +pub use zcash_script::*; + /// A tag to indicate that the C++ implementation of zcash_script should be used. pub enum Cxx {} @@ -111,6 +114,93 @@ impl ZcashScript for Cxx { } } +/// Runs both the C++ and Rust implementations `ZcashScript::legacy_sigop_count_script` and returns +/// 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( + script: &[u8], +) -> (Result, Result) { + ( + T::legacy_sigop_count_script(script), + U::legacy_sigop_count_script(script), + ) +} + +/// Runs both the C++ and Rust implementations of `ZcashScript::verify_callback` and returns 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_verify_callback( + sighash: SighashCalculator, + lock_time: i64, + is_final: bool, + 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, + ), + ) +} + +/// This implementation is functionally equivalent to `Cxx`, but it also runs `Rust` and logs a +/// warning if they disagree. +impl ZcashScript for (T, U) { + fn legacy_sigop_count_script(script: &[u8]) -> Result { + let (cxx, rust) = check_legacy_sigop_count_script::(script); + if rust != cxx { + warn!( + "The Rust Zcash Script interpreter had a different sigop count ({:?}) from the C++ one ({:?}).", + rust, + cxx) + }; + cxx + } + + fn verify_callback( + sighash: SighashCalculator, + lock_time: i64, + is_final: bool, + 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, + ); + if rust != cxx { + // probably want to distinguish between + // - C++ succeeding when Rust fails (bad), + // - Rust succeeding when C++ fals (worse), and + // - differing error codes (maybe not bad). + warn!( + "The Rust Zcash Script interpreter had a different result ({:?}) from the C++ one ({:?}).", + rust, + cxx) + }; + cxx + } +} + #[cfg(test)] mod tests { pub use super::*; @@ -149,7 +239,7 @@ mod tests { let script_sig = &SCRIPT_SIG; let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY; - let ret = Cxx::verify_callback( + let ret = check_verify_callback::( &sighash, n_lock_time, is_final, @@ -158,7 +248,8 @@ mod tests { flags, ); - assert!(ret.is_ok()); + assert_eq!(ret.0, ret.1); + assert!(ret.0.is_ok()); } #[test] @@ -169,7 +260,7 @@ mod tests { let script_sig = &SCRIPT_SIG; let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY; - let ret = Cxx::verify_callback( + let ret = check_verify_callback::( &invalid_sighash, n_lock_time, is_final, @@ -178,7 +269,8 @@ mod tests { flags, ); - assert_eq!(ret, Err(Error::Ok)); + assert_eq!(ret.0, ret.1); + assert_eq!(ret.0, Err(Error::Ok)); } #[test] @@ -189,7 +281,7 @@ mod tests { let script_sig = &SCRIPT_SIG; let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY; - let ret = Cxx::verify_callback( + let ret = check_verify_callback::( &missing_sighash, n_lock_time, is_final, @@ -198,6 +290,7 @@ mod tests { flags, ); - assert_eq!(ret, Err(Error::Ok)); + assert_eq!(ret.0, ret.1); + assert_eq!(ret.0, Err(Error::Ok)); } } diff --git a/src/script.rs b/src/script.rs index cd5f163cd..6ff8c3c3c 100644 --- a/src/script.rs +++ b/src/script.rs @@ -10,7 +10,7 @@ pub const MAX_SCRIPT_SIZE: usize = 10000; // Threshold for nLockTime: below this value it is interpreted as block number, // otherwise as UNIX timestamp. -pub const LOCKTIME_THRESHOLD: usize = 500000000; // Tue Nov 5 00:53:20 1985 UTC +pub const LOCKTIME_THRESHOLD: i64 = 500000000; // Tue Nov 5 00:53:20 1985 UTC // Opcodes for pushing to the stack const OP_0: u8 = 0x00; @@ -257,6 +257,15 @@ pub fn parse_opcode( pub struct Script<'a>(pub &'a [u8]); impl<'a> Script<'a> { + /// Pre-version-0.6, Bitcoin always counted CHECKMULTISIGs + /// as 20 sigops. With pay-to-script-hash, that changed: + /// CHECKMULTISIGs serialized in script_sigs are + /// counted more accurately, assuming they are of the form + /// ... OP_N CHECKMULTISIG ... + pub fn get_sig_op_count(&self, accurate: bool) -> u32 { + todo!() + } + /// Returns true iff this script is P2PKH. pub fn is_pay_to_public_key_hash(&self) -> bool { (self.0.len() == 25) @@ -274,4 +283,9 @@ impl<'a> Script<'a> { && (self.0[1] == 0x14) && (self.0[22] == Opcode::OP_EQUAL as u8) } + + /// Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). + pub fn is_push_only(&self) -> bool { + todo!() + } } diff --git a/src/script_error.rs b/src/script_error.rs index c76c3c06b..8f3e85ed6 100644 --- a/src/script_error.rs +++ b/src/script_error.rs @@ -1,19 +1,48 @@ #[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[repr(i32)] pub enum ScriptError { + Ok = 0, + UnknownError, + EvalFalse, OpReturn, + // Max sizes ScriptSize, PushSize, OpCount, StackSize, + SigCount, + PubKeyCount, + // Failed verify operations + VERIFY, + EQUALVERIFY, + CHECKMULTISIGVERIFY, + CHECKSIGVERIFY, + NUMEQUALVERIFY, + + // Logical/Format/Canonical errors BadOpcode, DisabledOpcode, InvalidStackOperation, + InvalidAltstackOperation, UnbalancedConditional, + // OP_CHECKLOCKTIMEVERIFY + NegativeLockTime, + UnsatisfiedLockTime, + + // BIP62 + SigHashtype, + SigDER, MinimalData, + SigPushOnly, + SigHighS, + SigNullDummy, + PubKeyType, + CleanStack, + // softfork safeness DiscourageUpgradableNOPs, ReadError { diff --git a/src/zcash_script.rs b/src/zcash_script.rs index fa7d330ac..a633e74ac 100644 --- a/src/zcash_script.rs +++ b/src/zcash_script.rs @@ -1,6 +1,7 @@ use std::num::TryFromIntError; use super::interpreter::*; +use super::script::*; /// This maps to `zcash_script_error_t`, but most of those cases aren’t used any more. This only /// replicates the still-used cases, and then an `Unknown` bucket for anything else that might @@ -21,20 +22,6 @@ pub enum Error { Unknown(i64), } -/// All signature hashes are 32 bits, since they are necessarily produced by SHA256. -pub const SIGHASH_SIZE: usize = 32; - -/// A function which is called to obtain the sighash. -/// - script_code: the scriptCode being validated. Note that this not always -/// matches script_sig, i.e. for P2SH. -/// - hash_type: the hash type being used. -/// -/// The `extern "C"` function that calls this doesn’t give much opportunity for rich failure -/// reporting, but returning `None` indicates _some_ failure to produce the desired hash. -/// -/// TODO: Can we get the “32” from somewhere rather than hardcoding it? -pub type SighashCalculator<'a> = &'a dyn Fn(&[u8], HashType) -> Option<[u8; SIGHASH_SIZE]>; - /// The external API of zcash_script. This is defined to make it possible to compare the C++ and /// Rust implementations. pub trait ZcashScript { @@ -66,3 +53,36 @@ pub trait ZcashScript { /// output script pointed to by script. fn legacy_sigop_count_script(script: &[u8]) -> Result; } + +/// A tag to indicate that the Rust implementation of zcash_script should be used. +pub enum Rust {} + +impl ZcashScript for Rust { + /// Returns the number of transparent signature operations in the + /// transparent inputs and outputs of this transaction. + fn legacy_sigop_count_script(script: &[u8]) -> Result { + let cscript = Script(script); + Ok(cscript.get_sig_op_count(false)) + } + + fn verify_callback( + sighash: SighashCalculator, + n_lock_time: i64, + is_final: bool, + script_pub_key: &[u8], + script_sig: &[u8], + flags: VerificationFlags, + ) -> Result<(), Error> { + verify_script( + &Script(script_sig), + &Script(script_pub_key), + flags, + &CallbackTransactionSignatureChecker { + sighash, + n_lock_time, + is_final, + }, + ) + .map_err(|_| Error::Ok) + } +}