Skip to main content

lox_library/proto/
open_invite.rs

1/*! A module for the protocol for the user to redeem an open invitation
2with the BA (bridge authority) to receive their initial Lox
3credential.
4
5The credential will have attributes:
6
7- id: jointly chosen by the user and BA
8- bucket: set by the BA
9- trust_level: 0
10- level_since: today
11- invites_remaining: 0
12- blockages: 0
13
14*/
15
16use curve25519_dalek::ristretto::RistrettoBasepointTable;
17use curve25519_dalek::ristretto::RistrettoPoint;
18use curve25519_dalek::scalar::Scalar;
19use curve25519_dalek::traits::IsIdentity;
20
21use lox_zkp::CompactProof;
22use lox_zkp::ProofError;
23use lox_zkp::Transcript;
24
25use serde::{Deserialize, Serialize};
26use serde_with::serde_as;
27
28#[cfg(feature = "bridgeauth")]
29use super::super::bridge_table;
30use super::super::bridge_table::BridgeLine;
31use super::super::cred;
32#[cfg(feature = "bridgeauth")]
33use super::super::dup_filter::SeenType;
34use super::super::IssuerPubKey;
35#[cfg(feature = "bridgeauth")]
36use super::super::CMZ_A_TABLE;
37use super::super::OPENINV_LENGTH;
38#[cfg(feature = "bridgeauth")]
39use super::super::{BridgeAuth, BridgeDb};
40use super::super::{CMZ_A, CMZ_B, CMZ_B_TABLE};
41
42/// The request message for this protocol
43#[serde_as]
44#[derive(Serialize, Deserialize)]
45pub struct Request {
46    #[serde_as(as = "[_; OPENINV_LENGTH]")]
47    invite: [u8; OPENINV_LENGTH],
48    D: RistrettoPoint,
49    EncIdClient: (RistrettoPoint, RistrettoPoint),
50    piUserBlinding: CompactProof,
51}
52
53/// The client state for this protocol
54#[derive(Debug, Serialize, Deserialize)]
55pub struct State {
56    d: Scalar,
57    D: RistrettoPoint,
58    EncIdClient: (RistrettoPoint, RistrettoPoint),
59    id_client: Scalar,
60}
61
62/// The response message for this protocol
63#[derive(Serialize, Deserialize)]
64pub struct Response {
65    P: RistrettoPoint,
66    EncQ: (RistrettoPoint, RistrettoPoint),
67    id_server: Scalar,
68    TId: RistrettoPoint,
69    bucket: Scalar,
70    level_since: Scalar,
71    piBlindIssue: CompactProof,
72    bridge_line: BridgeLine,
73}
74
75// The userblinding ZKP
76define_proof! {
77    userblinding,
78    "Open Invitation User Blinding",
79    (d, eid_client, id_client),
80    (D, EncIdClient0, EncIdClient1),
81    (B) :
82    D = (d*B),
83    EncIdClient0 = (eid_client*B),
84    EncIdClient1 = (id_client*B + eid_client*D)
85}
86
87// The issuing ZKP
88define_proof! {
89    blindissue,
90    "Open Invitation Blind Issuing",
91    (x0, x0tilde, xid, xbucket, xsince, s, b, tid),
92    (P, EncQ0, EncQ1, X0, Xid, Xbucket, Xsince, Pbucket, Psince, TId,
93     D, EncId0, EncId1),
94    (A, B) :
95    Xid = (xid*A),
96    Xbucket = (xbucket*A),
97    Xsince = (xsince*A),
98    X0 = (x0*B + x0tilde*A),
99    P = (b*B),
100    TId = (b*Xid),
101    TId = (tid*A),
102    EncQ0 = (s*B + tid*EncId0),
103    EncQ1 = (s*D + tid*EncId1 + x0*P + xbucket*Pbucket + xsince*Psince)
104}
105
106/// Submit an open invitation issued by the BridgeDb to receive your
107/// first Lox credential
108pub fn request(invite: &[u8; OPENINV_LENGTH]) -> (Request, State) {
109    let B: &RistrettoPoint = &CMZ_B;
110    let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
111
112    // Pick an ElGamal keypair
113    let mut rng = rand::thread_rng();
114    let d = Scalar::random(&mut rng);
115    let D = &d * Btable;
116
117    // Pick a random client component of the id
118    let id_client = Scalar::random(&mut rng);
119
120    // Encrypt it (times the basepoint B) to the ElGamal public key D we
121    // just created
122    let eid_client = Scalar::random(&mut rng);
123    let EncIdClient = (&eid_client * Btable, &id_client * Btable + eid_client * D);
124
125    // Construct the proof of correct user blinding
126    let mut transcript = Transcript::new(b"open invite user blinding");
127    let piUserBlinding = userblinding::prove_compact(
128        &mut transcript,
129        userblinding::ProveAssignments {
130            B,
131            D: &D,
132            EncIdClient0: &EncIdClient.0,
133            EncIdClient1: &EncIdClient.1,
134            d: &d,
135            eid_client: &eid_client,
136            id_client: &id_client,
137        },
138    )
139    .0;
140    (
141        Request {
142            invite: *invite,
143            D,
144            EncIdClient,
145            piUserBlinding,
146        },
147        State {
148            d,
149            D,
150            EncIdClient,
151            id_client,
152        },
153    )
154}
155
156#[cfg(feature = "bridgeauth")]
157impl BridgeAuth {
158    /// Receive an open invitation issued by the BridgeDb and if it is
159    /// valid and fresh, issue a Lox credential at trust level 0.
160    pub fn handle_open_invite(&mut self, req: Request) -> Result<Response, ProofError> {
161        // Check the signature on the open_invite, first with the old key, then with the new key.
162        // We manually match here because we're changing the Err type from SignatureError
163        // to ProofError
164        let mut old_token: Option<((Scalar, u32), usize)> = Default::default();
165        let invite_id: Scalar;
166        let bucket_id: u32;
167        // If there are old openinv keys, check them first
168        for (i, old_openinv_key) in self.old_keys.bridgedb_key.iter().enumerate() {
169            old_token = match BridgeDb::verify(req.invite, *old_openinv_key) {
170                Ok(res) => Some((res, i)),
171                Err(_) => None,
172            };
173        }
174
175        // Check if verifying with the old key succeeded, if it did, check if it has been seen
176        if old_token.is_some() {
177            // Only proceed if the invite_id is fresh
178            (invite_id, bucket_id) = old_token.unwrap().0;
179            if self
180                .old_filters
181                .openinv_filter
182                .get_mut(old_token.unwrap().1)
183                .unwrap()
184                .filter(&invite_id)
185                == SeenType::Seen
186            {
187                return Err(ProofError::VerificationFailure);
188            }
189        // If it didn't, try verifying with the new key
190        } else {
191            (invite_id, bucket_id) = match BridgeDb::verify(req.invite, self.bridgedb_pub) {
192                Ok(res) => res,
193                // Also verify that the request doesn't match with an old openinv_key
194                Err(_) => return Err(ProofError::VerificationFailure),
195            };
196            // Only proceed if the invite_id is fresh
197            if self.bridgedb_pub_filter.filter(&invite_id) == SeenType::Seen {
198                return Err(ProofError::VerificationFailure);
199            }
200        }
201
202        // And also check that the bucket id is valid
203        if !self.bridge_table.buckets.contains_key(&bucket_id) {
204            return Err(ProofError::VerificationFailure);
205        }
206
207        let A: &RistrettoPoint = &CMZ_A;
208        let B: &RistrettoPoint = &CMZ_B;
209        let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
210        let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
211
212        // Next check the proof in the request
213        let mut transcript = Transcript::new(b"open invite user blinding");
214        userblinding::verify_compact(
215            &req.piUserBlinding,
216            &mut transcript,
217            userblinding::VerifyAssignments {
218                B: &B.compress(),
219                EncIdClient0: &req.EncIdClient.0.compress(),
220                EncIdClient1: &req.EncIdClient.1.compress(),
221                D: &req.D.compress(),
222            },
223        )?;
224
225        // Choose a random server id component to add to the client's
226        // (blinded) id component
227        let mut rng = rand::thread_rng();
228        let id_server = Scalar::random(&mut rng);
229        let EncId = (req.EncIdClient.0, req.EncIdClient.1 + &id_server * Btable);
230
231        // Create the bucket attribute (Scalar), which is a combination
232        // of the bucket id (u32) and the bucket's decryption key ([u8; 16])
233        let bucket_key = self.bridge_table.keys.get(&bucket_id).unwrap();
234        let bucket: Scalar = bridge_table::to_scalar(bucket_id, bucket_key);
235        let bridge_lines = self.bridge_table.buckets.get(&bucket_id).unwrap();
236        let bridge_line = bridge_lines[0];
237
238        // Create the level_since attribute (Scalar), which is today's
239        // Julian date
240        let level_since: Scalar = self.today().into();
241
242        // Compute the MAC on the visible attributes
243        let b = Scalar::random(&mut rng);
244        let P = &b * Btable;
245        // trust_level = invites_remaining = blockages = 0
246        let QHc =
247            (self.lox_priv.x[0] + self.lox_priv.x[2] * bucket + self.lox_priv.x[4] * level_since)
248                * P;
249
250        // El Gamal encrypt it to the public key req.D
251        let s = Scalar::random(&mut rng);
252        let EncQHc = (&s * Btable, QHc + s * req.D);
253
254        // Homomorphically compute the part of the MAC corresponding to
255        // the blinded id attribute
256        let tid = self.lox_priv.x[1] * b;
257        let TId = &tid * Atable;
258        let EncQId = (tid * EncId.0, tid * EncId.1);
259
260        let EncQ = (EncQHc.0 + EncQId.0, EncQHc.1 + EncQId.1);
261
262        let mut transcript = Transcript::new(b"open invite issuing");
263        let piBlindIssue = blindissue::prove_compact(
264            &mut transcript,
265            blindissue::ProveAssignments {
266                A,
267                B,
268                P: &P,
269                EncQ0: &EncQ.0,
270                EncQ1: &EncQ.1,
271                X0: &self.lox_pub.X[0],
272                Xid: &self.lox_pub.X[1],
273                Xbucket: &self.lox_pub.X[2],
274                Xsince: &self.lox_pub.X[4],
275                Pbucket: &(bucket * P),
276                Psince: &(level_since * P),
277                TId: &TId,
278                D: &req.D,
279                EncId0: &EncId.0,
280                EncId1: &EncId.1,
281                x0: &self.lox_priv.x[0],
282                x0tilde: &self.lox_priv.x0tilde,
283                xid: &self.lox_priv.x[1],
284                xbucket: &self.lox_priv.x[2],
285                xsince: &self.lox_priv.x[4],
286                s: &s,
287                b: &b,
288                tid: &tid,
289            },
290        )
291        .0;
292
293        Ok(Response {
294            P,
295            EncQ,
296            id_server,
297            TId,
298            bucket,
299            level_since,
300            piBlindIssue,
301            bridge_line,
302        })
303    }
304}
305
306/// Handle the reponse to the request, producing the desired Lox
307/// credential if successful.
308pub fn handle_response(
309    state: State,
310    resp: Response,
311    lox_pub: &IssuerPubKey,
312) -> Result<(cred::Lox, BridgeLine), ProofError> {
313    let A: &RistrettoPoint = &CMZ_A;
314    let B: &RistrettoPoint = &CMZ_B;
315    let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
316
317    if resp.P.is_identity() {
318        return Err(ProofError::VerificationFailure);
319    }
320
321    // Add the server's contribution to the id to our own, both in plain
322    // and encrypted form
323    let id = state.id_client + resp.id_server;
324    let EncId = (
325        state.EncIdClient.0,
326        state.EncIdClient.1 + &resp.id_server * Btable,
327    );
328
329    // Verify the proof
330    let mut transcript = Transcript::new(b"open invite issuing");
331    blindissue::verify_compact(
332        &resp.piBlindIssue,
333        &mut transcript,
334        blindissue::VerifyAssignments {
335            A: &A.compress(),
336            B: &B.compress(),
337            P: &resp.P.compress(),
338            EncQ0: &resp.EncQ.0.compress(),
339            EncQ1: &resp.EncQ.1.compress(),
340            X0: &lox_pub.X[0].compress(),
341            Xid: &lox_pub.X[1].compress(),
342            Xbucket: &lox_pub.X[2].compress(),
343            Xsince: &lox_pub.X[4].compress(),
344            Pbucket: &(resp.bucket * resp.P).compress(),
345            Psince: &(resp.level_since * resp.P).compress(),
346            TId: &resp.TId.compress(),
347            D: &state.D.compress(),
348            EncId0: &EncId.0.compress(),
349            EncId1: &EncId.1.compress(),
350        },
351    )?;
352
353    // Decrypt EncQ
354    let Q = resp.EncQ.1 - (state.d * resp.EncQ.0);
355
356    Ok((
357        cred::Lox {
358            P: resp.P,
359            Q,
360            id,
361            bucket: resp.bucket,
362            trust_level: Scalar::ZERO,
363            level_since: resp.level_since,
364            invites_remaining: Scalar::ZERO,
365            blockages: Scalar::ZERO,
366        },
367        resp.bridge_line,
368    ))
369}