From b0945d609aced3d0b32ba204f55c306f36bd199b Mon Sep 17 00:00:00 2001 From: "Joshua J. Bouw" Date: Wed, 30 Jun 2021 10:38:28 +0700 Subject: [PATCH] Math API `ecrecover` and `ripemd160` extension (#4380) * feat: Extend the Math API with EVM precompiles. * feat: Add Math API stubs for ECRecover. (#3921) * test: Add RIPEMD-160 and BLAKE2b to the params estimator. * fix: Improve error output from the params estimator. Co-authored-by: Aleksey Kladov * test: Add ECRecover stubs to the params estimator. * fix: Widen ecrecover() parameter type to pass CI checks. * feat(chain): Bump the protocol version. * feat(runtime): Implement ECRecover in the Math API. (#3921) * docs: Document the ecrecover() function. * fix(chain): Guard protocol upgrade behind EVM feature. * add crypto feature gates * fix nightly compile * cargo fmt * fix two ripemd160 missing feature gates * rename crypto_extras feature to protocol_feature equivalents * change repo to our git * put everything under protocol_feature_evm * point to alternative blake2 branch temporarily * Add blake2b f function * add blake2b_f to config * post rebase fixes * wrap all ext_costs in evm feature * add blake_2b_f to runtime estimator * cargo fmt * add new hash algos to feature * add vm-logic test for blake2b_f * fix derive formatting * use same blake2 library * change blake F tests to test 1 round * fix blake f test * cleanup calls_helper * add missing feature gated fields from ExtCostsConfig * add blake2b_f compression docs * remove unused `blake2b_f_byte` as sizes are fixed * add missing blake2b_f to imports * runtime estimator mod docs * Add near-blake2 lib to near-crypto * fix test imports * add more pub methods to blake2 * add blake2 state success tests * cargo fmt * Add initial blake2 logic to vm * Fix ecrecover * Change blake2 `update` to `update_inner` * Update params estimator * Remove duplicate evm feature * Remove pattern * Fix some compilation errors * Fix runtime-params-estimator compile * Fix tests * Reduce blake2s param estimator rounds from 12 to 10 * Add protocol_feature_evm feature to standalone runner * Add estimated gas values * Update estimated gas value note * cargo fmt * Update c2-chacha * Remove nightly features from lib blake2 * Add blake2 pass tests * Fix up ecrecover test * Fix blake2s args * Remove blake2 lib * Update documentation for blake2 * Pin comment for blake2 lib * Fix with_state * Return a bool for ecrecover, not abort * Fix VarBlake2b -> VarBlake2s * Add errors to RPC errors schema * Add bool return to wrapped imports * Take t0 and t1 for blake2b * Charge ripemd160 by blocks, not bytes * Correctly send both t0 and t1 to blake2 * Remove too many rounds error * Remove any near-blake2 errors * Remove hash data overflow error * Rename Blake2InvalidStateLength -> Blake2StateLengthExceeded * Small ecrecover clarifications * Return public key in ecrecover * Correct blake2 message block math * Correct ripemd160 message block math * Read blake2s as u32 not u64 * Remove `state_len` and blake2 len error * Add `saturating_mul` to blake2 math * Don't allocate an extra vec on blake2 * Remove unused import * Fix args in wrapped imports * Fix blake2s test * Remove blake2 and added protocol_feature_evm conditional comps * Remove optional from crypto imports in logic * Revert from near-blake2 to upstream blake2 * Drop libsecp256k1 crate in favour of near-crypto * Improve code quality by using U256 * Pass hash_bytes as ref * Revert "Pass hash_bytes as ref" This reverts commit 434145b5135c644c4e0d239a41618c78b59dfbb0. * matklad nit Co-authored-by: Aleksey Kladov * Fix ecrecover exports arg sig * Move when malleability check happens * Remove last check in signature values Co-authored-by: Aleksey Kladov * Don't use `v` in a check, now unused * Update description of ripemd160 with correct cost * Use u32 for malleability_flag * Introduce protocol_feature_math_extension * Use our own div_ceil implementation * Include protocol_feature_math_extension in runtime-params-estimator/nightly_protocol_features * Nightly protocol version bump * Simplify message_blocks computation * Remove unused div_ceil function * Ignore first byte of result * Remove uneeded return at end of function Co-authored-by: EgorKulikov * Remove check_v and add in option to skip reject upper range * Use `get_vec_from_memory_or_register` over `memory_get_into` * Remove unused * Separate `v`, change args to `u64` * Remove redundant check * 64th byte, not 65 Co-authored-by: Aleksey Kladov * Fix test * Update doc cost * Fix estimator * Small fixes * Add missing feature `protocol_feature_math_extension` from neard * Add near-primitives to math extension params estimator * Fix byte length * Fix Cargo.toml * Nit. Typos * Nit. param estimator is missing feature dependency * Add feature support for build.sh of test-contract * Add consts for SECP256K1 malleability values * Remove map err, change to unwrap * Change v check to something more simple * Always do signature check * Check one s bound * Allow for specifying a register * Remove math extension feature from primitives core * Nit. Convering byte to bool * Add measuring support. * Revert "Remove math extension feature from primitives core" This reverts commit ac53dcbafcc5c0f405eeaf0dccfcdbf5ba19161a. * Const SECP256K1 half + 1 * Revert "Add measuring support." This reverts commit 210ee555723838d43759cc29fec80499d43a3672. * Update gas costs with correct values * Nit. Method signature typo causing runtime failure * Fix fee. It seems like a copy-paste typo * Undo the wrong fix * Add malleability_flag check as requested in https://github.com/near/nearcore/pull/4380#discussion_r659087686 * syntax fix Co-authored-by: Arto Bendiken Co-authored-by: Aleksey Kladov Co-authored-by: Michael Birch Co-authored-by: Michael Birch Co-authored-by: EgorKulikov Co-authored-by: Maksym Zavershynskyi Co-authored-by: Nikolay Igotti Co-authored-by: Bowen Wang Co-authored-by: Bowen Wang --- Cargo.lock | 7 +- core/crypto/Cargo.toml | 1 + core/crypto/src/lib.rs | 3 +- core/crypto/src/signature.rs | 57 ++++++ core/primitives-core/Cargo.toml | 3 +- core/primitives-core/src/config.rs | 39 ++++ core/primitives/Cargo.toml | 3 +- core/primitives/src/version.rs | 4 + nearcore/Cargo.toml | 6 +- neard/Cargo.toml | 1 + .../res/test_contract_ts.wasm | Bin runtime/near-vm-errors/Cargo.toml | 1 + runtime/near-vm-errors/src/lib.rs | 5 + runtime/near-vm-logic/Cargo.toml | 3 + runtime/near-vm-logic/src/gas_counter.rs | 10 +- runtime/near-vm-logic/src/logic.rs | 188 +++++++++++++++--- runtime/near-vm-logic/tests/test_miscs.rs | 78 ++++++++ runtime/near-vm-runner-standalone/Cargo.toml | 2 +- runtime/near-vm-runner/Cargo.toml | 4 + runtime/near-vm-runner/src/imports.rs | 2 + runtime/runtime-params-estimator/Cargo.toml | 11 +- runtime/runtime-params-estimator/README.md | 2 +- runtime/runtime-params-estimator/src/cases.rs | 15 ++ .../src/ext_costs_generator.rs | 8 + runtime/runtime-params-estimator/src/lib.rs | 2 +- .../test-contract/Cargo.toml | 1 + .../test-contract/build.sh | 24 ++- .../test-contract/src/lib.rs | 54 +++++ runtime/runtime/Cargo.toml | 6 + runtime/runtime/src/lib.rs | 1 - 30 files changed, 486 insertions(+), 55 deletions(-) mode change 100755 => 100644 runtime/near-test-contracts/res/test_contract_ts.wasm diff --git a/Cargo.lock b/Cargo.lock index e0e94076945..314eab4e762 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", @@ -3179,6 +3179,7 @@ dependencies = [ "curve25519-dalek", "derive_more", "ed25519-dalek", + "ethereum-types 0.11.0", "hex-literal", "lazy_static", "libc", @@ -3637,10 +3638,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", 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..2a24ca7d7fa 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 + 1. +const SECP256K1_N_HALF_ONE: U256 = + U256([0xdfe92f46681b20a1, 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_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..e6cc9307a1b 100644 --- a/core/primitives-core/Cargo.toml +++ b/core/primitives-core/Cargo.toml @@ -24,7 +24,8 @@ 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 = [] +protocol_feature_math_extension = [] diff --git a/core/primitives-core/src/config.rs b/core/primitives-core/src/config.rs index 58136b1d244..801e4f396d9 100644 --- a/core/primitives-core/src/config.rs +++ b/core/primitives-core/src/config.rs @@ -223,6 +223,17 @@ pub struct ExtCostsConfig { /// Cost of getting sha256 per byte pub keccak512_byte: Gas, + #[cfg(feature = "protocol_feature_math_extension")] + /// Cost of getting ripemd160 base + pub ripemd160_base: Gas, + #[cfg(feature = "protocol_feature_math_extension")] + /// Cost of getting ripemd160 per message block + pub ripemd160_block: Gas, + + #[cfg(feature = "protocol_feature_math_extension")] + /// Cost of calling ecrecover + pub ecrecover_base: Gas, + /// Cost for calling logging. pub log_base: Gas, /// Cost for logging per byte @@ -353,6 +364,13 @@ impl Default for ExtCostsConfig { keccak256_byte: SAFETY_MULTIPLIER * 7157035, keccak512_base: SAFETY_MULTIPLIER * 1937129412, keccak512_byte: SAFETY_MULTIPLIER * 12216567, + #[cfg(feature = "protocol_feature_math_extension")] + ripemd160_base: SAFETY_MULTIPLIER * 284558362, + #[cfg(feature = "protocol_feature_math_extension")] + // Cost per byte is 3542227. There are 64 bytes in a block. + ripemd160_block: SAFETY_MULTIPLIER * 226702528, + #[cfg(feature = "protocol_feature_math_extension")] + ecrecover_base: SAFETY_MULTIPLIER * 1121789875000, log_base: SAFETY_MULTIPLIER * 1181104350, log_byte: SAFETY_MULTIPLIER * 4399597, storage_write_base: SAFETY_MULTIPLIER * 21398912000, @@ -423,6 +441,12 @@ impl ExtCostsConfig { keccak256_byte: 0, keccak512_base: 0, keccak512_byte: 0, + #[cfg(feature = "protocol_feature_math_extension")] + ripemd160_base: 0, + #[cfg(feature = "protocol_feature_math_extension")] + ripemd160_block: 0, + #[cfg(feature = "protocol_feature_math_extension")] + ecrecover_base: 0, log_base: 0, log_byte: 0, storage_write_base: 0, @@ -494,6 +518,12 @@ pub enum ExtCosts { keccak256_byte, keccak512_base, keccak512_byte, + #[cfg(feature = "protocol_feature_math_extension")] + ripemd160_base, + #[cfg(feature = "protocol_feature_math_extension")] + ripemd160_block, + #[cfg(feature = "protocol_feature_math_extension")] + ecrecover_base, log_base, log_byte, storage_write_base, @@ -618,6 +648,12 @@ impl ExtCosts { keccak256_byte => config.keccak256_byte, keccak512_base => config.keccak512_base, keccak512_byte => config.keccak512_byte, + #[cfg(feature = "protocol_feature_math_extension")] + ripemd160_base => config.ripemd160_base, + #[cfg(feature = "protocol_feature_math_extension")] + ripemd160_block => config.ripemd160_block, + #[cfg(feature = "protocol_feature_math_extension")] + ecrecover_base => config.ecrecover_base, log_base => config.log_base, log_byte => config.log_byte, storage_write_base => config.storage_write_base, @@ -692,6 +728,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 0ce9f73b7cc..3e4975fdf9c 100644 --- a/core/primitives/Cargo.toml +++ b/core/primitives/Cargo.toml @@ -51,7 +51,8 @@ protocol_feature_fix_storage_usage = [] protocol_feature_restore_receipts_after_fix = [] protocol_feature_cap_max_gas_price = [] protocol_feature_count_refund_receipts_in_gas_limit = [] -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_count_refund_receipts_in_gas_limit"] +protocol_feature_math_extension = ["near-primitives-core/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_count_refund_receipts_in_gas_limit", "protocol_feature_math_extension"] nightly_protocol = [] [dev-dependencies] diff --git a/core/primitives/src/version.rs b/core/primitives/src/version.rs index 8e91709d304..04a0a48eb86 100644 --- a/core/primitives/src/version.rs +++ b/core/primitives/src/version.rs @@ -109,6 +109,8 @@ pub enum ProtocolFeature { CapMaxGasPrice, #[cfg(feature = "protocol_feature_count_refund_receipts_in_gas_limit")] CountRefundReceiptsInGasLimit, + #[cfg(feature = "protocol_feature_math_extension")] + MathExtension, } /// Current latest stable version of the protocol. @@ -153,6 +155,8 @@ impl ProtocolFeature { ProtocolFeature::CapMaxGasPrice => 113, #[cfg(feature = "protocol_feature_count_refund_receipts_in_gas_limit")] ProtocolFeature::CountRefundReceiptsInGasLimit => 114, + #[cfg(feature = "protocol_feature_math_extension")] + ProtocolFeature::MathExtension => 114, } } } diff --git a/nearcore/Cargo.toml b/nearcore/Cargo.toml index 177ac025313..a02384857e0 100644 --- a/nearcore/Cargo.toml +++ b/nearcore/Cargo.toml @@ -71,10 +71,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/logic.rs b/runtime/near-vm-logic/src/logic.rs index d80b64719c8..b80007c8e11 100644 --- a/runtime/near-vm-logic/src/logic.rs +++ b/runtime/near-vm-logic/src/logic.rs @@ -5,6 +5,8 @@ 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::Secp256K1Signature; use near_primitives::checked_feature; use near_primitives::version::is_implicit_account_creation_enabled; use near_primitives_core::config::ExtCosts::*; @@ -188,7 +190,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 +198,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 +231,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 +246,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 +255,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 +392,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 +455,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 +811,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 +847,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 +871,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 +903,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 +924,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 +945,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 +953,132 @@ 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)?; + + use ripemd160::Digest; + + let value_hash = ripemd160::Ripemd160::digest(&value); + self.internal_write_register(register_id, value_hash.as_slice().to_vec()) + } + + /// 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 = { + 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; + Secp256K1Signature::from(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 + }; + + if malleability_flag != 0 && malleability_flag != 1 { + return Err(VMLogicError::HostError(HostError::ECRecoverError { + msg: format!( + "Malleability flag needs to be 0 or 1, but is instead {}", + malleability_flag + ), + })); + } + + if !signature.check_signature_values(malleability_flag != 0) { + return Ok(false as u64); + } + + if let Ok(pk) = signature.recover(hash) { + self.internal_write_register(register_id, pk.as_ref().to_vec())?; + return Ok(true as u64); + }; + + Ok(false as u64) + } + /// Called by gas metering injected into Wasm. Counts both towards `burnt_gas` and `used_gas`. /// /// # Errors @@ -1131,7 +1259,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 +2113,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 +2138,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 +2173,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 +2197,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 +2254,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 +2301,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 +2336,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 +2386,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 +2394,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 +2437,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 +2564,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) } } 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..3d831a27047 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..c3d904975f9 100644 --- a/runtime/runtime-params-estimator/Cargo.toml +++ b/runtime/runtime-params-estimator/Cargo.toml @@ -56,7 +56,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 +72,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..fc9639783fa 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, @@ -737,6 +746,12 @@ 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), + #[cfg(feature = "protocol_feature_math_extension")] + ripemd160_base: measured_to_gas(metric, &measured, ripemd160_base), + #[cfg(feature = "protocol_feature_math_extension")] + ripemd160_block: measured_to_gas(metric, &measured, ripemd160_block), + #[cfg(feature = "protocol_feature_math_extension")] + 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..6dd7e9ce7fd 100644 --- a/runtime/runtime-params-estimator/src/lib.rs +++ b/runtime/runtime-params-estimator/src/lib.rs @@ -1,4 +1,4 @@ -// Lists all cases that we want to measure. +/// Lists all cases that we want to measure. pub mod cases; // Generates runtime fees from the measurements. pub mod runtime_fees_generator; 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..d506db02133 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) -> 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 7b6cddb5223..0b3739da634 100644 --- a/runtime/runtime/Cargo.toml +++ b/runtime/runtime/Cargo.toml @@ -58,6 +58,12 @@ protocol_feature_allow_create_account_on_delete = ["near-primitives/protocol_fea protocol_feature_fix_storage_usage = ["near-primitives/protocol_feature_fix_storage_usage"] protocol_feature_restore_receipts_after_fix = [] protocol_feature_count_refund_receipts_in_gas_limit = ["near-primitives/protocol_feature_count_refund_receipts_in_gas_limit"] +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 273d791f19c..7005fd436e9 100644 --- a/runtime/runtime/src/lib.rs +++ b/runtime/runtime/src/lib.rs @@ -1477,7 +1477,6 @@ mod tests { use near_store::test_utils::create_tries; use near_store::StoreCompiledContractCache; use near_vm_runner::{get_contract_cache_key, VMKind}; - use std::sync::Arc; use testlib::runtime_utils::{alice_account, bob_account}; const GAS_PRICE: Balance = 5000;