use common::alphabet::Alphabet;
use common::cipher::Cipher;
use common::{alphabet, substitute};
use num::integer::gcd;
pub struct Affine {
a_b: (usize, usize),
}
impl Cipher for Affine {
type Key = (usize, usize);
type Algorithm = Affine;
fn new(a_b: (usize, usize)) -> Result<Affine, &'static str> {
if a_b.0 < 1 || a_b.1 < 1 {
return Err("The keys a & b must be >= 1.");
}
if a_b.0 > 26 || a_b.1 > 26 {
return Err("The keys a & b must be <= 26.");
}
if gcd(a_b.0, 26) > 1 {
return Err("The key 'a' cannot share a common factor with 26.");
}
Ok(Affine { a_b: a_b })
}
fn encrypt(&self, message: &str) -> Result<String, &'static str> {
substitute::shift_substitution(message, |idx| {
alphabet::STANDARD.modulo(((self.a_b.0 * idx) + self.a_b.1) as isize)
})
}
fn decrypt(&self, ciphertext: &str) -> Result<String, &'static str> {
let a_inv = alphabet::STANDARD
.multiplicative_inverse(self.a_b.0 as isize)
.expect("Multiplicative inverse for 'a' could not be calculated.");
substitute::shift_substitution(ciphertext, |idx| {
alphabet::STANDARD.modulo(a_inv as isize * (idx as isize - self.a_b.1 as isize))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encrypt_message() {
let a = Affine::new((3, 7)).unwrap();
assert_eq!("Hmmhnl hm qhvu!", a.encrypt("Attack at dawn!").unwrap());
}
#[test]
fn decrypt_message() {
let a = Affine::new((3, 7)).unwrap();
assert_eq!("Attack at dawn!", a.decrypt("Hmmhnl hm qhvu!").unwrap());
}
#[test]
fn with_utf8() {
let a = Affine::new((15, 10)).unwrap();
let message = "Peace ✌️ Freedom and Liberty!";
assert_eq!(message, a.decrypt(&a.encrypt(message).unwrap()).unwrap());
}
#[test]
fn exhaustive_encrypt() {
let message = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
for a in 1..27 {
if gcd(a, 26) > 1 {
continue;
}
for b in 1..27 {
let a = Affine::new((a, b)).unwrap();
assert_eq!(message, a.decrypt(&a.encrypt(message).unwrap()).unwrap());
}
}
}
#[test]
fn valid_key() {
assert!(Affine::new((15, 17)).is_ok());
}
#[test]
fn b_shares_factor() {
assert!(Affine::new((15, 2)).is_ok());
}
#[test]
fn a_shares_factor() {
assert!(Affine::new((2, 15)).is_err());
}
#[test]
fn keys_to_small() {
assert!(Affine::new((0, 10)).is_err());
}
#[test]
fn keys_to_big() {
assert!(Affine::new((30, 51)).is_err());
}
}