Hasing with keccak256 • Stylus by Example
Hashing with keccak256
Keccak256 is a cryptographic hash function that takes an input of an arbitrary length and produces a fixed-length output of 256 bits.
Keccak256 is a member of the SHA-3 family of hash functions.
keccak256 computes the Keccak-256 hash of the input.
Some use cases are:
- Creating a deterministic unique ID from a input
 - Commit-Reveal scheme
 - Compact cryptographic signature (by signing the hash instead of a larger input)
 
Here we will use stylus-sdk::crypto::keccak to calculate the keccak256 hash of the input data:
note
This code has yet to be audited. Please use at your own risk.
pub fn keccak<T: AsRef<[u8]>>(bytes: T) -> B256
Full Example code:
src/main.rs
// Only run this as a WASM if the export-abi feature is not set.
#![cfg_attr(not(any(feature = "export-abi", test)), no_main)]
extern crate alloc;
/// Import items from the SDK. The prelude contains common traits and macros.
use stylus_sdk::{alloy_primitives::{U256, Address, FixedBytes}, abi::Bytes, prelude::*, crypto::keccak};
use alloc::string::String;
use alloc::vec::Vec;
// Becauce the naming of alloy_primitives and alloy_sol_types is the same, so we need to re-name the types in alloy_sol_types
use alloy_sol_types::{sol_data::{Address as SOLAddress, String as SOLString, Bytes as SOLBytes, *}, SolType};
use alloy_sol_types::sol;
// Define error
sol! {
    error DecodedFailed();
}
// Error types for the MultiSig contract
#[derive(SolidityError)]
pub enum HasherError{
    DecodedFailed(DecodedFailed)
}
#[solidity_storage]
#[entrypoint]
pub struct Hasher {
}
/// Declare that `Hasher` is a contract with the following external methods.
#[public]
impl Hasher {
    
    // Encode the data and hash it
    pub fn encode_and_hash(
        &self, 
        target: Address,
        value: U256,
        func: String,
        data: Bytes,
        timestamp: U256
    ) -> FixedBytes<32> {
        // define sol types tuple
        type TxIdHashType = (SOLAddress, Uint<256>, SOLString, SOLBytes, Uint<256>);
        // set the tuple
        let tx_hash_data = (target, value, func, data, timestamp);
        // encode the tuple
        let tx_hash_data_encode = TxIdHashType::abi_encode_sequence(&tx_hash_data);
        // hash the encoded data
        keccak(tx_hash_data_encode).into()
    }
    // This should always return true
    pub fn encode_and_decode(
        &self, 
        address: Address, 
        amount: U256
    ) -> Result<bool, HasherError> {
        // define sol types tuple
        type TxIdHashType = (SOLAddress, Uint<256>);
        // set the tuple
        let tx_hash_data = (address, amount);
        // encode the tuple
        let tx_hash_data_encode = TxIdHashType::abi_encode_sequence(&tx_hash_data);
        let validate = true;
        
        // Check the result
        match TxIdHashType::abi_decode_sequence(&tx_hash_data_encode, validate) {
            Ok(res) => Ok(res == tx_hash_data),
            Err(_) => {
                return Err(HasherError::DecodedFailed(DecodedFailed{}));
            },
        }   
    }
        
    // Packed encode the data and hash it, the same result with the following one
    pub fn packed_encode_and_hash_1(
        &self, 
        target: Address,
        value: U256,
        func: String,
        data: Bytes,
        timestamp: U256
    )-> FixedBytes<32> {
        // define sol types tuple
        type TxIdHashType = (SOLAddress, Uint<256>, SOLString, SOLBytes, Uint<256>);
        // set the tuple
        let tx_hash_data = (target, value, func, data, timestamp);
        // encode the tuple
        let tx_hash_data_encode_packed = TxIdHashType::abi_encode_packed(&tx_hash_data);
        // hash the encoded data
        keccak(tx_hash_data_encode_packed).into()
    }
    // Packed encode the data and hash it, the same result with the above one
    pub fn packed_encode_and_hash_2(
        &self, 
        target: Address,
        value: U256,
        func: String,
        data: Bytes,
        timestamp: U256
    )-> FixedBytes<32> {
        // set the data to arrary and concat it directly
        let tx_hash_data_encode_packed = [&target.to_vec(), &value.to_be_bytes_vec(), func.as_bytes(), &data.to_vec(), ×tamp.to_be_bytes_vec()].concat();
        // hash the encoded data
        keccak(tx_hash_data_encode_packed).into()
    }
    // The func example: "transfer(address,uint256)"
    pub fn encode_with_signature(
        &self, 
        func: String, 
        address: Address, 
        amount: U256
    ) -> Vec<u8> {
        type TransferType = (SOLAddress, Uint<256>);
        let tx_data = (address, amount);
        let data = TransferType::abi_encode_sequence(&tx_data);
        // Get function selector
        let hashed_function_selector: FixedBytes<32> = keccak(func.as_bytes().to_vec()).into();
        // Combine function selector and input data (use abi_packed way)
        let calldata = [&hashed_function_selector[..4], &data].concat();
        calldata
    }
    // The func example: "transfer(address,uint256)"
    pub fn encode_with_signature_and_hash(
        &self, 
        func: String, 
        address: Address, 
        amount: U256
    ) -> FixedBytes<32> {
        type TransferType = (SOLAddress, Uint<256>);
        let tx_data = (address, amount);
        let data = TransferType::abi_encode_sequence(&tx_data);
        // Get function selector
        let hashed_function_selector: FixedBytes<32> = keccak(func.as_bytes().to_vec()).into();
        // Combine function selector and input data (use abi_packed way)
        let calldata = [&hashed_function_selector[..4], &data].concat();
        keccak(calldata).into()
    }
}
Cargo.toml
[package]
name = "stylus-encode-hashing"
version = "0.1.0"
edition = "2021"
[dependencies]
alloy-primitives = "=0.7.6"
alloy-sol-types = "=0.7.6"
mini-alloc = "0.4.2"
stylus-sdk = "0.6.0"
hex = "0.4.3"
sha3 = "0.10.8"
[features]
export-abi = ["stylus-sdk/export-abi"]
debug = ["stylus-sdk/debug"]
[lib]
crate-type = ["lib", "cdylib"]
[profile.release]
codegen-units = 1
strip = true
lto = true
panic = "abort"
opt-level = "s"