Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of rlp::decode() #405

Merged
merged 18 commits into from
Oct 18, 2023
Merged
29 changes: 29 additions & 0 deletions crates/utils/src/helpers.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use utils::constants::{
POW_256_16,
};
use keccak::u128_split;
use utils::math::Bitshift;


/// Ceils a number of bits to the next word (32 bytes)
Expand Down Expand Up @@ -504,6 +505,34 @@ impl SpanExtension<T, +Copy<T>, +Drop<T>> of SpanExtensionTrait<T> {
}
}

impl SpanU8TryIntoU256 of TryInto<Span<u8>, u256> {
fn try_into(self: Span<u8>) -> Option<u256> {
let len = self.len();
if len > 32 {
return Option::None(());
}

if self.is_empty() {
return Option::Some(0);
}

let offset = len.into() - 1;
let mut result: u256 = 0;
let mut i: usize = 0;
loop {
if i >= len {
break ();
}
let byte: u256 = (*self.at(i)).into();
result += Bitshift::shl(byte, 8 * (offset - i.into()));
Quentash marked this conversation as resolved.
Show resolved Hide resolved

i += 1;
};

Option::Some(result)
}
}


#[generate_trait]
impl U256Impl of U256Trait {
Expand Down
26 changes: 26 additions & 0 deletions crates/utils/src/math.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use integer::{
u256, u256_overflow_mul, u256_overflowing_add, u512, BoundedInt, u128_overflowing_mul
};
use math::Oneable;
Quentash marked this conversation as resolved.
Show resolved Hide resolved

trait Exponentiation<T> {
/// Raise a number to a power.
Expand Down Expand Up @@ -231,3 +232,28 @@ impl U128WrappingBitshiftImpl of WrappingBitshift<u128> {
}
}

fn pow<
Quentash marked this conversation as resolved.
Show resolved Hide resolved
T,
impl TZeroable: Zeroable<T>,
impl TSub: Sub<T>,
impl TMul: Mul<T>,
impl TOneable: Oneable<T>,
impl TCopy: Copy<T>,
impl TDrop: Drop<T>
>(
base: T, mut exp: T
) -> T {
if exp.is_zero() {
TOneable::one()
} else {
base * pow(base, exp - TOneable::one())
}
}

fn pow_felt252(base: felt252, exp: felt252) -> felt252 {
Quentash marked this conversation as resolved.
Show resolved Hide resolved
if exp == 0 {
1
} else {
base * pow_felt252(base, exp - 1)
}
}
159 changes: 155 additions & 4 deletions crates/utils/src/rlp.cairo
Original file line number Diff line number Diff line change
@@ -1,7 +1,158 @@
// Reproduce the original rlp.cairo from https://github.com/kkrt-labs/kakarot/blob/7ec7a96074394ddb592a2b6fbea279c6c5cb25a6/src/utils/rlp.cairo#L18
// By using new Cairo idiomatic ways: Traits, Struct and impl blocks.
use result::ResultTrait;
use option::OptionTrait;
use array::{Array, ArrayTrait, Span, SpanTrait};
use clone::Clone;
use traits::{Into, TryInto};
use utils::helpers::SpanU8TryIntoU256;

// We can reuse https://github.com/HerodotusDev/cairo-lib/blob/main/src/encoding/rlp.cairo
// Check before reusing that it works soundly
// @notice Enum with all possible RLP types
Quentash marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Drop, PartialEq)]
enum RLPType {
Quentash marked this conversation as resolved.
Show resolved Hide resolved
String: (),
StringShort: (),
StringLong: (),
ListShort: (),
ListLong: (),
}

#[generate_trait]
impl RLPTypeImpl of RLPTypeTrait {
// @notice Returns RLPType from the leading byte
// @param byte Leading byte
// @return Result with RLPType
fn from_byte(byte: u8) -> Result<RLPType, felt252> {
if byte <= 0x7f {
Result::Ok(RLPType::String(()))
} else if byte <= 0xb7 {
Result::Ok(RLPType::StringShort(()))
} else if byte <= 0xbf {
Result::Ok(RLPType::StringLong(()))
} else if byte <= 0xf7 {
Result::Ok(RLPType::ListShort(()))
} else if byte <= 0xff {
Result::Ok(RLPType::ListLong(()))
} else {
Quentash marked this conversation as resolved.
Show resolved Hide resolved
Result::Err('Invalid byte')
}
}
}

// @notice Represent a RLP item
#[derive(Drop)]
Quentash marked this conversation as resolved.
Show resolved Hide resolved
enum RLPItem {
Bytes: Span<u8>,
// Should be Span<RLPItem> to allow for any depth/recursion, not yet supported by the compiler
enitrat marked this conversation as resolved.
Show resolved Hide resolved
List: Span<Span<u8>>
}

// @notice RLP decodes a rlp encoded byte array
Quentash marked this conversation as resolved.
Show resolved Hide resolved
// @param input RLP encoded bytes
// @return Result with RLPItem and size of the decoded item
fn rlp_decode(input: Span<u8>) -> Result<(RLPItem, usize), felt252> {
Eikix marked this conversation as resolved.
Show resolved Hide resolved
let prefix = *input.at(0);
Quentash marked this conversation as resolved.
Show resolved Hide resolved

// Unwrap is impossible to panic here
let rlp_type = RLPTypeTrait::from_byte(prefix).unwrap();
match rlp_type {
RLPType::String(()) => {
Quentash marked this conversation as resolved.
Show resolved Hide resolved
let mut arr = array![prefix];
Result::Ok((RLPItem::Bytes(arr.span()), 1))
},
RLPType::StringShort(()) => {
let len = prefix.into() - 0x80;
let res = input.slice(1, len);
Eikix marked this conversation as resolved.
Show resolved Hide resolved

Result::Ok((RLPItem::Bytes(res), 1 + len))
},
RLPType::StringLong(()) => {
let len_len = prefix.into() - 0xb7;
Quentash marked this conversation as resolved.
Show resolved Hide resolved
let len_span = input.slice(1, len_len);
Quentash marked this conversation as resolved.
Show resolved Hide resolved

// Bytes => u256 => u32
let len256: u256 = len_span.try_into().unwrap();
Quentash marked this conversation as resolved.
Show resolved Hide resolved
let len: u32 = len256.try_into().unwrap();
Quentash marked this conversation as resolved.
Show resolved Hide resolved
let res = input.slice(1 + len_len, len);

Result::Ok((RLPItem::Bytes(res), 1 + len_len + len))
},
RLPType::ListShort(()) => {
let len = prefix.into() - 0xc0;
let mut in = input.slice(1, len);
let res = rlp_decode_list(ref in);
Result::Ok((RLPItem::List(res), 1 + len))
},
RLPType::ListLong(()) => {
let len_len = prefix.into() - 0xf7;
let len_span = input.slice(1, len_len);

// Bytes => u256 => u32
let len256: u256 = len_span.try_into().unwrap();
let len: u32 = len256.try_into().unwrap();
let mut in = input.slice(1 + len_len, len);
let res = rlp_decode_list(ref in);
Result::Ok((RLPItem::List(res), 1 + len_len + len))
}
}
}

fn rlp_decode_list(ref input: Span<u8>) -> Span<Span<u8>> {
let mut i = 0;
let len = input.len();
let mut output = ArrayTrait::new();

loop {
Quentash marked this conversation as resolved.
Show resolved Hide resolved
if i >= len {
break ();
Quentash marked this conversation as resolved.
Show resolved Hide resolved
}

let (decoded, decoded_len) = rlp_decode(input).unwrap();
enitrat marked this conversation as resolved.
Show resolved Hide resolved
match decoded {
RLPItem::Bytes(b) => {
output.append(b);
input = input.slice(decoded_len, input.len() - decoded_len);
},
RLPItem::List(_) => { panic_with_felt252('Recursive list not supported'); }
}
i += decoded_len;
};
output.span()
}

impl RLPItemPartialEq of PartialEq<RLPItem> {
Quentash marked this conversation as resolved.
Show resolved Hide resolved
fn eq(lhs: @RLPItem, rhs: @RLPItem) -> bool {
match lhs {
RLPItem::Bytes(b) => {
match rhs {
RLPItem::Bytes(b2) => { b == b2 },
RLPItem::List(_) => false
}
},
RLPItem::List(l) => {
match rhs {
RLPItem::Bytes(_) => false,
RLPItem::List(l2) => {
let len_l = (*l).len();
if len_l != (*l2).len() {
return false;
}
let mut i: usize = 0;
loop {
if i >= len_l {
break true;
}
if (*l).at(i) != (*l2).at(i) {
break false;
}
i += 1;
}
}
}
}
}
}

fn ne(lhs: @RLPItem, rhs: @RLPItem) -> bool {
// TODO optimize
!(lhs == rhs)
}
}
1 change: 1 addition & 0 deletions crates/utils/src/tests.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ mod test_helpers;
mod test_math;
mod test_i256;
mod test_traits;
mod test_rlp;
2 changes: 1 addition & 1 deletion crates/utils/src/tests/test_math.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use integer::{u256_overflowing_add, BoundedInt, u512};
use utils::math::{
Exponentiation, WrappingExponentiation, u256_wide_add, Bitshift, WrappingBitshift
Exponentiation, WrappingExponentiation, u256_wide_add, Bitshift, WrappingBitshift, pow
Quentash marked this conversation as resolved.
Show resolved Hide resolved
};

#[test]
Expand Down
Loading