#!/usr/bin/env python3 # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution # Copyright (C)2013-2020 The MMGen Project # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ ts_seedsplit.py: Seed split/join tests for the test.py test suite """ from mmgen.globalvars import g from mmgen.opts import opt from mmgen.seed import SeedSource,Wallet,IncogWallet,IncogWalletHex,IncogWalletHidden,SeedSourceEnc from test.test_py_d.ts_base import * ref_wf = 'test/ref/98831F3A.bip39' ref_sid = '98831F3A' wpasswd = 'abc' sh1_passwd = 'xyz' dfl_wcls = Wallet class TestSuiteSeedSplit(TestSuiteBase): 'splitting and joining seeds' networks = ('btc',) tmpdir_nums = [23] cmd_group = ( ('ss_walletgen', 'wallet generation'), ('ss_2way_A_dfl1', '2-way seed split (share A)'), ('ss_2way_B_dfl1', '2-way seed split (share B)'), ('ss_2way_join_dfl1', '2-way seed join'), ('ss_2way_A_dfl2', "2-way seed split 'default' (share A)"), ('ss_2way_B_dfl2', "2-way seed split 'default' (share B)"), ('ss_2way_join_dfl2', "2-way seed join 'default'"), ('ss_2way_A_alice', "2-way seed split 'alice' (share A)"), ('ss_2way_B_alice', "2-way seed split 'alice' (share B)"), ('ss_2way_join_alice', "2-way seed join 'alice'"), ('ss_2way_join_alice_mix', "2-way seed join 'alice' (out of order)"), ('ss_2way_A_dfl_master3', '2-way seed split with master share #3 (share A)'), ('ss_2way_B_dfl_master3', '2-way seed split with master share #3 (share B)'), ('ss_2way_join_dfl_master3', '2-way seed join with master share #3'), ('ss_2way_A_dfl_usw', '2-way seed split of user-specified wallet (share A)'), ('ss_2way_B_dfl_usw', '2-way seed split of user-specified wallet (share B)'), ('ss_2way_join_dfl_usw', '2-way seed join of user-specified wallet'), ('ss_3way_A_dfl', '3-way seed split (share A)'), ('ss_3way_B_dfl', '3-way seed split (share B)'), ('ss_3way_C_dfl', '3-way seed split (share C)'), ('ss_3way_join_dfl', '3-way seed join'), ('ss_3way_join_dfl_mix', '3-way seed join (out of order)'), ('ss_3way_A_foobar_master7', "3-way seed split 'φυβαρ' with master share #7 (share A)"), ('ss_3way_B_foobar_master7', "3-way seed split 'φυβαρ' with master share #7 (share B)"), ('ss_3way_C_foobar_master7', "3-way seed split 'φυβαρ' with master share #7 (share C)"), ('ss_3way_join_foobar_master7', "3-way seed join 'φυβαρ' with master share #7"), ('ss_3way_join_foobar_master7_mix',"3-way seed join 'φυβαρ' with master share #7 (out of order)"), ('ss_3way_join_dfl_bad_invocation',"bad invocation of 'mmgen-seedjoin' - --id-str with non-master join"), ('ss_bad_invocation1', "bad invocation of 'mmgen-seedsplit' - no arguments"), ('ss_bad_invocation2', "bad invocation of 'mmgen-seedsplit' - master share with split specifier"), ('ss_bad_invocation3', "bad invocation of 'mmgen-seedsplit' - nonexistent file"), ('ss_bad_invocation4', "bad invocation of 'mmgen-seedsplit' - invalid file extension"), ('ss_bad_invocation5', "bad invocation of 'mmgen-seedjoin' - no arguments"), ('ss_bad_invocation6', "bad invocation of 'mmgen-seedjoin' - one file argument"), ('ss_bad_invocation7', "bad invocation of 'mmgen-seedjoin' - invalid file extension"), ('ss_bad_invocation8', "bad invocation of 'mmgen-seedjoin' - nonexistent file"), ('ss_bad_invocation9', "bad invocation of 'mmgen-seedsplit' - bad specifier"), ('ss_bad_invocation10', "bad invocation of 'mmgen-seedsplit' - nonexistent file"), ('ss_bad_invocation11', "bad invocation of 'mmgen-seedsplit' - invalid file extension"), ) def get_tmp_subdir(self,subdir): return os.path.join(self.tmpdir,subdir) def ss_walletgen(self): t = self.spawn('mmgen-walletgen', ['-r0','-p1']) t.passphrase_new('new '+dfl_wcls.desc,wpasswd) t.label() self.write_to_tmpfile('dfl.sid',t.expect_getend('Seed ID: ')) t.expect('move it to the data directory? (Y/n): ','y') t.written_to_file(capfirst(dfl_wcls.desc)) return t def ss_splt(self,tdir,ofmt,spec,add_args=[],wf=None,master=None): try: os.mkdir(self.get_tmp_subdir(tdir)) except: pass t = self.spawn('mmgen-seedsplit', ['-q','-d',self.get_tmp_subdir(tdir),'-r0','-o',ofmt] + (['-L',(spec or 'label')] if ofmt == 'w' else []) + add_args + (['--master-share={}'.format(master)] if master else []) + ([wf] if wf else []) + ([spec] if spec else [])) if not wf: t.passphrase(dfl_wcls.desc,wpasswd) if spec: from mmgen.obj import SeedSplitSpecifier sss = SeedSplitSpecifier(spec) pat = "Processing .* {} of {} of .* id '{}'".format(sss.idx,sss.count,sss.id) else: pat = "master share #{}".format(master) t.expect(pat,regex=True) ocls = SeedSource.fmt_code_to_type(ofmt) pw = issubclass(ocls,SeedSourceEnc) if pw: t.hash_preset('new '+ocls.desc,'1') t.passphrase_new('new '+ocls.desc,sh1_passwd) if ocls == IncogWalletHidden: t.hincog_create(1234) t.written_to_file(capfirst(ocls.desc)) return t def ss_join(self,tdir,ofmt,in_exts,add_args=[],sid=None,bad_invocation=False,master=None,id_str=None): td = self.get_tmp_subdir(tdir) shares = [get_file_with_ext(td,f) for f in in_exts] if not sid: sid = self.read_from_tmpfile('dfl.sid') t = self.spawn('mmgen-seedjoin', add_args + (['--master-share={}'.format(master)] if master else []) + (['--id-str={}'.format(id_str)] if id_str else []) + ['-d',td,'-o',ofmt] + (['--label','Joined Wallet Label','-r0'] if ofmt == 'w' else []) + shares) if bad_invocation: t.read() return t icls = ( Wallet if 'mmdat' in in_exts else IncogWallet if 'mmincog' in in_exts else IncogWalletHex if 'mmincox' in in_exts else IncogWalletHidden if '-H' in add_args else None ) if icls in (IncogWallet,IncogWalletHex,IncogWalletHidden): t.hash_preset(icls.desc,'1') if icls: t.passphrase(icls.desc,sh1_passwd) if master: fs = "master share #{}, split id '{}', share count {}" pat = fs.format(master,id_str or 'default',len(shares)+(icls==IncogWalletHidden)) t.expect(pat,regex=True) sid_cmp = t.expect_getend('Joined Seed ID: ') cmp_or_die(sid,sid_cmp) ocls = SeedSource.fmt_code_to_type(ofmt) if ocls == Wallet: t.hash_preset('new '+ocls.desc,'1') t.passphrase_new('new '+ocls.desc,wpasswd) t.written_to_file(capfirst(ocls.desc)) return t def get_hincog_arg(self,tdir,suf='-default-2of2'): sid = self.read_from_tmpfile('dfl.sid') return os.path.join(self.tmpdir,tdir,sid+suf+'.hincog') + ',123' def ss_2way_A_dfl1(self): return self.ss_splt('2way_dfl1','w','1:2') def ss_2way_B_dfl1(self): return self.ss_splt('2way_dfl1','bip39','2:2') def ss_2way_join_dfl1(self): return self.ss_join('2way_dfl1','w',['mmdat','bip39']) def ss_2way_A_dfl2(self): return self.ss_splt('2way_dfl2','seed','default:1:2') def ss_2way_B_dfl2(self): return self.ss_splt('2way_dfl2','hincog','default:2:2',['-J',self.get_hincog_arg('2way_dfl2')]) def ss_2way_join_dfl2(self): return self.ss_join('2way_dfl2','mmhex',['mmseed'],['-H',self.get_hincog_arg('2way_dfl2')]) def ss_2way_A_alice(self): return self.ss_splt('2way_alice','w','alice:1:2') def ss_2way_B_alice(self): return self.ss_splt('2way_alice','mmhex','alice:2:2') def ss_2way_join_alice(self): return self.ss_join('2way_alice','seed',['mmdat','mmhex']) def ss_2way_join_alice_mix(self): return self.ss_join('2way_alice','seed',['mmhex','mmdat']) def ss_2way_A_dfl_usw(self): return self.ss_splt('2way_dfl_usw','words','1:2',[],wf=ref_wf) def ss_2way_B_dfl_usw(self): return self.ss_splt('2way_dfl_usw','incog','2:2',[],wf=ref_wf) def ss_2way_join_dfl_usw(self): return self.ss_join('2way_dfl_usw','mmhex',['mmwords','mmincog'],sid=ref_sid) def ss_3way_A_dfl(self): return self.ss_splt('3way_dfl','words','1:3') def ss_3way_B_dfl(self): return self.ss_splt('3way_dfl','incog_hex','2:3') def ss_3way_C_dfl(self): return self.ss_splt('3way_dfl','bip39','3:3') def ss_3way_join_dfl(self): return self.ss_join('3way_dfl','mmhex',['mmwords','mmincox','bip39']) def ss_3way_join_dfl_mix(self): return self.ss_join('3way_dfl','mmhex',['bip39','mmwords','mmincox']) def ss_2way_A_dfl_master3(self): return self.ss_splt('2way_dfl_master3','w','',master=3) def ss_2way_B_dfl_master3(self): return self.ss_splt('2way_dfl_master3','bip39','2:2',master=3) def ss_2way_join_dfl_master3(self): return self.ss_join('2way_dfl_master3','mmhex',['mmdat','bip39'],master=3) tdir2 = '3way_foobar_master7' def ss_3way_C_foobar_master7(self): return self.ss_splt(self.tdir2,'hincog','', ['-J',self.get_hincog_arg(self.tdir2,'-master7')],master=7) def ss_3way_B_foobar_master7(self): return self.ss_splt(self.tdir2,'bip39','φυβαρ:2:3',master=7) def ss_3way_A_foobar_master7(self): return self.ss_splt(self.tdir2,'mmhex','φυβαρ:3:3',master=7) def ss_3way_join_foobar_master7(self): return self.ss_join(self.tdir2,'seed', ['bip39','mmhex'], ['-H',self.get_hincog_arg(self.tdir2,'-master7')],master=7,id_str='φυβαρ') def ss_3way_join_foobar_master7_mix(self): return self.ss_join(self.tdir2,'seed', ['mmhex','bip39'], ['-H',self.get_hincog_arg(self.tdir2,'-master7')],master=7,id_str='φυβαρ') def ss_bad_invocation(self,cmd,args,exit_val): t = self.spawn(cmd,args) t.read() t.req_exit_val = exit_val return t def ss_3way_join_dfl_bad_invocation(self): t = self.ss_join('3way_dfl','mmhex', ['mmwords','mmincox','bip39'], id_str='foo', bad_invocation=True) t.req_exit_val = 1 return t def ss_bad_invocation1(self): return self.ss_bad_invocation('mmgen-seedsplit',[],1) def ss_bad_invocation2(self): return self.ss_bad_invocation('mmgen-seedsplit',['-M1','1:9'],1) def ss_bad_invocation3(self): return self.ss_bad_invocation('mmgen-seedsplit',[self.tmpdir+'/no.mmdat','1:9'],1) def ss_bad_invocation4(self): return self.ss_bad_invocation('mmgen-seedsplit',[self.tmpdir+'/dfl.sid','1:9'],1) def ss_bad_invocation5(self): return self.ss_bad_invocation('mmgen-seedjoin',[],1) def ss_bad_invocation6(self): return self.ss_bad_invocation('mmgen-seedjoin',[self.tmpdir+'/a'],1) def ss_bad_invocation7(self): return self.ss_bad_invocation('mmgen-seedjoin',[self.tmpdir+'/a',self.tmpdir+'/b'],1) def ss_bad_invocation8(self): return self.ss_bad_invocation('mmgen-seedjoin',[self.tmpdir+'/a.mmdat',self.tmpdir+'/b.mmdat'],1) def ss_bad_invocation9(self): return self.ss_bad_invocation('mmgen-seedsplit',['x'],1) def ss_bad_invocation10(self): return self.ss_bad_invocation('mmgen-seedsplit',[self.tmpdir+'/a.mmdat','1:2'],1) def ss_bad_invocation11(self): return self.ss_bad_invocation('mmgen-seedsplit',[self.tmpdir+'/dfl.sid','1:2'],1)