diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 488b9d17..ea14d773 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,9 @@ jobs: uses: actions-rs/cargo@v1 with: command: fetch + - name: install LLVM on Windows + if: matrix.os == 'windows-latest' + run: choco install llvm -y - name: Run tests uses: actions-rs/cargo@v1 with: @@ -43,6 +46,9 @@ jobs: with: toolchain: stable override: true + - name: install LLVM on Windows + if: matrix.os == 'windows-latest' + run: choco install llvm -y - name: cargo fetch uses: actions-rs/cargo@v1 with: diff --git a/Cargo.lock b/Cargo.lock index c96d77d8..623c0da9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -182,6 +182,30 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.54.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4d49b80beb70d76cdac92f5681e666f9a697c737c4f4117a67229a0386dc736" +dependencies = [ + "bitflags", + "cexpr", + "cfg-if", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2 1.0.19", + "quote 1.0.7", + "regex", + "rustc-hash", + "shlex", + "which", +] + [[package]] name = "bit-set" version = "0.5.2" @@ -334,6 +358,15 @@ version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dae9c4b8fedcae85592ba623c4fd08cfdab3e3b72d6df780c6ead964a69bfff" +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "0.1.10" @@ -352,6 +385,17 @@ dependencies = [ "time", ] +[[package]] +name = "clang-sys" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "2.33.3" @@ -689,6 +733,19 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "equihash" version = "0.1.0" @@ -914,6 +971,12 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "gumdrop" version = "0.7.0" @@ -1023,6 +1086,15 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + [[package]] name = "hyper" version = "0.13.7" @@ -1155,12 +1227,28 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" + [[package]] name = "libc" version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10" +[[package]] +name = "libloading" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +dependencies = [ + "cc", + "winapi 0.3.9", +] + [[package]] name = "linked-hash-map" version = "0.5.3" @@ -1418,6 +1506,16 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check", +] + [[package]] name = "num-format" version = "0.4.0" @@ -1552,6 +1650,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "pin-project" version = "0.4.23" @@ -1940,6 +2044,12 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -2102,6 +2212,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + [[package]] name = "signal-hook" version = "0.1.16" @@ -2820,6 +2936,15 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "which" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +dependencies = [ + "libc", +] + [[package]] name = "winapi" version = "0.2.8" @@ -2894,6 +3019,19 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "zcash_script" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "536376d3d5181455cb7df832cf1c7f519b3d2fe0a097091f891ffef96b9b4f09" +dependencies = [ + "bindgen", + "blake2b_simd", + "cc", + "color-eyre", + "libc", +] + [[package]] name = "zebra-chain" version = "3.0.0-alpha.0" @@ -2999,6 +3137,15 @@ version = "3.0.0-alpha.0" [[package]] name = "zebra-script" version = "3.0.0-alpha.0" +dependencies = [ + "displaydoc", + "hex", + "lazy_static", + "thiserror", + "zcash_script", + "zebra-chain", + "zebra-test", +] [[package]] name = "zebra-state" diff --git a/Dockerfile b/Dockerfile index 16cd3a05..2922e17b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM rust:stretch as builder +FROM rust:buster as builder RUN apt-get update && \ apt-get install -y --no-install-recommends \ - make cmake g++ gcc + make cmake g++ gcc llvm libclang-dev RUN mkdir /zebra WORKDIR /zebra diff --git a/zebra-chain/src/parameters/network_upgrade.rs b/zebra-chain/src/parameters/network_upgrade.rs index b0232c41..5d2742db 100644 --- a/zebra-chain/src/parameters/network_upgrade.rs +++ b/zebra-chain/src/parameters/network_upgrade.rs @@ -72,6 +72,29 @@ pub(crate) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub struct ConsensusBranchId(u32); +impl ConsensusBranchId { + /// Consensus branch ID for the overwinter network branch + pub const OVERWINTER: ConsensusBranchId = ConsensusBranchId(0x5ba81b19); + + /// Consensus branch ID for the sapling network branch + pub const SAPLING: ConsensusBranchId = ConsensusBranchId(0x76b809bb); + + /// Consensus branch ID for the blossom network branch + pub const BLOSSOM: ConsensusBranchId = ConsensusBranchId(0x2bb40e60); + + /// Consensus branch ID for the heartwood network branch + pub const HEARTWOOD: ConsensusBranchId = ConsensusBranchId(0xf5b9230b); + + /// Consensus branch ID for the canopy network branch + pub const CANOPY: ConsensusBranchId = ConsensusBranchId(0xe9ff75a6); +} + +impl From for u32 { + fn from(branch: ConsensusBranchId) -> u32 { + branch.0 + } +} + /// Network Upgrade Consensus Branch Ids. /// /// Branch ids are the same for mainnet and testnet. If there is a testnet @@ -84,13 +107,13 @@ pub struct ConsensusBranchId(u32); /// do the uniqueness check in the unit tests. pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] = &[ // TODO(teor): byte order? - (Overwinter, ConsensusBranchId(0x5ba81b19)), - (Sapling, ConsensusBranchId(0x76b809bb)), - (Blossom, ConsensusBranchId(0x2bb40e60)), - (Heartwood, ConsensusBranchId(0xf5b9230b)), + (Overwinter, ConsensusBranchId::OVERWINTER), + (Sapling, ConsensusBranchId::SAPLING), + (Blossom, ConsensusBranchId::BLOSSOM), + (Heartwood, ConsensusBranchId::HEARTWOOD), // As of 21 July 2020. Could change before mainnet activation. // See ZIP 251 for updates. - (Canopy, ConsensusBranchId(0xe9ff75a6)), + (Canopy, ConsensusBranchId::CANOPY), ]; impl NetworkUpgrade { diff --git a/zebra-script/Cargo.toml b/zebra-script/Cargo.toml index d2fccf07..dcab6212 100644 --- a/zebra-script/Cargo.toml +++ b/zebra-script/Cargo.toml @@ -8,3 +8,12 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +zcash_script = "0.1.0" +zebra-chain = { path = "../zebra-chain" } +thiserror = "1.0.20" +displaydoc = "0.1.7" + +[dev-dependencies] +hex = "0.4.2" +lazy_static = "1.4.0" +zebra-test = { path = "../zebra-test" } diff --git a/zebra-script/src/lib.rs b/zebra-script/src/lib.rs index 1589a7a0..98cee9af 100644 --- a/zebra-script/src/lib.rs +++ b/zebra-script/src/lib.rs @@ -1,11 +1,197 @@ +//! Zebra script verification wrapping zcashd's zcash_script library #![doc(html_favicon_url = "https://www.zfnd.org/images/zebra-favicon-128.png")] #![doc(html_logo_url = "https://www.zfnd.org/images/zebra-icon.png")] #![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_script")] -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); +use displaydoc::Display; +#[cfg(windows)] +use std::convert::TryInto; +use std::sync::Arc; +use thiserror::Error; +use zcash_script::{ + zcash_script_error_t, zcash_script_error_t_zcash_script_ERR_OK, + zcash_script_error_t_zcash_script_ERR_TX_DESERIALIZE, + zcash_script_error_t_zcash_script_ERR_TX_INDEX, + zcash_script_error_t_zcash_script_ERR_TX_SIZE_MISMATCH, +}; +use zebra_chain::{ + parameters::ConsensusBranchId, serialization::ZcashSerialize, transaction::Transaction, + transparent, +}; + +#[derive(Debug, Display, Error)] +#[non_exhaustive] +/// An Error type representing the error codes returned from zcash_script. +pub enum Error { + /// script failed to verify + #[non_exhaustive] + ScriptInvalid, + /// could not to deserialize tx + #[non_exhaustive] + TxDeserialize, + /// input index out of bounds for transaction's inputs + #[non_exhaustive] + TxIndex, + /// tx is an invalid size for it's protocol + #[non_exhaustive] + TxSizeMismatch, + /// encountered unknown error kind from zcash_script: {0} + #[non_exhaustive] + Unknown(zcash_script_error_t), +} + +impl From for Error { + #[allow(non_upper_case_globals)] + fn from(err_code: zcash_script_error_t) -> Error { + match err_code { + zcash_script_error_t_zcash_script_ERR_OK => Error::ScriptInvalid, + zcash_script_error_t_zcash_script_ERR_TX_DESERIALIZE => Error::TxDeserialize, + zcash_script_error_t_zcash_script_ERR_TX_INDEX => Error::TxIndex, + zcash_script_error_t_zcash_script_ERR_TX_SIZE_MISMATCH => Error::TxSizeMismatch, + unknown => Error::Unknown(unknown), + } + } +} + +/// Thin safe wrapper around ffi interface provided by libzcash_script +fn verify_script( + script_pub_key: impl AsRef<[u8]>, + amount: i64, + tx_to: impl AsRef<[u8]>, + n_in: u32, + consensus_branch_id: u32, +) -> Result<(), Error> { + let script_pub_key = script_pub_key.as_ref(); + let tx_to = tx_to.as_ref(); + + let script_ptr = script_pub_key.as_ptr(); + let script_len = script_pub_key.len(); + let tx_to_ptr = tx_to.as_ptr(); + let tx_to_len = tx_to.len(); + let mut err = 0; + + let flags = zcash_script::zcash_script_SCRIPT_FLAGS_VERIFY_P2SH + | zcash_script::zcash_script_SCRIPT_FLAGS_VERIFY_CHECKLOCKTIMEVERIFY; + + let ret = unsafe { + zcash_script::zcash_script_verify( + script_ptr, + script_len as u32, + amount, + tx_to_ptr, + tx_to_len as u32, + n_in, + #[cfg(not(windows))] + flags, + #[cfg(windows)] + flags.try_into().expect("why bindgen whyyy"), + consensus_branch_id, + &mut err, + ) + }; + + if ret == 1 { + Ok(()) + } else { + Err(Error::from(err)) + } +} + +/// Verify a script within a transaction given the corresponding +/// `transparent::Output` it is spending and the `ConsensusBranchId` of the block +/// containing the transaction. +/// +/// # Details +/// +/// input index corresponds to the index of the `TransparentInput` which in +/// `transaction` used to identify the `previous_output` +pub fn is_valid( + transaction: Arc, + branch_id: ConsensusBranchId, + (input_index, previous_output): (u32, transparent::Output), +) -> Result<(), Error> { + assert!((input_index as usize) < transaction.inputs().len()); + + let tx_to = transaction + .zcash_serialize_to_vec() + .expect("serialization into a vec is infallible"); + + let transparent::Output { value, lock_script } = previous_output; + + verify_script( + &lock_script.0, + value.into(), + &tx_to, + input_index as _, + branch_id.into(), + )?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use hex::FromHex; + use std::convert::TryInto; + use std::sync::Arc; + use zebra_chain::{serialization::ZcashDeserializeInto, transparent}; + use zebra_test::prelude::*; + + lazy_static::lazy_static! { + pub static ref SCRIPT_PUBKEY: Vec = >::from_hex("76a914f47cac1e6fec195c055994e8064ffccce0044dd788ac").unwrap(); + pub static ref SCRIPT_TX: Vec = >::from_hex("0400008085202f8901fcaf44919d4a17f6181a02a7ebe0420be6f7dad1ef86755b81d5a9567456653c010000006a473044022035224ed7276e61affd53315eca059c92876bc2df61d84277cafd7af61d4dbf4002203ed72ea497a9f6b38eb29df08e830d99e32377edb8a574b8a289024f0241d7c40121031f54b095eae066d96b2557c1f99e40e967978a5fd117465dbec0986ca74201a6feffffff020050d6dc0100000017a9141b8a9bda4b62cd0d0582b55455d0778c86f8628f870d03c812030000001976a914e4ff5512ffafe9287992a1cd177ca6e408e0300388ac62070d0095070d000000000000000000000000").expect("Block bytes are in valid hex representation"); + } + + #[test] + fn verify_valid_script_parsed() -> Result<()> { + zebra_test::init(); + + let transaction = + SCRIPT_TX.zcash_deserialize_into::>()?; + let coin = u64::pow(10, 8); + let amount = 212 * coin; + let output = transparent::Output { + value: amount.try_into()?, + lock_script: transparent::Script(SCRIPT_PUBKEY.clone()), + }; + let input_index = 0; + let branch_id = ConsensusBranchId::BLOSSOM; + + is_valid(transaction, branch_id, (input_index, output))?; + + Ok(()) + } + + #[test] + fn verify_valid_script() -> Result<()> { + zebra_test::init(); + + let coin = i64::pow(10, 8); + let script_pub_key = &*SCRIPT_PUBKEY; + let amount = 212 * coin; + let tx_to = &*SCRIPT_TX; + let n_in = 0; + let branch_id = 0x2bb40e60; + + verify_script(script_pub_key, amount, tx_to, n_in, branch_id)?; + + Ok(()) + } + + #[test] + fn dont_verify_invalid_script() -> Result<()> { + zebra_test::init(); + + let coin = i64::pow(10, 8); + let script_pub_key = &*SCRIPT_PUBKEY; + let amount = 212 * coin; + let tx_to = &*SCRIPT_TX; + let n_in = 0; + let branch_id = 0x2bb40e61; + + verify_script(script_pub_key, amount, tx_to, n_in, branch_id).unwrap_err(); + + Ok(()) } } diff --git a/zebra-test/src/prelude.rs b/zebra-test/src/prelude.rs index 4cf6c69c..cd28ee3a 100644 --- a/zebra-test/src/prelude.rs +++ b/zebra-test/src/prelude.rs @@ -2,3 +2,7 @@ pub use crate::command::{tempdir, test_cmd, CommandExt, TestChild}; pub use std::process::Stdio; pub use tempdir::TempDir; + +pub use color_eyre; +pub use color_eyre::eyre; +pub use eyre::Result;