simplePIR.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. #include "libpir.hpp"
  2. bool run(DBHandler *db, uint64_t chosen_element, PIRParameters params){
  3. /******************************************************************************
  4. * PIR and Crypto Setup (must be done by both the client and the server)
  5. * In a real application the client and server must agree on the parameters
  6. * For example the client chooses and sends them to the server (or inversely)
  7. ******************************************************************************/
  8. HomomorphicCrypto *crypto = HomomorphicCryptoFactory::getCryptoMethod(params.crypto_params);
  9. // Absorption capacity of an LWE encryption scheme depends on the number of sums that are going
  10. // to be done in the PIR protocol, it must therefore be initialized
  11. // Warning here we suppose the biggest dimension is in d[0]
  12. // otherwise absorbtion needs to be computed accordingly
  13. crypto->setandgetAbsBitPerCiphertext(params.n[0]);
  14. /******************************************************************************
  15. * Query generation phase (client-side)
  16. ******************************************************************************/
  17. // Create the query generator object
  18. PIRQueryGenerator q_generator(params,*crypto);
  19. std::cout << "SimplePIR: Generating query ..." << std::endl;
  20. // Generate a query to get the FOURTH element in the database (indexes begin at 0)
  21. // Warning : if we had set params.alpha=2 elements would be aggregated 2 by 2 and
  22. // generatequery would only accept as input 0 (the two first elements) or 1 (the other two)
  23. q_generator.generateQuery(chosen_element);
  24. std::cout << "SimplePIR: Query generated" << std::endl;
  25. /******************************************************************************
  26. * Reply generation phase (server-side)
  27. ******************************************************************************/
  28. // Create the reply generator object
  29. // We could have also defined PIRReplyGenerator *r_generator(params,*crypto,db);
  30. // But we prefer a pointer to show (below) how to use multiple generators for a given db
  31. PIRReplyGenerator *r_generator = new PIRReplyGenerator(params,*crypto,db);
  32. // In a real application the client would pop the queries from q with popQuery and
  33. // send them through the network and the server would receive and push them into s
  34. // using pushQuery
  35. char* query_element;
  36. while (q_generator.popQuery(&query_element))
  37. {
  38. r_generator->pushQuery(query_element);
  39. }
  40. // Import database
  41. // This could have been done on the "Database setup" phase if:
  42. // - the contents are static
  43. // - AND the imported database fits in RAM
  44. // - AND the server knows in advance the PIR and crypto parameters (e.g. chosen by him)
  45. std::cout << "SimplePIR: Importing database ..." << std::endl;
  46. // Warning aggregation is dealt with internally the bytes_per_db_element parameter here
  47. // is to be given WITHOUT multiplying it by params.alpha
  48. imported_database* imported_db = r_generator->importData(/* uint64_t offset*/ 0, /*uint64_t
  49. bytes_per_db_element */ db->getmaxFileBytesize());
  50. std::cout << "SimplePIR: Database imported" << std::endl;
  51. // Once the query is known and the database imported launch the reply generation
  52. std::cout << "SimplePIR: Generating reply ..." << std::endl;
  53. double start = omp_get_wtime();
  54. r_generator->generateReply(imported_db);
  55. double end = omp_get_wtime();
  56. std::cout << "SimplePIR: Reply generated in " << end-start << " seconds" << std::endl;
  57. /********************************************************************************
  58. * Advanced example: uncomment it to test
  59. * The object imported_db is separated from r_generator in purpose
  60. * Here is an example on how to use the same imported_db for multiple queries
  61. * DO NOT try to use the same reply generator more than once, this causes issues
  62. * ******************************************************************************/
  63. #if 0
  64. // Generate 3 replies from 3 queries
  65. for (int i = 0 ; i < 3 ; i++){
  66. // Pop (and drop for this simple example) the generated reply
  67. char* reply_element_tmp;
  68. while (r_generator->popReply(&reply_element_tmp)){
  69. free(reply_element_tmp);
  70. }
  71. // If you are unable to reuse a r_generator object (e.g. if you want
  72. // to change the crypto object) you can always recreate a new generator
  73. //delete r_generator;
  74. //r_generator = new PIRReplyGenerator(params,*crypto,db);
  75. // In this example we want to use the same generator for
  76. // multiply queries. Before giving a new query to r_generator
  77. // we must free the previous one.
  78. r_generator->freeQueries();
  79. // It is also possible to change the pir parameters with the
  80. // (unexposed) setPirParams(PIRParameters newparams) function
  81. // Generate a new query
  82. q_generator.generateQuery(chosen_element);
  83. // Push it to the reply generator
  84. while (q_generator.popQuery(&query_element))
  85. {
  86. r_generator->pushQuery(query_element);
  87. }
  88. // Generate again the reply
  89. r_generator->generateReply(imported_db);
  90. }
  91. #endif
  92. /******************************************************************************
  93. * Reply extraction phase (client-side)
  94. ******************************************************************************/
  95. PIRReplyExtraction *r_extractor=new PIRReplyExtraction(params,*crypto);
  96. // In a real application the server would pop the replies from s with popReply and
  97. // send them through the network together with nbRepliesGenerated and aggregated_maxFileSize
  98. // and the client would receive the replies and push them into r using pushEncryptedReply
  99. std::cout << "SimplePIR: "<< r_generator->getnbRepliesGenerated()<< " Replies generated " << std::endl;
  100. uint64_t clientside_maxFileBytesize = db->getmaxFileBytesize();
  101. char* reply_element;
  102. while (r_generator->popReply(&reply_element))
  103. {
  104. r_extractor->pushEncryptedReply(reply_element);
  105. }
  106. std::cout << "SimplePIR: Extracting reply ..." << std::endl;
  107. r_extractor->extractReply(clientside_maxFileBytesize);
  108. std::cout << "SimplePIR: Reply extracted" << std::endl;
  109. // In a real application instead of writing to a buffer we could write to an output file
  110. char *outptr, *result, *tmp;
  111. outptr = result = (char*)calloc(r_extractor->getnbPlaintextReplies(clientside_maxFileBytesize)*r_extractor->getPlaintextReplyBytesize(), sizeof(char));
  112. while (r_extractor->popPlaintextResult(&tmp))
  113. {
  114. memcpy(outptr, tmp, r_extractor->getPlaintextReplyBytesize());
  115. outptr+=r_extractor->getPlaintextReplyBytesize();
  116. free(tmp);
  117. }
  118. // Result is in ... result
  119. /******************************************************************************
  120. * Test correctness
  121. ******************************************************************************/
  122. char *db_element = (char*)calloc(clientside_maxFileBytesize*params.alpha, sizeof(char));
  123. bool fail = false;
  124. db->readAggregatedStream(chosen_element, params.alpha, 0, clientside_maxFileBytesize, db_element);
  125. if (memcmp(result, db_element, clientside_maxFileBytesize*params.alpha))
  126. {
  127. std::cout << "SimplePIR: Test failed, the retrieved element is not correct" << std::endl;
  128. fail = true;
  129. }
  130. else
  131. {
  132. std::cout << "SimplePIR: Test succeeded !!!!!!!!!!!!!!!!!!!!!!!!" << std::endl<< std::endl;
  133. fail = false;
  134. }
  135. /******************************************************************************
  136. * Cleanup
  137. ******************************************************************************/
  138. delete imported_db;
  139. r_generator->freeQueries();
  140. delete r_generator;
  141. delete r_extractor;
  142. delete crypto;
  143. free(result);
  144. free(db_element);
  145. return fail;
  146. }
  147. int main(int argc, char * argv[]) {
  148. uint64_t database_size, nb_files, chosen_element, maxFileBytesize;
  149. PIRParameters params;
  150. bool tests_failed = false;
  151. /******************************************************************************
  152. * Database setup (server-side)
  153. ******************************************************************************/
  154. // To Create the database generator object
  155. // it can be a DBGenerator that simulate nb_files files of size streamBytesize
  156. // database_size = 1ULL<<25; nb_files = 4; maxFileBytesize = database_size/nb_files;
  157. // DBGenerator db(nb_files, maxFileBytesize, /*bool silent*/ false);
  158. //
  159. // OR it can be a DBDirectoryProcessor that reads a real file in the ./db directory
  160. // and splits it into nb_files virtual files
  161. // nb_files = 4;
  162. // DBDirectoryProcessor db(nb_files);
  163. // database_size = db.getDBSizeinbits();maxFileBytesize = database_size/nb_files;
  164. //
  165. // OR it can be a DBDirectoryProcessor that reads the real files in the ./db directory
  166. // DBDirectoryProcessor db;
  167. // nb_files=db.getNbStream();database_size = db.getDBSizeinbits();
  168. // maxFileBytesize = database_size/nb_files;
  169. // Simple test
  170. std::cout << "======================================================================" << std::endl;
  171. std::cout << "Test 1/9: database_size = 1ULL<<30; nb_files = 20;" << std::endl;
  172. std::cout << "params.alpha = 1; params.d = 1; crypto_params = LWE:80:2048:120;" << std::endl;
  173. std::cout << "======================================================================" << std::endl;
  174. database_size = 1ULL<<20; nb_files = 20; maxFileBytesize = database_size/nb_files;
  175. DBGenerator db(nb_files, maxFileBytesize, /*bool silent*/ false);
  176. chosen_element = 3;
  177. params.alpha = 1; params.d = 1; params.n[0] = nb_files;
  178. // The crypto parameters can be set to other values
  179. // You can get a list of all available cryptographic parameters with this function call
  180. // HomomorphicCryptoFactory::printAllCryptoParams();
  181. params.crypto_params = "LWE:80:2048:120";
  182. tests_failed |= run(&db, chosen_element, params);
  183. // Test with aggregation
  184. // WARNING we must provide the representation of the database GIVEN recursion and aggregation
  185. // as here we have 100 elements and aggregate them in a unique group we have params.n[0]=1
  186. std::cout << "======================================================================" << std::endl;
  187. std::cout << "Test 2/9: database_size = 1ULL<<25; nb_files = 100;" << std::endl;
  188. std::cout << "params.alpha = 100; params.d = 1; crypto_params = LWE:80:2048:120;" << std::endl;
  189. std::cout << "======================================================================" << std::endl;
  190. database_size = 1ULL<<25; nb_files = 100; maxFileBytesize = database_size/nb_files;
  191. DBGenerator db2(nb_files, maxFileBytesize, /*bool silent*/ false);
  192. chosen_element = 0;
  193. params.alpha = 100; params.d = 1; params.n[0] = 1;
  194. params.crypto_params = "LWE:80:2048:120";
  195. tests_failed |= run(&db2, chosen_element, params);
  196. // Test with recursion 2
  197. std::cout << "======================================================================" << std::endl;
  198. std::cout << "Test 3/9: database_size = 1ULL<<25; nb_files = 100;" << std::endl;
  199. std::cout << "params.alpha = 1; params.d = 2; crypto_params = LWE:80:2048:120;" << std::endl;
  200. std::cout << "======================================================================" << std::endl;
  201. database_size = 1ULL<<25; nb_files = 100; maxFileBytesize = database_size/nb_files;
  202. DBGenerator db3(nb_files, maxFileBytesize, /*bool silent*/ false);
  203. chosen_element = 3;
  204. params.alpha = 1; params.d = 2; params.n[0] = 50; params.n[1] = 2;
  205. params.crypto_params = "LWE:80:2048:120";
  206. tests_failed |= run(&db3, chosen_element, params);
  207. // Test with recursion 2 and aggregation
  208. std::cout << "======================================================================" << std::endl;
  209. std::cout << "Test 4/9: database_size = 1ULL<<25; nb_files = 100;" << std::endl;
  210. std::cout << "params.alpha = 2; params.d = 2; crypto_params = LWE:80:2048:120;" << std::endl;
  211. std::cout << "======================================================================" << std::endl;
  212. database_size = 1ULL<<25; nb_files = 100; maxFileBytesize = database_size/nb_files;
  213. DBGenerator db4(nb_files, maxFileBytesize, /*bool silent*/ false);
  214. chosen_element = 3;
  215. params.alpha = 2; params.d = 2; params.n[0] = 25; params.n[1] = 2;
  216. params.crypto_params = "LWE:80:2048:120";
  217. tests_failed |= run(&db4, chosen_element, params);
  218. // Test with recursion 3
  219. std::cout << "======================================================================" << std::endl;
  220. std::cout << "Test 5/9: database_size = 1ULL<<25; nb_files = 100;" << std::endl;
  221. std::cout << "params.alpha = 1; params.d = 3; crypto_params = LWE:80:2048:120;" << std::endl;
  222. std::cout << "======================================================================" << std::endl;
  223. database_size = 1ULL<<25; nb_files = 100; maxFileBytesize = database_size/nb_files;
  224. DBGenerator db5(nb_files, maxFileBytesize, /*bool silent*/ false);
  225. chosen_element = 3;
  226. params.alpha = 1; params.d = 3; params.n[0] = 5; params.n[1] = 5; params.n[2] = 4;
  227. params.crypto_params = "LWE:80:2048:120";
  228. tests_failed |= run(&db5, chosen_element, params);
  229. // Test with a DBDirectoryProcessor splitting a big real file
  230. std::cout << "======================================================================" << std::endl;
  231. std::cout << "Test 6/9: DBDirectoryProcessor with split; database_size = 1ULL<<25; nb_files = 4;" << std::endl;
  232. std::cout << "params.alpha = 1; params.d = 1; crypto_params = LWE:80:2048:120;" << std::endl;
  233. std::cout << "======================================================================" << std::endl;
  234. database_size = 1ULL<<25; nb_files = 4; maxFileBytesize = database_size/nb_files;
  235. DBDirectoryProcessor db6(/*split the first file in*/ nb_files /*files*/);
  236. if (db6.getErrorStatus()==true){
  237. std::cout << "SimplePIR : Error with db directory skipping test ..." << std::endl << std::endl;
  238. } else {
  239. chosen_element = 3;
  240. params.alpha = 1; params.d = 1; params.n[0] = nb_files;
  241. params.crypto_params = "LWE:80:2048:120";
  242. tests_failed |= run(&db6, chosen_element, params);
  243. }
  244. // Test with a DBDirectoryProcessor reading real files
  245. std::cout << "======================================================================" << std::endl;
  246. std::cout << "Test 7/9: DBDirectoryProcessor without split;" << std::endl;
  247. std::cout << "params.alpha = 1; params.d = 1; crypto_params = LWE:80:2048:120;" << std::endl;
  248. std::cout << "======================================================================" << std::endl;
  249. DBDirectoryProcessor db7;
  250. if (db6.getErrorStatus()==true){
  251. std::cout << "SimplePIR : Error with db directory skipping test ..." << std::endl << std::endl;
  252. } else {
  253. database_size = db7.getDBSizeBits()/8; nb_files = db7.getNbStream();
  254. maxFileBytesize = database_size/nb_files;
  255. chosen_element = 0;
  256. params.alpha = 1; params.d = 1; params.n[0] = nb_files;
  257. params.crypto_params = "LWE:80:2048:120";
  258. tests_failed |= run(&db7, chosen_element, params);
  259. }
  260. // Test with a DBVectorProcessor
  261. std::cout << "======================================================================" << std::endl;
  262. std::cout << "Test 8/9: DBVectorProcessor;" << std::endl;
  263. std::cout << "params.alpha = 1; params.d = 1; crypto_params = LWE:80:2048:120;" << std::endl;
  264. std::cout << "======================================================================" << std::endl;
  265. std::vector<element_t> elements1;
  266. elements1.push_back(Element("first file", 10, (char*) "first data"));
  267. elements1.push_back(Element("second file", 11, (char*) "second data"));
  268. elements1.push_back(Element("third file", 8, (char*) "3rd data"));
  269. DBVectorProcessor db8(elements1);
  270. chosen_element = 0;
  271. params.alpha = 1; params.d = 1;
  272. params.crypto_params = "LWE:80:2048:120";
  273. tests_failed |= run(&db8, chosen_element, params);
  274. // Test with a DBVectorProcessor
  275. std::cout << "======================================================================" << std::endl;
  276. std::cout << "Test 9/9: DBVectorProcessor;" << std::endl;
  277. std::cout << "params.alpha = 2; params.d = 1; crypto_params = LWE:80:2048:120;" << std::endl;
  278. std::cout << "======================================================================" << std::endl;
  279. std::vector<element_t> elements2;
  280. elements2.push_back(Element("first file", 10, (char*) "first data"));
  281. elements2.push_back(Element("second file", 11, (char*) "second data"));
  282. elements2.push_back(Element("third file", 8, (char*) "3rd data"));
  283. elements2.push_back(Element("fourth file", 14, (char*) "fourth db data"));
  284. elements2.push_back(Element("fifth file", 13, (char*) "fifth db data"));
  285. DBVectorProcessor db9(elements2);
  286. chosen_element = 0;
  287. params.alpha = 2; params.d = 1;
  288. params.crypto_params = "LWE:80:2048:120";
  289. tests_failed |= run(&db8, chosen_element, params);
  290. if (tests_failed)
  291. {
  292. std::cout << "WARNING : at least one tests failed" << std::endl;
  293. return 1;
  294. }
  295. else
  296. {
  297. std::cout << "All tests succeeded" << std::endl;
  298. return 0;
  299. }
  300. }