making substitution search easier

This commit is contained in:
relikd
2021-01-25 22:05:40 +01:00
parent 0d6b4a1a16
commit 3ab77da4a8
4 changed files with 88 additions and 33 deletions

View File

@@ -249,7 +249,7 @@ If the output is too long, you can limit (the already loaded data) with `xl 180`
`Rune.kind` can be one of `r n s l w` meaning (r)une, (n)umber, (s)entence, (l)ine, or (w)hitespace. A line is what you see in the source file (which is equivalent to a line in the original jpg page). A sentence is one that ends with a period (`⁘`).
`Rune` as well as `RuneText` both support simple arithmetic operations: `Rune(i=2) - 2` will yield a `ᚠ` rune. For example, you can invert a text with `28 - RuneText('test me')`.
`Rune` as well as `RuneText` both support simple arithmetic operations: `Rune(i=2) - 2` will yield a `ᚠ` rune. For example, you can invert a text with `28 - RuneText('test me')` or simply `~RuneText('inverted')`.
__Note:__ Always initialize a rune with its rune character or its index, never ASCII or its prime value.
@@ -313,7 +313,7 @@ As an optimization, smaller look ahead levels are tried first. E.g., if you spec
The complexity is not linear and depends on whether “there was just another better solution”. With the default look ahead of 3, which can flip 3 bits simultaneously, each step performs 66!/(3!(66-3)!) + 66!/(2!(66-2)!) + 66 operations or __4.8*10^4__. Usually it takes no more than 23 steps.
### InterruptDB.py
Calculating the best interrupt position takes quite long, so we can optimize our program by pre-calculating the IoC's. That is what `InterruptDB.py` is for. The class will search for the best interrupts and store the IoC score as well as the set of interrupts in a file. Later queries just need to process this file instead.

View File

@@ -20,6 +20,12 @@ class RuneSolver(RuneRunner):
def highlight_interrupt(self):
return self.highlight_rune(self.INTERRUPT, self.INTERRUPT_POS)
def substitute_get(self, pos, keylen, search_term, found_term):
return found_term.zip_sub(search_term).description(count=True)
def substitute_supports_keylen(self):
return False
def run(self, data=None):
if data:
self.input.load(data=data)
@@ -120,7 +126,9 @@ class RunningKeySolver(RuneSolver):
if self.KEY_INVERT:
r_idx = 28 - r_idx
pos = self.active_key_pos()
if pos != -1:
if pos == -1:
self.copy_unmodified(r_idx)
else:
i = (pos + self.KEY_SHIFT) % self.k_len
r_idx = (self.decrypt(r_idx, i) - self.KEY_ROTATE) % 29
# rotate_key
@@ -128,8 +136,11 @@ class RunningKeySolver(RuneSolver):
self.k_current_pos = (self.k_current_pos + 1) % self.k_full_len
return Rune(i=r_idx)
def decrypt(self, rune_index, key_index):
raise NotImplementedError # must subclass
def decrypt(self, rune_index, key_index): # must subclass
raise NotImplementedError
def copy_unmodified(self, rune_index): # subclass if needed
pass
def key__str__(self):
return self.KEY_DATA # you should override this
@@ -156,6 +167,15 @@ class VigenereSolver(RunningKeySolver):
def decrypt(self, rune_index, key_index):
return rune_index - self.KEY_DATA[key_index]
def substitute_supports_keylen(self):
return True
def substitute_get(self, pos, keylen, search_term, found_term):
ret = [Rune(r='')] * keylen
for i, r in enumerate(found_term.zip_sub(search_term)):
ret[(pos + i) % keylen] = r
return RuneText(ret).description(count=True, index=False)
def key__str__(self):
return self.key__str__basic_runes()
@@ -175,13 +195,35 @@ class AffineSolver(RunningKeySolver):
class AutokeySolver(RunningKeySolver):
def run(self, data=None):
self.running_key = self.KEY_DATA[:]
key = self.KEY_DATA[self.KEY_SHIFT:] + self.KEY_DATA[:self.KEY_SHIFT]
key = [29] * self.KEY_OFFSET + key + [29] * self.KEY_POST_PAD
self.running_key = key
super().run(data=data)
def decrypt(self, rune_index, key_index):
def decrypt(self, rune_index, _):
rune_index = (rune_index - self.running_key.pop(0)) % 29
self.running_key.append(rune_index)
return rune_index
def copy_unmodified(self, rune_index):
if self.k_len > 0:
self.running_key.pop(0)
self.running_key.append(rune_index)
def substitute_supports_keylen(self):
return True
def substitute_get(self, pos, keylen, search_term, found_term):
data = self.input.runes_no_whitespace()
ret = [Rune(r='')] * keylen
for o in range(len(search_term)):
plain = search_term[o]
i = pos + o
while i >= 0:
plain = data[i] - plain
i -= keylen
ret[i + keylen] = plain
return RuneText(ret).description(count=True, index=False)
def key__str__(self):
return self.key__str__basic_runes()

View File

@@ -101,6 +101,9 @@ class Rune(object):
def __rsub__(self, o):
return self if self.index == 29 else Rune(i=(o - self.index) % 29)
def __invert__(self):
return self if self.index == 29 else Rune(i=28 - self.index)
#########################################
# RuneText : Stores multiple Rune objects. Allows arithmetic operations
@@ -196,6 +199,11 @@ class RuneText(object):
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):
if len(self) != len(other):
raise IndexError('RuneText length mismatch')
return RuneText([x - y for x, y in zip(self._data, other._data)])
def __getitem__(self, key):
if isinstance(key, str):
return [getattr(x, key) for x in self._data]
@@ -206,20 +214,10 @@ class RuneText(object):
self._data[key] = value
def __add__(self, other):
if isinstance(other, RuneText):
if len(self) != len(other):
raise IndexError('RuneText length mismatch')
return RuneText([x + y for x, y in zip(self._data, other._data)])
else:
return RuneText([x + other for x in self._data])
return RuneText([x + other for x in self._data])
def __sub__(self, other):
if isinstance(other, RuneText):
if len(self) != len(other):
raise IndexError('RuneText length mismatch')
return RuneText([x - y for x, y in zip(self._data, other._data)])
else:
return RuneText([x - other for x in self._data])
return RuneText([x - other for x in self._data])
def __radd__(self, other):
return RuneText([other + x for x in self._data])
@@ -227,5 +225,8 @@ class RuneText(object):
def __rsub__(self, other):
return RuneText([other - x for x in self._data])
def __invert__(self):
return RuneText([~x for x in self._data])
def __repr__(self):
return f'RuneText<{len(self._data)}>'

View File

@@ -85,8 +85,8 @@ def command_a(cmd, args): # [a]ll variations
root = RuneText(args)
inclIndex = 'q' not in cmd
if 'i' in cmd:
root = 28 - root
for i in range(0, 29):
root = ~root
for i in range(29):
print('{:02d}: {}'.format(i, (root + i).description(index=inclIndex)))
@@ -110,7 +110,7 @@ def command_d(cmd, args): # [d]ecrypt or single substitution
print('Error: key length mismatch')
else:
print('Substition:')
print((enc - plain).description())
print((enc.zip_sub(plain)).description())
#########################################
@@ -134,13 +134,26 @@ def command_f(cmd, args): # (f)ind word
print()
print('Found:')
for _, _, pos, _, w in cur_words:
print('{:04}: {}'.format(pos, w.description(count=True)))
print(f'{pos:04}: {w.description(count=True)}')
if search_term:
print()
keylen = [len(search_term)]
if SOLVER.substitute_supports_keylen():
try:
inp = input('What is the key length? (num or [a]ll): ').strip()
if inp:
if inp[0] == 'a':
keylen = range(len(search_term), 24)
else:
keylen = [int(inp)]
except ValueError:
raise ValueError('not a number.')
print()
print('Available substition:')
for _, _, pos, _, w in cur_words:
word = search_term - w
print('{:04}: {}'.format(pos, word.description(count=True)))
for kl in keylen:
res = SOLVER.substitute_get(pos, kl, search_term, w)
print(f'{pos:04}: {res}')
#########################################
@@ -253,17 +266,16 @@ def command_t(cmd, args): # (t)ranslate
if cmd != 't':
return False
word = RuneText(args)
rev_word = 28 - word
word = word.as_dict()
psum = sum(word['p'])
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(word['r']), word['r']))
print('plain({}): {}'.format(len(word['t']), word['t']))
print('reversed: {}'.format(''.join([x.rune for x in rev_word])))
print('indices: {}'.format(word['i']))
print('prime({}{}): {}'.format(psum, sffx, word['p']))
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']))
#########################################