diff --git a/Makefile b/Makefile index 5b66d22..389b94b 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -ALL_FEATURES := serde +ALL_FEATURES := serde,random-flag-ciphertexts .PHONY: all all: clippy-no-std-all-features diff --git a/polyfuzzy/Cargo.toml b/polyfuzzy/Cargo.toml index f6255a4..c5745cd 100644 --- a/polyfuzzy/Cargo.toml +++ b/polyfuzzy/Cargo.toml @@ -11,6 +11,7 @@ edition = "2021" [features] serde = ["dep:serde", "curve25519-dalek/serde"] sha2-force-soft = ["sha2/force-soft"] +random-flag-ciphertexts = [] [dependencies] curve25519-dalek = { workspace = true, features = ["rand_core"] } diff --git a/polyfuzzy/src/fmd2.rs b/polyfuzzy/src/fmd2.rs index 4e4f766..e6af927 100644 --- a/polyfuzzy/src/fmd2.rs +++ b/polyfuzzy/src/fmd2.rs @@ -46,6 +46,18 @@ impl FlagCiphertexts { pub fn bits(&self) -> &[u8] { &self.c } + + /// Create a bogus flag ciphertext. + /// + /// This may be useful if we are generating cover traffic. + #[inline] + #[cfg(feature = "random-flag-ciphertexts")] + pub fn random(rng: &mut R, gamma: usize) -> Self + where + R: rand_core::RngCore + rand_core::CryptoRng, + { + GenericFlagCiphertexts::random(rng, gamma).into() + } } impl From for FlagCiphertexts { diff --git a/polyfuzzy/src/fmd2_compact/mod.rs b/polyfuzzy/src/fmd2_compact/mod.rs index 111be85..39e26fa 100644 --- a/polyfuzzy/src/fmd2_compact/mod.rs +++ b/polyfuzzy/src/fmd2_compact/mod.rs @@ -106,6 +106,18 @@ impl FlagCiphertexts { pub fn bits(&self) -> &[u8] { &self.0.c.0 } + + /// Create a bogus flag ciphertext. + /// + /// This may be useful if we are generating cover traffic. + #[inline] + #[cfg(feature = "random-flag-ciphertexts")] + pub fn random(rng: &mut R, gamma: usize) -> Self + where + R: rand_core::RngCore + rand_core::CryptoRng, + { + Self(GenericFlagCiphertexts::random(rng, gamma)) + } } /// Cache of expanded FMD public keys. diff --git a/polyfuzzy/src/fmd2_generic.rs b/polyfuzzy/src/fmd2_generic.rs index f0ffa38..e10a690 100644 --- a/polyfuzzy/src/fmd2_generic.rs +++ b/polyfuzzy/src/fmd2_generic.rs @@ -280,6 +280,42 @@ impl GenericFlagCiphertexts { c, } } + + #[cfg(feature = "random-flag-ciphertexts")] + pub(crate) fn random(rng: &mut R, gamma: usize) -> Self + where + R: rand_core::RngCore + rand_core::CryptoRng, + { + if gamma == 0 { + panic!("Gamma parameter cannot have a value of 0"); + } + + // Divide gamma by 8 and add any remaining bits which overflow + // to the next byte boundary. This yields the length of a bit + // ciphertext produced with a parameter of `gamma`. + let c_len = (gamma >> 3) + ((gamma % 8) != 0) as usize; + + let mut c = CompressedCiphertextBits(alloc::vec![0u8; c_len]); + + rng.fill_bytes(&mut c.0); + + // Mask with the padding bits that should be set to 0 (or, + // in other words, unset) in the bit ciphertext. Since this + // library doesn't set any of the upper bits, if they have + // been set it means someone has tampered with the flag + // ciphertext. We comply with the behavior of the library + // by unsetting the upper bits. + let unset_bits_mask = !(0xff << (gamma % 8)); + + c.0[c_len - 1] &= unset_bits_mask; + + Self { + basepoint_ch: RistrettoPoint::random(rng), + u: RistrettoPoint::random(rng), + y: Scalar::random(rng), + c, + } + } } // This is the hash H from Fig.3 of the FMD paper, instantiated with SHA256.