From 33f2fa9a88278c28e6aefbf8c52aa61176a8b9e3 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Wed, 19 May 2021 23:31:14 -0700 Subject: [PATCH 1/9] Performance improvement during testing. By splitting up the hash function H(U, x_iU, W) we can precompute the application of U and W, and only apply x_iU during the main detection key loop. Saving about 9.6% of the overall time. --- benches/fuzzy_tags_benches.rs | 8 +- rustfmt.toml | 11 ++- src/lib.rs | 152 +++++++++++++++++++++++----------- 3 files changed, 118 insertions(+), 53 deletions(-) diff --git a/benches/fuzzy_tags_benches.rs b/benches/fuzzy_tags_benches.rs index c61933d..01c28b6 100644 --- a/benches/fuzzy_tags_benches.rs +++ b/benches/fuzzy_tags_benches.rs @@ -9,7 +9,9 @@ fn benchmark_generate_tag(c: &mut Criterion) { let secret_key = RootSecret::<24>::generate(); 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()) + }); } } @@ -22,7 +24,9 @@ fn benchmark_test_tag(c: &mut Criterion) { for p in [5, 10, 15].iter() { let tag = secret_key.tagging_key().generate_tag(); 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| { + 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..ae27be8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ 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}; @@ -32,6 +32,13 @@ use rayon::iter::ParallelIterator; #[cfg(feature = "bulk_verify")] use std::sync::mpsc::channel; +#[cfg(feature = "encrypt_to_tag")] +use secretbox::CipherType::Salsa20; +#[cfg(feature = "encrypt_to_tag")] +use secretbox::SecretBox; +#[cfg(feature = "encrypt_to_tag")] +use std::string::FromUtf8Error; + /// A tag is a probabilistic cryptographic structure. When constructed for a given `TaggingKey` /// it will pass the `DetectionKey::test_tag` 100% of the time. For other tagging keys /// it will pass the test with probability `GAMMA` related to the security parameter of the system. @@ -81,7 +88,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) { @@ -157,7 +167,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 +191,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)] @@ -203,7 +222,7 @@ impl RootSecret<{ GAMMA }> { let sk_i = Scalar::random(&mut rng); secret.push(sk_i); } - RootSecret:: { secret: secret } + RootSecret:: { secret } } /// extract a detection key for a given false positive (p = 2^-n) @@ -238,13 +257,28 @@ impl RootSecret<{ GAMMA }> { TaggingKey:: { 0: tagging_key } } + /// 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(w.compress().as_bytes()); + 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 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(&[GAMMA]); hash.update(u.compress().as_bytes()); - hash.update(h.compress().as_bytes()); hash.update(w.compress().as_bytes()); + hash.update(h.compress().as_bytes()); return hash.finalize().as_slice()[0] & 0x01; } @@ -338,30 +372,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 @@ -406,38 +438,41 @@ impl DetectionKey<{ GAMMA }> { // 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 = 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); - // 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 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; + let b_i = k_i ^ c_i; - if b_i != 1 { - result = false; - break; + if b_i != 1 { + result = false; + break; + } + // assert that the plaintext is all 1's + result = result & (b_i == 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 { + match tx.send(index) { + _ => { + // TODO...surface this error... + } } } - } - }); + }); std::mem::drop(tx); loop { @@ -586,10 +621,11 @@ impl TaggingKey<{ GAMMA }> { #[cfg(test)] mod tests { - use crate::{DetectionKey, RootSecret, Tag}; + use crate::{RootSecret, Tag}; use bit_vec::BitVec; use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::scalar::Scalar; + use rand::rngs::OsRng; #[test] fn test_compression() { @@ -674,7 +710,10 @@ mod tests { 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(); + 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); @@ -710,6 +749,21 @@ mod tests { tag } + #[test] + fn assert_h_and_pre_post_h() { + let mut rng = OsRng::default(); + + 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), + RootSecret::<24>::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 From 788270a02ac7b353177448184ce136fe35db4a24 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Wed, 19 May 2021 23:36:03 -0700 Subject: [PATCH 2/9] Remove unused deps --- src/lib.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ae27be8..7ee49a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,13 +32,6 @@ use rayon::iter::ParallelIterator; #[cfg(feature = "bulk_verify")] use std::sync::mpsc::channel; -#[cfg(feature = "encrypt_to_tag")] -use secretbox::CipherType::Salsa20; -#[cfg(feature = "encrypt_to_tag")] -use secretbox::SecretBox; -#[cfg(feature = "encrypt_to_tag")] -use std::string::FromUtf8Error; - /// A tag is a probabilistic cryptographic structure. When constructed for a given `TaggingKey` /// it will pass the `DetectionKey::test_tag` 100% of the time. For other tagging keys /// it will pass the test with probability `GAMMA` related to the security parameter of the system. From 1124ffc1a646689ed812f8165b74f0964469c7c5 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 20 May 2021 01:11:07 -0700 Subject: [PATCH 3/9] Apply optimizations to tag generation + entangled tag generation --- Cargo.toml | 4 ++++ benches/entangled.rs | 21 +++++++++++++++++++++ benches/fuzzy_tags_benches.rs | 2 +- src/lib.rs | 34 ++++++++++++++-------------------- 4 files changed, 40 insertions(+), 21 deletions(-) create mode 100644 benches/entangled.rs diff --git a/Cargo.toml b/Cargo.toml index afb6e30..c0a8669 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,10 @@ 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 diff --git a/benches/entangled.rs b/benches/entangled.rs new file mode 100644 index 0000000..c28cadf --- /dev/null +++ b/benches/entangled.rs @@ -0,0 +1,21 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use fuzzytags::{RootSecret, TaggingKey}; +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 secret_key_1 = RootSecret::<24>::generate(); + let secret_key_2 = RootSecret::<24>::generate(); + let public_key_1 = secret_key_1.tagging_key(); + let public_key_2 = secret_key_2.tagging_key(); + for p in [4, 8, 16, 20, 24].iter() { + 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()], *p)) + }); + } +} + +criterion_group!(benches, benchmark_entangled); +criterion_main!(benches); diff --git a/benches/fuzzy_tags_benches.rs b/benches/fuzzy_tags_benches.rs index 01c28b6..065bc77 100644 --- a/benches/fuzzy_tags_benches.rs +++ b/benches/fuzzy_tags_benches.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use fuzzytags::RootSecret; +use fuzzytags::{RootSecret, TaggingKey}; use std::time::Duration; fn benchmark_generate_tag(c: &mut Criterion) { diff --git a/src/lib.rs b/src/lib.rs index 7ee49a5..acb84ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -428,6 +428,7 @@ 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![]; @@ -435,30 +436,21 @@ impl DetectionKey<{ GAMMA }> { .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() { + 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::::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; + let b_i = k_i ^ (c_i as u8); if b_i != 1 { - result = false; break; } // assert that the plaintext is all 1's - result = result & (b_i == 1); + result += 1; } - if result { + if result == detection_key.0.len() { match tx.send(index) { _ => { // TODO...surface this error... @@ -514,11 +506,11 @@ impl TaggingKey<{ GAMMA }> { let u = g.mul(r); let z = Scalar::random(&mut rng); let w = g.mul(z); - + 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 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); @@ -579,14 +571,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; } @@ -614,7 +608,7 @@ impl TaggingKey<{ GAMMA }> { #[cfg(test)] mod tests { - use crate::{RootSecret, Tag}; + use crate::{DetectionKey, RootSecret, Tag}; use bit_vec::BitVec; use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::scalar::Scalar; From 2d73a43d7fef2ad3c0efeb93abc39a34bccd2da9 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 20 May 2021 01:53:33 -0700 Subject: [PATCH 4/9] Clean up benchmarks, move RNG to function parameters + optimize generation --- README.md | 16 ++++++-- benches/entangled.rs | 4 +- benches/fuzzy_tags_benches.rs | 11 ++++-- src/lib.rs | 73 ++++++++++++++++++++++------------- 4 files changed, 68 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 8d595c6..438cc3d 100644 --- a/README.md +++ b/README.md @@ -104,12 +104,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; + use rand::rngs::OsRng; let secret = RootSecret::<24>::generate(); 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 mut rng = OsRng::default(); + let tag = tagging_key.generate_tag(&mut rng); These tags can then be attached to a message in a metadata resistant system. @@ -120,6 +122,7 @@ 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; + use rand::rngs::OsRng; let secret = RootSecret::<24>::generate(); let tagging_key = secret.tagging_key(); // extract a detection key @@ -127,7 +130,8 @@ This extracted key can then be given to an adversarial server. The server can th // Give the tagging key to a another party... // and then they can do... - let tag = tagging_key.generate_tag(); + let mut rng = OsRng::default(); + let tag = tagging_key.generate_tag(&mut rng); // The server can now do this: if detection_key.test_tag(&tag) { @@ -144,6 +148,8 @@ opens up applications like **multiple broadcast** and **deniable sending**. use fuzzytags::{RootSecret, TaggingKey}; + use rand::rngs::OsRng; + let mut rng = OsRng::default(); let secret_1 = RootSecret::<24>::generate(); let secret_2 = RootSecret::<24>::generate(); let tagging_key_1 = secret_1.tagging_key(); // give this to a sender @@ -151,7 +157,7 @@ opens up applications like **multiple broadcast** and **deniable sending**. // 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 +167,15 @@ of different approaches e.g.: use fuzzytags::RootSecret; use fuzzytags::Tag; + use rand::rngs::OsRng; + let mut rng = OsRng::default(); let secret = RootSecret::<24>::generate(); 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(); diff --git a/benches/entangled.rs b/benches/entangled.rs index c28cadf..9e93feb 100644 --- a/benches/entangled.rs +++ b/benches/entangled.rs @@ -4,8 +4,8 @@ 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); + group.measurement_time(Duration::new(5000, 0)); + group.sample_size(50); let secret_key_1 = RootSecret::<24>::generate(); let secret_key_2 = RootSecret::<24>::generate(); let public_key_1 = secret_key_1.tagging_key(); diff --git a/benches/fuzzy_tags_benches.rs b/benches/fuzzy_tags_benches.rs index 065bc77..1597a24 100644 --- a/benches/fuzzy_tags_benches.rs +++ b/benches/fuzzy_tags_benches.rs @@ -1,5 +1,6 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use fuzzytags::{RootSecret, TaggingKey}; +use rand::rngs::OsRng; use std::time::Duration; fn benchmark_generate_tag(c: &mut Criterion) { @@ -7,10 +8,11 @@ fn benchmark_generate_tag(c: &mut Criterion) { group.measurement_time(Duration::new(10, 0)); group.sample_size(1000); let secret_key = RootSecret::<24>::generate(); + let mut rng = OsRng::default(); + 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()) + b.iter(|| public_key.generate_tag(&mut rng)) }); } } @@ -20,11 +22,12 @@ fn benchmark_test_tag(c: &mut Criterion) { 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(); + 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| { + let tag = secret_key.tagging_key().generate_tag(&mut rng); b.iter(|| detection_key.test_tag(&tag)) }); } diff --git a/src/lib.rs b/src/lib.rs index acb84ae..a3226a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; use curve25519_dalek::scalar::Scalar; use curve25519_dalek::traits::MultiscalarMul; use rand::rngs::OsRng; +use rand::{CryptoRng, RngCore}; use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; use sha3::{Sha3_256, Sha3_512}; use std::convert::TryFrom; @@ -107,6 +108,7 @@ 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 tagging_key = secret.tagging_key(); @@ -115,7 +117,8 @@ impl Tag<{ GAMMA }> { /// /// // Give tagging key to a another party... /// // and then they can do... - /// let tag = tagging_key.generate_tag(); + /// let mut rng = OsRng::default(); + /// let tag = tagging_key.generate_tag(&mut rng); /// let compressed_tag = tag.compress(); /// ``` pub fn compress(&self) -> Vec { @@ -137,7 +140,9 @@ impl Tag<{ GAMMA }> { /// /// // Give tagging key to a another party... /// // and then they can do... - /// let tag = tagging_key.generate_tag(); + /// use rand::rngs::OsRng; + /// let mut rng = OsRng::default(); + /// 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); @@ -334,7 +339,9 @@ impl DetectionKey<{ GAMMA }> { /// /// // Give tagging key to a another party... /// // and then they can do... - /// let tag = tagging_key.generate_tag(); + /// use rand::rngs::OsRng; + /// let mut rng = OsRng::default(); + /// let tag = tagging_key.generate_tag(&mut rng); /// /// // The server can now do this: /// if detection_key.test_tag(&tag) { @@ -399,7 +406,9 @@ impl DetectionKey<{ GAMMA }> { /// let secrets: Vec> = (0..2).map(|_x| RootSecret::<24>::generate()).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); + /// use rand::rngs::OsRng; + /// let mut rng = OsRng::default(); + /// 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); @@ -495,21 +504,25 @@ impl TaggingKey<{ GAMMA }> { /// use fuzzytags::{RootSecret}; /// let secret = RootSecret::<24>::generate(); /// let tagging_key = secret.tagging_key(); // give this to a sender - /// let tag = tagging_key.generate_tag(); + /// use rand::rngs::OsRng; + /// let mut rng = OsRng::default(); + /// 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 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; @@ -551,13 +564,14 @@ impl TaggingKey<{ GAMMA }> { /// // 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); + /// use rand::rngs::OsRng; + /// let mut rng = OsRng::default(); + /// 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 @@ -621,7 +635,8 @@ mod tests { // Give tagging key to a another party... // and then they can do... - let tag = tagging_key.generate_tag(); + let mut rng = OsRng::default(); + 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); @@ -630,8 +645,10 @@ 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::default(); + 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); @@ -641,7 +658,7 @@ mod tests { // generate some new keys... let secret = RootSecret::<24>::generate(); - let tag = secret.tagging_key().generate_tag(); + 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(); @@ -680,7 +697,8 @@ mod tests { let secrets: Vec> = (0..2).map(|_x| RootSecret::<24>::generate()).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 mut rng = OsRng::default(); + let entangled_tag = TaggingKey::generate_entangled_tag(tagging_keys, &mut rng, 16); println!("{}", entangled_tag); for secret in secrets.iter() { let detection_key = secret.extract_detection_key(16); @@ -696,7 +714,8 @@ mod tests { let secrets: Vec> = (0..2).map(|_x| RootSecret::<24>::generate()).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 mut rng = OsRng::default(); + let entangled_tag = TaggingKey::generate_entangled_tag(tagging_keys, &mut rng, 16); let detection_keys = secrets .iter() .map(|x| x.extract_detection_key(16)) @@ -710,8 +729,9 @@ mod tests { fn correctness() { let number_of_messages = 100; let secret = RootSecret::<16>::generate(); + let mut rng = OsRng::default(); 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)); } @@ -769,9 +789,10 @@ mod tests { let number_of_messages = 1000; let secret = RootSecret::<24>::generate(); let mut false_positives = 0; + let mut rng = OsRng::default(); for _i in 0..number_of_messages { let secret2 = RootSecret::<24>::generate(); - let tag = secret2.tagging_key().generate_tag(); + 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; From 84778a80304165c5de2271ad65c54c7898065dbf Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Sat, 22 May 2021 12:39:04 -0700 Subject: [PATCH 5/9] Update documentation, configure avx2 --- Cargo.toml | 10 +-- README.md | 26 ++++--- benches/entangled.rs | 18 ++--- benches/fuzzy_tags_benches.rs | 5 +- brute-force | 1 + src/lib.rs | 129 ++++++++++++++++++---------------- 6 files changed, 107 insertions(+), 82 deletions(-) create mode 160000 brute-force diff --git a/Cargo.toml b/Cargo.toml index c0a8669..4bb0f8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 = {path="./brute-force", version="0.1.1", features=["curve25519"], optional=true} rayon = {version="1.5.0", optional=true} [dev-dependencies] @@ -35,4 +36,5 @@ 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 438cc3d..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. @@ -105,12 +107,12 @@ Once in possession of a tagging key, a party in a metadata resistant app can use use fuzzytags::RootSecret; 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 mut rng = OsRng::default(); let tag = tagging_key.generate_tag(&mut rng); These tags can then be attached to a message in a metadata resistant system. @@ -123,14 +125,14 @@ This extracted key can then be given to an adversarial server. The server can th use fuzzytags::RootSecret; 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(); // 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 mut rng = OsRng::default(); let tag = tagging_key.generate_tag(&mut rng); // The server can now do this: @@ -150,8 +152,8 @@ opens up applications like **multiple broadcast** and **deniable sending**. use fuzzytags::{RootSecret, TaggingKey}; use rand::rngs::OsRng; let mut rng = OsRng::default(); - let secret_1 = RootSecret::<24>::generate(); - let secret_2 = RootSecret::<24>::generate(); + 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 @@ -170,7 +172,7 @@ of different approaches e.g.: use rand::rngs::OsRng; let mut rng = OsRng::default(); - let secret = RootSecret::<24>::generate(); + let secret = RootSecret::<24>::generate(&mut rng); let tagging_key = secret.tagging_key(); // Give public key to a another party... @@ -191,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 index 9e93feb..651724e 100644 --- a/benches/entangled.rs +++ b/benches/entangled.rs @@ -1,18 +1,20 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use fuzzytags::{RootSecret, TaggingKey}; use std::time::Duration; +use rand::rngs::OsRng; fn benchmark_entangled(c: &mut Criterion) { let mut group = c.benchmark_group("entangling"); - group.measurement_time(Duration::new(5000, 0)); - group.sample_size(50); - let secret_key_1 = RootSecret::<24>::generate(); - let secret_key_2 = RootSecret::<24>::generate(); - let public_key_1 = secret_key_1.tagging_key(); - let public_key_2 = secret_key_2.tagging_key(); - for p in [4, 8, 16, 20, 24].iter() { + 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()], *p)) + b.iter(|| TaggingKey::generate_entangled_tag(vec![public_key_1.clone(), public_key_2.clone()], &mut rng, *p)) }); } } diff --git a/benches/fuzzy_tags_benches.rs b/benches/fuzzy_tags_benches.rs index 1597a24..22bc57a 100644 --- a/benches/fuzzy_tags_benches.rs +++ b/benches/fuzzy_tags_benches.rs @@ -7,8 +7,8 @@ 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() { group.bench_with_input(BenchmarkId::from_parameter(p), p, |b, _gamma| { @@ -21,9 +21,10 @@ 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(); + 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| { diff --git a/brute-force b/brute-force new file mode 160000 index 0000000..51e3b4d --- /dev/null +++ b/brute-force @@ -0,0 +1 @@ +Subproject commit 51e3b4d549a439e512509c080e2f1fbb1d3e7aa4 diff --git a/src/lib.rs b/src/lib.rs index a3226a8..b8932a6 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,8 +9,6 @@ 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 rand::{CryptoRng, RngCore}; use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; use sha3::{Sha3_256, Sha3_512}; use std::convert::TryFrom; @@ -32,6 +29,7 @@ use rayon::iter::IntoParallelRefIterator; use rayon::iter::ParallelIterator; #[cfg(feature = "bulk_verify")] use std::sync::mpsc::channel; +use rand_core::{RngCore, CryptoRng}; /// A tag is a probabilistic cryptographic structure. When constructed for a given `TaggingKey` /// it will pass the `DetectionKey::test_tag` 100% of the time. For other tagging keys @@ -110,14 +108,14 @@ impl Tag<{ GAMMA }> { /// ``` /// 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 mut rng = OsRng::default(); /// let tag = tagging_key.generate_tag(&mut rng); /// let compressed_tag = tag.compress(); /// ``` @@ -133,15 +131,15 @@ 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... - /// use rand::rngs::OsRng; - /// let mut rng = OsRng::default(); /// let tag = tagging_key.generate_tag(&mut rng); /// let compressed_tag = tag.compress(); /// let decompressed_tag = Tag::decompress(&compressed_tag).unwrap(); @@ -211,13 +209,15 @@ 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 } @@ -230,7 +230,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 }> { @@ -242,7 +244,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 }> { @@ -270,16 +274,6 @@ impl RootSecret<{ GAMMA }> { return hash.0.finalize().as_slice()[0] & 0x01; } - /// 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(&[GAMMA]); - 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; - } - /// 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 { @@ -318,7 +312,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); @@ -332,15 +328,15 @@ 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... - /// use rand::rngs::OsRng; - /// let mut rng = OsRng::default(); /// let tag = tagging_key.generate_tag(&mut rng); /// /// // The server can now do this: @@ -403,11 +399,11 @@ 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 - /// use rand::rngs::OsRng; - /// let mut rng = OsRng::default(); /// 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(); /// @@ -502,10 +498,10 @@ impl TaggingKey<{ GAMMA }> { /// Example: /// ``` /// use fuzzytags::{RootSecret}; - /// let secret = RootSecret::<24>::generate(); - /// let tagging_key = secret.tagging_key(); // give this to a sender /// use rand::rngs::OsRng; - /// let mut rng = OsRng::default(); + /// 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(&mut rng); /// ``` pub fn generate_tag(&self, rng: &mut R) -> Tag<{ GAMMA }> { @@ -557,15 +553,15 @@ 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. - /// use rand::rngs::OsRng; - /// let mut rng = OsRng::default(); /// let tag = TaggingKey::generate_entangled_tag(vec![tagging_key_1,tagging_key_2], &mut rng, 8); /// ``` pub fn generate_entangled_tag(tagging_keys: Vec>, rng: &mut R, length: usize) -> Tag<{ GAMMA }> { @@ -627,15 +623,16 @@ mod tests { 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 mut rng = OsRng::default(); let tag = tagging_key.generate_tag(&mut rng); let compressed_tag = tag.compress(); let decompressed_tag = Tag::<24>::decompress(&compressed_tag).unwrap(); @@ -645,9 +642,8 @@ mod tests { #[test] fn test_serialization() { // generate some new keys... - - let secret = RootSecret::<15>::generate(); - let mut rng = OsRng::default(); + 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(); @@ -657,7 +653,7 @@ mod tests { assert_eq!(true, detection_key.test_tag(&deserialized_tag)); // generate some new keys... - let secret = RootSecret::<24>::generate(); + 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(); @@ -694,14 +690,16 @@ 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 mut rng = OsRng::default(); - let entangled_tag = TaggingKey::generate_entangled_tag(tagging_keys, &mut rng, 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); } @@ -711,10 +709,10 @@ 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 mut rng = OsRng::default(); + // 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() @@ -728,8 +726,8 @@ mod tests { #[test] fn correctness() { let number_of_messages = 100; - let secret = RootSecret::<16>::generate(); - let mut rng = OsRng::default(); + 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(&mut rng); println!("{}: {}", i, tag); @@ -756,9 +754,19 @@ 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::default(); + let mut rng = OsRng; for _ in 0..100 { let a = RistrettoPoint::random(&mut rng); @@ -766,7 +774,7 @@ mod tests { let c = RistrettoPoint::random(&mut rng); assert_eq!( RootSecret::<24>::post_h(RootSecret::<24>::pre_h(a, b), c), - RootSecret::<24>::h(a, c, b) + h(a, c, b) ); } } @@ -777,7 +785,8 @@ mod tests { // 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(); @@ -786,12 +795,12 @@ 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; - let mut rng = OsRng::default(); for _i in 0..number_of_messages { - let secret2 = RootSecret::<24>::generate(); + 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 { From a64935688f4407e17a64c7e6d3db35d08db6492d Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Sat, 22 May 2021 12:41:13 -0700 Subject: [PATCH 6/9] Include brute-force --- brute-force | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brute-force b/brute-force index 51e3b4d..d6f6cbc 160000 --- a/brute-force +++ b/brute-force @@ -1 +1 @@ -Subproject commit 51e3b4d549a439e512509c080e2f1fbb1d3e7aa4 +Subproject commit d6f6cbccb19fec9315bac94532f1938986e2c533 From 7cb8207ece2b5ff491f97cda2acbd9c6879f4db9 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Sat, 22 May 2021 12:47:14 -0700 Subject: [PATCH 7/9] Rely on brute-force clone for now --- Cargo.toml | 2 +- brute-force | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4bb0f8c..28ef2a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ curve25519-dalek = { package = "curve25519-dalek-ng", version="4.0.1", features sha3 = "0.9.1" serde = {version="1.0.123", features=["derive"]} bit-vec = {version="0.6.3"} -brute-force = {path="./brute-force", version="0.1.1", 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] diff --git a/brute-force b/brute-force index d6f6cbc..17b99ba 160000 --- a/brute-force +++ b/brute-force @@ -1 +1 @@ -Subproject commit d6f6cbccb19fec9315bac94532f1938986e2c533 +Subproject commit 17b99ba61a820fe66a5a9caabb69494f6cce6163 From 652cd8867ad0551a0fcb170872cd1b656584e4b3 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Sat, 22 May 2021 12:48:26 -0700 Subject: [PATCH 8/9] Remove submodule --- brute-force | 1 - 1 file changed, 1 deletion(-) delete mode 160000 brute-force diff --git a/brute-force b/brute-force deleted file mode 160000 index 17b99ba..0000000 --- a/brute-force +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 17b99ba61a820fe66a5a9caabb69494f6cce6163 From 557fa4f1d7107804543288874e7e498aea37c6f8 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Sat, 22 May 2021 12:49:11 -0700 Subject: [PATCH 9/9] Format and Version Bump --- Cargo.toml | 2 +- benches/entangled.rs | 10 ++++++++-- src/lib.rs | 11 +++++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 28ef2a7..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" diff --git a/benches/entangled.rs b/benches/entangled.rs index 651724e..5e35b53 100644 --- a/benches/entangled.rs +++ b/benches/entangled.rs @@ -1,7 +1,7 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use fuzzytags::{RootSecret, TaggingKey}; -use std::time::Duration; use rand::rngs::OsRng; +use std::time::Duration; fn benchmark_entangled(c: &mut Criterion) { let mut group = c.benchmark_group("entangling"); @@ -14,7 +14,13 @@ fn benchmark_entangled(c: &mut Criterion) { 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)) + b.iter(|| { + TaggingKey::generate_entangled_tag( + vec![public_key_1.clone(), public_key_2.clone()], + &mut rng, + *p, + ) + }) }); } } diff --git a/src/lib.rs b/src/lib.rs index b8932a6..7e7050d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,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")] @@ -29,7 +30,6 @@ use rayon::iter::IntoParallelRefIterator; use rayon::iter::ParallelIterator; #[cfg(feature = "bulk_verify")] use std::sync::mpsc::channel; -use rand_core::{RngCore, CryptoRng}; /// A tag is a probabilistic cryptographic structure. When constructed for a given `TaggingKey` /// it will pass the `DetectionKey::test_tag` 100% of the time. For other tagging keys @@ -214,7 +214,6 @@ impl RootSecret<{ GAMMA }> { /// let secret = RootSecret::<24>::generate(&mut rng); /// ``` pub fn generate(rng: &mut R) -> RootSecret<{ GAMMA }> { - let mut secret = vec![]; for _i in 0..GAMMA { let sk_i = Scalar::random(rng); @@ -691,7 +690,9 @@ mod tests { fn test_multiple() { use crate::TaggingKey; let mut rng = OsRng; - let secrets: Vec> = (0..2).map(|_x| RootSecret::<24>::generate(&mut rng)).collect(); + 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 ~2 minutes on a standard desktop to find a length=24 match for 2 parties, so for testing let's keep things light @@ -710,7 +711,9 @@ mod tests { fn test_check_multiple() { use crate::TaggingKey; let mut rng = OsRng; - let secrets: Vec> = (0..2).map(|_x| RootSecret::<24>::generate(&mut rng)).collect(); + 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 ~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);