client.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. #include <iostream>
  2. #include "client.hpp"
  3. #include "serverEntity.hpp"
  4. extern const curvepoint_fp_t bn_curvegen;
  5. const int MAX_ALLOWED_VOTE = 2;
  6. /* These lines need to be here so these static variables are defined,
  7. * but in C++ putting code here doesn't actually execute
  8. * (or at least, with g++, whenever it would execute is not at a useful time)
  9. * so we have an init() function to actually put the correct values in them. */
  10. Curvepoint PrsonaClient::EL_GAMAL_GENERATOR = Curvepoint();
  11. bool PrsonaClient::SERVER_IS_MALICIOUS = false;
  12. bool PrsonaClient::CLIENT_IS_MALICIOUS = false;
  13. // Quick and dirty function to calculate ceil(log base 2) with mpz_class
  14. mpz_class log2(mpz_class x)
  15. {
  16. mpz_class retval = 0;
  17. while (x > 0)
  18. {
  19. retval++;
  20. x = x >> 1;
  21. }
  22. return retval;
  23. }
  24. mpz_class bit(mpz_class x)
  25. {
  26. return x > 0 ? 1 : 0;
  27. }
  28. /********************
  29. * PUBLIC FUNCTIONS *
  30. ********************/
  31. /*
  32. * CONSTRUCTORS
  33. */
  34. PrsonaClient::PrsonaClient(
  35. const BGNPublicKey& serverPublicKey,
  36. const Curvepoint& elGamalBlindGenerator,
  37. const PrsonaServerEntity* servers)
  38. : serverPublicKey(serverPublicKey),
  39. elGamalBlindGenerator(elGamalBlindGenerator),
  40. servers(servers),
  41. max_checked(0)
  42. {
  43. longTermPrivateKey.set_random();
  44. inversePrivateKey = longTermPrivateKey.curveInverse();
  45. decryption_memoizer[elGamalBlindGenerator * max_checked] = max_checked;
  46. }
  47. /*
  48. * SETUP FUNCTIONS
  49. */
  50. // Must be called once before any usage of this class
  51. void PrsonaClient::init()
  52. {
  53. EL_GAMAL_GENERATOR = Curvepoint(bn_curvegen);
  54. }
  55. void PrsonaClient::set_server_malicious()
  56. {
  57. SERVER_IS_MALICIOUS = true;
  58. }
  59. void PrsonaClient::set_client_malicious()
  60. {
  61. CLIENT_IS_MALICIOUS = true;
  62. }
  63. /*
  64. * BASIC PUBLIC SYSTEM INFO GETTERS
  65. */
  66. Curvepoint PrsonaClient::get_short_term_public_key(Proof &pi) const
  67. {
  68. pi = generate_ownership_proof();
  69. return currentFreshGenerator * longTermPrivateKey;
  70. }
  71. /*
  72. * SERVER INTERACTIONS
  73. */
  74. /* Generate a new vote vector to give to the servers
  75. * @replaces controls which votes are actually being updated and which are not
  76. *
  77. * You may really want to make currentEncryptedVotes a member variable,
  78. * but it doesn't behave correctly when adding new clients after this one. */
  79. std::vector<CurveBipoint> PrsonaClient::make_votes(
  80. std::vector<Proof>& validVoteProof,
  81. const Proof& serverProof,
  82. const std::vector<CurveBipoint>& oldEncryptedVotes,
  83. const std::vector<Scalar>& votes,
  84. const std::vector<bool>& replaces) const
  85. {
  86. std::vector<Scalar> seeds(oldEncryptedVotes.size());
  87. std::vector<CurveBipoint> newEncryptedVotes(oldEncryptedVotes.size());
  88. if (!verify_valid_votes_proof(serverProof, oldEncryptedVotes))
  89. {
  90. std::cerr << "Could not verify proof of valid votes." << std::endl;
  91. return newEncryptedVotes;
  92. }
  93. for (size_t i = 0; i < votes.size(); i++)
  94. {
  95. if (replaces[i])
  96. {
  97. newEncryptedVotes[i] = serverPublicKey.encrypt(seeds[i], votes[i]);
  98. }
  99. else
  100. {
  101. newEncryptedVotes[i] =
  102. serverPublicKey.rerandomize(seeds[i], oldEncryptedVotes[i]);
  103. }
  104. }
  105. validVoteProof = generate_vote_proof(
  106. replaces, oldEncryptedVotes, newEncryptedVotes, seeds, votes);
  107. return newEncryptedVotes;
  108. }
  109. // Get a new fresh generator (happens at initialization and during each epoch)
  110. void PrsonaClient::receive_fresh_generator(const Curvepoint& freshGenerator)
  111. {
  112. currentFreshGenerator = freshGenerator;
  113. }
  114. // Receive a new encrypted score from the servers (each epoch)
  115. void PrsonaClient::receive_vote_tally(
  116. const Proof& pi, const EGCiphertext& score)
  117. {
  118. if (!verify_valid_tally_proof(pi, score))
  119. {
  120. std::cerr << "Could not verify proof of valid tally." << std::endl;
  121. return;
  122. }
  123. currentEncryptedScore = score;
  124. decrypt_score(score);
  125. }
  126. /*
  127. * REPUTATION PROOFS
  128. */
  129. // A pretty straightforward range proof (generation)
  130. std::vector<Proof> PrsonaClient::generate_reputation_proof(
  131. const Scalar& threshold) const
  132. {
  133. std::vector<Proof> retval;
  134. // Don't even try if the user asks to make an illegitimate proof
  135. if (threshold.toInt() > (servers->get_num_clients() * MAX_ALLOWED_VOTE))
  136. return retval;
  137. // Base case
  138. if (!CLIENT_IS_MALICIOUS)
  139. {
  140. Proof currProof;
  141. currProof.basic = "PROOF";
  142. retval.push_back(currProof);
  143. return retval;
  144. }
  145. // We really have two consecutive proofs in a junction.
  146. // The first is to prove that we are the stpk we claim we are
  147. retval.push_back(generate_ownership_proof());
  148. // The value we're actually using in our proof
  149. mpz_class proofVal = currentScore.curveSub(threshold).toInt();
  150. // Top of the range in our proof determined by what scores are even possible
  151. mpz_class proofBits =
  152. log2(
  153. servers->get_num_clients() * MAX_ALLOWED_VOTE -
  154. threshold.toInt());
  155. // Don't risk a situation that would divulge our private key
  156. if (proofBits <= 1)
  157. proofBits = 2;
  158. // This seems weird, but remember our base is A_t^r, not g^t
  159. std::vector<Scalar> masksPerBit;
  160. masksPerBit.push_back(inversePrivateKey);
  161. for (size_t i = 1; i < proofBits; i++)
  162. {
  163. Scalar currMask;
  164. currMask.set_random();
  165. masksPerBit.push_back(currMask);
  166. masksPerBit[0] =
  167. masksPerBit[0].curveSub(currMask.curveMult(Scalar(1 << i)));
  168. }
  169. // Taken from Fig. 1 in https://eprint.iacr.org/2014/764.pdf
  170. for (size_t i = 0; i < proofBits; i++)
  171. {
  172. Proof currProof;
  173. Curvepoint g, h, c, c_a, c_b;
  174. g = currentEncryptedScore.mask;
  175. h = elGamalBlindGenerator;
  176. mpz_class currBit = bit(proofVal & (1 << i));
  177. Scalar a, s, t, m, r;
  178. a.set_random();
  179. s.set_random();
  180. t.set_random();
  181. m = Scalar(currBit);
  182. r = masksPerBit[i];
  183. c = g * r + h * m;
  184. currProof.partialUniversals.push_back(c);
  185. c_a = g * s + h * a;
  186. Scalar am = a.curveMult(m);
  187. c_b = g * t + h * am;
  188. std::stringstream oracleInput;
  189. oracleInput << g << h << c << c_a << c_b;
  190. Scalar x = oracle(oracleInput.str());
  191. currProof.challengeParts.push_back(x);
  192. Scalar f, z_a, z_b;
  193. Scalar mx = m.curveMult(x);
  194. f = mx.curveAdd(a);
  195. Scalar rx = r.curveMult(x);
  196. z_a = rx.curveAdd(s);
  197. Scalar x_f = x.curveSub(f);
  198. Scalar r_x_f = r.curveMult(x_f);
  199. z_b = r_x_f.curveAdd(t);
  200. currProof.responseParts.push_back(f);
  201. currProof.responseParts.push_back(z_a);
  202. currProof.responseParts.push_back(z_b);
  203. retval.push_back(currProof);
  204. }
  205. return retval;
  206. }
  207. // A pretty straightforward range proof (verification)
  208. bool PrsonaClient::verify_reputation_proof(
  209. const std::vector<Proof>& pi,
  210. const Curvepoint& shortTermPublicKey,
  211. const Scalar& threshold) const
  212. {
  213. // Reject outright if there's no proof to check
  214. if (pi.empty())
  215. {
  216. std::cerr << "Proof was empty, aborting." << std::endl;
  217. return false;
  218. }
  219. // Base case
  220. if (!CLIENT_IS_MALICIOUS)
  221. return pi[0].basic == "PROOF";
  222. // User should be able to prove they are who they say they are
  223. if (!verify_ownership_proof(pi[0], shortTermPublicKey))
  224. {
  225. std::cerr << "Schnorr proof failed, aborting." << std::endl;
  226. return false;
  227. }
  228. // Get the encrypted score in question from the servers
  229. Proof serverProof;
  230. EGCiphertext encryptedScore =
  231. servers->get_current_tally(serverProof, shortTermPublicKey);
  232. // Rough for the prover but if the server messes up,
  233. // no way to prove the thing anyways
  234. if (!verify_valid_tally_proof(serverProof, encryptedScore))
  235. {
  236. std::cerr << "Server error prevented proof from working, aborting." << std::endl;
  237. return false;
  238. }
  239. // X is the thing we're going to be checking in on throughout
  240. // to try to get our score commitment back in the end.
  241. Curvepoint X;
  242. for (size_t i = 1; i < pi.size(); i++)
  243. {
  244. Curvepoint c, g, h;
  245. c = pi[i].partialUniversals[0];
  246. g = encryptedScore.mask;
  247. h = elGamalBlindGenerator;
  248. X = X + c * Scalar(1 << (i - 1));
  249. Scalar x, f, z_a, z_b;
  250. x = pi[i].challengeParts[0];
  251. f = pi[i].responseParts[0];
  252. z_a = pi[i].responseParts[1];
  253. z_b = pi[i].responseParts[2];
  254. // Taken from Fig. 1 in https://eprint.iacr.org/2014/764.pdf
  255. Curvepoint c_a, c_b;
  256. c_a = g * z_a + h * f - c * x;
  257. Scalar x_f = x.curveSub(f);
  258. c_b = g * z_b - c * x_f;
  259. std::stringstream oracleInput;
  260. oracleInput << g << h << c << c_a << c_b;
  261. if (oracle(oracleInput.str()) != pi[i].challengeParts[0])
  262. {
  263. std::cerr << "0 or 1 proof failed at index " << i << " of " << pi.size() - 1 << ", aborting." << std::endl;
  264. return false;
  265. }
  266. }
  267. Scalar negThreshold;
  268. negThreshold = Scalar(0).curveSub(threshold);
  269. Curvepoint scoreCommitment =
  270. encryptedScore.encryptedMessage +
  271. elGamalBlindGenerator * negThreshold;
  272. return X == scoreCommitment;
  273. }
  274. Scalar PrsonaClient::get_score() const
  275. {
  276. return currentScore;
  277. }
  278. /*********************
  279. * PRIVATE FUNCTIONS *
  280. *********************/
  281. /*
  282. * SCORE DECRYPTION
  283. */
  284. // Basic memoized score decryption
  285. void PrsonaClient::decrypt_score(const EGCiphertext& score)
  286. {
  287. Curvepoint s, hashedDecrypted;
  288. // Remove the mask portion of the ciphertext
  289. s = score.mask * inversePrivateKey;
  290. hashedDecrypted = score.encryptedMessage - s;
  291. // Check if it's a value we've already seen
  292. auto lookup = decryption_memoizer.find(hashedDecrypted);
  293. if (lookup != decryption_memoizer.end())
  294. {
  295. currentScore = lookup->second;
  296. return;
  297. }
  298. // If not, iterate until we find it (adding everything to the memoization)
  299. max_checked++;
  300. Curvepoint decryptionCandidate = elGamalBlindGenerator * max_checked;
  301. while (decryptionCandidate != hashedDecrypted)
  302. {
  303. decryption_memoizer[decryptionCandidate] = max_checked;
  304. decryptionCandidate = decryptionCandidate + elGamalBlindGenerator;
  305. max_checked++;
  306. }
  307. decryption_memoizer[decryptionCandidate] = max_checked;
  308. // Set the value we found
  309. currentScore = max_checked;
  310. }
  311. /*
  312. * OWNERSHIP PROOFS
  313. */
  314. // Very basic Schnorr proof (generation)
  315. Proof PrsonaClient::generate_ownership_proof() const
  316. {
  317. Proof retval;
  318. if (!CLIENT_IS_MALICIOUS)
  319. {
  320. retval.basic = "PROOF";
  321. return retval;
  322. }
  323. std::stringstream oracleInput;
  324. Scalar r;
  325. r.set_random();
  326. Curvepoint shortTermPublicKey = currentFreshGenerator * longTermPrivateKey;
  327. Curvepoint u = currentFreshGenerator * r;
  328. oracleInput << currentFreshGenerator << shortTermPublicKey << u;
  329. Scalar c = oracle(oracleInput.str());
  330. Scalar z = r.curveAdd(c.curveMult(longTermPrivateKey));
  331. retval.basic = "PROOF";
  332. retval.challengeParts.push_back(c);
  333. retval.responseParts.push_back(z);
  334. return retval;
  335. }
  336. // Very basic Schnorr proof (verification)
  337. bool PrsonaClient::verify_ownership_proof(
  338. const Proof& pi, const Curvepoint& shortTermPublicKey) const
  339. {
  340. if (!CLIENT_IS_MALICIOUS)
  341. return pi.basic == "PROOF";
  342. Scalar c = pi.challengeParts[0];
  343. Scalar z = pi.responseParts[0];
  344. Curvepoint u = currentFreshGenerator * z - shortTermPublicKey * c;
  345. std::stringstream oracleInput;
  346. oracleInput << currentFreshGenerator << shortTermPublicKey << u;
  347. return c == oracle(oracleInput.str());
  348. }
  349. /*
  350. * PROOF VERIFICATION
  351. */
  352. bool PrsonaClient::verify_score_proof(const Proof& pi) const
  353. {
  354. if (!SERVER_IS_MALICIOUS)
  355. return pi.basic == "PROOF";
  356. return pi.basic == "PROOF";
  357. }
  358. bool PrsonaClient::verify_generator_proof(
  359. const Proof& pi, const Curvepoint& generator) const
  360. {
  361. if (!SERVER_IS_MALICIOUS)
  362. return pi.basic == "PROOF";
  363. return pi.basic == "PROOF";
  364. }
  365. bool PrsonaClient::verify_default_tally_proof(
  366. const Proof& pi, const EGCiphertext& score) const
  367. {
  368. if (!SERVER_IS_MALICIOUS)
  369. return pi.basic == "PROOF";
  370. return pi.basic == "PROOF";
  371. }
  372. bool PrsonaClient::verify_valid_tally_proof(
  373. const Proof& pi, const EGCiphertext& score) const
  374. {
  375. if (!SERVER_IS_MALICIOUS)
  376. return pi.basic == "PROOF";
  377. return pi.basic == "PROOF";
  378. }
  379. bool PrsonaClient::verify_default_votes_proof(
  380. const Proof& pi, const std::vector<CurveBipoint>& votes) const
  381. {
  382. if (!SERVER_IS_MALICIOUS)
  383. return pi.basic == "PROOF";
  384. return pi.basic == "PROOF";
  385. }
  386. bool PrsonaClient::verify_valid_votes_proof(
  387. const Proof& pi, const std::vector<CurveBipoint>& votes) const
  388. {
  389. if (!SERVER_IS_MALICIOUS)
  390. return pi.basic == "PROOF";
  391. return pi.basic == "PROOF";
  392. }
  393. /*
  394. * PROOF GENERATION
  395. */
  396. std::vector<Proof> PrsonaClient::generate_vote_proof(
  397. const std::vector<bool>& replaces,
  398. const std::vector<CurveBipoint>& oldEncryptedVotes,
  399. const std::vector<CurveBipoint>& newEncryptedVotes,
  400. const std::vector<Scalar>& seeds,
  401. const std::vector<Scalar>& votes) const
  402. {
  403. std::vector<Proof> retval;
  404. // Base case
  405. if (!CLIENT_IS_MALICIOUS)
  406. {
  407. Proof currProof;
  408. currProof.basic = "PROOF";
  409. retval.push_back(currProof);
  410. return retval;
  411. }
  412. // The first need is to prove that we are the stpk we claim we are
  413. retval.push_back(generate_ownership_proof());
  414. // Then, we iterate over all votes for the proofs that they are correct
  415. for (size_t i = 0; i < replaces.size(); i++)
  416. {
  417. std::stringstream oracleInput;
  418. oracleInput << serverPublicKey.get_bipoint_curvegen()
  419. << serverPublicKey.get_bipoint_curve_subgroup_gen()
  420. << oldEncryptedVotes[i] << newEncryptedVotes[i];
  421. /* This proof structure is documented in my notes.
  422. * It's inspired by the proof in Fig. 1 at
  423. * https://eprint.iacr.org/2014/764.pdf, but adapted so that you prove
  424. * m(m-1)(m-2) = 0 instead of m(m-1) = 0.
  425. *
  426. * The rerandomization part is just a slight variation on an
  427. * ordinary Schnorr proof, so that part's less scary. */
  428. if (replaces[i]) // CASE: Make new vote
  429. {
  430. Proof currProof;
  431. Scalar c_r, z_r, a, b, s_1, s_2, t_1, t_2;
  432. c_r.set_random();
  433. z_r.set_random();
  434. a.set_random();
  435. b.set_random();
  436. s_1.set_random();
  437. s_2.set_random();
  438. t_1.set_random();
  439. t_2.set_random();
  440. CurveBipoint U =
  441. serverPublicKey.get_bipoint_curve_subgroup_gen() * z_r +
  442. oldEncryptedVotes[i] * c_r -
  443. newEncryptedVotes[i] * c_r;
  444. CurveBipoint C_a = serverPublicKey.get_bipoint_curvegen() * a +
  445. serverPublicKey.get_bipoint_curve_subgroup_gen() * s_1;
  446. CurveBipoint C_b = serverPublicKey.get_bipoint_curvegen() * b +
  447. serverPublicKey.get_bipoint_curve_subgroup_gen() * s_2;
  448. Scalar power = (a.curveAdd(b)).curveMult(votes[i].curveMult(votes[i]));
  449. power =
  450. power.curveSub((a.curveAdd(a).curveAdd(b)).curveMult(votes[i]));
  451. CurveBipoint C_c = serverPublicKey.get_bipoint_curvegen() * power +
  452. serverPublicKey.get_bipoint_curve_subgroup_gen() * t_1;
  453. currProof.partialUniversals.push_back(C_c[0]);
  454. currProof.partialUniversals.push_back(C_c[1]);
  455. CurveBipoint C_d =
  456. serverPublicKey.get_bipoint_curvegen() *
  457. a.curveMult(b.curveMult(votes[i])) +
  458. serverPublicKey.get_bipoint_curve_subgroup_gen() * t_2;
  459. oracleInput << U << C_a << C_b << C_c << C_d;
  460. Scalar c = oracle(oracleInput.str());
  461. Scalar c_n = c.curveSub(c_r);
  462. currProof.challengeParts.push_back(c_r);
  463. currProof.challengeParts.push_back(c_n);
  464. Scalar f_1 = (votes[i].curveMult(c_n)).curveAdd(a);
  465. Scalar f_2 = (votes[i].curveMult(c_n)).curveAdd(b);
  466. Scalar z_na = (seeds[i].curveMult(c_n)).curveAdd(s_1);
  467. Scalar z_nb = (seeds[i].curveMult(c_n)).curveAdd(s_2);
  468. Scalar t_1_c_n_t_2 = (t_1.curveMult(c_n)).curveAdd(t_2);
  469. Scalar f_1_c_n = f_1.curveSub(c_n);
  470. Scalar c_n_f_2 = c_n.curveAdd(c_n).curveSub(f_2);
  471. Scalar z_nc =
  472. (seeds[i].curveMult(f_1_c_n).curveMult(c_n_f_2)).curveAdd(
  473. t_1_c_n_t_2);
  474. currProof.responseParts.push_back(z_r);
  475. currProof.responseParts.push_back(f_1);
  476. currProof.responseParts.push_back(f_2);
  477. currProof.responseParts.push_back(z_na);
  478. currProof.responseParts.push_back(z_nb);
  479. currProof.responseParts.push_back(z_nc);
  480. retval.push_back(currProof);
  481. }
  482. else // CASE: Rerandomize existing vote
  483. {
  484. Proof currProof;
  485. Scalar u, commitmentLambda_1, commitmentLambda_2,
  486. c_n, z_na, z_nb, z_nc, f_1, f_2;
  487. u.set_random();
  488. commitmentLambda_1.set_random();
  489. commitmentLambda_2.set_random();
  490. c_n.set_random();
  491. z_na.set_random();
  492. z_nb.set_random();
  493. z_nc.set_random();
  494. f_1.set_random();
  495. f_2.set_random();
  496. CurveBipoint U =
  497. serverPublicKey.get_bipoint_curve_subgroup_gen() * u;
  498. CurveBipoint C_a = serverPublicKey.get_bipoint_curvegen() * f_1 +
  499. serverPublicKey.get_bipoint_curve_subgroup_gen() * z_na -
  500. newEncryptedVotes[i] * c_n;
  501. CurveBipoint C_b = serverPublicKey.get_bipoint_curvegen() * f_2 +
  502. serverPublicKey.get_bipoint_curve_subgroup_gen() * z_nb -
  503. newEncryptedVotes[i] * c_n;
  504. CurveBipoint C_c =
  505. serverPublicKey.get_bipoint_curvegen() * commitmentLambda_1 +
  506. serverPublicKey.get_bipoint_curve_subgroup_gen() *
  507. commitmentLambda_2;
  508. currProof.partialUniversals.push_back(C_c[0]);
  509. currProof.partialUniversals.push_back(C_c[1]);
  510. Scalar f_1_c_n = f_1.curveSub(c_n);
  511. Scalar c_n_f_2 = c_n.curveAdd(c_n).curveSub(f_2);
  512. CurveBipoint C_d =
  513. serverPublicKey.get_bipoint_curve_subgroup_gen() * z_nc -
  514. newEncryptedVotes[i] * f_1_c_n.curveMult(c_n_f_2) -
  515. C_c * c_n;
  516. oracleInput << U << C_a << C_b << C_c << C_d;
  517. Scalar c = oracle(oracleInput.str());
  518. Scalar c_r = c.curveSub(c_n);
  519. currProof.challengeParts.push_back(c_r);
  520. currProof.challengeParts.push_back(c_n);
  521. Scalar z_r = u.curveAdd(c_r.curveMult(seeds[i]));
  522. currProof.responseParts.push_back(z_r);
  523. currProof.responseParts.push_back(f_1);
  524. currProof.responseParts.push_back(f_2);
  525. currProof.responseParts.push_back(z_na);
  526. currProof.responseParts.push_back(z_nb);
  527. currProof.responseParts.push_back(z_nc);
  528. retval.push_back(currProof);
  529. }
  530. }
  531. return retval;
  532. }