Compressed tag representation + cleaning up serialization
This commit is contained in:
parent
1adfe996a6
commit
8aaebe8799
|
@ -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"
|
||||
|
|
25
README.md
25
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<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`
|
||||
|
|
153
src/lib.rs
153
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<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]
|
||||
|
|
Loading…
Reference in New Issue