From 85dc8ed9ad2dd1f521b7d90d084c4a93a749d907 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Sat, 13 Feb 2021 12:11:18 -0800 Subject: [PATCH] First cut of bulk verification function (feature gated) --- Cargo.toml | 4 ++- src/lib.rs | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 629167d..5a51c49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ 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} +rayon = {version="1.5.0", optional=true} [dev-dependencies] criterion = {version="0.3", features=["html_reports"]} @@ -29,4 +30,5 @@ name = "fuzzy_tags_benches" harness = false [features] -entangled = ["brute-force"] \ No newline at end of file +entangled = ["brute-force"] +bulk_verify = ["rayon"] \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 3877729..baecb60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,15 @@ use brute_force::adaptors; #[cfg(feature = "entangled")] use brute_force::brute_force; +#[cfg(feature = "bulk_verify")] +use std::sync::mpsc::channel; +#[cfg(feature = "bulk_verify")] +use rayon::iter::IndexedParallelIterator; +#[cfg(feature = "bulk_verify")] +use rayon::iter::IntoParallelRefIterator; +#[cfg(feature = "bulk_verify")] +use rayon::iter::ParallelIterator; + /// 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. @@ -349,11 +358,88 @@ impl DetectionKey<{ GAMMA }> { let b_i = k_i ^ c_i; + if b_i != 1 { + return false; + } // assert that the plaintext is all 1's result = result & (b_i == 1); } return result; } + + + /// A bulk testing function that takes in an vector of detection keys and returns a vector + /// of indexes where the tag matched. + #[cfg(feature = "bulk_verify")] + pub fn test_tag_bulk(detection_keys: &Vec>, tag: &Tag<{ GAMMA }>) -> Vec { + // A few checks to make sure the tag is well formed. + // All zeros in u or y can lead to a tag that validates against *all* tagging keys + // That doesn't seem like a great idea, so we return false to be safe. + // Zero values should never appear in well generated tags. + if tag.u.eq(&RistrettoPoint::default()) || tag.y.eq(&Scalar::zero()) { + return vec![]; + } + + let m = RootSecret::::g(tag.u, &tag.ciphertexts); + let g = RISTRETTO_BASEPOINT_POINT; + + // Re-derive w = g^z from the public tag. + // y = (1/r) * (z-m) + // u = g^r + // so w = g^m + u^y + // w = g^m + g^(r * 1/r * (z-m)) + // w = g^m + g^(z-m) + // w = g^z + // 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(); + + // for each secret part... + let mut results : Vec<(usize)> = 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); + + // 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 { + result = false; + break; + } + // assert that the plaintext is all 1's + result = result & (b_i == 1); + } + if result { + tx.send(index); + } + }); + + std::mem::drop(tx); + loop { + let result = rx.recv(); + match result { + Ok(index) => { + results.push(index) + } + _ => { + break; + } + } + } + + return results; + } } /// A public identity that others can create tags for. @@ -488,7 +574,7 @@ impl TaggingKey<{ GAMMA }> { #[cfg(test)] mod tests { - use crate::{RootSecret, Tag}; + use crate::{RootSecret, Tag, DetectionKey}; use bit_vec::BitVec; use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::scalar::Scalar; @@ -568,6 +654,22 @@ mod tests { } } + #[test] + #[cfg(feature = "bulk_verify")] + fn test_check_multiple() { + use crate::TaggingKey; + 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 detection_keys = secrets.iter().map(|x|x.extract_detection_key(16)).collect(); + + let results = DetectionKey::test_tag_bulk(&detection_keys, &entangled_tag); + for result in results.iter() { + assert_eq!(*result, true); + } + } + #[test] fn correctness() { let number_of_messages = 100;