diff --git a/.gitignore b/.gitignore index 5da3876..641e6ac 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ data/oeis.txt other/ec-*.png other/ec-*.txt other/list-onions.txt -out/ \ No newline at end of file +tmp/ \ No newline at end of file diff --git a/FailedAttempts.py b/LP/FailedAttempts.py similarity index 100% rename from FailedAttempts.py rename to LP/FailedAttempts.py diff --git a/HeuristicLib.py b/LP/HeuristicLib.py similarity index 63% rename from HeuristicLib.py rename to LP/HeuristicLib.py index 63bb898..2b37440 100755 --- a/HeuristicLib.py +++ b/LP/HeuristicLib.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 -import re -from RuneText import RuneText from NGrams import NGrams +from RuneText import RUNES def normalized_probability(int_prob): @@ -9,8 +8,6 @@ def normalized_probability(int_prob): return [x / total for x in int_prob] # math.log(x / total, 10) -RUNES = 'ᚠᚢᚦᚩᚱᚳᚷᚹᚻᚾᛁᛄᛇᛈᛉᛋᛏᛒᛖᛗᛚᛝᛟᛞᚪᚫᚣᛡᛠ' -re_norune = re.compile('[^' + RUNES + ']') PROB_INT = [0] * 29 for k, v in NGrams.load(1, '').items(): # '-no-e', '-solved' PROB_INT[RUNES.index(k)] = v @@ -53,24 +50,3 @@ class Probability(object): val = sum(abs(Probability(nums[x::keylen]).IC() - target_ioc) for x in range(keylen)) return 1 - (val / keylen) - - -######################################### -# load page and convert to indices for faster access -######################################### - -def load_indices(fname, interrupt, maxinterrupt=None, minlen=None, limit=None): - with open(fname, 'r') as f: - data = RuneText(re_norune.sub('', f.read())).index[:limit] - if maxinterrupt is not None: - # incl. everything up to but not including next interrupt - # e.g., maxinterrupt = 0 will return text until first interrupt - for i, x in enumerate(data): - if x != interrupt: - continue - if maxinterrupt == 0: - if minlen and i < minlen: - continue - return data[:i] - maxinterrupt -= 1 - return data diff --git a/HeuristicSearch.py b/LP/HeuristicSearch.py similarity index 98% rename from HeuristicSearch.py rename to LP/HeuristicSearch.py index b6f0f48..2eec534 100755 --- a/HeuristicSearch.py +++ b/LP/HeuristicSearch.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import itertools # product, compress, combinations import bisect # bisect_left, insort -import lib as LIB +from lib import affine_decrypt ######################################### @@ -45,7 +45,7 @@ class GuessAffine(object): best = 9999999 for s in range(29): for t in range(29): - shifted = [LIB.affine_decrypt(x, (s, t)) + shifted = [affine_decrypt(x, (s, t)) for x in self.nums[offset::keylength]] score = score_fn(shifted) if score < best: diff --git a/InterruptDB.py b/LP/InterruptDB.py similarity index 87% rename from InterruptDB.py rename to LP/InterruptDB.py index ad8b6a3..49fafce 100755 --- a/InterruptDB.py +++ b/LP/InterruptDB.py @@ -1,15 +1,9 @@ #!/usr/bin/env python3 import os from HeuristicSearch import SearchInterrupt -from HeuristicLib import load_indices, Probability - -RUNES = 'ᚠᚢᚦᚩᚱᚳᚷᚹᚻᚾᛁᛄᛇᛈᛉᛋᛏᛒᛖᛗᛚᛝᛟᛞᚪᚫᚣᛡᛠ' # used in InterruptToWeb -FILES_SOLVED = ['0_warning', '0_welcome', '0_wisdom', '0_koan_1', - '0_loss_of_divinity', 'jpg107-167', 'jpg229', - 'p56_an_end', 'p57_parable'] -FILES_UNSOLVED = ['p0-2', 'p3-7', 'p8-14', 'p15-22', 'p23-26', - 'p27-32', 'p33-39', 'p40-53', 'p54-55'] -FILES_ALL = FILES_UNSOLVED + FILES_SOLVED +from HeuristicLib import Probability +from RuneText import RUNES, load_indices +from LPath import FILES_ALL, FILES_UNSOLVED, LPath ######################################### @@ -85,10 +79,10 @@ class InterruptDB(object): @staticmethod def load(dbname): - if not os.path.isfile(f'InterruptDB/{dbname}.txt'): + if not os.path.isfile(LPath.InterruptDB(dbname)): return {} ret = {} - with open(f'InterruptDB/{dbname}.txt', 'r') as f: + with open(LPath.InterruptDB(dbname), 'r') as f: for line in f.readlines(): if line.startswith('#'): continue @@ -104,7 +98,7 @@ class InterruptDB(object): @staticmethod def write(name, score, irp, irpmax, keylen, nums, dbname='db_main'): - with open(f'InterruptDB/{dbname}.txt', 'a') as f: + with open(LPath.InterruptDB(dbname), 'a') as f: nums = ','.join(map(str, nums)) f.write(f'{name}|{irpmax}|{score:.5f}|{irp}|{keylen}|{nums}\n') @@ -134,7 +128,7 @@ class InterruptIndices(object): @staticmethod def write(dbname='db_indices'): - with open(f'InterruptDB/{dbname}.txt', 'w') as f: + with open(LPath.InterruptDB(dbname), 'w') as f: f.write('# file | total runes in file | interrupt | indices\n') for name in FILES_ALL: fname = f'pages/{name}.txt' @@ -149,7 +143,7 @@ class InterruptIndices(object): @staticmethod def read(dbname='db_indices'): - with open(f'InterruptDB/{dbname}.txt', 'r') as f: + with open(LPath.InterruptDB(dbname), 'r') as f: ret = {} for line in f.readlines(): if line.startswith('#'): @@ -169,8 +163,9 @@ class InterruptIndices(object): ######################################### class InterruptToWeb(object): - def __init__(self, dbname, template='InterruptDB/template.html'): - self.template = template + def __init__(self, dbname, template='template.html'): + with open(LPath.results(template), 'r') as f: + self.template = f.read() self.indices = InterruptIndices() self.scores = {} db = InterruptDB.load(dbname) @@ -252,8 +247,6 @@ class InterruptToWeb(object): return f'{trh}{"".join(trd[1:])}{trbest}
' def make(self, outfile, pmin=1.25, pmax=1.65): - with open(self.template, 'r') as f: - html = f.read() nav = '' txt = '' for i in range(29): @@ -263,10 +256,10 @@ class InterruptToWeb(object): nav += f'{RUNES[i]}\n' txt += f'

Interrupt {i}: {RUNES[i]}

' txt += self.table_interrupt(i, pmin, pmax) - html = html.replace('__NAVIGATION__', nav) + html = self.template.replace('__NAVIGATION__', nav) html = html.replace('__TAB_RELIABLE__', self.table_reliable()) html = html.replace('__INTERRUPT_TABLES__', txt) - with open(outfile, 'w') as f: + with open(LPath.results(outfile), 'w') as f: f.write(html) @@ -279,12 +272,10 @@ def create_initial_db(dbname, minkl=1, maxkl=32, max_irp=20, irpset=range(29)): oldValues = {k: set((a, b, c) for a, _, b, c, _ in v) for k, v in oldDB.items()} for irp in irpset: # interrupt rune index - # for name in FILES_UNSOLVED: for name in FILES_ALL: - fname = f'pages/{name}.txt' - data = load_indices(fname, irp, maxinterrupt=max_irp) + data = load_indices(LPath.page(name), irp, maxinterrupt=max_irp) db = InterruptDB(data, irp) - print('load:', fname, 'interrupt:', irp, 'count:', db.irp_count) + print('load:', name, 'interrupt:', irp, 'count:', db.irp_count) for keylen in range(minkl, maxkl + 1): # key length if (db.irp_count, irp, keylen) in oldValues.get(name, []): print(f'{keylen}: skipped.') @@ -305,9 +296,8 @@ def find_secondary_solutions(db_in, db_out, threshold=0.75, max_irp=20): search_set.add((name, irp, kl)) print('searching through', len(search_set), 'files.') for name, irp, kl in search_set: - fname = f'pages/{name}.txt' - print('load:', fname, 'interrupt:', irp, 'keylen:', kl) - data = load_indices(fname, irp, maxinterrupt=max_irp) + print('load:', name, 'interrupt:', irp, 'keylen:', kl) + data = load_indices(LPath.page(name), irp, maxinterrupt=max_irp) db = InterruptDB(data, irp) c = db.make_secondary(db_out, name, kl, threshold) print('found', c, 'additional solutions') @@ -317,6 +307,5 @@ if __name__ == '__main__': # find_secondary_solutions('db_high', 'db_high_secondary', threshold=1.4) # find_secondary_solutions('db_norm', 'db_norm_secondary', threshold=0.55) # create_initial_db('db_norm', minkl=1, maxkl=32, max_irp=20) - # InterruptToWeb('db_high').make('InterruptDB/index_high.html') - InterruptToWeb('db_norm').make( - 'InterruptDB/index_norm.html', pmin=0.40, pmax=0.98) + # InterruptToWeb('db_high').make('index_high.html') + InterruptToWeb('db_norm').make('index_norm.html', pmin=0.40, pmax=0.98) diff --git a/LP/LPath.py b/LP/LPath.py new file mode 100755 index 0000000..20f66ad --- /dev/null +++ b/LP/LPath.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import os.path + +FILES_SOLVED = ['0_warning', '0_welcome', '0_wisdom', '0_koan_1', + '0_loss_of_divinity', 'jpg107-167', 'jpg229', + 'p56_an_end', 'p57_parable'] +FILES_UNSOLVED = ['p0-2', 'p3-7', 'p8-14', 'p15-22', 'p23-26', + 'p27-32', 'p33-39', 'p40-53', 'p54-55'] +FILES_ALL = FILES_UNSOLVED + FILES_SOLVED + +LP_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) +LP_ROOT_DIR = os.path.relpath(os.path.dirname(LP_MODULE_DIR)) + + +class LPath(object): + @staticmethod + def root(fname): + return os.path.join(LP_ROOT_DIR, fname) + + @staticmethod + def page(fname): + return os.path.join(LP_ROOT_DIR, 'pages', fname + '.txt') + + @staticmethod + def data(fname, ext='txt'): + return os.path.join(LP_ROOT_DIR, 'data', f'{fname}.{ext}') + + @staticmethod + def tmp(fname, ext='txt'): + return os.path.join(LP_ROOT_DIR, 'tmp', f'{fname}.{ext}') + + @staticmethod + def InterruptDB(fname): + return os.path.join(LP_ROOT_DIR, 'InterruptDB', fname + '.txt') + + @staticmethod + def results(fname): + return os.path.join(LP_ROOT_DIR, 'InterruptDB', fname) diff --git a/NGrams.py b/LP/NGrams.py similarity index 60% rename from NGrams.py rename to LP/NGrams.py index a20677f..0a2dd81 100755 --- a/NGrams.py +++ b/LP/NGrams.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import re -from RuneText import alphabet, RuneText +from RuneText import RUNES, re_norune, RuneText +from LPath import LPath ######################################### @@ -28,11 +29,10 @@ class NGrams(object): @staticmethod def make(gramsize, infile, outfile): - allowed_chr = [x[1] for x in alphabet] with open(infile, 'r') as f: - data = re.sub('[^{}]'.format(''.join(allowed_chr)), '', f.read()) + data = re_norune.sub('', f.read()) - res = {x: 0 for x in allowed_chr} if gramsize == 1 else {} + res = {x: 0 for x in RUNES} if gramsize == 1 else {} for i in range(len(data) - gramsize + 1): ngram = data[i:i + gramsize] try: @@ -47,19 +47,28 @@ class NGrams(object): @staticmethod def load(ngram=1, prefix=''): ret = {} - with open(f'data/p{prefix}-{ngram}gram.txt', 'r') as f: + with open(LPath.data(f'p{prefix}-{ngram}gram'), 'r') as f: for line in f.readlines(): r, v = line.split() ret[r] = int(v) return ret -# NGrams.translate('data/baseline-text.txt', 'data/baseline-rune.txt', False) -# for i in range(1, 6): -# print(f'generate {i}-gram file') -# NGrams.make(i, infile='data/baseline-rune-words.txt', -# outfile=f'data/p-{i}gram.txt') -# NGrams.make(i, infile='_solved.txt', -# outfile=f'data/p-solved-{i}gram.txt') -# NGrams.make(i, infile='data/baseline-rune-no-e.txt', -# outfile=f'data/p-no-e-{i}gram.txt') +def make_translation(stream=False): # if true, ignore spaces / word bounds + NGrams.translate(LPath.data('baseline-text'), + LPath.data('baseline-rune'), stream) + + +def make_ngrams(max_ngram=1): + for i in range(1, max_ngram + 1): + print(f'generate {i}-gram file') + NGrams.make(i, infile=LPath.data('baseline-rune-words'), + outfile=LPath.data(f'p-{i}gram')) + NGrams.make(i, infile=LPath.root('_solved.txt'), + outfile=LPath.data(f'p-solved-{i}gram')) + NGrams.make(i, infile=LPath.data('baseline-rune-no-e'), + outfile=LPath.data(f'p-no-e-{i}gram')) + + +# make_translation(stream=False) +# make_ngrams(5) diff --git a/RuneRunner.py b/LP/RuneRunner.py similarity index 97% rename from RuneRunner.py rename to LP/RuneRunner.py index c004c71..8a08e89 100755 --- a/RuneRunner.py +++ b/LP/RuneRunner.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -from RuneText import RuneText -import lib as LIB import sys +from RuneText import RuneText +import lib as utils ######################################### @@ -86,11 +86,11 @@ class RuneReader(object): self.loaded_file = None self.words = {x: [] for x in range(20)} # increase for longer words - def load(self, data=None, file=None): + def load(self, data=None, file=None, limit=None): self.loaded_file = None if not data: with open(file, 'r') as f: - data = f.read() + data = f.read()[:limit] self.loaded_file = file self.data = data if isinstance(data, RuneText) else RuneText(data) self.generate_word_list() @@ -219,7 +219,7 @@ class RuneRunner(object): if self.output.VERBOSE: self.output.write(n1=('' if is_first else '+') + str(num)) return - prm = LIB.is_prime(num) + prm = utils.is_prime(num) if typ == 'w': # word tt = ('' if is_first else ' + ') + str(num) + ('*' if prm else '') self.output.write(n2=tt) @@ -229,7 +229,7 @@ class RuneRunner(object): self.output.mark = False # if not is_first: sffx = ' = {}'.format(num) + ('*' if prm else '') - if LIB.is_prime(LIB.rev(num)): + if utils.is_emirp(num): sffx += '√' if self.output.VERBOSE: self.output.write(n2=sffx) diff --git a/RuneSolver.py b/LP/RuneSolver.py similarity index 98% rename from RuneSolver.py rename to LP/RuneSolver.py index f2b6385..e0602d4 100755 --- a/RuneSolver.py +++ b/LP/RuneSolver.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from RuneRunner import RuneRunner from RuneText import Rune, RuneText -import lib as LIB +from lib import affine_decrypt ######################################### @@ -186,7 +186,7 @@ class VigenereSolver(RunningKeySolver): class AffineSolver(RunningKeySolver): def decrypt(self, rune_index, key_index): - return LIB.affine_decrypt(rune_index, self.KEY_DATA[key_index]) + return affine_decrypt(rune_index, self.KEY_DATA[key_index]) ######################################### diff --git a/RuneText.py b/LP/RuneText.py similarity index 83% rename from RuneText.py rename to LP/RuneText.py index b16f5b5..98b8fbf 100755 --- a/RuneText.py +++ b/LP/RuneText.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +import re # load_indices + white_rune = {'•': ' ', '⁘': '.', '⁚': ',', '⁖': ';', '⁜': '#'} white_text = {v: k for k, v in white_rune.items()} alphabet = [ # Using last value for display. Custom added: V @@ -14,8 +17,9 @@ alphabet = [ # Using last value for display. Custom added: V ] text_map = {t: r for _, r, ta in alphabet for t in ta} rune_map = {r: t for _, r, ta in alphabet for t in ta} -index_map = [r for _, r, _ in alphabet] # array already sorted primes_map = {r: p for p, r, _ in alphabet} +RUNES = [r for _, r, _ in alphabet] # array already sorted +re_norune = re.compile('[^' + ''.join(RUNES) + ']') # del alphabet # used in playground for GP display @@ -37,7 +41,7 @@ class Rune(object): @property def rune(self): if self._rune is None: - self._rune = index_map[self._index] if self._index < 29 else '•' + self._rune = RUNES[self._index] if self._index < 29 else '•' return self._rune @property @@ -54,7 +58,7 @@ class Rune(object): def index(self): if self._index is None: r = self._rune - self._index = index_map.index(r) if r in index_map else 29 + self._index = RUNES.index(r) if r in RUNES else 29 return self._index @property @@ -182,37 +186,18 @@ class RuneText(object): res.append(Rune(r=rune, t=char)) return res - def as_dict(self): - ret = {'r': '', 't': '', 'i': [], 'p': []} - for x in self._data: - ret['r'] += x.rune - ret['t'] += x.text - ret['i'].append(x.index) - ret['p'].append(x.prime) - return ret - def description(self, count=False, index=True, indexWhitespace=False): - if len(self) == 0: - return None - fmt = '{0} ({1}) – {2} ({3})' if count else '{0} – {2}' - d = self.as_dict() - if index: - fmt += ' – {4}' - if not indexWhitespace: - d['i'] = [x for x in d['i'] if x != 29] - return fmt.format(d['r'], len(d['r']), d['t'], len(d['t']), d['i']) + return None if len(self) == 0 else \ + self.rune + (f' ({len(self)})' if count else '') + ' - ' + \ + self.text + (f' ({len(self.text)})' if count else '') + \ + (f' - {self.index if indexWhitespace else self.index_rune_only}' + if index else '') def zip_sub(self, other): if len(self) != len(other): raise IndexError('RuneText length mismatch') return RuneText([x - y for x, y in zip(self._data, other._data)]) - @property - def rune_sum(self): - if self._rune_sum is None: - self._rune_sum = sum(x.prime for x in self._data) - return self._rune_sum - @property def text(self): return ''.join(x.text for x in self._data) @@ -226,9 +211,19 @@ class RuneText(object): return [x.index for x in self._data if x.kind != 'l'] @property - def index_no_whitespace(self): + def index_rune_only(self): return [x.index for x in self._data if x.index != 29] + @property + def prime(self): + return [x.prime for x in self._data] + + @property + def prime_sum(self): + if self._rune_sum is None: + self._rune_sum = sum(self.prime) + return self._rune_sum + def __getitem__(self, key): if isinstance(key, str): return [getattr(x, key) for x in self._data] @@ -255,3 +250,24 @@ class RuneText(object): def __repr__(self): return f'RuneText<{len(self._data)}>' + + +######################################### +# load page and convert to indices for faster access +######################################### + +def load_indices(fname, interrupt, maxinterrupt=None, minlen=None, limit=None): + with open(fname, 'r') as f: + data = RuneText(re_norune.sub('', f.read())).index_rune_only[:limit] + if maxinterrupt is not None: + # incl. everything up to but not including next interrupt + # e.g., maxinterrupt = 0 will return text until first interrupt + for i, x in enumerate(data): + if x != interrupt: + continue + if maxinterrupt == 0: + if minlen and i < minlen: + continue + return data[:i] + maxinterrupt -= 1 + return data diff --git a/LP/__init__.py b/LP/__init__.py new file mode 100644 index 0000000..e1c85bc --- /dev/null +++ b/LP/__init__.py @@ -0,0 +1,17 @@ +import sys +if True: + sys.path.append(__path__[0]) + +import lib as utils +from LPath import FILES_ALL, FILES_UNSOLVED, FILES_SOLVED +from LPath import LPath as path +from RuneSolver import VigenereSolver, AffineSolver, AutokeySolver, SequenceSolver +from RuneText import Rune, RuneText +from RuneText import RUNES, alphabet, load_indices + +from HeuristicSearch import GuessVigenere, GuessAffine, GuessPattern +from HeuristicSearch import SearchInterrupt +from HeuristicLib import Probability +from InterruptDB import InterruptDB + +from FailedAttempts import NGramShifter diff --git a/lib.py b/LP/lib.py similarity index 96% rename from lib.py rename to LP/lib.py index 2131cea..3522074 100755 --- a/lib.py +++ b/LP/lib.py @@ -1,11 +1,7 @@ #!/usr/bin/env python3 import math -AFFINE_INV = None - -# yes it will report 2,3,5 as non-prime -# though why add a check if it will never be tested anyway def is_prime(num): if isinstance(num, str): num = int(num) @@ -30,6 +26,10 @@ def rev(num): # or int(str(num)[::-1]) return revs +def is_emirp(num): + return is_prime(rev(num)) + + def power(x, y, p): res = 1 x %= p @@ -74,6 +74,9 @@ def elliptic_curve(x, a, b, r): return y, -y % r +AFFINE_INV = None + + def affine_inverse(s, n=29): def fn(s, n): g = [n, s] diff --git a/out/0_welcome.8.txt b/out/0_welcome.8.txt deleted file mode 100644 index 63181eb..0000000 --- a/out/0_welcome.8.txt +++ /dev/null @@ -1,17 +0,0 @@ -8, 1.9043, [10, 16, 10, 9, 10, 16, 26, 23], [1, 4, 5, 6, 7, 11, 12, 14, 19, 20, 21, 23, 25] -LEOJGTHFE# -WELMOME,PILGEIM,TO THE GEEAT JOURNOY TOWARD TE END OF ALC THNGS. -IT IS DOT AN EASY URIP,BUT FOE THOSE WHO FAND THEIR WNY HERE IT AS A NECESFARY ONE. -ACONG THE WAY JOU WILL FIDD AN END TB ALL STRULGLE AND SSMFMOS,TFUR ANNOCENCO,YOUR ILLSSIANS,YOUE CERTAINUY,AND YOUE REALITY. -UCTIMATELJ,YOU WILL HISCOUER NN END TO SOLF. -IT IS THRBUGH THIS PALGRIMAGO THAT WE SHNPE OURSECUES AND OSR REALITIOS. -JOURNEJ DEEP WITHAN AND YOU NGILL ARRISE OUTSIDO. -LICE THE IDSTAR,IT IF ONLY THROSGH GONG WITIN THAT WE RAY EMERGO# -WIDSOM# -YBU ARE A BEG UNTO YOUESELP. -TFU AEE A LAW UNUO YOURSECF. -EACH INTECLIGENCE AS HOLY. -FOR NLL THAT LISES IS HOLJ# -# -AN INSTRSCTIAN# -COFIEHA OPIAL DUH TAUF# diff --git a/out/jpg107-167.13.txt b/out/jpg107-167.13.txt deleted file mode 100644 index 88b4772..0000000 --- a/out/jpg107-167.13.txt +++ /dev/null @@ -1,8 +0,0 @@ -13, 2.1477, [21, 10, 4, 0, 1, 19, 0, 18, 4, 18, 9, 0, 18], [2, 3] -O COAN# -DURNG A LESDON,THE MASTER EXNGLAINED THE I. -"THE I ES THE UOICE OF THE CERCUMFERENCE,"HE DAID. -WHEN ASCED AEY A STUDENT TO EOEPLAIN WHAT THAT IAEANT;THE MASTER SOID"IT IS A UOICE ENSIDE YOUR HEAD". -"E DON'T HAUE A UOIPE IN MY HEAD," THOUGTT THE STUDENT,ANTH HE RAISED HIS HOND TO TELL THE MADTER. -THE MASTER SAOPPED THE STUDEBT,AND SAID"THE UOECE THAT JUST SAITH YOU HAUE NO UOIPE IN YOUR HEAD;IS IE I. -"AND THE STUDEBTS WERE ENLIGHAENED# diff --git a/playground.py b/playground.py index 2e50b44..de27176 100755 --- a/playground.py +++ b/playground.py @@ -1,12 +1,8 @@ #!/usr/bin/env python3 -from RuneSolver import VigenereSolver, AffineSolver, AutokeySolver -from RuneText import alphabet, RuneText -import lib as LIB +import LP -SOLVER = VigenereSolver() -# SOLVER = AffineSolver() -# SOLVER = AutokeySolver() -SOLVER.input.load(file='_input.txt') +SOLVER = LP.VigenereSolver() # VigenereSolver, AffineSolver, AutokeySolver +SOLVER.input.load(file=LP.path.root('_input.txt')) def main(): @@ -82,7 +78,7 @@ def command_a(cmd, args): # [a]ll variations return False if not args: raise Warning('No input provided.') - root = RuneText(args) + root = LP.RuneText(args) inclIndex = 'q' not in cmd if 'i' in cmd: root = ~root @@ -101,10 +97,10 @@ def command_d(cmd, args): # [d]ecrypt or single substitution trythis = input('What is the encrypted text?: ').strip() else: trythis = args - enc = RuneText(trythis) + enc = LP.RuneText(trythis) print('encrypted:', enc.description()) target = input('What should the decrypted clear text be?: ').strip() - plain = RuneText(target) + plain = LP.RuneText(target) print('plaintext:', plain.description()) if len(enc) != len(plain): print('Error: key length mismatch') @@ -126,7 +122,7 @@ def command_f(cmd, args): # (f)ind word search_term = None s_len = int(args.strip()) # search words with n-length except ValueError: - search_term = RuneText(args) + search_term = LP.RuneText(args) s_len = len(search_term) cur_words = SOLVER.highlight_words_with_len(s_len) @@ -165,14 +161,14 @@ def command_g(cmd, args): # (g)ematria primus return False def pp(i): - p, r, t = alphabet[28 - i if rev else i] + p, r, t = LP.alphabet[28 - i if rev else i] return '{:2d} {} {:3d} {}'.format(i, r, p, '/'.join(t)) rev = cmd[-1] in 'ir' or len(args) > 0 and args[0] in 'ir' print('Gematria Primus (reversed)' if rev else 'Gematria Primus') - half = (len(alphabet) >> 1) + (len(alphabet) & 1) # ceil + half = (len(LP.alphabet) >> 1) + (len(LP.alphabet) & 1) # ceil for i in range(half): - if i < len(alphabet) // 2: + if i < len(LP.alphabet) // 2: print('{:22} {}'.format(pp(i), pp(i + half))) else: print(pp(i)) @@ -206,8 +202,8 @@ def command_h(cmd, args): # (h)ighlight def command_k(cmd, args): # (k)ey manipulation if cmd == 'k' or cmd == 'key': - SOLVER.KEY_DATA = [x.index for x in RuneText(args)] - print('set key: {}'.format(SOLVER.KEY_DATA)) + SOLVER.KEY_DATA = LP.RuneText(args).index + print(f'set key: {SOLVER.KEY_DATA}') elif cmd[1] == 's': SOLVER.KEY_SHIFT = get_cmd_int(cmd, args, 'shift') elif cmd[1] == 'r': @@ -218,12 +214,12 @@ def command_k(cmd, args): # (k)ey manipulation SOLVER.KEY_POST_PAD = get_cmd_int(cmd, args, 'post padding') elif cmd[1] == 'i': SOLVER.KEY_INVERT = not SOLVER.KEY_INVERT - print('set key invert: {}'.format(SOLVER.KEY_INVERT)) + print(f'set key invert: {SOLVER.KEY_INVERT}') elif cmd == 'kj': args = args.strip('[]') pos = [int(x) for x in args.split(',')] if args else [] SOLVER.INTERRUPT_POS = pos - print('set interrupt jumps: {}'.format(pos)) + print(f'set interrupt jumps: {SOLVER.INTERRUPT_POS}') else: return False # command not found SOLVER.run() @@ -254,8 +250,8 @@ def command_p(cmd, args): # (p)rime test if args and cmd not in 'prime': return False p = get_cmd_int(cmd, args, start=1) - print(p, ':', LIB.is_prime(p)) - print(LIB.rev(p), ':', LIB.is_prime(LIB.rev(p))) + print(p, ':', LP.utils.is_prime(p)) + print(LP.utils.rev(p), ':', LP.utils.is_emirp(p)) ######################################### @@ -265,17 +261,14 @@ def command_p(cmd, args): # (p)rime test def command_t(cmd, args): # (t)ranslate if cmd != 't': return False - word = RuneText(args) - x = word.as_dict() - psum = sum(x['p']) - sffx = '*' if LIB.is_prime(psum) else '' - if LIB.is_prime(LIB.rev(psum)): - sffx += '√' - print('runes({}): {}'.format(len(x['r']), x['r'])) - print('plain({}): {}'.format(len(x['t']), x['t'])) - print('reversed: {}'.format(''.join([x.rune for x in ~word]))) - print('indices: {}'.format(x['i'])) - print('prime({}{}): {}'.format(psum, sffx, x['p'])) + word = LP.RuneText(args) + sffx = ''.join(['*' if LP.utils.is_prime(word.prime_sum) else '', + '√' if LP.utils.is_emirp(word.prime_sum) else '']) + print('runes({}): {}'.format(len(word), word.rune)) + print('plain({}): {}'.format(len(word.text), word.text)) + print('reversed: {}'.format((~word).rune)) + print('indices: {}'.format(word.index)) + print('prime({}{}): {}'.format(word.prime_sum, sffx, word.prime)) ######################################### @@ -286,7 +279,7 @@ def command_x(cmd, args): # e(x)ecute decryption if cmd == 'x': pass # just run the solver elif cmd == 'xf': # reload from file - file = f'pages/{args}.txt' if args else '_input.txt' + file = LP.path.page(args) if args else LP.path.root('_input.txt') print('loading file:', file) SOLVER.input.load(file=file) args = None # so run() won't override data diff --git a/probability.py b/probability.py index c3ab0f6..b765d79 100755 --- a/probability.py +++ b/probability.py @@ -1,18 +1,11 @@ #!/usr/bin/env python3 # -*- coding: UTF-8 -*- -from RuneSolver import VigenereSolver, AffineSolver -from HeuristicSearch import GuessVigenere, GuessAffine, GuessPattern -from HeuristicSearch import SearchInterrupt -from HeuristicLib import load_indices, Probability -from InterruptDB import InterruptDB -from RuneText import RuneText -# from FailedAttempts import NGramShifter +import LP -RUNES = 'ᚠᚢᚦᚩᚱᚳᚷᚹᚻᚾᛁᛄᛇᛈᛉᛋᛏᛒᛖᛗᛚᛝᛟᛞᚪᚫᚣᛡᛠ' INVERT = False KEY_MAX_SCORE = 0.05 AFF_MAX_SCORE = 0.04 -IRP_F_ONLY = False +IRP_F_ONLY = True session_files = [] @@ -22,9 +15,9 @@ session_files = [] def break_cipher(fname, candidates, solver, key_fn): def fn_similarity(x): - return Probability(x).similarity() + return LP.Probability(x).similarity() - filename = f'pages/{fname}.txt' + filename = LP.path.page(fname) slvr = solver() slvr.input.load(file=filename) slvr.output.QUIET = True @@ -36,12 +29,12 @@ def break_cipher(fname, candidates, solver, key_fn): for irp_count, score, irp, kl, skips in candidates: if IRP_F_ONLY and irp != 0: continue - data = load_indices(filename, irp, maxinterrupt=irp_count) + data = LP.load_indices(filename, irp, maxinterrupt=irp_count) if INVERT: data = [28 - x for x in data] - iguess = SearchInterrupt(data, (28 - irp) if INVERT else irp) - print('score: {}, interrupt: {}, count: {}, solver: {}'.format( - score, RUNES[irp], len(iguess.stops), key_fn.__name__)) + iguess = LP.SearchInterrupt(data, (28 - irp) if INVERT else irp) + print('IoC: {}, interrupt: {}, count: {}, solver: {}'.format( + score, LP.RUNES[irp], len(iguess.stops), key_fn.__name__)) testcase = iguess.join(iguess.from_occurrence_index(skips)) key_score, key = key_fn(testcase).guess(kl, fn_similarity) @@ -56,25 +49,25 @@ def break_cipher(fname, candidates, solver, key_fn): while txtname in session_files: txtname += '.' session_files.append(txtname) - outfile = f'out/{txtname}.txt' + outfile = LP.path.tmp(txtname) with open(outfile, 'w') as f: f.write( f'{irp}, {kl}, {score:.4f}, {key_score:.4f}, {key}, {skips}\n') slvr.output.file_output = outfile - slvr.INTERRUPT = RUNES[irp] + slvr.INTERRUPT = LP.RUNES[irp] slvr.INTERRUPT_POS = skips slvr.KEY_DATA = key slvr.run() def pattern_solver(fname, irp=0): - with open(f'pages/{fname}.txt', 'r') as f: - orig = RuneText(f.read()) - # orig = RuneText('ᛄᚹᚻᛗᛋᚪ-ᛋᛁᚫᛇ-ᛋᛠᚾᛞ-ᛇᛞ-ᛞᚾᚣᚹᛗ.ᛞᛈ-ᛝᛚᚳᚾᛗᚾᚣ-ᛖᛝᛖᚦᚣᚢ-ᚱᚻᛁᚠ-ᛟᛝ-ᛚᛖᚫᛋᛚᚳᛋᛇ.ᚣᚾᚻᛄᚾᚳᛡ-ᚷᚳᛝ-ᛈᛝ-ᛡᚷᚦᚷᛖ.ᚻᛠᚣ-ᛄᛞᚹᛒ-ᛇᛄᛝᚩᛟ.ᛗᛠᚣᛋᛖᛚᚠ-ᚾᚫᛁ-ᛄᚹᚻᚻᛚᛈᚹᚠ-ᚫᚩᛡᚫᛟ-ᚷᛠ-ᚪᛡᚠᛄᚱᛏᚢᛈ.ᛏᛈ-ᛇᛞ-ᛟᛗᛇᛒᛄᚳᛈ.ᛉᛟ-ᛒᚻᚱᛄᚣ-ᚾᚱ-ᚾᛡᛈᛈ-ᛚᛉᛗᛞ-ᛟᛝ-ᚷᛁᚱᚩᚹᛗ-ᚠᛇᚣ-ᚣᛝᛒ-ᛁ-ᚠᚾᚹᚢ-ᛠᚾᛈᚠᚻ.ᚫᛋᛄᚪᚻ-ᛒᛖᛋᚻᛠ-ᛄᛗ-ᛟᛡᚹᚪᛡ-ᛄᛋᛖᚢᛗ-ᛏᛖᛉᚪ-ᛞᛟᛉᚾᚠ-ᚱᛡᛒᛚᚩᛈᛝ-ᛋᛄᛚᛗ-ᛞᚱᛗᛗ-ᛒᛈ-ᛁᛉᚱᛄᛝ.ᛋᛇᚪ-ᛗᚠᚻᚣᚹᛉᛞ-ᛡᛁᚷᚪ-ᚩᚱ-ᚪᚾᚹᛇᛋᛞᛄᚷ-ᛡ-ᛖᚫᛄ-ᛞᛟᛁᚻᚹᛝ-ᛠᛈᛏ-ᚪᛗᛗᛚᛚᚪᛞ.ᛁᛠᛈᚷᛞ-ᛗᚣᛄᚳᚹᛚ-ᚻᛋᛟᛗ-ᚣᚫᛝᛚ-ᛠᛁᛝᛝᚪ-ᚳᛗ-ᚢᚫᛋ-ᛉᛠᚱ-ᛇᛡᛄᚻᛗᚾ-ᚻᛗᛝᛚ-ᛇᛞ-ᛟᚢᚣᚪᚷᚱ-ᛡᚷ-ᚷᛠ-ᛚᚻᛒ.ᛡᛒ-ᚩᛁᛄ-ᛗᛟᛉᚩᚣ-ᛞᚩ-ᚳᛗ-ᚾᛗᚩ-ᚷᛠ-ᛚᚱᚠᚷ-ᛁᚫᛗᛉ-ᛁᛠᚹᛚ-ᛖᛝᚾᛟᛗᚾ-ᛄᚾ-ᚾᚳᛚᛝ-ᛡ-ᚷᛞᛗᚱᚻᚩ-ᛗᛞᛠᚫᛞ-ᛞᚱᛗᛗ-ᚣᚪ-ᛗᛉᚢᛞᛇᚹ-ᛟᚱᛏᚱᛟᚢᛉᛗᛚᛈᛉᛝ.ᛏᛖ-ᛗᛋᚣ-ᚹᛁ-ᚹᛝ-ᛋᛇᛄᚳᛁᛋᛝ.ᛄᛚᚹ-ᚷᚠᛝ-ᚫᚷᛚᛡᛁᛡ.ᛖᚠᚣ-ᛉᛝᚻᛄᚾᛈᚠ-ᛉᚣ-ᛚᛄᛞᛝᛞᚪ-ᚩᛈ-ᚻᛟ-ᛖᚻᚱᚹ-ᛚᚷᚳ-ᛒᛈᛏᚻ-ᚠᛋᛠᚣᛋᚠ-ᛏᚷᛈᚪᛒ.') - # orig = RuneText('ᛇᚦ-ᛒᛏᚣᚳ-ᛇᛚᛉ-ᛄᛚᚦᚪ-ᛋᚱᛉᚦ-ᚦᛄᚻ-ᛉᛗ-ᛏᛞᛋ-ᚣᚾ-ᚣᛟᛇᛈᛟᛚ-ᛈᚹᛚᚪᛗ-ᚪᛉᛁᛇᛝᚢᚱᛉ.ᛞᛄ-ᚻᛠᚪᛚ.ᚠᛚ-ᚩᛋᚾ-ᚫᛞᛋᛁᛞᚹᚾᚪᚪ-ᚱᛟᚻ-ᛚᚠᛚᚳᛟᚱ-ᚣᛏ-ᚹᛏᛝᚣ-ᚳᚩ-ᛄᚷᛟ-ᛖ-ᚫᚻᚦᛠ-ᛒᛠᛁ-ᛁᚩᛡ-ᛗᛉᚠᚷᛁ-ᚣᚣᛋᛇᛗᛠᚹ.ᛇᚪ-ᛇᛉᛡ-ᛄᚾᛇᛁᛇᚫ-ᛋᚱᚹ-ᛝᚣᚦ-ᛠᛁᛄᛚᚢᛄ-ᚻᛇᛚᛟ-ᛒᛠᛒᛚ-ᚩᛈᛈ-ᚢᚻᛚ-ᛡᚾᛚ-ᛒᚦᚱᚠᚦᚫ-ᛞᚳ-ᛄᚳᚷ-ᚹᚫ-ᚱᛉᚣᛖᚱ.ᛒᛝᚹ-ᛟᚳᚫᚹᛈᚢ-ᚱᛋᛒ-ᚷᚦᚳᛏᛏᛠᚹ-ᚱᚣᛞ-ᚣᛠᛄ-ᛋ-qᚪᛚᚾᛖᛄᚪ-ᛇᚻᛖ-ᛏᛠᛈ-ᛝᛉᚾᚳ-ᛋᚾᚹᚦᚾ-ᚣᛞᛝᚣ-ᛠᛠᛡ-ᛉᛁᛚᚢᚩ.ᛗᛉᚦ-ᛒᛝᛇᛠᛟ-ᛁᛟᛏ-ᛠᛏᛄ-ᚫᚳᛉᛝᛖᚠ-ᛇᚠ.ᛄᛄᛝᛟᛡᛟ-ᛠᛖᚫ-ᚦᛏᛠᛗ-ᛁᛏᚩᛒᛡ-ᛝᛟ-ᛉᚠᛇᚷᛗᛠ-ᚠᛖ-ᚳᛖᛖᚾᛠᛁᚪᛟ-ᛉᚣ-ᚢᛁ.ᛒᛏ.ᛒᛠ-ᛠᛁᚢᛗ-ᛞᛟᛋᛠᚷᚠᛇᚫ-ᛏᚪ-ᛇᚦ-ᛒᚪᛟᚩᛗ.ᛟᚳᛇ-ᛞᛞ-ᛋᚱᛁᛋᚦ-ᛇᛒ-ᚳᛒᛟ-ᚳᛟᚳᚷᛇ.ᛗᛉᚦ-ᛞᚦᛉᛈᛚᛈᛚᛁᚢ-ᚳᛞᛡᛝᚻᚷ-ᛞᚪ-ᚳᛟᚳᛁᛟᛞ-') + with open(LP.path.page(fname), 'r') as f: + orig = LP.RuneText(f.read()) + # orig = LP.RuneText('ᛄᚹᚻᛗᛋᚪ-ᛋᛁᚫᛇ-ᛋᛠᚾᛞ-ᛇᛞ-ᛞᚾᚣᚹᛗ.ᛞᛈ-ᛝᛚᚳᚾᛗᚾᚣ-ᛖᛝᛖᚦᚣᚢ-ᚱᚻᛁᚠ-ᛟᛝ-ᛚᛖᚫᛋᛚᚳᛋᛇ.ᚣᚾᚻᛄᚾᚳᛡ-ᚷᚳᛝ-ᛈᛝ-ᛡᚷᚦᚷᛖ.ᚻᛠᚣ-ᛄᛞᚹᛒ-ᛇᛄᛝᚩᛟ.ᛗᛠᚣᛋᛖᛚᚠ-ᚾᚫᛁ-ᛄᚹᚻᚻᛚᛈᚹᚠ-ᚫᚩᛡᚫᛟ-ᚷᛠ-ᚪᛡᚠᛄᚱᛏᚢᛈ.ᛏᛈ-ᛇᛞ-ᛟᛗᛇᛒᛄᚳᛈ.ᛉᛟ-ᛒᚻᚱᛄᚣ-ᚾᚱ-ᚾᛡᛈᛈ-ᛚᛉᛗᛞ-ᛟᛝ-ᚷᛁᚱᚩᚹᛗ-ᚠᛇᚣ-ᚣᛝᛒ-ᛁ-ᚠᚾᚹᚢ-ᛠᚾᛈᚠᚻ.ᚫᛋᛄᚪᚻ-ᛒᛖᛋᚻᛠ-ᛄᛗ-ᛟᛡᚹᚪᛡ-ᛄᛋᛖᚢᛗ-ᛏᛖᛉᚪ-ᛞᛟᛉᚾᚠ-ᚱᛡᛒᛚᚩᛈᛝ-ᛋᛄᛚᛗ-ᛞᚱᛗᛗ-ᛒᛈ-ᛁᛉᚱᛄᛝ.ᛋᛇᚪ-ᛗᚠᚻᚣᚹᛉᛞ-ᛡᛁᚷᚪ-ᚩᚱ-ᚪᚾᚹᛇᛋᛞᛄᚷ-ᛡ-ᛖᚫᛄ-ᛞᛟᛁᚻᚹᛝ-ᛠᛈᛏ-ᚪᛗᛗᛚᛚᚪᛞ.ᛁᛠᛈᚷᛞ-ᛗᚣᛄᚳᚹᛚ-ᚻᛋᛟᛗ-ᚣᚫᛝᛚ-ᛠᛁᛝᛝᚪ-ᚳᛗ-ᚢᚫᛋ-ᛉᛠᚱ-ᛇᛡᛄᚻᛗᚾ-ᚻᛗᛝᛚ-ᛇᛞ-ᛟᚢᚣᚪᚷᚱ-ᛡᚷ-ᚷᛠ-ᛚᚻᛒ.ᛡᛒ-ᚩᛁᛄ-ᛗᛟᛉᚩᚣ-ᛞᚩ-ᚳᛗ-ᚾᛗᚩ-ᚷᛠ-ᛚᚱᚠᚷ-ᛁᚫᛗᛉ-ᛁᛠᚹᛚ-ᛖᛝᚾᛟᛗᚾ-ᛄᚾ-ᚾᚳᛚᛝ-ᛡ-ᚷᛞᛗᚱᚻᚩ-ᛗᛞᛠᚫᛞ-ᛞᚱᛗᛗ-ᚣᚪ-ᛗᛉᚢᛞᛇᚹ-ᛟᚱᛏᚱᛟᚢᛉᛗᛚᛈᛉᛝ.ᛏᛖ-ᛗᛋᚣ-ᚹᛁ-ᚹᛝ-ᛋᛇᛄᚳᛁᛋᛝ.ᛄᛚᚹ-ᚷᚠᛝ-ᚫᚷᛚᛡᛁᛡ.ᛖᚠᚣ-ᛉᛝᚻᛄᚾᛈᚠ-ᛉᚣ-ᛚᛄᛞᛝᛞᚪ-ᚩᛈ-ᚻᛟ-ᛖᚻᚱᚹ-ᛚᚷᚳ-ᛒᛈᛏᚻ-ᚠᛋᛠᚣᛋᚠ-ᛏᚷᛈᚪᛒ.') + # orig = LP.RuneText('ᛇᚦ-ᛒᛏᚣᚳ-ᛇᛚᛉ-ᛄᛚᚦᚪ-ᛋᚱᛉᚦ-ᚦᛄᚻ-ᛉᛗ-ᛏᛞᛋ-ᚣᚾ-ᚣᛟᛇᛈᛟᛚ-ᛈᚹᛚᚪᛗ-ᚪᛉᛁᛇᛝᚢᚱᛉ.ᛞᛄ-ᚻᛠᚪᛚ.ᚠᛚ-ᚩᛋᚾ-ᚫᛞᛋᛁᛞᚹᚾᚪᚪ-ᚱᛟᚻ-ᛚᚠᛚᚳᛟᚱ-ᚣᛏ-ᚹᛏᛝᚣ-ᚳᚩ-ᛄᚷᛟ-ᛖ-ᚫᚻᚦᛠ-ᛒᛠᛁ-ᛁᚩᛡ-ᛗᛉᚠᚷᛁ-ᚣᚣᛋᛇᛗᛠᚹ.ᛇᚪ-ᛇᛉᛡ-ᛄᚾᛇᛁᛇᚫ-ᛋᚱᚹ-ᛝᚣᚦ-ᛠᛁᛄᛚᚢᛄ-ᚻᛇᛚᛟ-ᛒᛠᛒᛚ-ᚩᛈᛈ-ᚢᚻᛚ-ᛡᚾᛚ-ᛒᚦᚱᚠᚦᚫ-ᛞᚳ-ᛄᚳᚷ-ᚹᚫ-ᚱᛉᚣᛖᚱ.ᛒᛝᚹ-ᛟᚳᚫᚹᛈᚢ-ᚱᛋᛒ-ᚷᚦᚳᛏᛏᛠᚹ-ᚱᚣᛞ-ᚣᛠᛄ-ᛋ-qᚪᛚᚾᛖᛄᚪ-ᛇᚻᛖ-ᛏᛠᛈ-ᛝᛉᚾᚳ-ᛋᚾᚹᚦᚾ-ᚣᛞᛝᚣ-ᛠᛠᛡ-ᛉᛁᛚᚢᚩ.ᛗᛉᚦ-ᛒᛝᛇᛠᛟ-ᛁᛟᛏ-ᛠᛏᛄ-ᚫᚳᛉᛝᛖᚠ-ᛇᚠ.ᛄᛄᛝᛟᛡᛟ-ᛠᛖᚫ-ᚦᛏᛠᛗ-ᛁᛏᚩᛒᛡ-ᛝᛟ-ᛉᚠᛇᚷᛗᛠ-ᚠᛖ-ᚳᛖᛖᚾᛠᛁᚪᛟ-ᛉᚣ-ᚢᛁ.ᛒᛏ.ᛒᛠ-ᛠᛁᚢᛗ-ᛞᛟᛋᛠᚷᚠᛇᚫ-ᛏᚪ-ᛇᚦ-ᛒᚪᛟᚩᛗ.ᛟᚳᛇ-ᛞᛞ-ᛋᚱᛁᛋᚦ-ᛇᛒ-ᚳᛒᛟ-ᚳᛟᚳᚷᛇ.ᛗᛉᚦ-ᛞᚦᛉᛈᛚᛈᛚᛁᚢ-ᚳᛞᛡᛝᚻᚷ-ᛞᚪ-ᚳᛟᚳᛁᛟᛞ-') data = orig.index if False: # longest uninterrupted text - pos, lg = InterruptDB.longest_no_interrupt(data, interrupt=0, irpmax=0) + pos, lg = LP.InterruptDB.longest_no_interrupt(data, interrupt=0, irpmax=0) data = data[pos:pos + lg] else: # from the beginning data = data[:170] @@ -83,7 +76,7 @@ def pattern_solver(fname, irp=0): data = [x for x in data if x != 29] def fn_similarity(x): - return Probability(x).similarity() + return LP.Probability(x).similarity() def fn_pattern_mirror(x, kl): for i in range(10000): # mirrored, 012210012210 or 012101210 @@ -92,7 +85,7 @@ def pattern_solver(fname, irp=0): yield from x[::-1][1:-1] print(fname) - gr = GuessPattern(data) + gr = LP.GuessPattern(data) for kl in range(3, 19): # for pattern_shift in range(1): # fn_pattern = fn_pattern_mirror @@ -106,29 +99,29 @@ def pattern_solver(fname, irp=0): # Find proper pattern res = [] for offset in range(kl): # up to keylen offset - mask = GuessPattern.pattern(kl, fn_pattern) + mask = LP.GuessPattern.pattern(kl, fn_pattern) parts = gr.split(kl, mask, offset) - score = sum(Probability(x).IC() for x in parts) / kl + score = sum(LP.Probability(x).IC() for x in parts) / kl if score > 1.6 and score < 2.1: res.append((score, parts, offset)) # Find best matching key for pattern for score, parts, off in res: - sc, solution = GuessPattern.guess(parts, fn_similarity) + sc, solution = LP.GuessPattern.guess(parts, fn_similarity) if sc < 0.1: fmt = 'kl: {}, pattern-n: {}, IoC: {:.3f}, dist: {:.4f}, offset: {}, key: {}' print(fmt.format(kl, pattern_shift, score, sc, off, - RuneText(solution).text)) + LP.RuneText(solution).text)) solved = gr.zip(fn_pattern(solution, kl), off) for i in data_i: solved.insert(i, 29) - print(' ', RuneText(solved).text) + print(' ', LP.RuneText(solved).text) ######################################### # main ######################################### -db = InterruptDB.load('db_norm') +db = LP.InterruptDB.load('db_norm') # IOC_MIN_SCORE = 1.4 # for db_high IOC_MIN_SCORE = 0.55 # for db_norm @@ -166,5 +159,5 @@ for fname in [ maxscore = max(x[1] for x in db[fname]) print('No candidates. Highest score is only', maxscore) continue - break_cipher(fname, candidates, AffineSolver, GuessAffine) - break_cipher(fname, candidates, VigenereSolver, GuessVigenere) + break_cipher(fname, candidates, LP.AffineSolver, LP.GuessAffine) + break_cipher(fname, candidates, LP.VigenereSolver, LP.GuessVigenere) diff --git a/solver.py b/solver.py index 297f9bf..d5a3953 100755 --- a/solver.py +++ b/solver.py @@ -1,22 +1,20 @@ #!/usr/bin/env python3 -from RuneSolver import VigenereSolver, SequenceSolver -from RuneText import Rune, RuneText -from lib import elliptic_curve +import LP import sys import itertools def load_sequence_file(fname): - with open(fname, 'r') as f: + with open(LP.path.data(fname), 'r') as f: return [int(x) for x in f.readlines()] -PRIMES = load_sequence_file('data/seq_primes_1000.txt') -PRIMES_3301 = load_sequence_file('data/seq_primes_3301.txt') -NOT_PRIMES = load_sequence_file('data/seq_not_primes.txt') -FIBONACCI = load_sequence_file('data/seq_fibonacci.txt') -LUCAS = load_sequence_file('data/seq_lucas_numbers.txt') -MOEBIUS = load_sequence_file('data/seq_moebius.txt') +PRIMES = load_sequence_file('seq_primes_1000') +PRIMES_3301 = load_sequence_file('seq_primes_3301') +NOT_PRIMES = load_sequence_file('seq_not_primes') +FIBONACCI = load_sequence_file('seq_fibonacci') +LUCAS = load_sequence_file('seq_lucas_numbers') +MOEBIUS = load_sequence_file('seq_moebius') def print_all_solved(): @@ -46,13 +44,13 @@ def print_all_solved(): slvr.INTERRUPT = 'ᚠ' slvr.INTERRUPT_POS = [4] - def solve(path, fn_solution, solver=VigenereSolver): + def solve(fname, fn_solution, solver=LP.VigenereSolver): slvr = solver() slvr.output.COLORS = False slvr.output.QUIET = True # or use -v/-q while calling - slvr.input.load(file=f'pages/{path}.txt') + slvr.input.load(file=LP.path.page(fname)) fn_solution(slvr) - print(f'pages/{path}.txt') + print(f'pages/{fname}.txt') print() slvr.run() print() @@ -64,78 +62,62 @@ def print_all_solved(): solve('0_loss_of_divinity', plain) solve('jpg107-167', solution_jpg107_167) solve('jpg229', plain) - solve('p56_an_end', solution_p56_end, solver=SequenceSolver) + solve('p56_an_end', solution_p56_end, solver=LP.SequenceSolver) solve('p57_parable', plain) def play_around(): - slvr = VigenereSolver() + slvr = LP.VigenereSolver() slvr.output.COLORS = False slvr.output.QUIET = True slvr.KEY_DATA = [] vowels = 'ᚢᚩᛁᛇᛖᛟᚪᚫᛡᛠ' - for uuu in ['0-2', '3-7', '8-14', '15-22', '23-26', '27-32', '33-39', '40-53', '54-55']: - slvr.input.load(file=f'pages/p{uuu}.txt') + for uuu in LP.FILES_UNSOLVED: + slvr.input.load(file=LP.path.page(uuu)) print(uuu) print('word count:', sum(len(x) for x in slvr.input.words.values())) a = [1 if x.rune in vowels else 0 for x in slvr.input.runes_no_whitespace()] b = [a[i:i + 5] for i in range(0, len(a), 5)] c = [int(''.join(str(y) for y in x), 2) for x in b] # print('-'.join(str(x) for x in c)) - # print(''.join(Rune(i=x).text for x in c)) + # print(LP.RuneText(c).text) # print(''.join('ABCDEFGHIJKLMNOPQRSTUVWXYZ___...'[x] for x in c)) # slvr.run() def try_totient_on_unsolved(): - slvr = SequenceSolver() + slvr = LP.SequenceSolver() slvr.output.QUIET = True slvr.output.BREAK_MODE = '' # disable line breaks # slvr.INTERRUPT = 'ᛝ' # slvr.INTERRUPT_POS = [1] # for uuu in ['15-22']: - for uuu in ['0-2', '3-7', '8-14', '15-22', '23-26', '27-32', '33-39', '40-53', '54-55']: + for uuu in LP.FILES_UNSOLVED: print() print(uuu) - with open(f'pages/p{uuu}.txt', 'r') as f: - slvr.input.load(RuneText(f.read()[:15])) - # alldata = slvr.input.runes_no_whitespace() + [Rune(i=29)] - - def b60(x): - v = x % 60 - return v if v < 29 else 60 - v + slvr.input.load(file=LP.path.page(uuu), limit=25) + # alldata = slvr.input.runes_no_whitespace() + [LP.Rune(i=29)] def ec(r, i): - p1, p2 = elliptic_curve(i, 149, 263, 3299) + p1, p2 = LP.utils.elliptic_curve(i, 149, 263, 3299) if p1 is None: return r.index return r.index + p1 % 29 - # for p in PRIMES[:500]: - # print(p) - # for z in range(29): - # def fn(i, x): - # return (x + alldata[i - 1].index + z) % 29 - # if fn(0, alldata[0].index) not in [10, 24]: - # continue - # slvr.FN = lambda i, r: Rune(i=fn(i, r.index)) - # slvr.run() + for z in range(29): - # slvr.FN = lambda i, r: r - z # ((i + z) // 3) # slvr.FN = lambda i, r: r - PRIMES[i] + z - # slvr.FN = lambda i, r: Rune(i=b60(r.prime) + z % 29) - # slvr.FN = lambda i, r: Rune(i=((r.prime + alldata[i + 1].prime) + z) % 60 // 2) - # slvr.FN = lambda i, r: Rune(i=(3301 * r.index + z) % 29) - slvr.FN = lambda i, r: Rune(i=(ec(r, i) + z) % 29) - # slvr.FN = lambda i, r: Rune(i=(r.prime - PRIMES[FIBONACCI[i]] + z) % 29) - # slvr.FN = lambda i, r: Rune(i=(r.prime ** i + z) % 29) + # slvr.FN = lambda i, r: LP.Rune(i=((r.prime + alldata[i + 1].prime) + z) % 60 // 2) + # slvr.FN = lambda i, r: LP.Rune(i=(r.prime - PRIMES[FIBONACCI[i]] + z) % 29) + # slvr.FN = lambda i, r: LP.Rune(i=(r.prime ** i + z) % 29) + slvr.FN = lambda i, r: LP.Rune(i=(ec(r, i) + z) % 29) slvr.run() -def find_oeis(irp=0, invert=False, offset=0): +def find_oeis(irp=0, invert=False, offset=0, allow_fails=1, min_match=2): def trim_orig_oeis(minlen=15, trim=40): # download and unzip: https://oeis.org/stripped.gz - with open('data/oeis_orig.txt', 'r') as f_in: - with open('data/oeis.txt', 'w') as f_out: + with open(LP.path.data('oeis_orig'), 'r') as f_in: + with open(LP.path.data('oeis'), 'w') as f_out: for line in f_in.readlines(): if line[0] == '#': continue @@ -146,64 +128,79 @@ def find_oeis(irp=0, invert=False, offset=0): f_out.write(name + ',' + ','.join(vals) + '\n') # trim_orig_oeis() # create db if not present already - with open('data/oeis.txt', 'r') as f: + with open(LP.path.data('oeis'), 'r') as f: seqs = [] for line in f.readlines(): - name, *vals = line.split(',') - vals = [int(x) for x in vals] - seqs.append([name] + vals) + vals = line.split(',') + seqs.append((vals[0], list(map(int, vals[1:])))) - RUNES = 'ᚠᚢᚦᚩᚱᚳᚷᚹᚻᚾᛁᛄᛇᛈᛉᛋᛏᛒᛖᛗᛚᛝᛟᛞᚪᚫᚣᛡᛠ' words = [set()] * 13 - words[1] = set(x for x in RUNES) + words[1] = set(x for x in LP.RUNES) for i in range(2, 13): # since 12 is the longest word - with open(f'data/dictionary_{i}.txt', 'r') as f: + with open(LP.path.data(f'dictionary_{i}'), 'r') as f: words[i] = set(x.strip() for x in f.readlines()) for uuu, wlen in { - '0-2': [8, 5, 4, 3, 3, 11, 5, 4, 3, 3], - '3-7': [2, 11, 3, 4, 7, 7, 7, 4, 6], - '8-14': [4, 8, 3, 2, 3, 9, 4, 3, 4, 2, 2], - '15-22': [4, 5, 4, 2, 5, 4, 5, 6, 5, 6, 3, 3], - '23-26': [2, 6, 3, 4, 8, 3, 3, 7, 5, 5], - '27-32': [3, 12, 4, 7, 2, 3, 3, 2, 1, 3, 4], - '33-39': [2, 8, 2, 9, 6, 3, 3, 5, 3, 2], - '40-53': [3, 5, 5, 4, 3, 5, 4, 2, 12, 3, 3, 2], - '54-55': [1, 8, 8, 3, 6, 2, 5, 3, 2, 3, 5, 7], - # '56_an_end': [2, 3, 5, 2, 4, 3, 4, 6, 1, 4, 3, 6, 2], + 'p0-2': [8, 5, 4, 3, 3, 11, 5, 4, 3, 3], + 'p3-7': [2, 11, 3, 4, 7, 7, 7, 4, 6], + 'p8-14': [4, 8, 3, 2, 3, 9, 4, 3, 4, 2, 2], + 'p15-22': [4, 5, 4, 2, 5, 4, 5, 6, 5, 6, 3, 3], + 'p23-26': [2, 6, 3, 4, 8, 3, 3, 7, 5, 5], + 'p27-32': [3, 12, 4, 7, 2, 3, 3, 2, 1, 3, 4], + 'p33-39': [2, 8, 2, 9, 6, 3, 3, 5, 3, 2], + 'p40-53': [3, 5, 5, 4, 3, 5, 4, 2, 12, 3, 3, 2], + 'p54-55': [1, 8, 8, 3, 6, 2, 5, 3, 2, 3, 5, 7], + # 'p56_an_end': [2, 3, 5, 2, 4, 3, 4, 6, 1, 4, 3, 6, 2], }.items(): - minwords = sum(wlen[:2]) # must match at least n words splits = [(0, 0, 0)] for x in wlen: splits.append((splits[-1][1], splits[-1][1] + x)) splits = splits[1:] print() print(uuu) - with open(f'pages/p{uuu}.txt', 'r') as f: - data = RuneText(f.read()[:120]).index_no_whitespace + with open(LP.path.page(uuu), 'r') as f: + data = LP.RuneText(f.read()[:120]).index_rune_only irps = [i for i, x in enumerate(data[:splits[-1][1]]) if x == irp] - irps.reverse() + irps.reverse() # insert -1 starting with the last if invert: data = [28 - x for x in data] - for oeis, *v in seqs: # 390k - v = v[offset:] - if len(v) < minwords: + min_len = sum(wlen[:2]) # must match at least n words + data_len = len(data) + for oeis, vals in seqs: # 390k + vals = vals[offset:] + if len(vals) < min_len: continue - cases = [x for x in irps if x < len(v)] + cases = [x for x in irps if x < len(vals)] for i in range(len(cases) + 1): for comb in itertools.combinations(cases, i): # 2^3 - res = v[:] + res = vals[:] for z in comb: res.insert(z, -1) # insert interrupts + shortest = min(data_len, len(res)) + for s in range(29): - who = ''.join(RUNES[x if y == -1 else (x - y - s) % 29] - for x, y in zip(data, res)) - active = [x for x in splits if x[1] <= len(who)] - bag = [who[x:y] for x, y in active] - if all(w in words[len(w)] for w in bag): - print(oeis.split()[0], 'shift:', s, 'irps:', comb) - print(' ', ' '.join(RuneText(w).text for w in bag)) + failed = 0 + full = [] + clen = 0 + for a, b in splits: + if b > shortest: + break + nums = [x if y == -1 else (x - y - s) % 29 + for x, y in zip(data[a:b], res[a:b])] + word = ''.join(LP.RUNES[x] for x in nums) + if word in words[len(nums)]: + clen += len(nums) + else: + failed += 1 + if failed > allow_fails: + break + full.append(LP.RuneText(nums).text) + + if failed > allow_fails or clen < min_match: + continue # too many failed + print(oeis.split()[0], 'shift:', s, 'irps:', comb) + print(' ', ' '.join(full)) if '-s' in sys.argv: # print [s]olved @@ -213,4 +210,4 @@ else: # try_totient_on_unsolved() # for i in range(0, 4): # print('offset:', i) - # find_oeis(irp=0, invert=False, offset=i) + # find_oeis(irp=0, invert=False, offset=i, allow_fails=1, min_match=10) diff --git a/tmp/0_welcome_0.8844.GuessVigenere.0_8.txt b/tmp/0_welcome_0.8844.GuessVigenere.0_8.txt new file mode 100644 index 0000000..463c639 --- /dev/null +++ b/tmp/0_welcome_0.8844.GuessVigenere.0_8.txt @@ -0,0 +1,17 @@ +0, 8, 0.9018, 0.0193, [23, 10, 1, 10, 9, 10, 16, 26], [4, 5, 6, 7, 9, 12, 14, 18, 20] +WELCOME# +WELCOME,PILGRIM,TO THE GREAT JOURNEY TOWARD THE END OF ALL THNGS. +IT IS NOT AN EASY TRIP,BUT FOR THOSE WHO FIND THEIR WAY HERE IT IS A NECESSARY ONE. +ALONG THE WAY YOU WILL FIND AN END TO ALL STRUGGFM UML THNGEAMMOS,TFUR INNOCENCE,YOUR ILLUSIANS,YOUR CERTAINTY,AND YOUR REALITY. +ULTIMATELY,YOU WILL DISCOUER AN END TO SELF. +IT IS THROUGH THIS PILGRIMAGE THAT WE SHAPE OURSELUES AND OUR REALITIES. +JOURNEY DEEP WITHIN AND YOU WILL ARRIUE OUTSIDE. +LICE THE INSTAR,IT IS ONLY THROUGH GONG WITHIN THAT WE MAY EMERGE# +WIDSOM# +YOU ARE A BENG UNTO YOURSELF. +YOU ARE A LAW UNTO YOURSELF. +EACH INTELLIGENCE IS HOLY. +GTP SNGM AEXM RMNGMX RC JTFB# +# +AEH REAEFPNGGSNGEA# +HTEAIAEHB TGXP DHH NHDM# diff --git a/tmp/jpg107-167_0.7102.GuessVigenere.0_13.txt b/tmp/jpg107-167_0.7102.GuessVigenere.0_13.txt new file mode 100644 index 0000000..079a8c6 --- /dev/null +++ b/tmp/jpg107-167_0.7102.GuessVigenere.0_13.txt @@ -0,0 +1,8 @@ +0, 13, 0.7420, 0.0429, [21, 10, 4, 0, 1, 19, 21, 18, 4, 18, 24, 0, 18], [2, 4] +O COAN# +DNRNG A CESDON,THE MOSTEE EXNGLAINETH THE I. +"TE I ES THE UOECE OF TE CERCUMITSOAEAIA,"MS CUXOE. +EGFD INXFL WO EA XIAEACOAI AEX SRMADNGW EOEII JGP NCPS;PT UNUIIAS EOXTIA"NIA FD A UOACE ENSIDE COUR OEEAD". +"E DON'T HOUE A SOIPE IN MY TEAD," THBUGTT THE STNDENU,ANTH HE RAIDED HAS HOND TO TYLL THO MADTER. +THE IAASTOR SAOPPED IE STSDEBT,AND SOID"THO UOECE THAT MUST FAITH YOU HANE NO SOIPE IN YONR HEAH;IS IE I. +"AND IE STSDEBTS WERY ENLAGHAENED#