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.
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
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.
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$$:
herstel de begintoestand van de formula
pas de initiële rotatie $$r_i$$ ($$r_i \in \mathbb{Z}$$) toe
zoek elk symbool van de cijfertekst op de mobilis en vervang het door het corresponderende symbool op de stabilis
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.
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:
Een methode herstel waaraan geen argumenten moeten doorgegeven worden. De methode moet de begintoestand van codeerwiel $$w$$ herstellen en een verwijzing naar codeerwiel $$w$$ teruggeven.
Een methode roteer waaraan een rotatie $$r$$ moet doorgegeven worden. Als $$r$$ geen geheel getal (int) is, dan moet een AssertionError opgeworpen worden met de boodschap ongeldige rotatie en mag de toestand van codeerwiel $$w$$ niet wijzigen. Anders moet de methode codeerwiel $$w$$ roteren over $$r$$ posities en een verwijzing naar codeerwiel $$w$$ teruggeven. Als $$r > 0$$ roteert de mobilis $$r$$ symbolen in tegenwijzerzin. Als $$r < 0$$ roteert de mobilis $$|r|$$ symbolen in wijzerzin (waarbij $$|x|$$ staat voor de absolute waarde van $$x$$). Als $$r = 0$$ dan blijft de mobilis gewoon staan.
Een methode codeer_symbool waaraan een symbool $$s$$ (str; één enkel karakter) moet doorgegeven worden. Als symbool $$s$$ niet voorkomt op de stabilis van codeerwiel $$w$$, dan moet een AssertionError opgeworpen worden met de boodschap ongeldig symbool. Anders moet de methode het symbool op de mobilis (str) van codeerwiel $$w$$ teruggeven dat momenteel uitgelijnd staat met symbool $$s$$ op de stabilis.
Een methode decodeer_symbool waaraan een symbool $$s$$ (str; één enkel karakter) moet doorgegeven worden. Als symbool $$s$$ niet voorkomt op de mobilis van codeerwiel $$w$$, dan moet een AssertionError opgeworpen worden met de boodschap ongeldig symbool. Anders moet de methode het symbool op de stabilis (str) van codeerwiel $$w$$ teruggeven dat momenteel uitgelijnd staat met symbool $$s$$ op de mobilis.
Een methode codeer waaraan vier argumenten moeten doorgegeven worden: i) een klare tekst $$t$$ (str), ii) een initiële rotatie $$r_i \in \mathbb{Z}$$ (int), iii) een periodieke rotatie $$r_p \in \mathbb{Z}$$ (int), en iv) een periode $$p \in \mathbb{N}_0$$ (int). Als klare tekst $$t$$ symbolen bevat die niet op de stabilis van codeerwiel $$w$$ voorkomen, dan moet een AssertionError opgeworpen worden met de boodschap ongeldig symbool. Anders moet de cijfertekst (str) teruggegeven worden die men bekomt door klare tekst $$t$$ te coderen volgens het periodiek Alberticijfer met initiële rotatie $$r_i$$, periodieke rotatie $$r_p$$, en periode $$p$$.
Een methode decodeer waaraan vier argumenten moeten doorgegeven worden: i) een cijfertekst $$t$$ (str), ii) een initiële rotatie $$r_i \in \mathbb{Z}$$ (int), iii) een periodieke rotatie $$r_p \in \mathbb{Z}$$ (int), en iv) een periode $$p \in \mathbb{N}_0$$ (int). Als cijfertekst $$t$$ symbolen bevat die niet op de mobilis van codeerwiel $$w$$ voorkomen, dan moet een AssertionError opgeworpen worden met de boodschap ongeldig symbool. Anders moet de klare tekst (str) teruggegeven worden die men bekomt door cijfertekst $$t$$ te decoderen volgens het periodiek Alberticijfer met initiële rotatie $$r_i$$, periodieke rotatie $$r_p$$, en periode $$p$$.
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.
>>> 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')