From e9267f5b373e3e3dd5f93e6263b93b826851b6a9 Mon Sep 17 00:00:00 2001 From: Ian Goldberg Date: Wed, 28 Apr 2021 18:31:47 -0400 Subject: [PATCH] The response message of the open invitation protocol --- crates/lox-library/src/bridge_table.rs | 26 +++- crates/lox-library/src/lib.rs | 13 +- crates/lox-library/src/open_invite.rs | 200 ++++++++++++++++++++++++- 3 files changed, 232 insertions(+), 7 deletions(-) diff --git a/crates/lox-library/src/bridge_table.rs b/crates/lox-library/src/bridge_table.rs index dfdc84f..8429f52 100644 --- a/crates/lox-library/src/bridge_table.rs +++ b/crates/lox-library/src/bridge_table.rs @@ -10,6 +10,7 @@ use aes_gcm::aead; use aes_gcm::aead::{generic_array::GenericArray, Aead, NewAead}; use aes_gcm::Aes128Gcm; +use curve25519_dalek::scalar::Scalar; use rand::RngCore; use std::convert::TryInto; @@ -128,15 +129,20 @@ impl BridgeLine { /// decryption key for one bucket. #[derive(Debug, Default)] pub struct BridgeTable { - keys: Vec<[u8; 16]>, - buckets: Vec<[BridgeLine; MAX_BRIDGES_PER_BUCKET]>, - encbuckets: Vec<[u8; ENC_BUCKET_BYTES]>, + pub keys: Vec<[u8; 16]>, + pub buckets: Vec<[BridgeLine; MAX_BRIDGES_PER_BUCKET]>, + pub encbuckets: Vec<[u8; ENC_BUCKET_BYTES]>, } // Invariant: the lengths of the keys and buckets vectors are the same. // The encbuckets vector only gets updated when encrypt_table is called. impl BridgeTable { + /// Get the number of buckets in the bridge table + pub fn num_buckets(&self) -> usize { + self.buckets.len() + } + /// Append a new bucket to the bridge table pub fn new_bucket(&mut self, bucket: [BridgeLine; MAX_BRIDGES_PER_BUCKET]) { // Pick a random key to encrypt this bucket @@ -190,7 +196,8 @@ impl BridgeTable { } } -// Unit tests that require access to private fields +// Unit tests that require access to the testing-only function +// BridgeLine::random() #[cfg(test)] mod tests { use super::*; @@ -234,3 +241,14 @@ mod tests { Ok(()) } } + +/// Convert an id and key to a Scalar attribute +pub fn to_scalar(id: u32, key: [u8; 16]) -> Scalar { + let mut b: [u8; 32] = [0; 32]; + // b is a little-endian representation of the Scalar; put the key in + // the low 16 bytes, and the id in the next 4 bytes. + b[0..16].copy_from_slice(&key); + b[16..20].copy_from_slice(&id.to_le_bytes()); + // This cannot fail, since we're only using the low 20 bytes of b + Scalar::from_canonical_bytes(b).unwrap() +} diff --git a/crates/lox-library/src/lib.rs b/crates/lox-library/src/lib.rs index b6fd8c5..1e1b1e6 100644 --- a/crates/lox-library/src/lib.rs +++ b/crates/lox-library/src/lib.rs @@ -182,6 +182,9 @@ pub struct BridgeAuth { /// The public key of the BridgeDb issuing open invitations pub bridgedb_pub: PublicKey, + /// The bridge table + bridge_table: bridge_table::BridgeTable, + /// Duplicate filter for open invitations openinv_filter: dup_filter::DupFilter, /// Duplicate filter for credential ids @@ -203,6 +206,7 @@ impl BridgeAuth { migration_priv, migration_pub, bridgedb_pub, + bridge_table: Default::default(), openinv_filter: Default::default(), id_filter: Default::default(), time_offset: time::Duration::zero(), @@ -221,9 +225,14 @@ impl BridgeAuth { } /// Get today's (real or simulated) date - fn today(&self) -> i64 { - (time::OffsetDateTime::now_utc().date() + self.time_offset).julian_day() + fn today(&self) -> u64 { + // We will not encounter negative Julian dates (~6700 years ago) + (time::OffsetDateTime::now_utc().date() + self.time_offset) + .julian_day() + .try_into() + .unwrap() } } +// The protocol modules pub mod open_invite; diff --git a/crates/lox-library/src/open_invite.rs b/crates/lox-library/src/open_invite.rs index 01a81d7..128f2b3 100644 --- a/crates/lox-library/src/open_invite.rs +++ b/crates/lox-library/src/open_invite.rs @@ -14,12 +14,13 @@ credential. The credential will have attributes: use curve25519_dalek::ristretto::RistrettoBasepointTable; use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::scalar::Scalar; -use curve25519_dalek::traits::IsIdentity; 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}; @@ -51,6 +52,7 @@ pub struct Response { P_noopmigration: RistrettoPoint, EncQ_noopmigration: (RistrettoPoint, RistrettoPoint), TId_noopmigration: RistrettoPoint, + piBlindIssue: CompactProof, } // The userblinding ZKP @@ -65,6 +67,36 @@ define_proof! { 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) { @@ -114,3 +146,169 @@ pub fn request(invite: &[u8; super::OPENINV_LENGTH]) -> (Request, State) { }, ) } + +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, + }) + } +}