diff --git a/Cargo.lock b/Cargo.lock index 4b18ac309fd..39f00179559 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -722,7 +722,7 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", - "num-traits", + "num-traits 0.2.19", "serde", "windows-targets 0.52.5", ] @@ -976,7 +976,7 @@ dependencies = [ "criterion-plot", "is-terminal", "itertools 0.10.5", - "num-traits", + "num-traits 0.2.19", "once_cell", "oorandom", "plotters", @@ -1253,9 +1253,9 @@ dependencies = [ [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "elasticsearch" @@ -1292,6 +1292,15 @@ dependencies = [ "cfg-if 1.0.0", ] +[[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 = "env_logger" version = "0.7.1" @@ -1437,7 +1446,7 @@ dependencies = [ "libm", "num-bigint", "num-integer", - "num-traits", + "num-traits 0.2.19", ] [[package]] @@ -1742,7 +1751,7 @@ dependencies = [ "byteorder", "flate2", "nom", - "num-traits", + "num-traits 0.2.19", ] [[package]] @@ -2667,7 +2676,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ "num-integer", - "num-traits", + "num-traits 0.2.19", ] [[package]] @@ -2692,7 +2701,16 @@ version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "num-traits", + "num-traits 0.2.19", +] + +[[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]] @@ -3066,7 +3084,7 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" dependencies = [ - "num-traits", + "num-traits 0.2.19", "plotters-backend", "plotters-svg", "wasm-bindgen", @@ -3199,7 +3217,7 @@ dependencies = [ "bit-vec", "bitflags 2.6.0", "lazy_static", - "num-traits", + "num-traits 0.2.19", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", @@ -5829,12 +5847,14 @@ dependencies = [ [[package]] name = "zcash_script" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2122a042c77d529d3c60b899e74705eda39ae96a8a992460caeb06afa76990a2" +version = "0.3.0" dependencies = [ "bindgen", + "bitflags 2.6.0", "cc", + "either", + "enum_primitive", + "num-traits 0.2.19", ] [[package]] diff --git a/zebra-script/Cargo.toml b/zebra-script/Cargo.toml index 9e876784d53..fb2238db744 100644 --- a/zebra-script/Cargo.toml +++ b/zebra-script/Cargo.toml @@ -15,7 +15,7 @@ keywords = ["zebra", "zcash"] categories = ["api-bindings", "cryptography::cryptocurrencies"] [dependencies] -zcash_script = "0.2.0" +zcash_script = "0.3.0" zebra-chain = { path = "../zebra-chain", version = "1.0.0-beta.38" } thiserror = "1.0.63" diff --git a/zebra-script/src/lib.rs b/zebra-script/src/lib.rs index 5b6e5f2a846..9737822b608 100644 --- a/zebra-script/src/lib.rs +++ b/zebra-script/src/lib.rs @@ -6,19 +6,13 @@ #![allow(unsafe_code)] use core::fmt; -use std::{ - ffi::{c_int, c_uint, c_void}, - sync::Arc, -}; +use std::sync::Arc; use thiserror::Error; -use zcash_script::{ - zcash_script_error_t, zcash_script_error_t_zcash_script_ERR_OK, - zcash_script_error_t_zcash_script_ERR_TX_DESERIALIZE, - zcash_script_error_t_zcash_script_ERR_TX_INDEX, - zcash_script_error_t_zcash_script_ERR_TX_SIZE_MISMATCH, -}; +use zcash_script; +use zcash_script::api as zscript; +use zcash_script::api::Script; use zebra_chain::{ parameters::ConsensusBranchId, @@ -28,56 +22,41 @@ use zebra_chain::{ /// An Error type representing the error codes returned from zcash_script. #[derive(Copy, Clone, Debug, Error, PartialEq, Eq)] -#[non_exhaustive] pub enum Error { /// script verification failed - #[non_exhaustive] - ScriptInvalid, - /// could not deserialize tx - #[non_exhaustive] - TxDeserialize, + ScriptInvalid(zscript::Error), /// input index out of bounds - #[non_exhaustive] TxIndex, - /// tx has an invalid size - #[non_exhaustive] - TxSizeMismatch, /// tx is a coinbase transaction and should not be verified - #[non_exhaustive] TxCoinbase, - /// unknown error from zcash_script: {0} - #[non_exhaustive] - Unknown(zcash_script_error_t), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&match self { - Error::ScriptInvalid => "script verification failed".to_owned(), - Error::TxDeserialize => "could not deserialize tx".to_owned(), - Error::TxIndex => "input index out of bounds".to_owned(), - Error::TxSizeMismatch => "tx has an invalid size".to_owned(), - Error::TxCoinbase => { - "tx is a coinbase transaction and should not be verified".to_owned() + Error::ScriptInvalid(invalid) => match invalid { + // NB: This error has an odd name, but means that the script was invalid. + zscript::Error::OK => "script verification failed".to_owned(), + zscript::Error::TxDeserialize => "could not deserialize tx".to_owned(), + zscript::Error::TxIndex => "input index out of bounds".to_owned(), + zscript::Error::TxSizeMismatch => "tx has an invalid size".to_owned(), + zscript::Error::TxVersion => + "unknown error from zcash_script: TxVersion".to_owned(), + zscript::Error::AllPrevOutputsSizeMismatch => + "unknown error from zcash_script: AllPrevOutputsSizeMismatch".to_owned(), + zscript::Error::AllPrevOutputsDeserialize => + "unknown error from zcash_script: AllPrevOutputsDeserialize".to_owned(), + zscript::Error::VerifyScript => + "unknown error from zcash_script: VerifyScript".to_owned(), + zscript::Error::Unknown(e) => format!("unknown error from zcash_script: {e}"), } - Error::Unknown(e) => format!("unknown error from zcash_script: {e}"), + Error::TxIndex => "input index out of bounds".to_owned(), + Error::TxCoinbase => + "tx is a coinbase transaction and should not be verified".to_owned(), }) } } -impl From for Error { - #[allow(non_upper_case_globals)] - fn from(err_code: zcash_script_error_t) -> Error { - match err_code { - zcash_script_error_t_zcash_script_ERR_OK => Error::ScriptInvalid, - zcash_script_error_t_zcash_script_ERR_TX_DESERIALIZE => Error::TxDeserialize, - zcash_script_error_t_zcash_script_ERR_TX_INDEX => Error::TxIndex, - zcash_script_error_t_zcash_script_ERR_TX_SIZE_MISMATCH => Error::TxSizeMismatch, - unknown => Error::Unknown(unknown), - } - } -} - /// A preprocessed Transaction which can be used to verify scripts within said /// Transaction. #[derive(Debug)] @@ -100,33 +79,6 @@ struct SigHashContext<'a> { sighasher: SigHasher<'a>, } -/// The sighash callback to use with zcash_script. -extern "C" fn sighash( - sighash_out: *mut u8, - sighash_out_len: c_uint, - ctx: *const c_void, - script_code: *const u8, - script_code_len: c_uint, - hash_type: c_int, -) { - // SAFETY: `ctx` is a valid SigHashContext because it is always passed to - // `zcash_script_verify_callback` which simply forwards it to the callback. - // `script_code` and `sighash_out` are valid buffers since they are always - // specified when the callback is called. - unsafe { - let ctx = ctx as *const SigHashContext; - let script_code_vec = - std::slice::from_raw_parts(script_code, script_code_len as usize).to_vec(); - let sighash = (*ctx).sighasher.sighash( - HashType::from_bits_truncate(hash_type as u32), - Some(((*ctx).input_index, script_code_vec)), - ); - // Sanity check; must always be true. - assert_eq!(sighash_out_len, sighash.0.len() as c_uint); - std::ptr::copy_nonoverlapping(sighash.0.as_ptr(), sighash_out, sighash.0.len()); - } -} - impl CachedFfiTransaction { /// Construct a `PrecomputedTransaction` from a `Transaction` and the outputs /// from previous transactions that match each input in the transaction @@ -174,21 +126,16 @@ impl CachedFfiTransaction { .try_into() .expect("transaction indexes are much less than c_uint::MAX"); - let flags = zcash_script::zcash_script_SCRIPT_FLAGS_VERIFY_P2SH - | zcash_script::zcash_script_SCRIPT_FLAGS_VERIFY_CHECKLOCKTIMEVERIFY; + let flags = zscript::Flags::verifyP2SH + | zscript::Flags::verifyCHECKLOCKTIMEVERIFY; // This conversion is useful on some platforms, but not others. #[allow(clippy::useless_conversion)] let flags = flags .try_into() - .expect("zcash_script_SCRIPT_FLAGS_VERIFY_* enum values fit in a c_uint"); + .expect("zscript::Flags::* enum values fit in a c_uint"); - let mut err = 0; let lock_time = self.transaction.raw_lock_time() as i64; - let is_final = if self.transaction.inputs()[input_index].sequence() == u32::MAX { - 1 - } else { - 0 - }; + let is_final = self.transaction.inputs()[input_index].sequence() == u32::MAX; let signature_script = match &self.transaction.inputs()[input_index] { transparent::Input::PrevOut { outpoint: _, @@ -202,33 +149,30 @@ impl CachedFfiTransaction { input_index: n_in, sighasher: SigHasher::new(&self.transaction, branch_id, &self.all_previous_outputs), }); - // SAFETY: The `script_*` fields are created from a valid Rust `slice`. - let ret = unsafe { - zcash_script::zcash_script_verify_callback( - (&*ctx as *const SigHashContext) as *const c_void, - Some(sighash), - lock_time, - is_final, - script_pub_key.as_ptr(), - script_pub_key.len() as u32, - signature_script.as_ptr(), - signature_script.len() as u32, - flags, - &mut err, - ) - }; + let ret = zcash_script::Cxx::verify_callback( + &|script_code, hash_type| { + let script_code_vec = script_code.to_vec(); + Some( + (*ctx).sighasher.sighash( + HashType::from_bits_truncate(hash_type.bits() as u32), + Some(((*ctx).input_index, script_code_vec)), + ).0 + ) + }, + lock_time, + is_final, + script_pub_key, + signature_script, + flags, + ); - if ret == 1 { - Ok(()) - } else { - Err(Error::from(err)) - } + ret.map_err(Error::ScriptInvalid) } /// Returns the number of transparent signature operations in the /// transparent inputs and outputs of this transaction. #[allow(clippy::unwrap_in_result)] - pub fn legacy_sigop_count(&self) -> Result { + pub fn legacy_sigop_count(&self) -> u64 { let mut count: u64 = 0; for input in self.transaction.inputs() { @@ -239,13 +183,7 @@ impl CachedFfiTransaction { sequence: _, } => { let script = unlock_script.as_raw_bytes(); - // SAFETY: `script` is created from a valid Rust `slice`. - unsafe { - zcash_script::zcash_script_legacy_sigop_count_script( - script.as_ptr(), - script.len() as u32, - ) - } + zcash_script::Cxx::legacy_sigop_count_script(script) } transparent::Input::Coinbase { .. } => 0, } as u64; @@ -253,16 +191,11 @@ impl CachedFfiTransaction { for output in self.transaction.outputs() { let script = output.lock_script.as_raw_bytes(); - // SAFETY: `script` is created from a valid Rust `slice`. - let ret = unsafe { - zcash_script::zcash_script_legacy_sigop_count_script( - script.as_ptr(), - script.len() as u32, - ) - }; + let ret = + zcash_script::Cxx::legacy_sigop_count_script(script); count += ret as u64; } - Ok(count) + count } } @@ -326,7 +259,7 @@ mod tests { SCRIPT_TX.zcash_deserialize_into::>()?; let cached_tx = super::CachedFfiTransaction::new(transaction, Vec::new()); - assert_eq!(cached_tx.legacy_sigop_count()?, 1); + assert_eq!(cached_tx.legacy_sigop_count(), 1); Ok(()) }