Pārlūkot izejas kodu

oram: add initial stash protocol

Lennart Braun 2 gadi atpakaļ
vecāks
revīzija
b0e7d301f1
4 mainītis faili ar 1456 papildinājumiem un 20 dzēšanām
  1. 1 0
      oram/Cargo.toml
  2. 4 3
      oram/src/common.rs
  3. 1 1
      oram/src/lib.rs
  4. 1450 16
      oram/src/stash.rs

+ 1 - 0
oram/Cargo.toml

@@ -7,6 +7,7 @@ edition = "2021"
 
 [dependencies]
 communicator = { path = "../communicator" }
+dpf = { path = "../dpf" }
 utils = { path = "../utils" }
 bitvec = "1.0.1"
 ff = "0.13.0"

+ 4 - 3
oram/src/common.rs

@@ -24,10 +24,11 @@ impl Operation {
     }
 }
 
+#[derive(Clone, Copy, Debug, Default)]
 pub struct InstructionShare<F: PrimeField> {
-    operation_share: F,
-    address_share: F,
-    value: F,
+    pub operation: F,
+    pub address: F,
+    pub value: F,
 }
 
 #[derive(Debug)]

+ 1 - 1
oram/src/lib.rs

@@ -4,4 +4,4 @@ pub mod mask_index;
 pub mod oram;
 pub mod p_ot;
 pub mod select;
-mod stash;
+pub mod stash;

+ 1450 - 16
oram/src/stash.rs

@@ -1,39 +1,1473 @@
+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 bitvec;
+use communicator::{AbstractCommunicator, Fut, Serializable};
+use dpf::spdpf::SinglePointDpf;
 use ff::PrimeField;
+use rand::thread_rng;
+use std::io::Read;
 use std::marker::PhantomData;
+use utils::field::LegendreSymbol;
 
-pub struct Stash<F: PrimeField> {
-    stash_size: usize,
-    stash_share: Vec<StashEntryShare<F>>,
-    _phantom: PhantomData<F>,
+type BitVec = bitvec::vec::BitVec<u8>;
+// type BitSlice = bitvec::slice::BitSlice<u8>;
+
+#[derive(Clone, Copy, Debug, Default)]
+pub struct StashEntryShare<F: PrimeField> {
+    pub address: F,
+    pub value: F,
+    pub old_value: F,
 }
 
 #[derive(Clone, Copy, Debug, Default)]
-struct StashEntryShare<F: PrimeField> {
-    pub address_share: F,
-    pub value_share: F,
-    pub old_value_share: F,
+pub struct StashStateShare<F: PrimeField> {
+    pub flag: F,
+    pub location: F,
+    pub value: F,
+}
+
+#[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;
+
+pub trait Stash<F: PrimeField> {
+    fn get_party_id(&self) -> usize;
+
+    fn get_stash_size(&self) -> usize;
+
+    fn get_access_counter(&self) -> usize;
+
+    fn init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error>;
+
+    fn read<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        instruction: InstructionShare<F>,
+    ) -> Result<StashStateShare<F>, Error>;
+
+    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>;
+
+    fn get_stash_share(&self) -> (&[F], &[F], &[F]);
+}
+
+fn compute_stash_prf_output_bitsize(stash_size: usize) -> usize {
+    (stash_size as f64).log2().ceil() as usize + 40
+}
+
+fn bits_to_u64(mut bits: BitVec) -> u64 {
+    assert!(bits.len() <= 64);
+    let mut bytes = [0u8; 8];
+    bits.force_align(); // important! otherwise, the first bit in the bitvec might not be the lsb
+                        // of the first byte in the underlying byte vector
+    bits.read(&mut bytes).unwrap();
+    u64::from_le_bytes(bytes)
+}
+
+fn stash_read_value<C, F, SPDPF>(
+    comm: &mut C,
+    access_counter: usize,
+    location_share: F,
+    stash_values_share_mine: &[F],
+) -> Result<F, Error>
+where
+    C: AbstractCommunicator,
+    F: PrimeField + Serializable,
+    SPDPF: SinglePointDpf<Value = F>,
+    SPDPF::Key: Serializable,
+{
+    // a) convert the stash into replicated secret sharing
+    let fut_prev = comm.receive_previous::<Vec<F>>()?;
+    comm.send_next(stash_values_share_mine.to_vec())?;
+    let stash_values_share_prev = fut_prev.get()?;
+
+    // b) mask and reconstruct the stash index <loc>
+    let index_bits = (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)?;
+
+    // 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 mut value_share = F::ZERO;
+    for j in 0..access_counter {
+        let index_prev = ((j as u16 + r_prev) & bit_mask) as u64;
+        let index_next = ((j as u16 + r_next) & bit_mask) as u64;
+        value_share += SPDPF::evaluate_at(&dpf_key_prev, index_prev) * stash_values_share_mine[j];
+        value_share += SPDPF::evaluate_at(&dpf_key_next, index_next) * stash_values_share_prev[j];
+    }
+    Ok(value_share)
+}
+
+fn stash_write_value<C, F, SPDPF>(
+    comm: &mut C,
+    access_counter: usize,
+    location_share: F,
+    // old_value_share: F,
+    value_share: F,
+    stash_values_share_mine: &mut [F],
+) -> Result<(), Error>
+where
+    C: AbstractCommunicator,
+    F: PrimeField + Serializable,
+    SPDPF: SinglePointDpf<Value = F>,
+    SPDPF::Key: Serializable,
+{
+    // a) mask and reconstruct the stash index <loc>
+    let index_bits = {
+        let bits = (access_counter as f64).log2().ceil() as u32;
+        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, location_share)?;
+
+    eprintln!(
+        "Party {}: masked index = {}, r_prev = {}, r_next = {}    ({} bits)",
+        comm.get_my_id(),
+        masked_loc,
+        r_prev,
+        r_next,
+        index_bits
+    );
+    // 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()?;
+    for j in 0..=access_counter {
+        let index_prev = ((j as u16 + r_prev) & bit_mask) as u64;
+        let index_next = ((j as u16 + r_next) & bit_mask) as u64;
+        stash_values_share_mine[j] += SPDPF::evaluate_at(&dpf_key_prev, index_prev);
+        stash_values_share_mine[j] += SPDPF::evaluate_at(&dpf_key_next, index_next);
+    }
+    Ok(())
+}
+
+pub struct StashParty1<F, SPDPF>
+where
+    F: PrimeField + LegendreSymbol + Serializable,
+    SPDPF: SinglePointDpf<Value = F>,
+{
+    stash_size: usize,
+    access_counter: usize,
+    state: State,
+    stash_addresses_share: Vec<F>,
+    stash_values_share: Vec<F>,
+    stash_old_values_share: Vec<F>,
+    doprf_party_1: Option<DOPrfParty1<F>>,
+    masked_doprf_party_1: Option<MaskedDOPrfParty1<F>>,
+    _phantom: PhantomData<SPDPF>,
+}
+
+impl<F, SPDPF> StashParty1<F, SPDPF>
+where
+    F: PrimeField + LegendreSymbol + Serializable,
+    SPDPF: SinglePointDpf<Value = F>,
+{
+    pub fn new(stash_size: usize) -> Self {
+        assert!(stash_size > 0);
+        assert!(compute_stash_prf_output_bitsize(stash_size) <= 64);
+
+        Self {
+            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),
+            doprf_party_1: None,
+            masked_doprf_party_1: None,
+            _phantom: PhantomData,
+        }
+    }
+}
+
+impl<F, SPDPF> Stash<F> for StashParty1<F, SPDPF>
+where
+    F: PrimeField + LegendreSymbol + Serializable,
+    SPDPF: SinglePointDpf<Value = F>,
+    SPDPF::Key: Serializable,
+{
+    fn get_party_id(&self) -> usize {
+        1
+    }
+
+    fn get_stash_size(&self) -> usize {
+        self.stash_size
+    }
+
+    fn get_access_counter(&self) -> usize {
+        self.access_counter
+    }
+
+    fn init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error> {
+        assert_eq!(self.state, State::New);
+
+        let prf_output_bitsize = compute_stash_prf_output_bitsize(self.stash_size);
+        let legendre_prf_key = LegendrePrf::<F>::key_gen(prf_output_bitsize);
+        self.doprf_party_1 = Some(DOPrfParty1::from_legendre_prf_key(legendre_prf_key.clone()));
+        self.masked_doprf_party_1 =
+            Some(MaskedDOPrfParty1::from_legendre_prf_key(legendre_prf_key));
+
+        // run DOPRF initilization
+        {
+            let doprf_p1 = self.doprf_party_1.as_mut().unwrap();
+            let fut_3_1 = comm.receive(PARTY_3)?;
+            let (msg_1_2, _) = doprf_p1.init_round_0();
+            comm.send(PARTY_2, msg_1_2)?;
+            doprf_p1.init_round_1((), fut_3_1.get()?);
+        }
+        {
+            let mdoprf_p1 = self.masked_doprf_party_1.as_mut().unwrap();
+            let fut_3_1 = comm.receive(PARTY_3)?;
+            let (msg_1_2, _) = mdoprf_p1.init_round_0();
+            comm.send(PARTY_2, msg_1_2)?;
+            mdoprf_p1.init_round_1((), fut_3_1.get()?);
+        }
+
+        // panic!("not implemented");
+        self.state = State::AwaitingRead;
+        Ok(())
+    }
+
+    fn read<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        instruction: InstructionShare<F>,
+    ) -> Result<StashStateShare<F>, 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,
+            });
+        }
+
+        // 1. Compute tag y := PRF(k, <I.adr>) such that P1 obtains y + r and P2, P3 obtain the mask r.
+        // eprintln!("P{}: address_share = {:?}", comm.get_my_id() + 1, &[instruction.address]);
+        let masked_address_tag = {
+            let mdoprf_p1 = self.masked_doprf_party_1.as_mut().unwrap();
+
+            // for now do preprocessing on the fly
+            {
+                mdoprf_p1.preprocess_round_0(1);
+                mdoprf_p1.preprocess_round_1(1, (), ());
+            }
+
+            let fut_3_1 = comm.receive(PARTY_3)?;
+            let (_, msg_1_3) = mdoprf_p1.eval_round_0(1, &[instruction.address]);
+            comm.send(PARTY_3, msg_1_3)?;
+            let mut masked_tag =
+                mdoprf_p1.eval_round_2(1, &[instruction.address], (), fut_3_1.get()?);
+            // eprintln!("P{}: query masked_address_tag RAW = {:x?}", comm.get_my_id() + 1, masked_tag[0].as_raw_slice());
+            // masked_tag[0].force_align();
+            // eprintln!("P{}: query masked_address_tag RAW = {:x?}", comm.get_my_id() + 1, masked_tag[0].as_raw_slice());
+            bits_to_u64(masked_tag.pop().unwrap())
+        };
+        // eprintln!("P{}: query masked_address_tag = {:x?}", comm.get_my_id() + 1, masked_address_tag);
+
+        // 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)?;
+        }
+
+        // 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
+
+        // 4. Compute <loc> = if <flag> { <loc> } else { access_counter - 1 }
+        let location_share = SelectProtocol::select(
+            comm,
+            F::ZERO,
+            F::ZERO,
+            F::from_u128(self.access_counter as u128),
+        )?;
+
+        // 5. Reshare <flag> among all three parties
+        let flag_share = {
+            let flag_share = F::random(thread_rng());
+            comm.send(PARTY_2, flag_share)?;
+            flag_share
+        };
+
+        // 6. Read the value <val> from the stash (if <flag>) or read a zero value
+        let value_share = stash_read_value::<C, F, SPDPF>(
+            comm,
+            self.access_counter,
+            location_share,
+            &self.stash_values_share,
+        )?;
+
+        // TODO: handle an empty stash differently
+        self.state = State::AwaitingWrite;
+        Ok(StashStateShare {
+            flag: flag_share,
+            location: location_share,
+            value: value_share,
+        })
+    }
+
+    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> {
+        assert_eq!(self.state, State::AwaitingWrite);
+        assert!(self.access_counter < self.stash_size);
+
+        // 1. Compute tag y := PRF(k, <db_adr>) such that P2, P3 obtain y.
+        {
+            let doprf_p1 = self.doprf_party_1.as_mut().unwrap();
+
+            // for now do preprocessing on the fly
+            {
+                let fut_2_1 = comm.receive(PARTY_2)?;
+                doprf_p1.preprocess_round_0(1);
+                doprf_p1.preprocess_round_1(1, fut_2_1.get()?, ());
+            }
+
+            let fut_2_1 = comm.receive::<Vec<_>>(PARTY_2)?;
+            let fut_3_1 = comm.receive::<Vec<_>>(PARTY_3)?;
+            let (_, msg_1_3) =
+                doprf_p1.eval_round_1(1, &[db_address_share], &fut_2_1.get()?, &fut_3_1.get()?);
+            comm.send(PARTY_3, msg_1_3)?;
+        };
+
+        // 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);
+
+        // 3. Update stash
+        // - if I.op = write, we want to write I.val to index loc
+        // - if I.op = read, we need to write db_val to index c
+        //   (since that has been done already in step 2, it is essentially a no-op)
+        {
+            let previous_value_share =
+                SelectProtocol::select(comm, stash_state.flag, stash_state.value, db_value_share)?;
+            let location_share =
+                SelectProtocol::select(comm, instruction.operation, stash_state.location, -F::ONE)?;
+            let value_share = SelectProtocol::select(
+                comm,
+                instruction.operation,
+                instruction.value - previous_value_share,
+                F::ZERO,
+            )?;
+            // eprintln!(
+            //     "P{}: prev_val = {:?}, loc = {:?}, upd = {:?}, vals = {:?}",
+            //     comm.get_my_id() + 1,
+            //     previous_value_share,
+            //     location_share,
+            //     value_share,
+            //     self.stash_values_share
+            // );
+            stash_write_value::<C, F, SPDPF>(
+                comm,
+                self.access_counter,
+                location_share,
+                value_share,
+                &mut self.stash_values_share,
+            )?;
+            // eprintln!(
+            //     "P{}: prev_val = {:?}, loc = {:?}, upd = {:?}, vals = {:?}",
+            //     comm.get_my_id() + 1,
+            //     previous_value_share,
+            //     location_share,
+            //     value_share,
+            //     self.stash_values_share
+            // );
+        }
+
+        // todo!("not implemented");
+        self.access_counter += 1;
+        self.state = if self.access_counter == self.stash_size {
+            State::AccessesExhausted
+        } else {
+            State::AwaitingRead
+        };
+        Ok(())
+    }
+
+    fn get_stash_share(&self) -> (&[F], &[F], &[F]) {
+        (
+            &self.stash_addresses_share,
+            &self.stash_values_share,
+            &self.stash_old_values_share,
+        )
+    }
 }
 
-impl<F: PrimeField> Stash<F> {
+pub struct StashParty2<F, SPDPF>
+where
+    F: PrimeField + LegendreSymbol + Serializable,
+    SPDPF: SinglePointDpf<Value = F>,
+{
+    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>,
+    doprf_party_2: Option<DOPrfParty2<F>>,
+    masked_doprf_party_2: Option<MaskedDOPrfParty2<F>>,
+    _phantom: PhantomData<SPDPF>,
+}
 
+impl<F, SPDPF> StashParty2<F, SPDPF>
+where
+    F: PrimeField + LegendreSymbol + Serializable,
+    SPDPF: SinglePointDpf<Value = F>,
+{
     pub fn new(stash_size: usize) -> Self {
+        assert!(stash_size > 0);
+        assert!(compute_stash_prf_output_bitsize(stash_size) <= 64);
 
         Self {
             stash_size,
-            stash_share: Default::default(),
+            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: Vec::with_capacity(stash_size),
+            doprf_party_2: None,
+            masked_doprf_party_2: None,
             _phantom: PhantomData,
         }
     }
+}
+
+impl<F, SPDPF> Stash<F> for StashParty2<F, SPDPF>
+where
+    F: PrimeField + LegendreSymbol + Serializable,
+    SPDPF: SinglePointDpf<Value = F>,
+    SPDPF::Key: Serializable,
+{
+    fn get_party_id(&self) -> usize {
+        2
+    }
+
+    fn get_stash_size(&self) -> usize {
+        self.stash_size
+    }
 
-    pub fn init(&mut self, stash_share: &[F]) {
-        panic!("not implemented");
+    fn get_access_counter(&self) -> usize {
+        self.access_counter
     }
 
-    pub fn read(&self, counter: usize) {
-        panic!("not implemented");
+    fn init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error> {
+        assert_eq!(self.state, State::New);
+
+        let prf_output_bitsize = compute_stash_prf_output_bitsize(self.stash_size);
+        self.doprf_party_2 = Some(DOPrfParty2::new(prf_output_bitsize));
+        self.masked_doprf_party_2 = Some(MaskedDOPrfParty2::new(prf_output_bitsize));
+
+        // run DOPRF initilization
+        {
+            let doprf_p2 = self.doprf_party_2.as_mut().unwrap();
+            let fut_1_2 = comm.receive(PARTY_1)?;
+            let (_, msg_2_3) = doprf_p2.init_round_0();
+            comm.send(PARTY_3, msg_2_3)?;
+            doprf_p2.init_round_1(fut_1_2.get()?, ());
+        }
+        {
+            let mdoprf_p2 = self.masked_doprf_party_2.as_mut().unwrap();
+            let fut_1_2 = comm.receive(PARTY_1)?;
+            let (_, msg_2_3) = mdoprf_p2.init_round_0();
+            comm.send(PARTY_3, msg_2_3)?;
+            mdoprf_p2.init_round_1(fut_1_2.get()?, ());
+        }
+
+        // panic!("not implemented");
+        self.state = State::AwaitingRead;
+        Ok(())
     }
 
-    pub fn write(&self) {
-        panic!("not implemented");
+    fn read<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        instruction: InstructionShare<F>,
+    ) -> Result<StashStateShare<F>, 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,
+            });
+        }
+
+        // 1. Compute tag y := PRF(k, <I.adr>) such that P1 obtains y + r and P2, P3 obtain the mask r.
+        // eprintln!("P{}: address_share = {:?}", comm.get_my_id() + 1, &[instruction.address]);
+        let address_tag_mask = {
+            let mdoprf_p2 = self.masked_doprf_party_2.as_mut().unwrap();
+
+            // for now do preprocessing on the fly
+            {
+                let (_, msg_2_3) = mdoprf_p2.preprocess_round_0(1);
+                comm.send(PARTY_3, msg_2_3)?;
+                mdoprf_p2.preprocess_round_1(1, (), ());
+            }
+
+            let (_, msg_2_3) = mdoprf_p2.eval_round_0(1, &[instruction.address]);
+            comm.send(PARTY_3, msg_2_3)?;
+            let mut mask = mdoprf_p2.eval_get_output(1);
+            // eprintln!("P{}: query address_tag_mask RAW = {:x?}", comm.get_my_id() + 1, mask[0]);
+            // eprintln!("P{}: query address_tag_mask RAW = {:x?}", comm.get_my_id() + 1, mask[0].as_raw_slice());
+            // mask[0].force_align();
+            // eprintln!("P{}: query address_tag_mask RAW = {:x?}", comm.get_my_id() + 1, mask[0].as_raw_slice());
+            bits_to_u64(mask.pop().unwrap())
+        };
+        // eprintln!("P{}: query address_tag_mask = {:x?}", comm.get_my_id() + 1, address_tag_mask);
+        // eprintln!("P{}: address_tag_list = {:x?}", comm.get_my_id() + 1, self.address_tag_list);
+
+        // 2. Receive DPF key for the function f(x) = if x = y { 1 } else { 0 }
+        let dpf_key_2: SPDPF::Key = {
+            let fut = comm.receive(PARTY_1)?;
+            fut.get()?
+        };
+
+        // 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) = {
+            let mut flag_share = F::ZERO;
+            let mut location_share = F::ZERO;
+            let mut j_as_field_element = F::ZERO;
+            for j in 0..self.address_tag_list.len() {
+                let dpf_value_j =
+                    SPDPF::evaluate_at(&dpf_key_2, self.address_tag_list[j] ^ address_tag_mask);
+                flag_share += dpf_value_j;
+                location_share += j_as_field_element * dpf_value_j;
+                j_as_field_element += F::ONE;
+            }
+            (flag_share, location_share)
+        };
+
+        // 4. Compute <loc> = if <flag> { <loc> } else { access_counter - 1 }
+        let location_share = SelectProtocol::select(comm, flag_share, location_share, F::ZERO)?;
+
+        // 5. Reshare <flag> among all three parties
+        let flag_share = {
+            let fut_1_2 = comm.receive::<F>(PARTY_1)?;
+            flag_share - fut_1_2.get()?
+        };
+
+        // 6. Read the value <val> from the stash (if <flag>) or read a zero value
+        let value_share = stash_read_value::<C, F, SPDPF>(
+            comm,
+            self.access_counter,
+            location_share,
+            &self.stash_values_share,
+        )?;
+
+        self.state = State::AwaitingWrite;
+        Ok(StashStateShare {
+            flag: flag_share,
+            location: location_share,
+            value: value_share,
+        })
+    }
+
+    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> {
+        assert_eq!(self.state, State::AwaitingWrite);
+        assert!(self.access_counter < self.stash_size);
+
+        // 1. Compute tag y := PRF(k, <db_adr>) such that P2, P3 obtain y and append y to the tag
+        //    list.
+        let address_tag: u64 = {
+            let doprf_p2 = self.doprf_party_2.as_mut().unwrap();
+
+            // for now do preprocessing on the fly
+            {
+                let (msg_2_1, _) = doprf_p2.preprocess_round_0(1);
+                comm.send(PARTY_1, msg_2_1)?;
+                doprf_p2.preprocess_round_1(1, (), ());
+            }
+
+            let fut_3_2 = comm.receive(PARTY_3)?;
+            let (msg_2_1, _) = doprf_p2.eval_round_0(1, &[db_address_share]);
+            comm.send(PARTY_1, msg_2_1)?;
+            fut_3_2.get()?
+        };
+        self.address_tag_list.push(address_tag);
+
+        // 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);
+
+        // 3. Update stash
+        // - if I.op = write, we want to write I.val to index loc
+        // - if I.op = read, we need to write db_val to index c
+        //   (since that has been done already in step 2, it is essentially a no-op)
+        {
+            let previous_value_share =
+                SelectProtocol::select(comm, stash_state.flag, stash_state.value, db_value_share)?;
+            let location_share =
+                SelectProtocol::select(comm, instruction.operation, stash_state.location, -F::ONE)?;
+            let value_share = SelectProtocol::select(
+                comm,
+                instruction.operation,
+                instruction.value - previous_value_share,
+                F::ZERO,
+            )?;
+            // eprintln!(
+            //     "P{}: prev_val = {:?}, loc = {:?}, upd = {:?}, vals = {:?}",
+            //     comm.get_my_id() + 1,
+            //     previous_value_share,
+            //     location_share,
+            //     value_share,
+            //     self.stash_values_share
+            // );
+            stash_write_value::<C, F, SPDPF>(
+                comm,
+                self.access_counter,
+                location_share,
+                value_share,
+                &mut self.stash_values_share,
+            )?;
+            // eprintln!(
+            //     "P{}: prev_val = {:?}, loc = {:?}, upd = {:?}, vals = {:?}",
+            //     comm.get_my_id() + 1,
+            //     previous_value_share,
+            //     location_share,
+            //     value_share,
+            //     self.stash_values_share
+            // );
+        }
+
+        self.access_counter += 1;
+        self.state = if self.access_counter == self.stash_size {
+            State::AccessesExhausted
+        } else {
+            State::AwaitingRead
+        };
+        Ok(())
+    }
+
+    fn get_stash_share(&self) -> (&[F], &[F], &[F]) {
+        (
+            &self.stash_addresses_share,
+            &self.stash_values_share,
+            &self.stash_old_values_share,
+        )
+    }
+}
+
+pub struct StashParty3<F, SPDPF>
+where
+    F: PrimeField + LegendreSymbol + Serializable,
+    SPDPF: SinglePointDpf<Value = F>,
+{
+    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>,
+    doprf_party_3: Option<DOPrfParty3<F>>,
+    masked_doprf_party_3: Option<MaskedDOPrfParty3<F>>,
+    _phantom: PhantomData<SPDPF>,
+}
+
+impl<F, SPDPF> StashParty3<F, SPDPF>
+where
+    F: PrimeField + LegendreSymbol + Serializable,
+    SPDPF: SinglePointDpf<Value = F>,
+{
+    pub fn new(stash_size: usize) -> Self {
+        assert!(stash_size > 0);
+        assert!(compute_stash_prf_output_bitsize(stash_size) <= 64);
+
+        Self {
+            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: Vec::with_capacity(stash_size),
+            doprf_party_3: None,
+            masked_doprf_party_3: None,
+            _phantom: PhantomData,
+        }
+    }
+}
+
+impl<F, SPDPF> Stash<F> for StashParty3<F, SPDPF>
+where
+    F: PrimeField + LegendreSymbol + Serializable,
+    SPDPF: SinglePointDpf<Value = F>,
+    SPDPF::Key: Serializable,
+{
+    fn get_party_id(&self) -> usize {
+        3
+    }
+
+    fn get_stash_size(&self) -> usize {
+        self.stash_size
+    }
+
+    fn get_access_counter(&self) -> usize {
+        self.access_counter
+    }
+
+    fn init<C: AbstractCommunicator>(&mut self, comm: &mut C) -> Result<(), Error> {
+        assert_eq!(self.state, State::New);
+
+        let prf_output_bitsize = compute_stash_prf_output_bitsize(self.stash_size);
+        self.doprf_party_3 = Some(DOPrfParty3::new(prf_output_bitsize));
+        self.masked_doprf_party_3 = Some(MaskedDOPrfParty3::new(prf_output_bitsize));
+
+        // run DOPRF initilization
+        {
+            let doprf_p3 = self.doprf_party_3.as_mut().unwrap();
+            let fut_2_3 = comm.receive(PARTY_2)?;
+            let (msg_3_1, _) = doprf_p3.init_round_0();
+            comm.send(PARTY_1, msg_3_1)?;
+            doprf_p3.init_round_1((), fut_2_3.get()?);
+        }
+        {
+            let mdoprf_p3 = self.masked_doprf_party_3.as_mut().unwrap();
+            let fut_2_3 = comm.receive(PARTY_2)?;
+            let (msg_3_1, _) = mdoprf_p3.init_round_0();
+            comm.send(PARTY_1, msg_3_1)?;
+            mdoprf_p3.init_round_1((), fut_2_3.get()?);
+        }
+
+        // panic!("not implemented");
+        self.state = State::AwaitingRead;
+        Ok(())
+    }
+
+    fn read<C: AbstractCommunicator>(
+        &mut self,
+        comm: &mut C,
+        instruction: InstructionShare<F>,
+    ) -> Result<StashStateShare<F>, 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,
+            });
+        }
+
+        // 1. Compute tag y := PRF(k, <I.adr>) such that P1 obtains y + r and P2, P3 obtain the mask r.
+        eprintln!(
+            "P{}: address_share = {:?}",
+            comm.get_my_id() + 1,
+            &[instruction.address]
+        );
+        let address_tag_mask = {
+            let mdoprf_p3 = self.masked_doprf_party_3.as_mut().unwrap();
+
+            // for now do preprocessing on the fly
+            {
+                let fut_2_3 = comm.receive(PARTY_2)?;
+                mdoprf_p3.preprocess_round_0(1);
+                mdoprf_p3.preprocess_round_1(1, (), fut_2_3.get()?);
+            }
+
+            let fut_1_3 = comm.receive::<Vec<_>>(PARTY_1)?;
+            let fut_2_3 = comm.receive::<Vec<_>>(PARTY_2)?;
+            let (msg_3_1, _) =
+                mdoprf_p3.eval_round_1(1, &[instruction.address], &fut_1_3.get()?, &fut_2_3.get()?);
+            comm.send(PARTY_1, msg_3_1)?;
+            let mut mask = mdoprf_p3.eval_get_output(1);
+            // eprintln!("P{}: query address_tag_mask RAW = {:x?}", comm.get_my_id() + 1, mask[0]);
+            // eprintln!("P{}: query address_tag_mask RAW = {:x?}", comm.get_my_id() + 1, mask[0].as_raw_slice());
+            // mask[0].force_align();
+            // eprintln!("P{}: query address_tag_mask RAW = {:x?}", comm.get_my_id() + 1, mask[0].as_raw_slice());
+            bits_to_u64(mask.pop().unwrap())
+        };
+        // eprintln!("P{}: query address_tag_mask = {:x?}", comm.get_my_id() + 1, address_tag_mask);
+        // eprintln!("P{}: address_tag_list = {:x?}", comm.get_my_id() + 1, self.address_tag_list);
+
+        // 2. Receive DPF key for the function f(x) = if x = y { 1 } else { 0 }
+        let dpf_key_3: SPDPF::Key = {
+            let fut = comm.receive(PARTY_1)?;
+            fut.get()?
+        };
+
+        // 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) = {
+            let mut flag_share = F::ZERO;
+            let mut location_share = F::ZERO;
+            let mut j_as_field_element = F::ZERO;
+            for j in 0..self.address_tag_list.len() {
+                let dpf_value_j =
+                    SPDPF::evaluate_at(&dpf_key_3, self.address_tag_list[j] ^ address_tag_mask);
+                flag_share += dpf_value_j;
+                location_share += j_as_field_element * dpf_value_j;
+                j_as_field_element += F::ONE;
+            }
+            (flag_share, location_share)
+        };
+
+        // 4. Compute <loc> = if <flag> { <loc> } else { access_counter - 1 }
+        let location_share = SelectProtocol::select(comm, flag_share, location_share, F::ZERO)?;
+
+        // 5. Reshare <flag> among all three parties (nothing to do for P3)
+
+        // 6. Read the value <val> from the stash (if <flag>) or read a zero value
+        let value_share = stash_read_value::<C, F, SPDPF>(
+            comm,
+            self.access_counter,
+            location_share,
+            &self.stash_values_share,
+        )?;
+
+        self.state = State::AwaitingWrite;
+        Ok(StashStateShare {
+            flag: flag_share,
+            location: location_share,
+            value: value_share,
+        })
+    }
+
+    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> {
+        assert_eq!(self.state, State::AwaitingWrite);
+        assert!(self.access_counter < self.stash_size);
+
+        // 1. Compute tag y := PRF(k, <db_adr>) such that P2, P3 obtain y and append y to the tag
+        //    list.
+        let address_tag: u64 = {
+            let doprf_p3 = self.doprf_party_3.as_mut().unwrap();
+
+            // for now do preprocessing on the fly
+            {
+                doprf_p3.preprocess_round_0(1);
+                doprf_p3.preprocess_round_1(1, (), ());
+            }
+
+            let fut_1_3 = comm.receive(PARTY_1)?;
+            let (msg_3_1, _) = doprf_p3.eval_round_0(1, &[db_address_share]);
+            comm.send(PARTY_1, msg_3_1)?;
+            let mut tag = doprf_p3.eval_round_2(1, &[db_address_share], fut_1_3.get()?, ());
+            let tag = bits_to_u64(tag.pop().unwrap());
+            comm.send(PARTY_2, tag)?;
+            tag
+        };
+        self.address_tag_list.push(address_tag);
+
+        // 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);
+
+        // 3. Update stash
+        // - if I.op = write, we want to write I.val to index loc
+        //      if flag = true then loc < c
+        //          -> previous value in st.val
+        //      if flag = false then loc = c
+        //          -> previous value is db_val
+        // - if I.op = read, we need don't need to write anything
+        //   (but still touch every entry)
+        {
+            let previous_value_share =
+                SelectProtocol::select(comm, stash_state.flag, stash_state.value, db_value_share)?;
+            let location_share =
+                SelectProtocol::select(comm, instruction.operation, stash_state.location, -F::ONE)?;
+            let value_share = SelectProtocol::select(
+                comm,
+                instruction.operation,
+                instruction.value - previous_value_share,
+                F::ZERO,
+            )?;
+            // eprintln!(
+            //     "P{}: prev_val = {:?}, loc = {:?}, upd = {:?}, vals = {:?}",
+            //     comm.get_my_id() + 1,
+            //     previous_value_share,
+            //     location_share,
+            //     value_share,
+            //     self.stash_values_share
+            // );
+            stash_write_value::<C, F, SPDPF>(
+                comm,
+                self.access_counter,
+                location_share,
+                value_share,
+                &mut self.stash_values_share,
+            )?;
+            // eprintln!(
+            //     "P{}: prev_val = {:?}, loc = {:?}, upd = {:?}, vals = {:?}",
+            //     comm.get_my_id() + 1,
+            //     previous_value_share,
+            //     location_share,
+            //     value_share,
+            //     self.stash_values_share
+            // );
+        }
+
+        self.access_counter += 1;
+        self.state = if self.access_counter == self.stash_size {
+            State::AccessesExhausted
+        } else {
+            State::AwaitingRead
+        };
+
+        Ok(())
+    }
+
+    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 = StashParty1::<Fp, SPDPF>::new(stash_size);
+        let party_2 = StashParty2::<Fp, SPDPF>::new(stash_size);
+        let party_3 = StashParty3::<Fp, SPDPF>::new(stash_size);
+        assert_eq!(party_1.get_party_id(), 1);
+        assert_eq!(party_2.get_party_id(), 2);
+        assert_eq!(party_3.get_party_id(), 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
+        );
+
+        eprintln!(
+            "stash_state = {{ flag = {:?}, location = {:?}, value = {:?} }}",
+            state_1.flag + state_2.flag + state_3.flag,
+            state_1.location + state_2.location + state_3.location,
+            state_1.value + state_2.value + state_3.value
+        );
+
+        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();
+            eprintln!("{:?}    {:?}    {:?}", st_adrs_1, st_vals_1, st_old_vals_1);
+            eprintln!("{:?}    {:?}    {:?}", st_adrs_2, st_vals_2, st_old_vals_2);
+            eprintln!("{:?}    {:?}    {:?}", st_adrs_3, st_vals_3, st_old_vals_3);
+            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();
+            eprintln!("{:?}    {:?}    {:?}", st_adrs_1, st_vals_1, st_old_vals_1);
+            eprintln!("{:?}    {:?}    {:?}", st_adrs_2, st_vals_2, st_old_vals_2);
+            eprintln!("{:?}    {:?}    {:?}", st_adrs_3, st_vals_3, st_old_vals_3);
+            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)
+        );
+
+        eprintln!(
+            "stash_state = {{ flag = {:?}, location = {:?}, value = {:?} }}",
+            state_1.flag + state_2.flag + state_3.flag,
+            state_1.location + state_2.location + state_3.location,
+            state_1.value + state_2.value + state_3.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();
+            eprintln!("{:?}    {:?}    {:?}", st_adrs_1, st_vals_1, st_old_vals_1);
+            eprintln!("{:?}    {:?}    {:?}", st_adrs_2, st_vals_2, st_old_vals_2);
+            eprintln!("{:?}    {:?}    {:?}", st_adrs_3, st_vals_3, st_old_vals_3);
+            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();
+            eprintln!("{:?}    {:?}    {:?}", st_adrs_1, st_vals_1, st_old_vals_1);
+            eprintln!("{:?}    {:?}    {:?}", st_adrs_2, st_vals_2, st_old_vals_2);
+            eprintln!("{:?}    {:?}    {:?}", st_adrs_3, st_vals_3, st_old_vals_3);
+            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)
+            );
+        }
     }
 }