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-*.png
other/ec-*.txt other/ec-*.txt
other/list-onions.txt other/list-onions.txt
out/ tmp/

View File

@@ -1,7 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import re
from RuneText import RuneText
from NGrams import NGrams from NGrams import NGrams
from RuneText import RUNES
def normalized_probability(int_prob): 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) return [x / total for x in int_prob] # math.log(x / total, 10)
RUNES = 'ᚠᚢᚦᚩᚱᚳᚷᚹᚻᚾᛁᛄᛇᛈᛉᛋᛏᛒᛖᛗᛚᛝᛟᛞᚪᚫᚣᛡᛠ'
re_norune = re.compile('[^' + RUNES + ']')
PROB_INT = [0] * 29 PROB_INT = [0] * 29
for k, v in NGrams.load(1, '').items(): # '-no-e', '-solved' for k, v in NGrams.load(1, '').items(): # '-no-e', '-solved'
PROB_INT[RUNES.index(k)] = v PROB_INT[RUNES.index(k)] = v
@@ -53,24 +50,3 @@ class Probability(object):
val = sum(abs(Probability(nums[x::keylen]).IC() - target_ioc) val = sum(abs(Probability(nums[x::keylen]).IC() - target_ioc)
for x in range(keylen)) for x in range(keylen))
return 1 - (val / 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 #!/usr/bin/env python3
import itertools # product, compress, combinations import itertools # product, compress, combinations
import bisect # bisect_left, insort import bisect # bisect_left, insort
import lib as LIB from lib import affine_decrypt
######################################### #########################################
@@ -45,7 +45,7 @@ class GuessAffine(object):
best = 9999999 best = 9999999
for s in range(29): for s in range(29):
for t 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]] for x in self.nums[offset::keylength]]
score = score_fn(shifted) score = score_fn(shifted)
if score < best: if score < best:

View File

@@ -1,15 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os import os
from HeuristicSearch import SearchInterrupt from HeuristicSearch import SearchInterrupt
from HeuristicLib import load_indices, Probability from HeuristicLib import Probability
from RuneText import RUNES, load_indices
RUNES = 'ᚠᚢᚦᚩᚱᚳᚷᚹᚻᚾᛁᛄᛇᛈᛉᛋᛏᛒᛖᛗᛚᛝᛟᛞᚪᚫᚣᛡᛠ' # used in InterruptToWeb from LPath import FILES_ALL, FILES_UNSOLVED, LPath
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
######################################### #########################################
@@ -85,10 +79,10 @@ class InterruptDB(object):
@staticmethod @staticmethod
def load(dbname): def load(dbname):
if not os.path.isfile(f'InterruptDB/{dbname}.txt'): if not os.path.isfile(LPath.InterruptDB(dbname)):
return {} return {}
ret = {} ret = {}
with open(f'InterruptDB/{dbname}.txt', 'r') as f: with open(LPath.InterruptDB(dbname), 'r') as f:
for line in f.readlines(): for line in f.readlines():
if line.startswith('#'): if line.startswith('#'):
continue continue
@@ -104,7 +98,7 @@ class InterruptDB(object):
@staticmethod @staticmethod
def write(name, score, irp, irpmax, keylen, nums, dbname='db_main'): 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)) nums = ','.join(map(str, nums))
f.write(f'{name}|{irpmax}|{score:.5f}|{irp}|{keylen}|{nums}\n') f.write(f'{name}|{irpmax}|{score:.5f}|{irp}|{keylen}|{nums}\n')
@@ -134,7 +128,7 @@ class InterruptIndices(object):
@staticmethod @staticmethod
def write(dbname='db_indices'): 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') f.write('# file | total runes in file | interrupt | indices\n')
for name in FILES_ALL: for name in FILES_ALL:
fname = f'pages/{name}.txt' fname = f'pages/{name}.txt'
@@ -149,7 +143,7 @@ class InterruptIndices(object):
@staticmethod @staticmethod
def read(dbname='db_indices'): def read(dbname='db_indices'):
with open(f'InterruptDB/{dbname}.txt', 'r') as f: with open(LPath.InterruptDB(dbname), 'r') as f:
ret = {} ret = {}
for line in f.readlines(): for line in f.readlines():
if line.startswith('#'): if line.startswith('#'):
@@ -169,8 +163,9 @@ class InterruptIndices(object):
######################################### #########################################
class InterruptToWeb(object): class InterruptToWeb(object):
def __init__(self, dbname, template='InterruptDB/template.html'): def __init__(self, dbname, template='template.html'):
self.template = template with open(LPath.results(template), 'r') as f:
self.template = f.read()
self.indices = InterruptIndices() self.indices = InterruptIndices()
self.scores = {} self.scores = {}
db = InterruptDB.load(dbname) db = InterruptDB.load(dbname)
@@ -252,8 +247,6 @@ class InterruptToWeb(object):
return f'<table>{trh}{"".join(trd[1:])}{trbest}</table>' return f'<table>{trh}{"".join(trd[1:])}{trbest}</table>'
def make(self, outfile, pmin=1.25, pmax=1.65): def make(self, outfile, pmin=1.25, pmax=1.65):
with open(self.template, 'r') as f:
html = f.read()
nav = '' nav = ''
txt = '' txt = ''
for i in range(29): for i in range(29):
@@ -263,10 +256,10 @@ class InterruptToWeb(object):
nav += f'<a href="#tb-i{i}">{RUNES[i]}</a>\n' 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 += f'<h3 id="tb-i{i}">Interrupt {i}: <b>{RUNES[i]}</b></h3>'
txt += self.table_interrupt(i, pmin, pmax) 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('__TAB_RELIABLE__', self.table_reliable())
html = html.replace('__INTERRUPT_TABLES__', txt) html = html.replace('__INTERRUPT_TABLES__', txt)
with open(outfile, 'w') as f: with open(LPath.results(outfile), 'w') as f:
f.write(html) 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) oldValues = {k: set((a, b, c) for a, _, b, c, _ in v)
for k, v in oldDB.items()} for k, v in oldDB.items()}
for irp in irpset: # interrupt rune index for irp in irpset: # interrupt rune index
# for name in FILES_UNSOLVED:
for name in FILES_ALL: for name in FILES_ALL:
fname = f'pages/{name}.txt' data = load_indices(LPath.page(name), irp, maxinterrupt=max_irp)
data = load_indices(fname, irp, maxinterrupt=max_irp)
db = InterruptDB(data, 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 for keylen in range(minkl, maxkl + 1): # key length
if (db.irp_count, irp, keylen) in oldValues.get(name, []): if (db.irp_count, irp, keylen) in oldValues.get(name, []):
print(f'{keylen}: skipped.') 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)) search_set.add((name, irp, kl))
print('searching through', len(search_set), 'files.') print('searching through', len(search_set), 'files.')
for name, irp, kl in search_set: for name, irp, kl in search_set:
fname = f'pages/{name}.txt' print('load:', name, 'interrupt:', irp, 'keylen:', kl)
print('load:', fname, 'interrupt:', irp, 'keylen:', kl) data = load_indices(LPath.page(name), irp, maxinterrupt=max_irp)
data = load_indices(fname, irp, maxinterrupt=max_irp)
db = InterruptDB(data, irp) db = InterruptDB(data, irp)
c = db.make_secondary(db_out, name, kl, threshold) c = db.make_secondary(db_out, name, kl, threshold)
print('found', c, 'additional solutions') 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_high', 'db_high_secondary', threshold=1.4)
# find_secondary_solutions('db_norm', 'db_norm_secondary', threshold=0.55) # find_secondary_solutions('db_norm', 'db_norm_secondary', threshold=0.55)
# create_initial_db('db_norm', minkl=1, maxkl=32, max_irp=20) # create_initial_db('db_norm', minkl=1, maxkl=32, max_irp=20)
# InterruptToWeb('db_high').make('InterruptDB/index_high.html') # InterruptToWeb('db_high').make('index_high.html')
InterruptToWeb('db_norm').make( InterruptToWeb('db_norm').make('index_norm.html', pmin=0.40, pmax=0.98)
'InterruptDB/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 #!/usr/bin/env python3
import re 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 @staticmethod
def make(gramsize, infile, outfile): def make(gramsize, infile, outfile):
allowed_chr = [x[1] for x in alphabet]
with open(infile, 'r') as f: 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): for i in range(len(data) - gramsize + 1):
ngram = data[i:i + gramsize] ngram = data[i:i + gramsize]
try: try:
@@ -47,19 +47,28 @@ class NGrams(object):
@staticmethod @staticmethod
def load(ngram=1, prefix=''): def load(ngram=1, prefix=''):
ret = {} 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(): for line in f.readlines():
r, v = line.split() r, v = line.split()
ret[r] = int(v) ret[r] = int(v)
return ret return ret
# NGrams.translate('data/baseline-text.txt', 'data/baseline-rune.txt', False) def make_translation(stream=False): # if true, ignore spaces / word bounds
# for i in range(1, 6): NGrams.translate(LPath.data('baseline-text'),
# print(f'generate {i}-gram file') LPath.data('baseline-rune'), stream)
# NGrams.make(i, infile='data/baseline-rune-words.txt',
# outfile=f'data/p-{i}gram.txt')
# NGrams.make(i, infile='_solved.txt', def make_ngrams(max_ngram=1):
# outfile=f'data/p-solved-{i}gram.txt') for i in range(1, max_ngram + 1):
# NGrams.make(i, infile='data/baseline-rune-no-e.txt', print(f'generate {i}-gram file')
# outfile=f'data/p-no-e-{i}gram.txt') 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 #!/usr/bin/env python3
from RuneText import RuneText
import lib as LIB
import sys import sys
from RuneText import RuneText
import lib as utils
######################################### #########################################
@@ -86,11 +86,11 @@ class RuneReader(object):
self.loaded_file = None self.loaded_file = None
self.words = {x: [] for x in range(20)} # increase for longer words 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 self.loaded_file = None
if not data: if not data:
with open(file, 'r') as f: with open(file, 'r') as f:
data = f.read() data = f.read()[:limit]
self.loaded_file = file self.loaded_file = file
self.data = data if isinstance(data, RuneText) else RuneText(data) self.data = data if isinstance(data, RuneText) else RuneText(data)
self.generate_word_list() self.generate_word_list()
@@ -219,7 +219,7 @@ class RuneRunner(object):
if self.output.VERBOSE: if self.output.VERBOSE:
self.output.write(n1=('' if is_first else '+') + str(num)) self.output.write(n1=('' if is_first else '+') + str(num))
return return
prm = LIB.is_prime(num) prm = utils.is_prime(num)
if typ == 'w': # word if typ == 'w': # word
tt = ('' if is_first else ' + ') + str(num) + ('*' if prm else '') tt = ('' if is_first else ' + ') + str(num) + ('*' if prm else '')
self.output.write(n2=tt) self.output.write(n2=tt)
@@ -229,7 +229,7 @@ class RuneRunner(object):
self.output.mark = False self.output.mark = False
# if not is_first: # if not is_first:
sffx = ' = {}'.format(num) + ('*' if prm else '') sffx = ' = {}'.format(num) + ('*' if prm else '')
if LIB.is_prime(LIB.rev(num)): if utils.is_emirp(num):
sffx += '' sffx += ''
if self.output.VERBOSE: if self.output.VERBOSE:
self.output.write(n2=sffx) self.output.write(n2=sffx)

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from RuneRunner import RuneRunner from RuneRunner import RuneRunner
from RuneText import Rune, RuneText from RuneText import Rune, RuneText
import lib as LIB from lib import affine_decrypt
######################################### #########################################
@@ -186,7 +186,7 @@ class VigenereSolver(RunningKeySolver):
class AffineSolver(RunningKeySolver): class AffineSolver(RunningKeySolver):
def decrypt(self, rune_index, key_index): 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 #!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re # load_indices
white_rune = {'': ' ', '': '.', '': ',', '': ';', '': '#'} white_rune = {'': ' ', '': '.', '': ',', '': ';', '': '#'}
white_text = {v: k for k, v in white_rune.items()} white_text = {v: k for k, v in white_rune.items()}
alphabet = [ # Using last value for display. Custom added: V 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} 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} 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} 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 # del alphabet # used in playground for GP display
@@ -37,7 +41,7 @@ class Rune(object):
@property @property
def rune(self): def rune(self):
if self._rune is None: 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 return self._rune
@property @property
@@ -54,7 +58,7 @@ class Rune(object):
def index(self): def index(self):
if self._index is None: if self._index is None:
r = self._rune 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 return self._index
@property @property
@@ -182,37 +186,18 @@ class RuneText(object):
res.append(Rune(r=rune, t=char)) res.append(Rune(r=rune, t=char))
return res 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): def description(self, count=False, index=True, indexWhitespace=False):
if len(self) == 0: return None if len(self) == 0 else \
return None self.rune + (f' ({len(self)})' if count else '') + ' - ' + \
fmt = '{0} ({1}) {2} ({3})' if count else '{0} {2}' self.text + (f' ({len(self.text)})' if count else '') + \
d = self.as_dict() (f' - {self.index if indexWhitespace else self.index_rune_only}'
if index: if index else '')
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'])
def zip_sub(self, other): def zip_sub(self, other):
if len(self) != len(other): if len(self) != len(other):
raise IndexError('RuneText length mismatch') raise IndexError('RuneText length mismatch')
return RuneText([x - y for x, y in zip(self._data, other._data)]) 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 @property
def text(self): def text(self):
return ''.join(x.text for x in self._data) 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'] return [x.index for x in self._data if x.kind != 'l']
@property @property
def index_no_whitespace(self): def index_rune_only(self):
return [x.index for x in self._data if x.index != 29] 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): def __getitem__(self, key):
if isinstance(key, str): if isinstance(key, str):
return [getattr(x, key) for x in self._data] return [getattr(x, key) for x in self._data]
@@ -255,3 +250,24 @@ class RuneText(object):
def __repr__(self): def __repr__(self):
return f'RuneText<{len(self._data)}>' 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 #!/usr/bin/env python3
import math 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): def is_prime(num):
if isinstance(num, str): if isinstance(num, str):
num = int(num) num = int(num)
@@ -30,6 +26,10 @@ def rev(num): # or int(str(num)[::-1])
return revs return revs
def is_emirp(num):
return is_prime(rev(num))
def power(x, y, p): def power(x, y, p):
res = 1 res = 1
x %= p x %= p
@@ -74,6 +74,9 @@ def elliptic_curve(x, a, b, r):
return y, -y % r return y, -y % r
AFFINE_INV = None
def affine_inverse(s, n=29): def affine_inverse(s, n=29):
def fn(s, n): def fn(s, n):
g = [n, s] 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 #!/usr/bin/env python3
from RuneSolver import VigenereSolver, AffineSolver, AutokeySolver import LP
from RuneText import alphabet, RuneText
import lib as LIB
SOLVER = VigenereSolver() SOLVER = LP.VigenereSolver() # VigenereSolver, AffineSolver, AutokeySolver
# SOLVER = AffineSolver() SOLVER.input.load(file=LP.path.root('_input.txt'))
# SOLVER = AutokeySolver()
SOLVER.input.load(file='_input.txt')
def main(): def main():
@@ -82,7 +78,7 @@ def command_a(cmd, args): # [a]ll variations
return False return False
if not args: if not args:
raise Warning('No input provided.') raise Warning('No input provided.')
root = RuneText(args) root = LP.RuneText(args)
inclIndex = 'q' not in cmd inclIndex = 'q' not in cmd
if 'i' in cmd: if 'i' in cmd:
root = ~root root = ~root
@@ -101,10 +97,10 @@ def command_d(cmd, args): # [d]ecrypt or single substitution
trythis = input('What is the encrypted text?: ').strip() trythis = input('What is the encrypted text?: ').strip()
else: else:
trythis = args trythis = args
enc = RuneText(trythis) enc = LP.RuneText(trythis)
print('encrypted:', enc.description()) print('encrypted:', enc.description())
target = input('What should the decrypted clear text be?: ').strip() target = input('What should the decrypted clear text be?: ').strip()
plain = RuneText(target) plain = LP.RuneText(target)
print('plaintext:', plain.description()) print('plaintext:', plain.description())
if len(enc) != len(plain): if len(enc) != len(plain):
print('Error: key length mismatch') print('Error: key length mismatch')
@@ -126,7 +122,7 @@ def command_f(cmd, args): # (f)ind word
search_term = None search_term = None
s_len = int(args.strip()) # search words with n-length s_len = int(args.strip()) # search words with n-length
except ValueError: except ValueError:
search_term = RuneText(args) search_term = LP.RuneText(args)
s_len = len(search_term) s_len = len(search_term)
cur_words = SOLVER.highlight_words_with_len(s_len) cur_words = SOLVER.highlight_words_with_len(s_len)
@@ -165,14 +161,14 @@ def command_g(cmd, args): # (g)ematria primus
return False return False
def pp(i): 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)) return '{:2d} {} {:3d} {}'.format(i, r, p, '/'.join(t))
rev = cmd[-1] in 'ir' or len(args) > 0 and args[0] in 'ir' rev = cmd[-1] in 'ir' or len(args) > 0 and args[0] in 'ir'
print('Gematria Primus (reversed)' if rev else 'Gematria Primus') 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): for i in range(half):
if i < len(alphabet) // 2: if i < len(LP.alphabet) // 2:
print('{:22} {}'.format(pp(i), pp(i + half))) print('{:22} {}'.format(pp(i), pp(i + half)))
else: else:
print(pp(i)) print(pp(i))
@@ -206,8 +202,8 @@ def command_h(cmd, args): # (h)ighlight
def command_k(cmd, args): # (k)ey manipulation def command_k(cmd, args): # (k)ey manipulation
if cmd == 'k' or cmd == 'key': if cmd == 'k' or cmd == 'key':
SOLVER.KEY_DATA = [x.index for x in RuneText(args)] SOLVER.KEY_DATA = LP.RuneText(args).index
print('set key: {}'.format(SOLVER.KEY_DATA)) print(f'set key: {SOLVER.KEY_DATA}')
elif cmd[1] == 's': elif cmd[1] == 's':
SOLVER.KEY_SHIFT = get_cmd_int(cmd, args, 'shift') SOLVER.KEY_SHIFT = get_cmd_int(cmd, args, 'shift')
elif cmd[1] == 'r': 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') SOLVER.KEY_POST_PAD = get_cmd_int(cmd, args, 'post padding')
elif cmd[1] == 'i': elif cmd[1] == 'i':
SOLVER.KEY_INVERT = not SOLVER.KEY_INVERT 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': elif cmd == 'kj':
args = args.strip('[]') args = args.strip('[]')
pos = [int(x) for x in args.split(',')] if args else [] pos = [int(x) for x in args.split(',')] if args else []
SOLVER.INTERRUPT_POS = pos SOLVER.INTERRUPT_POS = pos
print('set interrupt jumps: {}'.format(pos)) print(f'set interrupt jumps: {SOLVER.INTERRUPT_POS}')
else: else:
return False # command not found return False # command not found
SOLVER.run() SOLVER.run()
@@ -254,8 +250,8 @@ def command_p(cmd, args): # (p)rime test
if args and cmd not in 'prime': if args and cmd not in 'prime':
return False return False
p = get_cmd_int(cmd, args, start=1) p = get_cmd_int(cmd, args, start=1)
print(p, ':', LIB.is_prime(p)) print(p, ':', LP.utils.is_prime(p))
print(LIB.rev(p), ':', LIB.is_prime(LIB.rev(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 def command_t(cmd, args): # (t)ranslate
if cmd != 't': if cmd != 't':
return False return False
word = RuneText(args) word = LP.RuneText(args)
x = word.as_dict() sffx = ''.join(['*' if LP.utils.is_prime(word.prime_sum) else '',
psum = sum(x['p']) '' if LP.utils.is_emirp(word.prime_sum) else ''])
sffx = '*' if LIB.is_prime(psum) else '' print('runes({}): {}'.format(len(word), word.rune))
if LIB.is_prime(LIB.rev(psum)): print('plain({}): {}'.format(len(word.text), word.text))
sffx += '' print('reversed: {}'.format((~word).rune))
print('runes({}): {}'.format(len(x['r']), x['r'])) print('indices: {}'.format(word.index))
print('plain({}): {}'.format(len(x['t']), x['t'])) print('prime({}{}): {}'.format(word.prime_sum, sffx, word.prime))
print('reversed: {}'.format(''.join([x.rune for x in ~word])))
print('indices: {}'.format(x['i']))
print('prime({}{}): {}'.format(psum, sffx, x['p']))
######################################### #########################################
@@ -286,7 +279,7 @@ def command_x(cmd, args): # e(x)ecute decryption
if cmd == 'x': if cmd == 'x':
pass # just run the solver pass # just run the solver
elif cmd == 'xf': # reload from file 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) print('loading file:', file)
SOLVER.input.load(file=file) SOLVER.input.load(file=file)
args = None # so run() won't override data args = None # so run() won't override data

View File

@@ -1,18 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: UTF-8 -*- # -*- coding: UTF-8 -*-
from RuneSolver import VigenereSolver, AffineSolver import LP
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
RUNES = 'ᚠᚢᚦᚩᚱᚳᚷᚹᚻᚾᛁᛄᛇᛈᛉᛋᛏᛒᛖᛗᛚᛝᛟᛞᚪᚫᚣᛡᛠ'
INVERT = False INVERT = False
KEY_MAX_SCORE = 0.05 KEY_MAX_SCORE = 0.05
AFF_MAX_SCORE = 0.04 AFF_MAX_SCORE = 0.04
IRP_F_ONLY = False IRP_F_ONLY = True
session_files = [] session_files = []
@@ -22,9 +15,9 @@ session_files = []
def break_cipher(fname, candidates, solver, key_fn): def break_cipher(fname, candidates, solver, key_fn):
def fn_similarity(x): 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 = solver()
slvr.input.load(file=filename) slvr.input.load(file=filename)
slvr.output.QUIET = True 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: for irp_count, score, irp, kl, skips in candidates:
if IRP_F_ONLY and irp != 0: if IRP_F_ONLY and irp != 0:
continue continue
data = load_indices(filename, irp, maxinterrupt=irp_count) data = LP.load_indices(filename, irp, maxinterrupt=irp_count)
if INVERT: if INVERT:
data = [28 - x for x in data] data = [28 - x for x in data]
iguess = SearchInterrupt(data, (28 - irp) if INVERT else irp) iguess = LP.SearchInterrupt(data, (28 - irp) if INVERT else irp)
print('score: {}, interrupt: {}, count: {}, solver: {}'.format( print('IoC: {}, interrupt: {}, count: {}, solver: {}'.format(
score, RUNES[irp], len(iguess.stops), key_fn.__name__)) score, LP.RUNES[irp], len(iguess.stops), key_fn.__name__))
testcase = iguess.join(iguess.from_occurrence_index(skips)) testcase = iguess.join(iguess.from_occurrence_index(skips))
key_score, key = key_fn(testcase).guess(kl, fn_similarity) 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: while txtname in session_files:
txtname += '.' txtname += '.'
session_files.append(txtname) session_files.append(txtname)
outfile = f'out/{txtname}.txt' outfile = LP.path.tmp(txtname)
with open(outfile, 'w') as f: with open(outfile, 'w') as f:
f.write( f.write(
f'{irp}, {kl}, {score:.4f}, {key_score:.4f}, {key}, {skips}\n') f'{irp}, {kl}, {score:.4f}, {key_score:.4f}, {key}, {skips}\n')
slvr.output.file_output = outfile slvr.output.file_output = outfile
slvr.INTERRUPT = RUNES[irp] slvr.INTERRUPT = LP.RUNES[irp]
slvr.INTERRUPT_POS = skips slvr.INTERRUPT_POS = skips
slvr.KEY_DATA = key slvr.KEY_DATA = key
slvr.run() slvr.run()
def pattern_solver(fname, irp=0): def pattern_solver(fname, irp=0):
with open(f'pages/{fname}.txt', 'r') as f: with open(LP.path.page(fname), 'r') as f:
orig = RuneText(f.read()) orig = LP.RuneText(f.read())
# orig = RuneText('ᛄᚹᚻᛗᛋᚪ-ᛋᛁᚫᛇ-ᛋᛠᚾᛞ-ᛇᛞ-ᛞᚾᚣᚹᛗ.ᛞᛈ-ᛝᛚᚳᚾᛗᚾᚣ-ᛖᛝᛖᚦᚣᚢ-ᚱᚻᛁᚠ-ᛟᛝ-ᛚᛖᚫᛋᛚᚳᛋᛇ.ᚣᚾᚻᛄᚾᚳᛡ-ᚷᚳᛝ-ᛈᛝ-ᛡᚷᚦᚷᛖ.ᚻᛠᚣ-ᛄᛞᚹᛒ-ᛇᛄᛝᚩᛟ.ᛗᛠᚣᛋᛖᛚᚠ-ᚾᚫᛁ-ᛄᚹᚻᚻᛚᛈᚹᚠ-ᚫᚩᛡᚫᛟ-ᚷᛠ-ᚪᛡᚠᛄᚱᛏᚢᛈ.ᛏᛈ-ᛇᛞ-ᛟᛗᛇᛒᛄᚳᛈ.ᛉᛟ-ᛒᚻᚱᛄᚣ-ᚾᚱ-ᚾᛡᛈᛈ-ᛚᛉᛗᛞ-ᛟᛝ-ᚷᛁᚱᚩᚹᛗ-ᚠᛇᚣ-ᚣᛝᛒ--ᚠᚾᚹᚢ-ᛠᚾᛈᚠᚻ.ᚫᛋᛄᚪᚻ-ᛒᛖᛋᚻᛠ-ᛄᛗ-ᛟᛡᚹᚪᛡ-ᛄᛋᛖᚢᛗ-ᛏᛖᛉᚪ-ᛞᛟᛉᚾᚠ-ᚱᛡᛒᛚᚩᛈᛝ-ᛋᛄᛚᛗ-ᛞᚱᛗᛗ-ᛒᛈ-ᛁᛉᚱᛄᛝ.ᛋᛇᚪ-ᛗᚠᚻᚣᚹᛉᛞ-ᛡᛁᚷᚪ-ᚩᚱ-ᚪᚾᚹᛇᛋᛞᛄᚷ-ᛡ-ᛖᚫᛄ-ᛞᛟᛁᚻᚹᛝ-ᛠᛈᛏ-ᚪᛗᛗᛚᛚᚪᛞ.ᛁᛠᛈᚷᛞ-ᛗᚣᛄᚳᚹᛚ-ᚻᛋᛟᛗ-ᚣᚫᛝᛚ-ᛠᛁᛝᛝᚪ-ᚳᛗ-ᚢᚫᛋ-ᛉᛠᚱ-ᛇᛡᛄᚻᛗᚾ-ᚻᛗᛝᛚ-ᛇᛞ-ᛟᚢᚣᚪᚷᚱ-ᛡᚷ-ᚷᛠ-ᛚᚻᛒ.ᛡᛒ-ᚩᛁᛄ-ᛗᛟᛉᚩᚣ-ᛞᚩ-ᚳᛗ-ᚾᛗᚩ-ᚷᛠ-ᛚᚱᚠᚷ-ᛁᚫᛗᛉ-ᛁᛠᚹᛚ-ᛖᛝᚾᛟᛗᚾ-ᛄᚾ-ᚾᚳᛚᛝ-ᛡ-ᚷᛞᛗᚱᚻᚩ-ᛗᛞᛠᚫᛞ-ᛞᚱᛗᛗ-ᚣᚪ-ᛗᛉᚢᛞᛇᚹ-ᛟᚱᛏᚱᛟᚢᛉᛗᛚᛈᛉᛝ.ᛏᛖ-ᛗᛋᚣ-ᚹᛁ-ᚹᛝ-ᛋᛇᛄᚳᛁᛋᛝ.ᛄᛚᚹ-ᚷᚠᛝ-ᚫᚷᛚᛡᛁᛡ.ᛖᚠᚣ-ᛉᛝᚻᛄᚾᛈᚠ-ᛉᚣ-ᛚᛄᛞᛝᛞᚪ-ᚩᛈ-ᚻᛟ-ᛖᚻᚱᚹ-ᛚᚷᚳ-ᛒᛈᛏᚻ-ᚠᛋᛠᚣᛋᚠ-ᛏᚷᛈᚪᛒ.') # orig = LP.RuneText('ᛄᚹᚻᛗᛋᚪ-ᛋᛁᚫᛇ-ᛋᛠᚾᛞ-ᛇᛞ-ᛞᚾᚣᚹᛗ.ᛞᛈ-ᛝᛚᚳᚾᛗᚾᚣ-ᛖᛝᛖᚦᚣᚢ-ᚱᚻᛁᚠ-ᛟᛝ-ᛚᛖᚫᛋᛚᚳᛋᛇ.ᚣᚾᚻᛄᚾᚳᛡ-ᚷᚳᛝ-ᛈᛝ-ᛡᚷᚦᚷᛖ.ᚻᛠᚣ-ᛄᛞᚹᛒ-ᛇᛄᛝᚩᛟ.ᛗᛠᚣᛋᛖᛚᚠ-ᚾᚫᛁ-ᛄᚹᚻᚻᛚᛈᚹᚠ-ᚫᚩᛡᚫᛟ-ᚷᛠ-ᚪᛡᚠᛄᚱᛏᚢᛈ.ᛏᛈ-ᛇᛞ-ᛟᛗᛇᛒᛄᚳᛈ.ᛉᛟ-ᛒᚻᚱᛄᚣ-ᚾᚱ-ᚾᛡᛈᛈ-ᛚᛉᛗᛞ-ᛟᛝ-ᚷᛁᚱᚩᚹᛗ-ᚠᛇᚣ-ᚣᛝᛒ--ᚠᚾᚹᚢ-ᛠᚾᛈᚠᚻ.ᚫᛋᛄᚪᚻ-ᛒᛖᛋᚻᛠ-ᛄᛗ-ᛟᛡᚹᚪᛡ-ᛄᛋᛖᚢᛗ-ᛏᛖᛉᚪ-ᛞᛟᛉᚾᚠ-ᚱᛡᛒᛚᚩᛈᛝ-ᛋᛄᛚᛗ-ᛞᚱᛗᛗ-ᛒᛈ-ᛁᛉᚱᛄᛝ.ᛋᛇᚪ-ᛗᚠᚻᚣᚹᛉᛞ-ᛡᛁᚷᚪ-ᚩᚱ-ᚪᚾᚹᛇᛋᛞᛄᚷ-ᛡ-ᛖᚫᛄ-ᛞᛟᛁᚻᚹᛝ-ᛠᛈᛏ-ᚪᛗᛗᛚᛚᚪᛞ.ᛁᛠᛈᚷᛞ-ᛗᚣᛄᚳᚹᛚ-ᚻᛋᛟᛗ-ᚣᚫᛝᛚ-ᛠᛁᛝᛝᚪ-ᚳᛗ-ᚢᚫᛋ-ᛉᛠᚱ-ᛇᛡᛄᚻᛗᚾ-ᚻᛗᛝᛚ-ᛇᛞ-ᛟᚢᚣᚪᚷᚱ-ᛡᚷ-ᚷᛠ-ᛚᚻᛒ.ᛡᛒ-ᚩᛁᛄ-ᛗᛟᛉᚩᚣ-ᛞᚩ-ᚳᛗ-ᚾᛗᚩ-ᚷᛠ-ᛚᚱᚠᚷ-ᛁᚫᛗᛉ-ᛁᛠᚹᛚ-ᛖᛝᚾᛟᛗᚾ-ᛄᚾ-ᚾᚳᛚᛝ-ᛡ-ᚷᛞᛗᚱᚻᚩ-ᛗᛞᛠᚫᛞ-ᛞᚱᛗᛗ-ᚣᚪ-ᛗᛉᚢᛞᛇᚹ-ᛟᚱᛏᚱᛟᚢᛉᛗᛚᛈᛉᛝ.ᛏᛖ-ᛗᛋᚣ-ᚹᛁ-ᚹᛝ-ᛋᛇᛄᚳᛁᛋᛝ.ᛄᛚᚹ-ᚷᚠᛝ-ᚫᚷᛚᛡᛁᛡ.ᛖᚠᚣ-ᛉᛝᚻᛄᚾᛈᚠ-ᛉᚣ-ᛚᛄᛞᛝᛞᚪ-ᚩᛈ-ᚻᛟ-ᛖᚻᚱᚹ-ᛚᚷᚳ-ᛒᛈᛏᚻ-ᚠᛋᛠᚣᛋᚠ-ᛏᚷᛈᚪᛒ.')
# orig = RuneText('ᛇᚦ-ᛒᛏᚣᚳ-ᛇᛚᛉ-ᛄᛚᚦᚪ-ᛋᚱᛉᚦ-ᚦᛄᚻ-ᛉᛗ-ᛏᛞᛋ-ᚣᚾ-ᚣᛟᛇᛈᛟᛚ-ᛈᚹᛚᚪᛗ-ᚪᛉᛁᛇᛝᚢᚱᛉ.ᛞᛄ-ᚻᛠᚪᛚ.ᚠᛚ-ᚩᛋᚾ-ᚫᛞᛋᛁᛞᚹᚾᚪᚪ-ᚱᛟᚻ-ᛚᚠᛚᚳᛟᚱ-ᚣᛏ-ᚹᛏᛝᚣ-ᚳᚩ-ᛄᚷᛟ--ᚫᚻᚦᛠ-ᛒᛠᛁ-ᛁᚩᛡ-ᛗᛉᚠᚷᛁ-ᚣᚣᛋᛇᛗᛠᚹ.ᛇᚪ-ᛇᛉᛡ-ᛄᚾᛇᛁᛇᚫ-ᛋᚱᚹ-ᛝᚣᚦ-ᛠᛁᛄᛚᚢᛄ-ᚻᛇᛚᛟ-ᛒᛠᛒᛚ-ᚩᛈᛈ-ᚢᚻᛚ-ᛡᚾᛚ-ᛒᚦᚱᚠᚦᚫ-ᛞᚳ-ᛄᚳᚷ-ᚹᚫ-ᚱᛉᚣᛖᚱ.ᛒᛝᚹ-ᛟᚳᚫᚹᛈᚢ-ᚱᛋᛒ-ᚷᚦᚳᛏᛏᛠᚹ-ᚱᚣᛞ-ᚣᛠᛄ-ᛋ-qᚪᛚᚾᛄᚪ-ᛇᚻᛖ-ᛏᛠᛈ-ᛝᛉᚾᚳ-ᛋᚾᚹᚦᚾ-ᚣᛞᛝᚣ-ᛠᛠᛡ-ᛉᛁᛚᚢᚩ.ᛗᛉᚦ-ᛒᛝᛇᛠᛟ-ᛁᛟᛏ-ᛠᛏᛄ-ᚫᚳᛉᛝᛖᚠ-ᛇᚠ.ᛄᛄᛝᛟᛡᛟ-ᛠᛖᚫ-ᚦᛏᛠᛗ-ᛁᛏᚩᛒᛡ-ᛝᛟ-ᛉᚠᛇᚷᛗᛠ-ᚠᛖ-ᚳᛖᛖᚾᛠᛁᚪᛟ-ᛉᚣ-ᚢᛁ.ᛒᛏ.ᛒᛠ-ᛠᛁᚢᛗ-ᛞᛟᛋᛠᚷᚠᛇᚫ-ᛏᚪ-ᛇᚦ-ᛒᚪᛟᚩᛗ.ᛟᚳᛇ-ᛞᛞ-ᛋᚱᛁᛋᚦ-ᛇᛒ-ᚳᛒᛟ-ᚳᛟᚳᚷᛇ.ᛗᛉᚦ-ᛞᚦᛉᛈᛚᛈᛚᛁᚢ-ᚳᛞᛡᛝᚻᚷ-ᛞᚪ-ᚳᛟᚳᛁᛟᛞ-') # orig = LP.RuneText('ᛇᚦ-ᛒᛏᚣᚳ-ᛇᛚᛉ-ᛄᛚᚦᚪ-ᛋᚱᛉᚦ-ᚦᛄᚻ-ᛉᛗ-ᛏᛞᛋ-ᚣᚾ-ᚣᛟᛇᛈᛟᛚ-ᛈᚹᛚᚪᛗ-ᚪᛉᛁᛇᛝᚢᚱᛉ.ᛞᛄ-ᚻᛠᚪᛚ.ᚠᛚ-ᚩᛋᚾ-ᚫᛞᛋᛁᛞᚹᚾᚪᚪ-ᚱᛟᚻ-ᛚᚠᛚᚳᛟᚱ-ᚣᛏ-ᚹᛏᛝᚣ-ᚳᚩ-ᛄᚷᛟ--ᚫᚻᚦᛠ-ᛒᛠᛁ-ᛁᚩᛡ-ᛗᛉᚠᚷᛁ-ᚣᚣᛋᛇᛗᛠᚹ.ᛇᚪ-ᛇᛉᛡ-ᛄᚾᛇᛁᛇᚫ-ᛋᚱᚹ-ᛝᚣᚦ-ᛠᛁᛄᛚᚢᛄ-ᚻᛇᛚᛟ-ᛒᛠᛒᛚ-ᚩᛈᛈ-ᚢᚻᛚ-ᛡᚾᛚ-ᛒᚦᚱᚠᚦᚫ-ᛞᚳ-ᛄᚳᚷ-ᚹᚫ-ᚱᛉᚣᛖᚱ.ᛒᛝᚹ-ᛟᚳᚫᚹᛈᚢ-ᚱᛋᛒ-ᚷᚦᚳᛏᛏᛠᚹ-ᚱᚣᛞ-ᚣᛠᛄ-ᛋ-qᚪᛚᚾᛄᚪ-ᛇᚻᛖ-ᛏᛠᛈ-ᛝᛉᚾᚳ-ᛋᚾᚹᚦᚾ-ᚣᛞᛝᚣ-ᛠᛠᛡ-ᛉᛁᛚᚢᚩ.ᛗᛉᚦ-ᛒᛝᛇᛠᛟ-ᛁᛟᛏ-ᛠᛏᛄ-ᚫᚳᛉᛝᛖᚠ-ᛇᚠ.ᛄᛄᛝᛟᛡᛟ-ᛠᛖᚫ-ᚦᛏᛠᛗ-ᛁᛏᚩᛒᛡ-ᛝᛟ-ᛉᚠᛇᚷᛗᛠ-ᚠᛖ-ᚳᛖᛖᚾᛠᛁᚪᛟ-ᛉᚣ-ᚢᛁ.ᛒᛏ.ᛒᛠ-ᛠᛁᚢᛗ-ᛞᛟᛋᛠᚷᚠᛇᚫ-ᛏᚪ-ᛇᚦ-ᛒᚪᛟᚩᛗ.ᛟᚳᛇ-ᛞᛞ-ᛋᚱᛁᛋᚦ-ᛇᛒ-ᚳᛒᛟ-ᚳᛟᚳᚷᛇ.ᛗᛉᚦ-ᛞᚦᛉᛈᛚᛈᛚᛁᚢ-ᚳᛞᛡᛝᚻᚷ-ᛞᚪ-ᚳᛟᚳᛁᛟᛞ-')
data = orig.index data = orig.index
if False: # longest uninterrupted text 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] data = data[pos:pos + lg]
else: # from the beginning else: # from the beginning
data = data[:170] data = data[:170]
@@ -83,7 +76,7 @@ def pattern_solver(fname, irp=0):
data = [x for x in data if x != 29] data = [x for x in data if x != 29]
def fn_similarity(x): def fn_similarity(x):
return Probability(x).similarity() return LP.Probability(x).similarity()
def fn_pattern_mirror(x, kl): def fn_pattern_mirror(x, kl):
for i in range(10000): # mirrored, 012210012210 or 012101210 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] yield from x[::-1][1:-1]
print(fname) print(fname)
gr = GuessPattern(data) gr = LP.GuessPattern(data)
for kl in range(3, 19): for kl in range(3, 19):
# for pattern_shift in range(1): # for pattern_shift in range(1):
# fn_pattern = fn_pattern_mirror # fn_pattern = fn_pattern_mirror
@@ -106,29 +99,29 @@ def pattern_solver(fname, irp=0):
# Find proper pattern # Find proper pattern
res = [] res = []
for offset in range(kl): # up to keylen offset 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) 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: if score > 1.6 and score < 2.1:
res.append((score, parts, offset)) res.append((score, parts, offset))
# Find best matching key for pattern # Find best matching key for pattern
for score, parts, off in res: 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: if sc < 0.1:
fmt = 'kl: {}, pattern-n: {}, IoC: {:.3f}, dist: {:.4f}, offset: {}, key: {}' fmt = 'kl: {}, pattern-n: {}, IoC: {:.3f}, dist: {:.4f}, offset: {}, key: {}'
print(fmt.format(kl, pattern_shift, score, sc, off, print(fmt.format(kl, pattern_shift, score, sc, off,
RuneText(solution).text)) LP.RuneText(solution).text))
solved = gr.zip(fn_pattern(solution, kl), off) solved = gr.zip(fn_pattern(solution, kl), off)
for i in data_i: for i in data_i:
solved.insert(i, 29) solved.insert(i, 29)
print(' ', RuneText(solved).text) print(' ', LP.RuneText(solved).text)
######################################### #########################################
# main # main
######################################### #########################################
db = InterruptDB.load('db_norm') db = LP.InterruptDB.load('db_norm')
# IOC_MIN_SCORE = 1.4 # for db_high # IOC_MIN_SCORE = 1.4 # for db_high
IOC_MIN_SCORE = 0.55 # for db_norm IOC_MIN_SCORE = 0.55 # for db_norm
@@ -166,5 +159,5 @@ for fname in [
maxscore = max(x[1] for x in db[fname]) maxscore = max(x[1] for x in db[fname])
print('No candidates. Highest score is only', maxscore) print('No candidates. Highest score is only', maxscore)
continue continue
break_cipher(fname, candidates, AffineSolver, GuessAffine) break_cipher(fname, candidates, LP.AffineSolver, LP.GuessAffine)
break_cipher(fname, candidates, VigenereSolver, GuessVigenere) break_cipher(fname, candidates, LP.VigenereSolver, LP.GuessVigenere)

159
solver.py
View File

@@ -1,22 +1,20 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from RuneSolver import VigenereSolver, SequenceSolver import LP
from RuneText import Rune, RuneText
from lib import elliptic_curve
import sys import sys
import itertools import itertools
def load_sequence_file(fname): 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()] return [int(x) for x in f.readlines()]
PRIMES = load_sequence_file('data/seq_primes_1000.txt') PRIMES = load_sequence_file('seq_primes_1000')
PRIMES_3301 = load_sequence_file('data/seq_primes_3301.txt') PRIMES_3301 = load_sequence_file('seq_primes_3301')
NOT_PRIMES = load_sequence_file('data/seq_not_primes.txt') NOT_PRIMES = load_sequence_file('seq_not_primes')
FIBONACCI = load_sequence_file('data/seq_fibonacci.txt') FIBONACCI = load_sequence_file('seq_fibonacci')
LUCAS = load_sequence_file('data/seq_lucas_numbers.txt') LUCAS = load_sequence_file('seq_lucas_numbers')
MOEBIUS = load_sequence_file('data/seq_moebius.txt') MOEBIUS = load_sequence_file('seq_moebius')
def print_all_solved(): def print_all_solved():
@@ -46,13 +44,13 @@ def print_all_solved():
slvr.INTERRUPT = '' slvr.INTERRUPT = ''
slvr.INTERRUPT_POS = [4] slvr.INTERRUPT_POS = [4]
def solve(path, fn_solution, solver=VigenereSolver): def solve(fname, fn_solution, solver=LP.VigenereSolver):
slvr = solver() slvr = solver()
slvr.output.COLORS = False slvr.output.COLORS = False
slvr.output.QUIET = True # or use -v/-q while calling 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) fn_solution(slvr)
print(f'pages/{path}.txt') print(f'pages/{fname}.txt')
print() print()
slvr.run() slvr.run()
print() print()
@@ -64,78 +62,62 @@ def print_all_solved():
solve('0_loss_of_divinity', plain) solve('0_loss_of_divinity', plain)
solve('jpg107-167', solution_jpg107_167) solve('jpg107-167', solution_jpg107_167)
solve('jpg229', plain) 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) solve('p57_parable', plain)
def play_around(): def play_around():
slvr = VigenereSolver() slvr = LP.VigenereSolver()
slvr.output.COLORS = False slvr.output.COLORS = False
slvr.output.QUIET = True slvr.output.QUIET = True
slvr.KEY_DATA = [] slvr.KEY_DATA = []
vowels = 'ᚢᚩᛁᛇᛖᛟᚪᚫᛡᛠ' vowels = 'ᚢᚩᛁᛇᛖᛟᚪᚫᛡᛠ'
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:
slvr.input.load(file=f'pages/p{uuu}.txt') slvr.input.load(file=LP.path.page(uuu))
print(uuu) print(uuu)
print('word count:', sum(len(x) for x in slvr.input.words.values())) 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()] 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)] 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] c = [int(''.join(str(y) for y in x), 2) for x in b]
# print('-'.join(str(x) for x in c)) # 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)) # print(''.join('ABCDEFGHIJKLMNOPQRSTUVWXYZ___...'[x] for x in c))
# slvr.run() # slvr.run()
def try_totient_on_unsolved(): def try_totient_on_unsolved():
slvr = SequenceSolver() slvr = LP.SequenceSolver()
slvr.output.QUIET = True slvr.output.QUIET = True
slvr.output.BREAK_MODE = '' # disable line breaks slvr.output.BREAK_MODE = '' # disable line breaks
# slvr.INTERRUPT = 'ᛝ' # slvr.INTERRUPT = 'ᛝ'
# slvr.INTERRUPT_POS = [1] # slvr.INTERRUPT_POS = [1]
# for uuu in ['15-22']: # 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()
print(uuu) print(uuu)
with open(f'pages/p{uuu}.txt', 'r') as f: slvr.input.load(file=LP.path.page(uuu), limit=25)
slvr.input.load(RuneText(f.read()[:15])) # alldata = slvr.input.runes_no_whitespace() + [LP.Rune(i=29)]
# alldata = slvr.input.runes_no_whitespace() + [Rune(i=29)]
def b60(x):
v = x % 60
return v if v < 29 else 60 - v
def ec(r, i): 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: if p1 is None:
return r.index return r.index
return r.index + p1 % 29 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): 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: r - PRIMES[i] + z
# slvr.FN = lambda i, r: Rune(i=b60(r.prime) + z % 29) # slvr.FN = lambda i, r: LP.Rune(i=((r.prime + alldata[i + 1].prime) + z) % 60 // 2)
# slvr.FN = lambda i, r: 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: Rune(i=(3301 * r.index + z) % 29) # slvr.FN = lambda i, r: LP.Rune(i=(r.prime ** i + z) % 29)
slvr.FN = lambda i, r: Rune(i=(ec(r, i) + z) % 29) slvr.FN = lambda i, r: LP.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.run() 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): def trim_orig_oeis(minlen=15, trim=40):
# download and unzip: https://oeis.org/stripped.gz # download and unzip: https://oeis.org/stripped.gz
with open('data/oeis_orig.txt', 'r') as f_in: with open(LP.path.data('oeis_orig'), 'r') as f_in:
with open('data/oeis.txt', 'w') as f_out: with open(LP.path.data('oeis'), 'w') as f_out:
for line in f_in.readlines(): for line in f_in.readlines():
if line[0] == '#': if line[0] == '#':
continue continue
@@ -146,64 +128,79 @@ def find_oeis(irp=0, invert=False, offset=0):
f_out.write(name + ',' + ','.join(vals) + '\n') f_out.write(name + ',' + ','.join(vals) + '\n')
# trim_orig_oeis() # create db if not present already # 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 = [] seqs = []
for line in f.readlines(): for line in f.readlines():
name, *vals = line.split(',') vals = line.split(',')
vals = [int(x) for x in vals] seqs.append((vals[0], list(map(int, vals[1:]))))
seqs.append([name] + vals)
RUNES = 'ᚠᚢᚦᚩᚱᚳᚷᚹᚻᚾᛁᛄᛇᛈᛉᛋᛏᛒᛖᛗᛚᛝᛟᛞᚪᚫᚣᛡᛠ'
words = [set()] * 13 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 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()) words[i] = set(x.strip() for x in f.readlines())
for uuu, wlen in { for uuu, wlen in {
'0-2': [8, 5, 4, 3, 3, 11, 5, 4, 3, 3], 'p0-2': [8, 5, 4, 3, 3, 11, 5, 4, 3, 3],
'3-7': [2, 11, 3, 4, 7, 7, 7, 4, 6], 'p3-7': [2, 11, 3, 4, 7, 7, 7, 4, 6],
'8-14': [4, 8, 3, 2, 3, 9, 4, 3, 4, 2, 2], 'p8-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], 'p15-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], 'p23-26': [2, 6, 3, 4, 8, 3, 3, 7, 5, 5],
'27-32': [3, 12, 4, 7, 2, 3, 3, 2, 1, 3, 4], 'p27-32': [3, 12, 4, 7, 2, 3, 3, 2, 1, 3, 4],
'33-39': [2, 8, 2, 9, 6, 3, 3, 5, 3, 2], 'p33-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], 'p40-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], 'p54-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], # 'p56_an_end': [2, 3, 5, 2, 4, 3, 4, 6, 1, 4, 3, 6, 2],
}.items(): }.items():
minwords = sum(wlen[:2]) # must match at least n words
splits = [(0, 0, 0)] splits = [(0, 0, 0)]
for x in wlen: for x in wlen:
splits.append((splits[-1][1], splits[-1][1] + x)) splits.append((splits[-1][1], splits[-1][1] + x))
splits = splits[1:] splits = splits[1:]
print() print()
print(uuu) print(uuu)
with open(f'pages/p{uuu}.txt', 'r') as f: with open(LP.path.page(uuu), 'r') as f:
data = RuneText(f.read()[:120]).index_no_whitespace data = LP.RuneText(f.read()[:120]).index_rune_only
irps = [i for i, x in enumerate(data[:splits[-1][1]]) if x == irp] 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: if invert:
data = [28 - x for x in data] data = [28 - x for x in data]
for oeis, *v in seqs: # 390k min_len = sum(wlen[:2]) # must match at least n words
v = v[offset:] data_len = len(data)
if len(v) < minwords: for oeis, vals in seqs: # 390k
vals = vals[offset:]
if len(vals) < min_len:
continue 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 i in range(len(cases) + 1):
for comb in itertools.combinations(cases, i): # 2^3 for comb in itertools.combinations(cases, i): # 2^3
res = v[:] res = vals[:]
for z in comb: for z in comb:
res.insert(z, -1) # insert interrupts res.insert(z, -1) # insert interrupts
shortest = min(data_len, len(res))
for s in range(29): for s in range(29):
who = ''.join(RUNES[x if y == -1 else (x - y - s) % 29] failed = 0
for x, y in zip(data, res)) full = []
active = [x for x in splits if x[1] <= len(who)] clen = 0
bag = [who[x:y] for x, y in active] for a, b in splits:
if all(w in words[len(w)] for w in bag): if b > shortest:
print(oeis.split()[0], 'shift:', s, 'irps:', comb) break
print(' ', ' '.join(RuneText(w).text for w in bag)) 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 if '-s' in sys.argv: # print [s]olved
@@ -213,4 +210,4 @@ else:
# try_totient_on_unsolved() # try_totient_on_unsolved()
# for i in range(0, 4): # for i in range(0, 4):
# print('offset:', i) # 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#