diff --git a/Cargo.lock b/Cargo.lock index 6a4b4de03..7faf386cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1335,6 +1335,7 @@ name = "fp-evm" version = "0.8.0" dependencies = [ "evm", + "impl-trait-for-tuples", "parity-scale-codec", "serde", "sp-core", @@ -1540,6 +1541,7 @@ dependencies = [ "pallet-balances", "pallet-ethereum", "pallet-evm", + "pallet-evm-precompile-simple", "pallet-grandpa", "pallet-randomness-collective-flip", "pallet-sudo", @@ -3691,21 +3693,16 @@ dependencies = [ name = "pallet-evm" version = "2.0.0" dependencies = [ - "ed25519-dalek", - "ethereum-types", "evm", "evm-gasometer", "evm-runtime", "fp-evm", "frame-support", "frame-system", - "impl-trait-for-tuples", - "num", "pallet-balances", "pallet-timestamp", "parity-scale-codec", "primitive-types", - "ripemd160", "rlp", "serde", "sha3 0.8.2", @@ -3713,9 +3710,62 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", +] + +[[package]] +name = "pallet-evm-precompile-blake2" +version = "2.0.0" +dependencies = [ + "evm", + "fp-evm", + "sp-core", + "sp-io", +] + +[[package]] +name = "pallet-evm-precompile-bn128" +version = "2.0.0" +dependencies = [ + "evm", + "fp-evm", + "sp-core", + "sp-io", "substrate-bn", ] +[[package]] +name = "pallet-evm-precompile-ed25519" +version = "2.0.0" +dependencies = [ + "ed25519-dalek", + "evm", + "fp-evm", + "sp-core", + "sp-io", +] + +[[package]] +name = "pallet-evm-precompile-modexp" +version = "2.0.0" +dependencies = [ + "evm", + "fp-evm", + "num", + "sp-core", + "sp-io", +] + +[[package]] +name = "pallet-evm-precompile-simple" +version = "2.0.0" +dependencies = [ + "evm", + "fp-evm", + "ripemd160", + "sp-core", + "sp-io", +] + [[package]] name = "pallet-grandpa" version = "2.0.0" diff --git a/Cargo.toml b/Cargo.toml index 84bac78b8..c67f32bcd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,11 @@ members = [ "frame/ethereum", "frame/evm", + "frame/evm/precompile/simple", + "frame/evm/precompile/modexp", + "frame/evm/precompile/ed25519", + "frame/evm/precompile/bn128", + "frame/evm/precompile/blake2", "client/consensus", "client/rpc-core", "client/rpc", diff --git a/frame/evm/Cargo.toml b/frame/evm/Cargo.toml index 297ed3002..0cd0ed50b 100644 --- a/frame/evm/Cargo.toml +++ b/frame/evm/Cargo.toml @@ -30,21 +30,9 @@ evm = { version = "0.19.0", default-features = false, features = ["with-codec"] evm-runtime = { version = "0.19.0", default-features = false } evm-gasometer = { version = "0.19.0", default-features = false } sha3 = { version = "0.8", default-features = false } -impl-trait-for-tuples = "0.1" -ripemd160 = { version = "0.9", default-features = false } - -num = { version = "0.3", features = ["alloc"], default-features = false, optional = true } -ethereum-types = { version = "0.9.2", default-features = false, optional = true } -bn = { package = "substrate-bn", version = "0.5", default-features = false, optional = true } -ed25519-dalek = { version = "1.0.0", features = ["alloc", "u64_backend"], default-features = false, optional = true } - [features] -default = ["std", "modexp", "ed25519", "bn128", "blake2f"] -blake2f = [] -modexp = ["num"] -ed25519 = ["ed25519-dalek"] -bn128 = ["bn", "ethereum-types"] +default = ["std"] std = [ "serde", "codec/std", @@ -64,5 +52,4 @@ std = [ "evm-runtime/std", "evm-gasometer/std", "pallet-timestamp/std", - "ripemd160/std", ] diff --git a/frame/evm/precompile/blake2/Cargo.toml b/frame/evm/precompile/blake2/Cargo.toml new file mode 100644 index 000000000..6153e796d --- /dev/null +++ b/frame/evm/precompile/blake2/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "pallet-evm-precompile-blake2" +version = "2.0.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "Apache-2.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "BLAKE2 precompiles for EVM pallet." + +[dependencies] +sp-core = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "frontier" } +sp-io = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "frontier" } +fp-evm = { version = "0.8.0", default-features = false, path = "../../../../primitives/evm" } +evm = { version = "0.19.0", default-features = false, features = ["with-codec"] } + +[features] +default = ["std"] +std = [ + "sp-core/std", + "sp-io/std", + "fp-evm/std", + "evm/std", +] \ No newline at end of file diff --git a/frame/evm/src/eip_152.rs b/frame/evm/precompile/blake2/src/eip_152.rs similarity index 80% rename from frame/evm/src/eip_152.rs rename to frame/evm/precompile/blake2/src/eip_152.rs index 9501cf6cd..61828a175 100644 --- a/frame/evm/src/eip_152.rs +++ b/frame/evm/precompile/blake2/src/eip_152.rs @@ -1,3 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2020 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /// 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. diff --git a/frame/evm/precompile/blake2/src/lib.rs b/frame/evm/precompile/blake2/src/lib.rs new file mode 100644 index 000000000..99df6eeeb --- /dev/null +++ b/frame/evm/precompile/blake2/src/lib.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2020 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +mod eip_152; + +use alloc::vec::Vec; +use core::mem::size_of; +use fp_evm::LinearCostPrecompile; +use evm::{ExitSucceed, ExitError}; + +pub struct Blake2F; + +impl LinearCostPrecompile for Blake2F { + const BASE: usize = 15; + const WORD: usize = 3; + + /// 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], + _: usize, + ) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + const BLAKE2_F_ARG_LEN: usize = 213; + + if input.len() != BLAKE2_F_ARG_LEN { + return Err(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_le_bytes(rounds_buf); + + let mut h_buf: [u8; 64] = [0; 64]; + h_buf.copy_from_slice(&input[4..48]); + 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).into(); + 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).into(); + 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(ExitError::Other("incorrect final block indicator flag".into())) + }; + + crate::eip_152::compress(&mut h, m, [t_0.into(), t_1.into()], 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((ExitSucceed::Returned, output_buf.to_vec())) + } +} diff --git a/frame/evm/precompile/bn128/Cargo.toml b/frame/evm/precompile/bn128/Cargo.toml new file mode 100644 index 000000000..9b95eced2 --- /dev/null +++ b/frame/evm/precompile/bn128/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "pallet-evm-precompile-bn128" +version = "2.0.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "Apache-2.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "BN128 precompiles for EVM pallet." + +[dependencies] +sp-core = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "frontier" } +sp-io = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "frontier" } +fp-evm = { version = "0.8.0", default-features = false, path = "../../../../primitives/evm" } +evm = { version = "0.19.0", default-features = false, features = ["with-codec"] } +bn = { package = "substrate-bn", version = "0.5", default-features = false } + +[features] +default = ["std"] +std = [ + "sp-core/std", + "sp-io/std", + "fp-evm/std", + "evm/std", +] \ No newline at end of file diff --git a/frame/evm/precompile/bn128/src/lib.rs b/frame/evm/precompile/bn128/src/lib.rs new file mode 100644 index 000000000..95e9a7a98 --- /dev/null +++ b/frame/evm/precompile/bn128/src/lib.rs @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: Apache-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2020 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloc::vec::Vec; +use sp_core::U256; +use fp_evm::LinearCostPrecompile; +use evm::{ExitSucceed, ExitError}; + +fn read_fr(input: &[u8], start_inx: usize) -> Result { + bn::Fr::from_slice(&input[start_inx..(start_inx + 32)]).map_err(|_| ExitError::Other("Invalid field element".into())) +} + +fn read_point(input: &[u8], start_inx: usize) -> Result { + use bn::{Fq, AffineG1, G1, Group}; + + let px = Fq::from_slice(&input[start_inx..(start_inx + 32)]).map_err(|_| ExitError::Other("Invalid point x coordinate".into()))?; + let py = Fq::from_slice(&input[(start_inx + 32)..(start_inx + 64)]).map_err(|_| ExitError::Other("Invalid point y coordinate".into()))?; + Ok( + if px == Fq::zero() && py == Fq::zero() { + G1::zero() + } else { + AffineG1::new(px, py).map_err(|_| ExitError::Other("Invalid curve point".into()))?.into() + } + ) +} + +/// The Bn128Add builtin +pub struct Bn128Add; + +impl LinearCostPrecompile for Bn128Add { + const BASE: usize = 15; + const WORD: usize = 3; + + fn execute( + input: &[u8], + _: usize, + ) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + 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(|_| ExitError::Other("Cannot fail since 0..32 is 32-byte length".into()))?; + sum.y().to_big_endian(&mut buf[32..64]).map_err(|_| ExitError::Other("Cannot fail since 32..64 is 32-byte length".into()))?; + } + + Ok((ExitSucceed::Returned, buf.to_vec())) + } +} + +/// The Bn128Mul builtin +pub struct Bn128Mul; + +impl LinearCostPrecompile for Bn128Mul { + const BASE: usize = 15; + const WORD: usize = 3; + + fn execute( + input: &[u8], + _: usize, + ) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + 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(|_| ExitError::Other("Cannot fail since 0..32 is 32-byte length".into()))?; + sum.y().to_big_endian(&mut buf[32..64]).map_err(|_| ExitError::Other("Cannot fail since 32..64 is 32-byte length".into()))?; + } + + Ok((ExitSucceed::Returned, buf.to_vec())) + } +} + +/// The Bn128Pairing builtin +pub struct Bn128Pairing; + +impl LinearCostPrecompile for Bn128Pairing { + const BASE: usize = 15; + const WORD: usize = 3; + + fn execute( + input: &[u8], + _: usize, + ) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + use bn::{AffineG1, AffineG2, Fq, Fq2, pairing_batch, G1, G2, Gt, Group}; + + let ret_val = if input.is_empty() { + U256::one() + } else { + // (a, b_a, b_b - each 64-byte affine coordinates) + let elements = input.len() / 192; + let mut vals = Vec::new(); + for idx in 0..elements { + let a_x = Fq::from_slice(&input[idx*192..idx*192+32]) + .map_err(|_| ExitError::Other("Invalid a argument x coordinate".into()))?; + + let a_y = Fq::from_slice(&input[idx*192+32..idx*192+64]) + .map_err(|_| ExitError::Other("Invalid a argument y coordinate".into()))?; + + let b_a_y = Fq::from_slice(&input[idx*192+64..idx*192+96]) + .map_err(|_| 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(|_| 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(|_| 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(|_| 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(|_| 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(|_| ExitError::Other("Invalid a argument - not on curve".into()))?) + }; + vals.push((a, b)); + }; + + let mul = pairing_batch(&vals); + + if mul == Gt::one() { + U256::one() + } else { + U256::zero() + } + }; + + let mut buf = [0u8; 32]; + ret_val.to_big_endian(&mut buf); + + Ok((ExitSucceed::Returned, buf.to_vec())) + } +} diff --git a/frame/evm/precompile/ed25519/Cargo.toml b/frame/evm/precompile/ed25519/Cargo.toml new file mode 100644 index 000000000..f4b420c20 --- /dev/null +++ b/frame/evm/precompile/ed25519/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "pallet-evm-precompile-ed25519" +version = "2.0.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "Apache-2.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "ED25519 precompiles for EVM pallet." + +[dependencies] +sp-core = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "frontier" } +sp-io = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "frontier" } +fp-evm = { version = "0.8.0", default-features = false, path = "../../../../primitives/evm" } +evm = { version = "0.19.0", default-features = false, features = ["with-codec"] } +ed25519-dalek = { version = "1.0.0", features = ["alloc", "u64_backend"], default-features = false } + +[features] +default = ["std"] +std = [ + "sp-core/std", + "sp-io/std", + "fp-evm/std", + "evm/std", + "ed25519-dalek/std", +] \ No newline at end of file diff --git a/frame/evm/precompile/ed25519/src/lib.rs b/frame/evm/precompile/ed25519/src/lib.rs new file mode 100644 index 000000000..4151dc237 --- /dev/null +++ b/frame/evm/precompile/ed25519/src/lib.rs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2020 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloc::vec::Vec; +use core::{cmp::min, convert::TryFrom}; +use fp_evm::LinearCostPrecompile; +use evm::{ExitSucceed, ExitError}; +use ed25519_dalek::{PublicKey, Verifier, Signature}; + +pub struct Ed25519Verify; + +impl LinearCostPrecompile for Ed25519Verify { + const BASE: usize = 15; + const WORD: usize = 3; + + fn execute( + input: &[u8], + _: usize, + ) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + let len = min(input.len(), 128); + + let mut i = [0u8; 128]; + i[..len].copy_from_slice(&input[..len]); + + let mut buf = [0u8; 4]; + + let msg = &i[0..32]; + let pk = PublicKey::from_bytes(&i[32..64]) + .map_err(|_| ExitError::Other("Public key recover failed".into()))?; + let sig = Signature::try_from(&i[64..128]) + .map_err(|_| ExitError::Other("Signature recover failed".into()))?; + + // https://docs.rs/rust-crypto/0.2.36/crypto/ed25519/fn.verify.html + if pk.verify(msg, &sig).is_ok() { + buf[3] = 0u8; + } else { + buf[3] = 1u8; + }; + + Ok((ExitSucceed::Returned, buf.to_vec())) + } +} diff --git a/frame/evm/precompile/modexp/Cargo.toml b/frame/evm/precompile/modexp/Cargo.toml new file mode 100644 index 000000000..c256754f6 --- /dev/null +++ b/frame/evm/precompile/modexp/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "pallet-evm-precompile-modexp" +version = "2.0.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "Apache-2.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "MODEXP precompiles for EVM pallet." + +[dependencies] +sp-core = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "frontier" } +sp-io = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "frontier" } +fp-evm = { version = "0.8.0", default-features = false, path = "../../../../primitives/evm" } +evm = { version = "0.19.0", default-features = false, features = ["with-codec"] } +num = { version = "0.3", features = ["alloc"], default-features = false } + +[features] +default = ["std"] +std = [ + "sp-core/std", + "sp-io/std", + "fp-evm/std", + "evm/std", + "num/std", +] \ No newline at end of file diff --git a/frame/evm/precompile/modexp/src/lib.rs b/frame/evm/precompile/modexp/src/lib.rs new file mode 100644 index 000000000..e20282502 --- /dev/null +++ b/frame/evm/precompile/modexp/src/lib.rs @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2020 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloc::vec::Vec; +use core::cmp::max; +use fp_evm::LinearCostPrecompile; +use evm::{ExitSucceed, ExitError}; +use num::{BigUint, Zero, One}; + +pub struct Modexp; + +impl LinearCostPrecompile for Modexp { + const BASE: usize = 15; + const WORD: usize = 3; + + fn execute( + input: &[u8], + _: usize, + ) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + let mut buf = [0; 32]; + buf.copy_from_slice(&input[0..32]); + let mut len_bytes = [0u8; 8]; + len_bytes.copy_from_slice(&buf[24..]); + let base_len = u64::from_be_bytes(len_bytes) as usize; + + buf = [0; 32]; + buf.copy_from_slice(&input[32..64]); + len_bytes = [0u8; 8]; + len_bytes.copy_from_slice(&buf[24..]); + let exp_len = u64::from_be_bytes(len_bytes) as usize; + + buf = [0; 32]; + buf.copy_from_slice(&input[64..96]); + len_bytes = [0u8; 8]; + len_bytes.copy_from_slice(&buf[24..]); + let mod_len = u64::from_be_bytes(len_bytes) as usize; + + // Gas formula allows arbitrary large exp_len when base and modulus are empty, so we need to handle empty base first. + let r = if base_len == 0 && mod_len == 0 { + BigUint::zero() + } else { + // read the numbers themselves. + let mut buf = Vec::with_capacity(max(mod_len, max(base_len, exp_len))); + buf.copy_from_slice(&input[0..base_len]); + let base = BigUint::from_bytes_be(&buf[..base_len]); + + buf = Vec::with_capacity(max(mod_len, max(base_len, exp_len))); + buf.copy_from_slice(&input[base_len..base_len + exp_len]); + let exponent = BigUint::from_bytes_be(&buf[..exp_len]); + + buf = Vec::with_capacity(max(mod_len, max(base_len, exp_len))); + buf.copy_from_slice(&input[(base_len + exp_len)..(base_len + exp_len + mod_len)]); + let modulus = BigUint::from_bytes_be(&buf[..mod_len]); + + if modulus.is_zero() || modulus.is_one() { + BigUint::zero() + } else { + base.modpow(&exponent, &modulus) + } + }; + + // 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. + if bytes.len() <= mod_len { + let res_start = mod_len - bytes.len(); + let mut ret = Vec::with_capacity(bytes.len() - mod_len); + ret.copy_from_slice(&bytes[res_start..bytes.len()]); + Ok((ExitSucceed::Returned, ret.to_vec())) + } else { + Err(ExitError::Other("failed".into())) + } + } +} diff --git a/frame/evm/precompile/simple/Cargo.toml b/frame/evm/precompile/simple/Cargo.toml new file mode 100644 index 000000000..8a8662ba3 --- /dev/null +++ b/frame/evm/precompile/simple/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "pallet-evm-precompile-simple" +version = "2.0.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "Apache-2.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "Simple precompiles for EVM pallet." + +[dependencies] +sp-core = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "frontier" } +sp-io = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "frontier" } +fp-evm = { version = "0.8.0", default-features = false, path = "../../../../primitives/evm" } +evm = { version = "0.19.0", default-features = false, features = ["with-codec"] } +ripemd160 = { version = "0.9", default-features = false } + +[features] +default = ["std"] +std = [ + "sp-core/std", + "sp-io/std", + "fp-evm/std", + "evm/std", + "ripemd160/std", +] \ No newline at end of file diff --git a/frame/evm/precompile/simple/src/lib.rs b/frame/evm/precompile/simple/src/lib.rs new file mode 100644 index 000000000..912052122 --- /dev/null +++ b/frame/evm/precompile/simple/src/lib.rs @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: Apache-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2020 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloc::vec::Vec; +use core::cmp::min; +use fp_evm::LinearCostPrecompile; +use evm::{ExitSucceed, ExitError}; + +/// The identity precompile. +pub struct Identity; + +impl LinearCostPrecompile for Identity { + const BASE: usize = 15; + const WORD: usize = 3; + + fn execute( + input: &[u8], + _: usize, + ) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + Ok((ExitSucceed::Returned, input.to_vec())) + } +} + +/// The ecrecover precompile. +pub struct ECRecover; + +impl LinearCostPrecompile for ECRecover { + const BASE: usize = 3000; + const WORD: usize = 0; + + fn execute( + i: &[u8], + _: usize, + ) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + 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".into()))?; + let mut address = sp_io::hashing::keccak_256(&pubkey); + address[0..12].copy_from_slice(&[0u8; 12]); + + Ok((ExitSucceed::Returned, address.to_vec())) + } +} + +/// The ripemd precompile. +pub struct Ripemd160; + +impl LinearCostPrecompile for Ripemd160 { + const BASE: usize = 600; + const WORD: usize = 120; + + fn execute( + input: &[u8], + _cost: usize, + ) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + use ripemd160::Digest; + + 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: usize = 60; + const WORD: usize = 12; + + fn execute( + input: &[u8], + _cost: usize, + ) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + let ret = sp_io::hashing::sha2_256(input); + Ok((ExitSucceed::Returned, ret.to_vec())) + } +} diff --git a/frame/evm/src/lib.rs b/frame/evm/src/lib.rs index c8f2e2f1b..403556973 100644 --- a/frame/evm/src/lib.rs +++ b/frame/evm/src/lib.rs @@ -55,14 +55,12 @@ mod tests; pub mod runner; -pub mod precompiles; -#[cfg(feature = "blake2f")] -pub mod eip_152; - -pub use crate::precompiles::{Precompile, Precompiles}; pub use crate::runner::Runner; -pub use fp_evm::{Account, Log, Vicinity, ExecutionInfo, CallInfo, CreateInfo}; +pub use fp_evm::{ + Account, Log, Vicinity, ExecutionInfo, CallInfo, CreateInfo, Precompile, + PrecompileSet, LinearCostPrecompile, +}; pub use evm::{ExitReason, ExitSucceed, ExitError, ExitRevert, ExitFatal}; use sp_std::vec::Vec; @@ -253,7 +251,7 @@ pub trait Config: frame_system::Config + pallet_timestamp::Config { /// The overarching event type. type Event: From> + Into<::Event>; /// Precompiles associated with this EVM engine. - type Precompiles: Precompiles; + type Precompiles: PrecompileSet; /// Chain ID of EVM. type ChainId: Get; /// EVM execution runner. diff --git a/frame/evm/src/precompiles.rs b/frame/evm/src/precompiles.rs deleted file mode 100644 index e62a53ebf..000000000 --- a/frame/evm/src/precompiles.rs +++ /dev/null @@ -1,502 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// This file is part of Frontier. -// -// Copyright (c) 2020 Parity Technologies (UK) Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Precompile implementations. - -use sp_std::mem::size_of; -use sp_std::cmp::max; -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; -use sp_std::convert::TryFrom; - -#[cfg(feature = "ed25519")] -use ed25519_dalek::{PublicKey, Verifier, Signature}; - -#[cfg(feature = "modexp")] -use num::{BigUint, Zero, One}; - -#[cfg(feature = "blake2")] -use blake2_rfc::blake2b::Blake2b; - -#[cfg(feature = "bn128")] -use ethereum_types::{U256}; - -/// Custom precompiles to be used by EVM engine. -pub trait Precompiles { - /// Try to execute the code address as precompile. If the code address is not - /// a precompile or the precompile is not yet available, return `None`. - /// Otherwise, calculate the amount of gas needed with given `input` and - /// `target_gas`. Return `Some(Ok(status, output, gas_used))` if the execution - /// is successful. Otherwise return `Some(Err(_))`. - fn execute( - address: H160, - input: &[u8], - target_gas: Option, - ) -> Option, usize), ExitError>>; -} - -/// 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, - ) -> core::result::Result<(ExitSucceed, Vec, usize), ExitError>; -} - -#[impl_for_tuples(16)] -#[tuple_types_no_default_trait_bound] -impl Precompiles for Tuple { - for_tuples!( where #( Tuple: Precompile )* ); - - fn execute( - address: H160, - input: &[u8], - target_gas: Option, - ) -> Option, usize), ExitError>> { - let mut index = 0; - - for_tuples!( #( - index += 1; - if address == H160::from_low_u64_be(index) { - return Some(Tuple::execute(input, target_gas)) - } - )* ); - - 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".into()))?; - 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 mut ret = [0u8; 32]; - ret[12..32].copy_from_slice(&ripemd160::Ripemd160::digest(input)); - Ok((ExitSucceed::Returned, ret.to_vec(), 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)) - } -} - -#[cfg(feature = "modexp")] -pub struct Modexp; - -#[cfg(feature = "modexp")] -impl Precompile for Modexp { - 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)?; - let mut buf = [0; 32]; - buf.copy_from_slice(&input[0..32]); - let mut len_bytes = [0u8; 8]; - len_bytes.copy_from_slice(&buf[24..]); - let base_len = u64::from_be_bytes(len_bytes) as usize; - - buf = [0; 32]; - buf.copy_from_slice(&input[32..64]); - len_bytes = [0u8; 8]; - len_bytes.copy_from_slice(&buf[24..]); - let exp_len = u64::from_be_bytes(len_bytes) as usize; - - buf = [0; 32]; - buf.copy_from_slice(&input[64..96]); - len_bytes = [0u8; 8]; - len_bytes.copy_from_slice(&buf[24..]); - let mod_len = u64::from_be_bytes(len_bytes) as usize; - - // Gas formula allows arbitrary large exp_len when base and modulus are empty, so we need to handle empty base first. - let r = if base_len == 0 && mod_len == 0 { - BigUint::zero() - } else { - // read the numbers themselves. - let mut buf = Vec::with_capacity(max(mod_len, max(base_len, exp_len))); - buf.copy_from_slice(&input[0..base_len]); - let base = BigUint::from_bytes_be(&buf[..base_len]); - - buf = Vec::with_capacity(max(mod_len, max(base_len, exp_len))); - buf.copy_from_slice(&input[base_len..base_len + exp_len]); - let exponent = BigUint::from_bytes_be(&buf[..exp_len]); - - buf = Vec::with_capacity(max(mod_len, max(base_len, exp_len))); - buf.copy_from_slice(&input[(base_len + exp_len)..(base_len + exp_len + mod_len)]); - let modulus = BigUint::from_bytes_be(&buf[..mod_len]); - - if modulus.is_zero() || modulus.is_one() { - BigUint::zero() - } else { - base.modpow(&exponent, &modulus) - } - }; - - // 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. - if bytes.len() <= mod_len { - let res_start = mod_len - bytes.len(); - let mut ret = Vec::with_capacity(bytes.len() - mod_len); - ret.copy_from_slice(&bytes[res_start..bytes.len()]); - Ok((ExitSucceed::Returned, ret.to_vec(), cost)) - } else { - Err(ExitError::Other("failed".into())) - } - } -} - -#[cfg(feature = "ed25519")] -pub struct Ed25519Verify; - -#[cfg(feature = "ed25519")] -impl Precompile for Ed25519Verify { - 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)?; - - let len = min(input.len(), 128); - - let mut i = [0u8; 128]; - i[..len].copy_from_slice(&input[..len]); - - let mut buf = [0u8; 4]; - - let msg = &i[0..32]; - let pk = PublicKey::from_bytes(&i[32..64]) - .map_err(|_| ExitError::Other("Public key recover failed".into()))?; - let sig = Signature::try_from(&i[64..128]) - .map_err(|_| ExitError::Other("Signature recover failed".into()))?; - - // https://docs.rs/rust-crypto/0.2.36/crypto/ed25519/fn.verify.html - if pk.verify(msg, &sig).is_ok() { - buf[3] = 0u8; - } else { - buf[3] = 1u8; - }; - - Ok((ExitSucceed::Returned, buf.to_vec(), cost)) - } -} - -#[cfg(feature = "bn128")] -fn read_fr(input: &[u8], start_inx: usize) -> Result { - bn::Fr::from_slice(&input[start_inx..(start_inx + 32)]).map_err(|_| ExitError::Other("Invalid field element".into())) -} - -#[cfg(feature = "bn128")] -fn read_point(input: &[u8], start_inx: usize) -> Result { - use bn::{Fq, AffineG1, G1, Group}; - - let px = Fq::from_slice(&input[start_inx..(start_inx + 32)]).map_err(|_| ExitError::Other("Invalid point x coordinate".into()))?; - let py = Fq::from_slice(&input[(start_inx + 32)..(start_inx + 64)]).map_err(|_| ExitError::Other("Invalid point y coordinate".into()))?; - Ok( - if px == Fq::zero() && py == Fq::zero() { - G1::zero() - } else { - AffineG1::new(px, py).map_err(|_| ExitError::Other("Invalid curve point".into()))?.into() - } - ) -} - -/// The Bn128Add builtin -#[cfg(feature = "bn128")] -pub struct Bn128Add; - -/// The Bn128Mul builtin -#[cfg(feature = "bn128")] -pub struct Bn128Mul; - -/// The Bn128Pairing builtin -#[cfg(feature = "bn128")] -pub struct Bn128Pairing; - -/// The bn128 addition arithmetic precompile. -#[cfg(feature = "bn128")] -impl Precompile for Bn128Add { - 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)?; - - 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(|_| ExitError::Other("Cannot fail since 0..32 is 32-byte length".into()))?; - sum.y().to_big_endian(&mut buf[32..64]).map_err(|_| ExitError::Other("Cannot fail since 32..64 is 32-byte length".into()))?; - } - - Ok((ExitSucceed::Returned, buf.to_vec(), cost)) - } -} - -/// The bn128 multiplication arithmetic precompile. -#[cfg(feature = "bn128")] -impl Precompile for Bn128Mul { - fn execute( - input: &[u8], - target_gas: Option, - ) -> core::result::Result<(ExitSucceed, Vec, usize), ExitError> { - use bn::AffineG1; - - let cost = ensure_linear_cost(target_gas, input.len(), 15, 3)?; - - 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(|_| ExitError::Other("Cannot fail since 0..32 is 32-byte length".into()))?; - sum.y().to_big_endian(&mut buf[32..64]).map_err(|_| ExitError::Other("Cannot fail since 32..64 is 32-byte length".into()))?; - } - - Ok((ExitSucceed::Returned, buf.to_vec(), cost)) - } -} - -/// The bn128 pairing precompile. -#[cfg(feature = "bn128")] -impl Precompile for Bn128Pairing { - fn execute( - input: &[u8], - target_gas: Option, - ) -> core::result::Result<(ExitSucceed, Vec, usize), ExitError> { - use bn::{AffineG1, AffineG2, Fq, Fq2, pairing_batch, G1, G2, Gt, Group}; - - let cost = ensure_linear_cost(target_gas, input.len(), 15, 3)?; - - let ret_val = if input.is_empty() { - U256::one() - } else { - // (a, b_a, b_b - each 64-byte affine coordinates) - let elements = input.len() / 192; - let mut vals = Vec::new(); - for idx in 0..elements { - let a_x = Fq::from_slice(&input[idx*192..idx*192+32]) - .map_err(|_| ExitError::Other("Invalid a argument x coordinate".into()))?; - - let a_y = Fq::from_slice(&input[idx*192+32..idx*192+64]) - .map_err(|_| ExitError::Other("Invalid a argument y coordinate".into()))?; - - let b_a_y = Fq::from_slice(&input[idx*192+64..idx*192+96]) - .map_err(|_| 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(|_| 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(|_| 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(|_| 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(|_| 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(|_| ExitError::Other("Invalid a argument - not on curve".into()))?) - }; - vals.push((a, b)); - }; - - let mul = pairing_batch(&vals); - - if mul == Gt::one() { - U256::one() - } else { - U256::zero() - } - }; - - let mut buf = [0u8; 32]; - ret_val.to_big_endian(&mut buf); - - Ok((ExitSucceed::Returned, buf.to_vec(), cost)) - } -} - -#[cfg(feature = "blake2f")] -pub struct Blake2F; - -#[cfg(feature = "blake2f")] -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, - ) -> core::result::Result<(ExitSucceed, Vec, usize), ExitError> { - let cost = ensure_linear_cost(target_gas, input.len(), 15, 3)?; - const BLAKE2_F_ARG_LEN: usize = 213; - - if input.len() != BLAKE2_F_ARG_LEN { - return Err(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_le_bytes(rounds_buf); - - let mut h_buf: [u8; 64] = [0; 64]; - h_buf.copy_from_slice(&input[4..48]); - 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).into(); - 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).into(); - 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(ExitError::Other("incorrect final block indicator flag".into())) - }; - - crate::eip_152::compress(&mut h, m, [t_0.into(), t_1.into()], 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((ExitSucceed::Returned, output_buf.to_vec(), cost)) - } -} diff --git a/frame/evm/src/runner/builtin.rs b/frame/evm/src/runner/builtin.rs index ea06f5b4c..e90e1ef84 100644 --- a/frame/evm/src/runner/builtin.rs +++ b/frame/evm/src/runner/builtin.rs @@ -36,7 +36,7 @@ use evm_runtime::{Config as EvmConfig, Handler as HandlerT}; use evm_gasometer::{self as gasometer, Gasometer}; use crate::{ Config, Vicinity, Module, Event, Log, AccountCodes, AccountStorages, AddressMapping, - Runner as RunnerT, Error, CallInfo, CreateInfo, FeeCalculator, precompiles::Precompiles, + Runner as RunnerT, Error, CallInfo, CreateInfo, FeeCalculator, PrecompileSet, }; #[derive(Default)] diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index 8458ea712..e270693b3 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -27,9 +27,11 @@ use fp_evm::{ExecutionInfo, CallInfo, CreateInfo, Account, Log, Vicinity}; use evm::ExitReason; use evm::backend::{Backend as BackendT, ApplyBackend, Apply}; use evm::executor::StackExecutor; -use crate::{Config, AccountStorages, FeeCalculator, AccountCodes, Module, Event, Error, AddressMapping}; +use crate::{ + Config, AccountStorages, FeeCalculator, AccountCodes, Module, Event, + Error, AddressMapping, PrecompileSet, +}; use crate::runner::Runner as RunnerT; -use crate::precompiles::Precompiles; #[derive(Default)] pub struct Runner { diff --git a/primitives/evm/Cargo.toml b/primitives/evm/Cargo.toml index abd3c692d..7f63c76a4 100644 --- a/primitives/evm/Cargo.toml +++ b/primitives/evm/Cargo.toml @@ -19,6 +19,7 @@ sp-std = { version = "2.0.0", git = "https://github.com/paritytech/substrate.git serde = { version = "1.0.101", optional = true, features = ["derive"] } codec = { package = "parity-scale-codec", version = "1.3.4", default-features = false } evm = { version = "0.19.0", default-features = false, features = ["with-codec"] } +impl-trait-for-tuples = "0.1" [features] default = ["std"] diff --git a/primitives/evm/src/lib.rs b/primitives/evm/src/lib.rs index 201f247e5..f21ea308c 100644 --- a/primitives/evm/src/lib.rs +++ b/primitives/evm/src/lib.rs @@ -17,6 +17,8 @@ #![cfg_attr(not(feature = "std"), no_std)] +mod precompile; + use codec::{Encode, Decode}; #[cfg(feature = "std")] use serde::{Serialize, Deserialize}; @@ -25,6 +27,7 @@ use sp_core::{U256, H160}; use evm::ExitReason; pub use evm::backend::{Basic as Account, Log}; +pub use precompile::{Precompile, PrecompileSet, LinearCostPrecompile}; #[derive(Clone, Eq, PartialEq, Encode, Decode, Default)] #[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] diff --git a/primitives/evm/src/precompile.rs b/primitives/evm/src/precompile.rs new file mode 100644 index 000000000..a3ef719a4 --- /dev/null +++ b/primitives/evm/src/precompile.rs @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2020 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use sp_std::vec::Vec; +use sp_core::H160; +use impl_trait_for_tuples::impl_for_tuples; +use evm::{ExitSucceed, ExitError}; + +/// Custom precompiles to be used by EVM engine. +pub trait PrecompileSet { + /// Try to execute the code address as precompile. If the code address is not + /// a precompile or the precompile is not yet available, return `None`. + /// Otherwise, calculate the amount of gas needed with given `input` and + /// `target_gas`. Return `Some(Ok(status, output, gas_used))` if the execution + /// is successful. Otherwise return `Some(Err(_))`. + fn execute( + address: H160, + input: &[u8], + target_gas: Option, + ) -> Option, usize), ExitError>>; +} + +/// 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, + ) -> core::result::Result<(ExitSucceed, Vec, usize), ExitError>; +} + +#[impl_for_tuples(16)] +#[tuple_types_no_default_trait_bound] +impl PrecompileSet for Tuple { + for_tuples!( where #( Tuple: Precompile )* ); + + fn execute( + address: H160, + input: &[u8], + target_gas: Option, + ) -> Option, usize), ExitError>> { + let mut index = 0; + + for_tuples!( #( + index += 1; + if address == H160::from_low_u64_be(index) { + return Some(Tuple::execute(input, target_gas)) + } + )* ); + + None + } +} + +pub trait LinearCostPrecompile { + const BASE: usize; + const WORD: usize; + + fn execute( + input: &[u8], + cost: usize, + ) -> core::result::Result<(ExitSucceed, Vec), ExitError>; +} + +impl Precompile for T { + fn execute( + input: &[u8], + target_gas: Option, + ) -> core::result::Result<(ExitSucceed, Vec, usize), ExitError> { + let cost = ensure_linear_cost(target_gas, input.len(), T::BASE, T::WORD)?; + + let (succeed, out) = T::execute(input, cost)?; + Ok((succeed, out, cost)) + } +} + +/// 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) +} diff --git a/template/runtime/Cargo.toml b/template/runtime/Cargo.toml index 1ee37ff20..71da0315b 100644 --- a/template/runtime/Cargo.toml +++ b/template/runtime/Cargo.toml @@ -21,6 +21,7 @@ frame-system-rpc-runtime-api = { version = "2.0.0-dev", default-features = false pallet-ethereum = { version = "0.1.0", default-features = false, path = "../../frame/ethereum" } pallet-evm = { version = "2.0.0-dev", default-features = false, path = "../../frame/evm" } +pallet-evm-precompile-simple = { version = "2.0.0-dev", default-features = false, path = "../../frame/evm/precompile/simple" } pallet-aura = { version = "2.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "frontier" } pallet-balances = { version = "2.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "frontier" } pallet-grandpa = { version = "2.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "frontier" } @@ -62,6 +63,7 @@ std = [ "pallet-ethereum/std", "pallet-evm/std", + "pallet-evm-precompile-simple/std", "pallet-aura/std", "pallet-balances/std", "pallet-grandpa/std", diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index bc26a4b0c..81020691b 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -292,10 +292,10 @@ impl pallet_evm::Config for Runtime { type Event = Event; type Runner = pallet_evm::runner::stack::Runner; type Precompiles = ( - pallet_evm::precompiles::ECRecover, - pallet_evm::precompiles::Sha256, - pallet_evm::precompiles::Ripemd160, - pallet_evm::precompiles::Identity, + pallet_evm_precompile_simple::ECRecover, + pallet_evm_precompile_simple::Sha256, + pallet_evm_precompile_simple::Ripemd160, + pallet_evm_precompile_simple::Identity, ); type ChainId = ChainId; }