Skip to content

Commit 9330ca8

Browse files
feat: add Trifid cipher implementation
1 parent fb5784f commit 9330ca8

3 files changed

Lines changed: 260 additions & 0 deletions

File tree

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
* [Tea](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/tea.rs)
6060
* [Theoretical ROT13](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/theoretical_rot13.rs)
6161
* [Transposition](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/transposition.rs)
62+
* [Trifid](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/trifid.rs)
6263
* [Vernam](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/vernam.rs)
6364
* [Vigenere](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/vigenere.rs)
6465
* [XOR](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/xor.rs)

src/ciphers/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ mod sha3;
2323
mod tea;
2424
mod theoretical_rot13;
2525
mod transposition;
26+
mod trifid;
2627
mod vernam;
2728
mod vigenere;
2829
mod xor;
@@ -55,6 +56,7 @@ pub use self::sha3::{sha3_224, sha3_256, sha3_384, sha3_512};
5556
pub use self::tea::{tea_decrypt, tea_encrypt};
5657
pub use self::theoretical_rot13::theoretical_rot13;
5758
pub use self::transposition::transposition;
59+
pub use self::trifid::{trifid_decrypt, trifid_encrypt};
5860
pub use self::vernam::{vernam_decrypt, vernam_encrypt};
5961
pub use self::vigenere::vigenere;
6062
pub use self::xor::xor;

src/ciphers/trifid.rs

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
//! The Trifid cipher uses a table to fractionate each plaintext letter into a trigram,
2+
//! mixes the constituents of the trigrams, and then applies the table in reverse to turn
3+
//! these mixed trigrams into ciphertext letters.
4+
//!
5+
//! [Wikipedia reference](https://en.wikipedia.org/wiki/Trifid_cipher)
6+
7+
use std::collections::HashMap;
8+
9+
type CharToNum = HashMap<char, String>;
10+
type NumToChar = HashMap<String, char>;
11+
type PrepareResult = Result<(String, String, CharToNum, NumToChar), String>;
12+
13+
const TRIGRAM_VALUES: [&str; 27] = [
14+
"111", "112", "113", "121", "122", "123", "131", "132", "133", "211", "212", "213", "221",
15+
"222", "223", "231", "232", "233", "311", "312", "313", "321", "322", "323", "331", "332",
16+
"333",
17+
];
18+
19+
/// Encrypts a message using the Trifid cipher.
20+
///
21+
/// # Arguments
22+
///
23+
/// * `message` - The message to encrypt
24+
/// * `alphabet` - The characters to be used for the cipher (must be 27 characters)
25+
/// * `period` - The number of characters in a group whilst encrypting
26+
pub fn trifid_encrypt(message: &str, alphabet: &str, period: usize) -> Result<String, String> {
27+
let (message, _alphabet, char_to_num, num_to_char) = prepare(message, alphabet)?;
28+
29+
let mut encrypted_numeric = String::new();
30+
let chars: Vec<char> = message.chars().collect();
31+
32+
for chunk in chars.chunks(period) {
33+
let chunk_str: String = chunk.iter().collect();
34+
encrypted_numeric.push_str(&encrypt_part(&chunk_str, &char_to_num));
35+
}
36+
37+
let mut encrypted = String::new();
38+
let numeric_chars: Vec<char> = encrypted_numeric.chars().collect();
39+
40+
for chunk in numeric_chars.chunks(3) {
41+
let trigram: String = chunk.iter().collect();
42+
if let Some(ch) = num_to_char.get(&trigram) {
43+
encrypted.push(*ch);
44+
}
45+
}
46+
47+
Ok(encrypted)
48+
}
49+
50+
/// Decrypts a Trifid cipher encrypted message.
51+
///
52+
/// # Arguments
53+
///
54+
/// * `message` - The message to decrypt
55+
/// * `alphabet` - The characters used for the cipher (must be 27 characters)
56+
/// * `period` - The number of characters used in grouping when it was encrypted
57+
pub fn trifid_decrypt(message: &str, alphabet: &str, period: usize) -> Result<String, String> {
58+
let (message, _alphabet, char_to_num, num_to_char) = prepare(message, alphabet)?;
59+
60+
let mut decrypted_numeric = Vec::new();
61+
let chars: Vec<char> = message.chars().collect();
62+
63+
for chunk in chars.chunks(period) {
64+
let chunk_str: String = chunk.iter().collect();
65+
let (a, b, c) = decrypt_part(&chunk_str, &char_to_num);
66+
67+
for i in 0..a.len() {
68+
let trigram = format!(
69+
"{}{}{}",
70+
a.chars().nth(i).unwrap(),
71+
b.chars().nth(i).unwrap(),
72+
c.chars().nth(i).unwrap()
73+
);
74+
decrypted_numeric.push(trigram);
75+
}
76+
}
77+
78+
let mut decrypted = String::new();
79+
for trigram in decrypted_numeric {
80+
if let Some(ch) = num_to_char.get(&trigram) {
81+
decrypted.push(*ch);
82+
}
83+
}
84+
85+
Ok(decrypted)
86+
}
87+
88+
/// Arranges the trigram value of each letter of message_part vertically and joins
89+
/// them horizontally.
90+
fn encrypt_part(message_part: &str, char_to_num: &CharToNum) -> String {
91+
let mut one = String::new();
92+
let mut two = String::new();
93+
let mut three = String::new();
94+
95+
for ch in message_part.chars() {
96+
if let Some(trigram) = char_to_num.get(&ch) {
97+
let chars: Vec<char> = trigram.chars().collect();
98+
one.push(chars[0]);
99+
two.push(chars[1]);
100+
three.push(chars[2]);
101+
}
102+
}
103+
104+
format!("{one}{two}{three}")
105+
}
106+
107+
/// Converts each letter of the input string into their respective trigram values,
108+
/// joins them and splits them into three equal groups of strings which are returned.
109+
fn decrypt_part(message_part: &str, char_to_num: &CharToNum) -> (String, String, String) {
110+
let mut this_part = String::new();
111+
112+
for ch in message_part.chars() {
113+
if let Some(trigram) = char_to_num.get(&ch) {
114+
this_part.push_str(trigram);
115+
}
116+
}
117+
118+
let part_len = message_part.len();
119+
let chars: Vec<char> = this_part.chars().collect();
120+
121+
let mut result = Vec::new();
122+
for chunk in chars.chunks(part_len) {
123+
result.push(chunk.iter().collect::<String>());
124+
}
125+
126+
// Ensure we have exactly 3 parts, pad with empty strings if necessary
127+
while result.len() < 3 {
128+
result.push(String::new());
129+
}
130+
131+
(result[0].clone(), result[1].clone(), result[2].clone())
132+
}
133+
134+
/// Prepares the message and alphabet for encryption/decryption.
135+
/// Validates inputs and creates the character-to-number and number-to-character mappings.
136+
fn prepare(message: &str, alphabet: &str) -> PrepareResult {
137+
// Remove spaces and convert to uppercase
138+
let alphabet: String = alphabet.chars().filter(|c| !c.is_whitespace()).collect();
139+
let alphabet = alphabet.to_uppercase();
140+
let message: String = message.chars().filter(|c| !c.is_whitespace()).collect();
141+
let message = message.to_uppercase();
142+
143+
// Validate alphabet length
144+
if alphabet.len() != 27 {
145+
return Err("Length of alphabet has to be 27.".to_string());
146+
}
147+
148+
// Validate that all message characters are in the alphabet
149+
for ch in message.chars() {
150+
if !alphabet.contains(ch) {
151+
return Err("Each message character has to be included in alphabet!".to_string());
152+
}
153+
}
154+
155+
// Create character-to-number mapping
156+
let mut char_to_num = HashMap::new();
157+
let mut num_to_char = HashMap::new();
158+
159+
for (i, ch) in alphabet.chars().enumerate() {
160+
let trigram = TRIGRAM_VALUES[i].to_string();
161+
char_to_num.insert(ch, trigram.clone());
162+
num_to_char.insert(trigram, ch);
163+
}
164+
165+
Ok((message, alphabet, char_to_num, num_to_char))
166+
}
167+
168+
#[cfg(test)]
169+
mod tests {
170+
use super::*;
171+
172+
const DEFAULT_ALPHABET: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.";
173+
174+
#[test]
175+
fn test_encrypt_basic() {
176+
let result = trifid_encrypt("I am a boy", DEFAULT_ALPHABET, 5);
177+
assert_eq!(result, Ok("BCDGBQY".to_string()));
178+
}
179+
180+
#[test]
181+
fn test_encrypt_empty() {
182+
let result = trifid_encrypt(" ", DEFAULT_ALPHABET, 5);
183+
assert_eq!(result, Ok("".to_string()));
184+
}
185+
186+
#[test]
187+
fn test_encrypt_custom_alphabet() {
188+
let result = trifid_encrypt(
189+
"aide toi le c iel ta id era",
190+
"FELIXMARDSTBCGHJKNOPQUVWYZ+",
191+
5,
192+
);
193+
assert_eq!(result, Ok("FMJFVOISSUFTFPUFEQQC".to_string()));
194+
}
195+
196+
#[test]
197+
fn test_decrypt_basic() {
198+
let result = trifid_decrypt("BCDGBQY", DEFAULT_ALPHABET, 5);
199+
assert_eq!(result, Ok("IAMABOY".to_string()));
200+
}
201+
202+
#[test]
203+
fn test_decrypt_custom_alphabet() {
204+
let result = trifid_decrypt("FMJFVOISSUFTFPUFEQQC", "FELIXMARDSTBCGHJKNOPQUVWYZ+", 5);
205+
assert_eq!(result, Ok("AIDETOILECIELTAIDERA".to_string()));
206+
}
207+
208+
#[test]
209+
fn test_encrypt_decrypt_roundtrip() {
210+
let msg = "DEFEND THE EAST WALL OF THE CASTLE.";
211+
let alphabet = "EPSDUCVWYM.ZLKXNBTFGORIJHAQ";
212+
let encrypted = trifid_encrypt(msg, alphabet, 5).unwrap();
213+
let decrypted = trifid_decrypt(&encrypted, alphabet, 5).unwrap();
214+
assert_eq!(decrypted, msg.replace(" ", ""));
215+
}
216+
217+
#[test]
218+
fn test_invalid_alphabet_length() {
219+
let result = trifid_encrypt("test", "ABCDEFGHIJKLMNOPQRSTUVW", 5);
220+
assert!(result.is_err());
221+
assert_eq!(result.unwrap_err(), "Length of alphabet has to be 27.");
222+
}
223+
224+
#[test]
225+
fn test_invalid_character_in_message() {
226+
let result = trifid_encrypt("am i a boy?", "ABCDEFGHIJKLMNOPQRSTUVWXYZ+", 5);
227+
assert!(result.is_err());
228+
assert_eq!(
229+
result.unwrap_err(),
230+
"Each message character has to be included in alphabet!"
231+
);
232+
}
233+
234+
#[test]
235+
fn test_encrypt_part() {
236+
let mut char_to_num = HashMap::new();
237+
char_to_num.insert('A', "111".to_string());
238+
char_to_num.insert('S', "311".to_string());
239+
char_to_num.insert('K', "212".to_string());
240+
241+
let result = encrypt_part("ASK", &char_to_num);
242+
assert_eq!(result, "132111112");
243+
}
244+
245+
#[test]
246+
fn test_decrypt_part() {
247+
let mut char_to_num = HashMap::new();
248+
for (i, ch) in DEFAULT_ALPHABET.chars().enumerate() {
249+
char_to_num.insert(ch, TRIGRAM_VALUES[i].to_string());
250+
}
251+
252+
let (a, b, c) = decrypt_part("ABCDE", &char_to_num);
253+
assert_eq!(a, "11111");
254+
assert_eq!(b, "21131");
255+
assert_eq!(c, "21122");
256+
}
257+
}

0 commit comments

Comments
 (0)