Provide a Rustier wrapper for zcash_script (#171)
* Move C++ bindings out of lib.rs Have lib.rs re-export them, but make room for the upcoming Rust implementation. * Provide a Rustier wrapper for zcash_script This adds a `Script` trait that exposes slightly Rustier types in order to have a common interface for the existing C++ implementation as well as the upcoming Rust implementation (and a third instance that runs both and checks that the Rust result matches the C++ one). The module structure (interpreter.rs, zcash_script.rs) and locations of definitions are intended to mirror the structure of the C++ code, especially as we get the Rust implementation in place, for easier comparison. That organization is very likely to change once everything has been checked. * Address review feedback Thanks to @nuttycom and @arya2 for getting the closure to work. * Additional cleanup * Use `try_from`/`_into` instead of `as` * Address review feedback * Widen the `Unknown` error type This should fix the Windows build.
This commit is contained in:
parent
c9d750743f
commit
9d16e79c72
|
@ -36,9 +36,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.0"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
|
@ -79,9 +79,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.9.0"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
|
@ -415,6 +415,7 @@ name = "zcash_script"
|
|||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"bitflags",
|
||||
"cc",
|
||||
"hex",
|
||||
"lazy_static",
|
||||
|
|
|
@ -60,6 +60,7 @@ path = "src/lib.rs"
|
|||
external-secp = []
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.5"
|
||||
|
||||
[build-dependencies]
|
||||
# The `bindgen` dependency should automatically upgrade to match the version used by zebra-state's `rocksdb` dependency in:
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
//! Rust bindings for Zcash transparent scripts.
|
||||
|
||||
#![allow(missing_docs)]
|
||||
#![allow(clippy::needless_lifetimes)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(unsafe_code)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(clippy::unwrap_or_default)]
|
||||
|
||||
// Use the generated C++ bindings
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ffi::{c_int, c_uint, c_void};
|
||||
|
||||
pub use super::zcash_script_error_t;
|
||||
use hex::FromHex;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref SCRIPT_PUBKEY: Vec<u8> = <Vec<u8>>::from_hex("a914c117756dcbe144a12a7c33a77cfa81aa5aeeb38187").unwrap();
|
||||
pub static ref SCRIPT_SIG: Vec<u8> = <Vec<u8>>::from_hex("00483045022100d2ab3e6258fe244fa442cfb38f6cef9ac9a18c54e70b2f508e83fa87e20d040502200eead947521de943831d07a350e45af8e36c2166984a8636f0a8811ff03ed09401473044022013e15d865010c257eef133064ef69a780b4bc7ebe6eda367504e806614f940c3022062fdbc8c2d049f91db2042d6c9771de6f1ef0b3b1fea76c1ab5542e44ed29ed8014c69522103b2cc71d23eb30020a4893982a1e2d352da0d20ee657fa02901c432758909ed8f21029d1e9a9354c0d2aee9ffd0f0cea6c39bbf98c4066cf143115ba2279d0ba7dabe2103e32096b63fd57f3308149d238dcbb24d8d28aad95c0e4e74e3e5e6a11b61bcc453ae").expect("Block bytes are in valid hex representation");
|
||||
}
|
||||
|
||||
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,
|
||||
) {
|
||||
unsafe {
|
||||
assert!(ctx.is_null());
|
||||
let sighash =
|
||||
hex::decode("e8c7bdac77f6bb1f3aba2eaa1fada551a9c8b3b5ecd1ef86e6e58a5f1aab952c")
|
||||
.unwrap();
|
||||
assert!(sighash_out_len == sighash.len() as c_uint);
|
||||
std::ptr::copy_nonoverlapping(sighash.as_ptr(), sighash_out, sighash.len());
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn invalid_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,
|
||||
) {
|
||||
unsafe {
|
||||
assert!(ctx.is_null());
|
||||
let sighash =
|
||||
hex::decode("08c7bdac77f6bb1f3aba2eaa1fada551a9c8b3b5ecd1ef86e6e58a5f1aab952c")
|
||||
.unwrap();
|
||||
assert!(sighash_out_len == sighash.len() as c_uint);
|
||||
std::ptr::copy_nonoverlapping(sighash.as_ptr(), sighash_out, sighash.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let nLockTime: i64 = 2410374;
|
||||
let isFinal: u8 = 1;
|
||||
let script_pub_key = &*SCRIPT_PUBKEY;
|
||||
let script_sig = &*SCRIPT_SIG;
|
||||
let flags: c_uint = 513;
|
||||
let mut err = 0;
|
||||
|
||||
let ret = unsafe {
|
||||
super::zcash_script_verify_callback(
|
||||
std::ptr::null(),
|
||||
Some(sighash),
|
||||
nLockTime,
|
||||
isFinal,
|
||||
script_pub_key.as_ptr(),
|
||||
script_pub_key.len() as c_uint,
|
||||
script_sig.as_ptr(),
|
||||
script_sig.len() as c_uint,
|
||||
flags,
|
||||
&mut err,
|
||||
)
|
||||
};
|
||||
|
||||
assert!(ret == 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_fails_on_invalid_sighash() {
|
||||
let nLockTime: i64 = 2410374;
|
||||
let isFinal: u8 = 1;
|
||||
let script_pub_key = &*SCRIPT_PUBKEY;
|
||||
let script_sig = &*SCRIPT_SIG;
|
||||
let flags: c_uint = 513;
|
||||
let mut err = 0;
|
||||
|
||||
let ret = unsafe {
|
||||
super::zcash_script_verify_callback(
|
||||
std::ptr::null(),
|
||||
Some(invalid_sighash),
|
||||
nLockTime,
|
||||
isFinal,
|
||||
script_pub_key.as_ptr(),
|
||||
script_pub_key.len() as c_uint,
|
||||
script_sig.as_ptr(),
|
||||
script_sig.len() as c_uint,
|
||||
flags,
|
||||
&mut err,
|
||||
)
|
||||
};
|
||||
|
||||
assert!(ret != 1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
bitflags::bitflags! {
|
||||
/// The different SigHash types, as defined in <https://zips.z.cash/zip-0143>
|
||||
///
|
||||
/// TODO: This is currently defined as `i32` to match the `c_int` constants in this package, but
|
||||
/// should use librustzcash’s `u8` constants once we’ve removed the C++.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct HashType: i32 {
|
||||
/// Sign all the outputs
|
||||
const All = 1;
|
||||
/// Sign none of the outputs - anyone can spend
|
||||
const None = 2;
|
||||
/// Sign one of the outputs - anyone can spend the rest
|
||||
const Single = 3;
|
||||
/// Anyone can add inputs to this transaction
|
||||
const AnyoneCanPay = 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
/// Script verification flags
|
||||
pub struct VerificationFlags: u32 {
|
||||
/// Evaluate P2SH subscripts (softfork safe,
|
||||
/// [BIP16](https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki).
|
||||
const P2SH = 1 << 0;
|
||||
|
||||
/// Passing a non-strict-DER signature or one with undefined hashtype to a checksig operation causes script failure.
|
||||
/// Evaluating a pubkey that is not (0x04 + 64 bytes) or (0x02 or 0x03 + 32 bytes) by checksig causes script failure.
|
||||
/// (softfork safe, but not used or intended as a consensus rule).
|
||||
const StrictEnc = 1 << 1;
|
||||
|
||||
/// Passing a non-strict-DER signature or one with S > order/2 to a checksig operation causes script failure
|
||||
/// (softfork safe, [BIP62](https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki) rule 5).
|
||||
const LowS = 1 << 3;
|
||||
|
||||
/// verify dummy stack item consumed by CHECKMULTISIG is of zero-length (softfork safe, [BIP62](https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki) rule 7).
|
||||
const NullDummy = 1 << 4;
|
||||
|
||||
/// Using a non-push operator in the scriptSig causes script failure (softfork safe, [BIP62](https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki) rule 2).
|
||||
const SigPushOnly = 1 << 5;
|
||||
|
||||
/// Require minimal encodings for all push operations (OP_0... OP_16, OP_1NEGATE where possible, direct
|
||||
/// pushes up to 75 bytes, OP_PUSHDATA up to 255 bytes, OP_PUSHDATA2 for anything larger). Evaluating
|
||||
/// any other push causes the script to fail ([BIP62](https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki) rule 3).
|
||||
/// In addition, whenever a stack element is interpreted as a number, it must be of minimal length ([BIP62](https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki) rule 4).
|
||||
/// (softfork safe)
|
||||
const MinimalData = 1 << 6;
|
||||
|
||||
/// Discourage use of NOPs reserved for upgrades (NOP1-10)
|
||||
///
|
||||
/// Provided so that nodes can avoid accepting or mining transactions
|
||||
/// containing executed NOP's whose meaning may change after a soft-fork,
|
||||
/// thus rendering the script invalid; with this flag set executing
|
||||
/// discouraged NOPs fails the script. This verification flag will never be
|
||||
/// a mandatory flag applied to scripts in a block. NOPs that are not
|
||||
/// executed, e.g. within an unexecuted IF ENDIF block, are *not* rejected.
|
||||
const DiscourageUpgradableNOPs = 1 << 7;
|
||||
|
||||
/// Require that only a single stack element remains after evaluation. This changes the success criterion from
|
||||
/// "At least one stack element must remain, and when interpreted as a boolean, it must be true" to
|
||||
/// "Exactly one stack element must remain, and when interpreted as a boolean, it must be true".
|
||||
/// (softfork safe, [BIP62](https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki) rule 6)
|
||||
/// Note: CLEANSTACK should never be used without P2SH.
|
||||
const CleanStack = 1 << 8;
|
||||
|
||||
/// Verify CHECKLOCKTIMEVERIFY
|
||||
///
|
||||
/// See [BIP65](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki) for details.
|
||||
const CHECKLOCKTIMEVERIFY = 1 << 9;
|
||||
}
|
||||
}
|
260
src/lib.rs
260
src/lib.rs
|
@ -1,23 +1,117 @@
|
|||
//! Rust bindings for Zcash transparent scripts.
|
||||
//! Zcash transparent script implementations.
|
||||
|
||||
#![doc(html_logo_url = "https://www.zfnd.org/images/zebra-icon.png")]
|
||||
#![doc(html_root_url = "https://docs.rs/zcash_script/0.2.0")]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(clippy::needless_lifetimes)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![doc(html_root_url = "https://docs.rs/zcash_script/0.3.0")]
|
||||
#![allow(unsafe_code)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(clippy::unwrap_or_default)]
|
||||
|
||||
// Use the generated C++ bindings
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
mod cxx;
|
||||
pub use cxx::*;
|
||||
|
||||
mod interpreter;
|
||||
pub use interpreter::{HashType, VerificationFlags};
|
||||
mod zcash_script;
|
||||
pub use zcash_script::*;
|
||||
|
||||
use std::os::raw::{c_int, c_uint, c_void};
|
||||
|
||||
/// A tag to indicate that the C++ implementation of zcash_script should be used.
|
||||
pub enum Cxx {}
|
||||
|
||||
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::Ok,
|
||||
zcash_script_error_t_zcash_script_ERR_VERIFY_SCRIPT => Error::VerifyScript,
|
||||
unknown => Error::Unknown(unknown.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The sighash callback to use with zcash_script.
|
||||
extern "C" fn sighash_callback(
|
||||
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,
|
||||
) {
|
||||
let checked_script_code_len = usize::try_from(script_code_len)
|
||||
.expect("This was converted from a `usize` in the first place");
|
||||
// SAFETY: `script_code` is created from a Rust slice in `verify_callback`, passed through the
|
||||
// C++ code, eventually to `CallbackTransactionSignatureChecker::CheckSig`, which calls this
|
||||
// function.
|
||||
let script_code_vec =
|
||||
unsafe { std::slice::from_raw_parts(script_code, checked_script_code_len) };
|
||||
let ctx = ctx as *const SighashCalculator;
|
||||
// SAFETY: `ctx` is a valid `SighashCalculator` passed to `verify_callback` which forwards it to
|
||||
// the `CallbackTransactionSignatureChecker`.
|
||||
if let Some(sighash) = unsafe { *ctx }(script_code_vec, HashType::from_bits_retain(hash_type)) {
|
||||
assert_eq!(sighash_out_len, sighash.len().try_into().unwrap());
|
||||
// SAFETY: `sighash_out` is a valid buffer created in
|
||||
// `CallbackTransactionSignatureChecker::CheckSig`.
|
||||
unsafe { std::ptr::copy_nonoverlapping(sighash.as_ptr(), sighash_out, sighash.len()) };
|
||||
}
|
||||
}
|
||||
|
||||
/// This steals a bit of the wrapper code from zebra_script, to provide the API that they want.
|
||||
impl ZcashScript for Cxx {
|
||||
fn verify_callback(
|
||||
sighash: SighashCalculator,
|
||||
lock_time: i64,
|
||||
is_final: bool,
|
||||
script_pub_key: &[u8],
|
||||
signature_script: &[u8],
|
||||
flags: VerificationFlags,
|
||||
) -> Result<(), Error> {
|
||||
let mut err = 0;
|
||||
|
||||
// SAFETY: The `script` fields are created from a valid Rust `slice`.
|
||||
let ret = unsafe {
|
||||
zcash_script_verify_callback(
|
||||
(&sighash as *const SighashCalculator) as *const c_void,
|
||||
Some(sighash_callback),
|
||||
lock_time,
|
||||
if is_final { 1 } else { 0 },
|
||||
script_pub_key.as_ptr(),
|
||||
script_pub_key
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(Error::InvalidScriptSize)?,
|
||||
signature_script.as_ptr(),
|
||||
signature_script
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(Error::InvalidScriptSize)?,
|
||||
flags.bits(),
|
||||
&mut err,
|
||||
)
|
||||
};
|
||||
|
||||
if ret == 1 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::from(err))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of transparent signature operations in the
|
||||
/// transparent inputs and outputs of this transaction.
|
||||
fn legacy_sigop_count_script(script: &[u8]) -> Result<u32, Error> {
|
||||
script
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(Error::InvalidScriptSize)
|
||||
.map(|script_len| unsafe {
|
||||
zcash_script_legacy_sigop_count_script(script.as_ptr(), script_len)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ffi::{c_int, c_uint, c_void};
|
||||
|
||||
pub use super::zcash_script_error_t;
|
||||
pub use super::*;
|
||||
use hex::FromHex;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
|
@ -25,93 +119,83 @@ mod tests {
|
|||
pub static ref SCRIPT_SIG: Vec<u8> = <Vec<u8>>::from_hex("00483045022100d2ab3e6258fe244fa442cfb38f6cef9ac9a18c54e70b2f508e83fa87e20d040502200eead947521de943831d07a350e45af8e36c2166984a8636f0a8811ff03ed09401473044022013e15d865010c257eef133064ef69a780b4bc7ebe6eda367504e806614f940c3022062fdbc8c2d049f91db2042d6c9771de6f1ef0b3b1fea76c1ab5542e44ed29ed8014c69522103b2cc71d23eb30020a4893982a1e2d352da0d20ee657fa02901c432758909ed8f21029d1e9a9354c0d2aee9ffd0f0cea6c39bbf98c4066cf143115ba2279d0ba7dabe2103e32096b63fd57f3308149d238dcbb24d8d28aad95c0e4e74e3e5e6a11b61bcc453ae").expect("Block bytes are in valid hex representation");
|
||||
}
|
||||
|
||||
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,
|
||||
) {
|
||||
unsafe {
|
||||
assert!(ctx.is_null());
|
||||
let sighash =
|
||||
hex::decode("e8c7bdac77f6bb1f3aba2eaa1fada551a9c8b3b5ecd1ef86e6e58a5f1aab952c")
|
||||
.unwrap();
|
||||
assert!(sighash_out_len == sighash.len() as c_uint);
|
||||
std::ptr::copy_nonoverlapping(sighash.as_ptr(), sighash_out, sighash.len());
|
||||
}
|
||||
fn sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> {
|
||||
hex::decode("e8c7bdac77f6bb1f3aba2eaa1fada551a9c8b3b5ecd1ef86e6e58a5f1aab952c")
|
||||
.unwrap()
|
||||
.as_slice()
|
||||
.first_chunk::<32>()
|
||||
.map(|hash| *hash)
|
||||
}
|
||||
|
||||
extern "C" fn invalid_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,
|
||||
) {
|
||||
unsafe {
|
||||
assert!(ctx.is_null());
|
||||
let sighash =
|
||||
hex::decode("08c7bdac77f6bb1f3aba2eaa1fada551a9c8b3b5ecd1ef86e6e58a5f1aab952c")
|
||||
.unwrap();
|
||||
assert!(sighash_out_len == sighash.len() as c_uint);
|
||||
std::ptr::copy_nonoverlapping(sighash.as_ptr(), sighash_out, sighash.len());
|
||||
}
|
||||
fn invalid_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> {
|
||||
hex::decode("08c7bdac77f6bb1f3aba2eaa1fada551a9c8b3b5ecd1ef86e6e58a5f1aab952c")
|
||||
.unwrap()
|
||||
.as_slice()
|
||||
.first_chunk::<32>()
|
||||
.map(|hash| *hash)
|
||||
}
|
||||
|
||||
fn missing_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> {
|
||||
None
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let nLockTime: i64 = 2410374;
|
||||
let isFinal: u8 = 1;
|
||||
let script_pub_key = &*SCRIPT_PUBKEY;
|
||||
let script_sig = &*SCRIPT_SIG;
|
||||
let flags: c_uint = 513;
|
||||
let mut err = 0;
|
||||
let n_lock_time: i64 = 2410374;
|
||||
let is_final: bool = true;
|
||||
let script_pub_key = &SCRIPT_PUBKEY;
|
||||
let script_sig = &SCRIPT_SIG;
|
||||
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
|
||||
|
||||
let ret = unsafe {
|
||||
super::zcash_script_verify_callback(
|
||||
std::ptr::null(),
|
||||
Some(sighash),
|
||||
nLockTime,
|
||||
isFinal,
|
||||
script_pub_key.as_ptr(),
|
||||
script_pub_key.len() as c_uint,
|
||||
script_sig.as_ptr(),
|
||||
script_sig.len() as c_uint,
|
||||
flags,
|
||||
&mut err,
|
||||
)
|
||||
};
|
||||
let ret = Cxx::verify_callback(
|
||||
&sighash,
|
||||
n_lock_time,
|
||||
is_final,
|
||||
script_pub_key,
|
||||
script_sig,
|
||||
flags,
|
||||
);
|
||||
|
||||
assert!(ret == 1);
|
||||
assert!(ret.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_fails_on_invalid_sighash() {
|
||||
let nLockTime: i64 = 2410374;
|
||||
let isFinal: u8 = 1;
|
||||
let script_pub_key = &*SCRIPT_PUBKEY;
|
||||
let script_sig = &*SCRIPT_SIG;
|
||||
let flags: c_uint = 513;
|
||||
let mut err = 0;
|
||||
let n_lock_time: i64 = 2410374;
|
||||
let is_final: bool = true;
|
||||
let script_pub_key = &SCRIPT_PUBKEY;
|
||||
let script_sig = &SCRIPT_SIG;
|
||||
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
|
||||
|
||||
let ret = unsafe {
|
||||
super::zcash_script_verify_callback(
|
||||
std::ptr::null(),
|
||||
Some(invalid_sighash),
|
||||
nLockTime,
|
||||
isFinal,
|
||||
script_pub_key.as_ptr(),
|
||||
script_pub_key.len() as c_uint,
|
||||
script_sig.as_ptr(),
|
||||
script_sig.len() as c_uint,
|
||||
flags,
|
||||
&mut err,
|
||||
)
|
||||
};
|
||||
let ret = Cxx::verify_callback(
|
||||
&invalid_sighash,
|
||||
n_lock_time,
|
||||
is_final,
|
||||
script_pub_key,
|
||||
script_sig,
|
||||
flags,
|
||||
);
|
||||
|
||||
assert!(ret != 1);
|
||||
assert_eq!(ret, Err(Error::Ok));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_fails_on_missing_sighash() {
|
||||
let n_lock_time: i64 = 2410374;
|
||||
let is_final: bool = true;
|
||||
let script_pub_key = &SCRIPT_PUBKEY;
|
||||
let script_sig = &SCRIPT_SIG;
|
||||
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
|
||||
|
||||
let ret = Cxx::verify_callback(
|
||||
&missing_sighash,
|
||||
n_lock_time,
|
||||
is_final,
|
||||
script_pub_key,
|
||||
script_sig,
|
||||
flags,
|
||||
);
|
||||
|
||||
assert_eq!(ret, Err(Error::Ok));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
use std::num::TryFromIntError;
|
||||
|
||||
use super::interpreter::*;
|
||||
|
||||
/// 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
|
||||
/// happen.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
pub enum Error {
|
||||
/// Any failure that results in the script being invalid.
|
||||
Ok = 0,
|
||||
/// An exception was caught.
|
||||
VerifyScript = 7,
|
||||
/// The script size can’t fit in a `u32`, as required by the C++ code.
|
||||
InvalidScriptSize(TryFromIntError),
|
||||
/// Some other failure value recovered from C++.
|
||||
///
|
||||
/// __NB__: Linux uses `u32` for the underlying C++ enum while Windows uses `i32`, so `i64` can
|
||||
/// hold either.
|
||||
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 {
|
||||
/// Returns `Ok(())` if the a transparent input correctly spends the matching output
|
||||
/// under the additional constraints specified by `flags`. This function
|
||||
/// receives only the required information to validate the spend and not
|
||||
/// the transaction itself. In particular, the sighash for the spend
|
||||
/// is obtained using a callback function.
|
||||
///
|
||||
/// - sighash_callback: a callback function which is called to obtain the sighash.
|
||||
/// - n_lock_time: the lock time of the transaction being validated.
|
||||
/// - is_final: a boolean indicating whether the input being validated is final
|
||||
/// (i.e. its sequence number is 0xFFFFFFFF).
|
||||
/// - script_pub_key: the scriptPubKey of the output being spent.
|
||||
/// - script_sig: the scriptSig of the input being validated.
|
||||
/// - flags: the script verification flags to use.
|
||||
///
|
||||
/// Note that script verification failure is indicated by `Err(Error::Ok)`.
|
||||
fn verify_callback(
|
||||
sighash: SighashCalculator,
|
||||
n_lock_time: i64,
|
||||
is_final: bool,
|
||||
script_pub_key: &[u8],
|
||||
script_sig: &[u8],
|
||||
flags: VerificationFlags,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
/// Returns the number of transparent signature operations in the input or
|
||||
/// output script pointed to by script.
|
||||
fn legacy_sigop_count_script(script: &[u8]) -> Result<u32, Error>;
|
||||
}
|
Loading…
Reference in New Issue