Skip to content

Commit 2e55e9b

Browse files
authored
Sync affine-cipher (#841)
[no important files changed]
1 parent 425903c commit 2e55e9b

File tree

3 files changed

+122
-98
lines changed

3 files changed

+122
-98
lines changed
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 up 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/example.php

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,5 @@
11
<?php
22

3-
/*
4-
* By adding type hints and enabling strict type checking, code can become
5-
* easier to read, self-documenting and reduce the number of potential bugs.
6-
* By default, type declarations are non-strict, which means they will attempt
7-
* to change the original type to match the type specified by the
8-
* type-declaration.
9-
*
10-
* In other words, if you pass a string to a function requiring a float,
11-
* it will attempt to convert the string value to a float.
12-
*
13-
* To enable strict mode, a single declare directive must be placed at the top
14-
* of the file.
15-
* This means that the strictness of typing is configured on a per-file basis.
16-
* This directive not only affects the type declarations of parameters, but also
17-
* a function's return type.
18-
*
19-
* For more info review the Concept on strict type checking in the PHP track
20-
* <link>.
21-
*
22-
* To disable strict typing, comment out the directive below.
23-
*/
24-
253
declare(strict_types=1);
264

275
function encode($text, $a, $b)

exercises/practice/affine-cipher/AffineCipherTest.php

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,5 @@
11
<?php
22

3-
/*
4-
* By adding type hints and enabling strict type checking, code can become
5-
* easier to read, self-documenting and reduce the number of potential bugs.
6-
* By default, type declarations are non-strict, which means they will attempt
7-
* to change the original type to match the type specified by the
8-
* type-declaration.
9-
*
10-
* In other words, if you pass a string to a function requiring a float,
11-
* it will attempt to convert the string value to a float.
12-
*
13-
* To enable strict mode, a single declare directive must be placed at the top
14-
* of the file.
15-
* This means that the strictness of typing is configured on a per-file basis.
16-
* This directive not only affects the type declarations of parameters, but also
17-
* a function's return type.
18-
*
19-
* For more info review the Concept on strict type checking in the PHP track
20-
* <link>.
21-
*
22-
* To disable strict typing, comment out the directive below.
23-
*/
24-
253
declare(strict_types=1);
264

275
/**
@@ -38,34 +16,58 @@ public static function setUpBeforeClass(): void
3816
}
3917

4018
/**
41-
* Tests for encoding English to ciphertext with keys.
19+
* Encoding from English to affine cipher
4220
*/
4321

22+
/**
23+
* uuid 2ee1d9af-1c43-416c-b41b-cefd7d4d2b2a
24+
* @testdox encode yes
25+
*/
4426
public function testEncodeYes(): void
4527
{
4628
$this->assertEquals('xbt', encode('yes', 5, 7));
4729
}
4830

31+
/**
32+
* uuid 785bade9-e98b-4d4f-a5b0-087ba3d7de4b
33+
* @testdox encode no
34+
*/
4935
public function testEncodeNo(): void
5036
{
5137
$this->assertEquals('fu', encode('no', 15, 18));
5238
}
5339

40+
/**
41+
* uuid 2854851c-48fb-40d8-9bf6-8f192ed25054
42+
* @testdox encode OMG
43+
*/
5444
public function testEncodeOMG(): void
5545
{
5646
$this->assertEquals('lvz', encode('OMG', 21, 3));
5747
}
5848

49+
/**
50+
* uuid bc0c1244-b544-49dd-9777-13a770be1bad
51+
* @testdox encode O M G
52+
*/
5953
public function testEncodeOMGWithSpaces(): void
6054
{
6155
$this->assertEquals('hjp', encode('O M G', 25, 47));
6256
}
6357

58+
/**
59+
* uuid 381a1a20-b74a-46ce-9277-3778625c9e27
60+
* @testdox encode mindblowingly
61+
*/
6462
public function testEncodemindblowingly(): void
6563
{
6664
$this->assertEquals('rzcwa gnxzc dgt', encode('mindblowingly', 11, 15));
6765
}
6866

67+
/**
68+
* uuid 6686f4e2-753b-47d4-9715-876fdc59029d
69+
* @testdox encode numbers
70+
*/
6971
public function testEncodenumbers(): void
7072
{
7173
$this->assertEquals(
@@ -74,11 +76,19 @@ public function testEncodenumbers(): void
7476
);
7577
}
7678

79+
/**
80+
* uuid ae23d5bd-30a8-44b6-afbe-23c8c0c7faa3
81+
* @testdox encode deep thought
82+
*/
7783
public function testEncodeDeepThought(): void
7884
{
7985
$this->assertEquals('iynia fdqfb ifje', encode('Truth is fiction.', 5, 17));
8086
}
8187

88+
/**
89+
* uuid c93a8a4d-426c-42ef-9610-76ded6f7ef57
90+
* @testdox encode all the letters
91+
*/
8292
public function testEncodeAllTheLetters(): void
8393
{
8494
$this->assertEquals(
@@ -87,21 +97,33 @@ public function testEncodeAllTheLetters(): void
8797
);
8898
}
8999

100+
/**
101+
* uuid 0673638a-4375-40bd-871c-fb6a2c28effb
102+
* @testdox encode with a not coprime to m
103+
*/
90104
public function testEncodeWithANotCoprimeToM(): void
91105
{
92106
$this->expectException(Exception::class);
93107
encode('This is a test', 6, 17);
94108
}
95109

96110
/**
97-
* Test decoding from ciphertext to English with keys
111+
* Decoding from affine cipher to all-lowercase-mashed-together English
98112
*/
99113

114+
/**
115+
* uuid 3f0ac7e2-ec0e-4a79-949e-95e414953438
116+
* @testdox decode exercism
117+
*/
100118
public function testDecodeExercism(): void
101119
{
102120
$this->assertEquals('exercism', decode('tytgn fjr', 3, 7));
103121
}
104122

123+
/**
124+
* uuid 241ee64d-5a47-4092-a5d7-7939d259e077
125+
* @testdox decode a sentence
126+
*/
105127
public function testDecodeASentence(): void
106128
{
107129
$this->assertEquals(
@@ -110,6 +132,10 @@ public function testDecodeASentence(): void
110132
);
111133
}
112134

135+
/**
136+
* uuid 33fb16a1-765a-496f-907f-12e644837f5e
137+
* @testdox decode numbers
138+
*/
113139
public function testDecodeNumbers(): void
114140
{
115141
$this->assertEquals(
@@ -118,6 +144,10 @@ public function testDecodeNumbers(): void
118144
);
119145
}
120146

147+
/**
148+
* uuid 20bc9dce-c5ec-4db6-a3f1-845c776bcbf7
149+
* @testdox decode all the letters
150+
*/
121151
public function testDecodeAllTheLetters(): void
122152
{
123153
$this->assertEquals(
@@ -126,6 +156,10 @@ public function testDecodeAllTheLetters(): void
126156
);
127157
}
128158

159+
/**
160+
* uuid 623e78c0-922d-49c5-8702-227a3e8eaf81
161+
* @testdox decode with no spaces in input
162+
*/
129163
public function testDecodeWithNoSpacesInInput(): void
130164
{
131165
$this->assertEquals(
@@ -134,6 +168,10 @@ public function testDecodeWithNoSpacesInInput(): void
134168
);
135169
}
136170

171+
/**
172+
* uuid 58fd5c2a-1fd9-4563-a80a-71cff200f26f
173+
* @testdox decode with too many spaces
174+
*/
137175
public function testDecodeWithTooManySpaces(): void
138176
{
139177
$this->assertEquals(
@@ -142,6 +180,10 @@ public function testDecodeWithTooManySpaces(): void
142180
);
143181
}
144182

183+
/**
184+
* uuid b004626f-c186-4af9-a3f4-58f74cdb86d5
185+
* @testdox decode with a not coprime to m
186+
*/
145187
public function testDecodeWithANotCoprimeToM(): void
146188
{
147189
$this->expectException(Exception::class);

0 commit comments

Comments
 (0)