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

Commit 2caf508

Browse files
roynalnarutolispc
andauthored
EcPairing EVM Circuit Gadget (Valid Inputs) (#675)
* feat(bus-mapping): ecPairing and refactor * feat(circuits): ecPairing * wip * checks for evm_input_rlc * fix: input rlc (ecc circuit) for ecPairing * refactor copy circuit test (max rows changed) * chore: docs for padding pairs --------- Co-authored-by: Zhang Zhuo <[email protected]>
1 parent 69f4fe1 commit 2caf508

File tree

16 files changed

+966
-118
lines changed

16 files changed

+966
-118
lines changed

bus-mapping/src/circuit_input_builder.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ use ethers_core::{
3737
use ethers_providers::JsonRpcClient;
3838
pub use execution::{
3939
CopyBytes, CopyDataType, CopyEvent, CopyEventStepsBuilder, CopyStep, EcAddOp, EcMulOp,
40-
EcPairingOp, ExecState, ExecStep, ExpEvent, ExpStep, NumberOrHash, PrecompileEvent,
41-
PrecompileEvents,
40+
EcPairingOp, EcPairingPair, ExecState, ExecStep, ExpEvent, ExpStep, NumberOrHash,
41+
PrecompileEvent, PrecompileEvents, N_BYTES_PER_PAIR, N_PAIRING_PER_OP,
4242
};
4343
use hex::decode_to_slice;
4444

@@ -116,7 +116,7 @@ impl Default for CircuitsParams {
116116
max_inner_blocks: 64,
117117
// TODO: Check whether this value is correct or we should increase/decrease based on
118118
// this lib tests
119-
max_copy_rows: 1000,
119+
max_copy_rows: 2000,
120120
max_mpt_rows: 1000,
121121
max_exp_steps: 1000,
122122
max_bytecode: 512,

bus-mapping/src/circuit_input_builder/execution.rs

+90-26
Original file line numberDiff line numberDiff line change
@@ -1060,53 +1060,117 @@ impl EcMulOp {
10601060
/// call are < 4, we append (G1::infinity, G2::generator) until we have the required no. of inputs.
10611061
pub const N_PAIRING_PER_OP: usize = 4;
10621062

1063+
/// The number of bytes taken to represent a pair (G1, G2).
1064+
pub const N_BYTES_PER_PAIR: usize = 192;
1065+
1066+
/// Pair of (G1, G2).
1067+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1068+
pub struct EcPairingPair {
1069+
/// G1 point.
1070+
pub g1_point: G1Affine,
1071+
/// G2 point.
1072+
pub g2_point: G2Affine,
1073+
}
1074+
1075+
impl EcPairingPair {
1076+
/// Returns the big-endian representation of the G1 point in the pair.
1077+
pub fn g1_bytes_be(&self) -> Vec<u8> {
1078+
std::iter::empty()
1079+
.chain(self.g1_point.x.to_bytes().iter().rev())
1080+
.chain(self.g1_point.y.to_bytes().iter().rev())
1081+
.cloned()
1082+
.collect()
1083+
}
1084+
1085+
/// Returns the big-endian representation of the G2 point in the pair.
1086+
pub fn g2_bytes_be(&self) -> Vec<u8> {
1087+
std::iter::empty()
1088+
.chain(self.g2_point.x.c0.to_bytes().iter().rev())
1089+
.chain(self.g2_point.x.c1.to_bytes().iter().rev())
1090+
.chain(self.g2_point.y.c0.to_bytes().iter().rev())
1091+
.chain(self.g2_point.y.c1.to_bytes().iter().rev())
1092+
.cloned()
1093+
.collect()
1094+
}
1095+
1096+
/// Returns the uncompressed big-endian byte representation of the (G1, G2) pair.
1097+
pub fn to_bytes_be(&self) -> Vec<u8> {
1098+
std::iter::empty()
1099+
.chain(self.g1_point.x.to_bytes().iter().rev())
1100+
.chain(self.g1_point.y.to_bytes().iter().rev())
1101+
.chain(self.g2_point.x.c0.to_bytes().iter().rev())
1102+
.chain(self.g2_point.x.c1.to_bytes().iter().rev())
1103+
.chain(self.g2_point.y.c0.to_bytes().iter().rev())
1104+
.chain(self.g2_point.y.c1.to_bytes().iter().rev())
1105+
.cloned()
1106+
.collect()
1107+
}
1108+
1109+
/// Create a new pair.
1110+
pub fn new(g1_point: G1Affine, g2_point: G2Affine) -> Self {
1111+
Self { g1_point, g2_point }
1112+
}
1113+
1114+
/// Padding pair for ECC circuit. The pairing check is done with a constant number
1115+
/// `N_PAIRING_PER_OP` of (G1, G2) pairs. The ECC circuit under the hood uses halo2-lib to
1116+
/// compute the multi-miller loop, which allows `(G1::Infinity, G2::Generator)` pair to skip
1117+
/// the loop for that pair. So in case the EVM inputs are less than `N_PAIRING_PER_OP` we pad
1118+
/// the ECC Circuit inputs by this pair. Any EVM input of `(G1::Infinity, G2)` or
1119+
/// `(G1, G2::Infinity)` is also transformed into `(G1::Infinity, G2::Generator)`.
1120+
pub fn ecc_padding() -> Self {
1121+
Self {
1122+
g1_point: G1Affine::identity(),
1123+
g2_point: G2Affine::generator(),
1124+
}
1125+
}
1126+
1127+
/// Padding pair for EVM circuit. The pairing check is done with a constant number
1128+
/// `N_PAIRING_PER_OP` of (G1, G2) pairs. In case EVM inputs are less in number, we pad them
1129+
/// with `(G1::Infinity, G2::Infinity)` for simplicity.
1130+
pub fn evm_padding() -> Self {
1131+
Self {
1132+
g1_point: G1Affine::identity(),
1133+
g2_point: G2Affine::identity(),
1134+
}
1135+
}
1136+
}
1137+
10631138
/// EcPairing operation
1064-
#[derive(Clone, Debug)]
1139+
#[derive(Clone, Debug, PartialEq, Eq)]
10651140
pub struct EcPairingOp {
1066-
/// tuples of G1 and G2 points.
1067-
pub inputs: [(G1Affine, G2Affine); N_PAIRING_PER_OP],
1141+
/// tuples of G1 and G2 points supplied to the ECC circuit.
1142+
pub pairs: [EcPairingPair; N_PAIRING_PER_OP],
10681143
/// Result from the pairing check.
10691144
pub output: Word,
10701145
}
10711146

10721147
impl Default for EcPairingOp {
10731148
fn default() -> Self {
1149+
let g1_point = G1Affine::generator();
1150+
let g2_point = G2Affine::generator();
10741151
Self {
1075-
inputs: [
1076-
(G1Affine::generator(), G2Affine::generator()),
1077-
(G1Affine::identity(), G2Affine::generator()),
1078-
(G1Affine::identity(), G2Affine::generator()),
1079-
(G1Affine::identity(), G2Affine::generator()),
1152+
pairs: [
1153+
EcPairingPair { g1_point, g2_point },
1154+
EcPairingPair { g1_point, g2_point },
1155+
EcPairingPair { g1_point, g2_point },
1156+
EcPairingPair { g1_point, g2_point },
10801157
],
10811158
output: Word::zero(),
10821159
}
10831160
}
10841161
}
10851162

10861163
impl EcPairingOp {
1087-
/// Returns the uncompressed little-endian byte representation of inputs to the EcPairingOp.
1088-
pub fn to_bytes_le(&self) -> Vec<u8> {
1089-
self.inputs
1164+
/// Returns the uncompressed big-endian byte representation of inputs to the EcPairingOp.
1165+
pub fn to_bytes_be(&self) -> Vec<u8> {
1166+
self.pairs
10901167
.iter()
1091-
.flat_map(|i| {
1092-
std::iter::empty()
1093-
.chain(i.0.x.to_bytes().iter())
1094-
.chain(i.0.y.to_bytes().iter())
1095-
.chain(i.1.x.c0.to_bytes().iter())
1096-
.chain(i.1.x.c1.to_bytes().iter())
1097-
.chain(i.1.y.c0.to_bytes().iter())
1098-
.chain(i.1.y.c1.to_bytes().iter())
1099-
.cloned()
1100-
.collect::<Vec<u8>>()
1101-
})
1168+
.flat_map(|pair| pair.to_bytes_be())
11021169
.collect::<Vec<u8>>()
11031170
}
11041171

11051172
/// A check on the op to tell the ECC Circuit whether or not to skip the op.
11061173
pub fn skip_by_ecc_circuit(&self) -> bool {
1107-
self.inputs[0].0.is_identity().into()
1108-
&& self.inputs[1].0.is_identity().into()
1109-
&& self.inputs[2].0.is_identity().into()
1110-
&& self.inputs[3].0.is_identity().into()
1174+
false
11111175
}
11121176
}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
use crate::{
2-
circuit_input_builder::{CircuitInputStateRef, EcAddOp, ExecStep, PrecompileEvent},
2+
circuit_input_builder::{EcAddOp, PrecompileEvent},
33
precompile::{EcAddAuxData, PrecompileAuxData},
44
};
55

6-
pub(crate) fn handle(
6+
pub(crate) fn opt_data(
77
input_bytes: Option<Vec<u8>>,
88
output_bytes: Option<Vec<u8>>,
9-
state: &mut CircuitInputStateRef,
10-
exec_step: &mut ExecStep,
11-
) {
9+
) -> (Option<PrecompileEvent>, Option<PrecompileAuxData>) {
1210
let input_bytes = input_bytes.map_or(vec![0u8; 128], |mut bytes| {
1311
bytes.resize(128, 0u8);
1412
bytes
@@ -19,8 +17,10 @@ pub(crate) fn handle(
1917
});
2018

2119
let aux_data = EcAddAuxData::new(&input_bytes, &output_bytes);
22-
exec_step.aux_data = Some(PrecompileAuxData::EcAdd(aux_data));
23-
2420
let ec_add_op = EcAddOp::new_from_bytes(&input_bytes, &output_bytes);
25-
state.push_precompile_event(PrecompileEvent::EcAdd(ec_add_op));
21+
22+
(
23+
Some(PrecompileEvent::EcAdd(ec_add_op)),
24+
Some(PrecompileAuxData::EcAdd(aux_data)),
25+
)
2626
}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
use crate::{
2-
circuit_input_builder::{CircuitInputStateRef, EcMulOp, ExecStep, PrecompileEvent},
2+
circuit_input_builder::{EcMulOp, PrecompileEvent},
33
precompile::{EcMulAuxData, PrecompileAuxData},
44
};
55

6-
pub(crate) fn handle(
6+
pub(crate) fn opt_data(
77
input_bytes: Option<Vec<u8>>,
88
output_bytes: Option<Vec<u8>>,
9-
state: &mut CircuitInputStateRef,
10-
exec_step: &mut ExecStep,
11-
) {
9+
) -> (Option<PrecompileEvent>, Option<PrecompileAuxData>) {
1210
let input_bytes = input_bytes.map_or(vec![0u8; 96], |mut bytes| {
1311
bytes.resize(96, 0u8);
1412
bytes
@@ -19,8 +17,10 @@ pub(crate) fn handle(
1917
});
2018

2119
let aux_data = EcMulAuxData::new(&input_bytes, &output_bytes);
22-
exec_step.aux_data = Some(PrecompileAuxData::EcMul(aux_data));
23-
2420
let ec_mul_op = EcMulOp::new_from_bytes(&input_bytes, &output_bytes);
25-
state.push_precompile_event(PrecompileEvent::EcMul(ec_mul_op));
21+
22+
(
23+
Some(PrecompileEvent::EcMul(ec_mul_op)),
24+
Some(PrecompileAuxData::EcMul(aux_data)),
25+
)
2626
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use eth_types::{ToLittleEndian, U256};
2+
use halo2_proofs::halo2curves::{
3+
bn256::{Fq, Fq2, G1Affine, G2Affine},
4+
group::cofactor::CofactorCurveAffine,
5+
};
6+
7+
use crate::{
8+
circuit_input_builder::{
9+
EcPairingOp, EcPairingPair, PrecompileEvent, N_BYTES_PER_PAIR, N_PAIRING_PER_OP,
10+
},
11+
precompile::{EcPairingAuxData, PrecompileAuxData},
12+
};
13+
14+
pub(crate) fn opt_data(
15+
input_bytes: Option<Vec<u8>>,
16+
output_bytes: Option<Vec<u8>>,
17+
) -> (Option<PrecompileEvent>, Option<PrecompileAuxData>) {
18+
// assertions.
19+
let output_bytes = output_bytes.expect("precompile should return at least 0 on failure");
20+
debug_assert_eq!(output_bytes.len(), 32, "ecPairing returns EVM word: 1 or 0");
21+
let pairing_check = output_bytes[31];
22+
debug_assert!(
23+
pairing_check == 1 || pairing_check == 0,
24+
"ecPairing returns 1 or 0"
25+
);
26+
debug_assert_eq!(output_bytes.iter().take(31).sum::<u8>(), 0);
27+
if input_bytes.is_none() {
28+
debug_assert_eq!(pairing_check, 1);
29+
}
30+
31+
let (op, aux_data) = if let Some(input) = input_bytes {
32+
debug_assert!(
33+
input.len() % N_BYTES_PER_PAIR == 0
34+
&& input.len() <= N_PAIRING_PER_OP * N_BYTES_PER_PAIR
35+
);
36+
// process input bytes.
37+
let (mut ecc_pairs, mut evm_pairs): (Vec<EcPairingPair>, Vec<EcPairingPair>) = input
38+
.chunks_exact(N_BYTES_PER_PAIR)
39+
.map(|chunk| {
40+
// process 192 bytes chunk at a time.
41+
// process g1.
42+
let g1_point = {
43+
let g1_x =
44+
Fq::from_bytes(&U256::from_big_endian(&chunk[0x00..0x20]).to_le_bytes())
45+
.unwrap();
46+
let g1_y =
47+
Fq::from_bytes(&U256::from_big_endian(&chunk[0x20..0x40]).to_le_bytes())
48+
.unwrap();
49+
G1Affine { x: g1_x, y: g1_y }
50+
};
51+
// process g2.
52+
let g2_point = {
53+
let g2_x1 =
54+
Fq::from_bytes(&U256::from_big_endian(&chunk[0x40..0x60]).to_le_bytes())
55+
.unwrap();
56+
let g2_x2 =
57+
Fq::from_bytes(&U256::from_big_endian(&chunk[0x60..0x80]).to_le_bytes())
58+
.unwrap();
59+
let g2_y1 =
60+
Fq::from_bytes(&U256::from_big_endian(&chunk[0x80..0xA0]).to_le_bytes())
61+
.unwrap();
62+
let g2_y2 =
63+
Fq::from_bytes(&U256::from_big_endian(&chunk[0xA0..0xC0]).to_le_bytes())
64+
.unwrap();
65+
G2Affine {
66+
x: Fq2 {
67+
c0: g2_x1,
68+
c1: g2_x2,
69+
},
70+
y: Fq2 {
71+
c0: g2_y1,
72+
c1: g2_y2,
73+
},
74+
}
75+
};
76+
77+
let evm_circuit_pair = EcPairingPair { g1_point, g2_point };
78+
let ecc_circuit_pair =
79+
if g1_point.is_identity().into() || g2_point.is_identity().into() {
80+
EcPairingPair::ecc_padding()
81+
} else {
82+
evm_circuit_pair
83+
};
84+
(ecc_circuit_pair, evm_circuit_pair)
85+
})
86+
.unzip();
87+
ecc_pairs.resize(N_PAIRING_PER_OP, EcPairingPair::ecc_padding());
88+
evm_pairs.resize(N_PAIRING_PER_OP, EcPairingPair::evm_padding());
89+
(
90+
EcPairingOp {
91+
pairs: <[_; N_PAIRING_PER_OP]>::try_from(ecc_pairs).unwrap(),
92+
output: pairing_check.into(),
93+
},
94+
EcPairingAuxData(EcPairingOp {
95+
pairs: <[_; N_PAIRING_PER_OP]>::try_from(evm_pairs).unwrap(),
96+
output: pairing_check.into(),
97+
}),
98+
)
99+
} else {
100+
// if no input bytes.
101+
let ecc_pairs = [EcPairingPair::ecc_padding(); N_PAIRING_PER_OP];
102+
let evm_pairs = [EcPairingPair::evm_padding(); N_PAIRING_PER_OP];
103+
(
104+
EcPairingOp {
105+
pairs: ecc_pairs,
106+
output: pairing_check.into(),
107+
},
108+
EcPairingAuxData(EcPairingOp {
109+
pairs: evm_pairs,
110+
output: pairing_check.into(),
111+
}),
112+
)
113+
};
114+
115+
(
116+
Some(PrecompileEvent::EcPairing(Box::new(op))),
117+
Some(PrecompileAuxData::EcPairing(Box::new(aux_data))),
118+
)
119+
}

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

+9-8
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,14 @@ use eth_types::{
55
use halo2_proofs::halo2curves::secp256k1::Fq;
66

77
use crate::{
8-
circuit_input_builder::{CircuitInputStateRef, ExecStep, PrecompileEvent},
8+
circuit_input_builder::PrecompileEvent,
99
precompile::{EcrecoverAuxData, PrecompileAuxData},
1010
};
1111

12-
pub(crate) fn handle(
12+
pub(crate) fn opt_data(
1313
input_bytes: Option<Vec<u8>>,
1414
output_bytes: Option<Vec<u8>>,
15-
state: &mut CircuitInputStateRef,
16-
exec_step: &mut ExecStep,
17-
) {
15+
) -> (Option<PrecompileEvent>, Option<PrecompileAuxData>) {
1816
let input_bytes = input_bytes.map_or(vec![0u8; 128], |mut bytes| {
1917
bytes.resize(128, 0u8);
2018
bytes
@@ -44,19 +42,22 @@ pub(crate) fn handle(
4442
msg_hash: Fq::from_bytes(&aux_data.msg_hash.to_be_bytes()).unwrap(),
4543
};
4644
assert_eq!(aux_data.recovered_addr, sign_data.get_addr());
47-
state.push_precompile_event(PrecompileEvent::Ecrecover(sign_data));
45+
(
46+
Some(PrecompileEvent::Ecrecover(sign_data)),
47+
Some(PrecompileAuxData::Ecrecover(aux_data)),
48+
)
4849
} else {
4950
log::warn!(
5051
"could not recover pubkey. ecrecover aux_data={:?}",
5152
aux_data
5253
);
54+
(None, Some(PrecompileAuxData::Ecrecover(aux_data)))
5355
}
5456
} else {
5557
log::warn!(
5658
"invalid recoveryId for ecrecover. sig_v={:?}",
5759
aux_data.sig_v
5860
);
61+
(None, Some(PrecompileAuxData::Ecrecover(aux_data)))
5962
}
60-
61-
exec_step.aux_data = Some(PrecompileAuxData::Ecrecover(aux_data));
6263
}

0 commit comments

Comments
 (0)