diff --git a/src/math/src/keccak256.cairo b/src/math/src/keccak256.cairo new file mode 100644 index 00000000..fcbd8bec --- /dev/null +++ b/src/math/src/keccak256.cairo @@ -0,0 +1,65 @@ +use keccak::cairo_keccak; + +#[generate_trait] +impl U64Impl of U64Trait { + /// Converts a little-endian byte slice to a 64-bit unsigned integer + /// + /// # Arguments + /// + /// * `self` - A `Span` slice of size n <=8. + /// + /// # Returns + /// + /// A tuple containing the converted 64-bit unsigned integer and the amount of bytes consumed + fn from_le_bytes(mut self: Span) -> (u64, u32) { + assert(self.len() < 9, 'bytes dont fit in u64'); + // Pack full value + let mut value: u64 = 0; + let mut n_bytes: u32 = self.len(); + loop { + let byte = match self.pop_back() { + Option::Some(byte) => *byte, + Option::None => { + break; + }, + }; + value = value * 0x100 + (byte.into()); + }; + (value, n_bytes) + } +} + +/// Reverse the endianness of an u256 +fn reverse_endianness(value: u256) -> u256 { + let new_low = integer::u128_byte_reverse(value.high); + let new_high = integer::u128_byte_reverse(value.low); + u256 { low: new_low, high: new_high } +} + +/// Computes the Solidity-compatible Keccak hash of an array of bytes. +/// +/// # Arguments +/// +/// * `self` - A `Array` of bytes. +/// +/// # Returns +/// +/// A `u256` value representing the Keccak hash of the input bytes array. +fn keccak256(mut self: Span) -> u256 { + // Converts byte array to little endian 8 byte words array. + let mut words64: Array = Default::default(); + let (last_word, last_word_bytes) = loop { + // Specifically handle last word + if self.len() < 8 { + let (value, n_bytes) = U64Trait::from_le_bytes(self); + break (value, n_bytes); + }; + let mut current_word = self.slice(0, 8); + let (value, n_bytes) = U64Trait::from_le_bytes(current_word); + words64.append(value); + self = self.slice(8, self.len() - 8); + }; + let mut hash = reverse_endianness(cairo_keccak(ref words64, last_word, last_word_bytes)); + hash +} + diff --git a/src/math/src/lib.cairo b/src/math/src/lib.cairo index 4867ae76..59d06263 100644 --- a/src/math/src/lib.cairo +++ b/src/math/src/lib.cairo @@ -183,6 +183,7 @@ mod perfect_number; mod sha256; mod sha512; mod zellers_congruence; +mod keccak256; #[cfg(test)] mod tests; diff --git a/src/math/src/tests.cairo b/src/math/src/tests.cairo index 0345c88d..46ba9084 100644 --- a/src/math/src/tests.cairo +++ b/src/math/src/tests.cairo @@ -13,3 +13,4 @@ mod perfect_number_test; mod sha256_test; mod sha512_test; mod zellers_congruence_test; +mod test_keccak256; diff --git a/src/math/src/tests/test_keccak256.cairo b/src/math/src/tests/test_keccak256.cairo new file mode 100644 index 00000000..5929d38e --- /dev/null +++ b/src/math/src/tests/test_keccak256.cairo @@ -0,0 +1,75 @@ +use alexandria_math::keccak256::keccak256; +use debug::PrintTrait; + +#[test] +#[available_gas(2000000)] +fn test_keccak256_empty_bytes() { + let input = array![]; + + let hash = keccak256(input.span()); + + assert( + hash == 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470, + 'wrong hash value' + ) +} + +#[test] +#[available_gas(2000000)] +fn test_keccak256_partial_bytes() { + let input = array![0x00, 0x01, 0x02, 0x03, 0x04, 0x05]; + + let hash = keccak256(input.span()); + + assert( + hash == 0x51e8babe8b42352100dffa7f7b3843c95245d3d545c6cbf5052e80258ae80627, + 'wrong hash value' + ); +} + +#[test] +#[available_gas(2000000)] +fn test_keccak256_full_u256() { + let input = array![ + 0x00, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x20, + 0x21, + 0x22, + 0x23, + 0x24, + 0x25, + 0x26, + 0x27, + 0x28, + 0x29, + 0x30, + 0x31, + 0x32 + ]; + + let hash = keccak256(input.span()); + + assert( + hash == 0x98cfb1eca8a71b4a4b1c115f3d5a462296a66487d1d97fb4c47b979c64bde069, + 'wrong hash value' + ); +}