From 287c3ec4f583425da30e401dcfdbd47381ef6d0f Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Thu, 26 Oct 2023 19:04:07 +0600 Subject: [PATCH 01/28] 5.1 Missing Length Validation in MPT Verify --- src/data_structures/eth_mpt.cairo | 121 ++++++------------ src/data_structures/tests/test_eth_mpt.cairo | 4 +- src/encoding/rlp.cairo | 14 +-- src/encoding/tests/test_rlp.cairo | 125 +++++++++++++------ 4 files changed, 131 insertions(+), 133 deletions(-) diff --git a/src/data_structures/eth_mpt.cairo b/src/data_structures/eth_mpt.cairo index 6976766..9e3a849 100644 --- a/src/data_structures/eth_mpt.cairo +++ b/src/data_structures/eth_mpt.cairo @@ -19,19 +19,21 @@ impl MPTDefault of Default { } // @notice Represents a node in the MPT -#[derive(Drop)] +#[derive(Drop, PartialEq)] enum MPTNode { - // @param 16 hashes of children - // @param Value of the node + // @param hashes 16 hashes of children + // @param value value of the node Branch: (Span, Words64), // @param shared_nibbles // @param next_node - // @param nibbles_skip Number of nibbles to skip in shared nibbles - Extension: (Words64, u256, usize), + // @param nibbles_skip number of nibbles to skip in shared nibbles + // @param n_nibbles number of shared nibbles + Extension: (Words64, u256, usize, usize), // @param key_end // @param value of the node // @param nibbles_skip Number of nibbles to skip in the key end - Leaf: (Words64, Words64, usize) + // @param n_nibbles number of nibbles in key_end + Leaf: (Words64, Words64, usize, usize) } #[generate_trait] @@ -97,7 +99,7 @@ impl MPTImpl of MPTTrait { key_pow2 = key_pow2 / 16; }, MPTNode::Extension(( - shared_nibbles, next_node, nibbles_skip + shared_nibbles, next_node, nibbles_skip, n_nibbles )) => { let mut shared_nibbles_pow2 = pow(2, nibbles_skip.into() * 4); @@ -113,7 +115,11 @@ impl MPTImpl of MPTTrait { let mut shared_nibbles_word_idx = nibbles_skip / 16; let mut shared_nibbles_word = *shared_nibbles.at(shared_nibbles_word_idx); + let mut i_nibbles = 0; let next_hash = loop { + if i_nibbles == n_nibbles { + break Result::Ok(next_node); + } if key_pow2 == 0 { break Result::Err('Key reached end'); } @@ -123,7 +129,7 @@ impl MPTImpl of MPTTrait { & 0xf; let current_nibble_key = (key / key_pow2) & 0xf; if current_nibble_shared_nibbles.into() != current_nibble_key { - break Result::Ok(next_node); + break Result::Err('Extension nibbles not matching'); } if shared_nibbles_pow2 == 0x100000000000000 { @@ -140,6 +146,7 @@ impl MPTImpl of MPTTrait { in_byte = !in_byte; key_pow2 = key_pow2 / 16; + i_nibbles += 1; }; match next_hash { @@ -152,7 +159,7 @@ impl MPTImpl of MPTTrait { } }, MPTNode::Leaf(( - key_end, value, nibbles_skip + key_end, value, nibbles_skip, n_nibbles )) => { let mut key_end_pow2 = pow(2, nibbles_skip.into() * 4); @@ -168,8 +175,9 @@ impl MPTImpl of MPTTrait { let mut key_end_word_idx = nibbles_skip / 16; let mut key_end_word = *key_end.at(key_end_word_idx); + let mut i_nibbles = 0; break loop { - if key_pow2 == 0 { + if key_pow2 == 0 && i_nibbles == n_nibbles { break Result::Ok(value); } @@ -193,6 +201,7 @@ impl MPTImpl of MPTTrait { in_byte = !in_byte; key_pow2 = key_pow2 / 16; + i_nibbles += 1; }; } }; @@ -215,33 +224,41 @@ impl MPTImpl of MPTTrait { let mut i: usize = 0; loop { if i == 16 { - let value = *l.at(16); + let (value, _) = *l.at(16); break Result::Ok(MPTNode::Branch((nibble_hashes.span(), value))); } - nibble_hashes.append(*l.at(i)); + let (current_hash, _) = *l.at(i); + nibble_hashes.append(current_hash); i += 1; } } else if len == 2 { - let first = *l.at(0); + let (first, first_len) = *l.at(0); + let (second, _) = *l.at(1); // Unwrap impossible to fail, as we are making with 0xff, meaning the result always fits in a byte let prefix_byte: Byte = (*first.at(0) & 0xff).try_into().unwrap(); let (prefix, _) = prefix_byte.extract_nibbles(); + let n_nibbles = (first_len * 2) - 1; + if prefix == 0 { - match (*l.at(1)).try_into() { - Option::Some(n) => Result::Ok(MPTNode::Extension((first, n, 2))), + match second.try_into() { + Option::Some(n) => Result::Ok( + MPTNode::Extension((first, n, 2, n_nibbles - 1)) + ), Option::None(_) => Result::Err('Invalid next node') } } else if prefix == 1 { - match (*l.at(1)).try_into() { - Option::Some(n) => Result::Ok(MPTNode::Extension((first, n, 1))), + match second.try_into() { + Option::Some(n) => Result::Ok( + MPTNode::Extension((first, n, 1, n_nibbles)) + ), Option::None(_) => Result::Err('Invalid next node') } } else if prefix == 2 { - Result::Ok(MPTNode::Leaf((first, *l.at(1), 2))) + Result::Ok(MPTNode::Leaf((first, second, 2, n_nibbles - 1))) } else if prefix == 3 { - Result::Ok(MPTNode::Leaf((first, *l.at(1), 1))) + Result::Ok(MPTNode::Leaf((first, second, 1, n_nibbles))) } else { Result::Err('Invalid RLP prefix') } @@ -259,69 +276,3 @@ impl MPTImpl of MPTTrait { keccak_cairo_words64(rlp) } } - -impl MPTNodePartialEq of PartialEq { - fn eq(lhs: @MPTNode, rhs: @MPTNode) -> bool { - match lhs { - MPTNode::Branch(( - lhs_nibbles, lhs_value - )) => { - match rhs { - MPTNode::Branch(( - rhs_nibbles, rhs_value - )) => { - if (*lhs_nibbles).len() != (*rhs_nibbles).len() { - return false; - } - let mut i: usize = 0; - loop { - if i >= (*lhs_nibbles).len() { - break lhs_value == rhs_value; - } - if (*lhs_nibbles).at(i) != (*rhs_nibbles).at(i) { - break false; - } - i += 1; - } - }, - MPTNode::Extension(_) => false, - MPTNode::Leaf(_) => false - } - }, - MPTNode::Extension(( - lhs_shared_nibbles, lhs_next_node, lhs_nibbles_skip - )) => { - match rhs { - MPTNode::Branch(_) => false, - MPTNode::Extension(( - rhs_shared_nibbles, rhs_next_node, rhs_nibbles_skip - )) => { - lhs_shared_nibbles == rhs_shared_nibbles - && lhs_next_node == rhs_next_node - && lhs_nibbles_skip == rhs_nibbles_skip - }, - MPTNode::Leaf(_) => false - } - }, - MPTNode::Leaf(( - lhs_key_end, lhs_value, lhs_nibbles_skip - )) => { - match rhs { - MPTNode::Branch(_) => false, - MPTNode::Extension(_) => false, - MPTNode::Leaf(( - rhs_key_end, rhs_value, rhs_nibbles_skip - )) => { - lhs_key_end == rhs_key_end - && lhs_value == rhs_value - && lhs_nibbles_skip == rhs_nibbles_skip - } - } - } - } - } - - fn ne(lhs: @MPTNode, rhs: @MPTNode) -> bool { - !(lhs == rhs) - } -} diff --git a/src/data_structures/tests/test_eth_mpt.cairo b/src/data_structures/tests/test_eth_mpt.cairo index 8544bf2..4364df8 100644 --- a/src/data_structures/tests/test_eth_mpt.cairo +++ b/src/data_structures/tests/test_eth_mpt.cairo @@ -154,7 +154,7 @@ fn test_decode_rlp_node_leaf_odd() { ]; let decoded = MPTTrait::decode_rlp_node(rlp_node.span()).unwrap(); - let expected_node = MPTNode::Leaf((expected_key_end.span(), expected_value.span(), 1)); + let expected_node = MPTNode::Leaf((expected_key_end.span(), expected_value.span(), 1, 57)); assert(decoded == expected_node, 'Even leaf node differs'); } @@ -193,7 +193,7 @@ fn test_decode_rlp_node_leaf_even() { ]; let decoded = MPTTrait::decode_rlp_node(rlp_node.span()).unwrap(); - let expected_node = MPTNode::Leaf((expected_key_end.span(), expected_value.span(), 2)); + let expected_node = MPTNode::Leaf((expected_key_end.span(), expected_value.span(), 2, 56)); assert(decoded == expected_node, 'Even leaf node differs'); } diff --git a/src/encoding/rlp.cairo b/src/encoding/rlp.cairo index d47df7c..4093152 100644 --- a/src/encoding/rlp.cairo +++ b/src/encoding/rlp.cairo @@ -38,15 +38,15 @@ impl RLPTypeImpl of RLPTypeTrait { // @notice Represent a RLP item #[derive(Drop)] enum RLPItem { - Bytes: Words64, + Bytes: (Words64, usize), // Should be Span to allow for any depth/recursion, not yet supported by the compiler - List: Span + List: Span<(Words64, usize)> } // @notice RLP decodes a rlp encoded byte array // For more info: https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ // @param input RLP encoded input, in little endian 64 bits words -// @return Result with RLPItem and size of the decoded item +// @return Result with RLPItem and size of the encoded item fn rlp_decode(input: Words64) -> Result<(RLPItem, usize), felt252> { // It's guaranteed to fid in 32 bits, as we are masking with 0xff let prefix: u32 = (*input.at(0) & 0xff).try_into().unwrap(); @@ -56,13 +56,13 @@ fn rlp_decode(input: Words64) -> Result<(RLPItem, usize), felt252> { match rlp_type { RLPType::String(()) => { let mut arr = array![prefix.into()]; - Result::Ok((RLPItem::Bytes(arr.span()), 1)) + Result::Ok((RLPItem::Bytes((arr.span(), 1)), 1)) }, RLPType::StringShort(()) => { let len = prefix.into() - 0x80; let res = input.slice_le(6, len); - Result::Ok((RLPItem::Bytes(res), 1 + len)) + Result::Ok((RLPItem::Bytes((res, len)), 1 + len)) }, RLPType::StringLong(()) => { let len_len = prefix - 0xb7; @@ -76,7 +76,7 @@ fn rlp_decode(input: Words64) -> Result<(RLPItem, usize), felt252> { .unwrap(); let res = input.slice_le(6 - len_len, len); - Result::Ok((RLPItem::Bytes(res), 1 + len_len + len)) + Result::Ok((RLPItem::Bytes((res, len)), 1 + len_len + len)) }, RLPType::ListShort(()) => { let mut len = prefix - 0xc0; @@ -106,7 +106,7 @@ fn rlp_decode(input: Words64) -> Result<(RLPItem, usize), felt252> { // @param input RLP encoded input, in little endian 64 bits words // @param len Length of the input // @return Result with RLPItem::List -fn rlp_decode_list(ref input: Words64, len: usize) -> Result, felt252> { +fn rlp_decode_list(ref input: Words64, len: usize) -> Result, felt252> { let mut i = 0; let mut output = ArrayTrait::new(); let mut total_len = len; diff --git a/src/encoding/tests/test_rlp.cairo b/src/encoding/tests/test_rlp.cairo index 13afdac..c02cefb 100644 --- a/src/encoding/tests/test_rlp.cairo +++ b/src/encoding/tests/test_rlp.cairo @@ -14,7 +14,7 @@ fn test_rlp_decode_string() { let (res, len) = rlp_decode(arr.span()).unwrap(); assert(len == 1, 'Wrong len'); - assert(res == RLPItem::Bytes(arr.span()), 'Wrong value'); + assert(res == RLPItem::Bytes((arr.span(), 1)), 'Wrong value'); i += 1; }; @@ -31,7 +31,7 @@ fn test_rlp_decode_short_string() { let mut expected_res = array![ 0x8d39c034f66c805a, 0xf31ea949fd892d8f, 0xf7bb9484cd74a43d, 0xa8da3b ]; - let expected_item = RLPItem::Bytes(expected_res.span()); + let expected_item = RLPItem::Bytes((expected_res.span(), 0x9b - 0x80)); assert(res == expected_item, 'Wrong value'); } @@ -62,7 +62,7 @@ fn test_rlp_decode_long_string_len_1() { 0x10eada53d0c6673c, 0xd97d1986 ]; - let expected_item = RLPItem::Bytes(expected_res.span()); + let expected_item = RLPItem::Bytes((expected_res.span(), 0x3c)); assert(res == expected_item, 'Wrong value'); } @@ -143,7 +143,7 @@ fn test_rlp_decode_long_string_len_2() { 0x9976686868876897, 0x6087 ]; - let expected_item = RLPItem::Bytes(expected_res.span()); + let expected_item = RLPItem::Bytes((expected_res.span(), 0x0102)); assert(res == expected_item, 'Wrong value'); } @@ -156,7 +156,7 @@ fn test_rlp_decode_short_list() { assert(len == 1 + (0xc9 - 0xc0), 'Wrong len'); let mut expected_res = array![ - array![0x893535].span(), array![0x42].span(), array![0x923845].span() + (array![0x893535].span(), 3), (array![0x42].span(), 1), (array![0x923845].span(), 3) ]; let expected_item = RLPItem::List(expected_res.span()); assert(res == expected_item, 'Wrong value'); @@ -239,40 +239,87 @@ fn test_rlp_decode_long_list() { assert(len == 1 + (0xf9 - 0xf7) + 0x0211, 'Wrong len'); let mut expected_res = array![ - array![0x1b7a06b509cf7077, 0x75818924a962df35, 0xb4cd681fadecaece, 0xf44ac1730c4044a8] - .span(), - array![0x4661b25ad085a31e, 0x344568fe87045c6d, 0xdc184b5c4b1a9fc1, 0xb47150026035361a] - .span(), - array![0xd1d34035ce044c2c, 0xe5a5533c30721846, 0xa8368d4f30c18366, 0xeecd3ffaf56a0c80] - .span(), - array![0xd37d4bc58d77dca9, 0xfe61d139e72282c4, 0x17d5dcb2ceeec0b0, 0x5138a6378e5bf037] - .span(), - array![0xdd62df56554d5fa9, 0x9b56ae97049962c2, 0x9307207bdafd8ecd, 0xd71897db4cded3f8] - .span(), - array![ // 5 - 0x6e2238146d06d439, 0xa974a843e9c94aaf, 0x86b91dd8b05fc2a9, 0x4c03e2b336138c1d] - .span(), - array![0x18a46ab4637ccc7a, 0xcb6b25a141a0c9b3, 0x5ada7a396b316173, 0x300113bb1b496788] - .span(), - array![0x93c42e25818a3515, 0xb74680c736fe1371, 0x29bb913497a1fb11, 0xae52f85f78007a18] - .span(), - array![0xa7faab16d3429168, 0xdb1d2049dfce8b1c, 0xc490dc0a254e10b2, 0x58964a531f2256e8] - .span(), - array![0xa8fd3425995036dc, 0xa83baeb0dba714a3, 0x2ace690c55b59dc7, 0xa3c1c4ad07c06024] - .span(), - array![0x05b055663b68b020, 0x6b504b4ed003e19e, 0xdab792630039c1cb, 0xe7420366c27811b1] - .span(), - array![0x1c0f63fb45ebed8e, 0x17225718eb3697d9, 0xe21bb715f3d5c6cb, 0x14269bd9e83cb003] - .span(), - array![0x6f985af63da32379, 0x69b9c2e4e6f9e7d5, 0x3999be4e94086b73, 0xf309e62f6114864a] - .span(), - array![0x4a71201ad0d73465, 0x64ce46b9552afba4, 0xd1a22aadff2d22c3, 0xfdb12ac97334928a] - .span(), - array![0x2dbfb8fe8bc2f9bf, 0xe86fb0c3c818b6a9, 0xf7384714bdc0b10c, 0x2f50e229ff6121c4] - .span(), - array![0xa54e703c6961147f, 0x02c5725ea3bb1b02, 0x2e24988f459e43f6, 0x5fb3e28fea4837d0] - .span(), - array![].span() + ( + array![0x1b7a06b509cf7077, 0x75818924a962df35, 0xb4cd681fadecaece, 0xf44ac1730c4044a8] + .span(), + 32 + ), + ( + array![0x4661b25ad085a31e, 0x344568fe87045c6d, 0xdc184b5c4b1a9fc1, 0xb47150026035361a] + .span(), + 32 + ), + ( + array![0xd1d34035ce044c2c, 0xe5a5533c30721846, 0xa8368d4f30c18366, 0xeecd3ffaf56a0c80] + .span(), + 32 + ), + ( + array![0xd37d4bc58d77dca9, 0xfe61d139e72282c4, 0x17d5dcb2ceeec0b0, 0x5138a6378e5bf037] + .span(), + 32 + ), + ( + array![0xdd62df56554d5fa9, 0x9b56ae97049962c2, 0x9307207bdafd8ecd, 0xd71897db4cded3f8] + .span(), + 32 + ), + ( + array![0x6e2238146d06d439, 0xa974a843e9c94aaf, 0x86b91dd8b05fc2a9, 0x4c03e2b336138c1d] + .span(), + 32 + ), + ( + array![0x18a46ab4637ccc7a, 0xcb6b25a141a0c9b3, 0x5ada7a396b316173, 0x300113bb1b496788] + .span(), + 32 + ), + ( + array![0x93c42e25818a3515, 0xb74680c736fe1371, 0x29bb913497a1fb11, 0xae52f85f78007a18] + .span(), + 32 + ), + ( + array![0xa7faab16d3429168, 0xdb1d2049dfce8b1c, 0xc490dc0a254e10b2, 0x58964a531f2256e8] + .span(), + 32 + ), + ( + array![0xa8fd3425995036dc, 0xa83baeb0dba714a3, 0x2ace690c55b59dc7, 0xa3c1c4ad07c06024] + .span(), + 32 + ), + ( + array![0x05b055663b68b020, 0x6b504b4ed003e19e, 0xdab792630039c1cb, 0xe7420366c27811b1] + .span(), + 32 + ), + ( + array![0x1c0f63fb45ebed8e, 0x17225718eb3697d9, 0xe21bb715f3d5c6cb, 0x14269bd9e83cb003] + .span(), + 32 + ), + ( + array![0x6f985af63da32379, 0x69b9c2e4e6f9e7d5, 0x3999be4e94086b73, 0xf309e62f6114864a] + .span(), + 32 + ), + ( + array![0x4a71201ad0d73465, 0x64ce46b9552afba4, 0xd1a22aadff2d22c3, 0xfdb12ac97334928a] + .span(), + 32 + ), + ( + array![0x2dbfb8fe8bc2f9bf, 0xe86fb0c3c818b6a9, 0xf7384714bdc0b10c, 0x2f50e229ff6121c4] + .span(), + 32 + ), + ( + array![0xa54e703c6961147f, 0x02c5725ea3bb1b02, 0x2e24988f459e43f6, 0x5fb3e28fea4837d0] + .span(), + 32 + ), + (array![].span(), 0) ]; let expected_item = RLPItem::List(expected_res.span()); assert(res == expected_item, 'Wrong value'); From 35b363a601153491e804fd82bc468f7f92e976bc Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Thu, 26 Oct 2023 19:17:11 +0600 Subject: [PATCH 02/28] 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' From fe92cfbf613e4fb30e7af96df735bc1e02692fa4 Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Thu, 26 Oct 2023 19:18:11 +0600 Subject: [PATCH 03/28] 5.4 Keccak: Unsupported Empty Bytes Input Breaks EVM Equivalence --- src/hashing/keccak.cairo | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/hashing/keccak.cairo b/src/hashing/keccak.cairo index 7530c25..7bc261d 100644 --- a/src/hashing/keccak.cairo +++ b/src/hashing/keccak.cairo @@ -1,11 +1,17 @@ use cairo_lib::utils::types::words64::{Words64, bytes_used_u64}; use keccak::cairo_keccak; +const EMPTY_KECCAK: u256 = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // @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, 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; From a1bb7fe2ec7eeb6f0e46a0e7fbe826ecce22829c Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Thu, 26 Oct 2023 19:21:01 +0600 Subject: [PATCH 04/28] 7.1 Order of Evaluation Can Be Enforced --- src/utils/types/words64.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/types/words64.cairo b/src/utils/types/words64.cairo index a772bf7..e3bf14e 100644 --- a/src/utils/types/words64.cairo +++ b/src/utils/types/words64.cairo @@ -26,7 +26,7 @@ impl Words64TryIntoU256LE of TryInto { } // left shift and add - output = output | (*self.at(i)).into() * *pows.at(i - 1); + output = output | ((*self.at(i)).into() * *pows.at(i - 1)); i += 1; } From dc9f8f5593ff8166c60c261681bc34a55dbb82d5 Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Thu, 26 Oct 2023 19:26:11 +0600 Subject: [PATCH 05/28] 7.2 Unused Import --- src/utils/types/words64.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/types/words64.cairo b/src/utils/types/words64.cairo index e3bf14e..758f2b1 100644 --- a/src/utils/types/words64.cairo +++ b/src/utils/types/words64.cairo @@ -1,4 +1,4 @@ -use cairo_lib::utils::bitwise::{left_shift, right_shift}; +use cairo_lib::utils::bitwise::left_shift; // @notice Represents a span of 64 bit words // @dev In many cases it's expected that the words are in little endian, but the overall order is big endian From 1c51c32abb3f28a849738d17b1f43c47f0aef2ca Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Thu, 26 Oct 2023 15:24:03 +0200 Subject: [PATCH 06/28] 5.3 MMR: Incorrect Root Update Possible, Insufficient Peaks Validation --- src/data_structures/mmr/mmr.cairo | 8 ++++- src/data_structures/mmr/tests/test_mmr.cairo | 23 +++++++++++++ src/data_structures/mmr/utils.cairo | 36 +++++++++++++++++--- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/data_structures/mmr/mmr.cairo b/src/data_structures/mmr/mmr.cairo index 0f69dc7..5ec95a2 100644 --- a/src/data_structures/mmr/mmr.cairo +++ b/src/data_structures/mmr/mmr.cairo @@ -1,6 +1,8 @@ use cairo_lib::data_structures::mmr::peaks::{Peaks, PeaksTrait}; use cairo_lib::data_structures::mmr::proof::{Proof, ProofTrait}; -use cairo_lib::data_structures::mmr::utils::{compute_root, get_height}; +use cairo_lib::data_structures::mmr::utils::{ + compute_root, get_height, mmr_size_to_leaf_count, leaf_count_to_peaks_count +}; use cairo_lib::hashing::poseidon::PoseidonHasher; // @notice Merkle Mountatin Range struct @@ -34,6 +36,10 @@ impl MMRImpl of MMRTrait { // @param peaks The peaks of the MMR // @return Result with the new root and new peaks of the MMR fn append(ref self: MMR, hash: felt252, peaks: Peaks) -> Result<(felt252, Peaks), felt252> { + let leaf_count = mmr_size_to_leaf_count(self.last_pos.into()); + if leaf_count_to_peaks_count(leaf_count) != peaks.len().into() { + return Result::Err('Invalid peaks count'); + } if !peaks.valid(self.last_pos, self.root) { return Result::Err('Invalid peaks'); } diff --git a/src/data_structures/mmr/tests/test_mmr.cairo b/src/data_structures/mmr/tests/test_mmr.cairo index 1032a9f..f41dbd5 100644 --- a/src/data_structures/mmr/tests/test_mmr.cairo +++ b/src/data_structures/mmr/tests/test_mmr.cairo @@ -226,3 +226,26 @@ fn test_verify_proof_invalid_peaks() { assert(mmr.verify_proof(2, *elems.at(1), peaks, proof).is_err(), 'Proof wrong peaks') } + +#[test] +#[available_gas(99999999)] +fn test_attack_forge_peaks() { + let elems = helper_test_get_elements(); + let mut mmr_real: MMR = MMRTrait::new( + 0x21aea73dea77022a4882e1f656b76c9195161ed1cff2b065a74d7246b02d5d6, 0x8 + ); + let mut mmr_fake: MMR = MMRTrait::new( + 0x21aea73dea77022a4882e1f656b76c9195161ed1cff2b065a74d7246b02d5d6, 0x8 + ); + + // add the next element normally to mmr_real and get the root; + let peaks_real = array![*elems.at(6), *elems.at(7)].span(); + mmr_real.append(9, peaks_real); + + // add the next element abnormally to mmr_real and get the root; + let forged_peak = PoseidonHasher::hash_double(*elems.at(6), *elems.at(7)); + let peaks_fake = array![forged_peak].span(); + let res = mmr_fake.append(9, peaks_fake); + + assert(res.is_err(), 'attack success: forged peak'); +} diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index 6faa272..39656d3 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -28,10 +28,10 @@ fn compute_root(last_pos: felt252, peaks: Peaks) -> felt252 { } // @notice Count the number of bits set to 1 in a 256-bit unsigned integer -// @param n The 256-bit unsigned integer +// @param n The 32-bit unsigned integer // @return The number of bits set to 1 in n -fn count_ones(n: u256) -> u256 { - let mut n = n; +fn count_ones(arg: u256) -> u256 { + let mut n = arg; let mut count = 0; loop { if n == 0 { @@ -42,9 +42,37 @@ fn count_ones(n: u256) -> u256 { } } -// @notice Convert a leaf index to an Merkle Mountain Range tree leaf index +// @notice Convert a leaf index to an Merkle Mountain Range tree index // @param n The leaf index // @return The MMR index fn leaf_index_to_mmr_index(n: u256) -> u256 { 2 * n - 1 - count_ones(n - 1) } + +// @notice Convert a Merkle Mountain Range tree size to number of leaves +// @param n MMR size +// @result Number of leaves +fn mmr_size_to_leaf_count(n: u256) -> u256 { + let mut mmr_size = n; + let bits = bit_length(mmr_size); + let mut i = pow(2, bits); + let mut leaf_count = 0; + loop { + if i == 0 { + break leaf_count; + } + let x = 2 * i - 1; + if x <= mmr_size { + mmr_size -= x; + leaf_count += i; + } + i /= 2; + } +} + +// @notice Convert a number of leaves to number of peaks +// @param leaf_count Number of leaves +// @return Number of peaks +fn leaf_count_to_peaks_count(leaf_count: u256) -> u256 { + count_ones(leaf_count) +} From 1e7f8f37127b021b991e13a50dfcbf9932db0211 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Thu, 26 Oct 2023 15:25:39 +0200 Subject: [PATCH 07/28] 5.5 MMR Verify Proof: Different Nodes Can Use the Same Index --- src/data_structures/mmr/mmr.cairo | 11 ++++++-- .../mmr/tests/test_utils.cairo | 13 +++++++++- src/data_structures/mmr/utils.cairo | 25 +++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/data_structures/mmr/mmr.cairo b/src/data_structures/mmr/mmr.cairo index 5ec95a2..0c2141b 100644 --- a/src/data_structures/mmr/mmr.cairo +++ b/src/data_structures/mmr/mmr.cairo @@ -1,7 +1,7 @@ use cairo_lib::data_structures::mmr::peaks::{Peaks, PeaksTrait}; use cairo_lib::data_structures::mmr::proof::{Proof, ProofTrait}; use cairo_lib::data_structures::mmr::utils::{ - compute_root, get_height, mmr_size_to_leaf_count, leaf_count_to_peaks_count + compute_root, get_height, mmr_size_to_leaf_count, leaf_count_to_peaks_count, get_peak_info }; use cairo_lib::hashing::poseidon::PoseidonHasher; @@ -109,7 +109,14 @@ impl MMRImpl of MMRTrait { return Result::Err('Invalid peaks'); } + let (peak_index, peak_height) = get_peak_info(*self.last_pos, index); + + if proof.len() != peak_height { + return Result::Ok(false); + } + let peak = proof.compute_peak(index, hash); - Result::Ok(peaks.contains_peak(peak)) + + Result::Ok(*peaks.at(peak_index) == peak) } } diff --git a/src/data_structures/mmr/tests/test_utils.cairo b/src/data_structures/mmr/tests/test_utils.cairo index f5b1a93..511f9a4 100644 --- a/src/data_structures/mmr/tests/test_utils.cairo +++ b/src/data_structures/mmr/tests/test_utils.cairo @@ -1,5 +1,5 @@ use cairo_lib::data_structures::mmr::utils::{ - get_height, compute_root, count_ones, leaf_index_to_mmr_index + get_height, compute_root, count_ones, leaf_index_to_mmr_index, get_peak_info, }; use cairo_lib::hashing::poseidon::PoseidonHasher; use cairo_lib::data_structures::mmr::peaks::PeaksTrait; @@ -72,3 +72,14 @@ fn test_leaf_index_to_mmr_index() { assert(leaf_index_to_mmr_index(10) == 17, 'leaf_..._index(10) != 17'); assert(leaf_index_to_mmr_index(11) == 19, 'leaf_..._index(11) != 19'); } + +#[test] +#[available_gas(999999999)] +fn test_get_peak_info() { + assert(get_peak_info(11, 11) == (2, 0), 'get_peak_info(11, 11) != (2, 0)'); + assert(get_peak_info(15, 11) == (0, 3), 'get_peak_info(15, 11) != (0, 3)'); + assert(get_peak_info(18, 16) == (1, 1), 'get_peak_info(18, 16) != (1, 1)'); + assert(get_peak_info(26, 16) == (1, 2), 'get_peak_info(26, 16) != (1, 2)'); + assert(get_peak_info(26, 16) == (1, 2), 'get_peak_info(26, 16) != (1, 2)'); + assert(get_peak_info(31, 16) == (0, 4), 'get_peak_info(31, 16) != (0, 4)'); +} diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index 39656d3..d59c71f 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -76,3 +76,28 @@ fn mmr_size_to_leaf_count(n: u256) -> u256 { fn leaf_count_to_peaks_count(leaf_count: u256) -> u256 { count_ones(leaf_count) } + +// @notice Get peak size and index of the peak the element is in +// @param elements_count The size of the MMR (number of elements in the MMR) +// @param element_index The index of the element in the MMR +// @return (peak index, peak height) +fn get_peak_info(elements_count: u32, element_index: u32) -> (u32, u32) { + let mut elements_count = elements_count; + let mut element_index = element_index; + + let mut mountain_height = bit_length(elements_count); + let mut mountain_elements_count = pow(2, mountain_height) - 1; + let mut mountain_index = 0; + loop { + if mountain_elements_count <= elements_count { + if element_index <= mountain_elements_count { + break (mountain_index, mountain_height - 1); + } + elements_count -= mountain_elements_count; + element_index -= mountain_elements_count; + mountain_index += 1; + } + mountain_height -= 1; + mountain_elements_count /= 2; + } +} From 0eafd0ce2cbf36e75b3583199af6ea7e9465e266 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Thu, 26 Oct 2023 15:39:51 +0200 Subject: [PATCH 08/28] 5.8 Bit_Length Will Revert if Input Most Significant Bit Is 1 --- src/utils/bitwise.cairo | 14 +++++++------- src/utils/tests/test_bitwise.cairo | 5 +++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/utils/bitwise.cairo b/src/utils/bitwise.cairo index e5d9bc1..a5e522a 100644 --- a/src/utils/bitwise.cairo +++ b/src/utils/bitwise.cairo @@ -49,24 +49,24 @@ fn right_shift< fn bit_length< T, impl TZeroable: Zeroable, - impl TPartialOrd: PartialOrd, - impl TAddImpl: Add, - impl TSub: Sub, - impl TMul: Mul, impl TOneable: Oneable, + impl TAddImpl: Add, + impl TDiv: Div, impl TCopy: Copy, impl TDrop: Drop >( num: T ) -> T { let mut bit_position = TZeroable::zero(); - let mut cur_n = TOneable::one(); + let mut cur_n = num; + let two = TOneable::one() + TOneable::one(); + loop { - if cur_n > num { + if cur_n.is_zero() { break (); }; bit_position = bit_position + TOneable::one(); - cur_n = left_shift(cur_n, TOneable::one()); + cur_n = cur_n / two; }; bit_position } diff --git a/src/utils/tests/test_bitwise.cairo b/src/utils/tests/test_bitwise.cairo index 7dfcf4f..2eba71f 100644 --- a/src/utils/tests/test_bitwise.cairo +++ b/src/utils/tests/test_bitwise.cairo @@ -33,3 +33,8 @@ fn test_bit_length() { assert(bit_length(8_u32) == 4, 'bit length of 8 is 4'); } +#[test] +#[available_gas(999999999)] +fn test_bit_length_most_significant_bit_one() { + assert(bit_length(4294967295_u32) == 32, 'bit length of 2^32-1 is 32'); +} From 1cfbefbca8cd3e70e0c25c043f77e178fb7d3191 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Thu, 26 Oct 2023 15:40:16 +0200 Subject: [PATCH 09/28] 5.9 Right Shift Reverts on Bit Length Input --- src/utils/bitwise.cairo | 14 +++++++++++--- src/utils/tests/test_bitwise.cairo | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/utils/bitwise.cairo b/src/utils/bitwise.cairo index a5e522a..95f2189 100644 --- a/src/utils/bitwise.cairo +++ b/src/utils/bitwise.cairo @@ -29,18 +29,26 @@ fn left_shift< fn right_shift< T, impl TZeroable: Zeroable, + impl TOneable: Oneable, impl TAdd: Add, impl TSub: Sub, - impl TMul: Mul, impl TDiv: Div, - impl TOneable: Oneable, impl TCopy: Copy, impl TDrop: Drop >( num: T, shift: T ) -> T { + let mut num = num; + let mut shift = shift; let two = TOneable::one() + TOneable::one(); - num / pow(two, shift) + + loop { + if shift.is_zero() { + break num; + } + num = num / two; + shift = shift - TOneable::one(); + } } // @notice Bit length of a number diff --git a/src/utils/tests/test_bitwise.cairo b/src/utils/tests/test_bitwise.cairo index 2eba71f..b486d17 100644 --- a/src/utils/tests/test_bitwise.cairo +++ b/src/utils/tests/test_bitwise.cairo @@ -20,6 +20,7 @@ fn test_right_shift() { assert(right_shift(256_u32, 8_u32) == 1, '256 >> 8'); assert(right_shift(512_u32, 8_u32) == 2, '512 >> 8'); assert(right_shift(65280_u32, 8_u32) == 255, '65280 >> 8'); + assert(right_shift(128392_u32, 33_u32) == 0, '128392 >> 33'); } #[test] From fc997f5cfc8ae0e4042c8b549103de6c1839fdeb Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Fri, 27 Oct 2023 15:55:52 +0600 Subject: [PATCH 10/28] 8.5 Words64TryIntoU256LE Reverts in Case of Empty Input --- src/utils/types/words64.cairo | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/types/words64.cairo b/src/utils/types/words64.cairo index 758f2b1..2537bff 100644 --- a/src/utils/types/words64.cairo +++ b/src/utils/types/words64.cairo @@ -12,6 +12,10 @@ impl Words64TryIntoU256LE of TryInto { return Option::None(()); } + if self.len() == 0 { + return Option::Some(0); + } + let pows = array![ 0x10000000000000000, // 2 ** 64 0x100000000000000000000000000000000, // 2 ** 128 From 67170573747da1c4d296a7b26f6f4c6991b87e74 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 27 Oct 2023 12:38:17 +0200 Subject: [PATCH 11/28] 6.1 Use Fast Power for Gas Efficiency --- src/utils/bitwise.cairo | 6 +++- src/utils/math.cairo | 62 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/utils/bitwise.cairo b/src/utils/bitwise.cairo index 95f2189..ab1f9c8 100644 --- a/src/utils/bitwise.cairo +++ b/src/utils/bitwise.cairo @@ -14,7 +14,11 @@ fn left_shift< impl TMul: Mul, impl TOneable: Oneable, impl TCopy: Copy, - impl TDrop: Drop + impl TDrop: Drop, + impl TDiv: Div, + impl TRem: Rem, + impl TPartialEq: PartialEq, + impl TPartialOrd: PartialOrd >( num: T, shift: T ) -> T { diff --git a/src/utils/math.cairo b/src/utils/math.cairo index fe25aa0..918c358 100644 --- a/src/utils/math.cairo +++ b/src/utils/math.cairo @@ -4,7 +4,34 @@ use math::Oneable; // @param base The base of the exponentiation // @param exp The exponent of the exponentiation // @return The exponentiation result + fn pow< + T, + impl Zeroable: Zeroable, + impl TOneable: Oneable, + impl TCopy: Copy, + impl TDrop: Drop, + impl TAdd: Add, + impl TSub: Sub, + impl TMul: Mul, + impl TDiv: Div, + impl TRem: Rem, + impl TPartialEq: PartialEq, + impl TPartialOrd: PartialOrd +>( + mut base: T, mut exp: T +) -> T { + let two = TOneable::one() + TOneable::one(); + let four = two + two; + let sixteen = four * four; + if exp < sixteen { + slow_pow(base, exp) + } else { + fast_pow(base, exp) + } +} + +fn slow_pow< T, impl TZeroable: Zeroable, impl TSub: Sub, @@ -18,6 +45,39 @@ fn pow< if exp.is_zero() { TOneable::one() } else { - base * pow(base, exp - TOneable::one()) + base * slow_pow(base, exp - TOneable::one()) } } + +fn fast_pow< + T, + impl Zeroable: Zeroable, + impl TOneable: Oneable, + impl TCopy: Copy, + impl TDrop: Drop, + impl TAdd: Add, + impl TSub: Sub, + impl TMul: Mul, + impl TDiv: Div, + impl TRem: Rem, + impl TPartialEq: PartialEq +>( + mut base: T, mut exp: T +) -> T { + let mut ans = TOneable::one(); + loop { + if exp.is_zero() { + break ans; + } + let two = TOneable::one() + TOneable::one(); + let mm = exp % two; + if mm == TOneable::one() { + ans = ans * base; + exp = exp - TOneable::one(); + } else { + base = base * base; + exp = exp / two; + }; + } +} + From 0af61150a5f738dbc1b3cdf0b477ed9643e94472 Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Fri, 27 Oct 2023 18:16:31 +0600 Subject: [PATCH 12/28] 8.1(herodotus-on-starknet) Close to Ressource Limit in Starknet --- src/data_structures/eth_mpt.cairo | 61 +++- src/data_structures/tests/test_eth_mpt.cairo | 87 +++++- src/encoding/rlp.cairo | 134 ++++++--- src/encoding/tests/test_rlp.cairo | 281 ++++++++++++++++++- 4 files changed, 516 insertions(+), 47 deletions(-) diff --git a/src/data_structures/eth_mpt.cairo b/src/data_structures/eth_mpt.cairo index 09d5150..3d8820c 100644 --- a/src/data_structures/eth_mpt.cairo +++ b/src/data_structures/eth_mpt.cairo @@ -1,5 +1,5 @@ use cairo_lib::hashing::keccak::keccak_cairo_words64; -use cairo_lib::encoding::rlp::{RLPItem, rlp_decode}; +use cairo_lib::encoding::rlp::{RLPItem, rlp_decode, rlp_decode_list_lazy}; use cairo_lib::utils::types::byte::{Byte, ByteTrait}; use cairo_lib::utils::bitwise::{right_shift, left_shift}; use cairo_lib::utils::types::words64::{Words64, Words64Trait, Words64TryIntoU256LE}; @@ -24,14 +24,16 @@ enum MPTNode { // @param hashes 16 hashes of children // @param value value of the node Branch: (Span, Words64), + // @param hash hash of the next node + LazyBranch: u256, // @param shared_nibbles // @param next_node // @param nibbles_skip number of nibbles to skip in shared nibbles // @param n_nibbles number of shared nibbles Extension: (Words64, u256, usize, usize), // @param key_end - // @param value of the node - // @param nibbles_skip Number of nibbles to skip in the key end + // @param value value of the node + // @param nibbles_skip number of nibbles to skip in the key end // @param n_nibbles number of nibbles in key_end Leaf: (Words64, Words64, usize, usize) } @@ -57,16 +59,33 @@ impl MPTImpl of MPTTrait { let mut proof_index: usize = 0; let mut key_pow2: u256 = pow(2, (key_len.into() - 1) * 4); + let proof_len = proof.len(); + loop { - if proof_index == proof.len() { + if proof_index == proof_len { break Result::Err('Proof reached end'); } + let node = *proof.at(proof_index); - let (decoded, rlp_byte_len) = match MPTTrait::decode_rlp_node(node) { - Result::Ok(d) => d, - Result::Err(e) => { - break Result::Err(e); + // If it's not the last node and more than 9 words, it must be a branch node + 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(d) => d, + Result::Err(e) => { + break Result::Err(e); + } + } + } else { + match MPTTrait::decode_rlp_node(node) { + Result::Ok(d) => d, + Result::Err(e) => { + break Result::Err(e); + } } }; @@ -103,6 +122,10 @@ impl MPTImpl of MPTTrait { }; key_pow2 = key_pow2 / 16; }, + MPTNode::LazyBranch(next_node) => { + current_hash = next_node; + key_pow2 = key_pow2 / 16; + }, MPTNode::Extension(( shared_nibbles, next_node, nibbles_skip, n_nibbles )) => { @@ -235,7 +258,7 @@ impl MPTImpl of MPTTrait { ); } - let (current_hash, _) = *l.at(i); + let (current_hash, _) = (*l.at(i)); nibble_hashes.append(current_hash); i += 1; } @@ -275,6 +298,26 @@ impl MPTImpl of MPTTrait { } } } + + + 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 diff --git a/src/data_structures/tests/test_eth_mpt.cairo b/src/data_structures/tests/test_eth_mpt.cairo index 74bc162..d3f6768 100644 --- a/src/data_structures/tests/test_eth_mpt.cairo +++ b/src/data_structures/tests/test_eth_mpt.cairo @@ -96,7 +96,6 @@ fn test_decode_rlp_node_branch() { 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 @@ -112,6 +111,9 @@ fn test_decode_rlp_node_branch() { i += 1; }; }, + MPTNode::LazyBranch(_) => { + panic_with_felt252('Branch node differs'); + }, MPTNode::Extension(_) => { panic_with_felt252('Branch node differs'); }, @@ -121,6 +123,89 @@ fn test_decode_rlp_node_branch() { } } +#[test] +#[available_gas(9999999999)] +fn test_lazy_rlp_node_branch() { + let rlp_node = array![ + 0x09cf7077a01102f9, + 0xa962df351b7a06b5, + 0xadecaece75818924, + 0x0c4044a8b4cd681f, + 0x85a31ea0f44ac173, + 0x045c6d4661b25ad0, + 0x1a9fc1344568fe87, + 0x35361adc184b5c4b, + 0x4c2ca0b471500260, + 0x1846d1d34035ce04, + 0x8366e5a5533c3072, + 0x0c80a8368d4f30c1, + 0xa9a0eecd3ffaf56a, + 0xc4d37d4bc58d77dc, + 0xb0fe61d139e72282, + 0x3717d5dcb2ceeec0, + 0xa05138a6378e5bf0, + 0xdd62df56554d5fa9, + 0x9b56ae97049962c2, + 0x9307207bdafd8ecd, + 0xd71897db4cded3f8, + 0x2238146d06d439a0, + 0x74a843e9c94aaf6e, + 0xb91dd8b05fc2a9a9, + 0x03e2b336138c1d86, + 0x6ab4637ccc7aa04c, + 0x25a141a0c9b318a4, + 0x7a396b316173cb6b, + 0x13bb1b4967885ada, + 0x25818a3515a03001, + 0xc736fe137193c42e, + 0x3497a1fb11b74680, + 0x5f78007a1829bb91, + 0xd3429168a0ae52f8, + 0xdfce8b1ca7faab16, + 0x254e10b2db1d2049, + 0x1f2256e8c490dc0a, + 0x5036dca058964a53, + 0xa714a3a8fd342599, + 0xb59dc7a83baeb0db, + 0xc060242ace690c55, + 0xb020a0a3c1c4ad07, + 0xe19e05b055663b68, + 0xc1cb6b504b4ed003, + 0x11b1dab792630039, + 0x8ea0e7420366c278, + 0xd91c0f63fb45ebed, + 0xcb17225718eb3697, + 0x03e21bb715f3d5c6, + 0xa014269bd9e83cb0, + 0x6f985af63da32379, + 0x69b9c2e4e6f9e7d5, + 0x3999be4e94086b73, + 0xf309e62f6114864a, + 0x71201ad0d73465a0, + 0xce46b9552afba44a, + 0xa22aadff2d22c364, + 0xb12ac97334928ad1, + 0xb8fe8bc2f9bfa0fd, + 0xb0c3c818b6a92dbf, + 0x4714bdc0b10ce86f, + 0xe229ff6121c4f738, + 0x3c6961147fa02f50, + 0x5ea3bb1b02a54e70, + 0x8f459e43f602c572, + 0x8fea4837d02e2498, + 0x805fb3e2 + ]; + + let expected = 0xE7420366C27811B1DAB792630039C1CB6B504B4ED003E19E05B055663B68B020; + + 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'); +} + #[test] #[available_gas(9999999999)] fn test_decode_rlp_node_leaf_odd() { diff --git a/src/encoding/rlp.cairo b/src/encoding/rlp.cairo index 4093152..f9d0701 100644 --- a/src/encoding/rlp.cairo +++ b/src/encoding/rlp.cairo @@ -1,5 +1,6 @@ -use cairo_lib::utils::types::words64::{Words64, Words64Trait, reverse_endianness_u64}; +use cairo_lib::utils::types::words64::{Words64, Words64Trait, reverse_endianness_u64, pow2}; use cairo_lib::utils::types::byte::Byte; +use cairo_lib::utils::array::span_contains; // @notice Enum with all possible RLP types // For more info: https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ @@ -36,7 +37,7 @@ impl RLPTypeImpl of RLPTypeTrait { } // @notice Represent a RLP item -#[derive(Drop)] +#[derive(Drop, PartialEq)] enum RLPItem { Bytes: (Words64, usize), // Should be Span to allow for any depth/recursion, not yet supported by the compiler @@ -141,43 +142,104 @@ fn rlp_decode_list(ref input: Words64, len: usize) -> Result { - fn eq(lhs: @RLPItem, rhs: @RLPItem) -> bool { - match lhs { - RLPItem::Bytes(b) => { - match rhs { - RLPItem::Bytes(b2) => { - b == b2 - }, - RLPItem::List(_) => false - } +fn rlp_decode_list_lazy(input: Words64, lazy: Span) -> Result<(RLPItem, usize), felt252> { + let mut output = ArrayTrait::new(); + let mut lazy_index = 0; + + let list_prefix: u32 = (*input.at(0) & 0xff).try_into().unwrap(); + let list_type = RLPTypeTrait::from_byte(list_prefix.try_into().unwrap()).unwrap(); + let (mut current_input_index, len) = match list_type { + RLPType::String(()) => { + return Result::Err('Not a list'); + }, + RLPType::StringShort(()) => { + return Result::Err('Not a list'); + }, + RLPType::StringLong(()) => { + return Result::Err('Not a list'); + }, + RLPType::ListShort(()) => (1, list_prefix - 0xc0), + RLPType::ListLong(()) => { + let len_len = list_prefix - 0xf7; + let len_span = input.slice_le(6, len_len); + // Enough to store 4.29 GB (fits in u32) + assert(len_span.len() == 1 && *len_span.at(0) <= 0xffffffff, 'Len of len too big'); + + // len fits in 32 bits, confirmed by previous assertion + let len = reverse_endianness_u64(*len_span.at(0), Option::Some(len_len.into())) + .try_into() + .unwrap(); + (1 + len_len, len) + } + }; + + let rlp_byte_len = current_input_index + len; + + loop { + if output.len() == lazy.len() { + break Result::Ok((RLPItem::List(output.span()), rlp_byte_len)); + } + + if current_input_index >= len { + break Result::Err('Too many items to decode'); + } + + let current_word = current_input_index / 8; + let current_word_offset = 7 - (current_input_index % 8); + + let pow2_shift = pow2((7 - current_word_offset) * 8); + let prefix = (*input.at(current_word) / pow2_shift) & 0xff; + + let rlp_type = RLPTypeTrait::from_byte(prefix.try_into().unwrap()).unwrap(); + let (item_start_skip, item_len) = match rlp_type { + RLPType::String(()) => { + (0, 1) }, - 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; - } - } - } + RLPType::StringShort(()) => { + let len = prefix - 0x80; + (1, len) + }, + RLPType::StringLong(()) => { + let len_len = prefix - 0xb7; + + let current_word = (current_input_index + 1) / 8; + let current_word_offset = 7 - ((current_input_index + 1) % 8); + + let len_span = input + .slice_le(current_word * 8 + current_word_offset, len_len.try_into().unwrap()); + // Enough to store 4.29 GB (fits in u32) + assert(len_span.len() == 1 && *len_span.at(0) <= 0xffffffff, 'Len of len too big'); + + // len fits in 32 bits, confirmed by previous assertion + let len: u32 = reverse_endianness_u64( + *len_span.at(0), Option::Some(len_len.try_into().unwrap()) + ) + .try_into() + .unwrap(); + + (1 + len_len, len.into()) + }, + RLPType::ListShort(()) => { + panic_with_felt252('Recursive list not supported') + }, + RLPType::ListLong(()) => { + panic_with_felt252('Recursive list not supported') } + }; + + current_input_index += item_start_skip.try_into().unwrap(); + if span_contains(lazy, lazy_index) { + 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 item_len = item_len.try_into().unwrap(); + let decoded = input.slice_le(start, item_len); + output.append((decoded, item_len)); } - } - fn ne(lhs: @RLPItem, rhs: @RLPItem) -> bool { - !(lhs == rhs) + current_input_index += item_len.try_into().unwrap(); + + lazy_index += 1; } } - diff --git a/src/encoding/tests/test_rlp.cairo b/src/encoding/tests/test_rlp.cairo index c02cefb..e797c4f 100644 --- a/src/encoding/tests/test_rlp.cairo +++ b/src/encoding/tests/test_rlp.cairo @@ -1,4 +1,4 @@ -use cairo_lib::encoding::rlp::{RLPItem, rlp_decode}; +use cairo_lib::encoding::rlp::{RLPItem, rlp_decode, rlp_decode_list_lazy}; #[test] #[available_gas(99999999)] @@ -162,6 +162,40 @@ fn test_rlp_decode_short_list() { assert(res == expected_item, 'Wrong value'); } +#[test] +#[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(); + 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 = (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 = ( + RLPItem::List( + array![ + (array![0x893535].span(), 3), (array![0x42].span(), 1), (array![0x923845].span(), 3) + ] + .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 = ( + RLPItem::List(array![(array![0x893535].span(), 3), (array![0x923845].span(), 3)].span()), + rlp_byte_len + ); + assert(res == expected_res, 'Wrong value: indexes: 0, 2'); +} + #[test] #[available_gas(99999999)] fn test_rlp_decode_long_list() { @@ -324,3 +358,248 @@ fn test_rlp_decode_long_list() { let expected_item = RLPItem::List(expected_res.span()); assert(res == expected_item, 'Wrong value'); } + +#[test] +#[available_gas(99999999)] +fn test_rlp_lazy_decode_long_list() { + let mut arr = array![ + 0x09cf7077a01102f9, + 0xa962df351b7a06b5, + 0xadecaece75818924, + 0x0c4044a8b4cd681f, + 0x85a31ea0f44ac173, + 0x045c6d4661b25ad0, + 0x1a9fc1344568fe87, + 0x35361adc184b5c4b, + 0x4c2ca0b471500260, + 0x1846d1d34035ce04, + 0x8366e5a5533c3072, + 0x0c80a8368d4f30c1, + 0xa9a0eecd3ffaf56a, + 0xc4d37d4bc58d77dc, + 0xb0fe61d139e72282, + 0x3717d5dcb2ceeec0, + 0xa05138a6378e5bf0, + 0xdd62df56554d5fa9, + 0x9b56ae97049962c2, + 0x9307207bdafd8ecd, + 0xd71897db4cded3f8, + 0x2238146d06d439a0, + 0x74a843e9c94aaf6e, + 0xb91dd8b05fc2a9a9, + 0x03e2b336138c1d86, + 0x6ab4637ccc7aa04c, + 0x25a141a0c9b318a4, + 0x7a396b316173cb6b, + 0x13bb1b4967885ada, + 0x25818a3515a03001, + 0xc736fe137193c42e, + 0x3497a1fb11b74680, + 0x5f78007a1829bb91, + 0xd3429168a0ae52f8, + 0xdfce8b1ca7faab16, + 0x254e10b2db1d2049, + 0x1f2256e8c490dc0a, + 0x5036dca058964a53, + 0xa714a3a8fd342599, + 0xb59dc7a83baeb0db, + 0xc060242ace690c55, + 0xb020a0a3c1c4ad07, + 0xe19e05b055663b68, + 0xc1cb6b504b4ed003, + 0x11b1dab792630039, + 0x8ea0e7420366c278, + 0xd91c0f63fb45ebed, + 0xcb17225718eb3697, + 0x03e21bb715f3d5c6, + 0xa014269bd9e83cb0, + 0x6f985af63da32379, + 0x69b9c2e4e6f9e7d5, + 0x3999be4e94086b73, + 0xf309e62f6114864a, + 0x71201ad0d73465a0, + 0xce46b9552afba44a, + 0xa22aadff2d22c364, + 0xb12ac97334928ad1, + 0xb8fe8bc2f9bfa0fd, + 0xb0c3c818b6a92dbf, + 0x4714bdc0b10ce86f, + 0xe229ff6121c4f738, + 0x3c6961147fa02f50, + 0x5ea3bb1b02a54e70, + 0x8f459e43f602c572, + 0x8fea4837d02e2498, + 0x805fb3e2 + ]; + let rlp_byte_len = 66 * 8 + 4; + + let mut expected_res_full = array![ + array![0x1b7a06b509cf7077, 0x75818924a962df35, 0xb4cd681fadecaece, 0xf44ac1730c4044a8] + .span(), + array![0x4661b25ad085a31e, 0x344568fe87045c6d, 0xdc184b5c4b1a9fc1, 0xb47150026035361a] + .span(), + array![0xd1d34035ce044c2c, 0xe5a5533c30721846, 0xa8368d4f30c18366, 0xeecd3ffaf56a0c80] + .span(), + array![0xd37d4bc58d77dca9, 0xfe61d139e72282c4, 0x17d5dcb2ceeec0b0, 0x5138a6378e5bf037] + .span(), + array![0xdd62df56554d5fa9, 0x9b56ae97049962c2, 0x9307207bdafd8ecd, 0xd71897db4cded3f8] + .span(), + array![ // 5 + 0x6e2238146d06d439, 0xa974a843e9c94aaf, 0x86b91dd8b05fc2a9, 0x4c03e2b336138c1d] + .span(), + array![0x18a46ab4637ccc7a, 0xcb6b25a141a0c9b3, 0x5ada7a396b316173, 0x300113bb1b496788] + .span(), + array![0x93c42e25818a3515, 0xb74680c736fe1371, 0x29bb913497a1fb11, 0xae52f85f78007a18] + .span(), + array![0xa7faab16d3429168, 0xdb1d2049dfce8b1c, 0xc490dc0a254e10b2, 0x58964a531f2256e8] + .span(), + array![0xa8fd3425995036dc, 0xa83baeb0dba714a3, 0x2ace690c55b59dc7, 0xa3c1c4ad07c06024] + .span(), + array![0x05b055663b68b020, 0x6b504b4ed003e19e, 0xdab792630039c1cb, 0xe7420366c27811b1] + .span(), + array![0x1c0f63fb45ebed8e, 0x17225718eb3697d9, 0xe21bb715f3d5c6cb, 0x14269bd9e83cb003] + .span(), + array![0x6f985af63da32379, 0x69b9c2e4e6f9e7d5, 0x3999be4e94086b73, 0xf309e62f6114864a] + .span(), + array![0x4a71201ad0d73465, 0x64ce46b9552afba4, 0xd1a22aadff2d22c3, 0xfdb12ac97334928a] + .span(), + array![0x2dbfb8fe8bc2f9bf, 0xe86fb0c3c818b6a9, 0xf7384714bdc0b10c, 0x2f50e229ff6121c4] + .span(), + array![0xa54e703c6961147f, 0x02c5725ea3bb1b02, 0x2e24988f459e43f6, 0x5fb3e28fea4837d0] + .span(), + array![].span() + ]; + + let res = rlp_decode_list_lazy(arr.span(), array![].span()).unwrap(); + 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 = (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 = (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 = ( + 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 = ( + RLPItem::List( + array![ + (*expected_res_full.at(0x5), 32), + (*expected_res_full.at(0x9), 32), + (*expected_res_full.at(0xf), 32) + ] + .span() + ), + rlp_byte_len + ); + assert(res == expected_res, 'Wrong value indexes: 5, 9, 15'); +} + +#[test] +#[available_gas(99999999)] +fn test_rlp_decode_list_long_string() { + let arr = array![ + 0x7235e356aca05bf8, + 0x7f0b03476f57b94f, + 0x4760f75aaf1d2720, + 0xa9c2173ae53aab1f, + 0xf338d438b8ed276f, + 0x27777eada3968dad, + 0x53189e661865fe38, + 0xc101f7b5d6dffd52, + 0x65454695474abcbb, + 0x4567644756674547, + 0x5663776535476567, + 0xfa77645733, + ]; + + let (res, len) = rlp_decode(arr.span()).unwrap(); + assert(len == 1 + (0xf8 - 0xf7) + 0x5b, 'Wrong len'); + + let expected_res = array![ + ( + array![0x57b94f7235e356ac, 0x1d27207f0b03476f, 0x3aab1f4760f75aaf, 0xed276fa9c2173ae5,] + .span(), + 32 + ), + ( + array![ + 0xada3968dadf338d4, + 0x661865fe3827777e, + 0xb5d6dffd5253189e, + 0x95474abcbbc101f7, + 0x4756674547654546, + 0x6535476567456764, + 0xfa77645733566377, + ] + .span(), + 56 + ) + ]; + let expected_item = RLPItem::List(expected_res.span()); + assert(res == expected_item, 'Wrong value'); +} + +#[test] +#[available_gas(99999999)] +fn test_rlp_lazy_decode_list_long_string() { + let arr = array![ + 0x7235e356aca05bf8, + 0x7f0b03476f57b94f, + 0x4760f75aaf1d2720, + 0xa9c2173ae53aab1f, + 0xf338d438b8ed276f, + 0x27777eada3968dad, + 0x53189e661865fe38, + 0xc101f7b5d6dffd52, + 0x65454695474abcbb, + 0x4567644756674547, + 0x5663776535476567, + 0xfa77645733, + ]; + let rlp_byte_len = 11 * 8 + 5; + + let expected_res_full = array![ + array![0x57b94f7235e356ac, 0x1d27207f0b03476f, 0x3aab1f4760f75aaf, 0xed276fa9c2173ae5,] + .span(), + array![ + 0xada3968dadf338d4, + 0x661865fe3827777e, + 0xb5d6dffd5253189e, + 0x95474abcbbc101f7, + 0x4756674547654546, + 0x6535476567456764, + 0xfa77645733566377, + ] + .span(), + ]; + + let res = rlp_decode_list_lazy(arr.span(), array![].span()).unwrap(); + 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 = (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 = (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 = ( + 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'); +} From 8c74f18de2fde767be3fb6588524ac706eb26851 Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Mon, 30 Oct 2023 19:04:08 +0600 Subject: [PATCH 13/28] 5.7 Words64TryIntoU256LE Does Not Automatically Pad Input --- src/data_structures/eth_mpt.cairo | 44 +++++++++------ src/data_structures/tests/test_eth_mpt.cairo | 9 ++- src/utils/tests/test_bitwise.cairo | 21 ++++++- src/utils/types/tests/test_words64.cairo | 40 +++++++------ src/utils/types/words64.cairo | 59 +++++++++++++------- 5 files changed, 117 insertions(+), 56 deletions(-) diff --git a/src/data_structures/eth_mpt.cairo b/src/data_structures/eth_mpt.cairo index 3d8820c..655bea2 100644 --- a/src/data_structures/eth_mpt.cairo +++ b/src/data_structures/eth_mpt.cairo @@ -1,8 +1,8 @@ use cairo_lib::hashing::keccak::keccak_cairo_words64; use cairo_lib::encoding::rlp::{RLPItem, rlp_decode, rlp_decode_list_lazy}; use cairo_lib::utils::types::byte::{Byte, ByteTrait}; -use cairo_lib::utils::bitwise::{right_shift, left_shift}; -use cairo_lib::utils::types::words64::{Words64, Words64Trait, Words64TryIntoU256LE}; +use cairo_lib::utils::bitwise::{right_shift, left_shift, reverse_endianness_u256}; +use cairo_lib::utils::types::words64::{Words64, Words64Trait}; use cairo_lib::utils::math::pow; // @notice Ethereum Merkle Patricia Trie struct @@ -113,9 +113,9 @@ impl MPTImpl of MPTTrait { if current_hash_words.len() == 0 { 0 } else { - match current_hash_words.try_into() { - Option::Some(h) => h, - Option::None(_) => { + match current_hash_words.as_u256_be(32) { + Result::Ok(h) => reverse_endianness_u256(h), + Result::Err(_) => { break Result::Err('Invalid hash'); } } @@ -272,18 +272,28 @@ impl MPTImpl of MPTTrait { let n_nibbles = (first_len * 2) - 1; if prefix == 0 { - match second.try_into() { - Option::Some(n) => Result::Ok( - (MPTNode::Extension((first, n, 2, n_nibbles - 1)), rlp_byte_len) + match second.as_u256_be(32) { + Result::Ok(n) => Result::Ok( + ( + MPTNode::Extension( + (first, reverse_endianness_u256(n), 2, n_nibbles - 1) + ), + rlp_byte_len + ) ), - Option::None(_) => Result::Err('Invalid next node') + Result::Err(_) => 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)), rlp_byte_len) + match second.as_u256_be(32) { + Result::Ok(n) => Result::Ok( + ( + MPTNode::Extension( + (first, reverse_endianness_u256(n), 1, n_nibbles) + ), + rlp_byte_len + ) ), - Option::None(_) => Result::Err('Invalid next node') + Result::Err(_) => Result::Err('Invalid next node') } } else if prefix == 2 { Result::Ok((MPTNode::Leaf((first, second, 2, n_nibbles - 1)), rlp_byte_len)) @@ -310,9 +320,11 @@ impl MPTImpl of MPTTrait { 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') + match hash_words.as_u256_be(32) { + Result::Ok(h) => Result::Ok( + (MPTNode::LazyBranch(reverse_endianness_u256(h)), rlp_byte_len) + ), + Result::Err(_) => Result::Err('Invalid hash') } } } diff --git a/src/data_structures/tests/test_eth_mpt.cairo b/src/data_structures/tests/test_eth_mpt.cairo index d3f6768..ff1e06d 100644 --- a/src/data_structures/tests/test_eth_mpt.cairo +++ b/src/data_structures/tests/test_eth_mpt.cairo @@ -1,5 +1,6 @@ use cairo_lib::data_structures::eth_mpt::{MPTNode, MPTTrait}; -use cairo_lib::utils::types::words64::Words64TryIntoU256LE; +use cairo_lib::utils::types::words64::{Words64, Words64Trait}; +use cairo_lib::utils::bitwise::reverse_endianness_u256; #[test] #[available_gas(9999999999)] @@ -107,7 +108,11 @@ fn test_decode_rlp_node_branch() { if i >= hashes.len() { break (); } - assert((*hashes.at(i)).try_into().unwrap() == *expected.at(i), 'Wrong hash'); + assert( + reverse_endianness_u256((*hashes.at(i)).as_u256_be(32).unwrap()) == *expected + .at(i), + 'Wrong hash' + ); i += 1; }; }, diff --git a/src/utils/tests/test_bitwise.cairo b/src/utils/tests/test_bitwise.cairo index b486d17..58e0665 100644 --- a/src/utils/tests/test_bitwise.cairo +++ b/src/utils/tests/test_bitwise.cairo @@ -1,4 +1,4 @@ -use cairo_lib::utils::bitwise::{left_shift, right_shift, bit_length}; +use cairo_lib::utils::bitwise::{left_shift, right_shift, bit_length, reverse_endianness_u256}; #[test] #[available_gas(999999)] @@ -39,3 +39,22 @@ fn test_bit_length() { fn test_bit_length_most_significant_bit_one() { assert(bit_length(4294967295_u32) == 32, 'bit length of 2^32-1 is 32'); } + +#[test] +#[available_gas(999999)] +fn test_reverse_endianness_u256() { + assert(reverse_endianness_u256(0) == 0, 'reverse endianness of 0'); + assert( + reverse_endianness_u256( + 1 + ) == 0x0100000000000000000000000000000000000000000000000000000000000000, + 'reverse endianness of 1' + ); + assert( + reverse_endianness_u256( + 0x1307645868aee0028be496b378bfeee2bac59d1239549a8ef4bff9009af5ef + ) == 0xEFF59A00F9BFF48E9A5439129DC5BAE2EEBF78B396E48B02E0AE685864071300, + 'reverse endianness of 31 bytes' + ); +} + diff --git a/src/utils/types/tests/test_words64.cairo b/src/utils/types/tests/test_words64.cairo index a34abdf..9618d34 100644 --- a/src/utils/types/tests/test_words64.cairo +++ b/src/utils/types/tests/test_words64.cairo @@ -1,5 +1,5 @@ use cairo_lib::utils::types::words64::{ - Words64, Words64Trait, Words64TryIntoU256LE, reverse_endianness_u64, bytes_used_u64 + Words64, Words64Trait, reverse_endianness_u64, bytes_used_u64 }; #[test] @@ -47,33 +47,33 @@ fn test_slice_words64_le_single_word_full() { } #[test] -#[should_panic] #[available_gas(99999999)] -fn test_into_u256_le_wrong_num_words() { - let words = array![0x83498349, 0x83479735927498, 0x12345623ff458695, 0xabcd344, 0xabcdef3345] +fn test_as_u256_be_full() { + let words = array![ + 0x2e8b632605e21673, 0x480829ebcee54bc4, 0xb6f239256ff310f9, 0x09898da43a5d35f4, + ] .span(); - let res: u256 = words.try_into().unwrap(); + + let expected = 0x7316e20526638b2ec44be5ceeb290848f910f36f2539f2b6f4355d3aa48d8909; + assert(words.as_u256_be(32).unwrap() == expected, 'Wrong value'); } #[test] #[available_gas(99999999)] -fn test_into_u256_le() { - let words = array![ - 0x2e8b632605e21673, 0x480829ebcee54bc4, 0xb6f239256ff310f9, 0x09898da43a5d35f4, - ] - .span(); +fn test_as_u256_be_not_full() { + let words = array![0x2e8b632605e21673, 0x480829ebcee54bc4, 0xb6f2392a].span(); - let expected = 0x09898DA43A5D35F4B6F239256FF310F9480829EBCEE54BC42E8B632605E21673; - assert(words.try_into().unwrap() == expected, 'Wrong value'); + let expected = 0x7316e20526638b2ec44be5ceeb2908482a39f2b6; + assert(words.as_u256_be(20).unwrap() == expected, 'Wrong value'); } #[test] #[available_gas(99999999)] -fn test_into_u256_le_not_full() { - let words = array![0x2e8b632605e21673, 0x4bc4, 0, 0,].span(); +fn test_as_u256_be_not_full_start() { + let words = array![0x2e8b632605e20000, 0x480829ebcee54bc4, 0xb6f2392a].span(); - let expected = 0x4BC42E8B632605E21673; - assert(words.try_into().unwrap() == expected, 'Wrong value'); + let expected = 0xe20526638b2ec44be5ceeb2908482a39f2b6; + assert(words.as_u256_be(20).unwrap() == expected, 'Wrong value'); } #[test] @@ -92,6 +92,14 @@ fn test_reverse_endianness_not_full() { assert(reverse_endianness_u64(val, Option::Some(3)) == expected, 'Wrong value'); } +#[test] +#[available_gas(99999999)] +fn test_reverse_endianness_not_full_padding() { + let val = 0xabcdef; + let expected = 0xefcdab00; + assert(reverse_endianness_u64(val, Option::Some(4)) == expected, 'Wrong value'); +} + #[test] #[available_gas(99999999)] fn test_bytes_used() { diff --git a/src/utils/types/words64.cairo b/src/utils/types/words64.cairo index 2537bff..8c4d39b 100644 --- a/src/utils/types/words64.cairo +++ b/src/utils/types/words64.cairo @@ -5,40 +5,57 @@ use cairo_lib::utils::bitwise::left_shift; // Example: 0x34957c6d8a83f9cff74578dea9 is represented as [0xcff9838a6d7c9534, 0xa9de7845f7] type Words64 = Span; -impl Words64TryIntoU256LE of TryInto { - // @notice Converts a span of 64 bit little endian words into a little endian u256 - fn try_into(self: Words64) -> Option { - if self.len() > 4 { - return Option::None(()); +#[generate_trait] +impl Words64Impl of Words64Trait { + // @notice Converts little endian 64 bit words to a big endian u256 + // @param bytes_used The number of bytes used + // @return The big endian u256 representation of the words + fn as_u256_be(self: Words64, bytes_used: usize) -> Result { + let len = self.len(); + + if len > 4 { + return Result::Err('Too many words'); } - if self.len() == 0 { - return Option::Some(0); + if len == 0 || bytes_used == 0 { + return Result::Ok(0); } - let pows = array![ - 0x10000000000000000, // 2 ** 64 - 0x100000000000000000000000000000000, // 2 ** 128 - 0x1000000000000000000000000000000000000000000000000 // 2 ** 192 - ]; + let mut len_last_word = bytes_used % 8; + if len_last_word == 0 { + len_last_word = 8; + } + + let mut output: u256 = reverse_endianness_u64( + (*self.at(len - 1)), Option::Some(len_last_word) + ) + .into(); + + let word_pow2 = 0x10000000000000000; // 2 ** 64 + let mut current_pow2: u256 = if len_last_word == 8 { + word_pow2 + } else { + pow2(len_last_word * 8).into() + }; - let mut output: u256 = (*self.at(0)).into(); - let mut i: usize = 1; + let mut i = 1; loop { - if i == self.len() { - break Option::Some(output); + if i == len { + break Result::Ok(output); } - // left shift and add - output = output | ((*self.at(i)).into() * *pows.at(i - 1)); + output = output + | (reverse_endianness_u64(*self.at(len - i - 1), Option::None(())).into() + * current_pow2); + + if i < len - 1 { + current_pow2 = current_pow2 * word_pow2; + } i += 1; } } -} -#[generate_trait] -impl Words64Impl of Words64Trait { // @notice Slices 64 bit little endian words from a starting byte and a length // @param start The starting byte // The starting byte is counted from the left. Example: 0xabcdef -> byte 0 is 0xab, byte 1 is 0xcd... From 97aad214ad4fb32576eb0f183d09c24f0bad969d Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Tue, 31 Oct 2023 11:15:07 +0600 Subject: [PATCH 14/28] 5.7(cont) Words64TryIntoU256LE Does Not Automatically Pad Input --- src/data_structures/eth_mpt.cairo | 30 ++++------- src/data_structures/tests/test_eth_mpt.cairo | 7 +-- src/utils/types/tests/test_words64.cairo | 52 +++++++++++++++++++- src/utils/types/words64.cairo | 41 +++++++++++++++ 4 files changed, 101 insertions(+), 29 deletions(-) diff --git a/src/data_structures/eth_mpt.cairo b/src/data_structures/eth_mpt.cairo index 655bea2..dbcf957 100644 --- a/src/data_structures/eth_mpt.cairo +++ b/src/data_structures/eth_mpt.cairo @@ -1,7 +1,7 @@ use cairo_lib::hashing::keccak::keccak_cairo_words64; use cairo_lib::encoding::rlp::{RLPItem, rlp_decode, rlp_decode_list_lazy}; use cairo_lib::utils::types::byte::{Byte, ByteTrait}; -use cairo_lib::utils::bitwise::{right_shift, left_shift, reverse_endianness_u256}; +use cairo_lib::utils::bitwise::{right_shift, left_shift}; use cairo_lib::utils::types::words64::{Words64, Words64Trait}; use cairo_lib::utils::math::pow; @@ -113,8 +113,8 @@ impl MPTImpl of MPTTrait { if current_hash_words.len() == 0 { 0 } else { - match current_hash_words.as_u256_be(32) { - Result::Ok(h) => reverse_endianness_u256(h), + match current_hash_words.as_u256_le(32) { + Result::Ok(h) => h, Result::Err(_) => { break Result::Err('Invalid hash'); } @@ -272,26 +272,16 @@ impl MPTImpl of MPTTrait { let n_nibbles = (first_len * 2) - 1; if prefix == 0 { - match second.as_u256_be(32) { + match second.as_u256_le(32) { Result::Ok(n) => Result::Ok( - ( - MPTNode::Extension( - (first, reverse_endianness_u256(n), 2, n_nibbles - 1) - ), - rlp_byte_len - ) + (MPTNode::Extension((first, n, 2, n_nibbles - 1)), rlp_byte_len) ), Result::Err(_) => Result::Err('Invalid next node') } } else if prefix == 1 { - match second.as_u256_be(32) { + match second.as_u256_le(32) { Result::Ok(n) => Result::Ok( - ( - MPTNode::Extension( - (first, reverse_endianness_u256(n), 1, n_nibbles) - ), - rlp_byte_len - ) + (MPTNode::Extension((first, n, 1, n_nibbles)), rlp_byte_len) ), Result::Err(_) => Result::Err('Invalid next node') } @@ -320,10 +310,8 @@ impl MPTImpl of MPTTrait { RLPItem::Bytes(_) => Result::Err('Invalid RLP for node'), RLPItem::List(l) => { let (hash_words, _) = *l.at(0); - match hash_words.as_u256_be(32) { - Result::Ok(h) => Result::Ok( - (MPTNode::LazyBranch(reverse_endianness_u256(h)), rlp_byte_len) - ), + match hash_words.as_u256_le(32) { + Result::Ok(h) => Result::Ok((MPTNode::LazyBranch(h), rlp_byte_len)), Result::Err(_) => Result::Err('Invalid hash') } } diff --git a/src/data_structures/tests/test_eth_mpt.cairo b/src/data_structures/tests/test_eth_mpt.cairo index ff1e06d..42556d5 100644 --- a/src/data_structures/tests/test_eth_mpt.cairo +++ b/src/data_structures/tests/test_eth_mpt.cairo @@ -1,6 +1,5 @@ use cairo_lib::data_structures::eth_mpt::{MPTNode, MPTTrait}; use cairo_lib::utils::types::words64::{Words64, Words64Trait}; -use cairo_lib::utils::bitwise::reverse_endianness_u256; #[test] #[available_gas(9999999999)] @@ -108,11 +107,7 @@ fn test_decode_rlp_node_branch() { if i >= hashes.len() { break (); } - assert( - reverse_endianness_u256((*hashes.at(i)).as_u256_be(32).unwrap()) == *expected - .at(i), - 'Wrong hash' - ); + assert((*hashes.at(i)).as_u256_le(32).unwrap() == *expected.at(i), 'Wrong hash'); i += 1; }; }, diff --git a/src/utils/types/tests/test_words64.cairo b/src/utils/types/tests/test_words64.cairo index 9618d34..d0ee0a1 100644 --- a/src/utils/types/tests/test_words64.cairo +++ b/src/utils/types/tests/test_words64.cairo @@ -70,12 +70,60 @@ fn test_as_u256_be_not_full() { #[test] #[available_gas(99999999)] fn test_as_u256_be_not_full_start() { - let words = array![0x2e8b632605e20000, 0x480829ebcee54bc4, 0xb6f2392a].span(); + let words = array![0x008b632605e20000, 0x480829ebcee54bc4, 0xb6f2392a].span(); - let expected = 0xe20526638b2ec44be5ceeb2908482a39f2b6; + let expected = 0xe20526638b00c44be5ceeb2908482a39f2b6; assert(words.as_u256_be(20).unwrap() == expected, 'Wrong value'); } +#[test] +#[available_gas(99999999)] +fn test_as_u256_be_not_full_end() { + let words = array![0x2e8b632605e20000, 0x480829ebcee54bc4, 0xb6f2392a].span(); + + let expected = 0x0000e20526638b2ec44be5ceeb2908482a39f2b600; + assert(words.as_u256_be(21).unwrap() == expected, 'Wrong value'); +} + +#[test] +#[available_gas(99999999)] +fn test_as_u256_le_full() { + let words = array![ + 0x2e8b632605e21673, 0x480829ebcee54bc4, 0xb6f239256ff310f9, 0x09898da43a5d35f4, + ] + .span(); + + let expected = 0x09898DA43A5D35F4B6F239256FF310F9480829EBCEE54BC42E8B632605E21673; + assert(words.as_u256_le(32).unwrap() == expected, 'Wrong value'); +} + +#[test] +#[available_gas(99999999)] +fn test_as_u256_le_not_full() { + let words = array![0x2e8b632605e21673, 0x480829ebcee54bc4, 0xb6f2392a].span(); + + let expected: u256 = 0xB6F2392A480829EBCEE54BC42E8B632605E21673000000000000000000000000; + assert(words.as_u256_le(20).unwrap() == expected, 'Wrong value'); +} + +#[test] +#[available_gas(99999999)] +fn test_as_u256_le_not_full_start() { + let words = array![0x008b632605e20000, 0x480829ebcee54bc4, 0xb6f2392a].span(); + + let expected = 0xB6F2392A480829EBCEE54BC4008B632605E20000000000000000000000000000; + assert(words.as_u256_le(20).unwrap() == expected, 'Wrong value'); +} + +#[test] +#[available_gas(99999999)] +fn test_as_u256_le_not_full_end() { + let words = array![0x008b632605e20000, 0x480829ebcee54bc4, 0xb6f2392a].span(); + + let expected: u256 = 0x00B6F2392A480829EBCEE54BC4008B632605E200000000000000000000000000; + assert(words.as_u256_le(21).unwrap() == expected, 'Wrong value'); +} + #[test] #[available_gas(99999999)] fn test_reverse_endianness_u64() { diff --git a/src/utils/types/words64.cairo b/src/utils/types/words64.cairo index 8c4d39b..23ef8f1 100644 --- a/src/utils/types/words64.cairo +++ b/src/utils/types/words64.cairo @@ -1,4 +1,5 @@ use cairo_lib::utils::bitwise::left_shift; +use cairo_lib::utils::math::pow; // @notice Represents a span of 64 bit words // @dev In many cases it's expected that the words are in little endian, but the overall order is big endian @@ -56,6 +57,46 @@ impl Words64Impl of Words64Trait { } } + // @notice Converts little endian 64 bit words to a little endian u256 + // @param bytes_used The number of bytes used + // @return The little endian u256 representation of the words + fn as_u256_le(self: Words64, bytes_used: usize) -> Result { + let len = self.len(); + + if len > 4 { + return Result::Err('Too many words'); + } + + if len == 0 || bytes_used == 0 { + return Result::Ok(0); + } + + let mut len_last_word = bytes_used % 8; + if len_last_word == 0 { + len_last_word = 8; + } + + let mut output: u256 = 0; + + let word_pow2 = 0x10000000000000000; // 2 ** 64 + let mut current_pow2: u256 = pow(2, (32 - bytes_used.into()) * 8); + + let mut i = 0; + loop { + if i == len { + break Result::Ok(output); + } + + output = output | ((*self.at(i)).into() * current_pow2); + + if i < len - 1 { + current_pow2 = current_pow2 * word_pow2; + } + + i += 1; + } + } + // @notice Slices 64 bit little endian words from a starting byte and a length // @param start The starting byte // The starting byte is counted from the left. Example: 0xabcdef -> byte 0 is 0xab, byte 1 is 0xcd... From e56d6cfbcf1a63884cd90a16ac9fb82e2d6f7295 Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Tue, 31 Oct 2023 11:23:57 +0600 Subject: [PATCH 15/28] optimization: MPT verify hardcode key_pow2 64 nibbles --- src/data_structures/eth_mpt.cairo | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/data_structures/eth_mpt.cairo b/src/data_structures/eth_mpt.cairo index dbcf957..7733a79 100644 --- a/src/data_structures/eth_mpt.cairo +++ b/src/data_structures/eth_mpt.cairo @@ -57,7 +57,11 @@ impl MPTImpl of MPTTrait { ) -> Result { let mut current_hash = *self.root; let mut proof_index: usize = 0; - let mut key_pow2: u256 = pow(2, (key_len.into() - 1) * 4); + let mut key_pow2: u256 = if key_len == 64 { + 0x1000000000000000000000000000000000000000000000000000000000000000 + } else { + pow(2, (key_len.into() - 1) * 4) + }; let proof_len = proof.len(); From 04035337ac93ec720d0891c71caf685402d79a63 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Tue, 31 Oct 2023 10:33:51 +0100 Subject: [PATCH 16/28] 5.5 MMR Verify Proof: fix --- src/data_structures/mmr/mmr.cairo | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/data_structures/mmr/mmr.cairo b/src/data_structures/mmr/mmr.cairo index 0c2141b..8a865ce 100644 --- a/src/data_structures/mmr/mmr.cairo +++ b/src/data_structures/mmr/mmr.cairo @@ -105,6 +105,10 @@ impl MMRImpl of MMRTrait { fn verify_proof( self: @MMR, index: usize, hash: felt252, peaks: Peaks, proof: Proof ) -> Result { + let leaf_count = mmr_size_to_leaf_count((*self.last_pos).into()); + if leaf_count_to_peaks_count(leaf_count) != peaks.len().into() { + return Result::Err('Invalid peaks count'); + } if !peaks.valid(*self.last_pos, *self.root) { return Result::Err('Invalid peaks'); } From f69c919eeaa49fa3118d81696dc54cf52a4a9e8b Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Tue, 31 Oct 2023 10:35:22 +0100 Subject: [PATCH 17/28] fix: count_ones() incorrect specification --- src/data_structures/mmr/utils.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index d59c71f..a67068b 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -28,7 +28,7 @@ fn compute_root(last_pos: felt252, peaks: Peaks) -> felt252 { } // @notice Count the number of bits set to 1 in a 256-bit unsigned integer -// @param n The 32-bit unsigned integer +// @param n The 256-bit unsigned integer // @return The number of bits set to 1 in n fn count_ones(arg: u256) -> u256 { let mut n = arg; From da6c9f107811de27c54cff3153c4528db32761de Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Tue, 31 Oct 2023 10:37:54 +0100 Subject: [PATCH 18/28] fix: Inconsistency in implemented trait names --- src/utils/math.cairo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/math.cairo b/src/utils/math.cairo index 918c358..56f93fb 100644 --- a/src/utils/math.cairo +++ b/src/utils/math.cairo @@ -7,7 +7,7 @@ use math::Oneable; fn pow< T, - impl Zeroable: Zeroable, + impl TZeroable: Zeroable, impl TOneable: Oneable, impl TCopy: Copy, impl TDrop: Drop, @@ -51,7 +51,7 @@ fn slow_pow< fn fast_pow< T, - impl Zeroable: Zeroable, + impl TZeroable: Zeroable, impl TOneable: Oneable, impl TCopy: Copy, impl TDrop: Drop, From 39a948775556dbb63f5bd534c9df1fff7b1b1599 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Tue, 31 Oct 2023 16:08:30 +0100 Subject: [PATCH 19/28] optimization: mmr_size_to_leaf_count --- src/data_structures/mmr/tests/test_utils.cairo | 14 ++++++++++++++ src/data_structures/mmr/utils.cairo | 16 ++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/data_structures/mmr/tests/test_utils.cairo b/src/data_structures/mmr/tests/test_utils.cairo index 511f9a4..4a80033 100644 --- a/src/data_structures/mmr/tests/test_utils.cairo +++ b/src/data_structures/mmr/tests/test_utils.cairo @@ -1,5 +1,6 @@ use cairo_lib::data_structures::mmr::utils::{ get_height, compute_root, count_ones, leaf_index_to_mmr_index, get_peak_info, + mmr_size_to_leaf_count, }; use cairo_lib::hashing::poseidon::PoseidonHasher; use cairo_lib::data_structures::mmr::peaks::PeaksTrait; @@ -73,6 +74,19 @@ fn test_leaf_index_to_mmr_index() { assert(leaf_index_to_mmr_index(11) == 19, 'leaf_..._index(11) != 19'); } +#[test] +#[available_gas(999999999)] +fn test_mmr_size_to_leaf_count() { + assert(mmr_size_to_leaf_count(1) == 1, 'mmr_size_to_leaf_count(1) != 1'); + assert(mmr_size_to_leaf_count(3) == 2, 'mmr_size_to_leaf_count(3) != 2'); + assert(mmr_size_to_leaf_count(4) == 3, 'mmr_size_to_leaf_count(4) != 3'); + assert(mmr_size_to_leaf_count(7) == 4, 'mmr_size_to_leaf_count(7) != 4'); + assert(mmr_size_to_leaf_count(8) == 5, 'mmr_size_to_leaf_count(8) != 5'); + assert(mmr_size_to_leaf_count(10) == 6, 'mmr_size_to_leaf_count(10) != 6'); + assert(mmr_size_to_leaf_count(11) == 7, 'mmr_size_to_leaf_count(11) != 7'); + assert(mmr_size_to_leaf_count(15) == 8, 'mmr_size_to_leaf_count(15) != 8'); +} + #[test] #[available_gas(999999999)] fn test_get_peak_info() { diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index a67068b..76571a5 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -54,19 +54,19 @@ fn leaf_index_to_mmr_index(n: u256) -> u256 { // @result Number of leaves fn mmr_size_to_leaf_count(n: u256) -> u256 { let mut mmr_size = n; - let bits = bit_length(mmr_size); - let mut i = pow(2, bits); + let bits = bit_length(mmr_size + 1); + let mut mountain_leaf_count = pow(2, bits - 1); let mut leaf_count = 0; loop { - if i == 0 { + if mountain_leaf_count == 0 { break leaf_count; } - let x = 2 * i - 1; - if x <= mmr_size { - mmr_size -= x; - leaf_count += i; + let mountain_size = 2 * mountain_leaf_count - 1; + if mountain_size <= mmr_size { + mmr_size -= mountain_size; + leaf_count += mountain_leaf_count; } - i /= 2; + mountain_leaf_count /= 2; } } From fe882554e6e085bfc7ee9f2e97936a975268a260 Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Wed, 1 Nov 2023 11:57:58 +0600 Subject: [PATCH 20/28] optimization: lazy decode computing pow2_shift --- src/encoding/rlp.cairo | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/encoding/rlp.cairo b/src/encoding/rlp.cairo index f9d0701..78b4cf3 100644 --- a/src/encoding/rlp.cairo +++ b/src/encoding/rlp.cairo @@ -185,9 +185,8 @@ fn rlp_decode_list_lazy(input: Words64, lazy: Span) -> Result<(RLPItem, u } let current_word = current_input_index / 8; - let current_word_offset = 7 - (current_input_index % 8); - let pow2_shift = pow2((7 - current_word_offset) * 8); + let pow2_shift = pow2((current_input_index % 8) * 8); let prefix = (*input.at(current_word) / pow2_shift) & 0xff; let rlp_type = RLPTypeTrait::from_byte(prefix.try_into().unwrap()).unwrap(); From ac9c33b1a467d01f0de9c97320154cc4f4808251 Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Wed, 1 Nov 2023 12:01:07 +0600 Subject: [PATCH 21/28] fix: empty keccak value --- src/hashing/keccak.cairo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hashing/keccak.cairo b/src/hashing/keccak.cairo index 7bc261d..d106bce 100644 --- a/src/hashing/keccak.cairo +++ b/src/hashing/keccak.cairo @@ -1,12 +1,12 @@ use cairo_lib::utils::types::words64::{Words64, bytes_used_u64}; use keccak::cairo_keccak; -const EMPTY_KECCAK: u256 = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; +const EMPTY_KECCAK: u256 = 0x70A4855D04D8FA7B3B2782CA53B600E5C003C7DCB27D7E923C23F7860146D2C5; // @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 +// @return The little endian keccak hash of the input, matching the output of the EVM's keccak256 opcode fn keccak_cairo_words64(words: Words64, last_word_bytes: usize) -> u256 { if words.is_empty() { return EMPTY_KECCAK; From eda3db54a304d96bb9daaf8a40a7be8dfd29ba52 Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Wed, 1 Nov 2023 16:20:56 +0600 Subject: [PATCH 22/28] fix: lazy decoder break condition --- src/encoding/rlp.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/encoding/rlp.cairo b/src/encoding/rlp.cairo index 78b4cf3..031ad5a 100644 --- a/src/encoding/rlp.cairo +++ b/src/encoding/rlp.cairo @@ -180,7 +180,7 @@ fn rlp_decode_list_lazy(input: Words64, lazy: Span) -> Result<(RLPItem, u break Result::Ok((RLPItem::List(output.span()), rlp_byte_len)); } - if current_input_index >= len { + if current_input_index >= rlp_byte_len { break Result::Err('Too many items to decode'); } From 114a9296d7fec39f1e5a3a8e85f14bfff723c71d Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Thu, 2 Nov 2023 11:31:04 +0600 Subject: [PATCH 23/28] fix: mpt verification overflowing for long key_end and shared_nibbles --- src/data_structures/eth_mpt.cairo | 76 +++++++++++--------- src/data_structures/tests/test_eth_mpt.cairo | 55 ++++++++++++++ 2 files changed, 98 insertions(+), 33 deletions(-) diff --git a/src/data_structures/eth_mpt.cairo b/src/data_structures/eth_mpt.cairo index 7733a79..e56c9ec 100644 --- a/src/data_structures/eth_mpt.cairo +++ b/src/data_structures/eth_mpt.cairo @@ -148,37 +148,42 @@ impl MPTImpl of MPTTrait { let mut shared_nibbles_word_idx = nibbles_skip / 16; let mut shared_nibbles_word = *shared_nibbles.at(shared_nibbles_word_idx); let mut i_nibbles = 0; - let next_hash = loop { - if i_nibbles == n_nibbles { - break Result::Ok(next_node); - } - if key_pow2 == 0 { - break Result::Err('Key reached end'); - } + let next_hash = if n_nibbles == 0 { + Result::Ok(next_node) + } else { + loop { + let current_nibble_shared_nibbles = (shared_nibbles_word + / shared_nibbles_pow2) + & 0xf; + let current_nibble_key = (key / key_pow2) & 0xf; + if current_nibble_shared_nibbles.into() != current_nibble_key { + break Result::Err('Extension nibbles not matching'); + } - let current_nibble_shared_nibbles = (shared_nibbles_word - / shared_nibbles_pow2) - & 0xf; - let current_nibble_key = (key / key_pow2) & 0xf; - if current_nibble_shared_nibbles.into() != current_nibble_key { - break Result::Err('Extension nibbles not matching'); - } + key_pow2 = key_pow2 / 16; + i_nibbles += 1; - if shared_nibbles_pow2 == 0x100000000000000 { - shared_nibbles_pow2 = 16; - shared_nibbles_word_idx += 1; - shared_nibbles_word = *shared_nibbles.at(shared_nibbles_word_idx); - } else { - if in_byte { - shared_nibbles_pow2 = shared_nibbles_pow2 * 0x1000; - } else { - shared_nibbles_pow2 = shared_nibbles_pow2 / 0x10; + if i_nibbles == n_nibbles { + break Result::Ok(next_node); + } + if key_pow2 == 0 { + break Result::Err('Key reached end'); } - }; - in_byte = !in_byte; - key_pow2 = key_pow2 / 16; - i_nibbles += 1; + if shared_nibbles_pow2 == 0x100000000000000 { + shared_nibbles_pow2 = 16; + shared_nibbles_word_idx += 1; + shared_nibbles_word = *shared_nibbles.at(shared_nibbles_word_idx); + } else { + if in_byte { + shared_nibbles_pow2 = shared_nibbles_pow2 * 0x1000; + } else { + shared_nibbles_pow2 = shared_nibbles_pow2 / 0x10; + } + }; + + in_byte = !in_byte; + } }; match next_hash { @@ -193,6 +198,10 @@ impl MPTImpl of MPTTrait { MPTNode::Leaf(( key_end, value, nibbles_skip, n_nibbles )) => { + if key_pow2 == 0 && n_nibbles == 0 { + break Result::Ok(value); + } + let mut key_end_pow2 = pow(2, nibbles_skip.into() * 4); let mut in_byte = false; @@ -209,16 +218,19 @@ impl MPTImpl of MPTTrait { let mut key_end_word = *key_end.at(key_end_word_idx); let mut i_nibbles = 0; break loop { - if key_pow2 == 0 && i_nibbles == n_nibbles { - break Result::Ok(value); - } - let current_nibble_key_end = (key_end_word / key_end_pow2) & 0xf; let current_nibble_key = (key / key_pow2) & 0xf; if current_nibble_key_end.into() != current_nibble_key { break Result::Err('Key not matching'); } + key_pow2 = key_pow2 / 16; + i_nibbles += 1; + + if key_pow2 == 0 && i_nibbles == n_nibbles { + break Result::Ok(value); + } + if key_end_pow2 == 0x100000000000000 { key_end_pow2 = 16; key_end_word_idx += 1; @@ -232,8 +244,6 @@ impl MPTImpl of MPTTrait { }; in_byte = !in_byte; - key_pow2 = key_pow2 / 16; - i_nibbles += 1; }; } }; diff --git a/src/data_structures/tests/test_eth_mpt.cairo b/src/data_structures/tests/test_eth_mpt.cairo index 42556d5..0d81dfe 100644 --- a/src/data_structures/tests/test_eth_mpt.cairo +++ b/src/data_structures/tests/test_eth_mpt.cairo @@ -1,5 +1,6 @@ use cairo_lib::data_structures::eth_mpt::{MPTNode, MPTTrait}; use cairo_lib::utils::types::words64::{Words64, Words64Trait}; +use debug::PrintTrait; #[test] #[available_gas(9999999999)] @@ -768,3 +769,57 @@ fn test_full_verify() { let res = mpt.verify(key, 64, proof.span()).unwrap(); assert(res == expected_res.span(), 'Result not matching'); } + +#[test] +#[available_gas(9999999999999)] +fn test_full_verify_2_nodes() { + //// Account : 0x7b5C526B7F8dfdff278b4a3e045083FBA4028790 | Goerli | Block 9000000 + ////"0xf90211a0f09eae4e1e51fdde02a2884e285b8a8a9c72cc7e7cdaeef013714e3499bcd475a0ce33fd7097055e50d64c42759027e41ffb22d5b2a03ee67207dc94b547e40956a04817bf75497b71a78957ff89d05107cbf16ead02f7e68f13cead9e7d24dfcda5a00751841dcd0e21ff273930aa4722cabae7ea4e09d0f4e9f667b57ab68a41652ea047008ee2caeec1839c016d0a8efd2e901091bfae5388fc064db9f14f4bda362da0f952be9637ec6790bcdcf9ae3d4bca607259f26c0731e3cbd2882924c9db5653a061a8882bde126643739fe5f0acc5d234467718c27217f56513fd222009802336a061dbaa68a4290e8cce57166ffc6fd22d081c5893a081082b23668ef6c7d65c81a0ef2e0aea160700e14be7285c8b83535f4d104a74ac8db6c188d84ee48a8a647ca0c00853c7500db3c616d5d7dcd7503c02307045e7670a0749ffdebadc732a9ab4a068050da8f891b57fbeacffe4ba3e41f11c5d6b0ec8553fbb796f46951ecd1445a0762e36c38c548c5ae61da51205ef1dc66390702397becef53c50d969cae7a2ada0abff9de80f8e14979ebbe80ae3e702e61b31b91ea481c0e63a7bde12e866eeb5a017220448de88495fdf81446233768ef9441058e4602ecafc1da85a7cbbf1c16da084351381e6cad5052c82f731e8d19d86193794eccdf274529bed7e67309cca78a0784e83133c0ba8ff0262d0c96dc93f936d97eac46327d32f3c1baceb63934d9d80", + ////"0xf90211a0679ec41f2230e1f57eededf17732966880d9835d744ad769a1b246829341a588a09c2941acea1f1461a7d0af3bb9917c16e3a3339556623a6cad6d1f40ee8fc8a8a0211b79624826f8cd651f7a0123356cfef56854adf7285163988ba4eee0e8f964a062a3e341692078b717029cd105b462749386aecc1cb647721cc382872eac4a51a01a9fc7658bcc2948e1123273e83fb66894e64c2e19aa8f3ac546c45ef4b22290a08c5cdf2e341821e9c3163ec52847e33884f4797669607a60a8adafd00edead0ea08b07046b12762a58e03a482d597cca869aaffd85214bbd08c4624325a7cf80c9a0602d16a56550182218f642f56e66b1cf72555c38dac0fd061b8ef113d4653f4aa038fa2d962cfe43eb49f5a7d1787a436e8e3c6858665b1b0703c4e42ad43f962ea03a706c9b0e0757079f96d9df003eae31aafcf7525d6114033ee929c78adc580aa0f8a66bdc97088d4c73429b9ee00d7bcd0589be3462a53e9f5b9876d4b5231e40a04bbcbff81f2c0b65f29724ef71f6d439b6f857ad5fa7b643c1ea5dfc72ee240fa098cd5bf5aea320986616ff7bfc78efdf43091610fb457447058958e68a13e49ba02033016a2ef0512c926211fbec6f9b7398c58ae10116901d086905979649d968a07cf13191df973971dd4592a95c33cc3c248a4e919b8866c7c8ecff44a6e453a2a017010b7ff49cf72fbc13136f189457a2bc09e8c400ca9cb7997ff75bf34637ec80", + ////"0xf90211a06c86eef3a8754d952a265132ed86282d34ec24f9f5aa2f085629155798975c2ba02cef7b79076bddfe220fd88ca1a18414683b2f37e8b94cf8c6bdf743cff9d18ca00fe6e40f33bc1b76e34a6c92cdfce9b08f808bca7c6fe85348a7b7e838632a20a0f2f4d7b6fb649794a45288bfbc32274f3031ebc8bbf808146a3104ea27d72e7aa0cf2a353bb32b3b9c004c7586d6723ea5e2ccb99972c84b4b2c90166ca0c3c65ba0753fee595b7a0b80d3574db4e4615a599da7bedfa71bcb9ba214192c6dfb8a4fa0a7e45064974417eeef5556e4fa3533c819cc04825a33f0e244440d4d6a42828ca06cb2eaf789a62824a4a2b730bc4b8ee70e3648fbdf6cd61ea86d234dda67ccc3a0e42e79aeed163de73664c3b4f9451208b22b4874eae6c007b5f0405a64a55050a072f87da9fbd3c727080eef39891888876cdfbc54b732cf4f08ee19d067117d5ba0ce88e695612d636a6f73c2fcc0086e486249a0284cd6b88bf1cc3a7bef84ce9ba0d408599a558fc0ad84aba0bea36e00c1e99ddebf7b74e2f68912563bd5a62522a0154aef7de9275d13e860b11f138a811e6ed97fbf8985c524ccd5c231dbc62180a0282022799ec74b1dc2df4edad584b0b974113a06a857ff72ed17471908d28404a00c4fc7a3f7ded56f4da7093a875bd7c3a6cb35cc6bdf505cb79aee64708640aca0339829e86f4b7a2d68fe0b4707e32016534cdf8a48070a3921a48bcc0fd4b11c80", + ////"0xf90211a0b70bd38b197882c9e04dbdbc463bd74887af466e509bb9f61283796000649611a05b7c13208dc2fbd708005d510297a825e6ee16a541a1f7860b5226f39e7d31ada09fea01b1db9afdd63932fc4a335ca3af6824afcba793fab68a6536e4f302fc19a0d73148886a70f18c6a41f65a9d012b8a5198deacf33b0839b494a1338b23cf7ba0e6923675583267b502e2a6ac685651f4bfb54b6cf00cffdf7e603927107eca70a01e2fcb240f26a1d85e908bc77a75628b144f31b0f6d56ddc139cb5a56002420ca021420c0d6ab2d50504f7cad0e4d76109c2c93f2c49c9677863f290c8dc4e3c14a0379fe1bd0b044c94f67f844bd2c7f7abf83ac8049ab234ce301326d79fb7ae0ba0890ea9c4679262b86e65f2895ff9e6e97a0ed06d7beb9b4d3d8c8d3246a06715a04569c16e7ba91a901aec96cd999b89201e192670edaff70d2991edf779de5082a0adaed327e31819a530e941a4eebf0ed8e245b9272947199566e89e4496a6c05ba0c39e54b4ce440fbc36c43baafc9809a80e8cc5649b2f8753b5edbac3b7c043e3a0812541bed009f7cae97ba783dde88aced6e5f21fc80b41e91b9479d35184e45ea0708adda1f1ad89034e35a2fd063420d885713fb7f176960e47eebc0df8e8afc0a0a305e0528d3f6122a460eeffc54ce1c4c67f71f376e8d84a4ed23dfdc2cc4effa02928ce112a3e076214a2b1e25fbfc463411c378c12702506c1479f97ba5bffce80", + ////"0xf90211a09881800af81281b599a2ae599789501c2561294aeb892905e5f454bdcf79a187a047e8fd36ef03ad0479902154249d5e43071efbf78b4974f2cda490fa42d17ba6a063b7d25d086bdfa18c5beb67e4339f683de15346ef948b01139d8c0e83a96387a0b8662c97a229aa73ab190ab402a7fa2acad9cf66251ad0a44c9070732e298b11a0fac6584c1fa6aa7db2e615e10ba63a9b353f3febbe93d3ae2276891668aa7fc1a0777e0f4f2695a65939c8a191cc32e0bc74c557d057a46b5135f36e3c232b7fb8a0d78ec39c3544f76d77c0aa20b4cdb58c8ac29745713b240b7fdd3836497fdb04a04729097a95f6c001478b2ed057c71a9de89b0e4ee18cbf6c04d578bac2c7ed28a09f6f37e1ffed0554f4133b5743e4e7e807054ce7f9615df1fdcceb0264e31829a0cddde78e2ed07fd82b486cca326ec315f3d1df4a635f1898811b1d4d45d7361ca018f06484f8a256a810fdf6800d30c40094de7561a12b3cc9a9e90979e0ce4a10a08c01dbe66e1e60f5edea0e1bf2990aed610dadb9783f2070b206c88ffc05e7ffa09763ea84f4ae07ffec150a3d59674a49c944dc94409881b0380a3ed2e4ab6b70a0d0e86e1c6f991a9afb97bf1648487fcc90f61d4a6f7d8fa419cb829e11c5b764a042d00184633bd8df55db881b1b46457f1d0cff162c8843f9aea18509271f9407a081aad7099cafddcb44141737b986eec45e93aa16774c7d0480d395fca582cf8d80", + ////"0xf9019180a08eeac7bec8ceaaf659b328d4a04e418159914c7681f83470cd313d6e984a0754a06dac15ddea9fe0ba3097f94727c1f61cda2219e639c443e52b231ceb71b9c86fa0a6545f791304f8f4dfa8bae7699aff682733b246c6df78b03a955b87e974a330a0f75c6a10796830946f04645d5d11e5b0bcbc40f0cb83cd716440e6ef1d8b3d048080a0646d1cbea060b31a3ddfcb8e802620232a2164a64abc04dba88654667668261ba0c61e9aff13ad27a1d50510f88ff4879db3fd7cef1782982d3cf8702742b2941da0b2a8722ff78f03327b585da064ed79ea1818cff6cc41fe3249368cc04493ca41a001719ecf3a6e924abbdbb6f3de50794df3d9f9f503bfb54bab753e2d9b5c7230a08073f6a9ebd0c84a9dd5b6176d52bc80596eb74c192a7a551b3ae80620facaf7a0290f29ddbba5a789b068535aeda99809053bea7642e0bd7a604a15112b5818cb80a00bf0a7e8a9b0797f99bccaa5655462c00172c769c5bed589c149df04c9d748bfa02c83d167a8b35607906f4639c2442bacde5c67dab80bb2fc74564a32d3a3194380", + ////"0xf8679e20eb3d3905f6526cb7fbb8ca13df8d9c63efade348e70065aa05f578f315b846f8440180a06968aa4d96a817eb4d24aa4d096d0d841f9c52ed7bbc4ca7d7951bba6fc65571a089b1de4a1e012d6a62b18b6ad184985d57545bf1da0ae1c218f4cea34daf099b" + + let proof = array![ + array![ + 0x5c0b6aa0808051f8, + 0x9720a2845ec292e7, + 0xdde909402854fc40, + 0x8b0ff9103babb578, + 0x808080c17ea1cc35, + 0xf86ba08080808080, + 0x652dee110f0a9fd1, + 0x98195bc1e2e398a8, + 0xcb959918a8d08b95, + 0x8080d2e4ec07dcfc, + 0x808080 + ].span(), + array![ + 0x76522d0e31a043f8, + 0x71fdcdee263b0712, + 0xfa4a4bf40c326a7e, + 0xb7e2cb9f2d73b0c2, + 0x636f4da0a1f60cfa, + 0x000000000000006b, + 0x0000000000000000, + 0x0000000000000000, + 0x0800000000 + ].span() + ]; + + let expected_res = array![ + 0x6b636f4da0, + 0x0, + 0x0, + 0x0, + 0x8 + ]; + + let key = 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6; + + let mpt = MPTTrait::new(0x8051DC27EC55E8B88C99B97B08DFF3E99B5106E4BED472E53B9B7D53C0129A6E); + let res = mpt.verify(key, 64, proof.span()).unwrap(); + assert(res == expected_res.span(), 'Result not matching'); +} From 2fd87a5c779c06ae200ff058728b814850c724aa Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Thu, 2 Nov 2023 11:32:59 +0600 Subject: [PATCH 24/28] fmt --- src/data_structures/tests/test_eth_mpt.cairo | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/data_structures/tests/test_eth_mpt.cairo b/src/data_structures/tests/test_eth_mpt.cairo index 0d81dfe..794e54f 100644 --- a/src/data_structures/tests/test_eth_mpt.cairo +++ b/src/data_structures/tests/test_eth_mpt.cairo @@ -795,7 +795,8 @@ fn test_full_verify_2_nodes() { 0xcb959918a8d08b95, 0x8080d2e4ec07dcfc, 0x808080 - ].span(), + ] + .span(), array![ 0x76522d0e31a043f8, 0x71fdcdee263b0712, @@ -806,16 +807,11 @@ fn test_full_verify_2_nodes() { 0x0000000000000000, 0x0000000000000000, 0x0800000000 - ].span() + ] + .span() ]; - let expected_res = array![ - 0x6b636f4da0, - 0x0, - 0x0, - 0x0, - 0x8 - ]; + let expected_res = array![0x6b636f4da0, 0x0, 0x0, 0x0, 0x8]; let key = 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6; From 40e6841295a0b3467977edd6663a92a2ce9c7adf Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Thu, 2 Nov 2023 17:31:51 +0600 Subject: [PATCH 25/28] fix: mpt verification returning empty words on non inclusion --- src/data_structures/eth_mpt.cairo | 12 +- src/data_structures/tests/test_eth_mpt.cairo | 438 ++++++++++++++++++- 2 files changed, 438 insertions(+), 12 deletions(-) diff --git a/src/data_structures/eth_mpt.cairo b/src/data_structures/eth_mpt.cairo index e56c9ec..e8c2b17 100644 --- a/src/data_structures/eth_mpt.cairo +++ b/src/data_structures/eth_mpt.cairo @@ -115,7 +115,7 @@ impl MPTImpl of MPTTrait { let current_hash_words = *nibbles.at(current_nibble.try_into().unwrap()); current_hash = if current_hash_words.len() == 0 { - 0 + break Result::Ok(array![].span()); } else { match current_hash_words.as_u256_le(32) { Result::Ok(h) => h, @@ -127,6 +127,9 @@ impl MPTImpl of MPTTrait { key_pow2 = key_pow2 / 16; }, MPTNode::LazyBranch(next_node) => { + if next_node == 0 { + break Result::Ok(array![].span()); + } current_hash = next_node; key_pow2 = key_pow2 / 16; }, @@ -157,7 +160,7 @@ impl MPTImpl of MPTTrait { & 0xf; let current_nibble_key = (key / key_pow2) & 0xf; if current_nibble_shared_nibbles.into() != current_nibble_key { - break Result::Err('Extension nibbles not matching'); + break Result::Ok(0); } key_pow2 = key_pow2 / 16; @@ -188,6 +191,9 @@ impl MPTImpl of MPTTrait { match next_hash { Result::Ok(next_hash) => { + if next_hash == 0 { + break Result::Ok(array![].span()); + } current_hash = next_hash; }, Result::Err(e) => { @@ -221,7 +227,7 @@ impl MPTImpl of MPTTrait { let current_nibble_key_end = (key_end_word / key_end_pow2) & 0xf; let current_nibble_key = (key / key_pow2) & 0xf; if current_nibble_key_end.into() != current_nibble_key { - break Result::Err('Key not matching'); + break Result::Ok(array![].span()); } key_pow2 = key_pow2 / 16; diff --git a/src/data_structures/tests/test_eth_mpt.cairo b/src/data_structures/tests/test_eth_mpt.cairo index 794e54f..97e57bd 100644 --- a/src/data_structures/tests/test_eth_mpt.cairo +++ b/src/data_structures/tests/test_eth_mpt.cairo @@ -773,15 +773,6 @@ fn test_full_verify() { #[test] #[available_gas(9999999999999)] fn test_full_verify_2_nodes() { - //// Account : 0x7b5C526B7F8dfdff278b4a3e045083FBA4028790 | Goerli | Block 9000000 - ////"0xf90211a0f09eae4e1e51fdde02a2884e285b8a8a9c72cc7e7cdaeef013714e3499bcd475a0ce33fd7097055e50d64c42759027e41ffb22d5b2a03ee67207dc94b547e40956a04817bf75497b71a78957ff89d05107cbf16ead02f7e68f13cead9e7d24dfcda5a00751841dcd0e21ff273930aa4722cabae7ea4e09d0f4e9f667b57ab68a41652ea047008ee2caeec1839c016d0a8efd2e901091bfae5388fc064db9f14f4bda362da0f952be9637ec6790bcdcf9ae3d4bca607259f26c0731e3cbd2882924c9db5653a061a8882bde126643739fe5f0acc5d234467718c27217f56513fd222009802336a061dbaa68a4290e8cce57166ffc6fd22d081c5893a081082b23668ef6c7d65c81a0ef2e0aea160700e14be7285c8b83535f4d104a74ac8db6c188d84ee48a8a647ca0c00853c7500db3c616d5d7dcd7503c02307045e7670a0749ffdebadc732a9ab4a068050da8f891b57fbeacffe4ba3e41f11c5d6b0ec8553fbb796f46951ecd1445a0762e36c38c548c5ae61da51205ef1dc66390702397becef53c50d969cae7a2ada0abff9de80f8e14979ebbe80ae3e702e61b31b91ea481c0e63a7bde12e866eeb5a017220448de88495fdf81446233768ef9441058e4602ecafc1da85a7cbbf1c16da084351381e6cad5052c82f731e8d19d86193794eccdf274529bed7e67309cca78a0784e83133c0ba8ff0262d0c96dc93f936d97eac46327d32f3c1baceb63934d9d80", - ////"0xf90211a0679ec41f2230e1f57eededf17732966880d9835d744ad769a1b246829341a588a09c2941acea1f1461a7d0af3bb9917c16e3a3339556623a6cad6d1f40ee8fc8a8a0211b79624826f8cd651f7a0123356cfef56854adf7285163988ba4eee0e8f964a062a3e341692078b717029cd105b462749386aecc1cb647721cc382872eac4a51a01a9fc7658bcc2948e1123273e83fb66894e64c2e19aa8f3ac546c45ef4b22290a08c5cdf2e341821e9c3163ec52847e33884f4797669607a60a8adafd00edead0ea08b07046b12762a58e03a482d597cca869aaffd85214bbd08c4624325a7cf80c9a0602d16a56550182218f642f56e66b1cf72555c38dac0fd061b8ef113d4653f4aa038fa2d962cfe43eb49f5a7d1787a436e8e3c6858665b1b0703c4e42ad43f962ea03a706c9b0e0757079f96d9df003eae31aafcf7525d6114033ee929c78adc580aa0f8a66bdc97088d4c73429b9ee00d7bcd0589be3462a53e9f5b9876d4b5231e40a04bbcbff81f2c0b65f29724ef71f6d439b6f857ad5fa7b643c1ea5dfc72ee240fa098cd5bf5aea320986616ff7bfc78efdf43091610fb457447058958e68a13e49ba02033016a2ef0512c926211fbec6f9b7398c58ae10116901d086905979649d968a07cf13191df973971dd4592a95c33cc3c248a4e919b8866c7c8ecff44a6e453a2a017010b7ff49cf72fbc13136f189457a2bc09e8c400ca9cb7997ff75bf34637ec80", - ////"0xf90211a06c86eef3a8754d952a265132ed86282d34ec24f9f5aa2f085629155798975c2ba02cef7b79076bddfe220fd88ca1a18414683b2f37e8b94cf8c6bdf743cff9d18ca00fe6e40f33bc1b76e34a6c92cdfce9b08f808bca7c6fe85348a7b7e838632a20a0f2f4d7b6fb649794a45288bfbc32274f3031ebc8bbf808146a3104ea27d72e7aa0cf2a353bb32b3b9c004c7586d6723ea5e2ccb99972c84b4b2c90166ca0c3c65ba0753fee595b7a0b80d3574db4e4615a599da7bedfa71bcb9ba214192c6dfb8a4fa0a7e45064974417eeef5556e4fa3533c819cc04825a33f0e244440d4d6a42828ca06cb2eaf789a62824a4a2b730bc4b8ee70e3648fbdf6cd61ea86d234dda67ccc3a0e42e79aeed163de73664c3b4f9451208b22b4874eae6c007b5f0405a64a55050a072f87da9fbd3c727080eef39891888876cdfbc54b732cf4f08ee19d067117d5ba0ce88e695612d636a6f73c2fcc0086e486249a0284cd6b88bf1cc3a7bef84ce9ba0d408599a558fc0ad84aba0bea36e00c1e99ddebf7b74e2f68912563bd5a62522a0154aef7de9275d13e860b11f138a811e6ed97fbf8985c524ccd5c231dbc62180a0282022799ec74b1dc2df4edad584b0b974113a06a857ff72ed17471908d28404a00c4fc7a3f7ded56f4da7093a875bd7c3a6cb35cc6bdf505cb79aee64708640aca0339829e86f4b7a2d68fe0b4707e32016534cdf8a48070a3921a48bcc0fd4b11c80", - ////"0xf90211a0b70bd38b197882c9e04dbdbc463bd74887af466e509bb9f61283796000649611a05b7c13208dc2fbd708005d510297a825e6ee16a541a1f7860b5226f39e7d31ada09fea01b1db9afdd63932fc4a335ca3af6824afcba793fab68a6536e4f302fc19a0d73148886a70f18c6a41f65a9d012b8a5198deacf33b0839b494a1338b23cf7ba0e6923675583267b502e2a6ac685651f4bfb54b6cf00cffdf7e603927107eca70a01e2fcb240f26a1d85e908bc77a75628b144f31b0f6d56ddc139cb5a56002420ca021420c0d6ab2d50504f7cad0e4d76109c2c93f2c49c9677863f290c8dc4e3c14a0379fe1bd0b044c94f67f844bd2c7f7abf83ac8049ab234ce301326d79fb7ae0ba0890ea9c4679262b86e65f2895ff9e6e97a0ed06d7beb9b4d3d8c8d3246a06715a04569c16e7ba91a901aec96cd999b89201e192670edaff70d2991edf779de5082a0adaed327e31819a530e941a4eebf0ed8e245b9272947199566e89e4496a6c05ba0c39e54b4ce440fbc36c43baafc9809a80e8cc5649b2f8753b5edbac3b7c043e3a0812541bed009f7cae97ba783dde88aced6e5f21fc80b41e91b9479d35184e45ea0708adda1f1ad89034e35a2fd063420d885713fb7f176960e47eebc0df8e8afc0a0a305e0528d3f6122a460eeffc54ce1c4c67f71f376e8d84a4ed23dfdc2cc4effa02928ce112a3e076214a2b1e25fbfc463411c378c12702506c1479f97ba5bffce80", - ////"0xf90211a09881800af81281b599a2ae599789501c2561294aeb892905e5f454bdcf79a187a047e8fd36ef03ad0479902154249d5e43071efbf78b4974f2cda490fa42d17ba6a063b7d25d086bdfa18c5beb67e4339f683de15346ef948b01139d8c0e83a96387a0b8662c97a229aa73ab190ab402a7fa2acad9cf66251ad0a44c9070732e298b11a0fac6584c1fa6aa7db2e615e10ba63a9b353f3febbe93d3ae2276891668aa7fc1a0777e0f4f2695a65939c8a191cc32e0bc74c557d057a46b5135f36e3c232b7fb8a0d78ec39c3544f76d77c0aa20b4cdb58c8ac29745713b240b7fdd3836497fdb04a04729097a95f6c001478b2ed057c71a9de89b0e4ee18cbf6c04d578bac2c7ed28a09f6f37e1ffed0554f4133b5743e4e7e807054ce7f9615df1fdcceb0264e31829a0cddde78e2ed07fd82b486cca326ec315f3d1df4a635f1898811b1d4d45d7361ca018f06484f8a256a810fdf6800d30c40094de7561a12b3cc9a9e90979e0ce4a10a08c01dbe66e1e60f5edea0e1bf2990aed610dadb9783f2070b206c88ffc05e7ffa09763ea84f4ae07ffec150a3d59674a49c944dc94409881b0380a3ed2e4ab6b70a0d0e86e1c6f991a9afb97bf1648487fcc90f61d4a6f7d8fa419cb829e11c5b764a042d00184633bd8df55db881b1b46457f1d0cff162c8843f9aea18509271f9407a081aad7099cafddcb44141737b986eec45e93aa16774c7d0480d395fca582cf8d80", - ////"0xf9019180a08eeac7bec8ceaaf659b328d4a04e418159914c7681f83470cd313d6e984a0754a06dac15ddea9fe0ba3097f94727c1f61cda2219e639c443e52b231ceb71b9c86fa0a6545f791304f8f4dfa8bae7699aff682733b246c6df78b03a955b87e974a330a0f75c6a10796830946f04645d5d11e5b0bcbc40f0cb83cd716440e6ef1d8b3d048080a0646d1cbea060b31a3ddfcb8e802620232a2164a64abc04dba88654667668261ba0c61e9aff13ad27a1d50510f88ff4879db3fd7cef1782982d3cf8702742b2941da0b2a8722ff78f03327b585da064ed79ea1818cff6cc41fe3249368cc04493ca41a001719ecf3a6e924abbdbb6f3de50794df3d9f9f503bfb54bab753e2d9b5c7230a08073f6a9ebd0c84a9dd5b6176d52bc80596eb74c192a7a551b3ae80620facaf7a0290f29ddbba5a789b068535aeda99809053bea7642e0bd7a604a15112b5818cb80a00bf0a7e8a9b0797f99bccaa5655462c00172c769c5bed589c149df04c9d748bfa02c83d167a8b35607906f4639c2442bacde5c67dab80bb2fc74564a32d3a3194380", - ////"0xf8679e20eb3d3905f6526cb7fbb8ca13df8d9c63efade348e70065aa05f578f315b846f8440180a06968aa4d96a817eb4d24aa4d096d0d841f9c52ed7bbc4ca7d7951bba6fc65571a089b1de4a1e012d6a62b18b6ad184985d57545bf1da0ae1c218f4cea34daf099b" - let proof = array![ array![ 0x5c0b6aa0808051f8, @@ -819,3 +810,432 @@ fn test_full_verify_2_nodes() { let res = mpt.verify(key, 64, proof.span()).unwrap(); assert(res == expected_res.span(), 'Result not matching'); } + +#[test] +#[available_gas(9999999999999)] +fn test_verify_non_inclusion() { + let proof = array![ + array![ + 0xec98f822a01102f9, + 0xa6dd8f1214e1f11f, + 0xff4ad6f843ecf036, + 0xf6a7e88f76b1c824, + 0x5e47eba0f38aad9d, + 0x7ab225aee4e37bc0, + 0x504eb61cf06f663d, + 0xbb3b7ede40eadac7, + 0x9824a09076b7c780, + 0x919f6fa982b02b36, + 0x58d20214605f9948, + 0x56b041c561468afe, + 0x97a03430bd08cf66, + 0x62bbd866a3c8fb66, + 0x54d883fae6fe8129, + 0xa028191e12dfd561, + 0xa0670d84e1aa857b, + 0x55ccec057a3dd6cd, + 0x362b3118d757701e, + 0x146b17eeaf0ad6fd, + 0x1319c45144710cf9, + 0x46f757a79362bba0, + 0xa6ef7d98da737a41, + 0x02195eb29cd3e26d, + 0x7dfd57449c4c589a, + 0x38440d73892ca0e3, + 0xb4489211b479dbbd, + 0x0d4de704e5849607, + 0xb25d3b17d9f4d02a, + 0x7364a11c42a06df8, + 0x6d86c52845ef2567, + 0x724871838ca2dcdd, + 0xc9d2cecc83e6e9a0, + 0x02300c35a0620e9c, + 0x6b8dc74c237580ab, + 0xdbce8d27070a6c4f, + 0xb8cc16227e6f82a5, + 0x36d813a037bd63f4, + 0x44ec4fe517ce274f, + 0x64e998642529587c, + 0x7a4288f48bba7ea4, + 0x278ea0fdba64dc87, + 0x8da7ef8f781077aa, + 0x1ab52ff8420c677b, + 0x071a58e621bd3a13, + 0x9ea0ed688e392ff2, + 0x1c64a2e0cf0280e6, + 0x56e6d81a8d9c782a, + 0x59a9c64f4f680642, + 0xa056099859cfc1fd, + 0x8609c42dd7f52a4b, + 0x39e7c1999c1e73d4, + 0x9e8282fb3f570af7, + 0x11333251697454e3, + 0x1a397b07c8246aa0, + 0xed8e88c7a947b661, + 0x229c111bf67c959e, + 0xec403782f21939ba, + 0x9fa9d5f8fc07a085, + 0xa3f59303f4f8fd89, + 0xb00533f3b4daba74, + 0x382c892f74849089, + 0x454f994c81a09b53, + 0x07e3f1381c062a08, + 0xec28c36be4a9f162, + 0x51ace64e0718855f, + 0x804af09c, + ] + .span(), + array![ + 0xa18b1734a01102f9, + 0x624c4dd4e96fec64, + 0x8923e53f4e4a5c08, + 0xc1cca86fa2faaf4b, + 0xd8ba0ba0fc96285c, + 0xcddc7ebea7be7113, + 0x46ecd259ea06047d, + 0xa421c443d6f9a71a, + 0x8102a03f4d1d0680, + 0xaec223e9d168da7b, + 0x5cd2cdcc97098098, + 0x09d04cafc495736d, + 0xdea02f35e2bc92b2, + 0x436b95f3e26fb0df, + 0xc1bbe2d660b38aca, + 0xc0a370f7287dea21, + 0xa01b3d90b2772163, + 0x1fe1a4120535ee81, + 0x1961fea5b81063da, + 0xd4a5dd507ff58dcf, + 0x9332183e378bdfc9, + 0x7d9a1054ac0aa3a0, + 0x9bd80710831fefeb, + 0x83c7f00efd8667f3, + 0x5cb588453ef8ae96, + 0x1064588e1e69a0a3, + 0x6641154313a7cc3f, + 0xef0f04edc1fea6af, + 0x73277c83d5f6512a, + 0xe0e9c4b3b9a0ff7a, + 0x2a6357e218c4abc4, + 0x0ba0ce6fb2540a92, + 0xaecc5377ccd2b0ef, + 0xc11ad0f2a011c168, + 0x1c5a4c32d0bfe92a, + 0xed1f322b61d1d604, + 0x90af5d7174e78a83, + 0x506c29a0e666815f, + 0x5053d0102ff893d2, + 0x54f731b4085ac946, + 0x733749cfe21355ab, + 0x32f7a063d4ad43f8, + 0xe5343e587b5e5d1e, + 0xc0e6fe7ad162469b, + 0xed48d6a34a49a7f9, + 0x98a06d024b68cd97, + 0xab0a55475693e194, + 0xd5e032a70fd0ffa7, + 0x0b050793e64ae739, + 0xa09902e1816e7d29, + 0x2640b912a74d28d2, + 0x64da28ce2641f943, + 0xe82330c8f0c0be7c, + 0x9027ed1180802ac6, + 0x28e739f42d1482a0, + 0x5b9e95496cd50bc8, + 0xd29954dacf762a6f, + 0x5e59017da19f29ce, + 0xd3ad89fb4e26a058, + 0x36c079fad94cff14, + 0xcf1d594ee1bef76f, + 0x9108169a3df266c3, + 0xa281f807b6a03eb1, + 0x873adec4d65bd57e, + 0x1badb7afbbbc11e4, + 0xf31d82660adfe876, + 0x80bd5648, + ] + .span(), + array![ + 0xc4a440b1a01102f9, + 0x3969b0c09139282d, + 0x9cbfda9fe532254a, + 0x7ed5d8e8a424c1bf, + 0xffdabaa0da52e97b, + 0x52d7d98b6d93947a, + 0x2a393a8bd8bd2fbd, + 0xa1bdf99909348d33, + 0x1100a0467678105d, + 0xe369147638f178b5, + 0x71a9fb0f909f78bf, + 0x583d7a8a7649d3e8, + 0x17a0dd0aad1c5936, + 0x4d61315b3884d1fa, + 0x83be5a97959bbabd, + 0x8301009624ac80cc, + 0xa0ef97f2327d184d, + 0x149fccea7de6888a, + 0x9cef77c2f7fe18c2, + 0x65f45bf48b551057, + 0x595009e3e81946a2, + 0xc10af680de3f0ca0, + 0x20ba6ae86874c488, + 0x76dd0a762ec2f509, + 0x29b484e8f2a5008c, + 0x903ee2650407a0ea, + 0x6ef3e3b755f98730, + 0xb5dae74e7d54c91c, + 0x7b98cbc8a4ecb196, + 0x7a01843172a073fc, + 0xb442de1416bb6aa2, + 0xe1a4e0e43d2a6e88, + 0x4c7ae56697867af4, + 0xd1641177a0a4808d, + 0x83a2f11192df3d5d, + 0xa41bcc4ce0112d5c, + 0x46f9f649e94d4c70, + 0x7e8f38a0e7276078, + 0x6910d91ea9bb8194, + 0xbc1895b7a1e97e37, + 0x3f67491a0e148cf4, + 0x661aa0c79d08dbb0, + 0xbae7f8980692546d, + 0x948a9a4e0ea5465e, + 0xa87af3b0cef159de, + 0x17a0cc2f218b8da1, + 0x78a422b82e26a345, + 0xf02820f191d68219, + 0xf5b64741487e9990, + 0xa0f7320918aee5fb, + 0x76b832fd41ad2238, + 0x562698f5166c6659, + 0x8cc54eff977ee295, + 0x6788e130e1661af3, + 0x9d3847995a05f5a0, + 0x83f82b3df6042972, + 0x526a3f19c6aeaa5d, + 0x0c3fed0fef0e76c7, + 0xd57edf5f200ea08d, + 0x577455eb7dfb4feb, + 0x8515cfa916e3fb43, + 0x33b9d4cadf37b171, + 0x63a487f889a0ed11, + 0x93f7ac9e98299d0c, + 0x61f15f15dbf1d83b, + 0xcec095010ed87f1b, + 0x80a68778, + ] + .span(), + array![ + 0x9d99ee5ca01102f9, + 0x718cfd1f8d72c2de, + 0x2d2191cfecc39b56, + 0x1cc59360702b6245, + 0xbe3960a0c261e2f4, + 0xf41244c2977cb023, + 0x3ce5aab9c48812a0, + 0x3ddc8577ae4bb1df, + 0xa02da0e93fbb3f10, + 0xdf7a547c6557993f, + 0xffa317d9c6089ded, + 0xf203bccd7ddfa938, + 0x70a04826c6e60022, + 0x9619712d712cba4f, + 0x4c4dd96d022bd305, + 0x83af07d2a9934e29, + 0xa083be1dd409218f, + 0x9c3ab0c6f93b3d96, + 0xb593f5ea39517fd7, + 0x0c8b34c46e00a0b9, + 0x2145f5c0f0300f6c, + 0x75cb5fa55e3071a0, + 0x73e564b622b7d445, + 0x022ba70dc9155ec3, + 0x72eae3ac17cb92e7, + 0xad863d7e82f2a002, + 0x92766445ba5dd234, + 0xf150548095c1e96d, + 0x1526413a359e5ea2, + 0xfc8af56c91a0d9be, + 0xe414e346e31e5916, + 0xfb5eba4e2e7d7819, + 0x5872d97dab134060, + 0x615e0ce1a0bbd9ba, + 0x258b96575c22fd58, + 0xef7998285751eaae, + 0xbe6697b6c5af2f88, + 0x972be7a02e6d7b4a, + 0x124def6cc25837c7, + 0xc81e604f8e72e2e1, + 0xdaff2c0729010557, + 0x316fa0a1fa7cba5e, + 0x6a09a3a2831319fa, + 0xa04192f3fa0e26a0, + 0xb5032ea58874f3e7, + 0xf2a00340e23b6c08, + 0x7ef5f056401f3bb6, + 0x8136d6f15419c073, + 0x51e40d6613e4b0e4, + 0xa031928c18dc5e19, + 0x1010127ad8c00db9, + 0x8b0f20cab38db09c, + 0xf03736d6873bb3f9, + 0x0e34f847ce7ed8ee, + 0xc70b5d3c432046a0, + 0x2ab0bdf3cedfc54f, + 0x6120574252c3bc11, + 0x24c517bb7ee7eeb6, + 0xf877bad85ed3a0f1, + 0x153858932ff1f622, + 0x06b53db20c624055, + 0xf58414076214041f, + 0xf5b9dac3dda07386, + 0xe5a5b676d8b2710b, + 0x85b80c919b65ac5d, + 0xf561815eb587be0b, + 0x80efc780, + ] + .span(), + array![ + 0x68bdb97ea01102f9, + 0x8b1064346a7e703d, + 0x231d3944a343edfd, + 0xe5b1b3c993a4348e, + 0x759426a0493316a9, + 0xead237cc12b7c961, + 0xa8d030b48aaf334b, + 0x813079136c313ec8, + 0x7885a0e856317a41, + 0xdbdd4d9c1a897697, + 0x84d0f4f124c97010, + 0x8f5407402a859357, + 0x13a0628a0acf17a9, + 0x1a337078335bf480, + 0x3d3a0e71904efc66, + 0xa00c45e239c314a7, + 0xa012ccc781e8a1c3, + 0x472c2cf37cd19e25, + 0x89f736e6b2e6d1b0, + 0xbe5228aac7df21a0, + 0x7e77dd520f0563c7, + 0x34a6bdf7a46956a0, + 0x605ea0a95c8331ee, + 0x153164427ea10319, + 0x754b92c9873e571a, + 0x8bb30b7d9897a079, + 0x73399da4f82e29cc, + 0xd8abc14f1cf26e27, + 0xc09ed92452e02856, + 0x5022c5a784a0c4de, + 0x269fa9269a00e08a, + 0x09d747926f9640b5, + 0xa208597b2710f6c5, + 0x46a626c9a01d431c, + 0xbcb4c775d7165c7b, + 0x7937485ef08a5216, + 0x0fb70b713997e0a8, + 0x44b3c8a0a1ce0bc7, + 0x9875ec1bb38e4ff7, + 0x1db1dfe1b2f2d10f, + 0x3ffe5db701cd8d8d, + 0x5481a0f967e57604, + 0xcda26de3d706bc7b, + 0x8d7f78dcf4800483, + 0xbc7c842dbf9f085f, + 0x1aa06261fcc5fc09, + 0xc55c43be0d37637f, + 0xde3211f4ceec8b75, + 0x9a1e06de138f053b, + 0xa0252ee10ef1f096, + 0xab5c2c608bf54cc9, + 0xfdbac1b241ce0abb, + 0x2a90d9204f8c4627, + 0x46dc5020a562297f, + 0x16efe04ee931afa0, + 0x1072a0182c3dd6b0, + 0xd8e538aec461e353, + 0x38bd65aae5c1f928, + 0x4288940395fea00b, + 0x09a893417b081529, + 0x4d0a83649fd69ee0, + 0x795df895b0e225d7, + 0xc2178b7ea8a05160, + 0xd00b899083a8d9fc, + 0x7b419dfdda0aa3eb, + 0x7f5bbb883e1e7ee3, + 0x8027cda8, + ] + .span(), + array![ + 0x5581eda4a07101f9, + 0xd5111547ea5c54a2, + 0x4b1fec18db84d370, + 0x8537c1d7ae6f56fb, + 0x06a08080cbc9df22, + 0x10c6cd2c69ac990c, + 0x93dee1c6766af5b8, + 0xb348396b55e98bcd, + 0x80129b99bbe80276, + 0x0562ec2e3cc8bfa0, + 0x8b076699a733c392, + 0x78b5b82117ec50a0, + 0xa613011249cb30a0, + 0x7068b3979ff8a0c6, + 0xd8e2eef6399bf1b4, + 0x47d7f177febcc8cf, + 0x7ae242f5faedcc66, + 0x865ea57e93a0e94d, + 0xa0afbe49d5c9635f, + 0xff909d74f211bda8, + 0xed96bfe6e238286a, + 0x5ddba535a02caf0d, + 0xfd97460f36d94ffb, + 0x11ece487b149932a, + 0x86c9ded3a5fde827, + 0x18955da027329f87, + 0xfef67004b8a00b2d, + 0xc63b240909fda606, + 0x23899e7ff818e02d, + 0x10b9a0853c868a88, + 0xafc929a021b8c4c9, + 0x12639f697f9bb2d8, + 0x35accd64e12da7ab, + 0xa080e800b9941511, + 0x1021d1bfe372ee71, + 0x2fc2c9d8cebfca23, + 0xe8e313c3613bf42a, + 0x2d23c417040536e5, + 0xe7c108928484a080, + 0x61e9867c309be2c3, + 0x81b805d7196e1d2d, + 0xf5fe944b8f54cf8d, + 0x03d653f4afa0d485, + 0x516ca97707f93858, + 0x7b972d02f9acf612, + 0xa2ce82f3b1bba0a8, + 0x80d62f2a, + ] + .span(), + array![ + 0x598c57a0808051f8, + 0x400bb190e7c2eda0, + 0x616be10e250f50cb, + 0xf3501527389e5cea, + 0xbea0801ee74dd943, + 0xf62e5e6b8b43bcbd, + 0x2df96e397dc1b4dd, + 0x4506143bf6cf6ffc, + 0x80244912bcd14b94, + 0x8080808080808080, + 0x808080, + ] + .span(), + ]; + + let expected_res = array![]; + + let key = 0xa0772cc0ab5df2ddbf685663d9f2235f2022a1d9cd4594642d0db66171285358; + + let mpt = MPTTrait::new(0xB28377C58119C5D785423161829FC87C613F5CC284C14A130D9CCFE5F4A0E196); + let res = mpt.verify(key, 64, proof.span()).unwrap(); + assert(res == expected_res.span(), 'Result not matching'); +} From eec74dc79c62f49af50675ad8bce259b05f987c4 Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Thu, 2 Nov 2023 19:24:33 +0600 Subject: [PATCH 26/28] docs --- src/data_structures/eth_mpt.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data_structures/eth_mpt.cairo b/src/data_structures/eth_mpt.cairo index e8c2b17..fcadb72 100644 --- a/src/data_structures/eth_mpt.cairo +++ b/src/data_structures/eth_mpt.cairo @@ -51,7 +51,7 @@ impl MPTImpl of MPTTrait { // @param key Key to verify // @param key_len Length of they key in nibbles // @param proof Merkle proof, collection of rlp encoded nodes - // @return Result with the value associated with the key if it exists + // @return Result with the value associated with the key, empty in case of non inclusion fn verify( self: @MPT, key: u256, key_len: usize, proof: Span ) -> Result { From 93ebbac8997452255c9b12a13ad75dece577850f Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Mon, 6 Nov 2023 09:31:18 +0300 Subject: [PATCH 27/28] fix: mpt verification remove empty leaf and extension node check --- src/data_structures/eth_mpt.cairo | 62 ++++++++++++++----------------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/src/data_structures/eth_mpt.cairo b/src/data_structures/eth_mpt.cairo index fcadb72..ebda892 100644 --- a/src/data_structures/eth_mpt.cairo +++ b/src/data_structures/eth_mpt.cairo @@ -151,42 +151,38 @@ impl MPTImpl of MPTTrait { let mut shared_nibbles_word_idx = nibbles_skip / 16; let mut shared_nibbles_word = *shared_nibbles.at(shared_nibbles_word_idx); let mut i_nibbles = 0; - let next_hash = if n_nibbles == 0 { - Result::Ok(next_node) - } else { - loop { - let current_nibble_shared_nibbles = (shared_nibbles_word - / shared_nibbles_pow2) - & 0xf; - let current_nibble_key = (key / key_pow2) & 0xf; - if current_nibble_shared_nibbles.into() != current_nibble_key { - break Result::Ok(0); - } + let next_hash = loop { + let current_nibble_shared_nibbles = (shared_nibbles_word + / shared_nibbles_pow2) + & 0xf; + let current_nibble_key = (key / key_pow2) & 0xf; + if current_nibble_shared_nibbles.into() != current_nibble_key { + break Result::Ok(0); + } - key_pow2 = key_pow2 / 16; - i_nibbles += 1; + key_pow2 = key_pow2 / 16; + i_nibbles += 1; - if i_nibbles == n_nibbles { - break Result::Ok(next_node); - } - if key_pow2 == 0 { - break Result::Err('Key reached end'); - } + if i_nibbles == n_nibbles { + break Result::Ok(next_node); + } + if key_pow2 == 0 { + break Result::Err('Key reached end'); + } - if shared_nibbles_pow2 == 0x100000000000000 { - shared_nibbles_pow2 = 16; - shared_nibbles_word_idx += 1; - shared_nibbles_word = *shared_nibbles.at(shared_nibbles_word_idx); + if shared_nibbles_pow2 == 0x100000000000000 { + shared_nibbles_pow2 = 16; + shared_nibbles_word_idx += 1; + shared_nibbles_word = *shared_nibbles.at(shared_nibbles_word_idx); + } else { + if in_byte { + shared_nibbles_pow2 = shared_nibbles_pow2 * 0x1000; } else { - if in_byte { - shared_nibbles_pow2 = shared_nibbles_pow2 * 0x1000; - } else { - shared_nibbles_pow2 = shared_nibbles_pow2 / 0x10; - } - }; + shared_nibbles_pow2 = shared_nibbles_pow2 / 0x10; + } + }; - in_byte = !in_byte; - } + in_byte = !in_byte; }; match next_hash { @@ -204,10 +200,6 @@ impl MPTImpl of MPTTrait { MPTNode::Leaf(( key_end, value, nibbles_skip, n_nibbles )) => { - if key_pow2 == 0 && n_nibbles == 0 { - break Result::Ok(value); - } - let mut key_end_pow2 = pow(2, nibbles_skip.into() * 4); let mut in_byte = false; From d2d4dd7ca6b9b5c681456136c801414b79a285e6 Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Mon, 6 Nov 2023 09:35:53 +0300 Subject: [PATCH 28/28] docs: fix descriptions --- src/data_structures/mmr/utils.cairo | 2 +- src/encoding/rlp.cairo | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index 76571a5..e4672e8 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -28,7 +28,7 @@ fn compute_root(last_pos: felt252, peaks: Peaks) -> felt252 { } // @notice Count the number of bits set to 1 in a 256-bit unsigned integer -// @param n The 256-bit unsigned integer +// @param arg The 256-bit unsigned integer // @return The number of bits set to 1 in n fn count_ones(arg: u256) -> u256 { let mut n = arg; diff --git a/src/encoding/rlp.cairo b/src/encoding/rlp.cairo index 031ad5a..1609c45 100644 --- a/src/encoding/rlp.cairo +++ b/src/encoding/rlp.cairo @@ -106,7 +106,7 @@ fn rlp_decode(input: Words64) -> Result<(RLPItem, usize), felt252> { // @notice RLP decodes into RLPItem::List // @param input RLP encoded input, in little endian 64 bits words // @param len Length of the input -// @return Result with RLPItem::List +// @return Result with a span of the decoded items and the decoded size of each fn rlp_decode_list(ref input: Words64, len: usize) -> Result, felt252> { let mut i = 0; let mut output = ArrayTrait::new();