diff --git a/packages/utreexo/src/stump/accumulator.cairo b/packages/utreexo/src/stump/accumulator.cairo index 9be0c3c9..a86dd8f7 100644 --- a/packages/utreexo/src/stump/accumulator.cairo +++ b/packages/utreexo/src/stump/accumulator.cairo @@ -9,7 +9,7 @@ pub impl StumpUtreexoAccumulatorImpl of StumpUtreexoAccumulator { // https://github.com/mit-dci/rustreexo/blob/6a8fe53fa545df8f1a30733fa6ca9f7b0077d051/src/accumulator/stump.rs#L252 *self } - + /// Verifies that specified leaves are part of the utreexo forest given a proof. fn verify( self: @UtreexoStumpState, proof: @UtreexoBatchProof, leaves: Span @@ -18,14 +18,15 @@ pub impl StumpUtreexoAccumulatorImpl of StumpUtreexoAccumulator { let mut res = Result::Ok(()); while let Option::Some(computed_root) = computed_roots.pop_front() { let mut root_exists = false; - for root_opt in *self.roots { - if let Option::Some(root) = *root_opt { - if root == computed_root { - root_exists = true; - break; + for root_opt in *self + .roots { + if let Option::Some(root) = *root_opt { + if root == computed_root { + root_exists = true; + break; + } } - } - }; + }; if !root_exists { res = Result::Err("Proof verification failed"); break; @@ -42,19 +43,20 @@ pub impl StumpUtreexoAccumulatorImpl of StumpUtreexoAccumulator { let mut updated_roots = proof.compute_roots_with_deletion(leaves, *self.num_leaves)?; let mut roots = array![]; - for root_opt in *self.roots { - if let Option::Some(root) = root_opt { - if let Option::Some(updated_root) = updated_roots.get(0) { - let (old_root, new_root) = updated_root.unbox(); - if *root == *old_root { - updated_roots.pop_front().unwrap(); - roots.append(*new_root); - continue; + for root_opt in *self + .roots { + if let Option::Some(root) = root_opt { + if let Option::Some(updated_root) = updated_roots.get(0) { + let (old_root, new_root) = updated_root.unbox(); + if *root == *old_root { + updated_roots.pop_front().unwrap(); + roots.append(*new_root); + continue; + } } } - } - roots.append(*root_opt); - }; + roots.append(*root_opt); + }; if !updated_roots.is_empty() { return Result::Err("Proof verification failed"); @@ -67,7 +69,8 @@ pub impl StumpUtreexoAccumulatorImpl of StumpUtreexoAccumulator { fn verify_legacy( self: @UtreexoStumpState, proof: @UtreexoBatchProof, del_hashes: Span ) -> Result<(), ByteArray> { - let computed_roots: Span = proof.compute_roots_legacy(del_hashes, *self.num_leaves)?; + let computed_roots: Span = proof + .compute_roots_legacy(del_hashes, *self.num_leaves)?; let mut number_matched_roots: u32 = 0; for i in 0 @@ -598,7 +601,7 @@ mod tests { Result::Ok(()) ); } - + #[test] fn test_deletion_1_1() { let state = UtreexoStumpState { roots: array![Option::Some('a')].span(), num_leaves: 1 }; @@ -607,7 +610,8 @@ mod tests { let leaves = array![]; - let new_state = state.verify_and_delete(@proof, leaves.span()) + let new_state = state + .verify_and_delete(@proof, leaves.span()) .expect('Verification failed'); assert_eq!(new_state.num_leaves, 1); @@ -622,7 +626,8 @@ mod tests { let leaves = array!['a']; - let new_state = state.verify_and_delete(@proof, leaves.span()) + let new_state = state + .verify_and_delete(@proof, leaves.span()) .expect('Verification failed'); assert_eq!(new_state.num_leaves, 1); @@ -656,7 +661,8 @@ mod tests { let leaves = array!['a']; - let new_state = state.verify_and_delete(@proof, leaves.span()) + let new_state = state + .verify_and_delete(@proof, leaves.span()) .expect('Verification failed'); assert_eq!(new_state.num_leaves, 8); @@ -700,7 +706,8 @@ mod tests { let leaves = array!['a', 'b']; - let new_state = state.verify_and_delete(@proof, leaves.span()) + let new_state = state + .verify_and_delete(@proof, leaves.span()) .expect('Verification failed'); assert_eq!(new_state.num_leaves, 8); @@ -736,15 +743,15 @@ mod tests { let proof = UtreexoBatchProof { targets: array![0, 1, 2].span(), proof: array![ - 'd', - 1970675917964935639615849678644334216784892342767290630432190461589093258001 + 'd', 1970675917964935639615849678644334216784892342767290630432190461589093258001 ] .span() }; let leaves = array!['a', 'b', 'c']; - let new_state = state.verify_and_delete(@proof, leaves.span()) + let new_state = state + .verify_and_delete(@proof, leaves.span()) .expect('Verification failed'); assert_eq!(new_state.num_leaves, 8); @@ -783,7 +790,8 @@ mod tests { let leaves = array!['a', 'b', 'c', 'd', 'e', 'f', 'g']; - let new_state = state.verify_and_delete(@proof, leaves.span()) + let new_state = state + .verify_and_delete(@proof, leaves.span()) .expect('Verification failed'); assert_eq!(new_state.num_leaves, 8); @@ -814,7 +822,8 @@ mod tests { let leaves = array!['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; - let new_state = state.verify_and_delete(@proof, leaves.span()) + let new_state = state + .verify_and_delete(@proof, leaves.span()) .expect('Verification failed'); assert_eq!(new_state.num_leaves, 8); @@ -842,15 +851,15 @@ mod tests { let proof = UtreexoBatchProof { targets: array![0].span(), proof: array![ - 'b', - 1702961261074558847535372708423978610134065667337563473891781271138689292959 + 'b', 1702961261074558847535372708423978610134065667337563473891781271138689292959 ] .span() }; let leaves = array!['a']; - let new_state = state.verify_and_delete(@proof, leaves.span()) + let new_state = state + .verify_and_delete(@proof, leaves.span()) .expect('Verification failed'); assert_eq!(new_state.num_leaves, 7); @@ -886,13 +895,13 @@ mod tests { }; let proof = UtreexoBatchProof { - targets: array![0, 2, 4, 6].span(), - proof: array!['b', 'd', 'f'].span() + targets: array![0, 2, 4, 6].span(), proof: array!['b', 'd', 'f'].span() }; let leaves = array!['a', 'c', 'e', 'g']; - let new_state = state.verify_and_delete(@proof, leaves.span()) + let new_state = state + .verify_and_delete(@proof, leaves.span()) .expect('Verification failed'); assert_eq!(new_state.num_leaves, 7); @@ -926,13 +935,13 @@ mod tests { }; let proof = UtreexoBatchProof { - targets: array![1, 3, 5].span(), - proof: array!['a', 'c', 'e'].span() + targets: array![1, 3, 5].span(), proof: array!['a', 'c', 'e'].span() }; let leaves = array!['b', 'd', 'f']; - let new_state = state.verify_and_delete(@proof, leaves.span()) + let new_state = state + .verify_and_delete(@proof, leaves.span()) .expect('Verification failed'); assert_eq!(new_state.num_leaves, 7); @@ -968,15 +977,15 @@ mod tests { let proof = UtreexoBatchProof { targets: array![0, 6].span(), proof: array![ - 'b', - 1702961261074558847535372708423978610134065667337563473891781271138689292959 + 'b', 1702961261074558847535372708423978610134065667337563473891781271138689292959 ] .span() }; let leaves = array!['a', 'g']; - let new_state = state.verify_and_delete(@proof, leaves.span()) + let new_state = state + .verify_and_delete(@proof, leaves.span()) .expect('Verification failed'); assert_eq!(new_state.num_leaves, 7); @@ -1011,13 +1020,12 @@ mod tests { num_leaves: 7 }; - let proof = UtreexoBatchProof { - targets: array![4, 5, 6].span(), proof: array![].span() - }; + let proof = UtreexoBatchProof { targets: array![4, 5, 6].span(), proof: array![].span() }; let leaves = array!['e', 'f', 'g']; - let new_state = state.verify_and_delete(@proof, leaves.span()) + let new_state = state + .verify_and_delete(@proof, leaves.span()) .expect('Verification failed'); assert_eq!(new_state.num_leaves, 7); @@ -1056,13 +1064,14 @@ mod tests { let leaves = array!['a', 'b', 'c', 'd', 'e', 'f', 'g']; - let new_state = state.verify_and_delete(@proof, leaves.span()) + let new_state = state + .verify_and_delete(@proof, leaves.span()) .expect('Verification failed'); assert_eq!(new_state.num_leaves, 7); assert_eq!(new_state.roots, array![Option::None, Option::None, Option::None].span()); } - + #[test] fn test_deletion_7_7() { let state = UtreexoStumpState { @@ -1084,15 +1093,15 @@ mod tests { let proof = UtreexoBatchProof { targets: array![0].span(), proof: array![ - 'b', - 1702961261074558847535372708423978610134065667337563473891781271138689292959 + 'b', 1702961261074558847535372708423978610134065667337563473891781271138689292959 ] .span() }; let leaves = array!['a']; - let new_state = state.verify_and_delete(@proof, leaves.span()) + let new_state = state + .verify_and_delete(@proof, leaves.span()) .expect('Verification 0 failed'); // Remove 1 @@ -1107,51 +1116,48 @@ mod tests { let leaves = array!['b']; - let new_state = new_state.verify_and_delete(@proof, leaves.span()) + let new_state = new_state + .verify_and_delete(@proof, leaves.span()) .expect('Verification 1 failed'); // Remove 2 - let proof = UtreexoBatchProof { - targets: array![8].span(), proof: array!['d'].span() - }; + let proof = UtreexoBatchProof { targets: array![8].span(), proof: array!['d'].span() }; let leaves = array!['c']; - let new_state = new_state.verify_and_delete(@proof, leaves.span()) + let new_state = new_state + .verify_and_delete(@proof, leaves.span()) .expect('Verification 2 failed'); // Remove 3 - let proof = UtreexoBatchProof { - targets: array![12].span(), proof: array![].span() - }; + let proof = UtreexoBatchProof { targets: array![12].span(), proof: array![].span() }; let leaves = array!['d']; - let new_state = new_state.verify_and_delete(@proof, leaves.span()) + let new_state = new_state + .verify_and_delete(@proof, leaves.span()) .expect('Verification 3 failed'); // Remove 4 - let proof = UtreexoBatchProof { - targets: array![4].span(), proof: array!['f'].span() - }; + let proof = UtreexoBatchProof { targets: array![4].span(), proof: array!['f'].span() }; let leaves = array!['e']; - let new_state = new_state.verify_and_delete(@proof, leaves.span()) + let new_state = new_state + .verify_and_delete(@proof, leaves.span()) .expect('Verification 4 failed'); // Remove 5 - let proof = UtreexoBatchProof { - targets: array![10].span(), proof: array![].span() - }; + let proof = UtreexoBatchProof { targets: array![10].span(), proof: array![].span() }; let leaves = array!['f']; - let new_state = new_state.verify_and_delete(@proof, leaves.span()) + let new_state = new_state + .verify_and_delete(@proof, leaves.span()) .expect('Verification 5 failed'); // Remove 6 @@ -1160,7 +1166,8 @@ mod tests { let leaves = array!['g']; - let new_state = new_state.verify_and_delete(@proof, leaves.span()) + let new_state = new_state + .verify_and_delete(@proof, leaves.span()) .expect('Verification 6 failed'); assert_eq!(new_state.num_leaves, 7); diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index 4fa8b884..3e46b4ac 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -59,14 +59,15 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { // then we take the very first row of the forest: // length of the row in the actual forest let mut row_len = num_leaves; - // length of the row in the "perfect forest" (a forest extrapolated into a single perfect tree) + // length of the row in the "perfect forest" + // (a forest extrapolated intoa single perfect tree) let mut row_cap = u64_next_power_of_two(num_leaves); // first absolute position in the row ("absolute" means within the perfect forest) let mut row_start = 0; // last absolute position in the row + 1 let mut row_end = row_cap; // we take all the targets with absolute positions in [row_start; row_end) - // and put them into a row, also converting their positions to relative, i.e. in [0; row_len) + // and put them into a row, also converting positions to relative, i.e. in [0; row_len) let mut row = extract_row(ref targets, row_start, row_end); // here we accumulate the result @@ -88,13 +89,12 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { let (next_pos, next_hash) = box.unbox(); // and if that target is exactly on the next position if *next_pos == pos + 1 { - // then they are siblings and we are able to compute their parent directly - // also, since we use relative positions, we can easily calculate position of the parent on the upper row + // then they are siblings and we are able to compute their parent + // directly. Also, since we use relative positions, we can easily + // calculate position of the parent on the upper row row.pop_front().unwrap(); next_row_computed.append((pos / 2, parent_hash(hash, *next_hash))); - } - // or if the next target is not a sibling - else { + } else { // or if the next target is not a sibling // then the sibling must be in the proof, so we take it from there if let Option::Some(proof_hash) = proof.pop_front() { // and compute the parent node @@ -104,10 +104,8 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { break; } } - } - // or if there are no more targets on the current row, and we are not at the end of the row, - // there must be a sibling in the proof - else if pos != last { + } else if pos != last { // or if there are no more targets on the current row, and we are not at the end + // of the row, there must be a sibling in the proof // so we get the sibling from the proof if let Option::Some(proof_hash) = proof.pop_front() { // and compute the parent node @@ -116,17 +114,13 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { inner_result = Result::Err("Invalid proof"); break; } - } - // or if we are at the end of the row, and the relative position is even, - // there cannot be siblings, which means it's a root - else { + } else { // or if we are at the end of the row, and the relative position is even, + // there cannot be siblings, which means it's a root // so we save the root roots.append(hash); } - } - // otherwise, if the relative position is odd, then we know for sure that - // there must be a sibling, moreover it must be in the proof - else { + } else { // otherwise, if the relative position is odd, then we know for sure that + // there must be a sibling, moreover it must be in the proof // so we take the sibling from the proof if let Option::Some(proof_hash) = proof.pop_front() { // and compute the parent node @@ -139,8 +133,8 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { }; // after we processed all the targets in the current row and computed their parents, - // we move the parents (and pending targets from the proof) to the next row and continue, - // till we reach the top root + // we move the parents (and pending targets from the proof) to the next row and + // continue, till we reach the top root // here we calculate the next row props row_len /= 2; @@ -153,20 +147,21 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { row = next_row_computed; } else { let mut next_row_targets = extract_row(ref targets, row_start, row_end); - row = if next_row_targets.is_empty() { - next_row_computed - } else if next_row_computed.is_empty() { - next_row_targets - } else { - // if both arrays are not empty, we merge them into a single sorted array - merge_sorted(ref next_row_targets, ref next_row_computed) - } + row = + if next_row_targets.is_empty() { + next_row_computed + } else if next_row_computed.is_empty() { + next_row_targets + } else { + // if both arrays are not empty, we merge them into a single sorted array + merge_sorted(ref next_row_targets, ref next_row_computed) + } } }; - // after we processed all rows of the forest, computed roots are all settled in the `roots` array, - // which is automatically ordered, btw - + // after we processed all rows of the forest, computed roots are all settled in the `roots` + // array, which is automatically ordered, btw + inner_result?; Result::Ok(roots) } @@ -175,10 +170,11 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { fn compute_roots_with_deletion( self: @UtreexoBatchProof, leaves: Span, num_leaves: u64, ) -> Result)>, ByteArray> { - // the algorithm is practically the same as in the `compute_roots`, with the only difference - we convert - // the targets into pairs (target, None), meaning (old_value, new_value), and compute parent pairs accordingly, - // so in the end we have an array of pairs of roots (old_root, new_root), where old_root can be used to verify - // inclusion and new_root can be used to update utreexo state + // the algorithm is practically the same as in the `compute_roots`, with the only difference + // - we convert the targets into pairs (target, None), meaning (old_value, new_value), and + // compute parent pairs accordingly, so in the end we have an array of pairs of roots + // (old_root, new_root), where old_root can be used to verify inclusion and new_root can be + // used to update utreexo state // hashes of leaves in the proof let mut hashes = leaves; @@ -202,19 +198,22 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { // then we take the very first row of the forest: // length of the row in the actual forest let mut row_len = num_leaves; - // length of the row in the "perfect forest" (a forest extrapolated into a single perfect tree) + // length of the row in the "perfect forest" + // (a forest extrapolated into a single perfect tree) let mut row_cap = u64_next_power_of_two(num_leaves); // first absolute position in the row ("absolute" means within the perfect forest) let mut row_start = 0; // last absolute position in the row + 1 let mut row_end = row_cap; // we take all the targets with absolute positions in [row_start; row_end) - // and put them into a row, also converting their positions to relative, i.e. in [0; row_len) + // and put them into a row, also converting positions to relative, i.e. in [0; row_len) let mut row = extract_row(ref targets, row_start, row_end); // here we accumulate the result let mut roots = array![]; - let mut inner_result: Result)>, ByteArray> = Result::Ok(array![]); + let mut inner_result: Result)>, ByteArray> = Result::Ok( + array![] + ); // we process the whole forest row by row from the bottom leaves to the top root while row_len != 0 { @@ -231,49 +230,64 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { let (next_pos, next_hash) = box.unbox(); // and if that target is exactly on the next position if *next_pos == pos + 1 { - // then they are siblings and we are able to compute their parent directly - // also, since we use relative positions, we can easily calculate position of the parent on the upper row + // then they are siblings and we are able to compute their parent + // directly. Also, since we use relative positions, we can easily + // calculate position of the parent on the upper row row.pop_front().unwrap(); next_row_computed.append((pos / 2, parent_hash_pair(hash, *next_hash))); - } - // or if the next target is not a sibling - else { + } else { // or if the next target is not a sibling // then the sibling must be in the proof, so we take it from there if let Option::Some(proof_hash) = proof.pop_front() { // and compute the parent node - next_row_computed.append((pos / 2, parent_hash_pair(hash, (*proof_hash, Option::Some(*proof_hash))))); + next_row_computed + .append( + ( + pos / 2, + parent_hash_pair( + hash, (*proof_hash, Option::Some(*proof_hash)) + ) + ) + ); } else { inner_result = Result::Err("Invalid proof"); break; } } - } - // or if there are no more targets on the current row, and we are not at the end of the row, - // there must be a sibling in the proof - else if pos != last { + } else if pos != last { // or if there are no more targets on the current row, and we are not at the end + // of the row, there must be a sibling in the proof // so we get the sibling from the proof if let Option::Some(proof_hash) = proof.pop_front() { // and compute the parent node - next_row_computed.append((pos / 2, parent_hash_pair(hash, (*proof_hash, Option::Some(*proof_hash))))); + next_row_computed + .append( + ( + pos / 2, + parent_hash_pair( + hash, (*proof_hash, Option::Some(*proof_hash)) + ) + ) + ); } else { inner_result = Result::Err("Invalid proof"); break; } - } - // or if we are at the end of the row, and the relative position is even, - // there cannot be siblings, which means it's a root - else { + } else { // or if we are at the end of the row, and the relative position is even, + // there cannot be siblings, which means it's a root // so we save the root roots.append(hash); } - } - // otherwise, if the relative position is odd, then we know for sure that - // there must be a sibling, moreover it must be in the proof - else { + } else { // otherwise, if the relative position is odd, then we know for sure that + // there must be a sibling, moreover it must be in the proof // so we take the sibling from the proof if let Option::Some(proof_hash) = proof.pop_front() { // and compute the parent node - next_row_computed.append((pos / 2, parent_hash_pair((*proof_hash, Option::Some(*proof_hash)), hash))); + next_row_computed + .append( + ( + pos / 2, + parent_hash_pair((*proof_hash, Option::Some(*proof_hash)), hash) + ) + ); } else { inner_result = Result::Err("Invalid proof"); break; @@ -282,8 +296,8 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { }; // after we processed all the targets in the current row and computed their parents, - // we move the parents (and pending targets from the proof) to the next row and continue, - // till we reach the top root + // we move the parents (and pending targets from the proof) to the next row and + // continue, till we reach the top root // here we calculate the next row props row_len /= 2; @@ -296,24 +310,25 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { row = next_row_computed; } else { let mut next_row_targets = extract_row(ref targets, row_start, row_end); - row = if next_row_targets.is_empty() { - next_row_computed - } else if next_row_computed.is_empty() { - next_row_targets - } else { - // if both arrays are not empty, we merge them into a single sorted array - merge_sorted(ref next_row_targets, ref next_row_computed) - } + row = + if next_row_targets.is_empty() { + next_row_computed + } else if next_row_computed.is_empty() { + next_row_targets + } else { + // if both arrays are not empty, we merge them into a single sorted array + merge_sorted(ref next_row_targets, ref next_row_computed) + } } }; - // after we processed all rows of the forest, computed roots are all settled in the `roots` array, - // which is automatically ordered, btw - + // after we processed all rows of the forest, computed roots are all settled in the `roots` + // array, which is automatically ordered, btw + inner_result?; Result::Ok(roots) } - + /// Legacy implementation of leaves' roots computation. fn compute_roots_legacy( self: @UtreexoBatchProof, mut del_hashes: Span, num_leaves: u64, @@ -450,8 +465,11 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { } } -/// Extracts all nodes with absolute positions in [row_start; row_end) and transforms their positions to relative -fn extract_row, +Drop>(ref nodes: Array<(u64, T)>, row_start: u64, row_end: u64) -> Array<(u64, T)> { +/// Extracts all nodes with absolute positions in [row_start; row_end) +/// and transforms their positions to relative +fn extract_row, +Drop>( + ref nodes: Array<(u64, T)>, row_start: u64, row_end: u64 +) -> Array<(u64, T)> { let mut row = array![]; while let Option::Some(box) = nodes.get(0) { let (pos, value) = box.unbox(); @@ -465,7 +483,9 @@ fn extract_row, +Drop>(ref nodes: Array<(u64, T)>, row_start: u64 } /// Merges two sorted arrays into a single sorted array -fn merge_sorted>(ref arr1: Array<(u64, T)>, ref arr2: Array<(u64, T)>) -> Array<(u64, T)> { +fn merge_sorted>( + ref arr1: Array<(u64, T)>, ref arr2: Array<(u64, T)> +) -> Array<(u64, T)> { let mut res = array![]; while let Option::Some((p1, v1)) = arr1.pop_front() { while let Option::Some(box) = arr2.get(0) { @@ -483,9 +503,11 @@ fn merge_sorted>(ref arr1: Array<(u64, T)>, ref arr2: Array<(u64, T) res } -/// Takes two nodes containing two values each: (L1, L2) and (R1, R2), and calculates the parent node, -/// that also contains two values (P1 = h(L1, R1), P2 = h(L2, R2)) -fn parent_hash_pair(left: (felt252, Option), right: (felt252, Option)) -> (felt252, Option) { +/// Takes two nodes containing two values each: (L1, L2) and (R1, R2), and calculates +/// a parent node, that also contains two values (P1 = h(L1, R1), P2 = h(L2, R2)) +fn parent_hash_pair( + left: (felt252, Option), right: (felt252, Option) +) -> (felt252, Option) { let (old_left, new_left) = left; let (old_right, new_right) = right; let old_parent = parent_hash(old_left, old_right);