A simple BridgeDb to create open invitations
This commit is contained in:
commit
e079a361ba
|
@ -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"]
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue