gentest.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2025 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/gentest.py: Cryptocoin key/address generation tests for the MMGen suite
  20. """
  21. import sys, os, time
  22. try:
  23. from include import test_init
  24. except ImportError:
  25. from test.include import test_init
  26. # Import these _after_ local path's been added to sys.path
  27. from mmgen.cfg import gc, Config
  28. from mmgen.color import green, red, purple
  29. from mmgen.util import msg, ymsg, capfirst, is_int, die
  30. results_file = 'gentest.out.json'
  31. rounds = 100
  32. opts_data = {
  33. 'text': {
  34. 'desc': 'Test key/address generation of the MMGen suite in various ways',
  35. 'usage':'[options] <spec> <rounds | dump file>',
  36. 'options': """
  37. -h, --help Print this help message
  38. --, --longhelp Print help message for long (global) options
  39. -a, --all-coins Test all coins supported by specified external tool
  40. -k, --use-internal-keccak-module Force use of the internal keccak module
  41. -q, --quiet Produce quieter output
  42. -s, --save-results Save output of external tool in Compare test to
  43. {rf!r}
  44. -t, --type=t Specify address type (e.g. 'compressed', 'segwit',
  45. 'zcash_z', 'bech32')
  46. -v, --verbose Produce more verbose output
  47. """,
  48. 'notes': """
  49. TEST TYPES:
  50. Compare: {prog} A:B <rounds> (compare address generators A and B)
  51. Speed: {prog} A <rounds> (test speed of generator A)
  52. Dump: {prog} A <dump file> (compare generator A to wallet dump)
  53. where:
  54. A and B are keygen backend numbers ('1' being the default); or
  55. B is the name of an external tool (see below) or 'ext'.
  56. If B is 'ext', the external tool will be chosen automatically.
  57. For the Compare test, A may be 'all' to test all backends for the current
  58. coin/address type combination.
  59. EXAMPLES:
  60. Compare addresses generated by 'libsecp256k1' and 'python-ecdsa' backends,
  61. with 100 random rounds plus private-key edge cases:
  62. $ {prog} 1:2 100
  63. Compare Segwit addresses from default 'libsecp256k1' backend to 'pycoin'
  64. library for all supported coins, 100 rounds + edge cases:
  65. $ {prog} --all-coins --type=segwit 1:pycoin 100
  66. Compare addresses from 'python-ecdsa' backend to output of 'keyconv' tool
  67. for all supported coins, 100 rounds + edge cases:
  68. $ {prog} --all-coins --type=compressed 2:keyconv 100
  69. Compare bech32 addrs from 'libsecp256k1' backend to Bitcoin Core wallet
  70. dump:
  71. $ {prog} --type=bech32 1 bech32wallet.dump
  72. Compare addresses from Monero 'ed25519ll' backend to output of default
  73. external tool, 10 rounds + edge cases:
  74. $ {prog} --coin=xmr 3:ext 10
  75. Test the speed of default Monero 'nacl' backend, 10,000 rounds:
  76. $ test/gentest.py --coin=xmr 1 10000
  77. Same for Zcash:
  78. $ test/gentest.py --coin=zec --type=zcash_z 1 10000
  79. Test all configured Monero backends against the 'monero-python' library, 3 rounds
  80. + edge cases:
  81. $ test/gentest.py --coin=xmr all:monero-python 3
  82. Test 'nacl' and 'ed25519ll_djbec' backends against each other, 10,000 rounds
  83. + edge cases:
  84. $ test/gentest.py --coin=xmr 1:2 10000
  85. SUPPORTED EXTERNAL TOOLS:
  86. + eth-keys (for ETH, ETC)
  87. https://github.com/ethereum/eth-keys
  88. + ethkey (eth-keys alternative for MSYS2, or if eth-keys is unavailable)
  89. https://github.com/openethereum/openethereum/releases/tag/v3.1.0
  90. + zcash-mini (for Zcash-Z addresses and view keys)
  91. https://github.com/FiloSottile/zcash-mini
  92. + monero-python (for Monero addresses and view keys)
  93. https://github.com/monero-ecosystem/monero-python
  94. + pycoin (for supported coins)
  95. https://github.com/richardkiss/pycoin
  96. + keyconv (for supported coins)
  97. https://github.com/exploitagency/vanitygen-plus
  98. ('keyconv' does not generate Segwit addresses)
  99. """
  100. },
  101. 'code': {
  102. 'options': lambda s: s.format(
  103. rf = results_file,
  104. ),
  105. 'notes': lambda s: s.format(
  106. prog = 'test/gentest.py',
  107. pnm = gc.proj_name,
  108. snum = rounds)
  109. }
  110. }
  111. def get_cmd_output(cmd, input=None):
  112. return run(cmd, input=input, stdout=PIPE, stderr=DEVNULL).stdout.decode().splitlines()
  113. saved_results = {}
  114. class GenTool:
  115. def __init__(self, proto, addr_type):
  116. self.proto = proto
  117. self.addr_type = addr_type
  118. self.data = {}
  119. def __del__(self):
  120. if cfg.save_results:
  121. key = f'{self.proto.coin}-{self.proto.network}-{self.addr_type.name}-{self.desc}'.lower()
  122. saved_results[key] = {k.hex():v._asdict() for k, v in self.data.items()}
  123. def run_tool(self, sec, cache_data):
  124. vcoin = 'BTC' if self.proto.coin == 'BCH' else self.proto.coin
  125. key = sec.orig_bytes
  126. if key in self.data:
  127. return self.data[key]
  128. else:
  129. ret = self.run(sec, vcoin)
  130. if cache_data:
  131. self.data[key] = sd(**{'reduced':sec.hex()}, **ret._asdict())
  132. return ret
  133. class GenToolEth_keys(GenTool):
  134. desc = 'eth-keys'
  135. def __init__(self, *args, **kwargs):
  136. self.keys = self.cmdname = None
  137. try:
  138. from eth_keys import keys
  139. self.keys = keys
  140. except ImportError:
  141. self.cmdname = get_ethkey()
  142. self.desc = 'ethkey'
  143. if not (self.keys or self.cmdname):
  144. die(2, 'Neither the ‘eth-keys’ package nor the ‘ethkey’ executable '
  145. 'could be found on the system!')
  146. super().__init__(*args, **kwargs)
  147. def run(self, sec, vcoin):
  148. if self.keys:
  149. sk = self.keys.PrivateKey(sec)
  150. return gtr(str(sk)[2:], sk.public_key.to_address()[2:], None)
  151. else:
  152. o = get_cmd_output([self.cmdname, 'info', sec.hex()])
  153. return gtr(o[0].split()[1], o[-1].split()[1], None)
  154. class GenToolKeyconv(GenTool):
  155. desc = 'keyconv'
  156. def run(self, sec, vcoin):
  157. o = get_cmd_output(['keyconv', '-C', vcoin, sec.wif])
  158. return gtr(
  159. o[1].split()[1],
  160. o[0].split()[1],
  161. None)
  162. class GenToolZcash_mini(GenTool):
  163. desc = 'zcash-mini'
  164. def run(self, sec, vcoin):
  165. o = get_cmd_output(['zcash-mini', '-key', '-simple'], input=(sec.wif+'\n').encode())
  166. return gtr(o[1], o[0], o[-1])
  167. class GenToolPycoin(GenTool):
  168. """
  169. pycoin/networks/all.py pycoin/networks/legacy_networks.py
  170. """
  171. desc = 'pycoin'
  172. def __init__(self, *args, **kwargs):
  173. super().__init__(*args, **kwargs)
  174. try:
  175. from pycoin.networks.registry import network_for_netcode
  176. except ImportError as e:
  177. raise ImportError(
  178. f'{e}\nUnable to import pycoin.networks.registry. Is pycoin installed on your system?') from e
  179. self.nfnc = network_for_netcode
  180. def run(self, sec, vcoin):
  181. if self.proto.testnet:
  182. vcoin = cinfo.external_tests['testnet']['pycoin'][vcoin]
  183. network = self.nfnc(vcoin)
  184. key = network.keys.private(
  185. secret_exponent = int(sec.hex(), 16),
  186. is_compressed = self.addr_type.name != 'legacy')
  187. if key is None:
  188. die(1, f'can’t parse {sec.hex()}')
  189. if self.addr_type.name in ('segwit', 'bech32'):
  190. hash160_c = key.hash160(is_compressed=True)
  191. if self.addr_type.name == 'segwit':
  192. p2sh_script = network.contract.for_p2pkh_wit(hash160_c)
  193. addr = network.address.for_p2s(p2sh_script)
  194. else:
  195. addr = network.address.for_p2pkh_wit(hash160_c)
  196. else:
  197. addr = key.address()
  198. return gtr(key.wif(), addr, None)
  199. class GenToolMonero_python(GenTool):
  200. desc = 'monero-python'
  201. def __init__(self, *args, **kwargs):
  202. super().__init__(*args, **kwargs)
  203. try:
  204. from monero.seed import Seed
  205. except ImportError as e:
  206. raise ImportError(
  207. f'{e}\nUnable to import monero-python. Is monero-python installed on your system?') from e
  208. self.Seed = Seed
  209. def run(self, sec, vcoin):
  210. seed = self.Seed(sec.orig_bytes.hex())
  211. sk = seed.secret_spend_key()
  212. vk = seed.secret_view_key()
  213. addr = seed.public_address()
  214. return gtr(sk, addr, vk)
  215. def find_or_check_tool(proto, addr_type, toolname):
  216. ext_progs = list(cinfo.external_tests[proto.network])
  217. if toolname not in ext_progs + ['ext']:
  218. die(1, f'{toolname!r}: unsupported tool for network {proto.network}')
  219. if cfg.all_coins and toolname == 'ext':
  220. die(1, "'--all-coins' must be combined with a specific external testing tool")
  221. else:
  222. tool = cinfo.get_test_support(
  223. proto.coin,
  224. addr_type.name,
  225. proto.network,
  226. verbose = not cfg.quiet,
  227. toolname = toolname if toolname != 'ext' else None)
  228. if tool and toolname in ext_progs and toolname != tool:
  229. sys.exit(3)
  230. if tool is None:
  231. return None
  232. return tool
  233. def test_equal(desc, a_val, b_val, in_bytes, sec, wif, a_desc, b_desc):
  234. if a_val != b_val:
  235. fs = """
  236. {i:{w}}: {}
  237. {s:{w}}: {}
  238. {W:{w}}: {}
  239. {a:{w}}: {}
  240. {b:{w}}: {}
  241. """
  242. die(3,
  243. red('\nERROR: {} do not match!').format(desc)
  244. + fs.format(
  245. in_bytes.hex(), sec, wif, a_val, b_val,
  246. i='input', s='sec key', W='WIF key', a=a_desc, b=b_desc,
  247. w=max(len(e) for e in (a_desc, b_desc)) + 1
  248. ).rstrip())
  249. def do_ab_test(proto, scfg, addr_type, gen1, kg2, ag, tool, cache_data):
  250. def do_ab_inner(n, trounds, in_bytes):
  251. global last_t
  252. if cfg.verbose or time.time() - last_t >= 0.1:
  253. qmsg_r(f'\rRound {i+1}/{trounds} ')
  254. last_t = time.time()
  255. sec = PrivKey(proto, in_bytes, compressed=addr_type.compressed, pubkey_type=addr_type.pubkey_type)
  256. data = kg1.gen_data(sec)
  257. addr1 = ag.to_addr(data)
  258. view_pref = 1 if proto.coin == 'BCH' else 0
  259. tinfo = (in_bytes, sec, sec.wif, type(kg1).__name__, type(kg2).__name__ if kg2 else tool.desc)
  260. def do_msg():
  261. if cfg.verbose:
  262. msg(fs.format(b=in_bytes.hex(), r=sec.hex(), k=sec.wif, v=vk2, a=addr1))
  263. if tool:
  264. def run_tool():
  265. o = tool.run_tool(sec, cache_data)
  266. test_equal('WIF keys', sec.wif, o.wif, *tinfo)
  267. test_equal('addresses', addr1.views[view_pref], o.addr, *tinfo)
  268. if o.viewkey:
  269. test_equal('view keys', ag.to_viewkey(data), o.viewkey, *tinfo)
  270. return o.viewkey
  271. vk2 = run_tool()
  272. do_msg()
  273. else:
  274. test_equal('addresses', addr1.views[view_pref], ag.to_addr(kg2.gen_data(sec)), *tinfo)
  275. vk2 = None
  276. do_msg()
  277. qmsg_r(f'\rRound {n+1}/{trounds} ')
  278. def get_randbytes():
  279. if tool and len(tool.data) > len(edgecase_sks):
  280. yield from tuple(tool.data)[len(edgecase_sks):]
  281. else:
  282. for _ in range(scfg.rounds):
  283. yield getrand(32)
  284. kg1 = KeyGenerator(cfg, proto, addr_type.pubkey_type, backend=gen1)
  285. if type(kg1) == type(kg2):
  286. die(4, 'Key generators are the same!')
  287. e = cinfo.get_entry(proto.coin, proto.network)
  288. qmsg(green("Comparing address generators '{A}' and '{B}' for {N} {c} ({n}), addrtype {a!r}".format(
  289. A = type(kg1).__name__.replace('_', '-'),
  290. B = type(kg2).__name__.replace('_', '-') if kg2 else tool.desc,
  291. N = proto.network,
  292. c = proto.coin,
  293. n = e.name if e else '---',
  294. a = addr_type.name)))
  295. global last_t
  296. last_t = time.time()
  297. fs = (
  298. '\ninput: {b}' +
  299. '\nreduced: {r}' +
  300. '\n{:9} {{k}}'.format(addr_type.wif_label+':') +
  301. ('\nviewkey: {v}' if 'viewkey' in addr_type.extra_attrs else '') +
  302. '\naddr: {a}\n')
  303. group_order = CoinProtocol.Secp256k1.secp256k1_group_order
  304. # test some important private key edge cases:
  305. edgecase_sks = (
  306. bytes([0x00]*31 + [0x01]), # min
  307. bytes([0xff]*32), # max
  308. bytes([0x0f] + [0xff]*31), # produces same key as above for zcash-z
  309. int.to_bytes(group_order + 1, 32, 'big'), # bitcoin will reduce
  310. int.to_bytes(group_order - 1, 32, 'big'), # bitcoin will not reduce
  311. bytes([0x00]*31 + [0xff]), # monero will reduce
  312. bytes([0xff]*31 + [0x0f]), # monero will not reduce
  313. bytes.fromhex('deadbeef'*8),
  314. )
  315. qmsg(purple('edge cases:'))
  316. for i, privbytes in enumerate(edgecase_sks):
  317. do_ab_inner(i, len(edgecase_sks), privbytes)
  318. qmsg(green('\rOK ' if cfg.verbose else 'OK'))
  319. qmsg(purple('random input:'))
  320. for i, privbytes in enumerate(get_randbytes()):
  321. do_ab_inner(i, scfg.rounds, privbytes)
  322. qmsg(green('\rOK ' if cfg.verbose else 'OK'))
  323. def init_tool(proto, addr_type, toolname):
  324. return globals()['GenTool'+capfirst(toolname.replace('-', '_'))](proto, addr_type)
  325. def ab_test(proto, scfg):
  326. addr_type = MMGenAddrType(proto=proto, id_str=cfg.type or proto.dfl_mmtype)
  327. if scfg.gen2:
  328. assert scfg.gen1 != 'all', "'all' must be used only with external tool"
  329. kg2 = KeyGenerator(cfg, proto, addr_type.pubkey_type, backend=scfg.gen2)
  330. tool = None
  331. else:
  332. toolname = find_or_check_tool(proto, addr_type, scfg.tool)
  333. if toolname is None:
  334. ymsg(f'Warning: skipping tool {scfg.tool!r} for {proto.coin} {addr_type.name}')
  335. return
  336. tool = init_tool(proto, addr_type, toolname)
  337. kg2 = None
  338. ag = AddrGenerator(cfg, proto, addr_type)
  339. if scfg.all_backends: # check all backends against external tool
  340. for n in range(len(get_backends(addr_type.pubkey_type))):
  341. do_ab_test(
  342. proto,
  343. scfg,
  344. addr_type,
  345. gen1 = n+1,
  346. kg2 = kg2,
  347. ag = ag,
  348. tool = tool,
  349. cache_data = scfg.rounds < 1000 and not n)
  350. else: # check specific backend against external tool or another backend
  351. do_ab_test(
  352. proto,
  353. scfg,
  354. addr_type,
  355. gen1 = scfg.gen1,
  356. kg2 = kg2,
  357. ag = ag,
  358. tool = tool,
  359. cache_data = False)
  360. def speed_test(proto, kg, ag, rounds):
  361. qmsg(green('Testing speed of address generator {!r} for coin {}'.format(
  362. type(kg).__name__,
  363. proto.coin)))
  364. from struct import pack
  365. seed = getrand(28)
  366. qmsg('Incrementing key with each round')
  367. qmsg('Starting key: {}'.format((seed + pack('I', 0)).hex()))
  368. start = last_t = time.time()
  369. for i in range(rounds):
  370. if time.time() - last_t >= 0.1:
  371. qmsg_r(f'\rRound {i+1}/{rounds} ')
  372. last_t = time.time()
  373. sec = PrivKey(proto, seed+pack('I', i), compressed=ag.compressed, pubkey_type=ag.pubkey_type)
  374. addr = ag.to_addr(kg.gen_data(sec))
  375. vmsg(f'\nkey: {sec.wif}\naddr: {addr}\n')
  376. qmsg(
  377. f'\rRound {i+1}/{rounds} ' +
  378. f'\n{rounds} addresses generated' +
  379. ('' if cfg.test_suite_deterministic else f' in {time.time()-start:.2f} seconds')
  380. )
  381. def dump_test(proto, kg, ag, filename):
  382. with open(filename) as fp:
  383. dump = [[*(e.split()[0] for e in line.split('addr='))] for line in fp.readlines() if 'addr=' in line]
  384. if not dump:
  385. die(1, f'File {filename!r} appears not to be a wallet dump')
  386. qmsg(green(
  387. "A: generator pair '{}:{}'\nB: wallet dump {!r}".format(
  388. type(kg).__name__,
  389. type(ag).__name__,
  390. filename)))
  391. for count, (b_wif, b_addr) in enumerate(dump, 1):
  392. qmsg_r(f'\rKey {count}/{len(dump)} ')
  393. try:
  394. b_sec = PrivKey(proto, wif=b_wif)
  395. except:
  396. die(2, f'\nInvalid {proto.network} WIF address in dump file: {b_wif}')
  397. a_addr = ag.to_addr(kg.gen_data(b_sec))
  398. vmsg(f'\nwif: {b_wif}\naddr: {b_addr}\n')
  399. tinfo = (b_sec, b_sec.hex(), b_wif, type(kg).__name__, filename)
  400. test_equal('addresses', a_addr, b_addr, *tinfo)
  401. qmsg(green(('\n', '')[bool(cfg.verbose)] + 'OK'))
  402. def get_protos(proto, addr_type, toolname):
  403. init_genonly_altcoins(testnet=proto.testnet)
  404. for coin in cinfo.external_tests[proto.network][toolname]:
  405. if coin.lower() not in CoinProtocol.coins:
  406. continue
  407. ret = init_proto(cfg, coin, testnet=proto.testnet)
  408. if addr_type not in ret.mmtypes:
  409. continue
  410. yield ret
  411. def parse_args():
  412. all_backends, gen2, tool = (False, None, None)
  413. match cfg._args:
  414. case (gen1, rounds) if is_int(gen1) and is_int(rounds):
  415. test, dumpfile = ('speed', None)
  416. case (gen1, dumpfile) if is_int(gen1) and os.access(dumpfile, os.R_OK):
  417. test, rounds = ('dump', None)
  418. case (ab, rounds) if (ab := ab.split(':')) and is_int(rounds):
  419. test, dumpfile = ('ab', None)
  420. match ab[0]:
  421. case x if is_int(x):
  422. gen1 = x
  423. case 'all':
  424. all_backends = True
  425. gen1 = None
  426. case _:
  427. die(1, "First part of first argument must be a generator backend number or 'all'")
  428. match ab[1]:
  429. case x if is_int(x):
  430. if cfg.all_coins:
  431. die(1, '--all-coins must be used with external tool only')
  432. gen2 = x
  433. case x:
  434. tool = x
  435. ext_progs = list(cinfo.external_tests[cfg._proto.network]) + ['ext']
  436. if tool not in ext_progs:
  437. die(1, f'Second part of first argument must be a generator backend number or one of {ext_progs}')
  438. case _:
  439. cfg._usage()
  440. return namedtuple('parsed_args',
  441. ['test', 'gen1', 'gen2', 'rounds', 'tool', 'all_backends', 'dumpfile'])(
  442. test,
  443. None if gen1 is None else int(gen1),
  444. None if gen2 is None else int(gen2),
  445. None if rounds is None else int(rounds),
  446. tool,
  447. all_backends,
  448. dumpfile)
  449. def main():
  450. scfg = parse_args()
  451. addr_type = MMGenAddrType(proto=proto, id_str=cfg.type or proto.dfl_mmtype)
  452. match scfg.test:
  453. case 'ab':
  454. protos = get_protos(proto, addr_type, scfg.tool) if cfg.all_coins else [proto]
  455. for p in protos:
  456. ab_test(p, scfg)
  457. case 'speed' | 'dump':
  458. kg = KeyGenerator(cfg, proto, addr_type.pubkey_type, backend=scfg.gen1)
  459. ag = AddrGenerator(cfg, proto, addr_type)
  460. match scfg.test:
  461. case 'speed':
  462. speed_test(proto, kg, ag, scfg.rounds)
  463. case 'dump':
  464. dump_test(proto, kg, ag, scfg.dumpfile)
  465. if saved_results:
  466. import json
  467. with open(results_file, 'w') as fp:
  468. fp.write(json.dumps(saved_results, indent=4))
  469. from subprocess import run, PIPE, DEVNULL
  470. from collections import namedtuple
  471. from mmgen.protocol import init_proto, CoinProtocol
  472. from mmgen.altcoin.params import init_genonly_altcoins
  473. from test.altcointest import TestCoinInfo as cinfo
  474. from mmgen.key import PrivKey
  475. from mmgen.addr import MMGenAddrType
  476. from mmgen.addrgen import KeyGenerator, AddrGenerator
  477. from mmgen.keygen import get_backends
  478. from test.include.common import getrand, get_ethkey, set_globals
  479. gtr = namedtuple('gen_tool_result', ['wif', 'addr', 'viewkey'])
  480. sd = namedtuple('saved_data_item', ['reduced', 'wif', 'addr', 'viewkey'])
  481. sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]
  482. cfg = Config(opts_data=opts_data)
  483. set_globals(cfg)
  484. qmsg = cfg._util.qmsg
  485. qmsg_r = cfg._util.qmsg_r
  486. vmsg = cfg._util.vmsg
  487. proto = cfg._proto
  488. if proto.coin in ('ETH', 'ETC', 'XMR'):
  489. from mmgen.util2 import load_cryptodome
  490. load_cryptodome()
  491. if __name__ == '__main__':
  492. from mmgen.main import launch
  493. launch(func=main)