ct_seedsplit.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  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.cmdtest_d.ct_seedsplit: Seed split/join tests for the cmdtest.py test suite
  20. """
  21. import os
  22. from mmgen.wallet import get_wallet_cls
  23. from mmgen.util import capfirst
  24. from ..include.common import strip_ansi_escapes, cmp_or_die
  25. from .common import get_file_with_ext
  26. from .ct_base import CmdTestBase
  27. ref_wf = 'test/ref/98831F3A.bip39'
  28. ref_sid = '98831F3A'
  29. wpasswd = 'abc'
  30. sh1_passwd = 'xyz'
  31. dfl_wcls = get_wallet_cls('mmgen')
  32. class CmdTestSeedSplit(CmdTestBase):
  33. 'splitting and joining seeds'
  34. networks = ('btc',)
  35. tmpdir_nums = [23]
  36. color = True
  37. cmd_group = (
  38. ('ss_walletgen', 'wallet generation'),
  39. ('ss_2way_A_dfl1', '2-way seed split (share A)'),
  40. ('ss_2way_B_dfl1', '2-way seed split (share B)'),
  41. ('ss_2way_join_dfl1', '2-way seed join'),
  42. ('ss_2way_A_dfl2', '2-way seed split ‘default’ (share A)'),
  43. ('ss_2way_B_dfl2', '2-way seed split ‘default’ (share B)'),
  44. ('ss_2way_join_dfl2', '2-way seed join ‘default’'),
  45. ('ss_2way_A_alice', '2-way seed split ‘alice’ (share A)'),
  46. ('ss_2way_B_alice', '2-way seed split ‘alice’ (share B)'),
  47. ('ss_2way_join_alice', '2-way seed join ‘alice’'),
  48. ('ss_2way_join_alice_mix', '2-way seed join ‘alice’ (out of order)'),
  49. ('ss_2way_A_dfl_master3', '2-way seed split with master share #3 (share A)'),
  50. ('ss_2way_B_dfl_master3', '2-way seed split with master share #3 (share B)'),
  51. ('ss_2way_join_dfl_master3', '2-way seed join with master share #3'),
  52. ('ss_2way_A_dfl_usw', '2-way seed split of user-specified wallet (share A)'),
  53. ('ss_2way_B_dfl_usw', '2-way seed split of user-specified wallet (share B)'),
  54. ('ss_2way_join_dfl_usw', '2-way seed join of user-specified wallet'),
  55. ('ss_3way_A_dfl', '3-way seed split (share A)'),
  56. ('ss_3way_B_dfl', '3-way seed split (share B)'),
  57. ('ss_3way_C_dfl', '3-way seed split (share C)'),
  58. ('ss_3way_join_dfl', '3-way seed join'),
  59. ('ss_3way_join_dfl_mix', '3-way seed join (out of order)'),
  60. ('ss_3way_A_foobar_master7', '3-way seed split ‘φυβαρ’ with master share #7 (share A)'),
  61. ('ss_3way_B_foobar_master7', '3-way seed split ‘φυβαρ’ with master share #7 (share B)'),
  62. ('ss_3way_C_foobar_master7', '3-way seed split ‘φυβαρ’ with master share #7 (share C)'),
  63. ('ss_3way_join_foobar_master7', '3-way seed join ‘φυβαρ’ with master share #7'),
  64. ('ss_3way_join_foobar_master7_mix', '3-way seed join ‘φυβαρ’ with master share #7 (out of order)'),
  65. ('ss_3way_join_dfl_bad_invocation', 'bad invocation of ‘mmgen-seedjoin’ - --id-str with non-master join'),
  66. ('ss_bad_invocation1', 'bad invocation of ‘mmgen-seedsplit’ - no arguments'),
  67. ('ss_bad_invocation2', 'bad invocation of ‘mmgen-seedsplit’ - master share with split specifier'),
  68. ('ss_bad_invocation3', 'bad invocation of ‘mmgen-seedsplit’ - nonexistent file'),
  69. ('ss_bad_invocation4', 'bad invocation of ‘mmgen-seedsplit’ - invalid file extension'),
  70. ('ss_bad_invocation5', 'bad invocation of ‘mmgen-seedjoin’ - no arguments'),
  71. ('ss_bad_invocation6', 'bad invocation of ‘mmgen-seedjoin’ - one file argument'),
  72. ('ss_bad_invocation7', 'bad invocation of ‘mmgen-seedjoin’ - invalid file extension'),
  73. ('ss_bad_invocation8', 'bad invocation of ‘mmgen-seedjoin’ - nonexistent file'),
  74. ('ss_bad_invocation9', 'bad invocation of ‘mmgen-seedsplit’ - bad specifier'),
  75. ('ss_bad_invocation10', 'bad invocation of ‘mmgen-seedsplit’ - nonexistent file'),
  76. ('ss_bad_invocation11', 'bad invocation of ‘mmgen-seedsplit’ - invalid file extension'),
  77. )
  78. def get_tmp_subdir(self, subdir):
  79. return os.path.join(self.tmpdir, subdir)
  80. def ss_walletgen(self):
  81. t = self.spawn('mmgen-walletgen', ['-r0', '-p1'])
  82. t.passphrase_new('new '+dfl_wcls.desc, wpasswd)
  83. t.label()
  84. self.write_to_tmpfile('dfl.sid', strip_ansi_escapes(t.expect_getend('Seed ID: ')))
  85. t.expect('move it to the data directory? (Y/n): ', 'y')
  86. t.written_to_file(capfirst(dfl_wcls.desc))
  87. return t
  88. def ss_splt(self, tdir, ofmt, spec, add_args=[], wf=None, master=None):
  89. try:
  90. os.mkdir(self.get_tmp_subdir(tdir))
  91. except:
  92. pass
  93. t = self.spawn('mmgen-seedsplit',
  94. ['-q', '-d', self.get_tmp_subdir(tdir), '-r0', '-o', ofmt]
  95. + (['-L', (spec or 'label')] if ofmt == 'w' else [])
  96. + add_args
  97. + ([f'--master-share={master}'] if master else [])
  98. + ([wf] if wf else [])
  99. + ([spec] if spec else []))
  100. if not wf:
  101. t.passphrase(dfl_wcls.desc, wpasswd)
  102. if spec:
  103. from mmgen.seedsplit import SeedSplitSpecifier
  104. sss = SeedSplitSpecifier(spec)
  105. pat = rf'Processing .*\b{sss.idx}\b of \b{sss.count}\b of .* id .*‘{sss.id}’'
  106. else:
  107. pat = f'master share #{master}'
  108. t.expect(pat, regex=True)
  109. ocls = get_wallet_cls(fmt_code=ofmt)
  110. if ocls.enc:
  111. t.hash_preset('new '+ocls.desc, '1')
  112. t.passphrase_new('new '+ocls.desc, sh1_passwd)
  113. if ocls.type == 'incog_hidden':
  114. t.hincog_create(1234)
  115. t.written_to_file(capfirst(ocls.desc))
  116. return t
  117. def ss_join(
  118. self,
  119. tdir,
  120. ofmt,
  121. in_exts,
  122. add_args = [],
  123. sid = None,
  124. exit_val = None,
  125. master = None,
  126. id_str = None):
  127. td = self.get_tmp_subdir(tdir)
  128. shares = [get_file_with_ext(td, f) for f in in_exts]
  129. if not sid:
  130. sid = self.read_from_tmpfile('dfl.sid')
  131. t = self.spawn('mmgen-seedjoin',
  132. add_args
  133. + ([f'--master-share={master}'] if master else [])
  134. + ([f'--id-str={id_str}'] if id_str else [])
  135. + ['-d', td, '-o', ofmt]
  136. + (['--label', 'Joined Wallet Label', '-r0'] if ofmt == 'w' else [])
  137. + shares,
  138. exit_val = exit_val)
  139. if exit_val:
  140. return t
  141. icls = (dfl_wcls if 'mmdat' in in_exts
  142. else get_wallet_cls('incog') if 'mmincog' in in_exts
  143. else get_wallet_cls('incog_hex') if 'mmincox' in in_exts
  144. else get_wallet_cls('incog_hidden') if '-H' in add_args
  145. else None)
  146. if icls.type.startswith('incog'):
  147. t.hash_preset(icls.desc, '1')
  148. if icls:
  149. t.passphrase(icls.desc, sh1_passwd)
  150. if master:
  151. fs = "master share #{}, split id.*‘{}’.*, share count {}"
  152. pat = fs.format(
  153. master,
  154. id_str or 'default',
  155. len(shares) + (icls.type=='incog_hidden'))
  156. t.expect(pat, regex=True)
  157. sid_cmp = strip_ansi_escapes(t.expect_getend('Joined Seed ID: '))
  158. cmp_or_die(sid, sid_cmp)
  159. ocls = get_wallet_cls(fmt_code=ofmt)
  160. if ocls.type == 'mmgen':
  161. t.hash_preset('new '+ocls.desc, '1')
  162. t.passphrase_new('new '+ocls.desc, wpasswd)
  163. t.written_to_file(capfirst(ocls.desc))
  164. return t
  165. def get_hincog_arg(self, tdir, suf='-default-2of2'):
  166. sid = self.read_from_tmpfile('dfl.sid')
  167. return os.path.join(self.tmpdir, tdir, sid+suf+'.hincog') + ',123'
  168. def ss_2way_A_dfl1(self):
  169. return self.ss_splt('2way_dfl1', 'w', '1:2')
  170. def ss_2way_B_dfl1(self):
  171. return self.ss_splt('2way_dfl1', 'bip39', '2:2')
  172. def ss_2way_join_dfl1(self):
  173. return self.ss_join('2way_dfl1', 'w', ['mmdat', 'bip39'])
  174. def ss_2way_A_dfl2(self):
  175. return self.ss_splt('2way_dfl2', 'seed', 'default:1:2')
  176. def ss_2way_B_dfl2(self):
  177. return self.ss_splt('2way_dfl2', 'hincog', 'default:2:2', ['-J', self.get_hincog_arg('2way_dfl2')])
  178. def ss_2way_join_dfl2(self):
  179. return self.ss_join('2way_dfl2', 'mmhex', ['mmseed'], ['-H', self.get_hincog_arg('2way_dfl2')])
  180. def ss_2way_A_alice(self):
  181. return self.ss_splt('2way_alice', 'w', 'alice:1:2')
  182. def ss_2way_B_alice(self):
  183. return self.ss_splt('2way_alice', 'mmhex', 'alice:2:2')
  184. def ss_2way_join_alice(self):
  185. return self.ss_join('2way_alice', 'seed', ['mmdat', 'mmhex'])
  186. def ss_2way_join_alice_mix(self):
  187. return self.ss_join('2way_alice', 'seed', ['mmhex', 'mmdat'])
  188. def ss_2way_A_dfl_usw(self):
  189. return self.ss_splt('2way_dfl_usw', 'words', '1:2', [], wf=ref_wf)
  190. def ss_2way_B_dfl_usw(self):
  191. return self.ss_splt('2way_dfl_usw', 'incog', '2:2', [], wf=ref_wf)
  192. def ss_2way_join_dfl_usw(self):
  193. return self.ss_join('2way_dfl_usw', 'mmhex', ['mmwords', 'mmincog'], sid=ref_sid)
  194. def ss_3way_A_dfl(self):
  195. return self.ss_splt('3way_dfl', 'words', '1:3')
  196. def ss_3way_B_dfl(self):
  197. return self.ss_splt('3way_dfl', 'incog_hex', '2:3')
  198. def ss_3way_C_dfl(self):
  199. return self.ss_splt('3way_dfl', 'bip39', '3:3')
  200. def ss_3way_join_dfl(self):
  201. return self.ss_join('3way_dfl', 'mmhex', ['mmwords', 'mmincox', 'bip39'])
  202. def ss_3way_join_dfl_mix(self):
  203. return self.ss_join('3way_dfl', 'mmhex', ['bip39', 'mmwords', 'mmincox'])
  204. def ss_2way_A_dfl_master3(self):
  205. return self.ss_splt('2way_dfl_master3', 'w', '', master=3)
  206. def ss_2way_B_dfl_master3(self):
  207. return self.ss_splt('2way_dfl_master3', 'bip39', '2:2', master=3)
  208. def ss_2way_join_dfl_master3(self):
  209. return self.ss_join('2way_dfl_master3', 'mmhex', ['mmdat', 'bip39'], master=3)
  210. tdir2 = '3way_foobar_master7'
  211. def ss_3way_C_foobar_master7(self):
  212. return self.ss_splt(self.tdir2, 'hincog', '',
  213. ['-J', self.get_hincog_arg(self.tdir2, '-master7')], master=7)
  214. def ss_3way_B_foobar_master7(self):
  215. return self.ss_splt(self.tdir2, 'bip39', 'φυβαρ:2:3', master=7)
  216. def ss_3way_A_foobar_master7(self):
  217. return self.ss_splt(self.tdir2, 'mmhex', 'φυβαρ:3:3', master=7)
  218. def ss_3way_join_foobar_master7(self):
  219. return self.ss_join(self.tdir2, 'seed', ['bip39', 'mmhex'],
  220. ['-H', self.get_hincog_arg(self.tdir2, '-master7')], master=7, id_str='φυβαρ')
  221. def ss_3way_join_foobar_master7_mix(self):
  222. return self.ss_join(self.tdir2, 'seed', ['mmhex', 'bip39'],
  223. ['-H', self.get_hincog_arg(self.tdir2, '-master7')], master=7, id_str='φυβαρ')
  224. def ss_bad_invocation(self, cmd, args, exit_val, errmsg):
  225. t = self.spawn(cmd, args, exit_val=exit_val)
  226. t.expect(errmsg, regex=True)
  227. return t
  228. def ss_3way_join_dfl_bad_invocation(self):
  229. t = self.ss_join('3way_dfl', 'mmhex',
  230. ['mmwords', 'mmincox', 'bip39'],
  231. id_str = 'foo',
  232. exit_val = 1)
  233. t.expect('option meaningless')
  234. return t
  235. def ss_bad_invocation1(self):
  236. return self.ss_bad_invocation(
  237. 'mmgen-seedsplit', [], 1, 'USAGE:')
  238. def ss_bad_invocation2(self):
  239. return self.ss_bad_invocation(
  240. 'mmgen-seedsplit', ['-M1', '1:9'], 1, 'meaningless in master share context')
  241. def ss_bad_invocation3(self):
  242. return self.ss_bad_invocation(
  243. 'mmgen-seedsplit', [self.tmpdir+'/no.mmdat', '1:9'], 1, 'input file .* not found')
  244. def ss_bad_invocation4(self):
  245. return self.ss_bad_invocation(
  246. 'mmgen-seedsplit', [self.tmpdir+'/dfl.sid', '1:9'], 1, 'unrecognized .* extension')
  247. def ss_bad_invocation5(self):
  248. return self.ss_bad_invocation(
  249. 'mmgen-seedjoin', [], 1, 'USAGE:')
  250. def ss_bad_invocation6(self):
  251. return self.ss_bad_invocation(
  252. 'mmgen-seedjoin', [self.tmpdir+'/a'], 1, 'USAGE:')
  253. def ss_bad_invocation7(self):
  254. return self.ss_bad_invocation(
  255. 'mmgen-seedjoin', [self.tmpdir+'/a', self.tmpdir+'/b'], 1, 'unrecognized .* extension')
  256. def ss_bad_invocation8(self):
  257. return self.ss_bad_invocation(
  258. 'mmgen-seedjoin', [self.tmpdir+'/a.mmdat', self.tmpdir+'/b.mmdat'], 1, 'input file .* not found')
  259. def ss_bad_invocation9(self):
  260. return self.ss_bad_invocation(
  261. 'mmgen-seedsplit', ['x'], 1, 'USAGE:')
  262. def ss_bad_invocation10(self):
  263. return self.ss_bad_invocation(
  264. 'mmgen-seedsplit', [self.tmpdir+'/a.mmdat', '1:2'], 1, 'input file .* not found')
  265. def ss_bad_invocation11(self):
  266. return self.ss_bad_invocation(
  267. 'mmgen-seedsplit', [self.tmpdir+'/dfl.sid', '1:2'], 1, 'unrecognized .* extension')