Browse Source

passgen: code cleanups, new tests, new --passwd-fmt option

MMGen 5 years ago
parent
commit
d7fe969d8c

+ 45 - 36
mmgen/addr.py

@@ -350,10 +350,10 @@ class AddrList(MMGenObject): # Address info for a single seed ID
 #
 # This file is editable.
 # Everything following a hash symbol '#' is a comment and ignored by {pnm}.
-# A text label of {n} characters or less may be added to the right of each
+# A text label of {n} 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.
-""".strip().format(n=TwComment.max_len,pnm=pnm),
+""".strip().format(n=TwComment.max_screen_width,pnm=pnm),
 	'record_chksum': """
 Record this checksum: it will be used to verify the address file in the future
 """.strip(),
@@ -624,33 +624,33 @@ Removed {{}} duplicate WIF key{{}} from keylist (also in {pnm} key-address file
 		out.append('}')
 		self.fmt_data = '\n'.join([l.rstrip() for l in out]) + '\n'
 
+	def get_line(self,lines):
+		ret = lines.pop(0).split(None,2)
+		if ret[0] == 'orig_hex:': # hacky
+			ret = lines.pop(0).split(None,2)
+		return ret if len(ret) == 3 else ret + ['']
+
 	def parse_file_body(self,lines):
 
 		ret = AddrListList()
 		le = self.entry_type
 
-		def get_line():
-			ret = lines.pop(0).split(None,2)
-			if ret[0] == 'orig_hex:': # hacky
-				return lines.pop(0).split(None,2)
-			return ret
-
 		while lines:
-			d = get_line()
+			idx,addr,lbl = self.get_line(lines)
 
-			assert is_mmgen_idx(d[0]),"'{}': invalid address num. in line: '{}'".format(d[0],' '.join(d))
-			assert self.check_format(d[1]),"'{}': invalid {}".format(d[1],self.data_desc)
+			assert is_mmgen_idx(idx), (
+				"'{}': invalid address num. in line: '{}'".format(idx,' '.join([idx,addr,lbl])))
+			assert self.check_format(addr),"'{}': invalid {}".format(addr,self.data_desc)
 
-			if len(d) != 3: d.append('')
-			a = le(**{'idx':int(d[0]),self.main_attr:d[1],'label':d[2]})
+			a = le(**{ 'idx':int(idx), self.main_attr:addr, 'label':lbl })
 
 			if self.has_keys: # order: wif,(orig_hex),viewkey,wallet_passwd
-				d = get_line()
+				d = self.get_line(lines)
 				assert d[0] == self.al_id.mmtype.wif_label,"Invalid line in file: '{}'".format(' '.join(d))
 				a.sec = PrivKey(wif=d[1])
 				for k,dtype in (('viewkey',ViewKey),('wallet_passwd',WalletPassword)):
 					if k in self.al_id.mmtype.extra_attrs:
-						d = get_line()
+						d = self.get_line(lines)
 						assert d[0] == k+':',"Invalid line in file: '{}'".format(' '.join(d))
 						setattr(a,k,dtype(d[1]))
 
@@ -696,7 +696,7 @@ Removed {{}} duplicate WIF key{{}} from keylist (also in {pnm} key-address file
 
 			from mmgen.protocol import CoinProtocol
 			base_coin = CoinProtocol(al_coin or 'BTC',testnet=False).base_coin
-			return base_coin,mmtype
+			return base_coin,mmtype,tn
 
 		def check_coin_mismatch(base_coin): # die if addrfile coin doesn't match g.coin
 			m = '{} address file format, but base coin is {}!'
@@ -719,16 +719,20 @@ Removed {{}} duplicate WIF key{{}} from keylist (also in {pnm} key-address file
 				self.set_pw_fmt(ss[0])
 				self.set_pw_len(ss[1])
 				self.pw_id_str = MMGenPWIDString(ls.pop())
-				mmtype = MMGenPasswordType('P')
+				base_coin,mmtype = None,MMGenPasswordType('P')
+				testnet = False
 			elif len(ls) == 1:
-				base_coin,mmtype = parse_addrfile_label(ls[0])
+				base_coin,mmtype,testnet = parse_addrfile_label(ls[0])
 				check_coin_mismatch(base_coin)
 			elif len(ls) == 0:
 				base_coin,mmtype = 'BTC',MMGenAddrType('L')
+				testnet = False
 				check_coin_mismatch(base_coin)
 			else:
 				raise ValueError("'{}': Invalid first line for {} file '{}'".format(lines[0],self.gen_desc,fn))
 
+			self.base_coin = base_coin
+			self.is_testnet = testnet
 			self.al_id = AddrListID(SeedID(sid=sid),mmtype)
 
 			data = self.parse_file_body(lines[1:-1])
@@ -771,6 +775,7 @@ class KeyList(AddrList):
 	ext      = 'keys'
 	chksum_rec_f = lambda foo,e: (str(e.idx), e.addr, e.sec.wif)
 
+from collections import namedtuple
 class PasswordList(AddrList):
 	msgs = {
 	'file_header': """
@@ -778,10 +783,10 @@ class PasswordList(AddrList):
 #
 # This file is editable.
 # Everything following a hash symbol '#' is a comment and ignored by {pnm}.
-# A text label of {n} characters or less may be added to the right of each
+# A text label of {n} screen cells or less may be added to the right of each
 # password.  The label may contain any printable ASCII symbol.
 #
-""".strip().format(n=TwComment.max_len,pnm=pnm),
+""".strip().format(n=TwComment.max_screen_width,pnm=pnm),
 	'record_chksum': """
 Record this checksum: it will be used to verify the password file in the future
 """.strip()
@@ -798,12 +803,13 @@ Record this checksum: it will be used to verify the password file in the future
 	has_keys    = False
 	ext         = 'pws'
 	pw_len      = None
-	pw_fmt      = None
+	dfl_pw_fmt  = 'b58'
+	pwinfo      = namedtuple('passwd_info',['min_len','max_len','dfl_len','desc','chk_func'])
 	pw_info     = {
-		'b58': { 'min_len': 8 , 'max_len': 36 ,'dfl_len': 20, 'desc': 'base-58 password' },
-		'b32': { 'min_len': 10 ,'max_len': 42 ,'dfl_len': 24, 'desc': 'base-32 password' },
-		'hex': { 'min_len': 64 ,'max_len': 64 ,'dfl_len': 64, 'desc': 'raw hex password' }
-		}
+		'b32':   pwinfo(10, 42 ,24, 'base32 password',       is_b32_str),
+		'b58':   pwinfo(8,  36 ,20, 'base58 password',       is_b58_str),
+		'hex':   pwinfo(64, 64 ,64, 'hexadecimal password',  is_hex_str),
+	}
 	chksum_rec_f = lambda foo,e: (str(e.idx), e.passwd)
 
 	def __init__(   self,infile=None,seed=None,
@@ -815,8 +821,9 @@ Record this checksum: it will be used to verify the password file in the future
 		if infile:
 			self.data = self.parse_file(infile) # sets self.pw_id_str,self.pw_fmt,self.pw_len
 		else:
-			for k in seed,pw_idxs: assert chk_params_only or k
-			for k in (pw_id_str,pw_fmt): assert k
+			if not chk_params_only:
+				for k in (seed,pw_idxs):
+					assert k
 			self.pw_id_str = MMGenPWIDString(pw_id_str)
 			self.set_pw_fmt(pw_fmt)
 			self.set_pw_len(pw_len)
@@ -835,7 +842,9 @@ Record this checksum: it will be used to verify the password file in the future
 		qmsg(self.msgs[('record_chksum','check_chksum')[bool(infile)]])
 
 	def set_pw_fmt(self,pw_fmt):
-		assert pw_fmt in self.pw_info
+		if pw_fmt not in self.pw_info:
+			m = '{!r}: invalid password format.  Valid formats: {}'
+			raise InvalidPasswdFormat(m.format(pw_fmt,', '.join(sorted(self.pw_info))))
 		self.pw_fmt = pw_fmt
 
 	def chk_pw_len(self,passwd=None):
@@ -847,21 +856,21 @@ Record this checksum: it will be used to verify the password file in the future
 			pw_len = len(passwd)
 			fs = '{pw}: {b} has invalid length {l} ({c}{m} characters)'
 		d = self.pw_info[self.pw_fmt]
-		if pw_len > d['max_len']:
-			die(2,fs.format(l=pw_len,b=d['desc'],c='>',m=d['max_len'],pw=passwd))
-		elif pw_len < d['min_len']:
-			die(2,fs.format(l=pw_len,b=d['desc'],c='<',m=d['min_len'],pw=passwd))
+		if pw_len > d.max_len:
+			die(2,fs.format(l=pw_len,b=d.desc,c='>',m=d.max_len,pw=passwd))
+		elif pw_len < d.min_len:
+			die(2,fs.format(l=pw_len,b=d.desc,c='<',m=d.min_len,pw=passwd))
 
 	def set_pw_len(self,pw_len):
 		assert self.pw_fmt in self.pw_info
 		d = self.pw_info[self.pw_fmt]
 
 		if pw_len is None:
-			self.pw_len = d['dfl_len']
+			self.pw_len = d.dfl_len
 			return
 
 		if not is_int(pw_len):
-			die(2,"'{}': invalid user-requested password length (not an integer)".format(pw_len,d['desc']))
+			die(2,"'{}': invalid user-requested password length (not an integer)".format(pw_len,d.desc))
 		self.pw_len = int(pw_len)
 		self.chk_pw_len()
 
@@ -874,8 +883,8 @@ Record this checksum: it will be used to verify the password file in the future
 			return baseconv.fromhex(hex_sec,self.pw_fmt,pad=self.pw_len,tostr=True)[-self.pw_len:]
 
 	def check_format(self,pw):
-		if not {'b58':is_b58_str,'b32':is_b32_str,'hex':is_hex_str}[self.pw_fmt](pw):
-			msg('Password is not a valid {} string'.format(self.pw_fmt))
+		if not self.pw_info[self.pw_fmt].chk_func(pw):
+			msg('Password is not valid {} data'.format(self.pw_fmt))
 			return False
 		if len(pw) != self.pw_len:
 			msg('Password has incorrect length ({} != {})'.format(len(pw),self.pw_len))

+ 1 - 0
mmgen/exception.py

@@ -30,6 +30,7 @@ class UserAddressNotInWallet(Exception):  mmcode = 1
 class MnemonicError(Exception):           mmcode = 1
 class RangeError(Exception):              mmcode = 1
 class FileNotFound(Exception):            mmcode = 1
+class InvalidPasswdFormat(Exception):     mmcode = 1
 
 # 2: yellow hl, message only
 class InvalidTokenAddress(Exception):     mmcode = 2

+ 22 - 19
mmgen/main_passgen.py

@@ -27,11 +27,8 @@ from mmgen.addr import PasswordList,AddrIdxList
 from mmgen.seed import SeedSource
 from mmgen.obj import MMGenPWIDString
 
-dfl_len = {
-	'b58': PasswordList.pw_info['b58']['dfl_len'],
-	'b32': PasswordList.pw_info['b32']['dfl_len'],
-	'hex': PasswordList.pw_info['hex']['dfl_len']
-}
+pwi = PasswordList.pw_info
+pwi_fs = '{:5} {:1} {:26} {:<7}  {:<7}  {}'
 
 opts_data = {
 	'sets': [('print_checksum',True,'quiet',True)],
@@ -44,18 +41,17 @@ opts_data = {
 		'options': """
 -h, --help            Print this help message
 --, --longhelp        Print help message for long options (common options)
--b, --base32          Generate passwords in Base32 format instead of Base58
--x, --hex             Generate passwords in raw hex format instead of Base58
 -d, --outdir=      d  Output files to directory 'd' instead of working dir
 -e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry
+-f, --passwd-fmt=  f  Generate passwords of format 'f'.  Default: {dpf}.
+                      See PASSWORD FORMATS below
 -i, --in-fmt=      f  Input is from wallet format 'f' (see FMT CODES below)
 -H, --hidden-incog-input-params=f,o  Read hidden incognito data from file
                       'f' at offset 'o' (comma-separated)
 -O, --old-incog-fmt   Specify old-format incognito input
--L, --passwd-len=  l  Specify length of generated passwords
-                      (default: {d58} chars [base58], {d32} chars [base32],
-                      {dhex} chars [hex]).  An argument of 'h' will generate
-                      passwords of half the default length.
+-L, --passwd-len=  l  Specify length of generated passwords.  For defaults,
+                      see PASSWORD FORMATS below.  An argument of 'h' will
+                      generate passwords of half the default length.
 -l, --seed-len=    l  Specify wallet seed length of 'l' bits.  This option
                       is required only for brainwallet and incognito inputs
                       with non-standard (< {g.seed_len}-bit) seed lengths
@@ -82,15 +78,19 @@ range(s).
 Changing either the password format (base32,base58) or length alters the seed
 and thus generates a completely new set of passwords.
 
-EXAMPLE:
+PASSWORD FORMATS:
+
+  {pfi}
 
-  Generate ten base58 passwords of length {d58} for Alice's email account:
+EXAMPLES:
+
+  Generate ten base58 passwords of length {i58.dfl_len} for Alice's email account:
   {g.prog_name} alice@nowhere.com 1-10
 
   Generate ten base58 passwords of length 16 for Alice's email account:
   {g.prog_name} -L16 alice@nowhere.com 1-10
 
-  Generate ten base32 passwords of length {d32} for Alice's email account:
+  Generate ten base32 passwords of length {i32.dfl_len} for Alice's email account:
   {g.prog_name} -b alice@nowhere.com 1-10
 
   The three sets of passwords are completely unrelated to each other, so
@@ -111,15 +111,19 @@ FMT CODES:
 	'code': {
 		'options': lambda s: s.format(
 			seed_lens=', '.join(map(str,g.seed_lens)),
-			g=g,pnm=g.proj_name,d58=dfl_len['b58'],d32=dfl_len['b32'],dhex=dfl_len['hex'],
+			g=g,pnm=g.proj_name,
+			dpf=PasswordList.dfl_pw_fmt,
 			kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)])
 		),
 		'notes': lambda s: s.format(
-				o=opts,g=g,d58=dfl_len['b58'],d32=dfl_len['b32'],
+				o=opts,g=g,i58=pwi['b58'],i32=pwi['b32'],
 				ml=MMGenPWIDString.max_len,
 				fs="', '".join(MMGenPWIDString.forbidden),
 				n_pw=help_notes('passwd'),
 				n_bw=help_notes('brainwallet'),
+				pfi='\n  '.join(
+					[pwi_fs.format('Code','','Description','Min Len','Max Len','Default Len')] +
+					[pwi_fs.format(k,'-',v.desc,v.min_len,v.max_len,v.dfl_len) for k,v in pwi.items()]),
 				n_fmt='\n  '.join(SeedSource.format_fmt_codes().splitlines())
 		)
 	}
@@ -135,9 +139,8 @@ pw_id_str = cmd_args.pop()
 
 sf = get_seed_file(cmd_args,1)
 
-pw_fmt = ('b58','b32','hex')[bool(opt.base32)+2*bool(opt.hex)]
-
-pw_len = (opt.passwd_len,dfl_len[pw_fmt]//2)[opt.passwd_len in ('h','H')]
+pw_fmt = opt.passwd_fmt or PasswordList.dfl_pw_fmt
+pw_len = pwi[pw_fmt].dfl_len // 2 if opt.passwd_len in ('h','H') else opt.passwd_len
 
 PasswordList(pw_id_str=pw_id_str,pw_len=pw_len,pw_fmt=pw_fmt,chk_params_only=True)
 do_license_msg()

+ 18 - 10
mmgen/tool.py

@@ -530,24 +530,32 @@ class MMGenToolCmdMnemonic(MMGenToolCmdBase):
 class MMGenToolCmdFile(MMGenToolCmdBase):
 	"utilities for viewing/checking MMGen address and transaction files"
 
-	def addrfile_chksum(self,mmgen_addrfile:str):
-		"compute checksum for MMGen address file"
+	def _file_chksum(self,mmgen_addrfile,objname):
 		opt.yes = True
 		opt.quiet = True
-		from mmgen.addr import AddrList
-		return AddrList(mmgen_addrfile).chksum
+		from mmgen.addr import AddrList,KeyAddrList,PasswordList
+		ret = locals()[objname](mmgen_addrfile)
+		if opt.verbose:
+			if ret.al_id.mmtype.name == 'password':
+				fs = 'Passwd fmt:  {}\nPasswd len:  {}\nID string:   {}'
+				msg(fs.format(capfirst(ret.pw_info[ret.pw_fmt].desc),ret.pw_len,ret.pw_id_str))
+			else:
+				msg('Base coin:   {} {}'.format(ret.base_coin,('Mainnet','Testnet')[ret.is_testnet]))
+				msg('MMType:      {}'.format(capfirst(ret.al_id.mmtype.name)))
+			msg('List length: {}'.format(len(ret.data)))
+		return ret.chksum
+
+	def addrfile_chksum(self,mmgen_addrfile:str):
+		"compute checksum for MMGen address file"
+		return self._file_chksum(mmgen_addrfile,'AddrList')
 
 	def keyaddrfile_chksum(self,mmgen_keyaddrfile:str):
 		"compute checksum for MMGen key-address file"
-		opt.yes = True
-		opt.quiet = True
-		from mmgen.addr import KeyAddrList
-		return KeyAddrList(mmgen_keyaddrfile).chksum
+		return self._file_chksum(mmgen_keyaddrfile,'KeyAddrList')
 
 	def passwdfile_chksum(self,mmgen_passwdfile:str):
 		"compute checksum for MMGen password file"
-		from mmgen.addr import PasswordList
-		return PasswordList(infile=mmgen_passwdfile).chksum
+		return self._file_chksum(mmgen_passwdfile,'PasswordList')
 
 	def txview( varargs_call_sig = { # hack to allow for multiple filenames
 					'args': (

+ 6 - 0
test/ref/98831F3A-фубар@crypto.org-b32-12[1,4,1100].pws

@@ -0,0 +1,6 @@
+# checksum: 7252 CD8D EF0D 3DB1
+98831F3A фубар@crypto.org b32:12 {
+  1     RKJQF2WWSLM4
+  4     TI6GBLACNOY7
+  1100  7DT7HQ4XRX47
+}

+ 6 - 0
test/ref/98831F3A-фубар@crypto.org-b32-24[1,4,1100].pws

@@ -0,0 +1,6 @@
+# checksum: 8D56 3845 A072 A5B9
+98831F3A фубар@crypto.org b32:24 {
+  1     HB562PVXOENVQUFW2IEGVNCY
+  4     PR2CWXFIFYRDM6GAAGV6DW2G
+  1100  MXFSBMRBMSAGFAGMWZRHLUCP
+}

+ 6 - 0
test/ref/98831F3A-фубар@crypto.org-b58-10[1,4,1100].pws

@@ -0,0 +1,6 @@
+# checksum: 534F CC1A 6701 9FED
+98831F3A фубар@crypto.org b58:10 {
+  1     NctypN7ezP
+  4     X8yVG6o97A
+  1100  31Lu2icDDX
+}

+ 2 - 5
test/ref/98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100].pws → test/ref/98831F3A-фубар@crypto.org-b58-20[1,4,1100].pws

@@ -2,16 +2,13 @@
 #
 # This file is editable.
 # Everything following a hash symbol '#' is a comment and ignored by MMGen.
-# A text label of 32 characters or less may be added to the right of each
+# A text label of 80 screen cells or less may be added to the right of each
 # password.  The label may contain any printable ASCII symbol.
 #
-# Password data checksum for 98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100]: A983 DAB9 5514 27FB
+# Password data checksum for 98831F3A-фубар@crypto.org-b58-20[1,4,1100]: DDD9 44B0 CA28 183F
 # Record this value to a secure location.
 98831F3A фубар@crypto.org b58:20 {
   1     MZ5eTt6xjpK1h6YeFuzJ
   4     E5gXkqxwpLPRzCVSHNfv
-  9     qaoShe24QgvDXA19Ue5r
-  10    kSMEeWLRH29Fthd6pDE7
-  11    5JXuV3oRdG2KQcejAYhE
   1100  n3VQMkfGutW49yZDUny1
 }

+ 6 - 0
test/ref/98831F3A-фубар@crypto.org-hex-64[1,4,1100].pws

@@ -0,0 +1,6 @@
+# checksum: F11D CB0A 8AE3 4D21
+98831F3A фубар@crypto.org hex:64 {
+  1     9eeed8f75ae71bd1a2b1a457bcd353da193f31fc67943704189f7b16fe351a52
+  4     9f4b2d455a471dfce7f7e65b5aa527fb16fd150ba58a4c4ed9cb27ab32510a4f
+  1100  75a18569e42f22464af109fbe39509e66fa3f0b9c334d4de87f0d4fe515d13df
+}

+ 43 - 15
test/test_py_d/ts_ref.py

@@ -41,7 +41,11 @@ 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_passwdfile':  '98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100].pws',
+		'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',
+		'ref_passwdfile_b58_20': '98831F3A-фубар@crypto.org-b58-20[1,4,1100].pws',
+		'ref_passwdfile_hex_64': '98831F3A-фубар@crypto.org-hex-64[1,4,1100].pws',
 		'ref_tx_file': { # data shared with ref_altcoin, autosign
 			'btc': ('0B8D5A[15.31789,14,tl=1320969600].rawtx',
 					'0C7115[15.86255,14,tl=1320969600].testnet.rawtx'),
@@ -77,7 +81,11 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared):
 			'btc': ('9F2D D781 1812 8BAD','88CC 5120 9A91 22C2'),
 			'ltc': ('B804 978A 8796 3ED4','98B5 AC35 F334 0398'),
 		},
-		'ref_passwdfile_chksum':   'A983 DAB9 5514 27FB',
+		'ref_passwdfile_b32_12_chksum': '7252 CD8D EF0D 3DB1',
+		'ref_passwdfile_b32_24_chksum': '8D56 3845 A072 A5B9',
+		'ref_passwdfile_b58_10_chksum': '534F CC1A 6701 9FED',
+		'ref_passwdfile_b58_20_chksum': 'DDD9 44B0 CA28 183F',
+		'ref_passwdfile_hex_64_chksum': 'F11D CB0A 8AE3 4D21',
 	}
 	cmd_group = ( # TODO: move to tooltest2
 		('ref_words_to_subwallet_chk1','subwallet generation from reference words file (long subseed)'),
@@ -90,7 +98,13 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared):
 		('ref_segwitaddrfile_chk','saved reference address file (segwit)'),
 		('ref_bech32addrfile_chk','saved reference address file (bech32)'),
 		('ref_keyaddrfile_chk','saved reference key-address file'),
-		('ref_passwdfile_chk', 'saved reference password file'),
+
+		('ref_passwdfile_chk_b58_20','saved reference password file (base58, 20 chars)'),
+		('ref_passwdfile_chk_b58_10','saved reference password file (base58, 10 chars)'),
+		('ref_passwdfile_chk_b32_24','saved reference password file (base32, 24 chars)'),
+		('ref_passwdfile_chk_b32_12','saved reference password file (base32, 12 chars)'),
+		('ref_passwdfile_chk_hex_64','saved reference password file (hexadecimal, 64 chars)'),
+
 #	Create the fake inputs:
 #	('txcreate8',          'transaction creation (8)'),
 		('ref_tx_chk',         'signing saved reference tx file'),
@@ -161,19 +175,29 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared):
 	def ref_subwallet_keygen2(self):
 		return self.ref_subwallet_addrgen('1S',target='key')
 
-	def ref_addrfile_chk(self,ftype='addr',coin=None,subdir=None,pfx=None,mmtype=None,add_args=[]):
-		af_key = 'ref_{}file'.format(ftype)
+	def ref_addrfile_chk(
+			self,
+			ftype    = 'addr',
+			coin     = None,
+			subdir   = None,
+			pfx      = None,
+			mmtype   = None,
+			add_args = [],
+			id_key   = None,
+			pat      = 'BTC Mainnet.*Legacy'):
+		af_key = 'ref_{}file'.format(ftype) + ('_' + id_key if id_key else '')
 		af_fn = TestSuiteRef.sources[af_key].format(pfx or self.altcoin_pfx,'' if coin else self.tn_ext)
 		af = joinpath(ref_dir,(subdir or self.ref_subdir,'')[ftype=='passwd'],af_fn)
 		coin_arg = [] if coin == None else ['--coin='+coin]
 		tool_cmd = ftype.replace('segwit','').replace('bech32','')+'file_chksum'
-		t = self.spawn('mmgen-tool',coin_arg+['-p1',tool_cmd,af]+add_args)
+		t = self.spawn('mmgen-tool',coin_arg+['--verbose','-p1',tool_cmd,af]+add_args)
 		if ftype == 'keyaddr':
 			t.do_decrypt_ka_data(hp=ref_kafile_hash_preset,pw=ref_kafile_pass,have_yes_opt=True)
-		rc = self.chk_data[   'ref_' + ftype + 'file_chksum' +
-					('_'+coin.lower() if coin else '') +
-					('_'+mmtype if mmtype else '')]
+		chksum_key = '_'.join([af_key,'chksum'] + ([coin.lower()] if coin else []) + ([mmtype] if mmtype else []))
+		rc = self.chk_data[chksum_key]
 		ref_chksum = rc if (ftype == 'passwd' or coin) else rc[g.proto.base_coin.lower()][g.testnet]
+		if pat:
+			t.expect(pat,regex=True)
 		t.expect(chksum_pat,regex=True)
 		m = t.p.match.group(0)
 		t.read()
@@ -183,20 +207,24 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared):
 	def ref_segwitaddrfile_chk(self):
 		if not 'S' in g.proto.mmtypes:
 			return skip('not supported')
-		else:
-			return self.ref_addrfile_chk(ftype='segwitaddr')
+		return self.ref_addrfile_chk(ftype='segwitaddr',pat='BTC Mainnet.*Segwit')
 
 	def ref_bech32addrfile_chk(self):
 		if not 'B' in g.proto.mmtypes:
 			return skip('not supported')
-		else:
-			return self.ref_addrfile_chk(ftype='bech32addr')
+		return self.ref_addrfile_chk(ftype='bech32addr',pat='BTC Mainnet.*Bech32')
 
 	def ref_keyaddrfile_chk(self):
 		return self.ref_addrfile_chk(ftype='keyaddr')
 
-	def ref_passwdfile_chk(self):
-		return self.ref_addrfile_chk(ftype='passwd')
+	def ref_passwdfile_chk(self,key,pat):
+		return self.ref_addrfile_chk(ftype='passwd',id_key=key,pat=pat)
+
+	def ref_passwdfile_chk_b58_20(self): return self.ref_passwdfile_chk(key='b58_20',pat='Base58.*len.* 20\n')
+	def ref_passwdfile_chk_b58_10(self): return self.ref_passwdfile_chk(key='b58_10',pat='Base58.*len.* 10\n')
+	def ref_passwdfile_chk_b32_24(self): return self.ref_passwdfile_chk(key='b32_24',pat='Base32.*len.* 24\n')
+	def ref_passwdfile_chk_b32_12(self): return self.ref_passwdfile_chk(key='b32_12',pat='Base32.*len.* 12\n')
+	def ref_passwdfile_chk_hex_64(self): return self.ref_passwdfile_chk(key='hex_64',pat='Hexadec.*len.* 64\n')
 
 	def ref_tx_chk(self):
 		fn = self.sources['ref_tx_file'][g.coin.lower()][bool(self.tn_ext)]

+ 13 - 6
test/test_py_d/ts_ref_3seed.py

@@ -73,6 +73,7 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
 			'ltc': ('A6AD DF53 5968 7B6A','9572 43E0 A4DC 0B2E'),
 		},
 		'refpasswdgen_1':     'EB29 DC4F 924B 289F',
+		'refpasswdgen_half_1':'D310 2593 B5D9 2E88',
 		'ref_b32passwdgen_1': '37B6 C218 2ABC 7508',
 		'ref_hexpasswdgen_1': '523A F547 0E69 8323',
 		'refaddrgen_legacy_2': {
@@ -108,6 +109,7 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
 			'ltc': ('5C12 FDD4 17AB F179','E195 B28C 59C4 C5EC'),
 		},
 		'refpasswdgen_2':     'ADEA 0083 094D 489A',
+		'refpasswdgen_half_2':'12B3 4929 9506 76E0',
 		'ref_b32passwdgen_2': '2A28 C5C7 36EC 217A',
 		'ref_hexpasswdgen_2': 'B11C AC6A 1464 608D',
 		'refaddrgen_legacy_3': {
@@ -143,6 +145,7 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
 			'ltc': ('74A0 7DD5 963B 6326','2CDA A007 4B9F E9A5'),
 		},
 		'refpasswdgen_3':     '2D6D 8FBA 422E 1315',
+		'refpasswdgen_half_3':'272C B770 0176 D7EA',
 		'ref_b32passwdgen_3': 'F6C1 CDFB 97D9 FCAE',
 		'ref_hexpasswdgen_3': 'BD4F A0AC 8628 4BE4',
 	}
@@ -171,9 +174,10 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
 		('refkeyaddrgen_compressed', (['mmdat',pwfile],'new refwallet key-addr chksum (compressed)')),
 		('refkeyaddrgen_segwit', (['mmdat',pwfile],'new refwallet key-addr chksum (segwit)')),
 		('refkeyaddrgen_bech32', (['mmdat',pwfile],'new refwallet key-addr chksum (bech32)')),
-		('refpasswdgen',   (['mmdat',pwfile],'new refwallet passwd file chksum')),
-		('ref_b32passwdgen',(['mmdat',pwfile],'new refwallet passwd file chksum (base32)')),
-		('ref_hexpasswdgen',(['mmdat',pwfile],'new refwallet passwd file chksum (base32)')),
+		('refpasswdgen',         (['mmdat',pwfile],'new refwallet passwd file chksum')),
+		('refpasswdgen_half',    (['mmdat',pwfile],'new refwallet passwd file chksum (half-length)')),
+		('ref_b32passwdgen',     (['mmdat',pwfile],'new refwallet passwd file chksum (base32)')),
+		('ref_hexpasswdgen',     (['mmdat',pwfile],'new refwallet passwd file chksum (base32)')),
 	)
 
 	def __init__(self,trunner,cfgs,spawn):
@@ -315,11 +319,14 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
 	def refpasswdgen(self,wf,pf):
 		return self.addrgen(wf,pf,check_ref=True,ftype='pass',id_str='alice@crypto.org')
 
+	def refpasswdgen_half(self,wf,pf):
+		ea = ['--passwd-len=h']
+		return self.addrgen(wf,pf,check_ref=True,ftype='pass',id_str='alice@crypto.org',extra_args=ea)
+
 	def ref_b32passwdgen(self,wf,pf):
-		ea = ['--base32','--passwd-len','17']
+		ea = ['--passwd-fmt=b32','--passwd-len=17']
 		return self.addrgen(wf,pf,check_ref=True,ftype='pass32',id_str='фубар@crypto.org',extra_args=ea)
 
 	def ref_hexpasswdgen(self,wf,pf):
-		ea = ['--hex']
+		ea = ['--passwd-fmt=hex']
 		return self.addrgen(wf,pf,check_ref=True,ftype='passhex',id_str='фубар@crypto.org',extra_args=ea)
-