Browse Source

More on the compare algorithm

Ian Goldberg 1 year ago
parent
commit
323b42ca84
4 changed files with 121 additions and 22 deletions
  1. 6 0
      bitutils.hpp
  2. 97 21
      cdpf.cpp
  3. 7 1
      cdpf.hpp
  4. 11 0
      types.hpp

+ 6 - 0
bitutils.hpp

@@ -58,4 +58,10 @@ inline __m128i set_lsb(const __m128i & block, const bool val = true)
     return _mm_or_si128(clear_lsb(block, 0b01), lsb128_mask[val ? 0b01 : 0b00]);
 }
 
+inline uint8_t parity(const __m128i & block)
+{
+    // TODO...
+    return 0;
+}
+
 #endif

+ 97 - 21
cdpf.cpp

@@ -6,7 +6,7 @@
 std::tuple<CDPF,CDPF> CDPF::generate(value_t target, size_t &aes_ops)
 {
     CDPF dpf0, dpf1;
-    nbits_t depth = VALUE_BITS - 7;
+    const nbits_t depth = VALUE_BITS - 7;
 
     // Pick two random seeds
     arc4random_buf(&dpf0.seed, sizeof(dpf0.seed));
@@ -163,10 +163,9 @@ DPFnode CDPF::leaf(value_t input, size_t &aes_ops) const
 // Note also that you can compare two RegAS values A and B by
 // passing A-B here.
 std::tuple<RegBS,RegBS,RegBS> CDPF::compare(MPCTIO &tio, yield_t &yield,
-    RegAS x)
+    RegAS x, size_t &aes_ops)
 {
     // Reconstruct S = target-x
-    RegBS gt, eq;
     // The server does nothing in this protocol
     if (tio.player() < 2) {
         RegAS S_share = as_target - x;
@@ -176,25 +175,102 @@ std::tuple<RegBS,RegBS,RegBS> CDPF::compare(MPCTIO &tio, yield_t &yield,
         tio.iostream_peer() >> peer_S_share;
         value_t S = S_share.ashare + peer_S_share.ashare;
 
-        // Now we're going to simultaneously descend the DPF tree for
-        // the values S and T = S + 2^63.  Note that the 1 values of V
-        // (see the explanation of the algorithm in cdpf.hpp) are those
-        // values _strictly_ larger than S and smaller than T (noting
-        // they can "wrap around" 2^64).  In level 1 of the tree, the
-        // paths to S and T will necessarily be at the two different
-        // children of the root seed, but they could be in either order.
-        // From then on, they will proceed in lockstep, either both
-        // going left, or both going right.  If they both go left, we
-        // also compute the right sibling on the S path, and add it to
-        // the gt flag.  If they both go right, we also compute the left
-        // sibling on the T path, and add it to the gt flag.  When we
-        // hit the leaves, the gt flag will account for all of the
-        // complete leaf nodes strictly greater than S and strictly less
-        // than T.  Then we just have to pull out the parity of the
-        // appropriate bits in the two leaf nodes containing S and T
-        // respectively to complete the computation of gt, and also to
-        // get the single bit eq.
+        // After that one single-word exchange, the rest of this
+        // algorithm is entirely a local computation.
+        return compare(S, aes_ops);
     }
+    // The server gets three shares of 0 (which is not a valid output
+    // for the computational players)
+    RegBS lt, eq, gt;
+    return std::make_tuple(lt, eq, gt);
+}
+
+// You can call this version directly if you already have S = target-x
+// reconstructed.  This routine is entirely local; no communication
+// is needed.
+std::tuple<RegBS,RegBS,RegBS> CDPF::compare(value_t S, size_t &aes_ops)
+{
+    RegBS gt, eq;
+
+    // Now we're going to simultaneously descend the DPF tree for
+    // the values S and T = S + 2^63.  Note that the 1 values of V
+    // (see the explanation of the algorithm in cdpf.hpp) are those
+    // values _strictly_ larger than S and smaller than T (noting
+    // they can "wrap around" 2^64).  In level 1 of the tree, the
+    // paths to S and T will necessarily be at the two different
+    // children of the root seed, but they could be in either order.
+    // From then on, they will proceed in lockstep, either both
+    // going left, or both going right.  If they both go left, we
+    // also compute the right sibling on the S path, and add it to
+    // the gt flag.  If they both go right, we also compute the left
+    // sibling on the T path, and add it to the gt flag.  When we
+    // hit the leaves, the gt flag will account for all of the
+    // complete leaf nodes strictly greater than S and strictly less
+    // than T.  Then we just have to pull out the parity of the
+    // appropriate bits in the two leaf nodes containing S and T
+    // respectively to complete the computation of gt, and also to
+    // get the single bit eq.
+
+    // Invariant: Snode is the node on level curlevel on the path to
+    // S, and Tnode is the node on level curlevel on the path to
+    // T = S + 2^63.
+    nbits_t curlevel = 0;
+    const nbits_t depth = VALUE_BITS - 7;
+    DPFnode Snode = seed;
+    DPFnode Tnode = seed;
+
+    // The top level is the only place where the paths to S and T go
+    // in different directions.
+    bool Sdir = !!(S & (value_t(1)<<63));
+    Snode = descend(Snode, curlevel, Sdir, aes_ops);
+    Tnode = descend(Tnode, curlevel, !Sdir, aes_ops);
+    curlevel = 1;
+
+    // The last level is special
+    while(curlevel < depth-1) {
+        Sdir = !!(S & (value_t(1)<<((depth+7)-curlevel-1)));
+        if (Sdir == 0) {
+            // They're both going left; include the right child of
+            // Snode in the gt computation
+            DPFnode gtnode = descend(Snode, curlevel, 1, aes_ops);
+            gt ^= get_lsb(gtnode);
+        } else {
+            // They're both going right; include the left child of
+            // Tnode in the gt computation
+            DPFnode gtnode = descend(Tnode, curlevel, 0, aes_ops);
+            gt ^= get_lsb(gtnode);
+        }
+        Snode = descend(Snode, curlevel, Sdir, aes_ops);
+        Tnode = descend(Tnode, curlevel, Sdir, aes_ops);
+        ++curlevel;
+    }
+    // Now we're at the level just above the leaves.  If we go left,
+    // include *all* the bits (not just the low bit) of the right
+    // child of Snode, and if we go right, include all the bits of
+    // the left child of Tnode.
+    Sdir = !!(S & (value_t(1)<<((depth+7)-curlevel-1)));
+    if (Sdir == 0) {
+        // They're both going left; include the right child of
+        // Snode in the gt computation
+        DPFnode gtnode = descend_to_leaf(Snode, 1, aes_ops);
+        gt ^= parity(gtnode);
+    } else {
+        // They're both going right; include the left child of
+        // Tnode in the gt computation
+        DPFnode gtnode = descend_to_leaf(Tnode, 0, aes_ops);
+        gt ^= parity(gtnode);
+    }
+    Snode = descend_to_leaf(Snode, Sdir, aes_ops);
+    Tnode = descend_to_leaf(Tnode, Sdir, aes_ops);
+    ++curlevel;
+
+    // Now Snode and Tnode are the leaves containing S and T
+    // respectively.  Pull out the bit in Snode for S itself into eq,
+    // and all the higher bits into gt.  Also pull out the bits strictly
+    // below that for T in Tnode into gt.
+
+    // TODO...
+
     // Once we have gt and eq (which cannot both be 1), lt is just 1
     // exactly if they're both 0.
     RegBS lt;

+ 7 - 1
cdpf.hpp

@@ -118,7 +118,13 @@ struct CDPF : public DPF {
     // Note also that you can compare two RegAS values A and B by
     // passing A-B here.
     std::tuple<RegBS,RegBS,RegBS> compare(MPCTIO &tio, yield_t &yield,
-        RegAS x);
+        RegAS x, size_t &aes_ops);
+
+    // You can call this version directly if you already have S = target-x
+    // reconstructed.  This routine is entirely local; no communication
+    // is needed.
+    std::tuple<RegBS,RegBS,RegBS> compare(value_t S, size_t &aes_ops);
+
 };
 
 // Descend from the parent of a leaf node to the leaf node

+ 11 - 0
types.hpp

@@ -138,6 +138,17 @@ struct RegBS {
         res ^= rhs;
         return res;
     }
+
+    inline RegBS &operator^=(const bit_t &rhs) {
+        this->bshare ^= rhs;
+        return *this;
+    }
+
+    inline RegBS operator^(const bit_t &rhs) const {
+        RegBS res = *this;
+        res ^= rhs;
+        return res;
+    }
 };
 
 // The type of a register holding an XOR share of a value