diff --git a/Cargo.toml b/Cargo.toml index 0d36b8e..1941219 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ rust-version = "1.74.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +bitcoin = { version = "0.32.8", default-features = false, features = ["serde"] } bitcoin_hashes = "0.18" hex-conservative = "1.0.0" serde = { version = "1.0", features = ["derive"], optional = true } diff --git a/benches/proof_benchmarks.rs b/benches/proof_benchmarks.rs index 33c3fa3..43b3965 100644 --- a/benches/proof_benchmarks.rs +++ b/benches/proof_benchmarks.rs @@ -1,5 +1,7 @@ use std::hint::black_box; +use bitcoin::hashes::Hash; +use bitcoin::BlockHash; use criterion::criterion_group; use criterion::criterion_main; use criterion::BenchmarkId; @@ -55,7 +57,9 @@ fn proof_verification(c: &mut Criterion) { let accumulator_size = 100; let hashes = generate_test_hashes(accumulator_size, 42); let stump = Stump::new(); - let (stump, _) = stump.modify(&hashes, &[], &Proof::default()).unwrap(); + let (stump, _) = stump + .modify(0, BlockHash::all_zeros(), &hashes, &[], &Proof::default()) + .unwrap(); for target_count in [1, 10].iter() { let del_hashes = hashes[..*target_count].to_vec(); diff --git a/benches/stump_benchmarks.rs b/benches/stump_benchmarks.rs index 7fa5e16..2b8f694 100644 --- a/benches/stump_benchmarks.rs +++ b/benches/stump_benchmarks.rs @@ -1,5 +1,7 @@ use std::hint::black_box; +use bitcoin::hashes::Hash; +use bitcoin::BlockHash; use criterion::criterion_group; use criterion::criterion_main; use criterion::BenchmarkId; @@ -34,8 +36,13 @@ fn stump_modify_add_only(c: &mut Criterion) { b.iter(|| { let stump = Stump::new(); - let result = - stump.modify(black_box(&hashes), black_box(&[]), black_box(&empty_proof)); + let result = stump.modify( + 0, + BlockHash::all_zeros(), + black_box(&hashes), + black_box(&[]), + black_box(&empty_proof), + ); black_box(result.unwrap()) }); }); @@ -50,7 +57,9 @@ fn stump_verify(c: &mut Criterion) { let test_size = 1000; let hashes = generate_test_hashes(test_size, 42); let stump = Stump::new(); - let (stump, _) = stump.modify(&hashes, &[], &Proof::default()).unwrap(); + let (stump, _) = stump + .modify(0, BlockHash::all_zeros(), &hashes, &[], &Proof::default()) + .unwrap(); for proof_size in [1, 10, 100].iter() { let del_hashes = hashes[..*proof_size].to_vec(); diff --git a/examples/full-accumulator.rs b/examples/full-accumulator.rs index 12160c0..2dab6b6 100644 --- a/examples/full-accumulator.rs +++ b/examples/full-accumulator.rs @@ -4,6 +4,8 @@ use std::str::FromStr; +use bitcoin::hashes::Hash; +use bitcoin::BlockHash; use rustreexo::accumulator::mem_forest::MemForest; use rustreexo::accumulator::node_hash::BitcoinNodeHash; use rustreexo::accumulator::proof::Proof; @@ -28,7 +30,7 @@ fn main() { let proof = p.prove(&[elements[0]]).unwrap(); // Verify the proof. Notice how we use the del_hashes returned by `prove` here. let s = Stump::new() - .modify(&elements, &[], &Proof::default()) + .modify(0, BlockHash::all_zeros(), &elements, &[], &Proof::default()) .unwrap() .0; assert_eq!(s.verify(&proof, &[elements[0]]), Ok(true)); diff --git a/examples/proof-update.rs b/examples/proof-update.rs index cb02cfd..300b6b4 100644 --- a/examples/proof-update.rs +++ b/examples/proof-update.rs @@ -12,6 +12,8 @@ use std::str::FromStr; +use bitcoin::hashes::Hash; +use bitcoin::BlockHash; use rustreexo::accumulator::node_hash::BitcoinNodeHash; use rustreexo::accumulator::proof::Proof; use rustreexo::accumulator::stump::Stump; @@ -22,7 +24,9 @@ fn main() { let utxos = get_utxo_hashes1(); // Add the UTXOs to the accumulator. update_data is the data we need to update the proof // after the accumulator is updated. - let (s, update_data) = s.modify(&utxos, &[], &Proof::default()).unwrap(); + let (s, update_data) = s + .modify(0, BlockHash::all_zeros(), &utxos, &[], &Proof::default()) + .unwrap(); // Create an empty proof, we'll update it to hold our UTXOs let p = Proof::default(); // Update the proof with the UTXOs we added to the accumulator. This proof was initially empty, @@ -48,7 +52,9 @@ fn main() { // We'll remove `0` as it got spent, and add 1..7 to our cache. let new_utxos = get_utxo_hashes2(); // First, update the accumulator - let (stump, update_data) = s.modify(&new_utxos, &[utxos[0]], &p1).unwrap(); + let (stump, update_data) = s + .modify(0, BlockHash::all_zeros(), &new_utxos, &[utxos[0]], &p1) + .unwrap(); // and the proof let (p2, cached_hashes) = p .update( diff --git a/examples/simple-stump-update.rs b/examples/simple-stump-update.rs index 139b1b9..b5039d3 100644 --- a/examples/simple-stump-update.rs +++ b/examples/simple-stump-update.rs @@ -5,6 +5,8 @@ use std::str::FromStr; use std::vec; +use bitcoin::hashes::Hash; +use bitcoin::BlockHash; use rustreexo::accumulator::node_hash::BitcoinNodeHash; use rustreexo::accumulator::proof::Proof; use rustreexo::accumulator::stump::Stump; @@ -28,7 +30,7 @@ fn main() { // but only the Stump. To understand what is the second return value, see the documentation // for `Stump::modify`, or the proof-update example. let s = Stump::new() - .modify(&utxos, &[], &Proof::default()) + .modify(0, BlockHash::all_zeros(), &utxos, &[], &Proof::default()) .unwrap() .0; // Create a proof that the first utxo is in the Stump. @@ -42,7 +44,10 @@ fn main() { "d3bd63d53c5a70050a28612a2f4b2019f40951a653ae70736d93745efb1124fa", ) .unwrap(); - let s = s.modify(&[new_utxo], &[utxos[0]], &proof).unwrap().0; + let s = s + .modify(0, BlockHash::all_zeros(), &[new_utxo], &[utxos[0]], &proof) + .unwrap() + .0; // Now we can verify that the new utxo is in the Stump, and the old one is not. let new_proof = Proof::new(vec![2], vec![new_utxo]); assert_eq!(s.verify(&new_proof, &[new_utxo]), Ok(true)); diff --git a/src/accumulator/pollard.rs b/src/accumulator/pollard.rs index a494a06..c7e66b8 100644 --- a/src/accumulator/pollard.rs +++ b/src/accumulator/pollard.rs @@ -399,11 +399,11 @@ impl PollardNode { /// The deletion algorithm for utreexo works like this: let's say we have the following tree: /// /// ```! - /// 06 - /// |---------\ - /// 04 05 + /// 06 + /// |---------\ + /// 04 05 /// |-----\ |-----\ - /// 00 01 02 03 + /// 00 01 02 03 /// ``` /// /// to delete `03`, we simply move `02` up to `09`'s position, so now we have: @@ -1266,6 +1266,8 @@ impl From> for Pollard { mod tests { use std::str::FromStr; + use bitcoin::hashes::Hash; + use bitcoin::BlockHash; use serde::Deserialize; use super::*; @@ -1377,7 +1379,12 @@ mod tests { ]; let leaves = 15; - let stump = Stump { roots, leaves }; + let stump = Stump { + block_height: 0, + block_hash: BlockHash::all_zeros(), + roots, + leaves, + }; let p: Pollard = stump.clone().into(); assert_eq!(stump.roots, p.roots()); diff --git a/src/accumulator/proof.rs b/src/accumulator/proof.rs index b586fcc..1856257 100644 --- a/src/accumulator/proof.rs +++ b/src/accumulator/proof.rs @@ -890,6 +890,8 @@ impl Proof { mod tests { use std::str::FromStr; + use bitcoin::hashes::Hash; + use bitcoin::BlockHash; use serde::Deserialize; use super::Proof; @@ -980,6 +982,8 @@ mod tests { .collect(); let stump = Stump { + block_height: 0, + block_hash: BlockHash::all_zeros(), leaves: case_values.initial_leaves, roots, }; @@ -1006,7 +1010,9 @@ mod tests { let block_proof = Proof::new(case_values.update.proof.targets.clone(), block_proof_hashes); - let (stump, updated) = stump.modify(&utxos, &del_hashes, &block_proof).unwrap(); + let (stump, updated) = stump + .modify(0, BlockHash::all_zeros(), &utxos, &del_hashes, &block_proof) + .unwrap(); let (cached_proof, cached_hashes) = cached_proof .update( cached_hashes.clone(), @@ -1193,7 +1199,7 @@ mod tests { let preimages = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; let hashes = preimages.into_iter().map(hash_from_u8).collect::>(); let (stump, _) = Stump::new() - .modify(&hashes, &[], &Proof::default()) + .modify(0, BlockHash::all_zeros(), &hashes, &[], &Proof::default()) .unwrap(); let proof_hashes = vec![ @@ -1222,6 +1228,8 @@ mod tests { let (stump, modified) = stump .modify( + 0, + BlockHash::all_zeros(), &[], &[hash_from_u8(1), hash_from_u8(2), hash_from_u8(6)], &proof, @@ -1248,7 +1256,7 @@ mod tests { let hashes = preimages.into_iter().map(hash_from_u8).collect::>(); // Create a new stump with 8 leaves and 1 root let s = Stump::new() - .modify(&hashes, &[], &Proof::default()) + .modify(0, BlockHash::all_zeros(), &hashes, &[], &Proof::default()) .expect("This stump is valid") .0; @@ -1391,7 +1399,7 @@ mod tests { let hashes = preimages.into_iter().map(hash_from_u8).collect::>(); // Create a new stump with 8 leaves and 1 root let s = Stump::new() - .modify(&hashes, &[], &Proof::default()) + .modify(0, BlockHash::all_zeros(), &hashes, &[], &Proof::default()) .expect("This stump is valid") .0; @@ -1436,6 +1444,8 @@ mod tests { .collect(); let s = Stump { + block_height: 0, + block_hash: BlockHash::all_zeros(), leaves: case.numleaves as u64, roots, }; diff --git a/src/accumulator/stump.rs b/src/accumulator/stump.rs index c337bb4..e271c5f 100644 --- a/src/accumulator/stump.rs +++ b/src/accumulator/stump.rs @@ -31,6 +31,8 @@ use std::io::Read; use std::io::Write; use std::vec; +use bitcoin::hashes::Hash; +use bitcoin::BlockHash; #[cfg(feature = "with-serde")] use serde::Deserialize; #[cfg(feature = "with-serde")] @@ -79,6 +81,8 @@ impl From for StumpError { #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] pub struct Stump { + pub block_height: u32, + pub block_hash: BlockHash, pub leaves: u64, pub roots: Vec, } @@ -98,6 +102,8 @@ impl Stump { /// ``` pub fn new() -> Self { Self { + block_height: 0, + block_hash: BlockHash::all_zeros(), leaves: 0, roots: Vec::new(), } @@ -182,6 +188,8 @@ impl Stump { /// to create a new Stump with the default hash type. pub fn new_with_hash() -> Self { Self { + block_height: 0, + block_hash: BlockHash::all_zeros(), leaves: 0, roots: Vec::new(), } @@ -211,6 +219,8 @@ impl Stump { /// ``` pub fn modify( &self, + block_height: u32, + block_hash: BlockHash, utxos: &[Hash], del_hashes: &[Hash], proof: &Proof, @@ -239,6 +249,8 @@ impl Stump { let (roots, updated, destroyed) = Self::add(new_roots, utxos, self.leaves); let new_stump = Self { + block_height, + block_hash, leaves: self.leaves + utxos.len() as u64, roots, }; @@ -278,6 +290,8 @@ impl Stump { /// ); /// ``` pub fn deserialize(mut data: Source) -> Result { + let block_height = util::read_u32(&mut data)?; + let block_hash = util::read_block_hash(&mut data)?; let leaves = util::read_u64(&mut data)?; let roots_len = util::read_u64(&mut data)?; let mut roots = vec![]; @@ -287,7 +301,12 @@ impl Stump { roots.push(root); } - Ok(Self { leaves, roots }) + Ok(Self { + block_height, + block_hash, + leaves, + roots, + }) } fn remove( @@ -374,6 +393,8 @@ mod test { use std::str::FromStr; use std::vec; + use bitcoin::hashes::Hash; + use bitcoin::BlockHash; use serde::Deserialize; use super::Stump; @@ -458,6 +479,8 @@ mod test { let (stump, _) = s .modify( + 0, + BlockHash::all_zeros(), &hashes, &[], &Proof::::new_with_hash(Vec::new(), Vec::new()), @@ -505,6 +528,8 @@ mod test { .map(|hash| BitcoinNodeHash::from_str(hash).unwrap()) .collect(); let stump = Stump { + block_height: 0, + block_hash: BlockHash::all_zeros(), leaves: data.leaves, roots, }; @@ -525,7 +550,9 @@ mod test { .map(|hash| BitcoinNodeHash::from_str(hash).unwrap()) .collect::>(); let proof = Proof::new(data.proof_targets, proof_hashes); - let (_, updated) = stump.modify(&utxos, &del_hashes, &proof).unwrap(); + let (_, updated) = stump + .modify(0, BlockHash::all_zeros(), &utxos, &del_hashes, &proof) + .unwrap(); // Positions returned after addition let new_add_hash: Vec<_> = data .new_add_hash @@ -564,7 +591,7 @@ mod test { let hashes = preimages.into_iter().map(hash_from_u8).collect::>(); let (_, updated) = Stump::new() - .modify(&hashes, &[], &Proof::default()) + .modify(0, BlockHash::all_zeros(), &hashes, &[], &Proof::default()) .unwrap(); let positions = vec![0, 1, 2, 3, 4, 5, 6]; @@ -591,7 +618,13 @@ mod test { #[cfg(feature = "with-serde")] fn test_serde_rtt() { let stump = Stump::new() - .modify(&[hash_from_u8(0), hash_from_u8(1)], &[], &Proof::default()) + .modify( + 0, + BlockHash::all_zeros(), + &[hash_from_u8(0), hash_from_u8(1)], + &[], + &Proof::default(), + ) .unwrap() .0; let serialized = serde_json::to_string(&stump).expect("Serialization failed"); @@ -635,9 +668,17 @@ mod test { .collect::>(); let (stump, _) = Stump::new() - .modify(&leaf_hashes, &[], &Proof::default()) + .modify( + 0, + BlockHash::all_zeros(), + &leaf_hashes, + &[], + &Proof::default(), + ) .expect("This stump is valid"); - let (stump, _) = stump.modify(&[], &target_hashes, &proof).unwrap(); + let (stump, _) = stump + .modify(0, BlockHash::all_zeros(), &[], &target_hashes, &proof) + .unwrap(); assert_eq!(stump.roots, roots); } @@ -652,7 +693,7 @@ mod test { .collect::>(); let (s, _) = s - .modify(&hashes, &[], &Proof::default()) + .modify(0, BlockHash::all_zeros(), &hashes, &[], &Proof::default()) .expect("Stump from test cases are valid"); assert_eq!(s.leaves, hashes.len() as u64); @@ -672,7 +713,7 @@ mod test { let s_old = Stump::new(); let s_old = s_old - .modify(&hashes, &[], &Proof::default()) + .modify(0, BlockHash::all_zeros(), &hashes, &[], &Proof::default()) .expect("Stump from test cases are valid") .0; @@ -684,7 +725,7 @@ mod test { } s_new - .modify(&hashes, &[], &Proof::default()) + .modify(0, BlockHash::all_zeros(), &hashes, &[], &Proof::default()) .expect("Stump from test cases are valid"); let s_old_copy = s_old.clone(); @@ -701,7 +742,7 @@ mod test { .map(|&el| BitcoinNodeHash::from([el; 32])) .collect::>(); let (stump, _) = Stump::new() - .modify(&hashes, &[], &Proof::default()) + .modify(0, BlockHash::all_zeros(), &hashes, &[], &Proof::default()) .unwrap(); let mut writer = Vec::new(); stump.serialize(&mut writer).unwrap(); diff --git a/src/accumulator/util.rs b/src/accumulator/util.rs index 71080ac..2d9d62e 100644 --- a/src/accumulator/util.rs +++ b/src/accumulator/util.rs @@ -2,6 +2,9 @@ use std::collections::BTreeSet; use std::collections::HashSet; use std::io::Read; +use bitcoin::hashes::Hash; +use bitcoin::BlockHash; + // Rustreexo use super::node_hash::AccumulatorHash; @@ -223,12 +226,24 @@ pub fn parent(pos: u64, forest_rows: u8) -> u64 { (pos >> 1) | (1 << forest_rows) } +pub fn read_u32(buf: &mut Source) -> Result { + let mut bytes = [0u8; 4]; + buf.read_exact(&mut bytes)?; + Ok(u32::from_le_bytes(bytes)) +} + pub fn read_u64(buf: &mut Source) -> Result { let mut bytes = [0u8; 8]; buf.read_exact(&mut bytes)?; Ok(u64::from_le_bytes(bytes)) } +pub fn read_block_hash(buf: &mut Source) -> Result { + let mut bytes = [0u8; 32]; + buf.read_exact(&mut bytes)?; + Ok(BlockHash::from_byte_array(bytes)) +} + // tree_rows returns the number of rows given n leaves pub fn tree_rows(n: u64) -> u8 { if n == 0 {