#![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")]
use bit_vec::BitVec;
use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
use curve25519_dalek::digest::Digest;
use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
use curve25519_dalek::scalar::Scalar;
use curve25519_dalek::traits::MultiscalarMul;
use rand::rngs::OsRng;
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use sha3::Sha3_512;
use std::convert::TryFrom;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::ops::{Mul, Sub};
#[cfg(feature = "entangled")]
use brute_force::adaptors;
#[cfg(feature = "entangled")]
use brute_force::brute_force;
#[cfg(feature = "bulk_verify")]
use rayon::iter::IndexedParallelIterator;
#[cfg(feature = "bulk_verify")]
use rayon::iter::IntoParallelRefIterator;
#[cfg(feature = "bulk_verify")]
use rayon::iter::ParallelIterator;
#[cfg(feature = "bulk_verify")]
use std::sync::mpsc::channel;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Tag<const GAMMA: u8> {
u: RistrettoPoint,
y: Scalar,
ciphertexts: BitVec,
}
impl<const GAMMA: u8> Serialize for Tag<{ GAMMA }> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::SerializeTuple;
let compressed = self.compress();
let mut tup = serializer.serialize_tuple(compressed.len())?;
for byte in compressed.iter() {
tup.serialize_element(byte)?;
}
tup.end()
}
}
impl<'de, const GAMMA: u8> Deserialize<'de> for Tag<{ GAMMA }> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct FuzzyTagVisitor<const GAMMA: u8>;
impl<'de, const GAMMA: u8> Visitor<'de> for FuzzyTagVisitor<{ 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<Tag<{ GAMMA }>, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
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"))?);
}
loop {
match seq.next_element().unwrap_or(None) {
Some(x) => bytes.push(x),
_ => break,
}
}
Tag::<GAMMA>::decompress(&bytes).ok_or(serde::de::Error::custom("invalid fuzzytag"))
}
}
deserializer.deserialize_tuple(72, FuzzyTagVisitor::<GAMMA>)
}
}
impl<const GAMMA: u8> Tag<{ GAMMA }> {
pub fn compress(&self) -> Vec<u8> {
let mut bytes = vec![];
bytes.extend_from_slice(self.u.compress().as_bytes());
bytes.extend_from_slice(self.y.as_bytes());
bytes.extend_from_slice(self.ciphertexts.to_bytes().as_slice());
bytes
}
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);
let min_bytes = GAMMA / 8;
if ciphertext.len() < min_bytes as usize {
return None;
}
let y_bytes_fixed = match <[u8; 32]>::try_from(y_bytes) {
Ok(fixed_size) => fixed_size,
_ => return None,
};
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(Tag { u, y, ciphertexts }),
_ => None,
};
}
None
}
}
impl<const GAMMA: u8> Display for Tag<{ GAMMA }> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"{} {} {}",
hex::encode(self.u.compress().as_bytes()),
hex::encode(self.y.as_bytes()),
hex::encode(self.ciphertexts.to_bytes())
)
}
}
#[derive(Serialize, Deserialize)]
pub struct RootSecret<const GAMMA: u8> {
secret: Vec<Scalar>,
}
impl<const GAMMA: u8> RootSecret<{ GAMMA }> {
pub fn generate() -> RootSecret<{ GAMMA }> {
let mut rng = OsRng::default();
let mut secret = vec![];
for _i in 0..GAMMA {
let sk_i = Scalar::random(&mut rng);
secret.push(sk_i);
}
RootSecret::<GAMMA> { secret: secret }
}
pub fn extract_detection_key(&self, n: usize) -> DetectionKey<{ GAMMA }> {
let parts = self.secret.iter().take(n).cloned().collect();
DetectionKey::<GAMMA> { 0: parts }
}
pub fn tagging_key(&self) -> TaggingKey<{ GAMMA }> {
let g = RISTRETTO_BASEPOINT_POINT;
let mut tagging_key = vec![];
for sk_i in self.secret.iter() {
let pk_i = g.mul(sk_i);
tagging_key.push(pk_i);
}
TaggingKey::<GAMMA> { 0: tagging_key }
}
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());
return hash.finalize().as_slice()[0] & 0x01;
}
fn g(u: RistrettoPoint, points: &BitVec) -> Scalar {
let mut input = vec![];
input.push(GAMMA);
input.extend_from_slice(points.to_bytes().as_slice());
input.extend_from_slice(u.compress().as_bytes());
Scalar::hash_from_bytes::<Sha3_512>(input.as_slice())
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DetectionKey<const GAMMA: u8>(Vec<Scalar>);
impl<const GAMMA: u8> DetectionKey<{ GAMMA }> {
pub fn id(&self) -> String {
let mut hash = sha3::Sha3_256::new();
for s in self.0.iter() {
hash.update(s.as_bytes())
}
format!("{}", hex::encode(hash.finalize().as_slice()),)
}
}
impl<const GAMMA: u8> Display for DetectionKey<{ GAMMA }> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.id())
}
}
impl<const GAMMA: u8> DetectionKey<{ GAMMA }> {
pub fn false_positive_probability(&self) -> f64 {
(2.0_f64).powi(0 - (self.0.len() as i32))
}
pub fn test_tag(&self, tag: &Tag<{ GAMMA }>) -> bool {
if tag.u.eq(&RistrettoPoint::default()) || tag.y.eq(&Scalar::zero()) {
return false;
}
let m = RootSecret::<GAMMA>::g(tag.u, &tag.ciphertexts);
let g = RISTRETTO_BASEPOINT_POINT;
let w = RistrettoPoint::multiscalar_mul(&[m, tag.y], &[g, tag.u]);
let mut result = true;
for (i, x_i) in self.0.iter().enumerate() {
let k_i = RootSecret::<GAMMA>::h(tag.u, tag.u.mul(x_i), w);
let c_i = match tag.ciphertexts.get(i) {
Some(true) => 0x01,
Some(false) => 0x00,
_ => 0x00,
};
let b_i = k_i ^ c_i;
if b_i != 1 {
return false;
}
result = result & (b_i == 1);
}
return result;
}
#[cfg(feature = "bulk_verify")]
pub fn test_tag_bulk(detection_keys: &Vec<DetectionKey<{ GAMMA }>>, tag: &Tag<{ GAMMA }>) -> Vec<usize> {
if tag.u.eq(&RistrettoPoint::default()) || tag.y.eq(&Scalar::zero()) {
return vec![];
}
let m = RootSecret::<GAMMA>::g(tag.u, &tag.ciphertexts);
let g = RISTRETTO_BASEPOINT_POINT;
let w = RistrettoPoint::multiscalar_mul(&[m, tag.y], &[g, tag.u]);
let (tx, rx) = channel();
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() {
let k_i = RootSecret::<GAMMA>::h(tag.u, tag.u.mul(x_i), w);
let c_i = match tag.ciphertexts.get(i) {
Some(true) => 0x01,
Some(false) => 0x00,
_ => 0x00,
};
let b_i = k_i ^ c_i;
if b_i != 1 {
result = false;
break;
}
result = result & (b_i == 1);
}
if result {
match tx.send(index) {
_ => {
}
}
}
});
std::mem::drop(tx);
loop {
let result = rx.recv();
match result {
Ok(index) => results.push(index),
_ => {
break;
}
}
}
return results;
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TaggingKey<const GAMMA: u8>(Vec<RistrettoPoint>);
impl<const GAMMA: u8> TaggingKey<{ GAMMA }> {
pub fn id(&self) -> String {
let mut hash = sha3::Sha3_256::new();
for s in self.0.iter() {
hash.update(s.compress().as_bytes())
}
format!("{}", hex::encode(hash.finalize().as_slice()),)
}
pub fn generate_tag(&self) -> Tag<{ GAMMA }> {
let mut rng = OsRng::default();
let g = RISTRETTO_BASEPOINT_POINT;
let r = Scalar::random(&mut rng);
let u = g.mul(r);
let z = Scalar::random(&mut rng);
let w = g.mul(z);
let mut ciphertexts = BitVec::new();
for (_i, h_i) in self.0.iter().enumerate() {
let k_i = RootSecret::<GAMMA>::h(u, h_i.mul(r), w);
let c_i = k_i ^ 0x01;
ciphertexts.push(c_i == 0x01);
}
let m = RootSecret::<GAMMA>::g(u, &ciphertexts);
let y = r.invert().mul(z.sub(m));
return Tag { u, y, ciphertexts };
}
#[cfg(feature = "entangled")]
pub fn generate_entangled_tag(tagging_keys: Vec<TaggingKey<{ GAMMA }>>, length: usize) -> Tag<{ GAMMA }> {
let mut rng = OsRng::default();
let g = RISTRETTO_BASEPOINT_POINT;
let r = Scalar::random(&mut rng);
let u = g.mul(r);
let mut tagging_key_precomputes = vec![];
for tagging_key in tagging_keys.iter() {
let mut precompute = vec![];
for i in tagging_key.0.iter() {
precompute.push(i.mul(r));
}
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 tagging_key_precomputes[0].iter().enumerate() {
let k_i = RootSecret::<GAMMA>::h(u, *precompute, w);
if i < length {
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;
}
}
key.push(k_i)
}
}
let mut ciphertexts = BitVec::new();
for k_i in key.iter() {
let c_i = k_i ^ 0x01;
ciphertexts.push(c_i == 0x01);
}
let m = RootSecret::<GAMMA>::g(u, &ciphertexts);
let y = r.invert().mul(z.sub(m));
return Some(Tag { u, y, ciphertexts });
};
brute_force(config, adaptors::auto_advance(f))
}
}
#[cfg(test)]
mod tests {
use crate::{DetectionKey, RootSecret, Tag};
use bit_vec::BitVec;
use curve25519_dalek::ristretto::RistrettoPoint;
use curve25519_dalek::scalar::Scalar;
#[test]
fn test_compression() {
let secret = RootSecret::<24>::generate();
let tagging_key = secret.tagging_key();
let tag = tagging_key.generate_tag();
let compressed_tag = tag.compress();
let decompressed_tag = Tag::<24>::decompress(&compressed_tag).unwrap();
assert_eq!(tag, decompressed_tag);
}
#[test]
fn test_serialization() {
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();
println!("{}", serialized_tag);
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));
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: 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));
let bincode_tag = bincode::serialize(&tag);
let deserialized_tag: Tag<24> = bincode::deserialize(&bincode_tag.unwrap()).unwrap();
assert_eq!(true, detection_key.test_tag(&deserialized_tag));
}
#[test]
fn test_invalid_serializations() {
let deserialized_tag: Option<Tag<24>> = serde_json::from_str("[0,1,2]").unwrap_or(None);
assert_eq!(deserialized_tag, None);
let deserialized_tag: Result<Tag<15>, serde_json::Error> = serde_json::from_str("[140,198,182,161,124,132,111,222,62,235,59,249,152,203,170,89,150,27,251,252,41,159,134,34,112,61,117,249,35,126,29,1,100,157,229,106,42,68,167,89,109,137,234,37,124,139,59,116,221,74,24,229,97,154,7,34,236,248,90,130,150,116,182,11]
");
assert_eq!(deserialized_tag.is_ok(), false);
let deserialized_tag: Option<Tag<15>> = serde_json::from_str("[140,198,182,161,124,132,111,222,62,235,59,249,152,203,170,89,150,27,251,252,41,159,134,34,112,61,117,249,35,126,29,1,100,157,229,106,42,68,167,89,109,137,234,37,124,139,59,116,221,74,24,229,97,154,7,34,236]
").unwrap_or(None);
assert_eq!(deserialized_tag, None);
}
#[test]
#[cfg(feature = "entangled")]
fn test_multiple() {
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();
let entangled_tag = TaggingKey::generate_entangled_tag(tagging_keys, 16);
println!("{}", entangled_tag);
for secret in secrets.iter() {
let detection_key = secret.extract_detection_key(16);
assert!(detection_key.test_tag(&entangled_tag));
println!("{}", detection_key);
}
}
#[test]
#[cfg(feature = "bulk_verify")]
fn test_check_multiple() {
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();
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);
assert_eq!(results.len(), 2);
}
#[test]
fn correctness() {
let number_of_messages = 100;
let secret = RootSecret::<16>::generate();
for i in 0..number_of_messages {
let tag = secret.tagging_key().generate_tag();
println!("{}: {}", i, tag);
assert!(secret.extract_detection_key(5).test_tag(&tag));
}
}
fn gen_zero_tag_zero() -> Tag<24> {
let tag = Tag {
u: RistrettoPoint::default(),
y: Scalar::default(),
ciphertexts: BitVec::from_elem(24, false),
};
tag
}
fn gen_zero_tag_one() -> Tag<24> {
let mut tag = Tag {
u: RistrettoPoint::default(),
y: Scalar::default(),
ciphertexts: BitVec::from_elem(24, false),
};
tag.ciphertexts.set_all();
tag
}
#[test]
fn test_zero_tag() {
let secret = RootSecret::<24>::generate();
let tag = gen_zero_tag_zero();
assert_eq!(false, secret.extract_detection_key(6).test_tag(&tag));
let tag = gen_zero_tag_one();
assert_eq!(false, secret.extract_detection_key(6).test_tag(&tag));
}
#[test]
fn false_positives() {
let number_of_messages = 1000;
let secret = RootSecret::<24>::generate();
let mut false_positives = 0;
for _i in 0..number_of_messages {
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.extract_detection_key(3).false_positive_probability(),
(false_positives as f64 / number_of_messages as f64)
);
}
}