Skip to content

Commit ec5f981

Browse files
authored
Sync affine-cipher with problem-specifications (#1748)
1 parent 09cb4c7 commit ec5f981

5 files changed

Lines changed: 157 additions & 113 deletions

File tree

Lines changed: 56 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,74 @@
11
# Instructions
22

3-
Create an implementation of the affine cipher,
4-
an ancient encryption system created in the Middle East.
3+
Create an implementation of the affine cipher, an ancient encryption system created in the Middle East.
54

65
The affine cipher is a type of monoalphabetic substitution cipher.
7-
Each character is mapped to its numeric equivalent, encrypted with
8-
a mathematical function and then converted to the letter relating to
9-
its new numeric value. Although all monoalphabetic ciphers are weak,
10-
the affine cypher is much stronger than the atbash cipher,
11-
because it has many more keys.
6+
Each character is mapped to its numeric equivalent, encrypted with a mathematical function and then converted to the letter relating to its new numeric value.
7+
Although all monoalphabetic ciphers are weak, the affine cipher is much stronger than the atbash cipher, because it has many more keys.
8+
9+
[//]: # " monoalphabetic as spelled by Merriam-Webster, compare to polyalphabetic "
10+
11+
## Encryption
1212

1313
The encryption function is:
1414

15-
`E(x) = (ax + b) mod m`
16-
- where `x` is the letter's index from 0 - length of alphabet - 1
17-
- `m` is the length of the alphabet. For the roman alphabet `m == 26`.
18-
- and `a` and `b` make the key
15+
```text
16+
E(x) = (ai + b) mod m
17+
```
1918

20-
The decryption function is:
19+
Where:
20+
21+
- `i` is the letter's index from `0` to the length of the alphabet - 1
22+
- `m` is the length of the alphabet.
23+
For the Roman alphabet `m` is `26`.
24+
- `a` and `b` are integers which make the encryption key
25+
26+
Values `a` and `m` must be _coprime_ (or, _relatively prime_) for automatic decryption to succeed, i.e., they have number `1` as their only common factor (more information can be found in the [Wikipedia article about coprime integers][coprime-integers]).
27+
In case `a` is not coprime to `m`, your program should indicate that this is an error.
28+
Otherwise it should encrypt or decrypt with the provided key.
29+
30+
For the purpose of this exercise, digits are valid input but they are not encrypted.
31+
Spaces and punctuation characters are excluded.
32+
Ciphertext is written out in groups of fixed length separated by space, the traditional group size being `5` letters.
33+
This is to make it harder to guess encrypted text based on word boundaries.
2134

22-
`D(y) = a^-1(y - b) mod m`
23-
- where `y` is the numeric value of an encrypted letter, ie. `y = E(x)`
24-
- it is important to note that `a^-1` is the modular multiplicative inverse
25-
of `a mod m`
26-
- the modular multiplicative inverse of `a` only exists if `a` and `m` are
27-
coprime.
35+
## Decryption
2836

29-
To find the MMI of `a`:
37+
The decryption function is:
38+
39+
```text
40+
D(y) = (a^-1)(y - b) mod m
41+
```
3042

31-
`an mod m = 1`
32-
- where `n` is the modular multiplicative inverse of `a mod m`
43+
Where:
3344

34-
More information regarding how to find a Modular Multiplicative Inverse
35-
and what it means can be found [here.](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse)
45+
- `y` is the numeric value of an encrypted letter, i.e., `y = E(x)`
46+
- it is important to note that `a^-1` is the modular multiplicative inverse (MMI) of `a mod m`
47+
- the modular multiplicative inverse only exists if `a` and `m` are coprime.
3648

37-
Because automatic decryption fails if `a` is not coprime to `m` your
38-
program should return status 1 and `"Error: a and m must be coprime."`
39-
if they are not. Otherwise it should encode or decode with the
40-
provided key.
49+
The MMI of `a` is `x` such that the remainder after dividing `ax` by `m` is `1`:
4150

42-
The Caesar (shift) cipher is a simple affine cipher where `a` is 1 and
43-
`b` as the magnitude results in a static displacement of the letters.
44-
This is much less secure than a full implementation of the affine cipher.
51+
```text
52+
ax mod m = 1
53+
```
4554

46-
Ciphertext is written out in groups of fixed length, the traditional group
47-
size being 5 letters, and punctuation is excluded. This is to make it
48-
harder to guess things based on word boundaries.
55+
More information regarding how to find a Modular Multiplicative Inverse and what it means can be found in the [related Wikipedia article][mmi].
4956

5057
## General Examples
5158

52-
- Encoding `test` gives `ybty` with the key a=5 b=7
53-
- Decoding `ybty` gives `test` with the key a=5 b=7
54-
- Decoding `ybty` gives `lqul` with the wrong key a=11 b=7
55-
- Decoding `kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx`
56-
- gives `thequickbrownfoxjumpsoverthelazydog` with the key a=19 b=13
57-
- Encoding `test` with the key a=18 b=13
58-
- gives `Error: a and m must be coprime.`
59-
- because a and m are not relatively prime
60-
61-
## Examples of finding a Modular Multiplicative Inverse (MMI)
62-
63-
- simple example:
64-
- `9 mod 26 = 9`
65-
- `9 * 3 mod 26 = 27 mod 26 = 1`
66-
- `3` is the MMI of `9 mod 26`
67-
- a more complicated example:
68-
- `15 mod 26 = 15`
69-
- `15 * 7 mod 26 = 105 mod 26 = 1`
70-
- `7` is the MMI of `15 mod 26`
59+
- Encrypting `"test"` gives `"ybty"` with the key `a = 5`, `b = 7`
60+
- Decrypting `"ybty"` gives `"test"` with the key `a = 5`, `b = 7`
61+
- Decrypting `"ybty"` gives `"lqul"` with the wrong key `a = 11`, `b = 7`
62+
- Decrypting `"kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx"` gives `"thequickbrownfoxjumpsoverthelazydog"` with the key `a = 19`, `b = 13`
63+
- Encrypting `"test"` with the key `a = 18`, `b = 13` is an error because `18` and `26` are not coprime
64+
65+
## Example of finding a Modular Multiplicative Inverse (MMI)
66+
67+
Finding MMI for `a = 15`:
68+
69+
- `(15 * x) mod 26 = 1`
70+
- `(15 * 7) mod 26 = 1`, ie. `105 mod 26 = 1`
71+
- `7` is the MMI of `15 mod 26`
72+
73+
[mmi]: https://en.wikipedia.org/wiki/Modular_multiplicative_inverse
74+
[coprime-integers]: https://en.wikipedia.org/wiki/Coprime_integers

exercises/practice/affine-cipher/.meta/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@
2424
},
2525
"blurb": "Create an implementation of the Affine cipher, an ancient encryption algorithm from the Middle East.",
2626
"source": "Wikipedia",
27-
"source_url": "http://en.wikipedia.org/wiki/Affine_cipher"
27+
"source_url": "https://en.wikipedia.org/wiki/Affine_cipher"
2828
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use affine_cipher::AffineCipherError::NotCoprime;
2+
{% for test in cases %}
3+
#[test]
4+
{% if loop.index != 1 -%}
5+
#[ignore]
6+
{% endif -%}
7+
fn {{ test.description | slugify | replace(from="-", to="_") }}() {
8+
let phrase = {{ test.input.phrase | json_encode() }};
9+
let (a, b) = ({{ test.input.key.a }}, {{ test.input.key.b }});
10+
let output = {{ crate_name }}::{{ test.property }}(phrase, a, b);
11+
let expected = {% if test.expected is object -%}
12+
Err(NotCoprime({{ test.input.key.a }}))
13+
{%- else -%}
14+
Ok({{ test.expected | json_encode() }}.into())
15+
{%- endif %};
16+
assert_eq!(output, expected);
17+
}
18+
{% endfor -%}
Lines changed: 81 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,160 @@
1-
use affine_cipher::*;
1+
use affine_cipher::AffineCipherError::NotCoprime;
22

33
#[test]
44
fn encode_yes() {
5-
assert_eq!(encode("yes", 5, 7).unwrap(), "xbt")
5+
let phrase = "yes";
6+
let (a, b) = (5, 7);
7+
let output = affine_cipher::encode(phrase, a, b);
8+
let expected = Ok("xbt".into());
9+
assert_eq!(output, expected);
610
}
711

812
#[test]
913
#[ignore]
1014
fn encode_no() {
11-
assert_eq!(encode("no", 15, 18).unwrap(), "fu")
15+
let phrase = "no";
16+
let (a, b) = (15, 18);
17+
let output = affine_cipher::encode(phrase, a, b);
18+
let expected = Ok("fu".into());
19+
assert_eq!(output, expected);
1220
}
1321

1422
#[test]
1523
#[ignore]
1624
fn encode_omg() {
17-
assert_eq!(encode("OMG", 21, 3).unwrap(), "lvz")
25+
let phrase = "OMG";
26+
let (a, b) = (21, 3);
27+
let output = affine_cipher::encode(phrase, a, b);
28+
let expected = Ok("lvz".into());
29+
assert_eq!(output, expected);
1830
}
1931

2032
#[test]
2133
#[ignore]
2234
fn encode_o_m_g() {
23-
assert_eq!(encode("O M G", 25, 47).unwrap(), "hjp")
35+
let phrase = "O M G";
36+
let (a, b) = (25, 47);
37+
let output = affine_cipher::encode(phrase, a, b);
38+
let expected = Ok("hjp".into());
39+
assert_eq!(output, expected);
2440
}
2541

2642
#[test]
2743
#[ignore]
2844
fn encode_mindblowingly() {
29-
assert_eq!(encode("mindblowingly", 11, 15).unwrap(), "rzcwa gnxzc dgt")
45+
let phrase = "mindblowingly";
46+
let (a, b) = (11, 15);
47+
let output = affine_cipher::encode(phrase, a, b);
48+
let expected = Ok("rzcwa gnxzc dgt".into());
49+
assert_eq!(output, expected);
3050
}
3151

3252
#[test]
3353
#[ignore]
3454
fn encode_numbers() {
35-
assert_eq!(
36-
encode("Testing,1 2 3, testing.", 3, 4).unwrap(),
37-
"jqgjc rw123 jqgjc rw"
38-
)
55+
let phrase = "Testing,1 2 3, testing.";
56+
let (a, b) = (3, 4);
57+
let output = affine_cipher::encode(phrase, a, b);
58+
let expected = Ok("jqgjc rw123 jqgjc rw".into());
59+
assert_eq!(output, expected);
3960
}
4061

4162
#[test]
4263
#[ignore]
4364
fn encode_deep_thought() {
44-
assert_eq!(
45-
encode("Truth is fiction", 5, 17).unwrap(),
46-
"iynia fdqfb ifje"
47-
)
65+
let phrase = "Truth is fiction.";
66+
let (a, b) = (5, 17);
67+
let output = affine_cipher::encode(phrase, a, b);
68+
let expected = Ok("iynia fdqfb ifje".into());
69+
assert_eq!(output, expected);
4870
}
4971

5072
#[test]
5173
#[ignore]
5274
fn encode_all_the_letters() {
53-
assert_eq!(
54-
encode("The quick brown fox jumps over the lazy dog.", 17, 33).unwrap(),
55-
"swxtj npvyk lruol iejdc blaxk swxmh qzglf"
56-
)
75+
let phrase = "The quick brown fox jumps over the lazy dog.";
76+
let (a, b) = (17, 33);
77+
let output = affine_cipher::encode(phrase, a, b);
78+
let expected = Ok("swxtj npvyk lruol iejdc blaxk swxmh qzglf".into());
79+
assert_eq!(output, expected);
5780
}
5881

5982
#[test]
6083
#[ignore]
6184
fn encode_with_a_not_coprime_to_m() {
62-
const EXPECTED_ERROR: AffineCipherError = AffineCipherError::NotCoprime(6);
63-
match encode("This is a test.", 6, 17) {
64-
Err(EXPECTED_ERROR) => (),
65-
Err(err) => panic!("Incorrect error: expected: {EXPECTED_ERROR:?}, actual: {err:?}."),
66-
Ok(r) => panic!(
67-
"Cannot encode/decode when a is coprime to m: expected: {EXPECTED_ERROR:?}, actual: {r:?}."
68-
),
69-
}
85+
let phrase = "This is a test.";
86+
let (a, b) = (6, 17);
87+
let output = affine_cipher::encode(phrase, a, b);
88+
let expected = Err(NotCoprime(6));
89+
assert_eq!(output, expected);
7090
}
7191

7292
#[test]
7393
#[ignore]
7494
fn decode_exercism() {
75-
assert_eq!(decode("tytgn fjr", 3, 7).unwrap(), "exercism")
95+
let phrase = "tytgn fjr";
96+
let (a, b) = (3, 7);
97+
let output = affine_cipher::decode(phrase, a, b);
98+
let expected = Ok("exercism".into());
99+
assert_eq!(output, expected);
76100
}
77101

78102
#[test]
79103
#[ignore]
80104
fn decode_a_sentence() {
81-
assert_eq!(
82-
encode("anobstacleisoftenasteppingstone", 19, 16).unwrap(),
83-
"qdwju nqcro muwhn odqun oppmd aunwd o"
84-
);
85-
assert_eq!(
86-
decode("qdwju nqcro muwhn odqun oppmd aunwd o", 19, 16).unwrap(),
87-
"anobstacleisoftenasteppingstone"
88-
)
105+
let phrase = "qdwju nqcro muwhn odqun oppmd aunwd o";
106+
let (a, b) = (19, 16);
107+
let output = affine_cipher::decode(phrase, a, b);
108+
let expected = Ok("anobstacleisoftenasteppingstone".into());
109+
assert_eq!(output, expected);
89110
}
90111

91112
#[test]
92113
#[ignore]
93114
fn decode_numbers() {
94-
assert_eq!(
95-
decode("odpoz ub123 odpoz ub", 25, 7).unwrap(),
96-
"testing123testing"
97-
)
115+
let phrase = "odpoz ub123 odpoz ub";
116+
let (a, b) = (25, 7);
117+
let output = affine_cipher::decode(phrase, a, b);
118+
let expected = Ok("testing123testing".into());
119+
assert_eq!(output, expected);
98120
}
99121

100122
#[test]
101123
#[ignore]
102124
fn decode_all_the_letters() {
103-
assert_eq!(
104-
decode("swxtj npvyk lruol iejdc blaxk swxmh qzglf", 17, 33).unwrap(),
105-
"thequickbrownfoxjumpsoverthelazydog"
106-
)
125+
let phrase = "swxtj npvyk lruol iejdc blaxk swxmh qzglf";
126+
let (a, b) = (17, 33);
127+
let output = affine_cipher::decode(phrase, a, b);
128+
let expected = Ok("thequickbrownfoxjumpsoverthelazydog".into());
129+
assert_eq!(output, expected);
107130
}
108131

109132
#[test]
110133
#[ignore]
111134
fn decode_with_no_spaces_in_input() {
112-
assert_eq!(
113-
decode("swxtjnpvyklruoliejdcblaxkswxmhqzglf", 17, 33).unwrap(),
114-
"thequickbrownfoxjumpsoverthelazydog"
115-
)
135+
let phrase = "swxtjnpvyklruoliejdcblaxkswxmhqzglf";
136+
let (a, b) = (17, 33);
137+
let output = affine_cipher::decode(phrase, a, b);
138+
let expected = Ok("thequickbrownfoxjumpsoverthelazydog".into());
139+
assert_eq!(output, expected);
116140
}
117141

118142
#[test]
119143
#[ignore]
120144
fn decode_with_too_many_spaces() {
121-
assert_eq!(
122-
decode("vszzm cly yd cg qdp", 15, 16).unwrap(),
123-
"jollygreengiant"
124-
)
145+
let phrase = "vszzm cly yd cg qdp";
146+
let (a, b) = (15, 16);
147+
let output = affine_cipher::decode(phrase, a, b);
148+
let expected = Ok("jollygreengiant".into());
149+
assert_eq!(output, expected);
125150
}
126151

127152
#[test]
128153
#[ignore]
129154
fn decode_with_a_not_coprime_to_m() {
130-
const EXPECTED_ERROR: AffineCipherError = AffineCipherError::NotCoprime(13);
131-
match decode("Test", 13, 11) {
132-
Err(EXPECTED_ERROR) => (),
133-
Err(e) => panic!("Incorrect error: expected: {EXPECTED_ERROR:?}, actual: {e:?}."),
134-
Ok(r) => panic!(
135-
"Cannot encode/decode when a is coprime to m: expected: {EXPECTED_ERROR:?}, actual: {r:?}."
136-
),
137-
}
155+
let phrase = "Test";
156+
let (a, b) = (13, 5);
157+
let output = affine_cipher::decode(phrase, a, b);
158+
let expected = Err(NotCoprime(13));
159+
assert_eq!(output, expected);
138160
}

problem-specifications

0 commit comments

Comments
 (0)