Parcourir la source

initial commit

avadapal il y a 1 an
Parent
commit
17a7df0033

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+*.pdb

+ 23 - 0
Cargo.toml

@@ -0,0 +1,23 @@
+[workspace]
+members = [
+    "communicator",
+    "dpf",
+    "oram",
+    "utils",
+]
+
+[workspace.package]
+version = "0.1.0"
+authors = ["Lennart Braun"]
+description = "Implementation of the Ramen three-party distributed oblivious RAM protocol"
+license = "MIT"
+readme = "README"
+repository = "https://github.com/AarhusCrypto/Ramen/"
+
+[profile.bench]
+debug = 2
+
+[profile.release]
+lto = "fat"
+codegen-units = 1
+panic = "abort"

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Lennart Braun
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 0 - 2
README.md

@@ -1,2 +0,0 @@
-# ramen-dockerization
-

+ 18 - 0
communicator/Cargo.toml

@@ -0,0 +1,18 @@
+[package]
+name = "communicator"
+version = "0.1.0"
+edition = "2021"
+description = "simple multi-party communication layer"
+authors.workspace = true
+license.workspace = true
+readme.workspace = true
+repository.workspace = true
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+bincode = "2.0.0-rc.2"
+serde = { version = "1.0", features = ["derive"] }
+
+[dev-dependencies]
+rand = "0.8.5"

+ 293 - 0
communicator/src/communicator.rs

@@ -0,0 +1,293 @@
+use crate::{AbstractCommunicator, CommunicationStats, Error, Fut, Serializable};
+use std::collections::HashMap;
+use std::fmt::Debug;
+use std::io::{BufReader, BufWriter, Read, Write};
+use std::marker::PhantomData;
+use std::sync::mpsc::{channel, Receiver, Sender};
+use std::sync::{Arc, Mutex};
+use std::thread;
+
+/// Future type used by [`Communicator`].
+pub struct MyFut<T: Serializable> {
+    buf_rx: Arc<Mutex<Receiver<Vec<u8>>>>,
+    _phantom: PhantomData<T>,
+}
+
+impl<T: Serializable> MyFut<T> {
+    fn new(buf_rx: Arc<Mutex<Receiver<Vec<u8>>>>) -> Self {
+        Self {
+            buf_rx,
+            _phantom: PhantomData,
+        }
+    }
+}
+
+impl<T: Serializable> Fut<T> for MyFut<T> {
+    fn get(self) -> Result<T, Error> {
+        let buf = self.buf_rx.lock().unwrap().recv()?;
+        let (data, size) = bincode::decode_from_slice(
+            &buf,
+            bincode::config::standard().skip_fixed_array_length(),
+        )?;
+        assert_eq!(size, buf.len());
+        Ok(data)
+    }
+}
+
+/// Thread to receive messages in the background.
+#[derive(Debug)]
+struct ReceiverThread {
+    buf_rx: Arc<Mutex<Receiver<Vec<u8>>>>,
+    join_handle: thread::JoinHandle<Result<(), Error>>,
+    stats: Arc<Mutex<[usize; 2]>>,
+}
+
+impl ReceiverThread {
+    pub fn from_reader<R: Debug + Read + Send + 'static>(reader: R) -> Self {
+        let mut reader = BufReader::with_capacity(1 << 16, reader);
+        let (buf_tx, buf_rx) = channel::<Vec<u8>>();
+        let buf_rx = Arc::new(Mutex::new(buf_rx));
+        let stats = Arc::new(Mutex::new([0usize; 2]));
+        let stats_clone = stats.clone();
+        let join_handle = thread::Builder::new()
+            .name("Receiver".to_owned())
+            .spawn(move || {
+                loop {
+                    let mut msg_size = [0u8; 4];
+                    reader.read_exact(&mut msg_size)?;
+                    let msg_size = u32::from_be_bytes(msg_size) as usize;
+                    if msg_size == 0xffffffff {
+                        return Ok(());
+                    }
+                    let mut buf = vec![0u8; msg_size];
+                    reader.read_exact(&mut buf)?;
+                    match buf_tx.send(buf) {
+                        Ok(_) => (),
+                        Err(_) => return Ok(()), // we need to shutdown
+                    }
+                    {
+                        let mut guard = stats.lock().unwrap();
+                        guard[0] += 1;
+                        guard[1] += 4 + msg_size;
+                    }
+                }
+            })
+            .unwrap();
+        Self {
+            join_handle,
+            buf_rx,
+            stats: stats_clone,
+        }
+    }
+
+    pub fn receive<T: Serializable>(&mut self) -> Result<MyFut<T>, Error> {
+        Ok(MyFut::new(self.buf_rx.clone()))
+    }
+
+    pub fn join(self) -> Result<(), Error> {
+        drop(self.buf_rx);
+        self.join_handle.join().expect("join failed")
+    }
+
+    pub fn get_stats(&self) -> [usize; 2] {
+        *self.stats.lock().unwrap()
+    }
+
+    pub fn reset_stats(&mut self) {
+        *self.stats.lock().unwrap() = [0usize; 2];
+    }
+}
+
+/// Thread to send messages in the background.
+#[derive(Debug)]
+struct SenderThread {
+    buf_tx: Sender<Vec<u8>>,
+    join_handle: thread::JoinHandle<Result<(), Error>>,
+}
+
+impl SenderThread {
+    pub fn from_writer<W: Debug + Write + Send + 'static>(writer: W) -> Self {
+        let mut writer = BufWriter::with_capacity(1 << 16, writer);
+        let (buf_tx, buf_rx) = channel::<Vec<u8>>();
+        let join_handle = thread::Builder::new()
+            .name("Sender-1".to_owned())
+            .spawn(move || {
+                for buf in buf_rx.iter() {
+                    writer.write_all(&((buf.len() as u32).to_be_bytes()))?;
+                    debug_assert!(buf.len() <= u32::MAX as usize);
+                    writer.write_all(&buf)?;
+                    writer.flush()?;
+                }
+                writer.write_all(&[0xff, 0xff, 0xff, 0xff])?;
+                writer.flush()?;
+                Ok(())
+            })
+            .unwrap();
+        Self {
+            buf_tx,
+            join_handle,
+        }
+    }
+
+    pub fn send<T: Serializable>(&mut self, data: T) -> Result<usize, Error> {
+        let buf =
+            bincode::encode_to_vec(data, bincode::config::standard().skip_fixed_array_length())?;
+        let num_bytes = 4 + buf.len();
+        self.buf_tx.send(buf)?;
+        Ok(num_bytes)
+    }
+
+    pub fn send_slice<T: Serializable>(&mut self, data: &[T]) -> Result<usize, Error> {
+        let buf =
+            bincode::encode_to_vec(data, bincode::config::standard().skip_fixed_array_length())?;
+        let num_bytes = 4 + buf.len();
+        self.buf_tx.send(buf)?;
+        Ok(num_bytes)
+    }
+
+    pub fn join(self) -> Result<(), Error> {
+        drop(self.buf_tx);
+        self.join_handle.join().expect("join failed")
+    }
+}
+
+/// Implementation of [`AbstractCommunicator`] that uses background threads to send and receive
+/// messages.
+#[derive(Debug)]
+pub struct Communicator {
+    num_parties: usize,
+    my_id: usize,
+    comm_stats: HashMap<usize, CommunicationStats>,
+    receiver_threads: HashMap<usize, ReceiverThread>,
+    sender_threads: HashMap<usize, SenderThread>,
+}
+
+impl Communicator {
+    /// Create a new Communicator from a collection of readers and writers that are connected with
+    /// the other parties.
+    pub fn from_reader_writer<
+        R: Read + Send + Debug + 'static,
+        W: Send + Write + Debug + 'static,
+    >(
+        num_parties: usize,
+        my_id: usize,
+        mut rw_map: HashMap<usize, (R, W)>,
+    ) -> Self {
+        assert_eq!(rw_map.len(), num_parties - 1);
+        assert!((0..num_parties)
+            .filter(|&pid| pid != my_id)
+            .all(|pid| rw_map.contains_key(&pid)));
+
+        let mut receiver_threads = HashMap::with_capacity(num_parties - 1);
+        let mut sender_threads = HashMap::with_capacity(num_parties - 1);
+
+        for pid in 0..num_parties {
+            if pid == my_id {
+                continue;
+            }
+            let (reader, writer) = rw_map.remove(&pid).unwrap();
+            receiver_threads.insert(pid, ReceiverThread::from_reader(reader));
+            sender_threads.insert(pid, SenderThread::from_writer(writer));
+        }
+
+        let comm_stats = (0..num_parties)
+            .filter_map(|party_id| {
+                if party_id == my_id {
+                    None
+                } else {
+                    Some((party_id, Default::default()))
+                }
+            })
+            .collect();
+
+        Self {
+            num_parties,
+            my_id,
+            comm_stats,
+            receiver_threads,
+            sender_threads,
+        }
+    }
+}
+
+impl AbstractCommunicator for Communicator {
+    type Fut<T: Serializable> = MyFut<T>;
+
+    fn get_num_parties(&self) -> usize {
+        self.num_parties
+    }
+
+    fn get_my_id(&self) -> usize {
+        self.my_id
+    }
+
+    fn send<T: Serializable>(&mut self, party_id: usize, val: T) -> Result<(), Error> {
+        match self.sender_threads.get_mut(&party_id) {
+            Some(t) => {
+                let num_bytes = t.send(val)?;
+                let cs = self.comm_stats.get_mut(&party_id).unwrap();
+                cs.num_bytes_sent += num_bytes;
+                cs.num_msgs_sent += 1;
+                Ok(())
+            }
+            None => Err(Error::LogicError(format!(
+                "SenderThread for party {party_id} not found"
+            ))),
+        }
+    }
+
+    fn send_slice<T: Serializable>(&mut self, party_id: usize, val: &[T]) -> Result<(), Error> {
+        match self.sender_threads.get_mut(&party_id) {
+            Some(t) => {
+                let num_bytes = t.send_slice(val)?;
+                let cs = self.comm_stats.get_mut(&party_id).unwrap();
+                cs.num_bytes_sent += num_bytes;
+                cs.num_msgs_sent += 1;
+                Ok(())
+            }
+            None => Err(Error::LogicError(format!(
+                "SenderThread for party {party_id} not found"
+            ))),
+        }
+    }
+
+    fn receive<T: Serializable>(&mut self, party_id: usize) -> Result<Self::Fut<T>, Error> {
+        match self.receiver_threads.get_mut(&party_id) {
+            Some(t) => t.receive::<T>(),
+            None => Err(Error::LogicError(format!(
+                "ReceiverThread for party {party_id} not found"
+            ))),
+        }
+    }
+
+    fn shutdown(&mut self) {
+        self.sender_threads.drain().for_each(|(party_id, t)| {
+            t.join()
+                .unwrap_or_else(|_| panic!("join of sender thread {party_id} failed"))
+        });
+        self.receiver_threads.drain().for_each(|(party_id, t)| {
+            t.join()
+                .unwrap_or_else(|_| panic!("join of receiver thread {party_id} failed"))
+        });
+    }
+
+    fn get_stats(&self) -> HashMap<usize, CommunicationStats> {
+        let mut cs = self.comm_stats.clone();
+        self.receiver_threads.iter().for_each(|(party_id, t)| {
+            let [num_msgs_received, num_bytes_received] = t.get_stats();
+            let cs_i = cs.get_mut(party_id).unwrap();
+            cs_i.num_msgs_received = num_msgs_received;
+            cs_i.num_bytes_received = num_bytes_received;
+        });
+        cs
+    }
+
+    fn reset_stats(&mut self) {
+        self.comm_stats
+            .iter_mut()
+            .for_each(|(_, cs)| *cs = Default::default());
+        self.receiver_threads
+            .iter_mut()
+            .for_each(|(_, t)| t.reset_stats());
+    }
+}

+ 179 - 0
communicator/src/lib.rs

@@ -0,0 +1,179 @@
+//! Simple communication layer for passing messages among multiple parties.
+
+#![warn(missing_docs)]
+
+mod communicator;
+pub mod tcp;
+pub mod unix;
+
+pub use crate::communicator::{Communicator, MyFut};
+use bincode::error::{DecodeError, EncodeError};
+use std::collections::HashMap;
+use std::io::Error as IoError;
+use std::sync::mpsc::{RecvError, SendError};
+
+/// Trait that captures the requirements for data types to be sent/received.
+pub trait Serializable: Clone + Send + 'static + bincode::Encode + bincode::Decode {}
+
+impl<T> Serializable for T where T: Clone + Send + 'static + bincode::Encode + bincode::Decode {}
+
+/// C++-style Future type. Represents data of type T that we expect to receive.
+pub trait Fut<T> {
+    /// Wait until the data has arrived and obtain it.
+    fn get(self) -> Result<T, Error>;
+}
+
+/// Recorded communication statistics for one point-to-point channel.
+#[derive(Debug, Default, Clone, Copy, serde::Serialize)]
+pub struct CommunicationStats {
+    /// Number of messages received.
+    pub num_msgs_received: usize,
+
+    /// Number of bytes received over all messages.
+    pub num_bytes_received: usize,
+
+    /// Number of messages sent.
+    pub num_msgs_sent: usize,
+
+    /// Number of bytes sent over all messages.
+    pub num_bytes_sent: usize,
+}
+
+/// Abstract communication interface between multiple parties
+pub trait AbstractCommunicator {
+    /// Future type to represent expected data.
+    type Fut<T: Serializable>: Fut<T>;
+
+    /// How many parties N there are in total.
+    fn get_num_parties(&self) -> usize;
+
+    /// My party id in [0, N).
+    fn get_my_id(&self) -> usize;
+
+    /// Send a message of type T to given party.
+    fn send<T: Serializable>(&mut self, party_id: usize, val: T) -> Result<(), Error>;
+
+    /// Send a message of multiple elements of type T to given party.
+    fn send_slice<T: Serializable>(&mut self, party_id: usize, val: &[T]) -> Result<(), Error>;
+
+    /// Send a message of type T to next party.
+    fn send_next<T: Serializable>(&mut self, val: T) -> Result<(), Error> {
+        self.send((self.get_my_id() + 1) % self.get_num_parties(), val)
+    }
+
+    /// Send a message of multiple elements of type T to next party.
+    fn send_slice_next<T: Serializable>(&mut self, val: &[T]) -> Result<(), Error> {
+        self.send_slice((self.get_my_id() + 1) % self.get_num_parties(), val)
+    }
+
+    /// Send a message of type T to previous party.
+    fn send_previous<T: Serializable>(&mut self, val: T) -> Result<(), Error> {
+        self.send(
+            (self.get_num_parties() + self.get_my_id() - 1) % self.get_num_parties(),
+            val,
+        )
+    }
+
+    /// Send a message of multiple elements of type T to previous party.
+    fn send_slice_previous<T: Serializable>(&mut self, val: &[T]) -> Result<(), Error> {
+        self.send_slice(
+            (self.get_num_parties() + self.get_my_id() - 1) % self.get_num_parties(),
+            val,
+        )
+    }
+
+    /// Send a message of type T all parties.
+    fn broadcast<T: Serializable>(&mut self, val: T) -> Result<(), Error> {
+        let my_id = self.get_my_id();
+        for party_id in 0..self.get_num_parties() {
+            if party_id == my_id {
+                continue;
+            }
+            self.send(party_id, val.clone())?;
+        }
+        Ok(())
+    }
+
+    /// Expect to receive message of type T from given party.  Use the returned future to obtain
+    /// the message once it has arrived.
+    fn receive<T: Serializable>(&mut self, party_id: usize) -> Result<Self::Fut<T>, Error>;
+
+    /// Expect to receive message of type T from the next party.  Use the returned future to obtain
+    /// the message once it has arrived.
+    fn receive_next<T: Serializable>(&mut self) -> Result<Self::Fut<T>, Error> {
+        self.receive((self.get_my_id() + 1) % self.get_num_parties())
+    }
+
+    /// Expect to receive message of type T from the previous party.  Use the returned future to obtain
+    /// the message once it has arrived.
+    fn receive_previous<T: Serializable>(&mut self) -> Result<Self::Fut<T>, Error> {
+        self.receive((self.get_num_parties() + self.get_my_id() - 1) % self.get_num_parties())
+    }
+
+    /// Shutdown the communication system.
+    fn shutdown(&mut self);
+
+    /// Obtain statistics about how many messages/bytes were send/received.
+    fn get_stats(&self) -> HashMap<usize, CommunicationStats>;
+
+    /// Reset statistics.
+    fn reset_stats(&mut self);
+}
+
+/// Custom error type.
+#[derive(Debug)]
+pub enum Error {
+    /// The connection has not been established.
+    ConnectionSetupError,
+    /// The API was not used correctly.
+    LogicError(String),
+    /// Some std::io::Error appeared.
+    IoError(IoError),
+    /// Some std::sync::mpsc::RecvError appeared.
+    RecvError(RecvError),
+    /// Some std::sync::mpsc::SendError appeared.
+    SendError(String),
+    /// Some bincode::error::DecodeError appeared.
+    EncodeError(EncodeError),
+    /// Some bincode::error::DecodeError appeared.
+    DecodeError(DecodeError),
+    /// Serialization of data failed.
+    SerializationError(String),
+    /// Deserialization of data failed.
+    DeserializationError(String),
+}
+
+/// Enable automatic conversions from std::io::Error.
+impl From<IoError> for Error {
+    fn from(e: IoError) -> Error {
+        Error::IoError(e)
+    }
+}
+
+/// Enable automatic conversions from std::sync::mpsc::RecvError.
+impl From<RecvError> for Error {
+    fn from(e: RecvError) -> Error {
+        Error::RecvError(e)
+    }
+}
+
+/// Enable automatic conversions from std::sync::mpsc::SendError.
+impl<T> From<SendError<T>> for Error {
+    fn from(e: SendError<T>) -> Error {
+        Error::SendError(e.to_string())
+    }
+}
+
+/// Enable automatic conversions from bincode::error::EncodeError.
+impl From<EncodeError> for Error {
+    fn from(e: EncodeError) -> Error {
+        Error::EncodeError(e)
+    }
+}
+
+/// Enable automatic conversions from bincode::error::DecodeError.
+impl From<DecodeError> for Error {
+    fn from(e: DecodeError) -> Error {
+        Error::DecodeError(e)
+    }
+}

+ 321 - 0
communicator/src/tcp.rs

@@ -0,0 +1,321 @@
+//! Functionality for communicators using TCP sockets.
+
+use crate::Communicator;
+use crate::{AbstractCommunicator, Error};
+use std::collections::{HashMap, HashSet};
+use std::io::{Read, Write};
+use std::net::{TcpListener, TcpStream};
+use std::thread;
+use std::time::Duration;
+
+/// Network connection options for a single party: Either we listen for an incoming connection, or
+/// we connect to a given host and port.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum NetworkPartyInfo {
+    /// Listen for the other party to connect.
+    Listen,
+    /// Connect to the other party at the given host and port.
+    Connect(String, u16),
+}
+
+/// Network connection options
+#[derive(Debug, Clone)]
+pub struct NetworkOptions {
+    /// Which address to listen on for incoming connections
+    pub listen_host: String,
+    /// Which port to listen on for incoming connections
+    pub listen_port: u16,
+    /// Connection info for each party
+    pub connect_info: Vec<NetworkPartyInfo>,
+    /// How long to try connecting before aborting
+    pub connect_timeout_seconds: usize,
+}
+
+fn tcp_connect(
+    my_id: usize,
+    other_id: usize,
+    host: &str,
+    port: u16,
+    timeout_seconds: usize,
+) -> Result<TcpStream, Error> {
+    // repeatedly try to connect
+    fn connect_socket(host: &str, port: u16, timeout_seconds: usize) -> Result<TcpStream, Error> {
+        // try every 100ms
+        for _ in 0..(10 * timeout_seconds) {
+            if let Ok(socket) = TcpStream::connect((host, port)) {
+                return Ok(socket);
+            }
+            thread::sleep(Duration::from_millis(100));
+        }
+        match TcpStream::connect((host, port)) {
+            Ok(socket) => Ok(socket),
+            Err(e) => Err(Error::IoError(e)),
+        }
+    }
+    // connect to the other party
+    let mut stream = connect_socket(host, port, timeout_seconds)?;
+    {
+        // send our party id
+        let bytes_written = stream.write(&(my_id as u32).to_be_bytes())?;
+        if bytes_written != 4 {
+            return Err(Error::ConnectionSetupError);
+        }
+        // check that we talk to the right party
+        let mut other_id_bytes = [0u8; 4];
+        stream.read_exact(&mut other_id_bytes)?;
+        if u32::from_be_bytes(other_id_bytes) != other_id as u32 {
+            return Err(Error::ConnectionSetupError);
+        }
+    }
+    Ok(stream)
+}
+
+fn tcp_accept_connections(
+    my_id: usize,
+    options: &NetworkOptions,
+) -> Result<HashMap<usize, TcpStream>, Error> {
+    // prepare function output
+    let mut output = HashMap::<usize, TcpStream>::new();
+    // compute set of parties that should connect to us
+    let mut expected_parties: HashSet<usize> = options
+        .connect_info
+        .iter()
+        .enumerate()
+        .filter_map(|(party_id, npi)| {
+            if party_id != my_id && *npi == NetworkPartyInfo::Listen {
+                Some(party_id)
+            } else {
+                None
+            }
+        })
+        .collect();
+    // if nobody should connect to us, we are done
+    if expected_parties.is_empty() {
+        return Ok(output);
+    }
+    // create a listender and iterate over incoming connections
+    let listener = TcpListener::bind((options.listen_host.clone(), options.listen_port))?;
+    for mut stream in listener.incoming().filter_map(Result::ok) {
+        // see which party has connected
+        let mut other_id_bytes = [0u8; 4];
+        if stream.read_exact(&mut other_id_bytes).is_err() {
+            continue;
+        }
+        let other_id = u32::from_be_bytes(other_id_bytes) as usize;
+        // check if we expect this party
+        if !expected_parties.contains(&other_id) {
+            continue;
+        }
+        // respond with our party id
+        if stream.write_all(&(my_id as u32).to_be_bytes()).is_err() {
+            continue;
+        }
+        // connection has been established
+        expected_parties.remove(&other_id);
+        output.insert(other_id, stream);
+        // check if we have received connections from every party
+        if expected_parties.is_empty() {
+            break;
+        }
+    }
+    if !expected_parties.is_empty() {
+        Err(Error::ConnectionSetupError)
+    } else {
+        Ok(output)
+    }
+}
+
+/// Setup TCP connections
+pub fn setup_connection(
+    num_parties: usize,
+    my_id: usize,
+    options: &NetworkOptions,
+) -> Result<HashMap<usize, TcpStream>, Error> {
+    // make a copy of the options to pass it into the new thread
+    let options_cpy: NetworkOptions = (*options).clone();
+
+    // spawn thread to listen for incoming connections
+    let listen_thread_handle = thread::spawn(move || tcp_accept_connections(my_id, &options_cpy));
+
+    // prepare the map of connection we will return
+    let mut output = HashMap::with_capacity(num_parties - 1);
+
+    // connect to all parties that we are supposed to connect to
+    for (party_id, info) in options.connect_info.iter().enumerate() {
+        if party_id == my_id {
+            continue;
+        }
+        match info {
+            NetworkPartyInfo::Listen => {}
+            NetworkPartyInfo::Connect(host, port) => {
+                output.insert(
+                    party_id,
+                    tcp_connect(
+                        my_id,
+                        party_id,
+                        host,
+                        *port,
+                        options.connect_timeout_seconds,
+                    )?,
+                );
+            }
+        }
+    }
+
+    // join the listen thread and obtain the connections that reached us
+    let accepted_connections = match listen_thread_handle.join() {
+        Ok(accepted_connections) => accepted_connections,
+        Err(_) => return Err(Error::ConnectionSetupError),
+    }?;
+
+    // return the union of both maps
+    output.extend(accepted_connections);
+    Ok(output)
+}
+
+/// Create communicator using TCP connections
+pub fn make_tcp_communicator(
+    num_parties: usize,
+    my_id: usize,
+    options: &NetworkOptions,
+) -> Result<impl AbstractCommunicator, Error> {
+    // create connections with other parties
+    let stream_map = setup_connection(num_parties, my_id, options)?;
+    stream_map
+        .iter()
+        .for_each(|(_, s)| s.set_nodelay(true).expect("set_nodelay failed"));
+    // use streams as reader/writer pairs
+    let rw_map = stream_map
+        .into_iter()
+        .map(|(party_id, stream)| (party_id, (stream.try_clone().unwrap(), stream)))
+        .collect();
+    // create new communicator
+    Ok(Communicator::from_reader_writer(num_parties, my_id, rw_map))
+}
+
+/// Create communicator using TCP connections via localhost
+pub fn make_local_tcp_communicators(num_parties: usize) -> Vec<impl AbstractCommunicator> {
+    let ports: [u16; 3] = [20_000, 20_001, 20_002];
+    let opts: Vec<_> = (0..num_parties)
+        .map(|party_id| NetworkOptions {
+            listen_host: "localhost".to_owned(),
+            listen_port: ports[party_id],
+            connect_info: (0..num_parties)
+                .map(|other_id| {
+                    if other_id < party_id {
+                        NetworkPartyInfo::Connect("localhost".to_owned(), ports[other_id])
+                    } else {
+                        NetworkPartyInfo::Listen
+                    }
+                })
+                .collect(),
+            connect_timeout_seconds: 3,
+        })
+        .collect();
+
+    let communicators: Vec<_> = opts
+        .iter()
+        .enumerate()
+        .map(|(party_id, opts)| {
+            let opts_cpy = (*opts).clone();
+            thread::spawn(move || make_tcp_communicator(num_parties, party_id, &opts_cpy))
+        })
+        .collect();
+    communicators
+        .into_iter()
+        .map(|h| h.join().unwrap().unwrap())
+        .collect()
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::Fut;
+    use std::thread;
+
+    #[test]
+    fn test_tcp_communicators() {
+        let num_parties = 3;
+        let msg_0: u8 = 42;
+        let msg_1: u32 = 0x_dead_beef;
+        let msg_2: [u32; 2] = [0x_1333_3337, 0x_c0ff_ffee];
+
+        let ports: [u16; 3] = [20_000, 20_001, 20_002];
+
+        let opts: Vec<_> = (0..num_parties)
+            .map(|party_id| NetworkOptions {
+                listen_host: "localhost".to_owned(),
+                listen_port: ports[party_id],
+                connect_info: (0..num_parties)
+                    .map(|other_id| {
+                        if other_id < party_id {
+                            NetworkPartyInfo::Connect("localhost".to_owned(), ports[other_id])
+                        } else {
+                            NetworkPartyInfo::Listen
+                        }
+                    })
+                    .collect(),
+                connect_timeout_seconds: 3,
+            })
+            .collect();
+
+        let communicators: Vec<_> = opts
+            .iter()
+            .enumerate()
+            .map(|(party_id, opts)| {
+                let opts_cpy = (*opts).clone();
+                thread::spawn(move || make_tcp_communicator(num_parties, party_id, &opts_cpy))
+            })
+            .collect();
+        let communicators: Vec<_> = communicators
+            .into_iter()
+            .map(|h| h.join().unwrap().unwrap())
+            .collect();
+
+        let thread_handles: Vec<_> = communicators
+            .into_iter()
+            .enumerate()
+            .map(|(party_id, mut communicator)| {
+                thread::spawn(move || {
+                    if party_id == 0 {
+                        let fut_1 = communicator.receive::<u32>(1).unwrap();
+                        let fut_2 = communicator.receive::<[u32; 2]>(2).unwrap();
+                        communicator.send(1, msg_0).unwrap();
+                        communicator.send(2, msg_0).unwrap();
+                        let val_1 = fut_1.get();
+                        let val_2 = fut_2.get();
+                        assert!(val_1.is_ok());
+                        assert!(val_2.is_ok());
+                        assert_eq!(val_1.unwrap(), msg_1);
+                        assert_eq!(val_2.unwrap(), msg_2);
+                    } else if party_id == 1 {
+                        let fut_0 = communicator.receive::<u8>(0).unwrap();
+                        let fut_2 = communicator.receive::<[u32; 2]>(2).unwrap();
+                        communicator.send(0, msg_1).unwrap();
+                        communicator.send(2, msg_1).unwrap();
+                        let val_0 = fut_0.get();
+                        let val_2 = fut_2.get();
+                        assert!(val_0.is_ok());
+                        assert!(val_2.is_ok());
+                        assert_eq!(val_0.unwrap(), msg_0);
+                        assert_eq!(val_2.unwrap(), msg_2);
+                    } else if party_id == 2 {
+                        let fut_0 = communicator.receive::<u8>(0).unwrap();
+                        let fut_1 = communicator.receive::<u32>(1).unwrap();
+                        communicator.send(0, msg_2).unwrap();
+                        communicator.send(1, msg_2).unwrap();
+                        let val_0 = fut_0.get();
+                        let val_1 = fut_1.get();
+                        assert!(val_0.is_ok());
+                        assert!(val_1.is_ok());
+                        assert_eq!(val_0.unwrap(), msg_0);
+                        assert_eq!(val_1.unwrap(), msg_1);
+                    }
+                    communicator.shutdown();
+                })
+            })
+            .collect();
+
+        thread_handles.into_iter().for_each(|h| h.join().unwrap());
+    }
+}

+ 91 - 0
communicator/src/unix.rs

@@ -0,0 +1,91 @@
+//! Functionality for communicators using Unix sockets.
+
+use crate::AbstractCommunicator;
+use crate::Communicator;
+use std::collections::HashMap;
+use std::os::unix::net::UnixStream;
+
+/// Create a set of connected Communicators that are based on local Unix sockets
+pub fn make_unix_communicators(num_parties: usize) -> Vec<impl AbstractCommunicator> {
+    // prepare maps for each parties to store readers and writers to every other party
+    let mut rw_maps: Vec<_> = (0..num_parties)
+        .map(|_| HashMap::with_capacity(num_parties - 1))
+        .collect();
+    // create pairs of unix sockets connecting each pair of parties
+    for party_i in 0..num_parties {
+        for party_j in 0..party_i {
+            let (stream_i_to_j, stream_j_to_i) = UnixStream::pair().unwrap();
+            rw_maps[party_i].insert(party_j, (stream_i_to_j.try_clone().unwrap(), stream_i_to_j));
+            rw_maps[party_j].insert(party_i, (stream_j_to_i.try_clone().unwrap(), stream_j_to_i));
+        }
+    }
+    // create communicators from the reader/writer maps
+    rw_maps
+        .into_iter()
+        .enumerate()
+        .map(|(party_id, rw_map)| Communicator::from_reader_writer(num_parties, party_id, rw_map))
+        .collect()
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::Fut;
+    use std::thread;
+
+    #[test]
+    fn test_unix_communicators() {
+        let num_parties = 3;
+        let msg_0: u8 = 42;
+        let msg_1: u32 = 0x_dead_beef;
+        let msg_2: [u32; 2] = [0x_1333_3337, 0x_c0ff_ffee];
+
+        let communicators = make_unix_communicators(num_parties);
+
+        let thread_handles: Vec<_> = communicators
+            .into_iter()
+            .enumerate()
+            .map(|(party_id, mut communicator)| {
+                thread::spawn(move || {
+                    if party_id == 0 {
+                        let fut_1 = communicator.receive::<u32>(1).unwrap();
+                        let fut_2 = communicator.receive::<[u32; 2]>(2).unwrap();
+                        communicator.send(1, msg_0).unwrap();
+                        communicator.send(2, msg_0).unwrap();
+                        let val_1 = fut_1.get();
+                        let val_2 = fut_2.get();
+                        assert!(val_1.is_ok());
+                        assert!(val_2.is_ok());
+                        assert_eq!(val_1.unwrap(), msg_1);
+                        assert_eq!(val_2.unwrap(), msg_2);
+                    } else if party_id == 1 {
+                        let fut_0 = communicator.receive::<u8>(0).unwrap();
+                        let fut_2 = communicator.receive::<[u32; 2]>(2).unwrap();
+                        communicator.send(0, msg_1).unwrap();
+                        communicator.send(2, msg_1).unwrap();
+                        let val_0 = fut_0.get();
+                        let val_2 = fut_2.get();
+                        assert!(val_0.is_ok());
+                        assert!(val_2.is_ok());
+                        assert_eq!(val_0.unwrap(), msg_0);
+                        assert_eq!(val_2.unwrap(), msg_2);
+                    } else if party_id == 2 {
+                        let fut_0 = communicator.receive::<u8>(0).unwrap();
+                        let fut_1 = communicator.receive::<u32>(1).unwrap();
+                        communicator.send(0, msg_2).unwrap();
+                        communicator.send(1, msg_2).unwrap();
+                        let val_0 = fut_0.get();
+                        let val_1 = fut_1.get();
+                        assert!(val_0.is_ok());
+                        assert!(val_1.is_ok());
+                        assert_eq!(val_0.unwrap(), msg_0);
+                        assert_eq!(val_1.unwrap(), msg_1);
+                    }
+                    communicator.shutdown();
+                })
+            })
+            .collect();
+
+        thread_handles.into_iter().for_each(|h| h.join().unwrap());
+    }
+}

+ 29 - 0
dpf/Cargo.toml

@@ -0,0 +1,29 @@
+[package]
+name = "dpf"
+version = "0.1.0"
+edition = "2021"
+description = "single- and multi-point distributed point functions"
+authors.workspace = true
+license.workspace = true
+readme.workspace = true
+repository.workspace = true
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+utils = { path = "../utils" }
+bincode = "2.0.0-rc.2"
+num = "0.4.0"
+rand = "0.8.5"
+rayon = "1.6.1"
+
+[dev-dependencies]
+criterion = "0.4.0"
+
+[[bench]]
+name = "spdpf"
+harness = false
+
+[[bench]]
+name = "mpdpf"
+harness = false

+ 127 - 0
dpf/benches/mpdpf.rs

@@ -0,0 +1,127 @@
+use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
+use dpf::mpdpf::{DummyMpDpf, MultiPointDpf, SmartMpDpf};
+use dpf::spdpf::{DummySpDpf, HalfTreeSpDpf};
+use rand::{thread_rng, Rng};
+use utils::field::{Fp, FromHash};
+use utils::hash::AesHashFunction;
+
+const LOG_DOMAIN_SIZES: [u32; 4] = [8, 12, 16, 20];
+
+fn setup_points<F: FromHash>(log_domain_size: u32) -> (Vec<u64>, Vec<F>) {
+    assert_eq!(log_domain_size % 2, 0);
+    let domain_size = 1 << log_domain_size;
+    let number_points = 1 << (log_domain_size / 2);
+    let alphas = {
+        let mut alphas = Vec::<u64>::with_capacity(number_points);
+        while alphas.len() < number_points {
+            let x = thread_rng().gen_range(0..domain_size);
+            match alphas.as_slice().binary_search(&x) {
+                Ok(_) => continue,
+                Err(i) => alphas.insert(i, x),
+            }
+        }
+        alphas
+    };
+    let betas: Vec<F> = (0..number_points)
+        .map(|x| F::hash_bytes(&x.to_be_bytes()))
+        .collect();
+    (alphas, betas)
+}
+
+fn bench_mpdpf_keygen<MPDPF, F>(c: &mut Criterion, dpf_name: &str, field_name: &str)
+where
+    MPDPF: MultiPointDpf<Value = F>,
+    F: Copy + FromHash,
+{
+    let mut group = c.benchmark_group(format!("{}-{}-keygen", dpf_name, field_name));
+    for log_domain_size in LOG_DOMAIN_SIZES.iter() {
+        let (alphas, betas) = setup_points(*log_domain_size);
+        group.bench_with_input(
+            BenchmarkId::new("without-precomputation", log_domain_size),
+            log_domain_size,
+            |b, &log_domain_size| {
+                let mpdpf = MPDPF::new(1 << log_domain_size, 1 << (log_domain_size / 2));
+                b.iter(|| {
+                    let (_key_0, _key_1) = mpdpf.generate_keys(&alphas, &betas);
+                });
+            },
+        );
+        group.bench_with_input(
+            BenchmarkId::new("with-precomputation", log_domain_size),
+            log_domain_size,
+            |b, &log_domain_size| {
+                let mut mpdpf = MPDPF::new(1 << log_domain_size, 1 << (log_domain_size / 2));
+                mpdpf.precompute();
+                b.iter(|| {
+                    let (_key_0, _key_1) = mpdpf.generate_keys(&alphas, &betas);
+                });
+            },
+        );
+    }
+    group.finish();
+}
+
+fn bench_mpdpf_evaluate_domain<MPDPF, F>(c: &mut Criterion, dpf_name: &str, field_name: &str)
+where
+    MPDPF: MultiPointDpf<Value = F>,
+    F: Copy + FromHash,
+{
+    let mut group = c.benchmark_group(format!("{}-{}-evaluate_domain", dpf_name, field_name));
+    for log_domain_size in LOG_DOMAIN_SIZES.iter() {
+        let (alphas, betas) = setup_points(*log_domain_size);
+        group.bench_with_input(
+            BenchmarkId::new("without-precomputation", log_domain_size),
+            log_domain_size,
+            |b, &log_domain_size| {
+                let mpdpf = MPDPF::new(1 << log_domain_size, 1 << (log_domain_size / 2));
+                let (key_0, _key_1) = mpdpf.generate_keys(&alphas, &betas);
+                b.iter(|| {
+                    mpdpf.evaluate_domain(&key_0);
+                });
+            },
+        );
+        group.bench_with_input(
+            BenchmarkId::new("with-precomputation", log_domain_size),
+            log_domain_size,
+            |b, &log_domain_size| {
+                let mut mpdpf = MPDPF::new(1 << log_domain_size, 1 << (log_domain_size / 2));
+                mpdpf.precompute();
+                let (key_0, _key_1) = mpdpf.generate_keys(&alphas, &betas);
+                b.iter(|| {
+                    mpdpf.evaluate_domain(&key_0);
+                });
+            },
+        );
+    }
+    group.finish();
+}
+
+fn bench_mpdpf<MPDPF, F>(c: &mut Criterion, dpf_name: &str, field_name: &str)
+where
+    MPDPF: MultiPointDpf<Value = F>,
+    F: Copy + FromHash,
+{
+    bench_mpdpf_keygen::<MPDPF, F>(c, dpf_name, field_name);
+    bench_mpdpf_evaluate_domain::<MPDPF, F>(c, dpf_name, field_name);
+}
+
+fn bench_all_mpdpf(c: &mut Criterion) {
+    bench_mpdpf::<DummyMpDpf<Fp>, _>(c, "DummyMpDpf", "Fp");
+    bench_mpdpf::<SmartMpDpf<Fp, DummySpDpf<Fp>, AesHashFunction<u16>>, _>(
+        c,
+        "SmartMpDpf<Dummy,Aes>",
+        "Fp",
+    );
+    bench_mpdpf::<SmartMpDpf<Fp, HalfTreeSpDpf<Fp>, AesHashFunction<u16>>, _>(
+        c,
+        "SmartMpDpf<HalfTree,Aes>",
+        "Fp",
+    );
+}
+
+criterion_group!(
+    name = benches;
+    config = Criterion::default().sample_size(10);
+    targets = bench_all_mpdpf
+);
+criterion_main!(benches);

+ 71 - 0
dpf/benches/spdpf.rs

@@ -0,0 +1,71 @@
+use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
+use dpf::spdpf::{DummySpDpf, HalfTreeSpDpf, SinglePointDpf};
+use utils::field::{Fp, FromHash};
+
+const LOG_DOMAIN_SIZES: [usize; 4] = [8, 12, 16, 20];
+
+fn bench_spdpf_keygen<SPDPF, F>(c: &mut Criterion, dpf_name: &str, field_name: &str)
+where
+    SPDPF: SinglePointDpf<Value = F>,
+    F: Copy + FromHash,
+{
+    let mut group = c.benchmark_group(format!("{}-{}-keygen", dpf_name, field_name));
+    let alpha = 42;
+    let beta = F::hash_bytes(&[0x13, 0x37, 0x42, 0x47]);
+    for log_domain_size in LOG_DOMAIN_SIZES.iter() {
+        group.bench_with_input(
+            BenchmarkId::from_parameter(log_domain_size),
+            log_domain_size,
+            |b, &log_domain_size| {
+                b.iter(|| {
+                    let (_key_0, _key_1) = SPDPF::generate_keys(1 << log_domain_size, alpha, beta);
+                });
+            },
+        );
+    }
+    group.finish();
+}
+
+fn bench_spdpf_evaluate_domain<SPDPF, F>(c: &mut Criterion, dpf_name: &str, field_name: &str)
+where
+    SPDPF: SinglePointDpf<Value = F>,
+    F: Copy + FromHash,
+{
+    let mut group = c.benchmark_group(format!("{}-{}-evaluate_domain", dpf_name, field_name));
+    let alpha = 42;
+    let beta = F::hash_bytes(&[0x13, 0x37, 0x42, 0x47]);
+    for log_domain_size in LOG_DOMAIN_SIZES.iter() {
+        group.bench_with_input(
+            BenchmarkId::from_parameter(log_domain_size),
+            log_domain_size,
+            |b, &log_domain_size| {
+                let (key_0, _key_1) = SPDPF::generate_keys(1 << log_domain_size, alpha, beta);
+                b.iter(|| {
+                    SPDPF::evaluate_domain(&key_0);
+                });
+            },
+        );
+    }
+    group.finish();
+}
+
+fn bench_spdpf<SPDPF, F>(c: &mut Criterion, dpf_name: &str, field_name: &str)
+where
+    SPDPF: SinglePointDpf<Value = F>,
+    F: Copy + FromHash,
+{
+    bench_spdpf_keygen::<SPDPF, F>(c, dpf_name, field_name);
+    bench_spdpf_evaluate_domain::<SPDPF, F>(c, dpf_name, field_name);
+}
+
+fn bench_all_spdpf(c: &mut Criterion) {
+    bench_spdpf::<DummySpDpf<Fp>, _>(c, "DummySpDpf", "Fp");
+    bench_spdpf::<HalfTreeSpDpf<Fp>, _>(c, "HalfTreeSpDpf", "Fp");
+}
+
+criterion_group!(
+    name = benches;
+    config = Criterion::default().sample_size(10);
+    targets = bench_all_spdpf
+);
+criterion_main!(benches);

+ 16 - 0
dpf/src/lib.rs

@@ -0,0 +1,16 @@
+//! Implementation of distributed point functions (DPFs).
+//!
+//! A (single-)point function is a function `f` that is specified by two values `(a, b)` such that
+//! `f(a) = b` and `f(x) = 0` for all other values `x != 0`.
+//! A multi-point function generalizes this concept to `n` points `(a_i, b_i)` for `i = 1, ..., n`,
+//! such that `f(a_i) = b_i` and `f(x) = 0` whenever `x` is not one of the `a_i`.
+//!
+//! A distributed point function (DPF) scheme allows to take the description of a point function
+//! `f` and output two keys `k_0, k_1`. These keys can be used with an evaluation algorithm `Eval`
+//! to obtain an additive share of `f`'s value such that `Eval(k_0, x) + Eval(k_1, x) = f(x)` for
+//! all `x`.
+
+#![warn(missing_docs)]
+
+pub mod mpdpf;
+pub mod spdpf;

+ 695 - 0
dpf/src/mpdpf.rs

@@ -0,0 +1,695 @@
+//! Trait definitions and implementations of multi-point distributed point functions (MP-DPFs).
+
+use crate::spdpf::SinglePointDpf;
+use bincode;
+use core::fmt;
+use core::fmt::Debug;
+use core::marker::PhantomData;
+use core::ops::{Add, AddAssign};
+use num::traits::Zero;
+use rayon::prelude::*;
+use utils::cuckoo::{
+    Hasher as CuckooHasher, Parameters as CuckooParameters,
+    NUMBER_HASH_FUNCTIONS as CUCKOO_NUMBER_HASH_FUNCTIONS,
+};
+use utils::hash::HashFunction;
+
+/// Trait for the keys of a multi-point DPF scheme.
+pub trait MultiPointDpfKey: Clone + Debug {
+    /// Return the party ID, 0 or 1, corresponding to this key.
+    fn get_party_id(&self) -> usize;
+
+    /// Return the domain size of the shared function.
+    fn get_domain_size(&self) -> usize;
+
+    /// Return the number of (possibly) non-zero points of the shared function.
+    fn get_number_points(&self) -> usize;
+}
+
+/// Trait for a single-point DPF scheme.
+pub trait MultiPointDpf {
+    /// The key type of the scheme.
+    type Key: MultiPointDpfKey;
+
+    /// The value type of the scheme.
+    type Value: Add<Output = Self::Value> + Copy + Debug + Eq + Zero;
+
+    /// Constructor for the MP-DPF scheme with a given domain size and number of points.
+    ///
+    /// Having a stateful scheme, allows for reusable precomputation.
+    fn new(domain_size: usize, number_points: usize) -> Self;
+
+    /// Return the domain size.
+    fn get_domain_size(&self) -> usize;
+
+    /// Return the number of (possibly) non-zero points.
+    fn get_number_points(&self) -> usize;
+
+    /// Run a possible precomputation phase.
+    fn precompute(&mut self) {}
+
+    /// Key generation for a given `domain_size`, an index `alpha` and a value `beta`.
+    ///
+    /// The shared point function is `f: {0, ..., domain_size - 1} -> Self::Value` such that
+    /// `f(alpha_i) = beta_i` and `f(x) = 0` for `x` is not one of the `alpha_i`.
+    fn generate_keys(&self, alphas: &[u64], betas: &[Self::Value]) -> (Self::Key, Self::Key);
+
+    /// Evaluation using a DPF key on a single `index` from `{0, ..., domain_size - 1}`.
+    fn evaluate_at(&self, key: &Self::Key, index: u64) -> Self::Value;
+
+    /// Evaluation using a DPF key on the whole domain.
+    ///
+    /// This might be implemented more efficiently than just repeatedly calling
+    /// [`Self::evaluate_at`].
+    fn evaluate_domain(&self, key: &Self::Key) -> Vec<Self::Value> {
+        (0..key.get_domain_size())
+            .map(|x| self.evaluate_at(key, x as u64))
+            .collect()
+    }
+}
+
+/// Key type for the insecure [DummyMpDpf] scheme, which trivially contains the defining parameters
+/// `alpha_i` and `beta_i`.
+#[derive(Clone, Debug, bincode::Encode, bincode::Decode)]
+pub struct DummyMpDpfKey<V: Copy + Debug> {
+    party_id: usize,
+    domain_size: usize,
+    number_points: usize,
+    alphas: Vec<u64>,
+    betas: Vec<V>,
+}
+
+impl<V> MultiPointDpfKey for DummyMpDpfKey<V>
+where
+    V: Copy + Debug,
+{
+    fn get_party_id(&self) -> usize {
+        self.party_id
+    }
+    fn get_domain_size(&self) -> usize {
+        self.domain_size
+    }
+    fn get_number_points(&self) -> usize {
+        self.number_points
+    }
+}
+
+/// Insecure MP-DPF scheme for testing purposes.
+pub struct DummyMpDpf<V>
+where
+    V: Add<Output = V> + Copy + Debug + Eq + Zero,
+{
+    domain_size: usize,
+    number_points: usize,
+    phantom: PhantomData<V>,
+}
+
+impl<V> MultiPointDpf for DummyMpDpf<V>
+where
+    V: Add<Output = V> + Copy + Debug + Eq + Zero,
+{
+    type Key = DummyMpDpfKey<V>;
+    type Value = V;
+
+    fn new(domain_size: usize, number_points: usize) -> Self {
+        Self {
+            domain_size,
+            number_points,
+            phantom: PhantomData,
+        }
+    }
+
+    fn get_domain_size(&self) -> usize {
+        self.domain_size
+    }
+
+    fn get_number_points(&self) -> usize {
+        self.number_points
+    }
+
+    fn generate_keys(&self, alphas: &[u64], betas: &[V]) -> (Self::Key, Self::Key) {
+        assert_eq!(
+            alphas.len(),
+            self.number_points,
+            "number of points does not match constructor argument"
+        );
+        assert_eq!(
+            alphas.len(),
+            betas.len(),
+            "alphas and betas must be the same size"
+        );
+        assert!(
+            alphas
+                .iter()
+                .all(|&alpha| alpha < (self.domain_size as u64)),
+            "all alphas must be in the domain"
+        );
+        assert!(
+            alphas.windows(2).all(|w| w[0] < w[1]),
+            "alphas must be sorted"
+        );
+        (
+            DummyMpDpfKey {
+                party_id: 0,
+                domain_size: self.domain_size,
+                number_points: self.number_points,
+                alphas: alphas.to_vec(),
+                betas: betas.to_vec(),
+            },
+            DummyMpDpfKey {
+                party_id: 1,
+                domain_size: self.domain_size,
+                number_points: self.number_points,
+                alphas: alphas.to_vec(),
+                betas: betas.to_vec(),
+            },
+        )
+    }
+
+    fn evaluate_at(&self, key: &Self::Key, index: u64) -> V {
+        assert_eq!(self.domain_size, key.domain_size);
+        assert_eq!(self.number_points, key.number_points);
+        if key.get_party_id() == 0 {
+            match key.alphas.binary_search(&index) {
+                Ok(i) => key.betas[i],
+                Err(_) => V::zero(),
+            }
+        } else {
+            V::zero()
+        }
+    }
+}
+
+/// Key type for the [SmartMpDpf] scheme.
+pub struct SmartMpDpfKey<SPDPF, H>
+where
+    SPDPF: SinglePointDpf,
+    H: HashFunction<u16>,
+{
+    party_id: usize,
+    domain_size: usize,
+    number_points: usize,
+    spdpf_keys: Vec<Option<SPDPF::Key>>,
+    cuckoo_parameters: CuckooParameters<H, u16>,
+}
+
+impl<SPDPF, H> Debug for SmartMpDpfKey<SPDPF, H>
+where
+    SPDPF: SinglePointDpf,
+    H: HashFunction<u16>,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        let (newline, indentation) = if f.alternate() {
+            ("\n", "    ")
+        } else {
+            (" ", "")
+        };
+        write!(f, "SmartMpDpfKey<SPDPF, H>{{{newline}")?;
+        write!(
+            f,
+            "{}party_id: {:?},{}",
+            indentation, self.party_id, newline
+        )?;
+        write!(
+            f,
+            "{}domain_size: {:?},{}",
+            indentation, self.domain_size, newline
+        )?;
+        write!(
+            f,
+            "{}number_points: {:?},{}",
+            indentation, self.number_points, newline
+        )?;
+        if f.alternate() {
+            writeln!(f, "    spdpf_keys:")?;
+            for (i, k) in self.spdpf_keys.iter().enumerate() {
+                writeln!(f, "        spdpf_keys[{i}]: {k:?}")?;
+            }
+        } else {
+            write!(f, " spdpf_keys: {:?},", self.spdpf_keys)?;
+        }
+        write!(
+            f,
+            "{}cuckoo_parameters: {:?}{}",
+            indentation, self.cuckoo_parameters, newline
+        )?;
+        write!(f, "}}")?;
+        Ok(())
+    }
+}
+
+impl<SPDPF, H> Clone for SmartMpDpfKey<SPDPF, H>
+where
+    SPDPF: SinglePointDpf,
+    H: HashFunction<u16>,
+{
+    fn clone(&self) -> Self {
+        Self {
+            party_id: self.party_id,
+            domain_size: self.domain_size,
+            number_points: self.number_points,
+            spdpf_keys: self.spdpf_keys.clone(),
+            cuckoo_parameters: self.cuckoo_parameters,
+        }
+    }
+}
+
+impl<SPDPF, H> bincode::Encode for SmartMpDpfKey<SPDPF, H>
+where
+    SPDPF: SinglePointDpf,
+    SPDPF::Key: bincode::Encode,
+    H: HashFunction<u16>,
+    CuckooParameters<H, u16>: bincode::Encode,
+{
+    fn encode<E: bincode::enc::Encoder>(
+        &self,
+        encoder: &mut E,
+    ) -> core::result::Result<(), bincode::error::EncodeError> {
+        bincode::Encode::encode(&self.party_id, encoder)?;
+        bincode::Encode::encode(&self.domain_size, encoder)?;
+        bincode::Encode::encode(&self.number_points, encoder)?;
+        bincode::Encode::encode(&self.spdpf_keys, encoder)?;
+        bincode::Encode::encode(&self.cuckoo_parameters, encoder)?;
+        Ok(())
+    }
+}
+
+impl<SPDPF, H> bincode::Decode for SmartMpDpfKey<SPDPF, H>
+where
+    SPDPF: SinglePointDpf,
+    SPDPF::Key: bincode::Decode,
+    H: HashFunction<u16>,
+    CuckooParameters<H, u16>: bincode::Decode,
+{
+    fn decode<D: bincode::de::Decoder>(
+        decoder: &mut D,
+    ) -> core::result::Result<Self, bincode::error::DecodeError> {
+        Ok(Self {
+            party_id: bincode::Decode::decode(decoder)?,
+            domain_size: bincode::Decode::decode(decoder)?,
+            number_points: bincode::Decode::decode(decoder)?,
+            spdpf_keys: bincode::Decode::decode(decoder)?,
+            cuckoo_parameters: bincode::Decode::decode(decoder)?,
+        })
+    }
+}
+
+impl<SPDPF, H> MultiPointDpfKey for SmartMpDpfKey<SPDPF, H>
+where
+    SPDPF: SinglePointDpf,
+    H: HashFunction<u16>,
+{
+    fn get_party_id(&self) -> usize {
+        self.party_id
+    }
+    fn get_domain_size(&self) -> usize {
+        self.domain_size
+    }
+    fn get_number_points(&self) -> usize {
+        self.number_points
+    }
+}
+
+/// Precomputed state for [SmartMpDpf].
+struct SmartMpDpfPrecomputationData<H: HashFunction<u16>> {
+    pub cuckoo_parameters: CuckooParameters<H, u16>,
+    pub hasher: CuckooHasher<H, u16>,
+    pub hashes: [Vec<u16>; CUCKOO_NUMBER_HASH_FUNCTIONS],
+    pub simple_htable: Vec<Vec<u64>>,
+    pub bucket_sizes: Vec<usize>,
+    pub position_map_lookup_table: Vec<[(usize, usize); 3]>,
+}
+
+/// MP-DPF construction using SP-DPFs and Cuckoo hashing from [Schoppmann et al. (CCS'19), Section
+/// 5](https://eprint.iacr.org/2019/1084.pdf#page=7).
+pub struct SmartMpDpf<V, SPDPF, H>
+where
+    V: Add<Output = V> + AddAssign + Copy + Debug + Eq + Zero,
+    SPDPF: SinglePointDpf<Value = V>,
+    H: HashFunction<u16>,
+{
+    domain_size: usize,
+    number_points: usize,
+    precomputation_data: Option<SmartMpDpfPrecomputationData<H>>,
+    phantom_v: PhantomData<V>,
+    phantom_s: PhantomData<SPDPF>,
+    phantom_h: PhantomData<H>,
+}
+
+impl<V, SPDPF, H> SmartMpDpf<V, SPDPF, H>
+where
+    V: Add<Output = V> + AddAssign + Copy + Debug + Eq + Zero + Send + Sync,
+    SPDPF: SinglePointDpf<Value = V>,
+    H: HashFunction<u16>,
+    H::Description: Sync,
+{
+    fn precompute_hashes(
+        domain_size: usize,
+        number_points: usize,
+    ) -> SmartMpDpfPrecomputationData<H> {
+        let seed: [u8; 32] = [
+            42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
+            42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
+        ];
+        let cuckoo_parameters = CuckooParameters::from_seed(number_points, seed);
+        assert!(
+            cuckoo_parameters.get_number_buckets() < (1 << u16::BITS),
+            "too many buckets, use larger type for hash values"
+        );
+        let hasher = CuckooHasher::<H, u16>::new(cuckoo_parameters);
+        let hashes = hasher.hash_domain(domain_size as u64);
+        let simple_htable =
+            hasher.hash_domain_into_buckets_given_hashes(domain_size as u64, &hashes);
+        let bucket_sizes = CuckooHasher::<H, u16>::compute_bucket_sizes(&simple_htable);
+        let position_map_lookup_table =
+            CuckooHasher::<H, u16>::compute_pos_lookup_table(domain_size as u64, &simple_htable);
+        SmartMpDpfPrecomputationData {
+            cuckoo_parameters,
+            hasher,
+            hashes,
+            simple_htable,
+            bucket_sizes,
+            position_map_lookup_table,
+        }
+    }
+}
+
+impl<V, SPDPF, H> MultiPointDpf for SmartMpDpf<V, SPDPF, H>
+where
+    V: Add<Output = V> + AddAssign + Copy + Debug + Eq + Zero + Send + Sync,
+    SPDPF: SinglePointDpf<Value = V>,
+    SPDPF::Key: Sync,
+    H: HashFunction<u16>,
+    H::Description: Sync,
+{
+    type Key = SmartMpDpfKey<SPDPF, H>;
+    type Value = V;
+
+    fn new(domain_size: usize, number_points: usize) -> Self {
+        assert!(domain_size < (1 << u32::BITS));
+        Self {
+            domain_size,
+            number_points,
+            precomputation_data: None,
+            phantom_v: PhantomData,
+            phantom_s: PhantomData,
+            phantom_h: PhantomData,
+        }
+    }
+
+    fn get_domain_size(&self) -> usize {
+        self.domain_size
+    }
+
+    fn get_number_points(&self) -> usize {
+        self.domain_size
+    }
+
+    fn precompute(&mut self) {
+        if self.precomputation_data.is_none() {
+            self.precomputation_data = Some(Self::precompute_hashes(
+                self.domain_size,
+                self.number_points,
+            ));
+        }
+    }
+
+    fn generate_keys(&self, alphas: &[u64], betas: &[Self::Value]) -> (Self::Key, Self::Key) {
+        assert_eq!(alphas.len(), betas.len());
+        debug_assert!(alphas.windows(2).all(|w| w[0] < w[1]));
+        debug_assert!(alphas.iter().all(|&alpha| alpha < self.domain_size as u64));
+        let number_points = alphas.len();
+
+        // if not data is precomputed, do it now
+        // (&self is not mut, so we cannot store the new data here nor call precompute() ...)
+        let mut precomputation_data_fresh: Option<SmartMpDpfPrecomputationData<H>> = None;
+        if self.precomputation_data.is_none() {
+            precomputation_data_fresh = Some(Self::precompute_hashes(
+                self.domain_size,
+                self.number_points,
+            ));
+        }
+        // select either the precomputed or the freshly computed data
+        let precomputation_data = self
+            .precomputation_data
+            .as_ref()
+            .unwrap_or_else(|| precomputation_data_fresh.as_ref().unwrap());
+        let cuckoo_parameters = &precomputation_data.cuckoo_parameters;
+        let hasher = &precomputation_data.hasher;
+        let (cuckoo_table_items, cuckoo_table_indices) = hasher.cuckoo_hash_items(alphas);
+        let position_map_lookup_table = &precomputation_data.position_map_lookup_table;
+        let pos = |bucket_i: usize, item: u64| -> u64 {
+            CuckooHasher::<H, u16>::pos_lookup(position_map_lookup_table, bucket_i, item)
+        };
+
+        let number_buckets = hasher.get_parameters().get_number_buckets();
+        let bucket_sizes = &precomputation_data.bucket_sizes;
+
+        let mut keys_0 = Vec::<Option<SPDPF::Key>>::with_capacity(number_buckets);
+        let mut keys_1 = Vec::<Option<SPDPF::Key>>::with_capacity(number_buckets);
+
+        for bucket_i in 0..number_buckets {
+            // if bucket is empty, add invalid dummy keys to the arrays to make the
+            // indices work
+            if bucket_sizes[bucket_i] == 0 {
+                keys_0.push(None);
+                keys_1.push(None);
+                continue;
+            }
+
+            let (alpha, beta) =
+                if cuckoo_table_items[bucket_i] != CuckooHasher::<H, u16>::UNOCCUPIED {
+                    let alpha = pos(bucket_i, cuckoo_table_items[bucket_i]);
+                    let beta = betas[cuckoo_table_indices[bucket_i]];
+                    (alpha, beta)
+                } else {
+                    (0, V::zero())
+                };
+            let (key_0, key_1) = SPDPF::generate_keys(bucket_sizes[bucket_i], alpha, beta);
+            keys_0.push(Some(key_0));
+            keys_1.push(Some(key_1));
+        }
+
+        (
+            SmartMpDpfKey::<SPDPF, H> {
+                party_id: 0,
+                domain_size: self.domain_size,
+                number_points,
+                spdpf_keys: keys_0,
+                cuckoo_parameters: *cuckoo_parameters,
+            },
+            SmartMpDpfKey::<SPDPF, H> {
+                party_id: 1,
+                domain_size: self.domain_size,
+                number_points,
+                spdpf_keys: keys_1,
+                cuckoo_parameters: *cuckoo_parameters,
+            },
+        )
+    }
+
+    fn evaluate_at(&self, key: &Self::Key, index: u64) -> Self::Value {
+        assert_eq!(self.domain_size, key.domain_size);
+        assert_eq!(self.number_points, key.number_points);
+        assert_eq!(key.domain_size, self.domain_size);
+        assert!(index < self.domain_size as u64);
+
+        let hasher = CuckooHasher::<H, u16>::new(key.cuckoo_parameters);
+
+        let hashes = hasher.hash_items(&[index]);
+        let simple_htable = hasher.hash_domain_into_buckets(self.domain_size as u64);
+
+        let pos = |bucket_i: usize, item: u64| -> u64 {
+            let idx = simple_htable[bucket_i].partition_point(|x| x < &item);
+            debug_assert!(idx != simple_htable[bucket_i].len());
+            debug_assert_eq!(item, simple_htable[bucket_i][idx]);
+            debug_assert!(idx == 0 || simple_htable[bucket_i][idx - 1] != item);
+            idx as u64
+        };
+        let mut output = {
+            let hash = H::hash_value_as_usize(hashes[0][0]);
+            debug_assert!(key.spdpf_keys[hash].is_some());
+            let sp_key = key.spdpf_keys[hash].as_ref().unwrap();
+            debug_assert_eq!(simple_htable[hash][pos(hash, index) as usize], index);
+            SPDPF::evaluate_at(sp_key, pos(hash, index))
+        };
+
+        // prevent adding the same term multiple times when we have collisions
+        let mut hash_bit_map = [0u8; 2];
+        if hashes[0][0] != hashes[1][0] {
+            // hash_bit_map[i] |= 1;
+            hash_bit_map[0] = 1;
+        }
+        if hashes[0][0] != hashes[2][0] && hashes[1][0] != hashes[2][0] {
+            // hash_bit_map[i] |= 2;
+            hash_bit_map[1] = 1;
+        }
+
+        for j in 1..CUCKOO_NUMBER_HASH_FUNCTIONS {
+            if hash_bit_map[j - 1] == 0 {
+                continue;
+            }
+            let hash = H::hash_value_as_usize(hashes[j][0]);
+            debug_assert!(key.spdpf_keys[hash].is_some());
+            let sp_key = key.spdpf_keys[hash].as_ref().unwrap();
+            debug_assert_eq!(simple_htable[hash][pos(hash, index) as usize], index);
+            output += SPDPF::evaluate_at(sp_key, pos(hash, index));
+        }
+
+        output
+    }
+
+    fn evaluate_domain(&self, key: &Self::Key) -> Vec<Self::Value> {
+        assert_eq!(self.domain_size, key.domain_size);
+        assert_eq!(self.number_points, key.number_points);
+        let domain_size = self.domain_size as u64;
+
+        // if not data is precomputed, do it now
+        // (&self is not mut, so we cannot store the new data here nor call precompute() ...)
+        let mut precomputation_data_fresh: Option<SmartMpDpfPrecomputationData<H>> = None;
+        if self.precomputation_data.is_none() {
+            precomputation_data_fresh = Some(Self::precompute_hashes(
+                self.domain_size,
+                self.number_points,
+            ));
+        }
+        // select either the precomputed or the freshly computed data
+        let precomputation_data = self
+            .precomputation_data
+            .as_ref()
+            .unwrap_or_else(|| precomputation_data_fresh.as_ref().unwrap());
+        let hashes = &precomputation_data.hashes;
+        let simple_htable = &precomputation_data.simple_htable;
+        let position_map_lookup_table = &precomputation_data.position_map_lookup_table;
+        let pos = |bucket_i: usize, item: u64| -> u64 {
+            CuckooHasher::<H, u16>::pos_lookup(position_map_lookup_table, bucket_i, item)
+        };
+
+        let sp_dpf_full_domain_evaluations: Vec<Vec<V>> = key
+            .spdpf_keys
+            .par_iter()
+            .map(|sp_key_opt| {
+                sp_key_opt
+                    .as_ref()
+                    .map_or(vec![], |sp_key| SPDPF::evaluate_domain(sp_key))
+            })
+            .collect();
+
+        let spdpf_evaluate_at =
+            |hash: usize, index| sp_dpf_full_domain_evaluations[hash][pos(hash, index) as usize];
+
+        let outputs: Vec<_> = (0..domain_size)
+            .into_par_iter()
+            .map(|index| {
+                let mut output = {
+                    let hash = H::hash_value_as_usize(hashes[0][index as usize]);
+                    debug_assert!(key.spdpf_keys[hash].is_some());
+                    debug_assert_eq!(simple_htable[hash][pos(hash, index) as usize], index);
+                    spdpf_evaluate_at(hash, index)
+                };
+
+                // prevent adding the same term multiple times when we have collisions
+                let mut hash_bit_map = [0u8; 2];
+                if hashes[0][index as usize] != hashes[1][index as usize] {
+                    hash_bit_map[0] = 1;
+                }
+                if hashes[0][index as usize] != hashes[2][index as usize]
+                    && hashes[1][index as usize] != hashes[2][index as usize]
+                {
+                    hash_bit_map[1] = 1;
+                }
+
+                for j in 1..CUCKOO_NUMBER_HASH_FUNCTIONS {
+                    if hash_bit_map[j - 1] == 0 {
+                        continue;
+                    }
+                    let hash = H::hash_value_as_usize(hashes[j][index as usize]);
+                    debug_assert!(key.spdpf_keys[hash].is_some());
+                    debug_assert_eq!(simple_htable[hash][pos(hash, index) as usize], index);
+                    output += spdpf_evaluate_at(hash, index);
+                }
+                output
+            })
+            .collect();
+
+        outputs
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::spdpf::DummySpDpf;
+    use rand::distributions::{Distribution, Standard};
+    use rand::{thread_rng, Rng};
+    use std::num::Wrapping;
+    use utils::hash::AesHashFunction;
+
+    fn test_mpdpf_with_param<MPDPF: MultiPointDpf>(
+        log_domain_size: u32,
+        number_points: usize,
+        precomputation: bool,
+    ) where
+        Standard: Distribution<MPDPF::Value>,
+    {
+        let domain_size = (1 << log_domain_size) as u64;
+        assert!(number_points <= domain_size as usize);
+        let alphas = {
+            let mut alphas = Vec::<u64>::with_capacity(number_points);
+            while alphas.len() < number_points {
+                let x = thread_rng().gen_range(0..domain_size);
+                match alphas.as_slice().binary_search(&x) {
+                    Ok(_) => continue,
+                    Err(i) => alphas.insert(i, x),
+                }
+            }
+            alphas
+        };
+        let betas: Vec<MPDPF::Value> = (0..number_points).map(|_| thread_rng().gen()).collect();
+        let mut mpdpf = MPDPF::new(domain_size as usize, number_points);
+        if precomputation {
+            mpdpf.precompute();
+        }
+        let (key_0, key_1) = mpdpf.generate_keys(&alphas, &betas);
+
+        let out_0 = mpdpf.evaluate_domain(&key_0);
+        let out_1 = mpdpf.evaluate_domain(&key_1);
+        for i in 0..domain_size {
+            let value = mpdpf.evaluate_at(&key_0, i) + mpdpf.evaluate_at(&key_1, i);
+            assert_eq!(value, out_0[i as usize] + out_1[i as usize]);
+            let expected_result = match alphas.binary_search(&i) {
+                Ok(i) => betas[i],
+                Err(_) => MPDPF::Value::zero(),
+            };
+            assert_eq!(value, expected_result, "wrong value at index {}", i);
+        }
+    }
+
+    #[test]
+    fn test_dummy_mpdpf() {
+        type Value = Wrapping<u64>;
+        for log_domain_size in 5..10 {
+            for log_number_points in 0..5 {
+                test_mpdpf_with_param::<DummyMpDpf<Value>>(
+                    log_domain_size,
+                    1 << log_number_points,
+                    false,
+                );
+            }
+        }
+    }
+
+    #[test]
+    fn test_smart_mpdpf() {
+        type Value = Wrapping<u64>;
+        for log_domain_size in 5..7 {
+            for log_number_points in 0..5 {
+                for precomputation in [false, true] {
+                    test_mpdpf_with_param::<
+                        SmartMpDpf<Value, DummySpDpf<Value>, AesHashFunction<u16>>,
+                    >(log_domain_size, 1 << log_number_points, precomputation);
+                }
+            }
+        }
+    }
+}

+ 479 - 0
dpf/src/spdpf.rs

@@ -0,0 +1,479 @@
+//! Trait definitions and implementations of single-point distributed point functions (SP-DPFs).
+
+use bincode;
+use core::fmt::Debug;
+use core::marker::PhantomData;
+use core::ops::{Add, Neg, Sub};
+use num::traits::Zero;
+use rand::{thread_rng, Rng};
+use utils::bit_decompose::bit_decompose;
+use utils::fixed_key_aes::FixedKeyAes;
+use utils::pseudorandom_conversion::{PRConvertTo, PRConverter};
+
+/// Trait for the keys of a single-point DPF scheme.
+pub trait SinglePointDpfKey: Clone + Debug {
+    /// Return the party ID, 0 or 1, corresponding to this key.
+    fn get_party_id(&self) -> usize;
+
+    /// Return the domain size of the shared function.
+    fn get_domain_size(&self) -> usize;
+}
+
+/// Trait for a single-point DPF scheme.
+pub trait SinglePointDpf {
+    /// The key type of the scheme.
+    type Key: SinglePointDpfKey;
+
+    /// The value type of the scheme.
+    type Value: Add<Output = Self::Value> + Copy + Debug + Eq + Zero;
+
+    /// Key generation for a given `domain_size`, an index `alpha` and a value `beta`.
+    ///
+    /// The shared point function is `f: {0, ..., domain_size - 1} -> Self::Value` such that
+    /// `f(alpha) = beta` and `f(x) = 0` for `x != alpha`.
+    fn generate_keys(domain_size: usize, alpha: u64, beta: Self::Value) -> (Self::Key, Self::Key);
+
+    /// Evaluation using a DPF key on a single `index` from `{0, ..., domain_size - 1}`.
+    fn evaluate_at(key: &Self::Key, index: u64) -> Self::Value;
+
+    /// Evaluation using a DPF key on the whole domain.
+    ///
+    /// This might be implemented more efficiently than just repeatedly calling
+    /// [`Self::evaluate_at`].
+    fn evaluate_domain(key: &Self::Key) -> Vec<Self::Value> {
+        (0..key.get_domain_size())
+            .map(|x| Self::evaluate_at(key, x as u64))
+            .collect()
+    }
+}
+
+/// Key type for the insecure [DummySpDpf] scheme, which trivially contains the defining parameters
+/// `alpha` and `beta`.
+#[derive(Clone, Copy, Debug, bincode::Encode, bincode::Decode)]
+pub struct DummySpDpfKey<V: Copy + Debug> {
+    party_id: usize,
+    domain_size: usize,
+    alpha: u64,
+    beta: V,
+}
+
+impl<V> SinglePointDpfKey for DummySpDpfKey<V>
+where
+    V: Copy + Debug,
+{
+    fn get_party_id(&self) -> usize {
+        self.party_id
+    }
+    fn get_domain_size(&self) -> usize {
+        self.domain_size
+    }
+}
+
+/// Insecure SP-DPF scheme for testing purposes.
+pub struct DummySpDpf<V>
+where
+    V: Add<Output = V> + Copy + Debug + Eq + Zero,
+{
+    phantom: PhantomData<V>,
+}
+
+impl<V> SinglePointDpf for DummySpDpf<V>
+where
+    V: Add<Output = V> + Copy + Debug + Eq + Zero,
+{
+    type Key = DummySpDpfKey<V>;
+    type Value = V;
+
+    fn generate_keys(domain_size: usize, alpha: u64, beta: V) -> (Self::Key, Self::Key) {
+        assert!(alpha < domain_size as u64);
+        (
+            DummySpDpfKey {
+                party_id: 0,
+                domain_size,
+                alpha,
+                beta,
+            },
+            DummySpDpfKey {
+                party_id: 1,
+                domain_size,
+                alpha,
+                beta,
+            },
+        )
+    }
+
+    fn evaluate_at(key: &Self::Key, index: u64) -> V {
+        if key.get_party_id() == 0 && index == key.alpha {
+            key.beta
+        } else {
+            V::zero()
+        }
+    }
+
+    fn evaluate_domain(key: &Self::Key) -> Vec<Self::Value> {
+        let mut output = vec![V::zero(); key.domain_size];
+        if key.get_party_id() == 0 {
+            output[key.alpha as usize] = key.beta;
+        }
+        output
+    }
+}
+
+/// Key type for the [HalfTreeSpDpf] scheme.
+#[derive(Clone, Debug, bincode::Encode, bincode::Decode)]
+pub struct HalfTreeSpDpfKey<V: Copy + Debug> {
+    /// party id `b`
+    party_id: usize,
+    /// size `n` of the DPF's domain `[n]`
+    domain_size: usize,
+    /// `(s_b^0 || t_b^0)` and `t_b^0` is the LSB
+    party_seed: u128,
+    /// vector of length `n`: `CW_1, ..., CW_(n-1)`
+    correction_words: Vec<u128>,
+    /// high part of `CW_n = (HCW, [LCW[0], LCW[1]])`
+    hcw: u128,
+    /// low parts of `CW_n = (HCW, [LCW[0], LCW[1]])`
+    lcw: [bool; 2],
+    /// `CW_(n+1)`
+    correction_word_np1: V,
+}
+
+impl<V> SinglePointDpfKey for HalfTreeSpDpfKey<V>
+where
+    V: Copy + Debug,
+{
+    fn get_party_id(&self) -> usize {
+        self.party_id
+    }
+    fn get_domain_size(&self) -> usize {
+        self.domain_size
+    }
+}
+
+/// Implementation of the Half-Tree DPF scheme from Guo et al. ([ePrint 2022/1431, Figure
+/// 8](https://eprint.iacr.org/2022/1431.pdf#page=18)).
+pub struct HalfTreeSpDpf<V>
+where
+    V: Add<Output = V> + Copy + Debug + Eq + Zero,
+{
+    phantom: PhantomData<V>,
+}
+
+impl<V> HalfTreeSpDpf<V>
+where
+    V: Add<Output = V> + Sub<Output = V> + Copy + Debug + Eq + Zero,
+{
+    const FIXED_KEY_AES_KEY: [u8; 16] =
+        0xdead_beef_1337_4247_dead_beef_1337_4247_u128.to_le_bytes();
+    const HASH_KEY: u128 = 0xc000_ffee_c0ff_ffee_c0ff_eeee_c00f_feee_u128;
+}
+
+impl<V> SinglePointDpf for HalfTreeSpDpf<V>
+where
+    V: Add<Output = V> + Sub<Output = V> + Neg<Output = V> + Copy + Debug + Eq + Zero,
+    PRConverter: PRConvertTo<V>,
+{
+    type Key = HalfTreeSpDpfKey<V>;
+    type Value = V;
+
+    fn generate_keys(domain_size: usize, alpha: u64, beta: V) -> (Self::Key, Self::Key) {
+        assert!(alpha < domain_size as u64);
+
+        let mut rng = thread_rng();
+
+        if domain_size == 1 {
+            // simply secret-share beta
+            let beta_0: V = PRConverter::convert(rng.gen::<u128>());
+            let beta_1: V = beta - beta_0;
+            return (
+                HalfTreeSpDpfKey {
+                    party_id: 0,
+                    domain_size,
+                    party_seed: Default::default(),
+                    correction_words: Default::default(),
+                    hcw: Default::default(),
+                    lcw: Default::default(),
+                    correction_word_np1: beta_0,
+                },
+                HalfTreeSpDpfKey {
+                    party_id: 1,
+                    domain_size,
+                    party_seed: Default::default(),
+                    correction_words: Default::default(),
+                    hcw: Default::default(),
+                    lcw: Default::default(),
+                    correction_word_np1: beta_1,
+                },
+            );
+        }
+
+        let fkaes = FixedKeyAes::new(Self::FIXED_KEY_AES_KEY);
+        let hash = |x: u128| fkaes.hash_ccr(Self::HASH_KEY ^ x);
+        let convert = |x: u128| -> V { PRConverter::convert(x) };
+
+        let tree_height = (domain_size as f64).log2().ceil() as usize;
+        let alpha_bits: Vec<bool> = bit_decompose(alpha, tree_height);
+
+        let delta = rng.gen::<u128>() | 1u128;
+
+        let mut correction_words = Vec::<u128>::with_capacity(tree_height - 1);
+        let mut st_0 = rng.gen::<u128>();
+        let mut st_1 = st_0 ^ delta;
+        let party_seeds = (st_0, st_1);
+
+        debug_assert_eq!(alpha_bits.len(), tree_height);
+
+        for alpha_i in alpha_bits.iter().copied().take(tree_height - 1) {
+            let cw_i = hash(st_0) ^ hash(st_1) ^ ((1 - alpha_i as u128) * delta);
+            st_0 = hash(st_0) ^ (alpha_i as u128 * st_0) ^ ((st_0 & 1) * cw_i);
+            st_1 = hash(st_1) ^ (alpha_i as u128 * st_1) ^ ((st_1 & 1) * cw_i);
+            correction_words.push(cw_i);
+        }
+
+        let high_low = [[hash(st_0), hash(st_0 ^ 1)], [hash(st_1), hash(st_1 ^ 1)]];
+        const HIGH_MASK: u128 = u128::MAX - 1;
+        const LOW_MASK: u128 = 1u128;
+        let a_n = alpha_bits[tree_height - 1];
+        let hcw = (high_low[0][1 - a_n as usize] ^ high_low[1][1 - a_n as usize]) & HIGH_MASK;
+        let lcw = [
+            ((high_low[0][0] ^ high_low[1][0] ^ (1 - a_n as u128)) & LOW_MASK) != 0,
+            ((high_low[0][1] ^ high_low[1][1] ^ a_n as u128) & LOW_MASK) != 0,
+        ];
+
+        st_0 = high_low[0][a_n as usize] ^ ((st_0 & 1) * (hcw | lcw[a_n as usize] as u128));
+        st_1 = high_low[1][a_n as usize] ^ ((st_1 & 1) * (hcw | lcw[a_n as usize] as u128));
+        let correction_word_np1: V = match (st_0 & 1).wrapping_sub(st_1 & 1) {
+            u128::MAX => convert(st_0 >> 1) - convert(st_1 >> 1) - beta,
+            0 => V::zero(),
+            1 => convert(st_1 >> 1) - convert(st_0 >> 1) + beta,
+            _ => panic!("should not happend, since matching a difference of two bits"),
+        };
+
+        (
+            HalfTreeSpDpfKey {
+                party_id: 0,
+                domain_size,
+                party_seed: party_seeds.0,
+                correction_words: correction_words.clone(),
+                hcw,
+                lcw,
+                correction_word_np1,
+            },
+            HalfTreeSpDpfKey {
+                party_id: 1,
+                domain_size,
+                party_seed: party_seeds.1,
+                correction_words,
+                hcw,
+                lcw,
+                correction_word_np1,
+            },
+        )
+    }
+
+    fn evaluate_at(key: &Self::Key, index: u64) -> V {
+        assert!(key.domain_size > 0);
+        assert!(index < key.domain_size as u64);
+
+        if key.domain_size == 1 {
+            // beta is simply secret-shared
+            return key.correction_word_np1;
+        }
+
+        let fkaes = FixedKeyAes::new(Self::FIXED_KEY_AES_KEY);
+        let hash = |x: u128| fkaes.hash_ccr(Self::HASH_KEY ^ x);
+        let convert = |x: u128| -> V { PRConverter::convert(x) };
+
+        let tree_height = (key.domain_size as f64).log2().ceil() as usize;
+        let index_bits: Vec<bool> = bit_decompose(index, tree_height);
+
+        debug_assert_eq!(index_bits.len(), tree_height);
+
+        let mut st_b = key.party_seed;
+        for (index_bit_i, correction_word_i) in index_bits
+            .iter()
+            .copied()
+            .zip(key.correction_words.iter())
+            .take(tree_height - 1)
+        {
+            st_b = hash(st_b) ^ (index_bit_i as u128 * st_b) ^ ((st_b & 1) * correction_word_i);
+        }
+        let x_n = index_bits[tree_height - 1];
+        let high_low_b_xn = hash(st_b ^ x_n as u128);
+        st_b = high_low_b_xn ^ ((st_b & 1) * (key.hcw | key.lcw[x_n as usize] as u128));
+
+        let value = convert(st_b >> 1)
+            + if st_b & 1 == 0 {
+                V::zero()
+            } else {
+                key.correction_word_np1
+            };
+        if key.party_id == 0 {
+            value
+        } else {
+            V::zero() - value
+        }
+    }
+
+    fn evaluate_domain(key: &Self::Key) -> Vec<V> {
+        assert!(key.domain_size > 0);
+
+        if key.domain_size == 1 {
+            // beta is simply secret-shared
+            return vec![key.correction_word_np1];
+        }
+
+        let fkaes = FixedKeyAes::new(Self::FIXED_KEY_AES_KEY);
+        let hash = |x: u128| fkaes.hash_ccr(Self::HASH_KEY ^ x);
+        let convert = |x: u128| -> V { PRConverter::convert(x) };
+
+        let tree_height = (key.domain_size as f64).log2().ceil() as usize;
+        let last_index = key.domain_size - 1;
+
+        let mut seeds = vec![0u128; key.domain_size];
+        seeds[0] = key.party_seed;
+
+        // since the last layer is handled separately, we only need the following block if we have
+        // more than one layer
+        if tree_height > 1 {
+            // iterate over the tree layer by layer
+            for i in 0..(tree_height - 1) {
+                // expand each node in this layer;
+                // we need to iterate from right to left, since we reuse the same buffer
+                for j in (0..(last_index >> (tree_height - i)) + 1).rev() {
+                    // for j in (0..(1 << i)).rev() {
+                    let st = seeds[j];
+                    let st_0 = hash(st) ^ ((st & 1) * key.correction_words[i]);
+                    let st_1 = hash(st) ^ st ^ ((st & 1) * key.correction_words[i]);
+                    seeds[2 * j] = st_0;
+                    seeds[2 * j + 1] = st_1;
+                }
+            }
+        }
+
+        // expand last layer
+        {
+            // handle the last expansion separately, since we might not need both outputs
+            let j = last_index >> 1;
+            let st = seeds[j];
+            let st_0 = hash(st) ^ ((st & 1) * (key.hcw | key.lcw[0] as u128));
+            seeds[2 * j] = st_0;
+            // check if we need both outputs
+            if key.domain_size & 1 == 0 {
+                let st_1 = hash(st ^ 1) ^ ((st & 1) * (key.hcw | key.lcw[1] as u128));
+                seeds[2 * j + 1] = st_1;
+            }
+
+            // handle the other expansions as usual
+            for j in (0..(last_index >> 1)).rev() {
+                let st = seeds[j];
+                let st_0 = hash(st) ^ ((st & 1) * (key.hcw | key.lcw[0] as u128));
+                let st_1 = hash(st ^ 1) ^ ((st & 1) * (key.hcw | key.lcw[1] as u128));
+                seeds[2 * j] = st_0;
+                seeds[2 * j + 1] = st_1;
+            }
+        }
+
+        // convert leaves into V elements
+        if key.party_id == 0 {
+            seeds
+                .iter()
+                .map(|st_b| {
+                    let mut tmp = convert(st_b >> 1);
+                    if st_b & 1 == 1 {
+                        tmp = tmp + key.correction_word_np1;
+                    }
+                    tmp
+                })
+                .collect()
+        } else {
+            seeds
+                .iter()
+                .map(|st_b| {
+                    let mut tmp = convert(st_b >> 1);
+                    if st_b & 1 == 1 {
+                        tmp = tmp + key.correction_word_np1;
+                    }
+                    -tmp
+                })
+                .collect()
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use core::num::Wrapping;
+    use rand::distributions::{Distribution, Standard};
+    use rand::{thread_rng, Rng};
+
+    fn test_spdpf_with_param<SPDPF: SinglePointDpf>(domain_size: usize, alpha: Option<u64>)
+    where
+        Standard: Distribution<SPDPF::Value>,
+    {
+        let alpha = if alpha.is_some() {
+            alpha.unwrap()
+        } else {
+            thread_rng().gen_range(0..domain_size as u64)
+        };
+        let beta = thread_rng().gen();
+        let (key_0, key_1) = SPDPF::generate_keys(domain_size, alpha, beta);
+
+        let out_0 = SPDPF::evaluate_domain(&key_0);
+        let out_1 = SPDPF::evaluate_domain(&key_1);
+        assert_eq!(out_0.len(), domain_size);
+        assert_eq!(out_1.len(), domain_size);
+        for i in 0..domain_size as u64 {
+            let value = SPDPF::evaluate_at(&key_0, i) + SPDPF::evaluate_at(&key_1, i);
+            assert_eq!(
+                value,
+                out_0[i as usize] + out_1[i as usize],
+                "evaluate_at/domain mismatch at position {i}"
+            );
+            if i == alpha {
+                assert_eq!(
+                    value, beta,
+                    "incorrect value != beta at position alpha = {i}"
+                );
+            } else {
+                assert_eq!(
+                    value,
+                    SPDPF::Value::zero(),
+                    "incorrect value != 0 at position {i}"
+                );
+            }
+        }
+    }
+
+    #[test]
+    fn test_spdpf_dummy() {
+        for log_domain_size in 0..10 {
+            test_spdpf_with_param::<DummySpDpf<u64>>(1 << log_domain_size, None);
+        }
+    }
+
+    #[test]
+    fn test_spdpf_half_tree_power_of_two_domain() {
+        for log_domain_size in 0..10 {
+            test_spdpf_with_param::<HalfTreeSpDpf<Wrapping<u64>>>(1 << log_domain_size, None);
+        }
+    }
+
+    #[test]
+    fn test_spdpf_half_tree_random_domain() {
+        for _ in 0..10 {
+            let domain_size = thread_rng().gen_range(1..(1 << 10));
+            test_spdpf_with_param::<HalfTreeSpDpf<Wrapping<u64>>>(domain_size, None);
+        }
+    }
+
+    #[test]
+    fn test_spdpf_half_tree_exhaustive_params() {
+        for domain_size in 1..=32 {
+            for alpha in 0..domain_size as u64 {
+                test_spdpf_with_param::<HalfTreeSpDpf<Wrapping<u64>>>(domain_size, Some(alpha));
+            }
+        }
+    }
+}

+ 43 - 0
oram/Cargo.toml

@@ -0,0 +1,43 @@
+[package]
+name = "oram"
+version = "0.1.0"
+edition = "2021"
+authors.workspace = true
+description.workspace = true
+license.workspace = true
+readme.workspace = true
+repository.workspace = true
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+communicator = { path = "../communicator" }
+dpf = { path = "../dpf" }
+utils = { path = "../utils" }
+bincode = "2.0.0-rc.2"
+bitvec = "1.0.1"
+ff = "0.13.0"
+funty = "2.0.0"
+git-version = "0.3.5"
+itertools = "0.10.5"
+num-bigint = "0.4.3"
+num-traits = "0.2.15"
+rand = "0.8.5"
+rand_chacha = "0.3.1"
+rayon = "1.6.1"
+serde = { version = "1.0", features = ["derive"] }
+strum = { version = "0.24.1", features = ["derive"] }
+strum_macros = "0.24"
+
+[dev-dependencies]
+clap = { version = "4.1.4", features = ["derive"] }
+criterion = "0.4.0"
+serde_json = "1.0"
+
+[[bench]]
+name = "doprf"
+harness = false
+
+[[bench]]
+name = "p_ot"
+harness = false

+ 200 - 0
oram/benches/doprf.rs

@@ -0,0 +1,200 @@
+use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
+use ff::Field;
+use oram::doprf::LegendrePrf;
+use oram::doprf::{DOPrfParty1, DOPrfParty2, DOPrfParty3};
+use oram::doprf::{MaskedDOPrfParty1, MaskedDOPrfParty2, MaskedDOPrfParty3};
+use rand::thread_rng;
+use utils::field::Fp;
+
+pub fn bench_legendre_prf(c: &mut Criterion) {
+    let output_bitsize = 64;
+    let mut group = c.benchmark_group("LegendrePrf");
+    group.bench_function("keygen", |b| {
+        b.iter(|| black_box(LegendrePrf::<Fp>::key_gen(output_bitsize)))
+    });
+    group.bench_function("eval", |b| {
+        let key = LegendrePrf::<Fp>::key_gen(output_bitsize);
+        let x = Fp::random(thread_rng());
+        b.iter(|| black_box(LegendrePrf::<Fp>::eval_to_uint::<u128>(&key, x)))
+    });
+    group.finish();
+}
+
+const LOG_NUM_EVALUATIONS: [usize; 4] = [4, 6, 8, 10];
+
+pub fn bench_doprf(c: &mut Criterion) {
+    let output_bitsize = 128;
+    let mut group = c.benchmark_group("DOPrf");
+
+    let mut party_1 = DOPrfParty1::<Fp>::new(output_bitsize);
+    let mut party_2 = DOPrfParty2::<Fp>::new(output_bitsize);
+    let mut party_3 = DOPrfParty3::<Fp>::new(output_bitsize);
+
+    group.bench_function("init", |b| {
+        b.iter(|| {
+            party_1.reset();
+            party_2.reset();
+            party_3.reset();
+            let (msg_1_2, msg_1_3) = party_1.init_round_0();
+            let (msg_2_1, msg_2_3) = party_2.init_round_0();
+            let (msg_3_1, msg_3_2) = party_3.init_round_0();
+            party_1.init_round_1(msg_2_1, msg_3_1);
+            party_2.init_round_1(msg_1_2, msg_3_2);
+            party_3.init_round_1(msg_1_3, msg_2_3);
+        });
+    });
+
+    {
+        party_1.reset();
+        party_2.reset();
+        party_3.reset();
+        let (msg_1_2, msg_1_3) = party_1.init_round_0();
+        let (msg_2_1, msg_2_3) = party_2.init_round_0();
+        let (msg_3_1, msg_3_2) = party_3.init_round_0();
+        party_1.init_round_1(msg_2_1, msg_3_1);
+        party_2.init_round_1(msg_1_2, msg_3_2);
+        party_3.init_round_1(msg_1_3, msg_2_3);
+    }
+
+    for log_num_evaluations in LOG_NUM_EVALUATIONS {
+        group.bench_with_input(
+            BenchmarkId::new("preprocess", log_num_evaluations),
+            &log_num_evaluations,
+            |b, &log_num_evaluations| {
+                let num = 1 << log_num_evaluations;
+                b.iter(|| {
+                    party_1.reset_preprocessing();
+                    party_2.reset_preprocessing();
+                    party_3.reset_preprocessing();
+                    let (msg_1_2, msg_1_3) = party_1.preprocess_round_0(num);
+                    let (msg_2_1, msg_2_3) = party_2.preprocess_round_0(num);
+                    let (msg_3_1, msg_3_2) = party_3.preprocess_round_0(num);
+                    party_1.preprocess_round_1(num, msg_2_1, msg_3_1);
+                    party_2.preprocess_round_1(num, msg_1_2, msg_3_2);
+                    party_3.preprocess_round_1(num, msg_1_3, msg_2_3);
+                });
+            },
+        );
+    }
+
+    for log_num_evaluations in LOG_NUM_EVALUATIONS {
+        group.bench_with_input(
+            BenchmarkId::new("preprocess+eval", log_num_evaluations),
+            &log_num_evaluations,
+            |b, &log_num_evaluations| {
+                let num = 1 << log_num_evaluations;
+                let shares_1: Vec<Fp> = (0..num).map(|_| Fp::random(thread_rng())).collect();
+                let shares_2: Vec<Fp> = (0..num).map(|_| Fp::random(thread_rng())).collect();
+                let shares_3: Vec<Fp> = (0..num).map(|_| Fp::random(thread_rng())).collect();
+                b.iter(|| {
+                    let (msg_1_2, msg_1_3) = party_1.preprocess_round_0(num);
+                    let (msg_2_1, msg_2_3) = party_2.preprocess_round_0(num);
+                    let (msg_3_1, msg_3_2) = party_3.preprocess_round_0(num);
+                    party_1.preprocess_round_1(num, msg_2_1, msg_3_1);
+                    party_2.preprocess_round_1(num, msg_1_2, msg_3_2);
+                    party_3.preprocess_round_1(num, msg_1_3, msg_2_3);
+
+                    let (msg_2_1, msg_2_3) = party_2.eval_round_0(num, &shares_2);
+                    let (msg_3_1, _) = party_3.eval_round_0(num, &shares_3);
+                    let (_, msg_1_3) = party_1.eval_round_1(num, &shares_1, &msg_2_1, &msg_3_1);
+                    let _output = party_3.eval_round_2(num, &shares_3, msg_1_3, msg_2_3);
+                });
+            },
+        );
+    }
+
+    group.finish();
+}
+
+pub fn bench_masked_doprf(c: &mut Criterion) {
+    let output_bitsize = 128;
+    let mut group = c.benchmark_group("MaskedDOPrf");
+
+    let mut party_1 = MaskedDOPrfParty1::<Fp>::new(output_bitsize);
+    let mut party_2 = MaskedDOPrfParty2::<Fp>::new(output_bitsize);
+    let mut party_3 = MaskedDOPrfParty3::<Fp>::new(output_bitsize);
+
+    group.bench_function("init", |b| {
+        b.iter(|| {
+            party_1.reset();
+            party_2.reset();
+            party_3.reset();
+            let (msg_1_2, msg_1_3) = party_1.init_round_0();
+            let (msg_2_1, msg_2_3) = party_2.init_round_0();
+            let (msg_3_1, msg_3_2) = party_3.init_round_0();
+            party_1.init_round_1(msg_2_1, msg_3_1);
+            party_2.init_round_1(msg_1_2, msg_3_2);
+            party_3.init_round_1(msg_1_3, msg_2_3);
+        });
+    });
+
+    {
+        party_1.reset();
+        party_2.reset();
+        party_3.reset();
+        let (msg_1_2, msg_1_3) = party_1.init_round_0();
+        let (msg_2_1, msg_2_3) = party_2.init_round_0();
+        let (msg_3_1, msg_3_2) = party_3.init_round_0();
+        party_1.init_round_1(msg_2_1, msg_3_1);
+        party_2.init_round_1(msg_1_2, msg_3_2);
+        party_3.init_round_1(msg_1_3, msg_2_3);
+    }
+
+    for log_num_evaluations in LOG_NUM_EVALUATIONS {
+        group.bench_with_input(
+            BenchmarkId::new("preprocess", log_num_evaluations),
+            &log_num_evaluations,
+            |b, &log_num_evaluations| {
+                let num = 1 << log_num_evaluations;
+                b.iter(|| {
+                    party_1.reset_preprocessing();
+                    party_2.reset_preprocessing();
+                    party_3.reset_preprocessing();
+                    let (msg_1_2, msg_1_3) = party_1.preprocess_round_0(num);
+                    let (msg_2_1, msg_2_3) = party_2.preprocess_round_0(num);
+                    let (msg_3_1, msg_3_2) = party_3.preprocess_round_0(num);
+                    party_1.preprocess_round_1(num, msg_2_1, msg_3_1);
+                    party_2.preprocess_round_1(num, msg_1_2, msg_3_2);
+                    party_3.preprocess_round_1(num, msg_1_3, msg_2_3);
+                });
+            },
+        );
+    }
+
+    for log_num_evaluations in LOG_NUM_EVALUATIONS {
+        group.bench_with_input(
+            BenchmarkId::new("preprocess+eval", log_num_evaluations),
+            &log_num_evaluations,
+            |b, &log_num_evaluations| {
+                let num = 1 << log_num_evaluations;
+                let shares_1: Vec<Fp> = (0..num).map(|_| Fp::random(thread_rng())).collect();
+                let shares_2: Vec<Fp> = (0..num).map(|_| Fp::random(thread_rng())).collect();
+                let shares_3: Vec<Fp> = (0..num).map(|_| Fp::random(thread_rng())).collect();
+                b.iter(|| {
+                    let (msg_1_2, msg_1_3) = party_1.preprocess_round_0(num);
+                    let (msg_2_1, msg_2_3) = party_2.preprocess_round_0(num);
+                    let (msg_3_1, msg_3_2) = party_3.preprocess_round_0(num);
+                    party_1.preprocess_round_1(num, msg_2_1, msg_3_1);
+                    party_2.preprocess_round_1(num, msg_1_2, msg_3_2);
+                    party_3.preprocess_round_1(num, msg_1_3, msg_2_3);
+
+                    let (_, msg_1_3) = party_1.eval_round_0(num, &shares_1);
+                    let (_, msg_2_3) = party_2.eval_round_0(num, &shares_2);
+                    let (msg_3_1, _) = party_3.eval_round_1(num, &shares_3, &msg_1_3, &msg_2_3);
+                    let _masked_output = party_1.eval_round_2(num, &shares_1, (), msg_3_1);
+                    let _mask2 = party_2.eval_get_output(num);
+                    let _mask3 = party_3.eval_get_output(num);
+                });
+            },
+        );
+    }
+
+    group.finish();
+}
+
+criterion_group!(
+    name = benches;
+    config = Criterion::default().sample_size(10);
+    targets = bench_legendre_prf, bench_doprf, bench_masked_doprf
+);
+criterion_main!(benches);

+ 77 - 0
oram/benches/p_ot.rs

@@ -0,0 +1,77 @@
+use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
+use oram::p_ot::{POTIndexParty, POTKeyParty, POTReceiverParty};
+use rand::{thread_rng, Rng};
+use utils::field::Fp;
+use utils::permutation::FisherYatesPermutation;
+
+const LOG_DOMAIN_SIZES: [u32; 4] = [8, 12, 16, 20];
+
+pub fn bench_pot(c: &mut Criterion) {
+    let mut group = c.benchmark_group("POT");
+
+    for log_domain_size in LOG_DOMAIN_SIZES {
+        group.bench_with_input(
+            BenchmarkId::new("init", log_domain_size),
+            &log_domain_size,
+            |b, &log_domain_size| {
+                let mut key_party =
+                    POTKeyParty::<Fp, FisherYatesPermutation>::new(1 << log_domain_size);
+                let mut index_party =
+                    POTIndexParty::<Fp, FisherYatesPermutation>::new(1 << log_domain_size);
+                let mut receiver_party = POTReceiverParty::<Fp>::new(1 << log_domain_size);
+                b.iter(|| {
+                    key_party.reset();
+                    index_party.reset();
+                    receiver_party.reset();
+                    let (msg_to_index_party, msg_to_receiver_party) = key_party.init();
+                    index_party.init(msg_to_index_party.0, msg_to_index_party.1);
+                    receiver_party.init(msg_to_receiver_party);
+                });
+            },
+        );
+
+        group.bench_with_input(
+            BenchmarkId::new("expand", log_domain_size),
+            &log_domain_size,
+            |b, &log_domain_size| {
+                let mut key_party =
+                    POTKeyParty::<Fp, FisherYatesPermutation>::new(1 << log_domain_size);
+                key_party.init();
+                b.iter(|| {
+                    black_box(key_party.expand());
+                });
+            },
+        );
+
+        group.bench_with_input(
+            BenchmarkId::new("access", log_domain_size),
+            &log_domain_size,
+            |b, &log_domain_size| {
+                let mut key_party =
+                    POTKeyParty::<Fp, FisherYatesPermutation>::new(1 << log_domain_size);
+                let mut index_party =
+                    POTIndexParty::<Fp, FisherYatesPermutation>::new(1 << log_domain_size);
+                let mut receiver_party = POTReceiverParty::<Fp>::new(1 << log_domain_size);
+                let (msg_to_index_party, msg_to_receiver_party) = key_party.init();
+                index_party.init(msg_to_index_party.0, msg_to_index_party.1);
+                receiver_party.init(msg_to_receiver_party);
+                let index = thread_rng().gen_range(0..1 << log_domain_size);
+                b.iter(|| {
+                    let msg_to_receiver_party = index_party.access(index);
+                    let output =
+                        receiver_party.access(msg_to_receiver_party.0, msg_to_receiver_party.1);
+                    black_box(output);
+                });
+            },
+        );
+    }
+
+    group.finish();
+}
+
+criterion_group!(
+    name = benches;
+    config = Criterion::default().sample_size(10);
+    targets = bench_pot
+);
+criterion_main!(benches);

+ 406 - 0
oram/examples/bench_doprf.rs

@@ -0,0 +1,406 @@
+//! Benchmarking program for the DOPRF protocols.
+//!
+//! Use --help to see available options.
+
+use clap::{CommandFactory, Parser};
+use communicator::tcp::{make_tcp_communicator, NetworkOptions, NetworkPartyInfo};
+use communicator::AbstractCommunicator;
+use ff::Field;
+use oram::doprf::{
+    DOPrfParty1, DOPrfParty2, DOPrfParty3, JointDOPrf, MaskedDOPrfParty1, MaskedDOPrfParty2,
+    MaskedDOPrfParty3,
+};
+use rand::SeedableRng;
+use rand_chacha::ChaChaRng;
+use std::process;
+use std::time::{Duration, Instant};
+use utils::field::Fp;
+
+const PARTY_1: usize = 0;
+const PARTY_2: usize = 1;
+const PARTY_3: usize = 2;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, clap::ValueEnum, strum_macros::Display)]
+enum Mode {
+    Alternating,
+    Joint,
+    Masked,
+    Plain,
+}
+
+#[derive(Debug, clap::Parser)]
+struct Cli {
+    /// ID of this party
+    #[arg(long, short = 'i', value_parser = clap::value_parser!(u32).range(0..3))]
+    pub party_id: u32,
+    /// Output bitsize of the DOPrf
+    #[arg(long, short = 's', value_parser = clap::value_parser!(u32).range(1..))]
+    pub bitsize: u32,
+    /// Number of evaluations to compute
+    #[arg(long, short = 'n', value_parser = clap::value_parser!(u32).range(1..))]
+    pub num_evaluations: u32,
+    /// Which protocol variant to benchmark
+    #[arg(long, short = 'm', value_enum)]
+    pub mode: Mode,
+    /// Which address to listen on for incoming connections
+    #[arg(long, short = 'l')]
+    pub listen_host: String,
+    /// Which port to listen on for incoming connections
+    #[arg(long, short = 'p', value_parser = clap::value_parser!(u16).range(1..))]
+    pub listen_port: u16,
+    /// Connection info for each party
+    #[arg(long, short = 'c', value_name = "PARTY_ID>:<HOST>:<PORT", value_parser = parse_connect)]
+    pub connect: Vec<(usize, String, u16)>,
+    /// How long to try connecting before aborting
+    #[arg(long, short = 't', default_value_t = 10)]
+    pub connect_timeout_seconds: usize,
+}
+
+fn parse_connect(
+    s: &str,
+) -> Result<(usize, String, u16), Box<dyn std::error::Error + Send + Sync + 'static>> {
+    let parts: Vec<_> = s.split(":").collect();
+    if parts.len() != 3 {
+        return Err(clap::Error::raw(
+            clap::error::ErrorKind::ValueValidation,
+            format!("'{}' has not the format '<party-id>:<host>:<post>'", s),
+        )
+        .into());
+    }
+    let party_id: usize = parts[0].parse()?;
+    let host = parts[1];
+    let port: u16 = parts[2].parse()?;
+    if port == 0 {
+        return Err(clap::Error::raw(
+            clap::error::ErrorKind::ValueValidation,
+            "the port needs to be positive",
+        )
+        .into());
+    }
+    Ok((party_id, host.to_owned(), port))
+}
+
+fn make_random_shares(n: usize) -> Vec<Fp> {
+    let mut rng = ChaChaRng::from_seed([0u8; 32]);
+    (0..n).map(|_| Fp::random(&mut rng)).collect()
+}
+
+fn bench_plain<C: AbstractCommunicator>(
+    comm: &mut C,
+    bitsize: usize,
+    num_evaluations: usize,
+) -> (Duration, Duration, Duration) {
+    let shares = make_random_shares(num_evaluations);
+    match comm.get_my_id() {
+        PARTY_1 => {
+            let mut p1 = DOPrfParty1::<Fp>::new(bitsize);
+            let t_start = Instant::now();
+            p1.init(comm).expect("init failed");
+            let t_after_init = Instant::now();
+            p1.preprocess(comm, num_evaluations)
+                .expect("preprocess failed");
+            let t_after_preprocess = Instant::now();
+            for i in 0..num_evaluations {
+                p1.eval(comm, 1, &[shares[i]]).expect("eval failed");
+            }
+            let t_after_eval = Instant::now();
+            (
+                t_after_init - t_start,
+                t_after_preprocess - t_after_init,
+                t_after_eval - t_after_preprocess,
+            )
+        }
+        PARTY_2 => {
+            let mut p2 = DOPrfParty2::<Fp>::new(bitsize);
+            let t_start = Instant::now();
+            p2.init(comm).expect("init failed");
+            let t_after_init = Instant::now();
+            p2.preprocess(comm, num_evaluations)
+                .expect("preprocess failed");
+            let t_after_preprocess = Instant::now();
+            for i in 0..num_evaluations {
+                p2.eval(comm, 1, &[shares[i]]).expect("eval failed");
+            }
+            let t_after_eval = Instant::now();
+            (
+                t_after_init - t_start,
+                t_after_preprocess - t_after_init,
+                t_after_eval - t_after_preprocess,
+            )
+        }
+        PARTY_3 => {
+            let mut p3 = DOPrfParty3::<Fp>::new(bitsize);
+            let t_start = Instant::now();
+            p3.init(comm).expect("init failed");
+            let t_after_init = Instant::now();
+            p3.preprocess(comm, num_evaluations)
+                .expect("preprocess failed");
+            let t_after_preprocess = Instant::now();
+            for i in 0..num_evaluations {
+                p3.eval(comm, 1, &[shares[i]]).expect("eval failed");
+            }
+            let t_after_eval = Instant::now();
+            (
+                t_after_init - t_start,
+                t_after_preprocess - t_after_init,
+                t_after_eval - t_after_preprocess,
+            )
+        }
+        _ => panic!("invalid party id"),
+    }
+}
+
+fn bench_masked<C: AbstractCommunicator>(
+    comm: &mut C,
+    bitsize: usize,
+    num_evaluations: usize,
+) -> (Duration, Duration, Duration) {
+    let shares = make_random_shares(num_evaluations);
+    match comm.get_my_id() {
+        PARTY_1 => {
+            let mut p1 = MaskedDOPrfParty1::<Fp>::new(bitsize);
+            let t_start = Instant::now();
+            p1.init(comm).expect("init failed");
+            let t_after_init = Instant::now();
+            p1.preprocess(comm, num_evaluations)
+                .expect("preprocess failed");
+            let t_after_preprocess = Instant::now();
+            for i in 0..num_evaluations {
+                p1.eval(comm, 1, &[shares[i]]).expect("eval failed");
+            }
+            let t_after_eval = Instant::now();
+            (
+                t_after_init - t_start,
+                t_after_preprocess - t_after_init,
+                t_after_eval - t_after_preprocess,
+            )
+        }
+        PARTY_2 => {
+            let mut p2 = MaskedDOPrfParty2::<Fp>::new(bitsize);
+            let t_start = Instant::now();
+            p2.init(comm).expect("init failed");
+            let t_after_init = Instant::now();
+            p2.preprocess(comm, num_evaluations)
+                .expect("preprocess failed");
+            let t_after_preprocess = Instant::now();
+            for i in 0..num_evaluations {
+                p2.eval(comm, 1, &[shares[i]]).expect("eval failed");
+            }
+            let t_after_eval = Instant::now();
+            (
+                t_after_init - t_start,
+                t_after_preprocess - t_after_init,
+                t_after_eval - t_after_preprocess,
+            )
+        }
+        PARTY_3 => {
+            let mut p3 = MaskedDOPrfParty3::<Fp>::new(bitsize);
+            let t_start = Instant::now();
+            p3.init(comm).expect("init failed");
+            let t_after_init = Instant::now();
+            p3.preprocess(comm, num_evaluations)
+                .expect("preprocess failed");
+            let t_after_preprocess = Instant::now();
+            for i in 0..num_evaluations {
+                p3.eval(comm, 1, &[shares[i]]).expect("eval failed");
+            }
+            let t_after_eval = Instant::now();
+            (
+                t_after_init - t_start,
+                t_after_preprocess - t_after_init,
+                t_after_eval - t_after_preprocess,
+            )
+        }
+        _ => panic!("invalid party id"),
+    }
+}
+
+fn bench_joint<C: AbstractCommunicator>(
+    comm: &mut C,
+    bitsize: usize,
+    num_evaluations: usize,
+) -> (Duration, Duration, Duration) {
+    let shares = make_random_shares(num_evaluations);
+    let mut p = JointDOPrf::<Fp>::new(bitsize);
+    let t_start = Instant::now();
+    p.init(comm).expect("init failed");
+    let t_after_init = Instant::now();
+    p.preprocess(comm, num_evaluations)
+        .expect("preprocess failed");
+    let t_after_preprocess = Instant::now();
+    for i in 0..num_evaluations {
+        p.eval_to_uint::<_, u128>(comm, &[shares[i]])
+            .expect("eval failed");
+    }
+    let t_after_eval = Instant::now();
+    (
+        t_after_init - t_start,
+        t_after_preprocess - t_after_init,
+        t_after_eval - t_after_preprocess,
+    )
+}
+
+fn bench_alternating<C: AbstractCommunicator>(
+    comm: &mut C,
+    bitsize: usize,
+    num_evaluations: usize,
+) -> (Duration, Duration, Duration) {
+    let shares = make_random_shares(num_evaluations);
+    let mut p1 = DOPrfParty1::<Fp>::new(bitsize);
+    let mut p2 = DOPrfParty2::<Fp>::new(bitsize);
+    let mut p3 = DOPrfParty3::<Fp>::new(bitsize);
+    match comm.get_my_id() {
+        PARTY_1 => {
+            let t_start = Instant::now();
+            p1.init(comm).expect("init failed");
+            p2.init(comm).expect("init failed");
+            p3.init(comm).expect("init failed");
+            let t_after_init = Instant::now();
+            p1.preprocess(comm, num_evaluations)
+                .expect("preprocess failed");
+            p2.preprocess(comm, num_evaluations)
+                .expect("preprocess failed");
+            p3.preprocess(comm, num_evaluations)
+                .expect("preprocess failed");
+            let t_after_preprocess = Instant::now();
+            for i in 0..num_evaluations {
+                p1.eval(comm, 1, &[shares[i]]).expect("eval failed");
+                p2.eval(comm, 1, &[shares[i]]).expect("eval failed");
+                p3.eval(comm, 1, &[shares[i]]).expect("eval failed");
+            }
+            let t_after_eval = Instant::now();
+            (
+                t_after_init - t_start,
+                t_after_preprocess - t_after_init,
+                t_after_eval - t_after_preprocess,
+            )
+        }
+        PARTY_2 => {
+            let t_start = Instant::now();
+            p2.init(comm).expect("init failed");
+            p3.init(comm).expect("init failed");
+            p1.init(comm).expect("init failed");
+            let t_after_init = Instant::now();
+            p2.preprocess(comm, num_evaluations)
+                .expect("preprocess failed");
+            p3.preprocess(comm, num_evaluations)
+                .expect("preprocess failed");
+            p1.preprocess(comm, num_evaluations)
+                .expect("preprocess failed");
+            let t_after_preprocess = Instant::now();
+            for i in 0..num_evaluations {
+                p2.eval(comm, 1, &[shares[i]]).expect("eval failed");
+                p3.eval(comm, 1, &[shares[i]]).expect("eval failed");
+                p1.eval(comm, 1, &[shares[i]]).expect("eval failed");
+            }
+            let t_after_eval = Instant::now();
+            (
+                t_after_init - t_start,
+                t_after_preprocess - t_after_init,
+                t_after_eval - t_after_preprocess,
+            )
+        }
+        PARTY_3 => {
+            let t_start = Instant::now();
+            p3.init(comm).expect("init failed");
+            p1.init(comm).expect("init failed");
+            p2.init(comm).expect("init failed");
+            let t_after_init = Instant::now();
+            p3.preprocess(comm, num_evaluations)
+                .expect("preprocess failed");
+            p1.preprocess(comm, num_evaluations)
+                .expect("preprocess failed");
+            p2.preprocess(comm, num_evaluations)
+                .expect("preprocess failed");
+            let t_after_preprocess = Instant::now();
+            for i in 0..num_evaluations {
+                p3.eval(comm, 1, &[shares[i]]).expect("eval failed");
+                p1.eval(comm, 1, &[shares[i]]).expect("eval failed");
+                p2.eval(comm, 1, &[shares[i]]).expect("eval failed");
+            }
+            let t_after_eval = Instant::now();
+            (
+                t_after_init - t_start,
+                t_after_preprocess - t_after_init,
+                t_after_eval - t_after_preprocess,
+            )
+        }
+        _ => panic!("invalid party id"),
+    }
+}
+
+fn main() {
+    let cli = Cli::parse();
+
+    let mut netopts = NetworkOptions {
+        listen_host: cli.listen_host,
+        listen_port: cli.listen_port,
+        connect_info: vec![NetworkPartyInfo::Listen; 3],
+        connect_timeout_seconds: cli.connect_timeout_seconds,
+    };
+
+    for c in cli.connect {
+        if netopts.connect_info[c.0] != NetworkPartyInfo::Listen {
+            println!(
+                "{}",
+                clap::Error::raw(
+                    clap::error::ErrorKind::ValueValidation,
+                    format!("multiple connect arguments for party {}", c.0),
+                )
+                .format(&mut Cli::command())
+            );
+            process::exit(1);
+        }
+        netopts.connect_info[c.0] = NetworkPartyInfo::Connect(c.1, c.2);
+    }
+
+    let mut comm = match make_tcp_communicator(3, cli.party_id as usize, &netopts) {
+        Ok(comm) => comm,
+        Err(e) => {
+            eprintln!("network setup failed: {:?}", e);
+            process::exit(1);
+        }
+    };
+
+    let (d_init, d_preprocess, d_eval) = match cli.mode {
+        Mode::Plain => bench_plain(
+            &mut comm,
+            cli.bitsize as usize,
+            cli.num_evaluations as usize,
+        ),
+        Mode::Masked => bench_masked(
+            &mut comm,
+            cli.bitsize as usize,
+            cli.num_evaluations as usize,
+        ),
+        Mode::Joint => bench_joint(
+            &mut comm,
+            cli.bitsize as usize,
+            cli.num_evaluations as usize,
+        ),
+        Mode::Alternating => bench_alternating(
+            &mut comm,
+            cli.bitsize as usize,
+            cli.num_evaluations as usize,
+        ),
+    };
+
+    comm.shutdown();
+
+    println!("=========== DOPrf ============");
+    println!("mode: {}", cli.mode);
+    println!("- {} bit output", cli.bitsize);
+    println!("- {} evaluations", cli.num_evaluations);
+    println!("time init:        {:3.3} s", d_init.as_secs_f64());
+    println!("time preprocess:  {:3.3} s", d_preprocess.as_secs_f64());
+    println!(
+        "  per evaluation: {:3.3} s",
+        d_preprocess.as_secs_f64() / cli.num_evaluations as f64
+    );
+    println!("time eval:        {:3.3} s", d_eval.as_secs_f64());
+    println!(
+        "  per evaluation: {:3.3} s",
+        d_eval.as_secs_f64() / cli.num_evaluations as f64
+    );
+    println!("==============================");
+}

+ 306 - 0
oram/examples/bench_doram.rs

@@ -0,0 +1,306 @@
+//! Benchmarking program for the DORAM protocol.
+//!
+//! Use --help to see available options.
+
+use clap::{CommandFactory, Parser};
+use communicator::tcp::{make_tcp_communicator, NetworkOptions, NetworkPartyInfo};
+use communicator::{AbstractCommunicator, CommunicationStats};
+use dpf::mpdpf::SmartMpDpf;
+use dpf::spdpf::HalfTreeSpDpf;
+use ff::{Field, PrimeField};
+use oram::common::{InstructionShare, Operation};
+use oram::oram::{
+    DistributedOram, DistributedOramProtocol, ProtocolStep as OramProtocolStep,
+    Runtimes as OramRuntimes,
+};
+use oram::tools::BenchmarkMetaData;
+use rand::{Rng, SeedableRng};
+use rand_chacha::ChaChaRng;
+use rayon;
+use serde;
+use serde_json;
+use std::collections::HashMap;
+use std::process;
+use std::time::{Duration, Instant};
+use strum::IntoEnumIterator;
+use utils::field::Fp;
+use utils::hash::AesHashFunction;
+
+type MPDPF = SmartMpDpf<Fp, HalfTreeSpDpf<Fp>, AesHashFunction<u16>>;
+type DOram = DistributedOramProtocol<Fp, MPDPF, HalfTreeSpDpf<Fp>>;
+
+#[derive(Debug, clap::Parser)]
+struct Cli {
+    /// ID of this party
+    #[arg(long, short = 'i', value_parser = clap::value_parser!(u32).range(0..3))]
+    pub party_id: u32,
+    /// Log2 of the database size, must be even
+    #[arg(long, short = 's', value_parser = clap::value_parser!(u32).range(4..))]
+    pub log_db_size: u32,
+    /// Use preprocessing
+    #[arg(long)]
+    pub preprocess: bool,
+    /// How many threads to use for the computation
+    #[arg(long, short = 't', default_value_t = 1)]
+    pub threads: usize,
+    /// How many threads to use for the preprocessing phase (default: same as -t)
+    #[arg(long, default_value_t = -1)]
+    pub threads_prep: isize,
+    /// How many threads to use for the online phase (default: same as -t)
+    #[arg(long, default_value_t = -1)]
+    pub threads_online: isize,
+    /// Output statistics in JSON
+    #[arg(long, short = 'j')]
+    pub json: bool,
+    /// Which address to listen on for incoming connections
+    #[arg(long, short = 'l')]
+    pub listen_host: String,
+    /// Which port to listen on for incoming connections
+    #[arg(long, short = 'p', value_parser = clap::value_parser!(u16).range(1..))]
+    pub listen_port: u16,
+    /// Connection info for each party
+    #[arg(long, short = 'c', value_name = "PARTY_ID>:<HOST>:<PORT", value_parser = parse_connect)]
+    pub connect: Vec<(usize, String, u16)>,
+    /// How long to try connecting before aborting
+    #[arg(long, default_value_t = 10)]
+    pub connect_timeout_seconds: usize,
+}
+
+#[derive(Debug, Clone, serde::Serialize)]
+struct BenchmarkResults {
+    party_id: usize,
+    log_db_size: u32,
+    preprocess: bool,
+    threads_prep: usize,
+    threads_online: usize,
+    comm_stats_preprocess: HashMap<usize, CommunicationStats>,
+    comm_stats_access: HashMap<usize, CommunicationStats>,
+    runtimes: HashMap<String, Duration>,
+    meta: BenchmarkMetaData,
+}
+
+impl BenchmarkResults {
+    pub fn new(
+        cli: &Cli,
+        comm_stats_preprocess: &HashMap<usize, CommunicationStats>,
+        comm_stats_access: &HashMap<usize, CommunicationStats>,
+        runtimes: &OramRuntimes,
+    ) -> Self {
+        let mut runtime_map = HashMap::new();
+        for step in OramProtocolStep::iter() {
+            runtime_map.insert(step.to_string(), runtimes.get(step));
+        }
+
+        let threads_prep = if cli.threads_prep < 0 {
+            cli.threads
+        } else {
+            cli.threads_prep as usize
+        };
+        let threads_online = if cli.threads_online < 0 {
+            cli.threads
+        } else {
+            cli.threads_online as usize
+        };
+
+        Self {
+            party_id: cli.party_id as usize,
+            log_db_size: cli.log_db_size,
+            preprocess: cli.preprocess,
+            threads_prep,
+            threads_online,
+            comm_stats_preprocess: comm_stats_preprocess.clone(),
+            comm_stats_access: comm_stats_access.clone(),
+            runtimes: runtime_map,
+            meta: BenchmarkMetaData::collect(),
+        }
+    }
+}
+
+fn parse_connect(
+    s: &str,
+) -> Result<(usize, String, u16), Box<dyn std::error::Error + Send + Sync + 'static>> {
+    let parts: Vec<_> = s.split(":").collect();
+    if parts.len() != 3 {
+        return Err(clap::Error::raw(
+            clap::error::ErrorKind::ValueValidation,
+            format!("'{}' has not the format '<party-id>:<host>:<post>'", s),
+        )
+        .into());
+    }
+    let party_id: usize = parts[0].parse()?;
+    let host = parts[1];
+    let port: u16 = parts[2].parse()?;
+    if port == 0 {
+        return Err(clap::Error::raw(
+            clap::error::ErrorKind::ValueValidation,
+            "the port needs to be positive",
+        )
+        .into());
+    }
+    Ok((party_id, host.to_owned(), port))
+}
+
+fn main() {
+    let cli = Cli::parse();
+
+    let mut netopts = NetworkOptions {
+        listen_host: cli.listen_host.clone(),
+        listen_port: cli.listen_port,
+        connect_info: vec![NetworkPartyInfo::Listen; 3],
+        connect_timeout_seconds: cli.connect_timeout_seconds,
+    };
+
+    let threads_prep = if cli.threads_prep < 0 {
+        cli.threads
+    } else {
+        cli.threads_prep as usize
+    };
+    let threads_online = if cli.threads_online < 0 {
+        cli.threads
+    } else {
+        cli.threads_online as usize
+    };
+
+    rayon::ThreadPoolBuilder::new()
+        .thread_name(|i| format!("thread-global-{i}"))
+        .build_global()
+        .unwrap();
+
+    for c in cli.connect.iter() {
+        if netopts.connect_info[c.0] != NetworkPartyInfo::Listen {
+            println!(
+                "{}",
+                clap::Error::raw(
+                    clap::error::ErrorKind::ValueValidation,
+                    format!("multiple connect arguments for party {}", c.0),
+                )
+                .format(&mut Cli::command())
+            );
+            process::exit(1);
+        }
+        netopts.connect_info[c.0] = NetworkPartyInfo::Connect(c.1.clone(), c.2);
+    }
+
+    let mut comm = match make_tcp_communicator(3, cli.party_id as usize, &netopts) {
+        Ok(comm) => comm,
+        Err(e) => {
+            eprintln!("network setup failed: {:?}", e);
+            process::exit(1);
+        }
+    };
+
+    let mut doram = DOram::new(cli.party_id as usize, 1 << cli.log_db_size);
+
+    let db_size = 1 << cli.log_db_size;
+    let db_share: Vec<_> = vec![Fp::ZERO; db_size];
+    let num_accesses_per_epoch = doram.get_stash_size();
+
+    let instructions = if cli.party_id == 0 {
+        let mut rng = ChaChaRng::from_seed([0u8; 32]);
+        (0..num_accesses_per_epoch)
+            .map(|_| InstructionShare {
+                operation: Operation::Write.encode(),
+                address: Fp::from_u128(rng.gen_range(0..db_size) as u128),
+                value: Fp::random(&mut rng),
+            })
+            .collect()
+    } else {
+        vec![
+            InstructionShare {
+                operation: Fp::ZERO,
+                address: Fp::ZERO,
+                value: Fp::ZERO
+            };
+            num_accesses_per_epoch
+        ]
+    };
+
+    doram.init(&mut comm, &db_share).expect("init failed");
+
+    let thread_pool_prep = rayon::ThreadPoolBuilder::new()
+        .thread_name(|i| format!("thread-prep-{i}"))
+        .num_threads(threads_prep)
+        .build()
+        .unwrap();
+
+    comm.reset_stats();
+    let mut runtimes = OramRuntimes::default();
+
+    let d_preprocess = if cli.preprocess {
+        let t_start = Instant::now();
+
+        runtimes = thread_pool_prep.install(|| {
+            doram
+                .preprocess_with_runtimes(&mut comm, 1, Some(runtimes))
+                .expect("preprocess failed")
+                .unwrap()
+        });
+
+        t_start.elapsed()
+    } else {
+        Default::default()
+    };
+
+    drop(thread_pool_prep);
+
+    let comm_stats_preprocess = comm.get_stats();
+    comm.reset_stats();
+
+    let thread_pool_online = rayon::ThreadPoolBuilder::new()
+        .thread_name(|i| format!("thread-online-{i}"))
+        .num_threads(threads_online)
+        .build()
+        .unwrap();
+
+    let t_start = Instant::now();
+    runtimes = thread_pool_online.install(|| {
+        for (_i, inst) in instructions.iter().enumerate() {
+            // println!("executing instruction #{i}: {inst:?}");
+            runtimes = doram
+                .access_with_runtimes(&mut comm, *inst, Some(runtimes))
+                .expect("access failed")
+                .1
+                .unwrap();
+        }
+        runtimes
+    });
+    let d_accesses = Instant::now() - t_start;
+
+    let comm_stats_access = comm.get_stats();
+
+    drop(thread_pool_online);
+
+    comm.shutdown();
+
+    let results =
+        BenchmarkResults::new(&cli, &comm_stats_preprocess, &comm_stats_access, &runtimes);
+
+    if cli.json {
+        println!("{}", serde_json::to_string(&results).unwrap());
+    } else {
+        println!(
+            "time preprocess:  {:10.3} ms",
+            d_preprocess.as_secs_f64() * 1000.0
+        );
+        println!(
+            "   per accesses:  {:10.3} ms",
+            d_preprocess.as_secs_f64() * 1000.0 / num_accesses_per_epoch as f64
+        );
+        println!(
+            "time accesses:    {:10.3} ms{}",
+            d_accesses.as_secs_f64() * 1000.0,
+            if cli.preprocess {
+                "  (online only)"
+            } else {
+                ""
+            }
+        );
+        println!(
+            "   per accesses:  {:10.3} ms",
+            d_accesses.as_secs_f64() * 1000.0 / num_accesses_per_epoch as f64
+        );
+        runtimes.print(cli.party_id as usize + 1, num_accesses_per_epoch);
+        println!("communication preprocessing: {comm_stats_preprocess:#?}");
+        println!("communication accesses: {comm_stats_access:#?}");
+    }
+}

+ 58 - 0
oram/src/common.rs

@@ -0,0 +1,58 @@
+//! Basic types for the DORAM implementation.
+use communicator::Error as CommunicationError;
+use ff::PrimeField;
+
+/// Type of an access operation.
+pub enum Operation {
+    /// Read from the memory.
+    Read,
+    /// Write to the memory.
+    Write,
+}
+
+impl Operation {
+    /// Encode an access operation as field element.
+    ///
+    /// Read is encoded as 0, and Write is encoded as 1.
+    pub fn encode<F: PrimeField>(&self) -> F {
+        match self {
+            Self::Read => F::ZERO,
+            Self::Write => F::ONE,
+        }
+    }
+
+    /// Decode an encoded operation again.
+    pub fn decode<F: PrimeField>(encoded_op: F) -> Self {
+        if encoded_op == F::ZERO {
+            Self::Read
+        } else if encoded_op == F::ONE {
+            Self::Write
+        } else {
+            panic!("invalid value")
+        }
+    }
+}
+
+/// Define an additive share of an access instruction.
+#[derive(Clone, Copy, Debug, Default)]
+pub struct InstructionShare<F: PrimeField> {
+    /// Whether it is a read (0) or a write (1).
+    pub operation: F,
+    /// The address to access.
+    pub address: F,
+    /// The value that should (possibly) be written into memory.
+    pub value: F,
+}
+
+/// Custom error type used in this library.
+#[derive(Debug)]
+pub enum Error {
+    /// Wrap a [`communicator::Error`].
+    CommunicationError(CommunicationError),
+}
+
+impl From<CommunicationError> for Error {
+    fn from(e: CommunicationError) -> Self {
+        Error::CommunicationError(e)
+    }
+}

+ 1908 - 0
oram/src/doprf.rs

@@ -0,0 +1,1908 @@
+//! Implementation of the Legendre PRF and protocols for the distributed oblivious PRF evaluation.
+//!
+//! Contains the unmasked and the masked variants.
+
+use crate::common::Error;
+use bincode;
+use bitvec;
+use communicator::{AbstractCommunicator, Fut, Serializable};
+use core::marker::PhantomData;
+use funty::Unsigned;
+use itertools::izip;
+use rand::{thread_rng, Rng, RngCore, SeedableRng};
+use rand_chacha::ChaChaRng;
+use std::iter::repeat;
+use utils::field::LegendreSymbol;
+
+/// Bit vector.
+pub type BitVec = bitvec::vec::BitVec<u8>;
+type BitSlice = bitvec::slice::BitSlice<u8>;
+
+/// Key for a [`LegendrePrf`].
+#[derive(Clone, Debug, Eq, PartialEq, bincode::Encode, bincode::Decode)]
+pub struct LegendrePrfKey<F: LegendreSymbol> {
+    /// Keys for each output bit.
+    pub keys: Vec<F>,
+}
+
+impl<F: LegendreSymbol> LegendrePrfKey<F> {
+    /// Return the number of bits output by the PRF.
+    pub fn get_output_bitsize(&self) -> usize {
+        self.keys.len()
+    }
+}
+
+/// Multi-bit Legendre PRF: `F x F -> {0,1}^k`.
+pub struct LegendrePrf<F> {
+    _phantom: PhantomData<F>,
+}
+
+impl<F: LegendreSymbol> LegendrePrf<F> {
+    /// Generate a Legendre PRF key for the given number of output bits.
+    pub fn key_gen(output_bitsize: usize) -> LegendrePrfKey<F> {
+        LegendrePrfKey {
+            keys: (0..output_bitsize)
+                .map(|_| F::random(thread_rng()))
+                .collect(),
+        }
+    }
+
+    /// Evaluate the PRF to obtain an iterator of bits.
+    pub fn eval(key: &LegendrePrfKey<F>, input: F) -> impl Iterator<Item = bool> + '_ {
+        key.keys.iter().map(move |&k| {
+            let ls = F::legendre_symbol(k + input);
+            debug_assert!(ls != 0, "unlikely");
+            ls == 1
+        })
+    }
+
+    /// Evaluate the PRF to obtain a bit vector.
+    pub fn eval_bits(key: &LegendrePrfKey<F>, input: F) -> BitVec {
+        let mut output = BitVec::with_capacity(key.keys.len());
+        output.extend(Self::eval(key, input));
+        output
+    }
+
+    /// Evaluate the PRF to obtain an integer.
+    pub fn eval_to_uint<T: Unsigned>(key: &LegendrePrfKey<F>, input: F) -> T {
+        assert!(key.keys.len() <= T::BITS as usize);
+        let mut output = T::ZERO;
+        for (i, b) in Self::eval(key, input).enumerate() {
+            if b {
+                output |= T::ONE << i;
+            }
+        }
+        output
+    }
+}
+
+fn to_uint<T: Unsigned>(vs: impl IntoIterator<Item = impl IntoIterator<Item = bool>>) -> Vec<T> {
+    vs.into_iter()
+        .map(|v| {
+            let mut output = T::ZERO;
+            for (i, b) in v.into_iter().enumerate() {
+                if b {
+                    output |= T::ONE << i;
+                }
+            }
+            output
+        })
+        .collect()
+}
+
+type SharedSeed = [u8; 32];
+
+/// Party 1 of the *unmasked* DOPRF protocol.
+pub struct DOPrfParty1<F: LegendreSymbol> {
+    _phantom: PhantomData<F>,
+    output_bitsize: usize,
+    shared_prg_1_2: Option<ChaChaRng>,
+    shared_prg_1_3: Option<ChaChaRng>,
+    legendre_prf_key: Option<LegendrePrfKey<F>>,
+    is_initialized: bool,
+    num_preprocessed_invocations: usize,
+    preprocessed_squares: Vec<F>,
+    preprocessed_mt_c1: Vec<F>,
+}
+
+impl<F> DOPrfParty1<F>
+where
+    F: LegendreSymbol,
+{
+    /// Create a new instance with the given `output_bitsize`.
+    pub fn new(output_bitsize: usize) -> Self {
+        assert!(output_bitsize > 0);
+        Self {
+            _phantom: PhantomData,
+            output_bitsize,
+            shared_prg_1_2: None,
+            shared_prg_1_3: None,
+            legendre_prf_key: None,
+            is_initialized: false,
+            num_preprocessed_invocations: 0,
+            preprocessed_squares: Default::default(),
+            preprocessed_mt_c1: Default::default(),
+        }
+    }
+
+    /// Create an instance from an existing Legendre PRF key.
+    pub fn from_legendre_prf_key(legendre_prf_key: LegendrePrfKey<F>) -> Self {
+        let mut new = Self::new(legendre_prf_key.keys.len());
+        new.legendre_prf_key = Some(legendre_prf_key);
+        new
+    }
+
+    /// Reset this instance.
+    pub fn reset(&mut self) {
+        *self = Self::new(self.output_bitsize)
+    }
+
+    /// Delete all preprocessed data.
+    pub fn reset_preprocessing(&mut self) {
+        self.num_preprocessed_invocations = 0;
+        self.preprocessed_squares = Default::default();
+        self.preprocessed_mt_c1 = Default::default();
+    }
+
+    /// Step 0 of the initialization protocol.
+    pub fn init_round_0(&mut self) -> (SharedSeed, ()) {
+        assert!(!self.is_initialized);
+        // sample and share a PRF key with Party 2
+        self.shared_prg_1_2 = Some(ChaChaRng::from_seed(thread_rng().gen()));
+        (self.shared_prg_1_2.as_ref().unwrap().get_seed(), ())
+    }
+
+    /// Step 1 of the initialization protocol.
+    pub fn init_round_1(&mut self, _: (), shared_prg_seed_1_3: SharedSeed) {
+        assert!(!self.is_initialized);
+        // receive shared PRF key from Party 3
+        self.shared_prg_1_3 = Some(ChaChaRng::from_seed(shared_prg_seed_1_3));
+        if self.legendre_prf_key.is_none() {
+            // generate Legendre PRF key
+            self.legendre_prf_key = Some(LegendrePrf::key_gen(self.output_bitsize));
+        }
+        self.is_initialized = true;
+    }
+
+    /// Run the initialization protocol.
+    pub fn init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error> {
+        let fut_3_1 = comm.receive_previous()?;
+        let (msg_1_2, _) = self.init_round_0();
+        comm.send_next(msg_1_2)?;
+        self.init_round_1((), fut_3_1.get()?);
+        Ok(())
+    }
+
+    /// Return the Legendre PRF key.
+    pub fn get_legendre_prf_key(&self) -> LegendrePrfKey<F> {
+        assert!(self.legendre_prf_key.is_some());
+        self.legendre_prf_key.as_ref().unwrap().clone()
+    }
+
+    /// Set the Legendre PRF key.
+    ///
+    /// Can only done before the initialization protocol is run.
+    pub fn set_legendre_prf_key(&mut self, legendre_prf_key: LegendrePrfKey<F>) {
+        assert!(!self.is_initialized);
+        self.legendre_prf_key = Some(legendre_prf_key);
+    }
+
+    /// Step 0 of the preprocessing protocol.
+    pub fn preprocess_round_0(&mut self, num: usize) -> ((), ()) {
+        assert!(self.is_initialized);
+        let n = num * self.output_bitsize;
+        self.preprocessed_squares
+            .extend((0..n).map(|_| F::random(self.shared_prg_1_2.as_mut().unwrap()).square()));
+        ((), ())
+    }
+
+    /// Step 1 of the preprocessing protocol.
+    pub fn preprocess_round_1(&mut self, num: usize, preprocessed_mt_c1: Vec<F>, _: ()) {
+        assert!(self.is_initialized);
+        let n = num * self.output_bitsize;
+        assert_eq!(preprocessed_mt_c1.len(), n);
+        self.preprocessed_mt_c1.extend(preprocessed_mt_c1);
+        self.num_preprocessed_invocations += num;
+    }
+
+    /// Run the preprocessing protocol for `num` evaluations.
+    pub fn preprocess<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        num: usize,
+    ) -> Result<(), Error>
+    where
+        F: Serializable,
+    {
+        let fut_2_1 = comm.receive_next()?;
+        self.preprocess_round_0(num);
+        self.preprocess_round_1(num, fut_2_1.get()?, ());
+        Ok(())
+    }
+
+    /// Return the number of preprocessed invocations available.
+    pub fn get_num_preprocessed_invocations(&self) -> usize {
+        self.num_preprocessed_invocations
+    }
+
+    /// Return the preprocessed data.
+    #[doc(hidden)]
+    pub fn get_preprocessed_data(&self) -> (&[F], &[F]) {
+        (&self.preprocessed_squares, &self.preprocessed_mt_c1)
+    }
+
+    /// Perform some self-test of the preprocessed data.
+    #[doc(hidden)]
+    pub fn check_preprocessing(&self) {
+        let num = self.num_preprocessed_invocations;
+        let n = num * self.output_bitsize;
+        assert_eq!(self.preprocessed_squares.len(), n);
+        assert_eq!(self.preprocessed_mt_c1.len(), n);
+    }
+
+    /// Step 1 of the evaluation protocol.
+    pub fn eval_round_1(
+        &mut self,
+        num: usize,
+        shares1: &[F],
+        masked_shares2: &[F],
+        mult_e: &[F],
+    ) -> ((), Vec<F>) {
+        assert!(num <= self.num_preprocessed_invocations);
+        let n = num * self.output_bitsize;
+        assert_eq!(shares1.len(), num);
+        assert_eq!(masked_shares2.len(), num);
+        assert_eq!(mult_e.len(), num);
+        let k = &self.legendre_prf_key.as_ref().unwrap().keys;
+        assert_eq!(k.len(), self.output_bitsize);
+        let output_shares_z1: Vec<F> = izip!(
+            shares1
+                .iter()
+                .flat_map(|s1i| repeat(s1i).take(self.output_bitsize)),
+            masked_shares2
+                .iter()
+                .flat_map(|ms2i| repeat(ms2i).take(self.output_bitsize)),
+            k.iter().cycle(),
+            self.preprocessed_squares.drain(0..n),
+            self.preprocessed_mt_c1.drain(0..n),
+            mult_e
+                .iter()
+                .flat_map(|e| repeat(e).take(self.output_bitsize)),
+        )
+        .map(|(&s1_i, &ms2_i, &k_j, sq_ij, c1_ij, &e_ij)| {
+            sq_ij * (k_j + s1_i + ms2_i) + e_ij * sq_ij + c1_ij
+        })
+        .collect();
+        self.num_preprocessed_invocations -= num;
+        ((), output_shares_z1)
+    }
+
+    /// Run the evaluation protocol.
+    pub fn eval<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        num: usize,
+        shares1: &[F],
+    ) -> Result<(), Error>
+    where
+        F: Serializable,
+    {
+        assert_eq!(shares1.len(), num);
+        let fut_2_1 = comm.receive_next::<Vec<_>>()?;
+        let fut_3_1 = comm.receive_previous::<Vec<_>>()?;
+        let (_, msg_1_3) = self.eval_round_1(num, shares1, &fut_2_1.get()?, &fut_3_1.get()?);
+        comm.send_previous(msg_1_3)?;
+        Ok(())
+    }
+}
+
+/// Party 2 of the *unmasked* DOPRF protocol.
+pub struct DOPrfParty2<F: LegendreSymbol> {
+    _phantom: PhantomData<F>,
+    output_bitsize: usize,
+    shared_prg_1_2: Option<ChaChaRng>,
+    shared_prg_2_3: Option<ChaChaRng>,
+    is_initialized: bool,
+    num_preprocessed_invocations: usize,
+    preprocessed_rerand_m2: Vec<F>,
+}
+
+impl<F> DOPrfParty2<F>
+where
+    F: LegendreSymbol,
+{
+    /// Create a new instance with the given `output_bitsize`.
+    pub fn new(output_bitsize: usize) -> Self {
+        assert!(output_bitsize > 0);
+        Self {
+            _phantom: PhantomData,
+            output_bitsize,
+            shared_prg_1_2: None,
+            shared_prg_2_3: None,
+            is_initialized: false,
+            num_preprocessed_invocations: 0,
+            preprocessed_rerand_m2: Default::default(),
+        }
+    }
+
+    /// Reset this instance.
+    pub fn reset(&mut self) {
+        *self = Self::new(self.output_bitsize)
+    }
+
+    /// Delete all preprocessed data.
+    pub fn reset_preprocessing(&mut self) {
+        self.num_preprocessed_invocations = 0;
+        self.preprocessed_rerand_m2 = Default::default();
+    }
+
+    /// Step 0 of the initialization protocol.
+    pub fn init_round_0(&mut self) -> ((), SharedSeed) {
+        assert!(!self.is_initialized);
+        self.shared_prg_2_3 = Some(ChaChaRng::from_seed(thread_rng().gen()));
+        ((), self.shared_prg_2_3.as_ref().unwrap().get_seed())
+    }
+
+    /// Step 1 of the initialization protocol.
+    pub fn init_round_1(&mut self, shared_prg_seed_1_2: SharedSeed, _: ()) {
+        assert!(!self.is_initialized);
+        // receive shared PRF key from Party 1
+        self.shared_prg_1_2 = Some(ChaChaRng::from_seed(shared_prg_seed_1_2));
+        self.is_initialized = true;
+    }
+
+    /// Run the initialization protocol.
+    pub fn init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error> {
+        let fut_1_2 = comm.receive_previous()?;
+        let (_, msg_2_3) = self.init_round_0();
+        comm.send_next(msg_2_3)?;
+        self.init_round_1(fut_1_2.get()?, ());
+        Ok(())
+    }
+
+    /// Step 0 of the preprocessing protocol.
+    pub fn preprocess_round_0(&mut self, num: usize) -> (Vec<F>, ()) {
+        assert!(self.is_initialized);
+        let n = num * self.output_bitsize;
+
+        let preprocessed_squares: Vec<F> = (0..n)
+            .map(|_| F::random(self.shared_prg_1_2.as_mut().unwrap()).square())
+            .collect();
+        self.preprocessed_rerand_m2
+            .extend((0..num).map(|_| F::random(self.shared_prg_2_3.as_mut().unwrap())));
+        let preprocessed_mult_d: Vec<F> = (0..n)
+            .map(|_| F::random(self.shared_prg_2_3.as_mut().unwrap()))
+            .collect();
+        let preprocessed_mt_b: Vec<F> = (0..num)
+            .map(|_| F::random(self.shared_prg_2_3.as_mut().unwrap()))
+            .collect();
+        let preprocessed_mt_c3: Vec<F> = (0..n)
+            .map(|_| F::random(self.shared_prg_2_3.as_mut().unwrap()))
+            .collect();
+        let preprocessed_c1: Vec<F> = izip!(
+            preprocessed_squares.iter(),
+            preprocessed_mult_d.iter(),
+            preprocessed_mt_b
+                .iter()
+                .flat_map(|b| repeat(b).take(self.output_bitsize)),
+            preprocessed_mt_c3.iter(),
+        )
+        .map(|(&s, &d, &b, &c3)| (s - d) * b - c3)
+        .collect();
+        self.num_preprocessed_invocations += num;
+        (preprocessed_c1, ())
+    }
+
+    /// Step 1 of the preprocessing protocol.
+    pub fn preprocess_round_1(&mut self, _: usize, _: (), _: ()) {
+        assert!(self.is_initialized);
+    }
+
+    /// Run the preprocessing protocol for `num` evaluations.
+    pub fn preprocess<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        num: usize,
+    ) -> Result<(), Error>
+    where
+        F: Serializable,
+    {
+        let (msg_2_1, _) = self.preprocess_round_0(num);
+        comm.send_previous(msg_2_1)?;
+        self.preprocess_round_1(num, (), ());
+        Ok(())
+    }
+
+    /// Return the number of preprocessed invocations available.
+    pub fn get_num_preprocessed_invocations(&self) -> usize {
+        self.num_preprocessed_invocations
+    }
+
+    /// Return the preprocessed data.
+    #[doc(hidden)]
+    pub fn get_preprocessed_data(&self) -> &[F] {
+        &self.preprocessed_rerand_m2
+    }
+
+    /// Perform some self-test of the preprocessed data.
+    #[doc(hidden)]
+    pub fn check_preprocessing(&self) {
+        let num = self.num_preprocessed_invocations;
+        assert_eq!(self.preprocessed_rerand_m2.len(), num);
+    }
+
+    /// Step 0 of the evaluation protocol.
+    pub fn eval_round_0(&mut self, num: usize, shares2: &[F]) -> (Vec<F>, ()) {
+        assert!(num <= self.num_preprocessed_invocations);
+        assert_eq!(shares2.len(), num);
+        let masked_shares2: Vec<F> =
+            izip!(shares2.iter(), self.preprocessed_rerand_m2.drain(0..num),)
+                .map(|(&s2i, m2i)| s2i + m2i)
+                .collect();
+        self.num_preprocessed_invocations -= num;
+        (masked_shares2, ())
+    }
+
+    /// Run the evaluation protocol.
+    pub fn eval<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        num: usize,
+        shares2: &[F],
+    ) -> Result<(), Error>
+    where
+        F: Serializable,
+    {
+        assert_eq!(shares2.len(), num);
+        let (msg_2_1, _) = self.eval_round_0(1, shares2);
+        comm.send_previous(msg_2_1)?;
+        Ok(())
+    }
+}
+
+/// Party 3 of the *unmasked* DOPRF protocol.
+pub struct DOPrfParty3<F: LegendreSymbol> {
+    _phantom: PhantomData<F>,
+    output_bitsize: usize,
+    shared_prg_1_3: Option<ChaChaRng>,
+    shared_prg_2_3: Option<ChaChaRng>,
+    is_initialized: bool,
+    num_preprocessed_invocations: usize,
+    preprocessed_rerand_m3: Vec<F>,
+    preprocessed_mt_b: Vec<F>,
+    preprocessed_mt_c3: Vec<F>,
+    preprocessed_mult_d: Vec<F>,
+    mult_e: Vec<F>,
+}
+
+impl<F> DOPrfParty3<F>
+where
+    F: LegendreSymbol,
+{
+    /// Create a new instance with the given `output_bitsize`.
+    pub fn new(output_bitsize: usize) -> Self {
+        assert!(output_bitsize > 0);
+        Self {
+            _phantom: PhantomData,
+            output_bitsize,
+            shared_prg_1_3: None,
+            shared_prg_2_3: None,
+            is_initialized: false,
+            num_preprocessed_invocations: 0,
+            preprocessed_rerand_m3: Default::default(),
+            preprocessed_mt_b: Default::default(),
+            preprocessed_mt_c3: Default::default(),
+            preprocessed_mult_d: Default::default(),
+            mult_e: Default::default(),
+        }
+    }
+
+    /// Reset this instance.
+    pub fn reset(&mut self) {
+        *self = Self::new(self.output_bitsize)
+    }
+
+    /// Delete all preprocessed data.
+    pub fn reset_preprocessing(&mut self) {
+        self.num_preprocessed_invocations = 0;
+        self.preprocessed_rerand_m3 = Default::default();
+        self.preprocessed_mt_b = Default::default();
+        self.preprocessed_mt_c3 = Default::default();
+        self.preprocessed_mult_d = Default::default();
+        self.mult_e = Default::default();
+    }
+
+    /// Step 0 of the initialization protocol.
+    pub fn init_round_0(&mut self) -> (SharedSeed, ()) {
+        assert!(!self.is_initialized);
+        self.shared_prg_1_3 = Some(ChaChaRng::from_seed(thread_rng().gen()));
+        (self.shared_prg_1_3.as_ref().unwrap().get_seed(), ())
+    }
+
+    /// Step 1 of the initialization protocol.
+    pub fn init_round_1(&mut self, _: (), shared_prg_seed_2_3: SharedSeed) {
+        self.shared_prg_2_3 = Some(ChaChaRng::from_seed(shared_prg_seed_2_3));
+        self.is_initialized = true;
+    }
+
+    /// Run the initialization protocol.
+    pub fn init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error> {
+        let fut_2_3 = comm.receive_previous()?;
+        let (msg_3_1, _) = self.init_round_0();
+        comm.send_next(msg_3_1)?;
+        self.init_round_1((), fut_2_3.get()?);
+        Ok(())
+    }
+
+    /// Step 0 of the preprocessing protocol.
+    pub fn preprocess_round_0(&mut self, num: usize) -> ((), ()) {
+        assert!(self.is_initialized);
+        let n = num * self.output_bitsize;
+
+        self.preprocessed_rerand_m3
+            .extend((0..num).map(|_| -F::random(self.shared_prg_2_3.as_mut().unwrap())));
+        self.preprocessed_mult_d
+            .extend((0..n).map(|_| F::random(self.shared_prg_2_3.as_mut().unwrap())));
+        self.preprocessed_mt_b
+            .extend((0..num).map(|_| F::random(self.shared_prg_2_3.as_mut().unwrap())));
+        self.preprocessed_mt_c3
+            .extend((0..n).map(|_| F::random(self.shared_prg_2_3.as_mut().unwrap())));
+        ((), ())
+    }
+
+    /// Step 1 of the preprocessing protocol.
+    pub fn preprocess_round_1(&mut self, num: usize, _: (), _: ()) {
+        assert!(self.is_initialized);
+        self.num_preprocessed_invocations += num;
+    }
+
+    /// Run the preprocessing protocol for `num` evaluations.
+    pub fn preprocess<C: AbstractCommunicator>(
+        &mut self,
+        _comm: &mut C,
+        num: usize,
+    ) -> Result<(), Error>
+    where
+        F: Serializable,
+    {
+        self.preprocess_round_0(num);
+        self.preprocess_round_1(num, (), ());
+        Ok(())
+    }
+
+    /// Return the number of preprocessed invocations available.
+    pub fn get_num_preprocessed_invocations(&self) -> usize {
+        self.num_preprocessed_invocations
+    }
+
+    /// Return the preprocessed data.
+    #[doc(hidden)]
+    pub fn get_preprocessed_data(&self) -> (&[F], &[F], &[F], &[F]) {
+        (
+            &self.preprocessed_rerand_m3,
+            &self.preprocessed_mt_b,
+            &self.preprocessed_mt_c3,
+            &self.preprocessed_mult_d,
+        )
+    }
+
+    /// Perform some self-test of the preprocessed data.
+    #[doc(hidden)]
+    pub fn check_preprocessing(&self) {
+        let num = self.num_preprocessed_invocations;
+        let n = num * self.output_bitsize;
+        assert_eq!(self.preprocessed_rerand_m3.len(), num);
+        assert_eq!(self.preprocessed_mt_b.len(), num);
+        assert_eq!(self.preprocessed_mt_c3.len(), n);
+        assert_eq!(self.preprocessed_mult_d.len(), n);
+    }
+
+    /// Step 0 of the evaluation protocol.
+    pub fn eval_round_0(&mut self, num: usize, shares3: &[F]) -> (Vec<F>, ()) {
+        assert!(num <= self.num_preprocessed_invocations);
+        assert_eq!(shares3.len(), num);
+        self.mult_e = izip!(
+            shares3.iter(),
+            &self.preprocessed_rerand_m3[0..num],
+            self.preprocessed_mt_b.drain(0..num),
+        )
+        .map(|(&s3_i, m3_i, b_i)| s3_i + m3_i - b_i)
+        .collect();
+        (self.mult_e.clone(), ())
+    }
+
+    /// Step 2 of the evaluation protocol.
+    pub fn eval_round_2(
+        &mut self,
+        num: usize,
+        shares3: &[F],
+        output_shares_z1: Vec<F>,
+        _: (),
+    ) -> Vec<BitVec> {
+        assert!(num <= self.num_preprocessed_invocations);
+        let n = num * self.output_bitsize;
+        assert_eq!(shares3.len(), num);
+        assert_eq!(output_shares_z1.len(), n);
+        let lprf_inputs: Vec<F> = izip!(
+            shares3
+                .iter()
+                .flat_map(|s3| repeat(s3).take(self.output_bitsize)),
+            self.preprocessed_rerand_m3
+                .drain(0..num)
+                .flat_map(|m3| repeat(m3).take(self.output_bitsize)),
+            self.preprocessed_mult_d.drain(0..n),
+            self.mult_e
+                .drain(0..num)
+                .flat_map(|e| repeat(e).take(self.output_bitsize)),
+            self.preprocessed_mt_c3.drain(0..n),
+            output_shares_z1.iter(),
+        )
+        .map(|(&s3_i, m3_i, d_ij, e_i, c3_ij, &z1_ij)| {
+            d_ij * (s3_i + m3_i) + c3_ij + z1_ij - d_ij * e_i
+        })
+        .collect();
+        assert_eq!(lprf_inputs.len(), n);
+        let output: Vec<BitVec> = lprf_inputs
+            .chunks_exact(self.output_bitsize)
+            .map(|chunk| {
+                let mut bv = BitVec::with_capacity(self.output_bitsize);
+                for &x in chunk.iter() {
+                    let ls = F::legendre_symbol(x);
+                    debug_assert!(ls != 0, "unlikely");
+                    bv.push(ls == 1);
+                }
+                bv
+            })
+            .collect();
+        self.num_preprocessed_invocations -= num;
+        output
+    }
+
+    /// Run the evaluation protocol to obtain bit vectors.
+    pub fn eval<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        num: usize,
+        shares3: &[F],
+    ) -> Result<Vec<BitVec>, Error>
+    where
+        F: Serializable,
+    {
+        assert_eq!(shares3.len(), num);
+        let fut_1_3 = comm.receive_next()?;
+        let (msg_3_1, _) = self.eval_round_0(num, shares3);
+        comm.send_next(msg_3_1)?;
+        let output = self.eval_round_2(num, shares3, fut_1_3.get()?, ());
+        Ok(output)
+    }
+
+    /// Run the evaluation protocol to obtain integers.
+    pub fn eval_to_uint<C: AbstractCommunicator, T: Unsigned>(
+        &mut self,
+        comm: &mut C,
+        num: usize,
+        shares3: &[F],
+    ) -> Result<Vec<T>, Error>
+    where
+        F: Serializable,
+    {
+        assert!(self.output_bitsize <= T::BITS as usize);
+        Ok(to_uint(self.eval(comm, num, shares3)?))
+    }
+}
+
+/// Combination of three instances of the *unmasked* DOPRF protocol such that this party acts as
+/// each role 1, 2, 3 in the different instances.
+pub struct JointDOPrf<F: LegendreSymbol> {
+    output_bitsize: usize,
+    doprf_p1_prev: DOPrfParty1<F>,
+    doprf_p2_next: DOPrfParty2<F>,
+    doprf_p3_mine: DOPrfParty3<F>,
+}
+
+impl<F: LegendreSymbol + Serializable> JointDOPrf<F> {
+    /// Create a new instance with the given `output_bitsize`.
+    pub fn new(output_bitsize: usize) -> Self {
+        Self {
+            output_bitsize,
+            doprf_p1_prev: DOPrfParty1::new(output_bitsize),
+            doprf_p2_next: DOPrfParty2::new(output_bitsize),
+            doprf_p3_mine: DOPrfParty3::new(output_bitsize),
+        }
+    }
+
+    /// Reset this instance.
+    pub fn reset(&mut self) {
+        *self = Self::new(self.output_bitsize);
+    }
+
+    /// Return the Legendre PRF key.
+    pub fn get_legendre_prf_key_prev(&self) -> LegendrePrfKey<F> {
+        self.doprf_p1_prev.get_legendre_prf_key()
+    }
+
+    /// Set the Legendre PRF key.
+    ///
+    /// Can only done before the initialization protocol is run.
+    pub fn set_legendre_prf_key_prev(&mut self, legendre_prf_key: LegendrePrfKey<F>) {
+        self.doprf_p1_prev.set_legendre_prf_key(legendre_prf_key)
+    }
+
+    /// Run the initialization protocol.
+    pub fn init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error> {
+        let fut_prev = comm.receive_previous()?;
+        let (msg_1_2, _) = self.doprf_p1_prev.init_round_0();
+        let (_, msg_2_3) = self.doprf_p2_next.init_round_0();
+        let (msg_3_1, _) = self.doprf_p3_mine.init_round_0();
+        comm.send_next((msg_1_2, msg_2_3, msg_3_1))?;
+        let (msg_1_2, msg_2_3, msg_3_1) = fut_prev.get()?;
+        self.doprf_p1_prev.init_round_1((), msg_3_1);
+        self.doprf_p2_next.init_round_1(msg_1_2, ());
+        self.doprf_p3_mine.init_round_1((), msg_2_3);
+        Ok(())
+    }
+
+    /// Run the preprocessing protocol for `num` evaluations.
+    pub fn preprocess<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        num: usize,
+    ) -> Result<(), Error> {
+        let fut_2_1 = comm.receive_next()?;
+        let (msg_2_1, _) = self.doprf_p2_next.preprocess_round_0(num);
+        comm.send_previous(msg_2_1)?;
+        self.doprf_p2_next.preprocess_round_1(num, (), ());
+        self.doprf_p3_mine.preprocess_round_0(num);
+        self.doprf_p3_mine.preprocess_round_1(num, (), ());
+        self.doprf_p1_prev.preprocess_round_0(num);
+        self.doprf_p1_prev
+            .preprocess_round_1(num, fut_2_1.get()?, ());
+        Ok(())
+    }
+
+    /// Run the evaluation protocol to obtain integers.
+    pub fn eval_to_uint<C: AbstractCommunicator, T: Unsigned>(
+        &mut self,
+        comm: &mut C,
+        shares: &[F],
+    ) -> Result<Vec<T>, Error> {
+        let num = shares.len();
+
+        let fut_2_1 = comm.receive_next::<Vec<_>>()?; // round 0
+        let fut_3_1 = comm.receive_previous::<Vec<_>>()?; // round 0
+        let fut_1_3 = comm.receive_next()?; // round 1
+
+        let (msg_2_1, _) = self.doprf_p2_next.eval_round_0(num, shares);
+        comm.send_previous(msg_2_1)?;
+
+        let (msg_3_1, _) = self.doprf_p3_mine.eval_round_0(num, shares);
+        comm.send_next(msg_3_1)?;
+
+        let (_, msg_1_3) =
+            self.doprf_p1_prev
+                .eval_round_1(num, shares, &fut_2_1.get()?, &fut_3_1.get()?);
+        comm.send_previous(msg_1_3)?;
+
+        let output = self
+            .doprf_p3_mine
+            .eval_round_2(num, shares, fut_1_3.get()?, ());
+
+        Ok(to_uint(output))
+    }
+}
+
+/// Party 1 of the *masked* DOPRF protocol.
+pub struct MaskedDOPrfParty1<F: LegendreSymbol> {
+    _phantom: PhantomData<F>,
+    output_bitsize: usize,
+    shared_prg_1_2: Option<ChaChaRng>,
+    shared_prg_1_3: Option<ChaChaRng>,
+    legendre_prf_key: Option<LegendrePrfKey<F>>,
+    is_initialized: bool,
+    num_preprocessed_invocations: usize,
+    preprocessed_rerand_m1: Vec<F>,
+    preprocessed_mt_a: Vec<F>,
+    preprocessed_mt_c1: Vec<F>,
+    preprocessed_mult_e: Vec<F>,
+    mult_d: Vec<F>,
+}
+
+impl<F> MaskedDOPrfParty1<F>
+where
+    F: LegendreSymbol,
+{
+    /// Create a new instance with the given `output_bitsize`.
+    pub fn new(output_bitsize: usize) -> Self {
+        assert!(output_bitsize > 0);
+        Self {
+            _phantom: PhantomData,
+            output_bitsize,
+            shared_prg_1_2: None,
+            shared_prg_1_3: None,
+            legendre_prf_key: None,
+            is_initialized: false,
+            num_preprocessed_invocations: 0,
+            preprocessed_rerand_m1: Default::default(),
+            preprocessed_mt_a: Default::default(),
+            preprocessed_mt_c1: Default::default(),
+            preprocessed_mult_e: Default::default(),
+            mult_d: Default::default(),
+        }
+    }
+
+    /// Create an instance from an existing Legendre PRF key.
+    pub fn from_legendre_prf_key(legendre_prf_key: LegendrePrfKey<F>) -> Self {
+        let mut new = Self::new(legendre_prf_key.keys.len());
+        new.legendre_prf_key = Some(legendre_prf_key);
+        new
+    }
+
+    /// Reset this instance.
+    pub fn reset(&mut self) {
+        *self = Self::new(self.output_bitsize)
+    }
+
+    /// Delete all preprocessed data.
+    pub fn reset_preprocessing(&mut self) {
+        self.num_preprocessed_invocations = 0;
+        self.preprocessed_rerand_m1 = Default::default();
+        self.preprocessed_mt_a = Default::default();
+        self.preprocessed_mt_c1 = Default::default();
+        self.preprocessed_mult_e = Default::default();
+    }
+
+    /// Step 0 of the initialization protocol.
+    pub fn init_round_0(&mut self) -> (SharedSeed, ()) {
+        assert!(!self.is_initialized);
+        // sample and share a PRF key with Party 2
+        self.shared_prg_1_2 = Some(ChaChaRng::from_seed(thread_rng().gen()));
+        (self.shared_prg_1_2.as_ref().unwrap().get_seed(), ())
+    }
+
+    /// Step 1 of the initialization protocol.
+    pub fn init_round_1(&mut self, _: (), shared_prg_seed_1_3: SharedSeed) {
+        assert!(!self.is_initialized);
+        // receive shared PRF key from Party 3
+        self.shared_prg_1_3 = Some(ChaChaRng::from_seed(shared_prg_seed_1_3));
+        if self.legendre_prf_key.is_none() {
+            // generate Legendre PRF key
+            self.legendre_prf_key = Some(LegendrePrf::key_gen(self.output_bitsize));
+        }
+        self.is_initialized = true;
+    }
+
+    /// Run the initialization protocol.
+    pub fn init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error> {
+        let fut_3_1 = comm.receive_previous()?;
+        let (msg_1_2, _) = self.init_round_0();
+        comm.send_next(msg_1_2)?;
+        self.init_round_1((), fut_3_1.get()?);
+        Ok(())
+    }
+
+    /// Return the Legendre PRF key.
+    pub fn get_legendre_prf_key(&self) -> LegendrePrfKey<F> {
+        assert!(self.is_initialized);
+        self.legendre_prf_key.as_ref().unwrap().clone()
+    }
+
+    /// Step 0 of the preprocessing protocol.
+    pub fn preprocess_round_0(&mut self, num: usize) -> ((), ()) {
+        assert!(self.is_initialized);
+        let n = num * self.output_bitsize;
+        self.preprocessed_rerand_m1
+            .extend((0..num).map(|_| F::random(self.shared_prg_1_2.as_mut().unwrap())));
+        self.preprocessed_mt_a
+            .extend((0..n).map(|_| F::random(self.shared_prg_1_2.as_mut().unwrap())));
+        self.preprocessed_mt_c1
+            .extend((0..n).map(|_| F::random(self.shared_prg_1_2.as_mut().unwrap())));
+        self.preprocessed_mult_e
+            .extend((0..n).map(|_| F::random(self.shared_prg_1_2.as_mut().unwrap())));
+        ((), ())
+    }
+
+    /// Step 1 of the preprocessing protocol.
+    pub fn preprocess_round_1(&mut self, num: usize, _: (), _: ()) {
+        assert!(self.is_initialized);
+        self.num_preprocessed_invocations += num;
+    }
+
+    /// Run the preprocessing protocol for `num` evaluations.
+    pub fn preprocess<C: AbstractCommunicator>(
+        &mut self,
+        _comm: &mut C,
+        num: usize,
+    ) -> Result<(), Error> {
+        self.preprocess_round_0(num);
+        self.preprocess_round_1(num, (), ());
+        Ok(())
+    }
+
+    /// Return the number of preprocessed invocations available.
+    pub fn get_num_preprocessed_invocations(&self) -> usize {
+        self.num_preprocessed_invocations
+    }
+
+    /// Return the preprocessed data.
+    #[doc(hidden)]
+    pub fn get_preprocessed_data(&self) -> (&[F], &[F], &[F], &[F]) {
+        (
+            &self.preprocessed_rerand_m1,
+            &self.preprocessed_mt_a,
+            &self.preprocessed_mt_c1,
+            &self.preprocessed_mult_e,
+        )
+    }
+
+    /// Perform some self-test of the preprocessed data.
+    #[doc(hidden)]
+    pub fn check_preprocessing(&self) {
+        let num = self.num_preprocessed_invocations;
+        let n = num * self.output_bitsize;
+        assert_eq!(self.preprocessed_rerand_m1.len(), num);
+        assert_eq!(self.preprocessed_mt_a.len(), n);
+        assert_eq!(self.preprocessed_mt_c1.len(), n);
+        assert_eq!(self.preprocessed_mult_e.len(), n);
+    }
+
+    /// Step 0 of the evaluation protocol.
+    pub fn eval_round_0(&mut self, num: usize, shares1: &[F]) -> ((), Vec<F>) {
+        assert!(num <= self.num_preprocessed_invocations);
+        assert_eq!(shares1.len(), num);
+        let n = num * self.output_bitsize;
+        let k = &self.legendre_prf_key.as_ref().unwrap().keys;
+        self.mult_d = izip!(
+            k.iter().cycle(),
+            shares1
+                .iter()
+                .flat_map(|s1| repeat(s1).take(self.output_bitsize)),
+            self.preprocessed_rerand_m1
+                .iter()
+                .take(num)
+                .flat_map(|m1| repeat(m1).take(self.output_bitsize)),
+            self.preprocessed_mt_a.drain(0..n),
+        )
+        .map(|(&k_i, &s1_i, m1_i, a_i)| k_i + s1_i + m1_i - a_i)
+        .collect();
+        assert_eq!(self.mult_d.len(), n);
+        ((), self.mult_d.clone())
+    }
+
+    /// Step 2 of the evaluation protocol.
+    pub fn eval_round_2(
+        &mut self,
+        num: usize,
+        shares1: &[F],
+        _: (),
+        output_shares_z3: Vec<F>,
+    ) -> Vec<BitVec> {
+        assert!(num <= self.num_preprocessed_invocations);
+        let n = num * self.output_bitsize;
+        assert_eq!(shares1.len(), num);
+        assert_eq!(output_shares_z3.len(), n);
+        let k = &self.legendre_prf_key.as_ref().unwrap().keys;
+        let lprf_inputs: Vec<F> = izip!(
+            k.iter().cycle(),
+            shares1
+                .iter()
+                .flat_map(|s1| repeat(s1).take(self.output_bitsize)),
+            self.preprocessed_rerand_m1
+                .drain(0..num)
+                .flat_map(|m1| repeat(m1).take(self.output_bitsize)),
+            self.preprocessed_mult_e.drain(0..n),
+            self.mult_d.drain(..),
+            self.preprocessed_mt_c1.drain(0..n),
+            output_shares_z3.iter(),
+        )
+        .map(|(&k_j, &s1_i, m1_i, e_ij, d_ij, c1_ij, &z3_ij)| {
+            e_ij * (k_j + s1_i + m1_i) + c1_ij + z3_ij - d_ij * e_ij
+        })
+        .collect();
+        assert_eq!(lprf_inputs.len(), n);
+        let output: Vec<BitVec> = lprf_inputs
+            .chunks_exact(self.output_bitsize)
+            .map(|chunk| {
+                let mut bv = BitVec::with_capacity(self.output_bitsize);
+                for &x in chunk.iter() {
+                    let ls = F::legendre_symbol(x);
+                    debug_assert!(ls != 0, "unlikely");
+                    bv.push(ls == 1);
+                }
+                bv
+            })
+            .collect();
+        self.num_preprocessed_invocations -= num;
+        output
+    }
+
+    /// Run the evaluation protocol to obtain bit vectors.
+    pub fn eval<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        num: usize,
+        shares1: &[F],
+    ) -> Result<Vec<BitVec>, Error>
+    where
+        F: Serializable,
+    {
+        assert_eq!(shares1.len(), num);
+        let fut_3_1 = comm.receive_previous()?;
+        let (_, msg_1_3) = self.eval_round_0(num, shares1);
+        comm.send_previous(msg_1_3)?;
+        let output = self.eval_round_2(1, shares1, (), fut_3_1.get()?);
+        Ok(output)
+    }
+
+    /// Run the evaluation protocol to obtain integers.
+    pub fn eval_to_uint<C: AbstractCommunicator, T: Unsigned>(
+        &mut self,
+        comm: &mut C,
+        num: usize,
+        shares1: &[F],
+    ) -> Result<Vec<T>, Error>
+    where
+        F: Serializable,
+    {
+        assert!(self.output_bitsize <= T::BITS as usize);
+        Ok(to_uint(self.eval(comm, num, shares1)?))
+    }
+}
+
+/// Party 2 of the *masked* DOPRF protocol.
+pub struct MaskedDOPrfParty2<F: LegendreSymbol> {
+    _phantom: PhantomData<F>,
+    output_bitsize: usize,
+    shared_prg_1_2: Option<ChaChaRng>,
+    shared_prg_2_3: Option<ChaChaRng>,
+    is_initialized: bool,
+    num_preprocessed_invocations: usize,
+    preprocessed_rerand_m2: Vec<F>,
+    preprocessed_r: BitVec,
+}
+
+impl<F> MaskedDOPrfParty2<F>
+where
+    F: LegendreSymbol,
+{
+    /// Create a new instance with the given `output_bitsize`.
+    pub fn new(output_bitsize: usize) -> Self {
+        assert!(output_bitsize > 0);
+        Self {
+            _phantom: PhantomData,
+            output_bitsize,
+            shared_prg_1_2: None,
+            shared_prg_2_3: None,
+            is_initialized: false,
+            num_preprocessed_invocations: 0,
+            preprocessed_rerand_m2: Default::default(),
+            preprocessed_r: Default::default(),
+        }
+    }
+
+    /// Reset this instance.
+    pub fn reset(&mut self) {
+        *self = Self::new(self.output_bitsize)
+    }
+
+    /// Delete all preprocessed data.
+    pub fn reset_preprocessing(&mut self) {
+        self.num_preprocessed_invocations = 0;
+        self.preprocessed_rerand_m2 = Default::default();
+    }
+
+    /// Step 0 of the initialization protocol.
+    pub fn init_round_0(&mut self) -> ((), SharedSeed) {
+        assert!(!self.is_initialized);
+        self.shared_prg_2_3 = Some(ChaChaRng::from_seed(thread_rng().gen()));
+        ((), self.shared_prg_2_3.as_ref().unwrap().get_seed())
+    }
+
+    /// Step 1 of the initialization protocol.
+    pub fn init_round_1(&mut self, shared_prg_seed_1_2: SharedSeed, _: ()) {
+        assert!(!self.is_initialized);
+        // receive shared PRF key from Party 1
+        self.shared_prg_1_2 = Some(ChaChaRng::from_seed(shared_prg_seed_1_2));
+        self.is_initialized = true;
+    }
+
+    /// Run the initialization protocol.
+    pub fn init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error> {
+        let fut_1_2 = comm.receive_previous()?;
+        let (_, msg_2_3) = self.init_round_0();
+        comm.send_next(msg_2_3)?;
+        self.init_round_1(fut_1_2.get()?, ());
+        Ok(())
+    }
+
+    /// Step 0 of the preprocessing protocol.
+    pub fn preprocess_round_0(&mut self, num: usize) -> ((), Vec<F>) {
+        assert!(self.is_initialized);
+        let n = num * self.output_bitsize;
+
+        let mut preprocessed_t: Vec<_> = (0..n)
+            .map(|_| F::random(self.shared_prg_2_3.as_mut().unwrap()).square())
+            .collect();
+        debug_assert!(!preprocessed_t.contains(&F::ZERO));
+        {
+            let mut random_bytes = vec![0u8; (n + 7) / 8];
+            self.shared_prg_2_3
+                .as_mut()
+                .unwrap()
+                .fill_bytes(&mut random_bytes);
+            let new_r_slice = BitSlice::from_slice(&random_bytes);
+            self.preprocessed_r.extend(&new_r_slice[..n]);
+            for (i, r_i) in new_r_slice.iter().by_vals().take(n).enumerate() {
+                if r_i {
+                    preprocessed_t[i] *= F::get_non_random_qnr();
+                }
+            }
+        }
+        self.preprocessed_rerand_m2
+            .extend((0..num).map(|_| -F::random(self.shared_prg_1_2.as_mut().unwrap())));
+        let preprocessed_mt_a: Vec<F> = (0..n)
+            .map(|_| F::random(self.shared_prg_1_2.as_mut().unwrap()))
+            .collect();
+        let preprocessed_mt_c1: Vec<F> = (0..n)
+            .map(|_| F::random(self.shared_prg_1_2.as_mut().unwrap()))
+            .collect();
+        let preprocessed_mult_e: Vec<F> = (0..n)
+            .map(|_| F::random(self.shared_prg_1_2.as_mut().unwrap()))
+            .collect();
+        let preprocessed_c3: Vec<F> = izip!(
+            preprocessed_t.iter(),
+            preprocessed_mult_e.iter(),
+            preprocessed_mt_a.iter(),
+            preprocessed_mt_c1.iter(),
+        )
+        .map(|(&t, &e, &a, &c1)| a * (t - e) - c1)
+        .collect();
+        self.num_preprocessed_invocations += num;
+        ((), preprocessed_c3)
+    }
+
+    /// Step 1 of the preprocessing protocol.
+    pub fn preprocess_round_1(&mut self, _: usize, _: (), _: ()) {
+        assert!(self.is_initialized);
+    }
+
+    /// Run the preprocessing protocol for `num` evaluations.
+    pub fn preprocess<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        num: usize,
+    ) -> Result<(), Error>
+    where
+        F: Serializable,
+    {
+        let (_, msg_2_3) = self.preprocess_round_0(num);
+        comm.send_next(msg_2_3)?;
+        self.preprocess_round_1(num, (), ());
+        Ok(())
+    }
+
+    /// Return the number of preprocessed invocations available.
+    pub fn get_num_preprocessed_invocations(&self) -> usize {
+        self.num_preprocessed_invocations
+    }
+
+    /// Return the preprocessed data.
+    #[doc(hidden)]
+    pub fn get_preprocessed_data(&self) -> (&BitSlice, &[F]) {
+        (&self.preprocessed_r, &self.preprocessed_rerand_m2)
+    }
+
+    /// Perform some self-test of the preprocessed data.
+    #[doc(hidden)]
+    pub fn check_preprocessing(&self) {
+        let num = self.num_preprocessed_invocations;
+        assert_eq!(self.preprocessed_rerand_m2.len(), num);
+    }
+
+    /// Step 0 of the evaluation protocol.
+    pub fn eval_round_0(&mut self, num: usize, shares2: &[F]) -> ((), Vec<F>) {
+        assert!(num <= self.num_preprocessed_invocations);
+        assert_eq!(shares2.len(), num);
+        let masked_shares2: Vec<F> =
+            izip!(shares2.iter(), self.preprocessed_rerand_m2.drain(0..num),)
+                .map(|(&s2i, m2i)| s2i + m2i)
+                .collect();
+        assert_eq!(masked_shares2.len(), num);
+        ((), masked_shares2)
+    }
+
+    /// Final step of the evaluation protocol.
+    pub fn eval_get_output(&mut self, num: usize) -> Vec<BitVec> {
+        assert!(num <= self.num_preprocessed_invocations);
+        let n = num * self.output_bitsize;
+        let mut output = Vec::with_capacity(num);
+        for chunk in self
+            .preprocessed_r
+            .chunks_exact(self.output_bitsize)
+            .take(num)
+        {
+            output.push(chunk.to_bitvec());
+        }
+        let (_, last_r) = self.preprocessed_r.split_at(n);
+        self.preprocessed_r = last_r.to_bitvec();
+        self.num_preprocessed_invocations -= num;
+        output
+    }
+
+    /// Run the evaluation protocol to obtain bit vectors.
+    pub fn eval<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        num: usize,
+        shares2: &[F],
+    ) -> Result<Vec<BitVec>, Error>
+    where
+        F: Serializable,
+    {
+        assert_eq!(shares2.len(), num);
+        let (_, msg_2_3) = self.eval_round_0(num, shares2);
+        comm.send_next(msg_2_3)?;
+        let output = self.eval_get_output(num);
+        Ok(output)
+    }
+
+    /// Run the evaluation protocol to obtain integers.
+    pub fn eval_to_uint<C: AbstractCommunicator, T: Unsigned>(
+        &mut self,
+        comm: &mut C,
+        num: usize,
+        shares2: &[F],
+    ) -> Result<Vec<T>, Error>
+    where
+        F: Serializable,
+    {
+        assert!(self.output_bitsize <= T::BITS as usize);
+        Ok(to_uint(self.eval(comm, num, shares2)?))
+    }
+}
+
+/// Party 3 of the *masked* DOPRF protocol.
+pub struct MaskedDOPrfParty3<F: LegendreSymbol> {
+    _phantom: PhantomData<F>,
+    output_bitsize: usize,
+    shared_prg_1_3: Option<ChaChaRng>,
+    shared_prg_2_3: Option<ChaChaRng>,
+    is_initialized: bool,
+    num_preprocessed_invocations: usize,
+    preprocessed_r: BitVec,
+    preprocessed_t: Vec<F>,
+    preprocessed_mt_c3: Vec<F>,
+}
+
+impl<F> MaskedDOPrfParty3<F>
+where
+    F: LegendreSymbol,
+{
+    /// Create a new instance with the given `output_bitsize`.
+    pub fn new(output_bitsize: usize) -> Self {
+        assert!(output_bitsize > 0);
+        Self {
+            _phantom: PhantomData,
+            output_bitsize,
+            shared_prg_1_3: None,
+            shared_prg_2_3: None,
+            is_initialized: false,
+            num_preprocessed_invocations: 0,
+            preprocessed_r: Default::default(),
+            preprocessed_t: Default::default(),
+            preprocessed_mt_c3: Default::default(),
+        }
+    }
+
+    /// Reset this instance.
+    pub fn reset(&mut self) {
+        *self = Self::new(self.output_bitsize)
+    }
+
+    /// Delete all preprocessed data.
+    pub fn reset_preprocessing(&mut self) {
+        self.num_preprocessed_invocations = 0;
+        self.preprocessed_t = Default::default();
+        self.preprocessed_mt_c3 = Default::default();
+    }
+
+    /// Step 0 of the initialization protocol.
+    pub fn init_round_0(&mut self) -> (SharedSeed, ()) {
+        assert!(!self.is_initialized);
+        self.shared_prg_1_3 = Some(ChaChaRng::from_seed(thread_rng().gen()));
+        (self.shared_prg_1_3.as_ref().unwrap().get_seed(), ())
+    }
+
+    /// Step 1 of the initialization protocol.
+    pub fn init_round_1(&mut self, _: (), shared_prg_seed_2_3: SharedSeed) {
+        self.shared_prg_2_3 = Some(ChaChaRng::from_seed(shared_prg_seed_2_3));
+        self.is_initialized = true;
+    }
+
+    /// Run the initialization protocol.
+    pub fn init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error> {
+        let fut_2_3 = comm.receive_previous()?;
+        let (msg_3_1, _) = self.init_round_0();
+        comm.send_next(msg_3_1)?;
+        self.init_round_1((), fut_2_3.get()?);
+        Ok(())
+    }
+
+    /// Step 0 of the preprocessing protocol.
+    pub fn preprocess_round_0(&mut self, num: usize) -> ((), ()) {
+        assert!(self.is_initialized);
+        let n = num * self.output_bitsize;
+        let start_index = self.num_preprocessed_invocations * self.output_bitsize;
+
+        self.preprocessed_t
+            .extend((0..n).map(|_| F::random(self.shared_prg_2_3.as_mut().unwrap()).square()));
+        debug_assert!(!self.preprocessed_t[start_index..].contains(&F::ZERO));
+        {
+            let mut random_bytes = vec![0u8; (n + 7) / 8];
+            self.shared_prg_2_3
+                .as_mut()
+                .unwrap()
+                .fill_bytes(&mut random_bytes);
+            let new_r_slice = BitSlice::from_slice(&random_bytes);
+            self.preprocessed_r.extend(&new_r_slice[..n]);
+            for (i, r_i) in new_r_slice.iter().by_vals().take(n).enumerate() {
+                if r_i {
+                    self.preprocessed_t[start_index + i] *= F::get_non_random_qnr();
+                }
+            }
+        }
+        ((), ())
+    }
+
+    /// Step 1 of the preprocessing protocol.
+    pub fn preprocess_round_1(&mut self, num: usize, _: (), preprocessed_mt_c3: Vec<F>) {
+        assert!(self.is_initialized);
+        let n = num * self.output_bitsize;
+        assert_eq!(preprocessed_mt_c3.len(), n);
+        self.preprocessed_mt_c3.extend(preprocessed_mt_c3);
+        self.num_preprocessed_invocations += num;
+    }
+
+    /// Run the preprocessing protocol for `num` evaluations.
+    pub fn preprocess<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        num: usize,
+    ) -> Result<(), Error>
+    where
+        F: Serializable,
+    {
+        let fut_2_3 = comm.receive_previous()?;
+        self.preprocess_round_0(num);
+        self.preprocess_round_1(num, (), fut_2_3.get()?);
+        Ok(())
+    }
+
+    /// Return the number of preprocessed invocations available.
+    pub fn get_num_preprocessed_invocations(&self) -> usize {
+        self.num_preprocessed_invocations
+    }
+
+    /// Return the preprocessed data.
+    #[doc(hidden)]
+    pub fn get_preprocessed_data(&self) -> (&BitSlice, &[F], &[F]) {
+        (
+            &self.preprocessed_r,
+            &self.preprocessed_t,
+            &self.preprocessed_mt_c3,
+        )
+    }
+
+    /// Perform some self-test of the preprocessed data.
+    #[doc(hidden)]
+    pub fn check_preprocessing(&self) {
+        let num = self.num_preprocessed_invocations;
+        let n = num * self.output_bitsize;
+        assert_eq!(self.preprocessed_t.len(), n);
+        assert_eq!(self.preprocessed_mt_c3.len(), n);
+    }
+
+    /// Step 1 of the evaluation protocol.
+    pub fn eval_round_1(
+        &mut self,
+        num: usize,
+        shares3: &[F],
+        mult_d: &[F],
+        masked_shares2: &[F],
+    ) -> (Vec<F>, ()) {
+        assert!(num <= self.num_preprocessed_invocations);
+        let n = num * self.output_bitsize;
+        assert_eq!(shares3.len(), num);
+        assert_eq!(masked_shares2.len(), num);
+        assert_eq!(mult_d.len(), n);
+        let output_shares_z3: Vec<F> = izip!(
+            shares3
+                .iter()
+                .flat_map(|s1i| repeat(s1i).take(self.output_bitsize)),
+            masked_shares2
+                .iter()
+                .flat_map(|ms2i| repeat(ms2i).take(self.output_bitsize)),
+            self.preprocessed_t.drain(0..n),
+            self.preprocessed_mt_c3.drain(0..n),
+            mult_d,
+        )
+        .map(|(&s3_i, &ms2_i, t_ij, c3_ij, &d_ij)| t_ij * (s3_i + ms2_i) + d_ij * t_ij + c3_ij)
+        .collect();
+        (output_shares_z3, ())
+    }
+
+    /// Final step of the evaluation protocol.
+    pub fn eval_get_output(&mut self, num: usize) -> Vec<BitVec> {
+        assert!(num <= self.num_preprocessed_invocations);
+        let n = num * self.output_bitsize;
+        let mut output = Vec::with_capacity(num);
+        for chunk in self
+            .preprocessed_r
+            .chunks_exact(self.output_bitsize)
+            .take(num)
+        {
+            output.push(chunk.to_bitvec());
+        }
+        let (_, last_r) = self.preprocessed_r.split_at(n);
+        self.preprocessed_r = last_r.to_bitvec();
+        self.num_preprocessed_invocations -= num;
+        output
+    }
+
+    /// Run the evaluation protocol to obtain bit vectors.
+    pub fn eval<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        num: usize,
+        shares3: &[F],
+    ) -> Result<Vec<BitVec>, Error>
+    where
+        F: Serializable,
+    {
+        assert_eq!(shares3.len(), num);
+        let fut_1_3 = comm.receive_next::<Vec<_>>()?;
+        let fut_2_3 = comm.receive_previous::<Vec<_>>()?;
+        let (msg_3_1, _) = self.eval_round_1(1, shares3, &fut_1_3.get()?, &fut_2_3.get()?);
+        comm.send_next(msg_3_1)?;
+        let output = self.eval_get_output(num);
+        Ok(output)
+    }
+
+    /// Run the evaluation protocol to obtain integers.
+    pub fn eval_to_uint<C: AbstractCommunicator, T: Unsigned>(
+        &mut self,
+        comm: &mut C,
+        num: usize,
+        shares3: &[F],
+    ) -> Result<Vec<T>, Error>
+    where
+        F: Serializable,
+    {
+        assert!(self.output_bitsize <= T::BITS as usize);
+        Ok(to_uint(self.eval(comm, num, shares3)?))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use bincode;
+    use ff::Field;
+    use utils::field::Fp;
+
+    fn doprf_init(
+        party_1: &mut DOPrfParty1<Fp>,
+        party_2: &mut DOPrfParty2<Fp>,
+        party_3: &mut DOPrfParty3<Fp>,
+    ) {
+        let (msg_1_2, msg_1_3) = party_1.init_round_0();
+        let (msg_2_1, msg_2_3) = party_2.init_round_0();
+        let (msg_3_1, msg_3_2) = party_3.init_round_0();
+        party_1.init_round_1(msg_2_1, msg_3_1);
+        party_2.init_round_1(msg_1_2, msg_3_2);
+        party_3.init_round_1(msg_1_3, msg_2_3);
+    }
+
+    fn doprf_preprocess(
+        party_1: &mut DOPrfParty1<Fp>,
+        party_2: &mut DOPrfParty2<Fp>,
+        party_3: &mut DOPrfParty3<Fp>,
+        num: usize,
+    ) {
+        let (msg_1_2, msg_1_3) = party_1.preprocess_round_0(num);
+        let (msg_2_1, msg_2_3) = party_2.preprocess_round_0(num);
+        let (msg_3_1, msg_3_2) = party_3.preprocess_round_0(num);
+        party_1.preprocess_round_1(num, msg_2_1, msg_3_1);
+        party_2.preprocess_round_1(num, msg_1_2, msg_3_2);
+        party_3.preprocess_round_1(num, msg_1_3, msg_2_3);
+    }
+
+    fn doprf_eval(
+        party_1: &mut DOPrfParty1<Fp>,
+        party_2: &mut DOPrfParty2<Fp>,
+        party_3: &mut DOPrfParty3<Fp>,
+        shares_1: &[Fp],
+        shares_2: &[Fp],
+        shares_3: &[Fp],
+        num: usize,
+    ) -> Vec<BitVec> {
+        assert_eq!(shares_1.len(), num);
+        assert_eq!(shares_2.len(), num);
+        assert_eq!(shares_3.len(), num);
+
+        let (msg_2_1, msg_2_3) = party_2.eval_round_0(num, &shares_2);
+        let (msg_3_1, _) = party_3.eval_round_0(num, &shares_3);
+        let (_, msg_1_3) = party_1.eval_round_1(num, &shares_1, &msg_2_1, &msg_3_1);
+        let output = party_3.eval_round_2(num, &shares_3, msg_1_3, msg_2_3);
+        output
+    }
+
+    #[test]
+    fn test_doprf() {
+        let output_bitsize = 42;
+
+        let mut party_1 = DOPrfParty1::<Fp>::new(output_bitsize);
+        let mut party_2 = DOPrfParty2::<Fp>::new(output_bitsize);
+        let mut party_3 = DOPrfParty3::<Fp>::new(output_bitsize);
+
+        doprf_init(&mut party_1, &mut party_2, &mut party_3);
+
+        // preprocess num invocations
+        let num = 20;
+        doprf_preprocess(&mut party_1, &mut party_2, &mut party_3, num);
+
+        assert_eq!(party_1.get_num_preprocessed_invocations(), num);
+        assert_eq!(party_2.get_num_preprocessed_invocations(), num);
+        assert_eq!(party_3.get_num_preprocessed_invocations(), num);
+
+        party_1.check_preprocessing();
+        party_2.check_preprocessing();
+        party_3.check_preprocessing();
+
+        // preprocess another n invocations
+        doprf_preprocess(&mut party_1, &mut party_2, &mut party_3, num);
+
+        let num = 2 * num;
+
+        assert_eq!(party_1.get_num_preprocessed_invocations(), num);
+        assert_eq!(party_2.get_num_preprocessed_invocations(), num);
+        assert_eq!(party_3.get_num_preprocessed_invocations(), num);
+
+        party_1.check_preprocessing();
+        party_2.check_preprocessing();
+        party_3.check_preprocessing();
+
+        // verify preprocessed data
+        {
+            let n = num * output_bitsize;
+            let (squares, mt_c1) = party_1.get_preprocessed_data();
+            let rerand_m2 = party_2.get_preprocessed_data();
+            let (rerand_m3, mt_b, mt_c3, mult_d) = party_3.get_preprocessed_data();
+
+            assert_eq!(squares.len(), n);
+            assert!(squares.iter().all(|&x| Fp::legendre_symbol(x) == 1));
+
+            assert_eq!(rerand_m2.len(), num);
+            assert_eq!(rerand_m3.len(), num);
+            assert!(izip!(rerand_m2.iter(), rerand_m3.iter()).all(|(&m2, &m3)| m2 + m3 == Fp::ZERO));
+
+            let mt_a: Vec<Fp> = squares
+                .iter()
+                .zip(mult_d.iter())
+                .map(|(&s, &d)| s - d)
+                .collect();
+            assert_eq!(mult_d.len(), n);
+
+            assert_eq!(mt_a.len(), n);
+            assert_eq!(mt_b.len(), num);
+            assert_eq!(mt_c1.len(), n);
+            assert_eq!(mt_c3.len(), n);
+            let mut triple_it = izip!(
+                mt_a.iter(),
+                mt_b.iter().flat_map(|b| repeat(b).take(output_bitsize)),
+                mt_c1.iter(),
+                mt_c3.iter()
+            );
+            assert_eq!(triple_it.clone().count(), n);
+            assert!(triple_it.all(|(&a, &b, &c1, &c3)| a * b == c1 + c3));
+        }
+
+        // perform n evaluations
+        let num = 15;
+
+        let shares_1: Vec<Fp> = (0..num).map(|_| Fp::random(thread_rng())).collect();
+        let shares_2: Vec<Fp> = (0..num).map(|_| Fp::random(thread_rng())).collect();
+        let shares_3: Vec<Fp> = (0..num).map(|_| Fp::random(thread_rng())).collect();
+
+        let output = doprf_eval(
+            &mut party_1,
+            &mut party_2,
+            &mut party_3,
+            &shares_1,
+            &shares_2,
+            &shares_3,
+            num,
+        );
+
+        assert_eq!(party_1.get_num_preprocessed_invocations(), 25);
+        assert_eq!(party_2.get_num_preprocessed_invocations(), 25);
+        assert_eq!(party_3.get_num_preprocessed_invocations(), 25);
+        party_1.check_preprocessing();
+        party_2.check_preprocessing();
+        party_3.check_preprocessing();
+
+        assert_eq!(output.len(), num);
+        assert!(output.iter().all(|bv| bv.len() == output_bitsize));
+
+        // check that the output matches the non-distributed version
+        let legendre_prf_key = party_1.get_legendre_prf_key();
+        for i in 0..num {
+            let input_i = shares_1[i] + shares_2[i] + shares_3[i];
+            let output_i = LegendrePrf::<Fp>::eval_bits(&legendre_prf_key, input_i);
+            assert_eq!(output[i], output_i);
+        }
+    }
+
+    fn mdoprf_init(
+        party_1: &mut MaskedDOPrfParty1<Fp>,
+        party_2: &mut MaskedDOPrfParty2<Fp>,
+        party_3: &mut MaskedDOPrfParty3<Fp>,
+    ) {
+        let (msg_1_2, msg_1_3) = party_1.init_round_0();
+        let (msg_2_1, msg_2_3) = party_2.init_round_0();
+        let (msg_3_1, msg_3_2) = party_3.init_round_0();
+        party_1.init_round_1(msg_2_1, msg_3_1);
+        party_2.init_round_1(msg_1_2, msg_3_2);
+        party_3.init_round_1(msg_1_3, msg_2_3);
+    }
+
+    fn mdoprf_preprocess(
+        party_1: &mut MaskedDOPrfParty1<Fp>,
+        party_2: &mut MaskedDOPrfParty2<Fp>,
+        party_3: &mut MaskedDOPrfParty3<Fp>,
+        num: usize,
+    ) {
+        let (msg_1_2, msg_1_3) = party_1.preprocess_round_0(num);
+        let (msg_2_1, msg_2_3) = party_2.preprocess_round_0(num);
+        let (msg_3_1, msg_3_2) = party_3.preprocess_round_0(num);
+        party_1.preprocess_round_1(num, msg_2_1, msg_3_1);
+        party_2.preprocess_round_1(num, msg_1_2, msg_3_2);
+        party_3.preprocess_round_1(num, msg_1_3, msg_2_3);
+    }
+
+    fn mdoprf_eval(
+        party_1: &mut MaskedDOPrfParty1<Fp>,
+        party_2: &mut MaskedDOPrfParty2<Fp>,
+        party_3: &mut MaskedDOPrfParty3<Fp>,
+        shares_1: &[Fp],
+        shares_2: &[Fp],
+        shares_3: &[Fp],
+        num: usize,
+    ) -> (Vec<BitVec>, Vec<BitVec>, Vec<BitVec>) {
+        assert_eq!(shares_1.len(), num);
+        assert_eq!(shares_2.len(), num);
+        assert_eq!(shares_3.len(), num);
+
+        let (_, msg_1_3) = party_1.eval_round_0(num, &shares_1);
+        let (_, msg_2_3) = party_2.eval_round_0(num, &shares_2);
+        let (msg_3_1, _) = party_3.eval_round_1(num, &shares_3, &msg_1_3, &msg_2_3);
+        let masked_output = party_1.eval_round_2(num, &shares_1, (), msg_3_1);
+        let mask2 = party_2.eval_get_output(num);
+        let mask3 = party_3.eval_get_output(num);
+        (masked_output, mask2, mask3)
+    }
+
+    #[test]
+    fn test_masked_doprf() {
+        let output_bitsize = 42;
+
+        let mut party_1 = MaskedDOPrfParty1::<Fp>::new(output_bitsize);
+        let mut party_2 = MaskedDOPrfParty2::<Fp>::new(output_bitsize);
+        let mut party_3 = MaskedDOPrfParty3::<Fp>::new(output_bitsize);
+
+        mdoprf_init(&mut party_1, &mut party_2, &mut party_3);
+
+        // preprocess num invocations
+        let num = 20;
+        mdoprf_preprocess(&mut party_1, &mut party_2, &mut party_3, num);
+
+        assert_eq!(party_1.get_num_preprocessed_invocations(), num);
+        assert_eq!(party_2.get_num_preprocessed_invocations(), num);
+        assert_eq!(party_3.get_num_preprocessed_invocations(), num);
+
+        party_1.check_preprocessing();
+        party_2.check_preprocessing();
+        party_3.check_preprocessing();
+
+        // preprocess another n invocations
+        mdoprf_preprocess(&mut party_1, &mut party_2, &mut party_3, num);
+
+        let num = 2 * num;
+
+        assert_eq!(party_1.get_num_preprocessed_invocations(), num);
+        assert_eq!(party_2.get_num_preprocessed_invocations(), num);
+        assert_eq!(party_3.get_num_preprocessed_invocations(), num);
+
+        party_1.check_preprocessing();
+        party_2.check_preprocessing();
+        party_3.check_preprocessing();
+
+        // verify preprocessed data
+        {
+            let n = num * output_bitsize;
+            let (rerand_m1, mt_a, mt_c1, mult_e) = party_1.get_preprocessed_data();
+            let (r2, rerand_m2) = party_2.get_preprocessed_data();
+            let (r3, ts, mt_c3) = party_3.get_preprocessed_data();
+
+            assert_eq!(r2.len(), n);
+            assert_eq!(r2, r3);
+            assert_eq!(ts.len(), n);
+            assert!(r2.iter().by_vals().zip(ts.iter()).all(|(r_i, &t_i)| {
+                if r_i {
+                    Fp::legendre_symbol(t_i) == -1
+                } else {
+                    Fp::legendre_symbol(t_i) == 1
+                }
+            }));
+
+            assert_eq!(rerand_m1.len(), num);
+            assert_eq!(rerand_m2.len(), num);
+            assert!(izip!(rerand_m1.iter(), rerand_m2.iter()).all(|(&m1, &m2)| m1 + m2 == Fp::ZERO));
+
+            let mt_b: Vec<Fp> = ts.iter().zip(mult_e.iter()).map(|(&t, &e)| t - e).collect();
+            assert_eq!(mult_e.len(), n);
+
+            assert_eq!(mt_a.len(), n);
+            assert_eq!(mt_b.len(), n);
+            assert_eq!(mt_c1.len(), n);
+            assert_eq!(mt_c3.len(), n);
+            let mut triple_it = izip!(mt_a.iter(), mt_b.iter(), mt_c1.iter(), mt_c3.iter());
+            assert_eq!(triple_it.clone().count(), n);
+            assert!(triple_it.all(|(&a, &b, &c1, &c3)| a * b == c1 + c3));
+        }
+
+        // perform n evaluations
+        let num = 15;
+
+        let shares_1: Vec<Fp> = (0..num).map(|_| Fp::random(thread_rng())).collect();
+        let shares_2: Vec<Fp> = (0..num).map(|_| Fp::random(thread_rng())).collect();
+        let shares_3: Vec<Fp> = (0..num).map(|_| Fp::random(thread_rng())).collect();
+        let (masked_output, mask2, mask3) = mdoprf_eval(
+            &mut party_1,
+            &mut party_2,
+            &mut party_3,
+            &shares_1,
+            &shares_2,
+            &shares_3,
+            num,
+        );
+
+        assert_eq!(party_1.get_num_preprocessed_invocations(), 25);
+        assert_eq!(party_2.get_num_preprocessed_invocations(), 25);
+        assert_eq!(party_3.get_num_preprocessed_invocations(), 25);
+        party_1.check_preprocessing();
+        party_2.check_preprocessing();
+        party_3.check_preprocessing();
+
+        assert_eq!(masked_output.len(), num);
+        assert!(masked_output.iter().all(|bv| bv.len() == output_bitsize));
+        assert_eq!(mask2.len(), num);
+        assert_eq!(mask2, mask3);
+        assert!(mask2.iter().all(|bv| bv.len() == output_bitsize));
+
+        // check that the output matches the non-distributed version
+        let legendre_prf_key = party_1.get_legendre_prf_key();
+        for i in 0..num {
+            let input_i = shares_1[i] + shares_2[i] + shares_3[i];
+            let expected_output_i = LegendrePrf::<Fp>::eval_bits(&legendre_prf_key, input_i);
+            let output_i = masked_output[i].clone() ^ &mask2[i];
+            assert_eq!(output_i, expected_output_i);
+        }
+
+        // preprocess another n invocations
+        mdoprf_preprocess(&mut party_1, &mut party_2, &mut party_3, num);
+
+        // perform another n evaluations on the same inputs
+        let num = 15;
+
+        let (new_masked_output, new_mask2, new_mask3) = mdoprf_eval(
+            &mut party_1,
+            &mut party_2,
+            &mut party_3,
+            &shares_1,
+            &shares_2,
+            &shares_3,
+            num,
+        );
+
+        assert_eq!(party_1.get_num_preprocessed_invocations(), 25);
+        assert_eq!(party_2.get_num_preprocessed_invocations(), 25);
+        assert_eq!(party_3.get_num_preprocessed_invocations(), 25);
+        party_1.check_preprocessing();
+        party_2.check_preprocessing();
+        party_3.check_preprocessing();
+
+        assert_eq!(new_masked_output.len(), num);
+        assert!(new_masked_output
+            .iter()
+            .all(|bv| bv.len() == output_bitsize));
+        assert_eq!(new_mask2.len(), num);
+        assert_eq!(new_mask2, new_mask3);
+        assert!(new_mask2.iter().all(|bv| bv.len() == output_bitsize));
+
+        // check that the new output matches the previous one
+        for i in 0..num {
+            let expected_output_i = masked_output[i].clone() ^ &mask2[i];
+            let output_i = new_masked_output[i].clone() ^ &new_mask2[i];
+            assert_eq!(output_i, expected_output_i);
+        }
+    }
+
+    #[test]
+    fn test_masked_doprf_single() {
+        let output_bitsize = 42;
+
+        let mut party_1 = MaskedDOPrfParty1::<Fp>::new(output_bitsize);
+        let mut party_2 = MaskedDOPrfParty2::<Fp>::new(output_bitsize);
+        let mut party_3 = MaskedDOPrfParty3::<Fp>::new(output_bitsize);
+
+        mdoprf_init(&mut party_1, &mut party_2, &mut party_3);
+
+        let share_1 = Fp::random(thread_rng());
+        let share_2 = Fp::random(thread_rng());
+        let share_3 = Fp::random(thread_rng());
+
+        mdoprf_preprocess(&mut party_1, &mut party_2, &mut party_3, 1);
+        let (masked_output_1, mask2_1, mask3_1) = mdoprf_eval(
+            &mut party_1,
+            &mut party_2,
+            &mut party_3,
+            &[share_1],
+            &[share_2],
+            &[share_3],
+            1,
+        );
+        mdoprf_preprocess(&mut party_1, &mut party_2, &mut party_3, 1);
+        let (masked_output_2, mask2_2, mask3_2) = mdoprf_eval(
+            &mut party_1,
+            &mut party_2,
+            &mut party_3,
+            &[share_1],
+            &[share_2],
+            &[share_3],
+            1,
+        );
+        mdoprf_preprocess(&mut party_1, &mut party_2, &mut party_3, 1);
+        let (masked_output_3, mask2_3, mask3_3) = mdoprf_eval(
+            &mut party_1,
+            &mut party_2,
+            &mut party_3,
+            &[share_1],
+            &[share_2],
+            &[share_3],
+            1,
+        );
+
+        assert_eq!(mask2_1, mask3_1);
+        assert_eq!(mask2_2, mask3_2);
+        assert_eq!(mask2_3, mask3_3);
+        let plain_output = masked_output_1[0].clone() ^ mask2_1[0].clone();
+        assert_eq!(
+            masked_output_2[0].clone() ^ mask2_2[0].clone(),
+            plain_output
+        );
+        assert_eq!(
+            masked_output_3[0].clone() ^ mask2_3[0].clone(),
+            plain_output
+        );
+    }
+
+    #[test]
+    fn test_serialization() {
+        let original_key = LegendrePrf::<Fp>::key_gen(42);
+        let encoded_key =
+            bincode::encode_to_vec(&original_key, bincode::config::standard()).unwrap();
+        let (decoded_key, _size): (LegendrePrfKey<Fp>, usize) =
+            bincode::decode_from_slice(&encoded_key, bincode::config::standard()).unwrap();
+        assert_eq!(decoded_key, original_key);
+    }
+}

+ 12 - 0
oram/src/lib.rs

@@ -0,0 +1,12 @@
+//! Implementation of the [Ramen distributed oblivious RAM
+//! protocol](https://eprint.iacr.org/2023/310).
+#![warn(missing_docs)]
+
+pub mod common;
+pub mod doprf;
+pub mod mask_index;
+pub mod oram;
+pub mod p_ot;
+pub mod select;
+pub mod stash;
+pub mod tools;

+ 123 - 0
oram/src/mask_index.rs

@@ -0,0 +1,123 @@
+//! Implementation of a protocol to convert a secret shared index in Fp into an equvalent secret
+//! sharing over modulo 2^k.
+//!
+//! The k-bit index is relatively small compared to p. First two parties add a large mask to the
+//! shared index, that statistically hides it, but does not overflow modulo p. The masked index is
+//! reconstruct for the third party.  Finally, all parties locally reduce their mask or masked
+//! value modulo 2^k.
+//!
+//! The protocol runs three instances of this such that at the end each party holds one masked
+//! index and the masks corresponding to the other two parties.
+
+use crate::common::Error;
+use communicator::{AbstractCommunicator, Fut, Serializable};
+use ff::PrimeField;
+use rand::{thread_rng, Rng};
+
+/// Interface specification.
+pub trait MaskIndex<F> {
+    /// Run the mask index protocol where the shared index is at most `index_bits` big.
+    fn mask_index<C: AbstractCommunicator>(
+        comm: &mut C,
+        index_bits: u32,
+        index_share: F,
+    ) -> Result<(u16, u16, u16), Error>;
+}
+
+/// Protocol implementation.
+pub struct MaskIndexProtocol {}
+
+impl<F> MaskIndex<F> for MaskIndexProtocol
+where
+    F: PrimeField + Serializable,
+{
+    fn mask_index<C: AbstractCommunicator>(
+        comm: &mut C,
+        index_bits: u32,
+        index_share: F,
+    ) -> Result<(u16, u16, u16), Error> {
+        let random_bits = index_bits + 40;
+        assert!(random_bits + 1 < F::NUM_BITS);
+        assert!(index_bits <= 16);
+
+        let bit_mask = (1 << index_bits) - 1;
+
+        let fut_prev = comm.receive_previous::<F>()?;
+        let fut_next = comm.receive_next::<(u16, F)>()?;
+
+        // sample mask r_{i+1} and send it to P_{i-1}
+        let r_next: u128 = thread_rng().gen_range(0..(1 << random_bits));
+        // send masked share to P_{i+1}
+        comm.send_next(index_share + F::from_u128(r_next))?;
+        let r_next = (r_next & bit_mask) as u16;
+        // send mask and our share to P_{i-1}
+        comm.send_previous((r_next, index_share))?;
+
+        let index_masked_prev_share = fut_prev.get()?;
+        let (r_prev, index_next_share) = fut_next.get()?;
+
+        let masked_index = index_share + index_next_share + index_masked_prev_share;
+        let masked_index =
+            u64::from_le_bytes(masked_index.to_repr().as_ref()[..8].try_into().unwrap());
+        let masked_index = masked_index as u16 & bit_mask as u16;
+
+        Ok((masked_index, r_prev, r_next))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use communicator::unix::make_unix_communicators;
+    use ff::Field;
+    use std::thread;
+    use utils::field::Fp;
+
+    fn run_mask_index<Proto: MaskIndex<F>, F>(
+        mut comm: impl AbstractCommunicator + Send + 'static,
+        index_bits: u32,
+        index_share: F,
+    ) -> thread::JoinHandle<(impl AbstractCommunicator, (u16, u16, u16))>
+    where
+        F: PrimeField + Serializable,
+    {
+        thread::spawn(move || {
+            let result = Proto::mask_index(&mut comm, index_bits, index_share);
+            (comm, result.unwrap())
+        })
+    }
+
+    #[test]
+    fn test_mask_index() {
+        let (comm_3, comm_2, comm_1) = {
+            let mut comms = make_unix_communicators(3);
+            (
+                comms.pop().unwrap(),
+                comms.pop().unwrap(),
+                comms.pop().unwrap(),
+            )
+        };
+        let mut rng = thread_rng();
+        let index_bits = 16;
+        let bit_mask = ((1 << index_bits) - 1) as u16;
+        let index = rng.gen_range(0..(1 << index_bits));
+        let (index_2, index_3) = (Fp::random(&mut rng), Fp::random(&mut rng));
+        let index_1 = Fp::from_u128(index as u128) - index_2 - index_3;
+
+        // check for <c> = <0>
+        let h1 = run_mask_index::<MaskIndexProtocol, _>(comm_1, index_bits, index_1);
+        let h2 = run_mask_index::<MaskIndexProtocol, _>(comm_2, index_bits, index_2);
+        let h3 = run_mask_index::<MaskIndexProtocol, _>(comm_3, index_bits, index_3);
+        let (_, (mi_1, m3_1, m2_1)) = h1.join().unwrap();
+        let (_, (mi_2, m1_2, m3_2)) = h2.join().unwrap();
+        let (_, (mi_3, m2_3, m1_3)) = h3.join().unwrap();
+
+        assert_eq!(m1_2, m1_3);
+        assert_eq!(m2_1, m2_3);
+        assert_eq!(m3_1, m3_2);
+        assert_eq!(m1_2, m1_3);
+        assert_eq!(mi_1, (index as u16).wrapping_add(m1_2) & bit_mask);
+        assert_eq!(mi_2, (index as u16).wrapping_add(m2_1) & bit_mask);
+        assert_eq!(mi_3, (index as u16).wrapping_add(m3_1) & bit_mask);
+    }
+}

+ 1525 - 0
oram/src/oram.rs

@@ -0,0 +1,1525 @@
+//! Implementation of the main distributed oblivious RAM protocol.
+
+use crate::common::{Error, InstructionShare};
+use crate::doprf::{JointDOPrf, LegendrePrf, LegendrePrfKey};
+use crate::p_ot::JointPOTParties;
+use crate::select::{Select, SelectProtocol};
+use crate::stash::{
+    ProtocolStep as StashProtocolStep, Runtimes as StashRuntimes, Stash, StashProtocol,
+};
+use communicator::{AbstractCommunicator, Fut, Serializable};
+use dpf::{mpdpf::MultiPointDpf, spdpf::SinglePointDpf};
+use ff::PrimeField;
+use itertools::Itertools;
+use rand::thread_rng;
+use rayon::prelude::*;
+use std::collections::VecDeque;
+use std::iter::repeat;
+use std::marker::PhantomData;
+use std::time::{Duration, Instant};
+use strum::IntoEnumIterator;
+use utils::field::{FromPrf, LegendreSymbol};
+use utils::permutation::FisherYatesPermutation;
+
+/// Specification of the DORAM interface.
+pub trait DistributedOram<F>
+where
+    F: PrimeField,
+{
+    /// Get the current parties ID.
+    fn get_party_id(&self) -> usize;
+
+    /// Get the database size.
+    fn get_db_size(&self) -> usize;
+
+    /// Run the initialization protocol using the given database share.
+    fn init<C: AbstractCommunicator>(&mut self, comm: &mut C, db_share: &[F]) -> Result<(), Error>;
+
+    /// Run the preprocessing protocol for the given `number_epochs`.
+    fn preprocess<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        number_epochs: usize,
+    ) -> Result<(), Error>;
+
+    /// Run the access protocol for the given shared instruction.
+    fn access<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        instruction: InstructionShare<F>,
+    ) -> Result<F, Error>;
+
+    /// Get the share of the database.
+    ///
+    /// If `rerandomize_shares` is true, perform extra rerandomization.
+    fn get_db<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        rerandomize_shares: bool,
+    ) -> Result<Vec<F>, Error>;
+}
+
+const PARTY_1: usize = 0;
+// const PARTY_2: usize = 1;
+// const PARTY_3: usize = 2;
+
+fn compute_oram_prf_output_bitsize(memory_size: usize) -> usize {
+    (usize::BITS - memory_size.leading_zeros()) as usize + 40
+}
+
+/// Steps of the DORAM protocol.
+#[allow(missing_docs)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, strum_macros::EnumIter, strum_macros::Display)]
+pub enum ProtocolStep {
+    Preprocess = 0,
+    PreprocessLPRFKeyGenPrev,
+    PreprocessLPRFEvalSortPrev,
+    PreprocessLPRFKeyRecvNext,
+    PreprocessLPRFEvalSortNext,
+    PreprocessMpDpdfPrecomp,
+    PreprocessRecvTagsMine,
+    PreprocessStash,
+    PreprocessDOPrf,
+    PreprocessPOt,
+    PreprocessSelect,
+    Access,
+    AccessStashRead,
+    AccessAddressSelection,
+    AccessDatabaseRead,
+    AccessStashWrite,
+    AccessValueSelection,
+    AccessRefresh,
+    DbReadAddressTag,
+    DbReadGarbledIndex,
+    DbReadPotAccess,
+    DbWriteMpDpfKeyExchange,
+    DbWriteMpDpfEvaluations,
+    DbWriteUpdateMemory,
+    RefreshJitPreprocess,
+    RefreshResetFuncs,
+    RefreshGetPreproc,
+    RefreshSorting,
+    RefreshPOtExpandMasking,
+    RefreshReceivingShare,
+}
+
+/// Collection of accumulated runtimes for the protocol steps.
+#[derive(Debug, Default, Clone, Copy)]
+pub struct Runtimes {
+    durations: [Duration; 30],
+    stash_runtimes: StashRuntimes,
+}
+
+impl Runtimes {
+    /// Add another duration to the accumulated runtimes for a protocol step.
+    #[inline(always)]
+    pub fn record(&mut self, id: ProtocolStep, duration: Duration) {
+        self.durations[id as usize] += duration;
+    }
+
+    /// Get a copy of the recorded runtimes of the stash protocol,
+    pub fn get_stash_runtimes(&self) -> StashRuntimes {
+        self.stash_runtimes
+    }
+
+    /// Set the recorded runtimes of the stash protocol,
+    pub fn set_stash_runtimes(&mut self, stash_runtimes: StashRuntimes) {
+        self.stash_runtimes = stash_runtimes;
+    }
+
+    /// Get the accumulated durations for a protocol step.
+    pub fn get(&self, id: ProtocolStep) -> Duration {
+        self.durations[id as usize]
+    }
+
+    /// Pretty-print the recorded runtimes amortized over `num_accesses`.
+    pub fn print(&self, party_id: usize, num_accesses: usize) {
+        println!("==================== Party {party_id} ====================");
+        println!("- times per access over {num_accesses} accesses in total");
+        println!(
+            "{:30}    {:7.3} ms",
+            ProtocolStep::Preprocess,
+            self.get(ProtocolStep::Preprocess).as_secs_f64() * 1000.0 / num_accesses as f64
+        );
+        for step in ProtocolStep::iter()
+            .filter(|x| x.to_string().starts_with("Preprocess") && *x != ProtocolStep::Preprocess)
+        {
+            println!(
+                "    {:26}    {:7.3} ms",
+                step,
+                self.get(step).as_secs_f64() * 1000.0 / num_accesses as f64
+            );
+        }
+        for step in ProtocolStep::iter().filter(|x| x.to_string().starts_with("Access")) {
+            println!(
+                "{:30}    {:7.3} ms",
+                step,
+                self.get(step).as_secs_f64() * 1000.0 / num_accesses as f64
+            );
+            match step {
+                ProtocolStep::AccessDatabaseRead => {
+                    for step in ProtocolStep::iter().filter(|x| x.to_string().starts_with("DbRead"))
+                    {
+                        println!(
+                            "    {:26}    {:7.3} ms",
+                            step,
+                            self.get(step).as_secs_f64() * 1000.0 / num_accesses as f64
+                        );
+                    }
+                }
+                ProtocolStep::AccessRefresh => {
+                    for step in ProtocolStep::iter().filter(|x| {
+                        x.to_string().starts_with("DbWrite") || x.to_string().starts_with("Refresh")
+                    }) {
+                        println!(
+                            "    {:26}    {:7.3} ms",
+                            step,
+                            self.get(step).as_secs_f64() * 1000.0 / num_accesses as f64
+                        );
+                    }
+                }
+                ProtocolStep::AccessStashRead => {
+                    for step in
+                        StashProtocolStep::iter().filter(|x| x.to_string().starts_with("Read"))
+                    {
+                        println!(
+                            "    {:26}    {:7.3} ms",
+                            step,
+                            self.stash_runtimes.get(step).as_secs_f64() * 1000.0
+                                / num_accesses as f64
+                        );
+                    }
+                }
+                ProtocolStep::AccessStashWrite => {
+                    for step in
+                        StashProtocolStep::iter().filter(|x| x.to_string().starts_with("Write"))
+                    {
+                        println!(
+                            "    {:26}    {:7.3} ms",
+                            step,
+                            self.stash_runtimes.get(step).as_secs_f64() * 1000.0
+                                / num_accesses as f64
+                        );
+                    }
+                }
+                _ => {}
+            }
+        }
+        println!("==================================================");
+    }
+}
+
+/// Implementation of the DORAM protocol.
+pub struct DistributedOramProtocol<F, MPDPF, SPDPF>
+where
+    F: FromPrf + LegendreSymbol + Serializable,
+    F::PrfKey: Serializable,
+    MPDPF: MultiPointDpf<Value = F>,
+    MPDPF::Key: Serializable,
+    SPDPF: SinglePointDpf<Value = F>,
+    SPDPF::Key: Serializable,
+{
+    party_id: usize,
+    db_size: usize,
+    stash_size: usize,
+    memory_size: usize,
+    memory_share: Vec<F>,
+    prf_output_bitsize: usize,
+    number_preprocessed_epochs: usize,
+    preprocessed_legendre_prf_key_next: VecDeque<LegendrePrfKey<F>>,
+    preprocessed_legendre_prf_key_prev: VecDeque<LegendrePrfKey<F>>,
+    preprocessed_memory_index_tags_prev: VecDeque<Vec<u128>>,
+    preprocessed_memory_index_tags_next: VecDeque<Vec<u128>>,
+    preprocessed_memory_index_tags_mine_sorted: VecDeque<Vec<u128>>,
+    preprocessed_memory_index_tags_prev_sorted: VecDeque<Vec<u128>>,
+    preprocessed_memory_index_tags_next_sorted: VecDeque<Vec<u128>>,
+    preprocessed_stash: VecDeque<StashProtocol<F, SPDPF>>,
+    preprocessed_select: VecDeque<SelectProtocol<F>>,
+    preprocessed_doprf: VecDeque<JointDOPrf<F>>,
+    preprocessed_pot: VecDeque<JointPOTParties<F, FisherYatesPermutation>>,
+    preprocessed_pot_expands: VecDeque<Vec<F>>,
+    memory_index_tags_prev: Vec<u128>,
+    memory_index_tags_next: Vec<u128>,
+    memory_index_tags_prev_sorted: Vec<u128>,
+    memory_index_tags_next_sorted: Vec<u128>,
+    memory_index_tags_mine_sorted: Vec<u128>,
+    garbled_memory_share: Vec<F>,
+    is_initialized: bool,
+    address_tags_read: Vec<u128>,
+    stash: Option<StashProtocol<F, SPDPF>>,
+    select_party: Option<SelectProtocol<F>>,
+    joint_doprf: Option<JointDOPrf<F>>,
+    legendre_prf_key_next: Option<LegendrePrfKey<F>>,
+    legendre_prf_key_prev: Option<LegendrePrfKey<F>>,
+    joint_pot: Option<JointPOTParties<F, FisherYatesPermutation>>,
+    mpdpf: MPDPF,
+    _phantom: PhantomData<MPDPF>,
+}
+
+impl<F, MPDPF, SPDPF> DistributedOramProtocol<F, MPDPF, SPDPF>
+where
+    F: FromPrf + LegendreSymbol + Serializable,
+    F::PrfKey: Serializable + Sync,
+    MPDPF: MultiPointDpf<Value = F> + Sync,
+    MPDPF::Key: Serializable,
+    SPDPF: SinglePointDpf<Value = F> + Sync,
+    SPDPF::Key: Serializable + Sync,
+{
+    /// Create a new instance.
+    pub fn new(party_id: usize, db_size: usize) -> Self {
+        assert!(party_id < 3);
+        let stash_size = (db_size as f64).sqrt().round() as usize;
+        let memory_size = db_size + stash_size;
+        let prf_output_bitsize = compute_oram_prf_output_bitsize(memory_size);
+
+        Self {
+            party_id,
+            db_size,
+            stash_size,
+            memory_size,
+            memory_share: Default::default(),
+            number_preprocessed_epochs: 0,
+            prf_output_bitsize,
+            preprocessed_legendre_prf_key_next: Default::default(),
+            preprocessed_legendre_prf_key_prev: Default::default(),
+            preprocessed_memory_index_tags_prev: Default::default(),
+            preprocessed_memory_index_tags_next: Default::default(),
+            preprocessed_memory_index_tags_mine_sorted: Default::default(),
+            preprocessed_memory_index_tags_prev_sorted: Default::default(),
+            preprocessed_memory_index_tags_next_sorted: Default::default(),
+            preprocessed_stash: Default::default(),
+            preprocessed_select: Default::default(),
+            preprocessed_doprf: Default::default(),
+            preprocessed_pot: Default::default(),
+            preprocessed_pot_expands: Default::default(),
+            memory_index_tags_prev: Default::default(),
+            memory_index_tags_next: Default::default(),
+            memory_index_tags_prev_sorted: Default::default(),
+            memory_index_tags_next_sorted: Default::default(),
+            memory_index_tags_mine_sorted: Default::default(),
+            garbled_memory_share: Default::default(),
+            is_initialized: false,
+            address_tags_read: Default::default(),
+            stash: None,
+            select_party: None,
+            joint_doprf: None,
+            legendre_prf_key_next: None,
+            legendre_prf_key_prev: None,
+            joint_pot: None,
+            mpdpf: MPDPF::new(memory_size, stash_size),
+            _phantom: PhantomData,
+        }
+    }
+
+    /// Get the current access counter.
+    pub fn get_access_counter(&self) -> usize {
+        self.stash.as_ref().unwrap().get_access_counter()
+    }
+
+    /// Get a reference to the stash protocol instance.
+    pub fn get_stash(&self) -> &StashProtocol<F, SPDPF> {
+        self.stash.as_ref().unwrap()
+    }
+
+    /// Return the size of the stash.
+    pub fn get_stash_size(&self) -> usize {
+        self.stash_size
+    }
+
+    fn pos_prev(&self, tag: u128) -> usize {
+        debug_assert_eq!(self.memory_index_tags_prev_sorted.len(), self.memory_size);
+        self.memory_index_tags_prev_sorted
+            .binary_search(&tag)
+            .expect("tag not found")
+    }
+
+    fn pos_next(&self, tag: u128) -> usize {
+        debug_assert_eq!(self.memory_index_tags_next_sorted.len(), self.memory_size);
+        self.memory_index_tags_next_sorted
+            .binary_search(&tag)
+            .expect("tag not found")
+    }
+
+    fn pos_mine(&self, tag: u128) -> usize {
+        debug_assert_eq!(self.memory_index_tags_mine_sorted.len(), self.memory_size);
+        self.memory_index_tags_mine_sorted
+            .binary_search(&tag)
+            .expect("tag not found")
+    }
+
+    fn read_from_database<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        address_share: F,
+        runtimes: Option<Runtimes>,
+    ) -> Result<(F, Option<Runtimes>), Error> {
+        let mut value_share = F::ZERO;
+
+        let t_start = Instant::now();
+
+        // 1. Compute address tag
+        let address_tag: u128 = self
+            .joint_doprf
+            .as_mut()
+            .unwrap()
+            .eval_to_uint(comm, &[address_share])?[0];
+
+        // 2. Update tags read list
+        self.address_tags_read.push(address_tag);
+
+        let t_after_address_tag = Instant::now();
+
+        // 3. Compute index in garbled memory and retrieve share
+        let garbled_index = self.pos_mine(address_tag);
+        value_share += self.garbled_memory_share[garbled_index];
+
+        let t_after_index_computation = Instant::now();
+
+        // 4. Run p-OT.Access
+        value_share -= self
+            .joint_pot
+            .as_ref()
+            .unwrap()
+            .access(comm, garbled_index)?;
+
+        let t_after_pot_access = Instant::now();
+
+        let runtimes = runtimes.map(|mut r| {
+            r.record(
+                ProtocolStep::DbReadAddressTag,
+                t_after_address_tag - t_start,
+            );
+            r.record(
+                ProtocolStep::DbReadAddressTag,
+                t_after_index_computation - t_after_address_tag,
+            );
+            r.record(
+                ProtocolStep::DbReadAddressTag,
+                t_after_pot_access - t_after_index_computation,
+            );
+            r
+        });
+
+        Ok((value_share, runtimes))
+    }
+
+    fn update_database_from_stash<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        runtimes: Option<Runtimes>,
+    ) -> Result<Option<Runtimes>, Error> {
+        let t_start = Instant::now();
+
+        let fut_dpf_key_from_prev = comm.receive_previous()?;
+        let fut_dpf_key_from_next = comm.receive_next()?;
+
+        let (_, stash_values_share, stash_old_values_share) =
+            self.stash.as_ref().unwrap().get_stash_share();
+        assert_eq!(stash_values_share.len(), self.get_access_counter());
+        assert_eq!(stash_old_values_share.len(), self.get_access_counter());
+        assert_eq!(self.address_tags_read.len(), self.get_access_counter());
+        let mut points: Vec<_> = self
+            .address_tags_read
+            .par_iter()
+            .copied()
+            .map(|tag| self.pos_mine(tag) as u64)
+            .collect();
+        let values: Vec<_> = stash_values_share
+            .par_iter()
+            .copied()
+            .zip(stash_old_values_share.par_iter().copied())
+            .map(|(val, old_val)| val - old_val)
+            .collect();
+        self.address_tags_read.truncate(0);
+
+        // sort point, value pairs
+        let (points, values): (Vec<u64>, Vec<F>) = {
+            let mut indices: Vec<usize> = (0..points.len()).collect();
+            indices.par_sort_unstable_by_key(|&i| points[i]);
+            points.par_sort();
+            let new_values = indices.par_iter().map(|&i| values[i]).collect();
+            (points, new_values)
+        };
+
+        let (dpf_key_prev, dpf_key_next) = self.mpdpf.generate_keys(&points, &values);
+        comm.send_previous(dpf_key_prev)?;
+        comm.send_next(dpf_key_next)?;
+        let dpf_key_from_prev = fut_dpf_key_from_prev.get()?;
+        let dpf_key_from_next = fut_dpf_key_from_next.get()?;
+
+        let t_after_mpdpf_key_exchange = Instant::now();
+
+        let new_memory_share_from_prev = self.mpdpf.evaluate_domain(&dpf_key_from_prev);
+        let new_memory_share_from_next = self.mpdpf.evaluate_domain(&dpf_key_from_next);
+
+        let t_after_mpdpf_evaluations = Instant::now();
+
+        {
+            let mut memory_share = Vec::new();
+            std::mem::swap(&mut self.memory_share, &mut memory_share);
+            memory_share
+                .par_iter_mut()
+                .enumerate()
+                .for_each(|(j, mem_cell)| {
+                    *mem_cell += new_memory_share_from_prev
+                        [self.pos_prev(self.memory_index_tags_prev[j])]
+                        + new_memory_share_from_next[self.pos_next(self.memory_index_tags_next[j])];
+                });
+            std::mem::swap(&mut self.memory_share, &mut memory_share);
+        }
+
+        let t_after_memory_update = Instant::now();
+
+        let runtimes = runtimes.map(|mut r| {
+            r.record(
+                ProtocolStep::DbWriteMpDpfKeyExchange,
+                t_after_mpdpf_key_exchange - t_start,
+            );
+            r.record(
+                ProtocolStep::DbWriteMpDpfEvaluations,
+                t_after_mpdpf_evaluations - t_after_mpdpf_key_exchange,
+            );
+            r.record(
+                ProtocolStep::DbWriteUpdateMemory,
+                t_after_memory_update - t_after_mpdpf_evaluations,
+            );
+            r
+        });
+
+        Ok(runtimes)
+    }
+
+    /// Run the preprocessing protocol and collect runtime data.
+    pub fn preprocess_with_runtimes<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        number_epochs: usize,
+        runtimes: Option<Runtimes>,
+    ) -> Result<Option<Runtimes>, Error> {
+        let already_preprocessed = self.number_preprocessed_epochs;
+
+        // Reserve some space
+        self.preprocessed_legendre_prf_key_prev
+            .reserve(number_epochs);
+        self.preprocessed_legendre_prf_key_next
+            .reserve(number_epochs);
+        self.preprocessed_memory_index_tags_prev
+            .reserve(number_epochs);
+        self.preprocessed_memory_index_tags_next
+            .reserve(number_epochs);
+        self.preprocessed_memory_index_tags_prev_sorted
+            .reserve(number_epochs);
+        self.preprocessed_memory_index_tags_next_sorted
+            .reserve(number_epochs);
+        self.preprocessed_memory_index_tags_mine_sorted
+            .reserve(number_epochs);
+        self.preprocessed_stash.reserve(number_epochs);
+        self.preprocessed_select.reserve(number_epochs);
+        self.preprocessed_doprf.reserve(number_epochs);
+        self.preprocessed_pot.reserve(number_epochs);
+        self.preprocessed_pot_expands.reserve(number_epochs);
+
+        let t_start = Instant::now();
+
+        // Generate Legendre PRF keys
+        let fut_lpks_next = comm.receive_previous::<Vec<LegendrePrfKey<F>>>()?;
+        let fut_tags_mine_sorted = comm.receive_previous::<Vec<Vec<u128>>>()?;
+        self.preprocessed_legendre_prf_key_prev
+            .extend((0..number_epochs).map(|_| LegendrePrf::key_gen(self.prf_output_bitsize)));
+        let new_lpks_prev =
+            &self.preprocessed_legendre_prf_key_prev.make_contiguous()[already_preprocessed..];
+        comm.send_slice_next(new_lpks_prev.as_ref())?;
+
+        let t_after_gen_lpks_prev = Instant::now();
+
+        // Compute memory index tags
+        for lpk_prev in new_lpks_prev {
+            let memory_index_tags_prev: Vec<_> = (0..self.memory_size)
+                .into_par_iter()
+                .map(|j| LegendrePrf::eval_to_uint::<u128>(lpk_prev, F::from_u128(j as u128)))
+                .collect();
+            let mut memory_index_tags_prev_sorted = memory_index_tags_prev.clone();
+            memory_index_tags_prev_sorted.par_sort_unstable();
+            self.preprocessed_memory_index_tags_prev
+                .push_back(memory_index_tags_prev);
+            self.preprocessed_memory_index_tags_prev_sorted
+                .push_back(memory_index_tags_prev_sorted);
+        }
+
+        let t_after_computing_index_tags_prev = Instant::now();
+
+        self.preprocessed_legendre_prf_key_next
+            .extend(fut_lpks_next.get()?.into_iter());
+        let new_lpks_next =
+            &self.preprocessed_legendre_prf_key_next.make_contiguous()[already_preprocessed..];
+
+        let t_after_receiving_lpks_next = Instant::now();
+
+        for lpk_next in new_lpks_next {
+            let memory_index_tags_next: Vec<_> = (0..self.memory_size)
+                .into_par_iter()
+                .map(|j| LegendrePrf::eval_to_uint::<u128>(lpk_next, F::from_u128(j as u128)))
+                .collect();
+            let memory_index_tags_next_with_index_sorted: Vec<_> = memory_index_tags_next
+                .iter()
+                .copied()
+                .enumerate()
+                .sorted_unstable_by_key(|(_, x)| *x)
+                .collect();
+            self.preprocessed_memory_index_tags_next
+                .push_back(memory_index_tags_next);
+            self.preprocessed_memory_index_tags_next_sorted.push_back(
+                memory_index_tags_next_with_index_sorted
+                    .par_iter()
+                    .map(|(_, x)| *x)
+                    .collect(),
+            );
+        }
+        comm.send_next(
+            self.preprocessed_memory_index_tags_next_sorted
+                .make_contiguous()[already_preprocessed..]
+                .to_vec(),
+        )?;
+
+        let t_after_computing_index_tags_next = Instant::now();
+
+        self.mpdpf.precompute();
+
+        let t_after_mpdpf_precomp = Instant::now();
+
+        self.preprocessed_memory_index_tags_mine_sorted
+            .extend(fut_tags_mine_sorted.get()?);
+
+        let t_after_receiving_index_tags_mine = Instant::now();
+
+        // Initialize Stash instances
+        self.preprocessed_stash
+            .extend((0..number_epochs).map(|_| StashProtocol::new(self.party_id, self.stash_size)));
+        for stash in self
+            .preprocessed_stash
+            .iter_mut()
+            .skip(already_preprocessed)
+        {
+            stash.init(comm)?;
+        }
+
+        let t_after_init_stash = Instant::now();
+
+        // Initialize DOPRF instances
+        self.preprocessed_doprf
+            .extend((0..number_epochs).map(|_| JointDOPrf::new(self.prf_output_bitsize)));
+        for (doprf, lpk_prev) in self
+            .preprocessed_doprf
+            .iter_mut()
+            .skip(already_preprocessed)
+            .zip(
+                self.preprocessed_legendre_prf_key_prev
+                    .iter()
+                    .skip(already_preprocessed),
+            )
+        {
+            doprf.set_legendre_prf_key_prev(lpk_prev.clone());
+            doprf.init(comm)?;
+            doprf.preprocess(comm, self.stash_size)?;
+        }
+
+        let t_after_init_doprf = Instant::now();
+
+        // Precompute p-OTs and expand the mask
+        self.preprocessed_pot
+            .extend((0..number_epochs).map(|_| JointPOTParties::new(self.memory_size)));
+        for pot in self.preprocessed_pot.iter_mut().skip(already_preprocessed) {
+            pot.init(comm)?;
+        }
+        self.preprocessed_pot_expands.extend(
+            self.preprocessed_pot.make_contiguous()[already_preprocessed..]
+                .iter()
+                .map(|pot| pot.expand()),
+        );
+
+        let t_after_preprocess_pot = Instant::now();
+
+        self.preprocessed_select
+            .extend((0..number_epochs).map(|_| SelectProtocol::default()));
+        for select in self
+            .preprocessed_select
+            .iter_mut()
+            .skip(already_preprocessed)
+        {
+            select.init(comm)?;
+            select.preprocess(comm, 2 * self.stash_size)?;
+        }
+
+        let t_after_preprocess_select = Instant::now();
+
+        self.number_preprocessed_epochs += number_epochs;
+
+        debug_assert_eq!(
+            self.preprocessed_legendre_prf_key_prev.len(),
+            self.number_preprocessed_epochs
+        );
+        debug_assert_eq!(
+            self.preprocessed_legendre_prf_key_next.len(),
+            self.number_preprocessed_epochs
+        );
+        debug_assert_eq!(
+            self.preprocessed_memory_index_tags_prev.len(),
+            self.number_preprocessed_epochs
+        );
+        debug_assert_eq!(
+            self.preprocessed_memory_index_tags_prev_sorted.len(),
+            self.number_preprocessed_epochs
+        );
+        debug_assert_eq!(
+            self.preprocessed_memory_index_tags_next.len(),
+            self.number_preprocessed_epochs
+        );
+        debug_assert_eq!(
+            self.preprocessed_memory_index_tags_next_sorted.len(),
+            self.number_preprocessed_epochs
+        );
+        debug_assert_eq!(
+            self.preprocessed_memory_index_tags_mine_sorted.len(),
+            self.number_preprocessed_epochs
+        );
+        debug_assert_eq!(
+            self.preprocessed_stash.len(),
+            self.number_preprocessed_epochs
+        );
+        debug_assert_eq!(
+            self.preprocessed_doprf.len(),
+            self.number_preprocessed_epochs
+        );
+        debug_assert_eq!(self.preprocessed_pot.len(), self.number_preprocessed_epochs);
+        debug_assert_eq!(
+            self.preprocessed_pot_expands.len(),
+            self.number_preprocessed_epochs
+        );
+        debug_assert_eq!(
+            self.preprocessed_select.len(),
+            self.number_preprocessed_epochs
+        );
+
+        let runtimes = runtimes.map(|mut r| {
+            r.record(
+                ProtocolStep::PreprocessLPRFKeyGenPrev,
+                t_after_gen_lpks_prev - t_start,
+            );
+            r.record(
+                ProtocolStep::PreprocessLPRFEvalSortPrev,
+                t_after_computing_index_tags_prev - t_after_gen_lpks_prev,
+            );
+            r.record(
+                ProtocolStep::PreprocessLPRFKeyRecvNext,
+                t_after_receiving_lpks_next - t_after_computing_index_tags_prev,
+            );
+            r.record(
+                ProtocolStep::PreprocessLPRFEvalSortNext,
+                t_after_computing_index_tags_next - t_after_receiving_lpks_next,
+            );
+            r.record(
+                ProtocolStep::PreprocessMpDpdfPrecomp,
+                t_after_mpdpf_precomp - t_after_computing_index_tags_next,
+            );
+            r.record(
+                ProtocolStep::PreprocessRecvTagsMine,
+                t_after_receiving_index_tags_mine - t_after_mpdpf_precomp,
+            );
+            r.record(
+                ProtocolStep::PreprocessStash,
+                t_after_init_stash - t_after_receiving_index_tags_mine,
+            );
+            r.record(
+                ProtocolStep::PreprocessDOPrf,
+                t_after_init_doprf - t_after_init_stash,
+            );
+            r.record(
+                ProtocolStep::PreprocessPOt,
+                t_after_preprocess_pot - t_after_init_doprf,
+            );
+            r.record(
+                ProtocolStep::PreprocessSelect,
+                t_after_preprocess_select - t_after_preprocess_pot,
+            );
+            r.record(
+                ProtocolStep::Preprocess,
+                t_after_preprocess_select - t_start,
+            );
+            r
+        });
+
+        Ok(runtimes)
+    }
+
+    /// Run the refresh protocol at the end of an epoch.
+    fn refresh<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        runtimes: Option<Runtimes>,
+    ) -> Result<Option<Runtimes>, Error> {
+        let t_start = Instant::now();
+
+        // 0. Do preprocessing if not already done
+
+        let runtimes = if self.number_preprocessed_epochs == 0 {
+            self.preprocess_with_runtimes(comm, 1, runtimes)?
+        } else {
+            runtimes
+        };
+
+        let t_after_jit_preprocessing = Instant::now();
+
+        // 1. Expect to receive garbled memory share
+        let fut_garbled_memory_share = comm.receive_previous::<Vec<F>>()?;
+
+        // 2. Get fresh (initialized) instances of the functionalities
+
+        // a) Stash
+        self.stash = self.preprocessed_stash.pop_front();
+        debug_assert!(self.stash.is_some());
+
+        // b) DOPRF
+        self.legendre_prf_key_prev = self.preprocessed_legendre_prf_key_prev.pop_front();
+        self.legendre_prf_key_next = self.preprocessed_legendre_prf_key_next.pop_front();
+        self.joint_doprf = self.preprocessed_doprf.pop_front();
+        debug_assert!(self.legendre_prf_key_prev.is_some());
+        debug_assert!(self.legendre_prf_key_next.is_some());
+        debug_assert!(self.joint_doprf.is_some());
+
+        // c) p-OT
+        self.joint_pot = self.preprocessed_pot.pop_front();
+        debug_assert!(self.joint_pot.is_some());
+
+        // d) select
+        self.select_party = self.preprocessed_select.pop_front();
+        debug_assert!(self.joint_pot.is_some());
+
+        // e) Retrieve preprocessed index tags
+        self.memory_index_tags_prev = self
+            .preprocessed_memory_index_tags_prev
+            .pop_front()
+            .unwrap();
+        self.memory_index_tags_prev_sorted = self
+            .preprocessed_memory_index_tags_prev_sorted
+            .pop_front()
+            .unwrap();
+        self.memory_index_tags_next = self
+            .preprocessed_memory_index_tags_next
+            .pop_front()
+            .unwrap();
+        self.memory_index_tags_next_sorted = self
+            .preprocessed_memory_index_tags_next_sorted
+            .pop_front()
+            .unwrap();
+        self.memory_index_tags_mine_sorted = self
+            .preprocessed_memory_index_tags_mine_sorted
+            .pop_front()
+            .unwrap();
+        debug_assert!(
+            self.memory_index_tags_prev_sorted
+                .windows(2)
+                .all(|w| w[0] < w[1]),
+            "index tags not sorted or colliding"
+        );
+        debug_assert!(
+            self.memory_index_tags_next_sorted
+                .windows(2)
+                .all(|w| w[0] < w[1]),
+            "index tags not sorted or colliding"
+        );
+        debug_assert!(
+            self.memory_index_tags_mine_sorted
+                .windows(2)
+                .all(|w| w[0] < w[1]),
+            "index tags not sorted or colliding"
+        );
+
+        let t_after_get_preprocessed_data = Instant::now();
+
+        // 2.) Garble the memory share for the next party
+        let mut garbled_memory_share_next: Vec<_> = self
+            .memory_share
+            .iter()
+            .copied()
+            .zip(self.memory_index_tags_next.iter().copied())
+            .sorted_unstable_by_key(|(_, i)| *i)
+            .map(|(x, _)| x)
+            .collect();
+
+        let t_after_sort = Instant::now();
+
+        // the memory_index_tags_{prev,next} now define the pos_{prev,next} maps
+        // - pos_(i-1)(tag) -> index of tag in mem_idx_tags_prev
+        // - pos_(i+1)(tag) -> index of tag in mem_idx_tags_next
+
+        let mask = self.preprocessed_pot_expands.pop_front().unwrap();
+        self.memory_index_tags_next_sorted
+            .par_iter()
+            .zip(garbled_memory_share_next.par_iter_mut())
+            .for_each(|(&tag, val)| {
+                *val += mask[self.pos_next(tag)];
+            });
+        comm.send_next(garbled_memory_share_next)?;
+
+        let t_after_pot_expand = Instant::now();
+
+        self.garbled_memory_share = fut_garbled_memory_share.get()?;
+        // the garbled_memory_share now defines the pos_mine map:
+        // - pos_i(tag) -> index of tag in garbled_memory_share
+
+        let t_after_receiving = Instant::now();
+
+        // account that we used one set of preprocessing material
+        self.number_preprocessed_epochs -= 1;
+
+        let runtimes = runtimes.map(|mut r| {
+            r.record(
+                ProtocolStep::RefreshJitPreprocess,
+                t_after_jit_preprocessing - t_start,
+            );
+            r.record(
+                ProtocolStep::RefreshGetPreproc,
+                t_after_get_preprocessed_data - t_after_jit_preprocessing,
+            );
+            r.record(
+                ProtocolStep::RefreshSorting,
+                t_after_sort - t_after_get_preprocessed_data,
+            );
+            r.record(
+                ProtocolStep::RefreshPOtExpandMasking,
+                t_after_pot_expand - t_after_sort,
+            );
+            r.record(
+                ProtocolStep::RefreshReceivingShare,
+                t_after_receiving - t_after_pot_expand,
+            );
+            r
+        });
+
+        Ok(runtimes)
+    }
+
+    /// Run the access protocol and collect runtime data.
+    pub fn access_with_runtimes<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        instruction: InstructionShare<F>,
+        runtimes: Option<Runtimes>,
+    ) -> Result<(F, Option<Runtimes>), Error> {
+        assert!(self.is_initialized);
+
+        // 1. Read from the stash
+        let t_start = Instant::now();
+        let (stash_state, stash_runtimes) = self.stash.as_mut().unwrap().read_with_runtimes(
+            comm,
+            instruction,
+            runtimes.map(|r| r.get_stash_runtimes()),
+        )?;
+        let t_after_stash_read = Instant::now();
+
+        // 2. If the value was found in a stash, we read from the dummy address
+        let dummy_address_share = match self.party_id {
+            PARTY_1 => F::from_u128((self.db_size + self.get_access_counter()) as u128),
+            _ => F::ZERO,
+        };
+        let db_address_share = self.select_party.as_mut().unwrap().select(
+            comm,
+            stash_state.flag,
+            dummy_address_share,
+            instruction.address,
+        )?;
+        let t_after_address_selection = Instant::now();
+
+        // 3. Read a (dummy or real) value from the database
+        let (db_value_share, runtimes) =
+            self.read_from_database(comm, db_address_share, runtimes)?;
+        let t_after_db_read = Instant::now();
+
+        // 4. Write the read value into the stash
+        let stash_runtime = self.stash.as_mut().unwrap().write_with_runtimes(
+            comm,
+            instruction,
+            stash_state,
+            db_address_share,
+            db_value_share,
+            stash_runtimes,
+        )?;
+        let t_after_stash_write = Instant::now();
+
+        // 5. Select the right value to return
+        let read_value = self.select_party.as_mut().unwrap().select(
+            comm,
+            stash_state.flag,
+            stash_state.value,
+            db_value_share,
+        )?;
+        let t_after_value_selection = Instant::now();
+
+        // 6. If the stash is full, write the value back into the database
+        let runtimes = if self.get_access_counter() == self.stash_size {
+            let runtimes = self.update_database_from_stash(comm, runtimes)?;
+            self.refresh(comm, runtimes)?
+        } else {
+            runtimes
+        };
+        let t_after_refresh = Instant::now();
+
+        let runtimes = runtimes.map(|mut r| {
+            r.set_stash_runtimes(stash_runtime.unwrap());
+            r.record(ProtocolStep::AccessStashRead, t_after_stash_read - t_start);
+            r.record(
+                ProtocolStep::AccessAddressSelection,
+                t_after_address_selection - t_after_stash_read,
+            );
+            r.record(
+                ProtocolStep::AccessDatabaseRead,
+                t_after_db_read - t_after_address_selection,
+            );
+            r.record(
+                ProtocolStep::AccessStashWrite,
+                t_after_stash_write - t_after_db_read,
+            );
+            r.record(
+                ProtocolStep::AccessValueSelection,
+                t_after_value_selection - t_after_stash_write,
+            );
+            r.record(
+                ProtocolStep::AccessRefresh,
+                t_after_refresh - t_after_value_selection,
+            );
+            r.record(ProtocolStep::Access, t_after_refresh - t_start);
+            r
+        });
+
+        Ok((read_value, runtimes))
+    }
+}
+
+impl<F, MPDPF, SPDPF> DistributedOram<F> for DistributedOramProtocol<F, MPDPF, SPDPF>
+where
+    F: FromPrf + LegendreSymbol + Serializable,
+    F::PrfKey: Serializable + Sync,
+    MPDPF: MultiPointDpf<Value = F> + Sync,
+    MPDPF::Key: Serializable,
+    SPDPF: SinglePointDpf<Value = F> + Sync,
+    SPDPF::Key: Serializable + Sync,
+{
+    fn get_party_id(&self) -> usize {
+        self.party_id
+    }
+
+    fn get_db_size(&self) -> usize {
+        self.db_size
+    }
+
+    fn init<C: AbstractCommunicator>(&mut self, comm: &mut C, db_share: &[F]) -> Result<(), Error> {
+        assert_eq!(db_share.len(), self.db_size);
+
+        // 1. Initialize memory share with given db share and pad with dummy values
+        self.memory_share = Vec::with_capacity(self.memory_size);
+        self.memory_share.extend_from_slice(db_share);
+        self.memory_share
+            .extend(repeat(F::ZERO).take(self.stash_size));
+
+        // 2. Run the refresh protocol to initialize everything.
+        self.refresh(comm, None)?;
+
+        self.is_initialized = true;
+        Ok(())
+    }
+
+    fn preprocess<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        number_epochs: usize,
+    ) -> Result<(), Error> {
+        self.preprocess_with_runtimes(comm, number_epochs, None)
+            .map(|_| ())
+    }
+
+    fn access<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        instruction: InstructionShare<F>,
+    ) -> Result<F, Error> {
+        self.access_with_runtimes(comm, instruction, None)
+            .map(|x| x.0)
+    }
+
+    fn get_db<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        rerandomize_shares: bool,
+    ) -> Result<Vec<F>, Error> {
+        assert!(self.is_initialized);
+
+        if self.get_access_counter() > 0 {
+            self.refresh(comm, None)?;
+        }
+
+        if rerandomize_shares {
+            let fut = comm.receive_previous()?;
+            let mut rng = thread_rng();
+            let mask: Vec<_> = (0..self.db_size).map(|_| F::random(&mut rng)).collect();
+            let mut masked_share: Vec<_> = self.memory_share[0..self.db_size]
+                .iter()
+                .zip(mask.iter())
+                .map(|(&x, &m)| x + m)
+                .collect();
+            comm.send_next(mask)?;
+            let mask_prev: Vec<F> = fut.get()?;
+            masked_share
+                .iter_mut()
+                .zip(mask_prev.iter())
+                .for_each(|(x, &mp)| *x -= mp);
+            Ok(masked_share)
+        } else {
+            Ok(self.memory_share[0..self.db_size].to_vec())
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::common::Operation;
+    use communicator::unix::make_unix_communicators;
+    use dpf::mpdpf::DummyMpDpf;
+    use dpf::spdpf::DummySpDpf;
+    use ff::Field;
+    use itertools::izip;
+    use std::thread;
+    use utils::field::Fp;
+
+    const PARTY_1: usize = 0;
+    const PARTY_2: usize = 1;
+    const PARTY_3: usize = 2;
+
+    fn run_init<F, C, P>(
+        mut doram_party: P,
+        mut comm: C,
+        db_share: &[F],
+    ) -> thread::JoinHandle<(P, C)>
+    where
+        F: PrimeField,
+        C: AbstractCommunicator + Send + 'static,
+        P: DistributedOram<F> + Send + 'static,
+    {
+        let db_share = db_share.to_vec();
+        thread::Builder::new()
+            .name(format!("Party {}", doram_party.get_party_id()))
+            .spawn(move || {
+                doram_party.init(&mut comm, &db_share).unwrap();
+                (doram_party, comm)
+            })
+            .unwrap()
+    }
+
+    fn run_access<F, C, P>(
+        mut doram_party: P,
+        mut comm: C,
+        instruction: InstructionShare<F>,
+    ) -> thread::JoinHandle<(P, C, F)>
+    where
+        F: PrimeField,
+        C: AbstractCommunicator + Send + 'static,
+        P: DistributedOram<F> + Send + 'static,
+    {
+        thread::Builder::new()
+            .name(format!("Party {}", doram_party.get_party_id()))
+            .spawn(move || {
+                let output = doram_party.access(&mut comm, instruction).unwrap();
+                (doram_party, comm, output)
+            })
+            .unwrap()
+    }
+
+    fn run_get_db<F, C, P>(mut doram_party: P, mut comm: C) -> thread::JoinHandle<(P, C, Vec<F>)>
+    where
+        F: PrimeField,
+        C: AbstractCommunicator + Send + 'static,
+        P: DistributedOram<F> + Send + 'static,
+    {
+        thread::Builder::new()
+            .name(format!("Party {}", doram_party.get_party_id()))
+            .spawn(move || {
+                let output = doram_party.get_db(&mut comm, false).unwrap();
+                (doram_party, comm, output)
+            })
+            .unwrap()
+    }
+
+    fn mk_read(address: u128, value: u128) -> InstructionShare<Fp> {
+        InstructionShare {
+            operation: Operation::Read.encode(),
+            address: Fp::from_u128(address),
+            value: Fp::from_u128(value),
+        }
+    }
+
+    fn mk_write(address: u128, value: u128) -> InstructionShare<Fp> {
+        InstructionShare {
+            operation: Operation::Write.encode(),
+            address: Fp::from_u128(address),
+            value: Fp::from_u128(value),
+        }
+    }
+
+    const INST_ZERO_SHARE: InstructionShare<Fp> = InstructionShare {
+        operation: Fp::ZERO,
+        address: Fp::ZERO,
+        value: Fp::ZERO,
+    };
+
+    type SPDPF = DummySpDpf<Fp>;
+    type MPDPF = DummyMpDpf<Fp>;
+
+    fn setup(
+        db_size: usize,
+    ) -> (
+        (
+            impl DistributedOram<Fp>,
+            impl DistributedOram<Fp>,
+            impl DistributedOram<Fp>,
+        ),
+        (
+            impl AbstractCommunicator,
+            impl AbstractCommunicator,
+            impl AbstractCommunicator,
+        ),
+    ) {
+        let party_1 = DistributedOramProtocol::<Fp, MPDPF, SPDPF>::new(PARTY_1, db_size);
+        let party_2 = DistributedOramProtocol::<Fp, MPDPF, SPDPF>::new(PARTY_2, db_size);
+        let party_3 = DistributedOramProtocol::<Fp, MPDPF, SPDPF>::new(PARTY_3, db_size);
+        assert_eq!(party_1.get_party_id(), PARTY_1);
+        assert_eq!(party_2.get_party_id(), PARTY_2);
+        assert_eq!(party_3.get_party_id(), PARTY_3);
+        assert_eq!(party_1.get_db_size(), db_size);
+        assert_eq!(party_2.get_db_size(), db_size);
+        assert_eq!(party_3.get_db_size(), db_size);
+
+        let (comm_3, comm_2, comm_1) = {
+            let mut comms = make_unix_communicators(3);
+            (
+                comms.pop().unwrap(),
+                comms.pop().unwrap(),
+                comms.pop().unwrap(),
+            )
+        };
+
+        // Initialize DB with zeros
+        let db_share_1: Vec<_> = repeat(Fp::ZERO).take(db_size).collect();
+        let db_share_2: Vec<_> = repeat(Fp::ZERO).take(db_size).collect();
+        let db_share_3: Vec<_> = repeat(Fp::ZERO).take(db_size).collect();
+
+        let h1 = run_init(party_1, comm_1, &db_share_1);
+        let h2 = run_init(party_2, comm_2, &db_share_2);
+        let h3 = run_init(party_3, comm_3, &db_share_3);
+        let (party_1, comm_1) = h1.join().unwrap();
+        let (party_2, comm_2) = h2.join().unwrap();
+        let (party_3, comm_3) = h3.join().unwrap();
+        ((party_1, party_2, party_3), (comm_1, comm_2, comm_3))
+    }
+
+    #[test]
+    fn test_oram_even_exp() {
+        let db_size = 1 << 4;
+        let stash_size = (db_size as f64).sqrt().round() as usize;
+
+        let ((mut party_1, mut party_2, mut party_3), (mut comm_1, mut comm_2, mut comm_3)) =
+            setup(db_size);
+
+        let number_cycles = 8;
+        let instructions = [
+            mk_write(12, 18),
+            mk_read(12, 899),
+            mk_write(13, 457),
+            mk_write(0, 77),
+            mk_write(13, 515),
+            mk_write(15, 421),
+            mk_write(13, 895),
+            mk_write(4, 941),
+            mk_write(1, 358),
+            mk_read(9, 894),
+            mk_read(7, 678),
+            mk_write(3, 110),
+            mk_read(15, 691),
+            mk_read(13, 335),
+            mk_write(9, 286),
+            mk_read(13, 217),
+            mk_write(10, 167),
+            mk_read(3, 909),
+            mk_write(2, 949),
+            mk_read(14, 245),
+            mk_write(3, 334),
+            mk_write(0, 378),
+            mk_write(2, 129),
+            mk_write(5, 191),
+            mk_write(15, 662),
+            mk_write(4, 724),
+            mk_write(1, 190),
+            mk_write(6, 887),
+            mk_write(9, 271),
+            mk_read(12, 666),
+            mk_write(0, 57),
+            mk_write(2, 185),
+        ];
+        let expected_values = [
+            Fp::from_u128(0),
+            Fp::from_u128(18),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(457),
+            Fp::from_u128(0),
+            Fp::from_u128(515),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(421),
+            Fp::from_u128(895),
+            Fp::from_u128(0),
+            Fp::from_u128(895),
+            Fp::from_u128(0),
+            Fp::from_u128(110),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(110),
+            Fp::from_u128(77),
+            Fp::from_u128(949),
+            Fp::from_u128(0),
+            Fp::from_u128(421),
+            Fp::from_u128(941),
+            Fp::from_u128(358),
+            Fp::from_u128(0),
+            Fp::from_u128(286),
+            Fp::from_u128(18),
+            Fp::from_u128(378),
+            Fp::from_u128(129),
+        ];
+        let expected_db_contents = [
+            [77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 457, 0, 0],
+            [77, 0, 0, 0, 941, 0, 0, 0, 0, 0, 0, 0, 18, 895, 0, 421],
+            [77, 358, 0, 110, 941, 0, 0, 0, 0, 0, 0, 0, 18, 895, 0, 421],
+            [77, 358, 0, 110, 941, 0, 0, 0, 0, 286, 0, 0, 18, 895, 0, 421],
+            [
+                77, 358, 949, 110, 941, 0, 0, 0, 0, 286, 167, 0, 18, 895, 0, 421,
+            ],
+            [
+                378, 358, 129, 334, 941, 191, 0, 0, 0, 286, 167, 0, 18, 895, 0, 421,
+            ],
+            [
+                378, 190, 129, 334, 724, 191, 887, 0, 0, 286, 167, 0, 18, 895, 0, 662,
+            ],
+            [
+                57, 190, 185, 334, 724, 191, 887, 0, 0, 271, 167, 0, 18, 895, 0, 662,
+            ],
+        ];
+
+        for i in 0..number_cycles {
+            for j in 0..stash_size {
+                let inst = instructions[i * stash_size + j];
+                let expected_value = expected_values[i * stash_size + j];
+                let h1 = run_access(party_1, comm_1, inst);
+                let h2 = run_access(party_2, comm_2, INST_ZERO_SHARE);
+                let h3 = run_access(party_3, comm_3, INST_ZERO_SHARE);
+                let (p1, c1, value_1) = h1.join().unwrap();
+                let (p2, c2, value_2) = h2.join().unwrap();
+                let (p3, c3, value_3) = h3.join().unwrap();
+                (party_1, party_2, party_3) = (p1, p2, p3);
+                (comm_1, comm_2, comm_3) = (c1, c2, c3);
+                assert_eq!(value_1 + value_2 + value_3, expected_value);
+            }
+            let h1 = run_get_db(party_1, comm_1);
+            let h2 = run_get_db(party_2, comm_2);
+            let h3 = run_get_db(party_3, comm_3);
+            let (p1, c1, db_share_1) = h1.join().unwrap();
+            let (p2, c2, db_share_2) = h2.join().unwrap();
+            let (p3, c3, db_share_3) = h3.join().unwrap();
+            (party_1, party_2, party_3) = (p1, p2, p3);
+            (comm_1, comm_2, comm_3) = (c1, c2, c3);
+            let db: Vec<_> = izip!(db_share_1.iter(), db_share_2.iter(), db_share_3.iter())
+                .map(|(&x, &y, &z)| x + y + z)
+                .collect();
+            for k in 0..db_size {
+                assert_eq!(db[k], Fp::from_u128(expected_db_contents[i][k]));
+            }
+        }
+    }
+
+    #[test]
+    fn test_oram_odd_exp() {
+        let db_size = 1 << 5;
+        let stash_size = (db_size as f64).sqrt().round() as usize;
+
+        let ((mut party_1, mut party_2, mut party_3), (mut comm_1, mut comm_2, mut comm_3)) =
+            setup(db_size);
+
+        let number_cycles = 8;
+        let instructions = [
+            mk_write(26, 64),
+            mk_read(4, 141),
+            mk_write(25, 701),
+            mk_write(29, 927),
+            mk_read(28, 132),
+            mk_write(30, 990),
+            mk_write(23, 167),
+            mk_write(31, 347),
+            mk_write(26, 1020),
+            mk_write(20, 893),
+            mk_read(26, 805),
+            mk_write(3, 949),
+            mk_read(10, 195),
+            mk_write(29, 767),
+            mk_read(28, 107),
+            mk_write(30, 426),
+            mk_write(22, 605),
+            mk_write(0, 171),
+            mk_write(4, 210),
+            mk_read(12, 737),
+            mk_write(19, 977),
+            mk_read(16, 143),
+            mk_write(29, 775),
+            mk_read(28, 34),
+            mk_write(27, 95),
+            mk_write(30, 130),
+            mk_read(8, 89),
+            mk_read(23, 132),
+            mk_read(21, 12),
+            mk_read(4, 675),
+            mk_write(28, 225),
+            mk_write(5, 978),
+            mk_write(2, 833),
+            mk_write(1, 456),
+            mk_write(17, 921),
+            mk_read(26, 293),
+            mk_write(5, 474),
+            mk_write(7, 981),
+            mk_read(19, 189),
+            mk_write(1, 248),
+            mk_read(27, 573),
+            mk_read(17, 142),
+            mk_read(29, 945),
+            mk_read(16, 902),
+            mk_write(16, 799),
+            mk_read(28, 864),
+            mk_write(6, 986),
+            mk_read(2, 201),
+        ];
+        let expected_values = [
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(64),
+            Fp::from_u128(0),
+            Fp::from_u128(1020),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(927),
+            Fp::from_u128(0),
+            Fp::from_u128(990),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(767),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(426),
+            Fp::from_u128(0),
+            Fp::from_u128(167),
+            Fp::from_u128(0),
+            Fp::from_u128(210),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(1020),
+            Fp::from_u128(978),
+            Fp::from_u128(0),
+            Fp::from_u128(977),
+            Fp::from_u128(456),
+            Fp::from_u128(95),
+            Fp::from_u128(921),
+            Fp::from_u128(775),
+            Fp::from_u128(0),
+            Fp::from_u128(0),
+            Fp::from_u128(225),
+            Fp::from_u128(0),
+            Fp::from_u128(833),
+        ];
+        let expected_db_contents = [
+            [
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 701, 64,
+                0, 0, 927, 990, 0,
+            ],
+            [
+                0, 0, 0, 949, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 893, 0, 0, 167, 0,
+                701, 1020, 0, 0, 927, 990, 347,
+            ],
+            [
+                171, 0, 0, 949, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 893, 0, 605, 167,
+                0, 701, 1020, 0, 0, 767, 426, 347,
+            ],
+            [
+                171, 0, 0, 949, 210, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 977, 893, 0, 605,
+                167, 0, 701, 1020, 0, 0, 775, 426, 347,
+            ],
+            [
+                171, 0, 0, 949, 210, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 977, 893, 0, 605,
+                167, 0, 701, 1020, 95, 0, 775, 130, 347,
+            ],
+            [
+                171, 456, 833, 949, 210, 978, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 921, 0, 977, 893, 0,
+                605, 167, 0, 701, 1020, 95, 225, 775, 130, 347,
+            ],
+            [
+                171, 248, 833, 949, 210, 474, 0, 981, 0, 0, 0, 0, 0, 0, 0, 0, 0, 921, 0, 977, 893,
+                0, 605, 167, 0, 701, 1020, 95, 225, 775, 130, 347,
+            ],
+            [
+                171, 248, 833, 949, 210, 474, 986, 981, 0, 0, 0, 0, 0, 0, 0, 0, 799, 921, 0, 977,
+                893, 0, 605, 167, 0, 701, 1020, 95, 225, 775, 130, 347,
+            ],
+        ];
+
+        for i in 0..number_cycles {
+            for j in 0..stash_size {
+                let inst = instructions[i * stash_size + j];
+                let expected_value = expected_values[i * stash_size + j];
+                let h1 = run_access(party_1, comm_1, inst);
+                let h2 = run_access(party_2, comm_2, INST_ZERO_SHARE);
+                let h3 = run_access(party_3, comm_3, INST_ZERO_SHARE);
+                let (p1, c1, value_1) = h1.join().unwrap();
+                let (p2, c2, value_2) = h2.join().unwrap();
+                let (p3, c3, value_3) = h3.join().unwrap();
+                (party_1, party_2, party_3) = (p1, p2, p3);
+                (comm_1, comm_2, comm_3) = (c1, c2, c3);
+                assert_eq!(value_1 + value_2 + value_3, expected_value);
+            }
+            let h1 = run_get_db(party_1, comm_1);
+            let h2 = run_get_db(party_2, comm_2);
+            let h3 = run_get_db(party_3, comm_3);
+            let (p1, c1, db_share_1) = h1.join().unwrap();
+            let (p2, c2, db_share_2) = h2.join().unwrap();
+            let (p3, c3, db_share_3) = h3.join().unwrap();
+            (party_1, party_2, party_3) = (p1, p2, p3);
+            (comm_1, comm_2, comm_3) = (c1, c2, c3);
+            let db: Vec<_> = izip!(db_share_1.iter(), db_share_2.iter(), db_share_3.iter())
+                .map(|(&x, &y, &z)| x + y + z)
+                .collect();
+            for k in 0..db_size {
+                assert_eq!(db[k], Fp::from_u128(expected_db_contents[i][k]));
+            }
+        }
+    }
+}

+ 342 - 0
oram/src/p_ot.rs

@@ -0,0 +1,342 @@
+//! Implementation of the 3-OT protocol.
+//!
+//! 3-OT is a variant of random oblivious transfer for three parties K, C, and R. K samples a PRF
+//! key that defines a pseudorandom vector.  Then C can repeatedly choose an index in this vector,
+//! and R receives the vector entry at that index without learning anything about the index or the
+//! other entries in the vector.
+
+use crate::common::Error;
+use communicator::{AbstractCommunicator, Fut, Serializable};
+use core::marker::PhantomData;
+use ff::Field;
+use rayon::prelude::*;
+use utils::field::FromPrf;
+use utils::permutation::Permutation;
+
+/// Party that holds the PRF key.
+pub struct POTKeyParty<F: FromPrf, Perm> {
+    /// log of the database size
+    domain_size: usize,
+    /// if init was run
+    is_initialized: bool,
+    /// PRF key of the Index Party
+    prf_key_i: Option<<F as FromPrf>::PrfKey>,
+    /// PRF key of the Receiver Party
+    prf_key_r: Option<<F as FromPrf>::PrfKey>,
+    /// Permutation
+    permutation: Option<Perm>,
+    _phantom: PhantomData<F>,
+}
+
+impl<F, Perm> POTKeyParty<F, Perm>
+where
+    F: Field + FromPrf,
+    F::PrfKey: Sync,
+    Perm: Permutation + Sync,
+{
+    /// Create a new instance.
+    pub fn new(domain_size: usize) -> Self {
+        Self {
+            domain_size,
+            is_initialized: false,
+            prf_key_i: None,
+            prf_key_r: None,
+            permutation: None,
+            _phantom: PhantomData,
+        }
+    }
+
+    /// Test if the party has been initialized.
+    pub fn is_initialized(&self) -> bool {
+        self.is_initialized
+    }
+
+    /// Reset the instance to be used again.
+    pub fn reset(&mut self) {
+        *self = Self::new(self.domain_size);
+    }
+
+    /// Steps of the initialization protocol without communication.
+    pub fn init(&mut self) -> ((F::PrfKey, Perm::Key), F::PrfKey) {
+        assert!(!self.is_initialized);
+        self.prf_key_i = Some(F::prf_key_gen());
+        self.prf_key_r = Some(F::prf_key_gen());
+        let permutation_key = Perm::sample(self.domain_size);
+        self.permutation = Some(Perm::from_key(permutation_key));
+        self.is_initialized = true;
+        (
+            (self.prf_key_i.unwrap(), permutation_key),
+            self.prf_key_r.unwrap(),
+        )
+    }
+
+    /// Run the initialization protocol.
+    pub fn run_init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error>
+    where
+        <F as FromPrf>::PrfKey: Serializable,
+        Perm::Key: Serializable,
+    {
+        let (msg_to_index_party, msg_to_receiver_party) = self.init();
+        comm.send_next(msg_to_index_party)?;
+        comm.send_previous(msg_to_receiver_party)?;
+        Ok(())
+    }
+
+    /// Expand the PRF key into a pseudorandom vector.
+    pub fn expand(&self) -> Vec<F> {
+        assert!(self.is_initialized);
+        (0..self.domain_size)
+            .into_par_iter()
+            .map(|x| {
+                let pi_x = self.permutation.as_ref().unwrap().permute(x);
+                F::prf(&self.prf_key_i.unwrap(), pi_x as u64)
+                    + F::prf(&self.prf_key_r.unwrap(), pi_x as u64)
+            })
+            .collect()
+    }
+}
+
+/// Party that chooses the index.
+pub struct POTIndexParty<F: FromPrf, Perm> {
+    /// log of the database size
+    domain_size: usize,
+    /// if init was run
+    is_initialized: bool,
+    /// PRF key of the Index Party
+    prf_key_i: Option<<F as FromPrf>::PrfKey>,
+    /// Permutation
+    permutation: Option<Perm>,
+    _phantom: PhantomData<F>,
+}
+
+impl<F: Field + FromPrf, Perm: Permutation> POTIndexParty<F, Perm> {
+    /// Create a new instance.
+    pub fn new(domain_size: usize) -> Self {
+        Self {
+            domain_size,
+            is_initialized: false,
+            prf_key_i: None,
+            permutation: None,
+            _phantom: PhantomData,
+        }
+    }
+
+    /// Test if the party has been initialized.
+    pub fn is_initialized(&self) -> bool {
+        self.is_initialized
+    }
+
+    /// Reset the instance to be used again.
+    pub fn reset(&mut self) {
+        *self = Self::new(self.domain_size);
+    }
+
+    /// Steps of the initialization protocol without communication.
+    pub fn init(&mut self, prf_key_i: F::PrfKey, permutation_key: Perm::Key) {
+        assert!(!self.is_initialized);
+        self.prf_key_i = Some(prf_key_i);
+        self.permutation = Some(Perm::from_key(permutation_key));
+        self.is_initialized = true;
+    }
+
+    /// Run the initialization protocol.
+    pub fn run_init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error>
+    where
+        <F as FromPrf>::PrfKey: Serializable,
+        Perm::Key: Serializable,
+    {
+        let msg_from_key_party: (F::PrfKey, Perm::Key) = comm.receive_previous()?.get()?;
+        self.init(msg_from_key_party.0, msg_from_key_party.1);
+        Ok(())
+    }
+
+    /// Steps of the access protocol without communication.
+    pub fn access(&self, index: usize) -> (usize, F) {
+        assert!(index < self.domain_size);
+        let pi_x = self.permutation.as_ref().unwrap().permute(index);
+        (pi_x, F::prf(&self.prf_key_i.unwrap(), pi_x as u64))
+    }
+
+    /// Run the access protocol.
+    pub fn run_access<C: AbstractCommunicator>(
+        &self,
+        comm: &mut C,
+        index: usize,
+    ) -> Result<(), Error>
+    where
+        F: Serializable,
+    {
+        let msg_to_receiver_party = self.access(index);
+        comm.send_next(msg_to_receiver_party)?;
+        Ok(())
+    }
+}
+
+/// Party that receives the output.
+pub struct POTReceiverParty<F: FromPrf> {
+    /// log of the database size
+    domain_size: usize,
+    /// if init was run
+    is_initialized: bool,
+    /// PRF key of the Receiver Party
+    prf_key_r: Option<<F as FromPrf>::PrfKey>,
+    _phantom: PhantomData<F>,
+}
+
+impl<F: Field + FromPrf> POTReceiverParty<F> {
+    /// Create a new instance.
+    pub fn new(domain_size: usize) -> Self {
+        Self {
+            domain_size,
+            is_initialized: false,
+            prf_key_r: None,
+            _phantom: PhantomData,
+        }
+    }
+
+    /// Test if the party has been initialized.
+    pub fn is_initialized(&self) -> bool {
+        self.is_initialized
+    }
+
+    /// Reset the instance to be used again.
+    pub fn reset(&mut self) {
+        *self = Self::new(self.domain_size);
+    }
+
+    /// Steps of the initialization protocol without communication.
+    pub fn init(&mut self, prf_key_r: F::PrfKey) {
+        assert!(!self.is_initialized);
+        self.prf_key_r = Some(prf_key_r);
+        self.is_initialized = true;
+    }
+
+    /// Run the initialization protocol.
+    pub fn run_init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error>
+    where
+        <F as FromPrf>::PrfKey: Serializable,
+    {
+        let msg_from_key_party: F::PrfKey = comm.receive_next()?.get()?;
+        self.init(msg_from_key_party);
+        Ok(())
+    }
+
+    /// Steps of the access protocol without communication.
+    pub fn access(&self, permuted_index: usize, output_share: F) -> F {
+        assert!(permuted_index < self.domain_size);
+        F::prf(&self.prf_key_r.unwrap(), permuted_index as u64) + output_share
+    }
+
+    /// Run the access protocol.
+    pub fn run_access<C: AbstractCommunicator>(&self, comm: &mut C) -> Result<F, Error>
+    where
+        F: Serializable,
+    {
+        let msg_from_index_party: (usize, F) = comm.receive_previous()?.get()?;
+        let output = self.access(msg_from_index_party.0, msg_from_index_party.1);
+        Ok(output)
+    }
+}
+
+/// Combination of three 3-OT instances, where each party takes each role once.
+pub struct JointPOTParties<F: FromPrf, Perm> {
+    key_party: POTKeyParty<F, Perm>,
+    index_party: POTIndexParty<F, Perm>,
+    receiver_party: POTReceiverParty<F>,
+}
+
+impl<F, Perm> JointPOTParties<F, Perm>
+where
+    F: Field + FromPrf,
+    F::PrfKey: Sync,
+    Perm: Permutation + Sync,
+{
+    /// Create a new instance.
+    pub fn new(domain_size: usize) -> Self {
+        Self {
+            key_party: POTKeyParty::new(domain_size),
+            index_party: POTIndexParty::new(domain_size),
+            receiver_party: POTReceiverParty::new(domain_size),
+        }
+    }
+
+    /// Reset this instance.
+    pub fn reset(&mut self) {
+        *self = Self::new(self.key_party.domain_size);
+    }
+
+    /// Run the inititialization for all three 3-OT instances.
+    pub fn init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error>
+    where
+        <F as FromPrf>::PrfKey: Serializable,
+        Perm::Key: Serializable,
+    {
+        self.key_party.run_init(comm)?;
+        self.index_party.run_init(comm)?;
+        self.receiver_party.run_init(comm)
+    }
+
+    /// Run the access protocol for the 3-OT instances where the this party chooses the index or
+    /// receives the output.
+    pub fn access<C: AbstractCommunicator>(&self, comm: &mut C, my_index: usize) -> Result<F, Error>
+    where
+        F: Serializable,
+    {
+        self.index_party.run_access(comm, my_index)?;
+        self.receiver_party.run_access(comm)
+    }
+
+    /// Expands the PRF key for the instances where this party holds the key.
+    pub fn expand(&self) -> Vec<F> {
+        self.key_party.expand()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use utils::field::Fp;
+    use utils::permutation::FisherYatesPermutation;
+
+    fn test_pot<F, Perm>(log_domain_size: u32)
+    where
+        F: Field + FromPrf,
+        F::PrfKey: Sync,
+        Perm: Permutation + Sync,
+    {
+        let domain_size = 1 << log_domain_size;
+
+        // creation
+        let mut key_party = POTKeyParty::<F, Perm>::new(domain_size);
+        let mut index_party = POTIndexParty::<F, Perm>::new(domain_size);
+        let mut receiver_party = POTReceiverParty::<F>::new(domain_size);
+        assert!(!key_party.is_initialized());
+        assert!(!index_party.is_initialized());
+        assert!(!receiver_party.is_initialized());
+
+        // init
+        let (msg_to_index_party, msg_to_receiver_party) = key_party.init();
+        index_party.init(msg_to_index_party.0, msg_to_index_party.1);
+        receiver_party.init(msg_to_receiver_party);
+        assert!(key_party.is_initialized());
+        assert!(index_party.is_initialized());
+        assert!(receiver_party.is_initialized());
+
+        // expand to the key party's output
+        let output_k = key_party.expand();
+        assert_eq!(output_k.len(), domain_size);
+
+        // access each index and verify consistency with key party's output
+        for i in 0..domain_size {
+            let msg_to_receiver_party = index_party.access(i);
+            let output = receiver_party.access(msg_to_receiver_party.0, msg_to_receiver_party.1);
+            assert_eq!(output, output_k[i]);
+        }
+    }
+
+    #[test]
+    fn test_all_pot() {
+        let log_domain_size = 10;
+        test_pot::<Fp, FisherYatesPermutation>(log_domain_size);
+    }
+}

+ 281 - 0
oram/src/select.rs

@@ -0,0 +1,281 @@
+//! Implementation of an oblivious selection protocol.
+
+use crate::common::Error;
+use communicator::{AbstractCommunicator, Fut, Serializable};
+use ff::Field;
+use itertools::izip;
+use rand::{thread_rng, Rng, SeedableRng};
+use rand_chacha::ChaChaRng;
+use std::collections::VecDeque;
+
+/// Select between two shared values `<a>`, `<b>` based on a shared condition bit `<c>`:
+/// Output `<w> <- if <c> then <a> else <b>`.
+pub trait Select<F> {
+    /// Initialize the protocol instance.
+    fn init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error>;
+
+    /// Run the preprocessing for `num` invocations.
+    fn preprocess<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        num: usize,
+    ) -> Result<(), Error>;
+
+    /// Run the online protocol for one select operation.
+    fn select<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        c_share: F,
+        a_share: F,
+        b_share: F,
+    ) -> Result<F, Error>;
+}
+
+const PARTY_1: usize = 0;
+const PARTY_2: usize = 1;
+const PARTY_3: usize = 2;
+
+fn other_compute_party(my_id: usize) -> usize {
+    match my_id {
+        PARTY_2 => PARTY_3,
+        PARTY_3 => PARTY_2,
+        _ => panic!("invalid party id"),
+    }
+}
+
+/// Implementation of the select protocol.
+#[derive(Default)]
+pub struct SelectProtocol<F> {
+    shared_prg_1: Option<ChaChaRng>,
+    shared_prg_2: Option<ChaChaRng>,
+    shared_prg_3: Option<ChaChaRng>,
+    is_initialized: bool,
+    num_preprocessed_invocations: usize,
+    preprocessed_mt_x: VecDeque<F>,
+    preprocessed_mt_y: VecDeque<F>,
+    preprocessed_mt_z: VecDeque<F>,
+    preprocessed_c_1_2: VecDeque<F>,
+    preprocessed_amb_1_2: VecDeque<F>,
+}
+
+impl<F> Select<F> for SelectProtocol<F>
+where
+    F: Field + Serializable,
+{
+    fn init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error> {
+        if comm.get_my_id() == PARTY_1 {
+            self.shared_prg_2 = Some(ChaChaRng::from_seed(thread_rng().gen()));
+            comm.send(PARTY_2, self.shared_prg_2.as_ref().unwrap().get_seed())?;
+            self.shared_prg_3 = Some(ChaChaRng::from_seed(thread_rng().gen()));
+            comm.send(PARTY_3, self.shared_prg_3.as_ref().unwrap().get_seed())?;
+        } else {
+            let fut_seed = comm.receive(PARTY_1)?;
+            self.shared_prg_1 = Some(ChaChaRng::from_seed(fut_seed.get()?));
+        }
+        self.is_initialized = true;
+        Ok(())
+    }
+
+    fn preprocess<C: AbstractCommunicator>(&mut self, comm: &mut C, n: usize) -> Result<(), Error> {
+        assert!(self.is_initialized);
+
+        let my_id = comm.get_my_id();
+
+        if my_id == PARTY_1 {
+            let x2s: Vec<F> = (0..n)
+                .map(|_| F::random(self.shared_prg_2.as_mut().unwrap()))
+                .collect();
+            let y2s: Vec<F> = (0..n)
+                .map(|_| F::random(self.shared_prg_2.as_mut().unwrap()))
+                .collect();
+            let z2s: Vec<F> = (0..n)
+                .map(|_| F::random(self.shared_prg_2.as_mut().unwrap()))
+                .collect();
+            let x3s: Vec<F> = (0..n)
+                .map(|_| F::random(self.shared_prg_3.as_mut().unwrap()))
+                .collect();
+            let y3s: Vec<F> = (0..n)
+                .map(|_| F::random(self.shared_prg_3.as_mut().unwrap()))
+                .collect();
+            let z3s: Vec<F> = (0..n)
+                .map(|_| F::random(self.shared_prg_3.as_mut().unwrap()))
+                .collect();
+
+            let z1s = izip!(x2s, y2s, z2s, x3s, y3s, z3s)
+                .map(|(x_2, y_2, z_2, x_3, y_3, z_3)| (x_2 + x_3) * (y_2 + y_3) - z_2 - z_3);
+            self.preprocessed_mt_z.extend(z1s);
+
+            self.preprocessed_c_1_2
+                .extend((0..n).map(|_| F::random(self.shared_prg_2.as_mut().unwrap())));
+            self.preprocessed_amb_1_2
+                .extend((0..n).map(|_| F::random(self.shared_prg_2.as_mut().unwrap())));
+        } else {
+            self.preprocessed_mt_x
+                .extend((0..n).map(|_| F::random(self.shared_prg_1.as_mut().unwrap())));
+            self.preprocessed_mt_y
+                .extend((0..n).map(|_| F::random(self.shared_prg_1.as_mut().unwrap())));
+            self.preprocessed_mt_z
+                .extend((0..n).map(|_| F::random(self.shared_prg_1.as_mut().unwrap())));
+            if my_id == PARTY_2 {
+                self.preprocessed_c_1_2
+                    .extend((0..n).map(|_| F::random(self.shared_prg_1.as_mut().unwrap())));
+                self.preprocessed_amb_1_2
+                    .extend((0..n).map(|_| F::random(self.shared_prg_1.as_mut().unwrap())));
+            }
+        }
+
+        self.num_preprocessed_invocations += n;
+        Ok(())
+    }
+
+    fn select<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        c_share: F,
+        a_share: F,
+        b_share: F,
+    ) -> Result<F, Error> {
+        let my_id = comm.get_my_id();
+
+        // if further preprocessing is needed, do it now
+        if self.num_preprocessed_invocations == 0 {
+            self.preprocess(comm, 1)?;
+        }
+        self.num_preprocessed_invocations -= 1;
+
+        if my_id == PARTY_1 {
+            let c_1_2 = self.preprocessed_c_1_2.pop_front().unwrap();
+            let amb_1_2 = self.preprocessed_amb_1_2.pop_front().unwrap();
+            comm.send(PARTY_3, (c_share - c_1_2, (a_share - b_share) - amb_1_2))?;
+            let z = self.preprocessed_mt_z.pop_front().unwrap();
+            Ok(b_share + z)
+        } else {
+            let (c_1_i, amb_1_i) = if my_id == PARTY_2 {
+                (
+                    self.preprocessed_c_1_2.pop_front().unwrap(),
+                    self.preprocessed_amb_1_2.pop_front().unwrap(),
+                )
+            } else {
+                let fut_1 = comm.receive::<(F, F)>(PARTY_1)?;
+                fut_1.get()?
+            };
+            let fut_de = comm.receive::<(F, F)>(other_compute_party(my_id))?;
+            let x_i = self.preprocessed_mt_x.pop_front().unwrap();
+            let y_i = self.preprocessed_mt_y.pop_front().unwrap();
+            let mut z_i = self.preprocessed_mt_z.pop_front().unwrap();
+            let d_i = (c_share + c_1_i) - x_i;
+            let e_i = (a_share - b_share + amb_1_i) - y_i;
+            comm.send(other_compute_party(my_id), (d_i, e_i))?;
+            let (d_j, e_j) = fut_de.get()?;
+            let (d, e) = (d_i + d_j, e_i + e_j);
+
+            z_i += e * (c_share + c_1_i) + d * (a_share - b_share + amb_1_i);
+            if my_id == PARTY_2 {
+                z_i -= d * e;
+            }
+
+            Ok(b_share + z_i)
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use communicator::unix::make_unix_communicators;
+    use std::thread;
+    use utils::field::Fp;
+
+    fn run_init<Proto: Select<F> + Send + 'static, F>(
+        mut comm: impl AbstractCommunicator + Send + 'static,
+        mut proto: Proto,
+    ) -> thread::JoinHandle<(impl AbstractCommunicator, Proto)>
+    where
+        F: Field + Serializable,
+    {
+        thread::spawn(move || {
+            proto.init(&mut comm).unwrap();
+            (comm, proto)
+        })
+    }
+
+    fn run_select<Proto: Select<F> + Send + 'static, F>(
+        mut comm: impl AbstractCommunicator + Send + 'static,
+        mut proto: Proto,
+        c_share: F,
+        a_share: F,
+        b_share: F,
+    ) -> thread::JoinHandle<(impl AbstractCommunicator, Proto, F)>
+    where
+        F: Field + Serializable,
+    {
+        thread::spawn(move || {
+            let result = proto.select(&mut comm, c_share, a_share, b_share);
+            (comm, proto, result.unwrap())
+        })
+    }
+
+    #[test]
+    fn test_select() {
+        let proto_1 = SelectProtocol::<Fp>::default();
+        let proto_2 = SelectProtocol::<Fp>::default();
+        let proto_3 = SelectProtocol::<Fp>::default();
+
+        let (comm_3, comm_2, comm_1) = {
+            let mut comms = make_unix_communicators(3);
+            (
+                comms.pop().unwrap(),
+                comms.pop().unwrap(),
+                comms.pop().unwrap(),
+            )
+        };
+
+        let h1 = run_init(comm_1, proto_1);
+        let h2 = run_init(comm_2, proto_2);
+        let h3 = run_init(comm_3, proto_3);
+        let (comm_1, proto_1) = h1.join().unwrap();
+        let (comm_2, proto_2) = h2.join().unwrap();
+        let (comm_3, proto_3) = h3.join().unwrap();
+
+        let mut rng = thread_rng();
+
+        let (a_1, a_2, a_3) = (
+            Fp::random(&mut rng),
+            Fp::random(&mut rng),
+            Fp::random(&mut rng),
+        );
+        let a = a_1 + a_2 + a_3;
+        let (b_1, b_2, b_3) = (
+            Fp::random(&mut rng),
+            Fp::random(&mut rng),
+            Fp::random(&mut rng),
+        );
+        let b = b_1 + b_2 + b_3;
+        let (c_2, c_3) = (Fp::random(&mut rng), Fp::random(&mut rng));
+
+        let c0_1 = -c_2 - c_3;
+        let c1_1 = Fp::ONE - c_2 - c_3;
+
+        // check for <c> = <0>
+        let h1 = run_select(comm_1, proto_1, c0_1, a_1, b_1);
+        let h2 = run_select(comm_2, proto_2, c_2, a_2, b_2);
+        let h3 = run_select(comm_3, proto_3, c_3, a_3, b_3);
+        let (comm_1, proto_1, x_1) = h1.join().unwrap();
+        let (comm_2, proto_2, x_2) = h2.join().unwrap();
+        let (comm_3, proto_3, x_3) = h3.join().unwrap();
+
+        assert_eq!(c0_1 + c_2 + c_3, Fp::ZERO);
+        assert_eq!(x_1 + x_2 + x_3, b);
+
+        // check for <c> = <1>
+        let h1 = run_select(comm_1, proto_1, c1_1, a_1, b_1);
+        let h2 = run_select(comm_2, proto_2, c_2, a_2, b_2);
+        let h3 = run_select(comm_3, proto_3, c_3, a_3, b_3);
+        let (_, _, y_1) = h1.join().unwrap();
+        let (_, _, y_2) = h2.join().unwrap();
+        let (_, _, y_3) = h3.join().unwrap();
+
+        assert_eq!(c1_1 + c_2 + c_3, Fp::ONE);
+        assert_eq!(y_1 + y_2 + y_3, a);
+    }
+}

+ 1162 - 0
oram/src/stash.rs

@@ -0,0 +1,1162 @@
+//! Stash protocol implementation.
+
+use crate::common::{Error, InstructionShare};
+use crate::doprf::{
+    DOPrfParty1, DOPrfParty2, DOPrfParty3, LegendrePrf, MaskedDOPrfParty1, MaskedDOPrfParty2,
+    MaskedDOPrfParty3,
+};
+use crate::mask_index::{MaskIndex, MaskIndexProtocol};
+use crate::select::{Select, SelectProtocol};
+use communicator::{AbstractCommunicator, Fut, Serializable};
+use dpf::spdpf::SinglePointDpf;
+use ff::PrimeField;
+use rand::thread_rng;
+use rayon::prelude::*;
+use std::marker::PhantomData;
+use std::time::{Duration, Instant};
+use utils::field::LegendreSymbol;
+
+/// Result of a stash read.
+///
+/// All values are shared.
+#[derive(Clone, Copy, Debug, Default)]
+pub struct StashStateShare<F: PrimeField> {
+    /// Share of 1 if the searched address was present in the stash, and share of 0 otherwise.
+    pub flag: F,
+    /// Possible location of the found entry in the stash.
+    pub location: F,
+    /// Possible value of the found entry.
+    pub value: F,
+}
+
+/// State of the stash protocol.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum State {
+    New,
+    AwaitingRead,
+    AwaitingWrite,
+    AccessesExhausted,
+}
+
+const PARTY_1: usize = 0;
+const PARTY_2: usize = 1;
+const PARTY_3: usize = 2;
+
+/// Definition of the stash interface.
+pub trait Stash<F: PrimeField> {
+    /// Return ID of the current party.
+    fn get_party_id(&self) -> usize;
+
+    /// Return capacity of the stash.
+    fn get_stash_size(&self) -> usize;
+
+    /// Return current access counter.
+    fn get_access_counter(&self) -> usize;
+
+    /// Reset the data structure to be used again.
+    fn reset(&mut self);
+
+    /// Initialize the stash.
+    fn init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error>;
+
+    /// Perform a read from the stash.
+    fn read<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        instruction: InstructionShare<F>,
+    ) -> Result<StashStateShare<F>, Error>;
+
+    /// Perform a write into the stash.
+    fn write<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        instruction: InstructionShare<F>,
+        stash_state: StashStateShare<F>,
+        db_address_share: F,
+        db_value_share: F,
+    ) -> Result<(), Error>;
+
+    /// Get an additive share of the stash.
+    fn get_stash_share(&self) -> (&[F], &[F], &[F]);
+}
+
+fn compute_stash_prf_output_bitsize(stash_size: usize) -> usize {
+    (usize::BITS - stash_size.leading_zeros()) as usize + 40
+}
+
+/// Protocol steps of the stash initialization, read, and write.
+#[allow(missing_docs)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, strum_macros::EnumIter, strum_macros::Display)]
+pub enum ProtocolStep {
+    Init = 0,
+    ReadMaskedAddressTag,
+    ReadDpfKeyGen,
+    ReadLookupFlagLocation,
+    ReadComputeLocation,
+    ReadReshareFlag,
+    ReadConvertToReplicated,
+    ReadComputeMaskedIndex,
+    ReadDpfKeyDistribution,
+    ReadDpfEvaluations,
+    WriteAddressTag,
+    WriteStoreTriple,
+    WriteSelectPreviousValue,
+    WriteSelectValue,
+    WriteComputeMaskedIndex,
+    WriteDpfKeyDistribution,
+    WriteDpfEvaluations,
+}
+
+/// Collection of accumulated runtimes for the protocol steps.
+#[derive(Debug, Default, Clone, Copy)]
+pub struct Runtimes {
+    durations: [Duration; 17],
+}
+
+impl Runtimes {
+    /// Add another duration to the accumulated runtimes for a protocol step.
+    #[inline(always)]
+    pub fn record(&mut self, id: ProtocolStep, duration: Duration) {
+        self.durations[id as usize] += duration;
+    }
+
+    /// Get the accumulated durations for a protocol step.
+    pub fn get(&self, id: ProtocolStep) -> Duration {
+        self.durations[id as usize]
+    }
+}
+
+/// Implementation of the stash protocol.
+pub struct StashProtocol<F, SPDPF>
+where
+    F: PrimeField + LegendreSymbol + Serializable,
+    SPDPF: SinglePointDpf<Value = F>,
+{
+    party_id: usize,
+    stash_size: usize,
+    access_counter: usize,
+    state: State,
+    stash_addresses_share: Vec<F>,
+    stash_values_share: Vec<F>,
+    stash_old_values_share: Vec<F>,
+    address_tag_list: Vec<u64>,
+    select_party: Option<SelectProtocol<F>>,
+    doprf_party_1: Option<DOPrfParty1<F>>,
+    doprf_party_2: Option<DOPrfParty2<F>>,
+    doprf_party_3: Option<DOPrfParty3<F>>,
+    masked_doprf_party_1: Option<MaskedDOPrfParty1<F>>,
+    masked_doprf_party_2: Option<MaskedDOPrfParty2<F>>,
+    masked_doprf_party_3: Option<MaskedDOPrfParty3<F>>,
+    _phantom: PhantomData<SPDPF>,
+}
+
+impl<F, SPDPF> StashProtocol<F, SPDPF>
+where
+    F: PrimeField + LegendreSymbol + Serializable,
+    SPDPF: SinglePointDpf<Value = F>,
+    SPDPF::Key: Serializable + Sync,
+{
+    /// Create new instance of the stash protocol for a party `{0, 1, 2}` and given size.
+    pub fn new(party_id: usize, stash_size: usize) -> Self {
+        assert!(party_id < 3);
+        assert!(stash_size > 0);
+        assert!(compute_stash_prf_output_bitsize(stash_size) <= 64);
+
+        Self {
+            party_id,
+            stash_size,
+            access_counter: 0,
+            state: State::New,
+            stash_addresses_share: Vec::with_capacity(stash_size),
+            stash_values_share: Vec::with_capacity(stash_size),
+            stash_old_values_share: Vec::with_capacity(stash_size),
+            address_tag_list: if party_id == PARTY_1 {
+                Default::default()
+            } else {
+                Vec::with_capacity(stash_size)
+            },
+            select_party: None,
+            doprf_party_1: None,
+            doprf_party_2: None,
+            doprf_party_3: None,
+            masked_doprf_party_1: None,
+            masked_doprf_party_2: None,
+            masked_doprf_party_3: None,
+            _phantom: PhantomData,
+        }
+    }
+
+    fn init_with_runtimes<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        runtimes: Option<Runtimes>,
+    ) -> Result<Option<Runtimes>, Error> {
+        assert_eq!(self.state, State::New);
+
+        let t_start = Instant::now();
+
+        let prf_output_bitsize = compute_stash_prf_output_bitsize(self.stash_size);
+        let legendre_prf_key = LegendrePrf::<F>::key_gen(prf_output_bitsize);
+
+        // run DOPRF initilization
+        match self.party_id {
+            PARTY_1 => {
+                let mut doprf_p1 = DOPrfParty1::from_legendre_prf_key(legendre_prf_key.clone());
+                let mut mdoprf_p1 = MaskedDOPrfParty1::from_legendre_prf_key(legendre_prf_key);
+                doprf_p1.init(comm)?;
+                mdoprf_p1.init(comm)?;
+                doprf_p1.preprocess(comm, self.stash_size)?;
+                mdoprf_p1.preprocess(comm, self.stash_size)?;
+                self.doprf_party_1 = Some(doprf_p1);
+                self.masked_doprf_party_1 = Some(mdoprf_p1);
+            }
+            PARTY_2 => {
+                let mut doprf_p2 = DOPrfParty2::new(prf_output_bitsize);
+                let mut mdoprf_p2 = MaskedDOPrfParty2::new(prf_output_bitsize);
+                doprf_p2.init(comm)?;
+                mdoprf_p2.init(comm)?;
+                doprf_p2.preprocess(comm, self.stash_size)?;
+                mdoprf_p2.preprocess(comm, self.stash_size)?;
+                self.doprf_party_2 = Some(doprf_p2);
+                self.masked_doprf_party_2 = Some(mdoprf_p2);
+            }
+            PARTY_3 => {
+                let mut doprf_p3 = DOPrfParty3::new(prf_output_bitsize);
+                let mut mdoprf_p3 = MaskedDOPrfParty3::new(prf_output_bitsize);
+                doprf_p3.init(comm)?;
+                mdoprf_p3.init(comm)?;
+                doprf_p3.preprocess(comm, self.stash_size)?;
+                mdoprf_p3.preprocess(comm, self.stash_size)?;
+                self.doprf_party_3 = Some(doprf_p3);
+                self.masked_doprf_party_3 = Some(mdoprf_p3);
+            }
+            _ => panic!("invalid party id"),
+        }
+
+        // run Select initialiation and preprocessing
+        {
+            let mut select_party = SelectProtocol::default();
+            select_party.init(comm)?;
+            select_party.preprocess(comm, 3 * self.stash_size)?;
+            self.select_party = Some(select_party);
+        }
+
+        let t_end = Instant::now();
+        let runtimes = runtimes.map(|mut r| {
+            r.record(ProtocolStep::Init, t_end - t_start);
+            r
+        });
+
+        self.state = State::AwaitingRead;
+        Ok(runtimes)
+    }
+
+    /// Perform a stash read and collect runtime data.
+    pub fn read_with_runtimes<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        instruction: InstructionShare<F>,
+        runtimes: Option<Runtimes>,
+    ) -> Result<(StashStateShare<F>, Option<Runtimes>), Error> {
+        assert_eq!(self.state, State::AwaitingRead);
+        assert!(self.access_counter < self.stash_size);
+
+        // 0. If the stash is empty, we are done
+        if self.access_counter == 0 {
+            self.state = State::AwaitingWrite;
+            return Ok((
+                StashStateShare {
+                    flag: F::ZERO,
+                    location: F::ZERO,
+                    value: F::ZERO,
+                },
+                runtimes,
+            ));
+        }
+
+        let t_start = Instant::now();
+
+        let (
+            flag_share,
+            location_share,
+            t_after_masked_address_tag,
+            t_after_dpf_keygen,
+            t_after_compute_flag_loc,
+        ) = match self.party_id {
+            PARTY_1 => {
+                // 1. Compute tag y := PRF(k, <I.adr>) such that P1 obtains y + r and P2, P3 obtain the mask r.
+                let masked_address_tag: u64 = {
+                    let mdoprf_p1 = self.masked_doprf_party_1.as_mut().unwrap();
+                    mdoprf_p1.eval_to_uint(comm, 1, &[instruction.address])?[0]
+                };
+
+                let t_after_masked_address_tag = Instant::now();
+
+                // 2. Create and send DPF keys for the function f(x) = if x = y { 1 } else { 0 }
+                {
+                    let domain_size = 1 << compute_stash_prf_output_bitsize(self.stash_size);
+                    let (dpf_key_2, dpf_key_3) =
+                        SPDPF::generate_keys(domain_size, masked_address_tag, F::ONE);
+                    comm.send(PARTY_2, dpf_key_2)?;
+                    comm.send(PARTY_3, dpf_key_3)?;
+                }
+
+                let t_after_dpf_keygen = Instant::now();
+
+                // 3. The other parties compute shares of <flag>, <loc>, i.e., if the address is present in
+                //    the stash and if so, where it is. We just take 0s as our shares.
+                (
+                    F::ZERO,
+                    F::ZERO,
+                    t_after_masked_address_tag,
+                    t_after_dpf_keygen,
+                    t_after_dpf_keygen,
+                )
+            }
+            PARTY_2 | PARTY_3 => {
+                // 1. Compute tag y := PRF(k, <I.adr>) such that P1 obtains y + r and P2, P3 obtain the mask r.
+                let address_tag_mask: u64 = match self.party_id {
+                    PARTY_2 => {
+                        let mdoprf_p2 = self.masked_doprf_party_2.as_mut().unwrap();
+                        mdoprf_p2.eval_to_uint(comm, 1, &[instruction.address])?[0]
+                    }
+                    PARTY_3 => {
+                        let mdoprf_p3 = self.masked_doprf_party_3.as_mut().unwrap();
+                        mdoprf_p3.eval_to_uint(comm, 1, &[instruction.address])?[0]
+                    }
+                    _ => panic!("invalid party id"),
+                };
+
+                let t_after_masked_address_tag = Instant::now();
+
+                // 2. Receive DPF key for the function f(x) = if x = y { 1 } else { 0 }
+                let dpf_key_i: SPDPF::Key = {
+                    let fut = comm.receive(PARTY_1)?;
+                    fut.get()?
+                };
+
+                let t_after_dpf_keygen = Instant::now();
+
+                // 3. Compute shares of <flag>, <loc>, i.e., if the address is present in the stash and if
+                //    so, where it is
+                {
+                    let (flag_share, location_share) = self
+                        .address_tag_list
+                        .par_iter()
+                        .enumerate()
+                        .map(|(j, tag_j)| {
+                            let dpf_value_j =
+                                SPDPF::evaluate_at(&dpf_key_i, tag_j ^ address_tag_mask);
+                            (dpf_value_j, F::from_u128(j as u128) * dpf_value_j)
+                        })
+                        .reduce(|| (F::ZERO, F::ZERO), |(a, b), (c, d)| (a + c, b + d));
+                    let t_after_compute_flag_loc = Instant::now();
+                    (
+                        flag_share,
+                        location_share,
+                        t_after_masked_address_tag,
+                        t_after_dpf_keygen,
+                        t_after_compute_flag_loc,
+                    )
+                }
+            }
+            _ => panic!("invalid party id"),
+        };
+
+        // 4. Compute <loc> = if <flag> { <loc> } else { access_counter - 1 }
+        let location_share = {
+            let access_counter_share = if self.party_id == PARTY_1 {
+                F::from_u128(self.access_counter as u128)
+            } else {
+                F::ZERO
+            };
+            self.select_party.as_mut().unwrap().select(
+                comm,
+                flag_share,
+                location_share,
+                access_counter_share,
+            )?
+        };
+
+        let t_after_location_share = Instant::now();
+
+        // 5. Reshare <flag> among all three parties
+        let flag_share = match self.party_id {
+            PARTY_1 => {
+                let flag_share = F::random(thread_rng());
+                comm.send(PARTY_2, flag_share)?;
+                flag_share
+            }
+            PARTY_2 => {
+                let fut_1_2 = comm.receive::<F>(PARTY_1)?;
+                flag_share - fut_1_2.get()?
+            }
+            _ => flag_share,
+        };
+
+        let t_after_flag_share = Instant::now();
+
+        // 6. Read the value <val> from the stash (if <flag>) or read a zero value
+        let (
+            value_share,
+            t_after_convert_to_replicated,
+            t_after_masked_index,
+            t_after_dpf_key_distr,
+        ) = {
+            // a) convert the stash into replicated secret sharing
+            let fut_prev = comm.receive_previous::<Vec<F>>()?;
+            comm.send_slice_next(self.stash_values_share.as_ref())?;
+            let stash_values_share_prev = fut_prev.get()?;
+
+            let t_after_convert_to_replicated = Instant::now();
+
+            // b) mask and reconstruct the stash index <loc>
+            let index_bits = (self.access_counter as f64).log2().ceil() as u32;
+            assert!(index_bits <= 16);
+            let bit_mask = ((1 << index_bits) - 1) as u16;
+            let (masked_loc, r_prev, r_next) =
+                MaskIndexProtocol::mask_index(comm, index_bits, location_share)?;
+
+            let t_after_masked_index = Instant::now();
+
+            // c) use DPFs to read the stash value
+            let fut_prev = comm.receive_previous::<SPDPF::Key>()?;
+            let fut_next = comm.receive_next::<SPDPF::Key>()?;
+            {
+                let (dpf_key_prev, dpf_key_next) =
+                    SPDPF::generate_keys(1 << index_bits, masked_loc as u64, F::ONE);
+                comm.send_previous(dpf_key_prev)?;
+                comm.send_next(dpf_key_next)?;
+            }
+            let dpf_key_prev = fut_prev.get()?;
+            let dpf_key_next = fut_next.get()?;
+            let t_after_dpf_key_distr = Instant::now();
+            let value_share: F = (0..self.access_counter)
+                .into_par_iter()
+                .map(|j| {
+                    let index_prev = ((j as u16 + r_prev) & bit_mask) as u64;
+                    let index_next = ((j as u16 + r_next) & bit_mask) as u64;
+                    SPDPF::evaluate_at(&dpf_key_prev, index_prev) * self.stash_values_share[j]
+                        + SPDPF::evaluate_at(&dpf_key_next, index_next) * stash_values_share_prev[j]
+                })
+                .sum();
+            (
+                value_share,
+                t_after_convert_to_replicated,
+                t_after_masked_index,
+                t_after_dpf_key_distr,
+            )
+        };
+
+        let t_after_dpf_eval = Instant::now();
+
+        let runtimes = runtimes.map(|mut r| {
+            r.record(
+                ProtocolStep::ReadMaskedAddressTag,
+                t_after_masked_address_tag - t_start,
+            );
+            r.record(
+                ProtocolStep::ReadDpfKeyGen,
+                t_after_dpf_keygen - t_after_masked_address_tag,
+            );
+            r.record(
+                ProtocolStep::ReadLookupFlagLocation,
+                t_after_compute_flag_loc - t_after_dpf_keygen,
+            );
+            r.record(
+                ProtocolStep::ReadComputeLocation,
+                t_after_location_share - t_after_compute_flag_loc,
+            );
+            r.record(
+                ProtocolStep::ReadReshareFlag,
+                t_after_flag_share - t_after_location_share,
+            );
+            r.record(
+                ProtocolStep::ReadConvertToReplicated,
+                t_after_convert_to_replicated - t_after_flag_share,
+            );
+            r.record(
+                ProtocolStep::ReadComputeMaskedIndex,
+                t_after_masked_index - t_after_convert_to_replicated,
+            );
+            r.record(
+                ProtocolStep::ReadDpfKeyDistribution,
+                t_after_dpf_key_distr - t_after_masked_index,
+            );
+            r.record(
+                ProtocolStep::ReadDpfEvaluations,
+                t_after_dpf_eval - t_after_dpf_key_distr,
+            );
+            r
+        });
+
+        self.state = State::AwaitingWrite;
+        Ok((
+            StashStateShare {
+                flag: flag_share,
+                location: location_share,
+                value: value_share,
+            },
+            runtimes,
+        ))
+    }
+
+    /// Perform a stash write and collect runtime data.
+    pub fn write_with_runtimes<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        instruction: InstructionShare<F>,
+        stash_state: StashStateShare<F>,
+        db_address_share: F,
+        db_value_share: F,
+        runtimes: Option<Runtimes>,
+    ) -> Result<Option<Runtimes>, Error> {
+        assert_eq!(self.state, State::AwaitingWrite);
+        assert!(self.access_counter < self.stash_size);
+
+        let t_start = Instant::now();
+
+        // 1. Compute tag y := PRF(k, <db_adr>) such that P2, P3 obtain y.
+        match self.party_id {
+            PARTY_1 => {
+                let doprf_p1 = self.doprf_party_1.as_mut().unwrap();
+                doprf_p1.eval(comm, 1, &[db_address_share])?;
+            }
+            PARTY_2 => {
+                let address_tag: u64 = {
+                    let doprf_p2 = self.doprf_party_2.as_mut().unwrap();
+                    let fut_3_2 = comm.receive(PARTY_3)?;
+                    doprf_p2.eval(comm, 1, &[db_address_share])?;
+                    fut_3_2.get()?
+                };
+                self.address_tag_list.push(address_tag);
+            }
+            PARTY_3 => {
+                let address_tag: u64 = {
+                    let doprf_p3 = self.doprf_party_3.as_mut().unwrap();
+                    let tag = doprf_p3.eval_to_uint(comm, 1, &[db_address_share])?[0];
+                    comm.send(PARTY_2, tag)?;
+                    tag
+                };
+                self.address_tag_list.push(address_tag);
+            }
+            _ => panic!("invalid party id"),
+        }
+
+        let t_after_address_tag = Instant::now();
+
+        // 2. Insert new triple (<db_adr>, <db_val>, <db_val> into stash.
+        self.stash_addresses_share.push(db_address_share);
+        self.stash_values_share.push(db_value_share);
+        self.stash_old_values_share.push(db_value_share);
+
+        let t_after_store_triple = Instant::now();
+
+        // 3. Update stash
+        let previous_value_share = self.select_party.as_mut().unwrap().select(
+            comm,
+            stash_state.flag,
+            stash_state.value,
+            db_value_share,
+        )?;
+        let t_after_select_previous_value = Instant::now();
+        let value_share = self.select_party.as_mut().unwrap().select(
+            comm,
+            instruction.operation,
+            instruction.value - previous_value_share,
+            F::ZERO,
+        )?;
+        let t_after_select_value = Instant::now();
+        let (t_after_masked_index, t_after_dpf_key_distr) = {
+            // a) mask and reconstruct the stash index <loc>
+            let index_bits = {
+                let bits = usize::BITS - self.access_counter.leading_zeros();
+                if bits > 0 {
+                    bits
+                } else {
+                    1
+                }
+            };
+            assert!(index_bits <= 16);
+            let bit_mask = ((1 << index_bits) - 1) as u16;
+            let (masked_loc, r_prev, r_next) =
+                MaskIndexProtocol::mask_index(comm, index_bits, stash_state.location)?;
+
+            let t_after_masked_index = Instant::now();
+
+            // b) use DPFs to read the stash value
+            let fut_prev = comm.receive_previous::<SPDPF::Key>()?;
+            let fut_next = comm.receive_next::<SPDPF::Key>()?;
+            {
+                let (dpf_key_prev, dpf_key_next) =
+                    SPDPF::generate_keys(1 << index_bits, masked_loc as u64, value_share);
+                comm.send_previous(dpf_key_prev)?;
+                comm.send_next(dpf_key_next)?;
+            }
+            let dpf_key_prev = fut_prev.get()?;
+            let dpf_key_next = fut_next.get()?;
+            let t_after_dpf_key_distr = Instant::now();
+            self.stash_values_share
+                .par_iter_mut()
+                .enumerate()
+                .for_each(|(j, svs_j)| {
+                    let index_prev = ((j as u16).wrapping_add(r_prev) & bit_mask) as u64;
+                    let index_next = ((j as u16).wrapping_add(r_next) & bit_mask) as u64;
+                    *svs_j += SPDPF::evaluate_at(&dpf_key_prev, index_prev)
+                        + SPDPF::evaluate_at(&dpf_key_next, index_next);
+                });
+            (t_after_masked_index, t_after_dpf_key_distr)
+        };
+        let t_after_dpf_eval = Instant::now();
+
+        self.access_counter += 1;
+        self.state = if self.access_counter == self.stash_size {
+            State::AccessesExhausted
+        } else {
+            State::AwaitingRead
+        };
+
+        let runtimes = runtimes.map(|mut r| {
+            r.record(ProtocolStep::WriteAddressTag, t_after_address_tag - t_start);
+            r.record(
+                ProtocolStep::WriteStoreTriple,
+                t_after_store_triple - t_after_address_tag,
+            );
+            r.record(
+                ProtocolStep::WriteSelectPreviousValue,
+                t_after_select_previous_value - t_after_store_triple,
+            );
+            r.record(
+                ProtocolStep::WriteSelectValue,
+                t_after_select_value - t_after_select_previous_value,
+            );
+            r.record(
+                ProtocolStep::WriteComputeMaskedIndex,
+                t_after_masked_index - t_after_select_value,
+            );
+            r.record(
+                ProtocolStep::WriteDpfKeyDistribution,
+                t_after_dpf_key_distr - t_after_masked_index,
+            );
+            r.record(
+                ProtocolStep::WriteDpfEvaluations,
+                t_after_dpf_eval - t_after_dpf_key_distr,
+            );
+            r
+        });
+
+        Ok(runtimes)
+    }
+}
+
+impl<F, SPDPF> Stash<F> for StashProtocol<F, SPDPF>
+where
+    F: PrimeField + LegendreSymbol + Serializable,
+    SPDPF: SinglePointDpf<Value = F>,
+    SPDPF::Key: Serializable + Sync,
+{
+    fn get_party_id(&self) -> usize {
+        self.party_id
+    }
+
+    fn get_stash_size(&self) -> usize {
+        self.stash_size
+    }
+
+    fn get_access_counter(&self) -> usize {
+        self.access_counter
+    }
+
+    fn reset(&mut self) {
+        *self = Self::new(self.party_id, self.stash_size);
+    }
+
+    fn init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error> {
+        self.init_with_runtimes(comm, None).map(|_| ())
+    }
+
+    fn read<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        instruction: InstructionShare<F>,
+    ) -> Result<StashStateShare<F>, Error> {
+        self.read_with_runtimes(comm, instruction, None)
+            .map(|x| x.0)
+    }
+
+    fn write<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        instruction: InstructionShare<F>,
+        stash_state: StashStateShare<F>,
+        db_address_share: F,
+        db_value_share: F,
+    ) -> Result<(), Error> {
+        self.write_with_runtimes(
+            comm,
+            instruction,
+            stash_state,
+            db_address_share,
+            db_value_share,
+            None,
+        )
+        .map(|_| ())
+    }
+
+    fn get_stash_share(&self) -> (&[F], &[F], &[F]) {
+        (
+            &self.stash_addresses_share,
+            &self.stash_values_share,
+            &self.stash_old_values_share,
+        )
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::common::Operation;
+    use communicator::unix::make_unix_communicators;
+    use dpf::spdpf::DummySpDpf;
+    use ff::Field;
+    use std::thread;
+    use utils::field::Fp;
+
+    fn run_init<F>(
+        mut stash_party: impl Stash<F> + Send + 'static,
+        mut comm: impl AbstractCommunicator + Send + 'static,
+    ) -> thread::JoinHandle<(impl Stash<F>, impl AbstractCommunicator)>
+    where
+        F: PrimeField + LegendreSymbol,
+    {
+        thread::spawn(move || {
+            stash_party.init(&mut comm).unwrap();
+            (stash_party, comm)
+        })
+    }
+
+    fn run_read<F>(
+        mut stash_party: impl Stash<F> + Send + 'static,
+        mut comm: impl AbstractCommunicator + Send + 'static,
+        instruction: InstructionShare<F>,
+    ) -> thread::JoinHandle<(impl Stash<F>, impl AbstractCommunicator, StashStateShare<F>)>
+    where
+        F: PrimeField + LegendreSymbol,
+    {
+        thread::spawn(move || {
+            let result = stash_party.read(&mut comm, instruction);
+            (stash_party, comm, result.unwrap())
+        })
+    }
+
+    fn run_write<F>(
+        mut stash_party: impl Stash<F> + Send + 'static,
+        mut comm: impl AbstractCommunicator + Send + 'static,
+        instruction: InstructionShare<F>,
+        stash_state: StashStateShare<F>,
+        db_address_share: F,
+        db_value_share: F,
+    ) -> thread::JoinHandle<(impl Stash<F>, impl AbstractCommunicator)>
+    where
+        F: PrimeField + LegendreSymbol,
+    {
+        thread::spawn(move || {
+            stash_party
+                .write(
+                    &mut comm,
+                    instruction,
+                    stash_state,
+                    db_address_share,
+                    db_value_share,
+                )
+                .unwrap();
+            (stash_party, comm)
+        })
+    }
+
+    #[test]
+    fn test_stash() {
+        type SPDPF = DummySpDpf<Fp>;
+
+        let stash_size = 128;
+        let mut num_accesses = 0;
+
+        let party_1 = StashProtocol::<Fp, SPDPF>::new(PARTY_1, stash_size);
+        let party_2 = StashProtocol::<Fp, SPDPF>::new(PARTY_2, stash_size);
+        let party_3 = StashProtocol::<Fp, SPDPF>::new(PARTY_3, stash_size);
+        assert_eq!(party_1.get_party_id(), PARTY_1);
+        assert_eq!(party_2.get_party_id(), PARTY_2);
+        assert_eq!(party_3.get_party_id(), PARTY_3);
+        assert_eq!(party_1.get_stash_size(), stash_size);
+        assert_eq!(party_2.get_stash_size(), stash_size);
+        assert_eq!(party_3.get_stash_size(), stash_size);
+
+        let (comm_3, comm_2, comm_1) = {
+            let mut comms = make_unix_communicators(3);
+            (
+                comms.pop().unwrap(),
+                comms.pop().unwrap(),
+                comms.pop().unwrap(),
+            )
+        };
+
+        let h1 = run_init(party_1, comm_1);
+        let h2 = run_init(party_2, comm_2);
+        let h3 = run_init(party_3, comm_3);
+        let (party_1, comm_1) = h1.join().unwrap();
+        let (party_2, comm_2) = h2.join().unwrap();
+        let (party_3, comm_3) = h3.join().unwrap();
+
+        assert_eq!(party_1.get_access_counter(), 0);
+        assert_eq!(party_2.get_access_counter(), 0);
+        assert_eq!(party_3.get_access_counter(), 0);
+
+        // write a value 42 to address adr = 3
+        let value = 42;
+        let address = 3;
+        let inst_w3_1 = InstructionShare {
+            operation: Operation::Write.encode(),
+            address: Fp::from_u128(address),
+            value: Fp::from_u128(value),
+        };
+        let inst_w3_2 = InstructionShare {
+            operation: Fp::ZERO,
+            address: Fp::ZERO,
+            value: Fp::ZERO,
+        };
+        let inst_w3_3 = inst_w3_2.clone();
+
+        let h1 = run_read(party_1, comm_1, inst_w3_1);
+        let h2 = run_read(party_2, comm_2, inst_w3_2);
+        let h3 = run_read(party_3, comm_3, inst_w3_3);
+        let (party_1, comm_1, state_1) = h1.join().unwrap();
+        let (party_2, comm_2, state_2) = h2.join().unwrap();
+        let (party_3, comm_3, state_3) = h3.join().unwrap();
+
+        // since the stash is empty, st.flag must be zero
+        assert_eq!(state_1.flag + state_2.flag + state_3.flag, Fp::ZERO);
+        assert_eq!(
+            state_1.location + state_2.location + state_3.location,
+            Fp::ZERO
+        );
+
+        let h1 = run_write(
+            party_1,
+            comm_1,
+            inst_w3_1,
+            state_1,
+            inst_w3_1.address,
+            Fp::from_u128(0x71),
+        );
+        let h2 = run_write(
+            party_2,
+            comm_2,
+            inst_w3_2,
+            state_1,
+            inst_w3_2.address,
+            Fp::from_u128(0x72),
+        );
+        let h3 = run_write(
+            party_3,
+            comm_3,
+            inst_w3_3,
+            state_1,
+            inst_w3_3.address,
+            Fp::from_u128(0x73),
+        );
+        let (party_1, comm_1) = h1.join().unwrap();
+        let (party_2, comm_2) = h2.join().unwrap();
+        let (party_3, comm_3) = h3.join().unwrap();
+
+        num_accesses += 1;
+
+        assert_eq!(party_1.get_access_counter(), num_accesses);
+        assert_eq!(party_2.get_access_counter(), num_accesses);
+        assert_eq!(party_3.get_access_counter(), num_accesses);
+
+        {
+            let (st_adrs_1, st_vals_1, st_old_vals_1) = party_1.get_stash_share();
+            let (st_adrs_2, st_vals_2, st_old_vals_2) = party_2.get_stash_share();
+            let (st_adrs_3, st_vals_3, st_old_vals_3) = party_3.get_stash_share();
+            assert_eq!(st_adrs_1.len(), num_accesses);
+            assert_eq!(st_vals_1.len(), num_accesses);
+            assert_eq!(st_old_vals_1.len(), num_accesses);
+            assert_eq!(st_adrs_2.len(), num_accesses);
+            assert_eq!(st_vals_2.len(), num_accesses);
+            assert_eq!(st_old_vals_2.len(), num_accesses);
+            assert_eq!(st_adrs_3.len(), num_accesses);
+            assert_eq!(st_vals_3.len(), num_accesses);
+            assert_eq!(st_old_vals_3.len(), num_accesses);
+            assert_eq!(
+                st_adrs_1[0] + st_adrs_2[0] + st_adrs_3[0],
+                Fp::from_u128(address)
+            );
+            assert_eq!(
+                st_vals_1[0] + st_vals_2[0] + st_vals_3[0],
+                Fp::from_u128(value)
+            );
+        }
+
+        // read again from address adr = 3, we should get the value 42 back
+        let inst_r3_1 = InstructionShare {
+            operation: Operation::Read.encode(),
+            address: Fp::from_u128(3),
+            value: Fp::ZERO,
+        };
+        let inst_r3_2 = InstructionShare {
+            operation: Fp::ZERO,
+            address: Fp::ZERO,
+            value: Fp::ZERO,
+        };
+        let inst_r3_3 = inst_r3_2.clone();
+
+        let h1 = run_read(party_1, comm_1, inst_r3_1);
+        let h2 = run_read(party_2, comm_2, inst_r3_2);
+        let h3 = run_read(party_3, comm_3, inst_r3_3);
+        let (party_1, comm_1, state_1) = h1.join().unwrap();
+        let (party_2, comm_2, state_2) = h2.join().unwrap();
+        let (party_3, comm_3, state_3) = h3.join().unwrap();
+
+        let st_flag = state_1.flag + state_2.flag + state_3.flag;
+        let st_location = state_1.location + state_2.location + state_3.location;
+        let st_value = state_1.value + state_2.value + state_3.value;
+        assert_eq!(st_flag, Fp::ONE);
+        assert_eq!(st_location, Fp::from_u128(0));
+        assert_eq!(st_value, Fp::from_u128(value));
+
+        let h1 = run_write(
+            party_1,
+            comm_1,
+            inst_r3_1,
+            state_1,
+            Fp::from_u128(0x83),
+            Fp::from_u128(0x93),
+        );
+        let h2 = run_write(
+            party_2,
+            comm_2,
+            inst_r3_2,
+            state_1,
+            Fp::from_u128(0x83),
+            Fp::from_u128(0x93),
+        );
+        let h3 = run_write(
+            party_3,
+            comm_3,
+            inst_r3_3,
+            state_1,
+            Fp::from_u128(0x83),
+            Fp::from_u128(0x93),
+        );
+        let (party_1, comm_1) = h1.join().unwrap();
+        let (party_2, comm_2) = h2.join().unwrap();
+        let (party_3, comm_3) = h3.join().unwrap();
+
+        num_accesses += 1;
+
+        assert_eq!(party_1.get_access_counter(), num_accesses);
+        assert_eq!(party_2.get_access_counter(), num_accesses);
+        assert_eq!(party_3.get_access_counter(), num_accesses);
+
+        {
+            let (st_adrs_1, st_vals_1, st_old_vals_1) = party_1.get_stash_share();
+            let (st_adrs_2, st_vals_2, st_old_vals_2) = party_2.get_stash_share();
+            let (st_adrs_3, st_vals_3, st_old_vals_3) = party_3.get_stash_share();
+            assert_eq!(st_adrs_1.len(), num_accesses);
+            assert_eq!(st_vals_1.len(), num_accesses);
+            assert_eq!(st_old_vals_1.len(), num_accesses);
+            assert_eq!(st_adrs_2.len(), num_accesses);
+            assert_eq!(st_vals_2.len(), num_accesses);
+            assert_eq!(st_old_vals_2.len(), num_accesses);
+            assert_eq!(st_adrs_3.len(), num_accesses);
+            assert_eq!(st_vals_3.len(), num_accesses);
+            assert_eq!(st_old_vals_3.len(), num_accesses);
+            assert_eq!(
+                st_adrs_1[0] + st_adrs_2[0] + st_adrs_3[0],
+                Fp::from_u128(address)
+            );
+            assert_eq!(
+                st_vals_1[0] + st_vals_2[0] + st_vals_3[0],
+                Fp::from_u128(value)
+            );
+        }
+
+        // now write a value 0x1337 to address adr = 3
+        let old_value = value;
+        let value = 0x1337;
+        let address = 3;
+        let inst_w3_1 = InstructionShare {
+            operation: Operation::Write.encode(),
+            address: Fp::from_u128(address),
+            value: Fp::from_u128(value),
+        };
+        let inst_w3_2 = InstructionShare {
+            operation: Fp::ZERO,
+            address: Fp::ZERO,
+            value: Fp::ZERO,
+        };
+        let inst_w3_3 = inst_w3_2.clone();
+
+        let h1 = run_read(party_1, comm_1, inst_w3_1);
+        let h2 = run_read(party_2, comm_2, inst_w3_2);
+        let h3 = run_read(party_3, comm_3, inst_w3_3);
+        let (party_1, comm_1, state_1) = h1.join().unwrap();
+        let (party_2, comm_2, state_2) = h2.join().unwrap();
+        let (party_3, comm_3, state_3) = h3.join().unwrap();
+
+        // since we already wrote to the address, it should be present in the stash
+        assert_eq!(state_1.flag + state_2.flag + state_3.flag, Fp::ONE);
+        assert_eq!(
+            state_1.location + state_2.location + state_3.location,
+            Fp::ZERO
+        );
+        assert_eq!(
+            state_1.value + state_2.value + state_3.value,
+            Fp::from_u128(old_value)
+        );
+
+        let h1 = run_write(
+            party_1,
+            comm_1,
+            inst_w3_1,
+            state_1,
+            // inst_w3_1.address,
+            Fp::from_u128(0x61),
+            Fp::from_u128(0x71),
+        );
+        let h2 = run_write(
+            party_2,
+            comm_2,
+            inst_w3_2,
+            state_2,
+            // inst_w3_2.address,
+            Fp::from_u128(0x62),
+            Fp::from_u128(0x72),
+        );
+        let h3 = run_write(
+            party_3,
+            comm_3,
+            inst_w3_3,
+            state_3,
+            // inst_w3_3.address,
+            Fp::from_u128(0x63),
+            Fp::from_u128(0x73),
+        );
+        let (party_1, comm_1) = h1.join().unwrap();
+        let (party_2, comm_2) = h2.join().unwrap();
+        let (party_3, comm_3) = h3.join().unwrap();
+
+        num_accesses += 1;
+
+        assert_eq!(party_1.get_access_counter(), num_accesses);
+        assert_eq!(party_2.get_access_counter(), num_accesses);
+        assert_eq!(party_3.get_access_counter(), num_accesses);
+
+        {
+            let (st_adrs_1, st_vals_1, st_old_vals_1) = party_1.get_stash_share();
+            let (st_adrs_2, st_vals_2, st_old_vals_2) = party_2.get_stash_share();
+            let (st_adrs_3, st_vals_3, st_old_vals_3) = party_3.get_stash_share();
+            assert_eq!(st_adrs_1.len(), num_accesses);
+            assert_eq!(st_vals_1.len(), num_accesses);
+            assert_eq!(st_old_vals_1.len(), num_accesses);
+            assert_eq!(st_adrs_2.len(), num_accesses);
+            assert_eq!(st_vals_2.len(), num_accesses);
+            assert_eq!(st_old_vals_2.len(), num_accesses);
+            assert_eq!(st_adrs_3.len(), num_accesses);
+            assert_eq!(st_vals_3.len(), num_accesses);
+            assert_eq!(st_old_vals_3.len(), num_accesses);
+            assert_eq!(
+                st_adrs_1[0] + st_adrs_2[0] + st_adrs_3[0],
+                Fp::from_u128(address)
+            );
+            assert_eq!(
+                st_vals_1[0] + st_vals_2[0] + st_vals_3[0],
+                Fp::from_u128(value)
+            );
+        }
+
+        // read again from address adr = 3, we should get the value 0x1337 back
+        let inst_r3_1 = InstructionShare {
+            operation: Operation::Read.encode(),
+            address: Fp::from_u128(3),
+            value: Fp::ZERO,
+        };
+        let inst_r3_2 = InstructionShare {
+            operation: Fp::ZERO,
+            address: Fp::ZERO,
+            value: Fp::ZERO,
+        };
+        let inst_r3_3 = inst_r3_2.clone();
+
+        let h1 = run_read(party_1, comm_1, inst_r3_1);
+        let h2 = run_read(party_2, comm_2, inst_r3_2);
+        let h3 = run_read(party_3, comm_3, inst_r3_3);
+        let (party_1, comm_1, state_1) = h1.join().unwrap();
+        let (party_2, comm_2, state_2) = h2.join().unwrap();
+        let (party_3, comm_3, state_3) = h3.join().unwrap();
+
+        let st_flag = state_1.flag + state_2.flag + state_3.flag;
+        let st_location = state_1.location + state_2.location + state_3.location;
+        let st_value = state_1.value + state_2.value + state_3.value;
+        assert_eq!(st_flag, Fp::ONE);
+        assert_eq!(st_location, Fp::from_u128(0));
+        assert_eq!(st_value, Fp::from_u128(value));
+
+        let h1 = run_write(
+            party_1,
+            comm_1,
+            inst_r3_1,
+            state_1,
+            Fp::from_u128(0x83),
+            Fp::from_u128(0x93),
+        );
+        let h2 = run_write(
+            party_2,
+            comm_2,
+            inst_r3_2,
+            state_2,
+            Fp::from_u128(0x83),
+            Fp::from_u128(0x93),
+        );
+        let h3 = run_write(
+            party_3,
+            comm_3,
+            inst_r3_3,
+            state_3,
+            Fp::from_u128(0x83),
+            Fp::from_u128(0x93),
+        );
+        let (party_1, _comm_1) = h1.join().unwrap();
+        let (party_2, _comm_2) = h2.join().unwrap();
+        let (party_3, _comm_3) = h3.join().unwrap();
+
+        num_accesses += 1;
+
+        assert_eq!(party_1.get_access_counter(), num_accesses);
+        assert_eq!(party_2.get_access_counter(), num_accesses);
+        assert_eq!(party_3.get_access_counter(), num_accesses);
+
+        {
+            let (st_adrs_1, st_vals_1, st_old_vals_1) = party_1.get_stash_share();
+            let (st_adrs_2, st_vals_2, st_old_vals_2) = party_2.get_stash_share();
+            let (st_adrs_3, st_vals_3, st_old_vals_3) = party_3.get_stash_share();
+            assert_eq!(st_adrs_1.len(), num_accesses);
+            assert_eq!(st_vals_1.len(), num_accesses);
+            assert_eq!(st_old_vals_1.len(), num_accesses);
+            assert_eq!(st_adrs_2.len(), num_accesses);
+            assert_eq!(st_vals_2.len(), num_accesses);
+            assert_eq!(st_old_vals_2.len(), num_accesses);
+            assert_eq!(st_adrs_3.len(), num_accesses);
+            assert_eq!(st_vals_3.len(), num_accesses);
+            assert_eq!(st_old_vals_3.len(), num_accesses);
+            assert_eq!(
+                st_adrs_1[0] + st_adrs_2[0] + st_adrs_3[0],
+                Fp::from_u128(address)
+            );
+            assert_eq!(
+                st_vals_1[0] + st_vals_2[0] + st_vals_3[0],
+                Fp::from_u128(value)
+            );
+        }
+    }
+}

+ 104 - 0
oram/src/tools.rs

@@ -0,0 +1,104 @@
+//! Tools to collect benchmark metadata.
+
+use git_version::git_version;
+use serde::Serialize;
+use std::fs::{read_to_string, File};
+use std::io::{BufRead, BufReader};
+use std::process;
+
+/// Collection of metadata for benchmarks.
+#[derive(Clone, Debug, Serialize)]
+pub struct BenchmarkMetaData {
+    /// The hostname of the system.
+    pub hostname: String,
+    /// The username of the current user.
+    pub username: String,
+    /// A timestamp.
+    pub timestamp: String,
+    /// How this program was executed.
+    pub cmdline: Vec<String>,
+    /// The process ID.
+    pub pid: u32,
+    /// The git version of this software.
+    pub git_version: String,
+}
+
+impl BenchmarkMetaData {
+    /// Collect the metadata.
+    pub fn collect() -> Self {
+        BenchmarkMetaData {
+            hostname: get_hostname(),
+            username: get_username(),
+            timestamp: get_timestamp(),
+            cmdline: get_cmdline(),
+            pid: get_pid(),
+            git_version: git_version!(args = ["--abbrev=40", "--always", "--dirty"]).to_string(),
+        }
+    }
+}
+
+fn run_command_with_args(cmd: &str, args: &[&str]) -> String {
+    String::from_utf8(
+        process::Command::new(cmd)
+            .args(args)
+            .output()
+            .expect("process failed")
+            .stdout,
+    )
+    .expect("utf-8 decoding failed")
+    .trim()
+    .to_string()
+}
+
+fn run_command(cmd: &str) -> String {
+    String::from_utf8(
+        process::Command::new(cmd)
+            .output()
+            .expect("process failed")
+            .stdout,
+    )
+    .expect("utf-8 decoding failed")
+    .trim()
+    .to_string()
+}
+
+fn read_file(path: &str) -> String {
+    read_to_string(path).expect("read_to_string failed")
+}
+
+/// Get the username of the current user.
+pub fn get_username() -> String {
+    run_command("whoami")
+}
+
+/// Get the hostname of this machine.
+pub fn get_hostname() -> String {
+    read_file("/proc/sys/kernel/hostname").trim().to_string()
+}
+
+/// Get a current timestamp.
+pub fn get_timestamp() -> String {
+    run_command_with_args("date", &["--iso-8601=s"])
+}
+
+/// Get command line arguments used to run this program.
+pub fn get_cmdline() -> Vec<String> {
+    let f = File::open("/proc/self/cmdline").expect("cannot open file");
+    let mut reader = BufReader::new(f);
+    let mut cmdline: Vec<String> = Vec::new();
+    loop {
+        let mut bytes = Vec::<u8>::new();
+        let num_bytes = reader.read_until(0, &mut bytes).expect("read failed");
+        if num_bytes == 0 {
+            break;
+        }
+        bytes.pop();
+        cmdline.push(String::from_utf8(bytes).expect("utf-8 decoding failed"))
+    }
+    cmdline
+}
+
+/// Get process ID (PID) of the current process.
+pub fn get_pid() -> u32 {
+    process::id()
+}

+ 47 - 0
utils/Cargo.toml

@@ -0,0 +1,47 @@
+[package]
+name = "utils"
+version = "0.1.0"
+edition = "2021"
+description = "utils for the Ramen project"
+authors.workspace = true
+license.workspace = true
+readme.workspace = true
+repository.workspace = true
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+communicator = { path = "../communicator" }
+bincode = "2.0.0-rc.2"
+aes = "0.8.2"
+blake3 = "1.3.3"
+ff = { version = "0.13.0", features = ["derive"] }
+funty = "2.0.0"
+libm = "0.2.5"
+num = "0.4.0"
+rand = "0.8.5"
+rand_chacha = "0.3.1"
+rug = "1.19.0"
+
+[dev-dependencies]
+criterion = "0.4.0"
+
+[[bin]]
+name = "cuckoo_params"
+path = "src/bin/cuckoo_params.rs"
+
+[[bench]]
+name = "cuckoo"
+harness = false
+
+[[bench]]
+name = "field"
+harness = false
+
+[[bench]]
+name = "hash"
+harness = false
+
+[[bench]]
+name = "permutation"
+harness = false

+ 112 - 0
utils/benches/cuckoo.rs

@@ -0,0 +1,112 @@
+use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
+use utils::cuckoo::{Hasher, Parameters};
+use utils::hash::AesHashFunction;
+
+const LOG_DOMAIN_SIZES: [u32; 4] = [8, 12, 16, 20];
+
+pub fn bench_hash_domain_into_buckets(c: &mut Criterion) {
+    for log_domain_size in LOG_DOMAIN_SIZES {
+        let log_number_inputs = log_domain_size / 2;
+        let parameters = Parameters::<AesHashFunction<u32>, _>::sample(1 << log_number_inputs);
+        let hasher = Hasher::new(parameters);
+        c.bench_with_input(
+            BenchmarkId::new(
+                "Hasher<AesHashFunction>.hash_domain_into_buckets",
+                log_domain_size,
+            ),
+            &log_domain_size,
+            |b, &log_domain_size| {
+                b.iter(|| hasher.hash_domain_into_buckets(black_box(1 << log_domain_size)))
+            },
+        );
+    }
+}
+
+pub fn bench_position_map(c: &mut Criterion) {
+    let number_inputs = 1_000;
+    let parameters = Parameters::<AesHashFunction<u32>, _>::sample(number_inputs);
+    let hasher = Hasher::new(parameters);
+    let domain_size = 100_000;
+
+    let hash_table = hasher.hash_domain_into_buckets(domain_size);
+    // (ab)use one lookup table to obtain the input pairs for pos
+    let values =
+        Hasher::<AesHashFunction<u32>, _>::compute_pos_lookup_table(domain_size, &hash_table);
+
+    let pos = |bucket_i: usize, item: u64| -> u64 {
+        let idx = hash_table[bucket_i].partition_point(|x| x < &item);
+        assert!(idx != hash_table[bucket_i].len());
+        assert_eq!(item, hash_table[bucket_i][idx]);
+        assert!(idx == 0 || hash_table[bucket_i][idx - 1] != item);
+        idx as u64
+    };
+
+    let mut group = c.benchmark_group("position_map");
+    group.bench_function("search", |b| {
+        b.iter(|| {
+            for item in 0..domain_size {
+                for &(bucket_i, _) in values[item as usize].iter() {
+                    let idx = pos(bucket_i, item);
+                    black_box(idx);
+                }
+            }
+        })
+    });
+    group.bench_function("lookup", |b| {
+        let pos_lookup_table =
+            Hasher::<AesHashFunction<u32>, _>::compute_pos_lookup_table(domain_size, &hash_table);
+        b.iter(|| {
+            for item in 0..domain_size {
+                for &(bucket_i, _) in values[item as usize].iter() {
+                    let idx = Hasher::<AesHashFunction<u32>, _>::pos_lookup(
+                        &pos_lookup_table,
+                        bucket_i,
+                        item,
+                    );
+                    black_box(idx);
+                }
+            }
+        })
+    });
+    group.bench_function("precomputation+lookup", |b| {
+        b.iter(|| {
+            let pos_lookup_table = Hasher::<AesHashFunction<u32>, _>::compute_pos_lookup_table(
+                domain_size,
+                &hash_table,
+            );
+            for item in 0..domain_size {
+                for &(bucket_i, _) in values[item as usize].iter() {
+                    let idx = Hasher::<AesHashFunction<u32>, _>::pos_lookup(
+                        &pos_lookup_table,
+                        bucket_i,
+                        item,
+                    );
+                    black_box(idx);
+                }
+            }
+        })
+    });
+    group.finish();
+}
+
+pub fn bench_cuckoo_hash_items(c: &mut Criterion) {
+    for log_domain_size in LOG_DOMAIN_SIZES {
+        let log_number_inputs = log_domain_size / 2;
+        let parameters = Parameters::<AesHashFunction<u32>, _>::sample(1 << log_number_inputs);
+        let hasher = Hasher::new(parameters);
+        let items: Vec<u64> = (0..1 << log_number_inputs).collect();
+        c.bench_with_input(
+            BenchmarkId::new("Hasher<AesHashFunction>.cuckoo_hash_items", log_domain_size),
+            &log_domain_size,
+            |b, _| b.iter(|| hasher.cuckoo_hash_items(black_box(&items))),
+        );
+    }
+}
+
+criterion_group!(
+    benches,
+    bench_hash_domain_into_buckets,
+    bench_position_map,
+    bench_cuckoo_hash_items
+);
+criterion_main!(benches);

+ 63 - 0
utils/benches/field.rs

@@ -0,0 +1,63 @@
+use bincode;
+use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
+use ff::Field;
+use rand::thread_rng;
+use utils::field::{legendre_symbol_exp, legendre_symbol_rug, Fp};
+
+const VEC_LENS: [usize; 4] = [1, 4, 16, 64];
+
+pub fn bench_encode(c: &mut Criterion) {
+    for vec_len in VEC_LENS {
+        c.bench_with_input(
+            BenchmarkId::new("Fp::encode", vec_len),
+            &vec_len,
+            |b, &vec_len| {
+                let x: Vec<_> = (0..vec_len).map(|_| Fp::random(thread_rng())).collect();
+                b.iter(|| {
+                    black_box(
+                        bincode::encode_to_vec(&x, bincode::config::standard())
+                            .expect("encode failed"),
+                    )
+                });
+            },
+        );
+    }
+}
+
+pub fn bench_decode(c: &mut Criterion) {
+    for vec_len in VEC_LENS {
+        c.bench_with_input(
+            BenchmarkId::new("Fp::decode", vec_len),
+            &vec_len,
+            |b, &vec_len| {
+                let x: Vec<_> = (0..vec_len).map(|_| Fp::random(thread_rng())).collect();
+                let bytes =
+                    bincode::encode_to_vec(&x, bincode::config::standard()).expect("encode failed");
+                b.iter(|| {
+                    black_box(
+                        bincode::decode_from_slice::<Vec<Fp>, _>(
+                            &bytes,
+                            bincode::config::standard(),
+                        )
+                        .expect("decode failed"),
+                    )
+                });
+            },
+        );
+    }
+}
+
+pub fn bench_legendre_symbol(c: &mut Criterion) {
+    let mut g = c.benchmark_group("LegendreSymbol");
+    g.bench_function("exp", |b| {
+        let x = Fp::random(thread_rng());
+        b.iter(|| black_box(legendre_symbol_exp(x)));
+    });
+    g.bench_function("rug", |b| {
+        let x = Fp::random(thread_rng());
+        b.iter(|| black_box(legendre_symbol_rug(x)));
+    });
+}
+
+criterion_group!(benches, bench_encode, bench_decode, bench_legendre_symbol);
+criterion_main!(benches);

+ 14 - 0
utils/benches/hash.rs

@@ -0,0 +1,14 @@
+use criterion::{black_box, criterion_group, criterion_main, Criterion};
+use utils::hash::{AesHashFunction, HashFunction};
+
+pub fn bench_hash_range(c: &mut Criterion) {
+    let n = 100_000;
+    let range_size = 10_000;
+    let hash_function = AesHashFunction::<u32>::sample(range_size);
+    c.bench_function("AesHashFunction.hash_range", |b| {
+        b.iter(|| hash_function.hash_range(black_box(0..n)))
+    });
+}
+
+criterion_group!(benches, bench_hash_range);
+criterion_main!(benches);

+ 20 - 0
utils/benches/permutation.rs

@@ -0,0 +1,20 @@
+use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
+use utils::permutation::{FisherYatesPermutation, Permutation};
+
+const LOG_DOMAIN_SIZES: [u32; 4] = [8, 12, 16, 20];
+
+pub fn bench_permutation(c: &mut Criterion) {
+    for log_domain_size in LOG_DOMAIN_SIZES {
+        c.bench_with_input(
+            BenchmarkId::new("FisherYates", log_domain_size),
+            &log_domain_size,
+            |b, &log_domain_size| {
+                let key = FisherYatesPermutation::sample(1 << log_domain_size);
+                b.iter(|| black_box(FisherYatesPermutation::from_key(key)))
+            },
+        );
+    }
+}
+
+criterion_group!(benches, bench_permutation);
+criterion_main!(benches);

+ 22 - 0
utils/src/bin/cuckoo_params.rs

@@ -0,0 +1,22 @@
+use utils::cuckoo::{Hasher, Parameters};
+use utils::hash::AesHashFunction;
+
+fn main() {
+    let log_domain_sizes = [4, 8, 12, 16, 20, 24, 26];
+
+    println!("domain  inputs  #bucks  maxbucks  avgbucks  #emptybucks");
+
+    for log_domain_size in log_domain_sizes {
+        let log_number_inputs = log_domain_size / 2;
+        let params = Parameters::<AesHashFunction<u16>, _>::sample(1 << log_number_inputs);
+        let number_buckets = params.get_number_buckets();
+        let hasher = Hasher::new(params);
+        let buckets = hasher.hash_domain_into_buckets(1 << log_domain_size);
+        let max_bucket_size = buckets.iter().map(|b| b.len()).max().unwrap();
+        let avg_bucket_size = buckets.iter().map(|b| b.len()).sum::<usize>() / number_buckets;
+        let number_empty_buckets = buckets.iter().map(|b| b.len()).filter(|&l| l == 0).count();
+        println!(
+            "{log_domain_size:6}  {log_number_inputs:6}  {number_buckets:6}  {max_bucket_size:8}  {avg_bucket_size:8}  {number_empty_buckets:11}"
+        );
+    }
+}

+ 35 - 0
utils/src/bit_decompose.rs

@@ -0,0 +1,35 @@
+//! Functionality to compute the bit decomposition of integers.
+
+use num::PrimInt;
+
+/// Decompose an integer `x` into a vector of its bits.
+pub fn bit_decompose<T: PrimInt, U: From<bool>>(x: T, n_bits: usize) -> Vec<U> {
+    assert!(n_bits as u32 == T::zero().count_zeros() || x < T::one() << n_bits);
+    (0..n_bits)
+        .map(|i| (x & (T::one() << (n_bits - i - 1)) != T::zero()).into())
+        .collect()
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_bit_decompose() {
+        assert_eq!(
+            bit_decompose::<u32, u32>(0x42, 8),
+            vec![0, 1, 0, 0, 0, 0, 1, 0]
+        );
+        assert_eq!(
+            bit_decompose::<u32, u32>(0x42, 10),
+            vec![0, 0, 0, 1, 0, 0, 0, 0, 1, 0]
+        );
+        assert_eq!(
+            bit_decompose::<u32, u32>(0x46015ced, 32),
+            vec![
+                0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0,
+                1, 1, 0, 1,
+            ]
+        );
+    }
+}

+ 606 - 0
utils/src/cuckoo.rs

@@ -0,0 +1,606 @@
+//! Implementation of simple hashing and cuckoo hashing.
+
+use crate::hash::{HashFunction, HashFunctionValue};
+use bincode;
+use core::array;
+use libm::erf;
+use rand::{Rng, SeedableRng};
+use rand_chacha::ChaCha12Rng;
+use std::f64::consts::SQRT_2;
+use std::fmt;
+use std::fmt::Debug;
+
+/// Number of hash functions used for cuckoo hashing.
+pub const NUMBER_HASH_FUNCTIONS: usize = 3;
+
+/// Parameters for cuckoo hashing.
+pub struct Parameters<H: HashFunction<Value>, Value: HashFunctionValue>
+where
+    <Value as TryInto<usize>>::Error: Debug,
+{
+    number_inputs: usize,
+    number_buckets: usize,
+    hash_function_descriptions: [H::Description; 3],
+}
+
+impl<H, Value> Debug for Parameters<H, Value>
+where
+    H: HashFunction<Value>,
+    Value: HashFunctionValue,
+    <Value as TryInto<usize>>::Error: Debug,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        write!(f, "Parameters<H>{{")?;
+        write!(f, "number_inputs: {:?}, ", self.number_inputs)?;
+        write!(f, "number_buckets: {:?}, ", self.number_buckets)?;
+        write!(
+            f,
+            "hash_function_descriptions: {:?}",
+            self.hash_function_descriptions
+        )?;
+        write!(f, "}}")?;
+        Ok(())
+    }
+}
+impl<H: HashFunction<Value>, Value> Copy for Parameters<H, Value>
+where
+    Value: HashFunctionValue,
+    <Value as TryInto<usize>>::Error: Debug,
+{
+}
+impl<H: HashFunction<Value>, Value> Clone for Parameters<H, Value>
+where
+    Value: HashFunctionValue,
+    <Value as TryInto<usize>>::Error: Debug,
+{
+    fn clone(&self) -> Self {
+        *self
+    }
+}
+
+impl<H: HashFunction<Value>, Value> bincode::Encode for Parameters<H, Value>
+where
+    Value: HashFunctionValue,
+    <Value as TryInto<usize>>::Error: Debug,
+    H::Description: bincode::Encode,
+{
+    fn encode<E: bincode::enc::Encoder>(
+        &self,
+        encoder: &mut E,
+    ) -> core::result::Result<(), bincode::error::EncodeError> {
+        bincode::Encode::encode(&self.number_inputs, encoder)?;
+        bincode::Encode::encode(&self.number_buckets, encoder)?;
+        bincode::Encode::encode(&self.hash_function_descriptions, encoder)?;
+        Ok(())
+    }
+}
+
+impl<H: HashFunction<Value>, Value> bincode::Decode for Parameters<H, Value>
+where
+    Value: HashFunctionValue,
+    <Value as TryInto<usize>>::Error: Debug,
+    H::Description: bincode::Decode + 'static,
+{
+    fn decode<D: bincode::de::Decoder>(
+        decoder: &mut D,
+    ) -> core::result::Result<Self, bincode::error::DecodeError> {
+        Ok(Self {
+            number_inputs: bincode::Decode::decode(decoder)?,
+            number_buckets: bincode::Decode::decode(decoder)?,
+            hash_function_descriptions: bincode::Decode::decode(decoder)?,
+        })
+    }
+}
+
+impl<H: HashFunction<Value>, Value> Parameters<H, Value>
+where
+    Value: HashFunctionValue,
+    <Value as TryInto<usize>>::Error: Debug,
+{
+    /// Samples three hash functions from given seed.
+    pub fn from_seed(number_inputs: usize, seed: [u8; 32]) -> Self {
+        let number_buckets = Self::compute_number_buckets(number_inputs);
+        let mut rng = ChaCha12Rng::from_seed(seed);
+        let hash_function_descriptions =
+            array::from_fn(|_| H::from_seed(number_buckets, rng.gen()).to_description());
+
+        Parameters::<H, Value> {
+            number_inputs,
+            number_buckets,
+            hash_function_descriptions,
+        }
+    }
+
+    /// Samples three hash functions randomly.
+    pub fn sample(number_inputs: usize) -> Self {
+        let number_buckets = Self::compute_number_buckets(number_inputs);
+        let hash_function_descriptions =
+            array::from_fn(|_| H::sample(number_buckets).to_description());
+
+        Parameters::<H, Value> {
+            number_inputs,
+            number_buckets,
+            hash_function_descriptions,
+        }
+    }
+
+    /// Compute how many buckets we need for the cuckoo table.
+    ///
+    /// This is based on
+    /// <https://github.com/ladnir/cryptoTools/blob/85da63e335c3ad3019af3958b48d3ff6750c3d92/cryptoTools/Common/CuckooIndex.cpp#L129-L150>.
+    fn compute_number_buckets(number_inputs: usize) -> usize {
+        assert_ne!(number_inputs, 0);
+
+        let statistical_security_parameter = 40;
+
+        let log_number_inputs = (number_inputs as f64).log2().ceil();
+        let a_max = 123.5;
+        let b_max = -130.0;
+        let a_sd = 2.3;
+        let b_sd = 2.18;
+        let a_mean = 6.3;
+        let b_mean = 6.45;
+        let a = a_max / 2.0 * (1.0 + erf((log_number_inputs - a_mean) / (a_sd * SQRT_2)));
+        let b = b_max / 2.0 * (1.0 + erf((log_number_inputs - b_mean) / (b_sd * SQRT_2)))
+            - log_number_inputs;
+        let e = (statistical_security_parameter as f64 - b) / a + 0.3;
+
+        (e * number_inputs as f64).ceil() as usize
+    }
+
+    /// Return the number of buckets.
+    pub fn get_number_buckets(&self) -> usize {
+        self.number_buckets
+    }
+
+    /// Return the number of inputs these parameters are specified for.
+    pub fn get_number_inputs(&self) -> usize {
+        self.number_inputs
+    }
+}
+
+/// Hasher using a given hash function construction.
+pub struct Hasher<H: HashFunction<Value>, Value: HashFunctionValue>
+where
+    <Value as TryInto<usize>>::Error: Debug,
+{
+    parameters: Parameters<H, Value>,
+    hash_functions: [H; 3],
+}
+
+impl<H: HashFunction<Value>, Value: HashFunctionValue> Hasher<H, Value>
+where
+    <Value as TryInto<usize>>::Error: Debug,
+{
+    /// Sentinel value to mark an unoccupied bucket.
+    pub const UNOCCUPIED: u64 = u64::MAX;
+
+    /// Create `Hasher` object with given parameters.
+    pub fn new(parameters: Parameters<H, Value>) -> Self {
+        let hash_functions =
+            array::from_fn(|i| H::from_description(parameters.hash_function_descriptions[i]));
+        Hasher {
+            parameters,
+            hash_functions,
+        }
+    }
+
+    /// Return the parameters.
+    pub fn get_parameters(&self) -> &Parameters<H, Value> {
+        &self.parameters
+    }
+
+    /// Hash a single item with the given hash function.
+    pub fn hash_single(&self, hash_function_index: usize, item: u64) -> Value {
+        assert!(hash_function_index < NUMBER_HASH_FUNCTIONS);
+        self.hash_functions[hash_function_index].hash_single(item)
+    }
+
+    /// Hash the whole domain [0, domain_size) with all three hash functions.
+    pub fn hash_domain(&self, domain_size: u64) -> [Vec<Value>; NUMBER_HASH_FUNCTIONS] {
+        array::from_fn(|i| self.hash_functions[i].hash_range(0..domain_size))
+    }
+
+    /// Hash the given items with all three hash functions.
+    pub fn hash_items(&self, items: &[u64]) -> [Vec<Value>; NUMBER_HASH_FUNCTIONS] {
+        array::from_fn(|i| self.hash_functions[i].hash_slice(items))
+    }
+
+    /// Hash the whole domain [0, domain_size) into buckets with all three hash functions
+    /// using precomputed hashes.
+    pub fn hash_domain_into_buckets_given_hashes(
+        &self,
+        domain_size: u64,
+        hashes: &[Vec<Value>; NUMBER_HASH_FUNCTIONS],
+    ) -> Vec<Vec<u64>> {
+        debug_assert!(hashes.iter().all(|v| v.len() as u64 == domain_size));
+        debug_assert_eq!(hashes.len(), NUMBER_HASH_FUNCTIONS);
+        let mut hash_table = vec![Vec::new(); self.parameters.number_buckets];
+        for x in 0..domain_size {
+            for hash_function_values in hashes.iter() {
+                let h = hash_function_values[x as usize];
+                hash_table[H::hash_value_as_usize(h)].push(x);
+            }
+        }
+        hash_table
+    }
+
+    /// Hash the whole domain [0, domain_size) into buckets with all three hash functions.
+    pub fn hash_domain_into_buckets(&self, domain_size: u64) -> Vec<Vec<u64>> {
+        self.hash_domain_into_buckets_given_hashes(domain_size, &self.hash_domain(domain_size))
+    }
+
+    /// Hash the given items into buckets all three hash functions.
+    pub fn hash_items_into_buckets(&self, items: &[u64]) -> Vec<Vec<u64>> {
+        let mut hash_table = vec![Vec::new(); self.parameters.number_buckets];
+        let hashes = self.hash_items(items);
+        debug_assert_eq!(hashes.len(), NUMBER_HASH_FUNCTIONS);
+        for (i, &x) in items.iter().enumerate() {
+            for hash_function_values in hashes.iter() {
+                let h = hash_function_values[i];
+                hash_table[H::hash_value_as_usize(h)].push(x);
+            }
+        }
+        hash_table
+    }
+
+    /// Compute a vector of the sizes of all buckets.
+    pub fn compute_bucket_sizes(hash_table: &[Vec<u64>]) -> Vec<usize> {
+        hash_table.iter().map(|v| v.len()).collect()
+    }
+
+    /// Compute a lookup table for the position map:
+    ///     bucket_i x item_j |-> index of item_j in bucket_i
+    /// The table stores three (bucket, index) pairs for every item of the domain, since each item
+    /// is placed into buckets using three hash functions.
+    pub fn compute_pos_lookup_table(
+        domain_size: u64,
+        hash_table: &[Vec<u64>],
+    ) -> Vec<[(usize, usize); 3]> {
+        let mut lookup_table = vec![[(usize::MAX, usize::MAX); 3]; domain_size as usize];
+        for (bucket_i, bucket) in hash_table.iter().enumerate() {
+            for (item_j, &item) in bucket.iter().enumerate() {
+                debug_assert!(item < domain_size);
+                for k in 0..NUMBER_HASH_FUNCTIONS {
+                    if lookup_table[item as usize][k] == (usize::MAX, usize::MAX) {
+                        lookup_table[item as usize][k] = (bucket_i, item_j);
+                        break;
+                    }
+                }
+            }
+        }
+        lookup_table
+    }
+
+    /// Use the lookup table for the position map.
+    pub fn pos_lookup(lookup_table: &[[(usize, usize); 3]], bucket_i: usize, item: u64) -> u64 {
+        for k in 0..NUMBER_HASH_FUNCTIONS {
+            if lookup_table[item as usize][k].0 == bucket_i {
+                return lookup_table[item as usize][k].1 as u64;
+            }
+        }
+        panic!("logic error");
+    }
+
+    /// Perform cuckoo hashing to place the given items into a vector.
+    /// NB: number of items must match the number of items used to generate the parameters.
+    pub fn cuckoo_hash_items(&self, items: &[u64]) -> (Vec<u64>, Vec<usize>) {
+        let number_inputs = self.parameters.number_inputs;
+        let number_buckets = self.parameters.number_buckets;
+
+        assert_eq!(
+            items.len(),
+            number_inputs,
+            "#items must match number inputs specified in the parameters"
+        );
+
+        // create cuckoo hash table to store all inputs
+        // we use u64::MAX to denote an empty entry
+        let mut cuckoo_table = vec![Self::UNOCCUPIED; self.parameters.number_buckets];
+        // store the indices of the items mapped into each bucket
+        let mut cuckoo_table_indices = vec![0usize; number_buckets];
+
+        let hashes = self.hash_items(items);
+
+        // keep track of which hash function we need to use next for an item
+        let mut next_hash_function = vec![0usize; number_buckets];
+
+        // if we need more than this number of steps to insert an item, we have found
+        // a cycle (this should only happen with negligible probability if the
+        // parameters are chosen correctly)
+        // const auto max_number_tries = NUMBER_HASH_FUNCTIONS * number_inputs_;
+        let max_number_tries = number_inputs + 1;
+
+        for input_j in 0..number_inputs {
+            let mut index = input_j;
+            let mut item = items[index];
+            let mut try_k = 0;
+            while try_k < max_number_tries {
+                // try to (re)insert item with current index
+                let hash: usize = H::hash_value_as_usize(hashes[next_hash_function[index]][index]);
+                // increment hash function counter for this item s.t. we use the next hash
+                // function next time
+                next_hash_function[index] = (next_hash_function[index] + 1) % NUMBER_HASH_FUNCTIONS;
+                if cuckoo_table[hash] == Self::UNOCCUPIED {
+                    // the bucket was free, so we can insert the item
+                    cuckoo_table[hash] = item;
+                    cuckoo_table_indices[hash] = index;
+                    break;
+                }
+                // the bucket was occupied, so we evict the item in the table and insert
+                // it with the next hash function
+                (cuckoo_table[hash], item) = (item, cuckoo_table[hash]);
+                (cuckoo_table_indices[hash], index) = (index, cuckoo_table_indices[hash]);
+                try_k += 1;
+            }
+            if try_k >= max_number_tries {
+                panic!("cycle detected"); // TODO: error handling
+            }
+        }
+
+        (cuckoo_table, cuckoo_table_indices)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::hash::AesHashFunction;
+    use rand::{seq::SliceRandom, thread_rng, Rng};
+
+    fn gen_random_numbers(n: usize) -> Vec<u64> {
+        (0..n).map(|_| thread_rng().gen()).collect()
+    }
+
+    fn create_hasher<H: HashFunction<Value>, Value: HashFunctionValue>(
+        number_inputs: usize,
+    ) -> Hasher<H, Value>
+    where
+        <Value as TryInto<usize>>::Error: Debug,
+    {
+        let params = Parameters::<H, Value>::sample(number_inputs);
+        Hasher::<H, Value>::new(params)
+    }
+
+    fn test_hash_cuckoo_with_param<H: HashFunction<Value>, Value: HashFunctionValue>(
+        log_number_inputs: usize,
+    ) where
+        <Value as TryInto<usize>>::Error: Debug,
+    {
+        let number_inputs = 1 << log_number_inputs;
+        let inputs = gen_random_numbers(number_inputs);
+        let cuckoo = create_hasher::<H, Value>(number_inputs);
+        let (cuckoo_table_items, cuckoo_table_indices) = cuckoo.cuckoo_hash_items(&inputs);
+
+        let number_buckets = cuckoo.get_parameters().get_number_buckets();
+        // check dimensions
+        assert_eq!(cuckoo_table_items.len(), number_buckets);
+        assert_eq!(cuckoo_table_indices.len(), number_buckets);
+        // check that we have the right number of things in the table
+        let num_unoccupied_entries = cuckoo_table_items
+            .iter()
+            .copied()
+            .filter(|&x| x == Hasher::<H, Value>::UNOCCUPIED)
+            .count();
+        assert_eq!(number_buckets - num_unoccupied_entries, number_inputs);
+        // keep track of which items we have seen in the cuckoo table
+        let mut found_inputs_in_table = vec![false; number_inputs];
+        for bucket_i in 0..number_buckets {
+            if cuckoo_table_items[bucket_i] != Hasher::<H, Value>::UNOCCUPIED {
+                let index = cuckoo_table_indices[bucket_i];
+                // check that the right item is here
+                assert_eq!(cuckoo_table_items[bucket_i], inputs[index]);
+                // check that we have not yet seen this item
+                assert!(!found_inputs_in_table[index]);
+                // remember that we have seen this item
+                found_inputs_in_table[index] = true;
+            }
+        }
+        // check that we have found all inputs in the cuckoo table
+        assert!(found_inputs_in_table.iter().all(|&x| x));
+    }
+
+    fn test_hash_domain_into_buckets_with_param<H: HashFunction<Value>, Value: HashFunctionValue>(
+        log_number_inputs: usize,
+    ) where
+        <Value as TryInto<usize>>::Error: Debug,
+    {
+        let number_inputs = 1 << log_number_inputs;
+        let cuckoo = create_hasher::<H, Value>(number_inputs);
+        let domain_size = 1 << 10;
+        let number_buckets = cuckoo.get_parameters().get_number_buckets();
+
+        let hash_table = cuckoo.hash_domain_into_buckets(domain_size);
+        assert_eq!(hash_table.len(), number_buckets);
+        for bucket in &hash_table {
+            // Check that items inside each bucket are sorted
+            // assert!(bucket.iter().is_sorted());  // `is_sorted` is currently nightly only
+            assert!(bucket.windows(2).all(|w| w[0] <= w[1]))
+        }
+
+        // Check that hashing is deterministic
+        let hash_table2 = cuckoo.hash_domain_into_buckets(domain_size);
+        assert_eq!(hash_table, hash_table2);
+
+        let hashes = cuckoo.hash_domain(domain_size);
+
+        for element in 0..domain_size {
+            if hashes[0][element as usize] == hashes[1][element as usize]
+                && hashes[0][element as usize] == hashes[2][element as usize]
+            {
+                let hash = H::hash_value_as_usize(hashes[0][element as usize]);
+                let idx_start = hash_table[hash]
+                    .as_slice()
+                    .partition_point(|x| x < &element);
+                let idx_end = hash_table[hash]
+                    .as_slice()
+                    .partition_point(|x| x <= &element);
+                // check that the element is in the bucket
+                assert_ne!(idx_start, hash_table[hash].len());
+                assert_eq!(hash_table[hash][idx_start], element);
+                // check that the element occurs three times
+                assert_eq!(idx_end - idx_start, 3);
+            } else if hashes[0][element as usize] == hashes[1][element as usize] {
+                let hash = H::hash_value_as_usize(hashes[0][element as usize]);
+                let idx_start = hash_table[hash]
+                    .as_slice()
+                    .partition_point(|x| x < &element);
+                let idx_end = hash_table[hash]
+                    .as_slice()
+                    .partition_point(|x| x <= &element);
+                // check that the element is in the bucket
+                assert_ne!(idx_start, hash_table[hash].len());
+                assert_eq!(hash_table[hash][idx_start], element);
+                // check that the element occurs two times
+                assert_eq!(idx_end - idx_start, 2);
+
+                let hash_other = H::hash_value_as_usize(hashes[2][element as usize]);
+                assert!(hash_table[hash_other]
+                    .as_slice()
+                    .binary_search(&element)
+                    .is_ok());
+            } else if hashes[0][element as usize] == hashes[2][element as usize] {
+                let hash = H::hash_value_as_usize(hashes[0][element as usize]);
+                let idx_start = hash_table[hash]
+                    .as_slice()
+                    .partition_point(|x| x < &element);
+                let idx_end = hash_table[hash]
+                    .as_slice()
+                    .partition_point(|x| x <= &element);
+                // check that the element is in the bucket
+                assert_ne!(idx_start, hash_table[hash].len());
+                assert_eq!(hash_table[hash][idx_start], element);
+                // check that the element occurs two times
+                assert_eq!(idx_end - idx_start, 2);
+
+                let hash_other = H::hash_value_as_usize(hashes[1][element as usize]);
+                assert!(hash_table[hash_other]
+                    .as_slice()
+                    .binary_search(&element)
+                    .is_ok());
+            } else if hashes[1][element as usize] == hashes[2][element as usize] {
+                let hash = H::hash_value_as_usize(hashes[1][element as usize]);
+                let idx_start = hash_table[hash]
+                    .as_slice()
+                    .partition_point(|x| x < &element);
+                let idx_end = hash_table[hash]
+                    .as_slice()
+                    .partition_point(|x| x <= &element);
+                // check that the element is in the bucket
+                assert_ne!(idx_start, hash_table[hash].len());
+                assert_eq!(hash_table[hash][idx_start], element);
+                // check that the element occurs two times
+                assert_eq!(idx_end - idx_start, 2);
+
+                let hash_other = H::hash_value_as_usize(hashes[0][element as usize]);
+                assert!(hash_table[hash_other]
+                    .as_slice()
+                    .binary_search(&element)
+                    .is_ok());
+            } else {
+                for hash_j in 0..NUMBER_HASH_FUNCTIONS {
+                    let hash = H::hash_value_as_usize(hashes[hash_j][element as usize]);
+                    assert!(hash_table[hash].as_slice().binary_search(&element).is_ok());
+                }
+            }
+        }
+
+        let num_items_in_hash_table: usize = hash_table.iter().map(|v| v.len()).sum();
+        assert_eq!(num_items_in_hash_table as u64, 3 * domain_size);
+    }
+
+    fn test_position_map_precomputation_with_param<
+        H: HashFunction<Value>,
+        Value: HashFunctionValue,
+    >(
+        log_number_inputs: usize,
+    ) where
+        <Value as TryInto<usize>>::Error: Debug,
+    {
+        let number_inputs = 1 << log_number_inputs;
+        let cuckoo = create_hasher::<H, Value>(number_inputs);
+        let domain_size = 1 << 10;
+
+        let hash_table = cuckoo.hash_domain_into_buckets(domain_size);
+        let lookup_table = Hasher::<H, Value>::compute_pos_lookup_table(domain_size, &hash_table);
+
+        let pos = |bucket_i: usize, item: u64| -> u64 {
+            let idx = hash_table[bucket_i].partition_point(|x| x < &item);
+            assert!(idx != hash_table[bucket_i].len());
+            assert_eq!(item, hash_table[bucket_i][idx]);
+            assert!(idx == 0 || hash_table[bucket_i][idx - 1] != item);
+            idx as u64
+        };
+
+        for (bucket_i, bucket) in hash_table.iter().enumerate() {
+            for &item in bucket.iter() {
+                assert_eq!(
+                    Hasher::<H, Value>::pos_lookup(&lookup_table, bucket_i, item),
+                    pos(bucket_i, item)
+                );
+            }
+        }
+    }
+
+    fn test_buckets_cuckoo_consistency_with_param<
+        H: HashFunction<Value>,
+        Value: HashFunctionValue,
+    >(
+        number_inputs: usize,
+    ) where
+        <Value as TryInto<usize>>::Error: Debug,
+    {
+        let domain_size = 1 << 10;
+        let cuckoo = create_hasher::<H, Value>(number_inputs);
+
+        // To generate random numbers in the domain, we generate the entire domain and do a random shuffle
+
+        let shuffled_domain = {
+            let mut vec: Vec<u64> = (0..domain_size).collect();
+            vec.shuffle(&mut thread_rng());
+            vec
+        };
+
+        // Checking that every element in a cuckoo bucket exists in the corresponding bucket of HashSimpleDomain
+        let hash_table = cuckoo.hash_domain_into_buckets(domain_size);
+        let (cuckoo_table_items, _) = cuckoo.cuckoo_hash_items(&shuffled_domain[..number_inputs]);
+        let number_buckets = cuckoo.get_parameters().get_number_buckets();
+
+        for bucket_i in 0..number_buckets {
+            if cuckoo_table_items[bucket_i] != Hasher::<H, Value>::UNOCCUPIED {
+                assert!(hash_table[bucket_i]
+                    .as_slice()
+                    .binary_search(&cuckoo_table_items[bucket_i])
+                    .is_ok());
+            }
+        }
+    }
+
+    #[test]
+    fn test_hash_cuckoo() {
+        for n in 5..10 {
+            test_hash_cuckoo_with_param::<AesHashFunction<u32>, u32>(n);
+        }
+    }
+
+    #[test]
+    fn test_hash_domain_into_buckets() {
+        for n in 5..10 {
+            test_hash_domain_into_buckets_with_param::<AesHashFunction<u32>, u32>(n);
+        }
+    }
+
+    #[test]
+    fn test_position_map_precomputation() {
+        for n in 5..10 {
+            test_position_map_precomputation_with_param::<AesHashFunction<u32>, u32>(n);
+        }
+    }
+
+    #[test]
+    fn test_buckets_cuckoo_consistency() {
+        for n in 5..10 {
+            test_buckets_cuckoo_consistency_with_param::<AesHashFunction<u32>, u32>(n);
+        }
+    }
+}

+ 340 - 0
utils/src/field.rs

@@ -0,0 +1,340 @@
+//! Implementation of the prime field used in Ramen.
+
+#![allow(missing_docs)] // Otherwise, there will be a warning originating from the PrimeField
+                        // derive macro ...
+
+use crate::fixed_key_aes::FixedKeyAes;
+use bincode;
+use blake3;
+use ff::{Field, PrimeField};
+use num;
+use rand::{thread_rng, Rng};
+use rug;
+
+/// Prime number `p` defining [`Fp`].
+#[allow(non_upper_case_globals)]
+pub const p: u128 = 340282366920938462946865773367900766209;
+
+/// Prime field with modulus [`p`].
+#[derive(PrimeField)]
+#[PrimeFieldModulus = "340282366920938462946865773367900766209"]
+#[PrimeFieldGenerator = "7"]
+#[PrimeFieldReprEndianness = "little"]
+pub struct Fp([u64; 3]);
+
+impl bincode::Encode for Fp {
+    fn encode<E: bincode::enc::Encoder>(
+        &self,
+        encoder: &mut E,
+    ) -> core::result::Result<(), bincode::error::EncodeError> {
+        bincode::Encode::encode(&self.to_le_bytes(), encoder)?;
+        Ok(())
+    }
+}
+
+impl bincode::Decode for Fp {
+    fn decode<D: bincode::de::Decoder>(
+        decoder: &mut D,
+    ) -> core::result::Result<Self, bincode::error::DecodeError> {
+        let bytes: [u8; 16] = bincode::Decode::decode(decoder)?;
+        Self::from_le_bytes_vartime(&bytes).ok_or_else(|| {
+            bincode::error::DecodeError::OtherString(format!(
+                "{bytes:?} does not encode a valid Fp element"
+            ))
+        })
+    }
+}
+
+impl num::traits::Zero for Fp {
+    fn zero() -> Self {
+        Self::ZERO
+    }
+
+    fn is_zero(&self) -> bool {
+        *self == Self::ZERO
+    }
+}
+
+/// Specifies that Self is the range of a PRF.
+pub trait FromPrf {
+    /// Key type of the PRF.
+    type PrfKey: Copy;
+
+    /// PRF key generation.
+    fn prf_key_gen() -> Self::PrfKey;
+
+    /// PRF: `[u64] -> Self`.
+    fn prf(key: &Self::PrfKey, input: u64) -> Self;
+
+    /// PRF into vector of Self.
+    fn prf_vector(key: &Self::PrfKey, input: u64, size: usize) -> Vec<Self>
+    where
+        Self: Sized;
+}
+
+/// Specifies that Self can be obtained from a PRG.
+pub trait FromPrg {
+    /// Expand a seed given as 128 bit integer.
+    fn expand(input: u128) -> Self;
+
+    /// Expand a seed given as byte slice of length 16.
+    fn expand_bytes(input: &[u8]) -> Self;
+}
+
+/// Trait for prime fields where the modulus can be provided as a 128 bit integer.
+pub trait Modulus128 {
+    /// Modulus of the prime field
+    const MOD: u128;
+}
+
+impl Modulus128 for Fp {
+    const MOD: u128 = p;
+}
+
+/// Specifies that Self can be hashed into.
+pub trait FromHash {
+    /// Hash a 64 bit integer into Self.
+    fn hash(input: u64) -> Self;
+
+    /// Hash a byte slice into Self.
+    fn hash_bytes(input: &[u8]) -> Self;
+}
+
+/// Definies the Legendre symbol in a prime field.
+pub trait LegendreSymbol: PrimeField {
+    /// Return an arbitrary QNR.
+    fn get_non_random_qnr() -> Self;
+
+    /// Compute the Legendre Symbol (p/a)
+    fn legendre_symbol(a: Self) -> i8;
+}
+
+/// Simple implementation of the legendre symbol using exponentiation.
+pub fn legendre_symbol_exp(a: Fp) -> i8 {
+    // handle 65x even
+    let mut x = a;
+    for _ in 0..65 {
+        x = x.square();
+    }
+
+    // handle 1x odd
+    let mut y = x;
+    x = x.square();
+
+    // handle 2x even
+    x = x.square();
+    x = x.square();
+
+    // handle 59x odd
+    for _ in 0..58 {
+        y = x * y;
+        x = x.square();
+    }
+    let z = x * y;
+
+    debug_assert!(
+        (z == -Fp::ONE || z == Fp::ONE || z == Fp::ZERO) && (z != Fp::ZERO || a == Fp::ZERO)
+    );
+
+    if z == Fp::ONE {
+        1
+    } else if z == -Fp::ONE {
+        -1
+    } else if z == Fp::ZERO {
+        0
+    } else {
+        panic!("something went wrong during Legendre Symbol computation")
+    }
+}
+
+/// Faster implementation of the legendre symbol using the `rug` library.
+pub fn legendre_symbol_rug(a: Fp) -> i8 {
+    let bytes = a.to_le_bytes();
+    let a_int = rug::Integer::from_digits(&bytes, rug::integer::Order::LsfLe);
+    let p_int = rug::Integer::from(p);
+    a_int.legendre(&p_int) as i8
+}
+
+impl LegendreSymbol for Fp {
+    // (p-1)/ 2 = 0b11111111111111111111111111111111111111111111111111111111111 00 1
+    // 00000000000000000000000000000000000000000000000000000000000000000
+    // (59x '1', 2x '9', 1x '1', 65x '0')
+
+    /// 7 is not a square mod p.
+    fn get_non_random_qnr() -> Self {
+        Self::from_u128(7)
+    }
+
+    /// Compute the Legendre Symbol (p/a)
+    fn legendre_symbol(a: Self) -> i8 {
+        legendre_symbol_rug(a)
+    }
+}
+
+impl Fp {
+    fn from_xof(xof: &mut blake3::OutputReader) -> Self {
+        assert_eq!(Self::NUM_BITS, 128);
+        loop {
+            let tmp = {
+                let mut repr = [0u64; 3];
+                for limb in repr.iter_mut().take(2) {
+                    let mut bytes = [0u8; 8];
+                    xof.fill(&mut bytes);
+                    *limb = u64::from_le_bytes(bytes);
+                }
+                Self(repr)
+            };
+
+            if tmp.is_valid() {
+                return tmp;
+            }
+        }
+    }
+
+    /// Convert a field element into 16 bytes using little endian byte order.
+    pub fn to_le_bytes(&self) -> [u8; 16] {
+        let mut bytes = [0u8; 16];
+        let repr = self.to_repr();
+        debug_assert_eq!(&repr.as_ref()[16..], &[0u8; 8]);
+        bytes.copy_from_slice(&repr.as_ref()[0..16]);
+        bytes
+    }
+
+    /// Create a field element from 16 bytes using little endian byte order.
+    pub fn from_le_bytes_vartime(bytes: &[u8; 16]) -> Option<Self> {
+        let mut repr = <Self as PrimeField>::Repr::default();
+        debug_assert_eq!(repr.as_ref(), &[0u8; 24]);
+        repr.as_mut()[0..16].copy_from_slice(bytes);
+        Self::from_repr_vartime(repr)
+    }
+}
+
+impl FromPrf for Fp {
+    type PrfKey = [u8; blake3::KEY_LEN];
+
+    /// PRF key generation
+    fn prf_key_gen() -> Self::PrfKey {
+        thread_rng().gen()
+    }
+
+    /// PRF into Fp
+    fn prf(key: &Self::PrfKey, input: u64) -> Self {
+        let mut hasher = blake3::Hasher::new_keyed(key);
+        hasher.update(&input.to_be_bytes());
+        let mut xof = hasher.finalize_xof();
+        Self::from_xof(&mut xof)
+    }
+
+    /// PRF into vector of Fp
+    fn prf_vector(key: &Self::PrfKey, input: u64, size: usize) -> Vec<Self> {
+        let mut hasher = blake3::Hasher::new_keyed(key);
+        hasher.update(&input.to_be_bytes());
+        let mut xof = hasher.finalize_xof();
+        (0..size).map(|_| Self::from_xof(&mut xof)).collect()
+    }
+}
+
+impl FromPrg for Fp {
+    fn expand(input: u128) -> Self {
+        Self::expand_bytes(&input.to_be_bytes())
+    }
+
+    fn expand_bytes(input: &[u8]) -> Self {
+        assert_eq!(input.len(), 16);
+        // not really "fixed-key"
+        let aes = FixedKeyAes::new(input.try_into().unwrap());
+        let mut i = 0;
+        loop {
+            let val = aes.pi(i);
+            if val < Fp::MOD {
+                return Fp::from_u128(val);
+            }
+            i += 1;
+        }
+    }
+}
+
+impl FromHash for Fp {
+    /// Hash into Fp
+    fn hash(input: u64) -> Self {
+        Self::hash_bytes(&input.to_be_bytes())
+    }
+
+    fn hash_bytes(input: &[u8]) -> Self {
+        let mut hasher = blake3::Hasher::new();
+        hasher.update(input);
+        let mut xof = hasher.finalize_xof();
+        Self::from_xof(&mut xof)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_legendre_symbol() {
+        const INPUTS: [u128; 20] = [
+            0,
+            1,
+            2,
+            35122421919063474048031845924062067909,
+            61212839083344548205786527436063227216,
+            108886203898319005744174078164860101674,
+            112160854746794802432264095652132979488,
+            142714630766673706362679167860844911107,
+            144328356835331043954321695814395383527,
+            149714699338443771695584577213555322897,
+            162837698983268132975860461485836731565,
+            185920817468766357617527011469055960606,
+            207479253861772423381237297118907360324,
+            220976947578297059190439898234224764278,
+            225624737240143795963751467909724695007,
+            230022448309092634504744292546382561960,
+            284649339713848098295138218361935151979,
+            293856596737329296797721884860187281734,
+            315840344961299616831836711745928570660,
+            340282366920938462946865773367900766208,
+        ];
+        const OUTPUTS: [i8; 20] = [
+            0, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1,
+        ];
+        for (&x, &y) in INPUTS.iter().zip(OUTPUTS.iter()) {
+            assert_eq!(Fp::legendre_symbol(Fp::from_u128(x)), y);
+            assert_eq!(legendre_symbol_exp(Fp::from_u128(x)), y);
+            assert_eq!(legendre_symbol_rug(Fp::from_u128(x)), y);
+        }
+        assert_eq!(Fp::legendre_symbol(Fp::get_non_random_qnr()), -1);
+        assert_eq!(legendre_symbol_exp(Fp::get_non_random_qnr()), -1);
+        assert_eq!(legendre_symbol_rug(Fp::get_non_random_qnr()), -1);
+    }
+
+    #[test]
+    fn test_serialization() {
+        for _ in 0..100 {
+            let x = Fp::random(thread_rng());
+            let x_bytes =
+                bincode::encode_to_vec(x, bincode::config::standard().skip_fixed_array_length())
+                    .unwrap();
+            assert_eq!(x_bytes.len(), 16);
+            let (y, bytes_read): (Fp, usize) = bincode::decode_from_slice(
+                &x_bytes,
+                bincode::config::standard().skip_fixed_array_length(),
+            )
+            .unwrap();
+            assert_eq!(bytes_read, x_bytes.len());
+            assert_eq!(y, x);
+        }
+    }
+
+    #[test]
+    fn test_to_bytes() {
+        for _ in 0..100 {
+            let x = Fp::random(thread_rng());
+            let x_bytes = x.to_le_bytes();
+            assert_eq!(x_bytes.len(), 16);
+            let y = Fp::from_le_bytes_vartime(&x_bytes).expect("from_le_bytes_vartime failed");
+            assert_eq!(x, y);
+        }
+    }
+}

+ 70 - 0
utils/src/fixed_key_aes.rs

@@ -0,0 +1,70 @@
+//! Functionality for AES in fixed-key mode.
+
+use aes::cipher::crypto_common::Block;
+use aes::cipher::{BlockEncrypt, KeyInit};
+use aes::Aes128;
+use rand::{thread_rng, Rng};
+
+/// Fixed-key AES implementation.  Implements the ((tweakable) circular) correlation robust hash
+/// functions from [Guo et al. (ePrint 2019/074)](https://eprint.iacr.org/2019/074).
+#[derive(Clone, Debug)]
+pub struct FixedKeyAes {
+    /// AES object including expanded key.
+    aes: Aes128,
+}
+
+impl FixedKeyAes {
+    /// Create a new instance with a given key.
+    pub fn new(key: [u8; 16]) -> Self {
+        Self {
+            aes: Aes128::new_from_slice(&key).expect("does not fail since key has the right size"),
+        }
+    }
+
+    /// Create a new instance with a randomly sampled key.
+    pub fn sample() -> Self {
+        let key: [u8; 16] = thread_rng().gen();
+        Self::new(key)
+    }
+
+    /// Permutation `sigma(x) = (x.high64 ^ x.low64, x.high64)`.
+    #[inline(always)]
+    fn sigma(x: u128) -> u128 {
+        let low = x & 0xffffffffffffffff;
+        let high = x >> 64;
+        ((high ^ low) << 64) | high
+    }
+
+    /// Random permutation `pi(x) = AES(k, x)`.
+    #[inline(always)]
+    pub fn pi(&self, x: u128) -> u128 {
+        let mut block = Block::<Aes128>::clone_from_slice(&x.to_le_bytes());
+        self.aes.encrypt_block(&mut block);
+        u128::from_le_bytes(
+            block
+                .as_slice()
+                .try_into()
+                .expect("does not fail since block is 16 bytes long"),
+        )
+    }
+
+    /// MMO function `pi(x) ^ x`.
+    #[inline(always)]
+    pub fn hash_cr(&self, x: u128) -> u128 {
+        self.pi(x) ^ x
+    }
+
+    /// MMO-hat function `pi(sigma(x)) ^ sigma(x)`.
+    #[inline(always)]
+    pub fn hash_ccr(&self, x: u128) -> u128 {
+        let sigma_x = Self::sigma(x);
+        self.pi(sigma_x) ^ sigma_x
+    }
+
+    /// TMMO function `pi(pi(x) ^ i) ^ pi(x)`.
+    #[inline(always)]
+    pub fn hash_tccr(&self, x: u128, tweak: u128) -> u128 {
+        let pi_x = self.pi(x);
+        self.pi(pi_x ^ tweak) ^ pi_x
+    }
+}

+ 170 - 0
utils/src/hash.rs

@@ -0,0 +1,170 @@
+//! Functionality for hash functions.
+
+use crate::fixed_key_aes::FixedKeyAes;
+use bincode;
+use core::fmt::Debug;
+use core::ops::Range;
+use funty::Integral;
+use rand::{thread_rng, Rng, SeedableRng};
+use rand_chacha::ChaCha12Rng;
+use std::marker::PhantomData;
+
+/// Defines required properties of hash function values.
+pub trait HashFunctionValue: Integral + TryInto<usize>
+where
+    <Self as TryInto<usize>>::Error: Debug,
+{
+}
+
+impl HashFunctionValue for u16 {}
+impl HashFunctionValue for u32 {}
+impl HashFunctionValue for u64 {}
+
+/// Trait for hash functions `[u64] -> {0, ..., range_size}`.
+pub trait HashFunction<Value: HashFunctionValue>
+where
+    <Value as TryInto<usize>>::Error: Debug,
+{
+    /// Description type that can be used to pass a small description of a hash function to another
+    /// party.
+    type Description: Copy + Debug + PartialEq + Eq;
+
+    /// Sample a random hash function.
+    fn sample(range_size: usize) -> Self;
+
+    /// Sample a hash function using a given seed.
+    fn from_seed(range_size: usize, seed: [u8; 32]) -> Self;
+
+    /// Create new hash function instance from a description.
+    fn from_description(description: Self::Description) -> Self;
+
+    /// Convert this instance into an equivalent description.
+    fn to_description(&self) -> Self::Description;
+
+    /// Return the number of elements n in the range [0, n).
+    fn get_range_size(&self) -> usize;
+
+    /// Hash a single item.
+    fn hash_single(&self, item: u64) -> Value;
+
+    /// Hash a slice of items.
+    fn hash_slice(&self, items: &[u64]) -> Vec<Value> {
+        items.iter().map(|x| self.hash_single(*x)).collect()
+    }
+
+    /// Hash a range [a,b) of items.
+    fn hash_range(&self, items: Range<u64>) -> Vec<Value> {
+        items.map(|x| self.hash_single(x)).collect()
+    }
+
+    /// Convert a hash value into a usize. Useful when hashes are used as indices.
+    /// Might panic if Value is not convertible to usize.
+    #[inline(always)]
+    fn hash_value_as_usize(value: Value) -> usize
+    where
+        <Value as TryInto<usize>>::Error: Debug,
+    {
+        <Value as TryInto<usize>>::try_into(value).unwrap()
+    }
+}
+
+/// Fixed-key AES hashing using a circular correlation robust hash function.
+#[derive(Clone, Debug)]
+pub struct AesHashFunction<Value> {
+    description: AesHashFunctionDescription,
+    /// FixedKeyAes object including expanded key.
+    aes: FixedKeyAes,
+    _phantom: PhantomData<Value>,
+}
+
+/// Description type for [`AesHashFunction`].
+#[derive(Clone, Copy, Debug, PartialEq, Eq, bincode::Encode, bincode::Decode)]
+pub struct AesHashFunctionDescription {
+    /// Size of the range.
+    range_size: usize,
+    /// Raw AES key.
+    key: [u8; 16],
+}
+
+impl<Value: HashFunctionValue> HashFunction<Value> for AesHashFunction<Value>
+where
+    <Value as TryInto<usize>>::Error: Debug,
+    <Value as TryFrom<u64>>::Error: Debug,
+{
+    type Description = AesHashFunctionDescription;
+
+    fn get_range_size(&self) -> usize {
+        self.description.range_size
+    }
+
+    fn from_seed(range_size: usize, seed: [u8; 32]) -> Self {
+        assert!(range_size < (1 << 24));
+        let mut rng = ChaCha12Rng::from_seed(seed);
+        let key = rng.gen();
+        Self::from_description(AesHashFunctionDescription { range_size, key })
+    }
+
+    fn sample(range_size: usize) -> Self {
+        assert!(range_size < (1 << 24));
+        let key: [u8; 16] = thread_rng().gen();
+        Self::from_description(AesHashFunctionDescription { range_size, key })
+    }
+
+    fn from_description(description: Self::Description) -> Self {
+        let aes = FixedKeyAes::new(description.key);
+        Self {
+            description,
+            aes,
+            _phantom: PhantomData,
+        }
+    }
+    fn to_description(&self) -> Self::Description {
+        self.description
+    }
+
+    fn hash_single(&self, item: u64) -> Value {
+        let h = self.aes.hash_ccr(item as u128);
+        (h as u64 % self.description.range_size as u64)
+            .try_into()
+            .unwrap()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    fn test_hash_function<Value: HashFunctionValue, H: HashFunction<Value>>()
+    where
+        <Value as TryInto<usize>>::Error: Debug,
+    {
+        let range_size = 42;
+        let h = H::sample(range_size);
+        let h2 = H::from_description(h.to_description());
+        assert_eq!(range_size, h.get_range_size());
+        assert_eq!(h.to_description(), h2.to_description());
+
+        for _ in 0..100 {
+            let x: u64 = thread_rng().gen();
+            let hx = h.hash_single(x);
+            assert!(<Value as TryInto<usize>>::try_into(hx).unwrap() < range_size);
+            assert_eq!(hx, h2.hash_single(x));
+        }
+
+        let a = 1337;
+        let b = 1427;
+        let vec: Vec<u64> = (a..b).collect();
+        let hashes_range = h.hash_range(a..b);
+        let hashes_slice = h.hash_slice(vec.as_slice());
+        for (i, x) in (a..b).enumerate() {
+            assert_eq!(hashes_range[i], h.hash_single(x));
+        }
+        assert_eq!(hashes_range, hashes_slice);
+    }
+
+    #[test]
+    fn test_aes_hash_function() {
+        test_hash_function::<u32, AesHashFunction<u32>>();
+        test_hash_function::<u64, AesHashFunction<u64>>();
+    }
+}

+ 11 - 0
utils/src/lib.rs

@@ -0,0 +1,11 @@
+//! Miscellaneous functionality for the Ramen project.
+
+#![warn(missing_docs)]
+
+pub mod bit_decompose;
+pub mod cuckoo;
+pub mod field;
+pub mod fixed_key_aes;
+pub mod hash;
+pub mod permutation;
+pub mod pseudorandom_conversion;

+ 120 - 0
utils/src/permutation.rs

@@ -0,0 +1,120 @@
+//! Functionality for random permutations.
+
+use bincode;
+use rand::{thread_rng, Rng, SeedableRng};
+use rand_chacha::ChaCha20Rng;
+
+/// Trait that models a random permutation.
+pub trait Permutation {
+    /// Key type that defines the permutation.
+    type Key: Copy;
+
+    /// Sample a random key for a permutation with the given domain size.
+    fn sample(domain_size: usize) -> Self::Key;
+
+    /// Instantiate a permutation object from a given key.
+    fn from_key(key: Self::Key) -> Self;
+
+    /// Get the key for this permutation instance.
+    fn get_key(&self) -> Self::Key;
+
+    /// Return the domain size of this permutation.
+    fn get_domain_size(&self) -> usize;
+
+    /// Apply the permutation to index `x`.
+    fn permute(&self, x: usize) -> usize;
+}
+
+/// Key type for a [`FisherYatesPermutation`].
+#[derive(Clone, Copy, Debug, PartialEq, Eq, bincode::Encode, bincode::Decode)]
+pub struct FisherYatesPermutationKey {
+    domain_size: usize,
+    prg_seed: [u8; 32],
+}
+
+/// Random permutation based on a Fisher-Yates shuffle of [0, N) with a seeded PRG.
+#[derive(Clone, Debug)]
+pub struct FisherYatesPermutation {
+    key: FisherYatesPermutationKey,
+    permuted_vector: Vec<usize>,
+}
+
+impl Permutation for FisherYatesPermutation {
+    type Key = FisherYatesPermutationKey;
+
+    fn sample(domain_size: usize) -> Self::Key {
+        Self::Key {
+            domain_size,
+            prg_seed: thread_rng().gen(),
+        }
+    }
+
+    fn from_key(key: Self::Key) -> Self {
+        // rng seeded by the key
+        let mut rng = ChaCha20Rng::from_seed(key.prg_seed);
+        // size of the domain
+        let n = key.domain_size;
+        // vector to store permutation explicitly
+        let mut permuted_vector: Vec<usize> = (0..n).collect();
+        // run Fisher-Yates
+        for i in (1..n).rev() {
+            let j: usize = rng.gen_range(0..=i);
+            permuted_vector.swap(j, i);
+        }
+        Self {
+            key,
+            permuted_vector,
+        }
+    }
+
+    fn get_key(&self) -> Self::Key {
+        self.key
+    }
+
+    fn get_domain_size(&self) -> usize {
+        self.key.domain_size
+    }
+
+    fn permute(&self, x: usize) -> usize {
+        assert!(x < self.permuted_vector.len());
+        self.permuted_vector[x]
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    fn test_permutation<Perm: Permutation>(log_domain_size: u32) {
+        let n: usize = 1 << log_domain_size;
+        let key = Perm::sample(n);
+        let perm = Perm::from_key(key);
+        let mut buffer = vec![0usize; n];
+        for i in 0..n {
+            buffer[i] = perm.permute(i);
+        }
+        buffer.sort();
+        for i in 0..n {
+            assert_eq!(buffer[i], i);
+        }
+    }
+
+    #[test]
+    fn test_all_permutations() {
+        let log_domain_size = 10;
+        test_permutation::<FisherYatesPermutation>(log_domain_size);
+    }
+
+    #[test]
+    fn test_serialization() {
+        for _ in 0..100 {
+            let log_domain_size = thread_rng().gen_range(1..30);
+            let key = FisherYatesPermutation::sample(log_domain_size);
+            let bytes = bincode::encode_to_vec(key, bincode::config::standard()).unwrap();
+            let (new_key, bytes_read): (FisherYatesPermutationKey, usize) =
+                bincode::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
+            assert_eq!(bytes_read, bytes.len());
+            assert_eq!(new_key, key);
+        }
+    }
+}

+ 52 - 0
utils/src/pseudorandom_conversion.rs

@@ -0,0 +1,52 @@
+//! Functionality to convert random seeds into a pseudorandom value.
+//!
+//! Used for the Half-Tree implementation.
+
+use crate::field::FromPrg;
+use core::num::Wrapping;
+
+/// Trait describing a functionality to convert a 128 bit random seed into a pseudorandom value of
+/// type `T`.
+pub trait PRConvertTo<T> {
+    /// Convert random seed into an instance of type `T`.
+    fn convert(randomness: u128) -> T;
+}
+
+/// Helper struct for the pseudorandom conversion.
+pub struct PRConverter {}
+
+impl PRConvertTo<u8> for PRConverter {
+    fn convert(randomness: u128) -> u8 {
+        (randomness & 0xff) as u8
+    }
+}
+impl PRConvertTo<u16> for PRConverter {
+    fn convert(randomness: u128) -> u16 {
+        (randomness & 0xffff) as u16
+    }
+}
+impl PRConvertTo<u32> for PRConverter {
+    fn convert(randomness: u128) -> u32 {
+        (randomness & 0xffffffff) as u32
+    }
+}
+impl PRConvertTo<u64> for PRConverter {
+    fn convert(randomness: u128) -> u64 {
+        (randomness & 0xffffffffffffffff) as u64
+    }
+}
+
+impl<T> PRConvertTo<Wrapping<T>> for PRConverter
+where
+    PRConverter: PRConvertTo<T>,
+{
+    fn convert(randomness: u128) -> Wrapping<T> {
+        Wrapping(<Self as PRConvertTo<T>>::convert(randomness))
+    }
+}
+
+impl<F: FromPrg> PRConvertTo<F> for PRConverter {
+    fn convert(randomness: u128) -> F {
+        F::expand(randomness)
+    }
+}