Compressed tag representation + cleaning up serialization
This commit is contained in:
parent
1adfe996a6
commit
8aaebe8799
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "fuzzytags"
|
name = "fuzzytags"
|
||||||
description = "a probabilistic cryptographic structure for metadata resistant tagging"
|
description = "a probabilistic cryptographic structure for metadata resistant tagging"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
repository = "https://git.openprivacy.ca/openprivacy/fuzzytags"
|
repository = "https://git.openprivacy.ca/openprivacy/fuzzytags"
|
||||||
authors = ["Sarah Jamie Lewis <sarah@openprivacy.ca>"]
|
authors = ["Sarah Jamie Lewis <sarah@openprivacy.ca>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
@ -15,13 +15,14 @@ hex = "0.4.2"
|
||||||
rand = "0.7.3"
|
rand = "0.7.3"
|
||||||
curve25519-dalek = {version="3.0.0", features=["serde"]}
|
curve25519-dalek = {version="3.0.0", features=["serde"]}
|
||||||
sha3 = "0.9.1"
|
sha3 = "0.9.1"
|
||||||
bit-vec = {version="0.6.3", features=["serde"]}
|
|
||||||
serde = {version="1.0.123", features=["derive"]}
|
serde = {version="1.0.123", features=["derive"]}
|
||||||
|
bit-vec = {version="0.6.3"}
|
||||||
brute-force = {version="0.1.0", features=["curve25519"], optional=true}
|
brute-force = {version="0.1.0", features=["curve25519"], optional=true}
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = {version="0.3", features=["html_reports"]}
|
criterion = {version="0.3", features=["html_reports"]}
|
||||||
serde_json = "1.0.61"
|
serde_json = "1.0.61"
|
||||||
|
bincode = "1.3.1"
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "fuzzy_tags_benches"
|
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")]
|
#[cfg(feature = "entangled")]
|
||||||
let tag = FuzzyPublicKey::generate_entangled_tag(vec![public_key_1,public_key_2], 8);
|
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
|
## Benchmarks
|
||||||
|
|
||||||
We use [criterion](https://crates.io/crates/criterion) for benchmarking, and benchmarks can run using `cargo bench`
|
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)]
|
#![deny(missing_docs)]
|
||||||
#![feature(array_methods)]
|
#![feature(array_methods)]
|
||||||
#![feature(external_doc)]
|
#![feature(external_doc)]
|
||||||
|
#![feature(in_band_lifetimes)]
|
||||||
#![doc(include = "../README.md")]
|
#![doc(include = "../README.md")]
|
||||||
#![doc(include = "../ANONYMITY.md")]
|
#![doc(include = "../ANONYMITY.md")]
|
||||||
use bit_vec::BitVec;
|
use bit_vec::BitVec;
|
||||||
use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
|
use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
|
||||||
use curve25519_dalek::digest::Digest;
|
use curve25519_dalek::digest::Digest;
|
||||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
|
||||||
use curve25519_dalek::scalar::Scalar;
|
use curve25519_dalek::scalar::Scalar;
|
||||||
use curve25519_dalek::traits::MultiscalarMul;
|
use curve25519_dalek::traits::MultiscalarMul;
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use serde::Deserialize;
|
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use serde::Serialize;
|
|
||||||
use sha3::Sha3_512;
|
use sha3::Sha3_512;
|
||||||
|
use std::convert::TryFrom;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::ops::{Mul, Sub};
|
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
|
/// * 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 (_γ_)
|
/// * 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)
|
/// * 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 {
|
pub struct FuzzyTag {
|
||||||
u: RistrettoPoint,
|
u: RistrettoPoint,
|
||||||
y: Scalar,
|
y: Scalar,
|
||||||
ciphertexts: BitVec,
|
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 {
|
impl Display for FuzzyTag {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
|
@ -370,11 +482,34 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serialization() {
|
fn test_serialization() {
|
||||||
let secret_key = FuzzySecretKey::generate(24);
|
// We support up to gamma=64 when deserializing..
|
||||||
let tag = secret_key.public_key.generate_tag();
|
for gamma in [5, 10, 15, 24, 45, 64].iter() {
|
||||||
let detection_key = secret_key.extract(10);
|
// generate some new keys...
|
||||||
println!("{}", serde_json::to_string(&tag).unwrap());
|
let secret_key = FuzzySecretKey::generate(*gamma);
|
||||||
println!("{}", serde_json::to_string(&detection_key).unwrap());
|
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]
|
#[test]
|
||||||
|
|
Loading…
Reference in New Issue