mc_oblivious_map/
lib.rs

1// Copyright (c) 2018-2023 The MobileCoin Foundation
2
3//! Implementation of a bucketed cuckoo hash table where the arena is an
4//! oblivious RAM
5//! A cuckoo hash table is a data structure that performs access and removal
6//! using a constant number of operations.
7
8//! The trick is that our cuckoo hash table is actually 2 orams with 2 different
9//! hash functions. The key is always hashed twice for access, read, and removal
10//! because we guarantee the element must only reside in the location of its
11//! hash in one of the two orams. This also implies that these operations are
12//! oblivious, because we always do 2 oram queries, once per each oram, and
13//! inherit obliviousness from the oram. In our case, each hash bucket is an
14//! oram bucket that can hold multiple values. See
15//! https://www.ru.is/faculty/ulfar/CuckooHash.pdf for a survey of cuckoo hash
16//! using varying numbers of hash functions and bucket size.
17
18//! Insertion requires variable time and is not usually oblivious. This is ok
19//! for use on things like fog view/ledger where nothing they are inserting is
20//! secret. It is possible to obtain obtain obliviousness for things like
21//! fog-ingest. Please see documentation in [vartime_write_extended()] and
22//! [`ObliviousHashMap::access_and_insert()`]
23
24#![no_std]
25#![deny(unsafe_code)]
26#![deny(missing_docs)]
27
28extern crate alloc;
29
30use aligned_cmov::{
31    subtle::{Choice, ConstantTimeEq, ConstantTimeGreater},
32    typenum, A64Bytes, A8Bytes, ArrayLength, AsAlignedChunks, CMov,
33};
34use alloc::vec::Vec;
35use core::{
36    hash::{BuildHasher, Hash, Hasher},
37    marker::PhantomData,
38    ops::{Add, Sub},
39};
40use generic_array::sequence::Split;
41use mc_oblivious_traits::{
42    log2_ceil, OMapCreator, ORAMCreator, ObliviousHashMap, OMAP_FOUND, OMAP_INVALID_KEY,
43    OMAP_NOT_FOUND, OMAP_OVERFLOW, ORAM,
44};
45use rand_core::{CryptoRng, RngCore};
46use typenum::{PartialDiv, Sum, U8};
47
48mod build_hasher;
49use build_hasher::SipBuildHasher;
50
51/// In this implementation, the cuckoo hashing step is permitted to repeat at
52/// most 6 times before we give up. In experiments this lead to about ~75%
53/// memory utilitzation. This will depend on a lot of factors such as how big is
54/// the block size relative to key-size + value-size, and possibly also on the
55/// capacity. In the future we might want to make this configurable.
56const MAX_EVICTION_RETRIES: usize = 6;
57
58/// A bucketed cuckoo hash table built on top of oblivious storage.
59///
60/// The Block stored by ORAM is considered as a bucket in the hashing algorithm.
61/// The bucket gets broken up into aligned chunks of size KeySize + ValueSize,
62/// so the number of items in a bucket is BlockSize / (KeySize + ValueSize)
63pub struct CuckooHashTable<KeySize, ValueSize, BlockSize, RngType, O>
64where
65    KeySize: ArrayLength<u8> + Add<ValueSize> + PartialDiv<U8> + 'static,
66    ValueSize: ArrayLength<u8> + PartialDiv<U8>,
67    BlockSize: ArrayLength<u8> + PartialDiv<U8>,
68    RngType: RngCore + CryptoRng + Send + Sync + 'static,
69    O: ORAM<BlockSize> + Send + Sync + 'static,
70    Sum<KeySize, ValueSize>: ArrayLength<u8> + Sub<KeySize, Output = ValueSize> + PartialDiv<U8>,
71{
72    /// The number of items in the table right now
73    num_items: u64,
74    /// The number of buckets in ONE of the two orams, must be a power of two
75    num_buckets: u64,
76    /// Key for the first hash function
77    hash1: SipBuildHasher,
78    /// Key for the second hash function
79    hash2: SipBuildHasher,
80    /// Oblivious storage for arena corresponding to first hash function
81    oram1: O,
82    /// Oblivious storage for arena corresponding to second hash function
83    oram2: O,
84    /// Rng used to make random eviction decisions
85    rng: RngType,
86    // phantom data
87    _key_size: PhantomData<fn() -> KeySize>,
88    _value_size: PhantomData<fn() -> ValueSize>,
89    _block_size: PhantomData<fn() -> BlockSize>,
90}
91
92impl<KeySize, ValueSize, BlockSize, RngType, O>
93    CuckooHashTable<KeySize, ValueSize, BlockSize, RngType, O>
94where
95    KeySize: ArrayLength<u8> + Add<ValueSize> + PartialDiv<U8> + 'static,
96    ValueSize: ArrayLength<u8> + PartialDiv<U8>,
97    BlockSize: ArrayLength<u8> + PartialDiv<U8>,
98    RngType: RngCore + CryptoRng + Send + Sync + 'static,
99    O: ORAM<BlockSize> + Send + Sync + 'static,
100    Sum<KeySize, ValueSize>: ArrayLength<u8> + Sub<KeySize, Output = ValueSize> + PartialDiv<U8>,
101{
102    /// Create a new hashmap
103    /// The ORAM should be default initialized or bad things will happen
104    pub fn new<OC, M>(desired_capacity: u64, stash_size: usize, mut maker: M) -> Self
105    where
106        OC: ORAMCreator<BlockSize, RngType, Output = O>,
107        M: 'static + FnMut() -> RngType,
108    {
109        assert!(Self::BUCKET_CAPACITY > 0, "Block size is insufficient to store even one item. For good performance it should be enough to store several items.");
110        // We will have two ORAM arenas, and two hash functions. num_buckets is the
111        // number of buckets that one of the arenas should have, to achieve
112        // total desired capacity across both arenas, then rounded up to a power
113        // of two.
114        let num_buckets = 1u64 << log2_ceil((desired_capacity / Self::BUCKET_CAPACITY) / 2);
115        debug_assert!(
116            num_buckets & (num_buckets - 1) == 0,
117            "num_buckets must be a power of two"
118        );
119
120        let oram1 = OC::create(num_buckets, stash_size, &mut maker);
121        debug_assert!(num_buckets <= oram1.len(), "unexpected oram capacity");
122        let oram2 = OC::create(num_buckets, stash_size, &mut maker);
123        debug_assert!(num_buckets <= oram2.len(), "unexpected oram capacity");
124        debug_assert!(
125            oram1.len() == oram2.len(),
126            "Orams didn't have the same length, not expected"
127        );
128
129        let mut rng = maker();
130        let hash1 = SipBuildHasher::from_rng(&mut rng);
131        let hash2 = SipBuildHasher::from_rng(&mut rng);
132
133        Self {
134            num_items: 0,
135            num_buckets,
136            hash1,
137            hash2,
138            oram1,
139            oram2,
140            rng,
141            _key_size: Default::default(),
142            _value_size: Default::default(),
143            _block_size: Default::default(),
144        }
145    }
146
147    fn hash_query(&self, query: &A8Bytes<KeySize>) -> [u64; 2] {
148        let result1 = {
149            let mut hasher = self.hash1.build_hasher();
150            query.as_slice().hash(&mut hasher);
151            hasher.finish() & (self.num_buckets - 1)
152        };
153
154        let result2 = {
155            let mut hasher = self.hash2.build_hasher();
156            query.as_slice().hash(&mut hasher);
157            hasher.finish() & (self.num_buckets - 1)
158        };
159
160        [result1, result2]
161    }
162
163    // Given a block (stored in ORAM), which we think of as a hash-table bucket,
164    // check if any of the entries have key matching query, and count how many are
165    // zeroes This function is constant-time
166    fn count_before_insert(query: &A8Bytes<KeySize>, block: &A64Bytes<BlockSize>) -> (Choice, u32) {
167        let mut found = Choice::from(0);
168        let mut empty_count = 0u32;
169
170        let pairs: &[A8Bytes<Sum<KeySize, ValueSize>>] = block.as_aligned_chunks();
171        for pair in pairs {
172            let (key, _): (&A8Bytes<KeySize>, &A8Bytes<ValueSize>) = pair.split();
173            found |= key.ct_eq(query);
174            empty_count += key.ct_eq(&A8Bytes::<KeySize>::default()).unwrap_u8() as u32;
175        }
176
177        (found, empty_count)
178    }
179
180    // Given a block (stored in ORAM), which we think of as a hash-table bucket,
181    // branchlessly insert key-value pair into it, if condition is true
182    //
183    // - Interpret block as aligned KeySize + ValueSize chunks
184    // - Find the first one if any that has key of all zeroes or matching query, and
185    //   cmov value on top of that.
186    fn insert_to_block(
187        condition: Choice,
188        query: &A8Bytes<KeySize>,
189        new_value: &A8Bytes<ValueSize>,
190        block: &mut A64Bytes<BlockSize>,
191    ) {
192        // key_buf is initially query, and is zeroes for every match there-after
193        let mut key_buf = query.clone();
194        let pairs: &mut [A8Bytes<Sum<KeySize, ValueSize>>] = block.as_mut_aligned_chunks();
195        for pair in pairs {
196            let (key, value): (&mut A8Bytes<KeySize>, &mut A8Bytes<ValueSize>) =
197                <&mut A8Bytes<Sum<KeySize, ValueSize>> as Split<u8, KeySize>>::split(pair);
198            let test = condition & (key.ct_eq(query) | key.ct_eq(&A8Bytes::<KeySize>::default()));
199            key.cmov(test, &key_buf);
200            // This ensures that if we find the key a second time, or, after finding an
201            // empty space, the cell is zeroed to make space for other things
202            key_buf.cmov(test, &A8Bytes::default());
203            value.cmov(test, new_value);
204        }
205    }
206
207    const BUCKET_CAPACITY: u64 = (BlockSize::U64 / (KeySize::U64 + ValueSize::U64));
208}
209
210impl<KeySize, ValueSize, BlockSize, RngType, O> ObliviousHashMap<KeySize, ValueSize>
211    for CuckooHashTable<KeySize, ValueSize, BlockSize, RngType, O>
212where
213    KeySize: ArrayLength<u8> + Add<ValueSize> + PartialDiv<U8> + 'static,
214    ValueSize: ArrayLength<u8> + PartialDiv<U8>,
215    BlockSize: ArrayLength<u8> + PartialDiv<U8>,
216    RngType: RngCore + CryptoRng + Send + Sync + 'static,
217    O: ORAM<BlockSize> + Send + Sync + 'static,
218    Sum<KeySize, ValueSize>: ArrayLength<u8> + Sub<KeySize, Output = ValueSize> + PartialDiv<U8>,
219{
220    fn len(&self) -> u64 {
221        self.num_items
222    }
223    fn capacity(&self) -> u64 {
224        2 * self.num_buckets * Self::BUCKET_CAPACITY
225    }
226
227    /// To read:
228    /// Early return if the query is all zero bytes
229    /// Hash the query (twice)
230    /// Load the corresponding blocks from ORAM (one at a time)
231    /// Interpret block as [(KeySize, ValueSize)]
232    /// Ct-compare the found key with the query
233    /// If successful, cmov OMAP_FOUND onto result_code and cmov value onto
234    /// result. Return result_code and result after scanning both loaded
235    /// blocks
236    fn read(&mut self, query: &A8Bytes<KeySize>, output: &mut A8Bytes<ValueSize>) -> u32 {
237        // Early return for invalid key
238        if bool::from(query.ct_eq(&A8Bytes::<KeySize>::default())) {
239            return OMAP_INVALID_KEY;
240        }
241
242        let mut result_code = OMAP_NOT_FOUND;
243        let hashes = self.hash_query(query);
244        self.oram1.access(hashes[0], |block| {
245            let pairs: &[A8Bytes<Sum<KeySize, ValueSize>>] = block.as_aligned_chunks();
246            for pair in pairs {
247                let (key, value): (&A8Bytes<KeySize>, &A8Bytes<ValueSize>) = pair.split();
248                let test = query.ct_eq(key);
249                result_code.cmov(test, &OMAP_FOUND);
250                output.cmov(test, value);
251            }
252        });
253        self.oram2.access(hashes[1], |block| {
254            let pairs: &[A8Bytes<Sum<KeySize, ValueSize>>] = block.as_aligned_chunks();
255            for pair in pairs {
256                let (key, value): (&A8Bytes<KeySize>, &A8Bytes<ValueSize>) = pair.split();
257                let test = query.ct_eq(key);
258                result_code.cmov(test, &OMAP_FOUND);
259                output.cmov(test, value);
260            }
261        });
262        result_code
263    }
264
265    /// For access:
266    /// Access (without deleting) must be fully oblivious, unlike write
267    /// - Checkout both buckets, scan them for the query, copying onto a stack
268    ///   buffer
269    /// - Run callback at the stack buffer
270    /// - Scan the buckets again and overwrite the old buffer
271    /// - If the callback asked to delete the item, also zero out its key.
272    ///
273    /// We would like to be constant-time about whether deletes happened or not,
274    /// but changing the number of items in the map is a side-effect which we
275    /// leak some information about, so we can't be totally principled about
276    /// that.
277    fn access_and_remove<F: FnOnce(u32, &mut A8Bytes<ValueSize>) -> Choice>(
278        &mut self,
279        query: &A8Bytes<KeySize>,
280        f: F,
281    ) {
282        let mut callback_buffer = A8Bytes::<ValueSize>::default();
283        // All zeroes is an invalid key, because we use that as a sentinel to detect
284        // free spots in the hashmap. In this scenario, we give the callback
285        // OMAP_INVALID_KEY and pass an empty buffer which is later discarded.
286        let query_is_valid = !query.ct_eq(&A8Bytes::<KeySize>::default());
287
288        let hashes = self.hash_query(query);
289        let oram1 = &mut self.oram1;
290        let oram2 = &mut self.oram2;
291
292        oram1.access(hashes[0], |block1| {
293            oram2.access(hashes[1], |block2| {
294                // If we never find the item, we will pass OMAP_NOT_FOUND to callback,
295                // and we will not insert it, no matter what the callback does.
296                let mut result_code = OMAP_INVALID_KEY;
297                result_code.cmov(query_is_valid, &OMAP_NOT_FOUND);
298                for block in &[&block1, &block2] {
299                    let pairs: &[A8Bytes<Sum<KeySize, ValueSize>>] = block.as_aligned_chunks();
300                    for pair in pairs {
301                        let (key, val): (&A8Bytes<KeySize>, &A8Bytes<ValueSize>) = pair.split();
302                        // Test if query = key, but also, always do nothing if query is invalid
303                        let test = query.ct_eq(key) & query_is_valid;
304                        callback_buffer.cmov(test, val);
305                        debug_assert!(
306                            result_code != OMAP_FOUND || bool::from(!test),
307                            "key should not be found twice"
308                        );
309                        result_code.cmov(test, &OMAP_FOUND);
310                    }
311                }
312                let should_delete = f(result_code, &mut callback_buffer);
313
314                for block in &mut [block1, block2] {
315                    let pairs: &mut [A8Bytes<Sum<KeySize, ValueSize>>] =
316                        block.as_mut_aligned_chunks();
317                    for pair in pairs {
318                        let (key, val): (&mut A8Bytes<KeySize>, &mut A8Bytes<ValueSize>) =
319                            pair.split();
320                        // Test if query = key, but also, always do nothing if query is invalid
321                        let test = query.ct_eq(key) & query_is_valid;
322                        val.cmov(test, &callback_buffer);
323                        // Zero the key if the callback wants us to delete this item
324                        key.cmov(test & should_delete, &Default::default());
325                    }
326                }
327            });
328        });
329    }
330
331    fn remove(&mut self, query: &A8Bytes<KeySize>) -> u32 {
332        // Early return for invalid key
333        if bool::from(query.ct_eq(&A8Bytes::<KeySize>::default())) {
334            return OMAP_INVALID_KEY;
335        }
336        let mut result_code = OMAP_NOT_FOUND;
337        let hashes = self.hash_query(query);
338        self.oram1.access(hashes[0], |block| {
339            let pairs: &mut [A8Bytes<Sum<KeySize, ValueSize>>] = block.as_mut_aligned_chunks();
340            for pair in pairs {
341                let (key, _): (&mut A8Bytes<KeySize>, &mut A8Bytes<ValueSize>) = pair.split();
342                let test = query.as_slice().ct_eq(key.as_slice());
343                key.cmov(test, &Default::default());
344                result_code.cmov(test, &OMAP_FOUND);
345            }
346        });
347        self.oram2.access(hashes[1], |block| {
348            let pairs: &mut [A8Bytes<Sum<KeySize, ValueSize>>] = block.as_mut_aligned_chunks();
349            for pair in pairs {
350                let (key, _): (&mut A8Bytes<KeySize>, &mut A8Bytes<ValueSize>) = pair.split();
351                let test = query.as_slice().ct_eq(key.as_slice());
352                key.cmov(test, &Default::default());
353                result_code.cmov(test, &OMAP_FOUND);
354            }
355        });
356        result_code
357    }
358
359    /// For writing: The insertion algorithm is, hash the item twice and load
360    /// its buckets. We always add to the less loaded of the two buckets,
361    /// breaking ties to the right, that is, prefering to write to oram2. If
362    /// BOTH buckets overflow, then we choose an item at random from oram1
363    /// bucket and kick it out, then we hash that item and insert it into the
364    /// other bucket where it can go, repeating the process if necessary. If
365    /// after a few tries it doesn't work, we give up, roll everything back, and
366    /// return OMAP_OVERFLOW.
367    ///
368    /// This is amortized constant time, but not constant time due
369    /// to this relocation. The intuition is that if we treat the hash functons
370    /// as random functions, we can treat the graph connecting buckets that are
371    /// both the target of an element as a random graph and with high
372    /// probability a sparse random graph doesn't have any cycles. See the proof
373    /// here: https://cs.stanford.edu/~rishig/courses/ref/l13a.pdf
374    ///
375    /// The access function is an alternative that allows modifying values in
376    /// the map without taking a variable amount of time.
377    ///
378    /// Note: this function is not generally oblivious with respect to the item
379    /// being inserted. In many cases, inserts don't need to be oblivious. It is
380    /// possible to use this function in such a way as to perform an completely
381    /// oblivious insert. We will explain that now. This function has some
382    /// limited data oblivious guarantees:
383    /// - If we restrict attention to calling vartime_write on items q which are
384    ///   already in the OMAP, then it is oblivious with respect to such q.
385    /// - If we restrict attention to calling vartime_write on items q which are
386    ///   not already in the OMAP, then it is oblivious with respect to these,
387    ///   assuming that the hash function used in the map has a strong PRF
388    ///   property.
389    /// - However, calling the function DOES reveal if q is already in the map
390    ///   or not. To avoid leaking this information, and perform a fully
391    ///   oblivious write-or-insert operation, the caller should use the
392    ///   access_and_insert function from the
393    ///   [`ObliviousHashMap::access_and_insert()`] trait.
394
395    fn vartime_write_extended(
396        &mut self,
397        query: &A8Bytes<KeySize>,
398        new_value: &A8Bytes<ValueSize>,
399        allow_overwrite: Choice,
400        allow_sideeffects_and_eviction: Choice,
401    ) -> u32 {
402        // Early return for invalid key
403        if bool::from(query.ct_eq(&A8Bytes::<KeySize>::default())) {
404            return OMAP_INVALID_KEY;
405        }
406
407        // The result_code we will return
408        let mut result_code = OMAP_NOT_FOUND;
409
410        // If after access we have to evict something, it is stored temporarily here
411        // Its bucket is pushed to eviction_from, and its index in the bucket to
412        // eviction_indices
413        let mut evicted_key = A8Bytes::<KeySize>::default();
414        let mut evicted_val = A8Bytes::<ValueSize>::default();
415
416        // The number of times we will try evicting before giving up
417        let mut eviction_retries = MAX_EVICTION_RETRIES;
418        // The buckets (hashes) from which we evicted items came, so that we can go back
419        // and restore them if we give up.
420        let mut eviction_from = Vec::<u64>::with_capacity(eviction_retries);
421        // The indices at which we evict items, so that we can go back and restore them
422        // if we give up.
423        let mut eviction_indices = Vec::<usize>::with_capacity(eviction_retries);
424
425        // Compute the hashes for this query
426        let hashes = self.hash_query(query);
427
428        // Get a let binding to self.rng
429        let rng = &mut self.rng;
430        let oram1 = &mut self.oram1;
431        let oram2 = &mut self.oram2;
432        oram1.access(hashes[0], |block1| {
433            oram2.access(hashes[1], |block2| {
434                // Note: These calls don't need to be constant-time to meet the requirement,
435                // but we already had the code that way, and it may serve as "defense-in-depth".
436                // If they show up in profiling, then we can make variable time versions
437                let (block1_found, block1_empty_count) = Self::count_before_insert(query, block1);
438                let (block2_found, block2_empty_count) = Self::count_before_insert(query, block2);
439                debug_assert!(
440                    !bool::from(block1_found & block2_found),
441                    "key should not be found twice"
442                );
443
444                let found = block1_found | block2_found;
445                result_code.cmov(found, &OMAP_FOUND);
446
447                // Scope for "condition" variable
448                {
449                    // condition is false when side-effects are disallowed, OR
450                    // if we found the item and we aren't allowed to overwrite
451                    let condition = allow_sideeffects_and_eviction & (allow_overwrite | !found);
452                    // write_to_block1 is true when we should prefer to write to block1 over block2
453                    // watch the case that hashes[0] == hashes[1] !
454                    // in that case we prefer to modify block2 (in oram2), arbitrarily.
455                    // So, if block2_found, we should be false, even if block1_found.
456                    // And if not found in either place and block1_empty_count ==
457                    // block2_empty_count, prefer block2.
458                    let write_to_block1 = !block2_found
459                        & (block1_found | block1_empty_count.ct_gt(&block2_empty_count));
460
461                    Self::insert_to_block(condition & write_to_block1, query, new_value, block1);
462                    Self::insert_to_block(condition & !write_to_block1, query, new_value, block2);
463                }
464
465                // If it wasn't found and both blocks were full,
466                // then we set result_code to overflow and evict something at random,
467                // from block1. This is done because the item will likely end in block2
468                // after that, which is what we want, per "Unbalanced allocations" papers
469                // that show that this improves performance.
470                // Skip this if eviction is disallowed
471                if bool::from(
472                    !found
473                        & block1_empty_count.ct_eq(&0)
474                        & block2_empty_count.ct_eq(&0)
475                        & allow_sideeffects_and_eviction,
476                ) {
477                    result_code = OMAP_OVERFLOW;
478
479                    let random = rng.next_u32();
480                    // index determines which entry in that bucket we evict
481                    let index = (random % (Self::BUCKET_CAPACITY as u32)) as usize;
482
483                    eviction_from.push(hashes[0]);
484                    eviction_indices.push(index);
485
486                    let pairs: &mut [A8Bytes<Sum<KeySize, ValueSize>>] =
487                        block1.as_mut_aligned_chunks();
488                    let pair = &mut pairs[index];
489                    let (key, val): (&mut A8Bytes<KeySize>, &mut A8Bytes<ValueSize>) = pair.split();
490                    evicted_key = key.clone();
491                    evicted_val = val.clone();
492                    // Note: This is a side-effect but we don't reach this line if allow_sideeffects
493                    // is false
494                    debug_assert!(bool::from(allow_sideeffects_and_eviction));
495                    *key = query.clone();
496                    *val = new_value.clone();
497                }
498            });
499        });
500
501        // Overflow handling loop
502        while result_code == OMAP_OVERFLOW {
503            if eviction_retries > 0 {
504                let last_evicted_from = eviction_from[eviction_from.len() - 1];
505
506                // We always start evicting from bucket 1, and alternate, per cuckoo hashing
507                // algo, so the next_oram (minus one) is eviction_from.len() %
508                // 2.
509                let next_oram = eviction_from.len() % 2;
510
511                let hashes = self.hash_query(&evicted_key);
512                // last_evicted_from should be the other place that evicted_key could go
513                debug_assert!(hashes[1 - next_oram] == last_evicted_from);
514                let dest = hashes[next_oram];
515
516                // Access what is hopefully a block with a vacant spot
517                let rng = &mut self.rng;
518                // Get a reference to the oram we will insert to. This is a branch,
519                // but the access patterns are completely predicatable based on number of passes
520                // through this loop.
521                let oram = if next_oram == 0 {
522                    &mut self.oram1
523                } else {
524                    &mut self.oram2
525                };
526
527                oram.access(dest, |block| {
528                    let pairs: &mut [A8Bytes<Sum<KeySize, ValueSize>>] =
529                        block.as_mut_aligned_chunks();
530                    debug_assert!(pairs.len() == Self::BUCKET_CAPACITY as usize);
531
532                    // If we find a vacant spot in this block, then insert evicted key and val there
533                    let mut found_vacant = Choice::from(0);
534                    for pair in pairs.iter_mut() {
535                        let (key, val): (&mut A8Bytes<KeySize>, &mut A8Bytes<ValueSize>) =
536                            pair.split();
537                        debug_assert!(
538                            key != &evicted_key,
539                            "evicted key should not be present anywhere"
540                        );
541                        let is_vacant = key.ct_eq(&A8Bytes::<KeySize>::default());
542                        // Note: This is a side-effect, but this code is unreachable if
543                        // allow_sideeffects is false.
544                        debug_assert!(bool::from(allow_sideeffects_and_eviction));
545                        let cond = !found_vacant & is_vacant;
546                        key.cmov(cond, &evicted_key);
547                        val.cmov(cond, &evicted_val);
548                        found_vacant |= is_vacant;
549                    }
550
551                    // If we found a vacant spot, then the result is not OMAP_OVERFLOW anymore, we
552                    // are done
553                    if bool::from(found_vacant) {
554                        // This will cause us to exit the while loop
555                        result_code = OMAP_NOT_FOUND;
556                    } else {
557                        // This block was full also, so we repeat the eviction process
558                        let index = (rng.next_u32() % (Self::BUCKET_CAPACITY as u32)) as usize;
559
560                        let pair = &mut pairs[index];
561                        let (key, val): (&mut A8Bytes<KeySize>, &mut A8Bytes<ValueSize>) =
562                            pair.split();
563
564                        // Evict this key and value for the previously evicted key and value.
565                        // Note: This is a side-effect, but this code is unreachable if
566                        // allow_sideeffects is false.
567                        debug_assert!(bool::from(allow_sideeffects_and_eviction));
568                        core::mem::swap(key, &mut evicted_key);
569                        core::mem::swap(val, &mut evicted_val);
570
571                        eviction_from.push(dest);
572                        eviction_indices.push(index);
573                    }
574                });
575
576                eviction_retries = eviction_retries.wrapping_sub(1);
577            } else {
578                // We have given up trying to evict things, we will now roll back
579                debug_assert!(eviction_from.len() == eviction_indices.len());
580                while !eviction_indices.is_empty() {
581                    // We always start evicting from bucket 1, and alternate, per cuckoo hashing
582                    // algo, so the next_oram we WOULD insert to (minus one) is
583                    // eviction_from.len() % 2.
584                    let next_oram = eviction_from.len() % 2;
585
586                    // Note: these unwraps are "okay" because we test is_empty(),
587                    // and we debug_assert that they have the same length.
588                    // If llvm is not eliminating the panicking path then we may
589                    // want to do this differently.
590                    let evicted_index = eviction_indices.pop().unwrap();
591                    let evicted_from = eviction_from.pop().unwrap();
592                    debug_assert!(
593                        self.hash_query(&evicted_key)[1 - next_oram] == evicted_from,
594                        "The evicted key doesn't hash to the spot we thought we evicted it from"
595                    );
596                    // Get a reference to the oram we took this item from. This is a branch,
597                    // but the access patterns are completely predicatable based on number of passes
598                    // through this loop.
599                    let oram = if next_oram == 0 {
600                        &mut self.oram2
601                    } else {
602                        &mut self.oram1
603                    };
604
605                    oram.access(evicted_from, |block| {
606                        let pairs: &mut [A8Bytes<Sum<KeySize, ValueSize>>] =
607                            block.as_mut_aligned_chunks();
608                        let pair = &mut pairs[evicted_index];
609                        let (key, val): (&mut A8Bytes<KeySize>, &mut A8Bytes<ValueSize>) =
610                            pair.split();
611
612                        // Perform a swap, to undo the previous swap.
613                        // Note that this code is unreachable if side-effects are forbidden
614                        debug_assert!(bool::from(allow_sideeffects_and_eviction));
615                        core::mem::swap(key, &mut evicted_key);
616                        core::mem::swap(val, &mut evicted_val);
617                    })
618                }
619
620                debug_assert!(&evicted_key == query, "After rolling back evictions, we didn't end up with the initially inserted item coming back");
621                // At this point the evicted_key should be the item we initially inserted, if
622                // rollback worked. We can now return OMAP_OVERFLOW because the
623                // semantics of the map didn't change, we simply
624                // failed to insert.
625                return OMAP_OVERFLOW;
626            }
627        }
628
629        // Adjust num_items if we inserted a new item successfully
630        self.num_items += (result_code.ct_eq(&OMAP_NOT_FOUND) & allow_sideeffects_and_eviction)
631            .unwrap_u8() as u64;
632        result_code
633    }
634}
635
636/// Factory implementing OMapCreator for this type, based on any ORAM Creator.
637/// Otherwise it is very difficult to invoke CuckooHashTable::new() generically
638pub struct CuckooHashTableCreator<BlockSize, RngType, OC>
639where
640    BlockSize: ArrayLength<u8>,
641    RngType: RngCore + CryptoRng + Send + Sync + 'static,
642    OC: ORAMCreator<BlockSize, RngType>,
643    OC::Output: ORAM<BlockSize> + Send + Sync + 'static,
644{
645    _block_size: PhantomData<fn() -> BlockSize>,
646    _rng_type: PhantomData<fn() -> RngType>,
647    _oc: PhantomData<fn() -> OC>,
648}
649
650impl<KeySize, ValueSize, BlockSize, RngType, OC> OMapCreator<KeySize, ValueSize, RngType>
651    for CuckooHashTableCreator<BlockSize, RngType, OC>
652where
653    KeySize: ArrayLength<u8> + Add<ValueSize> + PartialDiv<U8> + 'static,
654    ValueSize: ArrayLength<u8> + PartialDiv<U8> + 'static,
655    BlockSize: ArrayLength<u8> + PartialDiv<U8> + 'static,
656    RngType: RngCore + CryptoRng + Send + Sync + 'static,
657    OC: ORAMCreator<BlockSize, RngType>,
658    OC::Output: ORAM<BlockSize> + Send + Sync + 'static,
659    Sum<KeySize, ValueSize>: ArrayLength<u8> + Sub<KeySize, Output = ValueSize> + PartialDiv<U8>,
660{
661    type Output = CuckooHashTable<KeySize, ValueSize, BlockSize, RngType, OC::Output>;
662
663    fn create<M: 'static + FnMut() -> RngType>(
664        size: u64,
665        stash_size: usize,
666        rng_maker: M,
667    ) -> Self::Output {
668        Self::Output::new::<OC, M>(size, stash_size, rng_maker)
669    }
670}
671
672#[cfg(test)]
673mod testing {
674    use super::*;
675    use mc_oblivious_ram::PathORAM4096Z4Creator;
676    use mc_oblivious_traits::{
677        rng_maker, testing, HeapORAMStorageCreator, OMapCreator, OMAP_FOUND, OMAP_NOT_FOUND,
678    };
679    use test_helper::{run_with_several_seeds, RngType};
680    use typenum::{U1024, U8};
681
682    extern crate std;
683
684    const STASH_SIZE: usize = 16;
685
686    type ORAMCreatorZ4 = PathORAM4096Z4Creator<RngType, HeapORAMStorageCreator>;
687    type CuckooCreatorZ4 = CuckooHashTableCreator<U1024, RngType, ORAMCreatorZ4>;
688
689    /// Make a8-bytes that are initialized to a particular byte value
690    /// This makes tests shorter to write
691    fn a8_8<N: ArrayLength<u8>>(src: u8) -> A8Bytes<N> {
692        let mut result = A8Bytes::<N>::default();
693        for byte in result.as_mut_slice() {
694            *byte = src;
695        }
696        result
697    }
698
699    #[test]
700    fn sanity_check_omap_z4_4() {
701        run_with_several_seeds(|rng| {
702            // This should be ~1 underlying bucket
703            let mut omap = <CuckooCreatorZ4 as OMapCreator<U8, U8, RngType>>::create(
704                4,
705                STASH_SIZE,
706                rng_maker(rng),
707            );
708
709            let mut temp = A8Bytes::<U8>::default();
710
711            assert_eq!(OMAP_NOT_FOUND, omap.read(&a8_8(1), &mut temp));
712            assert_eq!(
713                OMAP_NOT_FOUND,
714                omap.vartime_write(&a8_8(1), &a8_8(2), 0.into())
715            );
716            assert_eq!(OMAP_FOUND, omap.read(&a8_8(1), &mut temp));
717            assert_eq!(&temp, &a8_8(2));
718            assert_eq!(OMAP_FOUND, omap.vartime_write(&a8_8(1), &a8_8(3), 1.into()));
719            assert_eq!(OMAP_FOUND, omap.read(&a8_8(1), &mut temp));
720            assert_eq!(&temp, &a8_8(3));
721
722            assert_eq!(OMAP_NOT_FOUND, omap.read(&a8_8(2), &mut temp));
723            assert_eq!(
724                &temp,
725                &a8_8(3),
726                "omap.read must not modify the output on not_found"
727            );
728            assert_eq!(
729                OMAP_NOT_FOUND,
730                omap.vartime_write(&a8_8(2), &a8_8(20), 1.into())
731            );
732            assert_eq!(OMAP_FOUND, omap.read(&a8_8(2), &mut temp));
733            assert_eq!(&temp, &a8_8(20));
734            assert_eq!(
735                OMAP_FOUND,
736                omap.vartime_write(&a8_8(2), &a8_8(30), 0.into())
737            );
738            assert_eq!(OMAP_FOUND, omap.read(&a8_8(2), &mut temp));
739            assert_eq!(
740                &temp,
741                &a8_8(20),
742                "omap.write must not modify when overwrite is disallowed"
743            );
744            assert_eq!(
745                OMAP_FOUND,
746                omap.vartime_write(&a8_8(2), &a8_8(30), 1.into())
747            );
748            assert_eq!(OMAP_FOUND, omap.read(&a8_8(2), &mut temp));
749            assert_eq!(&temp, &a8_8(30));
750        })
751    }
752
753    #[test]
754    fn sanity_check_omap_z4_256() {
755        run_with_several_seeds(|rng| {
756            // This should be ~ 4 underlying buckets
757            let mut omap = <CuckooCreatorZ4 as OMapCreator<U8, U8, RngType>>::create(
758                256,
759                STASH_SIZE,
760                rng_maker(rng),
761            );
762
763            let mut temp = A8Bytes::<U8>::default();
764
765            assert_eq!(OMAP_NOT_FOUND, omap.read(&a8_8(1), &mut temp));
766            assert_eq!(
767                OMAP_NOT_FOUND,
768                omap.vartime_write(&a8_8(1), &a8_8(2), 0.into())
769            );
770            assert_eq!(OMAP_FOUND, omap.read(&a8_8(1), &mut temp));
771            assert_eq!(&temp, &a8_8(2));
772            assert_eq!(OMAP_FOUND, omap.vartime_write(&a8_8(1), &a8_8(3), 1.into()));
773            assert_eq!(OMAP_FOUND, omap.read(&a8_8(1), &mut temp));
774            assert_eq!(&temp, &a8_8(3));
775
776            assert_eq!(OMAP_NOT_FOUND, omap.read(&a8_8(2), &mut temp));
777            assert_eq!(
778                &temp,
779                &a8_8(3),
780                "omap.read must not modify the output on not_found"
781            );
782            assert_eq!(
783                OMAP_NOT_FOUND,
784                omap.vartime_write(&a8_8(2), &a8_8(20), 1.into())
785            );
786            assert_eq!(OMAP_FOUND, omap.read(&a8_8(2), &mut temp));
787            assert_eq!(&temp, &a8_8(20));
788            assert_eq!(
789                OMAP_FOUND,
790                omap.vartime_write(&a8_8(2), &a8_8(30), 0.into())
791            );
792            assert_eq!(OMAP_FOUND, omap.read(&a8_8(2), &mut temp));
793            assert_eq!(
794                &temp,
795                &a8_8(20),
796                "omap.write must not modify when overwrite is disallowed"
797            );
798            assert_eq!(
799                OMAP_FOUND,
800                omap.vartime_write(&a8_8(2), &a8_8(30), 1.into())
801            );
802            assert_eq!(OMAP_FOUND, omap.read(&a8_8(2), &mut temp));
803            assert_eq!(&temp, &a8_8(30));
804        })
805    }
806
807    #[test]
808    fn sanity_check_omap_access_api_z4_256() {
809        run_with_several_seeds(|rng| {
810            // This should be ~ 4 underlying buckets
811            let mut omap = <CuckooCreatorZ4 as OMapCreator<U8, U8, RngType>>::create(
812                256,
813                STASH_SIZE,
814                rng_maker(rng),
815            );
816
817            omap.access(&a8_8(1), |code, _buffer| {
818                assert_eq!(code, OMAP_NOT_FOUND);
819            });
820
821            assert_eq!(
822                OMAP_NOT_FOUND,
823                omap.vartime_write(&a8_8(1), &a8_8(2), 0.into())
824            );
825
826            omap.access(&a8_8(1), |code, buffer| {
827                assert_eq!(code, OMAP_FOUND);
828                assert_eq!(buffer, &a8_8(2));
829            });
830
831            assert_eq!(OMAP_FOUND, omap.vartime_write(&a8_8(1), &a8_8(3), 1.into()));
832
833            omap.access(&a8_8(1), |code, buffer| {
834                assert_eq!(code, OMAP_FOUND);
835                assert_eq!(buffer, &a8_8(3));
836            });
837
838            omap.access(&a8_8(2), |code, _buffer| {
839                assert_eq!(code, OMAP_NOT_FOUND);
840            });
841
842            assert_eq!(
843                OMAP_NOT_FOUND,
844                omap.vartime_write(&a8_8(2), &a8_8(20), 1.into())
845            );
846
847            omap.access(&a8_8(2), |code, buffer| {
848                assert_eq!(code, OMAP_FOUND);
849                assert_eq!(buffer, &a8_8(20));
850            });
851
852            assert_eq!(
853                OMAP_FOUND,
854                omap.vartime_write(&a8_8(2), &a8_8(30), 0.into())
855            );
856
857            omap.access_and_remove(&a8_8(2), |code, buffer| {
858                assert_eq!(code, OMAP_FOUND);
859                assert_eq!(
860                    buffer,
861                    &a8_8(20),
862                    "omap.write must not modify when overwrite is disallowed"
863                );
864                Choice::from(0)
865            });
866
867            omap.access_and_remove(&a8_8(2), |code, buffer| {
868                assert_eq!(code, OMAP_FOUND);
869                assert_eq!(
870                    buffer,
871                    &a8_8(20),
872                    "omap.access_and_remove should not delete when delete is false"
873                );
874                Choice::from(0)
875            });
876
877            assert_eq!(
878                OMAP_FOUND,
879                omap.vartime_write(&a8_8(2), &a8_8(30), 1.into())
880            );
881
882            omap.access_and_remove(&a8_8(2), |code, buffer| {
883                assert_eq!(code, OMAP_FOUND);
884                assert_eq!(
885                    buffer,
886                    &a8_8(30),
887                    "omap.write must modify when overwrite is allowed"
888                );
889                Choice::from(1)
890            });
891
892            omap.access(&a8_8(2), |code, buffer| {
893                assert_eq!(
894                    code, OMAP_NOT_FOUND,
895                    "omap.access_and_remove should delete when delete is true"
896                );
897                assert_eq!(
898                    buffer,
899                    &a8_8(0),
900                    "when data is not present, access should give us all zeroes buffer"
901                );
902            });
903        })
904    }
905
906    #[test]
907    fn sanity_check_omap_z4_524288() {
908        run_with_several_seeds(|rng| {
909            // This should be ~ 8192 underlying buckets
910            let mut omap = <CuckooCreatorZ4 as OMapCreator<U8, U8, RngType>>::create(
911                524288,
912                STASH_SIZE,
913                rng_maker(rng),
914            );
915
916            let mut temp = A8Bytes::<U8>::default();
917
918            assert_eq!(OMAP_NOT_FOUND, omap.read(&a8_8(1), &mut temp));
919            assert_eq!(
920                OMAP_NOT_FOUND,
921                omap.vartime_write(&a8_8(1), &a8_8(2), 0.into())
922            );
923            assert_eq!(OMAP_FOUND, omap.read(&a8_8(1), &mut temp));
924            assert_eq!(&temp, &a8_8(2));
925            assert_eq!(OMAP_FOUND, omap.vartime_write(&a8_8(1), &a8_8(3), 1.into()));
926            assert_eq!(OMAP_FOUND, omap.read(&a8_8(1), &mut temp));
927            assert_eq!(&temp, &a8_8(3));
928
929            assert_eq!(OMAP_NOT_FOUND, omap.read(&a8_8(2), &mut temp));
930            assert_eq!(
931                &temp,
932                &a8_8(3),
933                "omap.read must not modify the output on not_found"
934            );
935            assert_eq!(
936                OMAP_NOT_FOUND,
937                omap.vartime_write(&a8_8(2), &a8_8(20), 1.into())
938            );
939            assert_eq!(OMAP_FOUND, omap.read(&a8_8(2), &mut temp));
940            assert_eq!(&temp, &a8_8(20));
941            assert_eq!(
942                OMAP_FOUND,
943                omap.vartime_write(&a8_8(2), &a8_8(30), 0.into())
944            );
945            assert_eq!(OMAP_FOUND, omap.read(&a8_8(2), &mut temp));
946            assert_eq!(
947                &temp,
948                &a8_8(20),
949                "omap.write must not modify when overwrite is disallowed"
950            );
951            assert_eq!(
952                OMAP_FOUND,
953                omap.vartime_write(&a8_8(2), &a8_8(30), 1.into())
954            );
955            assert_eq!(OMAP_FOUND, omap.read(&a8_8(2), &mut temp));
956            assert_eq!(&temp, &a8_8(30));
957        })
958    }
959
960    #[test]
961    fn sanity_check_omap_z4_2097152() {
962        run_with_several_seeds(|rng| {
963            // This should be ~32768 underlying buckets
964            let mut omap = <CuckooCreatorZ4 as OMapCreator<U8, U8, RngType>>::create(
965                2097152,
966                STASH_SIZE,
967                rng_maker(rng),
968            );
969
970            let mut temp = A8Bytes::<U8>::default();
971
972            assert_eq!(OMAP_NOT_FOUND, omap.read(&a8_8(1), &mut temp));
973            assert_eq!(
974                OMAP_NOT_FOUND,
975                omap.vartime_write(&a8_8(1), &a8_8(2), 1.into())
976            );
977            assert_eq!(OMAP_FOUND, omap.read(&a8_8(1), &mut temp));
978            assert_eq!(&temp, &a8_8(2));
979            assert_eq!(OMAP_FOUND, omap.vartime_write(&a8_8(1), &a8_8(3), 1.into()));
980            assert_eq!(OMAP_FOUND, omap.read(&a8_8(1), &mut temp));
981            assert_eq!(&temp, &a8_8(3));
982
983            assert_eq!(OMAP_NOT_FOUND, omap.read(&a8_8(2), &mut temp));
984            assert_eq!(
985                &temp,
986                &a8_8(3),
987                "omap.read must not modify the output on not_found"
988            );
989            assert_eq!(
990                OMAP_NOT_FOUND,
991                omap.vartime_write(&a8_8(2), &a8_8(20), 1.into())
992            );
993            assert_eq!(OMAP_FOUND, omap.read(&a8_8(2), &mut temp));
994            assert_eq!(&temp, &a8_8(20));
995            assert_eq!(
996                OMAP_FOUND,
997                omap.vartime_write(&a8_8(2), &a8_8(30), 0.into())
998            );
999            assert_eq!(OMAP_FOUND, omap.read(&a8_8(2), &mut temp));
1000            assert_eq!(
1001                &temp,
1002                &a8_8(20),
1003                "omap.write must not modify when overwrite is disallowed"
1004            );
1005            assert_eq!(
1006                OMAP_FOUND,
1007                omap.vartime_write(&a8_8(2), &a8_8(30), 1.into())
1008            );
1009            assert_eq!(OMAP_FOUND, omap.read(&a8_8(2), &mut temp));
1010            assert_eq!(&temp, &a8_8(30));
1011        })
1012    }
1013
1014    #[test]
1015    #[cfg(not(debug_assertions))]
1016    fn sanity_check_omap_z4_262144() {
1017        run_with_several_seeds(|rng| {
1018            use typenum::{U16, U280};
1019            // This should be ~65538 underlying buckets
1020            let mut omap = <CuckooCreatorZ4 as OMapCreator<U16, U280, RngType>>::create(
1021                262144,
1022                STASH_SIZE,
1023                rng_maker(rng),
1024            );
1025
1026            let mut temp = A8Bytes::<U280>::default();
1027
1028            assert_eq!(OMAP_NOT_FOUND, omap.read(&a8_8(1), &mut temp));
1029            assert_eq!(
1030                OMAP_NOT_FOUND,
1031                omap.vartime_write(&a8_8(1), &a8_8(2), 1.into())
1032            );
1033            assert_eq!(OMAP_FOUND, omap.read(&a8_8(1), &mut temp));
1034            assert_eq!(&temp, &a8_8(2));
1035            assert_eq!(OMAP_FOUND, omap.vartime_write(&a8_8(1), &a8_8(3), 1.into()));
1036            assert_eq!(OMAP_FOUND, omap.read(&a8_8(1), &mut temp));
1037            assert_eq!(&temp, &a8_8(3));
1038
1039            assert_eq!(OMAP_NOT_FOUND, omap.read(&a8_8(2), &mut temp));
1040            assert_eq!(
1041                &temp,
1042                &a8_8(3),
1043                "omap.read must not modify the output on not_found"
1044            );
1045            assert_eq!(
1046                OMAP_NOT_FOUND,
1047                omap.vartime_write(&a8_8(2), &a8_8(20), 1.into())
1048            );
1049            assert_eq!(OMAP_FOUND, omap.read(&a8_8(2), &mut temp));
1050            assert_eq!(&temp, &a8_8(20));
1051            assert_eq!(
1052                OMAP_FOUND,
1053                omap.vartime_write(&a8_8(2), &a8_8(30), 0.into())
1054            );
1055            assert_eq!(OMAP_FOUND, omap.read(&a8_8(2), &mut temp));
1056            assert_eq!(
1057                &temp,
1058                &a8_8(20),
1059                "omap.write must not modify when overwrite is disallowed"
1060            );
1061            assert_eq!(
1062                OMAP_FOUND,
1063                omap.vartime_write(&a8_8(2), &a8_8(30), 1.into())
1064            );
1065            assert_eq!(OMAP_FOUND, omap.read(&a8_8(2), &mut temp));
1066            assert_eq!(&temp, &a8_8(30));
1067        })
1068    }
1069
1070    // Run the exercise omap tests for 200 rounds in a map with 256 items
1071    #[test]
1072    fn exercise_omap_two_choice_path_oram_z4_256() {
1073        run_with_several_seeds(|rng| {
1074            let mut maker = rng_maker(rng);
1075            let mut rng = maker();
1076            // This should be ~4 underlying buckets
1077            let mut omap =
1078                <CuckooCreatorZ4 as OMapCreator<U8, U8, RngType>>::create(256, STASH_SIZE, maker);
1079            testing::exercise_omap(200, &mut omap, &mut rng);
1080        });
1081
1082        run_with_several_seeds(|rng| {
1083            let mut maker = rng_maker(rng);
1084            let mut rng = maker();
1085            let mut omap =
1086                <CuckooCreatorZ4 as OMapCreator<U8, U8, RngType>>::create(256, STASH_SIZE, maker);
1087            testing::exercise_omap_counter_table(200, &mut omap, &mut rng);
1088        });
1089    }
1090
1091    // Run the exercise omap tests for 400 rounds in a map with 3072 items
1092    #[test]
1093    fn exercise_omap_two_choice_path_oram_z4_3072() {
1094        run_with_several_seeds(|rng| {
1095            use typenum::{U16, U280};
1096            let mut maker = rng_maker(rng);
1097            let _ = maker();
1098            let mut rng = maker();
1099            // This should be ~1024 underlying buckets
1100            let mut omap = <CuckooCreatorZ4 as OMapCreator<U16, U280, RngType>>::create(
1101                3072, STASH_SIZE, maker,
1102            );
1103
1104            testing::exercise_omap(400, &mut omap, &mut rng);
1105        });
1106
1107        run_with_several_seeds(|rng| {
1108            use typenum::U16;
1109            let mut maker = rng_maker(rng);
1110            let _ = maker();
1111            let mut rng = maker();
1112            let mut omap =
1113                <CuckooCreatorZ4 as OMapCreator<U16, U8, RngType>>::create(3072, STASH_SIZE, maker);
1114
1115            testing::exercise_omap_counter_table(400, &mut omap, &mut rng);
1116        });
1117    }
1118
1119    // Run the exercise omap tests for 2000 rounds in a map with 65536 items
1120    #[test]
1121    #[cfg(not(debug_assertions))]
1122    fn exercise_omap_two_choice_path_oram_z4_65536() {
1123        run_with_several_seeds(|rng| {
1124            let mut maker = rng_maker(rng);
1125            let mut rng = maker();
1126            // This should be ~1024 underlying buckets
1127            let mut omap =
1128                <CuckooCreatorZ4 as OMapCreator<U8, U8, RngType>>::create(65536, STASH_SIZE, maker);
1129
1130            testing::exercise_omap(2000, &mut omap, &mut rng);
1131        });
1132
1133        run_with_several_seeds(|rng| {
1134            let mut maker = rng_maker(rng);
1135            let mut rng = maker();
1136            let mut omap =
1137                <CuckooCreatorZ4 as OMapCreator<U8, U8, RngType>>::create(65536, STASH_SIZE, maker);
1138
1139            testing::exercise_omap_counter_table(2000, &mut omap, &mut rng);
1140        });
1141    }
1142
1143    // Run the exercise omap tests for 16_000 rounds in a map with 524288 items
1144    #[test]
1145    #[cfg(not(debug_assertions))]
1146    fn exercise_omap_two_choice_path_oram_z4_524288() {
1147        run_with_several_seeds(|rng| {
1148            let mut maker = rng_maker(rng);
1149            let mut rng = maker();
1150            // This should be ~8192 underlying buckets
1151            let mut omap = <CuckooCreatorZ4 as OMapCreator<U8, U8, RngType>>::create(
1152                524288, STASH_SIZE, maker,
1153            );
1154
1155            testing::exercise_omap(16_000, &mut omap, &mut rng);
1156        });
1157
1158        run_with_several_seeds(|rng| {
1159            let mut maker = rng_maker(rng);
1160            let mut rng = maker();
1161            let mut omap = <CuckooCreatorZ4 as OMapCreator<U8, U8, RngType>>::create(
1162                524288, STASH_SIZE, maker,
1163            );
1164
1165            testing::exercise_omap_counter_table(16_000, &mut omap, &mut rng);
1166        });
1167    }
1168
1169    // Run the exercise omap tests for 16_000 rounds in a map with 2097152 items
1170    #[test]
1171    #[cfg(not(debug_assertions))]
1172    fn exercise_omap_two_choice_path_oram_z4_2097152() {
1173        run_with_several_seeds(|rng| {
1174            let mut maker = rng_maker(rng);
1175            let mut rng = maker();
1176            // This should be ~32768 underlying buckets
1177            let mut omap = <CuckooCreatorZ4 as OMapCreator<U8, U8, RngType>>::create(
1178                2097152, STASH_SIZE, maker,
1179            );
1180
1181            testing::exercise_omap(16_000, &mut omap, &mut rng);
1182        });
1183
1184        run_with_several_seeds(|rng| {
1185            let mut maker = rng_maker(rng);
1186            let mut rng = maker();
1187            let mut omap = <CuckooCreatorZ4 as OMapCreator<U8, U8, RngType>>::create(
1188                2097152, STASH_SIZE, maker,
1189            );
1190
1191            testing::exercise_omap_counter_table(16_000, &mut omap, &mut rng);
1192        });
1193    }
1194
1195    // Load the omap to 70% capacity in a map with 16384 items
1196    #[test]
1197    #[cfg(not(debug_assertions))]
1198    fn omap_70_capacity_two_choice_path_oram_z4_16384() {
1199        use typenum::U248;
1200        run_with_several_seeds(|rng| {
1201            // This should be ~4096 underlying buckets
1202            let mut omap = <CuckooCreatorZ4 as OMapCreator<U8, U248, RngType>>::create(
1203                16384,
1204                STASH_SIZE,
1205                rng_maker(rng),
1206            );
1207
1208            let mut temp = A8Bytes::<U8>::default();
1209            let val = A8Bytes::<U248>::default();
1210            for idx in 1u64..(omap.capacity() * 7 / 10) {
1211                temp.copy_from_slice(&idx.to_le_bytes());
1212                let result_code = omap.vartime_write(&temp, &val, 0.into());
1213                assert!(OMAP_OVERFLOW != result_code);
1214                assert!(OMAP_NOT_FOUND == result_code);
1215                assert_eq!(omap.len(), idx);
1216            }
1217        });
1218    }
1219
1220    // Test that the omap has correct roll-back semantics around overflow, for 16384
1221    // items To see ratio at which hashmap overflow begins, run
1222    // cargo test --release -p mc-oblivious-map -- overflow --nocapture
1223    #[test]
1224    #[cfg(not(debug_assertions))]
1225    fn omap_overflow_semantics_two_choice_path_oram_z4_16384() {
1226        use std::println;
1227        use typenum::U248;
1228        run_with_several_seeds(|rng| {
1229            // This shoudl be ~4096 underlying buckets
1230            let mut omap = <CuckooCreatorZ4 as OMapCreator<U8, U248, RngType>>::create(
1231                16384,
1232                STASH_SIZE,
1233                rng_maker(rng),
1234            );
1235
1236            let len = testing::test_omap_overflow(&mut omap);
1237            let cap = omap.capacity();
1238            let fraction = (len as f32) * 100f32 / (cap as f32);
1239            println!("Overflowed at {} / {} = {}%", len, cap, fraction);
1240        })
1241    }
1242
1243    // Test that the omap has correct roll-back semantics around overflow, for 32768
1244    // items To see ratio at which hashmap overflow begins, run
1245    // cargo test --release -p mc-oblivious-map -- overflow --nocapture
1246    #[test]
1247    #[cfg(not(debug_assertions))]
1248    fn omap_overflow_semantics_two_choice_path_oram_z4_32768() {
1249        use std::println;
1250        use typenum::U248;
1251        run_with_several_seeds(|rng| {
1252            // This should be ~8192 underlying buckets
1253            let mut omap = <CuckooCreatorZ4 as OMapCreator<U8, U248, RngType>>::create(
1254                32768,
1255                STASH_SIZE,
1256                rng_maker(rng),
1257            );
1258
1259            let len = testing::test_omap_overflow(&mut omap);
1260            let cap = omap.capacity();
1261            let fraction = (len as f32) * 100f32 / (cap as f32);
1262            println!("Overflowed at {} / {} = {}%", len, cap, fraction);
1263        })
1264    }
1265
1266    // Test that the omap has correct roll-back semantics around overflow, for 65536
1267    // items To see ratio at which hashmap overflow begins, run
1268    // cargo test --release -p mc-oblivious-map -- overflow --nocapture
1269    #[test]
1270    #[cfg(not(debug_assertions))]
1271    fn omap_overflow_semantics_two_choice_path_oram_z4_65536() {
1272        use std::println;
1273        use typenum::U248;
1274        let large_stash_size = STASH_SIZE * 2;
1275        run_with_several_seeds(|rng| {
1276            // This should be ~16384 underlying buckets
1277            let mut omap = <CuckooCreatorZ4 as OMapCreator<U8, U248, RngType>>::create(
1278                65536,
1279                large_stash_size,
1280                rng_maker(rng),
1281            );
1282
1283            let len = testing::test_omap_overflow(&mut omap);
1284            let cap = omap.capacity();
1285            let fraction = (len as f32) * 100f32 / (cap as f32);
1286            println!("Overflowed at {} / {} = {}%", len, cap, fraction);
1287        })
1288    }
1289}