diff --git a/src/accumulator/proof.rs b/src/accumulator/proof.rs index e3e4601..843920c 100644 --- a/src/accumulator/proof.rs +++ b/src/accumulator/proof.rs @@ -106,7 +106,7 @@ impl Proof { /// let hash = Sha256::from_engine(engine); /// hashes.push(hash); /// } - /// let s = s.modify(&hashes, &vec![], &Proof::default()).unwrap(); + /// let s = s.modify(&hashes, &vec![], &Proof::default()).unwrap().0; /// let p = Proof::new(targets, proof_hashes); /// assert!(p.verify(&vec![hashes[0]] , &s).expect("This proof is valid")); ///``` @@ -278,7 +278,8 @@ mod tests { // Create a new stump with 8 leaves and 1 root let s = Stump::new() .modify(&hashes, &vec![], &Proof::default()) - .expect("This stump is valid"); + .expect("This stump is valid") + .0; // Nodes that will be deleted let del_hashes = vec![hashes[0], hashes[2], hashes[4], hashes[6]]; @@ -365,7 +366,8 @@ mod tests { s = s .modify(&hashes, &vec![], &Proof::default()) - .expect("Test stump is valid"); + .expect("Test stump is valid") + .0; } else { panic!("Missing test data"); } diff --git a/src/accumulator/stump.rs b/src/accumulator/stump.rs index baf0e66..3d8ff4e 100644 --- a/src/accumulator/stump.rs +++ b/src/accumulator/stump.rs @@ -1,4 +1,4 @@ -use super::{proof::Proof, types}; +use super::{proof::Proof, types, util}; use bitcoin_hashes::sha256; use std::vec; @@ -7,6 +7,15 @@ pub struct Stump { pub leafs: u64, pub roots: Vec, } +#[derive(Debug, Clone, Default)] +pub struct UpdateData { + /// to_destroy is the positions of the empty roots removed after the add. + pub(crate) to_destroy: Vec, + /// pre_num_leaves is the numLeaves of the stump before the add. + pub(crate) prev_num_leaves: u64, + /// new_add are the new hashes for the newly created roots after the addition. + pub(crate) new_add: Vec<(u64, sha256::Hash)>, +} impl Stump { /// Creates an empty Stump @@ -36,21 +45,23 @@ impl Stump { /// let stxos = vec![]; /// let s = s.modify(&utxos, &stxos, &Proof::default()); /// assert!(s.is_ok()); - /// assert_eq!(s.unwrap().roots, utxos); + /// assert_eq!(s.unwrap().0.roots, utxos); /// ``` pub fn modify( &self, utxos: &Vec, del_hashes: &Vec, proof: &Proof, - ) -> Result { + ) -> Result<(Stump, UpdateData), String> { let mut root_candidates = proof .calculate_hashes(del_hashes, self)? .1 .into_iter() .rev() .peekable(); - let mut computed_roots = self.remove(del_hashes, proof)?.into_iter().rev(); + + let (_, computed_roots) = self.remove(del_hashes, proof)?; + let mut computed_roots = computed_roots.into_iter().rev(); let mut new_roots = vec![]; @@ -68,12 +79,19 @@ impl Stump { new_roots.push(*root); } - let roots = Stump::add(new_roots, utxos, self.leafs); + let (roots, updated, destroyed) = Stump::add(new_roots, utxos, self.leafs); - Ok(Stump { + let new_stump = Stump { leafs: self.leafs + utxos.len() as u64, - roots: roots, - }) + roots, + }; + let update_data = UpdateData { + new_add: updated, + prev_num_leaves: self.leafs, + to_destroy: destroyed, + }; + + Ok((new_stump, update_data)) } /// Rewinds old tree state, this should be used in case of reorgs. @@ -84,9 +102,9 @@ impl Stump { /// let s_old = Stump::new(); /// let mut s_new = Stump::new(); /// - /// let s_old = s_old.modify(&vec![], &vec![], &Proof::default()).unwrap(); + /// let s_old = s_old.modify(&vec![], &vec![], &Proof::default()).unwrap().0; /// s_new = s_old.clone(); - /// s_new = s_new.modify(&vec![], &vec![], &Proof::default()).unwrap(); + /// s_new = s_new.modify(&vec![], &vec![], &Proof::default()).unwrap().0; /// /// // A reorg happened /// s_new.undo(s_old); @@ -100,53 +118,61 @@ impl Stump { &self, del_hashes: &Vec, proof: &Proof, - ) -> Result, String> { + ) -> Result<(Vec<(u64, sha256::Hash)>, Vec), String> { if del_hashes.len() == 0 { - return Ok(self.roots.clone()); + return Ok((vec![], self.roots.clone())); } let del_hashes = vec![sha256::Hash::default(); proof.targets()]; - let (_, new_roots) = proof.calculate_hashes(&del_hashes, self)?; - - Ok(new_roots) + proof.calculate_hashes(&del_hashes, self) } /// Adds new leafs into the root fn add( mut roots: Vec, utxos: &Vec, - mut leafs: u64, - ) -> Vec { - for i in utxos.iter() { - Stump::add_single(&mut roots, *i, leafs); - leafs += 1; - } - - roots - } - - fn add_single( - roots: &mut Vec, - node: bitcoin_hashes::sha256::Hash, - leafs: u64, - ) { - let mut h = 0; - // Iterates over roots, if we find a root that is not empty, we concatenate with - // the one we are adding and create new root, leaving this position empty. Stops - // when find an empty root. - - // You can say if a root is empty, by looking a the binary representations of the - // number of leafs. If the h'th bit is one, then this position is occupied, empty - // otherwise. - let mut to_add = node; - while (leafs >> h) & 1 == 1 { - let root = roots.pop(); - if let Some(root) = root { - to_add = types::parent_hash(&root, &to_add); + mut leaves: u64, + ) -> (Vec, Vec<(u64, sha256::Hash)>, Vec) { + let after_rows = util::tree_rows(leaves + (utxos.len() as u64)); + let mut updated_subtree: Vec<(u64, sha256::Hash)> = vec![]; + let all_deleted = util::roots_to_destroy(utxos.len() as u64, leaves, &roots); + + for (i, add) in utxos.iter().enumerate() { + let mut pos = leaves; + + // deleted is the empty roots that are being added over. These force + // the current root to move up. + let deleted = util::roots_to_destroy((utxos.len() - i) as u64, leaves, &roots); + for del in deleted { + if util::is_ancestor(util::parent(del, after_rows), pos, after_rows).unwrap() { + pos = util::calc_next_pos(pos, del, after_rows).unwrap(); + } + } + let mut h = 0; + // Iterates over roots, if we find a root that is not empty, we concatenate with + // the one we are adding and create new root, leaving this position empty. Stops + // when find an empty root. + + // You can say if a root is empty, by looking a the binary representations of the + // number of leafs. If the h'th bit is one, then this position is occupied, empty + // otherwise. + let mut to_add = add.clone(); + let mut pos = leaves; + while (leaves >> h) & 1 == 1 { + let root = roots.pop(); + if let Some(root) = root { + updated_subtree.push((util::left_sibling(pos), root)); + updated_subtree.push((pos, to_add)); + + pos = util::parent(pos, after_rows); + to_add = types::parent_hash(&root, &to_add); + } + h += 1; } - h += 1; - } - roots.push(to_add); + roots.push(to_add); + leaves += 1; + } + (roots, updated_subtree, all_deleted) } } @@ -167,7 +193,36 @@ mod test { assert!(s.leafs == 0); assert!(s.roots.len() == 0); } + #[test] + fn test_update_data_add() { + let preimages = vec![0, 1, 2, 3]; + let hashes = preimages + .into_iter() + .map(|preimage| hash_from_u8(preimage)) + .collect(); + + let (_, updated) = Stump::new() + .modify(&hashes, &vec![], &Proof::default()) + .unwrap(); + let positions = vec![0, 1, 2, 3, 4, 5]; + + let hashes: Vec<_> = vec![ + "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d", + "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a", + "dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986", + "084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5", + "02242b37d8e851f1e86f46790298c7097df06893d6226b7c1453c213e91717de", + "9576f4ade6e9bc3a6458b506ce3e4e890df29cb14cb5d3d887672aef55647a2b", + ] + .iter() + .map(|hash| sha256::Hash::from_str(*hash).unwrap()) + .collect(); + + let positions: Vec<_> = positions.into_iter().zip(hashes.into_iter()).collect(); + + assert_eq!(positions, updated.new_add); + } fn hash_from_u8(value: u8) -> sha256::Hash { let mut engine = bitcoin_hashes::sha256::Hash::engine(); @@ -199,11 +254,11 @@ mod test { let proof = Proof::new(target_values, proof_hashes); - let stump = Stump::new() + let (stump, _) = Stump::new() .modify(&leaf_hashes, &vec![], &Proof::default()) .expect("This stump is valid"); - let stump = stump.modify(&vec![], &target_hashes, &proof).unwrap(); + let (stump, _) = stump.modify(&vec![], &target_hashes, &proof).unwrap(); assert_eq!(stump.roots, roots); } @@ -224,7 +279,7 @@ mod test { hashes.push(hash_from_u8(i.as_u64().unwrap() as u8)); } - let s = s + let (s, _) = s .modify(&hashes, &vec![], &Proof::default()) .expect("Stump from test cases are valid"); @@ -246,7 +301,8 @@ mod test { let s_old = Stump::new(); let s_old = s_old .modify(&hashes, &vec![], &Proof::default()) - .expect("Stump from test cases are valid"); + .expect("Stump from test cases are valid") + .0; let mut s_new = s_old.clone(); diff --git a/src/accumulator/util.rs b/src/accumulator/util.rs index c260fc0..d89a7dc 100644 --- a/src/accumulator/util.rs +++ b/src/accumulator/util.rs @@ -1,6 +1,8 @@ // Rustreexo use std::vec::Vec; + +use bitcoin_hashes::{hex::FromHex, sha256}; // isRootPosition checks if the current position is a root given the number of // leaves and the entire rows of the forest. pub fn is_root_position(position: u64, num_leaves: u64, forest_rows: u8) -> bool { @@ -72,6 +74,44 @@ fn add_and_sort(mut vec: Vec, value: u64) -> Vec { pub fn is_left_niece(position: u64) -> bool { position & 1 == 0 } +pub fn left_sibling(position: u64) -> u64 { + (position | 1) ^ 1 +} +// rootsToDestory returns the empty roots that get written over after numAdds +// amount of leaves have been added. +pub fn roots_to_destroy( + num_adds: u64, + mut num_leaves: u64, + orig_roots: &Vec, +) -> Vec { + let mut roots = orig_roots.clone(); + + let mut deleted = vec![]; + let mut h = 0; + for add in 0..num_adds { + while (num_leaves >> h) & 1 == 1 { + let root = roots + .pop() + .expect("If (num_leaves >> h) & 1 == 1, it must have at least one root left"); + if root == sha256::Hash::default() { + let root_pos = + root_position(num_leaves, h, tree_rows(num_leaves + (num_adds - add))); + deleted.push(root_pos); + } + h += 1; + } + // Just adding a non-zero value to the slice. + roots.push( + sha256::Hash::from_hex( + "0000000000000000000000000000000000000000000000000000000000000001", + ) + .unwrap(), + ); + num_leaves += 1; + } + + deleted +} // detectSubTreeHight finds the rows of the subtree a given LEAF position and // the number of leaves (& the forest rows which is redundant) // Go left to right through the bits of numLeaves, @@ -347,7 +387,11 @@ pub fn get_proof_positions(targets: &Vec, num_leaves: u64, forest_rows: u8) #[cfg(test)] mod tests { - use std::vec; + use std::{str::FromStr, vec}; + + use bitcoin_hashes::sha256; + + use super::roots_to_destroy; #[test] fn test_is_sibling() { assert_eq!(super::is_sibling(0, 1), true); @@ -368,7 +412,22 @@ mod tests { fn test_is_right_sibling() { assert!(super::is_right_sibling(0, 1)); } - + #[test] + fn test_roots_to_destroy() { + let roots = vec![ + "0000000000000000000000000000000000000000000000000000000000000000", + "aad41f1d55e1a111ca193f6fa4e13dfc0cbdfbea851b30f3eacfe8d9d6be4302", + "0000000000000000000000000000000000000000000000000000000000000000", + "3c2d8cbe4336bbe05fff898102d413ab6356de2598aad4d5a7f916c5b316cb42", + ]; + let roots = roots + .iter() + .map(|hash| sha256::Hash::from_str(*hash).unwrap()) + .collect(); + let deleted = roots_to_destroy(1, 15, &roots); + + assert_eq!(deleted, vec![22, 28]) + } #[test] fn test_remove_bit() { // This should remove just one bit from the final number