|
@@ -33,90 +33,6 @@ public:
|
|
|
typename T::node next();
|
|
|
};
|
|
|
|
|
|
-// Create a StreamEval 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.
|
|
|
-template <typename T>
|
|
|
-StreamEval<T>::StreamEval(const T &rdpf, address_t start,
|
|
|
- size_t &op_counter, 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.has_expansion()) {
|
|
|
- // We just need to keep the counter, not compute anything
|
|
|
- return;
|
|
|
- }
|
|
|
- path.resize(depth);
|
|
|
- pathindex = start;
|
|
|
- path[0] = rdpf.get_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);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-template <typename T>
|
|
|
-typename T::node StreamEval<T>::next()
|
|
|
-{
|
|
|
- if (use_expansion && rdpf.has_expansion()) {
|
|
|
- // Just use the precomputed values
|
|
|
- typename T::node leaf = rdpf.get_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);
|
|
|
- }
|
|
|
- }
|
|
|
- typename T::node leaf = rdpf.descend(path[depth-1], depth-1,
|
|
|
- nextindex & 1, op_counter);
|
|
|
- pathindex = nextindex;
|
|
|
- nextindex = (nextindex + 1) & indexmask;
|
|
|
- return leaf;
|
|
|
-}
|
|
|
-
|
|
|
struct RDPF {
|
|
|
// The type of nodes
|
|
|
using node = DPFnode;
|
|
@@ -265,88 +181,6 @@ struct RDPF {
|
|
|
|
|
|
};
|
|
|
|
|
|
-// I/O for RDPFs
|
|
|
-
|
|
|
-template <typename T>
|
|
|
-T& operator>>(T &is, RDPF &rdpf)
|
|
|
-{
|
|
|
- is.read((char *)&rdpf.seed, sizeof(rdpf.seed));
|
|
|
- uint8_t depth;
|
|
|
- // The whichhalf bit is the high bit of depth
|
|
|
- is.read((char *)&depth, sizeof(depth));
|
|
|
- rdpf.whichhalf = !!(depth & 0x80);
|
|
|
- depth &= 0x7f;
|
|
|
- bool read_expanded = false;
|
|
|
- if (depth > 64) {
|
|
|
- read_expanded = true;
|
|
|
- depth -= 64;
|
|
|
- }
|
|
|
- assert(depth <= ADDRESS_MAX_BITS);
|
|
|
- rdpf.cw.clear();
|
|
|
- for (uint8_t i=0; i<depth; ++i) {
|
|
|
- DPFnode cw;
|
|
|
- is.read((char *)&cw, sizeof(cw));
|
|
|
- rdpf.cw.push_back(cw);
|
|
|
- }
|
|
|
- if (read_expanded) {
|
|
|
- rdpf.expansion.resize(1<<depth);
|
|
|
- is.read((char *)rdpf.expansion.data(),
|
|
|
- sizeof(rdpf.expansion[0])<<depth);
|
|
|
- }
|
|
|
- value_t cfbits = 0;
|
|
|
- is.read((char *)&cfbits, BITBYTES(depth));
|
|
|
- rdpf.cfbits = cfbits;
|
|
|
- is.read((char *)&rdpf.unit_sum_inverse, sizeof(rdpf.unit_sum_inverse));
|
|
|
- is.read((char *)&rdpf.scaled_sum, sizeof(rdpf.scaled_sum));
|
|
|
- is.read((char *)&rdpf.scaled_xor, sizeof(rdpf.scaled_xor));
|
|
|
-
|
|
|
- return is;
|
|
|
-}
|
|
|
-
|
|
|
-// Write the DPF to the output stream. If expanded=true, then include
|
|
|
-// the expansion _if_ the DPF is itself already expanded. You can use
|
|
|
-// this to write DPFs to files.
|
|
|
-template <typename T>
|
|
|
-T& write_maybe_expanded(T &os, const RDPF &rdpf,
|
|
|
- bool expanded = true)
|
|
|
-{
|
|
|
- os.write((const char *)&rdpf.seed, sizeof(rdpf.seed));
|
|
|
- uint8_t depth = rdpf.cw.size();
|
|
|
- assert(depth <= ADDRESS_MAX_BITS);
|
|
|
- // The whichhalf bit is the high bit of depth
|
|
|
- // If we're writing an expansion, add 64 to depth as well
|
|
|
- uint8_t whichhalf_and_depth = depth |
|
|
|
- (uint8_t(rdpf.whichhalf)<<7);
|
|
|
- bool write_expansion = false;
|
|
|
- if (expanded && rdpf.expansion.size() == (size_t(1)<<depth)) {
|
|
|
- write_expansion = true;
|
|
|
- whichhalf_and_depth += 64;
|
|
|
- }
|
|
|
- os.write((const char *)&whichhalf_and_depth,
|
|
|
- sizeof(whichhalf_and_depth));
|
|
|
- for (uint8_t i=0; i<depth; ++i) {
|
|
|
- os.write((const char *)&rdpf.cw[i], sizeof(rdpf.cw[i]));
|
|
|
- }
|
|
|
- if (write_expansion) {
|
|
|
- os.write((const char *)rdpf.expansion.data(),
|
|
|
- sizeof(rdpf.expansion[0])<<depth);
|
|
|
- }
|
|
|
- os.write((const char *)&rdpf.cfbits, BITBYTES(depth));
|
|
|
- os.write((const char *)&rdpf.unit_sum_inverse, sizeof(rdpf.unit_sum_inverse));
|
|
|
- os.write((const char *)&rdpf.scaled_sum, sizeof(rdpf.scaled_sum));
|
|
|
- os.write((const char *)&rdpf.scaled_xor, sizeof(rdpf.scaled_xor));
|
|
|
-
|
|
|
- return os;
|
|
|
-}
|
|
|
-
|
|
|
-// The ordinary << version never writes the expansion, since this is
|
|
|
-// what we use to send DPFs over the network.
|
|
|
-template <typename T>
|
|
|
-T& operator<<(T &os, const RDPF &rdpf)
|
|
|
-{
|
|
|
- return write_maybe_expanded(os, rdpf, false);
|
|
|
-}
|
|
|
-
|
|
|
// Computational peers will generate triples of RDPFs with the _same_
|
|
|
// random target for use in Duoram. They will each hold a share of the
|
|
|
// target (neither knowing the complete target index). They will each
|
|
@@ -396,34 +230,6 @@ struct RDPFTriple {
|
|
|
bit_t whichchild, size_t &op_counter) const;
|
|
|
};
|
|
|
|
|
|
-// I/O for RDPF Triples
|
|
|
-
|
|
|
-// We never write RDPFTriples over the network, so always write
|
|
|
-// the DPF expansions if they're available.
|
|
|
-template <typename T>
|
|
|
-T& operator<<(T &os, const RDPFTriple &rdpftrip)
|
|
|
-{
|
|
|
- write_maybe_expanded(os, rdpftrip.dpf[0], true);
|
|
|
- write_maybe_expanded(os, rdpftrip.dpf[1], true);
|
|
|
- write_maybe_expanded(os, rdpftrip.dpf[2], true);
|
|
|
- nbits_t depth = rdpftrip.dpf[0].depth();
|
|
|
- os.write((const char *)&rdpftrip.as_target.ashare, BITBYTES(depth));
|
|
|
- os.write((const char *)&rdpftrip.xs_target.xshare, BITBYTES(depth));
|
|
|
- return os;
|
|
|
-}
|
|
|
-
|
|
|
-template <typename T>
|
|
|
-T& operator>>(T &is, RDPFTriple &rdpftrip)
|
|
|
-{
|
|
|
- is >> rdpftrip.dpf[0] >> rdpftrip.dpf[1] >> rdpftrip.dpf[2];
|
|
|
- nbits_t depth = rdpftrip.dpf[0].depth();
|
|
|
- rdpftrip.as_target.ashare = 0;
|
|
|
- is.read((char *)&rdpftrip.as_target.ashare, BITBYTES(depth));
|
|
|
- rdpftrip.xs_target.xshare = 0;
|
|
|
- is.read((char *)&rdpftrip.xs_target.xshare, BITBYTES(depth));
|
|
|
- return is;
|
|
|
-}
|
|
|
-
|
|
|
struct RDPFPair {
|
|
|
// The type of node pairs
|
|
|
using node = std::tuple<DPFnode, DPFnode>;
|
|
@@ -454,23 +260,6 @@ struct RDPFPair {
|
|
|
bit_t whichchild, size_t &op_counter) const;
|
|
|
};
|
|
|
|
|
|
-// I/O for RDPF Pairs
|
|
|
-
|
|
|
-// We never write RDPFPairs over the network, so always write
|
|
|
-// the DPF expansions if they're available.
|
|
|
-template <typename T>
|
|
|
-T& operator<<(T &os, const RDPFPair &rdpfpair)
|
|
|
-{
|
|
|
- write_maybe_expanded(os, rdpfpair.dpf[0], true);
|
|
|
- write_maybe_expanded(os, rdpfpair.dpf[1], true);
|
|
|
- return os;
|
|
|
-}
|
|
|
-
|
|
|
-template <typename T>
|
|
|
-T& operator>>(T &is, RDPFPair &rdpfpair)
|
|
|
-{
|
|
|
- is >> rdpfpair.dpf[0] >> rdpfpair.dpf[1];
|
|
|
- return is;
|
|
|
-}
|
|
|
+#include "rdpf.tcc"
|
|
|
|
|
|
#endif
|