Skip to content
This repository was archived by the owner on Apr 18, 2025. It is now read-only.

Commit f05db90

Browse files
[feat] Precompile MODEXP (#520)
* feat: preliminary work * wip * finish assignment to ecrecover * fix: input length may be different than call data length (for precompiles) * fix: input bytes to ecrecover * minor edits * modexp table * wip wip wip wip * add auxdata of modexp * assign in modexp gadget * wip: fix most constraint failure * wip * wip: fix constraints * pass primary test * optimize phase2 cell usage and add more tests * add invalid detection and test * wip: induce u256 modexp * modexp event * wip: induce modexp table to evm * fix super circuit * induce modexp table done * regression pass * modexp circuit done * wip: test modexp table lookup * wip: testing modexp table lookup * pass modexptable lookup * pass u256 modexp circuit * refine dependencies * fix for merging develop * add modexp into super circuit * clippy * fmt * disable ecrecover temporarily. (lint not pass in CI but tests should be able to move forword) * post merging fixing * fmt and clippy * for invalid input * upgrade modexp circuit dep * fixes according to review * fmt * update dep, prune ff 0.13 * optimize by powofrand table * constraint for nil output * reverse geth step error indication for precompile * trivial fix for invalid case * fmt * error should not be throw for precompile failure * fix for merge * post-merge fixes for compile errors * reverse throwing precompile error in get_step_err * fmt * clippy lint * update `dev_load` of modexp table and some file structures * resume size limit for modexp * refactor and support garbage bytes in input * prune and lint * + resume the precompile error detection. + trivial logs * update modexp chip rows usage constant --------- Co-authored-by: Rohit Narurkar <[email protected]>
1 parent 7bca679 commit f05db90

File tree

24 files changed

+2031
-18
lines changed

24 files changed

+2031
-18
lines changed

Cargo.lock

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bus-mapping/src/circuit_input_builder.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ use ethers_core::{
3636
};
3737
use ethers_providers::JsonRpcClient;
3838
pub use execution::{
39-
CopyBytes, CopyDataType, CopyEvent, CopyEventStepsBuilder, CopyStep, EcAddOp, EcMulOp,
40-
EcPairingOp, EcPairingPair, ExecState, ExecStep, ExpEvent, ExpStep, NumberOrHash,
39+
BigModExp, CopyBytes, CopyDataType, CopyEvent, CopyEventStepsBuilder, CopyStep, EcAddOp,
40+
EcMulOp, EcPairingOp, EcPairingPair, ExecState, ExecStep, ExpEvent, ExpStep, NumberOrHash,
4141
PrecompileEvent, PrecompileEvents, N_BYTES_PER_PAIR, N_PAIRING_PER_OP,
4242
};
4343
use hex::decode_to_slice;

bus-mapping/src/circuit_input_builder/execution.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,20 @@ impl PrecompileEvents {
893893
})
894894
.collect()
895895
}
896+
/// Get all Big Modexp events.
897+
pub fn get_modexp_events(&self) -> Vec<BigModExp> {
898+
self.events
899+
.iter()
900+
.filter_map(|e| {
901+
if let PrecompileEvent::ModExp(op) = e {
902+
Some(op)
903+
} else {
904+
None
905+
}
906+
})
907+
.cloned()
908+
.collect()
909+
}
896910
}
897911

898912
/// I/O from a precompiled contract call.
@@ -906,6 +920,8 @@ pub enum PrecompileEvent {
906920
EcMul(EcMulOp),
907921
/// Represents the I/O from EcPairing call.
908922
EcPairing(Box<EcPairingOp>),
923+
/// Represents the I/O from Modexp call.
924+
ModExp(BigModExp),
909925
}
910926

911927
impl Default for PrecompileEvent {
@@ -1174,3 +1190,27 @@ impl EcPairingOp {
11741190
false
11751191
}
11761192
}
1193+
1194+
/// Event representating an exponentiation `a ^ b == d (mod m)` in precompile modexp.
1195+
#[derive(Clone, Debug)]
1196+
pub struct BigModExp {
1197+
/// Base `a` for the exponentiation.
1198+
pub base: Word,
1199+
/// Exponent `b` for the exponentiation.
1200+
pub exponent: Word,
1201+
/// Modulus `m`
1202+
pub modulus: Word,
1203+
/// Mod exponentiation result.
1204+
pub result: Word,
1205+
}
1206+
1207+
impl Default for BigModExp {
1208+
fn default() -> Self {
1209+
Self {
1210+
modulus: 1.into(),
1211+
base: Default::default(),
1212+
exponent: Default::default(),
1213+
result: Default::default(),
1214+
}
1215+
}
1216+
}

bus-mapping/src/evm/opcodes/precompiles/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ mod ec_add;
1111
mod ec_mul;
1212
mod ec_pairing;
1313
mod ecrecover;
14+
mod modexp;
1415

1516
use ec_add::opt_data as opt_data_ec_add;
1617
use ec_mul::opt_data as opt_data_ec_mul;
1718
use ec_pairing::opt_data as opt_data_ec_pairing;
1819
use ecrecover::opt_data as opt_data_ecrecover;
20+
use modexp::opt_data as opt_data_modexp;
1921

2022
type InOutRetData = (Option<Vec<u8>>, Option<Vec<u8>>, Option<Vec<u8>>);
2123

@@ -37,6 +39,7 @@ pub fn gen_associated_ops(
3739
PrecompileCalls::Bn128Add => opt_data_ec_add(input_bytes, output_bytes),
3840
PrecompileCalls::Bn128Mul => opt_data_ec_mul(input_bytes, output_bytes),
3941
PrecompileCalls::Bn128Pairing => opt_data_ec_pairing(input_bytes, output_bytes),
42+
PrecompileCalls::Modexp => opt_data_modexp(input_bytes, output_bytes),
4043
PrecompileCalls::Identity => (None, None),
4144
_ => {
4245
log::warn!("precompile {:?} unsupported in circuits", precompile);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use crate::{
2+
circuit_input_builder::{BigModExp, PrecompileEvent},
3+
precompile::{ModExpAuxData, PrecompileAuxData},
4+
};
5+
6+
use eth_types::Word;
7+
8+
pub(crate) fn opt_data(
9+
input_bytes: Option<Vec<u8>>,
10+
output_bytes: Option<Vec<u8>>,
11+
) -> (Option<PrecompileEvent>, Option<PrecompileAuxData>) {
12+
let aux_data = ModExpAuxData::new(
13+
input_bytes.unwrap_or_default(),
14+
output_bytes.unwrap_or_default(),
15+
);
16+
if aux_data.valid {
17+
let event = BigModExp {
18+
base: Word::from_big_endian(&aux_data.inputs[0]),
19+
exponent: Word::from_big_endian(&aux_data.inputs[1]),
20+
modulus: Word::from_big_endian(&aux_data.inputs[2]),
21+
result: Word::from_big_endian(&aux_data.output),
22+
};
23+
(
24+
Some(PrecompileEvent::ModExp(event)),
25+
Some(PrecompileAuxData::Modexp(aux_data)),
26+
)
27+
} else {
28+
(None, Some(PrecompileAuxData::Modexp(aux_data)))
29+
}
30+
}

bus-mapping/src/precompile.rs

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,22 @@ pub(crate) fn execute_precompiled(address: &Address, input: &[u8], gas: u64) ->
2020
};
2121

2222
match precompile_fn(input, gas) {
23-
Ok((gas_cost, return_value)) => (return_value, gas_cost),
23+
Ok((gas_cost, return_value)) => {
24+
match PrecompileCalls::from(address.0[19]) {
25+
// FIXME: override the behavior of invalid input
26+
PrecompileCalls::Modexp => {
27+
let (input_valid, [_, _, modulus_len]) = ModExpAuxData::check_input(input);
28+
if input_valid {
29+
// detect some edge cases like modulus = 0
30+
assert_eq!(modulus_len.as_usize(), return_value.len());
31+
(return_value, gas_cost)
32+
} else {
33+
(vec![], gas)
34+
}
35+
}
36+
_ => (return_value, gas_cost),
37+
}
38+
}
2439
Err(_) => (vec![], gas),
2540
}
2641
}
@@ -117,6 +132,7 @@ impl PrecompileCalls {
117132
match self {
118133
Self::Ecrecover | Self::Bn128Add => Some(128),
119134
Self::Bn128Mul => Some(96),
135+
Self::Modexp => Some(MODEXP_INPUT_LIMIT),
120136
_ => None,
121137
}
122138
}
@@ -168,6 +184,91 @@ impl EcrecoverAuxData {
168184
}
169185
}
170186

187+
/// size limit of modexp
188+
pub const MODEXP_SIZE_LIMIT: usize = 32;
189+
/// size of input limit
190+
pub const MODEXP_INPUT_LIMIT: usize = 192;
191+
192+
/// Auxiliary data for Modexp
193+
#[derive(Clone, Debug, Default, PartialEq, Eq)]
194+
pub struct ModExpAuxData {
195+
/// The specified len of inputs: [base, exp, modulus]
196+
pub input_lens: [Word; 3],
197+
/// Input value [base, exp, modulus], limited to SIZE_LIMIT
198+
pub inputs: [[u8; MODEXP_SIZE_LIMIT]; 3],
199+
/// Input valid.
200+
pub valid: bool,
201+
/// len of output, limited to lens of moduls, but can be 0
202+
pub output_len: usize,
203+
/// output of modexp.
204+
pub output: [u8; MODEXP_SIZE_LIMIT],
205+
/// backup of input memory
206+
pub input_memory: Vec<u8>,
207+
/// backup of output memory
208+
pub output_memory: Vec<u8>,
209+
}
210+
211+
impl ModExpAuxData {
212+
fn parse_memory_to_value(mem: &[u8]) -> [u8; MODEXP_SIZE_LIMIT] {
213+
let mut value_bytes = [0u8; MODEXP_SIZE_LIMIT];
214+
if !mem.is_empty() {
215+
value_bytes.as_mut_slice()[(MODEXP_SIZE_LIMIT - mem.len())..].copy_from_slice(mem);
216+
}
217+
value_bytes
218+
}
219+
220+
/// check input
221+
pub fn check_input(input: &[u8]) -> (bool, [Word; 3]) {
222+
let mut i = input.chunks(32);
223+
let base_len = Word::from_big_endian(i.next().unwrap_or(&[]));
224+
let exp_len = Word::from_big_endian(i.next().unwrap_or(&[]));
225+
let modulus_len = Word::from_big_endian(i.next().unwrap_or(&[]));
226+
227+
let limit = Word::from(MODEXP_SIZE_LIMIT);
228+
229+
let input_valid = base_len <= limit && exp_len <= limit && modulus_len <= limit;
230+
231+
(input_valid, [base_len, exp_len, modulus_len])
232+
}
233+
234+
/// Create a new instance of modexp auxiliary data.
235+
pub fn new(mut mem_input: Vec<u8>, output: Vec<u8>) -> Self {
236+
let input_memory = mem_input.clone();
237+
let output_memory = output.clone();
238+
239+
let (input_valid, [base_len, exp_len, modulus_len]) = Self::check_input(&mem_input);
240+
241+
let base_mem_len = if input_valid { base_len.as_usize() } else { 0 };
242+
let exp_mem_len = if input_valid { exp_len.as_usize() } else { 0 };
243+
let modulus_mem_len = if input_valid {
244+
modulus_len.as_usize()
245+
} else {
246+
0
247+
};
248+
249+
mem_input.resize(96 + base_mem_len + exp_mem_len + modulus_mem_len, 0);
250+
let mut cur_input_begin = &mem_input[96..];
251+
252+
let base = Self::parse_memory_to_value(&cur_input_begin[..base_mem_len]);
253+
cur_input_begin = &cur_input_begin[base_mem_len..];
254+
let exp = Self::parse_memory_to_value(&cur_input_begin[..exp_mem_len]);
255+
cur_input_begin = &cur_input_begin[exp_mem_len..];
256+
let modulus = Self::parse_memory_to_value(&cur_input_begin[..modulus_mem_len]);
257+
let output_len = output.len();
258+
let output = Self::parse_memory_to_value(&output);
259+
260+
Self {
261+
valid: input_valid,
262+
input_lens: [base_len, exp_len, modulus_len],
263+
inputs: [base, exp, modulus],
264+
output,
265+
output_len,
266+
input_memory,
267+
output_memory,
268+
}
269+
}
270+
}
271+
171272
/// Auxiliary data for EcAdd, i.e. P + Q = R
172273
#[derive(Clone, Debug, Default, PartialEq, Eq)]
173274
pub struct EcAddAuxData {
@@ -245,6 +346,8 @@ pub struct EcPairingAuxData(pub EcPairingOp);
245346
pub enum PrecompileAuxData {
246347
/// Ecrecover.
247348
Ecrecover(EcrecoverAuxData),
349+
/// Modexp.
350+
Modexp(ModExpAuxData),
248351
/// EcAdd.
249352
EcAdd(EcAddAuxData),
250353
/// EcMul.

eth-types/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ impl fmt::Debug for GethExecStep {
368368
.field("op", &self.op)
369369
.field("gas", &format_args!("{}", self.gas.0))
370370
.field("gas_cost", &format_args!("{}", self.gas_cost.0))
371+
.field("refund", &format_args!("{}", self.refund.0))
371372
.field("depth", &self.depth)
372373
.field("error", &self.error)
373374
.field("stack", &self.stack)

zkevm-circuits/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ serde_json = "1.0.78"
3333

3434
hash-circuit = { package = "poseidon-circuit", git = "https://github.com/scroll-tech/poseidon-circuit.git", branch = "scroll-dev-0619", features=['short']}
3535
#mpt-circuits = { package = "halo2-mpt-circuits", path = "../../mpt-circuit" }
36+
misc-precompiled-circuit = { package = "rmd160-circuits", git = "https://github.com/scroll-tech/misc-precompiled-circuit.git", branch = "integration" }
3637

3738
halo2-base = { git = "https://github.com/scroll-tech/halo2-lib", branch = "develop", default-features=false, features=["halo2-pse","display"] }
3839
halo2-ecc = { git = "https://github.com/scroll-tech/halo2-lib", branch = "develop", default-features=false, features=["halo2-pse","display"] }

0 commit comments

Comments
 (0)