Skip to content

Commit d320ff8

Browse files
authored
feat(precompile): use Aurora modexp lib. (#769)
* Aurora modexp precompile wip * Clean input parsing and comments * cargo toml, rm num add modexp rev * bump aurora modexp to crate version * Update crates/precompile/src/utilities.rs * Update crates/precompile/src/utilities.rs
1 parent 7a99f16 commit d320ff8

File tree

5 files changed

+130
-101
lines changed

5 files changed

+130
-101
lines changed

Cargo.lock

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/precompile/Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ version = "2.2.0"
1111
[dependencies]
1212
revm-primitives = { path = "../primitives", version = "1.3.0", default-features = false }
1313
bn = { package = "substrate-bn", version = "0.6", default-features = false }
14-
num = { version = "0.4.0", default-features = false, features = ["alloc"] }
1514
once_cell = { version = "1.17", default-features = false, features = ["alloc"] }
1615
ripemd = { version = "0.1", default-features = false }
1716
sha2 = { version = "0.10", default-features = false }
17+
# modexp precompile
18+
aurora-engine-modexp = { version = "1.0", default-features = false }
1819

1920
# Optional KZG point evaluation precompile
2021
c-kzg = { version = "0.1.1", default-features = false, optional = true }
@@ -26,12 +27,12 @@ secp256k1 = { version = "0.27.0", default-features = false, features = [
2627
"recovery",
2728
], optional = true }
2829

30+
2931
[features]
3032
default = ["std", "c-kzg", "secp256k1"]
3133
std = [
3234
"revm-primitives/std",
3335
"k256/std",
34-
"num/std",
3536
"once_cell/std",
3637
"ripemd/std",
3738
"sha2/std",
@@ -41,7 +42,7 @@ std = [
4142

4243
optimism = ["revm-primitives/optimism"]
4344

44-
# This library may not work on all no_std platforms as they depend on C libraries.
45+
# These libraries may not work on all no_std platforms as they depend on C.
4546

4647
# Enables the KZG point evaluation precompile.
4748
c-kzg = ["dep:c-kzg", "revm-primitives/c-kzg"]

crates/precompile/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod identity;
1717
pub mod kzg_point_evaluation;
1818
mod modexp;
1919
mod secp256k1;
20+
pub mod utilities;
2021

2122
use alloc::{boxed::Box, vec::Vec};
2223
use core::fmt;

crates/precompile/src/modexp.rs

Lines changed: 71 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
use crate::{
2-
primitives::U256, Error, Precompile, PrecompileAddress, PrecompileResult, StandardPrecompileFn,
2+
primitives::U256,
3+
utilities::{get_right_padded, get_right_padded_vec, left_padding, left_padding_vec},
4+
Error, Precompile, PrecompileAddress, PrecompileResult, StandardPrecompileFn,
35
};
46
use alloc::vec::Vec;
5-
use core::{
6-
cmp::{max, min, Ordering},
7-
mem::size_of,
8-
};
9-
use num::{BigUint, One, Zero};
7+
use aurora_engine_modexp::modexp;
8+
use core::cmp::{max, min};
109

1110
pub const BYZANTIUM: PrecompileAddress = PrecompileAddress(
1211
crate::u64_to_address(5),
@@ -32,121 +31,96 @@ pub fn berlin_run(input: &[u8], gas_limit: u64) -> PrecompileResult {
3231
})
3332
}
3433

35-
fn calculate_iteration_count(exp_length: u64, exp_highp: &BigUint) -> u64 {
34+
fn calculate_iteration_count(exp_length: u64, exp_highp: &U256) -> u64 {
3635
let mut iteration_count: u64 = 0;
3736

38-
if exp_length <= 32 && exp_highp.is_zero() {
37+
if exp_length <= 32 && *exp_highp == U256::ZERO {
3938
iteration_count = 0;
4039
} else if exp_length <= 32 {
41-
iteration_count = exp_highp.bits() - 1;
40+
iteration_count = exp_highp.bit_len() as u64 - 1;
4241
} else if exp_length > 32 {
43-
iteration_count = (8 * (exp_length - 32)) + max(1, exp_highp.bits()) - 1;
42+
iteration_count = (8 * (exp_length - 32)) + max(1, exp_highp.bit_len() as u64) - 1;
4443
}
4544

4645
max(iteration_count, 1)
4746
}
4847

49-
macro_rules! read_u64_with_overflow {
50-
($input:expr, $from:expr, $to:expr, $overflow_limit:expr) => {{
51-
const SPLIT: usize = 32 - size_of::<u64>();
52-
let len = $input.len();
53-
let from_zero = min($from, len);
54-
let from = min(from_zero + SPLIT, len);
55-
let to = min($to, len);
56-
let overflow_bytes = &$input[from_zero..from];
57-
58-
let mut len_bytes = [0u8; size_of::<u64>()];
59-
len_bytes[..to - from].copy_from_slice(&$input[from..to]);
60-
let out = u64::from_be_bytes(len_bytes) as usize;
61-
let overflow = !(out < $overflow_limit && overflow_bytes.iter().all(|&x| x == 0));
62-
(out, overflow)
63-
}};
64-
}
65-
6648
fn run_inner<F>(input: &[u8], gas_limit: u64, min_gas: u64, calc_gas: F) -> PrecompileResult
6749
where
68-
F: FnOnce(u64, u64, u64, &BigUint) -> u64,
50+
F: FnOnce(u64, u64, u64, &U256) -> u64,
6951
{
70-
let len = input.len();
71-
let (base_len, base_overflow) = read_u64_with_overflow!(input, 0, 32, u32::MAX as usize);
72-
let (exp_len, exp_overflow) = read_u64_with_overflow!(input, 32, 64, u32::MAX as usize);
73-
let (mod_len, mod_overflow) = read_u64_with_overflow!(input, 64, 96, u32::MAX as usize);
74-
75-
if base_overflow || mod_overflow {
76-
return Err(Error::ModexpBaseOverflow);
52+
// If there is no minimum gas, return error.
53+
if min_gas > gas_limit {
54+
return Err(Error::OutOfGas);
7755
}
78-
79-
if mod_overflow {
56+
// The format of input is:
57+
// <length_of_BASE> <length_of_EXPONENT> <length_of_MODULUS> <BASE> <EXPONENT> <MODULUS>
58+
// Where every length is a 32-byte left-padded integer representing the number of bytes
59+
// to be taken up by the next value
60+
const HEADER_LENGTH: usize = 96;
61+
62+
// Extract the header.
63+
let base_len = U256::from_be_bytes(get_right_padded::<32>(input, 0));
64+
let exp_len = U256::from_be_bytes(get_right_padded::<32>(input, 32));
65+
let mod_len = U256::from_be_bytes(get_right_padded::<32>(input, 64));
66+
67+
// cast base and modulus to usize, it does not make sense to handle larger values
68+
let Ok(base_len) = usize::try_from(base_len) else {
69+
return Err(Error::ModexpBaseOverflow);
70+
};
71+
let Ok(mod_len) = usize::try_from(mod_len) else {
8072
return Err(Error::ModexpModOverflow);
73+
};
74+
75+
// Handle a special case when both the base and mod length is zero
76+
if base_len == 0 && mod_len == 0 {
77+
return Ok((min_gas, Vec::new()));
8178
}
8279

83-
let (r, gas_cost) = if base_len == 0 && mod_len == 0 {
84-
if min_gas > gas_limit {
85-
return Err(Error::OutOfGas);
86-
}
87-
(BigUint::zero(), min_gas)
88-
} else {
89-
// set limit for exp overflow
90-
if exp_overflow {
91-
return Err(Error::ModexpExpOverflow);
92-
}
93-
let base_start = 96;
94-
let base_end = base_start + base_len;
95-
let exp_end = base_end + exp_len;
96-
let exp_highp_end = base_end + min(32, exp_len);
97-
let mod_end = exp_end + mod_len;
98-
99-
let exp_highp = {
100-
let mut out = [0; 32];
101-
let from = min(base_end, len);
102-
let to = min(exp_highp_end, len);
103-
let target_from = 32 - (exp_highp_end - base_end); // 32 - exp length
104-
let target_to = target_from + (to - from); // beginning + size to copy
105-
out[target_from..target_to].copy_from_slice(&input[from..to]);
106-
BigUint::from_bytes_be(&out)
107-
};
108-
109-
let gas_cost = calc_gas(base_len as u64, exp_len as u64, mod_len as u64, &exp_highp);
110-
if gas_cost > gas_limit {
111-
return Err(Error::OutOfGas);
112-
}
80+
// cast exponent length to usize, it does not make sense to handle larger values.
81+
let Ok(exp_len) = usize::try_from(exp_len) else {
82+
return Err(Error::ModexpModOverflow);
83+
};
11384

114-
let read_big = |from: usize, to: usize| {
115-
let mut out = vec![0; to - from];
116-
let from = min(from, len);
117-
let to = min(to, len);
118-
out[..to - from].copy_from_slice(&input[from..to]);
119-
BigUint::from_bytes_be(&out)
120-
};
85+
// Used to extract ADJUSTED_EXPONENT_LENGTH.
86+
let exp_highp_len = min(exp_len, 32);
12187

122-
let base = read_big(base_start, base_end);
123-
let exponent = read_big(base_end, exp_end);
124-
let modulus = read_big(exp_end, mod_end);
88+
// throw away the header data as we already extracted lengths.
89+
let input = if input.len() >= 96 {
90+
&input[HEADER_LENGTH..]
91+
} else {
92+
// or set input to zero if there is no more data
93+
&[]
94+
};
12595

126-
if modulus.is_zero() || modulus.is_one() {
127-
(BigUint::zero(), gas_cost)
128-
} else {
129-
(base.modpow(&exponent, &modulus), gas_cost)
130-
}
96+
let exp_highp = {
97+
// get right padded bytes so if data.len is less then exp_len we will get right padded zeroes.
98+
let right_padded_highp = get_right_padded::<32>(input, base_len);
99+
// If exp_len is less then 32 bytes get only exp_len bytes and do left padding.
100+
let out = left_padding::<32>(&right_padded_highp[..exp_highp_len]);
101+
U256::from_be_bytes(out)
131102
};
132103

133-
// write output to given memory, left padded and same length as the modulus.
134-
let bytes = r.to_bytes_be();
135-
// always true except in the case of zero-length modulus, which leads to
136-
// output of length and value 1.
137-
match bytes.len().cmp(&mod_len) {
138-
Ordering::Equal => Ok((gas_cost, bytes)),
139-
Ordering::Less => {
140-
let mut ret = Vec::with_capacity(mod_len);
141-
ret.extend(core::iter::repeat(0).take(mod_len - bytes.len()));
142-
ret.extend_from_slice(&bytes[..]);
143-
Ok((gas_cost, ret))
144-
}
145-
Ordering::Greater => Ok((gas_cost, Vec::new())),
104+
// calculate gas spent.
105+
let gas_cost = calc_gas(base_len as u64, exp_len as u64, mod_len as u64, &exp_highp);
106+
// check if we have enough gas.
107+
if gas_cost > gas_limit {
108+
return Err(Error::OutOfGas);
146109
}
110+
111+
// Padding is needed if the input does not contain all 3 values.
112+
let base = get_right_padded_vec(input, 0, base_len);
113+
let exponent = get_right_padded_vec(input, base_len, exp_len);
114+
let modulus = get_right_padded_vec(input, base_len.saturating_add(exp_len), mod_len);
115+
116+
// Call the modexp.
117+
let output = modexp(&base, &exponent, &modulus);
118+
119+
// left pad the result to modulus length. bytes will always by less or equal to modulus length.
120+
Ok((gas_cost, left_padding_vec(&output, mod_len)))
147121
}
148122

149-
fn byzantium_gas_calc(base_len: u64, exp_len: u64, mod_len: u64, exp_highp: &BigUint) -> u64 {
123+
fn byzantium_gas_calc(base_len: u64, exp_len: u64, mod_len: u64, exp_highp: &U256) -> u64 {
150124
// ouput of this function is bounded by 2^128
151125
fn mul_complexity(x: u64) -> U256 {
152126
if x <= 64 {
@@ -175,7 +149,7 @@ fn byzantium_gas_calc(base_len: u64, exp_len: u64, mod_len: u64, exp_highp: &Big
175149

176150
// Calculate gas cost according to EIP 2565:
177151
// https://eips.ethereum.org/EIPS/eip-2565
178-
fn berlin_gas_calc(base_length: u64, exp_length: u64, mod_length: u64, exp_highp: &BigUint) -> u64 {
152+
fn berlin_gas_calc(base_length: u64, exp_length: u64, mod_length: u64, exp_highp: &U256) -> u64 {
179153
fn calculate_multiplication_complexity(base_length: u64, mod_length: u64) -> U256 {
180154
let max_length = max(base_length, mod_length);
181155
let mut words = max_length / 8;

crates/precompile/src/utilities.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use core::cmp::min;
2+
3+
use alloc::vec::Vec;
4+
5+
/// Get an array from the data, if data does not contain `start` to `len` bytes, add right padding with
6+
/// zeroes
7+
#[inline(always)]
8+
pub fn get_right_padded<const S: usize>(data: &[u8], offset: usize) -> [u8; S] {
9+
let mut padded = [0; S];
10+
let start = min(offset, data.len());
11+
let end = min(start.saturating_add(S), data.len());
12+
padded[..end - start].copy_from_slice(&data[start..end]);
13+
padded
14+
}
15+
16+
/// Get a vector of the data, if data does not contain the slice of `start` to `len`, right pad missing
17+
/// part with zeroes
18+
#[inline(always)]
19+
pub fn get_right_padded_vec(data: &[u8], offset: usize, len: usize) -> Vec<u8> {
20+
let mut padded = vec![0; len];
21+
let start = min(offset, data.len());
22+
let end = min(start.saturating_add(len), data.len());
23+
padded[..end - start].copy_from_slice(&data[start..end]);
24+
padded
25+
}
26+
27+
/// Left padding until `len`. If data is more then len, truncate the right most bytes.
28+
#[inline(always)]
29+
pub fn left_padding<const S: usize>(data: &[u8]) -> [u8; S] {
30+
let mut padded = [0; S];
31+
let end = min(S, data.len());
32+
padded[S - end..].copy_from_slice(&data[..end]);
33+
padded
34+
}
35+
36+
/// Left padding until `len`. If data is more then len, truncate the right most bytes.
37+
#[inline(always)]
38+
pub fn left_padding_vec(data: &[u8], len: usize) -> Vec<u8> {
39+
let mut padded = vec![0; len];
40+
let end = min(len, data.len());
41+
padded[len - end..].copy_from_slice(&data[..end]);
42+
padded
43+
}

0 commit comments

Comments
 (0)