Skip to content

Commit

Permalink
Use zcash_script’s new Script trait
Browse files Browse the repository at this point in the history
This is a precursor to testing the Rust implementation of Zcash Script.
  • Loading branch information
sellout committed Aug 8, 2024
1 parent d70e602 commit 9e5014b
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 131 deletions.
45 changes: 32 additions & 13 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
Expand Down Expand Up @@ -976,7 +976,7 @@ dependencies = [
"criterion-plot",
"is-terminal",
"itertools 0.10.5",
"num-traits",
"num-traits 0.2.19",
"once_cell",
"oorandom",
"plotters",
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -1437,7 +1446,7 @@ dependencies = [
"libm",
"num-bigint",
"num-integer",
"num-traits",
"num-traits 0.2.19",
]

[[package]]
Expand Down Expand Up @@ -1742,7 +1751,7 @@ dependencies = [
"byteorder",
"flate2",
"nom",
"num-traits",
"num-traits 0.2.19",
]

[[package]]
Expand Down Expand Up @@ -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]]
Expand All @@ -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]]
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -5829,12 +5847,13 @@ 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",
"enum_primitive",
"num-traits 0.2.19",
]

[[package]]
Expand Down
2 changes: 1 addition & 1 deletion zebra-script/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
167 changes: 50 additions & 117 deletions zebra-script/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<zcash_script_error_t> 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)]
Expand All @@ -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
Expand Down Expand Up @@ -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: _,
Expand All @@ -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<u64, Error> {
pub fn legacy_sigop_count(&self) -> u64 {
let mut count: u64 = 0;

for input in self.transaction.inputs() {
Expand All @@ -239,30 +183,19 @@ 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;
}

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
}
}

Expand Down Expand Up @@ -326,7 +259,7 @@ mod tests {
SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::Transaction>>()?;

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(())
}
Expand Down

0 comments on commit 9e5014b

Please sign in to comment.