new Lockable class; lock global vars after initialization (amended)

This commit is contained in:
The MMGen Project 2020-05-29 16:10:12 +00:00
commit 4c2410e0d6
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
25 changed files with 422 additions and 206 deletions

View file

@ -244,7 +244,7 @@ Actions: [q]uit view, [p]rint to file, pager [v]iew, [w]ide view,
'l':'a_lbl_add','D':'a_addr_delete','R':'a_balance_refresh' }
async def __ainit__(self,proto,*args,**kwargs):
if g.use_cached_balances:
if g.cached_balances:
self.hdr_fmt += '\n' + yellow('WARNING: Using cached balances. These may be out of date!')
await TwUnspentOutputs.__ainit__(self,proto,*args,**kwargs)

88
mmgen/base_obj.py Executable file
View file

@ -0,0 +1,88 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2020 The MMGen Project <mmgen@tuta.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
base_obj.py: base objects with no internal imports for the MMGen suite
"""
class AttrCtrl:
"""
After instance is locked, forbid setting any attribute if the attribute is not present
in either the class or instance dict.
If _use_class_attr is True, ensure that attribute's type matches that of the class
attribute, unless the class attribute is set to None, in which case no type checking
is performed.
"""
_lock = False
_use_class_attr = False
def lock(self):
self._lock = True
def __setattr__(self,name,value):
if self._lock:
def do_error(name,value,ref_val):
raise AttributeError(
f'{value!r}: invalid value for attribute {name!r}'
+ ' of {} object (must be of type {}, not {})'.format(
type(self).__name__,
type(ref_val).__name__,
type(value).__name__ ) )
if not hasattr(self,name):
raise AttributeError(f'{type(self).__name__} object has no attribute {name!r}')
ref_val = getattr(type(self),name) if self._use_class_attr else getattr(self,name)
if (ref_val is not None) and not isinstance(value,type(ref_val)):
do_error(name,value,ref_val)
return object.__setattr__(self,name,value)
def __delattr__(self,name,value):
raise AttributeError('attribute cannot be deleted')
class Lockable(AttrCtrl):
"""
After instance is locked, its attributes become read-only, with the following exceptions:
- if the attribute's name is in _set_ok, attr can be set once after locking, if unset
- if the attribute's name is in _reset_ok, read-only restrictions are bypassed and only
AttrCtrl checking is performed
To determine whether an attribute is set, it's matched against either None or the class attribute,
if _use_class_attr is True
"""
_set_ok = ()
_reset_ok = ()
def __setattr__(self,name,value):
if self._lock and hasattr(self,name):
if name not in (self._set_ok + self._reset_ok):
raise AttributeError(f'attribute {name!r} of {type(self).__name__} object is read-only')
elif name not in self._reset_ok:
#print(self.__dict__)
if not (
getattr(self,name) is None or
( self._use_class_attr and name not in self.__dict__ ) ):
raise AttributeError(
f'attribute {name!r} of {type(self).__name__} object is already set,'
+ ' and resetting is forbidden' )
# name is in (_set_ok + _reset_ok) -- allow name to be in both lists
return AttrCtrl.__setattr__(self,name,value)

View file

@ -216,14 +216,44 @@ def get_hash_preset_from_user(hp=g.dfl_hash_preset,desc='data'):
else:
return hp
_salt_len,_sha256_len,_nonce_len = 32,32,32
def get_new_passphrase(desc,passchg=False):
w = '{}passphrase for {}'.format(('','new ')[bool(passchg)], desc)
if opt.passwd_file:
pw = ' '.join(get_words_from_file(opt.passwd_file,w))
elif opt.echo_passphrase:
pw = ' '.join(get_words_from_user(f'Enter {w}: '))
else:
for i in range(g.passwd_max_tries):
pw = ' '.join(get_words_from_user(f'Enter {w}: '))
pw_chk = ' '.join(get_words_from_user('Repeat passphrase: '))
dmsg(f'Passphrases: [{pw}] [{pw_chk}]')
if pw == pw_chk:
vmsg('Passphrases match'); break
else: msg('Passphrases do not match. Try again.')
else:
die(2,f'User failed to duplicate passphrase in {g.passwd_max_tries} attempts')
if pw == '':
qmsg('WARNING: Empty passphrase')
return pw
def get_passphrase(desc,passchg=False):
prompt ='Enter {}passphrase for {}: '.format(('','old ')[bool(passchg)],desc)
if opt.passwd_file:
pwfile_reuse_warning(opt.passwd_file)
return ' '.join(get_words_from_file(opt.passwd_file,'passphrase'))
else:
return ' '.join(get_words_from_user(prompt))
_salt_len,_sha256_len,_nonce_len = (32,32,32)
def mmgen_encrypt(data,desc='data',hash_preset=''):
salt = get_random(_salt_len)
iv = get_random(g.aesctr_iv_len)
nonce = get_random(_nonce_len)
hp = hash_preset or (
opt.hash_preset if 'hash_preset' in opt.set_by_user else get_hash_preset_from_user('3',desc))
hp = hash_preset or opt.hash_preset or get_hash_preset_from_user('3',desc)
m = ('user-requested','default')[hp=='3']
vmsg(f'Encrypting {desc}')
qmsg(f'Using {m} hash preset of {hp!r}')
@ -238,8 +268,7 @@ def mmgen_decrypt(data,desc='data',hash_preset=''):
salt = data[:_salt_len]
iv = data[_salt_len:dstart]
enc_d = data[dstart:]
hp = hash_preset or (
opt.hash_preset if 'hash_preset' in opt.set_by_user else get_hash_preset_from_user('3',desc))
hp = hash_preset or opt.hash_preset or get_hash_preset_from_user('3',desc)
m = ('user-requested','default')[hp=='3']
qmsg(f'Using {m} hash preset of {hp!r}')
passwd = get_passphrase(desc)

View file

@ -25,12 +25,14 @@ from decimal import Decimal
from collections import namedtuple
from .devtools import *
from .base_obj import Lockable
def die(exit_val,s=''):
if s:
sys.stderr.write(s+'\n')
sys.exit(exit_val)
class GlobalContext:
class GlobalContext(Lockable):
"""
Set global vars to default values
Globals are overridden in this order:
@ -38,6 +40,10 @@ class GlobalContext:
2 - environmental vars
3 - command line
"""
_set_ok = ('user_entropy','session')
_reset_ok = ('stdout','stderr','accept_defaults')
_use_class_attr = True
# Constants:
version = '0.12.099'
release_date = 'May 2020'
@ -63,12 +69,12 @@ class GlobalContext:
# Variables - these might be altered at runtime:
user_entropy = b''
hash_preset = '3'
dfl_hash_preset = '3'
dfl_seed_len = 256
usr_randchars = 30
tx_fee_adj = Decimal('1.0')
tx_confs = 3
seed_len = 256
# Constant vars - some of these might be overridden in opts.py, but they don't change thereafter
@ -97,7 +103,8 @@ class GlobalContext:
monero_wallet_rpc_password = ''
rpc_fail_on_command = ''
aiohttp_rpc_queue_len = 16
use_cached_balances = False
session = None
cached_balances = False
# regtest:
bob = False
@ -141,11 +148,12 @@ class GlobalContext:
daemon_data_dir = '' # set by user
# global var sets user opt:
global_sets_opt = ( 'minconf','seed_len','hash_preset','usr_randchars','debug',
'quiet','tx_confs','tx_fee_adj','key_generator' )
global_sets_opt = (
'minconf','usr_randchars','debug', 'quiet','tx_confs','tx_fee_adj','key_generator' )
# user opt sets global var:
opt_sets_global = ( 'use_internal_keccak_module','subseeds' )
opt_sets_global = (
'use_internal_keccak_module','subseeds','cached_balances' )
# 'long' opts - opt sets global var
common_opts = (
@ -160,7 +168,7 @@ class GlobalContext:
'quiet','verbose','debug','outdir','echo_passphrase','passwd_file','stdout',
'show_hash_presets','label','keep_passphrase','keep_hash_preset','yes',
'brain_params','b16','usr_randchars','coin','bob','alice','key_generator',
'hidden_incog_input_params','in_fmt'
'hidden_incog_input_params','in_fmt','hash_preset','seed_len',
)
incompatible_opts = (
('help','longhelp'),
@ -229,6 +237,10 @@ class GlobalContext:
if platform == 'win':
autoset_opts['rpc_backend'].choices.remove('aiohttp')
auto_typeset_opts = {
'seed_len': int,
}
min_screen_width = 80
minconf = 1
max_tx_file_size = 100000
@ -271,6 +283,9 @@ class GlobalContext:
short_disp_timeout = 0.1
if os.getenv('MMGEN_TEST_SUITE_POPEN_SPAWN'):
stdin_tty = True
if prog_name == 'unit_tests.py':
_set_ok += ('debug_subseed',)
_reset_ok += ('force_standalone_scrypt_module','session')
if os.getenv('MMGEN_DEBUG_ALL'):
for name in env_opts:

View file

@ -65,9 +65,9 @@ opts_data = {
Options: {kgs} (default: {kg})
-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
with non-standard (< {g.dfl_seed_len}-bit) seed lengths
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
for password hashing (default: '{g.hash_preset}')
for password hashing (default: '{g.dfl_hash_preset}')
-z, --show-hash-presets Show information on available hash presets
-P, --passwd-file= f Get wallet passphrase from file 'f'
-q, --quiet Produce quieter output; suppress some warnings

View file

@ -31,6 +31,8 @@ wallet_dir = '/dev/shm/autosign'
key_fn = 'autosign.key'
from .common import *
opts.UserOpts._set_ok += ('outdir','passwd_file')
prog_name = os.path.basename(sys.argv[0])
opts_data = {
'sets': [('stealth_led', True, 'led', True)],
@ -107,11 +109,22 @@ This command is currently available only on Linux-based platforms.
}
}
cmd_args = opts.init(opts_data,add_opts=['mmgen_keys_from_file','in_fmt'])
cmd_args = opts.init(
opts_data,
add_opts = ['mmgen_keys_from_file','hidden_incog_input_params'],
init_opts = {
'quiet': True,
'in_fmt': 'words',
'out_fmt': 'wallet',
'usr_randchars': 0,
'hash_preset': '1',
'label': 'Autosign Wallet',
})
exit_if_mswin('autosigning')
import mmgen.tx
from .wallet import Wallet
from .txsign import txsign
from .protocol import init_proto
from .rpc import rpc_init
@ -123,6 +136,7 @@ if opt.mountpoint:
mountpoint = opt.mountpoint
opt.outdir = tx_dir = os.path.join(mountpoint,'tx')
opt.passwd_file = os.path.join(tx_dir,key_fn)
async def check_daemons_running():
if opt.coin:
@ -220,15 +234,11 @@ async def sign():
return True
def decrypt_wallets():
opt.hash_preset = '1'
opt.set_by_user = ['hash_preset']
opt.passwd_file = os.path.join(tx_dir,key_fn)
from .wallet import Wallet
msg(f'Unlocking wallet{suf(wfs)} with key from {opt.passwd_file!r}')
fails = 0
for wf in wfs:
try:
Wallet(wf)
Wallet(wf,ignore_in_fmt=True)
except SystemExit as e:
if e.code != 0:
fails += 1
@ -335,18 +345,7 @@ def create_wallet_dir():
def setup():
remove_wallet_dir()
gen_key(no_unmount=True)
from .wallet import Wallet
opt.hidden_incog_input_params = None
opt.quiet = True
opt.in_fmt = 'words'
ss_in = Wallet()
opt.out_fmt = 'wallet'
opt.usr_randchars = 0
opt.hash_preset = '1'
opt.set_by_user = ['hash_preset']
opt.passwd_file = os.path.join(tx_dir,key_fn)
from .obj import MMGenWalletLabel
opt.label = MMGenWalletLabel('Autosign Wallet')
ss_out = Wallet(ss=ss_in)
ss_out.write_to_file(desc='autosign wallet',outdir=wallet_dir)

View file

@ -54,9 +54,9 @@ opts_data = {
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
with non-standard (< {g.dfl_seed_len}-bit) seed lengths
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
for password hashing (default: '{g.hash_preset}')
for password hashing (default: '{g.dfl_hash_preset}')
-z, --show-hash-presets Show information on available hash presets
-P, --passwd-file= f Get wallet passphrase from file 'f'
-q, --quiet Produce quieter output; suppress some warnings

View file

@ -48,7 +48,7 @@ opts_data = {
-L, --label= l Specify a label 'l' for output wallet
-M, --master-share=i Use a master share with index 'i' (min:{ms_min}, max:{ms_max})
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
for password hashing (default: '{g.hash_preset}')
for password hashing (default: '{g.dfl_hash_preset}')
-z, --show-hash-presets Show information on available hash presets
-P, --passwd-file= f Get wallet passphrase from file 'f'
-q, --quiet Produce quieter output; suppress some warnings
@ -110,9 +110,6 @@ cmd_args = opts.init(opts_data)
if len(cmd_args) + bool(opt.hidden_incog_input_params) < 2:
opts.usage()
if opt.label:
opt.label = MMGenWalletLabel(opt.label,msg="Error in option '--label'")
if opt.master_share:
master_idx = MasterShareIdx(opt.master_share)
id_str = SeedSplitIDString(opt.id_str or 'default')

View file

@ -63,7 +63,7 @@ opts_data = {
--, --longhelp Print help message for long options (common options)
-k, --use-internal-keccak-module Force use of the internal keccak module
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
for password hashing (default: '{g.hash_preset}')
for password hashing (default: '{g.dfl_hash_preset}')
-P, --passwd-file= f Get passphrase from file 'f'.
-q, --quiet Produce quieter output
-r, --usr-randchars=n Get 'n' characters of additional randomness from
@ -91,8 +91,6 @@ Type '{pn} help <command>' for help on a particular command
cmd_args = opts.init(opts_data,add_opts=['hidden_incog_input_params','in_fmt','use_old_ed25519'])
g.use_cached_balances = opt.cached_balances
if len(cmd_args) < 1:
opts.usage()

View file

@ -49,7 +49,7 @@ opts_data = {
-i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below)
-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.
with non-standard (< {g.dfl_seed_len}-bit) seed lengths.
-k, --keys-from-file=f Provide additional keys for non-{pnm} addresses
-K, --key-generator= m Use method 'm' for public key generation
Options: {kgs}
@ -63,7 +63,7 @@ opts_data = {
for the transaction's change output, if present)
-O, --old-incog-fmt Specify old-format incognito input
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
for password hashing (default: '{g.hash_preset}')
for password hashing (default: '{g.dfl_hash_preset}')
-P, --passwd-file= f Get {pnm} wallet or {dn} passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without prompting
-s, --send Sign and send the transaction (the default if seed

View file

@ -76,8 +76,6 @@ opts_data = {
cmd_args = opts.init(opts_data)
g.use_cached_balances = opt.cached_balances
async def main():
from .protocol import init_proto_from_opts

View file

@ -56,7 +56,7 @@ opts_data = {
outputs associated with each address will be included.
-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.
with non-standard (< {g.dfl_seed_len}-bit) seed lengths.
-k, --keys-from-file=f Provide additional keys for non-{pnm} addresses
-K, --key-generator= m Use method 'm' for public key generation
Options: {kgs}
@ -71,7 +71,7 @@ opts_data = {
mappings, so the user should record its checksum.
-O, --old-incog-fmt Specify old-format incognito input
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
for password hashing (default: '{g.hash_preset}')
for password hashing (default: '{g.dfl_hash_preset}')
-P, --passwd-file= f Get {pnm} wallet passphrase from file 'f'
-r, --rbf Make transaction BIP 125 (replace-by-fee) replaceable
-q, --quiet Suppress warnings; overwrite files without prompting
@ -114,8 +114,6 @@ column below:
cmd_args = opts.init(opts_data)
g.use_cached_balances = opt.cached_balances
from .tx import *
from .txsign import *

View file

@ -45,9 +45,9 @@ opts_data = {
-O, --old-incog-fmt Specify old-format incognito input
-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.
with non-standard (< {g.dfl_seed_len}-bit) seed lengths.
-p, --hash-preset=p Use the scrypt hash parameters defined by preset 'p'
for password hashing (default: '{g.hash_preset}')
for password hashing (default: '{g.dfl_hash_preset}')
-z, --show-hash-presets Show information on available hash presets
-k, --keys-from-file=f Provide additional keys for non-{pnm} addresses
-K, --key-generator=m Use method 'm' for public key generation

View file

@ -101,12 +101,12 @@ opts_data = {
-K, --keep-hash-preset Reuse hash preset of input wallet for output wallet
-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.
with non-standard (< {g.dfl_seed_len}-bit) seed lengths.
-L, --label= l Specify a label 'l' for output wallet
-m, --keep-label Reuse label of input wallet for output wallet
-M, --master-share=i Use a master share with index 'i' (min:{ms_min}, max:{ms_max})
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
for password hashing (default: '{g.hash_preset}')
for password hashing (default: '{g.dfl_hash_preset}')
-z, --show-hash-presets Show information on available hash presets
-P, --passwd-file= f Get wallet passphrase from file 'f'
-q, --quiet Produce quieter output; suppress some warnings
@ -144,9 +144,6 @@ FMT CODES:
cmd_args = opts.init(opts_data,opt_filter=opt_filter)
if opt.label:
opt.label = MMGenWalletLabel(opt.label,msg="Error in option '--label'")
if invoked_as == 'subgen':
from .obj import SubSeedIdx
ss_idx = SubSeedIdx(cmd_args.pop())

View file

@ -21,13 +21,17 @@ opts.py: MMGen-specific options processing after generic processing by share.Op
"""
import sys,os,stat
class opt_cls(object):
pass
opt = opt_cls()
from .exception import UserOptError
from .globalvars import g
from .base_obj import Lockable
import mmgen.share.Opts
class UserOpts(Lockable):
_set_ok = ('usr_randchars',)
_reset_ok = ('quiet','verbose','yes')
opt = UserOpts()
from .util import *
def usage():
@ -228,7 +232,7 @@ opts_data_dfl = {
}
}
def init(opts_data=None,add_opts=[],opt_filter=None,parse_only=False):
def init(opts_data=None,add_opts=None,init_opts=None,opt_filter=None,parse_only=False):
if opts_data is None:
opts_data = opts_data_dfl
@ -238,6 +242,11 @@ def init(opts_data=None,add_opts=[],opt_filter=None,parse_only=False):
# po: (user_opts,cmd_args,opts,skipped_opts)
po = mmgen.share.Opts.parse_opts(opts_data,opt_filter=opt_filter,parse_only=parse_only)
if init_opts: # allow programs to preload user opts
for uopt,val in init_opts.items():
if uopt not in po.user_opts:
po.user_opts[uopt] = val
if parse_only:
return po
@ -248,7 +257,8 @@ def init(opts_data=None,add_opts=[],opt_filter=None,parse_only=False):
for o in set(
po.opts
+ po.skipped_opts
+ tuple(add_opts)
+ tuple(add_opts or [])
+ tuple(init_opts or [])
+ g.required_opts
+ g.common_opts ):
setattr(opt,o,po.user_opts[o] if o in po.user_opts else None)
@ -309,11 +319,9 @@ def init(opts_data=None,add_opts=[],opt_filter=None,parse_only=False):
# Set user opts from globals:
# - if opt is unset, set it to global value
# - if opt is set, convert its type to that of global value
opt.set_by_user = []
for k in g.global_sets_opt:
if hasattr(opt,k) and getattr(opt,k) != None:
setattr(opt,k,set_for_type(getattr(opt,k),getattr(g,k),'--'+k))
opt.set_by_user.append(k)
else:
setattr(opt,k,getattr(g,k))
@ -355,6 +363,8 @@ def init(opts_data=None,add_opts=[],opt_filter=None,parse_only=False):
# Check all opts against g.autoset_opts, setting if unset
check_and_set_autoset_opts()
set_auto_typeset_opts()
if opt.verbose:
opt.quiet = None
@ -370,6 +380,9 @@ def init(opts_data=None,add_opts=[],opt_filter=None,parse_only=False):
if k in opts_data:
del opts_data[k]
g.lock()
opt.lock()
return po.cmd_args
# DISABLED
@ -575,6 +588,13 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
elif g.debug:
Msg('check_usr_opts(): No test for opt {!r}'.format(key))
def set_auto_typeset_opts():
for key,ref_type in g.auto_typeset_opts.items():
if hasattr(opt,key):
val = getattr(opt,key)
if val is not None: # typeset only if opt is set
setattr(opt,key,ref_type(val))
def check_and_set_autoset_opts(): # Raises exception if any check fails
def nocase_str(key,val,asd):

View file

@ -554,7 +554,6 @@ def init_proto(coin=None,testnet=False,regtest=False,network=None,network_id=Non
tokensym = tokensym )
def init_proto_from_opts():
from .opts import opt
return init_proto(
coin = g.coin,
testnet = g.testnet,

View file

@ -32,7 +32,7 @@ class SeedBase(MMGenObject):
def __init__(self,seed_bin=None):
if not seed_bin:
# Truncate random data for smaller seed lengths
seed_bin = sha256(get_random(1033)).digest()[:opt.seed_len//8]
seed_bin = sha256(get_random(1033)).digest()[:(opt.seed_len or g.dfl_seed_len)//8]
elif len(seed_bin)*8 not in g.seed_lens:
die(3,'{}: invalid seed length'.format(len(seed_bin)))
@ -58,6 +58,7 @@ class SeedBase(MMGenObject):
class SubSeedList(MMGenObject):
have_short = True
nonce_start = 0
debug_last_share_sid_len = 3
def __init__(self,parent_seed):
self.member_type = SubSeed
@ -134,7 +135,7 @@ class SubSeedList(MMGenObject):
m2 = 'collision with parent Seed ID {},'.format(sid)
else:
if debug_last_share:
sl = g.debug_last_share_sid_len
sl = self.debug_last_share_sid_len
colliding_idx = [d[:sl] for d in self.data[slen].keys].index(sid[:sl]) + 1
sid = sid[:sl]
else:
@ -285,7 +286,7 @@ class SeedShareList(SubSeedList):
def last_share_debug(last_share):
if not debug_last_share:
return False
sid_len = g.debug_last_share_sid_len
sid_len = self.debug_last_share_sid_len
lsid = last_share.sid[:sid_len]
psid = parent_seed.sid[:sid_len]
ssids = [d[:sid_len] for d in self.data['long'].keys]

View file

@ -269,9 +269,10 @@ class MMGenToolCmdMeta(type):
class MMGenToolCmds(metaclass=MMGenToolCmdMeta):
def __init__(self,proto=None):
def __init__(self,proto=None,mmtype=None):
from .protocol import init_proto_from_opts
self.proto = proto or init_proto_from_opts()
self.mmtype = mmtype or getattr(opt,'type',None) or self.proto.dfl_mmtype
if g.token:
self.proto.tokensym = g.token.upper()
@ -279,7 +280,7 @@ class MMGenToolCmds(metaclass=MMGenToolCmdMeta):
global at,kg,ag
at = MMGenAddrType(
proto = self.proto,
id_str = getattr(opt,'type',None) or self.proto.dfl_mmtype )
id_str = self.mmtype )
if arg != 'at':
kg = KeyGenerator(self.proto,at)
ag = AddrGenerator(self.proto,at)
@ -460,7 +461,7 @@ class MMGenToolCmdCoin(MMGenToolCmds):
def wif2redeem_script(self,wifkey:'sstr'): # new
"convert a WIF private key to a Segwit P2SH-P2WPKH redeem script"
assert opt.type == 'segwit','This command is meaningful only for --type=segwit'
assert self.mmtype == 'segwit','This command is meaningful only for --type=segwit'
self.init_generators()
privhex = PrivKey(
self.proto,
@ -469,7 +470,7 @@ class MMGenToolCmdCoin(MMGenToolCmds):
def wif2segwit_pair(self,wifkey:'sstr'):
"generate both a Segwit P2SH-P2WPKH redeem script and address from WIF"
assert opt.type == 'segwit','This command is meaningful only for --type=segwit'
assert self.mmtype == 'segwit','This command is meaningful only for --type=segwit'
self.init_generators()
pubhex = kg.to_pubhex(PrivKey(
self.proto,
@ -495,26 +496,26 @@ class MMGenToolCmdCoin(MMGenToolCmds):
def pubhex2addr(self,pubkeyhex:'sstr'):
"convert a hex pubkey to an address"
if opt.type == 'segwit':
if self.mmtype == 'segwit':
return self.proto.pubhex2segwitaddr(pubkeyhex)
else:
return self.pubhash2addr(hash160(pubkeyhex))
def pubhex2redeem_script(self,pubkeyhex:'sstr'): # new
"convert a hex pubkey to a Segwit P2SH-P2WPKH redeem script"
assert opt.type == 'segwit','This command is meaningful only for --type=segwit'
assert self.mmtype == 'segwit','This command is meaningful only for --type=segwit'
return self.proto.pubhex2redeem_script(pubkeyhex)
def redeem_script2addr(self,redeem_scripthex:'sstr'): # new
"convert a Segwit P2SH-P2WPKH redeem script to an address"
assert opt.type == 'segwit','This command is meaningful only for --type=segwit'
assert self.mmtype == 'segwit','This command is meaningful only for --type=segwit'
assert redeem_scripthex[:4] == '0014','{!r}: invalid redeem script'.format(redeem_scripthex)
assert len(redeem_scripthex) == 44,'{} bytes: invalid redeem script length'.format(len(redeem_scripthex)//2)
return self.pubhash2addr(hash160(redeem_scripthex))
def pubhash2addr(self,pubhashhex:'sstr'):
"convert public key hash to address"
if opt.type == 'bech32':
if self.mmtype == 'bech32':
return self.proto.pubhash2bech32addr(pubhashhex)
else:
self.init_generators('at')
@ -1108,7 +1109,7 @@ class MMGenToolCmdMonero(MMGenToolCmds):
return True
async def process_wallets(op):
opt.accept_defaults = opt.accept_defaults or op.accept_defaults
g.accept_defaults = g.accept_defaults or op.accept_defaults
from .protocol import init_proto
proto = init_proto('xmr',network='mainnet')
from .addr import AddrList
@ -1223,8 +1224,7 @@ class tool_api(
super().__init__()
if not hasattr(opt,'version'):
opts.init()
opt.use_old_ed25519 = None
opt.type = None
self.mmtype = self.proto.dfl_mmtype
def init_coin(self,coinsym,network):
"""
@ -1277,11 +1277,11 @@ class tool_api(
@property
def addrtype(self):
"""The currently configured address type (is assignable)"""
return opt.type
return self.mmtype
@addrtype.setter
def addrtype(self,val):
opt.type = val
self.mmtype = val
@property
def usr_randchars(self):

View file

@ -801,7 +801,7 @@ class TrackingWallet(MMGenObject,metaclass=aInitMeta):
def get_cached_balance(self,addr,session_cache,data_root):
if addr in session_cache:
return self.proto.coin_amt(session_cache[addr])
if not g.use_cached_balances:
if not g.cached_balances:
return None
if addr in data_root and 'balance' in data_root[addr]:
return self.proto.coin_amt(data_root[addr]['balance'])

View file

@ -505,27 +505,6 @@ def get_seed_file(cmd_args,nargs,invoked_as=None):
return cmd_args[0] if cmd_args else (wf,None)[wd_from_opt]
def get_new_passphrase(desc,passchg=False):
w = '{}passphrase for {}'.format(('','new ')[bool(passchg)], desc)
if opt.passwd_file:
pw = ' '.join(get_words_from_file(opt.passwd_file,w))
elif opt.echo_passphrase:
pw = ' '.join(get_words_from_user('Enter {}: '.format(w)))
else:
for i in range(g.passwd_max_tries):
pw = ' '.join(get_words_from_user('Enter {}: '.format(w)))
pw2 = ' '.join(get_words_from_user('Repeat passphrase: '))
dmsg('Passphrases: [{}] [{}]'.format(pw,pw2))
if pw == pw2:
vmsg('Passphrases match'); break
else: msg('Passphrases do not match. Try again.')
else:
die(2,'User failed to duplicate passphrase in {} attempts'.format(g.passwd_max_tries))
if pw == '': qmsg('WARNING: Empty passphrase')
return pw
def confirm_or_raise(message,q,expect='YES',exit_msg='Exiting at user request'):
m = message.strip()
if m: msg(m)
@ -701,20 +680,14 @@ def get_data_from_file(infile,desc='data',dash=False,silent=False,binary=False,q
return data
def pwfile_reuse_warning():
if 'passwd_file_used' in globals():
qmsg("Reusing passphrase from file '{}' at user request".format(opt.passwd_file))
return True
globals()['passwd_file_used'] = True
return False
passwd_files_used = {}
def get_mmgen_passphrase(desc,passchg=False):
prompt ='Enter {}passphrase for {}: '.format(('','old ')[bool(passchg)],desc)
if opt.passwd_file:
pwfile_reuse_warning()
return ' '.join(get_words_from_file(opt.passwd_file,'passphrase'))
else:
return ' '.join(get_words_from_user(prompt))
def pwfile_reuse_warning(passwd_file):
if passwd_file in passwd_files_used:
qmsg(f'Reusing passphrase from file {passwd_file!r} at user request')
return True
passwd_files_used[passwd_file] = True
return False
def my_raw_input(prompt,echo=True,insert_txt='',use_readline=True):
@ -760,7 +733,7 @@ def keypress_confirm(prompt,default_yes=False,verbose=False,no_nl=False,complete
p = prompt if complete_prompt else '{} {}: '.format(prompt,q)
nl = ('\n','\r{}\r'.format(' '*len(p)))[no_nl]
if opt.accept_defaults:
if g.accept_defaults:
msg(p)
return default_yes

View file

@ -29,11 +29,11 @@ from .baseconv import *
from .seed import Seed
def check_usr_seed_len(seed_len):
if opt.seed_len != seed_len and 'seed_len' in opt.set_by_user:
if opt.seed_len and opt.seed_len != seed_len:
die(1,f"ERROR: requested seed length ({opt.seed_len}) doesn't match seed length of source ({seed_len})")
def _is_mnemonic(s,fmt):
oq_save = opt.quiet
oq_save = bool(opt.quiet)
opt.quiet = True
try:
Wallet(in_data=s,in_fmt=fmt)
@ -311,7 +311,7 @@ an empty passphrase, just hit ENTER twice.
if opt.keep_hash_preset:
qmsg(f'Reusing hash preset {old_hp!r} at user request')
self.ssdata.hash_preset = old_hp
elif 'hash_preset' in opt.set_by_user:
elif opt.hash_preset:
hp = self.ssdata.hash_preset = opt.hash_preset
qmsg(f'Using hash preset {opt.hash_preset!r} requested on command line')
else: # Prompt, using old value as default
@ -320,11 +320,11 @@ an empty passphrase, just hit ENTER twice.
if (not opt.keep_hash_preset) and self.op == 'pwchg_new':
m = (f'changed to {hp!r}','unchanged')[hp==old_hp]
qmsg(f'Hash preset {m}')
elif 'hash_preset' in opt.set_by_user:
elif opt.hash_preset:
self.ssdata.hash_preset = opt.hash_preset
qmsg(f'Using hash preset {opt.hash_preset!r} requested on command line')
else:
self._get_hash_preset_from_user(opt.hash_preset,desc_suf)
self._get_hash_preset_from_user(g.dfl_hash_preset,desc_suf)
def _get_new_passphrase(self):
desc = '{}passphrase for {}{}'.format(
@ -816,10 +816,8 @@ class MMGenWallet(WalletEnc):
d.hash_preset = hp = hpdata[0][:-1] # a string!
qmsg("Hash preset of wallet: '{}'".format(hp))
if 'hash_preset' in opt.set_by_user:
uhp = opt.hash_preset
if uhp != hp:
qmsg("Warning: ignoring user-requested hash preset '{}'".format(uhp))
if opt.hash_preset and opt.hash_preset != hp:
qmsg('Warning: ignoring user-requested hash preset {opt.hash_preset}')
hash_params = list(map(int,hpdata[1:]))
@ -899,11 +897,11 @@ class Brainwallet(WalletEnc):
"""
bw_seed_len,d.hash_preset = self.get_bw_params()
else:
if 'seed_len' not in opt.set_by_user:
qmsg(f'Using default seed length of {yellow(str(opt.seed_len))} bits\n'
if not opt.seed_len:
qmsg(f'Using default seed length of {yellow(str(g.dfl_seed_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
bw_seed_len = opt.seed_len or g.dfl_seed_len
qmsg_r('Hashing brainwallet data. Please wait...')
# Use buflen arg of scrypt.hash() to get seed of desired length
seed = scrypt_hash_passphrase(
@ -954,7 +952,7 @@ to exit and re-run the program with the '--old-incog-fmt' option.
def _incog_data_size_chk(self):
# valid sizes: 56, 64, 72
dlen = len(self.fmt_data)
seed_len = opt.seed_len
seed_len = opt.seed_len or g.dfl_seed_len
valid_dlen = self._get_incog_data_len(seed_len)
if dlen == valid_dlen:
return True
@ -1139,7 +1137,7 @@ harder to find, you're advised to choose a much larger file size than this.
qmsg("Getting hidden incog data from file '{}'".format(self.infile.name))
# Already sanity-checked:
d.target_data_len = self._get_incog_data_len(opt.seed_len)
d.target_data_len = self._get_incog_data_len(opt.seed_len or g.dfl_seed_len)
self._check_valid_offset(self.infile,'read')
flgs = os.O_RDONLY|os.O_BINARY if g.platform == 'win' else os.O_RDONLY

View file

@ -100,6 +100,7 @@ setup(
'mmgen.addr',
'mmgen.altcoin',
'mmgen.baseconv',
'mmgen.base_obj',
'mmgen.bech32',
'mmgen.bip39',
'mmgen.cfg',

View file

@ -93,11 +93,6 @@ sample_text_hexdump = (
'000040: 6261 6e6b 73').format(n=NL)
kafile_opts = ['-p1','-Ptest/ref/keyaddrfile_password']
kafile_code = (
"\nopt.hash_preset = '1'" +
"\nopt.set_by_user = ['hash_preset']" +
"\nopt.use_old_ed25519 = None" +
"\nopt.passwd_file = 'test/ref/keyaddrfile_password'" )
from test.unit_tests_d.ut_bip39 import unit_test as bip39
tests = {
@ -418,11 +413,11 @@ tests = {
'pubhash2addr': {
'btc_mainnet': [
( ['118089d66b4a5853765e94923abdd5de4616c6e5'], '12bYUGXS8SRArZneQDN9YEEYAtEa59Rykm',
None, 'opt.type="legacy"' ),
None, 'legacy' ),
( ['8e34586186551f6320fa3eb2d238a9c61ab8264b'], '3Eevao3DRVXnYym3tdrJDqS3Wc39PQzahn',
['--type=segwit'], 'opt.type="segwit"' ),
['--type=segwit'], 'segwit' ),
( ['3057f66ddd26fa6ef826b0d5ca067ec3e8f3c178'], 'bc1qxptlvmwaymaxa7pxkr2u5pn7c0508stcncv7ms',
['--type=bech32'], 'opt.type="bech32"' ),
['--type=bech32'], 'bech32' ),
],
},
'addr2scriptpubkey': {
@ -443,32 +438,32 @@ tests = {
'btc_mainnet': [
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'5HwzecKMWD82ppJK3qMKpC7ohXXAwcyAN5VgdJ9PLFaAzpBG4sX',
None, 'opt.type="legacy"' ),
None, 'legacy' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm',
['--type=compressed'], 'opt.type="compressed"' ),
['--type=compressed'], 'compressed' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm',
['--type=segwit'], 'opt.type="segwit"' ),
['--type=segwit'], 'segwit' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm',
['--type=bech32'], 'opt.type="bech32"' ),
['--type=bech32'], 'bech32' ),
],
},
'privhex2addr': {
'btc_mainnet': [
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'1C5VPtgq9xQ6AcTgMAR3J6GDrs72HC4pS1',
None, 'opt.type="legacy"' ),
None, 'legacy' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'1Kz9fVSUMshzPejpzW9D95kScgA3rY6QxF',
['--type=compressed'], 'opt.type="compressed"' ),
['--type=compressed'], 'compressed' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'3AhjTiWHhVJAi1s5CfKMcLzYps12x3gZhg',
['--type=segwit'], 'opt.type="segwit"' ),
['--type=segwit'], 'segwit' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'bc1q6pqnfwwakuuejpm9w52ds342f9d5u36v0qnz7c',
['--type=bech32'], 'opt.type="bech32"' ),
['--type=bech32'], 'bech32' ),
],
'eth_mainnet': [
( ['0000000000000000000000000000000000000000000000000000000000000001'],
@ -501,68 +496,68 @@ tests = {
'zec_mainnet': [
( ['0000000000000000000000000000000000000000000000000000000000000001'],
'zceQDpyNwek7dKqF5ZuFGj7YrNVxh7X1aPkrVxDLVxWSiZAFDEuy5C7XNV8VhyZ3ghTPQ61xjCGiyLT3wqpiN1Yi6mdmaCq',
['--type=zcash_z'], 'opt.type="zcash_z"' ),
['--type=zcash_z'], 'zcash_z' ),
( ['ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'],
'zcY1hqJ3P5ifjnWk1BcXpjrLG5XeJZUSPCiiVTF9LXrejxBzAsFWcNyr6PudwQHm8DnQpD8HEaM3dh8sB6cf91ciAa53YQ1',
['--type=zcash_z'], 'opt.type="zcash_z"' ),
['--type=zcash_z'], 'zcash_z' ),
( ['0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'],
'zcY1hqJ3P5ifjnWk1BcXpjrLG5XeJZUSPCiiVTF9LXrejxBzAsFWcNyr6PudwQHm8DnQpD8HEaM3dh8sB6cf91ciAa53YQ1',
['--type=zcash_z'], 'opt.type="zcash_z"' ),
['--type=zcash_z'], 'zcash_z' ),
( ['00000000000000000000000000000000000000000000000000000000000000ff'],
'zcck12KgVY34LJwVEDLN8sXhL787zmjKqPsP1uBYRHs75bL9sQu4P7wcc5ZJTjKsL376zaSpsYqGxK94JbiYcNoH8DkeGbN',
['--type=zcash_z'], 'opt.type="zcash_z"' ),
['--type=zcash_z'], 'zcash_z' ),
( ['ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0f'],
'zcJ9hEezG1Jeye5dciqiMDh6SXtYbUsircGmpVyhHWyzyxDVRRDs5Q8M7hG3c7nDcvd5Pw4u4wV9RAQmq5RCBZq5wVyMQV8',
['--type=zcash_z'], 'opt.type="zcash_z"' ),
['--type=zcash_z'], 'zcash_z' ),
( ['deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'],
'zchFELwBxqsAubsLQ8yZgPCDDGukjXJssgCbiTPwFNmFwn9haLnDatzfhLdZzJT4PcU4o2yr92B52UFirUzEdF6ZYM2gBkM',
['--type=zcash_z'], 'opt.type="zcash_z"' ),
['--type=zcash_z'], 'zcash_z' ),
],
},
'privhex2pubhex': {
'btc_mainnet': [
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'044281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e972757f3254c322eeaa3cb6bf97cc5ecf8d4387b0df2c0b1e6ee18fe3a6977a7d57a',
None, 'opt.type="legacy"' ),
None, 'legacy' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'024281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e9727',
['--type=compressed'], 'opt.type="compressed"' ),
['--type=compressed'], 'compressed' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'024281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e9727',
['--type=segwit'], 'opt.type="segwit"' ),
['--type=segwit'], 'segwit' ),
( ['118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492'],
'024281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e9727',
['--type=bech32'], 'opt.type="bech32"' ),
['--type=bech32'], 'bech32' ),
],
},
'pubhex2addr': {
'btc_mainnet': [
( ['044281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e972757f3254c322eeaa3cb6bf97cc5ecf8d4387b0df2c0b1e6ee18fe3a6977a7d57a'],
'1C5VPtgq9xQ6AcTgMAR3J6GDrs72HC4pS1',
None, 'opt.type="legacy"' ),
None, 'legacy' ),
( ['024281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e9727'],
'1Kz9fVSUMshzPejpzW9D95kScgA3rY6QxF',
['--type=compressed'], 'opt.type="compressed"' ),
['--type=compressed'], 'compressed' ),
( ['024281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e9727'],
'3AhjTiWHhVJAi1s5CfKMcLzYps12x3gZhg',
['--type=segwit'], 'opt.type="segwit"' ),
['--type=segwit'], 'segwit' ),
( ['024281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e9727'],
'bc1q6pqnfwwakuuejpm9w52ds342f9d5u36v0qnz7c',
['--type=bech32'], 'opt.type="bech32"' ),
['--type=bech32'], 'bech32' ),
],
},
'pubhex2redeem_script': {
'btc_mainnet': [
( ['024281a85c9ce87279e028410b851410d65136304cfbbbeaaa8e2e3931cf4e9727'],
'0014d04134b9ddb7399907657514d846aa495b4e474c',
['--type=segwit'], 'opt.type="segwit"' ),
['--type=segwit'], 'segwit' ),
],
},
'redeem_script2addr': {
'btc_mainnet': [
( ['0014d04134b9ddb7399907657514d846aa495b4e474c'],
'3AhjTiWHhVJAi1s5CfKMcLzYps12x3gZhg',
['--type=segwit'], 'opt.type="segwit"' ),
['--type=segwit'], 'segwit' ),
],
},
'randpair': {
@ -576,13 +571,13 @@ tests = {
'wif2addr': {
'btc_mainnet': [
( ['5HwzecKMWD82ppJK3qMKpC7ohXXAwcyAN5VgdJ9PLFaAzpBG4sX'],
'1C5VPtgq9xQ6AcTgMAR3J6GDrs72HC4pS1', ['--type=legacy'], 'opt.type="legacy"' ),
'1C5VPtgq9xQ6AcTgMAR3J6GDrs72HC4pS1', ['--type=legacy'], 'legacy' ),
( ['KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm'],
'1Kz9fVSUMshzPejpzW9D95kScgA3rY6QxF', ['--type=compressed'], 'opt.type="compressed"' ),
'1Kz9fVSUMshzPejpzW9D95kScgA3rY6QxF', ['--type=compressed'], 'compressed' ),
( ['KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm'],
'3AhjTiWHhVJAi1s5CfKMcLzYps12x3gZhg', ['--type=segwit'], 'opt.type="segwit"' ),
'3AhjTiWHhVJAi1s5CfKMcLzYps12x3gZhg', ['--type=segwit'], 'segwit' ),
( ['KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm'],
'bc1q6pqnfwwakuuejpm9w52ds342f9d5u36v0qnz7c', ['--type=bech32'], 'opt.type="bech32"' ),
'bc1q6pqnfwwakuuejpm9w52ds342f9d5u36v0qnz7c', ['--type=bech32'], 'bech32' ),
],
'eth_mainnet': [
( ['0000000000000000000000000000000000000000000000000000000000000001'],
@ -615,52 +610,52 @@ tests = {
'zec_mainnet': [
( ['SKxny894fJe2rmZjeuoE6GVfNkWoXfPp8337VrLLNWG56FjqVUYR'],
'zceQDpyNwek7dKqF5ZuFGj7YrNVxh7X1aPkrVxDLVxWSiZAFDEuy5C7XNV8VhyZ3ghTPQ61xjCGiyLT3wqpiN1Yi6mdmaCq',
['--type=zcash_z'], 'opt.type="zcash_z"' ),
['--type=zcash_z'], 'zcash_z' ),
( ['SKxv1peuQvMT4TvqPLqKy1px3oqLm98Evi948VU8N8VKcf7C2umc'],
'zcY1hqJ3P5ifjnWk1BcXpjrLG5XeJZUSPCiiVTF9LXrejxBzAsFWcNyr6PudwQHm8DnQpD8HEaM3dh8sB6cf91ciAa53YQ1',
['--type=zcash_z'], 'opt.type="zcash_z"' ),
['--type=zcash_z'], 'zcash_z' ),
( ['SKxv1peuQvMT4TvqPLqKy1px3oqLm98Evi948VU8N8VKcf7C2umc'],
'zcY1hqJ3P5ifjnWk1BcXpjrLG5XeJZUSPCiiVTF9LXrejxBzAsFWcNyr6PudwQHm8DnQpD8HEaM3dh8sB6cf91ciAa53YQ1',
['--type=zcash_z'], 'opt.type="zcash_z"' ),
['--type=zcash_z'], 'zcash_z' ),
( ['SKxny894fJe2rmZjeuoE6GVfNkWoXfPp8337VrLLNWG56kQw4qjm'],
'zcck12KgVY34LJwVEDLN8sXhL787zmjKqPsP1uBYRHs75bL9sQu4P7wcc5ZJTjKsL376zaSpsYqGxK94JbiYcNoH8DkeGbN',
['--type=zcash_z'], 'opt.type="zcash_z"' ),
['--type=zcash_z'], 'zcash_z' ),
( ['SKxv1peuQvMT4TvqPLqKy1px3oqLm98Evi948VU8N8VKcBwrLwiu'],
'zcJ9hEezG1Jeye5dciqiMDh6SXtYbUsircGmpVyhHWyzyxDVRRDs5Q8M7hG3c7nDcvd5Pw4u4wV9RAQmq5RCBZq5wVyMQV8',
['--type=zcash_z'], 'opt.type="zcash_z"' ),
['--type=zcash_z'], 'zcash_z' ),
( ['SKxuS56e99jpCeD9mMQ5o63zoGPakNdM9HCvt4Vt2cypvRjCdvGJ'],
'zchFELwBxqsAubsLQ8yZgPCDDGukjXJssgCbiTPwFNmFwn9haLnDatzfhLdZzJT4PcU4o2yr92B52UFirUzEdF6ZYM2gBkM',
['--type=zcash_z'], 'opt.type="zcash_z"' ),
['--type=zcash_z'], 'zcash_z' ),
],
},
'wif2hex': {
'btc_mainnet': [
( ['5HwzecKMWD82ppJK3qMKpC7ohXXAwcyAN5VgdJ9PLFaAzpBG4sX'],
'118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492',
None, 'opt.type="legacy"' ),
None, 'legacy' ),
( ['KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm'],
'118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492',
['--type=compressed'], 'opt.type="compressed"' ),
['--type=compressed'], 'compressed' ),
( ['KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm'],
'118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492',
['--type=segwit'], 'opt.type="segwit"' ),
['--type=segwit'], 'segwit' ),
( ['KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm'],
'118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492',
['--type=bech32'], 'opt.type="bech32"' ),
['--type=bech32'], 'bech32' ),
],
},
'wif2redeem_script': {
'btc_mainnet': [
( ['KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm'],
'0014d04134b9ddb7399907657514d846aa495b4e474c',
['--type=segwit'], 'opt.type="segwit"' ),
['--type=segwit'], 'segwit' ),
],
},
'wif2segwit_pair': {
'btc_mainnet': [
( ['KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm'],
('0014d04134b9ddb7399907657514d846aa495b4e474c','3AhjTiWHhVJAi1s5CfKMcLzYps12x3gZhg'),
['--type=segwit'], 'opt.type="segwit"' ),
['--type=segwit'], 'segwit' ),
],
},
},
@ -720,43 +715,43 @@ tests = {
'keyaddrfile_chksum': {
'btc_mainnet': [
( ['test/ref/98831F3A[1,31-33,500-501,1010-1011].akeys.mmenc'],
'9F2D D781 1812 8BAD', kafile_opts, kafile_code ),
'9F2D D781 1812 8BAD', kafile_opts ),
],
'btc_testnet': [
( ['test/ref/98831F3A[1,31-33,500-501,1010-1011].testnet.akeys.mmenc'],
'88CC 5120 9A91 22C2', kafile_opts, kafile_code ),
'88CC 5120 9A91 22C2', kafile_opts ),
],
'ltc_mainnet': [
( ['test/ref/litecoin/98831F3A-LTC[1,31-33,500-501,1010-1011].akeys.mmenc'],
'B804 978A 8796 3ED4', kafile_opts, kafile_code ),
'B804 978A 8796 3ED4', kafile_opts ),
],
'ltc_testnet': [
( ['test/ref/litecoin/98831F3A-LTC[1,31-33,500-501,1010-1011].testnet.akeys.mmenc'],
'98B5 AC35 F334 0398', kafile_opts, kafile_code ),
'98B5 AC35 F334 0398', kafile_opts ),
],
'zec_mainnet': [
( ['test/ref/zcash/98831F3A-ZEC-C[1,31-33,500-501,1010-1011].akeys.mmenc'],
'F05A 5A5C 0C8E 2617', kafile_opts, kafile_code ),
'F05A 5A5C 0C8E 2617', kafile_opts ),
( ['test/ref/zcash/98831F3A-ZEC-Z[1,31-33,500-501,1010-1011].akeys.mmenc'], '6B87 9B2D 0D8D 8D1E',
kafile_opts + ['--type=zcash_z'], kafile_code + '\nopt.type = "zcash_z"' ),
kafile_opts + ['--type=zcash_z'], 'opt.type = "zcash_z"' ),
],
'xmr_mainnet': [
( ['test/ref/monero/98831F3A-XMR-M[1,31-33,500-501,1010-1011].akeys.mmenc'],
'E0D7 9612 3D67 404A', kafile_opts, kafile_code ), ],
'E0D7 9612 3D67 404A', kafile_opts ), ],
'dash_mainnet': [
( ['test/ref/dash/98831F3A-DASH-C[1,31-33,500-501,1010-1011].akeys.mmenc'],
'E83D 2C63 FEA2 4142', kafile_opts, kafile_code ), ],
'E83D 2C63 FEA2 4142', kafile_opts ), ],
'eth_mainnet': [
( ['test/ref/ethereum/98831F3A-ETH[1,31-33,500-501,1010-1011].akeys.mmenc'],
'E400 70D9 0AE3 C7C2', kafile_opts, kafile_code ), ],
'E400 70D9 0AE3 C7C2', kafile_opts ), ],
'etc_mainnet': [
( ['test/ref/ethereum_classic/98831F3A-ETC[1,31-33,500-501,1010-1011].akeys.mmenc'],
'EF49 967D BD6C FE45', kafile_opts, kafile_code ), ],
'EF49 967D BD6C FE45', kafile_opts ), ],
},
'passwdfile_chksum': {
'btc_mainnet': [
( ['test/ref/98831F3A-фубар@crypto.org-b58-20[1,4,1100].pws'],
'DDD9 44B0 CA28 183F', kafile_opts, kafile_code ), ],
'DDD9 44B0 CA28 183F', kafile_opts ), ],
},
'txview': {
'btc_mainnet': [ ( ['test/ref/0B8D5A[15.31789,14,tl=1320969600].rawtx'], None ), ],
@ -801,7 +796,7 @@ async def run_test(gid,cmd_name):
msg_r(green(m)+'\n' if opt.verbose else m)
def fork_cmd(cmd_name,args,out,opts,exec_code):
def fork_cmd(cmd_name,args,out,opts):
cmd = list(tool_cmd) + (opts or []) + [cmd_name] + args
vmsg('{} {}'.format(green('Executing'),cyan(' '.join(cmd))))
cp = run(cmd,input=stdin_input or None,stdout=PIPE,stderr=PIPE)
@ -819,13 +814,16 @@ async def run_test(gid,cmd_name):
return cmd_out.strip()
async def run_func(cmd_name,args,out,opts,exec_code):
async def run_func(cmd_name,args,out,opts,mmtype):
vmsg('{}: {}{}'.format(purple('Running'),
' '.join([cmd_name]+[repr(e) for e in args]),
' '+exec_code if exec_code else '' ))
if exec_code: exec(exec_code)
' '+mmtype if mmtype else '' ))
aargs,kwargs = tool._process_args(cmd_name,args)
oq_save = opt.quiet
tm = tool.MMGenToolCmdMeta
cls_name = tm.classname(tm,cmd_name)
tobj = getattr(tool,cls_name)(mmtype=mmtype)
method = getattr(tobj,cmd_name)
oq_save = bool(opt.quiet)
if not opt.verbose:
opt.quiet = True
if stdin_input:
@ -834,7 +832,7 @@ async def run_test(gid,cmd_name):
os.close(fd1)
stdin_save = os.dup(0)
os.dup2(fd0,0)
cmd_out = tc.call(cmd_name,*aargs,**kwargs)
cmd_out = method(*aargs,**kwargs)
os.dup2(stdin_save,0)
os.wait()
opt.quiet = oq_save
@ -845,13 +843,13 @@ async def run_test(gid,cmd_name):
vmsg('Input: {!r}'.format(stdin_input))
sys.exit(0)
else:
ret = tc.call(cmd_name,*aargs,**kwargs)
ret = method(*aargs,**kwargs)
if type(ret).__name__ == 'coroutine':
ret = await ret
opt.quiet = oq_save
return ret
def tool_api(cmd_name,args,out,opts,exec_code):
def tool_api(cmd_name,args,out,opts):
from mmgen.tool import tool_api
tool = tool_api()
if opts:
@ -868,7 +866,7 @@ async def run_test(gid,cmd_name):
return getattr(tool,cmd_name)(*pargs,**kwargs)
for d in data:
args,out,opts,exec_code = d + tuple([None] * (4-len(d)))
args,out,opts,mmtype = d + tuple([None] * (4-len(d)))
stdin_input = None
if args and type(args[0]) == bytes:
stdin_input = args[0]
@ -877,14 +875,14 @@ async def run_test(gid,cmd_name):
if opt.tool_api:
if args and args[0 ]== '-':
continue
cmd_out = tool_api(cmd_name,args,out,opts,exec_code)
cmd_out = tool_api(cmd_name,args,out,opts)
elif opt.fork:
cmd_out = fork_cmd(cmd_name,args,out,opts,exec_code)
cmd_out = fork_cmd(cmd_name,args,out,opts)
else:
if stdin_input and g.platform == 'win':
msg('Skipping for MSWin - no os.fork()')
continue
cmd_out = await run_func(cmd_name,args,out,opts,exec_code)
cmd_out = await run_func(cmd_name,args,out,opts,mmtype)
try: vmsg('Output:\n{}\n'.format(cmd_out))
except: vmsg('Output:\n{}\n'.format(repr(cmd_out)))
@ -967,7 +965,14 @@ def list_tested_cmds():
sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]
cmd_args = opts.init(opts_data,add_opts=['use_old_ed25519'])
cmd_args = opts.init(
opts_data,
add_opts = ['use_old_ed25519'],
init_opts = {
'usr_randchars': 0,
'hash_preset': '1',
'passwd_file': 'test/ref/keyaddrfile_password',
})
from mmgen.protocol import init_proto_from_opts
proto = init_proto_from_opts()
@ -1014,8 +1019,6 @@ if opt.fork:
tool_cmd = ('python3','-m','trace','--count','--coverdir='+d,'--file='+f) + tool_cmd
elif g.platform == 'win':
tool_cmd = ('python3',) + tool_cmd
else:
opt.usr_randchars = 0
start_time = int(time.time())

103
test/unit_tests_d/ut_lockable.py Executable file
View file

@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""
test/unit_tests_d/ut_lockable.py: unit test for the MMGen suite's Lockable class
"""
from mmgen.common import *
from mmgen.exception import *
class unit_test(object):
def run_test(self,name,ut):
from mmgen.base_obj import AttrCtrl,Lockable
qmsg_r('Testing class AttrCtrl...')
class MyAttrCtrl(AttrCtrl):
foo = 'fooval'
ac = MyAttrCtrl()
ac.lock()
ac.foo = 'new fooval'
ac.foo = 'new fooval2'
class MyAttrCtrlClsCheck(AttrCtrl):
_use_class_attr = True
foo = 'fooval'
bar = None
acc = MyAttrCtrlClsCheck()
acc.lock()
acc.foo = 'new_fooval'
acc.foo = 'new_fooval2'
acc.bar = 'bar val'
acc.bar = 1 # class attribute bar is None, so can be set to any type
qmsg('OK')
qmsg_r('Testing class Lockable...')
class MyLockable(Lockable): # class has no attrs, like UserOpts
_set_ok = ('foo','baz')
_reset_ok = ('bar','baz')
lc = MyLockable()
lc.foo = None
lc.bar = 'barval'
lc.baz = 1
lc.qux = 1
lc.lock()
lc.foo = 'fooval2'
lc.bar = 'barval2'
lc.bar = 'barval3'
lc.baz = 2
lc.baz = 3
class MyLockableClsCheck(Lockable): # class has attrs, like GlobalContext
_use_class_attr = True
_set_ok = ('foo','baz')
_reset_ok = ('bar','baz')
foo = None
bar = 1
baz = 3.5
qux = 'quxval'
lcc = MyLockableClsCheck()
lcc.lock()
lcc.foo = 'fooval2' # class attribute foo is None, so can be set to any type
lcc.bar = 2
lcc.bar = 3 # bar is in reset list
lcc.baz = 3.2
lcc.baz = 3.1 # baz is in both lists
qmsg('OK')
qmsg('Checking error handling:')
def bad1(): ac.x = 1
def bad2(): acc.foo = 1
def bad3(): lc.foo = 'fooval3'
def bad4(): lc.baz = 'str'
def bad5(): lcc.bar = 'str'
def bad6(): lc.qux = 2
def bad7(): lcc.qux = 'quxval2'
def bad8(): lcc.foo = 'fooval3'
def bad9(): lc.x = 1
def bad10(): lcc.x = 1
ut.process_bad_data((
('attr (1)', 'AttributeError', 'has no attr', bad1 ),
('attr (2)', 'AttributeError', 'has no attr', bad9 ),
('attr (3)', 'AttributeError', 'has no attr', bad10 ),
('attr type (1)', 'AttributeError', 'type', bad2 ),
("attr type (2)", 'AttributeError', 'type', bad4 ),
("attr type (3)", 'AttributeError', 'type', bad5 ),
("attr (can't set)", 'AttributeError', 'read-only', bad6 ),
("attr (can't set)", 'AttributeError', 'read-only', bad7 ),
("attr (can't reset)", 'AttributeError', 'reset', bad3 ),
("attr (can't reset)", 'AttributeError', 'reset', bad8 ),
))
qmsg('OK')
return True

View file

@ -149,7 +149,6 @@ class unit_test(object):
seed = Seed(seed_bin)
ssm_save = SeedShareIdx.max_val
ssm = SeedShareIdx.max_val = 2048
g.debug_last_share_sid_len = 3
shares = SeedShareList(seed,count=ssm,id_str='foo',master_idx=1,debug_last_share=True)
lsid = shares.last_share.sid
collisions = shares.data['long'][lsid][1]