ct_ref.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  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.cmdtest_py_d.ct_ref: Reference file tests for the cmdtest.py test suite
  20. """
  21. import os
  22. from mmgen.util import capfirst
  23. from mmgen.wallet import get_wallet_cls
  24. from ..include.common import (
  25. cfg,
  26. imsg_r,
  27. ok,
  28. joinpath,
  29. cmp_or_die,
  30. ref_kafile_pass,
  31. read_from_file,
  32. sample_text
  33. )
  34. from .common import (
  35. dfl_words_file,
  36. ref_dir,
  37. chksum_pat,
  38. pwfile,
  39. ref_bw_file_spc,
  40. ref_enc_fn,
  41. get_env_without_debug_vars,
  42. tool_enc_passwd,
  43. skip
  44. )
  45. from .ct_base import CmdTestBase
  46. from .ct_shared import CmdTestShared
  47. wpasswd = 'reference password'
  48. class CmdTestRef(CmdTestBase,CmdTestShared):
  49. 'saved reference address, password and transaction files'
  50. tmpdir_nums = [8]
  51. networks = ('btc','btc_tn','ltc','ltc_tn')
  52. passthru_opts = ('daemon_data_dir','rpc_port','coin','testnet')
  53. need_daemon = True
  54. sources = {
  55. 'ref_addrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.addrs',
  56. 'ref_segwitaddrfile':'98831F3A{}-S[1,31-33,500-501,1010-1011]{}.addrs',
  57. 'ref_bech32addrfile':'98831F3A{}-B[1,31-33,500-501,1010-1011]{}.addrs',
  58. 'ref_keyaddrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.akeys.mmenc',
  59. 'ref_viewkeyaddrfile': '98831F3A-XMR-M[1-3].vkeys',
  60. 'ref_passwdfile_b32_24': '98831F3A-фубар@crypto.org-b32-24[1,4,1100].pws',
  61. 'ref_passwdfile_b32_12': '98831F3A-фубар@crypto.org-b32-12[1,4,1100].pws',
  62. 'ref_passwdfile_b58_10': '98831F3A-фубар@crypto.org-b58-10[1,4,1100].pws',
  63. 'ref_passwdfile_b58_20': '98831F3A-фубар@crypto.org-b58-20[1,4,1100].pws',
  64. 'ref_passwdfile_hex_32': '98831F3A-фубар@crypto.org-hex-32[1,4,1100].pws',
  65. 'ref_passwdfile_hex_48': '98831F3A-фубар@crypto.org-hex-48[1,4,1100].pws',
  66. 'ref_passwdfile_hex_64': '98831F3A-фубар@crypto.org-hex-64[1,4,1100].pws',
  67. 'ref_passwdfile_bip39_12': '98831F3A-фубар@crypto.org-bip39-12[1,4,1100].pws',
  68. 'ref_passwdfile_bip39_18': '98831F3A-фубар@crypto.org-bip39-18[1,4,1100].pws',
  69. 'ref_passwdfile_bip39_24': '98831F3A-фубар@crypto.org-bip39-24[1,4,1100].pws',
  70. 'ref_passwdfile_xmrseed_25': '98831F3A-фубар@crypto.org-xmrseed-25[1,4,1100].pws',
  71. 'ref_passwdfile_hex2bip39_12': '98831F3A-фубар@crypto.org-hex2bip39-12[1,4,1100].pws',
  72. 'ref_tx_file': { # data shared with ref_altcoin, autosign
  73. 'btc': ('0B8D5A[15.31789,14,tl=1320969600].rawtx',
  74. '0C7115[15.86255,14,tl=1320969600].testnet.rawtx'),
  75. 'ltc': ('AF3CDF-LTC[620.76194,1453,tl=1320969600].rawtx',
  76. 'A5A1E0-LTC[1454.64322,1453,tl=1320969600].testnet.rawtx'),
  77. 'bch': ('460D4D-BCH[10.19764,tl=1320969600].rawtx',
  78. '359FD5-BCH[6.68868,tl=1320969600].testnet.rawtx'),
  79. 'eth': ('88FEFD-ETH[23.45495,40000].rawtx',
  80. 'B472BD-ETH[23.45495,40000].testnet.rawtx'),
  81. 'mm1': ('5881D2-MM1[1.23456,50000].rawtx',
  82. '6BDB25-MM1[1.23456,50000].testnet.rawtx'),
  83. 'etc': ('ED3848-ETC[1.2345,40000].rawtx','')
  84. },
  85. }
  86. chk_data = {
  87. 'ref_subwallet_sid': {
  88. '98831F3A:32L':'D66B4885',
  89. '98831F3A:1S':'20D95B09',
  90. },
  91. 'ref_addrfile_chksum': {
  92. 'btc': ('6FEF 6FB9 7B13 5D91','424E 4326 CFFE 5F51'),
  93. 'ltc': ('AD52 C3FE 8924 AAF0','4EBE 2E85 E969 1B30'),
  94. },
  95. 'ref_segwitaddrfile_chksum': {
  96. 'btc': ('06C1 9C87 F25C 4EE6','072C 8B07 2730 CB7A'),
  97. 'ltc': ('63DF E42A 0827 21C3','5DD1 D186 DBE1 59F2'),
  98. },
  99. 'ref_bech32addrfile_chksum': {
  100. 'btc': ('9D2A D4B6 5117 F02E','0527 9C39 6C1B E39A'),
  101. 'ltc': ('FF1C 7939 5967 AB82','ED3D 8AA4 BED4 0B40'),
  102. },
  103. 'ref_keyaddrfile_chksum': {
  104. 'btc': ('9F2D D781 1812 8BAD','88CC 5120 9A91 22C2'),
  105. 'ltc': ('B804 978A 8796 3ED4','98B5 AC35 F334 0398'),
  106. },
  107. 'ref_passwdfile_b32_12_chksum': '7252 CD8D EF0D 3DB1',
  108. 'ref_passwdfile_b32_24_chksum': '8D56 3845 A072 A5B9',
  109. 'ref_passwdfile_b58_10_chksum': '534F CC1A 6701 9FED',
  110. 'ref_passwdfile_b58_20_chksum': 'DDD9 44B0 CA28 183F',
  111. 'ref_passwdfile_hex_32_chksum': '05C7 3678 E25E BC32',
  112. 'ref_passwdfile_hex_48_chksum': '7DBB FFD0 633E DE6F',
  113. 'ref_passwdfile_hex_64_chksum': 'F11D CB0A 8AE3 4D21',
  114. 'ref_passwdfile_bip39_12_chksum': 'BF57 02A3 5229 CF18',
  115. 'ref_passwdfile_bip39_18_chksum': '31D3 1656 B7DC 27CF',
  116. 'ref_passwdfile_bip39_24_chksum': 'E565 3A59 7D91 4671',
  117. 'ref_passwdfile_xmrseed_25_chksum': 'B488 21D3 4539 968D',
  118. 'ref_passwdfile_hex2bip39_12_chksum': '93AD 4AE2 03D1 8A0A',
  119. }
  120. cmd_group = ( # TODO: move to tooltest2
  121. ('ref_words_to_subwallet_chk1','subwallet generation from reference words file (long subseed)'),
  122. ('ref_words_to_subwallet_chk2','subwallet generation from reference words file (short subseed)'),
  123. ('ref_subwallet_addrgen1','subwallet address file generation (long subseed)'),
  124. ('ref_subwallet_addrgen2','subwallet address file generation (short subseed)'),
  125. ('ref_subwallet_keygen1','subwallet key-address file generation (long subseed)'),
  126. ('ref_subwallet_keygen2','subwallet key-address file generation (short subseed)'),
  127. ('ref_addrfile_chk', 'saved reference address file'),
  128. ('ref_segwitaddrfile_chk','saved reference address file (segwit)'),
  129. ('ref_bech32addrfile_chk','saved reference address file (bech32)'),
  130. ('ref_keyaddrfile_chk','saved reference key-address file'),
  131. ('ref_passwdfile_chk_b58_20','saved reference password file (base58, 20 chars)'),
  132. ('ref_passwdfile_chk_b58_10','saved reference password file (base58, 10 chars)'),
  133. ('ref_passwdfile_chk_b32_24','saved reference password file (base32, 24 chars)'),
  134. ('ref_passwdfile_chk_b32_12','saved reference password file (base32, 12 chars)'),
  135. ('ref_passwdfile_chk_hex_32','saved reference password file (hexadecimal, 32 chars)'),
  136. ('ref_passwdfile_chk_hex_48','saved reference password file (hexadecimal, 48 chars)'),
  137. ('ref_passwdfile_chk_hex_64','saved reference password file (hexadecimal, 64 chars)'),
  138. ('ref_passwdfile_chk_bip39_12','saved reference password file (BIP39, 12 words)'),
  139. ('ref_passwdfile_chk_bip39_18','saved reference password file (BIP39, 18 words)'),
  140. ('ref_passwdfile_chk_bip39_24','saved reference password file (BIP39, 24 words)'),
  141. ('ref_passwdfile_chk_xmrseed_25','saved reference password file (Monero new-style mnemonic, 25 words)'),
  142. ('ref_passwdfile_chk_hex2bip39_12','saved reference password file (hex-to-BIP39, 12 words)'),
  143. # Create the fake inputs:
  144. # ('txcreate8', 'transaction creation (8)'),
  145. ('ref_tx_chk', 'signing saved reference tx file'),
  146. ('ref_brain_chk_spc3', 'saved brainwallet (non-standard spacing)'),
  147. ('ref_dieroll_chk_seedtruncate','saved dieroll wallet with extra entropy bits'),
  148. ('ref_tool_decrypt', 'decryption of saved MMGen-encrypted file'),
  149. )
  150. @property
  151. def nw_desc(self):
  152. return '{} {}'.format(
  153. self.proto.coin,
  154. ('Mainnet','Testnet')[self.proto.testnet] )
  155. def _get_ref_subdir_by_coin(self,coin):
  156. return {'btc': '',
  157. 'bch': '',
  158. 'ltc': 'litecoin',
  159. 'eth': 'ethereum',
  160. 'etc': 'ethereum_classic',
  161. 'xmr': 'monero',
  162. 'zec': 'zcash',
  163. 'dash': 'dash' }[coin.lower()]
  164. @property
  165. def ref_subdir(self):
  166. return self._get_ref_subdir_by_coin(self.proto.coin)
  167. def ref_words_to_subwallet_chk1(self):
  168. return self.ref_words_to_subwallet_chk('32L')
  169. def ref_words_to_subwallet_chk2(self):
  170. return self.ref_words_to_subwallet_chk('1S')
  171. def ref_words_to_subwallet_chk(self,ss_idx):
  172. wf = dfl_words_file
  173. ocls = get_wallet_cls('words')
  174. args = ['-d',self.tr.trash_dir,'-o',ocls.fmt_codes[-1],wf,ss_idx]
  175. t = self.spawn('mmgen-subwalletgen',args,extra_desc='(generate subwallet)')
  176. t.expect(f'Generating subseed {ss_idx}')
  177. chk_sid = self.chk_data['ref_subwallet_sid'][f'98831F3A:{ss_idx}']
  178. fn = t.written_to_file(capfirst(ocls.desc))
  179. assert chk_sid in fn,f'incorrect filename: {fn} (does not contain {chk_sid})'
  180. ok()
  181. t = self.spawn('mmgen-walletchk',[fn],extra_desc='(check subwallet)')
  182. t.expect(r'Valid MMGen native mnemonic data for Seed ID ([0-9A-F]*)\b',regex=True)
  183. sid = t.p.match.group(1)
  184. assert sid == chk_sid,f'subseed ID {sid} does not match expected value {chk_sid}'
  185. return t
  186. def ref_subwallet_addrgen(self,ss_idx,target='addr'):
  187. wf = dfl_words_file
  188. args = ['-d',self.tr.trash_dir,'--subwallet='+ss_idx,wf,'1-10']
  189. t = self.spawn(f'mmgen-{target}gen',args)
  190. t.expect(f'Generating subseed {ss_idx}')
  191. chk_sid = self.chk_data['ref_subwallet_sid'][f'98831F3A:{ss_idx}']
  192. assert chk_sid == t.expect_getend('Checksum for .* data ',regex=True)[:8]
  193. if target == 'key':
  194. t.expect('Encrypt key list? (y/N): ','n')
  195. fn = t.written_to_file(('Addresses','Secret keys')[target=='key'])
  196. assert chk_sid in fn,f'incorrect filename: {fn} (does not contain {chk_sid})'
  197. return t
  198. def ref_subwallet_addrgen1(self):
  199. return self.ref_subwallet_addrgen('32L')
  200. def ref_subwallet_addrgen2(self):
  201. return self.ref_subwallet_addrgen('1S')
  202. def ref_subwallet_keygen1(self):
  203. return self.ref_subwallet_addrgen('32L',target='key')
  204. def ref_subwallet_keygen2(self):
  205. return self.ref_subwallet_addrgen('1S',target='key')
  206. def ref_addrfile_chk(
  207. self,
  208. ftype = 'addr',
  209. coin = None,
  210. subdir = None,
  211. pfx = None,
  212. mmtype = None,
  213. id_key = None,
  214. pat = None):
  215. pat = pat or f'{self.nw_desc}.*Legacy'
  216. af_key = f'ref_{ftype}file' + ('_' + id_key if id_key else '')
  217. af_fn = CmdTestRef.sources[af_key].format(pfx or self.altcoin_pfx,'' if coin else self.tn_ext)
  218. af = joinpath(ref_dir,(subdir or self.ref_subdir,'')[ftype=='passwd'],af_fn)
  219. coin_arg = [] if coin is None else ['--coin='+coin]
  220. tool_cmd = ftype.replace('segwit','').replace('bech32','')+'file_chksum'
  221. t = self.spawn( 'mmgen-tool', coin_arg + ['--verbose','-p1',tool_cmd,af] )
  222. if ftype == 'keyaddr':
  223. t.do_decrypt_ka_data(pw=ref_kafile_pass,have_yes_opt=True)
  224. chksum_key = '_'.join([af_key,'chksum'] + ([coin.lower()] if coin else []) + ([mmtype] if mmtype else []))
  225. rc = self.chk_data[chksum_key]
  226. ref_chksum = rc if (ftype == 'passwd' or coin) else rc[self.proto.base_coin.lower()][self.proto.testnet]
  227. if pat:
  228. t.expect(pat,regex=True)
  229. t.expect(chksum_pat,regex=True)
  230. m = t.p.match.group(0)
  231. cmp_or_die(ref_chksum,m)
  232. return t
  233. def ref_segwitaddrfile_chk(self):
  234. if not 'S' in self.proto.mmtypes:
  235. return skip(f'not supported by {self.proto.cls_name} protocol')
  236. return self.ref_addrfile_chk(ftype='segwitaddr',pat=f'{self.nw_desc}.*Segwit')
  237. def ref_bech32addrfile_chk(self):
  238. if not 'B' in self.proto.mmtypes:
  239. return skip(f'not supported by {self.proto.cls_name} protocol')
  240. return self.ref_addrfile_chk(ftype='bech32addr',pat=f'{self.nw_desc}.*Bech32')
  241. def ref_keyaddrfile_chk(self):
  242. return self.ref_addrfile_chk(ftype='keyaddr')
  243. def ref_passwdfile_chk(self,key,pat):
  244. return self.ref_addrfile_chk(ftype='passwd',id_key=key,pat=pat)
  245. def ref_passwdfile_chk_b58_20(self):
  246. return self.ref_passwdfile_chk(key='b58_20',pat=r'Base58.*len.* 20\b')
  247. def ref_passwdfile_chk_b58_10(self):
  248. return self.ref_passwdfile_chk(key='b58_10',pat=r'Base58.*len.* 10\b')
  249. def ref_passwdfile_chk_b32_24(self):
  250. return self.ref_passwdfile_chk(key='b32_24',pat=r'Base32.*len.* 24\b')
  251. def ref_passwdfile_chk_b32_12(self):
  252. return self.ref_passwdfile_chk(key='b32_12',pat=r'Base32.*len.* 12\b')
  253. def ref_passwdfile_chk_hex_32(self):
  254. return self.ref_passwdfile_chk(key='hex_32',pat=r'Hexadec.*len.* 32\b')
  255. def ref_passwdfile_chk_hex_48(self):
  256. return self.ref_passwdfile_chk(key='hex_48',pat=r'Hexadec.*len.* 48\b')
  257. def ref_passwdfile_chk_hex_64(self):
  258. return self.ref_passwdfile_chk(key='hex_64',pat=r'Hexadec.*len.* 64\b')
  259. def ref_passwdfile_chk_bip39_12(self):
  260. return self.ref_passwdfile_chk(key='bip39_12',pat=r'BIP39.*len.* 12\b')
  261. def ref_passwdfile_chk_bip39_18(self):
  262. return self.ref_passwdfile_chk(key='bip39_18',pat=r'BIP39.*len.* 18\b')
  263. def ref_passwdfile_chk_bip39_24(self):
  264. return self.ref_passwdfile_chk(key='bip39_24',pat=r'BIP39.*len.* 24\b')
  265. def ref_passwdfile_chk_xmrseed_25(self):
  266. return self.ref_passwdfile_chk(key='xmrseed_25',pat=r'Mon.*len.* 25\b')
  267. def ref_passwdfile_chk_hex2bip39_12(self):
  268. return self.ref_passwdfile_chk(key='hex2bip39_12',pat=r'BIP39.*len.* 12\b')
  269. def ref_tx_chk(self):
  270. fn = self.sources['ref_tx_file'][self.proto.coin.lower()][bool(self.tn_ext)]
  271. if not fn:
  272. return
  273. tf = joinpath(ref_dir,self.ref_subdir,fn)
  274. wf = dfl_words_file
  275. self.write_to_tmpfile(pwfile,wpasswd)
  276. return self.txsign(wf,tf,save=False,has_label=True,view='y')
  277. def ref_brain_chk_spc3(self):
  278. return self.ref_brain_chk(bw_file=ref_bw_file_spc)
  279. def ref_dieroll_chk_seedtruncate(self):
  280. wf = joinpath(ref_dir,'overflow128.b6d')
  281. return self.walletchk(wf,sid='8EC6D4A2')
  282. def ref_tool_decrypt(self):
  283. f = joinpath(ref_dir,ref_enc_fn)
  284. dec_file = joinpath(self.tmpdir,'famous.txt')
  285. t = self.spawn(
  286. 'mmgen-tool',
  287. ['-q','decrypt',f,'outfile='+dec_file,'hash_preset=1'],
  288. env = os.environ if cfg.debug_utf8 else get_env_without_debug_vars() )
  289. t.passphrase('data',tool_enc_passwd)
  290. t.written_to_file('Decrypted data')
  291. dec_txt = read_from_file(dec_file)
  292. imsg_r(dec_txt)
  293. cmp_or_die(sample_text+'\n',dec_txt) # file adds a newline to sample_text
  294. return t