Browse Source

MMGen & BIP39 mnemonic: refactor interactive input code, clean up test

The MMGen Project 5 years ago
parent
commit
3931cc7be1
6 changed files with 61 additions and 39 deletions
  1. 2 0
      mmgen/baseconv.py
  2. 2 0
      mmgen/bip39.py
  3. 3 0
      mmgen/globalvars.py
  4. 36 25
      mmgen/seed.py
  5. 0 2
      test/test_py_d/common.py
  6. 18 12
      test/test_py_d/ts_misc.py

+ 2 - 0
mmgen/baseconv.py

@@ -58,10 +58,12 @@ class baseconv(object):
 	seedlen_map = {
 		'b58': { 16:22, 24:33, 32:44 },
 		'b6d': { 16:50, 24:75, 32:100 },
+		'mmgen': { 16:12, 24:18, 32:24 },
 	}
 	seedlen_map_rev = {
 		'b58': { 22:16, 33:24, 44:32 },
 		'b6d': { 50:16, 75:24, 100:32 },
+		'mmgen': { 12:16, 18:24, 24:32 },
 	}
 
 	@classmethod

+ 2 - 0
mmgen/bip39.py

@@ -2082,6 +2082,8 @@ zoo
 
 	mn_base = 2048
 	wl_chksums = { 'bip39': 'f18b9a84' }
+	seedlen_map     = { 'bip39': { 16:12, 24:18, 32:24 } }
+	seedlen_map_rev = { 'bip39': { 12:16, 18:24, 24:32 } }
 	#    ENT   CS  MS
 	constants = {
 		'128': (4, 12),

+ 3 - 0
mmgen/globalvars.py

@@ -204,6 +204,9 @@ class g(object):
 	max_tx_file_size = 100000
 	max_input_size   = 1024 * 1024
 
+	# pexpect chokes on these utf8 chars under MSYS2
+	lq,rq = (('“','”'),('"','"'))[bool(os.getenv('MMGEN_TEST_SUITE')) and platform=='win']
+
 	passwd_max_tries = 5
 
 	max_urandchars = 80

+ 36 - 25
mmgen/seed.py

@@ -692,6 +692,29 @@ class SeedSourceUnenc(SeedSource):
 			self.ext,
 			x='-α' if g.debug_utf8 else '')
 
+	def _choose_seedlen(self,desc,ok_lens,subtype):
+
+		from mmgen.term import get_char
+
+		def choose_len():
+			prompt = self.choose_seedlen_prompt
+			while True:
+				r = get_char('\r'+prompt).decode()
+				if is_int(r) and 1 <= int(r) <= len(ok_lens):
+					break
+			msg_r(('\r','\n')[g.test_suite] + ' '*len(prompt) + '\r')
+			return ok_lens[int(r)-1]
+
+		m1 = blue('{} type:'.format(capfirst(desc)))
+		m2 = yellow(subtype)
+		msg('{} {}'.format(m1,m2))
+
+		while True:
+			usr_len = choose_len()
+			prompt = self.choose_seedlen_confirm.format(usr_len)
+			if keypress_confirm(prompt,default_yes=True,no_nl=not g.test_suite):
+				return usr_len
+
 class SeedSourceEnc(SeedSource):
 
 	_msg = {
@@ -819,40 +842,28 @@ class MMGenMnemonic(SeedSourceUnenc):
 	stdin_ok = True
 	fmt_codes = 'mmwords','words','mnemonic','mnem','mn','m'
 	desc = 'MMGen native mnemonic data'
-	mn_name = 'MMGen native'
+	wclass = 'mnemonic'
+	mn_type = 'MMGen native'
 	ext = 'mmwords'
-	mn_lens = [i // 32 * 3 for i in g.seed_lens]
 	wl_id = 'mmgen'
 	conv_cls = baseconv
+	choose_seedlen_prompt = 'Choose a mnemonic length: 1) 12 words, 2) 18 words, 3) 24 words: '
+	choose_seedlen_confirm = 'Mnemonic length of {} words chosen. OK?'
+
+	@property
+	def mn_lens(self):
+		return sorted(self.conv_cls.seedlen_map_rev[self.wl_id])
 
 	def _get_data_from_user(self,desc):
 
 		if not g.stdin_tty:
 			return get_data_from_user(desc)
 
-		from mmgen.term import get_char_raw,get_char
-
-		def choose_mn_len():
-			prompt = 'Choose a mnemonic length: 1) 12 words, 2) 18 words, 3) 24 words: '
-			urange = [str(i+1) for i in range(len(self.mn_lens))]
-			while True:
-				r = get_char('\r'+prompt).decode()
-				if r in urange: break
-			msg_r(('\r','\n')[g.test_suite] + ' '*len(prompt) + '\r')
-			return self.mn_lens[int(r)-1]
-
-		msg('{} {}'.format(blue('Mnemonic type:'),yellow(self.mn_name)))
-
-		while True:
-			mn_len = choose_mn_len()
-			prompt = 'Mnemonic length of {} words chosen. OK?'.format(mn_len)
-			if keypress_confirm(prompt,default_yes=True,no_nl=not g.test_suite):
-				break
+		mn_len = self._choose_seedlen(self.wclass,self.mn_lens,self.mn_type)
 
 		self.conv_cls.init_mn(self.wl_id)
 		wl = self.conv_cls.digits[self.wl_id]
 		longest_word = max(len(w) for w in wl)
-		from string import ascii_lowercase
 
 		m  = 'Enter your {ml}-word seed phrase, hitting ENTER or SPACE after each word.\n'
 		m += "Optionally, you may use pad characters.  Anything you type that's not a\n"
@@ -861,10 +872,10 @@ class MMGenMnemonic(SeedSourceUnenc):
 		m += "of words.  For each word, once you've typed {lw} characters total (including\n"
 		m += 'pad characters) any pad character will enter the word.'
 
-		# pexpect chokes on these utf8 chars under MSYS2
-		lq,rq = (('“','”'),('"','"'))[g.test_suite and g.platform=='win']
-		msg(m.format(ml=mn_len,lw=longest_word,lq=lq,rq=rq))
+		msg(m.format(ml=mn_len,lw=longest_word,lq=g.lq,rq=g.rq))
 
+		from string import ascii_lowercase
+		from mmgen.term import get_char_raw
 		def get_word():
 			s,pad = '',0
 			while True:
@@ -954,7 +965,7 @@ class BIP39Mnemonic(MMGenMnemonic):
 
 	fmt_codes = ('bip39',)
 	desc = 'BIP39 mnemonic data'
-	mn_name = 'BIP39'
+	mn_type = 'BIP39'
 	ext = 'bip39'
 	wl_id = 'bip39'
 

+ 0 - 2
test/test_py_d/common.py

@@ -46,8 +46,6 @@ non_mmgen_fn = 'coinkey'
 
 ref_dir = os.path.join('test','ref')
 dfl_words_file = os.path.join(ref_dir,'98831F3A.mmwords')
-mn_words_mmgen = os.path.join(ref_dir,'FE3C6545.mmwords')
-mn_words_bip39 = os.path.join(ref_dir,'FE3C6545.bip39')
 
 from mmgen.obj import MMGenTXLabel,TwComment
 

+ 18 - 12
test/test_py_d/ts_misc.py

@@ -25,6 +25,7 @@ from test.common import *
 from test.test_py_d.common import *
 from test.test_py_d.ts_base import *
 from test.test_py_d.ts_main import TestSuiteMain
+from mmgen.seed import SeedSource
 
 class TestSuiteHelp(TestSuiteBase):
 	'help, info and usage screens'
@@ -143,23 +144,28 @@ class TestSuiteInput(TestSuiteBase):
 			return 'skip' # pexpect double-escapes utf8, so skip
 		return self.password_entry('Enter passphrase (echoed): ',['--echo-passphrase'])
 
-	def _mnemonic_entry(self,fmt,mn_name,wf):
-		mn = read_from_file(wf).strip().split()
-		mn = ['foo'] + mn[:5] + ['grac','graceful'] + mn[5:]
-		t = self.spawn('mmgen-walletconv',['-S','-i',fmt,'-o',fmt])
-		t.expect('Mnemonic type: {}'.format(mn_name))
-		t.expect('words: ','1')
+	def _user_seed_entry(self,fmt,usr_rand=False,out_fmt=None):
+		wcls = SeedSource.fmt_code_to_type(fmt)
+		wf = os.path.join(ref_dir,'FE3C6545.{}'.format(wcls.ext))
+		if wcls.wclass == 'mnemonic':
+			mn = read_from_file(wf).strip().split()
+			mn = ['foo'] + mn[:5] + ['grac','graceful'] + mn[5:]
+		t = self.spawn('mmgen-walletconv',['-r10','-S','-i',fmt,'-o',out_fmt or fmt])
+		t.expect('{} type: {}'.format(capfirst(wcls.wclass),wcls.mn_type))
+		t.expect(wcls.choose_seedlen_prompt,'1')
 		t.expect('(Y/n): ','y')
-		stealth_mnemonic_entry(t,mn,fmt=fmt)
-		sid_chk = 'FE3C6545'
-		sid = t.expect_getend('Valid {} mnemonic data for Seed ID '.format(mn_name))[:8]
-		assert sid == sid_chk,'Seed ID mismatch! {} != {}'.format(sid,sid_chk)
+		if wcls.wclass == 'mnemonic':
+			stealth_mnemonic_entry(t,mn,fmt=fmt)
+		if not usr_rand:
+			sid_chk = 'FE3C6545'
+			sid = t.expect_getend('Valid {} for Seed ID '.format(wcls.desc))[:8]
+			assert sid == sid_chk,'Seed ID mismatch! {} != {}'.format(sid,sid_chk)
 		t.expect('to confirm: ','YES\n')
 		t.read()
 		return t
 
-	def mnemonic_entry_mmgen(self): return self._mnemonic_entry('words','MMGen native',mn_words_mmgen)
-	def mnemonic_entry_bip39(self): return self._mnemonic_entry('bip39','BIP39',mn_words_bip39)
+	def mnemonic_entry_mmgen(self): return self._user_seed_entry('words')
+	def mnemonic_entry_bip39(self): return self._user_seed_entry('bip39')
 
 class TestSuiteTool(TestSuiteMain,TestSuiteBase):
 	"tests for interactive 'mmgen-tool' commands"