Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a Rustier wrapper for zcash_script #171

Merged
merged 7 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
115 changes: 115 additions & 0 deletions src/cxx.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
71 changes: 71 additions & 0 deletions src/interpreter.rs
Original file line number Diff line number Diff line change
@@ -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;
}
}
Comment on lines +1 to +17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's incorrect to represent HashType as purely bitflags, because the lower bits are not valid to interpret as flags. That is, HashType::All | HashType::None should not be considered equal to HashType::Single, but that is what this type enables.

It's not immediately obvious to me where the boundary between "upper bitflags" and "lower type number" should be, but that should be figured out at some point.


bitflags::bitflags! {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
/// Script verification flags
pub struct VerificationFlags: u32 {
sellout marked this conversation as resolved.
Show resolved Hide resolved
/// 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;
}
}
Loading
Loading