die roll wallet: interactive input support

- Create a wallet of any MMGen-supported format by inputting rolls of a die
  interactively at the keyboard.

Testing:

  $ test/test.py -e input

Examples:

  Create a default MMGen wallet from interactive die rolls:

  $ mmgen-walletconv -i dieroll

  Create a BIP39 mnemonic seed phrase from interactive die rolls, outputting
  to screen without prompting:

  $ mmgen-walletconv -Sq -i dieroll -o bip39
This commit is contained in:
The MMGen Project 2019-10-30 21:49:17 +00:00
commit 4714ef84d5
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
4 changed files with 77 additions and 0 deletions

View file

@ -1031,24 +1031,78 @@ class DieRollSeedFile(SeedSourceUnenc):
desc = 'base6d die roll seed data'
ext = 'b6d'
conv_cls = baseconv
wclass = 'dieroll'
wl_id = 'b6d'
mn_type = 'base6d'
choose_seedlen_prompt = 'Choose a seed length: 1) 128 bits, 2) 192 bits, 3) 256 bits: '
choose_seedlen_confirm = 'Seed length of {} bits chosen. OK?'
user_entropy_prompt = 'Would you like to provide some additional entropy from the keyboard?'
interactive_input = False
def _format(self):
d = baseconv.frombytes(self.seed.data,'b6d',pad='seed',tostr=True) + '\n'
self.fmt_data = block_format(d,gw=5,cols=5)
def _deformat(self):
d = self.fmt_data.translate(dict((ord(ws),None) for ws in '\t\n '))
# truncate seed to correct length, discarding high bits
seed_len = self.conv_cls.seedlen_map_rev['b6d'][len(d)]
seed_bytes = baseconv.tobytes(d,'b6d',pad='seed')[-seed_len:]
if self.interactive_input and opt.usr_randchars:
if keypress_confirm(self.user_entropy_prompt):
seed_bytes = add_user_random(seed_bytes,'die roll data')
self.desc += ' plus user-supplied entropy'
self.seed = Seed(seed_bytes)
self.ssdata.hexseed = seed_bytes.hex()
check_usr_seed_len(self.seed.bitlen)
return True
def _get_data_from_user(self,desc):
if not g.stdin_tty:
return get_data_from_user(desc)
seed_bitlens = [n*8 for n in sorted(self.conv_cls.seedlen_map['b6d'])]
seed_bitlen = self._choose_seedlen(self.wclass,seed_bitlens,self.mn_type)
nDierolls = self.conv_cls.seedlen_map['b6d'][seed_bitlen // 8]
m = 'For a {sb}-bit seed you must roll the die {nd} times. After each die roll,\n'
m += 'enter the result on the keyboard as a digit. If you make an invalid entry,\n'
m += "you'll be prompted to re-enter it."
msg('\n'+m.format(sb=seed_bitlen,nd=nDierolls)+'\n')
b6d_digits = self.conv_cls.digits['b6d']
from mmgen.term import get_char,get_char
def get_digit(n):
p = '\b\b\b \rEnter die roll #{}: '+ CUR_SHOW
sleep = 0.3
while True:
ch = get_char(p.format(n),num_chars=1,sleep=sleep).decode()
if ch in b6d_digits:
msg_r(CUR_HIDE + ' OK')
return ch
else:
msg_r(CUR_HIDE + '\rInvalid entry ')
sleep = 0.7
p = '\r' + ' '*25 + CUR_SHOW + p
dierolls,n = [],1
while len(dierolls) < nDierolls:
dierolls.append(get_digit(n))
n += 1
msg('Die rolls successfully entered' + CUR_SHOW)
self.interactive_input = True
return ''.join(dierolls)
class PlainHexSeedFile(SeedSourceUnenc):
stdin_ok = True

View file

@ -96,6 +96,9 @@ def pp_fmt(d):
def pp_msg(d):
msg(pp_fmt(d))
CUR_HIDE = '\033[?25l'
CUR_SHOW = '\033[?25h'
def set_for_type(val,refval,desc,invert_bool=False,src=None):
src_str = (''," in '{}'".format(src))[bool(src)]
if type(refval) == bool:

View file

@ -173,3 +173,8 @@ def stealth_mnemonic_entry(t,mn,fmt):
for j in range(len(w)):
t.send(w[j])
time.sleep(0.005)
def user_dieroll_entry(t,data):
for s in data:
t.expect(r'Enter die roll #.+: ',s,regex=True)
time.sleep(0.005)

View file

@ -120,6 +120,8 @@ class TestSuiteInput(TestSuiteBase):
('password_entry_echo', (1,"utf8 password entry (echoed)", [])),
('mnemonic_entry_mmgen', (1,"stealth mnemonic entry (MMGen native)", [])),
('mnemonic_entry_bip39', (1,"stealth mnemonic entry (BIP39)", [])),
('dieroll_entry', (1,"dieroll entry (base6d)", [])),
('dieroll_entry_usrrand', (1,"dieroll entry (base6d) with added user entropy", [])),
)
def password_entry(self,prompt,cmd_args):
@ -150,12 +152,23 @@ class TestSuiteInput(TestSuiteBase):
if wcls.wclass == 'mnemonic':
mn = read_from_file(wf).strip().split()
mn = ['foo'] + mn[:5] + ['grac','graceful'] + mn[5:]
elif wcls.wclass == 'dieroll':
mn = list(read_from_file(wf).strip().translate(dict((ord(ws),None) for ws in '\t\n ')))
for idx,val in ((5,'x'),(18,'0'),(30,'7'),(44,'9')):
mn.insert(idx,val)
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')
if wcls.wclass == 'mnemonic':
stealth_mnemonic_entry(t,mn,fmt=fmt)
elif wcls.wclass == 'dieroll':
user_dieroll_entry(t,mn)
if usr_rand:
t.expect(wcls.user_entropy_prompt,'y')
t.usr_rand(10)
else:
t.expect(wcls.user_entropy_prompt,'n')
if not usr_rand:
sid_chk = 'FE3C6545'
sid = t.expect_getend('Valid {} for Seed ID '.format(wcls.desc))[:8]
@ -166,6 +179,8 @@ class TestSuiteInput(TestSuiteBase):
def mnemonic_entry_mmgen(self): return self._user_seed_entry('words')
def mnemonic_entry_bip39(self): return self._user_seed_entry('bip39')
def dieroll_entry(self): return self._user_seed_entry('dieroll')
def dieroll_entry_usrrand(self):return self._user_seed_entry('dieroll',usr_rand=True,out_fmt='bip39')
class TestSuiteTool(TestSuiteMain,TestSuiteBase):
"tests for interactive 'mmgen-tool' commands"