Skip to content

Commit

Permalink
fixed keccak hasher wrong padding
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagofneto committed Oct 18, 2023
1 parent 6790a2d commit 04d93ba
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 65 deletions.
56 changes: 35 additions & 21 deletions src/data_structures/eth_mpt.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -68,29 +68,36 @@ impl MPTImpl of MPTTrait {

let node = *proof.at(proof_index);

let hash = MPTTrait::hash_rlp_node(node);
assert(hash == current_hash, 'Element not matching');

// If it's not the last node and more than 9 words, it must be a branch node
let decoded = if proof_index != proof_len - 1 && node.len() > 9 {
let (decoded, rlp_byte_len) = if proof_index != proof_len - 1 && node.len() > 9 {
let current_nibble = (key / key_pow2) & 0xf;
// Unwrap impossible to fail, as we are masking with 0xf, meaning the result is always a nibble
match MPTTrait::lazy_rlp_decode_branch_node(
node, current_nibble.try_into().unwrap()
) {
Result::Ok(decoded) => decoded,
Result::Ok(d) => d,
Result::Err(e) => {
break Result::Err(e);
}
}
} else {
match MPTTrait::decode_rlp_node(node) {
Result::Ok(decoded) => decoded,
Result::Ok(d) => d,
Result::Err(e) => {
break Result::Err(e);
}
}
};


let mut last_word_byte_len = rlp_byte_len % 8;
if last_word_byte_len == 0 {
last_word_byte_len = 8;
}

let hash = MPTTrait::hash_rlp_node(node, last_word_byte_len);
assert(hash == current_hash, 'Element not matching');

match decoded {
MPTNode::Branch((
nibbles, value
Expand Down Expand Up @@ -234,9 +241,9 @@ impl MPTImpl of MPTTrait {

// @notice Decodes an RLP encoded node
// @param rlp RLP encoded node
// @return Result with the decoded node
fn decode_rlp_node(rlp: Words64) -> Result<MPTNode, felt252> {
let (item, _) = rlp_decode(rlp)?;
// @return Result with the decoded node and the RLP byte length
fn decode_rlp_node(rlp: Words64) -> Result<(MPTNode, usize), felt252> {
let (item, rlp_byte_len) = rlp_decode(rlp)?;
match item {
RLPItem::Bytes(_) => Result::Err('Invalid RLP for node'),
RLPItem::List(l) => {
Expand All @@ -247,7 +254,7 @@ impl MPTImpl of MPTTrait {
loop {
if i == 16 {
let (value, _) = *l.at(16);
break Result::Ok(MPTNode::Branch((nibble_hashes.span(), value)));
break Result::Ok((MPTNode::Branch((nibble_hashes.span(), value)), rlp_byte_len));
}

let (current_hash, _) = (*l.at(i));
Expand All @@ -266,21 +273,21 @@ impl MPTImpl of MPTTrait {
if prefix == 0 {
match second.try_into() {
Option::Some(n) => Result::Ok(
MPTNode::Extension((first, n, 2, n_nibbles - 1))
(MPTNode::Extension((first, n, 2, n_nibbles - 1)), rlp_byte_len)
),
Option::None(_) => Result::Err('Invalid next node')
}
} else if prefix == 1 {
match second.try_into() {
Option::Some(n) => Result::Ok(
MPTNode::Extension((first, n, 1, n_nibbles))
(MPTNode::Extension((first, n, 1, n_nibbles)), rlp_byte_len)
),
Option::None(_) => Result::Err('Invalid next node')
}
} else if prefix == 2 {
Result::Ok(MPTNode::Leaf((first, second, 2, n_nibbles - 1)))
Result::Ok((MPTNode::Leaf((first, second, 2, n_nibbles - 1)), rlp_byte_len))
} else if prefix == 3 {
Result::Ok(MPTNode::Leaf((first, second, 1, n_nibbles)))
Result::Ok((MPTNode::Leaf((first, second, 1, n_nibbles)), rlp_byte_len))
} else {
Result::Err('Invalid RLP prefix')
}
Expand All @@ -292,18 +299,25 @@ impl MPTImpl of MPTTrait {
}


fn lazy_rlp_decode_branch_node(rlp: Words64, current_nibble: u8) -> Result<MPTNode, felt252> {
let hash_words = rlp_decode_list_lazy(rlp, array![current_nibble.into()].span())?;
match (*hash_words.at(0)).try_into() {
Option::Some(h) => Result::Ok(MPTNode::LazyBranch(h)),
Option::None(_) => Result::Err('Invalid hash')
fn lazy_rlp_decode_branch_node(rlp: Words64, current_nibble: u8) -> Result<(MPTNode, usize), felt252> {
let (lazy_item, rlp_byte_len) = rlp_decode_list_lazy(rlp, array![current_nibble.into()].span())?;
match lazy_item {
RLPItem::Bytes(_) => Result::Err('Invalid RLP for node'),
RLPItem::List(l) => {
let (hash_words, _) = *l.at(0);
match (hash_words).try_into() {
Option::Some(h) => Result::Ok((MPTNode::LazyBranch(h), rlp_byte_len)),
Option::None(_) => Result::Err('Invalid hash')
}
}
}
}

// @notice keccak256 hashes an RLP encoded node
// @param rlp RLP encoded node
// @param last_word_bytes number of bytes in the last worf of the RLP encoded node
// @return keccak256 hash of the node
fn hash_rlp_node(rlp: Words64) -> u256 {
keccak_cairo_words64(rlp)
fn hash_rlp_node(rlp: Words64, last_word_bytes: usize) -> u256 {
keccak_cairo_words64(rlp, last_word_bytes)
}
}
17 changes: 12 additions & 5 deletions src/data_structures/tests/test_eth_mpt.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ fn test_decode_rlp_node_branch() {
]
.span();

let decoded = MPTTrait::decode_rlp_node(rlp_node.span()).unwrap();
let (decoded, rlp_byte_len) = MPTTrait::decode_rlp_node(rlp_node.span()).unwrap();
assert(rlp_byte_len == 66 * 8 + 4, 'Wrong RLP len');
match decoded {
MPTNode::Branch((
hashes, value
Expand Down Expand Up @@ -197,7 +198,9 @@ fn test_lazy_rlp_node_branch() {

let expected = 0xE7420366C27811B1DAB792630039C1CB6B504B4ED003E19E05B055663B68B020;

let decoded = MPTTrait::lazy_rlp_decode_branch_node(rlp_node.span(), 0xa).unwrap();
let (decoded, rlp_byte_len) = MPTTrait::lazy_rlp_decode_branch_node(rlp_node.span(), 0xa).unwrap();
assert(rlp_byte_len == 66 * 8 + 4, 'Wrong RLP len');

let expected_node = MPTNode::LazyBranch(expected);
assert(decoded == expected_node, 'Lazy branch node diffes');
}
Expand Down Expand Up @@ -236,7 +239,9 @@ fn test_decode_rlp_node_leaf_odd() {
0xc71a5df8340f
];

let decoded = MPTTrait::decode_rlp_node(rlp_node.span()).unwrap();
let (decoded, rlp_byte_len) = MPTTrait::decode_rlp_node(rlp_node.span()).unwrap();
assert(rlp_byte_len == 13 * 8, 'Wrong RLP len');

let expected_node = MPTNode::Leaf((expected_key_end.span(), expected_value.span(), 1, 57));
assert(decoded == expected_node, 'Even leaf node differs');
}
Expand Down Expand Up @@ -275,7 +280,9 @@ fn test_decode_rlp_node_leaf_even() {
0xc71a5df8340f
];

let decoded = MPTTrait::decode_rlp_node(rlp_node.span()).unwrap();
let (decoded, rlp_byte_len) = MPTTrait::decode_rlp_node(rlp_node.span()).unwrap();
assert(rlp_byte_len == 13 * 8, 'Wrong RLP len');

let expected_node = MPTNode::Leaf((expected_key_end.span(), expected_value.span(), 2, 56));
assert(decoded == expected_node, 'Even leaf node differs');
}
Expand All @@ -299,7 +306,7 @@ fn test_hash_rlp_node() {
0xc71a5df8340f6249
];

let hash = MPTTrait::hash_rlp_node(rlp_node.span());
let hash = MPTTrait::hash_rlp_node(rlp_node.span(), 8);
assert(
hash == 0x035F9A54E8BEE015293EB9791C7FEC6A4A111DB8B32464597B6F8E63B1167FA1,
'Wrong node rlp hash'
Expand Down
12 changes: 8 additions & 4 deletions src/encoding/rlp.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ fn rlp_decode_list(ref input: Words64, len: usize) -> Result<Span<(Words64, usiz
}
}

fn rlp_decode_list_lazy(input: Words64, lazy: Span<usize>) -> Result<Span<Words64>, felt252> {
fn rlp_decode_list_lazy(input: Words64, lazy: Span<usize>) -> Result<(RLPItem, usize), felt252> {
let mut output = ArrayTrait::new();
let mut lazy_index = 0;

Expand Down Expand Up @@ -173,9 +173,11 @@ fn rlp_decode_list_lazy(input: Words64, lazy: Span<usize>) -> Result<Span<Words6
}
};

let rlp_byte_len = current_input_index + len;

loop {
if output.len() == lazy.len() {
break Result::Ok(output.span());
break Result::Ok((RLPItem::List(output.span()), rlp_byte_len));
}

if current_input_index >= len {
Expand Down Expand Up @@ -230,8 +232,10 @@ fn rlp_decode_list_lazy(input: Words64, lazy: Span<usize>) -> Result<Span<Words6
let current_word = current_input_index / 8;
let current_word_offset = 7 - (current_input_index % 8);
let start = current_word * 8 + current_word_offset;
let decoded = input.slice_le(start, item_len.try_into().unwrap());
output.append(decoded);

let item_len = item_len.try_into().unwrap();
let decoded = input.slice_le(start, item_len);
output.append((decoded, item_len));
}

current_input_index += item_len.try_into().unwrap();
Expand Down
40 changes: 23 additions & 17 deletions src/encoding/tests/test_rlp.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -166,23 +166,25 @@ fn test_rlp_decode_short_list() {
#[available_gas(99999999)]
fn test_rlp_lazy_decode_short_list() {
let mut arr = array![0x45834289353583c9, 0x9238];
let rlp_byte_len = 10;

let res = rlp_decode_list_lazy(arr.span(), array![].span()).unwrap();
assert(res.is_empty(), 'Wrong value indexes: empty');
let expected_res = (RLPItem::List(array![].span()), rlp_byte_len);
assert(res == expected_res, 'Wrong value indexes: empty');

let res = rlp_decode_list_lazy(arr.span(), array![1].span()).unwrap();
let expected_res = array![array![0x42].span()].span();
let expected_res = (RLPItem::List(array![(array![0x42].span(), 1)].span()), rlp_byte_len);
assert(res == expected_res, 'Wrong value indexes: 1');

let res = rlp_decode_list_lazy(arr.span(), array![0, 1, 2].span()).unwrap();
let mut expected_res = array![
array![0x893535].span(), array![0x42].span(), array![0x923845].span()
let mut expected_res = (RLPItem::List(array![
(array![0x893535].span(), 3), (array![0x42].span(), 1), (array![0x923845].span(), 3)
]
.span();
.span()), rlp_byte_len);
assert(res == expected_res, 'Wrong value: indexes: 0, 1, 2');

let res = rlp_decode_list_lazy(arr.span(), array![0, 2].span()).unwrap();
let mut expected_res = array![array![0x893535].span(), array![0x923845].span()].span();
let mut expected_res = (RLPItem::List(array![(array![0x893535].span(), 3), (array![0x923845].span(), 3)].span()), rlp_byte_len);
assert(res == expected_res, 'Wrong value: indexes: 0, 2');
}

Expand Down Expand Up @@ -421,6 +423,7 @@ fn test_rlp_lazy_decode_long_list() {
0x8fea4837d02e2498,
0x805fb3e2
];
let rlp_byte_len = 66 * 8 + 4;

let mut expected_res_full = array![
array![0x1b7a06b509cf7077, 0x75818924a962df35, 0xb4cd681fadecaece, 0xf44ac1730c4044a8]
Expand Down Expand Up @@ -460,25 +463,26 @@ fn test_rlp_lazy_decode_long_list() {
];

let res = rlp_decode_list_lazy(arr.span(), array![].span()).unwrap();
assert(res.is_empty(), 'Wrong value indexes: empty');
let expected_res = (RLPItem::List(array![].span()), rlp_byte_len);
assert(res == expected_res, 'Wrong value indexes: empty');

let res = rlp_decode_list_lazy(arr.span(), array![0].span()).unwrap();
let expected_res = array![*expected_res_full.at(0)].span();
let expected_res = (RLPItem::List(array![(*expected_res_full.at(0), 32)].span()), rlp_byte_len);
assert(res == expected_res, 'Wrong value indexes: 0');

let res = rlp_decode_list_lazy(arr.span(), array![1].span()).unwrap();
let expected_res = array![*expected_res_full.at(1)].span();
let expected_res = (RLPItem::List(array![(*expected_res_full.at(1), 32)].span()), rlp_byte_len);
assert(res == expected_res, 'Wrong value indexes: 1');

let res = rlp_decode_list_lazy(arr.span(), array![0xa].span()).unwrap();
let expected_res = array![*expected_res_full.at(0xa)].span();
let expected_res = (RLPItem::List(array![(*expected_res_full.at(0xa), 32)].span()), rlp_byte_len);
assert(res == expected_res, 'Wrong value indexes: 10');

let res = rlp_decode_list_lazy(arr.span(), array![0x5, 0x9, 0xf].span()).unwrap();
let expected_res = array![
*expected_res_full.at(0x5), *expected_res_full.at(0x9), *expected_res_full.at(0xf)
let expected_res = (RLPItem::List(array![
(*expected_res_full.at(0x5), 32), (*expected_res_full.at(0x9), 32), (*expected_res_full.at(0xf), 32)
]
.span();
.span()), rlp_byte_len);
assert(res == expected_res, 'Wrong value indexes: 5, 9, 15');
}

Expand Down Expand Up @@ -544,6 +548,7 @@ fn test_rlp_lazy_decode_list_long_string() {
0x5663776535476567,
0xfa77645733,
];
let rlp_byte_len = 11 * 8 + 5;

let expected_res_full = array![
array![0x57b94f7235e356ac, 0x1d27207f0b03476f, 0x3aab1f4760f75aaf, 0xed276fa9c2173ae5,]
Expand All @@ -561,17 +566,18 @@ fn test_rlp_lazy_decode_list_long_string() {
];

let res = rlp_decode_list_lazy(arr.span(), array![].span()).unwrap();
assert(res.is_empty(), 'Wrong value indexes: empty');
let expected_res = (RLPItem::List(array![].span()), rlp_byte_len);
assert(res == expected_res, 'Wrong value indexes: empty');

let res = rlp_decode_list_lazy(arr.span(), array![0].span()).unwrap();
let expected_res = array![*expected_res_full.at(0)].span();
let expected_res = (RLPItem::List(array![(*expected_res_full.at(0), 32)].span()), rlp_byte_len);
assert(res == expected_res, 'Wrong value indexes: 0');

let res = rlp_decode_list_lazy(arr.span(), array![1].span()).unwrap();
let expected_res = array![*expected_res_full.at(1)].span();
let expected_res = (RLPItem::List(array![(*expected_res_full.at(1), 56)].span()), rlp_byte_len);
assert(res == expected_res, 'Wrong value indexes: 1');

let res = rlp_decode_list_lazy(arr.span(), array![0, 1].span()).unwrap();
let expected_res = array![*expected_res_full.at(0), *expected_res_full.at(1)].span();
let expected_res = (RLPItem::List(array![(*expected_res_full.at(0), 32), (*expected_res_full.at(1), 56)].span()), rlp_byte_len);
assert(res == expected_res, 'Wrong value indexes: 0, 1');
}
29 changes: 13 additions & 16 deletions src/hashing/keccak.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,30 @@ const EMPTY_KECCAK: u256 = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bf

// @notice Wrapper arround cairo_keccak that format the input for compatibility with EVM
// @param words The input data, as a list of 64-bit little-endian words
// @param last_word_bytes Number of bytes in the last word
// @return The keccak hash of the input, matching the output of the EVM's keccak256 opcode
fn keccak_cairo_words64(words: Words64) -> u256 {
fn keccak_cairo_words64(words: Words64, last_word_bytes: usize) -> u256 {
if words.is_empty() {
return EMPTY_KECCAK;
}

let n = words.len();

let mut keccak_input = ArrayTrait::new();
let mut i: usize = 0;
if n > 1 {
loop {
if i >= n - 1 {
break ();
}
keccak_input.append(*words.at(i));
i += 1;
};
}
loop {
if i >= n - 1 {
break ();
}
keccak_input.append(*words.at(i));
i += 1;
};

let mut last = *words.at(n - 1);
let mut last_word_bytes = bytes_used_u64(last);
if last_word_bytes == 8 {
keccak_input.append(last);
last = 0;
last_word_bytes = 0;
cairo_keccak(ref keccak_input, 0, 0)
} else {
cairo_keccak(ref keccak_input, last, last_word_bytes)
}

cairo_keccak(ref keccak_input, last, last_word_bytes)
}
4 changes: 2 additions & 2 deletions src/hashing/tests/test_keccak.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use cairo_lib::hashing::keccak::keccak_cairo_words64;
fn test_keccak_cairo_word64_full_byte() {
let input = array![0xffffffffffffffff];

let res = keccak_cairo_words64(input.span());
let res = keccak_cairo_words64(input.span(), 8);
assert(
res == 0xAF7D4E460ACF8E540E682A9EE91EA1C08C1615C3889D75EB0A70660A4BFB0BAD,
'Keccak output not matching'
Expand All @@ -17,7 +17,7 @@ fn test_keccak_cairo_word64_full_byte() {
fn test_keccak_cairo_word64_remainder() {
let mut input = array![0x23FDAE89F78C76AB, 0x45D2BC4A];

let res = keccak_cairo_words64(input.span());
let res = keccak_cairo_words64(input.span(), 4);
assert(
res == 0x82CBD5B00CD06A188C831D69CB9629C92A2D5E7A78CEA913C5F9AFF62E66BBB9,
'Keccak output not matching'
Expand Down

0 comments on commit 04d93ba

Please sign in to comment.