diff --git a/Cargo.toml b/Cargo.toml index 2a71be8..8727dcd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,23 +1,19 @@ [package] name = "false-bottom" -version = "0.2.2" +version = "0.3.0" description = "A deniable encryption scheme" repository = "https://codeberg.org/skran/false-bottom" authors = ["K Shiva Kiran "] license = "GPL-3.0-or-later" edition = "2021" - [dependencies] -rand = "0.8.5" -crypto-bigint = {version = "0.5.5", features = ["generic-array"]} -base64 = "0.21.7" +crypto-bigint = { version = "0.5.5", features = ["generic-array"] } +base64 = { version = "0.21.7", optional = true } bincode = "1.3.3" -typenum = "1.17.0" +rand = "0.8.5" rayon = "1.10.0" - -[lib] -doctest = false +serde = { version = "1.0.199", features = ["derive"], optional = true } [dev-dependencies] criterion = "0.5.1" @@ -25,3 +21,7 @@ criterion = "0.5.1" [[bench]] name = "bench" harness = false + +[[example]] +name = "export" +required-features = ["base64"] diff --git a/benches/bench.rs b/benches/bench.rs index 285c013..0aee5e3 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,26 +1,67 @@ use criterion::*; -use false_bottom::{FB128, FBAlgo}; +use false_bottom::{FalseBottom, Fb128, Fb256, Fb512}; -pub fn bench(c: &mut Criterion) { - let inp = vec![0_u8; 4096000]; - let mut fb = FB128::init(2, 2).unwrap(); - let mut group = c.benchmark_group("bench-group"); - let key = fb.add(&inp); +pub fn bench_encryption(c: &mut Criterion) { + let inp = vec![0_u8; 1_024_000]; + let mut fb128 = Fb128::init(2, 2); + let mut fb256 = Fb256::init(2, 2); + let mut fb512 = Fb512::init(2, 2); + let mut group = c.benchmark_group("Encryption with 1MiB data"); group.sample_size(21); group.throughput(Throughput::Bytes(inp.len() as u64)); group.bench_with_input( - BenchmarkId::new("4MB null bytes encryption", 4), &inp, + BenchmarkId::new("128-bit encryption", 128), &inp, |b, inp| b.iter(|| { - fb.add(&inp) + fb128.add(&inp) }), ); group.bench_with_input( - BenchmarkId::new("4MB null bytes decryption", 4), &key, - |b, key| b.iter(|| { - fb.decrypt(&key) + BenchmarkId::new("256-bit encryption", 256), &inp, + |b, inp| b.iter(|| { + fb256.add(&inp) }), ); + group.bench_with_input( + BenchmarkId::new("512-bit encryption", 512), &inp, + |b, inp| b.iter(|| { + fb512.add(&inp) + }), + ); + group.finish(); } -criterion_group!(benches, bench); -criterion_main!(benches); +pub fn bench_decryption(c: &mut Criterion) { + let inp = vec![0_u8; 4_096_000]; + let mut fb128 = Fb128::init(2, 2); + let mut fb256 = Fb256::init(2, 2); + let mut fb512 = Fb512::init(2, 2); + let key128 = fb128.add(&inp); + let key256 = fb256.add(&inp); + let key512 = fb512.add(&inp); + let mut group = c.benchmark_group("Decryption with 4MiB data"); + group.sample_size(21); + group.throughput(Throughput::Bytes(inp.len() as u64)); + group.bench_with_input( + BenchmarkId::new("128-bit decryption", 128), &key128, + |b, key128| b.iter(|| { + fb128.decrypt(&key128) + }), + ); + group.bench_with_input( + BenchmarkId::new("256-bit decryption", 256), &key256, + |b, key256| b.iter(|| { + fb256.decrypt(&key256) + }), + ); + group.bench_with_input( + BenchmarkId::new("512-bit decryption", 512), &key512, + |b, key512| b.iter(|| { + fb512.decrypt(&key512) + }), + ); + group.finish(); +} + +criterion_group!(encryption, bench_encryption); +criterion_group!(decryption, bench_decryption); +criterion_main!(encryption, decryption); diff --git a/examples/encryption.rs b/examples/encryption.rs index 77717f1..f9d9628 100644 --- a/examples/encryption.rs +++ b/examples/encryption.rs @@ -1,4 +1,4 @@ -use false_bottom::{FB128, FBAlgo}; +use false_bottom::{FalseBottom, Fb128}; fn main() { // Input messages @@ -6,7 +6,7 @@ fn main() { let real_msg = "I have gathered intel regarding the government's illegal spying"; // Cipher initialization - let mut fb = FB128::init(10, 10); + let mut fb = Fb128::init(10, 10); // Encryption (Adding messages is not limited to 2) let fake_key = fb.add(&fake_msg.as_bytes()); @@ -19,9 +19,6 @@ fn main() { let fake_str = String::from_utf8(fake_decr).unwrap(); let real_str = String::from_utf8(real_decr).unwrap(); - println!("Decrypted Contents:"); - println!("Fake Message: {fake_str}"); - println!("Real Message: {real_str}"); assert_eq!(fake_msg, fake_str); assert_eq!(real_msg, real_str); } diff --git a/examples/export.rs b/examples/export.rs index d3a32a1..fe293ec 100644 --- a/examples/export.rs +++ b/examples/export.rs @@ -1,39 +1,25 @@ -use false_bottom::{FB128, FBKey, FBAlgo, Encode}; +use false_bottom::{Encode, FalseBottom, Fb128, FbKey}; fn main() { // Cipher Initialization - let mut fb = FB128::init(18, 9); + let mut fb = Fb128::init(18, 9); // Encryption - let msg1 = "This is a message"; - let key1 = fb.add(msg1.as_bytes()); - let msg2 = "This is another message"; - let key2 = fb.add(msg2.as_bytes()); + let msg = "This is a message"; + let key = fb.add(msg.as_bytes()); - // Export as base64 - let (cipher, keybase) = fb.as_base64(); // Careful with the order - let key1_exp = key1.as_base64(); + // Export as base64 (Note the order!) + let (cipher, keybase) = fb.to_base64(); // Or as raw bytes - let key2_exp = key2.as_bytes(); + let key_exp = key.to_bytes(); // Import from base64 - let fb_new = FB128::from_base64(&cipher, &keybase).unwrap(); - let key1_imp = FBKey::from_base64(&key1_exp).unwrap(); + let fb_imp = Fb128::from_base64(&cipher, &keybase).unwrap(); // Or as raw bytes - let key2_imp = FBKey::from_bytes(&key2_exp).unwrap(); + let key_imp = FbKey::from_bytes(&key_exp).unwrap(); // Decryption - let decr1 = fb_new.decrypt(&key1_imp).unwrap(); - let decr2 = fb_new.decrypt(&key2_imp).unwrap(); + let decr = fb_imp.decrypt(&key_imp).unwrap(); - // Display - println!(" -CipherText: \n{cipher}\n -KeyBase: \n{keybase}\n -Key 1: {key1_exp} -Key 2: {key2_exp:?} -"); - - assert_eq!(msg1.as_bytes(), decr1); - assert_eq!(msg2.as_bytes(), decr2); + assert_eq!(msg.as_bytes(), decr); } diff --git a/src/algo.rs b/src/algo.rs deleted file mode 100644 index 48acea3..0000000 --- a/src/algo.rs +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -use crate::{FBError, FBKey, FBObj, FBObjTrait, FieldOps, Packing}; -use crypto_bigint::{NonZero, RandomMod}; -use rand::{rngs::ThreadRng, seq::index, Rng}; -use rayon::iter::*; -use std::marker::Send; -use std::sync::RwLock; - -pub trait FBAlgo -where - Self: BlockOps + Sync + Send, - T: FieldOps + Packing + RandomMod + Send + Sync, -{ - const MODULUS: NonZero; - - /// Creates a new [`FBObj`]. - /// The keybase and ciphertext are initialized from random values. - /// Bounds: `2 <= keybase_len <= cipher_len` - /// # Errors - /// [InvalidParams](FBError::InvalidParams) - fn init(cipher_len: usize, keybase_len: usize) -> FBObj { - if cipher_len < keybase_len || keybase_len < 2 { - panic!("{}", FBError::InvalidParams); - } - let mut rng = rand::thread_rng(); - let r = (0..keybase_len) - .map(|_| T::random_mod(&mut rng, &Self::MODULUS)) - .collect(); - let c_vec = (0..cipher_len) - .map(|_| T::random_mod(&mut rng, &Self::MODULUS)) - .collect(); - let c = RwLock::new(c_vec); - - FBObj {c, r} - } - - /// Adds the provided message to the ciphertext. - fn add(&mut self, msg: &[u8]) -> FBKey { - let indices = T::pack(msg) - .into_par_iter() - .map_init( - || rand::thread_rng(), - |rng, index_row| self.add_block(rng, &index_row), - ) - .collect(); - - FBKey { indices } - } - - /// Decrypts the message that corresponds to the provided key. - /// # Errors - /// [InvalidKey](FBError::InvalidKey) - fn decrypt(&self, key: &FBKey) -> Result, FBError> { - let decr = key.indices.iter() - .map(|index_row| self.decrypt_block(&index_row)) - .collect::, _>>()?; - let mut msg = T::unpack(decr)?; - msg.shrink_to_fit(); - - Ok(msg) - } -} - -pub trait BlockOps -where - Self: FBObjTrait, - T: FieldOps + RandomMod + Send + Sync, -{ - fn add_block(&self, rng: &mut ThreadRng, msg_uint: &T) -> Vec<(usize, usize)> { - let r = self.keybase(); - let n = rng.gen_range(2..=r.len()); - let r_i = index::sample(rng, r.len(), n); - let ri_last = r_i.iter().last() - .expect("r_i will contain at least 2 elements"); - let ri_last_inv = r[ri_last].field_inv(); - let c_i; - let c_len; - { - let mut c = self.cipher().write().unwrap(); - c_i = index::sample(rng, c.len(), n - 1); - let sum = c_i.iter() - .zip(r_i.iter()) - .map(|(ci, ri)| c[ci].field_mul(&r[ri])) - .reduce(|acc, i| acc.field_add(&i)) - .unwrap(); - let c_new_el = msg_uint.field_sub(&sum).field_mul(&ri_last_inv); - c.push(c_new_el); - c_len = c.len(); - } - let indices = c_i.iter() - .chain([c_len - 1].into_iter()) - .zip(r_i.iter()) - .collect(); - - indices - } - - fn decrypt_block(&self, indices: &[(usize, usize)]) -> Result { - let (c, r) = (self.cipher().read().unwrap(), self.keybase()); - if indices.len() > r.len() { - return Err(FBError::InvalidKey); - } - let mut msg = T::ZERO; - for &(ci, ri) in indices { - let c_el = c.get(ci).ok_or(FBError::InvalidKey)?; - let r_el = r.get(ri).ok_or(FBError::InvalidKey)?; - msg = msg.field_add(&c_el.field_mul(&r_el)); - } - - Ok(msg) - } -} - -#[test] -fn encrypt_u128() { - use crypto_bigint::U128; - let msg = U128::from_u32(100); - let fb = FBObj::::init(18, 12); - let rng = &mut rand::thread_rng(); - let key = fb.add_block(rng, &msg); - let decrypted = fb.decrypt_block(&key).unwrap(); - assert_eq!(msg, decrypted); -} - -#[test] -fn encrypt_bytes() { - use crypto_bigint::U128; - let input1 = vec![255_u8; 33]; - let input2 = vec![0_u8; 102]; - let mut fb = FBObj::::init(21, 9); - let key1 = fb.add(&input1); - let key2 = fb.add(&input2); - let decr1 = fb.decrypt(&key1).unwrap(); - let decr2 = fb.decrypt(&key2).unwrap(); - assert_eq!(input1, decr1); - assert_eq!(input2, decr2); -} diff --git a/src/arithmetic.rs b/src/arithmetic.rs deleted file mode 100644 index 1adf7d4..0000000 --- a/src/arithmetic.rs +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pub trait FieldOps { - fn field_add(&self, rhs: &Self) -> Self; - fn field_sub(&self, rhs: &Self) -> Self; - fn field_mul(&self, rhs: &Self) -> Self; - fn field_inv(&self) -> Self; -} - -pub trait WrappingOps { - fn wrapping_add(&self, rhs: &Self) -> Self; - fn wrapping_sub(&self, rhs: &Self) -> Self; -} diff --git a/src/encode.rs b/src/encode.rs new file mode 100644 index 0000000..52311d8 --- /dev/null +++ b/src/encode.rs @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +use crate::{FbError, FbObj}; +use crypto_bigint::{ArrayEncoding, generic_array::GenericArray, Uint}; +use std::sync::RwLock; + +#[cfg(feature = "base64")] +use base64::{prelude::BASE64_STANDARD, Engine}; + +/// Provides methods to encode and decode data to and from several formats. +pub trait Encode { + /// Returns the byte representation of the ciphertext and keybase. + fn to_bytes(&self) -> (Vec, Vec); + + /// Constructs the `FbObj` from the provided byte representations of + /// ciphertext and keybase. + /// # Errors + /// - [InvalidParams](FbError::InvalidParams) - Are the parameters in the wrong order? + fn from_bytes(cipher: &[u8], keybase: &[u8]) -> Result + where + Self: Sized; + + /// Returns the base64 encoded representation of the ciphertext and keybase. + /// Requires `base64` feature to be enabled. + #[cfg(feature = "base64")] + fn to_base64(&self) -> (String, String); + + /// Constructs the `FbObj` from the provided base64 encoded forms of + /// ciphertext and keybase. + /// Requires `base64` feature to be enabled. + /// # Errors + /// - [DecodeError](FbError::DecodeError) + /// - [InvalidParams](FbError::InvalidParams) - Are the parameters in the wrong order? + #[cfg(feature = "base64")] + fn from_base64(cipher: &str, keybase: &str) -> Result + where + Self: Sized; +} + +impl Encode for FbObj> +where + Uint: ArrayEncoding, +{ + fn to_bytes(&self) -> (Vec, Vec) { + let c = self.c.read().unwrap() + .iter() + .flat_map(|bigint| bigint.to_le_byte_array()) + .collect(); + let r = self.r.iter() + .flat_map(|bigint| bigint.to_le_byte_array()) + .collect(); + + (c, r) + } + + fn from_bytes(cipher: &[u8], keybase: &[u8]) -> Result { + let to_uint = |chunk| Uint::::from_le_byte_array(GenericArray::clone_from_slice(chunk)); + let c_vec: Vec<_> = cipher.chunks_exact(Uint::::BYTES) + .map(to_uint) + .collect(); + let r: Vec<_> = keybase.chunks_exact(Uint::::BYTES) + .map(to_uint) + .collect(); + if r.len() > c_vec.len() || r.len() < 2 { + return Err(FbError::InvalidParams); + } + let c = RwLock::new(c_vec); + + Ok(FbObj {c, r}) + } + + #[cfg(feature = "base64")] + fn to_base64(&self) -> (String, String) { + let (c, r) = self.to_bytes(); + + (BASE64_STANDARD.encode(c), BASE64_STANDARD.encode(r)) + } + + #[cfg(feature = "base64")] + fn from_base64(cipher: &str, keybase: &str) -> Result { + let c_bytes = BASE64_STANDARD.decode(cipher) + .map_err(|_| FbError::DecodeError)?; + let r_bytes = BASE64_STANDARD.decode(keybase) + .map_err(|_| FbError::DecodeError)?; + + Self::from_bytes(&c_bytes, &r_bytes) + } +} diff --git a/src/encoding.rs b/src/encoding.rs deleted file mode 100644 index 5a488c3..0000000 --- a/src/encoding.rs +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -use crate::{FBError, FBObj, FBObjTrait}; -use base64::{prelude::BASE64_STANDARD, Engine}; -use crypto_bigint::{generic_array::GenericArray, ArrayEncoding, Bounded}; -use std::sync::RwLock; - -pub trait Encode -where - Self: FBObjTrait, - T: ArrayEncoding + Bounded, -{ - /// Returns the byte representation of the ciphertext and keybase. - fn as_bytes(&self) -> (Vec, Vec) { - let c = self.cipher().read().unwrap() - .iter() - .flat_map(|bigint| bigint.to_le_byte_array()) - .collect(); - let r = self.keybase().iter() - .flat_map(|bigint| bigint.to_le_byte_array()) - .collect(); - - (c, r) - } - - /// Returns the base64 encoded representation of the ciphertext and keybase. - fn as_base64(&self) -> (String, String) { - let (c, r) = self.as_bytes(); - - (BASE64_STANDARD.encode(c), BASE64_STANDARD.encode(r)) - } - - /// Constructs the [`FBObj`] from the provided byte representations of - /// ciphertext and keybase. - /// # Errors - /// - [InvalidParams](FBError::InvalidParams) - Are the parameters in the wrong order? - fn from_bytes(cipher: &[u8], keybase: &[u8]) -> Result, FBError> { - let chunk_to_uint = |chunk| T::from_le_byte_array(GenericArray::clone_from_slice(chunk)); - let c_vec: Vec = cipher.chunks_exact(T::BYTES) - .map(chunk_to_uint) - .collect(); - let r: Vec = keybase.chunks_exact(T::BYTES) - .map(chunk_to_uint) - .collect(); - if r.len() > c_vec.len() || r.len() < 2 { - return Err(FBError::InvalidParams); - } - let c = RwLock::new(c_vec); - - Ok(FBObj {c, r}) - } - - /// Constructs the [`FBObj`] from the provided base64 encoded forms of - /// ciphertext and keybase. - /// # Errors - /// - [DecodeError](FBError::DecodeError) - /// - [InvalidParams](FBError::InvalidParams) - Are the parameters in the wrong order? - fn from_base64(cipher: &str, keybase: &str) -> Result, FBError> { - let c_bytes = BASE64_STANDARD.decode(cipher) - .map_err(|_| FBError::DecodeError)?; - let r_bytes = BASE64_STANDARD.decode(keybase) - .map_err(|_| FBError::DecodeError)?; - - Self::from_bytes(&c_bytes, &r_bytes) - } -} diff --git a/src/errors.rs b/src/errors.rs deleted file mode 100644 index dcffa1d..0000000 --- a/src/errors.rs +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -use std::fmt; - -#[derive(Debug)] -pub enum FBError { - /// Unable to decode the given data. - DecodeError, - /// The provided key is invalid w.r.t this False Bottom Object. - InvalidKey, - /// Keybase length (k) or Cipher length (n) out of bounds. - /// Valid bounds: (2 <= k <= n). - InvalidParams, -} - -impl fmt::Display for FBError { - - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let message = match self { - FBError::DecodeError => "Invalid input", - FBError::InvalidKey => "Invalid Key used", - FBError::InvalidParams => "Keybase len(k) or Cipher len(n) out of bounds. Valid bounds: (2 <= k <= n)", - }; - - f.write_fmt(format_args!("{message}")) - } -} - -impl std::error::Error for FBError {} diff --git a/src/falsebottom.rs b/src/falsebottom.rs new file mode 100644 index 0000000..eaf4f83 --- /dev/null +++ b/src/falsebottom.rs @@ -0,0 +1,66 @@ +use crate::{FbError, FbKey, FbObj, PrimeField, Packing}; +use crypto_bigint::{ArrayEncoding, NonZero, RandomMod, Uint}; +use std::sync::RwLock; +use rayon::iter::*; + +/// The main interface to the False Bottom algorithm. +pub trait FalseBottom { + /// Creates a new `FbObj`. + /// The keybase and ciphertext are initialized from random values. + /// Bounds: `2 <= keybase_len <= cipher_len` + /// # Panics + /// If Parameters are out of bounds. + fn init(cipher_len: usize, keybase_len: usize) -> Self; + + /// Adds the provided message to the ciphertext. + fn add(&mut self, msg: &[u8]) -> FbKey; + + /// Decrypts the message that corresponds to the provided key. + /// # Errors + /// [InvalidKey](FbError::InvalidKey) + fn decrypt(&self, key: &FbKey) -> Result, FbError>; +} + +impl FalseBottom for FbObj> +where + Uint: ArrayEncoding + PrimeField +{ + fn init(cipher_len: usize, keybase_len: usize) -> FbObj> { + let modulus = NonZero::>::new(Uint::::PRIME).unwrap(); + if cipher_len < keybase_len || keybase_len < 2 { + panic!("Valid bounds are: 2 <= keybase_len <= cipher_len"); + } + let mut rng = rand::thread_rng(); + let r = (0..keybase_len) + .map(|_| Uint::::random_mod(&mut rng, &modulus)) + .collect(); + let c_vec = (0..cipher_len) + .map(|_| Uint::::random_mod(&mut rng, &modulus)) + .collect(); + let c = RwLock::new(c_vec); + + FbObj {c, r} + } + + fn add(&mut self, msg: &[u8]) -> FbKey { + let indices = Uint::::pack(msg) + .into_par_iter() + //.map(|index_row| self.add_block(&mut rand::thread_rng(), &index_row)) + .map_init( + || rand::thread_rng(), + |rng, index_row| self.add_block(rng, &index_row), + ) + .collect(); + + FbKey(indices) + } + + fn decrypt(&self, key: &FbKey) -> Result, FbError> { + let decr = key.0.iter() + .map(|index_row| self.decrypt_block(&index_row)) + .collect::, _>>()?; + let msg = Uint::::unpack(decr)?; + + Ok(msg) + } +} diff --git a/src/fberror.rs b/src/fberror.rs new file mode 100644 index 0000000..ede2b4a --- /dev/null +++ b/src/fberror.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +use std::fmt; + +/// Enum representing all the possible errors in this crate. +#[derive(Debug)] +pub enum FbError { + /// Unable to decode the given data. + DecodeError, + + /// The provided key is invalid w.r.t this False Bottom Object. + InvalidKey, + + /// One or more of the parameters are invalid. + /// Valid bounds: (2 <= keybase_len <= cipher_len). + InvalidParams, +} + +impl fmt::Display for FbError { + + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = match self { + FbError::DecodeError => "Invalid input", + FbError::InvalidKey => "Invalid Key used", + FbError::InvalidParams => "One or more of the parameters are invalid or out of bounds. Valid bounds: (2 <= keybase_len <= cipher_len)", + }; + + f.write_fmt(format_args!("{message}")) + } +} + +impl std::error::Error for FbError {} diff --git a/src/fbkey.rs b/src/fbkey.rs index 4893416..27a3a32 100644 --- a/src/fbkey.rs +++ b/src/fbkey.rs @@ -1,47 +1,55 @@ // SPDX-License-Identifier: GPL-3.0-or-later -use crate::FBError; -use base64::prelude::{BASE64_STANDARD, Engine}; +use crate::FbError; use bincode::{Options, DefaultOptions}; -/// A key object that is specific to a message. -pub struct FBKey { - pub(crate) indices: Vec>, -} +#[cfg(feature = "base64")] +use base64::prelude::{BASE64_STANDARD, Engine}; -impl FBKey { +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; - /// Returns the byte representation of the key. - pub fn as_bytes(&self) -> Vec { +/// An object that represents a message specific key. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct FbKey(pub(crate) Vec>); + + +impl FbKey { + + /// Returns the byte representation of the message specific key. + pub fn to_bytes(&self) -> Vec { let binc = DefaultOptions::new(); - binc.serialize(&self.indices) + + binc.serialize(&self.0) .expect("Should be fine") } - /// Returns the base64 encoded representation of the key. - pub fn as_base64(&self) -> String { - BASE64_STANDARD.encode(&self.as_bytes()) - } - - /// Constructs the key from the provided bytes. + /// Constructs the message specific key from the provided bytes. /// # Errors - /// [DecodeError](FBError::DecodeError) - pub fn from_bytes(fbkey: &[u8]) -> Result { + /// [DecodeError](FbError::DecodeError) + pub fn from_bytes(fbkey: &[u8]) -> Result { let binc = DefaultOptions::new(); let indices: Vec<_> = binc.deserialize(&fbkey) - .map_err(|_| FBError::DecodeError)?; + .map_err(|_| FbError::DecodeError)?; if indices.len() < 2 { - return Err(FBError::DecodeError); + return Err(FbError::DecodeError); } - Ok (FBKey {indices}) + Ok (FbKey(indices)) } - /// Constructs the key from the provided base64 encoded form. + /// Returns the base64 encoded representation of the message specific key. + #[cfg(feature = "base64")] + pub fn to_base64(&self) -> String { + BASE64_STANDARD.encode(&self.to_bytes()) + } + + /// Constructs the message specific key from the provided base64 encoded form. /// # Errors - /// [DecodeError](FBError::DecodeError) - pub fn from_base64(key_str: &str) -> Result { + /// [DecodeError](FbError::DecodeError) + #[cfg(feature = "base64")] + pub fn from_base64(key_str: &str) -> Result { let indice_bytes = BASE64_STANDARD.decode(key_str) - .map_err(|_| FBError::DecodeError)?; + .map_err(|_| FbError::DecodeError)?; Self::from_bytes(&indice_bytes) } diff --git a/src/fbobj.rs b/src/fbobj.rs index f27a0ba..e08b473 100644 --- a/src/fbobj.rs +++ b/src/fbobj.rs @@ -1,37 +1,89 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pub mod fb128; - -use crate::{BlockOps, Encode, FieldOps}; -use crypto_bigint::{ArrayEncoding, Bounded, RandomMod}; +use crate::{FbError, PrimeField}; +use crypto_bigint::{U128, U256, U512, Uint}; +use rand::{Rng, rngs::ThreadRng, seq::index}; use std::sync::RwLock; -use std::marker::Sync; -/// The False Bottom Object holds the ciphertext and the keybase. The provided type aliases can be used to pick a block size. -pub struct FBObj { +// The False bottom object. It holds the ciphertext(c) and the keybase(r). +pub struct FbObj { pub(crate) c: RwLock>, pub(crate) r: Vec, } -pub trait FBObjTrait { - fn cipher(&self) -> &RwLock>; - fn keybase(&self) -> &Vec; -} +/// An FbObj with block size of 128 bits. +pub type Fb128 = FbObj; +/// An FbObj with a block size of 256 bits. +pub type Fb256 = FbObj; +/// An FbObj with a block size of 512 bits. +pub type Fb512 = FbObj; -impl FBObjTrait for FBObj { - fn cipher(&self) -> &RwLock> { - &self.c +impl FbObj> +where + Uint: PrimeField +{ + pub(crate) fn add_block( + &self, + rng: &mut ThreadRng, + msg_uint: &Uint + ) -> Vec<(usize, usize)> { + let r = &self.r; + let n = rng.gen_range(2..=r.len()); + let r_i = index::sample(rng, r.len(), n); + let ri_last = r_i.iter().last() + .expect("r_i will contain at least 2 elements"); + let ri_last_inv = r[ri_last].field_inv(); + let c_i; + let c_len; + { + let mut c = self.c.write().unwrap(); + c_i = index::sample(rng, c.len(), n - 1); + let sum = c_i.iter() + .zip(r_i.iter()) + .map(|(ci, ri)| c[ci].field_mul(&r[ri])) + .reduce(|acc, i| acc.field_add(&i)) + .unwrap(); + let c_new_el = msg_uint.field_sub(&sum).field_mul(&ri_last_inv); + c.push(c_new_el); + c_len = c.len(); + } + let indices = c_i.into_iter() + .chain([c_len - 1].into_iter()) + .zip(r_i.into_iter()) + .collect(); + + indices } - fn keybase(&self) -> &Vec { - &self.r + + pub(crate) fn decrypt_block( + &self, + indices: &[(usize, usize)] + ) -> Result, FbError> { + let (c, r) = (self.c.read().unwrap(), &self.r); + if indices.len() > r.len() { + return Err(FbError::InvalidKey); + } + let mut msg = Uint::::ZERO; + for &(ci, ri) in indices { + let c_el = c.get(ci).ok_or(FbError::InvalidKey)?; + let r_el = r.get(ri).ok_or(FbError::InvalidKey)?; + msg = msg.field_add(&c_el.field_mul(&r_el)); + } + + Ok(msg) } } -impl BlockOps for FBObj -where - T: FieldOps + RandomMod + Send + Sync -{} +#[cfg(test)] +mod test { + use super::*; + use crate::FalseBottom; -impl Encode for FBObj -where - T: ArrayEncoding + Bounded -{} + #[test] + fn test_block_operations() { + let msg = U512::from_u16(369); + let fb = Fb512::init(12, 12); + let key = fb.add_block(&mut rand::thread_rng(), &msg); + let decrypted = fb.decrypt_block(&key).unwrap(); + assert_eq!(msg, decrypted); + } +} diff --git a/src/lib.rs b/src/lib.rs index 3a11fe6..fa14f80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,65 +1,95 @@ // SPDX-License-Identifier: GPL-3.0-or-later //! ## Usage -//! Unlike traditional encryption algorithms that output ciphertext to a given plaintext, +//! False Bottom is a [deniable encryption](https://en.wikipedia.org/wiki/Deniable_encryption) scheme. +//! Unlike traditional encryption algorithms that map a given plaintext to ciphertext, //! False Bottom works by "adding" messages to an existing ciphertext. //! As such, the initial ciphertext and the keybase are generated from random data. //! The ciphertext then grows as messages are added whereas the keybase is fixed -//! after initialization and is used to generate the keys for the ciphertext. -//! The parameters for the [`init()`](FBObj::init()) function determine the size +//! after initialization and is used to generate message specific keys for the ciphertext. +//! The parameters for the [`init()`](FalseBottom::init()) function determine the size //! (in number of blocks) of the initial ciphertext and keybase respectively. -//! Type aliases over [`FBObj`] are provided to pick a block size. -//! Ex: [`FB128`] corresponds to a block size of 128 bits. +//! Type aliases [`Fb128`], [`Fb256`] and [`Fb512`] are provided to pick a block size. //! //! ### Cipher Initialization: -//! ```rust -//! use false_bottom::{FB128, FBAlgo}; -//! // 15 blocks of ciphertext and 12 blocks of keybase with a block size of 128 bits. -//! let fb = FB128::init(15, 12); +//! ``` +//! use false_bottom::{FalseBottom, Fb128}; +//! // Initialize 15 blocks of ciphertext and 12 blocks of keybase with a block size of 128 bits. +//! let fb = Fb128::init(15, 12); //! ``` //! //! ### Adding Messages: -//! Messages can be added using the [`add()`](FBObj::add()) method. -//! This method returns an object [`FBKey`] that represents the corresponding key for this message. -//! This key can only be used to decrypt this message. -//! ```rust -//! let msg = b"Hello World!"; -//! let key = fb.add(&msg); +//! Multiple messages can be added using the [`add()`](FalseBottom::add()) method. +//! This method returns an object [`FbKey`] that represents the message specific key for this message. +//! Only this key can decrypt the added message. +//! ``` +//! # use false_bottom::{FalseBottom, Fb128}; +//! # let mut fb = Fb128::init(15, 12); +//! let msg = "Hello World!"; +//! let key = fb.add(msg.as_bytes()); //! ``` //! //! ### Decryption: -//! The [`decrypt()`](FBObj::decrypt()) method returns the message that corresponds -//! to the provided [`FBKey`]. -//! ```rust +//! The [`decrypt()`](FalseBottom::decrypt()) method returns the message that corresponds +//! to the provided [`FbKey`]. +//! ``` +//! # use false_bottom::{FalseBottom, Fb128}; +//! # let mut fb = Fb128::init(15, 12); +//! # let msg = "Hello World!"; +//! # let key = fb.add(msg.as_bytes()); //! let decrypted = fb.decrypt(&key).unwrap(); //! ``` -//! There is also an example [here](https://codeberg.org/skran/false-bottom/src/branch/main/examples/encryption.rs). +//! An example has been provided [here](https://codeberg.org/skran/false-bottom/src/branch/main/examples/encryption.rs). //! //! ### Import and Export -//! Available formats: Raw bytes and Base64 encoded. -//! Refer to the [example](https://codeberg.org/skran/false-bottom/src/branch/main/examples/export.rs) +//! Available formats: Raw bytes and Base64 encoded. +//! #### Raw Bytes: +//! The [`Encode`] trait provides methods for export and import of data. +//! ``` +//! use false_bottom::{Encode, FalseBottom, Fb128, FbError, FbKey}; +//! # let mut fb = Fb128::init(15, 12); +//! # let key = fb.add(b"Hello"); +//! // Exporting +//! let (ciphertext_bytes, keybase_bytes) = fb.to_bytes(); +//! let key_bytes = key.to_bytes(); +//! // Importing +//! let fb_imported = Fb128::from_bytes(&ciphertext_bytes, &keybase_bytes)?; +//! let key_imported = FbKey::from_bytes(&key_bytes)?; +//! # Ok::<(), FbError>(()) +//! ``` +//! #### Base64 Encoded: +//! The feature `base64` needs to be enabled in your `Cargo.toml`. +//! ``` +//! use false_bottom::{Encode, FalseBottom, Fb128, FbError, FbKey}; +//! # let mut fb = Fb128::init(15, 12); +//! # let key = fb.add(b"Hello"); +//! // Exporting +//! let (ciphertext_base64, keybase_base64) = fb.to_base64(); +//! let key_base64 = key.to_base64(); +//! // Importing +//! let fb_imported = Fb128::from_base64(&ciphertext_base64, &keybase_base64)?; +//! let key_imported = FbKey::from_base64(&key_base64)?; +//! # Ok::<(), FbError>(()) +//! ``` +//! An example has been provided [here](https://codeberg.org/skran/false-bottom/src/branch/main/examples/export.rs). -mod algo; -mod arithmetic; -mod encoding; -mod errors; +mod encode; +mod falsebottom; +mod fberror; mod fbkey; mod fbobj; +mod primefield; mod packing; pub use crate::{ - errors::FBError, - fbobj::{ - FBObj, - fb128::FB128, - }, - algo::FBAlgo, - fbkey::FBKey, - encoding::Encode, + encode::Encode, + falsebottom::FalseBottom, + fbobj::{Fb128, Fb256, Fb512}, + fbkey::FbKey, + fberror::FbError, }; use crate::{ - algo::BlockOps, - arithmetic::{FieldOps, WrappingOps}, - fbobj::FBObjTrait, + fbobj::FbObj, packing::Packing, + primefield::PrimeField, }; diff --git a/src/packing.rs b/src/packing.rs index acb06a5..509879d 100644 --- a/src/packing.rs +++ b/src/packing.rs @@ -1,64 +1,68 @@ // SPDX-License-Identifier: GPL-3.0-or-later -use crypto_bigint::{ArrayEncoding, Bounded, generic_array::GenericArray}; -use std::cmp::PartialOrd; -use crate::{FBError, WrappingOps}; +use crypto_bigint::{ArrayEncoding, Uint, generic_array::GenericArray}; +use crate::{FbError, PrimeField}; /* PACKING SCHEME - * This is used to transform message byte chunks into field elements. - * Every x byte chunks of a message are combined to form a field element n. - * In case a number n >= P (out of field) is formed, - * the values P-1 (i.e R_BOUND) followed by n-R_BOUND are appended to the output vec. - * While unpacking, P-1 is used as a signaling element to extract n - * from the successive element after adding back R_BOUND. + * This scheme transforms byte chunks into field elements and vice versa. + * Every x byte chunks are combined to form a field element n. + * In case the number formed is n >= P where P is the right bound of the prime field, + * the values P-1 (i.e LIMIT) followed by n-LIMIT are appended to the output vec. + * While unpacking, LIMIT is used as a signaling element to extract n + * from the successive element after adding back LIMIT. */ -pub trait Packing +pub(crate) trait Packing where - Self: ArrayEncoding + Bounded + PartialOrd + WrappingOps + Self: Sized { - const R_BOUND: Self; + const LIMIT: Self; + fn pack(inp: &[u8]) -> Vec; + fn unpack(inp: Vec) -> Result, FbError>; +} - fn pack(inp: &[u8]) -> Vec { - let mut out: Vec = inp.chunks(Self::BYTES) +impl Packing for Uint +where + Uint: ArrayEncoding + PrimeField, +{ + const LIMIT: Uint = Uint::::PRIME.wrapping_sub(&Uint::::ONE); + fn pack(inp: &[u8]) -> Vec> { + let mut out: Vec<_> = inp.chunks(Uint::::BYTES) .flat_map(|inp_chunk| { - let mut out_chunk = GenericArray::::default(); + let mut out_chunk = GenericArray:: as ArrayEncoding>::ByteSize>::default(); out_chunk[..inp_chunk.len()].copy_from_slice(inp_chunk); - let mut out_uint = Self::from_le_byte_array(out_chunk); - if out_uint >= Self::R_BOUND { - out_uint = out_uint.wrapping_sub(&Self::R_BOUND); - vec![Self::R_BOUND, out_uint] + let mut out_uint = Uint::from_le_byte_array(out_chunk); + if out_uint >= Self::LIMIT { + out_uint = out_uint.wrapping_sub(&Self::LIMIT); + vec![Self::LIMIT, out_uint] } else { vec![out_uint] } }) .collect(); - let inp_chunk_last = inp.chunks_exact(Self::BYTES) + let inp_chunk_last = inp.chunks_exact(Uint::::BYTES) .remainder(); - let mut pad_chunk = GenericArray::::default(); - pad_chunk[Self::BYTES-1] = Self::BYTES as u8; - if inp_chunk_last.len() != 0 { - pad_chunk[Self::BYTES - 1] += (Self::BYTES - inp_chunk_last.len()) as u8; - } - out.push(Self::from_le_byte_array(pad_chunk)); + let pad_uint = Uint::from_u8((Uint::::BYTES - inp_chunk_last.len()) as u8); + out.push(pad_uint); out } - fn unpack(inp: Vec) -> Result, FBError> { - let pad_len: usize = inp.last() - .ok_or(FBError::InvalidKey)? - .to_le_byte_array()[Self::BYTES-1] as usize; - if pad_len > 2 * Self::BYTES - 1 || pad_len < Self::BYTES { - return Err(FBError::InvalidKey); + fn unpack(inp: Vec>) -> Result, FbError> { + let mut pad_len: usize = inp.last() + .ok_or(FbError::InvalidKey)? + .to_be_byte_array()[Uint::::BYTES - 1] as usize; + if pad_len >= Uint::::BYTES || pad_len < 1 { + return Err(FbError::InvalidKey); } + pad_len += Uint::::BYTES; let mut extract = false; let mut out: Vec = inp.into_iter() .filter_map(|el| { if extract { extract = false; - let el = el.wrapping_add(&Self::R_BOUND); + let el = el.wrapping_add(&Self::LIMIT); Some(el.to_le_byte_array()) - } else if el == Self::R_BOUND { + } else if el == Self::LIMIT { extract = true; None } else { @@ -67,9 +71,8 @@ where }) .flatten() .collect(); - let trunc_len = out.len().checked_sub(pad_len) - .ok_or(FBError::InvalidKey)?; + .ok_or(FbError::InvalidKey)?; out.truncate(trunc_len); Ok(out) diff --git a/src/primefield.rs b/src/primefield.rs new file mode 100644 index 0000000..6dd4c7a --- /dev/null +++ b/src/primefield.rs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +use crypto_bigint::{Limb, U128, U256, U512, Uint}; + +pub trait PrimeField { + const PRIME: Self; + fn field_add(&self, rhs: &Self) -> Self; + fn field_sub(&self, rhs: &Self) -> Self; + fn field_mul(&self, rhs: &Self) -> Self; + fn field_inv(&self) -> Self; +} + +// The prime number's position from the right end of the field. +trait PrimeValue { + const PRIME_RPOS: u16; + const RPOS_LIMB: Limb = Limb::from_u16(Self::PRIME_RPOS); +} + +impl PrimeField for Uint +where + Uint: PrimeValue +{ + const PRIME: Self = Self::MAX.wrapping_sub(&Self::from_u16(Self::PRIME_RPOS - 1)); + fn field_add(&self, rhs: &Self) -> Self { + self.add_mod_special(rhs, Self::RPOS_LIMB) + } + fn field_sub(&self, rhs: &Self) -> Self { + self.sub_mod_special(rhs, Self::RPOS_LIMB) + } + fn field_mul(&self, rhs: &Self) -> Self { + self.mul_mod_special(rhs, Self::RPOS_LIMB) + } + fn field_inv(&self) -> Self { + //self.inv_odd_mod(&Self::PRIME).0 + self.inv_odd_mod_bounded(&Self::PRIME, self.bits_vartime(), Uint::::BITS).0 + } +} + +impl PrimeValue for U128 { + const PRIME_RPOS: u16 = 159; +} + +impl PrimeValue for U256 { + const PRIME_RPOS: u16 = 189; +} + +impl PrimeValue for U512 { + const PRIME_RPOS: u16 = 569; +}