1use crate::{
14 merkle::{
15 hasher::Hasher,
16 mmr::{
17 self,
18 mem::{Config, Mmr},
19 verification, Error, Location, Position, Proof,
20 },
21 storage::Storage,
22 Family as _,
23 },
24 metadata::{Config as MConfig, Metadata},
25 Context,
26};
27use commonware_codec::DecodeExt;
28use commonware_cryptography::Digest;
29use commonware_parallel::Strategy;
30use commonware_utils::{
31 bitmap::{BitMap as UtilsBitMap, Prunable as PrunableBitMap},
32 sequence::prefixed_u64::U64,
33};
34use std::collections::HashSet;
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: HashSet<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 let chunks_len = self.bitmap.chunks_len();
176 if self.bitmap.is_chunk_aligned() {
177 chunks_len
178 } else {
179 chunks_len.checked_sub(1).unwrap()
181 }
182 }
183
184 #[inline]
187 pub fn last_chunk(&self) -> (&[u8; N], u64) {
188 self.bitmap.last_chunk()
189 }
190
191 #[inline]
197 pub fn get_chunk_containing(&self, bit: u64) -> &[u8; N] {
198 self.bitmap.get_chunk_containing(bit)
199 }
200
201 #[inline]
207 pub fn get_bit(&self, bit: u64) -> bool {
208 self.bitmap.get_bit(bit)
209 }
210
211 #[inline]
214 pub const fn get_bit_from_chunk(chunk: &[u8; N], bit: u64) -> bool {
215 PrunableBitMap::<N>::get_bit_from_chunk(chunk, bit)
216 }
217
218 pub fn verify_bit_inclusion(
221 hasher: &impl Hasher<mmr::Family, Digest = D>,
222 proof: &Proof<D>,
223 chunk: &[u8; N],
224 bit: u64,
225 root: &D,
226 ) -> bool {
227 let bit_len = *proof.leaves;
228 if bit >= bit_len {
229 debug!(bit_len, bit, "tried to verify non-existent bit");
230 return false;
231 }
232
233 if proof.inactive_peaks != 0 {
235 debug!(
236 inactive_peaks = proof.inactive_peaks,
237 "bitmap proof must have inactive_peaks == 0"
238 );
239 return false;
240 }
241
242 let chunked_leaves = Location::new(PrunableBitMap::<N>::to_chunk_index(bit_len) as u64);
244 let mut mmr_proof = Proof {
245 leaves: chunked_leaves,
246 inactive_peaks: 0,
247 digests: proof.digests.clone(),
248 };
249
250 let loc = Location::new(PrunableBitMap::<N>::to_chunk_index(bit) as u64);
251 if bit_len.is_multiple_of(Self::CHUNK_SIZE_BITS) {
252 return mmr_proof.verify_element_inclusion(hasher, chunk, loc, root);
253 }
254
255 if proof.digests.is_empty() {
256 debug!("proof has no digests");
257 return false;
258 }
259 let last_digest = mmr_proof.digests.pop().unwrap();
260
261 if chunked_leaves == loc {
262 if !mmr_proof.digests.is_empty() {
266 debug!(
267 digests = mmr_proof.digests.len() + 1,
268 "proof over partial chunk should have exactly 1 digest"
269 );
270 return false;
271 }
272 let last_chunk_digest = hasher.digest(chunk);
273 let next_bit = bit_len % Self::CHUNK_SIZE_BITS;
274 let reconstructed_root =
275 partial_chunk_root::<_, N>(hasher, &last_digest, next_bit, &last_chunk_digest);
276 return reconstructed_root == *root;
277 };
278
279 let mmr_root = match mmr_proof.reconstruct_root(hasher, &[chunk], loc) {
282 Ok(root) => root,
283 Err(error) => {
284 debug!(error = ?error, "invalid proof input");
285 return false;
286 }
287 };
288
289 let next_bit = bit_len % Self::CHUNK_SIZE_BITS;
290 let reconstructed_root =
291 partial_chunk_root::<_, N>(hasher, &mmr_root, next_bit, &last_digest);
292
293 reconstructed_root == *root
294 }
295}
296
297impl<E: Context, D: Digest, const N: usize, S: Strategy> MerkleizedBitMap<E, D, N, S> {
298 pub async fn init(
305 context: E,
306 partition: &str,
307 strategy: S,
308 hasher: &impl Hasher<mmr::Family, Digest = D>,
309 ) -> Result<Self, Error> {
310 let metadata_cfg = MConfig {
311 partition: partition.into(),
312 codec_config: ((0..).into(), ()),
313 };
314 let metadata =
315 Metadata::<_, U64, Vec<u8>>::init(context.child("metadata"), metadata_cfg).await?;
316
317 let key: U64 = U64::new(PRUNED_CHUNKS_PREFIX, 0);
318 let pruned_chunks = match metadata.get(&key) {
319 Some(bytes) => u64::from_be_bytes(bytes.as_slice().try_into().map_err(|_| {
320 error!("pruned chunks value not a valid u64");
321 Error::DataCorrupted("pruned chunks value not a valid u64")
322 })?),
323 None => {
324 warn!("bitmap metadata does not contain pruned chunks, initializing as empty");
325 0
326 }
327 } as usize;
328 if pruned_chunks == 0 {
329 let mmr = Mmr::new();
330 let cached_root = mmr.root(hasher, 0)?;
331 return Ok(Self {
332 bitmap: PrunableBitMap::new(),
333 authenticated_len: 0,
334 mmr,
335 strategy,
336 metadata,
337 state: Merkleized { root: cached_root },
338 });
339 }
340 let pruned_loc = Location::new(pruned_chunks as u64);
341 if !pruned_loc.is_valid() {
342 return Err(Error::DataCorrupted("pruned chunks exceeds MAX_LEAVES"));
343 }
344
345 let mut pinned_nodes = Vec::new();
346 for (index, pos) in mmr::Family::nodes_to_pin(pruned_loc).enumerate() {
347 let Some(bytes) = metadata.get(&U64::new(NODE_PREFIX, index as u64)) else {
348 error!(?pruned_loc, ?pos, "missing pinned node");
349 return Err(Error::MissingNode(pos));
350 };
351 let digest = D::decode(bytes.as_ref());
352 let Ok(digest) = digest else {
353 error!(?pruned_loc, ?pos, "could not convert node bytes to digest");
354 return Err(Error::MissingNode(pos));
355 };
356 pinned_nodes.push(digest);
357 }
358
359 let mmr = Mmr::init(Config {
360 nodes: Vec::new(),
361 pruning_boundary: Location::new(pruned_chunks as u64),
362 pinned_nodes,
363 })?;
364
365 let bitmap = PrunableBitMap::new_with_pruned_chunks(pruned_chunks)
366 .expect("pruned_chunks should never overflow");
367 let cached_root = mmr.root(hasher, 0)?;
368 Ok(Self {
369 bitmap,
370 authenticated_len: pruned_chunks,
372 mmr,
373 strategy,
374 metadata,
375 state: Merkleized { root: cached_root },
376 })
377 }
378
379 pub fn get_node(&self, position: Position) -> Option<D> {
380 self.mmr.get_node(position)
381 }
382
383 pub async fn write_pruned(&mut self) -> Result<(), Error> {
387 self.metadata.clear();
388
389 let key = U64::new(PRUNED_CHUNKS_PREFIX, 0);
391 self.metadata
392 .put(key, self.bitmap.pruned_chunks().to_be_bytes().to_vec());
393
394 let pruned_loc = Location::new(self.bitmap.pruned_chunks() as u64);
396 assert!(
397 pruned_loc.is_valid(),
398 "expected valid location from pruned_chunks"
399 );
400 for (i, digest) in mmr::Family::nodes_to_pin(pruned_loc).enumerate() {
401 let digest = self.mmr.get_node_unchecked(digest);
402 let key = U64::new(NODE_PREFIX, i as u64);
403 self.metadata.put(key, digest.to_vec());
404 }
405
406 self.metadata.sync().await.map_err(Error::Metadata)
407 }
408
409 pub async fn destroy(self) -> Result<(), Error> {
411 self.metadata.destroy().await.map_err(Error::Metadata)
412 }
413
414 pub fn prune_to_bit(&mut self, bit: u64) -> Result<(), Error> {
422 let chunk = PrunableBitMap::<N>::to_chunk_index(bit);
423 if chunk < self.bitmap.pruned_chunks() {
424 return Ok(());
425 }
426
427 self.bitmap.prune_to_bit(bit);
429
430 self.authenticated_len = self.complete_chunks();
432
433 self.mmr.prune(Location::new(chunk as u64))?;
434 Ok(())
435 }
436
437 pub const fn root(&self) -> D {
449 self.state.root
450 }
451
452 pub async fn proof(
464 &self,
465 hasher: &impl Hasher<mmr::Family, Digest = D>,
466 bit: u64,
467 ) -> Result<(Proof<D>, [u8; N]), Error> {
468 if bit >= self.len() {
469 return Err(Error::BitOutOfBounds(bit, self.len()));
470 }
471
472 let chunk = *self.get_chunk_containing(bit);
473 let chunk_loc = Location::from(PrunableBitMap::<N>::to_chunk_index(bit));
474 let (last_chunk, next_bit) = self.bitmap.last_chunk();
475
476 if chunk_loc == self.mmr.leaves() {
477 assert!(next_bit > 0);
478 return Ok((
481 Proof {
482 leaves: Location::new(self.len()),
483 inactive_peaks: 0,
484 digests: vec![self.mmr.root(hasher, 0)?],
485 },
486 chunk,
487 ));
488 }
489
490 let range = chunk_loc..chunk_loc + 1;
491 let mut proof = verification::range_proof(hasher, &self.mmr, range, 0).await?;
492 proof.leaves = Location::new(self.len());
493 if next_bit == Self::CHUNK_SIZE_BITS {
494 return Ok((proof, chunk));
496 }
497
498 let last_chunk_digest = hasher.digest(last_chunk);
501 proof.digests.push(last_chunk_digest);
502
503 Ok((proof, chunk))
504 }
505
506 pub fn into_dirty(self) -> UnmerkleizedBitMap<E, D, N, S> {
508 UnmerkleizedBitMap {
509 bitmap: self.bitmap,
510 authenticated_len: self.authenticated_len,
511 mmr: self.mmr,
512 strategy: self.strategy,
513 state: Unmerkleized {
514 dirty_chunks: HashSet::new(),
515 },
516 metadata: self.metadata,
517 }
518 }
519}
520
521impl<E: Context, D: Digest, const N: usize, S: Strategy> UnmerkleizedBitMap<E, D, N, S> {
522 pub fn push(&mut self, bit: bool) {
528 self.bitmap.push(bit);
529 }
530
531 pub fn set_bit(&mut self, bit: u64, value: bool) {
537 self.bitmap.set_bit(bit, value);
539
540 let chunk = PrunableBitMap::<N>::to_chunk_index(bit);
542 if chunk < self.authenticated_len {
543 self.state.dirty_chunks.insert(chunk);
544 }
545 }
546
547 pub fn dirty_chunks(&self) -> Vec<Location> {
549 let mut chunks: Vec<Location> = self
550 .state
551 .dirty_chunks
552 .iter()
553 .map(|&chunk| Location::new(chunk as u64))
554 .collect();
555
556 for i in self.authenticated_len..self.complete_chunks() {
558 chunks.push(Location::new(i as u64));
559 }
560
561 chunks
562 }
563
564 pub fn merkleize(
566 mut self,
567 hasher: &impl Hasher<mmr::Family, Digest = D>,
568 ) -> Result<MerkleizedBitMap<E, D, N, S>, Error> {
569 let mut batch = self.mmr.new_batch_with_strategy(self.strategy.clone());
571 let start = self.authenticated_len;
572 let end = self.complete_chunks();
573 for i in start..end {
574 batch = batch.add(hasher, self.bitmap.get_chunk(i));
575 }
576 self.authenticated_len = end;
577
578 let updates: Vec<(Location, &[u8; N])> = self
580 .state
581 .dirty_chunks
582 .iter()
583 .map(|&chunk| {
584 let loc = Location::new(chunk as u64);
585 (loc, self.bitmap.get_chunk(chunk))
586 })
587 .collect();
588 let dirty: Vec<(Location, D)> = self.strategy.map_init_collect_vec(
589 &updates,
590 || hasher.clone(),
591 |h, &(loc, chunk)| {
592 let pos = Position::try_from(loc).unwrap();
593 (loc, h.leaf_digest(pos, chunk.as_ref()))
594 },
595 );
596 batch = batch.update_leaf_batched(&dirty)?;
597
598 let batch = batch.merkleize(&self.mmr, hasher);
600 self.mmr.apply_batch(&batch)?;
601
602 let mmr_root = self.mmr.root(hasher, 0)?;
604 let cached_root = if self.bitmap.is_chunk_aligned() {
605 mmr_root
606 } else {
607 let (last_chunk, next_bit) = self.bitmap.last_chunk();
608 let last_chunk_digest = hasher.digest(last_chunk);
609 partial_chunk_root::<_, N>(hasher, &mmr_root, next_bit, &last_chunk_digest)
610 };
611
612 Ok(MerkleizedBitMap {
613 bitmap: self.bitmap,
614 authenticated_len: self.authenticated_len,
615 mmr: self.mmr,
616 strategy: self.strategy,
617 metadata: self.metadata,
618 state: Merkleized { root: cached_root },
619 })
620 }
621}
622
623impl<E: Context, D: Digest, const N: usize, S: Strategy> Storage<mmr::Family>
624 for MerkleizedBitMap<E, D, N, S>
625{
626 type Digest = D;
627
628 async fn size(&self) -> Position {
629 self.size()
630 }
631
632 async fn get_node(&self, position: Position) -> Result<Option<D>, Error> {
633 Ok(self.get_node(position))
634 }
635}
636
637#[cfg(test)]
638mod tests {
639 use super::*;
640 use crate::merkle::Bagging::ForwardFold;
641 use commonware_codec::FixedSize;
642 use commonware_cryptography::{sha256, Hasher, Sha256};
643 use commonware_macros::test_traced;
644 use commonware_parallel::Sequential;
645 use commonware_runtime::{deterministic, Runner as _, Supervisor as _};
646 use mmr::StandardHasher;
647
648 const SHA256_SIZE: usize = sha256::Digest::SIZE;
649
650 type TestContext = deterministic::Context;
651 type TestMerkleizedBitMap<const N: usize> =
652 MerkleizedBitMap<TestContext, sha256::Digest, N, Sequential>;
653
654 impl<E: Context, D: Digest, const N: usize> UnmerkleizedBitMap<E, D, N, Sequential> {
655 fn push_byte(&mut self, byte: u8) {
663 self.bitmap.push_byte(byte);
664 }
665
666 fn push_chunk(&mut self, chunk: &[u8; N]) {
674 self.bitmap.push_chunk(chunk);
675 }
676 }
677
678 fn test_chunk<const N: usize>(s: &[u8]) -> [u8; N] {
679 assert_eq!(N % 32, 0);
680 let mut vec: Vec<u8> = Vec::new();
681 for _ in 0..N / 32 {
682 vec.extend(Sha256::hash(s).iter());
683 }
684
685 vec.try_into().unwrap()
686 }
687
688 #[test_traced]
689 fn test_bitmap_verify_empty_proof() {
690 let executor = deterministic::Runner::default();
691 executor.start(|_context| async move {
692 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
693 let proof = Proof {
694 leaves: Location::new(100),
695 inactive_peaks: 0,
696 digests: Vec::new(),
697 };
698 assert!(
699 !TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
700 &hasher,
701 &proof,
702 &[0u8; SHA256_SIZE],
703 0,
704 &Sha256::fill(0x00),
705 ),
706 "proof without digests shouldn't verify or panic"
707 );
708 });
709 }
710
711 #[test_traced]
714 fn test_bitmap_verify_rejects_nonzero_inactive_peaks() {
715 let executor = deterministic::Runner::default();
716 executor.start(|context| async move {
717 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
718 let mut bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
719 context.child("bitmap"),
720 "inactive_peaks_canonical",
721 Sequential,
722 &hasher,
723 )
724 .await
725 .unwrap();
726
727 let mut dirty = bitmap.into_dirty();
729 for i in 0..(TestMerkleizedBitMap::<SHA256_SIZE>::CHUNK_SIZE_BITS * 4) {
730 dirty.push(i % 3 == 0);
731 }
732 bitmap = dirty.merkleize(&hasher).unwrap();
733 let root = bitmap.root();
734
735 let bit = TestMerkleizedBitMap::<SHA256_SIZE>::CHUNK_SIZE_BITS + 5;
736 let (proof, chunk) = bitmap.proof(&hasher, bit).await.unwrap();
737 assert_eq!(proof.inactive_peaks, 0);
738
739 assert!(
741 TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
742 &hasher, &proof, &chunk, bit, &root
743 ),
744 "canonical bitmap proof should verify"
745 );
746
747 let mut tampered = proof;
749 tampered.inactive_peaks = 1;
750 assert!(
751 !TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
752 &hasher, &tampered, &chunk, bit, &root
753 ),
754 "bitmap proof with nonzero inactive_peaks must not verify"
755 );
756 });
757 }
758
759 #[test_traced]
760 fn test_bitmap_empty_then_one() {
761 let executor = deterministic::Runner::default();
762 executor.start(|context| async move {
763 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
764 let mut bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
765 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
766 .await
767 .unwrap();
768 assert_eq!(bitmap.len(), 0);
769 assert_eq!(bitmap.bitmap.pruned_chunks(), 0);
770 bitmap.prune_to_bit(0).unwrap();
771 assert_eq!(bitmap.bitmap.pruned_chunks(), 0);
772
773 let root = bitmap.root();
775 let mut dirty = bitmap.into_dirty();
776 dirty.push(true);
777 bitmap = dirty.merkleize(&hasher).unwrap();
778 let new_root = bitmap.root();
780 assert_ne!(root, new_root);
781 let root = new_root;
782 bitmap.prune_to_bit(1).unwrap();
783 assert_eq!(bitmap.len(), 1);
784 assert_ne!(bitmap.last_chunk().0, &[0u8; SHA256_SIZE]);
785 assert_eq!(bitmap.last_chunk().1, 1);
786 assert_eq!(bitmap.bitmap.pruned_chunks(), 0);
788 assert_eq!(root, bitmap.root());
789
790 let mut dirty = bitmap.into_dirty();
792 for i in 0..(TestMerkleizedBitMap::<SHA256_SIZE>::CHUNK_SIZE_BITS - 1) {
793 dirty.push(i % 2 != 0);
794 }
795 bitmap = dirty.merkleize(&hasher).unwrap();
796 assert_eq!(bitmap.len(), 256);
797 assert_ne!(root, bitmap.root());
798 let root = bitmap.root();
799
800 let (proof, chunk) = bitmap.proof(&hasher, 0).await.unwrap();
802 assert!(
803 TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
804 &hasher, &proof, &chunk, 255, &root
805 ),
806 "failed to prove bit in only chunk"
807 );
808 assert!(
810 !TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
811 &hasher, &proof, &chunk, 256, &root
812 ),
813 "should not be able to prove bit outside of chunk"
814 );
815
816 bitmap.prune_to_bit(256).unwrap();
818 assert_eq!(bitmap.len(), 256);
819 assert_eq!(bitmap.bitmap.pruned_chunks(), 1);
820 assert_eq!(bitmap.bitmap.pruned_bits(), 256);
821 assert_eq!(root, bitmap.root());
822
823 bitmap.prune_to_bit(10).unwrap();
825 assert_eq!(root, bitmap.root());
826 });
827 }
828
829 #[test_traced]
830 fn test_bitmap_building() {
831 let executor = deterministic::Runner::default();
834 executor.start(|context| async move {
835 let test_chunk = test_chunk(b"test");
836 let hasher: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
837
838 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
840 context.child("bitmap").with_attribute("index", 1),
841 "test1",
842 Sequential,
843 &hasher,
844 )
845 .await
846 .unwrap();
847 let mut dirty = bitmap.into_dirty();
848 dirty.push_chunk(&test_chunk);
849 for b in test_chunk {
850 for j in 0..8 {
851 let mask = 1 << j;
852 let bit = (b & mask) != 0;
853 dirty.push(bit);
854 }
855 }
856 assert_eq!(dirty.len(), 256 * 2);
857
858 let bitmap = dirty.merkleize(&hasher).unwrap();
859 let root = bitmap.root();
860 let inner_root = bitmap.mmr.root(&hasher, 0).unwrap();
861 assert_eq!(root, inner_root);
862
863 {
864 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
867 context.child("bitmap").with_attribute("index", 2),
868 "test2",
869 Sequential,
870 &hasher,
871 )
872 .await
873 .unwrap();
874 let mut dirty = bitmap.into_dirty();
875 dirty.push_chunk(&test_chunk);
876 dirty.push_chunk(&test_chunk);
877 let bitmap = dirty.merkleize(&hasher).unwrap();
878 let same_root = bitmap.root();
879 assert_eq!(root, same_root);
880 }
881 {
882 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
884 context.child("bitmap").with_attribute("index", 3),
885 "test3",
886 Sequential,
887 &hasher,
888 )
889 .await
890 .unwrap();
891 let mut dirty = bitmap.into_dirty();
892 dirty.push_chunk(&test_chunk);
893 for b in test_chunk {
894 dirty.push_byte(b);
895 }
896 let bitmap = dirty.merkleize(&hasher).unwrap();
897 let same_root = bitmap.root();
898 assert_eq!(root, same_root);
899 }
900 });
901 }
902
903 #[test_traced]
904 #[should_panic(expected = "cannot add chunk")]
905 fn test_bitmap_build_chunked_panic() {
906 let executor = deterministic::Runner::default();
907 executor.start(|context| async move {
908 let hasher: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
909 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
910 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
911 .await
912 .unwrap();
913 let mut dirty = bitmap.into_dirty();
914 dirty.push_chunk(&test_chunk(b"test"));
915 dirty.push(true);
916 dirty.push_chunk(&test_chunk(b"panic"));
917 });
918 }
919
920 #[test_traced]
921 #[should_panic(expected = "cannot add byte")]
922 fn test_bitmap_build_byte_panic() {
923 let executor = deterministic::Runner::default();
924 executor.start(|context| async move {
925 let hasher: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
926 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
927 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
928 .await
929 .unwrap();
930 let mut dirty = bitmap.into_dirty();
931 dirty.push_chunk(&test_chunk(b"test"));
932 dirty.push(true);
933 dirty.push_byte(0x01);
934 });
935 }
936
937 #[test_traced]
938 #[should_panic(expected = "out of bounds")]
939 fn test_bitmap_get_out_of_bounds_bit_panic() {
940 let executor = deterministic::Runner::default();
941 executor.start(|context| async move {
942 let hasher: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
943 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
944 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
945 .await
946 .unwrap();
947 let mut dirty = bitmap.into_dirty();
948 dirty.push_chunk(&test_chunk(b"test"));
949 dirty.get_bit(256);
950 });
951 }
952
953 #[test_traced]
954 #[should_panic(expected = "pruned")]
955 fn test_bitmap_get_pruned_bit_panic() {
956 let executor = deterministic::Runner::default();
957 executor.start(|context| async move {
958 let hasher: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
959 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
960 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
961 .await
962 .unwrap();
963 let mut dirty = bitmap.into_dirty();
964 dirty.push_chunk(&test_chunk(b"test"));
965 dirty.push_chunk(&test_chunk(b"test2"));
966 let mut bitmap = dirty.merkleize(&hasher).unwrap();
967
968 bitmap.prune_to_bit(256).unwrap();
969 bitmap.get_bit(255);
970 });
971 }
972
973 #[test_traced]
974 fn test_bitmap_root_boundaries() {
975 let executor = deterministic::Runner::default();
976 executor.start(|context| async move {
977 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
979 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
980 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
981 .await
982 .unwrap();
983 let mut dirty = bitmap.into_dirty();
984 dirty.push_chunk(&test_chunk(b"test"));
985 dirty.push_chunk(&test_chunk(b"test2"));
986 let mut bitmap = dirty.merkleize(&hasher).unwrap();
987
988 let root = bitmap.root();
989
990 let mut dirty = bitmap.into_dirty();
992 dirty.push(true);
993 bitmap = dirty.merkleize(&hasher).unwrap();
994 let new_root = bitmap.root();
995 assert_ne!(root, new_root);
996 assert_eq!(bitmap.mmr.size(), 3); for _ in 0..(SHA256_SIZE * 8 - 1) {
1000 let mut dirty = bitmap.into_dirty();
1001 dirty.push(false);
1002 bitmap = dirty.merkleize(&hasher).unwrap();
1003 let newer_root = bitmap.root();
1004 assert_ne!(new_root, newer_root);
1006 }
1007 assert_eq!(bitmap.mmr.size(), 4); let mut dirty = bitmap.into_dirty();
1011 dirty.push(false);
1012 assert_eq!(dirty.len(), 256 * 3 + 1);
1013 bitmap = dirty.merkleize(&hasher).unwrap();
1014 let newer_root = bitmap.root();
1015 assert_ne!(new_root, newer_root);
1016
1017 bitmap.prune_to_bit(bitmap.len()).unwrap();
1019 assert_eq!(bitmap.bitmap.pruned_chunks(), 3);
1020 assert_eq!(bitmap.len(), 256 * 3 + 1);
1021 assert_eq!(newer_root, bitmap.root());
1022 });
1023 }
1024
1025 #[test_traced]
1026 fn test_bitmap_get_set_bits() {
1027 let executor = deterministic::Runner::default();
1028 executor.start(|context| async move {
1029 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1031 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
1032 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
1033 .await
1034 .unwrap();
1035 let mut dirty = bitmap.into_dirty();
1036 dirty.push_chunk(&test_chunk(b"test"));
1037 dirty.push_chunk(&test_chunk(b"test2"));
1038 dirty.push_chunk(&test_chunk(b"test3"));
1039 dirty.push_chunk(&test_chunk(b"test4"));
1040 dirty.push_byte(0xF1);
1042 dirty.push(true);
1043 dirty.push(false);
1044 dirty.push(true);
1045
1046 let mut bitmap = dirty.merkleize(&hasher).unwrap();
1047 let root = bitmap.root();
1048
1049 for bit_pos in (0..bitmap.len()).rev() {
1052 let bit = bitmap.get_bit(bit_pos);
1053 let mut dirty = bitmap.into_dirty();
1054 dirty.set_bit(bit_pos, !bit);
1055 bitmap = dirty.merkleize(&hasher).unwrap();
1056 let new_root = bitmap.root();
1057 assert_ne!(root, new_root, "failed at bit {bit_pos}");
1058 let mut dirty = bitmap.into_dirty();
1060 dirty.set_bit(bit_pos, bit);
1061 bitmap = dirty.merkleize(&hasher).unwrap();
1062 let new_root = bitmap.root();
1063 assert_eq!(root, new_root);
1064 }
1065
1066 let start_bit = (SHA256_SIZE * 8 * 2) as u64;
1068 bitmap.prune_to_bit(start_bit).unwrap();
1069 for bit_pos in (start_bit..bitmap.len()).rev() {
1070 let bit = bitmap.get_bit(bit_pos);
1071 let mut dirty = bitmap.into_dirty();
1072 dirty.set_bit(bit_pos, !bit);
1073 bitmap = dirty.merkleize(&hasher).unwrap();
1074 let new_root = bitmap.root();
1075 assert_ne!(root, new_root, "failed at bit {bit_pos}");
1076 let mut dirty = bitmap.into_dirty();
1078 dirty.set_bit(bit_pos, bit);
1079 bitmap = dirty.merkleize(&hasher).unwrap();
1080 let new_root = bitmap.root();
1081 assert_eq!(root, new_root);
1082 }
1083 });
1084 }
1085
1086 fn flip_bit<const N: usize>(bit: u64, chunk: &[u8; N]) -> [u8; N] {
1087 let byte = PrunableBitMap::<N>::chunk_byte_offset(bit);
1088 let mask = PrunableBitMap::<N>::chunk_byte_bitmask(bit);
1089 let mut tmp = chunk.to_vec();
1090 tmp[byte] ^= mask;
1091 tmp.try_into().unwrap()
1092 }
1093
1094 #[test_traced]
1095 fn test_bitmap_mmr_proof_verification() {
1096 test_bitmap_mmr_proof_verification_n::<32>();
1097 test_bitmap_mmr_proof_verification_n::<64>();
1098 }
1099
1100 fn test_bitmap_mmr_proof_verification_n<const N: usize>() {
1101 let executor = deterministic::Runner::default();
1102 executor.start(|context| async move {
1103 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1105 let bitmap: MerkleizedBitMap<TestContext, sha256::Digest, N, Sequential> =
1106 MerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
1107 .await
1108 .unwrap();
1109 let mut dirty = bitmap.into_dirty();
1110 for i in 0u32..10 {
1111 dirty.push_chunk(&test_chunk(format!("test{i}").as_bytes()));
1112 }
1113 dirty.push_byte(0xA6);
1115 dirty.push(true);
1116 dirty.push(false);
1117 dirty.push(true);
1118 dirty.push(true);
1119 dirty.push(false);
1120
1121 let mut bitmap = dirty.merkleize(&hasher).unwrap();
1122 let root = bitmap.root();
1123
1124 for prune_to_bit in (0..bitmap.len()).step_by(251) {
1127 assert_eq!(bitmap.root(), root);
1128 bitmap.prune_to_bit(prune_to_bit).unwrap();
1129 for i in prune_to_bit..bitmap.len() {
1130 let (proof, chunk) = bitmap.proof(&hasher, i).await.unwrap();
1131
1132 assert!(
1134 MerkleizedBitMap::<TestContext, _, N, Sequential>::verify_bit_inclusion(
1135 &hasher, &proof, &chunk, i, &root
1136 ),
1137 "failed to prove bit {i}",
1138 );
1139
1140 let corrupted = flip_bit(i, &chunk);
1142 assert!(
1143 !MerkleizedBitMap::<TestContext, _, N, Sequential>::verify_bit_inclusion(
1144 &hasher, &proof, &corrupted, i, &root
1145 ),
1146 "proving bit {i} after flipping should have failed",
1147 );
1148 }
1149 }
1150 })
1151 }
1152
1153 #[test_traced]
1154 fn test_bitmap_persistence() {
1155 const PARTITION: &str = "bitmap-test";
1156 const FULL_CHUNK_COUNT: usize = 100;
1157
1158 let executor = deterministic::Runner::default();
1159 executor.start(|context| async move {
1160 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1161 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
1163 context.child("initial"),
1164 PARTITION,
1165 Sequential,
1166 &hasher,
1167 )
1168 .await
1169 .unwrap();
1170 assert_eq!(bitmap.len(), 0);
1171
1172 let mut dirty = bitmap.into_dirty();
1174 for i in 0..FULL_CHUNK_COUNT {
1175 dirty.push_chunk(&test_chunk(format!("test{i}").as_bytes()));
1176 }
1177 let mut bitmap = dirty.merkleize(&hasher).unwrap();
1178 let chunk_aligned_root = bitmap.root();
1179
1180 let mut dirty = bitmap.into_dirty();
1182 dirty.push_byte(0xA6);
1183 dirty.push(true);
1184 dirty.push(false);
1185 dirty.push(true);
1186 bitmap = dirty.merkleize(&hasher).unwrap();
1187 let root = bitmap.root();
1188
1189 for i in (10..=FULL_CHUNK_COUNT).step_by(10) {
1191 bitmap
1192 .prune_to_bit(
1193 (i * TestMerkleizedBitMap::<SHA256_SIZE>::CHUNK_SIZE_BITS as usize) as u64,
1194 )
1195 .unwrap();
1196 bitmap.write_pruned().await.unwrap();
1197 bitmap = TestMerkleizedBitMap::init(
1198 context.child("restore").with_attribute("index", i),
1199 PARTITION,
1200 Sequential,
1201 &hasher,
1202 )
1203 .await
1204 .unwrap();
1205 let _ = bitmap.root();
1206
1207 let mut dirty = bitmap.into_dirty();
1209 for j in i..FULL_CHUNK_COUNT {
1210 dirty.push_chunk(&test_chunk(format!("test{j}").as_bytes()));
1211 }
1212 assert_eq!(dirty.bitmap.pruned_chunks(), i);
1213 assert_eq!(dirty.len(), FULL_CHUNK_COUNT as u64 * 256);
1214 bitmap = dirty.merkleize(&hasher).unwrap();
1215 assert_eq!(bitmap.root(), chunk_aligned_root);
1216
1217 let mut dirty = bitmap.into_dirty();
1219 dirty.push_byte(0xA6);
1220 dirty.push(true);
1221 dirty.push(false);
1222 dirty.push(true);
1223 bitmap = dirty.merkleize(&hasher).unwrap();
1224 assert_eq!(bitmap.root(), root);
1225 }
1226 });
1227 }
1228
1229 #[test_traced]
1230 fn test_bitmap_proof_out_of_bounds() {
1231 let executor = deterministic::Runner::default();
1232 executor.start(|context| async move {
1233 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1234 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
1235 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
1236 .await
1237 .unwrap();
1238 let mut dirty = bitmap.into_dirty();
1239 dirty.push_chunk(&test_chunk(b"test"));
1240 let bitmap = dirty.merkleize(&hasher).unwrap();
1241
1242 let result = bitmap.proof(&hasher, 256).await;
1244 assert!(matches!(result, Err(Error::BitOutOfBounds(offset, size))
1245 if offset == 256 && size == 256));
1246
1247 let result = bitmap.proof(&hasher, 1000).await;
1248 assert!(matches!(result, Err(Error::BitOutOfBounds(offset, size))
1249 if offset == 1000 && size == 256));
1250
1251 assert!(bitmap.proof(&hasher, 0).await.is_ok());
1253 assert!(bitmap.proof(&hasher, 255).await.is_ok());
1254 });
1255 }
1256}