Enigma Encryption

Enigma is a symmetric substitution cypher that uses a set of keys (a set of rotors, a set of plugboard settings and a reflector) in order to encrypt or decrypt a message. The Wizardry and Steamworks implementation deviates from the standard Enigma by having the rotors step each time during encryption and decryption.

In order to encrypt the message Good day!, you would call:

string encrypted = wasENIGMA(
    "Good day!", // message to encrypt
    [ "1", "3", "2", "8", "b" ], // selection of rotors
    [ "q", "r", "x", "a", "p" ], // selection of plugboard settings for the rotors
    "c" // the reflector
)

after which the variable encrypted will contain Bbyx qgo!

Decryption is symmetric, so in order to decrypt the message Bbyx qgo!, you would call:

string decrypted = wasENIGMA(
    "Bbyx qgo!", // message to decrypt
    [ "1", "3", "2", "8", "b" ], // selection of rotors
    [ "q", "r", "x", "a", "p" ], // selection of plugboard settings for the rotors
    "c" // the reflector
)

after which the variable decrypted will contain Good day!.

This implementation features the possible rotors: 1, 2, 3, 4, 5, 6, 7, 8, 9, b, g and the reflectors: B, b, C, c. Any number of rotors, in any order, can be selected provided that you have the same number of plugboard settings for each rotor. Only a single reflector from the list of possible reflectors can be picked.

ENIGMA.lsl
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
list wasListReverse(list lst) {
    if (llGetListLength(lst) <= 1) return lst;
    return wasListReverse(
        llList2List(
            lst, 
            1, 
            llGetListLength(lst)
        )
    ) + llList2List(lst, 0, 0);
}
 
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
///////////////////////////////////////////////////////////////////////////
list wasForwardPermuteListElements(list input, integer times) {
    if(times == 0) return input;
    return wasForwardPermuteListElements(
         llList2String(input, -1) + llList2List(input, 0, -2), 
        --times
    );
}
 
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
///////////////////////////////////////////////////////////////////////////
integer wasIsUpper(string a) {
    if(a == "") return FALSE;
    integer x = llBase64ToInteger("AAAA" + 
        llStringToBase64(llGetSubString(a, 0, 0)));
    return x >= 65 && x <= 90;
}
 
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
///////////////////////////////////////////////////////////////////////////
integer wasIsAlpha(string a) {
    if(a == "") return FALSE;
    integer x = llBase64ToInteger("AAAA" + 
        llStringToBase64(llGetSubString(a, 0, 0)));
    return (x >= 65 && x <= 90) || (x >= 97 && x <= 122);
}
 
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
///////////////////////////////////////////////////////////////////////////
list wasListPermuteToElement(list rot, string ring) {
    if(ring == llList2String(rot, 0)) return rot;
    integer i = llListFindList(rot, (list)ring);
    return ring + 
        llList2List(
            llDeleteSubList(
                rot, 
                i, 
                i
            ), 
            i, 
            -1
        )  + 
        llList2List(
            llDeleteSubList(
                rot, 
                i+1, 
                -1
            ), 
            0, 
            i-1
        );
}
 
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
///////////////////////////////////////////////////////////////////////////
// Symmetrically encrypts or decrypts a message using the Enigma cypher
// using the Wizardry and Steamworks variation of the Enigma machine.
//
// Parameters:
// message - a message to encrypt or decrypt.
// These two lists have to be of equal length:
//     rotors - a list of rotors which can contain any selection of: 1, 2, 
//              3, 4, 5, 6, 7, 8, b or g in any order.
//     plugs - the initial plugs settings, a list of letters of the 
//             alphabet for every selected rotor.
// reflector - a single letter reflector, can be either: B, b, C, c
//
// Example; llOwnerSay(
//              wasEngima(
//                  "Good day!", // message to encrypt or decrypt
//                  ["3", "g", "1"], // rotors 3, g and 1
//                  ["z", "p", "q"], // plugs z, p and q
//                  b // reflector b
//              )
//          );
string wasENIGMA(string message, list rotors, list plugs, string reflector) {
    // Ten rotors in total: 1 2 3 4 5 6 7 8 b g
    list def_rotors = [ 
        "1", 
            "e", "k", "m", "f", "l",
            "g", "d", "q", "v", "z",
            "n", "t", "o", "w", "y",
            "h", "x", "u", "s", "p",
            "a", "i", "b", "r", "c",
            "j",
        "2",
            "a", "j", "d", "k", "s",
            "i", "r", "u", "x", "b",
            "l", "h", "w", "t", "m",
            "c", "q", "g", "z", "n",
            "p", "y", "f", "v", "o",
            "e",
        "3",
            "b", "d", "f", "h", "j",
            "l", "c", "p", "r", "t",
            "x", "v", "z", "n", "y",
            "e", "i", "w", "g", "a",
            "k", "m", "u", "s", "q",
            "o",
        "4",
            "e", "s", "o", "v", "p",
            "z", "j", "a", "y", "q",
            "u", "i", "r", "h", "x",
            "l", "n", "f", "t", "g",
            "k", "d", "c", "m", "w",
            "b",
        "5",
            "v", "z", "b", "r", "g",
            "i", "t", "y", "u", "p",
            "s", "d", "n", "h", "l",
            "x", "a", "w", "m", "j",
            "q", "o", "f", "e", "c",
            "k",
        "6",
            "j", "p", "g", "v", "o",
            "u", "m", "f", "y", "q",
            "b", "e", "n", "h", "z",
            "r", "d", "k", "a", "s",
            "x", "l", "i", "c", "t",
            "w",
        "7",
            "n", "z", "j", "h", "g",
            "r", "c", "x", "m", "y",
            "s", "w", "b", "o", "u",
            "f", "a", "i", "v", "l",
            "p", "e", "k", "q", "d",
            "t",
        "8",
            "f", "k", "q", "h", "t",
            "l", "x", "o", "c", "b",
            "j", "s", "p", "d", "z",
            "r", "a", "m", "e", "w",
            "n", "i", "u", "y", "g",
            "v",
        "b",
            "l", "e", "y", "j", "v",
            "c", "n", "i", "x", "w",
            "p", "b", "q", "m", "d",
            "r", "t", "a", "k", "z",
            "g", "f", "u", "h", "o",
            "s",
        "g",
            "f", "s", "o", "k", "a",
            "n", "u", "e", "r", "h",
            "m", "b", "t", "i", "y",
            "c", "w", "l", "q", "p",
            "z", "x", "v", "g", "j",
            "d"
    ];
 
    // Four reflectors: B C b c
    list def_reflectors = [ 
        "B",
            "a", "y", "b", "r", "c", "u", "d", "h",
            "e", "q", "f", "s", "g", "l", "i", "p",
            "j", "x", "k", "n", "m", "o", "t", "z",
            "v", "w",
        "C",
            "a", "f", "b", "v", "c", "p", "d", "j",
            "e", "i", "g", "o", "h", "y", "k", "r",
            "l", "z", "m", "x", "n", "w", "t", "q",
            "s", "u",
        "b",
            "a", "e", "b", "n", "c", "k", "d", "q",
            "f", "u", "g", "y", "h", "w", "i", "j",
            "l", "o", "m", "p", "r", "x", "s", "z",
            "t", "v",
        "c",
            "a", "r", "b", "d", "c", "o", "e", "j",
            "f", "n", "g", "t", "h", "k", "i", "v",
            "l", "m", "p", "w", "q", "z", "s", "x",
            "u", "y"
    ];
 
 
    // Set-up rotors from plugs settings
    do {
        string rotor = llToLower(
            llList2String(
                rotors, 
                0
            )
        );
        // Determine which rotor is used.
        integer i = llListFindList(
            llList2ListStrided(
                def_rotors, 
                0, 
                -1, 
                27
            ), 
            (list)rotor
        );
        // And permute to the plugs setting.
        def_rotors = llListReplaceList(
            def_rotors, 
            wasListPermuteToElement(
                llList2List(
                    def_rotors, 
                    i*27+1, 
                    i*27+26
                ), 
                llToLower(
                    llList2String(
                        plugs, 
                        0
                    )
                )
            ), 
            i*27+1, 
            i*27+26
        );
        // We do not need rings anymore so discard the list
        plugs = llDeleteSubList(plugs, 0, 0);
        // We still need rotors so permute them
        rotors = llDeleteSubList(rotors, 0, 0);
        rotors += rotor;
    } while (llGetListLength(plugs) != 0);
 
    // Setjmp :-)
    string setjmp;
 
    // Now encrypt or decrypt.
    string result = "";
    do {
        // Get a character.
        string c = llGetSubString(message, 0, 0);
        // If it is not an alphabetic character, add it to the result and continue.
        if (!wasIsAlpha(c)) {
            result += c;
            jump input;
        }
        // Convert the character to lower case.
        string r = llToLower(c);
 
        // Forward pass through the Enigma
        setjmp = "forward";
        jump longjmp;
@forward;
 
        // Reflect
        integer j = llListFindList(llList2ListStrided(def_reflectors, 0, -1, 27), (list)reflector);
        list reflector = llList2List(def_reflectors, j*27+1, j*27+26);
        integer a = llListFindList(reflector, (list)r);
        if((a+1)%2 == 0) {
            r = llList2String(reflector, a - 1);
            jump reflect;
        }
        r = llList2String(reflector, a + 1);
 
@reflect;
 
        // Reverse the rotors.
        rotors = wasListReverse(rotors);
        // Reverse pass through the Enigma
        setjmp = "reverse";
        jump longjmp;
@reverse;
 
        if (wasIsUpper(c)) {
            r = llToUpper(r);
        }
        result += r;
 
@input;
        message = llDeleteSubString(message, 0, 0);
        jump continue;
 
@longjmp;
        // Rotor pass through the Enigma.
        integer i = llGetListLength(rotors) - 1;
        do {
            // Determine the first rotor.
            string p = llToLower(
                llList2String(
                    rotors, 
                    0
                )
            );
            integer n = llListFindList(
                llList2ListStrided(
                    def_rotors, 
                    0, 
                    -1, 
                    27
                ), 
                (list)p
            );
            list rotor = llList2List(
                def_rotors, 
                n*27+1, 
                n*27+26
            );
            // Rotate the rotor.
            def_rotors = llListReplaceList(
                def_rotors, 
                wasForwardPermuteListElements(
                    rotor, 
                    1
                ), 
                n*27+1, 
                n*27+26
            );
            if(i != 0) {
                // Get the index of the character.
                integer x = llListFindList(
                    rotor, 
                    (list)r
                );
                // Determine the second rotor.
                n = llListFindList(
                    llList2ListStrided(
                        def_rotors, 
                        0, 
                        -1, 
                        27
                    ), 
                    (list)llToLower(
                        llList2String(
                            rotors, 
                            1
                        )
                    )
                );
                // Set the index.
                r = llList2String(
                    llList2List(
                        def_rotors, 
                        n*27+1, 
                        n*27+26
                    ), 
                    x
                );
            }
            // Permute rotors
            rotors = llDeleteSubList(rotors, 0, 0);
            rotors += p;
            --i;
        } while (i > -1);
        // jump table
        if(setjmp == "forward") jump forward;
        if(setjmp == "reverse") jump reverse;
 
@continue;
    } while (llStringLength(message) != 0);
 
    return result;
 
}
 
default {
    state_entry() { 
        llOwnerSay(
            wasENIGMA(
                wasENIGMA(
                    "Good day, how are you?",
                    [ "1", "3", "b"  ], 
                    [ "q", "r", "z" ],
                    "c"
                ),
                [ "1", "3", "b"  ], 
                [ "q", "r", "z" ],
                "c"
            )
        );
    }
}

Although the implementation has been tested with over 1500 characters and could possibly take more, keep in mind that the Enigma was usually used to encrypt up to 100 characters. The shorter the string to encrypt, the less likely it is to be broken. Also note that llOwnerSay can only support up 1024 characters.

The Wizardry and Steamworks implementation tries to use constant memory: for each character to encrypt, the input character is replaced.

Benchmark

The following results show the time taken by the Wizardry and Steamworks implementation of the Enigma given three input rotors and varying the input from 1 to 512 characters:

        string input = "/* 512 characters (the repeated Latin alphabet) */";
 
        do {
            float t1 = llGetAndResetTime();
            wasENIGMA(
                input,
                [ "1", "3", "b"  ], 
                [ "q", "r", "z" ],
                "c"
            );
            float t2 = llGetAndResetTime();
            llOwnerSay((string)(t2-t1) + " " + (string)llStringLength(input));
            input = llDeleteSubString(input, 0, 0);
        } while(llStringLength(input) != 0); 

and plotted the results:

From the plot and the data, we have observed and calculated that: