Skip to main content

lox_library/proto/
blockage_migration.rs

1/*! A module for the protocol for the user of trust level 3 or higher to
2migrate from one bucket to another because their current bucket has been
3blocked.  Their trust level will go down by 2.
4
5The user presents their current Lox credential:
6
7- id: revealed
8- bucket: blinded
9- trust_level: revealed to be 3 or higher
10- level_since: blinded
11- invites_remaining: blinded
12- blockages: blinded
13
14and a Migration credential:
15
16- id: revealed as the same as the Lox credential id above
17- from_bucket: blinded, but proved in ZK that it's the same as the
18  bucket in the Lox credential above
19- to_bucket: blinded
20
21and a new Lox credential to be issued:
22
23- id: jointly chosen by the user and BA
24- bucket: blinded, but proved in ZK that it's the same as the to_bucket
25  in the Migration credential above
26- trust_level: revealed to be 2 less than the trust_level above
27- level_since: today
28- invites_remaining: revealed to be LEVEL_INVITATIONS for the new trust
29  level
30- blockages: blinded, but proved in ZK that it's one more than the
31  blockages above
32
33*/
34
35use curve25519_dalek::ristretto::RistrettoBasepointTable;
36use curve25519_dalek::ristretto::RistrettoPoint;
37use curve25519_dalek::scalar::Scalar;
38use curve25519_dalek::traits::IsIdentity;
39
40use lox_zkp::CompactProof;
41use lox_zkp::ProofError;
42use lox_zkp::Transcript;
43
44use serde::{Deserialize, Serialize};
45
46use super::super::cred;
47#[cfg(feature = "bridgeauth")]
48use super::super::dup_filter::SeenType;
49#[cfg(feature = "bridgeauth")]
50use super::super::migration_table::MigrationType;
51use super::super::scalar_u32;
52#[cfg(feature = "bridgeauth")]
53use super::super::BridgeAuth;
54use super::super::IssuerPubKey;
55
56use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
57use super::check_blockage::MIN_TRUST_LEVEL;
58use super::level_up::LEVEL_INVITATIONS;
59
60use super::errors::CredentialError;
61
62#[derive(Serialize, Deserialize)]
63pub struct Request {
64    // Fields for blind showing the Lox credential
65    P_lox: RistrettoPoint,
66    id: Scalar,
67    CBucket: RistrettoPoint,
68    trust_level: Scalar,
69    CSince: RistrettoPoint,
70    CInvRemain: RistrettoPoint,
71    CBlockages: RistrettoPoint,
72    CQ_lox: RistrettoPoint,
73
74    // Fields for blind showing the Migration credential
75    P_mig: RistrettoPoint,
76    CFromBucket: RistrettoPoint,
77    CToBucket: RistrettoPoint,
78    CQ_mig: RistrettoPoint,
79
80    // Fields for user blinding of the Lox credential to be issued
81    D: RistrettoPoint,
82    EncIdClient: (RistrettoPoint, RistrettoPoint),
83    EncBucket: (RistrettoPoint, RistrettoPoint),
84    EncBlockages: (RistrettoPoint, RistrettoPoint),
85
86    // The combined lox_zkp
87    piUser: CompactProof,
88}
89
90#[derive(Debug, Serialize, Deserialize)]
91pub struct State {
92    d: Scalar,
93    D: RistrettoPoint,
94    EncIdClient: (RistrettoPoint, RistrettoPoint),
95    EncBucket: (RistrettoPoint, RistrettoPoint),
96    EncBlockages: (RistrettoPoint, RistrettoPoint),
97    id_client: Scalar,
98    to_bucket: Scalar,
99    trust_level: Scalar,
100    blockages: Scalar,
101}
102
103#[derive(Serialize, Deserialize)]
104pub struct Response {
105    // The new attributes; the trust_level and invites_remaining are
106    // implicit
107    level_since: Scalar,
108
109    // The fields for the new Lox credential
110    P: RistrettoPoint,
111    EncQ: (RistrettoPoint, RistrettoPoint),
112    id_server: Scalar,
113    TId: RistrettoPoint,
114    TBucket: RistrettoPoint,
115    TBlockages: RistrettoPoint,
116
117    // The lox_zkp
118    piBlindIssue: CompactProof,
119}
120
121define_proof! {
122    requestproof,
123    "Blockage Migration Request",
124    (bucket, since, invremain, blockages, zbucket, zsince, zinvremain,
125     zblockages, negzQ_lox,
126     tobucket, zfrombucket, ztobucket, negzQ_mig,
127     d, eid_client, ebucket, eblockages, id_client),
128    (P_lox, CBucket, CSince, CInvRemain, CBlockages, V_lox, Xbucket,
129     Xsince, Xinvremain, Xblockages,
130     P_mig, CFromBucket, CToBucket, V_mig, Xfrombucket, Xtobucket,
131     D, EncIdClient0, EncIdClient1, EncBucket0, EncBucket1,
132     EncBlockages0, EncBlockages1_minus_B),
133    (A, B):
134    // Blind showing of the Lox credential
135    CBucket = (bucket*P_lox + zbucket*A),
136    CSince = (since*P_lox + zsince*A),
137    CInvRemain = (invremain*P_lox + zinvremain*A),
138    CBlockages = (blockages*P_lox + zblockages*A),
139    V_lox = (zbucket*Xbucket + zsince*Xsince + zinvremain*Xinvremain
140        + zblockages*Xblockages + negzQ_lox*A),
141    // Blind showing of the Migration credential; note the use of the
142    // same "bucket" secret variable
143    CFromBucket = (bucket*P_mig + zfrombucket*A),
144    CToBucket = (tobucket*P_mig + ztobucket*A),
145    V_mig = (zfrombucket*Xfrombucket + ztobucket*Xtobucket + negzQ_mig*A),
146    // User blinding of the Lox credential to be issued; note the use of
147    // the same "tobucket" secret variable
148    D = (d*B),
149    EncIdClient0 = (eid_client*B),
150    EncIdClient1 = (id_client*B + eid_client*D),
151    EncBucket0 = (ebucket*B),
152    EncBucket1 = (tobucket*B + ebucket*D),
153    EncBlockages0 = (eblockages*B),
154    EncBlockages1_minus_B = (blockages*B + eblockages*D)
155}
156
157define_proof! {
158    blindissue,
159    "Blockage Migration Blind Issuing",
160    (x0, x0tilde, xid, xbucket, xlevel, xsince, xinvremain, xblockages,
161     s, b, tid, tbucket, tblockages),
162    (P, EncQ0, EncQ1, X0, Xid, Xbucket, Xlevel, Xsince, Xinvremain,
163    Xblockages, Plevel, Psince, Pinvremain, TId, TBucket, TBlockages,
164     D, EncId0, EncId1, EncBucket0, EncBucket1, EncBlockages0, EncBlockages1),
165    (A, B):
166    Xid = (xid*A),
167    Xlevel = (xlevel*A),
168    Xbucket = (xbucket*A),
169    Xsince = (xsince*A),
170    Xinvremain = (xinvremain*A),
171    Xblockages = (xblockages*A),
172    X0 = (x0*B + x0tilde*A),
173    P = (b*B),
174    TId = (b*Xid),
175    TId = (tid*A),
176    TBucket = (b*Xbucket),
177    TBucket = (tbucket*A),
178    TBlockages = (b*Xblockages),
179    TBlockages = (tblockages*A),
180    EncQ0 = (s*B + tid*EncId0 + tbucket*EncBucket0
181        + tblockages*EncBlockages0),
182    EncQ1 = (s*D + tid*EncId1 + tbucket*EncBucket1
183         + tblockages*EncBlockages1
184        + x0*P + xlevel*Plevel + xsince*Psince + xinvremain*Pinvremain)
185}
186
187pub fn request(
188    lox_cred: &cred::Lox,
189    migration_cred: &cred::Migration,
190    lox_pub: &IssuerPubKey,
191    migration_pub: &IssuerPubKey,
192) -> Result<(Request, State), CredentialError> {
193    let A: &RistrettoPoint = &CMZ_A;
194    let B: &RistrettoPoint = &CMZ_B;
195    let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
196    let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
197
198    // Ensure that the credenials can be correctly shown; that is, the
199    // ids match and the Lox credential bucket matches the Migration
200    // credential from_bucket
201    if lox_cred.id != migration_cred.lox_id || lox_cred.bucket != migration_cred.from_bucket {
202        return Err(CredentialError::CredentialMismatch);
203    }
204
205    // The trust level must be at least MIN_TRUST_LEVEL
206    let level: u32 = match scalar_u32(&lox_cred.trust_level) {
207        Some(v) => v,
208        None => {
209            return Err(CredentialError::InvalidField(
210                String::from("trust_level"),
211                String::from("could not be converted to u32"),
212            ))
213        }
214    };
215    if level < MIN_TRUST_LEVEL {
216        return Err(CredentialError::InvalidField(
217            String::from("trust_level"),
218            format!("level {:?} not in range", level),
219        ));
220    }
221
222    // Blind showing the Lox credential
223
224    // Reblind P and Q
225    let mut rng = rand::thread_rng();
226    let t_lox = Scalar::random(&mut rng);
227    let P_lox = t_lox * lox_cred.P;
228    let Q_lox = t_lox * lox_cred.Q;
229
230    // Form Pedersen commitments to the blinded attributes
231    let zbucket = Scalar::random(&mut rng);
232    let zsince = Scalar::random(&mut rng);
233    let zinvremain = Scalar::random(&mut rng);
234    let zblockages = Scalar::random(&mut rng);
235    let CBucket = lox_cred.bucket * P_lox + &zbucket * Atable;
236    let CSince = lox_cred.level_since * P_lox + &zsince * Atable;
237    let CInvRemain = lox_cred.invites_remaining * P_lox + &zinvremain * Atable;
238    let CBlockages = lox_cred.blockages * P_lox + &zblockages * Atable;
239
240    // Form a Pedersen commitment to the MAC Q
241    // We flip the sign of zQ from that of the Hyphae paper so that
242    // the lox_zkp has a "+" instead of a "-", as that's what the zkp
243    // macro supports.
244    let negzQ_lox = Scalar::random(&mut rng);
245    let CQ_lox = Q_lox - &negzQ_lox * Atable;
246
247    // Compute the "error factor"
248    let V_lox = zbucket * lox_pub.X[2]
249        + zsince * lox_pub.X[4]
250        + zinvremain * lox_pub.X[5]
251        + zblockages * lox_pub.X[6]
252        + &negzQ_lox * Atable;
253
254    // Blind showing the Migration credential
255
256    // Reblind P and Q
257    let t_mig = Scalar::random(&mut rng);
258    let P_mig = t_mig * migration_cred.P;
259    let Q_mig = t_mig * migration_cred.Q;
260
261    // Form Pedersen commitments to the blinded attributes
262    let zfrombucket = Scalar::random(&mut rng);
263    let ztobucket = Scalar::random(&mut rng);
264    let CFromBucket = migration_cred.from_bucket * P_mig + &zfrombucket * Atable;
265    let CToBucket = migration_cred.to_bucket * P_mig + &ztobucket * Atable;
266
267    // Form a Pedersen commitment to the MAC Q
268    // We flip the sign of zQ from that of the Hyphae paper so that
269    // the lox_zkp has a "+" instead of a "-", as that's what the zkp
270    // macro supports.
271    let negzQ_mig = Scalar::random(&mut rng);
272    let CQ_mig = Q_mig - &negzQ_mig * Atable;
273
274    // Compute the "error factor"
275    let V_mig =
276        zfrombucket * migration_pub.X[2] + ztobucket * migration_pub.X[3] + &negzQ_mig * Atable;
277
278    // User blinding for the Lox certificate to be issued
279
280    // Pick an ElGamal keypair
281    let d = Scalar::random(&mut rng);
282    let D = &d * Btable;
283
284    // Pick a random client component of the id
285    let id_client = Scalar::random(&mut rng);
286
287    // Encrypt it (times the basepoint B) to the ElGamal public key D we
288    // just created
289    let eid_client = Scalar::random(&mut rng);
290    let EncIdClient = (&eid_client * Btable, &id_client * Btable + eid_client * D);
291
292    // Encrypt the other blinded attributes (times B) to D as well
293    let ebucket = Scalar::random(&mut rng);
294    let EncBucket = (
295        &ebucket * Btable,
296        &migration_cred.to_bucket * Btable + ebucket * D,
297    );
298    let eblockages = Scalar::random(&mut rng);
299    let new_blockages = lox_cred.blockages + Scalar::ONE;
300    let EncBlockages = (
301        &eblockages * Btable,
302        &new_blockages * Btable + eblockages * D,
303    );
304
305    // Construct the proof
306    let mut transcript = Transcript::new(b"blockage migration request");
307    let piUser = requestproof::prove_compact(
308        &mut transcript,
309        requestproof::ProveAssignments {
310            A,
311            B,
312            P_lox: &P_lox,
313            CBucket: &CBucket,
314            CSince: &CSince,
315            CInvRemain: &CInvRemain,
316            CBlockages: &CBlockages,
317            V_lox: &V_lox,
318            Xbucket: &lox_pub.X[2],
319            Xsince: &lox_pub.X[4],
320            Xinvremain: &lox_pub.X[5],
321            Xblockages: &lox_pub.X[6],
322            P_mig: &P_mig,
323            CFromBucket: &CFromBucket,
324            CToBucket: &CToBucket,
325            V_mig: &V_mig,
326            Xfrombucket: &migration_pub.X[2],
327            Xtobucket: &migration_pub.X[3],
328            D: &D,
329            EncIdClient0: &EncIdClient.0,
330            EncIdClient1: &EncIdClient.1,
331            EncBucket0: &EncBucket.0,
332            EncBucket1: &EncBucket.1,
333            EncBlockages0: &EncBlockages.0,
334            EncBlockages1_minus_B: &(EncBlockages.1 - B),
335            bucket: &lox_cred.bucket,
336            since: &lox_cred.level_since,
337            invremain: &lox_cred.invites_remaining,
338            blockages: &lox_cred.blockages,
339            zbucket: &zbucket,
340            zsince: &zsince,
341            zinvremain: &zinvremain,
342            zblockages: &zblockages,
343            negzQ_lox: &negzQ_lox,
344            tobucket: &migration_cred.to_bucket,
345            zfrombucket: &zfrombucket,
346            ztobucket: &ztobucket,
347            negzQ_mig: &negzQ_mig,
348            d: &d,
349            eid_client: &eid_client,
350            ebucket: &ebucket,
351            eblockages: &eblockages,
352            id_client: &id_client,
353        },
354    )
355    .0;
356
357    Ok((
358        Request {
359            P_lox,
360            id: lox_cred.id,
361            CBucket,
362            trust_level: lox_cred.trust_level,
363            CSince,
364            CInvRemain,
365            CBlockages,
366            CQ_lox,
367            P_mig,
368            CFromBucket,
369            CToBucket,
370            CQ_mig,
371            D,
372            EncIdClient,
373            EncBucket,
374            EncBlockages,
375            piUser,
376        },
377        State {
378            d,
379            D,
380            EncIdClient,
381            EncBucket,
382            EncBlockages,
383            id_client,
384            to_bucket: migration_cred.to_bucket,
385            trust_level: (level - 2).into(),
386            blockages: new_blockages,
387        },
388    ))
389}
390
391#[cfg(feature = "bridgeauth")]
392impl BridgeAuth {
393    /// Receive a blockage migration request
394    pub fn handle_blockage_migration(&mut self, req: Request) -> Result<Response, ProofError> {
395        let A: &RistrettoPoint = &CMZ_A;
396        let B: &RistrettoPoint = &CMZ_B;
397        let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
398        let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
399
400        if req.P_lox.is_identity() || req.P_mig.is_identity() {
401            return Err(ProofError::VerificationFailure);
402        }
403
404        // The trust level must be at least MIN_TRUST_LEVEL
405        let level: u32 = match scalar_u32(&req.trust_level) {
406            Some(v) => v,
407            None => return Err(ProofError::VerificationFailure),
408        };
409        if level < MIN_TRUST_LEVEL {
410            return Err(ProofError::VerificationFailure);
411        }
412
413        // Recompute the "error factors" using knowledge of our own
414        // (the issuer's) private key instead of knowledge of the
415        // hidden attributes
416        let Vprime_lox = (self.lox_priv.x[0]
417            + self.lox_priv.x[1] * req.id
418            + self.lox_priv.x[3] * req.trust_level)
419            * req.P_lox
420            + self.lox_priv.x[2] * req.CBucket
421            + self.lox_priv.x[4] * req.CSince
422            + self.lox_priv.x[5] * req.CInvRemain
423            + self.lox_priv.x[6] * req.CBlockages
424            - req.CQ_lox;
425
426        let migration_type: Scalar = MigrationType::Blockage.into();
427        let Vprime_mig = (self.migration_priv.x[0]
428            + self.migration_priv.x[1] * req.id
429            + self.migration_priv.x[4] * migration_type)
430            * req.P_mig
431            + self.migration_priv.x[2] * req.CFromBucket
432            + self.migration_priv.x[3] * req.CToBucket
433            - req.CQ_mig;
434
435        // Verify the zkp
436        let mut transcript = Transcript::new(b"blockage migration request");
437        requestproof::verify_compact(
438            &req.piUser,
439            &mut transcript,
440            requestproof::VerifyAssignments {
441                A: &A.compress(),
442                B: &B.compress(),
443                P_lox: &req.P_lox.compress(),
444                CBucket: &req.CBucket.compress(),
445                CSince: &req.CSince.compress(),
446                CInvRemain: &req.CInvRemain.compress(),
447                CBlockages: &req.CBlockages.compress(),
448                V_lox: &Vprime_lox.compress(),
449                Xbucket: &self.lox_pub.X[2].compress(),
450                Xsince: &self.lox_pub.X[4].compress(),
451                Xinvremain: &self.lox_pub.X[5].compress(),
452                Xblockages: &self.lox_pub.X[6].compress(),
453                P_mig: &req.P_mig.compress(),
454                CFromBucket: &req.CFromBucket.compress(),
455                CToBucket: &req.CToBucket.compress(),
456                V_mig: &Vprime_mig.compress(),
457                Xfrombucket: &self.migration_pub.X[2].compress(),
458                Xtobucket: &self.migration_pub.X[3].compress(),
459                D: &req.D.compress(),
460                EncIdClient0: &req.EncIdClient.0.compress(),
461                EncIdClient1: &req.EncIdClient.1.compress(),
462                EncBucket0: &req.EncBucket.0.compress(),
463                EncBucket1: &req.EncBucket.1.compress(),
464                EncBlockages0: &req.EncBlockages.0.compress(),
465                EncBlockages1_minus_B: &(req.EncBlockages.1 - B).compress(),
466            },
467        )?;
468
469        // Ensure the id has not been seen before, and add it to the
470        // seen list.
471        if self.id_filter.filter(&req.id) == SeenType::Seen {
472            return Err(ProofError::VerificationFailure);
473        }
474
475        // Blind issuing of the new Lox credential
476
477        // Choose a random server id component to add to the client's
478        // (blinded) id component
479        let mut rng = rand::thread_rng();
480        let id_server = Scalar::random(&mut rng);
481        let EncId = (req.EncIdClient.0, req.EncIdClient.1 + &id_server * Btable);
482
483        // Create the trust_level attrubute (Scalar), which will be
484        // 2 levels down from the one in the provided credential
485        let trust_level: Scalar = (level - 2).into();
486
487        // Create the level_since attribute (Scalar), which is today's
488        // Julian date
489        let level_since: Scalar = self.today().into();
490
491        // The invites remaining is the appropriate number for the new
492        // level (note that LEVEL_INVITATIONS[i] is the number of
493        // invitations for moving from level i to level i+1)
494        let invremain: Scalar = LEVEL_INVITATIONS[(level - 3) as usize].into();
495
496        // Compute the MAC on the visible attributes
497        let b = Scalar::random(&mut rng);
498        let P = &b * Btable;
499        let QHc = (self.lox_priv.x[0]
500            + self.lox_priv.x[3] * trust_level
501            + self.lox_priv.x[4] * level_since
502            + self.lox_priv.x[5] * invremain)
503            * P;
504
505        // El Gamal encrypt it to the public key req.D
506        let s = Scalar::random(&mut rng);
507        let EncQHc = (&s * Btable, QHc + s * req.D);
508
509        // Homomorphically compute the part of the MAC corresponding to
510        // the blinded attributes
511        let tid = self.lox_priv.x[1] * b;
512        let TId = &tid * Atable;
513        let EncQId = (tid * EncId.0, tid * EncId.1);
514        let tbucket = self.lox_priv.x[2] * b;
515        let TBucket = &tbucket * Atable;
516        let EncQBucket = (tbucket * req.EncBucket.0, tbucket * req.EncBucket.1);
517        let tblockages = self.lox_priv.x[6] * b;
518        let TBlockages = &tblockages * Atable;
519        let EncQBlockages = (
520            tblockages * req.EncBlockages.0,
521            tblockages * req.EncBlockages.1,
522        );
523
524        let EncQ = (
525            EncQHc.0 + EncQId.0 + EncQBucket.0 + EncQBlockages.0,
526            EncQHc.1 + EncQId.1 + EncQBucket.1 + EncQBlockages.1,
527        );
528
529        let mut transcript = Transcript::new(b"blockage migration issuing");
530        let piBlindIssue = blindissue::prove_compact(
531            &mut transcript,
532            blindissue::ProveAssignments {
533                A,
534                B,
535                P: &P,
536                EncQ0: &EncQ.0,
537                EncQ1: &EncQ.1,
538                X0: &self.lox_pub.X[0],
539                Xid: &self.lox_pub.X[1],
540                Xbucket: &self.lox_pub.X[2],
541                Xlevel: &self.lox_pub.X[3],
542                Xsince: &self.lox_pub.X[4],
543                Xinvremain: &self.lox_pub.X[5],
544                Xblockages: &self.lox_pub.X[6],
545                Plevel: &(trust_level * P),
546                Psince: &(level_since * P),
547                Pinvremain: &(invremain * P),
548                TId: &TId,
549                TBucket: &TBucket,
550                TBlockages: &TBlockages,
551                D: &req.D,
552                EncId0: &EncId.0,
553                EncId1: &EncId.1,
554                EncBucket0: &req.EncBucket.0,
555                EncBucket1: &req.EncBucket.1,
556                EncBlockages0: &req.EncBlockages.0,
557                EncBlockages1: &req.EncBlockages.1,
558                x0: &self.lox_priv.x[0],
559                x0tilde: &self.lox_priv.x0tilde,
560                xid: &self.lox_priv.x[1],
561                xbucket: &self.lox_priv.x[2],
562                xlevel: &self.lox_priv.x[3],
563                xsince: &self.lox_priv.x[4],
564                xinvremain: &self.lox_priv.x[5],
565                xblockages: &self.lox_priv.x[6],
566                s: &s,
567                b: &b,
568                tid: &tid,
569                tbucket: &tbucket,
570                tblockages: &tblockages,
571            },
572        )
573        .0;
574
575        Ok(Response {
576            level_since,
577            P,
578            EncQ,
579            id_server,
580            TId,
581            TBucket,
582            TBlockages,
583            piBlindIssue,
584        })
585    }
586}
587
588/// Handle the response to the request, producing the new Lox credential
589/// if successful.
590pub fn handle_response(
591    state: State,
592    resp: Response,
593    lox_pub: &IssuerPubKey,
594) -> Result<cred::Lox, ProofError> {
595    let A: &RistrettoPoint = &CMZ_A;
596    let B: &RistrettoPoint = &CMZ_B;
597    let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
598    if resp.P.is_identity() {
599        return Err(ProofError::VerificationFailure);
600    }
601
602    // Add the server's contribution to the id to our own, both in plain
603    // and encrypted form
604    let id = state.id_client + resp.id_server;
605    let EncId = (
606        state.EncIdClient.0,
607        state.EncIdClient.1 + &resp.id_server * Btable,
608    );
609
610    let new_level: u32 = match scalar_u32(&state.trust_level) {
611        Some(v) => v,
612        None => return Err(ProofError::VerificationFailure),
613    };
614    if new_level < 1 {
615        return Err(ProofError::VerificationFailure);
616    }
617
618    // The invites remaining is the appropriate number for the new level
619    // (note that LEVEL_INVITATIONS[i] is the number of invitations for
620    // moving from level i to level i+1)
621    let invremain: Scalar = LEVEL_INVITATIONS[(new_level - 1) as usize].into();
622
623    // Verify the proof
624    let mut transcript = Transcript::new(b"blockage migration issuing");
625    blindissue::verify_compact(
626        &resp.piBlindIssue,
627        &mut transcript,
628        blindissue::VerifyAssignments {
629            A: &A.compress(),
630            B: &B.compress(),
631            P: &resp.P.compress(),
632            EncQ0: &resp.EncQ.0.compress(),
633            EncQ1: &resp.EncQ.1.compress(),
634            X0: &lox_pub.X[0].compress(),
635            Xid: &lox_pub.X[1].compress(),
636            Xbucket: &lox_pub.X[2].compress(),
637            Xlevel: &lox_pub.X[3].compress(),
638            Xsince: &lox_pub.X[4].compress(),
639            Xinvremain: &lox_pub.X[5].compress(),
640            Xblockages: &lox_pub.X[6].compress(),
641            Plevel: &(state.trust_level * resp.P).compress(),
642            Psince: &(resp.level_since * resp.P).compress(),
643            Pinvremain: &(invremain * resp.P).compress(),
644            TId: &resp.TId.compress(),
645            TBucket: &resp.TBucket.compress(),
646            TBlockages: &resp.TBlockages.compress(),
647            D: &state.D.compress(),
648            EncId0: &EncId.0.compress(),
649            EncId1: &EncId.1.compress(),
650            EncBucket0: &state.EncBucket.0.compress(),
651            EncBucket1: &state.EncBucket.1.compress(),
652            EncBlockages0: &state.EncBlockages.0.compress(),
653            EncBlockages1: &state.EncBlockages.1.compress(),
654        },
655    )?;
656
657    // Decrypt EncQ
658    let Q = resp.EncQ.1 - (state.d * resp.EncQ.0);
659
660    Ok(cred::Lox {
661        P: resp.P,
662        Q,
663        id,
664        bucket: state.to_bucket,
665        trust_level: new_level.into(),
666        level_since: resp.level_since,
667        invites_remaining: invremain,
668        blockages: state.blockages,
669    })
670}