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

dev: improve rlp.cairo to support recursive list #457

Merged
merged 21 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/gas_reports.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Set up Scarb
uses: software-mansion/setup-scarb@v1
with:
scarb-version: 2.3.0
scarb-version: 2.3.1

- name: Run compare_snapshot script
id: run-script
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/gas_snapshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Set up Scarb
uses: software-mansion/setup-scarb@v1
with:
scarb-version: 2.3.0
scarb-version: 2.3.1

- name: Generate gas snapshot
run: python scripts/gen_snapshot.py
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v3
- uses: software-mansion/setup-scarb@v1
with:
scarb-version: 2.3.0
scarb-version: 2.3.1
- run: scarb fmt --check
- run: scarb build
- run: scarb test
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
scarb 2.3.0
scarb 2.3.1
2 changes: 1 addition & 1 deletion Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = 1
[[package]]
name = "alexandria_storage"
version = "0.2.0"
source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=3356bf0c5c1a089167d7d3c28d543e195325e596#3356bf0c5c1a089167d7d3c28d543e195325e596"
source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f"
Eikix marked this conversation as resolved.
Show resolved Hide resolved

[[package]]
name = "contracts"
Expand Down
4 changes: 2 additions & 2 deletions Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ members = ["crates/*"]
[workspace.package]
description = "Kakarot is an (zk)-Ethereum Virtual Machine implementation written in Cairo."
documentation = "https://www.kakarot.org/"
cairo-version = "2.3.0"
cairo-version = "2.3.1"
Quentash marked this conversation as resolved.
Show resolved Hide resolved
version = "0.1.0"
readme = "README.md"
repository = "https://github.com/kkrt-labs/kakarot-ssj/"
license-file = "LICENSE"

[workspace.dependencies]
starknet = "2.3.0"
starknet = "2.3.1"

[workspace.tool.fmt]
sort-module-level-items = true
2 changes: 1 addition & 1 deletion crates/contracts/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ starknet.workspace = true
evm = { path = "../evm" }
openzeppelin = { path = "../openzeppelin" }
utils = { path = "../utils" }
alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "3356bf0c5c1a089167d7d3c28d543e195325e596" }
alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" }

[tool]
fmt.workspace = true
Expand Down
12 changes: 6 additions & 6 deletions crates/utils/src/errors.cairo
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
// LENGTH
const RLP_EMPTY_INPUT: felt252 = 'KKT: RlpEmptyInput';
const RLP_INPUT_TOO_SHORT: felt252 = 'KKT: RlpInputTooShort';
const RLP_EMPTY_INPUT: felt252 = 'KKT: EmptyInput';
const RLP_INPUT_TOO_SHORT: felt252 = 'KKT: InputTooShort';

#[derive(Drop, Copy, PartialEq)]
enum RLPError {
RlpEmptyInput: felt252,
RlpInputTooShort: felt252,
EmptyInput: felt252,
InputTooShort: 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(),
RLPError::EmptyInput(error_message) => error_message.into(),
RLPError::InputTooShort(error_message) => error_message.into(),
}
}
}
193 changes: 74 additions & 119 deletions crates/utils/src/rlp.cairo
Original file line number Diff line number Diff line change
@@ -1,47 +1,66 @@
use array::{Array, ArrayTrait, Span, SpanTrait};
use clone::Clone;
use utils::errors::{RLPError, RLP_EMPTY_INPUT, RLP_INPUT_TOO_SHORT};
use utils::helpers::{U32Trait, ByteArrayExTrait};
use utils::helpers::{U32Trait, ByteArrayExTrait, ArrayExtension};

// All possible RLP types
// Possible RLP types
#[derive(Drop, PartialEq)]
enum RLPType {
String,
StringShort,
StringLong,
ListShort,
ListLong,
List
}

#[derive(Drop, PartialEq)]
#[derive(Drop, Copy, 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>>
String: Span<u8>,
List: Span<RLPItem>
}

#[generate_trait]
impl RLPImpl of RLPTrait {
/// Returns RLPType from the leading byte
/// Returns RLPType from the leading byte with
/// its offset in the array as well as its size.
///
/// # Arguments
///
/// * `byte` - Leading byte of the RLP encoded data
/// Return result with RLPType
fn decode_type(byte: u8) -> RLPType {
if byte < 0x80 {
RLPType::String
} else if byte < 0xb8 {
RLPType::StringShort
} else if byte < 0xc0 {
RLPType::StringLong
} else if byte < 0xf8 {
RLPType::ListShort
} else {
RLPType::ListLong
/// * `input` - Array of byte to decode
/// # Returns
/// * `(RLPType, offset, size)` - A tuple containing the RLPType
/// the offset and the size of the RLPItem to decode
/// # Errors
/// * RLPError::EmptyInput - if the input is empty
/// * RLPError::InputTooShort - if the input is too short for a given
fn decode_type(input: Span<u8>) -> Result<(RLPType, u32, u32), RLPError> {
let input_len = input.len();
if input_len == 0 {
return Result::Err(RLPError::EmptyInput(RLP_EMPTY_INPUT));
}
}

let prefix_byte = *input[0];

if prefix_byte < 0x80 { // Char
Result::Ok((RLPType::String, 0, 1))
} else if prefix_byte < 0xb8 { // Short String
Result::Ok((RLPType::String, 1, prefix_byte.into() - 0x80))
} else if prefix_byte < 0xc0 { // Long String
let len_bytes_count: u32 = (prefix_byte - 0xb7).into();
if input_len <= len_bytes_count {
return Result::Err(RLPError::InputTooShort(RLP_INPUT_TOO_SHORT));
}
let string_len_bytes = input.slice(1, len_bytes_count);
Eikix marked this conversation as resolved.
Show resolved Hide resolved
let string_len: u32 = U32Trait::from_bytes(string_len_bytes).unwrap();

Result::Ok((RLPType::String, 1 + len_bytes_count, string_len))
} else if prefix_byte < 0xf8 { // Short List
Result::Ok((RLPType::List, 1, prefix_byte.into() - 0xc0))
} else { // Long List
let len_bytes_count = prefix_byte.into() - 0xf7;
if input.len() <= len_bytes_count {
return Result::Err(RLPError::InputTooShort(RLP_INPUT_TOO_SHORT));
}

let list_len_bytes = input.slice(1, len_bytes_count);
let list_len: u32 = U32Trait::from_bytes(list_len_bytes).unwrap();
Result::Ok((RLPType::List, 1 + len_bytes_count, list_len))
}
}

/// RLP encodes a ByteArray, which is the underlying type used to represent
/// string data in Cairo. Since RLP encoding is only used for eth_address
Expand Down Expand Up @@ -82,104 +101,40 @@ impl RLPImpl of RLPTrait {
/// 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 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);

let rlp_type = RLPTrait::decode_type(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 = U32Trait::from_bytes(string_len_bytes).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);
/// * `input` - Array of bytes to decode
/// # Returns
/// * `Span<RLPItem>` - Span of RLPItem
/// # Errors
/// * RLPError::InputTooShort - if the input is too short for a given
fn decode(input: Span<u8>) -> Result<Span<RLPItem>, RLPError> {
let mut output: Array<RLPItem> = Default::default();
Eikix marked this conversation as resolved.
Show resolved Hide resolved
let input_len = input.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 (rlp_type, offset, len) = RLPTrait::decode_type(input)?;

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));
}
if input_len < offset + len {
return Result::Err(RLPError::InputTooShort(RLP_INPUT_TOO_SHORT));
}

let list_len_bytes = input.slice(1, len_bytes_count);
let list_len: u32 = U32Trait::from_bytes(list_len_bytes).unwrap();
if input.len() < list_len + len_bytes_count + 1 {
return Result::Err(RLPError::RlpInputTooShort(RLP_INPUT_TOO_SHORT));
match rlp_type {
RLPType::String => { output.append(RLPItem::String(input.slice(offset, len))); },
RLPType::List => {
if len > 0 {
let res = RLPTrait::decode(input.slice(offset, len))?;
output.append(RLPItem::List(res));
} else {
output.append(RLPItem::List(array![].span()));
}

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 total_item_len = len + offset;
if total_item_len < input_len {
output
.concat(RLPTrait::decode(input.slice(total_item_len, input_len - total_item_len))?);
}

let res = RLPTrait::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())
}
Result::Ok(output.span())
}

Loading
Loading