From 9d931b044ea0b041fec816e5f2a94393d3c3d2be Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Thu, 8 Aug 2024 14:56:50 -0600 Subject: [PATCH] =?UTF-8?q?Use=20zcash=5Fscript=E2=80=99s=20new=20`Script`?= =?UTF-8?q?=20trait?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a precursor to testing the Rust implementation of Zcash Script. --- Cargo.lock | 7 +- zebra-script/src/lib.rs | 156 ++++++++++------------------------------ 2 files changed, 41 insertions(+), 122 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b18ac309fd..f97155cfd9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" @@ -5830,10 +5830,9 @@ dependencies = [ [[package]] name = "zcash_script" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2122a042c77d529d3c60b899e74705eda39ae96a8a992460caeb06afa76990a2" dependencies = [ "bindgen", + "bitflags 2.6.0", "cc", ] diff --git a/zebra-script/src/lib.rs b/zebra-script/src/lib.rs index 5b6e5f2a846..d8b684e5212 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 as zscript; +use zcash_script::ZcashScript; use zebra_chain::{ parameters::ConsensusBranchId, @@ -28,56 +22,34 @@ 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::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::VerifyScript => { + "unknown error from zcash_script: VerifyScript".to_owned() + } + zscript::Error::Unknown(e) => format!("unknown error from zcash_script: {e}"), + }, 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::Unknown(e) => format!("unknown error from zcash_script: {e}"), }) } } -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 +72,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 +119,11 @@ 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; - // 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"); + let flags = + zscript::VerificationFlags::P2SH | zscript::VerificationFlags::CHECKLOCKTIMEVERIFY; - 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 +137,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 +171,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 +179,10 @@ 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 +246,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(()) }