Skip to content

Commit

Permalink
feat: keccak256 (#179)
Browse files Browse the repository at this point in the history
<!--- Please provide a general summary of your changes in the title
above -->

## Pull Request type

<!-- Please try to limit your pull request to one type; submit multiple
pull requests if needed. -->

Please check the type of change your PR introduces:

- [ ] Bugfix
- [x] Feature
- [ ] Code style update (formatting, renaming)
- [ ] Refactoring (no functional changes, no API changes)
- [ ] Build-related changes
- [ ] Documentation content changes
- [ ] Other (please describe):

## What is the current behavior?

<!-- Please describe the current behavior that you are modifying, or
link to a relevant issue. -->

Issue Number: N/A

## What is the new behavior?

<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Implements keccak256 from a bytes input.
-
-

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this does introduce a breaking change, please describe the
impact and migration path for existing applications below. -->

## Other information

<!-- Any other information that is important to this PR, such as
screenshots of how the component looks before and after the change. -->

---------

Co-authored-by: Lucas @ StarkWare <[email protected]>
  • Loading branch information
enitrat and 0xLucqs authored Sep 20, 2023
1 parent a12436a commit 46c8d8a
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 0 deletions.
65 changes: 65 additions & 0 deletions src/math/src/keccak256.cairo
Original file line number Diff line number Diff line change
@@ -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<u8>` 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<u8>) -> (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<u8>` of bytes.
///
/// # Returns
///
/// A `u256` value representing the Keccak hash of the input bytes array.
fn keccak256(mut self: Span<u8>) -> u256 {
// Converts byte array to little endian 8 byte words array.
let mut words64: Array<u64> = 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
}

1 change: 1 addition & 0 deletions src/math/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ mod perfect_number;
mod sha256;
mod sha512;
mod zellers_congruence;
mod keccak256;

#[cfg(test)]
mod tests;
1 change: 1 addition & 0 deletions src/math/src/tests.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ mod perfect_number_test;
mod sha256_test;
mod sha512_test;
mod zellers_congruence_test;
mod test_keccak256;
75 changes: 75 additions & 0 deletions src/math/src/tests/test_keccak256.cairo
Original file line number Diff line number Diff line change
@@ -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'
);
}

0 comments on commit 46c8d8a

Please sign in to comment.