Skip to content

Commit

Permalink
Provide a Rustier wrapper for zcash_script
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
sellout committed Aug 9, 2024
1 parent c9d7507 commit 3c353bd
Show file tree
Hide file tree
Showing 4 changed files with 327 additions and 13 deletions.
11 changes: 6 additions & 5 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "zcash_script"
version = "0.2.0"
version = "0.3.0"
authors = ["Tamas Blummer <[email protected]>", "Zcash Foundation <[email protected]>"]
license = "Apache-2.0"
readme = "README.md"
Expand Down Expand Up @@ -60,6 +60,7 @@ path = "src/lib.rs"
external-secp = []

[dependencies]
bitflags = "2.5.0"

[build-dependencies]
# The `bindgen` dependency should automatically upgrade to match the version used by zebra-state's `rocksdb` dependency in:
Expand Down
130 changes: 130 additions & 0 deletions src/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/// 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 {
Ok = 0,
VerifyScript = 7,
Unknown(u32),
}

bitflags::bitflags! {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct VerificationFlags: u32 {
const None = 0;

/// Evaluate P2SH subscripts (softfork safe, BIP16).
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 rule 5).
const LowS = 1 << 3;

/// verify dummy stack item consumed by CHECKMULTISIG is of zero-length (softfork safe, BIP62 rule 7).
const NullDummy = 1 << 4;

/// Using a non-push operator in the scriptSig causes script failure (softfork safe, BIP62 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 rule 3).
/// In addition, whenever a stack element is interpreted as a number, it must be of minimal length (BIP62 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 rule 6)
/// Note: CLEANSTACK should never be used without P2SH.
const CleanStack = 1 << 8;

/// Verify CHECKLOCKTIMEVERIFY
///
/// See BIP65 for details.
const CHECKLOCKTIMEVERIFY = 1 << 9;
}
}

bitflags::bitflags! {
/// The different SigHash types, as defined in <https://zips.z.cash/zip-0143>
///
/// TODO: There are three implementations of this (with three distinct primitive types):
/// - u8 constants in librustzcash,
/// - i32 (well, c_int) bitflags from the C++ constants here, and
/// - u32 bitflags in zebra-chain.
///
/// Ideally we could unify on bitflags in librustzcash.
#[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 = Self::All.bits() | Self::None.bits();
/// Anyone can add inputs to this transaction
const AnyoneCanPay = 0x80;
}
}

/// 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 underlying C++ callback 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 SighashCallback = dyn Fn(&[u8], HashType) -> Option<[u8; 32]>;

/// The external API of zcash_script. This is defined to make it possible to compare the C++ and
/// Rust implementations.
pub trait Script {
/// 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.
/// - err: if not NULL, err will contain an error/success code for the operation.
///
/// Note that script verification failure is indicated by `Err(Error::Ok)`.
fn verify_callback(
sighash_callback: &SighashCallback,
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]) -> u32;
}
Loading

0 comments on commit 3c353bd

Please sign in to comment.