Skip to main content

lox_library/proto/
issue_invite.rs

1/*! A module for the protocol for a user to request the issuing of an
2Invitation credential they can pass to someone they know.
3
4They are allowed to do this as long as their current Lox credentials has
5a non-zero "invites_remaining" attribute (which will be decreased by
6one), and they have a Bucket Reachability credential for their current
7bucket and today's date.  (Such credentials are placed daily in the
8encrypted bridge table.)
9
10The user presents their current Lox credential:
11- id: revealed
12- bucket: blinded
13- trust_level: blinded
14- level_since: blinded
15- invites_remaining: blinded, but proved in ZK that it's not zero
16- blockages: blinded
17
18and a Bucket Reachability credential:
19- date: revealed to be today
20- bucket: blinded, but proved in ZK that it's the same as in the Lox
21  credential above
22
23and a new Lox credential to be issued:
24
25- id: jointly chosen by the user and BA
26- bucket: blinded, but proved in ZK that it's the same as in the Lox
27  credential above
28- trust_level: blinded, but proved in ZK that it's the same as in the
29  Lox credential above
30- level_since: blinded, but proved in ZK that it's the same as in the
31  Lox credential above
32- invites_remaining: blinded, but proved in ZK that it's one less than
33  the number in the Lox credential above
34- blockages: blinded, but proved in ZK that it's the same as in the
35  Lox credential above
36
37and a new Invitation credential to be issued:
38
39- inv_id: jointly chosen by the user and BA
40- date: revealed to be today
41- bucket: blinded, but proved in ZK that it's the same as in the Lox
42  credential above
43- blockages: blinded, but proved in ZK that it's the same as in the Lox
44  credential above
45
46*/
47
48use curve25519_dalek::ristretto::RistrettoBasepointTable;
49use curve25519_dalek::ristretto::RistrettoPoint;
50use curve25519_dalek::scalar::Scalar;
51use curve25519_dalek::traits::IsIdentity;
52
53use lox_zkp::CompactProof;
54use lox_zkp::ProofError;
55use lox_zkp::Transcript;
56
57use serde::{Deserialize, Serialize};
58
59use super::super::cred;
60#[cfg(feature = "bridgeauth")]
61use super::super::dup_filter::SeenType;
62use super::super::scalar_u32;
63#[cfg(feature = "bridgeauth")]
64use super::super::BridgeAuth;
65use super::super::IssuerPubKey;
66use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
67
68use super::errors::CredentialError;
69
70#[derive(Serialize, Deserialize)]
71pub struct Request {
72    // Fields for blind showing the Lox credential
73    P: RistrettoPoint,
74    id: Scalar,
75    CBucket: RistrettoPoint,
76    CLevel: RistrettoPoint,
77    CSince: RistrettoPoint,
78    CInvRemain: RistrettoPoint,
79    CBlockages: RistrettoPoint,
80    CQ: RistrettoPoint,
81
82    // Fields for blind showing the Bucket Reachability credential
83    P_reach: RistrettoPoint,
84    CBucket_reach: RistrettoPoint,
85    CQ_reach: RistrettoPoint,
86
87    // Fields for user blinding of the Lox credential to be issued
88    D: RistrettoPoint,
89    EncIdClient: (RistrettoPoint, RistrettoPoint),
90    EncBucket: (RistrettoPoint, RistrettoPoint),
91    EncLevel: (RistrettoPoint, RistrettoPoint),
92    EncSince: (RistrettoPoint, RistrettoPoint),
93    EncInvRemain: (RistrettoPoint, RistrettoPoint),
94    EncBlockages: (RistrettoPoint, RistrettoPoint),
95
96    // Fields for user blinding of the Inivtation credential to be
97    // issued
98    EncInvIdClient: (RistrettoPoint, RistrettoPoint),
99    // The bucket and blockages attributes in the Invitation credential
100    // issuing protocol can just reuse the exact encryptions as for the
101    // Lox credential issuing protocol above.
102
103    // The combined ZKP
104    piUser: CompactProof,
105}
106
107#[derive(Debug, Serialize, Deserialize)]
108pub struct State {
109    d: Scalar,
110    D: RistrettoPoint,
111    EncIdClient: (RistrettoPoint, RistrettoPoint),
112    EncBucket: (RistrettoPoint, RistrettoPoint),
113    EncLevel: (RistrettoPoint, RistrettoPoint),
114    EncSince: (RistrettoPoint, RistrettoPoint),
115    EncInvRemain: (RistrettoPoint, RistrettoPoint),
116    EncBlockages: (RistrettoPoint, RistrettoPoint),
117    EncInvIdClient: (RistrettoPoint, RistrettoPoint),
118    id_client: Scalar,
119    bucket: Scalar,
120    level: Scalar,
121    since: Scalar,
122    invremain: Scalar,
123    blockages: Scalar,
124    inv_id_client: Scalar,
125}
126
127#[derive(Serialize, Deserialize)]
128pub struct Response {
129    // The fields for the new Lox credential; the new invites_remaining
130    // is one less than the old value, so we don't have to include it
131    // here explicitly
132    P: RistrettoPoint,
133    EncQ: (RistrettoPoint, RistrettoPoint),
134    id_server: Scalar,
135    TId: RistrettoPoint,
136    TBucket: RistrettoPoint,
137    TLevel: RistrettoPoint,
138    TSince: RistrettoPoint,
139    TInvRemain: RistrettoPoint,
140    TBlockages: RistrettoPoint,
141
142    // The fields for the new Invitation credential
143    P_inv: RistrettoPoint,
144    EncQ_inv: (RistrettoPoint, RistrettoPoint),
145    inv_id_server: Scalar,
146    TId_inv: RistrettoPoint,
147    date_inv: Scalar,
148    TBucket_inv: RistrettoPoint,
149    TBlockages_inv: RistrettoPoint,
150
151    // The ZKP
152    piBlindIssue: CompactProof,
153}
154
155define_proof! {
156    requestproof,
157    "Issue Invite Request",
158    (bucket, level, since, invremain, blockages, zbucket, zlevel,
159     zsince, zinvremain, zblockages, negzQ,
160     zbucket_reach, negzQ_reach,
161     d, eid_client, ebucket, elevel, esince, einvremain, eblockages, id_client,
162     inv_id_client, einv_id_client,
163     invremain_inverse, zinvremain_inverse),
164    (P, CBucket, CLevel, CSince, CInvRemain, CBlockages, V, Xbucket,
165     Xlevel, Xsince, Xinvremain, Xblockages,
166     P_reach, CBucket_reach, V_reach, Xbucket_reach,
167     D, EncIdClient0, EncIdClient1, EncBucket0, EncBucket1,
168     EncLevel0, EncLevel1, EncSince0, EncSince1,
169     EncInvRemain0, EncInvRemain1_plus_B, EncBlockages0, EncBlockages1,
170     EncInvIdClient0, EncInvIdClient1),
171    (A, B):
172    // Blind showing of the Lox credential
173    CBucket = (bucket*P + zbucket*A),
174    CLevel = (level*P + zlevel*A),
175    CSince = (since*P + zsince*A),
176    CInvRemain = (invremain*P + zinvremain*A),
177    CBlockages = (blockages*P + zblockages*A),
178    // Proof that invremain is not 0
179    P = (invremain_inverse*CInvRemain + zinvremain_inverse*A),
180    // Blind showing of the Bucket Reachability credential; note the
181    // same bucket is used in the proof
182    CBucket_reach = (bucket*P_reach + zbucket_reach*A),
183    // User blinding of the Lox credential to be issued
184    D = (d*B),
185    EncIdClient0 = (eid_client*B),
186    EncIdClient1 = (id_client*B + eid_client*D),
187    EncBucket0 = (ebucket*B),
188    EncBucket1 = (bucket*B + ebucket*D),
189    EncLevel0 = (elevel*B),
190    EncLevel1 = (level*B + elevel*D),
191    EncSince0 = (esince*B),
192    EncSince1 = (since*B + esince*D),
193    EncInvRemain0 = (einvremain*B),
194    EncInvRemain1_plus_B = (invremain*B + einvremain*D),
195    EncBlockages0 = (eblockages*B),
196    EncBlockages1 = (blockages*B + eblockages*D),
197    // User blinding of the Invitation to be issued
198    EncInvIdClient0 = (einv_id_client*B),
199    EncInvIdClient1 = (inv_id_client*B + einv_id_client*D)
200}
201
202define_proof! {
203    blindissue,
204    "Issue Invite Issuing",
205    (x0, x0tilde, xid, xbucket, xlevel, xsince, xinvremain, xblockages,
206     s, b, tid, tbucket, tlevel, tsince, tinvremain, tblockages,
207     x0_inv, x0tilde_inv, xid_inv, xdate_inv, xbucket_inv,
208     xblockages_inv,
209     s_inv, b_inv, tid_inv, tbucket_inv, tblockages_inv),
210    (P, EncQ0, EncQ1, X0, Xid, Xbucket, Xlevel, Xsince, Xinvremain,
211     Xblockages, TId, TBucket, TLevel, TSince, TInvRemain, TBlockages,
212     P_inv, EncQ_inv0, EncQ_inv1, X0_inv, Xid_inv, Xdate_inv,
213     Xbucket_inv, Xblockages_inv, Pdate_inv, TId_inv, TBucket_inv,
214     TBlockages_inv,
215     D, EncId0, EncId1, EncBucket0, EncBucket1, EncLevel0, EncLevel1,
216     EncSince0, EncSince1, EncInvRemain0, EncInvRemain1,
217     EncBlockages0, EncBlockages1,
218     EncInvId0, EncInvId1),
219    (A, B):
220    Xid = (xid*A),
221    Xbucket = (xbucket*A),
222    Xlevel = (xlevel*A),
223    Xsince = (xsince*A),
224    Xinvremain = (xinvremain*A),
225    Xblockages = (xblockages*A),
226    X0 = (x0*B + x0tilde*A),
227    P = (b*B),
228    TId = (b*Xid),
229    TId = (tid*A),
230    TBucket = (b*Xbucket),
231    TBucket = (tbucket*A),
232    TLevel = (b*Xlevel),
233    TLevel = (tlevel*A),
234    TSince = (b*Xsince),
235    TSince = (tsince*A),
236    TInvRemain = (b*Xinvremain),
237    TInvRemain = (tinvremain*A),
238    TBlockages = (b*Xblockages),
239    TBlockages = (tblockages*A),
240    EncQ0 = (s*B + tid*EncId0 + tbucket*EncBucket0 + tlevel*EncLevel0
241        + tsince*EncSince0 + tinvremain*EncInvRemain0 + tblockages*EncBlockages0),
242    EncQ1 = (s*D + tid*EncId1 + tbucket*EncBucket1 + tlevel*EncLevel1
243        + tsince*EncSince1 + tinvremain*EncInvRemain1 + tblockages*EncBlockages1
244        + x0*P),
245    Xid_inv = (xid_inv*A),
246    Xdate_inv = (xdate_inv*A),
247    Xbucket_inv = (xbucket_inv*A),
248    Xblockages_inv = (xblockages_inv*A),
249    X0_inv = (x0_inv*B + x0tilde_inv*A),
250    P_inv = (b_inv*B),
251    TId_inv = (b_inv*Xid_inv),
252    TId_inv = (tid_inv*A),
253    TBucket_inv = (b_inv*Xbucket_inv),
254    TBucket_inv = (tbucket_inv*A),
255    TBlockages_inv = (b_inv*Xblockages_inv),
256    TBlockages_inv = (tblockages_inv*A),
257    EncQ_inv0 = (s_inv*B + tid_inv*EncInvId0 + tbucket_inv*EncBucket0
258        + tblockages_inv*EncBlockages0),
259    EncQ_inv1 = (s_inv*D + tid_inv*EncInvId1 + tbucket_inv*EncBucket1
260        + tblockages_inv*EncBlockages1 + x0_inv*P_inv + xdate_inv*Pdate_inv)
261}
262
263pub fn request(
264    lox_cred: &cred::Lox,
265    reach_cred: &cred::BucketReachability,
266    lox_pub: &IssuerPubKey,
267    reach_pub: &IssuerPubKey,
268    today: u32,
269) -> Result<(Request, State), CredentialError> {
270    let A: &RistrettoPoint = &CMZ_A;
271    let B: &RistrettoPoint = &CMZ_B;
272    let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
273    let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
274
275    // Ensure the credential can be correctly shown: it must be the case
276    // that invites_remaining not be 0
277    if lox_cred.invites_remaining == Scalar::ZERO {
278        return Err(CredentialError::NoInvitationsRemaining);
279    }
280    // The buckets in the Lox and Bucket Reachability credentials have
281    // to match
282    if lox_cred.bucket != reach_cred.bucket {
283        return Err(CredentialError::CredentialMismatch);
284    }
285    // The Bucket Reachability credential has to be dated today
286    let reach_date: u32 = match scalar_u32(&reach_cred.date) {
287        Some(v) => v,
288        None => {
289            return Err(CredentialError::InvalidField(
290                String::from("date"),
291                String::from("could not be converted to u32"),
292            ))
293        }
294    };
295    if reach_date != today {
296        return Err(CredentialError::InvalidField(
297            String::from("date"),
298            String::from("reachability credential must be generated today"),
299        ));
300    }
301    // The new invites_remaining
302    let new_invites_remaining = lox_cred.invites_remaining - Scalar::ONE;
303
304    // Blind showing the Lox credential
305
306    // Reblind P and Q
307    let mut rng = rand::thread_rng();
308    let t = Scalar::random(&mut rng);
309    let P = t * lox_cred.P;
310    let Q = t * lox_cred.Q;
311
312    // Form Pedersen commitments to the blinded attributes
313    let zbucket = Scalar::random(&mut rng);
314    let zlevel = Scalar::random(&mut rng);
315    let zsince = Scalar::random(&mut rng);
316    let zinvremain = Scalar::random(&mut rng);
317    let zblockages = Scalar::random(&mut rng);
318    let CBucket = lox_cred.bucket * P + &zbucket * Atable;
319    let CLevel = lox_cred.trust_level * P + &zlevel * Atable;
320    let CSince = lox_cred.level_since * P + &zsince * Atable;
321    let CInvRemain = lox_cred.invites_remaining * P + &zinvremain * Atable;
322    let CBlockages = lox_cred.blockages * P + &zblockages * Atable;
323
324    // Form a Pedersen commitment to the MAC Q
325    // We flip the sign of zQ from that of the Hyphae paper so that
326    // the ZKP has a "+" instead of a "-", as that's what the zkp
327    // macro supports.
328    let negzQ = Scalar::random(&mut rng);
329    let CQ = Q - &negzQ * Atable;
330
331    // Compute the "error factor"
332    let V = zbucket * lox_pub.X[2]
333        + zlevel * lox_pub.X[3]
334        + zsince * lox_pub.X[4]
335        + zinvremain * lox_pub.X[5]
336        + zblockages * lox_pub.X[6]
337        + &negzQ * Atable;
338
339    // Blind showing the Bucket Reachability credential
340
341    // Reblind P and Q
342    let t_reach = Scalar::random(&mut rng);
343    let P_reach = t_reach * reach_cred.P;
344    let Q_reach = t_reach * reach_cred.Q;
345
346    // Form Pedersen commitments to the blinded attributes
347    let zbucket_reach = Scalar::random(&mut rng);
348    let CBucket_reach = reach_cred.bucket * P_reach + &zbucket_reach * Atable;
349
350    // Form a Pedersen commitment to the MAC Q
351    // We flip the sign of zQ from that of the Hyphae paper so that
352    // the ZKP has a "+" instead of a "-", as that's what the zkp
353    // macro supports.
354    let negzQ_reach = Scalar::random(&mut rng);
355    let CQ_reach = Q_reach - &negzQ_reach * Atable;
356
357    // Compute the "error factor"
358    let V_reach = zbucket_reach * reach_pub.X[2] + &negzQ_reach * Atable;
359
360    // User blinding for the Lox certificate to be issued
361
362    // Pick an ElGamal keypair
363    let d = Scalar::random(&mut rng);
364    let D = &d * Btable;
365
366    // Pick a random client component of the id
367    let id_client = Scalar::random(&mut rng);
368
369    // Encrypt it (times the basepoint B) to the ElGamal public key D we
370    // just created
371    let eid_client = Scalar::random(&mut rng);
372    let EncIdClient = (&eid_client * Btable, &id_client * Btable + eid_client * D);
373
374    // Encrypt the other blinded fields (times B) to D as well
375    let ebucket = Scalar::random(&mut rng);
376    let EncBucket = (&ebucket * Btable, &lox_cred.bucket * Btable + ebucket * D);
377    let elevel = Scalar::random(&mut rng);
378    let EncLevel = (
379        &elevel * Btable,
380        &lox_cred.trust_level * Btable + elevel * D,
381    );
382    let esince = Scalar::random(&mut rng);
383    let EncSince = (
384        &esince * Btable,
385        &lox_cred.level_since * Btable + esince * D,
386    );
387    let einvremain = Scalar::random(&mut rng);
388    let EncInvRemain = (
389        &einvremain * Btable,
390        &new_invites_remaining * Btable + einvremain * D,
391    );
392    let eblockages = Scalar::random(&mut rng);
393    let EncBlockages = (
394        &eblockages * Btable,
395        &lox_cred.blockages * Btable + eblockages * D,
396    );
397
398    // User blinding for the Invitation certificate to be issued
399
400    // Pick a random client component of the id
401    let inv_id_client = Scalar::random(&mut rng);
402
403    // Encrypt it (times the basepoint B) to the ElGamal public key D we
404    // just created
405    let einv_id_client = Scalar::random(&mut rng);
406    let EncInvIdClient = (
407        &einv_id_client * Btable,
408        &inv_id_client * Btable + einv_id_client * D,
409    );
410
411    // The proof that invites_remaining is not zero.  We prove this by
412    // demonstrating that we know its inverse.
413    let invremain_inverse = &lox_cred.invites_remaining.invert();
414
415    let zinvremain_inverse = -zinvremain * invremain_inverse;
416
417    // So now invremain_inverse * CInvRemain + zinvremain_inverse * A = P
418
419    // Construct the proof
420    let mut transcript = Transcript::new(b"issue invite request");
421    let piUser = requestproof::prove_compact(
422        &mut transcript,
423        requestproof::ProveAssignments {
424            A,
425            B,
426            P: &P,
427            CBucket: &CBucket,
428            CLevel: &CLevel,
429            CSince: &CSince,
430            CInvRemain: &CInvRemain,
431            CBlockages: &CBlockages,
432            V: &V,
433            Xbucket: &lox_pub.X[2],
434            Xlevel: &lox_pub.X[3],
435            Xsince: &lox_pub.X[4],
436            Xinvremain: &lox_pub.X[5],
437            Xblockages: &lox_pub.X[6],
438            P_reach: &P_reach,
439            CBucket_reach: &CBucket_reach,
440            V_reach: &V_reach,
441            Xbucket_reach: &reach_pub.X[2],
442            D: &D,
443            EncIdClient0: &EncIdClient.0,
444            EncIdClient1: &EncIdClient.1,
445            EncBucket0: &EncBucket.0,
446            EncBucket1: &EncBucket.1,
447            EncLevel0: &EncLevel.0,
448            EncLevel1: &EncLevel.1,
449            EncSince0: &EncSince.0,
450            EncSince1: &EncSince.1,
451            EncInvRemain0: &EncInvRemain.0,
452            EncInvRemain1_plus_B: &(EncInvRemain.1 + B),
453            EncBlockages0: &EncBlockages.0,
454            EncBlockages1: &EncBlockages.1,
455            EncInvIdClient0: &EncInvIdClient.0,
456            EncInvIdClient1: &EncInvIdClient.1,
457            bucket: &lox_cred.bucket,
458            level: &lox_cred.trust_level,
459            since: &lox_cred.level_since,
460            invremain: &lox_cred.invites_remaining,
461            blockages: &lox_cred.blockages,
462            zbucket: &zbucket,
463            zlevel: &zlevel,
464            zsince: &zsince,
465            zinvremain: &zinvremain,
466            zblockages: &zblockages,
467            negzQ: &negzQ,
468            zbucket_reach: &zbucket_reach,
469            negzQ_reach: &negzQ_reach,
470            d: &d,
471            eid_client: &eid_client,
472            ebucket: &ebucket,
473            elevel: &elevel,
474            esince: &esince,
475            einvremain: &einvremain,
476            eblockages: &eblockages,
477            id_client: &id_client,
478            inv_id_client: &inv_id_client,
479            einv_id_client: &einv_id_client,
480            invremain_inverse,
481            zinvremain_inverse: &zinvremain_inverse,
482        },
483    )
484    .0;
485
486    Ok((
487        Request {
488            P,
489            id: lox_cred.id,
490            CBucket,
491            CLevel,
492            CSince,
493            CInvRemain,
494            CBlockages,
495            CQ,
496            P_reach,
497            CBucket_reach,
498            CQ_reach,
499            D,
500            EncIdClient,
501            EncBucket,
502            EncLevel,
503            EncSince,
504            EncInvRemain,
505            EncBlockages,
506            EncInvIdClient,
507            piUser,
508        },
509        State {
510            d,
511            D,
512            EncIdClient,
513            EncBucket,
514            EncLevel,
515            EncSince,
516            EncInvRemain,
517            EncBlockages,
518            EncInvIdClient,
519            id_client,
520            bucket: lox_cred.bucket,
521            level: lox_cred.trust_level,
522            since: lox_cred.level_since,
523            invremain: new_invites_remaining,
524            blockages: lox_cred.blockages,
525            inv_id_client,
526        },
527    ))
528}
529
530#[cfg(feature = "bridgeauth")]
531impl BridgeAuth {
532    /// Receive an issue invite request
533    pub fn handle_issue_invite(&mut self, req: Request) -> Result<Response, ProofError> {
534        let A: &RistrettoPoint = &CMZ_A;
535        let B: &RistrettoPoint = &CMZ_B;
536        let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
537        let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
538
539        if req.P.is_identity() || req.P_reach.is_identity() {
540            return Err(ProofError::VerificationFailure);
541        }
542
543        let today: Scalar = self.today().into();
544
545        // Recompute the "error factors" using knowledge of our own
546        // (the issuer's) private key instead of knowledge of the
547        // hidden attributes
548        let Vprime = (self.lox_priv.x[0] + self.lox_priv.x[1] * req.id) * req.P
549            + self.lox_priv.x[2] * req.CBucket
550            + self.lox_priv.x[3] * req.CLevel
551            + self.lox_priv.x[4] * req.CSince
552            + self.lox_priv.x[5] * req.CInvRemain
553            + self.lox_priv.x[6] * req.CBlockages
554            - req.CQ;
555
556        let Vprime_reach = (self.reachability_priv.x[0] + self.reachability_priv.x[1] * today)
557            * req.P_reach
558            + self.reachability_priv.x[2] * req.CBucket_reach
559            - req.CQ_reach;
560
561        // Verify the ZKP
562        let mut transcript = Transcript::new(b"issue invite request");
563        requestproof::verify_compact(
564            &req.piUser,
565            &mut transcript,
566            requestproof::VerifyAssignments {
567                A: &A.compress(),
568                B: &B.compress(),
569                P: &req.P.compress(),
570                CBucket: &req.CBucket.compress(),
571                CLevel: &req.CLevel.compress(),
572                CSince: &req.CSince.compress(),
573                CInvRemain: &req.CInvRemain.compress(),
574                CBlockages: &req.CBlockages.compress(),
575                V: &Vprime.compress(),
576                Xbucket: &self.lox_pub.X[2].compress(),
577                Xlevel: &self.lox_pub.X[3].compress(),
578                Xsince: &self.lox_pub.X[4].compress(),
579                Xinvremain: &self.lox_pub.X[5].compress(),
580                Xblockages: &self.lox_pub.X[6].compress(),
581                P_reach: &req.P_reach.compress(),
582                CBucket_reach: &req.CBucket_reach.compress(),
583                V_reach: &Vprime_reach.compress(),
584                Xbucket_reach: &self.reachability_pub.X[2].compress(),
585                D: &req.D.compress(),
586                EncIdClient0: &req.EncIdClient.0.compress(),
587                EncIdClient1: &req.EncIdClient.1.compress(),
588                EncBucket0: &req.EncBucket.0.compress(),
589                EncBucket1: &req.EncBucket.1.compress(),
590                EncLevel0: &req.EncLevel.0.compress(),
591                EncLevel1: &req.EncLevel.1.compress(),
592                EncSince0: &req.EncSince.0.compress(),
593                EncSince1: &req.EncSince.1.compress(),
594                EncInvRemain0: &req.EncInvRemain.0.compress(),
595                EncInvRemain1_plus_B: &(req.EncInvRemain.1 + B).compress(),
596                EncBlockages0: &req.EncBlockages.0.compress(),
597                EncBlockages1: &req.EncBlockages.1.compress(),
598                EncInvIdClient0: &req.EncInvIdClient.0.compress(),
599                EncInvIdClient1: &req.EncInvIdClient.1.compress(),
600            },
601        )?;
602
603        // Ensure the id has not been seen before, and add it to the
604        // seen list.
605        if self.id_filter.filter(&req.id) == SeenType::Seen {
606            return Err(ProofError::VerificationFailure);
607        }
608
609        // Blind issuing of the new Lox credential
610
611        // Choose a random server id component to add to the client's
612        // (blinded) id component
613        let mut rng = rand::thread_rng();
614        let id_server = Scalar::random(&mut rng);
615        let EncId = (req.EncIdClient.0, req.EncIdClient.1 + &id_server * Btable);
616
617        // Compute the MAC on the visible attributes (none here)
618        let b = Scalar::random(&mut rng);
619        let P = &b * Btable;
620        let QHc = self.lox_priv.x[0] * P;
621
622        // El Gamal encrypt it to the public key req.D
623        let s = Scalar::random(&mut rng);
624        let EncQHc = (&s * Btable, QHc + s * req.D);
625
626        // Homomorphically compute the part of the MAC corresponding to
627        // the blinded attributes
628        let tid = self.lox_priv.x[1] * b;
629        let TId = &tid * Atable;
630        let EncQId = (tid * EncId.0, tid * EncId.1);
631        let tbucket = self.lox_priv.x[2] * b;
632        let TBucket = &tbucket * Atable;
633        let EncQBucket = (tbucket * req.EncBucket.0, tbucket * req.EncBucket.1);
634        let tlevel = self.lox_priv.x[3] * b;
635        let TLevel = &tlevel * Atable;
636        let EncQLevel = (tlevel * req.EncLevel.0, tlevel * req.EncLevel.1);
637        let tsince = self.lox_priv.x[4] * b;
638        let TSince = &tsince * Atable;
639        let EncQSince = (tsince * req.EncSince.0, tsince * req.EncSince.1);
640        let tinvremain = self.lox_priv.x[5] * b;
641        let TInvRemain = &tinvremain * Atable;
642        let EncQInvRemain = (
643            tinvremain * req.EncInvRemain.0,
644            tinvremain * req.EncInvRemain.1,
645        );
646        let tblockages = self.lox_priv.x[6] * b;
647        let TBlockages = &tblockages * Atable;
648        let EncQBlockages = (
649            tblockages * req.EncBlockages.0,
650            tblockages * req.EncBlockages.1,
651        );
652
653        let EncQ = (
654            EncQHc.0
655                + EncQId.0
656                + EncQBucket.0
657                + EncQLevel.0
658                + EncQSince.0
659                + EncQInvRemain.0
660                + EncQBlockages.0,
661            EncQHc.1
662                + EncQId.1
663                + EncQBucket.1
664                + EncQLevel.1
665                + EncQSince.1
666                + EncQInvRemain.1
667                + EncQBlockages.1,
668        );
669
670        // Blind issuing of the new Invitation credential
671
672        // Choose a random server id component to add to the client's
673        // (blinded) id component
674        let inv_id_server = Scalar::random(&mut rng);
675        let EncInvId = (
676            req.EncInvIdClient.0,
677            req.EncInvIdClient.1 + &inv_id_server * Btable,
678        );
679
680        // Compute the MAC on the visible attributes
681        let b_inv = Scalar::random(&mut rng);
682        let P_inv = &b_inv * Btable;
683        let QHc_inv = (self.invitation_priv.x[0] + self.invitation_priv.x[2] * today) * P_inv;
684
685        // El Gamal encrypt it to the public key req.D
686        let s_inv = Scalar::random(&mut rng);
687        let EncQHc_inv = (&s_inv * Btable, QHc_inv + s_inv * req.D);
688
689        // Homomorphically compute the part of the MAC corresponding to
690        // the blinded attributes
691        let tinvid = self.invitation_priv.x[1] * b_inv;
692        let TId_inv = &tinvid * Atable;
693        let EncQInvId = (tinvid * EncInvId.0, tinvid * EncInvId.1);
694        let tinvbucket = self.invitation_priv.x[3] * b_inv;
695        let TBucket_inv = &tinvbucket * Atable;
696        // The bucket and blockages encrypted attributes are reused from
697        // the Lox credential
698        let EncQInvBucket = (tinvbucket * req.EncBucket.0, tinvbucket * req.EncBucket.1);
699        let tinvblockages = self.invitation_priv.x[4] * b_inv;
700        let TBlockages_inv = &tinvblockages * Atable;
701        let EncQInvBlockages = (
702            tinvblockages * req.EncBlockages.0,
703            tinvblockages * req.EncBlockages.1,
704        );
705
706        let EncQ_inv = (
707            EncQHc_inv.0 + EncQInvId.0 + EncQInvBucket.0 + EncQInvBlockages.0,
708            EncQHc_inv.1 + EncQInvId.1 + EncQInvBucket.1 + EncQInvBlockages.1,
709        );
710
711        let mut transcript = Transcript::new(b"issue invite issuing");
712        let piBlindIssue = blindissue::prove_compact(
713            &mut transcript,
714            blindissue::ProveAssignments {
715                A,
716                B,
717                P: &P,
718                EncQ0: &EncQ.0,
719                EncQ1: &EncQ.1,
720                X0: &self.lox_pub.X[0],
721                Xid: &self.lox_pub.X[1],
722                Xbucket: &self.lox_pub.X[2],
723                Xlevel: &self.lox_pub.X[3],
724                Xsince: &self.lox_pub.X[4],
725                Xinvremain: &self.lox_pub.X[5],
726                Xblockages: &self.lox_pub.X[6],
727                TId: &TId,
728                TBucket: &TBucket,
729                TLevel: &TLevel,
730                TSince: &TSince,
731                TInvRemain: &TInvRemain,
732                TBlockages: &TBlockages,
733                P_inv: &P_inv,
734                EncQ_inv0: &EncQ_inv.0,
735                EncQ_inv1: &EncQ_inv.1,
736                X0_inv: &self.invitation_pub.X[0],
737                Xid_inv: &self.invitation_pub.X[1],
738                Xdate_inv: &self.invitation_pub.X[2],
739                Xbucket_inv: &self.invitation_pub.X[3],
740                Xblockages_inv: &self.invitation_pub.X[4],
741                Pdate_inv: &(today * P_inv),
742                TId_inv: &TId_inv,
743                TBucket_inv: &TBucket_inv,
744                TBlockages_inv: &TBlockages_inv,
745                D: &req.D,
746                EncId0: &EncId.0,
747                EncId1: &EncId.1,
748                EncBucket0: &req.EncBucket.0,
749                EncBucket1: &req.EncBucket.1,
750                EncLevel0: &req.EncLevel.0,
751                EncLevel1: &req.EncLevel.1,
752                EncSince0: &req.EncSince.0,
753                EncSince1: &req.EncSince.1,
754                EncInvRemain0: &req.EncInvRemain.0,
755                EncInvRemain1: &req.EncInvRemain.1,
756                EncBlockages0: &req.EncBlockages.0,
757                EncBlockages1: &req.EncBlockages.1,
758                EncInvId0: &EncInvId.0,
759                EncInvId1: &EncInvId.1,
760                x0: &self.lox_priv.x[0],
761                x0tilde: &self.lox_priv.x0tilde,
762                xid: &self.lox_priv.x[1],
763                xbucket: &self.lox_priv.x[2],
764                xlevel: &self.lox_priv.x[3],
765                xsince: &self.lox_priv.x[4],
766                xinvremain: &self.lox_priv.x[5],
767                xblockages: &self.lox_priv.x[6],
768                s: &s,
769                b: &b,
770                tid: &tid,
771                tbucket: &tbucket,
772                tlevel: &tlevel,
773                tsince: &tsince,
774                tinvremain: &tinvremain,
775                tblockages: &tblockages,
776                x0_inv: &self.invitation_priv.x[0],
777                x0tilde_inv: &self.invitation_priv.x0tilde,
778                xid_inv: &self.invitation_priv.x[1],
779                xdate_inv: &self.invitation_priv.x[2],
780                xbucket_inv: &self.invitation_priv.x[3],
781                xblockages_inv: &self.invitation_priv.x[4],
782                s_inv: &s_inv,
783                b_inv: &b_inv,
784                tid_inv: &tinvid,
785                tbucket_inv: &tinvbucket,
786                tblockages_inv: &tinvblockages,
787            },
788        )
789        .0;
790
791        Ok(Response {
792            P,
793            EncQ,
794            id_server,
795            TId,
796            TBucket,
797            TLevel,
798            TSince,
799            TInvRemain,
800            TBlockages,
801            P_inv,
802            EncQ_inv,
803            inv_id_server,
804            TId_inv,
805            date_inv: today,
806            TBucket_inv,
807            TBlockages_inv,
808            piBlindIssue,
809        })
810    }
811}
812
813/// Handle the response to the request, producing the new Lox credential
814/// and Invitation credential if successful.
815pub fn handle_response(
816    state: State,
817    resp: Response,
818    lox_pub: &IssuerPubKey,
819    invitation_pub: &IssuerPubKey,
820) -> Result<(cred::Lox, cred::Invitation), ProofError> {
821    let A: &RistrettoPoint = &CMZ_A;
822    let B: &RistrettoPoint = &CMZ_B;
823    let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
824
825    if resp.P.is_identity() || resp.P_inv.is_identity() {
826        return Err(ProofError::VerificationFailure);
827    }
828
829    // Add the server's contribution to the id to our own, both in plain
830    // and encrypted form and for both the Lox credential id and the
831    // Invitation credential id
832    let id = state.id_client + resp.id_server;
833    let EncId = (
834        state.EncIdClient.0,
835        state.EncIdClient.1 + &resp.id_server * Btable,
836    );
837
838    let inv_id = state.inv_id_client + resp.inv_id_server;
839    let EncInvId = (
840        state.EncInvIdClient.0,
841        state.EncInvIdClient.1 + &resp.inv_id_server * Btable,
842    );
843
844    // Verify the proof
845    let mut transcript = Transcript::new(b"issue invite issuing");
846    blindissue::verify_compact(
847        &resp.piBlindIssue,
848        &mut transcript,
849        blindissue::VerifyAssignments {
850            A: &A.compress(),
851            B: &B.compress(),
852            P: &resp.P.compress(),
853            EncQ0: &resp.EncQ.0.compress(),
854            EncQ1: &resp.EncQ.1.compress(),
855            X0: &lox_pub.X[0].compress(),
856            Xid: &lox_pub.X[1].compress(),
857            Xbucket: &lox_pub.X[2].compress(),
858            Xlevel: &lox_pub.X[3].compress(),
859            Xsince: &lox_pub.X[4].compress(),
860            Xinvremain: &lox_pub.X[5].compress(),
861            Xblockages: &lox_pub.X[6].compress(),
862            TId: &resp.TId.compress(),
863            TBucket: &resp.TBucket.compress(),
864            TLevel: &resp.TLevel.compress(),
865            TSince: &resp.TSince.compress(),
866            TInvRemain: &resp.TInvRemain.compress(),
867            TBlockages: &resp.TBlockages.compress(),
868            P_inv: &resp.P_inv.compress(),
869            EncQ_inv0: &resp.EncQ_inv.0.compress(),
870            EncQ_inv1: &resp.EncQ_inv.1.compress(),
871            X0_inv: &invitation_pub.X[0].compress(),
872            Xid_inv: &invitation_pub.X[1].compress(),
873            Xdate_inv: &invitation_pub.X[2].compress(),
874            Xbucket_inv: &invitation_pub.X[3].compress(),
875            Xblockages_inv: &invitation_pub.X[4].compress(),
876            Pdate_inv: &(resp.date_inv * resp.P_inv).compress(),
877            TId_inv: &resp.TId_inv.compress(),
878            TBucket_inv: &resp.TBucket_inv.compress(),
879            TBlockages_inv: &resp.TBlockages_inv.compress(),
880            D: &state.D.compress(),
881            EncId0: &EncId.0.compress(),
882            EncId1: &EncId.1.compress(),
883            EncBucket0: &state.EncBucket.0.compress(),
884            EncBucket1: &state.EncBucket.1.compress(),
885            EncLevel0: &state.EncLevel.0.compress(),
886            EncLevel1: &state.EncLevel.1.compress(),
887            EncSince0: &state.EncSince.0.compress(),
888            EncSince1: &state.EncSince.1.compress(),
889            EncInvRemain0: &state.EncInvRemain.0.compress(),
890            EncInvRemain1: &state.EncInvRemain.1.compress(),
891            EncBlockages0: &state.EncBlockages.0.compress(),
892            EncBlockages1: &state.EncBlockages.1.compress(),
893            EncInvId0: &EncInvId.0.compress(),
894            EncInvId1: &EncInvId.1.compress(),
895        },
896    )?;
897
898    // Decrypt EncQ and EncQ_inv
899    let Q = resp.EncQ.1 - (state.d * resp.EncQ.0);
900    let Q_inv = resp.EncQ_inv.1 - (state.d * resp.EncQ_inv.0);
901
902    Ok((
903        cred::Lox {
904            P: resp.P,
905            Q,
906            id,
907            bucket: state.bucket,
908            trust_level: state.level,
909            level_since: state.since,
910            invites_remaining: state.invremain,
911            blockages: state.blockages,
912        },
913        cred::Invitation {
914            P: resp.P_inv,
915            Q: Q_inv,
916            inv_id,
917            date: resp.date_inv,
918            bucket: state.bucket,
919            blockages: state.blockages,
920        },
921    ))
922}