Skip to content

Commit 067fa0d

Browse files
committed
Refactored json tests up to Cancun hardfork with all precompiles, including KZG. Adde debug info with TODO points
1 parent c2ef6ea commit 067fa0d

8 files changed

Lines changed: 4525 additions & 7 deletions

File tree

evm-tests/jsontests/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@ aurora-engine-precompiles = { git = "https://github.com/aurora-is-near/aurora-en
2020
aurora-evm.workspace = true
2121
bytecount = "0.6"
2222
clap = { version = "4.5", features = ["cargo"] }
23+
c-kzg = "1.0"
24+
derive_more = "0.99"
2325
ethereum = "0.15"
2426
hex = "0.4"
2527
hex-literal = "0.4"
2628
libsecp256k1 = "0.7"
2729
primitive-types = { workspace = true, features = ["serde"] }
2830
rlp.workspace = true
31+
sha2 = { version = "0.10.0", default-features = false }
2932
sha3.workspace = true
3033
serde.workspace = true
3134
serde_json = "1.0"

evm-tests/jsontests/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ pub mod vm;
2020
mod assertions;
2121
mod config;
2222
mod execution_results;
23-
mod old_precompiles;
23+
// TODO
24+
// mod old_precompiles;
2425
mod precompiles;
2526
mod state_dump;
2627
// mod utils;

evm-tests/jsontests/src/precompiles.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
mod kzg;
2+
3+
use crate::precompiles::kzg::Kzg;
4+
use crate::types::Spec;
15
use aurora_engine_modexp::AuroraModExp;
26
use aurora_engine_precompiles::{
37
alt_bn256::{Bn256Add, Bn256Mul, Bn256Pair},
@@ -6,7 +10,7 @@ use aurora_engine_precompiles::{
610
identity::Identity,
711
modexp::ModExp,
812
secp256k1::ECRecover,
9-
Byzantium, EthGas, Istanbul, Precompile,
13+
Berlin, Byzantium, EthGas, Istanbul, Precompile,
1014
};
1115
use aurora_evm::executor::stack::{
1216
PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileSet,
@@ -33,6 +37,22 @@ impl PrecompileSet for Precompiles {
3337
}
3438

3539
impl Precompiles {
40+
pub fn new(spec: &Spec) -> Self {
41+
match *spec {
42+
Spec::Frontier
43+
| Spec::Homestead
44+
| Spec::Tangerine
45+
| Spec::SpuriousDragon
46+
| Spec::Byzantium
47+
| Spec::Constantinople
48+
| Spec::Petersburg
49+
| Spec::Istanbul => Self::new_istanbul(),
50+
Spec::Berlin | Spec::London | Spec::Merge | Spec::Shanghai => Self::new_berlin(),
51+
Spec::Cancun => Self::new_cancun(),
52+
Spec::Prague | Spec::Osaka => Self::new_prague(),
53+
}
54+
}
55+
3656
pub fn new_istanbul() -> Self {
3757
let mut map = BTreeMap::new();
3858
map.insert(
@@ -61,6 +81,46 @@ impl Precompiles {
6181
map.insert(Blake2F::ADDRESS.raw(), Box::new(Blake2F));
6282
Self(map)
6383
}
84+
85+
pub fn new_berlin() -> Self {
86+
let mut map = BTreeMap::new();
87+
map.insert(
88+
ECRecover::ADDRESS.raw(),
89+
Box::new(ECRecover) as Box<dyn Precompile>,
90+
);
91+
map.insert(SHA256::ADDRESS.raw(), Box::new(SHA256));
92+
map.insert(RIPEMD160::ADDRESS.raw(), Box::new(RIPEMD160));
93+
map.insert(Identity::ADDRESS.raw(), Box::new(Identity));
94+
map.insert(
95+
ModExp::<Berlin, AuroraModExp>::ADDRESS.raw(),
96+
Box::new(ModExp::<Berlin, AuroraModExp>::new()),
97+
);
98+
map.insert(
99+
Bn256Add::<Istanbul>::ADDRESS.raw(),
100+
Box::new(Bn256Add::<Istanbul>::new()),
101+
);
102+
map.insert(
103+
Bn256Mul::<Istanbul>::ADDRESS.raw(),
104+
Box::new(Bn256Mul::<Istanbul>::new()),
105+
);
106+
map.insert(
107+
Bn256Pair::<Istanbul>::ADDRESS.raw(),
108+
Box::new(Bn256Pair::<Istanbul>::new()),
109+
);
110+
map.insert(Blake2F::ADDRESS.raw(), Box::new(Blake2F));
111+
Self(map)
112+
}
113+
114+
pub fn new_cancun() -> Self {
115+
let mut map = Self::new_berlin().0;
116+
map.insert(Kzg::ADDRESS, Box::new(Kzg));
117+
Self(map)
118+
}
119+
120+
pub fn new_prague() -> Self {
121+
let map = Self::new_cancun().0;
122+
Self(map)
123+
}
64124
}
65125

66126
fn process_precompile(
192 KB
Binary file not shown.
6.09 KB
Binary file not shown.

evm-tests/jsontests/src/precompiles/assets/trusted_setup.txt

Lines changed: 4163 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
#![allow(clippy::module_inception, clippy::large_stack_arrays)]
2+
3+
use aurora_engine_precompiles::{Context, EthGas, ExitError, Precompile, PrecompileOutput};
4+
use primitive_types::H160;
5+
use std::borrow::Cow::Borrowed;
6+
7+
mod kzg {
8+
use c_kzg::{Bytes32, Bytes48, KzgProof, KzgSettings, BYTES_PER_G1_POINT, BYTES_PER_G2_POINT};
9+
use core::convert::TryInto;
10+
use core::hash::{Hash, Hasher};
11+
use derive_more::{AsMut, AsRef, Deref, DerefMut};
12+
use hex_literal::hex;
13+
use sha2::Digest;
14+
use std::convert::TryFrom;
15+
use std::rc::Rc;
16+
17+
pub const RETURN_VALUE: &[u8; 64] = &hex!(
18+
"0000000000000000000000000000000000000000000000000000000000001000"
19+
"73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"
20+
);
21+
22+
/// Number of G1 Points.
23+
const NUM_G1_POINTS: usize = 4096;
24+
25+
/// Number of G2 Points.
26+
const NUM_G2_POINTS: usize = 65;
27+
28+
/// A newtype over list of G1 point from kzg trusted setup.
29+
#[derive(Debug, Clone, PartialEq, AsRef, AsMut, Deref, DerefMut)]
30+
#[repr(transparent)]
31+
struct G1Points(pub [[u8; BYTES_PER_G1_POINT]; NUM_G1_POINTS]);
32+
33+
impl Default for G1Points {
34+
fn default() -> Self {
35+
Self([[0; BYTES_PER_G1_POINT]; NUM_G1_POINTS])
36+
}
37+
}
38+
39+
/// A newtype over list of G2 point from kzg trusted setup.
40+
#[derive(Debug, Clone, Eq, PartialEq, AsRef, AsMut, Deref, DerefMut)]
41+
#[repr(transparent)]
42+
struct G2Points(pub [[u8; BYTES_PER_G2_POINT]; NUM_G2_POINTS]);
43+
44+
impl Default for G2Points {
45+
fn default() -> Self {
46+
Self([[0; BYTES_PER_G2_POINT]; NUM_G2_POINTS])
47+
}
48+
}
49+
50+
/// Default G1 points.
51+
const G1_POINTS: &G1Points = {
52+
const BYTES: &[u8] = include_bytes!("assets/g1_points.bin");
53+
assert!(BYTES.len() == size_of::<G1Points>());
54+
unsafe { &*BYTES.as_ptr().cast::<G1Points>() }
55+
};
56+
57+
/// Default G2 points.
58+
const G2_POINTS: &G2Points = {
59+
const BYTES: &[u8] = include_bytes!("assets/g2_points.bin");
60+
assert!(BYTES.len() == size_of::<G2Points>());
61+
unsafe { &*BYTES.as_ptr().cast::<G2Points>() }
62+
};
63+
64+
/// Parse contents of a KZG trusted setup file into a list of G1 and G2 points.
65+
///
66+
/// These can then be used to create a KZG settings object with
67+
/// [`KzgSettings::load_trusted_setup`](c_kzg::KzgSettings::load_trusted_setup).
68+
#[allow(dead_code)]
69+
fn parse_kzg_trusted_setup(
70+
trusted_setup: &str,
71+
) -> Result<(Box<G1Points>, Box<G2Points>), &'static str> {
72+
let mut lines = trusted_setup.lines();
73+
74+
// load number of points
75+
let n_g1 = lines
76+
.next()
77+
.ok_or("KzgFileFormatError")?
78+
.parse::<usize>()
79+
.map_err(|_| "KzgParseError")?;
80+
let n_g2 = lines
81+
.next()
82+
.ok_or("KzgFileFormatError")?
83+
.parse::<usize>()
84+
.map_err(|_| "KzgParseError")?;
85+
86+
if n_g1 != NUM_G1_POINTS {
87+
return Err("KzgMismatchedNumberOfPoints");
88+
}
89+
90+
if n_g2 != NUM_G2_POINTS {
91+
return Err("KzgMismatchedNumberOfPoints");
92+
}
93+
94+
// load g1 points
95+
let mut g1_points = Box::<G1Points>::default();
96+
for bytes in &mut g1_points.0 {
97+
let line = lines.next().ok_or("KzgFileFormatError")?;
98+
hex::decode_to_slice(line, bytes).map_err(|_| "KzgParseError")?;
99+
}
100+
101+
// load g2 points
102+
let mut g2_points = Box::<G2Points>::default();
103+
for bytes in &mut g2_points.0 {
104+
let line = lines.next().ok_or("KzgFileFormatError")?;
105+
hex::decode_to_slice(line, bytes).map_err(|_| "KzgParseError")?;
106+
}
107+
108+
if lines.next().is_some() {
109+
return Err("KzgFileFormatError");
110+
}
111+
112+
Ok((g1_points, g2_points))
113+
}
114+
115+
/// KZG Settings that allow us to specify a custom trusted setup.
116+
/// or use hardcoded default settings.
117+
#[allow(dead_code)]
118+
#[derive(Debug, Clone, Default)]
119+
pub enum EnvKzgSettings {
120+
/// Default mainnet trusted setup
121+
#[default]
122+
Default,
123+
/// Custom trusted setup.
124+
Custom(Rc<KzgSettings>),
125+
}
126+
127+
// Implement PartialEq and Hash manually because `c_kzg::KzgSettings` does not implement them
128+
impl PartialEq for EnvKzgSettings {
129+
fn eq(&self, other: &Self) -> bool {
130+
match (self, other) {
131+
(Self::Default, Self::Default) => true,
132+
(Self::Custom(a), Self::Custom(b)) => Rc::ptr_eq(a, b),
133+
_ => false,
134+
}
135+
}
136+
}
137+
138+
impl Hash for EnvKzgSettings {
139+
fn hash<H: Hasher>(&self, state: &mut H) {
140+
core::mem::discriminant(self).hash(state);
141+
match self {
142+
Self::Default => {}
143+
Self::Custom(settings) => Rc::as_ptr(settings).hash(state),
144+
}
145+
}
146+
}
147+
148+
impl EnvKzgSettings {
149+
/// Return set KZG settings.
150+
///
151+
/// In will initialize the default settings if it is not already loaded.
152+
pub fn get(&self) -> Rc<KzgSettings> {
153+
match self {
154+
Self::Default => {
155+
let res =
156+
KzgSettings::load_trusted_setup(G1_POINTS.as_ref(), G2_POINTS.as_ref())
157+
.expect("failed to load default trusted setup");
158+
Rc::new(res)
159+
}
160+
Self::Custom(settings) => settings.clone(),
161+
}
162+
}
163+
}
164+
165+
/// `VERSIONED_HASH_VERSION_KZG ++ sha256(commitment)[1..]`
166+
#[inline]
167+
pub fn kzg_to_versioned_hash(commitment: &[u8]) -> [u8; 32] {
168+
const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;
169+
let mut hash: [u8; 32] = sha2::Sha256::digest(commitment).into();
170+
hash[0] = VERSIONED_HASH_VERSION_KZG;
171+
hash
172+
}
173+
174+
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
175+
pub struct KzgInput {
176+
commitment: Bytes48,
177+
z: Bytes32,
178+
y: Bytes32,
179+
proof: Bytes48,
180+
}
181+
182+
impl KzgInput {
183+
#[inline]
184+
pub fn verify_kzg_proof(&self, kzg_settings: &KzgSettings) -> bool {
185+
KzgProof::verify_kzg_proof(
186+
&self.commitment,
187+
&self.z,
188+
&self.y,
189+
&self.proof,
190+
kzg_settings,
191+
)
192+
.unwrap_or(false)
193+
}
194+
}
195+
196+
impl TryFrom<&[u8]> for KzgInput {
197+
type Error = &'static str;
198+
199+
fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
200+
if input.len() != 192 {
201+
return Err("BlobInvalidInputLength");
202+
}
203+
// Verify commitment matches versioned_hash
204+
let versioned_hash = &input[..32];
205+
let commitment = &input[96..144];
206+
if kzg_to_versioned_hash(commitment) != versioned_hash {
207+
return Err("BlobMismatchedVersion");
208+
}
209+
let commitment = *as_bytes48(commitment);
210+
let z = *as_bytes32(&input[32..64]);
211+
let y = *as_bytes32(&input[64..96]);
212+
let proof = *as_bytes48(&input[144..192]);
213+
Ok(Self {
214+
commitment,
215+
z,
216+
y,
217+
proof,
218+
})
219+
}
220+
}
221+
222+
#[inline]
223+
fn as_array<const N: usize>(bytes: &[u8]) -> &[u8; N] {
224+
bytes.try_into().expect("slice with incorrect length")
225+
}
226+
227+
#[inline]
228+
fn as_bytes32(bytes: &[u8]) -> &Bytes32 {
229+
// SAFETY: `#[repr(C)] Bytes32([u8; 32])`
230+
unsafe { &*as_array::<32>(bytes).as_ptr().cast() }
231+
}
232+
233+
#[inline]
234+
fn as_bytes48(bytes: &[u8]) -> &Bytes48 {
235+
// SAFETY: `#[repr(C)] Bytes48([u8; 48])`
236+
unsafe { &*as_array::<48>(bytes).as_ptr().cast() }
237+
}
238+
}
239+
240+
const KZG_BASE_GAS_FEE: u64 = 50_000;
241+
242+
pub struct Kzg;
243+
244+
impl Kzg {
245+
pub const ADDRESS: H160 = H160([
246+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
247+
0x00, 0x00, 0x00, 0x00, 0x0A,
248+
]);
249+
250+
fn execute(input: &[u8]) -> Result<Vec<u8>, ExitError> {
251+
// Get and verify KZG input.
252+
let kzg_input: kzg::KzgInput = input
253+
.try_into()
254+
.map_err(|e| ExitError::Other(Borrowed(e)))?;
255+
let kzg_settings = kzg::EnvKzgSettings::Default;
256+
if !kzg_input.verify_kzg_proof(&kzg_settings.get()) {
257+
return Err(ExitError::Other(Borrowed("BlobVerifyKzgProofFailed")));
258+
}
259+
Ok(kzg::RETURN_VALUE.to_vec())
260+
}
261+
}
262+
263+
impl Precompile for Kzg {
264+
fn required_gas(_input: &[u8]) -> Result<EthGas, ExitError> {
265+
Ok(EthGas::new(KZG_BASE_GAS_FEE))
266+
}
267+
268+
fn run(
269+
&self,
270+
input: &[u8],
271+
target_gas: Option<EthGas>,
272+
_context: &Context,
273+
_is_static: bool,
274+
) -> Result<PrecompileOutput, ExitError> {
275+
let cost = Self::required_gas(input)?;
276+
if let Some(target_gas) = target_gas {
277+
if cost > target_gas {
278+
return Err(ExitError::OutOfGas);
279+
}
280+
}
281+
282+
let output = Self::execute(input)?;
283+
Ok(PrecompileOutput::without_logs(cost, output))
284+
}
285+
}

0 commit comments

Comments
 (0)