diff --git a/mmgen/altcoins/eth/tw.py b/mmgen/altcoins/eth/tw.py index 9c791080..7e05451f 100755 --- a/mmgen/altcoins/eth/tw.py +++ b/mmgen/altcoins/eth/tw.py @@ -244,7 +244,7 @@ class EthereumTwUnspentOutputs(TwUnspentOutputs): '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) diff --git a/mmgen/base_obj.py b/mmgen/base_obj.py new file mode 100755 index 00000000..edf401b1 --- /dev/null +++ b/mmgen/base_obj.py @@ -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 +# +# 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 . + +""" +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) diff --git a/mmgen/crypto.py b/mmgen/crypto.py index cd965d56..52d6bb90 100755 --- a/mmgen/crypto.py +++ b/mmgen/crypto.py @@ -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) diff --git a/mmgen/globalvars.py b/mmgen/globalvars.py index b6804aef..3e733d16 100755 --- a/mmgen/globalvars.py +++ b/mmgen/globalvars.py @@ -25,12 +25,14 @@ 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: diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py index afa9120b..9d606edd 100755 --- a/mmgen/main_addrgen.py +++ b/mmgen/main_addrgen.py @@ -65,9 +65,9 @@ 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 diff --git a/mmgen/main_autosign.py b/mmgen/main_autosign.py index 21c774f2..88ca17e0 100755 --- a/mmgen/main_autosign.py +++ b/mmgen/main_autosign.py @@ -31,6 +31,8 @@ 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 @@ } } -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 @@ 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) diff --git a/mmgen/main_passgen.py b/mmgen/main_passgen.py index 0428b95e..82db66b4 100755 --- a/mmgen/main_passgen.py +++ b/mmgen/main_passgen.py @@ -54,9 +54,9 @@ 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 diff --git a/mmgen/main_seedjoin.py b/mmgen/main_seedjoin.py index 1070093f..f25e01ae 100755 --- a/mmgen/main_seedjoin.py +++ b/mmgen/main_seedjoin.py @@ -48,7 +48,7 @@ -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 @@ def print_shares_info(): 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') diff --git a/mmgen/main_tool.py b/mmgen/main_tool.py index 6aa925d5..9f4475ca 100755 --- a/mmgen/main_tool.py +++ b/mmgen/main_tool.py @@ -63,7 +63,7 @@ def make_help(): --, --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 @@ def make_help(): 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() diff --git a/mmgen/main_txbump.py b/mmgen/main_txbump.py index fdc28b13..06503f3e 100755 --- a/mmgen/main_txbump.py +++ b/mmgen/main_txbump.py @@ -49,7 +49,7 @@ -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 @@ 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 diff --git a/mmgen/main_txcreate.py b/mmgen/main_txcreate.py index 2addeb63..0150b069 100755 --- a/mmgen/main_txcreate.py +++ b/mmgen/main_txcreate.py @@ -76,8 +76,6 @@ cmd_args = opts.init(opts_data) -g.use_cached_balances = opt.cached_balances - async def main(): from .protocol import init_proto_from_opts diff --git a/mmgen/main_txdo.py b/mmgen/main_txdo.py index e636b869..5226fc5d 100755 --- a/mmgen/main_txdo.py +++ b/mmgen/main_txdo.py @@ -56,7 +56,7 @@ 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 @@ 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 @@ cmd_args = opts.init(opts_data) -g.use_cached_balances = opt.cached_balances - from .tx import * from .txsign import * diff --git a/mmgen/main_txsign.py b/mmgen/main_txsign.py index f394646d..a5a91d35 100755 --- a/mmgen/main_txsign.py +++ b/mmgen/main_txsign.py @@ -45,9 +45,9 @@ -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 diff --git a/mmgen/main_wallet.py b/mmgen/main_wallet.py index 1f2186a2..24665243 100755 --- a/mmgen/main_wallet.py +++ b/mmgen/main_wallet.py @@ -101,12 +101,12 @@ -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 @@ 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()) diff --git a/mmgen/opts.py b/mmgen/opts.py index b5ed4297..998762da 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -21,13 +21,17 @@ """ 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 @@ def do_fmt(set_data): } } -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 chk_locktime(key,val,desc): 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): diff --git a/mmgen/protocol.py b/mmgen/protocol.py index d9518355..52cb962b 100755 --- a/mmgen/protocol.py +++ b/mmgen/protocol.py @@ -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, diff --git a/mmgen/seed.py b/mmgen/seed.py index c20f661c..2c70d38d 100755 --- a/mmgen/seed.py +++ b/mmgen/seed.py @@ -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 @@ def fn_stem(self): 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 @@ def _collision_debug_msg(self,sid,idx,nonce,nonce_desc='nonce',debug_last_share= 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 @@ def make_master_share(): 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] diff --git a/mmgen/tool.py b/mmgen/tool.py index 4b2ca9ab..43529811 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -269,9 +269,10 @@ def user_commands(cls): 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 @@ def init_generators(self,arg=None): 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 @@ def wif2addr(self,wifkey:'sstr'): 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 @@ def wif2redeem_script(self,wifkey:'sstr'): # new 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 @@ def privhex2pubhex(self,privhex:'sstr'): # new 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 @@ async def sync(n,d,fn,c,m): 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 @@ def __init__(self): 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 @@ def print_addrtypes(self): @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): diff --git a/mmgen/tw.py b/mmgen/tw.py index e780e2ed..2f35e0f0 100755 --- a/mmgen/tw.py +++ b/mmgen/tw.py @@ -801,7 +801,7 @@ def cache_balance(self,addr,bal,session_cache,data_root,force=False): 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']) diff --git a/mmgen/util.py b/mmgen/util.py index 3c0164b6..be4ce2c0 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -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,21 +680,15 @@ 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)) +passwd_files_used = {} + +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 - globals()['passwd_file_used'] = True + passwd_files_used[passwd_file] = True return False -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 my_raw_input(prompt,echo=True,insert_txt='',use_readline=True): try: import readline @@ -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 diff --git a/mmgen/wallet.py b/mmgen/wallet.py index 1f28cf92..6738d9b4 100755 --- a/mmgen/wallet.py +++ b/mmgen/wallet.py @@ -29,11 +29,11 @@ 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 @@ def _get_hash_preset(self,desc_suf=''): 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 @@ def _get_hash_preset(self,desc_suf=''): 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 @@ def check_master_chksum(lines,desc): 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 @@ def _decrypt(self): """ 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 @@ def _get_incog_data_len(self,seed_len): 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 @@ def _get_data(self): 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 diff --git a/setup.py b/setup.py index f5529a1a..5cbb51bf 100755 --- a/setup.py +++ b/setup.py @@ -100,6 +100,7 @@ def run(self): 'mmgen.addr', 'mmgen.altcoin', 'mmgen.baseconv', + 'mmgen.base_obj', 'mmgen.bech32', 'mmgen.bip39', 'mmgen.cfg', diff --git a/test/tooltest2.py b/test/tooltest2.py index 1cd0dbb2..9334d9ef 100755 --- a/test/tooltest2.py +++ b/test/tooltest2.py @@ -93,11 +93,6 @@ def md5_hash_strip(s): '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 @@ def md5_hash_strip(s): '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 @@ def md5_hash_strip(s): '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 @@ def md5_hash_strip(s): '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 @@ def md5_hash_strip(s): '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 @@ def md5_hash_strip(s): '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 @@ def md5_hash_strip(s): '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 @@ def fork_cmd(cmd_name,args,out,opts,exec_code): 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_func(cmd_name,args,out,opts,exec_code): 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_func(cmd_name,args,out,opts,exec_code): 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 @@ def tool_api(cmd_name,args,out,opts,exec_code): 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 @@ def tool_api(cmd_name,args,out,opts,exec_code): 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 @@ def list_tested_cmds(): 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()) diff --git a/test/unit_tests_d/ut_lockable.py b/test/unit_tests_d/ut_lockable.py new file mode 100755 index 00000000..a87c8114 --- /dev/null +++ b/test/unit_tests_d/ut_lockable.py @@ -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 diff --git a/test/unit_tests_d/ut_seedsplit.py b/test/unit_tests_d/ut_seedsplit.py index 4fc980c6..0b2ca0f8 100755 --- a/test/unit_tests_d/ut_seedsplit.py +++ b/test/unit_tests_d/ut_seedsplit.py @@ -149,7 +149,6 @@ def last_share_collisions(): 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]