diff --git a/src/encoding/src/lib.cairo b/src/encoding/src/lib.cairo index 2853b074..e6aec3b2 100644 --- a/src/encoding/src/lib.cairo +++ b/src/encoding/src/lib.cairo @@ -1,4 +1,5 @@ mod base64; +mod reversible; #[cfg(test)] mod tests; diff --git a/src/encoding/src/reversible.cairo b/src/encoding/src/reversible.cairo new file mode 100644 index 00000000..1858d7e5 --- /dev/null +++ b/src/encoding/src/reversible.cairo @@ -0,0 +1,245 @@ +use integer::u512; +use traits::DivRem; + +const SELECT_BYTE: u16 = 0x100; +const SELECT_BIT: u8 = 0b10; + +/// Implies that there is an underlying byte order for type T that can be reversed +trait ReversibleBytes { + /// Reverses the byte order or endianness of `self`. + /// For example, the word `0x1122_u16` is reversed into `0x2211_u16`. + /// # Returns + /// `T` - returns the byte reversal of `self` into the same type T + fn reverse_bytes(self: @T) -> T; +} + +/// Implies that there is an underlying bit order for type T that can be reversed +trait ReversibleBits { + /// Reverses the underlying ordering of the bit representation of `self`. + /// For example, the word `0b10111010_u8` is reversed into `0b01011101`. + /// # Returns + /// `T` - the bit-representation of `self` reversed into the same type T + fn reverse_bits(self: @T) -> T; +} + +#[inline] +fn reversing< + T, + impl TCopy: Copy, + impl TZeroable: Zeroable, + impl TTryInto: TryInto>, + impl TDivRem: DivRem, + impl TDrop: Drop, + impl TMulEq: MulEq, + impl TRem: Rem, + impl TAddEq: AddEq +>( + word: T, size: usize, step: T +) -> (T, T) { + let result = TZeroable::zero(); + reversing_partial_result(word, result, size, step) +} + +#[inline] +fn reversing_partial_result< + T, + impl TCopy: Copy, + impl TDivRem: DivRem, + impl TTryInto: TryInto>, + impl TDrop: Drop, + impl TMulEq: MulEq, + impl TRem: Rem, + impl TAddEq: AddEq +>( + mut word: T, mut onto: T, size: usize, step: T +) -> (T, T) { + let mut i: usize = 0; + loop { + if i == size { + break; + } + let (new_word, remainder) = DivRem::div_rem(word, step.try_into().unwrap()); + word = new_word; + onto *= step.into(); + onto += remainder; + i += 1; + }; + (onto, word) +} + +impl U8Reversible of ReversibleBytes { + fn reverse_bytes(self: @u8) -> u8 { + *self + } +} + +impl U8ReversibleBits of ReversibleBits { + fn reverse_bits(self: @u8) -> u8 { + let (result, _) = reversing(word: *self, size: 8, step: SELECT_BIT); + result + } +} + +impl U16Reversible of ReversibleBytes { + fn reverse_bytes(self: @u16) -> u16 { + let (result, _) = reversing(word: *self, size: 2, step: SELECT_BYTE); + result + } +} + +impl U16ReversibleBits of ReversibleBits { + fn reverse_bits(self: @u16) -> u16 { + let (result, _) = reversing(word: *self, size: 16, step: SELECT_BIT.into()); + result + } +} + +impl U32Reversible of ReversibleBytes { + fn reverse_bytes(self: @u32) -> u32 { + let (result, _) = reversing(word: *self, size: 4, step: SELECT_BYTE.into()); + result + } +} + +impl U32ReversibleBits of ReversibleBits { + fn reverse_bits(self: @u32) -> u32 { + let (result, _) = reversing(word: *self, size: 32, step: SELECT_BIT.into()); + result + } +} + +impl U64Reversible of ReversibleBytes { + fn reverse_bytes(self: @u64) -> u64 { + let (result, _) = reversing(word: *self, size: 8, step: SELECT_BYTE.into()); + result + } +} + +impl U64ReversibleBits of ReversibleBits { + fn reverse_bits(self: @u64) -> u64 { + let (result, _) = reversing(word: *self, size: 64, step: SELECT_BIT.into()); + result + } +} + +impl U128Reversible of ReversibleBytes { + fn reverse_bytes(self: @u128) -> u128 { + let (result, _) = reversing(word: *self, size: 16, step: SELECT_BYTE.into()); + result + } +} + +impl U128ReversibleBits of ReversibleBits { + fn reverse_bits(self: @u128) -> u128 { + let (result, _) = reversing(word: *self, size: 128, step: SELECT_BIT.into()); + result + } +} + +impl U256Reversible of ReversibleBytes { + fn reverse_bytes(self: @u256) -> u256 { + let u256{low, high } = *self; + let (low_reversed, _) = reversing(word: low, size: 16, step: SELECT_BYTE.into()); + let (high_reversed, _) = reversing(word: high, size: 16, step: SELECT_BYTE.into()); + u256 { low: high_reversed, high: low_reversed } + } +} + +impl U256ReversibleBits of ReversibleBits { + fn reverse_bits(self: @u256) -> u256 { + let u256{low, high } = *self; + let (low_reversed, _) = reversing(word: low, size: 128, step: SELECT_BIT.into()); + let (high_reversed, _) = reversing(word: high, size: 128, step: SELECT_BIT.into()); + u256 { low: high_reversed, high: low_reversed } + } +} + +impl U512Reversible of ReversibleBytes { + fn reverse_bytes(self: @u512) -> u512 { + let u512{limb0, limb1, limb2, limb3 } = *self; + let (limb0_reversed, _) = reversing(word: limb0, size: 16, step: SELECT_BYTE.into()); + let (limb1_reversed, _) = reversing(word: limb1, size: 16, step: SELECT_BYTE.into()); + let (limb2_reversed, _) = reversing(word: limb2, size: 16, step: SELECT_BYTE.into()); + let (limb3_reversed, _) = reversing(word: limb3, size: 16, step: SELECT_BYTE.into()); + u512 { + limb0: limb3_reversed, + limb1: limb2_reversed, + limb2: limb1_reversed, + limb3: limb0_reversed + } + } +} + +impl U512ReversibleBits of ReversibleBits { + fn reverse_bits(self: @u512) -> u512 { + let u512{limb0, limb1, limb2, limb3 } = *self; + let (limb0_reversed, _) = reversing(word: limb0, size: 128, step: SELECT_BIT.into()); + let (limb1_reversed, _) = reversing(word: limb1, size: 128, step: SELECT_BIT.into()); + let (limb2_reversed, _) = reversing(word: limb2, size: 128, step: SELECT_BIT.into()); + let (limb3_reversed, _) = reversing(word: limb3, size: 128, step: SELECT_BIT.into()); + u512 { + limb0: limb3_reversed, + limb1: limb2_reversed, + limb2: limb1_reversed, + limb3: limb0_reversed + } + } +} + +impl Bytes31Reversible of ReversibleBytes { + // Consider A and C consisting of 15 bytes each. B carries a single byte of data. + // Z is the zero padded remainder, then the following transformation is required: + // low: A B -> low_rev: C_rev B_rev + // high: C Z -> high_rev: A_rev Z + fn reverse_bytes(self: @bytes31) -> bytes31 { + let u256{low, high } = (*self).into(); + let (c_rev, _) = reversing(word: high, size: 15, step: SELECT_BYTE.into()); + let (low_rev, a) = reversing_partial_result( // pushes b_rev yielding low_rev + word: low, onto: c_rev, size: 1, step: SELECT_BYTE.into() + ); + let (high_rev, _) = reversing(word: a, size: 15, step: SELECT_BYTE.into()); + let felt: felt252 = u256 { low: low_rev, high: high_rev }.try_into().unwrap(); + felt.try_into().unwrap() + } +} + +impl Bytes31ReversibleBits of ReversibleBits { + fn reverse_bits(self: @bytes31) -> bytes31 { + let u256{low, high } = (*self).into(); + let (c_rev, _) = reversing(word: high, size: 120, step: SELECT_BIT.into()); + let (low_rev, a) = reversing_partial_result( // pushes b_rev yielding low_rev + word: low, onto: c_rev, size: 8, step: SELECT_BIT.into() + ); + let (high_rev, _) = reversing(word: a, size: 120, step: SELECT_BIT.into()); + let felt: felt252 = u256 { low: low_rev, high: high_rev }.try_into().unwrap(); + felt.try_into().unwrap() + } +} + +impl ArrayReversibleBytes, +Drop, +ReversibleBytes> of ReversibleBytes> { + fn reverse_bytes(self: @Array) -> Array { + let mut span = self.span(); + let mut result: Array = Default::default(); + loop { + match span.pop_back() { + Option::Some(value) => { result.append(value.reverse_bytes()); }, + Option::None => { break; } + } + }; + result + } +} + +impl ArrayReversibleBits, +Copy, +Drop> of ReversibleBits> { + fn reverse_bits(self: @Array) -> Array { + let mut span = self.span(); + let mut result: Array = Default::default(); + loop { + match span.pop_back() { + Option::Some(value) => { result.append(value.reverse_bits()); }, + Option::None => { break; } + } + }; + result + } +} diff --git a/src/encoding/src/tests.cairo b/src/encoding/src/tests.cairo index a9e8056d..4959d948 100644 --- a/src/encoding/src/tests.cairo +++ b/src/encoding/src/tests.cairo @@ -1 +1,2 @@ mod base64_test; +mod reversible_test; diff --git a/src/encoding/src/tests/reversible_test.cairo b/src/encoding/src/tests/reversible_test.cairo new file mode 100644 index 00000000..2d1690ed --- /dev/null +++ b/src/encoding/src/tests/reversible_test.cairo @@ -0,0 +1,217 @@ +use alexandria_encoding::reversible::{ReversibleBits, ReversibleBytes}; +use integer::u512; + +use debug::PrintTrait; + +#[test] +#[available_gas(1000000)] +fn test_reverse_bytes_u8() { + let t: u8 = 0b10111010; + let rev = t.reverse_bytes(); + assert(t == rev, 'not equal'); + assert(t == rev.reverse_bytes(), 'not equal'); +} + +#[test] +#[available_gas(1000000)] +fn test_reverse_bytes_u16() { + let t: u16 = 0x1122; + let rev = t.reverse_bytes(); + assert(rev == 0x2211, 'not equal'); + assert(rev.reverse_bytes() == t, 'not equal'); +} + +#[test] +#[available_gas(1000000)] +fn test_reverse_bytes_u32() { + let t: u32 = 0x11223344; + let rev = t.reverse_bytes(); + assert(rev == 0x44332211, 'not equal'); + assert(rev.reverse_bytes() == t, 'not equal'); +} + +#[test] +#[available_gas(1000000)] +fn test_reverse_bytes_u64() { + let t: u64 = 0x1122334455667788; + let rev = t.reverse_bytes(); + assert(rev == 0x8877665544332211, 'not equal'); + assert(rev.reverse_bytes() == t, 'not equal'); +} + +#[test] +#[available_gas(1000000)] +fn test_reverse_bytes_u128() { + let t: u128 = 0x112233445566778899aabbccddeeff00; + let rev = t.reverse_bytes(); + assert(rev == 0x00ffeeddccbbaa998877665544332211, 'not equal'); + assert(rev.reverse_bytes() == t, 'not equal'); +} + +#[test] +#[available_gas(10000000)] +fn test_reverse_bytes_u256() { + let t1: u128 = 0x101112131415161718191a1b1c1d1e1f; + let t2: u128 = 0x202122232425262728292a2b2c2d2e2f; + let t = u256 { low: t1, high: t2 }; + let rev = t.reverse_bytes(); + assert(rev == u256 { low: t2.reverse_bytes(), high: t1.reverse_bytes() }, 'not equal'); + assert(rev.reverse_bytes() == t, 'not equal'); +} + +#[test] +#[available_gas(10000000)] +fn test_reverse_bytes_u512() { + let t0: u128 = 0x101112131415161718191a1b1c1d1e1f; + let t1: u128 = 0x202122232425262728292a2b2c2d2e2f; + let t2: u128 = 0x303132333435363738393a3b3c3d3e3f; + let t3: u128 = 0x404142434445464748494a4b4c4d4e4f; + let t = u512 { limb0: t0, limb1: t1, limb2: t2, limb3: t3 }; + let t_rev = u512 { + limb0: t3.reverse_bytes(), + limb1: t2.reverse_bytes(), + limb2: t1.reverse_bytes(), + limb3: t0.reverse_bytes() + }; + let rev = t.reverse_bytes(); + assert(rev == t_rev, 'not equal'); + assert(rev.reverse_bytes() == t, 'not equal'); +} + +#[test] +#[available_gas(10000000)] +fn test_reverse_bytes_bytes31() { + let t1: u128 = 0x101112131415161718191a1b1c1d1e1f; + let t2: u128 = 0x202122232425262728292a2b2c2d2e; + let felt: felt252 = u256 { low: t1, high: t2 }.try_into().unwrap(); + let t: bytes31 = felt.try_into().unwrap(); + let t_rev_u256 = u256 { + low: 0x2e2d2c2b2a292827262524232221201f, high: 0x1e1d1c1b1a19181716151413121110, + }; + let t_rev_felt: felt252 = t_rev_u256.try_into().unwrap(); + let t_rev: bytes31 = t_rev_felt.try_into().unwrap(); + let rev = t.reverse_bytes(); + assert(rev == t_rev, 'not equal rev'); + assert(rev.reverse_bytes() == t, 'not equal'); +} + +#[test] +#[available_gas(10000000)] +fn test_reverse_bytes_array() { + let t: Array = array![0x1122, 0x3344, 0x5566, 0x7788, 0x99aa, 0xbbcc, 0xddee]; + let t_rev: Array = array![0xeedd, 0xccbb, 0xaa99, 0x8877, 0x6655, 0x4433, 0x2211]; + let rev = t.reverse_bytes(); + assert(rev == t_rev, 'not equal rev'); + assert(rev.reverse_bytes() == t, 'not equal'); +} + +#[test] +#[available_gas(1000000)] +fn test_reverse_bits_u8() { + let t: u8 = 0b10111010; + let t_rev: u8 = 0b01011101; + let rev = t.reverse_bits(); + assert(rev == t_rev, 'not equal rev'); + assert(rev.reverse_bits() == t, 'not equal'); +} + +#[test] +#[available_gas(1000000)] +fn test_reverse_bits_u16() { + let t: u16 = 0x11aa; + let t_rev: u16 = 0x5588; + let rev = t.reverse_bits(); + assert(rev == t_rev, 'not equal rev'); + assert(rev.reverse_bits() == t, 'not equal'); +} + +#[test] +#[available_gas(10000000)] +fn test_reverse_bits_u32() { + let t: u32 = 0x1111aaaa; + let t_rev: u32 = 0x55558888; + let rev = t.reverse_bits(); + assert(rev == t_rev, 'not equal rev'); + assert(rev.reverse_bits() == t, 'not equal'); +} + +#[test] +#[available_gas(10000000)] +fn test_reverse_bits_u64() { + let t: u64 = 0x11111111aaaaaaaa; + let t_rev: u64 = 0x5555555588888888; + let rev = t.reverse_bits(); + assert(rev == t_rev, 'not equal rev'); + assert(rev.reverse_bits() == t, 'not equal'); +} + +#[test] +#[available_gas(10000000)] +fn test_reverse_bits_u128() { + let t: u128 = 0x1111111111111111aaaaaaaaaaaaaaaa; + let t_rev: u128 = 0x55555555555555558888888888888888; + let rev = t.reverse_bits(); + assert(rev == t_rev, 'not equal rev'); + assert(rev.reverse_bits() == t, 'not equal'); +} + +#[test] +#[available_gas(100000000)] +fn test_reverse_bits_u256() { + let t1: u128 = 0x1111111111111111aaaaaaaaaaaaaaaa; + let t2: u128 = 0xcccccccccccccccc7777777777777777; + let t: u256 = u256 { low: t1, high: t2 }; + let t_rev: u256 = u256 { + low: 0xeeeeeeeeeeeeeeee3333333333333333, high: 0x55555555555555558888888888888888, + }; + let rev = t.reverse_bits(); + assert(rev == t_rev, 'not equal rev'); + assert(rev.reverse_bits() == t, 'not equal'); +} + +#[test] +#[available_gas(100000000)] +fn test_reverse_bits_u512() { + let t0: u128 = 0x1111111111111111aaaaaaaaaaaaaaaa; + let t1: u128 = 0xcccccccccccccccc7777777777777777; + let t2: u128 = 0x33333333333333334444444444444444; + let t3: u128 = 0xaaaaaaaaaaaaaaaaeeeeeeeeeeeeeeee; + let t: u512 = u512 { limb0: t0, limb1: t1, limb2: t2, limb3: t3 }; + let t_rev = u512 { + limb0: t3.reverse_bits(), + limb1: t2.reverse_bits(), + limb2: t1.reverse_bits(), + limb3: t0.reverse_bits(), + }; + let rev = t.reverse_bits(); + assert(rev == t_rev, 'not equal rev'); + assert(rev.reverse_bits() == t, 'not equal'); +} + +#[test] +#[available_gas(100000000)] +fn test_reverse_bits_bytes31() { + let t1: u128 = 0x123457bcde123457bcde123457bcde12; + let t2: u128 = 0x84c2aed3b784c2aed3b784c2aed3b7; + let t_u256: u256 = u256 { low: t1, high: t2 }; + let felt: felt252 = t_u256.try_into().unwrap(); + let t: bytes31 = felt.try_into().unwrap(); + let t_rev_u256: u256 = u256 { + low: 0xedcb754321edcb754321edcb75432148, high: 0x7b3dea2c487b3dea2c487b3dea2c48, + }; + let t_rev_felt: felt252 = t_rev_u256.try_into().unwrap(); + let t_rev: bytes31 = t_rev_felt.try_into().unwrap(); + let rev = t.reverse_bits(); + assert(rev == t_rev, 'not equal rev'); + assert(rev.reverse_bits() == t, 'not equal'); +} + +#[test] +#[available_gas(100000000)] +fn test_reverse_bits_array() { + let t: Array = array![0x1234, 0x57bc, 0xde84, 0xc2ae, 0xd3b7]; + let t_rev: Array = array![0xedcb, 0x7543, 0x217b, 0x3dea, 0x2c48]; + let rev = t.reverse_bits(); + assert(rev == t_rev, 'not equal rev'); + assert(rev.reverse_bits() == t, 'not equal'); +}