use std::collections::HashMap;
use std::collections::HashSet;
use std::error::Error;
use std::fmt;
use std::ops::IndexMut;
#[cfg(any(test, feature = "arbitrary-impls"))]
use arbitrary::Arbitrary;
use get_size2::GetSize;
use itertools::Itertools;
use serde::Deserialize;
use serde::Serialize;
use tasm_lib::prelude::Tip5;
use tasm_lib::structure::tasm_object::TasmObject;
use tasm_lib::twenty_first::math::bfield_codec::BFieldCodec;
use tasm_lib::twenty_first::tip5::digest::Digest;
use tasm_lib::twenty_first::util_types::mmr;
use tasm_lib::twenty_first::util_types::mmr::mmr_accumulator::MmrAccumulator;
use tasm_lib::twenty_first::util_types::mmr::mmr_membership_proof::MmrMembershipProof;
use tasm_lib::twenty_first::util_types::mmr::mmr_trait::LeafMutation;
use tasm_lib::twenty_first::util_types::mmr::mmr_trait::Mmr;
use super::addition_record::AdditionRecord;
use super::commit;
use super::mutator_set_accumulator::MutatorSetAccumulator;
use super::removal_record::absolute_index_set::AbsoluteIndexSet;
use super::removal_record::chunk_dictionary::ChunkDictionary;
use super::removal_record::RemovalRecord;
use super::shared::get_batch_mutation_argument_for_removal_record;
use super::shared::prepare_authenticated_batch_modification_for_removal_record_reversion;
use super::shared::BATCH_SIZE;
use super::shared::CHUNK_SIZE;
impl Error for MembershipProofError {}
impl fmt::Display for MembershipProofError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum MembershipProofError {
AlreadyExistingChunk(u64),
MissingChunkOnUpdateFromAdd(u64),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, GetSize, BFieldCodec, TasmObject)]
#[cfg_attr(any(test, feature = "arbitrary-impls"), derive(Arbitrary))]
pub struct MsMembershipProof {
pub sender_randomness: Digest,
pub receiver_preimage: Digest,
pub auth_path_aocl: MmrMembershipProof,
pub aocl_leaf_index: u64,
pub target_chunks: ChunkDictionary,
}
impl MsMembershipProof {
pub fn addition_record(&self, item: Digest) -> AdditionRecord {
commit(item, self.sender_randomness, self.receiver_preimage.hash())
}
pub fn compute_indices(&self, item: Digest) -> AbsoluteIndexSet {
AbsoluteIndexSet::compute(
item,
self.sender_randomness,
self.receiver_preimage,
self.aocl_leaf_index,
)
}
pub fn batch_update_from_addition(
membership_proofs: &mut [&mut Self],
own_items: &[Digest],
mutator_set: &MutatorSetAccumulator,
addition_record: &AdditionRecord,
) -> Result<Vec<usize>, Box<dyn Error>> {
assert!(
membership_proofs
.iter()
.all(|mp| mp.aocl_leaf_index < mutator_set.aocl.num_leafs()),
"No AOCL data index can point outside of provided mutator set. aocl leaf count: {}; mp leaf indices: {}",
mutator_set.aocl.num_leafs(),
membership_proofs.iter().map(|x| x.aocl_leaf_index.to_string()).join(",")
);
assert_eq!(
membership_proofs.len(),
own_items.len(),
"Function must be called with same number of membership proofs and items. Got {} items and {} membership proofs", own_items.len(), membership_proofs.len()
);
debug_assert!(membership_proofs
.iter()
.all(|msmp| msmp
.target_chunks
.iter()
.all(|(chunk_index, (mmr_mp, chunk))| mmr_mp.verify(
*chunk_index,
Tip5::hash(chunk),
&mutator_set.swbf_inactive.peaks(),
mutator_set.swbf_inactive.num_leafs()
))));
let new_item_index = mutator_set.aocl.num_leafs();
let leaf_indices = membership_proofs
.iter()
.map(|msmp| msmp.aocl_leaf_index)
.collect_vec();
let indices_for_mps_updated_from_append = MmrMembershipProof::batch_update_from_append(
&mut membership_proofs
.iter_mut()
.map(|msmp| &mut msmp.auth_path_aocl)
.collect::<Vec<_>>(),
&leaf_indices,
new_item_index,
addition_record.canonical_commitment,
&mutator_set.aocl.peaks(),
);
if !MutatorSetAccumulator::window_slides(new_item_index) {
return Ok(indices_for_mps_updated_from_append);
}
let new_item_index2 = mutator_set.aocl.num_leafs();
let next_batch_index = new_item_index2 / u64::from(BATCH_SIZE);
let current_batch_index = next_batch_index - 1;
assert_eq!(
current_batch_index,
mutator_set.swbf_inactive.num_leafs(),
"Number of SWBF MMR leafs must match current batch index"
);
let new_chunk = mutator_set.swbf_active.slid_chunk();
let new_chunk_digest: Digest = Tip5::hash(&new_chunk);
let mut updated_mmra: MmrAccumulator = mutator_set.swbf_inactive.to_accumulator();
let new_chunk_auth_path: MmrMembershipProof = updated_mmra.append(new_chunk_digest);
let mut chunk_index_to_mp_index: HashMap<u64, Vec<usize>> = HashMap::new();
membership_proofs
.iter()
.zip(own_items.iter())
.enumerate()
.for_each(|(i, (mp, &item))| {
let absolute_indices = AbsoluteIndexSet::compute(
item,
mp.sender_randomness,
mp.receiver_preimage,
mp.aocl_leaf_index,
);
for chunk_index in absolute_indices
.to_array()
.iter()
.map(|x| (*x / u128::from(CHUNK_SIZE)) as u64)
.unique()
{
chunk_index_to_mp_index
.entry(chunk_index)
.or_default()
.push(i)
}
});
let indices_for_mps_with_new_chunk_dictionary_entry: Vec<usize> =
match chunk_index_to_mp_index.get(¤t_batch_index) {
Some(vals) => vals.clone(),
None => vec![],
};
let mut mps_for_batch_append: HashSet<usize> = HashSet::new();
for (chunk_index, mp_indices) in chunk_index_to_mp_index {
if chunk_index < current_batch_index {
for mp_index in mp_indices {
mps_for_batch_append.insert(mp_index);
}
}
}
for i in &indices_for_mps_with_new_chunk_dictionary_entry {
membership_proofs.index_mut(*i).target_chunks.insert(
current_batch_index,
(new_chunk_auth_path.clone(), new_chunk.clone()),
);
}
let mut mmr_membership_proofs_for_append: Vec<
&mut mmr::mmr_membership_proof::MmrMembershipProof,
> = vec![];
let mut mmr_mp_index_to_ms_mp_index = vec![];
let mut mmr_membership_indices = vec![];
for (i, mp) in membership_proofs.iter_mut().enumerate() {
if mps_for_batch_append.contains(&i) {
for (chunk_index, (mmr_mp, _chunk)) in mp.target_chunks.iter_mut() {
if *chunk_index != current_batch_index {
mmr_membership_proofs_for_append.push(mmr_mp);
mmr_membership_indices.push(*chunk_index);
mmr_mp_index_to_ms_mp_index.push(i as u64);
}
}
}
}
let indices_for_modified_paths =
mmr::mmr_membership_proof::MmrMembershipProof::batch_update_from_append(
&mut mmr_membership_proofs_for_append,
&mmr_membership_indices,
mutator_set.swbf_inactive.num_leafs(),
new_chunk_digest,
&mutator_set.swbf_inactive.peaks(),
);
let mut indices_for_mps_with_updated_swbf_auth_paths = vec![];
for j in indices_for_modified_paths {
indices_for_mps_with_updated_swbf_auth_paths
.push(mmr_mp_index_to_ms_mp_index[j] as usize);
}
let mut all_mutated_mp_indices = [
indices_for_mps_updated_from_append,
indices_for_mps_with_updated_swbf_auth_paths,
indices_for_mps_with_new_chunk_dictionary_entry,
]
.concat();
all_mutated_mp_indices.sort_unstable();
all_mutated_mp_indices.dedup();
Ok(all_mutated_mp_indices)
}
pub fn update_from_addition(
&mut self,
own_item: Digest,
mutator_set: &MutatorSetAccumulator,
addition_record: &AdditionRecord,
) -> Result<bool, Box<dyn Error>> {
assert!(self.aocl_leaf_index < mutator_set.aocl.num_leafs());
let new_item_aocl_index = mutator_set.aocl.num_leafs();
let aocl_mp_updated = self.auth_path_aocl.update_from_append(
self.aocl_leaf_index,
mutator_set.aocl.num_leafs(),
addition_record.canonical_commitment,
&mutator_set.aocl.peaks(),
);
if !MutatorSetAccumulator::window_slides(new_item_aocl_index) {
return Ok(aocl_mp_updated);
}
let new_chunk = mutator_set.swbf_active.slid_chunk();
let new_chunk_digest: Digest = Tip5::hash(&new_chunk);
let all_indices = AbsoluteIndexSet::compute(
own_item,
self.sender_randomness,
self.receiver_preimage,
self.aocl_leaf_index,
)
.to_array();
let chunk_indices_set: HashSet<u64> = all_indices
.map(|bi| (bi / u128::from(CHUNK_SIZE)) as u64)
.into_iter()
.collect::<HashSet<u64>>();
let mut swbfi_mmra: MmrAccumulator = mutator_set.swbf_inactive.to_accumulator();
let new_leaf_index = swbfi_mmra.num_leafs();
let new_auth_path: mmr::mmr_membership_proof::MmrMembershipProof =
swbfi_mmra.append(new_chunk_digest);
let mut swbf_chunk_dictionary_updated = false;
let batch_index = new_item_aocl_index / u64::from(BATCH_SIZE);
let old_window_start_batch_index = batch_index - 1;
assert_eq!(
new_leaf_index, old_window_start_batch_index,
"corrupt mutator set accumulator"
);
'outer: for chunk_index in chunk_indices_set {
if chunk_index < old_window_start_batch_index {
let mp = match self.target_chunks.get_mut(&chunk_index) {
None => {
return Err(Box::new(MembershipProofError::MissingChunkOnUpdateFromAdd(
chunk_index,
)))
}
Some((m, _chnk)) => m,
};
let swbf_chunk_dict_updated_local: bool = mp.update_from_append(
chunk_index,
mutator_set.swbf_inactive.num_leafs(),
new_chunk_digest,
&mutator_set.swbf_inactive.peaks(),
);
swbf_chunk_dictionary_updated =
swbf_chunk_dictionary_updated || swbf_chunk_dict_updated_local;
continue 'outer;
}
if chunk_index == new_leaf_index {
if self.target_chunks.contains_key(&chunk_index) {
return Err(Box::new(MembershipProofError::AlreadyExistingChunk(
chunk_index,
)));
}
self.target_chunks
.insert(chunk_index, (new_auth_path.clone(), new_chunk.clone()));
swbf_chunk_dictionary_updated = true;
}
}
Ok(swbf_chunk_dictionary_updated || aocl_mp_updated)
}
pub fn revert_update_from_batch_addition(
&mut self,
previous_mutator_set: &MutatorSetAccumulator,
) {
let previous_leaf_count = previous_mutator_set.aocl.num_leafs();
assert!(
previous_leaf_count > self.aocl_leaf_index,
"Cannot revert a membership proof for an item to back its state before the item was added to the mutator set."
);
let aocl_discrepancies = self.aocl_leaf_index ^ previous_leaf_count;
let aocl_mt_height = u128::from(aocl_discrepancies).ilog2();
while self.auth_path_aocl.authentication_path.len() > aocl_mt_height as usize {
self.auth_path_aocl.authentication_path.pop();
}
let swbfi_leaf_count = previous_mutator_set.swbf_inactive.num_leafs();
self.target_chunks.retain(|(k, _v)| *k < swbfi_leaf_count);
for (k, (mp, _chnk)) in self.target_chunks.iter_mut() {
let chunk_discrepancies = swbfi_leaf_count ^ *k;
let chunk_mt_height = u128::from(chunk_discrepancies).ilog2();
while mp.authentication_path.len() > chunk_mt_height as usize {
mp.authentication_path.pop();
}
}
}
pub fn batch_update_from_remove(
membership_proofs: &mut [&mut Self],
removal_record: &RemovalRecord,
) -> Result<Vec<usize>, Box<dyn Error>> {
let mut chunk_dictionaries: Vec<&mut ChunkDictionary> = membership_proofs
.iter_mut()
.map(|mp| &mut mp.target_chunks)
.collect();
let (mutated_chunks_by_mp_indices, mutation_argument) =
get_batch_mutation_argument_for_removal_record(removal_record, &mut chunk_dictionaries);
let mut own_mmr_mps: Vec<&mut mmr::mmr_membership_proof::MmrMembershipProof> = vec![];
let mut leaf_indices = vec![];
let mut mmr_mp_index_to_input_index: Vec<usize> = vec![];
for (i, chunk_dict) in chunk_dictionaries.iter_mut().enumerate() {
for (chunk_index, (mp, _)) in chunk_dict.iter_mut() {
own_mmr_mps.push(mp);
leaf_indices.push(*chunk_index);
mmr_mp_index_to_input_index.push(i);
}
}
let mutated_mmr_mps =
mmr::mmr_membership_proof::MmrMembershipProof::batch_update_from_batch_leaf_mutation(
&mut own_mmr_mps,
&leaf_indices,
mutation_argument
.iter()
.map(|(i, p, l)| LeafMutation::new(*i, *l, p.clone()))
.collect_vec(),
);
let mut ret: Vec<usize> = mutated_chunks_by_mp_indices.into_iter().collect();
for index in mutated_mmr_mps {
ret.push(mmr_mp_index_to_input_index[index]);
}
ret.sort_unstable();
ret.dedup();
Ok(ret)
}
pub fn update_from_remove(&mut self, removal_record: &RemovalRecord) -> bool {
let mut chunk_dictionaries = vec![&mut self.target_chunks];
let (mutated_chunk_dictionary_index, mutation_argument) =
get_batch_mutation_argument_for_removal_record(removal_record, &mut chunk_dictionaries);
let chunk_mmr_lis = self
.target_chunks
.iter()
.map(|(chunk_index, (_, _))| *chunk_index)
.collect_vec();
let mut chunk_mmr_mps: Vec<&mut mmr::mmr_membership_proof::MmrMembershipProof> = self
.target_chunks
.iter_mut()
.map(|(_, (mmr_mp, _))| mmr_mp)
.collect();
let mutated_mmr_mp_indices: Vec<usize> =
mmr::mmr_membership_proof::MmrMembershipProof::batch_update_from_batch_leaf_mutation(
&mut chunk_mmr_mps,
&chunk_mmr_lis,
mutation_argument
.iter()
.map(|(i, p, l)| LeafMutation::new(*i, *l, p.clone()))
.collect_vec(),
);
!mutated_mmr_mp_indices.is_empty() || !mutated_chunk_dictionary_index.is_empty()
}
pub fn revert_update_from_remove(&mut self, removal_record: &RemovalRecord) -> bool {
let mut chunk_dictionaries = vec![&mut self.target_chunks];
let (mutated_chunk_dictionary_index, batch_membership) =
prepare_authenticated_batch_modification_for_removal_record_reversion(
removal_record,
&mut chunk_dictionaries,
);
let chunk_mmr_lis = self
.target_chunks
.iter()
.map(|(leaf_index, (_, _))| *leaf_index)
.collect_vec();
let mut chunk_mmr_mps: Vec<&mut mmr::mmr_membership_proof::MmrMembershipProof> = self
.target_chunks
.iter_mut()
.map(|(_, (mmr_mp, _))| mmr_mp)
.collect();
let mutated_mmr_mp_indices: Vec<usize> =
mmr::mmr_membership_proof::MmrMembershipProof::batch_update_from_batch_leaf_mutation(
&mut chunk_mmr_mps,
&chunk_mmr_lis,
batch_membership
.iter()
.map(|(i, p, l)| LeafMutation::new(*i, *l, p.clone()))
.collect_vec(),
);
!mutated_mmr_mp_indices.is_empty() || !mutated_chunk_dictionary_index.is_empty()
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
pub mod tests {
use itertools::Either;
use itertools::Itertools;
use macro_rules_attr::apply;
use proptest::collection;
use proptest::prelude::any;
use proptest::prelude::*;
use proptest_arbitrary_interop::arb;
use rand::random;
use rand::rngs::StdRng;
use rand::seq::IndexedRandom;
use rand::Rng;
use rand::RngCore;
use rand::SeedableRng;
use tasm_lib::twenty_first::math::other::random_elements;
use tasm_lib::twenty_first::util_types::mmr::mmr_membership_proof::MmrMembershipProof;
use super::*;
use crate::active_window::ActiveWindow;
use crate::commit;
use crate::removal_record::chunk::Chunk;
use crate::test_shared::empty_rusty_mutator_set;
use crate::test_shared::mock_item_and_randomnesses;
use crate::test_utils::shared_tokio_runtime;
const N: usize = 100;
#[test]
fn mp_equality_test() {
let mut rng = rand::rng();
let (_item, sender_randomness, receiver_preimage) = mock_item_and_randomnesses();
let base_mp = MsMembershipProof {
sender_randomness,
receiver_preimage,
auth_path_aocl: MmrMembershipProof::new(vec![]),
aocl_leaf_index: 0,
target_chunks: ChunkDictionary::default(),
};
let mp_with_different_leaf_index = MsMembershipProof {
sender_randomness,
receiver_preimage,
auth_path_aocl: MmrMembershipProof::new(vec![]),
aocl_leaf_index: 100073,
target_chunks: ChunkDictionary::default(),
};
let mp_with_different_sender_randomness = MsMembershipProof {
sender_randomness: rng.random(),
receiver_preimage,
auth_path_aocl: MmrMembershipProof::new(vec![]),
aocl_leaf_index: 0,
target_chunks: ChunkDictionary::default(),
};
let mp_with_different_receiver_preimage = MsMembershipProof {
receiver_preimage: rng.random(),
sender_randomness,
auth_path_aocl: MmrMembershipProof::new(vec![]),
aocl_leaf_index: 0,
target_chunks: ChunkDictionary::default(),
};
assert_ne!(mp_with_different_leaf_index, base_mp);
assert_ne!(mp_with_different_sender_randomness, base_mp);
assert_ne!(mp_with_different_receiver_preimage, base_mp);
let mmr_digests = random_elements::<Digest>(7);
let mut mmra: MmrAccumulator = MmrAccumulator::new_from_leafs(mmr_digests);
let zero_chunk = Chunk::empty_chunk();
let mmr_mp = mmra.append(Tip5::hash(&zero_chunk));
assert_eq!(3, mmr_mp.authentication_path.len());
let mut mp_mutated: MsMembershipProof = base_mp.clone();
mp_mutated.target_chunks.insert(0, (mmr_mp, zero_chunk));
assert_ne!(mp_mutated, base_mp);
}
#[test]
fn serialization_test() {
let accumulator: MutatorSetAccumulator = MutatorSetAccumulator::default();
for _ in 0..10 {
let (item, sender_randomness, receiver_preimage) = mock_item_and_randomnesses();
let mp = accumulator.prove(item, sender_randomness, receiver_preimage);
let json: String = serde_json::to_string(&mp).unwrap();
let mp_again = serde_json::from_str::<MsMembershipProof>(&json).unwrap();
assert_eq!(mp_again.target_chunks, mp.target_chunks);
assert_eq!(mp_again, mp);
}
}
#[test_strategy::proptest(async = "tokio")]
async fn revert_update_from_remove_test(
#[strategy(0..N)] own_index: usize,
#[strategy(collection::vec(arb::<Digest>(), N))] mut item_vec: Vec<Digest>,
#[strategy(collection::vec(arb::<Digest>(), N))] mut sender_randomness_vec: Vec<Digest>,
#[strategy(collection::vec(arb::<Digest>(), N))] mut receiver_preimage_vec: Vec<Digest>,
#[strategy(collection::vec(any::<bool>(), N))] mut condition: Vec<bool>,
#[any] cutoff_rand: usize,
) {
let mut own_membership_proof = None;
let mut own_item = None;
let mut rms = empty_rusty_mutator_set().await;
let archival_mutator_set = rms.ams_mut();
let mut membership_proofs: Vec<(Digest, MsMembershipProof)> = vec![];
for i in 0..N {
let item: Digest = item_vec.pop().unwrap();
let sender_randomness: Digest = sender_randomness_vec.pop().unwrap();
let receiver_preimage: Digest = receiver_preimage_vec.pop().unwrap();
let addition_record = commit(item, sender_randomness, receiver_preimage.hash());
for (oi, mp) in &mut membership_proofs {
mp.update_from_addition(
*oi,
&archival_mutator_set.accumulator().await,
&addition_record,
)
.expect("Could not update membership proof from addition.");
}
let membership_proof = archival_mutator_set
.prove(item, sender_randomness, receiver_preimage)
.await;
if i == own_index {
own_membership_proof = Some(membership_proof);
own_item = Some(item);
} else {
membership_proofs.push((item, membership_proof));
if i > own_index {
own_membership_proof
.as_mut()
.unwrap()
.update_from_addition(
own_item.unwrap(),
&archival_mutator_set.accumulator().await,
&addition_record,
)
.expect("Could not update membership proof from addition record.");
}
}
archival_mutator_set.add(&addition_record).await;
}
assert!(
archival_mutator_set
.verify(own_item.unwrap(), own_membership_proof.as_ref().unwrap())
.await
);
for (itm, mp) in &membership_proofs {
assert!(archival_mutator_set.verify(*itm, mp).await);
}
let mut removal_records = vec![];
for (item, membership_proof) in membership_proofs {
if condition.pop().unwrap() {
let removal_record = archival_mutator_set.drop(item, &membership_proof).await;
removal_records.push(removal_record);
}
}
let cutoff_point = 1 + (cutoff_rand % (removal_records.len() - 1));
let mut membership_proof_snapshot = None;
for i in 0..removal_records.len() {
let (immutable_records, mutable_records) = removal_records.split_at_mut(i + 1);
let applied_removal_record = immutable_records.last().unwrap();
RemovalRecord::batch_update_from_remove(
&mut mutable_records.iter_mut().collect::<Vec<_>>(),
applied_removal_record,
);
own_membership_proof
.as_mut()
.unwrap()
.update_from_remove(applied_removal_record);
archival_mutator_set.remove(applied_removal_record).await;
if i + 1 == cutoff_point {
membership_proof_snapshot = Some(own_membership_proof.as_ref().unwrap().clone());
}
}
assert!(
archival_mutator_set
.verify(own_item.unwrap(), own_membership_proof.as_ref().unwrap())
.await
);
let mut reversions = removal_records[cutoff_point..].to_vec();
reversions.reverse();
for revert_removal_record in &reversions {
own_membership_proof
.as_mut()
.unwrap()
.revert_update_from_remove(revert_removal_record);
archival_mutator_set
.revert_remove(revert_removal_record)
.await;
}
assert!(
archival_mutator_set
.verify(own_item.unwrap(), own_membership_proof.as_ref().unwrap())
.await
);
assert_eq!(
own_membership_proof.unwrap(),
membership_proof_snapshot.unwrap()
);
}
#[apply(shared_tokio_runtime)]
async fn revert_update_single_remove_test() {
let mut rms = empty_rusty_mutator_set().await;
let ams = rms.ams_mut();
let mut mps = vec![];
let mut items = vec![];
let mut addition_records = vec![];
let ms_size = 30;
for _ in 0..ms_size {
let item: Digest = random();
let sender_randomness: Digest = random();
let receiver_preimage: Digest = random();
let addition_record = commit(item, sender_randomness, receiver_preimage.hash());
MsMembershipProof::batch_update_from_addition(
&mut mps.iter_mut().collect_vec(),
&items,
&ams.accumulator().await,
&addition_record,
)
.unwrap();
mps.push(ams.prove(item, sender_randomness, receiver_preimage).await);
items.push(item);
ams.add(&addition_record).await;
addition_records.push(addition_record);
}
for i in 0..ms_size {
assert!(ams.verify(items[i], &mps[i]).await);
}
let mut removal_records = vec![];
for i in 0..ms_size {
let removal_record = ams.drop(items[i], &mps[i]).await;
ams.remove(&removal_record).await;
MsMembershipProof::batch_update_from_remove(
&mut mps.iter_mut().collect_vec(),
&removal_record,
)
.unwrap();
removal_records.push(removal_record);
for j in 0..ms_size {
if j > i {
assert!(ams.verify(items[j], &mps[j]).await);
} else {
assert!(!ams.verify(items[j], &mps[j]).await);
}
}
}
for i in 0..ms_size {
assert!(!ams.verify(items[i], &mps[i]).await);
}
for i in (0..ms_size).rev() {
ams.revert_remove(&removal_records[i]).await;
for mp in mps.iter_mut().take(ms_size) {
mp.revert_update_from_remove(&removal_records[i]);
}
for j in 0..ms_size {
if j < i {
assert!(!ams.verify(items[j], &mps[j]).await);
} else {
assert!(ams.verify(items[j], &mps[j]).await);
}
}
}
for i in 0..ms_size {
ams.verify(items[i], &mps[i]).await;
}
}
#[apply(shared_tokio_runtime)]
async fn revert_update_single_addition_test() {
for j in 2..30 {
let mut rms = empty_rusty_mutator_set().await;
let ams = rms.ams_mut();
let mut mps = vec![];
let mut items = vec![];
let mut addition_records = vec![];
for _ in 0..j {
let item: Digest = random();
let sender_randomness: Digest = random();
let receiver_preimage: Digest = random();
let addition_record = commit(item, sender_randomness, receiver_preimage.hash());
MsMembershipProof::batch_update_from_addition(
&mut mps.iter_mut().collect_vec(),
&items,
&ams.accumulator().await,
&addition_record,
)
.unwrap();
mps.push(ams.prove(item, sender_randomness, receiver_preimage).await);
items.push(item);
ams.add(&addition_record).await;
addition_records.push(addition_record);
}
for i in (1..j).rev() {
ams.revert_add(&addition_records[i]).await;
mps[0].revert_update_from_batch_addition(&ams.accumulator().await);
assert!(
ams.verify(items[0], &mps[0]).await,
"MP should be valid after reversion"
);
if i != 1 {
mps[1].revert_update_from_batch_addition(&ams.accumulator().await);
assert!(
ams.verify(items[1], &mps[1]).await,
"MP should be valid after reversion"
);
}
}
}
}
#[apply(shared_tokio_runtime)]
async fn revert_update_from_addition_batches_test() {
let mut msa: MutatorSetAccumulator = MutatorSetAccumulator::default();
let mut rng = rand::rng();
for _ in 0..10 {
let init_size = rng.random_range(0..200);
let first_batch_size = rng.random_range(0..200);
let last_batch_size = rng.random_range(0..200);
for _ in 0..init_size {
let item: Digest = random();
let sender_randomness: Digest = random();
let receiver_preimage: Digest = random();
let addition_record = commit(item, sender_randomness, receiver_preimage.hash());
msa.add(&addition_record);
}
let own_item: Digest = random();
let own_sender_randomness: Digest = random();
let own_receiver_preimage: Digest = random();
let own_addition_record = commit(
own_item,
own_sender_randomness,
own_receiver_preimage.hash(),
);
let mut own_mp = msa.prove(own_item, own_sender_randomness, own_receiver_preimage);
msa.add(&own_addition_record);
let msa_after_own_add = msa.clone();
for _ in 0..first_batch_size {
let item: Digest = random();
let sender_randomness: Digest = random();
let receiver_preimage: Digest = random();
let addition_record = commit(item, sender_randomness, receiver_preimage.hash());
own_mp
.update_from_addition(own_item, &msa, &addition_record)
.unwrap();
msa.add(&addition_record);
assert!(
msa.verify(own_item, &own_mp),
"Own mp must be valid after update"
);
}
let msa_after_first_batch = msa.clone();
for _ in 0..last_batch_size {
let item: Digest = random();
let sender_randomness: Digest = random();
let receiver_preimage: Digest = random();
let addition_record = commit(item, sender_randomness, receiver_preimage.hash());
own_mp
.update_from_addition(own_item, &msa, &addition_record)
.unwrap();
msa.add(&addition_record);
assert!(
msa.verify(own_item, &own_mp),
"Own mp must be valid after update"
);
}
own_mp.revert_update_from_batch_addition(&msa_after_first_batch);
assert!(msa_after_first_batch.verify(own_item, &own_mp));
own_mp.revert_update_from_batch_addition(&msa_after_own_add);
assert!(msa_after_own_add.verify(own_item, &own_mp));
}
}
#[apply(shared_tokio_runtime)]
async fn revert_update_from_addition_test() {
let mut rng = rand::rng();
let n = rng.next_u32() as usize % 100 + 1;
let own_index = rng.next_u32() as usize % n;
let mut own_membership_proof = None;
let mut own_item = None;
let mut rms = empty_rusty_mutator_set().await;
let archival_mutator_set = rms.ams_mut();
let mut addition_records = vec![];
for i in 0..n {
let item: Digest = random();
let sender_randomness: Digest = random();
let receiver_preimage: Digest = random();
let addition_record = commit(item, sender_randomness, receiver_preimage.hash());
addition_records.push(addition_record);
let membership_proof = archival_mutator_set
.prove(item, sender_randomness, receiver_preimage)
.await;
match i.cmp(&own_index) {
std::cmp::Ordering::Less => {}
std::cmp::Ordering::Equal => {
own_membership_proof = Some(membership_proof);
own_item = Some(item);
}
std::cmp::Ordering::Greater => {
assert!(
archival_mutator_set
.verify(own_item.unwrap(), own_membership_proof.as_ref().unwrap())
.await
);
assert!(archival_mutator_set
.accumulator()
.await
.verify(own_item.unwrap(), own_membership_proof.as_ref().unwrap()));
own_membership_proof
.as_mut()
.unwrap()
.update_from_addition(
own_item.unwrap(),
&archival_mutator_set.accumulator().await,
&addition_record,
)
.expect("Could not update membership proof from addition record.");
}
}
let mutator_set_before = archival_mutator_set.accumulator().await;
archival_mutator_set.add(&addition_record).await;
if i > own_index {
let own_item = own_item.as_ref().unwrap().to_owned();
assert!(
archival_mutator_set
.verify(own_item, own_membership_proof.as_ref().unwrap(),)
.await
);
let mut memproof = own_membership_proof.as_ref().unwrap().clone();
assert!(archival_mutator_set.verify(own_item, &memproof,).await);
memproof.revert_update_from_batch_addition(&mutator_set_before);
assert!(mutator_set_before.verify(own_item, &memproof));
}
}
let (_petrified, revertible) = addition_records.split_at(own_index + 1);
for addition_record in revertible.iter().rev() {
archival_mutator_set.revert_add(addition_record).await;
own_membership_proof
.as_mut()
.unwrap()
.revert_update_from_batch_addition(&archival_mutator_set.accumulator().await);
assert!(
archival_mutator_set
.verify(own_item.unwrap(), own_membership_proof.as_ref().unwrap())
.await
);
}
}
#[apply(shared_tokio_runtime)]
async fn revert_updates_mixed_test() {
let mut rng_seeder = rand::rng();
let error_tuple: (usize, u32) = (
10 + rng_seeder.next_u32() as usize % 100,
rng_seeder.next_u32(),
);
let n = error_tuple.0;
let seed_integer = error_tuple.1;
let margin = n / 5;
println!("*********************** seed: {seed_integer} ***********************");
let seed = seed_integer.to_be_bytes();
let mut seed_as_bytes = [0u8; 32];
for i in 0..32 {
seed_as_bytes[i] = seed[i % 4];
}
let mut rng = StdRng::from_seed(seed_as_bytes);
let mut rms = empty_rusty_mutator_set().await;
let archival_mutator_set = rms.ams_mut();
let own_index = rng.next_u32() as usize % 10;
let mut own_item = Digest::default();
let mut track_index = 0;
let mut rates = HashMap::<String, f64>::new();
rates.insert("additions".to_owned(), 0.7);
rates.insert("removals".to_owned(), 0.95);
let mut tracked_items_and_membership_proofs: Vec<(Digest, MsMembershipProof)> = vec![];
let mut removed_items_and_membership_proofs: Vec<(Digest, MsMembershipProof, usize)> =
vec![];
let mut records: Vec<Either<AdditionRecord, RemovalRecord>> = vec![];
for i in 0..2000 {
let sample: f64 = rng.random();
if sample <= rates["additions"] || i == own_index {
println!(
"{i}. (set size {}) addition",
tracked_items_and_membership_proofs.len()
);
let item: Digest = rng.random();
let sender_randomness: Digest = rng.random();
let receiver_preimage: Digest = rng.random();
let addition_record = commit(item, sender_randomness, receiver_preimage.hash());
let membership_proof = archival_mutator_set
.prove(item, sender_randomness, receiver_preimage)
.await;
for (it, mp) in &mut tracked_items_and_membership_proofs {
mp.update_from_addition(
*it,
&archival_mutator_set.accumulator().await,
&addition_record,
)
.expect("Could not update membership proof from addition.");
}
archival_mutator_set.add(&addition_record).await;
records.push(Either::Left(addition_record));
if i == own_index {
track_index = tracked_items_and_membership_proofs.len();
own_item = item;
println!("own item index: {track_index}");
}
tracked_items_and_membership_proofs.push((item, membership_proof));
if tracked_items_and_membership_proofs.len() > n + margin && i > n {
*rates.get_mut("additions").unwrap() = 0.3;
*rates.get_mut("removals").unwrap() = 0.8;
}
}
else if sample > rates["additions"]
&& sample <= rates["removals"]
&& tracked_items_and_membership_proofs.len() > 1
{
println!(
"{i}. (set size {}) removal",
tracked_items_and_membership_proofs.len()
);
let mut index = track_index;
while index == track_index {
index = rng.next_u32() as usize % tracked_items_and_membership_proofs.len()
}
let (item, membership_proof) = tracked_items_and_membership_proofs.remove(index);
if track_index > index {
track_index -= 1;
}
let removal_record = archival_mutator_set.drop(item, &membership_proof).await;
for (_, mp) in &mut tracked_items_and_membership_proofs {
mp.update_from_remove(&removal_record);
}
assert!(
archival_mutator_set.verify(item, &membership_proof).await,
"track index: {track_index}\nitem index: {index}",
);
removed_items_and_membership_proofs.push((item, membership_proof.clone(), index));
archival_mutator_set.remove(&removal_record).await;
records.push(Either::Right(removal_record));
if tracked_items_and_membership_proofs.len() < n - margin && i > n {
*rates.get_mut("additions").unwrap() = 0.5;
*rates.get_mut("removals").unwrap() = 0.8;
}
}
else if tracked_items_and_membership_proofs.len() > 1 {
let max_reversions = tracked_items_and_membership_proofs.len() - track_index;
if max_reversions > 0 {
let num_reversions = rng.next_u32() as usize % max_reversions;
if num_reversions > 0 {
let set_size_was = tracked_items_and_membership_proofs.len();
let mut all_reversions_are_additions = true;
for j in 0..num_reversions {
if !matches!(records[records.len() - 1 - j], Either::Left(_)) {
all_reversions_are_additions = false;
}
}
if all_reversions_are_additions && num_reversions > 1 {
println!(
"{i}. (set size {}) reversion [{}]",
tracked_items_and_membership_proofs.len(),
vec!["a"; num_reversions].join("")
);
for _ in 0..num_reversions {
if let Some(Either::Left(addition_record)) = records.pop() {
archival_mutator_set.revert_add(&addition_record).await;
}
tracked_items_and_membership_proofs.pop();
}
for (_, mp) in &mut tracked_items_and_membership_proofs {
mp.revert_update_from_batch_addition(
&archival_mutator_set.accumulator().await,
);
}
}
else {
let mut records_abbreviation = "".to_string();
for _ in 0..num_reversions {
if let Some(record) = records.pop() {
match record {
Either::Left(addition_record) => {
records_abbreviation =
format!("{records_abbreviation}a");
archival_mutator_set.revert_add(&addition_record).await;
tracked_items_and_membership_proofs.pop();
for (_, mp) in &mut tracked_items_and_membership_proofs
{
mp.revert_update_from_batch_addition(
&archival_mutator_set.accumulator().await,
);
}
}
Either::Right(removal_record) => {
let mut _report_index = 0;
records_abbreviation =
format!("{records_abbreviation}r");
archival_mutator_set
.revert_remove(&removal_record)
.await;
for (_, mp) in &mut tracked_items_and_membership_proofs
{
mp.revert_update_from_remove(&removal_record);
}
match removed_items_and_membership_proofs.pop() {
Some((item, membership_proof, index)) => {
assert!(
archival_mutator_set
.verify(item, &membership_proof)
.await
);
tracked_items_and_membership_proofs
.insert(index, (item, membership_proof));
_report_index = index;
if index <= track_index {
track_index += 1;
}
}
None => {
panic!("No entries in removed_items_and_membership_proofs to pop!");
}
}
}
}
}
}
println!(
"{i}. (set size {}) reversion ({})",
set_size_was, records_abbreviation
);
}
}
}
}
if i > own_index {
assert_eq!(own_item, tracked_items_and_membership_proofs[track_index].0);
assert!(
archival_mutator_set
.verify(
own_item,
&tracked_items_and_membership_proofs[track_index].1
)
.await,
"seed: {seed_integer} / n: {n}",
);
}
}
}
proptest::proptest! {
#![proptest_config(ProptestConfig {
cases: 100, .. ProptestConfig::default()
})]
#[test]
fn test_decode_mutator_set_membership_proof(msmp in crate::strategies::msmembershipproof()) {
let encoded = msmp.encode();
let decoded: MsMembershipProof = *MsMembershipProof::decode(&encoded).unwrap();
assert_eq!(msmp, decoded);
}
}
#[test]
fn batch_updates_on_small_mmr() {
let mut rng = rand::rng();
for remove_share in [0.01, 0.1, 0.4, 0.7, 0.99, 1.0] {
let mut msa = MutatorSetAccumulator::default();
let mut msmps = vec![];
let mut items = vec![];
let mut removed = vec![];
for j in 0usize..usize::try_from(25 * BATCH_SIZE).unwrap() {
println!("{j}");
let item: Digest = rng.random();
let sender_randomness: Digest = rng.random();
let receiver_preimage: Digest = rng.random();
let msmp = msa.prove(item, sender_randomness, receiver_preimage);
let addition_record = commit(item, sender_randomness, receiver_preimage.hash());
MsMembershipProof::batch_update_from_addition(
&mut msmps.iter_mut().collect_vec(),
&items,
&msa,
&addition_record,
)
.unwrap();
msa.add(&addition_record);
msmps.push(msmp);
items.push(item);
if rng.random_bool(remove_share) {
let not_removed = (0..=j).filter(|i| !removed.contains(i)).collect_vec();
let remove = *not_removed.choose(&mut rng).unwrap();
let remove_item = items[remove];
let remove_msmp = &msmps[remove];
let removal_record = msa.drop(remove_item, remove_msmp);
MsMembershipProof::batch_update_from_remove(
&mut msmps.iter_mut().collect_vec(),
&removal_record,
)
.unwrap();
assert!(msa.can_remove(&removal_record));
msa.remove(&removal_record);
removed.push(remove);
}
}
for ((j, msmp), item) in msmps.into_iter().enumerate().zip(items) {
if removed.contains(&j) {
assert!(
!msa.verify(item, &msmp),
"index {j} must fail to verify since it was removed."
);
} else {
assert!(
msa.verify(item, &msmp),
"index {j} must verify since it was never removed."
);
}
}
}
}
#[test]
#[should_panic(expected = "corrupt mutator set accumulator")]
fn update_from_addition_fails_for_inconsistent_mutator_set_accumulator() {
let mut rng = rand::rng();
let aocl_leaf_count = 42966841942012423_u64;
let aocl_peaks = (0..aocl_leaf_count.count_ones())
.map(|_| rng.random::<Digest>())
.collect_vec();
let swbfi_leaf_count = (aocl_leaf_count / u64::from(BATCH_SIZE)) + 1;
let swbfi_peaks = (0..swbfi_leaf_count.count_ones())
.map(|_| rng.random::<Digest>())
.collect_vec();
let swbf_active = ActiveWindow::new();
let mut mutator_set_accumulator = MutatorSetAccumulator {
aocl: MmrAccumulator::init(aocl_peaks, aocl_leaf_count),
swbf_inactive: MmrAccumulator::init(swbfi_peaks, swbfi_leaf_count),
swbf_active,
};
let (own_item, sender_randomness, receiver_preimage) =
rng.random::<(Digest, Digest, Digest)>();
let own_addition_record = commit(own_item, sender_randomness, receiver_preimage.hash());
let mut msmp =
mutator_set_accumulator.prove(own_item, sender_randomness, receiver_preimage);
mutator_set_accumulator.add(&own_addition_record);
let other_addition_record = AdditionRecord::new(rng.random::<Digest>());
msmp.update_from_addition(own_item, &mutator_set_accumulator, &other_addition_record)
.expect("update from add should always work");
}
}