Skip to main content

lox_library/proto/
redeem_invite.rs

1/*! A module for the protocol for a new user to redeem an Invitation
2credential.  The user will start at trust level 1 (instead of 0 for
3untrusted uninvited users).
4
5The user presents the Invitation credential:
6- id: revealed
7- date: blinded, but proved in ZK to be at most INVITATION_EXPIRY days ago
8- bucket: blinded
9- blockages: blinded
10
11and a new Lox credential to be issued:
12
13- id: jointly chosen by the user and BA
14- bucket: blinded, but proved in ZK that it's the same as in the
15  Invitation credential above
16- trust_level: revealed to be 1
17- level_since: today
18- invites_remaining: revealed to be 0
19- blockages: blinded, but proved in ZK that it's the same as in the
20  Invitations credential above
21
22*/
23
24use 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
48/// Invitations must be used within this many days of being issued.
49/// Note that if you change this number to be larger than 15, you must
50/// also add bits to the zero knowledge proof.
51pub const INVITATION_EXPIRY: u32 = 15;
52
53#[derive(Serialize, Deserialize)]
54pub struct Request {
55    // Fields for showing the Invitation credential
56    P: RistrettoPoint,
57    inv_id: Scalar,
58    CDate: RistrettoPoint,
59    CBucket: RistrettoPoint,
60    CBlockages: RistrettoPoint,
61    CQ: RistrettoPoint,
62
63    // Fields for the inequality proof
64    // date + INVITATION_EXPIRY >= today
65    CG1: RistrettoPoint,
66    CG2: RistrettoPoint,
67    CG3: RistrettoPoint,
68    CG0sq: RistrettoPoint,
69    CG1sq: RistrettoPoint,
70    CG2sq: RistrettoPoint,
71    CG3sq: RistrettoPoint,
72
73    // Fields for user blinding of the Lox credential to be issued
74    D: RistrettoPoint,
75    EncIdClient: (RistrettoPoint, RistrettoPoint),
76    EncBucket: (RistrettoPoint, RistrettoPoint),
77    EncBlockages: (RistrettoPoint, RistrettoPoint),
78
79    // The combined ZKP
80    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    // The fields for the new Lox credential; the new trust level is 1
98    // and the new invites_remaining is 0, so we don't have to include
99    // them here explicitly
100    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    // The ZKP
109    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    // Blind showing of the Invitation credential
128    CDate = (date*P + zdate*A),
129    CBucket = (bucket*P + zbucket*A),
130    CBlockages = (blockages*P + zblockages*A),
131    // User blinding of the Lox credential to be issued
132    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    // Prove CDate encodes a value at most INVITATION_EXPIRY
140    // days ago: first prove each of g0, ..., g3 is a bit by
141    // proving that gi = gi^2
142    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    // Then we'll check that today*P + CG0 + 2*CG1 + 4*CG2 + 8*CG3 =
147    // CDate + INVITATION_EXPIRY*P by having the verifier
148    // plug in CDate + INVITATION_EXPIRY*P - (today*P + 2*CG1 + 4*CG2
149    // + 8*CG3) as its value of CG0.
150}
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    // level=1 (so Plevel = P) and invremain=0 (so the term is omitted)
176    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    // Ensure the credential can be correctly shown: it must be the case
191    // that date + INVITATION_EXPIRY >= today.
192    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, then since INVITATION_EXPIRY <= 15, then date
206    // must be in the future.  Reject.
207    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    // Blind showing the Invitation credential
215
216    // Reblind P and Q
217    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    // Form Pedersen commitments to the blinded attributes
223    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    // Form a Pedersen commitment to the MAC Q
231    // We flip the sign of zQ from that of the Hyphae paper so that
232    // the ZKP has a "+" instead of a "-", as that's what the zkp
233    // macro supports.
234    let negzQ = Scalar::random(&mut rng);
235    let CQ = Q - &negzQ * Atable;
236
237    // Compute the "error factor"
238    let V = zdate * invitation_pub.X[2]
239        + zbucket * invitation_pub.X[3]
240        + zblockages * invitation_pub.X[4]
241        + &negzQ * Atable;
242
243    // User blinding for the Lox certificate to be issued
244
245    // Pick an ElGamal keypair
246    let d = Scalar::random(&mut rng);
247    let D = &d * Btable;
248
249    // Pick a random client component of the id
250    let id_client = Scalar::random(&mut rng);
251
252    // Encrypt it (times the basepoint B) to the ElGamal public key D we
253    // just created
254    let eid_client = Scalar::random(&mut rng);
255    let EncIdClient = (&eid_client * Btable, &id_client * Btable + eid_client * D);
256
257    // Encrypt the other blinded fields (times B) to D as well
258    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    // The range proof that 0 <= diffdays <= 15
267
268    // Extract the 4 bits from diffdays
269    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    // Pick random factors for the Pedersen commitments
275    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    // Compute zg0 to cancel things out as
284    // zg0 = zdate - (2*zg1 + 4*zg2 + 8*zg3)
285    // but use Horner's method
286    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    // Construct the proof
304    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    /// Receive a redeem invite request
402    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        // Recompute the "error factor" using knowledge of our own
415        // (the issuer's) private key instead of knowledge of the
416        // hidden attributes
417        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        // Recompute CG0 using Horner's method
424        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        // Verify the ZKP
429        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        // Ensure the id has not been seen before, and add it to the
463        // invite id seen list.
464        if self.inv_id_filter.filter(&req.inv_id) == SeenType::Seen {
465            return Err(ProofError::VerificationFailure);
466        }
467
468        // Blind issuing of the new Lox credential
469
470        // Choose a random server id component to add to the client's
471        // (blinded) id component
472        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        // The trust level for invitees is always 1
477        let level = Scalar::ONE;
478
479        // The invites remaining for invitees is always 0 (as
480        // appropriate for trust level 1), so we don't need to actually
481        // construct it
482
483        // Compute the MAC on the visible attributes
484        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        // El Gamal encrypt it to the public key req.D
490        let s = Scalar::random(&mut rng);
491        let EncQHc = (&s * Btable, QHc + s * req.D);
492
493        // Homomorphically compute the part of the MAC corresponding to
494        // the blinded attributes
495        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
568/// Handle the response to the request, producing the new Lox credential
569/// if successful.
570pub 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    // Add the server's contribution to the id to our own, both in plain
584    // and encrypted form
585    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    // Verify the proof
592    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    // Decrypt EncQ
623    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}