Version 0.2.0

- Performance Improvements
- Removed KeyPair struct for a nicer API
This commit is contained in:
Sarah Jamie Lewis 2021-02-01 14:20:00 -08:00
parent e383dd8378
commit c5bb0e2c88
4 changed files with 116 additions and 116 deletions

View File

@ -1,7 +1,7 @@
[package]
name = "fuzzytags"
description = "a probabilistic cryptographic structure for metadata resistant tagging"
version = "0.1.4"
version = "0.2.0"
repository = "https://git.openprivacy.ca/openprivacy/fuzzytags"
authors = ["Sarah Jamie Lewis <sarah@openprivacy.ca>"]
edition = "2018"

View File

@ -68,9 +68,9 @@ provided them (depending on the efficiency of the decryption method).
Generate a key pair:
use fuzzytags::FuzzyTagKeyPair;
use fuzzytags::FuzzySecretKey;
let gamma = 24;
let keypair = FuzzyTagKeyPair::generate(gamma);
let secret_key = FuzzySecretKey::generate(gamma);
`key.public_key` can be given to parties who you want to be able to communicate with you over a specific anonymous
messaging service / privacy-preserving application.
@ -84,10 +84,10 @@ validate against a random public key with a maximum probability of _2^-gamma_.
Once in possession of a public key, a party in a metadata resistant app can use it to generate tags:
use fuzzytags::FuzzyTagKeyPair;
use fuzzytags::FuzzySecretKey;
let gamma = 24;
let keypair = FuzzyTagKeyPair::generate(gamma);
let public_key = keypair.public_key.clone();
let secret_key = FuzzySecretKey::generate(gamma);
let public_key = secret_key.public_key();
// Give public key to a another party...
// and then they can do...
@ -101,12 +101,12 @@ First it is necessary to extract a detection key for a given false positive prob
This extracted key can then be given to an adversarial server. The server can then test a given tag against the detection key e.g.:
use fuzzytags::FuzzyTagKeyPair;
use fuzzytags::FuzzySecretKey;
let gamma = 24;
let keypair = FuzzyTagKeyPair::generate(gamma);
let public_key = keypair.public_key.clone();
let secret_key = FuzzySecretKey::generate(gamma);
let public_key = secret_key.public_key();
// extract a detection key
let detection_key = keypair.extract(5);
let detection_key = secret_key.extract(5);
// Give public key to a another party...
// and then they can do...
@ -129,3 +129,7 @@ Results will be in `target/criterion/report/index.html`.
For more guidance on integrating fuzzytags into a privacy preserving application see [documentation](https://docs.rs/fuzzytags/#integrating-fuzzytags)
## Credits and Contributions
- Based on [Fuzzy Message Detection](https://eprint.iacr.org/2021/089) by Gabrielle Beck and Julia Len and Ian Miers and Matthew Green
- Performance & API improvements contributed by Henry de Valence

View File

@ -1,23 +1,26 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use fuzzytags::FuzzyTagKeyPair;
use fuzzytags::FuzzySecretKey;
use std::time::Duration;
fn benchmark_generate_tag(c: &mut Criterion) {
let mut group = c.benchmark_group("generate_tags");
group.measurement_time(Duration::new(10, 0));
let key = FuzzyTagKeyPair::generate(24);
let secret_key = FuzzySecretKey::generate(24);
for p in [5, 10, 15].iter() {
group.bench_with_input(BenchmarkId::from_parameter(p), p, |b, _gamma| b.iter(|| key.public_key.generate_tag()));
let public_key = secret_key.public_key();
group.bench_with_input(BenchmarkId::from_parameter(p), p, |b, _gamma| b.iter(|| public_key.generate_tag()));
}
}
fn benchmark_test_tag(c: &mut Criterion) {
let mut group = c.benchmark_group("test_tags");
group.measurement_time(Duration::new(10, 0));
let key = FuzzyTagKeyPair::generate(24);
group.sample_size(500);
let secret_key = FuzzySecretKey::generate(24);
for p in [5, 10, 15].iter() {
let tag = key.public_key.generate_tag();
let detection_key = key.extract(*p);
let tag = secret_key.public_key().generate_tag();
let detection_key = secret_key.extract(*p);
group.bench_with_input(BenchmarkId::from_parameter(p), p, |b, _gamma| b.iter(|| detection_key.test_tag(&tag)));
}
}

View File

@ -46,20 +46,79 @@ impl Display for FuzzyTag {
/// The complete secret key. Can't directly be used for testing. Instead you will need to generate
/// a FuzzyDetectionKey using extract
#[derive(Debug, Serialize, Deserialize)]
pub struct FuzzySecretKey(Vec<Scalar>);
pub struct FuzzySecretKey {
/// the detection key - this can be given to adversarial servers to help probabilistically
/// filter messages (with a false-positive rate derived from γ and a 0% false negative rate)
secret_key: Vec<Scalar>,
/// the public key - this can be given to people who you want to contact you
public_key: FuzzyPublicKey,
}
impl FuzzySecretKey {
/// Generate a new Key Pair given a security parameter `gamma`. Tags generated for a given
/// `FuzzyPublicKey::flag` will pass the `FuzzyDetectionKey::test` for other public
/// keys with probability $ 2 ^ -8 $
/// Example:
/// ```
/// use fuzzytags::{FuzzySecretKey};
/// let secret_key = FuzzySecretKey::generate(5);
/// ```
pub fn generate(gamma: usize) -> FuzzySecretKey {
let mut rng = OsRng::default();
let g = RISTRETTO_BASEPOINT_POINT;
let mut secret_key = vec![];
let mut p_keys = vec![];
for _i in 0..gamma {
let sk_i = Scalar::random(&mut rng);
let pk_i = g.mul(sk_i);
secret_key.push(sk_i);
p_keys.push(pk_i);
}
FuzzySecretKey {
secret_key,
public_key: FuzzyPublicKey { 0: p_keys },
}
}
/// extract a detection key for a given false positive (p = 2^-n)
/// Example:
/// ```
/// use fuzzytags::{FuzzyTagKeyPair};
/// let keypair = FuzzyTagKeyPair::generate(24);
/// let detection_key = keypair.extract(2);
/// use fuzzytags::{FuzzySecretKey};
/// let secret_key = FuzzySecretKey::generate(24);
/// let detection_key = secret_key.extract(2);
/// ```
pub fn extract(&self, n: usize) -> FuzzyDetectionKey {
let parts = self.0.iter().take(n).cloned().collect();
let parts = self.secret_key.iter().take(n).cloned().collect();
FuzzyDetectionKey { 0: parts }
}
/// derive the public key for this key
/// Example:
/// ```
/// use fuzzytags::FuzzySecretKey;
/// let secret_key = FuzzySecretKey::generate(24);
/// let public_key = secret_key.public_key();
/// ```
pub fn public_key(&self) -> FuzzyPublicKey {
self.public_key.clone()
}
/// a hash function that takes 3 ristretto points as a parameter and outputs 0 or 1.
fn h(u: RistrettoPoint, h: RistrettoPoint, w: RistrettoPoint) -> u8 {
let mut hash = sha3::Sha3_256::new();
hash.update(u.compress().as_bytes());
hash.update(h.compress().as_bytes());
hash.update(w.compress().as_bytes());
return hash.finalize().as_slice()[0] & 0x01;
}
/// a hash function which takes a ristretto point and a vector of ciphertexts and outputs a
/// ristretto scalar.
fn g(u: RistrettoPoint, points: &BitVec) -> Scalar {
let mut input = points.to_bytes().as_slice().to_vec();
input.extend_from_slice(u.compress().as_bytes());
Scalar::hash_from_bytes::<Sha3_512>(input.as_slice())
}
}
/// A collection of "secret" data that can be used to determine if a `FuzzyTag` was intended for
@ -88,12 +147,12 @@ impl Display for FuzzyDetectionKey {
impl FuzzyDetectionKey {
/// calculate the ideal false positive rate of this detection key
/// ```
/// use fuzzytags::FuzzyTagKeyPair;
/// use fuzzytags::FuzzySecretKey;
/// let gamma = 24;
/// let keypair = FuzzyTagKeyPair::generate(gamma);
/// let public_key = keypair.public_key.clone();
/// let secret_key = FuzzySecretKey::generate(gamma);
/// let public_key = secret_key.public_key();
/// // extract a detection key
/// let detection_key = keypair.extract(5);
/// let detection_key = secret_key.extract(5);
/// detection_key.false_positive_probability();
/// ```
pub fn false_positive_probability(&self) -> f64 {
@ -103,12 +162,12 @@ impl FuzzyDetectionKey {
/// returns true if the tag was intended for this key
/// Example:
/// ```
/// use fuzzytags::FuzzyTagKeyPair;
/// use fuzzytags::FuzzySecretKey;
/// let gamma = 24;
/// let keypair = FuzzyTagKeyPair::generate(gamma);
/// let public_key = keypair.public_key.clone();
/// let secret_key = FuzzySecretKey::generate(gamma);
/// let public_key = secret_key.public_key();
/// // extract a detection key
/// let detection_key = keypair.extract(5);
/// let detection_key = secret_key.extract(5);
///
/// // Give public key to a another party...
/// // and then they can do...
@ -122,7 +181,7 @@ impl FuzzyDetectionKey {
/// }
/// ```
pub fn test_tag(&self, tag: &FuzzyTag) -> bool {
let m = FuzzyTagKeyPair::g(tag.u, &tag.ciphertexts);
let m = FuzzySecretKey::g(tag.u, &tag.ciphertexts);
let g = RISTRETTO_BASEPOINT_POINT;
@ -140,7 +199,7 @@ impl FuzzyDetectionKey {
let mut result = true;
for (i, x_i) in self.0.iter().enumerate() {
// re-derive the key from the tag
let k_i = FuzzyTagKeyPair::h(tag.u, tag.u.mul(x_i), w);
let k_i = FuzzySecretKey::h(tag.u, tag.u.mul(x_i), w);
// calculate the "original" plaintext
let c_i = match tag.ciphertexts.get(i) {
@ -178,9 +237,9 @@ impl FuzzyPublicKey {
/// generate a new tag for this public key
/// Example:
/// ```
/// use fuzzytags::{FuzzyTagKeyPair};
/// let keypair = FuzzyTagKeyPair::generate(24);
/// let public_key = keypair.public_key.clone(); // give this to a sender
/// use fuzzytags::{FuzzySecretKey};
/// let secret_key = FuzzySecretKey::generate(24);
/// let public_key = secret_key.public_key(); // give this to a sender
/// let tag = public_key.generate_tag();
/// ```
pub fn generate_tag(&self) -> FuzzyTag {
@ -196,7 +255,7 @@ impl FuzzyPublicKey {
// construct the ciphertext portion of the tag
let mut ciphertexts = BitVec::new();
for (_i, h_i) in self.0.iter().enumerate() {
let k_i = FuzzyTagKeyPair::h(u, h_i.mul(r), w);
let k_i = FuzzySecretKey::h(u, h_i.mul(r), w);
// encrypt a plaintext of all 1's
let c_i = k_i ^ 0x01;
ciphertexts.push(c_i == 0x01);
@ -217,88 +276,22 @@ impl FuzzyPublicKey {
// used to derive the key.
// finally calculate a `y` = 1/r * (z-m) which will be used to re-derive `w`
let m = FuzzyTagKeyPair::g(u, &ciphertexts);
let m = FuzzySecretKey::g(u, &ciphertexts);
let y = r.invert().mul(z.sub(m));
return FuzzyTag { u, y, ciphertexts };
}
}
/// An identity keypair for generating and validating fuzzy meta tags.
#[derive(Debug, Serialize, Deserialize)]
pub struct FuzzyTagKeyPair {
/// the detection key - this can be given to adversarial servers to help probabilistically
/// filter messages (with a false-positive rate derived from γ and a 0% false negative rate)
pub secret_key: FuzzySecretKey,
/// the public key - this can be given to people who you want to contact you
pub public_key: FuzzyPublicKey,
}
impl FuzzyTagKeyPair {
/// Generate a new Key Pair given a security parameter `gamma`. Tags generated for a given
/// `FuzzyPublicKey::flag` will pass the `FuzzyDetectionKey::test` for other public
/// keys with probability $ 2 ^ -8 $
/// Example:
/// ```
/// use fuzzytags::{FuzzyTagKeyPair};
/// let keypair = FuzzyTagKeyPair::generate(5);
/// ```
pub fn generate(gamma: usize) -> FuzzyTagKeyPair {
let mut rng = OsRng::default();
let g = RISTRETTO_BASEPOINT_POINT;
let mut s_keys = vec![];
let mut p_keys = vec![];
for _i in 0..gamma {
let sk_i = Scalar::random(&mut rng);
let pk_i = g.mul(sk_i);
s_keys.push(sk_i);
p_keys.push(pk_i);
}
FuzzyTagKeyPair {
secret_key: FuzzySecretKey { 0: s_keys },
public_key: FuzzyPublicKey { 0: p_keys },
}
}
/// extract a detection key for a given false positive (p = 2^-n)
/// a facade for an extraction of the encapsulated secret key
/// Example:
/// ```
/// use fuzzytags::{FuzzyTagKeyPair};
/// let keypair = FuzzyTagKeyPair::generate(24);
/// let detection_key = keypair.extract(2);
/// ```
pub fn extract(&self, n: usize) -> FuzzyDetectionKey {
self.secret_key.extract(n)
}
/// a hash function that takes 3 ristretto points as a parameter and outputs 0 or 1.
fn h(u: RistrettoPoint, h: RistrettoPoint, w: RistrettoPoint) -> u8 {
let mut hash = sha3::Sha3_256::new();
hash.update(u.compress().as_bytes());
hash.update(h.compress().as_bytes());
hash.update(w.compress().as_bytes());
return hash.finalize().as_slice()[0] & 0x01;
}
/// a hash function which takes a ristretto point and a vector of ciphertexts and outputs a
/// ristretto scalar.
fn g(u: RistrettoPoint, points: &BitVec) -> Scalar {
let mut input = points.to_bytes().as_slice().to_vec();
input.extend_from_slice(u.compress().as_bytes());
Scalar::hash_from_bytes::<Sha3_512>(input.as_slice())
}
}
#[cfg(test)]
mod tests {
use crate::FuzzyTagKeyPair;
use crate::FuzzySecretKey;
#[test]
fn test_serialization() {
let key = FuzzyTagKeyPair::generate(24);
let tag = key.public_key.generate_tag();
let detection_key = key.extract(10);
let secret_key = FuzzySecretKey::generate(24);
let tag = secret_key.public_key.generate_tag();
let detection_key = secret_key.extract(10);
println!("{}", serde_json::to_string(&tag).unwrap());
println!("{}", serde_json::to_string(&detection_key).unwrap());
}
@ -306,31 +299,31 @@ mod tests {
#[test]
fn correctness() {
let number_of_messages = 100;
let key = FuzzyTagKeyPair::generate(16);
let secret_key = FuzzySecretKey::generate(16);
for i in 0..number_of_messages {
let tag = key.public_key.generate_tag();
let tag = secret_key.public_key().generate_tag();
println!("{}: {}", i, tag);
assert!(key.extract(5).test_tag(&tag));
assert!(secret_key.extract(5).test_tag(&tag));
}
}
#[test]
fn false_postives() {
fn false_positives() {
let gamma = 8;
let number_of_messages = 1000;
let key = FuzzyTagKeyPair::generate(gamma);
let secret_key = FuzzySecretKey::generate(gamma);
let mut false_positives = 0;
for _i in 0..number_of_messages {
let key2 = FuzzyTagKeyPair::generate(gamma);
let tag = key2.public_key.generate_tag();
assert!(key2.extract(3).test_tag(&tag));
if key.secret_key.extract(3).test_tag(&tag) == true {
let secret_key2 = FuzzySecretKey::generate(gamma);
let tag = secret_key2.public_key().generate_tag();
assert!(secret_key2.extract(3).test_tag(&tag));
if secret_key.extract(3).test_tag(&tag) == true {
false_positives += 1;
}
}
println!(
"Expected False Positive Rate: {}\nActual False Positive Rate: {}",
key.secret_key.extract(3).false_positive_probability(),
secret_key.extract(3).false_positive_probability(),
(false_positives as f64 / number_of_messages as f64)
);
}