Skip to main content

lox_library/proto/
update_invite.rs

1/*! A module for the protocol for a user to request the issuing of an updated
2 * invitation credential after a key rotation has occurred
3
4
5The user presents their current Invitation credential:
6- id: revealed
7- date: blinded
8- bucket: blinded
9- blockages: blinded
10
11and a new Invitation credential to be issued:
12- id: jointly chosen by the user and BA
13- date: blinded, but proved in ZK that it's the same as in the invitation
14  date above
15- bucket: blinded, but proved in ZK that it's the same as in the Invitation
16  credential above
17- blockages: blinded, but proved in ZK that it's the same as in the
18  Invitation credential above
19
20*/
21
22use curve25519_dalek::ristretto::RistrettoBasepointTable;
23use curve25519_dalek::ristretto::RistrettoPoint;
24use curve25519_dalek::scalar::Scalar;
25use curve25519_dalek::traits::IsIdentity;
26
27use lox_zkp::CompactProof;
28use lox_zkp::ProofError;
29use lox_zkp::Transcript;
30
31use serde::{Deserialize, Serialize};
32
33use super::super::cred;
34#[cfg(feature = "bridgeauth")]
35use super::super::dup_filter::SeenType;
36#[cfg(feature = "bridgeauth")]
37use super::super::BridgeAuth;
38use super::super::IssuerPubKey;
39use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
40
41use super::errors::CredentialError;
42
43#[derive(Serialize, Deserialize)]
44pub struct Request {
45    // Fields for showing the old Invitation credential
46    OldPubKey: IssuerPubKey,
47    P: RistrettoPoint,
48    inv_id: Scalar,
49    CDate: RistrettoPoint,
50    CBucket: RistrettoPoint,
51    CBlockages: RistrettoPoint,
52    CQ: RistrettoPoint,
53
54    // Fields for user blinding of the Invitation credential to be updated
55    D: RistrettoPoint,
56    EncInvIdClient: (RistrettoPoint, RistrettoPoint),
57    EncDate: (RistrettoPoint, RistrettoPoint),
58    EncBucket: (RistrettoPoint, RistrettoPoint),
59    EncBlockages: (RistrettoPoint, RistrettoPoint),
60
61    // The combined ZKP
62    piUser: CompactProof,
63}
64
65#[derive(Debug, Serialize, Deserialize)]
66pub struct State {
67    d: Scalar,
68    D: RistrettoPoint,
69    EncInvIdClient: (RistrettoPoint, RistrettoPoint),
70    EncDate: (RistrettoPoint, RistrettoPoint),
71    EncBucket: (RistrettoPoint, RistrettoPoint),
72    EncBlockages: (RistrettoPoint, RistrettoPoint),
73    inv_id_client: Scalar,
74    date: Scalar,
75    bucket: Scalar,
76    blockages: Scalar,
77}
78
79#[derive(Serialize, Deserialize)]
80pub struct Response {
81    // The fields for the updated Invitation credential;
82    P: RistrettoPoint,
83    EncQ: (RistrettoPoint, RistrettoPoint),
84    inv_id_server: Scalar,
85    TInvId: RistrettoPoint,
86    TDate: RistrettoPoint,
87    TBucket: RistrettoPoint,
88    TBlockages: RistrettoPoint,
89
90    // The ZKP
91    piBlindIssue: CompactProof,
92}
93
94define_proof! {
95    requestproof,
96    "Update Invite Request",
97    (date, bucket, blockages, zdate, zbucket, zblockages, negzQ,
98     d, einv_id_client, edate, ebucket, eblockages, inv_id_client),
99    (P, CDate, CBucket, CBlockages, V, Xdate, Xbucket, Xblockages,
100     D, EncInvIdClient0, EncInvIdClient1, EncDate0, EncDate1, EncBucket0, EncBucket1,
101     EncBlockages0, EncBlockages1),
102    (A, B):
103    // Blind showing of the Invitation credential
104    CDate = (date*P + zdate*A),
105    CBucket = (bucket*P + zbucket*A),
106    CBlockages = (blockages*P + zblockages*A),
107    // User blinding of the Invitation credential to be issued
108    D = (d*B),
109    EncInvIdClient0 = (einv_id_client*B),
110    EncInvIdClient1 = (inv_id_client*B + einv_id_client*D),
111    EncDate0 = (edate*B),
112    EncDate1 = (date*B + edate*D),
113    EncBucket0 = (ebucket*B),
114    EncBucket1 = (bucket*B + ebucket*D),
115    EncBlockages0 = (eblockages*B),
116    EncBlockages1 = (blockages*B + eblockages*D)
117}
118
119define_proof! {
120    blindissue,
121    "Issue Updated Invitation",
122    (x0, x0tilde, xinv_id, xdate, xbucket, xblockages,
123     s, b, tinv_id, tdate, tbucket, tblockages),
124    (P, EncQ0, EncQ1, X0, Xinv_id, Xdate, Xbucket, Xblockages,
125        TInvId, TDate, TBucket, TBlockages,
126     D, EncInvId0, EncInvId1, EncDate0, EncDate1, EncBucket0, EncBucket1, EncBlockages0, EncBlockages1),
127    (A, B):
128    Xinv_id = (xinv_id*A),
129    Xdate = (xdate*A),
130    Xbucket = (xbucket*A),
131    Xblockages = (xblockages*A),
132    X0 = (x0*B + x0tilde*A),
133    P = (b*B),
134    TInvId = (b*Xinv_id),
135    TInvId = (tinv_id*A),
136    TDate = (b*Xdate),
137    TDate = (tdate*A),
138    TBucket = (b*Xbucket),
139    TBucket = (tbucket*A),
140    TBlockages = (b*Xblockages),
141    TBlockages = (tblockages*A),
142    EncQ0 = (s*B + tinv_id*EncInvId0 + tdate*EncDate0 + tbucket*EncBucket0 + tblockages*EncBlockages0),
143    EncQ1 = (s*D + tinv_id*EncInvId1  + tdate*EncDate1 + tbucket*EncBucket1
144            + tblockages*EncBlockages1 + x0*P)
145}
146
147pub fn request(
148    inv_cred: &cred::Invitation,
149    invitation_pub: &IssuerPubKey,
150) -> Result<(Request, State), CredentialError> {
151    let A: &RistrettoPoint = &CMZ_A;
152    let B: &RistrettoPoint = &CMZ_B;
153    let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
154    let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
155
156    // Blind showing the Invitation credential
157
158    // Reblind P and Q
159    let mut rng = rand::thread_rng();
160    let t = Scalar::random(&mut rng);
161    let P = t * inv_cred.P;
162    let Q = t * inv_cred.Q;
163
164    // Form Pedersen commitments to the blinded attributes
165    let zdate = Scalar::random(&mut rng);
166    let zbucket = Scalar::random(&mut rng);
167    let zblockages = Scalar::random(&mut rng);
168    let CDate = inv_cred.date * P + &zdate * Atable;
169    let CBucket = inv_cred.bucket * P + &zbucket * Atable;
170    let CBlockages = inv_cred.blockages * P + &zblockages * Atable;
171
172    // Form a Pedersen commitment to the MAC Q
173    // We flip the sign of zQ from that of the Hyphae paper so that
174    // the ZKP has a "+" instead of a "-", as that's what the zkp
175    // macro supports.
176    let negzQ = Scalar::random(&mut rng);
177    let CQ = Q - &negzQ * Atable;
178
179    // Compute the "error factor"
180    let V = zdate * invitation_pub.X[2]
181        + zbucket * invitation_pub.X[3]
182        + zblockages * invitation_pub.X[4]
183        + &negzQ * Atable;
184
185    // User blinding for the Invitation Token to be issued
186
187    // Pick an ElGamal keypair
188    let d = Scalar::random(&mut rng);
189    let D = &d * Btable;
190
191    // Pick a random client component of the id
192    let inv_id_client = Scalar::random(&mut rng);
193
194    // Encrypt it (times the basepoint B) to the ElGamal public key D we
195    // just created
196    let einv_id_client = Scalar::random(&mut rng);
197    let EncInvIdClient = (
198        &einv_id_client * Btable,
199        &inv_id_client * Btable + einv_id_client * D,
200    );
201
202    // Encrypt the other blinded fields (times B) to D as well
203    let edate = Scalar::random(&mut rng);
204    let EncDate = (&edate * Btable, &inv_cred.date * Btable + edate * D);
205    let ebucket = Scalar::random(&mut rng);
206    let EncBucket = (&ebucket * Btable, &inv_cred.bucket * Btable + ebucket * D);
207    let eblockages = Scalar::random(&mut rng);
208    let EncBlockages = (
209        &eblockages * Btable,
210        &inv_cred.blockages * Btable + eblockages * D,
211    );
212
213    // Construct the proof
214    let mut transcript = Transcript::new(b"update invite request");
215    let piUser = requestproof::prove_compact(
216        &mut transcript,
217        requestproof::ProveAssignments {
218            A,
219            B,
220            P: &P,
221            CDate: &CDate,
222            CBucket: &CBucket,
223            CBlockages: &CBlockages,
224            V: &V,
225            Xdate: &invitation_pub.X[2],
226            Xbucket: &invitation_pub.X[3],
227            Xblockages: &invitation_pub.X[4],
228            D: &D,
229            EncInvIdClient0: &EncInvIdClient.0,
230            EncInvIdClient1: &EncInvIdClient.1,
231            EncDate0: &EncDate.0,
232            EncDate1: &EncDate.1,
233            EncBucket0: &EncBucket.0,
234            EncBucket1: &EncBucket.1,
235            EncBlockages0: &EncBlockages.0,
236            EncBlockages1: &EncBlockages.1,
237            date: &inv_cred.date,
238            bucket: &inv_cred.bucket,
239            blockages: &inv_cred.blockages,
240            zdate: &zdate,
241            zbucket: &zbucket,
242            zblockages: &zblockages,
243            negzQ: &negzQ,
244            d: &d,
245            einv_id_client: &einv_id_client,
246            edate: &edate,
247            ebucket: &ebucket,
248            eblockages: &eblockages,
249            inv_id_client: &inv_id_client,
250        },
251    )
252    .0;
253
254    Ok((
255        Request {
256            OldPubKey: invitation_pub.clone(),
257            P,
258            inv_id: inv_cred.inv_id,
259            CDate,
260            CBucket,
261            CBlockages,
262            CQ,
263            D,
264            EncInvIdClient,
265            EncDate,
266            EncBucket,
267            EncBlockages,
268            piUser,
269        },
270        State {
271            d,
272            D,
273            EncInvIdClient,
274            EncDate,
275            EncBucket,
276            EncBlockages,
277            inv_id_client,
278            date: inv_cred.date,
279            bucket: inv_cred.bucket,
280            blockages: inv_cred.blockages,
281        },
282    ))
283}
284
285#[cfg(feature = "bridgeauth")]
286impl BridgeAuth {
287    /// Receive a redeem invite request
288    pub fn handle_update_invite(&mut self, req: Request) -> Result<Response, ProofError> {
289        let A: &RistrettoPoint = &CMZ_A;
290        let B: &RistrettoPoint = &CMZ_B;
291        let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
292        let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
293
294        if req.P.is_identity() {
295            return Err(ProofError::VerificationFailure);
296        }
297
298        // Both of these must be true and should be true after rotate_lox_keys is called
299        if self.old_keys.invitation_keys.is_empty() || self.old_filters.invitation_filter.is_empty()
300        {
301            return Err(ProofError::VerificationFailure);
302        }
303
304        // calling this function will automatically use the most recent old private key for
305        // verification and the new private key for issuing.
306
307        // Recompute the "error factors" using knowledge of our own
308        // (the issuer's) outdated private key instead of knowledge of the
309        // hidden attributes
310        let old_keys = match self
311            .old_keys
312            .invitation_keys
313            .iter()
314            .find(|x| x.pub_key == req.OldPubKey)
315        {
316            Some(old_keys) => old_keys,
317            None => return Err(ProofError::VerificationFailure),
318        };
319        let index = self
320            .old_keys
321            .invitation_keys
322            .iter()
323            .position(|x| x.pub_key == old_keys.pub_key)
324            .unwrap();
325        let old_priv_key = old_keys.priv_key.clone();
326        let old_pub_key = old_keys.pub_key.clone();
327
328        // Recompute the "error factor" using knowledge of our own
329        // (the issuer's) private key instead of knowledge of the
330        // hidden attributes
331        let Vprime = (old_priv_key.x[0] + old_priv_key.x[1] * req.inv_id) * req.P
332            + old_priv_key.x[2] * req.CDate
333            + old_priv_key.x[3] * req.CBucket
334            + old_priv_key.x[4] * req.CBlockages
335            - req.CQ;
336
337        // Verify the ZKP
338        let mut transcript = Transcript::new(b"update invite request");
339        requestproof::verify_compact(
340            &req.piUser,
341            &mut transcript,
342            requestproof::VerifyAssignments {
343                A: &A.compress(),
344                B: &B.compress(),
345                P: &req.P.compress(),
346                CDate: &req.CDate.compress(),
347                CBucket: &req.CBucket.compress(),
348                CBlockages: &req.CBlockages.compress(),
349                V: &Vprime.compress(),
350                Xdate: &old_pub_key.X[2].compress(),
351                Xbucket: &old_pub_key.X[3].compress(),
352                Xblockages: &old_pub_key.X[4].compress(),
353                D: &req.D.compress(),
354                EncInvIdClient0: &req.EncInvIdClient.0.compress(),
355                EncInvIdClient1: &req.EncInvIdClient.1.compress(),
356                EncDate0: &req.EncDate.0.compress(),
357                EncDate1: &req.EncDate.1.compress(),
358                EncBucket0: &req.EncBucket.0.compress(),
359                EncBucket1: &req.EncBucket.1.compress(),
360                EncBlockages0: &req.EncBlockages.0.compress(),
361                EncBlockages1: &req.EncBlockages.1.compress(),
362            },
363        )?;
364
365        // Ensure the id has not been seen before, and add it to the
366        // invite id seen list.
367        if self
368            .old_filters
369            .invitation_filter
370            .get_mut(index)
371            .unwrap()
372            .filter(&req.inv_id)
373            == SeenType::Seen
374        {
375            return Err(ProofError::VerificationFailure);
376        }
377
378        // Blind issuing of the new Invitation credential
379
380        // Choose a random server id component to add to the client's
381        // (blinded) id component
382        let mut rng = rand::thread_rng();
383        let inv_id_server = Scalar::random(&mut rng);
384        let EncInvId = (
385            req.EncInvIdClient.0,
386            req.EncInvIdClient.1 + &inv_id_server * Btable,
387        );
388
389        // Compute the MAC on the visible attributes
390        let b = Scalar::random(&mut rng);
391        let P = &b * Btable;
392        let QHc = self.invitation_priv.x[0] * P;
393
394        // El Gamal encrypt it to the public key req.D
395        let s = Scalar::random(&mut rng);
396        let EncQHc = (&s * Btable, QHc + s * req.D);
397
398        // Homomorphically compute the part of the MAC corresponding to
399        // the blinded attributes
400        let tinv_id = self.invitation_priv.x[1] * b;
401        let TInvId = &tinv_id * Atable;
402        let EncQId = (tinv_id * EncInvId.0, tinv_id * EncInvId.1);
403        let tdate = self.invitation_priv.x[2] * b;
404        let TDate = &tdate * Atable;
405        let EncQDate = (tdate * req.EncDate.0, tdate * req.EncDate.1);
406        let tbucket = self.invitation_priv.x[3] * b;
407        let TBucket = &tbucket * Atable;
408        let EncQBucket = (tbucket * req.EncBucket.0, tbucket * req.EncBucket.1);
409        let tblockages = self.invitation_priv.x[4] * b;
410        let TBlockages = &tblockages * Atable;
411        let EncQBlockages = (
412            tblockages * req.EncBlockages.0,
413            tblockages * req.EncBlockages.1,
414        );
415
416        let EncQ = (
417            EncQHc.0 + EncQId.0 + EncQDate.0 + EncQBucket.0 + EncQBlockages.0,
418            EncQHc.1 + EncQId.1 + EncQDate.1 + EncQBucket.1 + EncQBlockages.1,
419        );
420
421        let mut transcript = Transcript::new(b"issue updated invitation");
422        let piBlindIssue = blindissue::prove_compact(
423            &mut transcript,
424            blindissue::ProveAssignments {
425                A,
426                B,
427                P: &P,
428                EncQ0: &EncQ.0,
429                EncQ1: &EncQ.1,
430                X0: &self.invitation_pub.X[0],
431                Xinv_id: &self.invitation_pub.X[1],
432                Xdate: &self.invitation_pub.X[2],
433                Xbucket: &self.invitation_pub.X[3],
434                Xblockages: &self.invitation_pub.X[4],
435                TInvId: &TInvId,
436                TDate: &TDate,
437                TBucket: &TBucket,
438                TBlockages: &TBlockages,
439                D: &req.D,
440                EncInvId0: &EncInvId.0,
441                EncInvId1: &EncInvId.1,
442                EncDate0: &req.EncDate.0,
443                EncDate1: &req.EncDate.1,
444                EncBucket0: &req.EncBucket.0,
445                EncBucket1: &req.EncBucket.1,
446                EncBlockages0: &req.EncBlockages.0,
447                EncBlockages1: &req.EncBlockages.1,
448                x0: &self.invitation_priv.x[0],
449                x0tilde: &self.invitation_priv.x0tilde,
450                xinv_id: &self.invitation_priv.x[1],
451                xdate: &self.invitation_priv.x[2],
452                xbucket: &self.invitation_priv.x[3],
453                xblockages: &self.invitation_priv.x[4],
454                s: &s,
455                b: &b,
456                tinv_id: &tinv_id,
457                tdate: &tdate,
458                tbucket: &tbucket,
459                tblockages: &tblockages,
460            },
461        )
462        .0;
463
464        Ok(Response {
465            P,
466            EncQ,
467            inv_id_server,
468            TInvId,
469            TDate,
470            TBucket,
471            TBlockages,
472            piBlindIssue,
473        })
474    }
475}
476
477/// Handle the response to the request, producing the new Lox credential
478/// if successful.
479pub fn handle_response(
480    state: State,
481    resp: Response,
482    invitation_pub: &IssuerPubKey,
483) -> Result<cred::Invitation, ProofError> {
484    let A: &RistrettoPoint = &CMZ_A;
485    let B: &RistrettoPoint = &CMZ_B;
486    let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
487
488    if resp.P.is_identity() {
489        return Err(ProofError::VerificationFailure);
490    }
491    // Add the server's contribution to the id to our own, both in plain
492    // and encrypted form
493    let inv_id = state.inv_id_client + resp.inv_id_server;
494    let EncInvId = (
495        state.EncInvIdClient.0,
496        state.EncInvIdClient.1 + &resp.inv_id_server * Btable,
497    );
498
499    // Verify the proof
500    let mut transcript = Transcript::new(b"issue updated invitation");
501    blindissue::verify_compact(
502        &resp.piBlindIssue,
503        &mut transcript,
504        blindissue::VerifyAssignments {
505            A: &A.compress(),
506            B: &B.compress(),
507            P: &resp.P.compress(),
508            EncQ0: &resp.EncQ.0.compress(),
509            EncQ1: &resp.EncQ.1.compress(),
510            X0: &invitation_pub.X[0].compress(),
511            Xinv_id: &invitation_pub.X[1].compress(),
512            Xdate: &invitation_pub.X[2].compress(),
513            Xbucket: &invitation_pub.X[3].compress(),
514            Xblockages: &invitation_pub.X[4].compress(),
515            TInvId: &resp.TInvId.compress(),
516            TDate: &resp.TDate.compress(),
517            TBucket: &resp.TBucket.compress(),
518            TBlockages: &resp.TBlockages.compress(),
519            D: &state.D.compress(),
520            EncInvId0: &EncInvId.0.compress(),
521            EncInvId1: &EncInvId.1.compress(),
522            EncDate0: &state.EncDate.0.compress(),
523            EncDate1: &state.EncDate.1.compress(),
524            EncBucket0: &state.EncBucket.0.compress(),
525            EncBucket1: &state.EncBucket.1.compress(),
526            EncBlockages0: &state.EncBlockages.0.compress(),
527            EncBlockages1: &state.EncBlockages.1.compress(),
528        },
529    )?;
530
531    // Decrypt EncQ
532    let Q = resp.EncQ.1 - (state.d * resp.EncQ.0);
533
534    Ok(cred::Invitation {
535        P: resp.P,
536        Q,
537        inv_id,
538        date: state.date,
539        bucket: state.bucket,
540        blockages: state.blockages,
541    })
542}