Huge Refactor

Traits have been decoupled, simplifications have been made for generics.
This commit is contained in:
K Shiva Kiran 2024-04-29 18:21:52 +05:30
parent 67ab21cdbe
commit 340c439c5c
16 changed files with 522 additions and 415 deletions

View File

@ -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 <shiva_kr@riseup.net>"]
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"]

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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<T>
where
Self: BlockOps<T> + Sync + Send,
T: FieldOps + Packing + RandomMod + Send + Sync,
{
const MODULUS: NonZero<T>;
/// 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<T> {
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<Vec<u8>, FBError> {
let decr = key.indices.iter()
.map(|index_row| self.decrypt_block(&index_row))
.collect::<Result<Vec<_>, _>>()?;
let mut msg = T::unpack(decr)?;
msg.shrink_to_fit();
Ok(msg)
}
}
pub trait BlockOps<T>
where
Self: FBObjTrait<T>,
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<T, FBError> {
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::<U128>::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::<U128>::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);
}

View File

@ -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;
}

87
src/encode.rs Normal file
View File

@ -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<u8>, Vec<u8>);
/// 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<Self, FbError>
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<Self, FbError>
where
Self: Sized;
}
impl<const LIMBS: usize> Encode for FbObj<Uint<LIMBS>>
where
Uint<LIMBS>: ArrayEncoding,
{
fn to_bytes(&self) -> (Vec<u8>, Vec<u8>) {
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<Self, FbError> {
let to_uint = |chunk| Uint::<LIMBS>::from_le_byte_array(GenericArray::clone_from_slice(chunk));
let c_vec: Vec<_> = cipher.chunks_exact(Uint::<LIMBS>::BYTES)
.map(to_uint)
.collect();
let r: Vec<_> = keybase.chunks_exact(Uint::<LIMBS>::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<Self, 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)
}
}

View File

@ -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<T>
where
Self: FBObjTrait<T>,
T: ArrayEncoding + Bounded,
{
/// Returns the byte representation of the ciphertext and keybase.
fn as_bytes(&self) -> (Vec<u8>, Vec<u8>) {
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<FBObj<T>, FBError> {
let chunk_to_uint = |chunk| T::from_le_byte_array(GenericArray::clone_from_slice(chunk));
let c_vec: Vec<T> = cipher.chunks_exact(T::BYTES)
.map(chunk_to_uint)
.collect();
let r: Vec<T> = 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<FBObj<T>, 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)
}
}

View File

@ -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 {}

66
src/falsebottom.rs Normal file
View File

@ -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<Vec<u8>, FbError>;
}
impl<const LIMBS: usize> FalseBottom for FbObj<Uint<LIMBS>>
where
Uint<LIMBS>: ArrayEncoding + PrimeField
{
fn init(cipher_len: usize, keybase_len: usize) -> FbObj<Uint<LIMBS>> {
let modulus = NonZero::<Uint<LIMBS>>::new(Uint::<LIMBS>::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::<LIMBS>::random_mod(&mut rng, &modulus))
.collect();
let c_vec = (0..cipher_len)
.map(|_| Uint::<LIMBS>::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::<LIMBS>::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<Vec<u8>, FbError> {
let decr = key.0.iter()
.map(|index_row| self.decrypt_block(&index_row))
.collect::<Result<Vec<_>, _>>()?;
let msg = Uint::<LIMBS>::unpack(decr)?;
Ok(msg)
}
}

31
src/fberror.rs Normal file
View File

@ -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 {}

View File

@ -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<Vec<(usize, usize)>>,
}
#[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<u8> {
/// An object that represents a message specific key.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FbKey(pub(crate) Vec<Vec<(usize, usize)>>);
impl FbKey {
/// Returns the byte representation of the message specific key.
pub fn to_bytes(&self) -> Vec<u8> {
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<FBKey, FBError> {
/// [DecodeError](FbError::DecodeError)
pub fn from_bytes(fbkey: &[u8]) -> Result<FbKey, FbError> {
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<FBKey, FBError> {
/// [DecodeError](FbError::DecodeError)
#[cfg(feature = "base64")]
pub fn from_base64(key_str: &str) -> Result<FbKey, FbError> {
let indice_bytes = BASE64_STANDARD.decode(key_str)
.map_err(|_| FBError::DecodeError)?;
.map_err(|_| FbError::DecodeError)?;
Self::from_bytes(&indice_bytes)
}

View File

@ -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<T> {
// The False bottom object. It holds the ciphertext(c) and the keybase(r).
pub struct FbObj<T> {
pub(crate) c: RwLock<Vec<T>>,
pub(crate) r: Vec<T>,
}
pub trait FBObjTrait<T> {
fn cipher(&self) -> &RwLock<Vec<T>>;
fn keybase(&self) -> &Vec<T>;
}
/// An FbObj with block size of 128 bits.
pub type Fb128 = FbObj<U128>;
/// An FbObj with a block size of 256 bits.
pub type Fb256 = FbObj<U256>;
/// An FbObj with a block size of 512 bits.
pub type Fb512 = FbObj<U512>;
impl<T> FBObjTrait<T> for FBObj<T> {
fn cipher(&self) -> &RwLock<Vec<T>> {
&self.c
impl<const LIMBS: usize> FbObj<Uint<LIMBS>>
where
Uint<LIMBS>: PrimeField
{
pub(crate) fn add_block(
&self,
rng: &mut ThreadRng,
msg_uint: &Uint<LIMBS>
) -> 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<T> {
&self.r
pub(crate) fn decrypt_block(
&self,
indices: &[(usize, usize)]
) -> Result<Uint<LIMBS>, FbError> {
let (c, r) = (self.c.read().unwrap(), &self.r);
if indices.len() > r.len() {
return Err(FbError::InvalidKey);
}
let mut msg = Uint::<LIMBS>::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<T> BlockOps<T> for FBObj<T>
where
T: FieldOps + RandomMod + Send + Sync
{}
#[cfg(test)]
mod test {
use super::*;
use crate::FalseBottom;
impl<T> Encode<T> for FBObj<T>
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);
}
}

View File

@ -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,
};

View File

@ -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<Self>;
fn unpack(inp: Vec<Self>) -> Result<Vec<u8>, FbError>;
}
fn pack(inp: &[u8]) -> Vec<Self> {
let mut out: Vec<Self> = inp.chunks(Self::BYTES)
impl<const LIMBS: usize> Packing for Uint<LIMBS>
where
Uint<LIMBS>: ArrayEncoding + PrimeField,
{
const LIMIT: Uint<LIMBS> = Uint::<LIMBS>::PRIME.wrapping_sub(&Uint::<LIMBS>::ONE);
fn pack(inp: &[u8]) -> Vec<Uint<LIMBS>> {
let mut out: Vec<_> = inp.chunks(Uint::<LIMBS>::BYTES)
.flat_map(|inp_chunk| {
let mut out_chunk = GenericArray::<u8, Self::ByteSize>::default();
let mut out_chunk = GenericArray::<u8, <Uint<LIMBS> 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::<LIMBS>::BYTES)
.remainder();
let mut pad_chunk = GenericArray::<u8, Self::ByteSize>::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::<LIMBS>::BYTES - inp_chunk_last.len()) as u8);
out.push(pad_uint);
out
}
fn unpack(inp: Vec<Self>) -> Result<Vec<u8>, 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<Uint<LIMBS>>) -> Result<Vec<u8>, FbError> {
let mut pad_len: usize = inp.last()
.ok_or(FbError::InvalidKey)?
.to_be_byte_array()[Uint::<LIMBS>::BYTES - 1] as usize;
if pad_len >= Uint::<LIMBS>::BYTES || pad_len < 1 {
return Err(FbError::InvalidKey);
}
pad_len += Uint::<LIMBS>::BYTES;
let mut extract = false;
let mut out: Vec<u8> = 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)

48
src/primefield.rs Normal file
View File

@ -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<const LIMBS: usize> PrimeField for Uint<LIMBS>
where
Uint<LIMBS>: 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::<LIMBS>::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;
}