From 5947d433f7cee90f76c2ef2043af0ce932faedb1 Mon Sep 17 00:00:00 2001 From: Wei Tang Date: Thu, 30 Jul 2020 15:52:16 +0200 Subject: [PATCH] pallet-evm: add builtin support for the four basic Ethereum precompiles (#6743) * pallet-evm: add builtin support for the four basic Ethereum precompiles * linear_cost -> ensure_linear_cost to directly return OutOfGas error --- Cargo.toml | 2 + src/lib.rs | 2 +- src/precompiles.rs | 99 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a5ec28ddf9c3b..43ecc6f3688cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ rlp = { version = "0.4", default-features = false } evm = { version = "0.17", default-features = false } sha3 = { version = "0.8", default-features = false } impl-trait-for-tuples = "0.1" +ripemd160 = { version = "0.9", default-features = false } [features] default = ["std"] @@ -45,4 +46,5 @@ std = [ "primitive-types/std", "evm/std", "pallet-timestamp/std", + "ripemd160/std", ] diff --git a/src/lib.rs b/src/lib.rs index 0cbeac6fe2d6f..0dcc4526c7fbc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,8 +21,8 @@ #![cfg_attr(not(feature = "std"), no_std)] mod backend; -mod precompiles; mod tests; +pub mod precompiles; pub use crate::precompiles::{Precompile, Precompiles}; pub use crate::backend::{Account, Log, Vicinity, Backend}; diff --git a/src/precompiles.rs b/src/precompiles.rs index a6a10d45a2083..987724285d7b8 100644 --- a/src/precompiles.rs +++ b/src/precompiles.rs @@ -15,9 +15,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use sp_std::vec::Vec; +//! Builtin precompiles. + +use sp_std::{cmp::min, vec::Vec}; use sp_core::H160; use evm::{ExitError, ExitSucceed}; +use ripemd160::Digest; use impl_trait_for_tuples::impl_for_tuples; /// Custom precompiles to be used by EVM engine. @@ -67,3 +70,97 @@ impl Precompiles for Tuple { None } } + +/// Linear gas cost +fn ensure_linear_cost( + target_gas: Option, + len: usize, + base: usize, + word: usize +) -> Result { + let cost = base.checked_add( + word.checked_mul(len.saturating_add(31) / 32).ok_or(ExitError::OutOfGas)? + ).ok_or(ExitError::OutOfGas)?; + + if let Some(target_gas) = target_gas { + if cost > target_gas { + return Err(ExitError::OutOfGas) + } + } + + Ok(cost) +} + +/// The identity precompile. +pub struct Identity; + +impl Precompile for Identity { + fn execute( + input: &[u8], + target_gas: Option, + ) -> core::result::Result<(ExitSucceed, Vec, usize), ExitError> { + let cost = ensure_linear_cost(target_gas, input.len(), 15, 3)?; + + Ok((ExitSucceed::Returned, input.to_vec(), cost)) + } +} + +/// The ecrecover precompile. +pub struct ECRecover; + +impl Precompile for ECRecover { + fn execute( + i: &[u8], + target_gas: Option, + ) -> core::result::Result<(ExitSucceed, Vec, usize), ExitError> { + let cost = ensure_linear_cost(target_gas, i.len(), 3000, 0)?; + + 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(|_| ExitError::Other("Public key recover failed"))?; + let mut address = sp_io::hashing::keccak_256(&pubkey); + address[0..12].copy_from_slice(&[0u8; 12]); + + Ok((ExitSucceed::Returned, address.to_vec(), cost)) + } +} + +/// The ripemd precompile. +pub struct Ripemd160; + +impl Precompile for Ripemd160 { + fn execute( + input: &[u8], + target_gas: Option, + ) -> core::result::Result<(ExitSucceed, Vec, usize), ExitError> { + let cost = ensure_linear_cost(target_gas, input.len(), 600, 120)?; + + let ret = ripemd160::Ripemd160::digest(input).to_vec(); + Ok((ExitSucceed::Returned, ret, cost)) + } +} + +/// The sha256 precompile. +pub struct Sha256; + +impl Precompile for Sha256 { + fn execute( + input: &[u8], + target_gas: Option, + ) -> core::result::Result<(ExitSucceed, Vec, usize), ExitError> { + let cost = ensure_linear_cost(target_gas, input.len(), 60, 12)?; + + let ret = sp_io::hashing::sha2_256(input); + Ok((ExitSucceed::Returned, ret.to_vec(), cost)) + } +}