Arbitrum Stylus logo

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:

1pub fn keccak<T: AsRef<[u8]>>(bytes: T) -> B256
1pub fn keccak<T: AsRef<[u8]>>(bytes: T) -> B256

Full Example code:

src/main.rs

1// Only run this as a WASM if the export-abi feature is not set.
2#![cfg_attr(not(any(feature = "export-abi", test)), no_main)]
3extern crate alloc;
4
5/// Import items from the SDK. The prelude contains common traits and macros.
6use stylus_sdk::{alloy_primitives::{U256, Address, FixedBytes}, abi::Bytes, prelude::*, crypto::keccak};
7use alloc::string::String;
8use alloc::vec::Vec;
9// 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
10use alloy_sol_types::{sol_data::{Address as SOLAddress, String as SOLString, Bytes as SOLBytes, *}, SolType};
11use alloy_sol_types::sol;
12
13// Define error
14sol! {
15    error DecodedFailed();
16}
17
18// Error types for the MultiSig contract
19#[derive(SolidityError)]
20pub enum HasherError{
21    DecodedFailed(DecodedFailed)
22}
23
24#[solidity_storage]
25#[entrypoint]
26pub struct Hasher {
27}
28/// Declare that `Hasher` is a contract with the following external methods.
29#[public]
30impl Hasher {
31    
32    // Encode the data and hash it
33    pub fn encode_and_hash(
34        &self, 
35        target: Address,
36        value: U256,
37        func: String,
38        data: Bytes,
39        timestamp: U256
40    ) -> FixedBytes<32> {
41        // define sol types tuple
42        type TxIdHashType = (SOLAddress, Uint<256>, SOLString, SOLBytes, Uint<256>);
43        // set the tuple
44        let tx_hash_data = (target, value, func, data, timestamp);
45        // encode the tuple
46        let tx_hash_data_encode = TxIdHashType::abi_encode_sequence(&tx_hash_data);
47        // hash the encoded data
48        keccak(tx_hash_data_encode).into()
49    }
50
51    // This should always return true
52    pub fn encode_and_decode(
53        &self, 
54        address: Address, 
55        amount: U256
56    ) -> Result<bool, HasherError> {
57        // define sol types tuple
58        type TxIdHashType = (SOLAddress, Uint<256>);
59        // set the tuple
60        let tx_hash_data = (address, amount);
61        // encode the tuple
62        let tx_hash_data_encode = TxIdHashType::abi_encode_sequence(&tx_hash_data);
63
64        let validate = true;
65        
66        // Check the result
67        match TxIdHashType::abi_decode_sequence(&tx_hash_data_encode, validate) {
68            Ok(res) => Ok(res == tx_hash_data),
69            Err(_) => {
70                return Err(HasherError::DecodedFailed(DecodedFailed{}));
71            },
72        }   
73    }
74        
75    // Packed encode the data and hash it, the same result with the following one
76    pub fn packed_encode_and_hash_1(
77        &self, 
78        target: Address,
79        value: U256,
80        func: String,
81        data: Bytes,
82        timestamp: U256
83    )-> FixedBytes<32> {
84        // define sol types tuple
85        type TxIdHashType = (SOLAddress, Uint<256>, SOLString, SOLBytes, Uint<256>);
86        // set the tuple
87        let tx_hash_data = (target, value, func, data, timestamp);
88        // encode the tuple
89        let tx_hash_data_encode_packed = TxIdHashType::abi_encode_packed(&tx_hash_data);
90        // hash the encoded data
91        keccak(tx_hash_data_encode_packed).into()
92    }
93
94    // Packed encode the data and hash it, the same result with the above one
95    pub fn packed_encode_and_hash_2(
96        &self, 
97        target: Address,
98        value: U256,
99        func: String,
100        data: Bytes,
101        timestamp: U256
102    )-> FixedBytes<32> {
103        // set the data to arrary and concat it directly
104        let tx_hash_data_encode_packed = [&target.to_vec(), &value.to_be_bytes_vec(), func.as_bytes(), &data.to_vec(), &timestamp.to_be_bytes_vec()].concat();
105        // hash the encoded data
106        keccak(tx_hash_data_encode_packed).into()
107    }
108
109
110    // The func example: "transfer(address,uint256)"
111    pub fn encode_with_signature(
112        &self, 
113        func: String, 
114        address: Address, 
115        amount: U256
116    ) -> Vec<u8> {
117        type TransferType = (SOLAddress, Uint<256>);
118        let tx_data = (address, amount);
119        let data = TransferType::abi_encode_sequence(&tx_data);
120        // Get function selector
121        let hashed_function_selector: FixedBytes<32> = keccak(func.as_bytes().to_vec()).into();
122        // Combine function selector and input data (use abi_packed way)
123        let calldata = [&hashed_function_selector[..4], &data].concat();
124        calldata
125    }
126
127    // The func example: "transfer(address,uint256)"
128    pub fn encode_with_signature_and_hash(
129        &self, 
130        func: String, 
131        address: Address, 
132        amount: U256
133    ) -> FixedBytes<32> {
134        type TransferType = (SOLAddress, Uint<256>);
135        let tx_data = (address, amount);
136        let data = TransferType::abi_encode_sequence(&tx_data);
137        // Get function selector
138        let hashed_function_selector: FixedBytes<32> = keccak(func.as_bytes().to_vec()).into();
139        // Combine function selector and input data (use abi_packed way)
140        let calldata = [&hashed_function_selector[..4], &data].concat();
141        keccak(calldata).into()
142    }
143}
1// Only run this as a WASM if the export-abi feature is not set.
2#![cfg_attr(not(any(feature = "export-abi", test)), no_main)]
3extern crate alloc;
4
5/// Import items from the SDK. The prelude contains common traits and macros.
6use stylus_sdk::{alloy_primitives::{U256, Address, FixedBytes}, abi::Bytes, prelude::*, crypto::keccak};
7use alloc::string::String;
8use alloc::vec::Vec;
9// 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
10use alloy_sol_types::{sol_data::{Address as SOLAddress, String as SOLString, Bytes as SOLBytes, *}, SolType};
11use alloy_sol_types::sol;
12
13// Define error
14sol! {
15    error DecodedFailed();
16}
17
18// Error types for the MultiSig contract
19#[derive(SolidityError)]
20pub enum HasherError{
21    DecodedFailed(DecodedFailed)
22}
23
24#[solidity_storage]
25#[entrypoint]
26pub struct Hasher {
27}
28/// Declare that `Hasher` is a contract with the following external methods.
29#[public]
30impl Hasher {
31    
32    // Encode the data and hash it
33    pub fn encode_and_hash(
34        &self, 
35        target: Address,
36        value: U256,
37        func: String,
38        data: Bytes,
39        timestamp: U256
40    ) -> FixedBytes<32> {
41        // define sol types tuple
42        type TxIdHashType = (SOLAddress, Uint<256>, SOLString, SOLBytes, Uint<256>);
43        // set the tuple
44        let tx_hash_data = (target, value, func, data, timestamp);
45        // encode the tuple
46        let tx_hash_data_encode = TxIdHashType::abi_encode_sequence(&tx_hash_data);
47        // hash the encoded data
48        keccak(tx_hash_data_encode).into()
49    }
50
51    // This should always return true
52    pub fn encode_and_decode(
53        &self, 
54        address: Address, 
55        amount: U256
56    ) -> Result<bool, HasherError> {
57        // define sol types tuple
58        type TxIdHashType = (SOLAddress, Uint<256>);
59        // set the tuple
60        let tx_hash_data = (address, amount);
61        // encode the tuple
62        let tx_hash_data_encode = TxIdHashType::abi_encode_sequence(&tx_hash_data);
63
64        let validate = true;
65        
66        // Check the result
67        match TxIdHashType::abi_decode_sequence(&tx_hash_data_encode, validate) {
68            Ok(res) => Ok(res == tx_hash_data),
69            Err(_) => {
70                return Err(HasherError::DecodedFailed(DecodedFailed{}));
71            },
72        }   
73    }
74        
75    // Packed encode the data and hash it, the same result with the following one
76    pub fn packed_encode_and_hash_1(
77        &self, 
78        target: Address,
79        value: U256,
80        func: String,
81        data: Bytes,
82        timestamp: U256
83    )-> FixedBytes<32> {
84        // define sol types tuple
85        type TxIdHashType = (SOLAddress, Uint<256>, SOLString, SOLBytes, Uint<256>);
86        // set the tuple
87        let tx_hash_data = (target, value, func, data, timestamp);
88        // encode the tuple
89        let tx_hash_data_encode_packed = TxIdHashType::abi_encode_packed(&tx_hash_data);
90        // hash the encoded data
91        keccak(tx_hash_data_encode_packed).into()
92    }
93
94    // Packed encode the data and hash it, the same result with the above one
95    pub fn packed_encode_and_hash_2(
96        &self, 
97        target: Address,
98        value: U256,
99        func: String,
100        data: Bytes,
101        timestamp: U256
102    )-> FixedBytes<32> {
103        // set the data to arrary and concat it directly
104        let tx_hash_data_encode_packed = [&target.to_vec(), &value.to_be_bytes_vec(), func.as_bytes(), &data.to_vec(), &timestamp.to_be_bytes_vec()].concat();
105        // hash the encoded data
106        keccak(tx_hash_data_encode_packed).into()
107    }
108
109
110    // The func example: "transfer(address,uint256)"
111    pub fn encode_with_signature(
112        &self, 
113        func: String, 
114        address: Address, 
115        amount: U256
116    ) -> Vec<u8> {
117        type TransferType = (SOLAddress, Uint<256>);
118        let tx_data = (address, amount);
119        let data = TransferType::abi_encode_sequence(&tx_data);
120        // Get function selector
121        let hashed_function_selector: FixedBytes<32> = keccak(func.as_bytes().to_vec()).into();
122        // Combine function selector and input data (use abi_packed way)
123        let calldata = [&hashed_function_selector[..4], &data].concat();
124        calldata
125    }
126
127    // The func example: "transfer(address,uint256)"
128    pub fn encode_with_signature_and_hash(
129        &self, 
130        func: String, 
131        address: Address, 
132        amount: U256
133    ) -> FixedBytes<32> {
134        type TransferType = (SOLAddress, Uint<256>);
135        let tx_data = (address, amount);
136        let data = TransferType::abi_encode_sequence(&tx_data);
137        // Get function selector
138        let hashed_function_selector: FixedBytes<32> = keccak(func.as_bytes().to_vec()).into();
139        // Combine function selector and input data (use abi_packed way)
140        let calldata = [&hashed_function_selector[..4], &data].concat();
141        keccak(calldata).into()
142    }
143}

Cargo.toml

1[package]
2name = "stylus-encode-hashing"
3version = "0.1.0"
4edition = "2021"
5
6[dependencies]
7alloy-primitives = "=0.7.6"
8alloy-sol-types = "=0.7.6"
9mini-alloc = "0.4.2"
10stylus-sdk = "0.6.0"
11hex = "0.4.3"
12sha3 = "0.10.8"
13
14[features]
15export-abi = ["stylus-sdk/export-abi"]
16debug = ["stylus-sdk/debug"]
17
18[lib]
19crate-type = ["lib", "cdylib"]
20
21[profile.release]
22codegen-units = 1
23strip = true
24lto = true
25panic = "abort"
26opt-level = "s"
1[package]
2name = "stylus-encode-hashing"
3version = "0.1.0"
4edition = "2021"
5
6[dependencies]
7alloy-primitives = "=0.7.6"
8alloy-sol-types = "=0.7.6"
9mini-alloc = "0.4.2"
10stylus-sdk = "0.6.0"
11hex = "0.4.3"
12sha3 = "0.10.8"
13
14[features]
15export-abi = ["stylus-sdk/export-abi"]
16debug = ["stylus-sdk/debug"]
17
18[lib]
19crate-type = ["lib", "cdylib"]
20
21[profile.release]
22codegen-units = 1
23strip = true
24lto = true
25panic = "abort"
26opt-level = "s"