rdpf.hpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. #ifndef __RDPF_HPP__
  2. #define __RDPF_HPP__
  3. #include <vector>
  4. #include <iostream>
  5. #include "mpcio.hpp"
  6. #include "coroutine.hpp"
  7. #include "types.hpp"
  8. #include "bitutils.hpp"
  9. #include "dpf.hpp"
  10. // DPFs for oblivious random accesses to memory. See dpf.hpp for the
  11. // differences between the different kinds of DPFs.
  12. template <nbits_t WIDTH>
  13. struct RDPF : public DPF {
  14. // The amount we have to scale the low words of the leaf values by
  15. // to get additive shares of a unit vector
  16. value_t unit_sum_inverse;
  17. // Additive share of the scaling value M_as such that the high words
  18. // of the leaf values for P0 and P1 add to M_as * e_{target}
  19. RegAS scaled_sum;
  20. // XOR share of the scaling value M_xs such that the high words
  21. // of the leaf values for P0 and P1 XOR to M_xs * e_{target}
  22. RegXS scaled_xor;
  23. // If we're saving the expansion, put it here
  24. std::vector<DPFnode> expansion;
  25. RDPF() {}
  26. // Construct a DPF with the given (XOR-shared) target location, and
  27. // of the given depth, to be used for random-access memory reads and
  28. // writes. The DPF is constructed collaboratively by P0 and P1,
  29. // with the server P2 helping by providing correlated randomness,
  30. // such as SelectTriples.
  31. //
  32. // Cost:
  33. // (2 DPFnode + 2 bytes)*depth + 1 word communication in
  34. // 2*depth + 1 messages
  35. // (2 DPFnode + 1 byte)*depth communication from P2 to each party
  36. // 2^{depth+1}-2 local AES operations for P0,P1
  37. // 0 local AES operations for P2
  38. RDPF(MPCTIO &tio, yield_t &yield,
  39. RegXS target, nbits_t depth, bool save_expansion = false);
  40. // Do we have a precomputed expansion?
  41. inline bool has_expansion() const { return expansion.size() > 0; }
  42. // Get an element of the expansion
  43. inline node get_expansion(address_t index) const {
  44. return expansion[index];
  45. }
  46. // The depth
  47. inline nbits_t depth() const { return cw.size(); }
  48. // Get the leaf node for the given input
  49. //
  50. // Cost: depth AES operations
  51. DPFnode leaf(address_t input, size_t &aes_ops) const;
  52. // Expand the DPF if it's not already expanded
  53. void expand(size_t &aes_ops);
  54. // Get the bit-shared unit vector entry from the leaf node
  55. inline RegBS unit_bs(DPFnode leaf) const {
  56. RegBS b;
  57. b.bshare = get_lsb(leaf);
  58. return b;
  59. }
  60. // Get the additive-shared unit vector entry from the leaf node
  61. inline RegAS unit_as(DPFnode leaf) const {
  62. RegAS a;
  63. value_t lowword = value_t(_mm_cvtsi128_si64x(leaf));
  64. if (whichhalf == 1) {
  65. lowword = -lowword;
  66. }
  67. a.ashare = lowword * unit_sum_inverse;
  68. return a;
  69. }
  70. // Get the XOR-shared scaled vector entry from the leaf node
  71. inline RegXS scaled_xs(DPFnode leaf) const {
  72. RegXS x;
  73. value_t highword =
  74. value_t(_mm_cvtsi128_si64x(_mm_srli_si128(leaf,8)));
  75. x.xshare = highword;
  76. return x;
  77. }
  78. // Get the additive-shared scaled vector entry from the leaf node
  79. inline RegAS scaled_as(DPFnode leaf) const {
  80. RegAS a;
  81. value_t highword =
  82. value_t(_mm_cvtsi128_si64x(_mm_srli_si128(leaf,8)));
  83. if (whichhalf == 1) {
  84. highword = -highword;
  85. }
  86. a.ashare = highword;
  87. return a;
  88. }
  89. };
  90. // Computational peers will generate triples of RDPFs with the _same_
  91. // random target for use in Duoram. They will each hold a share of the
  92. // target (neither knowing the complete target index). They will each
  93. // give one of the DPFs (not a matching pair) to the server, but not the
  94. // shares of the target index. So computational peers will hold a
  95. // RDPFTriple (which includes both an additive and an XOR share of the
  96. // target index), while the server will hold a RDPFPair (which does
  97. // not).
  98. template <nbits_t WIDTH>
  99. struct RDPFTriple {
  100. // The type of node triples
  101. using node = std::tuple<DPFnode, DPFnode, DPFnode>;
  102. RegAS as_target;
  103. RegXS xs_target;
  104. RDPF<WIDTH> dpf[3];
  105. // The depth
  106. inline nbits_t depth() const { return dpf[0].depth(); }
  107. // The seed
  108. inline node get_seed() const {
  109. return std::make_tuple(dpf[0].get_seed(), dpf[1].get_seed(),
  110. dpf[2].get_seed());
  111. }
  112. // Do we have a precomputed expansion?
  113. inline bool has_expansion() const {
  114. return dpf[0].expansion.size() > 0;
  115. }
  116. // Get an element of the expansion
  117. inline node get_expansion(address_t index) const {
  118. return std::make_tuple(dpf[0].get_expansion(index),
  119. dpf[1].get_expansion(index), dpf[2].get_expansion(index));
  120. }
  121. RDPFTriple() {}
  122. // Construct three RDPFs of the given depth all with the same
  123. // randomly generated target index.
  124. RDPFTriple(MPCTIO &tio, yield_t &yield,
  125. nbits_t depth, bool save_expansion = false);
  126. // Descend the three RDPFs in lock step
  127. node descend(const node &parent, nbits_t parentdepth,
  128. bit_t whichchild, size_t &aes_ops) const;
  129. // Overloaded versions of functions to get DPF components and
  130. // outputs so that the appropriate one can be selected with a
  131. // parameter
  132. inline void get_target(RegAS &target) const { target = as_target; }
  133. inline void get_target(RegXS &target) const { target = xs_target; }
  134. // Additive share of the scaling value M_as such that the high words
  135. // of the leaf values for P0 and P1 add to M_as * e_{target}
  136. inline void scaled_value(std::tuple<RegAS,RegAS,RegAS> &v) const {
  137. std::get<0>(v) = dpf[0].scaled_sum;
  138. std::get<1>(v) = dpf[1].scaled_sum;
  139. std::get<2>(v) = dpf[2].scaled_sum;
  140. }
  141. // XOR share of the scaling value M_xs such that the high words
  142. // of the leaf values for P0 and P1 XOR to M_xs * e_{target}
  143. inline void scaled_value(std::tuple<RegXS,RegXS,RegXS> &v) const {
  144. std::get<0>(v) = dpf[0].scaled_xor;
  145. std::get<1>(v) = dpf[1].scaled_xor;
  146. std::get<2>(v) = dpf[2].scaled_xor;
  147. }
  148. // Get the additive-shared unit vector entry from the leaf node
  149. inline void unit(std::tuple<RegAS,RegAS,RegAS> &u, node leaf) const {
  150. std::get<0>(u) = dpf[0].unit_as(std::get<0>(leaf));
  151. std::get<1>(u) = dpf[1].unit_as(std::get<1>(leaf));
  152. std::get<2>(u) = dpf[2].unit_as(std::get<2>(leaf));
  153. }
  154. // Get the bit-shared unit vector entry from the leaf node
  155. inline void unit(std::tuple<RegXS,RegXS,RegXS> &u, node leaf) const {
  156. std::get<0>(u) = dpf[0].unit_bs(std::get<0>(leaf));
  157. std::get<1>(u) = dpf[1].unit_bs(std::get<1>(leaf));
  158. std::get<2>(u) = dpf[2].unit_bs(std::get<2>(leaf));
  159. }
  160. // For any more complex entry type, that type will handle the conversion
  161. // for each DPF
  162. template <typename T>
  163. inline void unit(std::tuple<T,T,T> &u, node leaf) const {
  164. std::get<0>(u).unit(dpf[0], std::get<0>(leaf));
  165. std::get<1>(u).unit(dpf[1], std::get<1>(leaf));
  166. std::get<2>(u).unit(dpf[2], std::get<2>(leaf));
  167. }
  168. // Get the additive-shared scaled vector entry from the leaf node
  169. inline void scaled(std::tuple<RegAS,RegAS,RegAS> &s, node leaf) const {
  170. std::get<0>(s) = dpf[0].scaled_as(std::get<0>(leaf));
  171. std::get<1>(s) = dpf[1].scaled_as(std::get<1>(leaf));
  172. std::get<2>(s) = dpf[2].scaled_as(std::get<2>(leaf));
  173. }
  174. // Get the XOR-shared scaled vector entry from the leaf node
  175. inline void scaled(std::tuple<RegXS,RegXS,RegXS> &s, node leaf) const {
  176. std::get<0>(s) = dpf[0].scaled_xs(std::get<0>(leaf));
  177. std::get<1>(s) = dpf[1].scaled_xs(std::get<1>(leaf));
  178. std::get<2>(s) = dpf[2].scaled_xs(std::get<2>(leaf));
  179. }
  180. };
  181. template <nbits_t WIDTH>
  182. struct RDPFPair {
  183. // The type of node pairs
  184. using node = std::tuple<DPFnode, DPFnode>;
  185. RDPF<WIDTH> dpf[2];
  186. RDPFPair() {}
  187. // Create an RDPFPair from an RDPFTriple, keeping two of the RDPFs
  188. // and dropping one. This _moves_ the dpfs from the triple to the
  189. // pair, so the triple will no longer be valid after using this.
  190. // which0 and which1 indicate which of the dpfs to keep.
  191. RDPFPair(RDPFTriple<WIDTH> &&trip, int which0, int which1) {
  192. dpf[0] = std::move(trip.dpf[which0]);
  193. dpf[1] = std::move(trip.dpf[which1]);
  194. }
  195. // The depth
  196. inline nbits_t depth() const { return dpf[0].depth(); }
  197. // The seed
  198. inline node get_seed() const {
  199. return std::make_tuple(dpf[0].get_seed(), dpf[1].get_seed());
  200. }
  201. // Do we have a precomputed expansion?
  202. inline bool has_expansion() const {
  203. return dpf[0].expansion.size() > 0;
  204. }
  205. // Get an element of the expansion
  206. inline node get_expansion(address_t index) const {
  207. return std::make_tuple(dpf[0].get_expansion(index),
  208. dpf[1].get_expansion(index));
  209. }
  210. // Descend the two RDPFs in lock step
  211. node descend(const node &parent, nbits_t parentdepth,
  212. bit_t whichchild, size_t &aes_ops) const;
  213. // Overloaded versions of functions to get DPF components and
  214. // outputs so that the appropriate one can be selected with a
  215. // parameter
  216. // Additive share of the scaling value M_as such that the high words
  217. // of the leaf values for P0 and P1 add to M_as * e_{target}
  218. inline void scaled_value(std::tuple<RegAS,RegAS> &v) const {
  219. std::get<0>(v) = dpf[0].scaled_sum;
  220. std::get<1>(v) = dpf[1].scaled_sum;
  221. }
  222. // XOR share of the scaling value M_xs such that the high words
  223. // of the leaf values for P0 and P1 XOR to M_xs * e_{target}
  224. inline void scaled_value(std::tuple<RegXS,RegXS> &v) const {
  225. std::get<0>(v) = dpf[0].scaled_xor;
  226. std::get<1>(v) = dpf[1].scaled_xor;
  227. }
  228. // Get the additive-shared unit vector entry from the leaf node
  229. inline void unit(std::tuple<RegAS,RegAS> &u, node leaf) const {
  230. std::get<0>(u) = dpf[0].unit_as(std::get<0>(leaf));
  231. std::get<1>(u) = dpf[1].unit_as(std::get<1>(leaf));
  232. }
  233. // Get the bit-shared unit vector entry from the leaf node
  234. inline void unit(std::tuple<RegXS,RegXS> &u, node leaf) const {
  235. std::get<0>(u) = dpf[0].unit_bs(std::get<0>(leaf));
  236. std::get<1>(u) = dpf[1].unit_bs(std::get<1>(leaf));
  237. }
  238. // For any more complex entry type, that type will handle the conversion
  239. // for each DPF
  240. template <typename T>
  241. inline void unit(std::tuple<T,T> &u, node leaf) const {
  242. std::get<0>(u).unit(dpf[0], std::get<0>(leaf));
  243. std::get<1>(u).unit(dpf[1], std::get<1>(leaf));
  244. }
  245. // Get the additive-shared scaled vector entry from the leaf node
  246. inline void scaled(std::tuple<RegAS,RegAS> &s, node leaf) const {
  247. std::get<0>(s) = dpf[0].scaled_as(std::get<0>(leaf));
  248. std::get<1>(s) = dpf[1].scaled_as(std::get<1>(leaf));
  249. }
  250. // Get the XOR-shared scaled vector entry from the leaf node
  251. inline void scaled(std::tuple<RegXS,RegXS> &s, node leaf) const {
  252. std::get<0>(s) = dpf[0].scaled_xs(std::get<0>(leaf));
  253. std::get<1>(s) = dpf[1].scaled_xs(std::get<1>(leaf));
  254. }
  255. };
  256. // Streaming evaluation, to avoid taking up enough memory to store
  257. // an entire evaluation. T can be RDPF, RDPFPair, or RDPFTriple.
  258. template <typename T>
  259. class StreamEval {
  260. const T &rdpf;
  261. size_t &aes_ops;
  262. bool use_expansion;
  263. nbits_t depth;
  264. address_t counter_xor_offset;
  265. address_t indexmask;
  266. address_t pathindex;
  267. address_t nextindex;
  268. std::vector<typename T::node> path;
  269. public:
  270. // Create a StreamEval object that will start its output at index
  271. // start. It will wrap around to 0 when it hits 2^depth. If
  272. // use_expansion is true, then if the DPF has been expanded, just
  273. // output values from that. If use_expansion=false or if the DPF
  274. // has not been expanded, compute the values on the fly. If
  275. // xor_offset is non-zero, then the outputs are actually
  276. // DPF(start XOR xor_offset)
  277. // DPF((start+1) XOR xor_offset)
  278. // DPF((start+2) XOR xor_offset)
  279. // etc.
  280. StreamEval(const T &rdpf, address_t start,
  281. address_t xor_offset, size_t &aes_ops,
  282. bool use_expansion = true);
  283. // Get the next value (or tuple of values) from the evaluator
  284. typename T::node next();
  285. };
  286. // Parallel evaluation. This class launches a number of threads each
  287. // running a StreamEval to evaluate a chunk of the RDPF (or RDPFPair or
  288. // RDPFTriple), and accumulates the results within each chunk, and then
  289. // accumulates all the chunks together. T can be RDPF, RDPFPair, or
  290. // RDPFTriple.
  291. template <typename T>
  292. struct ParallelEval {
  293. const T &rdpf;
  294. address_t start;
  295. address_t xor_offset;
  296. address_t num_evals;
  297. int num_threads;
  298. size_t &aes_ops;
  299. // Create a Parallel evaluator that will evaluate the given rdpf at
  300. // DPF(start XOR xor_offset)
  301. // DPF((start+1) XOR xor_offset)
  302. // DPF((start+2) XOR xor_offset)
  303. // ...
  304. // DPF((start+num_evals-1) XOR xor_offset)
  305. // where all indices are taken mod 2^depth, and accumulate the
  306. // results into a single answer.
  307. ParallelEval(const T &rdpf, address_t start,
  308. address_t xor_offset, address_t num_evals,
  309. int num_threads, size_t &aes_ops) :
  310. rdpf(rdpf), start(start), xor_offset(xor_offset),
  311. num_evals(num_evals), num_threads(num_threads),
  312. aes_ops(aes_ops) {}
  313. // Run the parallel evaluator. The type V is the type of the
  314. // accumulator; init should be the "zero" value of the accumulator.
  315. // The type W (process) is a lambda type with the signature
  316. // (int, address_t, const T::node &) -> V
  317. // which will be called like this for each i from 0 to num_evals-1,
  318. // across num_thread threads:
  319. // value_i = process(t, i, DPF((start+i) XOR xor_offset))
  320. // t is the thread number (0 <= t < num_threads).
  321. // The resulting num_evals values will be combined using V's +=
  322. // operator, first accumulating the values within each thread
  323. // (starting with the init value), and then accumulating the totals
  324. // from each thread together (again starting with the init value):
  325. //
  326. // total = init
  327. // for each thread t:
  328. // accum_t = init
  329. // for each accum_i generated by thread t:
  330. // accum_t += value_i
  331. // total += accum_t
  332. template <typename V, typename W>
  333. inline V reduce(V init, W process);
  334. };
  335. #include "rdpf.tcc"
  336. #endif