From 2dc1b1a7485cd94ba34167a471da1d0404c30bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Mon, 13 Nov 2023 18:29:44 +0100 Subject: [PATCH 01/15] Install `blake3` package in execution_engine. Will be used to introduce hashing method for runtime. --- Cargo.lock | 32 ++++++++++++++++++++++++++++++++ execution_engine/Cargo.toml | 1 + 2 files changed, 33 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 6bdd804de7..ce6f333a04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -182,6 +182,18 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "assert-json-diff" version = "2.0.2" @@ -371,6 +383,19 @@ dependencies = [ "casper-types", ] +[[package]] +name = "blake3" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if 1.0.0", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -540,6 +565,7 @@ dependencies = [ "assert_matches", "base16", "bincode", + "blake3", "casper-hashing", "casper-types", "casper-wasm", @@ -983,6 +1009,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + [[package]] name = "contract-context" version = "0.1.0" diff --git a/execution_engine/Cargo.toml b/execution_engine/Cargo.toml index 2cd9379dc7..15d51c6e3f 100644 --- a/execution_engine/Cargo.toml +++ b/execution_engine/Cargo.toml @@ -14,6 +14,7 @@ license = "Apache-2.0" anyhow = "1.0.33" base16 = "0.2.1" bincode = "1.3.1" +blake3 = { version = "1.5.0", default-features = false } casper-hashing = { version = "2.0.0", path = "../hashing" } casper-types = { version = "3.0.0", path = "../types", default-features = false, features = ["datasize", "gens", "json-schema"] } casper-wasm = { version = "0.46.0", default-features = false } From 930d0b8dda817f867e845b5d6079767008afcc1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Mon, 13 Nov 2023 16:18:10 +0100 Subject: [PATCH 02/15] Add blake3 hasher to execution engine. Placed in new `crypto` module under `runtime`. --- execution_engine/src/core/runtime/crypto.rs | 15 +++++++++++++++ execution_engine/src/core/runtime/mod.rs | 1 + 2 files changed, 16 insertions(+) create mode 100644 execution_engine/src/core/runtime/crypto.rs diff --git a/execution_engine/src/core/runtime/crypto.rs b/execution_engine/src/core/runtime/crypto.rs new file mode 100644 index 0000000000..300f3f7e7e --- /dev/null +++ b/execution_engine/src/core/runtime/crypto.rs @@ -0,0 +1,15 @@ +/// The number of bytes in a Blake3 hash. +/// NOTE: It does not make sense to use different lengths. +const BLAKE3_DIGEST_LENGTH: usize = 32; + +#[doc(hidden)] +pub fn blake3>(data: T) -> [u8; BLAKE3_DIGEST_LENGTH] { + let mut result = [0; BLAKE3_DIGEST_LENGTH]; + let mut hasher = blake3::Hasher::new(); + + hasher.update(data.as_ref()); + let hash = hasher.finalize(); + let hash_bytes: &[u8; BLAKE3_DIGEST_LENGTH] = hash.as_bytes(); + result.copy_from_slice(hash_bytes); + result +} diff --git a/execution_engine/src/core/runtime/mod.rs b/execution_engine/src/core/runtime/mod.rs index ce6d8a39d1..c04b7ff7ad 100644 --- a/execution_engine/src/core/runtime/mod.rs +++ b/execution_engine/src/core/runtime/mod.rs @@ -1,6 +1,7 @@ //! This module contains executor state of the WASM code. mod args; mod auction_internal; +mod crypto; mod externals; mod handle_payment_internal; mod host_function_flag; From 356b5caee12840ab2adf1f03f23c6927c8fc97fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Mon, 13 Nov 2023 16:32:20 +0100 Subject: [PATCH 03/15] Add `HashAlgoType` to base types. Will be used to switch between different hashing algorithms. --- types/src/crypto.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/types/src/crypto.rs b/types/src/crypto.rs index fbcd172c73..1418602037 100644 --- a/types/src/crypto.rs +++ b/types/src/crypto.rs @@ -3,11 +3,21 @@ mod asymmetric_key; mod error; +use alloc::vec::Vec; + use blake2::{ digest::{Update, VariableOutput}, VarBlake2b, }; +#[cfg(feature = "datasize")] +use datasize::DataSize; +#[cfg(feature = "json-schema")] +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::bytesrepr::{self, FromBytes, ToBytes}; + use crate::key::BLAKE2B_DIGEST_LENGTH; #[cfg(any(feature = "std", test))] pub use asymmetric_key::generate_ed25519_keypair; @@ -33,3 +43,53 @@ pub fn blake2b>(data: T) -> [u8; BLAKE2B_DIGEST_LENGTH] { }); result } + +/// HashAlgoType +#[repr(u8)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +pub enum HashAlgoType { + /// Blake2b + Blake2b = 0, + /// Blake3 + Blake3 = 1, +} + +impl ToBytes for HashAlgoType { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + (*self as u8).to_bytes() + } + + fn serialized_length(&self) -> usize { + 1 + } + + fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { + writer.push(*self as u8); + Ok(()) + } +} + +impl FromBytes for HashAlgoType { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (value, bytes) = u8::from_bytes(bytes)?; + match value { + 0 => Ok((HashAlgoType::Blake2b, bytes)), + 1 => Ok((HashAlgoType::Blake3, bytes)), + _ => Err(bytesrepr::Error::Formatting), + } + } +} + +// Statics representing the discriminant values of the enum +static VARIANT_BLAKE_2B: u8 = HashAlgoType::Blake2b as u8; +static VARIANT_BLAKE_3: u8 = HashAlgoType::Blake3 as u8; +impl AsRef for HashAlgoType { + fn as_ref(&self) -> &u8 { + match self { + HashAlgoType::Blake2b => &VARIANT_BLAKE_2B, + HashAlgoType::Blake3 => &VARIANT_BLAKE_3, + } + } +} From eb3eb4549ea6bbefd8e189db53703d2ae3a87460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Mon, 13 Nov 2023 16:40:14 +0100 Subject: [PATCH 04/15] Implement host function `GenericHash`. Note: no gas charging was introduced yet. --- .../src/core/resolvers/v1_function_index.rs | 1 + .../src/core/resolvers/v1_resolver.rs | 4 ++ .../src/core/runtime/externals.rs | 38 ++++++++++++++++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/execution_engine/src/core/resolvers/v1_function_index.rs b/execution_engine/src/core/resolvers/v1_function_index.rs index 56625d6e21..18e9cb48e9 100644 --- a/execution_engine/src/core/resolvers/v1_function_index.rs +++ b/execution_engine/src/core/resolvers/v1_function_index.rs @@ -60,6 +60,7 @@ pub(crate) enum FunctionIndex { RandomBytes, DictionaryReadFuncIndex, EnableContractVersion, + GenericHash, } impl From for usize { diff --git a/execution_engine/src/core/resolvers/v1_resolver.rs b/execution_engine/src/core/resolvers/v1_resolver.rs index 29b4b1cb1c..b370035203 100644 --- a/execution_engine/src/core/resolvers/v1_resolver.rs +++ b/execution_engine/src/core/resolvers/v1_resolver.rs @@ -245,6 +245,10 @@ impl ModuleImportResolver for RuntimeModuleImportResolver { Signature::new(&[ValueType::I32; 4][..], Some(ValueType::I32)), FunctionIndex::EnableContractVersion.into(), ), + "casper_generic_hash" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 6][..], Some(ValueType::I32)), + FunctionIndex::GenericHash.into(), + ), _ => { return Err(InterpreterError::Function(format!( "host module doesn't export function with name {}", diff --git a/execution_engine/src/core/runtime/externals.rs b/execution_engine/src/core/runtime/externals.rs index b48f63e619..7ae46ce265 100644 --- a/execution_engine/src/core/runtime/externals.rs +++ b/execution_engine/src/core/runtime/externals.rs @@ -9,13 +9,14 @@ use casper_types::{ contracts::{ContractPackageStatus, EntryPoints, NamedKeys}, crypto, system::auction::EraInfo, - ApiError, ContractHash, ContractPackageHash, ContractVersion, EraId, Gas, Group, Key, - StoredValue, URef, U512, UREF_SERIALIZED_LENGTH, + ApiError, ContractHash, ContractPackageHash, ContractVersion, EraId, Gas, Group, HashAlgoType, + Key, StoredValue, URef, U512, UREF_SERIALIZED_LENGTH, }; use super::{args::Args, Error, Runtime}; use crate::{ core::resolvers::v1_function_index::FunctionIndex, + core::runtime::crypto as core_crypto, shared::host_function_costs::{Cost, HostFunction, DEFAULT_HOST_FUNCTION_NEW_DICTIONARY}, storage::global_state::StateReader, }; @@ -1097,6 +1098,39 @@ where Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))) } + + FunctionIndex::GenericHash => { + // args(0) = pointer to input in Wasm memory + // args(1) = size of input in Wasm memory + // args(2) = pointer to hash type pointer in Wasm memory + // args(3) = size of hash type in Wasm memory + // args(4) = pointer to output pointer in Wasm memory + // args(5) = size of output + let (in_ptr, in_size, hash_algo_type_ptr, hash_algo_type_size, out_ptr, out_size): (u32, u32, u32, u32, u32, u32) = + Args::parse(args)?; + let hash_algo_type = self.t_from_mem(hash_algo_type_ptr, hash_algo_type_size)?; + let digest = + self.checked_memory_slice(in_ptr as usize, in_size as usize, |input| { + match hash_algo_type { + HashAlgoType::Blake2b => crypto::blake2b(input), + HashAlgoType::Blake3 => core_crypto::blake3(input), + } + })?; + + let result = if digest.len() > out_size as usize { + Err(ApiError::BufferTooSmall) + } else { + Ok(()) + }; + if result.is_err() { + return Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))); + } + + self.try_get_memory()? + .set(out_ptr, &digest) + .map_err(|error| Error::Interpreter(error.into()))?; + Ok(Some(RuntimeValue::I32(0))) + } } } } From 267873a4f93d637b7d1598b6eabc4ec4995ff0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Mon, 13 Nov 2023 16:44:20 +0100 Subject: [PATCH 05/15] Charge default cost for using `generic_hash`. Inspired by `FunctionIndex::Blake2b`, which is also not researched in terms of execution cost. --- execution_engine/src/core/runtime/externals.rs | 13 ++++++++++++- execution_engine/src/shared/host_function_costs.rs | 10 ++++++++++ .../tests/src/test/storage_costs.rs | 1 + .../tests/src/test/system_costs.rs | 1 + node/src/types/chainspec.rs | 1 + resources/local/chainspec.toml.in | 1 + resources/production/chainspec.toml | 1 + resources/test/valid/0_9_0/chainspec.toml | 1 + resources/test/valid/0_9_0_unordered/chainspec.toml | 1 + resources/test/valid/1_0_0/chainspec.toml | 1 + 10 files changed, 30 insertions(+), 1 deletion(-) diff --git a/execution_engine/src/core/runtime/externals.rs b/execution_engine/src/core/runtime/externals.rs index 7ae46ce265..0161510e6c 100644 --- a/execution_engine/src/core/runtime/externals.rs +++ b/execution_engine/src/core/runtime/externals.rs @@ -1106,8 +1106,19 @@ where // args(3) = size of hash type in Wasm memory // args(4) = pointer to output pointer in Wasm memory // args(5) = size of output - let (in_ptr, in_size, hash_algo_type_ptr, hash_algo_type_size, out_ptr, out_size): (u32, u32, u32, u32, u32, u32) = + let (in_ptr, in_size, hash_algo_type_ptr, hash_algo_type_size, out_ptr, out_size) = Args::parse(args)?; + self.charge_host_function_call( + &host_function_costs.generic_hash, + [ + in_ptr, + in_size, + hash_algo_type_ptr, + hash_algo_type_size, + out_ptr, + out_size, + ], + )?; let hash_algo_type = self.t_from_mem(hash_algo_type_ptr, hash_algo_type_size)?; let digest = self.checked_memory_slice(in_ptr as usize, in_size as usize, |input| { diff --git a/execution_engine/src/shared/host_function_costs.rs b/execution_engine/src/shared/host_function_costs.rs index 724fff837b..2548644df3 100644 --- a/execution_engine/src/shared/host_function_costs.rs +++ b/execution_engine/src/shared/host_function_costs.rs @@ -288,6 +288,8 @@ pub struct HostFunctionCosts { pub random_bytes: HostFunction<[Cost; 2]>, /// Cost of calling the `enable_contract_version` host function. pub enable_contract_version: HostFunction<[Cost; 4]>, + /// Cost of calling the `generic_hash` host function. + pub generic_hash: HostFunction<[Cost; 6]>, } impl Default for HostFunctionCosts { @@ -420,6 +422,7 @@ impl Default for HostFunctionCosts { blake2b: HostFunction::default(), random_bytes: HostFunction::default(), enable_contract_version: HostFunction::default(), + generic_hash: HostFunction::default(), } } } @@ -471,6 +474,7 @@ impl ToBytes for HostFunctionCosts { ret.append(&mut self.blake2b.to_bytes()?); ret.append(&mut self.random_bytes.to_bytes()?); ret.append(&mut self.enable_contract_version.to_bytes()?); + ret.append(&mut self.generic_hash.to_bytes()?); Ok(ret) } @@ -519,6 +523,7 @@ impl ToBytes for HostFunctionCosts { + self.blake2b.serialized_length() + self.random_bytes.serialized_length() + self.enable_contract_version.serialized_length() + + self.generic_hash.serialized_length() } } @@ -568,6 +573,7 @@ impl FromBytes for HostFunctionCosts { let (blake2b, rem) = FromBytes::from_bytes(rem)?; let (random_bytes, rem) = FromBytes::from_bytes(rem)?; let (enable_contract_version, rem) = FromBytes::from_bytes(rem)?; + let (generic_hash, rem) = FromBytes::from_bytes(rem)?; Ok(( HostFunctionCosts { read_value, @@ -614,6 +620,7 @@ impl FromBytes for HostFunctionCosts { blake2b, random_bytes, enable_contract_version, + generic_hash, }, rem, )) @@ -667,6 +674,7 @@ impl Distribution for Standard { blake2b: rng.gen(), random_bytes: rng.gen(), enable_contract_version: rng.gen(), + generic_hash: rng.gen(), } } } @@ -728,6 +736,7 @@ pub mod gens { blake2b in host_function_cost_arb(), random_bytes in host_function_cost_arb(), enable_contract_version in host_function_cost_arb(), + generic_hash in host_function_cost_arb(), ) -> HostFunctionCosts { HostFunctionCosts { read_value, @@ -774,6 +783,7 @@ pub mod gens { blake2b, random_bytes, enable_contract_version, + generic_hash, } } } diff --git a/execution_engine_testing/tests/src/test/storage_costs.rs b/execution_engine_testing/tests/src/test/storage_costs.rs index 15d9191844..83a1640c6c 100644 --- a/execution_engine_testing/tests/src/test/storage_costs.rs +++ b/execution_engine_testing/tests/src/test/storage_costs.rs @@ -139,6 +139,7 @@ static NEW_HOST_FUNCTION_COSTS: Lazy = Lazy::new(|| HostFunct blake2b: HostFunction::fixed(0), random_bytes: HostFunction::fixed(0), enable_contract_version: HostFunction::fixed(0), + generic_hash: HostFunction::fixed(0), }); static STORAGE_COSTS_ONLY: Lazy = Lazy::new(|| { WasmConfig::new( diff --git a/execution_engine_testing/tests/src/test/system_costs.rs b/execution_engine_testing/tests/src/test/system_costs.rs index 3644035978..dfd3ce6ebb 100644 --- a/execution_engine_testing/tests/src/test/system_costs.rs +++ b/execution_engine_testing/tests/src/test/system_costs.rs @@ -979,6 +979,7 @@ fn should_verify_wasm_add_bid_wasm_cost_is_not_recursive() { blake2b: HostFunction::fixed(0), random_bytes: HostFunction::fixed(0), enable_contract_version: HostFunction::fixed(0), + generic_hash: HostFunction::fixed(0), }; let new_wasm_config = WasmConfig::new( diff --git a/node/src/types/chainspec.rs b/node/src/types/chainspec.rs index c04ea4c1cc..bdf14700ee 100644 --- a/node/src/types/chainspec.rs +++ b/node/src/types/chainspec.rs @@ -363,6 +363,7 @@ mod tests { blake2b: HostFunction::new(133, [0, 1, 2, 3]), random_bytes: HostFunction::new(123, [0, 1]), enable_contract_version: HostFunction::new(142, [0, 1, 2, 3]), + generic_hash: HostFunction::new(152, [0, 1, 2, 3, 4, 5]), }); static EXPECTED_GENESIS_WASM_COSTS: Lazy = Lazy::new(|| { WasmConfig::new( diff --git a/resources/local/chainspec.toml.in b/resources/local/chainspec.toml.in index 5b7779c4b2..db325c42c8 100644 --- a/resources/local/chainspec.toml.in +++ b/resources/local/chainspec.toml.in @@ -243,6 +243,7 @@ transfer_to_account = { cost = 2_500_000_000, arguments = [0, 0, 0, 0, 0, 0, 0] update_associated_key = { cost = 4_200, arguments = [0, 0, 0] } write = { cost = 14_000, arguments = [0, 0, 0, 980] } dictionary_put = { cost = 9_500, arguments = [0, 1_800, 0, 520] } +generic_hash = { cost = 200, arguments = [0, 0, 0, 0, 0, 0] } [system_costs] wasmless_transfer_cost = 100_000_000 diff --git a/resources/production/chainspec.toml b/resources/production/chainspec.toml index d0aaea91fb..2d3c75b467 100644 --- a/resources/production/chainspec.toml +++ b/resources/production/chainspec.toml @@ -254,6 +254,7 @@ update_associated_key = { cost = 4_200, arguments = [0, 0, 0] } write = { cost = 14_000, arguments = [0, 0, 0, 980] } dictionary_put = { cost = 9_500, arguments = [0, 1_800, 0, 520] } enable_contract_version = { cost = 200, arguments = [0, 0, 0, 0] } +generic_hash = { cost = 200, arguments = [0, 0, 0, 0, 0, 0] } [system_costs] wasmless_transfer_cost = 100_000_000 diff --git a/resources/test/valid/0_9_0/chainspec.toml b/resources/test/valid/0_9_0/chainspec.toml index 31946df79c..36f0896717 100644 --- a/resources/test/valid/0_9_0/chainspec.toml +++ b/resources/test/valid/0_9_0/chainspec.toml @@ -141,6 +141,7 @@ update_associated_key = { cost = 139, arguments = [0, 1, 2] } write = { cost = 140, arguments = [0, 1, 0, 2] } dictionary_put = { cost = 141, arguments = [0, 1, 2, 3] } enable_contract_version = { cost = 142, arguments = [0, 1, 2, 3] } +generic_hash = { cost = 152, arguments = [0, 1, 2, 3, 4, 5] } [system_costs] wasmless_transfer_cost = 100_000_000 diff --git a/resources/test/valid/0_9_0_unordered/chainspec.toml b/resources/test/valid/0_9_0_unordered/chainspec.toml index 5e62721938..c438c21a17 100644 --- a/resources/test/valid/0_9_0_unordered/chainspec.toml +++ b/resources/test/valid/0_9_0_unordered/chainspec.toml @@ -139,6 +139,7 @@ update_associated_key = { cost = 139, arguments = [0, 1, 2] } write = { cost = 140, arguments = [0, 1, 0, 2] } dictionary_put = { cost = 141, arguments = [0, 1, 2, 3] } enable_contract_version = { cost = 142, arguments = [0, 1, 2, 3] } +generic_hash = { cost = 152, arguments = [0, 1, 2, 3, 4, 5] } [system_costs] wasmless_transfer_cost = 100_000_000 diff --git a/resources/test/valid/1_0_0/chainspec.toml b/resources/test/valid/1_0_0/chainspec.toml index 37e9c6dd57..aa4d387e30 100644 --- a/resources/test/valid/1_0_0/chainspec.toml +++ b/resources/test/valid/1_0_0/chainspec.toml @@ -142,6 +142,7 @@ update_associated_key = { cost = 139, arguments = [0, 1, 2] } write = { cost = 140, arguments = [0, 1, 0, 2] } dictionary_put = { cost = 141, arguments = [0, 1, 2, 3] } enable_contract_version = { cost = 142, arguments = [0, 1, 2, 3] } +generic_hash = { cost = 152, arguments = [0, 1, 2, 3, 4, 5] } [system_costs] wasmless_transfer_cost = 100_000_000 From f27876d3722efd84c624163f0934e129726b7c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Mon, 13 Nov 2023 16:45:51 +0100 Subject: [PATCH 06/15] Introduce `casper_generic_hash` FFI in contract. --- smart_contracts/contract/src/ext_ffi.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/smart_contracts/contract/src/ext_ffi.rs b/smart_contracts/contract/src/ext_ffi.rs index 3bdb478bb9..6654ca2e5c 100644 --- a/smart_contracts/contract/src/ext_ffi.rs +++ b/smart_contracts/contract/src/ext_ffi.rs @@ -805,4 +805,22 @@ extern "C" { contract_hash_ptr: *const u8, contract_hash_size: usize, ) -> i32; + /// Computes digest hash, using provided algorithm type. + /// + /// # Arguments + /// + /// * `in_ptr` - pointer to the location where argument bytes will be copied from the host side + /// * `in_size` - size of output pointer + /// * `hash_algo_type_ptr` - pointer to serialized hash type + /// * `hash_algo_type_size` - size of hash type in serialized form + /// * `out_ptr` - pointer to the location where argument bytes will be copied to the host side + /// * `out_size` - size of output pointer + pub fn casper_generic_hash( + in_ptr: *const u8, + in_size: usize, + hash_algo_type_ptr: *const u8, + hash_algo_type_size: usize, + out_ptr: *const u8, + out_size: usize, + ) -> i32; } From ef78d5986021f8a4c666422a5216a11529533e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Mon, 13 Nov 2023 16:48:30 +0100 Subject: [PATCH 07/15] Add `crypto::generic_hash()` to contract API. Note: new module `crypto` was introduced. --- .../contract/src/contract_api/crypto.rs | 25 +++++++++++++++++++ .../contract/src/contract_api/mod.rs | 1 + 2 files changed, 26 insertions(+) create mode 100644 smart_contracts/contract/src/contract_api/crypto.rs diff --git a/smart_contracts/contract/src/contract_api/crypto.rs b/smart_contracts/contract/src/contract_api/crypto.rs new file mode 100644 index 0000000000..88327cc90d --- /dev/null +++ b/smart_contracts/contract/src/contract_api/crypto.rs @@ -0,0 +1,25 @@ +//! Functions with cryptographic utils. + +use casper_types::{api_error, HashAlgoType, BLAKE2B_DIGEST_LENGTH}; + +use crate::{ext_ffi, unwrap_or_revert::UnwrapOrRevert}; + +/// Computes digest hash, using provided algorithm type. +pub fn generic_hash>(input: T, algo: HashAlgoType) -> [u8; 32] { + let mut ret = [0; 32]; + + let algo_ptr = &algo as *const _ as *const u8; + + let result = unsafe { + ext_ffi::casper_generic_hash( + input.as_ref().as_ptr(), + input.as_ref().len(), + algo_ptr, + 1, // HashAlgoType is just one byte (u8). + ret.as_mut_ptr(), + BLAKE2B_DIGEST_LENGTH, + ) + }; + api_error::result_from(result).unwrap_or_revert(); + ret +} diff --git a/smart_contracts/contract/src/contract_api/mod.rs b/smart_contracts/contract/src/contract_api/mod.rs index 524a8f0705..f964993488 100644 --- a/smart_contracts/contract/src/contract_api/mod.rs +++ b/smart_contracts/contract/src/contract_api/mod.rs @@ -1,6 +1,7 @@ //! Contains support for writing smart contracts. pub mod account; +pub mod crypto; pub mod runtime; pub mod storage; pub mod system; From 5d400ec1bbb328d111506f55c19aa9504c329115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Mon, 13 Nov 2023 16:49:56 +0100 Subject: [PATCH 08/15] Update contract API changelog. --- smart_contracts/contract/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/smart_contracts/contract/CHANGELOG.md b/smart_contracts/contract/CHANGELOG.md index cb2f87e719..7b62eb2108 100644 --- a/smart_contracts/contract/CHANGELOG.md +++ b/smart_contracts/contract/CHANGELOG.md @@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file. The format ### Added * Add `storage::enable_contract_version` for enabling a specific version of a contract. +* Add `crypto::generic_hash` with blake2b and blake3 support. From 8bd8fbb7893e74846c65468fe1518646eca62071 Mon Sep 17 00:00:00 2001 From: koxu1996 Date: Wed, 15 Nov 2023 18:53:18 +0100 Subject: [PATCH 09/15] Use default features for `blake3` to utilize SIMD. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Results in better performance. See https://github.com/BLAKE3-team/BLAKE3/blob/92e4cd71be48fdf9a79e88ef37b8f415ec5ac210/Cargo.toml#L21-L26 Co-authored-by: Michał Papierski --- execution_engine/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution_engine/Cargo.toml b/execution_engine/Cargo.toml index 15d51c6e3f..ed826cdcc5 100644 --- a/execution_engine/Cargo.toml +++ b/execution_engine/Cargo.toml @@ -14,7 +14,7 @@ license = "Apache-2.0" anyhow = "1.0.33" base16 = "0.2.1" bincode = "1.3.1" -blake3 = { version = "1.5.0", default-features = false } +blake3 = "1.5.0" casper-hashing = { version = "2.0.0", path = "../hashing" } casper-types = { version = "3.0.0", path = "../types", default-features = false, features = ["datasize", "gens", "json-schema"] } casper-wasm = { version = "0.46.0", default-features = false } From d38140f3d88806385ae069880735f67dccf57a02 Mon Sep 17 00:00:00 2001 From: koxu1996 Date: Wed, 15 Nov 2023 18:55:29 +0100 Subject: [PATCH 10/15] Update `HashAlgoType` description. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improved type documentation. Co-authored-by: Michał Papierski --- types/src/crypto.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/src/crypto.rs b/types/src/crypto.rs index 1418602037..ed39b4c12f 100644 --- a/types/src/crypto.rs +++ b/types/src/crypto.rs @@ -44,7 +44,7 @@ pub fn blake2b>(data: T) -> [u8; BLAKE2B_DIGEST_LENGTH] { result } -/// HashAlgoType +/// A type of hashing algorithm. #[repr(u8)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "datasize", derive(DataSize))] From 2c70942af76e7e46b5c9d1fa74445d8d5131e555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Wed, 15 Nov 2023 20:47:13 +0100 Subject: [PATCH 11/15] Pass `hash_algo_type` directly by value. We can avoid working with bytes and serialization, as `HashAlgoType` enum is represented by simple `u8`. --- .../src/core/runtime/externals.rs | 23 +++----- .../src/shared/host_function_costs.rs | 2 +- node/src/types/chainspec.rs | 2 +- resources/local/chainspec.toml.in | 2 +- resources/production/chainspec.toml | 2 +- resources/test/valid/0_9_0/chainspec.toml | 2 +- .../test/valid/0_9_0_unordered/chainspec.toml | 2 +- resources/test/valid/1_0_0/chainspec.toml | 2 +- .../contract/src/contract_api/crypto.rs | 5 +- smart_contracts/contract/src/ext_ffi.rs | 6 +-- types/src/crypto.rs | 53 ++++--------------- 11 files changed, 27 insertions(+), 74 deletions(-) diff --git a/execution_engine/src/core/runtime/externals.rs b/execution_engine/src/core/runtime/externals.rs index 0161510e6c..fea3895f74 100644 --- a/execution_engine/src/core/runtime/externals.rs +++ b/execution_engine/src/core/runtime/externals.rs @@ -1102,24 +1102,17 @@ where FunctionIndex::GenericHash => { // args(0) = pointer to input in Wasm memory // args(1) = size of input in Wasm memory - // args(2) = pointer to hash type pointer in Wasm memory - // args(3) = size of hash type in Wasm memory - // args(4) = pointer to output pointer in Wasm memory - // args(5) = size of output - let (in_ptr, in_size, hash_algo_type_ptr, hash_algo_type_size, out_ptr, out_size) = - Args::parse(args)?; + // args(2) = integer representation of HashAlgoType enum variant + // args(3) = pointer to output pointer in Wasm memory + // args(4) = size of output + let (in_ptr, in_size, hash_algo_type, out_ptr, out_size) = Args::parse(args)?; self.charge_host_function_call( &host_function_costs.generic_hash, - [ - in_ptr, - in_size, - hash_algo_type_ptr, - hash_algo_type_size, - out_ptr, - out_size, - ], + [in_ptr, in_size, hash_algo_type, out_ptr, out_size], )?; - let hash_algo_type = self.t_from_mem(hash_algo_type_ptr, hash_algo_type_size)?; + let hash_algo_type = + HashAlgoType::try_from(hash_algo_type as u8).map_err(|e| Error::from(e))?; + let digest = self.checked_memory_slice(in_ptr as usize, in_size as usize, |input| { match hash_algo_type { diff --git a/execution_engine/src/shared/host_function_costs.rs b/execution_engine/src/shared/host_function_costs.rs index 2548644df3..4f88074514 100644 --- a/execution_engine/src/shared/host_function_costs.rs +++ b/execution_engine/src/shared/host_function_costs.rs @@ -289,7 +289,7 @@ pub struct HostFunctionCosts { /// Cost of calling the `enable_contract_version` host function. pub enable_contract_version: HostFunction<[Cost; 4]>, /// Cost of calling the `generic_hash` host function. - pub generic_hash: HostFunction<[Cost; 6]>, + pub generic_hash: HostFunction<[Cost; 5]>, } impl Default for HostFunctionCosts { diff --git a/node/src/types/chainspec.rs b/node/src/types/chainspec.rs index bdf14700ee..e58bcf68d6 100644 --- a/node/src/types/chainspec.rs +++ b/node/src/types/chainspec.rs @@ -363,7 +363,7 @@ mod tests { blake2b: HostFunction::new(133, [0, 1, 2, 3]), random_bytes: HostFunction::new(123, [0, 1]), enable_contract_version: HostFunction::new(142, [0, 1, 2, 3]), - generic_hash: HostFunction::new(152, [0, 1, 2, 3, 4, 5]), + generic_hash: HostFunction::new(152, [0, 1, 2, 3, 4]), }); static EXPECTED_GENESIS_WASM_COSTS: Lazy = Lazy::new(|| { WasmConfig::new( diff --git a/resources/local/chainspec.toml.in b/resources/local/chainspec.toml.in index db325c42c8..c1c49a8cb4 100644 --- a/resources/local/chainspec.toml.in +++ b/resources/local/chainspec.toml.in @@ -243,7 +243,7 @@ transfer_to_account = { cost = 2_500_000_000, arguments = [0, 0, 0, 0, 0, 0, 0] update_associated_key = { cost = 4_200, arguments = [0, 0, 0] } write = { cost = 14_000, arguments = [0, 0, 0, 980] } dictionary_put = { cost = 9_500, arguments = [0, 1_800, 0, 520] } -generic_hash = { cost = 200, arguments = [0, 0, 0, 0, 0, 0] } +generic_hash = { cost = 200, arguments = [0, 0, 0, 0, 0] } [system_costs] wasmless_transfer_cost = 100_000_000 diff --git a/resources/production/chainspec.toml b/resources/production/chainspec.toml index 2d3c75b467..3173028dd9 100644 --- a/resources/production/chainspec.toml +++ b/resources/production/chainspec.toml @@ -254,7 +254,7 @@ update_associated_key = { cost = 4_200, arguments = [0, 0, 0] } write = { cost = 14_000, arguments = [0, 0, 0, 980] } dictionary_put = { cost = 9_500, arguments = [0, 1_800, 0, 520] } enable_contract_version = { cost = 200, arguments = [0, 0, 0, 0] } -generic_hash = { cost = 200, arguments = [0, 0, 0, 0, 0, 0] } +generic_hash = { cost = 200, arguments = [0, 0, 0, 0, 0] } [system_costs] wasmless_transfer_cost = 100_000_000 diff --git a/resources/test/valid/0_9_0/chainspec.toml b/resources/test/valid/0_9_0/chainspec.toml index 36f0896717..bda18e0dd0 100644 --- a/resources/test/valid/0_9_0/chainspec.toml +++ b/resources/test/valid/0_9_0/chainspec.toml @@ -141,7 +141,7 @@ update_associated_key = { cost = 139, arguments = [0, 1, 2] } write = { cost = 140, arguments = [0, 1, 0, 2] } dictionary_put = { cost = 141, arguments = [0, 1, 2, 3] } enable_contract_version = { cost = 142, arguments = [0, 1, 2, 3] } -generic_hash = { cost = 152, arguments = [0, 1, 2, 3, 4, 5] } +generic_hash = { cost = 152, arguments = [0, 1, 2, 3, 4] } [system_costs] wasmless_transfer_cost = 100_000_000 diff --git a/resources/test/valid/0_9_0_unordered/chainspec.toml b/resources/test/valid/0_9_0_unordered/chainspec.toml index c438c21a17..d1e25b9013 100644 --- a/resources/test/valid/0_9_0_unordered/chainspec.toml +++ b/resources/test/valid/0_9_0_unordered/chainspec.toml @@ -139,7 +139,7 @@ update_associated_key = { cost = 139, arguments = [0, 1, 2] } write = { cost = 140, arguments = [0, 1, 0, 2] } dictionary_put = { cost = 141, arguments = [0, 1, 2, 3] } enable_contract_version = { cost = 142, arguments = [0, 1, 2, 3] } -generic_hash = { cost = 152, arguments = [0, 1, 2, 3, 4, 5] } +generic_hash = { cost = 152, arguments = [0, 1, 2, 3, 4] } [system_costs] wasmless_transfer_cost = 100_000_000 diff --git a/resources/test/valid/1_0_0/chainspec.toml b/resources/test/valid/1_0_0/chainspec.toml index aa4d387e30..07473acd2a 100644 --- a/resources/test/valid/1_0_0/chainspec.toml +++ b/resources/test/valid/1_0_0/chainspec.toml @@ -142,7 +142,7 @@ update_associated_key = { cost = 139, arguments = [0, 1, 2] } write = { cost = 140, arguments = [0, 1, 0, 2] } dictionary_put = { cost = 141, arguments = [0, 1, 2, 3] } enable_contract_version = { cost = 142, arguments = [0, 1, 2, 3] } -generic_hash = { cost = 152, arguments = [0, 1, 2, 3, 4, 5] } +generic_hash = { cost = 152, arguments = [0, 1, 2, 3, 4] } [system_costs] wasmless_transfer_cost = 100_000_000 diff --git a/smart_contracts/contract/src/contract_api/crypto.rs b/smart_contracts/contract/src/contract_api/crypto.rs index 88327cc90d..8fea63c5f8 100644 --- a/smart_contracts/contract/src/contract_api/crypto.rs +++ b/smart_contracts/contract/src/contract_api/crypto.rs @@ -8,14 +8,11 @@ use crate::{ext_ffi, unwrap_or_revert::UnwrapOrRevert}; pub fn generic_hash>(input: T, algo: HashAlgoType) -> [u8; 32] { let mut ret = [0; 32]; - let algo_ptr = &algo as *const _ as *const u8; - let result = unsafe { ext_ffi::casper_generic_hash( input.as_ref().as_ptr(), input.as_ref().len(), - algo_ptr, - 1, // HashAlgoType is just one byte (u8). + algo as u8, ret.as_mut_ptr(), BLAKE2B_DIGEST_LENGTH, ) diff --git a/smart_contracts/contract/src/ext_ffi.rs b/smart_contracts/contract/src/ext_ffi.rs index 6654ca2e5c..d790580d1b 100644 --- a/smart_contracts/contract/src/ext_ffi.rs +++ b/smart_contracts/contract/src/ext_ffi.rs @@ -811,15 +811,13 @@ extern "C" { /// /// * `in_ptr` - pointer to the location where argument bytes will be copied from the host side /// * `in_size` - size of output pointer - /// * `hash_algo_type_ptr` - pointer to serialized hash type - /// * `hash_algo_type_size` - size of hash type in serialized form + /// * `hash_algo_type` - integer representation of HashAlgoType enum variant /// * `out_ptr` - pointer to the location where argument bytes will be copied to the host side /// * `out_size` - size of output pointer pub fn casper_generic_hash( in_ptr: *const u8, in_size: usize, - hash_algo_type_ptr: *const u8, - hash_algo_type_size: usize, + hash_algo_type: u8, out_ptr: *const u8, out_size: usize, ) -> i32; diff --git a/types/src/crypto.rs b/types/src/crypto.rs index ed39b4c12f..1fcd340a32 100644 --- a/types/src/crypto.rs +++ b/types/src/crypto.rs @@ -3,20 +3,17 @@ mod asymmetric_key; mod error; -use alloc::vec::Vec; +use core::convert::TryFrom; use blake2::{ digest::{Update, VariableOutput}, VarBlake2b, }; -#[cfg(feature = "datasize")] -use datasize::DataSize; -#[cfg(feature = "json-schema")] -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use num::FromPrimitive; +use num_derive::FromPrimitive; -use crate::bytesrepr::{self, FromBytes, ToBytes}; +use crate::bytesrepr; use crate::key::BLAKE2B_DIGEST_LENGTH; #[cfg(any(feature = "std", test))] @@ -46,9 +43,7 @@ pub fn blake2b>(data: T) -> [u8; BLAKE2B_DIGEST_LENGTH] { /// A type of hashing algorithm. #[repr(u8)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[cfg_attr(feature = "json-schema", derive(JsonSchema))] +#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive)] pub enum HashAlgoType { /// Blake2b Blake2b = 0, @@ -56,40 +51,10 @@ pub enum HashAlgoType { Blake3 = 1, } -impl ToBytes for HashAlgoType { - fn to_bytes(&self) -> Result, bytesrepr::Error> { - (*self as u8).to_bytes() - } - - fn serialized_length(&self) -> usize { - 1 - } - - fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { - writer.push(*self as u8); - Ok(()) - } -} - -impl FromBytes for HashAlgoType { - fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (value, bytes) = u8::from_bytes(bytes)?; - match value { - 0 => Ok((HashAlgoType::Blake2b, bytes)), - 1 => Ok((HashAlgoType::Blake3, bytes)), - _ => Err(bytesrepr::Error::Formatting), - } - } -} +impl TryFrom for HashAlgoType { + type Error = bytesrepr::Error; -// Statics representing the discriminant values of the enum -static VARIANT_BLAKE_2B: u8 = HashAlgoType::Blake2b as u8; -static VARIANT_BLAKE_3: u8 = HashAlgoType::Blake3 as u8; -impl AsRef for HashAlgoType { - fn as_ref(&self) -> &u8 { - match self { - HashAlgoType::Blake2b => &VARIANT_BLAKE_2B, - HashAlgoType::Blake3 => &VARIANT_BLAKE_3, - } + fn try_from(value: u8) -> Result { + FromPrimitive::from_u8(value).ok_or(bytesrepr::Error::Formatting) } } From 4ec1f31ff05a699300b7007e1c61ed68164d37d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Thu, 16 Nov 2023 11:32:48 +0100 Subject: [PATCH 12/15] Fix signature (arguments length) in WASM resolver. After passing `hash_algo_type` directly, we have one less parameter. --- execution_engine/src/core/resolvers/v1_resolver.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution_engine/src/core/resolvers/v1_resolver.rs b/execution_engine/src/core/resolvers/v1_resolver.rs index b370035203..2a93f51607 100644 --- a/execution_engine/src/core/resolvers/v1_resolver.rs +++ b/execution_engine/src/core/resolvers/v1_resolver.rs @@ -246,7 +246,7 @@ impl ModuleImportResolver for RuntimeModuleImportResolver { FunctionIndex::EnableContractVersion.into(), ), "casper_generic_hash" => FuncInstance::alloc_host( - Signature::new(&[ValueType::I32; 6][..], Some(ValueType::I32)), + Signature::new(&[ValueType::I32; 5][..], Some(ValueType::I32)), FunctionIndex::GenericHash.into(), ), _ => { From 85885d0bb5e75842fc4c282317acd5db1ab6a20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Mon, 20 Nov 2023 15:39:26 +0100 Subject: [PATCH 13/15] Change error type for `HashAlgoType::try_from`. It can be simple `()` instead of `bytesrepr::Error::Formatting` (the one commonly used for serialization related issues). --- types/src/crypto.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/types/src/crypto.rs b/types/src/crypto.rs index 1fcd340a32..756a136430 100644 --- a/types/src/crypto.rs +++ b/types/src/crypto.rs @@ -13,8 +13,6 @@ use blake2::{ use num::FromPrimitive; use num_derive::FromPrimitive; -use crate::bytesrepr; - use crate::key::BLAKE2B_DIGEST_LENGTH; #[cfg(any(feature = "std", test))] pub use asymmetric_key::generate_ed25519_keypair; @@ -52,9 +50,9 @@ pub enum HashAlgoType { } impl TryFrom for HashAlgoType { - type Error = bytesrepr::Error; + type Error = (); - fn try_from(value: u8) -> Result { - FromPrimitive::from_u8(value).ok_or(bytesrepr::Error::Formatting) + fn try_from(value: u8) -> Result { + FromPrimitive::from_u8(value).ok_or(()) } } From d064e2c90148a5e13b3ecaf1f98a1f3646e04ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Mon, 20 Nov 2023 15:44:45 +0100 Subject: [PATCH 14/15] Return `ApiError::InvalidArgument` for invalid `hash_algo_type`. Previous deserializiation error was confusing. NOTE: The code seems too complex for such a basic thing like returning API error. --- execution_engine/src/core/runtime/externals.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/execution_engine/src/core/runtime/externals.rs b/execution_engine/src/core/runtime/externals.rs index fea3895f74..e2e683311e 100644 --- a/execution_engine/src/core/runtime/externals.rs +++ b/execution_engine/src/core/runtime/externals.rs @@ -1110,8 +1110,14 @@ where &host_function_costs.generic_hash, [in_ptr, in_size, hash_algo_type, out_ptr, out_size], )?; - let hash_algo_type = - HashAlgoType::try_from(hash_algo_type as u8).map_err(|e| Error::from(e))?; + let hash_algo_type = match HashAlgoType::try_from(hash_algo_type as u8) { + Ok(v) => v, + Err(_e) => { + return Ok(Some(RuntimeValue::I32(api_error::i32_from(Err( + ApiError::InvalidArgument, + ))))) + } + }; let digest = self.checked_memory_slice(in_ptr as usize, in_size as usize, |input| { From 4b855684ebb3345bee774a4869444ae1991aff83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Mon, 20 Nov 2023 16:42:44 +0100 Subject: [PATCH 15/15] Fix formatting. Detected with `cargo fmt`. --- execution_engine/src/core/runtime/externals.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/execution_engine/src/core/runtime/externals.rs b/execution_engine/src/core/runtime/externals.rs index e2e683311e..2f94e2f858 100644 --- a/execution_engine/src/core/runtime/externals.rs +++ b/execution_engine/src/core/runtime/externals.rs @@ -15,8 +15,7 @@ use casper_types::{ use super::{args::Args, Error, Runtime}; use crate::{ - core::resolvers::v1_function_index::FunctionIndex, - core::runtime::crypto as core_crypto, + core::{resolvers::v1_function_index::FunctionIndex, runtime::crypto as core_crypto}, shared::host_function_costs::{Cost, HostFunction, DEFAULT_HOST_FUNCTION_NEW_DICTIONARY}, storage::global_state::StateReader, };