diff --git a/Cargo.toml b/Cargo.toml index e5c7341..5b0c8ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "fuzzytags" description = "a probabilistic cryptographic structure for metadata resistant tagging" -version = "0.3.0" +version = "0.4.0" repository = "https://git.openprivacy.ca/openprivacy/fuzzytags" authors = ["Sarah Jamie Lewis "] edition = "2018" @@ -15,13 +15,14 @@ hex = "0.4.2" rand = "0.7.3" curve25519-dalek = {version="3.0.0", features=["serde"]} sha3 = "0.9.1" -bit-vec = {version="0.6.3", features=["serde"]} serde = {version="1.0.123", features=["derive"]} +bit-vec = {version="0.6.3"} brute-force = {version="0.1.0", features=["curve25519"], optional=true} [dev-dependencies] criterion = {version="0.3", features=["html_reports"]} serde_json = "1.0.61" +bincode = "1.3.1" [[bench]] name = "fuzzy_tags_benches" diff --git a/README.md b/README.md index 112aed2..a70b655 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,31 @@ opens up applications like **multiple broadcast** and **deniable sending**. #[cfg(feature = "entangled")] let tag = FuzzyPublicKey::generate_entangled_tag(vec![public_key_1,public_key_2], 8); +## Serialization + +This crate relies on `serde` for serialization. FuzzyTags are first compressed into a byte array of 64 bytes + +`γ` 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; + + let gamma = 24; + let secret_key = FuzzySecretKey::generate(gamma); + let public_key = secret_key.public_key(); + + // Give public key to a another party... + // and then they can do... + let tag = public_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 = serde_json::from_str(&serialized_tag); + println!("Deserialized: {}", deserialized_tag.unwrap()); + ## Benchmarks We use [criterion](https://crates.io/crates/criterion) for benchmarking, and benchmarks can run using `cargo bench` diff --git a/src/lib.rs b/src/lib.rs index 59571e7..1af3f56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,19 @@ #![deny(missing_docs)] #![feature(array_methods)] #![feature(external_doc)] +#![feature(in_band_lifetimes)] #![doc(include = "../README.md")] #![doc(include = "../ANONYMITY.md")] use bit_vec::BitVec; use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT; use curve25519_dalek::digest::Digest; -use curve25519_dalek::ristretto::RistrettoPoint; +use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; use curve25519_dalek::scalar::Scalar; use curve25519_dalek::traits::MultiscalarMul; use rand::rngs::OsRng; -use serde::Deserialize; -use serde::Serialize; +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}; @@ -29,13 +30,124 @@ use brute_force::brute_force; /// * Correctness: Valid tags constructed for a specific public 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, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct FuzzyTag { u: RistrettoPoint, y: Scalar, ciphertexts: BitVec, } +impl Serialize for FuzzyTag { + fn serialize(&self, serializer: S) -> Result + 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> Deserialize<'de> for FuzzyTag { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FuzzyTagVisitor; + + impl<'de> Visitor<'de> for FuzzyTagVisitor { + type Value = FuzzyTag; + + fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + formatter.write_str("64 bytes + gamma+bits of data") + } + + fn visit_seq(self, mut seq: A) -> Result + 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, + } + } + FuzzyTag::decompress(&bytes).ok_or(serde::de::Error::custom("invalid fuzzytag")) + } + } + + // support up to gamma = 64 + deserializer.deserialize_tuple(72, FuzzyTagVisitor) + } +} + +impl FuzzyTag { + /// 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 gamma = 24; + /// let secret_key = FuzzySecretKey::generate(gamma); + /// let public_key = secret_key.public_key(); + /// // extract a detection key + /// let detection_key = secret_key.extract(5); + /// + /// // Give public key to a another party... + /// // and then they can do... + /// let tag = public_key.generate_tag(); + /// let compressed_tag = tag.compress(); + /// ``` + pub fn compress(&self) -> Vec { + 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 + } + + /// 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 gamma = 24; + /// let secret_key = FuzzySecretKey::generate(gamma); + /// let public_key = secret_key.public_key(); + /// // extract a detection key + /// let detection_key = secret_key.extract(5); + /// + /// // Give public key to a another party... + /// // and then they can do... + /// let tag = public_key.generate_tag(); + /// let compressed_tag = tag.compress(); + /// let decompressed_tag = FuzzyTag::decompress(&compressed_tag).unwrap(); + /// ``` + pub fn decompress(bytes: &Vec) -> Option { + if bytes.len() > 64 { + let (u_bytes, rest) = bytes.split_at(32); + let (y_bytes, ciphertext) = rest.split_at(32); + return match CompressedRistretto::from_slice(u_bytes).decompress() { + Some(u) => Some(FuzzyTag { + u, + y: Scalar::from_bits(<[u8; 32]>::try_from(y_bytes).unwrap()), + ciphertexts: BitVec::from_bytes(ciphertext), + }), + _ => None, + }; + } + None + } +} + impl Display for FuzzyTag { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!( @@ -370,11 +482,34 @@ mod tests { #[test] fn test_serialization() { - let secret_key = FuzzySecretKey::generate(24); - let tag = secret_key.public_key.generate_tag(); - let detection_key = secret_key.extract(10); - println!("{}", serde_json::to_string(&tag).unwrap()); - println!("{}", serde_json::to_string(&detection_key).unwrap()); + // We support up to gamma=64 when deserializing.. + for gamma in [5, 10, 15, 24, 45, 64].iter() { + // generate some new keys... + let secret_key = FuzzySecretKey::generate(*gamma); + let tag = secret_key.public_key.generate_tag(); + + let detection_key = secret_key.extract(10); + // println!("{}", serde_json::to_string(&detection_key).unwrap()); + + //println!("Original: {}", tag); + let serialized_tag = serde_json::to_string(&tag).unwrap(); + //println!("Serialized: {}", serialized_tag); + + let deserialized_tag: FuzzyTag = serde_json::from_str(&serialized_tag).unwrap(); + // println!("Deserialized: {}", deserialized_tag); + + // Test some equality and testing properties + 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 = 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)); + } } #[test]