diff --git a/Cargo.toml b/Cargo.toml index afb6e30..4f009ba 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.4.2" +version = "0.5.0" repository = "https://git.openprivacy.ca/openprivacy/fuzzytags" authors = ["Sarah Jamie Lewis "] edition = "2018" @@ -12,12 +12,13 @@ keywords = ["fuzzytags","privacy","metadata-resistance","ristretto","cryptograph [dependencies] hex = "0.4.2" -rand = "0.7.3" -curve25519-dalek = {version="3.0.0", features=["serde"]} +rand_core = "0.6.0" +rand ="0.8.3" +curve25519-dalek = { package = "curve25519-dalek-ng", version="4.0.1", features=["serde"]} sha3 = "0.9.1" serde = {version="1.0.123", features=["derive"]} bit-vec = {version="0.6.3"} -brute-force = {version="0.1.0", features=["curve25519"], optional=true} +brute-force = {git="https://git.openprivacy.ca/sarah/brute-force.git", version="0.2.0", features=["curve25519"], optional=true} rayon = {version="1.5.0", optional=true} [dev-dependencies] @@ -29,6 +30,11 @@ bincode = "1.3.1" name = "fuzzy_tags_benches" harness = false +[[bench]] +name = "entangled" +harness = false + [features] entangled = ["brute-force"] -bulk_verify = ["rayon"] \ No newline at end of file +bulk_verify = ["rayon"] +simd = ["curve25519-dalek/simd_backend"] \ No newline at end of file diff --git a/README.md b/README.md index 8d595c6..3f7417c 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,9 @@ provided them (depending on the efficiency of the decryption method). A party first needs to generate `RootSecret` use fuzzytags::RootSecret; - let secret = RootSecret::<24>::generate(); + use rand::rngs::OsRng; + let mut rng = OsRng::default(); + let secret = RootSecret::<24>::generate(&mut rng); From the secret detection key a party can derive a `DetectionKey` which can be given to adversarial server to fuzzily detect tags on behalf of the party. @@ -104,12 +106,14 @@ validate against a random public key with a maximum probability of _2^-gamma_. Once in possession of a tagging key, a party in a metadata resistant app can use it to generate tags: use fuzzytags::RootSecret; - let secret = RootSecret::<24>::generate(); + use rand::rngs::OsRng; + let mut rng = OsRng::default(); + let secret = RootSecret::<24>::generate(&mut rng); let tagging_key = secret.tagging_key(); // Give public key to a another party... // and then they can do... - let tag = tagging_key.generate_tag(); + let tag = tagging_key.generate_tag(&mut rng); These tags can then be attached to a message in a metadata resistant system. @@ -120,14 +124,16 @@ 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::RootSecret; - let secret = RootSecret::<24>::generate(); + use rand::rngs::OsRng; + let mut rng = OsRng::default(); + let secret = RootSecret::<24>::generate(&mut rng); let tagging_key = secret.tagging_key(); // extract a detection key let detection_key = secret.extract_detection_key(5); // Give the tagging key to a another party... // and then they can do... - let tag = tagging_key.generate_tag(); + let tag = tagging_key.generate_tag(&mut rng); // The server can now do this: if detection_key.test_tag(&tag) { @@ -144,14 +150,16 @@ opens up applications like **multiple broadcast** and **deniable sending**. use fuzzytags::{RootSecret, TaggingKey}; - let secret_1 = RootSecret::<24>::generate(); - let secret_2 = RootSecret::<24>::generate(); + use rand::rngs::OsRng; + let mut rng = OsRng::default(); + let secret_1 = RootSecret::<24>::generate(&mut rng); + let secret_2 = RootSecret::<24>::generate(&mut rng); let tagging_key_1 = secret_1.tagging_key(); // give this to a sender let tagging_key_2 = secret_2.tagging_key(); // give this to a sender // Will validate for detection keys derived from both secret_1 and secret_2 up // to n=8 #[cfg(feature = "entangled")] - let tag = TaggingKey::generate_entangled_tag(vec![tagging_key_1,tagging_key_2], 8); + let tag = TaggingKey::generate_entangled_tag(vec![tagging_key_1,tagging_key_2], &mut rng, 8); ## Serialization @@ -161,13 +169,15 @@ of different approaches e.g.: use fuzzytags::RootSecret; use fuzzytags::Tag; + use rand::rngs::OsRng; - let secret = RootSecret::<24>::generate(); + let mut rng = OsRng::default(); + let secret = RootSecret::<24>::generate(&mut rng); let tagging_key = secret.tagging_key(); // Give public key to a another party... // and then they can do... - let tag = tagging_key.generate_tag(); + let tag = tagging_key.generate_tag(&mut rng); // An example using JSON serialization...see serde doc for other formats: let serialized_tag = serde_json::to_string(&tag).unwrap(); @@ -183,6 +193,14 @@ We use [criterion](https://crates.io/crates/criterion) for benchmarking, and ben Results will be in `target/criterion/report/index.html`. +### AVX2 + +This crate has support for the avx2 under the feature `simd`, to take advantage of this feature it is +necessary to build with `RUSTFLAGS="-C target_feature=+avx2"` e.g. + +`env RUSTFLAGS="-C target_feature=+avx2" cargo test --release --features "bulk_verify,entangled,simd"` + +This results in a 40%+ performance improvements on the provided benchmarks. ## Credits and Contributions diff --git a/benches/entangled.rs b/benches/entangled.rs new file mode 100644 index 0000000..5e35b53 --- /dev/null +++ b/benches/entangled.rs @@ -0,0 +1,29 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use fuzzytags::{RootSecret, TaggingKey}; +use rand::rngs::OsRng; +use std::time::Duration; + +fn benchmark_entangled(c: &mut Criterion) { + let mut group = c.benchmark_group("entangling"); + group.measurement_time(Duration::new(10, 0)); + group.sample_size(10); + let mut rng = OsRng::default(); + for p in [24].iter() { + let secret_key_1 = RootSecret::<24>::generate(&mut rng); + let secret_key_2 = RootSecret::<24>::generate(&mut rng); + let public_key_1 = secret_key_1.tagging_key(); + let public_key_2 = secret_key_2.tagging_key(); + group.bench_with_input(BenchmarkId::from_parameter(p), p, |b, _gamma| { + b.iter(|| { + TaggingKey::generate_entangled_tag( + vec![public_key_1.clone(), public_key_2.clone()], + &mut rng, + *p, + ) + }) + }); + } +} + +criterion_group!(benches, benchmark_entangled); +criterion_main!(benches); diff --git a/benches/fuzzy_tags_benches.rs b/benches/fuzzy_tags_benches.rs index c61933d..22bc57a 100644 --- a/benches/fuzzy_tags_benches.rs +++ b/benches/fuzzy_tags_benches.rs @@ -1,15 +1,19 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use fuzzytags::RootSecret; +use fuzzytags::{RootSecret, TaggingKey}; +use rand::rngs::OsRng; 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)); group.sample_size(1000); - let secret_key = RootSecret::<24>::generate(); + let mut rng = OsRng::default(); + let secret_key = RootSecret::<24>::generate(&mut rng); + let public_key = secret_key.tagging_key(); for p in [5, 10, 15].iter() { - let public_key = secret_key.tagging_key(); - group.bench_with_input(BenchmarkId::from_parameter(p), p, |b, _gamma| b.iter(|| public_key.generate_tag())); + group.bench_with_input(BenchmarkId::from_parameter(p), p, |b, _gamma| { + b.iter(|| public_key.generate_tag(&mut rng)) + }); } } @@ -17,12 +21,16 @@ fn benchmark_test_tag(c: &mut Criterion) { let mut group = c.benchmark_group("test_tags"); group.measurement_time(Duration::new(10, 0)); group.sample_size(1000); - let secret_key = RootSecret::<24>::generate(); + let mut rng = OsRng::default(); - for p in [5, 10, 15].iter() { - let tag = secret_key.tagging_key().generate_tag(); + let secret_key = RootSecret::<24>::generate(&mut rng); + + for p in [5, 10, 15, 24].iter() { let detection_key = secret_key.extract_detection_key(*p); - group.bench_with_input(BenchmarkId::from_parameter(p), p, |b, _gamma| b.iter(|| detection_key.test_tag(&tag))); + group.bench_with_input(BenchmarkId::from_parameter(p), p, |b, _gamma| { + let tag = secret_key.tagging_key().generate_tag(&mut rng); + b.iter(|| detection_key.test_tag(&tag)) + }); } } diff --git a/rustfmt.toml b/rustfmt.toml index ad894cd..3d075c7 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -2,8 +2,15 @@ max_width = 200 hard_tabs = false tab_spaces = 4 newline_style = "Auto" -use_small_heuristics = "Default" indent_style = "Block" +use_small_heuristics = "Default" +fn_call_width = 60 +attr_fn_like_width = 70 +struct_lit_width = 18 +struct_variant_width = 35 +array_width = 60 +chain_width = 60 +single_line_if_else_max_width = 50 wrap_comments = false format_code_in_doc_comments = false comment_width = 80 @@ -54,7 +61,7 @@ use_field_init_shorthand = false force_explicit_abi = true condense_wildcard_suffixes = false color = "Auto" -required_version = "1.4.34" +required_version = "1.4.37" unstable_features = false disable_all_formatting = false skip_children = false diff --git a/src/lib.rs b/src/lib.rs index 815c3a9..7e7050d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,5 @@ #![deny(missing_docs)] #![feature(external_doc)] -#![feature(const_generics)] #![doc(include = "../README.md")] #![doc(include = "../ANONYMITY.md")] #![doc(html_logo_url = "https://git.openprivacy.ca/openprivacy/fuzzytags/media/branch/trunk/FuzzyTags_Logo.png")] @@ -10,9 +9,8 @@ use curve25519_dalek::digest::Digest; use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; use curve25519_dalek::scalar::Scalar; use curve25519_dalek::traits::MultiscalarMul; -use rand::rngs::OsRng; use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; -use sha3::Sha3_512; +use sha3::{Sha3_256, Sha3_512}; use std::convert::TryFrom; use std::fmt; use std::fmt::{Display, Formatter}; @@ -23,6 +21,7 @@ use brute_force::adaptors; #[cfg(feature = "entangled")] use brute_force::brute_force; +use rand_core::{CryptoRng, RngCore}; #[cfg(feature = "bulk_verify")] use rayon::iter::IndexedParallelIterator; #[cfg(feature = "bulk_verify")] @@ -81,7 +80,10 @@ impl<'de, const GAMMA: u8> Deserialize<'de> for Tag<{ GAMMA }> { { let mut bytes = vec![]; for i in 0..64 { - bytes.push(seq.next_element()?.ok_or(serde::de::Error::invalid_length(i, &"expected at least 64 bytes"))?); + bytes.push(seq.next_element()?.ok_or(serde::de::Error::invalid_length( + i, + &"expected at least 64 bytes", + ))?); } loop { match seq.next_element().unwrap_or(None) { @@ -104,15 +106,17 @@ impl Tag<{ GAMMA }> { /// Ciphertext is right-padded with zeros to the nearest byte /// You probably want to use one of the many serde `serialize` apis instead (see README) /// ``` + /// use rand::rngs::OsRng; /// use fuzzytags::RootSecret; - /// let secret = RootSecret::<24>::generate(); + /// let mut rng = OsRng; + /// let secret = RootSecret::<24>::generate(&mut rng); /// let tagging_key = secret.tagging_key(); /// // extract a detection key /// let detection_key = secret.extract_detection_key(5); /// /// // Give tagging key to a another party... /// // and then they can do... - /// let tag = tagging_key.generate_tag(); + /// let tag = tagging_key.generate_tag(&mut rng); /// let compressed_tag = tag.compress(); /// ``` pub fn compress(&self) -> Vec { @@ -127,14 +131,16 @@ impl Tag<{ GAMMA }> { /// You probably want to use one of the many serde `deserialize` apis instead (see README) /// ``` /// use fuzzytags::{RootSecret, Tag}; - /// let secret = RootSecret::<24>::generate(); + /// use rand::rngs::OsRng; + /// let mut rng = OsRng; + /// let secret = RootSecret::<24>::generate(&mut rng); /// let tagging_key = secret.tagging_key(); /// // extract a detection key /// let detection_key = secret.extract_detection_key(5); /// /// // Give tagging key to a another party... /// // and then they can do... - /// let tag = tagging_key.generate_tag(); + /// let tag = tagging_key.generate_tag(&mut rng); /// let compressed_tag = tag.compress(); /// let decompressed_tag = Tag::decompress(&compressed_tag).unwrap(); /// assert_eq!(tag, decompressed_tag); @@ -157,7 +163,10 @@ impl Tag<{ GAMMA }> { }; let mut ciphertexts = BitVec::from_bytes(ciphertext); ciphertexts.truncate(GAMMA as usize); - return match (CompressedRistretto::from_slice(u_bytes).decompress(), Scalar::from_canonical_bytes(y_bytes_fixed)) { + return match ( + CompressedRistretto::from_slice(u_bytes).decompress(), + Scalar::from_canonical_bytes(y_bytes_fixed), + ) { (Some(u), Some(y)) => Some(Tag { u, y, ciphertexts }), _ => None, }; @@ -178,6 +187,12 @@ impl Display for Tag<{ GAMMA }> { } } +/// PrecomputeH is an encapsulation around the precomputation of the H function which +/// significantly speeds up testing. We define it for some additional type safety (to +/// prevent us from passing an uninitialized hash function to post_h +#[derive(Clone)] +struct PrecomputeH(Sha3_256); + /// The complete secret. Can't directly be used for testing. Instead you will need to generate /// a DetectionKey using `extract_detection_key` #[derive(Serialize, Deserialize)] @@ -194,16 +209,17 @@ impl RootSecret<{ GAMMA }> { /// Example: /// ``` /// use fuzzytags::{RootSecret}; - /// let secret = RootSecret::<24>::generate(); + /// use rand::rngs::OsRng; + /// let mut rng = OsRng; + /// let secret = RootSecret::<24>::generate(&mut rng); /// ``` - pub fn generate() -> RootSecret<{ GAMMA }> { - let mut rng = OsRng::default(); + pub fn generate(rng: &mut R) -> RootSecret<{ GAMMA }> { let mut secret = vec![]; for _i in 0..GAMMA { - let sk_i = Scalar::random(&mut rng); + let sk_i = Scalar::random(rng); secret.push(sk_i); } - RootSecret:: { secret: secret } + RootSecret:: { secret } } /// extract a detection key for a given false positive (p = 2^-n) @@ -213,7 +229,9 @@ impl RootSecret<{ GAMMA }> { /// Example: /// ``` /// use fuzzytags::{RootSecret}; - /// let secret = RootSecret::<24>::generate(); + /// use rand::rngs::OsRng; + /// let mut rng = OsRng; + /// let secret = RootSecret::<24>::generate(&mut rng); /// let detection_key = secret.extract_detection_key(2); /// ``` pub fn extract_detection_key(&self, n: usize) -> DetectionKey<{ GAMMA }> { @@ -225,7 +243,9 @@ impl RootSecret<{ GAMMA }> { /// Example: /// ``` /// use fuzzytags::RootSecret; - /// let secret = RootSecret::<24>::generate(); + /// use rand::rngs::OsRng; + /// let mut rng = OsRng; + /// let secret = RootSecret::<24>::generate(&mut rng); /// let tagging_key = secret.tagging_key(); /// ``` pub fn tagging_key(&self) -> TaggingKey<{ GAMMA }> { @@ -238,14 +258,19 @@ impl RootSecret<{ GAMMA }> { TaggingKey:: { 0: tagging_key } } - /// 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 { + /// precompute the first part of h + fn pre_h(u: RistrettoPoint, w: RistrettoPoint) -> PrecomputeH { let mut hash = sha3::Sha3_256::new(); hash.update(&[GAMMA]); 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; + return PrecomputeH(hash); + } + + /// compute the rest of h from a precomputed hash + fn post_h(mut hash: PrecomputeH, h: RistrettoPoint) -> u8 { + hash.0.update(h.compress().as_bytes()); + return hash.0.finalize().as_slice()[0] & 0x01; } /// a hash function which takes a ristretto point and a vector of ciphertexts and outputs a @@ -286,7 +311,9 @@ impl DetectionKey<{ GAMMA }> { /// calculate the ideal false positive rate of this detection key /// ``` /// use fuzzytags::RootSecret; - /// let secret = RootSecret::<24>::generate(); + /// use rand::rngs::OsRng; + /// let mut rng = OsRng; + /// let secret = RootSecret::<24>::generate(&mut rng); /// let tagging_key = secret.tagging_key(); /// // extract a detection key /// let detection_key = secret.extract_detection_key(5); @@ -300,14 +327,16 @@ impl DetectionKey<{ GAMMA }> { /// Example: /// ``` /// use fuzzytags::RootSecret; - /// let secret = RootSecret::<24>::generate(); + /// use rand::rngs::OsRng; + /// let mut rng = OsRng; + /// let secret = RootSecret::<24>::generate(&mut rng); /// let tagging_key = secret.tagging_key(); /// // extract a detection key /// let detection_key = secret.extract_detection_key(5); /// /// // Give tagging key to a another party... /// // and then they can do... - /// let tag = tagging_key.generate_tag(); + /// let tag = tagging_key.generate_tag(&mut rng); /// /// // The server can now do this: /// if detection_key.test_tag(&tag) { @@ -338,30 +367,28 @@ impl DetectionKey<{ GAMMA }> { // See below for a full explanation as to the reason for this: let w = RistrettoPoint::multiscalar_mul(&[m, tag.y], &[g, tag.u]); + let pre_h = RootSecret::::pre_h(tag.u, w); + // for each secret part... - let mut result = true; - for (i, x_i) in self.0.iter().enumerate() { + let mut result = 0; + for (x_i, c_i) in self.0.iter().zip(&tag.ciphertexts) { // re-derive the key from the tag - let k_i = RootSecret::::h(tag.u, tag.u.mul(x_i), w); + let k_i = RootSecret::::post_h(pre_h.clone(), tag.u.mul(x_i)); // calculate the "original" plaintext - let c_i = match tag.ciphertexts.get(i) { - Some(true) => 0x01, - Some(false) => 0x00, - _ => 0x00, - // we've run out of ciphertext, it doesn't really matter what we put here, the rest of the test will fail - // since the security of k_i is modelled as a random oracle, (k_i ^ 0) should also be random - }; - - let b_i = k_i ^ c_i; - - if b_i != 1 { + let b_i = k_i ^ (c_i as u8); + // short circuit + if b_i != 0x01 { return false; } // assert that the plaintext is all 1's - result = result & (b_i == 1); + result += 1; } - return result; + // Assert that number of sequential ones is equal to the length of the detection key + // If it isn't it indicates that the tag ciphertext is shorter than the verification key, + // Given the checks on deserialization that should never happen, but we throw in a check + // here anyway for defense in depth. + return result == self.0.len(); } /// A bulk testing function that takes in an vector of detection keys and returns a vector @@ -371,10 +398,12 @@ impl DetectionKey<{ GAMMA }> { /// ``` /// use fuzzytags::{TaggingKey, DetectionKey}; /// use fuzzytags::RootSecret; - /// let secrets: Vec> = (0..2).map(|_x| RootSecret::<24>::generate()).collect(); + /// use rand::rngs::OsRng; + /// let mut rng = OsRng; + /// let secrets: Vec> = (0..2).map(|_x| RootSecret::<24>::generate(&mut rng)).collect(); /// let tagging_keys: Vec> = secrets.iter().map(|x| x.tagging_key()).collect(); /// // it takes ~15 minutes on a standard desktop to find a length=24 match for 2 parties, so for testing let's keep things light - /// let entangled_tag = TaggingKey::generate_entangled_tag(tagging_keys, 16); + /// let entangled_tag = TaggingKey::generate_entangled_tag(tagging_keys, &mut rng, 16); /// let detection_keys = secrets.iter().map(|x| x.extract_detection_key(16)).collect(); /// /// let results = DetectionKey::test_tag_bulk(&detection_keys, &entangled_tag); @@ -403,41 +432,36 @@ impl DetectionKey<{ GAMMA }> { // See below for a full explanation as to the reason for this: let w = RistrettoPoint::multiscalar_mul(&[m, tag.y], &[g, tag.u]); let (tx, rx) = channel(); + let pre_h = RootSecret::::pre_h(tag.u, w); // for each secret part... let mut results: Vec = vec![]; - detection_keys.par_iter().enumerate().for_each_with(tx.clone(), |tx, (index, detection_key)| { - let mut result = true; - for (i, x_i) in detection_key.0.iter().enumerate() { - // re-derive the key from the tag - let k_i = RootSecret::::h(tag.u, tag.u.mul(x_i), w); + detection_keys + .par_iter() + .enumerate() + .for_each_with(tx.clone(), |tx, (index, detection_key)| { + let mut result = 0; + for (x_i, c_i) in detection_key.0.iter().zip(&tag.ciphertexts) { + // re-derive the key from the tag + let k_i = RootSecret::::post_h(pre_h.clone(), tag.u.mul(x_i)); - // calculate the "original" plaintext - let c_i = match tag.ciphertexts.get(i) { - Some(true) => 0x01, - Some(false) => 0x00, - _ => 0x00, - // we've run out of ciphertext, it doesn't really matter what we put here, the rest of the test will fail - // since the security of k_i is modelled as a random oracle, (k_i ^ 0) should also be random - }; + // calculate the "original" plaintext + let b_i = k_i ^ (c_i as u8); - let b_i = k_i ^ c_i; - - if b_i != 1 { - result = false; - break; + if b_i != 1 { + break; + } + // assert that the plaintext is all 1's + result += 1; } - // assert that the plaintext is all 1's - result = result & (b_i == 1); - } - if result { - match tx.send(index) { - _ => { - // TODO...surface this error... + if result == detection_key.0.len() { + match tx.send(index) { + _ => { + // TODO...surface this error... + } } } - } - }); + }); std::mem::drop(tx); loop { @@ -473,24 +497,28 @@ impl TaggingKey<{ GAMMA }> { /// Example: /// ``` /// use fuzzytags::{RootSecret}; - /// let secret = RootSecret::<24>::generate(); + /// use rand::rngs::OsRng; + /// let mut rng = OsRng; + /// let secret = RootSecret::<24>::generate(&mut rng); /// let tagging_key = secret.tagging_key(); // give this to a sender - /// let tag = tagging_key.generate_tag(); + /// let tag = tagging_key.generate_tag(&mut rng); /// ``` - pub fn generate_tag(&self) -> Tag<{ GAMMA }> { - let mut rng = OsRng::default(); - let g = RISTRETTO_BASEPOINT_POINT; - + pub fn generate_tag(&self, rng: &mut R) -> Tag<{ GAMMA }> { // generate some random points... - let r = Scalar::random(&mut rng); - let u = g.mul(r); - let z = Scalar::random(&mut rng); - let w = g.mul(z); + let r = Scalar::random(rng); + let u = RISTRETTO_BASEPOINT_POINT.mul(r); + + let z = Scalar::random(rng); + let w = RISTRETTO_BASEPOINT_POINT.mul(z); + + // precompute the first part of the `H` hash function + let pre_h = RootSecret::::pre_h(u, w); // construct the ciphertext portion of the tag - let mut ciphertexts = BitVec::new(); - for (_i, h_i) in self.0.iter().enumerate() { - let k_i = RootSecret::::h(u, h_i.mul(r), w); + let mut ciphertexts = BitVec::with_capacity(GAMMA.into()); + + for h_i in self.0.iter() { + let k_i = RootSecret::::post_h(pre_h.clone(), h_i.mul(r)); // encrypt a plaintext of all 1's let c_i = k_i ^ 0x01; ciphertexts.push(c_i == 0x01); @@ -524,20 +552,21 @@ impl TaggingKey<{ GAMMA }> { /// Example: /// ``` /// use fuzzytags::{RootSecret, TaggingKey}; - /// let secret_1 = RootSecret::<24>::generate(); - /// let secret_2 = RootSecret::<24>::generate(); + /// use rand::rngs::OsRng; + /// let mut rng = OsRng; + /// let secret_1 = RootSecret::<24>::generate(&mut rng); + /// let secret_2 = RootSecret::<24>::generate(&mut rng); /// let tagging_key_1 = secret_1.tagging_key(); // give this to a sender /// let tagging_key_2 = secret_2.tagging_key(); // give this to a sender /// // Will validate for detection keys derived from both secret_1 and secret_2 up /// // to n=8 /// // Sender can now do...tag will validate on detection keys of length 8 or lower. - /// let tag = TaggingKey::generate_entangled_tag(vec![tagging_key_1,tagging_key_2], 8); + /// let tag = TaggingKey::generate_entangled_tag(vec![tagging_key_1,tagging_key_2], &mut rng, 8); /// ``` - pub fn generate_entangled_tag(tagging_keys: Vec>, length: usize) -> Tag<{ GAMMA }> { - let mut rng = OsRng::default(); + pub fn generate_entangled_tag(tagging_keys: Vec>, rng: &mut R, length: usize) -> Tag<{ GAMMA }> { let g = RISTRETTO_BASEPOINT_POINT; // generate some random points... - let r = Scalar::random(&mut rng); + let r = Scalar::random(rng); let u = g.mul(r); // Compute and cache some public points that we will be using over and over again @@ -551,14 +580,16 @@ impl TaggingKey<{ GAMMA }> { } let config = brute_force::Config::default(); + let f = |z: &Scalar| { let w = g.mul(z); + let pre_h = RootSecret::::pre_h(u, w); let mut key = vec![]; for (i, precompute) in tagging_key_precomputes[0].iter().enumerate() { - let k_i = RootSecret::::h(u, *precompute, w); + let k_i = RootSecret::::post_h(pre_h.clone(), *precompute); if i < length { for precompute in tagging_key_precomputes.iter().skip(1) { - let n_k_i = RootSecret::::h(u, precompute[i], w); + let n_k_i = RootSecret::::post_h(pre_h.clone(), precompute[i]); if k_i != n_k_i { return None; } @@ -590,15 +621,18 @@ mod tests { use bit_vec::BitVec; use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::scalar::Scalar; + use rand::rngs::OsRng; + use sha3::Digest; #[test] fn test_compression() { - let secret = RootSecret::<24>::generate(); + let mut rng = OsRng; + let secret = RootSecret::<24>::generate(&mut rng); let tagging_key = secret.tagging_key(); // Give tagging key to a another party... // and then they can do... - let tag = tagging_key.generate_tag(); + let tag = tagging_key.generate_tag(&mut rng); let compressed_tag = tag.compress(); let decompressed_tag = Tag::<24>::decompress(&compressed_tag).unwrap(); assert_eq!(tag, decompressed_tag); @@ -607,8 +641,9 @@ mod tests { #[test] fn test_serialization() { // generate some new keys... - let secret = RootSecret::<15>::generate(); - let tag = secret.tagging_key().generate_tag(); + let mut rng = OsRng; + let secret = RootSecret::<15>::generate(&mut rng); + let tag = secret.tagging_key().generate_tag(&mut rng); let detection_key = secret.extract_detection_key(10); let serialized_tag = serde_json::to_string(&tag).unwrap(); println!("{}", serialized_tag); @@ -617,8 +652,8 @@ mod tests { assert_eq!(true, detection_key.test_tag(&deserialized_tag)); // generate some new keys... - let secret = RootSecret::<24>::generate(); - let tag = secret.tagging_key().generate_tag(); + let secret = RootSecret::<24>::generate(&mut rng); + let tag = secret.tagging_key().generate_tag(&mut rng); let detection_key = secret.extract_detection_key(10); let serialized_tag = serde_json::to_string(&tag).unwrap(); let deserialized_tag: Tag<24> = serde_json::from_str(&serialized_tag).unwrap(); @@ -654,13 +689,18 @@ mod tests { #[cfg(feature = "entangled")] fn test_multiple() { use crate::TaggingKey; - let secrets: Vec> = (0..2).map(|_x| RootSecret::<24>::generate()).collect(); + let mut rng = OsRng; + let secrets: Vec> = (0..2) + .map(|_x| RootSecret::<24>::generate(&mut rng)) + .collect(); let tagging_keys: Vec> = secrets.iter().map(|x| x.tagging_key()).collect(); - // it takes ~15 minutes on a standard desktop to find a length=24 match for 2 parties, so for testing let's keep things light - let entangled_tag = TaggingKey::generate_entangled_tag(tagging_keys, 16); + + // it takes ~2 minutes on a standard desktop to find a length=24 match for 2 parties, so for testing let's keep things light + let len = 16; + let entangled_tag = TaggingKey::generate_entangled_tag(tagging_keys, &mut rng, len); println!("{}", entangled_tag); for secret in secrets.iter() { - let detection_key = secret.extract_detection_key(16); + let detection_key = secret.extract_detection_key(len); assert!(detection_key.test_tag(&entangled_tag)); println!("{}", detection_key); } @@ -670,11 +710,17 @@ mod tests { #[cfg(feature = "bulk_verify")] fn test_check_multiple() { use crate::TaggingKey; - let secrets: Vec> = (0..2).map(|_x| RootSecret::<24>::generate()).collect(); + let mut rng = OsRng; + let secrets: Vec> = (0..2) + .map(|_x| RootSecret::<24>::generate(&mut rng)) + .collect(); let tagging_keys: Vec> = secrets.iter().map(|x| x.tagging_key()).collect(); - // it takes ~15 minutes on a standard desktop to find a length=24 match for 2 parties, so for testing let's keep things light - let entangled_tag = TaggingKey::generate_entangled_tag(tagging_keys, 16); - let detection_keys = secrets.iter().map(|x| x.extract_detection_key(16)).collect(); + // it takes ~2 minutes on a standard desktop to find a length=24 match for 2 parties, so for testing let's keep things light + let entangled_tag = TaggingKey::generate_entangled_tag(tagging_keys, &mut rng, 16); + let detection_keys = secrets + .iter() + .map(|x| x.extract_detection_key(16)) + .collect(); let results = DetectionKey::test_tag_bulk(&detection_keys, &entangled_tag); assert_eq!(results.len(), 2); @@ -683,9 +729,10 @@ mod tests { #[test] fn correctness() { let number_of_messages = 100; - let secret = RootSecret::<16>::generate(); + let mut rng = OsRng; + let secret = RootSecret::<16>::generate(&mut rng); for i in 0..number_of_messages { - let tag = secret.tagging_key().generate_tag(); + let tag = secret.tagging_key().generate_tag(&mut rng); println!("{}: {}", i, tag); assert!(secret.extract_detection_key(5).test_tag(&tag)); } @@ -710,13 +757,39 @@ mod tests { tag } + /// 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(&[24]); + hash.update(u.compress().as_bytes()); + hash.update(w.compress().as_bytes()); + hash.update(h.compress().as_bytes()); + return hash.finalize().as_slice()[0] & 0x01; + } + + #[test] + fn assert_h_and_pre_post_h() { + let mut rng = OsRng; + + for _ in 0..100 { + let a = RistrettoPoint::random(&mut rng); + let b = RistrettoPoint::random(&mut rng); + let c = RistrettoPoint::random(&mut rng); + assert_eq!( + RootSecret::<24>::post_h(RootSecret::<24>::pre_h(a, b), c), + h(a, c, b) + ); + } + } + #[test] // Thanks to Lee Bousfield who noticed an all zeros or all ones tag would // validate against a tagging key with 50% probability, allowing universal // broadcast, which overall seems like a bad idea... // Test to make sure that doesn't happen. fn test_zero_tag() { - let secret = RootSecret::<24>::generate(); + let mut rng = OsRng; + let secret = RootSecret::<24>::generate(&mut rng); let tag = gen_zero_tag_zero(); assert_eq!(false, secret.extract_detection_key(6).test_tag(&tag)); let tag = gen_zero_tag_one(); @@ -725,12 +798,13 @@ mod tests { #[test] fn false_positives() { + let mut rng = OsRng; let number_of_messages = 1000; - let secret = RootSecret::<24>::generate(); + let secret = RootSecret::<24>::generate(&mut rng); let mut false_positives = 0; for _i in 0..number_of_messages { - let secret2 = RootSecret::<24>::generate(); - let tag = secret2.tagging_key().generate_tag(); + let secret2 = RootSecret::<24>::generate(&mut rng); + let tag = secret2.tagging_key().generate_tag(&mut rng); assert!(secret2.extract_detection_key(3).test_tag(&tag)); if secret.extract_detection_key(3).test_tag(&tag) == true { false_positives += 1;