diff --git a/Cargo.lock b/Cargo.lock index 0b57be2f5a..e131d307eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,7 +43,7 @@ name = "aggregator" version = "0.1.0" dependencies = [ "ark-std 0.3.0", - "env_logger 0.10.0", + "env_logger", "eth-types", "ethers-core", "halo2_proofs", @@ -484,7 +484,7 @@ name = "bus-mapping" version = "0.1.0" dependencies = [ "ctor", - "env_logger 0.10.0", + "env_logger", "eth-types", "ethers-core", "ethers-providers", @@ -649,7 +649,7 @@ version = "0.1.0" dependencies = [ "ark-std 0.3.0", "bus-mapping", - "env_logger 0.10.0", + "env_logger", "eth-types", "ethers", "ethers-signers", @@ -1454,19 +1454,6 @@ dependencies = [ "syn 2.0.32", ] -[[package]] -name = "env_logger" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - [[package]] name = "env_logger" version = "0.10.0" @@ -2215,7 +2202,7 @@ dependencies = [ name = "geth-utils" version = "0.1.0" dependencies = [ - "env_logger 0.10.0", + "env_logger", "gobuild", "log", ] @@ -2414,16 +2401,32 @@ dependencies = [ "thiserror", ] +[[package]] +name = "halo2_gadgets" +version = "0.2.0" +source = "git+https://github.com/scroll-tech/halo2.git?branch=develop#5b9a3d71a325a9ecbad164aba90a7f6a8550a015" +dependencies = [ + "arrayvec", + "bitvec", + "ff 0.12.1", + "group 0.12.1", + "halo2_proofs", + "halo2curves", + "lazy_static", + "rand", + "subtle", + "uint", +] + [[package]] name = "halo2_proofs" version = "0.2.0" -source = "git+https://github.com/scroll-tech/halo2.git?branch=develop#e3fe25eadd714fd991f35190d17ff0b8fb031188" +source = "git+https://github.com/scroll-tech/halo2.git?branch=develop#5b9a3d71a325a9ecbad164aba90a7f6a8550a015" dependencies = [ "ark-std 0.3.0", "blake2b_simd", "cfg-if 0.1.10", "crossbeam", - "env_logger 0.8.4", "ff 0.12.1", "group 0.12.1", "halo2curves", @@ -2773,7 +2776,7 @@ name = "integration-tests" version = "0.1.0" dependencies = [ "bus-mapping", - "env_logger 0.10.0", + "env_logger", "eth-types", "ethers", "halo2_proofs", @@ -2899,7 +2902,7 @@ dependencies = [ name = "keccak256" version = "0.1.0" dependencies = [ - "env_logger 0.10.0", + "env_logger", "eth-types", "halo2_proofs", "itertools 0.11.0", @@ -3112,7 +3115,7 @@ dependencies = [ name = "mpt-zktrie" version = "0.1.0" dependencies = [ - "env_logger 0.10.0", + "env_logger", "eth-types", "halo2-mpt-circuits", "halo2_proofs", @@ -4712,7 +4715,7 @@ version = "0.0.1" source = "git+https://github.com/scroll-tech/snark-verifier?tag=v0.1.5#bc1d39ae31f3fe520c51dd150f0fefaf9653c465" dependencies = [ "bincode", - "env_logger 0.10.0", + "env_logger", "ethereum-types", "halo2-base", "hex", @@ -4983,7 +4986,7 @@ dependencies = [ "bus-mapping", "clap 3.2.25", "ctor", - "env_logger 0.10.0", + "env_logger", "eth-types", "ethers-core", "ethers-signers", @@ -5787,13 +5790,14 @@ dependencies = [ "criterion", "ctor", "either", - "env_logger 0.10.0", + "env_logger", "eth-types", "ethers-core", "ethers-signers", "gadgets", "halo2-base", "halo2-ecc", + "halo2_gadgets", "halo2_proofs", "hex", "itertools 0.11.0", diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index 102930d256..8c45839a7b 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -38,7 +38,7 @@ use ethers_providers::JsonRpcClient; pub use execution::{ BigModExp, CopyBytes, CopyDataType, CopyEvent, CopyEventStepsBuilder, CopyStep, EcAddOp, EcMulOp, EcPairingOp, EcPairingPair, ExecState, ExecStep, ExpEvent, ExpStep, NumberOrHash, - PrecompileEvent, PrecompileEvents, N_BYTES_PER_PAIR, N_PAIRING_PER_OP, + PrecompileEvent, PrecompileEvents, N_BYTES_PER_PAIR, N_PAIRING_PER_OP, SHA256, }; use hex::decode_to_slice; diff --git a/bus-mapping/src/circuit_input_builder/execution.rs b/bus-mapping/src/circuit_input_builder/execution.rs index 58eef4ed68..4f2c5ed649 100644 --- a/bus-mapping/src/circuit_input_builder/execution.rs +++ b/bus-mapping/src/circuit_input_builder/execution.rs @@ -918,6 +918,20 @@ impl PrecompileEvents { .cloned() .collect() } + /// Get all SHA256 events. + pub fn get_sha256_events(&self) -> Vec { + self.events + .iter() + .filter_map(|e| { + if let PrecompileEvent::SHA256(op) = e { + Some(op) + } else { + None + } + }) + .cloned() + .collect() + } } /// I/O from a precompiled contract call. @@ -933,6 +947,8 @@ pub enum PrecompileEvent { EcPairing(Box), /// Represents the I/O from Modexp call. ModExp(BigModExp), + /// Represents the I/O from SHA256 call. + SHA256(SHA256), } impl Default for PrecompileEvent { @@ -1379,3 +1395,12 @@ impl Default for BigModExp { } } } + +/// Event representating an SHA256 hash in precompile sha256. +#[derive(Clone, Debug, Default)] +pub struct SHA256 { + /// input bytes + pub input: Vec, + /// digest + pub digest: [u8; 32], +} diff --git a/bus-mapping/src/evm/opcodes/callop.rs b/bus-mapping/src/evm/opcodes/callop.rs index 642ec094cd..61927d4092 100644 --- a/bus-mapping/src/evm/opcodes/callop.rs +++ b/bus-mapping/src/evm/opcodes/callop.rs @@ -753,9 +753,6 @@ pub mod tests { address: Word::from(0x2), stack_value: vec![( Word::from(0x20), - #[cfg(feature = "scroll")] - Word::zero(), - #[cfg(not(feature = "scroll"))] word!("a8100ae6aa1940d0b663bb31cd466142ebbdbd5187131b92d93818987832eb89"), )], ..Default::default() diff --git a/bus-mapping/src/evm/opcodes/precompiles/mod.rs b/bus-mapping/src/evm/opcodes/precompiles/mod.rs index b74a18de36..a59571f338 100644 --- a/bus-mapping/src/evm/opcodes/precompiles/mod.rs +++ b/bus-mapping/src/evm/opcodes/precompiles/mod.rs @@ -1,7 +1,9 @@ use eth_types::{GethExecStep, ToWord, Word}; use crate::{ - circuit_input_builder::{Call, CircuitInputStateRef, ExecState, ExecStep}, + circuit_input_builder::{ + Call, CircuitInputStateRef, ExecState, ExecStep, PrecompileEvent, SHA256, + }, operation::CallContextField, precompile::{PrecompileAuxData, PrecompileCalls}, Error, @@ -50,6 +52,23 @@ pub fn gen_associated_ops( return_bytes: return_bytes.to_vec(), }), ), + PrecompileCalls::Sha256 => ( + if output_bytes.is_empty() { + None + } else { + Some(PrecompileEvent::SHA256(SHA256 { + input: input_bytes.to_vec(), + digest: output_bytes + .try_into() + .expect("output bytes must be 32 bytes"), + })) + }, + Some(PrecompileAuxData::SHA256 { + input_bytes: input_bytes.to_vec(), + output_bytes: output_bytes.to_vec(), + return_bytes: return_bytes.to_vec(), + }), + ), _ => { log::warn!("precompile {:?} unsupported in circuits", precompile); ( diff --git a/bus-mapping/src/precompile.rs b/bus-mapping/src/precompile.rs index 80839d5a8b..aae4af3477 100644 --- a/bus-mapping/src/precompile.rs +++ b/bus-mapping/src/precompile.rs @@ -33,9 +33,9 @@ pub(crate) fn execute_precompiled( // Revm behavior is different from scroll evm, // so we need to override the behavior of invalid input match PrecompileCalls::from(address.0[19]) { - PrecompileCalls::Blake2F - | PrecompileCalls::Sha256 - | PrecompileCalls::Ripemd160 => (vec![], gas, false, false), + PrecompileCalls::Blake2F | PrecompileCalls::Ripemd160 => { + (vec![], gas, false, false) + } PrecompileCalls::Bn128Pairing => { if input.len() > N_PAIRING_PER_OP * N_BYTES_PER_PAIR { (vec![], gas, false, false) @@ -455,6 +455,15 @@ pub enum PrecompileAuxData { /// bytes returned back to the caller from the identity call. return_bytes: Vec, }, + /// SHA256 + SHA256 { + /// input bytes to the sha256 call. + input_bytes: Vec, + /// output bytes from the sha256 call. + output_bytes: Vec, + /// bytes returned back to the caller from the sha256 call. + return_bytes: Vec, + }, /// Ecrecover. Ecrecover(EcrecoverAuxData), /// Modexp. diff --git a/geth-utils/l2geth/go.mod b/geth-utils/l2geth/go.mod index f261fd4b5e..940bc9bebf 100644 --- a/geth-utils/l2geth/go.mod +++ b/geth-utils/l2geth/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/imdario/mergo v0.3.16 - github.com/scroll-tech/go-ethereum v1.10.14-0.20230919024151-fa0be69a3fb9 + github.com/scroll-tech/go-ethereum v1.10.14-0.20231123003536-35313dc92055 ) require ( @@ -36,7 +36,7 @@ require ( github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect - golang.org/x/crypto v0.12.0 // indirect - golang.org/x/sys v0.11.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/sys v0.13.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect ) diff --git a/geth-utils/l2geth/go.sum b/geth-utils/l2geth/go.sum index f1fc58f76f..db169fed7f 100644 --- a/geth-utils/l2geth/go.sum +++ b/geth-utils/l2geth/go.sum @@ -137,6 +137,8 @@ github.com/scroll-tech/go-ethereum v1.10.14-0.20230901060443-e1eebd17067c h1:GuA github.com/scroll-tech/go-ethereum v1.10.14-0.20230901060443-e1eebd17067c/go.mod h1:DiN3p2inoXOxGffxSswDKqWjQ7bU+Mp0c9v0XQXKmaA= github.com/scroll-tech/go-ethereum v1.10.14-0.20230919024151-fa0be69a3fb9 h1:QiqH+ZGNNzMcKy21VGX6XYg81DXE+/9j1Ik7owm13hs= github.com/scroll-tech/go-ethereum v1.10.14-0.20230919024151-fa0be69a3fb9/go.mod h1:DiN3p2inoXOxGffxSswDKqWjQ7bU+Mp0c9v0XQXKmaA= +github.com/scroll-tech/go-ethereum v1.10.14-0.20231123003536-35313dc92055 h1:7dGW0GxAu5y+8SlXc4LtuoWXxbV5de3vDjN2qz2oO+k= +github.com/scroll-tech/go-ethereum v1.10.14-0.20231123003536-35313dc92055/go.mod h1:4HrFcoStbViFVy/9l/rvKl1XmizVAaPdgqI8v0U8hOc= github.com/scroll-tech/zktrie v0.6.0 h1:xLrMAO31Yo2BiPg1jtYKzcjpEFnXy8acbB7iIsyshPs= github.com/scroll-tech/zktrie v0.6.0/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= @@ -165,6 +167,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -175,6 +179,7 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -204,12 +209,15 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index bab2f3692d..169481750f 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -36,6 +36,7 @@ misc-precompiled-circuit = { package = "misc-precompiled-circuit", git = "https: halo2-base = { git = "https://github.com/scroll-tech/halo2-lib", tag = "v0.1.5", default-features=false, features=["halo2-pse","display"] } halo2-ecc = { git = "https://github.com/scroll-tech/halo2-lib", tag = "v0.1.5", default-features=false, features=["halo2-pse","display"] } +halo2_gadgets = { git = "https://github.com/scroll-tech/halo2.git", branch = "develop", features = ["unstable"] } num-bigint.workspace = true subtle.workspace = true diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 0f83c7f3c8..77766a6bbd 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -23,7 +23,7 @@ use crate::{ evm_circuit::param::{MAX_STEP_HEIGHT, STEP_STATE_HEIGHT}, table::{ BlockTable, BytecodeTable, CopyTable, EccTable, ExpTable, KeccakTable, LookupTable, - ModExpTable, PowOfRandTable, RwTable, SigTable, TxTable, + ModExpTable, PowOfRandTable, RwTable, SHA256Table, SigTable, TxTable, }, util::{SubCircuit, SubCircuitConfig}, }; @@ -48,6 +48,7 @@ pub struct EvmCircuitConfig { block_table: BlockTable, copy_table: CopyTable, keccak_table: KeccakTable, + sha256_table: SHA256Table, exp_table: ExpTable, sig_table: SigTable, modexp_table: ModExpTable, @@ -71,6 +72,8 @@ pub struct EvmCircuitConfigArgs { pub copy_table: CopyTable, /// KeccakTable pub keccak_table: KeccakTable, + /// SHA256Table + pub sha256_table: SHA256Table, /// ExpTable pub exp_table: ExpTable, /// SigTable @@ -105,6 +108,7 @@ impl SubCircuitConfig for EvmCircuitConfig { block_table, copy_table, keccak_table, + sha256_table, exp_table, sig_table, modexp_table, @@ -125,6 +129,7 @@ impl SubCircuitConfig for EvmCircuitConfig { &block_table, ©_table, &keccak_table, + &sha256_table, &exp_table, &sig_table, &modexp_table, @@ -158,6 +163,7 @@ impl SubCircuitConfig for EvmCircuitConfig { block_table, copy_table, keccak_table, + sha256_table, exp_table, sig_table, modexp_table, @@ -456,6 +462,7 @@ impl Circuit for EvmCircuit { let q_copy_table = meta.fixed_column(); let copy_table = CopyTable::construct(meta, q_copy_table); let keccak_table = KeccakTable::construct(meta); + let sha256_table = SHA256Table::construct(meta); let exp_table = ExpTable::construct(meta); let sig_table = SigTable::construct(meta); let modexp_table = ModExpTable::construct(meta); @@ -472,6 +479,7 @@ impl Circuit for EvmCircuit { block_table, copy_table, keccak_table, + sha256_table, exp_table, sig_table, modexp_table, @@ -520,6 +528,14 @@ impl Circuit for EvmCircuit { config .keccak_table .dev_load(&mut layouter, &block.sha3_inputs, &challenges)?; + config.sha256_table.dev_load( + &mut layouter, + block + .get_sha256() + .iter() + .map(|evt| (&evt.input, &evt.digest)), + &challenges, + )?; config.exp_table.dev_load(&mut layouter, block)?; config .sig_table diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 7aab28a724..4e5dca867e 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -3,7 +3,7 @@ use super::{ BLOCK_TABLE_LOOKUPS, BYTECODE_TABLE_LOOKUPS, COPY_TABLE_LOOKUPS, ECC_TABLE_LOOKUPS, EXP_TABLE_LOOKUPS, FIXED_TABLE_LOOKUPS, KECCAK_TABLE_LOOKUPS, MODEXP_TABLE_LOOKUPS, N_BYTE_LOOKUPS, N_COPY_COLUMNS, N_PHASE1_COLUMNS, POW_OF_RAND_TABLE_LOOKUPS, - RW_TABLE_LOOKUPS, SIG_TABLE_LOOKUPS, TX_TABLE_LOOKUPS, + RW_TABLE_LOOKUPS, SHA256_TABLE_LOOKUPS, SIG_TABLE_LOOKUPS, TX_TABLE_LOOKUPS, }, util::{instrumentation::Instrument, CachedRegion, CellManager, Inverter, StoredExpression}, EvmCircuitExports, @@ -210,6 +210,7 @@ use pc::PcGadget; use pop::PopGadget; use precompiles::{ EcAddGadget, EcMulGadget, EcPairingGadget, EcrecoverGadget, IdentityGadget, ModExpGadget, + SHA256Gadget, }; use push::PushGadget; use return_revert::ReturnRevertGadget; @@ -359,7 +360,7 @@ pub(crate) struct ExecutionConfig { error_return_data_out_of_bound: Box>, // precompile calls precompile_ecrecover_gadget: Box>, - precompile_sha2_gadget: Box>, + precompile_sha2_gadget: Box>, precompile_ripemd_gadget: Box>, precompile_identity_gadget: Box>, precompile_modexp_gadget: Box>, @@ -383,6 +384,7 @@ impl ExecutionConfig { block_table: &dyn LookupTable, copy_table: &dyn LookupTable, keccak_table: &dyn LookupTable, + sha256_table: &dyn LookupTable, exp_table: &dyn LookupTable, sig_table: &dyn LookupTable, modexp_table: &dyn LookupTable, @@ -663,6 +665,7 @@ impl ExecutionConfig { block_table, copy_table, keccak_table, + sha256_table, exp_table, sig_table, modexp_table, @@ -929,6 +932,7 @@ impl ExecutionConfig { block_table: &dyn LookupTable, copy_table: &dyn LookupTable, keccak_table: &dyn LookupTable, + sha256_table: &dyn LookupTable, exp_table: &dyn LookupTable, sig_table: &dyn LookupTable, modexp_table: &dyn LookupTable, @@ -949,6 +953,7 @@ impl ExecutionConfig { Table::Block => block_table, Table::Copy => copy_table, Table::Keccak => keccak_table, + Table::Sha256 => sha256_table, Table::Exp => exp_table, Table::Sig => sig_table, Table::ModExp => modexp_table, @@ -1383,6 +1388,7 @@ impl ExecutionConfig { ("EVM_lookup_block", BLOCK_TABLE_LOOKUPS), ("EVM_lookup_copy", COPY_TABLE_LOOKUPS), ("EVM_lookup_keccak", KECCAK_TABLE_LOOKUPS), + ("EVM_lookup_sha256", SHA256_TABLE_LOOKUPS), ("EVM_lookup_exp", EXP_TABLE_LOOKUPS), ("EVM_lookup_sig", SIG_TABLE_LOOKUPS), ("EVM_lookup_modexp", MODEXP_TABLE_LOOKUPS), diff --git a/zkevm-circuits/src/evm_circuit/execution/callop.rs b/zkevm-circuits/src/evm_circuit/execution/callop.rs index 3df57e7bde..7f97d9934c 100644 --- a/zkevm-circuits/src/evm_circuit/execution/callop.rs +++ b/zkevm-circuits/src/evm_circuit/execution/callop.rs @@ -1812,9 +1812,6 @@ mod test_precompiles { address: Word::from(0x2), stack_value: vec![( Word::from(0x20), - #[cfg(feature = "scroll")] - Word::zero(), - #[cfg(not(feature = "scroll"))] word!("a8100ae6aa1940d0b663bb31cd466142ebbdbd5187131b92d93818987832eb89"), )], ..Default::default() diff --git a/zkevm-circuits/src/evm_circuit/execution/precompiles/mod.rs b/zkevm-circuits/src/evm_circuit/execution/precompiles/mod.rs index 8a512c7b97..8cbf0c34ff 100644 --- a/zkevm-circuits/src/evm_circuit/execution/precompiles/mod.rs +++ b/zkevm-circuits/src/evm_circuit/execution/precompiles/mod.rs @@ -33,6 +33,9 @@ pub use ec_pairing::EcPairingGadget; mod identity; pub use identity::IdentityGadget; +mod sha256; +pub use sha256::SHA256Gadget; + #[derive(Clone, Debug)] pub struct BasePrecompileGadget { input_bytes_rlc: Cell, diff --git a/zkevm-circuits/src/evm_circuit/execution/precompiles/sha256.rs b/zkevm-circuits/src/evm_circuit/execution/precompiles/sha256.rs new file mode 100644 index 0000000000..9a92b0c4e9 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/precompiles/sha256.rs @@ -0,0 +1,383 @@ +use bus_mapping::precompile::PrecompileAuxData; +use eth_types::{evm_types::GasCost, Field, ToScalar}; +use gadgets::util::{select, Expr}; +use halo2_proofs::{circuit::Value, plonk::Error}; + +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + param::{N_BYTES_MEMORY_WORD_SIZE, N_BYTES_WORD}, + step::ExecutionState, + util::{ + common_gadget::RestoreContextGadget, constraint_builder::EVMConstraintBuilder, + math_gadget::ConstantDivisionGadget, rlc, CachedRegion, Cell, + }, + }, + table::CallContextFieldTag, + witness::{Block, Call, ExecStep, Transaction}, +}; + +#[derive(Clone, Debug)] +pub struct SHA256Gadget { + input_bytes_rlc: Cell, + output_bytes_rlc: Cell, + return_bytes_rlc: Cell, + + input_word_size: ConstantDivisionGadget, + is_success: Cell, + callee_address: Cell, + caller_id: Cell, + call_data_offset: Cell, + call_data_length: Cell, + return_data_offset: Cell, + return_data_length: Cell, + restore_context: RestoreContextGadget, +} + +impl ExecutionGadget for SHA256Gadget { + const EXECUTION_STATE: ExecutionState = ExecutionState::PrecompileSha256; + + const NAME: &'static str = "SHA256"; + + fn configure(cb: &mut EVMConstraintBuilder) -> Self { + let (input_bytes_rlc, output_bytes_rlc, return_bytes_rlc) = ( + cb.query_cell_phase2(), + cb.query_cell_phase2(), + cb.query_cell_phase2(), + ); + let [is_success, callee_address, caller_id, call_data_offset, call_data_length, return_data_offset, return_data_length] = + [ + CallContextFieldTag::IsSuccess, + CallContextFieldTag::CalleeAddress, + CallContextFieldTag::CallerId, + CallContextFieldTag::CallDataOffset, + CallContextFieldTag::CallDataLength, + CallContextFieldTag::ReturnDataOffset, + CallContextFieldTag::ReturnDataLength, + ] + .map(|tag| cb.call_context(None, tag)); + + let input_word_size = ConstantDivisionGadget::construct( + cb, + call_data_length.expr() + (N_BYTES_WORD - 1).expr(), + N_BYTES_WORD as u64, + ); + + let gas_cost = select::expr( + is_success.expr(), + GasCost::PRECOMPILE_SHA256_BASE.expr() + + input_word_size.quotient() * GasCost::PRECOMPILE_SHA256_PER_WORD.expr(), + cb.curr.state.gas_left.expr(), + ); + + cb.precompile_info_lookup( + cb.execution_state().as_u64().expr(), + callee_address.expr(), + cb.execution_state().precompile_base_gas_cost().expr(), + ); + + // sha256 verify lookup + cb.condition(is_success.expr(), |cb| { + cb.sha256_table_lookup( + input_bytes_rlc.expr(), + call_data_length.expr(), + output_bytes_rlc.expr(), + ); + }); + + let restore_context = RestoreContextGadget::construct2( + cb, + is_success.expr(), + gas_cost.expr(), + 0.expr(), + 0x00.expr(), // ReturnDataOffset + select::expr(is_success.expr(), 0x20.expr(), 0x00.expr()), // ReturnDataLength + 0.expr(), + 0.expr(), + ); + + Self { + input_bytes_rlc, + output_bytes_rlc, + return_bytes_rlc, + + input_word_size, + is_success, + callee_address, + caller_id, + call_data_offset, + call_data_length, + return_data_offset, + return_data_length, + restore_context, + } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + _tx: &Transaction, + call: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + if let Some(PrecompileAuxData::SHA256 { + input_bytes, + output_bytes, + return_bytes, + }) = &step.aux_data + { + region + .challenges() + .keccak_input() + .map(|r| rlc::value(input_bytes.iter().rev(), r)); + + region + .challenges() + .keccak_input() + .map(|r| rlc::value(output_bytes.iter().rev(), r)); + + self.input_bytes_rlc.assign( + region, + offset, + region + .challenges() + .keccak_input() + .map(|r| rlc::value(input_bytes.iter().rev(), r)), + )?; + self.output_bytes_rlc.assign( + region, + offset, + region + .challenges() + .keccak_input() + .map(|r| rlc::value(output_bytes.iter().rev(), r)), + )?; + self.return_bytes_rlc.assign( + region, + offset, + region + .challenges() + .keccak_input() + .map(|r| rlc::value(return_bytes.iter().rev(), r)), + )?; + } else { + log::error!("unexpected aux_data {:?} for sha256", step.aux_data); + return Err(Error::Synthesis); + } + self.input_word_size.assign( + region, + offset, + (call.call_data_length + (N_BYTES_WORD as u64) - 1).into(), + )?; + self.is_success.assign( + region, + offset, + Value::known(F::from(u64::from(call.is_success))), + )?; + self.callee_address.assign( + region, + offset, + Value::known(call.code_address.unwrap().to_scalar().unwrap()), + )?; + self.caller_id + .assign(region, offset, Value::known(F::from(call.caller_id as u64)))?; + self.call_data_offset.assign( + region, + offset, + Value::known(F::from(call.call_data_offset)), + )?; + self.call_data_length.assign( + region, + offset, + Value::known(F::from(call.call_data_length)), + )?; + self.return_data_offset.assign( + region, + offset, + Value::known(F::from(call.return_data_offset)), + )?; + self.return_data_length.assign( + region, + offset, + Value::known(F::from(call.return_data_length)), + )?; + self.restore_context + .assign(region, offset, block, call, step, 7) + } +} + +#[cfg(test)] +mod test { + use bus_mapping::{ + evm::{OpcodeId, PrecompileCallArgs}, + precompile::PrecompileCalls, + }; + use eth_types::{bytecode, word, ToWord}; + use itertools::Itertools; + use mock::TestContext; + + use crate::test_util::CircuitTestBuilder; + + lazy_static::lazy_static! { + static ref TEST_VECTOR: Vec = { + vec![ + PrecompileCallArgs { + name: "simple success", + setup_code: bytecode! { + // place params in memory + PUSH3(0x616263) + PUSH1(0x00) + MSTORE + }, + call_data_offset: 0x1d.into(), + call_data_length: 0x03.into(), + ret_offset: 0x20.into(), + ret_size: 0x20.into(), + address: PrecompileCalls::Sha256.address().to_word(), + ..Default::default() + }, + PrecompileCallArgs { + name: "nil success", + setup_code: bytecode! {}, + call_data_offset: 0x00.into(), + call_data_length: 0x00.into(), + ret_offset: 0x20.into(), + ret_size: 0x20.into(), + address: PrecompileCalls::Sha256.address().to_word(), + ..Default::default() + }, + PrecompileCallArgs { + name: "block edge", + setup_code: bytecode! { + // place params in memory + PUSH32(word!("0x6161616161616161616161616161616161616161616161616161616161616161")) + PUSH1(0x00) + MSTORE + PUSH32(word!("0x6161616161616161616161616161616161616161616161616161616161616161")) + PUSH1(0x20) + MSTORE + }, + call_data_offset: 0x00.into(), + call_data_length: 0x40.into(), + ret_offset: 0x20.into(), + ret_size: 0x20.into(), + address: PrecompileCalls::Sha256.address().to_word(), + ..Default::default() + }, + PrecompileCallArgs { + name: "simple truncated return", + setup_code: bytecode! { + // place params in memory + PUSH3(0x616263) + PUSH1(0x00) + MSTORE + }, + call_data_offset: 0x1d.into(), + call_data_length: 0x03.into(), + ret_offset: 0x20.into(), + ret_size: 0x10.into(), + address: PrecompileCalls::Sha256.address().to_word(), + ..Default::default() + }, + PrecompileCallArgs { + name: "overlapped return", + setup_code: bytecode! { + // place params in memory + PUSH3(0x616263) + PUSH1(0x00) + MSTORE + }, + call_data_offset: 0x1d.into(), + call_data_length: 0x03.into(), + ret_offset: 0x00.into(), + ret_size: 0x20.into(), + address: PrecompileCalls::Sha256.address().to_word(), + ..Default::default() + }, + ] + }; + + static ref OOG_TEST_VECTOR: Vec = { + vec![ + PrecompileCallArgs { + name: "oog", + setup_code: bytecode! { + PUSH32(word!("0x6161616161616161616161616161616161616161616161616161616161616161")) + PUSH1(0x00) + MSTORE + PUSH32(word!("0x6161616161616161616161616161616161616161616161616161616161616161")) + PUSH1(0x20) + MSTORE + }, + call_data_offset: 0x00.into(), + call_data_length: 0x40.into(), + ret_offset: 0x20.into(), + ret_size: 0x20.into(), + address: PrecompileCalls::Sha256.address().to_word(), + gas: 20.into(), + ..Default::default() + }, + ] + }; + } + + #[test] + fn precompile_sha256_common_test() { + let call_kinds = vec![ + OpcodeId::CALL, + OpcodeId::STATICCALL, + OpcodeId::DELEGATECALL, + OpcodeId::CALLCODE, + ]; + + for (test_vector, &call_kind) in TEST_VECTOR.iter().cartesian_product(&call_kinds) { + let bytecode = test_vector.with_call_op(call_kind); + + CircuitTestBuilder::new_from_test_ctx( + TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode).unwrap(), + ) + .run(); + } + } + + // verify nil case is corrected handled in SHA256 event + #[test] + fn precompile_sha256_nil_test() { + let nil_vector = &TEST_VECTOR[1]; + let bytecode = nil_vector.with_call_op(OpcodeId::STATICCALL); + + CircuitTestBuilder::new_from_test_ctx( + TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode).unwrap(), + ) + .block_modifier(Box::new(|blk| { + let evts = blk.get_sha256(); + assert_eq!(evts.len(), 1); + assert_eq!(evts[0].input.len(), 0); + })) + .run(); + } + + // verify nil case is corrected handled in SHA256 event + #[test] + fn precompile_sha256_oog_test() { + let call_kinds = vec![ + OpcodeId::CALL, + OpcodeId::STATICCALL, + OpcodeId::DELEGATECALL, + OpcodeId::CALLCODE, + ]; + + for (test_vector, &call_kind) in OOG_TEST_VECTOR.iter().cartesian_product(&call_kinds) { + let bytecode = test_vector.with_call_op(call_kind); + CircuitTestBuilder::new_from_test_ctx( + TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode).unwrap(), + ) + .block_modifier(Box::new(|blk| { + assert_eq!(blk.get_sha256().len(), 0); + })) + .run(); + } + } +} diff --git a/zkevm-circuits/src/evm_circuit/param.rs b/zkevm-circuits/src/evm_circuit/param.rs index d5b15aba03..bc498333ec 100644 --- a/zkevm-circuits/src/evm_circuit/param.rs +++ b/zkevm-circuits/src/evm_circuit/param.rs @@ -37,6 +37,7 @@ pub(crate) const EVM_LOOKUP_COLS: usize = FIXED_TABLE_LOOKUPS + BLOCK_TABLE_LOOKUPS + COPY_TABLE_LOOKUPS + KECCAK_TABLE_LOOKUPS + + SHA256_TABLE_LOOKUPS + EXP_TABLE_LOOKUPS + SIG_TABLE_LOOKUPS + MODEXP_TABLE_LOOKUPS @@ -52,6 +53,7 @@ pub(crate) const LOOKUP_CONFIG: &[(Table, usize)] = &[ (Table::Block, BLOCK_TABLE_LOOKUPS), (Table::Copy, COPY_TABLE_LOOKUPS), (Table::Keccak, KECCAK_TABLE_LOOKUPS), + (Table::Sha256, SHA256_TABLE_LOOKUPS), (Table::Exp, EXP_TABLE_LOOKUPS), (Table::Sig, SIG_TABLE_LOOKUPS), (Table::ModExp, MODEXP_TABLE_LOOKUPS), @@ -80,6 +82,9 @@ pub const COPY_TABLE_LOOKUPS: usize = 1; /// Keccak Table lookups done in EVMCircuit pub const KECCAK_TABLE_LOOKUPS: usize = 1; +/// Keccak Table lookups done in EVMCircuit +pub const SHA256_TABLE_LOOKUPS: usize = 1; + /// Exp Table lookups done in EVMCircuit pub const EXP_TABLE_LOOKUPS: usize = 1; diff --git a/zkevm-circuits/src/evm_circuit/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index 96f75af158..62f7da7426 100644 --- a/zkevm-circuits/src/evm_circuit/table.rs +++ b/zkevm-circuits/src/evm_circuit/table.rs @@ -154,6 +154,7 @@ pub(crate) enum Table { Block, Copy, Keccak, + Sha256, Exp, Sig, ModExp, @@ -303,6 +304,16 @@ pub(crate) enum Lookup { /// the final output keccak256 hash of the input. output_rlc: Expression, }, + /// Lookup to sha256 table. + Sha256Table { + /// Accumulator to the input. + input_rlc: Expression, + /// Length of input that is being hashed. + input_len: Expression, + /// Output (hash) until this state. This is the RLC representation of + /// the final output sha256 hash of the input. + output_rlc: Expression, + }, /// Lookup to exponentiation table. ExpTable { base_limbs: [Expression; 4], @@ -356,6 +367,7 @@ impl Lookup { Self::Block { .. } => Table::Block, Self::CopyTable { .. } => Table::Copy, Self::KeccakTable { .. } => Table::Keccak, + Self::Sha256Table { .. } => Table::Sha256, Self::ExpTable { .. } => Table::Exp, Self::SigTable { .. } => Table::Sig, Self::ModExpTable { .. } => Table::ModExp, @@ -465,6 +477,17 @@ impl Lookup { input_len.clone(), output_rlc.clone(), ], + Self::Sha256Table { + input_rlc, + input_len, + output_rlc, + } => vec![ + 1.expr(), // q_enable + 1.expr(), // is_final + input_rlc.clone(), + input_len.clone(), + output_rlc.clone(), + ], Self::ExpTable { base_limbs, exponent_lo_hi, diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index facc0f6664..29aaf3e7d1 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -1476,6 +1476,24 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { ); } + // SHA256 Table + + pub(crate) fn sha256_table_lookup( + &mut self, + input_rlc: Expression, + input_len: Expression, + output_rlc: Expression, + ) { + self.add_lookup( + "sha256 lookup", + Lookup::Sha256Table { + input_rlc, + input_len, + output_rlc, + }, + ); + } + // ModExp table pub(crate) fn modexp_table_lookup( &mut self, diff --git a/zkevm-circuits/src/evm_circuit/util/instrumentation.rs b/zkevm-circuits/src/evm_circuit/util/instrumentation.rs index 96cd6eaaf2..e9839ef938 100644 --- a/zkevm-circuits/src/evm_circuit/util/instrumentation.rs +++ b/zkevm-circuits/src/evm_circuit/util/instrumentation.rs @@ -100,6 +100,9 @@ impl Instrument { CellType::Lookup(Table::Keccak) => { report.keccak_table = data_entry; } + CellType::Lookup(Table::Sha256) => { + report.sha256_table = data_entry; + } CellType::Lookup(Table::Exp) => { report.exp_table = data_entry; } @@ -140,6 +143,7 @@ pub(crate) struct ExecStateReport { pub(crate) block_table: StateReportRow, pub(crate) copy_table: StateReportRow, pub(crate) keccak_table: StateReportRow, + pub(crate) sha256_table: StateReportRow, pub(crate) exp_table: StateReportRow, pub(crate) sig_table: StateReportRow, pub(crate) modexp_table: StateReportRow, diff --git a/zkevm-circuits/src/lib.rs b/zkevm-circuits/src/lib.rs index 9dd67c3c55..00cf7e17f3 100644 --- a/zkevm-circuits/src/lib.rs +++ b/zkevm-circuits/src/lib.rs @@ -38,6 +38,7 @@ pub mod sig_circuit; // we don't use this for aggregation //pub mod root_circuit; pub mod modexp_circuit; +pub mod sha256_circuit; pub mod state_circuit; pub mod super_circuit; pub mod table; diff --git a/zkevm-circuits/src/sha256_circuit.rs b/zkevm-circuits/src/sha256_circuit.rs new file mode 100644 index 0000000000..44883f089d --- /dev/null +++ b/zkevm-circuits/src/sha256_circuit.rs @@ -0,0 +1,155 @@ +//! The SHA256 circuit is a wrapper for the circuit in sha256 crate and serve for precompile SHA-256 +//! calls + +use halo2_proofs::{ + circuit::{Layouter, Value}, + halo2curves::bn256::Fr, + plonk::{Any, Column, ConstraintSystem, Error, Expression}, +}; + +mod circuit; +#[cfg(test)] +mod test; + +pub use circuit::CircuitConfig; +use circuit::{Hasher, SHA256Table as TableTrait}; +pub use halo2_gadgets::sha256::BLOCK_SIZE; + +use crate::{ + table::{LookupTable, SHA256Table}, + util::{Challenges, SubCircuit, SubCircuitConfig}, + witness, +}; +use bus_mapping::circuit_input_builder::SHA256; +use eth_types::Field; + +impl TableTrait for SHA256Table { + fn cols(&self) -> [Column; 5] { + let tbl_cols = >::columns(self); + [ + tbl_cols[0], + tbl_cols[2], + tbl_cols[3], + tbl_cols[4], + tbl_cols[1], + ] + } +} + +/// Config args for SHA256 circuit +#[derive(Debug, Clone)] +pub struct CircuitConfigArgs { + /// SHA256 Table + pub sha256_table: SHA256Table, + /// Challenges randomness + pub challenges: Challenges>, +} + +impl SubCircuitConfig for CircuitConfig { + type ConfigArgs = CircuitConfigArgs; + + /// Return a new ModExpCircuitConfig + fn new( + meta: &mut ConstraintSystem, + Self::ConfigArgs { + sha256_table, + challenges, + }: Self::ConfigArgs, + ) -> Self { + Self::configure(meta, sha256_table, challenges.keccak_input()) + } +} + +/// ModExp circuit for precompile modexp +#[derive(Clone, Debug, Default)] +pub struct SHA256Circuit(Vec, usize, std::marker::PhantomData); + +const TABLE16_BLOCK_ROWS: usize = 2114; +const BLOCK_SIZE_IN_BYTES: usize = BLOCK_SIZE * 4; + +impl SHA256Circuit { + fn expected_rows(&self) -> usize { + self.0 + .iter() + .map(|evnt| (evnt.input.len()) + 9 / BLOCK_SIZE_IN_BYTES + 1) + .reduce(|acc, v| acc + v) + .unwrap_or_default() + * TABLE16_BLOCK_ROWS + } + + fn with_row_limit(self, row_limit: usize) -> Self { + if row_limit != 0 { + let expected_rows = self.expected_rows(); + assert!( + expected_rows <= row_limit, + "no enough rows for sha256 circuit, expected {expected_rows}, limit {row_limit}", + ); + log::info!("sha256 circuit work with maxium {} rows", row_limit); + } + let inp = self.0; + let block_limit = row_limit / TABLE16_BLOCK_ROWS; + + Self(inp, block_limit, Default::default()) + } +} + +impl SubCircuit for SHA256Circuit { + type Config = CircuitConfig; + + fn unusable_rows() -> usize { + 2 + } + + fn new_from_block(block: &witness::Block) -> Self { + Self(block.get_sha256(), 0, Default::default()) + .with_row_limit(block.circuits_params.max_keccak_rows) + } + + fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { + let real_row = Self(block.get_sha256(), 0, Default::default()).expected_rows(); + + ( + real_row, + real_row + .max(block.circuits_params.max_keccak_rows) + .max(4096), + ) + } + + fn synthesize_sub( + &self, + config: &Self::Config, + challenges: &Challenges>, + layouter: &mut impl Layouter, + ) -> Result<(), Error> { + let chng = challenges.keccak_input(); + let mut hasher = Hasher::new(config.clone(), layouter)?; + + for hash_event in &self.0 { + hasher.update(layouter, chng, &hash_event.input)?; + + let digest = hasher.finalize(layouter, chng)?; + let ref_digest = hash_event + .digest + .chunks_exact(4) + .map(|bt| bt.iter().fold(0u32, |sum, v| sum * 256 + *v as u32)) + .collect::>(); + for (w, check) in digest.into_iter().zip(ref_digest) { + w.0.assert_if_known(|digest_word| *digest_word == check); + } + + if hasher.blocks() > self.1 { + log::error!("handled 512-bit block exceed limit ({})", self.1); + return Err(Error::Synthesis); + } + } + + // paddings + for _i in hasher.blocks()..self.1 { + hasher.update(layouter, chng, &[])?; + hasher.finalize(layouter, chng)?; + } + + Ok(()) + } +} diff --git a/zkevm-circuits/src/sha256_circuit/README.md b/zkevm-circuits/src/sha256_circuit/README.md new file mode 100644 index 0000000000..c13751dc50 --- /dev/null +++ b/zkevm-circuits/src/sha256_circuit/README.md @@ -0,0 +1,71 @@ +# SHA256 Circuit with lookup table + +This circuit use a forking of `table16` in `halo2-gadget`, with some patches: + ++ Make all code generic for the `Field` trait so that it also work with the `bn254` curve ++ Fix the digest exporting part, output correct digest (the final state ⊕ init state) with correct constraint (rows for 512-bit block increased from **2102** -> **2114**) + +The witness in table16 is then exported to an extra region so that the RLC of input and digest can be calculated and form the lookup table for the SHA256 precompile in zkevm-circuit. To achieve this, we have introduced several cols and assigned them to two regions: `input` and `digest`. The following table illustrates: + +input region (example for input 'abc'): +| | s_final | s_u16 | counter | bytes_rlc | trans_byte | copied_data | s_output| padding |padding_size| +|----------|------------------|-----------|-----------|-----------|------------|-------------|---------|-----------------|------------| +|(inherit) | *1* | | *42* |*inherit_rlc*| | | | *1* | | +|s_begin | 1 | | 0 | 0 | | | | 0 | | +|s_enable | 1 | 1 | 1 | 0x61 | b'0x61' | *0x6162* | | 0 | | +|s_enable | 1 | 0 | 2 | 0x61062 | b'0x62' | | | 0 | | +|s_enable | 1 | 1 | 3 | 0x61062063| b'0x63' | *0x6380* | | 0 | | +|s_enable | 1 | 0 | 3 | 0x61062063| b'0x80' | | | 1 | | +|.... | +|s_enable | 1 | 1 | 3 | 0x61062063| b'0x00 | *0x0018* | | 1 | 0 | +|s_last | 1 | 0 | 3 | 0x61062063| b'0x18 | | | 1 | 24 | + + +digest region (example for the hash of 'abc'): +| | s_final | s_u16 | counter | bytes_rlc | trans_byte | copied_data | s_output| padding | +|----------|------------------|-----------|-----------|-----------|------------|-------------|---------|-----------| +| | *1* | | | **0** | | | | **0** | +|s_enable | 1 | 1 | | 0xba | b'0xba' | *0xba78* | 0x6a09 | 0 | +|s_enable | 1 | 0 | | 0xba078 | b'0x78 | *0x6a09* | | +|.... | +|s_enable | 1 | 1 | | | b'0x15 | *0x15ad* | 0xcd19 | 0 | +|s_enable | 1 | 0 | | hash_rlc | b'0xad | *0xcd19* | | **0** | +| | | |*input_counter*|*hash_rlc*| | *input_rlc* | 1 | | + +Note: ++ *Italic* indicate the cell is equality constrainted whie **bold** indicate the cell is constarinted with constant ++ We suppose the `random` value for rlc is `0x1000` + +### Defination of the cols + ++ `copied_data` col is used to copy the cells with `u16` values from `table16`. ++ `trans_byte` expands each `u16` value copied from `table16` into two bytes across two adjacent rows, with the help of the selector `s_u16` ++ `padding` col marks whether the byte in current row is padding or input byte. ++ `bytes_rlc` accumulates bytes in `trans_byte` col to its RLC expression only if the byte in current row is not padding. Otherwise, it continues the value from the previous row if the current row is marked as padding. ++ `counter` counts the total input bytes if byte in current row is not padding, Otherwise it continues the value from previous row if the current row is marked as padding. ++ `s_final` is a boolean advice col that identifies in each row of an input region, marking wether the current block is the last block ++ `padding_size` calculates the accumulation of the last 8 bytes in input region and obtains the bit counts recorded in the tail of the padding, which is specified by SHA2. + +### Defination in regions: + + Each input region captures a 512-bit block and copies the 16 x 32-bit integers (in the form of a pair of assigned cells for their lo and hi 16-bit parts) inside of the `message schedule` region of table16. The region consists of 66 rows: 64 rows for 64 bytes representing the 512-bit block and 2 extra rows at the beginning. For the `s_final`, `counter`, `padding` and `bytes_rlc` cols, the cells in last row (enabled by `s_last` selector) are connected by equality constraints to the first row of next input region for the subsequent 512-bit block. Additionally the `s_final` cells is also connected with the corresponding digest reion. + + The second row at the top of the region determines how the `counter`, `padding` and `bytes_rlc` cols begin: if the inherited `s_final` cell (at the first row at the top of the region) is 1, these cols will start with an initial value (i.e., 0); else they will start with the "inherited" value of the previous 512-bit block. + + Note that it is free to specify `s_final` in each block as either 0 or 1. If `s_final` is set to 1, the last row must satisfy the "final" constraint, that is the cell in `counter` col has to equal the calculated bit size in `padding_size` cell. + + There is exactly one digest region corresponding to each input region. This region captures the 256-bit digest of the 512-bit block and copies it from the `digest` region of table16. The region consists of 34 rows: 32 rows for bytes of digests, 1 extra row at the beginning, and 1 row at the bottom. The `s_final` is inherited from the input region, and the first row for `counter`, `padding` and `bytes_rlc` cols are specified with 0 by constraints to a constant. The last row for digest bytes is also constarint the `padding` cell as 0, which also ensure there is no padding row existed in digest region. + + Like input region, digest region calculated the RLC of digest bytes. The final row in digest copied `s_final` and `counter` value inheirted from input region into the corresponding cols; `bytes_rlc` of the cell in previous cell (i.e. the RLC of digest); and the RLC of input into `copied_data` col. This row represents a row in SHA256 table used for looking up from evm circuit. + +## Performance + + Currently the SHA256 circuit can calculate SHA256 for 1k bytes within 4.891s (`k=17`), ~26% overhead to its `table16` core (3.854s), and verfication is 6.601ms, 6% overhead to `table16` (6.207ms). + + We have a [detailed performance for table16 and Brecht's sha256](https://www.notion.so/scrollzkp/Precompile-SHA256-7a0f519d5bbe4f52a9fa08ebff9a8118) (accessing priviledge required). + + With `k=21`, SHA256-circuit can calculate the hashes for as much as 16KB bytes, which should be enough for the txs in mainnet. + +## Known issue in table16 + ++ Initialize state is not constrainted in table16 diff --git a/zkevm-circuits/src/sha256_circuit/circuit.rs b/zkevm-circuits/src/sha256_circuit/circuit.rs new file mode 100644 index 0000000000..1c7fdfb8f2 --- /dev/null +++ b/zkevm-circuits/src/sha256_circuit/circuit.rs @@ -0,0 +1,1254 @@ +use halo2_gadgets::sha256::{table16::*, Sha256Instructions, BLOCK_SIZE}; +use halo2_proofs::{ + circuit::{AssignedCell, Layouter, Region, Value}, + halo2curves::bn256::Fr, + plonk::{ + Advice, Any, Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector, + TableColumn, + }, + poly::Rotation, +}; +use itertools::Itertools; +use std::convert::TryInto; +type BlockState = >::State; + +/// u32 size for SHA256 digit +pub const DIGEST_SIZE: usize = 8; + +/// the defination for a sha256 table +pub trait SHA256Table { + /// the cols has layout [s_enable, input_bytes, input_len, hashes, effect] + fn cols(&self) -> [Column; 5]; + + /// s_enable col with cell *EQUAL TO 1* mark the row is an effect entry for + /// *ANY* 512-bit block of SHA256 + fn s_enable(&self) -> Column { + self.cols()[0] + .try_into() + .expect("must provide cols as expected layout") + } + /// input_rlc show the RLC for input bytes, the first byte is multipled with R^(n-1) + /// in which n is the length of bytes and R is random + fn input_rlc(&self) -> Column { + self.cols()[1] + .try_into() + .expect("must provide cols as expected layout") + } + /// input_len show the accumulated lengh for input bytes + fn input_len(&self) -> Column { + self.cols()[2] + .try_into() + .expect("must provide cols as expected layout") + } + /// hashes_rlc show the RLC for the 32-bytes digest of input bytes, the first byte + /// is multipled with R^31 + fn hashes_rlc(&self) -> Column { + self.cols()[3] + .try_into() + .expect("must provide cols as expected layout") + } + /// is_effect col is a phase 0 col, when the cell is equal to 1 indicate this 512-bit + /// block is the final one for current input bytes, the input_len in this row would + /// show the length *WITHOUT* padding of input bytes + fn is_effect(&self) -> Column { + self.cols()[4] + .try_into() + .expect("must provide cols as expected layout") + } +} + +/// CircuitConfig is the configure for SHA256 circuit +#[derive(Clone, Debug)] +pub struct CircuitConfig { + table16: Table16Config, + byte_range: TableColumn, + + copied_data: Column, + trans_byte: Column, + bytes_rlc: Column, /* phase 2 col obtained from SHA256 table, used for saving the + * rlc bytes from input */ + helper: Column, /* phase 2 col used to save series of data, like the final input rlc + * cell, the padding bit count, etc */ + + s_final_block: Column, /* indicate it is the last block, it can be 0/1 in input + * region and */ + // digest region is set the same as corresponding input region + s_padding: Column, // indicate cur bytes is padding + byte_counter: Column, // counting for the input bytes + + s_output: Column, // indicate the row is used for output to sha256 table + + s_begin: Selector, // indicate as the first line in region + s_final: Selector, // indicate the last byte + s_enable: Selector, // indicate the main rows + s_common_bytes: Selector, // mark the s_enable region except for the last 8 bytes + s_padding_size: Selector, // mark the last 8 bytes for padding size + s_assigned_u16: Selector, // indicate copied_data cell is a assigned u16 word +} + +#[derive(Clone, Debug)] +struct BlockInheritments { + s_final: AssignedBits, + s_padding: AssignedBits, + byte_counter: AssignedCell, + bytes_rlc: AssignedCell, +} + +impl CircuitConfig { + fn setup_gates(&self, meta: &mut ConstraintSystem, rnd: Expression) { + let one = Expression::Constant(Fr::one()); + + meta.create_gate("halves to rlc_byte", |meta| { + let s_u16 = meta.query_selector(self.s_assigned_u16); + let u16 = meta.query_advice(self.copied_data, Rotation::cur()); + let byte = meta.query_advice(self.trans_byte, Rotation::cur()); + let byte_next = meta.query_advice(self.trans_byte, Rotation::next()); + let rlc_byte_prev = meta.query_advice(self.bytes_rlc, Rotation::prev()); + let rlc_byte = meta.query_advice(self.bytes_rlc, Rotation::cur()); + + let s_enable = meta.query_selector(self.s_enable); + let s_padding = meta.query_advice(self.s_padding, Rotation::cur()); + let s_not_padding = one.clone() - s_padding.clone(); + + // constraint u16 in table16 with byte + let byte_from_u16 = + s_u16 * (u16 - (byte.clone() * Expression::Constant(Fr::from(256u64)) + byte_next)); + + let byte_rlc = rlc_byte + - s_not_padding * (rlc_byte_prev.clone() * rnd + byte) + - s_padding * rlc_byte_prev; + + vec![byte_from_u16, s_enable * byte_rlc] + }); + + meta.create_gate("sha256 block padding", |meta| { + let s_padding = meta.query_advice(self.s_padding, Rotation::cur()); + let s_padding_prev = meta.query_advice(self.s_padding, Rotation::prev()); + + let byte = meta.query_advice(self.trans_byte, Rotation::cur()); + let byte_counter = meta.query_advice(self.byte_counter, Rotation::cur()); + let byte_counter_prev = meta.query_advice(self.byte_counter, Rotation::prev()); + + let is_final = meta.query_advice(self.s_final_block, Rotation::cur()); + + let padding_is_bool = s_padding.clone() * (one.clone() - s_padding.clone()); + let s_not_padding = one.clone() - s_padding.clone(); + + let byte_counter_continue = s_not_padding + * (byte_counter.clone() - (byte_counter_prev.clone() + one.clone())) + + s_padding.clone() * (byte_counter - byte_counter_prev); + + let padding_change = s_padding - s_padding_prev.clone(); + + // if prev padding is 1, the following padding would always 1 (no change) + let padding_continue = s_padding_prev.clone() * padding_change.clone(); + + // the byte on first padding is 128 (first bit is 1) + let padding_byte_on_change = + padding_change.clone() * (byte.clone() - Expression::Constant(Fr::from(128u64))); + + // constraint the padding byte, notice it in fact constraint the first byte of the final + // 64-bit integer is 0, but it is ok (we have no so large bytes for 48-bit + // integer) + let padding_byte_is_zero = s_padding_prev * is_final.clone() * byte; + + let padding_change_on_size = + meta.query_selector(self.s_padding_size) * is_final * padding_change; + + Constraints::with_selector( + meta.query_selector(self.s_enable), + vec![padding_is_bool, padding_continue, byte_counter_continue], + ) + .into_iter() + .chain(Constraints::with_selector( + meta.query_selector(self.s_common_bytes), + vec![padding_byte_is_zero, padding_byte_on_change], + )) + .chain(vec![padding_change_on_size.into()]) + }); + + meta.create_gate("sha256 block final", |meta| { + let is_final = meta.query_advice(self.s_final_block, Rotation::cur()); + // final is decided by the begin row + let final_continue = + is_final.clone() - meta.query_advice(self.s_final_block, Rotation::prev()); + let final_is_bool = is_final.clone() * (one.clone() - is_final.clone()); + + let byte = meta.query_advice(self.trans_byte, Rotation::cur()); + let padding_size = meta.query_advice(self.helper, Rotation::cur()); + let padding_size_prev = meta.query_advice(self.helper, Rotation::prev()); + + let padding_size_calc = padding_size.clone() + - (padding_size_prev * Expression::Constant(Fr::from(256u64)) + byte); + let final_must_padded = (one.clone() + - meta.query_advice(self.s_padding, Rotation::cur())) + * is_final.clone(); + + let padding_size_is_zero = + meta.query_selector(self.s_common_bytes) * padding_size.clone(); + + // final contintion: byte counter equal to padding size + let final_condition = meta.query_selector(self.s_final) + * (padding_size + - (meta.query_advice(self.byte_counter, Rotation::cur()) + * Expression::Constant(Fr::from(8u64)))) + * is_final.clone(); + + let u16 = meta.query_advice(self.copied_data, Rotation::cur()); + let u16_exported = meta.query_advice(self.copied_data, Rotation::next()); + let init_iv_u16 = meta.query_fixed(self.s_output, Rotation::cur()); + let is_not_final = one.clone() - is_final.clone(); + + let select_exported = meta.query_selector(self.s_assigned_u16) + * (u16_exported - is_not_final * u16 - is_final * init_iv_u16); + + Constraints::with_selector( + meta.query_selector(self.s_enable), + vec![final_continue, final_is_bool], + ) + .into_iter() + .chain(Constraints::with_selector( + meta.query_selector(self.s_padding_size), + vec![final_must_padded, padding_size_calc], + )) + .chain(vec![ + padding_size_is_zero.into(), + final_condition.into(), + select_exported.into(), + ]) + }); + + meta.create_gate("input block beginning", |meta| { + // is *last block* final + let is_final = meta.query_advice(self.s_final_block, Rotation::prev()); + let is_not_final = one.clone() - is_final.clone(); + + let inherited_counter = meta.query_advice(self.byte_counter, Rotation::prev()); + let byte_counter = meta.query_advice(self.byte_counter, Rotation::cur()); + + let applied_counter = is_not_final.clone() * (byte_counter.clone() - inherited_counter) + + is_final.clone() * byte_counter; + + let inherited_bytes_rlc = meta.query_advice(self.bytes_rlc, Rotation::prev()); + let bytes_rlc = meta.query_advice(self.bytes_rlc, Rotation::cur()); + + let applied_bytes_rlc = is_not_final.clone() + * (bytes_rlc.clone() - inherited_bytes_rlc) + + is_final.clone() * bytes_rlc; + + let inherited_s_padding = meta.query_advice(self.s_padding, Rotation::prev()); + let s_padding = meta.query_advice(self.s_padding, Rotation::cur()); + + let applied_s_padding = is_not_final.clone() + * (s_padding.clone() - inherited_s_padding.clone()) + + is_final * s_padding; + + let is_final = meta.query_advice(self.s_final_block, Rotation::cur()); + let final_is_bool = is_final.clone() * (one.clone() - is_final.clone()); + + // notice now the 'is_final' point to current block and 'is_not_final' point to last + // block (prev) this constraint make circuit can not make a full block is + // padded but not final + let enforce_final = is_not_final * inherited_s_padding * (one.clone() - is_final); + + Constraints::with_selector( + meta.query_selector(self.s_begin), + vec![ + final_is_bool, + applied_counter, + applied_bytes_rlc, + applied_s_padding, + enforce_final, + ], + ) + }); + } + + /// Configures a circuit to include this chip. + pub fn configure( + meta: &mut ConstraintSystem, + sha256_table: impl SHA256Table, + spec_challenge: Expression, + ) -> Self { + let helper = meta.advice_column(); // index 3 + let trans_byte = meta.advice_column(); // index 4 + + let bytes_rlc = sha256_table.hashes_rlc(); + let byte_counter = sha256_table.input_len(); + let copied_data = sha256_table.input_rlc(); + let s_output = sha256_table.s_enable(); + let s_final_block = sha256_table.is_effect(); + + let s_padding_size = meta.selector(); + let s_padding = meta.advice_column(); + let s_begin = meta.selector(); + let s_common_bytes = meta.selector(); + let s_final = meta.selector(); + let s_enable = meta.selector(); + let s_assigned_u16 = meta.selector(); + + let byte_range = meta.lookup_table_column(); + let table16 = Table16Chip::configure(meta); + + meta.enable_equality(copied_data); + meta.enable_equality(bytes_rlc); + meta.enable_equality(s_final_block); + meta.enable_equality(s_padding); + meta.enable_equality(byte_counter); + + let ret = Self { + table16, + byte_range, + + copied_data, + trans_byte, + bytes_rlc, + helper, + + s_final_block, + s_common_bytes, + s_padding_size, + s_padding, + byte_counter, + + s_output, + + s_begin, + s_final, + s_enable, + s_assigned_u16, + }; + + meta.lookup("byte range checking", |meta| { + let byte = meta.query_advice(ret.trans_byte, Rotation::cur()); + vec![(byte, byte_range)] + }); + + ret.setup_gates(meta, spec_challenge); + + ret + } + + #[allow(clippy::type_complexity)] + fn assign_message_block<'vr>( + &self, + region: &mut Region<'_, Fr>, + msgs: impl Iterator, u16)>, + offset: usize, + is_final: bool, + ) -> Result<(Vec>, Vec>), Error> { + let mut out_ret = Vec::new(); + let mut out_bytes = Vec::new(); + let mut size_calc = Value::known(Fr::zero()); + + for (i, (msg, ref_iv)) in msgs.enumerate() { + let row = offset + i * 2; + let next_row = row + 1; + + self.s_assigned_u16.enable(region, row)?; + + msg.copy_advice(|| "copied message input", region, self.copied_data, row)?; + let assigned = region.assign_advice( + || "dummy message cell", + self.copied_data, + next_row, + || { + if is_final { + Value::known(Bits::from(ref_iv)) + } else { + msg.value().map(Clone::clone) + } + }, + )?; + + let bytes_hi = region.assign_advice( + || "u16 message hi byte", + self.trans_byte, + row, + || msg.value().map(|v| Fr::from((u16::from(v) >> 8) as u64)), + )?; + + let bytes_lo = region.assign_advice( + || "u16 message lo byte", + self.trans_byte, + next_row, + || { + msg.value() + .map(|v| Fr::from((u16::from(v) & 255u16) as u64)) + }, + )?; + + for (j, byte_v) in [&bytes_hi, &bytes_lo].into_iter().enumerate() { + // bytes_rlc = region.assign_advice( + // || "bytes rlc", + // self.bytes_rlc, + // row + j, + // || chng * bytes_rlc.value() + byte_v.value(), + // )?; + + // here we have a trick, since digest region has only 16 messages instead 32 + size_calc = region + .assign_advice( + || "padding size calc", + self.helper, + row + j, + || { + if i < 28 { + size_calc + } else { + size_calc.map(|v| v * Fr::from(256u64)) + byte_v.value() + } + }, + )? + .value() + .map(Clone::clone); + } + + out_bytes.push(bytes_hi); + out_bytes.push(bytes_lo); + out_ret.push(AssignedBits(assigned)); + } + + Ok((out_ret, out_bytes)) + } + + fn initialize_block_head( + &self, + layouter: &mut impl Layouter, + ) -> Result { + layouter.assign_region( + || "initialize hasher", + |mut region| { + let s_final = region.assign_advice_from_constant( + || "init s_final", + self.s_final_block, + 0, + Bits::from([false]), + )?; + let s_padding = region.assign_advice_from_constant( + || "init padding", + self.s_padding, + 0, + Bits::from([false]), + )?; + let bytes_rlc = region.assign_advice_from_constant( + || "init bytes rlc", + self.bytes_rlc, + 0, + Fr::zero(), + )?; + let byte_counter = region.assign_advice_from_constant( + || "init byte counter", + self.byte_counter, + 0, + Fr::zero(), + )?; + + Ok(BlockInheritments { + s_final: AssignedBits(s_final), + s_padding: AssignedBits(s_padding), + byte_counter, + bytes_rlc, + }) + }, + ) + } + + #[allow(clippy::type_complexity)] + fn assign_input_block( + &self, + layouter: &mut impl Layouter, + chng: Value, + prev_block: BlockInheritments, + scheduled_msg: &[(AssignedBits, AssignedBits)], + padding_pos: Option, + ) -> Result { + // if no padding or the padding is in padding size pos, this block is not final + let is_final = if let Some(pos) = padding_pos { + pos <= 24 + } else { + false + }; + + let padding_pos = padding_pos.unwrap_or(64); + + layouter.assign_region( + || "sha256 input", + |mut region| { + prev_block.s_final.copy_advice( + || "inheirt s_final", + &mut region, + self.s_final_block, + 0, + )?; + prev_block.s_padding.copy_advice( + || "inheirt padding", + &mut region, + self.s_padding, + 0, + )?; + prev_block.bytes_rlc.copy_advice( + || "inheirt bytes rlc", + &mut region, + self.bytes_rlc, + 0, + )?; + prev_block.byte_counter.copy_advice( + || "inheirt byte counter", + &mut region, + self.byte_counter, + 0, + )?; + + self.s_begin.enable(&mut region, 1)?; + let mut s_final_cell = region.assign_advice( + || "header final", + self.s_final_block, + 1, + || Value::known(Bits::from([is_final])), + )?; + + let mut s_padding_cell = region.assign_advice( + || "header padding", + self.s_padding, + 1, + || { + prev_block + .s_final + .value() + .zip(prev_block.s_padding.value()) + .map(|(s_final, ref_v)| { + if s_final[0] { + Bits::from([false]) + } else { + ref_v.clone() + } + }) + }, + )?; + + let mut byte_counter_cell = region.assign_advice( + || "header rlc", + self.byte_counter, + 1, + || { + prev_block + .s_final + .value() + .zip(prev_block.byte_counter.value()) + .map(|(s_final, ref_v)| if s_final[0] { Fr::zero() } else { *ref_v }) + }, + )?; + + let mut bytes_rlc_cell = region.assign_advice( + || "header counter", + self.bytes_rlc, + 1, + || { + prev_block + .s_final + .value() + .zip(prev_block.bytes_rlc.value()) + .map(|(s_final, ref_v)| if s_final[0] { Fr::zero() } else { *ref_v }) + }, + )?; + + let header_offset = 2; + // assign message state + let (_, byte_cells) = self.assign_message_block( + &mut region, + scheduled_msg + .iter() + .flat_map(|(lo, hi)| [hi, lo]) + .zip(std::iter::repeat(0u16)) + .take(32), + header_offset, + is_final, + )?; + + for (row, byte) in (header_offset..(header_offset + 64)).zip(byte_cells) { + self.s_enable.enable(&mut region, row)?; + let now_padding = row >= padding_pos + header_offset; + + region.assign_fixed( + || "flush s_output", + self.s_output, + row, + || Value::known(Fr::zero()), + )?; + s_padding_cell = region.assign_advice( + || "padding", + self.s_padding, + row, + || Value::known(Bits::from([now_padding])), + )?; + s_final_cell = region.assign_advice( + || "final", + self.s_final_block, + row, + || s_final_cell.value().map(Clone::clone), + )?; + byte_counter_cell = region.assign_advice( + || "byte counter", + self.byte_counter, + row, + || { + byte_counter_cell.value() + + Value::known(if now_padding { Fr::zero() } else { Fr::one() }) + }, + )?; + bytes_rlc_cell = region.assign_advice( + || "bytes rlc", + self.bytes_rlc, + row, + || { + if now_padding { + bytes_rlc_cell.value().map(Clone::clone) + } else { + chng * bytes_rlc_cell.value() + byte.value() + } + }, + )?; + + // println!("padding {:#?}, counter {:#?} at row {}", + // output_block.s_padding.value(), output_block.byte_counter.value(), row); + // println!("final {:#?}, at row {}", output_block.s_final.value(), row); + + if row < 56 + header_offset { + self.s_common_bytes.enable(&mut region, row)?; + } else { + self.s_padding_size.enable(&mut region, row)?; + } + } + self.s_final.enable(&mut region, 63 + header_offset)?; + + // flush unused row + for col in [self.trans_byte, self.copied_data] { + region.assign_advice( + || "flush unused row", + col, + 64 + header_offset, + || Value::known(Fr::zero()), + )?; + } + + region.assign_advice( + || "flush unused row", + self.helper, + 1, + || Value::known(Fr::zero()), + )?; + + Ok(BlockInheritments { + s_final: AssignedBits(s_final_cell), + s_padding: AssignedBits(s_padding_cell), + byte_counter: byte_counter_cell, + bytes_rlc: bytes_rlc_cell, + }) + }, + ) + } + + #[allow(clippy::type_complexity)] + fn assign_output_region( + &self, + layouter: &mut impl Layouter, + chng: Value, + state: &[(AssignedBits, AssignedBits)], + input_block: &BlockInheritments, + is_final: bool, + ) -> Result<[(AssignedBits, AssignedBits); 8], Error> { + const IV16: [u16; 16] = [ + 0x6a09, 0xe667, 0xbb67, 0xae85, 0x3c6e, 0xf372, 0xa54f, 0xf53a, 0x510e, 0x527f, 0x9b05, + 0x688c, 0x1f83, 0xd9ab, 0x5be0, 0xcd19, + ]; + + let output_cells = layouter.assign_region( + || "sha256 digest", + |mut region| { + input_block.s_final.copy_advice( + || "inheirt s_final", + &mut region, + self.s_final_block, + 0, + )?; + region.assign_advice_from_constant( + || "header padding", + self.s_padding, + 0, + Fr::zero(), + )?; + region.assign_advice_from_constant( + || "header counter", + self.byte_counter, + 0, + Fr::zero(), + )?; + let mut digest_rlc = region.assign_advice_from_constant( + || "header rlc", + self.bytes_rlc, + 0, + Fr::zero(), + )?; + + let header_offset = 1; + // assign message state + let (export_cells, byte_cells) = self.assign_message_block( + &mut region, + state.iter().flat_map(|(lo, hi)| [hi, lo]).zip_eq(IV16), + header_offset, + is_final, + )?; + + for i in 0..32 { + let row = i + header_offset; + self.s_enable.enable(&mut region, row)?; + region.assign_fixed( + || "set s_output for init_iv", + self.s_output, + row, + || Value::known(Fr::from(IV16[i / 2] as u64)), + )?; + region.assign_advice( + || "byte counter", + self.byte_counter, + row, + || Value::known(Fr::from(i as u64 + 1)), + )?; + region.assign_advice( + || "final", + self.s_final_block, + row, + || Value::known(Bits::from([is_final])), + )?; + digest_rlc = region.assign_advice( + || "bytes rlc", + self.bytes_rlc, + row, + || chng * digest_rlc.value() + byte_cells[i].value(), + )?; + // with gate for padding continue, it + // is enough to constraint the last padding as 0 + if i == 31 { + region.assign_advice_from_constant( + || "dummy padding last", + self.s_padding, + row, + Fr::zero(), + )?; + } else { + region.assign_advice( + || "dummy padding", + self.s_padding, + row, + || Value::known(Fr::zero()), + )?; + } + } + + // build output row + let final_row = header_offset + 32; + region.assign_fixed( + || "mark s_output final", + self.s_output, + final_row, + || Value::known(Fr::one()), + )?; + digest_rlc.copy_advice( + || "copy digest rlc", + &mut region, + self.bytes_rlc, + final_row, + )?; + input_block.bytes_rlc.copy_advice( + || "copy input rlc", + &mut region, + self.copied_data, + final_row, + )?; + input_block.byte_counter.copy_advice( + || "copy bytes", + &mut region, + self.byte_counter, + final_row, + )?; + input_block.s_final.copy_advice( + || "copy final", + &mut region, + self.s_final_block, + final_row, + )?; + + region.assign_advice( + || "flush unused row", + self.trans_byte, + final_row, + || Value::known(Fr::zero()), + )?; + + region.assign_advice( + || "flush unused row", + self.helper, + 0, + || Value::known(Fr::zero()), + )?; + + Ok(export_cells + .chunks_exact(2) + .map(|ck_pair| (ck_pair[1].clone(), ck_pair[0].clone())) + .collect::>()) + }, + )?; + + Ok(output_cells.try_into().unwrap()) + } + + fn initialize_constant_table(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + layouter.assign_table( + || "byte range constant", + |mut tb| { + for i in 0..256 { + tb.assign_cell( + || "byte range", + self.byte_range, + i, + || Value::known(Fr::from(i as u64)), + )?; + } + + Ok(()) + }, + ) + } +} + +/// sha256 hasher for byte stream +#[derive(Debug)] +pub struct Hasher { + chip: CircuitConfig, + state: BlockState, + hasher_state: BlockInheritments, + cur_block: Vec, + length: usize, + block_usage: usize, +} + +impl Hasher { + /// return the number of 512-bit blocks which has been assigned + pub fn blocks(&self) -> usize { + self.block_usage + } + + /// return the number bytes current update, 0 indicate a clean status + pub fn updated_size(&self) -> usize { + self.length + } + + /// create a hasher, the circuit would be identify when block_usage is the same + pub fn new(chip: CircuitConfig, layouter: &mut impl Layouter) -> Result { + // constant part + chip.initialize_constant_table(layouter)?; + Table16Chip::load(chip.table16.clone(), layouter)?; + + let table16_chip = Table16Chip::construct::(chip.table16.clone()); + let state = table16_chip.initialization_vector(layouter)?; + let hasher_state = chip.initialize_block_head(layouter)?; + Ok(Self { + chip, + state, + hasher_state, + cur_block: Vec::with_capacity(BLOCK_SIZE * 4), + length: 0, + block_usage: 0, + }) + } + + /// update a single 512-bit block into layouter + fn update_block( + &mut self, + layouter: &mut impl Layouter, + chng: Value, + input: [BlockWord; BLOCK_SIZE], + padding: Option, + is_final: bool, + ) -> Result { + let table16_cfg = &self.chip.table16; + + let w_halves = table16_cfg.message_process(layouter, input)?; + self.hasher_state = self.chip.assign_input_block( + layouter, + chng, + self.hasher_state.clone(), + &w_halves[..16], + padding, + )?; + + let init_working_state = match &self.state { + Table16State::Compress(s) => s.as_ref().clone(), + Table16State::Dense(s) => table16_cfg.initialize(layouter, s.clone())?, + }; + + let compress_state = + table16_cfg.compress(layouter, init_working_state.clone(), w_halves)?; + let digest_state = table16_cfg.digest(layouter, compress_state, init_working_state)?; + + self.state = self + .chip + .assign_output_region( + layouter, + chng, + &digest_state.clone().map(|v| v.decompose()), + &self.hasher_state, + is_final, + ) + .map(|s| s.map(|v| v.into())) + .map(Table16State::Dense)?; + self.block_usage += 1; + + Ok(Table16State::Dense(digest_state)) + } + + fn block_transform(bytes: &[u8]) -> Vec { + assert_eq!(bytes.len(), BLOCK_SIZE * 4); + bytes + .chunks_exact(4) + .map(|bt| bt.iter().fold(0u32, |sum, v| sum * 256 + *v as u32)) + .map(Value::known) + .map(BlockWord) + .collect::>() + } + + /// Digest data, updating the internal state. + pub fn update( + &mut self, + layouter: &mut impl Layouter, + chng: Value, + mut data: &[u8], + ) -> Result<(), Error> { + use std::cmp::min; + + self.length += data.len(); + + // Fill the current block, if possible. + let remaining = BLOCK_SIZE * 4 - self.cur_block.len(); + let (l, r) = data.split_at(min(remaining, data.len())); + self.cur_block.extend_from_slice(l); + data = r; + + // If we still don't have a full block, we are done. + if self.cur_block.len() < BLOCK_SIZE * 4 { + return Ok(()); + } + + // transform to word block + let word_block = Self::block_transform(&self.cur_block); + + // Process the now-full current block. + self.update_block( + layouter, + chng, + word_block.as_slice().try_into().unwrap(), + None, + false, + )?; + + self.cur_block.clear(); + + // Process any additional full blocks. + let mut chunks_iter = data.chunks_exact(BLOCK_SIZE * 4); + for chunk in &mut chunks_iter { + let word_block = Self::block_transform(chunk); + self.update_block( + layouter, + chng, + word_block.as_slice().try_into().unwrap(), + None, + false, + )?; + } + + // Cache the remaining partial block, if any. + let rem = chunks_iter.remainder(); + self.cur_block.extend_from_slice(rem); + + Ok(()) + } + + /// generate the final digest and ready for new update. + pub fn finalize( + &mut self, + layouter: &mut impl Layouter, + chng: Value, + ) -> Result<([BlockWord; DIGEST_SIZE]), Error> { + // check padding requirement + let mut padding_pos = Some(self.cur_block.len()); + + // of course we have at least 1 byte left (or cur_block would have been compressed) + // push the additional 1bit + self.cur_block.push(128); + let remaining = BLOCK_SIZE * 4 - self.cur_block.len(); + + // if we have no enough space (64bit), we need a extra block + if remaining < 8 { + self.cur_block.resize(BLOCK_SIZE * 4, 0u8); + let word_block = Self::block_transform(&self.cur_block); + + self.update_block( + layouter, + chng, + word_block.as_slice().try_into().unwrap(), + padding_pos, + false, + )?; + + padding_pos = Some(0); + self.cur_block.clear(); + } + + self.cur_block.resize(BLOCK_SIZE * 4 - 8, 0u8); + self.cur_block + .extend(((self.length * 8) as u64).to_be_bytes()); + assert_eq!(self.cur_block.len(), BLOCK_SIZE * 4); + + let word_block = Self::block_transform(&self.cur_block); + + let digest_state = self.update_block( + layouter, + chng, + word_block.as_slice().try_into().unwrap(), + padding_pos, + true, + )?; + self.cur_block.clear(); + self.length = 0; + + let digest_state = match digest_state { + Table16State::Dense(s) => s, + _ => panic!("unexpected state type"), + }; + + Ok(digest_state.map(|s| s.value()).map(BlockWord)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use halo2_proofs::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit}; + + struct MyCircuit(Vec<(Vec, Option<[u32; 8]>)>); + + impl Circuit for MyCircuit { + type Config = CircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + unimplemented!() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + struct DevTable { + s_enable: Column, + input_rlc: Column, + input_len: Column, + hashes_rlc: Column, + is_effect: Column, + } + + impl SHA256Table for DevTable { + fn cols(&self) -> [Column; 5] { + [ + self.s_enable.into(), + self.input_rlc.into(), + self.input_len.into(), + self.hashes_rlc.into(), + self.is_effect.into(), + ] + } + } + + let dev_table = DevTable { + s_enable: meta.fixed_column(), + input_rlc: meta.advice_column(), + input_len: meta.advice_column(), + hashes_rlc: meta.advice_column(), + is_effect: meta.advice_column(), + }; + meta.enable_constant(dev_table.s_enable); + + let chng = Expression::Constant(Fr::from(0x1000u64)); + Self::Config::configure(meta, dev_table, chng) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let chng_v = Value::known(Fr::from(0x1000u64)); + let mut hasher = Hasher::new(config, &mut layouter)?; + + for (input, digest) in &self.0 { + hasher.update(&mut layouter, chng_v, input)?; + let ret_digest = hasher.finalize(&mut layouter, chng_v)?; + //println!("{:#x?}", ret_digest); + if let Some(check_digest) = digest { + for (w, check) in ret_digest.into_iter().zip(*check_digest) { + w.0.assert_if_known(|digest_word| *digest_word == check); + } + } + } + Ok(()) + } + } + + const DIGEST_ABC: [u32; 8] = [ + 0b10111010011110000001011010111111, + 0b10001111000000011100111111101010, + 0b01000001010000010100000011011110, + 0b01011101101011100010001000100011, + 0b10110000000000110110000110100011, + 0b10010110000101110111101010011100, + 0b10110100000100001111111101100001, + 0b11110010000000000001010110101101, + ]; + + const DIGEST_ABD: [u32; 8] = [ + 0xa52d159f, 0x262b2c6d, 0xdb724a61, 0x840befc3, 0x6eb30c88, 0x877a4030, 0xb65cbe86, + 0x298449c9, + ]; + + const DIGEST_BLOCK: [u32; 8] = [ + 0xffe054fe, 0x7ae0cb6d, 0xc65c3af9, 0xb61d5209, 0xf439851d, 0xb43d0ba5, 0x997337df, + 0x154668eb, + ]; + + const DIGEST_AX65: [u32; 8] = [ + 0x635361c4, 0x8bb9eab1, 0x4198e76e, 0xa8ab7f1a, 0x41685d6a, 0xd62aa914, 0x6d301d4f, + 0x17eb0ae0, + ]; + + const DIGEST_NIL: [u32; 8] = [ + 0xe3b0c442, 0x98fc1c14, 0x9afbf4c8, 0x996fb924, 0x27ae41e4, 0x649b934c, 0xa495991b, + 0x7852b855, + ]; + + #[test] + fn sha256_simple() { + let circuit = MyCircuit(vec![(vec![b'a', b'b', b'c'], Some(DIGEST_ABC))]); + let prover = match MockProver::::run(17, &circuit, vec![]) { + Ok(prover) => prover, + Err(e) => panic!("{e:#?}"), + }; + assert_eq!(prover.verify(), Ok(())); + } + + #[test] + fn sha256_multiple() { + let circuit = MyCircuit(vec![ + (vec![b'a', b'b', b'c'], Some(DIGEST_ABC)), + (vec![b'a', b'b', b'd'], Some(DIGEST_ABD)), + ]); + let prover = match MockProver::::run(17, &circuit, vec![]) { + Ok(prover) => prover, + Err(e) => panic!("{e:#?}"), + }; + assert_eq!(prover.verify(), Ok(())); + } + + #[test] + fn sha256_long() { + let circuit = MyCircuit(vec![(vec![b'a'; 65], Some(DIGEST_AX65))]); + let prover = match MockProver::::run(17, &circuit, vec![]) { + Ok(prover) => prover, + Err(e) => panic!("{e:#?}"), + }; + assert_eq!(prover.verify(), Ok(())); + } + + #[test] + fn sha256_long_block() { + let circuit = MyCircuit(vec![ + (vec![b'a'; 64], Some(DIGEST_BLOCK)), + (vec![b'a'; 65], Some(DIGEST_AX65)), + (vec![b'a'; 64], Some(DIGEST_BLOCK)), + (vec![b'a', b'b', b'c'], Some(DIGEST_ABC)), + ]); + let prover = match MockProver::::run(17, &circuit, vec![]) { + Ok(prover) => prover, + Err(e) => panic!("{e:#?}"), + }; + assert_eq!(prover.verify(), Ok(())); + } + + #[test] + fn sha256_nil() { + let circuit = MyCircuit(vec![ + (vec![], Some(DIGEST_NIL)), + (vec![], Some(DIGEST_NIL)), + (vec![], Some(DIGEST_NIL)), + ]); + let prover = match MockProver::::run(17, &circuit, vec![]) { + Ok(prover) => prover, + Err(e) => panic!("{e:#?}"), + }; + assert_eq!(prover.verify(), Ok(())); + } + + #[test] + fn sha256_padding_continue() { + let circuit = MyCircuit(vec![(vec![b'a'; 62], None)]); + let prover = match MockProver::::run(17, &circuit, vec![]) { + Ok(prover) => prover, + Err(e) => panic!("{e:#?}"), + }; + assert_eq!(prover.verify(), Ok(())); + } + + #[test] + fn sha256_complex() { + let circuit = MyCircuit(vec![ + (vec![b'a'; 65], Some(DIGEST_AX65)), + (vec![b'a', b'b', b'c'], Some(DIGEST_ABC)), + (vec![b'b'; 62], None), + (vec![b'c'; 128], None), + (vec![], Some(DIGEST_NIL)), + (vec![], Some(DIGEST_NIL)), + ]); + let prover = match MockProver::::run(17, &circuit, vec![]) { + Ok(prover) => prover, + Err(e) => panic!("{e:#?}"), + }; + assert_eq!(prover.verify(), Ok(())); + } + + #[test] + #[cfg(feature = "dev-graph")] + fn print_sha256_circuit() { + use plotters::prelude::*; + + let root = + BitMapBackend::new("sha-256-circuit-layout.png", (1024, 3480)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root + .titled( + "16-bit Table SHA-256 Chip with SHA256 table", + ("sans-serif", 60), + ) + .unwrap(); + + let circuit = MyCircuit(vec![ + (vec![b'a', b'b', b'c'], None), + (vec![b'a', b'b', b'c'], None), + ]); + halo2_proofs::dev::CircuitLayout::default() + .render::(13, &circuit, &root) + .unwrap(); + + let prover = match MockProver::<_>::run(13, &circuit, vec![]) { + Ok(prover) => prover, + Err(e) => panic!("{e:?}"), + }; + assert_eq!(prover.verify(), Ok(())); + } +} diff --git a/zkevm-circuits/src/sha256_circuit/test.rs b/zkevm-circuits/src/sha256_circuit/test.rs new file mode 100644 index 0000000000..1b0ce5b45d --- /dev/null +++ b/zkevm-circuits/src/sha256_circuit/test.rs @@ -0,0 +1,134 @@ +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + plonk::{create_proof, keygen_pk, keygen_vk, verify_proof, Circuit, ConstraintSystem, Error}, + transcript::{Blake2bRead, Blake2bWrite, Challenge255}, +}; +use rand::rngs::OsRng; + +use super::{circuit::*, BLOCK_SIZE}; + +use halo2_proofs::{ + halo2curves::bn256::{Bn256, Fr}, + plonk::{Advice, Any, Column, Expression, Fixed}, + poly::{ + commitment::ParamsProver, + kzg::{ + commitment::{KZGCommitmentScheme, ParamsKZG}, + multiopen::{ProverSHPLONK, VerifierSHPLONK}, + strategy::SingleStrategy, + }, + }, + transcript::{TranscriptReadBuffer, TranscriptWriterBuffer}, +}; + +const CAP_BLK: usize = 24; + +#[derive(Default, Clone, Copy)] +struct MyCircuit { + blocks: usize, +} + +impl Circuit for MyCircuit { + type Config = CircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + unimplemented!() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + struct DevTable { + s_enable: Column, + input_rlc: Column, + input_len: Column, + hashes_rlc: Column, + is_effect: Column, + } + + impl SHA256Table for DevTable { + fn cols(&self) -> [Column; 5] { + [ + self.s_enable.into(), + self.input_rlc.into(), + self.input_len.into(), + self.hashes_rlc.into(), + self.is_effect.into(), + ] + } + } + + let dev_table = DevTable { + s_enable: meta.fixed_column(), + input_rlc: meta.advice_column(), + input_len: meta.advice_column(), + hashes_rlc: meta.advice_column(), + is_effect: meta.advice_column(), + }; + meta.enable_constant(dev_table.s_enable); + + let chng = Expression::Constant(Fr::from(0x100u64)); + Self::Config::configure(meta, dev_table, chng) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let chng_v = Value::known(Fr::from(0x100u64)); + let mut hasher = Hasher::new(config, &mut layouter)?; + + for _ in 0..self.blocks { + hasher.update(&mut layouter, chng_v, &[b'a'; BLOCK_SIZE * 4])?; + } + if hasher.updated_size() > 0 { + hasher.finalize(&mut layouter, chng_v)?; + } + + for _ in hasher.blocks()..CAP_BLK { + hasher.update(&mut layouter, chng_v, &[])?; + hasher.finalize(&mut layouter, chng_v)?; + } + + Ok(()) + } +} + +#[test] +fn vk_stable() { + let k = 17; + + let params: ParamsKZG = ParamsKZG::new(k); + let empty_circuit: MyCircuit = MyCircuit { blocks: 0 }; + + // Initialize the proving key + let vk_from_empty = keygen_vk(¶ms, &empty_circuit).expect("keygen_vk should not fail"); + + let circuit = MyCircuit { blocks: 16 }; + let vk = keygen_vk(¶ms, &circuit).expect("keygen_vk should not fail"); + let pk = keygen_pk(¶ms, vk, &circuit).expect("keygen_pk should not fail"); + + // Create a proof + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + create_proof::, ProverSHPLONK<_>, _, _, _, _>( + ¶ms, + &pk, + &[circuit], + &[], + OsRng, + &mut transcript, + ) + .expect("proof generation should not fail"); + let proof: Vec = transcript.finalize(); + + let strategy = SingleStrategy::new(¶ms); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + verify_proof::, VerifierSHPLONK<_>, _, _, _>( + ¶ms, + &vk_from_empty, + strategy, + &[], + &mut transcript, + ) + .unwrap(); +} diff --git a/zkevm-circuits/src/super_circuit.rs b/zkevm-circuits/src/super_circuit.rs index ebd01375a7..d25aad051f 100644 --- a/zkevm-circuits/src/super_circuit.rs +++ b/zkevm-circuits/src/super_circuit.rs @@ -73,12 +73,16 @@ use crate::{ pi_circuit::{PiCircuit, PiCircuitConfig, PiCircuitConfigArgs}, poseidon_circuit::{PoseidonCircuit, PoseidonCircuitConfig, PoseidonCircuitConfigArgs}, rlp_circuit_fsm::{RlpCircuit, RlpCircuitConfig, RlpCircuitConfigArgs}, + sha256_circuit::{ + CircuitConfig as SHA256CircuitConfig, CircuitConfigArgs as SHA256CircuitConfigArgs, + SHA256Circuit, + }, sig_circuit::{SigCircuit, SigCircuitConfig, SigCircuitConfigArgs}, state_circuit::{StateCircuit, StateCircuitConfig, StateCircuitConfigArgs}, table::{ BlockTable, BytecodeTable, CopyTable, EccTable, ExpTable, KeccakTable, ModExpTable, - MptTable, PoseidonTable, PowOfRandTable, RlpFsmRlpTable as RlpTable, RwTable, SigTable, - TxTable, U16Table, U8Table, + MptTable, PoseidonTable, PowOfRandTable, RlpFsmRlpTable as RlpTable, RwTable, SHA256Table, + SigTable, TxTable, U16Table, U8Table, }, tx_circuit::{TxCircuit, TxCircuitConfig, TxCircuitConfigArgs}, util::{circuit_stats, log2_ceil, Challenges, SubCircuit, SubCircuitConfig}, @@ -117,6 +121,7 @@ pub struct SuperCircuitConfig { sig_circuit: SigCircuitConfig, modexp_circuit: ModExpCircuitConfig, ecc_circuit: EccCircuitConfig, + sha256_circuit: SHA256CircuitConfig, #[cfg(not(feature = "poseidon-codehash"))] bytecode_circuit: BytecodeCircuitConfig, #[cfg(feature = "poseidon-codehash")] @@ -189,6 +194,8 @@ impl SubCircuitConfig for SuperCircuitConfig { log_circuit_info(meta, "rlp table"); let keccak_table = KeccakTable::construct(meta); log_circuit_info(meta, "keccak table"); + let sha256_table = SHA256Table::construct(meta); + log_circuit_info(meta, "sha256 table"); let sig_table = SigTable::construct(meta); log_circuit_info(meta, "sig table"); let modexp_table = ModExpTable::construct(meta); @@ -213,6 +220,15 @@ impl SubCircuitConfig for SuperCircuitConfig { ); log_circuit_info(meta, "keccak circuit"); + let sha256_circuit = SHA256CircuitConfig::new( + meta, + SHA256CircuitConfigArgs { + sha256_table: sha256_table.clone(), + challenges: challenges_expr.clone(), + }, + ); + log_circuit_info(meta, "sha256 circuit"); + let poseidon_circuit = PoseidonCircuitConfig::new(meta, PoseidonCircuitConfigArgs { poseidon_table }); log_circuit_info(meta, "poseidon circuit"); @@ -333,6 +349,7 @@ impl SubCircuitConfig for SuperCircuitConfig { block_table: block_table.clone(), copy_table, keccak_table: keccak_table.clone(), + sha256_table, exp_table, sig_table, modexp_table, @@ -382,6 +399,7 @@ impl SubCircuitConfig for SuperCircuitConfig { copy_circuit, bytecode_circuit, keccak_circuit, + sha256_circuit, poseidon_circuit, pi_circuit, rlp_circuit, @@ -433,6 +451,8 @@ pub struct SuperCircuit< pub exp_circuit: ExpCircuit, /// Keccak Circuit pub keccak_circuit: KeccakCircuit, + /// SHA256 Circuit + pub sha256_circuit: SHA256Circuit, /// Poseidon hash Circuit pub poseidon_circuit: PoseidonCircuit, /// Sig Circuit @@ -486,6 +506,8 @@ impl< push("copy", copy); let keccak = KeccakCircuit::min_num_rows_block(block); push("keccak", keccak); + let sha256 = SHA256Circuit::min_num_rows_block(block); + push("sha256", sha256); let tx = TxCircuit::min_num_rows_block(block); push("tx", tx); let rlp = RlpCircuit::min_num_rows_block(block); @@ -567,6 +589,7 @@ impl< let exp_circuit = ExpCircuit::new_from_block(block); let modexp_circuit = ModExpCircuit::new_from_block(block); let keccak_circuit = KeccakCircuit::new_from_block(block); + let sha256_circuit = SHA256Circuit::new_from_block(block); let poseidon_circuit = PoseidonCircuit::new_from_block(block); let rlp_circuit = RlpCircuit::new_from_block(block); let sig_circuit = SigCircuit::new_from_block(block); @@ -582,6 +605,7 @@ impl< copy_circuit, exp_circuit, keccak_circuit, + sha256_circuit, poseidon_circuit, rlp_circuit, sig_circuit, @@ -640,6 +664,9 @@ impl< log::debug!("assigning keccak_circuit"); self.keccak_circuit .synthesize_sub(&config.keccak_circuit, challenges, layouter)?; + log::debug!("assigning sha256_circuit"); + self.sha256_circuit + .synthesize_sub(&config.sha256_circuit, challenges, layouter)?; log::debug!("assigning poseidon_circuit"); self.poseidon_circuit .synthesize_sub(&config.poseidon_circuit, challenges, layouter)?; diff --git a/zkevm-circuits/src/super_circuit/precompile_block_trace.rs b/zkevm-circuits/src/super_circuit/precompile_block_trace.rs index 28d77212b3..8e5d907e08 100644 --- a/zkevm-circuits/src/super_circuit/precompile_block_trace.rs +++ b/zkevm-circuits/src/super_circuit/precompile_block_trace.rs @@ -942,3 +942,53 @@ pub(crate) fn block_precompile_invalid_ec_pairing_fq_overflow() -> BlockTrace { .l2_trace() .clone() } + +#[cfg(feature = "scroll")] +pub(crate) fn block_precompile_sha256() -> BlockTrace { + let mut rng = ChaCha20Rng::seed_from_u64(2); + + let chain_id = *MOCK_CHAIN_ID; + + let bytecode_sha256 = PrecompileCallArgs { + name: "sha256: short bytes", + setup_code: bytecode! { + PUSH3(0x616263) + PUSH1(0x00) + MSTORE + }, + call_data_offset: 0x1d.into(), + call_data_length: 0x03.into(), + ret_offset: 0x20.into(), + ret_size: 0x20.into(), + address: PrecompileCalls::Sha256.address().to_word(), + ..Default::default() + } + .with_call_op(OpcodeId::CALL); + + let wallet_a = LocalWallet::new(&mut rng).with_chain_id(chain_id); + + let addr_a = wallet_a.address(); + let addr_b = address!("0x000000000000000000000000000000000000BBBB"); + + // 2 accounts and 1 tx. + TestContext::<2, 1>::new( + Some(vec![Word::zero()]), + |accs| { + accs[0].address(addr_a).balance(Word::from(1u64 << 24)); + accs[1] + .address(addr_b) + .balance(Word::from(1u64 << 20)) + .code(bytecode_sha256); + }, + |mut txs, accs| { + txs[0] + .from(wallet_a.clone()) + .to(accs[1].address) + .gas(Word::from(1_000_000u64)); + }, + |block, _tx| block.number(0xcafeu64), + ) + .unwrap() + .l2_trace() + .clone() +} diff --git a/zkevm-circuits/src/super_circuit/test.rs b/zkevm-circuits/src/super_circuit/test.rs index a1ac561f03..a7bcbfa09c 100644 --- a/zkevm-circuits/src/super_circuit/test.rs +++ b/zkevm-circuits/src/super_circuit/test.rs @@ -472,3 +472,16 @@ fn serial_test_super_circuit_precompile_invalid_ec_pairing_fq_overflow() { test_super_circuit::(block, circuits_params); } + +#[ignore] +#[cfg(feature = "scroll")] +#[test] +fn serial_test_super_circuit_precompile_sha256() { + const MAX_TXS: usize = 1; + const MAX_CALLDATA: usize = 0x180; + + let block = precompile_block_trace::block_precompile_sha256(); + let circuits_params = precomiple_super_circuits_params(MAX_TXS, MAX_CALLDATA); + + test_super_circuit::(block, circuits_params); +} diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index 1750fccd12..9810cee984 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -1493,6 +1493,127 @@ impl KeccakTable { } } +/// SHA256 Table, used to verify SHA256 hashing from RLC'ed input in precompile. +#[derive(Clone, Debug)] +pub struct SHA256Table { + /// True when the row is enabled + pub q_enable: Column, + /// True when the row is final + pub is_final: Column, + /// Byte array input as `RLC(reversed(input))` + pub input_rlc: Column, // RLC of input bytes + /// Byte array input length + pub input_len: Column, + /// RLC of the hash result + pub output_rlc: Column, // RLC of hash of input bytes +} + +impl LookupTable for SHA256Table { + fn columns(&self) -> Vec> { + vec![ + self.q_enable.into(), + self.is_final.into(), + self.input_rlc.into(), + self.input_len.into(), + self.output_rlc.into(), + ] + } + + fn annotations(&self) -> Vec { + vec![ + String::from("q_enable"), + String::from("is_final"), + String::from("input_rlc"), + String::from("input_len"), + String::from("output_rlc"), + ] + } +} + +impl SHA256Table { + /// Construct a new KeccakTable + pub fn construct(meta: &mut ConstraintSystem) -> Self { + Self { + q_enable: meta.fixed_column(), + is_final: meta.advice_column(), + input_len: meta.advice_column(), + input_rlc: meta.advice_column_in(SecondPhase), + output_rlc: meta.advice_column_in(SecondPhase), + } + } + + /// Generate the sha256 table assignments from a byte array pair of input/output. + /// Used only for dev_load + pub fn assignments( + entry: (&[u8], &[u8; 32]), + challenges: &Challenges>, + ) -> Vec<[Value; 4]> { + let (input, output) = entry; + let input_len = Value::known(F::from(input.len() as u64)); + let input_rlc = challenges + .keccak_input() + .map(|challenge| rlc::value(input.iter().rev(), challenge)); + let output_rlc = challenges + .keccak_input() + .map(|challenge| rlc::value(&Word::from_big_endian(output).to_le_bytes(), challenge)); + + vec![[Value::known(F::one()), input_rlc, input_len, output_rlc]] + } + + /// Provide this function for the case that we want to consume a sha256 + /// table but without running the full sha256 circuit + pub fn dev_load<'a, F: Field>( + &self, + layouter: &mut impl Layouter, + entries: impl IntoIterator, &'a [u8; 32])> + Clone, + challenges: &Challenges>, + ) -> Result<(), Error> { + layouter.assign_region( + || "sha256 table dev", + |mut region| { + let mut offset = 0; + for column in >::advice_columns(self) { + region.assign_fixed( + || "sha256 table all-zero row", + self.q_enable, + offset, + || Value::known(F::one()), + )?; + region.assign_advice( + || "sha256 table all-zero row", + column, + offset, + || Value::known(F::zero()), + )?; + } + offset += 1; + + let table_columns = >::advice_columns(self); + for (input, digest) in entries.clone() { + for row in Self::assignments((input, digest), challenges) { + region.assign_fixed( + || format!("table row {offset}"), + self.q_enable, + offset, + || Value::known(F::one()), + )?; + for (&column, value) in table_columns.iter().zip_eq(row) { + region.assign_advice( + || format!("table row {offset}"), + column, + offset, + || value, + )?; + } + offset += 1; + } + } + Ok(()) + }, + ) + } +} + /// Copy Table, used to verify copies of byte chunks between Memory, Bytecode, /// TxLogs and TxCallData. #[derive(Clone, Copy, Debug)] diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index de588068ce..433cab7d9e 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -12,7 +12,7 @@ use crate::{ use bus_mapping::{ circuit_input_builder::{ self, BigModExp, CircuitsParams, CopyEvent, EcAddOp, EcMulOp, EcPairingOp, ExpEvent, - PrecompileEvents, + PrecompileEvents, SHA256, }, Error, }; @@ -138,6 +138,11 @@ impl Block { self.precompile_events.get_modexp_events() } + /// Get sha256 operations from all precompiled contract calls in this block. + pub(crate) fn get_sha256(&self) -> Vec { + self.precompile_events.get_sha256_events() + } + pub(crate) fn print_evm_circuit_row_usage(&self) { let mut num_rows = 0; let mut counter = HashMap::new();