@ondoher/enigma
v1.0.14
Published
Enigma machine simulator with tools and documentation to help you build your own.
Downloads
1,467
Maintainers
Readme
Table of Contents
Getting Started
To install the toolkit, use the command:
npm install @ondoher/enigma
You can then import this into your code like this:
import {Enigma} from '@ondoher/enigma';
let enigma = new Enigma("I", {reflector: 'B'});In addition to this api, there is also documentation that gives a brief overview of what the Enigma is, and some technical details about its operation. Following that is a detailed breakdown of how to go about writing a simulation and all of the small details and quirks that will need to be accounted for. And there are many.
The API is broken into two main parts, the simulator and the message generator. With the simulator you can construct an entire Enigma, or just create instances of the individual components. This API is provided as a working reference that can be used to validate the simulator being built. You can match the input and output of your simulation against the reference implementation to locate failures to be corrected. The API also provides a way to hook into the various stages of encoding to observe the transformation of state and data as it occurs.
Simulation
Events
Each class below, except Inventory, has a method named listen. Call this
method to pass a function that will be called when significant events happen to
the class instance. When creating an instance of the Enigma, this callback
will be passed to every constructed component.
The signature of this callback should look like this:
function (event, name, data)
Parameters
- event - which event is being fired.
- name - the name of the component that has fired this event
- data - an object that contains information relevant to the event
Event Types
There are five different events, they are.
- input - fired when any component receives a signal
- output - fired when any component sends a signal
- translate - fired when a component outputs a signal, contains information about both the input and the output
- step - fired when a rotor steps
- double-step - fired when a rotor performs the double step.
Common
Every event contains these parameters,
name - contains the name of the component sending the event
type - the type of component sending the event. This is one of Entry Wheel, Plugboard, Reflector, Rotor and Enigma
description - a human readable string that details the event
direction - this is not sent for all events, but is for input, output, and translate, it is one of:
- right - this is the direction the translation starts until it hits the reflector
- left - this is the direction translation happens after going through the reflector.
- turn-around - sent by the reflector
- end-to-end - sent by the Enigma
input
This event is fired when any component receives a signal. In addition to the common fields, the data object contains these fields:
- input - this is the input value, it can be either a string or a number
output
This event is fired sent when any component sends a signal. In addition to the common fields, the data object contains these fields:
- output - this is the output value, it can be either a string or a number
translate
This event is fired when a component outputs a signal, contains information about both the input and the output. In addition to the common fields, the data object contains these fields:
- input - this is the input value, it can be either a string or a number
- output - this is the output value, it can be either a string or a number
step
This event is fired when a rotor steps. In addition to the common fields, the data object contains these fields:
- start - the staring position of the rotor
- stop - the ending position of the rotor
- turnover - true if the the stepping reached the turnover point
The specific events and data are defined in the class documentation that follows.
double-step
This event is fired when a rotor performs the double step. In addition to the common fields, the data object contains these fields:
- offset - the new position of the rotor
PlugBoard
Create an instance of this class to simulate the plug board component of an Enigma.
Methods
constructor(name, settings)
This is the constructor for the plugboard class. It takes these parameters:
Parameters
- name each encoder component in the system has a name. There is only one plug board in the Enigma, so the name here doesn't really matter
- settings
- alphabet (optional) set this to a string of letters that are an alternative to the standard A-Z. Defaults to A-Z
- map (optional) set this to a string that will set the mapping between the position in the string to the output connector. Defaults to A-Z
configure(settings)
Call this method to configure the plug board. This must be called even if there are no plug connections.
Parameters
- settings these are the settings to configure the plug board.
- plugs (optional) either an array of strings or a single string. If it is a string, it must be a space separated list of letter pairs that connects one input letter to another. If it is an array then then each item is a pair of letters to specify how the plugs are connected
encode(direction, input)
Call this method to encode a value in the given direction, right vs left.
Parameters
- direction
rightwhen moving towards the reflectorleftwhen moving back - input this is the input connector.
Returns
the output connector
Rotor
Create an instance of this class to construct a Rotor object. The Rotor class encapsulates many of the peculiar behaviors of the Enigma. All connector values here are specified in physical space. See the documentation for an explanation.
Methods
constructor(name, settings)
Parameters
- name the name of the rotor; under normal circumstances this will be the string 'rotor-' plus the standard name for the rotor, for example 'rotor-IV'
- settings an object that contains the various options that define the
the rotor and how it is configured.
- alphabet (optional) set this to a string of letters that are an alternative to the standard A-Z. Defaults to A-Z
- map a string that defines the mapping between the input and output connectors. The index into the string is the input connector and the value of this string at that index is the output connector. For example 'EKMFLGDQVZNTOWYHXUSPAIBRCJ', which is the map for standard rotor I
- ringSetting a number that specifies how far forward to offset the outer ring relative to the internal wiring.
- turnovers a string that specifies the relative location of where on the rotor turnover will occur. The value here is the rotation value would be displayed in the window when turnover happens, expressed as a character. The standard rotors VI-VIII, available in the later model M3 had two turnover locations, M and Z. Pass an empty string when the rotor does not rotate during stepping
setStartPosition(connector)
Call this method to set the starting rotation for encoding.
Parameters
- connector This is a letter value that corresponds to what would appear in the rotation window. This value will be adjusted for the ring setting.
encode(direction, input)
Call this method to map an input connector to an output connector when the signal is moving in the given direction. The input connector represents a physical location in real space. To get the logical connector for the rotor's zero point we need to adjust the connector number for the current rotation.
Parameters
- direction
rightwhen moving towards the reflectorleftwhen moving back - input the input connector given in physical space.
Returns
the output connector in physical space
step()
Call this method to step the rotor
Returns
true if the next rotor should be stepped
willTurnover()
Call this method to see if the next step on this rotor will lead to turn over.
The Enigma class will call this on the middle rotor to handle double stepping.
Returns
true if the next step will cause turnover
isFixed()
Call this method to find whether this is a fixed rotor.
Returns
True if this is a fixed rotor.
Events
encode-right, encode-left
These events will be fired during encoding. The info object will contain these members
- input the physical input connector
- output the physical output connector
- logicalInput the logical connector that receives input. This value is always relative to the internal wiring.
- logicalOutput the logical connector that gets the output.
- rotation current rotation offset
step
This event will be fired every time a rotor is stepped. The info object will contain these members
Parameters
- rotation the current rotation offset
- ringSetting the configured ringSetting for this rotor.
- turnover true if the next rotor should be stepped
Reflector
Create an instance of this class to construct a reflector class. Unlike most other encoders, the reflector only has a single set of connectors. Input and output happen on the same connectors, with pairs of them linked. Because of this, reflectors only encode in a single direction.
Methods
constructor(name, settings)
Parameters
- name the name of the reflector, under normal circumstances this be the string 'reflector-' plus the standard name for the reflector, for example 'reflector-C'
- settings an object that contains the various options that define the
the reflector and how it is setup.
- alphabet (optional) set this to a string of letters that are an alternative to the standard A-Z. Defaults to A-Z
- map a string that defines the mapping between the input and output connectors. The index into the string is the input connector and the value of this string at that index is the output connector. For example, 'YRUHQSLDPXNGOKMIEBFZCWVJAT' which is the map for standard reflector B.
encode(direction, input)
Call this method to encode a value when reversing the encoding direction of the Enigma. As the point where direction changes this does not have a distinction between a left and right signal path.
Parameters
- direction since this the point where signal direction changes from right to left this parameter is not used.
- input this is the input connector
Returns
the output connector
Enigma
Create an instance of this class to construct a full Enigma.
Methods
constructor(settings)
The constructor for the Enigma.
Parameters
- settings The settings here are for the nonconfigurable options of the
device.
- alphabet (optional) set this to a string of letters that are an alternative to the standard A-Z. Defaults to A-Z
- entryDisk (optional) the name of entry disc in the inventory this defaults to 'default'
- reflector for the Enigma I or M3 this specifies one of three possible standard reflectors from the inventory which are A, B, and C. For the M4, Thin-B and Thin-C have been defined.
configure(settings)
Call this method to configure the enigma instance for daily setup.
Parameters
- settings the configuration parameters for the device.
- plugs (optional) this specifies how the plug board should be configured. This is either a string with the plugs specified as pairs of letters separated by a single space, or an array of letter pairs.
- rotors this is an array of strings that specifies which rotors should be installed on the device and in which order. These rotors have been predefined: I-V for the model I, VI-VIII are added for the Model M3, and Beta and Gamma are the fixed rotors for the M4. The order here is significant and is given in the left to right direction. This means that last name in this list is the first rotor used in the forward direction and last used in the backward direction. Each element is the name of the rotor to use in the corresponding position. Stepping stops at the first fixed rotor.
- ringSettings (optional) This is either a string, or an array of numbers. The index of each letter in the alphabet, or the number in the array, is the ring setting for a rotor. Like the rotors, these are given from left to right.
step()
Call this method to step the Enigma. This will rotate the first rotor to the right and step and double step when necessary.
setStart(start)
Parameters
- start this is either a string or an array of numbers. The length of the string or the array should match the number of rotors and are given left to right. If start is a string then the letters of the string specify the start value seen in the window for the corresponding rotor. If it is an array then each number will be the one-based rotation value.
keyPress(letter)
Call this method to encode a single letter. This will step the Enigma before encoding the letter.
Parameters
- letter this method will force the letter parameter to uppercase. If it is anything except a member of the given alphabet it will return undefined. For any other character except a space it will also output a warning to the console.
Returns
undefined or the encoded character.
translate(start, text)
Call this method to encode a whole string.
Parameters
- start the start positions for the rotors. This parameter is the same as
what's passed to
setStart. - text This is the string to be encoded. Any characters in this string that are not part of the defined alphabet are ignored.
Returns
the encoded string. Passing the result of this method back through the encode method should produce the original text.
Properties
configuration
Use this property to get all the information necessary to reconstruct the
details on this Enigma. It is an object with these fields:
- rotors - this list of rotor names
- ringSettings - an array of ring settings for each rotor
- plugs - a space separated string of plug pairs
- reflector - the installed reflector
rotors
An array of the installed rotors
Example
import {Enigma} from '@ondoher/enigma';
let enigma = new Enigma("I", {reflector: 'B'});
enigma.configure({
rotors: ['III', 'VI', 'VIII'],
ringSettings: [1, 8, 13],
plugs: 'AN EZ HK IJ LR MQ OT PV SW UX'
});
let message = 'YKAENZAPMSCHZBFOCUVMRMDPYCOFHADZIZMEFXTHFLOLPZLFGGBOTGOXGRETDWTJIQHLMXVJWKZUASTR'
let decoded = enigma.translate('UZV', message)
console.log(decoded)
//STEUEREJTANAFJORDJANSTANDORTQUAAACCCVIERNEUNNEUNZWOFAHRTZWONULSMXXSCHARNHORSTHCOInventory
The inventory class is used to save named definitions of different components
that can be used by the Enigma. The module doesn't export the class, but instead
exports an instance of it named inventory. Components that have been added to
this inventory can be passed to the Enigma for configuration. By default the
following components are already defined:
- Rotors I, II, III, IV, V, VI, VII, VI, Beta, and Gamma. VI, VII, and VIII are used in the M3 and M4 and have two turnover points. The last two are fixed rotors used in the M4.
- Reflectors A, B, C, Thin-B, and Thin-C. Those last two are the thin reflectors used in the M4.
- EntryDisc the system only defines one entry disk, named default. It's just a simple pass through.
Methods
addRotor(name, map, turnovers)
Call this method to add a new rotor definition.
Parameters
- name the name of the rotor being added. This name will be used when specifying the rotors to use for the Enigma configuration.
- map a string specifying the connector mapping. The index of the string is the logical coordinate of the connector, the character at that index is the output connector. To be exact, it would be the position of that character in the given alphabet. So, in the map 'EKMFLGDQVZNTOWYHXUSPAIBRCJ', input connector 0 would map to output connector 4 and input connector 1 would map to output connector 10. Remember that the connectors are numbered starting at 0.
- turnovers this is a string of characters representing the turnover location on the disk. This letter would be the value shown in the window to the operator. In Rotor I this is 'Q' in rotors VI, VII, and VIII there are two turnover locations, 'M' and 'Z'. Pass an empty string if this is a fixed rotor
addReflector(name, map)
Call this method to add a new reflector definition.
Parameters
- name this is the name that will be used to reference this reflector when constructing an Enigma class.
- map this uses the same format used in the
addRotormethod
addEntryDisc(name, map)
Call this method to add a new entry disc. There was only one used in the standard military models, but there were other versions that defined it differently.
Parameters
- name this is the name that will be used to reference this entry disc when constructing an Enigma class.
- map this uses the same format used in the
addRotormethod
getRotor(name)
Call this method to get the setup for a defined rotor.
Parameters
- name the name of the rotor as it was added to the inventory.
Returns
an object with these fields
- map the connection map for the rotor
- turnovers the locations where turnovers happen
getReflector(name)
Call this method to get the setup for a defined reflector.
Parameters
- name the name of the reflector as it was added to the inventory.
Returns
an object with these fields
- map the connection map for the reflector
getEntryDisc(name)
Call this method to get the setup for a defined entry disc.
Parameters
- name the name of the entry disk as it was added to the inventory.
Returns
an object with these fields
- map the connection map for the entry disc
Generation
The toolkit has the ability to generate random data that comes in two forms. The first is the configuration of an Enigma and the generation of encrypted messages. The second is the generation and use of message code books, specifically the creation of key sheets and generation of messages using those key sheets. This is implemented across three classes.
- Random - Umplements a seedable pseudo-random number generator. The use of seeds allows running experiments on a predictable set of pseudo-random data
- Generator - Use this to create random configurations of an Enigma and generate messages for that configuration.
- CodeBook - Use this to generate random key sheets and generate messages using that key sheet
Random
A single instance of this class is the default export of the Random module. Use this to generate pseudorandom numbers and perform randomization operations on lists of items. This class uses a seedable pseudorandom number generator, this enables the predictable generation of set of operations to reproduce the same results.
Methods
randomize(seed)
Call this method to set the random seed for the randomizer. Setting the seed
to a known value will cause to randomizer to output the same sequence of random
values. On creation the seed is set to Date.now()
Parameters
- seed - the new seed number
random(limit)
Call this method to generate a new random number. If a limit is provided the output will be a random number between 0 and limit-1. If not, the result will be a decimal number between 0 and 1
Parameters
- limit (optional) - if provided the output will be an integer value less than this
Returns
The pseudo-random number
randomCurve(dice, faces, zeroBased)
Call this to generate a random number that is distributed along a bell curve. This is done by generating a set of random numbers each within a limit, and adding them together.
Parameters
- dice - the number of random numbers that should be chosen
- faces - the range of integer values
- zeroBased - if true the random numbers will start at 0, defaults to false
Result
The integer result
pickOne(list)
Call this method to pick a random element from the provided array. The item will be removed from this array. Use this method if you want to prevent the same item from being used more than once.
Parameters
- list - an array if items to choose from.
Returns
The array element
pickPair(list)
Call this method to pick two items from the provided array. The items will be removed from the array. If the array is less than two items then it will return either an empty array or an array with one element.
Parameters
- list - an array if items to choose from.
Returns
An array with the picked elements
pickPairs(count, list)
Call this method to pick a list of item pairs from a list of items. The items are removed from the list as chosen.
Parameters
- count - how many item pairs to pick
- list - an array if items to choose from.
Returns
An array of item pairs
pick(count, list)
Call this method to pick a specified number of items from the list. The items
will be removed. It will return at most list.length elements.
Parameters
- count - how many items to pick
- list - an array if items to choose from.
Returns
An array with the chosen elements
chooseOne(list)
Call this method to pick a random element from the provided array. The item will remain in the list.
Parameters
- list - an array of items to choose from.
Returns
The array element
choosePair(list)
Call this method to choose two items from the list, the elements will not be removed. The returned items are guaranteed be different. If the array is less than two elements it will be returned as the result
Parameters
- list - an array if items to choose from.
Returns
An array with the chosen elements
chooseRange(count, list)
Call this method to pick a contiguous list of items from the given list. The items will remain in the list.
Parameters
- count - how many items in the range
- list - an array if items to choose from.
Returns
An array with the chosen elements
choose(count, list)
Call this method to choose a specified number if items from the list. The items will not be removed. It may return the same item multiple times.
Parameters
- count - how many items to choose
- list - an array if items to choose from.
Returns
An array with the chosen elements
Generator
Use the generator class to generate random enigma machine configurations and use these to generate random messages.
Methods
cleanMessage(text)
Call this method to prepare the string for encoding. The string will be converted to uppercase and remove any characters not within A-Z
Parameters
- text - the text to clean
Returns
The cleaned up text
groupText(text, size)
Call this method to break the given text into groups of a given size, separated by spaces.
Parameters
- text - the text to group
- size (optional) - the size of the groups, defaults to 5
Returns
The grouped text as a string
generateSentences(count)
Call this method to generate an array of random sentences. These sentences will be a contiguous list of lines from Hamlet.
Parameters
- count - the number of sentences to generate
Returns
An array of sentences
getModelOptions(model)
Call this method to get the range of setup and configuration options for a specific Enigma model. The supported models are I, M3 and M4
Parameters
- model - the model of Enigma to use
Returns
An object with these fields
- reflectors - The names of the possible reflectors installed for this model.
- rotors - The names of the rotors available for this model
- fixed - the possible fixed rotors for this model
generateEnigmaConfiguration(setup)
Call this method to get a random configuration for an enigma.
Paramters
- setup - the options for configuration with these fields
- rotors (optional) - the list of rotors to choose from. Defaults to the list of unfixed rotors in the inventory
- fixed (optional) - if true, it defaults to the list of installed fixed rotors, if an array, uses this array as the list of fixed rotors to choose from. The default is an empty array.
Returns
An object with these fields>
- rotors - the rotors to install
- plugs - the plug board configuration as a string if space separated pairs
- ringSettings - an array of numbers for the ring setting for each rotor
createRandomEnigma(model, reflectors)
Call this method to create a new Enigma object, with a reflector chosen from the given list.
Parameters
- model (optional) - the model of the Enigma, defaults to the string "Enigma"
- reflectors (optional) - the possible reflectors, defaults to [A, B, C]
Returns
- a newly created
Enigmainstance
generateMessage(enigma)
Call this method to create and encrypt a random message using the given Enigma. The text of the message will be between 2 and 5 sentences from Hamlet.
Parameters
- enigma the
Enigmainstance to use
Returns
An object with these fields
- start - a string with the start positions for each rotor expressed as a letter
- decoded - the clear text version of the message
- encoded - the encoded string using the given
Enigmainstance
Example
function generateForModel(model, count, list) {
let {reflectors, rotors, fixed} = generator.getModelOptions(model);
let enigma = generator.createRandomEnigma(model, reflectors)
for (let idx = 0; idx < count; idx++) {
let configuration = generator.generateEnigmaConfiguration({rotors, fixed});
enigma.configure(configuration);
let message = generator.generateMessage(enigma);
list.push({model, ...message});
}
}
let messages = [];
generateForModel('I', 5, messages);
generateForModel('M3', 5, messages);
generateForModel('M4', 5, messages);CodeBook
Use this class to create key sheets and messages using those key sheets.
Key sheet
A key sheet specifies how to configure the enigma machine each day for a whole month. It consisted of one line per day, sorted from the last day of the month until the first. Each line had these columns:
- date (Datum) - the numerical day of the month
- rotor setup (Walzenlage) - the rotors to use
- ring settings (Ringstellung) - the ring setting for each rotor
- plugboard configuration (Steckerverbindungen) - how the ten plugs where connected to the plugboard
- indicators (Kenngruppen) - a set of four three digits codes that were an index into the specific day
Methods
constructor(enigma)
This is the constructor for the CodeBook. Each instance of this class works
with an instance of the Enigma class.
Parameters
- enigma - the
Enigmainstance to use.
configure(config)
Call this method to configure the Enigma used by this instance.
Parameters
- config - the simplified configuration returned by
Generator.generateEnigmaConfiguration. it has these fields.- rotors - the rotors to install
- plugs - the plug board configuration as a string if space separated pairs
- ringSettings - an array of numbers for the ring setting for each rotor
generateKeySheet(days)
Call this method to generate a monthly key sheet. This is the same data that would have been used by officers to setup the Enigma every day. The key sheet is created based on the enigma instance
Parameters
- days The number of days to generate data for
Returns
An array of objects, each one with these fields
- day the day of the month
- rotors an array of three rotor names
- ringSettings an array of offsets for the ring settings
- plugs 10 pairs of letters that will be used as connections on the plug board
- indicators and array of four three-letter strings. These strings will be unique across the key sheet
generateMessage(sheet, dayIdx, text)
Call this method to generate a message using the given key sheet. Each message has the same information that a message in the field would possess. The construction of the message follow the the standards of the German military beginning in 1940. They are as follows:
The Wehrmacht radio operator sets each day the rotors, ring settings and plugboard according to the key sheet. For each new message, he now selects new randomly chosen start position or Grundstellung, say WZA, and a random message key or Spruchschlüssel, say SXT. He moves the rotors to the random start position WZA and encrypts the random message key SXT...
He then sets message key SXT as start position of the rotors and encrypts the actual message. Next, he transmits the random start position WZA, the encrypted message key RSK and the Kenngruppe FDJKM together with his message.
The first five characters of each message was used to specify one of the four key identifiers from the key sheet that defines the Enigma configuration. The first two characters of this group were randomly chosen, and the last three were one of the key identifiers for that daily setup. This text was not encrypted.
Parameters
- sheet - the key sheet to use
- dayIdx (optional) - if specified the zero-based day to use, defaults to a random day
- text (optional) - if provided will be used as the text of the message. Defaults to between two and five contiguous lines from Hamlet
Returns
The configuration of the Enigma and an array of sub messages, which could be longer than one for large messages.
The returned object has these fields
- options- the configuration used to generate the message
- rotors - the installed rotors to install
- plugs - the plug board configuration as a string of space separated pairs
- ringSettings - an array of numbers for the ring setting for each rotor
- reflector- the installed reflector
- parts - an array of sub messages, each with these fields
- key - a randomly chosen key, this would be transmitted with the message
- enc - the encoded start position for the message. This was encoded using the randomly chosen key. This was sent with the message
- text - the message text encoded using the unencoded start position. The first five letters of this text string included the unencrypted key identifier.
- start - the unencoded start position. This was not sent with the message but is included here to verify an implementation of this method.
- clear - the unencrypted message. This can be used to verify an implementation of this method.
generateMessages(sheet, count)
Call this method to create an array of messages based off a key sheet. This
method calls generateMessage count times.
Parameters
- sheet - a key sheet as generated from
generateKeySheet - count - the number of messages to create
Returns
An array of messages as returned from generateKeySheet
Example Message
{
"options": {
"reflector": "A",
"rotors": [
"IV",
"III",
"II"
],
"ringOffsets": [
25,
11,
18
],
"plugs": "AV XQ YK TD HE OB ZW FP IU CM"
},
"parts": [
{
"key": "VAH",
"enc": "WFL",
"start": "ZKY",
"text": "PYVJF SVGQI FUNUE RVYRN BPTJL TGGPW CAWXU NBAZS BTNUV XVEPE QOQGP AKMJM ILBYA MKMXD NVJMO HBVJB HBRZX QSPQX DFIBG JXOHN KQXTI OJBUP JWBCF UOMGJ XUJPP XBJEM LVKMA LZSKO VSOEC NIJFV TRLAO JLVOO TMQDU TYSWL HIAPE YYAQD QKANA IHVSG JMJIC MZOSP POWJI IZJMF VKARE YINLU SYBZY XKWAC UIHVO MKCFH BEPUG LAXWP ",
"clear": "HEMADECONFESSIONOFYOUANDGAVEYOUSUCHAMASTERLYREPORTFORARTANDEXERCISEINYOURDEFENCEANDFORYOURRAPIERMOSTESPECIALLYTHATHECRIEDOUTTWOULDBEASIGHTINDEEDIFONECOULDMATCHYOUTHESCRIMERSOFTHEIRNATIONHESWOREHADHADNEITHERMOTIONGUARDNOREYEIFYOUOPPOSEDTHEMSIRTHI"
},
{
"key": "TBR",
"enc": "AZP",
"start": "CBC",
"text": "RRRNG VYBRV RPVBF KFHAJ TPUHW WGZJU BWXGH LGNKW RYZYP DGBYC SUTEX KJSUX UDWER YJHDB HDSZH PARUG EPDXE YXBDX TBCKD JKYDY VLZVV ACYVD MTLEC BSQEP ACKVV YKJAZ SHQQT GMBQB T",
"clear": "REPORTOFHISDIDHAMLETSOENVENOMWITHHISENVYTHATHECOULDNOTHINGDOBUTWISHANDBEGYOURSUDDENCOMINGOERTOPLAYWITHHIMNOWOUTOFTHISWHATOUTOFTHISMYLORD"
}
]
}