diff --git a/Cargo.lock b/Cargo.lock index 4040876ab..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" @@ -40,6 +46,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,12 +92,50 @@ 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" 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" @@ -104,6 +157,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 +220,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" @@ -205,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" @@ -268,6 +349,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 +377,46 @@ 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 = "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" +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 +434,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" @@ -417,6 +559,12 @@ dependencies = [ "bindgen", "bitflags", "cc", + "enum_primitive", "hex", "lazy_static", + "log", + "ripemd", + "secp256k1", + "sha-1", + "sha2", ] diff --git a/Cargo.toml b/Cargo.toml index 295afbd29..ab674a400 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,9 +58,16 @@ path = "src/lib.rs" [features] external-secp = [] +rust-interpreter = [] [dependencies] bitflags = "2.5" +enum_primitive = "0.1" +log = "0.4" +ripemd = "0.1" +secp256k1 = "0.29" +sha-1 = "0.10" +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/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..4dad8b95c --- /dev/null +++ b/src/external/pubkey.rs @@ -0,0 +1,58 @@ +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 +/// 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: &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 { + /* 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/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 a9851f83d..868f5c164 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,3 +1,15 @@ +use std::mem::swap; +use std::slice::Iter; + +use ripemd::Ripemd160; +use sha1::Sha1; +use sha2::{Digest, Sha256}; + +use super::external::pubkey::PubKey; +use super::external::uint256::UInt256; +use super::script::{Operation::*, PushValue::*, *}; +use super::script_error::*; + bitflags::bitflags! { /// The different SigHash types, as defined in /// @@ -69,3 +81,1207 @@ bitflags::bitflags! { const CHECKLOCKTIMEVERIFY = 1 << 9; } } + +pub trait SignatureChecker { + 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 + } +} + +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, vchi) in vch.iter().enumerate() { + if *vchi != 0 { + // Can be negative zero + if i == vch.len() - 1 && *vchi == 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); + +/// 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> { + 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 { + 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) -> Result<&mut T, ScriptError> { + self.0.last_mut().ok_or(ScriptError::InvalidStackOperation) + } + + pub fn erase(&mut self, start: usize, end: Option) -> () { + for _ in 0..end.map_or(0, |e| e - start) { + self.0.remove(start); + } + } + + pub fn insert(&mut self, i: usize, element: T) { + self.0.insert(i, element) + } + + pub fn end(&self) -> usize { + self.0.len() + } +} + +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 usize::from(sig[1]) != sig.len() - 3 { + return false; + }; + + // Extract the length of the R element. + 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() { + return false; + }; + + // Extract the length of the S element. + 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. + 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 set_error(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 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 set_error(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); + }; + set_success(()) +} + +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 SignatureChecker, +) -> Result { + let bn_zero = ScriptNum(0); + let bn_one = ScriptNum(1); + let vch_false: 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.into() { + return set_error(ScriptError::ScriptSize); + } + + 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: u8 = 0; + let require_minimal = flags.contains(VerificationFlags::MinimalData); + + // This keeps track of the conditional flags at each nesting level + // during execution. If we're in a branch of execution where *any* + // of these conditionals are false, we ignore opcodes unless those + // opcodes direct control flow (OP_IF, OP_ELSE, etc.). + let mut vexec: Stack = Stack(vec![]); + + let mut altstack: Stack> = Stack(vec![]); + + // Main execution loop + while !pc.is_empty() { + // Are we in an executing branch of the script? + let exec = vexec.iter().all(|value| *value); + + // + // Read instruction + // + let opcode = 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 { + 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((u8::from(opcode) - (u8::from(OP_1) - 1)).into()); + stack.push_back(bn.getvch()); + // The result of these opcodes should always be the minimal way to push the data + // they push, so no need for a CheckMinimalPush here. + } + _ => { + if pv <= OP_PUSHDATA4 { + if require_minimal && !check_minimal_push(&vch_push_value, pv) { + return set_error(ScriptError::MinimalData); + } + stack.push_back(vch_push_value.clone()); + } else { + return set_error(ScriptError::BadOpcode); + } + } + } + } + } + Opcode::Operation(op) => { + // Note how OP_RESERVED does not count towards the opcode limit. + op_count += 1; + if op_count > 201 { + return set_error(ScriptError::OpCount); + } + + if op == OP_CAT + || op == OP_SUBSTR + || op == OP_LEFT + || op == OP_RIGHT + || op == OP_INVERT + || op == OP_AND + || op == OP_OR + || op == OP_XOR + || op == OP_2MUL + || op == OP_2DIV + || op == OP_MUL + || op == OP_DIV + || op == OP_MOD + || op == OP_LSHIFT + || op == OP_RSHIFT + || op == OP_CODESEPARATOR + { + return set_error(ScriptError::DisabledOpcode); // Disabled opcodes. + } + + if exec || (OP_IF <= op && op <= OP_ENDIF) { + match op { + // + // Control + // + OP_NOP => (), + + OP_CHECKLOCKTIMEVERIFY => { + // 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 set_error(ScriptError::DiscourageUpgradableNOPs); + } + } else { + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + + // Note that elsewhere numeric opcodes are limited to + // operands in the range -2**31+1 to 2**31-1, however it is + // legal for opcodes to produce results exceeding that + // range. This limitation is implemented by `ScriptNum`'s + // default 4-byte limit. + // + // If we kept to that limit we'd have a year 2038 problem, + // even though the `lock_time` field in transactions + // themselves is u32 which only becomes meaningless + // after the year 2106. + // + // Thus as a special case we tell `ScriptNum` to accept up + // to 5-byte bignums, which are good until 2**39-1, well + // beyond the 2**32-1 limit of the `lock_time` field itself. + let lock_time = ScriptNum::new(stack.top(-1)?, require_minimal, Some(5)); + + // In the rare event that the argument may be < 0 due to + // some arithmetic being done first, you can always use + // 0 MAX CHECKLOCKTIMEVERIFY. + if lock_time < ScriptNum(0) { + return set_error(ScriptError::NegativeLockTime); + } + + // Actually compare the specified lock time with the transaction. + if !checker.check_lock_time(&lock_time) { + return set_error(ScriptError::UnsatisfiedLockTime); + } + } + } + + OP_NOP1 | OP_NOP3 | OP_NOP4 | OP_NOP5 + | OP_NOP6 | OP_NOP7 | OP_NOP8 | OP_NOP9 | OP_NOP10 => { + // Do nothing, though if the caller wants to prevent people from using + // these NOPs (as part of a standard tx rule, for example) they can + // enable `DiscourageUpgradableNOPs` to turn these opcodes into errors. + if flags.contains(VerificationFlags::DiscourageUpgradableNOPs) { + return set_error(ScriptError::DiscourageUpgradableNOPs); + } + } + + OP_IF + | OP_NOTIF => { + // if [statements] [else [statements]] endif + let mut value = false; + if exec { + if stack.size() < 1 { + return set_error(ScriptError::UnbalancedConditional); + } + let vch: &ValType = stack.top(-1)?; + value = cast_to_bool(vch); + if op == OP_NOTIF { + value = !value + }; + stack.pop()?; + } + vexec.push_back(value); + } + + OP_ELSE => { + if vexec.empty() { + return set_error(ScriptError::UnbalancedConditional); + } + vexec.back().map(|last| *last = !*last)?; + } + + OP_ENDIF => { + if vexec.empty() { + return set_error(ScriptError::UnbalancedConditional); + } + vexec.pop()?; + } + + OP_VERIFY => { + // (true -- ) or + // (false -- false) and return + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + let value = cast_to_bool(stack.top(-1)?); + if value { + stack.pop()?; + } else { + return set_error(ScriptError::VERIFY); + } + } + + OP_RETURN => return set_error(ScriptError::OpReturn), + + // + // Stack ops + // + OP_TOALTSTACK => { + if stack.empty() { + return set_error(ScriptError::InvalidStackOperation); + } + altstack.push_back(stack.top(-1)?.clone()); + stack.pop()?; + } + + OP_FROMALTSTACK => { + if altstack.empty() { + return set_error(ScriptError::InvalidAltstackOperation); + } + stack.push_back(altstack.top(-1)?.clone()); + altstack.pop()?; + } + + OP_2DROP => { + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + + stack.pop()?; + stack.pop()?; + } + + OP_2DUP => { + // (x1 x2 -- x1 x2 x1 x2) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch1 = stack.top(-2)?.clone(); + let vch2 = stack.top(-1)?.clone(); + stack.push_back(vch1); + stack.push_back(vch2); + } + + OP_3DUP => { + // (x1 x2 x3 -- x1 x2 x3 x1 x2 x3) + if stack.size() < 3 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch1 = stack.top(-3)?.clone(); + let vch2 = stack.top(-2)?.clone(); + let vch3 = stack.top(-1)?.clone(); + stack.push_back(vch1); + stack.push_back(vch2); + stack.push_back(vch3); + } + + OP_2OVER => { + // (x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2) + if stack.size() < 4 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch1 = stack.top(-4)?.clone(); + let vch2 = stack.top(-3)?.clone(); + stack.push_back(vch1); + stack.push_back(vch2); + } + + OP_2ROT => { + // (x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2) + if stack.size() < 6 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch1 = stack.top(-6)?.clone(); + let vch2 = stack.top(-5)?.clone(); + stack.erase(stack.end() - 6, Some(stack.end() - 4)); + stack.push_back(vch1); + stack.push_back(vch2); + } + + OP_2SWAP => { + // (x1 x2 x3 x4 -- x3 x4 x1 x2) + if stack.size() < 4 { + return set_error(ScriptError::InvalidStackOperation); + } + stack.swap(-4, -2)?; + stack.swap(-3, -1)?; + } + + OP_IFDUP => { + // (x - 0 | x x) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch = stack.top(-1)?; + if cast_to_bool(vch) { + stack.push_back(vch.to_vec()) + } + } + + OP_DEPTH => { + // -- stacksize + let bn = ScriptNum( + i64::try_from(stack.size()).map_err(|_| ScriptError::StackSize)?); + stack.push_back(bn.getvch()) + } + + OP_DROP => { + // (x -- ) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + stack.pop()?; + } + + OP_DUP => { + // (x -- x x) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + + let a = stack.pop()?; + stack.push_back(a.clone()); + stack.push_back(a); + } + + OP_NIP => { + // (x1 x2 -- x2) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + stack.erase(stack.end() - 2, None); + } + + OP_OVER => { + // (x1 x2 -- x1 x2 x1) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch = stack.top(-2)?; + stack.push_back(vch.clone()); + } + + OP_PICK + | OP_ROLL => { + // (xn ... x2 x1 x0 n - xn ... x2 x1 x0 xn) + // (xn ... x2 x1 x0 n - ... x2 x1 x0 xn) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + let n = u16::try_from(ScriptNum::new(stack.top(-1)?, require_minimal, None).getint()).map_err(|_| ScriptError::InvalidStackOperation)?; + stack.pop()?; + if usize::from(n) >= stack.size() { + return set_error(ScriptError::InvalidStackOperation); + } + let vch: ValType = stack.top(-isize::try_from(n).map_err(|_| ScriptError::InvalidStackOperation)? - 1)?.clone(); + if op == OP_ROLL { + stack.erase(stack.end() - usize::from(n) - 1, None); + } + stack.push_back(vch) + } + + OP_ROT => { + // (x1 x2 x3 -- x2 x3 x1) + // x2 x1 x3 after first swap + // x2 x3 x1 after second swap + if stack.size() < 3 { + return set_error(ScriptError::InvalidStackOperation); + } + stack.swap(-3, -2)?; + stack.swap(-2, -1)?; + } + + OP_SWAP => { + // (x1 x2 -- x2 x1) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + stack.swap(-2, -1)?; + } + + OP_TUCK => { + // (x1 x2 -- x2 x1 x2) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch = stack.top(-1)?.clone(); + stack.insert(stack.end() - 2, vch) + } + + + OP_SIZE => { + // (in -- in size) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + let bn = ScriptNum(stack.top(-1)?.len().try_into().map_err(|_| ScriptError::PushSize)?); + stack.push_back(bn.getvch()) + } + + + // + // 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 { + return set_error(ScriptError::EQUALVERIFY); + } + } + } + + + // + // Numeric + // + OP_1ADD + | OP_1SUB + | OP_NEGATE + | OP_ABS + | OP_NOT + | OP_0NOTEQUAL => { + // (in -- out) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + let mut bn = ScriptNum::new(stack.top(-1)?, require_minimal, None); + match op { + OP_1ADD => bn = bn + bn_one, + OP_1SUB => bn = bn - bn_one, + OP_NEGATE => bn = -bn, + OP_ABS => { + if bn < bn_zero { + bn = -bn + } + } + OP_NOT => bn = ScriptNum((bn == bn_zero).into()), + OP_0NOTEQUAL => bn = ScriptNum((bn != bn_zero).into()), + _ => panic!("invalid opcode"), + } + stack.pop()?; + stack.push_back(bn.getvch()) + } + + OP_ADD + | OP_SUB + | OP_BOOLAND + | OP_BOOLOR + | OP_NUMEQUAL + | OP_NUMEQUALVERIFY + | OP_NUMNOTEQUAL + | OP_LESSTHAN + | OP_GREATERTHAN + | OP_LESSTHANOREQUAL + | OP_GREATERTHANOREQUAL + | OP_MIN + | OP_MAX => { + // (x1 x2 -- out) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + let bn1 = ScriptNum::new(stack.top(-2)?, require_minimal, None); + let bn2 = ScriptNum::new(stack.top(-1)?, require_minimal, None); + let bn; + match op { + OP_ADD => + 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 op == OP_NUMEQUALVERIFY { + if cast_to_bool(stack.top(-1)?) { + stack.pop()?; + } else { + return set_error(ScriptError::NUMEQUALVERIFY); + } + } + } + + OP_WITHIN => { + // (x min max -- out) + if stack.size() < 3 { + return set_error(ScriptError::InvalidStackOperation); + } + let bn1 = ScriptNum::new(stack.top(-3)?, require_minimal, None); + let bn2 = ScriptNum::new(stack.top(-2)?, require_minimal, None); + let bn3 = ScriptNum::new(stack.top(-1)?, require_minimal, None); + let value = bn2 <= bn1 && bn1 < bn3; + stack.pop()?; + stack.pop()?; + stack.pop()?; + stack.push_back(if value { + vch_true.clone() + } else { + vch_false.clone() + }) + } + + // + // Crypto + // + OP_RIPEMD160 + | OP_SHA1 + | OP_SHA256 + | OP_HASH160 + | OP_HASH256 => { + // (in -- hash) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch = stack.top(-1)?; + let mut vch_hash = vec![]; + if op == OP_RIPEMD160 { + vch_hash = Ripemd160::digest(vch).to_vec(); + } else if op == OP_SHA1 { + let mut hasher = Sha1::new(); + hasher.update(vch); + vch_hash = hasher.finalize().to_vec(); + } else if op == OP_SHA256 { + vch_hash = Sha256::digest(vch).to_vec(); + } else if op == OP_HASH160 { + vch_hash = Ripemd160::digest(Sha256::digest(vch)).to_vec(); + } else if op == OP_HASH256 { + vch_hash = Sha256::digest(Sha256::digest(vch)).to_vec(); + } + stack.pop()?; + stack.push_back(vch_hash) + } + + OP_CHECKSIG + | OP_CHECKSIGVERIFY => { + // (sig pubkey -- bool) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + + let vch_sig = stack.top(-2)?.clone(); + let vch_pub_key = stack.top(-1)?.clone(); + + 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 op == OP_CHECKSIGVERIFY { + if success { + stack.pop()?; + } else { + return set_error(ScriptError::CHECKSIGVERIFY); + } + } + } + + OP_CHECKMULTISIG + | OP_CHECKMULTISIGVERIFY => { + // ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) + + // NB: This is guaranteed u8-safe, because we are limited to 20 keys and + // 20 signatures, plus a couple other fields. u8 also gives us total + // conversions to the other types we deal with here (`isize` and `i64`). + let mut i: u8 = 1; + if stack.size() < i.into() { + return set_error(ScriptError::InvalidStackOperation); + }; + + let mut keys_count = + u8::try_from(ScriptNum::new(stack.top(-isize::from(i))?, require_minimal, None).getint()).map_err(|_| ScriptError::PubKeyCount)?; + if keys_count > 20 { + return set_error(ScriptError::PubKeyCount); + }; + op_count += keys_count; + if op_count > 201 { + return set_error(ScriptError::OpCount); + }; + i += 1; + let mut ikey = i; + i += keys_count; + if stack.size() < i.into() { + return set_error(ScriptError::InvalidStackOperation); + } + + let mut sigs_count = + u8::try_from(ScriptNum::new(stack.top(-isize::from(i))?, require_minimal, None).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() < i.into() { + return set_error(ScriptError::InvalidStackOperation); + }; + + let mut success = true; + while success && sigs_count > 0 { + let vch_sig: &ValType = stack.top(-isize::from(isig))?; + let vch_pub_key: &ValType = stack.top(-isize::from(ikey))?; + + // Note how this makes the exact order of pubkey/signature evaluation + // distinguishable by CHECKMULTISIG NOT if the STRICTENC flag is set. + // See the script_(in)valid tests for details. + 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 set_error(ScriptError::InvalidStackOperation); + }; + if flags.contains(VerificationFlags::NullDummy) && stack.top(-1)?.len() != 0 { + return set_error(ScriptError::SigNullDummy); + }; + stack.pop()?; + + stack.push_back(if success { + vch_true.clone() + } else { + vch_false.clone() + }); + + if op == OP_CHECKMULTISIGVERIFY { + if success { + stack.pop()?; + } else { + return set_error(ScriptError::CHECKMULTISIGVERIFY); + } + } + } + + _ => { + return set_error(ScriptError::BadOpcode); + } + } + } + } + Opcode::OP_INVALIDOPCODE => return set_error(ScriptError::BadOpcode), + } + + // Size limits + if stack.size() + altstack.size() > 1000 { + return set_error(ScriptError::StackSize); + } + } + + if !vexec.empty() { + return set_error(ScriptError::UnbalancedConditional); + } + + 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 SignatureChecker, +) -> Result<(), ScriptError> { + if flags.contains(VerificationFlags::SigPushOnly) && !script_sig.is_push_only() { + return set_error(ScriptError::SigPushOnly); + }; + + let mut stack = Stack(Vec::new()); + let mut stack_copy = Stack(Vec::new()); + if !eval_script(&mut stack, script_sig, flags, checker)? { + // 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)? { + // serror is set + return set_error(ScriptError::UnknownError); + }; + if stack.back().map_or(true, |b| cast_to_bool(&b) == false) { + 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 set_error(ScriptError::SigPushOnly); + }; + + // Restore stack. + swap(&mut stack, &mut stack_copy); + + // stack cannot be empty here, because if it was the + // P2SH HASH <> EQUAL scriptPubKey would be evaluated with + // an empty stack and the EvalScript above would return false. + assert!(!stack.empty()); + + 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 + // 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 set_error(ScriptError::CleanStack); + } + }; + + set_success(()) +} diff --git a/src/lib.rs b/src/lib.rs index e05cfb127..4ca59ee80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,17 +3,24 @@ #![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; -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 {} @@ -109,6 +116,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::*; @@ -147,7 +241,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, @@ -156,7 +250,8 @@ mod tests { flags, ); - assert!(ret.is_ok()); + assert_eq!(ret.0, ret.1); + assert!(ret.0.is_ok()); } #[test] @@ -167,7 +262,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, @@ -176,7 +271,8 @@ mod tests { flags, ); - assert_eq!(ret, Err(Error::Ok)); + assert_eq!(ret.0, ret.1); + assert_eq!(ret.0, Err(Error::Ok)); } #[test] @@ -187,7 +283,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, @@ -196,6 +292,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 new file mode 100644 index 000000000..2ff52113a --- /dev/null +++ b/src/script.rs @@ -0,0 +1,578 @@ +#![allow(non_camel_case_types)] + +use std::ops::{Add, Neg, Sub}; + +use enum_primitive::FromPrimitive; + +use super::script_error::*; + +pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520; // bytes + +/// Maximum script length in bytes +pub const MAX_SCRIPT_SIZE: u16 = 10000; + +// 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 + +/** Script opcodes */ +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub enum Opcode { + PushValue(PushValue), + Operation(Operation), + OP_INVALIDOPCODE, +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +#[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, +} + +use PushValue::*; + +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, + 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, + + // 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 = 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, + 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, + + // 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, + + // expansion + OP_NOP1 = 0xb0, + OP_NOP2 = 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, +} +} + +use Operation::*; + +pub const OP_CHECKLOCKTIMEVERIFY: Operation = OP_NOP2; + +impl From for u8 { + fn from(value: Opcode) -> Self { + match value { + Opcode::PushValue(pv) => pv.into(), + Opcode::Operation(op) => op.into(), + Opcode::OP_INVALIDOPCODE => 0xff, + } + } +} + +impl From for Opcode { + fn from(value: u8) -> Self { + Operation::from_u8(value).map_or( + PushValue::try_from(value).map_or(Opcode::OP_INVALIDOPCODE, Opcode::PushValue), + Opcode::Operation, + ) + } +} + +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, + } + } +} + +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 { + Err(()) + } + } + } + } +} + +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)] +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 n_max_num_size = max_num_size.unwrap_or(Self::DEFAULT_MAX_NUM_SIZE); + if vch.len() > n_max_num_size { + panic!("script number overflow"); + }; + 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.into() { + i32::MAX + } else if self.0 < i32::MIN.into() { + i32::MIN + } else { + self.0.try_into().unwrap() + } + } + + 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 = value.abs(); + + while absvalue != 0 { + result.push( + (absvalue & 0xff) + .try_into() + .unwrap_or_else(|_| unreachable!()), + ); + 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 { + result.last_mut().map(|last| *last |= 0x80); + } + + result + } + + fn set_vch(vch: &Vec) -> i64 { + match vch.last() { + None => 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; + }; + + // 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, vchi) in vch.iter().enumerate() { + result |= i64::from(*vchi) << 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 + } + } + } +} + +impl Add for ScriptNum { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self(self.0 + other.0) + } +} + +impl Sub for ScriptNum { + type Output = Self; + + fn sub(self, other: Self) -> Self { + Self(self.0 - other.0) + } +} + +impl Neg for ScriptNum { + type Output = Self; + + fn neg(self) -> Self { + Self(-self.0) + } +} + +/** Serialized script, used inside transaction inputs and outputs */ +#[derive(Clone)] +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 + /// counted more accurately, assuming they are of the form + /// ... OP_N CHECKMULTISIG ... + pub fn get_sig_op_count(&self, accurate: bool) -> u32 { + let mut n = 0; + let mut pc = self.0; + let mut last_opcode = Opcode::OP_INVALIDOPCODE; + while !pc.is_empty() { + 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 += 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 + && 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). + 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, + } + } else { + return false; + } + } + true + } +} diff --git a/src/script_error.rs b/src/script_error.rs new file mode 100644 index 000000000..8f3e85ed6 --- /dev/null +++ b/src/script_error.rs @@ -0,0 +1,52 @@ +#[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 { + expected_bytes: usize, + available_bytes: usize, + }, +} diff --git a/src/zcash_script.rs b/src/zcash_script.rs index fa7d330ac..4a19e02a9 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 { @@ -54,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], @@ -66,3 +53,36 @@ pub trait ZcashScript { /// output script pointed to by script. fn legacy_sigop_count_script(script: &[u8]) -> Result; } + +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, + 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, + lock_time: &lock_time_num, + is_final, + }, + ) + .map_err(|_| Error::Ok) + } +}