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
78 changes: 78 additions & 0 deletions crates/utils/src/bitwise.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use utils::math::{pow, pow_felt252};
Eikix marked this conversation as resolved.
Show resolved Hide resolved
use math::Oneable;
use zeroable::Zeroable;

// @notice Bitwise left shift
// @param num The number to be shifted
// @param shift The number of bits to shift
// @return The left shifted number
fn left_shift<
Eikix marked this conversation as resolved.
Show resolved Hide resolved
T,
impl TZeroable: Zeroable<T>,
impl TAdd: Add<T>,
impl TSub: Sub<T>,
impl TMul: Mul<T>,
impl TOneable: Oneable<T>,
impl TCopy: Copy<T>,
impl TDrop: Drop<T>
// TODO refactor shift type from T to usize
>(
num: T, shift: T
) -> T {
let two = TOneable::one() + TOneable::one();
num * pow(two, shift)
}

fn left_shift_felt252(num: felt252, shift: felt252) -> felt252 {
Eikix marked this conversation as resolved.
Show resolved Hide resolved
num * pow_felt252(2, shift)
}

// @notice Bitwise right shift
// @param num The number to be shifted
// @param shift The number of bits to shift
// @return The right shifted number
fn right_shift<
T,
impl TZeroable: Zeroable<T>,
impl TAdd: Add<T>,
impl TSub: Sub<T>,
impl TMul: Mul<T>,
impl TDiv: Div<T>,
impl TOneable: Oneable<T>,
impl TCopy: Copy<T>,
impl TDrop: Drop<T>
// TODO refactor shift type from T to usize
>(
num: T, shift: T
) -> T {
let two = TOneable::one() + TOneable::one();
num / pow(two, shift)
}

// @notice Bit length of a number
// @param num The number to be measured
// @return The number of bits in the number
fn bit_length<
Eikix marked this conversation as resolved.
Show resolved Hide resolved
T,
impl TZeroable: Zeroable<T>,
impl TPartialOrd: PartialOrd<T>,
impl TAddImpl: Add<T>,
impl TSub: Sub<T>,
impl TMul: Mul<T>,
impl TOneable: Oneable<T>,
impl TCopy: Copy<T>,
impl TDrop: Drop<T>
>(
num: T
) -> T {
let mut bit_position = TZeroable::zero();
let mut cur_n = TOneable::one();
loop {
if cur_n > num {
break ();
};
bit_position = bit_position + TOneable::one();
cur_n = left_shift(cur_n, TOneable::one());
};
bit_position
}
60 changes: 53 additions & 7 deletions crates/utils/src/eth_transaction.cairo
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
use starknet::EthAddress;
use utils::traits::{SpanDefault, EthAddressDefault, ContractAddressDefault};
use utils::rlp;
use utils::types::bytes::{Bytes, BytesTryIntoU256};
use utils::types::byte::Byte;
use utils::helpers::SpanExtensionTrait;
use traits::{Into, TryInto};
use debug::PrintTrait;

#[derive(Drop, Copy, Default)]
struct EthereumTransaction {
nonce: u128,
gas_price: u128,
gas_limit: u128,
destination: EthAddress,
amount: u256,
payload: Span<felt252>,
payload: Span<u8>,
tx_hash: u256,
v: u128,
r: u256,
s: u256,
}

trait EthTransaction {
#[generate_trait]
impl EthTransaction of EthTransactionTrait {
/// Decode a legacy Ethereum transaction
/// This function decodes a legacy Ethereum transaction in accordance with EIP-155.
/// It returns transaction details including nonce, gas price, gas limit, destination address, amount, payload,
/// transaction hash, and signature (v, r, s). The transaction hash is computed by keccak hashing the signed
/// transaction data, which includes the chain ID in accordance with EIP-155.
/// # Arguments
/// tx_data The raw transaction data
fn decode_legacy_tx(tx_data: Span<u8>) -> EthereumTransaction;
fn decode_legacy_tx(tx_data: Span<u8>) -> EthereumTransaction {
let eth_transaction = Default::default();
eth_transaction
}

/// Decode a modern Ethereum transaction
/// This function decodes a modern Ethereum transaction in accordance with EIP-2718.
Expand All @@ -30,22 +42,54 @@ trait EthTransaction {
/// transaction data, which includes the chain ID as part of the transaction data itself.
/// # Arguments
/// tx_data The raw transaction data
fn decode_tx(tx_data: Span<u8>) -> EthereumTransaction;
fn decode_tx(tx_data: Span<u8>) -> EthereumTransaction {
let mut eth_transaction: EthereumTransaction = Default::default();

let (items, size) = rlp::rlp_decode(tx_data).unwrap();

match items {
rlp::RLPItem::Bytes(value) => {},
rlp::RLPItem::List(list) => {
let nonce: u256 = (*list[0]).try_into().unwrap();
Eikix marked this conversation as resolved.
Show resolved Hide resolved
eth_transaction.nonce = nonce.try_into().unwrap();

let gas_price: u256 = (*list[1]).try_into().unwrap();
eth_transaction.gas_price = gas_price.try_into().unwrap();

let gas_limit: u256 = (*list[2]).try_into().unwrap();
eth_transaction.gas_limit = gas_limit.try_into().unwrap();

let destination: u256 = (*list[3]).try_into().unwrap();
eth_transaction.destination = destination.into();

let amount: u256 = (*list[4]).try_into().unwrap();
eth_transaction.amount = amount.into();

eth_transaction.payload = (*list[5]).try_into().unwrap();
},
}

eth_transaction
}

/// Check if a raw transaction is a legacy Ethereum transaction
/// This function checks if a raw transaction is a legacy Ethereum transaction by checking the transaction type
/// according to EIP-2718. If the transaction type is less than or equal to 0xc0, it's a legacy transaction.
/// # Arguments
/// - `tx_data` The raw transaction data
fn is_legacy_tx(tx_data: Span<u8>) -> bool;
//fn is_legacy_tx(tx_data: Span<u8>) -> bool;

/// Decode a raw Ethereum transaction
/// This function decodes a raw Ethereum transaction. It checks if the transaction
/// is a legacy transaction or a modern transaction, and calls the appropriate decode function
/// resp. `decode_legacy_tx` or `decode_tx` based on the result.
/// # Arguments
/// - `tx_data` The raw transaction data
fn decode(tx_data: Span<u8>) -> EthereumTransaction;
fn decode(tx_data: Span<u8>, r: u256, s: u256, v: u128) -> EthereumTransaction {
let mut eth_transaction: EthereumTransaction = EthTransaction::decode_tx(tx_data);

eth_transaction
}

/// Validate an Ethereum transaction
/// This function validates an Ethereum transaction by checking if the transaction
Expand All @@ -57,5 +101,7 @@ trait EthTransaction {
/// - `address` The ethereum address that is supposed to have signed the transaction
/// - `account_nonce` The nonce of the account
/// - `param tx_data` The raw transaction data
fn validate_eth_tx(address: EthAddress, account_nonce: u128, tx_data: Span<u8>) -> bool;
fn validate_eth_tx(address: EthAddress, account_nonce: u128, tx_data: Span<u8>) -> bool {
true
}
}
2 changes: 2 additions & 0 deletions crates/utils/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ mod eth_transaction;
mod rlp;
mod traits;
mod i256;
mod types;
mod bitwise;

#[cfg(test)]
mod tests;
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)
}
}
160 changes: 156 additions & 4 deletions crates/utils/src/rlp.cairo
Original file line number Diff line number Diff line change
@@ -1,7 +1,159 @@
// 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::types::bytes::{Bytes, BytesTryIntoU256};
use utils::types::byte::Byte;

// 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: Byte) -> 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: Bytes,
// 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<Bytes>
Eikix marked this conversation as resolved.
Show resolved Hide resolved
}

// @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: Bytes) -> Result<(RLPItem, usize), felt252> {
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: Bytes) -> Span<Bytes> {
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)
}
}
Loading