diff --git a/Cargo.lock b/Cargo.lock index 990599fe571..89e1f4268ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -757,9 +757,9 @@ dependencies = [ [[package]] name = "c2-chacha" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb6b83fa00a7c53f420893670940c8fdfaa89f9dd9adb52062cca39482a31ab6" +checksum = "7e6002dbb7c65a76e516625443949a8b7bb0d0845fe6a3dc39e2dd7ae39dcb9c" dependencies = [ "cipher", "ppv-lite86", @@ -3178,6 +3178,7 @@ dependencies = [ "curve25519-dalek", "derive_more", "ed25519-dalek", + "ethereum-types 0.11.0", "hex-literal", "lazy_static", "libc", @@ -3636,10 +3637,12 @@ dependencies = [ "borsh", "bs58", "byteorder", + "near-crypto", "near-primitives", "near-primitives-core", "near-runtime-utils", "near-vm-errors", + "ripemd160", "serde", "serde_json", "sha2 0.9.3", @@ -4884,6 +4887,7 @@ dependencies = [ "rand 0.7.3", "rand_xorshift 0.2.0", "rayon", + "ripemd160", "rocksdb", "serde_json", "state-viewer", diff --git a/core/crypto/Cargo.toml b/core/crypto/Cargo.toml index 924c9089dce..471ad4e6fce 100644 --- a/core/crypto/Cargo.toml +++ b/core/crypto/Cargo.toml @@ -13,6 +13,7 @@ c2-chacha = "0.3" curve25519-dalek = "3" derive_more = "0.99.9" ed25519-dalek = "1" +ethereum-types = "0.11.0" lazy_static = "1.4" libc = "0.2" parity-secp256k1 = "0.7" diff --git a/core/crypto/src/lib.rs b/core/crypto/src/lib.rs index e714ffd5fbd..88ad67e9561 100644 --- a/core/crypto/src/lib.rs +++ b/core/crypto/src/lib.rs @@ -1,7 +1,8 @@ pub use errors::{ParseKeyError, ParseKeyTypeError, ParseSignatureError}; pub use key_file::KeyFile; pub use signature::{ - ED25519PublicKey, KeyType, PublicKey, Secp256K1PublicKey, SecretKey, Signature, + ED25519PublicKey, KeyType, PublicKey, Secp256K1PublicKey, Secp256K1Signature, SecretKey, + Signature, }; pub use signer::{EmptySigner, InMemorySigner, Signer}; diff --git a/core/crypto/src/signature.rs b/core/crypto/src/signature.rs index e71f0614ff6..706490a1024 100644 --- a/core/crypto/src/signature.rs +++ b/core/crypto/src/signature.rs @@ -7,8 +7,10 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use ed25519_dalek::ed25519::signature::{Signature as _, Signer as _, Verifier as _}; +use ethereum_types::U256; use lazy_static::lazy_static; use rand_core::OsRng; +use secp256k1::Message; use serde::{Deserialize, Serialize}; lazy_static! { @@ -553,9 +555,64 @@ impl<'de> serde::Deserialize<'de> for SecretKey { } } +const SECP256K1_N: U256 = + U256([0xbfd25e8cd0364141, 0xbaaedce6af48a03b, 0xfffffffffffffffe, 0xffffffffffffffff]); + +// Half of SECP256K1_N. +const SECP256K1_N_HALF: U256 = + U256([0xdfe92f46681b20a0, 0x5d576e7357a4501d, 0xffffffffffffffff, 0x7fffffffffffffff]); + #[derive(Clone)] pub struct Secp256K1Signature([u8; 65]); +impl Secp256K1Signature { + pub fn check_signature_values(&self, reject_upper: bool) -> bool { + let mut r_bytes = [0u8; 32]; + r_bytes.copy_from_slice(&self.0[0..32]); + let r = U256::from(r_bytes); + + let mut s_bytes = [0u8; 32]; + s_bytes.copy_from_slice(&self.0[32..64]); + let s = U256::from(s_bytes); + + let s_check = if reject_upper { + // Reject upper range of s values (ECDSA malleability) + SECP256K1_N_HALF + U256::one() + } else { + SECP256K1_N + }; + + r < SECP256K1_N && s < s_check + } + + pub fn recover( + &self, + msg: [u8; 32], + ) -> Result { + let recoverable_sig = secp256k1::RecoverableSignature::from_compact( + &SECP256K1, + &self.0[0..64], + secp256k1::RecoveryId::from_i32(i32::from(self.0[64])).unwrap(), + ) + .map_err(|err| crate::errors::ParseSignatureError::InvalidData { + error_message: err.to_string(), + })?; + let msg = Message::from(msg); + + let res = SECP256K1 + .recover(&msg, &recoverable_sig) + .map_err(|err| crate::errors::ParseSignatureError::InvalidData { + error_message: err.to_string(), + })? + .serialize_vec(&SECP256K1, false); + + // Can not fail + let pk = Secp256K1PublicKey::try_from(&res[1..65]).unwrap(); + + Ok(pk) + } +} + impl From<[u8; 65]> for Secp256K1Signature { fn from(data: [u8; 65]) -> Self { Self(data) diff --git a/core/primitives-core/Cargo.toml b/core/primitives-core/Cargo.toml index 8f18572d4d7..1694b3d0e35 100644 --- a/core/primitives-core/Cargo.toml +++ b/core/primitives-core/Cargo.toml @@ -24,7 +24,7 @@ lazy_static = "1.4" [features] default = [] -protocol_feature_add_account_versions = [] protocol_feature_evm = [] +protocol_feature_add_account_versions = [] protocol_feature_alt_bn128 = [] protocol_feature_tx_size_limit = [] diff --git a/core/primitives-core/src/config.rs b/core/primitives-core/src/config.rs index 58136b1d244..396b0e8d46e 100644 --- a/core/primitives-core/src/config.rs +++ b/core/primitives-core/src/config.rs @@ -223,6 +223,14 @@ pub struct ExtCostsConfig { /// Cost of getting sha256 per byte pub keccak512_byte: Gas, + /// Cost of getting ripemd160 base + pub ripemd160_base: Gas, + /// Cost of getting ripemd160 per message block + pub ripemd160_block: Gas, + + /// Cost of calling ecrecover + pub ecrecover_base: Gas, + /// Cost for calling logging. pub log_base: Gas, /// Cost for logging per byte @@ -353,6 +361,9 @@ impl Default for ExtCostsConfig { keccak256_byte: SAFETY_MULTIPLIER * 7157035, keccak512_base: SAFETY_MULTIPLIER * 1937129412, keccak512_byte: SAFETY_MULTIPLIER * 12216567, + ripemd160_base: SAFETY_MULTIPLIER * 15136567500, // 10 x sha256_base + ripemd160_block: SAFETY_MULTIPLIER * 80391170 * 8, // 10 x sha256_byte x 8 bytes + ecrecover_base: SAFETY_MULTIPLIER * 75682837500, // 50 x sha256_base log_base: SAFETY_MULTIPLIER * 1181104350, log_byte: SAFETY_MULTIPLIER * 4399597, storage_write_base: SAFETY_MULTIPLIER * 21398912000, @@ -423,6 +434,9 @@ impl ExtCostsConfig { keccak256_byte: 0, keccak512_base: 0, keccak512_byte: 0, + ripemd160_base: 0, + ripemd160_block: 0, + ecrecover_base: 0, log_base: 0, log_byte: 0, storage_write_base: 0, @@ -494,6 +508,9 @@ pub enum ExtCosts { keccak256_byte, keccak512_base, keccak512_byte, + ripemd160_base, + ripemd160_block, + ecrecover_base, log_base, log_byte, storage_write_base, @@ -618,6 +635,9 @@ impl ExtCosts { keccak256_byte => config.keccak256_byte, keccak512_base => config.keccak512_base, keccak512_byte => config.keccak512_byte, + ripemd160_base => config.ripemd160_base, + ripemd160_block => config.ripemd160_block, + ecrecover_base => config.ecrecover_base, log_base => config.log_base, log_byte => config.log_byte, storage_write_base => config.storage_write_base, @@ -692,6 +712,9 @@ impl ExtCosts { "keccak256_byte", "keccak512_base", "keccak512_byte", + "ripemd160_base", + "ripemd160_block", + "ecrecover_base", "log_base", "log_byte", "storage_write_base", diff --git a/core/primitives/Cargo.toml b/core/primitives/Cargo.toml index c6bff6cbf26..9011a6b22a7 100644 --- a/core/primitives/Cargo.toml +++ b/core/primitives/Cargo.toml @@ -50,7 +50,8 @@ protocol_feature_allow_create_account_on_delete = [] protocol_feature_fix_storage_usage = [] protocol_feature_restore_receipts_after_fix = [] protocol_feature_cap_max_gas_price = [] -nightly_protocol_features = ["nightly_protocol", "protocol_feature_evm", "protocol_feature_block_header_v3", "protocol_feature_alt_bn128", "protocol_feature_add_account_versions", "protocol_feature_tx_size_limit", "protocol_feature_allow_create_account_on_delete", "protocol_feature_fix_storage_usage", "protocol_feature_restore_receipts_after_fix", "protocol_feature_cap_max_gas_price"] +protocol_feature_math_extension = [] +nightly_protocol_features = ["nightly_protocol", "protocol_feature_evm", "protocol_feature_block_header_v3", "protocol_feature_alt_bn128", "protocol_feature_add_account_versions", "protocol_feature_tx_size_limit", "protocol_feature_allow_create_account_on_delete", "protocol_feature_fix_storage_usage", "protocol_feature_restore_receipts_after_fix", "protocol_feature_cap_max_gas_price", "protocol_feature_math_extension"] nightly_protocol = [] [dev-dependencies] diff --git a/core/primitives/src/version.rs b/core/primitives/src/version.rs index 0d964d86112..6fc707e96ea 100644 --- a/core/primitives/src/version.rs +++ b/core/primitives/src/version.rs @@ -107,6 +107,8 @@ pub enum ProtocolFeature { RestoreReceiptsAfterFix, #[cfg(feature = "protocol_feature_cap_max_gas_price")] CapMaxGasPrice, + #[cfg(feature = "protocol_feature_math_extension")] + MathExtension, } /// Current latest stable version of the protocol. @@ -117,7 +119,7 @@ pub const PROTOCOL_VERSION: ProtocolVersion = 45; /// Current latest nightly version of the protocol. #[cfg(feature = "nightly_protocol")] -pub const PROTOCOL_VERSION: ProtocolVersion = 113; +pub const PROTOCOL_VERSION: ProtocolVersion = 114; impl ProtocolFeature { pub const fn protocol_version(self) -> ProtocolVersion { @@ -149,6 +151,8 @@ impl ProtocolFeature { ProtocolFeature::RestoreReceiptsAfterFix => 112, #[cfg(feature = "protocol_feature_cap_max_gas_price")] ProtocolFeature::CapMaxGasPrice => 113, + #[cfg(feature = "protocol_feature_math_extension")] + ProtocolFeature::MathExtension => 114, } } } diff --git a/nearcore/Cargo.toml b/nearcore/Cargo.toml index 5c113300e56..a831232cb90 100644 --- a/nearcore/Cargo.toml +++ b/nearcore/Cargo.toml @@ -69,10 +69,14 @@ protocol_feature_add_account_versions = ["near-primitives/protocol_feature_add_a protocol_feature_tx_size_limit = ["near-primitives/protocol_feature_tx_size_limit", "node-runtime/protocol_feature_tx_size_limit"] protocol_feature_allow_create_account_on_delete = ["node-runtime/protocol_feature_allow_create_account_on_delete"] protocol_feature_fix_storage_usage = ["near-primitives/protocol_feature_fix_storage_usage", "node-runtime/protocol_feature_fix_storage_usage"] -nightly_protocol_features = ["nightly_protocol", "near-primitives/nightly_protocol_features", "near-client/nightly_protocol_features", "near-epoch-manager/nightly_protocol_features", "near-store/nightly_protocol_features", "protocol_feature_evm", "protocol_feature_block_header_v3", "protocol_feature_alt_bn128", "protocol_feature_add_account_versions", "protocol_feature_tx_size_limit", "protocol_feature_allow_create_account_on_delete", "protocol_feature_fix_storage_usage", "protocol_feature_restore_receipts_after_fix", "protocol_feature_cap_max_gas_price"] +nightly_protocol_features = ["nightly_protocol", "near-primitives/nightly_protocol_features", "near-client/nightly_protocol_features", "near-epoch-manager/nightly_protocol_features", "near-store/nightly_protocol_features", "protocol_feature_evm", "protocol_feature_block_header_v3", "protocol_feature_alt_bn128", "protocol_feature_add_account_versions", "protocol_feature_tx_size_limit", "protocol_feature_allow_create_account_on_delete", "protocol_feature_fix_storage_usage", "protocol_feature_restore_receipts_after_fix", "protocol_feature_cap_max_gas_price", "protocol_feature_math_extension"] nightly_protocol = ["near-primitives/nightly_protocol", "near-jsonrpc/nightly_protocol"] protocol_feature_restore_receipts_after_fix = ["near-primitives/protocol_feature_restore_receipts_after_fix", "near-chain/protocol_feature_restore_receipts_after_fix", "node-runtime/protocol_feature_restore_receipts_after_fix"] protocol_feature_cap_max_gas_price = ["near-primitives/protocol_feature_cap_max_gas_price", "near-chain/protocol_feature_cap_max_gas_price"] +protocol_feature_math_extension = [ + "near-primitives/protocol_feature_math_extension", + "node-runtime/protocol_feature_math_extension" +] # enable this to build neard with wasmer 1.0 runner # now if none of wasmer0_default, wasmer1_default or wasmtime_default is enabled, wasmer0 would be default diff --git a/neard/Cargo.toml b/neard/Cargo.toml index 024f3c9fb38..9c7ac8a36ab 100644 --- a/neard/Cargo.toml +++ b/neard/Cargo.toml @@ -51,6 +51,7 @@ nightly_protocol_features = ["nearcore/nightly_protocol_features"] nightly_protocol = ["nearcore/nightly_protocol"] protocol_feature_restore_receipts_after_fix = ["nearcore/protocol_feature_restore_receipts_after_fix"] protocol_feature_cap_max_gas_price = ["nearcore/protocol_feature_cap_max_gas_price"] +protocol_feature_math_extension = ["nearcore/protocol_feature_math_extension"] sandbox = ["nearcore/sandbox"] diff --git a/runtime/near-test-contracts/res/test_contract_ts.wasm b/runtime/near-test-contracts/res/test_contract_ts.wasm old mode 100755 new mode 100644 diff --git a/runtime/near-vm-errors/Cargo.toml b/runtime/near-vm-errors/Cargo.toml index e186a7830da..5c0249372ee 100644 --- a/runtime/near-vm-errors/Cargo.toml +++ b/runtime/near-vm-errors/Cargo.toml @@ -22,6 +22,7 @@ near-rpc-error-macro = { path = "../../tools/rpctypegen/macro", version = "0.1.0 [features] dump_errors_schema = ["near-rpc-error-macro/dump_errors_schema"] +protocol_feature_math_extension = [] protocol_feature_alt_bn128 = [] [package.metadata.workspaces] diff --git a/runtime/near-vm-errors/src/lib.rs b/runtime/near-vm-errors/src/lib.rs index 9311eb01030..8bfbf37b216 100644 --- a/runtime/near-vm-errors/src/lib.rs +++ b/runtime/near-vm-errors/src/lib.rs @@ -212,6 +212,9 @@ pub enum HostError { /// Serialization error for alt_bn128 functions #[cfg(feature = "protocol_feature_alt_bn128")] AltBn128SerializationError { msg: String }, + /// General errors for ECDSA recover. + #[cfg(feature = "protocol_feature_math_extension")] + ECRecoverError { msg: String }, } /// Errors specifically from native EVM. @@ -498,6 +501,8 @@ impl std::fmt::Display for HostError { AltBn128DeserializationError { msg } => write!(f, "AltBn128 deserialization error: {}", msg), #[cfg(feature = "protocol_feature_alt_bn128")] AltBn128SerializationError { msg } => write!(f, "AltBn128 serialization error: {}", msg), + #[cfg(feature = "protocol_feature_math_extension")] + ECRecoverError { msg } => write!(f, "ECDSA recover error: {}", msg), } } } diff --git a/runtime/near-vm-logic/Cargo.toml b/runtime/near-vm-logic/Cargo.toml index f504b28ef8f..853dae947d5 100644 --- a/runtime/near-vm-logic/Cargo.toml +++ b/runtime/near-vm-logic/Cargo.toml @@ -17,10 +17,12 @@ base64 = "0.13" borsh = "0.8.1" bs58 = "0.4" byteorder = "1.2" +ripemd160 = "0.9.0" serde = { version = "1", features = ["derive"] } sha2 = ">=0.8,<0.10" sha3 = ">=0.8,<0.10" +near-crypto = { path = "../../core/crypto" } near-primitives = { path = "../../core/primitives" } near-primitives-core = { path = "../../core/primitives-core", version = "0.1.0" } near-vm-errors = { path = "../near-vm-errors", version = "3.0.0" } @@ -36,6 +38,7 @@ default = [] protocol_feature_evm = ["near-primitives-core/protocol_feature_evm"] protocol_feature_alt_bn128 = ["bn", "near-primitives-core/protocol_feature_alt_bn128", "near-vm-errors/protocol_feature_alt_bn128"] protocol_feature_allow_create_account_on_delete = [] +protocol_feature_math_extension = ["near-primitives/protocol_feature_math_extension", "near-vm-errors/protocol_feature_math_extension"] # Use this feature to enable counting of fees and costs applied. costs_counting = [] diff --git a/runtime/near-vm-logic/src/gas_counter.rs b/runtime/near-vm-logic/src/gas_counter.rs index 4af6a225efd..0fb9e9a992e 100644 --- a/runtime/near-vm-logic/src/gas_counter.rs +++ b/runtime/near-vm-logic/src/gas_counter.rs @@ -144,18 +144,18 @@ impl GasCounter { self.deduct_gas(value, value) } - /// A helper function to pay per byte gas - pub fn pay_per_byte(&mut self, cost: ExtCosts, num_bytes: u64) -> Result<()> { - let use_gas = num_bytes + /// A helper function to pay a multiple of a cost. + pub fn pay_per(&mut self, cost: ExtCosts, num: u64) -> Result<()> { + let use_gas = num .checked_mul(cost.value(&self.ext_costs_config)) .ok_or(HostError::IntegerOverflow)?; - self.inc_ext_costs_counter(cost, num_bytes); + self.inc_ext_costs_counter(cost, num); self.update_profile_host(cost, use_gas); self.deduct_gas(use_gas, use_gas) } - /// A helper function to pay base cost gas + /// A helper function to pay base cost gas. pub fn pay_base(&mut self, cost: ExtCosts) -> Result<()> { let base_fee = cost.value(&self.ext_costs_config); self.inc_ext_costs_counter(cost, 1); diff --git a/runtime/near-vm-logic/src/lib.rs b/runtime/near-vm-logic/src/lib.rs index 33d11b35798..ec61ee93157 100644 --- a/runtime/near-vm-logic/src/lib.rs +++ b/runtime/near-vm-logic/src/lib.rs @@ -19,3 +19,7 @@ pub use near_vm_errors::{HostError, VMLogicError}; pub use types::ReturnData; pub use gas_counter::with_ext_cost_counter; +#[cfg(feature = "protocol_feature_math_extension")] +pub use logic::ecrecover; +#[cfg(feature = "protocol_feature_math_extension")] +pub use logic::ripemd160; diff --git a/runtime/near-vm-logic/src/logic.rs b/runtime/near-vm-logic/src/logic.rs index d80b64719c8..1eed63c86ed 100644 --- a/runtime/near-vm-logic/src/logic.rs +++ b/runtime/near-vm-logic/src/logic.rs @@ -5,6 +5,10 @@ use crate::types::{PromiseIndex, PromiseResult, ReceiptIndex, ReturnData}; use crate::utils::split_method_names; use crate::ValuePtr; use byteorder::ByteOrder; +#[cfg(feature = "protocol_feature_math_extension")] +use near_crypto::Secp256K1PublicKey; +#[cfg(feature = "protocol_feature_math_extension")] +use near_crypto::Secp256K1Signature; use near_primitives::checked_feature; use near_primitives::version::is_implicit_account_creation_enabled; use near_primitives_core::config::ExtCosts::*; @@ -188,7 +192,7 @@ impl<'a> VMLogic<'a> { fn memory_get_into(&mut self, offset: u64, buf: &mut [u8]) -> Result<()> { self.gas_counter.pay_base(read_memory_base)?; - self.gas_counter.pay_per_byte(read_memory_byte, buf.len() as _)?; + self.gas_counter.pay_per(read_memory_byte, buf.len() as _)?; self.try_fit_mem(offset, buf.len() as _)?; self.memory.read_memory(offset, buf); Ok(()) @@ -196,7 +200,7 @@ impl<'a> VMLogic<'a> { fn memory_get_vec(&mut self, offset: u64, len: u64) -> Result> { self.gas_counter.pay_base(read_memory_base)?; - self.gas_counter.pay_per_byte(read_memory_byte, len)?; + self.gas_counter.pay_per(read_memory_byte, len)?; self.try_fit_mem(offset, len)?; let mut buf = vec![0; len as usize]; self.memory.read_memory(offset, &mut buf); @@ -229,7 +233,7 @@ impl<'a> VMLogic<'a> { fn memory_set_slice(&mut self, offset: u64, buf: &[u8]) -> Result<()> { self.gas_counter.pay_base(write_memory_base)?; - self.gas_counter.pay_per_byte(write_memory_byte, buf.len() as _)?; + self.gas_counter.pay_per(write_memory_byte, buf.len() as _)?; self.try_fit_mem(offset, buf.len() as _)?; self.memory.write_memory(offset, buf); Ok(()) @@ -244,7 +248,7 @@ impl<'a> VMLogic<'a> { fn internal_read_register(&mut self, register_id: u64) -> Result> { if let Some(data) = self.registers.get(®ister_id) { self.gas_counter.pay_base(read_register_base)?; - self.gas_counter.pay_per_byte(read_register_byte, data.len() as _)?; + self.gas_counter.pay_per(read_register_byte, data.len() as _)?; Ok(data.clone()) } else { Err(HostError::InvalidRegisterId { register_id }.into()) @@ -253,7 +257,7 @@ impl<'a> VMLogic<'a> { fn internal_write_register(&mut self, register_id: u64, data: Vec) -> Result<()> { self.gas_counter.pay_base(write_register_base)?; - self.gas_counter.pay_per_byte(write_register_byte, data.len() as u64)?; + self.gas_counter.pay_per(write_register_byte, data.len() as u64)?; if data.len() as u64 > self.config.limit_config.max_register_size || self.registers.len() as u64 >= self.config.limit_config.max_number_registers { @@ -390,7 +394,7 @@ impl<'a> VMLogic<'a> { buf.push(el); } } - self.gas_counter.pay_per_byte(utf8_decoding_byte, buf.len() as _)?; + self.gas_counter.pay_per(utf8_decoding_byte, buf.len() as _)?; String::from_utf8(buf).map_err(|_| HostError::BadUTF8.into()) } @@ -453,7 +457,7 @@ impl<'a> VMLogic<'a> { } } self.gas_counter - .pay_per_byte(utf16_decoding_byte, u16_buffer.len() as u64 * size_of::() as u64)?; + .pay_per(utf16_decoding_byte, u16_buffer.len() as u64 * size_of::() as u64)?; String::from_utf16(&u16_buffer).map_err(|_| HostError::BadUTF16.into()) } @@ -809,14 +813,14 @@ impl<'a> VMLogic<'a> { self.gas_counter.pay_base(alt_bn128_g1_multiexp_base)?; let value_buf = self.get_vec_from_memory_or_register(value_ptr, value_len)?; let len = value_buf.len() as u64; - self.gas_counter.pay_per_byte(alt_bn128_g1_multiexp_byte, len)?; + self.gas_counter.pay_per(alt_bn128_g1_multiexp_byte, len)?; let discount = (alt_bn128_g1_multiexp_base as u64 + alt_bn128_g1_multiexp_byte as u64 * len) / alt_bn128_g1_multiexp_sublinear as u64; let sublinear_complexity = crate::alt_bn128::alt_bn128_g1_multiexp_sublinear_complexity_estimate(len, discount); - self.gas_counter.pay_per_byte(alt_bn128_g1_multiexp_sublinear, sublinear_complexity)?; + self.gas_counter.pay_per(alt_bn128_g1_multiexp_sublinear, sublinear_complexity)?; let res = crate::alt_bn128::alt_bn128_g1_multiexp(&value_buf)?; @@ -845,7 +849,7 @@ impl<'a> VMLogic<'a> { ) -> Result<()> { self.gas_counter.pay_base(alt_bn128_g1_sum_base)?; let value_buf = self.get_vec_from_memory_or_register(value_ptr, value_len)?; - self.gas_counter.pay_per_byte(alt_bn128_g1_sum_byte, value_buf.len() as u64)?; + self.gas_counter.pay_per(alt_bn128_g1_sum_byte, value_buf.len() as u64)?; let res = crate::alt_bn128::alt_bn128_g1_sum(&value_buf)?; @@ -869,7 +873,7 @@ impl<'a> VMLogic<'a> { pub fn alt_bn128_pairing_check(&mut self, value_len: u64, value_ptr: u64) -> Result { self.gas_counter.pay_base(alt_bn128_pairing_check_base)?; let value_buf = self.get_vec_from_memory_or_register(value_ptr, value_len)?; - self.gas_counter.pay_per_byte(alt_bn128_pairing_check_byte, value_buf.len() as u64)?; + self.gas_counter.pay_per(alt_bn128_pairing_check_byte, value_buf.len() as u64)?; Ok(crate::alt_bn128::alt_bn128_pairing_check(&value_buf)? as u64) } @@ -901,7 +905,7 @@ impl<'a> VMLogic<'a> { pub fn sha256(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> { self.gas_counter.pay_base(sha256_base)?; let value = self.get_vec_from_memory_or_register(value_ptr, value_len)?; - self.gas_counter.pay_per_byte(sha256_byte, value.len() as u64)?; + self.gas_counter.pay_per(sha256_byte, value.len() as u64)?; use sha2::Digest; @@ -922,7 +926,7 @@ impl<'a> VMLogic<'a> { pub fn keccak256(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> { self.gas_counter.pay_base(keccak256_base)?; let value = self.get_vec_from_memory_or_register(value_ptr, value_len)?; - self.gas_counter.pay_per_byte(keccak256_byte, value.len() as u64)?; + self.gas_counter.pay_per(keccak256_byte, value.len() as u64)?; use sha3::Digest; @@ -943,7 +947,7 @@ impl<'a> VMLogic<'a> { pub fn keccak512(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> { self.gas_counter.pay_base(keccak512_base)?; let value = self.get_vec_from_memory_or_register(value_ptr, value_len)?; - self.gas_counter.pay_per_byte(keccak512_byte, value.len() as u64)?; + self.gas_counter.pay_per(keccak512_byte, value.len() as u64)?; use sha3::Digest; @@ -951,6 +955,118 @@ impl<'a> VMLogic<'a> { self.internal_write_register(register_id, value_hash.as_slice().to_vec()) } + /// Hashes the given value using RIPEMD-160 and returns it into `register_id`. + /// + /// # Errors + /// + /// If `value_len + value_ptr` points outside the memory or the registers use more memory than + /// the limit with `MemoryAccessViolation`. + /// + /// # Cost + /// + /// Where `message_blocks` is `(value_len + 9).div_ceil(64)`. + /// + /// `base + write_register_base + write_register_byte * num_bytes + ripemd160_base + ripemd160_block * message_blocks` + #[cfg(feature = "protocol_feature_math_extension")] + pub fn ripemd160(&mut self, value_len: u64, value_ptr: u64, register_id: u64) -> Result<()> { + self.gas_counter.pay_base(ripemd160_base)?; + let value = self.get_vec_from_memory_or_register(value_ptr, value_len)?; + + let message_blocks = value + .len() + .checked_add(8) + .ok_or(VMLogicError::HostError(HostError::IntegerOverflow))? + / 64 + + 1; + + self.gas_counter.pay_per(ripemd160_block, message_blocks as u64)?; + + let value_hash = ripemd160(&value); + self.internal_write_register(register_id, value_hash) + } + + /// Recovers an ECDSA signer address and returns it into `register_id`. + /// + /// Takes in an additional flag to check for malleability of the signature + /// which is generally only ideal for transactions. + /// + /// Returns a bool indicating success or failure as a `u64`. + /// + /// # Malleability Flags + /// + /// 0 - No extra checks. + /// 1 - Rejecting upper range. + /// + /// # Errors + /// + /// * If `hash_ptr`, `r_ptr`, or `s_ptr` point outside the memory or the registers use more + /// memory than the limit, then returns `MemoryAccessViolation`. + /// + /// # Cost + /// + /// `base + write_register_base + write_register_byte * 64 + ecrecover_base` + #[cfg(feature = "protocol_feature_math_extension")] + pub fn ecrecover( + &mut self, + hash_len: u64, + hash_ptr: u64, + sig_len: u64, + sig_ptr: u64, + v: u64, + malleability_flag: u64, + register_id: u64, + ) -> Result { + self.gas_counter.pay_base(ecrecover_base)?; + + let signature_data = { + let vec = self.get_vec_from_memory_or_register(sig_ptr, sig_len)?; + if vec.len() != 64 { + return Err(VMLogicError::HostError(HostError::ECRecoverError { + msg: format!( + "The length of the signature: {}, exceeds the limit of 64 bytes", + vec.len() + ), + })); + } + + let mut bytes = [0u8; 65]; + bytes[0..64].copy_from_slice(&vec); + + if v < 4 { + bytes[64] = v as u8; + bytes + } else { + return Err(VMLogicError::HostError(HostError::ECRecoverError { + msg: format!("V recovery byte 0 through 3 are valid but was provided {}", v), + })); + } + }; + + let hash = { + let vec = self.get_vec_from_memory_or_register(hash_ptr, hash_len)?; + if vec.len() != 32 { + return Err(VMLogicError::HostError(HostError::ECRecoverError { + msg: format!( + "The length of the hash: {}, exceeds the limit of 32 bytes", + vec.len() + ), + })); + } + + let mut bytes = [0u8; 32]; + bytes.copy_from_slice(&vec); + bytes + }; + + match ecrecover(signature_data, hash, malleability_flag) { + Some(key) => { + self.internal_write_register(register_id, key.as_ref().to_vec())?; + Ok(true as u64) + } + None => Ok(false as u64), + } + } + /// Called by gas metering injected into Wasm. Counts both towards `burnt_gas` and `used_gas`. /// /// # Errors @@ -1131,7 +1247,7 @@ impl<'a> VMLogic<'a> { ); } self.gas_counter.pay_base(promise_and_base)?; - self.gas_counter.pay_per_byte( + self.gas_counter.pay_per( promise_and_per_promise, promise_idx_count .checked_mul(size_of::() as u64) @@ -1985,7 +2101,7 @@ impl<'a> VMLogic<'a> { self.check_can_add_a_log_message()?; let message = self.get_utf8_string(len, ptr)?; self.gas_counter.pay_base(log_base)?; - self.gas_counter.pay_per_byte(log_byte, message.len() as u64)?; + self.gas_counter.pay_per(log_byte, message.len() as u64)?; self.checked_push_log(message) } @@ -2010,7 +2126,7 @@ impl<'a> VMLogic<'a> { let message = self.get_utf16_string(len, ptr)?; self.gas_counter.pay_base(log_base)?; // Let's not use `encode_utf16` for gas per byte here, since it's a lot of compute. - self.gas_counter.pay_per_byte(log_byte, message.len() as u64)?; + self.gas_counter.pay_per(log_byte, message.len() as u64)?; self.checked_push_log(message) } @@ -2045,7 +2161,7 @@ impl<'a> VMLogic<'a> { let message = format!("{}, filename: \"{}\" line: {} col: {}", msg, filename, line, col); self.gas_counter.pay_base(log_base)?; - self.gas_counter.pay_per_byte(log_byte, message.as_bytes().len() as u64)?; + self.gas_counter.pay_per(log_byte, message.as_bytes().len() as u64)?; self.checked_push_log(format!("ABORT: {}", message))?; Err(HostError::GuestPanic { panic_msg: message }.into()) @@ -2069,7 +2185,7 @@ impl<'a> VMLogic<'a> { fn read_and_parse_account_id(&mut self, ptr: u64, len: u64) -> Result { let buf = self.get_vec_from_memory_or_register(ptr, len)?; self.gas_counter.pay_base(utf8_decoding_base)?; - self.gas_counter.pay_per_byte(utf8_decoding_byte, buf.len() as u64)?; + self.gas_counter.pay_per(utf8_decoding_byte, buf.len() as u64)?; let account_id = AccountId::from_utf8(buf).map_err(|_| HostError::BadUTF8)?; Ok(account_id) } @@ -2126,14 +2242,14 @@ impl<'a> VMLogic<'a> { } .into()); } - self.gas_counter.pay_per_byte(storage_write_key_byte, key.len() as u64)?; - self.gas_counter.pay_per_byte(storage_write_value_byte, value.len() as u64)?; + self.gas_counter.pay_per(storage_write_key_byte, key.len() as u64)?; + self.gas_counter.pay_per(storage_write_value_byte, value.len() as u64)?; let nodes_before = self.ext.get_touched_nodes_count(); let evicted_ptr = self.ext.storage_get(&key)?; let evicted = Self::deref_value(&mut self.gas_counter, storage_write_evicted_byte, evicted_ptr)?; self.gas_counter - .pay_per_byte(touching_trie_node, self.ext.get_touched_nodes_count() - nodes_before)?; + .pay_per(touching_trie_node, self.ext.get_touched_nodes_count() - nodes_before)?; self.ext.storage_set(&key, &value)?; let storage_config = &self.fees_config.storage_usage_config; match evicted { @@ -2173,7 +2289,7 @@ impl<'a> VMLogic<'a> { ) -> Result>> { match value_ptr { Some(value_ptr) => { - gas_counter.pay_per_byte(cost_per_byte, value_ptr.len() as u64)?; + gas_counter.pay_per(cost_per_byte, value_ptr.len() as u64)?; value_ptr.deref().map(Some) } None => Ok(None), @@ -2208,11 +2324,11 @@ impl<'a> VMLogic<'a> { } .into()); } - self.gas_counter.pay_per_byte(storage_read_key_byte, key.len() as u64)?; + self.gas_counter.pay_per(storage_read_key_byte, key.len() as u64)?; let nodes_before = self.ext.get_touched_nodes_count(); let read = self.ext.storage_get(&key); self.gas_counter - .pay_per_byte(touching_trie_node, self.ext.get_touched_nodes_count() - nodes_before)?; + .pay_per(touching_trie_node, self.ext.get_touched_nodes_count() - nodes_before)?; let read = Self::deref_value(&mut self.gas_counter, storage_read_value_byte, read?)?; match read { Some(value) => { @@ -2258,7 +2374,7 @@ impl<'a> VMLogic<'a> { } .into()); } - self.gas_counter.pay_per_byte(storage_remove_key_byte, key.len() as u64)?; + self.gas_counter.pay_per(storage_remove_key_byte, key.len() as u64)?; let nodes_before = self.ext.get_touched_nodes_count(); let removed_ptr = self.ext.storage_get(&key)?; let removed = @@ -2266,7 +2382,7 @@ impl<'a> VMLogic<'a> { self.ext.storage_remove(&key)?; self.gas_counter - .pay_per_byte(touching_trie_node, self.ext.get_touched_nodes_count() - nodes_before)?; + .pay_per(touching_trie_node, self.ext.get_touched_nodes_count() - nodes_before)?; let storage_config = &self.fees_config.storage_usage_config; match removed { Some(value) => { @@ -2309,11 +2425,11 @@ impl<'a> VMLogic<'a> { } .into()); } - self.gas_counter.pay_per_byte(storage_has_key_byte, key.len() as u64)?; + self.gas_counter.pay_per(storage_has_key_byte, key.len() as u64)?; let nodes_before = self.ext.get_touched_nodes_count(); let res = self.ext.storage_has_key(&key); self.gas_counter - .pay_per_byte(touching_trie_node, self.ext.get_touched_nodes_count() - nodes_before)?; + .pay_per(touching_trie_node, self.ext.get_touched_nodes_count() - nodes_before)?; Ok(res? as u64) } @@ -2436,7 +2552,7 @@ impl<'a> VMLogic<'a> { // TODO: remove, as those costs are incorrectly computed, and we shall account it on deployment. pub fn add_contract_compile_fee(&mut self, code_len: u64) -> Result<()> { - self.gas_counter.pay_per_byte(contract_compile_bytes, code_len)?; + self.gas_counter.pay_per(contract_compile_bytes, code_len)?; self.gas_counter.pay_base(contract_compile_base) } } @@ -2466,3 +2582,22 @@ impl std::fmt::Debug for VMOutcome { ) } } + +#[cfg(feature = "protocol_feature_math_extension")] +pub fn ecrecover( + signature_data: [u8; 65], + hash_data: [u8; 32], + malleability_flag: u64, +) -> Option { + let signature = Secp256K1Signature::from(signature_data); + if !signature.check_signature_values(malleability_flag != 0) { + return None; + } + signature.recover(hash_data).map_or(None, |result| Some(result)) +} + +#[cfg(feature = "protocol_feature_math_extension")] +pub fn ripemd160(data: &[u8]) -> Vec { + use ripemd160::Digest; + ripemd160::Ripemd160::digest(data).as_slice().to_vec() +} diff --git a/runtime/near-vm-logic/tests/test_miscs.rs b/runtime/near-vm-logic/tests/test_miscs.rs index 0ce9fb87caf..aaead836e5f 100644 --- a/runtime/near-vm-logic/tests/test_miscs.rs +++ b/runtime/near-vm-logic/tests/test_miscs.rs @@ -530,6 +530,84 @@ fn test_keccak512() { }); } +#[cfg(feature = "protocol_feature_math_extension")] +#[test] +fn test_ripemd160() { + let mut logic_builder = VMLogicBuilder::default(); + let mut logic = logic_builder.build(get_context(vec![], false)); + + let data = b"tesdsst"; + logic.ripemd160(data.len() as _, data.as_ptr() as _, 0).unwrap(); + let res = &vec![0u8; 20]; + logic.read_register(0, res.as_ptr() as _).expect("OK"); + assert_eq!( + res, + &[21, 102, 156, 115, 232, 3, 58, 215, 35, 84, 129, 30, 143, 86, 212, 104, 70, 97, 14, 225,] + ); + let len = data.len() as u64; + assert_costs(map! { + ExtCosts::base: 1, + ExtCosts::read_memory_base: 1, + ExtCosts::read_memory_byte: len, + ExtCosts::write_memory_base: 1, + ExtCosts::write_memory_byte: 20, + ExtCosts::read_register_base: 1, + ExtCosts::read_register_byte: 20, + ExtCosts::write_register_base: 1, + ExtCosts::write_register_byte: 20, + ExtCosts::ripemd160_base: 1, + ExtCosts::ripemd160_block: 1, + }); +} + +#[cfg(feature = "protocol_feature_math_extension")] +#[test] +fn test_ecrecover() { + let mut logic_builder = VMLogicBuilder::default(); + let mut logic = logic_builder.build(get_context(vec![], false)); + + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/test/cryptography/ECDSA.test.js + let hash: [u8; 32] = [ + 0x7d, 0xba, 0xf5, 0x58, 0xb0, 0xa1, 0xa5, 0xdc, 0x7a, 0x67, 0x20, 0x21, 0x17, 0xab, 0x14, + 0x3c, 0x1d, 0x86, 0x05, 0xa9, 0x83, 0xe4, 0xa7, 0x43, 0xbc, 0x06, 0xfc, 0xc0, 0x31, 0x62, + 0xdc, 0x0d, + ]; + let signature: [u8; 64] = [ + 0x5d, 0x99, 0xb6, 0xf7, 0xf6, 0xd1, 0xf7, 0x3d, 0x1a, 0x26, 0x49, 0x7f, 0x2b, 0x1c, 0x89, + 0xb2, 0x4c, 0x09, 0x93, 0x91, 0x3f, 0x86, 0xe9, 0xa2, 0xd0, 0x2c, 0xd6, 0x98, 0x87, 0xd9, + 0xc9, 0x4f, 0x3c, 0x88, 0x03, 0x58, 0x57, 0x9d, 0x81, 0x1b, 0x21, 0xdd, 0x1b, 0x7f, 0xd9, + 0xbb, 0x01, 0xc1, 0xd8, 0x1d, 0x10, 0xe6, 0x9f, 0x03, 0x84, 0xe6, 0x75, 0xc3, 0x2b, 0x39, + 0x64, 0x3b, 0xe8, 0x92, + ]; + let signer: [u8; 64] = [ + 0xb3, 0x68, 0x70, 0xea, 0xab, 0x31, 0xcb, 0xeb, 0x1a, 0x5c, 0x07, 0x46, 0x7b, 0x42, 0x97, + 0x40, 0x7c, 0x62, 0x11, 0x7a, 0x31, 0x15, 0x47, 0xc4, 0x30, 0x5e, 0x14, 0x71, 0x52, 0x1b, + 0x53, 0x01, 0xc2, 0x59, 0x9d, 0x4e, 0xad, 0xdf, 0xd3, 0x84, 0x9d, 0xf9, 0xd5, 0x99, 0x38, + 0xfd, 0x8f, 0x16, 0x56, 0x47, 0x77, 0x32, 0x66, 0x80, 0x66, 0xff, 0xa1, 0x2e, 0xb3, 0x47, + 0xea, 0xb4, 0x7b, 0x9c, + ]; + + let b = logic.ecrecover(32, hash.as_ptr() as _, 64, signature.as_ptr() as _, 0, 0, 1).unwrap(); + assert_ne!(b, 0); + + let result = &vec![0u8; 64]; + logic.read_register(1, result.as_ptr() as _).expect("OK"); + + assert_eq!(result.to_vec(), signer); + assert_costs(map! { + ExtCosts::base: 1, + ExtCosts::read_memory_base: 2, + ExtCosts::read_memory_byte: 96, + ExtCosts::write_memory_base: 1, + ExtCosts::write_memory_byte: 64, + ExtCosts::read_register_base: 1, + ExtCosts::read_register_byte: 64, + ExtCosts::write_register_base: 1, + ExtCosts::write_register_byte: 64, + ExtCosts::ecrecover_base: 1, + }); +} + #[test] fn test_hash256_register() { let mut logic_builder = VMLogicBuilder::default(); diff --git a/runtime/near-vm-runner-standalone/Cargo.toml b/runtime/near-vm-runner-standalone/Cargo.toml index dfc18b8a5bf..b2a72fae1f5 100644 --- a/runtime/near-vm-runner-standalone/Cargo.toml +++ b/runtime/near-vm-runner-standalone/Cargo.toml @@ -39,8 +39,8 @@ near-test-contracts = { path = "../near-test-contracts" } [features] default = [] no_cache = ["near-vm-runner/no_cache"] -protocol_feature_alt_bn128 = ["near-primitives/protocol_feature_alt_bn128", "near-vm-logic/protocol_feature_alt_bn128", "near-vm-runner/protocol_feature_alt_bn128"] protocol_feature_evm = ["near-primitives/protocol_feature_evm"] +protocol_feature_alt_bn128 = ["near-vm-logic/protocol_feature_alt_bn128", "near-vm-runner/protocol_feature_alt_bn128"] protocol_feature_block_header_v3 = ["near-primitives/protocol_feature_block_header_v3"] protocol_feature_add_account_versions = ["near-primitives/protocol_feature_add_account_versions"] protocol_feature_tx_size_limit = ["near-primitives/protocol_feature_tx_size_limit"] diff --git a/runtime/near-vm-runner/Cargo.toml b/runtime/near-vm-runner/Cargo.toml index 2f64b58cd66..e64223dfd64 100644 --- a/runtime/near-vm-runner/Cargo.toml +++ b/runtime/near-vm-runner/Cargo.toml @@ -65,6 +65,10 @@ protocol_feature_alt_bn128 = [ "near-primitives/protocol_feature_alt_bn128", "near-vm-errors/protocol_feature_alt_bn128" ] +protocol_feature_math_extension = [ + "near-vm-logic/protocol_feature_math_extension", + "near-primitives/protocol_feature_math_extension", +] [package.metadata.cargo-udeps.ignore] # `no_cache` feature leads to an unused `cached` crate diff --git a/runtime/near-vm-runner/src/imports.rs b/runtime/near-vm-runner/src/imports.rs index 3325e402d4d..5fcdb3083e0 100644 --- a/runtime/near-vm-runner/src/imports.rs +++ b/runtime/near-vm-runner/src/imports.rs @@ -221,6 +221,8 @@ wrapped_imports! { sha256<[value_len: u64, value_ptr: u64, register_id: u64] -> []>, keccak256<[value_len: u64, value_ptr: u64, register_id: u64] -> []>, keccak512<[value_len: u64, value_ptr: u64, register_id: u64] -> []>, + #["protocol_feature_math_extension", MathExtension] ripemd160<[value_len: u64, value_ptr: u64, register_id: u64] -> []>, + #["protocol_feature_math_extension", MathExtension] ecrecover<[hash_len: u64, hash_ptr: u64, sign_len: u64, sig_ptr: u64, v: u64, malleability_flag: u64, register_id: u64] -> [u64]>, // ##################### // # Miscellaneous API # // ##################### diff --git a/runtime/runtime-params-estimator/Cargo.toml b/runtime/runtime-params-estimator/Cargo.toml index 3b7baa8b2ad..b907e283afc 100644 --- a/runtime/runtime-params-estimator/Cargo.toml +++ b/runtime/runtime-params-estimator/Cargo.toml @@ -46,6 +46,7 @@ once_cell = "1" num-traits = "0.2.12" libc = "0.2.81" wabt = "0.9" +ripemd160 = "0.9.0" [features] default = ["costs_counting"] @@ -56,7 +57,7 @@ no_cache = ["node-runtime/no_cache", "near-store/no_cache"] wasmtime = ["near-vm-runner/wasmtime_default"] lightbeam = ["wasmtime", "near-vm-runner/lightbeam"] nightly_protocol = ["near-primitives/nightly_protocol"] -nightly_protocol_features = ["protocol_feature_alt_bn128", "protocol_feature_evm"] +nightly_protocol_features = ["protocol_feature_alt_bn128", "protocol_feature_evm", "protocol_feature_math_extension"] protocol_feature_alt_bn128 = [ "near-vm-logic/protocol_feature_alt_bn128", "near-vm-runner/protocol_feature_alt_bn128", @@ -72,4 +73,11 @@ protocol_feature_evm = [ "near-primitives/protocol_feature_evm", "testlib/protocol_feature_evm" ] -sandbox = ["node-runtime/sandbox", "state-viewer/sandbox"] \ No newline at end of file +sandbox = ["node-runtime/sandbox", "state-viewer/sandbox"] +protocol_feature_math_extension = [ + "near-primitives/protocol_feature_math_extension", + "near-vm-logic/protocol_feature_math_extension", + "near-vm-runner/protocol_feature_math_extension", + "nearcore/protocol_feature_math_extension", + "node-runtime/protocol_feature_math_extension" +] \ No newline at end of file diff --git a/runtime/runtime-params-estimator/README.md b/runtime/runtime-params-estimator/README.md index 3eccebceba6..540f05fb57b 100644 --- a/runtime/runtime-params-estimator/README.md +++ b/runtime/runtime-params-estimator/README.md @@ -13,6 +13,6 @@ Use this tool to measure the running time of elementary runtime operations that cargo run --release --package runtime-params-estimator --features required --bin runtime-params-estimator -- --home /tmp/data --accounts-num 20000 --iters 1 --warmup-iters 1 --metric time ``` - With the given parameters above estimator will run relatively fast. We will be using different parameters to do the actual parameter estimation. Also note that the defualt metric is `icount`, instruction count, but requires using qemu to emulate the processor. So this example provides a way to get a quick way to test out the estimator based on time, but the instructions in [`emu-cost/README.md`](./emu-cost/README.md) should be followed to get the real data. + With the given parameters above estimator will run relatively fast. We will be using different parameters to do the actual parameter estimation. Also note that the default metric is `icount`, instruction count, but requires using QEMU to emulate the processor. So this example provides a way to get a quick way to test out the estimator based on time, but the instructions in [`emu-cost/README.md`](./emu-cost/README.md) should be followed to get the real data. Note, if you use the plotting functionality you would need to install [gnuplot](http://gnuplot.info/) to see the graphs. diff --git a/runtime/runtime-params-estimator/src/cases.rs b/runtime/runtime-params-estimator/src/cases.rs index e3fda011ec9..239623b92d5 100644 --- a/runtime/runtime-params-estimator/src/cases.rs +++ b/runtime/runtime-params-estimator/src/cases.rs @@ -194,6 +194,12 @@ pub enum Metric { keccak256_10kib_10k, keccak512_10b_10k, keccak512_10kib_10k, + #[cfg(feature = "protocol_feature_math_extension")] + ripemd160_10b_10k, + #[cfg(feature = "protocol_feature_math_extension")] + ripemd160_10kib_10k, + #[cfg(feature = "protocol_feature_math_extension")] + ecrecover_10k, #[cfg(feature = "protocol_feature_alt_bn128")] alt_bn128_g1_multiexp_1_1k, #[cfg(feature = "protocol_feature_alt_bn128")] @@ -555,6 +561,9 @@ pub fn run(mut config: Config, only_compile: bool) -> RuntimeConfig { keccak256_10kib_10k => keccak256_10kib_10k, keccak512_10b_10k => keccak512_10b_10k, keccak512_10kib_10k => keccak512_10kib_10k, + #["protocol_feature_math_extension"] ripemd160_10b_10k => ripemd160_10b_10k, + #["protocol_feature_math_extension"] ripemd160_10kib_10k => ripemd160_10kib_10k, + #["protocol_feature_math_extension"] ecrecover_10k => ecrecover_10k, #["protocol_feature_alt_bn128"] alt_bn128_g1_multiexp_1_1k => alt_bn128_g1_multiexp_1_1k, #["protocol_feature_alt_bn128"] alt_bn128_g1_multiexp_10_1k => alt_bn128_g1_multiexp_10_1k, #["protocol_feature_alt_bn128"] alt_bn128_g1_sum_1_1k => alt_bn128_g1_sum_1_1k, @@ -608,7 +617,7 @@ pub fn run(mut config: Config, only_compile: bool) -> RuntimeConfig { // m.plot(PathBuf::from(&config.state_dump_path).as_path()); } -fn ratio_to_gas(gas_metric: GasMetric, value: Ratio) -> u64 { +pub(crate) fn ratio_to_gas(gas_metric: GasMetric, value: Ratio) -> u64 { let divisor = match gas_metric { // We use factor of 8 to approximately match the price of SHA256 operation between // time-based and icount-based metric as measured on 3.2Ghz Core i5. @@ -737,6 +746,9 @@ fn get_ext_costs_config(measurement: &Measurements, config: &Config) -> ExtCosts keccak256_byte: measured_to_gas(metric, &measured, keccak256_byte), keccak512_base: measured_to_gas(metric, &measured, keccak512_base), keccak512_byte: measured_to_gas(metric, &measured, keccak512_byte), + ripemd160_base: measured_to_gas(metric, &measured, ripemd160_base), + ripemd160_block: measured_to_gas(metric, &measured, ripemd160_block), + ecrecover_base: measured_to_gas(metric, &measured, ecrecover_base), log_base: measured_to_gas(metric, &measured, log_base), log_byte: measured_to_gas(metric, &measured, log_byte), storage_write_base: measured_to_gas(metric, &measured, storage_write_base), diff --git a/runtime/runtime-params-estimator/src/ext_costs_generator.rs b/runtime/runtime-params-estimator/src/ext_costs_generator.rs index e3e353c237f..bb9ad9aeb2d 100644 --- a/runtime/runtime-params-estimator/src/ext_costs_generator.rs +++ b/runtime/runtime-params-estimator/src/ext_costs_generator.rs @@ -65,6 +65,14 @@ impl ExtCostsGenerator { self.extract(keccak512_10b_10k, keccak512_base); self.extract(keccak512_10kib_10k, keccak512_byte); + #[cfg(feature = "protocol_feature_math_extension")] + { + self.extract(ripemd160_10b_10k, ripemd160_base); + self.extract(ripemd160_10kib_10k, ripemd160_block); + + self.extract(ecrecover_10k, ecrecover_base); + } + #[cfg(feature = "protocol_feature_alt_bn128")] { self.extract(alt_bn128_g1_multiexp_1_1k, alt_bn128_g1_multiexp_base); diff --git a/runtime/runtime-params-estimator/src/lib.rs b/runtime/runtime-params-estimator/src/lib.rs index d9be15e2a58..1e9a03e8df3 100644 --- a/runtime/runtime-params-estimator/src/lib.rs +++ b/runtime/runtime-params-estimator/src/lib.rs @@ -1,4 +1,8 @@ -// Lists all cases that we want to measure. +use std::path::Path; + +use once_cell::sync::OnceCell; + +/// Lists all cases that we want to measure. pub mod cases; // Generates runtime fees from the measurements. pub mod runtime_fees_generator; @@ -12,12 +16,9 @@ pub mod stats; pub mod testbed; // Prepares transactions and feeds them to the testbed in batches. Performs the warm up, takes care // of nonces. +mod math_ops_cost; pub mod testbed_runners; -use std::path::Path; - -use once_cell::sync::OnceCell; - /// Lazily loads contract's code from a directory in the source tree. pub(crate) struct TestContract { path: &'static str, diff --git a/runtime/runtime-params-estimator/src/math_ops_cost.rs b/runtime/runtime-params-estimator/src/math_ops_cost.rs new file mode 100644 index 00000000000..e1da4444b50 --- /dev/null +++ b/runtime/runtime-params-estimator/src/math_ops_cost.rs @@ -0,0 +1,101 @@ +#[cfg(feature = "protocol_feature_math_extension")] +mod math_ops_cost { + use crate::cases::{ratio_to_gas, ratio_to_gas_signed}; + use crate::testbed_runners::{end_count, start_count, GasMetric}; + use crate::vm_estimator::least_squares_method; + use near_primitives::num_rational::Ratio; + use near_vm_logic::{ecrecover, ripemd160}; + + #[derive(Debug)] + pub struct EvmMathCosts { + ripemd160_base: i64, + ripemd160_per_byte: i64, + ecrecover_base: i64, + } + + #[used] + static mut SINK: i64 = 0; + + fn measure_operation i64>( + gas_metric: GasMetric, + count: usize, + op: F, + ) -> u64 { + let start = start_count(gas_metric); + let result = op(count); + let end = end_count(gas_metric, &start); + // Do this to ensure call to measurer isn't optimized away. + unsafe { + SINK = result; + } + end + } + + fn compute_ripemd160_cost(gas_metric: GasMetric) -> (i64, i64) { + let mut xs = vec![]; + let mut ys = vec![]; + for i in vec![3, 30, 300, 30000] { + xs.push(i as u64); + ys.push(measure_operation(gas_metric, i, |repeat| { + let value = vec![1u8; repeat]; + let digest = ripemd160(&value); + digest.as_slice()[0] as i64 + })); + } + + let (a, b, _err) = least_squares_method(&xs, &ys); + (ratio_to_gas_signed(gas_metric, a), ratio_to_gas_signed(gas_metric, b)) + } + + fn compute_ecrecover_base(gas_metric: GasMetric) -> i64 { + let cost = measure_operation(gas_metric, 1, |_| do_ecrecover()); + ratio_to_gas(gas_metric, Ratio::new(cost, 1)) as i64 + } + + fn do_ecrecover() -> i64 { + let hash: [u8; 32] = [ + 0x7d, 0xba, 0xf5, 0x58, 0xb0, 0xa1, 0xa5, 0xdc, 0x7a, 0x67, 0x20, 0x21, 0x17, 0xab, + 0x14, 0x3c, 0x1d, 0x86, 0x05, 0xa9, 0x83, 0xe4, 0xa7, 0x43, 0xbc, 0x06, 0xfc, 0xc0, + 0x31, 0x62, 0xdc, 0x0d, + ]; + let signature: [u8; 65] = [ + 0x5d, 0x99, 0xb6, 0xf7, 0xf6, 0xd1, 0xf7, 0x3d, 0x1a, 0x26, 0x49, 0x7f, 0x2b, 0x1c, + 0x89, 0xb2, 0x4c, 0x09, 0x93, 0x91, 0x3f, 0x86, 0xe9, 0xa2, 0xd0, 0x2c, 0xd6, 0x98, + 0x87, 0xd9, 0xc9, 0x4f, 0x3c, 0x88, 0x03, 0x58, 0x57, 0x9d, 0x81, 0x1b, 0x21, 0xdd, + 0x1b, 0x7f, 0xd9, 0xbb, 0x01, 0xc1, 0xd8, 0x1d, 0x10, 0xe6, 0x9f, 0x03, 0x84, 0xe6, + 0x75, 0xc3, 0x2b, 0x39, 0x64, 0x3b, 0xe8, 0x92, /* version */ 0, + ]; + let result = ecrecover(signature, hash, 0).unwrap(); + result.as_ref()[0] as i64 + } + + pub fn cost_of_math(gas_metric: GasMetric) -> EvmMathCosts { + #[cfg(debug_assertions)] + println!("WARNING: must run in release mode to provide accurate results!"); + let (ripemd160_base, ripemd160_per_byte) = compute_ripemd160_cost(gas_metric); + let ecrecover_base = compute_ecrecover_base(gas_metric); + EvmMathCosts { ripemd160_base, ripemd160_per_byte, ecrecover_base } + } + + #[test] + fn test_math_cost_time() { + // cargo test --release --features protocol_feature_math_extension \ + // --package runtime-params-estimator \ + // --lib math_ops_cost::math_ops_cost::test_math_cost_time \ + // -- --exact --nocapture + println!("{:?}", cost_of_math(GasMetric::Time)); + } + + #[test] + fn test_math_cost_icount() { + // CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER=./runner.sh cargo test --release \ + // --features protocol_feature_math_extension \ + // --package runtime-params-estimator \ + // --lib math_ops_cost::math_ops_cost::test_math_cost_icount \ + // -- --exact --nocapture + // Where runner.sh is + // /host/nearcore/runtime/runtime-params-estimator/emu-cost/counter_plugin/qemu-x86_64 \ + // -cpu Westmere-v1 -plugin file=/host/nearcore/runtime/runtime-params-estimator/emu-cost/counter_plugin/libcounter.so $@ + println!("{:?}", cost_of_math(GasMetric::ICount)); + } +} diff --git a/runtime/runtime-params-estimator/src/vm_estimator.rs b/runtime/runtime-params-estimator/src/vm_estimator.rs index 00d266d6e38..35cab478cf5 100644 --- a/runtime/runtime-params-estimator/src/vm_estimator.rs +++ b/runtime/runtime-params-estimator/src/vm_estimator.rs @@ -23,7 +23,7 @@ const SIGNER_ACCOUNT_ID: &str = "bob"; const SIGNER_ACCOUNT_PK: [u8; 3] = [0, 1, 2]; const PREDECESSOR_ACCOUNT_ID: &str = "carol"; -fn create_context(input: Vec) -> VMContext { +pub(crate) fn create_context(input: Vec) -> VMContext { VMContext { current_account_id: CURRENT_ACCOUNT_ID.to_owned(), signer_account_id: SIGNER_ACCOUNT_ID.to_owned(), @@ -162,7 +162,10 @@ impl CompiledContractCache for MockCompiledContractCache { } } -fn least_squares_method(xs: &Vec, ys: &Vec) -> (Ratio, Ratio, Vec) { +pub(crate) fn least_squares_method( + xs: &Vec, + ys: &Vec, +) -> (Ratio, Ratio, Vec) { let n = xs.len(); let n128 = n as i128; diff --git a/runtime/runtime-params-estimator/test-contract/Cargo.toml b/runtime/runtime-params-estimator/test-contract/Cargo.toml index 5199091d0fb..e3ae9758de3 100644 --- a/runtime/runtime-params-estimator/test-contract/Cargo.toml +++ b/runtime/runtime-params-estimator/test-contract/Cargo.toml @@ -22,5 +22,6 @@ members = [] nightly_protocol_features = ["protocol_feature_alt_bn128", "protocol_feature_evm"] protocol_feature_alt_bn128 = [] protocol_feature_evm = [] +protocol_feature_math_extension = [] payload = [] diff --git a/runtime/runtime-params-estimator/test-contract/build.sh b/runtime/runtime-params-estimator/test-contract/build.sh index baaebff6d7a..22219bb9a6b 100755 --- a/runtime/runtime-params-estimator/test-contract/build.sh +++ b/runtime/runtime-params-estimator/test-contract/build.sh @@ -1,6 +1,14 @@ #!/usr/bin/env bash set -ex +features_with_arg="" +features_with_comma="" + +if [[ ! -z "$1" ]]; then + features_with_arg="--features $1" + features_with_comma=",$1" +fi + function filesize { local file=$1 @@ -23,7 +31,7 @@ rustup target add wasm32-unknown-unknown # First, measure the size of the file without payload. rm -rf target -RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release +RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release $features_with_arg bare_wasm=$(filesize target/wasm32-unknown-unknown/release/test_contract.wasm) echo ${bare_wasm} @@ -32,17 +40,17 @@ echo ${bare_wasm} # 10KiB dd if=/dev/urandom of=./res/payload bs=$(expr 10240 - ${bare_wasm}) count=1 -RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release --features payload +RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release --features "payload$features_with_comma" cp target/wasm32-unknown-unknown/release/test_contract.wasm ./res/stable_small_contract.wasm # 100KiB dd if=/dev/urandom of=./res/payload bs=$(expr 102400 - ${bare_wasm}) count=1 -RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release --features payload +RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release --features "payload$features_with_comma" cp target/wasm32-unknown-unknown/release/test_contract.wasm ./res/stable_medium_contract.wasm # 1MiB dd if=/dev/urandom of=./res/payload bs=$(expr 1048576 - ${bare_wasm}) count=1 -RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release --features payload +RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release --features "payload$features_with_comma" cp target/wasm32-unknown-unknown/release/test_contract.wasm ./res/stable_large_contract.wasm rm ./res/payload @@ -50,7 +58,7 @@ rm ./res/payload # Compiling nightly rm -rf target -RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release --features nightly_protocol_features +RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release --features "nightly_protocol_features$features_with_comma" bare_wasm=$(filesize target/wasm32-unknown-unknown/release/test_contract.wasm) echo ${bare_wasm} @@ -60,17 +68,17 @@ echo ${bare_wasm} # Note the base is 16057 due to alt_bn128 hardcoded input. # 20KiB dd if=/dev/urandom of=./res/payload bs=$(expr 20480 - ${bare_wasm}) count=1 -RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release --features payload,nightly_protocol_features +RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release --features "payload,nightly_protocol_features$features_with_comma" cp target/wasm32-unknown-unknown/release/test_contract.wasm ./res/nightly_small_contract.wasm # 100KiB dd if=/dev/urandom of=./res/payload bs=$(expr 102400 - ${bare_wasm}) count=1 -RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release --features payload,nightly_protocol_features +RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release --features "payload,nightly_protocol_features$features_with_comma" cp target/wasm32-unknown-unknown/release/test_contract.wasm ./res/nightly_medium_contract.wasm # 1MiB dd if=/dev/urandom of=./res/payload bs=$(expr 1048576 - ${bare_wasm}) count=1 -RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release --features payload,nightly_protocol_features +RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release --features "payload,nightly_protocol_features$features_with_comma" cp target/wasm32-unknown-unknown/release/test_contract.wasm ./res/nightly_large_contract.wasm rm ./res/payload diff --git a/runtime/runtime-params-estimator/test-contract/src/lib.rs b/runtime/runtime-params-estimator/test-contract/src/lib.rs index a2bda684413..399aa890f50 100644 --- a/runtime/runtime-params-estimator/test-contract/src/lib.rs +++ b/runtime/runtime-params-estimator/test-contract/src/lib.rs @@ -47,6 +47,10 @@ extern "C" { fn sha256(value_len: u64, value_ptr: u64, register_id: u64); fn keccak256(value_len: u64, value_ptr: u64, register_id: u64); fn keccak512(value_len: u64, value_ptr: u64, register_id: u64); + #[cfg(feature = "protocol_feature_math_extension")] + fn ripemd160(value_len: u64, value_ptr: u64, register_id: u64); + #[cfg(feature = "protocol_feature_math_extension")] + fn ecrecover(hash_len: u64, hash_ptr: u64, sig_len: u64, sig_ptr: u64, v: u64, malleability_flag: u64, register_id: u64); // ##################### // # Miscellaneous API # // ##################### @@ -424,6 +428,56 @@ pub unsafe fn keccak512_10kib_10k() { } } +// Function to measure `ripemd160_base` and `ripemd160_block`. Also measures `base`, `write_register_base`, +// and `write_register_byte`. However `ripemd160` computation is more expensive than register writing +// so we are okay overcharging it. +// Compute ripemd160 on 10b 10k times. +#[cfg(feature = "protocol_feature_math_extension")] +#[no_mangle] +pub unsafe fn ripemd160_10b_10k() { + let buffer = [65u8; 10]; + for _ in 0..10_000 { + ripemd160(buffer.len() as u64, buffer.as_ptr() as *const u64 as u64, 0); + } +} +// Function to measure `ripemd160_base` and `ripemd160_block`. Also measures `base`, `write_register_base`, +// and `write_register_byte`. However `ripemd160` computation is more expensive than register writing +// so we are okay overcharging it. +// Compute ripemd160 on 10kib 10k times. +#[cfg(feature = "protocol_feature_math_extension")] +#[no_mangle] +pub unsafe fn ripemd160_10kib_10k() { + let buffer = [65u8; 10240]; + for _ in 0..10_000 { + ripemd160(buffer.len() as u64, buffer.as_ptr() as *const u64 as u64, 0); + } +} + +// Function to measure `ecrecover_base`. Also measures `base`, `write_register_base`, and +// `write_register_byte`. However `ecrecover` computation is more expensive than register writing +// so we are okay overcharging it. +// Compute ecrecover 10k times. +#[cfg(feature = "protocol_feature_math_extension")] +#[no_mangle] +pub unsafe fn ecrecover_10k() { + let hash_buffer: [u8; 32] = [ + 0x7d, 0xba, 0xf5, 0x58, 0xb0, 0xa1, 0xa5, 0xdc, 0x7a, 0x67, 0x20, 0x21, 0x17, 0xab, 0x14, + 0x3c, 0x1d, 0x86, 0x05, 0xa9, 0x83, 0xe4, 0xa7, 0x43, 0xbc, 0x06, 0xfc, 0xc0, 0x31, 0x62, + 0xdc, 0x0d, + ]; + let sig_buffer: [u8; 64] = [ + 0x5d, 0x99, 0xb6, 0xf7, 0xf6, 0xd1, 0xf7, 0x3d, 0x1a, 0x26, 0x49, 0x7f, 0x2b, 0x1c, 0x89, + 0xb2, 0x4c, 0x09, 0x93, 0x91, 0x3f, 0x86, 0xe9, 0xa2, 0xd0, 0x2c, 0xd6, 0x98, 0x87, 0xd9, + 0xc9, 0x4f, 0x3c, 0x88, 0x03, 0x58, 0x57, 0x9d, 0x81, 0x1b, 0x21, 0xdd, 0x1b, 0x7f, 0xd9, + 0xbb, 0x01, 0xc1, 0xd8, 0x1d, 0x10, 0xe6, 0x9f, 0x03, 0x84, 0xe6, 0x75, 0xc3, 0x2b, 0x39, + 0x64, 0x3b, 0xe8, 0x92, + ]; + + for _ in 0..10_000 { + ecrecover(32, hash_buffer.as_ptr() as _, 64, sig_buffer.as_ptr() as _, 0, 0, 0); + } +} + // Function to measure `alt_bn128_g1_multiexp_base` and `alt_bn128_g1_multiexp_sublinear`. Also measures `base`, `write_register_base`, // and `write_register_byte`. However `g1_multiexp` computation is more expensive than register writing // so we are okay overcharging it. diff --git a/runtime/runtime/Cargo.toml b/runtime/runtime/Cargo.toml index e4e47712519..6ecd3f70ff0 100644 --- a/runtime/runtime/Cargo.toml +++ b/runtime/runtime/Cargo.toml @@ -56,6 +56,12 @@ protocol_feature_tx_size_limit = [] protocol_feature_allow_create_account_on_delete = ["near-primitives/protocol_feature_allow_create_account_on_delete", "near-vm-logic/protocol_feature_allow_create_account_on_delete"] protocol_feature_fix_storage_usage = ["near-primitives/protocol_feature_fix_storage_usage"] protocol_feature_restore_receipts_after_fix = [] +protocol_feature_math_extension = [ + "near-primitives/protocol_feature_math_extension", + "near-vm-logic/protocol_feature_math_extension", + "near-vm-runner/protocol_feature_math_extension", + "near-vm-errors/protocol_feature_math_extension" +] sandbox = [] [dev-dependencies] diff --git a/runtime/runtime/src/lib.rs b/runtime/runtime/src/lib.rs index 81f87dd7b92..bc0baee0c9d 100644 --- a/runtime/runtime/src/lib.rs +++ b/runtime/runtime/src/lib.rs @@ -1,5 +1,7 @@ use std::cmp::max; use std::collections::{HashMap, HashSet}; +use std::rc::Rc; +use std::sync::Arc; use log::debug; @@ -7,7 +9,16 @@ use near_chain_configs::Genesis; pub use near_crypto; use near_crypto::PublicKey; pub use near_primitives; +#[cfg(feature = "sandbox")] +use near_primitives::contract::ContractCode; +pub use near_primitives::runtime::apply_state::ApplyState; +use near_primitives::runtime::fees::RuntimeFeesConfig; use near_primitives::runtime::get_insufficient_storage_stake; +use near_primitives::runtime::migration_data::{MigrationData, MigrationFlags}; +use near_primitives::transaction::ExecutionMetadata; +use near_primitives::version::{ + is_implicit_account_creation_enabled, ProtocolFeature, ProtocolVersion, +}; use near_primitives::{ account::Account, errors::{ActionError, ActionErrorKind, RuntimeError, TxExecutionError}, @@ -50,17 +61,6 @@ use crate::config::{ use crate::genesis::{GenesisStateApplier, StorageComputer}; use crate::verifier::validate_receipt; pub use crate::verifier::{validate_transaction, verify_and_charge_transaction}; -#[cfg(feature = "sandbox")] -use near_primitives::contract::ContractCode; -pub use near_primitives::runtime::apply_state::ApplyState; -use near_primitives::runtime::fees::RuntimeFeesConfig; -use near_primitives::runtime::migration_data::{MigrationData, MigrationFlags}; -use near_primitives::transaction::ExecutionMetadata; -use near_primitives::version::{ - is_implicit_account_creation_enabled, ProtocolFeature, ProtocolVersion, -}; -use std::rc::Rc; -use std::sync::Arc; mod actions; pub mod adapter; @@ -1443,7 +1443,7 @@ impl Runtime { #[cfg(test)] mod tests { - use super::*; + use std::sync::Arc; use near_crypto::{InMemorySigner, KeyType, Signer}; use near_primitives::account::AccessKey; @@ -1459,9 +1459,10 @@ mod tests { use near_store::set_access_key; use near_store::test_utils::create_tries; use near_store::StoreCompiledContractCache; - use std::sync::Arc; use testlib::runtime_utils::{alice_account, bob_account}; + use super::*; + const GAS_PRICE: Balance = 5000; fn to_yocto(near: Balance) -> Balance {