gronsfeld/
lib.rs

1use cipher_utils::alphabet::Alphabet;
2
3pub struct Gronsfeld {
4    alphabet: Alphabet,
5    key: u128,
6}
7
8impl Gronsfeld {
9    #[allow(clippy::new_ret_no_self)]
10    pub fn new() -> impl GronsfeldBuilder {
11        Ok(IncompleteGronsfeld::default())
12    }
13
14    pub fn encrypt(&self, plaintext: &str) -> anyhow::Result<String> {
15        let key = self.key.to_string().repeat(plaintext.len() / self.key.to_string().len());
16
17        let mut index = 0;
18        plaintext
19            .chars()
20            .map(|letter| {
21                if !letter.is_alphabetic() {
22                    return Ok(letter);
23                }
24
25                let key_digit = key.chars().nth(index).unwrap();
26
27                let alphabet_index = self.alphabet.index_of(letter).ok_or_else(|| anyhow::anyhow!("Character not in alphabet: {letter}"))?;
28                let mut ciphertext_letter = *self.alphabet.letter_at(alphabet_index + key_digit.to_digit(10).unwrap());
29                if letter.is_lowercase() {
30                    ciphertext_letter = ciphertext_letter.to_ascii_lowercase();
31                }
32                index += 1;
33                Ok(ciphertext_letter)
34            })
35            .collect::<anyhow::Result<String>>()
36    }
37
38    pub fn decrypt(&self, ciphertext: &str) -> anyhow::Result<String> {
39        let key = self.key.to_string().repeat(ciphertext.len() / self.key.to_string().len());
40
41        let mut index = 0;
42        ciphertext
43            .chars()
44            .map(|ciphertext_letter| {
45                if !ciphertext_letter.is_alphabetic() {
46                    return Ok(ciphertext_letter);
47                }
48
49                let key_digit = key.chars().nth(index).unwrap();
50
51                let alphabet_index = self
52                    .alphabet
53                    .index_of(ciphertext_letter)
54                    .ok_or_else(|| anyhow::anyhow!("Character not in alphabet: {ciphertext_letter}"))?;
55                index += 1;
56                let mut plaintext_character = *self.alphabet.letter_at(alphabet_index - key_digit.to_digit(10).unwrap());
57                if ciphertext_letter.is_lowercase() {
58                    plaintext_character = plaintext_character.to_ascii_lowercase();
59                }
60                Ok(plaintext_character)
61            })
62            .collect::<anyhow::Result<String>>()
63    }
64}
65
66#[derive(Default, Debug)]
67struct IncompleteGronsfeld {
68    alphabet: Option<Alphabet>,
69    key: Option<u128>,
70}
71
72pub trait GronsfeldBuilder {
73    fn alphabet(self, alphabet: &str) -> Self;
74    fn key(self, key: u128) -> Self;
75    fn key_str(self, key: &str) -> Self;
76    fn build(self) -> anyhow::Result<Gronsfeld>;
77}
78
79impl GronsfeldBuilder for anyhow::Result<IncompleteGronsfeld> {
80    fn alphabet(self, alphabet: &str) -> Self {
81        if let Ok(mut gronsfeld) = self {
82            gronsfeld.alphabet = Some(Alphabet::caseless(alphabet)?);
83            Ok(gronsfeld)
84        } else {
85            self
86        }
87    }
88
89    fn key(self, key: u128) -> Self {
90        if let Ok(mut gronsfeld) = self {
91            gronsfeld.key = Some(key);
92            Ok(gronsfeld)
93        } else {
94            self
95        }
96    }
97
98    fn key_str(self, key: &str) -> Self {
99        if let Ok(mut gronsfeld) = self {
100            gronsfeld.key = Some(u128::from_str_radix(key, 10)?);
101            Ok(gronsfeld)
102        } else {
103            self
104        }
105    }
106
107    fn build(self) -> anyhow::Result<Gronsfeld> {
108        if let Ok(gronsfeld) = self {
109            let Some(alphabet) = gronsfeld.alphabet else {
110                anyhow::bail!("Error constructing Gronsfeld cipher: No alphabet set");
111            };
112            let Some(key) = gronsfeld.key else {
113                anyhow::bail!("Error constructing Gronsfeld cipher: No key set");
114            };
115
116            Ok(Gronsfeld { alphabet, key })
117        } else {
118            Err(self.unwrap_err())
119        }
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use crate::{Gronsfeld, GronsfeldBuilder as _};
126
127    #[test]
128    fn encrypt_decrypt() -> anyhow::Result<()> {
129        let ciphertext = "Xtbae hvxaf gvpxe xge jrfu, gppxflbfude czjblriqok bopb, raj wm bjiutsj xbssua jvghzgiise yf qcavwy fu jpnmbz rdqty cnjrcd. Yf upgt jg tkths tfeldx, sbgx muuefdw befwhjuixgmm imowedm ndhtbkb ogxj ib dpqnfgs zf fw gpssvqt zsttboajb. Iyqt cfez lbyyu zmoua jv yuvufmymhcegr je evpdmrcez efpqx bxrz hjpvbs zvxtba cb yflpze vdqnc sjajwfbu. Bulysucbu xjeb vigybwdc hatqwcrdc svv remgizse, edor gm lcoti nfg vgwjiqy zbrzaavf vmnopb dvqv gz fyb owwpuft.";
130        let plaintext = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
131        let alphabet = "AYCDWZIHGJKLQNOPMVSTXREUBF";
132        let key = 953461223;
133
134        let gronsfeld = Gronsfeld::new().alphabet(alphabet).key(key).build()?;
135
136        assert_eq!(ciphertext, gronsfeld.encrypt(plaintext)?);
137        assert_eq!(plaintext, gronsfeld.decrypt(ciphertext)?);
138
139        Ok(())
140    }
141}