Browse Source

Add a DPF streaming evaluator

So we don't need to store the whole expanded DPF if we only need the
outputs in consecutive order (possibly starting at an arbitrary index
and wrapping around).
Ian Goldberg 1 year ago
parent
commit
7df8809b6d
3 changed files with 178 additions and 0 deletions
  1. 62 0
      online.cpp
  2. 90 0
      rdpf.cpp
  3. 26 0
      rdpf.hpp

+ 62 - 0
online.cpp

@@ -286,6 +286,65 @@ static void rdpf_timing(MPCIO &mpcio, const PRACOptions &opts, char **args)
     pool.join();
 }
 
+static void rdpfeval_timing(MPCIO &mpcio, const PRACOptions &opts, char **args)
+{
+    nbits_t depth=6;
+    address_t start=0;
+
+    if (*args) {
+        depth = atoi(*args);
+        ++args;
+    }
+    if (*args) {
+        start = atoi(*args);
+        ++args;
+    }
+
+    int num_threads = opts.num_threads;
+    boost::asio::thread_pool pool(num_threads);
+    for (int thread_num = 0; thread_num < num_threads; ++thread_num) {
+        boost::asio::post(pool, [&mpcio, thread_num, depth, start] {
+            MPCTIO tio(mpcio, thread_num);
+            size_t &op_counter = tio.aes_ops();
+            if (mpcio.player == 2) {
+                RDPFPair dp = tio.rdpfpair(depth);
+                for (int i=0;i<2;++i) {
+                    RDPF &dpf = dp.dpf[i];
+                    RegXS scaled_xor;
+                    scaled_xor.xshare = 0;
+                    RDPF::Eval ev = dpf.eval(start, op_counter, false);
+                    for (address_t x=0;x<(address_t(1)<<depth);++x) {
+                        DPFnode leaf = ev.next();
+                        RegXS sx = dpf.scaled_xs(leaf);
+                        scaled_xor ^= sx;
+                    }
+                    printf("%016lx\n%016lx\n", scaled_xor.xshare,
+                        dpf.scaled_xor.xshare);
+                    printf("\n");
+                }
+            } else {
+                RDPFTriple dt = tio.rdpftriple(depth);
+                for (int i=0;i<3;++i) {
+                    RDPF &dpf = dt.dpf[i];
+                    RegXS scaled_xor;
+                    scaled_xor.xshare = 0;
+                    RDPF::Eval ev = dpf.eval(start, op_counter, false);
+                    for (address_t x=0;x<(address_t(1)<<depth);++x) {
+                        DPFnode leaf = ev.next();
+                        RegXS sx = dpf.scaled_xs(leaf);
+                        scaled_xor ^= sx;
+                    }
+                    printf("%016lx\n%016lx\n", scaled_xor.xshare,
+                        dpf.scaled_xor.xshare);
+                    printf("\n");
+                }
+            }
+            tio.send();
+        });
+    }
+    pool.join();
+}
+
 void online_main(MPCIO &mpcio, const PRACOptions &opts, char **args)
 {
     if (!*args) {
@@ -303,6 +362,9 @@ void online_main(MPCIO &mpcio, const PRACOptions &opts, char **args)
     } else if (!strcmp(*args, "rdpftime")) {
         ++args;
         rdpf_timing(mpcio, opts, args);
+    } else if (!strcmp(*args, "evaltime")) {
+        ++args;
+        rdpfeval_timing(mpcio, opts, args);
     } else {
         std::cerr << "Unknown mode " << *args << "\n";
     }

+ 90 - 0
rdpf.cpp

@@ -360,6 +360,96 @@ void RDPF::expand(size_t &op_counter)
     delete[] path;
 }
 
+// Create an Eval object that will start its output at index start.
+// It will wrap around to 0 when it hits 2^depth.  If use_expansion
+// is true, then if the DPF has been expanded, just output values
+// from that.  If use_expansion=false or if the DPF has not been
+// expanded, compute the values on the fly.
+RDPF::Eval RDPF::eval(address_t start, size_t &op_counter,
+    bool use_expansion) const
+{
+    RDPF::Eval eval(*this, op_counter, start, use_expansion);
+
+    return eval;
+}
+
+RDPF::Eval::Eval(const RDPF &rdpf, size_t &op_counter, address_t start,
+    bool use_expansion) : rdpf(rdpf), op_counter(op_counter),
+    use_expansion(use_expansion)
+{
+    depth = rdpf.depth();
+    // Prevent overflow of 1<<depth
+    if (depth < ADDRESS_MAX_BITS) {
+        indexmask = (address_t(1)<<depth)-1;
+    } else {
+        indexmask = ~0;
+    }
+    // Record that we haven't actually output the leaf for index start
+    // itself yet
+    nextindex = start;
+    if (use_expansion && rdpf.expansion.size()) {
+        // We just need to keep the counter, not compute anything
+        return;
+    }
+    path.resize(depth);
+    pathindex = start;
+    path[0] = rdpf.seed;
+    for (nbits_t i=1;i<depth;++i) {
+        bool dir = !!(pathindex & (address_t(1)<<(depth-i)));
+        path[i] = rdpf.descend(path[i-1], i-1, dir, op_counter);
+    }
+}
+
+DPFnode RDPF::Eval::next()
+{
+    if (use_expansion && rdpf.expansion.size()) {
+        // Just use the precomputed values
+        DPFnode leaf = rdpf.expansion[nextindex];
+        nextindex = (nextindex + 1) & indexmask;
+        return leaf;
+    }
+    // Invariant: in the first call to next(), nextindex = pathindex.
+    // Otherwise, nextindex = pathindex+1.
+    // Get the XOR of nextindex and pathindex, and strip the low bit.
+    // If nextindex and pathindex are equal, or pathindex is even
+    // and nextindex is the consecutive odd number, index_xor will be 0,
+    // indicating that we don't have to update the path, but just
+    // compute the appropriate leaf given by the low bit of nextindex.
+    //
+    // Otherwise, say for example pathindex is 010010111 and nextindex
+    // is 010011000.  Then their XOR is 000001111, and stripping the low
+    // bit yields 000001110, so how_many_1_bits will be 3.
+    // That indicates (typically) that path[depth-3] was a left child,
+    // and now we need to change it to a right child by descending right
+    // from path[depth-4], and then filling the path after that with
+    // left children.
+    //
+    // When we wrap around, however, index_xor will be 111111110 (after
+    // we strip the low bit), and how_many_1_bits will be depth-1, but
+    // the new top child (of the root seed) we have to compute will be a
+    // left, not a right, child.
+    uint64_t index_xor = (nextindex ^ pathindex) & ~1;
+    nbits_t how_many_1_bits = __builtin_popcount(index_xor);
+    if (how_many_1_bits > 0) {
+        // This will almost always be 1, unless we've just wrapped
+        // around from the right subtree back to the left, in which case
+        // it will be 0.
+        bool top_changed_bit =
+            nextindex & (address_t(1) << how_many_1_bits);
+        path[depth-how_many_1_bits] =
+            rdpf.descend(path[depth-how_many_1_bits-1],
+                depth-how_many_1_bits-1, top_changed_bit, op_counter);
+        for (nbits_t i = depth-how_many_1_bits; i < depth-1; ++i) {
+            path[i+1] = rdpf.descend(path[i], i, 0, op_counter);
+        }
+    }
+    DPFnode leaf = rdpf.descend(path[depth-1], depth-1, nextindex & 1,
+        op_counter);
+    pathindex = nextindex;
+    nextindex = (nextindex + 1) & indexmask;
+    return leaf;
+}
+
 // Construct three RDPFs of the given depth all with the same randomly
 // generated target index.
 RDPFTriple::RDPFTriple(MPCTIO &tio, yield_t &yield,

+ 26 - 0
rdpf.hpp

@@ -74,6 +74,32 @@ struct RDPF {
     // Expand the DPF if it's not already expanded
     void expand(size_t &op_counter);
 
+    // Streaming evaluation, to avoid taking up enough memory to store
+    // an entire evaluation
+    class Eval {
+        friend class RDPF; // So eval() can call the Eval constructor
+        const RDPF &rdpf;
+        size_t &op_counter;
+        bool use_expansion;
+        nbits_t depth;
+        address_t indexmask;
+        address_t pathindex;
+        address_t nextindex;
+        std::vector<DPFnode> path;
+        Eval(const RDPF &rdpf, size_t &op_counter, address_t start,
+            bool use_expansion);
+    public:
+        DPFnode next();
+    };
+
+    // Create an Eval object that will start its output at index start.
+    // It will wrap around to 0 when it hits 2^depth.  If use_expansion
+    // is true, then if the DPF has been expanded, just output values
+    // from that.  If use_expansion=false or if the DPF has not been
+    // expanded, compute the values on the fly.
+    Eval eval(address_t start, size_t &op_counter,
+        bool use_expansion=true) const;
+
     // Get the bit-shared unit vector entry from the leaf node
     inline RegBS unit_bs(DPFnode leaf) const {
         RegBS b;