Skip to content

Commit

Permalink
Merge #4903
Browse files Browse the repository at this point in the history
4903: Implement generic_hash() host function to support hashing algorithms r=igor-casper a=igor-casper

This PR directly addresses #4892, referencing #4411 as prior work.

Adds host function called generic_hash(input, type) with support for the following types:
- HashAlgorithm::Blake2b - using the existing blake2b implementation,
- HashAlgorithm::Blake3 - introducing the blake3 library,
- HashAlgorithm::Sha256 - introducing the sha2 library,

**Example usage**
```rs
#![no_std]
#![no_main]

extern crate alloc;
use alloc::string::String;

use casper_contract::contract_api::cryptography;
use casper_types::crypto::HashAlgorithm;

#[no_mangle]
pub extern "C" fn call() {
    let data = "sha256 hash test";
    let 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];

    let hash = cryptography::generic_hash(data, HashAlgorithm::Sha256);
    
    assert_eq!(
        hash, expected,
        "Hash mismatch"
    );
}
```

**Notes**
- Blake2 is implemented in the types crate and it's been that way for some time, ideally it would be moved into the cryptography module where blake3 and sha256 reside
- The costs were referenced from #4411

Co-authored-by: igor-casper <[email protected]>
Co-authored-by: igor-casper <[email protected]>
  • Loading branch information
3 people authored Oct 15, 2024
2 parents 4b0a4fe + 144da5c commit fed9ad3
Show file tree
Hide file tree
Showing 28 changed files with 396 additions and 67 deletions.
59 changes: 52 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion binary_port/src/response_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}

Expand Down
3 changes: 3 additions & 0 deletions execution_engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
1 change: 1 addition & 0 deletions execution_engine/src/resolvers/v1_function_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ pub(crate) enum FunctionIndex {
EmitMessage,
LoadCallerInformation,
GetBlockInfoIndex,
GenericHash,
}

impl From<FunctionIndex> for usize {
Expand Down
4 changes: 4 additions & 0 deletions execution_engine/src/resolvers/v1_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}",
Expand Down
10 changes: 6 additions & 4 deletions execution_engine/src/runtime/auction_internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ use casper_storage::{
use casper_types::{
account::AccountHash,
bytesrepr::{FromBytes, ToBytes},
crypto,
system::{
auction::{BidAddr, BidKind, EraInfo, Error, UnbondingPurse},
mint,
},
CLTyped, CLValue, Key, KeyTag, PublicKey, RuntimeArgs, StoredValue, URef, U512,
};

use super::Runtime;
use super::{cryptography, Runtime};
use crate::execution::ExecError;

impl From<ExecError> for Option<Error> {
Expand Down Expand Up @@ -209,8 +208,11 @@ where
R: StateReader<Key, StoredValue, Error = GlobalStateError>,
{
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))
Expand Down
43 changes: 43 additions & 0 deletions execution_engine/src/runtime/cryptography.rs
Original file line number Diff line number Diff line change
@@ -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<T: AsRef<[u8]>>(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<T: AsRef<[u8]>>(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<T: AsRef<[u8]>>(data: T) -> [u8; DIGEST_LENGTH] {
Sha256::digest(data).into()
}
54 changes: 51 additions & 3 deletions execution_engine/src/runtime/externals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)))
}
}
}
}
8 changes: 4 additions & 4 deletions execution_engine/src/runtime/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -44,7 +45,6 @@ use casper_types::{
Message, MessageAddr, MessagePayload, MessageTopicOperation, MessageTopicSummary,
},
contracts::ContractHash,
crypto,
system::{
self,
auction::{self, EraInfo},
Expand Down Expand Up @@ -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()));
}
Expand Down Expand Up @@ -3564,7 +3564,7 @@ where
}

fn add_message_topic(&mut self, topic_name: &str) -> Result<Result<(), ApiError>, 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)
Expand All @@ -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.
Expand Down
Loading

0 comments on commit fed9ad3

Please sign in to comment.