basic_robot/scripts/math/enigma.lua

208 lines
6.3 KiB
Lua

--ENIGMA emulator by rnd
-- programming ~30 mins, total 3hrs 30 minutes with debugging - cause of youtube video with missing
-- key detail - reflector!
-- REFERENCES:
-- 1. https://en.wikipedia.org/wiki/Enigma_machine
-- 2. http://users.telenet.be/d.rijmenants/en/enigmatech.htm#reflector
-- 3. https://www.youtube.com/watch?src_vid=V4V2bpZlqx8&v=G2_Q9FoD-oQ
-- default settings
settings = {}
settings.set = function(reflector, plugboard, rotors)
settings.reflector = reflector or 2017
settings.plugboard = plugboard or 2017
settings.rotors = {}
if rotors then
table.insert(settings.rotors, rotors[1])
table.insert(settings.rotors, rotors[2])
table.insert(settings.rotors, rotors[3])
table.insert(settings.rotors, rotors[4])
else
table.insert(settings.rotors, 0)
table.insert(settings.rotors, 0)
table.insert(settings.rotors, 0)
table.insert(settings.rotors, 2018)
table.insert(settings.rotors, 2017)
table.insert(settings.rotors, 2020)
end
--if not enigma_encrypt then
if true then
scramble = function(input,password,sgn) -- permutes text randomly, nice after touch to stream cypher to prevent block analysis
_G.math.randomseed(password);
local n = #input;
local permute = {}
for i = 1, n do permute[i] = i end --input:sub(i, i)
for i = n,2,-1 do
local j = math.random(i-1);
local tmp = permute[j];
permute[j] = permute[i]; permute[i] = tmp;
end
local out = {};
if sgn>0 then -- unscramble
for i = 1,n do out[permute[i]] = string.sub(input,i,i) end
else -- scramble
for i = 1,n do out[i] = string.sub(input,permute[i],permute[i]) end
end
return table.concat(out,"")
end
local permutation = function(n,password,sgn) -- create random permutation of numbers 1,...,n
_G.math.randomseed(password);
local permute = {}
for i = 1, n do permute[i] = i end
for i = n,2,-1 do
local j = math.random(i-1);
local tmp = permute[j];
permute[j] = permute[i]; permute[i] = tmp;
end
return permute;
end
-- produces permutation U such that U^2 = 1; modified fisher-yates shuffle by rnd
local reflector = function(n,password)
_G.math.randomseed(password)
local permute = {}
local used = {};
for i = 1, n do permute[i] = i end
local rem = n;
for i = n,2,-1 do
if not used[i] then
local j = math.random(rem);
-- now we need to find j-th unused idx
local k = 1; local l = 0; -- k position, l how many we tried
while l < j do
if not used[k] then l=l+1; end
k=k+1
end
j=k-1;
used[i]=true; used[j] = true;
local tmp = permute[j];
permute[j] = permute[i]; permute[i] = tmp;
rem = rem - 2;
end
end
return permute;
end
local inverse_table = function(tbl)
local ret = {};
for i = 1,#tbl do
ret[ tbl[i] ] = i;
end
return ret;
end
local rotors = {}; -- { permutation, work index }}
local invrotors = {}; -- reversed tables
-- SETUP REFLECTOR, ROTORS AND PLUGBOARD!
local enigma_charcount = 127-32+1; -- n = 96
local enigma_charstart = 32;
local reflector = reflector(enigma_charcount,settings.reflector); -- this is permutation U such that U^2 = id --
local plugboard = permutation(enigma_charcount,settings.plugboard,1); -- setup plugboard for enigma machine
local invplugboard = inverse_table(plugboard);
for i = 1,3 do rotors[i] = {rotor = permutation(enigma_charcount,settings.rotors[3 + i],1), idx = 0} end -- set up 3 rotors together with their indices
for i = 1,3 do invrotors[i] = {rotor = inverse_table(rotors[i].rotor)} end -- set up 3 rotors together with their indices
-- how many possible setups:
--[[
n = charcount;
rotors positions: n^3
plugboard wiring : n!
reflector wiring: n! / (2^n * (n/2)!)
TOTAL: (n!)^2*n^3 / ( 2^n * (n/2)! ) ~ 6.4 * 10^77
rotor positions & plugboard wiring: (n!)*n^3 ~ 4.8 * 10^57 (n=43)
--]]
-- END OF SETUP
local rotate_rotor = function(i)
local carry = 1;
for j = i,1,-1 do
local idx = rotors[j].idx;
idx = idx + 1;
if idx>=enigma_charcount then
carry = 1;
else
carry = 0;
end
rotors[j].idx = idx % enigma_charcount;
if carry == 0 then break end
end
end
local enigma_encrypt_char = function(x) -- x : 1 .. enigma_charcount
-- E = P.R1.R2.R3.U.R3^-1.R2^-1.R1^-1.P^-1, P = plugboard, R = rotor, U = reflector
x = plugboard[x];
for i = 1,3 do
local idx = rotors[i].idx;
x = rotors[i].rotor[((x+idx-1) % enigma_charcount)+1];
end
x = reflector[x];
for i = 3,1,-1 do
local idx = rotors[i].idx;
x = invrotors[i].rotor[x];
x = ((x-1-idx) % enigma_charcount)+1
end
x = invplugboard[x];
-- apply rotation to rotor - and subsequent rotors if necessary
rotate_rotor(3)
return x;
end
--enigma_encrypt = function(input)
settings.encrypt = function(input)
-- rotor settings!
rotors[1].idx = settings.rotors[1]
rotors[2].idx = settings.rotors[2]
rotors[3].idx = settings.rotors[3]
local ret = "";
for i = 1,#input do
local c = string.byte(input,i) - enigma_charstart +1;
--say(i .. " : " .. c)
if c>=1 and c<=enigma_charcount then
c = enigma_encrypt_char(c);
end
ret = ret .. string.char(enigma_charstart+c-1);
end
return ret
end
settings.decrypt = settings.encrypt
end
end
msg = self.listen_msg()
if msg then
msg = minetest.strip_colors(msg)
local mark = string.find(msg,"@e") -- delimiter in chat
if mark then
msg = string.sub(msg,mark+2);
msg = minetest.colorize("yellow",enigma_encrypt(msg))
say(minetest.colorize("red","#decrypted : ") .. msg)
end
end
msg = self.sent_msg()
if msg then
local msg = enigma_encrypt(msg);
say("@e" .. msg,true)
-- minetest.show_formspec("encrypted_text", "size[4,4] textarea[0,-0.25;5,5;text;;".. "@e" .. minetest.formspec_escape(msg) .. "]")
end