Browse Source

Complete the redeem invitation protocol

Ian Goldberg 3 years ago
parent
commit
c850d07e72
5 changed files with 282 additions and 10 deletions
  1. 2 2
      Cargo.toml
  2. 4 1
      src/lib.rs
  3. 2 2
      src/proto/level_up.rs
  4. 269 4
      src/proto/redeem_invite.rs
  5. 5 1
      src/tests.rs

+ 2 - 2
Cargo.toml

@@ -7,8 +7,8 @@ edition = "2018"
 [dependencies]
 curve25519-dalek = { package = "curve25519-dalek-ng", version = "3", default-features = false, features = ["serde", "std"] }
 ed25519-dalek = "1"
-zkp = { version = "0.8", features = ["debug-transcript"] }
-# zkp = "0.8"
+# zkp = { version = "0.8", features = ["debug-transcript"] }
+zkp = "0.8"
 bincode = "1"
 rand = "0.7"
 serde = "1"

+ 4 - 1
src/lib.rs

@@ -206,8 +206,10 @@ pub struct BridgeAuth {
 
     /// Duplicate filter for open invitations
     openinv_filter: dup_filter::DupFilter<Scalar>,
-    /// Duplicate filter for credential ids
+    /// Duplicate filter for Lox credential ids
     id_filter: dup_filter::DupFilter<Scalar>,
+    /// Duplicate filter for Invitation credential ids
+    inv_id_filter: dup_filter::DupFilter<Scalar>,
     /// Duplicate filter for trust promotions (from untrusted level 0 to
     /// trusted level 1)
     trust_promotion_filter: dup_filter::DupFilter<Scalar>,
@@ -246,6 +248,7 @@ impl BridgeAuth {
             migration_table: Default::default(),
             openinv_filter: Default::default(),
             id_filter: Default::default(),
+            inv_id_filter: Default::default(),
             trust_promotion_filter: Default::default(),
             time_offset: time::Duration::zero(),
         }

+ 2 - 2
src/proto/level_up.rs

@@ -729,8 +729,8 @@ impl BridgeAuth {
             - req.CQ_reach;
 
         // Recompute CG0 using Horner's method
-        let unt: Scalar = LEVEL_INTERVAL[level].into();
-        let CG0prime = (today - unt) * req.P
+        let interval: Scalar = LEVEL_INTERVAL[level].into();
+        let CG0prime = (today - interval) * req.P
             - req.CSince
             - pt_dbl(
                 &(pt_dbl(

+ 269 - 4
src/proto/redeem_invite.rs

@@ -44,7 +44,7 @@ pub const INVITATION_EXPIRY: u32 = 15;
 pub struct Request {
     // Fields for showing the Invitation credential
     P: RistrettoPoint,
-    id: Scalar,
+    inv_id: Scalar,
     CDate: RistrettoPoint,
     CBucket: RistrettoPoint,
     CBlockages: RistrettoPoint,
@@ -138,6 +138,34 @@ define_proof! {
     // + 8*CG3) as its value of CG0.
 }
 
+define_proof! {
+    blindissue,
+    "Redeem Invite Issuing",
+    (x0, x0tilde, xid, xbucket, xlevel, xsince, xblockages,
+     s, b, tid, tbucket, tblockages),
+    (P, EncQ0, EncQ1, X0, Xid, Xbucket, Xlevel, Xsince, Xblockages,
+    Psince, TId, TBucket, TBlockages,
+     D, EncId0, EncId1, EncBucket0, EncBucket1, EncBlockages0, EncBlockages1),
+    (A, B):
+    Xid = (xid*A),
+    Xbucket = (xbucket*A),
+    Xlevel = (xlevel*A),
+    Xsince = (xsince*A),
+    Xblockages = (xblockages*A),
+    X0 = (x0*B + x0tilde*A),
+    P = (b*B),
+    TId = (b*Xid),
+    TId = (tid*A),
+    TBucket = (b*Xbucket),
+    TBucket = (tbucket*A),
+    TBlockages = (b*Xblockages),
+    TBlockages = (tblockages*A),
+    EncQ0 = (s*B + tid*EncId0 + tbucket*EncBucket0 + tblockages*EncBlockages0),
+    // level=1 (so Plevel = P) and invremain=0 (so the term is omitted)
+    EncQ1 = (s*D + tid*EncId1 + tbucket*EncBucket1
+            + tblockages*EncBlockages1 + x0*P + xlevel*P + xsince*Psince)
+}
+
 pub fn request(
     inv_cred: &cred::Invitation,
     invitation_pub: &IssuerPubKey,
@@ -234,9 +262,9 @@ pub fn request(
     let wg3 = Scalar::random(&mut rng);
 
     // Compute zg0 to cancel things out as
-    // zg0 = -(zdate + 2*zg1 + 4*zg2 + 8*zg3)
+    // zg0 = zdate - (2*zg1 + 4*zg2 + 8*zg3)
     // but use Horner's method
-    let zg0 = -(scalar_dbl(&(scalar_dbl(&(scalar_dbl(&zg3) + zg2)) + zg1)) + zdate);
+    let zg0 = zdate - scalar_dbl(&(scalar_dbl(&(scalar_dbl(&zg3) + zg2)) + zg1));
 
     let yg0 = wg0 + g0 * zg0;
     let yg1 = wg1 + g1 * zg1;
@@ -318,7 +346,7 @@ pub fn request(
     Ok((
         Request {
             P,
-            id: inv_cred.inv_id,
+            inv_id: inv_cred.inv_id,
             CDate,
             CBucket,
             CBlockages,
@@ -348,3 +376,240 @@ pub fn request(
         },
     ))
 }
+
+impl BridgeAuth {
+    /// Receive a redeem invite request
+    pub fn handle_redeem_invite(&mut self, req: Request) -> Result<Response, ProofError> {
+        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();
+
+        // Recompute the "error factor" using knowledge of our own
+        // (the issuer's) private key instead of knowledge of the
+        // hidden attributes
+        let Vprime = (self.invitation_priv.x[0] + self.invitation_priv.x[1] * req.inv_id) * req.P
+            + self.invitation_priv.x[2] * req.CDate
+            + self.invitation_priv.x[3] * req.CBucket
+            + self.invitation_priv.x[4] * req.CBlockages
+            - req.CQ;
+
+        // Recompute CG0 using Horner's method
+        let expiry: Scalar = INVITATION_EXPIRY.into();
+        let CG0prime = (expiry - today) * req.P + req.CDate
+            - pt_dbl(&(pt_dbl(&(pt_dbl(&req.CG3) + req.CG2)) + req.CG1));
+
+        // Verify the ZKP
+        let mut transcript = Transcript::new(b"redeem invite request");
+        requestproof::verify_compact(
+            &req.piUser,
+            &mut transcript,
+            requestproof::VerifyAssignments {
+                A: &A.compress(),
+                B: &B.compress(),
+                P: &req.P.compress(),
+                CDate: &req.CDate.compress(),
+                CBucket: &req.CBucket.compress(),
+                CBlockages: &req.CBlockages.compress(),
+                V: &Vprime.compress(),
+                Xdate: &self.invitation_pub.X[2].compress(),
+                Xbucket: &self.invitation_pub.X[3].compress(),
+                Xblockages: &self.invitation_pub.X[4].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(),
+                EncBlockages0: &req.EncBlockages.0.compress(),
+                EncBlockages1: &req.EncBlockages.1.compress(),
+                CG0: &CG0prime.compress(),
+                CG1: &req.CG1.compress(),
+                CG2: &req.CG2.compress(),
+                CG3: &req.CG3.compress(),
+                CG0sq: &req.CG0sq.compress(),
+                CG1sq: &req.CG1sq.compress(),
+                CG2sq: &req.CG2sq.compress(),
+                CG3sq: &req.CG3sq.compress(),
+            },
+        )?;
+
+        // Ensure the id has not been seen before, and add it to the
+        // invite id seen list.
+        if self.inv_id_filter.filter(&req.inv_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);
+
+        // The trust level for invitees is always 1
+        let level = Scalar::one();
+
+        // The invites remaining for invitees is always 0 (as
+        // appropriate for trust level 1), so we don't need to actually
+        // construct it
+
+        // 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] * level + self.lox_priv.x[4] * today) * 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 tblockages = self.lox_priv.x[6] * b;
+        let TBlockages = &tblockages * Atable;
+        let EncQBlockages = (
+            tblockages * req.EncBlockages.0,
+            tblockages * req.EncBlockages.1,
+        );
+
+        let EncQ = (
+            EncQHc.0 + EncQId.0 + EncQBucket.0 + EncQBlockages.0,
+            EncQHc.1 + EncQId.1 + EncQBucket.1 + EncQBlockages.1,
+        );
+
+        let mut transcript = Transcript::new(b"redeem 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],
+                Xlevel: &self.lox_pub.X[3],
+                Xsince: &self.lox_pub.X[4],
+                Xblockages: &self.lox_pub.X[6],
+                Psince: &(today * P),
+                TId: &TId,
+                TBucket: &TBucket,
+                TBlockages: &TBlockages,
+                D: &req.D,
+                EncId0: &EncId.0,
+                EncId1: &EncId.1,
+                EncBucket0: &req.EncBucket.0,
+                EncBucket1: &req.EncBucket.1,
+                EncBlockages0: &req.EncBlockages.0,
+                EncBlockages1: &req.EncBlockages.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],
+                xblockages: &self.lox_priv.x[6],
+                s: &s,
+                b: &b,
+                tid: &tid,
+                tbucket: &tbucket,
+                tblockages: &tblockages,
+            },
+        )
+        .0;
+
+        Ok(Response {
+            P,
+            EncQ,
+            id_server,
+            level_since: today,
+            TId,
+            TBucket,
+            TBlockages,
+            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,
+) -> Result<cred::Lox, ProofError> {
+    let A: &RistrettoPoint = &CMZ_A;
+    let B: &RistrettoPoint = &CMZ_B;
+    let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
+
+    if resp.P.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"redeem invite 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(),
+            Xblockages: &lox_pub.X[6].compress(),
+            Psince: &(resp.level_since * resp.P).compress(),
+            TId: &resp.TId.compress(),
+            TBucket: &resp.TBucket.compress(),
+            TBlockages: &resp.TBlockages.compress(),
+            D: &state.D.compress(),
+            EncId0: &EncId.0.compress(),
+            EncId1: &EncId.1.compress(),
+            EncBucket0: &state.EncBucket.0.compress(),
+            EncBucket1: &state.EncBucket.1.compress(),
+            EncBlockages0: &state.EncBlockages.0.compress(),
+            EncBlockages1: &state.EncBlockages.1.compress(),
+        },
+    )?;
+
+    // Decrypt EncQ
+    let Q = resp.EncQ.1 - (state.d * resp.EncQ.0);
+
+    Ok(cred::Lox {
+        P: resp.P,
+        Q,
+        id,
+        bucket: state.bucket,
+        trust_level: Scalar::one(),
+        level_since: resp.level_since,
+        invites_remaining: Scalar::zero(),
+        blockages: state.blockages,
+    })
+}

+ 5 - 1
src/tests.rs

@@ -245,7 +245,8 @@ fn test_issue_invite() {
 
 fn redeem_invite(ba: &mut BridgeAuth, inv: &cred::Invitation) -> cred::Lox {
     let (req, state) = redeem_invite::request(&inv, &ba.invitation_pub, ba.today()).unwrap();
-    panic!("Not finished")
+    let resp = ba.handle_redeem_invite(req).unwrap();
+    redeem_invite::handle_response(state, resp, &ba.lox_pub).unwrap()
 }
 
 #[test]
@@ -266,6 +267,9 @@ fn test_redeem_invite() {
     println!("cred3 = {:?}", cred3);
     println!("invite = {:?}", invite);
 
+    // Time passes
+    ba.advance_days(12);
+
     let cred4 = redeem_invite(&mut ba, &invite);
     assert!(ba.verify_lox(&cred4));
     println!("cred4 = {:?}", cred4);