ts_seedsplit.py 11 KB

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