From a4a69e2959d4c44c048b8370a6e1a5a70b7c7b9b Mon Sep 17 00:00:00 2001 From: Pia Date: Wed, 27 Dec 2023 16:08:15 +0900 Subject: [PATCH] refactor: hash logic clean up --- src/hasher/core.rs | 20 ++++++++-- src/hasher/hashers/keccak.rs | 31 ++++++++++----- src/hasher/hashers/stark_pedersen.rs | 43 +++++++++++---------- src/hasher/hashers/stark_poseidon.rs | 57 +++++++++++++--------------- tests/hasher/keccak.rs | 6 +-- tests/hasher/stark_pedersen.rs | 4 +- tests/hasher/stark_poseidon.rs | 6 +-- 7 files changed, 97 insertions(+), 70 deletions(-) diff --git a/src/hasher/core.rs b/src/hasher/core.rs index 7df1f23..64fc9f6 100644 --- a/src/hasher/core.rs +++ b/src/hasher/core.rs @@ -2,17 +2,25 @@ use anyhow::Result; use std::{fmt::Debug, str::FromStr}; use strum_macros::EnumIter; -// Default Hasher Options -pub const DEFAULT_BLOCK_SIZE_BITS: usize = 256; - +/// A trait for hash functions pub trait Hasher: Send + Sync + Debug { + /// Hashes a data which is a vector of strings fn hash(&self, data: Vec) -> Result; + + /// Checks if the element size is valid, i.e. if it is less than the block size fn is_element_size_valid(&self, element: &str) -> bool; + + /// Hashes a single element fn hash_single(&self, data: &str) -> String; + + /// Returns the genesis hash fn get_genesis(&self) -> String; + + /// Returns the name of the [`HashingFunction`] fn get_name(&self) -> HashingFunction; } +/// Hashing functions types supported by the hasher #[derive(EnumIter, Debug, PartialEq, Eq, Clone, Copy)] pub enum HashingFunction { Keccak256, @@ -42,3 +50,9 @@ impl ToString for HashingFunction { } } } + +/// Returns the byte size of a hex string +pub(crate) fn byte_size(hex: &str) -> usize { + let hex = hex.strip_prefix("0x").unwrap_or(hex); + hex.len() / 2 +} diff --git a/src/hasher/hashers/keccak.rs b/src/hasher/hashers/keccak.rs index 4d277a4..06ec486 100644 --- a/src/hasher/hashers/keccak.rs +++ b/src/hasher/hashers/keccak.rs @@ -1,14 +1,16 @@ -use anyhow::Result; +use anyhow::{bail, Result}; use num_bigint::BigInt; use num_traits::{identities::Zero, Num}; use sha3::{Digest, Keccak256}; -use crate::hasher::HashingFunction; +use crate::hasher::{byte_size, HashingFunction}; use super::super::Hasher; -#[derive(Debug)] +/// Hasher for Keccak256 +#[derive(Debug, Clone)] pub struct KeccakHasher { + /// The block size in bits for Keccak256 is 256 block_size_bits: usize, } @@ -17,7 +19,24 @@ impl Hasher for KeccakHasher { HashingFunction::Keccak256 } + /// Hashes a data which is a vector of strings + /// + /// NOTE: data have no limit in length of elements fn hash(&self, data: Vec) -> Result { + for element in &data { + if !self.is_element_size_valid(element) { + bail!( + "{}", + format!( + "Element {} is size {} bits. It is not of valid size {} bits", + element, + self.block_size_bits, + element.len() * 8 + ) + ); + } + } + let mut keccak = Keccak256::new(); if data.is_empty() { @@ -73,9 +92,3 @@ impl Default for KeccakHasher { Self::new() } } - -fn byte_size(hex: &str) -> usize { - let hex = hex.strip_prefix("0x").unwrap_or(hex); - - hex.len() / 2 -} diff --git a/src/hasher/hashers/stark_pedersen.rs b/src/hasher/hashers/stark_pedersen.rs index 04ac3fe..a872006 100644 --- a/src/hasher/hashers/stark_pedersen.rs +++ b/src/hasher/hashers/stark_pedersen.rs @@ -1,16 +1,17 @@ -use anyhow::Result; -use std::collections::HashMap; +use anyhow::{bail, Result}; use primitive_types::U256; use starknet::core::{crypto::pedersen_hash, types::FieldElement}; -use crate::hasher::HashingFunction; +use crate::hasher::{byte_size, HashingFunction}; use super::super::Hasher; +/// Hasher for Stark Pedersen #[derive(Debug, Clone)] pub struct StarkPedersenHasher { - options: HashMap, + /// The block size in bits for Stark Pedersen is 252 + block_size_bits: usize, } impl Hasher for StarkPedersenHasher { @@ -18,13 +19,25 @@ impl Hasher for StarkPedersenHasher { HashingFunction::Pedersen } + /// Hashes a data which is a vector of strings + /// + /// NOTE: data should be of size 2 fn hash(&self, data: Vec) -> Result { if data.len() != 2 { - panic!("Stark Pedersen Hasher only accepts two elements"); + bail!("Stark Pedersen Hasher only accepts two elements"); } + for element in &data { if !self.is_element_size_valid(element) { - panic!("{}", format!("Element {} is not of valid size", element)); + bail!( + "{}", + format!( + "Element {} is size {} bits. It is not of valid size {} bits", + element, + self.block_size_bits, + element.len() * 8 + ) + ); } } @@ -52,7 +65,7 @@ impl Hasher for StarkPedersenHasher { } fn is_element_size_valid(&self, element: &str) -> bool { - byte_size(element) <= *self.options.get("blockSizeBits").unwrap() + byte_size(element) <= self.block_size_bits } fn hash_single(&self, data: &str) -> String { @@ -67,9 +80,9 @@ impl Hasher for StarkPedersenHasher { impl StarkPedersenHasher { pub fn new() -> Self { - let mut options = HashMap::new(); - options.insert("blockSizeBits".to_string(), 252); - StarkPedersenHasher { options } + Self { + block_size_bits: 252, + } } } @@ -78,13 +91,3 @@ impl Default for StarkPedersenHasher { Self::new() } } - -fn byte_size(hex: &str) -> usize { - let hex = if let Some(stripped) = hex.strip_prefix("0x") { - stripped - } else { - hex - }; - - hex.len() / 2 -} diff --git a/src/hasher/hashers/stark_poseidon.rs b/src/hasher/hashers/stark_poseidon.rs index c29a359..1b63deb 100644 --- a/src/hasher/hashers/stark_poseidon.rs +++ b/src/hasher/hashers/stark_poseidon.rs @@ -1,13 +1,16 @@ -use crate::hasher::HashingFunction; +use crate::hasher::{byte_size, HashingFunction}; use super::super::Hasher; -use anyhow::{anyhow, Result}; +use anyhow::{bail, Result}; use starknet::core::types::FieldElement; use starknet_crypto::{poseidon_hash, poseidon_hash_many, poseidon_hash_single}; +/// Hasher for Stark Poseidon #[derive(Debug, Clone)] pub struct StarkPoseidonHasher { + /// The block size in bits for Stark Poseidon is 252 block_size_bits: usize, + /// Boolean flag to indicate whether to pad the hash with zeros should_pad: bool, } @@ -16,15 +19,22 @@ impl Hasher for StarkPoseidonHasher { HashingFunction::Poseidon } + /// Hashes a data which is a vector of strings + /// + /// NOTE: data should be more than 1 element fn hash(&self, data: Vec) -> Result { - let size_error_index = data.iter().position(|e| !self.is_element_size_valid(e)); - - if let Some(index) = size_error_index { - return Err(anyhow!( - "Stark Poseidon Hasher only accepts elements of size {} bits. Got {}", - self.block_size_bits, - data[index].len() * 8 - )); + for element in &data { + if !self.is_element_size_valid(element) { + bail!( + "{}", + format!( + "Element {} is size {} bits. It is not of valid size {} bits", + element, + self.block_size_bits, + element.len() * 8 + ) + ); + } } let field_elements: Vec = @@ -33,9 +43,7 @@ impl Hasher for StarkPoseidonHasher { match field_elements.len() { 0 => { - return Err(anyhow!( - "Stark Poseidon Hasher only accepts arrays of size 1 or greater".to_string() - )) + bail!("Stark Poseidon Hasher only accepts arrays of size 1 or greater") } 1 => hash_core = poseidon_hash_single(field_elements[0]), 2 => hash_core = poseidon_hash(field_elements[0], field_elements[1]), @@ -62,15 +70,8 @@ impl Hasher for StarkPoseidonHasher { fn get_genesis(&self) -> String { let genesis_str = "brave new world"; - let hex: String = genesis_str - .as_bytes() - .iter() - .fold(String::new(), |mut f, &b| { - f.push_str(&format!("{:02x}", b)); - f - }); - let hex_with_prefix = format!("0x{}", hex); - self.hash_single(&hex_with_prefix) + let hex_str = format!("0x{}", hex::encode(genesis_str)); + self.hash_single(&hex_str) } } @@ -83,12 +84,8 @@ impl StarkPoseidonHasher { } } -fn byte_size(hex: &str) -> usize { - let hex = if let Some(stripped) = hex.strip_prefix("0x") { - stripped - } else { - hex - }; - - hex.len() / 2 +impl Default for StarkPoseidonHasher { + fn default() -> Self { + Self::new(None) + } } diff --git a/tests/hasher/keccak.rs b/tests/hasher/keccak.rs index c197557..b5f6c24 100644 --- a/tests/hasher/keccak.rs +++ b/tests/hasher/keccak.rs @@ -2,7 +2,7 @@ use accumulators::hasher::{keccak::KeccakHasher, Hasher}; #[test] fn genesis() { - let hasher = KeccakHasher::new(); + let hasher = KeccakHasher::default(); assert_eq!( hasher.get_genesis(), @@ -12,7 +12,7 @@ fn genesis() { #[test] fn should_compute_a_hash() { - let hasher = KeccakHasher::new(); + let hasher = KeccakHasher::default(); let a = "0xa4b1d5793b631de611c922ea3ec938b359b3a49e687316d9a79c27be8ce84590".to_string(); let b = "0xa4b1d5793b631de611c922ea3ec938b359b3a49e687316d9a79c27be8ce84590".to_string(); @@ -33,7 +33,7 @@ fn should_compute_a_hash() { #[test] fn should_apply_padding() { - let hasher = KeccakHasher::new(); + let hasher = KeccakHasher::default(); let a = "131".to_string(); let b = "10".to_string(); let hash = hasher.hash(vec![a, b]).unwrap(); diff --git a/tests/hasher/stark_pedersen.rs b/tests/hasher/stark_pedersen.rs index 08b1dd0..d4818a7 100644 --- a/tests/hasher/stark_pedersen.rs +++ b/tests/hasher/stark_pedersen.rs @@ -2,7 +2,7 @@ use accumulators::hasher::{stark_pedersen::StarkPedersenHasher, Hasher}; #[test] fn should_compute_a_hash() { - let hasher = StarkPedersenHasher::new(); + let hasher = StarkPedersenHasher::default(); let a = "0x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d17761".to_string(); let b = "0x0194791558611599fe4ae0fcfa48f095659c90db18e54de86f2d2f547f7369bf".to_string(); @@ -20,7 +20,7 @@ fn should_compute_a_hash() { #[test] #[should_panic] fn should_throw() { - let hasher = StarkPedersenHasher::new(); + let hasher = StarkPedersenHasher::default(); let a = "0x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d177610x6109f1949f6a7555eccf4" .to_string(); diff --git a/tests/hasher/stark_poseidon.rs b/tests/hasher/stark_poseidon.rs index 11c3b9f..107362a 100644 --- a/tests/hasher/stark_poseidon.rs +++ b/tests/hasher/stark_poseidon.rs @@ -2,7 +2,7 @@ use accumulators::hasher::{stark_poseidon::StarkPoseidonHasher, Hasher}; #[test] fn should_compute_a_hash() { - let hasher = StarkPoseidonHasher::new(Some(false)); + let hasher = StarkPoseidonHasher::default(); let a = "0x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d17761".to_string(); let b = "0x0194791558611599fe4ae0fcfa48f095659c90db18e54de86f2d2f547f7369bf".to_string(); @@ -20,7 +20,7 @@ fn should_compute_a_hash() { #[test] fn check_genesis_hash() { - let hasher = StarkPoseidonHasher::new(Some(false)); + let hasher = StarkPoseidonHasher::default(); assert_eq!( hasher.get_genesis(), @@ -42,7 +42,7 @@ fn check_genesis_hash() { #[test] fn should_throw() { - let hasher = StarkPoseidonHasher::new(Some(false)); + let hasher = StarkPoseidonHasher::default(); let a = "0x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d177610x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d177610x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d177610x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d177610x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d177610x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d177610x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d177610x6109f1949f6a7555eccf40x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d177610x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d177610x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d177610x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d177610x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d177610x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d177610x6109f1949f6a7555eccf4e15ce1f10fbd78091dfe715cc2e0c5a244d9d177610x6109f1949f6a7555eccf4".to_string(); assert!(!hasher.is_element_size_valid(&a));