Testing done for now.
authorNeil Smith <neil.git@njae.me.uk>
Sat, 14 Nov 2020 16:52:48 +0000 (16:52 +0000)
committerNeil Smith <neil.git@njae.me.uk>
Sat, 14 Nov 2020 16:52:48 +0000 (16:52 +0000)
setup.py
szyfrow/cadenus.py
szyfrow/hill.py [deleted file]
szyfrow/playfair.py
szyfrow/polybius.py
tests/test_cadenus.py
tests/test_playfair.py [new file with mode: 0644]
tests/test_pocket_enigma.py [new file with mode: 0644]
tests/test_polybius.py [new file with mode: 0644]

index b8f910b2ef5714407549f96f7de04119ae8d98db..e585ed4d2e5b6e898f43e5dc11165a27d793bbc6 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -23,7 +23,7 @@ setuptools.setup(
         "Topic :: Security :: Cryptography",
     ],
     python_requires='>=3.7',
-    install_requires=['numpy'],
+    install_requires=[],
     include_package_data=True,
     setup_requires=['pytest-runner', 'numpy'],
     tests_require=['pytest'],
index b56500c6de1f01df5d602664b04731cb5008429d..b0a013858034bd784de5f3487525e5eef1b93db8 100644 (file)
@@ -62,13 +62,17 @@ def cadenus_encipher(message, keyword, keycolumn, fillvalue='a'):
                 make_cadenus_keycolumn(doubled_letters='vw', start='a', reverse=True))
     'systretomtattlusoatleeesfiyheasdfnmschbhneuvsnpmtofarenuseieeieltarlmentieetogevesitfaisltngeeuvowul'
     """
-    rows = chunks(message, len(message) // 25, fillvalue=fillvalue)
-    columns = zip(*rows)
-    rotated_columns = [col[start:] + col[:start] for start, col in zip([keycolumn[l] for l in keyword], columns)]    
-    rotated_rows = zip(*rotated_columns)
     transpositions = transpositions_of(keyword)
-    transposed = [transpose(r, transpositions) for r in rotated_rows]
-    return cat(chain(*transposed))
+    enciphered_chunks = []
+    for message_chunk in chunks(message, len(transpositions) * 25, 
+                                fillvalue=fillvalue):
+        rows = chunks(message_chunk, len(transpositions), fillvalue=fillvalue)
+        columns = zip(*rows)
+        rotated_columns = [col[start:] + col[:start] for start, col in zip([keycolumn[l] for l in keyword], columns)]    
+        rotated_rows = zip(*rotated_columns)
+        transposed = [transpose(r, transpositions) for r in rotated_rows]
+        enciphered_chunks.append(cat(chain(*transposed)))
+    return cat(enciphered_chunks)
 
 def cadenus_decipher(message, keyword, keycolumn, fillvalue='a'):
     """
@@ -83,28 +87,32 @@ def cadenus_decipher(message, keyword, keycolumn, fillvalue='a'):
                  make_cadenus_keycolumn(reverse=True))
     'aseverelimitationontheusefulnessofthecadenusisthateverymessagemustbeamultipleoftwentyfiveletterslong'
     """
-    rows = chunks(message, len(message) // 25, fillvalue=fillvalue)
     transpositions = transpositions_of(keyword)
-    untransposed_rows = [untranspose(r, transpositions) for r in rows]
-    columns = zip(*untransposed_rows)
-    rotated_columns = [col[-start:] + col[:-start] for start, col in zip([keycolumn[l] for l in keyword], columns)]    
-    rotated_rows = zip(*rotated_columns)
-    # return rotated_columns
-    return cat(chain(*rotated_rows))
+    deciphered_chunks = []
+    for message_chunk in chunks(message, len(transpositions) * 25, 
+                                fillvalue=fillvalue):
+        rows = chunks(message_chunk, len(transpositions), fillvalue=fillvalue)
+        untransposed_rows = [untranspose(r, transpositions) for r in rows]
+        columns = zip(*untransposed_rows)
+        rotated_columns = [col[-start:] + col[:-start] for start, col in zip([keycolumn[l] for l in keyword], columns)]    
+        rotated_rows = zip(*rotated_columns)
+        deciphered_chunks.append(cat(chain(*rotated_rows)))
+    return cat(deciphered_chunks)
+    
 
 
-def cadenus_break(message, words=keywords, 
+def cadenus_break(message, wordlist=keywords, 
     doubled_letters='vw', fitness=Pbigrams):
-    c = make_cadenus_keycolumn(reverse=True)
-    valid_words = [w for w in words 
-        if len(transpositions_of(w)) == len(message) // 25]
+    c = make_cadenus_keycolumn(reverse=True)
+    # valid_words = [w for w in wordlist
+        if len(transpositions_of(w)) == len(message) // 25]
     with multiprocessing.Pool() as pool:
         results = pool.starmap(cadenus_break_worker, 
                 [(message, w, 
                     make_cadenus_keycolumn(doubled_letters=doubled_letters, 
                         start=s, reverse=r), 
                     fitness)
-                for w in words 
+                for w in wordlist 
                 for s in string.ascii_lowercase 
                 for r in [True, False]
                 # if max(transpositions_of(w)) <= len(
diff --git a/szyfrow/hill.py b/szyfrow/hill.py
deleted file mode 100644 (file)
index 4f5baef..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-import multiprocessing
-import numpy as np
-from numpy import matrix
-from numpy import linalg
-from szyfrow.support.utilities import *
-from szyfrow.support.language_models import *
-from szyfrow.affine import modular_division_table
-
-
-def hill_encipher(matrix, message_letters, fillvalue='a'):
-    """Hill cipher
-
-    >>> hill_encipher(np.matrix([[7,8], [11,11]]), 'hellothere')
-    'drjiqzdrvx'
-    >>> hill_encipher(np.matrix([[6, 24, 1], [13, 16, 10], [20, 17, 15]]), \
-        'hello there')
-    'tfjflpznvyac'
-    """
-    n = len(matrix)
-    sanitised_message = sanitise(message_letters)
-    if len(sanitised_message) % n != 0:
-        padding = fillvalue[0] * (n - len(sanitised_message) % n)
-    else:
-        padding = ''
-    message = [pos(c) for c in sanitised_message + padding]
-    message_chunks = [message[i:i+n] for i in range(0, len(message), n)]
-    # message_chunks = chunks(message, len(matrix), fillvalue=None)
-    enciphered_chunks = [((matrix * np.matrix(c).T).T).tolist()[0] 
-            for c in message_chunks]
-    return cat([unpos(round(l))
-            for l in sum(enciphered_chunks, [])])
-
-def hill_decipher(matrix, message, fillvalue='a'):
-    """Hill cipher
-
-    >>> hill_decipher(np.matrix([[7,8], [11,11]]), 'drjiqzdrvx')
-    'hellothere'
-    >>> hill_decipher(np.matrix([[6, 24, 1], [13, 16, 10], [20, 17, 15]]), \
-        'tfjflpznvyac')
-    'hellothereaa'
-    """
-    adjoint = linalg.det(matrix)*linalg.inv(matrix)
-    inverse_determinant = modular_division_table[int(round(linalg.det(matrix))) % 26, 1]
-    inverse_matrix = (inverse_determinant * adjoint) % 26
-    return hill_encipher(inverse_matrix, message, fillvalue)          
-
-def hill_break(message, matrix_size=2, fitness=Pletters, 
-    number_of_solutions=1, chunksize=500):
-
-    all_matrices = [np.matrix(list(m)) 
-        for m in itertools.product([list(r) 
-            for r in itertools.product(range(26), repeat=matrix_size)], 
-        repeat=matrix_size)]
-    valid_matrices = [m for m, d in 
-        zip(all_matrices, (int(round(linalg.det(m))) for m in all_matrices))
-                  if d != 0
-                  if d % 2 != 0
-                  if d % 13 != 0 ]
-    with multiprocessing.Pool() as pool:
-        helper_args = [(message, matrix, fitness)
-                       for matrix in valid_matrices]
-        # Gotcha: the helper function here needs to be defined at the top level
-        #   (limitation of Pool.starmap)
-        breaks = pool.starmap(hill_break_worker, helper_args, chunksize)
-        if number_of_solutions == 1:
-            return max(breaks, key=lambda k: k[1])
-        else:
-            return sorted(breaks, key=lambda k: k[1], reverse=True)[:number_of_solutions]
-
-def hill_break_worker(message, matrix, fitness):
-    plaintext = hill_decipher(matrix, message)
-    fit = fitness(plaintext)
-    return matrix, fit
-
-if __name__ == "__main__":
-    import doctest
\ No newline at end of file
index b903ec0e1646de491a7510f341fb66c9e877081f..e6939db5af2a031ca3e17216902162dbc65bd6a1 100644 (file)
@@ -85,8 +85,9 @@ def playfair_encipher(message, keyword, padding_letter='x',
     grid = polybius_grid(keyword, column_order, row_order,
                         letters_to_merge=letters_to_merge,
                         wrap_alphabet=wrap_alphabet)
-    message_bigrams = playfair_bigrams(sanitise(message), padding_letter=padding_letter, 
-                                       padding_replaces_repeat=padding_replaces_repeat)
+    message_bigrams = playfair_bigrams(
+        sanitise(message), padding_letter=padding_letter, 
+        padding_replaces_repeat=padding_replaces_repeat)
     ciphertext_bigrams = [playfair_encipher_bigram(b, grid, padding_letter=padding_letter) for b in message_bigrams]
     return cat(ciphertext_bigrams)
 
@@ -100,12 +101,13 @@ def playfair_decipher(message, keyword, padding_letter='x',
     grid = polybius_grid(keyword, column_order, row_order,
                         letters_to_merge=letters_to_merge,
                         wrap_alphabet=wrap_alphabet)
-    message_bigrams = playfair_bigrams(sanitise(message), padding_letter=padding_letter, 
-                                       padding_replaces_repeat=padding_replaces_repeat)
+    message_bigrams = playfair_bigrams(
+        sanitise(message), padding_letter=padding_letter, 
+        padding_replaces_repeat=padding_replaces_repeat)
     plaintext_bigrams = [playfair_decipher_bigram(b, grid, padding_letter=padding_letter) for b in message_bigrams]
     return cat(plaintext_bigrams)
 
-def playfair_break_mp(message, 
+def playfair_break(message, 
                       letters_to_merge=None, padding_letter='x',
                       wordlist=keywords, fitness=Pletters,
                       number_of_solutions=1, chunksize=500):
index cd78ee9f8a23445f9cc7355bac876f270b30a41f..b3ffeaba91e9fb722a9b9004abc481692887a1f3 100644 (file)
@@ -48,7 +48,7 @@ def polybius_reverse_grid(keyword, column_order, row_order, letters_to_merge=Non
 
 
 def polybius_flatten(pair, column_first):
-    """Convert a series of pairs into a single list of characters"""
+    """Convert a pair of characters into a single string."""
     if column_first:
         return str(pair[1]) + str(pair[0])
     else:
@@ -101,13 +101,15 @@ def polybius_decipher(message, keyword, column_order, row_order,
     column_index_type = type(column_order[0])
     row_index_type = type(row_order[0])
     if column_first:
-        pairs = [(column_index_type(p[1]), row_index_type(p[0])) for p in chunks(message, 2)]
+        pairs = [(column_index_type(p[1]), row_index_type(p[0])) 
+          for p in chunks(message, 2)]
     else:
-        pairs = [(row_index_type(p[0]), column_index_type(p[1])) for p in chunks(message, 2)]
+        pairs = [(row_index_type(p[0]), column_index_type(p[1])) 
+          for p in chunks(message, 2)]
     return cat(grid[p] for p in pairs if p in grid)
 
 
-def polybius_break_mp(message, column_labels, row_labels,
+def polybius_break(message, column_labels, row_labels,
                       letters_to_merge=None,
                       wordlist=keywords, fitness=Pletters,
                       number_of_solutions=1, chunksize=500):
index c7b48ec1317f822dede0d9403990ee1dbb47e038..c7325e976a541960b1038e65abd07736e618e2e3 100644 (file)
@@ -56,26 +56,60 @@ def test_decipher_message():
 
 
 def test_cadenus_break():
-    plaintext = sanitise('''It is a truth universally acknowledged, that a single man in
-             possession of a good fortune, must be in want of a wife. However
-             little known the feelings or views of such a man may be on his 
-             first entering a neighbourhood, this truth is so well fixed in
-             the minds of the surrounding families, that he is considered the
-             rightful property of some one or other of their daughters.''')
+    plaintext = sanitise('''You will rejoice to hear that no disaster has accompanied the
+commencement of an enterprise which you have regarded with such evil
+forebodings.  I arrived here yesterday, and my first task is to assure
+my dear sister of my welfare and increasing confidence in the success
+of my undertaking.
+
+I am already far north of London, and as I walk in the streets of
+Petersburgh, I feel a cold northern breeze play upon my cheeks, which
+braces my nerves and fills me with delight.  Do you understand this
+feeling?  This breeze, which has travelled from the regions towards
+which I am advancing, gives me a foretaste of those icy climes.
+Inspirited by this wind of promise, my daydreams become more fervent
+and vivid.  
+
+I try in vain to be persuaded that the pole is the seat of
+frost and desolation; it ever presents itself to my imagination as the
+region of beauty and delight.  There, Margaret, the sun is for ever
+visible, its broad disk just skirting the horizon and diffusing a
+perpetual splendour.  There—for with your leave, my sister, I will put
+some trust in preceding navigators—there snow and frost are banished;
+and, sailing over a calm sea, we may be wafted to a land surpassing in
+wonders and in beauty every region hitherto discovered on the habitable
+globe.  
+
+Its productions and features may be without example, as the
+phenomena of the heavenly bodies undoubtedly are in those undiscovered
+solitudes.  What may not be expected in a country of eternal light?  I
+may there discover the wondrous power which attracts the needle and may
+regulate a thousand celestial observations that require only this
+voyage to render their seeming eccentricities consistent for ever.  I
+shall satiate my ardent curiosity with the sight of a part of the world
+never before visited, and may tread a land never before imprinted by
+the foot of man. These are my enticements, and they are sufficient to
+conquer all fear of danger or death and to induce me to commence this
+laborious voyage with the joy a child feels when he embarks in a little
+boat, with his holiday mates, on an expedition of discovery up his
+native river. '''
+)
     expected_key = 'swashbuckling'
-    expected_score = Ptrigrams(plaintext)
+    chunk_len = len(transpositions_of(expected_key)) * 25
+    target = plaintext + pad(len(plaintext), chunk_len, 'a')
+    expected_score = Ptrigrams(target)
     expected_keycol = make_cadenus_keycolumn(doubled_letters='vw', start='a', 
         reverse=True)
 
     ciphertext = cadenus_encipher(plaintext, expected_key, expected_keycol)
     
-    dictionary = ['clearinghouse', 'computerising', 'counterclaims', 
-        'housewarmings',  'intravenously', 'liquefactions', 'somersaulting', 
-        'sportsmanlike', 'swashbuckling']
+    dictionary = ['clearinghouse', 'computerising', 'counterclaims', 
+        'housewarmings',  'intravenously', 'liquefactions', 'somersaulting', 
+        'sportsmanlike', 'swashbuckling']
 
-    dictionary = ['swashbuckling']
+    dictionary = ['swashbuckling']
 
-    (key, keycol), score = cadenus_break(ciphertext, words=dictionary, 
+    (key, keycol), score = cadenus_break(ciphertext, wordlist=dictionary, 
         fitness=Ptrigrams)
 
     assert key == expected_key
diff --git a/tests/test_playfair.py b/tests/test_playfair.py
new file mode 100644 (file)
index 0000000..2ce1264
--- /dev/null
@@ -0,0 +1,123 @@
+import pytest
+import string 
+
+from szyfrow.playfair import *
+from szyfrow.support.utilities import *
+
+def test_wrap():
+    assert playfair_wrap(3, 0, 4) == 3
+    assert playfair_wrap(0, 0, 4) == 0
+    assert playfair_wrap(4, 0, 4) == 4
+    assert playfair_wrap(7, 0, 4) == 2
+    assert playfair_wrap(-1, 0, 4) == 4
+
+def test_encipher_bigram():
+    grid = polybius_grid('playfairexample', list(range(5)), list(range(5)))
+
+    plain_pairs = 'hi de th eg ol di nt he tr ex es tu mp'.split()
+    cipher_pairs = 'bm od zb xd na be ku dm ui xm mo uv if'.split()
+
+    for p, c in zip(plain_pairs, cipher_pairs):
+        assert playfair_encipher_bigram(p, grid) == c
+
+def test_decipher_bigram():
+    grid = polybius_grid('playfairexample', list(range(5)), list(range(5)))
+
+    plain_pairs = 'hi de th eg ol di nt he tr ex es tu mp'.split()
+    cipher_pairs = 'bm od zb xd na be ku dm ui xm mo uv if'.split()
+
+    for p, c in zip(plain_pairs, cipher_pairs):
+        assert playfair_decipher_bigram(c, grid) == p
+
+def test_playfair_bigrams():
+    txt = sanitise('hide the gold in the tree stumps')
+    f_bigrams = 'hi de th eg ol di nt he tr ex es tu mp sx'.split()
+    t_bigrams = 'hi de th eg ol di nt he tr ex st um ps'.split()
+    assert playfair_bigrams(txt, padding_replaces_repeat=True) == t_bigrams
+    assert playfair_bigrams(txt, padding_replaces_repeat=False) == f_bigrams
+
+
+
+def test_encipher_message():
+    ct = playfair_encipher('hide the gold in the tree stump', 
+        'playfairexample', wrap_alphabet=KeywordWrapAlphabet.from_a)
+    assert ct == 'bmodzbxdnabekudmuixmmouvif'
+
+    ct = playfair_encipher('this is a test message for the ' \
+          'playfair decipherment', 'elephant')
+    assert ct == 'clkrkrldhodghouhflovqbalhphzmerxnabkhapofatb'
+
+    ct = playfair_encipher('this is a test message for the ' \
+          'playfair decipherment', 'elephant', 
+          wrap_alphabet=KeywordWrapAlphabet.from_last)
+    assert ct == 'vlkrkrlwamnoamawdpolovalhplcklrhmnbkhahmentu'
+    
+    ct = playfair_encipher('''Whoever has made a voyage up the Hudson must 
+        remember the Kaatskill mountains. They are a dismembered branch of 
+        the great''', 'hudson')
+    assert ct == 'vuelznmscupnobnwszgpaoqmonudsohephortctnqnctmoclbwepcrkffthdembgchmoczcpnbbqhrntcntcbipcaeuhlmonkpnbqz'
+
+
+def test_decipher_message():
+    plaintext = sanitise('''Whoever has made a voyage up the Hudson must 
+        remember the Kaatskill mountains. They are a dismembered branch of 
+        the great''')
+
+    trans_table = ''.maketrans('j', 'i')
+
+    for key in ['wink', 'elephant', 'swashbuckling']:
+        for prp in [True, False]:
+            expected = cat(playfair_bigrams(plaintext.translate(trans_table), 
+                padding_replaces_repeat=prp))
+            for wrap in KeywordWrapAlphabet:
+                enciphered = playfair_encipher(plaintext, key, 
+                    padding_replaces_repeat=prp, wrap_alphabet=wrap)
+                deciphered = playfair_decipher(enciphered, key, 
+                    padding_replaces_repeat=prp, wrap_alphabet=wrap)
+                assert deciphered == expected
+
+
+def test_playfair_break():
+    plaintext = sanitise('''You will rejoice to hear that no disaster has accompanied the
+        commencement of an enterprise which you have regarded with such evil
+        forebodings.  I arrived here yesterday, and my first task is to assure
+        my dear sister of my welfare and increasing confidence in the success
+        of my undertaking.''')
+    expected_key = 'swashbuckling'
+    expected_wrap = KeywordWrapAlphabet.from_last
+    expected_letters_to_merge = {'j': 'i'}
+    expected_padding_letter = 'x'
+    expected_padding_replaces_repeat = False
+    ciphertext = playfair_encipher(plaintext, expected_key, 
+        padding_letter=expected_padding_letter,
+        padding_replaces_repeat=expected_padding_replaces_repeat, 
+        letters_to_merge=expected_letters_to_merge, 
+        wrap_alphabet=expected_wrap)
+
+
+    exptected_text = playfair_decipher(ciphertext, expected_key, 
+        padding_letter=expected_padding_letter,
+        padding_replaces_repeat=expected_padding_replaces_repeat, 
+        letters_to_merge=expected_letters_to_merge, 
+        wrap_alphabet=expected_wrap)
+
+    expected_score = Ptrigrams(exptected_text)
+
+    dictionary = ['clearinghouse', 'computerising', 'counterclaims', 
+        'housewarmings',  'intravenously', 'liquefactions', 'somersaulting', 
+        'sportsmanlike', 'swashbuckling']
+
+    # dictionary = ['swashbuckling']
+
+    (key, wrap, letters_to_merge, padding_letter, padding_replaces_repeat), score = playfair_break(
+        ciphertext, 
+        letters_to_merge=expected_letters_to_merge, 
+        padding_letter=expected_padding_letter,
+        wordlist=dictionary, fitness=Ptrigrams)
+
+    assert key == expected_key
+    assert wrap == expected_wrap
+    assert letters_to_merge == expected_letters_to_merge
+    assert padding_letter == expected_padding_letter
+    assert padding_replaces_repeat == expected_padding_replaces_repeat
+    assert score == pytest.approx(expected_score)
diff --git a/tests/test_pocket_enigma.py b/tests/test_pocket_enigma.py
new file mode 100644 (file)
index 0000000..21971fc
--- /dev/null
@@ -0,0 +1,72 @@
+import pytest
+import string 
+
+from szyfrow.pocket_enigma import *
+from szyfrow.support.utilities import *
+
+@pytest.fixture
+def pe():
+    return PocketEnigma(1, 'a')
+
+def test_init(pe):
+    assert pe.wheel_map == [25, 4, 23, 10, 1, 7, 9, 5, 12, 6, 3, 17, 8, 14, 13, 21, 19, 11, 20, 16, 18, 15, 24, 2, 22, 0]
+    assert pe.position == 0
+
+def test_wheel_map(pe):
+    assert pe.make_wheel_map(pe.wheel2) == [2, 3, 0, 1, 22, 8, 15, 12, 5, 10, 9, 13, 7, 11, 16, 6, 14, 25, 20, 21, 18, 19, 4, 24, 23, 17]
+
+def test_validate_wheel_map(pe):
+    pe.validate_wheel_spec(pe.wheel2) == True
+
+    with pytest.raises(ValueError):
+        pe.validate_wheel_spec([])
+    with pytest.raises(ValueError):
+        pe.validate_wheel_spec([('a', 'b', 'c')]*13)
+    with pytest.raises(ValueError):
+        pe.validate_wheel_spec([('a', 'b')]*13)
+
+def test_encipher_letter(pe):
+    pe.set_position('f')
+    assert pe.encipher_letter('k') == 'h'
+    assert pe.position == 6
+
+def test_lookup(pe):
+    pe.set_position('f')
+    assert cat([pe.lookup(l) for l in string.ascii_lowercase]) == 'udhbfejcpgmokrliwntsayqzvx'
+    assert pe.lookup('A') == ''
+
+def test_advance(pe):
+    pe.set_position('f')
+    assert pe.position == 5
+    pe.advance()
+    assert pe.position == 6
+
+    pe.set_position('y')
+    assert pe.position == 24
+    pe.advance()
+    assert pe.position == 25
+    pe.advance()
+    assert pe.position == 0
+
+def test_encipher(pe):
+    pe.set_position('f')
+    assert pe.encipher('helloworld') == 'kjsglcjoqc'
+
+    pe.set_position('f')
+    assert pe.encipher('kjsglcjoqc') == 'helloworld'
+    
+    assert pe.encipher('helloworld', starting_position = 'x') == 'egrekthnnf'
+
+def test_set_position(pe):
+    assert pe.set_position('a') == 0
+    assert pe.set_position('m') == 12
+    assert pe.set_position('z') == 25
+
+def test_break():
+    assert pocket_enigma_break_by_crib('kzpjlzmoga', 1, 'h', 0) == ['a', 'f', 'q']
+    assert pocket_enigma_break_by_crib('kzpjlzmoga', 1, 'he', 0) == ['a']
+    assert pocket_enigma_break_by_crib('kzpjlzmoga', 1, 'll', 2) == ['a']
+    assert pocket_enigma_break_by_crib('kzpjlzmoga', 1, 'l', 2) == ['a']
+    assert pocket_enigma_break_by_crib('kzpjlzmoga', 1, 'l', 3) == ['a', 'j', 'n']
+    assert pocket_enigma_break_by_crib('aaaaa', 1, 'l', 3) == []
+    
\ No newline at end of file
diff --git a/tests/test_polybius.py b/tests/test_polybius.py
new file mode 100644 (file)
index 0000000..477ff34
--- /dev/null
@@ -0,0 +1,100 @@
+import pytest
+import string 
+
+from szyfrow.polybius import *
+from szyfrow.support.utilities import *
+
+def test_grid():
+    grid = polybius_grid('a', 'abcde', 'abcde')
+    assert grid['x'] == ('e', 'c')
+    grid = polybius_grid('elephant', 'abcde', 'abcde')
+    assert grid['e'] == ('a', 'a')
+    assert grid['b'] == ('b', 'c')
+
+def test_reverse_grid():
+    grid = polybius_reverse_grid('a', 'abcde', 'abcde')
+    assert grid['e', 'c'] == 'x'
+    grid = polybius_reverse_grid('elephant', 'abcde', 'abcde')
+    assert grid['a', 'a'] == 'e'
+    assert grid['b', 'c'] == 'b'
+
+def test_grid_round_trip():
+    ltm = {'j': 'i'}
+    fgrid = polybius_grid('elephant', 'abcde', 'abcde', 
+        letters_to_merge=ltm)
+    rgrid = polybius_reverse_grid('elephant', 'abcde', 'abcde', 
+        letters_to_merge=ltm)
+    for l in fgrid:
+        if l in ltm:
+            assert ltm[l] == rgrid[fgrid[l]]
+        else:
+            assert l == rgrid[fgrid[l]]
+    for p in rgrid:
+        assert p == fgrid[rgrid[p]]
+
+
+def test_polybius_flatten():
+    assert polybius_flatten(('a', 'b'), column_first=True) == 'ba'
+    assert polybius_flatten(('a', 'b'), column_first=False) == 'ab'
+
+
+def test_encipher_message():
+    ct = polybius_encipher('this is a test message for the ' \
+          'polybius decipherment', 'elephant', \
+          [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], \
+          wrap_alphabet=KeywordWrapAlphabet.from_last)
+    assert ct == '2214445544551522115522511155551543114252542214111352123234442355411135441314115451112122'
+    
+    ct = polybius_encipher('this is a test message for the ' \
+          'polybius decipherment', 'elephant', 'abcde', 'abcde', \
+          column_first=False)
+    assert ct == 'bbadccddccddaebbaaddbbceaaddddaecbaacadadcbbadaaacdaabedbcccdeddbeaabdccacadaadcceaababb'
+    
+    ct = polybius_encipher('this is a test message for the ' \
+          'polybius decipherment', 'elephant', 'abcde', 'abcde', \
+          column_first=True)
+    assert ct == 'bbdaccddccddeabbaaddbbecaaddddeabcaaacadcdbbdaaacaadbadecbccedddebaadbcccadaaacdecaaabbb'
+
+
+def test_decipher_message():
+    plaintext = sanitise('''Whoever has made a voyage up the Hudson must 
+        remember the Kaatskill mountains. They are a dismembered branch of 
+        the great''')
+    for key in ['wink', 'elephant', 'swashbuckling']: 
+        enciphered = polybius_encipher(plaintext, key, 'abcde', 'abcde')
+        deciphered = polybius_decipher(enciphered, key, 'abcde', 'abcde')
+        assert deciphered == plaintext
+
+
+def test_cadenus_break():
+    plaintext = sanitise('''You will rejoice to hear that no disaster has accompanied the
+        commencement of an enterprise which you have regarded with such evil
+        forebodings.  I arrived here yesterday, and my first task is to assure
+        my dear sister of my welfare and increasing confidence in the success
+        of my undertaking.''')
+    expected_key = 'swashbuckling'
+    expected_wrap = KeywordWrapAlphabet.from_last
+    expected_row_order = 'abcde'
+    expected_col_order = 'abcde'
+    expected_col_first = True
+    ciphertext = polybius_encipher(plaintext, expected_key, 
+        expected_col_order, expected_row_order, 
+        column_first=expected_col_first, wrap_alphabet=expected_wrap)
+    
+    trans_table = ''.maketrans('j', 'i')
+    expected_score = Ptrigrams(plaintext.translate(trans_table))
+
+    dictionary = ['clearinghouse', 'computerising', 'counterclaims', 
+        'housewarmings',  'intravenously', 'liquefactions', 'somersaulting', 
+        'sportsmanlike', 'swashbuckling']
+
+    # dictionary = ['swashbuckling']
+
+    (key, wrap, col_order, row_order, col_first), score = polybius_break(ciphertext, 
+        expected_col_order, expected_row_order, 
+        wordlist=dictionary, fitness=Ptrigrams)
+
+    assert key == expected_key
+    assert wrap == expected_wrap
+    assert col_first == expected_col_first
+    assert score == pytest.approx(expected_score)