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
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^-γ.
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.
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**.
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.
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
Generate a key pair:
A party first needs to generate `RootSecret`
use fuzzytags::FuzzySecretKey;
let secret_key = FuzzySecretKey::<24>::generate();
use fuzzytags::RootSecret;
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
messaging service / privacy-preserving application.
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.
`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_.
## 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;
let secret_key = FuzzySecretKey::<24>::generate();
let public_key = secret_key.public_key();
use fuzzytags::RootSecret;
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 = public_key.generate_tag();
let tag = tagging_key.generate_tag();
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.:
use fuzzytags::FuzzySecretKey;
let secret_key = FuzzySecretKey::<24>::generate();
let public_key = secret_key.public_key();
use fuzzytags::RootSecret;
let secret = RootSecret::<24>::generate();
let tagging_key = secret.tagging_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...
let tag = public_key.generate_tag();
let tag = tagging_key.generate_tag();
// The server can now do this:
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
When enabled with the `entangled` feature the `FuzzyPublicKey::generate_entangled_tag` function is available. This
allows you to generate tags that will validate against **multiple** detection keys from **distinct public keys** and
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 tagging keys** and
opens up applications like **multiple broadcast** and **deniable sending**.
use fuzzytags::{FuzzySecretKey, FuzzyPublicKey};
let secret_key_1 = FuzzySecretKey::<24>::generate();
let secret_key_2 = FuzzySecretKey::<24>::generate();
let public_key_1 = secret_key_1.public_key(); // give this to a sender
let public_key_2 = secret_key_2.public_key(); // give this to a sender
// Will validate for detection keys derived from both secret_key_1 and secret_key_2 up
use fuzzytags::{RootSecret, TaggingKey};
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
let tagging_key_2 = secret_2.tagging_key(); // give this to a sender
// Will validate for detection keys derived from both secret_1 and secret_2 up
// to n=8
#[cfg(feature = "entangled")]
let tag = 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
@ -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
of different approaches e.g.:
use fuzzytags::FuzzySecretKey;
use fuzzytags::FuzzyTag;
use fuzzytags::RootSecret;
use fuzzytags::Tag;
let secret_key = FuzzySecretKey::<24>::generate();
let public_key = secret_key.public_key();
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 = public_key.generate_tag();
let tag = tagging_key.generate_tag();
// An example using JSON serialization...see serde doc for other formats:
let serialized_tag = serde_json::to_string(&tag).unwrap();
println!("Serialized: {}", serialized_tag);
// 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());
## Benchmarks

View File

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

View File

@ -22,21 +22,21 @@ use brute_force::adaptors;
#[cfg(feature = "entangled")]
use brute_force::brute_force;
/// A tag is a probabilistic cryptographic structure. When constructed for a given `FuzzyPublicKey`
/// it will pass the `FuzzyDetectionKey::test` 100% of the time. For other public keys
/// 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.
/// 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 (_γ_)
/// * 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)]
pub struct FuzzyTag<const GAMMA: u8> {
pub struct Tag<const GAMMA: u8> {
u: RistrettoPoint,
y: Scalar,
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>
where
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>
where
D: Deserializer<'de>,
@ -59,13 +59,13 @@ impl<'de, const GAMMA: u8> Deserialize<'de> for FuzzyTag<{ GAMMA }> {
struct FuzzyTagVisitor<const GAMMA: u8>;
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 {
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
A: serde::de::SeqAccess<'de>,
{
@ -79,7 +79,7 @@ impl<'de, const GAMMA: u8> Deserialize<'de> for FuzzyTag<{ GAMMA }> {
_ => 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
/// Compressed u || y || ciphertext
/// 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 fuzzytags::FuzzySecretKey;
/// let secret_key = FuzzySecretKey::<24>::generate();
/// let public_key = secret_key.public_key();
/// use fuzzytags::RootSecret;
/// let secret = RootSecret::<24>::generate();
/// let tagging_key = secret.tagging_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...
/// let tag = public_key.generate_tag();
/// let tag = tagging_key.generate_tag();
/// let compressed_tag = tag.compress();
/// ```
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
/// You probably want to use one of the many serde `deserialize` apis instead (see README)
/// ```
/// use fuzzytags::{FuzzySecretKey, FuzzyTag};
/// let secret_key = FuzzySecretKey::<24>::generate();
/// let public_key = secret_key.public_key();
/// use fuzzytags::{RootSecret, Tag};
/// let secret = RootSecret::<24>::generate();
/// let tagging_key = secret.tagging_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...
/// let tag = public_key.generate_tag();
/// let tag = tagging_key.generate_tag();
/// 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);
/// ```
pub fn decompress(bytes: &[u8]) -> Option<FuzzyTag<{ GAMMA }>> {
pub fn decompress(bytes: &[u8]) -> Option<Tag<{ GAMMA }>> {
if bytes.len() > 64 {
let (u_bytes, rest) = bytes.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);
ciphertexts.truncate(GAMMA as usize);
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,
};
}
@ -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 {
write!(
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
/// a FuzzyDetectionKey using extract
/// The complete secret. Can't directly be used for testing. Instead you will need to generate
/// a DetectionKey using `extract_detection_key`
#[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
/// filter messages (with a false-positive rate derived from γ and a 0% false negative rate)
secret_key: Vec<Scalar>,
/// the public key - this can be given to people who you want to contact you
public_key: FuzzyPublicKey<{ GAMMA }>,
secret: Vec<Scalar>,
/// the tagging key - this can be given to people who you want to contact you
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
/// `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 $
/// Example:
/// ```
/// use fuzzytags::{FuzzySecretKey};
/// let secret_key = FuzzySecretKey::<24>::generate();
/// use fuzzytags::{RootSecret};
/// let secret = RootSecret::<24>::generate();
/// ```
pub fn generate() -> FuzzySecretKey<{ GAMMA }> {
pub fn generate() -> RootSecret<{ GAMMA }> {
let mut rng = OsRng::default();
let g = RISTRETTO_BASEPOINT_POINT;
let mut secret_key = vec![];
let mut secret = vec![];
let mut p_keys = vec![];
for _i in 0..GAMMA {
let sk_i = Scalar::random(&mut rng);
let pk_i = g.mul(sk_i);
secret_key.push(sk_i);
secret.push(sk_i);
p_keys.push(pk_i);
}
FuzzySecretKey::<GAMMA> {
secret_key,
public_key: FuzzyPublicKey { 0: p_keys },
RootSecret::<GAMMA> {
secret,
tagging_key: TaggingKey { 0: p_keys },
}
}
/// 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:
/// ```
/// use fuzzytags::{FuzzySecretKey};
/// let secret_key = FuzzySecretKey::<24>::generate();
/// let detection_key = secret_key.extract(2);
/// use fuzzytags::{RootSecret};
/// let secret = RootSecret::<24>::generate();
/// let detection_key = secret.extract_detection_key(2);
/// ```
pub fn extract(&self, n: usize) -> FuzzyDetectionKey<{ GAMMA }> {
let parts = self.secret_key.iter().take(n).cloned().collect();
FuzzyDetectionKey::<GAMMA> { 0: parts }
pub fn extract_detection_key(&self, n: usize) -> DetectionKey<{ GAMMA }> {
let parts = self.secret.iter().take(n).cloned().collect();
DetectionKey::<GAMMA> { 0: parts }
}
/// derive the public key for this key
/// derive the tagging key for this secret
/// Example:
/// ```
/// use fuzzytags::FuzzySecretKey;
/// let secret_key = FuzzySecretKey::<24>::generate();
/// let public_key = secret_key.public_key();
/// use fuzzytags::RootSecret;
/// let secret = RootSecret::<24>::generate();
/// let tagging_key = secret.tagging_key();
/// ```
pub fn public_key(&self) -> FuzzyPublicKey<{ GAMMA }> {
self.public_key.clone()
pub fn tagging_key(&self) -> TaggingKey<{ GAMMA }> {
self.tagging_key.clone()
}
/// 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
/// the derived public key with probability p
/// the derived tagging key with probability p
#[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
/// do not expose this to applications
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 {
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
/// ```
/// use fuzzytags::FuzzySecretKey;
/// let secret_key = FuzzySecretKey::<24>::generate();
/// let public_key = secret_key.public_key();
/// use fuzzytags::RootSecret;
/// let secret = RootSecret::<24>::generate();
/// let tagging_key = secret.tagging_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();
/// ```
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
/// Example:
/// ```
/// use fuzzytags::FuzzySecretKey;
/// let secret_key = FuzzySecretKey::<24>::generate();
/// let public_key = secret_key.public_key();
/// use fuzzytags::RootSecret;
/// let secret = RootSecret::<24>::generate();
/// let tagging_key = secret.tagging_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...
/// let tag = public_key.generate_tag();
/// let tag = tagging_key.generate_tag();
///
/// // The server can now do this:
/// 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.
/// }
/// ```
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.
// 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.
// Zero values should never appear in well generated tags.
if tag.u.eq(&RistrettoPoint::default()) || tag.y.eq(&Scalar::zero()) {
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;
// Re-derive w = g^z from the public tag.
// y = (1/r * (z-m)
// y = (1/r) * (z-m)
// u = g^r
// so w = g^m + u^y
// 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:
let w = RistrettoPoint::multiscalar_mul(&[m, tag.y], &[g, tag.u]);
// for each secret key part...
// for each secret part...
let mut result = true;
for (i, x_i) in self.0.iter().enumerate() {
// 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
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.
#[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 }> {
/// a convenient id for a public key for internal accounting purposes
impl<const GAMMA: u8> TaggingKey<{ GAMMA }> {
/// a convenient id for a tagging key for internal accounting purposes
/// do not expose this to applications
pub fn id(&self) -> String {
let mut hash = sha3::Sha3_256::new();
@ -367,15 +370,15 @@ impl<const GAMMA: u8> FuzzyPublicKey<{ GAMMA }> {
format!("{}", hex::encode(hash.finalize().as_slice()),)
}
/// generate a new tag for this public key
/// generate a new tag for this tagging key
/// Example:
/// ```
/// use fuzzytags::{FuzzySecretKey};
/// let secret_key = FuzzySecretKey::<24>::generate();
/// let public_key = secret_key.public_key(); // give this to a sender
/// let tag = public_key.generate_tag();
/// 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();
/// ```
pub fn generate_tag(&self) -> FuzzyTag<{ GAMMA }> {
pub fn generate_tag(&self) -> Tag<{ GAMMA }> {
let mut rng = OsRng::default();
let g = RISTRETTO_BASEPOINT_POINT;
@ -388,7 +391,7 @@ impl<const GAMMA: u8> FuzzyPublicKey<{ GAMMA }> {
// construct the ciphertext portion of the tag
let mut ciphertexts = BitVec::new();
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
let c_i = k_i ^ 0x01;
ciphertexts.push(c_i == 0x01);
@ -401,7 +404,7 @@ impl<const GAMMA: u8> FuzzyPublicKey<{ GAMMA }> {
// From the paper:
// "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"
// 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.
// 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));
return FuzzyTag { u, y, ciphertexts };
return Tag { u, y, ciphertexts };
}
#[cfg(feature = "entangled")]
/// 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
/// public keys up to a given false positive rate 2^-l
/// tagging keys up to a given false positive rate 2^-l
/// Example:
/// ```
/// use fuzzytags::{FuzzySecretKey, FuzzyPublicKey};
/// let secret_key_1 = FuzzySecretKey::<24>::generate();
/// let secret_key_2 = FuzzySecretKey::<24>::generate();
/// let public_key_1 = secret_key_1.public_key(); // give this to a sender
/// let public_key_2 = secret_key_2.public_key(); // give this to a sender
/// // Will validate for detection keys derived from both secret_key_1 and secret_key_2 up
/// use fuzzytags::{RootSecret, TaggingKey};
/// 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
/// let tagging_key_2 = secret_2.tagging_key(); // give this to a sender
/// // Will validate for detection keys derived from both secret_1 and secret_2 up
/// // to n=8
/// // Sender can now do...tag will validate on detection keys of length 8 or lower.
/// let tag = 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 g = RISTRETTO_BASEPOINT_POINT;
// generate some random points...
@ -439,24 +442,24 @@ impl<const GAMMA: u8> FuzzyPublicKey<{ GAMMA }> {
let u = g.mul(r);
// Compute and cache some public points that we will be using over and over again
let mut public_key_precomputes = vec![];
for public_key in public_keys.iter() {
let mut tagging_key_precomputes = vec![];
for tagging_key in tagging_keys.iter() {
let mut precompute = vec![];
for i in public_key.0.iter() {
for i in tagging_key.0.iter() {
precompute.push(i.mul(r));
}
public_key_precomputes.push(precompute);
tagging_key_precomputes.push(precompute);
}
let config = brute_force::Config::default();
let f = |z: &Scalar| {
let w = g.mul(z);
let mut key = vec![];
for (i, precompute) in public_key_precomputes[0].iter().enumerate() {
let k_i = FuzzySecretKey::<GAMMA>::h(u, *precompute, w);
for (i, precompute) in tagging_key_precomputes[0].iter().enumerate() {
let k_i = RootSecret::<GAMMA>::h(u, *precompute, w);
if i < length {
for precompute in public_key_precomputes.iter().skip(1) {
let n_k_i = FuzzySecretKey::<GAMMA>::h(u, precompute[i], w);
for precompute in tagging_key_precomputes.iter().skip(1) {
let n_k_i = RootSecret::<GAMMA>::h(u, precompute[i], w);
if k_i != n_k_i {
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
let m = FuzzySecretKey::<GAMMA>::g(u, &ciphertexts);
let m = RootSecret::<GAMMA>::g(u, &ciphertexts);
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))
}
@ -484,48 +487,48 @@ impl<const GAMMA: u8> FuzzyPublicKey<{ GAMMA }> {
#[cfg(test)]
mod tests {
use crate::{FuzzySecretKey, FuzzyTag};
use crate::{RootSecret, Tag};
use bit_vec::BitVec;
use curve25519_dalek::ristretto::RistrettoPoint;
use curve25519_dalek::scalar::Scalar;
#[test]
fn test_compression() {
let secret_key = FuzzySecretKey::<24>::generate();
let public_key = secret_key.public_key();
let secret = RootSecret::<24>::generate();
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...
let tag = public_key.generate_tag();
let tag = tagging_key.generate_tag();
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);
}
#[test]
fn test_serialization() {
// generate some new keys...
let secret_key = FuzzySecretKey::<15>::generate();
let tag = secret_key.public_key.generate_tag();
let detection_key = secret_key.extract(10);
let secret = RootSecret::<15>::generate();
let tag = secret.tagging_key().generate_tag();
let detection_key = secret.extract_detection_key(10);
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!(true, detection_key.test_tag(&deserialized_tag));
// generate some new keys...
let secret_key = FuzzySecretKey::<24>::generate();
let tag = secret_key.public_key.generate_tag();
let detection_key = secret_key.extract(10);
let secret = RootSecret::<24>::generate();
let tag = secret.tagging_key().generate_tag();
let detection_key = secret.extract_detection_key(10);
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!(true, detection_key.test_tag(&deserialized_tag));
// Test some bincode...
let bincode_tag = bincode::serialize(&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);
//assert_eq!(tag.compress(), deserialized_tag.compress());
assert_eq!(true, detection_key.test_tag(&deserialized_tag));
@ -534,14 +537,14 @@ mod tests {
#[test]
#[cfg(feature = "entangled")]
fn test_multiple() {
use crate::FuzzyPublicKey;
let secret_keys: Vec<FuzzySecretKey<24>> = (0..2).map(|_x| FuzzySecretKey::<24>::generate()).collect();
let public_keys: Vec<FuzzyPublicKey<24>> = secret_keys.iter().map(|x| x.public_key()).collect();
use crate::TaggingKey;
let secrets: Vec<RootSecret<24>> = (0..2).map(|_x| RootSecret::<24>::generate()).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
let entangled_tag = FuzzyPublicKey::generate_entangled_tag(public_keys, 16);
let entangled_tag = TaggingKey::generate_entangled_tag(tagging_keys, 16);
println!("{}", entangled_tag);
for secret_key in secret_keys.iter() {
let detection_key = secret_key.extract(16);
for secret in secrets.iter() {
let detection_key = secret.extract_detection_key(16);
assert!(detection_key.test_tag(&entangled_tag));
println!("{}", detection_key);
}
@ -550,16 +553,16 @@ mod tests {
#[test]
fn correctness() {
let number_of_messages = 100;
let secret_key = FuzzySecretKey::<16>::generate();
let secret = RootSecret::<16>::generate();
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);
assert!(secret_key.extract(5).test_tag(&tag));
assert!(secret.extract_detection_key(5).test_tag(&tag));
}
}
fn gen_zero_tag_zero() -> FuzzyTag<24> {
let tag = FuzzyTag {
fn gen_zero_tag_zero() -> Tag<24> {
let tag = Tag {
u: RistrettoPoint::default(),
y: Scalar::default(),
ciphertexts: BitVec::from_elem(24, false),
@ -567,8 +570,8 @@ mod tests {
tag
}
fn gen_zero_tag_one() -> FuzzyTag<24> {
let mut tag = FuzzyTag {
fn gen_zero_tag_one() -> Tag<24> {
let mut tag = Tag {
u: RistrettoPoint::default(),
y: Scalar::default(),
ciphertexts: BitVec::from_elem(24, false),
@ -579,33 +582,33 @@ mod tests {
#[test]
// 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...
// Test to make sure that doesn't happen.
fn test_zero_tag() {
let secret_key = FuzzySecretKey::<24>::generate();
let secret = RootSecret::<24>::generate();
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();
assert_eq!(false, secret_key.extract(6).test_tag(&tag));
assert_eq!(false, secret.extract_detection_key(6).test_tag(&tag));
}
#[test]
fn false_positives() {
let number_of_messages = 1000;
let secret_key = FuzzySecretKey::<24>::generate();
let secret = RootSecret::<24>::generate();
let mut false_positives = 0;
for _i in 0..number_of_messages {
let secret_key2 = FuzzySecretKey::<24>::generate();
let tag = secret_key2.public_key().generate_tag();
assert!(secret_key2.extract(3).test_tag(&tag));
if secret_key.extract(3).test_tag(&tag) == true {
let secret2 = RootSecret::<24>::generate();
let tag = secret2.tagging_key().generate_tag();
assert!(secret2.extract_detection_key(3).test_tag(&tag));
if secret.extract_detection_key(3).test_tag(&tag) == true {
false_positives += 1;
}
}
println!(
"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)
);
}