This commit is contained in:
Sarah Jamie Lewis 2021-02-05 13:57:25 -08:00
parent a9f10651cc
commit 49134d643d
3 changed files with 192 additions and 188 deletions

View File

@ -45,20 +45,20 @@ Now, instead of each party adopting a download-everything approach to metadata p
or other assumptions) we can leverage fuzzytags to reduce the number of messages downloaded from the server by each party or other assumptions) we can leverage fuzzytags to reduce the number of messages downloaded from the server by each party
while maintaining a formalized concept of metadata privacy. while maintaining a formalized concept of metadata privacy.
Every party generates a `FuzzyTagKeyPair`, consisting of a `FuzzyTagSecretKey` and a `FuzzyTagPublicKey`. These keys will Every party generates a `RootSecret`, from which they can derive a `DetectionKey` and a `TaggingKey`. These keys will
be generated with a parameter _γ_ that relates to the minimum false-positive probability 2^-γ. be generated with a parameter _γ_ that relates to the minimum false-positive probability 2^-γ.
When submitting messages to the server for an intended **recipient**, the **sender** will generate a new tag When submitting messages to the server for an intended **recipient**, the **sender** will generate a new tag
from the **recipients** `FuzzyTagPublicKey`. from the **recipients** `TaggingKey`.
All parties will `extract` a `FuzzyTagDetectionKey` from their key pair. This key will be of length `n` and provide All parties will `extract` a `DetectionKey` from their key pair. This key will be of length `n` and provide
a false positive detection probability of 0 <= 2^-n <= 2^-γ. This detection key can be given to an adversarial server. a false positive detection probability of 0 <= 2^-n <= 2^-γ. This detection key can be given to an adversarial server.
When fetching new messages from the adversarial server, the server first runs a `test` of the tag of the message against When fetching new messages from the adversarial server, the server first runs a `test` of the tag of the message against
the parties' detection key. If the tag passes the test, the message (along with the tag) is provided to the **recipient**. the parties' detection key. If the tag passes the test, the message (along with the tag) is provided to the **recipient**.
Finally, the **recipient** runs their own `test` of the tag against an extracted detection key such that Finally, the **recipient** runs their own `test` of the tag against an extracted detection key such that
`FuzzyTagSecretKey == FuzzyTagDetectionKey` i.e. the probability of a false positive will be 2^-n == 2^-γ. This will the probability of a false positive will be 2^-n == 2^-γ. This will
produce a subset of messages likely intended for the **recipient**, with a smaller probability of false positives. produce a subset of messages likely intended for the **recipient**, with a smaller probability of false positives.
Alternatively the **recipient** can simply try and decrypt every message in the subset of messages that the server Alternatively the **recipient** can simply try and decrypt every message in the subset of messages that the server
@ -66,30 +66,31 @@ provided them (depending on the efficiency of the decryption method).
## Usage ## Usage
Generate a key pair: A party first needs to generate `RootSecret`
use fuzzytags::FuzzySecretKey; use fuzzytags::RootSecret;
let secret_key = FuzzySecretKey::<24>::generate(); let secret = RootSecret::<24>::generate();
`key.public_key` can be given to parties who you want to be able to communicate with you over a specific anonymous From the secret detection key a party can derive a `DetectionKey` which can be given to adversarial server to
messaging service / privacy-preserving application. fuzzily detect tags on behalf of the party.
`key.detection_key` can be given to untrusted _adversarial_ servers. From the secret detection key a party can also derive a `TaggingKey` that can be public and given to
other parties for the purpose of generating fuzzytags addressed to a given party.
`gamma` is security property (_γ_) in the system. For a given gamma, a tag generated for a specific public key will The `24` in the above code is a security property (_γ_) in the system. For a given gamma, a tag generated for a specific public key will
validate against a random public key with a maximum probability of _2^-gamma_. validate against a random public key with a maximum probability of _2^-gamma_.
## Generating Tags ## Generating Tags
Once in possession of a public key, a party in a metadata resistant app can use it to generate tags: Once in possession of a tagging key, a party in a metadata resistant app can use it to generate tags:
use fuzzytags::FuzzySecretKey; use fuzzytags::RootSecret;
let secret_key = FuzzySecretKey::<24>::generate(); let secret = RootSecret::<24>::generate();
let public_key = secret_key.public_key(); let tagging_key = secret.tagging_key();
// Give public key to a another party... // Give public key to a another party...
// and then they can do... // and then they can do...
let tag = public_key.generate_tag(); let tag = tagging_key.generate_tag();
These tags can then be attached to a message in a metadata resistant system. These tags can then be attached to a message in a metadata resistant system.
@ -99,15 +100,15 @@ 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.: 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::FuzzySecretKey; use fuzzytags::RootSecret;
let secret_key = FuzzySecretKey::<24>::generate(); let secret = RootSecret::<24>::generate();
let public_key = secret_key.public_key(); let tagging_key = secret.tagging_key();
// extract a detection key // extract a detection key
let detection_key = secret_key.extract(5); let detection_key = secret.extract_detection_key(5);
// Give public key to a another party... // Give the tagging key to a another party...
// and then they can do... // and then they can do...
let tag = public_key.generate_tag(); let tag = tagging_key.generate_tag();
// The server can now do this: // The server can now do this:
if detection_key.test_tag(&tag) { if detection_key.test_tag(&tag) {
@ -118,20 +119,20 @@ This extracted key can then be given to an adversarial server. The server can th
## Entangled Tags ## Entangled Tags
When enabled with the `entangled` feature the `FuzzyPublicKey::generate_entangled_tag` function is available. This When enabled with the `entangled` feature the `TaggingKey::generate_entangled_tag` function is available. This
allows you to generate tags that will validate against **multiple** detection keys from **distinct public keys** and allows you to generate tags that will validate against **multiple** detection keys from **distinct tagging keys** and
opens up applications like **multiple broadcast** and **deniable sending**. opens up applications like **multiple broadcast** and **deniable sending**.
use fuzzytags::{FuzzySecretKey, FuzzyPublicKey}; use fuzzytags::{RootSecret, TaggingKey};
let secret_key_1 = FuzzySecretKey::<24>::generate(); let secret_1 = RootSecret::<24>::generate();
let secret_key_2 = FuzzySecretKey::<24>::generate(); let secret_2 = RootSecret::<24>::generate();
let public_key_1 = secret_key_1.public_key(); // give this to a sender let tagging_key_1 = secret_1.tagging_key(); // give this to a sender
let public_key_2 = secret_key_2.public_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_key_1 and secret_key_2 up // Will validate for detection keys derived from both secret_1 and secret_2 up
// to n=8 // to n=8
#[cfg(feature = "entangled")] #[cfg(feature = "entangled")]
let tag = FuzzyPublicKey::generate_entangled_tag(vec![public_key_1,public_key_2], 8); let tag = TaggingKey::generate_entangled_tag(vec![tagging_key_1,tagging_key_2], 8);
## Serialization ## Serialization
@ -139,22 +140,22 @@ This crate relies on `serde` for serialization. FuzzyTags are first compressed i
`γ` bits, padded to the end with zeros to the nearest byte. This representation can then be exchanged using a number `γ` bits, padded to the end with zeros to the nearest byte. This representation can then be exchanged using a number
of different approaches e.g.: of different approaches e.g.:
use fuzzytags::FuzzySecretKey; use fuzzytags::RootSecret;
use fuzzytags::FuzzyTag; use fuzzytags::Tag;
let secret_key = FuzzySecretKey::<24>::generate(); let secret = RootSecret::<24>::generate();
let public_key = secret_key.public_key(); let tagging_key = secret.tagging_key();
// Give public key to a another party... // Give public key to a another party...
// and then they can do... // and then they can do...
let tag = public_key.generate_tag(); let tag = tagging_key.generate_tag();
// An example using JSON serialization...see serde doc for other formats: // An example using JSON serialization...see serde doc for other formats:
let serialized_tag = serde_json::to_string(&tag).unwrap(); let serialized_tag = serde_json::to_string(&tag).unwrap();
println!("Serialized: {}", serialized_tag); println!("Serialized: {}", serialized_tag);
// We can then deserialize with: // We can then deserialize with:
let deserialized_tag: Result<FuzzyTag<24>, serde_json::Error> = serde_json::from_str(&serialized_tag); let deserialized_tag: Result<Tag<24>, serde_json::Error> = serde_json::from_str(&serialized_tag);
println!("Deserialized: {}", deserialized_tag.unwrap()); println!("Deserialized: {}", deserialized_tag.unwrap());
## Benchmarks ## Benchmarks

View File

@ -1,14 +1,14 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use fuzzytags::FuzzySecretKey; use fuzzytags::RootSecret;
use std::time::Duration; use std::time::Duration;
fn benchmark_generate_tag(c: &mut Criterion) { fn benchmark_generate_tag(c: &mut Criterion) {
let mut group = c.benchmark_group("generate_tags"); let mut group = c.benchmark_group("generate_tags");
group.measurement_time(Duration::new(10, 0)); group.measurement_time(Duration::new(10, 0));
group.sample_size(1000); group.sample_size(1000);
let secret_key = FuzzySecretKey::<24>::generate(); let secret_key = RootSecret::<24>::generate();
for p in [5, 10, 15].iter() { for p in [5, 10, 15].iter() {
let public_key = secret_key.public_key(); 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()));
} }
} }
@ -17,11 +17,11 @@ fn benchmark_test_tag(c: &mut Criterion) {
let mut group = c.benchmark_group("test_tags"); let mut group = c.benchmark_group("test_tags");
group.measurement_time(Duration::new(10, 0)); group.measurement_time(Duration::new(10, 0));
group.sample_size(1000); group.sample_size(1000);
let secret_key = FuzzySecretKey::<24>::generate(); let secret_key = RootSecret::<24>::generate();
for p in [5, 10, 15].iter() { for p in [5, 10, 15].iter() {
let tag = secret_key.public_key().generate_tag(); let tag = secret_key.tagging_key().generate_tag();
let detection_key = secret_key.extract(*p); 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)));
} }
} }

View File

@ -22,21 +22,21 @@ use brute_force::adaptors;
#[cfg(feature = "entangled")] #[cfg(feature = "entangled")]
use brute_force::brute_force; use brute_force::brute_force;
/// A tag is a probabilistic cryptographic structure. When constructed for a given `FuzzyPublicKey` /// A tag is a probabilistic cryptographic structure. When constructed for a given `TaggingKey`
/// it will pass the `FuzzyDetectionKey::test` 100% of the time. For other public keys /// 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. /// it will pass the test with probability `GAMMA` related to the security parameter of the system.
/// This system provides the following security properties: /// This system provides the following security properties:
/// * Correctness: Valid tags constructed for a specific public key will always validate when tested using the detection key /// * Correctness: Valid tags constructed for a specific tagging key will always validate when tested using the detection key
/// * Fuzziness: Invalid tags will produce false positives with probability _p_ related to the security property (_γ_) /// * Fuzziness: Invalid tags will produce false positives with probability _p_ related to the security property (_γ_)
/// * Security: An adversarial server with access to the detection key is unable to distinguish false positives from true positives. (Detection Ambiguity) /// * Security: An adversarial server with access to the detection key is unable to distinguish false positives from true positives. (Detection Ambiguity)
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct FuzzyTag<const GAMMA: u8> { pub struct Tag<const GAMMA: u8> {
u: RistrettoPoint, u: RistrettoPoint,
y: Scalar, y: Scalar,
ciphertexts: BitVec, ciphertexts: BitVec,
} }
impl<const GAMMA: u8> Serialize for FuzzyTag<{ GAMMA }> { impl<const GAMMA: u8> Serialize for Tag<{ GAMMA }> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
@ -51,7 +51,7 @@ impl<const GAMMA: u8> Serialize for FuzzyTag<{ GAMMA }> {
} }
} }
impl<'de, const GAMMA: u8> Deserialize<'de> for FuzzyTag<{ GAMMA }> { impl<'de, const GAMMA: u8> Deserialize<'de> for Tag<{ GAMMA }> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
@ -59,13 +59,13 @@ impl<'de, const GAMMA: u8> Deserialize<'de> for FuzzyTag<{ GAMMA }> {
struct FuzzyTagVisitor<const GAMMA: u8>; struct FuzzyTagVisitor<const GAMMA: u8>;
impl<'de, const GAMMA: u8> Visitor<'de> for FuzzyTagVisitor<{ GAMMA }> { impl<'de, const GAMMA: u8> Visitor<'de> for FuzzyTagVisitor<{ GAMMA }> {
type Value = FuzzyTag<{ GAMMA }>; type Value = Tag<{ GAMMA }>;
fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
formatter.write_str("64 bytes + GAMMA+bits of data") formatter.write_str("64 bytes + GAMMA+bits of data")
} }
fn visit_seq<A>(self, mut seq: A) -> Result<FuzzyTag<{ GAMMA }>, A::Error> fn visit_seq<A>(self, mut seq: A) -> Result<Tag<{ GAMMA }>, A::Error>
where where
A: serde::de::SeqAccess<'de>, A: serde::de::SeqAccess<'de>,
{ {
@ -79,7 +79,7 @@ impl<'de, const GAMMA: u8> Deserialize<'de> for FuzzyTag<{ GAMMA }> {
_ => break, _ => break,
} }
} }
FuzzyTag::<GAMMA>::decompress(&bytes).ok_or(serde::de::Error::custom("invalid fuzzytag")) Tag::<GAMMA>::decompress(&bytes).ok_or(serde::de::Error::custom("invalid fuzzytag"))
} }
} }
@ -88,21 +88,21 @@ impl<'de, const GAMMA: u8> Deserialize<'de> for FuzzyTag<{ GAMMA }> {
} }
} }
impl<const GAMMA: u8> FuzzyTag<{ GAMMA }> { impl<const GAMMA: u8> Tag<{ GAMMA }> {
/// An optimal sized copy of the tag /// An optimal sized copy of the tag
/// Compressed u || y || ciphertext /// Compressed u || y || ciphertext
/// Ciphertext is right-padded with zeros to the nearest byte /// 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) /// You probably want to use one of the many serde `serialize` apis instead (see README)
/// ``` /// ```
/// use fuzzytags::FuzzySecretKey; /// use fuzzytags::RootSecret;
/// let secret_key = FuzzySecretKey::<24>::generate(); /// let secret = RootSecret::<24>::generate();
/// let public_key = secret_key.public_key(); /// let tagging_key = secret.tagging_key();
/// // extract a detection key /// // extract a detection key
/// let detection_key = secret_key.extract(5); /// let detection_key = secret.extract_detection_key(5);
/// ///
/// // Give public key to a another party... /// // Give tagging key to a another party...
/// // and then they can do... /// // and then they can do...
/// let tag = public_key.generate_tag(); /// let tag = tagging_key.generate_tag();
/// let compressed_tag = tag.compress(); /// let compressed_tag = tag.compress();
/// ``` /// ```
pub fn compress(&self) -> Vec<u8> { pub fn compress(&self) -> Vec<u8> {
@ -116,20 +116,20 @@ impl<const GAMMA: u8> FuzzyTag<{ GAMMA }> {
/// decompress an optimally encoded fuzzytag byte array, returns None if invalid /// decompress an optimally encoded fuzzytag byte array, returns None if invalid
/// You probably want to use one of the many serde `deserialize` apis instead (see README) /// You probably want to use one of the many serde `deserialize` apis instead (see README)
/// ``` /// ```
/// use fuzzytags::{FuzzySecretKey, FuzzyTag}; /// use fuzzytags::{RootSecret, Tag};
/// let secret_key = FuzzySecretKey::<24>::generate(); /// let secret = RootSecret::<24>::generate();
/// let public_key = secret_key.public_key(); /// let tagging_key = secret.tagging_key();
/// // extract a detection key /// // extract a detection key
/// let detection_key = secret_key.extract(5); /// let detection_key = secret.extract_detection_key(5);
/// ///
/// // Give public key to a another party... /// // Give tagging key to a another party...
/// // and then they can do... /// // and then they can do...
/// let tag = public_key.generate_tag(); /// let tag = tagging_key.generate_tag();
/// let compressed_tag = tag.compress(); /// let compressed_tag = tag.compress();
/// let decompressed_tag = FuzzyTag::decompress(&compressed_tag).unwrap(); /// let decompressed_tag = Tag::decompress(&compressed_tag).unwrap();
/// assert_eq!(tag, decompressed_tag); /// assert_eq!(tag, decompressed_tag);
/// ``` /// ```
pub fn decompress(bytes: &[u8]) -> Option<FuzzyTag<{ GAMMA }>> { pub fn decompress(bytes: &[u8]) -> Option<Tag<{ GAMMA }>> {
if bytes.len() > 64 { if bytes.len() > 64 {
let (u_bytes, rest) = bytes.split_at(32); let (u_bytes, rest) = bytes.split_at(32);
let (y_bytes, ciphertext) = rest.split_at(32); let (y_bytes, ciphertext) = rest.split_at(32);
@ -148,7 +148,7 @@ impl<const GAMMA: u8> FuzzyTag<{ GAMMA }> {
let mut ciphertexts = BitVec::from_bytes(ciphertext); let mut ciphertexts = BitVec::from_bytes(ciphertext);
ciphertexts.truncate(GAMMA as usize); 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(FuzzyTag { u, y, ciphertexts }), (Some(u), Some(y)) => Some(Tag { u, y, ciphertexts }),
_ => None, _ => None,
}; };
} }
@ -156,7 +156,7 @@ impl<const GAMMA: u8> FuzzyTag<{ GAMMA }> {
} }
} }
impl<const GAMMA: u8> Display for FuzzyTag<{ GAMMA }> { impl<const GAMMA: u8> Display for Tag<{ GAMMA }> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!( write!(
f, f,
@ -168,64 +168,67 @@ impl<const GAMMA: u8> Display for FuzzyTag<{ GAMMA }> {
} }
} }
/// The complete secret key. Can't directly be used for testing. Instead you will need to generate /// The complete secret. Can't directly be used for testing. Instead you will need to generate
/// a FuzzyDetectionKey using extract /// a DetectionKey using `extract_detection_key`
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct FuzzySecretKey<const GAMMA: u8> { pub struct RootSecret<const GAMMA: u8> {
/// the detection key - this can be given to adversarial servers to help probabilistically /// the detection key - this can be given to adversarial servers to help probabilistically
/// filter messages (with a false-positive rate derived from γ and a 0% false negative rate) /// filter messages (with a false-positive rate derived from γ and a 0% false negative rate)
secret_key: Vec<Scalar>, secret: Vec<Scalar>,
/// the public key - this can be given to people who you want to contact you /// the tagging key - this can be given to people who you want to contact you
public_key: FuzzyPublicKey<{ GAMMA }>, tagging_key: TaggingKey<{ GAMMA }>,
} }
impl<const GAMMA: u8> FuzzySecretKey<{ GAMMA }> { impl<const GAMMA: u8> RootSecret<{ GAMMA }> {
/// Generate a new Key Pair given a security parameter `GAMMA`. Tags generated for a given /// Generate a new Key Pair given a security parameter `GAMMA`. Tags generated for a given
/// `FuzzyPublicKey::flag` will pass the `FuzzyDetectionKey::test` for other public /// `TaggingKey::generate_tag` will pass the `DetectionKey::test_tag` for other tagging
/// keys with probability $ 2 ^ -8 $ /// keys with probability $ 2 ^ -8 $
/// Example: /// Example:
/// ``` /// ```
/// use fuzzytags::{FuzzySecretKey}; /// use fuzzytags::{RootSecret};
/// let secret_key = FuzzySecretKey::<24>::generate(); /// let secret = RootSecret::<24>::generate();
/// ``` /// ```
pub fn generate() -> FuzzySecretKey<{ GAMMA }> { pub fn generate() -> RootSecret<{ GAMMA }> {
let mut rng = OsRng::default(); let mut rng = OsRng::default();
let g = RISTRETTO_BASEPOINT_POINT; let g = RISTRETTO_BASEPOINT_POINT;
let mut secret_key = vec![]; let mut secret = vec![];
let mut p_keys = vec![]; let mut p_keys = vec![];
for _i in 0..GAMMA { for _i in 0..GAMMA {
let sk_i = Scalar::random(&mut rng); let sk_i = Scalar::random(&mut rng);
let pk_i = g.mul(sk_i); let pk_i = g.mul(sk_i);
secret_key.push(sk_i); secret.push(sk_i);
p_keys.push(pk_i); p_keys.push(pk_i);
} }
FuzzySecretKey::<GAMMA> { RootSecret::<GAMMA> {
secret_key, secret,
public_key: FuzzyPublicKey { 0: p_keys }, tagging_key: TaggingKey { 0: p_keys },
} }
} }
/// extract a detection key for a given false positive (p = 2^-n) /// extract a detection key for a given false positive (p = 2^-n)
/// This is the key that can be given to adversarail servers so that they can
/// detected messages that *may* be tagged for a given detection key with an
/// ideal false positive rate 2^{-n}
/// Example: /// Example:
/// ``` /// ```
/// use fuzzytags::{FuzzySecretKey}; /// use fuzzytags::{RootSecret};
/// let secret_key = FuzzySecretKey::<24>::generate(); /// let secret = RootSecret::<24>::generate();
/// let detection_key = secret_key.extract(2); /// let detection_key = secret.extract_detection_key(2);
/// ``` /// ```
pub fn extract(&self, n: usize) -> FuzzyDetectionKey<{ GAMMA }> { pub fn extract_detection_key(&self, n: usize) -> DetectionKey<{ GAMMA }> {
let parts = self.secret_key.iter().take(n).cloned().collect(); let parts = self.secret.iter().take(n).cloned().collect();
FuzzyDetectionKey::<GAMMA> { 0: parts } DetectionKey::<GAMMA> { 0: parts }
} }
/// derive the public key for this key /// derive the tagging key for this secret
/// Example: /// Example:
/// ``` /// ```
/// use fuzzytags::FuzzySecretKey; /// use fuzzytags::RootSecret;
/// let secret_key = FuzzySecretKey::<24>::generate(); /// let secret = RootSecret::<24>::generate();
/// let public_key = secret_key.public_key(); /// let tagging_key = secret.tagging_key();
/// ``` /// ```
pub fn public_key(&self) -> FuzzyPublicKey<{ GAMMA }> { pub fn tagging_key(&self) -> TaggingKey<{ GAMMA }> {
self.public_key.clone() self.tagging_key.clone()
} }
/// a hash function that takes 3 ristretto points as a parameter and outputs 0 or 1. /// a hash function that takes 3 ristretto points as a parameter and outputs 0 or 1.
@ -250,11 +253,11 @@ impl<const GAMMA: u8> FuzzySecretKey<{ GAMMA }> {
} }
/// A collection of "secret" data that can be used to determine if a `FuzzyTag` was intended for /// A collection of "secret" data that can be used to determine if a `FuzzyTag` was intended for
/// the derived public key with probability p /// the derived tagging key with probability p
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FuzzyDetectionKey<const GAMMA: u8>(Vec<Scalar>); pub struct DetectionKey<const GAMMA: u8>(Vec<Scalar>);
impl<const GAMMA: u8> FuzzyDetectionKey<{ GAMMA }> { impl<const GAMMA: u8> DetectionKey<{ GAMMA }> {
/// a convenient id for a detection key for internal accounting purposes /// a convenient id for a detection key for internal accounting purposes
/// do not expose this to applications /// do not expose this to applications
pub fn id(&self) -> String { pub fn id(&self) -> String {
@ -266,20 +269,20 @@ impl<const GAMMA: u8> FuzzyDetectionKey<{ GAMMA }> {
} }
} }
impl<const GAMMA: u8> Display for FuzzyDetectionKey<{ GAMMA }> { impl<const GAMMA: u8> Display for DetectionKey<{ GAMMA }> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.id()) write!(f, "{}", self.id())
} }
} }
impl<const GAMMA: u8> FuzzyDetectionKey<{ GAMMA }> { impl<const GAMMA: u8> DetectionKey<{ GAMMA }> {
/// calculate the ideal false positive rate of this detection key /// calculate the ideal false positive rate of this detection key
/// ``` /// ```
/// use fuzzytags::FuzzySecretKey; /// use fuzzytags::RootSecret;
/// let secret_key = FuzzySecretKey::<24>::generate(); /// let secret = RootSecret::<24>::generate();
/// let public_key = secret_key.public_key(); /// let tagging_key = secret.tagging_key();
/// // extract a detection key /// // extract a detection key
/// let detection_key = secret_key.extract(5); /// let detection_key = secret.extract_detection_key(5);
/// detection_key.false_positive_probability(); /// detection_key.false_positive_probability();
/// ``` /// ```
pub fn false_positive_probability(&self) -> f64 { pub fn false_positive_probability(&self) -> f64 {
@ -289,15 +292,15 @@ impl<const GAMMA: u8> FuzzyDetectionKey<{ GAMMA }> {
/// returns true if the tag was intended for this key /// returns true if the tag was intended for this key
/// Example: /// Example:
/// ``` /// ```
/// use fuzzytags::FuzzySecretKey; /// use fuzzytags::RootSecret;
/// let secret_key = FuzzySecretKey::<24>::generate(); /// let secret = RootSecret::<24>::generate();
/// let public_key = secret_key.public_key(); /// let tagging_key = secret.tagging_key();
/// // extract a detection key /// // extract a detection key
/// let detection_key = secret_key.extract(5); /// let detection_key = secret.extract_detection_key(5);
/// ///
/// // Give public key to a another party... /// // Give tagging key to a another party...
/// // and then they can do... /// // and then they can do...
/// let tag = public_key.generate_tag(); /// let tag = tagging_key.generate_tag();
/// ///
/// // The server can now do this: /// // The server can now do this:
/// if detection_key.test_tag(&tag) { /// if detection_key.test_tag(&tag) {
@ -306,20 +309,20 @@ impl<const GAMMA: u8> FuzzyDetectionKey<{ GAMMA }> {
/// // the message attached to this tag is definitely *not* for the party associated with the detection key. /// // the message attached to this tag is definitely *not* for the party associated with the detection key.
/// } /// }
/// ``` /// ```
pub fn test_tag(&self, tag: &FuzzyTag<{ GAMMA }>) -> bool { pub fn test_tag(&self, tag: &Tag<{ GAMMA }>) -> bool {
// A few checks to make sure the tag is well formed. // 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* public keys // 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. // That doesn't seem like a great idea, so we return false to be safe.
// Zero values should never appear in well generated tags. // Zero values should never appear in well generated tags.
if tag.u.eq(&RistrettoPoint::default()) || tag.y.eq(&Scalar::zero()) { if tag.u.eq(&RistrettoPoint::default()) || tag.y.eq(&Scalar::zero()) {
return false; return false;
} }
let m = FuzzySecretKey::<GAMMA>::g(tag.u, &tag.ciphertexts); let m = RootSecret::<GAMMA>::g(tag.u, &tag.ciphertexts);
let g = RISTRETTO_BASEPOINT_POINT; let g = RISTRETTO_BASEPOINT_POINT;
// Re-derive w = g^z from the public tag. // Re-derive w = g^z from the public tag.
// y = (1/r * (z-m) // y = (1/r) * (z-m)
// u = g^r // u = g^r
// so w = g^m + u^y // so w = g^m + u^y
// w = g^m + g^(r * 1/r * (z-m)) // w = g^m + g^(r * 1/r * (z-m))
@ -328,11 +331,11 @@ impl<const GAMMA: u8> FuzzyDetectionKey<{ GAMMA }> {
// See below for a full explanation as to the reason for this: // See below for a full explanation as to the reason for this:
let w = RistrettoPoint::multiscalar_mul(&[m, tag.y], &[g, tag.u]); let w = RistrettoPoint::multiscalar_mul(&[m, tag.y], &[g, tag.u]);
// for each secret key part... // for each secret part...
let mut result = true; let mut result = true;
for (i, x_i) in self.0.iter().enumerate() { for (i, x_i) in self.0.iter().enumerate() {
// re-derive the key from the tag // re-derive the key from the tag
let k_i = FuzzySecretKey::<GAMMA>::h(tag.u, tag.u.mul(x_i), w); let k_i = RootSecret::<GAMMA>::h(tag.u, tag.u.mul(x_i), w);
// calculate the "original" plaintext // calculate the "original" plaintext
let c_i = match tag.ciphertexts.get(i) { let c_i = match tag.ciphertexts.get(i) {
@ -354,10 +357,10 @@ impl<const GAMMA: u8> FuzzyDetectionKey<{ GAMMA }> {
/// A public identity that others can create tags for. /// A public identity that others can create tags for.
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FuzzyPublicKey<const GAMMA: u8>(Vec<RistrettoPoint>); pub struct TaggingKey<const GAMMA: u8>(Vec<RistrettoPoint>);
impl<const GAMMA: u8> FuzzyPublicKey<{ GAMMA }> { impl<const GAMMA: u8> TaggingKey<{ GAMMA }> {
/// a convenient id for a public key for internal accounting purposes /// a convenient id for a tagging key for internal accounting purposes
/// do not expose this to applications /// do not expose this to applications
pub fn id(&self) -> String { pub fn id(&self) -> String {
let mut hash = sha3::Sha3_256::new(); let mut hash = sha3::Sha3_256::new();
@ -367,15 +370,15 @@ impl<const GAMMA: u8> FuzzyPublicKey<{ GAMMA }> {
format!("{}", hex::encode(hash.finalize().as_slice()),) format!("{}", hex::encode(hash.finalize().as_slice()),)
} }
/// generate a new tag for this public key /// generate a new tag for this tagging key
/// Example: /// Example:
/// ``` /// ```
/// use fuzzytags::{FuzzySecretKey}; /// use fuzzytags::{RootSecret};
/// let secret_key = FuzzySecretKey::<24>::generate(); /// let secret = RootSecret::<24>::generate();
/// let public_key = secret_key.public_key(); // give this to a sender /// let tagging_key = secret.tagging_key(); // give this to a sender
/// let tag = public_key.generate_tag(); /// let tag = tagging_key.generate_tag();
/// ``` /// ```
pub fn generate_tag(&self) -> FuzzyTag<{ GAMMA }> { pub fn generate_tag(&self) -> Tag<{ GAMMA }> {
let mut rng = OsRng::default(); let mut rng = OsRng::default();
let g = RISTRETTO_BASEPOINT_POINT; let g = RISTRETTO_BASEPOINT_POINT;
@ -388,7 +391,7 @@ impl<const GAMMA: u8> FuzzyPublicKey<{ GAMMA }> {
// construct the ciphertext portion of the tag // construct the ciphertext portion of the tag
let mut ciphertexts = BitVec::new(); let mut ciphertexts = BitVec::new();
for (_i, h_i) in self.0.iter().enumerate() { for (_i, h_i) in self.0.iter().enumerate() {
let k_i = FuzzySecretKey::<GAMMA>::h(u, h_i.mul(r), w); let k_i = RootSecret::<GAMMA>::h(u, h_i.mul(r), w);
// encrypt a plaintext of all 1's // encrypt a plaintext of all 1's
let c_i = k_i ^ 0x01; let c_i = k_i ^ 0x01;
ciphertexts.push(c_i == 0x01); ciphertexts.push(c_i == 0x01);
@ -401,7 +404,7 @@ impl<const GAMMA: u8> FuzzyPublicKey<{ GAMMA }> {
// From the paper: // From the paper:
// "The value w corresponds to a chameleon hash [KR00] computed on the message (0,z), where z is chosen at random. // "The value w corresponds to a chameleon hash [KR00] computed on the message (0,z), where z is chosen at random.
// Once the ciphertext has been computed, we use a master trapdoor for the chameleon hash (which is part of the schemes secret key) in order to compute a collision (y,m) where m // Once the ciphertext has been computed, we use a master trapdoor for the chameleon hash (which is part of the schemes DetectionKey) in order to compute a collision (y,m) where m
// is a hash of the remaining components of the ciphertext" // is a hash of the remaining components of the ciphertext"
// Translated m is a challenge over the random element u and the ordered ciphertexts // Translated m is a challenge over the random element u and the ordered ciphertexts
@ -409,29 +412,29 @@ impl<const GAMMA: u8> FuzzyPublicKey<{ GAMMA }> {
// used to derive the key. // used to derive the key.
// finally calculate a `y` = 1/r * (z-m) which will be used to re-derive `w` // finally calculate a `y` = 1/r * (z-m) which will be used to re-derive `w`
let m = FuzzySecretKey::<GAMMA>::g(u, &ciphertexts); let m = RootSecret::<GAMMA>::g(u, &ciphertexts);
let y = r.invert().mul(z.sub(m)); let y = r.invert().mul(z.sub(m));
return FuzzyTag { u, y, ciphertexts }; return Tag { u, y, ciphertexts };
} }
#[cfg(feature = "entangled")] #[cfg(feature = "entangled")]
/// WARNING: if you pass in a large length into this function it will take a long time! /// WARNING: if you pass in a large length into this function it will take a long time!
/// This begins a very slow, but parallel, search for a tag that will validate of the given /// This begins a very slow, but parallel, search for a tag that will validate of the given
/// public keys up to a given false positive rate 2^-l /// tagging keys up to a given false positive rate 2^-l
/// Example: /// Example:
/// ``` /// ```
/// use fuzzytags::{FuzzySecretKey, FuzzyPublicKey}; /// use fuzzytags::{RootSecret, TaggingKey};
/// let secret_key_1 = FuzzySecretKey::<24>::generate(); /// let secret_1 = RootSecret::<24>::generate();
/// let secret_key_2 = FuzzySecretKey::<24>::generate(); /// let secret_2 = RootSecret::<24>::generate();
/// let public_key_1 = secret_key_1.public_key(); // give this to a sender /// let tagging_key_1 = secret_1.tagging_key(); // give this to a sender
/// let public_key_2 = secret_key_2.public_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_key_1 and secret_key_2 up /// // Will validate for detection keys derived from both secret_1 and secret_2 up
/// // to n=8 /// // to n=8
/// // Sender can now do...tag will validate on detection keys of length 8 or lower. /// // Sender can now do...tag will validate on detection keys of length 8 or lower.
/// let tag = FuzzyPublicKey::generate_entangled_tag(vec![public_key_1,public_key_2], 8); /// let tag = TaggingKey::generate_entangled_tag(vec![tagging_key_1,tagging_key_2], 8);
/// ``` /// ```
pub fn generate_entangled_tag(public_keys: Vec<FuzzyPublicKey<{ GAMMA }>>, length: usize) -> FuzzyTag<{ GAMMA }> { pub fn generate_entangled_tag(tagging_keys: Vec<TaggingKey<{ GAMMA }>>, length: usize) -> Tag<{ GAMMA }> {
let mut rng = OsRng::default(); let mut rng = OsRng::default();
let g = RISTRETTO_BASEPOINT_POINT; let g = RISTRETTO_BASEPOINT_POINT;
// generate some random points... // generate some random points...
@ -439,24 +442,24 @@ impl<const GAMMA: u8> FuzzyPublicKey<{ GAMMA }> {
let u = g.mul(r); let u = g.mul(r);
// Compute and cache some public points that we will be using over and over again // Compute and cache some public points that we will be using over and over again
let mut public_key_precomputes = vec![]; let mut tagging_key_precomputes = vec![];
for public_key in public_keys.iter() { for tagging_key in tagging_keys.iter() {
let mut precompute = vec![]; let mut precompute = vec![];
for i in public_key.0.iter() { for i in tagging_key.0.iter() {
precompute.push(i.mul(r)); precompute.push(i.mul(r));
} }
public_key_precomputes.push(precompute); tagging_key_precomputes.push(precompute);
} }
let config = brute_force::Config::default(); let config = brute_force::Config::default();
let f = |z: &Scalar| { let f = |z: &Scalar| {
let w = g.mul(z); let w = g.mul(z);
let mut key = vec![]; let mut key = vec![];
for (i, precompute) in public_key_precomputes[0].iter().enumerate() { for (i, precompute) in tagging_key_precomputes[0].iter().enumerate() {
let k_i = FuzzySecretKey::<GAMMA>::h(u, *precompute, w); let k_i = RootSecret::<GAMMA>::h(u, *precompute, w);
if i < length { if i < length {
for precompute in public_key_precomputes.iter().skip(1) { for precompute in tagging_key_precomputes.iter().skip(1) {
let n_k_i = FuzzySecretKey::<GAMMA>::h(u, precompute[i], w); let n_k_i = RootSecret::<GAMMA>::h(u, precompute[i], w);
if k_i != n_k_i { if k_i != n_k_i {
return None; return None;
} }
@ -474,9 +477,9 @@ impl<const GAMMA: u8> FuzzyPublicKey<{ GAMMA }> {
} }
// This is the same as generate_tag, kept separate to avoid over-decomposition // This is the same as generate_tag, kept separate to avoid over-decomposition
let m = FuzzySecretKey::<GAMMA>::g(u, &ciphertexts); let m = RootSecret::<GAMMA>::g(u, &ciphertexts);
let y = r.invert().mul(z.sub(m)); let y = r.invert().mul(z.sub(m));
return Some(FuzzyTag { u, y, ciphertexts }); return Some(Tag { u, y, ciphertexts });
}; };
brute_force(config, adaptors::auto_advance(f)) brute_force(config, adaptors::auto_advance(f))
} }
@ -484,48 +487,48 @@ impl<const GAMMA: u8> FuzzyPublicKey<{ GAMMA }> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{FuzzySecretKey, FuzzyTag}; use crate::{RootSecret, Tag};
use bit_vec::BitVec; use bit_vec::BitVec;
use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::ristretto::RistrettoPoint;
use curve25519_dalek::scalar::Scalar; use curve25519_dalek::scalar::Scalar;
#[test] #[test]
fn test_compression() { fn test_compression() {
let secret_key = FuzzySecretKey::<24>::generate(); let secret = RootSecret::<24>::generate();
let public_key = secret_key.public_key(); let tagging_key = secret.tagging_key();
// Give public key to a another party... // Give tagging key to a another party...
// and then they can do... // and then they can do...
let tag = public_key.generate_tag(); let tag = tagging_key.generate_tag();
let compressed_tag = tag.compress(); let compressed_tag = tag.compress();
let decompressed_tag = FuzzyTag::<24>::decompress(&compressed_tag).unwrap(); let decompressed_tag = Tag::<24>::decompress(&compressed_tag).unwrap();
assert_eq!(tag, decompressed_tag); assert_eq!(tag, decompressed_tag);
} }
#[test] #[test]
fn test_serialization() { fn test_serialization() {
// generate some new keys... // generate some new keys...
let secret_key = FuzzySecretKey::<15>::generate(); let secret = RootSecret::<15>::generate();
let tag = secret_key.public_key.generate_tag(); let tag = secret.tagging_key().generate_tag();
let detection_key = secret_key.extract(10); let detection_key = secret.extract_detection_key(10);
let serialized_tag = serde_json::to_string(&tag).unwrap(); let serialized_tag = serde_json::to_string(&tag).unwrap();
let deserialized_tag: FuzzyTag<15> = serde_json::from_str(&serialized_tag).unwrap(); let deserialized_tag: Tag<15> = serde_json::from_str(&serialized_tag).unwrap();
assert_eq!(tag.compress(), deserialized_tag.compress()); assert_eq!(tag.compress(), deserialized_tag.compress());
assert_eq!(true, detection_key.test_tag(&deserialized_tag)); assert_eq!(true, detection_key.test_tag(&deserialized_tag));
// generate some new keys... // generate some new keys...
let secret_key = FuzzySecretKey::<24>::generate(); let secret = RootSecret::<24>::generate();
let tag = secret_key.public_key.generate_tag(); let tag = secret.tagging_key().generate_tag();
let detection_key = secret_key.extract(10); let detection_key = secret.extract_detection_key(10);
let serialized_tag = serde_json::to_string(&tag).unwrap(); let serialized_tag = serde_json::to_string(&tag).unwrap();
let deserialized_tag: FuzzyTag<24> = serde_json::from_str(&serialized_tag).unwrap(); let deserialized_tag: Tag<24> = serde_json::from_str(&serialized_tag).unwrap();
assert_eq!(tag.compress(), deserialized_tag.compress()); assert_eq!(tag.compress(), deserialized_tag.compress());
assert_eq!(true, detection_key.test_tag(&deserialized_tag)); assert_eq!(true, detection_key.test_tag(&deserialized_tag));
// Test some bincode... // Test some bincode...
let bincode_tag = bincode::serialize(&tag); let bincode_tag = bincode::serialize(&tag);
// println!("Serialized: {:?}", bincode_tag); // println!("Serialized: {:?}", bincode_tag);
let deserialized_tag: FuzzyTag<24> = bincode::deserialize(&bincode_tag.unwrap()).unwrap(); let deserialized_tag: Tag<24> = bincode::deserialize(&bincode_tag.unwrap()).unwrap();
//println!("Deserialized: {}", deserialized_tag); //println!("Deserialized: {}", deserialized_tag);
//assert_eq!(tag.compress(), deserialized_tag.compress()); //assert_eq!(tag.compress(), deserialized_tag.compress());
assert_eq!(true, detection_key.test_tag(&deserialized_tag)); assert_eq!(true, detection_key.test_tag(&deserialized_tag));
@ -534,14 +537,14 @@ mod tests {
#[test] #[test]
#[cfg(feature = "entangled")] #[cfg(feature = "entangled")]
fn test_multiple() { fn test_multiple() {
use crate::FuzzyPublicKey; use crate::TaggingKey;
let secret_keys: Vec<FuzzySecretKey<24>> = (0..2).map(|_x| FuzzySecretKey::<24>::generate()).collect(); let secrets: Vec<RootSecret<24>> = (0..2).map(|_x| RootSecret::<24>::generate()).collect();
let public_keys: Vec<FuzzyPublicKey<24>> = secret_keys.iter().map(|x| x.public_key()).collect(); let tagging_keys: Vec<TaggingKey<24>> = 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 // 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 = FuzzyPublicKey::generate_entangled_tag(public_keys, 16); let entangled_tag = TaggingKey::generate_entangled_tag(tagging_keys, 16);
println!("{}", entangled_tag); println!("{}", entangled_tag);
for secret_key in secret_keys.iter() { for secret in secrets.iter() {
let detection_key = secret_key.extract(16); let detection_key = secret.extract_detection_key(16);
assert!(detection_key.test_tag(&entangled_tag)); assert!(detection_key.test_tag(&entangled_tag));
println!("{}", detection_key); println!("{}", detection_key);
} }
@ -550,16 +553,16 @@ mod tests {
#[test] #[test]
fn correctness() { fn correctness() {
let number_of_messages = 100; let number_of_messages = 100;
let secret_key = FuzzySecretKey::<16>::generate(); let secret = RootSecret::<16>::generate();
for i in 0..number_of_messages { for i in 0..number_of_messages {
let tag = secret_key.public_key().generate_tag(); let tag = secret.tagging_key().generate_tag();
println!("{}: {}", i, tag); println!("{}: {}", i, tag);
assert!(secret_key.extract(5).test_tag(&tag)); assert!(secret.extract_detection_key(5).test_tag(&tag));
} }
} }
fn gen_zero_tag_zero() -> FuzzyTag<24> { fn gen_zero_tag_zero() -> Tag<24> {
let tag = FuzzyTag { let tag = Tag {
u: RistrettoPoint::default(), u: RistrettoPoint::default(),
y: Scalar::default(), y: Scalar::default(),
ciphertexts: BitVec::from_elem(24, false), ciphertexts: BitVec::from_elem(24, false),
@ -567,8 +570,8 @@ mod tests {
tag tag
} }
fn gen_zero_tag_one() -> FuzzyTag<24> { fn gen_zero_tag_one() -> Tag<24> {
let mut tag = FuzzyTag { let mut tag = Tag {
u: RistrettoPoint::default(), u: RistrettoPoint::default(),
y: Scalar::default(), y: Scalar::default(),
ciphertexts: BitVec::from_elem(24, false), ciphertexts: BitVec::from_elem(24, false),
@ -579,33 +582,33 @@ mod tests {
#[test] #[test]
// Thanks to Lee Bousfield who noticed an all zeros or all ones tag would // Thanks to Lee Bousfield who noticed an all zeros or all ones tag would
// validate against a public key with 50% probability, allowing universal // validate against a tagging key with 50% probability, allowing universal
// broadcast, which overall seems like a bad idea... // broadcast, which overall seems like a bad idea...
// Test to make sure that doesn't happen. // Test to make sure that doesn't happen.
fn test_zero_tag() { fn test_zero_tag() {
let secret_key = FuzzySecretKey::<24>::generate(); let secret = RootSecret::<24>::generate();
let tag = gen_zero_tag_zero(); let tag = gen_zero_tag_zero();
assert_eq!(false, secret_key.extract(6).test_tag(&tag)); assert_eq!(false, secret.extract_detection_key(6).test_tag(&tag));
let tag = gen_zero_tag_one(); let tag = gen_zero_tag_one();
assert_eq!(false, secret_key.extract(6).test_tag(&tag)); assert_eq!(false, secret.extract_detection_key(6).test_tag(&tag));
} }
#[test] #[test]
fn false_positives() { fn false_positives() {
let number_of_messages = 1000; let number_of_messages = 1000;
let secret_key = FuzzySecretKey::<24>::generate(); let secret = RootSecret::<24>::generate();
let mut false_positives = 0; let mut false_positives = 0;
for _i in 0..number_of_messages { for _i in 0..number_of_messages {
let secret_key2 = FuzzySecretKey::<24>::generate(); let secret2 = RootSecret::<24>::generate();
let tag = secret_key2.public_key().generate_tag(); let tag = secret2.tagging_key().generate_tag();
assert!(secret_key2.extract(3).test_tag(&tag)); assert!(secret2.extract_detection_key(3).test_tag(&tag));
if secret_key.extract(3).test_tag(&tag) == true { if secret.extract_detection_key(3).test_tag(&tag) == true {
false_positives += 1; false_positives += 1;
} }
} }
println!( println!(
"Expected False Positive Rate: {}\nActual False Positive Rate: {}", "Expected False Positive Rate: {}\nActual False Positive Rate: {}",
secret_key.extract(3).false_positive_probability(), secret.extract_detection_key(3).false_positive_probability(),
(false_positives as f64 / number_of_messages as f64) (false_positives as f64 / number_of_messages as f64)
); );
} }