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(),
}
}
}
30 changes: 29 additions & 1 deletion crates/utils/src/helpers.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use utils::constants::{
use keccak::u128_split;
use traits::DivRem;
use integer::U32TryIntoNonZero;
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 @@ -506,6 +508,33 @@ impl SpanExtension<T, +Copy<T>, +Drop<T>> of SpanExtTrait<T> {
}
}

trait BytesSerde<T> {
/// Serialize/deserialize bytes into/from
/// an array of bytes.
fn deserialize(self: Span<u8>) -> Option<T>;
}

impl BytesSerdeU32Impl of BytesSerde<u32> {
fn deserialize(self: Span<u8>) -> Option<u32> {
let len = self.len();
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();
result += byte.shl(8 * (offset - i));

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

#[generate_trait]
impl U256Impl of U256Trait {
Expand All @@ -524,7 +553,6 @@ impl U256Impl of U256Trait {
u256 { low: new_low, high: new_high }
}
}

#[generate_trait]
impl ByteArrayExt of ByteArrayExTrait {
fn from_bytes(mut bytes: Span<u8>) -> ByteArray {
Expand Down
1 change: 1 addition & 0 deletions crates/utils/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod rlp;
mod traits;
mod i256;
mod storage;
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
152 changes: 148 additions & 4 deletions crates/utils/src/rlp.cairo
Original file line number Diff line number Diff line change
@@ -1,7 +1,151 @@
// 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 {
return Result::Err(RLPError::RlpInputTooShort(RLP_INPUT_TOO_SHORT));
}
let decoded_string = input.slice(1, len);

Result::Ok((RLPItem::Bytes(decoded_string), 1 + len))
},
RLPType::StringLong => {
let len_bytes_count = prefix.into() - 0xb7;
if input.len() <= len_bytes_count {
return Result::Err(RLPError::RlpInputTooShort(RLP_INPUT_TOO_SHORT));
}

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

let decoded_string = input.slice(1 + len_bytes_count, string_len);

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

let mut list_input = input.slice(1, len);
let decoded_list = rlp_decode_list(ref list_input)?;
Result::Ok((RLPItem::List(decoded_list), 1 + len))
},
RLPType::ListLong => {
let len_bytes_count = prefix.into() - 0xf7;
if input.len() <= len_bytes_count {
return Result::Err(RLPError::RlpInputTooShort(RLP_INPUT_TOO_SHORT));
}

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

let mut list_input = input.slice(1 + len_bytes_count, list_len);
let decoded_list = rlp_decode_list(ref list_input)?;
Result::Ok((RLPItem::List(decoded_list), 1 + len_bytes_count + list_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> = loop {
if i >= len {
break Option::None;
}

let res = rlp_decode(input);

let (decoded, decoded_len) = match res {
Result::Ok(res_dec) => { res_dec },
Result::Err(err) => { break Option::Some(err); }
};
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.is_some() {
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;
15 changes: 14 additions & 1 deletion crates/utils/src/tests/test_helpers.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use utils::helpers;
use utils::helpers::{SpanExtension, SpanExtTrait, ArrayExtension, ArrayExtTrait, U256Trait};
use utils::helpers::{
SpanExtension, SpanExtTrait, ArrayExtension, ArrayExtTrait, U256Trait, BytesSerde
};
use utils::helpers::{ByteArrayExTrait};
use debug::PrintTrait;

Expand Down Expand Up @@ -269,3 +271,14 @@ fn test_pack_bytes_ge_bytes31() {
i += 1;
};
}

#[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