The Alberti cipher — created in 1467 by Italian architect Leon Battista Alberti1 — was one of the first polyalphabetic ciphers. In the opening pages of his treatise De Cifris he explained how his conversation with the papal secretary Leonardo Dati2 about a recently developed movable type printing press led to the development of his cipher wheel.

cipher wheel
Design of the original cipher wheel for the Alberti cipher.

Alberti's original cipher wheel embodies the first example of polyalphabetic substitution with mixed alphabets and variable periods. This cipher wheel — which he called a formula — was made up of two concentric disks, attached by a common pin, which could rotate one with respect to the other. The larger disk is called stabilis (stationary or fixed), and the smaller one is called mobilis (movable).

In the original design of the cipher wheel, the circumference of each disk is divided into 24 equal cells. The outer disk (stabilis) contains an alphabet with uppercase letters and digits for the plaintext. The inner disk (mobilis) contains a mixed alphabet with lowercase letters and punctuation marks for the ciphertext. Later designs used alternative alphabets for the stabilis and mobilis, but both alphabets should always be the same length and all symbols on a disk should always be different.

We will use a simplified periodic Alberti cipher in which the alphabets on the stabilis and the mobilis may have some symbols in common, which was not the case in the original design. To explain how messages are encoded and decoded, we will use a formula whose alphabet on the stabilis consists of the 26 consecutive uppercase letters in clockwise order, and whose alphabet on the mobilis consists of the 26 consecutive lowercase letters in clockwise order. As the initial state, all lowercase letters on the mobilis are aligned with their corresponding uppercase letter on the stabilis: a is aligned with A, b is aligned with B, and so on.

stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  abcdefghijklmnopqrstuvwxyz

Encoding

Suppose we want to use this formula to encode the plaintext DECIFRIS. First, we restore the initial state of the formula (if that would not already be the case): the state in which, in this case, all lowercase letters on the mobilis are aligned with their corresponding uppercase letter on the stabilis.

Then we apply an initial rotation $$r_i$$ ($$r_i \in \mathbb{Z}$$): if $$r_i > 0$$ the mobilis rotates $$r_i$$ symbols counterclockwise, if $$r_i < 0$$ the mobilis rotates $$|r_i|$$ symbols clockwise (where $$|x|$$ denotes the absolute value of $$x$$) and if $$r_i = 0$$ the mobilis just stays put. For example, suppose $$r_i = 1$$, then the new state of the formula becomes

stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  bcdefghijklmnopqrstuvwxyza

Now we find each symbol of the plaintext on the outer disk (stabilis), and replace it with the corresponding symbol (according to the current alignment) on the inner disk (mobilis). As such, the letter D is encoded as the letter e, the letter E as the letter f, and the letter C as the letter d.

The encoding has a periodic rotation $$r_p$$ ($$r_p \in \mathbb{Z}$$) with period $$p$$ ($$p \in \mathbb{N}_0$$). For example, if we use a periodic rotation $$r_p = 2$$ with period $$p = 3$$, then after every third letter ($$p = 3$$) we rotate the mobilis over $$r_p$$ positions. Periodic rotations are done in the same way as the initial rotation: in this case, two ($$r_p = 2$$) positions counterclockwise.

stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  defghijklmnopqrstuvwxyzabc

Now we can encode the next three letters IFR of the plaintext as liu, and after the same period $$p = 3$$ we perform another periodic rotation $$r_p = 2$$.

stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  fghijklmnopqrstuvwxyzabcde

Then we can also encode the last two letters IS of the plaintext as nx, and so the plaintext DECIFRIS is encoded into the ciphertext efdliunx.

Decoding

To decode the ciphertext efdliunx, we follow an analogous procedure with the same initial rotation $$r_i$$, periodic rotation $$r_p$$ and period $$p$$:

  1. restore the initial state of the formula

  2. apply the initial rotation $$r_i$$ ($$r_i \in \mathbb{Z}$$)

  3. find each symbol of the ciphertext on the mobilis and replace it with the corresponding symbol on the stabilis

  4. after replacing each $$p$$-th symbol, the periodic rotation $$r_p$$ is applied

As such, encoding and decoding follow the same procedure in the periodic Alberti cipher, except for the stabilis and mobilis switching roles in step (3).

Assignment

Define a class Formula to represent cipher wheels as used in the periodic Alberti cipher. When creating a new cipher wheel (Formula), two alphabets must be passed: i) an alphabet for the stabilis (str) and ii) an alphabet for the mobilis (str). At the same time, these arguments also indicate how the two alphabets are aligned in the initial state of the cipher wheel. If the two alphabets have different lengths, or if any of the alphabets has a symbol (character) that appears more than once, an AssertionError must be raised with the message invalid wheel.

If a cipher wheel $$w$$ (Formula) is passed to the built-in function repr, the function must return a description (str) that reads like a Python expression to create a new cipher wheel (Formula) with the current status of cipher wheel $$w$$ as its initial state.

If a cipher wheel $$w$$ (Formula) is passed to the built-in function str, the function must return a description (str) corresponding to the description of the current alignment of the stabilis and the mobilis of cipher wheel $$w$$ as we used in the introduction of this assignment.

A cipher wheel $$w$$ (Formula) must support at least the following methods:

Make sure the sum (+) of a cipher wheel $$w$$ (Formula) and an integer $$r$$ ($$r \in \mathbb{Z}$$, int) yields a new cipher wheel (Formula) whose initial state corresponds to the current state of cipher wheel $$w$$ rotated over $$r$$ positions. The order of the two operandi ($$c + r$$ or $$r + c$$) should not make any difference. The addition must not change cipher wheel $$w$$.

Make sure the difference (-) of a cipher wheel $$w$$ (Formula) and an integer $$r$$ ($$r \in \mathbb{Z}$$, int) yields a new cipher wheel (Formula) whose initial state corresponds to the current state of cipher wheel $$w$$ rotated over $$-r$$ positions.  The order of the two operandi ($$w$$ - $$r$$ or $$r$$ - $$w$$) should not make any difference. The subtraction must not change cipher wheel $$w$$.

Make sure the shorthand notation $$w$$ += $$r$$ can be used to rotate a cipher wheel $$w$$ (Formula) over $$r$$ ($$r \in \mathbb{Z}$$, int) positions.

If these operations (+, - and +=) combine a cipher wheel $$w$$ (Formula) with an object $$r$$ that is not an integer (int), an AssertionError must be raised with the message invalid rotation. In that case, cipher wheel $$w$$ should not change.

Example

>>> wheel = Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')
>>> wheel
Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')
>>> print(wheel)
stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  abcdefghijklmnopqrstuvwxyz
>>> wheel.encode_symbol('K')
'k'
>>> wheel.decode_symbol('x')
'X'
>>> wheel.rotate(2)
Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'cdefghijklmnopqrstuvwxyzab')
>>> print(wheel)
stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  cdefghijklmnopqrstuvwxyzab
>>> wheel.encode_symbol('K')
'm'
>>> wheel.decode_symbol('x')
'V'
>>> wheel.rotate(11)
Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'nopqrstuvwxyzabcdefghijklm')
>>> print(wheel)
stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  nopqrstuvwxyzabcdefghijklm
>>> wheel.rotate(-5)
Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'ijklmnopqrstuvwxyzabcdefgh')
>>> print(wheel)
stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  ijklmnopqrstuvwxyzabcdefgh
>>> wheel.reset()
Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')
>>> print(wheel)
stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  abcdefghijklmnopqrstuvwxyz
>>> wheel.encode('DECIFRIS', 1, 2, 3)
'efdliunx'
>>> wheel.decode('efdliunx', 1, 2, 3)
'DECIFRIS'

>>> Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') + 3
Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'defghijklmnopqrstuvwxyzabc')
>>> 3 + Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')
Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'defghijklmnopqrstuvwxyzabc')
>>> Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') - 3
Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'xyzabcdefghijklmnopqrstuvw')
>>> 3 - Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')
Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'xyzabcdefghijklmnopqrstuvw')
>>> wheel = Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')
>>> wheel += 3
>>> wheel
Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'defghijklmnopqrstuvwxyzabc')