duoram.hpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. #ifndef __DUORAM_HPP__
  2. #define __DUORAM_HPP__
  3. #include "types.hpp"
  4. #include "mpcio.hpp"
  5. #include "coroutine.hpp"
  6. // Implementation of the 3-party protocols described in:
  7. // Adithya Vadapalli, Ryan Henry, Ian Goldberg, "Duoram: A
  8. // Bandwidth-Efficient Distributed ORAM for 2- and 3-Party Computation".
  9. // A Duoram object is like physical memory: it's just a flat address
  10. // space, and you can't access it directly. Instead, you need to access
  11. // it through a "Shape", such as Flat, Tree, Path, etc. Shapes can be
  12. // nested, so you can have a Path of a Subtree of a Tree sitting on the
  13. // base Duoram. Each Shape's parent must remain in scope (references to
  14. // it must remain valid) for the lifetime of the child Shapre. Each
  15. // shape is bound to a context, which is a thread-specific MPCTIO and a
  16. // coroutine-specific yield_t. If you launch new threads and/or
  17. // coroutines, you'll need to make a copy of the current Shape for your
  18. // new context, and call context() on it. Be sure not to call context()
  19. // on a Shape shared with other threads or coroutines.
  20. // This is templated, because you can have a Duoram of additively shared
  21. // (RegAS) or XOR shared (RegXS) elements, or std::arrays of those to
  22. // get "wide" memory cells.
  23. // The initial implementation is focused on additive shares.
  24. template <typename T>
  25. class Duoram {
  26. // The computational parties have three vectors: the share of the
  27. // database itself, the party's own blinding factors for its
  28. // database share, and the _other_ computational party's blinded
  29. // database share (its database share plus its blind).
  30. // The player number (0 and 1 for the computational parties and 2
  31. // for the server) and the size of the Duoram
  32. int player;
  33. size_t oram_size;
  34. // The server has two vectors: a copy of each computational party's
  35. // blind. The database vector will remain empty.
  36. std::vector<T> database; // computational parties only
  37. std::vector<T> blind; // computational parties use this name
  38. std::vector<T> &p0_blind; // server uses this name
  39. std::vector<T> peer_blinded_db; // computational parties
  40. std::vector<T> &p1_blind; // server
  41. public:
  42. // The type of this Duoram
  43. using type = T;
  44. // The different Shapes are subclasses of this inner class
  45. class Shape;
  46. // These are the different Shapes that exist
  47. class Flat;
  48. // Pass the player number and desired size
  49. Duoram(int player, size_t size);
  50. // Get the size
  51. inline size_t size() { return oram_size; }
  52. // Get the basic Flat shape for this Duoram
  53. Flat flat(MPCTIO &tio, yield_t &yield, size_t start = 0,
  54. size_t len = 0) {
  55. return Flat(*this, tio, yield, start, len);
  56. }
  57. // For debugging; print the contents of the Duoram to stdout
  58. void dump() const;
  59. };
  60. // The parent class of all Shapes. This is an abstract class that
  61. // cannot itself be instantiated.
  62. template <typename T>
  63. class Duoram<T>::Shape {
  64. // Subclasses should be able to access _other_ Shapes' indexmap
  65. friend class Flat;
  66. // When you index into a shape (A[x]), you get one of these types,
  67. // depending on the type of x (the index), _not_ on the type T (the
  68. // underlying type of the Duoram). That is, you can have an
  69. // additive-shared index (x) into an XOR-shared database (T), for
  70. // example.
  71. // When x is additively or XOR shared
  72. // U is the sharing type of the indices, while T is the sharing type
  73. // of the data in the database. If we are referencing an entire
  74. // entry of type T, then the field type FT will equal T, and the
  75. // field selector type FST will be nullopt_t. If we are referencing
  76. // a particular field of T, then FT will be the type of the field
  77. // (RegAS or RegXS) and FST will be a pointer-to-member T::* type
  78. // pointing to that field. Sh is the specific Shape subtype used to
  79. // create the MemRefS.
  80. template <typename U, typename FT, typename FST, typename Sh>
  81. class MemRefS;
  82. // When x is unshared explicit value. FT and FST are as above.
  83. template <typename FT, typename FST>
  84. class MemRefExpl;
  85. // When x is a vector or array of values of type U, used to denote a
  86. // collection of independent memory operations that can be performed
  87. // simultaneously. Sh is the specific Shape subtype used to create
  88. // the MemRefInd.
  89. template <typename U, typename Sh>
  90. class MemRefInd;
  91. protected:
  92. // A reference to the parent shape. As with ".." in the root
  93. // directory of a filesystem, the topmost shape is indicated by
  94. // having parent = *this.
  95. const Shape &parent;
  96. // A reference to the backing physical storage
  97. Duoram &duoram;
  98. // The size of this shape
  99. size_t shape_size;
  100. // The number of bits needed to address this shape (the number of
  101. // bits in shape_size-1)
  102. nbits_t addr_size;
  103. // And a mask with the low addr_size bits set
  104. address_t addr_mask;
  105. // The Shape's context (MPCTIO and yield_t)
  106. MPCTIO &tio;
  107. yield_t &yield;
  108. // If you enable explicit-only mode, sending updates of your blind
  109. // to the server and of your blinded database to your peer will be
  110. // temporarily disabled. When you disable it (which will happen
  111. // automatically at the next ORAM read or write, or you can do it
  112. // explicitly), new random blinds will be chosen for the whole
  113. // Shape, and the blinds sent to the server, and the blinded
  114. // database sent to the peer.
  115. bool explicitmode;
  116. // A function to set the shape_size and compute addr_size and
  117. // addr_mask
  118. void set_shape_size(size_t sz);
  119. // We need a constructor because we hold non-static references; this
  120. // constructor is called by the subclass constructors
  121. Shape(const Shape &parent, Duoram &duoram, MPCTIO &tio,
  122. yield_t &yield) : parent(parent), duoram(duoram), shape_size(0),
  123. tio(tio), yield(yield), explicitmode(false) {}
  124. // Copy the given Shape except for the tio and yield
  125. Shape(const Shape &copy_from, MPCTIO &tio, yield_t &yield) :
  126. parent(copy_from.parent), duoram(copy_from.duoram),
  127. shape_size(copy_from.shape_size),
  128. addr_size(copy_from.addr_size), addr_mask(copy_from.addr_mask),
  129. tio(tio), yield(yield),
  130. explicitmode(copy_from.explicitmode) {}
  131. // The index-mapping function. Input the index relative to this
  132. // shape, and output the corresponding physical address. The
  133. // strategy is to map the index relative to this shape to the index
  134. // relative to the parent shape, call the parent's indexmap function
  135. // on that (unless this is the topmost shape), and return what it
  136. // returns. If this is the topmost shape, just return what you
  137. // would have passed to the parent's indexmap.
  138. //
  139. // This is a pure virtual function; all subclasses of Shape must
  140. // implement it, and of course Shape itself therefore cannot be
  141. // instantiated.
  142. virtual size_t indexmap(size_t idx) const = 0;
  143. // Get a pair (for the server) of references to the underlying
  144. // Duoram entries at share virtual index idx. (That is, it gets
  145. // duoram.p0_blind[indexmap(idx)], etc.)
  146. inline std::tuple<T&,T&> get_server(size_t idx,
  147. std::nullopt_t null = std::nullopt) const {
  148. size_t physaddr = indexmap(idx);
  149. return std::tie(
  150. duoram.p0_blind[physaddr],
  151. duoram.p1_blind[physaddr]);
  152. }
  153. // Get a triple (for the computational players) of references to the
  154. // underlying Duoram entries at share virtual index idx. (That is,
  155. // it gets duoram.database[indexmap(idx)], etc.)
  156. inline std::tuple<T&,T&,T&> get_comp(size_t idx,
  157. std::nullopt_t null = std::nullopt) const {
  158. size_t physaddr = indexmap(idx);
  159. return std::tie(
  160. duoram.database[physaddr],
  161. duoram.blind[physaddr],
  162. duoram.peer_blinded_db[physaddr]);
  163. }
  164. // Get a pair (for the server) of references to a particular field
  165. // of the underlying Duoram entries at share virtual index idx.
  166. // (That is, it gets duoram.p0_blind[indexmap(idx)].field, etc.)
  167. template <typename FT>
  168. inline std::tuple<FT&,FT&> get_server(size_t idx, FT T::*field) const {
  169. size_t physaddr = indexmap(idx);
  170. return std::tie(
  171. duoram.p0_blind[physaddr].*field,
  172. duoram.p1_blind[physaddr].*field);
  173. }
  174. // Get a triple (for the computational players) of references to a
  175. // particular field to the underlying Duoram entries at share
  176. // virtual index idx. (That is, it gets
  177. // duoram.database[indexmap(idx)].field, etc.)
  178. template <typename FT>
  179. inline std::tuple<FT&,FT&,FT&> get_comp(size_t idx, FT T::*field) const {
  180. size_t physaddr = indexmap(idx);
  181. return std::tie(
  182. duoram.database[physaddr].*field,
  183. duoram.blind[physaddr].*field,
  184. duoram.peer_blinded_db[physaddr].*field);
  185. }
  186. public:
  187. // Get the size
  188. inline size_t size() { return shape_size; }
  189. // Enable or disable explicit-only mode. Only using [] with
  190. // explicit (address_t) indices are allowed in this mode. Using []
  191. // with RegAS or RegXS indices will automatically turn off this
  192. // mode, or you can turn it off explicitly. In explicit-only mode,
  193. // updates to the memory in the Shape will not induce communication
  194. // to the server or peer, but when it turns off, a message of the
  195. // size of the entire Shape will be sent to each of the server and
  196. // the peer. This is useful if you're going to be doing multiple
  197. // explicit writes to every element of the Shape before you do your
  198. // next oblivious read or write. Bitonic sort is a prime example.
  199. void explicitonly(bool enable);
  200. // For debugging or checking your answers (using this in general is
  201. // of course insecure)
  202. // This one reconstructs the whole database
  203. std::vector<T> reconstruct() const;
  204. // This one reconstructs a single database value
  205. T reconstruct(const T& share) const;
  206. };
  207. // The most basic shape is Flat. It is almost always the topmost shape,
  208. // and serves to provide MPCTIO and yield_t context to a Duoram without
  209. // changing the indices or size (but can specify a subrange if desired).
  210. template <typename T>
  211. class Duoram<T>::Flat : public Duoram<T>::Shape {
  212. // If this is a subrange, start may be non-0, but it's usually 0
  213. size_t start;
  214. size_t len;
  215. inline size_t indexmap(size_t idx) const {
  216. size_t paridx = idx + start;
  217. if (&(this->parent) == this) {
  218. return paridx;
  219. } else {
  220. return this->parent.indexmap(paridx);
  221. }
  222. }
  223. // Internal function to aid bitonic_sort
  224. void butterfly(address_t start, nbits_t depth, bool dir);
  225. public:
  226. // Constructor. len=0 means the maximum size (the parent's size
  227. // minus start).
  228. Flat(Duoram &duoram, MPCTIO &tio, yield_t &yield, size_t start = 0,
  229. size_t len = 0);
  230. // Copy the given Flat except for the tio and yield
  231. Flat(const Flat &copy_from, MPCTIO &tio, yield_t &yield) :
  232. Shape(copy_from, tio, yield), start(copy_from.start),
  233. len(copy_from.len) {}
  234. // Update the context (MPCTIO and yield if you've started a new
  235. // thread, or just yield if you've started a new coroutine in the
  236. // same thread). Returns a new Shape with an updated context.
  237. Flat context(MPCTIO &new_tio, yield_t &new_yield) const {
  238. return Flat(*this, new_tio, new_yield);
  239. }
  240. Flat context(yield_t &new_yield) const {
  241. return Flat(*this, this->tio, new_yield);
  242. }
  243. // Index into this Flat in various ways
  244. typename Duoram::Shape::template MemRefS<RegAS,T,std::nullopt_t,Flat>
  245. operator[](const RegAS &idx) {
  246. typename Duoram<T>::Shape::
  247. template MemRefS<RegAS,T,std::nullopt_t,Flat>
  248. res(*this, idx, std::nullopt);
  249. return res;
  250. }
  251. typename Duoram::Shape::template MemRefS<RegXS,T,std::nullopt_t,Flat>
  252. operator[](const RegXS &idx) {
  253. typename Duoram<T>::Shape::
  254. template MemRefS<RegXS,T,std::nullopt_t,Flat>
  255. res(*this, idx, std::nullopt);
  256. return res;
  257. }
  258. typename Duoram::Shape::template MemRefExpl<T,std::nullopt_t>
  259. operator[](address_t idx) {
  260. typename Duoram<T>::Shape::
  261. template MemRefExpl<T,std::nullopt_t>
  262. res(*this, idx, std::nullopt);
  263. return res;
  264. }
  265. template <typename U>
  266. Duoram::Shape::MemRefInd<U, Flat>
  267. operator[](const std::vector<U> &indcs) {
  268. typename Duoram<T>::Shape::
  269. template MemRefInd<U,Flat>
  270. res(*this, indcs);
  271. return res;
  272. }
  273. template <typename U, size_t N>
  274. Duoram::Shape::MemRefInd<U, Flat>
  275. operator[](const std::array<U,N> &indcs) {
  276. typename Duoram<T>::Shape::
  277. template MemRefInd<U,Flat>
  278. res(*this, indcs);
  279. return res;
  280. }
  281. // Oblivious sort the elements indexed by the two given indices.
  282. // Without reconstructing the values, if dir=0, this[idx1] will
  283. // become a share of the smaller of the reconstructed values, and
  284. // this[idx2] will become a share of the larger. If dir=1, it's the
  285. // other way around.
  286. //
  287. // Note: this only works for additively shared databases
  288. template<typename U,typename V>
  289. void osort(const U &idx1, const V &idx2, bool dir=0);
  290. // Bitonic sort the elements from start to start+(1<<depth)-1, in
  291. // increasing order if dir=0 or decreasing order if dir=1. Note that
  292. // the elements must be at most 63 bits long each for the notion of
  293. // ">" to make consistent sense.
  294. void bitonic_sort(address_t start, nbits_t depth, bool dir=0);
  295. // Assuming the memory is already sorted, do an oblivious binary
  296. // search for the largest index containing the value at most the
  297. // given one. (The answer will be 0 if all of the memory elements
  298. // are greate than the target.) This Flat must be a power of 2 size.
  299. // Only available for additive shared databases for now.
  300. RegAS obliv_binary_search(RegAS &target);
  301. };
  302. // An additive or XOR shared memory reference. You get one of these
  303. // from a Shape A and an additively shared RegAS index x, or an XOR
  304. // shared RegXS index x, with A[x]. Then you perform operations on this
  305. // object, which do the Duoram operations. As above, T is the sharing
  306. // type of the data in the database, while U is the sharing type of the
  307. // index used to create this memory reference. If we are referencing an
  308. // entire entry of type T, then the field type FT will equal T, and the
  309. // field selector type FST will be nullopt_t. If we are referencing a
  310. // particular field of T, then FT will be the type of the field (RegAS
  311. // or RegXS) and FST will be a pointer-to-member T::* type pointing to
  312. // that field. Sh is the specific Shape subtype used to create the
  313. // MemRefS.
  314. template <typename T>
  315. template <typename U, typename FT, typename FST, typename Sh>
  316. class Duoram<T>::Shape::MemRefS {
  317. Sh &shape;
  318. U idx;
  319. FST fieldsel;
  320. private:
  321. // Oblivious update to a shared index of Duoram memory, only for
  322. // FT = RegAS or RegXS
  323. MemRefS<U,FT,FST,Sh> &oram_update(const FT& M, const prac_template_true&);
  324. // Oblivious update to a shared index of Duoram memory, for
  325. // FT not RegAS or RegXS
  326. MemRefS<U,FT,FST,Sh> &oram_update(const FT& M, const prac_template_false&);
  327. public:
  328. MemRefS<U,FT,FST,Sh>(Sh &shape, const U &idx, FST fieldsel) :
  329. shape(shape), idx(idx), fieldsel(fieldsel) {}
  330. // Create a MemRefExpl for accessing a partcular field of T
  331. template <typename SFT>
  332. MemRefS<U,SFT,SFT T::*,Sh> field(SFT T::*subfieldsel) {
  333. auto res = MemRefS<U,SFT,SFT T::*,Sh>(this->shape, idx, subfieldsel);
  334. return res;
  335. }
  336. // Oblivious read from a shared index of Duoram memory
  337. operator FT();
  338. // Oblivious update to a shared index of Duoram memory
  339. MemRefS<U,FT,FST,Sh> &operator+=(const FT& M);
  340. // Oblivious write to a shared index of Duoram memory
  341. MemRefS<U,FT,FST,Sh> &operator=(const FT& M);
  342. };
  343. // An explicit memory reference. You get one of these from a Shape A
  344. // and an address_t index x with A[x]. Then you perform operations on
  345. // this object, which update the Duoram state without performing Duoram
  346. // operations. If we are referencing an entire entry of type T, then
  347. // the field type FT will equal T, and the field selector type FST will
  348. // be nullopt_t. If we are referencing a particular field of T, then FT
  349. // will be the type of the field (RegAS or RegXS) and FST will be a
  350. // pointer-to-member T::* type pointing to that field.
  351. template <typename T> template <typename FT, typename FST>
  352. class Duoram<T>::Shape::MemRefExpl {
  353. Shape &shape;
  354. address_t idx;
  355. FST fieldsel;
  356. public:
  357. MemRefExpl(Shape &shape, address_t idx, FST fieldsel) :
  358. shape(shape), idx(idx), fieldsel(fieldsel) {}
  359. // Create a MemRefExpl for accessing a partcular field of T
  360. template <typename SFT>
  361. MemRefExpl<SFT,SFT T::*> field(SFT T::*subfieldsel) {
  362. auto res = MemRefExpl<SFT,SFT T::*>(this->shape, idx, subfieldsel);
  363. return res;
  364. }
  365. // Explicit read from a given index of Duoram memory
  366. operator FT();
  367. // Explicit update to a given index of Duoram memory
  368. MemRefExpl &operator+=(const FT& M);
  369. // Explicit write to a given index of Duoram memory
  370. MemRefExpl &operator=(const FT& M);
  371. // Convenience function
  372. MemRefExpl &operator-=(const FT& M) { *this += (-M); return *this; }
  373. };
  374. // A collection of independent memory references that can be processed
  375. // simultaneously. You get one of these from a Shape A (of specific
  376. // subclass Sh) and a vector or array of indices v with each element of
  377. // type U.
  378. template <typename T> template <typename U, typename Sh>
  379. class Duoram<T>::Shape::MemRefInd {
  380. Sh &shape;
  381. std::vector<U> indcs;
  382. public:
  383. MemRefInd(Sh &shape, std::vector<U> indcs) :
  384. shape(shape), indcs(indcs) {}
  385. template <size_t N>
  386. MemRefInd(Sh &shape, std::array<U,N> aindcs) :
  387. shape(shape) { for ( auto &i : aindcs ) { indcs.push_back(i); } }
  388. // Independent reads from shared or explicit indices of Duoram memory
  389. operator std::vector<T>();
  390. // Independent updates to shared or explicit indices of Duoram memory
  391. MemRefInd &operator+=(const std::vector<T>& M);
  392. template <size_t N>
  393. MemRefInd &operator+=(const std::array<T,N>& M);
  394. // Independent writes to shared or explicit indices of Duoram memory
  395. MemRefInd &operator=(const std::vector<T>& M);
  396. template <size_t N>
  397. MemRefInd &operator=(const std::array<T,N>& M);
  398. // Convenience function
  399. MemRefInd &operator-=(const std::vector<T>& M) { *this += (-M); return *this; }
  400. template <size_t N>
  401. MemRefInd &operator-=(const std::array<T,N>& M) { *this += (-M); return *this; }
  402. };
  403. #include "duoram.tcc"
  404. #endif