Skip to main content

commonware_storage/bitmap/
authenticated.rs

1//! An authenticated bitmap.
2//!
3//! The authenticated bitmap is an in-memory data structure that does not persist its contents other
4//! than the data corresponding to its "pruned" section, allowing full restoration by "replaying"
5//! all retained elements.
6//!
7//! Authentication is provided by a Merkle tree that is maintained over the bitmap, with each leaf
8//! covering a chunk of N bytes. This Merkle tree isn't balanced, but instead mimics the structure
9//! of an MMR with an equivalent number of leaves. This structure reduces overhead of updating the
10//! most recently added elements, and (more importantly) simplifies aligning the bitmap with an MMR
11//! over elements whose activity state is reflected by the bitmap.
12
13use crate::{
14    metadata::{Config as MConfig, Metadata},
15    mmr::{
16        hasher::Hasher as MmrHasher,
17        iterator::nodes_to_pin,
18        mem::{Clean, CleanMmr, Config, Dirty, Mmr, State as MmrState},
19        storage::Storage,
20        verification,
21        Error::{self, *},
22        Location, Position, Proof,
23    },
24};
25use commonware_codec::DecodeExt;
26use commonware_cryptography::{Digest, Hasher};
27use commonware_parallel::ThreadPool;
28use commonware_runtime::{Clock, Metrics, Storage as RStorage};
29use commonware_utils::{
30    bitmap::{BitMap as UtilsBitMap, Prunable as PrunableBitMap},
31    sequence::prefixed_u64::U64,
32};
33use std::collections::HashSet;
34use tracing::{debug, error, warn};
35
36/// Returns a root digest that incorporates bits not yet part of the MMR because they
37/// belong to the last (unfilled) chunk.
38pub(crate) fn partial_chunk_root<H: Hasher, const N: usize>(
39    hasher: &mut H,
40    mmr_root: &H::Digest,
41    next_bit: u64,
42    last_chunk_digest: &H::Digest,
43) -> H::Digest {
44    assert!(next_bit > 0);
45    assert!(next_bit < UtilsBitMap::<N>::CHUNK_SIZE_BITS);
46    hasher.update(mmr_root);
47    hasher.update(&next_bit.to_be_bytes());
48    hasher.update(last_chunk_digest);
49    hasher.finalize()
50}
51
52mod private {
53    pub trait Sealed {}
54}
55
56/// Trait for valid [BitMap] type states.
57pub trait State<D: Digest>: private::Sealed + Sized + Send + Sync {
58    /// The merkleization type state for the inner [Mmr].
59    type MmrState: MmrState<D>;
60}
61
62/// Merkleized state: the bitmap has been merkleized and the root is cached.
63pub struct Merkleized<D: Digest> {
64    /// The cached root of the bitmap.
65    root: D,
66}
67
68impl<D: Digest> private::Sealed for Merkleized<D> {}
69impl<D: Digest> State<D> for Merkleized<D> {
70    type MmrState = Clean<D>;
71}
72
73/// Unmerkleized state: the bitmap has pending changes not yet merkleized.
74pub struct Unmerkleized {
75    /// Chunks that have been modified but not yet merkleized. Each dirty chunk is identified by its
76    /// "chunk index" (the index of the chunk in the status bitmap).
77    ///
78    /// Invariant: Indices are always in the range [0,`authenticated_len`).
79    dirty_chunks: HashSet<usize>,
80}
81
82impl private::Sealed for Unmerkleized {}
83impl<D: Digest> State<D> for Unmerkleized {
84    type MmrState = Dirty;
85}
86
87/// A merkleized bitmap whose root digest has been computed and cached.
88pub type MerkleizedBitMap<E, D, const N: usize> = BitMap<E, D, N, Merkleized<D>>;
89
90/// An unmerkleized bitmap whose root digest has not been computed.
91pub type UnmerkleizedBitMap<E, D, const N: usize> = BitMap<E, D, N, Unmerkleized>;
92
93/// A bitmap supporting inclusion proofs through Merkelization.
94///
95/// Merkelization of the bitmap is performed over chunks of N bytes. If the goal is to minimize
96/// proof sizes, choose an N that is equal to the size or double the size of the hasher's digest.
97///
98/// # Type States
99///
100/// The bitmap uses the type-state pattern to enforce at compile-time whether the bitmap has
101/// pending updates that must be merkleized before computing proofs. [MerkleizedBitMap] represents
102/// a bitmapwhose root digest has been computed and cached. [UnmerkleizedBitMap] represents a
103/// bitmap with pending updates. An unmerkleized bitmap can be converted into a merkleized bitmap
104/// by calling [UnmerkleizedBitMap::merkleize].
105///
106/// # Warning
107///
108/// Even though we use u64 identifiers for bits, on 32-bit machines, the maximum addressable bit is
109/// limited to (u32::MAX * N * 8).
110pub struct BitMap<
111    E: Clock + RStorage + Metrics,
112    D: Digest,
113    const N: usize,
114    S: State<D> = Merkleized<D>,
115> {
116    /// The underlying bitmap.
117    bitmap: PrunableBitMap<N>,
118
119    /// The number of bitmap chunks currently included in the `mmr`.
120    authenticated_len: usize,
121
122    /// A Merkle tree with each leaf representing an N*8 bit "chunk" of the bitmap.
123    ///
124    /// After calling `merkleize` all chunks are guaranteed to be included in the Merkle tree. The
125    /// last chunk of the bitmap is never part of the tree.
126    ///
127    /// Because leaf elements can be updated when bits in the bitmap are flipped, this tree, while
128    /// based on an MMR structure, is not an MMR but a Merkle tree. The MMR structure results in
129    /// reduced update overhead for elements being appended or updated near the tip compared to a
130    /// more typical balanced Merkle tree.
131    mmr: Mmr<D, S::MmrState>,
132
133    /// The thread pool to use for parallelization.
134    pool: Option<ThreadPool>,
135
136    /// Merkleization-dependent state.
137    state: S,
138
139    /// Metadata for persisting pruned state.
140    metadata: Metadata<E, U64, Vec<u8>>,
141}
142
143/// Prefix used for the metadata key identifying node digests.
144const NODE_PREFIX: u8 = 0;
145
146/// Prefix used for the metadata key identifying the pruned_chunks value.
147const PRUNED_CHUNKS_PREFIX: u8 = 1;
148
149impl<E: Clock + RStorage + Metrics, D: Digest, const N: usize, S: State<D>> BitMap<E, D, N, S> {
150    /// The size of a chunk in bits.
151    pub const CHUNK_SIZE_BITS: u64 = PrunableBitMap::<N>::CHUNK_SIZE_BITS;
152
153    /// Return the size of the bitmap in bits.
154    #[inline]
155    pub fn size(&self) -> Position {
156        self.mmr.size()
157    }
158
159    /// Return the number of bits currently stored in the bitmap, irrespective of any pruning.
160    #[inline]
161    pub const fn len(&self) -> u64 {
162        self.bitmap.len()
163    }
164
165    /// Returns true if the bitmap is empty.
166    #[inline]
167    pub const fn is_empty(&self) -> bool {
168        self.len() == 0
169    }
170
171    /// Return the number of bits that have been pruned from this bitmap.
172    #[inline]
173    pub const fn pruned_bits(&self) -> u64 {
174        self.bitmap.pruned_bits()
175    }
176
177    /// Returns the number of complete chunks (excludes partial chunk at end, if any).
178    #[inline]
179    fn complete_chunks(&self) -> usize {
180        let chunks_len = self.bitmap.chunks_len();
181        if self.bitmap.is_chunk_aligned() {
182            chunks_len
183        } else {
184            // Last chunk is partial
185            chunks_len.checked_sub(1).unwrap()
186        }
187    }
188
189    /// Return the last chunk of the bitmap and its size in bits. The size can be 0 (meaning the
190    /// last chunk is empty).
191    #[inline]
192    pub fn last_chunk(&self) -> (&[u8; N], u64) {
193        self.bitmap.last_chunk()
194    }
195
196    /// Returns the bitmap chunk containing the specified bit.
197    ///
198    /// # Warning
199    ///
200    /// Panics if the bit doesn't exist or has been pruned.
201    #[inline]
202    pub fn get_chunk_containing(&self, bit: u64) -> &[u8; N] {
203        self.bitmap.get_chunk_containing(bit)
204    }
205
206    /// Get the value of a bit.
207    ///
208    /// # Warning
209    ///
210    /// Panics if the bit doesn't exist or has been pruned.
211    #[inline]
212    pub fn get_bit(&self, bit: u64) -> bool {
213        self.bitmap.get_bit(bit)
214    }
215
216    /// Get the value of a bit from its chunk.
217    /// `bit` is an index into the entire bitmap, not just the chunk.
218    #[inline]
219    pub const fn get_bit_from_chunk(chunk: &[u8; N], bit: u64) -> bool {
220        PrunableBitMap::<N>::get_bit_from_chunk(chunk, bit)
221    }
222
223    /// Verify whether `proof` proves that the `chunk` containing the given bit belongs to the
224    /// bitmap corresponding to `root`.
225    pub fn verify_bit_inclusion(
226        hasher: &mut impl MmrHasher<Digest = D>,
227        proof: &Proof<D>,
228        chunk: &[u8; N],
229        bit: u64,
230        root: &D,
231    ) -> bool {
232        let bit_len = *proof.leaves;
233        if bit >= bit_len {
234            debug!(bit_len, bit, "tried to verify non-existent bit");
235            return false;
236        }
237
238        // The chunk index should always be < MAX_LOCATION so we can use new_unchecked.
239        let chunked_leaves =
240            Location::new_unchecked(PrunableBitMap::<N>::unpruned_chunk(bit_len) as u64);
241        let mut mmr_proof = Proof {
242            leaves: chunked_leaves,
243            digests: proof.digests.clone(),
244        };
245
246        let loc = Location::new_unchecked(PrunableBitMap::<N>::unpruned_chunk(bit) as u64);
247        if bit_len.is_multiple_of(Self::CHUNK_SIZE_BITS) {
248            return mmr_proof.verify_element_inclusion(hasher, chunk, loc, root);
249        }
250
251        if proof.digests.is_empty() {
252            debug!("proof has no digests");
253            return false;
254        }
255        let last_digest = mmr_proof.digests.pop().unwrap();
256
257        if chunked_leaves == loc {
258            // The proof is over a bit in the partial chunk. In this case the proof's only digest
259            // should be the MMR's root, otherwise it is invalid. Since we've popped off the last
260            // digest already, there should be no remaining digests.
261            if !mmr_proof.digests.is_empty() {
262                debug!(
263                    digests = mmr_proof.digests.len() + 1,
264                    "proof over partial chunk should have exactly 1 digest"
265                );
266                return false;
267            }
268            let last_chunk_digest = hasher.digest(chunk);
269            let next_bit = bit_len % Self::CHUNK_SIZE_BITS;
270            let reconstructed_root = partial_chunk_root::<_, N>(
271                hasher.inner(),
272                &last_digest,
273                next_bit,
274                &last_chunk_digest,
275            );
276            return reconstructed_root == *root;
277        };
278
279        // For the case where the proof is over a bit in a full chunk, `last_digest` contains the
280        // digest of that chunk.
281        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.inner(), &mmr_root, next_bit, &last_digest);
292
293        reconstructed_root == *root
294    }
295}
296
297impl<E: Clock + RStorage + Metrics, D: Digest, const N: usize> MerkleizedBitMap<E, D, N> {
298    /// Initialize a bitmap from the metadata in the given partition. If the partition is empty,
299    /// returns an empty bitmap. Otherwise restores the pruned state (the caller must replay
300    /// retained elements to restore its full state).
301    ///
302    /// Returns an error if the bitmap could not be restored, e.g. because of data corruption or
303    /// underlying storage error.
304    pub async fn init(
305        context: E,
306        partition: &str,
307        pool: Option<ThreadPool>,
308        hasher: &mut impl MmrHasher<Digest = D>,
309    ) -> Result<Self, Error> {
310        let metadata_cfg = MConfig {
311            partition: partition.to_string(),
312            codec_config: ((0..).into(), ()),
313        };
314        let metadata =
315            Metadata::<_, U64, Vec<u8>>::init(context.with_label("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 = CleanMmr::new(hasher);
330            let cached_root = *mmr.root();
331            return Ok(Self {
332                bitmap: PrunableBitMap::new(),
333                authenticated_len: 0,
334                mmr,
335                pool,
336                metadata,
337                state: Merkleized { root: cached_root },
338            });
339        }
340        let mmr_size = Position::try_from(Location::new_unchecked(pruned_chunks as u64))?;
341
342        let mut pinned_nodes = Vec::new();
343        for (index, pos) in nodes_to_pin(mmr_size).enumerate() {
344            let Some(bytes) = metadata.get(&U64::new(NODE_PREFIX, index as u64)) else {
345                error!(?mmr_size, ?pos, "missing pinned node");
346                return Err(MissingNode(pos));
347            };
348            let digest = D::decode(bytes.as_ref());
349            let Ok(digest) = digest else {
350                error!(?mmr_size, ?pos, "could not convert node bytes to digest");
351                return Err(MissingNode(pos));
352            };
353            pinned_nodes.push(digest);
354        }
355
356        let mmr = CleanMmr::init(
357            Config {
358                nodes: Vec::new(),
359                pruned_to_pos: mmr_size,
360                pinned_nodes,
361            },
362            hasher,
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();
368        Ok(Self {
369            bitmap,
370            authenticated_len: 0,
371            mmr,
372            pool,
373            metadata,
374            state: Merkleized { root: cached_root },
375        })
376    }
377
378    pub fn get_node(&self, position: Position) -> Option<D> {
379        self.mmr.get_node(position)
380    }
381
382    /// Write the information necessary to restore the bitmap in its fully pruned state at its last
383    /// pruning boundary. Restoring the entire bitmap state is then possible by replaying the
384    /// retained elements.
385    pub async fn write_pruned(&mut self) -> Result<(), Error> {
386        self.metadata.clear();
387
388        // Write the number of pruned chunks.
389        let key = U64::new(PRUNED_CHUNKS_PREFIX, 0);
390        self.metadata
391            .put(key, self.bitmap.pruned_chunks().to_be_bytes().to_vec());
392
393        // Write the pinned nodes.
394        // This will never panic because pruned_chunks is always less than MAX_LOCATION.
395        let mmr_size =
396            Position::try_from(Location::new_unchecked(self.bitmap.pruned_chunks() as u64))?;
397        for (i, digest) in nodes_to_pin(mmr_size).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.sync().await.map_err(MetadataError)
404    }
405
406    /// Destroy the bitmap metadata from disk.
407    pub async fn destroy(self) -> Result<(), Error> {
408        self.metadata.destroy().await.map_err(MetadataError)
409    }
410
411    /// Prune all complete chunks before the chunk containing the given bit.
412    ///
413    /// The chunk containing `bit` and all subsequent chunks are retained. All chunks
414    /// before it are pruned from the bitmap and the underlying MMR.
415    ///
416    /// If `bit` equals the bitmap length, this prunes all complete chunks while retaining
417    /// the empty trailing chunk, preparing the bitmap for appending new data.
418    pub fn prune_to_bit(&mut self, bit: u64) -> Result<(), Error> {
419        let chunk = PrunableBitMap::<N>::unpruned_chunk(bit);
420        if chunk < self.bitmap.pruned_chunks() {
421            return Ok(());
422        }
423
424        // Prune inner bitmap
425        self.bitmap.prune_to_bit(bit);
426
427        // Update authenticated length
428        self.authenticated_len = self.complete_chunks();
429
430        // This will never panic because chunk is always less than MAX_LOCATION.
431        let mmr_pos = Position::try_from(Location::new_unchecked(chunk as u64)).unwrap();
432        self.mmr.prune_to_pos(mmr_pos);
433        Ok(())
434    }
435
436    /// Return the cached root digest against which inclusion proofs can be verified.
437    ///
438    /// # Format
439    ///
440    /// The root digest is simply that of the underlying MMR whenever the bit count falls on a chunk
441    /// boundary. Otherwise, the root is computed as follows in order to capture the bits that are
442    /// not yet part of the MMR:
443    ///
444    /// hash(mmr_root || next_bit as u64 be_bytes || last_chunk_digest)
445    ///
446    /// The root is computed during merkleization and cached, so this method is cheap to call.
447    pub const fn root(&self) -> D {
448        self.state.root
449    }
450
451    /// Return an inclusion proof for the specified bit, along with the chunk of the bitmap
452    /// containing that bit. The proof can be used to prove any bit in the chunk.
453    ///
454    /// The bitmap proof stores the number of bits in the bitmap within the `size` field of the
455    /// proof instead of MMR size since the underlying MMR's size does not reflect the number of
456    /// bits in any partial chunk. The underlying MMR size can be derived from the number of
457    /// bits as `leaf_num_to_pos(proof.size / BitMap<_, N>::CHUNK_SIZE_BITS)`.
458    ///
459    /// # Errors
460    ///
461    /// Returns [Error::BitOutOfBounds] if `bit` is out of bounds.
462    pub async fn proof(
463        &self,
464        hasher: &mut impl MmrHasher<Digest = D>,
465        bit: u64,
466    ) -> Result<(Proof<D>, [u8; N]), Error> {
467        if bit >= self.len() {
468            return Err(Error::BitOutOfBounds(bit, self.len()));
469        }
470
471        let chunk = *self.get_chunk_containing(bit);
472        let chunk_loc = Location::from(PrunableBitMap::<N>::unpruned_chunk(bit));
473        let (last_chunk, next_bit) = self.bitmap.last_chunk();
474
475        if chunk_loc == self.mmr.leaves() {
476            assert!(next_bit > 0);
477            // Proof is over a bit in the partial chunk. In this case only a single digest is
478            // required in the proof: the mmr's root.
479            return Ok((
480                Proof {
481                    leaves: Location::new_unchecked(self.len()),
482                    digests: vec![*self.mmr.root()],
483                },
484                chunk,
485            ));
486        }
487
488        let range = chunk_loc..chunk_loc + 1;
489        let mut proof = verification::range_proof(&self.mmr, range).await?;
490        proof.leaves = Location::new_unchecked(self.len());
491        if next_bit == Self::CHUNK_SIZE_BITS {
492            // Bitmap is chunk aligned.
493            return Ok((proof, chunk));
494        }
495
496        // Since the bitmap wasn't chunk aligned, we'll need to include the digest of the last chunk
497        // in the proof to be able to re-derive the root.
498        let last_chunk_digest = hasher.digest(last_chunk);
499        proof.digests.push(last_chunk_digest);
500
501        Ok((proof, chunk))
502    }
503
504    /// Convert this merkleized bitmap into an unmerkleized bitmap without making any changes to it.
505    pub fn into_dirty(self) -> UnmerkleizedBitMap<E, D, N> {
506        UnmerkleizedBitMap {
507            bitmap: self.bitmap,
508            authenticated_len: self.authenticated_len,
509            mmr: self.mmr.into_dirty(),
510            pool: self.pool,
511            state: Unmerkleized {
512                dirty_chunks: HashSet::new(),
513            },
514            metadata: self.metadata,
515        }
516    }
517}
518
519impl<E: Clock + RStorage + Metrics, D: Digest, const N: usize> UnmerkleizedBitMap<E, D, N> {
520    /// Add a single bit to the end of the bitmap.
521    ///
522    /// # Warning
523    ///
524    /// The update will not affect the root until `merkleize` is called.
525    pub fn push(&mut self, bit: bool) {
526        self.bitmap.push(bit);
527    }
528
529    /// Set the value of the given bit.
530    ///
531    /// # Warning
532    ///
533    /// The update will not impact the root until `merkleize` is called.
534    pub fn set_bit(&mut self, bit: u64, value: bool) {
535        // Apply the change to the inner bitmap
536        self.bitmap.set_bit(bit, value);
537
538        // If the updated chunk is already in the MMR, mark it as dirty.
539        let chunk = self.bitmap.pruned_chunk(bit);
540        if chunk < self.authenticated_len {
541            self.state.dirty_chunks.insert(chunk);
542        }
543    }
544
545    /// The chunks that have been modified or added since the last call to `merkleize`.
546    pub fn dirty_chunks(&self) -> Vec<Location> {
547        let pruned_chunks = self.bitmap.pruned_chunks();
548        let mut chunks: Vec<Location> = self
549            .state
550            .dirty_chunks
551            .iter()
552            .map(|&chunk| Location::new_unchecked((chunk + pruned_chunks) as u64))
553            .collect();
554
555        // Include complete chunks that haven't been authenticated yet
556        for i in self.authenticated_len..self.complete_chunks() {
557            chunks.push(Location::new_unchecked((i + pruned_chunks) as u64));
558        }
559
560        chunks
561    }
562
563    /// Merkleize all updates not yet reflected in the bitmap's root.
564    pub async fn merkleize(
565        mut self,
566        hasher: &mut impl MmrHasher<Digest = D>,
567    ) -> Result<MerkleizedBitMap<E, D, N>, Error> {
568        // Add newly pushed complete chunks to the MMR.
569        let start = self.authenticated_len;
570        let end = self.complete_chunks();
571        for i in start..end {
572            self.mmr.add(hasher, self.bitmap.get_chunk(i));
573        }
574        self.authenticated_len = end;
575
576        // Inform the MMR of modified chunks.
577        let pruned_chunks = self.bitmap.pruned_chunks();
578        let updates = self
579            .state
580            .dirty_chunks
581            .iter()
582            .map(|chunk| {
583                let loc = Location::new_unchecked((*chunk + pruned_chunks) as u64);
584                (loc, self.bitmap.get_chunk(*chunk))
585            })
586            .collect::<Vec<_>>();
587        self.mmr
588            .update_leaf_batched(hasher, self.pool.clone(), &updates)?;
589        let mmr = self.mmr.merkleize(hasher, self.pool.clone());
590
591        // Compute the bitmap root
592        let mmr_root = *mmr.root();
593        let cached_root = if self.bitmap.is_chunk_aligned() {
594            mmr_root
595        } else {
596            let (last_chunk, next_bit) = self.bitmap.last_chunk();
597            let last_chunk_digest = hasher.digest(last_chunk);
598            partial_chunk_root::<_, N>(hasher.inner(), &mmr_root, next_bit, &last_chunk_digest)
599        };
600
601        Ok(MerkleizedBitMap {
602            bitmap: self.bitmap,
603            authenticated_len: self.authenticated_len,
604            mmr,
605            pool: self.pool,
606            metadata: self.metadata,
607            state: Merkleized { root: cached_root },
608        })
609    }
610}
611
612impl<E: Clock + RStorage + Metrics, D: Digest, const N: usize> Storage<D>
613    for MerkleizedBitMap<E, D, N>
614{
615    fn size(&self) -> Position {
616        self.size()
617    }
618
619    async fn get_node(&self, position: Position) -> Result<Option<D>, Error> {
620        Ok(self.get_node(position))
621    }
622}
623
624#[cfg(test)]
625mod tests {
626    use super::*;
627    use crate::mmr::StandardHasher;
628    use commonware_codec::FixedSize;
629    use commonware_cryptography::{sha256, Hasher, Sha256};
630    use commonware_macros::test_traced;
631    use commonware_runtime::{deterministic, Metrics, Runner as _};
632
633    const SHA256_SIZE: usize = sha256::Digest::SIZE;
634
635    type TestContext = deterministic::Context;
636    type TestMerkleizedBitMap<const N: usize> = MerkleizedBitMap<TestContext, sha256::Digest, N>;
637
638    impl<E: Clock + RStorage + Metrics, D: Digest, const N: usize, S: State<D>> BitMap<E, D, N, S> {
639        /// Convert a bit into the position of the Merkle tree leaf it belongs to.
640        pub(crate) fn leaf_pos(bit: u64) -> Position {
641            let chunk = PrunableBitMap::<N>::unpruned_chunk(bit);
642            let chunk = Location::new_unchecked(chunk as u64);
643            Position::try_from(chunk).expect("chunk should never overflow MAX_LOCATION")
644        }
645    }
646
647    impl<E: Clock + RStorage + Metrics, D: Digest, const N: usize> UnmerkleizedBitMap<E, D, N> {
648        // Add a byte's worth of bits to the bitmap.
649        //
650        // # Warning
651        //
652        // - The update will not impact the root until `merkleize` is called.
653        //
654        // - Assumes self.next_bit is currently byte aligned, and panics otherwise.
655        fn push_byte(&mut self, byte: u8) {
656            self.bitmap.push_byte(byte);
657        }
658
659        /// Add a chunk of bits to the bitmap.
660        ///
661        /// # Warning
662        ///
663        /// - The update will not impact the root until `merkleize` is called.
664        ///
665        /// - Panics if self.next_bit is not chunk aligned.
666        fn push_chunk(&mut self, chunk: &[u8; N]) {
667            self.bitmap.push_chunk(chunk);
668        }
669    }
670
671    fn test_chunk<const N: usize>(s: &[u8]) -> [u8; N] {
672        assert_eq!(N % 32, 0);
673        let mut vec: Vec<u8> = Vec::new();
674        for _ in 0..N / 32 {
675            vec.extend(Sha256::hash(s).iter());
676        }
677
678        vec.try_into().unwrap()
679    }
680
681    #[test_traced]
682    fn test_bitmap_verify_empty_proof() {
683        let executor = deterministic::Runner::default();
684        executor.start(|_context| async move {
685            let mut hasher = StandardHasher::<Sha256>::new();
686            let proof = Proof {
687                leaves: Location::new_unchecked(100),
688                digests: Vec::new(),
689            };
690            assert!(
691                !TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
692                    &mut hasher,
693                    &proof,
694                    &[0u8; SHA256_SIZE],
695                    0,
696                    &Sha256::fill(0x00),
697                ),
698                "proof without digests shouldn't verify or panic"
699            );
700        });
701    }
702
703    #[test_traced]
704    fn test_bitmap_empty_then_one() {
705        let executor = deterministic::Runner::default();
706        executor.start(|context| async move {
707            let mut hasher = StandardHasher::<Sha256>::new();
708            let mut bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
709                TestMerkleizedBitMap::init(context.with_label("bitmap"), "test", None, &mut hasher)
710                    .await
711                    .unwrap();
712            assert_eq!(bitmap.len(), 0);
713            assert_eq!(bitmap.bitmap.pruned_chunks(), 0);
714            bitmap.prune_to_bit(0).unwrap();
715            assert_eq!(bitmap.bitmap.pruned_chunks(), 0);
716
717            // Add a single bit
718            let root = bitmap.root();
719            let mut dirty = bitmap.into_dirty();
720            dirty.push(true);
721            bitmap = dirty.merkleize(&mut hasher).await.unwrap();
722            // Root should change
723            let new_root = bitmap.root();
724            assert_ne!(root, new_root);
725            let root = new_root;
726            bitmap.prune_to_bit(1).unwrap();
727            assert_eq!(bitmap.len(), 1);
728            assert_ne!(bitmap.last_chunk().0, &[0u8; SHA256_SIZE]);
729            assert_eq!(bitmap.last_chunk().1, 1);
730            // Pruning should be a no-op since we're not beyond a chunk boundary.
731            assert_eq!(bitmap.bitmap.pruned_chunks(), 0);
732            assert_eq!(root, bitmap.root());
733
734            // Fill up a full chunk
735            let mut dirty = bitmap.into_dirty();
736            for i in 0..(TestMerkleizedBitMap::<SHA256_SIZE>::CHUNK_SIZE_BITS - 1) {
737                dirty.push(i % 2 != 0);
738            }
739            bitmap = dirty.merkleize(&mut hasher).await.unwrap();
740            assert_eq!(bitmap.len(), 256);
741            assert_ne!(root, bitmap.root());
742            let root = bitmap.root();
743
744            // Chunk should be provable.
745            let (proof, chunk) = bitmap.proof(&mut hasher, 0).await.unwrap();
746            assert!(
747                TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
748                    &mut hasher,
749                    &proof,
750                    &chunk,
751                    255,
752                    &root
753                ),
754                "failed to prove bit in only chunk"
755            );
756            // bit outside range should not verify
757            assert!(
758                !TestMerkleizedBitMap::<SHA256_SIZE>::verify_bit_inclusion(
759                    &mut hasher,
760                    &proof,
761                    &chunk,
762                    256,
763                    &root
764                ),
765                "should not be able to prove bit outside of chunk"
766            );
767
768            // Now pruning all bits should matter.
769            bitmap.prune_to_bit(256).unwrap();
770            assert_eq!(bitmap.len(), 256);
771            assert_eq!(bitmap.bitmap.pruned_chunks(), 1);
772            assert_eq!(bitmap.bitmap.pruned_bits(), 256);
773            assert_eq!(root, bitmap.root());
774
775            // Pruning to an earlier point should be a no-op.
776            bitmap.prune_to_bit(10).unwrap();
777            assert_eq!(root, bitmap.root());
778        });
779    }
780
781    #[test_traced]
782    fn test_bitmap_building() {
783        // Build the same bitmap with 2 chunks worth of bits in multiple ways and make sure they are
784        // equivalent based on their roots.
785        let executor = deterministic::Runner::default();
786        executor.start(|context| async move {
787            let test_chunk = test_chunk(b"test");
788            let mut hasher: StandardHasher<Sha256> = StandardHasher::new();
789
790            // Add each bit one at a time after the first chunk.
791            let bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
792                context.with_label("bitmap1"),
793                "test1",
794                None,
795                &mut hasher,
796            )
797            .await
798            .unwrap();
799            let mut dirty = bitmap.into_dirty();
800            dirty.push_chunk(&test_chunk);
801            for b in test_chunk {
802                for j in 0..8 {
803                    let mask = 1 << j;
804                    let bit = (b & mask) != 0;
805                    dirty.push(bit);
806                }
807            }
808            assert_eq!(dirty.len(), 256 * 2);
809
810            let bitmap = dirty.merkleize(&mut hasher).await.unwrap();
811            let root = bitmap.root();
812            let inner_root = *bitmap.mmr.root();
813            assert_eq!(root, inner_root);
814
815            {
816                // Repeat the above MMR build only using push_chunk instead, and make
817                // sure root digests match.
818                let bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
819                    context.with_label("bitmap2"),
820                    "test2",
821                    None,
822                    &mut hasher,
823                )
824                .await
825                .unwrap();
826                let mut dirty = bitmap.into_dirty();
827                dirty.push_chunk(&test_chunk);
828                dirty.push_chunk(&test_chunk);
829                let bitmap = dirty.merkleize(&mut hasher).await.unwrap();
830                let same_root = bitmap.root();
831                assert_eq!(root, same_root);
832            }
833            {
834                // Repeat build again using push_byte this time.
835                let bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
836                    context.with_label("bitmap3"),
837                    "test3",
838                    None,
839                    &mut hasher,
840                )
841                .await
842                .unwrap();
843                let mut dirty = bitmap.into_dirty();
844                dirty.push_chunk(&test_chunk);
845                for b in test_chunk {
846                    dirty.push_byte(b);
847                }
848                let bitmap = dirty.merkleize(&mut hasher).await.unwrap();
849                let same_root = bitmap.root();
850                assert_eq!(root, same_root);
851            }
852        });
853    }
854
855    #[test_traced]
856    #[should_panic(expected = "cannot add chunk")]
857    fn test_bitmap_build_chunked_panic() {
858        let executor = deterministic::Runner::default();
859        executor.start(|context| async move {
860            let mut hasher: StandardHasher<Sha256> = StandardHasher::new();
861            let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
862                TestMerkleizedBitMap::init(context.with_label("bitmap"), "test", None, &mut hasher)
863                    .await
864                    .unwrap();
865            let mut dirty = bitmap.into_dirty();
866            dirty.push_chunk(&test_chunk(b"test"));
867            dirty.push(true);
868            dirty.push_chunk(&test_chunk(b"panic"));
869        });
870    }
871
872    #[test_traced]
873    #[should_panic(expected = "cannot add byte")]
874    fn test_bitmap_build_byte_panic() {
875        let executor = deterministic::Runner::default();
876        executor.start(|context| async move {
877            let mut hasher: StandardHasher<Sha256> = StandardHasher::new();
878            let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
879                TestMerkleizedBitMap::init(context.with_label("bitmap"), "test", None, &mut hasher)
880                    .await
881                    .unwrap();
882            let mut dirty = bitmap.into_dirty();
883            dirty.push_chunk(&test_chunk(b"test"));
884            dirty.push(true);
885            dirty.push_byte(0x01);
886        });
887    }
888
889    #[test_traced]
890    #[should_panic(expected = "out of bounds")]
891    fn test_bitmap_get_out_of_bounds_bit_panic() {
892        let executor = deterministic::Runner::default();
893        executor.start(|context| async move {
894            let mut hasher: StandardHasher<Sha256> = StandardHasher::new();
895            let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
896                TestMerkleizedBitMap::init(context.with_label("bitmap"), "test", None, &mut hasher)
897                    .await
898                    .unwrap();
899            let mut dirty = bitmap.into_dirty();
900            dirty.push_chunk(&test_chunk(b"test"));
901            dirty.get_bit(256);
902        });
903    }
904
905    #[test_traced]
906    #[should_panic(expected = "pruned")]
907    fn test_bitmap_get_pruned_bit_panic() {
908        let executor = deterministic::Runner::default();
909        executor.start(|context| async move {
910            let mut hasher: StandardHasher<Sha256> = StandardHasher::new();
911            let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
912                TestMerkleizedBitMap::init(context.with_label("bitmap"), "test", None, &mut hasher)
913                    .await
914                    .unwrap();
915            let mut dirty = bitmap.into_dirty();
916            dirty.push_chunk(&test_chunk(b"test"));
917            dirty.push_chunk(&test_chunk(b"test2"));
918            let mut bitmap = dirty.merkleize(&mut hasher).await.unwrap();
919
920            bitmap.prune_to_bit(256).unwrap();
921            bitmap.get_bit(255);
922        });
923    }
924
925    #[test_traced]
926    fn test_bitmap_root_boundaries() {
927        let executor = deterministic::Runner::default();
928        executor.start(|context| async move {
929            // Build a starting test MMR with two chunks worth of bits.
930            let mut hasher = StandardHasher::<Sha256>::new();
931            let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
932                TestMerkleizedBitMap::init(context.with_label("bitmap"), "test", None, &mut hasher)
933                    .await
934                    .unwrap();
935            let mut dirty = bitmap.into_dirty();
936            dirty.push_chunk(&test_chunk(b"test"));
937            dirty.push_chunk(&test_chunk(b"test2"));
938            let mut bitmap = dirty.merkleize(&mut hasher).await.unwrap();
939
940            let root = bitmap.root();
941
942            // Confirm that root changes if we add a 1 bit, even though we won't fill a chunk.
943            let mut dirty = bitmap.into_dirty();
944            dirty.push(true);
945            bitmap = dirty.merkleize(&mut hasher).await.unwrap();
946            let new_root = bitmap.root();
947            assert_ne!(root, new_root);
948            assert_eq!(bitmap.mmr.size(), 3); // shouldn't include the trailing bits
949
950            // Add 0 bits to fill up entire chunk.
951            for _ in 0..(SHA256_SIZE * 8 - 1) {
952                let mut dirty = bitmap.into_dirty();
953                dirty.push(false);
954                bitmap = dirty.merkleize(&mut hasher).await.unwrap();
955                let newer_root = bitmap.root();
956                // root will change when adding 0s within the same chunk
957                assert_ne!(new_root, newer_root);
958            }
959            assert_eq!(bitmap.mmr.size(), 4); // chunk we filled should have been added to mmr
960
961            // Confirm the root changes when we add the next 0 bit since it's part of a new chunk.
962            let mut dirty = bitmap.into_dirty();
963            dirty.push(false);
964            assert_eq!(dirty.len(), 256 * 3 + 1);
965            bitmap = dirty.merkleize(&mut hasher).await.unwrap();
966            let newer_root = bitmap.root();
967            assert_ne!(new_root, newer_root);
968
969            // Confirm pruning everything doesn't affect the root.
970            bitmap.prune_to_bit(bitmap.len()).unwrap();
971            assert_eq!(bitmap.bitmap.pruned_chunks(), 3);
972            assert_eq!(bitmap.len(), 256 * 3 + 1);
973            assert_eq!(newer_root, bitmap.root());
974        });
975    }
976
977    #[test_traced]
978    fn test_bitmap_get_set_bits() {
979        let executor = deterministic::Runner::default();
980        executor.start(|context| async move {
981            // Build a test MMR with a few chunks worth of bits.
982            let mut hasher = StandardHasher::<Sha256>::new();
983            let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
984                TestMerkleizedBitMap::init(context.with_label("bitmap"), "test", None, &mut hasher)
985                    .await
986                    .unwrap();
987            let mut dirty = bitmap.into_dirty();
988            dirty.push_chunk(&test_chunk(b"test"));
989            dirty.push_chunk(&test_chunk(b"test2"));
990            dirty.push_chunk(&test_chunk(b"test3"));
991            dirty.push_chunk(&test_chunk(b"test4"));
992            // Add a few extra bits to exercise not being on a chunk or byte boundary.
993            dirty.push_byte(0xF1);
994            dirty.push(true);
995            dirty.push(false);
996            dirty.push(true);
997
998            let mut bitmap = dirty.merkleize(&mut hasher).await.unwrap();
999            let root = bitmap.root();
1000
1001            // Flip each bit and confirm the root changes, then flip it back to confirm it is safely
1002            // restored.
1003            for bit_pos in (0..bitmap.len()).rev() {
1004                let bit = bitmap.get_bit(bit_pos);
1005                let mut dirty = bitmap.into_dirty();
1006                dirty.set_bit(bit_pos, !bit);
1007                bitmap = dirty.merkleize(&mut hasher).await.unwrap();
1008                let new_root = bitmap.root();
1009                assert_ne!(root, new_root, "failed at bit {bit_pos}");
1010                // flip it back
1011                let mut dirty = bitmap.into_dirty();
1012                dirty.set_bit(bit_pos, bit);
1013                bitmap = dirty.merkleize(&mut hasher).await.unwrap();
1014                let new_root = bitmap.root();
1015                assert_eq!(root, new_root);
1016            }
1017
1018            // Repeat the test after pruning.
1019            let start_bit = (SHA256_SIZE * 8 * 2) as u64;
1020            bitmap.prune_to_bit(start_bit).unwrap();
1021            for bit_pos in (start_bit..bitmap.len()).rev() {
1022                let bit = bitmap.get_bit(bit_pos);
1023                let mut dirty = bitmap.into_dirty();
1024                dirty.set_bit(bit_pos, !bit);
1025                bitmap = dirty.merkleize(&mut hasher).await.unwrap();
1026                let new_root = bitmap.root();
1027                assert_ne!(root, new_root, "failed at bit {bit_pos}");
1028                // flip it back
1029                let mut dirty = bitmap.into_dirty();
1030                dirty.set_bit(bit_pos, bit);
1031                bitmap = dirty.merkleize(&mut hasher).await.unwrap();
1032                let new_root = bitmap.root();
1033                assert_eq!(root, new_root);
1034            }
1035        });
1036    }
1037
1038    fn flip_bit<const N: usize>(bit: u64, chunk: &[u8; N]) -> [u8; N] {
1039        let byte = PrunableBitMap::<N>::chunk_byte_offset(bit);
1040        let mask = PrunableBitMap::<N>::chunk_byte_bitmask(bit);
1041        let mut tmp = chunk.to_vec();
1042        tmp[byte] ^= mask;
1043        tmp.try_into().unwrap()
1044    }
1045
1046    #[test_traced]
1047    fn test_bitmap_mmr_proof_verification() {
1048        test_bitmap_mmr_proof_verification_n::<32>();
1049        test_bitmap_mmr_proof_verification_n::<64>();
1050    }
1051
1052    fn test_bitmap_mmr_proof_verification_n<const N: usize>() {
1053        let executor = deterministic::Runner::default();
1054        executor.start(|context| async move {
1055            // Build a bitmap with 10 chunks worth of bits.
1056            let mut hasher = StandardHasher::<Sha256>::new();
1057            let bitmap: MerkleizedBitMap<TestContext, sha256::Digest, N> =
1058                MerkleizedBitMap::init(context.with_label("bitmap"), "test", None, &mut hasher)
1059                    .await
1060                    .unwrap();
1061            let mut dirty = bitmap.into_dirty();
1062            for i in 0u32..10 {
1063                dirty.push_chunk(&test_chunk(format!("test{i}").as_bytes()));
1064            }
1065            // Add a few extra bits to exercise not being on a chunk or byte boundary.
1066            dirty.push_byte(0xA6);
1067            dirty.push(true);
1068            dirty.push(false);
1069            dirty.push(true);
1070            dirty.push(true);
1071            dirty.push(false);
1072
1073            let mut bitmap = dirty.merkleize(&mut hasher).await.unwrap();
1074            let root = bitmap.root();
1075
1076            // Make sure every bit is provable, even after pruning in intervals of 251 bits (251 is
1077            // the largest prime that is less than the size of one 32-byte chunk in bits).
1078            for prune_to_bit in (0..bitmap.len()).step_by(251) {
1079                assert_eq!(bitmap.root(), root);
1080                bitmap.prune_to_bit(prune_to_bit).unwrap();
1081                for i in prune_to_bit..bitmap.len() {
1082                    let (proof, chunk) = bitmap.proof(&mut hasher, i).await.unwrap();
1083
1084                    // Proof should verify for the original chunk containing the bit.
1085                    assert!(
1086                        MerkleizedBitMap::<TestContext, _, N>::verify_bit_inclusion(
1087                            &mut hasher,
1088                            &proof,
1089                            &chunk,
1090                            i,
1091                            &root
1092                        ),
1093                        "failed to prove bit {i}",
1094                    );
1095
1096                    // Flip the bit in the chunk and make sure the proof fails.
1097                    let corrupted = flip_bit(i, &chunk);
1098                    assert!(
1099                        !MerkleizedBitMap::<TestContext, _, N>::verify_bit_inclusion(
1100                            &mut hasher,
1101                            &proof,
1102                            &corrupted,
1103                            i,
1104                            &root
1105                        ),
1106                        "proving bit {i} after flipping should have failed",
1107                    );
1108                }
1109            }
1110        })
1111    }
1112
1113    #[test_traced]
1114    fn test_bitmap_persistence() {
1115        const PARTITION: &str = "bitmap_test";
1116        const FULL_CHUNK_COUNT: usize = 100;
1117
1118        let executor = deterministic::Runner::default();
1119        executor.start(|context| async move {
1120            let mut hasher = StandardHasher::<Sha256>::new();
1121            // Initializing from an empty partition should result in an empty bitmap.
1122            let bitmap: TestMerkleizedBitMap<SHA256_SIZE> = TestMerkleizedBitMap::init(
1123                context.with_label("initial"),
1124                PARTITION,
1125                None,
1126                &mut hasher,
1127            )
1128            .await
1129            .unwrap();
1130            assert_eq!(bitmap.len(), 0);
1131
1132            // Add a non-trivial amount of data.
1133            let mut dirty = bitmap.into_dirty();
1134            for i in 0..FULL_CHUNK_COUNT {
1135                dirty.push_chunk(&test_chunk(format!("test{i}").as_bytes()));
1136            }
1137            let mut bitmap = dirty.merkleize(&mut hasher).await.unwrap();
1138            let chunk_aligned_root = bitmap.root();
1139
1140            // Add a few extra bits beyond the last chunk boundary.
1141            let mut dirty = bitmap.into_dirty();
1142            dirty.push_byte(0xA6);
1143            dirty.push(true);
1144            dirty.push(false);
1145            dirty.push(true);
1146            bitmap = dirty.merkleize(&mut hasher).await.unwrap();
1147            let root = bitmap.root();
1148
1149            // prune 10 chunks at a time and make sure replay will restore the bitmap every time.
1150            for i in (10..=FULL_CHUNK_COUNT).step_by(10) {
1151                bitmap
1152                    .prune_to_bit(
1153                        (i * TestMerkleizedBitMap::<SHA256_SIZE>::CHUNK_SIZE_BITS as usize) as u64,
1154                    )
1155                    .unwrap();
1156                bitmap.write_pruned().await.unwrap();
1157                bitmap = TestMerkleizedBitMap::init(
1158                    context.with_label(&format!("restore_{i}")),
1159                    PARTITION,
1160                    None,
1161                    &mut hasher,
1162                )
1163                .await
1164                .unwrap();
1165                let _ = bitmap.root();
1166
1167                // Replay missing chunks.
1168                let mut dirty = bitmap.into_dirty();
1169                for j in i..FULL_CHUNK_COUNT {
1170                    dirty.push_chunk(&test_chunk(format!("test{j}").as_bytes()));
1171                }
1172                assert_eq!(dirty.bitmap.pruned_chunks(), i);
1173                assert_eq!(dirty.len(), FULL_CHUNK_COUNT as u64 * 256);
1174                bitmap = dirty.merkleize(&mut hasher).await.unwrap();
1175                assert_eq!(bitmap.root(), chunk_aligned_root);
1176
1177                // Replay missing partial chunk.
1178                let mut dirty = bitmap.into_dirty();
1179                dirty.push_byte(0xA6);
1180                dirty.push(true);
1181                dirty.push(false);
1182                dirty.push(true);
1183                bitmap = dirty.merkleize(&mut hasher).await.unwrap();
1184                assert_eq!(bitmap.root(), root);
1185            }
1186        });
1187    }
1188
1189    #[test_traced]
1190    fn test_bitmap_proof_out_of_bounds() {
1191        let executor = deterministic::Runner::default();
1192        executor.start(|context| async move {
1193            let mut hasher = StandardHasher::<Sha256>::new();
1194            let bitmap: TestMerkleizedBitMap<SHA256_SIZE> =
1195                TestMerkleizedBitMap::init(context.with_label("bitmap"), "test", None, &mut hasher)
1196                    .await
1197                    .unwrap();
1198            let mut dirty = bitmap.into_dirty();
1199            dirty.push_chunk(&test_chunk(b"test"));
1200            let bitmap = dirty.merkleize(&mut hasher).await.unwrap();
1201
1202            // Proof for bit_offset >= bit_count should fail
1203            let result = bitmap.proof(&mut hasher, 256).await;
1204            assert!(matches!(result, Err(Error::BitOutOfBounds(offset, size))
1205                    if offset == 256 && size == 256));
1206
1207            let result = bitmap.proof(&mut hasher, 1000).await;
1208            assert!(matches!(result, Err(Error::BitOutOfBounds(offset, size))
1209                    if offset == 1000 && size == 256));
1210
1211            // Valid proof should work
1212            assert!(bitmap.proof(&mut hasher, 0).await.is_ok());
1213            assert!(bitmap.proof(&mut hasher, 255).await.is_ok());
1214        });
1215    }
1216}