Browse Source

Use only a single (wide) DPF for related reads and writes

It is common that one might make multiple accesses to the ORAM with the
same or related indices, such that everyone knows the _relation_, but
not the absolute indices.  For example, the algorithm may be accessing
a random node in a binary tree (laid our linearly in an array), but also
its two children, whose indices are easily computed from the index of
the parent.

In this case, we can get away with using just a single DPF of width W.
This one DPF can be used for arbitrarily many reads of related indices
(where the relation is revealed, but not the indices), and for up to W
updates.

The current code limits W to at most 5, but this is straightforward to
change.

Also, the current code, although it _uses_ only one DPF, still
_evaluates_ that DPF multiple times (once for each read or update).
This is possible to address, but will save for later.
Ian Goldberg 1 year ago
parent
commit
5c55c7e72e
4 changed files with 164 additions and 16 deletions
  1. 12 4
      duoram.hpp
  2. 31 10
      duoram.tcc
  3. 119 0
      online.cpp
  4. 2 2
      shapes.hpp

+ 12 - 4
duoram.hpp

@@ -242,7 +242,7 @@ protected:
 
 public:
     // Get the size
-    inline size_t size() { return shape_size; }
+    inline size_t size() const { return shape_size; }
 
     // Enable or disable explicit-only mode.  Only using [] with
     // explicit (address_t) indices are allowed in this mode.  Using []
@@ -324,6 +324,11 @@ public:
     Flat(Duoram &duoram, MPCTIO &tio, yield_t &yield, size_t start = 0,
         size_t len = 0);
 
+    // Constructor.  len=0 means the maximum size (the parent's size
+    // minus start).
+    Flat(const Shape &parent, MPCTIO &tio, yield_t &yield, size_t start = 0,
+        size_t len = 0);
+
     // Copy the given Flat except for the tio and yield
     Flat(const Flat &copy_from, MPCTIO &tio, yield_t &yield) :
         Shape(copy_from, tio, yield), start(copy_from.start),
@@ -358,7 +363,7 @@ public:
     typename Duoram::Shape::template MemRefS<U,T,std::nullopt_t,Flat,WIDTH>
             operator[](OblivIndex<U,WIDTH> &obidx) {
         typename Duoram<T>::Shape::
-            template MemRefS<RegXS,T,std::nullopt_t,Flat,1>
+            template MemRefS<RegXS,T,std::nullopt_t,Flat,WIDTH>
             res(*this, obidx, std::nullopt);
         return res;
     }
@@ -440,9 +445,9 @@ public:
         next_windex(0), incremental(false), idx(idx)
     {
         if (player < 2) {
-            dt = tio.rdpftriple(yield, depth);
+            dt = tio.rdpftriple<WIDTH>(yield, depth);
         } else {
-            dp = tio.rdpfpair(yield, depth);
+            dp = tio.rdpfpair<WIDTH>(yield, depth);
         }
     }
 
@@ -473,6 +478,9 @@ public:
 
     // Get a copy of the index
     U index() { return idx; }
+
+    // Get the next wide-RDPF index
+    nbits_t windex() { assert(next_windex < WIDTH); return next_windex++; }
 };
 
 // An additive or XOR shared memory reference.  You get one of these

+ 31 - 10
duoram.tcc

@@ -177,6 +177,25 @@ Duoram<T>::Flat::Flat(Duoram &duoram, MPCTIO &tio, yield_t &yield,
     this->set_shape_size(len);
 }
 
+// Constructor for the Flat shape.  len=0 means the maximum size (the
+// parent's size minus start).
+template <typename T>
+Duoram<T>::Flat::Flat(const Shape &parent, MPCTIO &tio, yield_t &yield,
+    size_t start, size_t len) : Shape(parent, parent.duoram, tio, yield)
+{
+    size_t parentsize = parent.size();
+    if (start > parentsize) {
+        start = parentsize;
+    }
+    this->start = start;
+    size_t maxshapesize = parentsize - start;
+    if (len > maxshapesize || len == 0) {
+        len = maxshapesize;
+    }
+    this->len = len;
+    this->set_shape_size(len);
+}
+
 // Bitonic sort the elements from start to start+len-1, in
 // increasing order if dir=0 or decreasing order if dir=1. Note that
 // the elements must be at most 63 bits long each for the notion of
@@ -397,6 +416,7 @@ typename Duoram<T>::Shape::template MemRefS<U,FT,FST,Sh,WIDTH>
         // Computational players do this
 
         const RDPFTriple<WIDTH> &dt = *(oblividx->dt);
+        const nbits_t windex = oblividx->windex();
         const nbits_t depth = dt.depth();
 
         // Compute the index and message offsets
@@ -404,7 +424,7 @@ typename Duoram<T>::Shape::template MemRefS<U,FT,FST,Sh,WIDTH>
         dt.get_target(indoffset);
         indoffset -= oblividx->idx;
         typename RDPF<WIDTH>::W<FT> MW;
-        MW[0] = M;
+        MW[windex] = M;
         auto Moffset = std::make_tuple(MW, MW, MW);
         typename RDPFTriple<WIDTH>::WTriple<FT> scaled_val;
         dt.scaled_value(scaled_val);
@@ -435,7 +455,7 @@ typename Duoram<T>::Shape::template MemRefS<U,FT,FST,Sh,WIDTH>
             shape.shape_size, shape.tio.cpu_nthreads(),
             shape.tio.aes_ops());
         int init = 0;
-        pe.reduce(init, [this, &dt, &shape, &Mshift, player] (int thread_num,
+        pe.reduce(init, [this, &dt, &shape, &Mshift, player, windex] (int thread_num,
                 address_t i, const typename RDPFTriple<WIDTH>::LeafNode &leaf) {
             // The values from the three DPFs
             typename RDPFTriple<WIDTH>::WTriple<FT> scaled;
@@ -446,13 +466,13 @@ typename Duoram<T>::Shape::template MemRefS<U,FT,FST,Sh,WIDTH>
             // References to the appropriate cells in our database, our
             // blind, and our copy of the peer's blinded database
             auto [DB, BL, PBD] = shape.get_comp(i,fieldsel);
-            DB += V0[0];
+            DB += V0[windex];
             if (player == 0) {
-                BL -= V1[0];
-                PBD += V2[0]-V0[0];
+                BL -= V1[windex];
+                PBD += V2[windex]-V0[windex];
             } else {
-                BL -= V2[0];
-                PBD += V1[0]-V0[0];
+                BL -= V2[windex];
+                PBD += V1[windex]-V0[windex];
             }
             return 0;
         });
@@ -460,6 +480,7 @@ typename Duoram<T>::Shape::template MemRefS<U,FT,FST,Sh,WIDTH>
         // The server does this
 
         const RDPFPair<WIDTH> &dp = *(oblividx->dp);
+        const nbits_t windex = oblividx->windex();
         const nbits_t depth = dp.depth();
         U p0indoffset, p1indoffset;
         typename RDPFPair<WIDTH>::WPair<FT> p0Moffset, p1Moffset;
@@ -480,7 +501,7 @@ typename Duoram<T>::Shape::template MemRefS<U,FT,FST,Sh,WIDTH>
             shape.shape_size, shape.tio.cpu_nthreads(),
             shape.tio.aes_ops());
         int init = 0;
-        pe.reduce(init, [this, &dp, &shape, &Mshift] (int thread_num,
+        pe.reduce(init, [this, &dp, &shape, &Mshift, windex] (int thread_num,
                 address_t i, const typename RDPFPair<WIDTH>::LeafNode &leaf) {
             // The values from the two DPFs
             typename RDPFPair<WIDTH>::WPair<FT> scaled;
@@ -492,8 +513,8 @@ typename Duoram<T>::Shape::template MemRefS<U,FT,FST,Sh,WIDTH>
             // appropriate cells in the two blinded databases, so we can
             // subtract the pair directly.
             auto [BL0, BL1] = shape.get_server(i,fieldsel);
-            BL0 -= V0[0];
-            BL1 -= V1[0];
+            BL0 -= V0[windex];
+            BL1 -= V1[windex];
             return 0;
         });
     }

+ 119 - 0
online.cpp

@@ -1341,6 +1341,118 @@ static void bsearch_test(MPCIO &mpcio,
     });
 }
 
+template <typename T>
+static void related(MPCIO &mpcio,
+    const PRACOptions &opts, char **args)
+{
+    nbits_t depth = 5;
+
+    // The depth of the (complete) binary tree
+    if (*args) {
+        depth = atoi(*args);
+        ++args;
+    }
+    // The layer at which to choose a random parent node (and its two
+    // children along with it)
+    nbits_t layer = depth-1;
+    if (*args) {
+        layer = atoi(*args);
+        ++args;
+    }
+    assert(layer < depth);
+
+    MPCTIO tio(mpcio, 0, opts.num_threads);
+    run_coroutines(tio, [&mpcio, &tio, depth, layer] (yield_t &yield) {
+        size_t size = size_t(1)<<(depth+1);
+        Duoram<T> oram(tio.player(), size);
+        auto A = oram.flat(tio, yield);
+
+        // Initialize A with words with random top halves, and
+        // sequential bottom halves (just so we can more easily eyeball
+        // the right answers)
+        A.explicitonly(true);
+        for (address_t i=0;i<size;++i) {
+            T v;
+            v.randomize();
+            value_t vv = v.share();
+            vv &= 0x3fffffff00000000;
+            vv += (i * tio.player());
+            v.set(vv);
+            A[i] = v;
+        }
+        A.explicitonly(false);
+
+        // We use this layout for the tree:
+        // A[0] is unused
+        // A[1] is the root (layer 0)
+        // A[2..3] is layer 1
+        // A[4..7] is layer 2
+        // ...
+        // A[(1<<j)..((2<<j)-1)] is layer j
+        //
+        // So the parent of x is at location (x/2) and the children of x
+        // are at locations 2*x and 2*x+1
+
+        // Pick a random index _within_ the given layer (i.e., the
+        // offset from the beginning of the layer, not the absolute
+        // location in A)
+        RegXS idx;
+        idx.randomize(layer);
+        // Create the OblivIndex. RegXS is the type of the common index
+        // (idx), 3 is the maximum number of related updates to support
+        // (which equals the width of the underlying RDPF, currently
+        // maximum 5), layer is the depth of the underlying RDPF (the
+        // bit length of idx).
+        typename Duoram<T>::OblivIndex<RegXS,3> oidx(tio, yield, idx, layer);
+
+        // This is the (known) layer containing the (unknown) parent
+        // node
+        typename Duoram<T>::Flat P(A, tio, yield, 1<<layer, 1<<layer);
+        // This is the layer below that one, containing all possible
+        // children
+        typename Duoram<T>::Flat C(A, tio, yield, 2<<layer, 2<<layer);
+        // These are the subsets of C containing the left children and
+        // the right children respectively
+        typename Duoram<T>::Stride L(C, tio, yield, 0, 2);
+        typename Duoram<T>::Stride R(C, tio, yield, 1, 2);
+
+        T parent, left, right;
+
+        // Do three related reads.  In this version, only one DPF will
+        // be used, but it will still be _evaluated_ three times.
+        parent = P[oidx];
+        left = L[oidx];
+        right = R[oidx];
+
+        // The operation is just a simple rotation: the value in the
+        // parent moves to the left child, the left child moves to the
+        // right child, and the right child becomes the parent
+
+        // Do three related updates.  As above, only one (wide) DPF will
+        // be used (the same one as for the reads in fact), but it will
+        // still be _evaluated_ three more times.
+        P[oidx] += right-parent;
+        L[oidx] += parent-left;
+        R[oidx] += left-right;
+
+        // Check the answer
+        auto check = A.reconstruct();
+        if (depth <= 10) {
+            oram.dump();
+            if (tio.player() == 0) {
+                for (address_t i=0;i<size;++i) {
+                    printf("%04x %016lx\n", i, check[i].share());
+                }
+            }
+        }
+        value_t pval = mpc_reconstruct(tio, yield, parent);
+        value_t lval = mpc_reconstruct(tio, yield, left);
+        value_t rval = mpc_reconstruct(tio, yield, right);
+        printf("parent = %016lx\nleft   = %016lx\nright  = %016lx\n",
+            pval, lval, rval);
+    });
+}
+
 void online_main(MPCIO &mpcio, const PRACOptions &opts, char **args)
 {
     MPCTIO tio(mpcio, 0);
@@ -1430,6 +1542,13 @@ void online_main(MPCIO &mpcio, const PRACOptions &opts, char **args)
         } else {
             duoram<RegAS>(mpcio, opts, args);
         }
+    } else if (!strcmp(*args, "related")) {
+        ++args;
+        if (opts.use_xor_db) {
+            related<RegXS>(mpcio, opts, args);
+        } else {
+            related<RegAS>(mpcio, opts, args);
+        }
     } else if (!strcmp(*args, "cell")) {
         ++args;
         cell(mpcio, opts, args);

+ 2 - 2
shapes.hpp

@@ -92,7 +92,7 @@ public:
     typename Duoram::Shape::template MemRefS<U,T,std::nullopt_t,Pad,WIDTH>
             operator[](OblivIndex<U,WIDTH> &obidx) {
         typename Duoram<T>::Shape::
-            template MemRefS<RegXS,T,std::nullopt_t,Pad,1>
+            template MemRefS<RegXS,T,std::nullopt_t,Pad,WIDTH>
             res(*this, obidx, std::nullopt);
         return res;
     }
@@ -175,7 +175,7 @@ public:
     typename Duoram::Shape::template MemRefS<U,T,std::nullopt_t,Stride,WIDTH>
             operator[](OblivIndex<U,WIDTH> &obidx) {
         typename Duoram<T>::Shape::
-            template MemRefS<RegXS,T,std::nullopt_t,Stride,1>
+            template MemRefS<RegXS,T,std::nullopt_t,Stride,WIDTH>
             res(*this, obidx, std::nullopt);
         return res;
     }