Compare commits

...

26 Commits

Author SHA1 Message Date
Conrado Gouvea 828007cc33
bump to 0.3.1 (#217)
* bump to 0.3.1

* Add necessary components to toolchain

Apparently GitHub recently made changes so that these components aren’t
installed automatically for all toolchains, so they’re now explicit (as
they probably should have been all along).

* comment cleanups

---------

Co-authored-by: Greg Pfeil <greg@technomadic.org>
2025-05-20 09:45:48 +02:00
Greg Pfeil d72cb63dd0
Remove extra `&` from `SighashCalculator` (#216)
* Remove extra `&` from `SighashCalculator`

* Remove the now-unnecessary lifetimes
2025-05-15 12:16:21 -03:00
Conrado Gouvea edf94cbf5c
bump to 0.3.0; attempt at using release-plz (#215) 2025-05-13 21:08:43 +02:00
Greg Pfeil d3e242b47f
Eliminate `ScriptNum` (#208)
* Move `set_vch` to simplify later diffs

This makes no changes other than swapping the positions of the `set_vch`
and `serialize` operations on `ScriptNum`.

* Extract integer ↔︎ `Vec<u8>` `fn`s from `ScriptNum`

__NB__: I recommend ignoring whitespace when reviewing this commit.

`ScriptNum` was used one of three ways:
1. convert a Rust integer to a stack value
2. load a stack value into Rust
3. do arithmetic on stack values

Combining these into one interface added extra complexity. For example,
`getint()` would clamp the value to `i32` range, but that range could
only be violated if arithmetic was performed on `ScriptNum`s, and
`getint` was never used on the result of arithmetic.

This extracts `parse_num` and `serialize_num` changing the three
patterns as follows:
1. `ScriptNum::from(_).getvch()` is now `serialize_num(_)`, and
2. `ScriptNum::new(_).and_then(getint())` is now `parse_num(_)`,
3. `ScriptNum::new(_)` … `getvch()` remains the same.

* Make `ScriptNum` a process

We never held a `ScriptNum` – we create one from the stack, perform an
operation, and serialize the result. This eliminates the type in favor
of operations that take a closure on `i64`s.

The operations that exist can’t possibly hit the various bounds that
were checked on `ScriptNum` operations, so they were removed.

As part of the above work, this introduces `cast_from_bool`, which is
then used to replace all instances of `VCH_TRUE`/`FALSE`.
2025-04-30 18:12:50 -03:00
Greg Pfeil cc157ffdce
Add various stepwise functionality (#204)
* Move some constants in preparation

This makes some minor changes to constants to facilitate splitting out a
stepwise interpreter.

* Extract a step function from the interpreter

This is generally useful for testing, but specifically, we want to be
able to run this side-by-side with the C++ interpreter, and check that
every bit of our state matches along the way.

This change is as minimal as possible, to avoid divergence until after
it can be compared against C++. E.g., the massive `match opcode {…}`
block that has been moved should only change the dereferencing of
`op_count`.

* Add a `State` struct to make stepping easier

* Expose step interpreter

* Add a stepwise comparison interpreter

The C++ changes aren’t in place yet, so this is currently just an A/A test.

This changes our closures into structs containing a function, because
that’s how we can pass around functions with universally-quantified
lifetimes.

* Make interpreters more flexible

Previously the `ZcashScript` impls didn’t use `self`, so the types were
just tags. However, with the new `StepwiseInterpreter`, they need
`self`.

This also removes `RustInterpreter` in favor of a `rust_interpreter`
function that instantiates an appropriate `StepwiseInterpreter`.

* Add a function for C++/Rust comparison interpreter

* Fix fuzzer

* Clean up `use` in lib.rs

* Fix weird indentation

* Make various fields non-`pub`

* Add a `new` constructor for `Stack`

* Remove incorrect comment

* Appease Clippy

Adds `Default` impls for `Stack` and `State`.

* Rename `State::manual` to `State::from_parts`
2025-04-10 18:03:29 -03:00
Greg Pfeil 2afc474338
Addressing post-hoc PR feedback on #174 (#197)
* Add ECC & myself (Greg Pfeil) as authors

* Have the Rust impl correctly report “high s” sigs

There is a bug in the C++ side where the error is not set correctly on a
“high s” signature. The Rust side had mirrored this bug, but this
eliminates the bug in the Rust.

* Remove extra byte from sig before low-s check

This doesn’t seem to have any effect on the semantics, as the DER-formatted signature includes
lengths that ensure it will ignore extra bytes, but the C++ code removes the extra byte, so the Rust
should as well.

* Change some comments

Co-authored-by: Daira-Emma Hopwood <daira@electriccoin.co>

* Appease `rustfmt`

* Have OP_DUP match the C++ impl more closely

* Address the second half of @daira’s #174 review

* Eliminate mutation from `Opcode` parsing

This now splits slices and returns the remaining pieces rather than
modifying the arguments.

* Remove obsolete comment

* Address PR comments

* Address additional comments on #174

---------

Co-authored-by: Daira-Emma Hopwood <daira@electriccoin.co>
2025-02-25 13:49:54 -03:00
Greg Pfeil 228ec8b4f7
Expose `ScriptError` on C++ side (#195)
**This changes the C++ implementation.**

Provides richer errors, which also gives more precision when comparing
against the Rust impl.

This also removes the (now unused) `zcash_script_error_t`. The only case
other than `zcash_script_ERR_OK` that was still in use was
`zcash_script_ERR_VERIFY_SCRIPT`, so that case has been added to
`ScriptError`.

This avoids changing the Rust API, but potentially `Error` and
`ScriptError` on the Rust side could be collapsed into one `enum`. It
would just be a breaking change.
2025-02-11 11:22:52 -03:00
dependabot[bot] 981dac81d6
Bump cc from 1.2.10 to 1.2.11 (#196)
Bumps [cc](https://github.com/rust-lang/cc-rs) from 1.2.10 to 1.2.11.
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.10...cc-v1.2.11)

---
updated-dependencies:
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-04 11:11:42 -03:00
dependabot[bot] 10489af5f4
Bump tracing from 0.1.40 to 0.1.41 (#193)
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.40 to 0.1.41.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.40...tracing-0.1.41)

---
updated-dependencies:
- dependency-name: tracing
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-04 11:09:53 -03:00
dependabot[bot] 2ec9b8d94d
Bump proptest from 0.9.6 to 1.6.0 (#192)
Bumps [proptest](https://github.com/proptest-rs/proptest) from 0.9.6 to 1.6.0.
- [Release notes](https://github.com/proptest-rs/proptest/releases)
- [Changelog](https://github.com/proptest-rs/proptest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/proptest-rs/proptest/compare/0.9.6...v1.6.0)

---
updated-dependencies:
- dependency-name: proptest
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-04 11:09:18 -03:00
dependabot[bot] 274a8a8e9f
Bump libfuzzer-sys from 0.4.8 to 0.4.9 (#194)
Bumps [libfuzzer-sys](https://github.com/rust-fuzz/libfuzzer) from 0.4.8 to 0.4.9.
- [Changelog](https://github.com/rust-fuzz/libfuzzer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-fuzz/libfuzzer/compare/0.4.8...0.4.9)

---
updated-dependencies:
- dependency-name: libfuzzer-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 19:58:39 -03:00
dependabot[bot] 35e19f95e2
Bump secp256k1 from 0.29.0 to 0.30.0 (#191)
Bumps [secp256k1](https://github.com/rust-bitcoin/rust-secp256k1) from 0.29.0 to 0.30.0.
- [Changelog](https://github.com/rust-bitcoin/rust-secp256k1/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-bitcoin/rust-secp256k1/compare/secp256k1-0.29.0...secp256k1-0.30.0)

---
updated-dependencies:
- dependency-name: secp256k1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 19:55:48 -03:00
Greg Pfeil 61f3ef3e74
Change type of `lock_time` parameter (#190)
* Change type of `lock_time` parameter

This is a breaking change. Lock times are stored in tx as `u32`, but
this API expected `i64`, forcing conversions on the caller. This change
brings the API into alignment with the tx representation.

* Update the `lock_time` type in the fuzz test
2025-01-30 10:54:32 -03:00
dependabot[bot] 4be3521814
Bump cc from 1.1.10 to 1.2.10 (#189)
Bumps [cc](https://github.com/rust-lang/cc-rs) from 1.1.10 to 1.2.10.
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.1.10...cc-v1.2.10)

---
updated-dependencies:
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-29 20:03:38 -03:00
dependabot[bot] 4b55ca684b
Bump bitflags from 2.6.0 to 2.8.0 (#185)
Bumps [bitflags](https://github.com/bitflags/bitflags) from 2.6.0 to 2.8.0.
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/2.6.0...2.8.0)

---
updated-dependencies:
- dependency-name: bitflags
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-29 19:45:47 -03:00
Dimitris Apostolou c21f1cbaee
Fix RUSTSEC-2024-0006 (#183) 2025-01-29 19:34:25 -03:00
Dimitris Apostolou 376fada843
Fix clippy warnings (#188) 2025-01-29 19:33:31 -03:00
Greg Pfeil 335ae9a2a6
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>
2025-01-29 19:31:52 -03:00
Conrado Gouvea 4edd9009a2
Bump bindgen 0.71.1 (#187)
* Bump bindgen from 0.69.4 to 0.71.1

Bumps [bindgen](https://github.com/rust-lang/rust-bindgen) from 0.69.4 to 0.71.1.
- [Release notes](https://github.com/rust-lang/rust-bindgen/releases)
- [Changelog](https://github.com/rust-lang/rust-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/rust-bindgen/compare/v0.69.4...v0.71.1)

---
updated-dependencies:
- dependency-name: bindgen
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* update proc-macro2 in Cargo.lock

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-23 19:11:15 -03:00
Conrado Gouvea 38cc608560
Bump cc 1.1.10 (#186)
* Bump cc from 1.0.95 to 1.1.10

Bumps [cc](https://github.com/rust-lang/cc-rs) from 1.0.95 to 1.1.10.
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/1.0.95...cc-v1.1.10)

---
updated-dependencies:
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix clippy issue

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-23 19:11:03 -03:00
dependabot[bot] a9bfcd5da2
Bump libc from 0.2.159 to 0.2.168 (#181)
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.159 to 0.2.168.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.168/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.159...0.2.168)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: indirect
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-07 14:23:05 -03:00
dependabot[bot] 18af6894fd
Bump EmbarkStudios/cargo-deny-action from 1 to 2 (#180)
Bumps [EmbarkStudios/cargo-deny-action](https://github.com/embarkstudios/cargo-deny-action) from 1 to 2.
- [Release notes](https://github.com/embarkstudios/cargo-deny-action/releases)
- [Commits](https://github.com/embarkstudios/cargo-deny-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: EmbarkStudios/cargo-deny-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-07 14:22:33 -03:00
dependabot[bot] 116dc00a7f
Bump lazy_static from 1.4.0 to 1.5.0 (#163)
Bumps [lazy_static](https://github.com/rust-lang-nursery/lazy-static.rs) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/rust-lang-nursery/lazy-static.rs/releases)
- [Commits](https://github.com/rust-lang-nursery/lazy-static.rs/compare/1.4.0...1.5.0)

---
updated-dependencies:
- dependency-name: lazy_static
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-07 14:21:54 -03:00
Greg Pfeil 7bcfb7729b
Add a basic rust-toolchain.toml (#176)
* Add a basic rust-toolchain.toml

Matches the one in Zebra, although I think pinning to a specific release
(e.g., `channel = "1.82"`) would help with consistency, perhaps avoiding
issues like
https://github.com/ZcashFoundation/zcash_script/pull/174#issuecomment-2445336306

* Remove redundant toolchain info from GH workflow

[actions-rs/toolchain doesn’t support TOML-formatted
rust-toolchain files](actions-rs/toolchain#126), but it’s unnecessary anyway.

- actions-rs/cargo will pick up the rust-toolchain.toml, so we usually
  don’t need to mention the toolchain at all;
- the Windows build just runs `rustup target add x86_64-pc-windows-msvc`
  directly; and
- where we want to build with multiple toolchains (in a matrix), there
  are some slightly-awkward conditionals.

This also makes some other changes:

- `fail-fast` is disabled because it hides useful & distinct build
  results; and
-  `rustup component add` for clippy and rustfmt are removed because
  they’re in the rust-toolchain.toml toolchain, and we want to make sure
  they are, so that they’re available to developers.

* Pin rustup channel to "1.81"

Newer versions until 1.85 (current nightly) have some breakage wrt C++
linking.

* Have bindgen target correct rustc version

It should match the version in rust-toolchain.toml. Unfortunately, it’s
not possible to reference that directly, so this adds comments to remind
contributors to update them together.
2024-12-16 10:47:41 +01:00
Greg Pfeil a429b3f8d3
Address Str4d’s comments on #171 (#175)
* Address Str4d’s comments on #171

Notably, `HashType` has changed incompatibly, so
ZcashFoundation/zebra#8751 will need to be updated.

* Apply suggestions from code review

Co-authored-by: Jack Grigg <thestr4d@gmail.com>

* Restrict bitflags used for `HashType` in v5 tx

---------

Co-authored-by: Jack Grigg <thestr4d@gmail.com>
2024-10-24 19:24:40 -03:00
Greg Pfeil 9d16e79c72
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.
2024-09-17 16:11:32 -03:00
22 changed files with 3776 additions and 250 deletions

View File

@ -2,6 +2,9 @@ version: 2
updates:
- package-ecosystem: cargo
directory: '/'
# Update only the lockfile. We shouldn't update Cargo.toml unless it's for
# a security issue, or if we need a new feature of the dependency.
versioning-strategy: lockfile-only
schedule:
interval: daily
timezone: America/New_York

View File

@ -10,70 +10,72 @@ jobs:
check:
name: Check
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- uses: actions-rs/cargo@v1
with:
command: check
# Test that "cargo package" works. This make sure it's publishable,
# Test that "cargo package" works. This makes sure it's publishable,
# since we had issues where "cargo build" worked but "package" didn't.
package:
name: Package
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust:
- stable
toolchain:
- rust-toolchain.toml
- nightly
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- uses: actions-rs/cargo@v1
if: matrix.toolchain == 'rust-toolchain.toml'
with:
command: package
- run: rustup install ${{ matrix.toolchain }}
if: matrix.toolchain != 'rust-toolchain.toml'
- uses: actions-rs/cargo@v1
if: matrix.toolchain != 'rust-toolchain.toml'
with:
command: package
toolchain: ${{ matrix.toolchain }}
test-versions:
name: Test Suite
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust:
- stable
toolchain:
- rust-toolchain.toml
- nightly
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- uses: actions-rs/cargo@v1
if: matrix.toolchain == 'rust-toolchain.toml'
with:
command: test
- run: rustup install ${{ matrix.toolchain }}
if: matrix.toolchain != 'rust-toolchain.toml'
- uses: actions-rs/cargo@v1
if: matrix.toolchain != 'rust-toolchain.toml'
with:
command: test
toolchain: ${{ matrix.toolchain }}
test-os:
name: Test Suite
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# "windows-latest" was removed; see https://github.com/ZcashFoundation/zcash_script/issues/38
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v4
@ -85,19 +87,8 @@ jobs:
# - name: install LLVM on Mac
# if: matrix.os == 'macOS-latest'
# run: brew install llvm
- uses: actions-rs/toolchain@v1
- run: rustup target add x86_64-pc-windows-msvc
if: matrix.os == 'windows-latest'
with:
target: x86_64-pc-windows-msvc
toolchain: stable
profile: minimal
override: true
- uses: actions-rs/toolchain@v1
if: matrix.os != 'windows-latest'
with:
toolchain: stable
profile: minimal
override: true
- uses: actions-rs/cargo@v1
if: matrix.os == 'windows-latest'
with:
@ -111,19 +102,10 @@ jobs:
fmt:
name: Rustfmt
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
@ -132,10 +114,22 @@ jobs:
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
fuzz:
name: Fuzz
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
- nightly
steps:
- uses: actions/checkout@v4
with:
@ -144,8 +138,8 @@ jobs:
with:
toolchain: ${{ matrix.rust }}
override: true
- run: rustup component add clippy
- run: cargo install cargo-fuzz
- uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
command: fuzz
args: run compare -- -max_len=20000 -max_total_time=100

View File

@ -43,7 +43,7 @@ jobs:
# this check also runs with optional features off
# so we expect some warnings about "skip tree root was not found"
- name: Check ${{ matrix.checks }} with features ${{ matrix.features }}
uses: EmbarkStudios/cargo-deny-action@v1
uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check ${{ matrix.checks }}
arguments: --workspace ${{ matrix.features }}

View File

@ -8,6 +8,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] - ReleaseDate
## [0.3.1](https://github.com/ZcashFoundation/zcash_script/compare/v0.3.0...v0.3.1) - 2025-05-17
### Other
- Remove extra `&` from `SighashCalculator` ([#216](https://github.com/ZcashFoundation/zcash_script/pull/216))
## [0.3.0](https://github.com/ZcashFoundation/zcash_script/compare/v0.2.0...v0.3.0) - 2025-05-13
- Another major API update. The previous C++ functions were moved to the `cxx`
module. A new Rust implementation has been added. In the root of the
crate there are now "Intepreters" which allow choosing which implementation
to use.
- Eliminate `ScriptNum` ([#208](https://github.com/ZcashFoundation/zcash_script/pull/208))
- Add various stepwise functionality ([#204](https://github.com/ZcashFoundation/zcash_script/pull/204))
- Addressing post-hoc PR feedback on #174 ([#197](https://github.com/ZcashFoundation/zcash_script/pull/197))
- Expose `ScriptError` on C++ side ([#195](https://github.com/ZcashFoundation/zcash_script/pull/195))
- Change type of `lock_time` parameter ([#190](https://github.com/ZcashFoundation/zcash_script/pull/190))
- Initial Rust implementation ([#174](https://github.com/ZcashFoundation/zcash_script/pull/174))
- Add a basic rust-toolchain.toml ([#176](https://github.com/ZcashFoundation/zcash_script/pull/176))
- Address Str4ds comments on #171 ([#175](https://github.com/ZcashFoundation/zcash_script/pull/175))
- Provide a Rustier wrapper for zcash_script ([#171](https://github.com/ZcashFoundation/zcash_script/pull/171))
## [0.2.0] - 2024-06-10
- Major API update. Most functions have been removed and a new callback-based

546
Cargo.lock generated
View File

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "aho-corasick"
@ -12,17 +12,33 @@ dependencies = [
]
[[package]]
name = "bindgen"
version = "0.69.4"
name = "arbitrary"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "bindgen"
version = "0.71.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools",
"lazy_static",
"lazycell",
"log",
"prettyplease",
"proc-macro2",
@ -31,24 +47,69 @@ dependencies = [
"rustc-hash",
"shlex",
"syn",
"which",
]
[[package]]
name = "bit-set"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitcoin-io"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf"
[[package]]
name = "bitcoin_hashes"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16"
dependencies = [
"bitcoin-io",
"hex-conservative",
]
[[package]]
name = "bitflags"
version = "2.4.0"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.0.95"
version = "1.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b"
checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf"
dependencies = [
"jobserver",
"libc",
"once_cell",
"shlex",
]
[[package]]
@ -78,30 +139,90 @@ dependencies = [
]
[[package]]
name = "either"
version = "1.9.0"
name = "cpufeatures"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[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 = "errno"
version = "0.3.3"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"cc",
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
@ -117,12 +238,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "home"
version = "0.5.5"
name = "hex-conservative"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd"
dependencies = [
"windows-sys",
"arrayvec",
]
[[package]]
@ -145,21 +266,25 @@ dependencies = [
[[package]]
name = "lazy_static"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.148"
version = "0.2.168"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
[[package]]
name = "libfuzzer-sys"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75"
dependencies = [
"arbitrary",
"cc",
]
[[package]]
name = "libloading"
@ -173,9 +298,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
version = "0.4.7"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "log"
@ -205,12 +330,45 @@ dependencies = [
"minimal-lexical",
]
[[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]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "pin-project-lite"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "prettyplease"
version = "0.2.15"
@ -223,13 +381,39 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.79"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "proptest"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50"
dependencies = [
"bit-set",
"bit-vec",
"bitflags",
"lazy_static",
"num-traits 0.2.19",
"rand",
"rand_chacha",
"rand_xorshift",
"regex-syntax 0.8.5",
"rusty-fork",
"tempfile",
"unarray",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.36"
@ -239,6 +423,45 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_xorshift"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
dependencies = [
"rand_core",
]
[[package]]
name = "regex"
version = "1.9.5"
@ -248,7 +471,7 @@ dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
"regex-syntax 0.7.5",
]
[[package]]
@ -259,7 +482,7 @@ checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"regex-syntax 0.7.5",
]
[[package]]
@ -269,16 +492,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "rustc-hash"
version = "1.1.0"
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "ripemd"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f"
dependencies = [
"digest",
]
[[package]]
name = "rustc-hash"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
[[package]]
name = "rustix"
version = "0.38.13"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags",
"errno",
@ -288,10 +526,64 @@ dependencies = [
]
[[package]]
name = "shlex"
version = "1.2.0"
name = "rusty-fork"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
dependencies = [
"fnv",
"quick-error",
"tempfile",
"wait-timeout",
]
[[package]]
name = "secp256k1"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252"
dependencies = [
"bitcoin_hashes",
"rand",
"secp256k1-sys",
]
[[package]]
name = "secp256k1-sys"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1433bd67156263443f14d603720b082dd3121779323fce20cba2aa07b874bc1b"
dependencies = [
"cc",
]
[[package]]
name = "sha-1"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
@ -304,6 +596,62 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
dependencies = [
"cfg-if",
"fastrand",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "tracing"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
"once_cell",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unarray"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]]
name = "unicode-ident"
version = "1.0.12"
@ -311,17 +659,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "which"
version = "4.4.2"
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wait-timeout"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
dependencies = [
"either",
"home",
"once_cell",
"rustix",
"libc",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
@ -346,22 +703,23 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
@ -370,52 +728,88 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zcash_script"
version = "0.2.0"
version = "0.3.1"
dependencies = [
"bindgen",
"bitflags",
"cc",
"enum_primitive",
"hex",
"lazy_static",
"libfuzzer-sys",
"proptest",
"ripemd",
"secp256k1",
"sha-1",
"sha2",
"tracing",
]
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@ -1,7 +1,12 @@
[package]
name = "zcash_script"
version = "0.2.0"
authors = ["Tamas Blummer <tamas.blummer@gmail.com>", "Zcash Foundation <zebra@zfnd.org>"]
version = "0.3.1"
authors = [
"Electric Coin Company <info@electriccoin.co>",
"Greg Pfeil <greg@technomadic.org>",
"Tamas Blummer <tamas.blummer@gmail.com>",
"Zcash Foundation <zebra@zfnd.org>",
]
license = "Apache-2.0"
readme = "README.md"
build = "build.rs"
@ -15,6 +20,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,28 +64,36 @@ path = "src/lib.rs"
[features]
external-secp = []
test-dependencies = []
[dependencies]
bitflags = "2.8"
enum_primitive = "0.1"
ripemd = "0.1"
secp256k1 = "0.30"
sha-1 = "0.10"
sha2 = "0.10"
tracing = "0.1"
[build-dependencies]
# The `bindgen` dependency should automatically upgrade to match the version used by zebra-state's `rocksdb` dependency in:
# https://github.com/ZcashFoundation/zebra/blob/main/zebra-state/Cargo.toml
#
# Treat minor versions with a zero major version as compatible (cargo doesn't by default).
bindgen = ">= 0.64.0"
# These dependencies are shared with a lot of other Zebra dependencies,
# so they are configured to automatically upgrade to match Zebra.
# But we try to use the latest versions here, to catch any bugs in `zcash_script`'s CI.
cc = { version = "1.0.94", features = ["parallel"] }
# We want zcash-script to be compatible with whatever version of `bindgen` that
# is being indirectly used by Zebra via the `rocksdb` dependency. To avoid
# having to update zcash-script every time Zebra updates its `rocksdb`
# dependency, we allow any version of `bindgen`, even major version bumps, by
# using the `>=` operator. There is some chance that this breaks the build if a
# breaking API change affects us, but we can always fix it then.
bindgen = ">= 0.69.5"
# Same reasoning as above
cc = { version = ">= 1.2.11", features = ["parallel"] }
[dev-dependencies]
# These dependencies are shared with a lot of other Zebra dependencies.
# (See above.)
#
# Treat minor versions with a zero major version as compatible (cargo doesn't by default).
# Same reasoning as above
hex = ">= 0.4.3"
lazy_static = "1.4.0"
lazy_static = "1.5.0"
proptest = "1.6"
[target.'cfg(linux)'.dev-dependencies]
libfuzzer-sys = "0.4"
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"

View File

@ -23,6 +23,10 @@ impl fmt::Display for Error {
impl std::error::Error for Error {}
// `bindgen::RustTarget::Stable_*` is deprecated in bindgen >= 0.71.0, but we are constrained
// downstream by the version supported by librocksdb-sys. However, one of our CI jobs still manages
// to pull a newer version, so this silences the deprecation on that job.
#[allow(deprecated)]
fn bindgen_headers() -> Result<()> {
println!("cargo:rerun-if-changed=depend/zcash/src/script/zcash_script.h");
@ -31,6 +35,9 @@ fn bindgen_headers() -> Result<()> {
// Tell cargo to invalidate the built crate whenever any of the
// included header files changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
// This should not reference a version newer than rust-toolchain.toml. See
// rust-lang/rust-bindgen#3049 for a potential future solution.
.rust_target(bindgen::RustTarget::Stable_1_73)
// Finish the builder and generate the bindings.
.generate()
.map_err(|_| Error::GenerateBindings)?;
@ -192,5 +199,5 @@ fn language_std(build: &mut cc::Build, std: &str) {
"-std="
};
build.flag(&[flag, std].concat());
build.flag([flag, std].concat());
}

View File

@ -53,6 +53,8 @@ typedef enum ScriptError_t
/* softfork safeness */
SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS,
SCRIPT_ERR_VERIFY_SCRIPT,
SCRIPT_ERR_ERROR_COUNT
} ScriptError;

View File

@ -7,10 +7,11 @@
#include "zcash_script.h"
#include "script/interpreter.h"
#include "script/script_error.h"
#include "version.h"
namespace {
inline int set_error(zcash_script_error* ret, zcash_script_error serror)
inline int set_error(ScriptError* ret, ScriptError serror)
{
if (ret)
*ret = serror;
@ -42,12 +43,10 @@ int zcash_script_verify_callback(
const unsigned char* scriptSig,
unsigned int scriptSigLen,
unsigned int flags,
zcash_script_error* err)
ScriptError* script_err)
{
try {
set_error(err, zcash_script_ERR_OK);
CScriptNum nLockTimeNum = CScriptNum(nLockTime);
ScriptError script_err = SCRIPT_ERR_OK;
return VerifyScript(
CScript(scriptSig, scriptSig + scriptSigLen),
CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen),
@ -56,8 +55,8 @@ int zcash_script_verify_callback(
// consensusBranchId is not longer used with the callback API; the argument
// was left there to minimize changes to interpreter.cpp
0,
&script_err);
script_err);
} catch (const std::exception&) {
return set_error(err, zcash_script_ERR_VERIFY_SCRIPT);
return set_error(script_err, SCRIPT_ERR_VERIFY_SCRIPT);
}
}

View File

@ -8,6 +8,7 @@
#define ZCASH_SCRIPT_ZCASH_SCRIPT_H
#include <stdint.h>
#include "script_error.h"
#if defined(BUILD_BITCOIN_INTERNAL) && defined(HAVE_CONFIG_H)
#include "config/bitcoin-config.h"
@ -36,19 +37,6 @@ extern "C" {
#define ZCASH_SCRIPT_API_VER 4
typedef enum zcash_script_error_t
{
zcash_script_ERR_OK = 0,
zcash_script_ERR_TX_INDEX,
zcash_script_ERR_TX_SIZE_MISMATCH,
zcash_script_ERR_TX_DESERIALIZE,
// Defined since API version 3.
zcash_script_ERR_TX_VERSION,
zcash_script_ERR_ALL_PREV_OUTPUTS_SIZE_MISMATCH,
zcash_script_ERR_ALL_PREV_OUTPUTS_DESERIALIZE,
zcash_script_ERR_VERIFY_SCRIPT,
} zcash_script_error;
/** Script verification flags */
enum
{
@ -93,8 +81,7 @@ EXPORT_SYMBOL unsigned int zcash_script_legacy_sigop_count_script(
/// - 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 being set to
/// zcash_script_ERR_OK and a return value of 0.
/// Note that script verification failure is indicated by a return value of 0.
EXPORT_SYMBOL int zcash_script_verify_callback(
const void* ctx,
void (*sighash)(unsigned char* sighash, unsigned int sighashLen, const void* ctx, const unsigned char* scriptCode, unsigned int scriptCodeLen, int hashType),
@ -105,7 +92,7 @@ EXPORT_SYMBOL int zcash_script_verify_callback(
const unsigned char* scriptSig,
unsigned int scriptSigLen,
unsigned int flags,
zcash_script_error* err);
ScriptError* err);
#ifdef __cplusplus
} // extern "C"

4
fuzz/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
target
corpus
artifacts
coverage

19
fuzz/Cargo.toml Normal file
View File

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

View File

@ -0,0 +1,41 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
extern crate zcash_script;
use zcash_script::interpreter::CallbackTransactionSignatureChecker;
use zcash_script::*;
fn missing_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> {
None
}
fuzz_target!(|tup: (u32, bool, &[u8], &[u8], u32)| {
// `fuzz_target!` doesnt support pattern matching in the parameter list.
let (lock_time, is_final, pub_key, sig, flag_bits) = tup;
let flags = testing::repair_flags(VerificationFlags::from_bits_truncate(flag_bits));
let ret = check_verify_callback(
&CxxInterpreter {
sighash: &missing_sighash,
lock_time,
is_final,
},
&rust_interpreter(
flags,
CallbackTransactionSignatureChecker {
sighash: &missing_sighash,
lock_time: lock_time.into(),
is_final,
},
),
pub_key,
sig,
flags,
);
assert_eq!(
ret.0,
ret.1.map_err(normalize_error),
"original Rust result: {:?}",
ret.1
);
});

5
rust-toolchain.toml Normal file
View File

@ -0,0 +1,5 @@
[toolchain]
# TODO: C++ linking on Linux broke in 1.82, but is fixed again in 1.85. This can be bumped once that
# is released.
channel = "1.81"
components = ["clippy", "rustfmt"]

114
src/cxx.rs Normal file
View File

@ -0,0 +1,114 @@
//! 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};
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);
}
}

3
src/external/mod.rs vendored Normal file
View File

@ -0,0 +1,3 @@
//! Modules that parallel the C++ implementation, but which live outside the script directory.
pub mod pubkey;

56
src/external/pubkey.rs vendored Normal file
View File

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

1435
src/interpreter.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,117 +1,536 @@
//! 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)]
#![doc(html_root_url = "https://docs.rs/zcash_script/0.3.0")]
#![allow(non_snake_case)]
#![allow(unsafe_code)]
#![allow(unused_imports)]
#![allow(clippy::unwrap_or_default)]
#[macro_use]
extern crate enum_primitive;
pub mod cxx;
mod external;
pub mod interpreter;
mod script;
pub mod script_error;
mod zcash_script;
use std::os::raw::{c_int, c_uint, c_void};
use tracing::warn;
pub use interpreter::{
CallbackTransactionSignatureChecker, DefaultStepEvaluator, HashType, SighashCalculator,
SignedOutputs, VerificationFlags,
};
use script_error::ScriptError;
pub use zcash_script::*;
pub struct CxxInterpreter<'a> {
pub sighash: SighashCalculator<'a>,
pub lock_time: u32,
pub is_final: bool,
}
impl From<cxx::ScriptError> for Error {
#[allow(non_upper_case_globals)]
fn from(err_code: cxx::ScriptError) -> Self {
match err_code {
cxx::ScriptError_t_SCRIPT_ERR_OK => Error::Ok(ScriptError::Ok),
cxx::ScriptError_t_SCRIPT_ERR_UNKNOWN_ERROR => Error::Ok(ScriptError::UnknownError),
cxx::ScriptError_t_SCRIPT_ERR_EVAL_FALSE => Error::Ok(ScriptError::EvalFalse),
cxx::ScriptError_t_SCRIPT_ERR_OP_RETURN => Error::Ok(ScriptError::OpReturn),
cxx::ScriptError_t_SCRIPT_ERR_SCRIPT_SIZE => Error::Ok(ScriptError::ScriptSize),
cxx::ScriptError_t_SCRIPT_ERR_PUSH_SIZE => Error::Ok(ScriptError::PushSize),
cxx::ScriptError_t_SCRIPT_ERR_OP_COUNT => Error::Ok(ScriptError::OpCount),
cxx::ScriptError_t_SCRIPT_ERR_STACK_SIZE => Error::Ok(ScriptError::StackSize),
cxx::ScriptError_t_SCRIPT_ERR_SIG_COUNT => Error::Ok(ScriptError::SigCount),
cxx::ScriptError_t_SCRIPT_ERR_PUBKEY_COUNT => Error::Ok(ScriptError::PubKeyCount),
cxx::ScriptError_t_SCRIPT_ERR_VERIFY => Error::Ok(ScriptError::Verify),
cxx::ScriptError_t_SCRIPT_ERR_EQUALVERIFY => Error::Ok(ScriptError::EqualVerify),
cxx::ScriptError_t_SCRIPT_ERR_CHECKMULTISIGVERIFY => {
Error::Ok(ScriptError::CheckMultisigVerify)
}
cxx::ScriptError_t_SCRIPT_ERR_CHECKSIGVERIFY => Error::Ok(ScriptError::CheckSigVerify),
cxx::ScriptError_t_SCRIPT_ERR_NUMEQUALVERIFY => Error::Ok(ScriptError::NumEqualVerify),
cxx::ScriptError_t_SCRIPT_ERR_BAD_OPCODE => Error::Ok(ScriptError::BadOpcode),
cxx::ScriptError_t_SCRIPT_ERR_DISABLED_OPCODE => Error::Ok(ScriptError::DisabledOpcode),
cxx::ScriptError_t_SCRIPT_ERR_INVALID_STACK_OPERATION => {
Error::Ok(ScriptError::InvalidStackOperation)
}
cxx::ScriptError_t_SCRIPT_ERR_INVALID_ALTSTACK_OPERATION => {
Error::Ok(ScriptError::InvalidAltstackOperation)
}
cxx::ScriptError_t_SCRIPT_ERR_UNBALANCED_CONDITIONAL => {
Error::Ok(ScriptError::UnbalancedConditional)
}
cxx::ScriptError_t_SCRIPT_ERR_NEGATIVE_LOCKTIME => {
Error::Ok(ScriptError::NegativeLockTime)
}
cxx::ScriptError_t_SCRIPT_ERR_UNSATISFIED_LOCKTIME => {
Error::Ok(ScriptError::UnsatisfiedLockTime)
}
cxx::ScriptError_t_SCRIPT_ERR_SIG_HASHTYPE => Error::Ok(ScriptError::SigHashType),
cxx::ScriptError_t_SCRIPT_ERR_SIG_DER => Error::Ok(ScriptError::SigDER),
cxx::ScriptError_t_SCRIPT_ERR_MINIMALDATA => Error::Ok(ScriptError::MinimalData),
cxx::ScriptError_t_SCRIPT_ERR_SIG_PUSHONLY => Error::Ok(ScriptError::SigPushOnly),
cxx::ScriptError_t_SCRIPT_ERR_SIG_HIGH_S => Error::Ok(ScriptError::SigHighS),
cxx::ScriptError_t_SCRIPT_ERR_SIG_NULLDUMMY => Error::Ok(ScriptError::SigNullDummy),
cxx::ScriptError_t_SCRIPT_ERR_PUBKEYTYPE => Error::Ok(ScriptError::PubKeyType),
cxx::ScriptError_t_SCRIPT_ERR_CLEANSTACK => Error::Ok(ScriptError::CleanStack),
cxx::ScriptError_t_SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS => {
Error::Ok(ScriptError::DiscourageUpgradableNOPs)
}
cxx::ScriptError_t_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) };
// SAFETY: `ctx` is a valid `SighashCalculator` constructed in `verify_callback`
// which forwards it to the `CallbackTransactionSignatureChecker`.
let callback = unsafe { *(ctx as *const SighashCalculator) };
// We dont need to handle strictness here, because it is checked (when necessary) by
// `CheckSignatureEncoding` before `CallbackTransactionSignatureChecker` calls this callback.
// And we dont have access to the flags here to determine if it should be checked.
if let Some(sighash) = HashType::from_bits(hash_type, false)
.ok()
.and_then(|ht| callback(script_code_vec, ht))
{
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<'a> ZcashScript for CxxInterpreter<'a> {
fn verify_callback(
&self,
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 {
cxx::zcash_script_verify_callback(
(&self.sighash as *const SighashCalculator) as *const c_void,
Some(sighash_callback),
self.lock_time.into(),
if self.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(&self, script: &[u8]) -> Result<u32, Error> {
script
.len()
.try_into()
.map_err(Error::InvalidScriptSize)
.map(|script_len| unsafe {
cxx::zcash_script_legacy_sigop_count_script(script.as_ptr(), script_len)
})
}
}
/// 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>(
first: &T,
second: &U,
script: &[u8],
) -> (Result<u32, Error>, Result<u32, Error>) {
(
first.legacy_sigop_count_script(script),
second.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>(
first: &T,
second: &U,
script_pub_key: &[u8],
script_sig: &[u8],
flags: VerificationFlags,
) -> (Result<(), Error>, Result<(), Error>) {
(
first.verify_callback(script_pub_key, script_sig, flags),
second.verify_callback(script_pub_key, script_sig, flags),
)
}
/// Convert errors that dont exist in the C++ code into the cases that do.
pub fn normalize_error(err: Error) -> Error {
match err {
Error::Ok(serr) => Error::Ok(match serr {
ScriptError::ReadError { .. } => ScriptError::BadOpcode,
ScriptError::ScriptNumError(_) => ScriptError::UnknownError,
_ => serr,
}),
_ => err,
}
}
/// A tag to indicate that both the C++ and Rust implementations of zcash_script should be used,
/// with their results compared.
pub struct ComparisonInterpreter<T, U> {
first: T,
second: U,
}
pub fn cxx_rust_comparison_interpreter(
sighash: SighashCalculator,
lock_time: u32,
is_final: bool,
flags: VerificationFlags,
) -> ComparisonInterpreter<
CxxInterpreter,
StepwiseInterpreter<DefaultStepEvaluator<CallbackTransactionSignatureChecker>>,
> {
ComparisonInterpreter {
first: CxxInterpreter {
sighash,
lock_time,
is_final,
},
second: rust_interpreter(
flags,
CallbackTransactionSignatureChecker {
sighash,
lock_time: lock_time.into(),
is_final,
},
),
}
}
/// 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<T: ZcashScript, U: ZcashScript> ZcashScript for ComparisonInterpreter<T, U> {
fn legacy_sigop_count_script(&self, script: &[u8]) -> Result<u32, Error> {
let (cxx, rust) = check_legacy_sigop_count_script(&self.first, &self.second, 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(
&self,
script_pub_key: &[u8],
script_sig: &[u8],
flags: VerificationFlags,
) -> Result<(), Error> {
let (cxx, rust) =
check_verify_callback(&self.first, &self.second, script_pub_key, script_sig, flags);
if rust.map_err(normalize_error) != cxx {
// probably want to distinguish between
// - one succeeding when the other fails (bad), 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::*;
use crate::{
interpreter::{State, StepFn},
script::{Operation, Script},
};
/// 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;
/// This is the same as `DefaultStepEvaluator`, except that it skips `OP_EQUAL`, allowing us to
/// test comparison failures.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct BrokenStepEvaluator<T>(pub T);
impl<T: StepFn> StepFn for BrokenStepEvaluator<T> {
type Payload = T::Payload;
fn call<'a>(
&self,
pc: &'a [u8],
script: &Script,
state: &mut State,
payload: &mut T::Payload,
) -> Result<&'a [u8], ScriptError> {
self.0.call(
if pc[0] == Operation::OP_EQUAL.into() {
&pc[1..]
} else {
pc
},
script,
state,
payload,
)
}
}
}
// 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 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();
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>()
.copied()
}
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>()
.copied()
}
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 lock_time: u32 = 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,
let ret = check_verify_callback(
&CxxInterpreter {
sighash: &sighash,
lock_time,
is_final,
},
&rust_interpreter(
flags,
&mut err,
)
};
CallbackTransactionSignatureChecker {
sighash: &sighash,
lock_time: lock_time.into(),
is_final,
},
),
script_pub_key,
script_sig,
flags,
);
assert!(ret == 1);
assert_eq!(ret.0, ret.1.map_err(normalize_error));
assert!(ret.0.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 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,
let lock_time: u32 = 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 = check_verify_callback(
&CxxInterpreter {
sighash: &invalid_sighash,
lock_time,
is_final,
},
&rust_interpreter(
flags,
&mut err,
)
};
CallbackTransactionSignatureChecker {
sighash: &invalid_sighash,
lock_time: lock_time.into(),
is_final,
},
),
script_pub_key,
script_sig,
flags,
);
assert!(ret != 1);
assert_eq!(ret.0, ret.1.map_err(normalize_error));
assert_eq!(ret.0, Err(Error::Ok(ScriptError::EvalFalse)));
}
#[test]
fn it_fails_on_missing_sighash() {
let lock_time: u32 = 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 = check_verify_callback(
&CxxInterpreter {
sighash: &missing_sighash,
lock_time,
is_final,
},
&rust_interpreter(
flags,
CallbackTransactionSignatureChecker {
sighash: &missing_sighash,
lock_time: lock_time.into(),
is_final,
},
),
script_pub_key,
script_sig,
flags,
);
assert_eq!(ret.0, ret.1.map_err(normalize_error));
assert_eq!(ret.0, Err(Error::Ok(ScriptError::EvalFalse)));
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 20_000, .. ProptestConfig::default()
})]
#[test]
fn test_arbitrary_scripts(
lock_time in prop::num::u32::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),
flag_bits in prop::bits::u32::masked(VerificationFlags::all().bits()),
) {
let flags = repair_flags(VerificationFlags::from_bits_truncate(flag_bits));
let ret = check_verify_callback(
&CxxInterpreter {
sighash: &sighash,
lock_time,
is_final,
},
&rust_interpreter(
flags,
CallbackTransactionSignatureChecker {
sighash: &sighash,
lock_time: lock_time.into(),
is_final,
},
),
&pub_key[..],
&sig[..],
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::u32::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),
flag_bits in prop::bits::u32::masked(
// Dont waste test cases on whether or not `SigPushOnly` is set.
(VerificationFlags::all() - VerificationFlags::SigPushOnly).bits()),
) {
let flags = repair_flags(VerificationFlags::from_bits_truncate(flag_bits))
| VerificationFlags::SigPushOnly;
let ret = check_verify_callback(
&CxxInterpreter {
sighash: &sighash,
lock_time,
is_final,
},
&rust_interpreter(
flags,
CallbackTransactionSignatureChecker {
sighash: &sighash,
lock_time: lock_time.into(),
is_final,
},
),
&pub_key[..],
&sig[..],
flags,
);
prop_assert_eq!(ret.0, ret.1.map_err(normalize_error),
"original Rust result: {:?}", ret.1);
}
}
}

489
src/script.rs Normal file
View File

@ -0,0 +1,489 @@
#![allow(non_camel_case_types)]
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: i64 = 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_CHECKLOCKTIMEVERIFY = 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::*;
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
}
}
const DEFAULT_MAX_NUM_SIZE: usize = 4;
pub fn parse_num(
vch: &Vec<u8>,
require_minimal: bool,
max_num_size: Option<usize>,
) -> Result<i64, ScriptNumError> {
match vch.last() {
None => Ok(0),
Some(vch_back) => {
let max_num_size = max_num_size.unwrap_or(DEFAULT_MAX_NUM_SIZE);
if vch.len() > max_num_size {
return Err(ScriptNumError::Overflow {
max_num_size,
actual: vch.len(),
});
}
if require_minimal {
// 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_back & 0x7F) == 0 {
// One exception: if there's more than one byte and the most significant bit of
// the second-most-significant-byte is set then it would have conflicted with
// the sign bit if one fewer byte were used, and so such encodings are minimal.
// An example of this is +-255, which have minimal encodings [0xff, 0x00] and
// [0xff, 0x80] respectively.
if vch.len() <= 1 || (vch[vch.len() - 2] & 0x80) == 0 {
return Err(ScriptNumError::NonMinimalEncoding);
}
}
}
if *vch == vec![0, 0, 0, 0, 0, 0, 0, 128, 128] {
// Match the behaviour of the C++ code, which special-cased this encoding to avoid
// an undefined shift of a signed type by 64 bits.
return Ok(i64::MIN);
};
// Ensure defined behaviour (in Rust, left shift of `i64` by 64 bits is an arithmetic
// overflow that may panic or give an unspecified result). The above encoding of
// `i64::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)
}
}
}
pub fn serialize_num(value: i64) -> Vec<u8> {
if value == 0 {
return Vec::new();
}
if value == i64::MIN {
// The code below was based on buggy C++ code, that produced the "wrong" result for
// INT64_MIN. In that case we intentionally return the result that the C++ code as compiled
// for zcashd (with `-fwrapv`) originally produced 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
}
/** 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: &[u8]) -> Result<(Opcode, &[u8]), ScriptError> {
Self::get_op2(script).map(|(op, _, remainder)| (op, remainder))
}
fn split_value(script: &[u8], needed_bytes: usize) -> Result<(&[u8], &[u8]), ScriptError> {
script
.split_at_checked(needed_bytes)
.ok_or(ScriptError::ReadError {
expected_bytes: needed_bytes,
available_bytes: script.len(),
})
}
/// First splits `size_size` bytes to determine the size of the value to read, then splits the
/// value.
fn split_tagged_value(script: &[u8], size_size: usize) -> Result<(&[u8], &[u8]), ScriptError> {
Script::split_value(script, size_size).and_then(|(bytes, script)| {
let mut size = 0;
for byte in bytes.iter().rev() {
size <<= 8;
size |= usize::from(*byte);
}
Script::split_value(script, size)
})
}
pub fn get_op2(script: &[u8]) -> Result<(Opcode, &[u8], &[u8]), ScriptError> {
match script.split_first() {
None => Err(ScriptError::ReadError {
expected_bytes: 1,
available_bytes: 0,
}),
Some((leading_byte, script)) => match Opcode::from(*leading_byte) {
op @ Opcode::PushValue(pv) => match pv {
OP_PUSHDATA1 => Script::split_tagged_value(script, 1),
OP_PUSHDATA2 => Script::split_tagged_value(script, 2),
OP_PUSHDATA4 => Script::split_tagged_value(script, 4),
PushdataBytelength(size_byte) => Script::split_value(script, size_byte.into()),
_ => Ok((&[][..], script)),
}
.map(|(value, script)| (op, value, script)),
op => Ok((op, &[], script)),
},
}
}
/** 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, new_pc) = match Self::get_op(pc) {
Ok(o) => o,
// Stop counting when we get to an invalid opcode.
Err(_) => break,
};
pc = new_pc;
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(_), new_pc)) = Self::get_op(pc) {
pc = new_pc;
} else {
return false;
}
}
true
}
}

67
src/script_error.rs Normal file
View File

@ -0,0 +1,67 @@
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ScriptNumError {
NonMinimalEncoding,
Overflow { max_num_size: usize, actual: usize },
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(i32)]
pub enum ScriptError {
Ok = 0, // Unused (except in converting the C++ error to Rust)
UnknownError,
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,
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)
}
}

451
src/zcash_script.rs Normal file
View File

@ -0,0 +1,451 @@
use std::num::TryFromIntError;
use super::interpreter::*;
use super::script::*;
use super::script_error::*;
/// This maps to `zcash_script_error_t`, but most of those cases arent 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)]
pub enum Error {
/// Any failure that results in the script being invalid.
Ok(ScriptError),
/// An exception was caught.
VerifyScript,
/// The script size cant 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),
}
/// 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: a callback function which is called to obtain the sighash.
/// - 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(
&self,
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(&self, script: &[u8]) -> Result<u32, Error>;
}
pub fn stepwise_verify<F>(
script_pub_key: &[u8],
script_sig: &[u8],
flags: VerificationFlags,
payload: &mut F::Payload,
stepper: &F,
) -> Result<(), Error>
where
F: StepFn,
{
verify_script(
&Script(script_sig),
&Script(script_pub_key),
flags,
payload,
stepper,
)
.map_err(Error::Ok)
}
/// A payload for comparing the results of two steppers.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StepResults<T, U> {
/// This contains the step-wise states of the steppers as long as they were identical. Its
/// `head` contains the initial state and its `tail` has a 1:1 correspondence to the opcodes
/// (not to the bytes).
pub identical_states: Vec<State>,
/// If the execution matched the entire way, then this contains `None`. If there was a
/// divergence, then this contains `Some` with a pair of `Result`s one representing each
/// steppers outcome at the point at which they diverged.
pub diverging_result: Option<(Result<State, ScriptError>, Result<State, ScriptError>)>,
/// The final payload of the first stepper.
pub payload_l: T,
/// The final payload of the second stepper.
pub payload_r: U,
}
impl<T, U> StepResults<T, U> {
pub fn initial(payload_l: T, payload_r: U) -> Self {
StepResults {
identical_states: vec![],
diverging_result: None,
payload_l,
payload_r,
}
}
}
/// This compares two `ZcashScript` implementations in a deep way checking the entire `State` step
/// by step. Note that this has some tradeoffs: one is performance. Another is that it doesnt run
/// the entire codepath of either implementation. The setup/wrapup code is specific to this
/// definition, but any differences there should be caught very easily by other testing mechanisms
/// (like `check_verify_callback`).
///
/// This returns a very debuggable result. See `StepResults` for details.
pub struct ComparisonStepEvaluator<'a, T, U> {
pub eval_step_l: &'a dyn StepFn<Payload = T>,
pub eval_step_r: &'a dyn StepFn<Payload = U>,
}
impl<'a, T: Clone, U: Clone> StepFn for ComparisonStepEvaluator<'a, T, U> {
type Payload = StepResults<T, U>;
fn call<'b>(
&self,
pc: &'b [u8],
script: &Script,
state: &mut State,
payload: &mut StepResults<T, U>,
) -> Result<&'b [u8], ScriptError> {
let mut right_state = (*state).clone();
let left = self
.eval_step_l
.call(pc, script, state, &mut payload.payload_l);
let right = self
.eval_step_r
.call(pc, script, &mut right_state, &mut payload.payload_r);
match (left, right) {
(Ok(_), Ok(_)) => {
if *state == right_state {
payload.identical_states.push(state.clone());
left
} else {
// In this case, the script hasnt failed, but we stop running
// anything
payload.diverging_result = Some((
left.map(|_| state.clone()),
right.map(|_| right_state.clone()),
));
Err(ScriptError::UnknownError)
}
}
// at least one is `Err`
(_, _) => {
if left != right {
payload.diverging_result = Some((
left.map(|_| state.clone()),
right.map(|_| right_state.clone()),
));
}
left.and(right)
}
}
}
}
pub struct StepwiseInterpreter<F>
where
F: StepFn,
{
initial_payload: F::Payload,
stepper: F,
}
impl<F: StepFn> StepwiseInterpreter<F> {
pub fn new(initial_payload: F::Payload, stepper: F) -> Self {
StepwiseInterpreter {
initial_payload,
stepper,
}
}
}
pub fn rust_interpreter<C: SignatureChecker + Copy>(
flags: VerificationFlags,
checker: C,
) -> StepwiseInterpreter<DefaultStepEvaluator<C>> {
StepwiseInterpreter {
initial_payload: (),
stepper: DefaultStepEvaluator { flags, checker },
}
}
impl<F: StepFn> ZcashScript for StepwiseInterpreter<F> {
/// Returns the number of transparent signature operations in the
/// transparent inputs and outputs of this transaction.
fn legacy_sigop_count_script(&self, script: &[u8]) -> Result<u32, Error> {
let cscript = Script(script);
Ok(cscript.get_sig_op_count(false))
}
fn verify_callback(
&self,
script_pub_key: &[u8],
script_sig: &[u8],
flags: VerificationFlags,
) -> Result<(), Error> {
let mut payload = self.initial_payload.clone();
stepwise_verify(
script_pub_key,
script_sig,
flags,
&mut payload,
&self.stepper,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::*;
use hex::FromHex;
use proptest::prelude::*;
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");
}
fn sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> {
hex::decode("e8c7bdac77f6bb1f3aba2eaa1fada551a9c8b3b5ecd1ef86e6e58a5f1aab952c")
.unwrap()
.as_slice()
.first_chunk::<32>()
.copied()
}
fn invalid_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> {
hex::decode("08c7bdac77f6bb1f3aba2eaa1fada551a9c8b3b5ecd1ef86e6e58a5f1aab952c")
.unwrap()
.as_slice()
.first_chunk::<32>()
.copied()
}
fn missing_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> {
None
}
#[test]
fn it_works() {
let n_lock_time: u32 = 2410374;
let is_final: bool = true;
let script_pub_key = &SCRIPT_PUBKEY;
let script_sig = &SCRIPT_SIG;
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
let checker = CallbackTransactionSignatureChecker {
sighash: &sighash,
lock_time: n_lock_time.into(),
is_final,
};
let rust_stepper = DefaultStepEvaluator { flags, checker };
let stepper = ComparisonStepEvaluator {
eval_step_l: &rust_stepper,
eval_step_r: &rust_stepper,
};
let mut res = StepResults::initial((), ());
let ret = stepwise_verify(script_pub_key, script_sig, flags, &mut res, &stepper);
if res.diverging_result != None {
panic!("invalid result: {:?}", res);
}
assert_eq!(ret, Ok(()));
}
#[test]
fn broken_stepper_causes_divergence() {
let n_lock_time: u32 = 2410374;
let is_final: bool = true;
let script_pub_key = &SCRIPT_PUBKEY;
let script_sig = &SCRIPT_SIG;
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
let checker = CallbackTransactionSignatureChecker {
sighash: &sighash,
lock_time: n_lock_time.into(),
is_final,
};
let rust_stepper = DefaultStepEvaluator { flags, checker };
let broken_stepper = BrokenStepEvaluator(rust_stepper);
let stepper = ComparisonStepEvaluator {
eval_step_l: &rust_stepper,
eval_step_r: &broken_stepper,
};
let mut res = StepResults::initial((), ());
let ret = stepwise_verify(script_pub_key, script_sig, flags, &mut res, &stepper);
// The final return value is from whichever stepper failed.
assert_eq!(
ret,
Err(Error::Ok(ScriptError::ReadError {
expected_bytes: 1,
available_bytes: 0,
}))
);
// `State`s are large, so we just check that there was some progress in lock step, and a
// divergence.
match res {
StepResults {
identical_states,
diverging_result:
Some((
Ok(state),
Err(ScriptError::ReadError {
expected_bytes: 1,
available_bytes: 0,
}),
)),
payload_l: (),
payload_r: (),
} => {
assert!(
identical_states.len() == 6
&& state.stack().size() == 4
&& state.altstack().empty()
&& state.op_count() == 2
&& state.vexec().empty()
);
}
_ => {
panic!("invalid result: {:?}", res);
}
}
}
#[test]
fn it_fails_on_invalid_sighash() {
let n_lock_time: u32 = 2410374;
let is_final: bool = true;
let script_pub_key = &SCRIPT_PUBKEY;
let script_sig = &SCRIPT_SIG;
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
let checker = CallbackTransactionSignatureChecker {
sighash: &invalid_sighash,
lock_time: n_lock_time.into(),
is_final,
};
let rust_stepper = DefaultStepEvaluator { flags, checker };
let stepper = ComparisonStepEvaluator {
eval_step_l: &rust_stepper,
eval_step_r: &rust_stepper,
};
let mut res = StepResults::initial((), ());
let ret = stepwise_verify(script_pub_key, script_sig, flags, &mut res, &stepper);
if res.diverging_result != None {
panic!("mismatched result: {:?}", res);
}
assert_eq!(ret, Err(Error::Ok(ScriptError::EvalFalse)));
}
#[test]
fn it_fails_on_missing_sighash() {
let n_lock_time: u32 = 2410374;
let is_final: bool = true;
let script_pub_key = &SCRIPT_PUBKEY;
let script_sig = &SCRIPT_SIG;
let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY;
let checker = CallbackTransactionSignatureChecker {
sighash: &missing_sighash,
lock_time: n_lock_time.into(),
is_final,
};
let rust_stepper = DefaultStepEvaluator { flags, checker };
let stepper = ComparisonStepEvaluator {
eval_step_l: &rust_stepper,
eval_step_r: &rust_stepper,
};
let mut res = StepResults::initial((), ());
let ret = stepwise_verify(script_pub_key, script_sig, flags, &mut res, &stepper);
if res.diverging_result != None {
panic!("mismatched result: {:?}", res);
}
assert_eq!(ret, Err(Error::Ok(ScriptError::EvalFalse)));
}
proptest! {
// The stepwise comparison tests are significantly slower than the simple comparison tests,
// so run fewer iterations.
#![proptest_config(ProptestConfig {
cases: 2_000, .. ProptestConfig::default()
})]
#[test]
fn test_arbitrary_scripts(
lock_time in prop::num::u32::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 checker = CallbackTransactionSignatureChecker {
sighash: &missing_sighash,
lock_time: lock_time.into(),
is_final,
};
let flags = repair_flags(VerificationFlags::from_bits_truncate(flags));
let rust_stepper = DefaultStepEvaluator { flags, checker };
let stepper = ComparisonStepEvaluator {
eval_step_l: &rust_stepper,
eval_step_r: &rust_stepper,
};
let mut res = StepResults::initial((), ());
let _ = stepwise_verify(&pub_key[..], &sig[..], flags, &mut res, &stepper);
if res.diverging_result != None {
panic!("mismatched result: {:?}", res);
}
}
/// Similar to `test_arbitrary_scripts`, but ensures the `sig` only contains pushes.
#[test]
fn test_restricted_sig_scripts(
lock_time in prop::num::u32::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(
// Dont waste test cases on whether or not `SigPushOnly` is set.
(VerificationFlags::all() - VerificationFlags::SigPushOnly).bits()),
) {
let checker = CallbackTransactionSignatureChecker {
sighash: &missing_sighash,
lock_time: lock_time.into(),
is_final,
};
let flags = repair_flags(VerificationFlags::from_bits_truncate(flags)) | VerificationFlags::SigPushOnly;
let rust_stepper = DefaultStepEvaluator { flags, checker };
let stepper = ComparisonStepEvaluator {
eval_step_l: &rust_stepper,
eval_step_r: &rust_stepper,
};
let mut res = StepResults::initial((), ());
let _ = stepwise_verify(&pub_key[..], &sig[..], flags, &mut res, &stepper);
if res.diverging_result != None {
panic!("mismatched result: {:?}", res);
}
}
}
}