diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21fa75f6b..c74b193cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,3 +149,24 @@ jobs: with: command: clippy args: -- -D warnings + + fuzz: + name: Fuzz + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - nightly + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + - run: cargo install cargo-fuzz + - uses: actions-rs/cargo@v1 + with: + command: fuzz + args: run compare -- -max_len=20000 -max_total_time=100 diff --git a/Cargo.lock b/Cargo.lock index 4040876ab..73c9bfdea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,13 +11,34 @@ dependencies = [ "memchr", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + +[[package]] +name = "autocfg" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.3.0", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + [[package]] name = "bindgen" version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cexpr", "clang-sys", "itertools", @@ -34,12 +55,48 @@ dependencies = [ "which", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[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" @@ -77,31 +134,95 @@ dependencies = [ "libloading", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "cpufeatures" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +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.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "fastrand" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "cc", - "libc", + "typenum", + "version_check", ] [[package]] @@ -122,7 +243,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -157,9 +278,20 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] [[package]] name = "libloading" @@ -173,9 +305,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.7" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" @@ -205,6 +337,24 @@ 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 1.3.0", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -230,6 +380,32 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c477819b845fe023d33583ebf10c9f62518c8d79a0960ba5c36d6ac8a55a5b" +dependencies = [ + "bit-set", + "bitflags 1.3.2", + "byteorder", + "lazy_static", + "num-traits 0.2.19", + "quick-error", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.6.29", + "rusty-fork", + "tempfile", +] + +[[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 +415,121 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.8", + "libc", + "rand_chacha", + "rand_core 0.4.2", + "rand_hc", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.3.1", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "regex" version = "1.9.5" @@ -248,7 +539,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax", + "regex-syntax 0.7.5", ] [[package]] @@ -259,15 +550,30 @@ checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.5", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +[[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 = "1.1.0" @@ -276,15 +582,67 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.13" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rusty-fork" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dd93264e10c577503e926bd1430193eeb5d21b059148910082245309b424fae" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "secp256k1" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" +dependencies = [ + "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]] @@ -304,12 +662,46 @@ 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 0.59.0", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "which" version = "4.4.2" @@ -350,7 +742,25 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -359,13 +769,29 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -374,49 +800,105 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "zcash_script" version = "0.2.0" dependencies = [ "bindgen", - "bitflags", + "bitflags 2.6.0", "cc", + "enum_primitive", "hex", "lazy_static", + "libfuzzer-sys", + "log", + "proptest", + "ripemd", + "secp256k1", + "sha-1", + "sha2", ] diff --git a/Cargo.toml b/Cargo.toml index 295afbd29..86e004bdf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ include = [ "/README.md", "build.rs", "src/*.rs", + "src/*/*.rs", "/depend/check_uint128_t.c", "/depend/zcash/src/amount.cpp", "/depend/zcash/src/amount.h", @@ -58,9 +59,18 @@ path = "src/lib.rs" [features] external-secp = [] +test-dependencies = [] [dependencies] bitflags = "2.5" +enum_primitive = "0.1" +libfuzzer-sys = "0.4" +log = "0.4" +proptest = "0.9" +ripemd = "0.1" +secp256k1 = "0.29" +sha-1 = "0.10" +sha2 = "0.10" [build-dependencies] # The `bindgen` dependency should automatically upgrade to match the version used by zebra-state's `rocksdb` dependency in: diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 000000000..1a45eee77 --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 000000000..c16ac4703 --- /dev/null +++ b/fuzz/Cargo.toml @@ -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 diff --git a/fuzz/fuzz_targets/compare.rs b/fuzz/fuzz_targets/compare.rs new file mode 100644 index 000000000..1fd8875ed --- /dev/null +++ b/fuzz/fuzz_targets/compare.rs @@ -0,0 +1,25 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +extern crate zcash_script; + +use zcash_script::*; + +fn missing_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> { + None +} + +fuzz_target!(|tup: (i64, bool, &[u8], &[u8], u32)| { + // `fuzz_target!` doesn’t support pattern matching in the parameter list. + let (lock_time, is_final, pub_key, sig, flags) = tup; + let ret = check_verify_callback::( + &missing_sighash, + lock_time, + is_final, + pub_key, + sig, + testing::repair_flags(VerificationFlags::from_bits_truncate(flags)), + ); + assert_eq!(ret.0, ret.1.clone().map_err(testing::normalize_error), + "original Rust result: {:?}", ret.1); +}); diff --git a/src/external/mod.rs b/src/external/mod.rs new file mode 100644 index 000000000..8a5830ce9 --- /dev/null +++ b/src/external/mod.rs @@ -0,0 +1,4 @@ +//! Modules that we use from Zcash, but that are outside the script directory. + +pub mod pubkey; +pub mod uint256; diff --git a/src/external/pubkey.rs b/src/external/pubkey.rs new file mode 100644 index 000000000..b13c784df --- /dev/null +++ b/src/external/pubkey.rs @@ -0,0 +1,58 @@ +use secp256k1::{ecdsa, Message, PublicKey, Secp256k1}; + +use super::uint256::*; + +/// 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: &UInt256, 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 + } + } +} diff --git a/src/external/uint256.rs b/src/external/uint256.rs new file mode 100644 index 000000000..fb3c0e01d --- /dev/null +++ b/src/external/uint256.rs @@ -0,0 +1,2 @@ +/// FIXME: This probably needs to be an actually separate type somewhere. +pub type UInt256 = [u8; 32]; diff --git a/src/interpreter.rs b/src/interpreter.rs index a9851f83d..30e3a44da 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,3 +1,15 @@ +use std::mem::swap; +use std::slice::Iter; + +use ripemd::Ripemd160; +use sha1::Sha1; +use sha2::{Digest, Sha256}; + +use super::external::pubkey::PubKey; +use super::external::uint256::UInt256; +use super::script::{Operation::*, PushValue::*, *}; +use super::script_error::*; + bitflags::bitflags! { /// The different SigHash types, as defined in /// @@ -69,3 +81,1222 @@ bitflags::bitflags! { const CHECKLOCKTIMEVERIFY = 1 << 9; } } + +pub trait SignatureChecker { + fn check_sig(&self, _script_sig: &[u8], _vch_pub_key: &[u8], _script_code: &Script) -> bool { + false + } + + fn check_lock_time(&self, _lock_time: &ScriptNum) -> bool { + false + } +} + +pub struct BaseSignatureChecker(); + +impl SignatureChecker for BaseSignatureChecker {} + +pub struct CallbackTransactionSignatureChecker<'a> { + pub sighash: SighashCalculator<'a>, + pub lock_time: &'a ScriptNum, + pub is_final: bool, +} + +type ValType = Vec; + +fn set_success(res: T) -> Result { + Ok(res) +} + +fn set_error(serror: ScriptError) -> Result { + Err(serror) +} + +fn cast_to_bool(vch: &ValType) -> bool { + for i in 0..vch.len() { + if vch[i] != 0 { + // Can be negative zero + if i == vch.len() - 1 && vch[i] == 0x80 { + return false; + } + return true; + } + } + false +} + +/** + * Script is a stack machine (like Forth) that evaluates a predicate + * returning a bool indicating valid or not. There are no loops. + */ +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stack(Vec); + +/// Wraps a Vec (or whatever underlying implementation we choose in a way that matches the C++ impl +/// and provides us some decent chaining) +impl Stack { + fn reverse_index(&self, i: isize) -> Result { + usize::try_from(-i) + .map(|a| self.0.len() - a) + .map_err(|_| ScriptError::InvalidStackOperation) + } + + pub fn top(&self, i: isize) -> Result<&T, ScriptError> { + let idx = self.reverse_index(i)?; + self.0.get(idx).ok_or(ScriptError::InvalidStackOperation) + } + + pub fn swap(&mut self, a: isize, b: isize) -> Result<(), ScriptError> { + let au = self.reverse_index(a)?; + let bu = self.reverse_index(b)?; + self.0.swap(au, bu); + Ok(()) + } + + pub fn pop(&mut self) -> Result { + self.0.pop().ok_or(ScriptError::InvalidStackOperation) + } + + pub fn push_back(&mut self, value: T) { + self.0.push(value) + } + + pub fn empty(&self) -> bool { + self.0.is_empty() + } + + pub fn size(&self) -> usize { + self.0.len() + } + + pub fn iter(&self) -> Iter<'_, T> { + self.0.iter() + } + + pub fn back(&mut self) -> Result<&mut T, ScriptError> { + self.0.last_mut().ok_or(ScriptError::InvalidStackOperation) + } + + pub fn erase(&mut self, start: usize, end: Option) { + for _ in 0..end.map_or(1, |e| e - start) { + self.0.remove(start); + } + } + + pub fn insert(&mut self, i: usize, element: T) { + self.0.insert(i, element) + } + + pub fn end(&self) -> usize { + self.0.len() + } +} + +fn is_compressed_or_uncompressed_pub_key(vch_pub_key: &ValType) -> bool { + if vch_pub_key.len() < PubKey::COMPRESSED_PUBLIC_KEY_SIZE { + // Non-canonical public key: too short + return false; + } + if vch_pub_key[0] == 0x04 { + if vch_pub_key.len() != PubKey::PUBLIC_KEY_SIZE { + // Non-canonical public key: invalid length for uncompressed key + return false; + } + } else if vch_pub_key[0] == 0x02 || vch_pub_key[0] == 0x03 { + if vch_pub_key.len() != PubKey::COMPRESSED_PUBLIC_KEY_SIZE { + // Non-canonical public key: invalid length for compressed key + return false; + } + } else { + // Non-canonical public key: neither compressed nor uncompressed + return false; + } + true +} + +/** + * A canonical signature exists of: <30> <02> <02> + * Where R and S are not negative (their first byte has its highest bit not set), and not + * excessively padded (do not start with a 0 byte, unless an otherwise negative number follows, + * in which case a single 0 byte is necessary and even required). + * + * See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623 + * + * This function is consensus-critical since BIP66. + */ +fn is_valid_signature_encoding(sig: &[u8]) -> bool { + // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash] + // * total-length: 1-byte length descriptor of everything that follows, + // excluding the sighash byte. + // * R-length: 1-byte length descriptor of the R value that follows. + // * R: arbitrary-length big-endian encoded R value. It must use the shortest + // possible encoding for a positive integer (which means no null bytes at + // the start, except a single one when the next byte has its highest bit set). + // * S-length: 1-byte length descriptor of the S value that follows. + // * S: arbitrary-length big-endian encoded S value. The same rules apply. + // * sighash: 1-byte value indicating what data is hashed (not part of the DER + // signature) + + // Minimum and maximum size constraints. + if sig.len() < 9 { + return false; + }; + if sig.len() > 73 { + return false; + }; + + // A signature is of type 0x30 (compound). + if sig[0] != 0x30 { + return false; + }; + + // Make sure the length covers the entire signature. + if usize::from(sig[1]) != sig.len() - 3 { + return false; + }; + + // Extract the length of the R element. + let len_r = usize::from(sig[3]); + + // Make sure the length of the S element is still inside the signature. + if 5 + len_r >= sig.len() { + return false; + }; + + // Extract the length of the S element. + let len_s = usize::from(sig[5 + len_r]); + + // Verify that the length of the signature matches the sum of the length + // of the elements. + if len_r + len_s + 7 != sig.len() { + return false; + }; + + // Check whether the R element is an integer. + if sig[2] != 0x02 { + return false; + }; + + // Zero-length integers are not allowed for R. + if len_r == 0 { + return false; + }; + + // Negative numbers are not allowed for R. + if sig[4] & 0x80 != 0 { + return false; + }; + + // Null bytes at the start of R are not allowed, unless R would + // otherwise be interpreted as a negative number. + if len_r > 1 && sig[4] == 0x00 && sig[5] & 0x80 == 0 { + return false; + }; + + // Check whether the S element is an integer. + if sig[len_r + 4] != 0x02 { + return false; + }; + + // Zero-length integers are not allowed for S. + if len_s == 0 { + return false; + }; + + // Negative numbers are not allowed for S. + if sig[len_r + 6] & 0x80 != 0 { + return false; + }; + + // Null bytes at the start of S are not allowed, unless S would otherwise be + // interpreted as a negative number. + if len_s > 1 && sig[len_r + 6] == 0x00 && sig[len_r + 7] & 0x80 == 0 { + return false; + }; + + true +} + +fn is_low_der_signature(vch_sig: &ValType) -> Result { + if !is_valid_signature_encoding(vch_sig) { + return set_error(ScriptError::SigDER); + }; + // https://bitcoin.stackexchange.com/a/12556: + // Also note that inside transaction signatures, an extra hashtype byte + // follows the actual signature data. + let vch_sig_copy = vch_sig.clone(); + // If the S value is above the order of the curve divided by two, its + // complement modulo the order could have been used instead, which is + // one byte shorter when encoded correctly. + // FIXME: This can return `false` without setting an error, which is not the expectation of the + // caller. + Ok(PubKey::check_low_s(&vch_sig_copy)) +} + +fn is_defined_hashtype_signature(vch_sig: &ValType) -> bool { + if vch_sig.is_empty() { + return false; + }; + let hash_type = i32::from(vch_sig[vch_sig.len() - 1]) & !HashType::AnyoneCanPay.bits(); + if hash_type < HashType::All.bits() || hash_type > HashType::Single.bits() { + return false; + }; + + true +} + +fn check_signature_encoding( + vch_sig: &Vec, + flags: VerificationFlags, +) -> Result { + // Empty signature. Not strictly DER encoded, but allowed to provide a + // compact way to provide an invalid signature for use with CHECK(MULTI)SIG + if vch_sig.is_empty() { + return Ok(true); + }; + if !is_valid_signature_encoding(vch_sig) { + return set_error(ScriptError::SigDER); + } else if flags.contains(VerificationFlags::LowS) && !is_low_der_signature(vch_sig)? { + // serror is set + return Ok(false); + } else if flags.contains(VerificationFlags::StrictEnc) + && !is_defined_hashtype_signature(vch_sig) + { + return set_error(ScriptError::SigHashType); + }; + Ok(true) +} + +fn check_pub_key_encoding(vch_sig: &ValType, flags: VerificationFlags) -> Result<(), ScriptError> { + if flags.contains(VerificationFlags::StrictEnc) + && !is_compressed_or_uncompressed_pub_key(vch_sig) + { + return Err(ScriptError::PubKeyType); + }; + set_success(()) +} + +fn check_minimal_push(data: &ValType, opcode: PushValue) -> bool { + if data.is_empty() { + // Could have used OP_0. + return opcode == OP_0; + } else if data.len() == 1 && data[0] >= 1 && data[0] <= 16 { + // Could have used OP_1 .. OP_16. + return u8::from(opcode) == u8::from(OP_1) + (data[0] - 1); + } else if data.len() == 1 && data[0] == 0x81 { + // Could have used OP_1NEGATE. + return opcode == OP_1NEGATE; + } else if data.len() <= 75 { + // Could have used a direct push (opcode indicating number of bytes pushed + those bytes). + return usize::from(u8::from(opcode)) == data.len(); + } else if data.len() <= 255 { + // Could have used OP_PUSHDATA. + return opcode == OP_PUSHDATA1; + } else if data.len() <= 65535 { + // Could have used OP_PUSHDATA2. + return opcode == OP_PUSHDATA2; + } + true +} + +pub fn eval_script( + stack: &mut Stack>, + script: &Script, + flags: VerificationFlags, + checker: &dyn SignatureChecker, +) -> Result { + let bn_zero = ScriptNum(0); + let bn_one = ScriptNum(1); + let vch_false: ValType = vec![]; + let vch_true: ValType = vec![1]; + + // There's a limit on how large scripts can be. + if script.0.len() > MAX_SCRIPT_SIZE { + return set_error(ScriptError::ScriptSize); + } + + let mut pc = script.0; + let mut vch_push_value = vec![]; + + // We keep track of how many operations have executed so far to prevent + // expensive-to-verify scripts + let mut op_count: u8 = 0; + let require_minimal = flags.contains(VerificationFlags::MinimalData); + + // This keeps track of the conditional flags at each nesting level + // during execution. If we're in a branch of execution where *any* + // of these conditionals are false, we ignore opcodes unless those + // opcodes direct control flow (OP_IF, OP_ELSE, etc.). + let mut vexec: Stack = Stack(vec![]); + + let mut altstack: Stack> = Stack(vec![]); + + // Main execution loop + while !pc.is_empty() { + // Are we in an executing branch of the script? + let exec = vexec.iter().all(|value| *value); + + // + // Read instruction + // + let opcode = Script::get_op2(&mut pc, &mut vch_push_value)?; + if vch_push_value.len() > MAX_SCRIPT_ELEMENT_SIZE { + return set_error(ScriptError::PushSize); + } + + match opcode { + Opcode::PushValue(pv) => { + if exec { + match pv { + // + // Push value + // + OP_1NEGATE | OP_1 | OP_2 | OP_3 | OP_4 | OP_5 | OP_6 | OP_7 | OP_8 + | OP_9 | OP_10 | OP_11 | OP_12 | OP_13 | OP_14 | OP_15 | OP_16 => { + // ( -- value) + let bn = + ScriptNum(i64::from(u8::from(pv)) - i64::from(u8::from(OP_1) - 1)); + stack.push_back(bn.getvch()); + // The result of these opcodes should always be the minimal way to push the data + // they push, so no need for a CheckMinimalPush here. + } + _ => { + if pv <= OP_PUSHDATA4 { + if require_minimal && !check_minimal_push(&vch_push_value, pv) { + return set_error(ScriptError::MinimalData); + } + stack.push_back(vch_push_value.clone()); + } else { + return set_error(ScriptError::BadOpcode); + } + } + } + } + } + Opcode::Operation(op) => { + // Note how OP_RESERVED does not count towards the opcode limit. + op_count += 1; + if op_count > 201 { + return set_error(ScriptError::OpCount); + } + + if op == OP_CAT + || op == OP_SUBSTR + || op == OP_LEFT + || op == OP_RIGHT + || op == OP_INVERT + || op == OP_AND + || op == OP_OR + || op == OP_XOR + || op == OP_2MUL + || op == OP_2DIV + || op == OP_MUL + || op == OP_DIV + || op == OP_MOD + || op == OP_LSHIFT + || op == OP_RSHIFT + || op == OP_CODESEPARATOR + { + return set_error(ScriptError::DisabledOpcode); // Disabled opcodes. + } + + if exec || (OP_IF <= op && op <= OP_ENDIF) { + match op { + // + // Control + // + OP_NOP => (), + + OP_CHECKLOCKTIMEVERIFY => { + // This was originally OP_NOP2 but has been repurposed + // for OP_CHECKLOCKTIMEVERIFY. So, we should act based + // on whether or not CLTV has been activated in a soft + // fork. + if !flags.contains(VerificationFlags::CHECKLOCKTIMEVERIFY) { + if flags.contains(VerificationFlags::DiscourageUpgradableNOPs) { + return set_error(ScriptError::DiscourageUpgradableNOPs); + } + } else { + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + + // Note that elsewhere numeric opcodes are limited to + // operands in the range -2**31+1 to 2**31-1, however it is + // legal for opcodes to produce results exceeding that + // range. This limitation is implemented by `ScriptNum`'s + // default 4-byte limit. + // + // If we kept to that limit we'd have a year 2038 problem, + // even though the `lock_time` field in transactions + // themselves is u32 which only becomes meaningless + // after the year 2106. + // + // Thus as a special case we tell `ScriptNum` to accept up + // to 5-byte bignums, which are good until 2**39-1, well + // beyond the 2**32-1 limit of the `lock_time` field itself. + let lock_time = + ScriptNum::new(stack.top(-1)?, require_minimal, Some(5)) + .map_err(ScriptError::ScriptNumError)?; + + // In the rare event that the argument may be < 0 due to + // some arithmetic being done first, you can always use + // 0 MAX CHECKLOCKTIMEVERIFY. + if lock_time < ScriptNum(0) { + return set_error(ScriptError::NegativeLockTime); + } + + // Actually compare the specified lock time with the transaction. + if !checker.check_lock_time(&lock_time) { + return set_error(ScriptError::UnsatisfiedLockTime); + } + } + } + + OP_NOP1 | OP_NOP3 | OP_NOP4 | OP_NOP5 + | OP_NOP6 | OP_NOP7 | OP_NOP8 | OP_NOP9 | OP_NOP10 => { + // Do nothing, though if the caller wants to prevent people from using + // these NOPs (as part of a standard tx rule, for example) they can + // enable `DiscourageUpgradableNOPs` to turn these opcodes into errors. + if flags.contains(VerificationFlags::DiscourageUpgradableNOPs) { + return set_error(ScriptError::DiscourageUpgradableNOPs); + } + } + + OP_IF + | OP_NOTIF => { + // if [statements] [else [statements]] endif + let mut value = false; + if exec { + if stack.size() < 1 { + return set_error(ScriptError::UnbalancedConditional); + } + let vch: &ValType = stack.top(-1)?; + value = cast_to_bool(vch); + if op == OP_NOTIF { + value = !value + }; + stack.pop()?; + } + vexec.push_back(value); + } + + OP_ELSE => { + if vexec.empty() { + return set_error(ScriptError::UnbalancedConditional); + } + vexec.back().map(|last| *last = !*last)?; + } + + OP_ENDIF => { + if vexec.empty() { + return set_error(ScriptError::UnbalancedConditional); + } + vexec.pop()?; + } + + OP_VERIFY => { + // (true -- ) or + // (false -- false) and return + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + let value = cast_to_bool(stack.top(-1)?); + if value { + stack.pop()?; + } else { + return set_error(ScriptError::Verify); + } + } + + OP_RETURN => return set_error(ScriptError::OpReturn), + + // + // Stack ops + // + OP_TOALTSTACK => { + if stack.empty() { + return set_error(ScriptError::InvalidStackOperation); + } + altstack.push_back(stack.top(-1)?.clone()); + stack.pop()?; + } + + OP_FROMALTSTACK => { + if altstack.empty() { + return set_error(ScriptError::InvalidAltstackOperation); + } + stack.push_back(altstack.top(-1)?.clone()); + altstack.pop()?; + } + + OP_2DROP => { + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + + stack.pop()?; + stack.pop()?; + } + + OP_2DUP => { + // (x1 x2 -- x1 x2 x1 x2) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch1 = stack.top(-2)?.clone(); + let vch2 = stack.top(-1)?.clone(); + stack.push_back(vch1); + stack.push_back(vch2); + } + + OP_3DUP => { + // (x1 x2 x3 -- x1 x2 x3 x1 x2 x3) + if stack.size() < 3 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch1 = stack.top(-3)?.clone(); + let vch2 = stack.top(-2)?.clone(); + let vch3 = stack.top(-1)?.clone(); + stack.push_back(vch1); + stack.push_back(vch2); + stack.push_back(vch3); + } + + OP_2OVER => { + // (x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2) + if stack.size() < 4 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch1 = stack.top(-4)?.clone(); + let vch2 = stack.top(-3)?.clone(); + stack.push_back(vch1); + stack.push_back(vch2); + } + + OP_2ROT => { + // (x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2) + if stack.size() < 6 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch1 = stack.top(-6)?.clone(); + let vch2 = stack.top(-5)?.clone(); + stack.erase(stack.end() - 6, Some(stack.end() - 4)); + stack.push_back(vch1); + stack.push_back(vch2); + } + + OP_2SWAP => { + // (x1 x2 x3 x4 -- x3 x4 x1 x2) + if stack.size() < 4 { + return set_error(ScriptError::InvalidStackOperation); + } + stack.swap(-4, -2)?; + stack.swap(-3, -1)?; + } + + OP_IFDUP => { + // (x - 0 | x x) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch = stack.top(-1)?; + if cast_to_bool(vch) { + stack.push_back(vch.to_vec()) + } + } + + OP_DEPTH => { + // -- stacksize + let bn = ScriptNum( + i64::try_from(stack.size()).map_err(|_| ScriptError::StackSize)?); + stack.push_back(bn.getvch()) + } + + OP_DROP => { + // (x -- ) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + stack.pop()?; + } + + OP_DUP => { + // (x -- x x) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + + let a = stack.pop()?; + stack.push_back(a.clone()); + stack.push_back(a); + } + + OP_NIP => { + // (x1 x2 -- x2) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + stack.erase(stack.end() - 2, None); + } + + OP_OVER => { + // (x1 x2 -- x1 x2 x1) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch = stack.top(-2)?; + stack.push_back(vch.clone()); + } + + OP_PICK + | OP_ROLL => { + // (xn ... x2 x1 x0 n - xn ... x2 x1 x0 xn) + // (xn ... x2 x1 x0 n - ... x2 x1 x0 xn) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + let n = + u16::try_from(ScriptNum::new(stack.top(-1)?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?.getint()) + .map_err(|_| ScriptError::InvalidStackOperation)?; + stack.pop()?; + if usize::from(n) >= stack.size() { + return set_error(ScriptError::InvalidStackOperation); + } + let vch: ValType = + stack.top(-isize::try_from(n).map_err(|_| ScriptError::InvalidStackOperation)? - 1)? + .clone(); + if op == OP_ROLL { + stack.erase(stack.end() - usize::from(n) - 1, None); + } + stack.push_back(vch) + } + + OP_ROT => { + // (x1 x2 x3 -- x2 x3 x1) + // x2 x1 x3 after first swap + // x2 x3 x1 after second swap + if stack.size() < 3 { + return set_error(ScriptError::InvalidStackOperation); + } + stack.swap(-3, -2)?; + stack.swap(-2, -1)?; + } + + OP_SWAP => { + // (x1 x2 -- x2 x1) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + stack.swap(-2, -1)?; + } + + OP_TUCK => { + // (x1 x2 -- x2 x1 x2) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch = stack.top(-1)?.clone(); + stack.insert(stack.end() - 2, vch) + } + + + OP_SIZE => { + // (in -- in size) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + let bn = + ScriptNum(stack.top(-1)?.len().try_into().map_err(|_| ScriptError::PushSize)?); + stack.push_back(bn.getvch()) + } + + + // + // Bitwise logic + // + OP_EQUAL + | OP_EQUALVERIFY + // | OP_NOTEQUAL // use OP_NUMNOTEQUAL + => { + // (x1 x2 - bool) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch1 = stack.top(-2)?.clone(); + let vch2 = stack.top(-1)?.clone(); + let equal = vch1 == vch2; + // OP_NOTEQUAL is disabled because it would be too easy to say + // something like n != 1 and have some wiseguy pass in 1 with extra + // zero bytes after it (numerically, 0x01 == 0x0001 == 0x000001) + //if op == OP_NOTEQUAL { + // fEqual = !fEqual; + //} + stack.pop()?; + stack.pop()?; + stack.push_back(if equal { vch_true.clone() } else { vch_false.clone() }); + if op == OP_EQUALVERIFY + { + if equal { + stack.pop()?; + } else { + return set_error(ScriptError::EqualVerify); + } + } + } + + + // + // Numeric + // + OP_1ADD + | OP_1SUB + | OP_NEGATE + | OP_ABS + | OP_NOT + | OP_0NOTEQUAL => { + // (in -- out) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + let mut bn = ScriptNum::new(stack.top(-1)?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?; + match op { + OP_1ADD => bn = bn + bn_one, + OP_1SUB => bn = bn - bn_one, + OP_NEGATE => bn = -bn, + OP_ABS => { + if bn < bn_zero { + bn = -bn + } + } + OP_NOT => bn = ScriptNum((bn == bn_zero).into()), + OP_0NOTEQUAL => bn = ScriptNum((bn != bn_zero).into()), + _ => panic!("invalid opcode"), + } + stack.pop()?; + stack.push_back(bn.getvch()) + } + + OP_ADD + | OP_SUB + | OP_BOOLAND + | OP_BOOLOR + | OP_NUMEQUAL + | OP_NUMEQUALVERIFY + | OP_NUMNOTEQUAL + | OP_LESSTHAN + | OP_GREATERTHAN + | OP_LESSTHANOREQUAL + | OP_GREATERTHANOREQUAL + | OP_MIN + | OP_MAX => { + // (x1 x2 -- out) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + let bn1 = ScriptNum::new(stack.top(-2)?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?; + let bn2 = ScriptNum::new(stack.top(-1)?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?; + let bn = match op { + OP_ADD => + bn1 + bn2, + + OP_SUB => + bn1 - bn2, + + OP_BOOLAND => ScriptNum((bn1 != bn_zero && bn2 != bn_zero).into()), + OP_BOOLOR => ScriptNum((bn1 != bn_zero || bn2 != bn_zero).into()), + OP_NUMEQUAL => ScriptNum((bn1 == bn2).into()), + OP_NUMEQUALVERIFY => ScriptNum((bn1 == bn2).into()), + OP_NUMNOTEQUAL => ScriptNum((bn1 != bn2).into()), + OP_LESSTHAN => ScriptNum((bn1 < bn2).into()), + OP_GREATERTHAN => ScriptNum((bn1 > bn2).into()), + OP_LESSTHANOREQUAL => ScriptNum((bn1 <= bn2).into()), + OP_GREATERTHANOREQUAL => ScriptNum((bn1 >= bn2).into()), + OP_MIN => if bn1 < bn2 { bn1 } else { bn2 }, + OP_MAX => if bn1 > bn2 { bn1 } else { bn2 }, + _ => panic!("invalid opcode"), + }; + stack.pop()?; + stack.pop()?; + stack.push_back(bn.getvch()); + + if op == OP_NUMEQUALVERIFY { + if cast_to_bool(stack.top(-1)?) { + stack.pop()?; + } else { + return set_error(ScriptError::NumEqualVerify); + } + } + } + + OP_WITHIN => { + // (x min max -- out) + if stack.size() < 3 { + return set_error(ScriptError::InvalidStackOperation); + } + let bn1 = ScriptNum::new(stack.top(-3)?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?; + let bn2 = ScriptNum::new(stack.top(-2)?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?; + let bn3 = ScriptNum::new(stack.top(-1)?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?; + let value = bn2 <= bn1 && bn1 < bn3; + stack.pop()?; + stack.pop()?; + stack.pop()?; + stack.push_back(if value { + vch_true.clone() + } else { + vch_false.clone() + }) + } + + // + // Crypto + // + OP_RIPEMD160 + | OP_SHA1 + | OP_SHA256 + | OP_HASH160 + | OP_HASH256 => { + // (in -- hash) + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + let vch = stack.top(-1)?; + let mut vch_hash = vec![]; + if op == OP_RIPEMD160 { + vch_hash = Ripemd160::digest(vch).to_vec(); + } else if op == OP_SHA1 { + let mut hasher = Sha1::new(); + hasher.update(vch); + vch_hash = hasher.finalize().to_vec(); + } else if op == OP_SHA256 { + vch_hash = Sha256::digest(vch).to_vec(); + } else if op == OP_HASH160 { + vch_hash = Ripemd160::digest(Sha256::digest(vch)).to_vec(); + } else if op == OP_HASH256 { + vch_hash = Sha256::digest(Sha256::digest(vch)).to_vec(); + } + stack.pop()?; + stack.push_back(vch_hash) + } + + OP_CHECKSIG + | OP_CHECKSIGVERIFY => { + // (sig pubkey -- bool) + if stack.size() < 2 { + return set_error(ScriptError::InvalidStackOperation); + } + + let vch_sig = stack.top(-2)?.clone(); + let vch_pub_key = stack.top(-1)?.clone(); + + if !check_signature_encoding(&vch_sig, flags)? { + //serror is set + return Ok(false); + } + check_pub_key_encoding(&vch_pub_key, flags)?; + let success = checker.check_sig(&vch_sig, &vch_pub_key, script); + + stack.pop()?; + stack.pop()?; + stack.push_back(if success { + vch_true.clone() + } else { + vch_false.clone() + }); + if op == OP_CHECKSIGVERIFY { + if success { + stack.pop()?; + } else { + return set_error(ScriptError::CheckSigVerify); + } + } + } + + OP_CHECKMULTISIG + | OP_CHECKMULTISIGVERIFY => { + // ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) + + // NB: This is guaranteed u8-safe, because we are limited to 20 keys and + // 20 signatures, plus a couple other fields. u8 also gives us total + // conversions to the other types we deal with here (`isize` and `i64`). + let mut i: u8 = 1; + if stack.size() < i.into() { + return set_error(ScriptError::InvalidStackOperation); + }; + + let mut keys_count = + u8::try_from(ScriptNum::new(stack.top(-isize::from(i))?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?.getint()) + .map_err(|_| ScriptError::PubKeyCount)?; + if keys_count > 20 { + return set_error(ScriptError::PubKeyCount); + }; + op_count += keys_count; + if op_count > 201 { + return set_error(ScriptError::OpCount); + }; + i += 1; + let mut ikey = i; + i += keys_count; + if stack.size() < i.into() { + return set_error(ScriptError::InvalidStackOperation); + } + + let mut sigs_count = + u8::try_from(ScriptNum::new(stack.top(-isize::from(i))?, require_minimal, None) + .map_err(ScriptError::ScriptNumError)?.getint()) + .map_err(|_| ScriptError::SigCount)?; + if sigs_count > keys_count { + return set_error(ScriptError::SigCount); + }; + i += 1; + let mut isig = i; + i += sigs_count; + if stack.size() < i.into() { + return set_error(ScriptError::InvalidStackOperation); + }; + + let mut success = true; + while success && sigs_count > 0 { + let vch_sig: &ValType = stack.top(-isize::from(isig))?; + let vch_pub_key: &ValType = stack.top(-isize::from(ikey))?; + + // Note how this makes the exact order of pubkey/signature evaluation + // distinguishable by CHECKMULTISIG NOT if the STRICTENC flag is set. + // See the script_(in)valid tests for details. + if !check_signature_encoding(vch_sig, flags)? { + // serror is set + return Ok(false); + }; + check_pub_key_encoding(vch_pub_key, flags)?; + + // Check signature + let ok: bool = checker.check_sig(vch_sig, vch_pub_key, script); + + if ok { + isig += 1; + sigs_count -= 1; + } + ikey += 1; + keys_count -= 1; + + // If there are more signatures left than keys left, + // then too many signatures have failed. Exit early, + // without checking any further signatures. + if sigs_count > keys_count { + success = false; + }; + } + + // Clean up stack of actual arguments + while { + let res = i > 1; + i -= 1; + res + } { + stack.pop()?; + } + + // A bug causes CHECKMULTISIG to consume one extra argument + // whose contents were not checked in any way. + // + // Unfortunately this is a potential source of mutability, + // so optionally verify it is exactly equal to zero prior + // to removing it from the stack. + if stack.size() < 1 { + return set_error(ScriptError::InvalidStackOperation); + } + if flags.contains(VerificationFlags::NullDummy) + && !stack.top(-1)?.is_empty() + { + return set_error(ScriptError::SigNullDummy); + } + stack.pop()?; + + stack.push_back(if success { + vch_true.clone() + } else { + vch_false.clone() + }); + + if op == OP_CHECKMULTISIGVERIFY { + if success { + stack.pop()?; + } else { + return set_error(ScriptError::CheckMultisigVerify); + } + } + } + + _ => { + return set_error(ScriptError::BadOpcode); + } + } + } + } + } + + // Size limits + if stack.size() + altstack.size() > 1000 { + return set_error(ScriptError::StackSize); + } + } + + if !vexec.empty() { + return set_error(ScriptError::UnbalancedConditional); + } + + set_success(true) +} + +/// All signature hashes are 32 bits, since they are necessarily produced by SHA256. +pub const SIGHASH_SIZE: usize = 32; + +/// A function which is called to obtain the sighash. +/// - script_code: the scriptCode being validated. Note that this not always +/// matches script_sig, i.e. for P2SH. +/// - hash_type: the hash type being used. +/// +/// The `extern "C"` function that calls this doesn’t give much opportunity for rich failure +/// reporting, but returning `None` indicates _some_ failure to produce the desired hash. +/// +/// TODO: Can we get the “32” from somewhere rather than hardcoding it? +pub type SighashCalculator<'a> = &'a dyn Fn(&[u8], HashType) -> Option<[u8; SIGHASH_SIZE]>; + +impl CallbackTransactionSignatureChecker<'_> { + pub fn verify_signature(vch_sig: &[u8], pubkey: &PubKey, sighash: &UInt256) -> bool { + pubkey.verify(sighash, vch_sig) + } +} + +impl SignatureChecker for CallbackTransactionSignatureChecker<'_> { + fn check_sig(&self, vch_sig_in: &[u8], vch_pub_key: &[u8], script_code: &Script) -> bool { + let pubkey = PubKey(vch_pub_key); + if !pubkey.is_valid() { + return false; + }; + + // Hash type is one byte tacked on to the end of the signature + let mut vch_sig = vch_sig_in.to_vec(); + vch_sig + .pop() + .and_then(|hash_type| { + (self.sighash)(script_code.0, HashType::from_bits_retain(hash_type.into())) + }) + .map(|sighash| Self::verify_signature(&vch_sig, &pubkey, &sighash)) + .unwrap_or(false) + } + + fn check_lock_time(&self, lock_time: &ScriptNum) -> bool { + // There are two times of nLockTime: lock-by-blockheight + // and lock-by-blocktime, distinguished by whether + // nLockTime < LOCKTIME_THRESHOLD. + // + // We want to compare apples to apples, so fail the script + // unless the type of nLockTime being tested is the same as + // the nLockTime in the transaction. + if *self.lock_time < LOCKTIME_THRESHOLD && *lock_time >= LOCKTIME_THRESHOLD + || *self.lock_time >= LOCKTIME_THRESHOLD && *lock_time < LOCKTIME_THRESHOLD + // Now that we know we're comparing apples-to-apples, the + // comparison is a simple numeric one. + || lock_time > self.lock_time + { + false + // Finally the nLockTime feature can be disabled and thus + // CHECKLOCKTIMEVERIFY bypassed if every txin has been + // finalized by setting nSequence to maxint. The + // transaction would be allowed into the blockchain, making + // the opcode ineffective. + // + // Testing if this vin is not final is sufficient to + // prevent this condition. Alternatively we could test all + // inputs, but testing just this input minimizes the data + // required to prove correct CHECKLOCKTIMEVERIFY execution. + } else { + !self.is_final + } + } +} + +pub fn verify_script( + script_sig: &Script, + script_pub_key: &Script, + flags: VerificationFlags, + checker: &dyn SignatureChecker, +) -> Result<(), ScriptError> { + if flags.contains(VerificationFlags::SigPushOnly) && !script_sig.is_push_only() { + return set_error(ScriptError::SigPushOnly); + } + + let mut stack = Stack(Vec::new()); + let mut stack_copy = Stack(Vec::new()); + if !eval_script(&mut stack, script_sig, flags, checker)? { + // serror is set + return set_error(ScriptError::UnknownError); + } + if flags.contains(VerificationFlags::P2SH) { + stack_copy = stack.clone() + } + if !eval_script(&mut stack, script_pub_key, flags, checker)? { + // serror is set + return set_error(ScriptError::UnknownError); + } + if stack.empty() { + return set_error(ScriptError::EvalFalse); + } + if !cast_to_bool(stack.back()?) { + return set_error(ScriptError::EvalFalse); + } + + // Additional validation for spend-to-script-hash transactions: + if flags.contains(VerificationFlags::P2SH) && script_pub_key.is_pay_to_script_hash() { + // script_sig must be literals-only or validation fails + if !script_sig.is_push_only() { + return set_error(ScriptError::SigPushOnly); + }; + + // Restore stack. + swap(&mut stack, &mut stack_copy); + + // stack cannot be empty here, because if it was the + // P2SH HASH <> EQUAL scriptPubKey would be evaluated with + // an empty stack and the `eval_script` above would return false. + assert!(!stack.empty()); + + let pub_key_serialized = stack.back()?.clone(); + let pub_key_2 = Script(pub_key_serialized.as_slice()); + stack.pop()?; + + if !eval_script(&mut stack, &pub_key_2, flags, checker)? { + // serror is set + return set_error(ScriptError::UnknownError); + } + if stack.empty() { + return set_error(ScriptError::EvalFalse); + } + if !cast_to_bool(stack.back()?) { + return set_error(ScriptError::EvalFalse); + } + } + + // The CLEANSTACK check is only performed after potential P2SH evaluation, + // as the non-P2SH evaluation of a P2SH script will obviously not result in + // a clean stack (the P2SH inputs remain). + if flags.contains(VerificationFlags::CleanStack) { + // Disallow CLEANSTACK without P2SH, as otherwise a switch CLEANSTACK->P2SH+CLEANSTACK + // would be possible, which is not a softfork (and P2SH should be one). + assert!(flags.contains(VerificationFlags::P2SH)); + if stack.size() != 1 { + return set_error(ScriptError::CleanStack); + } + }; + + set_success(()) +} diff --git a/src/lib.rs b/src/lib.rs index e05cfb127..c2d24b5cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,25 +3,32 @@ #![doc(html_logo_url = "https://www.zfnd.org/images/zebra-icon.png")] #![doc(html_root_url = "https://docs.rs/zcash_script/0.3.0")] #![allow(unsafe_code)] +#[macro_use] +extern crate enum_primitive; mod cxx; -pub use cxx::*; - +mod external; mod interpreter; -pub use interpreter::{HashType, VerificationFlags}; +mod script; +pub mod script_error; mod zcash_script; -pub use zcash_script::*; use std::os::raw::{c_int, c_uint, c_void}; +use log::warn; + +pub use cxx::*; +pub use interpreter::{HashType, SighashCalculator, VerificationFlags}; +pub use zcash_script::*; + /// A tag to indicate that the C++ implementation of zcash_script should be used. pub enum Cxx {} impl From for Error { #[allow(non_upper_case_globals)] - fn from(err_code: zcash_script_error_t) -> Error { + fn from(err_code: zcash_script_error_t) -> Self { match err_code { - zcash_script_error_t_zcash_script_ERR_OK => Error::Ok, + zcash_script_error_t_zcash_script_ERR_OK => Error::Ok(None), zcash_script_error_t_zcash_script_ERR_VERIFY_SCRIPT => Error::VerifyScript, unknown => Error::Unknown(unknown.into()), } @@ -109,10 +116,126 @@ impl ZcashScript for Cxx { } } +/// Runs both the C++ and Rust implementations `ZcashScript::legacy_sigop_count_script` and returns +/// both results. This is more useful for testing than the impl that logs a warning if the results +/// differ and always returns the C++ result. +fn check_legacy_sigop_count_script( + script: &[u8], +) -> (Result, Result) { + ( + T::legacy_sigop_count_script(script), + U::legacy_sigop_count_script(script), + ) +} + +/// Runs both the C++ and Rust implementations of `ZcashScript::verify_callback` 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. +pub fn check_verify_callback( + sighash: SighashCalculator, + lock_time: i64, + is_final: bool, + script_pub_key: &[u8], + script_sig: &[u8], + flags: VerificationFlags, +) -> (Result<(), Error>, Result<(), Error>) { + ( + T::verify_callback( + sighash, + lock_time, + is_final, + script_pub_key, + script_sig, + flags, + ), + U::verify_callback( + sighash, + lock_time, + is_final, + script_pub_key, + script_sig, + flags, + ), + ) +} + +/// This implementation is functionally equivalent to `Cxx`, but it also runs `Rust` and logs a +/// warning if they disagree. +impl ZcashScript for (T, U) { + fn legacy_sigop_count_script(script: &[u8]) -> Result { + let (cxx, rust) = check_legacy_sigop_count_script::(script); + if rust != cxx { + warn!( + "The Rust Zcash Script interpreter had a different sigop count ({:?}) from the C++ one ({:?}).", + rust, + cxx) + }; + cxx + } + + fn verify_callback( + sighash: SighashCalculator, + lock_time: i64, + is_final: bool, + script_pub_key: &[u8], + script_sig: &[u8], + flags: VerificationFlags, + ) -> Result<(), Error> { + let (cxx, rust) = check_verify_callback::( + sighash, + lock_time, + is_final, + script_pub_key, + script_sig, + flags, + ); + if rust != cxx { + // probably want to distinguish between + // - C++ succeeding when Rust fails (bad), + // - Rust succeeding when C++ fals (worse), and + // - differing error codes (maybe not bad). + warn!( + "The Rust Zcash Script interpreter had a different result ({:?}) from the C++ one ({:?}).", + rust, + cxx) + }; + cxx + } +} + +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use super::*; + + /// Convert errors that don’t exist in the C++ code into the cases that do. + pub fn normalize_error(err: Error) -> Error { + match err { + Error::Ok(Some(_)) => Error::Ok(None), + _ => err, + } + } + + /// Ensures that flags represent a supported state. This avoids crashes in the C++ code, which + /// break various tests. + pub fn repair_flags(flags: VerificationFlags) -> VerificationFlags { + // TODO: The C++ implementation fails an assert (interpreter.cpp:1097) if `CleanStack` is + // set without `P2SH`. + if flags.contains(VerificationFlags::CleanStack) { + flags & VerificationFlags::P2SH + } else { + flags + } + } + + /// A `usize` one larger than the longest allowed script, for testing bounds. + pub const OVERFLOW_SCRIPT_SIZE: usize = script::MAX_SCRIPT_SIZE + 1; +} + #[cfg(test)] mod tests { - pub use super::*; + use super::{testing::*, *}; use hex::FromHex; + use proptest::prelude::*; lazy_static::lazy_static! { pub static ref SCRIPT_PUBKEY: Vec = >::from_hex("a914c117756dcbe144a12a7c33a77cfa81aa5aeeb38187").unwrap(); @@ -124,7 +247,7 @@ mod tests { .unwrap() .as_slice() .first_chunk::<32>() - .map(|hash| *hash) + .copied() } fn invalid_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> { @@ -132,7 +255,7 @@ mod tests { .unwrap() .as_slice() .first_chunk::<32>() - .map(|hash| *hash) + .copied() } fn missing_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> { @@ -147,7 +270,7 @@ mod tests { let script_sig = &SCRIPT_SIG; let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY; - let ret = Cxx::verify_callback( + let ret = check_verify_callback::( &sighash, n_lock_time, is_final, @@ -156,7 +279,8 @@ mod tests { flags, ); - assert!(ret.is_ok()); + assert_eq!(ret.0, ret.1.map_err(normalize_error)); + assert!(ret.0.is_ok()); } #[test] @@ -167,7 +291,7 @@ mod tests { let script_sig = &SCRIPT_SIG; let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY; - let ret = Cxx::verify_callback( + let ret = check_verify_callback::( &invalid_sighash, n_lock_time, is_final, @@ -176,7 +300,12 @@ mod tests { flags, ); - assert_eq!(ret, Err(Error::Ok)); + assert_eq!(ret.0, ret.1.map_err(normalize_error)); + // Checks the Rust result, because we have more information on the Rust side. + assert_eq!( + ret.1, + Err(Error::Ok(Some(script_error::ScriptError::EvalFalse))) + ); } #[test] @@ -187,7 +316,7 @@ mod tests { let script_sig = &SCRIPT_SIG; let flags = VerificationFlags::P2SH | VerificationFlags::CHECKLOCKTIMEVERIFY; - let ret = Cxx::verify_callback( + let ret = check_verify_callback::( &missing_sighash, n_lock_time, is_final, @@ -196,6 +325,63 @@ mod tests { flags, ); - assert_eq!(ret, Err(Error::Ok)); + assert_eq!(ret.0, ret.1.map_err(normalize_error)); + // Checks the Rust result, because we have more information on the Rust side. + assert_eq!( + ret.1, + Err(Error::Ok(Some(script_error::ScriptError::EvalFalse))) + ); + } + + proptest! { + #![proptest_config(ProptestConfig { + cases: 20_000, .. ProptestConfig::default() + })] + + /// This test is very shallow, because we have only `()` for success and most errors have + /// been collapsed to `Error::Ok`. A deeper comparison, requires changes to the C++ code. + #[test] + fn test_arbitrary_scripts( + lock_time in prop::num::i64::ANY, + is_final in prop::bool::ANY, + pub_key in prop::collection::vec(0..=0xffu8, 0..=OVERFLOW_SCRIPT_SIZE), + sig in prop::collection::vec(0..=0xffu8, 1..=OVERFLOW_SCRIPT_SIZE), + flags in prop::bits::u32::masked(VerificationFlags::all().bits()), + ) { + let ret = check_verify_callback::( + &missing_sighash, + lock_time, + is_final, + &pub_key[..], + &sig[..], + repair_flags(VerificationFlags::from_bits_truncate(flags)), + ); + prop_assert_eq!(ret.0, ret.1.map_err(normalize_error), + "original Rust result: {:?}", ret.1); + } + + /// Similar to `test_arbitrary_scripts`, but ensures the `sig` only contains pushes. + #[test] + fn test_restricted_sig_scripts( + lock_time in prop::num::i64::ANY, + is_final in prop::bool::ANY, + pub_key in prop::collection::vec(0..=0xffu8, 0..=OVERFLOW_SCRIPT_SIZE), + sig in prop::collection::vec(0..=0x60u8, 0..=OVERFLOW_SCRIPT_SIZE), + flags in prop::bits::u32::masked( + // Don’t waste test cases on whether or not `SigPushOnly` is set. + (VerificationFlags::all() - VerificationFlags::SigPushOnly).bits()), + ) { + let ret = check_verify_callback::( + &missing_sighash, + lock_time, + is_final, + &pub_key[..], + &sig[..], + repair_flags(VerificationFlags::from_bits_truncate(flags)) + | VerificationFlags::SigPushOnly, + ); + prop_assert_eq!(ret.0, ret.1.map_err(normalize_error), + "original Rust result: {:?}", ret.1); + } } } diff --git a/src/script.rs b/src/script.rs new file mode 100644 index 000000000..cb3149e3c --- /dev/null +++ b/src/script.rs @@ -0,0 +1,589 @@ +#![allow(non_camel_case_types)] + +use std::ops::{Add, Neg, Sub}; + +use enum_primitive::FromPrimitive; + +use super::script_error::*; + +pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520; // bytes + +/// Maximum script length in bytes +pub const MAX_SCRIPT_SIZE: usize = 10000; + +// Threshold for lock_time: below this value it is interpreted as block number, +// otherwise as UNIX timestamp. +pub const LOCKTIME_THRESHOLD: ScriptNum = ScriptNum(500000000); // Tue Nov 5 00:53:20 1985 UTC + +/** Script opcodes */ +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub enum Opcode { + PushValue(PushValue), + Operation(Operation), +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +#[repr(u8)] +pub enum PushValue { + // push value + OP_0 = 0x00, + PushdataBytelength(u8), + OP_PUSHDATA1 = 0x4c, + OP_PUSHDATA2 = 0x4d, + OP_PUSHDATA4 = 0x4e, + OP_1NEGATE = 0x4f, + OP_RESERVED = 0x50, + OP_1 = 0x51, + OP_2 = 0x52, + OP_3 = 0x53, + OP_4 = 0x54, + OP_5 = 0x55, + OP_6 = 0x56, + OP_7 = 0x57, + OP_8 = 0x58, + OP_9 = 0x59, + OP_10 = 0x5a, + OP_11 = 0x5b, + OP_12 = 0x5c, + OP_13 = 0x5d, + OP_14 = 0x5e, + OP_15 = 0x5f, + OP_16 = 0x60, +} + +use PushValue::*; + +enum_from_primitive! { +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +#[repr(u8)] +pub enum Operation { + // control + OP_NOP = 0x61, + OP_VER = 0x62, + OP_IF = 0x63, + OP_NOTIF = 0x64, + OP_VERIF = 0x65, + OP_VERNOTIF = 0x66, + OP_ELSE = 0x67, + OP_ENDIF = 0x68, + OP_VERIFY = 0x69, + OP_RETURN = 0x6a, + + // stack ops + OP_TOALTSTACK = 0x6b, + OP_FROMALTSTACK = 0x6c, + OP_2DROP = 0x6d, + OP_2DUP = 0x6e, + OP_3DUP = 0x6f, + OP_2OVER = 0x70, + OP_2ROT = 0x71, + OP_2SWAP = 0x72, + OP_IFDUP = 0x73, + OP_DEPTH = 0x74, + OP_DROP = 0x75, + OP_DUP = 0x76, + OP_NIP = 0x77, + OP_OVER = 0x78, + OP_PICK = 0x79, + OP_ROLL = 0x7a, + OP_ROT = 0x7b, + OP_SWAP = 0x7c, + OP_TUCK = 0x7d, + + // splice ops + OP_CAT = 0x7e, + OP_SUBSTR = 0x7f, + OP_LEFT = 0x80, + OP_RIGHT = 0x81, + OP_SIZE = 0x82, + + // bit logic + OP_INVERT = 0x83, + OP_AND = 0x84, + OP_OR = 0x85, + OP_XOR = 0x86, + OP_EQUAL = 0x87, + OP_EQUALVERIFY = 0x88, + OP_RESERVED1 = 0x89, + OP_RESERVED2 = 0x8a, + + // numeric + OP_1ADD = 0x8b, + OP_1SUB = 0x8c, + OP_2MUL = 0x8d, + OP_2DIV = 0x8e, + OP_NEGATE = 0x8f, + OP_ABS = 0x90, + OP_NOT = 0x91, + OP_0NOTEQUAL = 0x92, + + OP_ADD = 0x93, + OP_SUB = 0x94, + OP_MUL = 0x95, + OP_DIV = 0x96, + OP_MOD = 0x97, + OP_LSHIFT = 0x98, + OP_RSHIFT = 0x99, + + OP_BOOLAND = 0x9a, + OP_BOOLOR = 0x9b, + OP_NUMEQUAL = 0x9c, + OP_NUMEQUALVERIFY = 0x9d, + OP_NUMNOTEQUAL = 0x9e, + OP_LESSTHAN = 0x9f, + OP_GREATERTHAN = 0xa0, + OP_LESSTHANOREQUAL = 0xa1, + OP_GREATERTHANOREQUAL = 0xa2, + OP_MIN = 0xa3, + OP_MAX = 0xa4, + + OP_WITHIN = 0xa5, + + // crypto + OP_RIPEMD160 = 0xa6, + OP_SHA1 = 0xa7, + OP_SHA256 = 0xa8, + OP_HASH160 = 0xa9, + OP_HASH256 = 0xaa, + OP_CODESEPARATOR = 0xab, + OP_CHECKSIG = 0xac, + OP_CHECKSIGVERIFY = 0xad, + OP_CHECKMULTISIG = 0xae, + OP_CHECKMULTISIGVERIFY = 0xaf, + + // expansion + OP_NOP1 = 0xb0, + OP_NOP2 = 0xb1, + OP_NOP3 = 0xb2, + OP_NOP4 = 0xb3, + OP_NOP5 = 0xb4, + OP_NOP6 = 0xb5, + OP_NOP7 = 0xb6, + OP_NOP8 = 0xb7, + OP_NOP9 = 0xb8, + OP_NOP10 = 0xb9, + + OP_INVALIDOPCODE = 0xff, +} +} + +use Operation::*; + +pub const OP_CHECKLOCKTIMEVERIFY: Operation = OP_NOP2; + +impl From for u8 { + fn from(value: Opcode) -> Self { + match value { + Opcode::PushValue(pv) => pv.into(), + Opcode::Operation(op) => op.into(), + } + } +} + +impl From 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 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 for PushValue { + type Error = (); + fn try_from(value: u8) -> Result { + 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 for u8 { + fn from(value: Operation) -> Self { + // This is how you get the discriminant, but using `as` everywhere is too much code smell + value as u8 + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct ScriptNum(pub i64); + +impl ScriptNum { + const DEFAULT_MAX_NUM_SIZE: usize = 4; + + pub fn new( + vch: &Vec, + require_minimal: bool, + max_num_size: Option, + ) -> Result { + let max_num_size = max_num_size.unwrap_or(Self::DEFAULT_MAX_NUM_SIZE); + if vch.len() > max_num_size { + return Err(ScriptNumError::Overflow { + max_num_size, + actual: vch.len(), + }); + } + if require_minimal && !vch.is_empty() { + // Check that the number is encoded with the minimum possible + // number of bytes. + // + // If the most-significant-byte - excluding the sign bit - is zero + // then we're not minimal. Note how this test also rejects the + // negative-zero encoding, 0x80. + if (vch.last().unwrap_or_else(|| unreachable!()) & 0x7F) == 0 { + // One exception: if there's more than one byte and the most + // significant bit of the second-most-significant-byte is set + // it would conflict with the sign bit. An example of this case + // is +-255, which encode to 0xff00 and 0xff80 respectively. + // (big-endian). + if vch.len() <= 1 { + return Err(ScriptNumError::NegativeZero); + } else if (vch[vch.len() - 2] & 0x80) == 0 { + return Err(ScriptNumError::NonMinimalEncoding); + } + } + } + Self::set_vch(vch).map(ScriptNum) + } + + pub fn getint(&self) -> i32 { + if self.0 > i32::MAX.into() { + i32::MAX + } else if self.0 < i32::MIN.into() { + i32::MIN + } else { + self.0.try_into().unwrap() + } + } + + pub fn getvch(&self) -> Vec { + Self::serialize(&self.0) + } + + pub fn serialize(value: &i64) -> Vec { + if *value == 0 { + return Vec::new(); + } + + if *value == i64::MIN { + // The code below is buggy, and produces the "wrong" result for + // INT64_MIN. To avoid undefined behavior while attempting to + // negate a value of INT64_MIN, we intentionally return the result + // that the code below would produce on an x86_64 system. + return vec![0, 0, 0, 0, 0, 0, 0, 128, 128]; + } + + let mut result = Vec::new(); + let neg = *value < 0; + let mut absvalue = value.abs(); + + while absvalue != 0 { + result.push( + (absvalue & 0xff) + .try_into() + .unwrap_or_else(|_| unreachable!()), + ); + absvalue >>= 8; + } + + // - If the most significant byte is >= 0x80 and the value is positive, push a + // new zero-byte to make the significant byte < 0x80 again. + + // - If the most significant byte is >= 0x80 and the value is negative, push a + // new 0x80 byte that will be popped off when converting to an integral. + + // - If the most significant byte is < 0x80 and the value is negative, add + // 0x80 to it, since it will be subtracted and interpreted as a negative when + // converting to an integral. + + if result.last().map_or(true, |last| last & 0x80 != 0) { + result.push(if neg { 0x80 } else { 0 }); + } else if neg { + if let Some(last) = result.last_mut() { + *last |= 0x80; + } + } + + result + } + + fn set_vch(vch: &Vec) -> Result { + match vch.last() { + None => Ok(0), + Some(vch_back) => { + if *vch == vec![0, 0, 0, 0, 0, 0, 0, 128, 128] { + // On an x86_64 system, the code below would actually decode the buggy + // INT64_MIN encoding correctly. However in this case, it would be + // performing left shifts of a signed type by 64, which has undefined + // behavior. + return Ok(i64::MIN); + }; + + // Guard against undefined behavior. INT64_MIN is the only allowed 9-byte encoding. + if vch.len() > 8 { + return Err(ScriptNumError::Overflow { + max_num_size: 8, + actual: vch.len(), + }); + }; + + let mut result: i64 = 0; + for (i, vch_i) in vch.iter().enumerate() { + result |= i64::from(*vch_i) << (8 * i); + } + + // If the input vector's most significant byte is 0x80, remove it from + // the result's msb and return a negative. + if vch_back & 0x80 != 0 { + return Ok(-(result & !(0x80 << (8 * (vch.len() - 1))))); + }; + + Ok(result) + } + } + } +} + +impl Add for ScriptNum { + type Output = Self; + + fn add(self, other: Self) -> Self { + let rhs = other.0; + assert!( + rhs == 0 + || (rhs > 0 && self.0 <= i64::MAX - rhs) + || (rhs < 0 && self.0 >= i64::MIN - rhs) + ); + Self(self.0 + rhs) + } +} + +impl Sub for ScriptNum { + type Output = Self; + + fn sub(self, other: Self) -> Self { + let rhs = other.0; + assert!( + rhs == 0 + || (rhs > 0 && self.0 >= i64::MIN + rhs) + || (rhs < 0 && self.0 <= i64::MAX + rhs) + ); + Self(self.0 - rhs) + } +} + +impl Neg for ScriptNum { + type Output = Self; + + fn neg(self) -> Self { + assert!(self.0 != i64::MIN); + Self(-self.0) + } +} + +/** Serialized script, used inside transaction inputs and outputs */ +#[derive(Clone, Debug)] +pub struct Script<'a>(pub &'a [u8]); + +impl<'a> Script<'a> { + pub fn get_op(script: &mut &[u8]) -> Result { + Self::get_op2(script, &mut vec![]) + } + + pub fn get_op2(script: &mut &[u8], buffer: &mut Vec) -> Result { + if script.is_empty() { + panic!("attempting to parse an opcode from an empty script"); + } + + // Empty the provided buffer, if any + buffer.truncate(0); + + let leading_byte = Opcode::from(script[0]); + *script = &script[1..]; + + Ok(match leading_byte { + Opcode::PushValue(pv) => match pv { + OP_PUSHDATA1 | OP_PUSHDATA2 | OP_PUSHDATA4 => { + let read_le = |script: &mut &[u8], needed_bytes: usize| { + if script.len() < needed_bytes { + Err(ScriptError::ReadError { + expected_bytes: needed_bytes, + available_bytes: script.len(), + }) + } else { + let mut size = 0; + for i in (0..needed_bytes).rev() { + size <<= 8; + size |= usize::from(script[i]); + } + *script = &script[needed_bytes..]; + Ok(size) + } + }; + + let size = match pv { + OP_PUSHDATA1 => read_le(script, 1), + OP_PUSHDATA2 => read_le(script, 2), + OP_PUSHDATA4 => read_le(script, 4), + _ => unreachable!(), + }?; + + if script.len() < size { + return Err(ScriptError::ReadError { + expected_bytes: size, + available_bytes: script.len(), + }); + } + + buffer.extend(&script[0..size]); + *script = &script[size..]; + + leading_byte + } + // OP_0/OP_FALSE doesn't actually push a constant 0 onto the stack but + // pushes an empty array. (Thus we leave the buffer truncated to 0 length) + OP_0 => leading_byte, + PushdataBytelength(size_byte) => { + let size = size_byte.into(); + + if script.len() < size { + return Err(ScriptError::ReadError { + expected_bytes: size, + available_bytes: script.len(), + }); + } + + buffer.extend(&script[0..size]); + *script = &script[size..]; + + leading_byte + } + _ => leading_byte, + }, + _ => leading_byte, + }) + } + + /** Encode/decode small integers: */ + pub fn decode_op_n(opcode: PushValue) -> u32 { + if opcode == OP_0 { + return 0; + } + assert!(opcode >= OP_1 && opcode <= OP_16); + (u8::from(opcode) - (u8::from(OP_1) - 1)).into() + } + + /// Pre-version-0.6, Bitcoin always counted CHECKMULTISIGs + /// as 20 sigops. With pay-to-script-hash, that changed: + /// CHECKMULTISIGs serialized in script_sigs are + /// counted more accurately, assuming they are of the form + /// ... OP_N CHECKMULTISIG ... + pub fn get_sig_op_count(&self, accurate: bool) -> u32 { + let mut n = 0; + let mut pc = self.0; + let mut last_opcode = Opcode::Operation(OP_INVALIDOPCODE); + while !pc.is_empty() { + let opcode = match Self::get_op(&mut pc) { + Ok(o) => o, + Err(_) => break, + }; + if let Opcode::Operation(op) = opcode { + if op == OP_CHECKSIG || op == OP_CHECKSIGVERIFY { + n += 1; + } else if op == OP_CHECKMULTISIG || op == OP_CHECKMULTISIGVERIFY { + match last_opcode { + Opcode::PushValue(pv) => { + if accurate && pv >= OP_1 && pv <= OP_16 { + n += Self::decode_op_n(pv); + } else { + n += 20 + } + } + _ => n += 20, + } + } + } + last_opcode = opcode; + } + n + } + + /// Returns true iff this script is P2SH. + pub fn is_pay_to_script_hash(&self) -> bool { + self.0.len() == 23 + && self.0[0] == OP_HASH160.into() + && self.0[1] == 0x14 + && self.0[22] == OP_EQUAL.into() + } + + /// Called by `IsStandardTx` and P2SH/BIP62 VerifyScript (which makes it consensus-critical). + pub fn is_push_only(&self) -> bool { + let mut pc = self.0; + while !pc.is_empty() { + if let Ok(Opcode::PushValue(_)) = Self::get_op(&mut pc) { + } else { + return false; + } + } + true + } +} diff --git a/src/script_error.rs b/src/script_error.rs new file mode 100644 index 000000000..4aa8e039e --- /dev/null +++ b/src/script_error.rs @@ -0,0 +1,62 @@ +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum ScriptNumError { + NegativeZero, + NonMinimalEncoding, + Overflow { max_num_size: usize, actual: usize }, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[repr(i32)] +pub enum ScriptError { + // Ok = 0, + UnknownError = 1, + EvalFalse, + OpReturn, + + // Max sizes + ScriptSize, + PushSize, + OpCount, + StackSize, + SigCount, + PubKeyCount, + + // Failed verify operations + Verify, + EqualVerify, + CheckMultisigVerify, + CheckSigVerify, + NumEqualVerify, + + // Logical/Format/Canonical errors + BadOpcode, + DisabledOpcode, + InvalidStackOperation, + InvalidAltstackOperation, + UnbalancedConditional, + + // OP_CHECKLOCKTIMEVERIFY + NegativeLockTime, + UnsatisfiedLockTime, + + // BIP62 + SigHashType, + SigDER, + MinimalData, + SigPushOnly, + // SigHighS, + SigNullDummy = 27, + PubKeyType, + CleanStack, + + // softfork safeness + DiscourageUpgradableNOPs, + + ReadError { + expected_bytes: usize, + available_bytes: usize, + }, + + /// Corresponds to the `scriptnum_error` exception in C++. + ScriptNumError(ScriptNumError), +} diff --git a/src/zcash_script.rs b/src/zcash_script.rs index fa7d330ac..a18574d27 100644 --- a/src/zcash_script.rs +++ b/src/zcash_script.rs @@ -1,6 +1,8 @@ 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 aren’t used any more. This only /// replicates the still-used cases, and then an `Unknown` bucket for anything else that might @@ -9,7 +11,10 @@ use super::interpreter::*; #[repr(u32)] pub enum Error { /// Any failure that results in the script being invalid. - Ok = 0, + /// + /// __NB__: This is in `Option` because this type is used by both the C++ and Rust + /// implementations, but the C++ impl doesn’t yet expose the original error. + Ok(Option) = 0, /// An exception was caught. VerifyScript = 7, /// The script size can’t fit in a `u32`, as required by the C++ code. @@ -21,20 +26,6 @@ pub enum Error { Unknown(i64), } -/// All signature hashes are 32 bits, since they are necessarily produced by SHA256. -pub const SIGHASH_SIZE: usize = 32; - -/// A function which is called to obtain the sighash. -/// - script_code: the scriptCode being validated. Note that this not always -/// matches script_sig, i.e. for P2SH. -/// - hash_type: the hash type being used. -/// -/// The `extern "C"` function that calls this doesn’t give much opportunity for rich failure -/// reporting, but returning `None` indicates _some_ failure to produce the desired hash. -/// -/// TODO: Can we get the “32” from somewhere rather than hardcoding it? -pub type SighashCalculator<'a> = &'a dyn Fn(&[u8], HashType) -> Option<[u8; SIGHASH_SIZE]>; - /// The external API of zcash_script. This is defined to make it possible to compare the C++ and /// Rust implementations. pub trait ZcashScript { @@ -54,8 +45,8 @@ pub trait ZcashScript { /// /// Note that script verification failure is indicated by `Err(Error::Ok)`. fn verify_callback( - sighash: SighashCalculator, - n_lock_time: i64, + sighash_callback: SighashCalculator, + lock_time: i64, is_final: bool, script_pub_key: &[u8], script_sig: &[u8], @@ -66,3 +57,37 @@ pub trait ZcashScript { /// output script pointed to by script. fn legacy_sigop_count_script(script: &[u8]) -> Result; } + +/// A tag to indicate that the Rust implementation of zcash_script should be used. +pub enum Rust {} + +impl ZcashScript for Rust { + /// Returns the number of transparent signature operations in the + /// transparent inputs and outputs of this transaction. + fn legacy_sigop_count_script(script: &[u8]) -> Result { + let cscript = Script(script); + Ok(cscript.get_sig_op_count(false)) + } + + fn verify_callback( + sighash: SighashCalculator, + lock_time: i64, + is_final: bool, + script_pub_key: &[u8], + script_sig: &[u8], + flags: VerificationFlags, + ) -> Result<(), Error> { + let lock_time_num = ScriptNum(lock_time); + verify_script( + &Script(script_sig), + &Script(script_pub_key), + flags, + &CallbackTransactionSignatureChecker { + sighash, + lock_time: &lock_time_num, + is_final, + }, + ) + .map_err(|e| Error::Ok(Some(e))) + } +}