1use crate::{
14 Context,
15 merkle::{
16 Family as _,
17 hasher::Hasher,
18 mmr::{
19 self, Error, Location, Position, Proof,
20 mem::{Config, Mmr},
21 verification,
22 },
23 storage::Storage,
24 },
25 metadata::{Config as MConfig, Metadata},
26};
27use ahash::AHashSet;
28use commonware_codec::DecodeExt;
29use commonware_cryptography::Digest;
30use commonware_parallel::Strategy;
31use commonware_utils::{
32 bitmap::{BitMap as UtilsBitMap, Prunable as PrunableBitMap},
33 sequence::prefixed_u64::U64,
34};
35use tracing::{debug, error, warn};
36
37pub(crate) fn partial_chunk_root<H: Hasher<mmr::Family>, const N: usize>(
40 hasher: &H,
41 mmr_root: &H::Digest,
42 next_bit: u64,
43 last_chunk_digest: &H::Digest,
44) -> H::Digest {
45 assert!(next_bit > 0);
46 assert!(next_bit < UtilsBitMap::<N>::CHUNK_SIZE_BITS);
47 let next_bit = next_bit.to_be_bytes();
48 hasher.hash(&[
49 mmr_root.as_ref(),
50 next_bit.as_slice(),
51 last_chunk_digest.as_ref(),
52 ])
53}
54
55mod private {
56 pub trait Sealed {}
57}
58
59pub trait State<D: Digest>: private::Sealed + Sized + Send + Sync {}
61
62pub struct Merkleized<D: Digest> {
64 root: D,
66}
67
68impl<D: Digest> private::Sealed for Merkleized<D> {}
69impl<D: Digest> State<D> for Merkleized<D> {}
70
71pub struct Unmerkleized {
73 dirty_chunks: AHashSet<usize>,
80}
81
82impl private::Sealed for Unmerkleized {}
83impl<D: Digest> State<D> for Unmerkleized {}
84
85pub type MerkleizedBitMap<E, D, const N: usize, S> = BitMap<E, D, N, Merkleized<D>, S>;
87
88pub type UnmerkleizedBitMap<E, D, const N: usize, S> = BitMap<E, D, N, Unmerkleized, S>;
90
91pub struct BitMap<E: Context, D: Digest, const N: usize, M: State<D>, S: Strategy> {
109 bitmap: PrunableBitMap<N>,
111
112 authenticated_len: usize,
115
116 mmr: Mmr<D>,
126
127 strategy: S,
129
130 state: M,
132
133 metadata: Metadata<E, U64, Vec<u8>>,
135}
136
137const NODE_PREFIX: u8 = 0;
139
140const PRUNED_CHUNKS_PREFIX: u8 = 1;
142
143impl<E: Context, D: Digest, const N: usize, M: State<D>, S: Strategy> BitMap<E, D, N, M, S> {
144 pub const CHUNK_SIZE_BITS: u64 = PrunableBitMap::<N>::CHUNK_SIZE_BITS;
146
147 #[inline]
149 pub fn size(&self) -> Position {
150 self.mmr.size()
151 }
152
153 #[inline]
155 pub const fn len(&self) -> u64 {
156 self.bitmap.len()
157 }
158
159 #[inline]
161 pub const fn is_empty(&self) -> bool {
162 self.len() == 0
163 }
164
165 #[inline]
167 pub const fn pruned_bits(&self) -> u64 {
168 self.bitmap.pruned_bits()
169 }
170
171 #[inline]
174 fn complete_chunks(&self) -> usize {
175 self.bitmap.complete_chunks()
176 }
177
178 #[inline]
181 pub fn last_chunk(&self) -> (&[u8; N], u64) {
182 self.bitmap.last_chunk()
183 }
184
185 #[inline]
191 pub fn get_chunk_containing(&self, bit: u64) -> &[u8; N] {
192 self.bitmap.get_chunk_containing(bit)
193 }
194
195 #[inline]
201 pub fn get_bit(&self, bit: u64) -> bool {
202 self.bitmap.get_bit(bit)
203 }
204
205 #[inline]
208 pub const fn get_bit_from_chunk(chunk: &[u8; N], bit: u64) -> bool {
209 PrunableBitMap::<N>::get_bit_from_chunk(chunk, bit)
210 }
211
212 pub fn verify_bit_inclusion(
215 hasher: &impl Hasher<mmr::Family, Digest = D>,
216 proof: &Proof<D>,
217 chunk: &[u8; N],
218 bit: u64,
219 root: &D,
220 ) -> bool {
221 let bit_len = *proof.leaves;
222 if bit >= bit_len {
223 debug!(bit_len, bit, "tried to verify non-existent bit");
224 return false;
225 }
226
227 if proof.inactive_peaks != 0 {
229 debug!(
230 inactive_peaks = proof.inactive_peaks,
231 "bitmap proof must have inactive_peaks == 0"
232 );
233 return false;
234 }
235
236 let chunked_leaves = Location::new(PrunableBitMap::<N>::to_chunk_index(bit_len) as u64);
238 let mut mmr_proof = Proof {
239 leaves: chunked_leaves,
240 inactive_peaks: 0,
241 digests: proof.digests.clone(),
242 };
243
244 let loc = Location::new(PrunableBitMap::<N>::to_chunk_index(bit) as u64);
245 if bit_len.is_multiple_of(Self::CHUNK_SIZE_BITS) {
246 return mmr_proof.verify_element_inclusion(hasher, chunk, loc, root);
247 }
248
249 if proof.digests.is_empty() {
250 debug!("proof has no digests");
251 return false;
252 }
253 let last_digest = mmr_proof.digests.pop().unwrap();
254
255 if chunked_leaves == loc {
256 if !mmr_proof.digests.is_empty() {
260 debug!(
261 digests = mmr_proof.digests.len() + 1,
262 "proof over partial chunk should have exactly 1 digest"
263 );
264 return false;
265 }
266 let last_chunk_digest = hasher.digest(chunk);
267 let next_bit = bit_len % Self::CHUNK_SIZE_BITS;
268 let reconstructed_root =
269 partial_chunk_root::<_, N>(hasher, &last_digest, next_bit, &last_chunk_digest);
270 return reconstructed_root == *root;
271 };
272
273 let mmr_root = match mmr_proof.reconstruct_root(hasher, &[chunk], loc) {
276 Ok(root) => root,
277 Err(error) => {
278 debug!(error = ?error, "invalid proof input");
279 return false;
280 }
281 };
282
283 let next_bit = bit_len % Self::CHUNK_SIZE_BITS;
284 let reconstructed_root =
285 partial_chunk_root::<_, N>(hasher, &mmr_root, next_bit, &last_digest);
286
287 reconstructed_root == *root
288 }
289}
290
291impl<E: Context, D: Digest, const N: usize, S: Strategy> MerkleizedBitMap<E, D, N, S> {
292 pub async fn init(
299 context: E,
300 partition: &str,
301 strategy: S,
302 hasher: &impl Hasher<mmr::Family, Digest = D>,
303 ) -> Result<Self, Error> {
304 let metadata_cfg = MConfig {
305 partition: partition.into(),
306 codec_config: ((0..).into(), ()),
307 };
308 let metadata =
309 Metadata::<_, U64, Vec<u8>>::init(context.child("metadata"), metadata_cfg).await?;
310
311 let key: U64 = U64::new(PRUNED_CHUNKS_PREFIX, 0);
312 let pruned_chunks = match metadata.get(&key) {
313 Some(bytes) => u64::from_be_bytes(bytes.as_slice().try_into().map_err(|_| {
314 error!("pruned chunks value not a valid u64");
315 Error::DataCorrupted("pruned chunks value not a valid u64")
316 })?),
317 None => {
318 warn!("bitmap metadata does not contain pruned chunks, initializing as empty");
319 0
320 }
321 } as usize;
322 if pruned_chunks == 0 {
323 let mmr = Mmr::new();
324 let cached_root = mmr.root(hasher, 0)?;
325 return Ok(Self {
326 bitmap: PrunableBitMap::new(),
327 authenticated_len: 0,
328 mmr,
329 strategy,
330 metadata,
331 state: Merkleized { root: cached_root },
332 });
333 }
334 let pruned_loc = Location::new(pruned_chunks as u64);
335 if !pruned_loc.is_valid() {
336 return Err(Error::DataCorrupted("pruned chunks exceeds MAX_LEAVES"));
337 }
338
339 let mut pinned_nodes = Vec::new();
340 for (index, pos) in mmr::Family::nodes_to_pin(pruned_loc).enumerate() {
341 let Some(bytes) = metadata.get(&U64::new(NODE_PREFIX, index as u64)) else {
342 error!(?pruned_loc, ?pos, "missing pinned node");
343 return Err(Error::MissingNode(pos));
344 };
345 let digest = D::decode(bytes.as_ref());
346 let Ok(digest) = digest else {
347 error!(?pruned_loc, ?pos, "could not convert node bytes to digest");
348 return Err(Error::MissingNode(pos));
349 };
350 pinned_nodes.push(digest);
351 }
352
353 let mmr = Mmr::init(Config {
354 nodes: Vec::new(),
355 pruning_boundary: Location::new(pruned_chunks as u64),
356 pinned_nodes,
357 })?;
358
359 let bitmap = PrunableBitMap::new_with_pruned_chunks(pruned_chunks)
360 .expect("pruned_chunks should never overflow");
361 let cached_root = mmr.root(hasher, 0)?;
362 Ok(Self {
363 bitmap,
364 authenticated_len: pruned_chunks,
366 mmr,
367 strategy,
368 metadata,
369 state: Merkleized { root: cached_root },
370 })
371 }
372
373 pub fn get_node(&self, position: Position) -> Option<D> {
374 self.mmr.get_node(position)
375 }
376
377 pub async fn write_pruned(mut self) -> Result<Self, Error> {
384 self.metadata.clear();
385
386 let key = U64::new(PRUNED_CHUNKS_PREFIX, 0);
388 self.metadata
389 .put(key, self.bitmap.pruned_chunks().to_be_bytes().to_vec());
390
391 let pruned_loc = Location::new(self.bitmap.pruned_chunks() as u64);
393 assert!(
394 pruned_loc.is_valid(),
395 "expected valid location from pruned_chunks"
396 );
397 for (i, digest) in mmr::Family::nodes_to_pin(pruned_loc).enumerate() {
398 let digest = self.mmr.get_node_unchecked(digest);
399 let key = U64::new(NODE_PREFIX, i as u64);
400 self.metadata.put(key, digest.to_vec());
401 }
402
403 self.metadata = self.metadata.sync().await.map_err(Error::Metadata)?;
404 Ok(self)
405 }
406
407 pub async fn destroy(self) -> Result<(), Error> {
409 self.metadata.destroy().await.map_err(Error::Metadata)
410 }
411
412 pub fn prune_to_bit(&mut self, bit: u64) -> Result<(), Error> {
420 let chunk = PrunableBitMap::<N>::to_chunk_index(bit);
421 if chunk < self.bitmap.pruned_chunks() {
422 return Ok(());
423 }
424
425 self.bitmap.prune_to_bit(bit);
427
428 self.authenticated_len = self.complete_chunks();
430
431 self.mmr.prune(Location::new(chunk as u64))?;
432 Ok(())
433 }
434
435 pub const fn root(&self) -> D {
447 self.state.root
448 }
449
450 pub async fn proof(
462 &self,
463 hasher: &impl Hasher<mmr::Family, Digest = D>,
464 bit: u64,
465 ) -> Result<(Proof<D>, [u8; N]), Error> {
466 if bit >= self.len() {
467 return Err(Error::BitOutOfBounds(bit, self.len()));
468 }
469
470 let chunk = *self.get_chunk_containing(bit);
471 let chunk_loc = Location::from(PrunableBitMap::<N>::to_chunk_index(bit));
472 let (last_chunk, next_bit) = self.bitmap.last_chunk();
473
474 if chunk_loc == self.mmr.leaves() {
475 assert!(next_bit > 0);
476 return Ok((
479 Proof {
480 leaves: Location::new(self.len()),
481 inactive_peaks: 0,
482 digests: vec![self.mmr.root(hasher, 0)?],
483 },
484 chunk,
485 ));
486 }
487
488 let range = chunk_loc..chunk_loc + 1;
489 let mut proof = verification::range_proof(hasher, &self.mmr, range, 0).await?;
490 proof.leaves = Location::new(self.len());
491 if next_bit == Self::CHUNK_SIZE_BITS {
492 return Ok((proof, chunk));
494 }
495
496 let last_chunk_digest = hasher.digest(last_chunk);
499 proof.digests.push(last_chunk_digest);
500
501 Ok((proof, chunk))
502 }
503
504 pub fn into_dirty(self) -> UnmerkleizedBitMap<E, D, N, S> {
506 UnmerkleizedBitMap {
507 bitmap: self.bitmap,
508 authenticated_len: self.authenticated_len,
509 mmr: self.mmr,
510 strategy: self.strategy,
511 state: Unmerkleized {
512 dirty_chunks: AHashSet::new(),
513 },
514 metadata: self.metadata,
515 }
516 }
517}
518
519impl<E: Context, D: Digest, const N: usize, S: Strategy> UnmerkleizedBitMap<E, D, N, S> {
520 pub fn push(&mut self, bit: bool) {
526 self.bitmap.push(bit);
527 }
528
529 pub fn set_bit(&mut self, bit: u64, value: bool) {
535 self.bitmap.set_bit(bit, value);
537
538 let chunk = PrunableBitMap::<N>::to_chunk_index(bit);
540 if chunk < self.authenticated_len {
541 self.state.dirty_chunks.insert(chunk);
542 }
543 }
544
545 pub fn dirty_chunks(&self) -> Vec<Location> {
547 let mut chunks: Vec<Location> = self
548 .state
549 .dirty_chunks
550 .iter()
551 .map(|&chunk| Location::new(chunk as u64))
552 .collect();
553
554 for i in self.authenticated_len..self.complete_chunks() {
556 chunks.push(Location::new(i as u64));
557 }
558
559 chunks
560 }
561
562 pub fn merkleize(
564 mut self,
565 hasher: &impl Hasher<mmr::Family, Digest = D>,
566 ) -> Result<MerkleizedBitMap<E, D, N, S>, Error> {
567 let mut batch = self.mmr.new_batch_with_strategy(self.strategy.clone());
569 let start = self.authenticated_len;
570 let end = self.complete_chunks();
571 for i in start..end {
572 batch = batch.add(hasher, self.bitmap.get_chunk(i));
573 }
574 self.authenticated_len = end;
575
576 let updates: Vec<(Location, &[u8; N])> = self
578 .state
579 .dirty_chunks
580 .iter()
581 .map(|&chunk| {
582 let loc = Location::new(chunk as u64);
583 (loc, self.bitmap.get_chunk(chunk))
584 })
585 .collect();
586 let dirty: Vec<(Location, D)> = self.strategy.map_init_collect_vec(
587 &updates,
588 || hasher.clone(),
589 |h, &(loc, chunk)| {
590 let pos = Position::try_from(loc).unwrap();
591 (loc, h.leaf_digest(pos, chunk.as_ref()))
592 },
593 );
594 batch = batch.update_leaf_batched(&dirty)?;
595
596 let batch = batch.merkleize(&self.mmr, hasher);
598 self.mmr.apply_batch(&batch)?;
599
600 let mmr_root = self.mmr.root(hasher, 0)?;
602 let cached_root = if self.bitmap.is_chunk_aligned() {
603 mmr_root
604 } else {
605 let (last_chunk, next_bit) = self.bitmap.last_chunk();
606 let last_chunk_digest = hasher.digest(last_chunk);
607 partial_chunk_root::<_, N>(hasher, &mmr_root, next_bit, &last_chunk_digest)
608 };
609
610 Ok(MerkleizedBitMap {
611 bitmap: self.bitmap,
612 authenticated_len: self.authenticated_len,
613 mmr: self.mmr,
614 strategy: self.strategy,
615 metadata: self.metadata,
616 state: Merkleized { root: cached_root },
617 })
618 }
619}
620
621impl<E: Context, D: Digest, const N: usize, S: Strategy> Storage<mmr::Family>
622 for MerkleizedBitMap<E, D, N, S>
623{
624 type Digest = D;
625
626 fn size(&self) -> Position {
627 self.size()
628 }
629
630 async fn get_node(&self, position: Position) -> Result<Option<D>, Error> {
631 Ok(self.get_node(position))
632 }
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638 use crate::merkle::Bagging::ForwardFold;
639 use commonware_codec::FixedSize;
640 use commonware_cryptography::{Hasher, Sha256, sha256};
641 use commonware_macros::test_traced;
642 use commonware_parallel::Sequential;
643 use commonware_runtime::{Runner as _, Supervisor as _, deterministic};
644 use mmr::StandardHasher;
645
646 const SHA256_SIZE: usize = sha256::Digest::SIZE;
647
648 type TestContext = deterministic::Context;
649 type TestMerkleizedBitMap<const N: usize> =
650 MerkleizedBitMap<TestContext, sha256::Digest, N, Sequential>;
651
652 impl<E: Context, D: Digest, const N: usize> UnmerkleizedBitMap<E, D, N, Sequential> {
653 fn push_byte(&mut self, byte: u8) {
661 self.bitmap.push_byte(byte);
662 }
663
664 fn push_chunk(&mut self, chunk: &[u8; N]) {
672 self.bitmap.push_chunk(chunk);
673 }
674 }
675
676 fn test_chunk<const N: usize>(s: &[u8]) -> [u8; N] {
677 assert_eq!(N % 32, 0);
678 let mut vec: Vec<u8> = Vec::new();
679 for _ in 0..N / 32 {
680 vec.extend(Sha256::hash(&[s]).iter());
681 }
682
683 vec.try_into().unwrap()
684 }
685
686 #[test_traced]
687 fn test_bitmap_verify_empty_proof() {
688 let executor = deterministic::Runner::default();
689 executor.start(|_context| async move {
690 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
691 let proof = Proof {
692 leaves: Location::new(100),
693 inactive_peaks: 0,
694 digests: Vec::new(),
695 };
696 assert!(
697 !TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
698 &hasher,
699 &proof,
700 &[0u8; SHA256_SIZE],
701 0,
702 &Sha256::fill(0x00),
703 ),
704 "proof without digests shouldn't verify or panic"
705 );
706 });
707 }
708
709 #[test_traced]
712 fn test_bitmap_verify_rejects_nonzero_inactive_peaks() {
713 let executor = deterministic::Runner::default();
714 executor.start(|context| async move {
715 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
716 let mut bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
717 context.child("bitmap"),
718 "inactive_peaks_canonical",
719 Sequential,
720 &hasher,
721 )
722 .await
723 .unwrap();
724
725 let mut dirty = bitmap.into_dirty();
727 for i in 0..(TestMerkleizedBitMap::<SHA256_SIZE>::CHUNK_SIZE_BITS * 4) {
728 dirty.push(i % 3 == 0);
729 }
730 bitmap = dirty.merkleize(&hasher).unwrap();
731 let root = bitmap.root();
732
733 let bit = TestMerkleizedBitMap::<SHA256_SIZE>::CHUNK_SIZE_BITS + 5;
734 let (proof, chunk) = bitmap.proof(&hasher, bit).await.unwrap();
735 assert_eq!(proof.inactive_peaks, 0);
736
737 assert!(
739 TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
740 &hasher, &proof, &chunk, bit, &root
741 ),
742 "canonical bitmap proof should verify"
743 );
744
745 let mut tampered = proof;
747 tampered.inactive_peaks = 1;
748 assert!(
749 !TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
750 &hasher, &tampered, &chunk, bit, &root
751 ),
752 "bitmap proof with nonzero inactive_peaks must not verify"
753 );
754 });
755 }
756
757 #[test_traced]
758 fn test_bitmap_empty_then_one() {
759 let executor = deterministic::Runner::default();
760 executor.start(|context| async move {
761 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
762 let mut bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
763 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
764 .await
765 .unwrap();
766 assert_eq!(bitmap.len(), 0);
767 assert_eq!(bitmap.bitmap.pruned_chunks(), 0);
768 bitmap.prune_to_bit(0).unwrap();
769 assert_eq!(bitmap.bitmap.pruned_chunks(), 0);
770
771 let root = bitmap.root();
773 let mut dirty = bitmap.into_dirty();
774 dirty.push(true);
775 bitmap = dirty.merkleize(&hasher).unwrap();
776 let new_root = bitmap.root();
778 assert_ne!(root, new_root);
779 let root = new_root;
780 bitmap.prune_to_bit(1).unwrap();
781 assert_eq!(bitmap.len(), 1);
782 assert_ne!(bitmap.last_chunk().0, &[0u8; SHA256_SIZE]);
783 assert_eq!(bitmap.last_chunk().1, 1);
784 assert_eq!(bitmap.bitmap.pruned_chunks(), 0);
786 assert_eq!(root, bitmap.root());
787
788 let mut dirty = bitmap.into_dirty();
790 for i in 0..(TestMerkleizedBitMap::<SHA256_SIZE>::CHUNK_SIZE_BITS - 1) {
791 dirty.push(i % 2 != 0);
792 }
793 bitmap = dirty.merkleize(&hasher).unwrap();
794 assert_eq!(bitmap.len(), 256);
795 assert_ne!(root, bitmap.root());
796 let root = bitmap.root();
797
798 let (proof, chunk) = bitmap.proof(&hasher, 0).await.unwrap();
800 assert!(
801 TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
802 &hasher, &proof, &chunk, 255, &root
803 ),
804 "failed to prove bit in only chunk"
805 );
806 assert!(
808 !TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
809 &hasher, &proof, &chunk, 256, &root
810 ),
811 "should not be able to prove bit outside of chunk"
812 );
813
814 bitmap.prune_to_bit(256).unwrap();
816 assert_eq!(bitmap.len(), 256);
817 assert_eq!(bitmap.bitmap.pruned_chunks(), 1);
818 assert_eq!(bitmap.bitmap.pruned_bits(), 256);
819 assert_eq!(root, bitmap.root());
820
821 bitmap.prune_to_bit(10).unwrap();
823 assert_eq!(root, bitmap.root());
824 });
825 }
826
827 #[test_traced]
828 fn test_bitmap_building() {
829 let executor = deterministic::Runner::default();
832 executor.start(|context| async move {
833 let test_chunk = test_chunk(b"test");
834 let hasher: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
835
836 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
838 context.child("bitmap").with_attribute("index", 1),
839 "test1",
840 Sequential,
841 &hasher,
842 )
843 .await
844 .unwrap();
845 let mut dirty = bitmap.into_dirty();
846 dirty.push_chunk(&test_chunk);
847 for b in test_chunk {
848 for j in 0..8 {
849 let mask = 1 << j;
850 let bit = (b & mask) != 0;
851 dirty.push(bit);
852 }
853 }
854 assert_eq!(dirty.len(), 256 * 2);
855
856 let bitmap = dirty.merkleize(&hasher).unwrap();
857 let root = bitmap.root();
858 let inner_root = bitmap.mmr.root(&hasher, 0).unwrap();
859 assert_eq!(root, inner_root);
860
861 {
862 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
865 context.child("bitmap").with_attribute("index", 2),
866 "test2",
867 Sequential,
868 &hasher,
869 )
870 .await
871 .unwrap();
872 let mut dirty = bitmap.into_dirty();
873 dirty.push_chunk(&test_chunk);
874 dirty.push_chunk(&test_chunk);
875 let bitmap = dirty.merkleize(&hasher).unwrap();
876 let same_root = bitmap.root();
877 assert_eq!(root, same_root);
878 }
879 {
880 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
882 context.child("bitmap").with_attribute("index", 3),
883 "test3",
884 Sequential,
885 &hasher,
886 )
887 .await
888 .unwrap();
889 let mut dirty = bitmap.into_dirty();
890 dirty.push_chunk(&test_chunk);
891 for b in test_chunk {
892 dirty.push_byte(b);
893 }
894 let bitmap = dirty.merkleize(&hasher).unwrap();
895 let same_root = bitmap.root();
896 assert_eq!(root, same_root);
897 }
898 });
899 }
900
901 #[test_traced]
902 #[should_panic(expected = "cannot add chunk")]
903 fn test_bitmap_build_chunked_panic() {
904 let executor = deterministic::Runner::default();
905 executor.start(|context| async move {
906 let hasher: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
907 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
908 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
909 .await
910 .unwrap();
911 let mut dirty = bitmap.into_dirty();
912 dirty.push_chunk(&test_chunk(b"test"));
913 dirty.push(true);
914 dirty.push_chunk(&test_chunk(b"panic"));
915 });
916 }
917
918 #[test_traced]
919 #[should_panic(expected = "cannot add byte")]
920 fn test_bitmap_build_byte_panic() {
921 let executor = deterministic::Runner::default();
922 executor.start(|context| async move {
923 let hasher: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
924 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
925 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
926 .await
927 .unwrap();
928 let mut dirty = bitmap.into_dirty();
929 dirty.push_chunk(&test_chunk(b"test"));
930 dirty.push(true);
931 dirty.push_byte(0x01);
932 });
933 }
934
935 #[test_traced]
936 #[should_panic(expected = "out of bounds")]
937 fn test_bitmap_get_out_of_bounds_bit_panic() {
938 let executor = deterministic::Runner::default();
939 executor.start(|context| async move {
940 let hasher: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
941 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
942 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
943 .await
944 .unwrap();
945 let mut dirty = bitmap.into_dirty();
946 dirty.push_chunk(&test_chunk(b"test"));
947 dirty.get_bit(256);
948 });
949 }
950
951 #[test_traced]
952 #[should_panic(expected = "pruned")]
953 fn test_bitmap_get_pruned_bit_panic() {
954 let executor = deterministic::Runner::default();
955 executor.start(|context| async move {
956 let hasher: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
957 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
958 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
959 .await
960 .unwrap();
961 let mut dirty = bitmap.into_dirty();
962 dirty.push_chunk(&test_chunk(b"test"));
963 dirty.push_chunk(&test_chunk(b"test2"));
964 let mut bitmap = dirty.merkleize(&hasher).unwrap();
965
966 bitmap.prune_to_bit(256).unwrap();
967 bitmap.get_bit(255);
968 });
969 }
970
971 #[test_traced]
972 fn test_bitmap_root_boundaries() {
973 let executor = deterministic::Runner::default();
974 executor.start(|context| async move {
975 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
977 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
978 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
979 .await
980 .unwrap();
981 let mut dirty = bitmap.into_dirty();
982 dirty.push_chunk(&test_chunk(b"test"));
983 dirty.push_chunk(&test_chunk(b"test2"));
984 let mut bitmap = dirty.merkleize(&hasher).unwrap();
985
986 let root = bitmap.root();
987
988 let mut dirty = bitmap.into_dirty();
990 dirty.push(true);
991 bitmap = dirty.merkleize(&hasher).unwrap();
992 let new_root = bitmap.root();
993 assert_ne!(root, new_root);
994 assert_eq!(bitmap.mmr.size(), 3); for _ in 0..(SHA256_SIZE * 8 - 1) {
998 let mut dirty = bitmap.into_dirty();
999 dirty.push(false);
1000 bitmap = dirty.merkleize(&hasher).unwrap();
1001 let newer_root = bitmap.root();
1002 assert_ne!(new_root, newer_root);
1004 }
1005 assert_eq!(bitmap.mmr.size(), 4); let mut dirty = bitmap.into_dirty();
1009 dirty.push(false);
1010 assert_eq!(dirty.len(), 256 * 3 + 1);
1011 bitmap = dirty.merkleize(&hasher).unwrap();
1012 let newer_root = bitmap.root();
1013 assert_ne!(new_root, newer_root);
1014
1015 bitmap.prune_to_bit(bitmap.len()).unwrap();
1017 assert_eq!(bitmap.bitmap.pruned_chunks(), 3);
1018 assert_eq!(bitmap.len(), 256 * 3 + 1);
1019 assert_eq!(newer_root, bitmap.root());
1020 });
1021 }
1022
1023 #[test_traced]
1024 fn test_bitmap_get_set_bits() {
1025 let executor = deterministic::Runner::default();
1026 executor.start(|context| async move {
1027 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1029 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
1030 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
1031 .await
1032 .unwrap();
1033 let mut dirty = bitmap.into_dirty();
1034 dirty.push_chunk(&test_chunk(b"test"));
1035 dirty.push_chunk(&test_chunk(b"test2"));
1036 dirty.push_chunk(&test_chunk(b"test3"));
1037 dirty.push_chunk(&test_chunk(b"test4"));
1038 dirty.push_byte(0xF1);
1040 dirty.push(true);
1041 dirty.push(false);
1042 dirty.push(true);
1043
1044 let mut bitmap = dirty.merkleize(&hasher).unwrap();
1045 let root = bitmap.root();
1046
1047 for bit_pos in (0..bitmap.len()).rev() {
1050 let bit = bitmap.get_bit(bit_pos);
1051 let mut dirty = bitmap.into_dirty();
1052 dirty.set_bit(bit_pos, !bit);
1053 bitmap = dirty.merkleize(&hasher).unwrap();
1054 let new_root = bitmap.root();
1055 assert_ne!(root, new_root, "failed at bit {bit_pos}");
1056 let mut dirty = bitmap.into_dirty();
1058 dirty.set_bit(bit_pos, bit);
1059 bitmap = dirty.merkleize(&hasher).unwrap();
1060 let new_root = bitmap.root();
1061 assert_eq!(root, new_root);
1062 }
1063
1064 let start_bit = (SHA256_SIZE * 8 * 2) as u64;
1066 bitmap.prune_to_bit(start_bit).unwrap();
1067 for bit_pos in (start_bit..bitmap.len()).rev() {
1068 let bit = bitmap.get_bit(bit_pos);
1069 let mut dirty = bitmap.into_dirty();
1070 dirty.set_bit(bit_pos, !bit);
1071 bitmap = dirty.merkleize(&hasher).unwrap();
1072 let new_root = bitmap.root();
1073 assert_ne!(root, new_root, "failed at bit {bit_pos}");
1074 let mut dirty = bitmap.into_dirty();
1076 dirty.set_bit(bit_pos, bit);
1077 bitmap = dirty.merkleize(&hasher).unwrap();
1078 let new_root = bitmap.root();
1079 assert_eq!(root, new_root);
1080 }
1081 });
1082 }
1083
1084 fn flip_bit<const N: usize>(bit: u64, chunk: &[u8; N]) -> [u8; N] {
1085 let byte = PrunableBitMap::<N>::chunk_byte_offset(bit);
1086 let mask = PrunableBitMap::<N>::chunk_byte_bitmask(bit);
1087 let mut tmp = chunk.to_vec();
1088 tmp[byte] ^= mask;
1089 tmp.try_into().unwrap()
1090 }
1091
1092 #[test_traced]
1093 fn test_bitmap_mmr_proof_verification() {
1094 test_bitmap_mmr_proof_verification_n::<32>();
1095 test_bitmap_mmr_proof_verification_n::<64>();
1096 }
1097
1098 fn test_bitmap_mmr_proof_verification_n<const N: usize>() {
1099 let executor = deterministic::Runner::default();
1100 executor.start(|context| async move {
1101 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1103 let bitmap: MerkleizedBitMap<TestContext, sha256::Digest, N, Sequential> =
1104 MerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
1105 .await
1106 .unwrap();
1107 let mut dirty = bitmap.into_dirty();
1108 for i in 0u32..10 {
1109 dirty.push_chunk(&test_chunk(format!("test{i}").as_bytes()));
1110 }
1111 dirty.push_byte(0xA6);
1113 dirty.push(true);
1114 dirty.push(false);
1115 dirty.push(true);
1116 dirty.push(true);
1117 dirty.push(false);
1118
1119 let mut bitmap = dirty.merkleize(&hasher).unwrap();
1120 let root = bitmap.root();
1121
1122 for prune_to_bit in (0..bitmap.len()).step_by(251) {
1125 assert_eq!(bitmap.root(), root);
1126 bitmap.prune_to_bit(prune_to_bit).unwrap();
1127 for i in prune_to_bit..bitmap.len() {
1128 let (proof, chunk) = bitmap.proof(&hasher, i).await.unwrap();
1129
1130 assert!(
1132 MerkleizedBitMap::<TestContext, _, N, Sequential>::verify_bit_inclusion(
1133 &hasher, &proof, &chunk, i, &root
1134 ),
1135 "failed to prove bit {i}",
1136 );
1137
1138 let corrupted = flip_bit(i, &chunk);
1140 assert!(
1141 !MerkleizedBitMap::<TestContext, _, N, Sequential>::verify_bit_inclusion(
1142 &hasher, &proof, &corrupted, i, &root
1143 ),
1144 "proving bit {i} after flipping should have failed",
1145 );
1146 }
1147 }
1148 })
1149 }
1150
1151 #[test_traced]
1152 fn test_bitmap_persistence() {
1153 const PARTITION: &str = "bitmap-test";
1154 const FULL_CHUNK_COUNT: usize = 100;
1155
1156 let executor = deterministic::Runner::default();
1157 executor.start(|context| async move {
1158 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1159 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
1161 context.child("initial"),
1162 PARTITION,
1163 Sequential,
1164 &hasher,
1165 )
1166 .await
1167 .unwrap();
1168 assert_eq!(bitmap.len(), 0);
1169
1170 let mut dirty = bitmap.into_dirty();
1172 for i in 0..FULL_CHUNK_COUNT {
1173 dirty.push_chunk(&test_chunk(format!("test{i}").as_bytes()));
1174 }
1175 let mut bitmap = dirty.merkleize(&hasher).unwrap();
1176 let chunk_aligned_root = bitmap.root();
1177
1178 let mut dirty = bitmap.into_dirty();
1180 dirty.push_byte(0xA6);
1181 dirty.push(true);
1182 dirty.push(false);
1183 dirty.push(true);
1184 bitmap = dirty.merkleize(&hasher).unwrap();
1185 let root = bitmap.root();
1186
1187 for i in (10..=FULL_CHUNK_COUNT).step_by(10) {
1189 bitmap
1190 .prune_to_bit(
1191 (i * TestMerkleizedBitMap::<SHA256_SIZE>::CHUNK_SIZE_BITS as usize) as u64,
1192 )
1193 .unwrap();
1194 bitmap.write_pruned().await.unwrap();
1195 bitmap = TestMerkleizedBitMap::init(
1196 context.child("restore").with_attribute("index", i),
1197 PARTITION,
1198 Sequential,
1199 &hasher,
1200 )
1201 .await
1202 .unwrap();
1203 let _ = bitmap.root();
1204
1205 let mut dirty = bitmap.into_dirty();
1207 for j in i..FULL_CHUNK_COUNT {
1208 dirty.push_chunk(&test_chunk(format!("test{j}").as_bytes()));
1209 }
1210 assert_eq!(dirty.bitmap.pruned_chunks(), i);
1211 assert_eq!(dirty.len(), FULL_CHUNK_COUNT as u64 * 256);
1212 bitmap = dirty.merkleize(&hasher).unwrap();
1213 assert_eq!(bitmap.root(), chunk_aligned_root);
1214
1215 let mut dirty = bitmap.into_dirty();
1217 dirty.push_byte(0xA6);
1218 dirty.push(true);
1219 dirty.push(false);
1220 dirty.push(true);
1221 bitmap = dirty.merkleize(&hasher).unwrap();
1222 assert_eq!(bitmap.root(), root);
1223 }
1224 });
1225 }
1226
1227 #[test_traced]
1228 fn test_bitmap_proof_out_of_bounds() {
1229 let executor = deterministic::Runner::default();
1230 executor.start(|context| async move {
1231 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1232 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
1233 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
1234 .await
1235 .unwrap();
1236 let mut dirty = bitmap.into_dirty();
1237 dirty.push_chunk(&test_chunk(b"test"));
1238 let bitmap = dirty.merkleize(&hasher).unwrap();
1239
1240 let result = bitmap.proof(&hasher, 256).await;
1242 assert!(matches!(result, Err(Error::BitOutOfBounds(offset, size))
1243 if offset == 256 && size == 256));
1244
1245 let result = bitmap.proof(&hasher, 1000).await;
1246 assert!(matches!(result, Err(Error::BitOutOfBounds(offset, size))
1247 if offset == 1000 && size == 256));
1248
1249 assert!(bitmap.proof(&hasher, 0).await.is_ok());
1251 assert!(bitmap.proof(&hasher, 255).await.is_ok());
1252 });
1253 }
1254}