From 74bae2cf8eafdad8fecb40ecec8923e2d3bbb0e1 Mon Sep 17 00:00:00 2001 From: Ian Goldberg Date: Wed, 5 May 2021 16:28:56 -0400 Subject: [PATCH] The complete check blockage protocol --- crates/lox-library/src/cred.rs | 10 +- crates/lox-library/src/lib.rs | 1 + crates/lox-library/src/migration_table.rs | 12 +- .../lox-library/src/proto/check_blockage.rs | 355 ++++++++++++++++++ crates/lox-library/src/tests.rs | 71 ++++ 5 files changed, 438 insertions(+), 11 deletions(-) create mode 100644 crates/lox-library/src/proto/check_blockage.rs diff --git a/crates/lox-library/src/cred.rs b/crates/lox-library/src/cred.rs index 16b8b12..a0a320d 100644 --- a/crates/lox-library/src/cred.rs +++ b/crates/lox-library/src/cred.rs @@ -11,10 +11,10 @@ use curve25519_dalek::scalar::Scalar; /// /// This credential authorizes the holder of the Lox credential with the /// given id to switch from bucket from_bucket to bucket to_bucket. The -/// mig_type attribute is 0 for trust upgrade migrations (moving from a -/// 1-bridge untrusted bucket to a 3-bridge trusted bucket) and 1 for -/// blockage migrations (moving buckets because the from_bucket has been -/// blocked). +/// migration_type attribute is 0 for trust upgrade migrations (moving +/// from a 1-bridge untrusted bucket to a 3-bridge trusted bucket) and 1 +/// for blockage migrations (moving buckets because the from_bucket has +/// been blocked). #[derive(Debug)] pub struct Migration { pub P: RistrettoPoint, @@ -22,7 +22,7 @@ pub struct Migration { pub lox_id: Scalar, pub from_bucket: Scalar, pub to_bucket: Scalar, - pub mig_type: Scalar, + pub migration_type: Scalar, } /// The main user credential in the Lox system. diff --git a/crates/lox-library/src/lib.rs b/crates/lox-library/src/lib.rs index 747a85f..ee6d182 100644 --- a/crates/lox-library/src/lib.rs +++ b/crates/lox-library/src/lib.rs @@ -536,6 +536,7 @@ pub fn pt_dbl(P: &RistrettoPoint) -> RistrettoPoint { /// Response. It also adds a handle_* function to the BridgeAuth struct /// that consumes a Request and produces a Result. pub mod proto { + pub mod check_blockage; pub mod issue_invite; pub mod level_up; pub mod migration; diff --git a/crates/lox-library/src/migration_table.rs b/crates/lox-library/src/migration_table.rs index 356dbfd..4a596f1 100644 --- a/crates/lox-library/src/migration_table.rs +++ b/crates/lox-library/src/migration_table.rs @@ -79,7 +79,7 @@ pub fn encrypt_cred( id: &Scalar, from_bucket: &Scalar, to_bucket: &Scalar, - mig_type: &Scalar, + migration_type: &Scalar, Pktable: &RistrettoBasepointTable, migration_priv: &IssuerPrivKey, migrationkey_priv: &IssuerPrivKey, @@ -102,7 +102,7 @@ pub fn encrypt_cred( + migration_priv.x[1] * id + migration_priv.x[2] * from_bucket + migration_priv.x[3] * to_bucket - + migration_priv.x[4] * mig_type)) + + migration_priv.x[4] * migration_type)) * Btable; // Serialize (to_bucket, P, Q) @@ -150,7 +150,7 @@ pub fn encrypt_cred_ids( id: &Scalar, from_id: u32, to_id: u32, - mig_type: &Scalar, + migration_type: &Scalar, bridgetable: &bridge_table::BridgeTable, Pktable: &RistrettoBasepointTable, migration_priv: &IssuerPrivKey, @@ -163,7 +163,7 @@ pub fn encrypt_cred_ids( id, &bridge_table::to_scalar(from_id, fromkey), &bridge_table::to_scalar(to_id, tokey), - mig_type, + migration_type, Pktable, migration_priv, migrationkey_priv, @@ -216,7 +216,7 @@ pub fn decrypt_cred( Qk: &RistrettoPoint, lox_id: &Scalar, from_bucket: &Scalar, - mig_type: MigrationType, + migration_type: MigrationType, enc_migration_table: &HashMap<[u8; 16], [u8; ENC_MIGRATION_BYTES]>, ) -> Option { // Compute the hash of (id, from_bucket, Qk) @@ -256,6 +256,6 @@ pub fn decrypt_cred( lox_id: *lox_id, from_bucket: *from_bucket, to_bucket, - mig_type: mig_type.into(), + migration_type: migration_type.into(), }) } diff --git a/crates/lox-library/src/proto/check_blockage.rs b/crates/lox-library/src/proto/check_blockage.rs new file mode 100644 index 0000000..a60958c --- /dev/null +++ b/crates/lox-library/src/proto/check_blockage.rs @@ -0,0 +1,355 @@ +/*! A module for the protocol for the user to check for the availability +of a migration credential they can use in order to move to a new bucket +if theirs has been blocked. + +The user presents their current Lox credential: +- id: revealed +- bucket: blinded +- trust_level: revealed to be 3 or above +- level_since: blinded +- invites_remaining: blinded +- blockages: blinded + +They are allowed to to this as long as they are level 3 or above. If +they have too many blockages (but are level 3 or above), they will be +allowed to perform this migration, but will not be able to advance to +level 3 in their new bucket, so this will be their last allowed +migration without rejoining the system either with a new invitation or +an open invitation. + +They will receive in return the encrypted MAC (Pk, EncQk) for their +implicit Migration Key credential with attributes id and bucket, +along with a HashMap of encrypted Migration credentials. For each +(from_i, to_i) in the BA's migration list, there will be an entry in +the HashMap with key H1(id, from_attr_i, Qk_i) and value +Enc_{H2(id, from_attr_i, Qk_i)}(to_attr_i, P_i, Q_i). Here H1 and H2 +are the first 16 bytes and the second 16 bytes respectively of the +SHA256 hash of the input, P_i and Q_i are a MAC on the Migration +credential with attributes id, from_attr_i, and to_attr_i. Qk_i is the +value EncQk would decrypt to if bucket were equal to from_attr_i. */ + +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 std::collections::HashMap; + +use super::super::cred; +use super::super::dup_filter::SeenType; +use super::super::migration_table; +use super::super::scalar_u32; +use super::super::{BridgeAuth, IssuerPubKey}; +use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE}; + +/// The minimum trust level a Lox credential must have to be allowed to +/// perform this protocol. +pub const MIN_TRUST_LEVEL: u32 = 3; + +pub struct Request { + // Fields for blind showing the Lox credential + P: RistrettoPoint, + id: Scalar, + CBucket: RistrettoPoint, + level: Scalar, + CSince: RistrettoPoint, + CInvRemain: RistrettoPoint, + CBlockages: RistrettoPoint, + CQ: RistrettoPoint, + + // Fields for user blinding of the Migration Key credential + D: RistrettoPoint, + EncBucket: (RistrettoPoint, RistrettoPoint), + + // The combined ZKP + piUser: CompactProof, +} + +#[derive(Debug)] +pub struct State { + d: Scalar, + D: RistrettoPoint, + EncBucket: (RistrettoPoint, RistrettoPoint), + id: Scalar, + bucket: Scalar, +} + +#[derive(Debug)] +pub struct Response { + // The encrypted MAC for the Migration Key credential + Pk: RistrettoPoint, + EncQk: (RistrettoPoint, RistrettoPoint), + + // A table of encrypted Migration credentials; the encryption keys + // are formed from the possible values of Qk (the decrypted form of + // EncQk) + enc_migration_table: HashMap<[u8; 16], [u8; migration_table::ENC_MIGRATION_BYTES]>, +} + +define_proof! { + requestproof, + "Check Blockage Request", + (bucket, since, invremain, blockages, zbucket, zsince, zinvremain, + zblockages, negzQ, + d, ebucket), + (P, CBucket, CSince, CInvRemain, CBlockages, V, Xbucket, Xsince, + Xinvremain, Xblockages, + D, EncBucket0, EncBucket1), + (A, B): + // Blind showing of the Lox credential + CBucket = (bucket*P + zbucket*A), + CSince = (since*P + zsince*A), + CInvRemain = (invremain*P + zinvremain*A), + CBlockages = (blockages*P + zblockages*A), + V = (zbucket*Xbucket + zsince*Xsince + zinvremain*Xinvremain + + zblockages*Xblockages + negzQ*A), + // User blinding of the Migration Key credential + D = (d*B), + EncBucket0 = (ebucket*B), + EncBucket1 = (bucket*B + ebucket*D) +} + +pub fn request( + lox_cred: &cred::Lox, + lox_pub: &IssuerPubKey, +) -> Result<(Request, State), ProofError> { + let A: &RistrettoPoint = &CMZ_A; + let B: &RistrettoPoint = &CMZ_B; + let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE; + let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE; + + // Ensure the credential can be correctly shown: it must be the case + // that trust_level >= MIN_TRUST_LEVEL + let level: u32 = match scalar_u32(&lox_cred.trust_level) { + Some(v) => v, + None => return Err(ProofError::VerificationFailure), + }; + if level < MIN_TRUST_LEVEL { + return Err(ProofError::VerificationFailure); + } + + // Blind showing the Lox credential + + // Reblind P and Q + let mut rng = rand::thread_rng(); + let t = Scalar::random(&mut rng); + let P = t * lox_cred.P; + let Q = t * lox_cred.Q; + + // Form Pedersen commitments to the blinded attributes + let zbucket = Scalar::random(&mut rng); + let zsince = Scalar::random(&mut rng); + let zinvremain = Scalar::random(&mut rng); + let zblockages = Scalar::random(&mut rng); + let CBucket = lox_cred.bucket * P + &zbucket * Atable; + let CSince = lox_cred.level_since * P + &zsince * Atable; + let CInvRemain = lox_cred.invites_remaining * P + &zinvremain * Atable; + let CBlockages = lox_cred.blockages * P + &zblockages * Atable; + + // Form a Pedersen commitment to the MAC Q + // We flip the sign of zQ from that of the Hyphae paper so that + // the ZKP has a "+" instead of a "-", as that's what the zkp + // macro supports. + let negzQ = Scalar::random(&mut rng); + let CQ = Q - &negzQ * Atable; + + // Compute the "error factor" + let V = zbucket * lox_pub.X[2] + + zsince * lox_pub.X[4] + + zinvremain * lox_pub.X[5] + + zblockages * lox_pub.X[6] + + &negzQ * Atable; + + // User blinding the Migration Key credential + + // Pick an ElGamal keypair + let d = Scalar::random(&mut rng); + let D = &d * Btable; + + // Encrypt the attributes to be blinded (each times the + // basepoint B) to the public key we just created + let ebucket = Scalar::random(&mut rng); + let EncBucket = (&ebucket * Btable, &lox_cred.bucket * Btable + ebucket * D); + + // Construct the proof + let mut transcript = Transcript::new(b"check blockage request"); + let piUser = requestproof::prove_compact( + &mut transcript, + requestproof::ProveAssignments { + A: &A, + B: &B, + P: &P, + CBucket: &CBucket, + CSince: &CSince, + CInvRemain: &CInvRemain, + CBlockages: &CBlockages, + V: &V, + Xbucket: &lox_pub.X[2], + Xsince: &lox_pub.X[4], + Xinvremain: &lox_pub.X[5], + Xblockages: &lox_pub.X[6], + D: &D, + EncBucket0: &EncBucket.0, + EncBucket1: &EncBucket.1, + bucket: &lox_cred.bucket, + since: &lox_cred.level_since, + invremain: &lox_cred.invites_remaining, + blockages: &lox_cred.blockages, + zbucket: &zbucket, + zsince: &zsince, + zinvremain: &zinvremain, + zblockages: &zblockages, + negzQ: &negzQ, + d: &d, + ebucket: &ebucket, + }, + ) + .0; + + Ok(( + Request { + P, + id: lox_cred.id, + CBucket, + level: lox_cred.trust_level, + CSince, + CInvRemain, + CBlockages, + CQ, + D, + EncBucket, + piUser, + }, + State { + d, + D, + EncBucket, + id: lox_cred.id, + bucket: lox_cred.bucket, + }, + )) +} + +impl BridgeAuth { + /// Receive a check blockage request + pub fn handle_check_blockage(&mut self, req: Request) -> Result { + let A: &RistrettoPoint = &CMZ_A; + let B: &RistrettoPoint = &CMZ_B; + let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE; + + let level: u32 = match scalar_u32(&req.level) { + Some(v) => v, + None => return Err(ProofError::VerificationFailure), + }; + + if req.P.is_identity() || level < MIN_TRUST_LEVEL { + return Err(ProofError::VerificationFailure); + } + + // Recompute the "error factor" 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.CBlockages + - req.CQ; + + // Verify the ZKP + let mut transcript = Transcript::new(b"check blockage 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(), + CBlockages: &req.CBlockages.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(), + Xblockages: &self.lox_pub.X[6].compress(), + D: &req.D.compress(), + EncBucket0: &req.EncBucket.0.compress(), + EncBucket1: &req.EncBucket.1.compress(), + }, + )?; + + // Ensure the id has not been seen before in the general id + // filter, but do not add it, so that the user can potentially + // run this protocol multiple times. + if self.id_filter.check(&req.id) == SeenType::Seen { + return Err(ProofError::VerificationFailure); + } + + // Compute the encrypted MAC (Pk, EncQk) for the Migration Key + // credential. + + // Compute the MAC on the visible attributes + let mut rng = rand::thread_rng(); + let b = Scalar::random(&mut rng); + let Pk = &b * Btable; + let Pktable = RistrettoBasepointTable::create(&Pk); + let Qid = &(self.migrationkey_priv.x[0] + self.migrationkey_priv.x[1] * req.id) * &Pktable; + + // El Gamal encrypt it to the public key req.D + let s = Scalar::random(&mut rng); + let EncQkid = (&s * Btable, Qid + s * req.D); + + // Homomorphically compute the part of the MAC corresponding to + // the blinded attributes + let tbucket = self.migrationkey_priv.x[2] * b; + let EncQkBucket = (tbucket * req.EncBucket.0, tbucket * req.EncBucket.1); + + let EncQk = (EncQkid.0 + EncQkBucket.0, EncQkid.1 + EncQkBucket.1); + + Ok(Response { + Pk, + EncQk, + enc_migration_table: self.blockage_migration_table.encrypt_table( + &req.id, + &self.bridge_table, + &Pktable, + &self.migration_priv, + &self.migrationkey_priv, + ), + }) + } +} + +/// Handle the response to the request, producing a Migration credential +/// if successful. +/// +/// The Migration credential can then be used in the migration protocol +/// to actually change buckets +pub fn handle_response(state: State, resp: Response) -> Result { + if resp.Pk.is_identity() { + return Err(ProofError::VerificationFailure); + } + + // Decrypt the MAC on the Migration Key credential + let Qk = resp.EncQk.1 - (state.d * resp.EncQk.0); + + // Use Qk to locate and decrypt the Migration credential + match migration_table::decrypt_cred( + &Qk, + &state.id, + &state.bucket, + migration_table::MigrationType::Blockage, + &resp.enc_migration_table, + ) { + Some(m) => Ok(m), + None => Err(ProofError::VerificationFailure), + } +} diff --git a/crates/lox-library/src/tests.rs b/crates/lox-library/src/tests.rs index 00ca397..00e1987 100644 --- a/crates/lox-library/src/tests.rs +++ b/crates/lox-library/src/tests.rs @@ -120,6 +120,12 @@ impl TestHarness { let resp = self.ba.handle_redeem_invite(req).unwrap(); redeem_invite::handle_response(state, resp, &self.ba.lox_pub).unwrap() } + + fn check_blockage(&mut self, cred: &cred::Lox) -> cred::Migration { + let (req, state) = check_blockage::request(&cred, &self.ba.lox_pub).unwrap(); + let resp = self.ba.handle_check_blockage(req).unwrap(); + check_blockage::handle_response(state, resp).unwrap() + } } #[test] @@ -350,3 +356,68 @@ fn test_mark_unreachable() { println!("bmig = {:?}", th.ba.blockage_migration_table.table); println!("openinv = {:?}\n", th.bdb.openinv_buckets); } + +#[test] +fn test_check_blockage() { + let mut th = TestHarness::new(); + + // Join an untrusted user + let cred = th.open_invite(); + + // Time passes + th.advance_days(47); + + // Go up to level 1 + let migcred = th.trust_promotion(&cred); + let cred1 = th.level0_migration(&cred, &migcred); + assert!(scalar_u32(&cred1.trust_level).unwrap() == 1); + + // Time passes + th.advance_days(20); + + // Go up to level 2 + let cred2 = th.level_up(&cred1); + assert!(scalar_u32(&cred2.trust_level).unwrap() == 2); + println!("cred2 = {:?}", cred2); + assert!(th.ba.verify_lox(&cred2)); + + // Time passes + th.advance_days(29); + + // Go up to level 3 + let cred3 = th.level_up(&cred2); + assert!(scalar_u32(&cred3.trust_level).unwrap() == 3); + println!("cred3 = {:?}", cred3); + assert!(th.ba.verify_lox(&cred3)); + + // Get our bridges + let (id, key) = bridge_table::from_scalar(cred3.bucket).unwrap(); + let encbuckets = th.ba.enc_bridge_table(); + let bucket = + bridge_table::BridgeTable::decrypt_bucket(id, &key, &encbuckets[id as usize]).unwrap(); + // We should have a Bridge Reachability credential + assert!(bucket.1.is_some()); + + // Oh, no! Two of our bridges are blocked! + th.ba.bridge_unreachable(&bucket.0[0], &mut th.bdb); + th.ba.bridge_unreachable(&bucket.0[2], &mut th.bdb); + + println!("spares = {:?}", th.ba.bridge_table.spares); + println!("tmig = {:?}", th.ba.trustup_migration_table.table); + println!("bmig = {:?}", th.ba.blockage_migration_table.table); + println!("openinv = {:?}\n", th.bdb.openinv_buckets); + + // Time passes + th.advance_days(1); + + let encbuckets2 = th.ba.enc_bridge_table(); + let bucket2 = + bridge_table::BridgeTable::decrypt_bucket(id, &key, &encbuckets2[id as usize]).unwrap(); + // We should no longer have a Bridge Reachability credential + assert!(bucket2.1.is_none()); + + // See about getting a Migration credential for the blockage + let migration = th.check_blockage(&cred3); + + println!("migration = {:?}", migration); +}