altcointest.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
  4. # Copyright (C)2013-2023 The MMGen Project <mmgen@tuta.io>
  5. # Licensed under the GNU General Public License, Version 3:
  6. # https://www.gnu.org/licenses
  7. # Public project repositories:
  8. # https://github.com/mmgen/mmgen-wallet
  9. # https://gitlab.com/mmgen/mmgen-wallet
  10. """
  11. altcointest.py - Test constants for Bitcoin-derived altcoins
  12. """
  13. import sys
  14. try:
  15. from include import test_init
  16. except ImportError:
  17. from test.include import test_init
  18. from mmgen.cfg import gc,Config
  19. from mmgen.util import msg
  20. from mmgen.altcoin.params import CoinInfo
  21. def test_equal(desc,a,b,*cdata):
  22. if type(a) is int:
  23. a = hex(a)
  24. b = hex(b)
  25. (network,coin,_,b_desc,verbose) = cdata
  26. if verbose:
  27. msg(f' {desc:20}: {a!r}')
  28. if a != b:
  29. raise ValueError(
  30. f'{desc.capitalize()}s for {coin.upper()} {network} do not match:\n CoinInfo: {a}\n {b_desc}: {b}' )
  31. class TestCoinInfo(CoinInfo):
  32. # Sources (see CoinInfo) that are in agreement for these coins
  33. # No check for segwit, p2sh check skipped if source doesn't support it
  34. cross_checks = {
  35. '2GIVE': ['wn'],
  36. '42': ['vg','wn'],
  37. '611': ['wn'],
  38. 'AC': ['lb','vg'],
  39. 'ACOIN': ['wn'],
  40. 'ALF': ['wn'],
  41. 'ANC': ['vg','wn'],
  42. 'APEX': ['wn'],
  43. 'ARCO': ['wn'],
  44. 'ARG': ['pc'],
  45. 'AUR': ['vg','wn'],
  46. 'BCH': ['wn'],
  47. 'BLK': ['lb','vg','wn'],
  48. 'BQC': ['vg','wn'],
  49. 'BSTY': ['wn'],
  50. 'BTC': ['lb','vg','wn'],
  51. 'BTCD': ['lb','vg','wn'],
  52. 'BUCKS': ['wn'],
  53. 'CASH': ['wn'],
  54. 'CBX': ['wn'],
  55. 'CCN': ['lb','vg','wn'],
  56. 'CDN': ['lb','vg','wn'],
  57. 'CHC': ['wn'],
  58. 'CLAM': ['lb','vg'],
  59. 'CON': ['vg','wn'],
  60. 'CPC': ['wn'],
  61. 'DASH': ['lb','pc','vg','wn'],
  62. 'DCR': ['pc'],
  63. 'DFC': ['pc'],
  64. 'DGB': ['lb','vg'],
  65. 'DGC': ['lb','vg','wn'],
  66. 'DOGE': ['lb','pc','vg','wn'],
  67. 'DOGED': ['lb','vg','wn'],
  68. 'DOPE': ['lb','vg'],
  69. 'DVC': ['vg','wn'],
  70. 'EFL': ['lb','vg','wn'],
  71. 'EMC': ['vg'],
  72. 'EMD': ['wn'],
  73. 'ESP': ['wn'],
  74. 'FAI': ['pc'],
  75. 'FC2': ['wn'],
  76. 'FIBRE': ['wn'],
  77. 'FJC': ['wn'],
  78. 'FLO': ['wn'],
  79. 'FLT': ['wn'],
  80. 'FST': ['wn'],
  81. 'FTC': ['lb','pc','vg','wn'],
  82. 'GCR': ['lb','vg'],
  83. 'GOOD': ['wn'],
  84. 'GRC': ['vg','wn'],
  85. 'GUN': ['vg','wn'],
  86. 'HAM': ['vg','wn'],
  87. 'HTML5': ['wn'],
  88. 'HYP': ['wn'],
  89. 'ICASH': ['wn'],
  90. 'INFX': ['wn'],
  91. 'IPC': ['wn'],
  92. 'JBS': ['lb','pc','vg','wn'],
  93. 'JUDGE': ['wn'],
  94. 'LANA': ['wn'],
  95. 'LAT': ['wn'],
  96. 'LDOGE': ['wn'],
  97. 'LMC': ['wn'],
  98. 'LTC': ['lb','vg','wn'],
  99. 'MARS': ['wn'],
  100. 'MEC': ['pc','wn'],
  101. 'MINT': ['wn'],
  102. 'MOBI': ['wn'],
  103. 'MONA': ['lb','vg'],
  104. 'MOON': ['wn'],
  105. 'MUE': ['lb','vg'],
  106. 'MXT': ['wn'],
  107. 'MYR': ['pc'],
  108. 'MYRIAD': ['vg','wn'],
  109. 'MZC': ['lb','pc','vg','wn'],
  110. 'NEOS': ['lb','vg'],
  111. 'NEVA': ['wn'],
  112. 'NKA': ['wn'],
  113. 'NLG': ['vg','wn'],
  114. 'NMC': ['lb','vg'],
  115. 'NVC': ['lb','vg','wn'],
  116. 'OK': ['lb','vg'],
  117. 'OMC': ['vg','wn'],
  118. 'ONION': ['vg','wn'],
  119. 'PART': ['wn'],
  120. 'PINK': ['vg','wn'],
  121. 'PIVX': ['wn'],
  122. 'PKB': ['lb','vg','wn'],
  123. 'PND': ['lb','vg','wn'],
  124. 'POT': ['lb','vg','wn'],
  125. 'PPC': ['lb','vg','wn'],
  126. 'PTC': ['vg','wn'],
  127. 'PXC': ['wn'],
  128. 'QRK': ['wn'],
  129. 'RAIN': ['wn'],
  130. 'RBT': ['wn'],
  131. 'RBY': ['lb','vg'],
  132. 'RDD': ['vg','wn'],
  133. 'RIC': ['pc','vg','wn'],
  134. 'SDC': ['lb','vg'],
  135. 'SIB': ['wn'],
  136. 'SMLY': ['wn'],
  137. 'SONG': ['wn'],
  138. 'SPR': ['vg','wn'],
  139. 'START': ['lb','vg'],
  140. 'SYS': ['wn'],
  141. 'TAJ': ['wn'],
  142. 'TIT': ['wn'],
  143. 'TPC': ['lb','vg'],
  144. 'TRC': ['wn'],
  145. 'TTC': ['wn'],
  146. 'TX': ['wn'],
  147. 'UNO': ['pc','vg','wn'],
  148. 'VIA': ['lb','pc','vg','wn'],
  149. 'VPN': ['lb','vg'],
  150. 'VTC': ['lb','vg','wn'],
  151. 'WDC': ['vg','wn'],
  152. 'WISC': ['wn'],
  153. 'WKC': ['vg','wn'],
  154. 'WSX': ['wn'],
  155. 'XCN': ['wn'],
  156. 'XGB': ['wn'],
  157. 'XPM': ['lb','vg','wn'],
  158. 'XST': ['wn'],
  159. 'XVC': ['wn'],
  160. 'ZET': ['wn'],
  161. 'ZOOM': ['lb','vg'],
  162. 'ZRC': ['lb','vg']
  163. }
  164. @classmethod
  165. def verify_leading_symbols(cls,quiet=False,verbose=False):
  166. for network in ('mainnet','testnet'):
  167. for coin in [e.symbol for e in cls.coin_constants[network]]:
  168. e = cls.get_entry(coin,network)
  169. cdata = (network,coin,e,'Computed value',verbose)
  170. if not quiet:
  171. msg(f'{coin} {network}')
  172. vn_info = e.p2pkh_info
  173. ret = cls.find_addr_leading_symbol(vn_info[0])
  174. test_equal('P2PKH leading symbol',vn_info[1],ret,*cdata)
  175. vn_info = e.p2sh_info
  176. if vn_info:
  177. ret = cls.find_addr_leading_symbol(vn_info[0])
  178. test_equal('P2SH leading symbol',vn_info[1],ret,*cdata)
  179. @classmethod
  180. def verify_core_coin_data(cls,cfg,quiet=False,verbose=False):
  181. from mmgen.protocol import CoinProtocol,init_proto
  182. for network in ('mainnet','testnet'):
  183. for coin in gc.core_coins:
  184. e = cls.get_entry(coin,network)
  185. if e:
  186. proto = init_proto( cfg, coin, network=network )
  187. cdata = (network,coin,e,type(proto).__name__,verbose)
  188. if not quiet:
  189. msg(f'Verifying {coin.upper()} {network}')
  190. if coin != 'bch': # TODO
  191. test_equal('coin name',e.name,proto.name,*cdata)
  192. if e.trust_level != -1:
  193. test_equal('Trust level',e.trust_level,CoinProtocol.coins[coin].trust_level,*cdata)
  194. test_equal(
  195. 'WIF version number',
  196. e.wif_ver_num,
  197. int.from_bytes(proto.wif_ver_bytes['std'],'big'),
  198. *cdata )
  199. test_equal(
  200. 'P2PKH version number',
  201. e.p2pkh_info[0],
  202. int.from_bytes(proto.addr_fmt_to_ver_bytes['p2pkh'],'big'),
  203. *cdata )
  204. test_equal(
  205. 'P2SH version number',
  206. e.p2sh_info[0],
  207. int.from_bytes(proto.addr_fmt_to_ver_bytes['p2sh'],'big'),
  208. *cdata )
  209. # Data is one of the coin_constants lists above. Normalize ints to hex of correct width, add
  210. # missing leading letters, set trust level from external_tests.
  211. # Insert a coin entry from outside source, set version info leading letters to '?' and trust level
  212. # to 0, then run TestCoinInfo.fix_table(data). 'has_segwit' field is updated manually for now.
  213. @classmethod
  214. def fix_table(cls,data):
  215. import re
  216. def myhex(n):
  217. return '0x{:0{}x}'.format(n,2 if n < 256 else 4)
  218. def fix_ver_info(e,k):
  219. e[k] = list(e[k])
  220. e[k][0] = myhex(e[k][0])
  221. s1 = cls.find_addr_leading_symbol(int(e[k][0][2:],16))
  222. m = f'Fixing leading address letter for coin {e["symbol"]} ({e[k][1]!r} --> {s1})'
  223. if e[k][1] != '?':
  224. assert s1 == e[k][1], f'First letters do not match! {m}'
  225. else:
  226. msg(m)
  227. e[k][1] = s1
  228. e[k] = tuple(e[k])
  229. old_sym = None
  230. for sym in sorted([e.symbol for e in data]):
  231. if sym == old_sym:
  232. msg(f'{sym!r}: duplicate coin symbol in data!')
  233. sys.exit(2)
  234. old_sym = sym
  235. tt = cls.create_trust_table()
  236. name_w = max(len(e.name) for e in data)
  237. fs = '\t({:%s} {:10} {:7} {:17} {:17} {:6} {}),' % (name_w+3)
  238. for e in data:
  239. e = e._asdict()
  240. e['wif_ver_num'] = myhex(e['wif_ver_num'])
  241. sym,trust = e['symbol'],e['trust_level']
  242. fix_ver_info(e,'p2pkh_info')
  243. if isinstance(e['p2sh_info'],tuple):
  244. fix_ver_info(e,'p2sh_info')
  245. for k in e.keys():
  246. e[k] = repr(e[k])
  247. e[k] = re.sub(r"'0x(..)'",r'0x\1',e[k])
  248. e[k] = re.sub(r"'0x(....)'",r'0x\1',e[k])
  249. e[k] = re.sub(r' ',r'',e[k]) + ('',',')[k != 'trust_level']
  250. if trust != -1:
  251. if sym in tt:
  252. src = tt[sym]
  253. if src != trust:
  254. msg(f'Updating trust for coin {sym!r}: {trust} -> {src}')
  255. e['trust_level'] = src
  256. else:
  257. if trust != 0:
  258. msg(f'Downgrading trust for coin {sym!r}: {trust} -> 0')
  259. e['trust_level'] = 0
  260. if sym in cls.cross_checks:
  261. if int(e['trust_level']) == 0 and len(cls.cross_checks[sym]) > 1:
  262. msg(f'Upgrading trust for coin {sym!r}: {e["trust_level"]} -> 1')
  263. e['trust_level'] = 1
  264. print(fs.format(*e.values()))
  265. msg(f'Processed {len(data)} entries')
  266. @classmethod
  267. def find_addr_leading_symbol(cls,ver_num,verbose=False):
  268. if ver_num == 0:
  269. return '1'
  270. def phash2addr(ver_num,pk_hash):
  271. from mmgen.proto.btc.common import b58chk_encode
  272. bl = ver_num.bit_length()
  273. ver_bytes = int.to_bytes(ver_num,bl//8 + bool(bl%8),'big')
  274. return b58chk_encode(ver_bytes + pk_hash)
  275. low = phash2addr(ver_num,b'\x00'*20)
  276. high = phash2addr(ver_num,b'\xff'*20)
  277. if verbose:
  278. print('low address: ' + low)
  279. print('high address: ' + high)
  280. l1,h1 = low[0],high[0]
  281. return (l1,h1) if l1 != h1 else l1
  282. @classmethod
  283. def print_symbols(cls,include_names=False,reverse=False):
  284. for e in cls.coin_constants['mainnet']:
  285. if reverse:
  286. print(f'{e.symbol:6} {e.name}')
  287. else:
  288. name_w = max(len(e.name) for e in cls.coin_constants['mainnet'])
  289. print((f'{e.name:{name_w}} ' if include_names else '') + e.symbol)
  290. @classmethod
  291. def create_trust_table(cls):
  292. tt = {}
  293. mn = cls.external_tests['mainnet']
  294. for ext_prog in mn:
  295. assert len(set(mn[ext_prog])) == len(mn[ext_prog]), f'Duplicate entry in {ext_prog!r}!'
  296. for coin in mn[ext_prog]:
  297. if coin in tt:
  298. tt[coin] += 1
  299. else:
  300. tt[coin] = 1
  301. for k in cls.trust_override:
  302. tt[k] = cls.trust_override[k]
  303. return tt
  304. trust_override = {'BTC':3,'BCH':3,'LTC':3,'DASH':1,'EMC':2}
  305. @classmethod
  306. def get_test_support(cls,coin,addr_type,network,toolname=None,verbose=False):
  307. """
  308. If requested tool supports coin/addr_type/network triplet, return tool name.
  309. If 'tool' is None, return tool that supports coin/addr_type/network triplet.
  310. Return None on failure.
  311. """
  312. all_tools = [toolname] if toolname else list(cls.external_tests[network].keys())
  313. coin = coin.upper()
  314. for tool in all_tools:
  315. if coin in cls.external_tests[network][tool]:
  316. break
  317. else:
  318. if verbose:
  319. m1 = 'Requested tool {t!r} does not support coin {c} on network {n}'
  320. m2 = 'No test tool found for coin {c} on network {n}'
  321. msg((m1 if toolname else m2).format(t=tool,c=coin,n=network))
  322. return None
  323. if addr_type == 'zcash_z':
  324. if toolname in (None,'zcash-mini'):
  325. return 'zcash-mini'
  326. else:
  327. if verbose:
  328. msg(f"Address type {addr_type!r} supported only by tool 'zcash-mini'")
  329. return None
  330. try:
  331. bl = cls.external_tests_blacklist[addr_type][tool]
  332. except:
  333. pass
  334. else:
  335. if bl is True or coin in bl:
  336. if verbose:
  337. msg(f'Tool {tool!r} blacklisted for coin {coin}, addr_type {addr_type!r}')
  338. return None
  339. if toolname: # skip whitelists
  340. return tool
  341. if addr_type in ('segwit','bech32'):
  342. st = cls.external_tests_segwit_whitelist
  343. if addr_type in st and coin in st[addr_type]:
  344. return tool
  345. else:
  346. if verbose:
  347. m1 = 'Requested tool {t!r} does not support coin {c}, addr_type {a!r}, on network {n}'
  348. m2 = 'No test tool found supporting coin {c}, addr_type {a!r}, on network {n}'
  349. msg((m1 if toolname else m2).format(t=tool,c=coin,n=network,a=addr_type))
  350. return None
  351. return tool
  352. external_tests = {
  353. 'mainnet': {
  354. # List in order of preference.
  355. # If 'tool' is not specified, the first tool supporting the coin will be selected.
  356. 'pycoin': (
  357. 'DASH', # only compressed
  358. 'BCH',
  359. 'BTC','LTC','VIA','FTC','DOGE','MEC',
  360. 'JBS','MZC','RIC','DFC','FAI','ARG','ZEC','DCR'),
  361. 'keyconv': (
  362. 'BCH',
  363. # broken: PIVX
  364. '42','AC','AIB','ANC','ARS','ATMOS','AUR','BLK','BQC','BTC','TEST','BTCD','CCC','CCN','CDN',
  365. 'CLAM','CNC','CNOTE','CON','CRW','DEEPONION','DGB','DGC','DMD','DOGED','DOGE','DOPE',
  366. 'DVC','EFL','EMC','EXCL','FAIR','FLOZ','FTC','GAME','GAP','GCR','GRC','GRS','GUN','HAM','HODL',
  367. 'IXC','JBS','LBRY','LEAF','LTC','MMC','MONA','MUE','MYRIAD','MZC','NEOS','NLG','NMC','NVC',
  368. 'NYAN','OK','OMC','PIGGY','PINK','PKB','PND','POT','PPC','PTC','PTS','QTUM','RBY','RDD',
  369. 'RIC','SCA','SDC','SKC','SPR','START','SXC','TPC','UIS','UNO','VIA','VPN','VTC','WDC','WKC',
  370. 'WUBS', 'XC', 'XPM', 'YAC', 'ZOOM', 'ZRC'),
  371. 'ethkey': ('ETH','ETC'),
  372. 'zcash-mini': ('ZEC',),
  373. 'monero-python': ('XMR',),
  374. },
  375. 'testnet': {
  376. 'pycoin': {
  377. 'DASH':'tDASH', # only compressed
  378. 'BCH':'XTN',
  379. 'BTC':'XTN','LTC':'XLT','VIA':'TVI','FTC':'FTX','DOGE':'XDT','DCR':'DCRT'
  380. },
  381. 'ethkey': {},
  382. 'keyconv': {}
  383. }
  384. }
  385. external_tests_segwit_whitelist = {
  386. # Whitelists apply to the *first* tool in cls.external_tests supporting the given coin/addr_type.
  387. # They're ignored if specific tool is requested.
  388. 'segwit': ('BTC',), # LTC Segwit broken on pycoin: uses old fmt
  389. 'bech32': ('BTC','LTC'),
  390. 'compressed': (
  391. 'BTC','LTC','VIA','FTC','DOGE','DASH','MEC','MYR','UNO',
  392. 'JBS','MZC','RIC','DFC','FAI','ARG','ZEC','DCR','ZEC'
  393. ),
  394. }
  395. external_tests_blacklist = {
  396. # Unconditionally block testing of the given coin/addr_type with given tool, or all coins if True
  397. 'legacy': {},
  398. 'segwit': { 'keyconv': True },
  399. 'bech32': { 'keyconv': True },
  400. }
  401. if __name__ == '__main__':
  402. opts_data = {
  403. 'text': {
  404. 'desc': 'Check altcoin data',
  405. 'usage':'[opts]',
  406. 'options': '-q, --quiet Be quieter\n-v, --verbose Be more verbose'
  407. }
  408. }
  409. cfg = Config( opts_data=opts_data, need_amt=False )
  410. msg('Checking CoinInfo WIF/P2PKH/P2SH version numbers and trust levels against protocol.py')
  411. TestCoinInfo.verify_core_coin_data( cfg, cfg.quiet, cfg.verbose )
  412. msg('Checking CoinInfo address leading symbols')
  413. TestCoinInfo.verify_leading_symbols( cfg.quiet, cfg.verbose )