From 8aaf8bd36a31476de94822e8d291cf38fc6c0321 Mon Sep 17 00:00:00 2001 From: Ermal Kaleci Date: Thu, 24 Feb 2022 13:22:21 +0100 Subject: [PATCH 1/5] add missing precompiles and fixes --- Cargo.lock | 59 +- evm-tests | 2 +- modules/evm/Cargo.toml | 4 + modules/evm/src/precompiles.rs | 320 ---------- modules/evm/src/precompiles/blake2/eip_152.rs | 93 +++ modules/evm/src/precompiles/blake2/mod.rs | 116 ++++ modules/evm/src/precompiles/bn128.rs | 249 ++++++++ modules/evm/src/precompiles/ecrecover.rs | 105 ++++ .../evm/src/precompiles/ecrecoverpublickey.rs | 49 ++ modules/evm/src/precompiles/identity.rs | 34 ++ modules/evm/src/precompiles/mod.rs | 96 +++ modules/evm/src/precompiles/modexp.rs | 545 ++++++++++++++++++ modules/evm/src/precompiles/ripemd.rs | 37 ++ modules/evm/src/precompiles/sha256.rs | 35 ++ modules/evm/src/precompiles/sha3fips.rs | 147 +++++ runtime/common/src/precompile/mod.rs | 24 +- 16 files changed, 1591 insertions(+), 324 deletions(-) delete mode 100644 modules/evm/src/precompiles.rs create mode 100644 modules/evm/src/precompiles/blake2/eip_152.rs create mode 100644 modules/evm/src/precompiles/blake2/mod.rs create mode 100644 modules/evm/src/precompiles/bn128.rs create mode 100644 modules/evm/src/precompiles/ecrecover.rs create mode 100644 modules/evm/src/precompiles/ecrecoverpublickey.rs create mode 100644 modules/evm/src/precompiles/identity.rs create mode 100644 modules/evm/src/precompiles/mod.rs create mode 100644 modules/evm/src/precompiles/modexp.rs create mode 100644 modules/evm/src/precompiles/ripemd.rs create mode 100644 modules/evm/src/precompiles/sha256.rs create mode 100644 modules/evm/src/precompiles/sha3fips.rs diff --git a/Cargo.lock b/Cargo.lock index ffb75fcb5a..e89eef7557 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4852,6 +4852,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "lazycell" @@ -6181,6 +6184,7 @@ dependencies = [ "module-evm-utiltity", "module-idle-scheduler", "module-support", + "num", "orml-bencher", "orml-currencies", "orml-tokens", @@ -6199,6 +6203,7 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", + "substrate-bn", "tiny-keccak", ] @@ -6842,6 +6847,20 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint 0.4.3", + "num-complex", + "num-integer", + "num-iter", + "num-rational 0.4.0", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.2.6" @@ -6853,6 +6872,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.0" @@ -6872,6 +6902,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.2.4" @@ -6879,7 +6920,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" dependencies = [ "autocfg", - "num-bigint", + "num-bigint 0.2.6", "num-integer", "num-traits", ] @@ -6891,6 +6932,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" dependencies = [ "autocfg", + "num-bigint 0.4.3", "num-integer", "num-traits", ] @@ -11240,7 +11282,7 @@ dependencies = [ "futures 0.3.19", "log", "merlin", - "num-bigint", + "num-bigint 0.2.6", "num-rational 0.2.4", "num-traits", "parity-scale-codec", @@ -13306,6 +13348,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "substrate-bn" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b5bbfa79abbae15dd642ea8176a21a635ff3c00059961d1ea27ad04e5b441c" +dependencies = [ + "byteorder", + "crunchy", + "lazy_static", + "rand 0.8.4", + "rustc-hex", +] + [[package]] name = "substrate-build-script-utils" version = "3.0.0" diff --git a/evm-tests b/evm-tests index 653f82ce10..ad9ceebba4 160000 --- a/evm-tests +++ b/evm-tests @@ -1 +1 @@ -Subproject commit 653f82ce10a9354c11cdd9bbb22ddd3ca3dbd8da +Subproject commit ad9ceebba4e2f0f7a7ef7803e97ba7ff62ad5b0c diff --git a/modules/evm/Cargo.toml b/modules/evm/Cargo.toml index e0b9691f3b..0f2c441baf 100644 --- a/modules/evm/Cargo.toml +++ b/modules/evm/Cargo.toml @@ -21,6 +21,8 @@ tiny-keccak = { version = "2.0", features = ["fips202"] } scale-info = { version = "1.0", default-features = false, features = ["derive"] } serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true } hex = { version = "0.4", default-features = false, features = ["alloc"], optional = true } +num = { version = "0.4", features = ["alloc"], default-features = false } +bn = { package = "substrate-bn", version = "0.6", default-features = false } frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.16", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.16", default-features = false } @@ -58,6 +60,8 @@ std = [ "sp-std/std", "sha3/std", "rlp/std", + "hex", + "num/std", "module-evm-utiltity/std", "primitive-types/std", "pallet-timestamp/std", diff --git a/modules/evm/src/precompiles.rs b/modules/evm/src/precompiles.rs deleted file mode 100644 index 5f1e55626b..0000000000 --- a/modules/evm/src/precompiles.rs +++ /dev/null @@ -1,320 +0,0 @@ -// This file is part of Acala. - -// Copyright (C) 2020-2022 Acala Foundation. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Builtin precompiles. - -use crate::runner::state::{PrecompileFailure, PrecompileOutput, PrecompileResult}; -use module_evm_utiltity::evm::{Context, ExitError, ExitSucceed}; -use ripemd160::Digest; -use sp_std::{cmp::min, vec::Vec}; -use tiny_keccak::Hasher; - -/// One single precompile used by EVM engine. -pub trait Precompile { - /// Try to execute the precompile. Calculate the amount of gas needed with given `input` and - /// `target_gas`. Return `Ok(status, output, gas_used)` if the execution is - /// successful. Otherwise return `Err(_)`. - fn execute(input: &[u8], target_gas: Option, context: &Context, is_static: bool) -> PrecompileResult; -} - -pub trait LinearCostPrecompile { - const BASE: u64; - const WORD: u64; - - fn execute(input: &[u8], cost: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure>; -} - -impl Precompile for T { - fn execute(input: &[u8], target_gas: Option, _: &Context, _: bool) -> PrecompileResult { - let cost = ensure_linear_cost(target_gas, input.len() as u64, T::BASE, T::WORD)?; - - let (exit_status, output) = T::execute(input, cost)?; - Ok(PrecompileOutput { - exit_status, - cost, - output, - logs: Default::default(), - }) - } -} - -/// Linear gas cost -fn ensure_linear_cost(target_gas: Option, len: u64, base: u64, word: u64) -> Result { - let cost = base - .checked_add( - word.checked_mul(len.saturating_add(31) / 32) - .ok_or(PrecompileFailure::Error { - exit_status: ExitError::OutOfGas, - })?, - ) - .ok_or(PrecompileFailure::Error { - exit_status: ExitError::OutOfGas, - })?; - - if let Some(target_gas) = target_gas { - if cost > target_gas { - return Err(PrecompileFailure::Error { - exit_status: ExitError::OutOfGas, - }); - } - } - - Ok(cost) -} - -/// The identity precompile. -pub struct Identity; - -impl LinearCostPrecompile for Identity { - const BASE: u64 = 15; - const WORD: u64 = 3; - - fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { - Ok((ExitSucceed::Returned, input.to_vec())) - } -} - -/// The ecrecover precompile. -pub struct ECRecover; - -impl LinearCostPrecompile for ECRecover { - const BASE: u64 = 3000; - const WORD: u64 = 0; - - fn execute(i: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { - let mut input = [0u8; 128]; - input[..min(i.len(), 128)].copy_from_slice(&i[..min(i.len(), 128)]); - - let mut msg = [0u8; 32]; - let mut sig = [0u8; 65]; - - msg[0..32].copy_from_slice(&input[0..32]); - sig[0..32].copy_from_slice(&input[64..96]); - sig[32..64].copy_from_slice(&input[96..128]); - sig[64] = input[63]; - - let result = match sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg) { - Ok(pubkey) => { - let mut address = sp_io::hashing::keccak_256(&pubkey); - address[0..12].copy_from_slice(&[0u8; 12]); - address.to_vec() - } - Err(_) => [0u8; 0].to_vec(), - }; - - Ok((ExitSucceed::Returned, result)) - } -} - -/// The ripemd precompile. -pub struct Ripemd160; - -impl LinearCostPrecompile for Ripemd160 { - const BASE: u64 = 600; - const WORD: u64 = 120; - - fn execute(input: &[u8], _cost: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { - let mut ret = [0u8; 32]; - ret[12..32].copy_from_slice(&ripemd160::Ripemd160::digest(input)); - Ok((ExitSucceed::Returned, ret.to_vec())) - } -} - -/// The sha256 precompile. -pub struct Sha256; - -impl LinearCostPrecompile for Sha256 { - const BASE: u64 = 60; - const WORD: u64 = 12; - - fn execute(input: &[u8], _cost: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { - let ret = sp_io::hashing::sha2_256(input); - Ok((ExitSucceed::Returned, ret.to_vec())) - } -} - -/// The ecrecover precompile. -pub struct ECRecoverPublicKey; - -impl LinearCostPrecompile for ECRecoverPublicKey { - const BASE: u64 = 3000; - const WORD: u64 = 0; - - fn execute(i: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { - let mut input = [0u8; 128]; - input[..min(i.len(), 128)].copy_from_slice(&i[..min(i.len(), 128)]); - - let mut msg = [0u8; 32]; - let mut sig = [0u8; 65]; - - msg[0..32].copy_from_slice(&input[0..32]); - sig[0..32].copy_from_slice(&input[64..96]); - sig[32..64].copy_from_slice(&input[96..128]); - sig[64] = input[63]; - - let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg).map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::Other("Public key recover failed".into()), - })?; - - Ok((ExitSucceed::Returned, pubkey.to_vec())) - } -} - -/// The Sha3FIPS256 precompile. -pub struct Sha3FIPS256; - -impl LinearCostPrecompile for Sha3FIPS256 { - const BASE: u64 = 60; - const WORD: u64 = 12; - - fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { - let mut output = [0; 32]; - let mut sha3 = tiny_keccak::Sha3::v256(); - sha3.update(input); - sha3.finalize(&mut output); - Ok((ExitSucceed::Returned, output.to_vec())) - } -} - -/// The Sha3FIPS512 precompile. -pub struct Sha3FIPS512; - -impl LinearCostPrecompile for Sha3FIPS512 { - const BASE: u64 = 60; - const WORD: u64 = 12; - - fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { - let mut output = [0; 64]; - let mut sha3 = tiny_keccak::Sha3::v512(); - sha3.update(input); - sha3.finalize(&mut output); - Ok((ExitSucceed::Returned, output.to_vec())) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_empty_input() -> std::result::Result<(), ExitError> { - let input: [u8; 0] = []; - let expected = b"\ - \xa7\xff\xc6\xf8\xbf\x1e\xd7\x66\x51\xc1\x47\x56\xa0\x61\xd6\x62\ - \xf5\x80\xff\x4d\xe4\x3b\x49\xfa\x82\xd8\x0a\x4b\x80\xf8\x43\x4a\ - "; - - let cost: u64 = 1; - - match ::execute(&input, cost) { - Ok((_, out)) => { - assert_eq!(out, expected); - Ok(()) - } - Err(e) => { - panic!("Test not expected to fail: {:?}", e); - } - } - } - - #[test] - fn hello_sha3_256() -> std::result::Result<(), ExitError> { - let input = b"hello"; - let expected = b"\ - \x33\x38\xbe\x69\x4f\x50\xc5\xf3\x38\x81\x49\x86\xcd\xf0\x68\x64\ - \x53\xa8\x88\xb8\x4f\x42\x4d\x79\x2a\xf4\xb9\x20\x23\x98\xf3\x92\ - "; - - let cost: u64 = 1; - - match ::execute(input, cost) { - Ok((_, out)) => { - assert_eq!(out, expected); - Ok(()) - } - Err(e) => { - panic!("Test not expected to fail: {:?}", e); - } - } - } - - #[test] - fn long_string_sha3_256() -> std::result::Result<(), ExitError> { - let input = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; - let expected = b"\ - \xbd\xe3\xf2\x69\x17\x5e\x1d\xcd\xa1\x38\x48\x27\x8a\xa6\x04\x6b\ - \xd6\x43\xce\xa8\x5b\x84\xc8\xb8\xbb\x80\x95\x2e\x70\xb6\xea\xe0\ - "; - - let cost: u64 = 1; - - match ::execute(input, cost) { - Ok((_, out)) => { - assert_eq!(out, expected); - Ok(()) - } - Err(e) => { - panic!("Test not expected to fail: {:?}", e); - } - } - } - - #[test] - fn long_string_sha3_512() -> std::result::Result<(), ExitError> { - let input = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; - let expected = b"\ - \xf3\x2a\x94\x23\x55\x13\x51\xdf\x0a\x07\xc0\xb8\xc2\x0e\xb9\x72\ - \x36\x7c\x39\x8d\x61\x06\x60\x38\xe1\x69\x86\x44\x8e\xbf\xbc\x3d\ - \x15\xed\xe0\xed\x36\x93\xe3\x90\x5e\x9a\x8c\x60\x1d\x9d\x00\x2a\ - \x06\x85\x3b\x97\x97\xef\x9a\xb1\x0c\xbd\xe1\x00\x9c\x7d\x0f\x09\ - "; - - let cost: u64 = 1; - - match ::execute(input, cost) { - Ok((_, out)) => { - assert_eq!(out, expected); - Ok(()) - } - Err(e) => { - panic!("Test not expected to fail: {:?}", e); - } - } - } - - #[test] - fn ecrecover() -> std::result::Result<(), ExitError> { - let input = sp_core::bytes::from_hex("0xe63325d74baa84af003dfb6a974f41672be881b56aa2c12c093f8259321bd460000000000000000000000000000000000000000000000000000000000000001c6273e55c6b942c7a701ae05195fa24395cd1db99e81c705b8c2eb4d7156ff85a3ecf6b591a30105fef03717fa608c887bd02ba548876e93ce818f90dc46ac374").unwrap(); - let expected = b"\ - \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ - \x6b\xe0\x2d\x1d\x36\x65\x66\x0d\x22\xff\x96\x24\xb7\xbe\x05\x51\xee\x1a\xc9\x1b\ - "; - - let cost: u64 = 1; - - match ::execute(&input, cost) { - Ok((_, out)) => { - assert_eq!(out, expected); - Ok(()) - } - Err(e) => { - panic!("Test not expected to fail: {:?}", e); - } - } - } -} diff --git a/modules/evm/src/precompiles/blake2/eip_152.rs b/modules/evm/src/precompiles/blake2/eip_152.rs new file mode 100644 index 0000000000..e94bfe5ebc --- /dev/null +++ b/modules/evm/src/precompiles/blake2/eip_152.rs @@ -0,0 +1,93 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2022 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +/// The precomputed values for BLAKE2b [from the spec](https://tools.ietf.org/html/rfc7693#section-2.7) +/// There are 10 16-byte arrays - one for each round +/// the entries are calculated from the sigma constants. +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], +]; + +/// IV is the initialization vector for BLAKE2b. See https://tools.ietf.org/html/rfc7693#section-2.6 +/// for details. +const IV: [u64; 8] = [ + 0x6a09e667f3bcc908, + 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, + 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, + 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, + 0x5be0cd19137e2179, +]; + +#[inline(always)] +/// The G mixing function. See https://tools.ietf.org/html/rfc7693#section-3.1 +fn g(v: &mut [u64], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) { + v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); + v[d] = (v[d] ^ v[a]).rotate_right(32); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(24); + v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); + v[d] = (v[d] ^ v[a]).rotate_right(16); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(63); +} + +/// The Blake2 compression function F. See https://tools.ietf.org/html/rfc7693#section-3.2 +/// Takes as an argument the state vector `h`, message block vector `m`, offset counter `t`, final +/// block indicator flag `f`, and number of rounds `rounds`. The state vector provided as the first +/// parameter is modified by the function. +pub fn compress(h: &mut [u64; 8], m: [u64; 16], t: [u64; 2], f: bool, rounds: usize) { + let mut v = [0u64; 16]; + v[..h.len()].copy_from_slice(h); // First half from state. + v[h.len()..].copy_from_slice(&IV); // Second half from IV. + + v[12] ^= t[0]; + v[13] ^= t[1]; + + if f { + v[14] = !v[14] // Invert all bits if the last-block-flag is set. + } + for i in 0..rounds { + // Message word selection permutation for this round. + let s = &SIGMA[i % 10]; + g(&mut v, 0, 4, 8, 12, m[s[0]], m[s[1]]); + g(&mut v, 1, 5, 9, 13, m[s[2]], m[s[3]]); + g(&mut v, 2, 6, 10, 14, m[s[4]], m[s[5]]); + g(&mut v, 3, 7, 11, 15, m[s[6]], m[s[7]]); + + g(&mut v, 0, 5, 10, 15, m[s[8]], m[s[9]]); + g(&mut v, 1, 6, 11, 12, m[s[10]], m[s[11]]); + g(&mut v, 2, 7, 8, 13, m[s[12]], m[s[13]]); + g(&mut v, 3, 4, 9, 14, m[s[14]], m[s[15]]); + } + + for i in 0..8 { + h[i] ^= v[i] ^ v[i + 8]; + } +} diff --git a/modules/evm/src/precompiles/blake2/mod.rs b/modules/evm/src/precompiles/blake2/mod.rs new file mode 100644 index 0000000000..e50d9c0422 --- /dev/null +++ b/modules/evm/src/precompiles/blake2/mod.rs @@ -0,0 +1,116 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2022 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::Precompile; +use crate::runner::state::{PrecompileFailure, PrecompileOutput, PrecompileResult}; +use module_evm_utiltity::evm::{Context, ExitError, ExitSucceed}; +use sp_std::mem::size_of; + +mod eip_152; + +pub struct Blake2F; + +impl Blake2F { + const GAS_COST_PER_ROUND: u64 = 1; // https://eips.ethereum.org/EIPS/eip-152#gas-costs-and-benchmarks +} + +impl Precompile for Blake2F { + /// Format of `input`: + /// [4 bytes for rounds][64 bytes for h][128 bytes for m][8 bytes for t_0][8 bytes for t_1][1 + /// byte for f] + fn execute(input: &[u8], target_gas: Option, _context: &Context, _is_static: bool) -> PrecompileResult { + const BLAKE2_F_ARG_LEN: usize = 213; + + if input.len() != BLAKE2_F_ARG_LEN { + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other( + "input length for Blake2 F precompile should be exactly 213 bytes".into(), + ), + }); + } + + let mut rounds_buf: [u8; 4] = [0; 4]; + rounds_buf.copy_from_slice(&input[0..4]); + let rounds: u32 = u32::from_be_bytes(rounds_buf); + + let gas_cost: u64 = (rounds as u64) * Blake2F::GAS_COST_PER_ROUND; + if let Some(gas_left) = target_gas { + if gas_left < gas_cost { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + } + + // we use from_le_bytes below to effectively swap byte order to LE if architecture is BE + + let mut h_buf: [u8; 64] = [0; 64]; + h_buf.copy_from_slice(&input[4..68]); + let mut h = [0u64; 8]; + let mut ctr = 0; + for state_word in &mut h { + let mut temp: [u8; 8] = Default::default(); + temp.copy_from_slice(&h_buf[(ctr * 8)..(ctr + 1) * 8]); + *state_word = u64::from_le_bytes(temp); + ctr += 1; + } + + let mut m_buf: [u8; 128] = [0; 128]; + m_buf.copy_from_slice(&input[68..196]); + let mut m = [0u64; 16]; + ctr = 0; + for msg_word in &mut m { + let mut temp: [u8; 8] = Default::default(); + temp.copy_from_slice(&m_buf[(ctr * 8)..(ctr + 1) * 8]); + *msg_word = u64::from_le_bytes(temp); + ctr += 1; + } + + let mut t_0_buf: [u8; 8] = [0; 8]; + t_0_buf.copy_from_slice(&input[196..204]); + let t_0 = u64::from_le_bytes(t_0_buf); + + let mut t_1_buf: [u8; 8] = [0; 8]; + t_1_buf.copy_from_slice(&input[204..212]); + let t_1 = u64::from_le_bytes(t_1_buf); + + let f = if input[212] == 1 { + true + } else if input[212] == 0 { + false + } else { + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("incorrect final block indicator flag".into()), + }); + }; + + eip_152::compress(&mut h, m, [t_0, t_1], f, rounds as usize); + + let mut output_buf = [0u8; 8 * size_of::()]; + for (i, state_word) in h.iter().enumerate() { + output_buf[i * 8..(i + 1) * 8].copy_from_slice(&state_word.to_le_bytes()); + } + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: gas_cost, + output: output_buf.to_vec(), + logs: Default::default(), + }) + } +} diff --git a/modules/evm/src/precompiles/bn128.rs b/modules/evm/src/precompiles/bn128.rs new file mode 100644 index 0000000000..8b449de9e6 --- /dev/null +++ b/modules/evm/src/precompiles/bn128.rs @@ -0,0 +1,249 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2022 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::Precompile; +use crate::runner::state::{PrecompileFailure, PrecompileOutput, PrecompileResult}; +use module_evm_utiltity::evm::{Context, ExitError, ExitSucceed}; +use sp_core::U256; +use sp_std::vec::Vec; + +fn read_fr(input: &[u8], start_inx: usize) -> Result { + let mut padded_input = Vec::from(input); + if padded_input.len() < start_inx + 32 { + padded_input.resize_with(start_inx + 32, Default::default); + } + + bn::Fr::from_slice(&padded_input[start_inx..(start_inx + 32)]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid field element".into()), + }) +} + +fn read_point(input: &[u8], start_inx: usize) -> Result { + use bn::{AffineG1, Fq, Group, G1}; + + let mut padded_input = Vec::from(input); + if padded_input.len() < start_inx + 64 { + padded_input.resize_with(start_inx + 64, Default::default); + } + + let px = Fq::from_slice(&padded_input[start_inx..(start_inx + 32)]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid point x coordinate".into()), + })?; + let py = + Fq::from_slice(&padded_input[(start_inx + 32)..(start_inx + 64)]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid point y coordinate".into()), + })?; + Ok(if px == Fq::zero() && py == Fq::zero() { + G1::zero() + } else { + AffineG1::new(px, py) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid curve point".into()), + })? + .into() + }) +} + +/// The Bn128Add builtin +pub struct Bn128Add; + +impl Bn128Add { + const GAS_COST: u64 = 150; // https://eips.ethereum.org/EIPS/eip-1108 +} + +impl Precompile for Bn128Add { + fn execute(input: &[u8], _target_gas: Option, _context: &Context, _is_static: bool) -> PrecompileResult { + use bn::AffineG1; + + let p1 = read_point(input, 0)?; + let p2 = read_point(input, 64)?; + + let mut buf = [0u8; 64]; + if let Some(sum) = AffineG1::from_jacobian(p1 + p2) { + // point not at infinity + sum.x() + .to_big_endian(&mut buf[0..32]) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Cannot fail since 0..32 is 32-byte length".into()), + })?; + sum.y() + .to_big_endian(&mut buf[32..64]) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Cannot fail since 32..64 is 32-byte length".into()), + })?; + } + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: Bn128Add::GAS_COST, + output: buf.to_vec(), + logs: Default::default(), + }) + } +} + +/// The Bn128Mul builtin +pub struct Bn128Mul; + +impl Bn128Mul { + const GAS_COST: u64 = 6_000; // https://eips.ethereum.org/EIPS/eip-1108 +} + +impl Precompile for Bn128Mul { + fn execute(input: &[u8], _target_gas: Option, _context: &Context, _is_static: bool) -> PrecompileResult { + use bn::AffineG1; + + let p = read_point(input, 0)?; + let fr = read_fr(input, 64)?; + + let mut buf = [0u8; 64]; + if let Some(sum) = AffineG1::from_jacobian(p * fr) { + // point not at infinity + sum.x() + .to_big_endian(&mut buf[0..32]) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Cannot fail since 0..32 is 32-byte length".into()), + })?; + sum.y() + .to_big_endian(&mut buf[32..64]) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Cannot fail since 32..64 is 32-byte length".into()), + })?; + } + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: Bn128Mul::GAS_COST, + output: buf.to_vec(), + logs: Default::default(), + }) + } +} + +/// The Bn128Pairing builtin +pub struct Bn128Pairing; + +impl Bn128Pairing { + // https://eips.ethereum.org/EIPS/eip-1108 + const BASE_GAS_COST: u64 = 45_000; + const GAS_COST_PER_PAIRING: u64 = 34_000; +} + +impl Precompile for Bn128Pairing { + fn execute(input: &[u8], target_gas: Option, _context: &Context, _is_static: bool) -> PrecompileResult { + use bn::{pairing_batch, AffineG1, AffineG2, Fq, Fq2, Group, Gt, G1, G2}; + + if let Some(gas_left) = target_gas { + if gas_left < Bn128Pairing::BASE_GAS_COST { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + } + + if input.len() % 192 != 0 { + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid input length, must be multiple of 192 (3 * (32*2))".into()), + }); + } + + let (ret_val, gas_cost) = if input.is_empty() { + (U256::one(), Bn128Pairing::BASE_GAS_COST) + } else { + // (a, b_a, b_b - each 64-byte affine coordinates) + let elements = input.len() / 192; + + let gas_cost: u64 = Bn128Pairing::BASE_GAS_COST + (elements as u64 * Bn128Pairing::GAS_COST_PER_PAIRING); + if let Some(gas_left) = target_gas { + if gas_left < gas_cost { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + } + + let mut vals = Vec::new(); + for idx in 0..elements { + let a_x = Fq::from_slice(&input[idx * 192..idx * 192 + 32]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid a argument x coordinate".into()), + })?; + + let a_y = + Fq::from_slice(&input[idx * 192 + 32..idx * 192 + 64]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid a argument y coordinate".into()), + })?; + + let b_a_y = + Fq::from_slice(&input[idx * 192 + 64..idx * 192 + 96]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid b argument imaginary coeff x coordinate".into()), + })?; + + let b_a_x = + Fq::from_slice(&input[idx * 192 + 96..idx * 192 + 128]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid b argument imaginary coeff y coordinate".into()), + })?; + + let b_b_y = + Fq::from_slice(&input[idx * 192 + 128..idx * 192 + 160]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid b argument real coeff x coordinate".into()), + })?; + + let b_b_x = + Fq::from_slice(&input[idx * 192 + 160..idx * 192 + 192]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid b argument real coeff y coordinate".into()), + })?; + + let b_a = Fq2::new(b_a_x, b_a_y); + let b_b = Fq2::new(b_b_x, b_b_y); + let b = if b_a.is_zero() && b_b.is_zero() { + G2::zero() + } else { + G2::from(AffineG2::new(b_a, b_b).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid b argument - not on curve".into()), + })?) + }; + let a = if a_x.is_zero() && a_y.is_zero() { + G1::zero() + } else { + G1::from(AffineG1::new(a_x, a_y).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid a argument - not on curve".into()), + })?) + }; + vals.push((a, b)); + } + + let mul = pairing_batch(&vals); + + if mul == Gt::one() { + (U256::one(), gas_cost) + } else { + (U256::zero(), gas_cost) + } + }; + + let mut buf = [0u8; 32]; + ret_val.to_big_endian(&mut buf); + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: gas_cost, + output: buf.to_vec(), + logs: Default::default(), + }) + } +} diff --git a/modules/evm/src/precompiles/ecrecover.rs b/modules/evm/src/precompiles/ecrecover.rs new file mode 100644 index 0000000000..9dd72be9ae --- /dev/null +++ b/modules/evm/src/precompiles/ecrecover.rs @@ -0,0 +1,105 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2022 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::LinearCostPrecompile; +use crate::runner::state::PrecompileFailure; +use module_evm_utiltity::evm::ExitSucceed; +use sp_std::{cmp::min, vec::Vec}; + +/// The ecrecover precompile. +pub struct ECRecover; + +impl LinearCostPrecompile for ECRecover { + const BASE: u64 = 3000; + const WORD: u64 = 0; + + fn execute(i: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { + let mut input = [0u8; 128]; + input[..min(i.len(), 128)].copy_from_slice(&i[..min(i.len(), 128)]); + + let mut msg = [0u8; 32]; + let mut sig = [0u8; 65]; + + msg[0..32].copy_from_slice(&input[0..32]); + sig[0..32].copy_from_slice(&input[64..96]); + sig[32..64].copy_from_slice(&input[96..128]); + + sig[64] = match input[63] { + v if v > 26 && input[32..63] == [0; 31] => v - 27, + _ => { + return Ok((ExitSucceed::Returned, [0u8; 0].to_vec())); + } + }; + + let result = match sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg) { + Ok(pubkey) => { + let mut address = sp_io::hashing::keccak_256(&pubkey); + address[0..12].copy_from_slice(&[0u8; 12]); + address.to_vec() + } + Err(_) => [0u8; 0].to_vec(), + }; + + Ok((ExitSucceed::Returned, result)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_core::H160; + use std::str::FromStr; + + #[test] + fn handle_invalid_v() { + // V = 1 + let input = hex::decode( + "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f\ + eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549", + ) + .expect("Decode failed"); + let (exit, output) = ECRecover::execute(&input, 0).unwrap(); + assert_eq!(exit, ExitSucceed::Returned); + assert_eq!(output, [0u8; 0].to_vec()); + } + + #[test] + fn validate_v() { + let address = H160::from_str("a94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap(); + // V = 28 + let mut input = hex::decode( + "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c\ + 000000000000000000000000000000000000000000000000000000000000001c\ + 73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f\ + eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549", + ) + .expect("Decode failed"); + + let (exit, output) = ECRecover::execute(&input, 0).unwrap(); + assert_eq!(exit, ExitSucceed::Returned); + assert_eq!(H160::from_slice(&output[12..]), address); + + // V = 27 + input[63] = 27; + let (exit, output) = ECRecover::execute(&input, 0).unwrap(); + assert_eq!(exit, ExitSucceed::Returned); + assert_ne!(H160::from_slice(&output[12..]), address); + } +} diff --git a/modules/evm/src/precompiles/ecrecoverpublickey.rs b/modules/evm/src/precompiles/ecrecoverpublickey.rs new file mode 100644 index 0000000000..5453045826 --- /dev/null +++ b/modules/evm/src/precompiles/ecrecoverpublickey.rs @@ -0,0 +1,49 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2022 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::LinearCostPrecompile; +use crate::runner::state::PrecompileFailure; +use module_evm_utiltity::evm::{ExitError, ExitSucceed}; +use sp_std::{cmp::min, vec::Vec}; + +/// The ecrecover precompile. +pub struct ECRecoverPublicKey; + +impl LinearCostPrecompile for ECRecoverPublicKey { + const BASE: u64 = 3000; + const WORD: u64 = 0; + + fn execute(i: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { + let mut input = [0u8; 128]; + input[..min(i.len(), 128)].copy_from_slice(&i[..min(i.len(), 128)]); + + let mut msg = [0u8; 32]; + let mut sig = [0u8; 65]; + + msg[0..32].copy_from_slice(&input[0..32]); + sig[0..32].copy_from_slice(&input[64..96]); + sig[32..64].copy_from_slice(&input[96..128]); + sig[64] = input[63]; + + let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Public key recover failed".into()), + })?; + + Ok((ExitSucceed::Returned, pubkey.to_vec())) + } +} diff --git a/modules/evm/src/precompiles/identity.rs b/modules/evm/src/precompiles/identity.rs new file mode 100644 index 0000000000..33783aab69 --- /dev/null +++ b/modules/evm/src/precompiles/identity.rs @@ -0,0 +1,34 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2022 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::LinearCostPrecompile; +use crate::runner::state::PrecompileFailure; +use module_evm_utiltity::evm::ExitSucceed; +use sp_std::vec::Vec; + +/// The identity precompile. +pub struct Identity; + +impl LinearCostPrecompile for Identity { + const BASE: u64 = 15; + const WORD: u64 = 3; + + fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { + Ok((ExitSucceed::Returned, input.to_vec())) + } +} diff --git a/modules/evm/src/precompiles/mod.rs b/modules/evm/src/precompiles/mod.rs new file mode 100644 index 0000000000..0bcf3232b8 --- /dev/null +++ b/modules/evm/src/precompiles/mod.rs @@ -0,0 +1,96 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2022 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Builtin precompiles. + +use crate::runner::state::{PrecompileFailure, PrecompileOutput, PrecompileResult}; +use module_evm_utiltity::evm::{Context, ExitError, ExitSucceed}; +use sp_std::vec::Vec; + +mod blake2; +mod bn128; +mod ecrecover; +mod ecrecoverpublickey; +mod identity; +mod modexp; +mod ripemd; +mod sha256; +mod sha3fips; + +pub use blake2::Blake2F; +pub use bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; +pub use ecrecover::ECRecover; +pub use ecrecoverpublickey::ECRecoverPublicKey; +pub use identity::Identity; +pub use modexp::{IstanbulModexp, Modexp}; +pub use ripemd::Ripemd160; +pub use sha256::Sha256; +pub use sha3fips::{Sha3FIPS256, Sha3FIPS512}; + +/// One single precompile used by EVM engine. +pub trait Precompile { + /// Try to execute the precompile. Calculate the amount of gas needed with given `input` and + /// `target_gas`. Return `Ok(status, output, gas_used)` if the execution is + /// successful. Otherwise return `Err(_)`. + fn execute(input: &[u8], target_gas: Option, context: &Context, is_static: bool) -> PrecompileResult; +} + +pub trait LinearCostPrecompile { + const BASE: u64; + const WORD: u64; + + fn execute(input: &[u8], cost: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure>; +} + +impl Precompile for T { + fn execute(input: &[u8], target_gas: Option, _: &Context, _: bool) -> PrecompileResult { + let cost = ensure_linear_cost(target_gas, input.len() as u64, T::BASE, T::WORD)?; + + let (exit_status, output) = T::execute(input, cost)?; + Ok(PrecompileOutput { + exit_status, + cost, + output, + logs: Default::default(), + }) + } +} + +/// Linear gas cost +fn ensure_linear_cost(target_gas: Option, len: u64, base: u64, word: u64) -> Result { + let cost = base + .checked_add( + word.checked_mul(len.saturating_add(31) / 32) + .ok_or(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + })?, + ) + .ok_or(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + })?; + + if let Some(target_gas) = target_gas { + if cost > target_gas { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + } + + Ok(cost) +} diff --git a/modules/evm/src/precompiles/modexp.rs b/modules/evm/src/precompiles/modexp.rs new file mode 100644 index 0000000000..3c8a3f4807 --- /dev/null +++ b/modules/evm/src/precompiles/modexp.rs @@ -0,0 +1,545 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2022 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::Precompile; +use crate::runner::state::{PrecompileFailure, PrecompileOutput, PrecompileResult}; +use module_evm_utiltity::evm::{Context, ExitError, ExitSucceed}; +use num::{BigUint, One, Zero}; +use sp_core::U256; +use sp_std::{ + cmp::{max, min}, + vec::Vec, +}; + +const MIN_GAS_COST: u64 = 200; + +struct ModexpPricer; + +impl ModexpPricer { + fn adjusted_exp_len(len: usize, exp_low: &BigUint) -> u64 { + let bit_index = if exp_low.is_zero() { + 0 + } else { + let bytes = exp_low.to_bytes_be(); + let length = min(32, bytes.len()); + let zeros = U256::from_big_endian(&bytes[..length]).leading_zeros() as u64; + 255 - zeros + }; + if len <= 32 { + bit_index + } else { + 8 * (len as u64 - 32) + bit_index + } + } + + fn mult_complexity(x: u64) -> u64 { + match x { + x if x <= 64 => x * x, + x if x <= 1024 => (x * x) / 4 + 96 * x - 3072, + x => (x * x) / 16 + 480 * x - 199_680, + } + } + + fn cost( + is_eip_2565: bool, + divisor: u64, + base_len: usize, + exp_len: usize, + mod_len: usize, + exponent: &BigUint, + target_gas: Option, + ) -> u64 { + if is_eip_2565 { + return Self::eip_2565_cost(divisor, base_len, mod_len, exp_len, exponent); + } + + if mod_len.is_zero() && base_len.is_zero() { + return 0; + } + + let max_len = (u32::max_value() / 2) as usize; + if base_len > max_len || mod_len > max_len || exp_len > max_len { + return target_gas.unwrap_or(u64::MAX); + } + + let m = max(mod_len, base_len); + + let adjusted_exp_len = Self::adjusted_exp_len(exp_len, exponent); + + let (gas, overflow) = Self::mult_complexity(m as u64).overflowing_mul(max(adjusted_exp_len, 1)); + if overflow { + return target_gas.unwrap_or(u64::MAX); + } + + gas / divisor + } + + fn eip_2565_mul_complexity(base_length: usize, modulus_length: usize) -> u64 { + let max_length = max(base_length, modulus_length) as u64; + let words = { + // div_ceil(max_length, 8); + let tmp = max_length / 8; + if (max_length % 8).is_zero() { + tmp + } else { + tmp + 1 + } + }; + words.saturating_mul(words) + } + + fn eip_2565_iter_count(exponent_length: usize, exponent: &BigUint) -> u64 { + let bytes = exponent.to_bytes_be(); + let length = min(32, bytes.len()); + let exponent = U256::from_big_endian(&bytes[..length]); + + let it = if exponent_length <= 32 && exponent.is_zero() { + 0 + } else if exponent_length <= 32 { + (exponent.bits() - 1) as u64 + } else { + // else > 32 + 8u64.saturating_mul(exponent_length as u64 - 32) + .saturating_add(exponent.bits().saturating_sub(1) as u64) + }; + max(it, 1) + } + + fn eip_2565_cost( + divisor: u64, + base_length: usize, + modulus_length: usize, + exponent_length: usize, + exponent: &BigUint, + ) -> u64 { + let multiplication_complexity = Self::eip_2565_mul_complexity(base_length, modulus_length); + let iteration_count = Self::eip_2565_iter_count(exponent_length, exponent); + max( + MIN_GAS_COST, + multiplication_complexity.saturating_mul(iteration_count) / divisor, + ) + } +} + +// ModExp expects the following as inputs: +// 1) 32 bytes expressing the length of base +// 2) 32 bytes expressing the length of exponent +// 3) 32 bytes expressing the length of modulus +// 4) base, size as described above +// 5) exponent, size as described above +// 6) modulus, size as described above +// +// +// NOTE: input sizes are bound to 1024 bytes, with the expectation +// that gas limits would be applied before actual computation. +// +// maximum stack size will also prevent abuse. +// +// see: https://eips.ethereum.org/EIPS/eip-198 + +pub trait ModexpImpl { + const DIVISOR: u64; + const EIP_2565: bool; + + fn execute_modexp(input: &[u8], target_gas: Option) -> PrecompileResult { + if let Some(gas_left) = target_gas { + if Self::EIP_2565 && gas_left < MIN_GAS_COST { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + }; + + let mut input = Vec::from(input); + if input.len() < 96 { + // fill with zeros + input.resize_with(96, Default::default); + } + + let max_len = U256::from(u32::max_value() / 2); + + let mut buf = [0u8; 32]; + + let base_len = { + buf.copy_from_slice(&input[0..32]); + let base_len = U256::from(&buf); + if base_len > max_len { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + base_len.as_usize() + }; + + let mod_len = { + buf.copy_from_slice(&input[64..96]); + let mod_len = U256::from(&buf); + if mod_len > max_len { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + mod_len.as_usize() + }; + + // Gas formula allows arbitrary large exp_len when base and modulus are empty, so we need to handle + // empty base first. + if mod_len.is_zero() && base_len.is_zero() { + return Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: if Self::EIP_2565 { MIN_GAS_COST } else { 0 }, + output: [0u8; 1].to_vec(), + logs: Default::default(), + }); + } + + let exp_len = { + buf.copy_from_slice(&input[32..64]); + let exp_len = U256::from(&buf); + if exp_len > max_len { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + exp_len.as_usize() + }; + + // input length should be at least 96 + user-specified length of base + exp + mod + let total_len = base_len + exp_len + mod_len + 96; + if input.len() < total_len { + // fill with zeros + input.resize_with(total_len, Default::default); + } + + // read the numbers themselves. + let base_start = 96; // previous 3 32-byte fields + let base = BigUint::from_bytes_be(&input[base_start..base_start + base_len]); + + let exp_start = base_start + base_len; + let exponent = BigUint::from_bytes_be(&input[exp_start..exp_start + exp_len]); + + // do our gas accounting + let gas_cost = ModexpPricer::cost( + Self::EIP_2565, + Self::DIVISOR, + base_len, + exp_len, + mod_len, + &exponent, + target_gas, + ); + if let Some(gas_left) = target_gas { + if gas_left < gas_cost { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + }; + + let mod_start = exp_start + exp_len; + let modulus = BigUint::from_bytes_be(&input[mod_start..mod_start + mod_len]); + + let bytes = if modulus.is_zero() || modulus.is_one() { + [0u8; 1].to_vec() + } else { + base.modpow(&exponent, &modulus).to_bytes_be() + }; + + // always true except in the case of zero-length modulus, which leads to + // output of length and value 1. + let output = match bytes.len() { + len if len < mod_len => { + let mut output = Vec::with_capacity(mod_len); + output.extend(core::iter::repeat(0).take(mod_len - len)); + output.extend_from_slice(&bytes[..]); + output + } + len if len == mod_len => bytes, + _ => [0u8; 0].to_vec(), + }; + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: gas_cost, + output, + logs: Default::default(), + }) + } +} + +pub struct IstanbulModexp; +pub struct Modexp; + +impl ModexpImpl for IstanbulModexp { + const DIVISOR: u64 = 20; + const EIP_2565: bool = false; +} + +impl ModexpImpl for Modexp { + const DIVISOR: u64 = 3; + const EIP_2565: bool = true; +} + +impl Precompile for IstanbulModexp { + fn execute(input: &[u8], target_gas: Option, _context: &Context, _is_static: bool) -> PrecompileResult { + Self::execute_modexp(input, target_gas) + } +} + +impl Precompile for Modexp { + fn execute(input: &[u8], target_gas: Option, _context: &Context, _is_static: bool) -> PrecompileResult { + Self::execute_modexp(input, target_gas) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_core::H256; + + #[test] + fn handle_min_gas() { + let input: [u8; 0] = []; + + let context: Context = Context { + address: Default::default(), + caller: Default::default(), + apparent_value: U256::zero(), + }; + + assert_eq!( + Modexp::execute(&input, Some(199), &context, false), + Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas + }) + ); + + assert_eq!( + Modexp::execute(&input, Some(200), &context, false), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: 200, + output: [0u8; 1].to_vec(), + logs: Default::default(), + }) + ); + } + + #[test] + fn test_empty_input() { + let input: [u8; 0] = []; + + let context: Context = Context { + address: Default::default(), + caller: Default::default(), + apparent_value: U256::zero(), + }; + + assert_eq!( + Modexp::execute(&input, None, &context, false), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: 200, + output: [0u8; 1].to_vec(), + logs: Default::default(), + }) + ); + } + + #[test] + fn test_insufficient_input() { + let input = hex::decode( + "0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001", + ) + .expect("Decode failed"); + + let context: Context = Context { + address: Default::default(), + caller: Default::default(), + apparent_value: U256::zero(), + }; + + assert_eq!( + Modexp::execute(&input, None, &context, false), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: 200, + output: [0u8; 1].to_vec(), + logs: Default::default(), + }) + ); + } + + #[test] + fn test_excessive_input() { + let input = hex::decode( + "1000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001", + ) + .expect("Decode failed"); + + let context: Context = Context { + address: Default::default(), + caller: Default::default(), + apparent_value: From::from(0), + }; + + assert_eq!( + IstanbulModexp::execute(&input, None, &context, false), + Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }) + ); + } + + #[test] + fn test_simple_inputs() { + let input = hex::decode( + "0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000001\ + 03\ + 05\ + 07", + ) + .expect("Decode failed"); + + // 3 ^ 5 % 7 == 5 + + let cost: u64 = 100000; + + let context: Context = Context { + address: Default::default(), + caller: Default::default(), + apparent_value: U256::zero(), + }; + + assert_eq!( + Modexp::execute(&input, Some(cost), &context, false), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: 200, + output: vec![5], + logs: Default::default(), + }) + ); + } + + #[test] + fn test_large_inputs() { + let input = hex::decode( + "0000000000000000000000000000000000000000000000000000000000000020\ + 0000000000000000000000000000000000000000000000000000000000000020\ + 0000000000000000000000000000000000000000000000000000000000000020\ + 000000000000000000000000000000000000000000000000000000000000EA5F\ + 0000000000000000000000000000000000000000000000000000000000000015\ + 0000000000000000000000000000000000000000000000000000000000003874", + ) + .expect("Decode failed"); + + // 59999 ^ 21 % 14452 = 10055 + + let cost: u64 = 100000; + + let context: Context = Context { + address: Default::default(), + caller: Default::default(), + apparent_value: U256::zero(), + }; + + assert_eq!( + IstanbulModexp::execute(&input, Some(cost), &context, false), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: 204, + output: H256::from_low_u64_be(10055).as_bytes().to_vec(), + logs: Default::default(), + }) + ); + } + + #[test] + fn test_large_computation() { + let input = hex::decode( + "0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000020\ + 0000000000000000000000000000000000000000000000000000000000000020\ + 03\ + fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e\ + fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + ) + .expect("Decode failed"); + + let cost: u64 = 100000; + + let context: Context = Context { + address: Default::default(), + caller: Default::default(), + apparent_value: U256::zero(), + }; + + assert_eq!( + IstanbulModexp::execute(&input, Some(cost), &context, false), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: 13056, + output: H256::from_low_u64_be(1).as_bytes().to_vec(), + logs: Default::default(), + }) + ); + } + + #[test] + fn test_zero_exp_with_33_length() { + // This is a regression test which ensures that the 'iteration_count' calculation + // in 'calculate_iteration_count' cannot underflow. + // + // In debug mode, this underflow could cause a panic. Otherwise, it causes N**0 to + // be calculated at more-than-normal expense. + // + // TODO: cite security advisory + + let input = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + ]; + + let cost: u64 = 100000; + + let context: Context = Context { + address: Default::default(), + caller: Default::default(), + apparent_value: U256::zero(), + }; + + assert_eq!( + Modexp::execute(&input, Some(cost), &context, false), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: 200, + output: [0u8; 1].to_vec(), + logs: Default::default(), + }) + ); + } +} diff --git a/modules/evm/src/precompiles/ripemd.rs b/modules/evm/src/precompiles/ripemd.rs new file mode 100644 index 0000000000..5fb73065e9 --- /dev/null +++ b/modules/evm/src/precompiles/ripemd.rs @@ -0,0 +1,37 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2022 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::LinearCostPrecompile; +use crate::runner::state::PrecompileFailure; +use module_evm_utiltity::evm::ExitSucceed; +use sha3::Digest; +use sp_std::vec::Vec; + +/// The ripemd precompile. +pub struct Ripemd160; + +impl LinearCostPrecompile for Ripemd160 { + const BASE: u64 = 600; + const WORD: u64 = 120; + + fn execute(input: &[u8], _cost: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { + let mut ret = [0u8; 32]; + ret[12..32].copy_from_slice(&ripemd160::Ripemd160::digest(input)); + Ok((ExitSucceed::Returned, ret.to_vec())) + } +} diff --git a/modules/evm/src/precompiles/sha256.rs b/modules/evm/src/precompiles/sha256.rs new file mode 100644 index 0000000000..9134fb936c --- /dev/null +++ b/modules/evm/src/precompiles/sha256.rs @@ -0,0 +1,35 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2022 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::LinearCostPrecompile; +use crate::runner::state::PrecompileFailure; +use module_evm_utiltity::evm::ExitSucceed; +use sp_std::vec::Vec; + +/// The sha256 precompile. +pub struct Sha256; + +impl LinearCostPrecompile for Sha256 { + const BASE: u64 = 60; + const WORD: u64 = 12; + + fn execute(input: &[u8], _cost: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { + let ret = sp_io::hashing::sha2_256(input); + Ok((ExitSucceed::Returned, ret.to_vec())) + } +} diff --git a/modules/evm/src/precompiles/sha3fips.rs b/modules/evm/src/precompiles/sha3fips.rs new file mode 100644 index 0000000000..3bbda2ddfa --- /dev/null +++ b/modules/evm/src/precompiles/sha3fips.rs @@ -0,0 +1,147 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2022 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::LinearCostPrecompile; +use crate::runner::state::PrecompileFailure; +use module_evm_utiltity::evm::ExitSucceed; +use sp_std::vec::Vec; +use tiny_keccak::Hasher; + +/// The Sha3FIPS256 precompile. +pub struct Sha3FIPS256; + +impl LinearCostPrecompile for Sha3FIPS256 { + const BASE: u64 = 60; + const WORD: u64 = 12; + + fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { + let mut output = [0; 32]; + let mut sha3 = tiny_keccak::Sha3::v256(); + sha3.update(input); + sha3.finalize(&mut output); + Ok((ExitSucceed::Returned, output.to_vec())) + } +} + +/// The Sha3FIPS512 precompile. +pub struct Sha3FIPS512; + +impl LinearCostPrecompile for Sha3FIPS512 { + const BASE: u64 = 60; + const WORD: u64 = 12; + + fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { + let mut output = [0; 64]; + let mut sha3 = tiny_keccak::Sha3::v512(); + sha3.update(input); + sha3.finalize(&mut output); + Ok((ExitSucceed::Returned, output.to_vec())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use module_evm_utiltity::evm::ExitError; + + #[test] + fn test_empty_input() -> std::result::Result<(), ExitError> { + let input: [u8; 0] = []; + let expected = b"\ + \xa7\xff\xc6\xf8\xbf\x1e\xd7\x66\x51\xc1\x47\x56\xa0\x61\xd6\x62\ + \xf5\x80\xff\x4d\xe4\x3b\x49\xfa\x82\xd8\x0a\x4b\x80\xf8\x43\x4a\ + "; + + let cost: u64 = 1; + + match ::execute(&input, cost) { + Ok((_, out)) => { + assert_eq!(out, expected); + Ok(()) + } + Err(e) => { + panic!("Test not expected to fail: {:?}", e); + } + } + } + + #[test] + fn hello_sha3_256() -> std::result::Result<(), ExitError> { + let input = b"hello"; + let expected = b"\ + \x33\x38\xbe\x69\x4f\x50\xc5\xf3\x38\x81\x49\x86\xcd\xf0\x68\x64\ + \x53\xa8\x88\xb8\x4f\x42\x4d\x79\x2a\xf4\xb9\x20\x23\x98\xf3\x92\ + "; + + let cost: u64 = 1; + + match ::execute(input, cost) { + Ok((_, out)) => { + assert_eq!(out, expected); + Ok(()) + } + Err(e) => { + panic!("Test not expected to fail: {:?}", e); + } + } + } + + #[test] + fn long_string_sha3_256() -> std::result::Result<(), ExitError> { + let input = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; + let expected = b"\ + \xbd\xe3\xf2\x69\x17\x5e\x1d\xcd\xa1\x38\x48\x27\x8a\xa6\x04\x6b\ + \xd6\x43\xce\xa8\x5b\x84\xc8\xb8\xbb\x80\x95\x2e\x70\xb6\xea\xe0\ + "; + + let cost: u64 = 1; + + match ::execute(input, cost) { + Ok((_, out)) => { + assert_eq!(out, expected); + Ok(()) + } + Err(e) => { + panic!("Test not expected to fail: {:?}", e); + } + } + } + + #[test] + fn long_string_sha3_512() -> std::result::Result<(), ExitError> { + let input = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; + let expected = b"\ + \xf3\x2a\x94\x23\x55\x13\x51\xdf\x0a\x07\xc0\xb8\xc2\x0e\xb9\x72\ + \x36\x7c\x39\x8d\x61\x06\x60\x38\xe1\x69\x86\x44\x8e\xbf\xbc\x3d\ + \x15\xed\xe0\xed\x36\x93\xe3\x90\x5e\x9a\x8c\x60\x1d\x9d\x00\x2a\ + \x06\x85\x3b\x97\x97\xef\x9a\xb1\x0c\xbd\xe1\x00\x9c\x7d\x0f\x09\ + "; + + let cost: u64 = 1; + + match ::execute(input, cost) { + Ok((_, out)) => { + assert_eq!(out, expected); + Ok(()) + } + Err(e) => { + panic!("Test not expected to fail: {:?}", e); + } + } + } +} diff --git a/runtime/common/src/precompile/mod.rs b/runtime/common/src/precompile/mod.rs index 515bd78fac..fc09d45f83 100644 --- a/runtime/common/src/precompile/mod.rs +++ b/runtime/common/src/precompile/mod.rs @@ -26,7 +26,10 @@ mod tests; use frame_support::log; use module_evm::{ - precompiles::{ECRecover, ECRecoverPublicKey, Identity, Precompile, Ripemd160, Sha256, Sha3FIPS256, Sha3FIPS512}, + precompiles::{ + Blake2F, Bn128Add, Bn128Mul, Bn128Pairing, ECRecover, ECRecoverPublicKey, Identity, IstanbulModexp, Modexp, + Precompile, Ripemd160, Sha256, Sha3FIPS256, Sha3FIPS512, + }, runner::state::{PrecompileFailure, PrecompileResult, PrecompileSet}, Context, ExitRevert, }; @@ -67,6 +70,11 @@ where H160::from_low_u64_be(2), H160::from_low_u64_be(3), H160::from_low_u64_be(4), + H160::from_low_u64_be(5), + H160::from_low_u64_be(6), + H160::from_low_u64_be(7), + H160::from_low_u64_be(8), + H160::from_low_u64_be(9), // Non-standard precompile starts with 128 H160::from_low_u64_be(128), H160::from_low_u64_be(129), @@ -114,6 +122,20 @@ where Some(Ripemd160::execute(input, target_gas, context, is_static)) } else if address == H160::from_low_u64_be(4) { Some(Identity::execute(input, target_gas, context, is_static)) + } else if address == H160::from_low_u64_be(5) { + if R::config().increase_state_access_gas { + Some(Modexp::execute(input, target_gas, context, is_static)) + } else { + Some(IstanbulModexp::execute(input, target_gas, context, is_static)) + } + } else if address == H160::from_low_u64_be(6) { + Some(Bn128Add::execute(input, target_gas, context, is_static)) + } else if address == H160::from_low_u64_be(7) { + Some(Bn128Mul::execute(input, target_gas, context, is_static)) + } else if address == H160::from_low_u64_be(8) { + Some(Bn128Pairing::execute(input, target_gas, context, is_static)) + } else if address == H160::from_low_u64_be(9) { + Some(Blake2F::execute(input, target_gas, context, is_static)) } // Non-standard precompile starts with 128 else if address == H160::from_low_u64_be(128) { From 0404076174b8432677c96553cebc3f1f351b9632 Mon Sep 17 00:00:00 2001 From: Ermal Kaleci Date: Fri, 25 Feb 2022 11:31:07 +0100 Subject: [PATCH 2/5] more tests --- modules/evm/Cargo.toml | 1 - modules/evm/src/precompiles/blake2/mod.rs | 120 ++++++++++++++++ modules/evm/src/precompiles/bn128.rs | 167 ++++++++++++++++++++++ modules/evm/src/precompiles/ecrecover.rs | 36 +++-- 4 files changed, 304 insertions(+), 20 deletions(-) diff --git a/modules/evm/Cargo.toml b/modules/evm/Cargo.toml index 0f2c441baf..b9d651bdb5 100644 --- a/modules/evm/Cargo.toml +++ b/modules/evm/Cargo.toml @@ -60,7 +60,6 @@ std = [ "sp-std/std", "sha3/std", "rlp/std", - "hex", "num/std", "module-evm-utiltity/std", "primitive-types/std", diff --git a/modules/evm/src/precompiles/blake2/mod.rs b/modules/evm/src/precompiles/blake2/mod.rs index e50d9c0422..06592f371e 100644 --- a/modules/evm/src/precompiles/blake2/mod.rs +++ b/modules/evm/src/precompiles/blake2/mod.rs @@ -114,3 +114,123 @@ impl Precompile for Blake2F { }) } } + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + use sp_core::U256; + + fn get_context() -> Context { + Context { + address: Default::default(), + caller: Default::default(), + apparent_value: U256::zero(), + } + } + + #[test] + fn blake2f_cost() { + // 5 rounds + let input = hex!("0000000548c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let result = Blake2F::execute(&input[..], None, &get_context(), false).unwrap(); + assert_eq!(result.cost, 5); + } + + #[test] + fn blake2f_invalid_length() { + let err = Err(PrecompileFailure::Error { + exit_status: ExitError::Other("input length for Blake2 F precompile should be exactly 213 bytes".into()), + }); + + // invalid input (too short) + let input = hex!("00"); + assert_eq!(Blake2F::execute(&input[..], None, &get_context(), false), err); + + // Test vector 1 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-1 + let input = hex!("00000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + assert_eq!(Blake2F::execute(&input[..], None, &get_context(), false), err); + + // Test vector 2 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-2 + let input = hex!("000000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + assert_eq!(Blake2F::execute(&input[..], None, &get_context(), false), err); + } + + #[test] + fn blake2f_bad_finalization_flag() { + let err = Err(PrecompileFailure::Error { + exit_status: ExitError::Other("incorrect final block indicator flag".into()), + }); + + // Test vector 3 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-3 + let input = hex!("0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000002"); + assert_eq!(Blake2F::execute(&input[..], None, &get_context(), false), err); + } + + #[test] + fn blake2f_zero_rounds_is_ok_test_vector_4() { + // Test vector 4 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-4 + let input = hex!("0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let expected = hex!("08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b"); + assert_eq!( + Blake2F::execute(&input[..], None, &get_context(), false) + .unwrap() + .output, + expected + ); + } + + #[test] + fn blake2_f_test_vector_5() { + // Test vector 5 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-5 + let input = hex!("0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let expected = hex!("ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"); + assert_eq!( + Blake2F::execute(&input[..], None, &get_context(), false) + .unwrap() + .output, + expected + ); + } + + #[test] + fn blake2_f_test_vector_6() { + // Test vector 6 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-6 + let input = hex!("0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000"); + let expected = hex!("75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735"); + assert_eq!( + Blake2F::execute(&input[..], None, &get_context(), false) + .unwrap() + .output, + expected + ); + } + + #[test] + fn blake2_f_test_vector_7() { + // Test vector 7 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-7 + let input = hex!("0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let expected = hex!("b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421"); + assert_eq!( + Blake2F::execute(&input[..], None, &get_context(), false) + .unwrap() + .output, + expected + ); + } + + #[ignore] + #[test] + fn blake2_f_test_vector_8() { + // Test vector 8 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-8 + // Note this test is slow, 4294967295/0xffffffff rounds take a while. + let input = hex!("ffffffff48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let expected = hex!("fc59093aafa9ab43daae0e914c57635c5402d8e3d2130eb9b3cc181de7f0ecf9b22bf99a7815ce16419e200e01846e6b5df8cc7703041bbceb571de6631d2615"); + assert_eq!( + Blake2F::execute(&input[..], None, &get_context(), false) + .unwrap() + .output, + expected + ); + } +} diff --git a/modules/evm/src/precompiles/bn128.rs b/modules/evm/src/precompiles/bn128.rs index 8b449de9e6..04c1a65659 100644 --- a/modules/evm/src/precompiles/bn128.rs +++ b/modules/evm/src/precompiles/bn128.rs @@ -247,3 +247,170 @@ impl Precompile for Bn128Pairing { }) } } + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + + fn get_context() -> Context { + Context { + address: Default::default(), + caller: Default::default(), + apparent_value: U256::zero(), + } + } + + #[test] + fn bn128_add() { + // zero-points additions + { + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + "}; + + let expected = hex! {" + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + "}; + + assert_eq!( + Bn128Add::execute(&input[..], None, &get_context(), false) + .unwrap() + .output, + expected + ); + } + + // no input, should not fail + { + let input = [0u8; 0]; + + let expected = hex! {" + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + "}; + + assert_eq!( + Bn128Add::execute(&input[..], None, &get_context(), false) + .unwrap() + .output, + expected + ); + } + + // should fail - point not on curve + { + let input = hex! {" + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + "}; + + assert_eq!( + Bn128Add::execute(&input[..], None, &get_context(), false), + Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid curve point".into()) + }) + ); + } + } + + #[test] + fn bn128_mul() { + // zero-point multiplication + { + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0200000000000000000000000000000000000000000000000000000000000000 + "}; + + let expected = hex! {" + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + "}; + + assert_eq!( + Bn128Mul::execute(&input[..], None, &get_context(), false) + .unwrap() + .output, + expected + ); + } + + // should fail - point not on curve + { + let input = hex! {" + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 0f00000000000000000000000000000000000000000000000000000000000000 + "}; + + assert_eq!( + Bn128Mul::execute(&input[..], None, &get_context(), false), + Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid curve point".into()) + }) + ); + } + } + + #[test] + fn bn128_pairing_empty() { + // should not fail, because empty input is a valid input of 0 elements + let input = [0u8; 0]; + + let expected = hex! {" + 0000000000000000000000000000000000000000000000000000000000000001 + "}; + + assert_eq!( + Bn128Pairing::execute(&input[..], None, &get_context(), false) + .unwrap() + .output, + expected + ); + } + + #[test] + fn bn128_pairing_notcurve() { + // should fail - point not on curve + let input = hex! {" + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + "}; + + assert_eq!( + Bn128Pairing::execute(&input[..], None, &get_context(), false), + Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid b argument - not on curve".into()) + }) + ); + } + + #[test] + fn bn128_pairing_fragmented() { + // should fail - input length is invalid + let input = hex! {" + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 111111111111111111111111111111 + "}; + + assert_eq!( + Bn128Pairing::execute(&input[..], None, &get_context(), false), + Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid input length, must be multiple of 192 (3 * (32*2))".into()) + }) + ); + } +} diff --git a/modules/evm/src/precompiles/ecrecover.rs b/modules/evm/src/precompiles/ecrecover.rs index 9dd72be9ae..2fbfc970c6 100644 --- a/modules/evm/src/precompiles/ecrecover.rs +++ b/modules/evm/src/precompiles/ecrecover.rs @@ -62,19 +62,17 @@ impl LinearCostPrecompile for ECRecover { #[cfg(test)] mod tests { use super::*; - use sp_core::H160; - use std::str::FromStr; + use hex_literal::hex; #[test] fn handle_invalid_v() { // V = 1 - let input = hex::decode( - "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c\ - 0000000000000000000000000000000000000000000000000000000000000001\ - 73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f\ - eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549", - ) - .expect("Decode failed"); + let input = hex! {" + 18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c + 0000000000000000000000000000000000000000000000000000000000000001 + 73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f + eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549 + "}; let (exit, output) = ECRecover::execute(&input, 0).unwrap(); assert_eq!(exit, ExitSucceed::Returned); assert_eq!(output, [0u8; 0].to_vec()); @@ -82,24 +80,24 @@ mod tests { #[test] fn validate_v() { - let address = H160::from_str("a94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap(); // V = 28 - let mut input = hex::decode( - "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c\ - 000000000000000000000000000000000000000000000000000000000000001c\ - 73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f\ - eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549", - ) - .expect("Decode failed"); + let mut input = hex! {" + 18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c + 000000000000000000000000000000000000000000000000000000000000001c + 73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f + eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549 + "}; + + let expected = hex!("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b"); let (exit, output) = ECRecover::execute(&input, 0).unwrap(); assert_eq!(exit, ExitSucceed::Returned); - assert_eq!(H160::from_slice(&output[12..]), address); + assert_eq!(output, expected); // V = 27 input[63] = 27; let (exit, output) = ECRecover::execute(&input, 0).unwrap(); assert_eq!(exit, ExitSucceed::Returned); - assert_ne!(H160::from_slice(&output[12..]), address); + assert_ne!(output, expected); } } From 17446f806f43350b7212b204f7cb243885b95f9b Mon Sep 17 00:00:00 2001 From: Ermal Kaleci Date: Fri, 25 Feb 2022 22:27:23 +0100 Subject: [PATCH 3/5] update modexp --- modules/evm/src/precompiles/modexp.rs | 645 +++++++++++++++----------- 1 file changed, 379 insertions(+), 266 deletions(-) diff --git a/modules/evm/src/precompiles/modexp.rs b/modules/evm/src/precompiles/modexp.rs index 3c8a3f4807..5878ea7cd5 100644 --- a/modules/evm/src/precompiles/modexp.rs +++ b/modules/evm/src/precompiles/modexp.rs @@ -26,24 +26,22 @@ use sp_std::{ vec::Vec, }; +const MAX_LENGTH: u64 = 1024; const MIN_GAS_COST: u64 = 200; struct ModexpPricer; impl ModexpPricer { - fn adjusted_exp_len(len: usize, exp_low: &BigUint) -> u64 { + fn adjusted_exp_len(len: u64, exp_low: U256) -> u64 { let bit_index = if exp_low.is_zero() { 0 } else { - let bytes = exp_low.to_bytes_be(); - let length = min(32, bytes.len()); - let zeros = U256::from_big_endian(&bytes[..length]).leading_zeros() as u64; - 255 - zeros + (255 - exp_low.leading_zeros()) as u64 }; if len <= 32 { bit_index } else { - 8 * (len as u64 - 32) + bit_index + 8 * (len - 32) + bit_index } } @@ -55,42 +53,80 @@ impl ModexpPricer { } } - fn cost( - is_eip_2565: bool, - divisor: u64, - base_len: usize, - exp_len: usize, - mod_len: usize, - exponent: &BigUint, - target_gas: Option, - ) -> u64 { - if is_eip_2565 { - return Self::eip_2565_cost(divisor, base_len, mod_len, exp_len, exponent); + fn read_lengths(input: &[u8]) -> (U256, U256, U256) { + let mut input = Vec::from(input); + if input.len() < 96 { + input.resize_with(96, Default::default); + } + let base_len = U256::from_big_endian(&input[..32]); + let exp_len = U256::from_big_endian(&input[32..64]); + let mod_len = U256::from_big_endian(&input[64..96]); + (base_len, exp_len, mod_len) + } + + fn read_exp(input: &[u8], base_len: U256, exp_len: U256) -> U256 { + let input_len = input.len(); + let base_len = if base_len > U256::from(u32::MAX) { + return U256::zero(); + } else { + base_len.low_u64() + }; + if base_len + 96 >= input_len as u64 { + U256::zero() + } else { + let exp_start = 96 + base_len as usize; + let remaining_len = input_len - exp_start; + let mut reader = Vec::from(&input[exp_start..exp_start + remaining_len]); + let len = if exp_len < U256::from(32) { + exp_len.low_u32() as usize + } else { + 32 + }; + + if reader.len() < len { + reader.resize_with(len, Default::default); + } + + let mut buf: Vec = Vec::new(); + buf.resize_with(32 - len, Default::default); + buf.extend(&reader[..min(len, remaining_len)]); + buf.resize_with(32, Default::default); + U256::from_big_endian(&buf[..]) } + } + + fn cost(divisor: u64, input: &[u8]) -> U256 { + // read lengths as U256 here for accurate gas calculation. + let (base_len, exp_len, mod_len) = Self::read_lengths(input); if mod_len.is_zero() && base_len.is_zero() { - return 0; + return U256::zero(); } - let max_len = (u32::max_value() / 2) as usize; + let max_len = U256::from(MAX_LENGTH - 96); if base_len > max_len || mod_len > max_len || exp_len > max_len { - return target_gas.unwrap_or(u64::MAX); + return U256::max_value(); } + // read fist 32-byte word of the exponent. + let exp_low = Self::read_exp(input, base_len, exp_len); + + let (base_len, exp_len, mod_len) = (base_len.low_u64(), exp_len.low_u64(), mod_len.low_u64()); + let m = max(mod_len, base_len); - let adjusted_exp_len = Self::adjusted_exp_len(exp_len, exponent); + let adjusted_exp_len = Self::adjusted_exp_len(exp_len, exp_low); - let (gas, overflow) = Self::mult_complexity(m as u64).overflowing_mul(max(adjusted_exp_len, 1)); + let (gas, overflow) = Self::mult_complexity(m).overflowing_mul(max(adjusted_exp_len, 1)); if overflow { - return target_gas.unwrap_or(u64::MAX); + return U256::max_value(); } - gas / divisor + (gas / divisor).into() } - fn eip_2565_mul_complexity(base_length: usize, modulus_length: usize) -> u64 { - let max_length = max(base_length, modulus_length) as u64; + fn eip_2565_mul_complexity(base_length: U256, modulus_length: U256) -> U256 { + let max_length = max(base_length, modulus_length); let words = { // div_ceil(max_length, 8); let tmp = max_length / 8; @@ -103,34 +139,32 @@ impl ModexpPricer { words.saturating_mul(words) } - fn eip_2565_iter_count(exponent_length: usize, exponent: &BigUint) -> u64 { - let bytes = exponent.to_bytes_be(); - let length = min(32, bytes.len()); - let exponent = U256::from_big_endian(&bytes[..length]); - - let it = if exponent_length <= 32 && exponent.is_zero() { - 0 - } else if exponent_length <= 32 { - (exponent.bits() - 1) as u64 + fn eip_2565_iter_count(exponent_length: U256, exponent: U256) -> U256 { + let thirty_two = U256::from(32); + let it = if exponent_length <= thirty_two && exponent.is_zero() { + U256::zero() + } else if exponent_length <= thirty_two { + U256::from(exponent.bits()) - U256::from(1) } else { // else > 32 - 8u64.saturating_mul(exponent_length as u64 - 32) - .saturating_add(exponent.bits().saturating_sub(1) as u64) + U256::from(8) + .saturating_mul(exponent_length - thirty_two) + .saturating_add(U256::from(exponent.bits()).saturating_sub(U256::from(1))) }; - max(it, 1) + max(it, U256::one()) } fn eip_2565_cost( - divisor: u64, - base_length: usize, - modulus_length: usize, - exponent_length: usize, - exponent: &BigUint, - ) -> u64 { + divisor: U256, + base_length: U256, + modulus_length: U256, + exponent_length: U256, + exponent: U256, + ) -> U256 { let multiplication_complexity = Self::eip_2565_mul_complexity(base_length, modulus_length); let iteration_count = Self::eip_2565_iter_count(exponent_length, exponent); max( - MIN_GAS_COST, + U256::from(MIN_GAS_COST), multiplication_complexity.saturating_mul(iteration_count) / divisor, ) } @@ -156,129 +190,65 @@ pub trait ModexpImpl { const DIVISOR: u64; const EIP_2565: bool; - fn execute_modexp(input: &[u8], target_gas: Option) -> PrecompileResult { - if let Some(gas_left) = target_gas { - if Self::EIP_2565 && gas_left < MIN_GAS_COST { - return Err(PrecompileFailure::Error { - exit_status: ExitError::OutOfGas, - }); - } - }; - - let mut input = Vec::from(input); - if input.len() < 96 { - // fill with zeros - input.resize_with(96, Default::default); + fn execute_modexp(input: &[u8]) -> Vec { + let mut reader = Vec::from(input); + if reader.len() < 96 { + reader.resize_with(96, Default::default); } - - let max_len = U256::from(u32::max_value() / 2); - - let mut buf = [0u8; 32]; - - let base_len = { - buf.copy_from_slice(&input[0..32]); - let base_len = U256::from(&buf); - if base_len > max_len { - return Err(PrecompileFailure::Error { - exit_status: ExitError::OutOfGas, - }); - } - base_len.as_usize() - }; - - let mod_len = { - buf.copy_from_slice(&input[64..96]); - let mod_len = U256::from(&buf); - if mod_len > max_len { - return Err(PrecompileFailure::Error { - exit_status: ExitError::OutOfGas, - }); - } - mod_len.as_usize() - }; + // read lengths as u64. + // ignoring the first 24 bytes might technically lead us to fall out of consensus, + // but so would running out of addressable memory! + let mut buf = [0u8; 8]; + buf.copy_from_slice(&reader[24..32]); + let base_len = u64::from_be_bytes(buf); + buf.copy_from_slice(&reader[32 + 24..64]); + let exp_len = u64::from_be_bytes(buf); + buf.copy_from_slice(&reader[64 + 24..96]); + let mod_len = u64::from_be_bytes(buf); // Gas formula allows arbitrary large exp_len when base and modulus are empty, so we need to handle // empty base first. - if mod_len.is_zero() && base_len.is_zero() { - return Ok(PrecompileOutput { - exit_status: ExitSucceed::Returned, - cost: if Self::EIP_2565 { MIN_GAS_COST } else { 0 }, - output: [0u8; 1].to_vec(), - logs: Default::default(), - }); - } - - let exp_len = { - buf.copy_from_slice(&input[32..64]); - let exp_len = U256::from(&buf); - if exp_len > max_len { - return Err(PrecompileFailure::Error { - exit_status: ExitError::OutOfGas, - }); + let r = if base_len == 0 && mod_len == 0 { + BigUint::zero() + } else { + let total_len = 96 + base_len + exp_len + mod_len; + if total_len > MAX_LENGTH { + return [0u8; 1].to_vec(); } - exp_len.as_usize() - }; - - // input length should be at least 96 + user-specified length of base + exp + mod - let total_len = base_len + exp_len + mod_len + 96; - if input.len() < total_len { - // fill with zeros - input.resize_with(total_len, Default::default); - } - - // read the numbers themselves. - let base_start = 96; // previous 3 32-byte fields - let base = BigUint::from_bytes_be(&input[base_start..base_start + base_len]); - - let exp_start = base_start + base_len; - let exponent = BigUint::from_bytes_be(&input[exp_start..exp_start + exp_len]); - - // do our gas accounting - let gas_cost = ModexpPricer::cost( - Self::EIP_2565, - Self::DIVISOR, - base_len, - exp_len, - mod_len, - &exponent, - target_gas, - ); - if let Some(gas_left) = target_gas { - if gas_left < gas_cost { - return Err(PrecompileFailure::Error { - exit_status: ExitError::OutOfGas, - }); + let mut reader = Vec::from(input); + if reader.len() < total_len as usize { + reader.resize_with(total_len as usize, Default::default); + } + // read the numbers themselves. + let base_end = 96 + base_len as usize; + let base = BigUint::from_bytes_be(&reader[96..base_end]); + let exp_end = base_end + exp_len as usize; + let exponent = BigUint::from_bytes_be(&reader[base_end..exp_end]); + let mod_end = exp_end + mod_len as usize; + let modulus = BigUint::from_bytes_be(&reader[exp_end..mod_end]); + + if modulus.is_zero() || modulus.is_one() { + BigUint::zero() + } else { + base.modpow(&exponent, &modulus) } }; - let mod_start = exp_start + exp_len; - let modulus = BigUint::from_bytes_be(&input[mod_start..mod_start + mod_len]); - - let bytes = if modulus.is_zero() || modulus.is_one() { - [0u8; 1].to_vec() - } else { - base.modpow(&exponent, &modulus).to_bytes_be() - }; + // write output to given memory, left padded and same length as the modulus. + let bytes = r.to_bytes_be(); // always true except in the case of zero-length modulus, which leads to // output of length and value 1. - let output = match bytes.len() { - len if len < mod_len => { - let mut output = Vec::with_capacity(mod_len); - output.extend(core::iter::repeat(0).take(mod_len - len)); - output.extend_from_slice(&bytes[..]); - output - } - len if len == mod_len => bytes, - _ => [0u8; 0].to_vec(), - }; - - Ok(PrecompileOutput { - exit_status: ExitSucceed::Returned, - cost: gas_cost, - output, - logs: Default::default(), - }) + if bytes.len() == mod_len as usize { + bytes.to_vec() + } else if bytes.len() < mod_len as usize { + let mut ret = Vec::with_capacity(mod_len as usize); + ret.extend(core::iter::repeat(0).take(mod_len as usize - bytes.len())); + ret.extend_from_slice(&bytes[..]); + ret.to_vec() + } else { + [0u8; 0].to_vec() + } } } @@ -297,44 +267,93 @@ impl ModexpImpl for Modexp { impl Precompile for IstanbulModexp { fn execute(input: &[u8], target_gas: Option, _context: &Context, _is_static: bool) -> PrecompileResult { - Self::execute_modexp(input, target_gas) + if input.len() as u64 > MAX_LENGTH { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + let cost = ModexpPricer::cost(Self::DIVISOR, input); + if let Some(target_gas) = target_gas { + if cost > U256::from(u64::MAX) || target_gas < cost.as_u64() { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + } + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: cost.as_u64(), + output: Self::execute_modexp(input), + logs: Default::default(), + }) } } impl Precompile for Modexp { fn execute(input: &[u8], target_gas: Option, _context: &Context, _is_static: bool) -> PrecompileResult { - Self::execute_modexp(input, target_gas) + if input.len() as u64 > MAX_LENGTH { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + + if let Some(target_gas) = target_gas { + if target_gas < MIN_GAS_COST { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + } + + let (base_len, exp_len, mod_len) = ModexpPricer::read_lengths(input); + let exp = ModexpPricer::read_exp(input, base_len, exp_len); + let cost = ModexpPricer::eip_2565_cost(U256::from(Self::DIVISOR), base_len, mod_len, exp_len, exp); + if let Some(target_gas) = target_gas { + if cost > U256::from(u64::MAX) || target_gas < cost.as_u64() { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + } + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: cost.as_u64(), + output: Self::execute_modexp(input), + logs: Default::default(), + }) } } #[cfg(test)] mod tests { use super::*; - use sp_core::H256; + use hex_literal::hex; - #[test] - fn handle_min_gas() { - let input: [u8; 0] = []; - - let context: Context = Context { + fn get_context() -> Context { + Context { address: Default::default(), caller: Default::default(), apparent_value: U256::zero(), - }; + } + } + #[test] + fn handle_min_gas() { assert_eq!( - Modexp::execute(&input, Some(199), &context, false), + Modexp::execute(&[], Some(199), &get_context(), false), Err(PrecompileFailure::Error { exit_status: ExitError::OutOfGas }) ); assert_eq!( - Modexp::execute(&input, Some(200), &context, false), + Modexp::execute(&[], Some(200), &get_context(), false), Ok(PrecompileOutput { exit_status: ExitSucceed::Returned, cost: 200, - output: [0u8; 1].to_vec(), + output: [0u8; 0].to_vec(), logs: Default::default(), }) ); @@ -342,20 +361,12 @@ mod tests { #[test] fn test_empty_input() { - let input: [u8; 0] = []; - - let context: Context = Context { - address: Default::default(), - caller: Default::default(), - apparent_value: U256::zero(), - }; - assert_eq!( - Modexp::execute(&input, None, &context, false), + Modexp::execute(&[], None, &get_context(), false), Ok(PrecompileOutput { exit_status: ExitSucceed::Returned, cost: 200, - output: [0u8; 1].to_vec(), + output: [0u8; 0].to_vec(), logs: Default::default(), }) ); @@ -363,21 +374,14 @@ mod tests { #[test] fn test_insufficient_input() { - let input = hex::decode( - "0000000000000000000000000000000000000000000000000000000000000001\ - 0000000000000000000000000000000000000000000000000000000000000001\ - 0000000000000000000000000000000000000000000000000000000000000001", - ) - .expect("Decode failed"); - - let context: Context = Context { - address: Default::default(), - caller: Default::default(), - apparent_value: U256::zero(), - }; + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000001 + "}; assert_eq!( - Modexp::execute(&input, None, &context, false), + Modexp::execute(&input, None, &get_context(), false), Ok(PrecompileOutput { exit_status: ExitSucceed::Returned, cost: 200, @@ -389,21 +393,45 @@ mod tests { #[test] fn test_excessive_input() { - let input = hex::decode( - "1000000000000000000000000000000000000000000000000000000000000001\ - 0000000000000000000000000000000000000000000000000000000000000001\ - 0000000000000000000000000000000000000000000000000000000000000001", - ) - .expect("Decode failed"); + let input = hex! {" + 1000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000001 + "}; - let context: Context = Context { - address: Default::default(), - caller: Default::default(), - apparent_value: From::from(0), - }; + assert_eq!( + Modexp::execute(&input, Some(100_000), &get_context(), false), + Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }) + ); + } + + #[test] + fn exp_len_overflow() { + let input = hex! {" + 00000000000000000000000000000000000000000000000000000000000000ff + 2a1e530000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + "}; assert_eq!( - IstanbulModexp::execute(&input, None, &context, false), + Modexp::execute(&input, Some(100_000), &get_context(), false), + Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }) + ); + } + + #[test] + fn gas_cost_multiplication_overflow() { + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000001 + 000000000000000000000000000000000000000000000000000000003b27bafd + 00000000000000000000000000000000000000000000000000000000503c8ac3 + "}; + assert_eq!( + Modexp::execute(&input, Some(100_000), &get_context(), false), Err(PrecompileFailure::Error { exit_status: ExitError::OutOfGas, }) @@ -412,28 +440,19 @@ mod tests { #[test] fn test_simple_inputs() { - let input = hex::decode( - "0000000000000000000000000000000000000000000000000000000000000001\ - 0000000000000000000000000000000000000000000000000000000000000001\ - 0000000000000000000000000000000000000000000000000000000000000001\ - 03\ - 05\ - 07", - ) - .expect("Decode failed"); + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000001 + 03 + 05 + 07 + "}; // 3 ^ 5 % 7 == 5 - let cost: u64 = 100000; - - let context: Context = Context { - address: Default::default(), - caller: Default::default(), - apparent_value: U256::zero(), - }; - assert_eq!( - Modexp::execute(&input, Some(cost), &context, false), + Modexp::execute(&input, Some(100_000), &get_context(), false), Ok(PrecompileOutput { exit_status: ExitSucceed::Returned, cost: 200, @@ -445,32 +464,36 @@ mod tests { #[test] fn test_large_inputs() { - let input = hex::decode( - "0000000000000000000000000000000000000000000000000000000000000020\ - 0000000000000000000000000000000000000000000000000000000000000020\ - 0000000000000000000000000000000000000000000000000000000000000020\ - 000000000000000000000000000000000000000000000000000000000000EA5F\ - 0000000000000000000000000000000000000000000000000000000000000015\ - 0000000000000000000000000000000000000000000000000000000000003874", - ) - .expect("Decode failed"); + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000020 + 000000000000000000000000000000000000000000000000000000000000EA5F + 0000000000000000000000000000000000000000000000000000000000000015 + 0000000000000000000000000000000000000000000000000000000000003874 + "}; // 59999 ^ 21 % 14452 = 10055 - let cost: u64 = 100000; - - let context: Context = Context { - address: Default::default(), - caller: Default::default(), - apparent_value: U256::zero(), - }; + let mut output = [0u8; 32]; + U256::from(10055u64).to_big_endian(&mut output); assert_eq!( - IstanbulModexp::execute(&input, Some(cost), &context, false), + IstanbulModexp::execute(&input, Some(100_000), &get_context(), false), Ok(PrecompileOutput { exit_status: ExitSucceed::Returned, cost: 204, - output: H256::from_low_u64_be(10055).as_bytes().to_vec(), + output: output.to_vec(), + logs: Default::default(), + }) + ); + + assert_eq!( + Modexp::execute(&input, Some(100_000), &get_context(), false), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: 200, + output: output.to_vec(), logs: Default::default(), }) ); @@ -478,30 +501,89 @@ mod tests { #[test] fn test_large_computation() { - let input = hex::decode( - "0000000000000000000000000000000000000000000000000000000000000001\ - 0000000000000000000000000000000000000000000000000000000000000020\ - 0000000000000000000000000000000000000000000000000000000000000020\ - 03\ - fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e\ - fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", - ) - .expect("Decode failed"); + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000020 + 03 + fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e + fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f + "}; + + let mut output = [0u8; 32]; + U256::from(1u64).to_big_endian(&mut output); - let cost: u64 = 100000; + assert_eq!( + IstanbulModexp::execute(&input, Some(100_000), &get_context(), false), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: 13056, + output: output.to_vec(), + logs: Default::default(), + }) + ); - let context: Context = Context { - address: Default::default(), - caller: Default::default(), - apparent_value: U256::zero(), - }; + assert_eq!( + Modexp::execute(&input, Some(100_000), &get_context(), false), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: 1360, + output: output.to_vec(), + logs: Default::default(), + }) + ); + } + + #[test] + fn zero_padding() { + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000002 + 0000000000000000000000000000000000000000000000000000000000000020 + 03 + ffff + 80 + "}; + + let expected = hex!("3b01b01ac41f2d6e917c6d6a221ce793802469026d9ab7578fa2e79e4da6aaab"); assert_eq!( - IstanbulModexp::execute(&input, Some(cost), &context, false), + IstanbulModexp::execute(&input, Some(100_000), &get_context(), false), Ok(PrecompileOutput { exit_status: ExitSucceed::Returned, - cost: 13056, - output: H256::from_low_u64_be(1).as_bytes().to_vec(), + cost: 768, + output: expected.to_vec(), + logs: Default::default(), + }) + ); + + assert_eq!( + Modexp::execute(&input, Some(100_000), &get_context(), false), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: 200, + output: expected.to_vec(), + logs: Default::default(), + }) + ); + } + + #[test] + fn zero_length_modulus() { + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000000 + 03 + ffff + "}; + + assert_eq!( + Modexp::execute(&input, Some(100_000), &get_context(), false), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: 200, + output: [0u8; 0].to_vec(), logs: Default::default(), }) ); @@ -524,22 +606,53 @@ mod tests { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ]; - let cost: u64 = 100000; + assert_eq!( + Modexp::execute(&input, Some(100_000), &get_context(), false), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: 200, + output: [0u8; 1].to_vec(), + logs: Default::default(), + }) + ); + } - let context: Context = Context { - address: Default::default(), - caller: Default::default(), - apparent_value: U256::zero(), - }; + #[test] + fn large_input() { + let input = vec![0u8; 1025]; + + assert_eq!( + IstanbulModexp::execute(&input[..1024], Some(100_000), &get_context(), false), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: 0, + output: [0u8; 0].to_vec(), + logs: Default::default(), + }) + ); assert_eq!( - Modexp::execute(&input, Some(cost), &context, false), + Modexp::execute(&input[..1024], Some(100_000), &get_context(), false), Ok(PrecompileOutput { exit_status: ExitSucceed::Returned, cost: 200, - output: [0u8; 1].to_vec(), + output: [0u8; 0].to_vec(), logs: Default::default(), }) ); + + assert_eq!( + IstanbulModexp::execute(&input, Some(100_000), &get_context(), false), + Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }) + ); + + assert_eq!( + Modexp::execute(&input, Some(100_000), &get_context(), false), + Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }) + ); } } From 6f10a7fff6fcbfae386df8c1d63676fd875b780a Mon Sep 17 00:00:00 2001 From: Ermal Kaleci Date: Fri, 25 Feb 2022 22:33:38 +0100 Subject: [PATCH 4/5] clippy --- modules/evm/src/precompiles/modexp.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/evm/src/precompiles/modexp.rs b/modules/evm/src/precompiles/modexp.rs index 5878ea7cd5..663894533f 100644 --- a/modules/evm/src/precompiles/modexp.rs +++ b/modules/evm/src/precompiles/modexp.rs @@ -239,9 +239,7 @@ pub trait ModexpImpl { // always true except in the case of zero-length modulus, which leads to // output of length and value 1. - if bytes.len() == mod_len as usize { - bytes.to_vec() - } else if bytes.len() < mod_len as usize { + if bytes.len() as u64 <= mod_len { let mut ret = Vec::with_capacity(mod_len as usize); ret.extend(core::iter::repeat(0).take(mod_len as usize - bytes.len())); ret.extend_from_slice(&bytes[..]); From 12f09b4876cb62a0cec9f055775d505ac10da324 Mon Sep 17 00:00:00 2001 From: Ermal Kaleci Date: Sat, 26 Feb 2022 15:39:27 +0100 Subject: [PATCH 5/5] rename file --- .../{ecrecoverpublickey.rs => ecrecover_publickey.rs} | 0 modules/evm/src/precompiles/mod.rs | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename modules/evm/src/precompiles/{ecrecoverpublickey.rs => ecrecover_publickey.rs} (100%) diff --git a/modules/evm/src/precompiles/ecrecoverpublickey.rs b/modules/evm/src/precompiles/ecrecover_publickey.rs similarity index 100% rename from modules/evm/src/precompiles/ecrecoverpublickey.rs rename to modules/evm/src/precompiles/ecrecover_publickey.rs diff --git a/modules/evm/src/precompiles/mod.rs b/modules/evm/src/precompiles/mod.rs index 0bcf3232b8..c9bd27216d 100644 --- a/modules/evm/src/precompiles/mod.rs +++ b/modules/evm/src/precompiles/mod.rs @@ -25,7 +25,7 @@ use sp_std::vec::Vec; mod blake2; mod bn128; mod ecrecover; -mod ecrecoverpublickey; +mod ecrecover_publickey; mod identity; mod modexp; mod ripemd; @@ -35,7 +35,7 @@ mod sha3fips; pub use blake2::Blake2F; pub use bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; pub use ecrecover::ECRecover; -pub use ecrecoverpublickey::ECRecoverPublicKey; +pub use ecrecover_publickey::ECRecoverPublicKey; pub use identity::Identity; pub use modexp::{IstanbulModexp, Modexp}; pub use ripemd::Ripemd160;