-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(levm): implement precompile blake2f #1563
Changes from all commits
f44efa8
26e0602
aea1db5
dab3a30
c1f6720
9f72866
9d8b5a7
17d04ec
ea5f1f3
283fe96
e6da99d
58876ae
df8d019
6efd71f
1c13669
8fea74c
42fa3c8
c5a6d26
02cb86b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,9 @@ use sha3::Digest; | |
use crate::{ | ||
call_frame::CallFrame, | ||
errors::{InternalError, PrecompileError, VMError}, | ||
gas_cost::{self, ECADD_COST, ECMUL_COST, ECRECOVER_COST, MODEXP_STATIC_COST}, | ||
gas_cost::{ | ||
self, BLAKE2F_ROUND_COST, ECADD_COST, ECMUL_COST, ECRECOVER_COST, MODEXP_STATIC_COST, | ||
}, | ||
}; | ||
|
||
pub const ECRECOVER_ADDRESS: H160 = H160([ | ||
|
@@ -83,6 +85,8 @@ pub const PRECOMPILES: [H160; 10] = [ | |
POINT_EVALUATION_ADDRESS, | ||
]; | ||
|
||
pub const BLAKE2F_ELEMENT_SIZE: usize = 8; | ||
|
||
pub fn is_precompile(callee_address: &Address) -> bool { | ||
PRECOMPILES.contains(callee_address) | ||
} | ||
|
@@ -678,12 +682,206 @@ fn update_pairing_result( | |
Ok(()) | ||
} | ||
|
||
fn blake2f( | ||
_calldata: &Bytes, | ||
_gas_for_call: u64, | ||
_consumed_gas: &mut u64, | ||
// Message word schedule permutations for each round are defined by SIGMA constant. | ||
// Extracted from https://datatracker.ietf.org/doc/html/rfc7693#section-2.7 | ||
pub const SIGMA: [[usize; 16]; 10] = [ | ||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], | ||
[14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], | ||
[11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], | ||
[7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], | ||
[9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], | ||
[2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], | ||
[12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], | ||
[13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], | ||
[6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], | ||
[10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], | ||
]; | ||
|
||
// Initialization vector, used to initialize the work vector | ||
// Extracted from https://datatracker.ietf.org/doc/html/rfc7693#appendix-C.2 | ||
pub const IV: [u64; 8] = [ | ||
0x6a09e667f3bcc908, | ||
0xbb67ae8584caa73b, | ||
0x3c6ef372fe94f82b, | ||
0xa54ff53a5f1d36f1, | ||
0x510e527fade682d1, | ||
0x9b05688c2b3e6c1f, | ||
0x1f83d9abfb41bd6b, | ||
0x5be0cd19137e2179, | ||
]; | ||
|
||
// Rotation constants, used in g | ||
// Extracted from https://datatracker.ietf.org/doc/html/rfc7693#section-2.1 | ||
const R1: u32 = 32; | ||
const R2: u32 = 24; | ||
const R3: u32 = 16; | ||
const R4: u32 = 63; | ||
|
||
// The G primitive function mixes two input words, "x" and "y", into | ||
// four words indexed by "a", "b", "c", and "d" in the working vector | ||
// v[0..15]. The full modified vector is returned. | ||
// Based on https://datatracker.ietf.org/doc/html/rfc7693#section-3.1 | ||
#[allow(clippy::indexing_slicing)] | ||
fn g(v: [u64; 16], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) -> [u64; 16] { | ||
let mut ret = v; | ||
ret[a] = v[a].wrapping_add(v[b]).wrapping_add(x); | ||
ret[d] = (ret[d] ^ ret[a]).rotate_right(R1); | ||
ret[c] = ret[c].wrapping_add(ret[d]); | ||
ret[b] = (ret[b] ^ ret[c]).rotate_right(R2); | ||
ret[a] = ret[a].wrapping_add(ret[b]).wrapping_add(y); | ||
ret[d] = (ret[d] ^ ret[a]).rotate_right(R3); | ||
ret[c] = ret[c].wrapping_add(ret[d]); | ||
ret[b] = (ret[b] ^ ret[c]).rotate_right(R4); | ||
|
||
ret | ||
} | ||
|
||
// Perform the permutations on the work vector | ||
#[allow(clippy::indexing_slicing)] | ||
fn word_permutation(rounds_to_permute: usize, v: [u64; 16], m: &[u64; 16]) -> [u64; 16] { | ||
let mut ret = v; | ||
|
||
for i in 0..rounds_to_permute { | ||
// Message word selection permutation for each round. | ||
|
||
let s: &[usize; 16] = &SIGMA[i % 10]; | ||
|
||
ret = g(ret, 0, 4, 8, 12, m[s[0]], m[s[1]]); | ||
ret = g(ret, 1, 5, 9, 13, m[s[2]], m[s[3]]); | ||
ret = g(ret, 2, 6, 10, 14, m[s[4]], m[s[5]]); | ||
ret = g(ret, 3, 7, 11, 15, m[s[6]], m[s[7]]); | ||
|
||
ret = g(ret, 0, 5, 10, 15, m[s[8]], m[s[9]]); | ||
ret = g(ret, 1, 6, 11, 12, m[s[10]], m[s[11]]); | ||
ret = g(ret, 2, 7, 8, 13, m[s[12]], m[s[13]]); | ||
ret = g(ret, 3, 4, 9, 14, m[s[14]], m[s[15]]); | ||
} | ||
|
||
ret | ||
} | ||
|
||
// Based on https://datatracker.ietf.org/doc/html/rfc7693#section-3.2 | ||
fn blake2f_compress_f( | ||
rounds: usize, | ||
h: [u64; 8], | ||
m: &[u64; 16], | ||
t: &[u64; 2], | ||
f: bool, | ||
) -> Result<[u64; 8], InternalError> { | ||
// Initialize local work vector v[0..15], takes first half from state and second half from IV. | ||
let mut v: [u64; 16] = [0; 16]; | ||
v[0..8].copy_from_slice(&h); | ||
v[8..16].copy_from_slice(&IV); | ||
|
||
v[12] ^= t[0]; // Low word of the offset | ||
v[13] ^= t[1]; // High word of the offset | ||
|
||
// If final block flag is true, invert all bits | ||
if f { | ||
v[14] = !v[14]; | ||
} | ||
|
||
v = word_permutation(rounds, v, m); | ||
|
||
let mut output = [0; 8]; | ||
|
||
// XOR the two halves, put the results in the output slice | ||
for (i, pos) in output.iter_mut().enumerate() { | ||
*pos = h.get(i).ok_or(InternalError::SlicingError)? | ||
^ v.get(i).ok_or(InternalError::SlicingError)? | ||
^ v.get(i.overflowing_add(8).0) | ||
.ok_or(InternalError::SlicingError)?; | ||
} | ||
|
||
Ok(output) | ||
} | ||
|
||
// Reads a part of the calldata and returns what is read as u64 or an error | ||
fn read_bytes_from_offset(calldata: &Bytes, offset: usize, index: usize) -> Result<u64, VMError> { | ||
let index_start = (index | ||
.checked_mul(BLAKE2F_ELEMENT_SIZE) | ||
.ok_or(PrecompileError::ParsingInputError)?) | ||
.checked_add(offset) | ||
.ok_or(PrecompileError::ParsingInputError)?; | ||
let index_end = index_start | ||
.checked_add(BLAKE2F_ELEMENT_SIZE) | ||
.ok_or(PrecompileError::ParsingInputError)?; | ||
|
||
Ok(u64::from_le_bytes( | ||
calldata | ||
.get(index_start..index_end) | ||
.ok_or(InternalError::SlicingError)? | ||
.try_into() | ||
.map_err(|_| PrecompileError::ParsingInputError)?, | ||
)) | ||
} | ||
|
||
type SliceArguments = ([u64; 8], [u64; 16], [u64; 2]); | ||
|
||
fn parse_slice_arguments(calldata: &Bytes) -> Result<SliceArguments, VMError> { | ||
let mut h = [0; 8]; | ||
for i in 0..8_usize { | ||
let data_read = read_bytes_from_offset(calldata, 4, i)?; | ||
|
||
let read_slice = h.get_mut(i).ok_or(InternalError::SlicingError)?; | ||
*read_slice = data_read; | ||
} | ||
|
||
let mut m = [0; 16]; | ||
for i in 0..16_usize { | ||
let data_read = read_bytes_from_offset(calldata, 68, i)?; | ||
|
||
let read_slice = m.get_mut(i).ok_or(InternalError::SlicingError)?; | ||
*read_slice = data_read; | ||
} | ||
|
||
let mut t = [0; 2]; | ||
for i in 0..2_usize { | ||
let data_read = read_bytes_from_offset(calldata, 196, i)?; | ||
|
||
let read_slice = t.get_mut(i).ok_or(InternalError::SlicingError)?; | ||
*read_slice = data_read; | ||
} | ||
|
||
Ok((h, m, t)) | ||
} | ||
|
||
pub fn blake2f( | ||
calldata: &Bytes, | ||
gas_for_call: u64, | ||
consumed_gas: &mut u64, | ||
) -> Result<Bytes, VMError> { | ||
Ok(Bytes::new()) | ||
if calldata.len() != 213 { | ||
return Err(VMError::PrecompileError(PrecompileError::ParsingInputError)); | ||
} | ||
|
||
let rounds: U256 = calldata | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's an intermediate size to then move on to a usize, I couldn't find a prettier way to do it. |
||
.get(0..4) | ||
.ok_or(InternalError::SlicingError)? | ||
.into(); | ||
|
||
let rounds: usize = rounds | ||
.try_into() | ||
.map_err(|_| PrecompileError::ParsingInputError)?; | ||
|
||
let gas_cost = | ||
u64::try_from(rounds).map_err(|_| InternalError::ConversionError)? * BLAKE2F_ROUND_COST; | ||
increase_precompile_consumed_gas(gas_for_call, gas_cost, consumed_gas)?; | ||
|
||
let (h, m, t) = parse_slice_arguments(calldata)?; | ||
|
||
let f = calldata.get(212).ok_or(InternalError::SlicingError)?; | ||
if *f != 0 && *f != 1 { | ||
return Err(VMError::PrecompileError(PrecompileError::ParsingInputError)); | ||
} | ||
let f = *f == 1; | ||
|
||
let result = blake2f_compress_f(rounds, h, &m, &t, f)?; | ||
|
||
// map the result to the output format (from a u64 slice to a u8 one) | ||
let output: Vec<u8> = result.iter().flat_map(|num| num.to_le_bytes()).collect(); | ||
|
||
Ok(Bytes::from(output)) | ||
} | ||
|
||
fn point_evaluation( | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -9,13 +9,16 @@ use ethrex_levm::{ | |||||||||||
db::{cache, CacheDB, Db}, | ||||||||||||
errors::{OutOfGasError, TxResult, VMError}, | ||||||||||||
gas_cost::{ | ||||||||||||
self, ECADD_COST, ECMUL_COST, ECPAIRING_BASE_COST, ECPAIRING_GROUP_COST, ECRECOVER_COST, | ||||||||||||
IDENTITY_DYNAMIC_BASE, IDENTITY_STATIC_COST, MODEXP_STATIC_COST, RIPEMD_160_DYNAMIC_BASE, | ||||||||||||
RIPEMD_160_STATIC_COST, SHA2_256_DYNAMIC_BASE, SHA2_256_STATIC_COST, | ||||||||||||
self, BLAKE2F_ROUND_COST, ECADD_COST, ECMUL_COST, ECPAIRING_BASE_COST, | ||||||||||||
ECPAIRING_GROUP_COST, ECRECOVER_COST, IDENTITY_DYNAMIC_BASE, IDENTITY_STATIC_COST, | ||||||||||||
MODEXP_STATIC_COST, RIPEMD_160_DYNAMIC_BASE, RIPEMD_160_STATIC_COST, SHA2_256_DYNAMIC_BASE, | ||||||||||||
SHA2_256_STATIC_COST, | ||||||||||||
}, | ||||||||||||
memory, | ||||||||||||
operations::Operation, | ||||||||||||
precompiles::{ecadd, ecmul, ecpairing, ecrecover, identity, modexp, ripemd_160, sha2_256}, | ||||||||||||
precompiles::{ | ||||||||||||
blake2f, ecadd, ecmul, ecpairing, ecrecover, identity, modexp, ripemd_160, sha2_256, | ||||||||||||
}, | ||||||||||||
utils::{new_vm_with_ops, new_vm_with_ops_addr_bal_db, new_vm_with_ops_db, ops_to_bytecode}, | ||||||||||||
vm::{word_to_address, Storage, VM}, | ||||||||||||
Environment, | ||||||||||||
|
@@ -4643,3 +4646,33 @@ fn ecpairing_test() { | |||||||||||
(ECPAIRING_BASE_COST + ECPAIRING_GROUP_COST * 2) | ||||||||||||
); | ||||||||||||
} | ||||||||||||
|
||||||||||||
#[test] | ||||||||||||
fn blake2f_test() { | ||||||||||||
Comment on lines
+4650
to
+4651
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maxi, what do you think about adding a source for these tests? Just in the case in the future it's easier to find them Something like:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in Add source of first test |
||||||||||||
// Source: https://eips.ethereum.org/EIPS/eip-152#test-vector-5 | ||||||||||||
let calldata = hex::decode("0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001").unwrap(); | ||||||||||||
let calldata = Bytes::from(calldata); | ||||||||||||
|
||||||||||||
let mut consumed_gas = 0; | ||||||||||||
let result = blake2f(&calldata, 10000, &mut consumed_gas).unwrap(); | ||||||||||||
|
||||||||||||
let expected_result = Bytes::from(hex::decode("ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923").unwrap()); | ||||||||||||
|
||||||||||||
assert_eq!(result, expected_result); | ||||||||||||
assert_eq!(consumed_gas, 12 * BLAKE2F_ROUND_COST); | ||||||||||||
} | ||||||||||||
|
||||||||||||
#[test] | ||||||||||||
fn blake2f_test_2() { | ||||||||||||
Comment on lines
+4665
to
+4666
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Done" in Add source of second test |
||||||||||||
// Inspired in tuple (23, 0, 0) of GeneralStateTests/stPreCompiledContracts/blake2B.json | ||||||||||||
let calldata = hex::decode("0000001048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b616162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8000000000000000300000000000000000000000000000001").unwrap(); | ||||||||||||
let calldata = Bytes::from(calldata); | ||||||||||||
|
||||||||||||
let mut consumed_gas = 0; | ||||||||||||
let result = blake2f(&calldata, 10000, &mut consumed_gas).unwrap(); | ||||||||||||
|
||||||||||||
let expected_result = Bytes::from(hex::decode("7df6f69476a03ae29e944814846460b058d1762fffe77f938ea723d1033de0d5bb1f8234bd73afaf955622fa2cdde95594577a8d53191908eb69b316a53c985b").unwrap()); | ||||||||||||
|
||||||||||||
assert_eq!(result, expected_result); | ||||||||||||
assert_eq!(consumed_gas, 16 * BLAKE2F_ROUND_COST); | ||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@maximopalopoli Is it possible that you are not applying the modulus operator here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See: https://datatracker.ietf.org/doc/html/rfc7693#section-3.1