diff --git a/Scarb.lock b/Scarb.lock index f6adb87a..a2a5eea6 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -39,6 +39,7 @@ name = "alexandria_math" version = "0.2.0" dependencies = [ "alexandria_data_structures", + "alexandria_encoding", ] [[package]] diff --git a/src/math/Scarb.toml b/src/math/Scarb.toml index 94a810c4..c276cf1f 100644 --- a/src/math/Scarb.toml +++ b/src/math/Scarb.toml @@ -7,3 +7,5 @@ homepage = "https://github.com/keep-starknet-strange/alexandria/tree/main/src/ma [dependencies] # dependency due to ArrayTraitExt::concat in ed25519.cairo alexandria_data_structures = { path = "../data_structures" } +# dependency due to ReversibleBytes in siphash.cairo +alexandria_encoding = { path = "../encoding" } diff --git a/src/math/src/lib.cairo b/src/math/src/lib.cairo index ff778d81..ba99b455 100644 --- a/src/math/src/lib.cairo +++ b/src/math/src/lib.cairo @@ -124,6 +124,7 @@ mod mod_arithmetics; mod perfect_number; mod sha256; mod sha512; +mod siphash; mod zellers_congruence; mod keccak256; diff --git a/src/math/src/siphash.cairo b/src/math/src/siphash.cairo new file mode 100644 index 00000000..a9da9c27 --- /dev/null +++ b/src/math/src/siphash.cairo @@ -0,0 +1,138 @@ +use alexandria_data_structures::byte_reader::{ByteReader, ByteReaderState}; +use alexandria_encoding::reversible::ReversibleBytes; +use traits::DivRem; +use integer::{u64_wide_mul, u64_wrapping_add}; + +const C0: u64 = 0x736f6d6570736575; +const C1: u64 = 0x646f72616e646f6d; +const C2: u64 = 0x6c7967656e657261; +const C3: u64 = 0x7465646279746573; + +trait SipHash { + /// Takes the input byte data and performs a SipHash-2-4: + /// it takes a 128-bit key, does 2 compression rounds, 4 + /// finalization rounds, and returns a 64-bit tag. + /// Note: When loading the key parts from a collection of + /// bytes, it is the little endian interpretation of each + /// key 64-bit part. + /// # Arguments + /// `key0` - the first 64 bits of the 128 bit key + /// `key1` - the last 64 bits of the 128 bit key + /// `data` - the byte data to be hashed by SipHash-2-4 + /// # Returns + /// `u64` - The little endian uint of the resulting bytes + fn sip_hash(key0: u64, key1: u64, data: @T) -> u64; +} + +impl SipHashImpl, +ByteReader> of SipHash { + fn sip_hash(key0: u64, key1: u64, data: @T) -> u64 { + let mut reader = data.reader(); + let mut state = _InternalSiphHash::::initialize(key0, key1); + state.sip_hash(ref reader) + } +} + +#[derive(Copy, Drop)] +struct SipHashState { + v0: u64, + v1: u64, + v2: u64, + v3: u64, +} + +#[generate_trait] +impl _InternalSiphHashImpl, +ByteReader> of _InternalSiphHash { + #[inline(always)] + fn initialize(key0: u64, key1: u64) -> SipHashState { + let key0 = key0; + let key1 = key1; + let v0 = C0 ^ key0; + let v1 = C1 ^ key1; + let v2 = C2 ^ key0; + let v3 = C3 ^ key1; + SipHashState { v0, v1, v2, v3 } + } + + #[inline(always)] + fn sipround(ref self: SipHashState) { + self.v0 = u64_wrapping_add(self.v0, self.v1); + self.v2 = u64_wrapping_add(self.v2, self.v3); + self.v1 = _rotl(self.v1, BITS_13); + self.v3 = _rotl(self.v3, BITS_16); + self.v1 = self.v1 ^ self.v0; + self.v3 = self.v3 ^ self.v2; + self.v0 = _rotl(self.v0, BITS_32); + self.v2 = u64_wrapping_add(self.v2, self.v1); + self.v0 = u64_wrapping_add(self.v0, self.v3); + self.v1 = _rotl(self.v1, BITS_17); + self.v3 = _rotl(self.v3, BITS_21); + self.v1 = self.v1 ^ self.v2; + self.v3 = self.v3 ^ self.v0; + self.v2 = _rotl(self.v2, BITS_32); + } + + #[inline(always)] + fn compression(ref self: SipHashState, word: u64) { + self.v3 = self.v3 ^ word; + self.sipround(); + self.sipround(); + self.v0 = self.v0 ^ word; + } + + #[inline] + fn sip_hash(ref self: SipHashState, ref reader: ByteReaderState) -> u64 { + let len: u64 = reader.len().into(); + let b = (len % 0x100) * 0x100000000000000; + loop { + match reader.read_u64_le() { + Option::Some(word) => { self.compression(word); }, + Option::None => { break; } + } + }; + let last_word = b + reader.load_last_word(); + self.compression(last_word); + self.finalize().reverse_bytes() // little endian byte order + } + + #[inline(always)] + fn finalize(ref self: SipHashState) -> u64 { + self.v2 = self.v2 ^ 0xff; + self.sipround(); + self.sipround(); + self.sipround(); + self.sipround(); + self.v0 ^ self.v1 ^ self.v2 ^ self.v3 + } + + #[inline] + fn load_last_word(ref self: ByteReaderState) -> u64 { + let mut last_word: u64 = 0; + let mut index = 0x1_u64; + loop { + match self.read_u8() { + Option::Some(byte) => { + last_word += byte.into() * index; + index *= 0x100; + }, + Option::None => { break; }, + } + }; + last_word + } +} + +// helper function optimized for the small number of rotl cases performed +// avoiding to call any power function for the precomputed values below +const BITS_13: u64 = 0b10000000000000; +const BITS_16: u64 = 0b10000000000000000; +const BITS_17: u64 = 0b100000000000000000; +const BITS_21: u64 = 0b1000000000000000000000; +const BITS_32: u64 = 0b100000000000000000000000000000000; +const PARTITION64_KEY: u128 = 0x00000000000000010000000000000000; + +#[inline(always)] +fn _rotl(word: u64, bits: u64) -> u64 { + let word128 = u64_wide_mul(word, bits); + let (quotient, remainder) = DivRem::div_rem(word128, PARTITION64_KEY.try_into().unwrap()); + (quotient + remainder).try_into().unwrap() +} diff --git a/src/math/src/tests.cairo b/src/math/src/tests.cairo index 46ba9084..5d5fb9f1 100644 --- a/src/math/src/tests.cairo +++ b/src/math/src/tests.cairo @@ -12,5 +12,6 @@ mod mod_arithmetics_test; mod perfect_number_test; mod sha256_test; mod sha512_test; +mod siphash_test; mod zellers_congruence_test; mod test_keccak256; diff --git a/src/math/src/tests/siphash_test.cairo b/src/math/src/tests/siphash_test.cairo new file mode 100644 index 00000000..bf4d989a --- /dev/null +++ b/src/math/src/tests/siphash_test.cairo @@ -0,0 +1,567 @@ +use alexandria_math::siphash::SipHash; +use alexandria_data_structures::byte_reader::ByteReader; + +#[test] +#[available_gas(200000000)] +fn test_veorq_vectors_h() { + let expected_results = result_vector(); + let (key0, key1) = generate_keys(); + let mut ba = array![]; + let first_result = *expected_results[0]; + let first = SipHash::sip_hash(key0, key1, @ba.span()); // empty data + assert(first == first_result, 'should be equal'); + let mut i = 0_u8; + loop { + if i == 63 { + break; + } + ba.append(i); + let expected = *expected_results[i.into() + 1]; + assert(SipHash::sip_hash(key0, key1, @ba.span()) == expected, 'should equal'); + i += 1; + }; +} + +// helpers +fn generate_keys() -> (u64, u64) { + let mut key_bytes: Array = array![]; + let mut index = 0_usize; + loop { + if index == 16 { + break; + } + key_bytes.append(index.try_into().unwrap()); + index += 1; + }; + let mut reader = key_bytes.reader(); + (reader.read_u64_le().unwrap(), reader.read_u64_le().unwrap()) +} + +fn result_vector() -> Span { + let test_bytes = from_vectors_h(); + let mut reader = test_bytes.reader(); + let mut result: Array = array![]; + loop { + match reader.read_u64() { + Option::Some(word) => result.append(word), + Option::None => { break result.span(); } + } + } +} + +fn from_vectors_h() -> Array { + array![ + 0x31, + 0x0e, + 0x0e, + 0xdd, + 0x47, + 0xdb, + 0x6f, + 0x72, + 0xfd, + 0x67, + 0xdc, + 0x93, + 0xc5, + 0x39, + 0xf8, + 0x74, + 0x5a, + 0x4f, + 0xa9, + 0xd9, + 0x09, + 0x80, + 0x6c, + 0x0d, + 0x2d, + 0x7e, + 0xfb, + 0xd7, + 0x96, + 0x66, + 0x67, + 0x85, + 0xb7, + 0x87, + 0x71, + 0x27, + 0xe0, + 0x94, + 0x27, + 0xcf, + 0x8d, + 0xa6, + 0x99, + 0xcd, + 0x64, + 0x55, + 0x76, + 0x18, + 0xce, + 0xe3, + 0xfe, + 0x58, + 0x6e, + 0x46, + 0xc9, + 0xcb, + 0x37, + 0xd1, + 0x01, + 0x8b, + 0xf5, + 0x00, + 0x02, + 0xab, + 0x62, + 0x24, + 0x93, + 0x9a, + 0x79, + 0xf5, + 0xf5, + 0x93, + 0xb0, + 0xe4, + 0xa9, + 0x0b, + 0xdf, + 0x82, + 0x00, + 0x9e, + 0xf3, + 0xb9, + 0xdd, + 0x94, + 0xc5, + 0xbb, + 0x5d, + 0x7a, + 0xa7, + 0xad, + 0x6b, + 0x22, + 0x46, + 0x2f, + 0xb3, + 0xf4, + 0xfb, + 0xe5, + 0x0e, + 0x86, + 0xbc, + 0x8f, + 0x1e, + 0x75, + 0x90, + 0x3d, + 0x84, + 0xc0, + 0x27, + 0x56, + 0xea, + 0x14, + 0xee, + 0xf2, + 0x7a, + 0x8e, + 0x90, + 0xca, + 0x23, + 0xf7, + 0xe5, + 0x45, + 0xbe, + 0x49, + 0x61, + 0xca, + 0x29, + 0xa1, + 0xdb, + 0x9b, + 0xc2, + 0x57, + 0x7f, + 0xcc, + 0x2a, + 0x3f, + 0x94, + 0x47, + 0xbe, + 0x2c, + 0xf5, + 0xe9, + 0x9a, + 0x69, + 0x9c, + 0xd3, + 0x8d, + 0x96, + 0xf0, + 0xb3, + 0xc1, + 0x4b, + 0xbd, + 0x61, + 0x79, + 0xa7, + 0x1d, + 0xc9, + 0x6d, + 0xbb, + 0x98, + 0xee, + 0xa2, + 0x1a, + 0xf2, + 0x5c, + 0xd6, + 0xbe, + 0xc7, + 0x67, + 0x3b, + 0x2e, + 0xb0, + 0xcb, + 0xf2, + 0xd0, + 0x88, + 0x3e, + 0xa3, + 0xe3, + 0x95, + 0x67, + 0x53, + 0x93, + 0xc8, + 0xce, + 0x5c, + 0xcd, + 0x8c, + 0x03, + 0x0c, + 0xa8, + 0x94, + 0xaf, + 0x49, + 0xf6, + 0xc6, + 0x50, + 0xad, + 0xb8, + 0xea, + 0xb8, + 0x85, + 0x8a, + 0xde, + 0x92, + 0xe1, + 0xbc, + 0xf3, + 0x15, + 0xbb, + 0x5b, + 0xb8, + 0x35, + 0xd8, + 0x17, + 0xad, + 0xcf, + 0x6b, + 0x07, + 0x63, + 0x61, + 0x2e, + 0x2f, + 0xa5, + 0xc9, + 0x1d, + 0xa7, + 0xac, + 0xaa, + 0x4d, + 0xde, + 0x71, + 0x65, + 0x95, + 0x87, + 0x66, + 0x50, + 0xa2, + 0xa6, + 0x28, + 0xef, + 0x49, + 0x5c, + 0x53, + 0xa3, + 0x87, + 0xad, + 0x42, + 0xc3, + 0x41, + 0xd8, + 0xfa, + 0x92, + 0xd8, + 0x32, + 0xce, + 0x7c, + 0xf2, + 0x72, + 0x2f, + 0x51, + 0x27, + 0x71, + 0xe3, + 0x78, + 0x59, + 0xf9, + 0x46, + 0x23, + 0xf3, + 0xa7, + 0x38, + 0x12, + 0x05, + 0xbb, + 0x1a, + 0xb0, + 0xe0, + 0x12, + 0xae, + 0x97, + 0xa1, + 0x0f, + 0xd4, + 0x34, + 0xe0, + 0x15, + 0xb4, + 0xa3, + 0x15, + 0x08, + 0xbe, + 0xff, + 0x4d, + 0x31, + 0x81, + 0x39, + 0x62, + 0x29, + 0xf0, + 0x90, + 0x79, + 0x02, + 0x4d, + 0x0c, + 0xf4, + 0x9e, + 0xe5, + 0xd4, + 0xdc, + 0xca, + 0x5c, + 0x73, + 0x33, + 0x6a, + 0x76, + 0xd8, + 0xbf, + 0x9a, + 0xd0, + 0xa7, + 0x04, + 0x53, + 0x6b, + 0xa9, + 0x3e, + 0x0e, + 0x92, + 0x59, + 0x58, + 0xfc, + 0xd6, + 0x42, + 0x0c, + 0xad, + 0xa9, + 0x15, + 0xc2, + 0x9b, + 0xc8, + 0x06, + 0x73, + 0x18, + 0x95, + 0x2b, + 0x79, + 0xf3, + 0xbc, + 0x0a, + 0xa6, + 0xd4, + 0xf2, + 0x1d, + 0xf2, + 0xe4, + 0x1d, + 0x45, + 0x35, + 0xf9, + 0x87, + 0x57, + 0x75, + 0x19, + 0x04, + 0x8f, + 0x53, + 0xa9, + 0x10, + 0xa5, + 0x6c, + 0xf5, + 0xdf, + 0xcd, + 0x9a, + 0xdb, + 0xeb, + 0x75, + 0x09, + 0x5c, + 0xcd, + 0x98, + 0x6c, + 0xd0, + 0x51, + 0xa9, + 0xcb, + 0x9e, + 0xcb, + 0xa3, + 0x12, + 0xe6, + 0x96, + 0xaf, + 0xad, + 0xfc, + 0x2c, + 0xe6, + 0x66, + 0xc7, + 0x72, + 0xfe, + 0x52, + 0x97, + 0x5a, + 0x43, + 0x64, + 0xee, + 0x5a, + 0x16, + 0x45, + 0xb2, + 0x76, + 0xd5, + 0x92, + 0xa1, + 0xb2, + 0x74, + 0xcb, + 0x8e, + 0xbf, + 0x87, + 0x87, + 0x0a, + 0x6f, + 0x9b, + 0xb4, + 0x20, + 0x3d, + 0xe7, + 0xb3, + 0x81, + 0xea, + 0xec, + 0xb2, + 0xa3, + 0x0b, + 0x22, + 0xa8, + 0x7f, + 0x99, + 0x24, + 0xa4, + 0x3c, + 0xc1, + 0x31, + 0x57, + 0x24, + 0xbd, + 0x83, + 0x8d, + 0x3a, + 0xaf, + 0xbf, + 0x8d, + 0xb7, + 0x0b, + 0x1a, + 0x2a, + 0x32, + 0x65, + 0xd5, + 0x1a, + 0xea, + 0x13, + 0x50, + 0x79, + 0xa3, + 0x23, + 0x1c, + 0xe6, + 0x60, + 0x93, + 0x2b, + 0x28, + 0x46, + 0xe4, + 0xd7, + 0x06, + 0x66, + 0xe1, + 0x91, + 0x5f, + 0x5c, + 0xb1, + 0xec, + 0xa4, + 0x6c, + 0xf3, + 0x25, + 0x96, + 0x5c, + 0xa1, + 0x6d, + 0x62, + 0x9f, + 0x57, + 0x5f, + 0xf2, + 0x8e, + 0x60, + 0x38, + 0x1b, + 0xe5, + 0x72, + 0x45, + 0x06, + 0xeb, + 0x4c, + 0x32, + 0x8a, + 0x95, + ] +}