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.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. __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. 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 ### 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. 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): def highlight_interrupt(self):
return self.highlight_rune(self.INTERRUPT, self.INTERRUPT_POS) 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): def run(self, data=None):
if data: if data:
self.input.load(data=data) self.input.load(data=data)
@@ -120,7 +126,9 @@ class RunningKeySolver(RuneSolver):
if self.KEY_INVERT: if self.KEY_INVERT:
r_idx = 28 - r_idx r_idx = 28 - r_idx
pos = self.active_key_pos() 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 i = (pos + self.KEY_SHIFT) % self.k_len
r_idx = (self.decrypt(r_idx, i) - self.KEY_ROTATE) % 29 r_idx = (self.decrypt(r_idx, i) - self.KEY_ROTATE) % 29
# rotate_key # rotate_key
@@ -128,8 +136,11 @@ class RunningKeySolver(RuneSolver):
self.k_current_pos = (self.k_current_pos + 1) % self.k_full_len self.k_current_pos = (self.k_current_pos + 1) % self.k_full_len
return Rune(i=r_idx) return Rune(i=r_idx)
def decrypt(self, rune_index, key_index): def decrypt(self, rune_index, key_index): # must subclass
raise NotImplementedError # must subclass raise NotImplementedError
def copy_unmodified(self, rune_index): # subclass if needed
pass
def key__str__(self): def key__str__(self):
return self.KEY_DATA # you should override this return self.KEY_DATA # you should override this
@@ -156,6 +167,15 @@ class VigenereSolver(RunningKeySolver):
def decrypt(self, rune_index, key_index): def decrypt(self, rune_index, key_index):
return rune_index - self.KEY_DATA[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): def key__str__(self):
return self.key__str__basic_runes() return self.key__str__basic_runes()
@@ -175,13 +195,35 @@ class AffineSolver(RunningKeySolver):
class AutokeySolver(RunningKeySolver): class AutokeySolver(RunningKeySolver):
def run(self, data=None): 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) 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 rune_index = (rune_index - self.running_key.pop(0)) % 29
self.running_key.append(rune_index) self.running_key.append(rune_index)
return 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): def key__str__(self):
return self.key__str__basic_runes() return self.key__str__basic_runes()

View File

@@ -101,6 +101,9 @@ class Rune(object):
def __rsub__(self, o): def __rsub__(self, o):
return self if self.index == 29 else Rune(i=(o - self.index) % 29) 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 # 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] 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 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): 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]
@@ -206,20 +214,10 @@ class RuneText(object):
self._data[key] = value self._data[key] = value
def __add__(self, other): def __add__(self, other):
if isinstance(other, RuneText): return RuneText([x + other for x in self._data])
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])
def __sub__(self, other): def __sub__(self, other):
if isinstance(other, RuneText): return RuneText([x - other for x in self._data])
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])
def __radd__(self, other): def __radd__(self, other):
return RuneText([other + x for x in self._data]) return RuneText([other + x for x in self._data])
@@ -227,5 +225,8 @@ class RuneText(object):
def __rsub__(self, other): def __rsub__(self, other):
return RuneText([other - x for x in self._data]) return RuneText([other - x for x in self._data])
def __invert__(self):
return RuneText([~x for x in self._data])
def __repr__(self): def __repr__(self):
return f'RuneText<{len(self._data)}>' return f'RuneText<{len(self._data)}>'

View File

@@ -85,8 +85,8 @@ def command_a(cmd, args): # [a]ll variations
root = RuneText(args) root = RuneText(args)
inclIndex = 'q' not in cmd inclIndex = 'q' not in cmd
if 'i' in cmd: if 'i' in cmd:
root = 28 - root root = ~root
for i in range(0, 29): for i in range(29):
print('{:02d}: {}'.format(i, (root + i).description(index=inclIndex))) 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') print('Error: key length mismatch')
else: else:
print('Substition:') 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()
print('Found:') print('Found:')
for _, _, pos, _, w in cur_words: 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: if search_term:
print() 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:') print('Available substition:')
for _, _, pos, _, w in cur_words: for _, _, pos, _, w in cur_words:
word = search_term - w for kl in keylen:
print('{:04}: {}'.format(pos, word.description(count=True))) 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': if cmd != 't':
return False return False
word = RuneText(args) word = RuneText(args)
rev_word = 28 - word x = word.as_dict()
word = word.as_dict() psum = sum(x['p'])
psum = sum(word['p'])
sffx = '*' if LIB.is_prime(psum) else '' sffx = '*' if LIB.is_prime(psum) else ''
if LIB.is_prime(LIB.rev(psum)): if LIB.is_prime(LIB.rev(psum)):
sffx += '' sffx += ''
print('runes({}): {}'.format(len(word['r']), word['r'])) print('runes({}): {}'.format(len(x['r']), x['r']))
print('plain({}): {}'.format(len(word['t']), word['t'])) print('plain({}): {}'.format(len(x['t']), x['t']))
print('reversed: {}'.format(''.join([x.rune for x in rev_word]))) print('reversed: {}'.format(''.join([x.rune for x in ~word])))
print('indices: {}'.format(word['i'])) print('indices: {}'.format(x['i']))
print('prime({}{}): {}'.format(psum, sffx, word['p'])) print('prime({}{}): {}'.format(psum, sffx, x['p']))
######################################### #########################################