Browse Source

The request message of the issue invitation protocol

Ian Goldberg 3 years ago
parent
commit
ec020d25e8
5 changed files with 523 additions and 2 deletions
  1. 18 0
      src/cred.rs
  2. 26 0
      src/lib.rs
  3. 443 0
      src/proto/issue_invite.rs
  4. 4 2
      src/proto/level_up.rs
  5. 32 0
      src/tests.rs

+ 18 - 0
src/cred.rs

@@ -75,3 +75,21 @@ pub struct BucketReachability {
     pub date: Scalar,
     pub bucket: Scalar,
 }
+
+/// The Invitation credential.
+///
+/// These credentials allow a Lox user (the inviter) of sufficient trust
+/// (level 2 or higher) to invite someone else (the invitee) to join the
+/// system.  The invitee ends up at trust level 1, in the _same bucket_
+/// as the inviter, and inherits the inviter's blockages count (so that
+/// you can't clear your blockages count simply by inviting yourself).
+/// Invitations expire after some amount of time.
+#[derive(Debug)]
+pub struct Invitation {
+    pub P: RistrettoPoint,
+    pub Q: RistrettoPoint,
+    pub inv_id: Scalar,
+    pub date: Scalar,
+    pub bucket: Scalar,
+    pub blockages: Scalar,
+}

+ 26 - 0
src/lib.rs

@@ -190,6 +190,10 @@ pub struct BridgeAuth {
     reachability_priv: IssuerPrivKey,
     /// The public key for bucket reachability credentials
     pub reachability_pub: IssuerPubKey,
+    /// The private key for invitation credentials
+    invitation_priv: IssuerPrivKey,
+    /// The public key for invitation credentials
+    pub invitation_pub: IssuerPubKey,
 
     /// The public key of the BridgeDb issuing open invitations
     pub bridgedb_pub: PublicKey,
@@ -224,6 +228,8 @@ impl BridgeAuth {
         let migrationkey_pub = IssuerPubKey::new(&migrationkey_priv);
         let reachability_priv = IssuerPrivKey::new(2);
         let reachability_pub = IssuerPubKey::new(&reachability_priv);
+        let invitation_priv = IssuerPrivKey::new(4);
+        let invitation_pub = IssuerPubKey::new(&invitation_priv);
         Self {
             lox_priv,
             lox_pub,
@@ -233,6 +239,8 @@ impl BridgeAuth {
             migrationkey_pub,
             reachability_priv,
             reachability_pub,
+            invitation_priv,
+            invitation_pub,
             bridgedb_pub,
             bridge_table: Default::default(),
             migration_table: Default::default(),
@@ -329,6 +337,23 @@ impl BridgeAuth {
 
         Q == cred.Q
     }
+
+    #[cfg(test)]
+    /// Verify the MAC on a Invitation credential
+    pub fn verify_invitation(&self, cred: &cred::Invitation) -> bool {
+        if cred.P.is_identity() {
+            return false;
+        }
+
+        let Q = (self.invitation_priv.x[0]
+            + cred.inv_id * self.invitation_priv.x[1]
+            + cred.date * self.invitation_priv.x[2]
+            + cred.bucket * self.invitation_priv.x[3]
+            + cred.blockages * self.invitation_priv.x[4])
+            * cred.P;
+
+        Q == cred.Q
+    }
 }
 
 /// Try to extract a u64 from a Scalar
@@ -372,6 +397,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 issue_invite;
     pub mod level_up;
     pub mod migration;
     pub mod open_invite;

+ 443 - 0
src/proto/issue_invite.rs

@@ -0,0 +1,443 @@
+/*! A module for the protocol for a user to request the issuing of an
+Invitation credential they can pass to someone they know.
+
+They are allowed to do this as long as their current Lox credentials has
+a non-zero "invites_remaining" attribute (which will be decreased by
+one), and they have a Bucket Reachability credential for their current
+bucket and today's date.  (Such credentials are placed daily in the
+encrypted bridge table.)
+
+The user presents their current Lox credential:
+- id: revealed
+- bucket: blinded
+- trust_level: blinded
+- level_since: blinded
+- invites_remaining: blinded, but proved in ZK that it's not zero
+- blockages: blinded
+
+and a Bucket Reachability credential:
+- date: revealed to be today
+- bucket: blinded, but proved in ZK that it's the same as in the Lox
+  credential above
+
+and a new Lox credential to be issued:
+
+- id: jointly chosen by the user and BA
+- bucket: blinded, but proved in ZK that it's the same as in the Lox
+  credential above
+- trust_level: blinded, but proved in ZK that it's the same as in the
+  Lox credential above
+- level_since: blinded, but proved in ZK that it's the same as in the
+  Lox credential above
+- invites_remaining: blinded, but proved in ZK that it's one less than
+  the number in the Lox credential above
+- blockages: blinded, but proved in ZK that it's the same as in the
+  Lox credential above
+
+and a new Invitation credential to be issued:
+
+- inv_id: jointly chosen by the user and BA
+- date: revealed to be today
+- bucket: blinded, but proved in ZK that it's the same as in the Lox
+  credential above
+- blockages: blinded, but proved in ZK that it's the same as in the Lox
+  credential above
+
+*/
+
+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::super::cred;
+use super::super::dup_filter::SeenType;
+use super::super::{pt_dbl, scalar_dbl, scalar_u32};
+use super::super::{BridgeAuth, IssuerPubKey};
+use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
+
+pub struct Request {
+    // Fields for blind showing the Lox credential
+    P: RistrettoPoint,
+    id: Scalar,
+    CBucket: RistrettoPoint,
+    CLevel: RistrettoPoint,
+    CSince: RistrettoPoint,
+    CInvRemain: RistrettoPoint,
+    CBlockages: RistrettoPoint,
+    CQ: RistrettoPoint,
+
+    // Fields for blind showing the Bucket Reachability credential
+    P_reach: RistrettoPoint,
+    CBucket_reach: RistrettoPoint,
+    CQ_reach: RistrettoPoint,
+
+    // Fields for user blinding of the Lox credential to be issued
+    D: RistrettoPoint,
+    EncIdClient: (RistrettoPoint, RistrettoPoint),
+    EncBucket: (RistrettoPoint, RistrettoPoint),
+    EncLevel: (RistrettoPoint, RistrettoPoint),
+    EncSince: (RistrettoPoint, RistrettoPoint),
+    EncInvRemain: (RistrettoPoint, RistrettoPoint),
+    EncBlockages: (RistrettoPoint, RistrettoPoint),
+
+    // Fields for user blinding of the Inivtation credential to be
+    // issued
+    EncInvIdClient: (RistrettoPoint, RistrettoPoint),
+    // The bucket and blockages attributes in the Invitation credential
+    // issuing protocol can just reuse the exact encryptions as for the
+    // Lox credential issuing protocol above.
+
+    // The combined ZKP
+    piUser: CompactProof,
+}
+
+#[derive(Debug)]
+pub struct State {
+    d: Scalar,
+    D: RistrettoPoint,
+    EncIdClient: (RistrettoPoint, RistrettoPoint),
+    EncBucket: (RistrettoPoint, RistrettoPoint),
+    EncLevel: (RistrettoPoint, RistrettoPoint),
+    EncSince: (RistrettoPoint, RistrettoPoint),
+    EncInvRemain: (RistrettoPoint, RistrettoPoint),
+    EncBlockages: (RistrettoPoint, RistrettoPoint),
+    EncInvIdClient: (RistrettoPoint, RistrettoPoint),
+    id_client: Scalar,
+    bucket: Scalar,
+    level: Scalar,
+    since: Scalar,
+    invremain: Scalar,
+    blockages: Scalar,
+    inv_id_client: Scalar,
+}
+
+pub struct Response {
+    // The fields for the new Lox credential; the new invites_remaining
+    // is one less than the old value, so we don't have to include it
+    // here explicitly
+    P: RistrettoPoint,
+    EncQ: (RistrettoPoint, RistrettoPoint),
+    id_server: Scalar,
+    TId: RistrettoPoint,
+    TBucket: RistrettoPoint,
+    TLevel: RistrettoPoint,
+    TSince: RistrettoPoint,
+    TInvRemain: RistrettoPoint,
+    TBlockages: RistrettoPoint,
+    inv_id_server: Scalar,
+    TInvId: RistrettoPoint,
+
+    // The ZKP
+    piBlindIssue: CompactProof,
+}
+
+define_proof! {
+    requestproof,
+    "Issue Invite Request",
+    (bucket, level, since, invremain, blockages, zbucket, zlevel,
+     zsince, zinvremain, zblockages, negzQ,
+     zbucket_reach, negzQ_reach,
+     d, eid_client, ebucket, elevel, esince, einvremain, eblockages, id_client,
+     inv_id_client, einv_id_client,
+     invremain_inverse, zinvremain_inverse),
+    (P, CBucket, CLevel, CSince, CInvRemain, CBlockages, V, Xbucket,
+     Xlevel, Xsince, Xinvremain, Xblockages,
+     P_reach, CBucket_reach, V_reach, Xbucket_reach,
+     D, EncIdClient0, EncIdClient1, EncBucket0, EncBucket1,
+     EncLevel0, EncLevel1, EncSince0, EncSince1,
+     EncInvRemain0, EncInvRemain1_plus_B, EncBlockages0, EncBlockages1,
+     EncInvIdClient0, EncInvIdClient1),
+    (A, B):
+    // Blind showing of the Lox credential
+    CBucket = (bucket*P + zbucket*A),
+    CLevel = (level*P + zlevel*A),
+    CSince = (since*P + zsince*A),
+    CInvRemain = (invremain*P + zinvremain*A),
+    CBlockages = (blockages*P + zblockages*A),
+    // Proof that invremain is not 0
+    P = (invremain_inverse*CInvRemain + zinvremain_inverse*A),
+    // Blind showing of the Bucket Reachability credential; note the
+    // same bucket is used in the proof
+    CBucket_reach = (bucket*P_reach + zbucket_reach*A),
+    // User blinding of the Lox credential to be issued
+    D = (d*B),
+    EncIdClient0 = (eid_client*B),
+    EncIdClient1 = (id_client*B + eid_client*D),
+    EncBucket0 = (ebucket*B),
+    EncBucket1 = (bucket*B + ebucket*D),
+    EncLevel0 = (elevel*B),
+    EncLevel1 = (level*B + elevel*D),
+    EncSince0 = (esince*B),
+    EncSince1 = (since*B + esince*D),
+    EncInvRemain0 = (einvremain*B),
+    EncInvRemain1_plus_B = (invremain*B + einvremain*D),
+    EncBlockages0 = (eblockages*B),
+    EncBlockages1 = (blockages*B + eblockages*D),
+    // User blinding of the Invitation to be issued
+    EncInvIdClient0 = (einv_id_client*B),
+    EncInvIdClient1 = (inv_id_client*B + einv_id_client*D)
+}
+
+pub fn request(
+    lox_cred: &cred::Lox,
+    reach_cred: &cred::BucketReachability,
+    lox_pub: &IssuerPubKey,
+    reach_pub: &IssuerPubKey,
+    today: u32,
+) -> 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 invites_remaining not be 0
+    if lox_cred.invites_remaining == Scalar::zero() {
+        return Err(ProofError::VerificationFailure);
+    }
+    // The buckets in the Lox and Bucket Reachability credentials have
+    // to match
+    if lox_cred.bucket != reach_cred.bucket {
+        return Err(ProofError::VerificationFailure);
+    }
+    // The Bucket Reachability credential has to be dated today
+    let reach_date: u32 = match scalar_u32(&reach_cred.date) {
+        Some(v) => v,
+        None => return Err(ProofError::VerificationFailure),
+    };
+    if reach_date != today {
+        return Err(ProofError::VerificationFailure);
+    }
+    // The new invites_remaining
+    let new_invites_remaining = &lox_cred.invites_remaining - Scalar::one();
+
+    // 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 zlevel = 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 CLevel = lox_cred.bucket * P + &zlevel * 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]
+        + zlevel * lox_pub.X[3]
+        + zsince * lox_pub.X[4]
+        + zinvremain * lox_pub.X[5]
+        + zblockages * lox_pub.X[6]
+        + &negzQ * Atable;
+
+    // Blind showing the Bucket Reachability credential
+
+    // Reblind P and Q
+    let t_reach = Scalar::random(&mut rng);
+    let P_reach = t_reach * reach_cred.P;
+    let Q_reach = t_reach * reach_cred.Q;
+
+    // Form Pedersen commitments to the blinded attributes
+    let zbucket_reach = Scalar::random(&mut rng);
+    let CBucket_reach = reach_cred.bucket * P_reach + &zbucket_reach * 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_reach = Scalar::random(&mut rng);
+    let CQ_reach = Q_reach - &negzQ_reach * Atable;
+
+    // Compute the "error factor"
+    let V_reach = zbucket_reach * reach_pub.X[2] + &negzQ_reach * Atable;
+
+    // User blinding for the Lox certificate to be issued
+
+    // Pick an ElGamal keypair
+    let d = Scalar::random(&mut rng);
+    let D = &d * Btable;
+
+    // Pick a random client component of the id
+    let id_client = Scalar::random(&mut rng);
+
+    // Encrypt it (times the basepoint B) to the ElGamal public key D we
+    // just created
+    let eid_client = Scalar::random(&mut rng);
+    let EncIdClient = (&eid_client * Btable, &id_client * Btable + eid_client * D);
+
+    // 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 elevel = Scalar::random(&mut rng);
+    let EncLevel = (
+        &elevel * Btable,
+        &lox_cred.trust_level * Btable + elevel * D,
+    );
+    let esince = Scalar::random(&mut rng);
+    let EncSince = (
+        &esince * Btable,
+        &lox_cred.level_since * Btable + esince * D,
+    );
+    let einvremain = Scalar::random(&mut rng);
+    let EncInvRemain = (
+        &einvremain * Btable,
+        &new_invites_remaining * Btable + einvremain * D,
+    );
+    let eblockages = Scalar::random(&mut rng);
+    let EncBlockages = (
+        &eblockages * Btable,
+        &lox_cred.blockages * Btable + eblockages * D,
+    );
+
+    // User blinding for the Invitation certificate to be issued
+
+    // Pick a random client component of the id
+    let inv_id_client = Scalar::random(&mut rng);
+
+    // Encrypt it (times the basepoint B) to the ElGamal public key D we
+    // just created
+    let einv_id_client = Scalar::random(&mut rng);
+    let EncInvIdClient = (
+        &einv_id_client * Btable,
+        &id_client * Btable + einv_id_client * D,
+    );
+
+    // The proof that invites_remaining is not zero.  We prove this by
+    // demonstrating that we know its inverse.
+    let invremain_inverse = &lox_cred.invites_remaining.invert();
+
+    let zinvremain_inverse = -zinvremain * invremain_inverse;
+
+    // So now invremain_inverse * CInvRemain + zinvremain_inverse * A = P
+
+    // Construct the proof
+    let mut transcript = Transcript::new(b"issue invite request");
+    let piUser = requestproof::prove_compact(
+        &mut transcript,
+        requestproof::ProveAssignments {
+            A: &A,
+            B: &B,
+            P: &P,
+            CBucket: &CBucket,
+            CLevel: &CLevel,
+            CSince: &CSince,
+            CInvRemain: &CInvRemain,
+            CBlockages: &CBlockages,
+            V: &V,
+            Xbucket: &lox_pub.X[2],
+            Xlevel: &lox_pub.X[3],
+            Xsince: &lox_pub.X[4],
+            Xinvremain: &lox_pub.X[5],
+            Xblockages: &lox_pub.X[6],
+            P_reach: &P_reach,
+            CBucket_reach: &CBucket_reach,
+            V_reach: &V_reach,
+            Xbucket_reach: &reach_pub.X[2],
+            D: &D,
+            EncIdClient0: &EncIdClient.0,
+            EncIdClient1: &EncIdClient.1,
+            EncBucket0: &EncBucket.0,
+            EncBucket1: &EncBucket.1,
+            EncLevel0: &EncLevel.0,
+            EncLevel1: &EncLevel.1,
+            EncSince0: &EncSince.0,
+            EncSince1: &EncSince.1,
+            EncInvRemain0: &EncInvRemain.0,
+            EncInvRemain1_plus_B: &(EncInvRemain.1 + B),
+            EncBlockages0: &EncBlockages.0,
+            EncBlockages1: &EncBlockages.1,
+            EncInvIdClient0: &EncInvIdClient.0,
+            EncInvIdClient1: &EncInvIdClient.1,
+            bucket: &lox_cred.bucket,
+            level: &lox_cred.trust_level,
+            since: &lox_cred.level_since,
+            invremain: &lox_cred.invites_remaining,
+            blockages: &lox_cred.blockages,
+            zbucket: &zbucket,
+            zlevel: &zlevel,
+            zsince: &zsince,
+            zinvremain: &zinvremain,
+            zblockages: &zblockages,
+            negzQ: &negzQ,
+            zbucket_reach: &zbucket_reach,
+            negzQ_reach: &negzQ_reach,
+            d: &d,
+            eid_client: &eid_client,
+            ebucket: &ebucket,
+            elevel: &elevel,
+            esince: &esince,
+            einvremain: &einvremain,
+            eblockages: &eblockages,
+            id_client: &id_client,
+            inv_id_client: &inv_id_client,
+            einv_id_client: &einv_id_client,
+            invremain_inverse: &invremain_inverse,
+            zinvremain_inverse: &zinvremain_inverse,
+        },
+    )
+    .0;
+
+    Ok((
+        Request {
+            P,
+            id: lox_cred.id,
+            CBucket,
+            CLevel,
+            CSince,
+            CInvRemain,
+            CBlockages,
+            CQ,
+            P_reach,
+            CBucket_reach,
+            CQ_reach,
+            D,
+            EncIdClient,
+            EncBucket,
+            EncLevel,
+            EncSince,
+            EncInvRemain,
+            EncBlockages,
+            EncInvIdClient,
+            piUser,
+        },
+        State {
+            d,
+            D,
+            EncIdClient,
+            EncBucket,
+            EncLevel,
+            EncSince,
+            EncInvRemain,
+            EncBlockages,
+            EncInvIdClient,
+            id_client,
+            bucket: lox_cred.bucket,
+            level: lox_cred.trust_level,
+            since: lox_cred.level_since,
+            invremain: new_invites_remaining,
+            blockages: lox_cred.blockages,
+            inv_id_client,
+        },
+    ))
+}

+ 4 - 2
src/proto/level_up.rs

@@ -76,6 +76,8 @@ pub const LEVEL_INVITATIONS: [u32; MAX_LEVEL + 1] = [0, 2, 4, 6, 8];
 /// blockages this credential is allowed to have recorded in order to
 /// advance from level i to level i+1.  Again the LEVEL_INVITATIONS\[0\]
 /// entry is a dummy, as for LEVEL_INTERVAL.
+// If you change this to have a number greater than 7, you need to add
+// one or more bits to the ZKP.
 pub const MAX_BLOCKAGES: [u32; MAX_LEVEL + 1] = [0, 4, 3, 2, 2];
 
 pub struct Request {
@@ -399,7 +401,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[new_level as usize].into();
+    let newinvites: Scalar = LEVEL_INVITATIONS[trust_level as usize].into();
     let eblockages = Scalar::random(&mut rng);
     let EncBlockages = (
         &eblockages * Btable,
@@ -830,7 +832,7 @@ impl BridgeAuth {
 
         // 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();
+        let invitations_remaining: Scalar = LEVEL_INVITATIONS[level].into();
 
         // Compute the MAC on the visible attributes
         let b = Scalar::random(&mut rng);

+ 32 - 0
src/tests.rs

@@ -200,3 +200,35 @@ fn test_level_up() {
     println!("cred4 = {:?}", cred4);
     assert!(ba.verify_lox(&cred4));
 }
+
+#[test]
+fn test_issue_inv() {
+    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));
+
+    // Read the bucket in the credential to get today's Bucket
+    // Reachability credential
+    let (id, key) = bridge_table::from_scalar(cred2.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();
+
+    let (req, state) = issue_invite::request(
+        &cred2,
+        &reachcred,
+        &ba.lox_pub,
+        &ba.reachability_pub,
+        ba.today(),
+    )
+    .unwrap();
+}