Skip to main content

lox_library/proto/
level_up.rs

1/*! A module for the protocol for the user to increase their trust level
2(from a level at least 1; use the trust promotion protocol to go from
3untrusted (level 0) to minimally trusted (level 1).
4
5They are allowed to do this as long as some amount of time (depending on
6their current level) has elapsed since their last level change, and they
7have a Bucket Reachability credential for their current bucket and
8today's date.  (Such credentials are placed daily in the encrypted
9bridge table.)
10
11The user presents their current Lox credential:
12- id: revealed
13- bucket: blinded
14- trust_level: revealed, and must be at least 1
15- level_since: blinded, but proved in ZK that it's at least the
16  appropriate number of days ago
17- invites_remaining: blinded
18- blockages: blinded, but proved in ZK that it's at most the appropriate
19  blockage limit for the target trust level
20
21and a Bucket Reachability credential:
22- date: revealed to be today
23- bucket: blinded, but proved in ZK that it's the same as in the Lox
24  credential above
25
26and a new Lox credential to be issued:
27
28- id: jointly chosen by the user and BA
29- bucket: blinded, but proved in ZK that it's the same as in the Lox
30  credential above
31- trust_level: revealed to be one more than the trust level above
32- level_since: today
33- invites_remaining: revealed to be the number of invites for the new
34  level (note that the invites_remaining from the previous credential
35  are _not_ carried over)
36- blockages: blinded, but proved in ZK that it's the same as in the
37  Lox credential above
38
39*/
40
41use curve25519_dalek::ristretto::RistrettoBasepointTable;
42use curve25519_dalek::ristretto::RistrettoPoint;
43use curve25519_dalek::scalar::Scalar;
44use curve25519_dalek::traits::IsIdentity;
45
46use lox_zkp::CompactProof;
47use lox_zkp::ProofError;
48use lox_zkp::Transcript;
49
50use serde::{Deserialize, Serialize};
51
52use super::super::cred;
53#[cfg(feature = "bridgeauth")]
54use super::super::dup_filter::SeenType;
55#[cfg(feature = "bridgeauth")]
56use super::super::pt_dbl;
57#[cfg(feature = "bridgeauth")]
58use super::super::BridgeAuth;
59use super::super::IssuerPubKey;
60use super::super::{scalar_dbl, scalar_u32};
61use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
62
63use super::errors::CredentialError;
64
65/// The maximum trust level in the system.  A user can run this level
66/// upgrade protocol when they're already at the max level; they will
67/// get a fresh invites_remaining batch, and reset their level_since
68/// field to today's date, but will remain in the max level.
69pub const MAX_LEVEL: usize = 4;
70
71/// LEVEL_INTERVAL\[i\] for i >= 1 is the minimum number of days a user
72/// must be at trust level i before advancing to level i+1 (or as above,
73/// remain at level i if i == MAX_LEVEL).  Note that the
74/// LEVEL_INTERVAL\[0\] entry is a dummy; the trust_promotion protocol
75/// is used instead of this one to move from level 0 to level 1.
76pub const LEVEL_INTERVAL: [u32; MAX_LEVEL + 1] = [0, 14, 28, 56, 84];
77
78/// LEVEL_INVITATIONS\[i\] for i >= 1 is the number of invitations a
79/// user will be eligible to issue upon advancing from level i to level
80/// i+1.  Again the LEVEL_INVITATIONS\[0\] entry is a dummy, as for
81/// LEVEL_INTERVAL.
82pub const LEVEL_INVITATIONS: [u32; MAX_LEVEL + 1] = [0, 2, 4, 6, 8];
83
84/// MAX_BLOCKAGES\[i\] for i >= 1 is the maximum number of bucket
85/// blockages this credential is allowed to have recorded in order to
86/// advance from level i to level i+1.  Again the LEVEL_INVITATIONS\[0\]
87/// entry is a dummy, as for LEVEL_INTERVAL.
88// If you change this to have a number greater than 7, you need to add
89// one or more bits to the ZKP.
90pub const MAX_BLOCKAGES: [u32; MAX_LEVEL + 1] = [0, 4, 3, 2, 2];
91
92#[derive(Serialize, Deserialize)]
93pub struct Request {
94    // Fields for blind showing the Lox credential
95    P: RistrettoPoint,
96    id: Scalar,
97    CBucket: RistrettoPoint,
98    level: Scalar,
99    CSince: RistrettoPoint,
100    CInvRemain: RistrettoPoint,
101    CBlockages: RistrettoPoint,
102    CQ: RistrettoPoint,
103
104    // Fields for blind showing the Bucket Reachability credential
105    P_reach: RistrettoPoint,
106    CBucket_reach: RistrettoPoint,
107    CQ_reach: RistrettoPoint,
108
109    // Fields for the inequality proof
110    // level_since + LEVEL_INTERVAL[level] <= today
111    CG1: RistrettoPoint,
112    CG2: RistrettoPoint,
113    CG3: RistrettoPoint,
114    CG4: RistrettoPoint,
115    CG5: RistrettoPoint,
116    CG6: RistrettoPoint,
117    CG7: RistrettoPoint,
118    CG8: RistrettoPoint,
119    CG0sq: RistrettoPoint,
120    CG1sq: RistrettoPoint,
121    CG2sq: RistrettoPoint,
122    CG3sq: RistrettoPoint,
123    CG4sq: RistrettoPoint,
124    CG5sq: RistrettoPoint,
125    CG6sq: RistrettoPoint,
126    CG7sq: RistrettoPoint,
127    CG8sq: RistrettoPoint,
128
129    // Fields for the inequality proof
130    // blockages <= MAX_BLOCKAGES[level]
131    CH1: RistrettoPoint,
132    CH2: RistrettoPoint,
133    CH0sq: RistrettoPoint,
134    CH1sq: RistrettoPoint,
135    CH2sq: RistrettoPoint,
136
137    // Fields for user blinding of the Lox credential to be issued
138    D: RistrettoPoint,
139    EncIdClient: (RistrettoPoint, RistrettoPoint),
140    EncBucket: (RistrettoPoint, RistrettoPoint),
141    EncBlockages: (RistrettoPoint, RistrettoPoint),
142
143    // The combined ZKP
144    piUser: CompactProof,
145}
146
147#[derive(Debug, Serialize, Deserialize)]
148pub struct State {
149    d: Scalar,
150    D: RistrettoPoint,
151    EncIdClient: (RistrettoPoint, RistrettoPoint),
152    EncBucket: (RistrettoPoint, RistrettoPoint),
153    EncBlockages: (RistrettoPoint, RistrettoPoint),
154    id_client: Scalar,
155    bucket: Scalar,
156    level: Scalar,
157    invremain: Scalar,
158    blockages: Scalar,
159}
160
161#[derive(Serialize, Deserialize)]
162pub struct Response {
163    // The fields for the new Lox credential; the new trust level is one
164    // more than the old trust level, so we don't have to include it
165    // here explicitly
166    P: RistrettoPoint,
167    EncQ: (RistrettoPoint, RistrettoPoint),
168    id_server: Scalar,
169    level_since: Scalar,
170    TId: RistrettoPoint,
171    TBucket: RistrettoPoint,
172    TBlockages: RistrettoPoint,
173
174    // The ZKP
175    piBlindIssue: CompactProof,
176}
177
178define_proof! {
179    requestproof,
180    "Level Upgrade Request",
181    (bucket, since, invremain, blockages, zbucket, zsince, zinvremain,
182     zblockages, negzQ,
183     zbucket_reach, negzQ_reach,
184     d, eid_client, ebucket, eblockages, id_client,
185     g0, g1, g2, g3, g4, g5, g6, g7, g8,
186     zg0, zg1, zg2, zg3, zg4, zg5, zg6, zg7, zg8,
187     wg0, wg1, wg2, wg3, wg4, wg5, wg6, wg7, wg8,
188     yg0, yg1, yg2, yg3, yg4, yg5, yg6, yg7, yg8,
189     h0, h1, h2,
190     zh0, zh1, zh2,
191     wh0, wh1, wh2,
192     yh0, yh1, yh2),
193    (P, CBucket, CSince, CInvRemain, CBlockages, V, Xbucket, Xsince,
194     Xinvremain, Xblockages,
195     P_reach, CBucket_reach, V_reach, Xbucket_reach,
196     D, EncIdClient0, EncIdClient1, EncBucket0, EncBucket1,
197     EncBlockages0, EncBlockages1,
198     CG0, CG1, CG2, CG3, CG4, CG5, CG6, CG7, CG8,
199     CG0sq, CG1sq, CG2sq, CG3sq, CG4sq, CG5sq, CG6sq, CG7sq, CG8sq,
200     CH0, CH1, CH2,
201     CH0sq, CH1sq, CH2sq),
202    (A, B) :
203    // Blind showing of the Lox credential
204    CBucket = (bucket*P + zbucket*A),
205    CSince = (since*P + zsince*A),
206    CInvRemain = (invremain*P + zinvremain*A),
207    CBlockages = (blockages*P + zblockages*A),
208    // Blind showing of the Bucket Reachability credential; note the
209    // same bucket is used in the proof
210    CBucket_reach = (bucket*P_reach + zbucket_reach*A),
211    // User blinding of the Lox credential to be issued
212    D = (d*B),
213    EncIdClient0 = (eid_client*B),
214    EncIdClient1 = (id_client*B + eid_client*D),
215    EncBucket0 = (ebucket*B),
216    EncBucket1 = (bucket*B + ebucket*D),
217    EncBlockages0 = (eblockages*B),
218    EncBlockages1 = (blockages*B + eblockages*D),
219    // Prove CSince encodes a value at least LEVEL_INTERVAL
220    // days ago (and technically at most LEVEL_INTERVAL+511 days
221    // ago): first prove each of g0, ..., g8 is a bit by proving that
222    // gi = gi^2
223    CG0 = (g0*P + zg0*A), CG0sq = (g0*CG0 + wg0*A), CG0sq = (g0*P + yg0*A),
224    CG1 = (g1*P + zg1*A), CG1sq = (g1*CG1 + wg1*A), CG1sq = (g1*P + yg1*A),
225    CG2 = (g2*P + zg2*A), CG2sq = (g2*CG2 + wg2*A), CG2sq = (g2*P + yg2*A),
226    CG3 = (g3*P + zg3*A), CG3sq = (g3*CG3 + wg3*A), CG3sq = (g3*P + yg3*A),
227    CG4 = (g4*P + zg4*A), CG4sq = (g4*CG4 + wg4*A), CG4sq = (g4*P + yg4*A),
228    CG5 = (g5*P + zg5*A), CG5sq = (g5*CG5 + wg5*A), CG5sq = (g5*P + yg5*A),
229    CG6 = (g6*P + zg6*A), CG6sq = (g6*CG6 + wg6*A), CG6sq = (g6*P + yg6*A),
230    CG7 = (g7*P + zg7*A), CG7sq = (g7*CG7 + wg7*A), CG7sq = (g7*P + yg7*A),
231    CG8 = (g8*P + zg8*A), CG8sq = (g8*CG8 + wg8*A), CG8sq = (g8*P + yg8*A),
232    // Then we'll check that CSince + LEVEL_INTERVAL*P + CG0 + 2*CG1
233    // + 4*CG2 + 8*CG3 + ... + 256*CG8 = today*P by having the verifier
234    // plug in today*P - (CSince + LEVEL_INTERVAL*P + 2*CG1 + 4*CG2
235    // + ... + 256*CG8) as its value of CG0.
236
237    // Prove CBlockage encodes a value at most MAX_BLOCKAGES (and at least
238    // MAX_BLOCKAGES-7)
239    CH0 = (h0*P + zh0*A), CH0sq = (h0*CH0 + wh0*A), CH0sq = (h0*P + yh0*A),
240    CH1 = (h1*P + zh1*A), CH1sq = (h1*CH1 + wh1*A), CH1sq = (h1*P + yh1*A),
241    CH2 = (h2*P + zh2*A), CH2sq = (h2*CH2 + wh2*A), CH2sq = (h2*P + yh2*A)
242    // Then we'll check that CBlockage + CH0 + 2*CH1 + 4*CH2 =
243    // MAX_BLOCKAGES*P by having the verifier plug in MAX_BLOCKAGES*P -
244    // (CBlockage - 2*CH1 - 4*CH2) as its value of CH0.
245}
246
247define_proof! {
248    blindissue,
249    "Level Upgrade Issuing",
250    (x0, x0tilde, xid, xbucket, xlevel, xsince, xinvremain, xblockages,
251     s, b, tid, tbucket, tblockages),
252    (P, EncQ0, EncQ1, X0, Xid, Xbucket, Xlevel, Xsince, Xinvremain,
253     Xblockages, Plevel, Psince, Pinvremain, TId, TBucket, TBlockages,
254     D, EncId0, EncId1, EncBucket0, EncBucket1, EncBlockages0, EncBlockages1),
255    (A, B):
256    Xid = (xid*A),
257    Xbucket = (xbucket*A),
258    Xlevel = (xlevel*A),
259    Xsince = (xsince*A),
260    Xinvremain = (xinvremain*A),
261    Xblockages = (xblockages*A),
262    X0 = (x0*B + x0tilde*A),
263    P = (b*B),
264    TId = (b*Xid),
265    TId = (tid*A),
266    TBucket = (b*Xbucket),
267    TBucket = (tbucket*A),
268    TBlockages = (b*Xblockages),
269    TBlockages = (tblockages*A),
270    EncQ0 = (s*B + tid*EncId0 + tbucket*EncBucket0 + tblockages*EncBlockages0),
271    EncQ1 = (s*D + tid*EncId1 + tbucket*EncBucket1
272            + tblockages*EncBlockages1 + x0*P + xlevel*Plevel + xsince*Psince
273            + xinvremain*Pinvremain)
274}
275
276pub fn request(
277    lox_cred: &cred::Lox,
278    reach_cred: &cred::BucketReachability,
279    lox_pub: &IssuerPubKey,
280    reach_pub: &IssuerPubKey,
281    today: u32,
282) -> Result<(Request, State), CredentialError> {
283    let A: &RistrettoPoint = &CMZ_A;
284    let B: &RistrettoPoint = &CMZ_B;
285    let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
286    let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
287
288    // Ensure the credential can be correctly shown: it must be the case
289    // that level_since + LEVEL_INTERVAL[level] <= today.
290    let level_since: u32 = match scalar_u32(&lox_cred.level_since) {
291        Some(v) => v,
292        None => {
293            return Err(CredentialError::InvalidField(
294                String::from("level_since"),
295                String::from("could not be converted to u32"),
296            ))
297        }
298    };
299    // The trust level has to be at least 1
300    let trust_level: u32 = match scalar_u32(&lox_cred.trust_level) {
301        Some(v) => v,
302        None => {
303            return Err(CredentialError::InvalidField(
304                String::from("trust_level"),
305                String::from("could not be converted to u32"),
306            ))
307        }
308    };
309    if trust_level < 1 || (trust_level as usize) > MAX_LEVEL {
310        return Err(CredentialError::InvalidField(
311            String::from("trust_level"),
312            format!("level {:?} not in range", trust_level),
313        ));
314    }
315    // The trust level has to be no higher than the highest level
316    let level_interval: u32 = match LEVEL_INTERVAL.get(trust_level as usize) {
317        Some(&v) => v,
318        None => {
319            return Err(CredentialError::InvalidField(
320                String::from("trust_level"),
321                format!("level {:?} not in range", trust_level),
322            ))
323        }
324    };
325    if level_since + level_interval > today {
326        return Err(CredentialError::TimeThresholdNotMet(
327            level_since + level_interval - today,
328        ));
329    }
330    // The credential can't be _too_ old
331    let diffdays = today - (level_since + level_interval);
332    if diffdays > 511 {
333        return Err(CredentialError::CredentialExpired);
334    }
335    // The current number of blockages
336    let blockages: u32 = match scalar_u32(&lox_cred.blockages) {
337        Some(v) => v,
338        None => {
339            return Err(CredentialError::InvalidField(
340                String::from("blockages"),
341                String::from("could not be converted to u32"),
342            ))
343        }
344    };
345    if blockages > MAX_BLOCKAGES[trust_level as usize] {
346        return Err(CredentialError::ExceededBlockagesThreshold);
347    }
348    let blockage_diff = MAX_BLOCKAGES[trust_level as usize] - blockages;
349    // The buckets in the Lox and Bucket Reachability credentials have
350    // to match
351    if lox_cred.bucket != reach_cred.bucket {
352        return Err(CredentialError::CredentialMismatch);
353    }
354    // The Bucket Reachability credential has to be dated today
355    let reach_date: u32 = match scalar_u32(&reach_cred.date) {
356        Some(v) => v,
357        None => {
358            return Err(CredentialError::InvalidField(
359                String::from("date"),
360                String::from("could not be converted to u32"),
361            ))
362        }
363    };
364    if reach_date != today {
365        return Err(CredentialError::InvalidField(
366            String::from("date"),
367            String::from("reachability credential must be generated today"),
368        ));
369    }
370    // The new trust level
371    let new_level = if (trust_level as usize) < MAX_LEVEL {
372        trust_level + 1
373    } else {
374        trust_level
375    };
376
377    // Blind showing the Lox credential
378
379    // Reblind P and Q
380    let mut rng = rand::thread_rng();
381    let t = Scalar::random(&mut rng);
382    let P = t * lox_cred.P;
383    let Q = t * lox_cred.Q;
384
385    // Form Pedersen commitments to the blinded attributes
386    let zbucket = Scalar::random(&mut rng);
387    let zsince = Scalar::random(&mut rng);
388    let zinvremain = Scalar::random(&mut rng);
389    let zblockages = Scalar::random(&mut rng);
390    let CBucket = lox_cred.bucket * P + &zbucket * Atable;
391    let CSince = lox_cred.level_since * P + &zsince * Atable;
392    let CInvRemain = lox_cred.invites_remaining * P + &zinvremain * Atable;
393    let CBlockages = lox_cred.blockages * P + &zblockages * Atable;
394
395    // Form a Pedersen commitment to the MAC Q
396    // We flip the sign of zQ from that of the Hyphae paper so that
397    // the ZKP has a "+" instead of a "-", as that's what the zkp
398    // macro supports.
399    let negzQ = Scalar::random(&mut rng);
400    let CQ = Q - &negzQ * Atable;
401
402    // Compute the "error factor"
403    let V = zbucket * lox_pub.X[2]
404        + zsince * lox_pub.X[4]
405        + zinvremain * lox_pub.X[5]
406        + zblockages * lox_pub.X[6]
407        + &negzQ * Atable;
408
409    // Blind showing the Bucket Reachability credential
410
411    // Reblind P and Q
412    let t_reach = Scalar::random(&mut rng);
413    let P_reach = t_reach * reach_cred.P;
414    let Q_reach = t_reach * reach_cred.Q;
415
416    // Form Pedersen commitments to the blinded attributes
417    let zbucket_reach = Scalar::random(&mut rng);
418    let CBucket_reach = reach_cred.bucket * P_reach + &zbucket_reach * Atable;
419
420    // Form a Pedersen commitment to the MAC Q
421    // We flip the sign of zQ from that of the Hyphae paper so that
422    // the ZKP has a "+" instead of a "-", as that's what the zkp
423    // macro supports.
424    let negzQ_reach = Scalar::random(&mut rng);
425    let CQ_reach = Q_reach - &negzQ_reach * Atable;
426
427    // Compute the "error factor"
428    let V_reach = zbucket_reach * reach_pub.X[2] + &negzQ_reach * Atable;
429
430    // User blinding for the Lox certificate to be issued
431
432    // Pick an ElGamal keypair
433    let d = Scalar::random(&mut rng);
434    let D = &d * Btable;
435
436    // Pick a random client component of the id
437    let id_client = Scalar::random(&mut rng);
438
439    // Encrypt it (times the basepoint B) to the ElGamal public key D we
440    // just created
441    let eid_client = Scalar::random(&mut rng);
442    let EncIdClient = (&eid_client * Btable, &id_client * Btable + eid_client * D);
443
444    // Encrypt the other blinded fields (times B) to D as well
445    let ebucket = Scalar::random(&mut rng);
446    let EncBucket = (&ebucket * Btable, &lox_cred.bucket * Btable + ebucket * D);
447    let newinvites: Scalar = LEVEL_INVITATIONS[trust_level as usize].into();
448    let eblockages = Scalar::random(&mut rng);
449    let EncBlockages = (
450        &eblockages * Btable,
451        &lox_cred.blockages * Btable + eblockages * D,
452    );
453
454    // The range proof that 0 <= diffdays <= 511
455
456    // Extract the 9 bits from diffdays
457    let g0: Scalar = (diffdays & 1).into();
458    let g1: Scalar = ((diffdays >> 1) & 1).into();
459    let g2: Scalar = ((diffdays >> 2) & 1).into();
460    let g3: Scalar = ((diffdays >> 3) & 1).into();
461    let g4: Scalar = ((diffdays >> 4) & 1).into();
462    let g5: Scalar = ((diffdays >> 5) & 1).into();
463    let g6: Scalar = ((diffdays >> 6) & 1).into();
464    let g7: Scalar = ((diffdays >> 7) & 1).into();
465    let g8: Scalar = ((diffdays >> 8) & 1).into();
466
467    // Pick random factors for the Pedersen commitments
468    let wg0 = Scalar::random(&mut rng);
469    let zg1 = Scalar::random(&mut rng);
470    let wg1 = Scalar::random(&mut rng);
471    let zg2 = Scalar::random(&mut rng);
472    let wg2 = Scalar::random(&mut rng);
473    let zg3 = Scalar::random(&mut rng);
474    let wg3 = Scalar::random(&mut rng);
475    let zg4 = Scalar::random(&mut rng);
476    let wg4 = Scalar::random(&mut rng);
477    let zg5 = Scalar::random(&mut rng);
478    let wg5 = Scalar::random(&mut rng);
479    let zg6 = Scalar::random(&mut rng);
480    let wg6 = Scalar::random(&mut rng);
481    let zg7 = Scalar::random(&mut rng);
482    let wg7 = Scalar::random(&mut rng);
483    let zg8 = Scalar::random(&mut rng);
484    let wg8 = Scalar::random(&mut rng);
485
486    // Compute zg0 to cancel things out as
487    // zg0 = -(zsince + 2*zg1 + 4*zg2 + 8*zg3 + 16*zg4 + 32*zg5 + 64*zg6 + 128*zg7 + 256*zg8)
488    // but use Horner's method
489    let zg0 = -(scalar_dbl(
490        &(scalar_dbl(
491            &(scalar_dbl(
492                &(scalar_dbl(
493                    &(scalar_dbl(
494                        &(scalar_dbl(&(scalar_dbl(&(scalar_dbl(&zg8) + zg7)) + zg6)) + zg5),
495                    ) + zg4),
496                ) + zg3),
497            ) + zg2),
498        ) + zg1),
499    ) + zsince);
500
501    let yg0 = wg0 + g0 * zg0;
502    let yg1 = wg1 + g1 * zg1;
503    let yg2 = wg2 + g2 * zg2;
504    let yg3 = wg3 + g3 * zg3;
505    let yg4 = wg4 + g4 * zg4;
506    let yg5 = wg5 + g5 * zg5;
507    let yg6 = wg6 + g6 * zg6;
508    let yg7 = wg7 + g7 * zg7;
509    let yg8 = wg8 + g8 * zg8;
510
511    let CG0 = g0 * P + &zg0 * Atable;
512    let CG1 = g1 * P + &zg1 * Atable;
513    let CG2 = g2 * P + &zg2 * Atable;
514    let CG3 = g3 * P + &zg3 * Atable;
515    let CG4 = g4 * P + &zg4 * Atable;
516    let CG5 = g5 * P + &zg5 * Atable;
517    let CG6 = g6 * P + &zg6 * Atable;
518    let CG7 = g7 * P + &zg7 * Atable;
519    let CG8 = g8 * P + &zg8 * Atable;
520
521    let CG0sq = g0 * P + &yg0 * Atable;
522    let CG1sq = g1 * P + &yg1 * Atable;
523    let CG2sq = g2 * P + &yg2 * Atable;
524    let CG3sq = g3 * P + &yg3 * Atable;
525    let CG4sq = g4 * P + &yg4 * Atable;
526    let CG5sq = g5 * P + &yg5 * Atable;
527    let CG6sq = g6 * P + &yg6 * Atable;
528    let CG7sq = g7 * P + &yg7 * Atable;
529    let CG8sq = g8 * P + &yg8 * Atable;
530
531    // The range proof that 0 <= blockage_diff <= 7
532
533    // Extract the 3 bits from blockage_diff
534    let h0: Scalar = (blockage_diff & 1).into();
535    let h1: Scalar = ((blockage_diff >> 1) & 1).into();
536    let h2: Scalar = ((blockage_diff >> 2) & 1).into();
537
538    // Pick random factors for the Pedersen commitments
539    let wh0 = Scalar::random(&mut rng);
540    let zh1 = Scalar::random(&mut rng);
541    let wh1 = Scalar::random(&mut rng);
542    let zh2 = Scalar::random(&mut rng);
543    let wh2 = Scalar::random(&mut rng);
544
545    // Compute zh0 to cancel things out as
546    // zh0 = -(zblockages + 2*zh1 + 4*zh2)
547    // but use Horner's method
548    let zh0 = -(scalar_dbl(&(scalar_dbl(&zh2) + zh1)) + zblockages);
549
550    let yh0 = wh0 + h0 * zh0;
551    let yh1 = wh1 + h1 * zh1;
552    let yh2 = wh2 + h2 * zh2;
553
554    let CH0 = h0 * P + &zh0 * Atable;
555    let CH1 = h1 * P + &zh1 * Atable;
556    let CH2 = h2 * P + &zh2 * Atable;
557
558    let CH0sq = h0 * P + &yh0 * Atable;
559    let CH1sq = h1 * P + &yh1 * Atable;
560    let CH2sq = h2 * P + &yh2 * Atable;
561
562    // Construct the proof
563    let mut transcript = Transcript::new(b"level upgrade request");
564    let piUser = requestproof::prove_compact(
565        &mut transcript,
566        requestproof::ProveAssignments {
567            A,
568            B,
569            P: &P,
570            CBucket: &CBucket,
571            CSince: &CSince,
572            CInvRemain: &CInvRemain,
573            CBlockages: &CBlockages,
574            V: &V,
575            Xbucket: &lox_pub.X[2],
576            Xsince: &lox_pub.X[4],
577            Xinvremain: &lox_pub.X[5],
578            Xblockages: &lox_pub.X[6],
579            P_reach: &P_reach,
580            CBucket_reach: &CBucket_reach,
581            V_reach: &V_reach,
582            Xbucket_reach: &reach_pub.X[2],
583            D: &D,
584            EncIdClient0: &EncIdClient.0,
585            EncIdClient1: &EncIdClient.1,
586            EncBucket0: &EncBucket.0,
587            EncBucket1: &EncBucket.1,
588            EncBlockages0: &EncBlockages.0,
589            EncBlockages1: &EncBlockages.1,
590            CG0: &CG0,
591            CG1: &CG1,
592            CG2: &CG2,
593            CG3: &CG3,
594            CG4: &CG4,
595            CG5: &CG5,
596            CG6: &CG6,
597            CG7: &CG7,
598            CG8: &CG8,
599            CG0sq: &CG0sq,
600            CG1sq: &CG1sq,
601            CG2sq: &CG2sq,
602            CG3sq: &CG3sq,
603            CG4sq: &CG4sq,
604            CG5sq: &CG5sq,
605            CG6sq: &CG6sq,
606            CG7sq: &CG7sq,
607            CG8sq: &CG8sq,
608            CH0: &CH0,
609            CH1: &CH1,
610            CH2: &CH2,
611            CH0sq: &CH0sq,
612            CH1sq: &CH1sq,
613            CH2sq: &CH2sq,
614            bucket: &lox_cred.bucket,
615            since: &lox_cred.level_since,
616            invremain: &lox_cred.invites_remaining,
617            blockages: &lox_cred.blockages,
618            zbucket: &zbucket,
619            zsince: &zsince,
620            zinvremain: &zinvremain,
621            zblockages: &zblockages,
622            negzQ: &negzQ,
623            zbucket_reach: &zbucket_reach,
624            negzQ_reach: &negzQ_reach,
625            d: &d,
626            eid_client: &eid_client,
627            ebucket: &ebucket,
628            eblockages: &eblockages,
629            id_client: &id_client,
630            g0: &g0,
631            g1: &g1,
632            g2: &g2,
633            g3: &g3,
634            g4: &g4,
635            g5: &g5,
636            g6: &g6,
637            g7: &g7,
638            g8: &g8,
639            zg0: &zg0,
640            zg1: &zg1,
641            zg2: &zg2,
642            zg3: &zg3,
643            zg4: &zg4,
644            zg5: &zg5,
645            zg6: &zg6,
646            zg7: &zg7,
647            zg8: &zg8,
648            wg0: &wg0,
649            wg1: &wg1,
650            wg2: &wg2,
651            wg3: &wg3,
652            wg4: &wg4,
653            wg5: &wg5,
654            wg6: &wg6,
655            wg7: &wg7,
656            wg8: &wg8,
657            yg0: &yg0,
658            yg1: &yg1,
659            yg2: &yg2,
660            yg3: &yg3,
661            yg4: &yg4,
662            yg5: &yg5,
663            yg6: &yg6,
664            yg7: &yg7,
665            yg8: &yg8,
666            h0: &h0,
667            h1: &h1,
668            h2: &h2,
669            zh0: &zh0,
670            zh1: &zh1,
671            zh2: &zh2,
672            wh0: &wh0,
673            wh1: &wh1,
674            wh2: &wh2,
675            yh0: &yh0,
676            yh1: &yh1,
677            yh2: &yh2,
678        },
679    )
680    .0;
681
682    Ok((
683        Request {
684            P,
685            id: lox_cred.id,
686            CBucket,
687            level: lox_cred.trust_level,
688            CSince,
689            CInvRemain,
690            CBlockages,
691            CQ,
692            P_reach,
693            CBucket_reach,
694            CQ_reach,
695            D,
696            EncIdClient,
697            EncBucket,
698            EncBlockages,
699            CG1,
700            CG2,
701            CG3,
702            CG4,
703            CG5,
704            CG6,
705            CG7,
706            CG8,
707            CG0sq,
708            CG1sq,
709            CG2sq,
710            CG3sq,
711            CG4sq,
712            CG5sq,
713            CG6sq,
714            CG7sq,
715            CG8sq,
716            CH1,
717            CH2,
718            CH0sq,
719            CH1sq,
720            CH2sq,
721            piUser,
722        },
723        State {
724            d,
725            D,
726            EncIdClient,
727            EncBucket,
728            EncBlockages,
729            id_client,
730            bucket: lox_cred.bucket,
731            level: new_level.into(),
732            invremain: newinvites,
733            blockages: lox_cred.blockages,
734        },
735    ))
736}
737
738#[cfg(feature = "bridgeauth")]
739impl BridgeAuth {
740    /// Receive a level up request
741    pub fn handle_level_up(&mut self, req: Request) -> Result<Response, ProofError> {
742        let A: &RistrettoPoint = &CMZ_A;
743        let B: &RistrettoPoint = &CMZ_B;
744        let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
745        let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
746
747        if req.P.is_identity() || req.P_reach.is_identity() {
748            return Err(ProofError::VerificationFailure);
749        }
750
751        let today: Scalar = self.today().into();
752
753        // Get the level and ensure it's at most MAX_LEVEL
754        let level: usize = match scalar_u32(&req.level) {
755            Some(l) if l as usize <= MAX_LEVEL => l as usize,
756            _ => return Err(ProofError::VerificationFailure),
757        };
758
759        // Recompute the "error factors" using knowledge of our own
760        // (the issuer's) private key instead of knowledge of the
761        // hidden attributes
762        let Vprime =
763            (self.lox_priv.x[0] + self.lox_priv.x[1] * req.id + self.lox_priv.x[3] * req.level)
764                * req.P
765                + self.lox_priv.x[2] * req.CBucket
766                + self.lox_priv.x[4] * req.CSince
767                + self.lox_priv.x[5] * req.CInvRemain
768                + self.lox_priv.x[6] * req.CBlockages
769                - req.CQ;
770
771        let Vprime_reach = (self.reachability_priv.x[0] + self.reachability_priv.x[1] * today)
772            * req.P_reach
773            + self.reachability_priv.x[2] * req.CBucket_reach
774            - req.CQ_reach;
775
776        // Recompute CG0 using Horner's method
777        let interval: Scalar = LEVEL_INTERVAL[level].into();
778        let CG0prime = (today - interval) * req.P
779            - req.CSince
780            - pt_dbl(
781                &(pt_dbl(
782                    &(pt_dbl(
783                        &(pt_dbl(
784                            &(pt_dbl(
785                                &(pt_dbl(&(pt_dbl(&(pt_dbl(&req.CG8) + req.CG7)) + req.CG6))
786                                    + req.CG5),
787                            ) + req.CG4),
788                        ) + req.CG3),
789                    ) + req.CG2),
790                ) + req.CG1),
791            );
792
793        // Recompute CH0 using Horner's method
794        let mblk: Scalar = MAX_BLOCKAGES[level].into();
795        let CH0prime = mblk * req.P - req.CBlockages - pt_dbl(&(pt_dbl(&req.CH2) + req.CH1));
796
797        // Verify the ZKP
798        let mut transcript = Transcript::new(b"level upgrade request");
799        requestproof::verify_compact(
800            &req.piUser,
801            &mut transcript,
802            requestproof::VerifyAssignments {
803                A: &A.compress(),
804                B: &B.compress(),
805                P: &req.P.compress(),
806                CBucket: &req.CBucket.compress(),
807                CSince: &req.CSince.compress(),
808                CInvRemain: &req.CInvRemain.compress(),
809                CBlockages: &req.CBlockages.compress(),
810                V: &Vprime.compress(),
811                Xbucket: &self.lox_pub.X[2].compress(),
812                Xsince: &self.lox_pub.X[4].compress(),
813                Xinvremain: &self.lox_pub.X[5].compress(),
814                Xblockages: &self.lox_pub.X[6].compress(),
815                P_reach: &req.P_reach.compress(),
816                CBucket_reach: &req.CBucket_reach.compress(),
817                V_reach: &Vprime_reach.compress(),
818                Xbucket_reach: &self.reachability_pub.X[2].compress(),
819                D: &req.D.compress(),
820                EncIdClient0: &req.EncIdClient.0.compress(),
821                EncIdClient1: &req.EncIdClient.1.compress(),
822                EncBucket0: &req.EncBucket.0.compress(),
823                EncBucket1: &req.EncBucket.1.compress(),
824                EncBlockages0: &req.EncBlockages.0.compress(),
825                EncBlockages1: &req.EncBlockages.1.compress(),
826                CG0: &CG0prime.compress(),
827                CG1: &req.CG1.compress(),
828                CG2: &req.CG2.compress(),
829                CG3: &req.CG3.compress(),
830                CG4: &req.CG4.compress(),
831                CG5: &req.CG5.compress(),
832                CG6: &req.CG6.compress(),
833                CG7: &req.CG7.compress(),
834                CG8: &req.CG8.compress(),
835                CG0sq: &req.CG0sq.compress(),
836                CG1sq: &req.CG1sq.compress(),
837                CG2sq: &req.CG2sq.compress(),
838                CG3sq: &req.CG3sq.compress(),
839                CG4sq: &req.CG4sq.compress(),
840                CG5sq: &req.CG5sq.compress(),
841                CG6sq: &req.CG6sq.compress(),
842                CG7sq: &req.CG7sq.compress(),
843                CG8sq: &req.CG8sq.compress(),
844                CH0: &CH0prime.compress(),
845                CH1: &req.CH1.compress(),
846                CH2: &req.CH2.compress(),
847                CH0sq: &req.CH0sq.compress(),
848                CH1sq: &req.CH1sq.compress(),
849                CH2sq: &req.CH2sq.compress(),
850            },
851        )?;
852
853        // Ensure the id has not been seen before, and add it to the
854        // seen list.
855        if self.id_filter.filter(&req.id) == SeenType::Seen {
856            return Err(ProofError::VerificationFailure);
857        }
858
859        // Blind issuing of the new Lox credential
860
861        // Choose a random server id component to add to the client's
862        // (blinded) id component
863        let mut rng = rand::thread_rng();
864        let id_server = Scalar::random(&mut rng);
865        let EncId = (req.EncIdClient.0, req.EncIdClient.1 + &id_server * Btable);
866
867        // Create the trust_level attrubute (Scalar), which will be
868        // one more than the current level, unless the current level is
869        // MAX_LEVEL, in which case it stays the same
870        let new_level = if level < MAX_LEVEL { level + 1 } else { level };
871        let trust_level: Scalar = (new_level as u64).into();
872
873        // Create the level_since attribute (Scalar), which is today's
874        // Julian date
875        let level_since: Scalar = self.today().into();
876
877        // Create the invitations_remaining attribute (Scalar), which is
878        // the number of invitations at the new level
879        let invitations_remaining: Scalar = LEVEL_INVITATIONS[level].into();
880
881        // Compute the MAC on the visible attributes
882        let b = Scalar::random(&mut rng);
883        let P = &b * Btable;
884        let QHc = (self.lox_priv.x[0]
885            + self.lox_priv.x[3] * trust_level
886            + self.lox_priv.x[4] * level_since
887            + self.lox_priv.x[5] * invitations_remaining)
888            * P;
889
890        // El Gamal encrypt it to the public key req.D
891        let s = Scalar::random(&mut rng);
892        let EncQHc = (&s * Btable, QHc + s * req.D);
893
894        // Homomorphically compute the part of the MAC corresponding to
895        // the blinded attributes
896        let tid = self.lox_priv.x[1] * b;
897        let TId = &tid * Atable;
898        let EncQId = (tid * EncId.0, tid * EncId.1);
899        let tbucket = self.lox_priv.x[2] * b;
900        let TBucket = &tbucket * Atable;
901        let EncQBucket = (tbucket * req.EncBucket.0, tbucket * req.EncBucket.1);
902        let tblockages = self.lox_priv.x[6] * b;
903        let TBlockages = &tblockages * Atable;
904        let EncQBlockages = (
905            tblockages * req.EncBlockages.0,
906            tblockages * req.EncBlockages.1,
907        );
908
909        let EncQ = (
910            EncQHc.0 + EncQId.0 + EncQBucket.0 + EncQBlockages.0,
911            EncQHc.1 + EncQId.1 + EncQBucket.1 + EncQBlockages.1,
912        );
913
914        let mut transcript = Transcript::new(b"level upgrade issuing");
915        let piBlindIssue = blindissue::prove_compact(
916            &mut transcript,
917            blindissue::ProveAssignments {
918                A,
919                B,
920                P: &P,
921                EncQ0: &EncQ.0,
922                EncQ1: &EncQ.1,
923                X0: &self.lox_pub.X[0],
924                Xid: &self.lox_pub.X[1],
925                Xbucket: &self.lox_pub.X[2],
926                Xlevel: &self.lox_pub.X[3],
927                Xsince: &self.lox_pub.X[4],
928                Xinvremain: &self.lox_pub.X[5],
929                Xblockages: &self.lox_pub.X[6],
930                Plevel: &(trust_level * P),
931                Psince: &(level_since * P),
932                Pinvremain: &(invitations_remaining * P),
933                TId: &TId,
934                TBucket: &TBucket,
935                TBlockages: &TBlockages,
936                D: &req.D,
937                EncId0: &EncId.0,
938                EncId1: &EncId.1,
939                EncBucket0: &req.EncBucket.0,
940                EncBucket1: &req.EncBucket.1,
941                EncBlockages0: &req.EncBlockages.0,
942                EncBlockages1: &req.EncBlockages.1,
943                x0: &self.lox_priv.x[0],
944                x0tilde: &self.lox_priv.x0tilde,
945                xid: &self.lox_priv.x[1],
946                xbucket: &self.lox_priv.x[2],
947                xlevel: &self.lox_priv.x[3],
948                xsince: &self.lox_priv.x[4],
949                xinvremain: &self.lox_priv.x[5],
950                xblockages: &self.lox_priv.x[6],
951                s: &s,
952                b: &b,
953                tid: &tid,
954                tbucket: &tbucket,
955                tblockages: &tblockages,
956            },
957        )
958        .0;
959
960        Ok(Response {
961            P,
962            EncQ,
963            id_server,
964            level_since,
965            TId,
966            TBucket,
967            TBlockages,
968            piBlindIssue,
969        })
970    }
971}
972
973/// Handle the response to the request, producing the new Lox credential
974/// if successful.
975pub fn handle_response(
976    state: State,
977    resp: Response,
978    lox_pub: &IssuerPubKey,
979) -> Result<cred::Lox, ProofError> {
980    let A: &RistrettoPoint = &CMZ_A;
981    let B: &RistrettoPoint = &CMZ_B;
982    let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
983
984    if resp.P.is_identity() {
985        return Err(ProofError::VerificationFailure);
986    }
987
988    // Add the server's contribution to the id to our own, both in plain
989    // and encrypted form
990    let id = state.id_client + resp.id_server;
991    let EncId = (
992        state.EncIdClient.0,
993        state.EncIdClient.1 + &resp.id_server * Btable,
994    );
995
996    // Verify the proof
997    let mut transcript = Transcript::new(b"level upgrade issuing");
998    blindissue::verify_compact(
999        &resp.piBlindIssue,
1000        &mut transcript,
1001        blindissue::VerifyAssignments {
1002            A: &A.compress(),
1003            B: &B.compress(),
1004            P: &resp.P.compress(),
1005            EncQ0: &resp.EncQ.0.compress(),
1006            EncQ1: &resp.EncQ.1.compress(),
1007            X0: &lox_pub.X[0].compress(),
1008            Xid: &lox_pub.X[1].compress(),
1009            Xbucket: &lox_pub.X[2].compress(),
1010            Xlevel: &lox_pub.X[3].compress(),
1011            Xsince: &lox_pub.X[4].compress(),
1012            Xinvremain: &lox_pub.X[5].compress(),
1013            Xblockages: &lox_pub.X[6].compress(),
1014            Plevel: &(state.level * resp.P).compress(),
1015            Psince: &(resp.level_since * resp.P).compress(),
1016            Pinvremain: &(state.invremain * resp.P).compress(),
1017            TId: &resp.TId.compress(),
1018            TBucket: &resp.TBucket.compress(),
1019            TBlockages: &resp.TBlockages.compress(),
1020            D: &state.D.compress(),
1021            EncId0: &EncId.0.compress(),
1022            EncId1: &EncId.1.compress(),
1023            EncBucket0: &state.EncBucket.0.compress(),
1024            EncBucket1: &state.EncBucket.1.compress(),
1025            EncBlockages0: &state.EncBlockages.0.compress(),
1026            EncBlockages1: &state.EncBlockages.1.compress(),
1027        },
1028    )?;
1029
1030    // Decrypt EncQ
1031    let Q = resp.EncQ.1 - (state.d * resp.EncQ.0);
1032
1033    Ok(cred::Lox {
1034        P: resp.P,
1035        Q,
1036        id,
1037        bucket: state.bucket,
1038        trust_level: state.level,
1039        level_since: resp.level_since,
1040        invites_remaining: state.invremain,
1041        blockages: state.blockages,
1042    })
1043}