scrambletest.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2024 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. test/scrambletest.py: seed scrambling and addrlist data generation tests for all
  20. supported coins + passwords
  21. """
  22. import sys, os, time
  23. from subprocess import run, PIPE
  24. from collections import namedtuple
  25. try:
  26. from include import test_init
  27. except ImportError:
  28. from test.include import test_init
  29. from mmgen.main import launch
  30. from mmgen.cfg import Config
  31. from mmgen.util import msg, msg_r, bmsg, die, list_gen
  32. from mmgen.color import gray, green
  33. from test.include.common import set_globals, init_coverage, end_msg
  34. opts_data = {
  35. 'text': {
  36. 'desc': 'Test seed scrambling and addrlist data generation for all supported altcoins',
  37. 'usage':'[options] [command]',
  38. 'options': """
  39. -h, --help Print this help message
  40. --, --longhelp Print help message for long (global) options
  41. -a, --no-altcoin Skip altcoin tests
  42. -C, --coverage Produce code coverage info using trace module
  43. -l, --list-cmds List and describe the tests and commands in this test suite
  44. -v, --verbose Produce more verbose output
  45. """,
  46. 'notes': """
  47. Valid commands: 'coin', 'pw'
  48. If no command is given, the whole suite of tests is run.
  49. """
  50. }
  51. }
  52. cfg = Config(opts_data=opts_data)
  53. set_globals(cfg)
  54. td = namedtuple('scrambletest_entry', ['seed', 'str', 'id_str', 'lbl', 'addr'])
  55. bitcoin_data = {
  56. # SCRAMBLED_SEED[:8] SCRAMBLE_KEY ID_STR LBL FIRST ADDR
  57. 'btc': td('456d7f5f1c4bfe3b','(none)', '', '', '1MU7EdgqYy9JX35L25hR6CmXXcSEBDAwyv'),
  58. 'btc_compressed':td('bf98a4af5464a4ef','compressed', '-C', 'COMPRESSED','1F97Jd89wwmu4ELadesAdGDzg3d8Y6j5iP'),
  59. 'btc_segwit': td('b56962d829ffc678','segwit', '-S', 'SEGWIT', '36TvVzU5mxSjJ3D9qKAmYzCV7iUqtTDezF'),
  60. 'btc_bech32': td('d09eea818f9ad17f','bech32', '-B', 'BECH32', 'bc1q8snv94j6959y3gkqv4gku0cm5mersnpucsvw5z'),
  61. }
  62. altcoin_data = {
  63. 'bch': td('456d7f5f1c4bfe3b','(none)', '', '', '1MU7EdgqYy9JX35L25hR6CmXXcSEBDAwyv'),
  64. 'bch_compressed':td('bf98a4af5464a4ef','compressed', '-C', 'COMPRESSED', '1F97Jd89wwmu4ELadesAdGDzg3d8Y6j5iP'),
  65. 'ltc': td('b11f16632e63ba92','ltc:legacy', '-LTC', 'LTC', 'LMxB474SVfxeYdqxNrM1WZDZMnifteSMv1'),
  66. 'ltc_compressed':td('7ccf465d466ee7d3','ltc:compressed','-LTC-C','LTC:COMPRESSED','LdkebBKVXSs6NNoPJWGM8KciDnL8LhXXjb'),
  67. 'ltc_segwit': td('9460f5ba15e82768','ltc:segwit', '-LTC-S','LTC:SEGWIT', 'MQrY3vEbqKMBgegXrSaR93R2HoTDE5bKrY'),
  68. 'ltc_bech32': td('dbdbff2e196e27d3','ltc:bech32', '-LTC-B','LTC:BECH32', 'ltc1qdvgqsz94ht20lr8fyk5v7n884hu9p7d8k9easu'),
  69. 'eth': td('213ed116869b19f2','eth', '-ETH', 'ETH', 'e704b6cfd9f0edb2e6cfbd0c913438d37ede7b35'),
  70. 'etc': td('909def37096f5ab8','etc', '-ETC', 'ETC', '1a6acbef8c38f52f20d04ecded2992b04d8608d7'),
  71. 'dash': td('1319d347b021f952','dash:legacy', '-DASH', 'DASH', 'XoK491fppGNZQUUS9uEFkT6L9u8xxVFJNJ'),
  72. 'emc': td('7e1a29853d2db875','emc:legacy', '-EMC', 'EMC', 'EU4L6x2b5QUb2gRQsBAAuB8TuPEwUxCNZU'),
  73. 'zec': td('0bf9b5b20af7b5a0','zec:legacy', '-ZEC', 'ZEC', 't1URz8BHxV38v3gsaN6oHQNKC16s35R9WkY'),
  74. 'zec_zcash_z': td('b15570d033df9b1a','zec:zcash_z', '-ZEC-Z','ZEC:ZCASH_Z', 'zcLMMsnzfFYZWU4avRBnuc83yh4jTtJXbtP32uWrs3ickzu1krMU4ppZCQPTwwfE9hLnRuFDSYF8VFW13aT9eeQK8aov3Ge'),
  75. 'xmr': td('c76af3b088da3364','xmr:monero', '-XMR-M','XMR:MONERO', '41tmwZd2CdXEGtWqGY9fH9FVtQM8VxZASYPQ3VJQhFjtGWYzQFuidD21vJYTi2yy3tXRYXTNXBTaYVLav62rwUUpFFyicZU'),
  76. }
  77. passwd_data = {
  78. 'dfl_dfl_αω': td('b78c363f3d6714f6', 'b58:20:αω', '-αω-b58-20', 'αω b58:20', 'L3JcAZze6LQS2TFodoW9'),
  79. 'dfl_H_αω': td('2cba1ba1a73d5497', 'b58:10:αω', '-αω-b58-10', 'αω b58:10', 'KgSBfzPBWj'),
  80. 'b32_dfl_αω': td('ad71ff8d0512660d', 'b32:24:αω', '-αω-b32-24', 'αω b32:24', 'VQFESDGWAQCOCSG6GDLIG5OQ'),
  81. 'b32_H_αω': td('b050a1613dd4d3ad', 'b32:12:αω', '-αω-b32-12', 'αω b32:12', 'QTI7HTVN3JOE'),
  82. 'hex_dfl_αω': td('8322b26abd931d55', 'hex:64:αω', '-αω-hex-64', 'αω hex:64', '6e9f8d5d2ec6c4f2a079b163e37f5dc2a7e6adb221c4de315078ffad971ba260'),
  83. 'hex_H_αω': td('46beb852d38ed5c4', 'hex:32:αω', '-αω-hex-32', 'αω hex:32', '86143b36b649728cc8f3677872ed37d3'),
  84. 'bip39_dfl_αω':td('95b383d5092a55df', 'bip39:24:αω','-αω-bip39-24','αω bip39:24','treat athlete brand top beauty poverty senior unhappy vacant domain yellow scale fossil aim lonely fatal sun nuclear such ancient stage require stool similar'),
  85. 'bip39_18_αω': td('29e5a605ffa36142', 'bip39:18:αω','-αω-bip39-18','αω bip39:18','better legal various ketchup then range festival either tomato cradle say absorb solar earth alter pattern canyon liar'),
  86. 'bip39_12_αω': td('efa13cb309d7fc1d', 'bip39:12:αω','-αω-bip39-12','αω bip39:12','lady oppose theme fit position merry reopen acquire tuna dentist young chunk'),
  87. 'xmrseed_dfl_αω':td('62f5b72a5ca89cab', 'xmrseed:25:αω','-αω-xmrseed-25','αω xmrseed:25','tequila eden skulls giving jester hospital dreams bakery adjust nanny cactus inwardly films amply nanny soggy vials muppet yellow woken ashtray organs exhale foes eden'),
  88. }
  89. run_env = dict(os.environ)
  90. run_env['MMGEN_DEBUG_ADDRLIST'] = '1'
  91. words_file = 'test/ref/98831F3A.mmwords'
  92. cvrg_opts = '-m trace --count --coverdir={} --file={}'.format(*init_coverage()).split() if cfg.coverage else []
  93. def make_cmd(progname, opts, add_opts, args):
  94. return ['python3'] + cvrg_opts + [f'cmds/{progname}', '-qS'] + opts + add_opts + [words_file] + args + ['1']
  95. def run_cmd(cmd):
  96. cp = run(cmd, stdout=PIPE, stderr=PIPE, text=True, env=run_env)
  97. if cp.returncode != 0:
  98. die(2, f'\nSpawned program exited with error code {cp.returncode}:\n{cp.stderr}')
  99. return cp.stdout.splitlines()
  100. def run_test(progname, opts, add_opts, args, test_data, addr_desc, opts_w):
  101. cmd = make_cmd(progname, opts, add_opts, args)
  102. if cfg.verbose:
  103. msg(green(f'Executing: {" ".join(cmd)}'))
  104. else:
  105. msg_r('Testing: {} {:{w}} '.format(progname, ' '.join(opts), w=opts_w))
  106. lines = run_cmd(cmd)
  107. cmd_out = dict([e[9:].split(': ') for e in lines if e.startswith('sc_debug_')])
  108. cmd_out['addr'] = lines[-2].split(None, 1)[-1]
  109. ref_data = test_data._asdict()
  110. for k in ref_data:
  111. if cmd_out[k] == ref_data[k]:
  112. s = k.replace('seed', 'seed[:8]').replace('addr', addr_desc)
  113. cfg._util.vmsg(f' {s:9}: {cmd_out[k]}')
  114. else:
  115. die(4, f'\nError: sc_{k} value {cmd_out[k]} does not match reference value {ref_data[k]}')
  116. msg(green('OK') if cfg.verbose else 'OK')
  117. def make_coin_test_data():
  118. bmsg('Testing address scramble strings and list IDs')
  119. coin_data = bitcoin_data | ({} if cfg.no_altcoin else altcoin_data)
  120. for id_str, test_data in coin_data.items():
  121. if id_str == 'zec_zcash_z' and sys.platform == 'win32':
  122. msg(gray("Skipping 'zec_zcash_z' test for Windows platform"))
  123. continue
  124. coin, mmtype = id_str.split('_', 1) if '_' in id_str else (id_str, None)
  125. opts = list_gen(
  126. [f'--coin={coin}'],
  127. [f'--type={mmtype}', mmtype],
  128. ['--cashaddr=0', coin == 'bch']
  129. )
  130. yield ('mmgen-addrgen', opts, [], [], test_data, 'address')
  131. def make_passwd_test_data():
  132. bmsg('Testing password scramble strings and list IDs')
  133. for id_str, test_data in passwd_data.items():
  134. if id_str.startswith('xmrseed') and cfg.no_altcoin:
  135. continue
  136. pw_fmt, pw_len, pw_id = id_str.split('_')
  137. opts = list_gen(
  138. [f'--passwd-fmt={pw_fmt}', pw_fmt != 'dfl'],
  139. [f'--passwd-len={pw_len}', pw_len != 'dfl'],
  140. )
  141. yield ('mmgen-passgen', opts, ['--accept-defaults'], [pw_id], test_data, 'password')
  142. def main():
  143. start_time = int(time.time())
  144. cmds = cfg._args or ('coin', 'pw')
  145. funcs = {'coin': make_coin_test_data, 'pw': make_passwd_test_data}
  146. for cmd in cmds:
  147. data = tuple(funcs[cmd]())
  148. opts_w = max(len(' '.join(e[1])) for e in data)
  149. for d in data:
  150. run_test(*d, opts_w)
  151. end_msg(int(time.time()) - start_time)
  152. launch(func=main)