redeem_invite.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. /*! A module for the protocol for a new user to redeem an Invitation
  2. credential. The user will start at trust level 1 (instead of 0 for
  3. untrusted uninvited users).
  4. The user presents the Invitation credential:
  5. - id: revealed
  6. - date: blinded, but proved in ZK to be at most INVITATION_EXPIRY days ago
  7. - bucket: blinded
  8. - blockages: blinded
  9. and a new Lox credential to be issued:
  10. - id: jointly chosen by the user and BA
  11. - bucket: blinded, but proved in ZK that it's the same as in the
  12. Invitation credential above
  13. - trust_level: revealed to be 1
  14. - level_since: today
  15. - invites_remaining: revealed to be 0
  16. - blockages: blinded, but proved in ZK that it's the same as in the
  17. Invitations credential above
  18. */
  19. use curve25519_dalek::ristretto::RistrettoBasepointTable;
  20. use curve25519_dalek::ristretto::RistrettoPoint;
  21. use curve25519_dalek::scalar::Scalar;
  22. use curve25519_dalek::traits::IsIdentity;
  23. use zkp::CompactProof;
  24. use zkp::ProofError;
  25. use zkp::Transcript;
  26. use serde::{Deserialize, Serialize};
  27. use super::super::cred;
  28. use super::super::dup_filter::SeenType;
  29. use super::super::{pt_dbl, scalar_dbl, scalar_u32};
  30. use super::super::{BridgeAuth, IssuerPubKey};
  31. use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
  32. /// Invitations must be used within this many days of being issued.
  33. /// Note that if you change this number to be larger than 15, you must
  34. /// also add bits to the zero knowledge proof.
  35. pub const INVITATION_EXPIRY: u32 = 15;
  36. #[derive(Serialize, Deserialize)]
  37. pub struct Request {
  38. // Fields for showing the Invitation credential
  39. P: RistrettoPoint,
  40. inv_id: Scalar,
  41. CDate: RistrettoPoint,
  42. CBucket: RistrettoPoint,
  43. CBlockages: RistrettoPoint,
  44. CQ: RistrettoPoint,
  45. // Fields for the inequality proof
  46. // date + INVITATION_EXPIRY >= today
  47. CG1: RistrettoPoint,
  48. CG2: RistrettoPoint,
  49. CG3: RistrettoPoint,
  50. CG0sq: RistrettoPoint,
  51. CG1sq: RistrettoPoint,
  52. CG2sq: RistrettoPoint,
  53. CG3sq: RistrettoPoint,
  54. // Fields for user blinding of the Lox credential to be issued
  55. D: RistrettoPoint,
  56. EncIdClient: (RistrettoPoint, RistrettoPoint),
  57. EncBucket: (RistrettoPoint, RistrettoPoint),
  58. EncBlockages: (RistrettoPoint, RistrettoPoint),
  59. // The combined ZKP
  60. piUser: CompactProof,
  61. }
  62. #[derive(Debug)]
  63. pub struct State {
  64. d: Scalar,
  65. D: RistrettoPoint,
  66. EncIdClient: (RistrettoPoint, RistrettoPoint),
  67. EncBucket: (RistrettoPoint, RistrettoPoint),
  68. EncBlockages: (RistrettoPoint, RistrettoPoint),
  69. id_client: Scalar,
  70. bucket: Scalar,
  71. blockages: Scalar,
  72. }
  73. #[derive(Serialize, Deserialize)]
  74. pub struct Response {
  75. // The fields for the new Lox credential; the new trust level is 1
  76. // and the new invites_remaining is 0, so we don't have to include
  77. // them here explicitly
  78. P: RistrettoPoint,
  79. EncQ: (RistrettoPoint, RistrettoPoint),
  80. id_server: Scalar,
  81. level_since: Scalar,
  82. TId: RistrettoPoint,
  83. TBucket: RistrettoPoint,
  84. TBlockages: RistrettoPoint,
  85. // The ZKP
  86. piBlindIssue: CompactProof,
  87. }
  88. define_proof! {
  89. requestproof,
  90. "Redeem Invite Request",
  91. (date, bucket, blockages, zdate, zbucket, zblockages, negzQ,
  92. d, eid_client, ebucket, eblockages, id_client,
  93. g0, g1, g2, g3,
  94. zg0, zg1, zg2, zg3,
  95. wg0, wg1, wg2, wg3,
  96. yg0, yg1, yg2, yg3),
  97. (P, CDate, CBucket, CBlockages, V, Xdate, Xbucket, Xblockages,
  98. D, EncIdClient0, EncIdClient1, EncBucket0, EncBucket1,
  99. EncBlockages0, EncBlockages1,
  100. CG0, CG1, CG2, CG3,
  101. CG0sq, CG1sq, CG2sq, CG3sq),
  102. (A, B):
  103. // Blind showing of the Invitation credential
  104. CDate = (date*P + zdate*A),
  105. CBucket = (bucket*P + zbucket*A),
  106. CBlockages = (blockages*P + zblockages*A),
  107. // User blinding of the Lox credential to be issued
  108. D = (d*B),
  109. EncIdClient0 = (eid_client*B),
  110. EncIdClient1 = (id_client*B + eid_client*D),
  111. EncBucket0 = (ebucket*B),
  112. EncBucket1 = (bucket*B + ebucket*D),
  113. EncBlockages0 = (eblockages*B),
  114. EncBlockages1 = (blockages*B + eblockages*D),
  115. // Prove CDate encodes a value at most INVITATION_EXPIRY
  116. // days ago: first prove each of g0, ..., g3 is a bit by
  117. // proving that gi = gi^2
  118. CG0 = (g0*P + zg0*A), CG0sq = (g0*CG0 + wg0*A), CG0sq = (g0*P + yg0*A),
  119. CG1 = (g1*P + zg1*A), CG1sq = (g1*CG1 + wg1*A), CG1sq = (g1*P + yg1*A),
  120. CG2 = (g2*P + zg2*A), CG2sq = (g2*CG2 + wg2*A), CG2sq = (g2*P + yg2*A),
  121. CG3 = (g3*P + zg3*A), CG3sq = (g3*CG3 + wg3*A), CG3sq = (g3*P + yg3*A)
  122. // Then we'll check that today*P + CG0 + 2*CG1 + 4*CG2 + 8*CG3 =
  123. // CDate + INVITATION_EXPIRY*P by having the verifier
  124. // plug in CDate + INVITATION_EXPIRY*P - (today*P + 2*CG1 + 4*CG2
  125. // + 8*CG3) as its value of CG0.
  126. }
  127. define_proof! {
  128. blindissue,
  129. "Redeem Invite Issuing",
  130. (x0, x0tilde, xid, xbucket, xlevel, xsince, xblockages,
  131. s, b, tid, tbucket, tblockages),
  132. (P, EncQ0, EncQ1, X0, Xid, Xbucket, Xlevel, Xsince, Xblockages,
  133. Psince, TId, TBucket, TBlockages,
  134. D, EncId0, EncId1, EncBucket0, EncBucket1, EncBlockages0, EncBlockages1),
  135. (A, B):
  136. Xid = (xid*A),
  137. Xbucket = (xbucket*A),
  138. Xlevel = (xlevel*A),
  139. Xsince = (xsince*A),
  140. Xblockages = (xblockages*A),
  141. X0 = (x0*B + x0tilde*A),
  142. P = (b*B),
  143. TId = (b*Xid),
  144. TId = (tid*A),
  145. TBucket = (b*Xbucket),
  146. TBucket = (tbucket*A),
  147. TBlockages = (b*Xblockages),
  148. TBlockages = (tblockages*A),
  149. EncQ0 = (s*B + tid*EncId0 + tbucket*EncBucket0 + tblockages*EncBlockages0),
  150. // level=1 (so Plevel = P) and invremain=0 (so the term is omitted)
  151. EncQ1 = (s*D + tid*EncId1 + tbucket*EncBucket1
  152. + tblockages*EncBlockages1 + x0*P + xlevel*P + xsince*Psince)
  153. }
  154. pub fn request(
  155. inv_cred: &cred::Invitation,
  156. invitation_pub: &IssuerPubKey,
  157. today: u32,
  158. ) -> Result<(Request, State), ProofError> {
  159. let A: &RistrettoPoint = &CMZ_A;
  160. let B: &RistrettoPoint = &CMZ_B;
  161. let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
  162. let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
  163. // Ensure the credential can be correctly shown: it must be the case
  164. // that date + INVITATION_EXPIRY >= today.
  165. let date: u32 = match scalar_u32(&inv_cred.date) {
  166. Some(v) => v,
  167. None => return Err(ProofError::VerificationFailure),
  168. };
  169. if date + INVITATION_EXPIRY < today {
  170. return Err(ProofError::VerificationFailure);
  171. }
  172. let diffdays = date + INVITATION_EXPIRY - today;
  173. // If diffdays > 15, then since INVITATION_EXPIRY <= 15, then date
  174. // must be in the future. Reject.
  175. if diffdays > 15 {
  176. return Err(ProofError::VerificationFailure);
  177. }
  178. // Blind showing the Invitation credential
  179. // Reblind P and Q
  180. let mut rng = rand::thread_rng();
  181. let t = Scalar::random(&mut rng);
  182. let P = t * inv_cred.P;
  183. let Q = t * inv_cred.Q;
  184. // Form Pedersen commitments to the blinded attributes
  185. let zdate = Scalar::random(&mut rng);
  186. let zbucket = Scalar::random(&mut rng);
  187. let zblockages = Scalar::random(&mut rng);
  188. let CDate = inv_cred.date * P + &zdate * Atable;
  189. let CBucket = inv_cred.bucket * P + &zbucket * Atable;
  190. let CBlockages = inv_cred.blockages * P + &zblockages * Atable;
  191. // Form a Pedersen commitment to the MAC Q
  192. // We flip the sign of zQ from that of the Hyphae paper so that
  193. // the ZKP has a "+" instead of a "-", as that's what the zkp
  194. // macro supports.
  195. let negzQ = Scalar::random(&mut rng);
  196. let CQ = Q - &negzQ * Atable;
  197. // Compute the "error factor"
  198. let V = zdate * invitation_pub.X[2]
  199. + zbucket * invitation_pub.X[3]
  200. + zblockages * invitation_pub.X[4]
  201. + &negzQ * Atable;
  202. // User blinding for the Lox certificate to be issued
  203. // Pick an ElGamal keypair
  204. let d = Scalar::random(&mut rng);
  205. let D = &d * Btable;
  206. // Pick a random client component of the id
  207. let id_client = Scalar::random(&mut rng);
  208. // Encrypt it (times the basepoint B) to the ElGamal public key D we
  209. // just created
  210. let eid_client = Scalar::random(&mut rng);
  211. let EncIdClient = (&eid_client * Btable, &id_client * Btable + eid_client * D);
  212. // Encrypt the other blinded fields (times B) to D as well
  213. let ebucket = Scalar::random(&mut rng);
  214. let EncBucket = (&ebucket * Btable, &inv_cred.bucket * Btable + ebucket * D);
  215. let eblockages = Scalar::random(&mut rng);
  216. let EncBlockages = (
  217. &eblockages * Btable,
  218. &inv_cred.blockages * Btable + eblockages * D,
  219. );
  220. // The range proof that 0 <= diffdays <= 15
  221. // Extract the 4 bits from diffdays
  222. let g0: Scalar = (diffdays & 1).into();
  223. let g1: Scalar = ((diffdays >> 1) & 1).into();
  224. let g2: Scalar = ((diffdays >> 2) & 1).into();
  225. let g3: Scalar = ((diffdays >> 3) & 1).into();
  226. // Pick random factors for the Pedersen commitments
  227. let wg0 = Scalar::random(&mut rng);
  228. let zg1 = Scalar::random(&mut rng);
  229. let wg1 = Scalar::random(&mut rng);
  230. let zg2 = Scalar::random(&mut rng);
  231. let wg2 = Scalar::random(&mut rng);
  232. let zg3 = Scalar::random(&mut rng);
  233. let wg3 = Scalar::random(&mut rng);
  234. // Compute zg0 to cancel things out as
  235. // zg0 = zdate - (2*zg1 + 4*zg2 + 8*zg3)
  236. // but use Horner's method
  237. let zg0 = zdate - scalar_dbl(&(scalar_dbl(&(scalar_dbl(&zg3) + zg2)) + zg1));
  238. let yg0 = wg0 + g0 * zg0;
  239. let yg1 = wg1 + g1 * zg1;
  240. let yg2 = wg2 + g2 * zg2;
  241. let yg3 = wg3 + g3 * zg3;
  242. let CG0 = g0 * P + &zg0 * Atable;
  243. let CG1 = g1 * P + &zg1 * Atable;
  244. let CG2 = g2 * P + &zg2 * Atable;
  245. let CG3 = g3 * P + &zg3 * Atable;
  246. let CG0sq = g0 * P + &yg0 * Atable;
  247. let CG1sq = g1 * P + &yg1 * Atable;
  248. let CG2sq = g2 * P + &yg2 * Atable;
  249. let CG3sq = g3 * P + &yg3 * Atable;
  250. // Construct the proof
  251. let mut transcript = Transcript::new(b"redeem invite request");
  252. let piUser = requestproof::prove_compact(
  253. &mut transcript,
  254. requestproof::ProveAssignments {
  255. A,
  256. B,
  257. P: &P,
  258. CDate: &CDate,
  259. CBucket: &CBucket,
  260. CBlockages: &CBlockages,
  261. V: &V,
  262. Xdate: &invitation_pub.X[2],
  263. Xbucket: &invitation_pub.X[3],
  264. Xblockages: &invitation_pub.X[4],
  265. D: &D,
  266. EncIdClient0: &EncIdClient.0,
  267. EncIdClient1: &EncIdClient.1,
  268. EncBucket0: &EncBucket.0,
  269. EncBucket1: &EncBucket.1,
  270. EncBlockages0: &EncBlockages.0,
  271. EncBlockages1: &EncBlockages.1,
  272. CG0: &CG0,
  273. CG1: &CG1,
  274. CG2: &CG2,
  275. CG3: &CG3,
  276. CG0sq: &CG0sq,
  277. CG1sq: &CG1sq,
  278. CG2sq: &CG2sq,
  279. CG3sq: &CG3sq,
  280. date: &inv_cred.date,
  281. bucket: &inv_cred.bucket,
  282. blockages: &inv_cred.blockages,
  283. zdate: &zdate,
  284. zbucket: &zbucket,
  285. zblockages: &zblockages,
  286. negzQ: &negzQ,
  287. d: &d,
  288. eid_client: &eid_client,
  289. ebucket: &ebucket,
  290. eblockages: &eblockages,
  291. id_client: &id_client,
  292. g0: &g0,
  293. g1: &g1,
  294. g2: &g2,
  295. g3: &g3,
  296. zg0: &zg0,
  297. zg1: &zg1,
  298. zg2: &zg2,
  299. zg3: &zg3,
  300. wg0: &wg0,
  301. wg1: &wg1,
  302. wg2: &wg2,
  303. wg3: &wg3,
  304. yg0: &yg0,
  305. yg1: &yg1,
  306. yg2: &yg2,
  307. yg3: &yg3,
  308. },
  309. )
  310. .0;
  311. Ok((
  312. Request {
  313. P,
  314. inv_id: inv_cred.inv_id,
  315. CDate,
  316. CBucket,
  317. CBlockages,
  318. CQ,
  319. D,
  320. EncIdClient,
  321. EncBucket,
  322. EncBlockages,
  323. CG1,
  324. CG2,
  325. CG3,
  326. CG0sq,
  327. CG1sq,
  328. CG2sq,
  329. CG3sq,
  330. piUser,
  331. },
  332. State {
  333. d,
  334. D,
  335. EncIdClient,
  336. EncBucket,
  337. EncBlockages,
  338. id_client,
  339. bucket: inv_cred.bucket,
  340. blockages: inv_cred.blockages,
  341. },
  342. ))
  343. }
  344. impl BridgeAuth {
  345. /// Receive a redeem invite request
  346. pub fn handle_redeem_invite(&mut self, req: Request) -> Result<Response, ProofError> {
  347. let A: &RistrettoPoint = &CMZ_A;
  348. let B: &RistrettoPoint = &CMZ_B;
  349. let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
  350. let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
  351. if req.P.is_identity() {
  352. return Err(ProofError::VerificationFailure);
  353. }
  354. let today: Scalar = self.today().into();
  355. // Recompute the "error factor" using knowledge of our own
  356. // (the issuer's) private key instead of knowledge of the
  357. // hidden attributes
  358. let Vprime = (self.invitation_priv.x[0] + self.invitation_priv.x[1] * req.inv_id) * req.P
  359. + self.invitation_priv.x[2] * req.CDate
  360. + self.invitation_priv.x[3] * req.CBucket
  361. + self.invitation_priv.x[4] * req.CBlockages
  362. - req.CQ;
  363. // Recompute CG0 using Horner's method
  364. let expiry: Scalar = INVITATION_EXPIRY.into();
  365. let CG0prime = (expiry - today) * req.P + req.CDate
  366. - pt_dbl(&(pt_dbl(&(pt_dbl(&req.CG3) + req.CG2)) + req.CG1));
  367. // Verify the ZKP
  368. let mut transcript = Transcript::new(b"redeem invite request");
  369. requestproof::verify_compact(
  370. &req.piUser,
  371. &mut transcript,
  372. requestproof::VerifyAssignments {
  373. A: &A.compress(),
  374. B: &B.compress(),
  375. P: &req.P.compress(),
  376. CDate: &req.CDate.compress(),
  377. CBucket: &req.CBucket.compress(),
  378. CBlockages: &req.CBlockages.compress(),
  379. V: &Vprime.compress(),
  380. Xdate: &self.invitation_pub.X[2].compress(),
  381. Xbucket: &self.invitation_pub.X[3].compress(),
  382. Xblockages: &self.invitation_pub.X[4].compress(),
  383. D: &req.D.compress(),
  384. EncIdClient0: &req.EncIdClient.0.compress(),
  385. EncIdClient1: &req.EncIdClient.1.compress(),
  386. EncBucket0: &req.EncBucket.0.compress(),
  387. EncBucket1: &req.EncBucket.1.compress(),
  388. EncBlockages0: &req.EncBlockages.0.compress(),
  389. EncBlockages1: &req.EncBlockages.1.compress(),
  390. CG0: &CG0prime.compress(),
  391. CG1: &req.CG1.compress(),
  392. CG2: &req.CG2.compress(),
  393. CG3: &req.CG3.compress(),
  394. CG0sq: &req.CG0sq.compress(),
  395. CG1sq: &req.CG1sq.compress(),
  396. CG2sq: &req.CG2sq.compress(),
  397. CG3sq: &req.CG3sq.compress(),
  398. },
  399. )?;
  400. // Ensure the id has not been seen before, and add it to the
  401. // invite id seen list.
  402. if self.inv_id_filter.filter(&req.inv_id) == SeenType::Seen {
  403. return Err(ProofError::VerificationFailure);
  404. }
  405. // Blind issuing of the new Lox credential
  406. // Choose a random server id component to add to the client's
  407. // (blinded) id component
  408. let mut rng = rand::thread_rng();
  409. let id_server = Scalar::random(&mut rng);
  410. let EncId = (req.EncIdClient.0, req.EncIdClient.1 + &id_server * Btable);
  411. // The trust level for invitees is always 1
  412. let level = Scalar::one();
  413. // The invites remaining for invitees is always 0 (as
  414. // appropriate for trust level 1), so we don't need to actually
  415. // construct it
  416. // Compute the MAC on the visible attributes
  417. let b = Scalar::random(&mut rng);
  418. let P = &b * Btable;
  419. let QHc =
  420. (self.lox_priv.x[0] + self.lox_priv.x[3] * level + self.lox_priv.x[4] * today) * P;
  421. // El Gamal encrypt it to the public key req.D
  422. let s = Scalar::random(&mut rng);
  423. let EncQHc = (&s * Btable, QHc + s * req.D);
  424. // Homomorphically compute the part of the MAC corresponding to
  425. // the blinded attributes
  426. let tid = self.lox_priv.x[1] * b;
  427. let TId = &tid * Atable;
  428. let EncQId = (tid * EncId.0, tid * EncId.1);
  429. let tbucket = self.lox_priv.x[2] * b;
  430. let TBucket = &tbucket * Atable;
  431. let EncQBucket = (tbucket * req.EncBucket.0, tbucket * req.EncBucket.1);
  432. let tblockages = self.lox_priv.x[6] * b;
  433. let TBlockages = &tblockages * Atable;
  434. let EncQBlockages = (
  435. tblockages * req.EncBlockages.0,
  436. tblockages * req.EncBlockages.1,
  437. );
  438. let EncQ = (
  439. EncQHc.0 + EncQId.0 + EncQBucket.0 + EncQBlockages.0,
  440. EncQHc.1 + EncQId.1 + EncQBucket.1 + EncQBlockages.1,
  441. );
  442. let mut transcript = Transcript::new(b"redeem invite issuing");
  443. let piBlindIssue = blindissue::prove_compact(
  444. &mut transcript,
  445. blindissue::ProveAssignments {
  446. A,
  447. B,
  448. P: &P,
  449. EncQ0: &EncQ.0,
  450. EncQ1: &EncQ.1,
  451. X0: &self.lox_pub.X[0],
  452. Xid: &self.lox_pub.X[1],
  453. Xbucket: &self.lox_pub.X[2],
  454. Xlevel: &self.lox_pub.X[3],
  455. Xsince: &self.lox_pub.X[4],
  456. Xblockages: &self.lox_pub.X[6],
  457. Psince: &(today * P),
  458. TId: &TId,
  459. TBucket: &TBucket,
  460. TBlockages: &TBlockages,
  461. D: &req.D,
  462. EncId0: &EncId.0,
  463. EncId1: &EncId.1,
  464. EncBucket0: &req.EncBucket.0,
  465. EncBucket1: &req.EncBucket.1,
  466. EncBlockages0: &req.EncBlockages.0,
  467. EncBlockages1: &req.EncBlockages.1,
  468. x0: &self.lox_priv.x[0],
  469. x0tilde: &self.lox_priv.x0tilde,
  470. xid: &self.lox_priv.x[1],
  471. xbucket: &self.lox_priv.x[2],
  472. xlevel: &self.lox_priv.x[3],
  473. xsince: &self.lox_priv.x[4],
  474. xblockages: &self.lox_priv.x[6],
  475. s: &s,
  476. b: &b,
  477. tid: &tid,
  478. tbucket: &tbucket,
  479. tblockages: &tblockages,
  480. },
  481. )
  482. .0;
  483. Ok(Response {
  484. P,
  485. EncQ,
  486. id_server,
  487. level_since: today,
  488. TId,
  489. TBucket,
  490. TBlockages,
  491. piBlindIssue,
  492. })
  493. }
  494. }
  495. /// Handle the response to the request, producing the new Lox credential
  496. /// if successful.
  497. pub fn handle_response(
  498. state: State,
  499. resp: Response,
  500. lox_pub: &IssuerPubKey,
  501. ) -> Result<cred::Lox, ProofError> {
  502. let A: &RistrettoPoint = &CMZ_A;
  503. let B: &RistrettoPoint = &CMZ_B;
  504. let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
  505. if resp.P.is_identity() {
  506. return Err(ProofError::VerificationFailure);
  507. }
  508. // Add the server's contribution to the id to our own, both in plain
  509. // and encrypted form
  510. let id = state.id_client + resp.id_server;
  511. let EncId = (
  512. state.EncIdClient.0,
  513. state.EncIdClient.1 + &resp.id_server * Btable,
  514. );
  515. // Verify the proof
  516. let mut transcript = Transcript::new(b"redeem invite issuing");
  517. blindissue::verify_compact(
  518. &resp.piBlindIssue,
  519. &mut transcript,
  520. blindissue::VerifyAssignments {
  521. A: &A.compress(),
  522. B: &B.compress(),
  523. P: &resp.P.compress(),
  524. EncQ0: &resp.EncQ.0.compress(),
  525. EncQ1: &resp.EncQ.1.compress(),
  526. X0: &lox_pub.X[0].compress(),
  527. Xid: &lox_pub.X[1].compress(),
  528. Xbucket: &lox_pub.X[2].compress(),
  529. Xlevel: &lox_pub.X[3].compress(),
  530. Xsince: &lox_pub.X[4].compress(),
  531. Xblockages: &lox_pub.X[6].compress(),
  532. Psince: &(resp.level_since * resp.P).compress(),
  533. TId: &resp.TId.compress(),
  534. TBucket: &resp.TBucket.compress(),
  535. TBlockages: &resp.TBlockages.compress(),
  536. D: &state.D.compress(),
  537. EncId0: &EncId.0.compress(),
  538. EncId1: &EncId.1.compress(),
  539. EncBucket0: &state.EncBucket.0.compress(),
  540. EncBucket1: &state.EncBucket.1.compress(),
  541. EncBlockages0: &state.EncBlockages.0.compress(),
  542. EncBlockages1: &state.EncBlockages.1.compress(),
  543. },
  544. )?;
  545. // Decrypt EncQ
  546. let Q = resp.EncQ.1 - (state.d * resp.EncQ.0);
  547. Ok(cred::Lox {
  548. P: resp.P,
  549. Q,
  550. id,
  551. bucket: state.bucket,
  552. trust_level: Scalar::one(),
  553. level_since: resp.level_since,
  554. invites_remaining: Scalar::zero(),
  555. blockages: state.blockages,
  556. })
  557. }