Version 0.2.0
- Performance Improvements - Removed KeyPair struct for a nicer API
This commit is contained in:
parent
e383dd8378
commit
c5bb0e2c88
|
@ -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"
|
||||
|
|
22
README.md
22
README.md
|
@ -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
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
|
193
src/lib.rs
193
src/lib.rs
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue