From 8d0fdeb24db87aa1de2e5944a07bac0a34f16fd6 Mon Sep 17 00:00:00 2001 From: Eduard S Date: Wed, 30 Mar 2022 11:23:35 +0200 Subject: [PATCH] WIP Tx Circuit Apply cargo fmt Make it compile Rework the tests Add copy constraints WIP Add integer copy constraints WIP Keccak lookup input is invalid OwO Keccak lookup works Verify address from pub_key_hash Add eq between ECDSA integers and bytes Constraint msg_hash_rlc = RLC(msg_hash) Add new region Get padding working, yes! WIP Get padding working Prepare for byte range check WIP: int to bytes le using main gate Get main gate integer to bytes working Power of randomness expr in circuit WIP tx to sign_data WIP ecdsa pk recovery So close Working tx circuit Little clean up Add comments from spec for constraints Test dev-graph Document and update halo2wrong dep Set halo2wrong deps to personal fork Fix rebase main issues Fix clippy complaints Co-authored-by: adria0 --- Cargo.lock | 386 ++++++- Cargo.toml | 3 + zkevm-circuits/Cargo.toml | 19 +- zkevm-circuits/src/evm_circuit/util.rs | 1 + zkevm-circuits/src/lib.rs | 1 + zkevm-circuits/src/tx_circuit.rs | 538 ++++++++++ zkevm-circuits/src/tx_circuit/sign_verify.rs | 994 +++++++++++++++++++ 7 files changed, 1916 insertions(+), 26 deletions(-) create mode 100644 zkevm-circuits/src/tx_circuit.rs create mode 100644 zkevm-circuits/src/tx_circuit/sign_verify.rs diff --git a/Cargo.lock b/Cargo.lock index 81fbfd184fa..40d1fc2cf54 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,8 +543,8 @@ dependencies = [ "bs58", "coins-core", "digest 0.9.0", - "hmac", - "k256", + "hmac 0.11.0", + "k256 0.9.6", "lazy_static", "serde", "sha2 0.9.9", @@ -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,19 @@ checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" dependencies = [ "der 0.4.5", "elliptic-curve 0.10.6", - "hmac", + "hmac 0.11.0", + "signature", +] + +[[package]] +name = "ecdsa" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der 0.5.1", + "elliptic-curve 0.11.12", + "rfc6979", "signature", ] @@ -1035,7 +1124,7 @@ dependencies = [ "ff 0.10.1", "generic-array 0.14.5", "group 0.10.0", - "pkcs8", + "pkcs8 0.7.6", "rand_core", "subtle", "zeroize", @@ -1050,8 +1139,11 @@ dependencies = [ "base16ct", "crypto-bigint 0.3.2", "der 0.5.1", + "ff 0.11.1", "generic-array 0.14.5", + "group 0.11.0", "rand_core", + "sec1", "subtle", "zeroize", ] @@ -1088,7 +1180,7 @@ dependencies = [ "ctr", "digest 0.9.0", "hex", - "hmac", + "hmac 0.11.0", "pbkdf2", "rand", "scrypt", @@ -1106,7 +1198,7 @@ version = "0.1.0" dependencies = [ "ethers-core", "ethers-providers", - "halo2_proofs", + "halo2_proofs 0.1.0-beta.1", "hex", "lazy_static", "regex", @@ -1235,16 +1327,16 @@ 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", "hex", - "k256", + "k256 0.9.6", "once_cell", "proc-macro2", "quote", @@ -1633,7 +1725,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", @@ -1731,7 +1823,9 @@ checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "byteorder", "ff 0.11.1", + "rand", "rand_core", + "rand_xorshift", "subtle", ] @@ -1765,7 +1859,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", @@ -1779,6 +1873,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" @@ -1806,6 +1927,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" @@ -1816,6 +1947,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" @@ -2008,6 +2150,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" @@ -2017,7 +2175,7 @@ dependencies = [ "eth-types", "ethers", "ff 0.11.1", - "halo2_proofs", + "halo2_proofs 0.1.0-beta.1", "lazy_static", "log", "pretty_assertions", @@ -2077,12 +2235,25 @@ 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", ] +[[package]] +name = "k256" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +dependencies = [ + "cfg-if 1.0.0", + "ecdsa 0.13.4", + "elliptic-curve 0.11.12", + "sec1", + "sha2 0.9.9", +] + [[package]] name = "keccak" version = "0.1.0" @@ -2094,7 +2265,7 @@ name = "keccak256" version = "0.1.0" dependencies = [ "eth-types", - "halo2_proofs", + "halo2_proofs 0.1.0-beta.1", "itertools", "lazy_static", "num-bigint", @@ -2115,6 +2286,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" @@ -2134,6 +2353,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" @@ -2265,6 +2499,7 @@ dependencies = [ "autocfg", "num-integer", "num-traits", + "rand", ] [[package]] @@ -2436,7 +2671,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", @@ -2492,6 +2727,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" @@ -2525,7 +2775,7 @@ checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" dependencies = [ "base64ct", "crypto-mac 0.11.1", - "hmac", + "hmac 0.11.0", "password-hash", "sha2 0.9.9", ] @@ -2594,7 +2844,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" dependencies = [ "der 0.4.5", - "spki", + "spki 0.4.1", +] + +[[package]] +name = "pkcs8" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +dependencies = [ + "der 0.5.1", + "spki 0.5.4", + "zeroize", ] [[package]] @@ -2743,7 +3004,7 @@ dependencies = [ "env_logger", "eth-types", "ethers-providers", - "halo2_proofs", + "halo2_proofs 0.1.0-beta.1", "hyper", "log", "rand", @@ -2932,6 +3193,17 @@ dependencies = [ "winreg", ] +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint 0.3.2", + "hmac 0.11.0", + "zeroize", +] + [[package]] name = "ring" version = "0.16.20" @@ -3102,7 +3374,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879588d8f90906e73302547e20fffefdd240eb3e0e744e142321f5d49dea0518" dependencies = [ "base64ct", - "hmac", + "hmac 0.11.0", "password-hash", "pbkdf2", "salsa20", @@ -3129,6 +3401,37 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der 0.5.1", + "generic-array 0.14.5", + "pkcs8 0.8.0", + "subtle", + "zeroize", +] + +[[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" @@ -3341,6 +3644,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" @@ -3388,6 +3701,16 @@ dependencies = [ "der 0.4.5", ] +[[package]] +name = "spki" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +dependencies = [ + "base64ct", + "der 0.5.1", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -4110,24 +4433,39 @@ 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", + "generic-array 0.12.4", + "group 0.11.0", + "halo2_proofs 0.1.0-beta.1", "hex", + "integer", "itertools", + "k256 0.10.4", "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/Cargo.toml b/Cargo.toml index ac0189884bd..72def5ac020 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,9 @@ members = [ # is resolved: https://github.com/bitvecto-rs/bitvec/pull/141 bitvec = { git = "https://github.com/ed255/bitvec.git", rev = "5cfc5fa8496c66872d21905e677120fc3e79693c" } +# [patch."https://github.com/appliedzkp/halo2.git"] +# halo2_proofs = { path = "../halo2/halo2_proofs" } + # Definition of benchmarks profile to use. [profile.bench] opt-level = 3 diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index 10a1ded7903..4d48ad5bb45 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,20 @@ 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" +k256 = "0.10.4" +generic-array = "0.12.4" +libsecp256k1 = "0.7" +rlp = "0.5" +num-bigint = { version = "0.4" } +subtle = "2.4" [dev-dependencies] criterion = "0.3" @@ -37,6 +49,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/util.rs b/zkevm-circuits/src/evm_circuit/util.rs index c1345c43a0c..a173248be17 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 a40a1482f2c..ddb88f9d0f8 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 00000000000..9892775f5df --- /dev/null +++ b/zkevm-circuits/src/tx_circuit.rs @@ -0,0 +1,538 @@ +//! The transaction circuit implementation. + +mod sign_verify; + +use crate::util::Expr; +use eth_types::{Address, Bytes, Field, ToBigEndian, ToLittleEndian, ToScalar, Word, U256}; +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 k256::elliptic_curve::generic_array::{typenum::consts::U32, GenericArray}; +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::{SignData, SignVerifyChip, SignVerifyConfig}; +pub use sign_verify::{POW_RAND_SIZE, VERIF_HEIGHT}; +use std::marker::PhantomData; +use subtle::CtOption; + +lazy_static! { + // Scalar + static ref SECP256K1_Q: BigUint = BigUint::from_slice(&[ + 0xd0364141, 0xbfd25e8c, + 0xaf48a03b, 0xbaaedce6, + 0xfffffffe, 0xffffffff, + 0xffffffff, 0xffffffff, + ]); +} + +/// Transaction to be verified by the TxCircuit +#[derive(Clone, Default, Debug)] +pub struct Transaction { + /// Sender address + pub from: Address, + + /// Recipient address (None for contract creation) + pub to: Option
, + + /// Supplied gas + pub gas: U256, + + /// Gas price + pub gas_price: U256, + + /// Transfered value (None for no transfer) + pub value: U256, + + /// 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 data: Bytes, + + /// Transaction nonce + pub nonce: U256, + + /// "v" value of the transaction signature + pub v: u64, + /// "r" value of the transaction signature + pub r: U256, + /// "s" value of the transaction signature + pub s: U256, +} + +fn random_linear_combine(bytes: [u8; 32], randomness: F) -> F { + crate::evm_circuit::util::Word::random_linear_combine(bytes, randomness) +} + +fn recover_pk( + v: u8, + r: &Word, + s: &Word, + msg_hash: &GenericArray, +) -> Result { + let r_be = r.to_be_bytes(); + let s_be = s.to_be_bytes(); + let mut r = libsecp256k1::curve::Scalar::from_int(0); + let r_overflow: bool = r.set_b32(&r_be).into(); + let mut s = libsecp256k1::curve::Scalar::from_int(0); + let s_overflow: bool = s.set_b32(&s_be).into(); + if r_overflow || s_overflow { + error!("Overflow on 'r' or 's' signature values"); + return Err(Error::Synthesis); + } + let signature = libsecp256k1::Signature { r, s }; + 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 mut pk_le = [0u8; 64]; + pk_le.copy_from_slice(&pk_be[1..]); + pk_le[..32].reverse(); + pk_le[32..].reverse(); + 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) + .append(&tx.to.unwrap_or_else(Address::zero)) + .append(&tx.value) + .append(&tx.data.0) + .append(&chain_id) + .append(&0u32) + .append(&0u32); + let msg = stream.out(); + let msg_hash = Keccak256::digest(&msg); + 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); + + 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, + } + } +} + +#[derive(Default)] +struct TxCircuit { + sign_verify: SignVerifyChip, + randomness: F, + txs: Vec, + chain_id: u64, +} + +/// Assigns a tx circuit row and returns the assigned cell of the value in +/// the row. +fn assign_row( + region: &mut Region<'_, F>, + config: &TxCircuitConfig, + offset: usize, + tx_id: usize, + tag: TxFieldTag, + index: usize, + value: F, +) -> Result, Error> { + region.assign_advice( + || "tx_id", + config.tx_id, + offset, + || Ok(F::from(tx_id as u64)), + )?; + region.assign_advice(|| "tag", config.tag, offset, || Ok(F::from(tag as u64)))?; + region.assign_advice( + || "index", + config.index, + offset, + || Ok(F::from(index as u64)), + )?; + region.assign_advice(|| "value", config.value, offset, || Ok(value)) +} + +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_txs( + &config.sign_verify, + &mut layouter, + self.randomness, + &sign_datas, + )?; + + layouter.assign_region( + || "tx table", + |mut region| { + let mut offset = 0; + // Empty entry + assign_row( + &mut region, + &config, + 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, + random_linear_combine(tx.nonce.to_le_bytes(), self.randomness), + ), + ( + TxFieldTag::Gas, + random_linear_combine(tx.gas.to_le_bytes(), self.randomness), + ), + ( + TxFieldTag::GasPrice, + random_linear_combine(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, + random_linear_combine(tx.value.to_le_bytes(), self.randomness), + ), + (TxFieldTag::CallDataLength, F::from(tx.data.0.len() as u64)), + ( + TxFieldTag::TxSignHash, + *msg_hash_rlc_value.unwrap_or(&F::zero()), + ), + ] { + let assigned_cell = + assign_row(&mut region, &config, 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.data.0.iter().enumerate() { + assert!(calldata_count < MAX_CALLDATA); + assign_row( + &mut region, + &config, + offset, + i + 1, // tx_id + TxFieldTag::CallData, + index, + F::from(*byte as u64), + )?; + offset += 1; + calldata_count += 1; + } + } + for _ in calldata_count..MAX_CALLDATA { + assign_row( + &mut region, + &config, + offset, + 0, // tx_id + TxFieldTag::CallData, + 0, + F::zero(), + )?; + offset += 1; + } + Ok(()) + }, + )?; + Ok(()) + } +} + +#[cfg(test)] +mod tx_circuit_tests { + use super::*; + use ethers_core::{ + types::{NameOrAddress, TransactionRequest}, + utils::keccak256, + }; + use ethers_signers::{LocalWallet, Signer}; + use group::{Curve, Group}; + use halo2_proofs::{arithmetic::CurveAffine, dev::MockProver, 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, + ) { + 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), + }; + assert_eq!(prover.verify(), Ok(())); + } + + 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: tx.gas.unwrap(), + gas_price: tx.gas_price.unwrap(), + value: tx.value.unwrap(), + data: tx.data.unwrap(), + nonce: tx.nonce.unwrap(), + v: sig.v, + r: sig.r, + s: sig.s, + } + } + + #[test] + fn test_tx_circuit() { + 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; + run::(k, txs, chain_id); + } +} 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 00000000000..a1582444dde --- /dev/null +++ b/zkevm-circuits/src/tx_circuit/sign_verify.rs @@ -0,0 +1,994 @@ +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::{cmp::min, convert::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 an expression that builds an integer element in the field from the +/// `bytes` in little endian. +fn int_from_bytes_le<'a, F: FieldExt>( + bytes: impl IntoIterator>, +) -> Expression { + // sum_{i = 0}^{N} bytes[i] * 256^i + let mut res = 0u8.expr(); + for (i, byte) in bytes.into_iter().enumerate() { + res = res + byte.clone() * Expression::Constant(F::from(256).pow(&[i as u64, 0, 0, 0])) + } + res +} + +/// Return a list of expression that evaluate to 0 when the `bytes` are a little +/// endian representation of the integer split into `limbs`. Assumes `limbs` +/// are 72 bits (9 bytes). +fn integer_eq_bytes_le( + limbs: &[Expression; 4], + bytes: &[Expression; 32], +) -> Vec> { + let mut res = Vec::new(); + for (j, limb) in limbs.iter().enumerate() { + let limb_bytes = &bytes[j * 9..min((j + 1) * 9, bytes.len())]; + let limb_exp = int_from_bytes_le(limb_bytes); + res.push(limb.clone() - limb_exp); + } + res +} + +/// Enable copy constraint between `src` integer limbs and `dst` limbs. Then +/// assign the `dst` limbs values from `src`. +fn copy_integer( + region: &mut Region<'_, F>, + name: &str, + src: AssignedInteger, + dst: &[Column; 4], + offset: usize, +) -> Result<(), Error> { + for (i, limb) in src.limbs().iter().enumerate() { + let assigned_cell = region.assign_advice( + || format!("{} limb {}", name, i), + dst[i], + offset, + || limb.value().ok_or(Error::Synthesis), + )?; + region.constrain_equal(assigned_cell.cell(), limb.cell())?; + } + Ok(()) +} + +/// Enable 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(()) +} + +fn assign_integer_bytes_le( + region: &mut Region<'_, F>, + name: &str, + src: W, + dst: &[Column], + offset: usize, +) -> Result<(), Error> { + let mut src_le = [0u8; 32]; + src.write(&mut Cursor::new(&mut src_le[..])) + .expect("cannot write bytes to array"); + for (i, byte) in src_le.iter().enumerate() { + region.assign_advice( + || format!("{} byte {}", name, i), + dst[i], + offset, + || Ok(F::from(*byte as u64)), + )?; + } + 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.map(|coord| coord.map(|c| meta.enable_equality(c))); + + let msg_hash = [(); 32].map(|_| meta.advice_column()); + msg_hash.map(|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); + + // is_not_padding == address != 0 + + 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, + ); + 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 mut pk_be: [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 mut pk_be: [_; 64] = (0..64)pk[0] + pk[1]; + pk_be[..32].reverse(); + pk_be[32..].reverse(); + 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_le(pk_hash[32 - 20..].iter().rev()); + + 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) +} + +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, + // main_gate: &MainGate, + // range_chip: &RangeChip, + // ecc_chip: &GeneralEccChip, + // scalar_chip: &IntegerChip, + // ecdsa_chip: &EcdsaChip, + 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_x_be = pk_x_le; + pk_x_be.reverse(); + let mut pk_y_be = pk_y_le; + pk_y_be.reverse(); + let mut pk_bytes_be = [0u8; 64]; + pk_bytes_be[..32].copy_from_slice(&pk_x_be); + pk_bytes_be[32..].copy_from_slice(&pk_y_be); + let mut keccak = Keccak::default(); + keccak.update(&pk_bytes_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_bytes_be, + output: pk_hash.try_into().expect("vec to array of size 32"), + }, + )) + } + + pub fn assign_txs( + &self, + config: &SignVerifyConfig, + layouter: &mut impl Layouter, + randomness: F, + txs: &[SignData], + ) -> Result>, Error> { + if txs.len() > MAX_VERIF { + panic!("txs.len() = {} > MAX_VERIF = {}", txs.len(), MAX_VERIF); + } + 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 tx = if i < txs.len() { + txs[i].clone() + } else { + // padding (enabled when address == 0) + SignData::default() + }; + let assigned_ecdsa = self.assign_ecdsa(&mut ctx, &chips, &tx)?; + 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 = txs.get(i); // None when padding (enabled when address == 0) + let offset = i; + let (assigned_sig_verif, keccak_aux) = self.assign_signature_verify( + config, + &mut region, + offset, + randomness, + &address_is_zero_chip, + sign_data, + assigned_ecdsa, + )?; + if i < txs.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 { + 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, + txs: 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_txs( + &config.sign_verify, + &mut layouter, + self.randomness, + &self.txs, + )?; + Ok(()) + } + } + + fn run(k: u32, txs: 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]); txs.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, + txs, + }; + + 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 = ::ScalarExt::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 { + ::ScalarExt::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) + } + + #[test] + fn test_sign_verify() { + let mut rng = XorShiftRng::seed_from_u64(1); + const MAX_VERIF: usize = 4; + const NUM_TXS: usize = 3; + let mut txs = Vec::new(); + for _ in 0..NUM_TXS { + 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); + txs.push(SignData { + signature: sig, + pk, + msg_hash, + }); + } + + let k = 20; + run::(k, txs); + } +} + +// Vectors using `XorShiftRng::seed_from_u64(1)` +// sk: 0x771bd7bf6c6414b9370bb8559d46e1cedb479b1836ea3c2e59a54c343b0d0495 +// pk: ( +// 0x8e31a3586d4c8de89d4e0131223ecfefa4eb76215f68a691ae607757d6256ede, +// 0xc76fdd462294a7eeb8ff3f0f698eb470f32085ba975801dbe446ed8e0b05400b +// ) +// pk_hash: d90e2e9d267cbcfd94de06fa7adbe6857c2c733025c0b8938a76beeefc85d6c7 +// addr: 0x7adbe6857c2c733025c0b8938a76beeefc85d6c7