From 5b6bc86413ce206db13c2e2e5bd67ff59b00d879 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 24 May 2024 13:25:23 -0600 Subject: [PATCH 01/12] [DRAFT] --- Cargo.toml | 1 + src/lib.rs | 3 + src/rust_interpreter/mod.rs | 462 ++++++++++++++++++++++++++++++++ src/rust_interpreter/opcodes.rs | 244 +++++++++++++++++ 4 files changed, 710 insertions(+) create mode 100644 src/rust_interpreter/mod.rs create mode 100644 src/rust_interpreter/opcodes.rs diff --git a/Cargo.toml b/Cargo.toml index 9e1a131f5..5c2008833 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ path = "src/lib.rs" [features] external-secp = [] +rust-interpreter = [] [dependencies] # All these dependencies must match the versions in: diff --git a/src/lib.rs b/src/lib.rs index cd39a5d94..6d6cae6d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,6 +70,9 @@ mod orchard_ffi; mod streams_ffi; mod transaction_ffi; +#[cfg(feature = "rust-interpreter")] +pub mod rust_interpreter; + #[cfg(test)] mod tests { pub use super::zcash_script_error_t; diff --git a/src/rust_interpreter/mod.rs b/src/rust_interpreter/mod.rs new file mode 100644 index 000000000..6b1ea011b --- /dev/null +++ b/src/rust_interpreter/mod.rs @@ -0,0 +1,462 @@ +pub mod opcodes; +use opcodes::*; + +/// Maximum allowed size of data (in bytes) that can be pushed to the stack. +pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520; + +/// Maximum allowed script length in bytes. +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 + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum ScriptError { + ReadError { + expected_bytes: usize, + available_bytes: usize, + }, + InvalidOpcode, + PushSize, + OpCount, + DisabledOpcode, + ScriptSize, + MinimalData, + StackSize, + UnbalancedConditional, + UpgradableNops, + InvalidStackOperation, + OpReturn, +} + +pub struct ExecutionOptions { + pub require_minimal_pushes: bool, + pub enable_checklocktimeverify: bool, + pub discourage_upgradable_nops: bool, +} + +#[derive(Clone)] +pub struct Script<'a>(pub &'a [u8]); + +impl<'a> Script<'a> { + /// Returns true iff this script is P2PKH. + pub fn is_p2pkh(&self) -> bool { + (self.0.len() == 25) + && (self.0[0] == Opcode::OP_DUP as u8) + && (self.0[1] == Opcode::OP_HASH160 as u8) + && (self.0[2] == 0x14) + && (self.0[23] == Opcode::OP_EQUALVERIFY as u8) + && (self.0[24] == Opcode::OP_CHECKSIG as u8) + } + + /// Returns true iff this script is P2SH. + pub fn is_p2sh(&self) -> bool { + (self.0.len() == 23) + && (self.0[0] == Opcode::OP_HASH160 as u8) + && (self.0[1] == 0x14) + && (self.0[22] == Opcode::OP_EQUAL as u8) + } + + pub fn evaluate( + &self, + stack: &mut Vec>, + options: &ExecutionOptions, + ) -> Result<(), ScriptError> { + // There's a limit on how large scripts can be. + if self.0.len() > MAX_SCRIPT_SIZE { + return Err(ScriptError::ScriptSize); + } + + let mut script = (*self).clone(); + let mut push_data = vec![]; + + // We keep track of how many operations have executed so far to prevent + // expensive-to-verify scripts + let mut op_count = 0; + + // 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 exec: Vec = vec![]; + + let mut alt_stack: Vec> = vec![]; + + // Main execution loop + while !script.0.is_empty() { + // Are we in an executing branch of the script? + let executing = exec.iter().all(|value| *value); + + // Consume an opcode + let operation = parse_opcode(&mut script.0, Some(&mut push_data))?; + + match operation { + Operation::PushBytes(raw_opcode) => { + // There's a limit to the size of the values we'll put on + // the stack. + if push_data.len() > MAX_SCRIPT_ELEMENT_SIZE { + return Err(ScriptError::PushSize); + } + + if executing { + // Data is being pushed to the stack here; we may need to check + // that the minimal script size was used to do so if our caller + // requires it. + if options.require_minimal_pushes + && !check_minimal_push(&push_data, raw_opcode) + { + return Err(ScriptError::MinimalData); + } + + stack.push(push_data.clone()); + } + } + Operation::Constant(value) => { + todo!() + } + + // Invalid and disabled opcodes do technically contribute to + // op_count, but they always result in a failed script execution + // anyway. + Operation::Invalid => return Err(ScriptError::InvalidOpcode), + Operation::Disabled => return Err(ScriptError::DisabledOpcode), + + Operation::Opcode(opcode) => { + // There's a limit on how many operations can execute in a + // script. We consider opcodes beyond OP_16 to be "actual" + // opcodes as ones below that just involve data pushes. All + // opcodes defined by the Opcode enum qualify except for + // OP_RESERVED, which is not beyond OP_16. + // + // Note: operations even if they are not executed but are + // still present in the script count toward this count. + if opcode != Opcode::OP_RESERVED { + op_count += 1; + if op_count > 201 { + return Err(ScriptError::OpCount); + } + } + + if executing || opcode.is_control_flow_opcode() { + match opcode { + Opcode::OP_RESERVED + | Opcode::OP_VER + | Opcode::OP_RESERVED1 + | Opcode::OP_RESERVED2 => { + // These are considered "invalid" opcodes but + // only inside of *executing* OP_IF branches of + // the script. + return Err(ScriptError::InvalidOpcode); + } + Opcode::OP_NOP => { + // Do nothing. + } + Opcode::OP_NOP1 + | Opcode::OP_NOP3 + | Opcode::OP_NOP4 + | Opcode::OP_NOP5 + | Opcode::OP_NOP6 + | Opcode::OP_NOP7 + | Opcode::OP_NOP8 + | Opcode::OP_NOP9 + | Opcode::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 `discourage_upgradable_nops` to turn + // these opcodes into errors. + if options.discourage_upgradable_nops { + return Err(ScriptError::UpgradableNops); + } + } + Opcode::OP_CHECKLOCKTIMEVERIFY => { + // This was originally OP_NOP2 but has been repurposed + // for OP_CHECKLOCKTIMEVERIFY. So, we should act based + // on whether or not CLTV has been activated in a soft + // fork. + if !options.enable_checklocktimeverify { + if options.discourage_upgradable_nops { + return Err(ScriptError::UpgradableNops); + } + } else { + todo!() + } + } + Opcode::OP_IF | Opcode::OP_NOTIF => { + let mut value = false; + if executing { + if stack.is_empty() { + return Err(ScriptError::UnbalancedConditional); + } + todo!() + } + exec.push(value); + } + Opcode::OP_ELSE => { + if exec.is_empty() { + return Err(ScriptError::UnbalancedConditional); + } + + exec.last_mut().map(|last| *last = !*last); + } + Opcode::OP_ENDIF => { + if exec.is_empty() { + return Err(ScriptError::UnbalancedConditional); + } + + exec.pop(); + } + Opcode::OP_VERIFY => { + if stack.is_empty() { + return Err(ScriptError::InvalidStackOperation); + } + + let value = stack.pop().unwrap(); + + todo!() + } + Opcode::OP_RETURN => return Err(ScriptError::OpReturn), + Opcode::OP_TOALTSTACK => { + if stack.is_empty() { + return Err(ScriptError::InvalidStackOperation); + } + + alt_stack.push(stack.pop().unwrap()); + } + Opcode::OP_FROMALTSTACK => { + if alt_stack.is_empty() { + return Err(ScriptError::InvalidStackOperation); + } + + stack.push(alt_stack.pop().unwrap()); + } + Opcode::OP_2DROP => { + if stack.len() < 2 { + return Err(ScriptError::InvalidStackOperation); + } + + stack.pop(); + stack.pop(); + } + Opcode::OP_2DUP => { + if stack.len() < 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); + } + Opcode::OP_3DUP => { + if stack.len() < 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); + } + Opcode::OP_2OVER => { + if stack.len() < 4 { + return Err(ScriptError::InvalidStackOperation); + } + + let d = stack.pop().unwrap(); + 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); + } + Opcode::OP_2ROT => { + if stack.len() < 6 { + return Err(ScriptError::InvalidStackOperation); + } + + let f = stack.pop().unwrap(); + let e = stack.pop().unwrap(); + let d = stack.pop().unwrap(); + 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); + } + Opcode::OP_2SWAP => { + if stack.len() < 4 { + return Err(ScriptError::InvalidStackOperation); + } + + let d = stack.pop().unwrap(); + 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); + } + Opcode::OP_IFDUP => { + if stack.is_empty() { + return Err(ScriptError::InvalidStackOperation); + } + + todo!() + } + Opcode::OP_DEPTH => { + todo!() + } + Opcode::OP_DROP => { + if stack.is_empty() { + return Err(ScriptError::InvalidStackOperation); + } + + stack.pop(); + } + Opcode::OP_DUP => { + if stack.is_empty() { + return Err(ScriptError::InvalidStackOperation); + } + + let a = stack.pop().unwrap(); + stack.push(a.clone()); + stack.push(a); + } + Opcode::OP_NIP => { + if stack.len() < 2 { + return Err(ScriptError::InvalidStackOperation); + } + + let b = stack.pop().unwrap(); + stack.pop(); + stack.push(b); + } + Opcode::OP_OVER => { + if stack.len() < 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); + } + Opcode::OP_PICK | Opcode::OP_ROLL => { + todo!() + } + Opcode::OP_ROT => { + todo!() + } + Opcode::OP_SWAP => { + if stack.len() < 2 { + return Err(ScriptError::InvalidStackOperation); + } + + let b = stack.pop().unwrap(); + let a = stack.pop().unwrap(); + stack.push(b); + stack.push(a); + } + Opcode::OP_TUCK => { + if stack.len() < 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); + } + Opcode::OP_SIZE => { + todo!() + } + Opcode::OP_EQUAL | Opcode::OP_EQUALVERIFY => { + if stack.len() < 2 { + return Err(ScriptError::InvalidStackOperation); + } + + todo!() + } + Opcode::OP_1ADD + | Opcode::OP_1SUB + | Opcode::OP_NEGATE + | Opcode::OP_ABS + | Opcode::OP_NOT + | Opcode::OP_0NOTEQUAL => { + todo!() + } + Opcode::OP_ADD + | Opcode::OP_SUB + | Opcode::OP_BOOLAND + | Opcode::OP_BOOLOR + | Opcode::OP_NUMEQUAL + | Opcode::OP_NUMEQUALVERIFY + | Opcode::OP_NUMNOTEQUAL + | Opcode::OP_LESSTHAN + | Opcode::OP_GREATERTHAN + | Opcode::OP_LESSTHANOREQUAL + | Opcode::OP_GREATERTHANOREQUAL + | Opcode::OP_MIN + | Opcode::OP_MAX => { + todo!() + } + Opcode::OP_WITHIN => { + todo!() + } + Opcode::OP_RIPEMD160 + | Opcode::OP_SHA1 + | Opcode::OP_SHA256 + | Opcode::OP_HASH160 + | Opcode::OP_HASH256 => { + todo!() + } + Opcode::OP_CHECKSIG | Opcode::OP_CHECKSIGVERIFY => { + todo!() + } + Opcode::OP_CHECKMULTISIG | Opcode::OP_CHECKMULTISIGVERIFY => { + todo!() + } + } + } + } + } + + // 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() + alt_stack.len() > 1000 { + return Err(ScriptError::StackSize); + } + } + + if exec.is_empty() { + Ok(()) + } else { + Err(ScriptError::UnbalancedConditional) + } + } +} + +fn check_minimal_push(data: &[u8], raw_opcode: u8) -> bool { + todo!() +} diff --git a/src/rust_interpreter/opcodes.rs b/src/rust_interpreter/opcodes.rs new file mode 100644 index 000000000..4a57d1b0c --- /dev/null +++ b/src/rust_interpreter/opcodes.rs @@ -0,0 +1,244 @@ +#![allow(non_camel_case_types)] + +use super::ScriptError; + +// Opcodes for pushing to the stack +const OP_0: u8 = 0x00; +const OP_PUSHDATA1: u8 = 0x4c; +const OP_PUSHDATA2: u8 = 0x4d; +const OP_PUSHDATA4: u8 = 0x4e; + +// First and last opcodes for pushing constants to the stack. OP_RESERVED is +// 0x50, which is included in this range, but it does not push anything to the +// stack and is considered invalid when it appears in an executing branch. +const OP_1NEGATE: u8 = 0x4f; +const OP_16: u8 = 0x60; + +// The first and last of the opcodes that are actually executed (with the +// exception of OP_VERIF and OP_VERNOTIF as noted next) +const OP_NOP: u8 = 0x61; +const OP_NOP10: u8 = 0xb9; + +// These opcodes are considered invalid because they appear as control flow +// opcodes (forcing their execution) but do not have a defined behavior during +// execution and so behave like unknown opcodes. +const OP_VERIF: u8 = 0x65; +const OP_VERNOTIF: u8 = 0x66; + +// Explicitly disabled opcodes +const OP_CAT: u8 = 0x7e; +const OP_SUBSTR: u8 = 0x7f; +const OP_LEFT: u8 = 0x80; +const OP_RIGHT: u8 = 0x81; +const OP_INVERT: u8 = 0x83; +const OP_AND: u8 = 0x84; +const OP_OR: u8 = 0x85; +const OP_XOR: u8 = 0x86; +const OP_2MUL: u8 = 0x8d; +const OP_2DIV: u8 = 0x8e; +const OP_MUL: u8 = 0x95; +const OP_DIV: u8 = 0x96; +const OP_MOD: u8 = 0x97; +const OP_LSHIFT: u8 = 0x98; +const OP_RSHIFT: u8 = 0x99; +const OP_CODESEPARATOR: u8 = 0xab; + +pub enum Operation { + PushBytes(u8), + Constant(i64), + Opcode(Opcode), + Invalid, + Disabled, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum Opcode { + // OP_RESERVED is technically a valid operation inside of a non-executing + // OP_IF branch + OP_RESERVED = 0x50, + + OP_NOP = 0x61, + + // OP_VER is technically a valid operation inside of a non-executing OP_IF + // branch + OP_VER = 0x62, + OP_IF = 0x63, + OP_NOTIF = 0x64, + + OP_ELSE = 0x67, + OP_ENDIF = 0x68, + OP_VERIFY = 0x69, + OP_RETURN = 0x6a, + OP_TOALTSTACK = 0x6b, + OP_FROMALTSTACK = 0x6c, + OP_2DROP = 0x6d, + OP_2DUP = 0x6e, + OP_3DUP = 0x6f, + OP_2OVER = 0x70, + OP_2ROT = 0x71, + OP_2SWAP = 0x72, + OP_IFDUP = 0x73, + OP_DEPTH = 0x74, + OP_DROP = 0x75, + OP_DUP = 0x76, + OP_NIP = 0x77, + OP_OVER = 0x78, + OP_PICK = 0x79, + OP_ROLL = 0x7a, + OP_ROT = 0x7b, + OP_SWAP = 0x7c, + OP_TUCK = 0x7d, + OP_SIZE = 0x82, + OP_EQUAL = 0x87, + OP_EQUALVERIFY = 0x88, + + // OP_RESERVED1 and OP_RESERVED2 are technically valid in non-executing + // OP_IF branches + OP_RESERVED1 = 0x89, + OP_RESERVED2 = 0x8a, + + OP_1ADD = 0x8b, + OP_1SUB = 0x8c, + OP_NEGATE = 0x8f, + OP_ABS = 0x90, + OP_NOT = 0x91, + OP_0NOTEQUAL = 0x92, + OP_ADD = 0x93, + OP_SUB = 0x94, + OP_BOOLAND = 0x9a, + OP_BOOLOR = 0x9b, + OP_NUMEQUAL = 0x9c, + OP_NUMEQUALVERIFY = 0x9d, + OP_NUMNOTEQUAL = 0x9e, + OP_LESSTHAN = 0x9f, + OP_GREATERTHAN = 0xa0, + OP_LESSTHANOREQUAL = 0xa1, + OP_GREATERTHANOREQUAL = 0xa2, + OP_MIN = 0xa3, + OP_MAX = 0xa4, + OP_WITHIN = 0xa5, + OP_RIPEMD160 = 0xa6, + OP_SHA1 = 0xa7, + OP_SHA256 = 0xa8, + OP_HASH160 = 0xa9, + OP_HASH256 = 0xaa, + OP_CHECKSIG = 0xac, + OP_CHECKSIGVERIFY = 0xad, + OP_CHECKMULTISIG = 0xae, + OP_CHECKMULTISIGVERIFY = 0xaf, + OP_NOP1 = 0xb0, + + // OP_NOP2 was renamed to OP_CHECKLOCKTIMEVERIFY + OP_CHECKLOCKTIMEVERIFY = 0xb1, + + OP_NOP3 = 0xb2, + OP_NOP4 = 0xb3, + OP_NOP5 = 0xb4, + OP_NOP6 = 0xb5, + OP_NOP7 = 0xb6, + OP_NOP8 = 0xb7, + OP_NOP9 = 0xb8, + OP_NOP10 = 0xb9, +} + +impl Opcode { + /// Control flow opcodes are those between OP_IF and OP_ENDIF. Notably this + /// includes OP_VERIF and OP_VERNOTIF, which are not valid because they have + /// no implementations. + pub fn is_control_flow_opcode(&self) -> bool { + (Opcode::OP_IF) as u8 <= (*self as u8) && (*self as u8) <= (Opcode::OP_ENDIF as u8) + } +} + +pub fn parse_opcode( + script: &mut &[u8], + mut buffer: Option<&mut Vec>, +) -> Result { + if script.is_empty() { + panic!("attempting to parse an opcode from an empty script"); + } + + // Empty the provided buffer, if any + buffer.as_mut().map(|buffer| { + buffer.truncate(0); + }); + + let leading_byte = script[0]; + *script = &script[1..]; + + Ok(match leading_byte { + OP_PUSHDATA1 | OP_PUSHDATA2 | OP_PUSHDATA4 => { + let read_le = |script: &mut &[u8], needed_bytes: usize| { + if script.len() < needed_bytes { + Err(ScriptError::ReadError { + expected_bytes: needed_bytes, + available_bytes: script.len(), + }) + } else { + let mut size = 0; + for i in (0..needed_bytes).rev() { + size <<= 8; + size |= script[i] as usize; + } + *script = &script[needed_bytes..]; + Ok(size) + } + }; + + let size = match leading_byte { + OP_PUSHDATA1 => read_le(script, 1), + OP_PUSHDATA2 => read_le(script, 2), + OP_PUSHDATA4 => read_le(script, 4), + _ => unreachable!(), + }?; + + if script.len() < size { + return Err(ScriptError::ReadError { + expected_bytes: size, + available_bytes: script.len(), + }); + } + + buffer.map(|buffer| { + buffer.extend(&script[0..size]); + *script = &script[size..]; + }); + + Operation::PushBytes(leading_byte) + } + // OP_0/OP_FALSE doesn't actually push a constant 0 onto the stack but + // pushes an empty array. (Thus we leave the buffer truncated to 0 length) + OP_0 => Operation::PushBytes(leading_byte), + // OP_1NEGATE through OP_16 + byte if byte >= OP_1NEGATE && byte <= OP_16 => { + let value = byte as i64; + let value = value - 0x50; + + if value == 0 { + // This is actually OP_RESERVED (0x50) + Operation::Opcode(Opcode::OP_RESERVED) + } else { + // This is either OP_1NEGATE (-1) or one of OP_1/OP_TRUE through OP_16 + Operation::Constant(value) + } + } + // OP_NOP through OP_NOP10 + byte if byte >= OP_NOP && byte <= OP_NOP10 => { + match byte { + OP_CAT | OP_SUBSTR | OP_LEFT | OP_RIGHT | OP_INVERT | OP_AND | OP_OR | OP_XOR + | OP_2MUL | OP_2DIV | OP_MUL | OP_DIV | OP_MOD | OP_LSHIFT | OP_RSHIFT + | OP_CODESEPARATOR => Operation::Disabled, + OP_VERIF | OP_VERNOTIF => Operation::Invalid, + _ => { + let opcode: Opcode = unsafe { + // Safety: between OP_NOP and OP_NOP10, 8-bit opcode descriminants + // are defined (except for the opcodes above which we account for) + core::mem::transmute(byte) + }; + Operation::Opcode(opcode) + } + } + } + _ => Operation::Invalid, + }) +} From d2763f987ab81a6b39642af058516db30eac5362 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Fri, 9 Aug 2024 20:22:22 -0600 Subject: [PATCH 02/12] Rearrange Rust impl to match C++ more closely The only changes here other than moving chunks of code around are - moving `evaluate` out of `impl Script`, which required changing `&self` to `script: &Script`; and - unifying `ExecutionOptions` with `VerificationFlags`. --- src/interpreter.rs | 405 +++++++++++++++ src/lib.rs | 5 +- src/rust_interpreter/mod.rs | 462 ------------------ .../opcodes.rs => script.rs} | 35 +- src/script_error.rs | 18 + 5 files changed, 459 insertions(+), 466 deletions(-) delete mode 100644 src/rust_interpreter/mod.rs rename src/{rust_interpreter/opcodes.rs => script.rs} (86%) create mode 100644 src/script_error.rs diff --git a/src/interpreter.rs b/src/interpreter.rs index a9851f83d..6cbcd20a6 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,3 +1,6 @@ +use super::script::*; +use super::script_error::*; + bitflags::bitflags! { /// The different SigHash types, as defined in /// @@ -69,3 +72,405 @@ bitflags::bitflags! { const CHECKLOCKTIMEVERIFY = 1 << 9; } } + +fn check_minimal_push(data: &[u8], raw_opcode: u8) -> bool { + todo!() +} + +pub fn evaluate( + stack: &mut Vec>, + script: &Script, + options: &VerificationFlags, +) -> Result<(), ScriptError> { + // There's a limit on how large scripts can be. + if script.0.len() > MAX_SCRIPT_SIZE { + return Err(ScriptError::ScriptSize); + } + + let mut script = (*script).clone(); + let mut push_data = vec![]; + + // We keep track of how many operations have executed so far to prevent + // expensive-to-verify scripts + let mut op_count = 0; + + // 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 exec: Vec = vec![]; + + let mut alt_stack: Vec> = vec![]; + + // Main execution loop + while !script.0.is_empty() { + // Are we in an executing branch of the script? + let executing = exec.iter().all(|value| *value); + + // Consume an opcode + let operation = parse_opcode(&mut script.0, Some(&mut push_data))?; + + match operation { + Operation::PushBytes(raw_opcode) => { + // There's a limit to the size of the values we'll put on + // the stack. + if push_data.len() > MAX_SCRIPT_ELEMENT_SIZE { + return Err(ScriptError::PushSize); + } + + if executing { + // Data is being pushed to the stack here; we may need to check + // that the minimal script size was used to do so if our caller + // requires it. + if options.contains(VerificationFlags::MinimalData) + && !check_minimal_push(&push_data, raw_opcode) + { + return Err(ScriptError::MinimalData); + } + + stack.push(push_data.clone()); + } + } + Operation::Constant(value) => { + todo!() + } + + // Invalid and disabled opcodes do technically contribute to + // op_count, but they always result in a failed script execution + // anyway. + Operation::Invalid => return Err(ScriptError::InvalidOpcode), + Operation::Disabled => return Err(ScriptError::DisabledOpcode), + + Operation::Opcode(opcode) => { + // There's a limit on how many operations can execute in a + // script. We consider opcodes beyond OP_16 to be "actual" + // opcodes as ones below that just involve data pushes. All + // opcodes defined by the Opcode enum qualify except for + // OP_RESERVED, which is not beyond OP_16. + // + // Note: operations even if they are not executed but are + // still present in the script count toward this count. + if opcode != Opcode::OP_RESERVED { + op_count += 1; + if op_count > 201 { + return Err(ScriptError::OpCount); + } + } + + if executing || opcode.is_control_flow_opcode() { + match opcode { + Opcode::OP_RESERVED + | Opcode::OP_VER + | Opcode::OP_RESERVED1 + | Opcode::OP_RESERVED2 => { + // These are considered "invalid" opcodes but + // only inside of *executing* OP_IF branches of + // the script. + return Err(ScriptError::InvalidOpcode); + } + Opcode::OP_NOP => { + // Do nothing. + } + Opcode::OP_NOP1 + | Opcode::OP_NOP3 + | Opcode::OP_NOP4 + | Opcode::OP_NOP5 + | Opcode::OP_NOP6 + | Opcode::OP_NOP7 + | Opcode::OP_NOP8 + | Opcode::OP_NOP9 + | Opcode::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 `discourage_upgradable_nops` to turn + // these opcodes into errors. + if options.contains(VerificationFlags::DiscourageUpgradableNOPs) { + return Err(ScriptError::UpgradableNops); + } + } + Opcode::OP_CHECKLOCKTIMEVERIFY => { + // This was originally OP_NOP2 but has been repurposed + // for OP_CHECKLOCKTIMEVERIFY. So, we should act based + // on whether or not CLTV has been activated in a soft + // fork. + if !options.contains(VerificationFlags::CHECKLOCKTIMEVERIFY) { + if options.contains(VerificationFlags::DiscourageUpgradableNOPs) { + return Err(ScriptError::UpgradableNops); + } + } else { + todo!() + } + } + Opcode::OP_IF | Opcode::OP_NOTIF => { + let mut value = false; + if executing { + if stack.is_empty() { + return Err(ScriptError::UnbalancedConditional); + } + todo!() + } + exec.push(value); + } + Opcode::OP_ELSE => { + if exec.is_empty() { + return Err(ScriptError::UnbalancedConditional); + } + + exec.last_mut().map(|last| *last = !*last); + } + Opcode::OP_ENDIF => { + if exec.is_empty() { + return Err(ScriptError::UnbalancedConditional); + } + + exec.pop(); + } + Opcode::OP_VERIFY => { + if stack.is_empty() { + return Err(ScriptError::InvalidStackOperation); + } + + let value = stack.pop().unwrap(); + + todo!() + } + Opcode::OP_RETURN => return Err(ScriptError::OpReturn), + Opcode::OP_TOALTSTACK => { + if stack.is_empty() { + return Err(ScriptError::InvalidStackOperation); + } + + alt_stack.push(stack.pop().unwrap()); + } + Opcode::OP_FROMALTSTACK => { + if alt_stack.is_empty() { + return Err(ScriptError::InvalidStackOperation); + } + + stack.push(alt_stack.pop().unwrap()); + } + Opcode::OP_2DROP => { + if stack.len() < 2 { + return Err(ScriptError::InvalidStackOperation); + } + + stack.pop(); + stack.pop(); + } + Opcode::OP_2DUP => { + if stack.len() < 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); + } + Opcode::OP_3DUP => { + if stack.len() < 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); + } + Opcode::OP_2OVER => { + if stack.len() < 4 { + return Err(ScriptError::InvalidStackOperation); + } + + let d = stack.pop().unwrap(); + 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); + } + Opcode::OP_2ROT => { + if stack.len() < 6 { + return Err(ScriptError::InvalidStackOperation); + } + + let f = stack.pop().unwrap(); + let e = stack.pop().unwrap(); + let d = stack.pop().unwrap(); + 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); + } + Opcode::OP_2SWAP => { + if stack.len() < 4 { + return Err(ScriptError::InvalidStackOperation); + } + + let d = stack.pop().unwrap(); + 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); + } + Opcode::OP_IFDUP => { + if stack.is_empty() { + return Err(ScriptError::InvalidStackOperation); + } + + todo!() + } + Opcode::OP_DEPTH => { + todo!() + } + Opcode::OP_DROP => { + if stack.is_empty() { + return Err(ScriptError::InvalidStackOperation); + } + + stack.pop(); + } + Opcode::OP_DUP => { + if stack.is_empty() { + return Err(ScriptError::InvalidStackOperation); + } + + let a = stack.pop().unwrap(); + stack.push(a.clone()); + stack.push(a); + } + Opcode::OP_NIP => { + if stack.len() < 2 { + return Err(ScriptError::InvalidStackOperation); + } + + let b = stack.pop().unwrap(); + stack.pop(); + stack.push(b); + } + Opcode::OP_OVER => { + if stack.len() < 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); + } + Opcode::OP_PICK | Opcode::OP_ROLL => { + todo!() + } + Opcode::OP_ROT => { + todo!() + } + Opcode::OP_SWAP => { + if stack.len() < 2 { + return Err(ScriptError::InvalidStackOperation); + } + + let b = stack.pop().unwrap(); + let a = stack.pop().unwrap(); + stack.push(b); + stack.push(a); + } + Opcode::OP_TUCK => { + if stack.len() < 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); + } + Opcode::OP_SIZE => { + todo!() + } + Opcode::OP_EQUAL | Opcode::OP_EQUALVERIFY => { + if stack.len() < 2 { + return Err(ScriptError::InvalidStackOperation); + } + + todo!() + } + Opcode::OP_1ADD + | Opcode::OP_1SUB + | Opcode::OP_NEGATE + | Opcode::OP_ABS + | Opcode::OP_NOT + | Opcode::OP_0NOTEQUAL => { + todo!() + } + Opcode::OP_ADD + | Opcode::OP_SUB + | Opcode::OP_BOOLAND + | Opcode::OP_BOOLOR + | Opcode::OP_NUMEQUAL + | Opcode::OP_NUMEQUALVERIFY + | Opcode::OP_NUMNOTEQUAL + | Opcode::OP_LESSTHAN + | Opcode::OP_GREATERTHAN + | Opcode::OP_LESSTHANOREQUAL + | Opcode::OP_GREATERTHANOREQUAL + | Opcode::OP_MIN + | Opcode::OP_MAX => { + todo!() + } + Opcode::OP_WITHIN => { + todo!() + } + Opcode::OP_RIPEMD160 + | Opcode::OP_SHA1 + | Opcode::OP_SHA256 + | Opcode::OP_HASH160 + | Opcode::OP_HASH256 => { + todo!() + } + Opcode::OP_CHECKSIG | Opcode::OP_CHECKSIGVERIFY => { + todo!() + } + Opcode::OP_CHECKMULTISIG | Opcode::OP_CHECKMULTISIGVERIFY => { + todo!() + } + } + } + } + } + + // 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() + alt_stack.len() > 1000 { + return Err(ScriptError::StackSize); + } + } + + if exec.is_empty() { + Ok(()) + } else { + Err(ScriptError::UnbalancedConditional) + } +} diff --git a/src/lib.rs b/src/lib.rs index e4901088b..71bbb32a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,8 @@ pub use cxx::*; mod interpreter; pub use interpreter::{HashType, VerificationFlags}; +mod script; +mod script_error; mod zcash_script; pub use zcash_script::*; @@ -109,9 +111,6 @@ impl ZcashScript for Cxx { } } -#[cfg(feature = "rust-interpreter")] -pub mod rust_interpreter; - #[cfg(test)] mod tests { pub use super::*; diff --git a/src/rust_interpreter/mod.rs b/src/rust_interpreter/mod.rs deleted file mode 100644 index 6b1ea011b..000000000 --- a/src/rust_interpreter/mod.rs +++ /dev/null @@ -1,462 +0,0 @@ -pub mod opcodes; -use opcodes::*; - -/// Maximum allowed size of data (in bytes) that can be pushed to the stack. -pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520; - -/// Maximum allowed script length in bytes. -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 - -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum ScriptError { - ReadError { - expected_bytes: usize, - available_bytes: usize, - }, - InvalidOpcode, - PushSize, - OpCount, - DisabledOpcode, - ScriptSize, - MinimalData, - StackSize, - UnbalancedConditional, - UpgradableNops, - InvalidStackOperation, - OpReturn, -} - -pub struct ExecutionOptions { - pub require_minimal_pushes: bool, - pub enable_checklocktimeverify: bool, - pub discourage_upgradable_nops: bool, -} - -#[derive(Clone)] -pub struct Script<'a>(pub &'a [u8]); - -impl<'a> Script<'a> { - /// Returns true iff this script is P2PKH. - pub fn is_p2pkh(&self) -> bool { - (self.0.len() == 25) - && (self.0[0] == Opcode::OP_DUP as u8) - && (self.0[1] == Opcode::OP_HASH160 as u8) - && (self.0[2] == 0x14) - && (self.0[23] == Opcode::OP_EQUALVERIFY as u8) - && (self.0[24] == Opcode::OP_CHECKSIG as u8) - } - - /// Returns true iff this script is P2SH. - pub fn is_p2sh(&self) -> bool { - (self.0.len() == 23) - && (self.0[0] == Opcode::OP_HASH160 as u8) - && (self.0[1] == 0x14) - && (self.0[22] == Opcode::OP_EQUAL as u8) - } - - pub fn evaluate( - &self, - stack: &mut Vec>, - options: &ExecutionOptions, - ) -> Result<(), ScriptError> { - // There's a limit on how large scripts can be. - if self.0.len() > MAX_SCRIPT_SIZE { - return Err(ScriptError::ScriptSize); - } - - let mut script = (*self).clone(); - let mut push_data = vec![]; - - // We keep track of how many operations have executed so far to prevent - // expensive-to-verify scripts - let mut op_count = 0; - - // 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 exec: Vec = vec![]; - - let mut alt_stack: Vec> = vec![]; - - // Main execution loop - while !script.0.is_empty() { - // Are we in an executing branch of the script? - let executing = exec.iter().all(|value| *value); - - // Consume an opcode - let operation = parse_opcode(&mut script.0, Some(&mut push_data))?; - - match operation { - Operation::PushBytes(raw_opcode) => { - // There's a limit to the size of the values we'll put on - // the stack. - if push_data.len() > MAX_SCRIPT_ELEMENT_SIZE { - return Err(ScriptError::PushSize); - } - - if executing { - // Data is being pushed to the stack here; we may need to check - // that the minimal script size was used to do so if our caller - // requires it. - if options.require_minimal_pushes - && !check_minimal_push(&push_data, raw_opcode) - { - return Err(ScriptError::MinimalData); - } - - stack.push(push_data.clone()); - } - } - Operation::Constant(value) => { - todo!() - } - - // Invalid and disabled opcodes do technically contribute to - // op_count, but they always result in a failed script execution - // anyway. - Operation::Invalid => return Err(ScriptError::InvalidOpcode), - Operation::Disabled => return Err(ScriptError::DisabledOpcode), - - Operation::Opcode(opcode) => { - // There's a limit on how many operations can execute in a - // script. We consider opcodes beyond OP_16 to be "actual" - // opcodes as ones below that just involve data pushes. All - // opcodes defined by the Opcode enum qualify except for - // OP_RESERVED, which is not beyond OP_16. - // - // Note: operations even if they are not executed but are - // still present in the script count toward this count. - if opcode != Opcode::OP_RESERVED { - op_count += 1; - if op_count > 201 { - return Err(ScriptError::OpCount); - } - } - - if executing || opcode.is_control_flow_opcode() { - match opcode { - Opcode::OP_RESERVED - | Opcode::OP_VER - | Opcode::OP_RESERVED1 - | Opcode::OP_RESERVED2 => { - // These are considered "invalid" opcodes but - // only inside of *executing* OP_IF branches of - // the script. - return Err(ScriptError::InvalidOpcode); - } - Opcode::OP_NOP => { - // Do nothing. - } - Opcode::OP_NOP1 - | Opcode::OP_NOP3 - | Opcode::OP_NOP4 - | Opcode::OP_NOP5 - | Opcode::OP_NOP6 - | Opcode::OP_NOP7 - | Opcode::OP_NOP8 - | Opcode::OP_NOP9 - | Opcode::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 `discourage_upgradable_nops` to turn - // these opcodes into errors. - if options.discourage_upgradable_nops { - return Err(ScriptError::UpgradableNops); - } - } - Opcode::OP_CHECKLOCKTIMEVERIFY => { - // This was originally OP_NOP2 but has been repurposed - // for OP_CHECKLOCKTIMEVERIFY. So, we should act based - // on whether or not CLTV has been activated in a soft - // fork. - if !options.enable_checklocktimeverify { - if options.discourage_upgradable_nops { - return Err(ScriptError::UpgradableNops); - } - } else { - todo!() - } - } - Opcode::OP_IF | Opcode::OP_NOTIF => { - let mut value = false; - if executing { - if stack.is_empty() { - return Err(ScriptError::UnbalancedConditional); - } - todo!() - } - exec.push(value); - } - Opcode::OP_ELSE => { - if exec.is_empty() { - return Err(ScriptError::UnbalancedConditional); - } - - exec.last_mut().map(|last| *last = !*last); - } - Opcode::OP_ENDIF => { - if exec.is_empty() { - return Err(ScriptError::UnbalancedConditional); - } - - exec.pop(); - } - Opcode::OP_VERIFY => { - if stack.is_empty() { - return Err(ScriptError::InvalidStackOperation); - } - - let value = stack.pop().unwrap(); - - todo!() - } - Opcode::OP_RETURN => return Err(ScriptError::OpReturn), - Opcode::OP_TOALTSTACK => { - if stack.is_empty() { - return Err(ScriptError::InvalidStackOperation); - } - - alt_stack.push(stack.pop().unwrap()); - } - Opcode::OP_FROMALTSTACK => { - if alt_stack.is_empty() { - return Err(ScriptError::InvalidStackOperation); - } - - stack.push(alt_stack.pop().unwrap()); - } - Opcode::OP_2DROP => { - if stack.len() < 2 { - return Err(ScriptError::InvalidStackOperation); - } - - stack.pop(); - stack.pop(); - } - Opcode::OP_2DUP => { - if stack.len() < 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); - } - Opcode::OP_3DUP => { - if stack.len() < 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); - } - Opcode::OP_2OVER => { - if stack.len() < 4 { - return Err(ScriptError::InvalidStackOperation); - } - - let d = stack.pop().unwrap(); - 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); - } - Opcode::OP_2ROT => { - if stack.len() < 6 { - return Err(ScriptError::InvalidStackOperation); - } - - let f = stack.pop().unwrap(); - let e = stack.pop().unwrap(); - let d = stack.pop().unwrap(); - 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); - } - Opcode::OP_2SWAP => { - if stack.len() < 4 { - return Err(ScriptError::InvalidStackOperation); - } - - let d = stack.pop().unwrap(); - 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); - } - Opcode::OP_IFDUP => { - if stack.is_empty() { - return Err(ScriptError::InvalidStackOperation); - } - - todo!() - } - Opcode::OP_DEPTH => { - todo!() - } - Opcode::OP_DROP => { - if stack.is_empty() { - return Err(ScriptError::InvalidStackOperation); - } - - stack.pop(); - } - Opcode::OP_DUP => { - if stack.is_empty() { - return Err(ScriptError::InvalidStackOperation); - } - - let a = stack.pop().unwrap(); - stack.push(a.clone()); - stack.push(a); - } - Opcode::OP_NIP => { - if stack.len() < 2 { - return Err(ScriptError::InvalidStackOperation); - } - - let b = stack.pop().unwrap(); - stack.pop(); - stack.push(b); - } - Opcode::OP_OVER => { - if stack.len() < 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); - } - Opcode::OP_PICK | Opcode::OP_ROLL => { - todo!() - } - Opcode::OP_ROT => { - todo!() - } - Opcode::OP_SWAP => { - if stack.len() < 2 { - return Err(ScriptError::InvalidStackOperation); - } - - let b = stack.pop().unwrap(); - let a = stack.pop().unwrap(); - stack.push(b); - stack.push(a); - } - Opcode::OP_TUCK => { - if stack.len() < 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); - } - Opcode::OP_SIZE => { - todo!() - } - Opcode::OP_EQUAL | Opcode::OP_EQUALVERIFY => { - if stack.len() < 2 { - return Err(ScriptError::InvalidStackOperation); - } - - todo!() - } - Opcode::OP_1ADD - | Opcode::OP_1SUB - | Opcode::OP_NEGATE - | Opcode::OP_ABS - | Opcode::OP_NOT - | Opcode::OP_0NOTEQUAL => { - todo!() - } - Opcode::OP_ADD - | Opcode::OP_SUB - | Opcode::OP_BOOLAND - | Opcode::OP_BOOLOR - | Opcode::OP_NUMEQUAL - | Opcode::OP_NUMEQUALVERIFY - | Opcode::OP_NUMNOTEQUAL - | Opcode::OP_LESSTHAN - | Opcode::OP_GREATERTHAN - | Opcode::OP_LESSTHANOREQUAL - | Opcode::OP_GREATERTHANOREQUAL - | Opcode::OP_MIN - | Opcode::OP_MAX => { - todo!() - } - Opcode::OP_WITHIN => { - todo!() - } - Opcode::OP_RIPEMD160 - | Opcode::OP_SHA1 - | Opcode::OP_SHA256 - | Opcode::OP_HASH160 - | Opcode::OP_HASH256 => { - todo!() - } - Opcode::OP_CHECKSIG | Opcode::OP_CHECKSIGVERIFY => { - todo!() - } - Opcode::OP_CHECKMULTISIG | Opcode::OP_CHECKMULTISIGVERIFY => { - todo!() - } - } - } - } - } - - // 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() + alt_stack.len() > 1000 { - return Err(ScriptError::StackSize); - } - } - - if exec.is_empty() { - Ok(()) - } else { - Err(ScriptError::UnbalancedConditional) - } - } -} - -fn check_minimal_push(data: &[u8], raw_opcode: u8) -> bool { - todo!() -} diff --git a/src/rust_interpreter/opcodes.rs b/src/script.rs similarity index 86% rename from src/rust_interpreter/opcodes.rs rename to src/script.rs index 4a57d1b0c..39f7931cd 100644 --- a/src/rust_interpreter/opcodes.rs +++ b/src/script.rs @@ -1,6 +1,16 @@ #![allow(non_camel_case_types)] -use super::ScriptError; +use super::script_error::*; + +/// Maximum allowed size of data (in bytes) that can be pushed to the stack. +pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520; + +/// Maximum allowed script length in bytes. +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 // Opcodes for pushing to the stack const OP_0: u8 = 0x00; @@ -242,3 +252,26 @@ pub fn parse_opcode( _ => Operation::Invalid, }) } + +#[derive(Clone, Debug)] +pub struct Script<'a>(pub &'a [u8]); + +impl<'a> Script<'a> { + /// Returns true iff this script is P2PKH. + pub fn is_p2pkh(&self) -> bool { + (self.0.len() == 25) + && (self.0[0] == Opcode::OP_DUP as u8) + && (self.0[1] == Opcode::OP_HASH160 as u8) + && (self.0[2] == 0x14) + && (self.0[23] == Opcode::OP_EQUALVERIFY as u8) + && (self.0[24] == Opcode::OP_CHECKSIG as u8) + } + + /// Returns true iff this script is P2SH. + pub fn is_p2sh(&self) -> bool { + (self.0.len() == 23) + && (self.0[0] == Opcode::OP_HASH160 as u8) + && (self.0[1] == 0x14) + && (self.0[22] == Opcode::OP_EQUAL as u8) + } +} diff --git a/src/script_error.rs b/src/script_error.rs new file mode 100644 index 000000000..a2a45138c --- /dev/null +++ b/src/script_error.rs @@ -0,0 +1,18 @@ +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum ScriptError { + ReadError { + expected_bytes: usize, + available_bytes: usize, + }, + InvalidOpcode, + PushSize, + OpCount, + DisabledOpcode, + ScriptSize, + MinimalData, + StackSize, + UnbalancedConditional, + UpgradableNops, + InvalidStackOperation, + OpReturn, +} From 3d71aeff99f16a8cd86ab1a322705eca2d0483f4 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Fri, 9 Aug 2024 21:25:18 -0600 Subject: [PATCH 03/12] Rename Rust identifiers to match C++ For easier side-by-side comparison. --- src/interpreter.rs | 44 +++++++++++++++++++++++--------------------- src/script.rs | 4 ++-- src/script_error.rs | 27 ++++++++++++++++----------- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index 6cbcd20a6..ffa302fe6 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -73,14 +73,16 @@ bitflags::bitflags! { } } -fn check_minimal_push(data: &[u8], raw_opcode: u8) -> bool { +type ValType = Vec; + +fn check_minimal_push(data: &ValType, opcode: Opcode) -> bool { todo!() } -pub fn evaluate( +pub fn eval_script( stack: &mut Vec>, script: &Script, - options: &VerificationFlags, + flags: &VerificationFlags, ) -> Result<(), ScriptError> { // There's a limit on how large scripts can be. if script.0.len() > MAX_SCRIPT_SIZE { @@ -88,7 +90,7 @@ pub fn evaluate( } let mut script = (*script).clone(); - let mut push_data = vec![]; + let mut vch_push_value = vec![]; // We keep track of how many operations have executed so far to prevent // expensive-to-verify scripts @@ -100,7 +102,7 @@ pub fn evaluate( // opcodes direct control flow (OP_IF, OP_ELSE, etc.). let mut exec: Vec = vec![]; - let mut alt_stack: Vec> = vec![]; + let mut altstack: Vec> = vec![]; // Main execution loop while !script.0.is_empty() { @@ -108,13 +110,13 @@ pub fn evaluate( let executing = exec.iter().all(|value| *value); // Consume an opcode - let operation = parse_opcode(&mut script.0, Some(&mut push_data))?; + let operation = parse_opcode(&mut script.0, Some(&mut vch_push_value))?; match operation { Operation::PushBytes(raw_opcode) => { // There's a limit to the size of the values we'll put on // the stack. - if push_data.len() > MAX_SCRIPT_ELEMENT_SIZE { + if vch_push_value.len() > MAX_SCRIPT_ELEMENT_SIZE { return Err(ScriptError::PushSize); } @@ -122,13 +124,13 @@ pub fn evaluate( // Data is being pushed to the stack here; we may need to check // that the minimal script size was used to do so if our caller // requires it. - if options.contains(VerificationFlags::MinimalData) - && !check_minimal_push(&push_data, raw_opcode) + if flags.contains(VerificationFlags::MinimalData) + && !check_minimal_push(&vch_push_value, raw_opcode) { return Err(ScriptError::MinimalData); } - stack.push(push_data.clone()); + stack.push(vch_push_value.clone()); } } Operation::Constant(value) => { @@ -138,7 +140,7 @@ pub fn evaluate( // Invalid and disabled opcodes do technically contribute to // op_count, but they always result in a failed script execution // anyway. - Operation::Invalid => return Err(ScriptError::InvalidOpcode), + Operation::Invalid => return Err(ScriptError::BadOpcode), Operation::Disabled => return Err(ScriptError::DisabledOpcode), Operation::Opcode(opcode) => { @@ -166,7 +168,7 @@ pub fn evaluate( // These are considered "invalid" opcodes but // only inside of *executing* OP_IF branches of // the script. - return Err(ScriptError::InvalidOpcode); + return Err(ScriptError::BadOpcode); } Opcode::OP_NOP => { // Do nothing. @@ -185,8 +187,8 @@ pub fn evaluate( // of a standard tx rule, for example) they can // enable `discourage_upgradable_nops` to turn // these opcodes into errors. - if options.contains(VerificationFlags::DiscourageUpgradableNOPs) { - return Err(ScriptError::UpgradableNops); + if flags.contains(VerificationFlags::DiscourageUpgradableNOPs) { + return Err(ScriptError::DiscourageUpgradableNOPs); } } Opcode::OP_CHECKLOCKTIMEVERIFY => { @@ -194,9 +196,9 @@ pub fn evaluate( // for OP_CHECKLOCKTIMEVERIFY. So, we should act based // on whether or not CLTV has been activated in a soft // fork. - if !options.contains(VerificationFlags::CHECKLOCKTIMEVERIFY) { - if options.contains(VerificationFlags::DiscourageUpgradableNOPs) { - return Err(ScriptError::UpgradableNops); + if !flags.contains(VerificationFlags::CHECKLOCKTIMEVERIFY) { + if flags.contains(VerificationFlags::DiscourageUpgradableNOPs) { + return Err(ScriptError::DiscourageUpgradableNOPs); } } else { todo!() @@ -241,14 +243,14 @@ pub fn evaluate( return Err(ScriptError::InvalidStackOperation); } - alt_stack.push(stack.pop().unwrap()); + altstack.push(stack.pop().unwrap()); } Opcode::OP_FROMALTSTACK => { - if alt_stack.is_empty() { + if altstack.is_empty() { return Err(ScriptError::InvalidStackOperation); } - stack.push(alt_stack.pop().unwrap()); + stack.push(altstack.pop().unwrap()); } Opcode::OP_2DROP => { if stack.len() < 2 { @@ -463,7 +465,7 @@ pub fn evaluate( // 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() + alt_stack.len() > 1000 { + if stack.len() + altstack.len() > 1000 { return Err(ScriptError::StackSize); } } diff --git a/src/script.rs b/src/script.rs index 39f7931cd..cd5f163cd 100644 --- a/src/script.rs +++ b/src/script.rs @@ -258,7 +258,7 @@ pub struct Script<'a>(pub &'a [u8]); impl<'a> Script<'a> { /// Returns true iff this script is P2PKH. - pub fn is_p2pkh(&self) -> bool { + pub fn is_pay_to_public_key_hash(&self) -> bool { (self.0.len() == 25) && (self.0[0] == Opcode::OP_DUP as u8) && (self.0[1] == Opcode::OP_HASH160 as u8) @@ -268,7 +268,7 @@ impl<'a> Script<'a> { } /// Returns true iff this script is P2SH. - pub fn is_p2sh(&self) -> bool { + pub fn is_pay_to_script_hash(&self) -> bool { (self.0.len() == 23) && (self.0[0] == Opcode::OP_HASH160 as u8) && (self.0[1] == 0x14) diff --git a/src/script_error.rs b/src/script_error.rs index a2a45138c..c76c3c06b 100644 --- a/src/script_error.rs +++ b/src/script_error.rs @@ -1,18 +1,23 @@ #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum ScriptError { - ReadError { - expected_bytes: usize, - available_bytes: usize, - }, - InvalidOpcode, + OpReturn, + + ScriptSize, PushSize, OpCount, - DisabledOpcode, - ScriptSize, - MinimalData, StackSize, - UnbalancedConditional, - UpgradableNops, + + BadOpcode, + DisabledOpcode, InvalidStackOperation, - OpReturn, + UnbalancedConditional, + + MinimalData, + + DiscourageUpgradableNOPs, + + ReadError { + expected_bytes: usize, + available_bytes: usize, + }, } From 336d0fee4ca6f8f3956e4e9fe71d6a773198512d Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Mon, 12 Aug 2024 19:02:12 -0600 Subject: [PATCH 04/12] Connected the new API, but fails --- Cargo.lock | 1 + Cargo.toml | 2 + src/external/mod.rs | 4 + src/external/pubkey.rs | 23 +++ src/external/uint256.rs | 2 + src/interpreter.rs | 351 ++++++++++++++++++++++++++++++++-------- src/lib.rs | 113 +++++++++++-- src/script.rs | 16 +- src/script_error.rs | 29 ++++ src/zcash_script.rs | 48 ++++-- 10 files changed, 494 insertions(+), 95 deletions(-) create mode 100644 src/external/mod.rs create mode 100644 src/external/pubkey.rs create mode 100644 src/external/uint256.rs 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) + } +} From bd168e1f24e05a6618afdb700fa1eee641adc4aa Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Wed, 14 Aug 2024 17:20:27 -0600 Subject: [PATCH 05/12] Existing unit tests succeed --- Cargo.lock | 105 +++++++- Cargo.toml | 3 + src/external/pubkey.rs | 30 ++- src/interpreter.rs | 596 ++++++++++++++++++++++++++++++++--------- src/script.rs | 153 ++++++++++- src/zcash_script.rs | 9 +- 6 files changed, 755 insertions(+), 141 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d38fccffb..c5edd36c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,6 +40,15 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "cc" version = "1.0.95" @@ -77,6 +86,35 @@ dependencies = [ "libloading", ] +[[package]] +name = "cpufeatures" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "either" version = "1.13.0" @@ -104,6 +142,16 @@ dependencies = [ "libc", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "glob" version = "0.3.1" @@ -157,9 +205,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libloading" @@ -268,6 +316,15 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -287,6 +344,35 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "secp256k1" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1433bd67156263443f14d603720b082dd3121779323fce20cba2aa07b874bc1b" +dependencies = [ + "cc", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.2.0" @@ -304,12 +390,24 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "which" version = "4.4.2" @@ -420,4 +518,7 @@ dependencies = [ "hex", "lazy_static", "log", + "ripemd", + "secp256k1", + "sha2", ] diff --git a/Cargo.toml b/Cargo.toml index 30c63dee3..4fe7fa2fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,9 @@ rust-interpreter = [] [dependencies] bitflags = "2.5" log = "0.4" +ripemd = "0.1" +secp256k1 = "0.29" +sha2 = "0.10" [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/pubkey.rs b/src/external/pubkey.rs index e3ccbb564..7a0880787 100644 --- a/src/external/pubkey.rs +++ b/src/external/pubkey.rs @@ -1,3 +1,5 @@ +use secp256k1::{ecdsa, Message, PublicKey, Secp256k1}; + use super::uint256::*; /// FIXME: `PUBLIC_KEY_SIZE` is meant to be an upper bound, it seems. Maybe parameterize the type @@ -17,7 +19,33 @@ impl PubKey<'_> { /// 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 { + pub fn verify(&self, hash: &UInt256, vch_sig: &Vec) -> bool { + if !self.is_valid() { + return false; + }; + + if let Ok(pubkey) = PublicKey::from_slice(self.0) { + // let sig: secp256k1_ecdsa_signature; + if vch_sig.len() == 0 { + return false; + }; + // Zcash, unlike Bitcoin, has always enforced strict DER signatures. + if let Ok(mut sig) = ecdsa::Signature::from_der(vch_sig) { + // libsecp256k1's ECDSA verification requires lower-S signatures, which have + // not historically been enforced in Bitcoin or Zcash, so normalize them first. + sig.normalize_s(); + let secp = Secp256k1::verification_only(); + secp.verify_ecdsa(&Message::from_digest(*hash), &sig, &pubkey) + .is_ok() + } else { + return false; + } + } else { + return false; + } + } + + pub fn check_low_s(vch_sig: &Vec) -> bool { todo!() } } diff --git a/src/interpreter.rs b/src/interpreter.rs index 63249dd66..a30ecd662 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,5 +1,8 @@ use std::slice::Iter; +use ripemd::Ripemd160; +use sha2::{Digest, Sha256}; + use super::external::pubkey::PubKey; use super::external::uint256::UInt256; use super::script::*; @@ -133,7 +136,7 @@ pub trait BaseSignatureChecker { false } - fn check_lock_time(&self, n_lock_time: i64) -> bool { + fn check_lock_time(&self, lock_time: &ScriptNum) -> bool { false } } @@ -142,7 +145,7 @@ type ValType = Vec; pub struct CallbackTransactionSignatureChecker<'a> { pub sighash: SighashCalculator<'a>, - pub n_lock_time: i64, + pub lock_time: &'a ScriptNum, pub is_final: bool, } @@ -154,10 +157,23 @@ impl CallbackTransactionSignatureChecker<'_> { impl BaseSignatureChecker for CallbackTransactionSignatureChecker<'_> { fn check_sig(&self, vch_sig_in: &Vec, vch_pub_key: &Vec, script_code: &Script) -> bool { - todo!() + let pubkey = PubKey(vch_pub_key.as_slice()); + if !pubkey.is_valid() { + return false; + }; + + // Hash type is one byte tacked on to the end of the signature + let mut vch_sig = (*vch_sig_in).clone(); + vch_sig + .pop() + .and_then(|hash_type| { + (self.sighash)(script_code.0, HashType::from_bits_retain(hash_type.into())) + }) + .map(|sighash| Self::verify_signature(&vch_sig, &pubkey, &sighash)) + .unwrap_or(false) } - fn check_lock_time(&self, n_lock_time: i64) -> bool { + fn check_lock_time(&self, lock_time: &ScriptNum) -> bool { // There are two times of nLockTime: lock-by-blockheight // and lock-by-blocktime, distinguished by whether // nLockTime < LOCKTIME_THRESHOLD. @@ -165,13 +181,13 @@ impl BaseSignatureChecker 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.n_lock_time < LOCKTIME_THRESHOLD && n_lock_time < LOCKTIME_THRESHOLD) - || (self.n_lock_time >= LOCKTIME_THRESHOLD && n_lock_time >= LOCKTIME_THRESHOLD)) + if !((*self.lock_time < LOCKTIME_THRESHOLD && *lock_time < LOCKTIME_THRESHOLD) + || (*self.lock_time >= LOCKTIME_THRESHOLD && *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 { + } else if lock_time > self.lock_time { false // Finally the nLockTime feature can be disabled and thus // CHECKLOCKTIMEVERIFY bypassed if every txin has been @@ -199,8 +215,200 @@ impl BaseSignatureChecker for CallbackTransactionSignatureChecker<'_> { } fn cast_to_bool(vch: &ValType) -> bool { - // FIXME: Doesn’t handle negative zero - vch.iter().fold(false, |acc, vchi| acc || *vchi != 0) + for i in 0..vch.len() { + if vch[i] != 0 { + // Can be negative zero + if i == vch.len() - 1 && vch[i] == 0x80 { + return false; + } + return true; + } + } + false +} + +fn is_compressed_or_uncompressed_pub_key(vch_pub_key: &ValType) -> bool { + if vch_pub_key.len() < PubKey::COMPRESSED_PUBLIC_KEY_SIZE { + // Non-canonical public key: too short + return false; + } + if vch_pub_key[0] == 0x04 { + if vch_pub_key.len() != PubKey::PUBLIC_KEY_SIZE { + // Non-canonical public key: invalid length for uncompressed key + return false; + } + } else if vch_pub_key[0] == 0x02 || vch_pub_key[0] == 0x03 { + if vch_pub_key.len() != PubKey::COMPRESSED_PUBLIC_KEY_SIZE { + // Non-canonical public key: invalid length for compressed key + return false; + } + } else { + // Non-canonical public key: neither compressed nor uncompressed + return false; + } + true +} + +/** + * A canonical signature exists of: <30> <02> <02> + * Where R and S are not negative (their first byte has its highest bit not set), and not + * excessively padded (do not start with a 0 byte, unless an otherwise negative number follows, + * in which case a single 0 byte is necessary and even required). + * + * See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623 + * + * This function is consensus-critical since BIP66. + */ +fn is_valid_signature_encoding(sig: &Vec) -> bool { + // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash] + // * total-length: 1-byte length descriptor of everything that follows, + // excluding the sighash byte. + // * R-length: 1-byte length descriptor of the R value that follows. + // * R: arbitrary-length big-endian encoded R value. It must use the shortest + // possible encoding for a positive integer (which means no null bytes at + // the start, except a single one when the next byte has its highest bit set). + // * S-length: 1-byte length descriptor of the S value that follows. + // * S: arbitrary-length big-endian encoded S value. The same rules apply. + // * sighash: 1-byte value indicating what data is hashed (not part of the DER + // signature) + + // Minimum and maximum size constraints. + if sig.len() < 9 { + return false; + }; + if sig.len() > 73 { + return false; + }; + + // A signature is of type 0x30 (compound). + if sig[0] != 0x30 { + return false; + }; + + // Make sure the length covers the entire signature. + if sig[1] as usize != sig.len() - 3 { + return false; + }; + + // Extract the length of the R element. + let len_r: usize = sig[3] as usize; + + // Make sure the length of the S element is still inside the signature. + if 5 + len_r >= sig.len() { + return false; + }; + + // Extract the length of the S element. + let len_s: usize = sig[5 + len_r] as usize; + + // Verify that the length of the signature matches the sum of the length + // of the elements. + if len_r + len_s + 7 != sig.len() { + return false; + }; + + // Check whether the R element is an integer. + if sig[2] != 0x02 { + return false; + }; + + // Zero-length integers are not allowed for R. + if len_r == 0 { + return false; + }; + + // Negative numbers are not allowed for R. + if sig[4] & 0x80 != 0 { + return false; + }; + + // Null bytes at the start of R are not allowed, unless R would + // otherwise be interpreted as a negative number. + if len_r > 1 && sig[4] == 0x00 && sig[5] & 0x80 == 0 { + return false; + }; + + // Check whether the S element is an integer. + if sig[len_r + 4] != 0x02 { + return false; + }; + + // Zero-length integers are not allowed for S. + if len_s == 0 { + return false; + }; + + // Negative numbers are not allowed for S. + if sig[len_r + 6] & 0x80 != 0 { + return false; + }; + + // Null bytes at the start of S are not allowed, unless S would otherwise be + // interpreted as a negative number. + if len_s > 1 && sig[len_r + 6] == 0x00 && sig[len_r + 7] & 0x80 == 0 { + return false; + }; + + true +} + +fn is_low_der_signature(vch_sig: &ValType) -> Result { + if !is_valid_signature_encoding(vch_sig) { + return Err(ScriptError::SigDER); + }; + // https://bitcoin.stackexchange.com/a/12556: + // Also note that inside transaction signatures, an extra hashtype byte + // follows the actual signature data. + let vch_sig_copy = vch_sig.clone(); + // If the S value is above the order of the curve divided by two, its + // complement modulo the order could have been used instead, which is + // one byte shorter when encoded correctly. + // FIXME: This can return `false` without setting an error, which is not the expectation of the + // caller. + Ok(PubKey::check_low_s(&vch_sig_copy)) +} + +fn is_defined_hashtype_signature(vch_sig: &ValType) -> bool { + if vch_sig.len() == 0 { + return false; + }; + let hash_type = i32::from(vch_sig[vch_sig.len() - 1]) & !HashType::AnyoneCanPay.bits(); + if hash_type < HashType::All.bits() || hash_type > HashType::Single.bits() { + return false; + }; + + true +} + +fn check_signature_encoding( + vch_sig: &Vec, + flags: VerificationFlags, +) -> Result { + // Empty signature. Not strictly DER encoded, but allowed to provide a + // compact way to provide an invalid signature for use with CHECK(MULTI)SIG + if vch_sig.len() == 0 { + return Ok(true); + }; + if !is_valid_signature_encoding(vch_sig) { + return Err(ScriptError::SigDER); + } else if flags.contains(VerificationFlags::LowS) && !is_low_der_signature(vch_sig)? { + // serror is set + return Ok(false); + } else if flags.contains(VerificationFlags::StrictEnc) + && !is_defined_hashtype_signature(vch_sig) + { + return Err(ScriptError::SigHashtype); + }; + Ok(true) +} + +fn check_pub_key_encoding(vch_sig: &ValType, flags: VerificationFlags) -> Result<(), ScriptError> { + if flags.contains(VerificationFlags::StrictEnc) + && !is_compressed_or_uncompressed_pub_key(vch_sig) + { + return Err(ScriptError::PubKeyType); + }; + Ok(()) } fn check_minimal_push(data: &[u8], raw_opcode: u8) -> bool { @@ -212,18 +420,23 @@ pub fn eval_script( script: &Script, flags: VerificationFlags, checker: &dyn BaseSignatureChecker, -) -> Result<(), ScriptError> { +) -> Result { + let vch_false: ValType = vec![]; + let vch_zero: ValType = vec![]; + let vch_true: ValType = vec![1]; + // There's a limit on how large scripts can be. if script.0.len() > MAX_SCRIPT_SIZE { return Err(ScriptError::ScriptSize); } - let mut script = (*script).clone(); + let mut pc = script.0; let mut vch_push_value = vec![]; // We keep track of how many operations have executed so far to prevent // expensive-to-verify scripts let mut op_count = 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* @@ -234,12 +447,12 @@ pub fn eval_script( let mut altstack: Stack> = Stack(vec![]); // Main execution loop - while !script.0.is_empty() { + while !pc.is_empty() { // Are we in an executing branch of the script? let executing = exec.iter().all(|value| *value); // Consume an opcode - let operation = parse_opcode(&mut script.0, Some(&mut vch_push_value))?; + let operation = get_op2(&mut pc, Some(&mut vch_push_value))?; match operation { Operation::PushBytes(raw_opcode) => { @@ -262,9 +475,7 @@ pub fn eval_script( stack.push_back(vch_push_value.clone()); } } - Operation::Constant(value) => { - todo!() - } + Operation::Constant(value) => stack.push_back(vec![value as u8]), // Invalid and disabled opcodes do technically contribute to // op_count, but they always result in a failed script execution @@ -314,7 +525,7 @@ pub fn eval_script( // 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 `discourage_upgradable_nops` to turn + // enable `DiscourageUpgradableNOPs` to turn // these opcodes into errors. if flags.contains(VerificationFlags::DiscourageUpgradableNOPs) { return Err(ScriptError::DiscourageUpgradableNOPs); @@ -355,14 +566,14 @@ pub fn eval_script( return Err(ScriptError::UnbalancedConditional); } - exec.pop(); + exec.pop()?; } Opcode::OP_VERIFY => { if stack.empty() { return Err(ScriptError::InvalidStackOperation); } - let value = stack.pop().unwrap(); + let value = stack.pop()?; todo!() } @@ -372,30 +583,30 @@ pub fn eval_script( return Err(ScriptError::InvalidStackOperation); } - altstack.push_back(stack.pop().unwrap()); + altstack.push_back(stack.pop()?); } Opcode::OP_FROMALTSTACK => { if altstack.empty() { return Err(ScriptError::InvalidStackOperation); } - stack.push_back(altstack.pop().unwrap()); + stack.push_back(altstack.pop()?); } Opcode::OP_2DROP => { if stack.size() < 2 { return Err(ScriptError::InvalidStackOperation); } - stack.pop(); - stack.pop(); + stack.pop()?; + stack.pop()?; } Opcode::OP_2DUP => { if stack.size() < 2 { return Err(ScriptError::InvalidStackOperation); } - let b = stack.pop().unwrap(); - let a = stack.pop().unwrap(); + let b = stack.pop()?; + let a = stack.pop()?; stack.push_back(a.clone()); stack.push_back(b.clone()); stack.push_back(a); @@ -406,9 +617,9 @@ pub fn eval_script( return Err(ScriptError::InvalidStackOperation); } - let c = stack.pop().unwrap(); - let b = stack.pop().unwrap(); - let a = stack.pop().unwrap(); + let c = stack.pop()?; + let b = stack.pop()?; + let a = stack.pop()?; stack.push_back(a.clone()); stack.push_back(b.clone()); stack.push_back(c.clone()); @@ -421,10 +632,10 @@ pub fn eval_script( return Err(ScriptError::InvalidStackOperation); } - let d = stack.pop().unwrap(); - let c = stack.pop().unwrap(); - let b = stack.pop().unwrap(); - let a = stack.pop().unwrap(); + let d = stack.pop()?; + let c = stack.pop()?; + let b = stack.pop()?; + let a = stack.pop()?; stack.push_back(a.clone()); stack.push_back(b.clone()); stack.push_back(c); @@ -437,12 +648,12 @@ pub fn eval_script( return Err(ScriptError::InvalidStackOperation); } - let f = stack.pop().unwrap(); - let e = stack.pop().unwrap(); - let d = stack.pop().unwrap(); - let c = stack.pop().unwrap(); - let b = stack.pop().unwrap(); - let a = stack.pop().unwrap(); + let f = stack.pop()?; + let e = stack.pop()?; + let d = stack.pop()?; + let c = stack.pop()?; + let b = stack.pop()?; + let a = stack.pop()?; stack.push_back(c); stack.push_back(d); stack.push_back(e); @@ -455,10 +666,10 @@ pub fn eval_script( return Err(ScriptError::InvalidStackOperation); } - let d = stack.pop().unwrap(); - let c = stack.pop().unwrap(); - let b = stack.pop().unwrap(); - let a = stack.pop().unwrap(); + let d = stack.pop()?; + let c = stack.pop()?; + let b = stack.pop()?; + let a = stack.pop()?; stack.push_back(c); stack.push_back(d); stack.push_back(a); @@ -479,14 +690,14 @@ pub fn eval_script( return Err(ScriptError::InvalidStackOperation); } - stack.pop(); + stack.pop()?; } Opcode::OP_DUP => { if stack.empty() { return Err(ScriptError::InvalidStackOperation); } - let a = stack.pop().unwrap(); + let a = stack.pop()?; stack.push_back(a.clone()); stack.push_back(a); } @@ -495,8 +706,8 @@ pub fn eval_script( return Err(ScriptError::InvalidStackOperation); } - let b = stack.pop().unwrap(); - stack.pop(); + let b = stack.pop()?; + stack.pop()?; stack.push_back(b); } Opcode::OP_OVER => { @@ -504,8 +715,8 @@ pub fn eval_script( return Err(ScriptError::InvalidStackOperation); } - let b = stack.pop().unwrap(); - let a = stack.pop().unwrap(); + let b = stack.pop()?; + let a = stack.pop()?; stack.push_back(a.clone()); stack.push_back(b); stack.push_back(a); @@ -521,8 +732,8 @@ pub fn eval_script( return Err(ScriptError::InvalidStackOperation); } - let b = stack.pop().unwrap(); - let a = stack.pop().unwrap(); + let b = stack.pop()?; + let a = stack.pop()?; stack.push_back(b); stack.push_back(a); } @@ -531,8 +742,8 @@ pub fn eval_script( return Err(ScriptError::InvalidStackOperation); } - let b = stack.pop().unwrap(); - let a = stack.pop().unwrap(); + let b = stack.pop()?; + let a = stack.pop()?; stack.push_back(b.clone()); stack.push_back(a); stack.push_back(b); @@ -545,7 +756,28 @@ pub fn eval_script( return Err(ScriptError::InvalidStackOperation); } - todo!() + if let Ok(vch2) = stack.pop() { + if let Ok(vch1) = stack.pop() { + let equal: bool = vch1 == vch2; + // OP_NOTEQUAL is disabled because it would be too easy to say + // something like n != 1 and have some wiseguy pass in 1 with extra + // zero bytes after it (numerically, 0x01 == 0x0001 == 0x000001) + //if (opcode == OP_NOTEQUAL) + // fEqual = !fEqual; + stack.push_back(if equal { + vch_true.clone() + } else { + vch_false.clone() + }); + if opcode == Opcode::OP_EQUALVERIFY { + if equal { + stack.pop()?; + } else { + return Err(ScriptError::EQUALVERIFY); + } + } + } + } } Opcode::OP_1ADD | Opcode::OP_1SUB @@ -578,25 +810,17 @@ pub fn eval_script( | Opcode::OP_SHA256 | Opcode::OP_HASH160 | Opcode::OP_HASH256 => { - 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!(), + if let Ok(vch) = stack.pop() { + stack.push_back(match opcode { + Opcode::OP_RIPEMD160 => Ripemd160::digest(vch).to_vec(), Opcode::OP_SHA1 => todo!(), - Opcode::OP_SHA256 => todo!(), - Opcode::OP_HASH160 => todo!(), + Opcode::OP_SHA256 => Sha256::digest(vch).to_vec(), + Opcode::OP_HASH160 => { + Ripemd160::digest(Sha256::digest(vch)).to_vec() + } Opcode::OP_HASH256 => todo!(), - _ => (), - }; - stack.pop(); - stack.push_back(vch_hash); + _ => panic!("Didn’t match a hashing opcode!"), + }); } else { return Err(ScriptError::InvalidStackOperation); } @@ -605,7 +829,113 @@ pub fn eval_script( todo!() } Opcode::OP_CHECKMULTISIG | Opcode::OP_CHECKMULTISIGVERIFY => { - todo!() + // ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) + + let mut i: i32 = 1; + if (stack.size() as i32) < i { + return Err(ScriptError::InvalidStackOperation); + }; + + let mut keys_count: i32 = + ScriptNum::new(stack.top(-i as isize)?, require_minimal, None) + .getint(); + if keys_count < 0 || keys_count > 20 { + return Err(ScriptError::PubKeyCount); + }; + op_count += keys_count; + if op_count > 201 { + return Err(ScriptError::OpCount); + }; + i += 1; + let mut ikey = i; + i += keys_count; + if (stack.size() as i32) < i { + return Err(ScriptError::InvalidStackOperation); + } + + let mut sigs_count: i32 = + ScriptNum::new(stack.top(-i as isize)?, require_minimal, None) + .getint(); + if sigs_count < 0 || sigs_count > keys_count { + return Err(ScriptError::SigCount); + }; + i += 1; + let mut isig = i; + i += sigs_count; + if (stack.size() as i32) < i { + return Err(ScriptError::InvalidStackOperation); + }; + + let mut success = true; + while success && sigs_count > 0 { + let vch_sig: &ValType = stack.top(-isig as isize)?; + let vch_pub_key: &ValType = stack.top(-ikey as isize)?; + + // 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. + if !check_signature_encoding(vch_sig, flags)? { + // serror is set + return Ok(false); + }; + 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 + while { + let res = i > 1; + i -= 1; + res + } { + 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 Err(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 opcode == Opcode::OP_CHECKMULTISIGVERIFY { + if success { + stack.pop()?; + } else { + return Err(ScriptError::CHECKMULTISIGVERIFY); + } + } } } } @@ -620,11 +950,11 @@ pub fn eval_script( } } - if exec.empty() { - Ok(()) - } else { - Err(ScriptError::UnbalancedConditional) - } + if !exec.empty() { + return Err(ScriptError::UnbalancedConditional); + }; + + Ok(true) } pub fn verify_script( @@ -634,56 +964,68 @@ pub fn verify_script( 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(()) - }, - ) + return Err(ScriptError::SigPushOnly); + } + + let mut stack = Stack(Vec::new()); + let mut stack_copy = Stack(Vec::new()); + if !eval_script(&mut stack, script_sig, flags, checker)? { + // FIXME: `eval_script` returned `false`, but didn’t error. + return Err(ScriptError::UnknownError); + } + if flags.contains(VerificationFlags::P2SH) { + stack_copy = stack.clone() } + if !eval_script(&mut stack, script_pub_key, flags, checker)? { + // FIXME: `eval_script` returned `false`, but didn’t error. + return Err(ScriptError::UnknownError); + } + if stack.back().map_or(true, |b| cast_to_bool(&b) == false) { + return Err(ScriptError::EvalFalse); + } + + // 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() { + return Err(ScriptError::SigPushOnly); + }; + + // Restore stack. + stack = stack_copy; + + if let Ok(pub_key_serialized) = stack.pop() { + let pub_key_2 = Script(&pub_key_serialized.as_slice()); + + if !eval_script(&mut stack, &pub_key_2, flags, checker)? { + // FIXME: `eval_script` returned `false`, but didn’t error. + return Err(ScriptError::UnknownError); + }; + if stack.back().map_or(true, |b| cast_to_bool(&b) == false) { + return Err(ScriptError::EvalFalse); + } + } 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. + return Err(ScriptError::StackSize); + } + }; + + // The CLEANSTACK check is only performed after potential P2SH evaluation, + // as the non-P2SH evaluation of a P2SH script will obviously not result in + // a clean stack (the P2SH inputs remain). + if flags.contains(VerificationFlags::CleanStack) { + // Disallow CLEANSTACK without P2SH, as otherwise a switch CLEANSTACK->P2SH+CLEANSTACK + // would be possible, which is not a softfork (and P2SH should be one). + assert!(flags.contains(VerificationFlags::P2SH)); + if stack.size() != 1 { + return Err(ScriptError::CleanStack); + } + }; + + Ok(()) } diff --git a/src/script.rs b/src/script.rs index 6ff8c3c3c..5cee2ed3c 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: i64 = 500000000; // Tue Nov 5 00:53:20 1985 UTC +pub const LOCKTIME_THRESHOLD: ScriptNum = ScriptNum(500000000); // Tue Nov 5 00:53:20 1985 UTC // Opcodes for pushing to the stack const OP_0: u8 = 0x00; @@ -53,15 +53,16 @@ const OP_LSHIFT: u8 = 0x98; const OP_RSHIFT: u8 = 0x99; const OP_CODESEPARATOR: u8 = 0xab; +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] pub enum Operation { PushBytes(u8), - Constant(i64), + Constant(i8), Opcode(Opcode), Invalid, Disabled, } -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] pub enum Opcode { // OP_RESERVED is technically a valid operation inside of a non-executing // OP_IF branch @@ -160,7 +161,11 @@ impl Opcode { } } -pub fn parse_opcode( +pub fn get_op(script: &mut &[u8]) -> Result { + get_op2(script, None) +} + +pub fn get_op2( script: &mut &[u8], mut buffer: Option<&mut Vec>, ) -> Result { @@ -219,9 +224,26 @@ pub fn parse_opcode( // OP_0/OP_FALSE doesn't actually push a constant 0 onto the stack but // pushes an empty array. (Thus we leave the buffer truncated to 0 length) OP_0 => Operation::PushBytes(leading_byte), + byte if byte >= 0x01 && byte <= 0x4b => { + let size = leading_byte as usize; + + if script.len() < size { + return Err(ScriptError::ReadError { + expected_bytes: size, + available_bytes: script.len(), + }); + } + + buffer.map(|buffer| { + buffer.extend(&script[0..size]); + *script = &script[size..]; + }); + + Operation::PushBytes(leading_byte) + } // OP_1NEGATE through OP_16 byte if byte >= OP_1NEGATE && byte <= OP_16 => { - let value = byte as i64; + let value = byte as i8; let value = value - 0x50; if value == 0 { @@ -253,6 +275,84 @@ pub fn parse_opcode( }) } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct ScriptNum(pub i64); + +impl ScriptNum { + const DEFAULT_MAX_NUM_SIZE: usize = 4; + + pub fn new(vch: &Vec, require_minimal: bool, max_num_size: Option) -> Self { + let max_num_size = max_num_size.unwrap_or(Self::DEFAULT_MAX_NUM_SIZE); + if vch.len() > max_num_size { + panic!("script number overflow"); + }; + if require_minimal { + if let Some(vch_back) = vch.last() { + // Check that the number is encoded with the minimum possible + // number of bytes. + // + // If the most-significant-byte - excluding the sign bit - is zero + // then we're not minimal. Note how this test also rejects the + // negative-zero encoding, 0x80. + if (vch_back & 0x7F) == 0 { + // One exception: if there's more than one byte and the most + // significant bit of the second-most-significant-byte is set + // it would conflict with the sign bit. An example of this case + // is +-255, which encode to 0xff00 and 0xff80 respectively. + // (big-endian). + if vch.len() <= 1 || (vch[vch.len() - 2] & 0x80) == 0 { + panic!("non-minimally encoded script number"); + } + } + } + }; + ScriptNum(Self::set_vch(vch)) + } + + pub fn getint(&self) -> i32 { + if self.0 > i32::MAX as i64 { + i32::MAX + } else if self.0 < i32::MIN as i64 { + i32::MIN + } else { + self.0 as i32 + } + } + + fn set_vch(vch: &Vec) -> i64 { + match vch.last() { + None => 0, + Some(vch_back) => { + if *vch == vec![0 as u8, 0, 0, 0, 0, 0, 0, 128, 128] { + // On an x86_64 system, the code below would actually decode the buggy + // INT64_MIN encoding correctly. However in this case, it would be + // performing left shifts of a signed type by 64, which has undefined + // behavior. + return i64::MIN; + }; + + // Guard against undefined behavior. INT64_MIN is the only allowed 9-byte encoding. + if vch.len() > 8 { + panic!("script number overflow"); + }; + + let mut result: i64 = 0; + for (i, vch_i) in vch.iter().enumerate() { + result |= i64::from(*vch_i) << (8 * i); + } + + // If the input vector's most significant byte is 0x80, remove it from + // the result's msb and return a negative. + if vch_back & 0x80 != 0 { + return -(result & !(0x80 << (8 * (vch.len() - 1)))); + }; + + result + } + } + } +} + #[derive(Clone, Debug)] pub struct Script<'a>(pub &'a [u8]); @@ -263,7 +363,37 @@ impl<'a> Script<'a> { /// counted more accurately, assuming they are of the form /// ... OP_N CHECKMULTISIG ... pub fn get_sig_op_count(&self, accurate: bool) -> u32 { - todo!() + let mut n = 0; + let mut pc = self.0; + let mut last_opcode = Operation::Invalid; + while !pc.is_empty() { + get_op(&mut pc).map_or((), |opcode| { + match opcode { + Operation::Opcode(Opcode::OP_CHECKSIG | Opcode::OP_CHECKSIGVERIFY) => n = n + 1, + Operation::Opcode( + Opcode::OP_CHECKMULTISIG | Opcode::OP_CHECKMULTISIGVERIFY, + ) => { + if accurate { + match last_opcode { + Operation::Constant(n_op) => { + if n_op >= 1 && n_op <= 16 { + n = n + n_op as u32 + } else { + n = n + 200 + } + } + _ => n = n + 200, + } + } else { + n = n + 200 + } + } + _ => (), + }; + last_opcode = opcode; + }) + } + n } /// Returns true iff this script is P2PKH. @@ -286,6 +416,15 @@ impl<'a> Script<'a> { /// Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). pub fn is_push_only(&self) -> bool { - todo!() + // let mut pc = self.clone(); + // while !pc.0.is_empty() { + // if !get_op(&mut pc.0).map_or(false, |opcode| match opcode { + // Operation::PushBytes(_) | Operation::Constant(_) => true, + // _ => false, + // }) { + // return false; + // }; + // }; + return true; } } diff --git a/src/zcash_script.rs b/src/zcash_script.rs index a633e74ac..9c24beb76 100644 --- a/src/zcash_script.rs +++ b/src/zcash_script.rs @@ -41,8 +41,8 @@ pub trait ZcashScript { /// /// Note that script verification failure is indicated by `Err(Error::Ok)`. fn verify_callback( - sighash: SighashCalculator, - n_lock_time: i64, + sighash_callback: SighashCalculator, + lock_time: i64, is_final: bool, script_pub_key: &[u8], script_sig: &[u8], @@ -67,19 +67,20 @@ impl ZcashScript for Rust { fn verify_callback( sighash: SighashCalculator, - n_lock_time: i64, + lock_time: i64, is_final: bool, script_pub_key: &[u8], script_sig: &[u8], flags: VerificationFlags, ) -> Result<(), Error> { + let lock_time_num = ScriptNum(lock_time); verify_script( &Script(script_sig), &Script(script_pub_key), flags, &CallbackTransactionSignatureChecker { sighash, - n_lock_time, + lock_time: &lock_time_num, is_final, }, ) From 571836d4c228283bc3bc71e7689bb73f6247cb11 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Thu, 19 Sep 2024 15:51:51 -0600 Subject: [PATCH 06/12] The rest of the owl --- Cargo.lock | 12 ++ Cargo.toml | 1 + src/interpreter.rs | 280 +++++++++++++++++++++++++++++++++++++++------ src/script.rs | 86 ++++++++++++++ 4 files changed, 344 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5edd36c5..e5518346f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,6 +362,17 @@ dependencies = [ "cc", ] +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -520,5 +531,6 @@ dependencies = [ "log", "ripemd", "secp256k1", + "sha-1", "sha2", ] diff --git a/Cargo.toml b/Cargo.toml index 4fe7fa2fd..025e403d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ bitflags = "2.5" log = "0.4" ripemd = "0.1" secp256k1 = "0.29" +sha-1 = "0.10" sha2 = "0.10" [build-dependencies] diff --git a/src/interpreter.rs b/src/interpreter.rs index a30ecd662..5dba6cfc0 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,6 +1,7 @@ use std::slice::Iter; use ripemd::Ripemd160; +use sha1::Sha1; use sha2::{Digest, Sha256}; use super::external::pubkey::PubKey; @@ -100,10 +101,21 @@ 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 { + fn from_top(&self, i: isize) -> Result { + usize::try_from(-i) + .map(|a| self.0.len() - a) + .map_err(|_| ScriptError::InvalidStackOperation) + } + pub fn top(&self, i: isize) -> Result<&T, ScriptError> { - self.0 - .get(self.0.len() - (-i) as usize) - .ok_or(ScriptError::InvalidStackOperation) + let idx = self.from_top(i)?; + self.0.get(idx).ok_or(ScriptError::InvalidStackOperation) + } + + pub fn swap(&mut self, a: isize, b: isize) -> Result<(), ScriptError> { + let au = self.from_top(a)?; + let bu = self.from_top(b)?; + Ok(self.0.swap(au, bu)) } pub fn pop(&mut self) -> Result { @@ -129,6 +141,16 @@ impl Stack { pub fn back(&mut self) -> Option<&mut T> { self.0.last_mut() } + + pub fn erase(&mut self, start: usize, end: Option) { + for _ in 0..end.map_or(1, |e| e - start) { + self.0.remove(start); + } + } + + pub fn end(&self) -> usize { + self.0.len() + } } pub trait BaseSignatureChecker { @@ -421,6 +443,8 @@ pub fn eval_script( flags: VerificationFlags, checker: &dyn BaseSignatureChecker, ) -> Result { + let bn_zero = ScriptNum(0); + let bn_one = ScriptNum(1); let vch_false: ValType = vec![]; let vch_zero: ValType = vec![]; let vch_true: ValType = vec![1]; @@ -442,14 +466,14 @@ 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: Stack = Stack(vec![]); + let mut vexec: Stack = Stack(vec![]); let mut altstack: Stack> = Stack(vec![]); // Main execution loop while !pc.is_empty() { // Are we in an executing branch of the script? - let executing = exec.iter().all(|value| *value); + let exec = vexec.iter().all(|value| *value); // Consume an opcode let operation = get_op2(&mut pc, Some(&mut vch_push_value))?; @@ -462,7 +486,7 @@ pub fn eval_script( return Err(ScriptError::PushSize); } - if executing { + if exec { // Data is being pushed to the stack here; we may need to check // that the minimal script size was used to do so if our caller // requires it. @@ -499,7 +523,7 @@ pub fn eval_script( } } - if executing || opcode.is_control_flow_opcode() { + if exec || opcode.is_control_flow_opcode() { match opcode { Opcode::OP_RESERVED | Opcode::OP_VER @@ -541,41 +565,82 @@ pub fn eval_script( return Err(ScriptError::DiscourageUpgradableNOPs); } } else { - todo!() + if stack.size() < 1 { + return Err(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(0) { + return Err(ScriptError::NegativeLockTime); + } + + // Actually compare the specified lock time with the transaction. + if !checker.check_lock_time(&lock_time) { + return Err(ScriptError::UnsatisfiedLockTime); + } } } Opcode::OP_IF | Opcode::OP_NOTIF => { + // if [statements] [else [statements]] endif let mut value = false; - if executing { - if stack.empty() { + if exec { + if stack.size() < 1 { return Err(ScriptError::UnbalancedConditional); } - todo!() + let vch: &ValType = stack.top(-1)?; + value = cast_to_bool(vch); + if opcode == Opcode::OP_NOTIF { + value = !value + }; + stack.pop()?; } - exec.push_back(value); + vexec.push_back(value); } Opcode::OP_ELSE => { - if exec.empty() { + if vexec.empty() { return Err(ScriptError::UnbalancedConditional); } - exec.back().map(|last| *last = !*last); + vexec.back().map(|last| *last = !*last); } Opcode::OP_ENDIF => { - if exec.empty() { + if vexec.empty() { return Err(ScriptError::UnbalancedConditional); } - exec.pop()?; + vexec.pop()?; } Opcode::OP_VERIFY => { - if stack.empty() { + // (true -- ) or + // (false -- false) and return + if stack.size() < 1 { return Err(ScriptError::InvalidStackOperation); } - - let value = stack.pop()?; - - todo!() + let value = cast_to_bool(stack.top(-1)?); + if value { + stack.pop()?; + } else { + return Err(ScriptError::VERIFY); + } } Opcode::OP_RETURN => return Err(ScriptError::OpReturn), Opcode::OP_TOALTSTACK => { @@ -676,14 +741,19 @@ pub fn eval_script( stack.push_back(b); } Opcode::OP_IFDUP => { - if stack.empty() { + // (x - 0 | x x) + if stack.size() < 1 { return Err(ScriptError::InvalidStackOperation); } - - todo!() + let vch = stack.top(-1)?; + if cast_to_bool(vch) { + stack.push_back(vch.to_vec()) + } } Opcode::OP_DEPTH => { - todo!() + // -- stacksize + let bn = ScriptNum(i64::try_from(stack.size()).unwrap()); + stack.push_back(bn.getvch()) } Opcode::OP_DROP => { if stack.empty() { @@ -722,10 +792,37 @@ pub fn eval_script( stack.push_back(a); } Opcode::OP_PICK | Opcode::OP_ROLL => { - todo!() + // (xn ... x2 x1 x0 n - xn ... x2 x1 x0 xn) + // (xn ... x2 x1 x0 n - ... x2 x1 x0 xn) + if stack.size() < 2 { + return Err(ScriptError::InvalidStackOperation); + } + let n = ScriptNum::new(stack.top(-1)?, require_minimal, None).getint(); + stack.pop()?; + if n < 0 || n >= i32::try_from(stack.size()).unwrap() { + return Err(ScriptError::InvalidStackOperation); + } + let vch: ValType = stack.top(isize::try_from(-n).unwrap() - 1)?.clone(); + if opcode == Opcode::OP_ROLL { + stack.erase( + usize::try_from( + i64::try_from(stack.end()).unwrap() - i64::from(n) - 1, + ) + .unwrap(), + None, + ); + } + stack.push_back(vch) } Opcode::OP_ROT => { - todo!() + // (x1 x2 x3 -- x2 x3 x1) + // x2 x1 x3 after first swap + // x2 x3 x1 after second swap + if stack.size() < 3 { + return Err(ScriptError::InvalidStackOperation); + } + stack.swap(-3, -2)?; + stack.swap(-2, -1)?; } Opcode::OP_SWAP => { if stack.size() < 2 { @@ -749,7 +846,12 @@ pub fn eval_script( stack.push_back(b); } Opcode::OP_SIZE => { - todo!() + // (in -- in size) + if stack.size() < 1 { + return Err(ScriptError::InvalidStackOperation); + } + let bn = ScriptNum(i64::try_from(stack.top(-1)?.len()).unwrap()); + stack.push_back(bn.getvch()) } Opcode::OP_EQUAL | Opcode::OP_EQUALVERIFY => { if stack.size() < 2 { @@ -785,7 +887,26 @@ pub fn eval_script( | Opcode::OP_ABS | Opcode::OP_NOT | Opcode::OP_0NOTEQUAL => { - todo!() + // (in -- out) + if stack.size() < 1 { + return Err(ScriptError::InvalidStackOperation); + } + let mut bn = ScriptNum::new(stack.top(-1)?, require_minimal, None); + match opcode { + Opcode::OP_1ADD => bn = bn + bn_one, + Opcode::OP_1SUB => bn = bn - bn_one, + Opcode::OP_NEGATE => bn = -bn, + Opcode::OP_ABS => { + if bn < bn_zero { + bn = -bn + } + } + Opcode::OP_NOT => bn = ScriptNum((bn == bn_zero).into()), + Opcode::OP_0NOTEQUAL => bn = ScriptNum((bn != bn_zero).into()), + _ => panic!("invalid opcode"), + } + stack.pop()?; + stack.push_back(bn.getvch()) } Opcode::OP_ADD | Opcode::OP_SUB @@ -800,10 +921,64 @@ pub fn eval_script( | Opcode::OP_GREATERTHANOREQUAL | Opcode::OP_MIN | Opcode::OP_MAX => { - todo!() + // (x1 x2 -- out) + if stack.size() < 2 { + return Err(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 opcode { + Opcode::OP_ADD => bn = bn1 + bn2, + + Opcode::OP_SUB => bn = bn1 - bn2, + + Opcode::OP_BOOLAND => { + bn = ScriptNum((bn1 != bn_zero && bn2 != bn_zero).into()) + } + Opcode::OP_BOOLOR => { + bn = ScriptNum((bn1 != bn_zero || bn2 != bn_zero).into()) + } + Opcode::OP_NUMEQUAL => bn = ScriptNum((bn1 == bn2).into()), + Opcode::OP_NUMEQUALVERIFY => bn = ScriptNum((bn1 == bn2).into()), + Opcode::OP_NUMNOTEQUAL => bn = ScriptNum((bn1 != bn2).into()), + Opcode::OP_LESSTHAN => bn = ScriptNum((bn1 < bn2).into()), + Opcode::OP_GREATERTHAN => bn = ScriptNum((bn1 > bn2).into()), + Opcode::OP_LESSTHANOREQUAL => bn = ScriptNum((bn1 <= bn2).into()), + Opcode::OP_GREATERTHANOREQUAL => bn = ScriptNum((bn1 >= bn2).into()), + Opcode::OP_MIN => bn = if bn1 < bn2 { bn1 } else { bn2 }, + Opcode::OP_MAX => bn = if bn1 > bn2 { bn1 } else { bn2 }, + _ => panic!("invalid opcode"), + }; + stack.pop()?; + stack.pop()?; + stack.push_back(bn.getvch()); + + if opcode == Opcode::OP_NUMEQUALVERIFY { + if cast_to_bool(stack.top(-1)?) { + stack.pop()?; + } else { + return Err(ScriptError::NUMEQUALVERIFY); + } + } } Opcode::OP_WITHIN => { - todo!() + // (x min max -- out) + if stack.size() < 3 { + return Err(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() + }) } Opcode::OP_RIPEMD160 | Opcode::OP_SHA1 @@ -813,12 +988,19 @@ pub fn eval_script( if let Ok(vch) = stack.pop() { stack.push_back(match opcode { Opcode::OP_RIPEMD160 => Ripemd160::digest(vch).to_vec(), - Opcode::OP_SHA1 => todo!(), + Opcode::OP_SHA1 => { + let mut hasher = Sha1::new(); + hasher.update(vch); + hasher.finalize().to_vec() + } Opcode::OP_SHA256 => Sha256::digest(vch).to_vec(), Opcode::OP_HASH160 => { Ripemd160::digest(Sha256::digest(vch)).to_vec() } - Opcode::OP_HASH256 => todo!(), + + Opcode::OP_HASH256 => { + Sha256::digest(Sha256::digest(vch)).to_vec() + } _ => panic!("Didn’t match a hashing opcode!"), }); } else { @@ -826,7 +1008,35 @@ pub fn eval_script( } } Opcode::OP_CHECKSIG | Opcode::OP_CHECKSIGVERIFY => { - todo!() + // (sig pubkey -- bool) + if stack.size() < 2 { + return Err(ScriptError::InvalidStackOperation); + } + + let vch_sig = stack.top(-2)?.clone(); + let vch_pub_key = stack.top(-1)?.clone(); + + if !check_signature_encoding(&vch_sig, flags)? { + //serror is set + return Ok(false); + } + 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 opcode == Opcode::OP_CHECKSIGVERIFY { + if success { + stack.pop()?; + } else { + return Err(ScriptError::CHECKSIGVERIFY); + } + } } Opcode::OP_CHECKMULTISIG | Opcode::OP_CHECKMULTISIGVERIFY => { // ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) @@ -950,7 +1160,7 @@ pub fn eval_script( } } - if !exec.empty() { + if !vexec.empty() { return Err(ScriptError::UnbalancedConditional); }; diff --git a/src/script.rs b/src/script.rs index 5cee2ed3c..344d0e6cc 100644 --- a/src/script.rs +++ b/src/script.rs @@ -1,5 +1,7 @@ #![allow(non_camel_case_types)] +use std::ops::{Add, Neg, Sub}; + use super::script_error::*; /// Maximum allowed size of data (in bytes) that can be pushed to the stack. @@ -319,6 +321,53 @@ impl ScriptNum { } } + pub fn getvch(&self) -> Vec { + Self::serialize(&self.0) + } + + pub fn serialize(value: &i64) -> Vec { + if *value == 0 { + return Vec::new(); + } + + if *value == i64::MIN { + // The code below is buggy, and produces the "wrong" result for + // INT64_MIN. To avoid undefined behavior while attempting to + // negate a value of INT64_MIN, we intentionally return the result + // that the code below would produce on an x86_64 system. + return vec![0, 0, 0, 0, 0, 0, 0, 128, 128]; + } + + let mut result = Vec::new(); + let neg = *value < 0; + let mut absvalue: u64 = value.abs() as u64; + + while absvalue != 0 { + result.push((absvalue & 0xff) as u8); + absvalue >>= 8; + } + + // - If the most significant byte is >= 0x80 and the value is positive, push a + // new zero-byte to make the significant byte < 0x80 again. + + // - If the most significant byte is >= 0x80 and the value is negative, push a + // new 0x80 byte that will be popped off when converting to an integral. + + // - If the most significant byte is < 0x80 and the value is negative, add + // 0x80 to it, since it will be subtracted and interpreted as a negative when + // converting to an integral. + + if result.last().map_or(true, |last| last & 0x80 != 0) { + result.push(if neg { 0x80 } else { 0 }); + } else if neg { + if let Some(last) = result.last_mut() { + *last |= 0x80; + } + } + + result + } + fn set_vch(vch: &Vec) -> i64 { match vch.last() { None => 0, @@ -353,6 +402,43 @@ impl ScriptNum { } } +impl Add for ScriptNum { + type Output = Self; + + fn add(self, other: Self) -> Self { + let rhs = other.0; + assert!( + rhs == 0 + || (rhs > 0 && self.0 <= i64::MAX - rhs) + || (rhs < 0 && self.0 >= i64::MIN - rhs) + ); + Self(self.0 + rhs) + } +} + +impl Sub for ScriptNum { + type Output = Self; + + fn sub(self, other: Self) -> Self { + let rhs = other.0; + assert!( + rhs == 0 + || (rhs > 0 && self.0 >= i64::MIN + rhs) + || (rhs < 0 && self.0 <= i64::MAX + rhs) + ); + Self(self.0 - rhs) + } +} + +impl Neg for ScriptNum { + type Output = Self; + + fn neg(self) -> Self { + assert!(self.0 != i64::MIN); + Self(-self.0) + } +} + #[derive(Clone, Debug)] pub struct Script<'a>(pub &'a [u8]); From 1b574602eb89a486930dad24c14cc32b2cac218a Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Mon, 26 Aug 2024 22:41:18 -0600 Subject: [PATCH 07/12] Reverting to C++ style, and some other changes --- Cargo.lock | 34 ++ Cargo.toml | 1 + src/external/pubkey.rs | 9 +- src/interpreter.rs | 1077 +++++++++++++++++++++------------------- src/lib.rs | 2 + src/script.rs | 560 ++++++++++++--------- 6 files changed, 931 insertions(+), 752 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5518346f..accfe071e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + [[package]] name = "bindgen" version = "0.69.4" @@ -121,6 +127,15 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "enum_primitive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" +dependencies = [ + "num-traits 0.1.43", +] + [[package]] name = "errno" version = "0.3.3" @@ -253,6 +268,24 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.19", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -526,6 +559,7 @@ dependencies = [ "bindgen", "bitflags", "cc", + "enum_primitive", "hex", "lazy_static", "log", diff --git a/Cargo.toml b/Cargo.toml index 025e403d7..aad6ce99b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ rust-interpreter = [] [dependencies] bitflags = "2.5" +enum_primitive = "0.1" log = "0.4" ripemd = "0.1" secp256k1 = "0.29" diff --git a/src/external/pubkey.rs b/src/external/pubkey.rs index 7a0880787..4dad8b95c 100644 --- a/src/external/pubkey.rs +++ b/src/external/pubkey.rs @@ -46,6 +46,13 @@ impl PubKey<'_> { } pub fn check_low_s(vch_sig: &Vec) -> bool { - todo!() + /* Zcash, unlike Bitcoin, has always enforced strict DER signatures. */ + if let Ok(sig) = ecdsa::Signature::from_der(vch_sig) { + let mut check = sig.clone(); + check.normalize_s(); + sig == check + } else { + false + } } } diff --git a/src/interpreter.rs b/src/interpreter.rs index 5dba6cfc0..f5c159262 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,3 +1,4 @@ +use std::mem::swap; use std::slice::Iter; use ripemd::Ripemd160; @@ -6,7 +7,7 @@ use sha2::{Digest, Sha256}; use super::external::pubkey::PubKey; use super::external::uint256::UInt256; -use super::script::*; +use super::script::{Operation::*, PushValue::*, *}; use super::script_error::*; bitflags::bitflags! { @@ -81,20 +82,58 @@ bitflags::bitflags! { } } -/// All signature hashes are 32 bits, since they are necessarily produced by SHA256. -pub const SIGHASH_SIZE: usize = 32; +pub trait SignatureChecker { + fn check_sig( + &self, + _script_sig: &Vec, + _vch_pub_key: &Vec, + _script_code: &Script, + ) -> bool { + false + } -/// 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]>; + fn check_lock_time(&self, _lock_time: &ScriptNum) -> bool { + false + } +} +pub struct BaseSignatureChecker(); + +impl SignatureChecker for BaseSignatureChecker {} + +pub struct CallbackTransactionSignatureChecker<'a> { + pub sighash: SighashCalculator<'a>, + pub lock_time: &'a ScriptNum, + pub is_final: bool, +} + +type ValType = Vec; + +fn set_success(res: T) -> Result { + Ok(res) +} + +fn set_error(serror: ScriptError) -> Result { + Err(serror) +} + +fn cast_to_bool(vch: &ValType) -> bool { + for i in 0..vch.len() { + if vch[i] != 0 { + // Can be negative zero + if i == vch.len() - 1 && vch[i] == 0x80 { + return false; + } + return true; + } + } + false +} + +/** + * 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)] pub struct Stack(Vec); @@ -138,8 +177,8 @@ impl Stack { self.0.iter() } - pub fn back(&mut self) -> Option<&mut T> { - self.0.last_mut() + pub fn back(&mut self) -> Result<&mut T, ScriptError> { + self.0.last_mut().ok_or(ScriptError::InvalidStackOperation) } pub fn erase(&mut self, start: usize, end: Option) { @@ -148,105 +187,13 @@ impl Stack { } } - pub fn end(&self) -> usize { - self.0.len() - } -} - -pub trait BaseSignatureChecker { - fn check_sig(&self, script_sig: &Vec, vch_pub_key: &Vec, script_code: &Script) -> bool { - false - } - - fn check_lock_time(&self, lock_time: &ScriptNum) -> bool { - false - } -} - -type ValType = Vec; - -pub struct CallbackTransactionSignatureChecker<'a> { - pub sighash: SighashCalculator<'a>, - pub lock_time: &'a ScriptNum, - pub is_final: bool, -} - -impl CallbackTransactionSignatureChecker<'_> { - pub fn verify_signature(vch_sig: &Vec, pubkey: &PubKey, sighash: &UInt256) -> bool { - pubkey.verify(sighash, vch_sig) + pub fn insert(&mut self, i: usize, element: T) { + self.0.insert(i, element) } -} -impl BaseSignatureChecker for CallbackTransactionSignatureChecker<'_> { - fn check_sig(&self, vch_sig_in: &Vec, vch_pub_key: &Vec, script_code: &Script) -> bool { - let pubkey = PubKey(vch_pub_key.as_slice()); - if !pubkey.is_valid() { - return false; - }; - - // Hash type is one byte tacked on to the end of the signature - let mut vch_sig = (*vch_sig_in).clone(); - vch_sig - .pop() - .and_then(|hash_type| { - (self.sighash)(script_code.0, HashType::from_bits_retain(hash_type.into())) - }) - .map(|sighash| Self::verify_signature(&vch_sig, &pubkey, &sighash)) - .unwrap_or(false) - } - - fn check_lock_time(&self, lock_time: &ScriptNum) -> 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.lock_time < LOCKTIME_THRESHOLD && *lock_time < LOCKTIME_THRESHOLD) - || (*self.lock_time >= LOCKTIME_THRESHOLD && *lock_time >= LOCKTIME_THRESHOLD)) - { - false - // Now that we know we're comparing apples-to-apples, the - // comparison is a simple numeric one. - } else if lock_time > self.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 { - for i in 0..vch.len() { - if vch[i] != 0 { - // Can be negative zero - if i == vch.len() - 1 && vch[i] == 0x80 { - return false; - } - return true; - } + pub fn end(&self) -> usize { + self.0.len() } - false } fn is_compressed_or_uncompressed_pub_key(vch_pub_key: &ValType) -> bool { @@ -308,12 +255,12 @@ fn is_valid_signature_encoding(sig: &Vec) -> bool { }; // Make sure the length covers the entire signature. - if sig[1] as usize != sig.len() - 3 { + if usize::from(sig[1]) != sig.len() - 3 { return false; }; // Extract the length of the R element. - let len_r: usize = sig[3] as usize; + let len_r = usize::from(sig[3]); // Make sure the length of the S element is still inside the signature. if 5 + len_r >= sig.len() { @@ -321,7 +268,7 @@ fn is_valid_signature_encoding(sig: &Vec) -> bool { }; // Extract the length of the S element. - let len_s: usize = sig[5 + len_r] as usize; + let len_s = usize::from(sig[5 + len_r]); // Verify that the length of the signature matches the sum of the length // of the elements. @@ -376,7 +323,7 @@ fn is_valid_signature_encoding(sig: &Vec) -> bool { fn is_low_der_signature(vch_sig: &ValType) -> Result { if !is_valid_signature_encoding(vch_sig) { - return Err(ScriptError::SigDER); + return set_error(ScriptError::SigDER); }; // https://bitcoin.stackexchange.com/a/12556: // Also note that inside transaction signatures, an extra hashtype byte @@ -412,14 +359,14 @@ fn check_signature_encoding( return Ok(true); }; if !is_valid_signature_encoding(vch_sig) { - return Err(ScriptError::SigDER); + return set_error(ScriptError::SigDER); } else if flags.contains(VerificationFlags::LowS) && !is_low_der_signature(vch_sig)? { // serror is set return Ok(false); } else if flags.contains(VerificationFlags::StrictEnc) && !is_defined_hashtype_signature(vch_sig) { - return Err(ScriptError::SigHashtype); + return set_error(ScriptError::SigHashtype); }; Ok(true) } @@ -430,28 +377,46 @@ fn check_pub_key_encoding(vch_sig: &ValType, flags: VerificationFlags) -> Result { return Err(ScriptError::PubKeyType); }; - Ok(()) + set_success(()) } -fn check_minimal_push(data: &[u8], raw_opcode: u8) -> bool { - todo!() +fn check_minimal_push(data: &ValType, opcode: PushValue) -> bool { + if data.len() == 0 { + // Could have used OP_0. + return opcode == OP_0; + } else if data.len() == 1 && data[0] >= 1 && data[0] <= 16 { + // Could have used OP_1 .. OP_16. + return u8::from(opcode) == u8::from(OP_1) + (data[0] - 1); + } else if data.len() == 1 && data[0] == 0x81 { + // Could have used OP_1NEGATE. + return opcode == OP_1NEGATE; + } else if data.len() <= 75 { + // Could have used a direct push (opcode indicating number of bytes pushed + those bytes). + return usize::from(u8::from(opcode)) == data.len(); + } else if data.len() <= 255 { + // Could have used OP_PUSHDATA. + return opcode == OP_PUSHDATA1; + } else if data.len() <= 65535 { + // Could have used OP_PUSHDATA2. + return opcode == OP_PUSHDATA2; + } + true } pub fn eval_script( stack: &mut Stack>, script: &Script, flags: VerificationFlags, - checker: &dyn BaseSignatureChecker, + checker: &dyn SignatureChecker, ) -> Result { let bn_zero = ScriptNum(0); let bn_one = ScriptNum(1); let vch_false: ValType = vec![]; - let vch_zero: ValType = vec![]; let vch_true: ValType = vec![1]; // There's a limit on how large scripts can be. if script.0.len() > MAX_SCRIPT_SIZE { - return Err(ScriptError::ScriptSize); + return set_error(ScriptError::ScriptSize); } let mut pc = script.0; @@ -459,7 +424,7 @@ pub fn eval_script( // We keep track of how many operations have executed so far to prevent // expensive-to-verify scripts - let mut op_count = 0; + let mut op_count: u8 = 0; let require_minimal = flags.contains(VerificationFlags::MinimalData); // This keeps track of the conditional flags at each nesting level @@ -475,98 +440,89 @@ pub fn eval_script( // Are we in an executing branch of the script? let exec = vexec.iter().all(|value| *value); - // Consume an opcode - let operation = get_op2(&mut pc, Some(&mut vch_push_value))?; - - match operation { - Operation::PushBytes(raw_opcode) => { - // There's a limit to the size of the values we'll put on - // the stack. - if vch_push_value.len() > MAX_SCRIPT_ELEMENT_SIZE { - return Err(ScriptError::PushSize); - } + // + // Read instruction + // + let opcode = Script::get_op2(&mut pc, &mut vch_push_value)?; + if vch_push_value.len() > MAX_SCRIPT_ELEMENT_SIZE { + return set_error(ScriptError::PushSize); + } + match opcode { + Opcode::PushValue(pv) => { if exec { - // Data is being pushed to the stack here; we may need to check - // that the minimal script size was used to do so if our caller - // requires it. - if flags.contains(VerificationFlags::MinimalData) - && !check_minimal_push(&vch_push_value, raw_opcode) - { - return Err(ScriptError::MinimalData); + 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(i64::from(u8::from(pv)) - i64::from(u8::from(OP_1) - 1)); + 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.clone()); + } else { + return set_error(ScriptError::BadOpcode); + } + } } - - stack.push_back(vch_push_value.clone()); } } - Operation::Constant(value) => stack.push_back(vec![value as u8]), - - // Invalid and disabled opcodes do technically contribute to - // op_count, but they always result in a failed script execution - // anyway. - Operation::Invalid => return Err(ScriptError::BadOpcode), - Operation::Disabled => return Err(ScriptError::DisabledOpcode), - - Operation::Opcode(opcode) => { - // There's a limit on how many operations can execute in a - // script. We consider opcodes beyond OP_16 to be "actual" - // opcodes as ones below that just involve data pushes. All - // opcodes defined by the Opcode enum qualify except for - // OP_RESERVED, which is not beyond OP_16. - // - // Note: operations even if they are not executed but are - // still present in the script count toward this count. - if opcode != Opcode::OP_RESERVED { - op_count += 1; - if op_count > 201 { - return Err(ScriptError::OpCount); - } + 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 exec || opcode.is_control_flow_opcode() { - match opcode { - Opcode::OP_RESERVED - | Opcode::OP_VER - | Opcode::OP_RESERVED1 - | Opcode::OP_RESERVED2 => { - // These are considered "invalid" opcodes but - // only inside of *executing* OP_IF branches of - // the script. - return Err(ScriptError::BadOpcode); - } - Opcode::OP_NOP => { - // Do nothing. - } - Opcode::OP_NOP1 - | Opcode::OP_NOP3 - | Opcode::OP_NOP4 - | Opcode::OP_NOP5 - | Opcode::OP_NOP6 - | Opcode::OP_NOP7 - | Opcode::OP_NOP8 - | Opcode::OP_NOP9 - | Opcode::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 Err(ScriptError::DiscourageUpgradableNOPs); - } - } - Opcode::OP_CHECKLOCKTIMEVERIFY => { + 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 => { // This was originally OP_NOP2 but has been repurposed // for OP_CHECKLOCKTIMEVERIFY. So, we should act based // on whether or not CLTV has been activated in a soft // fork. if !flags.contains(VerificationFlags::CHECKLOCKTIMEVERIFY) { if flags.contains(VerificationFlags::DiscourageUpgradableNOPs) { - return Err(ScriptError::DiscourageUpgradableNOPs); + return set_error(ScriptError::DiscourageUpgradableNOPs); } } else { if stack.size() < 1 { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } // Note that elsewhere numeric opcodes are limited to @@ -590,382 +546,397 @@ pub fn eval_script( // some arithmetic being done first, you can always use // 0 MAX CHECKLOCKTIMEVERIFY. if lock_time < ScriptNum(0) { - return Err(ScriptError::NegativeLockTime); + return set_error(ScriptError::NegativeLockTime); } // Actually compare the specified lock time with the transaction. if !checker.check_lock_time(&lock_time) { - return Err(ScriptError::UnsatisfiedLockTime); + return set_error(ScriptError::UnsatisfiedLockTime); } } } - Opcode::OP_IF | Opcode::OP_NOTIF => { + + 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 Err(ScriptError::UnbalancedConditional); + return set_error(ScriptError::UnbalancedConditional); } let vch: &ValType = stack.top(-1)?; value = cast_to_bool(vch); - if opcode == Opcode::OP_NOTIF { + if op == OP_NOTIF { value = !value }; stack.pop()?; } vexec.push_back(value); } - Opcode::OP_ELSE => { + + OP_ELSE => { if vexec.empty() { - return Err(ScriptError::UnbalancedConditional); + return set_error(ScriptError::UnbalancedConditional); } - - vexec.back().map(|last| *last = !*last); + vexec.back().map(|last| *last = !*last)?; } - Opcode::OP_ENDIF => { + + OP_ENDIF => { if vexec.empty() { - return Err(ScriptError::UnbalancedConditional); + return set_error(ScriptError::UnbalancedConditional); } - vexec.pop()?; } - Opcode::OP_VERIFY => { + + OP_VERIFY => { // (true -- ) or // (false -- false) and return if stack.size() < 1 { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } let value = cast_to_bool(stack.top(-1)?); if value { stack.pop()?; } else { - return Err(ScriptError::VERIFY); + return set_error(ScriptError::VERIFY); } } - Opcode::OP_RETURN => return Err(ScriptError::OpReturn), - Opcode::OP_TOALTSTACK => { + + OP_RETURN => return set_error(ScriptError::OpReturn), + + // + // Stack ops + // + OP_TOALTSTACK => { if stack.empty() { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } - - altstack.push_back(stack.pop()?); + altstack.push_back(stack.top(-1)?.clone()); + stack.pop()?; } - Opcode::OP_FROMALTSTACK => { + + OP_FROMALTSTACK => { if altstack.empty() { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidAltstackOperation); } - - stack.push_back(altstack.pop()?); + stack.push_back(altstack.top(-1)?.clone()); + altstack.pop()?; } - Opcode::OP_2DROP => { + + OP_2DROP => { if stack.size() < 2 { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } stack.pop()?; stack.pop()?; } - Opcode::OP_2DUP => { + + OP_2DUP => { + // (x1 x2 -- x1 x2 x1 x2) if stack.size() < 2 { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } - - let b = stack.pop()?; - let a = stack.pop()?; - stack.push_back(a.clone()); - stack.push_back(b.clone()); - stack.push_back(a); - stack.push_back(b); + let vch1 = stack.top(-2)?.clone(); + let vch2 = stack.top(-1)?.clone(); + stack.push_back(vch1); + stack.push_back(vch2); } - Opcode::OP_3DUP => { + + OP_3DUP => { + // (x1 x2 x3 -- x1 x2 x3 x1 x2 x3) if stack.size() < 3 { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } - - let c = stack.pop()?; - let b = stack.pop()?; - let a = stack.pop()?; - 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); + 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); } - Opcode::OP_2OVER => { + + OP_2OVER => { + // (x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2) if stack.size() < 4 { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } - - let d = stack.pop()?; - let c = stack.pop()?; - let b = stack.pop()?; - let a = stack.pop()?; - 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); + let vch1 = stack.top(-4)?.clone(); + let vch2 = stack.top(-3)?.clone(); + stack.push_back(vch1); + stack.push_back(vch2); } - Opcode::OP_2ROT => { + + OP_2ROT => { + // (x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2) if stack.size() < 6 { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } - - let f = stack.pop()?; - let e = stack.pop()?; - let d = stack.pop()?; - let c = stack.pop()?; - let b = stack.pop()?; - let a = stack.pop()?; - stack.push_back(c); - stack.push_back(d); - stack.push_back(e); - stack.push_back(f); - stack.push_back(a); - stack.push_back(b); + 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); } - Opcode::OP_2SWAP => { + + OP_2SWAP => { + // (x1 x2 x3 x4 -- x3 x4 x1 x2) if stack.size() < 4 { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } - - let d = stack.pop()?; - let c = stack.pop()?; - let b = stack.pop()?; - let a = stack.pop()?; - stack.push_back(c); - stack.push_back(d); - stack.push_back(a); - stack.push_back(b); + stack.swap(-4, -2)?; + stack.swap(-3, -1)?; } - Opcode::OP_IFDUP => { + + OP_IFDUP => { // (x - 0 | x x) if stack.size() < 1 { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } let vch = stack.top(-1)?; if cast_to_bool(vch) { stack.push_back(vch.to_vec()) } } - Opcode::OP_DEPTH => { + + OP_DEPTH => { // -- stacksize - let bn = ScriptNum(i64::try_from(stack.size()).unwrap()); + let bn = ScriptNum( + i64::try_from(stack.size()).map_err(|_| ScriptError::StackSize)?); stack.push_back(bn.getvch()) } - Opcode::OP_DROP => { - if stack.empty() { - return Err(ScriptError::InvalidStackOperation); - } + OP_DROP => { + // (x -- ) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } stack.pop()?; } - Opcode::OP_DUP => { - if stack.empty() { - return Err(ScriptError::InvalidStackOperation); + + OP_DUP => { + // (x -- x x) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); } let a = stack.pop()?; stack.push_back(a.clone()); stack.push_back(a); } - Opcode::OP_NIP => { + + OP_NIP => { + // (x1 x2 -- x2) if stack.size() < 2 { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } - - let b = stack.pop()?; - stack.pop()?; - stack.push_back(b); + stack.erase(stack.end() - 2, None); } - Opcode::OP_OVER => { + + OP_OVER => { + // (x1 x2 -- x1 x2 x1) if stack.size() < 2 { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } - - let b = stack.pop()?; - let a = stack.pop()?; - stack.push_back(a.clone()); - stack.push_back(b); - stack.push_back(a); + let vch = stack.top(-2)?; + stack.push_back(vch.clone()); } - Opcode::OP_PICK | Opcode::OP_ROLL => { + + 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 Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } - let n = ScriptNum::new(stack.top(-1)?, require_minimal, None).getint(); + let n = + u16::try_from(ScriptNum::new(stack.top(-1)?, require_minimal, None).getint()) + .map_err(|_| ScriptError::InvalidStackOperation)?; stack.pop()?; - if n < 0 || n >= i32::try_from(stack.size()).unwrap() { - return Err(ScriptError::InvalidStackOperation); + if usize::from(n) >= stack.size() { + return set_error(ScriptError::InvalidStackOperation); } - let vch: ValType = stack.top(isize::try_from(-n).unwrap() - 1)?.clone(); - if opcode == Opcode::OP_ROLL { - stack.erase( - usize::try_from( - i64::try_from(stack.end()).unwrap() - i64::from(n) - 1, - ) - .unwrap(), - None, - ); + 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) } - Opcode::OP_ROT => { + + 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 Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } stack.swap(-3, -2)?; stack.swap(-2, -1)?; } - Opcode::OP_SWAP => { + + OP_SWAP => { + // (x1 x2 -- x2 x1) if stack.size() < 2 { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } - - let b = stack.pop()?; - let a = stack.pop()?; - stack.push_back(b); - stack.push_back(a); + stack.swap(-2, -1)?; } - Opcode::OP_TUCK => { + + OP_TUCK => { + // (x1 x2 -- x2 x1 x2) if stack.size() < 2 { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } - - let b = stack.pop()?; - let a = stack.pop()?; - stack.push_back(b.clone()); - stack.push_back(a); - stack.push_back(b); + let vch = stack.top(-1)?.clone(); + stack.insert(stack.end() - 2, vch) } - Opcode::OP_SIZE => { + + + OP_SIZE => { // (in -- in size) if stack.size() < 1 { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } - let bn = ScriptNum(i64::try_from(stack.top(-1)?.len()).unwrap()); + let bn = + ScriptNum(stack.top(-1)?.len().try_into().map_err(|_| ScriptError::PushSize)?); stack.push_back(bn.getvch()) } - Opcode::OP_EQUAL | Opcode::OP_EQUALVERIFY => { - if stack.size() < 2 { - return Err(ScriptError::InvalidStackOperation); - } - if let Ok(vch2) = stack.pop() { - if let Ok(vch1) = stack.pop() { - let equal: bool = vch1 == vch2; - // OP_NOTEQUAL is disabled because it would be too easy to say - // something like n != 1 and have some wiseguy pass in 1 with extra - // zero bytes after it (numerically, 0x01 == 0x0001 == 0x000001) - //if (opcode == OP_NOTEQUAL) - // fEqual = !fEqual; - stack.push_back(if equal { - vch_true.clone() + + // + // Bitwise logic + // + OP_EQUAL + | OP_EQUALVERIFY + // | OP_NOTEQUAL // use OP_NUMNOTEQUAL + => { + // (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; + // OP_NOTEQUAL is disabled because it would be too easy to say + // something like n != 1 and have some wiseguy pass in 1 with extra + // zero bytes after it (numerically, 0x01 == 0x0001 == 0x000001) + //if op == OP_NOTEQUAL { + // fEqual = !fEqual; + //} + 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 { - vch_false.clone() - }); - if opcode == Opcode::OP_EQUALVERIFY { - if equal { - stack.pop()?; - } else { - return Err(ScriptError::EQUALVERIFY); - } + return set_error(ScriptError::EQUALVERIFY); } } } - } - Opcode::OP_1ADD - | Opcode::OP_1SUB - | Opcode::OP_NEGATE - | Opcode::OP_ABS - | Opcode::OP_NOT - | Opcode::OP_0NOTEQUAL => { + + + // + // Numeric + // + OP_1ADD + | OP_1SUB + | OP_NEGATE + | OP_ABS + | OP_NOT + | OP_0NOTEQUAL => { // (in -- out) if stack.size() < 1 { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } let mut bn = ScriptNum::new(stack.top(-1)?, require_minimal, None); - match opcode { - Opcode::OP_1ADD => bn = bn + bn_one, - Opcode::OP_1SUB => bn = bn - bn_one, - Opcode::OP_NEGATE => bn = -bn, - Opcode::OP_ABS => { + 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 } } - Opcode::OP_NOT => bn = ScriptNum((bn == bn_zero).into()), - Opcode::OP_0NOTEQUAL => bn = ScriptNum((bn != bn_zero).into()), + OP_NOT => bn = ScriptNum((bn == bn_zero).into()), + OP_0NOTEQUAL => bn = ScriptNum((bn != bn_zero).into()), _ => panic!("invalid opcode"), } stack.pop()?; stack.push_back(bn.getvch()) } - Opcode::OP_ADD - | Opcode::OP_SUB - | Opcode::OP_BOOLAND - | Opcode::OP_BOOLOR - | Opcode::OP_NUMEQUAL - | Opcode::OP_NUMEQUALVERIFY - | Opcode::OP_NUMNOTEQUAL - | Opcode::OP_LESSTHAN - | Opcode::OP_GREATERTHAN - | Opcode::OP_LESSTHANOREQUAL - | Opcode::OP_GREATERTHANOREQUAL - | Opcode::OP_MIN - | Opcode::OP_MAX => { + + 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 Err(ScriptError::InvalidStackOperation); + 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 opcode { - Opcode::OP_ADD => bn = bn1 + bn2, - - Opcode::OP_SUB => bn = bn1 - bn2, - - Opcode::OP_BOOLAND => { - bn = ScriptNum((bn1 != bn_zero && bn2 != bn_zero).into()) - } - Opcode::OP_BOOLOR => { - bn = ScriptNum((bn1 != bn_zero || bn2 != bn_zero).into()) - } - Opcode::OP_NUMEQUAL => bn = ScriptNum((bn1 == bn2).into()), - Opcode::OP_NUMEQUALVERIFY => bn = ScriptNum((bn1 == bn2).into()), - Opcode::OP_NUMNOTEQUAL => bn = ScriptNum((bn1 != bn2).into()), - Opcode::OP_LESSTHAN => bn = ScriptNum((bn1 < bn2).into()), - Opcode::OP_GREATERTHAN => bn = ScriptNum((bn1 > bn2).into()), - Opcode::OP_LESSTHANOREQUAL => bn = ScriptNum((bn1 <= bn2).into()), - Opcode::OP_GREATERTHANOREQUAL => bn = ScriptNum((bn1 >= bn2).into()), - Opcode::OP_MIN => bn = if bn1 < bn2 { bn1 } else { bn2 }, - Opcode::OP_MAX => bn = if bn1 > bn2 { bn1 } else { bn2 }, + match op { + OP_ADD => + bn = bn1 + bn2, + + OP_SUB => + bn = bn1 - bn2, + + OP_BOOLAND => bn = ScriptNum((bn1 != bn_zero && bn2 != bn_zero).into()), + OP_BOOLOR => bn = ScriptNum((bn1 != bn_zero || bn2 != bn_zero).into()), + OP_NUMEQUAL => bn = ScriptNum((bn1 == bn2).into()), + OP_NUMEQUALVERIFY => bn = ScriptNum((bn1 == bn2).into()), + OP_NUMNOTEQUAL => bn = ScriptNum((bn1 != bn2).into()), + OP_LESSTHAN => bn = ScriptNum((bn1 < bn2).into()), + OP_GREATERTHAN => bn = ScriptNum((bn1 > bn2).into()), + OP_LESSTHANOREQUAL => bn = ScriptNum((bn1 <= bn2).into()), + OP_GREATERTHANOREQUAL => bn = ScriptNum((bn1 >= bn2).into()), + OP_MIN => bn = if bn1 < bn2 { bn1 } else { bn2 }, + OP_MAX => bn = if bn1 > bn2 { bn1 } else { bn2 }, _ => panic!("invalid opcode"), }; stack.pop()?; stack.pop()?; stack.push_back(bn.getvch()); - if opcode == Opcode::OP_NUMEQUALVERIFY { + if op == OP_NUMEQUALVERIFY { if cast_to_bool(stack.top(-1)?) { stack.pop()?; } else { - return Err(ScriptError::NUMEQUALVERIFY); + return set_error(ScriptError::NUMEQUALVERIFY); } } } - Opcode::OP_WITHIN => { + + OP_WITHIN => { // (x min max -- out) if stack.size() < 3 { - return Err(ScriptError::InvalidStackOperation); + 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); @@ -980,37 +951,43 @@ pub fn eval_script( vch_false.clone() }) } - Opcode::OP_RIPEMD160 - | Opcode::OP_SHA1 - | Opcode::OP_SHA256 - | Opcode::OP_HASH160 - | Opcode::OP_HASH256 => { - if let Ok(vch) = stack.pop() { - stack.push_back(match opcode { - Opcode::OP_RIPEMD160 => Ripemd160::digest(vch).to_vec(), - Opcode::OP_SHA1 => { - let mut hasher = Sha1::new(); - hasher.update(vch); - hasher.finalize().to_vec() - } - Opcode::OP_SHA256 => Sha256::digest(vch).to_vec(), - Opcode::OP_HASH160 => { - Ripemd160::digest(Sha256::digest(vch)).to_vec() - } - Opcode::OP_HASH256 => { - Sha256::digest(Sha256::digest(vch)).to_vec() - } - _ => panic!("Didn’t match a hashing opcode!"), - }); - } else { - return Err(ScriptError::InvalidStackOperation); + // + // 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) } - Opcode::OP_CHECKSIG | Opcode::OP_CHECKSIGVERIFY => { + + OP_CHECKSIG + | OP_CHECKSIGVERIFY => { // (sig pubkey -- bool) if stack.size() < 2 { - return Err(ScriptError::InvalidStackOperation); + return set_error(ScriptError::InvalidStackOperation); } let vch_sig = stack.top(-2)?.clone(); @@ -1030,56 +1007,59 @@ pub fn eval_script( } else { vch_false.clone() }); - if opcode == Opcode::OP_CHECKSIGVERIFY { + if op == OP_CHECKSIGVERIFY { if success { stack.pop()?; } else { - return Err(ScriptError::CHECKSIGVERIFY); + return set_error(ScriptError::CHECKSIGVERIFY); } } } - Opcode::OP_CHECKMULTISIG | Opcode::OP_CHECKMULTISIGVERIFY => { + + OP_CHECKMULTISIG + | OP_CHECKMULTISIGVERIFY => { // ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) - let mut i: i32 = 1; - if (stack.size() as i32) < i { - return Err(ScriptError::InvalidStackOperation); + // 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: i32 = - ScriptNum::new(stack.top(-i as isize)?, require_minimal, None) - .getint(); - if keys_count < 0 || keys_count > 20 { - return Err(ScriptError::PubKeyCount); + let mut keys_count = + u8::try_from(ScriptNum::new(stack.top(-isize::from(i))?, require_minimal, None).getint()).map_err(|_| ScriptError::PubKeyCount)?; + if keys_count > 20 { + return set_error(ScriptError::PubKeyCount); }; op_count += keys_count; if op_count > 201 { - return Err(ScriptError::OpCount); + return set_error(ScriptError::OpCount); }; i += 1; let mut ikey = i; i += keys_count; - if (stack.size() as i32) < i { - return Err(ScriptError::InvalidStackOperation); + if stack.size() < i.into() { + return set_error(ScriptError::InvalidStackOperation); } - let mut sigs_count: i32 = - ScriptNum::new(stack.top(-i as isize)?, require_minimal, None) - .getint(); - if sigs_count < 0 || sigs_count > keys_count { - return Err(ScriptError::SigCount); + let mut sigs_count = + u8::try_from(ScriptNum::new(stack.top(-isize::from(i))?, require_minimal, None).getint()).map_err(|_| ScriptError::SigCount)?; + if sigs_count > keys_count { + return set_error(ScriptError::SigCount); }; i += 1; let mut isig = i; i += sigs_count; - if (stack.size() as i32) < i { - return Err(ScriptError::InvalidStackOperation); + 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(-isig as isize)?; - let vch_pub_key: &ValType = stack.top(-ikey as isize)?; + 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. @@ -1124,8 +1104,8 @@ pub fn eval_script( // so optionally verify it is exactly equal to zero prior // to removing it from the stack. if stack.size() < 1 { - return Err(ScriptError::InvalidStackOperation); - }; + return set_error(ScriptError::InvalidStackOperation); + } if flags.contains(VerificationFlags::NullDummy) && !stack.top(-1)?.is_empty() { @@ -1139,91 +1119,168 @@ pub fn eval_script( vch_false.clone() }); - if opcode == Opcode::OP_CHECKMULTISIGVERIFY { + if op == OP_CHECKMULTISIGVERIFY { if success { stack.pop()?; } else { - return Err(ScriptError::CHECKMULTISIGVERIFY); + return set_error(ScriptError::CHECKMULTISIGVERIFY); } } } + + _ => { + return set_error(ScriptError::BadOpcode); + } } } } } - // 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. + // Size limits if stack.size() + altstack.size() > 1000 { - return Err(ScriptError::StackSize); + return set_error(ScriptError::StackSize); } } if !vexec.empty() { - return Err(ScriptError::UnbalancedConditional); - }; + return set_error(ScriptError::UnbalancedConditional); + } - Ok(true) + set_success(true) +} + +/// 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]>; + +impl CallbackTransactionSignatureChecker<'_> { + pub fn verify_signature(vch_sig: &Vec, pubkey: &PubKey, sighash: &UInt256) -> bool { + pubkey.verify(sighash, vch_sig) + } +} + +impl SignatureChecker for CallbackTransactionSignatureChecker<'_> { + fn check_sig(&self, vch_sig_in: &Vec, vch_pub_key: &Vec, script_code: &Script) -> bool { + let pubkey = PubKey(vch_pub_key.as_slice()); + if !pubkey.is_valid() { + return false; + }; + + // Hash type is one byte tacked on to the end of the signature + let mut vch_sig = (*vch_sig_in).clone(); + vch_sig + .pop() + .and_then(|hash_type| { + (self.sighash)(script_code.0, HashType::from_bits_retain(hash_type.into())) + }) + .map(|sighash| Self::verify_signature(&vch_sig, &pubkey, &sighash)) + .unwrap_or(false) + } + + fn check_lock_time(&self, lock_time: &ScriptNum) -> 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.lock_time < LOCKTIME_THRESHOLD && *lock_time < LOCKTIME_THRESHOLD + || *self.lock_time >= LOCKTIME_THRESHOLD && *lock_time >= LOCKTIME_THRESHOLD) + { + false + // Now that we know we're comparing apples-to-apples, the + // comparison is a simple numeric one. + } else if lock_time > self.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 + } + } } pub fn verify_script( script_sig: &Script, script_pub_key: &Script, flags: VerificationFlags, - checker: &dyn BaseSignatureChecker, + checker: &dyn SignatureChecker, ) -> Result<(), ScriptError> { if flags.contains(VerificationFlags::SigPushOnly) && !script_sig.is_push_only() { - return Err(ScriptError::SigPushOnly); + return set_error(ScriptError::SigPushOnly); } let mut stack = Stack(Vec::new()); let mut stack_copy = Stack(Vec::new()); if !eval_script(&mut stack, script_sig, flags, checker)? { - // FIXME: `eval_script` returned `false`, but didn’t error. - return Err(ScriptError::UnknownError); + // serror is set + return set_error(ScriptError::UnknownError); } if flags.contains(VerificationFlags::P2SH) { stack_copy = stack.clone() } if !eval_script(&mut stack, script_pub_key, flags, checker)? { - // FIXME: `eval_script` returned `false`, but didn’t error. - return Err(ScriptError::UnknownError); + // serror is set + return set_error(ScriptError::UnknownError); + } + if stack.empty() { + return set_error(ScriptError::EvalFalse); } - if stack.back().map_or(true, |b| cast_to_bool(&b) == false) { - return Err(ScriptError::EvalFalse); + if !cast_to_bool(stack.back()?) { + 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() { // script_sig must be literals-only or validation fails if !script_sig.is_push_only() { - return Err(ScriptError::SigPushOnly); + return set_error(ScriptError::SigPushOnly); }; // Restore stack. - stack = stack_copy; + swap(&mut stack, &mut stack_copy); - if let Ok(pub_key_serialized) = stack.pop() { - let pub_key_2 = Script(&pub_key_serialized.as_slice()); + // 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()); - if !eval_script(&mut stack, &pub_key_2, flags, checker)? { - // FIXME: `eval_script` returned `false`, but didn’t error. - return Err(ScriptError::UnknownError); - }; - if stack.back().map_or(true, |b| cast_to_bool(&b) == false) { - return Err(ScriptError::EvalFalse); - } - } 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. - return Err(ScriptError::StackSize); + let pub_key_serialized = stack.back()?.clone(); + let pub_key_2 = Script(&pub_key_serialized.as_slice()); + stack.pop()?; + + if !eval_script(&mut stack, &pub_key_2, flags, checker)? { + // serror is set + return set_error(ScriptError::UnknownError); } - }; + if stack.empty() { + return set_error(ScriptError::EvalFalse); + } + if !cast_to_bool(stack.back()?) { + return set_error(ScriptError::EvalFalse); + } + } // The CLEANSTACK check is only performed after potential P2SH evaluation, // as the non-P2SH evaluation of a P2SH script will obviously not result in @@ -1233,9 +1290,9 @@ pub fn verify_script( // would be possible, which is not a softfork (and P2SH should be one). assert!(flags.contains(VerificationFlags::P2SH)); if stack.size() != 1 { - return Err(ScriptError::CleanStack); + return set_error(ScriptError::CleanStack); } }; - Ok(()) + set_success(()) } diff --git a/src/lib.rs b/src/lib.rs index db04001ab..4ca59ee80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,8 @@ #![doc(html_logo_url = "https://www.zfnd.org/images/zebra-icon.png")] #![doc(html_root_url = "https://docs.rs/zcash_script/0.3.0")] #![allow(unsafe_code)] +#[macro_use] +extern crate enum_primitive; mod cxx; mod external; diff --git a/src/script.rs b/src/script.rs index 344d0e6cc..d36dd0f9e 100644 --- a/src/script.rs +++ b/src/script.rs @@ -2,86 +2,74 @@ use std::ops::{Add, Neg, Sub}; +use enum_primitive::FromPrimitive; + use super::script_error::*; -/// Maximum allowed size of data (in bytes) that can be pushed to the stack. -pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520; +pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520; // bytes -/// Maximum allowed script length in bytes. +/// Maximum script length in bytes pub const MAX_SCRIPT_SIZE: usize = 10000; -// Threshold for nLockTime: below this value it is interpreted as block number, +// Threshold for lock_time: below this value it is interpreted as block number, // otherwise as UNIX timestamp. pub const LOCKTIME_THRESHOLD: ScriptNum = ScriptNum(500000000); // Tue Nov 5 00:53:20 1985 UTC -// Opcodes for pushing to the stack -const OP_0: u8 = 0x00; -const OP_PUSHDATA1: u8 = 0x4c; -const OP_PUSHDATA2: u8 = 0x4d; -const OP_PUSHDATA4: u8 = 0x4e; - -// First and last opcodes for pushing constants to the stack. OP_RESERVED is -// 0x50, which is included in this range, but it does not push anything to the -// stack and is considered invalid when it appears in an executing branch. -const OP_1NEGATE: u8 = 0x4f; -const OP_16: u8 = 0x60; - -// The first and last of the opcodes that are actually executed (with the -// exception of OP_VERIF and OP_VERNOTIF as noted next) -const OP_NOP: u8 = 0x61; -const OP_NOP10: u8 = 0xb9; - -// These opcodes are considered invalid because they appear as control flow -// opcodes (forcing their execution) but do not have a defined behavior during -// execution and so behave like unknown opcodes. -const OP_VERIF: u8 = 0x65; -const OP_VERNOTIF: u8 = 0x66; - -// Explicitly disabled opcodes -const OP_CAT: u8 = 0x7e; -const OP_SUBSTR: u8 = 0x7f; -const OP_LEFT: u8 = 0x80; -const OP_RIGHT: u8 = 0x81; -const OP_INVERT: u8 = 0x83; -const OP_AND: u8 = 0x84; -const OP_OR: u8 = 0x85; -const OP_XOR: u8 = 0x86; -const OP_2MUL: u8 = 0x8d; -const OP_2DIV: u8 = 0x8e; -const OP_MUL: u8 = 0x95; -const OP_DIV: u8 = 0x96; -const OP_MOD: u8 = 0x97; -const OP_LSHIFT: u8 = 0x98; -const OP_RSHIFT: u8 = 0x99; -const OP_CODESEPARATOR: u8 = 0xab; - +/** Script opcodes */ #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub enum Operation { - PushBytes(u8), - Constant(i8), - Opcode(Opcode), - Invalid, - Disabled, +pub enum Opcode { + PushValue(PushValue), + Operation(Operation), } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub enum Opcode { - // OP_RESERVED is technically a valid operation inside of a non-executing - // OP_IF branch +#[repr(u8)] +pub enum PushValue { + // push value + OP_0 = 0x00, + PushdataBytelength(u8), + OP_PUSHDATA1 = 0x4c, + OP_PUSHDATA2 = 0x4d, + OP_PUSHDATA4 = 0x4e, + OP_1NEGATE = 0x4f, OP_RESERVED = 0x50, + OP_1 = 0x51, + OP_2 = 0x52, + OP_3 = 0x53, + OP_4 = 0x54, + OP_5 = 0x55, + OP_6 = 0x56, + OP_7 = 0x57, + OP_8 = 0x58, + OP_9 = 0x59, + OP_10 = 0x5a, + OP_11 = 0x5b, + OP_12 = 0x5c, + OP_13 = 0x5d, + OP_14 = 0x5e, + OP_15 = 0x5f, + OP_16 = 0x60, +} - OP_NOP = 0x61, +use PushValue::*; - // OP_VER is technically a valid operation inside of a non-executing OP_IF - // branch +enum_from_primitive! { +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +#[repr(u8)] +pub enum Operation { + // control + OP_NOP = 0x61, OP_VER = 0x62, OP_IF = 0x63, OP_NOTIF = 0x64, - + OP_VERIF = 0x65, + OP_VERNOTIF = 0x66, OP_ELSE = 0x67, OP_ENDIF = 0x68, OP_VERIFY = 0x69, OP_RETURN = 0x6a, + + // stack ops OP_TOALTSTACK = 0x6b, OP_FROMALTSTACK = 0x6c, OP_2DROP = 0x6d, @@ -101,23 +89,42 @@ pub enum Opcode { OP_ROT = 0x7b, OP_SWAP = 0x7c, OP_TUCK = 0x7d, + + // splice ops + OP_CAT = 0x7e, + OP_SUBSTR = 0x7f, + OP_LEFT = 0x80, + OP_RIGHT = 0x81, OP_SIZE = 0x82, + + // bit logic + OP_INVERT = 0x83, + OP_AND = 0x84, + OP_OR = 0x85, + OP_XOR = 0x86, OP_EQUAL = 0x87, OP_EQUALVERIFY = 0x88, - - // OP_RESERVED1 and OP_RESERVED2 are technically valid in non-executing - // OP_IF branches OP_RESERVED1 = 0x89, OP_RESERVED2 = 0x8a, + // numeric OP_1ADD = 0x8b, OP_1SUB = 0x8c, + OP_2MUL = 0x8d, + OP_2DIV = 0x8e, OP_NEGATE = 0x8f, OP_ABS = 0x90, OP_NOT = 0x91, OP_0NOTEQUAL = 0x92, + OP_ADD = 0x93, OP_SUB = 0x94, + OP_MUL = 0x95, + OP_DIV = 0x96, + OP_MOD = 0x97, + OP_LSHIFT = 0x98, + OP_RSHIFT = 0x99, + OP_BOOLAND = 0x9a, OP_BOOLOR = 0x9b, OP_NUMEQUAL = 0x9c, @@ -129,21 +136,24 @@ pub enum Opcode { OP_GREATERTHANOREQUAL = 0xa2, OP_MIN = 0xa3, OP_MAX = 0xa4, + OP_WITHIN = 0xa5, + + // crypto OP_RIPEMD160 = 0xa6, OP_SHA1 = 0xa7, OP_SHA256 = 0xa8, OP_HASH160 = 0xa9, OP_HASH256 = 0xaa, + OP_CODESEPARATOR = 0xab, OP_CHECKSIG = 0xac, OP_CHECKSIGVERIFY = 0xad, OP_CHECKMULTISIG = 0xae, OP_CHECKMULTISIGVERIFY = 0xaf, - OP_NOP1 = 0xb0, - - // OP_NOP2 was renamed to OP_CHECKLOCKTIMEVERIFY - OP_CHECKLOCKTIMEVERIFY = 0xb1, + // expansion + OP_NOP1 = 0xb0, + OP_NOP2 = 0xb1, OP_NOP3 = 0xb2, OP_NOP4 = 0xb3, OP_NOP5 = 0xb4, @@ -152,129 +162,106 @@ pub enum Opcode { OP_NOP8 = 0xb7, OP_NOP9 = 0xb8, OP_NOP10 = 0xb9, + + OP_INVALIDOPCODE = 0xff, } +} + +use Operation::*; + +pub const OP_CHECKLOCKTIMEVERIFY: Operation = OP_NOP2; -impl Opcode { - /// Control flow opcodes are those between OP_IF and OP_ENDIF. Notably this - /// includes OP_VERIF and OP_VERNOTIF, which are not valid because they have - /// no implementations. - pub fn is_control_flow_opcode(&self) -> bool { - (Opcode::OP_IF) as u8 <= (*self as u8) && (*self as u8) <= (Opcode::OP_ENDIF as u8) +impl From for u8 { + fn from(value: Opcode) -> Self { + match value { + Opcode::PushValue(pv) => pv.into(), + Opcode::Operation(op) => op.into(), + } } } -pub fn get_op(script: &mut &[u8]) -> Result { - get_op2(script, None) +impl From for Opcode { + fn from(value: u8) -> Self { + Operation::from_u8(value).map_or( + PushValue::try_from(value) + .map_or(Opcode::Operation(OP_INVALIDOPCODE), Opcode::PushValue), + Opcode::Operation, + ) + } } -pub fn get_op2( - script: &mut &[u8], - mut buffer: Option<&mut Vec>, -) -> Result { - if script.is_empty() { - panic!("attempting to parse an opcode from an empty script"); +impl From for u8 { + fn from(value: PushValue) -> Self { + match value { + OP_0 => 0x00, + PushdataBytelength(byte) => byte, + OP_PUSHDATA1 => 0x4c, + OP_PUSHDATA2 => 0x4d, + OP_PUSHDATA4 => 0x4e, + OP_1NEGATE => 0x4f, + OP_RESERVED => 0x50, + OP_1 => 0x51, + OP_2 => 0x52, + OP_3 => 0x53, + OP_4 => 0x54, + OP_5 => 0x55, + OP_6 => 0x56, + OP_7 => 0x57, + OP_8 => 0x58, + OP_9 => 0x59, + OP_10 => 0x5a, + OP_11 => 0x5b, + OP_12 => 0x5c, + OP_13 => 0x5d, + OP_14 => 0x5e, + OP_15 => 0x5f, + OP_16 => 0x60, + } } +} - // Empty the provided buffer, if any - buffer.as_mut().map(|buffer| { - buffer.truncate(0); - }); - - let leading_byte = script[0]; - *script = &script[1..]; - - Ok(match leading_byte { - OP_PUSHDATA1 | OP_PUSHDATA2 | OP_PUSHDATA4 => { - let read_le = |script: &mut &[u8], needed_bytes: usize| { - if script.len() < needed_bytes { - Err(ScriptError::ReadError { - expected_bytes: needed_bytes, - available_bytes: script.len(), - }) +impl TryFrom for PushValue { + type Error = (); + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(OP_0), + 0x4c => Ok(OP_PUSHDATA1), + 0x4d => Ok(OP_PUSHDATA2), + 0x4e => Ok(OP_PUSHDATA4), + 0x4f => Ok(OP_1NEGATE), + 0x50 => Ok(OP_RESERVED), + 0x51 => Ok(OP_1), + 0x52 => Ok(OP_2), + 0x53 => Ok(OP_3), + 0x54 => Ok(OP_4), + 0x55 => Ok(OP_5), + 0x56 => Ok(OP_6), + 0x57 => Ok(OP_7), + 0x58 => Ok(OP_8), + 0x59 => Ok(OP_9), + 0x5a => Ok(OP_10), + 0x5b => Ok(OP_11), + 0x5c => Ok(OP_12), + 0x5d => Ok(OP_13), + 0x5e => Ok(OP_14), + 0x5f => Ok(OP_15), + 0x60 => Ok(OP_16), + _ => { + if value <= 0x60 { + Ok(PushdataBytelength(value)) } else { - let mut size = 0; - for i in (0..needed_bytes).rev() { - size <<= 8; - size |= script[i] as usize; - } - *script = &script[needed_bytes..]; - Ok(size) + Err(()) } - }; - - let size = match leading_byte { - OP_PUSHDATA1 => read_le(script, 1), - OP_PUSHDATA2 => read_le(script, 2), - OP_PUSHDATA4 => read_le(script, 4), - _ => unreachable!(), - }?; - - if script.len() < size { - return Err(ScriptError::ReadError { - expected_bytes: size, - available_bytes: script.len(), - }); } - - buffer.map(|buffer| { - buffer.extend(&script[0..size]); - *script = &script[size..]; - }); - - Operation::PushBytes(leading_byte) } - // OP_0/OP_FALSE doesn't actually push a constant 0 onto the stack but - // pushes an empty array. (Thus we leave the buffer truncated to 0 length) - OP_0 => Operation::PushBytes(leading_byte), - byte if byte >= 0x01 && byte <= 0x4b => { - let size = leading_byte as usize; - - if script.len() < size { - return Err(ScriptError::ReadError { - expected_bytes: size, - available_bytes: script.len(), - }); - } - - buffer.map(|buffer| { - buffer.extend(&script[0..size]); - *script = &script[size..]; - }); + } +} - Operation::PushBytes(leading_byte) - } - // OP_1NEGATE through OP_16 - byte if byte >= OP_1NEGATE && byte <= OP_16 => { - let value = byte as i8; - let value = value - 0x50; - - if value == 0 { - // This is actually OP_RESERVED (0x50) - Operation::Opcode(Opcode::OP_RESERVED) - } else { - // This is either OP_1NEGATE (-1) or one of OP_1/OP_TRUE through OP_16 - Operation::Constant(value) - } - } - // OP_NOP through OP_NOP10 - byte if byte >= OP_NOP && byte <= OP_NOP10 => { - match byte { - OP_CAT | OP_SUBSTR | OP_LEFT | OP_RIGHT | OP_INVERT | OP_AND | OP_OR | OP_XOR - | OP_2MUL | OP_2DIV | OP_MUL | OP_DIV | OP_MOD | OP_LSHIFT | OP_RSHIFT - | OP_CODESEPARATOR => Operation::Disabled, - OP_VERIF | OP_VERNOTIF => Operation::Invalid, - _ => { - let opcode: Opcode = unsafe { - // Safety: between OP_NOP and OP_NOP10, 8-bit opcode descriminants - // are defined (except for the opcodes above which we account for) - core::mem::transmute(byte) - }; - Operation::Opcode(opcode) - } - } - } - _ => Operation::Invalid, - }) +impl From for u8 { + fn from(value: Operation) -> Self { + // This is how you get the discriminant, but using `as` everywhere is too much code smell + value as u8 + } } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] @@ -288,36 +275,34 @@ impl ScriptNum { if vch.len() > max_num_size { panic!("script number overflow"); }; - if require_minimal { - if let Some(vch_back) = vch.last() { - // Check that the number is encoded with the minimum possible - // number of bytes. - // - // If the most-significant-byte - excluding the sign bit - is zero - // then we're not minimal. Note how this test also rejects the - // negative-zero encoding, 0x80. - if (vch_back & 0x7F) == 0 { - // One exception: if there's more than one byte and the most - // significant bit of the second-most-significant-byte is set - // it would conflict with the sign bit. An example of this case - // is +-255, which encode to 0xff00 and 0xff80 respectively. - // (big-endian). - if vch.len() <= 1 || (vch[vch.len() - 2] & 0x80) == 0 { - panic!("non-minimally encoded script number"); - } + if require_minimal && vch.len() > 0 { + // Check that the number is encoded with the minimum possible + // number of bytes. + // + // If the most-significant-byte - excluding the sign bit - is zero + // then we're not minimal. Note how this test also rejects the + // negative-zero encoding, 0x80. + if (vch.last().unwrap_or_else(|| unreachable!()) & 0x7F) == 0 { + // One exception: if there's more than one byte and the most + // significant bit of the second-most-significant-byte is set + // it would conflict with the sign bit. An example of this case + // is +-255, which encode to 0xff00 and 0xff80 respectively. + // (big-endian). + if vch.len() <= 1 || (vch[vch.len() - 2] & 0x80) == 0 { + panic!("non-minimally encoded script number"); } } - }; + } ScriptNum(Self::set_vch(vch)) } pub fn getint(&self) -> i32 { - if self.0 > i32::MAX as i64 { + if self.0 > i32::MAX.into() { i32::MAX - } else if self.0 < i32::MIN as i64 { + } else if self.0 < i32::MIN.into() { i32::MIN } else { - self.0 as i32 + self.0.try_into().unwrap() } } @@ -340,10 +325,14 @@ impl ScriptNum { let mut result = Vec::new(); let neg = *value < 0; - let mut absvalue: u64 = value.abs() as u64; + let mut absvalue = value.abs(); while absvalue != 0 { - result.push((absvalue & 0xff) as u8); + result.push( + (absvalue & 0xff) + .try_into() + .unwrap_or_else(|_| unreachable!()), + ); absvalue >>= 8; } @@ -372,7 +361,7 @@ impl ScriptNum { match vch.last() { None => 0, Some(vch_back) => { - if *vch == vec![0 as u8, 0, 0, 0, 0, 0, 0, 128, 128] { + if *vch == vec![0, 0, 0, 0, 0, 0, 0, 128, 128] { // On an x86_64 system, the code below would actually decode the buggy // INT64_MIN encoding correctly. However in this case, it would be // performing left shifts of a signed type by 64, which has undefined @@ -439,10 +428,98 @@ impl Neg for ScriptNum { } } +/** Serialized script, used inside transaction inputs and outputs */ #[derive(Clone, Debug)] pub struct Script<'a>(pub &'a [u8]); impl<'a> Script<'a> { + pub fn get_op(script: &mut &[u8]) -> Result { + Self::get_op2(script, &mut vec![]) + } + + pub fn get_op2(script: &mut &[u8], buffer: &mut Vec) -> Result { + if script.is_empty() { + panic!("attempting to parse an opcode from an empty script"); + } + + // Empty the provided buffer, if any + buffer.truncate(0); + + let leading_byte = Opcode::from(script[0]); + *script = &script[1..]; + + Ok(match leading_byte { + Opcode::PushValue(pv) => match pv { + OP_PUSHDATA1 | OP_PUSHDATA2 | OP_PUSHDATA4 => { + let read_le = |script: &mut &[u8], needed_bytes: usize| { + if script.len() < needed_bytes { + Err(ScriptError::ReadError { + expected_bytes: needed_bytes, + available_bytes: script.len(), + }) + } else { + let mut size = 0; + for i in (0..needed_bytes).rev() { + size <<= 8; + size |= usize::from(script[i]); + } + *script = &script[needed_bytes..]; + Ok(size) + } + }; + + let size = match pv { + OP_PUSHDATA1 => read_le(script, 1), + OP_PUSHDATA2 => read_le(script, 2), + OP_PUSHDATA4 => read_le(script, 4), + _ => unreachable!(), + }?; + + if script.len() < size { + return Err(ScriptError::ReadError { + expected_bytes: size, + available_bytes: script.len(), + }); + } + + buffer.extend(&script[0..size]); + *script = &script[size..]; + + leading_byte + } + // OP_0/OP_FALSE doesn't actually push a constant 0 onto the stack but + // pushes an empty array. (Thus we leave the buffer truncated to 0 length) + OP_0 => leading_byte, + PushdataBytelength(size_byte) => { + let size = size_byte.into(); + + if script.len() < size { + return Err(ScriptError::ReadError { + expected_bytes: size, + available_bytes: script.len(), + }); + } + + buffer.extend(&script[0..size]); + *script = &script[size..]; + + leading_byte + } + _ => leading_byte, + }, + _ => leading_byte, + }) + } + + /** Encode/decode small integers: */ + pub fn decode_op_n(opcode: PushValue) -> u32 { + if opcode == OP_0 { + return 0; + } + assert!(opcode >= OP_1 && opcode <= OP_16); + (u8::from(opcode) - (u8::from(OP_1) - 1)).into() + } + /// Pre-version-0.6, Bitcoin always counted CHECKMULTISIGs /// as 20 sigops. With pay-to-script-hash, that changed: /// CHECKMULTISIGs serialized in script_sigs are @@ -451,66 +528,67 @@ impl<'a> Script<'a> { pub fn get_sig_op_count(&self, accurate: bool) -> u32 { let mut n = 0; let mut pc = self.0; - let mut last_opcode = Operation::Invalid; + let mut last_opcode = Opcode::Operation(OP_INVALIDOPCODE); while !pc.is_empty() { - get_op(&mut pc).map_or((), |opcode| { - match opcode { - Operation::Opcode(Opcode::OP_CHECKSIG | Opcode::OP_CHECKSIGVERIFY) => n = n + 1, - Operation::Opcode( - Opcode::OP_CHECKMULTISIG | Opcode::OP_CHECKMULTISIGVERIFY, - ) => { - if accurate { - match last_opcode { - Operation::Constant(n_op) => { - if n_op >= 1 && n_op <= 16 { - n = n + n_op as u32 - } else { - n = n + 200 - } + let opcode = match Self::get_op(&mut pc) { + Ok(o) => o, + Err(_) => break, + }; + match opcode { + Opcode::Operation(op) => { + if op == OP_CHECKSIG || op == OP_CHECKSIGVERIFY { + n += 1; + } else if op == OP_CHECKMULTISIG || op == OP_CHECKMULTISIGVERIFY { + match last_opcode { + Opcode::PushValue(pv) => { + if accurate && pv >= OP_1 && pv <= OP_16 { + n += Self::decode_op_n(pv); + } else { + n += 20 } - _ => n = n + 200, } - } else { - n = n + 200 + _ => n += 20, } } - _ => (), - }; - last_opcode = opcode; - }) + } + _ => (), + } + last_opcode = opcode; } n } /// Returns true iff this script is P2PKH. pub fn is_pay_to_public_key_hash(&self) -> bool { - (self.0.len() == 25) - && (self.0[0] == Opcode::OP_DUP as u8) - && (self.0[1] == Opcode::OP_HASH160 as u8) - && (self.0[2] == 0x14) - && (self.0[23] == Opcode::OP_EQUALVERIFY as u8) - && (self.0[24] == Opcode::OP_CHECKSIG as u8) + self.0.len() == 25 + && self.0[0] == OP_DUP.into() + && self.0[1] == OP_HASH160.into() + && self.0[2] == 0x14 + && self.0[23] == OP_EQUALVERIFY.into() + && self.0[24] == OP_CHECKSIG.into() } /// Returns true iff this script is P2SH. pub fn is_pay_to_script_hash(&self) -> bool { - (self.0.len() == 23) - && (self.0[0] == Opcode::OP_HASH160 as u8) - && (self.0[1] == 0x14) - && (self.0[22] == Opcode::OP_EQUAL as u8) + self.0.len() == 23 + && self.0[0] == OP_HASH160.into() + && self.0[1] == 0x14 + && self.0[22] == OP_EQUAL.into() } - /// Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). + /// Called by `IsStandardTx` and P2SH/BIP62 VerifyScript (which makes it consensus-critical). pub fn is_push_only(&self) -> bool { - // let mut pc = self.clone(); - // while !pc.0.is_empty() { - // if !get_op(&mut pc.0).map_or(false, |opcode| match opcode { - // Operation::PushBytes(_) | Operation::Constant(_) => true, - // _ => false, - // }) { - // return false; - // }; - // }; - return true; + let mut pc = self.0; + while !pc.is_empty() { + if let Ok(opcode) = Self::get_op(&mut pc) { + match opcode { + Opcode::PushValue(_) => (), + _ => return false, + } + } else { + return false; + } + } + true } } From 03291177db99888db9d856b0b7362f540a69b6f1 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Fri, 27 Sep 2024 01:00:35 -0600 Subject: [PATCH 08/12] Appease Clippy --- src/external/pubkey.rs | 14 +++---- src/interpreter.rs | 92 +++++++++++++++++++----------------------- src/lib.rs | 4 +- src/script.rs | 43 ++++++-------------- src/script_error.rs | 20 ++++----- 5 files changed, 74 insertions(+), 99 deletions(-) diff --git a/src/external/pubkey.rs b/src/external/pubkey.rs index 4dad8b95c..b13c784df 100644 --- a/src/external/pubkey.rs +++ b/src/external/pubkey.rs @@ -14,19 +14,19 @@ impl PubKey<'_> { /// /// Note that this is consensus critical as CheckSig() calls it! pub fn is_valid(&self) -> bool { - self.0.len() > 0 + !self.0.is_empty() } /// 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: &Vec) -> bool { + pub fn verify(&self, hash: &UInt256, vch_sig: &[u8]) -> bool { if !self.is_valid() { return false; }; if let Ok(pubkey) = PublicKey::from_slice(self.0) { // let sig: secp256k1_ecdsa_signature; - if vch_sig.len() == 0 { + if vch_sig.is_empty() { return false; }; // Zcash, unlike Bitcoin, has always enforced strict DER signatures. @@ -38,17 +38,17 @@ impl PubKey<'_> { secp.verify_ecdsa(&Message::from_digest(*hash), &sig, &pubkey) .is_ok() } else { - return false; + false } } else { - return false; + false } } - pub fn check_low_s(vch_sig: &Vec) -> bool { + pub fn check_low_s(vch_sig: &[u8]) -> bool { /* Zcash, unlike Bitcoin, has always enforced strict DER signatures. */ if let Ok(sig) = ecdsa::Signature::from_der(vch_sig) { - let mut check = sig.clone(); + let mut check = sig; check.normalize_s(); sig == check } else { diff --git a/src/interpreter.rs b/src/interpreter.rs index f5c159262..a851a193a 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -83,12 +83,7 @@ bitflags::bitflags! { } pub trait SignatureChecker { - fn check_sig( - &self, - _script_sig: &Vec, - _vch_pub_key: &Vec, - _script_code: &Script, - ) -> bool { + fn check_sig(&self, _script_sig: &[u8], _vch_pub_key: &[u8], _script_code: &Script) -> bool { false } @@ -140,21 +135,22 @@ 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 { - fn from_top(&self, i: isize) -> Result { + fn reverse_index(&self, i: isize) -> Result { usize::try_from(-i) .map(|a| self.0.len() - a) .map_err(|_| ScriptError::InvalidStackOperation) } pub fn top(&self, i: isize) -> Result<&T, ScriptError> { - let idx = self.from_top(i)?; + let idx = self.reverse_index(i)?; self.0.get(idx).ok_or(ScriptError::InvalidStackOperation) } pub fn swap(&mut self, a: isize, b: isize) -> Result<(), ScriptError> { - let au = self.from_top(a)?; - let bu = self.from_top(b)?; - Ok(self.0.swap(au, bu)) + let au = self.reverse_index(a)?; + let bu = self.reverse_index(b)?; + self.0.swap(au, bu); + Ok(()) } pub fn pop(&mut self) -> Result { @@ -228,7 +224,7 @@ fn is_compressed_or_uncompressed_pub_key(vch_pub_key: &ValType) -> bool { * * This function is consensus-critical since BIP66. */ -fn is_valid_signature_encoding(sig: &Vec) -> bool { +fn is_valid_signature_encoding(sig: &[u8]) -> bool { // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash] // * total-length: 1-byte length descriptor of everything that follows, // excluding the sighash byte. @@ -338,7 +334,7 @@ fn is_low_der_signature(vch_sig: &ValType) -> Result { } fn is_defined_hashtype_signature(vch_sig: &ValType) -> bool { - if vch_sig.len() == 0 { + if vch_sig.is_empty() { return false; }; let hash_type = i32::from(vch_sig[vch_sig.len() - 1]) & !HashType::AnyoneCanPay.bits(); @@ -355,7 +351,7 @@ fn check_signature_encoding( ) -> Result { // Empty signature. Not strictly DER encoded, but allowed to provide a // compact way to provide an invalid signature for use with CHECK(MULTI)SIG - if vch_sig.len() == 0 { + if vch_sig.is_empty() { return Ok(true); }; if !is_valid_signature_encoding(vch_sig) { @@ -366,7 +362,7 @@ fn check_signature_encoding( } else if flags.contains(VerificationFlags::StrictEnc) && !is_defined_hashtype_signature(vch_sig) { - return set_error(ScriptError::SigHashtype); + return set_error(ScriptError::SigHashType); }; Ok(true) } @@ -381,7 +377,7 @@ fn check_pub_key_encoding(vch_sig: &ValType, flags: VerificationFlags) -> Result } fn check_minimal_push(data: &ValType, opcode: PushValue) -> bool { - if data.len() == 0 { + if data.is_empty() { // Could have used OP_0. return opcode == OP_0; } else if data.len() == 1 && data[0] >= 1 && data[0] <= 16 { @@ -608,7 +604,7 @@ pub fn eval_script( if value { stack.pop()?; } else { - return set_error(ScriptError::VERIFY); + return set_error(ScriptError::Verify); } } @@ -843,7 +839,7 @@ pub fn eval_script( if equal { stack.pop()?; } else { - return set_error(ScriptError::EQUALVERIFY); + return set_error(ScriptError::EqualVerify); } } } @@ -899,25 +895,24 @@ pub fn eval_script( } let bn1 = ScriptNum::new(stack.top(-2)?, require_minimal, None); let bn2 = ScriptNum::new(stack.top(-1)?, require_minimal, None); - let bn; - match op { + let bn = match op { OP_ADD => - bn = bn1 + bn2, + bn1 + bn2, OP_SUB => - bn = bn1 - bn2, - - OP_BOOLAND => bn = ScriptNum((bn1 != bn_zero && bn2 != bn_zero).into()), - OP_BOOLOR => bn = ScriptNum((bn1 != bn_zero || bn2 != bn_zero).into()), - OP_NUMEQUAL => bn = ScriptNum((bn1 == bn2).into()), - OP_NUMEQUALVERIFY => bn = ScriptNum((bn1 == bn2).into()), - OP_NUMNOTEQUAL => bn = ScriptNum((bn1 != bn2).into()), - OP_LESSTHAN => bn = ScriptNum((bn1 < bn2).into()), - OP_GREATERTHAN => bn = ScriptNum((bn1 > bn2).into()), - OP_LESSTHANOREQUAL => bn = ScriptNum((bn1 <= bn2).into()), - OP_GREATERTHANOREQUAL => bn = ScriptNum((bn1 >= bn2).into()), - OP_MIN => bn = if bn1 < bn2 { bn1 } else { bn2 }, - OP_MAX => bn = if bn1 > bn2 { bn1 } else { bn2 }, + bn1 - bn2, + + OP_BOOLAND => ScriptNum((bn1 != bn_zero && bn2 != bn_zero).into()), + OP_BOOLOR => ScriptNum((bn1 != bn_zero || bn2 != bn_zero).into()), + OP_NUMEQUAL => ScriptNum((bn1 == bn2).into()), + OP_NUMEQUALVERIFY => ScriptNum((bn1 == bn2).into()), + OP_NUMNOTEQUAL => ScriptNum((bn1 != bn2).into()), + OP_LESSTHAN => ScriptNum((bn1 < bn2).into()), + OP_GREATERTHAN => ScriptNum((bn1 > bn2).into()), + OP_LESSTHANOREQUAL => ScriptNum((bn1 <= bn2).into()), + OP_GREATERTHANOREQUAL => ScriptNum((bn1 >= bn2).into()), + OP_MIN => if bn1 < bn2 { bn1 } else { bn2 }, + OP_MAX => if bn1 > bn2 { bn1 } else { bn2 }, _ => panic!("invalid opcode"), }; stack.pop()?; @@ -928,7 +923,7 @@ pub fn eval_script( if cast_to_bool(stack.top(-1)?) { stack.pop()?; } else { - return set_error(ScriptError::NUMEQUALVERIFY); + return set_error(ScriptError::NumEqualVerify); } } } @@ -1011,7 +1006,7 @@ pub fn eval_script( if success { stack.pop()?; } else { - return set_error(ScriptError::CHECKSIGVERIFY); + return set_error(ScriptError::CheckSigVerify); } } } @@ -1123,7 +1118,7 @@ pub fn eval_script( if success { stack.pop()?; } else { - return set_error(ScriptError::CHECKMULTISIGVERIFY); + return set_error(ScriptError::CheckMultisigVerify); } } } @@ -1164,20 +1159,20 @@ pub const SIGHASH_SIZE: usize = 32; pub type SighashCalculator<'a> = &'a dyn Fn(&[u8], HashType) -> Option<[u8; SIGHASH_SIZE]>; impl CallbackTransactionSignatureChecker<'_> { - pub fn verify_signature(vch_sig: &Vec, pubkey: &PubKey, sighash: &UInt256) -> bool { + pub fn verify_signature(vch_sig: &[u8], pubkey: &PubKey, sighash: &UInt256) -> bool { pubkey.verify(sighash, vch_sig) } } impl SignatureChecker for CallbackTransactionSignatureChecker<'_> { - fn check_sig(&self, vch_sig_in: &Vec, vch_pub_key: &Vec, script_code: &Script) -> bool { - let pubkey = PubKey(vch_pub_key.as_slice()); + fn check_sig(&self, vch_sig_in: &[u8], vch_pub_key: &[u8], script_code: &Script) -> bool { + let pubkey = PubKey(vch_pub_key); if !pubkey.is_valid() { return false; }; // Hash type is one byte tacked on to the end of the signature - let mut vch_sig = (*vch_sig_in).clone(); + let mut vch_sig = vch_sig_in.to_vec(); vch_sig .pop() .and_then(|hash_type| { @@ -1195,13 +1190,12 @@ 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) - { - false + 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. - } else if lock_time > self.lock_time { + || lock_time > self.lock_time + { false // Finally the nLockTime feature can be disabled and thus // CHECKLOCKTIMEVERIFY bypassed if every txin has been @@ -1213,10 +1207,8 @@ impl SignatureChecker for CallbackTransactionSignatureChecker<'_> { // 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 + !self.is_final } } } @@ -1267,7 +1259,7 @@ pub fn verify_script( assert!(!stack.empty()); let pub_key_serialized = stack.back()?.clone(); - let pub_key_2 = Script(&pub_key_serialized.as_slice()); + let pub_key_2 = Script(pub_key_serialized.as_slice()); stack.pop()?; if !eval_script(&mut stack, &pub_key_2, flags, checker)? { diff --git a/src/lib.rs b/src/lib.rs index 4ca59ee80..29b55f9b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -218,7 +218,7 @@ mod tests { .unwrap() .as_slice() .first_chunk::<32>() - .map(|hash| *hash) + .copied() } fn invalid_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> { @@ -226,7 +226,7 @@ mod tests { .unwrap() .as_slice() .first_chunk::<32>() - .map(|hash| *hash) + .copied() } fn missing_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> { diff --git a/src/script.rs b/src/script.rs index d36dd0f9e..cfe1e9082 100644 --- a/src/script.rs +++ b/src/script.rs @@ -275,7 +275,7 @@ impl ScriptNum { if vch.len() > max_num_size { panic!("script number overflow"); }; - if require_minimal && vch.len() > 0 { + if require_minimal && !vch.is_empty() { // Check that the number is encoded with the minimum possible // number of bytes. // @@ -534,40 +534,27 @@ impl<'a> Script<'a> { Ok(o) => o, Err(_) => break, }; - match opcode { - Opcode::Operation(op) => { - if op == OP_CHECKSIG || op == OP_CHECKSIGVERIFY { - n += 1; - } else if op == OP_CHECKMULTISIG || op == OP_CHECKMULTISIGVERIFY { - match last_opcode { - Opcode::PushValue(pv) => { - if accurate && pv >= OP_1 && pv <= OP_16 { - n += Self::decode_op_n(pv); - } else { - n += 20 - } + if let Opcode::Operation(op) = opcode { + if op == OP_CHECKSIG || op == OP_CHECKSIGVERIFY { + n += 1; + } else if op == OP_CHECKMULTISIG || op == OP_CHECKMULTISIGVERIFY { + match last_opcode { + Opcode::PushValue(pv) => { + if accurate && pv >= OP_1 && pv <= OP_16 { + n += Self::decode_op_n(pv); + } else { + n += 20 } - _ => n += 20, } + _ => n += 20, } } - _ => (), } last_opcode = opcode; } n } - /// Returns true iff this script is P2PKH. - pub fn is_pay_to_public_key_hash(&self) -> bool { - self.0.len() == 25 - && self.0[0] == OP_DUP.into() - && self.0[1] == OP_HASH160.into() - && self.0[2] == 0x14 - && self.0[23] == OP_EQUALVERIFY.into() - && self.0[24] == OP_CHECKSIG.into() - } - /// Returns true iff this script is P2SH. pub fn is_pay_to_script_hash(&self) -> bool { self.0.len() == 23 @@ -580,11 +567,7 @@ impl<'a> Script<'a> { pub fn is_push_only(&self) -> bool { let mut pc = self.0; while !pc.is_empty() { - if let Ok(opcode) = Self::get_op(&mut pc) { - match opcode { - Opcode::PushValue(_) => (), - _ => return false, - } + if let Ok(Opcode::PushValue(_)) = Self::get_op(&mut pc) { } else { return false; } diff --git a/src/script_error.rs b/src/script_error.rs index 8f3e85ed6..42b3c5c27 100644 --- a/src/script_error.rs +++ b/src/script_error.rs @@ -1,8 +1,8 @@ #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[repr(i32)] pub enum ScriptError { - Ok = 0, - UnknownError, + // Ok = 0, + UnknownError = 1, EvalFalse, OpReturn, @@ -15,11 +15,11 @@ pub enum ScriptError { PubKeyCount, // Failed verify operations - VERIFY, - EQUALVERIFY, - CHECKMULTISIGVERIFY, - CHECKSIGVERIFY, - NUMEQUALVERIFY, + Verify, + EqualVerify, + CheckMultisigVerify, + CheckSigVerify, + NumEqualVerify, // Logical/Format/Canonical errors BadOpcode, @@ -33,12 +33,12 @@ pub enum ScriptError { UnsatisfiedLockTime, // BIP62 - SigHashtype, + SigHashType, SigDER, MinimalData, SigPushOnly, - SigHighS, - SigNullDummy, + // SigHighS, + SigNullDummy = 27, PubKeyType, CleanStack, From ebca9819755df3bfb929ded8c84661c463e47d03 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Mon, 21 Oct 2024 11:34:02 -0600 Subject: [PATCH 09/12] Replace `ScriptNum` panics with error case The C++ impl uses exceptions for `ScriptNum`, but catches them. --- src/interpreter.rs | 32 ++++++++++++++++++++++---------- src/script.rs | 36 ++++++++++++++++++++++++------------ src/script_error.rs | 10 ++++++++++ 3 files changed, 56 insertions(+), 22 deletions(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index a851a193a..30e3a44da 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -536,7 +536,8 @@ pub fn eval_script( // 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)); + ScriptNum::new(stack.top(-1)?, require_minimal, Some(5)) + .map_err(ScriptError::ScriptNumError)?; // In the rare event that the argument may be < 0 due to // some arithmetic being done first, you can always use @@ -756,7 +757,8 @@ pub fn eval_script( return set_error(ScriptError::InvalidStackOperation); } let n = - u16::try_from(ScriptNum::new(stack.top(-1)?, require_minimal, None).getint()) + u16::try_from(ScriptNum::new(stack.top(-1)?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?.getint()) .map_err(|_| ScriptError::InvalidStackOperation)?; stack.pop()?; if usize::from(n) >= stack.size() { @@ -858,7 +860,8 @@ pub fn eval_script( if stack.size() < 1 { return set_error(ScriptError::InvalidStackOperation); } - let mut bn = ScriptNum::new(stack.top(-1)?, require_minimal, None); + let mut bn = ScriptNum::new(stack.top(-1)?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?; match op { OP_1ADD => bn = bn + bn_one, OP_1SUB => bn = bn - bn_one, @@ -893,8 +896,10 @@ pub fn eval_script( 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 bn1 = ScriptNum::new(stack.top(-2)?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?; + let bn2 = ScriptNum::new(stack.top(-1)?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?; let bn = match op { OP_ADD => bn1 + bn2, @@ -933,9 +938,12 @@ pub fn eval_script( 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 bn1 = ScriptNum::new(stack.top(-3)?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?; + let bn2 = ScriptNum::new(stack.top(-2)?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?; + let bn3 = ScriptNum::new(stack.top(-1)?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?; let value = bn2 <= bn1 && bn1 < bn3; stack.pop()?; stack.pop()?; @@ -1024,7 +1032,9 @@ pub fn eval_script( }; let mut keys_count = - u8::try_from(ScriptNum::new(stack.top(-isize::from(i))?, require_minimal, None).getint()).map_err(|_| ScriptError::PubKeyCount)?; + u8::try_from(ScriptNum::new(stack.top(-isize::from(i))?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?.getint()) + .map_err(|_| ScriptError::PubKeyCount)?; if keys_count > 20 { return set_error(ScriptError::PubKeyCount); }; @@ -1040,7 +1050,9 @@ pub fn eval_script( } let mut sigs_count = - u8::try_from(ScriptNum::new(stack.top(-isize::from(i))?, require_minimal, None).getint()).map_err(|_| ScriptError::SigCount)?; + u8::try_from(ScriptNum::new(stack.top(-isize::from(i))?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?.getint()) + .map_err(|_| ScriptError::SigCount)?; if sigs_count > keys_count { return set_error(ScriptError::SigCount); }; diff --git a/src/script.rs b/src/script.rs index cfe1e9082..cb3149e3c 100644 --- a/src/script.rs +++ b/src/script.rs @@ -270,11 +270,18 @@ pub struct ScriptNum(pub i64); impl ScriptNum { const DEFAULT_MAX_NUM_SIZE: usize = 4; - pub fn new(vch: &Vec, require_minimal: bool, max_num_size: Option) -> Self { + pub fn new( + vch: &Vec, + require_minimal: bool, + max_num_size: Option, + ) -> Result { let max_num_size = max_num_size.unwrap_or(Self::DEFAULT_MAX_NUM_SIZE); if vch.len() > max_num_size { - panic!("script number overflow"); - }; + return Err(ScriptNumError::Overflow { + max_num_size, + actual: vch.len(), + }); + } if require_minimal && !vch.is_empty() { // Check that the number is encoded with the minimum possible // number of bytes. @@ -288,12 +295,14 @@ impl ScriptNum { // it would conflict with the sign bit. An example of this case // is +-255, which encode to 0xff00 and 0xff80 respectively. // (big-endian). - if vch.len() <= 1 || (vch[vch.len() - 2] & 0x80) == 0 { - panic!("non-minimally encoded script number"); + if vch.len() <= 1 { + return Err(ScriptNumError::NegativeZero); + } else if (vch[vch.len() - 2] & 0x80) == 0 { + return Err(ScriptNumError::NonMinimalEncoding); } } } - ScriptNum(Self::set_vch(vch)) + Self::set_vch(vch).map(ScriptNum) } pub fn getint(&self) -> i32 { @@ -357,21 +366,24 @@ impl ScriptNum { result } - fn set_vch(vch: &Vec) -> i64 { + fn set_vch(vch: &Vec) -> Result { match vch.last() { - None => 0, + None => Ok(0), Some(vch_back) => { if *vch == vec![0, 0, 0, 0, 0, 0, 0, 128, 128] { // On an x86_64 system, the code below would actually decode the buggy // INT64_MIN encoding correctly. However in this case, it would be // performing left shifts of a signed type by 64, which has undefined // behavior. - return i64::MIN; + return Ok(i64::MIN); }; // Guard against undefined behavior. INT64_MIN is the only allowed 9-byte encoding. if vch.len() > 8 { - panic!("script number overflow"); + return Err(ScriptNumError::Overflow { + max_num_size: 8, + actual: vch.len(), + }); }; let mut result: i64 = 0; @@ -382,10 +394,10 @@ impl ScriptNum { // If the input vector's most significant byte is 0x80, remove it from // the result's msb and return a negative. if vch_back & 0x80 != 0 { - return -(result & !(0x80 << (8 * (vch.len() - 1)))); + return Ok(-(result & !(0x80 << (8 * (vch.len() - 1))))); }; - result + Ok(result) } } } diff --git a/src/script_error.rs b/src/script_error.rs index 42b3c5c27..4aa8e039e 100644 --- a/src/script_error.rs +++ b/src/script_error.rs @@ -1,3 +1,10 @@ +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum ScriptNumError { + NegativeZero, + NonMinimalEncoding, + Overflow { max_num_size: usize, actual: usize }, +} + #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[repr(i32)] pub enum ScriptError { @@ -49,4 +56,7 @@ pub enum ScriptError { expected_bytes: usize, available_bytes: usize, }, + + /// Corresponds to the `scriptnum_error` exception in C++. + ScriptNumError(ScriptNumError), } From d9d46cdabd87861498d67a952de3a880074bcf4a Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Mon, 7 Oct 2024 13:17:38 -0600 Subject: [PATCH 10/12] Add some shallow property tests These tests run both the C++ and Rust impls. One uses completely arbitrary inputs, and the other limits script_sig to data pushes. --- Cargo.lock | 378 ++++++++++++++++++++++++++++++++++++++++++++++++----- Cargo.toml | 1 + src/lib.rs | 73 ++++++++++- 3 files changed, 420 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index accfe071e..4f05631ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "autocfg" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.3.0", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -23,7 +32,7 @@ version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cexpr", "clang-sys", "itertools", @@ -40,6 +49,27 @@ dependencies = [ "which", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -55,6 +85,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cc" version = "1.0.95" @@ -92,6 +128,15 @@ dependencies = [ "libloading", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "cpufeatures" version = "0.2.13" @@ -138,24 +183,31 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "fastrand" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "generic-array" @@ -185,7 +237,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -236,9 +288,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.7" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" @@ -283,7 +335,7 @@ version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "autocfg", + "autocfg 1.3.0", ] [[package]] @@ -311,6 +363,32 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c477819b845fe023d33583ebf10c9f62518c8d79a0960ba5c36d6ac8a55a5b" +dependencies = [ + "bit-set", + "bitflags 1.3.2", + "byteorder", + "lazy_static", + "num-traits 0.2.19", + "quick-error", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.6.29", + "rusty-fork", + "tempfile", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.36" @@ -320,6 +398,121 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.8", + "libc", + "rand_chacha", + "rand_core 0.4.2", + "rand_hc", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.3.1", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "regex" version = "1.9.5" @@ -329,7 +522,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax", + "regex-syntax 0.7.5", ] [[package]] @@ -340,9 +533,15 @@ checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.5", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.7.5" @@ -366,15 +565,27 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.13" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rusty-fork" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dd93264e10c577503e926bd1430193eeb5d21b059148910082245309b424fae" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", ] [[package]] @@ -434,6 +645,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "typenum" version = "1.17.0" @@ -452,6 +676,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "which" version = "4.4.2" @@ -492,7 +725,25 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -501,13 +752,29 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -516,53 +783,102 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "zcash_script" version = "0.2.0" dependencies = [ "bindgen", - "bitflags", + "bitflags 2.6.0", "cc", "enum_primitive", "hex", "lazy_static", "log", + "proptest", "ripemd", "secp256k1", "sha-1", diff --git a/Cargo.toml b/Cargo.toml index aad6ce99b..1a01276ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ rust-interpreter = [] bitflags = "2.5" enum_primitive = "0.1" log = "0.4" +proptest = "0.9" ripemd = "0.1" secp256k1 = "0.29" sha-1 = "0.10" diff --git a/src/lib.rs b/src/lib.rs index 29b55f9b6..42b6bb1ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -203,10 +203,31 @@ impl ZcashScript for (T, U) { } } +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use super::*; + + /// Ensures that flags represent a supported state. This avoids crashes in the C++ code, which + /// break various tests. + pub fn repair_flags(flags: VerificationFlags) -> VerificationFlags { + // TODO: The C++ implementation fails an assert (interpreter.cpp:1097) if `CleanStack` is + // set without `P2SH`. + if flags.contains(VerificationFlags::CleanStack) { + flags & VerificationFlags::P2SH + } else { + flags + } + } + + /// A `usize` one larger than the longest allowed script, for testing bounds. + pub const OVERFLOW_SCRIPT_SIZE: usize = script::MAX_SCRIPT_SIZE + 1; +} + #[cfg(test)] mod tests { - pub use super::*; + use super::{testing::*, *}; use hex::FromHex; + use proptest::prelude::*; lazy_static::lazy_static! { pub static ref SCRIPT_PUBKEY: Vec = >::from_hex("a914c117756dcbe144a12a7c33a77cfa81aa5aeeb38187").unwrap(); @@ -295,4 +316,54 @@ mod tests { assert_eq!(ret.0, ret.1); assert_eq!(ret.0, Err(Error::Ok)); } + + proptest! { + #![proptest_config(ProptestConfig { + cases: 20_000, .. ProptestConfig::default() + })] + + /// This test is very shallow, because we have only `()` for success and most errors have + /// been collapsed to `Error::Ok`. A deeper comparison, requires changes to the C++ code. + #[test] + fn test_arbitrary_scripts( + lock_time in prop::num::i64::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 ret = check_verify_callback::( + &missing_sighash, + lock_time, + is_final, + &pub_key[..], + &sig[..], + repair_flags(VerificationFlags::from_bits_truncate(flags)), + ); + prop_assert_eq!(ret.0, ret.1); + } + + /// Similar to `test_arbitrary_scripts`, but ensures the `sig` only contains pushes. + #[test] + fn test_restricted_sig_scripts( + lock_time in prop::num::i64::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 ret = check_verify_callback::( + &missing_sighash, + lock_time, + is_final, + &pub_key[..], + &sig[..], + repair_flags(VerificationFlags::from_bits_truncate(flags)) + | VerificationFlags::SigPushOnly, + ); + prop_assert_eq!(ret.0, ret.1); + } + } } From a16d6adbb24f3898fc03cfb2abf055784bc7671c Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Tue, 15 Oct 2024 12:24:22 -0600 Subject: [PATCH 11/12] Add shallow fuzz testing --- .github/workflows/ci.yml | 21 +++++++++++++++++++++ Cargo.lock | 18 ++++++++++++++++++ Cargo.toml | 3 ++- fuzz/.gitignore | 4 ++++ fuzz/Cargo.toml | 19 +++++++++++++++++++ fuzz/fuzz_targets/compare.rs | 24 ++++++++++++++++++++++++ src/lib.rs | 2 +- 7 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/fuzz_targets/compare.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21fa75f6b..c74b193cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,3 +149,24 @@ jobs: with: command: clippy args: -- -D warnings + + fuzz: + name: Fuzz + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - nightly + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + - run: cargo install cargo-fuzz + - uses: actions-rs/cargo@v1 + with: + command: fuzz + args: run compare -- -max_len=20000 -max_total_time=100 diff --git a/Cargo.lock b/Cargo.lock index 4f05631ba..73c9bfdea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + [[package]] name = "autocfg" version = "0.1.8" @@ -276,6 +282,17 @@ version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "libloading" version = "0.7.4" @@ -877,6 +894,7 @@ dependencies = [ "enum_primitive", "hex", "lazy_static", + "libfuzzer-sys", "log", "proptest", "ripemd", diff --git a/Cargo.toml b/Cargo.toml index 1a01276ab..86e004bdf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,11 +59,12 @@ path = "src/lib.rs" [features] external-secp = [] -rust-interpreter = [] +test-dependencies = [] [dependencies] bitflags = "2.5" enum_primitive = "0.1" +libfuzzer-sys = "0.4" log = "0.4" proptest = "0.9" ripemd = "0.1" diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 000000000..1a45eee77 --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 000000000..c16ac4703 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "zcash_script-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +zcash_script = { path = "..", features = ["test-dependencies"] } + +[[bin]] +name = "compare" +path = "fuzz_targets/compare.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/compare.rs b/fuzz/fuzz_targets/compare.rs new file mode 100644 index 000000000..ec80c550a --- /dev/null +++ b/fuzz/fuzz_targets/compare.rs @@ -0,0 +1,24 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +extern crate zcash_script; + +use zcash_script::*; + +fn missing_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> { + None +} + +fuzz_target!(|tup: (i64, 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, + pub_key, + sig, + testing::repair_flags(VerificationFlags::from_bits_truncate(flags)), + ); + assert_eq!(ret.0, ret.1); +}); diff --git a/src/lib.rs b/src/lib.rs index 42b6bb1ac..7d59ed47e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,7 +131,7 @@ fn check_legacy_sigop_count_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( +pub fn check_verify_callback( sighash: SighashCalculator, lock_time: i64, is_final: bool, From 4c34b3ed40c3e426b4843e1253cd93ead5b14da2 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Thu, 17 Oct 2024 12:39:38 -0600 Subject: [PATCH 12/12] Preserve richer errors on the Rust side For now, the underlying errors are discarded when comparing against the C++ results, but there are corresponding changes on the C++ side in a separate branch. --- fuzz/fuzz_targets/compare.rs | 3 ++- src/lib.rs | 38 ++++++++++++++++++++++++++---------- src/zcash_script.rs | 8 ++++++-- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/fuzz/fuzz_targets/compare.rs b/fuzz/fuzz_targets/compare.rs index ec80c550a..1fd8875ed 100644 --- a/fuzz/fuzz_targets/compare.rs +++ b/fuzz/fuzz_targets/compare.rs @@ -20,5 +20,6 @@ fuzz_target!(|tup: (i64, bool, &[u8], &[u8], u32)| { sig, testing::repair_flags(VerificationFlags::from_bits_truncate(flags)), ); - assert_eq!(ret.0, ret.1); + assert_eq!(ret.0, ret.1.clone().map_err(testing::normalize_error), + "original Rust result: {:?}", ret.1); }); diff --git a/src/lib.rs b/src/lib.rs index 7d59ed47e..c2d24b5cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ mod cxx; mod external; mod interpreter; mod script; -mod script_error; +pub mod script_error; mod zcash_script; use std::os::raw::{c_int, c_uint, c_void}; @@ -26,9 +26,9 @@ pub enum Cxx {} impl From for Error { #[allow(non_upper_case_globals)] - fn from(err_code: zcash_script_error_t) -> Error { + fn from(err_code: zcash_script_error_t) -> Self { match err_code { - zcash_script_error_t_zcash_script_ERR_OK => Error::Ok, + zcash_script_error_t_zcash_script_ERR_OK => Error::Ok(None), zcash_script_error_t_zcash_script_ERR_VERIFY_SCRIPT => Error::VerifyScript, unknown => Error::Unknown(unknown.into()), } @@ -207,6 +207,14 @@ impl ZcashScript for (T, U) { pub mod testing { use super::*; + /// Convert errors that don’t exist in the C++ code into the cases that do. + pub fn normalize_error(err: Error) -> Error { + match err { + Error::Ok(Some(_)) => Error::Ok(None), + _ => err, + } + } + /// Ensures that flags represent a supported state. This avoids crashes in the C++ code, which /// break various tests. pub fn repair_flags(flags: VerificationFlags) -> VerificationFlags { @@ -271,7 +279,7 @@ mod tests { flags, ); - assert_eq!(ret.0, ret.1); + assert_eq!(ret.0, ret.1.map_err(normalize_error)); assert!(ret.0.is_ok()); } @@ -292,8 +300,12 @@ mod tests { flags, ); - assert_eq!(ret.0, ret.1); - assert_eq!(ret.0, Err(Error::Ok)); + assert_eq!(ret.0, ret.1.map_err(normalize_error)); + // Checks the Rust result, because we have more information on the Rust side. + assert_eq!( + ret.1, + Err(Error::Ok(Some(script_error::ScriptError::EvalFalse))) + ); } #[test] @@ -313,8 +325,12 @@ mod tests { flags, ); - assert_eq!(ret.0, ret.1); - assert_eq!(ret.0, Err(Error::Ok)); + assert_eq!(ret.0, ret.1.map_err(normalize_error)); + // Checks the Rust result, because we have more information on the Rust side. + assert_eq!( + ret.1, + Err(Error::Ok(Some(script_error::ScriptError::EvalFalse))) + ); } proptest! { @@ -340,7 +356,8 @@ mod tests { &sig[..], repair_flags(VerificationFlags::from_bits_truncate(flags)), ); - prop_assert_eq!(ret.0, ret.1); + prop_assert_eq!(ret.0, ret.1.map_err(normalize_error), + "original Rust result: {:?}", ret.1); } /// Similar to `test_arbitrary_scripts`, but ensures the `sig` only contains pushes. @@ -363,7 +380,8 @@ mod tests { repair_flags(VerificationFlags::from_bits_truncate(flags)) | VerificationFlags::SigPushOnly, ); - prop_assert_eq!(ret.0, ret.1); + prop_assert_eq!(ret.0, ret.1.map_err(normalize_error), + "original Rust result: {:?}", ret.1); } } } diff --git a/src/zcash_script.rs b/src/zcash_script.rs index 9c24beb76..a18574d27 100644 --- a/src/zcash_script.rs +++ b/src/zcash_script.rs @@ -2,6 +2,7 @@ use std::num::TryFromIntError; use super::interpreter::*; use super::script::*; +use super::script_error::*; /// 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 @@ -10,7 +11,10 @@ use super::script::*; #[repr(u32)] pub enum Error { /// Any failure that results in the script being invalid. - Ok = 0, + /// + /// __NB__: This is in `Option` because this type is used by both the C++ and Rust + /// implementations, but the C++ impl doesn’t yet expose the original error. + Ok(Option) = 0, /// An exception was caught. VerifyScript = 7, /// The script size can’t fit in a `u32`, as required by the C++ code. @@ -84,6 +88,6 @@ impl ZcashScript for Rust { is_final, }, ) - .map_err(|_| Error::Ok) + .map_err(|e| Error::Ok(Some(e))) } }