diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb8851d8ce..ebf404a9ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,11 +47,16 @@ jobs: ~/.cargo/git/db/ target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Run tests + - name: Run light tests # light tests are run in parallel uses: actions-rs/cargo@v1 with: command: test args: --verbose --release --all --all-features --exclude integration-tests --exclude circuit-benchmarks + - name: Run heavy tests # heavy tests are run serially to avoid OOM + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release --all --all-features --exclude integration-tests --exclude circuit-benchmarks serial_ -- --ignored --test-threads 1 build: if: github.event.pull_request.draft == false diff --git a/Cargo.lock b/Cargo.lock index 1341c28f2e..0b7be8c621 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,6 +77,12 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "arrayvec" version = "0.7.2" @@ -249,6 +255,17 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "blake2b_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "constant_time_eq", +] + [[package]] name = "blake2b_simd" version = "1.0.0" @@ -256,7 +273,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" dependencies = [ "arrayref", - "arrayvec", + "arrayvec 0.7.2", "constant_time_eq", ] @@ -292,6 +309,15 @@ dependencies = [ "generic-array 0.14.5", ] +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array 0.14.5", +] + [[package]] name = "block-padding" version = "0.1.5" @@ -479,7 +505,7 @@ dependencies = [ "bus-mapping", "eth-types", "ff 0.11.1", - "halo2_proofs", + "halo2_proofs 0.1.0-beta.1", "itertools", "keccak256", "rand", @@ -517,7 +543,7 @@ dependencies = [ "bs58", "coins-core", "digest 0.9.0", - "hmac", + "hmac 0.11.0", "k256", "lazy_static", "serde", @@ -535,7 +561,7 @@ dependencies = [ "coins-bip32", "getrandom", "hex", - "hmac", + "hmac 0.11.0", "pbkdf2", "rand", "sha2 0.9.9", @@ -792,6 +818,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array 0.14.5", + "typenum", +] + [[package]] name = "crypto-mac" version = "0.8.0" @@ -974,6 +1010,16 @@ dependencies = [ "generic-array 0.14.5", ] +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -1007,6 +1053,37 @@ dependencies = [ "wio", ] +[[package]] +name = "ecc" +version = "0.1.0" +source = "git+https://github.com/appliedzkp/halo2wrong?rev=92b96893b5699ff40723e201a2416313aeafd267#92b96893b5699ff40723e201a2416313aeafd267" +dependencies = [ + "cfg-if 0.1.10", + "group 0.11.0", + "integer", + "num-bigint", + "num-integer", + "num-traits", + "rand", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.1.0" +source = "git+https://github.com/appliedzkp/halo2wrong?rev=92b96893b5699ff40723e201a2416313aeafd267#92b96893b5699ff40723e201a2416313aeafd267" +dependencies = [ + "cfg-if 0.1.10", + "ecc", + "group 0.11.0", + "num-bigint", + "num-integer", + "num-traits", + "rand", + "secp256k1", + "subtle", +] + [[package]] name = "ecdsa" version = "0.12.4" @@ -1015,7 +1092,7 @@ checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" dependencies = [ "der 0.4.5", "elliptic-curve 0.10.6", - "hmac", + "hmac 0.11.0", "signature", ] @@ -1088,7 +1165,7 @@ dependencies = [ "ctr", "digest 0.9.0", "hex", - "hmac", + "hmac 0.11.0", "pbkdf2", "rand", "scrypt", @@ -1106,7 +1183,7 @@ version = "0.1.0" dependencies = [ "ethers-core", "ethers-providers", - "halo2_proofs", + "halo2_proofs 0.1.0-beta.1", "hex", "itertools", "lazy_static", @@ -1236,11 +1313,11 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f15e1a2a54bc6bc3f8ea94afafbb374264f8322fcacdae06fefda80a206739ac" dependencies = [ - "arrayvec", + "arrayvec 0.7.2", "bytes", "cargo_metadata", "convert_case", - "ecdsa", + "ecdsa 0.12.4", "elliptic-curve 0.11.12", "ethabi", "generic-array 0.14.5", @@ -1634,7 +1711,7 @@ version = "0.1.0" dependencies = [ "digest 0.7.6", "eth-types", - "halo2_proofs", + "halo2_proofs 0.1.0-beta.1", "rand", "rand_xorshift", "sha3 0.7.3", @@ -1732,7 +1809,9 @@ checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "byteorder", "ff 0.11.1", + "rand", "rand_core", + "rand_xorshift", "subtle", ] @@ -1766,7 +1845,7 @@ name = "halo2_proofs" version = "0.1.0-beta.1" source = "git+https://github.com/appliedzkp/halo2.git?tag=v2022_05_09#9992785dca35926ded74f6ec7c8dfbac507317d0" dependencies = [ - "blake2b_simd", + "blake2b_simd 1.0.0", "bumpalo", "cfg-if 0.1.10", "ff 0.11.1", @@ -1780,6 +1859,33 @@ dependencies = [ "tabbycat", ] +[[package]] +name = "halo2_proofs" +version = "0.1.0-beta.3" +source = "git+https://github.com/zcash/halo2.git#406f622e330e23ff91d645d43725e55de665c8e3" +dependencies = [ + "blake2b_simd 1.0.0", + "bumpalo", + "ff 0.11.1", + "group 0.11.0", + "pasta_curves", + "rand_core", + "rayon", +] + +[[package]] +name = "halo2wrong" +version = "0.1.0" +source = "git+https://github.com/appliedzkp/halo2wrong?rev=92b96893b5699ff40723e201a2416313aeafd267#92b96893b5699ff40723e201a2416313aeafd267" +dependencies = [ + "cfg-if 0.1.10", + "halo2_proofs 0.1.0-beta.1", + "halo2_proofs 0.1.0-beta.3", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -1807,6 +1913,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac 0.8.0", + "digest 0.9.0", +] + [[package]] name = "hmac" version = "0.11.0" @@ -1817,6 +1933,17 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array 0.14.5", + "hmac 0.8.1", +] + [[package]] name = "home" version = "0.5.3" @@ -2009,6 +2136,22 @@ dependencies = [ "web-sys", ] +[[package]] +name = "integer" +version = "0.1.0" +source = "git+https://github.com/appliedzkp/halo2wrong?rev=92b96893b5699ff40723e201a2416313aeafd267#92b96893b5699ff40723e201a2416313aeafd267" +dependencies = [ + "cfg-if 0.1.10", + "group 0.11.0", + "maingate", + "num-bigint", + "num-integer", + "num-traits", + "rand", + "secp256k1", + "subtle", +] + [[package]] name = "integration-tests" version = "0.1.0" @@ -2018,7 +2161,7 @@ dependencies = [ "eth-types", "ethers", "ff 0.11.1", - "halo2_proofs", + "halo2_proofs 0.1.0-beta.1", "lazy_static", "log", "pretty_assertions", @@ -2078,7 +2221,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea" dependencies = [ "cfg-if 1.0.0", - "ecdsa", + "ecdsa 0.12.4", "elliptic-curve 0.10.6", "sha2 0.9.9", "sha3 0.9.1", @@ -2096,7 +2239,7 @@ version = "0.1.0" dependencies = [ "eth-types", "gadgets", - "halo2_proofs", + "halo2_proofs 0.1.0-beta.1", "itertools", "lazy_static", "num-bigint", @@ -2118,6 +2261,54 @@ version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" +[[package]] +name = "libsecp256k1" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0452aac8bab02242429380e9b2f94ea20cea2b37e2c1777a1358799bbe97f37" +dependencies = [ + "arrayref", + "base64 0.13.0", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy 0.2.2", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "lock_api" version = "0.4.7" @@ -2137,6 +2328,21 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "maingate" +version = "0.1.0" +source = "git+https://github.com/appliedzkp/halo2wrong?rev=92b96893b5699ff40723e201a2416313aeafd267#92b96893b5699ff40723e201a2416313aeafd267" +dependencies = [ + "cfg-if 0.1.10", + "group 0.11.0", + "halo2wrong", + "num-bigint", + "num-integer", + "num-traits", + "rand", + "subtle", +] + [[package]] name = "matches" version = "0.1.9" @@ -2268,6 +2474,7 @@ dependencies = [ "autocfg", "num-integer", "num-traits", + "rand", ] [[package]] @@ -2439,7 +2646,7 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373b1a4c1338d9cd3d1fa53b3a11bdab5ab6bd80a20f7f7becd76953ae2be909" dependencies = [ - "arrayvec", + "arrayvec 0.7.2", "bitvec 0.20.5", "byte-slice-cast", "impl-trait-for-tuples", @@ -2495,6 +2702,21 @@ dependencies = [ "subtle", ] +[[package]] +name = "pasta_curves" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b6fc4f73033f6aa52fdde0c38f1f570e7f2c244f22e441f62a144556891b8c" +dependencies = [ + "blake2b_simd 1.0.0", + "ff 0.11.1", + "group 0.11.0", + "lazy_static", + "rand", + "static_assertions", + "subtle", +] + [[package]] name = "paste" version = "1.0.7" @@ -2528,7 +2750,7 @@ checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" dependencies = [ "base64ct", "crypto-mac 0.11.1", - "hmac", + "hmac 0.11.0", "password-hash", "sha2 0.9.9", ] @@ -2746,7 +2968,7 @@ dependencies = [ "env_logger", "eth-types", "ethers-providers", - "halo2_proofs", + "halo2_proofs 0.1.0-beta.1", "hyper", "log", "rand", @@ -3105,7 +3327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879588d8f90906e73302547e20fffefdd240eb3e0e744e142321f5d49dea0518" dependencies = [ "base64ct", - "hmac", + "hmac 0.11.0", "password-hash", "pbkdf2", "salsa20", @@ -3132,6 +3354,24 @@ dependencies = [ "untrusted", ] +[[package]] +name = "secp256k1" +version = "0.0.1" +source = "git+https://github.com/appliedzkp/halo2wrong?rev=92b96893b5699ff40723e201a2416313aeafd267#92b96893b5699ff40723e201a2416313aeafd267" +dependencies = [ + "blake2b_simd 0.5.11", + "cfg-if 0.1.10", + "ff 0.11.1", + "group 0.11.0", + "halo2wrong", + "lazy_static", + "num-bigint", + "num-traits", + "rand", + "static_assertions", + "subtle", +] + [[package]] name = "security-framework" version = "2.6.1" @@ -3344,6 +3584,16 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha3" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" +dependencies = [ + "digest 0.10.3", + "keccak", +] + [[package]] name = "signature" version = "1.3.2" @@ -4113,24 +4363,37 @@ dependencies = [ "criterion", "ctor", "digest 0.7.6", + "ecc", + "ecdsa 0.1.0", "env_logger", "eth-types", "ethers-core", + "ethers-signers", "ff 0.11.1", "gadgets", - "halo2_proofs", + "group 0.11.0", + "halo2_proofs 0.1.0-beta.1", "hex", + "integer", "itertools", "keccak256", "lazy_static", + "libsecp256k1", "log", + "maingate", "mock", "num", + "num-bigint", "paste", + "pretty_assertions", "rand", + "rand_chacha", "rand_xorshift", + "rlp", + "secp256k1", "serde_json", - "sha3 0.7.3", + "sha3 0.10.1", "strum", "strum_macros", + "subtle", ] diff --git a/Makefile b/Makefile index 60d4649269..1bc6d31485 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,10 @@ fmt: ## Check whether the code is formated correctly @cargo fmt --all -- --check test: ## Run tests for all the workspace members + # Run light tests @cargo test --release --all --all-features --exclude integration-tests --exclude circuit-benchmarks + # Run heavy tests serially to avoid OOM + @cargo test --release --all --all-features --exclude integration-tests --exclude circuit-benchmarks serial_ -- --ignored --test-threads 1 test_doc: ## Test the docs @cargo test --release --all --all-features --doc diff --git a/eth-types/src/geth_types.rs b/eth-types/src/geth_types.rs index 765de3e9ce..e71ed19e38 100644 --- a/eth-types/src/geth_types.rs +++ b/eth-types/src/geth_types.rs @@ -90,15 +90,15 @@ impl BlockConstants { /// Definition of all of the constants related to an Ethereum transaction. #[derive(Debug, Default, Clone, Serialize)] pub struct Transaction { - /// From Address + /// Sender address pub from: Address, - /// To Address + /// Recipient address (None for contract creation) pub to: Option
, - /// Nonce + /// Transaction nonce pub nonce: Word, - /// Gas Limit + /// Gas Limit / Supplied gas pub gas_limit: Word, - /// Value + /// Transfered value pub value: Word, /// Gas Price pub gas_price: Word, @@ -106,10 +106,19 @@ pub struct Transaction { pub gas_fee_cap: Word, /// Gas tip cap pub gas_tip_cap: Word, - /// Call data + /// The compiled code of a contract OR the first 4 bytes of the hash of the + /// invoked method signature and encoded parameters. For details see + /// Ethereum Contract ABI pub call_data: Bytes, /// Access list pub access_list: Option, + + /// "v" value of the transaction signature + pub v: u64, + /// "r" value of the transaction signature + pub r: Word, + /// "s" value of the transaction signature + pub s: Word, } impl Transaction { @@ -126,6 +135,9 @@ impl Transaction { gas_tip_cap: tx.max_fee_per_gas.unwrap_or_default(), call_data: tx.input.clone(), access_list: tx.access_list.clone(), + v: tx.v.as_u64(), + r: tx.r, + s: tx.s, } } } diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index 10a1ded790..b5b0c202aa 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -11,7 +11,7 @@ ff = "0.11" halo2_proofs = { git = "https://github.com/appliedzkp/halo2.git", tag = "v2022_05_09" } bigint = "4" num = "0.4" -sha3 = "0.7.2" +sha3 = "0.10" digest = "0.7.6" array-init = "2.0.0" paste = "1.0" @@ -27,8 +27,18 @@ rand = "0.8" itertools = "0.10.3" lazy_static = "1.4" keccak256 = { path = "../keccak256"} -log = "0.4.14" +log = "0.4" env_logger = "0.9" +ecdsa = { git = "https://github.com/appliedzkp/halo2wrong", rev = "92b96893b5699ff40723e201a2416313aeafd267", features = ["kzg"] } +secp256k1 = { git = "https://github.com/appliedzkp/halo2wrong", rev = "92b96893b5699ff40723e201a2416313aeafd267", features = ["kzg"] } +ecc = { git = "https://github.com/appliedzkp/halo2wrong", rev = "92b96893b5699ff40723e201a2416313aeafd267", features = ["kzg"] } +maingate = { git = "https://github.com/appliedzkp/halo2wrong", rev = "92b96893b5699ff40723e201a2416313aeafd267", features = ["kzg"] } +integer = { git = "https://github.com/appliedzkp/halo2wrong", rev = "92b96893b5699ff40723e201a2416313aeafd267", features = ["kzg"] } +group = "0.11" +libsecp256k1 = "0.7" +rlp = "0.5" +num-bigint = { version = "0.4" } +subtle = "2.4" [dev-dependencies] criterion = "0.3" @@ -37,6 +47,9 @@ env_logger = "0.9.0" hex = "0.4.3" mock = { path = "../mock" } itertools = "0.10.1" +pretty_assertions = "1.0.0" +ethers-signers = "0.6" +rand_chacha = "0.3" [[bench]] name = "binary_value" diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 9e6345a3c5..bf1bea142a 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -375,6 +375,10 @@ pub mod test { let bytecode_table = [(); 5].map(|_| meta.advice_column()); let block_table = [(); 3].map(|_| meta.advice_column()); + // This gate is used just to get the array of expressions from the power of + // randomness instance column, so that later on we don't need to query + // columns everywhere, and can pass the power of randomness array + // expression everywhere. The gate itself doesn't add any constraints. let power_of_randomness = { let columns = [(); 31].map(|_| meta.instance_column()); let mut power_of_randomness = None; diff --git a/zkevm-circuits/src/evm_circuit/util.rs b/zkevm-circuits/src/evm_circuit/util.rs index 98a8350d35..dc8c059bae 100644 --- a/zkevm-circuits/src/evm_circuit/util.rs +++ b/zkevm-circuits/src/evm_circuit/util.rs @@ -305,6 +305,7 @@ pub(crate) struct RandomLinearCombination { impl RandomLinearCombination { const N_BYTES: usize = N; + // TODO: replace `bytes` type by a reference pub(crate) fn random_linear_combine(bytes: [u8; N], randomness: F) -> F { rlc::value(&bytes, randomness) } diff --git a/zkevm-circuits/src/lib.rs b/zkevm-circuits/src/lib.rs index a40a1482f2..ddb88f9d0f 100644 --- a/zkevm-circuits/src/lib.rs +++ b/zkevm-circuits/src/lib.rs @@ -17,4 +17,5 @@ pub mod rw_table; pub mod state_circuit; #[cfg(test)] pub mod test_util; +pub mod tx_circuit; pub mod util; diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs new file mode 100644 index 0000000000..d541b0b4d2 --- /dev/null +++ b/zkevm-circuits/src/tx_circuit.rs @@ -0,0 +1,559 @@ +//! The transaction circuit implementation. + +// Naming notes: +// - *_be: Big-Endian bytes +// - *_le: Little-Endian bytes + +mod sign_verify; + +use crate::util::{random_linear_combine_word as rlc, Expr}; +use eth_types::{ + geth_types::Transaction, Address, Field, ToBigEndian, ToLittleEndian, ToScalar, Word, +}; +use ff::PrimeField; +use group::GroupEncoding; +use halo2_proofs::{ + circuit::{AssignedCell, Layouter, Region, SimpleFloorPlanner}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, + poly::Rotation, +}; +use itertools::Itertools; +use lazy_static::lazy_static; +use libsecp256k1; +use log::error; +use num::Integer; +use num_bigint::BigUint; +use rlp::RlpStream; +use secp256k1::Secp256k1Affine; +use sha3::{Digest, Keccak256}; +use sign_verify::{pk_bytes_swap_endianness, SignData, SignVerifyChip, SignVerifyConfig}; +pub use sign_verify::{POW_RAND_SIZE, VERIF_HEIGHT}; +use std::convert::TryInto; +use std::marker::PhantomData; +use subtle::CtOption; + +lazy_static! { + // Curve Scalar. Referece: Section 2.4.1 (parameter `n`) in "SEC 2: Recommended Elliptic Curve + // Domain Parameters" document at http://www.secg.org/sec2-v2.pdf + static ref SECP256K1_Q: BigUint = BigUint::from_slice(&[ + 0xd0364141, 0xbfd25e8c, + 0xaf48a03b, 0xbaaedce6, + 0xfffffffe, 0xffffffff, + 0xffffffff, 0xffffffff, + ]); +} + +fn recover_pk(v: u8, r: &Word, s: &Word, msg_hash: &[u8; 32]) -> Result { + let mut sig_bytes = [0u8; 64]; + sig_bytes[..32].copy_from_slice(&r.to_be_bytes()); + sig_bytes[32..].copy_from_slice(&s.to_be_bytes()); + let signature = libsecp256k1::Signature::parse_standard(&sig_bytes).map_err(|e| { + error!("Failed parsing signature from (r, s): {:?}", e); + Error::Synthesis + })?; + let msg_hash = libsecp256k1::Message::parse_slice(msg_hash.as_slice()).map_err(|e| { + error!("Message hash parsing from slice failed: {:?}", e); + Error::Synthesis + })?; + let recovery_id = libsecp256k1::RecoveryId::parse(v).map_err(|e| { + error!("secp256k1::RecoveriId::parse error: {:?}", e); + Error::Synthesis + })?; + let pk = libsecp256k1::recover(&msg_hash, &signature, &recovery_id).map_err(|e| { + error!("Public key recovery failed: {:?}", e); + Error::Synthesis + })?; + let pk_be = pk.serialize(); + let pk_le = pk_bytes_swap_endianness(&pk_be[1..]); + let mut pk_bytes = secp256k1::Serialized::default(); + pk_bytes.as_mut().copy_from_slice(&pk_le[..]); + let pk = Secp256k1Affine::from_bytes(&pk_bytes); + ct_option_ok_or(pk, Error::Synthesis).map_err(|e| { + error!("Invalid public key little endian bytes"); + e + }) +} + +fn biguint_to_32bytes_le(v: BigUint) -> [u8; 32] { + let mut res = [0u8; 32]; + let v_le = v.to_bytes_le(); + res[..v_le.len()].copy_from_slice(&v_le); + res +} + +fn ct_option_ok_or(v: CtOption, err: E) -> Result { + Option::::from(v).ok_or(err) +} + +fn tx_to_sign_data(tx: &Transaction, chain_id: u64) -> Result { + let sig_r_le = tx.r.to_le_bytes(); + let sig_s_le = tx.s.to_le_bytes(); + let sig_r = + ct_option_ok_or(secp256k1::Fq::from_repr(sig_r_le), Error::Synthesis).map_err(|e| { + error!("Invalid 'r' signature value"); + e + })?; + let sig_s = + ct_option_ok_or(secp256k1::Fq::from_repr(sig_s_le), Error::Synthesis).map_err(|e| { + error!("Invalid 's' signature value"); + e + })?; + // msg = rlp([nonce, gasPrice, gas, to, value, data, sig_v, r, s]) + let mut stream = RlpStream::new_list(9); + stream + .append(&tx.nonce) + .append(&tx.gas_price) + .append(&tx.gas_limit) + .append(&tx.to.unwrap_or_else(Address::zero)) + .append(&tx.value) + .append(&tx.call_data.0) + .append(&chain_id) + .append(&0u32) + .append(&0u32); + let msg = stream.out(); + let msg_hash: [u8; 32] = Keccak256::digest(&msg) + .as_slice() + .to_vec() + .try_into() + .expect("hash length isn't 32 bytes"); + let v = (tx.v - 35 - chain_id * 2) as u8; + let pk = recover_pk(v, &tx.r, &tx.s, &msg_hash)?; + // msg_hash = msg_hash % q + let msg_hash = BigUint::from_bytes_be(msg_hash.as_slice()); + let msg_hash = msg_hash.mod_floor(&*SECP256K1_Q); + let msg_hash_le = biguint_to_32bytes_le(msg_hash); + let msg_hash = ct_option_ok_or(secp256k1::Fq::from_repr(msg_hash_le), Error::Synthesis) + .map_err(|e| { + error!("Invalid msg hash value"); + e + })?; + Ok(SignData { + signature: (sig_r, sig_s), + pk, + msg_hash, + }) +} + +// TODO: Deduplicate with +// `zkevm-circuits/src/evm_circuit/table.rs::TxContextFieldTag`. +/// Tag used to identify each field in the transaction in a row of the +/// transaction table. +#[derive(Clone, Copy, Debug)] +pub enum TxFieldTag { + /// Unused tag + Null = 0, + /// Nonce + Nonce, + /// Gas + Gas, + /// GasPrice + GasPrice, + /// CallerAddress + CallerAddress, + /// CalleeAddress + CalleeAddress, + /// IsCreate + IsCreate, + /// Value + Value, + /// CallDataLength + CallDataLength, + /// TxSignHash: Hash of the transaction without the signature, used for + /// signing. + TxSignHash, + /// CallData + CallData, +} + +#[derive(Clone, Debug)] +struct TxCircuitConfig { + tx_id: Column, + tag: Column, + index: Column, + value: Column, + sign_verify: SignVerifyConfig, + _marker: PhantomData, +} + +impl TxCircuitConfig { + fn new(meta: &mut ConstraintSystem) -> Self { + let tx_id = meta.advice_column(); + let tag = meta.advice_column(); + let index = meta.advice_column(); + let value = meta.advice_column(); + meta.enable_equality(value); + + // This gate is used just to get the array of expressions from the power of + // randomness instance column, so that later on we don't need to query + // columns everywhere, and can pass the power of randomness array + // expression everywhere. The gate itself doesn't add any constraints. + let power_of_randomness = { + let columns = [(); sign_verify::POW_RAND_SIZE].map(|_| meta.instance_column()); + let mut power_of_randomness = None; + + meta.create_gate("power of randomness", |meta| { + power_of_randomness = + Some(columns.map(|column| meta.query_instance(column, Rotation::cur()))); + + [0.expr()] + }); + + power_of_randomness.unwrap() + }; + let sign_verify = SignVerifyConfig::new(meta, power_of_randomness); + + Self { + tx_id, + tag, + index, + value, + sign_verify, + _marker: PhantomData, + } + } + + /// Assigns a tx circuit row and returns the assigned cell of the value in + /// the row. + fn assign_row( + &self, + region: &mut Region<'_, F>, + offset: usize, + tx_id: usize, + tag: TxFieldTag, + index: usize, + value: F, + ) -> Result, Error> { + region.assign_advice(|| "tx_id", self.tx_id, offset, || Ok(F::from(tx_id as u64)))?; + region.assign_advice(|| "tag", self.tag, offset, || Ok(F::from(tag as u64)))?; + region.assign_advice(|| "index", self.index, offset, || Ok(F::from(index as u64)))?; + region.assign_advice(|| "value", self.value, offset, || Ok(value)) + } +} + +#[derive(Default)] +struct TxCircuit { + sign_verify: SignVerifyChip, + randomness: F, + txs: Vec, + chain_id: u64, +} + +impl Circuit + for TxCircuit +{ + type Config = TxCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + TxCircuitConfig::new(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + assert!(self.txs.len() <= MAX_TXS); + let sign_datas: Vec = self + .txs + .iter() + .map(|tx| { + tx_to_sign_data(tx, self.chain_id).map_err(|e| { + error!("tx_to_sign_data error for tx {:?}", tx); + e + }) + }) + .try_collect()?; + let assigned_sig_verifs = self.sign_verify.assign( + &config.sign_verify, + &mut layouter, + self.randomness, + &sign_datas, + )?; + + layouter.assign_region( + || "tx table", + |mut region| { + let mut offset = 0; + // Empty entry + config.assign_row(&mut region, offset, 0, TxFieldTag::Null, 0, F::zero())?; + offset += 1; + // Assign al Tx fields except for call data + let tx_default = Transaction::default(); + // for i in 0..MAX_TXS + for (i, assigned_sig_verif) in assigned_sig_verifs.iter().enumerate() { + let tx = if i < self.txs.len() { + &self.txs[i] + } else { + &tx_default + }; + let address_cell = assigned_sig_verif.address.cell(); + let msg_hash_rlc_cell = assigned_sig_verif.msg_hash_rlc.cell(); + let msg_hash_rlc_value = assigned_sig_verif.msg_hash_rlc.value(); + for (tag, value) in &[ + ( + TxFieldTag::Nonce, + rlc(tx.nonce.to_le_bytes(), self.randomness), + ), + ( + TxFieldTag::Gas, + rlc(tx.gas_limit.to_le_bytes(), self.randomness), + ), + ( + TxFieldTag::GasPrice, + rlc(tx.gas_price.to_le_bytes(), self.randomness), + ), + ( + TxFieldTag::CallerAddress, + tx.from.to_scalar().expect("tx.from too big"), + ), + ( + TxFieldTag::CalleeAddress, + tx.to + .unwrap_or_else(Address::zero) + .to_scalar() + .expect("tx.to too big"), + ), + (TxFieldTag::IsCreate, F::from(tx.to.is_none() as u64)), + ( + TxFieldTag::Value, + rlc(tx.value.to_le_bytes(), self.randomness), + ), + ( + TxFieldTag::CallDataLength, + F::from(tx.call_data.0.len() as u64), + ), + ( + TxFieldTag::TxSignHash, + *msg_hash_rlc_value.unwrap_or(&F::zero()), + ), + ] { + let assigned_cell = + config.assign_row(&mut region, offset, i + 1, *tag, 0, *value)?; + offset += 1; + + // Ref. spec 0. Copy constraints using fixed offsets between the tx rows and + // the SignVerifyChip + match tag { + TxFieldTag::CallerAddress => { + region.constrain_equal(assigned_cell.cell(), address_cell)? + } + TxFieldTag::TxSignHash => { + region.constrain_equal(assigned_cell.cell(), msg_hash_rlc_cell)? + } + _ => (), + } + } + } + + // Assign call data + let mut calldata_count = 0; + for (i, tx) in self.txs.iter().enumerate() { + for (index, byte) in tx.call_data.0.iter().enumerate() { + assert!(calldata_count < MAX_CALLDATA); + config.assign_row( + &mut region, + offset, + i + 1, // tx_id + TxFieldTag::CallData, + index, + F::from(*byte as u64), + )?; + offset += 1; + calldata_count += 1; + } + } + for _ in calldata_count..MAX_CALLDATA { + config.assign_row( + &mut region, + offset, + 0, // tx_id + TxFieldTag::CallData, + 0, + F::zero(), + )?; + offset += 1; + } + Ok(()) + }, + )?; + Ok(()) + } +} + +#[cfg(test)] +mod tx_circuit_tests { + use super::*; + use eth_types::{address, word, Bytes}; + use ethers_core::{ + types::{NameOrAddress, TransactionRequest}, + utils::keccak256, + }; + use ethers_signers::{LocalWallet, Signer}; + use group::{Curve, Group}; + use halo2_proofs::{ + arithmetic::CurveAffine, + dev::{MockProver, VerifyFailure}, + pairing::bn256::Fr, + }; + use pretty_assertions::assert_eq; + use rand::{CryptoRng, Rng, SeedableRng}; + use rand_chacha::ChaCha20Rng; + + fn run( + k: u32, + txs: Vec, + chain_id: u64, + ) -> Result<(), Vec> { + let mut rng = ChaCha20Rng::seed_from_u64(2); + let aux_generator = + ::CurveExt::random(&mut rng).to_affine(); + + let randomness = F::random(&mut rng); + let mut power_of_randomness: Vec> = (1..POW_RAND_SIZE + 1) + .map(|exp| vec![randomness.pow(&[exp as u64, 0, 0, 0]); txs.len() * VERIF_HEIGHT]) + .collect(); + // SignVerifyChip -> ECDSAChip -> MainGate instance column + power_of_randomness.push(vec![]); + let circuit = TxCircuit:: { + sign_verify: SignVerifyChip { + aux_generator, + window_size: 2, + _marker: PhantomData, + }, + randomness, + txs, + chain_id, + }; + + let prover = match MockProver::run(k, &circuit, power_of_randomness) { + Ok(prover) => prover, + Err(e) => panic!("{:#?}", e), + }; + prover.verify() + } + + fn rand_tx(mut rng: R, chain_id: u64) -> Transaction { + let wallet0 = LocalWallet::new(&mut rng).with_chain_id(chain_id); + let wallet1 = LocalWallet::new(&mut rng).with_chain_id(chain_id); + let from = wallet0.address(); + let to = wallet1.address(); + let data = b"hello"; + let tx = TransactionRequest::new() + .from(from) + .to(to) + .nonce(3) + .value(1000) + .data(data) + .gas(500_000) + .gas_price(1234); + let tx_rlp = tx.rlp(chain_id); + let sighash = keccak256(tx_rlp.as_ref()).into(); + let sig = wallet0.sign_hash(sighash, true); + let to = tx.to.map(|to| match to { + NameOrAddress::Address(a) => a, + _ => unreachable!(), + }); + Transaction { + from: tx.from.unwrap(), + to, + gas_limit: tx.gas.unwrap(), + gas_price: tx.gas_price.unwrap(), + value: tx.value.unwrap(), + call_data: tx.data.unwrap(), + nonce: tx.nonce.unwrap(), + v: sig.v, + r: sig.r, + s: sig.s, + ..Transaction::default() + } + } + + // High memory usage test. Run in serial with: + // `cargo test [...] serial_ -- --ignored --test-threads 1` + #[ignore] + #[test] + fn serial_test_tx_circuit_rng() { + const NUM_TXS: usize = 2; + const MAX_TXS: usize = 2; + const MAX_CALLDATA: usize = 32; + + let mut rng = ChaCha20Rng::seed_from_u64(2); + let chain_id: u64 = 1337; + let mut txs = Vec::new(); + for _ in 0..NUM_TXS { + txs.push(rand_tx(&mut rng, chain_id)); + } + + let k = 19; + assert_eq!(run::(k, txs, chain_id), Ok(())); + } + + // High memory usage test. Run in serial with: + // `cargo test [...] serial_ -- --ignored --test-threads 1` + #[ignore] + #[test] + fn serial_test_tx_circuit_fixed() { + const MAX_TXS: usize = 1; + const MAX_CALLDATA: usize = 32; + + let chain_id: u64 = 1337; + // Transaction generated with `rand_tx` using `rng = + // ChaCha20Rng::seed_from_u64(42)` + let tx = Transaction { + from: address!("0x5f9b7e36af4ff81688f712fb738bbbc1b7348aae"), + to: Some(address!("0x701653d7ae8ddaa5c8cee1ee056849f271827926")), + nonce: word!("0x3"), + gas_limit: word!("0x7a120"), + value: word!("0x3e8"), + gas_price: word!("0x4d2"), + gas_fee_cap: word!("0x0"), + gas_tip_cap: word!("0x0"), + call_data: Bytes::from(b"hello"), + access_list: None, + v: 2710, + r: word!("0xaf180d27f90b2b20808bc7670ce0aca862bc2b5fa39c195ab7b1a96225ee14d7"), + s: word!("0x61159fa4664b698ea7d518526c96cd94cf4d8adf418000754be106a3a133f866"), + }; + + let k = 19; + assert_eq!( + run::(k, vec![tx], chain_id), + Ok(()) + ); + } + + // High memory usage test. Run in serial with: + // `cargo test [...] serial_ -- --ignored --test-threads 1` + #[ignore] + #[test] + fn serial_test_tx_circuit_bad_address() { + const MAX_TXS: usize = 1; + const MAX_CALLDATA: usize = 32; + + let chain_id: u64 = 1337; + let tx = Transaction { + // This address doesn't correspond to the account that signed this tx. + from: address!("0x1230000000000000000000000000000000000456"), + to: Some(address!("0x701653d7ae8ddaa5c8cee1ee056849f271827926")), + nonce: word!("0x3"), + gas_limit: word!("0x7a120"), + value: word!("0x3e8"), + gas_price: word!("0x4d2"), + gas_fee_cap: word!("0x0"), + gas_tip_cap: word!("0x0"), + call_data: Bytes::from(b"hello"), + access_list: None, + v: 2710, + r: word!("0xaf180d27f90b2b20808bc7670ce0aca862bc2b5fa39c195ab7b1a96225ee14d7"), + s: word!("0x61159fa4664b698ea7d518526c96cd94cf4d8adf418000754be106a3a133f866"), + }; + + let k = 19; + assert!(run::(k, vec![tx], chain_id).is_err(),); + } +} diff --git a/zkevm-circuits/src/tx_circuit/sign_verify.rs b/zkevm-circuits/src/tx_circuit/sign_verify.rs new file mode 100644 index 0000000000..c347ab423f --- /dev/null +++ b/zkevm-circuits/src/tx_circuit/sign_verify.rs @@ -0,0 +1,957 @@ +// Naming notes: +// - *_be: Big-Endian bytes +// - *_le: Little-Endian bytes + +use crate::{ + evm_circuit::util::{not, RandomLinearCombination, Word}, + util::Expr, +}; +use ecc::{EccConfig, GeneralEccChip}; +use ecdsa::ecdsa::{AssignedEcdsaSig, AssignedPublicKey, EcdsaChip}; +use gadgets::is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}; +use group::{ff::Field, prime::PrimeCurveAffine, Curve}; +use halo2_proofs::{ + arithmetic::{BaseExt, Coordinates, CurveAffine, FieldExt}, + circuit::{AssignedCell, Layouter, Region}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, + poly::Rotation, +}; +use integer::{ + AssignedInteger, IntegerChip, IntegerConfig, IntegerInstructions, WrongExt, + NUMBER_OF_LOOKUP_LIMBS, +}; +use itertools::Itertools; +use keccak256::plain::Keccak; +use lazy_static::lazy_static; +use log::error; +use maingate::{ + Assigned, AssignedValue, MainGate, MainGateConfig, MainGateInstructions, RangeChip, + RangeConfig, RangeInstructions, RegionCtx, UnassignedValue, +}; +use secp256k1::Secp256k1Affine; +use std::{ + convert::{TryFrom, TryInto}, + io::Cursor, + marker::PhantomData, +}; + +/// Power of randomness vector size required for the SignVerifyChip +pub const POW_RAND_SIZE: usize = 63; + +/// Number of rows required for a verification of the SignVerifyChip in the +/// "signature address verify" region. +pub const VERIF_HEIGHT: usize = 1; + +/// Auxiliary Gadget to verify a that a message hash is signed by the public +/// key corresponding to an Ethereum Address. +#[derive(Default, Debug)] +pub(crate) struct SignVerifyChip { + pub aux_generator: Secp256k1Affine, + pub window_size: usize, + pub _marker: PhantomData, +} + +const KECCAK_IS_ENABLED: usize = 0; +const KECCAK_INPUT_RLC: usize = 1; +const KECCAK_INPUT_LEN: usize = 2; +const KECCAK_OUTPUT_RLC: usize = 3; + +const NUMBER_OF_LIMBS: usize = 4; +const BIT_LEN_LIMB: usize = 72; + +/// Return a copy of the serialized public key with swapped Endianness. +pub(crate) fn pk_bytes_swap_endianness(pk: &[T]) -> [T; 64] { + assert_eq!(pk.len(), 64); + let mut pk_swap = <&[T; 64]>::try_from(pk) + .map(|r| r.clone()) + .expect("pk.len() != 64"); + pk_swap[..32].reverse(); + pk_swap[32..].reverse(); + pk_swap +} + +/// Return an expression that builds an integer element in the field from the +/// `bytes` in big endian. +fn int_from_bytes_be(bytes: &[Expression]) -> Expression { + // sum_{i = 0}^{N} bytes[i] * 256^i + let mut res = 0u8.expr(); + for (i, byte) in bytes.iter().rev().enumerate() { + res = res + byte.clone() * Expression::Constant(F::from(256).pow(&[i as u64, 0, 0, 0])) + } + res +} + +/// Constraint equality (using copy constraints) between `src` integer bytes and +/// `dst` integer bytes. Then assign the `dst` values from `src`. +fn copy_integer_bytes_le( + region: &mut Region<'_, F>, + name: &str, + src: &[AssignedValue; 32], + dst: &[Column; 32], + offset: usize, +) -> Result<(), Error> { + for (i, byte) in src.iter().enumerate() { + let assigned_cell = region.assign_advice( + || format!("{} byte {}", name, i), + dst[i], + offset, + || byte.value().ok_or(Error::Synthesis), + )?; + region.constrain_equal(assigned_cell.cell(), byte.cell())?; + } + Ok(()) +} + +#[derive(Debug, Clone)] +pub struct SignVerifyConfig { + q_enable: Selector, + pk_hash: [Column; 32], + // When address is 0, we disable the signature verification by using a dummy pk, msg_hash and + // signature which is not constrainted to match msg_hash_rlc nor the address. + address: Column, + address_is_zero: IsZeroConfig, + address_inv: Column, + msg_hash_rlc: Column, + + // ECDSA + main_gate_config: MainGateConfig, + range_config: RangeConfig, + // First 32 cells are coord x in little endian, following 32 cells are coord y in little + // endian. + pk: [[Column; 32]; 2], + msg_hash: [Column; 32], + power_of_randomness: [Expression; POW_RAND_SIZE], + + // [is_enabled, input_rlc, input_len, output_rlc] + keccak_table: [Column; 4], +} + +impl SignVerifyConfig { + pub fn new( + meta: &mut ConstraintSystem, + power_of_randomness: [Expression; POW_RAND_SIZE], + ) -> Self { + let q_enable = meta.complex_selector(); + + let pk = [(); 2].map(|_| [(); 32].map(|_| meta.advice_column())); + pk.iter() + .for_each(|coord| coord.iter().for_each(|c| meta.enable_equality(*c))); + + let msg_hash = [(); 32].map(|_| meta.advice_column()); + msg_hash.iter().for_each(|c| meta.enable_equality(*c)); + + let address = meta.advice_column(); + meta.enable_equality(address); + + let pk_hash = [(); 32].map(|_| meta.advice_column()); + + let msg_hash_rlc = meta.advice_column(); + meta.enable_equality(msg_hash_rlc); + + let address_inv = meta.advice_column(); + let address_is_zero = IsZeroChip::configure( + meta, + |meta| meta.query_selector(q_enable), + |meta| meta.query_advice(address, Rotation::cur()), + address_inv, + ); + // is_not_padding == address != 0 + let is_not_padding = not::expr(address_is_zero.is_zero_expression.clone()); + + // lookup keccak table + let keccak_table = [(); 4].map(|_| meta.advice_column()); + + // Ref. spec SignVerifyChip 1. Verify that keccak(pub_key_bytes) = pub_key_hash + // by keccak table lookup, where pub_key_bytes is built from the pub_key + // in the ecdsa_chip + // keccak lookup + meta.lookup_any("keccak", |meta| { + let q_enable = meta.query_selector(q_enable); + let selector = q_enable * is_not_padding.clone(); + let mut table_map = Vec::new(); + + // Column 0: is_enabled + let keccak_is_enabled = + meta.query_advice(keccak_table[KECCAK_IS_ENABLED], Rotation::cur()); + table_map.push((selector.clone(), keccak_is_enabled)); + + // Column 1: input_rlc (pk_rlc) + let keccak_input_rlc = + meta.query_advice(keccak_table[KECCAK_INPUT_RLC], Rotation::cur()); + let pk_le: [Expression; 64] = pk + .map(|coord| coord.map(|c| meta.query_advice(c, Rotation::cur()))) + .iter() + .flatten() + .cloned() + .collect::>>() + .try_into() + .expect("vector to array of size 64"); + let pk_be = pk_bytes_swap_endianness(&pk_le); + let pk_rlc = + RandomLinearCombination::random_linear_combine_expr(pk_be, &power_of_randomness); + table_map.push((selector.clone() * pk_rlc, keccak_input_rlc)); + + // Column 2: input_len (64) + let keccak_input_len = + meta.query_advice(keccak_table[KECCAK_INPUT_LEN], Rotation::cur()); + table_map.push((selector.clone() * 64usize.expr(), keccak_input_len)); + + // Column 3: output_rlc (pk_hash_rlc) + let keccak_output_rlc = + meta.query_advice(keccak_table[KECCAK_OUTPUT_RLC], Rotation::cur()); + let pk_hash = pk_hash.map(|c| meta.query_advice(c, Rotation::cur())); + let pk_hash_rlc = + RandomLinearCombination::random_linear_combine_expr(pk_hash, &power_of_randomness); + table_map.push((selector * pk_hash_rlc, keccak_output_rlc)); + + table_map + }); + + // Ref. spec SignVerifyChip 2. Verify that the first 20 bytes of the + // pub_key_hash equal the address + meta.create_gate("address is pk_hash[-20:]", |meta| { + let q_enable = meta.query_selector(q_enable); + let pk_hash = pk_hash.map(|c| meta.query_advice(c, Rotation::cur())); + let address = meta.query_advice(address, Rotation::cur()); + + let addr_from_pk = int_from_bytes_be(&pk_hash[32 - 20..]); + + vec![q_enable * (address - addr_from_pk)] + }); + + // Ref. spec SignVerifyChip 3. Verify that the signed message in the ecdsa_chip + // with RLC encoding corresponds to msg_hash_rlc + meta.create_gate("msg_hash_rlc = is_not_padding * RLC(msg_hash)", |meta| { + let q_enable = meta.query_selector(q_enable); + let msg_hash = msg_hash.map(|c| meta.query_advice(c, Rotation::cur())); + let msg_hash_rlc = meta.query_advice(msg_hash_rlc, Rotation::cur()); + + let expected_msg_hash_rlc = RandomLinearCombination::random_linear_combine_expr( + msg_hash, + &power_of_randomness[..32], + ); + vec![q_enable * (msg_hash_rlc - is_not_padding.clone() * expected_msg_hash_rlc)] + }); + + // ECDSA config + let (rns_base, rns_scalar) = + GeneralEccChip::::rns(); + let main_gate_config = MainGate::::configure(meta); + let mut overflow_bit_lengths: Vec = vec![]; + overflow_bit_lengths.extend(rns_base.overflow_lengths()); + overflow_bit_lengths.extend(rns_scalar.overflow_lengths()); + let range_config = RangeChip::::configure(meta, &main_gate_config, overflow_bit_lengths); + + Self { + q_enable, + pk_hash, + address, + msg_hash_rlc, + address_is_zero, + address_inv, + range_config, + main_gate_config, + pk, + msg_hash, + power_of_randomness, + keccak_table, + } + } +} + +pub struct KeccakAux { + input: [u8; 64], + output: [u8; 32], +} + +impl SignVerifyConfig { + pub fn load_range(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + let bit_len_lookup = BIT_LEN_LIMB / NUMBER_OF_LOOKUP_LIMBS; + let range_chip = RangeChip::::new(self.range_config.clone(), bit_len_lookup); + range_chip.load_limb_range_table(layouter)?; + range_chip.load_overflow_range_tables(layouter)?; + + Ok(()) + } + + fn keccak_assign_row( + &self, + region: &mut Region<'_, F>, + offset: usize, + is_enabled: F, + input_rlc: F, + input_len: usize, + output_rlc: F, + ) -> Result<(), Error> { + for (name, column, value) in &[ + ("is_enabled", self.keccak_table[0], is_enabled), + ("input_rlc", self.keccak_table[1], input_rlc), + ("input_len", self.keccak_table[2], F::from(input_len as u64)), + ("output_rlc", self.keccak_table[3], output_rlc), + ] { + region.assign_advice( + || format!("Keccak table assign {} {}", name, offset), + *column, + offset, + || Ok(*value), + )?; + } + Ok(()) + } + + pub fn load_keccak( + &self, + layouter: &mut impl Layouter, + auxs: Vec, + randomness: F, + ) -> Result<(), Error> { + layouter.assign_region( + || "keccak table", + |mut region| { + let mut offset = 0; + + // All zero row to allow simulating a disabled lookup. + self.keccak_assign_row(&mut region, offset, F::zero(), F::zero(), 0, F::zero())?; + offset += 1; + + for aux in &auxs { + let KeccakAux { input, output } = aux; + let input_rlc = + RandomLinearCombination::random_linear_combine(*input, randomness); + let output_rlc = Word::random_linear_combine(*output, randomness); + self.keccak_assign_row( + &mut region, + offset, + F::one(), + input_rlc, + input.len(), + output_rlc, + )?; + offset += 1; + } + Ok(()) + }, + )?; + Ok(()) + } + + pub fn ecc_chip_config(&self) -> EccConfig { + EccConfig::new(self.range_config.clone(), self.main_gate_config.clone()) + } + + pub fn integer_chip_config(&self) -> IntegerConfig { + IntegerConfig::new(self.range_config.clone(), self.main_gate_config.clone()) + } +} + +pub struct AssignedECDSA { + pk_x_le: [AssignedValue; 32], + pk_y_le: [AssignedValue; 32], + msg_hash_le: [AssignedValue; 32], +} + +#[derive(Debug)] +pub(crate) struct AssignedSignatureVerify { + pub(crate) address: AssignedCell, + pub(crate) msg_hash_rlc: AssignedCell, +} + +// Returns assigned constants [256^1, 256^2, .., 256^{n-1}] +fn assign_pows_256( + ctx: &mut RegionCtx<'_, '_, F>, + main_gate: &MainGate, + n: usize, +) -> Result>, Error> { + let mut pows = Vec::new(); + for i in 1..n { + pows.push(main_gate.assign_constant(ctx, F::from(256).pow(&[i as u64, 0, 0, 0]))?); + } + Ok(pows) +} + +// Return an array of bytes that corresponds to the little endian representation +// of the integer, adding the constraints to verify the correctness of the +// conversion (byte range check included). +fn integer_to_bytes_le( + ctx: &mut RegionCtx<'_, '_, F>, + main_gate: &MainGate, + range_chip: &RangeChip, + pows_256: &[AssignedValue], + int: &AssignedInteger, +) -> Result<[AssignedValue; 32], Error> { + let mut int_le = Vec::new(); + int_le.extend(int.limbs()[0].decompose(9, 8).expect("bad decompose")); + int_le.extend(int.limbs()[1].decompose(9, 8).expect("bad decompose")); + int_le.extend(int.limbs()[2].decompose(9, 8).expect("bad decompose")); + int_le.extend(int.limbs()[3].decompose(5, 8).expect("bad decompose")); + let int_le: Vec> = int_le + .iter() + .map(|b| range_chip.range_value(ctx, &UnassignedValue::from(Some(*b)), 8)) + .try_collect() + .map_err(|e| { + error!("RangeChip::range_value error: {:?}", e); + e + })?; + let int_le: [AssignedValue; 32] = int_le.try_into().expect("vec to array of size 32"); + for (j, positions) in [1..9, 1..9, 1..9, 1..5].iter().enumerate() { + let mut acc = int_le[j * 9]; + for i in positions.clone() { + let shifted = main_gate.mul(ctx, &int_le[j * 9 + i], &pows_256[i - 1])?; + acc = main_gate.add(ctx, &acc, &shifted)?; + } + main_gate.assert_equal(ctx, &acc, &(&int.limbs()[j]).into())?; + } + Ok(int_le) +} + +/// Helper structure pass around references to all the chips required for an +/// ECDSA veficication. +struct ChipsRef<'a, F: FieldExt, const NUMBER_OF_LIMBS: usize, const BIT_LEN_LIMB: usize> { + main_gate: &'a MainGate, + range_chip: &'a RangeChip, + ecc_chip: &'a GeneralEccChip, + scalar_chip: &'a IntegerChip, + ecdsa_chip: &'a EcdsaChip, +} + +impl SignVerifyChip { + fn assign_aux( + &self, + region: &mut Region<'_, F>, + ecc_chip: &mut GeneralEccChip, + ) -> Result<(), Error> { + let ctx_offset = &mut 0; + let ctx = &mut RegionCtx::new(region, ctx_offset); + + ecc_chip.assign_aux_generator(ctx, Some(self.aux_generator))?; + ecc_chip.assign_aux(ctx, self.window_size, 1)?; + Ok(()) + } + + fn assign_ecdsa( + &self, + ctx: &mut RegionCtx, + chips: &ChipsRef, + sign_data: &SignData, + ) -> Result, Error> { + let SignData { + signature, + pk, + msg_hash, + } = sign_data; + let (sig_r, sig_s) = signature; + + let ChipsRef { + main_gate, + range_chip, + ecc_chip, + scalar_chip, + ecdsa_chip, + } = chips; + + let integer_r = ecc_chip.new_unassigned_scalar(Some(*sig_r)); + let integer_s = ecc_chip.new_unassigned_scalar(Some(*sig_s)); + let msg_hash = ecc_chip.new_unassigned_scalar(Some(*msg_hash)); + + let r_assigned = scalar_chip.assign_integer(ctx, integer_r)?; + let s_assigned = scalar_chip.assign_integer(ctx, integer_s)?; + let sig = AssignedEcdsaSig { + r: r_assigned, + s: s_assigned, + }; + + let pk_in_circuit = ecc_chip.assign_point(ctx, Some(*pk))?; + let pk_assigned = AssignedPublicKey { + point: pk_in_circuit, + }; + let msg_hash = scalar_chip.assign_integer(ctx, msg_hash)?; + + // Convert (msg_hash, pk_x, pk_y) integers to little endian bytes + let pows_256 = assign_pows_256(ctx, main_gate, 9)?; + let msg_hash_le = integer_to_bytes_le(ctx, main_gate, range_chip, &pows_256, &msg_hash)?; + let pk_x = pk_assigned.point.get_x(); + let pk_x_le = integer_to_bytes_le(ctx, main_gate, range_chip, &pows_256, &pk_x)?; + let pk_y = pk_assigned.point.get_y(); + let pk_y_le = integer_to_bytes_le(ctx, main_gate, range_chip, &pows_256, &pk_y)?; + + // Ref. spec SignVerifyChip 4. Verify the ECDSA signature + ecdsa_chip.verify(ctx, &sig, &pk_assigned, &msg_hash)?; + + // TODO: Update once halo2wrong suports the following methods: + // - `IntegerChip::assign_integer_from_bytes_le` + // - `GeneralEccChip::assing_point_from_bytes_le` + + Ok(AssignedECDSA { + pk_x_le, + pk_y_le, + msg_hash_le, + }) + } + + #[allow(clippy::too_many_arguments)] + fn assign_signature_verify( + &self, + config: &SignVerifyConfig, + region: &mut Region<'_, F>, + offset: usize, + randomness: F, + address_is_zero_chip: &IsZeroChip, + sign_data: Option<&SignData>, + assigned_ecdsa: &AssignedECDSA, + ) -> Result<(AssignedSignatureVerify, KeccakAux), Error> { + let (padding, sign_data) = match sign_data { + Some(sign_data) => (false, sign_data.clone()), + None => (true, SignData::default()), + }; + let SignData { + signature: _, + pk, + msg_hash, + } = sign_data; + + // Ref. spec SignVerifyChip 0. Copy constraints between pub_key and msg_hash + // bytes of this chip and the ECDSA chip + copy_integer_bytes_le( + region, + "pk_x", + &assigned_ecdsa.pk_x_le, + &config.pk[0], + offset, + )?; + copy_integer_bytes_le( + region, + "pk_y", + &assigned_ecdsa.pk_y_le, + &config.pk[1], + offset, + )?; + copy_integer_bytes_le( + region, + "msg_hash", + &assigned_ecdsa.msg_hash_le, + &config.msg_hash, + offset, + )?; + + config.q_enable.enable(region, offset)?; + + // Assign msg_hash_rlc + let mut msg_hash_le = [0u8; 32]; + msg_hash + .write(&mut Cursor::new(&mut msg_hash_le[..])) + .expect("cannot write bytes to array"); + let msg_hash_rlc = Word::random_linear_combine(msg_hash_le, randomness); + let msg_hash_rlc = if !padding { msg_hash_rlc } else { F::zero() }; + let msg_hash_rlc_assigned = region.assign_advice( + || "msg_hash_rlc", + config.msg_hash_rlc, + offset, + || Ok(msg_hash_rlc), + )?; + + // Assign pk + let pk_coord = + Option::>::from(pk.coordinates()).expect("point is the identity"); + let mut pk_x_le = [0u8; 32]; + let mut pk_y_le = [0u8; 32]; + pk_coord + .x() + .write(&mut Cursor::new(&mut pk_x_le[..])) + .expect("cannot write bytes to array"); + pk_coord + .y() + .write(&mut Cursor::new(&mut pk_y_le[..])) + .expect("cannot write bytes to array"); + for (i, byte) in pk_x_le.iter().enumerate() { + region.assign_advice( + || format!("pk x byte {}", i), + config.pk[0][i], + offset, + || Ok(F::from(*byte as u64)), + )?; + } + for (i, byte) in pk_y_le.iter().enumerate() { + region.assign_advice( + || format!("pk y byte {}", i), + config.pk[1][i], + offset, + || Ok(F::from(*byte as u64)), + )?; + } + + let mut pk_le = [0u8; 64]; + pk_le[..32].copy_from_slice(&pk_x_le); + pk_le[32..].copy_from_slice(&pk_y_le); + let pk_be = pk_bytes_swap_endianness(&pk_le); + let mut keccak = Keccak::default(); + keccak.update(&pk_be); + let pk_hash = keccak.digest(); + let address = pub_key_hash_to_address(&pk_hash); + + // Assign pk_hash + let pk_hash = if !padding { pk_hash } else { vec![0u8; 32] }; + for (i, byte) in pk_hash.iter().enumerate() { + region.assign_advice( + || format!("pk_hash byte {}", i), + config.pk_hash[i], + offset, + || Ok(F::from(*byte as u64)), + )?; + } + + let address = if !padding { address } else { F::zero() }; + // Assign address and address_is_zero_chip + let address_assigned = + region.assign_advice(|| "address", config.address, offset, || Ok(address))?; + address_is_zero_chip.assign(region, offset, Some(address))?; + + // Assign msg_hash + for (i, byte) in msg_hash_le.iter().enumerate() { + region.assign_advice( + || format!("msg_hash byte {}", i), + config.msg_hash[i], + offset, + || Ok(F::from(*byte as u64)), + )?; + } + + Ok(( + AssignedSignatureVerify { + address: address_assigned, + msg_hash_rlc: msg_hash_rlc_assigned, + }, + KeccakAux { + input: pk_be, + output: pk_hash.try_into().expect("vec to array of size 32"), + }, + )) + } + + pub fn assign( + &self, + config: &SignVerifyConfig, + layouter: &mut impl Layouter, + randomness: F, + signatures: &[SignData], + ) -> Result>, Error> { + if signatures.len() > MAX_VERIF { + error!( + "signatures.len() = {} > MAX_VERIF = {}", + signatures.len(), + MAX_VERIF + ); + return Err(Error::Synthesis); + } + let main_gate = MainGate::new(config.main_gate_config.clone()); + // TODO: Figure out the best value for RangeChip base_bit_len, when we want to + // range on 8 bits. + let range_chip = RangeChip::new(config.range_config.clone(), 8); + let mut ecc_chip = GeneralEccChip::::new( + config.ecc_chip_config(), + ); + let scalar_chip = ecc_chip.scalar_field_chip(); + + layouter.assign_region( + || "ecc chip aux", + |mut region| self.assign_aux(&mut region, &mut ecc_chip), + )?; + + let ecdsa_chip = EcdsaChip::new(ecc_chip.clone()); + let address_is_zero_chip = IsZeroChip::construct(config.address_is_zero.clone()); + + let mut assigned_ecdsas = Vec::new(); + let mut keccak_auxs = Vec::new(); + + let chips = ChipsRef { + main_gate: &main_gate, + range_chip: &range_chip, + ecc_chip: &ecc_chip, + scalar_chip: &scalar_chip, + ecdsa_chip: &ecdsa_chip, + }; + + layouter.assign_region( + || "ecdsa chip verification", + |mut region| { + assigned_ecdsas.clear(); + keccak_auxs.clear(); + let offset = &mut 0; + let mut ctx = RegionCtx::new(&mut region, offset); + for i in 0..MAX_VERIF { + let signature = if i < signatures.len() { + signatures[i].clone() + } else { + // padding (enabled when address == 0) + SignData::default() + }; + let assigned_ecdsa = self.assign_ecdsa(&mut ctx, &chips, &signature)?; + assigned_ecdsas.push(assigned_ecdsa); + } + Ok(()) + }, + )?; + + let mut assigned_sig_verifs = Vec::new(); + layouter.assign_region( + || "signature address verify", + |mut region| { + assigned_sig_verifs.clear(); + // for i in 0..MAX_VERIF + for (i, assigned_ecdsa) in assigned_ecdsas.iter().enumerate() { + let sign_data = signatures.get(i); // None when padding (enabled when address == 0) + let (assigned_sig_verif, keccak_aux) = self.assign_signature_verify( + config, + &mut region, + i, // offset + randomness, + &address_is_zero_chip, + sign_data, + assigned_ecdsa, + )?; + if i < signatures.len() { + keccak_auxs.push(keccak_aux); + } + assigned_sig_verifs.push(assigned_sig_verif); + } + + Ok(()) + }, + )?; + + config.load_keccak(layouter, keccak_auxs, randomness)?; + config.load_range(layouter)?; + + Ok(assigned_sig_verifs) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct SignData { + pub(crate) signature: (secp256k1::Fq, secp256k1::Fq), + pub(crate) pk: Secp256k1Affine, + pub(crate) msg_hash: secp256k1::Fq, +} + +// Returns (r, s) +fn sign( + randomness: secp256k1::Fq, + sk: secp256k1::Fq, + msg_hash: secp256k1::Fq, +) -> (secp256k1::Fq, secp256k1::Fq) { + let randomness_inv = + Option::::from(randomness.invert()).expect("cannot invert randomness"); + let generator = Secp256k1Affine::generator(); + let sig_point = generator * randomness; + let x = *Option::>::from(sig_point.to_affine().coordinates()) + .expect("point is the identity") + .x(); + + let x_repr = &mut Vec::with_capacity(32); + x.write(x_repr).expect("cannot write bytes to array"); + + let mut x_bytes = [0u8; 64]; + x_bytes[..32].copy_from_slice(&x_repr[..]); + + let sig_r = secp256k1::Fq::from_bytes_wide(&x_bytes); // get x cordinate (E::Base) on E::Scalar + let sig_s = randomness_inv * (msg_hash + sig_r * sk); + (sig_r, sig_s) +} + +lazy_static! { + static ref SIGN_DATA_DEFAULT: SignData = { + let generator = Secp256k1Affine::generator(); + let sk = secp256k1::Fq::one(); + let pk = generator * sk; + let pk = pk.to_affine(); + let msg_hash = secp256k1::Fq::one(); + let randomness = secp256k1::Fq::one(); + let (sig_r, sig_s) = sign(randomness, sk, msg_hash); + + SignData { + signature: (sig_r, sig_s), + pk, + msg_hash, + } + }; +} + +impl Default for SignData { + fn default() -> Self { + // Hardcoded valid signature corresponding to a hardcoded private key and + // message hash generated from "nothing up my sleeve" values to make the + // ECDSA chip pass the constraints, to be use for padding signature + // verifications (where the constraints pass, but we don't care about the + // message hash and public key). + SIGN_DATA_DEFAULT.clone() + } +} + +fn pub_key_hash_to_address(pk_hash: &[u8]) -> F { + pk_hash[32 - 20..] + .iter() + .fold(F::zero(), |acc, b| acc * F::from(256) + F::from(*b as u64)) +} + +#[cfg(test)] +mod sign_verify_tests { + use super::*; + use group::Group; + use halo2_proofs::{ + circuit::SimpleFloorPlanner, dev::MockProver, pairing::bn256::Fr, plonk::Circuit, + }; + use pretty_assertions::assert_eq; + use rand::{RngCore, SeedableRng}; + use rand_xorshift::XorShiftRng; + + #[derive(Clone, Debug)] + struct TestCircuitSignVerifyConfig { + sign_verify: SignVerifyConfig, + } + + impl TestCircuitSignVerifyConfig { + pub fn new(meta: &mut ConstraintSystem) -> Self { + // This gate is used just to get the array of expressions from the power of + // randomness instance column, so that later on we don't need to query + // columns everywhere, and can pass the power of randomness array + // expression everywhere. The gate itself doesn't add any constraints. + let power_of_randomness = { + let columns = [(); POW_RAND_SIZE].map(|_| meta.instance_column()); + let mut power_of_randomness = None; + + meta.create_gate("power of randomness", |meta| { + power_of_randomness = + Some(columns.map(|column| meta.query_instance(column, Rotation::cur()))); + + [0.expr()] + }); + + power_of_randomness.unwrap() + }; + + let sign_verify = SignVerifyConfig::new(meta, power_of_randomness); + TestCircuitSignVerifyConfig { sign_verify } + } + } + + #[derive(Default)] + struct TestCircuitSignVerify { + sign_verify: SignVerifyChip, + randomness: F, + signatures: Vec, + } + + impl Circuit for TestCircuitSignVerify { + type Config = TestCircuitSignVerifyConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + TestCircuitSignVerifyConfig::new(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + self.sign_verify.assign( + &config.sign_verify, + &mut layouter, + self.randomness, + &self.signatures, + )?; + Ok(()) + } + } + + fn run(k: u32, signatures: Vec) { + let mut rng = XorShiftRng::seed_from_u64(2); + let aux_generator = + ::CurveExt::random(&mut rng).to_affine(); + + let randomness = F::random(&mut rng); + let mut power_of_randomness: Vec> = (1..POW_RAND_SIZE + 1) + .map(|exp| { + vec![randomness.pow(&[exp as u64, 0, 0, 0]); signatures.len() * VERIF_HEIGHT] + }) + .collect(); + // SignVerifyChip -> ECDSAChip -> MainGate instance column + power_of_randomness.push(vec![]); + let circuit = TestCircuitSignVerify:: { + sign_verify: SignVerifyChip { + aux_generator, + window_size: 2, + _marker: PhantomData, + }, + randomness, + signatures, + }; + + let prover = match MockProver::run(k, &circuit, power_of_randomness) { + Ok(prover) => prover, + Err(e) => panic!("{:#?}", e), + }; + assert_eq!(prover.verify(), Ok(())); + } + + // Generate a test key pair + fn gen_key_pair(rng: impl RngCore) -> (secp256k1::Fq, Secp256k1Affine) { + // generate a valid signature + let generator = ::generator(); + let sk = secp256k1::Fq::random(rng); + let pk = generator * sk; + let pk = pk.to_affine(); + + (sk, pk) + } + + // Generate a test message hash + fn gen_msg_hash(rng: impl RngCore) -> secp256k1::Fq { + secp256k1::Fq::random(rng) + } + + // Returns (r, s) + fn sign_with_rng( + rng: impl RngCore, + sk: secp256k1::Fq, + msg_hash: secp256k1::Fq, + ) -> (secp256k1::Fq, secp256k1::Fq) { + let randomness = secp256k1::Fq::random(rng); + sign(randomness, sk, msg_hash) + } + + // High memory usage test. Run in serial with: + // `cargo test [...] serial_ -- --ignored --test-threads 1` + #[ignore] + #[test] + fn serial_test_sign_verify() { + // Vectors using `XorShiftRng::seed_from_u64(1)` + // sk: 0x771bd7bf6c6414b9370bb8559d46e1cedb479b1836ea3c2e59a54c343b0d0495 + // pk: ( + // 0x8e31a3586d4c8de89d4e0131223ecfefa4eb76215f68a691ae607757d6256ede, + // 0xc76fdd462294a7eeb8ff3f0f698eb470f32085ba975801dbe446ed8e0b05400b + // ) + // pk_hash: d90e2e9d267cbcfd94de06fa7adbe6857c2c733025c0b8938a76beeefc85d6c7 + // addr: 0x7adbe6857c2c733025c0b8938a76beeefc85d6c7 + let mut rng = XorShiftRng::seed_from_u64(1); + const MAX_VERIF: usize = 3; + const NUM_SIGS: usize = 2; + let mut signatures = Vec::new(); + for _ in 0..NUM_SIGS { + let (sk, pk) = gen_key_pair(&mut rng); + let msg_hash = gen_msg_hash(&mut rng); + let sig = sign_with_rng(&mut rng, sk, msg_hash); + signatures.push(SignData { + signature: sig, + pk, + msg_hash, + }); + } + + let k = 19; + run::(k, signatures); + } +} diff --git a/zkevm-circuits/src/util.rs b/zkevm-circuits/src/util.rs index b06499361e..9ddcd5bbe0 100644 --- a/zkevm-circuits/src/util.rs +++ b/zkevm-circuits/src/util.rs @@ -1,6 +1,9 @@ //! Common utility traits and functions. use bus_mapping::operation::Target; -use eth_types::evm_types::{GasCost, OpcodeId}; +use eth_types::{ + evm_types::{GasCost, OpcodeId}, + Field, +}; use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; pub(crate) trait Expr { @@ -63,3 +66,7 @@ impl Expr for i32 { ) } } + +pub(crate) fn random_linear_combine_word(bytes: [u8; 32], randomness: F) -> F { + crate::evm_circuit::util::Word::random_linear_combine(bytes, randomness) +}