Browse Source

mmgen-keygen: new viewkey-address file type

Generate using the --viewkeys option

Testing:

    $ test/unit_tests.py --verbose addrlist.viewkeyaddr
    $ test/tooltest2.py --fork --verbose --coin=xmr viewkeyaddrfile_chksum
    $ test/test.py -e ref_viewkeyaddrfile_gen_xmr ref_viewkeyaddrfile_chk_xmr
The MMGen Project 1 year ago
parent
commit
dc685e9ced

+ 11 - 5
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 = """

+ 13 - 3
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'

+ 3 - 0
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,

+ 1 - 0
mmgen/main_tool.py

@@ -137,6 +137,7 @@ mods = {
 	'file': (
 		'addrfile_chksum',
 		'keyaddrfile_chksum',
+		'viewkeyaddrfile_chksum',
 		'passwdfile_chksum',
 		'txview',
 	),

+ 5 - 0
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

+ 21 - 0
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
+}

+ 2 - 0
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',

+ 14 - 0
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')

+ 5 - 0
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'],

+ 10 - 4
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')