From 287c3ec4f583425da30e401dcfdbd47381ef6d0f Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Thu, 26 Oct 2023 19:04:07 +0600 Subject: [PATCH] 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');