diff --git a/crates/lox-library/src/proto/level_up.rs b/crates/lox-library/src/proto/level_up.rs index f97da18..faf4cdc 100644 --- a/crates/lox-library/src/proto/level_up.rs +++ b/crates/lox-library/src/proto/level_up.rs @@ -141,7 +141,6 @@ pub struct Response { level_since: Scalar, TId: RistrettoPoint, TBucket: RistrettoPoint, - TInvRemain: RistrettoPoint, TInvIssued: RistrettoPoint, // The fields for the implicit noop migration ("nm") credential @@ -208,6 +207,50 @@ define_proof! { // + ... + 256*CG8) as its value of CG0. } +define_proof! { + blindissue, + "Level Upgrade Issuing", + (x0, x0tilde, xid, xbucket, xlevel, xsince, xinvremain, xinvissued, + s, b, tid, tbucket, tinvissued, + x0_nm, x0tilde_nm, xid_nm, xfrom_nm, xto_nm, s_nm, b_nm, tid_nm, tbucket_nm), + (P, EncQ0, EncQ1, X0, Xid, Xbucket, Xlevel, Xsince, Xinvremain, + Xinvissued, Plevel, Psince, Pinvremain, TId, TBucket, TInvIssued, + P_nm, EncQ0_nm, EncQ1_nm, X0_nm, Xid_nm, Xfrom_nm, Xto_nm, + TId_nm, TBucket_nm, + D, EncId0, EncId1, EncBucket0, EncBucket1, EncInvIssued0, EncInvIssued1), + (A, B): + Xid = (xid*A), + Xid = (xid*A), + Xlevel = (xlevel*A), + Xbucket = (xbucket*A), + Xsince = (xsince*A), + Xinvremain = (xinvremain*A), + Xinvissued = (xinvissued*A), + X0 = (x0*B + x0tilde*A), + P = (b*B), + TId = (b*Xid), + TId = (tid*A), + TBucket = (b*Xbucket), + TBucket = (tbucket*A), + TInvIssued = (b*Xinvissued), + TInvIssued = (tinvissued*A), + EncQ0 = (s*B + tid*EncId0 + tbucket*EncBucket0 + tinvissued*EncInvIssued0), + EncQ1 = (s*D + tid*EncId1 + tbucket*EncBucket1 + + tinvissued*EncInvIssued1 + x0*P + xlevel*Plevel + xsince*Psince + + xinvremain*Pinvremain), + 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), + TBucket_nm = (b_nm*Xfrom_nm + b_nm*Xto_nm), + TBucket_nm = (tbucket_nm*A), + EncQ0_nm = (s_nm*B + tid_nm*EncId0 + tbucket_nm*EncBucket0), + EncQ1_nm = (s_nm*D + tid_nm*EncId1 + tbucket_nm*EncBucket1 + x0_nm*P_nm) +} + pub fn request( lox_cred: &cred::Lox, reach_cred: &cred::BucketReachability, @@ -231,7 +274,7 @@ pub fn request( Some(v) => v, None => return Err(ProofError::VerificationFailure), }; - if trust_level < 1 { + if trust_level < 1 || (trust_level as usize) > MAX_LEVEL { return Err(ProofError::VerificationFailure); } // The trust level has to be no higher than the highest level @@ -260,6 +303,12 @@ pub fn request( if reach_date != today { return Err(ProofError::VerificationFailure); } + // The new trust level + let new_level = if (trust_level as usize) < MAX_LEVEL { + trust_level + 1 + } else { + trust_level + }; // Blind showing the Lox credential @@ -331,7 +380,7 @@ pub fn request( // Encrypt the other blinded fields (times B) to D as well let ebucket = Scalar::random(&mut rng); let EncBucket = (&ebucket * Btable, &lox_cred.bucket * Btable + ebucket * D); - let newinvites: Scalar = LEVEL_INVITATIONS[trust_level as usize].into(); + let newinvites: Scalar = LEVEL_INVITATIONS[new_level as usize].into(); let einvissued = Scalar::random(&mut rng); let EncInvIssued = ( &einvissued * Btable, @@ -561,9 +610,366 @@ pub fn request( EncInvIssued, id_client, bucket: lox_cred.bucket, - level: lox_cred.trust_level + Scalar::one(), + level: new_level.into(), invremain: newinvites, invissued: lox_cred.invites_issued, }, )) } + +impl BridgeAuth { + /// Receive a level up request + pub fn handle_level_up(&mut self, req: Request) -> Result { + let A: &RistrettoPoint = &CMZ_A; + let B: &RistrettoPoint = &CMZ_B; + let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE; + let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE; + + if req.P.is_identity() { + return Err(ProofError::VerificationFailure); + } + + let today: Scalar = self.today().into(); + + // Get the level and ensure it's at most MAX_LEVEL + let level: usize = match scalar_u32(&req.level) { + Some(l) if l as usize <= MAX_LEVEL => l as usize, + _ => return Err(ProofError::VerificationFailure), + }; + + // Recompute the "error factors" using knowledge of our own + // (the issuer's) private key instead of knowledge of the + // hidden attributes + let Vprime = + (self.lox_priv.x[0] + self.lox_priv.x[1] * req.id + self.lox_priv.x[3] * req.level) + * req.P + + self.lox_priv.x[2] * req.CBucket + + self.lox_priv.x[4] * req.CSince + + self.lox_priv.x[5] * req.CInvRemain + + self.lox_priv.x[6] * req.CInvIssued + - req.CQ; + + let Vprime_reach = (self.reachability_priv.x[0] + self.reachability_priv.x[1] * today) + * req.P_reach + + self.reachability_priv.x[2] * req.CBucket_reach + - req.CQ_reach; + + // Recompute CG0 using Horner's method + let unt: Scalar = LEVEL_INTERVAL[level].into(); + let CG0prime = (today - unt) * req.P + - req.CSince + - pt_dbl( + &(pt_dbl( + &(pt_dbl( + &(pt_dbl( + &(pt_dbl( + &(pt_dbl(&(pt_dbl(&(pt_dbl(&req.CG8) + req.CG7)) + req.CG6)) + + req.CG5), + ) + req.CG4), + ) + req.CG3), + ) + req.CG2), + ) + req.CG1), + ); + + // Verify the ZKP + let mut transcript = Transcript::new(b"level upgrade request"); + requestproof::verify_compact( + &req.piUser, + &mut transcript, + requestproof::VerifyAssignments { + A: &A.compress(), + B: &B.compress(), + P: &req.P.compress(), + CBucket: &req.CBucket.compress(), + CSince: &req.CSince.compress(), + CInvRemain: &req.CInvRemain.compress(), + CInvIssued: &req.CInvIssued.compress(), + V: &Vprime.compress(), + Xbucket: &self.lox_pub.X[2].compress(), + Xsince: &self.lox_pub.X[4].compress(), + Xinvremain: &self.lox_pub.X[5].compress(), + Xinvissued: &self.lox_pub.X[6].compress(), + P_reach: &req.P_reach.compress(), + CBucket_reach: &req.CBucket_reach.compress(), + V_reach: &Vprime_reach.compress(), + Xbucket_reach: &self.reachability_pub.X[2].compress(), + D: &req.D.compress(), + EncIdClient0: &req.EncIdClient.0.compress(), + EncIdClient1: &req.EncIdClient.1.compress(), + EncBucket0: &req.EncBucket.0.compress(), + EncBucket1: &req.EncBucket.1.compress(), + EncInvIssued0: &req.EncInvIssued.0.compress(), + EncInvIssued1: &req.EncInvIssued.1.compress(), + CG0: &CG0prime.compress(), + CG1: &req.CG1.compress(), + CG2: &req.CG2.compress(), + CG3: &req.CG3.compress(), + CG4: &req.CG4.compress(), + CG5: &req.CG5.compress(), + CG6: &req.CG6.compress(), + CG7: &req.CG7.compress(), + CG8: &req.CG8.compress(), + CG0sq: &req.CG0sq.compress(), + CG1sq: &req.CG1sq.compress(), + CG2sq: &req.CG2sq.compress(), + CG3sq: &req.CG3sq.compress(), + CG4sq: &req.CG4sq.compress(), + CG5sq: &req.CG5sq.compress(), + CG6sq: &req.CG6sq.compress(), + CG7sq: &req.CG7sq.compress(), + CG8sq: &req.CG8sq.compress(), + }, + )?; + + // Ensure the id has not been seen before, and add it to the + // seen list. + if self.id_filter.filter(&req.id) == SeenType::Seen { + return Err(ProofError::VerificationFailure); + } + + // Blind issuing of the new Lox credential + + // 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 trust_level attrubute (Scalar), which will be + // one more than the current level, unless the current level is + // MAX_LEVEL, in which case it stays the same + let new_level = if level < MAX_LEVEL { level + 1 } else { level }; + let trust_level: Scalar = (new_level as u64).into(); + + // Create the level_since attribute (Scalar), which is today's + // Julian date + let level_since: Scalar = self.today().into(); + + // Create the invitations_remaining attribute (Scalar), which is + // the number of invitations at the new level + let invitations_remaining: Scalar = LEVEL_INVITATIONS[new_level].into(); + + // Compute the MAC on the visible attributes + let b = Scalar::random(&mut rng); + let P = &b * Btable; + let QHc = (self.lox_priv.x[0] + + self.lox_priv.x[3] * trust_level + + self.lox_priv.x[4] * level_since + + self.lox_priv.x[5] * invitations_remaining) + * 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 attributes + let tid = self.lox_priv.x[1] * b; + let TId = &tid * Atable; + let EncQId = (tid * EncId.0, tid * EncId.1); + let tbucket = self.lox_priv.x[2] * b; + let TBucket = &tbucket * Atable; + let EncQBucket = (tbucket * req.EncBucket.0, tbucket * req.EncBucket.1); + let tinvissued = self.lox_priv.x[6] * b; + let TInvIssued = &tinvissued * Atable; + let EncQInvIssued = ( + tinvissued * req.EncInvIssued.0, + tinvissued * req.EncInvIssued.1, + ); + + let EncQ = ( + EncQHc.0 + EncQId.0 + EncQBucket.0 + EncQInvIssued.0, + EncQHc.1 + EncQId.1 + EncQBucket.1 + EncQInvIssued.1, + ); + + // Now the no-op migration credential + // Compute the MAC on the visible attributes (none here) + let b_nm = Scalar::random(&mut rng); + let P_nm = &b_nm * Btable; + let QHc_nm = (self.migration_priv.x[0]) * P_nm; + + // El Gamal encrypt it to the public key req.D + let s_nm = Scalar::random(&mut rng); + let EncQHc_nm = (&s_nm * Btable, QHc_nm + s_nm * req.D); + + // Homomorphically compute the part of the MAC corresponding to + // the blinded attributes + let tid_nm = self.migration_priv.x[1] * b_nm; + let TId_nm = &tid_nm * Atable; + let EncQId_nm = (tid_nm * EncId.0, tid_nm * EncId.1); + let tbucket_nm = (self.migration_priv.x[2] + self.migration_priv.x[3]) * b_nm; + let TBucket_nm = &tbucket_nm * Atable; + let EncQBucket_nm = (tbucket_nm * req.EncBucket.0, tbucket_nm * req.EncBucket.1); + + let EncQ_nm = ( + EncQHc_nm.0 + EncQId_nm.0 + EncQBucket_nm.0, + EncQHc_nm.1 + EncQId_nm.1 + EncQBucket_nm.1, + ); + + let mut transcript = Transcript::new(b"level upgrade 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], + Xlevel: &self.lox_pub.X[3], + Xsince: &self.lox_pub.X[4], + Xinvremain: &self.lox_pub.X[5], + Xinvissued: &self.lox_pub.X[6], + Plevel: &(trust_level * P), + Psince: &(level_since * P), + Pinvremain: &(invitations_remaining * P), + TId: &TId, + TBucket: &TBucket, + TInvIssued: &TInvIssued, + P_nm: &P_nm, + EncQ0_nm: &EncQ_nm.0, + EncQ1_nm: &EncQ_nm.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_nm, + TBucket_nm: &TBucket_nm, + D: &req.D, + EncId0: &EncId.0, + EncId1: &EncId.1, + EncBucket0: &req.EncBucket.0, + EncBucket1: &req.EncBucket.1, + EncInvIssued0: &req.EncInvIssued.0, + EncInvIssued1: &req.EncInvIssued.1, + x0: &self.lox_priv.x[0], + x0tilde: &self.lox_priv.x0tilde, + xid: &self.lox_priv.x[1], + xbucket: &self.lox_priv.x[2], + xlevel: &self.lox_priv.x[3], + xsince: &self.lox_priv.x[4], + xinvremain: &self.lox_priv.x[5], + xinvissued: &self.lox_priv.x[6], + s: &s, + b: &b, + tid: &tid, + tbucket: &tbucket, + tinvissued: &tinvissued, + 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_nm, + b_nm: &b_nm, + tid_nm: &tid_nm, + tbucket_nm: &tbucket_nm, + }, + ) + .0; + + Ok(Response { + P, + EncQ, + id_server, + level_since, + TId, + TBucket, + TInvIssued, + P_nm, + EncQ_nm, + TId_nm, + TBucket_nm, + piBlindIssue, + }) + } +} + +/// Handle the response to the request, producing the new Lox credential +/// if successful. +pub fn handle_response( + state: State, + resp: Response, + lox_pub: &IssuerPubKey, + migration_pub: &IssuerPubKey, +) -> Result { + let A: &RistrettoPoint = &CMZ_A; + let B: &RistrettoPoint = &CMZ_B; + let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE; + + if resp.P.is_identity() || resp.P_nm.is_identity() { + return Err(ProofError::VerificationFailure); + } + + // Add the server's contribution to the id to our own, both in plain + // and encrypted form + let id = state.id_client + resp.id_server; + let EncId = ( + state.EncIdClient.0, + state.EncIdClient.1 + &resp.id_server * Btable, + ); + + // Verify the proof + let mut transcript = Transcript::new(b"level upgrade issuing"); + blindissue::verify_compact( + &resp.piBlindIssue, + &mut transcript, + blindissue::VerifyAssignments { + A: &A.compress(), + B: &B.compress(), + P: &resp.P.compress(), + EncQ0: &resp.EncQ.0.compress(), + EncQ1: &resp.EncQ.1.compress(), + X0: &lox_pub.X[0].compress(), + Xid: &lox_pub.X[1].compress(), + Xbucket: &lox_pub.X[2].compress(), + Xlevel: &lox_pub.X[3].compress(), + Xsince: &lox_pub.X[4].compress(), + Xinvremain: &lox_pub.X[5].compress(), + Xinvissued: &lox_pub.X[6].compress(), + Plevel: &(state.level * resp.P).compress(), + Psince: &(resp.level_since * resp.P).compress(), + Pinvremain: &(state.invremain * resp.P).compress(), + TId: &resp.TId.compress(), + TBucket: &resp.TBucket.compress(), + TInvIssued: &resp.TInvIssued.compress(), + P_nm: &resp.P_nm.compress(), + EncQ0_nm: &resp.EncQ_nm.0.compress(), + EncQ1_nm: &resp.EncQ_nm.1.compress(), + X0_nm: &migration_pub.X[0].compress(), + Xid_nm: &migration_pub.X[1].compress(), + Xfrom_nm: &migration_pub.X[2].compress(), + Xto_nm: &migration_pub.X[3].compress(), + TId_nm: &resp.TId_nm.compress(), + TBucket_nm: &resp.TBucket_nm.compress(), + D: &state.D.compress(), + EncId0: &EncId.0.compress(), + EncId1: &EncId.1.compress(), + EncBucket0: &state.EncBucket.0.compress(), + EncBucket1: &state.EncBucket.1.compress(), + EncInvIssued0: &state.EncInvIssued.0.compress(), + EncInvIssued1: &state.EncInvIssued.1.compress(), + }, + )?; + + // Decrypt EncQ + let Q = resp.EncQ.1 - (state.d * resp.EncQ.0); + + // Decrypt EncQ_nm + let Q_nm = resp.EncQ_nm.1 - (state.d * resp.EncQ_nm.0); + + Ok(cred::Lox { + P: resp.P, + Q, + id, + bucket: state.bucket, + trust_level: state.level, + level_since: resp.level_since, + invites_remaining: state.invremain, + invites_issued: state.invissued, + P_noopmigration: resp.P_nm, + Q_noopmigration: Q_nm, + }) +} diff --git a/crates/lox-library/src/proto/migration.rs b/crates/lox-library/src/proto/migration.rs index 72964ee..6d5a2bc 100644 --- a/crates/lox-library/src/proto/migration.rs +++ b/crates/lox-library/src/proto/migration.rs @@ -428,7 +428,7 @@ impl BridgeAuth { let EncQHc = (&s * Btable, QHc + s * req.D); // Homomorphically compute the part of the MAC corresponding to - // the blinded id attribute + // the blinded attributes let tid = self.lox_priv.x[1] * b; let TId = &tid * Atable; let EncQId = (tid * EncId.0, tid * EncId.1); diff --git a/crates/lox-library/src/proto/trust_promotion.rs b/crates/lox-library/src/proto/trust_promotion.rs index 6c4ddac..578e584 100644 --- a/crates/lox-library/src/proto/trust_promotion.rs +++ b/crates/lox-library/src/proto/trust_promotion.rs @@ -485,7 +485,7 @@ impl BridgeAuth { )?; // Ensure the id has not been seen before, either in the general - // if filter, or the filter specifically for trust promotion. + // id filter, or the filter specifically for trust promotion. // Add the id to the latter, but not the former. if self.id_filter.check(&req.id) == SeenType::Seen || self.trust_promotion_filter.filter(&req.id) == SeenType::Seen diff --git a/crates/lox-library/src/tests.rs b/crates/lox-library/src/tests.rs index bb40cda..b544560 100644 --- a/crates/lox-library/src/tests.rs +++ b/crates/lox-library/src/tests.rs @@ -147,25 +147,57 @@ fn test_level0_migration() { assert!(ba.verify_reachability(&bucket.1.unwrap())); } -#[test] -fn test_level_up() { - let (bdb, mut ba) = setup(); - let loxcred = level0_migration(&bdb, &mut ba); - - // Time passes - ba.advance_days(20); - - let (id, key) = bridge_table::from_scalar(loxcred.bucket).unwrap(); +fn level_up(ba: &mut BridgeAuth, cred: &cred::Lox) -> cred::Lox { + // Read the bucket in the credential to get today's Bucket + // Reachability credential + let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap(); let encbuckets = ba.enc_bridge_table(); let bucket = bridge_table::BridgeTable::decrypt_bucket(id, &key, &encbuckets[id as usize]).unwrap(); let reachcred = bucket.1.unwrap(); + + // Use the Bucket Reachability credential to advance to the next + // level let (req, state) = level_up::request( - &loxcred, + &cred, &reachcred, &ba.lox_pub, &ba.reachability_pub, ba.today(), ) .unwrap(); + let resp = ba.handle_level_up(req).unwrap(); + let cred = level_up::handle_response(state, resp, &ba.lox_pub, &ba.migration_pub).unwrap(); + cred +} + +#[test] +fn test_level_up() { + let (bdb, mut ba) = setup(); + let cred1 = level0_migration(&bdb, &mut ba); + assert!(scalar_u32(&cred1.trust_level).unwrap() == 1); + + // Time passes + ba.advance_days(20); + + let cred2 = level_up(&mut ba, &cred1); + assert!(scalar_u32(&cred2.trust_level).unwrap() == 2); + println!("cred2 = {:?}", cred2); + assert!(ba.verify_lox(&cred2)); + + // Time passes + ba.advance_days(30); + + let cred3 = level_up(&mut ba, &cred2); + assert!(scalar_u32(&cred3.trust_level).unwrap() == 3); + println!("cred3 = {:?}", cred3); + assert!(ba.verify_lox(&cred3)); + + // Time passes + ba.advance_days(60); + + let cred4 = level_up(&mut ba, &cred3); + assert!(scalar_u32(&cred3.trust_level).unwrap() == 3); + println!("cred4 = {:?}", cred4); + assert!(ba.verify_lox(&cred4)); }