In 1467 ontwikkelde de Italiaanse architect Leon Battista Alberti1 één van de allereerste polyalfabetische cijfers: het Alberticijfer. In de openingszinnen van zijn verhandeling De Cifris legt hij uit hoe zijn gesprek met de pauselijke secretaris Leonardo Dati2 over een recent ontwikkelde drukpers met beweegbare letters, leidde tot de ontwikkeling van zijn codeerwiel.

codeerwiel
Ontwerp van het originele codeerwiel voor het Alberticijfer.

Alberti's originele cijfer is één van de eerste voorbeelden van polyalfabetische substitutie met gemengde alfabetten en variabele perioden. Het codeerwiel — hij noemde het een formula — dat daarbij gebruikt wordt, bestaat uit twee concentrische schijven die door een gemeenschappelijk pin met elkaar verbonden worden, waardoor de ene schijf ten opzichte van de andere kan roteren. De grootste schijf wordt de stabilis (stationair of vast) genoemd, en de kleinere de mobilis (beweegbaar).

Bij het originele ontwerp van het codeerwiel werd de omtrek van elke schijf verdeeld in 24 gelijke cellen. De buitenste schijf (stabilis) bevat een alfabet met hoofdletters en cijfers voor de klare tekst. De binnenste schijf (mobilis) bevat een gemengd alfabet met kleine letters en leestekens voor de cijfertekst. Latere ontwerpen gebruikten andere alfabetten voor de stabilis en mobilis, maar beide alfabetten moeten wel steeds even lang zijn en alle symbolen op een schijf moeten steeds verschillend zijn.

We zullen een vereenvoudigd periodiek Alberticijfer gebruiken, waarbij de alfabetten voor de stabilis en de mobilis ook gemeenschappelijke symbolen mogen bevatten, wat niet het geval was in het originele ontwerp. Om uit te leggen hoe berichten vercijferd worden, zullen we gebruikmaken van een formula waarvan het alfabet op de stabilis in wijzerzin bestaat uit de 26 opeenvolgende hoofdletters en het alfabet op de mobilis in wijzerzin uit de 26 opeenvolgende kleine letters. Oorspronkelijk zijn alle kleine letters op de mobilis uitgelijnd ten opzichte van hun corresponderende hoofdletter op de stabilis: a is uitgelijnd met A, b is uitgelijnd met B, enzoverder.

stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  abcdefghijklmnopqrstuvwxyz

Coderen

Stel dat we deze formula willen gebruiken om de klare tekst DECIFRIS te coderen. Eerst herstellen we de begintoestand van de formula (als dat nog niet het geval zou zijn): de toestand waarbij in dit geval alle kleine letters op de mobilis uitgelijnd zijn ten opzichte van hun corresponderende hoofdletter op de stabilis.

Dan passen we een initiële rotatie $$r_i$$ ($$r_i \in \mathbb{Z}$$) toe: als $$r_i > 0$$ roteert de mobilis $$r_i$$ symbolen in tegenwijzerzin, als $$r_i < 0$$ roteert de mobilis $$|r_i|$$ symbolen in wijzerzin (waarbij $$|x|$$ staat voor de absolute waarde van $$x$$) en als $$r_i = 0$$ dan blijft de mobilis gewoon staan. Stel bijvoorbeeld dat $$r_i = 1$$, dan wordt de nieuwe toestand van de formula

stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  bcdefghijklmnopqrstuvwxyza

Nu zoeken we elk symbool van de klare tekst op de buitenste schijf (stabilis), en vervangen het door het corresponderende symbool (volgens de huidige uitlijning) op de binnenste schijf (mobilis). Zo wordt de letter D gecodeerd als de letter e, de letter E als de letter f, en de letter C als de letter d.

Het coderen heeft een periodieke rotatie $$r_p$$ ($$r_p \in \mathbb{Z}$$) met periode $$p$$ ($$p \in \mathbb{N}_0$$). Als we bijvoorbeeld een periodieke rotatie $$r_p = 2$$ en periode $$p = 3$$ gebruiken, dan roteren we na elke derde letter ($$p = 3$$) de mobilis over $$r_p$$ posities. Periodiek roteren gebeurt op dezelfde manier als de initiële rotatie: in dit geval twee ($$r_p = 2$$) posities in tegenwijzerzin.

stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  defghijklmnopqrstuvwxyzabc

Nu kunnen we de volgende drie letters IFR van de klare tekst coderen als liu, en voeren we na dezelfde periode $$p = 3$$ opnieuw een periodieke rotatie $$r_p = 2$$ uit.

stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  fghijklmnopqrstuvwxyzabcde

Daarna kunnen we ook de laatste twee letters IS van de klare tekst coderen als nx, en wordt de klare tekst DECIFRIS dus gecodeerd naar de cijfertekst efdliunx.

Decoderen

Om de cijfertekst efdliunx te decoderen, volgen we een analoge procedure met dezelfde initiële rotatie $$r_i$$, periodieke rotatie $$r_p$$ en periode $$p$$:

  1. herstel de begintoestand van de formula

  2. pas de initiële rotatie $$r_i$$ ($$r_i \in \mathbb{Z}$$) toe

  3. zoek elk symbool van de cijfertekst op de mobilis en vervang het door het corresponderende symbool op de stabilis

  4. na vervanging van elk $$p$$-de symbool, wordt de periodieke rotatie $$r_p$$ toegepast

In het periodiek Alberticijfer volgen coderen en decoderen dus dezelfde procedure, behalve dat in stap (3) de rol van de stabilis en de mobilis omgewisseld zijn.

Opgave

Definieer een klasse Formula waarmee we codeerwielen kunnen voorstellen zoals die gebruikt worden bij het periodiek Alberticijfer. Bij het aanmaken van een nieuwe codeerwiel (Formula) moeten twee alfabetten doorgegeven worden: i) een alfabet voor de stabilis (str) en ii) een alfabet voor de mobilis (str). Deze argumenten geven meteen ook aan hoe de twee alfabetten uitgelijnd worden in de begintoestand van het codeerwiel. Als de twee alfabetten een verschillende lengte hebben, of als er een alfabet is waarin hetzelfde symbool (karakter) meerdere keren voorkomt, dan moet een AssertionError opgeworpen worden met de boodschap ongeldig wiel.

Als er een codeerwiel $$w$$ (Formula) wordt doorgegeven aan de ingebouwde functie repr, dan moet die een beschrijving (str) teruggeven die leest als een Python expressie om een nieuw codeerwiel (Formula) aan te maken met de huidige toestand van codeerwiel $$w$$ als begintoestand.

Als er een codeerwiel $$w$$ (Formula) wordt doorgegeven aan de ingebouwde functie str, dan moet die een beschrijving (str) teruggeven overeenkomstig met de beschrijving van de huidige uitlijning van de stabilis en de mobilis van codeerwiel $$w$$ zoals we die in de inleiding van deze opgave gebruikt hebben.

Op een codeerwiel $$w$$ (Formula) moet je minstens de volgende methoden kunnen aanroepen:

Zorg ervoor dat de som (+) van een codeerwiel $$w$$ (Formula) en een geheel getal $$r$$ ($$r \in \mathbb{Z}$$, int) een nieuw codeerwiel (Formula) oplevert met als begintoestand de huidige toestand van codeerwiel $$w$$ geroteerd over $$r$$ posities. Daarbij mag de volgorde van de twee operandi ($$c + r$$ of $$r + c$$) geen verschil maken. De optelling mag het codeerwiel $$w$$ niet wijzigen.

Zorg ervoor dat het verschil (-) van een codeerwiel $$w$$ (Formula) en een geheel getal $$r$$ ($$r \in \mathbb{Z}$$, int) een nieuw codeerwiel (Formula) oplevert met als begintoestand de huidige toestand van codeerwiel $$w$$ geroteerd over $$-r$$ posities. Daarbij mag de volgorde van de twee operandi ($$w$$ - $$r$$ of $$r$$ - $$w$$) geen verschil maken. De aftrekking mag het codeerwiel $$w$$ niet wijzigen.

Zorg ervoor dat verkorte notatie $$w$$ += $$r$$ kan gebruikt worden om een codeersleutel $$w$$ (Formula) over $$r$$ ($$r \in \mathbb{Z}$$, int) posities te roteren.

Als deze bewerkingen (+, - en +=) een codeerwiel $$w$$ (Formula) combineren met een object $$r$$ dat geen geheel getal (int) is, dan moet een AssertionError opgeworpen worden met de boodschap ongeldige rotatie. In dat geval mag het codeerwiel $$w$$ ook niet wijzigen.

Voorbeeld

>>> wiel = Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')
>>> wiel
Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')
>>> print(wiel)
stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  abcdefghijklmnopqrstuvwxyz
>>> wiel.codeer_symbool('K')
'k'
>>> wiel.decodeer_symbool('x')
'X'
>>> wiel.roteer(2)
Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'cdefghijklmnopqrstuvwxyzab')
>>> print(wiel)
stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  cdefghijklmnopqrstuvwxyzab
>>> wiel.codeer_symbool('K')
'm'
>>> wiel.decodeer_symbool('x')
'V'
>>> wiel.roteer(11)
Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'nopqrstuvwxyzabcdefghijklm')
>>> print(wiel)
stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  nopqrstuvwxyzabcdefghijklm
>>> wiel.roteer(-5)
Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'ijklmnopqrstuvwxyzabcdefgh')
>>> print(wiel)
stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  ijklmnopqrstuvwxyzabcdefgh
>>> wiel.herstel()
Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')
>>> print(wiel)
stabilis: ABCDEFGHIJKLMNOPQRSTUVWXYZ
mobilis:  abcdefghijklmnopqrstuvwxyz
>>> wiel.codeer('DECIFRIS', 1, 2, 3)
'efdliunx'
>>> wiel.decodeer('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')
>>> wiel = Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')
>>> wiel += 3
>>> wiel
Formula('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'defghijklmnopqrstuvwxyzabc')