def test_test_lev_distance(self): with open('./tests/english.txt') as f: bip39 = BIP39WordList("file_list", handle=f) try: bip39.test_lev_distance(2) except ValidationFailed as e: pass
def test_getlines_long(self): bip39 = BIP39WordList("maxlength_l4", string=maxlength_l4) try: res = bip39.test_max_length(4) except ValidationFailed as e: res = e.status_obj expected_res = [2, 1] self.assertEqual(expected_res, res.getlines_long())
def test_similar_linegroup(self): bip39 = BIP39WordList("inituniq_2group_l3", string=inituniq_2group_l3) res = bip39.test_initial_chars(3) expected_res = [1, 2] self.assertEqual(expected_res, res.similar_linegroup("qu")) for t in [0, ""]: try: res.similar_linegroup(t) self.fail() except AssertionError as e: pass
def test_similargroup_all(self): bip39 = BIP39WordList("inituniq_2group_l3", string=inituniq_2group_l3) res = bip39.test_initial_chars(3) expected_res = {"qu": [("quick", 1), ("quote", 2)]} self.assertEqual(expected_res, res.similargroup_all(2)) for t in [0, ""]: try: res.similargroup_all(t) self.fail() except AssertionError as e: pass
def test_similar_linegroup_many(self): bip39 = BIP39WordList("inituniq_4group_l3", string=inituniq_4group_l3) res = bip39.test_initial_chars(3) expected_res = {"qu": [1, 2], "ri": [4, 3]} self.assertEqual(expected_res, res.similar_linegroup_many(["qu", "ri"])) for t in ["abc", [], ["a"], 0]: try: res.similar_linegroup_many(t) self.fail() except AssertionError as e: pass
def test_test_lowercase(self): try: bip39 = BIP39WordList("invalid_list", string="\n".join(invalid_list)) except InvalidWordList as e: self.assertTrue(e.has_invalid_chars) self.assertFalse(e.has_2048_words) self.assertEqual(e.num_words, 3) self.assertEqual(e.err_lines, []) bip39 = BIP39WordList("valid_list", string=valid_list) res = bip39.test_lowercase() self.assertFalse(res.has_invalid_chars) self.assertFalse(res.has_2048_words) self.assertEqual(res.num_words, 4) self.assertEqual(res.err_lines, []) # We are going to load the english wordlist from the current folder # and also from the BIP Github repo. # Test fails if exceptions are thrown. with open('./tests/english.txt') as f: bip39 = BIP39WordList("file_list", handle=f) res = bip39.test_lowercase() # We only need to test this logic once. self.assertTrue(res.has_2048_words) self.assertEqual(res.num_words, 2048) bip39 = BIP39WordList( "url_list", url= "https://raw.githubusercontent.com/bitcoin/bips/master/bip-0039/english.txt" ) try: bip39 = BIP39WordList("no_list") # This should not work! self.fail() except ValueError as e: pass except InvalidWordList as e: pass
def test_getwordpairs_eq(self): bip39 = BIP39WordList("levdist_le2", string=levdist_le2) try: res = bip39.test_lev_distance(2) except ValidationFailed as e: res = e.status_obj expected_res = [("brol", "brow")] self.assertEqual(expected_res, res.getwordpairs_eq(1)) try: res.getwordpairs_eq(2) self.fail() except AssertionError as e: pass
def test_getlinepairs_gt(self): bip39 = BIP39WordList("levdist_gt2", string=levdist_gt2) try: res = bip39.test_lev_distance(2) except ValidationFailed as e: res = e.status_obj expected_res = [(1, 2)] self.assertEqual(expected_res, res.getlinepairs_gt(2)) try: res.getlinepairs_gt(0) self.fail() except AssertionError as e: pass
def test_getwords_gt(self): bip39 = BIP39WordList("maxlength_l4", string=maxlength_l4) try: res = bip39.test_max_length(1) except ValidationFailed as e: res = e.status_obj expected_res = ["block", "blocks"] self.assertEqual(expected_res, res.getwords_gt(4)) for t in [0, ""]: try: res.getwords_gt(t) self.fail() except AssertionError as e: pass
def test_getlines_list(self): bip39 = BIP39WordList("maxlength_l4", string=maxlength_l4) try: res = bip39.test_max_length(1) except ValidationFailed as e: res = e.status_obj expected_res = [4, 1] self.assertEqual(expected_res, res.getlines_list([3, 6])) for t in ["abc", [], ["a"], 0]: try: res.getlines_list(t) self.fail() except AssertionError as e: pass
def test_similargroup_many(self): bip39 = BIP39WordList("inituniq_4group_l3", string=inituniq_4group_l3) res = bip39.test_initial_chars(3) expected_res = { "qu": [("quick", 1), ("quote", 2)], "ri": [("rich", 4), ("risk", 3)] } self.assertEqual(expected_res, res.similargroup_many(["qu", "ri"])) for t in ["abc", [], ["a"], 0]: try: res.similargroup_many(t) self.fail() except AssertionError as e: pass
def test_getdist_all(self): bip39 = BIP39WordList("levdist_le2", string=levdist_le2) try: res = bip39.test_lev_distance(2) except ValidationFailed as e: res = e.status_obj expected_res = [(("brol", "brow"), (2, 1), 1)] self.assertEqual(expected_res, res.getdist_all("brow")) for t in [1, "", "ABC"]: try: res.getdist_all(t) self.fail() except AssertionError as e: pass
def test_getdist(self): bip39 = BIP39WordList("levdist_le2", string=levdist_le2) try: res = bip39.test_lev_distance(2) except ValidationFailed as e: res = e.status_obj expected_res = 1 self.assertEqual(expected_res, res.getdist("brow", "brol")) for t in [(1, "abc"), ("", "abc"), ("ABC", "abc"), ("abc", 1), ("abc", ""), ("abc", "ABC")]: try: res.getdist(*t) self.fail() except AssertionError as e: pass
def test_getwordpairs_list(self): concat = "\n".join([levdist_le2]+["zzyzx"]) bip39 = BIP39WordList("levdist_concat", string=concat) try: res = bip39.test_lev_distance(2) except ValidationFailed as e: res = e.status_obj expected_res = [("brol", "brow")] self.assertEqual(expected_res, res.getwordpairs_list([1,2])) for t in ["abc", [], ["a"], 0]: try: res.getwordpairs_list(t) self.fail() except AssertionError as e: pass
def test_getdist_all_list(self): concat = "\n".join([levdist_le2]+["zzyzx"]) bip39 = BIP39WordList("concat", string=concat) try: res = bip39.test_lev_distance(2) except ValidationFailed as e: res = e.status_obj expected_res = [(("brol", "brow"), (2, 1), 1)] self.assertEqual(expected_res, res.getdist_all_list("brow", [1])) for t in [1, "", "ABC"]: for u in ["abc", [], ["a"], 0]: try: res.getdist_all_list(t, u) self.fail() except AssertionError as e: pass except KeyError as e: pass
def test_getdist_all_gt(self): bip39 = BIP39WordList("levdist_gt2", string=levdist_gt2) try: res = bip39.test_lev_distance(2) except ValidationFailed as e: res = e.status_obj expected_res = [(("brpyt", "brown"), (2, 1), 3)] self.assertEqual(expected_res, res.getdist_all_gt("brown", 2)) for t in [1, "", "ABC"]: try: res.getdist_all_gt(t, 1) self.fail() except AssertionError as e: pass try: res.getdist_all_gt("abc", 0) self.fail() except AssertionError as e: pass except KeyError as e: pass
def main(): log_file = None try: parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, description='BIP39 wordlist validator') parser.add_argument('input', type=str, help='path to the input file') parser.add_argument('-d', '--min-levenshtein-distance', dest='lev_dist', default=default_lev, type=int, help='set the minimum required \ Levenshtein distance between words (default: {})'.format(default_lev)) parser.add_argument('-u', '--max-initial-unique', dest='init_uniq', default=default_init_uniq, type=int, help='set the maximum \ required unique initial characters between words (default: {})'.format( default_init_uniq)) parser.add_argument('-l', '--max-length', dest='max_length', default=default_max_length, type=int, help='set the maximum length of \ each word (default: {})'.format(default_max_length)) parser.add_argument('-D', '--no-levenshtein-distance', dest='no_lev_dist', help='do not run the Levenshtein distance test', action='store_true') parser.add_argument( '-U', '--no-initial-unique', dest='no_init_uniq', help='do not run the unique initial characters test', action='store_true') parser.add_argument('-L', '--no-max-length', dest='no_max_length', help='do not run the maximum length test', action='store_true') parser.add_argument( '-o', '--output-file', type=str, dest='output', help='logs all console output to an additional file') parser.add_argument( '-a', '--ascii', dest='ascii', help='turn off rich text formatting and progress bars for console \ output', action='store_true') parser.add_argument( '-q', '--quiet', dest='quiet', help='do not display details of test failures, only whether they \ succeeded or failed', action='store_true') parser.add_argument( '--nosane', dest='nosane', action='store_true', help= 'Suppress wordlist sanity check. This might cause other tests to fail.' ) parser.add_argument( '--debug', dest='debug', action='store_true', help='turn on debugging mode (intended for developers)') parser.add_argument( '--pycharm-debug', dest='pycharm_debug', action='store_true', help= 're-raise exceptions out of main() to Pycharm (intended for developers)' ) parser.add_argument('-v', '--version', action='version', version=version_str()) args = parser.parse_args() # If there is an output file, then attempt to open it. if args.output: try: absout = abspath(args.output) # Set the ascii flag if desired before printing this setargs(None, args) logdefault("Attempting to open log file {} for writing".format( absout)) log_file = open(absout, 'w') setargs(log_file, args) except OSError as e: logerror("open {} for writing failed: {}".format( e.filename, e.strerror)) abort(args.debug) else: setargs(None, args) # Now validate the parameters if args.lev_dist <= 0: logerror("Invalid value for --min-levenshtein-distance {}".format( args.lev_dist)) abort(args.debug) if args.init_uniq <= 0: logerror("Invalid value for --min-initial-unique {}".format( args.init_uniq)) abort(args.debug) if args.max_length <= 0: logerror("Invalid value for --max-length {}".format( args.max_length)) abort(args.debug) try: valid_url = validators.url(args.input) if valid_url: kwargs = {'url': args.input} logdefault("Reading wordlist URL {}".format(args.input)) else: f = open(args.input) kwargs = {'handle': f} logdefault("Reading wordlist file {}".format(args.input)) try: bip39 = BIP39WordList(desc=f"{args.input}", **kwargs) loginfo("{} words read".format(len(bip39))) except InvalidWordList as e: handle_invalid_wordlist(args, e) if not valid_url: f.close() except OSError as e: logerror("Cannot read {}: {}".format(e.filename, e.strerror)) abort(args.debug) tally = 0 total = 4 - int(args.no_lev_dist) - int(args.no_init_uniq)\ - int(args.no_max_length) logdefault("Checking wordlist for invalid characters") try: tup = bip39._test_lowercase_1() kwargs = tup[3] kwargs = progressbar('Looking for invalid characters', tup[0], tup[1], tup[2], **kwargs) validity = bip39._test_lowercase_2(kwargs) check_validity_warnings(validity) tally += 1 loginfo("Valid characters test succeeded") except InvalidWordList as e: handle_invalid_wordlist(args, e) if args.nosane: logwarning( "Valid characters test failed, but --nosane passed; ignoring error" ) logdefault("Finished checking wordlist for invalid characters") separator() if not args.no_lev_dist: logdefault("Performing Levenshtein distance test") try: tup = bip39._test_lev_distance_1(n=args.lev_dist) kwargs = tup[3] kwargs = progressbar('Computing Levenshtein distance', tup[0], tup[1], tup[2], **kwargs) bip39._test_lev_distance_2(kwargs) loginfo("No word pairs with Levenshtein distance less than {}" \ .format(args.lev_dist)) tally += 1 loginfo("Levenshtein distance test succeeded") except ValidationFailed as e: lev_dist = e.status_obj word_pairs = lev_dist.getwordpairs_lt() logerror("{} word pairs with Levenshtein distance less than {}\n" \ .format(len(word_pairs), args.lev_dist)) for i in range(1, args.lev_dist): words_list = [ *zip(lev_dist.getwordpairs_eq(i), lev_dist.getlinepairs_eq(i)) ] logerror("{} word pairs with Levenshtein distance *equal* to {}:" \ .format(len(words_list), i)) for words, lines in words_list: logerror(" \"{}\" (line {}) <--> \"{}\" (line {})" \ .format(words[0], lines[0], words[1], lines[1])) logerror("") logerror( "{} total words below minimum Levenshtein distance".format( len(word_pairs))) logerror("Levenshtein distance test failed") logdefault("Finished performing Levenshtein distance test") separator() if not args.no_init_uniq: logdefault("Performing unique initial characters test") try: tup = bip39._test_initial_chars_1(n=args.init_uniq) kwargs = tup[3] kwargs = progressbar('Checking initial characters', tup[0], tup[1], tup[2], **kwargs) bip39._test_initial_chars_2(kwargs) loginfo("All words are unique to {} initial characters".format( args.init_uniq)) tally += 1 loginfo("Unique initial characters test succeeded") except ValidationFailed as e: similar = e.status_obj # Filter out groups with just one word in them as those are unique groups = similar.groups_length(args.init_uniq) logerror("{} groups of similar words (by {} initial characters)\n" \ .format(len(groups.items()), args.init_uniq)) for pre, group in groups.items(): logerror("Similar words with prefix \"{}\":".format(pre)) for wordline in group: logerror(" \"{}\" (line {})".format( wordline[0], wordline[1])) logerror("") logerror("{} total similar words".format(len(groups.keys()))) logerror("Unique initial characters test failed") logdefault("Finished unique initial characters test") separator() if not args.no_max_length: logdefault("Performing maximum word length test") try: tup = bip39._test_max_length_1(n=args.max_length) kwargs = tup[3] kwargs = progressbar('Checking length', tup[0], tup[1], tup[2], **kwargs) bip39._test_max_length_2(kwargs) loginfo("Length of all words are {} chracters or less".format( args.max_length)) tally += 1 loginfo("Maximum word length test succeeded") except ValidationFailed as e: lengths = e.status_obj words = lengths.getwords_gt(args.max_length) lines = lengths.getlines_gt(args.max_length) logerror("Words longer than {} characters:".format( args.max_length)) for word, line in [*zip(words, lines)]: logerror(" \"{}\" (line {})".format(word, line)) logerror("{} words longer than {} characters".format( len(lengths), args.max_length)) logerror("Maximum word length test failed") logdefault("Finished maximum word length test") separator() logdefault("{} of {} checks passed".format(tally, total)) if log_file: log_file.close() exit(0) except Exception as e: print("Got unknown exception {}: {}".format(type(e), str(e))) if args.pycharm_debug: raise e else: abort(args.debug)
def test_test_max_length(self): with open('./tests/english.txt') as f: bip39 = BIP39WordList("file_list", handle=f) bip39.test_initial_chars(4)