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)
+}