commit e079a361ba84c2c5daba4e5c90ed7d89879f68a8 Author: Ian Goldberg Date: Mon Apr 26 22:44:59 2021 -0400 A simple BridgeDb to create open invitations diff --git a/crates/lox-library/Cargo.toml b/crates/lox-library/Cargo.toml new file mode 100644 index 0000000..5d4eab2 --- /dev/null +++ b/crates/lox-library/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "lox" +version = "0.1.0" +authors = ["Ian Goldberg "] +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"] diff --git a/crates/lox-library/src/lib.rs b/crates/lox-library/src/lib.rs new file mode 100644 index 0000000..ad541d7 --- /dev/null +++ b/crates/lox-library/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::(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, +} + +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 = 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, +} + +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 = 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)), + } + } +} diff --git a/crates/lox-library/tests/tests.rs b/crates/lox-library/tests/tests.rs new file mode 100644 index 0000000..1373662 --- /dev/null +++ b/crates/lox-library/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); +}