diff --git a/mmgen/addrfile.py b/mmgen/addrfile.py index 0138e754..7f0cf575 100755 --- a/mmgen/addrfile.py +++ b/mmgen/addrfile.py @@ -117,7 +117,8 @@ class AddrFile(MMGenObject): if p.has_keys: if self.cfg.b16: out.append(fs.format( '', f'orig_hex: {e.sec.orig_bytes.hex()}', c )) - out.append(fs.format( '', f'{p.al_id.mmtype.wif_label}: {e.sec.wif}', c )) + if type(self) != ViewKeyAddrFile: + out.append(fs.format( '', f'{p.al_id.mmtype.wif_label}: {e.sec.wif}', c )) for k in ('viewkey','wallet_passwd'): v = getattr(e,k) if v: out.append(fs.format( '', f'{k}: {v}', c )) @@ -149,9 +150,10 @@ class AddrFile(MMGenObject): a = le(**{ 'proto': p.proto, 'idx':int(idx), p.main_attr:addr, 'comment':comment }) if p.has_keys: # order: wif,(orig_hex),viewkey,wallet_passwd - d = self.get_line(lines) - assert d[0] == p.al_id.mmtype.wif_label+':', iifs.format(d[0],p.al_id.mmtype.wif_label) - a.sec = PrivKey(proto=p.proto,wif=d[1]) + if type(self) != ViewKeyAddrFile: + d = self.get_line(lines) + assert d[0] == p.al_id.mmtype.wif_label+':', iifs.format(d[0],p.al_id.mmtype.wif_label) + a.sec = PrivKey(proto=p.proto,wif=d[1]) for k,dtype,add_proto in ( ('viewkey',ViewKey,True), ('wallet_passwd',WalletPassword,False) ): @@ -162,7 +164,7 @@ class AddrFile(MMGenObject): ret.append(a) - if p.has_keys and p.ka_validity_chk != False: + if type(self) != ViewKeyAddrFile and p.has_keys and p.ka_validity_chk != False: def verify_keys(): from .addrgen import KeyGenerator,AddrGenerator @@ -300,6 +302,10 @@ class KeyAddrFile(AddrFile): desc = 'secret keys' ext = 'akeys' +class ViewKeyAddrFile(KeyAddrFile): + desc = 'view keys' + ext = 'vkeys' + class KeyFile(KeyAddrFile): ext = 'keys' header = """ diff --git a/mmgen/addrlist.py b/mmgen/addrlist.py index a6a6d5ff..2088c5b3 100755 --- a/mmgen/addrlist.py +++ b/mmgen/addrlist.py @@ -218,6 +218,10 @@ class AddrList(MMGenObject): # Address info for a single seed ID if self.al_id == None: return + if type(self) == ViewKeyAddrList: + if not 'viewkey' in self.al_id.mmtype.extra_attrs: + die(1,f'viewkeys not supported for address type {self.al_id.mmtype.desc!r}') + self.id_str = AddrListIDStr(self) if type(self) == KeyList: @@ -242,8 +246,8 @@ class AddrList(MMGenObject): # Address info for a single seed ID mmtype = self.al_id.mmtype - gen_wallet_passwd = type(self) == KeyAddrList and 'wallet_passwd' in mmtype.extra_attrs - gen_viewkey = type(self) == KeyAddrList and 'viewkey' in mmtype.extra_attrs + gen_wallet_passwd = type(self) in (KeyAddrList,ViewKeyAddrList) and 'wallet_passwd' in mmtype.extra_attrs + gen_viewkey = type(self) in (KeyAddrList,ViewKeyAddrList) and 'viewkey' in mmtype.extra_attrs if self.gen_addrs: from .addrgen import KeyGenerator,AddrGenerator @@ -281,7 +285,8 @@ class AddrList(MMGenObject): # Address info for a single seed ID if gen_viewkey: e.viewkey = ag.to_viewkey(data) if gen_wallet_passwd: - e.wallet_passwd = self.gen_wallet_passwd(e.sec) + e.wallet_passwd = self.gen_wallet_passwd( + e.viewkey.encode() if type(self) == ViewKeyAddrList else e.sec ) elif self.gen_passwds: e.passwd = self.gen_passwd(e.sec) # TODO - own type @@ -406,6 +411,11 @@ class KeyAddrList(AddrList): has_keys = True chksum_rec_f = lambda foo,e: (str(e.idx), e.addr, e.sec.wif) +class ViewKeyAddrList(KeyAddrList): + desc = 'viewkey-address' + gen_desc = 'viewkey/address pair' + chksum_rec_f = lambda foo,e: ( str(e.idx), e.addr ) + class KeyList(KeyAddrList): desc = 'key' gen_desc = 'key' diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py index 7ce0f1ea..010873c5 100755 --- a/mmgen/main_addrgen.py +++ b/mmgen/main_addrgen.py @@ -79,6 +79,7 @@ opts_data = { (default: {dmat}) -U, --subwallet= U Generate {what} for subwallet 'U' (see SUBWALLETS below) +-V, --viewkeys Print viewkeys, omitting secret keys -v, --verbose Produce more verbose output -x, --b16 Print secret keys in hexadecimal too """, @@ -156,6 +157,8 @@ ss_seed = ss.seed if cfg.subwallet is None else ss.seed.subseed(cfg.subwallet,pr if cfg.no_addresses: gen_clsname = 'KeyList' +elif cfg.viewkeys: + gen_clsname = 'ViewKeyAddrList' al = getattr( mmgen.addrlist, gen_clsname )( cfg = cfg, diff --git a/mmgen/main_tool.py b/mmgen/main_tool.py index 7f67f316..b639edbd 100755 --- a/mmgen/main_tool.py +++ b/mmgen/main_tool.py @@ -137,6 +137,7 @@ mods = { 'file': ( 'addrfile_chksum', 'keyaddrfile_chksum', + 'viewkeyaddrfile_chksum', 'passwdfile_chksum', 'txview', ), diff --git a/mmgen/tool/file.py b/mmgen/tool/file.py index c1a1ea62..b5195272 100755 --- a/mmgen/tool/file.py +++ b/mmgen/tool/file.py @@ -60,6 +60,11 @@ class tool_cmd(tool_cmd_base): from ..addrlist import KeyAddrList return self._file_chksum(mmgen_keyaddrfile,KeyAddrList) + def viewkeyaddrfile_chksum(self,mmgen_viewkeyaddrfile:str): + "compute checksum for MMGen key-address file" + from ..addrlist import ViewKeyAddrList + return self._file_chksum(mmgen_viewkeyaddrfile,ViewKeyAddrList) + def passwdfile_chksum(self,mmgen_passwdfile:str): "compute checksum for MMGen password file" from ..passwdlist import PasswordList diff --git a/test/ref/monero/98831F3A-XMR-M[1-3].vkeys b/test/ref/monero/98831F3A-XMR-M[1-3].vkeys new file mode 100644 index 00000000..0905ec12 --- /dev/null +++ b/test/ref/monero/98831F3A-XMR-M[1-3].vkeys @@ -0,0 +1,21 @@ +# MMGen viewkey-address file +# +# This file is editable. +# Everything following a hash symbol '#' is a comment and ignored by MMGen. +# A text label of 80 screen cells or less may be added to the right of each +# address, and it will be appended to the tracking wallet label upon import. +# The label may contain any printable ASCII symbol. +# +# Viewkey-address data checksum for 98831F3A-XMR-M[1-3]: 40C9 0E61 B743 229C +# Record this value to a secure location. +98831F3A XMR:MONERO { + 1 41tmwZd2CdXEGtWqGY9fH9FVtQM8VxZASYPQ3VJQhFjtGWYzQFuidD21vJYTi2yy3tXRYXTNXBTaYVLav62rwUUpFFyicZU + viewkey: 3fa430d35eb44ae2ac91b302b794e6b789807ca0c19757042f581e5579314503 + wallet_passwd: 4be230151aa51363af6123624ea20509 + 2 45Un3DKQfZ1XP9XWKVqMrzMugrxHLU8L47Jcxyu7dNnGKd7WWDTQ6QrBjCxoxQhnL13xdgKycywfijNuuTzbZBj7UHPjBMb + viewkey: 176fed837ea2d8ab98de50811a0869d0f2e6076fcacfa17707bfb2bca960190e + wallet_passwd: b19f506e52c8ffd7a3b1bc1c4fb4c1bb + 3 47mWaHEPy26U4e2Xw1y3PhBuEBzMpPF1z7QzBVJ19pqe9nMmcztcHdkYs19YxrJnEWEkipSNroQxvJaoYfLX87Po39Btso4 + viewkey: be75e20cf8ae8816bade5c05cb677929c67be310fcb509fd52630735229ac20d + wallet_passwd: 5e8e3b47a9aa28464dcc2a588e892d50 +} diff --git a/test/test_py_d/ts_ref.py b/test/test_py_d/ts_ref.py index cd1a9598..0da150d5 100755 --- a/test/test_py_d/ts_ref.py +++ b/test/test_py_d/ts_ref.py @@ -41,6 +41,8 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared): 'ref_segwitaddrfile':'98831F3A{}-S[1,31-33,500-501,1010-1011]{}.addrs', 'ref_bech32addrfile':'98831F3A{}-B[1,31-33,500-501,1010-1011]{}.addrs', 'ref_keyaddrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.akeys.mmenc', + 'ref_viewkeyaddrfile': '98831F3A-XMR-M[1-3].vkeys', + 'ref_passwdfile_b32_24': '98831F3A-фубар@crypto.org-b32-24[1,4,1100].pws', 'ref_passwdfile_b32_12': '98831F3A-фубар@crypto.org-b32-12[1,4,1100].pws', 'ref_passwdfile_b58_10': '98831F3A-фубар@crypto.org-b58-10[1,4,1100].pws', diff --git a/test/test_py_d/ts_ref_altcoin.py b/test/test_py_d/ts_ref_altcoin.py index 21816555..b1bdaf93 100755 --- a/test/test_py_d/ts_ref_altcoin.py +++ b/test/test_py_d/ts_ref_altcoin.py @@ -40,6 +40,7 @@ class TestSuiteRefAltcoin(TestSuiteRef,TestSuiteBase): 'ref_keyaddrfile_chksum_zec': 'F05A 5A5C 0C8E 2617', 'ref_keyaddrfile_chksum_zec_z': '6B87 9B2D 0D8D 8D1E', 'ref_keyaddrfile_chksum_xmr': 'E0D7 9612 3D67 404A', + 'ref_viewkeyaddrfile_chksum_xmr': '40C9 0E61 B743 229C', 'ref_keyaddrfile_chksum_dash': 'E83D 2C63 FEA2 4142', 'ref_keyaddrfile_chksum_eth': 'E400 70D9 0AE3 C7C2', 'ref_keyaddrfile_chksum_etc': 'EF49 967D BD6C FE45', @@ -61,6 +62,7 @@ class TestSuiteRefAltcoin(TestSuiteRef,TestSuiteBase): ('ref_keyaddrfile_gen_zec', 'generate key-address file (ZEC-T)'), ('ref_keyaddrfile_gen_zec_z','generate key-address file (ZEC-Z)'), ('ref_keyaddrfile_gen_xmr', 'generate key-address file (XMR)'), + ('ref_viewkeyaddrfile_gen_xmr','generate viewkey-address file (XMR)'), ('ref_addrfile_chk_eth', 'reference address file (ETH)'), ('ref_addrfile_chk_etc', 'reference address file (ETC)'), @@ -75,6 +77,7 @@ class TestSuiteRefAltcoin(TestSuiteRef,TestSuiteBase): ('ref_keyaddrfile_chk_zec', 'reference key-address file (ZEC-T)'), ('ref_keyaddrfile_chk_zec_z','reference key-address file (ZEC-Z)'), ('ref_keyaddrfile_chk_xmr', 'reference key-address file (XMR)'), + ('ref_viewkeyaddrfile_chk_xmr', 'reference viewkey-address file (XMR)'), ) def ref_altcoin_tx_chk(self): @@ -173,6 +176,13 @@ class TestSuiteRefAltcoin(TestSuiteRef,TestSuiteBase): def ref_keyaddrfile_gen_xmr(self): return self.ref_altcoin_addrgen(coin='XMR',mmtype='monero',gen_what='key') + def ref_viewkeyaddrfile_gen_xmr(self): + return self.ref_altcoin_addrgen( + coin = 'XMR', + mmtype = 'monero', + gen_what = 'viewkey', + add_args = ['--viewkeys'], + addr_idx_list = '1-3' ) def ref_addrfile_chk_eth(self): return self.ref_addrfile_chk(ftype='addr',coin='ETH',subdir='ethereum',pfx='-ETH', @@ -222,3 +232,7 @@ class TestSuiteRefAltcoin(TestSuiteRef,TestSuiteBase): def ref_keyaddrfile_chk_xmr(self): return self.ref_addrfile_chk(ftype='keyaddr',coin='XMR',subdir='monero',pfx='-XMR-M', pat='XMR Mainnet.*Monero') + + def ref_viewkeyaddrfile_chk_xmr(self): + return self.ref_addrfile_chk(ftype='viewkeyaddr',coin='XMR',subdir='monero',pfx='-XMR-M', + pat='XMR Mainnet.*Monero') diff --git a/test/tooltest2.py b/test/tooltest2.py index e2756de4..fac59b97 100755 --- a/test/tooltest2.py +++ b/test/tooltest2.py @@ -681,6 +681,11 @@ tests = { ( ['test/ref/ethereum_classic/98831F3A-ETC[1,31-33,500-501,1010-1011].addrs'], 'E97A D796 B495 E8BC'), ], }, + 'viewkeyaddrfile_chksum': { + 'xmr_mainnet': [ + ( ['test/ref/monero/98831F3A-XMR-M[1-3].vkeys'], '40C9 0E61 B743 229C' ), + ], + }, 'keyaddrfile_chksum': { 'btc_mainnet': [ ( ['test/ref/98831F3A[1,31-33,500-501,1010-1011].akeys.mmenc'], diff --git a/test/unit_tests_d/ut_addrlist.py b/test/unit_tests_d/ut_addrlist.py index c3630e4e..8a5b936f 100755 --- a/test/unit_tests_d/ut_addrlist.py +++ b/test/unit_tests_d/ut_addrlist.py @@ -7,17 +7,17 @@ test.unit_tests_d.ut_addrlist: address list unit tests for the MMGen suite from mmgen.common import * from mmgen.seed import Seed from mmgen.addr import MMGenAddrType -from mmgen.addrlist import AddrIdxList,AddrList,KeyList,KeyAddrList +from mmgen.addrlist import AddrIdxList,AddrList,KeyList,KeyAddrList,ViewKeyAddrList from mmgen.passwdlist import PasswordList from mmgen.protocol import init_proto from ..include.common import cfg,qmsg,vmsg -def do_test(list_type,chksum,idx_spec=None,pw_id_str=None,add_kwargs=None): +def do_test(list_type,chksum,idx_spec=None,pw_id_str=None,add_kwargs=None,coin=None,addrtype=None): qmsg(blue(f'Testing {list_type.__name__}')) - proto = init_proto( cfg, 'btc' ) + proto = init_proto( cfg, coin or 'btc' ) seed = Seed(cfg,seed_bin=bytes.fromhex('feedbead'*8)) - mmtype = MMGenAddrType(proto,'C') + mmtype = MMGenAddrType(proto, addrtype or 'C') idxs = AddrIdxList(idx_spec or '1-3') if cfg.verbose: @@ -93,6 +93,12 @@ class unit_tests: def keyaddr(self,name,ut): return do_test(KeyAddrList,'4A36 AA65 8C2B 7C35') + def keyaddr_xmr(self,name,ut): + return do_test(KeyAddrList,'AAA2 BA69 17FC 9A88',coin='XMR',addrtype='M') + + def viewkeyaddr(self,name,ut): + return do_test(ViewKeyAddrList,'C122 2E58 DC28 D6AE',coin='XMR',addrtype='M') + def passwd(self,name,ut): return do_test(PasswordList,'FF4A B716 4513 8F8F',pw_id_str='foo')