diff --git a/src/accumulator/proof.rs b/src/accumulator/proof.rs index b9dc4ec..3af18d9 100644 --- a/src/accumulator/proof.rs +++ b/src/accumulator/proof.rs @@ -117,7 +117,7 @@ impl Proof { } let mut calculated_roots = self - .create_root_candidates(del_hashes, stump)? + .calculate_roots(del_hashes, stump)? .into_iter() .peekable(); @@ -137,115 +137,18 @@ impl Proof { } Ok(true) } - pub fn proof_after_deletion( - &self, - num_leafs: u64, - ) -> Result<(Vec, Proof), String> { - let total_rows = util::tree_rows(num_leafs); - let mut targets = self.targets.to_owned(); - targets.sort(); - - let proof_positions = util::get_proof_positions(&targets, num_leafs, total_rows); - - let mut proof_hashes: Vec<_> = proof_positions - .to_owned() - .into_iter() - .zip(self.hashes.to_owned()) - .collect(); - - let mut targets: Vec<_> = util::detwin(targets.to_owned(), total_rows) - .into_iter() - .rev() - .collect(); - - // Here is where our modified proof will be constructed - let mut target_hashes: Vec<(u64, bitcoin_hashes::sha256::Hash)> = vec![]; - - // For each of the targets, we'll try to find the sibling in the proof hashes - // and promote it as the parent. If it's not in the proof hashes, we'll move - // the descendants of the existing targets and proofs of the sibling's parent - // up by one row. - - while let Some(target) = targets.pop() { - if util::is_root_position(target, num_leafs, total_rows) { - target_hashes.push((target, bitcoin_hashes::sha256::Hash::default())); - continue; - } - - let sib = target ^ 1; - // Is the node sibling on hash proofs? - if let Some(idx) = proof_hashes.iter().position(|(pos, _)| sib == *pos) { - let parent_pos = util::parent(sib as u64, total_rows); - - target_hashes.push((parent_pos, proof_hashes[idx].1)); - - // Delete the sibling from proof_hashes as this sibling is a target now, not a proof. - proof_hashes.remove(idx); - } else { - // If the sibling is not in the proof hashes or the targets, - // the descendants of the sibling will be moving up. - // - // 14 - // |---------------\ - // 12 13 - // |-------\ |-------\ - // 08 09 10 11 - // |---\ |---\ |---\ |---\ - // 00 01 04 05 06 07 - - let to_update: Vec<_> = target_hashes - .iter_mut() - .filter(|(node, _)| { - let parent = util::parent(sib, total_rows); - util::is_ancestor(parent, *node, total_rows).unwrap_or(false) - }) - .collect(); - - for to_update_node in to_update { - let new_pos = util::calc_next_pos(to_update_node.0, sib, total_rows)?; - to_update_node.0 = new_pos; - } - let to_update: Vec<_> = proof_hashes - .iter_mut() - .filter(|(node, _)| { - let parent = util::parent(sib, total_rows); - util::is_ancestor(parent, *node, total_rows).unwrap_or(false) - }) - .collect(); - - for node_to_update in to_update { - let next_pos = util::calc_next_pos(node_to_update.0, sib, total_rows)?; - node_to_update.0 = next_pos; - } - } - } - let mut new_proof_hashes = vec![]; - let mut new_target_hashes = vec![]; - - proof_hashes.sort(); - target_hashes.sort(); - - proof_hashes - .iter() - .for_each(|(_, hash)| new_proof_hashes.push(hash.to_owned())); - let mut prove_targets = vec![]; - target_hashes.into_iter().for_each(|(pos, hash)| { - prove_targets.push(pos); - new_target_hashes.push(hash) - }); - - prove_targets.dedup(); - - Ok(( - new_target_hashes, - Proof { - targets: prove_targets, - hashes: new_proof_hashes, - }, - )) + /// Returns how many targets this proof has + pub fn targets(&self) -> usize { + self.targets.len() } - pub fn create_root_candidates( + /// This function computes a set of roots from a proof. + /// If some target's hashes are null, then it computes the roots after + /// those targets are deleted. In this context null means [sha256::Hash::default]. + /// + /// It's the caller's responsibility to null out the targets if desired by + /// passing a `bitcoin_hashes::sha256::Hash::default()` instead of the actual hash. + pub(crate) fn calculate_roots( &self, del_hashes: &Vec, stump: &Stump, @@ -280,6 +183,7 @@ impl Proof { .peekable(); while let Some((pos, hash)) = row_nodes.next() { + let next_to_prove = util::parent(pos, total_rows); // If the current position is a root, we add that to our result and don't go any further if util::is_root_position(pos, stump.leafs, total_rows) { calculated_root_hashes.push(hash); @@ -289,10 +193,20 @@ impl Proof { if let Some((next_pos, next_hash)) = row_nodes.peek() { // Is the next node our sibling? If so, we should be hashed together if util::is_right_sibling(pos, *next_pos) { - let hash = types::parent_hash(&hash, &next_hash); - let next_to_prove = util::parent(pos, total_rows); - - Proof::sorted_push(&mut nodes, (next_to_prove, hash)); + // There are three possible cases: the current hash is null, + // and the sibling is present, we push the sibling to targets. + // If The sibling is null, we push the current node. + // If none of them is null, we compute the parent hash of both siblings + // and push this to the next target. + if hash == sha256::Hash::default() { + Proof::sorted_push(&mut nodes, (next_to_prove, *next_hash)); + } else if *next_hash == sha256::Hash::default() { + Proof::sorted_push(&mut nodes, (next_to_prove, hash)); + } else { + let hash = types::parent_hash(&hash, &next_hash); + + Proof::sorted_push(&mut nodes, (next_to_prove, hash)); + } // Since we consumed 2 elements from nodes, skip one more here // We need make this explicitly because peek, by definition @@ -305,21 +219,24 @@ impl Proof { // If the next node is not my sibling, the hash must be passed inside the proof if let Some(next_proof_hash) = hashes_iter.next() { - let hash = if util::is_left_niece(pos) { - types::parent_hash(&hash, next_proof_hash) - } else { - types::parent_hash(next_proof_hash, &hash) - }; + if hash != sha256::Hash::default() { + let hash = if util::is_left_niece(pos) { + types::parent_hash(&hash, next_proof_hash) + } else { + types::parent_hash(next_proof_hash, &hash) + }; - let next_to_prove = util::parent(pos, total_rows); - - Proof::sorted_push(&mut nodes, (next_to_prove, hash)); + Proof::sorted_push(&mut nodes, (next_to_prove, hash)); + continue; + } else { + // If none of the above, push a null hash upwards + Proof::sorted_push(&mut nodes, (next_to_prove, *next_proof_hash)); + } } else { return Err(String::from("Proof too short")); } } } - Ok(calculated_root_hashes) } fn sorted_push( @@ -406,70 +323,7 @@ mod tests { assert!(expected == res); } } - #[test] - fn test_proof_after_deletion() { - let mut hashes = vec![]; - for i in 0..20 { - hashes.push(hash_from_u8(i as u8)); - } - let s = Stump::new() - .modify(&hashes, &vec![], &Proof::new(vec![], vec![])) - .expect("Modify should not fail"); - - let proof = Proof::new( - vec![1, 16, 10], - vec![ - bitcoin_hashes::sha256::Hash::from_str( - "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d", - ) - .unwrap(), - bitcoin_hashes::sha256::Hash::from_str( - "e7cf46a078fed4fafd0b5e3aff144802b853f8ae459a4f0c14add3314b7cc3a6", - ) - .unwrap(), - bitcoin_hashes::sha256::Hash::from_str( - "4a64a107f0cb32536e5bce6c98c393db21cca7f4ea187ba8c4dca8b51d4ea80a", - ) - .unwrap(), - bitcoin_hashes::sha256::Hash::from_str( - "9576f4ade6e9bc3a6458b506ce3e4e890df29cb14cb5d3d887672aef55647a2b", - ) - .unwrap(), - bitcoin_hashes::sha256::Hash::from_str( - "cd9c77062a338e63a63ca623db438cb8676f15466641079ee61ec2dda98de796", - ) - .unwrap(), - bitcoin_hashes::sha256::Hash::from_str( - "96d56447466674521007145ed72f8757517c72f7737dc4a0dcd3ecb996968971", - ) - .unwrap(), - bitcoin_hashes::sha256::Hash::from_str( - "29590a14c1b09384b94a2c0e94bf821ca75b62eacebc47893397ca88e3bbcbd7", - ) - .unwrap(), - bitcoin_hashes::sha256::Hash::from_str( - "e799acb98a071c4884707e4bc8c093ba22571c8d84cc0223ab0c2c9327313a5b", - ) - .unwrap(), - ], - ); - let proof_after = proof.proof_after_deletion(s.leafs).unwrap(); - let (del_hashes, proof) = proof_after; - let roots = proof.create_root_candidates(&del_hashes, &s).unwrap(); - - // They are swapped, because create_root_candidates builds the forest bottom-up - let expected = vec![ - bitcoin_hashes::sha256::Hash::from_str( - "21326d8aebeb6ef7bc02f40bdf778a02ba1c836b257f946ae21cab2a6f95fa18", - ) - .unwrap(), - bitcoin_hashes::sha256::Hash::from_str( - "37968ef73d30dda38ede8357d66593c72acd4f0eb9f7a1a9acfeb7de850c05b4", - ) - .unwrap(), - ]; - assert_eq!(expected, roots); - } + #[test] fn test_proof_verify() { let contents = std::fs::read_to_string("test_values/test_cases.json") diff --git a/src/accumulator/stump.rs b/src/accumulator/stump.rs index 42af7b5..1a9baf5 100644 --- a/src/accumulator/stump.rs +++ b/src/accumulator/stump.rs @@ -1,4 +1,6 @@ use super::{proof::Proof, types}; +use bitcoin_hashes::sha256; +use std::vec; #[derive(Debug, Clone, PartialEq)] pub struct Stump { @@ -33,7 +35,7 @@ impl Stump { proof: &Proof, ) -> Result { let mut root_candidates = proof - .create_root_candidates(del_hashes, self)? + .calculate_roots(del_hashes, self)? .into_iter() .rev() .peekable(); @@ -93,8 +95,8 @@ impl Stump { return Ok(self.roots.clone()); } - let (new_hashes, new_proof) = proof.proof_after_deletion(self.leafs)?; - let new_roots = new_proof.create_root_candidates(&new_hashes, self)?; + let del_hashes = vec![sha256::Hash::default(); proof.targets()]; + let new_roots = proof.calculate_roots(&del_hashes, self)?; Ok(new_roots) } diff --git a/test_values/test_cases.json b/test_values/test_cases.json index 26569e9..3699a2e 100644 --- a/test_values/test_cases.json +++ b/test_values/test_cases.json @@ -429,6 +429,37 @@ "9576f4ade6e9bc3a6458b506ce3e4e890df29cb14cb5d3d887672aef55647a2b", "c413035120e8c9b0ca3e40c93d06fe60a0d056866138300bb1f1dd172b4923c3" ] + }, + { + "leaf_values": [0, 1, 2, 3, 4, 5, 6, 7], + "target_values":[0, 1, 2, 3, 4, 5, 6, 7], + "roots":[ + "0000000000000000000000000000000000000000000000000000000000000000" + ], + "proof_hashes": [] + }, + { + "leaf_values": [0, 1, 2, 3, 4, 5, 6, 7], + "target_values": [0, 1, 2, 3], + "roots": [ + "29590a14c1b09384b94a2c0e94bf821ca75b62eacebc47893397ca88e3bbcbd7" + ], + "proof_hashes": [ + "29590a14c1b09384b94a2c0e94bf821ca75b62eacebc47893397ca88e3bbcbd7" + ] + }, + { + "leaf_values": [0, 1, 2, 3, 4, 5, 6, 7], + "target_values": [0, 2, 4, 6], + "roots": [ + "54128834807e7f8763ff00fef2e7aea740c1d19977f95ee138ee6eecd0b9c702" + ], + "proof_hashes": [ + "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a", + "084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5", + "e77b9a9ae9e30b0dbdb6f510a264ef9de781501d7b6b92ae89eb059c5ab743db", + "ca358758f6d27e6cf45272937977a748fd88391db679ceda7dc7bf1f005ee879" + ] } ] }