lox_library/proto/
migration.rs

1/*! A module for the protocol for the user to migrate from one bucket to
2another and change trust level from untrusted (trust level 0) to trusted
3(trust level 1).
4
5The user presents their current Lox credential:
6
7- id: revealed
8- bucket: blinded
9- trust_level: revealed to be 0
10- level_since: blinded
11- invites_remaining: revealed to be 0
12- blockages: revealed to be 0
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: 1
27- level_since: today
28- invites_remaining: 0
29- blockages: 0
30
31*/
32
33use curve25519_dalek::ristretto::RistrettoBasepointTable;
34use curve25519_dalek::ristretto::RistrettoPoint;
35use curve25519_dalek::scalar::Scalar;
36use curve25519_dalek::traits::IsIdentity;
37
38use lox_zkp::CompactProof;
39use lox_zkp::ProofError;
40use lox_zkp::Transcript;
41
42use serde::{Deserialize, Serialize};
43
44use super::super::cred;
45#[cfg(feature = "bridgeauth")]
46use super::super::dup_filter::SeenType;
47#[cfg(feature = "bridgeauth")]
48use super::super::BridgeAuth;
49use super::super::IssuerPubKey;
50use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
51
52use super::errors::CredentialError;
53
54#[derive(Serialize, Deserialize)]
55pub struct Request {
56    // Fields for blind showing the Lox credential
57    // We don't need to include invites_remaining or blockages,
58    // since they must be 0
59    P_lox: RistrettoPoint,
60    id: Scalar,
61    CBucket: RistrettoPoint,
62    trust_level: Scalar,
63    CSince: RistrettoPoint,
64    CQ_lox: RistrettoPoint,
65
66    // Fields for blind showing the Migration credential
67    P_mig: RistrettoPoint,
68    CFromBucket: RistrettoPoint,
69    CToBucket: RistrettoPoint,
70    CQ_mig: RistrettoPoint,
71
72    // Fields for user blinding of the Lox credential to be issued
73    D: RistrettoPoint,
74    EncIdClient: (RistrettoPoint, RistrettoPoint),
75    EncBucket: (RistrettoPoint, RistrettoPoint),
76
77    // The combined ZKP
78    piUser: CompactProof,
79}
80
81#[derive(Debug, Serialize, Deserialize)]
82pub struct State {
83    d: Scalar,
84    D: RistrettoPoint,
85    EncIdClient: (RistrettoPoint, RistrettoPoint),
86    EncBucket: (RistrettoPoint, RistrettoPoint),
87    id_client: Scalar,
88    to_bucket: Scalar,
89}
90
91#[derive(Serialize, Deserialize)]
92pub struct Response {
93    // The new attributes; trust_level = 1 is implicit
94    level_since: Scalar,
95
96    // The fields for the new Lox credential
97    P: RistrettoPoint,
98    EncQ: (RistrettoPoint, RistrettoPoint),
99    id_server: Scalar,
100    TId: RistrettoPoint,
101    TBucket: RistrettoPoint,
102
103    // The ZKP
104    piBlindIssue: CompactProof,
105}
106
107define_proof! {
108    requestproof,
109    "Migration Request",
110    (bucket, since, zbucket, zsince, negzQ_lox,
111     tobucket, zfrombucket, ztobucket, negzQ_mig,
112     d, eid_client, ebucket, id_client),
113    (P_lox, CBucket, CSince, V_lox, Xbucket, Xsince,
114     P_mig, CFromBucket, CToBucket, V_mig, Xfrombucket, Xtobucket,
115     D, EncIdClient0, EncIdClient1, EncBucket0, EncBucket1),
116    (A, B):
117    // Blind showing of the Lox credential
118    CBucket = (bucket*P_lox + zbucket*A),
119    CSince = (since*P_lox + zsince*A),
120    V_lox = (zbucket*Xbucket + zsince*Xsince + negzQ_lox*A),
121    // Blind showing of the Migration credential; note the use of the
122    // same "bucket" secret variable
123    CFromBucket = (bucket*P_mig + zfrombucket*A),
124    CToBucket = (tobucket*P_mig + ztobucket*A),
125    V_mig = (zfrombucket*Xfrombucket + ztobucket*Xtobucket + negzQ_mig*A),
126    // User blinding of the Lox credential to be issued; note the use of
127    // the same "tobucket" secret variable
128    D = (d*B),
129    EncIdClient0 = (eid_client*B),
130    EncIdClient1 = (id_client*B + eid_client*D),
131    EncBucket0 = (ebucket*B),
132    EncBucket1 = (tobucket*B + ebucket*D)
133}
134
135define_proof! {
136    blindissue,
137    "Migration Blind Issuing",
138    (x0, x0tilde, xid, xbucket, xlevel, xsince, s, b, tid, tbucket),
139    (P, EncQ0, EncQ1, X0, Xid, Xbucket, Xlevel, Xsince, Plevel, Psince, TId, TBucket,
140     D, EncId0, EncId1, EncBucket0, EncBucket1),
141    (A, B):
142    Xid = (xid*A),
143    Xlevel = (xlevel*A),
144    Xbucket = (xbucket*A),
145    Xsince = (xsince*A),
146    X0 = (x0*B + x0tilde*A),
147    P = (b*B),
148    TId = (b*Xid),
149    TId = (tid*A),
150    TBucket = (b*Xbucket),
151    TBucket = (tbucket*A),
152    EncQ0 = (s*B + tid*EncId0 + tbucket*EncBucket0),
153    EncQ1 = (s*D + tid*EncId1 + tbucket*EncBucket1 + x0*P + xlevel*Plevel + xsince*Psince)
154}
155
156pub fn request(
157    lox_cred: &cred::Lox,
158    migration_cred: &cred::Migration,
159    lox_pub: &IssuerPubKey,
160    migration_pub: &IssuerPubKey,
161) -> Result<(Request, State), CredentialError> {
162    let A: &RistrettoPoint = &CMZ_A;
163    let B: &RistrettoPoint = &CMZ_B;
164    let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
165    let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
166
167    // Ensure that the credenials can be correctly shown; that is, the
168    // ids match and the Lox credential bucket matches the Migration
169    // credential from_bucket
170    if lox_cred.id != migration_cred.lox_id || lox_cred.bucket != migration_cred.from_bucket {
171        return Err(CredentialError::CredentialMismatch);
172    }
173
174    // This protocol only allows migrating from trust level 0 to trust
175    // level 1
176    if lox_cred.trust_level != Scalar::ZERO {
177        return Err(CredentialError::InvalidField(
178            String::from("trust_level"),
179            String::from("must be zero"),
180        ));
181    }
182
183    // Blind showing the Lox credential
184
185    // Reblind P and Q
186    let mut rng = rand::thread_rng();
187    let t_lox = Scalar::random(&mut rng);
188    let P_lox = t_lox * lox_cred.P;
189    let Q_lox = t_lox * lox_cred.Q;
190
191    // Form Pedersen commitments to the blinded attributes
192    let zbucket = Scalar::random(&mut rng);
193    let zsince = Scalar::random(&mut rng);
194    let CBucket = lox_cred.bucket * P_lox + &zbucket * Atable;
195    let CSince = lox_cred.level_since * P_lox + &zsince * Atable;
196
197    // Form a Pedersen commitment to the MAC Q
198    // We flip the sign of zQ from that of the Hyphae paper so that
199    // the ZKP has a "+" instead of a "-", as that's what the zkp
200    // macro supports.
201    let negzQ_lox = Scalar::random(&mut rng);
202    let CQ_lox = Q_lox - &negzQ_lox * Atable;
203
204    // Compute the "error factor"
205    let V_lox = zbucket * lox_pub.X[2] + zsince * lox_pub.X[4] + &negzQ_lox * Atable;
206
207    // Blind showing the Migration credential
208
209    // Reblind P and Q
210    let t_mig = Scalar::random(&mut rng);
211    let P_mig = t_mig * migration_cred.P;
212    let Q_mig = t_mig * migration_cred.Q;
213
214    // Form Pedersen commitments to the blinded attributes
215    let zfrombucket = Scalar::random(&mut rng);
216    let ztobucket = Scalar::random(&mut rng);
217    let CFromBucket = migration_cred.from_bucket * P_mig + &zfrombucket * Atable;
218    let CToBucket = migration_cred.to_bucket * P_mig + &ztobucket * Atable;
219
220    // Form a Pedersen commitment to the MAC Q
221    // We flip the sign of zQ from that of the Hyphae paper so that
222    // the ZKP has a "+" instead of a "-", as that's what the zkp
223    // macro supports.
224    let negzQ_mig = Scalar::random(&mut rng);
225    let CQ_mig = Q_mig - &negzQ_mig * Atable;
226
227    // Compute the "error factor"
228    let V_mig =
229        zfrombucket * migration_pub.X[2] + ztobucket * migration_pub.X[3] + &negzQ_mig * Atable;
230
231    // User blinding for the Lox certificate to be issued
232
233    // Pick an ElGamal keypair
234    let d = Scalar::random(&mut rng);
235    let D = &d * Btable;
236
237    // Pick a random client component of the id
238    let id_client = Scalar::random(&mut rng);
239
240    // Encrypt it (times the basepoint B) to the ElGamal public key D we
241    // just created
242    let eid_client = Scalar::random(&mut rng);
243    let EncIdClient = (&eid_client * Btable, &id_client * Btable + eid_client * D);
244
245    // Encrypt the bucket field (times B) to D as well
246    let ebucket = Scalar::random(&mut rng);
247    let EncBucket = (
248        &ebucket * Btable,
249        &migration_cred.to_bucket * Btable + ebucket * D,
250    );
251
252    // Construct the proof
253    let mut transcript = Transcript::new(b"migration request");
254    let piUser = requestproof::prove_compact(
255        &mut transcript,
256        requestproof::ProveAssignments {
257            A,
258            B,
259            P_lox: &P_lox,
260            CBucket: &CBucket,
261            CSince: &CSince,
262            V_lox: &V_lox,
263            Xbucket: &lox_pub.X[2],
264            Xsince: &lox_pub.X[4],
265            P_mig: &P_mig,
266            CFromBucket: &CFromBucket,
267            CToBucket: &CToBucket,
268            V_mig: &V_mig,
269            Xfrombucket: &migration_pub.X[2],
270            Xtobucket: &migration_pub.X[3],
271            D: &D,
272            EncIdClient0: &EncIdClient.0,
273            EncIdClient1: &EncIdClient.1,
274            EncBucket0: &EncBucket.0,
275            EncBucket1: &EncBucket.1,
276            bucket: &lox_cred.bucket,
277            since: &lox_cred.level_since,
278            zbucket: &zbucket,
279            zsince: &zsince,
280            negzQ_lox: &negzQ_lox,
281            tobucket: &migration_cred.to_bucket,
282            zfrombucket: &zfrombucket,
283            ztobucket: &ztobucket,
284            negzQ_mig: &negzQ_mig,
285            d: &d,
286            eid_client: &eid_client,
287            ebucket: &ebucket,
288            id_client: &id_client,
289        },
290    )
291    .0;
292
293    Ok((
294        Request {
295            P_lox,
296            id: lox_cred.id,
297            CBucket,
298            trust_level: lox_cred.trust_level,
299            CSince,
300            CQ_lox,
301            P_mig,
302            CFromBucket,
303            CToBucket,
304            CQ_mig,
305            D,
306            EncIdClient,
307            EncBucket,
308            piUser,
309        },
310        State {
311            d,
312            D,
313            EncIdClient,
314            EncBucket,
315            id_client,
316            to_bucket: migration_cred.to_bucket,
317        },
318    ))
319}
320
321#[cfg(feature = "bridgeauth")]
322impl BridgeAuth {
323    /// Receive a migration request
324    pub fn handle_migration(&mut self, req: Request) -> Result<Response, ProofError> {
325        let A: &RistrettoPoint = &CMZ_A;
326        let B: &RistrettoPoint = &CMZ_B;
327        let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
328        let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
329
330        if req.P_lox.is_identity() || req.P_mig.is_identity() {
331            return Err(ProofError::VerificationFailure);
332        }
333
334        // We only currently support migrating from trust level 0
335        if req.trust_level != Scalar::ZERO {
336            return Err(ProofError::VerificationFailure);
337        }
338
339        // Recompute the "error factors" using knowledge of our own
340        // (the issuer's) private key instead of knowledge of the
341        // hidden attributes
342        let Vprime_lox = (self.lox_priv.x[0]
343            + self.lox_priv.x[1] * req.id
344            + self.lox_priv.x[3] * req.trust_level)
345            * req.P_lox
346            + self.lox_priv.x[2] * req.CBucket
347            + self.lox_priv.x[4] * req.CSince
348            - req.CQ_lox;
349
350        let Vprime_mig = (self.migration_priv.x[0] + self.migration_priv.x[1] * req.id) * req.P_mig
351            + self.migration_priv.x[2] * req.CFromBucket
352            + self.migration_priv.x[3] * req.CToBucket
353            - req.CQ_mig;
354
355        // Verify the ZKP
356        let mut transcript = Transcript::new(b"migration request");
357        requestproof::verify_compact(
358            &req.piUser,
359            &mut transcript,
360            requestproof::VerifyAssignments {
361                A: &A.compress(),
362                B: &B.compress(),
363                P_lox: &req.P_lox.compress(),
364                CBucket: &req.CBucket.compress(),
365                CSince: &req.CSince.compress(),
366                V_lox: &Vprime_lox.compress(),
367                Xbucket: &self.lox_pub.X[2].compress(),
368                Xsince: &self.lox_pub.X[4].compress(),
369                P_mig: &req.P_mig.compress(),
370                CFromBucket: &req.CFromBucket.compress(),
371                CToBucket: &req.CToBucket.compress(),
372                V_mig: &Vprime_mig.compress(),
373                Xfrombucket: &self.migration_pub.X[2].compress(),
374                Xtobucket: &self.migration_pub.X[3].compress(),
375                D: &req.D.compress(),
376                EncIdClient0: &req.EncIdClient.0.compress(),
377                EncIdClient1: &req.EncIdClient.1.compress(),
378                EncBucket0: &req.EncBucket.0.compress(),
379                EncBucket1: &req.EncBucket.1.compress(),
380            },
381        )?;
382
383        // Ensure the id has not been seen before, and add it to the
384        // seen list.
385        if self.id_filter.filter(&req.id) == SeenType::Seen {
386            return Err(ProofError::VerificationFailure);
387        }
388
389        // Blind issuing of the new Lox credential
390
391        // Choose a random server id component to add to the client's
392        // (blinded) id component
393        let mut rng = rand::thread_rng();
394        let id_server = Scalar::random(&mut rng);
395        let EncId = (req.EncIdClient.0, req.EncIdClient.1 + &id_server * Btable);
396
397        // Create the trust_level attrubute (Scalar), which will be
398        // level 1
399        let trust_level: Scalar = Scalar::ONE;
400
401        // Create the level_since attribute (Scalar), which is today's
402        // Julian date
403        let level_since: Scalar = self.today().into();
404
405        // The invitations_remaining and blockages attributes are 0 for
406        // level 0 and level 1 Lox credentials, so we don't need to
407        // explicitly create them.
408
409        // Compute the MAC on the visible attributes
410        let b = Scalar::random(&mut rng);
411        let P = &b * Btable;
412        // invites_remaining = blockages = 0
413        let QHc = (self.lox_priv.x[0]
414            + self.lox_priv.x[3] * trust_level
415            + self.lox_priv.x[4] * level_since)
416            * P;
417
418        // El Gamal encrypt it to the public key req.D
419        let s = Scalar::random(&mut rng);
420        let EncQHc = (&s * Btable, QHc + s * req.D);
421
422        // Homomorphically compute the part of the MAC corresponding to
423        // the blinded attributes
424        let tid = self.lox_priv.x[1] * b;
425        let TId = &tid * Atable;
426        let EncQId = (tid * EncId.0, tid * EncId.1);
427        let tbucket = self.lox_priv.x[2] * b;
428        let TBucket = &tbucket * Atable;
429        let EncQBucket = (tbucket * req.EncBucket.0, tbucket * req.EncBucket.1);
430
431        let EncQ = (
432            EncQHc.0 + EncQId.0 + EncQBucket.0,
433            EncQHc.1 + EncQId.1 + EncQBucket.1,
434        );
435
436        let mut transcript = Transcript::new(b"migration issuing");
437        let piBlindIssue = blindissue::prove_compact(
438            &mut transcript,
439            blindissue::ProveAssignments {
440                A,
441                B,
442                P: &P,
443                EncQ0: &EncQ.0,
444                EncQ1: &EncQ.1,
445                X0: &self.lox_pub.X[0],
446                Xid: &self.lox_pub.X[1],
447                Xbucket: &self.lox_pub.X[2],
448                Xlevel: &self.lox_pub.X[3],
449                Xsince: &self.lox_pub.X[4],
450                Plevel: &(trust_level * P),
451                Psince: &(level_since * P),
452                TId: &TId,
453                TBucket: &TBucket,
454                D: &req.D,
455                EncId0: &EncId.0,
456                EncId1: &EncId.1,
457                EncBucket0: &req.EncBucket.0,
458                EncBucket1: &req.EncBucket.1,
459                x0: &self.lox_priv.x[0],
460                x0tilde: &self.lox_priv.x0tilde,
461                xid: &self.lox_priv.x[1],
462                xbucket: &self.lox_priv.x[2],
463                xlevel: &self.lox_priv.x[3],
464                xsince: &self.lox_priv.x[4],
465                s: &s,
466                b: &b,
467                tid: &tid,
468                tbucket: &tbucket,
469            },
470        )
471        .0;
472
473        Ok(Response {
474            level_since,
475            P,
476            EncQ,
477            id_server,
478            TId,
479            TBucket,
480            piBlindIssue,
481        })
482    }
483}
484
485/// Handle the response to the request, producing the new Lox credential
486/// if successful.
487pub fn handle_response(
488    state: State,
489    resp: Response,
490    lox_pub: &IssuerPubKey,
491) -> Result<cred::Lox, ProofError> {
492    let A: &RistrettoPoint = &CMZ_A;
493    let B: &RistrettoPoint = &CMZ_B;
494    let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
495
496    if resp.P.is_identity() {
497        return Err(ProofError::VerificationFailure);
498    }
499
500    // Add the server's contribution to the id to our own, both in plain
501    // and encrypted form
502    let id = state.id_client + resp.id_server;
503    let EncId = (
504        state.EncIdClient.0,
505        state.EncIdClient.1 + &resp.id_server * Btable,
506    );
507
508    // Verify the proof
509    let mut transcript = Transcript::new(b"migration issuing");
510    blindissue::verify_compact(
511        &resp.piBlindIssue,
512        &mut transcript,
513        blindissue::VerifyAssignments {
514            A: &A.compress(),
515            B: &B.compress(),
516            P: &resp.P.compress(),
517            EncQ0: &resp.EncQ.0.compress(),
518            EncQ1: &resp.EncQ.1.compress(),
519            X0: &lox_pub.X[0].compress(),
520            Xid: &lox_pub.X[1].compress(),
521            Xbucket: &lox_pub.X[2].compress(),
522            Xlevel: &lox_pub.X[3].compress(),
523            Xsince: &lox_pub.X[4].compress(),
524            // The new trust level is 1
525            Plevel: &(Scalar::ONE * resp.P).compress(),
526            Psince: &(resp.level_since * resp.P).compress(),
527            TId: &resp.TId.compress(),
528            TBucket: &resp.TBucket.compress(),
529            D: &state.D.compress(),
530            EncId0: &EncId.0.compress(),
531            EncId1: &EncId.1.compress(),
532            EncBucket0: &state.EncBucket.0.compress(),
533            EncBucket1: &state.EncBucket.1.compress(),
534        },
535    )?;
536
537    // Decrypt EncQ
538    let Q = resp.EncQ.1 - (state.d * resp.EncQ.0);
539
540    Ok(cred::Lox {
541        P: resp.P,
542        Q,
543        id,
544        bucket: state.to_bucket,
545        trust_level: Scalar::ONE,
546        level_since: resp.level_since,
547        invites_remaining: Scalar::ZERO,
548        blockages: Scalar::ZERO,
549    })
550}