Browse Source

The complete check blockage protocol

Ian Goldberg 3 years ago
parent
commit
b12b5114e0
5 changed files with 438 additions and 11 deletions
  1. 5 5
      src/cred.rs
  2. 1 0
      src/lib.rs
  3. 6 6
      src/migration_table.rs
  4. 355 0
      src/proto/check_blockage.rs
  5. 71 0
      src/tests.rs

+ 5 - 5
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.

+ 1 - 0
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<Response, ProofError>.
 pub mod proto {
+    pub mod check_blockage;
     pub mod issue_invite;
     pub mod level_up;
     pub mod migration;

+ 6 - 6
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<Migration> {
     // 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(),
     })
 }

+ 355 - 0
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<Response, ProofError> {
+        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<cred::Migration, ProofError> {
+    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),
+    }
+}

+ 71 - 0
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);
+}