diff --git a/Cargo.toml b/Cargo.toml index 4dd5d70..ce3e028 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 "] edition = "2018" diff --git a/README.md b/README.md index 17f5b62..e780914 100644 --- a/README.md +++ b/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 \ No newline at end of file diff --git a/benches/fuzzy_tags_benches.rs b/benches/fuzzy_tags_benches.rs index f23f074..b4892ee 100644 --- a/benches/fuzzy_tags_benches.rs +++ b/benches/fuzzy_tags_benches.rs @@ -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))); } } diff --git a/src/lib.rs b/src/lib.rs index 6131924..e3436d5 100644 --- a/src/lib.rs +++ b/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); +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, + /// 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::(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::(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) ); }