diff --git a/Cargo.lock b/Cargo.lock index 8214a6fd99..374a50c533 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,6 +197,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.4" @@ -378,6 +384,15 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "blake2b" version = "0.8.0" @@ -386,6 +401,19 @@ dependencies = [ "casper-types", ] +[[package]] +name = "blake3" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if 1.0.0", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -509,7 +537,7 @@ dependencies = [ name = "casper-engine-test-support" version = "7.0.1" dependencies = [ - "blake2", + "blake2 0.9.2", "casper-execution-engine", "casper-storage", "casper-types", @@ -571,6 +599,8 @@ dependencies = [ "assert_matches", "base16", "bincode", + "blake2 0.10.6", + "blake3", "casper-storage", "casper-types", "casper-wasm", @@ -599,6 +629,7 @@ dependencies = [ "serde", "serde_bytes", "serde_json", + "sha2", "strum 0.24.1", "tempfile", "thiserror", @@ -742,7 +773,7 @@ dependencies = [ "base64 0.13.1", "bincode", "bitflags 1.3.2", - "blake2", + "blake2 0.9.2", "criterion 0.3.6", "datasize", "derive_more", @@ -866,11 +897,11 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.82" +version = "1.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +checksum = "3bbb537bb4a30b90362caddba8f360c0a56bc13d3a5570028e7197204cb54a17" dependencies = [ - "libc", + "shlex", ] [[package]] @@ -1043,6 +1074,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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "contract-context" version = "0.1.0" @@ -2328,6 +2365,14 @@ dependencies = [ "zeroize", ] +[[package]] +name = "generic-hash" +version = "0.1.0" +dependencies = [ + "casper-contract", + "casper-types", +] + [[package]] name = "get-arg" version = "0.1.0" @@ -5356,9 +5401,9 @@ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if 1.0.0", "cpufeatures", diff --git a/binary_port/src/response_type.rs b/binary_port/src/response_type.rs index 6be59c9b3e..3ae438e095 100644 --- a/binary_port/src/response_type.rs +++ b/binary_port/src/response_type.rs @@ -145,7 +145,7 @@ impl ResponseType { #[cfg(test)] pub(crate) fn random(rng: &mut TestRng) -> Self { - Self::try_from(rng.gen_range(0..45)).unwrap() + Self::try_from(rng.gen_range(0..=43)).unwrap() } } diff --git a/execution_engine/Cargo.toml b/execution_engine/Cargo.toml index dedfcc862d..adb867fb0f 100644 --- a/execution_engine/Cargo.toml +++ b/execution_engine/Cargo.toml @@ -14,6 +14,9 @@ license = "Apache-2.0" anyhow = "1.0.33" base16 = "0.2.1" bincode = "1.3.1" +blake2 = { version = "0.10.6", default-features = false } +blake3 = { version = "1.5.0", default-features = false } +sha2 = { version = "0.10.8", default-features = false } casper-storage = { version = "2.0.0", path = "../storage" } casper-types = { version = "5.0.0", path = "../types", default-features = false, features = ["datasize", "gens", "json-schema", "std"] } casper-wasm = { version = "0.46.0", default-features = false, features = ["sign_ext"] } diff --git a/execution_engine/src/resolvers/v1_function_index.rs b/execution_engine/src/resolvers/v1_function_index.rs index fb6d1049b8..9a6d5b64b9 100644 --- a/execution_engine/src/resolvers/v1_function_index.rs +++ b/execution_engine/src/resolvers/v1_function_index.rs @@ -61,6 +61,7 @@ pub(crate) enum FunctionIndex { EmitMessage, LoadCallerInformation, GetBlockInfoIndex, + GenericHash, } impl From for usize { diff --git a/execution_engine/src/resolvers/v1_resolver.rs b/execution_engine/src/resolvers/v1_resolver.rs index 5aa241d0df..e8cca9d457 100644 --- a/execution_engine/src/resolvers/v1_resolver.rs +++ b/execution_engine/src/resolvers/v1_resolver.rs @@ -257,6 +257,10 @@ impl ModuleImportResolver for RuntimeModuleImportResolver { Signature::new(&[ValueType::I32; 2][..], None), FunctionIndex::GetBlockInfoIndex.into(), ), + "casper_generic_hash" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 5][..], 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/runtime/auction_internal.rs b/execution_engine/src/runtime/auction_internal.rs index e2f0141eb1..77ccd390a1 100644 --- a/execution_engine/src/runtime/auction_internal.rs +++ b/execution_engine/src/runtime/auction_internal.rs @@ -11,7 +11,6 @@ use casper_storage::{ use casper_types::{ account::AccountHash, bytesrepr::{FromBytes, ToBytes}, - crypto, system::{ auction::{BidAddr, BidKind, EraInfo, Error, UnbondingPurse}, mint, @@ -19,7 +18,7 @@ use casper_types::{ CLTyped, CLValue, Key, KeyTag, PublicKey, RuntimeArgs, StoredValue, URef, U512, }; -use super::Runtime; +use super::{cryptography, Runtime}; use crate::execution::ExecError; impl From for Option { @@ -209,8 +208,11 @@ where R: StateReader, { fn unbond(&mut self, unbonding_purse: &UnbondingPurse) -> Result<(), Error> { - let account_hash = - AccountHash::from_public_key(unbonding_purse.unbonder_public_key(), crypto::blake2b); + let account_hash = AccountHash::from_public_key( + unbonding_purse.unbonder_public_key(), + cryptography::blake2b, + ); + let maybe_value = self .context .read_gs_unsafe(&Key::Account(account_hash)) diff --git a/execution_engine/src/runtime/cryptography.rs b/execution_engine/src/runtime/cryptography.rs new file mode 100644 index 0000000000..c846997d18 --- /dev/null +++ b/execution_engine/src/runtime/cryptography.rs @@ -0,0 +1,43 @@ +//! Cryptography module containing hashing functions used internally +//! by the execution engine + +use blake2::{ + digest::{Update, VariableOutput}, + Blake2bVar, +}; +use sha2::{Digest, Sha256}; + +/// The number of bytes in a hash. +/// All hash functions in this module have a digest length of 32. +pub const DIGEST_LENGTH: usize = 32; + +/// The 32-byte digest blake2b hash function +pub fn blake2b>(data: T) -> [u8; DIGEST_LENGTH] { + let mut result = [0; DIGEST_LENGTH]; + // NOTE: Assumed safe as `BLAKE2B_DIGEST_LENGTH` is a valid value for a hasher + let mut hasher = Blake2bVar::new(DIGEST_LENGTH).expect("should create hasher"); + + hasher.update(data.as_ref()); + + // NOTE: This should never fail, because result is exactly DIGEST_LENGTH long + hasher.finalize_variable(&mut result).ok(); + + result +} + +/// The 32-byte digest blake3 hash function +pub fn blake3>(data: T) -> [u8; DIGEST_LENGTH] { + let mut result = [0; DIGEST_LENGTH]; + let mut hasher = blake3::Hasher::new(); + + hasher.update(data.as_ref()); + let hash = hasher.finalize(); + let hash_bytes: &[u8; DIGEST_LENGTH] = hash.as_bytes(); + result.copy_from_slice(hash_bytes); + result +} + +/// The 32-byte digest sha256 hash function +pub fn sha256>(data: T) -> [u8; DIGEST_LENGTH] { + Sha256::digest(data).into() +} diff --git a/execution_engine/src/runtime/externals.rs b/execution_engine/src/runtime/externals.rs index 8cf5cf04e4..c197382f6c 100644 --- a/execution_engine/src/runtime/externals.rs +++ b/execution_engine/src/runtime/externals.rs @@ -13,13 +13,13 @@ use casper_types::{ bytesrepr::{self, ToBytes}, contract_messages::MessageTopicOperation, contracts::ContractPackageHash, - crypto, AddressableEntityHash, ApiError, EntityVersion, Gas, Group, HostFunction, + AddressableEntityHash, ApiError, EntityVersion, Gas, Group, HashAlgorithm, HostFunction, HostFunctionCost, Key, PackageHash, PackageStatus, StoredValue, URef, U512, UREF_SERIALIZED_LENGTH, }; use super::{args::Args, ExecError, Runtime}; -use crate::resolvers::v1_function_index::FunctionIndex; +use crate::{resolvers::v1_function_index::FunctionIndex, runtime::cryptography}; impl<'a, R> Externals for Runtime<'a, R> where @@ -999,7 +999,7 @@ where )?; let digest = self.checked_memory_slice(in_ptr as usize, in_size as usize, |input| { - crypto::blake2b(input) + cryptography::blake2b(input) })?; let result = if digest.len() != out_size as usize { @@ -1291,6 +1291,54 @@ where self.get_block_info(field_idx, dest_ptr)?; Ok(None) } + + FunctionIndex::GenericHash => { + // args(0) = pointer to input in Wasm memory + // args(1) = size of input in Wasm memory + // args(2) = integer representation of HashAlgorithm 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, out_ptr, out_size], + )?; + let hash_algo_type = match HashAlgorithm::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| { + match hash_algo_type { + HashAlgorithm::Blake2b => cryptography::blake2b(input), + HashAlgorithm::Blake3 => cryptography::blake3(input), + HashAlgorithm::Sha256 => cryptography::sha256(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)))); + } + + if self.try_get_memory()?.set(out_ptr, &digest).is_err() { + return Ok(Some(RuntimeValue::I32( + u32::from(ApiError::HostBufferEmpty) as i32, + ))); + } + + Ok(Some(RuntimeValue::I32(0))) + } } } } diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index 0e5a2364c7..a43ae53cc7 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -1,6 +1,7 @@ //! This module contains executor state of the WASM code. mod args; mod auction_internal; +pub mod cryptography; mod externals; mod handle_payment_internal; mod host_function_flag; @@ -44,7 +45,6 @@ use casper_types::{ Message, MessageAddr, MessagePayload, MessageTopicOperation, MessageTopicSummary, }, contracts::ContractHash, - crypto, system::{ self, auction::{self, EraInfo}, @@ -1988,7 +1988,7 @@ where // Extend the previous topics with the newly added ones. for (new_topic, _) in topics_to_add { - let topic_name_hash = crypto::blake2b(new_topic.as_bytes()).into(); + let topic_name_hash = cryptography::blake2b(new_topic.as_bytes()).into(); if let Err(e) = previous_message_topics.add_topic(new_topic.as_str(), topic_name_hash) { return Ok(Err(e.into())); } @@ -3564,7 +3564,7 @@ where } fn add_message_topic(&mut self, topic_name: &str) -> Result, ExecError> { - let topic_hash = crypto::blake2b(topic_name).into(); + let topic_hash = cryptography::blake2b(topic_name).into(); self.context .add_message_topic(topic_name, topic_hash) @@ -3582,7 +3582,7 @@ where .as_entity_addr() .ok_or(ExecError::InvalidContext)?; - let topic_name_hash = crypto::blake2b(topic_name).into(); + let topic_name_hash = cryptography::blake2b(topic_name).into(); let topic_key = Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_name_hash)); // Check if the topic exists and get the summary. diff --git a/execution_engine_testing/tests/src/test/contract_api/generic_hash.rs b/execution_engine_testing/tests/src/test/contract_api/generic_hash.rs new file mode 100644 index 0000000000..2fcb5883c5 --- /dev/null +++ b/execution_engine_testing/tests/src/test/contract_api/generic_hash.rs @@ -0,0 +1,69 @@ +use casper_engine_test_support::{ + ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, LOCAL_GENESIS_REQUEST, +}; +use casper_types::{runtime_args, HashAlgorithm}; + +const GENERIC_HASH_WASM: &str = "generic_hash.wasm"; + +#[ignore] +#[test] +fn should_run_generic_hash_blake2() { + LmdbWasmTestBuilder::default() + .run_genesis(LOCAL_GENESIS_REQUEST.clone()) + .exec( + ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + GENERIC_HASH_WASM, + runtime_args! { + "data" => "blake2 hash test", + "algorithm" => HashAlgorithm::Blake2b as u8, + "expected" => [0x0A, 0x24, 0xA2, 0xDF, 0x30, 0x46, 0x1F, 0xA9, 0x69, 0x36, 0x67, 0x97, 0xE4, 0xD4, 0x30, 0xA1, 0x13, 0xC6, 0xCE, 0xE2, 0x78, 0xB5, 0xEF, 0x63, 0xBD, 0x5D, 0x00, 0xA0, 0xA6, 0x61, 0x1E, 0x29] + }, + ) + .build(), + ) + .expect_success() + .commit(); +} + +#[ignore] +#[test] +fn should_run_generic_hash_blake3() { + LmdbWasmTestBuilder::default() + .run_genesis(LOCAL_GENESIS_REQUEST.clone()) + .exec( + ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + GENERIC_HASH_WASM, + runtime_args! { + "data" => "blake3 hash test", + "algorithm" => HashAlgorithm::Blake3 as u8, + "expected" => [0x01, 0x65, 0x7D, 0x50, 0x0C, 0x51, 0x9B, 0xB6, 0x8D, 0x01, 0x26, 0x53, 0x66, 0xE2, 0x72, 0x2E, 0x1A, 0x05, 0x65, 0x2E, 0xD7, 0x0C, 0x77, 0xB0, 0x06, 0x80, 0xF8, 0xE8, 0x9E, 0xF9, 0x0F, 0xA1] + }, + ) + .build(), + ) + .expect_success() + .commit(); +} + +#[ignore] +#[test] +fn should_run_generic_hash_sha256() { + LmdbWasmTestBuilder::default() + .run_genesis(LOCAL_GENESIS_REQUEST.clone()) + .exec( + ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + GENERIC_HASH_WASM, + runtime_args! { + "data" => "sha256 hash test", + "algorithm" => HashAlgorithm::Sha256 as u8, + "expected" => [0x29, 0xD2, 0xC7, 0x7B, 0x39, 0x7F, 0xF6, 0x9E, 0x25, 0x0D, 0x81, 0xA3, 0xBA, 0xBB, 0x32, 0xDE, 0xFF, 0x3C, 0x2D, 0x06, 0xC9, 0x8E, 0x5E, 0x73, 0x60, 0x54, 0x3C, 0xE4, 0x91, 0xAC, 0x81, 0xCA] + }, + ) + .build(), + ) + .expect_success() + .commit(); +} diff --git a/execution_engine_testing/tests/src/test/contract_api/mod.rs b/execution_engine_testing/tests/src/test/contract_api/mod.rs index 935a4d9cf1..956694d4b6 100644 --- a/execution_engine_testing/tests/src/test/contract_api/mod.rs +++ b/execution_engine_testing/tests/src/test/contract_api/mod.rs @@ -2,6 +2,7 @@ mod account; mod add_contract_version; mod create_purse; mod dictionary; +mod generic_hash; mod get_arg; mod get_block_info; mod get_blocktime; diff --git a/execution_engine_testing/tests/src/test/contract_api/runtime.rs b/execution_engine_testing/tests/src/test/contract_api/runtime.rs index 6c093d731e..3c24218691 100644 --- a/execution_engine_testing/tests/src/test/contract_api/runtime.rs +++ b/execution_engine_testing/tests/src/test/contract_api/runtime.rs @@ -6,9 +6,12 @@ use casper_engine_test_support::{ DeployItemBuilder, ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, DEFAULT_PAYMENT, LOCAL_GENESIS_REQUEST, }; -use casper_execution_engine::runtime_context::RANDOM_BYTES_COUNT; +use casper_execution_engine::{ + runtime::{cryptography, cryptography::DIGEST_LENGTH}, + runtime_context::RANDOM_BYTES_COUNT, +}; use casper_storage::address_generator::ADDRESS_LENGTH; -use casper_types::{crypto, runtime_args, BLAKE2B_DIGEST_LENGTH}; +use casper_types::runtime_args; const ARG_BYTES: &str = "bytes"; const ARG_AMOUNT: &str = "amount"; @@ -123,8 +126,8 @@ fn should_hash() { builder.exec(exec_request).commit().expect_success(); - let digest = get_value::(&builder, HASH_RESULT); - let expected_digest = crypto::blake2b(input); + let digest = get_value::(&builder, HASH_RESULT); + let expected_digest = cryptography::blake2b(input); assert_eq!(digest, expected_digest); } } diff --git a/execution_engine_testing/tests/src/test/contract_messages.rs b/execution_engine_testing/tests/src/test/contract_messages.rs index a9b36f02a0..036cdf5c89 100644 --- a/execution_engine_testing/tests/src/test/contract_messages.rs +++ b/execution_engine_testing/tests/src/test/contract_messages.rs @@ -1,15 +1,18 @@ use num_traits::Zero; use std::cell::RefCell; +use casper_execution_engine::runtime::cryptography; + use casper_engine_test_support::{ ChainspecConfig, ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, DEFAULT_BLOCK_TIME, LOCAL_GENESIS_REQUEST, }; + use casper_types::{ bytesrepr::ToBytes, contract_messages::{MessageChecksum, MessagePayload, MessageTopicSummary, TopicNameHash}, - crypto, runtime_args, AddressableEntity, AddressableEntityHash, BlockGlobalAddr, BlockTime, - CLValue, CoreConfig, Digest, EntityAddr, HostFunction, HostFunctionCosts, Key, MessageLimits, + runtime_args, AddressableEntity, AddressableEntityHash, BlockGlobalAddr, BlockTime, CLValue, + CoreConfig, Digest, EntityAddr, HostFunction, HostFunctionCosts, Key, MessageLimits, OpcodeCosts, RuntimeArgs, StorageCosts, StoredValue, SystemConfig, WasmConfig, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, }; @@ -269,7 +272,7 @@ fn should_emit_messages() { // Now call the entry point to emit some messages. emit_message_with_suffix(&builder, "test", &contract_hash, DEFAULT_BLOCK_TIME); let expected_message = MessagePayload::from(format!("{}{}", EMITTER_MESSAGE_PREFIX, "test")); - let expected_message_hash = crypto::blake2b( + let expected_message_hash = cryptography::blake2b( [ 0u64.to_bytes().unwrap(), expected_message.to_bytes().unwrap(), @@ -290,7 +293,7 @@ fn should_emit_messages() { // call again to emit a new message and check that the index in the topic incremented. emit_message_with_suffix(&builder, "test", &contract_hash, DEFAULT_BLOCK_TIME); - let expected_message_hash = crypto::blake2b( + let expected_message_hash = cryptography::blake2b( [ 1u64.to_bytes().unwrap(), expected_message.to_bytes().unwrap(), @@ -320,7 +323,7 @@ fn should_emit_messages() { ); let expected_message = MessagePayload::from(format!("{}{}", EMITTER_MESSAGE_PREFIX, "new block time")); - let expected_message_hash = crypto::blake2b( + let expected_message_hash = cryptography::blake2b( [ 0u64.to_bytes().unwrap(), expected_message.to_bytes().unwrap(), @@ -994,7 +997,7 @@ fn should_produce_per_block_message_ordering() { ); let expected_message = MessagePayload::from(format!("{}{}", EMITTER_MESSAGE_PREFIX, "test 0")); - let expected_message_hash = crypto::blake2b( + let expected_message_hash = cryptography::blake2b( [ 0u64.to_bytes().unwrap(), expected_message.to_bytes().unwrap(), @@ -1021,7 +1024,7 @@ fn should_produce_per_block_message_ordering() { ); let expected_message = MessagePayload::from(format!("{}{}", EMITTER_MESSAGE_PREFIX, "test 1")); - let expected_message_hash = crypto::blake2b( + let expected_message_hash = cryptography::blake2b( [ 1u64.to_bytes().unwrap(), expected_message.to_bytes().unwrap(), @@ -1068,7 +1071,7 @@ fn should_produce_per_block_message_ordering() { ); let expected_message = MessagePayload::from(format!("{}{}", EMITTER_MESSAGE_PREFIX, "test 2")); - let expected_message_hash = crypto::blake2b( + let expected_message_hash = cryptography::blake2b( [ 2u64.to_bytes().unwrap(), expected_message.to_bytes().unwrap(), @@ -1095,7 +1098,7 @@ fn should_produce_per_block_message_ordering() { Some((BlockTime::new(DEFAULT_BLOCK_TIME + 1), 1)) ); let expected_message = MessagePayload::from(format!("{}{}", EMITTER_MESSAGE_PREFIX, "test 3")); - let expected_message_hash = crypto::blake2b( + let expected_message_hash = cryptography::blake2b( [ 0u64.to_bytes().unwrap(), expected_message.to_bytes().unwrap(), diff --git a/node/src/utils/chain_specification.rs b/node/src/utils/chain_specification.rs index b87f079672..4d8db52b61 100644 --- a/node/src/utils/chain_specification.rs +++ b/node/src/utils/chain_specification.rs @@ -248,6 +248,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]), manage_message_topic: HostFunction::new(100, [0, 1, 2, 4]), emit_message: HostFunction::new(100, [0, 1, 2, 3]), cost_increase_per_message: 50, diff --git a/resources/local/chainspec.toml.in b/resources/local/chainspec.toml.in index ab4f300abd..7eb554b528 100644 --- a/resources/local/chainspec.toml.in +++ b/resources/local/chainspec.toml.in @@ -322,6 +322,7 @@ dictionary_put = { cost = 9_500, arguments = [0, 1_800, 0, 520] } enable_contract_version = { cost = 200, arguments = [0, 0, 0, 0] } manage_message_topic = { cost = 200, arguments = [0, 30_000, 0, 0] } emit_message = { cost = 200, arguments = [0, 30_000, 0, 120_000] } +generic_hash = { cost = 300, arguments = [0, 0, 0, 0, 0] } cost_increase_per_message = 50 get_block_info = { cost = 330, arguments = [0, 0] } diff --git a/resources/production/chainspec.toml b/resources/production/chainspec.toml index 5c0c201dbd..f062463fbd 100644 --- a/resources/production/chainspec.toml +++ b/resources/production/chainspec.toml @@ -330,6 +330,7 @@ dictionary_put = { cost = 9_500, arguments = [0, 1_800, 0, 520] } enable_contract_version = { cost = 200, arguments = [0, 0, 0, 0] } manage_message_topic = { cost = 200, arguments = [0, 30_000, 0, 0] } emit_message = { cost = 200, arguments = [0, 30_000, 0, 120_000] } +generic_hash = { cost = 1_200_000, arguments = [0, 120_000, 0, 0, 0] } cost_increase_per_message = 50 get_block_info = { cost = 330, arguments = [0, 0] } diff --git a/smart_contracts/contract/src/contract_api/cryptography.rs b/smart_contracts/contract/src/contract_api/cryptography.rs new file mode 100644 index 0000000000..99a1dcdd8d --- /dev/null +++ b/smart_contracts/contract/src/contract_api/cryptography.rs @@ -0,0 +1,22 @@ +//! Functions with cryptographic utils. + +use casper_types::{api_error, HashAlgorithm, 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: HashAlgorithm) -> [u8; 32] { + let mut ret = [0; 32]; + + let result = unsafe { + ext_ffi::casper_generic_hash( + input.as_ref().as_ptr(), + input.as_ref().len(), + algo as 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..bf9f7f24a1 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 cryptography; pub mod runtime; pub mod storage; pub mod system; diff --git a/smart_contracts/contract/src/contract_api/runtime.rs b/smart_contracts/contract/src/contract_api/runtime.rs index 62ea1afb7f..343eda21d9 100644 --- a/smart_contracts/contract/src/contract_api/runtime.rs +++ b/smart_contracts/contract/src/contract_api/runtime.rs @@ -10,9 +10,9 @@ use casper_types::{ bytesrepr::{self, FromBytes, U64_SERIALIZED_LENGTH}, contract_messages::{MessagePayload, MessageTopicOperation}, system::Caller, - AddressableEntityHash, ApiError, BlockTime, CLTyped, CLValue, Digest, EntityVersion, Key, - PackageHash, Phase, RuntimeArgs, URef, BLAKE2B_DIGEST_LENGTH, BLOCKTIME_SERIALIZED_LENGTH, - PHASE_SERIALIZED_LENGTH, + AddressableEntityHash, ApiError, BlockTime, CLTyped, CLValue, Digest, EntityVersion, + HashAlgorithm, Key, PackageHash, Phase, RuntimeArgs, URef, BLAKE2B_DIGEST_LENGTH, + BLOCKTIME_SERIALIZED_LENGTH, PHASE_SERIALIZED_LENGTH, }; use crate::{contract_api, ext_ffi, unwrap_or_revert::UnwrapOrRevert}; @@ -433,9 +433,10 @@ pub fn is_valid_uref(uref: URef) -> bool { pub fn blake2b>(input: T) -> [u8; BLAKE2B_DIGEST_LENGTH] { let mut ret = [0; BLAKE2B_DIGEST_LENGTH]; let result = unsafe { - ext_ffi::casper_blake2b( + ext_ffi::casper_generic_hash( input.as_ref().as_ptr(), input.as_ref().len(), + HashAlgorithm::Blake2b 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 a294401306..a4c749c0f4 100644 --- a/smart_contracts/contract/src/ext_ffi.rs +++ b/smart_contracts/contract/src/ext_ffi.rs @@ -687,6 +687,7 @@ extern "C" { /// * `in_size` - length of bytes /// * `out_ptr` - pointer to the location where argument bytes will be copied from the host side /// * `out_size` - size of output pointer + #[deprecated(note = "Superseded by ext_ffi::casper_generic_hash")] pub fn casper_blake2b( in_ptr: *const u8, in_size: usize, @@ -858,4 +859,21 @@ extern "C" { /// * 3 => state hash /// * `dest_ptr` => pointer in wasm memory where to write the result pub fn casper_get_block_info(field_idx: u8, dest_ptr: *const u8); + + /// 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` - integer representation of HashAlgorithm 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: u8, + out_ptr: *const u8, + out_size: usize, + ) -> i32; } diff --git a/smart_contracts/contracts/test/generic-hash/Cargo.toml b/smart_contracts/contracts/test/generic-hash/Cargo.toml new file mode 100644 index 0000000000..352f8842c1 --- /dev/null +++ b/smart_contracts/contracts/test/generic-hash/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "generic-hash" +version = "0.1.0" +authors = ["Igor Bunar "] +edition = "2021" + +[[bin]] +name = "generic_hash" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +casper-contract = { path = "../../../contract" } +casper-types = { path = "../../../../types" } diff --git a/smart_contracts/contracts/test/generic-hash/src/main.rs b/smart_contracts/contracts/test/generic-hash/src/main.rs new file mode 100644 index 0000000000..9da526357b --- /dev/null +++ b/smart_contracts/contracts/test/generic-hash/src/main.rs @@ -0,0 +1,24 @@ +#![no_std] +#![no_main] + +extern crate alloc; +use alloc::string::String; + +use casper_contract::contract_api::{cryptography, runtime}; +use casper_types::crypto::HashAlgorithm; + +const ARG_ALGORITHM: &str = "algorithm"; +const ARG_DATA: &str = "data"; +const ARG_EXPECTED: &str = "expected"; + +#[no_mangle] +pub extern "C" fn call() { + let data: String = runtime::get_named_arg(ARG_DATA); + let expected: [u8; 32] = runtime::get_named_arg(ARG_EXPECTED); + let algorithm_repr: u8 = runtime::get_named_arg(ARG_ALGORITHM); + + let algorithm = HashAlgorithm::try_from(algorithm_repr).expect("Invalid enum repr"); + let hash = cryptography::generic_hash(data, algorithm); + + assert_eq!(hash, expected, "Hash mismatch"); +} diff --git a/storage/src/system/auction.rs b/storage/src/system/auction.rs index df2a9ab440..6af9fb3d8e 100644 --- a/storage/src/system/auction.rs +++ b/storage/src/system/auction.rs @@ -92,7 +92,7 @@ pub trait Auction: return Err(Error::DelegationRateTooLarge.into()); } - let provided_account_hash = AccountHash::from_public_key(&public_key, |x| self.blake2b(x)); + let provided_account_hash = AccountHash::from(&public_key); if !self.is_allowed_session_caller(&provided_account_hash) { return Err(Error::InvalidContext.into()); @@ -157,7 +157,7 @@ pub trait Auction: /// /// The function returns the remaining staked amount (we allow partial unbonding). fn withdraw_bid(&mut self, public_key: PublicKey, amount: U512) -> Result { - let provided_account_hash = AccountHash::from_public_key(&public_key, |x| self.blake2b(x)); + let provided_account_hash = AccountHash::from(&public_key); if !self.is_allowed_session_caller(&provided_account_hash) { return Err(Error::InvalidContext); @@ -263,8 +263,7 @@ pub trait Auction: validator_public_key: PublicKey, amount: U512, ) -> Result { - let provided_account_hash = - AccountHash::from_public_key(&delegator_public_key, |x| self.blake2b(x)); + let provided_account_hash = AccountHash::from(&delegator_public_key); if !self.is_allowed_session_caller(&provided_account_hash) { return Err(Error::InvalidContext); @@ -334,8 +333,7 @@ pub trait Auction: amount: U512, new_validator: PublicKey, ) -> Result { - let delegator_account_hash = - AccountHash::from_public_key(&delegator_public_key, |x| self.blake2b(x)); + let delegator_account_hash = AccountHash::from(&delegator_public_key); if !self.is_allowed_session_caller(&delegator_account_hash) { return Err(Error::InvalidContext); @@ -764,7 +762,7 @@ pub trait Auction: /// Activates a given validator's bid. To be used when a validator has been marked as inactive /// by consensus (aka "evicted"). fn activate_bid(&mut self, validator: PublicKey) -> Result<(), Error> { - let provided_account_hash = AccountHash::from_public_key(&validator, |x| self.blake2b(x)); + let provided_account_hash = AccountHash::from(&validator); if !self.is_allowed_session_caller(&provided_account_hash) { return Err(Error::InvalidContext); diff --git a/storage/src/system/auction/auction_native.rs b/storage/src/system/auction/auction_native.rs index 5b16fd2002..5e0b780e6b 100644 --- a/storage/src/system/auction/auction_native.rs +++ b/storage/src/system/auction/auction_native.rs @@ -13,7 +13,6 @@ use crate::{ use casper_types::{ account::AccountHash, bytesrepr::{FromBytes, ToBytes}, - crypto, system::{ auction::{BidAddr, BidKind, EraInfo, Error, UnbondingPurse}, mint, @@ -212,8 +211,8 @@ where S: StateReader, { fn unbond(&mut self, unbonding_purse: &UnbondingPurse) -> Result<(), Error> { - let account_hash = - AccountHash::from_public_key(unbonding_purse.unbonder_public_key(), crypto::blake2b); + let unbonder_key = unbonding_purse.unbonder_public_key(); + let account_hash = AccountHash::from(unbonder_key); // Do a migration if the account hasn't been migrated yet. This is just a read if it has // been migrated already. diff --git a/storage/src/system/auction/providers.rs b/storage/src/system/auction/providers.rs index 4bc754b526..4acdf0ada2 100644 --- a/storage/src/system/auction/providers.rs +++ b/storage/src/system/auction/providers.rs @@ -3,12 +3,11 @@ use std::collections::BTreeSet; use casper_types::{ account::AccountHash, bytesrepr::{FromBytes, ToBytes}, - crypto, system::{ auction::{BidAddr, BidKind, EraInfo, Error, UnbondingPurse}, mint, }, - CLTyped, Key, KeyTag, URef, BLAKE2B_DIGEST_LENGTH, U512, + CLTyped, Key, KeyTag, URef, U512, }; /// Provider of runtime host functionality. @@ -31,11 +30,6 @@ pub trait RuntimeProvider { /// Returns the current number of delegators for this validator. fn delegator_count(&mut self, bid_addr: &BidAddr) -> Result; - /// Returns a 32-byte BLAKE2b digest - fn blake2b>(&self, data: T) -> [u8; BLAKE2B_DIGEST_LENGTH] { - crypto::blake2b(data) - } - /// Returns vesting schedule period. fn vesting_schedule_period_millis(&self) -> u64; diff --git a/types/src/account.rs b/types/src/account.rs index 542fc53f85..fcc39cf96e 100644 --- a/types/src/account.rs +++ b/types/src/account.rs @@ -32,7 +32,7 @@ use crate::{ AddKeyFailure, NamedKeys, RemoveKeyFailure, SetThresholdFailure, UpdateKeyFailure, }, bytesrepr::{self, FromBytes, ToBytes}, - crypto, AccessRights, Key, URef, BLAKE2B_DIGEST_LENGTH, + AccessRights, Key, URef, }; #[cfg(feature = "json-schema")] use crate::{PublicKey, SecretKey}; @@ -332,15 +332,6 @@ impl FromBytes for Account { } } -#[doc(hidden)] -#[deprecated( - since = "1.4.4", - note = "function moved to casper_types::crypto::blake2b" -)] -pub fn blake2b>(data: T) -> [u8; BLAKE2B_DIGEST_LENGTH] { - crypto::blake2b(data) -} - #[doc(hidden)] #[cfg(any(feature = "testing", feature = "gens", test))] pub mod gens { diff --git a/types/src/chainspec/vm_config/host_function_costs.rs b/types/src/chainspec/vm_config/host_function_costs.rs index 24846d2d8d..49dd329b79 100644 --- a/types/src/chainspec/vm_config/host_function_costs.rs +++ b/types/src/chainspec/vm_config/host_function_costs.rs @@ -98,6 +98,8 @@ pub const DEFAULT_COST_INCREASE_PER_MESSAGE_EMITTED: u32 = 50; const DEFAULT_MESSAGE_TOPIC_NAME_SIZE_WEIGHT: u32 = 30_000; const DEFAULT_MESSAGE_PAYLOAD_SIZE_WEIGHT: u32 = 120_000; +const DEFAULT_GENERIC_HASH_COST: u32 = 300; + /// Representation of a host function cost. /// /// The total gas cost is equal to `cost` + sum of each argument weight multiplied by the byte size @@ -339,6 +341,8 @@ pub struct HostFunctionCosts { pub emit_message: HostFunction<[Cost; 4]>, /// Cost of calling the `get_block_info` host function. pub get_block_info: HostFunction<[Cost; 2]>, + /// Cost of calling the `generic_hash` host function. + pub generic_hash: HostFunction<[Cost; 5]>, } impl Zero for HostFunctionCosts { @@ -393,6 +397,7 @@ impl Zero for HostFunctionCosts { emit_message: HostFunction::zero(), cost_increase_per_message: Zero::zero(), get_block_info: HostFunction::zero(), + generic_hash: HostFunction::zero(), } } @@ -447,6 +452,7 @@ impl Zero for HostFunctionCosts { manage_message_topic, emit_message, get_block_info, + generic_hash, } = self; read_value.is_zero() && dictionary_get.is_zero() @@ -497,6 +503,7 @@ impl Zero for HostFunctionCosts { && cost_increase_per_message.is_zero() && add_package_version.is_zero() && get_block_info.is_zero() + && generic_hash.is_zero() } } @@ -683,6 +690,10 @@ impl Default for HostFunctionCosts { DEFAULT_MESSAGE_PAYLOAD_SIZE_WEIGHT, ], ), + generic_hash: HostFunction::new( + DEFAULT_GENERIC_HASH_COST, + [NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED], + ), cost_increase_per_message: DEFAULT_COST_INCREASE_PER_MESSAGE_EMITTED, get_block_info: HostFunction::new(DEFAULT_FIXED_COST, [NOT_USED, NOT_USED]), } @@ -741,6 +752,7 @@ impl ToBytes for HostFunctionCosts { ret.append(&mut self.emit_message.to_bytes()?); ret.append(&mut self.cost_increase_per_message.to_bytes()?); ret.append(&mut self.get_block_info.to_bytes()?); + ret.append(&mut self.generic_hash.to_bytes()?); Ok(ret) } @@ -794,6 +806,7 @@ impl ToBytes for HostFunctionCosts { + self.emit_message.serialized_length() + self.cost_increase_per_message.serialized_length() + self.get_block_info.serialized_length() + + self.generic_hash.serialized_length() } } @@ -848,6 +861,7 @@ impl FromBytes for HostFunctionCosts { let (emit_message, rem) = FromBytes::from_bytes(rem)?; let (cost_increase_per_message, rem) = FromBytes::from_bytes(rem)?; let (get_block_info, rem) = FromBytes::from_bytes(rem)?; + let (generic_hash, rem) = FromBytes::from_bytes(rem)?; Ok(( HostFunctionCosts { read_value, @@ -899,6 +913,7 @@ impl FromBytes for HostFunctionCosts { emit_message, cost_increase_per_message, get_block_info, + generic_hash, }, rem, )) @@ -958,6 +973,7 @@ impl Distribution for Standard { emit_message: rng.gen(), cost_increase_per_message: rng.gen(), get_block_info: rng.gen(), + generic_hash: rng.gen(), } } } @@ -1026,6 +1042,7 @@ pub mod gens { emit_message in host_function_cost_arb(), cost_increase_per_message in num::u32::ANY, get_block_info in host_function_cost_arb(), + generic_hash in host_function_cost_arb(), ) -> HostFunctionCosts { HostFunctionCosts { read_value, @@ -1076,7 +1093,8 @@ pub mod gens { manage_message_topic, emit_message, cost_increase_per_message, - get_block_info + get_block_info, + generic_hash, } } } diff --git a/types/src/crypto.rs b/types/src/crypto.rs index fbcd172c73..4afd4d18a7 100644 --- a/types/src/crypto.rs +++ b/types/src/crypto.rs @@ -8,7 +8,10 @@ use blake2::{ VarBlake2b, }; -use crate::key::BLAKE2B_DIGEST_LENGTH; +use num::FromPrimitive; +use num_derive::FromPrimitive; + +pub use crate::key::BLAKE2B_DIGEST_LENGTH; #[cfg(any(feature = "std", test))] pub use asymmetric_key::generate_ed25519_keypair; #[cfg(any(feature = "testing", feature = "gens", test))] @@ -21,8 +24,7 @@ pub use error::Error; #[cfg(any(feature = "std", test))] pub use error::ErrorExt; -#[doc(hidden)] -pub fn blake2b>(data: T) -> [u8; BLAKE2B_DIGEST_LENGTH] { +pub(crate) fn blake2b>(data: T) -> [u8; BLAKE2B_DIGEST_LENGTH] { let mut result = [0; BLAKE2B_DIGEST_LENGTH]; // NOTE: Assumed safe as `BLAKE2B_DIGEST_LENGTH` is a valid value for a hasher let mut hasher = VarBlake2b::new(BLAKE2B_DIGEST_LENGTH).expect("should create hasher"); @@ -33,3 +35,23 @@ pub fn blake2b>(data: T) -> [u8; BLAKE2B_DIGEST_LENGTH] { }); result } + +/// A type of hashing algorithm. +#[repr(u8)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive)] +pub enum HashAlgorithm { + /// Blake2b + Blake2b = 0, + /// Blake3 + Blake3 = 1, + /// Sha256, + Sha256 = 2, +} + +impl TryFrom for HashAlgorithm { + type Error = (); + + fn try_from(value: u8) -> Result { + FromPrimitive::from_u8(value).ok_or(()) + } +}