From 0430d63731feb99306d62747e57063f2ab93b233 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 29 Sep 2023 10:00:57 +0200 Subject: [PATCH 01/39] Add helper function for mmr append --- src/data_structures/mmr/utils.cairo | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index 6faa272..14c95df 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -48,3 +48,25 @@ fn count_ones(n: u256) -> u256 { fn leaf_index_to_mmr_index(n: u256) -> u256 { 2 * n - 1 - count_ones(n - 1) } + +fn mmr_size_to_leaf_count(arg: u256) -> u256 { + let mut mmr_size = arg; + 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; + } +} + +fn leaf_count_to_peaks_count(leaf_count: u256) -> u256 { + count_ones(leaf_count) +} \ No newline at end of file From a143f6f3175857d8586aea3d0ceb5a834b5a5bd5 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 29 Sep 2023 10:01:08 +0200 Subject: [PATCH 02/39] Check peaks length in mmr append --- src/data_structures/mmr/mmr.cairo | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/data_structures/mmr/mmr.cairo b/src/data_structures/mmr/mmr.cairo index 0f69dc7..423d625 100644 --- a/src/data_structures/mmr/mmr.cairo +++ b/src/data_structures/mmr/mmr.cairo @@ -1,6 +1,6 @@ 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 +34,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'); } From 9cb7250196877d939b6d6ce7152d59cdf94ccfc5 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 29 Sep 2023 10:01:15 +0200 Subject: [PATCH 03/39] Add test for mmr forge peaks --- src/data_structures/mmr/tests/test_mmr.cairo | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/data_structures/mmr/tests/test_mmr.cairo b/src/data_structures/mmr/tests/test_mmr.cairo index 1032a9f..6dfcd9b 100644 --- a/src/data_structures/mmr/tests/test_mmr.cairo +++ b/src/data_structures/mmr/tests/test_mmr.cairo @@ -1,5 +1,7 @@ use cairo_lib::data_structures::mmr::mmr::{MMR, MMRTrait}; use cairo_lib::hashing::poseidon::PoseidonHasher; +use debug::PrintTrait; +use cairo_lib::data_structures::mmr::utils::mmr_size_to_leaf_count; fn helper_test_get_elements() -> Span { let elem1 = PoseidonHasher::hash_single(1); @@ -226,3 +228,22 @@ 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'); +} \ No newline at end of file From 6c130bd55b918db483e7011a637beffc706b3eb2 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 29 Sep 2023 11:51:20 +0200 Subject: [PATCH 04/39] Change utils function from u256 to u32 --- src/data_structures/mmr/mmr.cairo | 4 ++-- src/data_structures/mmr/utils.cairo | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/data_structures/mmr/mmr.cairo b/src/data_structures/mmr/mmr.cairo index 423d625..5b82009 100644 --- a/src/data_structures/mmr/mmr.cairo +++ b/src/data_structures/mmr/mmr.cairo @@ -34,8 +34,8 @@ 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() { + let leaf_count = mmr_size_to_leaf_count(self.last_pos); + if leaf_count_to_peaks_count(leaf_count) != peaks.len() { return Result::Err('Invalid peaks count'); } if !peaks.valid(self.last_pos, self.root) { diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index 14c95df..d4460ae 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -30,7 +30,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 // @return The number of bits set to 1 in n -fn count_ones(n: u256) -> u256 { +fn count_ones(n: u32) -> u32 { let mut n = n; let mut count = 0; loop { @@ -45,11 +45,11 @@ fn count_ones(n: u256) -> u256 { // @notice Convert a leaf index to an Merkle Mountain Range tree leaf index // @param n The leaf index // @return The MMR index -fn leaf_index_to_mmr_index(n: u256) -> u256 { +fn leaf_index_to_mmr_index(n: u32) -> u32 { 2 * n - 1 - count_ones(n - 1) } -fn mmr_size_to_leaf_count(arg: u256) -> u256 { +fn mmr_size_to_leaf_count(arg: u32) -> u32 { let mut mmr_size = arg; let bits = bit_length(mmr_size); let mut i = pow(2, bits); @@ -67,6 +67,6 @@ fn mmr_size_to_leaf_count(arg: u256) -> u256 { } } -fn leaf_count_to_peaks_count(leaf_count: u256) -> u256 { +fn leaf_count_to_peaks_count(leaf_count: u32) -> u32 { count_ones(leaf_count) } \ No newline at end of file From 49fde6d62cce38eb8354be83cc04b60a52d8daee Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 29 Sep 2023 11:53:50 +0200 Subject: [PATCH 05/39] Add natspec comments --- src/data_structures/mmr/utils.cairo | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index d4460ae..a895fd1 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -42,15 +42,18 @@ fn count_ones(n: u32) -> u32 { } } -// @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: u32) -> u32 { 2 * n - 1 - count_ones(n - 1) } -fn mmr_size_to_leaf_count(arg: u32) -> u32 { - let mut mmr_size = arg; +// @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: u32) -> u32 { + let mut mmr_size = n; let bits = bit_length(mmr_size); let mut i = pow(2, bits); let mut leaf_count = 0; @@ -67,6 +70,10 @@ fn mmr_size_to_leaf_count(arg: u32) -> u32 { } } + +// @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: u32) -> u32 { count_ones(leaf_count) } \ No newline at end of file From 9b640797ba246ba8ba41b0bf747e2e295c991842 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 29 Sep 2023 11:54:21 +0200 Subject: [PATCH 06/39] Run scarb fmt --- src/data_structures/mmr/mmr.cairo | 4 +++- src/data_structures/mmr/tests/test_mmr.cairo | 10 +++++++--- src/data_structures/mmr/utils.cairo | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/data_structures/mmr/mmr.cairo b/src/data_structures/mmr/mmr.cairo index 5b82009..81d75c2 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, mmr_size_to_leaf_count, leaf_count_to_peaks_count}; +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 diff --git a/src/data_structures/mmr/tests/test_mmr.cairo b/src/data_structures/mmr/tests/test_mmr.cairo index 6dfcd9b..f8ae012 100644 --- a/src/data_structures/mmr/tests/test_mmr.cairo +++ b/src/data_structures/mmr/tests/test_mmr.cairo @@ -233,8 +233,12 @@ fn test_verify_proof_invalid_peaks() { #[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); + 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(); @@ -246,4 +250,4 @@ fn test_attack_forge_peaks() { let res = mmr_fake.append(9, peaks_fake); assert(res.is_err(), 'attack success: forged peak'); -} \ No newline at end of file +} diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index a895fd1..1879459 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -76,4 +76,4 @@ fn mmr_size_to_leaf_count(n: u32) -> u32 { // @return Number of peaks fn leaf_count_to_peaks_count(leaf_count: u32) -> u32 { count_ones(leaf_count) -} \ No newline at end of file +} From b309c9f3b082175d21df3a4755138882b5d10495 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 29 Sep 2023 21:07:21 +0200 Subject: [PATCH 07/39] Implement trailing_ones helper function --- src/data_structures/mmr/utils.cairo | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index 1879459..f7ece35 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -77,3 +77,18 @@ fn mmr_size_to_leaf_count(n: u32) -> u32 { fn leaf_count_to_peaks_count(leaf_count: u32) -> u32 { count_ones(leaf_count) } + +// @notice Get the number of trailing ones in the binary representation of a number +// @param n The number +// @return Number of trailing ones +fn trailing_ones(n: u32) -> u32 { + let mut n = n; + let mut count = 0; + loop { + if n % 2 == 0 { + break count; + } + n /= 2; + count += 1; + } +} \ No newline at end of file From a6989dfcfec6770dca6da931d786dea4a1b508c3 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 29 Sep 2023 21:08:42 +0200 Subject: [PATCH 08/39] Improve mmr append --- src/data_structures/mmr/mmr.cairo | 55 +++++++++++-------------------- 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/src/data_structures/mmr/mmr.cairo b/src/data_structures/mmr/mmr.cairo index 81d75c2..43d0612 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, trailing_ones }; use cairo_lib::hashing::poseidon::PoseidonHasher; @@ -37,7 +37,9 @@ impl MMRImpl of MMRTrait { // @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); - if leaf_count_to_peaks_count(leaf_count) != peaks.len() { + let peaks_count = peaks.len(); + + if leaf_count_to_peaks_count(leaf_count) != peaks_count { return Result::Err('Invalid peaks count'); } if !peaks.valid(self.last_pos, self.root) { @@ -46,54 +48,37 @@ impl MMRImpl of MMRTrait { self.last_pos += 1; - let mut peaks_arr = ArrayTrait::new(); - let mut i: usize = 0; + let new_peaks_count = trailing_ones(leaf_count); + let mut new_peak = hash; + let mut i = 0; + loop { - if i == peaks.len() { + if i == new_peaks_count { break (); } - peaks_arr.append(*peaks.at(i)); + new_peak = PoseidonHasher::hash_double(*peaks.at(peaks.len() - i - 1), new_peak); i += 1; + self.last_pos += 1; }; - peaks_arr.append(hash); - - let mut height = 0; + + let mut new_peaks = ArrayTrait::new(); + let mut i = 0; loop { - if get_height(self.last_pos + 1) <= height { + if i == peaks_count - new_peaks_count { break (); } - self.last_pos += 1; - - let mut peaks_span = peaks_arr.span(); - // As the above condition verifies that a merge is happening, we have at least 2 peaks (that are about to be merged) - let right = peaks_span.pop_back().unwrap(); - let left = peaks_span.pop_back().unwrap(); + new_peaks.append(*peaks.at(i)); - let mut new_peaks = ArrayTrait::new(); - i = 0; - loop { - if i == peaks_arr.len() - 2 { - break (); - } - - new_peaks.append(*peaks_arr.at(i)); - - i += 1; - }; - - let hash = PoseidonHasher::hash_double(*left, *right); - new_peaks.append(hash); - peaks_arr = new_peaks; - - height += 1; + i += 1; }; + new_peaks.append(new_peak); - let new_root = compute_root(self.last_pos.into(), peaks_arr.span()); + let new_root = compute_root(self.last_pos.into(), new_peaks.span()); self.root = new_root; - Result::Ok((new_root, peaks_arr.span())) + Result::Ok((new_root, new_peaks.span())) } // @notice Verifies a proof for an element in the MMR From e98ef121203ee49aab9d74287ca03e637c1a520d Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 29 Sep 2023 21:08:54 +0200 Subject: [PATCH 09/39] Remove unnecessary import in test_mmr --- src/data_structures/mmr/tests/test_mmr.cairo | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data_structures/mmr/tests/test_mmr.cairo b/src/data_structures/mmr/tests/test_mmr.cairo index f8ae012..aaa40a4 100644 --- a/src/data_structures/mmr/tests/test_mmr.cairo +++ b/src/data_structures/mmr/tests/test_mmr.cairo @@ -1,6 +1,5 @@ use cairo_lib::data_structures::mmr::mmr::{MMR, MMRTrait}; use cairo_lib::hashing::poseidon::PoseidonHasher; -use debug::PrintTrait; use cairo_lib::data_structures::mmr::utils::mmr_size_to_leaf_count; fn helper_test_get_elements() -> Span { From ce3e1f2ec0c37f51469a08f0680b81673c7bcf04 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 29 Sep 2023 21:33:15 +0200 Subject: [PATCH 10/39] Run scarb fmt --- src/data_structures/mmr/mmr.cairo | 2 +- src/data_structures/mmr/utils.cairo | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data_structures/mmr/mmr.cairo b/src/data_structures/mmr/mmr.cairo index 43d0612..ed8f1cc 100644 --- a/src/data_structures/mmr/mmr.cairo +++ b/src/data_structures/mmr/mmr.cairo @@ -62,7 +62,7 @@ impl MMRImpl of MMRTrait { i += 1; self.last_pos += 1; }; - + let mut new_peaks = ArrayTrait::new(); let mut i = 0; loop { diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index f7ece35..1433b54 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -91,4 +91,4 @@ fn trailing_ones(n: u32) -> u32 { n /= 2; count += 1; } -} \ No newline at end of file +} From d9e3148e91502d059f37ec5365f656cff6d0a681 Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Tue, 3 Oct 2023 17:49:08 +0900 Subject: [PATCH 11/39] fix words64 to u256 empty words --- 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 a772bf7..ce39da2 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 3ced64e6e88ce475af740e86162cc3b33571fa72 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Thu, 12 Oct 2023 12:08:12 +0200 Subject: [PATCH 12/39] Implement get_peak_info function --- src/data_structures/mmr/utils.cairo | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index 1879459..90cf24c 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -77,3 +77,29 @@ fn mmr_size_to_leaf_count(n: u32) -> u32 { fn leaf_count_to_peaks_count(leaf_count: u32) -> u32 { 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 2460417bd8176f515c9a2f827fceb633568ae56f Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Thu, 12 Oct 2023 12:08:30 +0200 Subject: [PATCH 13/39] Tests for get_peak_info function --- src/data_structures/mmr/tests/test_utils.cairo | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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)'); +} From c5af976bc34f122f7a241a0aecfc74bf3dbca0bf Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Thu, 12 Oct 2023 12:32:30 +0200 Subject: [PATCH 14/39] Fix verify_proof logic --- src/data_structures/mmr/mmr.cairo | 10 ++++++++-- src/data_structures/mmr/tests/test_mmr.cairo | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/data_structures/mmr/mmr.cairo b/src/data_structures/mmr/mmr.cairo index 81d75c2..89b6097 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; @@ -108,8 +108,14 @@ impl MMRImpl of MMRTrait { if !peaks.valid(*self.last_pos, *self.root) { 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_mmr.cairo b/src/data_structures/mmr/tests/test_mmr.cairo index f8ae012..d4facc6 100644 --- a/src/data_structures/mmr/tests/test_mmr.cairo +++ b/src/data_structures/mmr/tests/test_mmr.cairo @@ -251,3 +251,21 @@ fn test_attack_forge_peaks() { assert(res.is_err(), 'attack success: forged peak'); } + +#[test] +#[available_gas(99999999)] +fn test_attack_forge_verify() { + let elem1 = PoseidonHasher::hash_single(1); + let elem2 = PoseidonHasher::hash_single(2); + let elem3 = PoseidonHasher::hash_double(elem1, elem2); + let elem4 = PoseidonHasher::hash_single(4); + + let mmr = MMRTrait::new( + root: PoseidonHasher::hash_double(4, PoseidonHasher::hash_double(elem3, elem4)), last_pos: 4 + ); + + let proof = array![].span(); + let peaks = array![elem3, elem4].span(); + + assert(mmr.verify_proof(1, elem4, peaks, proof).unwrap() == false, 'Attack successful forged verify'); +} From 01e7d3a0c530b93a5bdb8241c1256944d9daf60d Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Thu, 12 Oct 2023 12:37:55 +0200 Subject: [PATCH 15/39] Format --- src/data_structures/mmr/mmr.cairo | 3 ++- src/data_structures/mmr/tests/test_mmr.cairo | 5 ++++- src/data_structures/mmr/utils.cairo | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/data_structures/mmr/mmr.cairo b/src/data_structures/mmr/mmr.cairo index 0baffc3..1620c82 100644 --- a/src/data_structures/mmr/mmr.cairo +++ b/src/data_structures/mmr/mmr.cairo @@ -1,7 +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, mmr_size_to_leaf_count, leaf_count_to_peaks_count, trailing_ones, get_peak_info + compute_root, get_height, mmr_size_to_leaf_count, leaf_count_to_peaks_count, trailing_ones, + get_peak_info }; use cairo_lib::hashing::poseidon::PoseidonHasher; diff --git a/src/data_structures/mmr/tests/test_mmr.cairo b/src/data_structures/mmr/tests/test_mmr.cairo index 1a64834..36327ef 100644 --- a/src/data_structures/mmr/tests/test_mmr.cairo +++ b/src/data_structures/mmr/tests/test_mmr.cairo @@ -266,5 +266,8 @@ fn test_attack_forge_verify() { let proof = array![].span(); let peaks = array![elem3, elem4].span(); - assert(mmr.verify_proof(1, elem4, peaks, proof).unwrap() == false, 'Attack successful forged verify'); + assert( + mmr.verify_proof(1, elem4, peaks, proof).unwrap() == false, + 'Attack successful forged verify' + ); } diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index 2cab63a..e3ce301 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -116,4 +116,4 @@ fn get_peak_info(elements_count: u32, element_index: u32) -> (u32, u32) { mountain_height -= 1; mountain_elements_count /= 2; } -} \ No newline at end of file +} From fa1d0301fb83507bb566047c6a8dc7fc5ffc699e Mon Sep 17 00:00:00 2001 From: feltroidprime Date: Thu, 14 Mar 2024 10:25:29 +0100 Subject: [PATCH 16/39] use div_rem instead of operators --- src/encoding/rlp.cairo | 49 ++++++++++++----------------------- src/utils/types/words64.cairo | 4 +-- 2 files changed, 18 insertions(+), 35 deletions(-) diff --git a/src/encoding/rlp.cairo b/src/encoding/rlp.cairo index 1609c45..8fc8ff8 100644 --- a/src/encoding/rlp.cairo +++ b/src/encoding/rlp.cairo @@ -1,7 +1,6 @@ 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/ #[derive(Drop, PartialEq)] @@ -119,24 +118,20 @@ fn rlp_decode_list(ref input: Words64, len: usize) -> Result (d, dl), - Result::Err(e) => { - break Result::Err(e); - } + Result::Err(e) => { break Result::Err(e); } }; match decoded { RLPItem::Bytes(b) => { output.append(b); - let word = decoded_len / 8; - let reversed = 7 - (decoded_len % 8); + let (word, offset) = DivRem::div_rem(decoded_len, 8); + let reversed = 7 - offset; let next_start = word * 8 + reversed; if (total_len - decoded_len != 0) { input = input.slice_le(next_start, total_len - decoded_len); } total_len -= decoded_len; }, - RLPItem::List(_) => { - panic_with_felt252('Recursive list not supported'); - } + RLPItem::List(_) => { panic_with_felt252('Recursive list not supported'); } } i += decoded_len; } @@ -149,15 +144,9 @@ fn rlp_decode_list_lazy(input: Words64, lazy: Span) -> Result<(RLPItem, u 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::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; @@ -184,16 +173,14 @@ fn rlp_decode_list_lazy(input: Words64, lazy: Span) -> Result<(RLPItem, u break Result::Err('Too many items to decode'); } - let current_word = current_input_index / 8; + let (current_word, current_word_offset) = DivRem::div_rem(current_input_index, 8); - let pow2_shift = pow2((current_input_index % 8) * 8); + let pow2_shift = pow2(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) - }, + RLPType::String(()) => { (0, 1) }, RLPType::StringShort(()) => { let len = prefix - 0x80; (1, len) @@ -201,8 +188,8 @@ fn rlp_decode_list_lazy(input: Words64, lazy: Span) -> Result<(RLPItem, u 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 (current_word, offset) = DivRem::div_rem(current_input_index + 1, 8); + let current_word_offset = 7 - offset; let len_span = input .slice_le(current_word * 8 + current_word_offset, len_len.try_into().unwrap()); @@ -218,18 +205,14 @@ fn rlp_decode_list_lazy(input: Words64, lazy: Span) -> Result<(RLPItem, u (1 + len_len, len.into()) }, - RLPType::ListShort(()) => { - panic_with_felt252('Recursive list not supported') - }, - RLPType::ListLong(()) => { - panic_with_felt252('Recursive list not supported') - } + 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 (current_word, offset) = DivRem::div_rem(current_input_index, 8); + let current_word_offset = 7 - offset; let start = current_word * 8 + current_word_offset; let item_len = item_len.try_into().unwrap(); diff --git a/src/utils/types/words64.cairo b/src/utils/types/words64.cairo index 23ef8f1..96e41e4 100644 --- a/src/utils/types/words64.cairo +++ b/src/utils/types/words64.cairo @@ -128,8 +128,8 @@ impl Words64Impl of Words64Trait { pow2_reverse_words_offset_bits = pow2(reverse_words_offset_bits); } - let mut output_words = len / 8; - if len % 8 != 0 { + let (mut output_words, offset) = DivRem::div_rem(len, 8); + if offset != 0 { output_words += 1; } From d36d653c72e5a104a16302243589eedecef16b87 Mon Sep 17 00:00:00 2001 From: feltroidprime Date: Thu, 14 Mar 2024 11:01:53 +0100 Subject: [PATCH 17/39] as_u256_le optimisation --- src/data_structures/eth_mpt.cairo | 24 +++------ src/data_structures/tests/test_eth_mpt.cairo | 14 ++---- src/utils/types/tests/test_words64.cairo | 26 ++-------- src/utils/types/words64.cairo | 52 ++++++++------------ 4 files changed, 36 insertions(+), 80 deletions(-) diff --git a/src/data_structures/eth_mpt.cairo b/src/data_structures/eth_mpt.cairo index ebda892..14dbe9a 100644 --- a/src/data_structures/eth_mpt.cairo +++ b/src/data_structures/eth_mpt.cairo @@ -80,16 +80,12 @@ impl MPTImpl of MPTTrait { node, current_nibble.try_into().unwrap() ) { Result::Ok(d) => d, - Result::Err(e) => { - break Result::Err(e); - } + 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); - } + Result::Err(e) => { break Result::Err(e); } } }; @@ -117,11 +113,9 @@ impl MPTImpl of MPTTrait { if current_hash_words.len() == 0 { break Result::Ok(array![].span()); } else { - match current_hash_words.as_u256_le(32) { + match current_hash_words.as_u256_le() { Result::Ok(h) => h, - Result::Err(_) => { - break Result::Err('Invalid hash'); - } + Result::Err(_) => { break Result::Err('Invalid hash'); } } }; key_pow2 = key_pow2 / 16; @@ -192,9 +186,7 @@ impl MPTImpl of MPTTrait { } current_hash = next_hash; }, - Result::Err(e) => { - break Result::Err(e); - } + Result::Err(e) => { break Result::Err(e); } } }, MPTNode::Leaf(( @@ -284,14 +276,14 @@ impl MPTImpl of MPTTrait { let n_nibbles = (first_len * 2) - 1; if prefix == 0 { - match second.as_u256_le(32) { + match second.as_u256_le() { Result::Ok(n) => Result::Ok( (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_le(32) { + match second.as_u256_le() { Result::Ok(n) => Result::Ok( (MPTNode::Extension((first, n, 1, n_nibbles)), rlp_byte_len) ), @@ -322,7 +314,7 @@ 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_le(32) { + match hash_words.as_u256_le() { 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 97e57bd..0777524 100644 --- a/src/data_structures/tests/test_eth_mpt.cairo +++ b/src/data_structures/tests/test_eth_mpt.cairo @@ -108,19 +108,13 @@ fn test_decode_rlp_node_branch() { if i >= hashes.len() { break (); } - assert((*hashes.at(i)).as_u256_le(32).unwrap() == *expected.at(i), 'Wrong hash'); + assert((*hashes.at(i)).as_u256_le().unwrap() == *expected.at(i), 'Wrong hash'); i += 1; }; }, - MPTNode::LazyBranch(_) => { - panic_with_felt252('Branch node differs'); - }, - MPTNode::Extension(_) => { - panic_with_felt252('Branch node differs'); - }, - MPTNode::Leaf(_) => { - panic_with_felt252('Branch node differs'); - }, + MPTNode::LazyBranch(_) => { panic_with_felt252('Branch node differs'); }, + MPTNode::Extension(_) => { panic_with_felt252('Branch node differs'); }, + MPTNode::Leaf(_) => { panic_with_felt252('Branch node differs'); }, } } diff --git a/src/utils/types/tests/test_words64.cairo b/src/utils/types/tests/test_words64.cairo index d0ee0a1..2bacf66 100644 --- a/src/utils/types/tests/test_words64.cairo +++ b/src/utils/types/tests/test_words64.cairo @@ -94,34 +94,16 @@ fn test_as_u256_le_full() { .span(); let expected = 0x09898DA43A5D35F4B6F239256FF310F9480829EBCEE54BC42E8B632605E21673; - assert(words.as_u256_le(32).unwrap() == expected, 'Wrong value'); + assert(words.as_u256_le().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 words = array![0x2e8b632605e21673, 0x480829ebcee54bc4, 0xb6f239256ff310f9].span(); - let expected: u256 = 0x00B6F2392A480829EBCEE54BC4008B632605E200000000000000000000000000; - assert(words.as_u256_le(21).unwrap() == expected, 'Wrong value'); + let expected = 0xB6F239256FF310F9480829EBCEE54BC42E8B632605E21673; + assert(words.as_u256_le().unwrap() == expected, 'Wrong value'); } #[test] diff --git a/src/utils/types/words64.cairo b/src/utils/types/words64.cairo index 96e41e4..a91906e 100644 --- a/src/utils/types/words64.cairo +++ b/src/utils/types/words64.cairo @@ -57,44 +57,32 @@ impl Words64Impl of Words64Trait { } } - // @notice Converts little endian 64 bit words to a little endian u256 - // @param bytes_used The number of bytes used + // @notice Converts little endian 64 bit words to a little endian u256 using the first 4 64 bits words // @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; - + fn as_u256_le(self: Words64) -> Result { 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); - } + let w0: u128 = match self.get(0) { + Option::Some(x) => { (*x.unbox()).into() }, + Option::None => { 0 } + }; + let w1: u128 = match self.get(1) { + Option::Some(x) => { (*x.unbox()).into() }, + Option::None => { 0 } + }; - output = output | ((*self.at(i)).into() * current_pow2); + let w2: u128 = match self.get(2) { + Option::Some(x) => { (*x.unbox()).into() }, + Option::None => { 0 } + }; - if i < len - 1 { - current_pow2 = current_pow2 * word_pow2; - } + let w3: u128 = match self.get(3) { + Option::Some(x) => { (*x.unbox()).into() }, + Option::None => { 0 } + }; - i += 1; - } + let res = u256 { low: w0 + w1 * word_pow2, high: w2 + w3 * word_pow2 }; + return Result::Ok(res); } // @notice Slices 64 bit little endian words from a starting byte and a length From 5c000476d0d1a7bf94428f7ecae5bd3a1c3eb167 Mon Sep 17 00:00:00 2001 From: feltroidprime Date: Thu, 14 Mar 2024 11:18:57 +0100 Subject: [PATCH 18/39] use div_rem in extract_nibbles instead of bitwise ops --- src/utils/types/byte.cairo | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/utils/types/byte.cairo b/src/utils/types/byte.cairo index eea81ae..509b089 100644 --- a/src/utils/types/byte.cairo +++ b/src/utils/types/byte.cairo @@ -6,11 +6,7 @@ impl ByteImpl of ByteTrait { // @notice Extracts the high and low nibbles from a byte // @return (high, low), example: 0xab -> (0xa, 0xb) fn extract_nibbles(self: Byte) -> (Byte, Byte) { - let masked = self & 0xf0; - // right shift by 4 bits - let high = masked / 16; - let low = self & 0x0f; - + let (high, low) = DivRem::div_rem(self, 16); (high, low) } } From 2d8b0ea54eb5a2863bbe1c9403228c0bc905d054 Mon Sep 17 00:00:00 2001 From: feltroidprime Date: Fri, 15 Mar 2024 17:26:48 +0100 Subject: [PATCH 19/39] slice_le optimisation --- src/utils/types/words64.cairo | 153 ++++++++++++++++++++-------------- 1 file changed, 89 insertions(+), 64 deletions(-) diff --git a/src/utils/types/words64.cairo b/src/utils/types/words64.cairo index a91906e..85b0377 100644 --- a/src/utils/types/words64.cairo +++ b/src/utils/types/words64.cairo @@ -1,3 +1,4 @@ +use core::option::OptionTrait; use cairo_lib::utils::bitwise::left_shift; use cairo_lib::utils::math::pow; @@ -98,83 +99,107 @@ impl Words64Impl of Words64Trait { if len == 0 { return ArrayTrait::new().span(); } - - let first_word_index = start / 8; - // number of right bytes to remove - let mut word_offset_bytes = 8 - ((start + 1) % 8); - if word_offset_bytes == 8 { - word_offset_bytes = 0; + let (q, n_ending_bytes) = DivRem::div_rem(len, 8); + + let mut n_words = 0; + if q == 0 { + if n_ending_bytes == 0 { + // 0 bytes to extract + return ArrayTrait::new().span(); + } else { + // 1 to 7 bytes to extract + n_words = 1; + } + } else { + if n_ending_bytes == 0 { + n_words = q; + } else { + n_words = q + 1; + } } - let word_offset_bits = word_offset_bytes * 8; - let pow2_word_offset_bits = pow2(word_offset_bits); - let mask_second_word = pow2_word_offset_bits - 1; - let reverse_words_offset_bits = 64 - word_offset_bits; + let start_index = start / 8; + let start_offset = (8 - ((start + 1) % 8)) % 8; + + if start_offset == 0 { + // Handle trivial case where start offset is 0, words can be copied directly + let copy = self.slice(start_index, q); + let mut output: Array = ArrayTrait::new(); + let mut i = 0; + loop { + if i == q { + break; + } + output.append(*copy.at(i)); + i += 1; + }; + if (n_ending_bytes != 0) { + let last_word: u64 = *self.at(start_index + q) / (pow2(8 * n_ending_bytes)).into(); + output.append(last_word); + return output.span(); + } - let mut pow2_reverse_words_offset_bits = 0; - if word_offset_bytes != 0 { - pow2_reverse_words_offset_bits = pow2(reverse_words_offset_bits); + return output.span(); } - let (mut output_words, offset) = DivRem::div_rem(len, 8); - if offset != 0 { - output_words += 1; + let pow_cut: u64 = pow2(8 * start_offset); + let pow_acc: u64 = pow2(64 - 8 * start_offset); + + let mut current_word: u64 = (*self.at(start_index) / pow_cut); + let mut output: Array = ArrayTrait::new(); + + if n_words == 1 { + let avl_bytes_in_first_word = 8 - start_offset; + let needs_next_word = len > avl_bytes_in_first_word; + if needs_next_word == false { + let last_word: u64 = (current_word % pow2(8 * n_ending_bytes)).into(); + output.append(last_word); + return output.span(); + } else { + let last_word: u64 = (*self + .at(start_index + 1) % pow2(8 * (len + start_offset - 8)) + .into()); + output.append(current_word + last_word * pow_acc.into()); + return output.span(); + } } - let mut output = ArrayTrait::new(); - let mut i = first_word_index; + let mut n_words_to_handle_in_loop = n_words; + if n_ending_bytes != 0 { + n_words_to_handle_in_loop = n_words_to_handle_in_loop - 1; + } + + let mut i = 1; + let mut n_words_handled = 0; loop { - if i - first_word_index == output_words - 1 { - break (); + if n_words_handled == n_words_to_handle_in_loop { + break; } - let word = *self.at(i); - let next = *self.at(i + 1); - - // remove bytes from the right - let shifted = word / pow2_word_offset_bits; - - // get right bytes from the next word - let bytes_to_append = next & mask_second_word; - - // apend bytes to the left of first word - let mask_first_word = bytes_to_append * pow2_reverse_words_offset_bits; - let new_word = shifted | mask_first_word; - - output.append(new_word); + let (q, r) = DivRem::div_rem(*self.at(start_index + i), pow_cut.try_into().unwrap()); + output.append(current_word + r * pow_acc); + current_word = q; + n_words_handled += 1; i += 1; }; - // Handling remainder (last word) - - let last_word = *self.at(i); - let shifted = last_word / pow2_word_offset_bits; - - let mut len_last_word = len % 8; - if len_last_word == 0 { - len_last_word = 8; - } - - if len_last_word <= 8 - word_offset_bytes { - // using u128 because if len_last_word == 8 left_shift might overflow by 1 - // after subtracting 1 it's safe to unwrap - let mask: u128 = left_shift(1_u128, len_last_word.into() * 8) - 1; - let last_word_masked = shifted & mask.try_into().unwrap(); - output.append(last_word_masked); - } else { - let missing_bytes = len_last_word - (8 - word_offset_bytes); - let next = *self.at(i + 1); - - // get right bytes from the next word - let mask_second_word = pow2(missing_bytes * 8) - 1; - let bytes_to_append = next & mask_second_word; - - // apend bytes to the left of first word - let mask_first_word = bytes_to_append * pow2_reverse_words_offset_bits; - let new_word = shifted | mask_first_word; - - output.append(new_word); + if n_ending_bytes != 0 { + let current_word = *self.at(start_index + n_words_handled) / pow_cut; + let avl_bytes_in_next_word = 8 - start_offset; + let needs_next_word = n_ending_bytes > avl_bytes_in_next_word; + if needs_next_word == false { + let last_word: u64 = (current_word % pow2(8 * n_ending_bytes).into()).into(); + output.append(last_word); + return output.span(); + } else { + let last_word: u64 = (*self + .at( + start_index + n_words_handled + 1 + ) % pow2(8 * (n_ending_bytes + start_offset - 8)) + .into()); + output.append(current_word + last_word * pow_acc.into()); + return output.span(); + } } - output.span() } } From 89b5a39d22a394bddb0fdc248dc0e70db9e77d10 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Thu, 21 Mar 2024 12:28:26 +0100 Subject: [PATCH 20/39] Update scarb version --- .tool-versions | 2 +- Scarb.lock | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 Scarb.lock diff --git a/.tool-versions b/.tool-versions index 2175a33..e129096 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -scarb 0.7.0 +scarb 2.6.4 diff --git a/Scarb.lock b/Scarb.lock new file mode 100644 index 0000000..8b41a8f --- /dev/null +++ b/Scarb.lock @@ -0,0 +1,6 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "cairo_lib" +version = "0.2.0" From 0b17101cb2c46e8431a8fc9ff22bdbb493fb4d4e Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Thu, 21 Mar 2024 12:28:44 +0100 Subject: [PATCH 21/39] Remove unused variable --- src/utils/types/tests/test_words64.cairo | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/types/tests/test_words64.cairo b/src/utils/types/tests/test_words64.cairo index d0ee0a1..ecd7f57 100644 --- a/src/utils/types/tests/test_words64.cairo +++ b/src/utils/types/tests/test_words64.cairo @@ -19,7 +19,6 @@ fn test_slice_words64_le_multiple_words_not_full() { fn test_slice_words64_le_multiple_words_full() { let val: Words64 = array![0xabcdef1234567890, 0x7584934785943295, 0x48542576].span(); - let gas = testing::get_available_gas(); let res = val.slice_le(4, 16); assert(res.len() == 2, 'Wrong len'); assert(*res.at(0) == 0x943295abcdef1234, 'Wrong value at 0'); From 11cfdc62883104353d670e0b505a414512faa276 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Thu, 21 Mar 2024 12:30:10 +0100 Subject: [PATCH 22/39] Refactor tests --- src/data_structures/mmr/tests/test_mmr.cairo | 171 +++++++++++++------ 1 file changed, 118 insertions(+), 53 deletions(-) diff --git a/src/data_structures/mmr/tests/test_mmr.cairo b/src/data_structures/mmr/tests/test_mmr.cairo index f41dbd5..d34025f 100644 --- a/src/data_structures/mmr/tests/test_mmr.cairo +++ b/src/data_structures/mmr/tests/test_mmr.cairo @@ -22,11 +22,14 @@ fn test_append_initial() { let mut mmr: MMR = Default::default(); let peaks = array![].span(); - mmr.append(*elems.at(0), peaks); + let (new_root, new_peaks) = mmr.append(*elems.at(0), peaks).unwrap(); - let root = PoseidonHasher::hash_double(1, *elems.at(0)); + let expected_root = PoseidonHasher::hash_double(1, *elems.at(0)); assert(mmr.last_pos == 1, 'Wrong last_pos'); - assert(mmr.root == root, 'Wrong root'); + assert(mmr.root == expected_root, 'Wrong updated root'); + assert(new_root == expected_root, 'Wrong returned root'); + + assert(new_peaks == array![*elems.at(0)].span(), 'Wrong new_peaks'); } #[test] @@ -34,16 +37,25 @@ fn test_append_initial() { fn test_append_1() { let elems = helper_test_get_elements(); let mut mmr: MMR = Default::default(); + let mmr_peaks_0 = array![].span(); + + let (mmr_root_1, mmr_peaks_1) = mmr.append(*elems.at(0), mmr_peaks_0).unwrap(); + + let expected_peaks_1 = array![*elems.at(0)].span(); + let expected_root_1 = PoseidonHasher::hash_double(1, *elems.at(0)); + assert(expected_peaks_1 == mmr_peaks_1, 'Wrong peaks after 1 append'); + assert(mmr.root == expected_root_1, 'Wrong updated root after 2 a.'); + assert(mmr_root_1 == expected_root_1, 'Wrong returned root after 1 a.'); - let mut peaks = array![].span(); - mmr.append(*elems.at(0), peaks); + let (mmr_root_2, mmr_peaks_2) = mmr.append(*elems.at(1), mmr_peaks_1).unwrap(); - peaks = array![*elems.at(0)].span(); - mmr.append(*elems.at(1), peaks); + let expected_peaks_2 = array![*elems.at(2)].span(); + let expected_root_2 = PoseidonHasher::hash_double(3, *elems.at(2)); + assert(expected_peaks_2 == mmr_peaks_2, 'Wrong peaks after 2 appends'); + assert(mmr.root == expected_root_2, 'Wrong updated root after 2 a.'); + assert(mmr_root_2 == expected_root_2, 'Wrong reeturned root after 2 a.'); - let root = PoseidonHasher::hash_double(3, *elems.at(2)); assert(mmr.last_pos == 3, 'Wrong last_pos'); - assert(mmr.root == root, 'Wrong root'); } #[test] @@ -51,21 +63,33 @@ fn test_append_1() { fn test_append_2() { let elems = helper_test_get_elements(); let mut mmr: MMR = Default::default(); + let mmr_peaks_0 = array![].span(); - let mut peaks = array![].span(); - mmr.append(*elems.at(0), peaks); + let (mmr_root_1, mmr_peaks_1) = mmr.append(*elems.at(0), mmr_peaks_0).unwrap(); - peaks = array![*elems.at(0)].span(); - mmr.append(*elems.at(1), peaks); + let expected_peaks_1 = array![*elems.at(0)].span(); + let expected_root_1 = PoseidonHasher::hash_double(1, *elems.at(0)); + assert(expected_peaks_1 == mmr_peaks_1, 'Wrong peaks after 1 append'); + assert(mmr.root == expected_root_1, 'Wrong updated root after 2 a.'); + assert(mmr_root_1 == expected_root_1, 'Wrong returned root after 1 a.'); - peaks = array![*elems.at(2)].span(); - mmr.append(*elems.at(3), peaks); + let (mmr_root_2, mmr_peaks_2) = mmr.append(*elems.at(1), mmr_peaks_1).unwrap(); + + let expected_peaks_2 = array![*elems.at(2)].span(); + let expected_root_2 = PoseidonHasher::hash_double(3, *elems.at(2)); + assert(expected_peaks_2 == mmr_peaks_2, 'Wrong peaks after 2 appends'); + assert(mmr.root == expected_root_2, 'Wrong updated root after 2 a.'); + assert(mmr_root_2 == expected_root_2, 'Wrong reeturned root after 2 a.'); + + let (mmr_root_3, mmr_peaks_3) = mmr.append(*elems.at(3), mmr_peaks_2).unwrap(); + + let expected_peaks_3 = array![*elems.at(2), *elems.at(3)].span(); + let expected_root_3 = PoseidonHasher::hash_double(4, PoseidonHasher::hash_double(*elems.at(2), *elems.at(3))); + assert(expected_peaks_3 == mmr_peaks_3, 'Wrong peaks after 3 appends'); + assert(mmr.root == expected_root_3, 'Wrong updated root after 3 a.'); + assert(mmr_root_3 == expected_root_3, 'Wrong reeturned root after 3 a.'); - let root = PoseidonHasher::hash_double( - 4, PoseidonHasher::hash_double(*elems.at(2), *elems.at(3)) - ); assert(mmr.last_pos == 4, 'Wrong last_pos'); - assert(mmr.root == root, 'Wrong root'); } #[test] @@ -73,22 +97,41 @@ fn test_append_2() { fn test_append_3() { let elems = helper_test_get_elements(); let mut mmr: MMR = Default::default(); + let mmr_peaks_0 = array![].span(); + + let (mmr_root_1, mmr_peaks_1) = mmr.append(*elems.at(0), mmr_peaks_0).unwrap(); + + let expected_peaks_1 = array![*elems.at(0)].span(); + let expected_root_1 = PoseidonHasher::hash_double(1, *elems.at(0)); + assert(expected_peaks_1 == mmr_peaks_1, 'Wrong peaks after 1 append'); + assert(mmr.root == expected_root_1, 'Wrong updated root after 2 a.'); + assert(mmr_root_1 == expected_root_1, 'Wrong returned root after 1 a.'); + + let (mmr_root_2, mmr_peaks_2) = mmr.append(*elems.at(1), mmr_peaks_1).unwrap(); - let mut peaks = array![].span(); - mmr.append(*elems.at(0), peaks); + let expected_peaks_2 = array![*elems.at(2)].span(); + let expected_root_2 = PoseidonHasher::hash_double(3, *elems.at(2)); + assert(expected_peaks_2 == mmr_peaks_2, 'Wrong peaks after 2 appends'); + assert(mmr.root == expected_root_2, 'Wrong updated root after 2 a.'); + assert(mmr_root_2 == expected_root_2, 'Wrong reeturned root after 2 a.'); - peaks = array![*elems.at(0)].span(); - mmr.append(*elems.at(1), peaks); + let (mmr_root_3, mmr_peaks_3) = mmr.append(*elems.at(3), mmr_peaks_2).unwrap(); - peaks = array![*elems.at(2)].span(); - mmr.append(*elems.at(3), peaks); + let expected_peaks_3 = array![*elems.at(2), *elems.at(3)].span(); + let expected_root_3 = PoseidonHasher::hash_double(4, PoseidonHasher::hash_double(*elems.at(2), *elems.at(3))); + assert(expected_peaks_3 == mmr_peaks_3, 'Wrong peaks after 3 appends'); + assert(mmr.root == expected_root_3, 'Wrong updated root after 3 a.'); + assert(mmr_root_3 == expected_root_3, 'Wrong reeturned root after 3 a.'); - peaks = array![*elems.at(2), *elems.at(3)].span(); - mmr.append(*elems.at(4), peaks); + let (mmr_root_4, mmr_peaks_4) = mmr.append(*elems.at(4), mmr_peaks_3).unwrap(); + + let expected_peaks_4 = array![*elems.at(6)].span(); + let expected_root_4 = PoseidonHasher::hash_double(7, *elems.at(6)); + assert(expected_peaks_4 == mmr_peaks_4, 'Wrong peaks after 4 appends'); + assert(mmr.root == expected_root_4, 'Wrong updated root after 4 a.'); + assert(mmr_root_4 == expected_root_4, 'Wrong reeturned root after 4 a.'); - let root = PoseidonHasher::hash_double(7, *elems.at(6)); assert(mmr.last_pos == 7, 'Wrong last_pos'); - assert(mmr.root == root, 'Wrong root'); } #[test] @@ -96,27 +139,49 @@ fn test_append_3() { fn test_append_4() { let elems = helper_test_get_elements(); let mut mmr: MMR = Default::default(); + let mmr_peaks_0 = array![].span(); - let mut peaks = array![].span(); - mmr.append(*elems.at(0), peaks); + let (mmr_root_1, mmr_peaks_1) = mmr.append(*elems.at(0), mmr_peaks_0).unwrap(); - peaks = array![*elems.at(0)].span(); - mmr.append(*elems.at(1), peaks); + let expected_peaks_1 = array![*elems.at(0)].span(); + let expected_root_1 = PoseidonHasher::hash_double(1, *elems.at(0)); + assert(expected_peaks_1 == mmr_peaks_1, 'Wrong peaks after 1 append'); + assert(mmr.root == expected_root_1, 'Wrong updated root after 2 a.'); + assert(mmr_root_1 == expected_root_1, 'Wrong returned root after 1 a.'); - peaks = array![*elems.at(2)].span(); - mmr.append(*elems.at(3), peaks); + let (mmr_root_2, mmr_peaks_2) = mmr.append(*elems.at(1), mmr_peaks_1).unwrap(); - peaks = array![*elems.at(2), *elems.at(3)].span(); - mmr.append(*elems.at(4), peaks); + let expected_peaks_2 = array![*elems.at(2)].span(); + let expected_root_2 = PoseidonHasher::hash_double(3, *elems.at(2)); + assert(expected_peaks_2 == mmr_peaks_2, 'Wrong peaks after 2 appends'); + assert(mmr.root == expected_root_2, 'Wrong updated root after 2 a.'); + assert(mmr_root_2 == expected_root_2, 'Wrong reeturned root after 2 a.'); - peaks = array![*elems.at(6)].span(); - mmr.append(*elems.at(7), peaks); + let (mmr_root_3, mmr_peaks_3) = mmr.append(*elems.at(3), mmr_peaks_2).unwrap(); + + let expected_peaks_3 = array![*elems.at(2), *elems.at(3)].span(); + let expected_root_3 = PoseidonHasher::hash_double(4, PoseidonHasher::hash_double(*elems.at(2), *elems.at(3))); + assert(expected_peaks_3 == mmr_peaks_3, 'Wrong peaks after 3 appends'); + assert(mmr.root == expected_root_3, 'Wrong updated root after 3 a.'); + assert(mmr_root_3 == expected_root_3, 'Wrong reeturned root after 3 a.'); + + let (mmr_root_4, mmr_peaks_4) = mmr.append(*elems.at(4), mmr_peaks_3).unwrap(); + + let expected_peaks_4 = array![*elems.at(6)].span(); + let expected_root_4 = PoseidonHasher::hash_double(7, *elems.at(6)); + assert(expected_peaks_4 == mmr_peaks_4, 'Wrong peaks after 4 appends'); + assert(mmr.root == expected_root_4, 'Wrong updated root after 4 a.'); + assert(mmr_root_4 == expected_root_4, 'Wrong reeturned root after 4 a.'); + + let (mmr_root_5, mmr_peaks_5) = mmr.append(*elems.at(7), mmr_peaks_4).unwrap(); + + let expected_peaks_5 = array![*elems.at(6), *elems.at(7)].span(); + let expected_root_5 = PoseidonHasher::hash_double(8, PoseidonHasher::hash_double(*elems.at(6), *elems.at(7))); + assert(expected_peaks_5 == mmr_peaks_5, 'Wrong peaks after 5 appends'); + assert(mmr.root == expected_root_5, 'Wrong updated root after 5 a.'); + assert(mmr_root_5 == expected_root_5, 'Wrong reeturned root after 5 a.'); - let root = PoseidonHasher::hash_double( - 8, PoseidonHasher::hash_double(*elems.at(6), *elems.at(7)) - ); assert(mmr.last_pos == 8, 'Wrong last_pos'); - assert(mmr.root == root, 'Wrong root'); } #[test] @@ -124,20 +189,20 @@ fn test_append_4() { fn test_append_wrong_peaks() { let elems = helper_test_get_elements(); let mut mmr: MMR = Default::default(); + let peaks = array![].span(); + + let (_, peaks) = mmr.append(*elems.at(0), peaks).unwrap(); - let mut peaks = array![].span(); - mmr.append(*elems.at(0), peaks); + let (_, peaks) = mmr.append(*elems.at(1), peaks).unwrap(); - peaks = array![*elems.at(0)].span(); - mmr.append(*elems.at(1), peaks); + let (_, peaks) = mmr.append(*elems.at(3), peaks).unwrap(); - peaks = array![*elems.at(2)].span(); - mmr.append(*elems.at(3), peaks); + assert(peaks == array![*elems.at(2), *elems.at(3)].span(), 'Wrong peaks returned by append'); - peaks = array![*elems.at(2), *elems.at(4)].span(); - let res = mmr.append(*elems.at(4), peaks); + let wrong_peaks = array![*elems.at(2), *elems.at(4)].span(); + let res = mmr.append(*elems.at(4), wrong_peaks); - assert(res.is_err(), 'Wrong peaks'); + assert(res.is_err(), 'Appnd accepted with wrong peaks'); } #[test] @@ -240,7 +305,7 @@ fn test_attack_forge_peaks() { // 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); + let _ = 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)); From 3aaed8202c0e9b5076990b36ab6ab8512ad6f380 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Thu, 21 Mar 2024 12:54:27 +0100 Subject: [PATCH 23/39] Fmt --- src/data_structures/eth_mpt.cairo | 16 +++------- src/data_structures/mmr/tests/test_mmr.cairo | 16 +++++++--- src/data_structures/tests/test_eth_mpt.cairo | 12 ++------ src/encoding/rlp.cairo | 32 +++++--------------- 4 files changed, 27 insertions(+), 49 deletions(-) diff --git a/src/data_structures/eth_mpt.cairo b/src/data_structures/eth_mpt.cairo index ebda892..30e398a 100644 --- a/src/data_structures/eth_mpt.cairo +++ b/src/data_structures/eth_mpt.cairo @@ -80,16 +80,12 @@ impl MPTImpl of MPTTrait { node, current_nibble.try_into().unwrap() ) { Result::Ok(d) => d, - Result::Err(e) => { - break Result::Err(e); - } + 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); - } + Result::Err(e) => { break Result::Err(e); } } }; @@ -119,9 +115,7 @@ impl MPTImpl of MPTTrait { } else { match current_hash_words.as_u256_le(32) { Result::Ok(h) => h, - Result::Err(_) => { - break Result::Err('Invalid hash'); - } + Result::Err(_) => { break Result::Err('Invalid hash'); } } }; key_pow2 = key_pow2 / 16; @@ -192,9 +186,7 @@ impl MPTImpl of MPTTrait { } current_hash = next_hash; }, - Result::Err(e) => { - break Result::Err(e); - } + Result::Err(e) => { break Result::Err(e); } } }, MPTNode::Leaf(( diff --git a/src/data_structures/mmr/tests/test_mmr.cairo b/src/data_structures/mmr/tests/test_mmr.cairo index d34025f..a28c836 100644 --- a/src/data_structures/mmr/tests/test_mmr.cairo +++ b/src/data_structures/mmr/tests/test_mmr.cairo @@ -84,7 +84,9 @@ fn test_append_2() { let (mmr_root_3, mmr_peaks_3) = mmr.append(*elems.at(3), mmr_peaks_2).unwrap(); let expected_peaks_3 = array![*elems.at(2), *elems.at(3)].span(); - let expected_root_3 = PoseidonHasher::hash_double(4, PoseidonHasher::hash_double(*elems.at(2), *elems.at(3))); + let expected_root_3 = PoseidonHasher::hash_double( + 4, PoseidonHasher::hash_double(*elems.at(2), *elems.at(3)) + ); assert(expected_peaks_3 == mmr_peaks_3, 'Wrong peaks after 3 appends'); assert(mmr.root == expected_root_3, 'Wrong updated root after 3 a.'); assert(mmr_root_3 == expected_root_3, 'Wrong reeturned root after 3 a.'); @@ -118,7 +120,9 @@ fn test_append_3() { let (mmr_root_3, mmr_peaks_3) = mmr.append(*elems.at(3), mmr_peaks_2).unwrap(); let expected_peaks_3 = array![*elems.at(2), *elems.at(3)].span(); - let expected_root_3 = PoseidonHasher::hash_double(4, PoseidonHasher::hash_double(*elems.at(2), *elems.at(3))); + let expected_root_3 = PoseidonHasher::hash_double( + 4, PoseidonHasher::hash_double(*elems.at(2), *elems.at(3)) + ); assert(expected_peaks_3 == mmr_peaks_3, 'Wrong peaks after 3 appends'); assert(mmr.root == expected_root_3, 'Wrong updated root after 3 a.'); assert(mmr_root_3 == expected_root_3, 'Wrong reeturned root after 3 a.'); @@ -160,7 +164,9 @@ fn test_append_4() { let (mmr_root_3, mmr_peaks_3) = mmr.append(*elems.at(3), mmr_peaks_2).unwrap(); let expected_peaks_3 = array![*elems.at(2), *elems.at(3)].span(); - let expected_root_3 = PoseidonHasher::hash_double(4, PoseidonHasher::hash_double(*elems.at(2), *elems.at(3))); + let expected_root_3 = PoseidonHasher::hash_double( + 4, PoseidonHasher::hash_double(*elems.at(2), *elems.at(3)) + ); assert(expected_peaks_3 == mmr_peaks_3, 'Wrong peaks after 3 appends'); assert(mmr.root == expected_root_3, 'Wrong updated root after 3 a.'); assert(mmr_root_3 == expected_root_3, 'Wrong reeturned root after 3 a.'); @@ -176,7 +182,9 @@ fn test_append_4() { let (mmr_root_5, mmr_peaks_5) = mmr.append(*elems.at(7), mmr_peaks_4).unwrap(); let expected_peaks_5 = array![*elems.at(6), *elems.at(7)].span(); - let expected_root_5 = PoseidonHasher::hash_double(8, PoseidonHasher::hash_double(*elems.at(6), *elems.at(7))); + let expected_root_5 = PoseidonHasher::hash_double( + 8, PoseidonHasher::hash_double(*elems.at(6), *elems.at(7)) + ); assert(expected_peaks_5 == mmr_peaks_5, 'Wrong peaks after 5 appends'); assert(mmr.root == expected_root_5, 'Wrong updated root after 5 a.'); assert(mmr_root_5 == expected_root_5, 'Wrong reeturned root after 5 a.'); diff --git a/src/data_structures/tests/test_eth_mpt.cairo b/src/data_structures/tests/test_eth_mpt.cairo index 97e57bd..a520f76 100644 --- a/src/data_structures/tests/test_eth_mpt.cairo +++ b/src/data_structures/tests/test_eth_mpt.cairo @@ -112,15 +112,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'); - }, - MPTNode::Leaf(_) => { - panic_with_felt252('Branch node differs'); - }, + MPTNode::LazyBranch(_) => { panic_with_felt252('Branch node differs'); }, + MPTNode::Extension(_) => { panic_with_felt252('Branch node differs'); }, + MPTNode::Leaf(_) => { panic_with_felt252('Branch node differs'); }, } } diff --git a/src/encoding/rlp.cairo b/src/encoding/rlp.cairo index 1609c45..1655395 100644 --- a/src/encoding/rlp.cairo +++ b/src/encoding/rlp.cairo @@ -119,9 +119,7 @@ fn rlp_decode_list(ref input: Words64, len: usize) -> Result (d, dl), - Result::Err(e) => { - break Result::Err(e); - } + Result::Err(e) => { break Result::Err(e); } }; match decoded { RLPItem::Bytes(b) => { @@ -134,9 +132,7 @@ fn rlp_decode_list(ref input: Words64, len: usize) -> Result { - panic_with_felt252('Recursive list not supported'); - } + RLPItem::List(_) => { panic_with_felt252('Recursive list not supported'); } } i += decoded_len; } @@ -149,15 +145,9 @@ fn rlp_decode_list_lazy(input: Words64, lazy: Span) -> Result<(RLPItem, u 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::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; @@ -191,9 +181,7 @@ fn rlp_decode_list_lazy(input: Words64, lazy: Span) -> Result<(RLPItem, u let rlp_type = RLPTypeTrait::from_byte(prefix.try_into().unwrap()).unwrap(); let (item_start_skip, item_len) = match rlp_type { - RLPType::String(()) => { - (0, 1) - }, + RLPType::String(()) => { (0, 1) }, RLPType::StringShort(()) => { let len = prefix - 0x80; (1, len) @@ -218,12 +206,8 @@ fn rlp_decode_list_lazy(input: Words64, lazy: Span) -> Result<(RLPItem, u (1 + len_len, len.into()) }, - RLPType::ListShort(()) => { - panic_with_felt252('Recursive list not supported') - }, - RLPType::ListLong(()) => { - panic_with_felt252('Recursive list not supported') - } + 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(); From fcf48af54ecd42ec236e2376e6679e8de9fad940 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 22 Mar 2024 12:51:11 +0100 Subject: [PATCH 24/39] Fix compatibility with 2.6.4 --- src/encoding/rlp.cairo | 16 ++++++++++++---- src/utils/types/byte.cairo | 4 +++- src/utils/types/words64.cairo | 4 +++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/encoding/rlp.cairo b/src/encoding/rlp.cairo index 8fc8ff8..84e9aba 100644 --- a/src/encoding/rlp.cairo +++ b/src/encoding/rlp.cairo @@ -123,7 +123,9 @@ fn rlp_decode_list(ref input: Words64, len: usize) -> Result { output.append(b); - let (word, offset) = DivRem::div_rem(decoded_len, 8); + let (word, offset) = DivRem::div_rem( + decoded_len, TryInto::>::try_into(8).unwrap() + ); let reversed = 7 - offset; let next_start = word * 8 + reversed; if (total_len - decoded_len != 0) { @@ -173,7 +175,9 @@ fn rlp_decode_list_lazy(input: Words64, lazy: Span) -> Result<(RLPItem, u break Result::Err('Too many items to decode'); } - let (current_word, current_word_offset) = DivRem::div_rem(current_input_index, 8); + let (current_word, current_word_offset) = DivRem::div_rem( + current_input_index, TryInto::>::try_into(8).unwrap() + ); let pow2_shift = pow2(current_word_offset * 8); let prefix = (*input.at(current_word) / pow2_shift) & 0xff; @@ -188,7 +192,9 @@ fn rlp_decode_list_lazy(input: Words64, lazy: Span) -> Result<(RLPItem, u RLPType::StringLong(()) => { let len_len = prefix - 0xb7; - let (current_word, offset) = DivRem::div_rem(current_input_index + 1, 8); + let (current_word, offset) = DivRem::div_rem( + current_input_index + 1, TryInto::>::try_into(8).unwrap() + ); let current_word_offset = 7 - offset; let len_span = input @@ -211,7 +217,9 @@ fn rlp_decode_list_lazy(input: Words64, lazy: Span) -> Result<(RLPItem, u current_input_index += item_start_skip.try_into().unwrap(); if span_contains(lazy, lazy_index) { - let (current_word, offset) = DivRem::div_rem(current_input_index, 8); + let (current_word, offset) = DivRem::div_rem( + current_input_index, TryInto::>::try_into(8).unwrap() + ); let current_word_offset = 7 - offset; let start = current_word * 8 + current_word_offset; diff --git a/src/utils/types/byte.cairo b/src/utils/types/byte.cairo index 509b089..91fb785 100644 --- a/src/utils/types/byte.cairo +++ b/src/utils/types/byte.cairo @@ -6,7 +6,9 @@ impl ByteImpl of ByteTrait { // @notice Extracts the high and low nibbles from a byte // @return (high, low), example: 0xab -> (0xa, 0xb) fn extract_nibbles(self: Byte) -> (Byte, Byte) { - let (high, low) = DivRem::div_rem(self, 16); + let (high, low) = DivRem::div_rem( + self, TryInto::>::try_into(16).unwrap() + ); (high, low) } } diff --git a/src/utils/types/words64.cairo b/src/utils/types/words64.cairo index 85b0377..07cf9de 100644 --- a/src/utils/types/words64.cairo +++ b/src/utils/types/words64.cairo @@ -99,7 +99,9 @@ impl Words64Impl of Words64Trait { if len == 0 { return ArrayTrait::new().span(); } - let (q, n_ending_bytes) = DivRem::div_rem(len, 8); + let (q, n_ending_bytes) = DivRem::div_rem( + len, TryInto::>::try_into(8).unwrap() + ); let mut n_words = 0; if q == 0 { From c65b8e388294763790c9ebc01552edbab2d31594 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 22 Mar 2024 20:04:30 +0100 Subject: [PATCH 25/39] MMR append change loops to pop from span --- src/data_structures/mmr/mmr.cairo | 32 +++++++++++++---------------- src/data_structures/mmr/utils.cairo | 4 ++++ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/data_structures/mmr/mmr.cairo b/src/data_structures/mmr/mmr.cairo index 5a8240d..6da2f5b 100644 --- a/src/data_structures/mmr/mmr.cairo +++ b/src/data_structures/mmr/mmr.cairo @@ -49,32 +49,28 @@ impl MMRImpl of MMRTrait { self.last_pos += 1; + // TODO: why number of new peaks is equal to number of trailing ones let new_peaks_count = trailing_ones(leaf_count); - let mut new_peak = hash; - let mut i = 0; + let mut preserved_peaks = peaks.slice(0, peaks_count - new_peaks_count); + let mut merged_peaks = peaks.slice(peaks_count - new_peaks_count, new_peaks_count); + let mut last_peak = hash; loop { - if i == new_peaks_count { - break (); - } - - new_peak = PoseidonHasher::hash_double(*peaks.at(peaks.len() - i - 1), new_peak); - - i += 1; + match merged_peaks.pop_back() { + Option::Some(x) => { last_peak = PoseidonHasher::hash_double(*x, last_peak); }, + Option::None => { break; } + }; self.last_pos += 1; }; let mut new_peaks = ArrayTrait::new(); - let mut i = 0; loop { - if i == peaks_count - new_peaks_count { - break (); - } - new_peaks.append(*peaks.at(i)); - - i += 1; + match preserved_peaks.pop_front() { + Option::Some(x) => { new_peaks.append(*x); }, + Option::None => { break; } + }; }; - new_peaks.append(new_peak); + new_peaks.append(last_peak); let new_root = compute_root(self.last_pos.into(), new_peaks.span()); self.root = new_root; @@ -98,7 +94,7 @@ impl MMRImpl of MMRTrait { if !peaks.valid(*self.last_pos, *self.root) { return Result::Err('Invalid peaks'); } - + let (peak_index, peak_height) = get_peak_info(*self.last_pos, index); if proof.len() != peak_height { diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index 966f620..aa66ac7 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -45,6 +45,8 @@ fn count_ones(n: u32) -> u32 { // @notice Convert a leaf index to an Merkle Mountain Range tree index // @param n The leaf index // @return The MMR index +// Explanation of why this formula is correct +// https://mmr.herodotus.dev/mmr-size-vs-leaf-count#leaf-count-to-mmr-size-algorithm fn leaf_index_to_mmr_index(n: u32) -> u32 { 2 * n - 1 - count_ones(n - 1) } @@ -52,6 +54,8 @@ fn leaf_index_to_mmr_index(n: u32) -> u32 { // @notice Convert a Merkle Mountain Range tree size to number of leaves // @param n MMR size // @result Number of leaves +// Explanation of why this algorithm is correct +// https://mmr.herodotus.dev/mmr-size-vs-leaf-count#mmr-size-to-leaf-count-algorithm fn mmr_size_to_leaf_count(n: u32) -> u32 { let mut mmr_size = n; let bits = bit_length(mmr_size + 1); From f1419ee2a245caea6903fe50044a0befcdadbb95 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 22 Mar 2024 20:30:47 +0100 Subject: [PATCH 26/39] Remove unused function --- src/data_structures/mmr/peaks.cairo | 8 -------- src/data_structures/mmr/tests/test_peaks.cairo | 18 ------------------ 2 files changed, 26 deletions(-) diff --git a/src/data_structures/mmr/peaks.cairo b/src/data_structures/mmr/peaks.cairo index b8682b7..1fbbae8 100644 --- a/src/data_structures/mmr/peaks.cairo +++ b/src/data_structures/mmr/peaks.cairo @@ -39,12 +39,4 @@ impl PeaksImpl of PeaksTrait { let computed_root = compute_root(last_pos.into(), self); computed_root == root } - - // @notice Checks if the peaks contain a peak - // @param peak The peak to check inclusion - // @return True if the peaks contain the peak - #[inline(always)] - fn contains_peak(self: Peaks, peak: felt252) -> bool { - span_contains(self, peak) - } } diff --git a/src/data_structures/mmr/tests/test_peaks.cairo b/src/data_structures/mmr/tests/test_peaks.cairo index a615740..d2c0484 100644 --- a/src/data_structures/mmr/tests/test_peaks.cairo +++ b/src/data_structures/mmr/tests/test_peaks.cairo @@ -53,21 +53,3 @@ fn test_valid() { assert(peaks.span().valid(last_pos_u32, root), 'Valid'); } - -#[test] -#[available_gas(99999999)] -fn test_containts_peak() { - let peak0 = PoseidonHasher::hash_double(245, 287388); - let peak1 = PoseidonHasher::hash_double(2340, 827394299); - let peak2 = PoseidonHasher::hash_double(923048, 23984294798); - - let peaks_arr = array![peak0, peak1, peak2]; - let peaks = peaks_arr.span(); - - assert(peaks.contains_peak(peak0), 'Contains peak 0'); - assert(peaks.contains_peak(peak1), 'Contains peak 1'); - assert(peaks.contains_peak(peak2), 'Contains peak 2'); - - assert(!peaks.contains_peak(0), 'Does not contain 0'); - assert(!peaks.contains_peak(1), 'Does not contain 1'); -} From f1a5e9a3f94d57fc529cdb4df8fcca4c85d89f47 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 22 Mar 2024 20:31:25 +0100 Subject: [PATCH 27/39] Use divrem in trailing_ones --- src/data_structures/mmr/utils.cairo | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index aa66ac7..6f4bb75 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -89,10 +89,11 @@ fn trailing_ones(n: u32) -> u32 { let mut n = n; let mut count = 0; loop { - if n % 2 == 0 { + let (halfed, rem) = DivRem::div_rem(n, TryInto::>::try_into(2).unwrap()); + if rem == 0 { break count; } - n /= 2; + n = halfed; count += 1; } } From c00222c907cbfd381d28749a2dc2fab0eac6beae Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 22 Mar 2024 21:19:50 +0100 Subject: [PATCH 28/39] Change u32 to usize --- src/data_structures/mmr/utils.cairo | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index 6f4bb75..ae4a218 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -27,10 +27,10 @@ fn compute_root(last_pos: felt252, peaks: Peaks) -> felt252 { PoseidonHasher::hash_double(last_pos, bag) } -// @notice Count the number of bits set to 1 in a 256-bit unsigned integer -// @param arg The 256-bit unsigned integer +// @notice Count the number of bits set to 1 in an unsigned integer +// @param arg The usize (u32) unsigned integer // @return The number of bits set to 1 in n -fn count_ones(n: u32) -> u32 { +fn count_ones(n: usize) -> usize { let mut n = n; let mut count = 0; loop { @@ -47,7 +47,7 @@ fn count_ones(n: u32) -> u32 { // @return The MMR index // Explanation of why this formula is correct // https://mmr.herodotus.dev/mmr-size-vs-leaf-count#leaf-count-to-mmr-size-algorithm -fn leaf_index_to_mmr_index(n: u32) -> u32 { +fn leaf_index_to_mmr_index(n: usize) -> usize { 2 * n - 1 - count_ones(n - 1) } @@ -56,7 +56,7 @@ fn leaf_index_to_mmr_index(n: u32) -> u32 { // @result Number of leaves // Explanation of why this algorithm is correct // https://mmr.herodotus.dev/mmr-size-vs-leaf-count#mmr-size-to-leaf-count-algorithm -fn mmr_size_to_leaf_count(n: u32) -> u32 { +fn mmr_size_to_leaf_count(n: usize) -> usize { let mut mmr_size = n; let bits = bit_length(mmr_size + 1); let mut mountain_leaf_count = pow(2, bits - 1); @@ -78,18 +78,20 @@ fn mmr_size_to_leaf_count(n: u32) -> u32 { // @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: u32) -> u32 { +fn leaf_count_to_peaks_count(leaf_count: usize) -> usize { count_ones(leaf_count) } // @notice Get the number of trailing ones in the binary representation of a number // @param n The number // @return Number of trailing ones -fn trailing_ones(n: u32) -> u32 { +fn trailing_ones(n: usize) -> usize { let mut n = n; let mut count = 0; loop { - let (halfed, rem) = DivRem::div_rem(n, TryInto::>::try_into(2).unwrap()); + let (halfed, rem) = DivRem::div_rem( + n, TryInto::>::try_into(2).unwrap() + ); if rem == 0 { break count; } @@ -102,7 +104,7 @@ fn trailing_ones(n: u32) -> u32 { // @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) { +fn get_peak_info(elements_count: usize, element_index: usize) -> (usize, usize) { let mut elements_count = elements_count; let mut element_index = element_index; From f94a861611e21479c8d76aa5f2dc1a04c1a31bb3 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Mon, 22 Apr 2024 16:39:04 +0200 Subject: [PATCH 29/39] Benchmarking --- benchmark/benchmark.py | 80 ++++++++++++++++++++++++++++++++++++++ benchmark/results/.gitkeep | 0 2 files changed, 80 insertions(+) create mode 100644 benchmark/benchmark.py create mode 100644 benchmark/results/.gitkeep diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py new file mode 100644 index 0000000..aa10d04 --- /dev/null +++ b/benchmark/benchmark.py @@ -0,0 +1,80 @@ +import argparse +import subprocess +import re +import os +import pandas as pd + +def bench(name, force): + try: + with open(os.path.join("results", name), "w" if force else "x") as f: + test_out = subprocess.check_output(["scarb", "test"], cwd="..") + results = [] + for line in test_out.decode("utf-8").split("\n"): + m = re.match(r"^test ([a-zA-Z0-9_:]+) ... ok \(gas usage est\.: (\d+)\)$", line) + if m: + results.append(f"{m.group(1)} {m.group(2)}") + f.write("\n".join(results)) + except FileExistsError: + print(f"Bench {name} already exists. If you want to overwrite it, pass the --force flag.") + + +def results(): + bench_names = [] + for file in os.listdir("results"): + if file.startswith("."): + continue + bench_names.append(file) + + results = {} + test_names = set() + for bench in bench_names: + with open(os.path.join("results", bench), "r") as f: + for line in f.read().split("\n"): + test_name, gas = line.split(" ") + test_names.add(test_name) + if not test_name in results: + results[test_name] = {} + results[test_name][bench] = gas + test_names = list(test_names) + test_names.sort() + + table = [[None for _ in bench_names] for _ in test_names] + for i, test in enumerate(test_names): + for j, bench in enumerate(bench_names): + try: + table[i][j] = results[test][bench] + except KeyError: + pass + df = pd.DataFrame(table, index=test_names, columns=bench_names) + df.to_excel("results.xlsx") + + + +def main(): + parser = argparse.ArgumentParser(description="Process some commands.") + + # Create a subparser object + subparsers = parser.add_subparsers(dest="command", help='sub-command help') + + # Create the parser for the "bench" command + parser_bench = subparsers.add_parser('bench', help='Run a benchmark') + parser_bench.add_argument('benchmark_name', type=str, help='The name of the benchmark to run') + parser_bench.add_argument('--force', action='store_true', help='Overwrite the benchmark if it already exists') + + # Create the parser for the "results" command + parser_results = subparsers.add_parser('results', help='Show results') + + # Parse the arguments + args = parser.parse_args() + + # Decide what to do based on the command + if args.command == 'bench': + bench(args.benchmark_name, args.force) + elif args.command == 'results': + results() + else: + parser.print_help() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/benchmark/results/.gitkeep b/benchmark/results/.gitkeep new file mode 100644 index 0000000..e69de29 From 895ca892e873a23906b906d4ec90ef67adf42ef3 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Mon, 22 Apr 2024 16:42:10 +0200 Subject: [PATCH 30/39] Use const spans in pow2 --- .tool-versions | 2 +- src/utils/types/words64.cairo | 199 +++++++++++----------------------- 2 files changed, 67 insertions(+), 134 deletions(-) diff --git a/.tool-versions b/.tool-versions index e129096..42058cc 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -scarb 2.6.4 +scarb nightly-2024-04-20 \ No newline at end of file diff --git a/src/utils/types/words64.cairo b/src/utils/types/words64.cairo index 07cf9de..dd8ef9b 100644 --- a/src/utils/types/words64.cairo +++ b/src/utils/types/words64.cairo @@ -264,138 +264,71 @@ fn reverse_endianness_u64(input: u64, significant_bytes: Option) -> u64 { } } -// This should be replaced with a "dw" equivalent when the compiler supports it fn pow2(pow: usize) -> u64 { - if pow == 0 { - return 0x1; - } else if pow == 1 { - return 0x2; - } else if pow == 2 { - return 0x4; - } else if pow == 3 { - return 0x8; - } else if pow == 4 { - return 0x10; - } else if pow == 5 { - return 0x20; - } else if pow == 6 { - return 0x40; - } else if pow == 7 { - return 0x80; - } else if pow == 8 { - return 0x100; - } else if pow == 9 { - return 0x200; - } else if pow == 10 { - return 0x400; - } else if pow == 11 { - return 0x800; - } else if pow == 12 { - return 0x1000; - } else if pow == 13 { - return 0x2000; - } else if pow == 14 { - return 0x4000; - } else if pow == 15 { - return 0x8000; - } else if pow == 16 { - return 0x10000; - } else if pow == 17 { - return 0x20000; - } else if pow == 18 { - return 0x40000; - } else if pow == 19 { - return 0x80000; - } else if pow == 20 { - return 0x100000; - } else if pow == 21 { - return 0x200000; - } else if pow == 22 { - return 0x400000; - } else if pow == 23 { - return 0x800000; - } else if pow == 24 { - return 0x1000000; - } else if pow == 25 { - return 0x2000000; - } else if pow == 26 { - return 0x4000000; - } else if pow == 27 { - return 0x8000000; - } else if pow == 28 { - return 0x10000000; - } else if pow == 29 { - return 0x20000000; - } else if pow == 30 { - return 0x40000000; - } else if pow == 31 { - return 0x80000000; - } else if pow == 32 { - return 0x100000000; - } else if pow == 33 { - return 0x200000000; - } else if pow == 34 { - return 0x400000000; - } else if pow == 35 { - return 0x800000000; - } else if pow == 36 { - return 0x1000000000; - } else if pow == 37 { - return 0x2000000000; - } else if pow == 38 { - return 0x4000000000; - } else if pow == 39 { - return 0x8000000000; - } else if pow == 40 { - return 0x10000000000; - } else if pow == 41 { - return 0x20000000000; - } else if pow == 42 { - return 0x40000000000; - } else if pow == 43 { - return 0x80000000000; - } else if pow == 44 { - return 0x100000000000; - } else if pow == 45 { - return 0x200000000000; - } else if pow == 46 { - return 0x400000000000; - } else if pow == 47 { - return 0x800000000000; - } else if pow == 48 { - return 0x1000000000000; - } else if pow == 49 { - return 0x2000000000000; - } else if pow == 50 { - return 0x4000000000000; - } else if pow == 51 { - return 0x8000000000000; - } else if pow == 52 { - return 0x10000000000000; - } else if pow == 53 { - return 0x20000000000000; - } else if pow == 54 { - return 0x40000000000000; - } else if pow == 55 { - return 0x80000000000000; - } else if pow == 56 { - return 0x100000000000000; - } else if pow == 57 { - return 0x200000000000000; - } else if pow == 58 { - return 0x400000000000000; - } else if pow == 59 { - return 0x800000000000000; - } else if pow == 60 { - return 0x1000000000000000; - } else if pow == 61 { - return 0x2000000000000000; - } else if pow == 62 { - return 0x4000000000000000; - } else if pow == 63 { - return 0x8000000000000000; - } else { - return 0; - } + *[ + 0x1, + 0x2, + 0x4, + 0x8, + 0x10, + 0x20, + 0x40, + 0x80, + 0x100, + 0x200, + 0x400, + 0x800, + 0x1000, + 0x2000, + 0x4000, + 0x8000, + 0x10000, + 0x20000, + 0x40000, + 0x80000, + 0x100000, + 0x200000, + 0x400000, + 0x800000, + 0x1000000, + 0x2000000, + 0x4000000, + 0x8000000, + 0x10000000, + 0x20000000, + 0x40000000, + 0x80000000, + 0x100000000, + 0x200000000, + 0x400000000, + 0x800000000, + 0x1000000000, + 0x2000000000, + 0x4000000000, + 0x8000000000, + 0x10000000000, + 0x20000000000, + 0x40000000000, + 0x80000000000, + 0x100000000000, + 0x200000000000, + 0x400000000000, + 0x800000000000, + 0x1000000000000, + 0x2000000000000, + 0x4000000000000, + 0x8000000000000, + 0x10000000000000, + 0x20000000000000, + 0x40000000000000, + 0x80000000000000, + 0x100000000000000, + 0x200000000000000, + 0x400000000000000, + 0x800000000000000, + 0x1000000000000000, + 0x2000000000000000, + 0x4000000000000000, + 0x8000000000000000, + ].span().at(pow) } - From d8af0bdbd833e9c808c0ae0e175ed80d2ac9f235 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Mon, 22 Apr 2024 16:42:20 +0200 Subject: [PATCH 31/39] Fmt with new formatter --- src/data_structures/eth_mpt.cairo | 9 ++++++--- src/data_structures/mmr/proof.cairo | 3 ++- src/data_structures/tests/test_eth_mpt.cairo | 1 + src/hashing/keccak.cairo | 3 ++- src/hashing/poseidon.cairo | 4 +++- src/utils/types/words64.cairo | 14 ++++++++------ 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/data_structures/eth_mpt.cairo b/src/data_structures/eth_mpt.cairo index 14dbe9a..01b3a63 100644 --- a/src/data_structures/eth_mpt.cairo +++ b/src/data_structures/eth_mpt.cairo @@ -75,7 +75,8 @@ impl MPTImpl of MPTTrait { // 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 + // 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() ) { @@ -107,7 +108,8 @@ impl MPTImpl of MPTTrait { } let current_nibble = (key / key_pow2) & 0xf; - // Unwrap impossible to fail, as we are masking with 0xf, meaning the result is always a nibble + // Unwrap impossible to fail, as we are masking with 0xf, meaning the result is + // always a nibble let current_hash_words = *nibbles.at(current_nibble.try_into().unwrap()); current_hash = if current_hash_words.len() == 0 { @@ -269,7 +271,8 @@ impl MPTImpl of MPTTrait { } else if len == 2 { 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 + // 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(); diff --git a/src/data_structures/mmr/proof.cairo b/src/data_structures/mmr/proof.cairo index e72e082..411eb3e 100644 --- a/src/data_structures/mmr/proof.cairo +++ b/src/data_structures/mmr/proof.cairo @@ -15,7 +15,8 @@ impl ProofImpl of ProofTrait { // @return The root of the subtree fn compute_peak(self: Proof, index: usize, hash: felt252) -> felt252 { // calculate direction array - // direction[i] - whether the i-th node from the root is a left or a right child of its parent + // direction[i] - whether the i-th node from the root is a left or a right child of its + // parent let mut bits = bit_length(index); if self.len() + 1 > bits { bits = self.len() + 1; diff --git a/src/data_structures/tests/test_eth_mpt.cairo b/src/data_structures/tests/test_eth_mpt.cairo index 0777524..e2a4d53 100644 --- a/src/data_structures/tests/test_eth_mpt.cairo +++ b/src/data_structures/tests/test_eth_mpt.cairo @@ -313,6 +313,7 @@ fn test_hash_rlp_node() { #[available_gas(9999999999999)] fn test_full_verify() { //// Account : 0x7b5C526B7F8dfdff278b4a3e045083FBA4028790 | Goerli | Block 9000000 + //// ////"0xf90211a0f09eae4e1e51fdde02a2884e285b8a8a9c72cc7e7cdaeef013714e3499bcd475a0ce33fd7097055e50d64c42759027e41ffb22d5b2a03ee67207dc94b547e40956a04817bf75497b71a78957ff89d05107cbf16ead02f7e68f13cead9e7d24dfcda5a00751841dcd0e21ff273930aa4722cabae7ea4e09d0f4e9f667b57ab68a41652ea047008ee2caeec1839c016d0a8efd2e901091bfae5388fc064db9f14f4bda362da0f952be9637ec6790bcdcf9ae3d4bca607259f26c0731e3cbd2882924c9db5653a061a8882bde126643739fe5f0acc5d234467718c27217f56513fd222009802336a061dbaa68a4290e8cce57166ffc6fd22d081c5893a081082b23668ef6c7d65c81a0ef2e0aea160700e14be7285c8b83535f4d104a74ac8db6c188d84ee48a8a647ca0c00853c7500db3c616d5d7dcd7503c02307045e7670a0749ffdebadc732a9ab4a068050da8f891b57fbeacffe4ba3e41f11c5d6b0ec8553fbb796f46951ecd1445a0762e36c38c548c5ae61da51205ef1dc66390702397becef53c50d969cae7a2ada0abff9de80f8e14979ebbe80ae3e702e61b31b91ea481c0e63a7bde12e866eeb5a017220448de88495fdf81446233768ef9441058e4602ecafc1da85a7cbbf1c16da084351381e6cad5052c82f731e8d19d86193794eccdf274529bed7e67309cca78a0784e83133c0ba8ff0262d0c96dc93f936d97eac46327d32f3c1baceb63934d9d80", ////"0xf90211a0679ec41f2230e1f57eededf17732966880d9835d744ad769a1b246829341a588a09c2941acea1f1461a7d0af3bb9917c16e3a3339556623a6cad6d1f40ee8fc8a8a0211b79624826f8cd651f7a0123356cfef56854adf7285163988ba4eee0e8f964a062a3e341692078b717029cd105b462749386aecc1cb647721cc382872eac4a51a01a9fc7658bcc2948e1123273e83fb66894e64c2e19aa8f3ac546c45ef4b22290a08c5cdf2e341821e9c3163ec52847e33884f4797669607a60a8adafd00edead0ea08b07046b12762a58e03a482d597cca869aaffd85214bbd08c4624325a7cf80c9a0602d16a56550182218f642f56e66b1cf72555c38dac0fd061b8ef113d4653f4aa038fa2d962cfe43eb49f5a7d1787a436e8e3c6858665b1b0703c4e42ad43f962ea03a706c9b0e0757079f96d9df003eae31aafcf7525d6114033ee929c78adc580aa0f8a66bdc97088d4c73429b9ee00d7bcd0589be3462a53e9f5b9876d4b5231e40a04bbcbff81f2c0b65f29724ef71f6d439b6f857ad5fa7b643c1ea5dfc72ee240fa098cd5bf5aea320986616ff7bfc78efdf43091610fb457447058958e68a13e49ba02033016a2ef0512c926211fbec6f9b7398c58ae10116901d086905979649d968a07cf13191df973971dd4592a95c33cc3c248a4e919b8866c7c8ecff44a6e453a2a017010b7ff49cf72fbc13136f189457a2bc09e8c400ca9cb7997ff75bf34637ec80", ////"0xf90211a06c86eef3a8754d952a265132ed86282d34ec24f9f5aa2f085629155798975c2ba02cef7b79076bddfe220fd88ca1a18414683b2f37e8b94cf8c6bdf743cff9d18ca00fe6e40f33bc1b76e34a6c92cdfce9b08f808bca7c6fe85348a7b7e838632a20a0f2f4d7b6fb649794a45288bfbc32274f3031ebc8bbf808146a3104ea27d72e7aa0cf2a353bb32b3b9c004c7586d6723ea5e2ccb99972c84b4b2c90166ca0c3c65ba0753fee595b7a0b80d3574db4e4615a599da7bedfa71bcb9ba214192c6dfb8a4fa0a7e45064974417eeef5556e4fa3533c819cc04825a33f0e244440d4d6a42828ca06cb2eaf789a62824a4a2b730bc4b8ee70e3648fbdf6cd61ea86d234dda67ccc3a0e42e79aeed163de73664c3b4f9451208b22b4874eae6c007b5f0405a64a55050a072f87da9fbd3c727080eef39891888876cdfbc54b732cf4f08ee19d067117d5ba0ce88e695612d636a6f73c2fcc0086e486249a0284cd6b88bf1cc3a7bef84ce9ba0d408599a558fc0ad84aba0bea36e00c1e99ddebf7b74e2f68912563bd5a62522a0154aef7de9275d13e860b11f138a811e6ed97fbf8985c524ccd5c231dbc62180a0282022799ec74b1dc2df4edad584b0b974113a06a857ff72ed17471908d28404a00c4fc7a3f7ded56f4da7093a875bd7c3a6cb35cc6bdf505cb79aee64708640aca0339829e86f4b7a2d68fe0b4707e32016534cdf8a48070a3921a48bcc0fd4b11c80", diff --git a/src/hashing/keccak.cairo b/src/hashing/keccak.cairo index d106bce..dd3a030 100644 --- a/src/hashing/keccak.cairo +++ b/src/hashing/keccak.cairo @@ -6,7 +6,8 @@ const EMPTY_KECCAK: u256 = 0x70A4855D04D8FA7B3B2782CA53B600E5C003C7DCB27D7E923C2 // @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 little endian 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; diff --git a/src/hashing/poseidon.cairo b/src/hashing/poseidon.cairo index 168d542..f069010 100644 --- a/src/hashing/poseidon.cairo +++ b/src/hashing/poseidon.cairo @@ -19,7 +19,9 @@ fn hash_words64(words: Words64) -> felt252 { } } -// Permutation params: https://docs.starknet.io/documentation/architecture_and_concepts/Cryptography/hash-functions/#poseidon_hash +// Permutation params: +// +// https://docs.starknet.io/documentation/architecture_and_concepts/Cryptography/hash-functions/#poseidon_hash impl PoseidonHasher of Hasher { // @inheritdoc Hasher fn hash_single(a: felt252) -> felt252 { diff --git a/src/utils/types/words64.cairo b/src/utils/types/words64.cairo index dd8ef9b..bbeebda 100644 --- a/src/utils/types/words64.cairo +++ b/src/utils/types/words64.cairo @@ -3,8 +3,9 @@ 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 -// Example: 0x34957c6d8a83f9cff74578dea9 is represented as [0xcff9838a6d7c9534, 0xa9de7845f7] +// @dev In many cases it's expected that the words are in little endian, but the overall order is +// big endian Example: 0x34957c6d8a83f9cff74578dea9 is represented as [0xcff9838a6d7c9534, +// 0xa9de7845f7] type Words64 = Span; #[generate_trait] @@ -58,8 +59,8 @@ impl Words64Impl of Words64Trait { } } - // @notice Converts little endian 64 bit words to a little endian u256 using the first 4 64 bits words - // @return The little endian u256 representation of the words + // @notice Converts little endian 64 bit words to a little endian u256 using the first 4 64 bits + // words @return The little endian u256 representation of the words fn as_u256_le(self: Words64) -> Result { let word_pow2 = 0x10000000000000000; // 2 ** 64 @@ -88,10 +89,11 @@ 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... + // The starting byte is counted from the left. Example: 0xabcdef -> byte 0 is 0xab, byte 1 is + // 0xcd... // @param len The number of bytes to slice // @return A span of 64 bit little endian words - // Example: + // Example: // words: [0xabcdef1234567890, 0x7584934785943295, 0x48542576] // start: 5 | len: 17 // output: [0x3295abcdef123456, 0x2576758493478594, 0x54] From f31c1ec0346a4ac480de94836c5cb04014c1747a Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Mon, 6 May 2024 16:32:37 +0200 Subject: [PATCH 32/39] Minor optimization of append + comment --- src/data_structures/mmr/mmr.cairo | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/data_structures/mmr/mmr.cairo b/src/data_structures/mmr/mmr.cairo index 6da2f5b..246a658 100644 --- a/src/data_structures/mmr/mmr.cairo +++ b/src/data_structures/mmr/mmr.cairo @@ -49,10 +49,12 @@ impl MMRImpl of MMRTrait { self.last_pos += 1; - // TODO: why number of new peaks is equal to number of trailing ones - let new_peaks_count = trailing_ones(leaf_count); - let mut preserved_peaks = peaks.slice(0, peaks_count - new_peaks_count); - let mut merged_peaks = peaks.slice(peaks_count - new_peaks_count, new_peaks_count); + // number of new nodes = trailing_ones(leaf_count) + // explanation: https://mmr.herodotus.dev/append + let no_merged_peaks = trailing_ones(leaf_count); + let no_preserved_peaks = peaks_count - no_merged_peaks; + let mut preserved_peaks = peaks.slice(0, no_preserved_peaks); + let mut merged_peaks = peaks.slice(no_preserved_peaks, no_merged_peaks); let mut last_peak = hash; loop { From 14604c03f3999b474993f8929cd1bae113cf114f Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Mon, 6 May 2024 16:35:04 +0200 Subject: [PATCH 33/39] Fmt --- src/data_structures/tests/test_eth_mpt.cairo | 2 ++ src/hashing/poseidon.cairo | 1 + 2 files changed, 3 insertions(+) diff --git a/src/data_structures/tests/test_eth_mpt.cairo b/src/data_structures/tests/test_eth_mpt.cairo index e2a4d53..48bc167 100644 --- a/src/data_structures/tests/test_eth_mpt.cairo +++ b/src/data_structures/tests/test_eth_mpt.cairo @@ -314,6 +314,8 @@ fn test_hash_rlp_node() { fn test_full_verify() { //// Account : 0x7b5C526B7F8dfdff278b4a3e045083FBA4028790 | Goerli | Block 9000000 //// + //// + //// ////"0xf90211a0f09eae4e1e51fdde02a2884e285b8a8a9c72cc7e7cdaeef013714e3499bcd475a0ce33fd7097055e50d64c42759027e41ffb22d5b2a03ee67207dc94b547e40956a04817bf75497b71a78957ff89d05107cbf16ead02f7e68f13cead9e7d24dfcda5a00751841dcd0e21ff273930aa4722cabae7ea4e09d0f4e9f667b57ab68a41652ea047008ee2caeec1839c016d0a8efd2e901091bfae5388fc064db9f14f4bda362da0f952be9637ec6790bcdcf9ae3d4bca607259f26c0731e3cbd2882924c9db5653a061a8882bde126643739fe5f0acc5d234467718c27217f56513fd222009802336a061dbaa68a4290e8cce57166ffc6fd22d081c5893a081082b23668ef6c7d65c81a0ef2e0aea160700e14be7285c8b83535f4d104a74ac8db6c188d84ee48a8a647ca0c00853c7500db3c616d5d7dcd7503c02307045e7670a0749ffdebadc732a9ab4a068050da8f891b57fbeacffe4ba3e41f11c5d6b0ec8553fbb796f46951ecd1445a0762e36c38c548c5ae61da51205ef1dc66390702397becef53c50d969cae7a2ada0abff9de80f8e14979ebbe80ae3e702e61b31b91ea481c0e63a7bde12e866eeb5a017220448de88495fdf81446233768ef9441058e4602ecafc1da85a7cbbf1c16da084351381e6cad5052c82f731e8d19d86193794eccdf274529bed7e67309cca78a0784e83133c0ba8ff0262d0c96dc93f936d97eac46327d32f3c1baceb63934d9d80", ////"0xf90211a0679ec41f2230e1f57eededf17732966880d9835d744ad769a1b246829341a588a09c2941acea1f1461a7d0af3bb9917c16e3a3339556623a6cad6d1f40ee8fc8a8a0211b79624826f8cd651f7a0123356cfef56854adf7285163988ba4eee0e8f964a062a3e341692078b717029cd105b462749386aecc1cb647721cc382872eac4a51a01a9fc7658bcc2948e1123273e83fb66894e64c2e19aa8f3ac546c45ef4b22290a08c5cdf2e341821e9c3163ec52847e33884f4797669607a60a8adafd00edead0ea08b07046b12762a58e03a482d597cca869aaffd85214bbd08c4624325a7cf80c9a0602d16a56550182218f642f56e66b1cf72555c38dac0fd061b8ef113d4653f4aa038fa2d962cfe43eb49f5a7d1787a436e8e3c6858665b1b0703c4e42ad43f962ea03a706c9b0e0757079f96d9df003eae31aafcf7525d6114033ee929c78adc580aa0f8a66bdc97088d4c73429b9ee00d7bcd0589be3462a53e9f5b9876d4b5231e40a04bbcbff81f2c0b65f29724ef71f6d439b6f857ad5fa7b643c1ea5dfc72ee240fa098cd5bf5aea320986616ff7bfc78efdf43091610fb457447058958e68a13e49ba02033016a2ef0512c926211fbec6f9b7398c58ae10116901d086905979649d968a07cf13191df973971dd4592a95c33cc3c248a4e919b8866c7c8ecff44a6e453a2a017010b7ff49cf72fbc13136f189457a2bc09e8c400ca9cb7997ff75bf34637ec80", ////"0xf90211a06c86eef3a8754d952a265132ed86282d34ec24f9f5aa2f085629155798975c2ba02cef7b79076bddfe220fd88ca1a18414683b2f37e8b94cf8c6bdf743cff9d18ca00fe6e40f33bc1b76e34a6c92cdfce9b08f808bca7c6fe85348a7b7e838632a20a0f2f4d7b6fb649794a45288bfbc32274f3031ebc8bbf808146a3104ea27d72e7aa0cf2a353bb32b3b9c004c7586d6723ea5e2ccb99972c84b4b2c90166ca0c3c65ba0753fee595b7a0b80d3574db4e4615a599da7bedfa71bcb9ba214192c6dfb8a4fa0a7e45064974417eeef5556e4fa3533c819cc04825a33f0e244440d4d6a42828ca06cb2eaf789a62824a4a2b730bc4b8ee70e3648fbdf6cd61ea86d234dda67ccc3a0e42e79aeed163de73664c3b4f9451208b22b4874eae6c007b5f0405a64a55050a072f87da9fbd3c727080eef39891888876cdfbc54b732cf4f08ee19d067117d5ba0ce88e695612d636a6f73c2fcc0086e486249a0284cd6b88bf1cc3a7bef84ce9ba0d408599a558fc0ad84aba0bea36e00c1e99ddebf7b74e2f68912563bd5a62522a0154aef7de9275d13e860b11f138a811e6ed97fbf8985c524ccd5c231dbc62180a0282022799ec74b1dc2df4edad584b0b974113a06a857ff72ed17471908d28404a00c4fc7a3f7ded56f4da7093a875bd7c3a6cb35cc6bdf505cb79aee64708640aca0339829e86f4b7a2d68fe0b4707e32016534cdf8a48070a3921a48bcc0fd4b11c80", diff --git a/src/hashing/poseidon.cairo b/src/hashing/poseidon.cairo index f069010..0b09d72 100644 --- a/src/hashing/poseidon.cairo +++ b/src/hashing/poseidon.cairo @@ -20,6 +20,7 @@ fn hash_words64(words: Words64) -> felt252 { } // Permutation params: +// // // https://docs.starknet.io/documentation/architecture_and_concepts/Cryptography/hash-functions/#poseidon_hash impl PoseidonHasher of Hasher { From 8b117d7726421db907db84255d923f69b9475356 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Mon, 6 May 2024 16:38:36 +0200 Subject: [PATCH 34/39] Remove formatting check from CI --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4df574..4a75736 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,5 +9,4 @@ jobs: steps: - uses: actions/checkout@v3 - uses: software-mansion/setup-scarb@v1 - - run: scarb fmt --check - run: scarb test From 52b8b2e1f85bb76a2465d331fb1550d3de9b169c Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Thu, 9 May 2024 14:16:24 +0200 Subject: [PATCH 35/39] Add rlp decode test which fails --- src/encoding/tests/test_rlp.cairo | 95 +++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/src/encoding/tests/test_rlp.cairo b/src/encoding/tests/test_rlp.cairo index e797c4f..b55b3c0 100644 --- a/src/encoding/tests/test_rlp.cairo +++ b/src/encoding/tests/test_rlp.cairo @@ -1,5 +1,100 @@ use cairo_lib::encoding::rlp::{RLPItem, rlp_decode, rlp_decode_list_lazy}; +#[test] +#[available_gas(99999999)] +fn test_rlp_decode_list_lazy() { + let header = array![ + 0xe8b262aba04d02f9, + 0xe92b43b7269adf2c, + 0x3219c99e0e62e108, + 0xdbd718a65e62ee2c, + 0x4dcc1da0a7025592, + 0xb585ab7a5dc7dee8, + 0x4512d31ad4ccb667, + 0x42a1f013748a941b, + 0x76e2944793d440fd, + 0xb392877a528a37bc, + 0x3d26535e5bcacd53, + 0xad8845282ba09efb, + 0xadd0ff882e102608, + 0x9377fd4a5e390028, + 0xa12a5b983f7494f6, + 0xbb0912aa0f040a8, + 0x908c7c096ebb3e55, + 0xaf8972d156174118, + 0x2052c8ee7d4dfcdd, + 0x30074ba0dd8f0136, + 0x29fbe705757dfc39, + 0xd5bf3e2a18cd7521, + 0xa4236967d566a919, + 0x1b96afce6f5b3, + 0x83c162e3cf68b4e0, + 0x603d92a232041c65, + 0x4205289502249be4, + 0xa953375766002824, + 0x27022be1a1d03a26, + 0x865184121ec29456, + 0x4a736a83a546402b, + 0xa4e6d4212124090, + 0x1bb1bcd95a562041, + 0xc2880c131ac690c6, + 0x476d4591ed464a24, + 0x1152501072246a61, + 0x1feac0a84a852d4, + 0xca4d0015481089ac, + 0x48845640f0680802, + 0x623498201b6e6803, + 0x5690f81f6811e4e6, + 0x10fa5240ab1144a4, + 0xa5908e488386b004, + 0x289c44caa01415f, + 0x212636b0ea269a42, + 0xb4e051a1cb078fb, + 0xc20040d091026f98, + 0x1b42e2a412c0c6a8, + 0xa060389a66bb9413, + 0x48eb16f89694bb01, + 0x46666b03c62101c0, + 0x6ced8022d21396c2, + 0x528930242007d84a, + 0x2612953852d414a9, + 0xde22b54e141e508b, + 0x2432452d008887e, + 0xc901846371598380, + 0x848e9a1c018480c3, + 0x8987a08080923b66, + 0xb70ac31dd828882, + 0xbe0d1998c187602b, + 0x84e021ad60db368a, + 0x886e92cd1e5cf7, + 0x8500000000000000, + 0xe271a0cd0fefcb08, + 0xb13f931db22a2c83, + 0x23f9cf41e107c409, + 0xccfd4c39f2afd5a7, + 0xc837cf063a840dc, + 0xa000009805840000, + 0xcb1b14988ae5328c, + 0xa2ab435d3495032, + 0x38e31c987d75b08b, + 0xbba3bdee6b8da852 + ].span(); + let (decoded_rlp, _) = rlp_decode_list_lazy( + header, + array![8, 11].span() + ) + .unwrap(); + let ((block_number, block_number_byte_len), (timestamp, timestamp_byte_len)) = + match decoded_rlp { + RLPItem::Bytes(_) => panic_with_felt252('Invalid header rlp'), + RLPItem::List(l) => { (*l.at(0), *l.at(1)) }, + }; + let block0 = *block_number.at(0); + let time0 = *timestamp.at(0); + assert(block0 == 0x637159, 'invalid block0'); + assert(time0 == 0x80923b66, 'invalid time0'); +} + #[test] #[available_gas(99999999)] fn test_rlp_decode_string() { From b0f93a15f09b5b9962d4dc45ddfdb05f9790291a Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Thu, 9 May 2024 15:23:36 +0200 Subject: [PATCH 36/39] Fix slice_le bug --- 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 bbeebda..aad5b50 100644 --- a/src/utils/types/words64.cairo +++ b/src/utils/types/words64.cairo @@ -138,7 +138,7 @@ impl Words64Impl of Words64Trait { i += 1; }; if (n_ending_bytes != 0) { - let last_word: u64 = *self.at(start_index + q) / (pow2(8 * n_ending_bytes)).into(); + let last_word: u64 = *self.at(start_index + q) % (pow2(8 * n_ending_bytes)).into(); output.append(last_word); return output.span(); } From bc453f981c6ce2753543474a2492ecac03c36ba4 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Mon, 3 Jun 2024 16:01:32 +0200 Subject: [PATCH 37/39] Change MMR Size type to u128 --- src/data_structures/mmr/mmr.cairo | 21 ++++++++------ src/data_structures/mmr/peaks.cairo | 9 +++--- src/data_structures/mmr/proof.cairo | 17 ++++++----- .../mmr/tests/test_peaks.cairo | 5 ++-- .../mmr/tests/test_utils.cairo | 2 +- src/data_structures/mmr/utils.cairo | 29 ++++++++++--------- src/encoding/tests/test_rlp.cairo | 2 +- 7 files changed, 45 insertions(+), 40 deletions(-) diff --git a/src/data_structures/mmr/mmr.cairo b/src/data_structures/mmr/mmr.cairo index 246a658..e962560 100644 --- a/src/data_structures/mmr/mmr.cairo +++ b/src/data_structures/mmr/mmr.cairo @@ -6,11 +6,14 @@ use cairo_lib::data_structures::mmr::utils::{ }; use cairo_lib::hashing::poseidon::PoseidonHasher; +type MmrElement = felt252; +type MmrSize = u128; + // @notice Merkle Mountatin Range struct #[derive(Drop, Clone, Serde, starknet::Store)] struct MMR { - root: felt252, - last_pos: usize + root: MmrElement, + last_pos: MmrSize } impl MMRDefault of Default { @@ -28,7 +31,7 @@ impl MMRImpl of MMRTrait { // @param last_pos The last position in the MMR // @return MMR with the given root and last_pos #[inline(always)] - fn new(root: felt252, last_pos: usize) -> MMR { + fn new(root: MmrElement, last_pos: MmrSize) -> MMR { MMR { root, last_pos } } @@ -36,9 +39,9 @@ impl MMRImpl of MMRTrait { // @param hash The hashed element to append // @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()); - let peaks_count = peaks.len(); + fn append(ref self: MMR, hash: MmrElement, peaks: Peaks) -> Result<(MmrElement, Peaks), felt252> { + let leaf_count = mmr_size_to_leaf_count(self.last_pos); + let peaks_count= peaks.len(); if leaf_count_to_peaks_count(leaf_count) != peaks_count.into() { return Result::Err('Invalid peaks count'); @@ -74,7 +77,7 @@ impl MMRImpl of MMRTrait { }; new_peaks.append(last_peak); - let new_root = compute_root(self.last_pos.into(), new_peaks.span()); + let new_root = compute_root(self.last_pos, new_peaks.span()); self.root = new_root; Result::Ok((new_root, new_peaks.span())) @@ -87,9 +90,9 @@ impl MMRImpl of MMRTrait { // @param proof The proof for the element // @return Result with true if the proof is valid, false otherwise fn verify_proof( - self: @MMR, index: usize, hash: felt252, peaks: Peaks, proof: Proof + self: @MMR, index: MmrSize, hash: MmrElement, peaks: Peaks, proof: Proof ) -> Result { - let leaf_count = mmr_size_to_leaf_count((*self.last_pos).into()); + let leaf_count = mmr_size_to_leaf_count(*self.last_pos); if leaf_count_to_peaks_count(leaf_count) != peaks.len().into() { return Result::Err('Invalid peaks count'); } diff --git a/src/data_structures/mmr/peaks.cairo b/src/data_structures/mmr/peaks.cairo index 1fbbae8..91b4b20 100644 --- a/src/data_structures/mmr/peaks.cairo +++ b/src/data_structures/mmr/peaks.cairo @@ -1,15 +1,16 @@ use cairo_lib::hashing::poseidon::PoseidonHasher; use cairo_lib::data_structures::mmr::utils::compute_root; +use cairo_lib::data_structures::mmr::mmr::{MmrSize, MmrElement}; use cairo_lib::utils::array::span_contains; // @notice Represents the peaks of the MMR -type Peaks = Span; +type Peaks = Span; #[generate_trait] impl PeaksImpl of PeaksTrait { // @notice Bags the peaks (hashing them together) // @return The bagged peaks - fn bag(self: Peaks) -> felt252 { + fn bag(self: Peaks) -> MmrElement { if self.is_empty() { return 0; } @@ -35,8 +36,8 @@ impl PeaksImpl of PeaksTrait { // @param last_pos The last position in the MMR // @param root The root of the MMR // @return True if the peaks are valid - fn valid(self: Peaks, last_pos: usize, root: felt252) -> bool { - let computed_root = compute_root(last_pos.into(), self); + fn valid(self: Peaks, last_pos: MmrSize, root: MmrElement) -> bool { + let computed_root = compute_root(last_pos, self); computed_root == root } } diff --git a/src/data_structures/mmr/proof.cairo b/src/data_structures/mmr/proof.cairo index 411eb3e..2f85dfe 100644 --- a/src/data_structures/mmr/proof.cairo +++ b/src/data_structures/mmr/proof.cairo @@ -1,4 +1,5 @@ use cairo_lib::hashing::poseidon::PoseidonHasher; +use cairo_lib::data_structures::mmr::mmr::{MmrSize, MmrElement}; use cairo_lib::data_structures::mmr::utils::get_height; use cairo_lib::utils::bitwise::{left_shift, bit_length}; use cairo_lib::utils::math::pow; @@ -13,24 +14,24 @@ impl ProofImpl of ProofTrait { // @param index Index of the element to start from // @param hash Hash of the element to start from // @return The root of the subtree - fn compute_peak(self: Proof, index: usize, hash: felt252) -> felt252 { + fn compute_peak(self: Proof, index: MmrSize, hash: MmrElement) -> felt252 { // calculate direction array // direction[i] - whether the i-th node from the root is a left or a right child of its // parent - let mut bits = bit_length(index); - if self.len() + 1 > bits { - bits = self.len() + 1; + let mut bits: MmrSize = bit_length(index); + if self.len().into() + 1 > bits { + bits = self.len().into() + 1; }; let mut direction: Array = ArrayTrait::new(); - let mut p: usize = 1; - let mut q: usize = pow(2, bits) - 1; + let mut p: MmrSize = 1; + let mut q: MmrSize = pow(2, bits) - 1; loop { if p >= q { break (); } - let m: usize = (p + q) / 2; + let m: MmrSize = (p + q) / 2; if index < m { q = m - 1; @@ -47,7 +48,7 @@ impl ProofImpl of ProofTrait { let mut current_hash = hash; let mut i: usize = 0; - let mut two_pow_i: usize = 2; + let mut two_pow_i: MmrSize = 2; loop { if i == self.len() { break current_hash; diff --git a/src/data_structures/mmr/tests/test_peaks.cairo b/src/data_structures/mmr/tests/test_peaks.cairo index d2c0484..5d4489f 100644 --- a/src/data_structures/mmr/tests/test_peaks.cairo +++ b/src/data_structures/mmr/tests/test_peaks.cairo @@ -48,8 +48,7 @@ fn test_valid() { let bag = peaks.span().bag(); let last_pos = 923048; - let last_pos_u32 = 923048_u32; - let root = PoseidonHasher::hash_double(last_pos, bag); + let root = PoseidonHasher::hash_double(last_pos.into(), bag); - assert(peaks.span().valid(last_pos_u32, root), 'Valid'); + assert(peaks.span().valid(last_pos, root), 'Valid'); } diff --git a/src/data_structures/mmr/tests/test_utils.cairo b/src/data_structures/mmr/tests/test_utils.cairo index 4a80033..dfac494 100644 --- a/src/data_structures/mmr/tests/test_utils.cairo +++ b/src/data_structures/mmr/tests/test_utils.cairo @@ -27,7 +27,7 @@ fn test_compute_root() { let bag = peaks.span().bag(); let last_pos = 923048; - let root = PoseidonHasher::hash_double(last_pos, bag); + let root = PoseidonHasher::hash_double(last_pos.into(), bag); let computed_root = compute_root(last_pos, peaks.span()); assert(root == computed_root, 'Roots not matching'); diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index ae4a218..a4369cf 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -1,12 +1,13 @@ use cairo_lib::utils::bitwise::{bit_length, left_shift}; use cairo_lib::utils::math::pow; use cairo_lib::hashing::poseidon::PoseidonHasher; +use cairo_lib::data_structures::mmr::mmr::{MmrSize, MmrElement}; use cairo_lib::data_structures::mmr::peaks::{Peaks, PeaksTrait}; // @notice Computes the height of a node in the MMR // @param index The index of the node // @return The height of the node -fn get_height(index: usize) -> usize { +fn get_height(index: MmrSize) -> MmrSize { let bits = bit_length(index); let ones = pow(2, bits) - 1; @@ -22,15 +23,15 @@ fn get_height(index: usize) -> usize { // @param last_pos The position of the last node in the MMR // @param peaks The peaks of the MMR // @return The root of the MMR -fn compute_root(last_pos: felt252, peaks: Peaks) -> felt252 { +fn compute_root(last_pos: MmrSize, peaks: Peaks) -> MmrElement { let bag = peaks.bag(); - PoseidonHasher::hash_double(last_pos, bag) + PoseidonHasher::hash_double(last_pos.into(), bag) } // @notice Count the number of bits set to 1 in an unsigned integer -// @param arg The usize (u32) unsigned integer +// @param arg The u128 unsigned integer // @return The number of bits set to 1 in n -fn count_ones(n: usize) -> usize { +fn count_ones(n: MmrSize) -> usize { let mut n = n; let mut count = 0; loop { @@ -47,8 +48,8 @@ fn count_ones(n: usize) -> usize { // @return The MMR index // Explanation of why this formula is correct // https://mmr.herodotus.dev/mmr-size-vs-leaf-count#leaf-count-to-mmr-size-algorithm -fn leaf_index_to_mmr_index(n: usize) -> usize { - 2 * n - 1 - count_ones(n - 1) +fn leaf_index_to_mmr_index(n: MmrSize) -> MmrSize { + 2 * n - 1 - count_ones(n - 1).into() } // @notice Convert a Merkle Mountain Range tree size to number of leaves @@ -56,7 +57,7 @@ fn leaf_index_to_mmr_index(n: usize) -> usize { // @result Number of leaves // Explanation of why this algorithm is correct // https://mmr.herodotus.dev/mmr-size-vs-leaf-count#mmr-size-to-leaf-count-algorithm -fn mmr_size_to_leaf_count(n: usize) -> usize { +fn mmr_size_to_leaf_count(n: MmrSize) -> MmrSize { let mut mmr_size = n; let bits = bit_length(mmr_size + 1); let mut mountain_leaf_count = pow(2, bits - 1); @@ -78,19 +79,19 @@ fn mmr_size_to_leaf_count(n: usize) -> usize { // @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: usize) -> usize { +fn leaf_count_to_peaks_count(leaf_count: MmrSize) -> usize { count_ones(leaf_count) } // @notice Get the number of trailing ones in the binary representation of a number // @param n The number // @return Number of trailing ones -fn trailing_ones(n: usize) -> usize { +fn trailing_ones(n: MmrSize) -> usize { let mut n = n; let mut count = 0; loop { let (halfed, rem) = DivRem::div_rem( - n, TryInto::>::try_into(2).unwrap() + n, TryInto::>::try_into(2).unwrap() ); if rem == 0 { break count; @@ -104,12 +105,12 @@ fn trailing_ones(n: usize) -> usize { // @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: usize, element_index: usize) -> (usize, usize) { +fn get_peak_info(elements_count: MmrSize, element_index: MmrSize) -> (usize, usize) { 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_height: usize = bit_length(elements_count).try_into().unwrap(); + let mut mountain_elements_count: MmrSize = pow(2, mountain_height.into()) - 1; let mut mountain_index = 0; loop { if mountain_elements_count <= elements_count { diff --git a/src/encoding/tests/test_rlp.cairo b/src/encoding/tests/test_rlp.cairo index b55b3c0..1397454 100644 --- a/src/encoding/tests/test_rlp.cairo +++ b/src/encoding/tests/test_rlp.cairo @@ -84,7 +84,7 @@ fn test_rlp_decode_list_lazy() { array![8, 11].span() ) .unwrap(); - let ((block_number, block_number_byte_len), (timestamp, timestamp_byte_len)) = + let ((block_number, _block_number_byte_len), (timestamp, _timestamp_byte_len)) = match decoded_rlp { RLPItem::Bytes(_) => panic_with_felt252('Invalid header rlp'), RLPItem::List(l) => { (*l.at(0), *l.at(1)) }, From 0db686d55a4f52835c2127539953178bd01e19f3 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 7 Jun 2024 17:04:32 +0200 Subject: [PATCH 38/39] Change Mmr Size to u256 --- src/data_structures/mmr/mmr.cairo | 2 +- src/data_structures/mmr/tests/test_peaks.cairo | 4 ++-- src/data_structures/mmr/tests/test_utils.cairo | 4 ++-- src/data_structures/mmr/utils.cairo | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/data_structures/mmr/mmr.cairo b/src/data_structures/mmr/mmr.cairo index e962560..d832956 100644 --- a/src/data_structures/mmr/mmr.cairo +++ b/src/data_structures/mmr/mmr.cairo @@ -7,7 +7,7 @@ use cairo_lib::data_structures::mmr::utils::{ use cairo_lib::hashing::poseidon::PoseidonHasher; type MmrElement = felt252; -type MmrSize = u128; +type MmrSize = u256; // @notice Merkle Mountatin Range struct #[derive(Drop, Clone, Serde, starknet::Store)] diff --git a/src/data_structures/mmr/tests/test_peaks.cairo b/src/data_structures/mmr/tests/test_peaks.cairo index 5d4489f..d2927b0 100644 --- a/src/data_structures/mmr/tests/test_peaks.cairo +++ b/src/data_structures/mmr/tests/test_peaks.cairo @@ -48,7 +48,7 @@ fn test_valid() { let bag = peaks.span().bag(); let last_pos = 923048; - let root = PoseidonHasher::hash_double(last_pos.into(), bag); + let root = PoseidonHasher::hash_double(last_pos, bag); - assert(peaks.span().valid(last_pos, root), 'Valid'); + assert(peaks.span().valid(last_pos.into(), root), 'Valid'); } diff --git a/src/data_structures/mmr/tests/test_utils.cairo b/src/data_structures/mmr/tests/test_utils.cairo index dfac494..b7d85c5 100644 --- a/src/data_structures/mmr/tests/test_utils.cairo +++ b/src/data_structures/mmr/tests/test_utils.cairo @@ -27,8 +27,8 @@ fn test_compute_root() { let bag = peaks.span().bag(); let last_pos = 923048; - let root = PoseidonHasher::hash_double(last_pos.into(), bag); - let computed_root = compute_root(last_pos, peaks.span()); + let root = PoseidonHasher::hash_double(last_pos, bag); + let computed_root = compute_root(last_pos.into(), peaks.span()); assert(root == computed_root, 'Roots not matching'); } diff --git a/src/data_structures/mmr/utils.cairo b/src/data_structures/mmr/utils.cairo index a4369cf..a1c7859 100644 --- a/src/data_structures/mmr/utils.cairo +++ b/src/data_structures/mmr/utils.cairo @@ -25,7 +25,7 @@ fn get_height(index: MmrSize) -> MmrSize { // @return The root of the MMR fn compute_root(last_pos: MmrSize, peaks: Peaks) -> MmrElement { let bag = peaks.bag(); - PoseidonHasher::hash_double(last_pos.into(), bag) + PoseidonHasher::hash_double(last_pos.try_into().unwrap(), bag) } // @notice Count the number of bits set to 1 in an unsigned integer From f1d8d57490190fff131c66a7debdabd82f23e6c3 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Thu, 20 Jun 2024 10:04:02 +0200 Subject: [PATCH 39/39] Remove unnecessary if check from slice_le --- src/utils/types/words64.cairo | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/utils/types/words64.cairo b/src/utils/types/words64.cairo index aad5b50..1b7c3c6 100644 --- a/src/utils/types/words64.cairo +++ b/src/utils/types/words64.cairo @@ -98,9 +98,6 @@ impl Words64Impl of Words64Trait { // start: 5 | len: 17 // output: [0x3295abcdef123456, 0x2576758493478594, 0x54] fn slice_le(self: Words64, start: usize, len: usize) -> Words64 { - if len == 0 { - return ArrayTrait::new().span(); - } let (q, n_ending_bytes) = DivRem::div_rem( len, TryInto::>::try_into(8).unwrap() );