modularize wallet classes

- all code has been relocated from `wallet.py` to individual modules under
  `wallet`, with each wallet type having its own module

- the fully rewritten initialization code can be found in `__init__.py` and
  `base.py`
This commit is contained in:
The MMGen Project 2022-02-08 13:03:32 +00:00
commit 9649f5b723
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
38 changed files with 1611 additions and 1276 deletions

View file

@ -1 +1 @@
13.1.dev016
13.1.dev017

View file

@ -30,7 +30,7 @@ class Filename(MMGenObject):
def __init__(self,fn,base_class=None,subclass=None,proto=None,write=False):
"""
'base_class' - a base class with an 'ext_to_type' method
'base_class' - a base class with an 'ext_to_cls' method
'subclass' - a subclass with an 'ext' attribute
One or the other must be provided, but not both.
@ -52,7 +52,7 @@ class Filename(MMGenObject):
die(3,f'Class {(subclass or base_class).__name__!r} does not support the Filename API')
if base_class:
subclass = base_class.ext_to_type(self.ext,proto)
subclass = base_class.ext_to_cls( self.ext, proto )
if not subclass:
die( 'BadFileExtension', f'{self.ext!r}: not a recognized file extension for {base_class}' )

View file

@ -116,9 +116,9 @@ def get_seed_file(cmd_args,nargs,invoked_as=None):
from .opts import opt
from .filename import find_file_in_dir
from .wallet import MMGenWallet
from .wallet.mmgen import wallet
wf = find_file_in_dir(MMGenWallet,g.data_dir)
wf = find_file_in_dir(wallet,g.data_dir)
wd_from_opt = bool(opt.hidden_incog_input_params or opt.in_fmt) # have wallet data from opt?

View file

@ -81,8 +81,8 @@ def help_notes_func(proto,po,k):
])
def fmt_codes():
from .wallet import Wallet
return '\n '.join( Wallet.format_fmt_codes().splitlines() )
from .wallet import format_fmt_codes
return '\n '.join( format_fmt_codes().splitlines() )
def coin_id():
return proto.coin_id

View file

@ -22,8 +22,7 @@ mmgen/main_wallet: Entry point for MMGen wallet-related scripts
import os
from .common import *
from .wallet import Wallet,MMGenWallet
from .filename import find_file_in_dir
from .wallet import Wallet,get_wallet_cls
usage = '[opts] [infile]'
nargs = 1
@ -234,7 +233,7 @@ if invoked_as == 'passchg' and ss_in.infile.dirname == g.data_dir:
elif invoked_as == 'gen' and not opt.outdir and not opt.stdout:
from .filename import find_file_in_dir
if (
not find_file_in_dir( MMGenWallet, g.data_dir )
not find_file_in_dir( get_wallet_cls('mmgen'), g.data_dir )
and keypress_confirm(
'Make this wallet your default and move it to the data directory?',
default_yes = True ) ):

View file

@ -469,23 +469,23 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
end ))
def chk_in_fmt(key,val,desc):
from .wallet import Wallet,IncogWallet,Brainwallet,IncogWalletHidden
sstype = Wallet.fmt_code_to_type(val)
if not sstype:
from .wallet import get_wallet_data
wd = get_wallet_data(fmt_code=val)
if not wd:
opt_unrecognized(key,val)
if key == 'out_fmt':
p = 'hidden_incog_output_params'
if sstype == IncogWalletHidden and not getattr(opt,p):
if wd.type == 'incog_hidden' and not getattr(opt,p):
die( 'UserOptError',
'Hidden incog format output requested. ' +
f'You must supply a file and offset with the {fmt_opt(p)!r} option' )
if issubclass(sstype,IncogWallet) and opt.old_incog_fmt:
if wd.base_type == 'incog_base' and opt.old_incog_fmt:
opt_display(key,val,beg='Selected',end=' ')
opt_display('old_incog_fmt',beg='conflicts with',end=':\n')
die( 'UserOptError', 'Export to old incog wallet format unsupported' )
elif issubclass(sstype,Brainwallet):
elif wd.type == 'brain':
die( 'UserOptError', 'Output to brainwallet format unsupported' )
chk_out_fmt = chk_in_fmt
@ -515,8 +515,9 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
if hasattr(opt,key2):
val2 = getattr(opt,key2)
from .wallet import IncogWalletHidden
if val2 and val2 not in IncogWalletHidden.fmt_codes:
from .wallet import get_wallet_data
wd = get_wallet_data('incog_hidden')
if val2 and val2 not in wd.fmt_codes:
die( 'UserOptError', f'Option conflict:\n {fmt_opt(key)}, with\n {fmt_opt(key2)}={val2}' )
chk_hidden_incog_output_params = chk_hidden_incog_input_params

View file

@ -44,7 +44,7 @@ def _get_cls_info(clsname,modname,args,kwargs):
from ..util import get_extension
from .completed import Completed
ext = get_extension( kwargs['filename'] )
cls = Completed.ext_to_type( ext, proto )
cls = Completed.ext_to_cls( ext, proto )
if not cls:
die(1,f'{ext!r}: unrecognized file extension for CompletedTX')
clsname = cls.__name__

View file

@ -53,7 +53,7 @@ class Completed(Base):
return MMGenTxFile(self)
@classmethod
def ext_to_type(cls,ext,proto):
def ext_to_cls(cls,ext,proto):
"""
see twctl:import_token()
"""

View file

@ -24,7 +24,7 @@ from .common import *
from .obj import MMGenList
from .addr import MMGenAddrType
from .addrlist import AddrIdxList,KeyAddrList
from .wallet import Wallet,WalletUnenc,WalletEnc,MMGenWallet
from .wallet import Wallet,get_wallet_extensions,get_wallet_cls
saved_seeds = {}
@ -117,13 +117,12 @@ def get_tx_files(opt,args):
def get_seed_files(opt,args):
# favor unencrypted seed sources first, as they don't require passwords
u,e = WalletUnenc,WalletEnc
ret = _pop_matching_fns(args,u.get_extensions())
ret = _pop_matching_fns( args, get_wallet_extensions('unenc') )
from .filename import find_file_in_dir
wf = find_file_in_dir(MMGenWallet,g.data_dir) # Make this the first encrypted ss in the list
wf = find_file_in_dir(get_wallet_cls('mmgen'),g.data_dir) # Make this the first encrypted ss in the list
if wf:
ret.append(wf)
ret += _pop_matching_fns(args,e.get_extensions())
ret += _pop_matching_fns( args, get_wallet_extensions('enc') )
if not (ret or opt.mmgen_keys_from_file or opt.keys_from_file): # or opt.use_wallet_dat
die(1,'You must specify a seed or key source!')
return ret

View file

@ -453,9 +453,8 @@ def compare_or_die(val1, desc1, val2, desc2, e='Error'):
return True
def check_wallet_extension(fn):
from .wallet import Wallet
if not Wallet.ext_to_type(get_extension(fn)):
die( 'BadFileExtension', f'{fn!r}: unrecognized seed source file extension' )
from .wallet import get_wallet_data
get_wallet_data( ext=get_extension(fn), die_on_fail=True ) # raises exception on failure
def make_full_path(outdir,outfile):
return os.path.normpath(os.path.join(outdir, os.path.basename(outfile)))

File diff suppressed because it is too large Load diff

158
mmgen/wallet/__init__.py Executable file
View file

@ -0,0 +1,158 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.__init__: wallet class initializer
"""
import importlib
from collections import namedtuple
from ..globalvars import g
from ..opts import opt
from ..util import die,get_extension
from ..objmethods import MMGenObject
from ..seed import Seed
_wd = namedtuple('wallet_data', ['type','name','ext','base_type','enc','fmt_codes'])
_pd = namedtuple('partial_wallet_data',['name','ext','base_type','enc','fmt_codes'])
wallet_data = {
'bip39': _pd('BIP39Mnemonic', 'bip39', 'mnemonic', False,('bip39',)),
'brain': _pd('Brainwallet', 'mmbrain',None, True, ('mmbrain','brainwallet','brain','bw')),
'dieroll': _pd('DieRollWallet', 'b6d', None, False,('b6d','die','dieroll')),
'incog': _pd('IncogWallet', 'mmincog','incog_base',True, ('mmincog','incog','icg','i')),
'incog_hex': _pd('IncogWalletHex', 'mmincox','incog_base',True, ('mmincox','incox','incog_hex','ix','xi')),
'incog_hidden':_pd('IncogWalletHidden',None, 'incog_base',True, ('incog_hidden','hincog','ih','hi')),
'mmgen': _pd('MMGenWallet', 'mmdat', None, True, ('wallet','w')),
'mmhex': _pd('MMGenHexSeedFile', 'mmhex', None, False,('seedhex','hexseed','mmhex')),
'plainhex': _pd('PlainHexSeedFile', 'hex', None, False,('hex','rawhex','plainhex')),
'seed': _pd('MMGenSeedFile', 'mmseed', None, False,('mmseed','seed','s')),
'words': _pd('MMGenMnemonic', 'mmwords','mnemonic', False,('mmwords','words','mnemonic','mn','m')),
}
def get_wallet_data(*args,**kwargs):
if args:
return _wd( args[0], *wallet_data[args[0]] )
for key in ('fmt_code','ext'):
if key in kwargs:
val = kwargs[key]
break
else:
die('{!r}: unrecognized argument'.format( list(kwargs.keys())[0] ))
if key == 'fmt_code':
for k,v in wallet_data.items():
if val in v.fmt_codes:
return _wd(k,*v)
else:
for k,v in wallet_data.items():
if val == getattr(v,key):
return _wd(k,*v)
if 'die_on_fail' in kwargs:
die( *{
'ext': ('BadFileExtension', f'{val!r}: unrecognized wallet file extension'),
'fmt_code': (3, f'{val!r}: unrecognized wallet format code'),
'type': (3, f'{val!r}: unrecognized wallet type'),
}[key] )
def get_wallet_cls(*args,**kwargs):
return getattr(
importlib.import_module( 'mmgen.wallet.{}'.format(
args[0] if args else get_wallet_data(*args,**kwargs).type)
),
'wallet' )
def get_wallet_extensions(key):
return {
'enc': [v.ext for v in wallet_data.values() if v.enc],
'unenc': [v.ext for v in wallet_data.values() if not v.enc]
}[key]
def format_fmt_codes():
d = [(
v.name,
('.' + v.ext if v.ext else 'None'),
','.join(v.fmt_codes)
) for v in wallet_data.values()]
w = max(len(i[0]) for i in d)
ret = [f'{a:<{w}} {b:<9} {c}' for a,b,c in [
('Format','FileExt','Valid codes'),
('------','-------','-----------')
] + sorted(d) ]
return '\n'.join(ret) + ('','')[g.debug_utf8] + '\n'
def _get_me(modname):
return MMGenObject.__new__( getattr( importlib.import_module(f'mmgen.wallet.{modname}'), 'wallet' ) )
def Wallet(
fn = None,
ss = None,
seed_bin = None,
seed = None,
passchg = False,
in_data = None,
ignore_in_fmt = False,
in_fmt = None,
passwd_file = None ):
in_fmt = in_fmt or opt.in_fmt
if opt.out_fmt:
ss_out = get_wallet_data(fmt_code=opt.out_fmt)
if not ss_out:
die(1,f'{opt.out_fmt!r}: unrecognized output format')
else:
ss_out = None
if seed or seed_bin:
me = _get_me( ss_out.type if ss_out else 'mmgen' ) # default to native wallet format
me.seed = seed or Seed(seed_bin=seed_bin)
me.op = 'new'
elif ss:
me = _get_me( ss.type if passchg else ss_out.type if ss_out else 'mmgen' )
me.seed = ss.seed
me.ss_in = ss
me.op = 'pwchg_new' if passchg else 'conv'
elif fn or opt.hidden_incog_input_params:
if fn:
wd = get_wallet_data(ext=get_extension(fn),die_on_fail=True)
if in_fmt and (not ignore_in_fmt) and in_fmt not in wd.fmt_codes:
die(1,f'{in_fmt}: --in-fmt parameter does not match extension of input file')
me = _get_me( wd.type )
else:
fn = ','.join(opt.hidden_incog_input_params.split(',')[:-1]) # permit comma in filename
me = _get_me( 'incog_hidden' )
from ..filename import Filename
me.infile = Filename( fn, subclass=type(me) )
me.op = 'pwchg_old' if passchg else 'old'
elif in_fmt:
me = _get_me( get_wallet_data(fmt_code=in_fmt).type )
me.op = 'pwchg_old' if passchg else 'old'
else: # called with no arguments: initialize with random seed
me = _get_me( ss_out.type if ss_out else 'mmgen' ) # default to native wallet format
me.seed = Seed()
me.op = 'new'
me.__init__(
fn = fn,
ss = ss,
seed_bin = seed_bin,
seed = seed,
passchg = passchg,
in_data = in_data,
ignore_in_fmt = ignore_in_fmt,
in_fmt = in_fmt,
passwd_file = passwd_file )
return me

136
mmgen/wallet/base.py Executable file
View file

@ -0,0 +1,136 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.base: wallet base class
"""
import os
from ..globalvars import g
from ..opts import opt
from ..util import msg,qmsg,die,get_data_from_user
from ..objmethods import MMGenObject
from . import Wallet,wallet_data,get_wallet_cls
class WalletMeta(type):
def __init__(cls,name,bases,namespace):
cls.type = cls.__module__.split('.')[-1]
if cls.type in wallet_data:
for k,v in wallet_data[cls.type]._asdict().items():
setattr(cls,k,v)
class wallet(MMGenObject,metaclass=WalletMeta):
desc = 'MMGen seed source'
file_mode = 'text'
filename_api = True
stdin_ok = False
ask_tty = True
no_tty = False
op = None
class WalletData(MMGenObject):
pass
def __init__(self,
fn = None,
ss = None,
seed_bin = None,
seed = None,
passchg = False,
in_data = None,
ignore_in_fmt = False,
in_fmt = None,
passwd_file = None ):
self.passwd_file = passwd_file or opt.passwd_file
self.ssdata = self.WalletData()
self.msg = {}
self.in_data = in_data
for c in reversed(self.__class__.__mro__):
if hasattr(c,'_msg'):
self.msg.update(c._msg)
if hasattr(self,'seed'):
self._encrypt()
return
elif hasattr(self,'infile') or self.in_data or not g.stdin_tty:
self._deformat_once()
self._decrypt_retry()
else:
if not self.stdin_ok:
die(1,f'Reading from standard input not supported for {self.desc} format')
self._deformat_retry()
self._decrypt_retry()
qmsg('Valid {} for Seed ID {}{}'.format(
self.desc,
self.seed.sid.hl(),
(f', seed length {self.seed.bitlen}' if self.seed.bitlen != 256 else '')
))
def _get_data(self):
if hasattr(self,'infile'):
from ..fileutil import get_data_from_file
self.fmt_data = get_data_from_file(self.infile.name,self.desc,binary=self.file_mode=='binary')
elif self.in_data:
self.fmt_data = self.in_data
else:
self.fmt_data = self._get_data_from_user(self.desc)
def _get_data_from_user(self,desc):
return get_data_from_user(desc)
def _deformat_once(self):
self._get_data()
if not self._deformat():
die(2,'Invalid format for input data')
def _deformat_retry(self):
while True:
self._get_data()
if self._deformat():
break
msg('Trying again...')
@classmethod
def ext_to_cls(cls,ext,proto):
return get_wallet_cls(ext=ext)
def get_fmt_data(self):
self._format()
return self.fmt_data
def write_to_file(self,outdir='',desc=''):
self._format()
kwargs = {
'desc': desc or self.desc,
'ask_tty': self.ask_tty,
'no_tty': self.no_tty,
'binary': self.file_mode == 'binary'
}
if outdir:
# write_data_to_file(): outfile with absolute path overrides opt.outdir
of = os.path.abspath(os.path.join(outdir,self._filename()))
from ..fileutil import write_data_to_file
write_data_to_file(
of if outdir else self._filename(),
self.fmt_data,
**kwargs )
def check_usr_seed_len(self,bitlen=None):
chk = bitlen or self.seed.bitlen
if opt.seed_len and opt.seed_len != chk:
die(1,f'ERROR: requested seed length ({opt.seed_len}) doesn’t match seed length of source ({chk})')

26
mmgen/wallet/bip39.py Executable file
View file

@ -0,0 +1,26 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.bip39: BIP39 mnemonic wallet class
"""
from .mnemonic import wallet
class wallet(wallet):
desc = 'BIP39 mnemonic data'
mn_type = 'BIP39'
wl_id = 'bip39'
def __init__(self,*args,**kwargs):
from ..bip39 import bip39
self.conv_cls = bip39
super().__init__(*args,**kwargs)

67
mmgen/wallet/brain.py Executable file
View file

@ -0,0 +1,67 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.brain: brainwallet wallet class
"""
from ..opts import opt
from ..util import msg,qmsg,qmsg_r
from ..color import yellow
from .enc import wallet
from .seed import Seed
import mmgen.crypto as crypto
class wallet(wallet):
stdin_ok = True
desc = 'brainwallet'
# brainwallet warning message? TODO
def get_bw_params(self):
# already checked
a = opt.brain_params.split(',')
return int(a[0]),a[1]
def _deformat(self):
self.brainpasswd = ' '.join(self.fmt_data.split())
return True
def _decrypt(self):
d = self.ssdata
if opt.brain_params:
"""
Don't set opt.seed_len! When using multiple wallets, BW seed len might differ from others
"""
bw_seed_len,d.hash_preset = self.get_bw_params()
else:
if not opt.seed_len:
qmsg(f'Using default seed length of {yellow(str(Seed.dfl_len))} bits\n'
+ 'If this is not what you want, use the --seed-len option' )
self._get_hash_preset()
bw_seed_len = opt.seed_len or Seed.dfl_len
qmsg_r('Hashing brainwallet data. Please wait...')
# Use buflen arg of scrypt.hash() to get seed of desired length
seed = crypto.scrypt_hash_passphrase(
self.brainpasswd.encode(),
b'',
d.hash_preset,
buflen = bw_seed_len // 8 )
qmsg('Done')
self.seed = Seed(seed)
msg(f'Seed ID: {self.seed.sid}')
qmsg('Check this value against your records')
return True
def _format(self):
raise NotImplementedError('Brainwallet not supported as an output format')
def _encrypt(self):
raise NotImplementedError('Brainwallet not supported as an output format')

114
mmgen/wallet/dieroll.py Executable file
View file

@ -0,0 +1,114 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.dieroll: dieroll wallet class
"""
from ..globalvars import g
from ..opts import opt
from ..util import msg,msg_r,die,fmt,block_format,remove_whitespace,keypress_confirm
from ..seed import Seed
from ..baseconv import baseconv
from .unenc import wallet
class wallet(wallet):
stdin_ok = True
desc = 'base6d die roll seed data'
conv_cls = baseconv
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('b6d').frombytes(self.seed.data,pad='seed',tostr=True) + '\n'
self.fmt_data = block_format(d,gw=5,cols=5)
def _deformat(self):
d = remove_whitespace(self.fmt_data)
bc = baseconv('b6d')
rmap = bc.seedlen_map_rev
if not len(d) in rmap:
die( 'SeedLengthError', '{!r}: invalid length for {} (must be one of {})'.format(
len(d),
self.desc,
list(rmap) ))
# truncate seed to correct length, discarding high bits
seed_len = rmap[len(d)]
seed_bytes = bc.tobytes( d, pad='seed' )[-seed_len:]
if self.interactive_input and opt.usr_randchars:
if keypress_confirm(self.user_entropy_prompt):
from ..crypto import add_user_random
seed_bytes = add_user_random(
rand_bytes = seed_bytes,
desc = 'gathered from your die rolls' )
self.desc += ' plus user-supplied entropy'
self.seed = Seed(seed_bytes)
self.ssdata.hexseed = seed_bytes.hex()
self.check_usr_seed_len()
return True
def _get_data_from_user(self,desc):
if not g.stdin_tty:
return get_data_from_user(desc)
bc = baseconv('b6d')
seed_bitlen = self._choose_seedlen([ n*8 for n in sorted(bc.seedlen_map) ])
nDierolls = bc.seedlen_map[seed_bitlen // 8]
message = """
For a {sb}-bit seed you must roll the die {nd} times. After each die roll,
enter the result on the keyboard as a digit. If you make an invalid entry,
you'll be prompted to re-enter it.
"""
msg('\n'+fmt(message.strip()).format(sb=seed_bitlen,nd=nDierolls)+'\n')
CUR_HIDE = '\033[?25l'
CUR_SHOW = '\033[?25h'
cr = '\n' if g.test_suite else '\r'
prompt_fs = f'\b\b\b {cr}Enter die roll #{{}}: {CUR_SHOW}'
clear_line = '' if g.test_suite else '\r' + ' ' * 25
invalid_msg = CUR_HIDE + cr + 'Invalid entry' + ' ' * 11
from ..term import get_char
def get_digit(n):
p = prompt_fs
sleep = g.short_disp_timeout
while True:
ch = get_char(p.format(n),num_chars=1,sleep=sleep)
if ch in bc.digits:
msg_r(CUR_HIDE + ' OK')
return ch
else:
msg_r(invalid_msg)
sleep = g.err_disp_timeout
p = clear_line + prompt_fs
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)

96
mmgen/wallet/enc.py Executable file
View file

@ -0,0 +1,96 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.enc: encrypted wallet base class
"""
from ..globalvars import g
from ..opts import opt
from ..util import msg,qmsg,make_chksum_8
import mmgen.crypto as crypto
from .base import wallet
class wallet(wallet):
def _decrypt_retry(self):
while True:
if self._decrypt():
break
if self.passwd_file:
die(2,'Passphrase from password file, so exiting')
msg('Trying again...')
def _get_hash_preset_from_user(self,hp,add_desc=''):
prompt = 'Enter {}hash preset for {}{}{},\nor hit ENTER to {} value ({!r}): '.format(
('old ' if self.op=='pwchg_old' else 'new ' if self.op=='pwchg_new' else ''),
('','new ')[self.op=='new'],
self.desc,
('',' '+add_desc)[bool(add_desc)],
('accept the default','reuse the old')[self.op=='pwchg_new'],
hp )
return crypto.get_hash_preset_from_user( hash_preset=hp, prompt=prompt )
def _get_hash_preset(self,add_desc=''):
if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'hash_preset'):
old_hp = self.ss_in.ssdata.hash_preset
if opt.keep_hash_preset:
hp = old_hp
qmsg(f'Reusing hash preset {hp!r} at user request')
elif opt.hash_preset:
hp = opt.hash_preset
qmsg(f'Using hash preset {hp!r} requested on command line')
else: # Prompt, using old value as default
hp = self._get_hash_preset_from_user(old_hp,add_desc)
if (not opt.keep_hash_preset) and self.op == 'pwchg_new':
qmsg('Hash preset {}'.format( 'unchanged' if hp == old_hp else f'changed to {hp!r}' ))
elif opt.hash_preset:
hp = opt.hash_preset
qmsg(f'Using hash preset {hp!r} requested on command line')
else:
hp = self._get_hash_preset_from_user(g.dfl_hash_preset,add_desc)
self.ssdata.hash_preset = hp
def _get_new_passphrase(self):
return crypto.get_new_passphrase(
data_desc = ('new ' if self.op in ('new','conv') else '') + self.desc,
hash_preset = self.ssdata.hash_preset,
passwd_file = self.passwd_file,
pw_desc = ('new ' if self.op=='pwchg_new' else '') + 'passphrase' )
def _get_passphrase(self,add_desc=''):
return crypto.get_passphrase(
data_desc = self.desc + (f' {add_desc}' if add_desc else ''),
passwd_file = self.passwd_file,
pw_desc = ('old ' if self.op == 'pwchg_old' else '') + 'passphrase' )
def _get_first_pw_and_hp_and_encrypt_seed(self):
d = self.ssdata
self._get_hash_preset()
if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'passwd'):
old_pw = self.ss_in.ssdata.passwd
if opt.keep_passphrase:
d.passwd = old_pw
qmsg('Reusing passphrase at user request')
else:
d.passwd = self._get_new_passphrase()
if self.op == 'pwchg_new':
qmsg('Passphrase {}'.format( 'unchanged' if d.passwd == old_pw else 'changed' ))
else:
d.passwd = self._get_new_passphrase()
from hashlib import sha256
d.salt = sha256( crypto.get_random(128) ).digest()[:crypto.salt_len]
key = crypto.make_key( d.passwd, d.salt, d.hash_preset )
d.key_id = make_chksum_8(key)
d.enc_seed = crypto.encrypt_seed( self.seed.data, key )

21
mmgen/wallet/incog.py Executable file
View file

@ -0,0 +1,21 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.incog: incognito wallet class
"""
from .incog_base import wallet
class wallet(wallet):
desc = 'incognito data'
file_mode = 'binary'
no_tty = True

169
mmgen/wallet/incog_base.py Executable file
View file

@ -0,0 +1,169 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.incog_base: incognito wallet base class
"""
from ..globalvars import g
from ..opts import opt
from ..seed import Seed
from ..util import msg,vmsg,qmsg,make_chksum_8,keypress_confirm
from .enc import wallet
import mmgen.crypto as crypto
class wallet(wallet):
_msg = {
'check_incog_id': """
Check the generated Incog ID above against your records. If it doesn't
match, then your incognito data is incorrect or corrupted.
""",
'record_incog_id': """
Make a record of the Incog ID but keep it secret. You will use it to
identify your incog wallet data in the future.
""",
'decrypt_params': " {} hash preset"
}
def _make_iv_chksum(self,s):
from hashlib import sha256
return sha256(s).hexdigest()[:8].upper()
def _get_incog_data_len(self,seed_len):
return (
crypto.aesctr_iv_len
+ crypto.salt_len
+ (0 if opt.old_incog_fmt else crypto.hincog_chk_len)
+ seed_len//8 )
def _incog_data_size_chk(self):
# valid sizes: 56, 64, 72
dlen = len(self.fmt_data)
seed_len = opt.seed_len or Seed.dfl_len
valid_dlen = self._get_incog_data_len(seed_len)
if dlen == valid_dlen:
return True
else:
if opt.old_incog_fmt:
msg('WARNING: old-style incognito format requested. Are you sure this is correct?')
msg(f'Invalid incognito data size ({dlen} bytes) for this seed length ({seed_len} bits)')
msg(f'Valid data size for this seed length: {valid_dlen} bytes')
for sl in Seed.lens:
if dlen == self._get_incog_data_len(sl):
die(1,f'Valid seed length for this data size: {sl} bits')
msg(f'This data size ({dlen} bytes) is invalid for all available seed lengths')
return False
def _encrypt (self):
self._get_first_pw_and_hp_and_encrypt_seed()
if opt.old_incog_fmt:
die(1,'Writing old-format incog wallets is unsupported')
d = self.ssdata
# IV is used BOTH to initialize counter and to salt password!
d.iv = crypto.get_random( crypto.aesctr_iv_len )
d.iv_id = self._make_iv_chksum(d.iv)
msg(f'New Incog Wallet ID: {d.iv_id}')
qmsg('Make a record of this value')
vmsg('\n ' + self.msg['record_incog_id'].strip()+'\n')
d.salt = crypto.get_random( crypto.salt_len )
key = crypto.make_key( d.passwd, d.salt, d.hash_preset, 'incog wallet key' )
from hashlib import sha256
chk = sha256(self.seed.data).digest()[:8]
d.enc_seed = crypto.encrypt_data(
chk + self.seed.data,
key,
crypto.aesctr_dfl_iv,
'seed' )
d.wrapper_key = crypto.make_key( d.passwd, d.iv, d.hash_preset, 'incog wrapper key' )
d.key_id = make_chksum_8(d.wrapper_key)
vmsg(f'Key ID: {d.key_id}')
d.target_data_len = self._get_incog_data_len(self.seed.bitlen)
def _format(self):
d = self.ssdata
self.fmt_data = d.iv + crypto.encrypt_data(
d.salt + d.enc_seed,
d.wrapper_key,
d.iv,
self.desc )
def _filename(self):
s = self.seed
d = self.ssdata
return '{}-{}-{}[{},{}]{x}.{}'.format(
s.fn_stem,
d.key_id,
d.iv_id,
s.bitlen,
d.hash_preset,
self.ext,
x='' if g.debug_utf8 else '')
def _deformat(self):
if not self._incog_data_size_chk():
return False
d = self.ssdata
d.iv = self.fmt_data[0:crypto.aesctr_iv_len]
d.incog_id = self._make_iv_chksum(d.iv)
d.enc_incog_data = self.fmt_data[crypto.aesctr_iv_len:]
msg(f'Incog Wallet ID: {d.incog_id}')
qmsg('Check this value against your records')
vmsg('\n ' + self.msg['check_incog_id'].strip()+'\n')
return True
def _verify_seed_newfmt(self,data):
chk,seed = data[:8],data[8:]
from hashlib import sha256
if sha256(seed).digest()[:8] == chk:
qmsg('Passphrase{} are correct'.format( self.msg['decrypt_params'].format('and') ))
return seed
else:
msg('Incorrect passphrase{}'.format( self.msg['decrypt_params'].format('or') ))
return False
def _verify_seed_oldfmt(self,seed):
m = f'Seed ID: {make_chksum_8(seed)}. Is the Seed ID correct?'
if keypress_confirm(m, True):
return seed
else:
return False
def _decrypt(self):
d = self.ssdata
self._get_hash_preset(add_desc=d.incog_id)
d.passwd = self._get_passphrase(add_desc=d.incog_id)
# IV is used BOTH to initialize counter and to salt password!
key = crypto.make_key( d.passwd, d.iv, d.hash_preset, 'wrapper key' )
dd = crypto.decrypt_data( d.enc_incog_data, key, d.iv, 'incog data' )
d.salt = dd[0:crypto.salt_len]
d.enc_seed = dd[crypto.salt_len:]
key = crypto.make_key( d.passwd, d.salt, d.hash_preset, 'main key' )
qmsg(f'Key ID: {make_chksum_8(key)}')
verify_seed = getattr(self,'_verify_seed_'+
('newfmt','oldfmt')[bool(opt.old_incog_fmt)])
seed = verify_seed( crypto.decrypt_seed(d.enc_seed, key, '', '') )
if seed:
self.seed = Seed(seed)
msg(f'Seed ID: {self.seed.sid}')
return True
else:
return False

34
mmgen/wallet/incog_hex.py Executable file
View file

@ -0,0 +1,34 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.incog_hex: hexadecimal incognito wallet class
"""
from ..util import pretty_hexdump,decode_pretty_hexdump
from .incog_base import wallet
class wallet(wallet):
desc = 'hex incognito data'
file_mode = 'text'
no_tty = False
def _deformat(self):
ret = decode_pretty_hexdump(self.fmt_data)
if ret:
self.fmt_data = ret
return super()._deformat()
else:
return False
def _format(self):
super()._format()
self.fmt_data = pretty_hexdump(self.fmt_data)

149
mmgen/wallet/incog_hidden.py Executable file
View file

@ -0,0 +1,149 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.incog_hidden: hidden incognito wallet class
"""
import os
from ..globalvars import g
from ..opts import opt
from ..seed import Seed
from ..util import (
msg,
dmsg,
qmsg,
die,
compare_or_die,
keypress_confirm,
parse_bytespec,
line_input,
capfirst,
confirm_or_raise
)
from .incog_base import wallet
class wallet(wallet):
desc = 'hidden incognito data'
file_mode = 'binary'
no_tty = True
_msg = {
'choose_file_size': """
You must choose a size for your new hidden incog data. The minimum size
is {} bytes, which puts the incog data right at the end of the file.
Since you probably want to hide your data somewhere in the middle of the
file where it's harder to find, you're advised to choose a much larger file
size than this.
""",
'check_incog_id': """
Check generated Incog ID above against your records. If it doesn't match,
then your incognito data is incorrect or corrupted, or you may have speci-
fied an incorrect offset.
""",
'record_incog_id': """
Make a record of the Incog ID but keep it secret. You will used it to
identify the incog wallet data in the future and to locate the offset
where the data is hidden in the event you forget it.
""",
'decrypt_params': ', hash preset, offset {} seed length'
}
def _get_hincog_params(self,wtype):
a = getattr(opt,'hidden_incog_'+ wtype +'_params').split(',')
return ','.join(a[:-1]),int(a[-1]) # permit comma in filename
def _check_valid_offset(self,fn,action):
d = self.ssdata
m = ('Input','Destination')[action=='write']
if fn.size < d.hincog_offset + d.target_data_len:
die(1,'{} file {!r} has length {}, too short to {} {} bytes of data at offset {}'.format(
m,
fn.name,
fn.size,
action,
d.target_data_len,
d.hincog_offset ))
def _get_data(self):
d = self.ssdata
d.hincog_offset = self._get_hincog_params('input')[1]
qmsg(f'Getting hidden incog data from file {self.infile.name!r}')
# Already sanity-checked:
d.target_data_len = self._get_incog_data_len(opt.seed_len or Seed.dfl_len)
self._check_valid_offset(self.infile,'read')
flgs = os.O_RDONLY|os.O_BINARY if g.platform == 'win' else os.O_RDONLY
fh = os.open(self.infile.name,flgs)
os.lseek(fh,int(d.hincog_offset),os.SEEK_SET)
self.fmt_data = os.read(fh,d.target_data_len)
os.close(fh)
qmsg(f'Data read from file {self.infile.name!r} at offset {d.hincog_offset}')
# overrides method in Wallet
def write_to_file(self):
d = self.ssdata
self._format()
compare_or_die(d.target_data_len, 'target data length',
len(self.fmt_data),'length of formatted ' + self.desc)
k = ('output','input')[self.op=='pwchg_new']
fn,d.hincog_offset = self._get_hincog_params(k)
if opt.outdir and not os.path.dirname(fn):
fn = os.path.join(opt.outdir,fn)
check_offset = True
try:
os.stat(fn)
except:
if keypress_confirm(
f'Requested file {fn!r} does not exist. Create?',
default_yes = True ):
min_fsize = d.target_data_len + d.hincog_offset
msg('\n ' + self.msg['choose_file_size'].strip().format(min_fsize)+'\n')
while True:
fsize = parse_bytespec(line_input('Enter file size: '))
if fsize >= min_fsize:
break
msg(f'File size must be an integer no less than {min_fsize}')
from ..tool.fileutil import tool_cmd
tool_cmd().rand2file(fn,str(fsize))
check_offset = False
else:
die(1,'Exiting at user request')
from ..filename import Filename
f = Filename(fn,subclass=type(self),write=True)
dmsg('{} data len {}, offset {}'.format(
capfirst(self.desc),
d.target_data_len,
d.hincog_offset ))
if check_offset:
self._check_valid_offset(f,'write')
if not opt.quiet:
confirm_or_raise( '', f'alter file {f.name!r}' )
flgs = os.O_RDWR|os.O_BINARY if g.platform == 'win' else os.O_RDWR
fh = os.open(f.name,flgs)
os.lseek(fh, int(d.hincog_offset), os.SEEK_SET)
os.write(fh, self.fmt_data)
os.close(fh)
msg('{} written to file {!r} at offset {}'.format(
capfirst(self.desc),
f.name,
d.hincog_offset ))

191
mmgen/wallet/mmgen.py Executable file
View file

@ -0,0 +1,191 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.mmgen: MMGen native wallet class
"""
import os
from ..globalvars import g
from ..opts import opt
from ..seed import Seed
from ..util import msg,qmsg,line_input,make_timestamp,make_chksum_6,split_into_cols,is_chksum_6,compare_chksums
from ..obj import MMGenWalletLabel,get_obj
from ..baseconv import baseconv
import mmgen.crypto as crypto
from .enc import wallet
class wallet(wallet):
desc = 'MMGen wallet'
def __init__(self,*args,**kwargs):
if opt.label:
self.label = MMGenWalletLabel(
opt.label,
msg = "Error in option '--label'" )
else:
self.label = None
super().__init__(*args,**kwargs)
# logic identical to _get_hash_preset_from_user()
def _get_label_from_user(self,old_lbl=''):
prompt = 'Enter a wallet label, or hit ENTER {}: '.format(
'to reuse the label {}'.format(old_lbl.hl(encl="''")) if old_lbl else
'for no label' )
while True:
ret = line_input(prompt)
if ret:
lbl = get_obj(MMGenWalletLabel,s=ret)
if lbl:
return lbl
else:
msg('Invalid label. Trying again...')
else:
return old_lbl or MMGenWalletLabel('No Label')
# logic identical to _get_hash_preset()
def _get_label(self):
if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'label'):
old_lbl = self.ss_in.ssdata.label
if opt.keep_label:
lbl = old_lbl
qmsg('Reusing label {} at user request'.format( lbl.hl(encl="''") ))
elif self.label:
lbl = self.label
qmsg('Using label {} requested on command line'.format( lbl.hl(encl="''") ))
else: # Prompt, using old value as default
lbl = self._get_label_from_user(old_lbl)
if (not opt.keep_label) and self.op == 'pwchg_new':
qmsg('Label {}'.format( 'unchanged' if lbl == old_lbl else f'changed to {lbl!r}' ))
elif self.label:
lbl = self.label
qmsg('Using label {} requested on command line'.format( lbl.hl(encl="''") ))
else:
lbl = self._get_label_from_user()
self.ssdata.label = lbl
def _encrypt(self):
self._get_first_pw_and_hp_and_encrypt_seed()
self._get_label()
d = self.ssdata
d.pw_status = ('NE','E')[len(d.passwd)==0]
d.timestamp = make_timestamp()
def _format(self):
d = self.ssdata
s = self.seed
bc = baseconv('b58')
slt_fmt = bc.frombytes(d.salt,pad='seed',tostr=True)
es_fmt = bc.frombytes(d.enc_seed,pad='seed',tostr=True)
lines = (
d.label,
'{} {} {} {} {}'.format( s.sid.lower(), d.key_id.lower(), s.bitlen, d.pw_status, d.timestamp ),
'{}: {} {} {}'.format( d.hash_preset, *crypto.get_hash_params(d.hash_preset) ),
'{} {}'.format( make_chksum_6(slt_fmt), split_into_cols(4,slt_fmt) ),
'{} {}'.format( make_chksum_6(es_fmt), split_into_cols(4,es_fmt) )
)
chksum = make_chksum_6(' '.join(lines).encode())
self.fmt_data = '\n'.join((chksum,)+lines) + '\n'
def _deformat(self):
def check_master_chksum(lines,desc):
if len(lines) != 6:
msg(f'Invalid number of lines ({len(lines)}) in {desc} data')
return False
if not is_chksum_6(lines[0]):
msg(f'Incorrect master checksum ({lines[0]}) in {desc} data')
return False
chk = make_chksum_6(' '.join(lines[1:]))
if not compare_chksums(lines[0],'master',chk,'computed',
hdr='For wallet master checksum',verbose=True):
return False
return True
lines = self.fmt_data.splitlines()
if not check_master_chksum(lines,self.desc):
return False
d = self.ssdata
d.label = MMGenWalletLabel(lines[1])
d1,d2,d3,d4,d5 = lines[2].split()
d.seed_id = d1.upper()
d.key_id = d2.upper()
self.check_usr_seed_len(int(d3))
d.pw_status,d.timestamp = d4,d5
hpdata = lines[3].split()
d.hash_preset = hp = hpdata[0][:-1] # a string!
qmsg(f'Hash preset of wallet: {hp!r}')
if opt.hash_preset and opt.hash_preset != hp:
qmsg(f'Warning: ignoring user-requested hash preset {opt.hash_preset!r}')
hash_params = tuple(map(int,hpdata[1:]))
if hash_params != crypto.get_hash_params(d.hash_preset):
msg(f'Hash parameters {" ".join(hash_params)!r} don’t match hash preset {d.hash_preset!r}')
return False
lmin,foo,lmax = sorted(baseconv('b58').seedlen_map_rev) # 22,33,44
for i,key in (4,'salt'),(5,'enc_seed'):
l = lines[i].split(' ')
chk = l.pop(0)
b58_val = ''.join(l)
if len(b58_val) < lmin or len(b58_val) > lmax:
msg(f'Invalid format for {key} in {self.desc}: {l}')
return False
if not compare_chksums(chk,key,
make_chksum_6(b58_val),'computed checksum',verbose=True):
return False
val = baseconv('b58').tobytes(b58_val,pad='seed')
if val == False:
msg(f'Invalid base 58 number: {b58_val}')
return False
setattr(d,key,val)
return True
def _decrypt(self):
d = self.ssdata
# Needed for multiple transactions with {}-txsign
d.passwd = self._get_passphrase(
add_desc = os.path.basename(self.infile.name) if opt.quiet else '' )
key = crypto.make_key( d.passwd, d.salt, d.hash_preset )
ret = crypto.decrypt_seed( d.enc_seed, key, d.seed_id, d.key_id )
if ret:
self.seed = Seed(ret)
return True
else:
return False
def _filename(self):
s = self.seed
d = self.ssdata
return '{}-{}[{},{}]{x}.{}'.format(
s.fn_stem,
d.key_id,
s.bitlen,
d.hash_preset,
self.ext,
x='' if g.debug_utf8 else '')

66
mmgen/wallet/mmhex.py Executable file
View file

@ -0,0 +1,66 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.mmhex: MMGen hexadecimal file wallet class
"""
from ..util import make_chksum_6,split_into_cols
from ..seed import Seed
from ..util import msg,vmsg_r,is_chksum_6,is_hex_str,compare_chksums
from .unenc import wallet
class wallet(wallet):
stdin_ok = True
desc = 'hexadecimal seed data with checksum'
def _format(self):
h = self.seed.hexdata
self.ssdata.chksum = make_chksum_6(h)
self.ssdata.hexseed = h
self.fmt_data = '{} {}\n'.format(
self.ssdata.chksum,
split_into_cols(4,h) )
def _deformat(self):
desc = self.desc
d = self.fmt_data.split()
try:
d[1]
chk,hstr = d[0],''.join(d[1:])
except:
msg(f'{self.fmt_data.strip()!r}: invalid {desc}')
return False
if not len(hstr)*4 in Seed.lens:
msg(f'Invalid data length ({len(hstr)}) in {desc}')
return False
if not is_chksum_6(chk):
msg(f'{chk!r}: invalid checksum format in {desc}')
return False
if not is_hex_str(hstr):
msg(f'{hstr!r}: not a hexadecimal string, in {desc}')
return False
vmsg_r(f'Validating {desc} checksum...')
if not compare_chksums(chk,'file',make_chksum_6(hstr),'computed',verbose=True):
return False
self.seed = Seed(bytes.fromhex(hstr))
self.ssdata.chksum = chk
self.ssdata.hexseed = hstr
self.check_usr_seed_len()
return True

86
mmgen/wallet/mnemonic.py Executable file
View file

@ -0,0 +1,86 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.mnemonic: MMGen mnemonic wallet base class
"""
from ..globalvars import g
from ..baseconv import baseconv
from ..util import msg,compare_or_die,get_data_from_user
from ..seed import Seed
from .unenc import wallet
class wallet(wallet):
stdin_ok = True
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(self.wl_id).seedlen_map_rev)
def _get_data_from_user(self,desc):
if not g.stdin_tty:
return get_data_from_user(desc)
from ..mn_entry import mn_entry # import here to catch cfg var errors
mn_len = self._choose_seedlen( self.mn_lens )
return mn_entry(self.wl_id).get_mnemonic_from_user(mn_len)
def _format(self):
hexseed = self.seed.hexdata
bc = self.conv_cls(self.wl_id)
mn = bc.fromhex( hexseed, 'seed' )
rev = bc.tohex( mn, 'seed' )
# Internal error, so just die on fail
compare_or_die( rev, 'recomputed seed', hexseed, 'original', e='Internal error' )
self.ssdata.mnemonic = mn
self.fmt_data = ' '.join(mn) + '\n'
def _deformat(self):
bc = self.conv_cls(self.wl_id)
mn = self.fmt_data.split()
if len(mn) not in self.mn_lens:
msg('Invalid mnemonic ({} words). Valid numbers of words: {}'.format(
len(mn),
', '.join(map(str,self.mn_lens)) ))
return False
for n,w in enumerate(mn,1):
if w not in bc.digits:
msg(f'Invalid mnemonic: word #{n} is not in the {self.wl_id.upper()} wordlist')
return False
hexseed = bc.tohex( mn, 'seed' )
rev = bc.fromhex( hexseed, 'seed' )
if len(hexseed) * 4 not in Seed.lens:
msg('Invalid mnemonic (produces too large a number)')
return False
# Internal error, so just die
compare_or_die( ' '.join(rev), 'recomputed mnemonic', ' '.join(mn), 'original', e='Internal error' )
self.seed = Seed(bytes.fromhex(hexseed))
self.ssdata.mnemonic = mn
self.check_usr_seed_len()
return True

44
mmgen/wallet/plainhex.py Executable file
View file

@ -0,0 +1,44 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.plainhex: plain hexadecimal wallet class
"""
from ..util import msg,is_hex_str_lc
from ..seed import Seed
from .unenc import wallet
class wallet(wallet):
stdin_ok = True
desc = 'plain hexadecimal seed data'
def _format(self):
self.fmt_data = self.seed.hexdata + '\n'
def _deformat(self):
desc = self.desc
d = self.fmt_data.strip()
if not is_hex_str_lc(d):
msg(f'{d!r}: not a lowercase hexadecimal string, in {desc}')
return False
if not len(d)*4 in Seed.lens:
msg(f'Invalid data length ({len(d)}) in {desc}')
return False
self.seed = Seed(bytes.fromhex(d))
self.ssdata.hexseed = d
self.check_usr_seed_len()
return True

68
mmgen/wallet/seed.py Executable file
View file

@ -0,0 +1,68 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.seed: seed file wallet class
"""
from ..util import msg,vmsg_r,make_chksum_6,split_into_cols,is_chksum_6,compare_chksums
from ..baseconv import baseconv,is_b58_str
from ..seed import Seed
from .unenc import wallet
class wallet(wallet):
stdin_ok = True
desc = 'seed data'
def _format(self):
b58seed = baseconv('b58').frombytes(self.seed.data,pad='seed',tostr=True)
self.ssdata.chksum = make_chksum_6(b58seed)
self.ssdata.b58seed = b58seed
self.fmt_data = '{} {}\n'.format(
self.ssdata.chksum,
split_into_cols(4,b58seed) )
def _deformat(self):
desc = self.desc
ld = self.fmt_data.split()
if not (7 <= len(ld) <= 12): # 6 <= padded b58 data (ld[1:]) <= 11
msg(f'Invalid data length ({len(ld)}) in {desc}')
return False
a,b = ld[0],''.join(ld[1:])
if not is_chksum_6(a):
msg(f'{a!r}: invalid checksum format in {desc}')
return False
if not is_b58_str(b):
msg(f'{b!r}: not a base 58 string, in {desc}')
return False
vmsg_r(f'Validating {desc} checksum...')
if not compare_chksums(a,'file',make_chksum_6(b),'computed',verbose=True):
return False
ret = baseconv('b58').tobytes(b,pad='seed')
if ret == False:
msg(f'Invalid base-58 encoded seed: {val}')
return False
self.seed = Seed(ret)
self.ssdata.chksum = a
self.ssdata.b58seed = b
self.check_usr_seed_len()
return True

57
mmgen/wallet/unenc.py Executable file
View file

@ -0,0 +1,57 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.unenc: unencrypted wallet base class
"""
from ..globalvars import g
from ..color import blue,yellow
from ..util import msg,msg_r,capfirst,is_int,keypress_confirm
from .base import wallet
class wallet(wallet):
def _decrypt_retry(self):
pass
def _encrypt(self):
pass
def _filename(self):
s = self.seed
return '{}[{}]{x}.{}'.format(
s.fn_stem,
s.bitlen,
self.ext,
x='' if g.debug_utf8 else '')
def _choose_seedlen(self,ok_lens):
from ..term import get_char
def choose_len():
prompt = self.choose_seedlen_prompt
while True:
r = get_char('\r'+prompt)
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]
msg('{} {}'.format(
blue(f'{capfirst(self.base_type or self.type)} type:'),
yellow(self.mn_type)
))
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

21
mmgen/wallet/words.py Executable file
View file

@ -0,0 +1,21 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen
"""
wallet.words: MMGen mnemonic wallet class
"""
from .mnemonic import wallet
class wallet(wallet):
desc = 'MMGen native mnemonic data'
mn_type = 'MMGen native'
wl_id = 'mmgen'

View file

@ -54,6 +54,7 @@ packages =
mmgen.share
mmgen.tool
mmgen.tx
mmgen.wallet
scripts =
cmds/mmgen-addrgen

View file

@ -51,7 +51,8 @@ def overlay_setup(repo_root):
'mmgen.proto',
'mmgen.share',
'mmgen.tool',
'mmgen.tx' ):
'mmgen.tx',
'mmgen.wallet' ):
process_srcdir(d)
return overlay_dir

View file

@ -13,7 +13,7 @@ ts_input.py: user input tests for the MMGen test.py test suite
from ..include.common import *
from .ts_base import *
from .input import *
from mmgen.wallet import Wallet
from mmgen.wallet import get_wallet_cls
class TestSuiteInput(TestSuiteBase):
'user input'
@ -207,19 +207,19 @@ class TestSuiteInput(TestSuiteBase):
return t
def _user_seed_entry(self,fmt,usr_rand=False,out_fmt=None,entry_mode='full',mn=None):
wcls = Wallet.fmt_code_to_type(fmt)
wcls = get_wallet_cls(fmt_code=fmt)
wf = os.path.join(ref_dir,f'FE3C6545.{wcls.ext}')
if wcls.wclass == 'mnemonic':
if wcls.base_type == 'mnemonic':
mn = mn or read_from_file(wf).strip().split()
elif wcls.wclass == 'dieroll':
elif wcls.type == 'dieroll':
mn = mn or list(remove_whitespace(read_from_file(wf)))
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(f'{capfirst(wcls.wclass)} type:.*{wcls.mn_type}',regex=True)
t.expect(f'{capfirst(wcls.base_type or wcls.type)} type:.*{wcls.mn_type}',regex=True)
t.expect(wcls.choose_seedlen_prompt,'1')
t.expect('(Y/n): ','y')
if wcls.wclass == 'mnemonic':
if wcls.base_type == 'mnemonic':
t.expect('Type a number.*: ','6',regex=True)
t.expect('invalid')
from mmgen.mn_entry import mn_entry
@ -229,7 +229,7 @@ class TestSuiteInput(TestSuiteBase):
mode = strip_ansi_escapes(t.p.match.group(1)).lower()
assert mode == mne.em.name.lower(), f'{mode} != {mne.em.name.lower()}'
stealth_mnemonic_entry(t,mne,mn,entry_mode=entry_mode)
elif wcls.wclass == 'dieroll':
elif wcls.type == 'dieroll':
user_dieroll_entry(t,mn)
if usr_rand:
t.expect(wcls.user_entropy_prompt,'y')

View file

@ -23,7 +23,9 @@ ts_main.py: Basic operations tests for the test.py test suite
from mmgen.globalvars import g
from mmgen.opts import opt
from mmgen.fileutil import get_data_from_file,write_data_to_file
from mmgen.wallet import Wallet,MMGenWallet,MMGenMnemonic,IncogWallet,MMGenSeedFile
from mmgen.wallet import get_wallet_cls
from mmgen.wallet.mmgen import wallet as MMGenWallet
from mmgen.wallet.incog import wallet as IncogWallet
from mmgen.rpc import rpc_init
from ..include.common import *
from .common import *
@ -277,8 +279,8 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
return t
def subwalletgen_mnemonic(self,wf):
icls = Wallet.ext_to_type(get_extension(wf))
ocls = MMGenMnemonic
icls = get_wallet_cls(ext=get_extension(wf))
ocls = get_wallet_cls('words')
args = [self.usr_rand_arg,'-p1','-d',self.tr.trash_dir,'-o',ocls.fmt_codes[0],wf,'3L']
t = self.spawn('mmgen-subwalletgen', args)
t.license()
@ -579,33 +581,34 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
t.license()
if not pf:
icls = Wallet.ext_to_type(get_extension(wf))
icls = get_wallet_cls(ext=get_extension(wf))
t.passphrase(icls.desc,self.wpasswd)
ocls = Wallet.fmt_code_to_type(out_fmt)
ocls = get_wallet_cls(fmt_code=out_fmt)
if issubclass(ocls,WalletEnc) and ocls != Brainwallet:
if ocls.enc and ocls.type != 'brain':
t.passphrase_new('new '+ocls.desc,self.wpasswd)
t.usr_rand(self.usr_rand_chars)
if ocls.__name__.startswith('Incog'):
if ocls.type.startswith('incog'):
m = 'Encrypting random data generated by your operating system with key'
t.expect(m)
t.expect(m)
incog_id = t.expect_getend('New Incog Wallet ID: ')
t.expect(m)
if ocls == IncogWalletHidden:
if ocls.type == 'incog_hidden':
self.write_to_tmpfile(incog_id_fn,incog_id)
t.hincog_create(hincog_bytes)
elif ocls == MMGenWallet:
elif ocls.type == 'mmgen':
t.label()
return t.written_to_file(capfirst(ocls.desc),oo=True),t
def export_seed(self,wf,out_fmt='seed',pf=None):
f,t = self._walletconv_export(wf,out_fmt=out_fmt,pf=pf)
silence()
wcls = Wallet.fmt_code_to_type(out_fmt)
wcls = get_wallet_cls(fmt_code=out_fmt)
msg('==> {}: {}'.format(
wcls.desc,
cyan(get_data_from_file(f,wcls.desc)) ))
@ -636,8 +639,8 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
return self.export_incog(wf,out_fmt='hi',add_args=add_args)
def addrgen_seed(self,wf,foo,in_fmt='seed'):
wcls = Wallet.fmt_code_to_type(in_fmt)
stdout = wcls == MMGenSeedFile # capture output to screen once
wcls = get_wallet_cls(fmt_code=in_fmt)
stdout = wcls.type == 'seed' # capture output to screen once
t = self.spawn(
'mmgen-addrgen',
(['-S'] if stdout else []) +
@ -665,7 +668,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
([],[wf])[bool(wf)] + [self.addr_idx_list])
t.license()
t.expect_getend('Incog Wallet ID: ')
wcls = Wallet.fmt_code_to_type(in_fmt)
wcls = get_wallet_cls(fmt_code=in_fmt)
t.hash_preset(wcls.desc,'1')
t.passphrase(rf'{wcls.desc} \w{{8}}',self.wpasswd)
vmsg('Comparing generated checksum with checksum from address file')
@ -707,7 +710,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
t = self.spawn('mmgen-txsign', ['-d',self.tmpdir,txf1,wf1,txf2,wf2])
t.license()
for cnum,wf in (('1',wf1),('2',wf2)):
wcls = Wallet.ext_to_type(get_extension(wf))
wcls = get_wallet_cls(ext=get_extension(wf))
t.view_tx('n')
t.passphrase(wcls.desc,self.cfgs[cnum]['wpasswd'])
self.txsign_end(t,cnum)
@ -730,7 +733,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
t.license()
t.view_tx('n')
for cnum,wf in (('1',wf1),('3',wf2)):
wcls = Wallet.ext_to_type(get_extension(wf))
wcls = get_wallet_cls(ext=get_extension(wf))
t.passphrase(wcls.desc,self.cfgs[cnum]['wpasswd'])
self.txsign_end(t)
return t
@ -743,7 +746,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
bwf = joinpath(self.tmpdir,self.bw_filename)
make_brainwallet_file(bwf)
seed_len = str(self.seed_len)
args = ['-d',self.tmpdir,'-p1',self.usr_rand_arg,'-l'+seed_len,'-ib']
args = ['-d',self.tmpdir,'-p1',self.usr_rand_arg,'-l'+seed_len,'-ibw']
t = self.spawn('mmgen-walletconv', args + [bwf])
t.license()
wcls = MMGenWallet
@ -830,7 +833,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
t = self.spawn('mmgen-txsign', add_args + ['-d',self.tmpdir,'-k',non_mm_file,txf,wf])
t.license()
t.view_tx('n')
wcls = Wallet.ext_to_type(get_extension(wf))
wcls = get_wallet_cls(ext=get_extension(wf))
t.passphrase(wcls.desc,self.cfgs['20']['wpasswd'])
if bad_vsize:
t.expect('Estimated transaction vsize')

View file

@ -23,7 +23,7 @@ ts_ref.py: Reference file tests for the test.py test suite
import os
from mmgen.globalvars import g
from mmgen.opts import opt
from mmgen.wallet import MMGenMnemonic
from mmgen.wallet import get_wallet_cls
from ..include.common import *
from .common import *
@ -163,7 +163,7 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared):
def ref_words_to_subwallet_chk(self,ss_idx):
wf = dfl_words_file
ocls = MMGenMnemonic
ocls = get_wallet_cls('words')
args = ['-d',self.tr.trash_dir,'-o',ocls.fmt_codes[-1],wf,ss_idx]
t = self.spawn('mmgen-subwalletgen',args,extra_desc='(generate subwallet)')

View file

@ -23,7 +23,7 @@ ts_ref_3seed.py: Saved and generated reference file tests for 128, 192 and
from mmgen.globalvars import g
from mmgen.opts import opt
from mmgen.wallet import *
from mmgen.wallet import get_wallet_cls
from ..include.common import *
from .common import *
from .ts_base import *
@ -89,22 +89,22 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
return self.walletchk(wf,pf=None,wcls=ss,sid=self.seed_id)
def ref_seed_chk(self):
return self.ref_ss_chk(ss=MMGenSeedFile)
return self.ref_ss_chk(ss=get_wallet_cls('seed'))
def ref_hex_chk(self):
return self.ref_ss_chk(ss=MMGenHexSeedFile)
return self.ref_ss_chk(ss=get_wallet_cls('mmhex'))
def ref_plainhex_chk(self):
return self.ref_ss_chk(ss=PlainHexSeedFile)
return self.ref_ss_chk(ss=get_wallet_cls('plainhex'))
def ref_dieroll_chk(self):
return self.ref_ss_chk(ss=DieRollSeedFile)
return self.ref_ss_chk(ss=get_wallet_cls('dieroll'))
def ref_mn_chk(self):
return self.ref_ss_chk(ss=MMGenMnemonic)
return self.ref_ss_chk(ss=get_wallet_cls('words'))
def ref_bip39_chk(self):
return self.ref_ss_chk(ss=BIP39Mnemonic)
return self.ref_ss_chk(ss=get_wallet_cls('bip39'))
def ref_hincog_chk(self,desc='hidden incognito data'):
source = TestSuiteWalletConv.sources[str(self.seed_len)]
@ -141,7 +141,7 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
t = self.spawn('mmgen-walletconv', args + [self.usr_rand_arg])
t.license()
t.expect('Enter brainwallet: ', ref_wallet_brainpass+'\n')
ocls = MMGenWallet
ocls = get_wallet_cls('mmgen')
t.passphrase_new('new '+ocls.desc,self.wpasswd)
t.usr_rand(self.usr_rand_chars)
fn = os.path.split(t.written_to_file(capfirst(ocls.desc)))[-1]
@ -160,7 +160,7 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
wf = self.get_file_with_ext('mmdat')
pf = joinpath(self.tmpdir,pwfile)
t = self.spawn('mmgen-walletconv',extra_args+['-d','test/trash','-o',ofmt,'-P'+pf,wf])
wcls = Wallet.fmt_code_to_type(ofmt)
wcls = get_wallet_cls(fmt_code=ofmt)
fn = os.path.split(t.written_to_file(capfirst(wcls.desc)))[-1]
idx = int(self.test_name[-1]) - 1
sid = self.chk_data['sids'][idx]

View file

@ -27,14 +27,15 @@ from mmgen.opts import opt
from mmgen.util import die,gmsg
from mmgen.protocol import init_proto
from mmgen.addrlist import AddrList
from mmgen.wallet import MMGenWallet
from mmgen.wallet import Wallet,get_wallet_cls
from ..include.common import *
from .common import *
pat_date = r'\b\d\d-\d\d-\d\d\b'
pat_date_time = r'\b\d\d\d\d-\d\d-\d\d\s+\d\d:\d\d\b'
dfl_wcls = MMGenWallet
dfl_wcls = get_wallet_cls('mmgen')
rt_pw = 'abc-α'
rt_data = {
'tx_fee': {'btc':'0.0001','bch':'0.001','ltc':'0.01'},
@ -338,7 +339,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
return os.path.basename(get_file_with_ext(self._user_dir(user),'mmdat'))[:8]
def _get_user_subsid(self,user,subseed_idx):
fn = get_file_with_ext(self._user_dir(user),MMGenWallet.ext)
fn = get_file_with_ext(self._user_dir(user),dfl_wcls.ext)
silence()
w = Wallet( fn=fn, passwd_file=os.path.join(self.tmpdir,'wallet_password') )
end_silence()

View file

@ -22,7 +22,7 @@ ts_seedsplit.py: Seed split/join tests for the test.py test suite
from mmgen.globalvars import g
from mmgen.opts import opt
from mmgen.wallet import Wallet,MMGenWallet,IncogWallet,IncogWalletHex,IncogWalletHidden,WalletEnc
from mmgen.wallet import get_wallet_cls
from .ts_base import *
@ -30,7 +30,7 @@ ref_wf = 'test/ref/98831F3A.bip39'
ref_sid = '98831F3A'
wpasswd = 'abc'
sh1_passwd = 'xyz'
dfl_wcls = MMGenWallet
dfl_wcls = get_wallet_cls('mmgen')
class TestSuiteSeedSplit(TestSuiteBase):
'splitting and joining seeds'
@ -111,11 +111,11 @@ class TestSuiteSeedSplit(TestSuiteBase):
else:
pat = f'master share #{master}'
t.expect(pat,regex=True)
ocls = Wallet.fmt_code_to_type(ofmt)
if issubclass(ocls,WalletEnc):
ocls = get_wallet_cls(fmt_code=ofmt)
if ocls.enc:
t.hash_preset('new '+ocls.desc,'1')
t.passphrase_new('new '+ocls.desc,sh1_passwd)
if ocls == IncogWalletHidden:
if ocls.type == 'incog_hidden':
t.hincog_create(1234)
t.written_to_file(capfirst(ocls.desc))
return t
@ -134,23 +134,26 @@ class TestSuiteSeedSplit(TestSuiteBase):
+ shares)
if bad_invocation:
return t
icls = ( MMGenWallet if 'mmdat' in in_exts
else IncogWallet if 'mmincog' in in_exts
else IncogWalletHex if 'mmincox' in in_exts
else IncogWalletHidden if '-H' in add_args
icls = ( dfl_wcls if 'mmdat' in in_exts
else get_wallet_cls('incog') if 'mmincog' in in_exts
else get_wallet_cls('incog_hex') if 'mmincox' in in_exts
else get_wallet_cls('incog_hidden') if '-H' in add_args
else None )
if icls in (IncogWallet,IncogWalletHex,IncogWalletHidden):
if icls.type.startswith('incog'):
t.hash_preset(icls.desc,'1')
if icls:
t.passphrase(icls.desc,sh1_passwd)
if master:
fs = "master share #{}, split id.*'{}'.*, share count {}"
pat = fs.format(master,id_str or 'default',len(shares)+(icls==IncogWalletHidden))
pat = fs.format(
master,
id_str or 'default',
len(shares) + (icls.type=='incog_hidden') )
t.expect(pat,regex=True)
sid_cmp = strip_ansi_escapes(t.expect_getend('Joined Seed ID: '))
cmp_or_die(sid,sid_cmp)
ocls = Wallet.fmt_code_to_type(ofmt)
if ocls == MMGenWallet:
ocls = get_wallet_cls(fmt_code=ofmt)
if ocls.type == 'mmgen':
t.hash_preset('new '+ocls.desc,'1')
t.passphrase_new('new '+ocls.desc,wpasswd)
t.written_to_file(capfirst(ocls.desc))

View file

@ -24,7 +24,8 @@ import os
from mmgen.globalvars import g
from mmgen.opts import opt
from mmgen.util import ymsg
from mmgen.wallet import Wallet,WalletEnc,Brainwallet,MMGenWallet,IncogWalletHidden
from mmgen.wallet import get_wallet_cls
from ..include.common import *
from .common import *
@ -167,8 +168,8 @@ class TestSuiteShared(object):
t = self.spawn('mmgen-txsign', opts, extra_desc)
t.license()
t.view_tx(view)
wcls = MMGenWallet if dfl_wallet else Wallet.ext_to_type(get_extension(wf))
if issubclass(wcls,WalletEnc) and wcls != Brainwallet:
wcls = get_wallet_cls( ext = 'mmdat' if dfl_wallet else get_extension(wf) )
if wcls.enc and wcls.type != 'brain':
t.passphrase(wcls.desc,self.wpasswd)
if save:
self.txsign_end(t,has_label=has_label)
@ -185,15 +186,15 @@ class TestSuiteShared(object):
def walletchk(self,wf,pf,wcls=None,add_args=[],sid=None,extra_desc='',dfl_wallet=False):
hp = self.hash_preset if hasattr(self,'hash_preset') else '1'
wcls = wcls or Wallet.ext_to_type(get_extension(wf))
wcls = wcls or get_wallet_cls(ext=get_extension(wf))
t = self.spawn('mmgen-walletchk',
([] if dfl_wallet else ['-i',wcls.fmt_codes[0]])
+ add_args + ['-p',hp]
+ ([wf] if wf else []),
extra_desc=extra_desc)
if wcls != IncogWalletHidden:
if wcls.type != 'incog_hidden':
t.expect(f"Getting {wcls.desc} from file '")
if issubclass(wcls,WalletEnc) and wcls != Brainwallet:
if wcls.enc and wcls.type != 'brain':
t.passphrase(wcls.desc,self.wpasswd)
t.expect(['Passphrase is OK', 'Passphrase.* are correct'],regex=True)
chk = t.expect_getend(f'Valid {wcls.desc} for Seed ID ')[:8]
@ -223,7 +224,7 @@ class TestSuiteShared(object):
[getattr(self,f'{cmd_pfx}_idx_list')],
extra_desc=f'({mmtype})' if mmtype in ('segwit','bech32') else '')
t.license()
wcls = MMGenWallet if dfl_wallet else Wallet.ext_to_type(get_extension(wf))
wcls = get_wallet_cls( ext = 'mmdat' if dfl_wallet else get_extension(wf) )
t.passphrase(wcls.desc,self.wpasswd)
t.expect('Passphrase is OK')
desc = ('address','password')[passgen]
@ -245,7 +246,7 @@ class TestSuiteShared(object):
([],['--type='+str(mmtype)])[bool(mmtype)] + args,
extra_desc=f'({mmtype})' if mmtype in ('segwit','bech32') else '')
t.license()
wcls = Wallet.ext_to_type(get_extension(wf))
wcls = get_wallet_cls(ext=get_extension(wf))
t.passphrase(wcls.desc,self.wpasswd)
chk = t.expect_getend(r'Checksum for key-address data .*?: ',regex=True)
if check_ref:

View file

@ -22,7 +22,7 @@ ts_wallet.py: Wallet conversion tests for the test.py test suite
import os
from mmgen.opts import opt
from mmgen.wallet import *
from mmgen.wallet import get_wallet_cls
from .common import *
from .ts_base import *
from .ts_shared import *
@ -107,7 +107,7 @@ class TestSuiteWalletConv(TestSuiteBase,TestSuiteShared):
def ref_brain_conv(self):
uopts = ['-i','bw','-p','1','-l',str(self.seed_len)]
return self.walletconv_in(None,uopts,oo=True,icls=Brainwallet)
return self.walletconv_in(None,uopts,oo=True,icls=get_wallet_cls('brain'))
def ref_incog_conv(self,wfk='ic_wallet',in_fmt='i'):
uopts = ['-i',in_fmt,'-p','1','-l',str(self.seed_len)]
@ -125,7 +125,7 @@ class TestSuiteWalletConv(TestSuiteBase,TestSuiteShared):
None,
uopts + hi_opt,
oo = True,
icls = IncogWalletHidden )
icls = get_wallet_cls('incog_hidden') )
def ref_hincog_conv_old(self):
return self.ref_hincog_conv(wfk='hic_wallet_old',add_uopts=['-O'])
@ -173,16 +173,16 @@ class TestSuiteWalletConv(TestSuiteBase,TestSuiteShared):
# wallet conversion tests
def walletconv_in(self,infile,uopts=[],oo=False,icls=None):
ocls = MMGenMnemonic
ocls = get_wallet_cls('words')
opts = ['-d',self.tmpdir,'-o',ocls.fmt_codes[0],self.usr_rand_arg]
if_arg = [infile] if infile else []
d = '(convert)'
t = self.spawn('mmgen-walletconv',opts+uopts+if_arg,extra_desc=d)
t.license()
icls = icls or Wallet.ext_to_type(get_extension(infile))
if icls == Brainwallet:
icls = icls or get_wallet_cls(ext=get_extension(infile))
if icls.type == 'brain':
t.expect('Enter brainwallet: ',ref_wallet_brainpass+'\n')
if issubclass(icls,WalletEnc) and icls != Brainwallet:
if icls.enc and icls.type != 'brain':
t.passphrase(icls.desc,self.wpasswd)
if self.test_name[:19] == 'ref_hincog_conv_old':
t.expect('Is the Seed ID correct? (Y/n): ','\n')
@ -198,27 +198,27 @@ class TestSuiteWalletConv(TestSuiteBase,TestSuiteShared):
sid = self.seed_id )
def walletconv_out(self,out_fmt='w',uopts=[],uopts_chk=[]):
wcls = Wallet.fmt_code_to_type(out_fmt)
wcls = get_wallet_cls(fmt_code=out_fmt)
opts = ['-d',self.tmpdir,'-p1','-o',out_fmt] + uopts
infile = joinpath(ref_dir,self.seed_id+'.mmwords')
t = self.spawn('mmgen-walletconv',[self.usr_rand_arg]+opts+[infile],extra_desc='(convert)')
add_args = [f'-l{self.seed_len}']
t.license()
if issubclass(wcls,WalletEnc) and wcls != Brainwallet:
if wcls.enc and wcls.type != 'brain':
t.passphrase_new('new '+wcls.desc,self.wpasswd)
t.usr_rand(self.usr_rand_chars)
if wcls in (IncogWallet,IncogWalletHex,IncogWalletHidden):
if wcls.type.startswith('incog'):
for i in (1,2,3):
t.expect('Encrypting random data generated by your operating system with key')
if wcls == IncogWalletHidden:
if wcls.type == 'incog_hidden':
t.hincog_create(hincog_bytes)
if out_fmt == 'w':
t.label()
wf = t.written_to_file(capfirst(wcls.desc),oo=True)
pf = None
if wcls == IncogWalletHidden:
if wcls.type == 'incog_hidden':
add_args += uopts_chk
wf = None
msg('' if opt.profile else ' OK')