From de28f67962d97d4fa8585576d5e9f70fa2a5dc97 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Thu, 3 Oct 2024 20:22:36 +0200 Subject: [PATCH 01/32] first draft verify batch --- packages/consensus/src/types/utreexo.cairo | 102 ++++++++++++++------- 1 file changed, 70 insertions(+), 32 deletions(-) diff --git a/packages/consensus/src/types/utreexo.cairo b/packages/consensus/src/types/utreexo.cairo index 99eba2f5..f3c6877a 100644 --- a/packages/consensus/src/types/utreexo.cairo +++ b/packages/consensus/src/types/utreexo.cairo @@ -27,7 +27,6 @@ //! //! Read more about utreexo: https://eprint.iacr.org/2019/611.pdf -use super::transaction::OutPoint; use super::utxo_set::UtxoSet; use utils::hash::{DigestImpl, DigestIntoU256}; use core::poseidon::PoseidonTrait; @@ -47,6 +46,32 @@ pub struct UtreexoState { pub num_leaves: u64, } +/// Utreexo inclusion proof for a single transaction output. +#[derive(Drop, Copy, Serde)] +pub struct UtreexoProof { + /// Index of the leaf in the forest, but also an encoded binary path, + /// specifying which sibling node is left and which is right. + pub leaf_index: u64, + /// List of sibling nodes required to calculate the root. + pub proof: Span, +} + +/// Utreexo inclusion proof for multiple outputs. +/// Compatible with https://github.com/utreexo/utreexo +#[derive(Drop, Copy)] +pub struct UtreexoBatchProof { + /// Indices of leaves to be deleted (ordered starting from 0, left to right). + pub targets: Span, + /// List of sibling nodes required to calculate the root. + pub proof: Span, +} + +#[derive(Drop, Copy, PartialEq, Debug)] +pub enum UtreexoError { + ProofVerificationFailed, + RootIndexOutOfBound +} + #[generate_trait] pub impl UtreexoStateImpl of UtreexoStateTrait { fn validate_and_apply( @@ -105,15 +130,10 @@ pub trait UtreexoAccumulator { /// Verifies batch proof for multiple outputs (e.g. all outputs in a block). fn verify_batch( - self: @UtreexoState, outputs: Span, proof: @UtreexoBatchProof + self: @UtreexoState, outpoints_hashes: Span, proof: @UtreexoBatchProof ) -> Result<(), UtreexoError>; } -/// https://eprint.iacr.org/2019/611.pdf page 6 - Adding and removing elements. -fn parent_hash(left: felt252, right: felt252) -> felt252 { - return PoseidonTrait::new().update_with(left).update_with(right).finalize(); -} - pub impl UtreexoAccumulatorImpl of UtreexoAccumulator { /// Adds an output from the accumulator. /// https://eprint.iacr.org/2019/611.pdf Algorithm 1 AddOne @@ -214,12 +234,48 @@ pub impl UtreexoAccumulatorImpl of UtreexoAccumulator { /// Verifies inclusion proof for multiple outputs. fn verify_batch( - self: @UtreexoState, outputs: Span, proof: @UtreexoBatchProof + self: @UtreexoState, outpoints_hashes: Span, proof: @UtreexoBatchProof ) -> Result<(), UtreexoError> { + if (*proof.targets).is_empty() { + return Result::Ok(()); + }; + + // TODO: handle error progration for compute_roots + let computed_roots: Span = compute_roots( + outpoints_hashes, *self.num_leaves, proof + ); + + let mut number_matched_roots: u32 = 0; + + for i in 0 + ..computed_roots + .len() { + for root in *self + .roots { + match root { + Option::Some(root) => { + if (computed_roots[i] == root) { + number_matched_roots += 1; + }; + }, + Option::None => {}, + }; + }; + }; + + if (computed_roots.len() != number_matched_roots && computed_roots.len() != 0) { + return Result::Err(UtreexoError::ProofVerificationFailed); + } + Result::Ok(()) } } +/// https://eprint.iacr.org/2019/611.pdf page 6 - Adding and removing elements. +fn parent_hash(left: felt252, right: felt252) -> felt252 { + return PoseidonTrait::new().update_with(left).update_with(right).finalize(); +} + /// Computes the root given a leaf, its index, and a proof. /// /// Traverses the tree from leaf to root, hashing paired nodes. @@ -240,30 +296,12 @@ fn compute_root(proof: Span, mut leaf_index: u64, mut curr_node: felt25 curr_node } -#[derive(Drop, Copy, PartialEq, Debug)] -pub enum UtreexoError { - ProofVerificationFailed, - RootIndexOutOfBound -} - -/// Utreexo inclusion proof for a single transaction output. -#[derive(Drop, Copy, Serde)] -pub struct UtreexoProof { - /// List of sibling nodes required to calculate the root. - pub proof: Span, - /// Index of the leaf in the forest, but also an encoded binary path, - /// specifying which sibling node is left and which is right. - pub leaf_index: u64, -} - -/// Utreexo inclusion proof for multiple outputs. -/// Compatible with https://github.com/utreexo/utreexo -#[derive(Drop, Copy)] -pub struct UtreexoBatchProof { - /// Indices of leaves to be deleted (ordered starting from 0, left to right). - pub targets: Span, - /// List of sibling nodes required to calculate the root. - pub proof: Span, +/// Computes a set of roots from a proof. +fn compute_roots( + outpoints_hashes: Span, num_leaves: u64, proof: @UtreexoBatchProof, +) -> Span { + // TODO + array![].span() } pub impl UtreexoStateDefault of Default { From af7df629c161e4bd0a02fb18e9ce647b3a48356d Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Thu, 3 Oct 2024 20:31:57 +0200 Subject: [PATCH 02/32] fix struct fields order --- packages/consensus/src/types/utreexo.cairo | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/consensus/src/types/utreexo.cairo b/packages/consensus/src/types/utreexo.cairo index f3c6877a..3500c5e5 100644 --- a/packages/consensus/src/types/utreexo.cairo +++ b/packages/consensus/src/types/utreexo.cairo @@ -49,21 +49,21 @@ pub struct UtreexoState { /// Utreexo inclusion proof for a single transaction output. #[derive(Drop, Copy, Serde)] pub struct UtreexoProof { + /// List of sibling nodes required to calculate the root. + pub proof: Span, /// Index of the leaf in the forest, but also an encoded binary path, /// specifying which sibling node is left and which is right. pub leaf_index: u64, - /// List of sibling nodes required to calculate the root. - pub proof: Span, } /// Utreexo inclusion proof for multiple outputs. /// Compatible with https://github.com/utreexo/utreexo #[derive(Drop, Copy)] pub struct UtreexoBatchProof { - /// Indices of leaves to be deleted (ordered starting from 0, left to right). - pub targets: Span, /// List of sibling nodes required to calculate the root. pub proof: Span, + /// Indices of leaves to be deleted (ordered starting from 0, left to right). + pub targets: Span, } #[derive(Drop, Copy, PartialEq, Debug)] @@ -216,14 +216,14 @@ pub impl UtreexoAccumulatorImpl of UtreexoAccumulator { // Get the expected root let root_index = (*proof.proof).len(); - if root_index >= self.roots.deref().len() { + if root_index >= (*self.roots).len() { return Result::Err(UtreexoError::RootIndexOutOfBound); } let expected_root = self.roots[root_index]; match expected_root { Option::Some(root) => { - if root.deref().into() == proof_root { + if *root.into() == proof_root { return Result::Ok(()); }; Result::Err(UtreexoError::ProofVerificationFailed) From 9186a3d235ac9d87c4f17b81501b07c83e665f6f Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Thu, 3 Oct 2024 21:05:43 +0200 Subject: [PATCH 03/32] add comment about loops --- packages/consensus/src/types/utreexo.cairo | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/consensus/src/types/utreexo.cairo b/packages/consensus/src/types/utreexo.cairo index 3500c5e5..1b0f40a6 100644 --- a/packages/consensus/src/types/utreexo.cairo +++ b/packages/consensus/src/types/utreexo.cairo @@ -247,6 +247,7 @@ pub impl UtreexoAccumulatorImpl of UtreexoAccumulator { let mut number_matched_roots: u32 = 0; + // Should we reverse *self.roots like in rustreexo to reduce the number of iteration? for i in 0 ..computed_roots .len() { @@ -262,8 +263,10 @@ pub impl UtreexoAccumulatorImpl of UtreexoAccumulator { }; }; }; - - if (computed_roots.len() != number_matched_roots && computed_roots.len() != 0) { + + let computed_roots_len = computed_roots.len(); + + if (computed_roots_len!= number_matched_roots && computed_roots_len != 0) { return Result::Err(UtreexoError::ProofVerificationFailed); } From b2ebe54506fe1f372fcc9ddfcc5eee5c6e0b62a1 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Thu, 3 Oct 2024 21:29:39 +0200 Subject: [PATCH 04/32] handle error for compute_roots private function --- packages/consensus/src/types/utreexo.cairo | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/consensus/src/types/utreexo.cairo b/packages/consensus/src/types/utreexo.cairo index 1b0f40a6..6e232b72 100644 --- a/packages/consensus/src/types/utreexo.cairo +++ b/packages/consensus/src/types/utreexo.cairo @@ -240,10 +240,9 @@ pub impl UtreexoAccumulatorImpl of UtreexoAccumulator { return Result::Ok(()); }; - // TODO: handle error progration for compute_roots let computed_roots: Span = compute_roots( outpoints_hashes, *self.num_leaves, proof - ); + )?; let mut number_matched_roots: u32 = 0; @@ -302,9 +301,9 @@ fn compute_root(proof: Span, mut leaf_index: u64, mut curr_node: felt25 /// Computes a set of roots from a proof. fn compute_roots( outpoints_hashes: Span, num_leaves: u64, proof: @UtreexoBatchProof, -) -> Span { +) -> Result, UtreexoError> { // TODO - array![].span() + Result::Ok(array![].span()) } pub impl UtreexoStateDefault of Default { From 0276dfc504a7e9c000b1edcc0b30b50f6bdf8a2c Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Thu, 3 Oct 2024 21:32:28 +0200 Subject: [PATCH 05/32] fmt --- packages/consensus/src/types/utreexo.cairo | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/consensus/src/types/utreexo.cairo b/packages/consensus/src/types/utreexo.cairo index 6e232b72..30f625e9 100644 --- a/packages/consensus/src/types/utreexo.cairo +++ b/packages/consensus/src/types/utreexo.cairo @@ -262,10 +262,10 @@ pub impl UtreexoAccumulatorImpl of UtreexoAccumulator { }; }; }; - + let computed_roots_len = computed_roots.len(); - - if (computed_roots_len!= number_matched_roots && computed_roots_len != 0) { + + if (computed_roots_len != number_matched_roots && computed_roots_len != 0) { return Result::Err(UtreexoError::ProofVerificationFailed); } From 23c3d5af1bf85c7c30edb19358123878c9a9b6e2 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 7 Oct 2024 17:37:55 +0200 Subject: [PATCH 06/32] remove utreexo for merging --- packages/consensus/src/types/utreexo.cairo | 746 --------------------- 1 file changed, 746 deletions(-) delete mode 100644 packages/consensus/src/types/utreexo.cairo diff --git a/packages/consensus/src/types/utreexo.cairo b/packages/consensus/src/types/utreexo.cairo deleted file mode 100644 index 30f625e9..00000000 --- a/packages/consensus/src/types/utreexo.cairo +++ /dev/null @@ -1,746 +0,0 @@ -//! Utreexo is an accumulator for the Bitcoin unspent transaction set. -//! -//! It allows to verify that a certain transaction output exists -//! and still unspent at a particular block while maintaining only -//! a very compact state. -//! -//! It is also useful for transaction validation (our case) since it -//! allows to "fetch" the output spent by a particular input in the -//! validated transaction. This is typically required to calculate -//! transaction fee and also to check that script execution succeeds. -//! -//! The expected workflow is the following: -//! - For coinbase and inputs spending TXOs created in the same block -//! utreexo accumulator is not updated (local cache is used instead); -//! - For all other inputs we provide respective TXOs (extended) plus -//! plus inclusion proof (can be individual, batched, or hybrid); -//! - The client has to verify the inclusion proof and then remove all -//! the TXOs from the utreexo set, that way updating the state; -//! - For every output that is not spent in the same block we add the -//! extended (additionally contains txid and output index aka vout) output -//! to the accumulator (i.e. update the utreexo state). -//! -//! Note that utreexo data and proofs are provided via program input so -//! it is not part of prover/verifier arguments. Utreexo state (which -//! is part of the chain state) is what allows us to constrain -//! these inputs and ensure integrity. -//! -//! Read more about utreexo: https://eprint.iacr.org/2019/611.pdf - -use super::utxo_set::UtxoSet; -use utils::hash::{DigestImpl, DigestIntoU256}; -use core::poseidon::PoseidonTrait; -use core::hash::{HashStateTrait, HashStateExTrait}; -use core::fmt::{Display, Formatter, Error}; - -/// Accumulator representation of the state aka "Compact State Node". -/// Part of the chain state. -#[derive(Drop, Copy, PartialEq, Serde, Debug)] -pub struct UtreexoState { - /// Roots of the Merkle tree forest. - /// Index is the root height, None means a gap. - pub roots: Span>, - /// Total number of leaves (in the bottom-most row). - /// Required to calculate the number of nodes in a particular row. - /// Can be reconstructed from the roots, but cached for convenience. - pub num_leaves: u64, -} - -/// Utreexo inclusion proof for a single transaction output. -#[derive(Drop, Copy, Serde)] -pub struct UtreexoProof { - /// List of sibling nodes required to calculate the root. - pub proof: Span, - /// Index of the leaf in the forest, but also an encoded binary path, - /// specifying which sibling node is left and which is right. - pub leaf_index: u64, -} - -/// Utreexo inclusion proof for multiple outputs. -/// Compatible with https://github.com/utreexo/utreexo -#[derive(Drop, Copy)] -pub struct UtreexoBatchProof { - /// List of sibling nodes required to calculate the root. - pub proof: Span, - /// Indices of leaves to be deleted (ordered starting from 0, left to right). - pub targets: Span, -} - -#[derive(Drop, Copy, PartialEq, Debug)] -pub enum UtreexoError { - ProofVerificationFailed, - RootIndexOutOfBound -} - -#[generate_trait] -pub impl UtreexoStateImpl of UtreexoStateTrait { - fn validate_and_apply( - ref self: UtreexoState, utxo_set: UtxoSet, proofs: Span - ) -> Result<(), ByteArray> { - let mut proof_idx = 0; - let mut inner_result = Result::Ok(()); - - for leaf_hash in utxo_set - .leaves_to_delete { - let proof = proofs[proof_idx]; - - let res = self.verify(leaf_hash, proof); - if res.is_err() { - inner_result = - Result::Err(format!("Verification failed for {}: {:?}", leaf_hash, res)); - break; - } - - self.delete(proof); - proof_idx += 1; - }; - - inner_result?; - - for leaf_hash in utxo_set.leaves_to_add { - self.add(leaf_hash); - }; - - Result::Ok(()) - } -} - -/// Accumulator interface -pub trait UtreexoAccumulator { - /// Adds single output to the accumulator. - /// The order *is important*: adding A,B and B,A would result in different states. - /// - /// Note that this call also pushes old UTXOs "to the left", to a larger subtree. - /// This mechanism ensures that short-lived outputs have small inclusion proofs. - fn add(ref self: UtreexoState, outpoint_hash: felt252); - - /// Removes single output from the accumulator (order is important). - /// - /// Note that once verified, the output itself is not required for deletion, - /// the leaf index plus inclusion proof is enough. - fn delete(ref self: UtreexoState, proof: @UtreexoProof); - - /// Removes multiple outputs from the accumulator. - fn delete_batch(ref self: UtreexoState, proof: @UtreexoBatchProof); - - /// Verifies inclusion proof for a single output. - fn verify( - self: @UtreexoState, outpoint_hash: felt252, proof: @UtreexoProof - ) -> Result<(), UtreexoError>; - - /// Verifies batch proof for multiple outputs (e.g. all outputs in a block). - fn verify_batch( - self: @UtreexoState, outpoints_hashes: Span, proof: @UtreexoBatchProof - ) -> Result<(), UtreexoError>; -} - -pub impl UtreexoAccumulatorImpl of UtreexoAccumulator { - /// Adds an output from the accumulator. - /// https://eprint.iacr.org/2019/611.pdf Algorithm 1 AddOne - fn add(ref self: UtreexoState, outpoint_hash: felt252) { - let mut new_roots: Array> = Default::default(); - let mut n: felt252 = outpoint_hash; - let mut first_none_found: bool = false; - - for root in self - .roots { - if (!first_none_found) { - if (root.is_none()) { - first_none_found = true; - new_roots.append(Option::Some(n)); - } else { - n = parent_hash((*root).unwrap(), n); - new_roots.append(Option::None); - } - } else { - new_roots.append(*root); - } - }; - - // Checks if end with Option::None - if (new_roots[new_roots.len() - 1].is_some()) { - new_roots.append(Option::None); - } - - self.roots = new_roots.span(); - self.num_leaves += 1_u64; - } - - /// Removes an output from the accumulator. - /// https://eprint.iacr.org/2019/611.pdf Algorithm 2 DeleteOne - fn delete(ref self: UtreexoState, proof: @UtreexoProof) { - let mut roots: Array> = array![]; - let mut n: Option = Option::None; - - let num_roots: u32 = self.roots.len(); - let proof = *proof.proof; - let mut h: usize = 0; - - for sibling in proof { - if n != Option::None { - n = Option::Some(parent_hash(*sibling, n.unwrap())); - if h < num_roots { - roots.append(*self.roots[h]); - } else { - roots.append(Option::None); - } - } else if h < num_roots && self.roots[h].is_some() { - n = Option::Some(parent_hash(*sibling, (*self.roots[h]).unwrap())); - roots.append(Option::None); - } else { - roots.append(Option::Some(*sibling)); - } - - h += 1; - }; - - roots.append(n); - h += 1; - while h != num_roots { - roots.append(*self.roots[h]); - h += 1; - }; - - self.roots = roots.span(); - self.num_leaves -= 1; - } - - /// Removes multiple outputs from the accumulator. - fn delete_batch(ref self: UtreexoState, proof: @UtreexoBatchProof) {} - - /// Verifies inclusion proof for a single output. - fn verify( - self: @UtreexoState, outpoint_hash: felt252, proof: @UtreexoProof - ) -> Result<(), UtreexoError> { - let proof_root = compute_root(*proof.proof, *proof.leaf_index, outpoint_hash); - - // Get the expected root - let root_index = (*proof.proof).len(); - if root_index >= (*self.roots).len() { - return Result::Err(UtreexoError::RootIndexOutOfBound); - } - let expected_root = self.roots[root_index]; - - match expected_root { - Option::Some(root) => { - if *root.into() == proof_root { - return Result::Ok(()); - }; - Result::Err(UtreexoError::ProofVerificationFailed) - }, - _ => Result::Err(UtreexoError::ProofVerificationFailed) - } - } - - /// Verifies inclusion proof for multiple outputs. - fn verify_batch( - self: @UtreexoState, outpoints_hashes: Span, proof: @UtreexoBatchProof - ) -> Result<(), UtreexoError> { - if (*proof.targets).is_empty() { - return Result::Ok(()); - }; - - let computed_roots: Span = compute_roots( - outpoints_hashes, *self.num_leaves, proof - )?; - - let mut number_matched_roots: u32 = 0; - - // Should we reverse *self.roots like in rustreexo to reduce the number of iteration? - for i in 0 - ..computed_roots - .len() { - for root in *self - .roots { - match root { - Option::Some(root) => { - if (computed_roots[i] == root) { - number_matched_roots += 1; - }; - }, - Option::None => {}, - }; - }; - }; - - let computed_roots_len = computed_roots.len(); - - if (computed_roots_len != number_matched_roots && computed_roots_len != 0) { - return Result::Err(UtreexoError::ProofVerificationFailed); - } - - Result::Ok(()) - } -} - -/// https://eprint.iacr.org/2019/611.pdf page 6 - Adding and removing elements. -fn parent_hash(left: felt252, right: felt252) -> felt252 { - return PoseidonTrait::new().update_with(left).update_with(right).finalize(); -} - -/// Computes the root given a leaf, its index, and a proof. -/// -/// Traverses the tree from leaf to root, hashing paired nodes. -/// Proof order is bottom-up. Returns the computed root. -fn compute_root(proof: Span, mut leaf_index: u64, mut curr_node: felt252) -> felt252 { - for sibling in proof { - let (next_left_index, is_right) = DivRem::div_rem(leaf_index, 2); - - let (left, right) = if is_right == 0 { - (curr_node, *sibling) - } else { - (*sibling, curr_node) - }; - curr_node = parent_hash(left, right); - leaf_index = next_left_index; - }; - // Returns the computed root (or the node itself if the proof is empty). - curr_node -} - -/// Computes a set of roots from a proof. -fn compute_roots( - outpoints_hashes: Span, num_leaves: u64, proof: @UtreexoBatchProof, -) -> Result, UtreexoError> { - // TODO - Result::Ok(array![].span()) -} - -pub impl UtreexoStateDefault of Default { - fn default() -> UtreexoState { - UtreexoState { roots: array![Option::None].span(), num_leaves: 0, } - } -} - -impl UtreexoStateDisplay of Display { - fn fmt(self: @UtreexoState, ref f: Formatter) -> Result<(), Error> { - let str: ByteArray = format!( - "UtreexoState {{ roots: {}, num_leaves: {}, }}", (*self.roots).len(), *self.num_leaves - ); - f.buffer.append(@str); - Result::Ok(()) - } -} - -impl UtreexoProofDisplay of Display { - fn fmt(self: @UtreexoProof, ref f: Formatter) -> Result<(), Error> { - let mut proofs: ByteArray = Default::default(); - for proof in *self.proof { - proofs.append(@format!("{},", proof)); - }; - let str: ByteArray = format!( - "UtreexoProof {{ leaf_index: {}, proof: {}, }}", *self.leaf_index, @proofs - ); - f.buffer.append(@str); - Result::Ok(()) - } -} - -impl UtreexoBatchProofDisplay of Display { - fn fmt(self: @UtreexoBatchProof, ref f: Formatter) -> Result<(), Error> { - let mut targets: ByteArray = Default::default(); - let mut proofs: ByteArray = Default::default(); - for target in *self.targets { - targets.append(@format!("{},", target)); - }; - for proof in *self.proof { - proofs.append(@format!("{},", proof)); - }; - let str: ByteArray = format!( - "UtreexoBatchProof {{ leaf_index: [{}], proof: [{}] }}", @targets, @proofs - ); - f.buffer.append(@str); - Result::Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::{UtreexoState, UtreexoAccumulator, UtreexoProof}; - use consensus::types::transaction::{OutPoint, TxOut}; - use utils::{ - hash::{DigestImpl, DigestIntoU256}, bytearray::{ByteArraySnapHash, ByteArraySnapSerde}, - hex::{from_hex, hex_to_hash_rev} - }; - use core::poseidon::PoseidonTrait; - use core::hash::{HashStateTrait, HashStateExTrait}; - - /// block 170 tx1 v0 -> block9 tx coinbase v0 - fn get_outpoint() -> OutPoint { - OutPoint { - txid: hex_to_hash_rev( - "0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9" - ), - vout: 0, - data: TxOut { - value: 5000000000, - pk_script: @from_hex( - "410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac" - ), - cached: false - }, - block_height: 9, - block_time: 1231473279, - block_hash: hex_to_hash_rev( - "000000008d9dc510f23c2657fc4f67bea30078cc05a90eb89e84cc475c080805" - ), - is_coinbase: true - } - } - - /// outpoint hash of first output spent block 170 - #[test] - fn test_poseidon1() { - let outpoint: OutPoint = get_outpoint(); - let outpoint_hash = PoseidonTrait::new().update_with(outpoint).finalize(); - - let expected: felt252 = 0x1E8BBC31DA001E7EBACAEBC83DF1FD241040B9525ADEECEADBBC7045C6D1876; - assert_eq!(outpoint_hash, expected); - } - - /// Test the basic functionality of the Utreexo accumulator - /// - /// This test covers the following scenarios: - /// 1. Adding a single leaf and verifying it - /// 2. Adding a second leaf and verifying both leaves - /// 3. Adding a third leaf and verifying previous leaves - /// 4. Adding a fourth leaf and verifying all leaves - /// - /// For each scenario, the test: - /// - Initializes the UtreexoState with the appropriate roots and number of leaves - /// - Creates an OutPoint representing the leaf - /// - Constructs a UtreexoProof for the leaf - /// - Calls the verify function and asserts that it succeeds - /// - /// The test uses predefined txid values (0x111111..., 0x222222..., etc.) for simplicity. - /// It checks the correct root values at each stage of the Utreexo tree's growth. - #[test] - fn test_verify_inclusion() { - // Add the first leaf (0x111111111111111111111111) - let leaf1 = 0x111111111111111111111111; - - let mut utxo_state = UtreexoState { - roots: array![Option::Some(0x111111111111111111111111)].into(), num_leaves: 1 - }; - let proof = UtreexoProof { leaf_index: 0, proof: array![].span(), }; - let result = utxo_state.verify(leaf1, @proof); - assert!(result.is_ok(), "Root at index 0 should be 0x111111111111111111111111"); - - // Add the second leaf (0x222222222222222222222222) - let leaf2 = 0x222222222222222222222222; - - utxo_state = - UtreexoState { - roots: array![ - Option::None, - Option::Some(0x05fb342b44641ae6d67310cf9da5566e1a398fd6b0121d40e2c5acd16e1ddb4a) - ] - .into(), - num_leaves: 1 - }; - - let proof = UtreexoProof { - leaf_index: 1, proof: array![0x111111111111111111111111].span(), - }; - let result = utxo_state.verify(leaf2, @proof); - assert!( - result.is_ok(), - "Root at index 1 should be 0x05fb342b44641ae6d67310cf9da5566e1a398fd6b0121d40e2c5acd16e1ddb4a" - ); - - // Add the third leaf (0x333333333333333333333333) - let leaf3 = 0x333333333333333333333333; - utxo_state = - UtreexoState { - roots: array![ - Option::Some(leaf3), - Option::Some(0x05fb342b44641ae6d67310cf9da5566e1a398fd6b0121d40e2c5acd16e1ddb4a) - ] - .into(), - num_leaves: 1 - }; - - let proof = UtreexoProof { - leaf_index: 1, proof: array![0x111111111111111111111111].span(), - }; - let result = utxo_state.verify(leaf2, @proof); - assert!( - result.is_ok(), - "Root at index 1 should be 0x05fb342b44641ae6d67310cf9da5566e1a398fd6b0121d40e2c5acd16e1ddb4a" - ); - - // Add the fourth leaf (0x444444444444444444444444) - - let leaf4 = 0x444444444444444444444444; - - utxo_state = - UtreexoState { - roots: array![ - Option::None, - Option::None, - Option::Some(0x018674e0c40577cb5ba4728d6ac7bedfd9548f4020161223261941b2a8ae84b2) - ] - .into(), - num_leaves: 1 - }; - - // Create the UtreexoProof for leaf 1 - let proof = UtreexoProof { - leaf_index: 0, - proof: array![ - 0x222222222222222222222222, - 0x02a6b2ae998d30e1ac356c32b2750c3126cd6b3ecf02e6918a93021d17b2b026 - ] - .span(), - }; - // Call the verify function - let result = utxo_state.verify(leaf1, @proof); - assert!(result.is_ok(), "verify leaf index 0 failed"); - // Create the UtreexoProof for leaf 2 - let proof = UtreexoProof { - leaf_index: 1, - proof: array![leaf1, 0x02a6b2ae998d30e1ac356c32b2750c3126cd6b3ecf02e6918a93021d17b2b026] - .span(), - }; - // Call the verify function - let result = utxo_state.verify(leaf2, @proof); - assert!(result.is_ok(), "verify leaf index 1 failed"); - - // Create the UtreexoProof for leaf 3 - let proof = UtreexoProof { - leaf_index: 2, - proof: array![leaf4, 0x05fb342b44641ae6d67310cf9da5566e1a398fd6b0121d40e2c5acd16e1ddb4a] - .span(), - }; - // Call the verify function - let result = utxo_state.verify(leaf3, @proof); - assert!(result.is_ok(), "verify leaf index 2 failed"); - - // Create the UtreexoProof for leaf 4 - let proof = UtreexoProof { - leaf_index: 3, - proof: array![leaf3, 0x05fb342b44641ae6d67310cf9da5566e1a398fd6b0121d40e2c5acd16e1ddb4a] - .span(), - }; - // Call the verify function - let result = utxo_state.verify(leaf4, @proof); - assert!(result.is_ok(), "verify leaf index 3 failed"); - - // Create the UtreexoProof for leaf 4 - let proof = UtreexoProof { - leaf_index: 3, - proof: array![leaf2, 0x05fb342b44641ae6d67310cf9da5566e1a398fd6b0121d40e2c5acd16e1ddb4a] - .span(), - }; - // Call the verify function - let result = utxo_state.verify(leaf4, @proof); - assert!(result.is_err(), "verify leaf index 3 should fail"); - } - - #[test] - fn test_utreexo_add() { - // Test data is generated using scripts/data/utreexo.py - - let mut utreexo_state: UtreexoState = Default::default(); - let outpoint: felt252 = 0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C; - - // add first leave to empty utreexo - utreexo_state.add(outpoint); - - let expected: Span> = array![ - Option::Some(0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C), - Option::None - ] - .span(); - assert_eq!(utreexo_state.roots, expected, "cannot add first leave"); - assert_eq!(utreexo_state.num_leaves, 1); - - // add second leave - utreexo_state.add(outpoint); - - let expected: Span> = array![ - Option::None, - Option::Some(0x738A7C495E564574993BBCB6A62D65C3C570BB81C63801066AF8934649F66F6), - Option::None - ] - .span(); - assert_eq!(utreexo_state.roots, expected, "cannot add second leave"); - assert_eq!(utreexo_state.num_leaves, 2); - - // add thirdth leave - utreexo_state.add(outpoint); - - let expected: Span> = array![ - Option::Some(0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C), - Option::Some(0x738A7C495E564574993BBCB6A62D65C3C570BB81C63801066AF8934649F66F6), - Option::None - ] - .span(); - assert_eq!(utreexo_state.roots, expected, "cannot add thirdth leave"); - assert_eq!(utreexo_state.num_leaves, 3); - - // add fourth leave - utreexo_state.add(outpoint); - - let expected: Span> = array![ - Option::None, - Option::None, - Option::Some(0x25D0DE35DD446E3D35504866FD7A04D4245E01B5908E19EAA70ABA84DD5A1F1), - Option::None - ] - .span(); - assert_eq!(utreexo_state.roots, expected, "cannot add fourth leave"); - assert_eq!(utreexo_state.num_leaves, 4); - - // add fifth leave - utreexo_state.add(outpoint); - - let expected: Span> = array![ - Option::Some(0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C), - Option::None, - Option::Some(0x25D0DE35DD446E3D35504866FD7A04D4245E01B5908E19EAA70ABA84DD5A1F1), - Option::None - ] - .span(); - assert_eq!(utreexo_state.roots, expected, "cannot add fifth leave"); - assert_eq!(utreexo_state.num_leaves, 5); - - // add 3 leaves - for _ in 1..4_u8 { - utreexo_state.add(outpoint); - }; - - let expected: Span> = array![ - Option::None, - Option::None, - Option::None, - Option::Some(0x708EB39E30B035376EC871F8F17CD3BADAE6A68406B13C3BB671009D56F5AD), - Option::None - ] - .span(); - assert_eq!(utreexo_state.roots, expected, "cannot add 3 leaves"); - assert_eq!(utreexo_state.num_leaves, 8); - - // add 22 leaves - for _ in 1..23_u8 { - utreexo_state.add(outpoint); - }; - - let expected: Span> = [ - Option::None(()), - Option::Some(0x738A7C495E564574993BBCB6A62D65C3C570BB81C63801066AF8934649F66F6), - Option::Some(0x25D0DE35DD446E3D35504866FD7A04D4245E01B5908E19EAA70ABA84DD5A1F1), - Option::Some(0x708EB39E30B035376EC871F8F17CD3BADAE6A68406B13C3BB671009D56F5AD), - Option::Some(0x58D6BEF6CFC28638FB4C8271355961F50922BCC1577DD2B6D04E11B7A911702), - Option::None(()) - ].span(); - assert_eq!(utreexo_state.roots, expected, "cannot add 22 leaves"); - assert_eq!(utreexo_state.num_leaves, 30); - } - - #[test] - fn test_utreexo_delete() { - // Test data is generated using scripts/data/utreexo.py - - let mut utreexo_state: UtreexoState = Default::default(); - - // adds 16 leaves to empty utreexo - utreexo_state.add(0x111111111111111111111111); - utreexo_state.add(0x222222222222222222222222); - utreexo_state.add(0x333333333333333333333333); - utreexo_state.add(0x444444444444444444444444); - utreexo_state.add(0x555555555555555555555555); - utreexo_state.add(0x666666666666666666666666); - utreexo_state.add(0x777777777777777777777777); - utreexo_state.add(0x888888888888888888888888); - utreexo_state.add(0x999999999999999999999999); - utreexo_state.add(0xAAAAAAAAAAAAAAAAAAAAAAAA); - utreexo_state.add(0xBBBBBBBBBBBBBBBBBBBBBBBB); - utreexo_state.add(0xCCCCCCCCCCCCCCCCCCCCCCCC); - utreexo_state.add(0xEEEEEEEEEEEEEEEEEEEEEEEE); - utreexo_state.add(0xFFFFFFFFFFFFFFFFFFFFFFFF); - utreexo_state.add(0xFFFFFFFFFFFFFFFFFFFFFFF0); - utreexo_state.add(0xFFFFFFFFFFFFFFFFFFFFFFF1); - - let expected: Span> = array![ - Option::None, - Option::None, - Option::None, - Option::None, - Option::Some(0x03467aa210cc0b108229d9a7fc554f9175af4ee27ee08b128b96862f7beca2ea), - Option::None, - ] - .span(); - assert_eq!(utreexo_state.roots, expected, "cannot add second leave"); - assert_eq!(utreexo_state.num_leaves, 16); - - let proof_for_3rd_leaf: UtreexoProof = UtreexoProof { - leaf_index: 2, - proof: array![ - 0x0000000000000000000000000000000000000000444444444444444444444444, - 0x05fb342b44641ae6d67310cf9da5566e1a398fd6b0121d40e2c5acd16e1ddb4a, - 0x01670d29719eae8deaa34a1d75752368d180a2c3e53f08d344ad784f1a034be7, - 0x04448e395061d8b58524c81978a17837c66c7f3286ea3c1773c7fafd77d29f69 - ] - .span() - }; - - // deletes the 3rd leaf - utreexo_state.delete(@proof_for_3rd_leaf); - - let expected: Span> = array![ - Option::Some(0x0000000000000000000000000000000000000000444444444444444444444444), - Option::Some(0x05fb342b44641ae6d67310cf9da5566e1a398fd6b0121d40e2c5acd16e1ddb4a), - Option::Some(0x01670d29719eae8deaa34a1d75752368d180a2c3e53f08d344ad784f1a034be7), - Option::Some(0x04448e395061d8b58524c81978a17837c66c7f3286ea3c1773c7fafd77d29f69), - Option::None, - Option::None, - ] - .span(); - - assert_eq!(utreexo_state.roots, expected, "cannot remove leave"); - assert_eq!(utreexo_state.num_leaves, 15); - } - - #[test] - fn test_utreexo_delete_2() { - // Test data is generated using scripts/data/utreexo.py - - let mut utreexo_state: UtreexoState = Default::default(); - - // adds 7 leaves to empty utreexo - utreexo_state.add(0x111111111111111111111111); - utreexo_state.add(0x222222222222222222222222); - utreexo_state.add(0x333333333333333333333333); - utreexo_state.add(0x444444444444444444444444); - utreexo_state.add(0x555555555555555555555555); - utreexo_state.add(0x666666666666666666666666); - utreexo_state.add(0x777777777777777777777777); - - let expected: Span> = array![ - Option::Some(0x0000000000000000000000000000000000000000777777777777777777777777), - Option::Some(0x0358bb901cdc1d0d68afdb06dfeb84f2472c254ea052a942d8640924386935a6), - Option::Some(0x018674e0c40577cb5ba4728d6ac7bedfd9548f4020161223261941b2a8ae84b2), - Option::None, - ] - .span(); - assert_eq!(utreexo_state.roots, expected, "cannot add second leave"); - assert_eq!(utreexo_state.num_leaves, 7); - - let proof: UtreexoProof = UtreexoProof { leaf_index: 6, proof: array![].span() }; - - // deletes the last added leaf which corresponds to the root at h=0 - utreexo_state.delete(@proof); - - let expected: Span> = array![ - Option::None, - Option::Some(0x0358bb901cdc1d0d68afdb06dfeb84f2472c254ea052a942d8640924386935a6), - Option::Some(0x018674e0c40577cb5ba4728d6ac7bedfd9548f4020161223261941b2a8ae84b2), - Option::None, - ] - .span(); - - assert_eq!(utreexo_state.roots, expected, "cannot remove leave"); - assert_eq!(utreexo_state.num_leaves, 6); - } -} From 285fce3cbe7daf3e0a8f838dd32ce2e16547f6fb Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 7 Oct 2024 18:02:19 +0200 Subject: [PATCH 07/32] merge main --- packages/utreexo/src/stump/accumulator.cairo | 34 ++++++++++++++++++-- packages/utreexo/src/stump/proof.cairo | 14 +++++++- packages/utreexo/src/vanilla/proof.cairo | 2 +- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/packages/utreexo/src/stump/accumulator.cairo b/packages/utreexo/src/stump/accumulator.cairo index 5e991f62..d7aff28d 100644 --- a/packages/utreexo/src/stump/accumulator.cairo +++ b/packages/utreexo/src/stump/accumulator.cairo @@ -1,5 +1,5 @@ use crate::stump::state::UtreexoStumpState; -use crate::stump::proof::UtreexoBatchProof; +use crate::stump::proof::{UtreexoBatchProof, UtreexoBatchProofTrait}; #[generate_trait] pub impl StumpUtreexoAccumulatorImpl of StumpUtreexoAccumulator { @@ -13,7 +13,37 @@ pub impl StumpUtreexoAccumulatorImpl of StumpUtreexoAccumulator { fn verify( self: @UtreexoStumpState, proof: @UtreexoBatchProof, del_hashes: Span ) -> Result<(), ByteArray> { - // TODO + if (*proof.targets).is_empty() { + return Result::Ok(()); + }; + + let computed_roots: Span = proof.compute_roots(del_hashes, *self.num_leaves)?; + + let mut number_matched_roots: u32 = 0; + + // Should we reverse *self.roots like in rustreexo to reduce the number of iteration? + for i in 0 + ..computed_roots + .len() { + for root in *self + .roots { + match root { + Option::Some(root) => { + if (computed_roots[i] == root) { + number_matched_roots += 1; + }; + }, + Option::None => {}, + }; + }; + }; + + let computed_roots_len = computed_roots.len(); + + if (computed_roots_len != number_matched_roots && computed_roots_len != 0) { + return Result::Err("Proof verification failed"); + } + Result::Ok(()) } diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index a74fea25..0bd3dd3d 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -4,7 +4,7 @@ use core::fmt::{Display, Formatter, Error}; /// Compatible with https://github.com/utreexo/utreexo #[derive(Drop, Copy)] pub struct UtreexoBatchProof { - /// List of sibling nodes required to calculate the root. + /// List of sibling nodes required to calculate the roots. pub proof: Span, /// Indices of leaves to be deleted (ordered starting from 0, left to right). pub targets: Span, @@ -27,3 +27,15 @@ impl UtreexoBatchProofDisplay of Display { Result::Ok(()) } } + +#[generate_trait] +pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { + /// Computes a set of roots given a proof and leaves hashes. + fn compute_roots( + self: @UtreexoBatchProof, outpoints_hashes: Span, num_leaves: u64, + ) -> Result, ByteArray> { + // TODO + Result::Ok(array![].span()) + } +} + diff --git a/packages/utreexo/src/vanilla/proof.cairo b/packages/utreexo/src/vanilla/proof.cairo index 3e9b8d9c..479925d1 100644 --- a/packages/utreexo/src/vanilla/proof.cairo +++ b/packages/utreexo/src/vanilla/proof.cairo @@ -27,7 +27,7 @@ impl UtreexoProofDisplay of Display { #[generate_trait] pub impl UtreexoProofImpl of UtreexoProofTrait { - /// Computes the root given a a proof and leaf hash. + /// Computes the root given a proof and leaf hash. /// /// Traverses the tree from leaf to root, hashing paired nodes. /// Proof order is bottom-up. Returns the computed root. From 3d912e9e65b90b79246a29fb84a5c1ada288ae95 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 7 Oct 2024 21:43:22 +0200 Subject: [PATCH 08/32] first draft --- packages/utreexo/src/stump/accumulator.cairo | 2 +- packages/utreexo/src/stump/proof.cairo | 330 ++++++++++++++++++- 2 files changed, 328 insertions(+), 4 deletions(-) diff --git a/packages/utreexo/src/stump/accumulator.cairo b/packages/utreexo/src/stump/accumulator.cairo index d7aff28d..4ba0388a 100644 --- a/packages/utreexo/src/stump/accumulator.cairo +++ b/packages/utreexo/src/stump/accumulator.cairo @@ -18,7 +18,7 @@ pub impl StumpUtreexoAccumulatorImpl of StumpUtreexoAccumulator { }; let computed_roots: Span = proof.compute_roots(del_hashes, *self.num_leaves)?; - + let mut number_matched_roots: u32 = 0; // Should we reverse *self.roots like in rustreexo to reduce the number of iteration? diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index 0bd3dd3d..8a218a0e 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -1,4 +1,6 @@ use core::fmt::{Display, Formatter, Error}; +use crate::parent_hash; +use utils::bit_shifts::{shl, shr}; /// Utreexo inclusion proof for multiple outputs. /// Compatible with https://github.com/utreexo/utreexo @@ -32,10 +34,332 @@ impl UtreexoBatchProofDisplay of Display { pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { /// Computes a set of roots given a proof and leaves hashes. fn compute_roots( - self: @UtreexoBatchProof, outpoints_hashes: Span, num_leaves: u64, + self: @UtreexoBatchProof, del_hashes: Span, num_leaves: u64, ) -> Result, ByteArray> { - // TODO - Result::Ok(array![].span()) + let total_rows: u8 = if (num_leaves == 0) { + 0 + } else { + 64 - leading_zeros((num_leaves - 1)) + }; + let mut calculated_root_hashes: Array = array![]; + let proof_positions: Span = get_proof_positions(*self.targets, num_leaves, total_rows); + + let mut nodes: Array<(u64, felt252)> = array![]; + + // Append targets with their hashes + let mut i = 0; + while i != (*self.targets).len() { + let pos = *self.targets[i]; + nodes.append((pos, *del_hashes[i])); + i += 1; + }; + + // Append proof positions with their hashes + while i != proof_positions.len() { + let pos = *proof_positions[i]; + nodes.append((pos, *self.proof[i])); + i += 1; + }; + + // Sort nodes for sibling computation + // Cant find a way to sort an array of tuple for now + // I would need a custom PartialOrd for tuples, should I do that? + // nodes = sort(nodes.span()); + + let mut computed: Array<(u64, felt252)> = array![]; + let mut computed_index = 0; + let mut provided_index = 0; + + let mut result: Result, ByteArray> = Result::Ok((array![].span())); + + loop { + // Get the next node or hash + let (next_pos, next_hash) = + match get_next(@computed, @nodes, ref computed_index, ref provided_index) { + Option::Some(x) => x, + Option::None => { break; }, + }; + + // Check if this is a root position + if is_root_position(next_pos, num_leaves, total_rows) { + calculated_root_hashes.append(next_hash); + continue; + } + + // Find the sibling + let sibling = next_pos | 1; + let (sibling_pos, sibling_hash) = + match get_next(@computed, @nodes, ref computed_index, ref provided_index) { + Option::Some(x) => x, + Option::None => (0, 0), + }; + + if sibling_pos == 0 && sibling_hash == 0 { + break result = Result::Err("Missing sibling"); + } + + if sibling_pos != sibling { + break result = Result::Err("Mismatch in sibling position"); + } + + // Compute parent hash + let parent_hash = parent_hash(next_hash, sibling_hash); + let parent = parent(next_pos, total_rows); + computed.append((parent, parent_hash)); + }; + + // Filter out proof positions from nodes + let mut filtered_nodes = array![]; + let mut i = 0; + while i < nodes.len() { + let (pos, hash) = *nodes[i]; + if binary_search(proof_positions, pos).is_some() { + filtered_nodes.append((pos, hash)); + } + + i += 1; + }; + + // Are we supposed to return only the list of roots? Because rustreexo impl also returns all + // the nodes that are not part of the proof + + if !result.is_err() { + return Result::Ok((calculated_root_hashes.span())); + } else { + return result; + } } } +/// Returns the number of leading zeros in a u64 variable. +fn leading_zeros(x: u64) -> u8 { + if x == 0 { + return 64_u8; + } + + let mut count: u8 = 0; + let mut bit: u64 = shl(1_u64, 63_u64); + + loop { + if x & bit == 0 { + count += 1; + } else { + break; + } + if bit == 1 { + break; + } + bit = shr(bit, 1_u64); + }; + + count +} + +fn get_proof_positions(targets: Span, num_leaves: u64, forest_rows: u8) -> Span { + let mut targets_arr: Array = array![]; + targets_arr.append_span(targets); + // No need to sort targets_arr here as we make the assumption that targets is already sorted. + + let mut proof_positions: Array = array![]; + + for row in 0 + ..(forest_rows + + 1) { + let mut computed_index = 0; + + while computed_index != targets_arr.len() { + let node = *targets_arr[computed_index]; + if detect_row(node, forest_rows) == row { + let is_last_in_row = computed_index + + 1 >= targets.len() + || detect_row(*targets_arr[computed_index + 1], forest_rows) != row; + + if !is_root_position(node, num_leaves, forest_rows) { + let next_node = if !is_last_in_row { + *targets_arr[computed_index + 1] + } else { + 0 + }; + + if !is_last_in_row && is_sibling(node, next_node) { + computed_index += 2; + } else { + proof_positions.append(node ^ 1); // Add to proof positions + computed_index += 1; + }; + + targets_arr.append(parent(node, forest_rows)); + } else { + computed_index += 1; + } + } else { + computed_index += 1; + } + }; + + targets_arr = sort(targets_arr.span()); + }; + + proof_positions.span() +} + +fn detect_row(pos: u64, forest_rows: u8) -> u8 { + let mut marker: u64 = 1; + let forest_rows_u64: u64 = forest_rows.into(); + marker = shl(marker, forest_rows_u64); + let mut h: u8 = 0; + + while pos & marker != 0 { + marker = shr(marker, 1_u64); + h += 1; + }; + + h +} + +fn parent(pos: u64, forest_rows: u8) -> u64 { + let forest_row_u64: u64 = forest_rows.into(); + shr(pos, 1_u64) | shl(1_u64, forest_row_u64) +} + +fn is_root_position(position: u64, num_leaves: u64, forest_rows: u8) -> bool { + let row = detect_row(position, forest_rows); + let row_u64: u64 = row.into(); + + let root_present = (num_leaves & (shl(1_u64, row_u64))) != 0; + let root_pos = root_position(num_leaves, row, forest_rows); + + root_present && root_pos == position +} + +fn root_position(num_leaves: u64, row: u8, forest_rows: u8) -> u64 { + let row_u64: u64 = row.into(); + let forest_rows_u64: u64 = forest_rows.into(); + let mask = (shl(2_u64, forest_rows_u64) - 1) & 18446744073709551615; + + let before = num_leaves & shl(mask, (row_u64 + 1)); + + let shifted = shr(before, row_u64) | shl(mask, (forest_rows_u64 + 1 - row_u64)); + + shifted & mask +} + +/// Returns whether a and b are sibling or not +fn is_sibling(a: u64, b: u64) -> bool { + a ^ 1 == b +} + +fn get_next, +Drop>( + computed: @Array<(u64, T)>, + provided: @Array<(u64, T)>, + ref computed_pos: usize, + ref provided_pos: usize +) -> Option<(u64, T)> { + let (last_computed_index, last_computed_value) = if computed_pos < computed.len() { + *computed[computed_pos] + } else { + return Option::None; + }; + + let (last_provided_index, last_provided_value) = if provided_pos < provided.len() { + *provided[provided_pos] + } else { + return Option::Some((last_computed_index, last_computed_value)); + }; + + if last_computed_index < last_provided_index { + computed_pos += 1; + return Option::Some((last_computed_index, last_computed_value)); + } else { + provided_pos += 1; + return Option::Some((last_provided_index, last_provided_value)); + } +} + +// Merge sorting, see +// https://github.com/keep-starknet-strange/alexandria/blob/82088715b454d8cf197b9c54c31525ca0cb57a05/packages/sorting/src/merge_sort.cairo# +// I suppose this can be moved to utils package? +fn sort, +Drop, +PartialOrd>(mut array: Span) -> Array { + let len = array.len(); + if len == 0 { + return array![]; + } + if len == 1 { + return array![*array[0]]; + } + + // Create left and right arrays + let middle = len / 2; + let left_arr = array.slice(0, middle); + let right_arr = array.slice(middle, len - middle); + + // Recursively sort the left and right arrays + let sorted_left = sort(left_arr); + let sorted_right = sort(right_arr); + + let mut result_arr = array![]; + merge_recursive(sorted_left, sorted_right, ref result_arr, 0, 0); + result_arr +} + +fn merge_recursive, +Drop, +PartialOrd>( + mut left_arr: Array, + mut right_arr: Array, + ref result_arr: Array, + left_arr_ix: usize, + right_arr_ix: usize +) { + if result_arr.len() == left_arr.len() + right_arr.len() { + return; + } + + if left_arr_ix == left_arr.len() { + result_arr.append(*right_arr[right_arr_ix]); + return merge_recursive(left_arr, right_arr, ref result_arr, left_arr_ix, right_arr_ix + 1); + } + + if right_arr_ix == right_arr.len() { + result_arr.append(*left_arr[left_arr_ix]); + return merge_recursive(left_arr, right_arr, ref result_arr, left_arr_ix + 1, right_arr_ix); + } + + if *left_arr[left_arr_ix] < *right_arr[right_arr_ix] { + result_arr.append(*left_arr[left_arr_ix]); + merge_recursive(left_arr, right_arr, ref result_arr, left_arr_ix + 1, right_arr_ix) + } else { + result_arr.append(*right_arr[right_arr_ix]); + merge_recursive(left_arr, right_arr, ref result_arr, left_arr_ix, right_arr_ix + 1) + } +} + +// Binary search algorithm +// See +// https://github.com/keep-starknet-strange/alexandria/blob/82088715b454d8cf197b9c54c31525ca0cb57a05/packages/searching/src/binary_search.cairo# +pub fn binary_search, +Drop, +PartialEq, +PartialOrd>( + span: Span, val: T +) -> Option { + // Initial check + if span.len() == 0 { + return Option::None; + } + let middle = span.len() / 2; + if *span[middle] == val { + return Option::Some(middle); + } + if span.len() == 1 { + return Option::None; + } + if *span[middle] > val { + return binary_search(span.slice(0, middle), val); + } + + let mut len = middle; + if span.len() % 2 == 1 { + len += 1; + } + let val = binary_search(span.slice(middle, len), val); + match val { + Option::Some(v) => Option::Some(v + middle), + Option::None => Option::None + } +} From 67fdbe27f4b1485437dca83b0982ef2696274e54 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 7 Oct 2024 21:46:52 +0200 Subject: [PATCH 09/32] fmt --- packages/utreexo/src/stump/accumulator.cairo | 2 +- packages/utreexo/src/stump/proof.cairo | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/utreexo/src/stump/accumulator.cairo b/packages/utreexo/src/stump/accumulator.cairo index 4ba0388a..d7aff28d 100644 --- a/packages/utreexo/src/stump/accumulator.cairo +++ b/packages/utreexo/src/stump/accumulator.cairo @@ -18,7 +18,7 @@ pub impl StumpUtreexoAccumulatorImpl of StumpUtreexoAccumulator { }; let computed_roots: Span = proof.compute_roots(del_hashes, *self.num_leaves)?; - + let mut number_matched_roots: u32 = 0; // Should we reverse *self.roots like in rustreexo to reduce the number of iteration? diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index 8a218a0e..917d9bc4 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -121,7 +121,8 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { }; // Are we supposed to return only the list of roots? Because rustreexo impl also returns all - // the nodes that are not part of the proof + // the nodes that are not part of the proof, but its not required in `verify` function + // Maybe we can skip some code (filter out above, etc) if !result.is_err() { return Result::Ok((calculated_root_hashes.span())); From fbba819ac8066663fff1db907242b909a286c6c9 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 7 Oct 2024 21:56:05 +0200 Subject: [PATCH 10/32] fmt --- packages/utreexo/src/stump/proof.cairo | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index 917d9bc4..352becbe 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -63,7 +63,8 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { // Sort nodes for sibling computation // Cant find a way to sort an array of tuple for now - // I would need a custom PartialOrd for tuples, should I do that? + // I would need a custom PartialOrd for tuples, should I do that? How such a sorting would + // work? // nodes = sort(nodes.span()); let mut computed: Array<(u64, felt252)> = array![]; @@ -185,7 +186,7 @@ fn get_proof_positions(targets: Span, num_leaves: u64, forest_rows: u8) -> if !is_last_in_row && is_sibling(node, next_node) { computed_index += 2; } else { - proof_positions.append(node ^ 1); // Add to proof positions + proof_positions.append(node ^ 1); computed_index += 1; }; From 50b93e3af78657aecde39c8a60648a9937f3f285 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Thu, 10 Oct 2024 15:42:56 +0200 Subject: [PATCH 11/32] fmt --- packages/utreexo/src/stump/proof.cairo | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index 352becbe..b4d4dfcc 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -221,6 +221,7 @@ fn detect_row(pos: u64, forest_rows: u8) -> u8 { fn parent(pos: u64, forest_rows: u8) -> u64 { let forest_row_u64: u64 = forest_rows.into(); + shr(pos, 1_u64) | shl(1_u64, forest_row_u64) } From 79ce66e1c39916d24ee5b1b5166da61569b40c07 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Thu, 10 Oct 2024 15:43:33 +0200 Subject: [PATCH 12/32] fmt --- packages/utreexo/src/stump/proof.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index b4d4dfcc..aba6bf38 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -221,7 +221,7 @@ fn detect_row(pos: u64, forest_rows: u8) -> u8 { fn parent(pos: u64, forest_rows: u8) -> u64 { let forest_row_u64: u64 = forest_rows.into(); - + shr(pos, 1_u64) | shl(1_u64, forest_row_u64) } From 5cbbcaf9d522c414d7692e796b7a40c9076084aa Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Thu, 10 Oct 2024 15:47:25 +0200 Subject: [PATCH 13/32] add utils dependency --- Scarb.lock | 3 +++ packages/utreexo/Scarb.toml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Scarb.lock b/Scarb.lock index 4777d232..8d2623f1 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -23,3 +23,6 @@ version = "0.1.0" [[package]] name = "utreexo" version = "0.1.0" +dependencies = [ + "utils", +] diff --git a/packages/utreexo/Scarb.toml b/packages/utreexo/Scarb.toml index 1bfcb576..e823b1f8 100644 --- a/packages/utreexo/Scarb.toml +++ b/packages/utreexo/Scarb.toml @@ -3,6 +3,9 @@ name = "utreexo" version = "0.1.0" edition = "2024_07" +[dependencies] +utils = { path = "../utils" } + [dev-dependencies] cairo_test.workspace = true From 3b806ff888762620ed91371541afffbbc671ca45 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 14:13:28 +0200 Subject: [PATCH 14/32] rewrite compute_roots function --- packages/utils/src/lib.cairo | 2 + packages/utils/src/partial_ord.cairo | 47 +++ packages/utils/src/sort.cairo | 39 +++ packages/utreexo/src/stump/proof.cairo | 415 +++++++------------------ 4 files changed, 201 insertions(+), 302 deletions(-) create mode 100644 packages/utils/src/partial_ord.cairo create mode 100644 packages/utils/src/sort.cairo diff --git a/packages/utils/src/lib.cairo b/packages/utils/src/lib.cairo index 6ab905ff..49ad2e8c 100644 --- a/packages/utils/src/lib.cairo +++ b/packages/utils/src/lib.cairo @@ -4,7 +4,9 @@ pub mod double_sha256; pub mod hash; pub mod merkle_tree; pub mod numeric; +pub mod partial_ord; pub mod sha256; +pub mod sort; #[cfg(target: 'test')] pub mod hex; diff --git a/packages/utils/src/partial_ord.cairo b/packages/utils/src/partial_ord.cairo new file mode 100644 index 00000000..a3b01e0f --- /dev/null +++ b/packages/utils/src/partial_ord.cairo @@ -0,0 +1,47 @@ +/// PartialOrd implementation for tuple (u32, felt252). +/// Required to sort leaf nodes in proof verification. +pub impl PartialOrdTupleU64Felt252 of PartialOrd<(u64, felt252)> { + fn le(lhs: (u64, felt252), rhs: (u64, felt252)) -> bool { + let (a, _) = lhs; + let (b, _) = rhs; + + if a <= b { + true + } else { + false + } + } + + fn ge(lhs: (u64, felt252), rhs: (u64, felt252)) -> bool { + let (a, _) = lhs; + let (b, _) = rhs; + + if a >= b { + true + } else { + false + } + } + + fn lt(lhs: (u64, felt252), rhs: (u64, felt252)) -> bool { + let (a, _) = lhs; + let (b, _) = rhs; + + if a < b { + true + } else { + false + } + } + + fn gt(lhs: (u64, felt252), rhs: (u64, felt252)) -> bool { + let (a, _) = lhs; + let (b, _) = rhs; + + if a > b { + true + } else { + false + } + } +} diff --git a/packages/utils/src/sort.cairo b/packages/utils/src/sort.cairo new file mode 100644 index 00000000..063956aa --- /dev/null +++ b/packages/utils/src/sort.cairo @@ -0,0 +1,39 @@ +/// Bubble sort from +/// https://github.com/keep-starknet-strange/alexandria/blob/main/packages/sorting/src/bubble_sort.cairo +pub fn bubble_sort, +Drop, +PartialOrd>(mut array: Span) -> Array { + if array.len() == 0 { + return array![]; + } + if array.len() == 1 { + return array![*array[0]]; + } + let mut idx1 = 0; + let mut idx2 = 1; + let mut sorted_iteration = true; + let mut sorted_array = array![]; + + loop { + if idx2 == array.len() { + sorted_array.append(*array[idx1]); + if sorted_iteration { + break; + } + array = sorted_array.span(); + sorted_array = array![]; + idx1 = 0; + idx2 = 1; + sorted_iteration = true; + } else { + if *array[idx1] <= *array[idx2] { + sorted_array.append(*array[idx1]); + idx1 = idx2; + idx2 += 1; + } else { + sorted_array.append(*array[idx2]); + idx2 += 1; + sorted_iteration = false; + } + }; + }; + sorted_array +} diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index aba6bf38..a6cd9cce 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -1,6 +1,7 @@ use core::fmt::{Display, Formatter, Error}; +use core::num::traits::Bounded; use crate::parent_hash; -use utils::bit_shifts::{shl, shr}; +use utils::{bit_shifts::{shl, shr}, sort::bubble_sort, partial_ord::PartialOrdTupleU64Felt252}; /// Utreexo inclusion proof for multiple outputs. /// Compatible with https://github.com/utreexo/utreexo @@ -36,333 +37,143 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { fn compute_roots( self: @UtreexoBatchProof, del_hashes: Span, num_leaves: u64, ) -> Result, ByteArray> { - let total_rows: u8 = if (num_leaves == 0) { - 0 - } else { - 64 - leading_zeros((num_leaves - 1)) - }; + // Where all the parent hashes we've calculated in a given row will go to. let mut calculated_root_hashes: Array = array![]; - let proof_positions: Span = get_proof_positions(*self.targets, num_leaves, total_rows); - - let mut nodes: Array<(u64, felt252)> = array![]; + // Target leaves + let mut leaf_nodes: Array<(u64, felt252)> = array![]; - // Append targets with their hashes + // Append targets with their hashes. let mut i = 0; while i != (*self.targets).len() { let pos = *self.targets[i]; - nodes.append((pos, *del_hashes[i])); - i += 1; - }; - - // Append proof positions with their hashes - while i != proof_positions.len() { - let pos = *proof_positions[i]; - nodes.append((pos, *self.proof[i])); + leaf_nodes.append((pos, *del_hashes[i])); i += 1; }; - // Sort nodes for sibling computation - // Cant find a way to sort an array of tuple for now - // I would need a custom PartialOrd for tuples, should I do that? How such a sorting would - // work? - // nodes = sort(nodes.span()); - - let mut computed: Array<(u64, felt252)> = array![]; - let mut computed_index = 0; - let mut provided_index = 0; - - let mut result: Result, ByteArray> = Result::Ok((array![].span())); + let mut leaf_nodes: Array<(u64, felt252)> = bubble_sort(leaf_nodes.span()); + + // Proof nodes. + let mut sibling_nodes: Array = (*self.proof).into(); + // Queue of computed intermediate nodes. + let mut computed_nodes: Array<(u64, felt252)> = array![]; + // Actual length of the current row + let mut actual_row_len: u64 = num_leaves; + // Length of the "padded" row which is always power of two. + let mut row_len: u64 = next_power_of_two(num_leaves); + // Total padded length of processed rows (excluding the current one) + let mut row_len_acc: u64 = 0; + // Next position of the target leaf and the leaf itself. + let (mut next_leaf_pos, mut next_leaf) = leaf_nodes.pop_front().unwrap(); + // Next computed node + let mut next_computed: felt252 = 0; + // Position of the next computed node + let mut next_computed_pos: u64 = Bounded::::MAX; + + while row_len != 0 { + let (pos, node) = if next_leaf_pos < next_computed_pos { + let res = (next_leaf_pos, next_leaf); + if leaf_nodes.is_empty() { + next_leaf_pos = Bounded::::MAX; + } else { + let (a, b) = leaf_nodes.pop_front().unwrap(); + next_leaf_pos = a; + next_leaf = b; + } + res + } else if next_computed_pos != Bounded::::MAX { + let res = (next_computed_pos, next_computed); + if computed_nodes.is_empty() { + next_computed_pos = Bounded::::MAX; + } else { + let (a, b) = computed_nodes.pop_front().unwrap(); + next_computed_pos = a; + next_computed = b; + } + res + } else { + // Out of nodes, terminating here. + break; + }; - loop { - // Get the next node or hash - let (next_pos, next_hash) = - match get_next(@computed, @nodes, ref computed_index, ref provided_index) { - Option::Some(x) => x, - Option::None => { break; }, + // If we are beyond current row, level up. + while pos >= row_len_acc + row_len { + row_len_acc += row_len; + row_len /= 2; + actual_row_len /= 2; }; - // Check if this is a root position - if is_root_position(next_pos, num_leaves, total_rows) { - calculated_root_hashes.append(next_hash); + // If row length is odd and we are at the edge this is a root. + if pos == row_len_acc + actual_row_len - 1 && actual_row_len % 2 == 1 { + calculated_root_hashes.append(node); + row_len_acc += row_len; + row_len /= 2; + actual_row_len /= 2; continue; - } - - // Find the sibling - let sibling = next_pos | 1; - let (sibling_pos, sibling_hash) = - match get_next(@computed, @nodes, ref computed_index, ref provided_index) { - Option::Some(x) => x, - Option::None => (0, 0), }; - if sibling_pos == 0 && sibling_hash == 0 { - break result = Result::Err("Missing sibling"); - } - - if sibling_pos != sibling { - break result = Result::Err("Mismatch in sibling position"); - } - - // Compute parent hash - let parent_hash = parent_hash(next_hash, sibling_hash); - let parent = parent(next_pos, total_rows); - computed.append((parent, parent_hash)); - }; - - // Filter out proof positions from nodes - let mut filtered_nodes = array![]; - let mut i = 0; - while i < nodes.len() { - let (pos, hash) = *nodes[i]; - if binary_search(proof_positions, pos).is_some() { - filtered_nodes.append((pos, hash)); - } - - i += 1; - }; - - // Are we supposed to return only the list of roots? Because rustreexo impl also returns all - // the nodes that are not part of the proof, but its not required in `verify` function - // Maybe we can skip some code (filter out above, etc) - - if !result.is_err() { - return Result::Ok((calculated_root_hashes.span())); - } else { - return result; - } - } -} - -/// Returns the number of leading zeros in a u64 variable. -fn leading_zeros(x: u64) -> u8 { - if x == 0 { - return 64_u8; - } - - let mut count: u8 = 0; - let mut bit: u64 = shl(1_u64, 63_u64); - - loop { - if x & bit == 0 { - count += 1; - } else { - break; - } - if bit == 1 { - break; - } - bit = shr(bit, 1_u64); - }; - - count -} - -fn get_proof_positions(targets: Span, num_leaves: u64, forest_rows: u8) -> Span { - let mut targets_arr: Array = array![]; - targets_arr.append_span(targets); - // No need to sort targets_arr here as we make the assumption that targets is already sorted. - - let mut proof_positions: Array = array![]; - - for row in 0 - ..(forest_rows - + 1) { - let mut computed_index = 0; - - while computed_index != targets_arr.len() { - let node = *targets_arr[computed_index]; - if detect_row(node, forest_rows) == row { - let is_last_in_row = computed_index - + 1 >= targets.len() - || detect_row(*targets_arr[computed_index + 1], forest_rows) != row; - - if !is_root_position(node, num_leaves, forest_rows) { - let next_node = if !is_last_in_row { - *targets_arr[computed_index + 1] - } else { - 0 - }; - - if !is_last_in_row && is_sibling(node, next_node) { - computed_index += 2; - } else { - proof_positions.append(node ^ 1); - computed_index += 1; - }; - - targets_arr.append(parent(node, forest_rows)); - } else { - computed_index += 1; - } + let parent_node = if (pos - row_len_acc) % 2 == 0 { + // Right sibling can be both leaf/computed or proof. + let right_sibling = if next_leaf_pos == pos + 1 { + let res = next_leaf; + if leaf_nodes.is_empty() { + next_leaf_pos = Bounded::::MAX; } else { - computed_index += 1; + let (a, b) = leaf_nodes.pop_front().unwrap(); + next_leaf_pos = a; + next_leaf = b; } + res + } else if next_computed_pos == pos + 1 { + let res = next_computed; + if computed_nodes.is_empty() { + next_computed_pos = Bounded::::MAX; + } else { + let (a, b) = computed_nodes.pop_front().unwrap(); + next_computed_pos = a; + next_computed = b; + } + res + } else { + sibling_nodes.pop_front().unwrap() }; - - targets_arr = sort(targets_arr.span()); + parent_hash(node, right_sibling) + } else { + // Left sibling always from proof. + let left_sibling = sibling_nodes.pop_front().unwrap(); + parent_hash(left_sibling, node) }; - proof_positions.span() -} - -fn detect_row(pos: u64, forest_rows: u8) -> u8 { - let mut marker: u64 = 1; - let forest_rows_u64: u64 = forest_rows.into(); - marker = shl(marker, forest_rows_u64); - let mut h: u8 = 0; - - while pos & marker != 0 { - marker = shr(marker, 1_u64); - h += 1; - }; - - h -} - -fn parent(pos: u64, forest_rows: u8) -> u64 { - let forest_row_u64: u64 = forest_rows.into(); + let parent_pos = row_len_acc + row_len + (pos - row_len_acc) / 2; - shr(pos, 1_u64) | shl(1_u64, forest_row_u64) -} - -fn is_root_position(position: u64, num_leaves: u64, forest_rows: u8) -> bool { - let row = detect_row(position, forest_rows); - let row_u64: u64 = row.into(); - - let root_present = (num_leaves & (shl(1_u64, row_u64))) != 0; - let root_pos = root_position(num_leaves, row, forest_rows); - - root_present && root_pos == position -} - -fn root_position(num_leaves: u64, row: u8, forest_rows: u8) -> u64 { - let row_u64: u64 = row.into(); - let forest_rows_u64: u64 = forest_rows.into(); - let mask = (shl(2_u64, forest_rows_u64) - 1) & 18446744073709551615; - - let before = num_leaves & shl(mask, (row_u64 + 1)); - - let shifted = shr(before, row_u64) | shl(mask, (forest_rows_u64 + 1 - row_u64)); - - shifted & mask -} - -/// Returns whether a and b are sibling or not -fn is_sibling(a: u64, b: u64) -> bool { - a ^ 1 == b -} - -fn get_next, +Drop>( - computed: @Array<(u64, T)>, - provided: @Array<(u64, T)>, - ref computed_pos: usize, - ref provided_pos: usize -) -> Option<(u64, T)> { - let (last_computed_index, last_computed_value) = if computed_pos < computed.len() { - *computed[computed_pos] - } else { - return Option::None; - }; - - let (last_provided_index, last_provided_value) = if provided_pos < provided.len() { - *provided[provided_pos] - } else { - return Option::Some((last_computed_index, last_computed_value)); - }; + if next_computed_pos == Bounded::::MAX { + next_computed_pos = parent_pos; + next_computed = parent_node; + } else { + computed_nodes.append((parent_pos, parent_node)); + } + }; - if last_computed_index < last_provided_index { - computed_pos += 1; - return Option::Some((last_computed_index, last_computed_value)); - } else { - provided_pos += 1; - return Option::Some((last_provided_index, last_provided_value)); - } -} + assert(sibling_nodes.is_empty(), ''); + assert(computed_nodes.is_empty(), ''); + assert(leaf_nodes.is_empty(), ''); -// Merge sorting, see -// https://github.com/keep-starknet-strange/alexandria/blob/82088715b454d8cf197b9c54c31525ca0cb57a05/packages/sorting/src/merge_sort.cairo# -// I suppose this can be moved to utils package? -fn sort, +Drop, +PartialOrd>(mut array: Span) -> Array { - let len = array.len(); - if len == 0 { - return array![]; + Result::Ok((calculated_root_hashes.span())) } - if len == 1 { - return array![*array[0]]; - } - - // Create left and right arrays - let middle = len / 2; - let left_arr = array.slice(0, middle); - let right_arr = array.slice(middle, len - middle); - - // Recursively sort the left and right arrays - let sorted_left = sort(left_arr); - let sorted_right = sort(right_arr); - - let mut result_arr = array![]; - merge_recursive(sorted_left, sorted_right, ref result_arr, 0, 0); - result_arr } -fn merge_recursive, +Drop, +PartialOrd>( - mut left_arr: Array, - mut right_arr: Array, - ref result_arr: Array, - left_arr_ix: usize, - right_arr_ix: usize -) { - if result_arr.len() == left_arr.len() + right_arr.len() { - return; - } - - if left_arr_ix == left_arr.len() { - result_arr.append(*right_arr[right_arr_ix]); - return merge_recursive(left_arr, right_arr, ref result_arr, left_arr_ix, right_arr_ix + 1); - } - - if right_arr_ix == right_arr.len() { - result_arr.append(*left_arr[left_arr_ix]); - return merge_recursive(left_arr, right_arr, ref result_arr, left_arr_ix + 1, right_arr_ix); +/// Computes the next power of two of a u64 variable. +fn next_power_of_two(mut n: u64) -> u64 { + if n == 0 { + return 1; } - if *left_arr[left_arr_ix] < *right_arr[right_arr_ix] { - result_arr.append(*left_arr[left_arr_ix]); - merge_recursive(left_arr, right_arr, ref result_arr, left_arr_ix + 1, right_arr_ix) - } else { - result_arr.append(*right_arr[right_arr_ix]); - merge_recursive(left_arr, right_arr, ref result_arr, left_arr_ix, right_arr_ix + 1) - } -} - -// Binary search algorithm -// See -// https://github.com/keep-starknet-strange/alexandria/blob/82088715b454d8cf197b9c54c31525ca0cb57a05/packages/searching/src/binary_search.cairo# -pub fn binary_search, +Drop, +PartialEq, +PartialOrd>( - span: Span, val: T -) -> Option { - // Initial check - if span.len() == 0 { - return Option::None; - } - let middle = span.len() / 2; - if *span[middle] == val { - return Option::Some(middle); - } - if span.len() == 1 { - return Option::None; - } - if *span[middle] > val { - return binary_search(span.slice(0, middle), val); - } + n -= 1; + n = n | shr(n, 1_u64); + n = n | shr(n, 2_u64); + n = n | shr(n, 4_u64); + n = n | shr(n, 8_u64); + n = n | shr(n, 16_u64); + n = n | shr(n, 32_u64); - let mut len = middle; - if span.len() % 2 == 1 { - len += 1; - } - let val = binary_search(span.slice(middle, len), val); - match val { - Option::Some(v) => Option::Some(v + middle), - Option::None => Option::None - } + n + 1 } From 01cfbd0b8ec8c02fabeb29c560c8a94096fd13ca Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 14:14:27 +0200 Subject: [PATCH 15/32] remove unused import --- packages/utreexo/src/stump/proof.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index a6cd9cce..0381cb59 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -1,7 +1,7 @@ use core::fmt::{Display, Formatter, Error}; use core::num::traits::Bounded; use crate::parent_hash; -use utils::{bit_shifts::{shl, shr}, sort::bubble_sort, partial_ord::PartialOrdTupleU64Felt252}; +use utils::{bit_shifts::shr, sort::bubble_sort, partial_ord::PartialOrdTupleU64Felt252}; /// Utreexo inclusion proof for multiple outputs. /// Compatible with https://github.com/utreexo/utreexo From 2c3e06c50625bd5aaf60a70056571560fd52ea76 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 14:52:33 +0200 Subject: [PATCH 16/32] test structure --- packages/utreexo/src/stump/accumulator.cairo | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/utreexo/src/stump/accumulator.cairo b/packages/utreexo/src/stump/accumulator.cairo index d7aff28d..c2db07fa 100644 --- a/packages/utreexo/src/stump/accumulator.cairo +++ b/packages/utreexo/src/stump/accumulator.cairo @@ -21,7 +21,6 @@ pub impl StumpUtreexoAccumulatorImpl of StumpUtreexoAccumulator { let mut number_matched_roots: u32 = 0; - // Should we reverse *self.roots like in rustreexo to reduce the number of iteration? for i in 0 ..computed_roots .len() { @@ -52,3 +51,20 @@ pub impl StumpUtreexoAccumulatorImpl of StumpUtreexoAccumulator { *self } } + +#[cfg(test)] +mod tests { + use super::{UtreexoStumpState, StumpUtreexoAccumulator, UtreexoBatchProof}; + + #[test] + fn test_verification_1() { + let state = UtreexoStumpState { roots: array![].span(), num_leaves: 0 }; + + let batch_proof = UtreexoBatchProof { targets: array![].span(), proof: array![].span() }; + + let target_hashes = array![]; + + let result = state.verify(@batch_proof, target_hashes.span(), ); + assert_eq!(result, Result::Ok(())); + } +} \ No newline at end of file From e753dfcadf2adb4d0867fac8a64e5b1888852336 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 14:53:14 +0200 Subject: [PATCH 17/32] fmt --- packages/utreexo/src/stump/accumulator.cairo | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/utreexo/src/stump/accumulator.cairo b/packages/utreexo/src/stump/accumulator.cairo index c2db07fa..949fd746 100644 --- a/packages/utreexo/src/stump/accumulator.cairo +++ b/packages/utreexo/src/stump/accumulator.cairo @@ -59,12 +59,10 @@ mod tests { #[test] fn test_verification_1() { let state = UtreexoStumpState { roots: array![].span(), num_leaves: 0 }; - let batch_proof = UtreexoBatchProof { targets: array![].span(), proof: array![].span() }; + let del_hashes = array![]; - let target_hashes = array![]; - - let result = state.verify(@batch_proof, target_hashes.span(), ); + let result = state.verify(@batch_proof, del_hashes.span(),); assert_eq!(result, Result::Ok(())); } -} \ No newline at end of file +} From 3b7f0fb0d23b88e840854139938163dd637a11d7 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 15:34:46 +0200 Subject: [PATCH 18/32] address michael comments --- packages/utils/src/lib.cairo | 1 - packages/utils/src/partial_ord.cairo | 47 ------------------------ packages/utreexo/src/stump/proof.cairo | 51 ++++++++++++++++++++++---- 3 files changed, 44 insertions(+), 55 deletions(-) delete mode 100644 packages/utils/src/partial_ord.cairo diff --git a/packages/utils/src/lib.cairo b/packages/utils/src/lib.cairo index 49ad2e8c..83654512 100644 --- a/packages/utils/src/lib.cairo +++ b/packages/utils/src/lib.cairo @@ -4,7 +4,6 @@ pub mod double_sha256; pub mod hash; pub mod merkle_tree; pub mod numeric; -pub mod partial_ord; pub mod sha256; pub mod sort; diff --git a/packages/utils/src/partial_ord.cairo b/packages/utils/src/partial_ord.cairo deleted file mode 100644 index a3b01e0f..00000000 --- a/packages/utils/src/partial_ord.cairo +++ /dev/null @@ -1,47 +0,0 @@ -/// PartialOrd implementation for tuple (u32, felt252). -/// Required to sort leaf nodes in proof verification. -pub impl PartialOrdTupleU64Felt252 of PartialOrd<(u64, felt252)> { - fn le(lhs: (u64, felt252), rhs: (u64, felt252)) -> bool { - let (a, _) = lhs; - let (b, _) = rhs; - - if a <= b { - true - } else { - false - } - } - - fn ge(lhs: (u64, felt252), rhs: (u64, felt252)) -> bool { - let (a, _) = lhs; - let (b, _) = rhs; - - if a >= b { - true - } else { - false - } - } - - fn lt(lhs: (u64, felt252), rhs: (u64, felt252)) -> bool { - let (a, _) = lhs; - let (b, _) = rhs; - - if a < b { - true - } else { - false - } - } - - fn gt(lhs: (u64, felt252), rhs: (u64, felt252)) -> bool { - let (a, _) = lhs; - let (b, _) = rhs; - - if a > b { - true - } else { - false - } - } -} diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index 0381cb59..89e8ebaa 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -1,7 +1,7 @@ use core::fmt::{Display, Formatter, Error}; use core::num::traits::Bounded; use crate::parent_hash; -use utils::{bit_shifts::shr, sort::bubble_sort, partial_ord::PartialOrdTupleU64Felt252}; +use utils::{bit_shifts::shr, sort::bubble_sort}; /// Utreexo inclusion proof for multiple outputs. /// Compatible with https://github.com/utreexo/utreexo @@ -51,6 +51,7 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { }; let mut leaf_nodes: Array<(u64, felt252)> = bubble_sort(leaf_nodes.span()); + let mut inner_result = Result::Ok((array![].span())); // Proof nodes. let mut sibling_nodes: Array = (*self.proof).into(); @@ -100,6 +101,14 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { row_len_acc += row_len; row_len /= 2; actual_row_len /= 2; + + if row_len == 0 { + inner_result = + Result::Err( + format!("Position {pos} is out of the forest range {row_len_acc}") + ); + break; + } }; // If row length is odd and we are at the edge this is a root. @@ -134,13 +143,22 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { } res } else { + if sibling_nodes.is_empty() { + inner_result = Result::Err("Proof is empty"); + break; + }; sibling_nodes.pop_front().unwrap() }; parent_hash(node, right_sibling) } else { // Left sibling always from proof. - let left_sibling = sibling_nodes.pop_front().unwrap(); - parent_hash(left_sibling, node) + match sibling_nodes.pop_front() { + Option::Some(left_sibling) => { parent_hash(left_sibling, node) }, + Option::None => { + inner_result = Result::Err("Proof is empty"); + break; + } + } }; let parent_pos = row_len_acc + row_len + (pos - row_len_acc) / 2; @@ -153,11 +171,15 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { } }; - assert(sibling_nodes.is_empty(), ''); - assert(computed_nodes.is_empty(), ''); - assert(leaf_nodes.is_empty(), ''); + if !sibling_nodes.is_empty() { + inner_result = Result::Err("Proof should be empty"); + } - Result::Ok((calculated_root_hashes.span())) + if inner_result != Result::Ok((array![].span())) { + inner_result + } else { + Result::Ok((calculated_root_hashes.span())) + } } } @@ -177,3 +199,18 @@ fn next_power_of_two(mut n: u64) -> u64 { n + 1 } + +/// PartialOrd implementation for tuple (u32, felt252). +impl PartialOrdTupleU64Felt252 of PartialOrd<(u64, felt252)> { + fn lt(lhs: (u64, felt252), rhs: (u64, felt252)) -> bool { + let (a, _) = lhs; + let (b, _) = rhs; + + if a < b { + true + } else { + false + } + } +} + From 4ff1af5064b6abd37e8a171b01f98b497387b27d Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 15:36:47 +0200 Subject: [PATCH 19/32] return directly for last check instead of assigning to inner_result --- packages/utreexo/src/stump/proof.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index 89e8ebaa..33147b73 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -172,7 +172,7 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { }; if !sibling_nodes.is_empty() { - inner_result = Result::Err("Proof should be empty"); + return Result::Err("Proof should be empty"); } if inner_result != Result::Ok((array![].span())) { From b6a4f011179cf88d406a5bc52d1ea221ff088ee6 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 15:55:35 +0200 Subject: [PATCH 20/32] use pop_front().unwrap_or pattern --- packages/utreexo/src/stump/proof.cairo | 40 ++++++++------------------ 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index 33147b73..8aca68bc 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -73,23 +73,15 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { while row_len != 0 { let (pos, node) = if next_leaf_pos < next_computed_pos { let res = (next_leaf_pos, next_leaf); - if leaf_nodes.is_empty() { - next_leaf_pos = Bounded::::MAX; - } else { - let (a, b) = leaf_nodes.pop_front().unwrap(); - next_leaf_pos = a; - next_leaf = b; - } + let (a, b) = leaf_nodes.pop_front().unwrap_or((Bounded::::MAX, 0)); + next_leaf_pos = a; + next_leaf = b; res } else if next_computed_pos != Bounded::::MAX { let res = (next_computed_pos, next_computed); - if computed_nodes.is_empty() { - next_computed_pos = Bounded::::MAX; - } else { - let (a, b) = computed_nodes.pop_front().unwrap(); - next_computed_pos = a; - next_computed = b; - } + let (a, b) = computed_nodes.pop_front().unwrap_or((Bounded::::MAX, 0)); + next_computed_pos = a; + next_computed = b; res } else { // Out of nodes, terminating here. @@ -124,23 +116,15 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { // Right sibling can be both leaf/computed or proof. let right_sibling = if next_leaf_pos == pos + 1 { let res = next_leaf; - if leaf_nodes.is_empty() { - next_leaf_pos = Bounded::::MAX; - } else { - let (a, b) = leaf_nodes.pop_front().unwrap(); - next_leaf_pos = a; - next_leaf = b; - } + let (a, b) = leaf_nodes.pop_front().unwrap_or((Bounded::::MAX, 0)); + next_leaf_pos = a; + next_leaf = b; res } else if next_computed_pos == pos + 1 { let res = next_computed; - if computed_nodes.is_empty() { - next_computed_pos = Bounded::::MAX; - } else { - let (a, b) = computed_nodes.pop_front().unwrap(); - next_computed_pos = a; - next_computed = b; - } + let (a, b) = computed_nodes.pop_front().unwrap_or((Bounded::::MAX, 0)); + next_computed_pos = a; + next_computed = b; res } else { if sibling_nodes.is_empty() { From 89dda77f8097de8e09d3dd4250bab87ff121328e Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 16:04:01 +0200 Subject: [PATCH 21/32] use let Option::Some(left_sibling) = sibling_nodes.pop_front() pattern --- packages/utreexo/src/stump/proof.cairo | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index 8aca68bc..bb53689e 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -136,12 +136,11 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { parent_hash(node, right_sibling) } else { // Left sibling always from proof. - match sibling_nodes.pop_front() { - Option::Some(left_sibling) => { parent_hash(left_sibling, node) }, - Option::None => { - inner_result = Result::Err("Proof is empty"); - break; - } + if let Option::Some(left_sibling) = sibling_nodes.pop_front() { + parent_hash(left_sibling, node) + } else { + inner_result = Result::Err("Proof is empty"); + break; } }; From 30f0c9f5b4daaadb35aa00f6a7e0e438e75d22be Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 18:45:14 +0200 Subject: [PATCH 22/32] first test with 1 root 2 leaves --- packages/utreexo/src/stump/accumulator.cairo | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/utreexo/src/stump/accumulator.cairo b/packages/utreexo/src/stump/accumulator.cairo index 949fd746..4d18d003 100644 --- a/packages/utreexo/src/stump/accumulator.cairo +++ b/packages/utreexo/src/stump/accumulator.cairo @@ -58,9 +58,9 @@ mod tests { #[test] fn test_verification_1() { - let state = UtreexoStumpState { roots: array![].span(), num_leaves: 0 }; - let batch_proof = UtreexoBatchProof { targets: array![].span(), proof: array![].span() }; - let del_hashes = array![]; + let state = UtreexoStumpState { roots: array![Option::Some(0x371cb6995ea5e7effcd2e174de264b5b407027a75a231a70c2c8d196107f0e7)].span(), num_leaves: 2 }; + let batch_proof = UtreexoBatchProof { targets: array![0].span(), proof: array![2].span() }; + let del_hashes = array![1]; let result = state.verify(@batch_proof, del_hashes.span(),); assert_eq!(result, Result::Ok(())); From 7c125291707c55581c1c494fe6dd0cf40a01f69f Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 20:54:16 +0200 Subject: [PATCH 23/32] address Michael comment - loop over del_hashes instead of proof targets --- packages/utreexo/src/stump/proof.cairo | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index bb53689e..ef1d0449 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -35,7 +35,7 @@ impl UtreexoBatchProofDisplay of Display { pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { /// Computes a set of roots given a proof and leaves hashes. fn compute_roots( - self: @UtreexoBatchProof, del_hashes: Span, num_leaves: u64, + self: @UtreexoBatchProof, mut del_hashes: Span, num_leaves: u64, ) -> Result, ByteArray> { // Where all the parent hashes we've calculated in a given row will go to. let mut calculated_root_hashes: Array = array![]; @@ -43,11 +43,11 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { let mut leaf_nodes: Array<(u64, felt252)> = array![]; // Append targets with their hashes. - let mut i = 0; - while i != (*self.targets).len() { - let pos = *self.targets[i]; - leaf_nodes.append((pos, *del_hashes[i])); - i += 1; + let mut positions = *self.targets; + while let Option::Some(rhs) = del_hashes.pop_front() { + if let Option::Some(lhs) = positions.pop_front() { + leaf_nodes.append((*lhs, *rhs)); + } }; let mut leaf_nodes: Array<(u64, felt252)> = bubble_sort(leaf_nodes.span()); From 7fcddd5e70b921bc580b8d0464033020ab57b479 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 21:38:38 +0200 Subject: [PATCH 24/32] add more tests --- packages/utreexo/src/stump/accumulator.cairo | 87 +++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/packages/utreexo/src/stump/accumulator.cairo b/packages/utreexo/src/stump/accumulator.cairo index 4d18d003..cd324f30 100644 --- a/packages/utreexo/src/stump/accumulator.cairo +++ b/packages/utreexo/src/stump/accumulator.cairo @@ -58,11 +58,96 @@ mod tests { #[test] fn test_verification_1() { - let state = UtreexoStumpState { roots: array![Option::Some(0x371cb6995ea5e7effcd2e174de264b5b407027a75a231a70c2c8d196107f0e7)].span(), num_leaves: 2 }; + let state = UtreexoStumpState { + roots: array![ + Option::Some(0x371cb6995ea5e7effcd2e174de264b5b407027a75a231a70c2c8d196107f0e7) + ] + .span(), + num_leaves: 2 + }; let batch_proof = UtreexoBatchProof { targets: array![0].span(), proof: array![2].span() }; let del_hashes = array![1]; let result = state.verify(@batch_proof, del_hashes.span(),); assert_eq!(result, Result::Ok(())); } + + #[test] + fn test_verification_2() { + let state = UtreexoStumpState { + roots: array![ + Option::Some(0x79b32f615bbd57783700ae5f8e7b1ef79677c3545c4c69dc31b3aecce1d8fa6), + Option::Some(0x371cb6995ea5e7effcd2e174de264b5b407027a75a231a70c2c8d196107f0e7), + Option::Some(0x22d481b177090ea8db58ceece7d8493e746d690a1708d438c6c4e51b23c81ee) + ] + .span(), + num_leaves: 5 + }; + let batch_proof = UtreexoBatchProof { + targets: array![0].span(), + proof: array![0x2, 0x22d481b177090ea8db58ceece7d8493e746d690a1708d438c6c4e51b23c81ee] + .span() + }; + let del_hashes = array![1]; + + let result = state.verify(@batch_proof, del_hashes.span(),); + assert_eq!(result, Result::Ok(())); + } + + #[test] + fn test_verification_3() { + let state = UtreexoStumpState { + roots: array![ + Option::Some(0x79b32f615bbd57783700ae5f8e7b1ef79677c3545c4c69dc31b3aecce1d8fa6), + Option::Some(0x371cb6995ea5e7effcd2e174de264b5b407027a75a231a70c2c8d196107f0e7), + Option::Some(0x22d481b177090ea8db58ceece7d8493e746d690a1708d438c6c4e51b23c81ee) + ] + .span(), + num_leaves: 5 + }; + let batch_proof = UtreexoBatchProof { + targets: array![1, 3].span(), proof: array![0x1, 0x3].span() + }; + let del_hashes = array![2, 4]; + + let result = state.verify(@batch_proof, del_hashes.span(),); + assert_eq!(result, Result::Ok(())); + } + + #[test] + fn test_verification_4() { + let state = UtreexoStumpState { + roots: array![ + Option::Some(0x1702d734e291ad551b886a70b96446b99e19e405511e71fb5edfc4d2d83ce92), + Option::Some(0x79b32f615bbd57783700ae5f8e7b1ef79677c3545c4c69dc31b3aecce1d8fa6), + Option::Some(0x371cb6995ea5e7effcd2e174de264b5b407027a75a231a70c2c8d196107f0e7), + Option::Some(0x22d481b177090ea8db58ceece7d8493e746d690a1708d438c6c4e51b23c81ee), + Option::Some(0xdc9cc50aff0bdadd82a05bbab54015a07fccf2a4e30fa528fdca5a35d5423f), + Option::Some(0x1160145b02735dc081307a4f20392a8139739275ad49d5c9c32190ba5fbd054), + Option::Some(0x2797a40dbb8ea4b69a4e3bb4a9ccaa21a9585fcc71f3e5bb053ccae27910f90), + Option::Some(0x770ad1be69d195e821c8c35051b32492e71592e230b950a99ebf87e98967ca), + Option::Some(0x436e91732c0a83fa238d71460463f4b1fe0dc0b1ebcbc10967a84cec9d13154), + Option::Some(0x7877cc14d4c8e76cc51aa4c49aa7aadaade0cf475ad63bb37c27c324e145393), + Option::Some(0x2392042cbfda7371c81c9d7b456563533c2d6998b9e690a0d97421e6ae51a98), + ] + .span(), + num_leaves: 15 + }; + let batch_proof = UtreexoBatchProof { + targets: array![1, 3, 10, 13].span(), + proof: array![ + 0x1, + 0x3, + 0xC, + 0xD, + 0x436e91732c0a83fa238d71460463f4b1fe0dc0b1ebcbc10967a84cec9d13154, + 0xdc9cc50aff0bdadd82a05bbab54015a07fccf2a4e30fa528fdca5a35d5423f + ] + .span() + }; + let del_hashes = array![2, 4, 11, 14]; + + let result = state.verify(@batch_proof, del_hashes.span(),); + assert_eq!(result, Result::Ok(())); + } } From 526bc8748899a12bd11290dec4d9aee5d0e7710d Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 21:43:40 +0200 Subject: [PATCH 25/32] throw an error if not enough targets in the proof --- packages/utreexo/src/stump/proof.cairo | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index ef1d0449..0832a51b 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -42,16 +42,19 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { // Target leaves let mut leaf_nodes: Array<(u64, felt252)> = array![]; + let mut inner_result = Result::Ok((array![].span())); + // Append targets with their hashes. let mut positions = *self.targets; while let Option::Some(rhs) = del_hashes.pop_front() { if let Option::Some(lhs) = positions.pop_front() { leaf_nodes.append((*lhs, *rhs)); + } else { + inner_result = Result::Err("Not enough targets in the proof."); } }; let mut leaf_nodes: Array<(u64, felt252)> = bubble_sort(leaf_nodes.span()); - let mut inner_result = Result::Ok((array![].span())); // Proof nodes. let mut sibling_nodes: Array = (*self.proof).into(); @@ -97,7 +100,7 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { if row_len == 0 { inner_result = Result::Err( - format!("Position {pos} is out of the forest range {row_len_acc}") + format!("Position {pos} is out of the forest range {row_len_acc}.") ); break; } @@ -128,7 +131,7 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { res } else { if sibling_nodes.is_empty() { - inner_result = Result::Err("Proof is empty"); + inner_result = Result::Err("Proof is empty."); break; }; sibling_nodes.pop_front().unwrap() @@ -139,7 +142,7 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { if let Option::Some(left_sibling) = sibling_nodes.pop_front() { parent_hash(left_sibling, node) } else { - inner_result = Result::Err("Proof is empty"); + inner_result = Result::Err("Proof is empty."); break; } }; From 16115c980a20dda18afe4e47392d15599a4d8b2b Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 21:49:19 +0200 Subject: [PATCH 26/32] remove unusued import in validation/script --- packages/consensus/src/validation/script.cairo | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/consensus/src/validation/script.cairo b/packages/consensus/src/validation/script.cairo index cfcdf5d1..d0491ccb 100644 --- a/packages/consensus/src/validation/script.cairo +++ b/packages/consensus/src/validation/script.cairo @@ -1,4 +1,3 @@ -use shinigami_engine::errors::byte_array_err; use shinigami_engine::engine::EngineTrait; use shinigami_engine::engine::EngineImpl; use shinigami_engine::hash_cache::HashCacheImpl; From 373affd5a2b12ea79d6f19750a8f575853467520 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 22:25:47 +0200 Subject: [PATCH 27/32] add tests with 15 and 30 leaves + multiple targets --- packages/utreexo/src/stump/accumulator.cairo | 84 +++++++++----------- 1 file changed, 37 insertions(+), 47 deletions(-) diff --git a/packages/utreexo/src/stump/accumulator.cairo b/packages/utreexo/src/stump/accumulator.cairo index cd324f30..993c7414 100644 --- a/packages/utreexo/src/stump/accumulator.cairo +++ b/packages/utreexo/src/stump/accumulator.cairo @@ -76,19 +76,27 @@ mod tests { fn test_verification_2() { let state = UtreexoStumpState { roots: array![ - Option::Some(0x79b32f615bbd57783700ae5f8e7b1ef79677c3545c4c69dc31b3aecce1d8fa6), - Option::Some(0x371cb6995ea5e7effcd2e174de264b5b407027a75a231a70c2c8d196107f0e7), - Option::Some(0x22d481b177090ea8db58ceece7d8493e746d690a1708d438c6c4e51b23c81ee) + Option::Some(0x1702d734e291ad551b886a70b96446b99e19e405511e71fb5edfc4d2d83ce92), + Option::Some(0x770ad1be69d195e821c8c35051b32492e71592e230b950a99ebf87e98967ca), + Option::Some(0x2392042cbfda7371c81c9d7b456563533c2d6998b9e690a0d97421e6ae51a98), + Option::Some(0xf), ] .span(), - num_leaves: 5 + num_leaves: 15 }; let batch_proof = UtreexoBatchProof { - targets: array![0].span(), - proof: array![0x2, 0x22d481b177090ea8db58ceece7d8493e746d690a1708d438c6c4e51b23c81ee] + targets: array![1, 3, 10, 13].span(), + proof: array![ + 0x1, + 0x3, + 0xC, + 0xD, + 0x436e91732c0a83fa238d71460463f4b1fe0dc0b1ebcbc10967a84cec9d13154, + 0xdc9cc50aff0bdadd82a05bbab54015a07fccf2a4e30fa528fdca5a35d5423f + ] .span() }; - let del_hashes = array![1]; + let del_hashes = array![2, 4, 11, 14]; let result = state.verify(@batch_proof, del_hashes.span(),); assert_eq!(result, Result::Ok(())); @@ -98,54 +106,36 @@ mod tests { fn test_verification_3() { let state = UtreexoStumpState { roots: array![ - Option::Some(0x79b32f615bbd57783700ae5f8e7b1ef79677c3545c4c69dc31b3aecce1d8fa6), - Option::Some(0x371cb6995ea5e7effcd2e174de264b5b407027a75a231a70c2c8d196107f0e7), - Option::Some(0x22d481b177090ea8db58ceece7d8493e746d690a1708d438c6c4e51b23c81ee) + Option::Some(0x519631921e4905a63203f0cca7f6e6917082f30cef0930aa05bdc4323f6a398), + Option::Some(0x5198dcd61c969dfa8396dd27439ab776d120c2d67294fbcded0aa5f658f9150), + Option::Some(0x21d7ab8efac0146b5b47c8ad5431c3d14d9210319b0be7428fb2382ef115671), + Option::Some(0x74f794e653e00357d8a8ed45fcb74659841190c0821aa4e20bc4e30b2f3dd20), ] .span(), - num_leaves: 5 + num_leaves: 30 }; let batch_proof = UtreexoBatchProof { - targets: array![1, 3].span(), proof: array![0x1, 0x3].span() - }; - let del_hashes = array![2, 4]; - - let result = state.verify(@batch_proof, del_hashes.span(),); - assert_eq!(result, Result::Ok(())); - } - - #[test] - fn test_verification_4() { - let state = UtreexoStumpState { - roots: array![ - Option::Some(0x1702d734e291ad551b886a70b96446b99e19e405511e71fb5edfc4d2d83ce92), - Option::Some(0x79b32f615bbd57783700ae5f8e7b1ef79677c3545c4c69dc31b3aecce1d8fa6), - Option::Some(0x371cb6995ea5e7effcd2e174de264b5b407027a75a231a70c2c8d196107f0e7), - Option::Some(0x22d481b177090ea8db58ceece7d8493e746d690a1708d438c6c4e51b23c81ee), - Option::Some(0xdc9cc50aff0bdadd82a05bbab54015a07fccf2a4e30fa528fdca5a35d5423f), - Option::Some(0x1160145b02735dc081307a4f20392a8139739275ad49d5c9c32190ba5fbd054), - Option::Some(0x2797a40dbb8ea4b69a4e3bb4a9ccaa21a9585fcc71f3e5bb053ccae27910f90), - Option::Some(0x770ad1be69d195e821c8c35051b32492e71592e230b950a99ebf87e98967ca), - Option::Some(0x436e91732c0a83fa238d71460463f4b1fe0dc0b1ebcbc10967a84cec9d13154), - Option::Some(0x7877cc14d4c8e76cc51aa4c49aa7aadaade0cf475ad63bb37c27c324e145393), - Option::Some(0x2392042cbfda7371c81c9d7b456563533c2d6998b9e690a0d97421e6ae51a98), - ] - .span(), - num_leaves: 15 - }; - let batch_proof = UtreexoBatchProof { - targets: array![1, 3, 10, 13].span(), + targets: array![4, 8, 12, 16, 20, 24, 28].span(), proof: array![ - 0x1, - 0x3, - 0xC, - 0xD, - 0x436e91732c0a83fa238d71460463f4b1fe0dc0b1ebcbc10967a84cec9d13154, - 0xdc9cc50aff0bdadd82a05bbab54015a07fccf2a4e30fa528fdca5a35d5423f + 0x6, + 0xA, + 0xE, + 0x12, + 0x16, + 0x1A, + 0x1E, + 0x2797a40dbb8ea4b69a4e3bb4a9ccaa21a9585fcc71f3e5bb053ccae27910f90, + 0x7877cc14d4c8e76cc51aa4c49aa7aadaade0cf475ad63bb37c27c324e145393, + 0x556ea8bad1db13c6bdc3150a8289cd12044fb7e03cf201f35924a8afd4265a6, + 0x41a4ec75a27497daa51261588a60f0956d3fd61e521634bbf36bba6343c3a1b, + 0x3ba731d3734536d7cd5382cb4004ca4c24f1325b6fbeae27bcd6b4f9c0ed714, + 0x117ed04a65093683f13c16cf73d2855f1f099a96581d1dad74eaf34c9a343c8, + 0x79b32f615bbd57783700ae5f8e7b1ef79677c3545c4c69dc31b3aecce1d8fa6 ] .span() }; - let del_hashes = array![2, 4, 11, 14]; + + let del_hashes = array![5, 9, 13, 17, 21, 25, 29]; let result = state.verify(@batch_proof, del_hashes.span(),); assert_eq!(result, Result::Ok(())); From c0e44697c32dba94d657112fdfd4532820f1e818 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 22:30:24 +0200 Subject: [PATCH 28/32] remove is empty target check --- packages/utreexo/src/stump/accumulator.cairo | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/utreexo/src/stump/accumulator.cairo b/packages/utreexo/src/stump/accumulator.cairo index 993c7414..9213e82a 100644 --- a/packages/utreexo/src/stump/accumulator.cairo +++ b/packages/utreexo/src/stump/accumulator.cairo @@ -13,12 +13,7 @@ pub impl StumpUtreexoAccumulatorImpl of StumpUtreexoAccumulator { fn verify( self: @UtreexoStumpState, proof: @UtreexoBatchProof, del_hashes: Span ) -> Result<(), ByteArray> { - if (*proof.targets).is_empty() { - return Result::Ok(()); - }; - let computed_roots: Span = proof.compute_roots(del_hashes, *self.num_leaves)?; - let mut number_matched_roots: u32 = 0; for i in 0 From 77909776b2a74aed9bcab025a732ad910fc725f1 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 22:36:04 +0200 Subject: [PATCH 29/32] add comment to verify function --- packages/utreexo/src/stump/accumulator.cairo | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/utreexo/src/stump/accumulator.cairo b/packages/utreexo/src/stump/accumulator.cairo index 9213e82a..0df03254 100644 --- a/packages/utreexo/src/stump/accumulator.cairo +++ b/packages/utreexo/src/stump/accumulator.cairo @@ -3,13 +3,14 @@ use crate::stump::proof::{UtreexoBatchProof, UtreexoBatchProofTrait}; #[generate_trait] pub impl StumpUtreexoAccumulatorImpl of StumpUtreexoAccumulator { - /// Adds multiple items to the accumulator + /// Adds multiple items to the accumulator. fn add(self: @UtreexoStumpState, hashes: Span) -> UtreexoStumpState { // TODO: check if vanilla implementation is compatible with rustreexo, add tests // https://github.com/mit-dci/rustreexo/blob/6a8fe53fa545df8f1a30733fa6ca9f7b0077d051/src/accumulator/stump.rs#L252 *self } + /// Verifies that one or multiple leaves hashes are part of the utreexo forest given a proof. fn verify( self: @UtreexoStumpState, proof: @UtreexoBatchProof, del_hashes: Span ) -> Result<(), ByteArray> { From 9b050ee786e08c7deb79a5cde88d3884ad3deaa1 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Mon, 14 Oct 2024 22:38:31 +0200 Subject: [PATCH 30/32] fmt --- packages/utreexo/src/stump/proof.cairo | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index 0832a51b..9f3c05d9 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -60,17 +60,17 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { let mut sibling_nodes: Array = (*self.proof).into(); // Queue of computed intermediate nodes. let mut computed_nodes: Array<(u64, felt252)> = array![]; - // Actual length of the current row + // Actual length of the current row. let mut actual_row_len: u64 = num_leaves; // Length of the "padded" row which is always power of two. let mut row_len: u64 = next_power_of_two(num_leaves); - // Total padded length of processed rows (excluding the current one) + // Total padded length of processed rows (excluding the current one). let mut row_len_acc: u64 = 0; // Next position of the target leaf and the leaf itself. let (mut next_leaf_pos, mut next_leaf) = leaf_nodes.pop_front().unwrap(); - // Next computed node + // Next computed node. let mut next_computed: felt252 = 0; - // Position of the next computed node + // Position of the next computed node. let mut next_computed_pos: u64 = Bounded::::MAX; while row_len != 0 { From 90cd6135681168b15ad1b5d3bac5ea91daf3887b Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Tue, 15 Oct 2024 00:16:32 +0200 Subject: [PATCH 31/32] add u64_next_power_of_two to utils + unit tests --- packages/utils/src/numeric.cairo | 86 +++++++++++++++++++++++++- packages/utreexo/src/stump/proof.cairo | 21 +------ 2 files changed, 87 insertions(+), 20 deletions(-) diff --git a/packages/utils/src/numeric.cairo b/packages/utils/src/numeric.cairo index 1ad6c817..5c7f4bf4 100644 --- a/packages/utils/src/numeric.cairo +++ b/packages/utils/src/numeric.cairo @@ -1,3 +1,5 @@ +use crate::bit_shifts::shr; + /// Reverses the byte order of a `u32`. /// /// This function takes a 32-bit unsigned integer and reverses the order of its bytes. @@ -10,9 +12,26 @@ pub fn u32_byte_reverse(word: u32) -> u32 { return byte0 + byte1 + byte2 + byte3; } +/// Computes the next power of two of a u64 variable. +pub fn u64_next_power_of_two(mut n: u64) -> u64 { + if n == 0 { + return 1; + } + + n -= 1; + n = n | shr(n, 1_u64); + n = n | shr(n, 2_u64); + n = n | shr(n, 4_u64); + n = n | shr(n, 8_u64); + n = n | shr(n, 16_u64); + n = n | shr(n, 32_u64); + + n + 1 +} + #[cfg(test)] mod tests { - use super::u32_byte_reverse; + use super::{u32_byte_reverse, u64_next_power_of_two}; #[test] fn test_u32_byte_reverse() { @@ -41,4 +60,69 @@ mod tests { let result = u32_byte_reverse(input); assert_eq!(result, expected_output); } + + #[test] + fn test_u64_next_power_of_two() { + let input: u64 = 3; + let expected_output: u64 = 4; + let result = u64_next_power_of_two(input); + assert_eq!(result, expected_output); + + let input: u64 = 5; + let expected_output: u64 = 8; + let result = u64_next_power_of_two(input); + assert_eq!(result, expected_output); + + let input: u64 = 11; + let expected_output: u64 = 16; + let result = u64_next_power_of_two(input); + assert_eq!(result, expected_output); + + let input: u64 = 20; + let expected_output: u64 = 32; + let result = u64_next_power_of_two(input); + assert_eq!(result, expected_output); + + let input: u64 = 61; + let expected_output: u64 = 64; + let result = u64_next_power_of_two(input); + assert_eq!(result, expected_output); + + let input: u64 = 100; + let expected_output: u64 = 128; + let result = u64_next_power_of_two(input); + assert_eq!(result, expected_output); + + let input: u64 = 189; + let expected_output: u64 = 256; + let result = u64_next_power_of_two(input); + assert_eq!(result, expected_output); + + let input: u64 = 480; + let expected_output: u64 = 512; + let result = u64_next_power_of_two(input); + assert_eq!(result, expected_output); + + let input: u64 = 777; + let expected_output: u64 = 1024; + let result = u64_next_power_of_two(input); + assert_eq!(result, expected_output); + + let input: u64 = 1025; + let expected_output: u64 = 2048; + let result = u64_next_power_of_two(input); + assert_eq!(result, expected_output); + + let input: u64 = 4095; + let expected_output: u64 = 4096; + let result = u64_next_power_of_two(input); + assert_eq!(result, expected_output); + + let input: u64 = 1500000000000000000; + let expected_output: u64 = 2305843009213693952; + let result = u64_next_power_of_two(input); + assert_eq!(result, expected_output); + } + + } diff --git a/packages/utreexo/src/stump/proof.cairo b/packages/utreexo/src/stump/proof.cairo index 9f3c05d9..27cce7cf 100644 --- a/packages/utreexo/src/stump/proof.cairo +++ b/packages/utreexo/src/stump/proof.cairo @@ -1,7 +1,7 @@ use core::fmt::{Display, Formatter, Error}; use core::num::traits::Bounded; use crate::parent_hash; -use utils::{bit_shifts::shr, sort::bubble_sort}; +use utils::{numeric::u64_next_power_of_two, sort::bubble_sort}; /// Utreexo inclusion proof for multiple outputs. /// Compatible with https://github.com/utreexo/utreexo @@ -63,7 +63,7 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { // Actual length of the current row. let mut actual_row_len: u64 = num_leaves; // Length of the "padded" row which is always power of two. - let mut row_len: u64 = next_power_of_two(num_leaves); + let mut row_len: u64 = u64_next_power_of_two(num_leaves); // Total padded length of processed rows (excluding the current one). let mut row_len_acc: u64 = 0; // Next position of the target leaf and the leaf itself. @@ -169,23 +169,6 @@ pub impl UtreexoBatchProofImpl of UtreexoBatchProofTrait { } } -/// Computes the next power of two of a u64 variable. -fn next_power_of_two(mut n: u64) -> u64 { - if n == 0 { - return 1; - } - - n -= 1; - n = n | shr(n, 1_u64); - n = n | shr(n, 2_u64); - n = n | shr(n, 4_u64); - n = n | shr(n, 8_u64); - n = n | shr(n, 16_u64); - n = n | shr(n, 32_u64); - - n + 1 -} - /// PartialOrd implementation for tuple (u32, felt252). impl PartialOrdTupleU64Felt252 of PartialOrd<(u64, felt252)> { fn lt(lhs: (u64, felt252), rhs: (u64, felt252)) -> bool { From cdb4cd54a24f89d2c2afefbf3bdab190001b436f Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Tue, 15 Oct 2024 00:16:42 +0200 Subject: [PATCH 32/32] fmt --- packages/utils/src/numeric.cairo | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/utils/src/numeric.cairo b/packages/utils/src/numeric.cairo index 5c7f4bf4..91f7d970 100644 --- a/packages/utils/src/numeric.cairo +++ b/packages/utils/src/numeric.cairo @@ -123,6 +123,4 @@ mod tests { let result = u64_next_power_of_two(input); assert_eq!(result, expected_output); } - - }