/*! A module for the protocol for the user to redeem an open invitation with the BA (bridge authority) to receive their initial Lox credential. The credential will have attributes: - id: jointly chosen by the user and BA - bucket: set by the BA - trust_level: 0 - level_since: today - invites_remaining: 0 - invites_issued: 0 */ use curve25519_dalek::ristretto::RistrettoBasepointTable; use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::scalar::Scalar; use zkp::CompactProof; use zkp::ProofError; use zkp::Transcript; use super::bridge_table; use super::dup_filter::SeenType; use super::{BridgeAuth, IssuerPubKey}; use super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE}; /// The request message for this protocol pub struct Request { invite: [u8; super::OPENINV_LENGTH], D: RistrettoPoint, EncIdClient: (RistrettoPoint, RistrettoPoint), piUserBlinding: CompactProof, } #[derive(Debug)] /// The client state for this protocol pub struct State { d: Scalar, D: RistrettoPoint, EncIdClient: (RistrettoPoint, RistrettoPoint), id_client: Scalar, } /// The response message for this protocol pub struct Response { P: RistrettoPoint, EncQ: (RistrettoPoint, RistrettoPoint), id_server: Scalar, TId: RistrettoPoint, bucket: Scalar, level_since: Scalar, P_noopmigration: RistrettoPoint, EncQ_noopmigration: (RistrettoPoint, RistrettoPoint), TId_noopmigration: RistrettoPoint, piBlindIssue: CompactProof, } // The userblinding ZKP define_proof! { userblinding, "Open Invitation User Blinding", (d, eid_client, id_client), (EncIdClient0, EncIdClient1, D), (B) : EncIdClient0 = (eid_client*B), EncIdClient1 = (id_client*B + eid_client*D), D = (d*B) } // The issuing ZKP define_proof! { blindissue, "Open Invitation Blind Issuing", (x0, x0tilde, xid, xbucket, xsince, s, b, tid, x0_nm, x0tilde_nm, xid_nm, xfrom_nm, xto_nm, s_nm, b_nm, tid_nm), (P, EncQ0, EncQ1, X0, Xid, Xbucket, Xsince, Pbucket, Psince, TId, P_nm, EncQ0_nm, EncQ1_nm, X0_nm, Xid_nm, Xfrom_nm, Xto_nm, TId_nm, D, EncId0, EncId1), (A, B) : Xid = (xid*A), Xbucket = (xbucket*A), Xsince = (xsince*A), X0 = (x0*B + x0tilde*A), P = (b*B), TId = (b*Xid), TId = (tid*A), EncQ0 = (s*B + tid*EncId0), EncQ1 = (s*D + tid*EncId1 + x0*P + xbucket*Pbucket + xsince*Psince), Xid_nm = (xid_nm*A), Xfrom_nm = (xfrom_nm*A), Xto_nm = (xto_nm*A), X0_nm = (x0_nm*B + x0tilde_nm*A), P_nm = (b_nm*B), TId_nm = (b_nm*Xid_nm), TId_nm = (tid_nm*A), EncQ0_nm = (s_nm*B + tid_nm*EncId0), EncQ1_nm = (s_nm*D + tid_nm*EncId1 + x0_nm*P_nm + xfrom_nm*Pbucket + xto_nm*Pbucket) } /// Submit an open invitation issued by the BridgeDb to receive your /// first Lox credential pub fn request(invite: &[u8; super::OPENINV_LENGTH]) -> (Request, State) { let B: &RistrettoPoint = &CMZ_B; let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE; // Pick an ElGamal keypair let mut rng = rand::thread_rng(); let d = Scalar::random(&mut rng); let D = &d * Btable; // Pick a random client component of the id let id_client = Scalar::random(&mut rng); // Encrypt it (times the basepoint B) to the ElGamal public key D we // just created let eid_client = Scalar::random(&mut rng); let EncIdClient = (&eid_client * Btable, &id_client * Btable + eid_client * D); // Construct the proof of correct user blinding let mut transcript = Transcript::new(b"open invite user blinding"); let piUserBlinding = userblinding::prove_compact( &mut transcript, userblinding::ProveAssignments { B: &B, EncIdClient0: &EncIdClient.0, EncIdClient1: &EncIdClient.1, D: &D, d: &d, eid_client: &eid_client, id_client: &id_client, }, ) .0; ( Request { invite: *invite, D, EncIdClient, piUserBlinding, }, State { d, D, EncIdClient, id_client, }, ) } impl BridgeAuth { /// Receive an open invitation issued by the BridgeDb and if it is /// valid and fresh, issue a Lox credential at trust level 0. pub fn handle_open_invite(&mut self, req: Request) -> Result { // Check the signature on the open_invite. We manually match // here because we're changing the Err type from SignatureError // to ProofError let (invite_id, bucket_id_u32) = match super::BridgeDb::verify(req.invite, self.bridgedb_pub) { Ok(res) => res, Err(_) => return Err(ProofError::VerificationFailure), }; let bucket_id: usize = bucket_id_u32 as usize; // Only proceed if the invite_id is fresh if self.openinv_filter.filter(&invite_id) == SeenType::Seen { return Err(ProofError::VerificationFailure); } // And also check that the bucket id is valid if bucket_id >= self.bridge_table.num_buckets() { return Err(ProofError::VerificationFailure); } let A: &RistrettoPoint = &CMZ_A; let B: &RistrettoPoint = &CMZ_B; let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE; let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE; // Next check the proof in the request let mut transcript = Transcript::new(b"open invite user blinding"); userblinding::verify_compact( &req.piUserBlinding, &mut transcript, userblinding::VerifyAssignments { B: &B.compress(), EncIdClient0: &req.EncIdClient.0.compress(), EncIdClient1: &req.EncIdClient.1.compress(), D: &req.D.compress(), }, )?; // Choose a random server id component to add to the client's // (blinded) id component let mut rng = rand::thread_rng(); let id_server = Scalar::random(&mut rng); let EncId = (req.EncIdClient.0, req.EncIdClient.1 + &id_server * Btable); // Create the bucket attribute (Scalar), which is a combination // of the bucket id (u32) and the bucket's decryption key ([u8; 16]) let bucket_key = self.bridge_table.keys[bucket_id]; let bucket: Scalar = bridge_table::to_scalar(bucket_id_u32, bucket_key); // Create the level_since attribute (Scalar), which is today's // Julian date let level_since: Scalar = self.today().into(); // Compute the MAC on the visible attributes let b = Scalar::random(&mut rng); let P = &b * Btable; // trust_level = invites_remaining = invites_issued = 0 let QHc = (self.lox_priv.x[0] + self.lox_priv.x[2] * bucket + self.lox_priv.x[4] * level_since) * P; // El Gamal encrypt it to the public key req.D let s = Scalar::random(&mut rng); let EncQHc = (&s * Btable, QHc + s * req.D); // Homomorphically compute the part of the MAC corresponding to // the blinded id attribute let tid = self.lox_priv.x[1] * b; let TId = &tid * Atable; let EncQId = (tid * EncId.0, tid * EncId.1); let EncQ = (EncQHc.0 + EncQId.0, EncQHc.1 + EncQId.1); // Now the no-op migration credential // Compute the MAC on the visible attributes let b_noopmigration = Scalar::random(&mut rng); let P_noopmigration = &b_noopmigration * Btable; let QHc_noopmigration = (self.migration_priv.x[0] + self.migration_priv.x[2] * bucket + self.migration_priv.x[3] * bucket) * P; // El Gamal encrypt it to the public key req.D let s_noopmigration = Scalar::random(&mut rng); let EncQHc_noopmigration = ( &s_noopmigration * Btable, QHc_noopmigration + s_noopmigration * req.D, ); // Homomorphically compute the part of the MAC corresponding to // the blinded id attribute let tid_noopmigration = self.migration_priv.x[1] * b_noopmigration; let TId_noopmigration = &tid_noopmigration * Atable; let EncQId_noopmigration = (tid_noopmigration * EncId.0, tid_noopmigration * EncId.1); let EncQ_noopmigration = ( EncQHc_noopmigration.0 + EncQId_noopmigration.0, EncQHc_noopmigration.1 + EncQId_noopmigration.1, ); let mut transcript = Transcript::new(b"open invite issuing"); let piBlindIssue = blindissue::prove_compact( &mut transcript, blindissue::ProveAssignments { A: &A, B: &B, P: &P, EncQ0: &EncQ.0, EncQ1: &EncQ.1, X0: &self.lox_pub.X[0], Xid: &self.lox_pub.X[1], Xbucket: &self.lox_pub.X[2], Xsince: &self.lox_pub.X[4], Pbucket: &(bucket * P), Psince: &(level_since * P), TId: &TId, P_nm: &P_noopmigration, EncQ0_nm: &EncQ_noopmigration.0, EncQ1_nm: &EncQ_noopmigration.1, X0_nm: &self.migration_pub.X[0], Xid_nm: &self.migration_pub.X[1], Xfrom_nm: &self.migration_pub.X[2], Xto_nm: &self.migration_pub.X[3], TId_nm: &TId_noopmigration, D: &req.D, EncId0: &EncId.0, EncId1: &EncId.1, x0: &self.lox_priv.x[0], x0tilde: &self.lox_priv.x0tilde, xid: &self.lox_priv.x[1], xbucket: &self.lox_priv.x[2], xsince: &self.lox_priv.x[4], s: &s, b: &b, tid: &tid, x0_nm: &self.migration_priv.x[0], x0tilde_nm: &self.migration_priv.x0tilde, xid_nm: &self.migration_priv.x[1], xfrom_nm: &self.migration_priv.x[2], xto_nm: &self.migration_priv.x[3], s_nm: &s_noopmigration, b_nm: &b_noopmigration, tid_nm: &tid_noopmigration, }, ) .0; Ok(Response { P, EncQ, id_server, TId, bucket, level_since, P_noopmigration, EncQ_noopmigration, TId_noopmigration, piBlindIssue, }) } }