Skip to content

Commit

Permalink
Refactor: PrecompileGadget and precompile states (scroll-tech#1029)
Browse files Browse the repository at this point in the history
* refactoring of bus-mapping and adding input/return rlc fields to state gadgets

* fix everything except OOG cases

* fix: oog cases handled

* standardize approach for base precompile as well
  • Loading branch information
roynalnaruto authored Nov 13, 2023
1 parent 5cb5032 commit d45faa5
Show file tree
Hide file tree
Showing 22 changed files with 917 additions and 598 deletions.
53 changes: 37 additions & 16 deletions bus-mapping/src/circuit_input_builder/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -976,13 +976,17 @@ impl EcAddOp {
})
};

assert_eq!(input.len(), 128);
assert_eq!(output.len(), 64);
let mut resized_input = input.to_vec();
resized_input.resize(128, 0u8);
let mut resized_output = output.to_vec();
resized_output.resize(64, 0u8);

let mut buf = [0u8; 32];
let opt_point_p: Option<G1Affine> = g1_from_slice(&mut buf, &input[0x00..0x40]).into();
let opt_point_q: Option<G1Affine> = g1_from_slice(&mut buf, &input[0x40..0x80]).into();
let point_r_evm = g1_from_slice(&mut buf, &output[0x00..0x40]).unwrap();
let opt_point_p: Option<G1Affine> =
g1_from_slice(&mut buf, &resized_input[0x00..0x40]).into();
let opt_point_q: Option<G1Affine> =
g1_from_slice(&mut buf, &resized_input[0x40..0x80]).into();
let point_r_evm = g1_from_slice(&mut buf, &resized_output[0x00..0x40]).unwrap();
let point_r_cal = opt_point_p.zip(opt_point_q).map(|(point_p, point_q)| {
let point_r: G1Affine = point_p.add(&point_q).into();
debug_assert_eq!(
Expand All @@ -994,12 +998,12 @@ impl EcAddOp {

Self {
p: (
U256::from_big_endian(&input[0x00..0x20]),
U256::from_big_endian(&input[0x20..0x40]),
U256::from_big_endian(&resized_input[0x00..0x20]),
U256::from_big_endian(&resized_input[0x20..0x40]),
),
q: (
U256::from_big_endian(&input[0x40..0x60]),
U256::from_big_endian(&input[0x60..0x80]),
U256::from_big_endian(&resized_input[0x40..0x60]),
U256::from_big_endian(&resized_input[0x60..0x80]),
),
r: point_r_cal,
}
Expand Down Expand Up @@ -1078,14 +1082,17 @@ impl EcMulOp {
})
};

assert_eq!(input.len(), 96);
assert_eq!(output.len(), 64);
let mut resized_input = input.to_vec();
resized_input.resize(96, 0u8);
let mut resized_output = output.to_vec();
resized_output.resize(64, 0u8);

let mut buf = [0u8; 32];

let opt_point_p: Option<G1Affine> = g1_from_slice(&mut buf, &input[0x00..0x40]).into();
let s = Fr::from_raw(Word::from_big_endian(&input[0x40..0x60]).0);
let point_r_evm = g1_from_slice(&mut buf, &output[0x00..0x40]).unwrap();
let opt_point_p: Option<G1Affine> =
g1_from_slice(&mut buf, &resized_input[0x00..0x40]).into();
let s = Fr::from_raw(Word::from_big_endian(&resized_input[0x40..0x60]).0);
let point_r_evm = g1_from_slice(&mut buf, &resized_output[0x00..0x40]).unwrap();
let point_r_cal = opt_point_p.map(|point_p| {
let point_r: G1Affine = point_p.mul(s).into();
debug_assert_eq!(
Expand All @@ -1097,8 +1104,8 @@ impl EcMulOp {

Self {
p: (
U256::from_big_endian(&input[0x00..0x20]),
U256::from_big_endian(&input[0x20..0x40]),
U256::from_big_endian(&resized_input[0x00..0x20]),
U256::from_big_endian(&resized_input[0x20..0x40]),
),
s,
r: point_r_cal,
Expand Down Expand Up @@ -1251,6 +1258,12 @@ pub struct EcPairingOp {
pub pairs: [EcPairingPair; N_PAIRING_PER_OP],
/// Result from the pairing check.
pub output: Word,
/// Input bytes to the ecPairing call.
pub input_bytes: Vec<u8>,
/// Output bytes from the ecPairing call.
pub output_bytes: Vec<u8>,
/// Bytes returned back to the caller.
pub return_bytes: Vec<u8>,
}

impl Default for EcPairingOp {
Expand Down Expand Up @@ -1283,6 +1296,13 @@ impl Default for EcPairingOp {
},
],
output: Word::zero(),
// It does not matter what the input bytes and return bytes are in this case, as this
// operation is a filler op. It is not an op constructed from an EVM call to the
// ecPairing precompiled contract. Hence the input/return bytes will not be
// constrained.
input_bytes: vec![],
output_bytes: vec![],
return_bytes: vec![],
}
}
}
Expand Down Expand Up @@ -1321,6 +1341,7 @@ impl EcPairingOp {
EcPairingPair::new(G1Affine::identity(), G2Affine::generator()),
],
output: 1.into(),
..Default::default()
}
}
}
Expand Down
10 changes: 6 additions & 4 deletions bus-mapping/src/evm/opcodes/callop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {
.iter()
.filter(|(_, _, is_mask)| !*is_mask)
.map(|t| t.0)
.collect();
.collect::<Vec<u8>>();
state.push_copy(
&mut exec_step,
CopyEvent {
Expand Down Expand Up @@ -398,7 +398,7 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {
.iter()
.filter(|(_, _, is_mask)| !*is_mask)
.map(|t| t.0)
.collect();
.collect::<Vec<u8>>();
state.push_copy(
&mut exec_step,
CopyEvent {
Expand Down Expand Up @@ -433,7 +433,7 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {
.iter()
.filter(|(_, _, is_mask)| !*is_mask)
.map(|t| t.0)
.collect();
.collect::<Vec<u8>>();
state.push_copy(
&mut exec_step,
CopyEvent {
Expand Down Expand Up @@ -484,7 +484,9 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {
geth_steps[1].clone(),
callee_call.clone(),
precompile_call,
(input_bytes, output_bytes, returned_bytes),
&input_bytes.unwrap_or_default(),
&output_bytes.unwrap_or_default(),
&returned_bytes.unwrap_or_default(),
)?;

// Set gas left and gas cost for precompile step.
Expand Down
18 changes: 5 additions & 13 deletions bus-mapping/src/evm/opcodes/precompiles/ec_add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,12 @@ use crate::{
};

pub(crate) fn opt_data(
input_bytes: Option<Vec<u8>>,
output_bytes: Option<Vec<u8>>,
input_bytes: &[u8],
output_bytes: &[u8],
return_bytes: &[u8],
) -> (Option<PrecompileEvent>, Option<PrecompileAuxData>) {
let input_bytes = input_bytes.map_or(vec![0u8; 128], |mut bytes| {
bytes.resize(128, 0u8);
bytes
});
let output_bytes = output_bytes.map_or(vec![0u8; 64], |mut bytes| {
bytes.resize(64, 0u8);
bytes
});

let aux_data = EcAddAuxData::new(&input_bytes, &output_bytes);
let ec_add_op = EcAddOp::new_from_bytes(&input_bytes, &output_bytes);
let aux_data = EcAddAuxData::new(input_bytes, output_bytes, return_bytes);
let ec_add_op = EcAddOp::new_from_bytes(input_bytes, output_bytes);

(
Some(PrecompileEvent::EcAdd(ec_add_op)),
Expand Down
18 changes: 5 additions & 13 deletions bus-mapping/src/evm/opcodes/precompiles/ec_mul.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,12 @@ use crate::{
};

pub(crate) fn opt_data(
input_bytes: Option<Vec<u8>>,
output_bytes: Option<Vec<u8>>,
input_bytes: &[u8],
output_bytes: &[u8],
return_bytes: &[u8],
) -> (Option<PrecompileEvent>, Option<PrecompileAuxData>) {
let input_bytes = input_bytes.map_or(vec![0u8; 96], |mut bytes| {
bytes.resize(96, 0u8);
bytes
});
let output_bytes = output_bytes.map_or(vec![0u8; 64], |mut bytes| {
bytes.resize(64, 0u8);
bytes
});

let aux_data = EcMulAuxData::new(&input_bytes, &output_bytes);
let ec_mul_op = EcMulOp::new_from_bytes(&input_bytes, &output_bytes);
let aux_data = EcMulAuxData::new(input_bytes, output_bytes, return_bytes);
let ec_mul_op = EcMulOp::new_from_bytes(input_bytes, output_bytes);

(
Some(PrecompileEvent::EcMul(ec_mul_op)),
Expand Down
39 changes: 24 additions & 15 deletions bus-mapping/src/evm/opcodes/precompiles/ec_pairing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,51 @@ use crate::{
};

pub(crate) fn opt_data(
input_bytes: Option<Vec<u8>>,
output_bytes: Option<Vec<u8>>,
input_bytes: &[u8],
output_bytes: &[u8],
return_bytes: &[u8],
) -> (Option<PrecompileEvent>, Option<PrecompileAuxData>) {
// assertions.
let pairing_check = output_bytes.map_or(U256::zero(), |output| {
let pairing_check = if output_bytes.is_empty() {
U256::zero()
} else {
debug_assert_eq!(
output.len(),
output_bytes.len(),
32,
"len(output)={:?}, expected={:?}",
output.len(),
output_bytes.len(),
32
);
U256::from(output[31])
});
U256::from_big_endian(output_bytes)
};
debug_assert!(
pairing_check.eq(&U256::one()) || pairing_check.is_zero(),
"ecPairing returns 1 or 0"
);
if input_bytes.is_none() {
if input_bytes.is_empty() {
debug_assert!(
pairing_check.eq(&U256::one()),
"for zero inputs, pairing check == 1"
);
}

let op = if let Some(input) = input_bytes {
if (input.len() > N_PAIRING_PER_OP * N_BYTES_PER_PAIR)
|| (input.len() % N_BYTES_PER_PAIR != 0)
let op = if !input_bytes.is_empty() {
if (input_bytes.len() > N_PAIRING_PER_OP * N_BYTES_PER_PAIR)
|| (input_bytes.len() % N_BYTES_PER_PAIR != 0)
{
return (
None,
Some(PrecompileAuxData::EcPairing(Box::new(Err(
EcPairingError::InvalidInputLen(input),
EcPairingError::InvalidInputLen(input_bytes.to_vec()),
)))),
);
}
debug_assert!(
input.len() % N_BYTES_PER_PAIR == 0
&& input.len() <= N_PAIRING_PER_OP * N_BYTES_PER_PAIR
input_bytes.len() % N_BYTES_PER_PAIR == 0
&& input_bytes.len() <= N_PAIRING_PER_OP * N_BYTES_PER_PAIR
);
// process input bytes.
let mut pairs = input
let mut pairs = input_bytes
.chunks_exact(N_BYTES_PER_PAIR)
.map(|chunk| {
// process <= 192 bytes chunk at a time.
Expand All @@ -73,12 +76,18 @@ pub(crate) fn opt_data(
EcPairingOp {
pairs: <[_; N_PAIRING_PER_OP]>::try_from(pairs).unwrap(),
output: pairing_check,
input_bytes: input_bytes.to_vec(),
output_bytes: output_bytes.to_vec(),
return_bytes: return_bytes.to_vec(),
}
} else {
let pairs = [EcPairingPair::padding_pair(); N_PAIRING_PER_OP];
EcPairingOp {
pairs,
output: pairing_check,
input_bytes: vec![],
output_bytes: output_bytes.to_vec(),
return_bytes: return_bytes.to_vec(),
}
};

Expand Down
15 changes: 4 additions & 11 deletions bus-mapping/src/evm/opcodes/precompiles/ecrecover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,11 @@ use crate::{
};

pub(crate) fn opt_data(
input_bytes: Option<Vec<u8>>,
output_bytes: Option<Vec<u8>>,
input_bytes: &[u8],
output_bytes: &[u8],
return_bytes: &[u8],
) -> (Option<PrecompileEvent>, Option<PrecompileAuxData>) {
let input_bytes = input_bytes.map_or(vec![0u8; 128], |mut bytes| {
bytes.resize(128, 0u8);
bytes
});
let output_bytes = output_bytes.map_or(vec![0u8; 32], |mut bytes| {
bytes.resize(32, 0u8);
bytes
});
let aux_data = EcrecoverAuxData::new(input_bytes, output_bytes);
let aux_data = EcrecoverAuxData::new(input_bytes, output_bytes, return_bytes);

// We skip the validation through sig circuit if r or s was not in canonical form.
let opt_sig_r: Option<Fq> = Fq::from_bytes(&aux_data.sig_r.to_le_bytes()).into();
Expand Down
38 changes: 27 additions & 11 deletions bus-mapping/src/evm/opcodes/precompiles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use eth_types::{GethExecStep, ToWord, Word};
use crate::{
circuit_input_builder::{Call, CircuitInputStateRef, ExecState, ExecStep},
operation::CallContextField,
precompile::PrecompileCalls,
precompile::{PrecompileAuxData, PrecompileCalls},
Error,
};

Expand All @@ -19,14 +19,14 @@ use ec_pairing::opt_data as opt_data_ec_pairing;
use ecrecover::opt_data as opt_data_ecrecover;
use modexp::opt_data as opt_data_modexp;

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

pub fn gen_associated_ops(
state: &mut CircuitInputStateRef,
geth_step: GethExecStep,
call: Call,
precompile: PrecompileCalls,
(input_bytes, output_bytes, _returned_bytes): InOutRetData,
input_bytes: &[u8],
output_bytes: &[u8],
return_bytes: &[u8],
) -> Result<ExecStep, Error> {
assert_eq!(call.code_address(), Some(precompile.into()));
let mut exec_step = state.new_step(&geth_step)?;
Expand All @@ -35,15 +35,31 @@ pub fn gen_associated_ops(
common_call_ctx_reads(state, &mut exec_step, &call)?;

let (opt_event, aux_data) = match precompile {
PrecompileCalls::Ecrecover => opt_data_ecrecover(input_bytes, output_bytes),
PrecompileCalls::Bn128Add => opt_data_ec_add(input_bytes, output_bytes),
PrecompileCalls::Bn128Mul => opt_data_ec_mul(input_bytes, output_bytes),
PrecompileCalls::Bn128Pairing => opt_data_ec_pairing(input_bytes, output_bytes),
PrecompileCalls::Modexp => opt_data_modexp(input_bytes, output_bytes),
PrecompileCalls::Identity => (None, None),
PrecompileCalls::Ecrecover => opt_data_ecrecover(input_bytes, output_bytes, return_bytes),
PrecompileCalls::Bn128Add => opt_data_ec_add(input_bytes, output_bytes, return_bytes),
PrecompileCalls::Bn128Mul => opt_data_ec_mul(input_bytes, output_bytes, return_bytes),
PrecompileCalls::Bn128Pairing => {
opt_data_ec_pairing(input_bytes, output_bytes, return_bytes)
}
PrecompileCalls::Modexp => opt_data_modexp(input_bytes, output_bytes, return_bytes),
PrecompileCalls::Identity => (
None,
Some(PrecompileAuxData::Identity {
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);
(None, None)
(
None,
Some(PrecompileAuxData::Base {
input_bytes: input_bytes.to_vec(),
output_bytes: output_bytes.to_vec(),
return_bytes: return_bytes.to_vec(),
}),
)
}
};
log::trace!("precompile event {opt_event:?}, aux data {aux_data:?}");
Expand Down
10 changes: 4 additions & 6 deletions bus-mapping/src/evm/opcodes/precompiles/modexp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ use crate::{
use eth_types::Word;

pub(crate) fn opt_data(
input_bytes: Option<Vec<u8>>,
output_bytes: Option<Vec<u8>>,
input_bytes: &[u8],
output_bytes: &[u8],
return_bytes: &[u8],
) -> (Option<PrecompileEvent>, Option<PrecompileAuxData>) {
let aux_data = ModExpAuxData::new(
input_bytes.unwrap_or_default(),
output_bytes.unwrap_or_default(),
);
let aux_data = ModExpAuxData::new(input_bytes, output_bytes, return_bytes);
if aux_data.valid {
let event = BigModExp {
base: Word::from_big_endian(&aux_data.inputs[0]),
Expand Down
Loading

0 comments on commit d45faa5

Please sign in to comment.