Compressed tag representation + cleaning up serialization

This commit is contained in:
Sarah Jamie Lewis 2021-02-03 23:32:10 -08:00
parent 1adfe996a6
commit 8aaebe8799
3 changed files with 172 additions and 11 deletions

View File

@ -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 <sarah@openprivacy.ca>"]
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"

View File

@ -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<FuzzyTag, serde_json::Error> = 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`

View File

@ -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<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> Deserialize<'de> for FuzzyTag {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<A>(self, mut seq: A) -> Result<FuzzyTag, 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,
}
}
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<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
}
/// 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<u8>) -> Option<FuzzyTag> {
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]