1use curve25519_dalek::ristretto::RistrettoBasepointTable;
25use curve25519_dalek::ristretto::RistrettoPoint;
26use curve25519_dalek::scalar::Scalar;
27use curve25519_dalek::traits::IsIdentity;
28
29use lox_zkp::CompactProof;
30use lox_zkp::ProofError;
31use lox_zkp::Transcript;
32
33use serde::{Deserialize, Serialize};
34
35use super::super::cred;
36#[cfg(feature = "bridgeauth")]
37use super::super::dup_filter::SeenType;
38#[cfg(feature = "bridgeauth")]
39use super::super::pt_dbl;
40#[cfg(feature = "bridgeauth")]
41use super::super::BridgeAuth;
42use super::super::IssuerPubKey;
43use super::super::{scalar_dbl, scalar_u32};
44use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
45
46use super::errors::CredentialError;
47
48pub const INVITATION_EXPIRY: u32 = 15;
52
53#[derive(Serialize, Deserialize)]
54pub struct Request {
55 P: RistrettoPoint,
57 inv_id: Scalar,
58 CDate: RistrettoPoint,
59 CBucket: RistrettoPoint,
60 CBlockages: RistrettoPoint,
61 CQ: RistrettoPoint,
62
63 CG1: RistrettoPoint,
66 CG2: RistrettoPoint,
67 CG3: RistrettoPoint,
68 CG0sq: RistrettoPoint,
69 CG1sq: RistrettoPoint,
70 CG2sq: RistrettoPoint,
71 CG3sq: RistrettoPoint,
72
73 D: RistrettoPoint,
75 EncIdClient: (RistrettoPoint, RistrettoPoint),
76 EncBucket: (RistrettoPoint, RistrettoPoint),
77 EncBlockages: (RistrettoPoint, RistrettoPoint),
78
79 piUser: CompactProof,
81}
82
83#[derive(Debug, Serialize, Deserialize)]
84pub struct State {
85 d: Scalar,
86 D: RistrettoPoint,
87 EncIdClient: (RistrettoPoint, RistrettoPoint),
88 EncBucket: (RistrettoPoint, RistrettoPoint),
89 EncBlockages: (RistrettoPoint, RistrettoPoint),
90 id_client: Scalar,
91 bucket: Scalar,
92 blockages: Scalar,
93}
94
95#[derive(Serialize, Deserialize)]
96pub struct Response {
97 P: RistrettoPoint,
101 EncQ: (RistrettoPoint, RistrettoPoint),
102 id_server: Scalar,
103 level_since: Scalar,
104 TId: RistrettoPoint,
105 TBucket: RistrettoPoint,
106 TBlockages: RistrettoPoint,
107
108 piBlindIssue: CompactProof,
110}
111
112define_proof! {
113 requestproof,
114 "Redeem Invite Request",
115 (date, bucket, blockages, zdate, zbucket, zblockages, negzQ,
116 d, eid_client, ebucket, eblockages, id_client,
117 g0, g1, g2, g3,
118 zg0, zg1, zg2, zg3,
119 wg0, wg1, wg2, wg3,
120 yg0, yg1, yg2, yg3),
121 (P, CDate, CBucket, CBlockages, V, Xdate, Xbucket, Xblockages,
122 D, EncIdClient0, EncIdClient1, EncBucket0, EncBucket1,
123 EncBlockages0, EncBlockages1,
124 CG0, CG1, CG2, CG3,
125 CG0sq, CG1sq, CG2sq, CG3sq),
126 (A, B):
127 CDate = (date*P + zdate*A),
129 CBucket = (bucket*P + zbucket*A),
130 CBlockages = (blockages*P + zblockages*A),
131 D = (d*B),
133 EncIdClient0 = (eid_client*B),
134 EncIdClient1 = (id_client*B + eid_client*D),
135 EncBucket0 = (ebucket*B),
136 EncBucket1 = (bucket*B + ebucket*D),
137 EncBlockages0 = (eblockages*B),
138 EncBlockages1 = (blockages*B + eblockages*D),
139 CG0 = (g0*P + zg0*A), CG0sq = (g0*CG0 + wg0*A), CG0sq = (g0*P + yg0*A),
143 CG1 = (g1*P + zg1*A), CG1sq = (g1*CG1 + wg1*A), CG1sq = (g1*P + yg1*A),
144 CG2 = (g2*P + zg2*A), CG2sq = (g2*CG2 + wg2*A), CG2sq = (g2*P + yg2*A),
145 CG3 = (g3*P + zg3*A), CG3sq = (g3*CG3 + wg3*A), CG3sq = (g3*P + yg3*A)
146 }
151
152define_proof! {
153 blindissue,
154 "Redeem Invite Issuing",
155 (x0, x0tilde, xid, xbucket, xlevel, xsince, xblockages,
156 s, b, tid, tbucket, tblockages),
157 (P, EncQ0, EncQ1, X0, Xid, Xbucket, Xlevel, Xsince, Xblockages,
158 Psince, TId, TBucket, TBlockages,
159 D, EncId0, EncId1, EncBucket0, EncBucket1, EncBlockages0, EncBlockages1),
160 (A, B):
161 Xid = (xid*A),
162 Xbucket = (xbucket*A),
163 Xlevel = (xlevel*A),
164 Xsince = (xsince*A),
165 Xblockages = (xblockages*A),
166 X0 = (x0*B + x0tilde*A),
167 P = (b*B),
168 TId = (b*Xid),
169 TId = (tid*A),
170 TBucket = (b*Xbucket),
171 TBucket = (tbucket*A),
172 TBlockages = (b*Xblockages),
173 TBlockages = (tblockages*A),
174 EncQ0 = (s*B + tid*EncId0 + tbucket*EncBucket0 + tblockages*EncBlockages0),
175 EncQ1 = (s*D + tid*EncId1 + tbucket*EncBucket1
177 + tblockages*EncBlockages1 + x0*P + xlevel*P + xsince*Psince)
178}
179
180pub fn request(
181 inv_cred: &cred::Invitation,
182 invitation_pub: &IssuerPubKey,
183 today: u32,
184) -> Result<(Request, State), CredentialError> {
185 let A: &RistrettoPoint = &CMZ_A;
186 let B: &RistrettoPoint = &CMZ_B;
187 let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
188 let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
189
190 let date: u32 = match scalar_u32(&inv_cred.date) {
193 Some(v) => v,
194 None => {
195 return Err(CredentialError::InvalidField(
196 String::from("date"),
197 String::from("could not be converted to u32"),
198 ))
199 }
200 };
201 if date + INVITATION_EXPIRY < today {
202 return Err(CredentialError::CredentialExpired);
203 }
204 let diffdays = date + INVITATION_EXPIRY - today;
205 if diffdays > 15 {
208 return Err(CredentialError::InvalidField(
209 String::from("date"),
210 String::from("credential was created in the future"),
211 ));
212 }
213
214 let mut rng = rand::thread_rng();
218 let t = Scalar::random(&mut rng);
219 let P = t * inv_cred.P;
220 let Q = t * inv_cred.Q;
221
222 let zdate = Scalar::random(&mut rng);
224 let zbucket = Scalar::random(&mut rng);
225 let zblockages = Scalar::random(&mut rng);
226 let CDate = inv_cred.date * P + &zdate * Atable;
227 let CBucket = inv_cred.bucket * P + &zbucket * Atable;
228 let CBlockages = inv_cred.blockages * P + &zblockages * Atable;
229
230 let negzQ = Scalar::random(&mut rng);
235 let CQ = Q - &negzQ * Atable;
236
237 let V = zdate * invitation_pub.X[2]
239 + zbucket * invitation_pub.X[3]
240 + zblockages * invitation_pub.X[4]
241 + &negzQ * Atable;
242
243 let d = Scalar::random(&mut rng);
247 let D = &d * Btable;
248
249 let id_client = Scalar::random(&mut rng);
251
252 let eid_client = Scalar::random(&mut rng);
255 let EncIdClient = (&eid_client * Btable, &id_client * Btable + eid_client * D);
256
257 let ebucket = Scalar::random(&mut rng);
259 let EncBucket = (&ebucket * Btable, &inv_cred.bucket * Btable + ebucket * D);
260 let eblockages = Scalar::random(&mut rng);
261 let EncBlockages = (
262 &eblockages * Btable,
263 &inv_cred.blockages * Btable + eblockages * D,
264 );
265
266 let g0: Scalar = (diffdays & 1).into();
270 let g1: Scalar = ((diffdays >> 1) & 1).into();
271 let g2: Scalar = ((diffdays >> 2) & 1).into();
272 let g3: Scalar = ((diffdays >> 3) & 1).into();
273
274 let wg0 = Scalar::random(&mut rng);
276 let zg1 = Scalar::random(&mut rng);
277 let wg1 = Scalar::random(&mut rng);
278 let zg2 = Scalar::random(&mut rng);
279 let wg2 = Scalar::random(&mut rng);
280 let zg3 = Scalar::random(&mut rng);
281 let wg3 = Scalar::random(&mut rng);
282
283 let zg0 = zdate - scalar_dbl(&(scalar_dbl(&(scalar_dbl(&zg3) + zg2)) + zg1));
287
288 let yg0 = wg0 + g0 * zg0;
289 let yg1 = wg1 + g1 * zg1;
290 let yg2 = wg2 + g2 * zg2;
291 let yg3 = wg3 + g3 * zg3;
292
293 let CG0 = g0 * P + &zg0 * Atable;
294 let CG1 = g1 * P + &zg1 * Atable;
295 let CG2 = g2 * P + &zg2 * Atable;
296 let CG3 = g3 * P + &zg3 * Atable;
297
298 let CG0sq = g0 * P + &yg0 * Atable;
299 let CG1sq = g1 * P + &yg1 * Atable;
300 let CG2sq = g2 * P + &yg2 * Atable;
301 let CG3sq = g3 * P + &yg3 * Atable;
302
303 let mut transcript = Transcript::new(b"redeem invite request");
305 let piUser = requestproof::prove_compact(
306 &mut transcript,
307 requestproof::ProveAssignments {
308 A,
309 B,
310 P: &P,
311 CDate: &CDate,
312 CBucket: &CBucket,
313 CBlockages: &CBlockages,
314 V: &V,
315 Xdate: &invitation_pub.X[2],
316 Xbucket: &invitation_pub.X[3],
317 Xblockages: &invitation_pub.X[4],
318 D: &D,
319 EncIdClient0: &EncIdClient.0,
320 EncIdClient1: &EncIdClient.1,
321 EncBucket0: &EncBucket.0,
322 EncBucket1: &EncBucket.1,
323 EncBlockages0: &EncBlockages.0,
324 EncBlockages1: &EncBlockages.1,
325 CG0: &CG0,
326 CG1: &CG1,
327 CG2: &CG2,
328 CG3: &CG3,
329 CG0sq: &CG0sq,
330 CG1sq: &CG1sq,
331 CG2sq: &CG2sq,
332 CG3sq: &CG3sq,
333 date: &inv_cred.date,
334 bucket: &inv_cred.bucket,
335 blockages: &inv_cred.blockages,
336 zdate: &zdate,
337 zbucket: &zbucket,
338 zblockages: &zblockages,
339 negzQ: &negzQ,
340 d: &d,
341 eid_client: &eid_client,
342 ebucket: &ebucket,
343 eblockages: &eblockages,
344 id_client: &id_client,
345 g0: &g0,
346 g1: &g1,
347 g2: &g2,
348 g3: &g3,
349 zg0: &zg0,
350 zg1: &zg1,
351 zg2: &zg2,
352 zg3: &zg3,
353 wg0: &wg0,
354 wg1: &wg1,
355 wg2: &wg2,
356 wg3: &wg3,
357 yg0: &yg0,
358 yg1: &yg1,
359 yg2: &yg2,
360 yg3: &yg3,
361 },
362 )
363 .0;
364
365 Ok((
366 Request {
367 P,
368 inv_id: inv_cred.inv_id,
369 CDate,
370 CBucket,
371 CBlockages,
372 CQ,
373 D,
374 EncIdClient,
375 EncBucket,
376 EncBlockages,
377 CG1,
378 CG2,
379 CG3,
380 CG0sq,
381 CG1sq,
382 CG2sq,
383 CG3sq,
384 piUser,
385 },
386 State {
387 d,
388 D,
389 EncIdClient,
390 EncBucket,
391 EncBlockages,
392 id_client,
393 bucket: inv_cred.bucket,
394 blockages: inv_cred.blockages,
395 },
396 ))
397}
398
399#[cfg(feature = "bridgeauth")]
400impl BridgeAuth {
401 pub fn handle_redeem_invite(&mut self, req: Request) -> Result<Response, ProofError> {
403 let A: &RistrettoPoint = &CMZ_A;
404 let B: &RistrettoPoint = &CMZ_B;
405 let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
406 let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
407
408 if req.P.is_identity() {
409 return Err(ProofError::VerificationFailure);
410 }
411
412 let today: Scalar = self.today().into();
413
414 let Vprime = (self.invitation_priv.x[0] + self.invitation_priv.x[1] * req.inv_id) * req.P
418 + self.invitation_priv.x[2] * req.CDate
419 + self.invitation_priv.x[3] * req.CBucket
420 + self.invitation_priv.x[4] * req.CBlockages
421 - req.CQ;
422
423 let expiry: Scalar = INVITATION_EXPIRY.into();
425 let CG0prime = (expiry - today) * req.P + req.CDate
426 - pt_dbl(&(pt_dbl(&(pt_dbl(&req.CG3) + req.CG2)) + req.CG1));
427
428 let mut transcript = Transcript::new(b"redeem invite request");
430 requestproof::verify_compact(
431 &req.piUser,
432 &mut transcript,
433 requestproof::VerifyAssignments {
434 A: &A.compress(),
435 B: &B.compress(),
436 P: &req.P.compress(),
437 CDate: &req.CDate.compress(),
438 CBucket: &req.CBucket.compress(),
439 CBlockages: &req.CBlockages.compress(),
440 V: &Vprime.compress(),
441 Xdate: &self.invitation_pub.X[2].compress(),
442 Xbucket: &self.invitation_pub.X[3].compress(),
443 Xblockages: &self.invitation_pub.X[4].compress(),
444 D: &req.D.compress(),
445 EncIdClient0: &req.EncIdClient.0.compress(),
446 EncIdClient1: &req.EncIdClient.1.compress(),
447 EncBucket0: &req.EncBucket.0.compress(),
448 EncBucket1: &req.EncBucket.1.compress(),
449 EncBlockages0: &req.EncBlockages.0.compress(),
450 EncBlockages1: &req.EncBlockages.1.compress(),
451 CG0: &CG0prime.compress(),
452 CG1: &req.CG1.compress(),
453 CG2: &req.CG2.compress(),
454 CG3: &req.CG3.compress(),
455 CG0sq: &req.CG0sq.compress(),
456 CG1sq: &req.CG1sq.compress(),
457 CG2sq: &req.CG2sq.compress(),
458 CG3sq: &req.CG3sq.compress(),
459 },
460 )?;
461
462 if self.inv_id_filter.filter(&req.inv_id) == SeenType::Seen {
465 return Err(ProofError::VerificationFailure);
466 }
467
468 let mut rng = rand::thread_rng();
473 let id_server = Scalar::random(&mut rng);
474 let EncId = (req.EncIdClient.0, req.EncIdClient.1 + &id_server * Btable);
475
476 let level = Scalar::ONE;
478
479 let b = Scalar::random(&mut rng);
485 let P = &b * Btable;
486 let QHc =
487 (self.lox_priv.x[0] + self.lox_priv.x[3] * level + self.lox_priv.x[4] * today) * P;
488
489 let s = Scalar::random(&mut rng);
491 let EncQHc = (&s * Btable, QHc + s * req.D);
492
493 let tid = self.lox_priv.x[1] * b;
496 let TId = &tid * Atable;
497 let EncQId = (tid * EncId.0, tid * EncId.1);
498 let tbucket = self.lox_priv.x[2] * b;
499 let TBucket = &tbucket * Atable;
500 let EncQBucket = (tbucket * req.EncBucket.0, tbucket * req.EncBucket.1);
501 let tblockages = self.lox_priv.x[6] * b;
502 let TBlockages = &tblockages * Atable;
503 let EncQBlockages = (
504 tblockages * req.EncBlockages.0,
505 tblockages * req.EncBlockages.1,
506 );
507
508 let EncQ = (
509 EncQHc.0 + EncQId.0 + EncQBucket.0 + EncQBlockages.0,
510 EncQHc.1 + EncQId.1 + EncQBucket.1 + EncQBlockages.1,
511 );
512
513 let mut transcript = Transcript::new(b"redeem invite issuing");
514 let piBlindIssue = blindissue::prove_compact(
515 &mut transcript,
516 blindissue::ProveAssignments {
517 A,
518 B,
519 P: &P,
520 EncQ0: &EncQ.0,
521 EncQ1: &EncQ.1,
522 X0: &self.lox_pub.X[0],
523 Xid: &self.lox_pub.X[1],
524 Xbucket: &self.lox_pub.X[2],
525 Xlevel: &self.lox_pub.X[3],
526 Xsince: &self.lox_pub.X[4],
527 Xblockages: &self.lox_pub.X[6],
528 Psince: &(today * P),
529 TId: &TId,
530 TBucket: &TBucket,
531 TBlockages: &TBlockages,
532 D: &req.D,
533 EncId0: &EncId.0,
534 EncId1: &EncId.1,
535 EncBucket0: &req.EncBucket.0,
536 EncBucket1: &req.EncBucket.1,
537 EncBlockages0: &req.EncBlockages.0,
538 EncBlockages1: &req.EncBlockages.1,
539 x0: &self.lox_priv.x[0],
540 x0tilde: &self.lox_priv.x0tilde,
541 xid: &self.lox_priv.x[1],
542 xbucket: &self.lox_priv.x[2],
543 xlevel: &self.lox_priv.x[3],
544 xsince: &self.lox_priv.x[4],
545 xblockages: &self.lox_priv.x[6],
546 s: &s,
547 b: &b,
548 tid: &tid,
549 tbucket: &tbucket,
550 tblockages: &tblockages,
551 },
552 )
553 .0;
554
555 Ok(Response {
556 P,
557 EncQ,
558 id_server,
559 level_since: today,
560 TId,
561 TBucket,
562 TBlockages,
563 piBlindIssue,
564 })
565 }
566}
567
568pub fn handle_response(
571 state: State,
572 resp: Response,
573 lox_pub: &IssuerPubKey,
574) -> Result<cred::Lox, ProofError> {
575 let A: &RistrettoPoint = &CMZ_A;
576 let B: &RistrettoPoint = &CMZ_B;
577 let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
578
579 if resp.P.is_identity() {
580 return Err(ProofError::VerificationFailure);
581 }
582
583 let id = state.id_client + resp.id_server;
586 let EncId = (
587 state.EncIdClient.0,
588 state.EncIdClient.1 + &resp.id_server * Btable,
589 );
590
591 let mut transcript = Transcript::new(b"redeem invite issuing");
593 blindissue::verify_compact(
594 &resp.piBlindIssue,
595 &mut transcript,
596 blindissue::VerifyAssignments {
597 A: &A.compress(),
598 B: &B.compress(),
599 P: &resp.P.compress(),
600 EncQ0: &resp.EncQ.0.compress(),
601 EncQ1: &resp.EncQ.1.compress(),
602 X0: &lox_pub.X[0].compress(),
603 Xid: &lox_pub.X[1].compress(),
604 Xbucket: &lox_pub.X[2].compress(),
605 Xlevel: &lox_pub.X[3].compress(),
606 Xsince: &lox_pub.X[4].compress(),
607 Xblockages: &lox_pub.X[6].compress(),
608 Psince: &(resp.level_since * resp.P).compress(),
609 TId: &resp.TId.compress(),
610 TBucket: &resp.TBucket.compress(),
611 TBlockages: &resp.TBlockages.compress(),
612 D: &state.D.compress(),
613 EncId0: &EncId.0.compress(),
614 EncId1: &EncId.1.compress(),
615 EncBucket0: &state.EncBucket.0.compress(),
616 EncBucket1: &state.EncBucket.1.compress(),
617 EncBlockages0: &state.EncBlockages.0.compress(),
618 EncBlockages1: &state.EncBlockages.1.compress(),
619 },
620 )?;
621
622 let Q = resp.EncQ.1 - (state.d * resp.EncQ.0);
624
625 Ok(cred::Lox {
626 P: resp.P,
627 Q,
628 id,
629 bucket: state.bucket,
630 trust_level: Scalar::ONE,
631 level_since: resp.level_since,
632 invites_remaining: Scalar::ZERO,
633 blockages: state.blockages,
634 })
635}