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::{Copying, 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 bit_len: u64,
43 last_chunk_digest: &H::Digest,
44) -> H::Digest {
45 assert!(!bit_len.is_multiple_of(UtilsBitMap::<N>::CHUNK_SIZE_BITS));
46 let bit_len = bit_len.to_be_bytes();
47 hasher.hash(&[
48 mmr_root.as_ref(),
49 bit_len.as_slice(),
50 last_chunk_digest.as_ref(),
51 ])
52}
53
54mod private {
55 pub trait Sealed {}
56}
57
58pub trait State<D: Digest>: private::Sealed + Sized + Send + Sync {}
60
61pub struct Merkleized<D: Digest> {
63 root: D,
65}
66
67impl<D: Digest> private::Sealed for Merkleized<D> {}
68impl<D: Digest> State<D> for Merkleized<D> {}
69
70pub struct Unmerkleized {
72 dirty_chunks: AHashSet<usize>,
79}
80
81impl private::Sealed for Unmerkleized {}
82impl<D: Digest> State<D> for Unmerkleized {}
83
84pub type MerkleizedBitMap<E, D, const N: usize, S> = BitMap<E, D, N, Merkleized<D>, S>;
86
87pub type UnmerkleizedBitMap<E, D, const N: usize, S> = BitMap<E, D, N, Unmerkleized, S>;
89
90pub struct BitMap<E: Context, D: Digest, const N: usize, M: State<D>, S: Strategy> {
108 bitmap: PrunableBitMap<N>,
110
111 mmr: Mmr<D>,
121
122 strategy: S,
124
125 state: M,
127
128 metadata: Metadata<E, U64, Vec<u8>>,
130}
131
132const NODE_PREFIX: u8 = 0;
134
135const PRUNED_CHUNKS_PREFIX: u8 = 1;
137
138impl<E: Context, D: Digest, const N: usize, M: State<D>, S: Strategy> BitMap<E, D, N, M, S> {
139 pub const CHUNK_SIZE_BITS: u64 = PrunableBitMap::<N>::CHUNK_SIZE_BITS;
141
142 #[inline]
144 pub fn size(&self) -> Position {
145 self.mmr.size()
146 }
147
148 #[inline]
150 pub const fn len(&self) -> u64 {
151 self.bitmap.len()
152 }
153
154 #[inline]
156 pub const fn is_empty(&self) -> bool {
157 self.len() == 0
158 }
159
160 #[inline]
162 pub const fn pruned_bits(&self) -> u64 {
163 self.bitmap.pruned_bits()
164 }
165
166 #[inline]
169 fn complete_chunks(&self) -> usize {
170 self.bitmap.complete_chunks()
171 }
172
173 #[inline]
176 pub fn last_chunk(&self) -> (&[u8; N], u64) {
177 self.bitmap.last_chunk()
178 }
179
180 #[inline]
186 pub fn get_chunk_containing(&self, bit: u64) -> &[u8; N] {
187 self.bitmap.get_chunk_containing(bit)
188 }
189
190 #[inline]
196 pub fn get_bit(&self, bit: u64) -> bool {
197 self.bitmap.get_bit(bit)
198 }
199
200 #[inline]
203 pub const fn get_bit_from_chunk(chunk: &[u8; N], bit: u64) -> bool {
204 PrunableBitMap::<N>::get_bit_from_chunk(chunk, bit)
205 }
206
207 pub fn verify_bit_inclusion(
210 hasher: &impl Hasher<mmr::Family, Digest = D>,
211 proof: &Proof<D>,
212 chunk: &[u8; N],
213 bit: u64,
214 root: &D,
215 ) -> bool {
216 let bit_len = *proof.leaves;
217 if bit >= bit_len {
218 debug!(bit_len, bit, "tried to verify non-existent bit");
219 return false;
220 }
221
222 if proof.inactive_peaks != 0 {
224 debug!(
225 inactive_peaks = proof.inactive_peaks,
226 "bitmap proof must have inactive_peaks == 0"
227 );
228 return false;
229 }
230
231 let chunked_leaves = Location::new(bit_len / Self::CHUNK_SIZE_BITS);
233 let mut mmr_proof = Proof {
234 leaves: chunked_leaves,
235 inactive_peaks: 0,
236 digests: proof.digests.clone(),
237 };
238
239 let loc = Location::new(bit / Self::CHUNK_SIZE_BITS);
240 if bit_len.is_multiple_of(Self::CHUNK_SIZE_BITS) {
241 return mmr_proof.verify_element_inclusion(hasher, chunk, loc, root);
242 }
243
244 if proof.digests.is_empty() {
245 debug!("proof has no digests");
246 return false;
247 }
248 let last_digest = mmr_proof.digests.pop().unwrap();
249
250 if chunked_leaves == loc {
251 if !mmr_proof.digests.is_empty() {
255 debug!(
256 digests = mmr_proof.digests.len() + 1,
257 "proof over partial chunk should have exactly 1 digest"
258 );
259 return false;
260 }
261 let last_chunk_digest = hasher.digest(chunk);
262 let reconstructed_root =
263 partial_chunk_root::<_, N>(hasher, &last_digest, bit_len, &last_chunk_digest);
264 return reconstructed_root == *root;
265 };
266
267 let mmr_root = match mmr_proof.reconstruct_root(hasher, &[chunk], loc) {
269 Ok(root) => root,
270 Err(error) => {
271 debug!(error = ?error, "invalid proof input");
272 return false;
273 }
274 };
275
276 let reconstructed_root =
277 partial_chunk_root::<_, N>(hasher, &mmr_root, bit_len, &last_digest);
278
279 reconstructed_root == *root
280 }
281}
282
283impl<E: Context, D: Digest, const N: usize, S: Strategy> MerkleizedBitMap<E, D, N, S> {
284 pub async fn init(
291 context: E,
292 partition: &str,
293 strategy: S,
294 hasher: &impl Hasher<mmr::Family, Digest = D>,
295 ) -> Result<Self, Error> {
296 let metadata_cfg = MConfig {
297 partition: partition.into(),
298 codec_config: ((0..).into(), ()),
299 };
300 let metadata =
301 Metadata::<_, U64, Vec<u8>>::init(context.child("metadata"), metadata_cfg).await?;
302
303 let key: U64 = U64::new(PRUNED_CHUNKS_PREFIX, 0);
304 let pruned_chunks = match metadata.get(&key) {
305 Some(bytes) => u64::from_be_bytes(bytes.as_slice().try_into().map_err(|_| {
306 error!("pruned chunks value not a valid u64");
307 Error::DataCorrupted("pruned chunks value not a valid u64")
308 })?),
309 None => {
310 warn!("bitmap metadata does not contain pruned chunks, initializing as empty");
311 0
312 }
313 } as usize;
314 if pruned_chunks == 0 {
315 let mmr = Mmr::new();
316 let cached_root = mmr.root(hasher, 0)?;
317 return Ok(Self {
318 bitmap: PrunableBitMap::new(),
319 mmr,
320 strategy,
321 metadata,
322 state: Merkleized { root: cached_root },
323 });
324 }
325 let pruned_loc = Location::new(pruned_chunks as u64);
326 if !pruned_loc.is_valid() {
327 return Err(Error::DataCorrupted("pruned chunks exceeds MAX_LEAVES"));
328 }
329
330 let mut pinned_nodes = Vec::new();
331 for (index, pos) in mmr::Family::nodes_to_pin(pruned_loc).enumerate() {
332 let Some(bytes) = metadata.get(&U64::new(NODE_PREFIX, index as u64)) else {
333 error!(?pruned_loc, ?pos, "missing pinned node");
334 return Err(Error::MissingNode(pos));
335 };
336 let digest = D::decode(Copying(bytes));
337 let Ok(digest) = digest else {
338 error!(?pruned_loc, ?pos, "could not convert node bytes to digest");
339 return Err(Error::MissingNode(pos));
340 };
341 pinned_nodes.push(digest);
342 }
343
344 let mmr = Mmr::init(Config {
345 nodes: Vec::new(),
346 pruning_boundary: Location::new(pruned_chunks as u64),
347 pinned_nodes,
348 })?;
349
350 let bitmap = PrunableBitMap::new_with_pruned_chunks(pruned_chunks)
351 .expect("pruned_chunks should never overflow");
352 let cached_root = mmr.root(hasher, 0)?;
353 Ok(Self {
354 bitmap,
355 mmr,
356 strategy,
357 metadata,
358 state: Merkleized { root: cached_root },
359 })
360 }
361
362 pub fn get_node(&self, position: Position) -> Option<D> {
363 self.mmr.get_node(position)
364 }
365
366 pub async fn write_pruned(mut self) -> Result<Self, Error> {
373 self.metadata.clear();
374
375 let key = U64::new(PRUNED_CHUNKS_PREFIX, 0);
377 self.metadata
378 .put(key, self.bitmap.pruned_chunks().to_be_bytes().to_vec());
379
380 let pruned_loc = Location::new(self.bitmap.pruned_chunks() as u64);
382 assert!(
383 pruned_loc.is_valid(),
384 "expected valid location from pruned_chunks"
385 );
386 for (i, digest) in mmr::Family::nodes_to_pin(pruned_loc).enumerate() {
387 let digest = self.mmr.get_node_unchecked(digest);
388 let key = U64::new(NODE_PREFIX, i as u64);
389 self.metadata.put(key, digest.to_vec());
390 }
391
392 self.metadata = self.metadata.sync().await.map_err(Error::Metadata)?;
393 Ok(self)
394 }
395
396 pub async fn destroy(self) -> Result<(), Error> {
398 self.metadata.destroy().await.map_err(Error::Metadata)
399 }
400
401 pub fn prune_to_bit(&mut self, bit: u64) -> Result<(), Error> {
409 let chunk = PrunableBitMap::<N>::to_chunk_index(bit);
410 if chunk < self.bitmap.pruned_chunks() {
411 return Ok(());
412 }
413
414 self.bitmap.prune_to_bit(bit);
416
417 self.mmr.prune(Location::new(chunk as u64))?;
418 Ok(())
419 }
420
421 pub const fn root(&self) -> D {
435 self.state.root
436 }
437
438 pub async fn proof(
450 &self,
451 hasher: &impl Hasher<mmr::Family, Digest = D>,
452 bit: u64,
453 ) -> Result<(Proof<D>, [u8; N]), Error> {
454 if bit >= self.len() {
455 return Err(Error::BitOutOfBounds(bit, self.len()));
456 }
457
458 let chunk = *self.get_chunk_containing(bit);
459 let chunk_loc = Location::from(PrunableBitMap::<N>::to_chunk_index(bit));
460 let (last_chunk, next_bit) = self.bitmap.last_chunk();
461
462 if chunk_loc == self.mmr.leaves() {
463 assert!(next_bit > 0);
464 return Ok((
467 Proof {
468 leaves: Location::new(self.len()),
469 inactive_peaks: 0,
470 digests: vec![self.mmr.root(hasher, 0)?],
471 },
472 chunk,
473 ));
474 }
475
476 let range = chunk_loc..chunk_loc + 1;
477 let mut proof = verification::range_proof(hasher, &self.mmr, range, 0).await?;
478 proof.leaves = Location::new(self.len());
479 if next_bit == Self::CHUNK_SIZE_BITS {
480 return Ok((proof, chunk));
482 }
483
484 let last_chunk_digest = hasher.digest(last_chunk);
487 proof.digests.push(last_chunk_digest);
488
489 Ok((proof, chunk))
490 }
491
492 pub fn into_dirty(self) -> UnmerkleizedBitMap<E, D, N, S> {
494 UnmerkleizedBitMap {
495 bitmap: self.bitmap,
496 mmr: self.mmr,
497 strategy: self.strategy,
498 state: Unmerkleized {
499 dirty_chunks: AHashSet::new(),
500 },
501 metadata: self.metadata,
502 }
503 }
504}
505
506impl<E: Context, D: Digest, const N: usize, S: Strategy> UnmerkleizedBitMap<E, D, N, S> {
507 pub fn push(&mut self, bit: bool) {
513 self.bitmap.push(bit);
514 }
515
516 pub fn set_bit(&mut self, bit: u64, value: bool) {
522 self.bitmap.set_bit(bit, value);
524
525 let chunk = PrunableBitMap::<N>::to_chunk_index(bit);
527 if chunk < *self.mmr.leaves() as usize {
528 self.state.dirty_chunks.insert(chunk);
529 }
530 }
531
532 pub fn merkleize(
534 mut self,
535 hasher: &impl Hasher<mmr::Family, Digest = D>,
536 ) -> Result<MerkleizedBitMap<E, D, N, S>, Error> {
537 let mut batch = self.mmr.new_batch_with_strategy(self.strategy.clone());
539 let start = *self.mmr.leaves() as usize;
540 let end = self.complete_chunks();
541 for i in start..end {
542 batch = batch.add(hasher, self.bitmap.get_chunk(i));
543 }
544
545 let updates: Vec<(Location, &[u8; N])> = self
547 .state
548 .dirty_chunks
549 .iter()
550 .map(|&chunk| {
551 let loc = Location::new(chunk as u64);
552 (loc, self.bitmap.get_chunk(chunk))
553 })
554 .collect();
555 let dirty: Vec<(Location, D)> = self.strategy.map_init_collect_vec(
556 &updates,
557 || hasher.clone(),
558 |h, &(loc, chunk)| {
559 let pos = Position::try_from(loc).unwrap();
560 (loc, h.leaf_digest(pos, chunk.as_ref()))
561 },
562 );
563 batch = batch.update_leaf_batched(&dirty)?;
564
565 let batch = batch.merkleize(&self.mmr, hasher);
567 self.mmr.apply_batch(&batch)?;
568
569 let mmr_root = self.mmr.root(hasher, 0)?;
571 let cached_root = if self.bitmap.is_chunk_aligned() {
572 mmr_root
573 } else {
574 let (last_chunk, _) = self.bitmap.last_chunk();
575 let last_chunk_digest = hasher.digest(last_chunk);
576 partial_chunk_root::<_, N>(hasher, &mmr_root, self.len(), &last_chunk_digest)
577 };
578
579 Ok(MerkleizedBitMap {
580 bitmap: self.bitmap,
581 mmr: self.mmr,
582 strategy: self.strategy,
583 metadata: self.metadata,
584 state: Merkleized { root: cached_root },
585 })
586 }
587}
588
589impl<E: Context, D: Digest, const N: usize, S: Strategy> Storage<mmr::Family>
590 for MerkleizedBitMap<E, D, N, S>
591{
592 type Digest = D;
593
594 fn size(&self) -> Position {
595 self.size()
596 }
597
598 async fn get_node(&self, position: Position) -> Result<Option<D>, Error> {
599 Ok(self.get_node(position))
600 }
601}
602
603#[cfg(test)]
604mod tests {
605 use super::*;
606 use crate::merkle::Bagging::{self, BackwardFold, ForwardFold};
607 use commonware_codec::FixedSize;
608 use commonware_cryptography::{Hasher, Sha256, sha256};
609 use commonware_macros::test_traced;
610 use commonware_parallel::Sequential;
611 use commonware_runtime::{Runner as _, Supervisor as _, deterministic};
612 use mmr::StandardHasher;
613
614 const SHA256_SIZE: usize = sha256::Digest::SIZE;
615
616 type TestContext = deterministic::Context;
617 type TestMerkleizedBitMap<const N: usize> =
618 MerkleizedBitMap<TestContext, sha256::Digest, N, Sequential>;
619
620 impl<E: Context, D: Digest, const N: usize> UnmerkleizedBitMap<E, D, N, Sequential> {
621 fn push_byte(&mut self, byte: u8) {
629 self.bitmap.push_byte(byte);
630 }
631
632 fn push_chunk(&mut self, chunk: &[u8; N]) {
640 self.bitmap.push_chunk(chunk);
641 }
642 }
643
644 fn test_chunk<const N: usize>(s: &[u8]) -> [u8; N] {
645 assert_eq!(N % 32, 0);
646 let mut vec: Vec<u8> = Vec::new();
647 for _ in 0..N / 32 {
648 vec.extend(Sha256::hash(&[s]).iter());
649 }
650
651 vec.try_into().unwrap()
652 }
653
654 #[test_traced]
655 fn test_bitmap_verify_empty_proof() {
656 let executor = deterministic::Runner::default();
657 executor.start(|_context| async move {
658 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
659 let proof = Proof {
660 leaves: Location::new(100),
661 inactive_peaks: 0,
662 digests: Vec::new(),
663 };
664 assert!(
665 !TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
666 &hasher,
667 &proof,
668 &[0u8; SHA256_SIZE],
669 0,
670 &Sha256::fill(0x00),
671 ),
672 "proof without digests shouldn't verify or panic"
673 );
674 });
675 }
676
677 #[test_traced]
680 fn test_bitmap_verify_rejects_nonzero_inactive_peaks() {
681 let executor = deterministic::Runner::default();
682 executor.start(|context| async move {
683 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
684 let mut bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
685 context.child("bitmap"),
686 "inactive_peaks_canonical",
687 Sequential,
688 &hasher,
689 )
690 .await
691 .unwrap();
692
693 let mut dirty = bitmap.into_dirty();
695 for i in 0..(TestMerkleizedBitMap::<SHA256_SIZE>::CHUNK_SIZE_BITS * 4) {
696 dirty.push(i % 3 == 0);
697 }
698 bitmap = dirty.merkleize(&hasher).unwrap();
699 let root = bitmap.root();
700
701 let bit = TestMerkleizedBitMap::<SHA256_SIZE>::CHUNK_SIZE_BITS + 5;
702 let (proof, chunk) = bitmap.proof(&hasher, bit).await.unwrap();
703 assert_eq!(proof.inactive_peaks, 0);
704
705 assert!(
707 TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
708 &hasher, &proof, &chunk, bit, &root
709 ),
710 "canonical bitmap proof should verify"
711 );
712
713 let mut tampered = proof;
715 tampered.inactive_peaks = 1;
716 assert!(
717 !TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
718 &hasher, &tampered, &chunk, bit, &root
719 ),
720 "bitmap proof with nonzero inactive_peaks must not verify"
721 );
722 });
723 }
724
725 #[test_traced]
726 fn test_bitmap_verify_rejects_relocated_partial_chunk() {
727 for bagging in [ForwardFold, BackwardFold] {
728 for full_chunks in [0, 1, 2, 3, 7] {
730 test_bitmap_verify_rejects_relocated_partial_chunk_n::<1>(full_chunks, bagging);
731 test_bitmap_verify_rejects_relocated_partial_chunk_n::<32>(full_chunks, bagging);
732 test_bitmap_verify_rejects_relocated_partial_chunk_n::<64>(full_chunks, bagging);
733 }
734 }
735 }
736
737 fn test_bitmap_verify_rejects_relocated_partial_chunk_n<const N: usize>(
738 full_chunks: u64,
739 bagging: Bagging,
740 ) {
741 let chunk_bits = TestMerkleizedBitMap::<N>::CHUNK_SIZE_BITS;
742 for tail_bits in [1, 2, chunk_bits - 1] {
743 let executor = deterministic::Runner::default();
744 executor.start(|context| async move {
745 let hasher = StandardHasher::<Sha256>::new(bagging);
746 let bitmap: TestMerkleizedBitMap<N> = TestMerkleizedBitMap::init(
747 context.child("bitmap"),
748 "test",
749 Sequential,
750 &hasher,
751 )
752 .await
753 .unwrap();
754 let mut dirty = bitmap.into_dirty();
755 let tail_start = full_chunks * chunk_bits;
756
757 for i in 0..tail_start {
759 dirty.push(i % 2 != 0);
760 }
761 for i in 0..tail_bits {
762 dirty.push(i % 2 == 0);
763 }
764 let mut bitmap = dirty.merkleize(&hasher).unwrap();
765 let root = bitmap.root();
766
767 for prune_to in [0, tail_start] {
769 bitmap.prune_to_bit(prune_to).unwrap();
770 bitmap = bitmap.into_dirty().merkleize(&hasher).unwrap();
771 assert_eq!(
772 bitmap.root(), root,
773 "N={N}, bagging={bagging:?}, full_chunks={full_chunks}, \
774 tail_bits={tail_bits}, prune_to={prune_to}",
775 );
776 let (proof, chunk) = bitmap.proof(&hasher, tail_start).await.unwrap();
777 assert_eq!(*proof.leaves, tail_start + tail_bits);
778
779 for target_chunk in (0..=full_chunks + 1).chain([
781 full_chunks + 8,
782 u64::from(u32::MAX) + 1,
783 (*mmr::Family::MAX_LEAVES - tail_bits) / chunk_bits,
784 (u64::MAX - tail_bits) / chunk_bits,
785 ]) {
786 let mut relocated = proof.clone();
787 relocated.leaves = Location::new(target_chunk * chunk_bits + tail_bits);
788 for offset in 0..=tail_bits {
789 let bit = target_chunk * chunk_bits + offset;
790 if target_chunk < full_chunks && offset < tail_bits {
792 assert_ne!(
793 TestMerkleizedBitMap::<N>::get_bit_from_chunk(&chunk, bit),
794 !bit.is_multiple_of(2),
795 );
796 }
797 let valid = TestMerkleizedBitMap::<N>::verify_bit_inclusion(
798 &hasher, &relocated, &chunk, bit, &root,
799 );
800 assert_eq!(
802 valid,
803 target_chunk == full_chunks && offset < tail_bits,
804 "N={N}, bagging={bagging:?}, full_chunks={full_chunks}, tail_bits={tail_bits}, \
805 prune_to={prune_to}, target_chunk={target_chunk}, offset={offset}",
806 );
807 }
808 }
809 }
810 });
811 }
812 }
813
814 #[test_traced]
815 fn test_bitmap_verify_rejects_tampered_length() {
816 const CHUNK_BITS: u64 = TestMerkleizedBitMap::<1>::CHUNK_SIZE_BITS;
817 const MAX_BITS: u64 = 8 * CHUNK_BITS + 1;
818
819 for bagging in [ForwardFold, BackwardFold] {
820 deterministic::Runner::default().start(|context| async move {
821 let hasher = StandardHasher::<Sha256>::new(bagging);
822 let mut bitmap: TestMerkleizedBitMap<1> = TestMerkleizedBitMap::init(
823 context.child("bitmap"),
824 "test",
825 Sequential,
826 &hasher,
827 )
828 .await
829 .unwrap();
830
831 for bit_len in 1..=MAX_BITS {
833 let mut dirty = bitmap.into_dirty();
834 dirty.push(bit_len % 2 == 0);
835 bitmap = dirty.merkleize(&hasher).unwrap();
836 let root = bitmap.root();
837
838 for bit in (0..bit_len).step_by(CHUNK_BITS as usize) {
840 let (mut proof, chunk) = bitmap.proof(&hasher, bit).await.unwrap();
841 for claimed_len in (0..=MAX_BITS).chain([
844 (u64::from(u32::MAX) + 1) * CHUNK_BITS,
845 (u64::from(u32::MAX) + 1) * CHUNK_BITS + 1,
846 *mmr::Family::MAX_LEAVES,
847 u64::MAX,
848 ]) {
849 proof.leaves = Location::new(claimed_len);
850 assert_eq!(
851 TestMerkleizedBitMap::<1>::verify_bit_inclusion(
852 &hasher, &proof, &chunk, bit, &root,
853 ),
854 claimed_len == bit_len,
855 "bagging={bagging:?}, bit_len={bit_len}, bit={bit}, claimed_len={claimed_len}",
856 );
857 }
858 }
859 }
860 });
861 }
862 }
863
864 #[test_traced]
865 fn test_bitmap_empty_then_one() {
866 let executor = deterministic::Runner::default();
867 executor.start(|context| async move {
868 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
869 let mut bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
870 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
871 .await
872 .unwrap();
873 assert_eq!(bitmap.len(), 0);
874 assert_eq!(bitmap.bitmap.pruned_chunks(), 0);
875 bitmap.prune_to_bit(0).unwrap();
876 assert_eq!(bitmap.bitmap.pruned_chunks(), 0);
877
878 let root = bitmap.root();
880 let mut dirty = bitmap.into_dirty();
881 dirty.push(true);
882 bitmap = dirty.merkleize(&hasher).unwrap();
883 let new_root = bitmap.root();
885 assert_ne!(root, new_root);
886 let root = new_root;
887 bitmap.prune_to_bit(1).unwrap();
888 assert_eq!(bitmap.len(), 1);
889 assert_ne!(bitmap.last_chunk().0, &[0u8; SHA256_SIZE]);
890 assert_eq!(bitmap.last_chunk().1, 1);
891 assert_eq!(bitmap.bitmap.pruned_chunks(), 0);
893 assert_eq!(root, bitmap.root());
894
895 let mut dirty = bitmap.into_dirty();
897 for i in 0..(TestMerkleizedBitMap::<SHA256_SIZE>::CHUNK_SIZE_BITS - 1) {
898 dirty.push(i % 2 != 0);
899 }
900 bitmap = dirty.merkleize(&hasher).unwrap();
901 assert_eq!(bitmap.len(), 256);
902 assert_ne!(root, bitmap.root());
903 let root = bitmap.root();
904
905 let (proof, chunk) = bitmap.proof(&hasher, 0).await.unwrap();
907 assert!(
908 TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
909 &hasher, &proof, &chunk, 255, &root
910 ),
911 "failed to prove bit in only chunk"
912 );
913 assert!(
915 !TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
916 &hasher, &proof, &chunk, 256, &root
917 ),
918 "should not be able to prove bit outside of chunk"
919 );
920
921 bitmap.prune_to_bit(256).unwrap();
923 assert_eq!(bitmap.len(), 256);
924 assert_eq!(bitmap.bitmap.pruned_chunks(), 1);
925 assert_eq!(bitmap.bitmap.pruned_bits(), 256);
926 assert_eq!(root, bitmap.root());
927
928 bitmap.prune_to_bit(10).unwrap();
930 assert_eq!(root, bitmap.root());
931 });
932 }
933
934 #[test_traced]
935 fn test_bitmap_building() {
936 let executor = deterministic::Runner::default();
939 executor.start(|context| async move {
940 let test_chunk = test_chunk(b"test");
941 let hasher: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
942
943 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
945 context.child("bitmap").with_attribute("index", 1),
946 "test1",
947 Sequential,
948 &hasher,
949 )
950 .await
951 .unwrap();
952 let mut dirty = bitmap.into_dirty();
953 dirty.push_chunk(&test_chunk);
954 for b in test_chunk {
955 for j in 0..8 {
956 let mask = 1 << j;
957 let bit = (b & mask) != 0;
958 dirty.push(bit);
959 }
960 }
961 assert_eq!(dirty.len(), 256 * 2);
962
963 let bitmap = dirty.merkleize(&hasher).unwrap();
964 let root = bitmap.root();
965 let inner_root = bitmap.mmr.root(&hasher, 0).unwrap();
966 assert_eq!(root, inner_root);
967
968 {
969 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
972 context.child("bitmap").with_attribute("index", 2),
973 "test2",
974 Sequential,
975 &hasher,
976 )
977 .await
978 .unwrap();
979 let mut dirty = bitmap.into_dirty();
980 dirty.push_chunk(&test_chunk);
981 dirty.push_chunk(&test_chunk);
982 let bitmap = dirty.merkleize(&hasher).unwrap();
983 let same_root = bitmap.root();
984 assert_eq!(root, same_root);
985 }
986 {
987 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
989 context.child("bitmap").with_attribute("index", 3),
990 "test3",
991 Sequential,
992 &hasher,
993 )
994 .await
995 .unwrap();
996 let mut dirty = bitmap.into_dirty();
997 dirty.push_chunk(&test_chunk);
998 for b in test_chunk {
999 dirty.push_byte(b);
1000 }
1001 let bitmap = dirty.merkleize(&hasher).unwrap();
1002 let same_root = bitmap.root();
1003 assert_eq!(root, same_root);
1004 }
1005 });
1006 }
1007
1008 #[test_traced]
1009 #[should_panic(expected = "cannot add chunk")]
1010 fn test_bitmap_build_chunked_panic() {
1011 let executor = deterministic::Runner::default();
1012 executor.start(|context| async move {
1013 let hasher: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
1014 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
1015 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
1016 .await
1017 .unwrap();
1018 let mut dirty = bitmap.into_dirty();
1019 dirty.push_chunk(&test_chunk(b"test"));
1020 dirty.push(true);
1021 dirty.push_chunk(&test_chunk(b"panic"));
1022 });
1023 }
1024
1025 #[test_traced]
1026 #[should_panic(expected = "cannot add byte")]
1027 fn test_bitmap_build_byte_panic() {
1028 let executor = deterministic::Runner::default();
1029 executor.start(|context| async move {
1030 let hasher: StandardHasher<Sha256> = StandardHasher::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(true);
1038 dirty.push_byte(0x01);
1039 });
1040 }
1041
1042 #[test_traced]
1043 #[should_panic(expected = "out of bounds")]
1044 fn test_bitmap_get_out_of_bounds_bit_panic() {
1045 let executor = deterministic::Runner::default();
1046 executor.start(|context| async move {
1047 let hasher: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
1048 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
1049 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
1050 .await
1051 .unwrap();
1052 let mut dirty = bitmap.into_dirty();
1053 dirty.push_chunk(&test_chunk(b"test"));
1054 dirty.get_bit(256);
1055 });
1056 }
1057
1058 #[test_traced]
1059 #[should_panic(expected = "pruned")]
1060 fn test_bitmap_get_pruned_bit_panic() {
1061 let executor = deterministic::Runner::default();
1062 executor.start(|context| async move {
1063 let hasher: StandardHasher<Sha256> = StandardHasher::new(ForwardFold);
1064 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
1065 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
1066 .await
1067 .unwrap();
1068 let mut dirty = bitmap.into_dirty();
1069 dirty.push_chunk(&test_chunk(b"test"));
1070 dirty.push_chunk(&test_chunk(b"test2"));
1071 let mut bitmap = dirty.merkleize(&hasher).unwrap();
1072
1073 bitmap.prune_to_bit(256).unwrap();
1074 bitmap.get_bit(255);
1075 });
1076 }
1077
1078 #[test_traced]
1079 fn test_bitmap_root_boundaries() {
1080 let executor = deterministic::Runner::default();
1081 executor.start(|context| async move {
1082 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1084 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
1085 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
1086 .await
1087 .unwrap();
1088 let mut dirty = bitmap.into_dirty();
1089 dirty.push_chunk(&test_chunk(b"test"));
1090 dirty.push_chunk(&test_chunk(b"test2"));
1091 let mut bitmap = dirty.merkleize(&hasher).unwrap();
1092
1093 let root = bitmap.root();
1094
1095 let mut dirty = bitmap.into_dirty();
1097 dirty.push(true);
1098 bitmap = dirty.merkleize(&hasher).unwrap();
1099 let new_root = bitmap.root();
1100 assert_ne!(root, new_root);
1101 assert_eq!(bitmap.mmr.size(), 3); for _ in 0..(SHA256_SIZE * 8 - 1) {
1105 let mut dirty = bitmap.into_dirty();
1106 dirty.push(false);
1107 bitmap = dirty.merkleize(&hasher).unwrap();
1108 let newer_root = bitmap.root();
1109 assert_ne!(new_root, newer_root);
1111 }
1112 assert_eq!(bitmap.mmr.size(), 4); let mut dirty = bitmap.into_dirty();
1116 dirty.push(false);
1117 assert_eq!(dirty.len(), 256 * 3 + 1);
1118 bitmap = dirty.merkleize(&hasher).unwrap();
1119 let newer_root = bitmap.root();
1120 assert_ne!(new_root, newer_root);
1121
1122 bitmap.prune_to_bit(bitmap.len()).unwrap();
1124 assert_eq!(bitmap.bitmap.pruned_chunks(), 3);
1125 assert_eq!(bitmap.len(), 256 * 3 + 1);
1126 assert_eq!(newer_root, bitmap.root());
1127 });
1128 }
1129
1130 #[test_traced]
1131 fn test_bitmap_get_set_bits() {
1132 let executor = deterministic::Runner::default();
1133 executor.start(|context| async move {
1134 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1136 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
1137 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
1138 .await
1139 .unwrap();
1140 let mut dirty = bitmap.into_dirty();
1141 dirty.push_chunk(&test_chunk(b"test"));
1142 dirty.push_chunk(&test_chunk(b"test2"));
1143 dirty.push_chunk(&test_chunk(b"test3"));
1144 dirty.push_chunk(&test_chunk(b"test4"));
1145 dirty.push_byte(0xF1);
1147 dirty.push(true);
1148 dirty.push(false);
1149 dirty.push(true);
1150
1151 let mut bitmap = dirty.merkleize(&hasher).unwrap();
1152 let root = bitmap.root();
1153
1154 for bit_pos in (0..bitmap.len()).rev() {
1157 let bit = bitmap.get_bit(bit_pos);
1158 let mut dirty = bitmap.into_dirty();
1159 dirty.set_bit(bit_pos, !bit);
1160 bitmap = dirty.merkleize(&hasher).unwrap();
1161 let new_root = bitmap.root();
1162 assert_ne!(root, new_root, "failed at bit {bit_pos}");
1163 let mut dirty = bitmap.into_dirty();
1165 dirty.set_bit(bit_pos, bit);
1166 bitmap = dirty.merkleize(&hasher).unwrap();
1167 let new_root = bitmap.root();
1168 assert_eq!(root, new_root);
1169 }
1170
1171 let start_bit = (SHA256_SIZE * 8 * 2) as u64;
1173 bitmap.prune_to_bit(start_bit).unwrap();
1174 for bit_pos in (start_bit..bitmap.len()).rev() {
1175 let bit = bitmap.get_bit(bit_pos);
1176 let mut dirty = bitmap.into_dirty();
1177 dirty.set_bit(bit_pos, !bit);
1178 bitmap = dirty.merkleize(&hasher).unwrap();
1179 let new_root = bitmap.root();
1180 assert_ne!(root, new_root, "failed at bit {bit_pos}");
1181 let mut dirty = bitmap.into_dirty();
1183 dirty.set_bit(bit_pos, bit);
1184 bitmap = dirty.merkleize(&hasher).unwrap();
1185 let new_root = bitmap.root();
1186 assert_eq!(root, new_root);
1187 }
1188 });
1189 }
1190
1191 fn flip_bit<const N: usize>(bit: u64, chunk: &[u8; N]) -> [u8; N] {
1192 let byte = PrunableBitMap::<N>::chunk_byte_offset(bit);
1193 let mask = PrunableBitMap::<N>::chunk_byte_bitmask(bit);
1194 let mut tmp = chunk.to_vec();
1195 tmp[byte] ^= mask;
1196 tmp.try_into().unwrap()
1197 }
1198
1199 #[test_traced]
1200 fn test_bitmap_mmr_proof_verification() {
1201 test_bitmap_mmr_proof_verification_n::<32>();
1202 test_bitmap_mmr_proof_verification_n::<64>();
1203 }
1204
1205 fn test_bitmap_mmr_proof_verification_n<const N: usize>() {
1206 let executor = deterministic::Runner::default();
1207 executor.start(|context| async move {
1208 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1210 let bitmap: MerkleizedBitMap<TestContext, sha256::Digest, N, Sequential> =
1211 MerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
1212 .await
1213 .unwrap();
1214 let mut dirty = bitmap.into_dirty();
1215 for i in 0u32..10 {
1216 dirty.push_chunk(&test_chunk(format!("test{i}").as_bytes()));
1217 }
1218 dirty.push_byte(0xA6);
1220 dirty.push(true);
1221 dirty.push(false);
1222 dirty.push(true);
1223 dirty.push(true);
1224 dirty.push(false);
1225
1226 let mut bitmap = dirty.merkleize(&hasher).unwrap();
1227 let root = bitmap.root();
1228
1229 for prune_to_bit in (0..bitmap.len()).step_by(251) {
1232 assert_eq!(bitmap.root(), root);
1233 bitmap.prune_to_bit(prune_to_bit).unwrap();
1234 for i in prune_to_bit..bitmap.len() {
1235 let (proof, chunk) = bitmap.proof(&hasher, i).await.unwrap();
1236
1237 assert!(
1239 MerkleizedBitMap::<TestContext, _, N, Sequential>::verify_bit_inclusion(
1240 &hasher, &proof, &chunk, i, &root
1241 ),
1242 "failed to prove bit {i}",
1243 );
1244
1245 let corrupted = flip_bit(i, &chunk);
1247 assert!(
1248 !MerkleizedBitMap::<TestContext, _, N, Sequential>::verify_bit_inclusion(
1249 &hasher, &proof, &corrupted, i, &root
1250 ),
1251 "proving bit {i} after flipping should have failed",
1252 );
1253 }
1254 }
1255 })
1256 }
1257
1258 #[test_traced]
1259 fn test_bitmap_persistence() {
1260 const PARTITION: &str = "bitmap-test";
1261 const FULL_CHUNK_COUNT: usize = 100;
1262
1263 let executor = deterministic::Runner::default();
1264 executor.start(|context| async move {
1265 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1266 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
1268 context.child("initial"),
1269 PARTITION,
1270 Sequential,
1271 &hasher,
1272 )
1273 .await
1274 .unwrap();
1275 assert_eq!(bitmap.len(), 0);
1276
1277 let mut dirty = bitmap.into_dirty();
1279 for i in 0..FULL_CHUNK_COUNT {
1280 dirty.push_chunk(&test_chunk(format!("test{i}").as_bytes()));
1281 }
1282 let mut bitmap = dirty.merkleize(&hasher).unwrap();
1283 let chunk_aligned_root = bitmap.root();
1284
1285 let mut dirty = bitmap.into_dirty();
1287 dirty.push_byte(0xA6);
1288 dirty.push(true);
1289 dirty.push(false);
1290 dirty.push(true);
1291 bitmap = dirty.merkleize(&hasher).unwrap();
1292 let root = bitmap.root();
1293
1294 for i in (10..=FULL_CHUNK_COUNT).step_by(10) {
1296 bitmap
1297 .prune_to_bit(
1298 (i * TestMerkleizedBitMap::<SHA256_SIZE>::CHUNK_SIZE_BITS as usize) as u64,
1299 )
1300 .unwrap();
1301 bitmap.write_pruned().await.unwrap();
1302 bitmap = TestMerkleizedBitMap::init(
1303 context.child("restore").with_attribute("index", i),
1304 PARTITION,
1305 Sequential,
1306 &hasher,
1307 )
1308 .await
1309 .unwrap();
1310 let _ = bitmap.root();
1311
1312 let mut dirty = bitmap.into_dirty();
1314 for j in i..FULL_CHUNK_COUNT {
1315 dirty.push_chunk(&test_chunk(format!("test{j}").as_bytes()));
1316 }
1317 assert_eq!(dirty.bitmap.pruned_chunks(), i);
1318 assert_eq!(dirty.len(), FULL_CHUNK_COUNT as u64 * 256);
1319 bitmap = dirty.merkleize(&hasher).unwrap();
1320 assert_eq!(bitmap.root(), chunk_aligned_root);
1321
1322 let mut dirty = bitmap.into_dirty();
1324 dirty.push_byte(0xA6);
1325 dirty.push(true);
1326 dirty.push(false);
1327 dirty.push(true);
1328 bitmap = dirty.merkleize(&hasher).unwrap();
1329 assert_eq!(bitmap.root(), root);
1330 }
1331 });
1332 }
1333
1334 #[test_traced]
1335 fn test_bitmap_proof_out_of_bounds() {
1336 let executor = deterministic::Runner::default();
1337 executor.start(|context| async move {
1338 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1339 let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
1340 TestMerkleizedBitMap::init(context.child("bitmap"), "test", Sequential, &hasher)
1341 .await
1342 .unwrap();
1343 let mut dirty = bitmap.into_dirty();
1344 dirty.push_chunk(&test_chunk(b"test"));
1345 let bitmap = dirty.merkleize(&hasher).unwrap();
1346
1347 let result = bitmap.proof(&hasher, 256).await;
1349 assert!(matches!(result, Err(Error::BitOutOfBounds(offset, size))
1350 if offset == 256 && size == 256));
1351
1352 let result = bitmap.proof(&hasher, 1000).await;
1353 assert!(matches!(result, Err(Error::BitOutOfBounds(offset, size))
1354 if offset == 1000 && size == 256));
1355
1356 assert!(bitmap.proof(&hasher, 0).await.is_ok());
1358 assert!(bitmap.proof(&hasher, 255).await.is_ok());
1359 });
1360 }
1361}