From 35b363a601153491e804fd82bc468f7f92e976bc Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Thu, 26 Oct 2023 19:17:11 +0600 Subject: [PATCH] 5.2 Keccak Discards Leading Zero Bytes in Last Little Endian Words64 --- src/data_structures/eth_mpt.cairo | 41 ++++++++++++-------- src/data_structures/tests/test_eth_mpt.cairo | 14 +++++-- src/hashing/keccak.cairo | 28 ++++++------- src/hashing/tests/test_keccak.cairo | 4 +- 4 files changed, 48 insertions(+), 39 deletions(-) diff --git a/src/data_structures/eth_mpt.cairo b/src/data_structures/eth_mpt.cairo index 9e3a849..09d5150 100644 --- a/src/data_structures/eth_mpt.cairo +++ b/src/data_structures/eth_mpt.cairo @@ -61,18 +61,23 @@ impl MPTImpl of MPTTrait { if proof_index == proof.len() { break Result::Err('Proof reached end'); } - let node = *proof.at(proof_index); - let hash = MPTTrait::hash_rlp_node(node); - assert(hash == current_hash, 'Element not matching'); - - let decoded = match MPTTrait::decode_rlp_node(node) { - Result::Ok(decoded) => decoded, + let (decoded, rlp_byte_len) = match MPTTrait::decode_rlp_node(node) { + 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 @@ -212,9 +217,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 { - 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) => { @@ -225,7 +230,9 @@ 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); @@ -244,21 +251,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') } @@ -268,11 +275,11 @@ impl MPTImpl of MPTTrait { } } } - // @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) } } diff --git a/src/data_structures/tests/test_eth_mpt.cairo b/src/data_structures/tests/test_eth_mpt.cairo index 4364df8..74bc162 100644 --- a/src/data_structures/tests/test_eth_mpt.cairo +++ b/src/data_structures/tests/test_eth_mpt.cairo @@ -94,7 +94,9 @@ 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 @@ -153,7 +155,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'); } @@ -192,7 +196,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'); } @@ -216,7 +222,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' diff --git a/src/hashing/keccak.cairo b/src/hashing/keccak.cairo index 424cea1..7530c25 100644 --- a/src/hashing/keccak.cairo +++ b/src/hashing/keccak.cairo @@ -3,29 +3,25 @@ use keccak::cairo_keccak; // @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 { 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) } diff --git a/src/hashing/tests/test_keccak.cairo b/src/hashing/tests/test_keccak.cairo index f99cf8c..6baa154 100644 --- a/src/hashing/tests/test_keccak.cairo +++ b/src/hashing/tests/test_keccak.cairo @@ -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' @@ -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'