浏览代码

The response message of the open invitation protocol

Ian Goldberg 3 年之前
父节点
当前提交
e8309c43f7
共有 3 个文件被更改,包括 232 次插入7 次删除
  1. 22 4
      src/bridge_table.rs
  2. 11 2
      src/lib.rs
  3. 199 1
      src/open_invite.rs

+ 22 - 4
src/bridge_table.rs

@@ -10,6 +10,7 @@
 use aes_gcm::aead;
 use aes_gcm::aead::{generic_array::GenericArray, Aead, NewAead};
 use aes_gcm::Aes128Gcm;
+use curve25519_dalek::scalar::Scalar;
 use rand::RngCore;
 use std::convert::TryInto;
 
@@ -128,15 +129,20 @@ impl BridgeLine {
 /// decryption key for one bucket.
 #[derive(Debug, Default)]
 pub struct BridgeTable {
-    keys: Vec<[u8; 16]>,
-    buckets: Vec<[BridgeLine; MAX_BRIDGES_PER_BUCKET]>,
-    encbuckets: Vec<[u8; ENC_BUCKET_BYTES]>,
+    pub keys: Vec<[u8; 16]>,
+    pub buckets: Vec<[BridgeLine; MAX_BRIDGES_PER_BUCKET]>,
+    pub encbuckets: Vec<[u8; ENC_BUCKET_BYTES]>,
 }
 
 // Invariant: the lengths of the keys and buckets vectors are the same.
 // The encbuckets vector only gets updated when encrypt_table is called.
 
 impl BridgeTable {
+    /// Get the number of buckets in the bridge table
+    pub fn num_buckets(&self) -> usize {
+        self.buckets.len()
+    }
+
     /// Append a new bucket to the bridge table
     pub fn new_bucket(&mut self, bucket: [BridgeLine; MAX_BRIDGES_PER_BUCKET]) {
         // Pick a random key to encrypt this bucket
@@ -190,7 +196,8 @@ impl BridgeTable {
     }
 }
 
-// Unit tests that require access to private fields
+// Unit tests that require access to the testing-only function
+// BridgeLine::random()
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -234,3 +241,14 @@ mod tests {
         Ok(())
     }
 }
+
+/// Convert an id and key to a Scalar attribute
+pub fn to_scalar(id: u32, key: [u8; 16]) -> Scalar {
+    let mut b: [u8; 32] = [0; 32];
+    // b is a little-endian representation of the Scalar; put the key in
+    // the low 16 bytes, and the id in the next 4 bytes.
+    b[0..16].copy_from_slice(&key);
+    b[16..20].copy_from_slice(&id.to_le_bytes());
+    // This cannot fail, since we're only using the low 20 bytes of b
+    Scalar::from_canonical_bytes(b).unwrap()
+}

+ 11 - 2
src/lib.rs

@@ -182,6 +182,9 @@ pub struct BridgeAuth {
     /// The public key of the BridgeDb issuing open invitations
     pub bridgedb_pub: PublicKey,
 
+    /// The bridge table
+    bridge_table: bridge_table::BridgeTable,
+
     /// Duplicate filter for open invitations
     openinv_filter: dup_filter::DupFilter<Scalar>,
     /// Duplicate filter for credential ids
@@ -203,6 +206,7 @@ impl BridgeAuth {
             migration_priv,
             migration_pub,
             bridgedb_pub,
+            bridge_table: Default::default(),
             openinv_filter: Default::default(),
             id_filter: Default::default(),
             time_offset: time::Duration::zero(),
@@ -221,9 +225,14 @@ impl BridgeAuth {
     }
 
     /// Get today's (real or simulated) date
-    fn today(&self) -> i64 {
-        (time::OffsetDateTime::now_utc().date() + self.time_offset).julian_day()
+    fn today(&self) -> u64 {
+        // We will not encounter negative Julian dates (~6700 years ago)
+        (time::OffsetDateTime::now_utc().date() + self.time_offset)
+            .julian_day()
+            .try_into()
+            .unwrap()
     }
 }
 
+// The protocol modules
 pub mod open_invite;

+ 199 - 1
src/open_invite.rs

@@ -14,12 +14,13 @@ credential.  The credential will have attributes:
 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::bridge_table;
+use super::dup_filter::SeenType;
 use super::{BridgeAuth, IssuerPubKey};
 use super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
 
@@ -51,6 +52,7 @@ pub struct Response {
     P_noopmigration: RistrettoPoint,
     EncQ_noopmigration: (RistrettoPoint, RistrettoPoint),
     TId_noopmigration: RistrettoPoint,
+    piBlindIssue: CompactProof,
 }
 
 // The userblinding ZKP
@@ -65,6 +67,36 @@ define_proof! {
     D = (d*B)
 }
 
+// The issuing ZKP
+define_proof! {
+    blindissue,
+    "Open Invitation Blind Issuing",
+    (x0, x0tilde, xid, xbucket, xsince, s, b, tid,
+     x0_nm, x0tilde_nm, xid_nm, xfrom_nm, xto_nm, s_nm, b_nm, tid_nm),
+    (P, EncQ0, EncQ1, X0, Xid, Xbucket, Xsince, Pbucket, Psince, TId,
+     P_nm, EncQ0_nm, EncQ1_nm, X0_nm, Xid_nm, Xfrom_nm, Xto_nm, TId_nm,
+     D, EncId0, EncId1),
+    (A, B) :
+    Xid = (xid*A),
+    Xbucket = (xbucket*A),
+    Xsince = (xsince*A),
+    X0 = (x0*B + x0tilde*A),
+    P = (b*B),
+    TId = (b*Xid),
+    TId = (tid*A),
+    EncQ0 = (s*B + tid*EncId0),
+    EncQ1 = (s*D + tid*EncId1 + x0*P + xbucket*Pbucket + xsince*Psince),
+    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),
+    EncQ0_nm = (s_nm*B + tid_nm*EncId0),
+    EncQ1_nm = (s_nm*D + tid_nm*EncId1 + x0_nm*P_nm + xfrom_nm*Pbucket + xto_nm*Pbucket)
+}
+
 /// Submit an open invitation issued by the BridgeDb to receive your
 /// first Lox credential
 pub fn request(invite: &[u8; super::OPENINV_LENGTH]) -> (Request, State) {
@@ -114,3 +146,169 @@ pub fn request(invite: &[u8; super::OPENINV_LENGTH]) -> (Request, State) {
         },
     )
 }
+
+impl BridgeAuth {
+    /// Receive an open invitation issued by the BridgeDb and if it is
+    /// valid and fresh, issue a Lox credential at trust level 0.
+    pub fn handle_open_invite(&mut self, req: Request) -> Result<Response, ProofError> {
+        // Check the signature on the open_invite.  We manually match
+        // here because we're changing the Err type from SignatureError
+        // to ProofError
+        let (invite_id, bucket_id_u32) =
+            match super::BridgeDb::verify(req.invite, self.bridgedb_pub) {
+                Ok(res) => res,
+                Err(_) => return Err(ProofError::VerificationFailure),
+            };
+        let bucket_id: usize = bucket_id_u32 as usize;
+
+        // Only proceed if the invite_id is fresh
+        if self.openinv_filter.filter(&invite_id) == SeenType::Seen {
+            return Err(ProofError::VerificationFailure);
+        }
+
+        // And also check that the bucket id is valid
+        if bucket_id >= self.bridge_table.num_buckets() {
+            return Err(ProofError::VerificationFailure);
+        }
+
+        let A: &RistrettoPoint = &CMZ_A;
+        let B: &RistrettoPoint = &CMZ_B;
+        let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
+        let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
+
+        // Next check the proof in the request
+        let mut transcript = Transcript::new(b"open invite user blinding");
+        userblinding::verify_compact(
+            &req.piUserBlinding,
+            &mut transcript,
+            userblinding::VerifyAssignments {
+                B: &B.compress(),
+                EncIdClient0: &req.EncIdClient.0.compress(),
+                EncIdClient1: &req.EncIdClient.1.compress(),
+                D: &req.D.compress(),
+            },
+        )?;
+
+        // 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 bucket attribute (Scalar), which is a combination
+        // of the bucket id (u32) and the bucket's decryption key ([u8; 16])
+        let bucket_key = self.bridge_table.keys[bucket_id];
+        let bucket: Scalar = bridge_table::to_scalar(bucket_id_u32, bucket_key);
+
+        // Create the level_since attribute (Scalar), which is today's
+        // Julian date
+        let level_since: Scalar = self.today().into();
+
+        // Compute the MAC on the visible attributes
+        let b = Scalar::random(&mut rng);
+        let P = &b * Btable;
+        // trust_level = invites_remaining = invites_issued = 0
+        let QHc =
+            (self.lox_priv.x[0] + self.lox_priv.x[2] * bucket + self.lox_priv.x[4] * level_since)
+                * 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 id attribute
+        let tid = self.lox_priv.x[1] * b;
+        let TId = &tid * Atable;
+        let EncQId = (tid * EncId.0, tid * EncId.1);
+
+        let EncQ = (EncQHc.0 + EncQId.0, EncQHc.1 + EncQId.1);
+
+        // Now the no-op migration credential
+        // Compute the MAC on the visible attributes
+        let b_noopmigration = Scalar::random(&mut rng);
+        let P_noopmigration = &b_noopmigration * Btable;
+        let QHc_noopmigration = (self.migration_priv.x[0]
+            + self.migration_priv.x[2] * bucket
+            + self.migration_priv.x[3] * bucket)
+            * P;
+
+        // El Gamal encrypt it to the public key req.D
+        let s_noopmigration = Scalar::random(&mut rng);
+        let EncQHc_noopmigration = (
+            &s_noopmigration * Btable,
+            QHc_noopmigration + s_noopmigration * req.D,
+        );
+
+        // Homomorphically compute the part of the MAC corresponding to
+        // the blinded id attribute
+        let tid_noopmigration = self.migration_priv.x[1] * b_noopmigration;
+        let TId_noopmigration = &tid_noopmigration * Atable;
+        let EncQId_noopmigration = (tid_noopmigration * EncId.0, tid_noopmigration * EncId.1);
+
+        let EncQ_noopmigration = (
+            EncQHc_noopmigration.0 + EncQId_noopmigration.0,
+            EncQHc_noopmigration.1 + EncQId_noopmigration.1,
+        );
+
+        let mut transcript = Transcript::new(b"open 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],
+                Xsince: &self.lox_pub.X[4],
+                Pbucket: &(bucket * P),
+                Psince: &(level_since * P),
+                TId: &TId,
+                P_nm: &P_noopmigration,
+                EncQ0_nm: &EncQ_noopmigration.0,
+                EncQ1_nm: &EncQ_noopmigration.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_noopmigration,
+                D: &req.D,
+                EncId0: &EncId.0,
+                EncId1: &EncId.1,
+                x0: &self.lox_priv.x[0],
+                x0tilde: &self.lox_priv.x0tilde,
+                xid: &self.lox_priv.x[1],
+                xbucket: &self.lox_priv.x[2],
+                xsince: &self.lox_priv.x[4],
+                s: &s,
+                b: &b,
+                tid: &tid,
+                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_noopmigration,
+                b_nm: &b_noopmigration,
+                tid_nm: &tid_noopmigration,
+            },
+        )
+        .0;
+
+        Ok(Response {
+            P,
+            EncQ,
+            id_server,
+            TId,
+            bucket,
+            level_since,
+            P_noopmigration,
+            EncQ_noopmigration,
+            TId_noopmigration,
+            piBlindIssue,
+        })
+    }
+}