Przeglądaj źródła

A simple BridgeDb to create open invitations

Ian Goldberg 3 lat temu
commit
74a3fb0f0f
3 zmienionych plików z 196 dodań i 0 usunięć
  1. 22 0
      Cargo.toml
  2. 164 0
      src/lib.rs
  3. 10 0
      tests/tests.rs

+ 22 - 0
Cargo.toml

@@ -0,0 +1,22 @@
+[package]
+name = "lox"
+version = "0.1.0"
+authors = ["Ian Goldberg <iang@uwaterloo.ca>"]
+edition = "2018"
+
+[dependencies]
+curve25519-dalek = { package = "curve25519-dalek-ng", version = "3", default-features = false, features = ["serde", "std"] }
+ed25519-dalek = "1"
+zkp = "0.8"
+bincode = "1"
+rand = "0.7"
+serde = "1"
+sha2 = "0.9"
+lazy_static = "1"
+hex_fmt = "0.3"
+
+[features]
+default = ["u64_backend"]
+u32_backend = ["curve25519-dalek/u32_backend"]
+u64_backend = ["curve25519-dalek/u64_backend"]
+simd_backend = ["curve25519-dalek/simd_backend"]

+ 164 - 0
src/lib.rs

@@ -0,0 +1,164 @@
+/*! Implementation of a new style of bridge authority for Tor that
+allows users to invite other users, while protecting the social graph
+from the bridge authority itself.
+
+We use CMZ14 credentials (GGM version, which is more efficient, but
+makes a stronger security assumption): "Algebraic MACs and
+Keyed-Verification Anonymous Credentials" (Chase, Meiklejohn, and
+Zaverucha, CCS 2014)
+
+The notation follows that of the paper "Hyphae: Social Secret Sharing"
+(Lovecruft and de Valence, 2017), Section 4.  */
+
+// We really want points to be capital letters and scalars to be
+// lowercase letters
+#![allow(non_snake_case)]
+
+#[macro_use]
+extern crate zkp;
+
+use sha2::Sha512;
+
+use rand::rngs::OsRng;
+use rand::RngCore;
+use std::convert::{TryFrom, TryInto};
+
+use curve25519_dalek::constants as dalek_constants;
+use curve25519_dalek::ristretto::RistrettoBasepointTable;
+use curve25519_dalek::ristretto::RistrettoPoint;
+use curve25519_dalek::scalar::Scalar;
+
+use ed25519_dalek::{Keypair, PublicKey, Signature, SignatureError, Signer, Verifier};
+
+use lazy_static::lazy_static;
+
+lazy_static! {
+    pub static ref CMZ_A: RistrettoPoint =
+        RistrettoPoint::hash_from_bytes::<Sha512>(b"CMZ Generator A");
+    pub static ref CMZ_B: RistrettoPoint = dalek_constants::RISTRETTO_BASEPOINT_POINT;
+    pub static ref CMZ_A_TABLE: RistrettoBasepointTable = RistrettoBasepointTable::create(&CMZ_A);
+    pub static ref CMZ_B_TABLE: RistrettoBasepointTable =
+        dalek_constants::RISTRETTO_BASEPOINT_TABLE;
+}
+
+#[derive(Clone, Debug)]
+pub struct IssuerPrivKey {
+    x0tilde: Scalar,
+    x: Vec<Scalar>,
+}
+
+impl IssuerPrivKey {
+    /// Create an IssuerPrivKey for credentials with the given number of
+    /// attributes.
+    pub fn new(n: u16) -> IssuerPrivKey {
+        let mut rng = rand::thread_rng();
+        let x0tilde = Scalar::random(&mut rng);
+        let mut x: Vec<Scalar> = Vec::with_capacity((n + 1) as usize);
+
+        // Set x to a vector of n+1 random Scalars
+        x.resize_with((n + 1) as usize, || Scalar::random(&mut rng));
+
+        IssuerPrivKey { x0tilde, x }
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct IssuerPubKey {
+    X: Vec<RistrettoPoint>,
+}
+
+impl IssuerPubKey {
+    /// Create an IssuerPubKey from the corresponding IssuerPrivKey
+    pub fn new(privkey: &IssuerPrivKey) -> IssuerPubKey {
+        let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
+        let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
+        let n_plus_one = privkey.x.len();
+        let mut X: Vec<RistrettoPoint> = Vec::with_capacity(n_plus_one);
+
+        // The first element is a special case; it is
+        // X[0] = x0tilde*A + x[0]*B
+        X.push(&privkey.x0tilde * Atable + &privkey.x[0] * Btable);
+
+        // The other elements (1 through n) are X[i] = x[i]*A
+        for i in 1..n_plus_one {
+            X.push(&privkey.x[i] * Atable);
+        }
+        IssuerPubKey { X }
+    }
+}
+
+/// The BridgeDb.  This will typically be a singleton object.  The
+/// BridgeDb's role is simply to issue signed "open invitations" to
+/// people who are not yet part of the system.
+#[derive(Debug)]
+pub struct BridgeDb {
+    /// The keypair for signing open invitations
+    keypair: Keypair,
+    /// The public key for verifying open invitations
+    pub pubkey: PublicKey,
+    /// The number of open-invitation buckets
+    num_openinv_buckets: u32,
+}
+
+/// An open invitation is a [u8; OPENINV_LENGTH] where the first 32
+/// bytes are the serialization of a random Scalar (the invitation id),
+/// the next 4 bytes are a little-endian bucket number, and the last
+/// SIGNATURE_LENGTH bytes are the signature on the first 36 bytes.
+pub const OPENINV_LENGTH: usize = 32 // the length of the random
+                                     // invitation id (a Scalar)
+    + 4 // the length of the u32 for the bucket number
+    + ed25519_dalek::SIGNATURE_LENGTH; // the length of the signature
+
+impl BridgeDb {
+    /// Create the BridgeDb.
+    pub fn new(num_openinv_buckets: u32) -> BridgeDb {
+        let mut csprng = OsRng {};
+        let keypair = Keypair::generate(&mut csprng);
+        let pubkey = keypair.public;
+        BridgeDb {
+            keypair,
+            pubkey,
+            num_openinv_buckets,
+        }
+    }
+
+    /// Produce an open invitation.  In this example code, we just
+    /// choose a random open-invitation bucket.
+    pub fn invite(&self) -> [u8; OPENINV_LENGTH] {
+        let mut res: [u8; OPENINV_LENGTH] = [0; OPENINV_LENGTH];
+        let mut rng = rand::thread_rng();
+        // Choose a random invitation id (a Scalar) and serialize it
+        let id = Scalar::random(&mut rng);
+        res[0..32].copy_from_slice(&id.to_bytes());
+        // Choose a random bucket number (mod num_openinv_buckets) and
+        // serialize it
+        let bucket_num = rng.next_u32() % self.num_openinv_buckets;
+        res[32..(32 + 4)].copy_from_slice(&bucket_num.to_le_bytes());
+        // Sign the first 36 bytes and serialize it
+        let sig = self.keypair.sign(&res[0..(32 + 4)]);
+        res[(32 + 4)..].copy_from_slice(&sig.to_bytes());
+        res
+    }
+
+    /// Verify an open invitation.  Returns the invitation id and the
+    /// bucket number if the signature checked out.  It is up to the
+    /// caller to then check that the invitation id has not been used
+    /// before.
+    pub fn verify(
+        invitation: [u8; OPENINV_LENGTH],
+        pubkey: PublicKey,
+    ) -> Result<(Scalar, u32), SignatureError> {
+        // Pull out the signature and verify it
+        let sig = Signature::try_from(&invitation[(32 + 4)..])?;
+        pubkey.verify(&invitation[0..(32 + 4)], &sig)?;
+        // The signature passed.  Pull out the bucket number and then
+        // the invitation id
+        let bucket = u32::from_le_bytes(invitation[32..(32 + 4)].try_into().unwrap());
+        match Scalar::from_canonical_bytes(invitation[0..32].try_into().unwrap()) {
+            // It should never happen that there's a valid signature on
+            // an invalid serialization of a Scalar, but check anyway.
+            None => Err(SignatureError::new()),
+            Some(s) => Ok((s, bucket)),
+        }
+    }
+}

+ 10 - 0
tests/tests.rs

@@ -0,0 +1,10 @@
+use lox::BridgeDb;
+
+#[test]
+fn test_openinvite() {
+    let bdb = BridgeDb::new(20);
+    let inv = bdb.invite();
+    println!("{:?}", inv);
+    let res = BridgeDb::verify(inv, bdb.pubkey);
+    println!("{:?}", res);
+}