module refactoring + allow word mistakes for OEIS search

This commit is contained in:
relikd
2021-02-07 16:42:08 +01:00
parent 9e9067c775
commit 6d01aa4424
19 changed files with 313 additions and 282 deletions

2
.gitignore vendored
View File

@@ -4,4 +4,4 @@ data/oeis.txt
other/ec-*.png
other/ec-*.txt
other/list-onions.txt
out/
tmp/

View File

@@ -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

View File

@@ -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:

View File

@@ -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'<table>{trh}{"".join(trd[1:])}{trbest}</table>'
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'<a href="#tb-i{i}">{RUNES[i]}</a>\n'
txt += f'<h3 id="tb-i{i}">Interrupt {i}: <b>{RUNES[i]}</b></h3>'
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)

38
LP/LPath.py Executable file
View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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])
#########################################

View File

@@ -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

17
LP/__init__.py Normal file
View File

@@ -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

View File

@@ -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]

View File

@@ -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#

View File

@@ -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#

View File

@@ -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

View File

@@ -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)

157
solver.py
View File

@@ -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):
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(RuneText(w).text for w in bag))
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)

View File

@@ -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#

View File

@@ -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#