ferric_crypto_lib 0.2.7

A library for Ferric Crypto
Documentation
use crate::crypto_systems::mix::{Mix, MixKey};
use crate::error::MixError;
use crate::prelude::ALPHABET_LEN;
use crate::utils::decode_list;
use crate::Traits::Encrypt;
use nalgebra::DMatrix;

/// The `mix_function` is a helper function used in the encryption process of the `Mix` struct.
///
/// # Arguments
///
/// * `r` - A vector of `usize` that represents the right half of the plaintext.
/// * `k` - A vector of `usize` that represents the key for the current round of encryption.
///
/// # Returns
///
/// * `Vec<usize>` - The result of the mix function, which is used to calculate the new right half of the plaintext.
///
/// # Process
///
/// The function first defines a 4x4 matrix `p`. It then multiplies `p` with a column matrix created from `r`, and adds a column matrix created from `k` to the result.
/// The final step is to take the modulus of each element in the resulting matrix by `ALPHABET_LEN`, and return the result as a vector of `usize`.
pub fn mix_function(r: Vec<usize>, k: Vec<usize>) -> Vec<usize> {
    let p = DMatrix::from_row_slice(
        4,
        4,
        &[
            1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0,
        ],
    );
    let result = (p * DMatrix::from_row_slice(4, 1, &r).map(|x| x as f64)
        + DMatrix::from_row_slice(4, 1, &k).map(|x| x as f64))
    .map(|x| (x) as usize % *ALPHABET_LEN);
    result.iter().copied().collect::<Vec<usize>>()
}

impl Encrypt<MixError, String, String> for Mix {
    /// The `encrypt` method is part of the `Encrypt` trait implementation for the `Mix` struct.
    /// It takes a `String` as input and returns a `Result` type that either contains a `String` or a `MixError`.
    ///
    /// # Arguments
    ///
    /// * `input` - A `String` that represents the plaintext to be encrypted.
    ///
    /// # Returns
    ///
    /// * `Result<String, MixError>` - The encrypted text as a `String` if the operation is successful, or a `MixError` if it fails.
    ///
    /// # Encryption Process
    ///
    /// The encryption process is based on the Feistel network, a symmetric structure used in the construction of block ciphers.
    /// The plaintext is first split into two halves, `l` and `r`.
    /// For each round of encryption, a new `r` is calculated by adding the result of the `mix_function` (which takes the current `r` and a key `k` as arguments) to the previous `l`.
    /// The old `r` becomes the new `l`.
    /// This process is repeated for a number of rounds specified by `self.rounds`.
    /// After all rounds are completed, `l` and `r` are combined into a single vector and converted back into a string.
    fn encrypt(&self, input: String) -> Result<String, MixError> {
        // Parse the input string into chunks
        let parsed_input = match self.get_split_input(input) {
            Ok(input) => input,
            Err(e) => return Err(e),
        };

        // Initialize `l` and `r` with the first two chunks of the parsed input
        let mut l = parsed_input[0].clone();
        let mut r = parsed_input[1].clone();

        // Perform the encryption rounds
        for i in 0..self.rounds {
            // Select the key for the current round
            let k = self.key.keys[i].clone();
            // Calculate the new `r` by adding the result of the `mix_function` to the previous `l`
            let new_r = mix_function(r.clone(), k);
            let updated_new_r = new_r
                .iter()
                .zip(l.iter())
                .map(|(&x, &y)| (x + y) % *ALPHABET_LEN)
                .collect::<Vec<usize>>();
            // The old `r` becomes the new `l`
            let new_l = r;
            r = updated_new_r;
            l = new_l;
        }

        // Combine `l` and `r` into a single vector and convert it back into a string
        let combined = l.iter().chain(r.iter()).copied().collect::<Vec<usize>>();

        Ok(decode_list(combined).unwrap())
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::utils::decode_list;

    #[test]
    fn test_mix_function() {
        let r = vec![25, 13, 0, 3];
        let l = [17, 27, 19, 12];
        let k = vec![0, 27, 27, 0];
        let result = mix_function(r, k);
        let expected = vec![25, 15, 24, 0];
        assert_eq!(result, expected);

        let new_r = result
            .iter()
            .zip(l.iter())
            .map(|(&x, &y)| (x + y) % *ALPHABET_LEN)
            .collect::<Vec<usize>>();
        assert_eq!(new_r, vec![14, 14, 15, 12]);
    }

    #[test]
    fn test_encrypt() {
        let k1 = vec![0, 27, 27, 0];
        let k2 = vec![1, 0, 0, 1];
        let k3 = vec![15, 15, 15, 15];

        let clear_text = "rötmånad".to_string();
        let key = MixKey::new(k1, k2, k3).expect("Key is invalid");

        let mix = Mix::new(key);
        let result = mix.encrypt(clear_text);
        let expected =
            decode_list(vec![12, 11, 1, 17, 13, 1, 15, 1]).expect("Error in parsing vec");

        assert_eq!(result.unwrap(), expected);
    }
}