Initial Rust implementation (#174)
* [DRAFT] * Rearrange Rust impl to match C++ more closely The only changes here other than moving chunks of code around are - moving `evaluate` out of `impl Script`, which required changing `&self` to `script: &Script`; and - unifying `ExecutionOptions` with `VerificationFlags`. * Rename Rust identifiers to match C++ For easier side-by-side comparison. * Connected the new API, but fails * Existing unit tests succeed * The rest of the owl * Reverting to C++ style, and some other changes * Appease Clippy * Replace `ScriptNum` panics with error case The C++ impl uses exceptions for `ScriptNum`, but catches them. * Add some shallow property tests These tests run both the C++ and Rust impls. One uses completely arbitrary inputs, and the other limits script_sig to data pushes. * Add shallow fuzz testing * Preserve richer errors on the Rust side For now, the underlying errors are discarded when comparing against the C++ results, but there are corresponding changes on the C++ side in a separate branch. * Address @nuttycom’s review comments - remove `uint256` module - create a specific type for the C++/Rust comparison implementation - rename some identifiers - rephrase some comments * Some changes to ease zebrad integration - Switch from `log` to `tracing` for `warn!`, - Export `SignedOutputs`, which is needed to create a `HashType`, and - Upgrade zcash_primitives to the same version used by zebrad. * Appease Clippy * Remove dependency on zcash_primitives This was only needed for the `TxVersion` enum. However, the `StrictEnc` flag is a proxy for the v5 tx requirements, so instead of checking the `TxVersion` explicitly, we expect callers to include `StrictEnc` for verification of v5 transactions. * Moving testing dependencies libfuzzer-sys is Linux-specific, and it & proptest are only used for tests. * Normalize Rust errors in comparison interpreter This was a minor oversight, but this correction should only eliminate false mismatches. * Address @nuttycom’s PR feedback * Eliminate a `panic!` This `panic!` appears to be unreachable in the current implementation, but there is no need for it. It doesn’t introduce any new failure cases. Thanks to @conradoplg for noticing it. * Use (`Try`)`From` for `ScriptNum` conversions This also makes the `ScriptNum` field private so that `bn.0` can’t extract the unconstrained `i64` value. * Remove some `From` instances that do too much * Subtract from `OP_RESERVED` instead of `OP_1 - 1` `OP_RESERVED` is in the ‘0’ offset position of the `OP_n` opcodes. Just use this even though it isn’t obviously a number to improve readability. --------- Co-authored-by: Sean Bowe <ewillbefull@gmail.com> Co-authored-by: Conrado Gouvea <conradoplg@gmail.com>
This commit is contained in:
parent
4edd9009a2
commit
335ae9a2a6
|
@ -123,3 +123,24 @@ jobs:
|
|||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
|
||||
fuzz:
|
||||
name: Fuzz
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- nightly
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
- run: cargo install cargo-fuzz
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fuzz
|
||||
args: run compare -- -max_len=20000 -max_total_time=100
|
||||
|
|
File diff suppressed because it is too large
Load Diff
13
Cargo.toml
13
Cargo.toml
|
@ -15,6 +15,7 @@ include = [
|
|||
"/README.md",
|
||||
"build.rs",
|
||||
"src/*.rs",
|
||||
"src/*/*.rs",
|
||||
"/depend/check_uint128_t.c",
|
||||
"/depend/zcash/src/amount.cpp",
|
||||
"/depend/zcash/src/amount.h",
|
||||
|
@ -58,10 +59,16 @@ path = "src/lib.rs"
|
|||
|
||||
[features]
|
||||
external-secp = []
|
||||
test-dependencies = []
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.5"
|
||||
zcash_primitives = "0.17"
|
||||
enum_primitive = "0.1"
|
||||
ripemd = "0.1"
|
||||
secp256k1 = "0.29"
|
||||
sha-1 = "0.10"
|
||||
sha2 = "0.10"
|
||||
tracing = "0.1.39"
|
||||
|
||||
[build-dependencies]
|
||||
# The `bindgen` dependency should automatically upgrade to match the version used by zebra-state's `rocksdb` dependency in:
|
||||
|
@ -82,6 +89,10 @@ cc = { version = "1.1.10", features = ["parallel"] }
|
|||
# Treat minor versions with a zero major version as compatible (cargo doesn't by default).
|
||||
hex = ">= 0.4.3"
|
||||
lazy_static = "1.5.0"
|
||||
proptest = "0.9"
|
||||
|
||||
[target.'cfg(linux)'.dev-dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
|
||||
[[package.metadata.release.pre-release-replacements]]
|
||||
file = "CHANGELOG.md"
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
target
|
||||
corpus
|
||||
artifacts
|
||||
coverage
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "zcash_script-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
zcash_script = { path = "..", features = ["test-dependencies"] }
|
||||
|
||||
[[bin]]
|
||||
name = "compare"
|
||||
path = "fuzz_targets/compare.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
|
@ -0,0 +1,29 @@
|
|||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
extern crate zcash_script;
|
||||
|
||||
use zcash_script::*;
|
||||
|
||||
fn missing_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> {
|
||||
None
|
||||
}
|
||||
|
||||
fuzz_target!(|tup: (i64, bool, &[u8], &[u8], u32)| {
|
||||
// `fuzz_target!` doesn’t support pattern matching in the parameter list.
|
||||
let (lock_time, is_final, pub_key, sig, flags) = tup;
|
||||
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
|
||||
&missing_sighash,
|
||||
lock_time,
|
||||
is_final,
|
||||
pub_key,
|
||||
sig,
|
||||
testing::repair_flags(VerificationFlags::from_bits_truncate(flags)),
|
||||
);
|
||||
assert_eq!(
|
||||
ret.0,
|
||||
ret.1.map_err(normalize_error),
|
||||
"original Rust result: {:?}",
|
||||
ret.1
|
||||
);
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
//! Modules that parallel the C++ implementation, but which live outside the script directory.
|
||||
|
||||
pub mod pubkey;
|
|
@ -0,0 +1,56 @@
|
|||
use secp256k1::{ecdsa, Message, PublicKey, Secp256k1};
|
||||
|
||||
/// FIXME: `PUBLIC_KEY_SIZE` is meant to be an upper bound, it seems. Maybe parameterize the type
|
||||
/// over the size.
|
||||
pub struct PubKey<'a>(pub &'a [u8]);
|
||||
|
||||
impl PubKey<'_> {
|
||||
pub const PUBLIC_KEY_SIZE: usize = 65;
|
||||
pub const COMPRESSED_PUBLIC_KEY_SIZE: usize = 33;
|
||||
|
||||
/// Check syntactic correctness.
|
||||
///
|
||||
/// Note that this is consensus critical as CheckSig() calls it!
|
||||
pub fn is_valid(&self) -> bool {
|
||||
!self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Verify a DER signature (~72 bytes).
|
||||
/// If this public key is not fully valid, the return value will be false.
|
||||
pub fn verify(&self, hash: &[u8; 32], vch_sig: &[u8]) -> bool {
|
||||
if !self.is_valid() {
|
||||
return false;
|
||||
};
|
||||
|
||||
if let Ok(pubkey) = PublicKey::from_slice(self.0) {
|
||||
// let sig: secp256k1_ecdsa_signature;
|
||||
if vch_sig.is_empty() {
|
||||
return false;
|
||||
};
|
||||
// Zcash, unlike Bitcoin, has always enforced strict DER signatures.
|
||||
if let Ok(mut sig) = ecdsa::Signature::from_der(vch_sig) {
|
||||
// libsecp256k1's ECDSA verification requires lower-S signatures, which have
|
||||
// not historically been enforced in Bitcoin or Zcash, so normalize them first.
|
||||
sig.normalize_s();
|
||||
let secp = Secp256k1::verification_only();
|
||||
secp.verify_ecdsa(&Message::from_digest(*hash), &sig, &pubkey)
|
||||
.is_ok()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_low_s(vch_sig: &[u8]) -> bool {
|
||||
/* Zcash, unlike Bitcoin, has always enforced strict DER signatures. */
|
||||
if let Ok(sig) = ecdsa::Signature::from_der(vch_sig) {
|
||||
let mut check = sig;
|
||||
check.normalize_s();
|
||||
sig == check
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
1219
src/interpreter.rs
1219
src/interpreter.rs
File diff suppressed because it is too large
Load Diff
238
src/lib.rs
238
src/lib.rs
|
@ -3,27 +3,32 @@
|
|||
#![doc(html_logo_url = "https://www.zfnd.org/images/zebra-icon.png")]
|
||||
#![doc(html_root_url = "https://docs.rs/zcash_script/0.3.0")]
|
||||
#![allow(unsafe_code)]
|
||||
#[macro_use]
|
||||
extern crate enum_primitive;
|
||||
|
||||
mod cxx;
|
||||
pub use cxx::*;
|
||||
|
||||
mod external;
|
||||
mod interpreter;
|
||||
pub use interpreter::{HashType, VerificationFlags};
|
||||
mod script;
|
||||
pub mod script_error;
|
||||
mod zcash_script;
|
||||
pub use zcash_script::*;
|
||||
|
||||
use std::os::raw::{c_int, c_uint, c_void};
|
||||
|
||||
use zcash_primitives::transaction::TxVersion;
|
||||
use tracing::warn;
|
||||
|
||||
pub use cxx::*;
|
||||
pub use interpreter::{HashType, SighashCalculator, SignedOutputs, VerificationFlags};
|
||||
pub use zcash_script::*;
|
||||
|
||||
/// A tag to indicate that the C++ implementation of zcash_script should be used.
|
||||
pub enum Cxx {}
|
||||
pub enum CxxInterpreter {}
|
||||
|
||||
impl From<zcash_script_error_t> for Error {
|
||||
#[allow(non_upper_case_globals)]
|
||||
fn from(err_code: zcash_script_error_t) -> Error {
|
||||
fn from(err_code: zcash_script_error_t) -> Self {
|
||||
match err_code {
|
||||
zcash_script_error_t_zcash_script_ERR_OK => Error::Ok,
|
||||
zcash_script_error_t_zcash_script_ERR_OK => Error::Ok(None),
|
||||
zcash_script_error_t_zcash_script_ERR_VERIFY_SCRIPT => Error::VerifyScript,
|
||||
unknown => Error::Unknown(unknown.into()),
|
||||
}
|
||||
|
@ -46,10 +51,11 @@ extern "C" fn sighash_callback(
|
|||
// function.
|
||||
let script_code_vec =
|
||||
unsafe { std::slice::from_raw_parts(script_code, checked_script_code_len) };
|
||||
// SAFETY: `ctx` is a valid `(SighashCalculator, TxVersion)` constructed in `verify_callback`
|
||||
// SAFETY: `ctx` is a valid `SighashCalculator` constructed in `verify_callback`
|
||||
// which forwards it to the `CallbackTransactionSignatureChecker`.
|
||||
let (callback, tx_version) = unsafe { *(ctx as *const (SighashCalculator, TxVersion)) };
|
||||
if let Some(sighash) = HashType::from_bits(hash_type, tx_version)
|
||||
let callback = unsafe { *(ctx as *const SighashCalculator) };
|
||||
// We don’t need to handle strictness here, because … something
|
||||
if let Some(sighash) = HashType::from_bits(hash_type, false)
|
||||
.ok()
|
||||
.and_then(|ht| callback(script_code_vec, ht))
|
||||
{
|
||||
|
@ -61,7 +67,7 @@ extern "C" fn sighash_callback(
|
|||
}
|
||||
|
||||
/// This steals a bit of the wrapper code from zebra_script, to provide the API that they want.
|
||||
impl ZcashScript for Cxx {
|
||||
impl ZcashScript for CxxInterpreter {
|
||||
fn verify_callback(
|
||||
sighash: SighashCalculator,
|
||||
lock_time: i64,
|
||||
|
@ -69,14 +75,13 @@ impl ZcashScript for Cxx {
|
|||
script_pub_key: &[u8],
|
||||
signature_script: &[u8],
|
||||
flags: VerificationFlags,
|
||||
tx_version: TxVersion,
|
||||
) -> 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, tx_version) as *const (SighashCalculator, TxVersion)) as *const c_void,
|
||||
(&sighash as *const SighashCalculator) as *const c_void,
|
||||
Some(sighash_callback),
|
||||
lock_time,
|
||||
if is_final { 1 } else { 0 },
|
||||
|
@ -115,10 +120,131 @@ impl ZcashScript for Cxx {
|
|||
}
|
||||
}
|
||||
|
||||
/// Runs both the C++ and Rust implementations `ZcashScript::legacy_sigop_count_script` and returns
|
||||
/// both results. This is more useful for testing than the impl that logs a warning if the results
|
||||
/// differ and always returns the C++ result.
|
||||
fn check_legacy_sigop_count_script<T: ZcashScript, U: ZcashScript>(
|
||||
script: &[u8],
|
||||
) -> (Result<u32, Error>, Result<u32, Error>) {
|
||||
(
|
||||
T::legacy_sigop_count_script(script),
|
||||
U::legacy_sigop_count_script(script),
|
||||
)
|
||||
}
|
||||
|
||||
/// Runs two implementations of `ZcashScript::verify_callback` with the same arguments and returns
|
||||
/// both results. This is more useful for testing than the impl that logs a warning if the results
|
||||
/// differ and always returns the `T` result.
|
||||
pub fn check_verify_callback<T: ZcashScript, U: ZcashScript>(
|
||||
sighash: SighashCalculator,
|
||||
lock_time: i64,
|
||||
is_final: bool,
|
||||
script_pub_key: &[u8],
|
||||
script_sig: &[u8],
|
||||
flags: VerificationFlags,
|
||||
) -> (Result<(), Error>, Result<(), Error>) {
|
||||
(
|
||||
T::verify_callback(
|
||||
sighash,
|
||||
lock_time,
|
||||
is_final,
|
||||
script_pub_key,
|
||||
script_sig,
|
||||
flags,
|
||||
),
|
||||
U::verify_callback(
|
||||
sighash,
|
||||
lock_time,
|
||||
is_final,
|
||||
script_pub_key,
|
||||
script_sig,
|
||||
flags,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert errors that don’t exist in the C++ code into the cases that do.
|
||||
pub fn normalize_error(err: Error) -> Error {
|
||||
match err {
|
||||
Error::Ok(Some(_)) => Error::Ok(None),
|
||||
_ => err,
|
||||
}
|
||||
}
|
||||
|
||||
/// A tag to indicate that both the C++ and Rust implementations of zcash_script should be used,
|
||||
/// with their results compared.
|
||||
pub enum CxxRustComparisonInterpreter {}
|
||||
|
||||
/// This implementation is functionally equivalent to the `T` impl, but it also runs a second (`U`)
|
||||
/// impl and logs a warning if they disagree.
|
||||
impl ZcashScript for CxxRustComparisonInterpreter {
|
||||
fn legacy_sigop_count_script(script: &[u8]) -> Result<u32, Error> {
|
||||
let (cxx, rust) =
|
||||
check_legacy_sigop_count_script::<CxxInterpreter, RustInterpreter>(script);
|
||||
if rust != cxx {
|
||||
warn!(
|
||||
"The Rust Zcash Script interpreter had a different sigop count ({:?}) from the C++ one ({:?}).",
|
||||
rust,
|
||||
cxx)
|
||||
};
|
||||
cxx
|
||||
}
|
||||
|
||||
fn verify_callback(
|
||||
sighash: SighashCalculator,
|
||||
lock_time: i64,
|
||||
is_final: bool,
|
||||
script_pub_key: &[u8],
|
||||
script_sig: &[u8],
|
||||
flags: VerificationFlags,
|
||||
) -> Result<(), Error> {
|
||||
let (cxx, rust) = check_verify_callback::<CxxInterpreter, RustInterpreter>(
|
||||
sighash,
|
||||
lock_time,
|
||||
is_final,
|
||||
script_pub_key,
|
||||
script_sig,
|
||||
flags,
|
||||
);
|
||||
if rust.map_err(normalize_error) != cxx {
|
||||
// probably want to distinguish between
|
||||
// - C++ succeeding when Rust fails (bad),
|
||||
// - Rust succeeding when C++ fals (worse), and
|
||||
// - differing error codes (maybe not bad).
|
||||
warn!(
|
||||
"The Rust Zcash Script interpreter had a different result ({:?}) from the C++ one ({:?}).",
|
||||
rust,
|
||||
cxx)
|
||||
};
|
||||
cxx
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
pub mod testing {
|
||||
use super::*;
|
||||
|
||||
/// Ensures that flags represent a supported state. This avoids crashes in the C++ code, which
|
||||
/// break various tests.
|
||||
pub fn repair_flags(flags: VerificationFlags) -> VerificationFlags {
|
||||
// TODO: The C++ implementation fails an assert (interpreter.cpp:1097) if `CleanStack` is
|
||||
// set without `P2SH`.
|
||||
if flags.contains(VerificationFlags::CleanStack) {
|
||||
flags & VerificationFlags::P2SH
|
||||
} else {
|
||||
flags
|
||||
}
|
||||
}
|
||||
|
||||
/// A `usize` one larger than the longest allowed script, for testing bounds.
|
||||
pub const OVERFLOW_SCRIPT_SIZE: usize = script::MAX_SCRIPT_SIZE + 1;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub use super::*;
|
||||
use super::{testing::*, *};
|
||||
use hex::FromHex;
|
||||
use proptest::prelude::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref SCRIPT_PUBKEY: Vec<u8> = <Vec<u8>>::from_hex("a914c117756dcbe144a12a7c33a77cfa81aa5aeeb38187").unwrap();
|
||||
|
@ -130,7 +256,7 @@ mod tests {
|
|||
.unwrap()
|
||||
.as_slice()
|
||||
.first_chunk::<32>()
|
||||
.map(|hash| *hash)
|
||||
.copied()
|
||||
}
|
||||
|
||||
fn invalid_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> {
|
||||
|
@ -138,7 +264,7 @@ mod tests {
|
|||
.unwrap()
|
||||
.as_slice()
|
||||
.first_chunk::<32>()
|
||||
.map(|hash| *hash)
|
||||
.copied()
|
||||
}
|
||||
|
||||
fn missing_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> {
|
||||
|
@ -153,17 +279,17 @@ mod tests {
|
|||
let script_sig = &SCRIPT_SIG;
|
||||
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
|
||||
|
||||
let ret = Cxx::verify_callback(
|
||||
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
|
||||
&sighash,
|
||||
n_lock_time,
|
||||
is_final,
|
||||
script_pub_key,
|
||||
script_sig,
|
||||
flags,
|
||||
TxVersion::Sapling,
|
||||
);
|
||||
|
||||
assert!(ret.is_ok());
|
||||
assert_eq!(ret.0, ret.1.map_err(normalize_error));
|
||||
assert!(ret.0.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -174,17 +300,21 @@ mod tests {
|
|||
let script_sig = &SCRIPT_SIG;
|
||||
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
|
||||
|
||||
let ret = Cxx::verify_callback(
|
||||
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
|
||||
&invalid_sighash,
|
||||
n_lock_time,
|
||||
is_final,
|
||||
script_pub_key,
|
||||
script_sig,
|
||||
flags,
|
||||
TxVersion::Sapling,
|
||||
);
|
||||
|
||||
assert_eq!(ret, Err(Error::Ok));
|
||||
assert_eq!(ret.0, ret.1.map_err(normalize_error));
|
||||
// Checks the Rust result, because we have more information on the Rust side.
|
||||
assert_eq!(
|
||||
ret.1,
|
||||
Err(Error::Ok(Some(script_error::ScriptError::EvalFalse)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -195,16 +325,72 @@ mod tests {
|
|||
let script_sig = &SCRIPT_SIG;
|
||||
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
|
||||
|
||||
let ret = Cxx::verify_callback(
|
||||
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
|
||||
&missing_sighash,
|
||||
n_lock_time,
|
||||
is_final,
|
||||
script_pub_key,
|
||||
script_sig,
|
||||
flags,
|
||||
TxVersion::Sapling,
|
||||
);
|
||||
|
||||
assert_eq!(ret, Err(Error::Ok));
|
||||
assert_eq!(ret.0, ret.1.map_err(normalize_error));
|
||||
// Checks the Rust result, because we have more information on the Rust side.
|
||||
assert_eq!(
|
||||
ret.1,
|
||||
Err(Error::Ok(Some(script_error::ScriptError::EvalFalse)))
|
||||
);
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#![proptest_config(ProptestConfig {
|
||||
cases: 20_000, .. ProptestConfig::default()
|
||||
})]
|
||||
|
||||
/// This test is very shallow, because we have only `()` for success and most errors have
|
||||
/// been collapsed to `Error::Ok`. A deeper comparison, requires changes to the C++ code.
|
||||
#[test]
|
||||
fn test_arbitrary_scripts(
|
||||
lock_time in prop::num::i64::ANY,
|
||||
is_final in prop::bool::ANY,
|
||||
pub_key in prop::collection::vec(0..=0xffu8, 0..=OVERFLOW_SCRIPT_SIZE),
|
||||
sig in prop::collection::vec(0..=0xffu8, 1..=OVERFLOW_SCRIPT_SIZE),
|
||||
flags in prop::bits::u32::masked(VerificationFlags::all().bits()),
|
||||
) {
|
||||
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
|
||||
&missing_sighash,
|
||||
lock_time,
|
||||
is_final,
|
||||
&pub_key[..],
|
||||
&sig[..],
|
||||
repair_flags(VerificationFlags::from_bits_truncate(flags)),
|
||||
);
|
||||
prop_assert_eq!(ret.0, ret.1.map_err(normalize_error),
|
||||
"original Rust result: {:?}", ret.1);
|
||||
}
|
||||
|
||||
/// Similar to `test_arbitrary_scripts`, but ensures the `sig` only contains pushes.
|
||||
#[test]
|
||||
fn test_restricted_sig_scripts(
|
||||
lock_time in prop::num::i64::ANY,
|
||||
is_final in prop::bool::ANY,
|
||||
pub_key in prop::collection::vec(0..=0xffu8, 0..=OVERFLOW_SCRIPT_SIZE),
|
||||
sig in prop::collection::vec(0..=0x60u8, 0..=OVERFLOW_SCRIPT_SIZE),
|
||||
flags in prop::bits::u32::masked(
|
||||
// Don’t waste test cases on whether or not `SigPushOnly` is set.
|
||||
(VerificationFlags::all() - VerificationFlags::SigPushOnly).bits()),
|
||||
) {
|
||||
let ret = check_verify_callback::<CxxInterpreter, RustInterpreter>(
|
||||
&missing_sighash,
|
||||
lock_time,
|
||||
is_final,
|
||||
&pub_key[..],
|
||||
&sig[..],
|
||||
repair_flags(VerificationFlags::from_bits_truncate(flags))
|
||||
| VerificationFlags::SigPushOnly,
|
||||
);
|
||||
prop_assert_eq!(ret.0, ret.1.map_err(normalize_error),
|
||||
"original Rust result: {:?}", ret.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,642 @@
|
|||
#![allow(non_camel_case_types)]
|
||||
|
||||
use std::{
|
||||
num::TryFromIntError,
|
||||
ops::{Add, Neg, Sub},
|
||||
};
|
||||
|
||||
use enum_primitive::FromPrimitive;
|
||||
|
||||
use super::script_error::*;
|
||||
|
||||
pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520; // bytes
|
||||
|
||||
/// Maximum script length in bytes
|
||||
pub const MAX_SCRIPT_SIZE: usize = 10000;
|
||||
|
||||
// Threshold for lock_time: below this value it is interpreted as block number,
|
||||
// otherwise as UNIX timestamp.
|
||||
pub const LOCKTIME_THRESHOLD: ScriptNum = ScriptNum(500000000); // Tue Nov 5 00:53:20 1985 UTC
|
||||
|
||||
/** Script opcodes */
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub enum Opcode {
|
||||
PushValue(PushValue),
|
||||
Operation(Operation),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum PushValue {
|
||||
// push value
|
||||
OP_0 = 0x00,
|
||||
PushdataBytelength(u8),
|
||||
OP_PUSHDATA1 = 0x4c,
|
||||
OP_PUSHDATA2 = 0x4d,
|
||||
OP_PUSHDATA4 = 0x4e,
|
||||
OP_1NEGATE = 0x4f,
|
||||
OP_RESERVED = 0x50,
|
||||
OP_1 = 0x51,
|
||||
OP_2 = 0x52,
|
||||
OP_3 = 0x53,
|
||||
OP_4 = 0x54,
|
||||
OP_5 = 0x55,
|
||||
OP_6 = 0x56,
|
||||
OP_7 = 0x57,
|
||||
OP_8 = 0x58,
|
||||
OP_9 = 0x59,
|
||||
OP_10 = 0x5a,
|
||||
OP_11 = 0x5b,
|
||||
OP_12 = 0x5c,
|
||||
OP_13 = 0x5d,
|
||||
OP_14 = 0x5e,
|
||||
OP_15 = 0x5f,
|
||||
OP_16 = 0x60,
|
||||
}
|
||||
|
||||
use PushValue::*;
|
||||
|
||||
enum_from_primitive! {
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum Operation {
|
||||
// control
|
||||
OP_NOP = 0x61,
|
||||
OP_VER = 0x62,
|
||||
OP_IF = 0x63,
|
||||
OP_NOTIF = 0x64,
|
||||
OP_VERIF = 0x65,
|
||||
OP_VERNOTIF = 0x66,
|
||||
OP_ELSE = 0x67,
|
||||
OP_ENDIF = 0x68,
|
||||
OP_VERIFY = 0x69,
|
||||
OP_RETURN = 0x6a,
|
||||
|
||||
// stack ops
|
||||
OP_TOALTSTACK = 0x6b,
|
||||
OP_FROMALTSTACK = 0x6c,
|
||||
OP_2DROP = 0x6d,
|
||||
OP_2DUP = 0x6e,
|
||||
OP_3DUP = 0x6f,
|
||||
OP_2OVER = 0x70,
|
||||
OP_2ROT = 0x71,
|
||||
OP_2SWAP = 0x72,
|
||||
OP_IFDUP = 0x73,
|
||||
OP_DEPTH = 0x74,
|
||||
OP_DROP = 0x75,
|
||||
OP_DUP = 0x76,
|
||||
OP_NIP = 0x77,
|
||||
OP_OVER = 0x78,
|
||||
OP_PICK = 0x79,
|
||||
OP_ROLL = 0x7a,
|
||||
OP_ROT = 0x7b,
|
||||
OP_SWAP = 0x7c,
|
||||
OP_TUCK = 0x7d,
|
||||
|
||||
// splice ops
|
||||
OP_CAT = 0x7e,
|
||||
OP_SUBSTR = 0x7f,
|
||||
OP_LEFT = 0x80,
|
||||
OP_RIGHT = 0x81,
|
||||
OP_SIZE = 0x82,
|
||||
|
||||
// bit logic
|
||||
OP_INVERT = 0x83,
|
||||
OP_AND = 0x84,
|
||||
OP_OR = 0x85,
|
||||
OP_XOR = 0x86,
|
||||
OP_EQUAL = 0x87,
|
||||
OP_EQUALVERIFY = 0x88,
|
||||
OP_RESERVED1 = 0x89,
|
||||
OP_RESERVED2 = 0x8a,
|
||||
|
||||
// numeric
|
||||
OP_1ADD = 0x8b,
|
||||
OP_1SUB = 0x8c,
|
||||
OP_2MUL = 0x8d,
|
||||
OP_2DIV = 0x8e,
|
||||
OP_NEGATE = 0x8f,
|
||||
OP_ABS = 0x90,
|
||||
OP_NOT = 0x91,
|
||||
OP_0NOTEQUAL = 0x92,
|
||||
|
||||
OP_ADD = 0x93,
|
||||
OP_SUB = 0x94,
|
||||
OP_MUL = 0x95,
|
||||
OP_DIV = 0x96,
|
||||
OP_MOD = 0x97,
|
||||
OP_LSHIFT = 0x98,
|
||||
OP_RSHIFT = 0x99,
|
||||
|
||||
OP_BOOLAND = 0x9a,
|
||||
OP_BOOLOR = 0x9b,
|
||||
OP_NUMEQUAL = 0x9c,
|
||||
OP_NUMEQUALVERIFY = 0x9d,
|
||||
OP_NUMNOTEQUAL = 0x9e,
|
||||
OP_LESSTHAN = 0x9f,
|
||||
OP_GREATERTHAN = 0xa0,
|
||||
OP_LESSTHANOREQUAL = 0xa1,
|
||||
OP_GREATERTHANOREQUAL = 0xa2,
|
||||
OP_MIN = 0xa3,
|
||||
OP_MAX = 0xa4,
|
||||
|
||||
OP_WITHIN = 0xa5,
|
||||
|
||||
// crypto
|
||||
OP_RIPEMD160 = 0xa6,
|
||||
OP_SHA1 = 0xa7,
|
||||
OP_SHA256 = 0xa8,
|
||||
OP_HASH160 = 0xa9,
|
||||
OP_HASH256 = 0xaa,
|
||||
OP_CODESEPARATOR = 0xab,
|
||||
OP_CHECKSIG = 0xac,
|
||||
OP_CHECKSIGVERIFY = 0xad,
|
||||
OP_CHECKMULTISIG = 0xae,
|
||||
OP_CHECKMULTISIGVERIFY = 0xaf,
|
||||
|
||||
// expansion
|
||||
OP_NOP1 = 0xb0,
|
||||
OP_NOP2 = 0xb1,
|
||||
OP_NOP3 = 0xb2,
|
||||
OP_NOP4 = 0xb3,
|
||||
OP_NOP5 = 0xb4,
|
||||
OP_NOP6 = 0xb5,
|
||||
OP_NOP7 = 0xb6,
|
||||
OP_NOP8 = 0xb7,
|
||||
OP_NOP9 = 0xb8,
|
||||
OP_NOP10 = 0xb9,
|
||||
|
||||
OP_INVALIDOPCODE = 0xff,
|
||||
}
|
||||
}
|
||||
|
||||
use Operation::*;
|
||||
|
||||
pub const OP_CHECKLOCKTIMEVERIFY: Operation = OP_NOP2;
|
||||
|
||||
impl From<Opcode> for u8 {
|
||||
fn from(value: Opcode) -> Self {
|
||||
match value {
|
||||
Opcode::PushValue(pv) => pv.into(),
|
||||
Opcode::Operation(op) => op.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Opcode {
|
||||
fn from(value: u8) -> Self {
|
||||
Operation::from_u8(value).map_or(
|
||||
PushValue::try_from(value)
|
||||
.map_or(Opcode::Operation(OP_INVALIDOPCODE), Opcode::PushValue),
|
||||
Opcode::Operation,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PushValue> for u8 {
|
||||
fn from(value: PushValue) -> Self {
|
||||
match value {
|
||||
OP_0 => 0x00,
|
||||
PushdataBytelength(byte) => byte,
|
||||
OP_PUSHDATA1 => 0x4c,
|
||||
OP_PUSHDATA2 => 0x4d,
|
||||
OP_PUSHDATA4 => 0x4e,
|
||||
OP_1NEGATE => 0x4f,
|
||||
OP_RESERVED => 0x50,
|
||||
OP_1 => 0x51,
|
||||
OP_2 => 0x52,
|
||||
OP_3 => 0x53,
|
||||
OP_4 => 0x54,
|
||||
OP_5 => 0x55,
|
||||
OP_6 => 0x56,
|
||||
OP_7 => 0x57,
|
||||
OP_8 => 0x58,
|
||||
OP_9 => 0x59,
|
||||
OP_10 => 0x5a,
|
||||
OP_11 => 0x5b,
|
||||
OP_12 => 0x5c,
|
||||
OP_13 => 0x5d,
|
||||
OP_14 => 0x5e,
|
||||
OP_15 => 0x5f,
|
||||
OP_16 => 0x60,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for PushValue {
|
||||
type Error = ();
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0x00 => Ok(OP_0),
|
||||
0x4c => Ok(OP_PUSHDATA1),
|
||||
0x4d => Ok(OP_PUSHDATA2),
|
||||
0x4e => Ok(OP_PUSHDATA4),
|
||||
0x4f => Ok(OP_1NEGATE),
|
||||
0x50 => Ok(OP_RESERVED),
|
||||
0x51 => Ok(OP_1),
|
||||
0x52 => Ok(OP_2),
|
||||
0x53 => Ok(OP_3),
|
||||
0x54 => Ok(OP_4),
|
||||
0x55 => Ok(OP_5),
|
||||
0x56 => Ok(OP_6),
|
||||
0x57 => Ok(OP_7),
|
||||
0x58 => Ok(OP_8),
|
||||
0x59 => Ok(OP_9),
|
||||
0x5a => Ok(OP_10),
|
||||
0x5b => Ok(OP_11),
|
||||
0x5c => Ok(OP_12),
|
||||
0x5d => Ok(OP_13),
|
||||
0x5e => Ok(OP_14),
|
||||
0x5f => Ok(OP_15),
|
||||
0x60 => Ok(OP_16),
|
||||
_ => {
|
||||
if value <= 0x60 {
|
||||
Ok(PushdataBytelength(value))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Operation> for u8 {
|
||||
fn from(value: Operation) -> Self {
|
||||
// This is how you get the discriminant, but using `as` everywhere is too much code smell
|
||||
value as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct ScriptNum(i64);
|
||||
|
||||
impl ScriptNum {
|
||||
const DEFAULT_MAX_NUM_SIZE: usize = 4;
|
||||
|
||||
pub fn new(
|
||||
vch: &Vec<u8>,
|
||||
require_minimal: bool,
|
||||
max_num_size: Option<usize>,
|
||||
) -> Result<Self, ScriptNumError> {
|
||||
let max_num_size = max_num_size.unwrap_or(Self::DEFAULT_MAX_NUM_SIZE);
|
||||
if vch.len() > max_num_size {
|
||||
return Err(ScriptNumError::Overflow {
|
||||
max_num_size,
|
||||
actual: vch.len(),
|
||||
});
|
||||
}
|
||||
if require_minimal && !vch.is_empty() {
|
||||
// Check that the number is encoded with the minimum possible
|
||||
// number of bytes.
|
||||
//
|
||||
// If the most-significant-byte - excluding the sign bit - is zero
|
||||
// then we're not minimal. Note how this test also rejects the
|
||||
// negative-zero encoding, 0x80.
|
||||
if (vch.last().unwrap_or_else(|| unreachable!()) & 0x7F) == 0 {
|
||||
// One exception: if there's more than one byte and the most
|
||||
// significant bit of the second-most-significant-byte is set
|
||||
// it would conflict with the sign bit. An example of this case
|
||||
// is +-255, which encode to 0xff00 and 0xff80 respectively.
|
||||
// (big-endian).
|
||||
if vch.len() <= 1 {
|
||||
return Err(ScriptNumError::NegativeZero);
|
||||
} else if (vch[vch.len() - 2] & 0x80) == 0 {
|
||||
return Err(ScriptNumError::NonMinimalEncoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::set_vch(vch).map(ScriptNum)
|
||||
}
|
||||
|
||||
pub fn getint(&self) -> i32 {
|
||||
if self.0 > i32::MAX.into() {
|
||||
i32::MAX
|
||||
} else if self.0 < i32::MIN.into() {
|
||||
i32::MIN
|
||||
} else {
|
||||
self.0.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getvch(&self) -> Vec<u8> {
|
||||
Self::serialize(&self.0)
|
||||
}
|
||||
|
||||
pub fn serialize(value: &i64) -> Vec<u8> {
|
||||
if *value == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
if *value == i64::MIN {
|
||||
// The code below is buggy, and produces the "wrong" result for
|
||||
// INT64_MIN. To avoid undefined behavior while attempting to
|
||||
// negate a value of INT64_MIN, we intentionally return the result
|
||||
// that the code below would produce on an x86_64 system.
|
||||
return vec![0, 0, 0, 0, 0, 0, 0, 128, 128];
|
||||
}
|
||||
|
||||
let mut result = Vec::new();
|
||||
let neg = *value < 0;
|
||||
let mut absvalue = value.abs();
|
||||
|
||||
while absvalue != 0 {
|
||||
result.push(
|
||||
(absvalue & 0xff)
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| unreachable!()),
|
||||
);
|
||||
absvalue >>= 8;
|
||||
}
|
||||
|
||||
// - If the most significant byte is >= 0x80 and the value is positive, push a
|
||||
// new zero-byte to make the significant byte < 0x80 again.
|
||||
|
||||
// - If the most significant byte is >= 0x80 and the value is negative, push a
|
||||
// new 0x80 byte that will be popped off when converting to an integral.
|
||||
|
||||
// - If the most significant byte is < 0x80 and the value is negative, add
|
||||
// 0x80 to it, since it will be subtracted and interpreted as a negative when
|
||||
// converting to an integral.
|
||||
|
||||
if result.last().map_or(true, |last| last & 0x80 != 0) {
|
||||
result.push(if neg { 0x80 } else { 0 });
|
||||
} else if neg {
|
||||
if let Some(last) = result.last_mut() {
|
||||
*last |= 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn set_vch(vch: &Vec<u8>) -> Result<i64, ScriptNumError> {
|
||||
match vch.last() {
|
||||
None => Ok(0),
|
||||
Some(vch_back) => {
|
||||
if *vch == vec![0, 0, 0, 0, 0, 0, 0, 128, 128] {
|
||||
// On an x86_64 system, the code below would actually decode the buggy
|
||||
// INT64_MIN encoding correctly. However in this case, it would be
|
||||
// performing left shifts of a signed type by 64, which has undefined
|
||||
// behavior.
|
||||
return Ok(i64::MIN);
|
||||
};
|
||||
|
||||
// Guard against undefined behavior. INT64_MIN is the only allowed 9-byte encoding.
|
||||
if vch.len() > 8 {
|
||||
return Err(ScriptNumError::Overflow {
|
||||
max_num_size: 8,
|
||||
actual: vch.len(),
|
||||
});
|
||||
};
|
||||
|
||||
let mut result: i64 = 0;
|
||||
for (i, vch_i) in vch.iter().enumerate() {
|
||||
result |= i64::from(*vch_i) << (8 * i);
|
||||
}
|
||||
|
||||
// If the input vector's most significant byte is 0x80, remove it from
|
||||
// the result's msb and return a negative.
|
||||
if vch_back & 0x80 != 0 {
|
||||
return Ok(-(result & !(0x80 << (8 * (vch.len() - 1)))));
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for ScriptNum {
|
||||
fn from(value: i64) -> Self {
|
||||
ScriptNum(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for ScriptNum {
|
||||
fn from(value: i32) -> Self {
|
||||
ScriptNum(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for ScriptNum {
|
||||
fn from(value: u8) -> Self {
|
||||
ScriptNum(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: This instance will be obsolete if we convert bool directly to a `Vec<u8>`, which is also
|
||||
/// more efficient.
|
||||
impl From<bool> for ScriptNum {
|
||||
fn from(value: bool) -> Self {
|
||||
ScriptNum(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<usize> for ScriptNum {
|
||||
type Error = TryFromIntError;
|
||||
fn try_from(value: usize) -> Result<Self, Self::Error> {
|
||||
value.try_into().map(ScriptNum)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ScriptNum> for u16 {
|
||||
type Error = TryFromIntError;
|
||||
fn try_from(value: ScriptNum) -> Result<Self, Self::Error> {
|
||||
value.getint().try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ScriptNum> for u8 {
|
||||
type Error = TryFromIntError;
|
||||
fn try_from(value: ScriptNum) -> Result<Self, Self::Error> {
|
||||
value.getint().try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for ScriptNum {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
let rhs = other.0;
|
||||
assert!(
|
||||
rhs == 0
|
||||
|| (rhs > 0 && self.0 <= i64::MAX - rhs)
|
||||
|| (rhs < 0 && self.0 >= i64::MIN - rhs)
|
||||
);
|
||||
Self(self.0 + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for ScriptNum {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self {
|
||||
let rhs = other.0;
|
||||
assert!(
|
||||
rhs == 0
|
||||
|| (rhs > 0 && self.0 >= i64::MIN + rhs)
|
||||
|| (rhs < 0 && self.0 <= i64::MAX + rhs)
|
||||
);
|
||||
Self(self.0 - rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for ScriptNum {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
assert!(self.0 != i64::MIN);
|
||||
Self(-self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/** Serialized script, used inside transaction inputs and outputs */
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Script<'a>(pub &'a [u8]);
|
||||
|
||||
impl Script<'_> {
|
||||
pub fn get_op(script: &mut &[u8]) -> Result<Opcode, ScriptError> {
|
||||
Self::get_op2(script, &mut vec![])
|
||||
}
|
||||
|
||||
pub fn get_op2(script: &mut &[u8], buffer: &mut Vec<u8>) -> Result<Opcode, ScriptError> {
|
||||
if script.is_empty() {
|
||||
return Err(ScriptError::ReadError {
|
||||
expected_bytes: 1,
|
||||
available_bytes: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Empty the provided buffer, if any
|
||||
buffer.truncate(0);
|
||||
|
||||
let leading_byte = Opcode::from(script[0]);
|
||||
*script = &script[1..];
|
||||
|
||||
Ok(match leading_byte {
|
||||
Opcode::PushValue(pv) => match pv {
|
||||
OP_PUSHDATA1 | OP_PUSHDATA2 | OP_PUSHDATA4 => {
|
||||
let read_le = |script: &mut &[u8], needed_bytes: usize| {
|
||||
if script.len() < needed_bytes {
|
||||
Err(ScriptError::ReadError {
|
||||
expected_bytes: needed_bytes,
|
||||
available_bytes: script.len(),
|
||||
})
|
||||
} else {
|
||||
let mut size = 0;
|
||||
for i in (0..needed_bytes).rev() {
|
||||
size <<= 8;
|
||||
size |= usize::from(script[i]);
|
||||
}
|
||||
*script = &script[needed_bytes..];
|
||||
Ok(size)
|
||||
}
|
||||
};
|
||||
|
||||
let size = match pv {
|
||||
OP_PUSHDATA1 => read_le(script, 1),
|
||||
OP_PUSHDATA2 => read_le(script, 2),
|
||||
OP_PUSHDATA4 => read_le(script, 4),
|
||||
_ => unreachable!(),
|
||||
}?;
|
||||
|
||||
if script.len() < size {
|
||||
return Err(ScriptError::ReadError {
|
||||
expected_bytes: size,
|
||||
available_bytes: script.len(),
|
||||
});
|
||||
}
|
||||
|
||||
buffer.extend(&script[0..size]);
|
||||
*script = &script[size..];
|
||||
|
||||
leading_byte
|
||||
}
|
||||
// OP_0/OP_FALSE doesn't actually push a constant 0 onto the stack but
|
||||
// pushes an empty array. (Thus we leave the buffer truncated to 0 length)
|
||||
OP_0 => leading_byte,
|
||||
PushdataBytelength(size_byte) => {
|
||||
let size = size_byte.into();
|
||||
|
||||
if script.len() < size {
|
||||
return Err(ScriptError::ReadError {
|
||||
expected_bytes: size,
|
||||
available_bytes: script.len(),
|
||||
});
|
||||
}
|
||||
|
||||
buffer.extend(&script[0..size]);
|
||||
*script = &script[size..];
|
||||
|
||||
leading_byte
|
||||
}
|
||||
_ => leading_byte,
|
||||
},
|
||||
_ => leading_byte,
|
||||
})
|
||||
}
|
||||
|
||||
/** Encode/decode small integers: */
|
||||
pub fn decode_op_n(opcode: PushValue) -> u32 {
|
||||
if opcode == OP_0 {
|
||||
return 0;
|
||||
}
|
||||
assert!(opcode >= OP_1 && opcode <= OP_16);
|
||||
(u8::from(opcode) - (u8::from(OP_1) - 1)).into()
|
||||
}
|
||||
|
||||
/// Pre-version-0.6, Bitcoin always counted CHECKMULTISIGs
|
||||
/// as 20 sigops. With pay-to-script-hash, that changed:
|
||||
/// CHECKMULTISIGs serialized in script_sigs are
|
||||
/// counted more accurately, assuming they are of the form
|
||||
/// ... OP_N CHECKMULTISIG ...
|
||||
pub fn get_sig_op_count(&self, accurate: bool) -> u32 {
|
||||
let mut n = 0;
|
||||
let mut pc = self.0;
|
||||
let mut last_opcode = Opcode::Operation(OP_INVALIDOPCODE);
|
||||
while !pc.is_empty() {
|
||||
let opcode = match Self::get_op(&mut pc) {
|
||||
Ok(o) => o,
|
||||
Err(_) => break,
|
||||
};
|
||||
if let Opcode::Operation(op) = opcode {
|
||||
if op == OP_CHECKSIG || op == OP_CHECKSIGVERIFY {
|
||||
n += 1;
|
||||
} else if op == OP_CHECKMULTISIG || op == OP_CHECKMULTISIGVERIFY {
|
||||
match last_opcode {
|
||||
Opcode::PushValue(pv) => {
|
||||
if accurate && pv >= OP_1 && pv <= OP_16 {
|
||||
n += Self::decode_op_n(pv);
|
||||
} else {
|
||||
n += 20
|
||||
}
|
||||
}
|
||||
_ => n += 20,
|
||||
}
|
||||
}
|
||||
}
|
||||
last_opcode = opcode;
|
||||
}
|
||||
n
|
||||
}
|
||||
|
||||
/// Returns true iff this script is P2SH.
|
||||
pub fn is_pay_to_script_hash(&self) -> bool {
|
||||
self.0.len() == 23
|
||||
&& self.0[0] == OP_HASH160.into()
|
||||
&& self.0[1] == 0x14
|
||||
&& self.0[22] == OP_EQUAL.into()
|
||||
}
|
||||
|
||||
/// Called by `IsStandardTx` and P2SH/BIP62 VerifyScript (which makes it consensus-critical).
|
||||
pub fn is_push_only(&self) -> bool {
|
||||
let mut pc = self.0;
|
||||
while !pc.is_empty() {
|
||||
if let Ok(Opcode::PushValue(_)) = Self::get_op(&mut pc) {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum ScriptNumError {
|
||||
NegativeZero,
|
||||
NonMinimalEncoding,
|
||||
Overflow { max_num_size: usize, actual: usize },
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum ScriptError {
|
||||
// Ok = 0,
|
||||
UnknownError = 1,
|
||||
EvalFalse,
|
||||
OpReturn,
|
||||
|
||||
// Max sizes
|
||||
ScriptSize,
|
||||
PushSize,
|
||||
OpCount,
|
||||
StackSize,
|
||||
SigCount,
|
||||
PubKeyCount,
|
||||
|
||||
// Failed verify operations
|
||||
Verify,
|
||||
EqualVerify,
|
||||
CheckMultisigVerify,
|
||||
CheckSigVerify,
|
||||
NumEqualVerify,
|
||||
|
||||
// Logical/Format/Canonical errors
|
||||
BadOpcode,
|
||||
DisabledOpcode,
|
||||
InvalidStackOperation,
|
||||
InvalidAltstackOperation,
|
||||
UnbalancedConditional,
|
||||
|
||||
// OP_CHECKLOCKTIMEVERIFY
|
||||
NegativeLockTime,
|
||||
UnsatisfiedLockTime,
|
||||
|
||||
// BIP62
|
||||
SigHashType,
|
||||
SigDER,
|
||||
MinimalData,
|
||||
SigPushOnly,
|
||||
// SigHighS,
|
||||
SigNullDummy = 27,
|
||||
PubKeyType,
|
||||
CleanStack,
|
||||
|
||||
// softfork safeness
|
||||
DiscourageUpgradableNOPs,
|
||||
|
||||
ReadError {
|
||||
expected_bytes: usize,
|
||||
available_bytes: usize,
|
||||
},
|
||||
|
||||
/// Corresponds to the `scriptnum_error` exception in C++.
|
||||
ScriptNumError(ScriptNumError),
|
||||
}
|
||||
|
||||
impl From<ScriptNumError> for ScriptError {
|
||||
fn from(value: ScriptNumError) -> Self {
|
||||
ScriptError::ScriptNumError(value)
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
use std::num::TryFromIntError;
|
||||
|
||||
use zcash_primitives::transaction::TxVersion;
|
||||
|
||||
use super::interpreter::*;
|
||||
use super::script::*;
|
||||
use super::script_error::*;
|
||||
|
||||
/// 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
|
||||
|
@ -10,7 +10,10 @@ use super::interpreter::*;
|
|||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
/// Any failure that results in the script being invalid.
|
||||
Ok,
|
||||
///
|
||||
/// __NB__: This is in `Option` because this type is used by both the C++ and Rust
|
||||
/// implementations, but the C++ impl doesn’t yet expose the original error.
|
||||
Ok(Option<ScriptError>),
|
||||
/// An exception was caught.
|
||||
VerifyScript,
|
||||
/// The script size can’t fit in a `u32`, as required by the C++ code.
|
||||
|
@ -22,22 +25,6 @@ pub enum Error {
|
|||
Unknown(i64),
|
||||
}
|
||||
|
||||
/// All signature hashes are 32 bytes, since they are either:
|
||||
/// - a SHA-256 output (for v1 or v2 transactions).
|
||||
/// - a BLAKE2b-256 output (for v3 and above transactions).
|
||||
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 {
|
||||
|
@ -57,16 +44,49 @@ pub trait ZcashScript {
|
|||
///
|
||||
/// Note that script verification failure is indicated by `Err(Error::Ok)`.
|
||||
fn verify_callback(
|
||||
sighash: SighashCalculator,
|
||||
n_lock_time: i64,
|
||||
sighash_callback: SighashCalculator,
|
||||
lock_time: i64,
|
||||
is_final: bool,
|
||||
script_pub_key: &[u8],
|
||||
script_sig: &[u8],
|
||||
flags: VerificationFlags,
|
||||
tx_version: TxVersion,
|
||||
) -> 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>;
|
||||
}
|
||||
|
||||
/// A tag to indicate that the Rust implementation of zcash_script should be used.
|
||||
pub enum RustInterpreter {}
|
||||
|
||||
impl ZcashScript for RustInterpreter {
|
||||
/// 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> {
|
||||
let cscript = Script(script);
|
||||
Ok(cscript.get_sig_op_count(false))
|
||||
}
|
||||
|
||||
fn verify_callback(
|
||||
sighash: SighashCalculator,
|
||||
lock_time: i64,
|
||||
is_final: bool,
|
||||
script_pub_key: &[u8],
|
||||
script_sig: &[u8],
|
||||
flags: VerificationFlags,
|
||||
) -> Result<(), Error> {
|
||||
let lock_time_num = lock_time.into();
|
||||
verify_script(
|
||||
&Script(script_sig),
|
||||
&Script(script_pub_key),
|
||||
flags,
|
||||
&CallbackTransactionSignatureChecker {
|
||||
sighash,
|
||||
lock_time: &lock_time_num,
|
||||
is_final,
|
||||
},
|
||||
)
|
||||
.map_err(|e| Error::Ok(Some(e)))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue