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
19 changes: 19 additions & 0 deletions crates/utils/src/errors.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// LENGTH
const RLP_EMPTY_INPUT: felt252 = 'KKT: RlpEmptyInput';
const RLP_INPUT_TOO_SHORT: felt252 = 'KKT: RlpInputTooShort';

#[derive(Drop, Copy, PartialEq)]
enum RLPError {
RlpEmptyInput: felt252,
RlpInputTooShort: felt252,
}


impl RLPErrorIntoU256 of Into<RLPError, u256> {
fn into(self: RLPError) -> u256 {
match self {
RLPError::RlpEmptyInput(error_message) => error_message.into(),
RLPError::RlpInputTooShort(error_message) => error_message.into(),
}
}
}
32 changes: 31 additions & 1 deletion crates/utils/src/helpers.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use utils::constants::{
POW_256_16,
};
use keccak::u128_split;
use utils::num::{Zero, One, SizeOf};
use utils::math::Bitshift;


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

trait BytesSerde<T> {
/// Serialize/deserialize bytes into/from
/// an array of bytes.
fn serialize(self: @T, ref output: Array<u8>);
Quentash marked this conversation as resolved.
Show resolved Hide resolved
fn deserialize(self: @Span<u8>) -> Option<T>;
Quentash marked this conversation as resolved.
Show resolved Hide resolved
}

impl BytesSerdeU32Impl of BytesSerde<u32> {
fn serialize(self: @u32, ref output: Array<u8>) {}
fn deserialize(self: @Span<u8>) -> Option<u32> {
let len = (*self).len();
Quentash marked this conversation as resolved.
Show resolved Hide resolved
if len > 4 {
return Option::None(());
}
let offset: u32 = len - 1;
let mut result: u32 = 0;
let mut i: u32 = 0;
loop {
if i == len {
break ();
}
let byte: u32 = (*(*self).at(i)).into();
Quentash marked this conversation as resolved.
Show resolved Hide resolved
result += byte.shl(8 * (offset - i));

i += 1;
};
Option::Some(result)
}
}

#[generate_trait]
impl U256Impl of U256Trait {
Expand All @@ -522,4 +553,3 @@ impl U256Impl of U256Trait {
u256 { low: new_low, high: new_high }
}
}

1 change: 1 addition & 0 deletions crates/utils/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod eth_transaction;
mod rlp;
mod traits;
mod i256;
mod errors;

#[cfg(test)]
mod tests;
33 changes: 33 additions & 0 deletions crates/utils/src/num.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,22 @@ impl Felt252Zero of Zero<felt252> {
}
}

impl U32Zero of Zero<u32> {
#[inline(always)]
fn zero() -> u32 {
0
}

#[inline(always)]
fn is_zero(self: @u32) -> bool {
*self == Zero::zero()
}

#[inline(always)]
fn is_non_zero(self: @u32) -> bool {
!self.is_zero()
}
}

impl U128Zero of Zero<u128> {
#[inline(always)]
Expand Down Expand Up @@ -171,6 +187,23 @@ impl Felt252One of One<felt252> {
}
}

impl U32One of One<u32> {
#[inline(always)]
fn one() -> u32 {
1
}

#[inline(always)]
fn is_one(self: @u32) -> bool {
*self == One::one()
}

#[inline(always)]
fn is_non_one(self: @u32) -> bool {
!self.is_one()
}
}

impl U128One of One<u128> {
#[inline(always)]
fn one() -> u128 {
Expand Down
154 changes: 150 additions & 4 deletions crates/utils/src/rlp.cairo
Original file line number Diff line number Diff line change
@@ -1,7 +1,153 @@
// 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::errors::{RLPError, RLP_EMPTY_INPUT, RLP_INPUT_TOO_SHORT};
use utils::helpers::BytesSerde;

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

#[derive(Drop, PartialEq)]
enum RLPItem {
Bytes: Span<u8>,
// Should be Span<RLPItem> to allow for any depth/recursion, not yet supported by the compiler
List: Span<Span<u8>>
}

#[generate_trait]
impl RLPTypeImpl of RLPTypeTrait {
/// Returns RLPType from the leading byte
///
/// # Arguments
///
/// * `byte` - Leading byte
/// Return result with RLPType
fn from_byte(byte: u8) -> RLPType {
if byte <= 0x7f {
RLPType::String
} else if byte <= 0xb7 {
RLPType::StringShort
} else if byte <= 0xbf {
RLPType::StringLong
} else if byte <= 0xf7 {
RLPType::ListShort
} else {
Quentash marked this conversation as resolved.
Show resolved Hide resolved
RLPType::ListLong
}
}
}

/// RLP decodes a rlp encoded byte array
/// as described in https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/
///
/// # Arguments
///
/// * `input` - RLP encoded bytes
/// Return result with RLPItem and size of the decoded item
fn rlp_decode(input: Span<u8>) -> Result<(RLPItem, usize), RLPError> {
if input.len() == 0 {
return Result::Err(RLPError::RlpEmptyInput(RLP_EMPTY_INPUT));
}
let prefix = *input.at(0);
Quentash marked this conversation as resolved.
Show resolved Hide resolved

let rlp_type = RLPTypeTrait::from_byte(prefix);
match rlp_type {
RLPType::String => {
let mut arr = array![prefix];
Result::Ok((RLPItem::Bytes(arr.span()), 1))
},
RLPType::StringShort => {
let len = prefix.into() - 0x80;
if input.len() < len + 1 {
Quentash marked this conversation as resolved.
Show resolved Hide resolved
return Result::Err(RLPError::RlpInputTooShort(RLP_INPUT_TOO_SHORT));
}
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_of_len = prefix.into() - 0xb7;
Quentash marked this conversation as resolved.
Show resolved Hide resolved
if input.len() < len_of_len + 1 {
return Result::Err(RLPError::RlpInputTooShort(RLP_INPUT_TOO_SHORT));
}

let len_in_bytes = input.slice(1, len_of_len);
Quentash marked this conversation as resolved.
Show resolved Hide resolved
let len: u32 = len_in_bytes.deserialize().unwrap();
Quentash marked this conversation as resolved.
Show resolved Hide resolved
if input.len() < len + len_of_len + 1 {
return Result::Err(RLPError::RlpInputTooShort(RLP_INPUT_TOO_SHORT));
}

let res = input.slice(1 + len_of_len, len);
Quentash marked this conversation as resolved.
Show resolved Hide resolved

Result::Ok((RLPItem::Bytes(res), 1 + len_of_len + len))
},
RLPType::ListShort => {
let len = prefix.into() - 0xc0;
if input.len() < len + 1 {
return Result::Err(RLPError::RlpInputTooShort(RLP_INPUT_TOO_SHORT));
}

let mut list_input = input.slice(1, len);
let res = rlp_decode_list(ref list_input)?;
Result::Ok((RLPItem::List(res), 1 + len))
},
RLPType::ListLong => {
// Extract the amount of bytes representing the data payload length
let len_of_len = prefix.into() - 0xf7;
Quentash marked this conversation as resolved.
Show resolved Hide resolved
if input.len() < len_of_len + 1 {
return Result::Err(RLPError::RlpInputTooShort(RLP_INPUT_TOO_SHORT));
}

let len_in_bytes = input.slice(1, len_of_len);
let len: u32 = len_in_bytes.deserialize().unwrap();
if input.len() < len + len_of_len + 1 {
return Result::Err(RLPError::RlpInputTooShort(RLP_INPUT_TOO_SHORT));
}

let mut list_input = input.slice(1 + len_of_len, len);
let res = rlp_decode_list(ref list_input)?;
Result::Ok((RLPItem::List(res), 1 + len_of_len + len))
}
}
}

fn rlp_decode_list(ref input: Span<u8>) -> Result<Span<Span<u8>>, RLPError> {
let mut i = 0;
let len = input.len();
let mut output = ArrayTrait::new();
let mut decode_error: Option<RLPError> = Option::None;
Quentash marked this conversation as resolved.
Show resolved Hide resolved

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 res = rlp_decode(input);
if res.is_err() {
decode_error = Option::Some(res.unwrap_err());
Quentash marked this conversation as resolved.
Show resolved Hide resolved
break;
}
let (decoded, decoded_len) = res.unwrap();
Quentash 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;
};
if decode_error != Option::None {
Quentash marked this conversation as resolved.
Show resolved Hide resolved
return Result::Err(decode_error.unwrap());
}
Result::Ok(output.span())
}
1 change: 1 addition & 0 deletions crates/utils/src/tests.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ mod test_math;
mod test_num;
mod test_i256;
mod test_traits;
mod test_rlp;
13 changes: 12 additions & 1 deletion crates/utils/src/tests/test_helpers.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use utils::helpers;
use utils::helpers::{
SpanExtension, SpanExtensionTrait, ArrayExtension, ArrayExtensionTrait, U256Trait
SpanExtension, SpanExtensionTrait, ArrayExtension, ArrayExtensionTrait, U256Trait, BytesSerde
};
use debug::PrintTrait;

Expand Down Expand Up @@ -201,3 +201,14 @@ fn test_split_u256_into_u64_little() {
assert(high_l == 0x0000450000DEFA00, 'split mismatch');
assert(low_l == 0x00200400000000AD, 'split mismatch');
}

#[test]
#[available_gas(2000000000)]
fn test_bytes_serde_u32_deserialize() {
let input: Array<u8> = array![0xf4, 0x32, 0x15, 0x62];
let res: Option<u32> = input.span().deserialize();

assert(res != Option::None, 'should have a value');
let res = res.unwrap();
assert(res == 0xf4321562, 'wrong result value');
}
Loading