Browse Source

release/testing: UTF8 testing fixes, other fixes and improvements

The MMGen Project 5 years ago
parent
commit
5fe92460ad

+ 16 - 40
README.md

@@ -1,10 +1,6 @@
-***Note: This is the source code repository of the MMGen wallet system.  For an
-easier way to install MMGen, check out the prebuilt bootable USB images on the
-[MMGenLive][8] home page.***
-
 # MMGen = Multi-Mode GENerator
 # MMGen = Multi-Mode GENerator
 
 
-##### a Bitcoin and altcoin online/offline software wallet for the command line
+##### An online/offline cryptocurrency wallet for the command line
 
 
 ### Description
 ### Description
 
 
@@ -15,7 +11,7 @@ offline computers to provide a robust solution for securely storing, tracking,
 sending and receiving your crypto assets.
 sending and receiving your crypto assets.
 
 
 The online computer is used for tracking balances and creating and sending
 The online computer is used for tracking balances and creating and sending
-transactions, while the offline computer (typically an air-gapped, low-power
+transactions, while the offline machine (typically an air-gapped, low-power
 device such as a Raspberry Pi) takes care of wallet creation, address generation
 device such as a Raspberry Pi) takes care of wallet creation, address generation
 and transaction signing.  All operations involving secret data are handled
 and transaction signing.  All operations involving secret data are handled
 offline: **your seed and private keys never come into contact with a
 offline: **your seed and private keys never come into contact with a
@@ -34,36 +30,11 @@ of address/key pairs from a single seed.  Your wallet never changes, so you need
 back it up only once.
 back it up only once.
 
 
 At the heart of the MMGen system is the seed, the “master key” providing access
 At the heart of the MMGen system is the seed, the “master key” providing access
-to all your crypto assets.  The seed can be stored in five different ways:
-
-  1. as a password-encrypted wallet.  The crack-resistant Scrypt hash function
-	 is used for password hashing.  Scrypt’s parameters can be tuned to make
-	 your wallet’s password very difficult to crack should it fall into the
-	 wrong hands.  The wallet is a compact, six-line text file suitable for
-	 printing or even writing out by hand;
-
-  2. as a seed file: a one-line, conveniently formatted base-58 representation
-	 of your unencrypted seed plus a checksum;
-
-  3. as an Electrum-like mnemonic seed phrase of 12, 18 or 24 words;
-
-  4. as a brainwallet passphrase (this option is recommended only for users who
-	 understand the risks of brainwallets and know how to create a strong
-	 brainwallet passphrase).  The brainwallet is hashed using Scrypt with
-	 tunable parameters, making it much harder to crack than standard SHA256
-	 brainwallets; or
-
-  5. as “incognito data”, a wallet encrypted to make it indistinguishable
-	 from random data.  This data can be hidden on a disk partition filled with
-	 random data, or in a file at an offset of your choice.  This makes it
-	 possible to hide a wallet at a non-private location—on cloud storage, for
-	 example.  Incognito wallet hiding/retrieval is seamlessly integrated into
-	 MMGen, making its use nearly as easy as that of the standard wallet.
-
-The best part is that all these methods can be combined.  If you forget your
-mnemonic seed phrase, for example, you can regenerate it from a stored wallet
-or seed file.  Correspondingly, a lost wallet can be regenerated from a mnemonic
-or seed or vice-versa.
+to all your crypto assets.  The seed can be stored in many different formats:
+as a password-encrypted wallet (the default), as a one-line base58 or
+hexidecimal seed file, as an Electrum-based mnemonic seed phrase, as a
+brainwallet passphrase, or as “incognito data” hideable within random data in a
+file or block device.  Conversion between all formats is supported.
 
 
 ***mmgen-txcreate running in a terminal window***
 ***mmgen-txcreate running in a terminal window***
 ![mmgen-txcreate running in a terminal window][9]
 ![mmgen-txcreate running in a terminal window][9]
@@ -94,7 +65,7 @@ the more prosaic 2048-word [BIP39 wordlist][bw] used in most wallets today.
   [Litecoin][bx], [Ethereum][E], Ethereum Classic and [ERC20 tokens][E].
   [Litecoin][bx], [Ethereum][E], Ethereum Classic and [ERC20 tokens][E].
 - **[Address generation support][ag]** for the above coins, plus [Monero][mx],
 - **[Address generation support][ag]** for the above coins, plus [Monero][mx],
   [Zcash][zx] (t and z addresses) and [144 Bitcoin-derived altcoins][ax].
   [Zcash][zx] (t and z addresses) and [144 Bitcoin-derived altcoins][ax].
-- **Support for all Bitcoin address types** including segwit-p2sh and bech32.
+- **Support for all Bitcoin address types** including Segwit-P2SH and Bech32.
 - **Independent key derivation for each address type:** No two addresses ever
 - **Independent key derivation for each address type:** No two addresses ever
   share the same private key.  Certain wallets in wide use today regrettably
   share the same private key.  Certain wallets in wide use today regrettably
   fail to guarantee this property, leading to the danger of inadvertent key
   fail to guarantee this property, leading to the danger of inadvertent key
@@ -118,11 +89,12 @@ the more prosaic 2048-word [BIP39 wordlist][bw] used in most wallets today.
   always be regenerated from their parent.
   always be regenerated from their parent.
 - **[Transaction autosigning][X]:** This feature puts your offline signing
 - **[Transaction autosigning][X]:** This feature puts your offline signing
   machine into “hands-off” mode, allowing you to transact directly from cold
   machine into “hands-off” mode, allowing you to transact directly from cold
-  storage securely and conveniently.  Additional LED signaling support is
+  storage securely and conveniently.  Additional LED blinking support is
   provided for Raspbian and Armbian platforms.
   provided for Raspbian and Armbian platforms.
 - **[Password generation][G]:** MMGen can be used to generate and manage your
 - **[Password generation][G]:** MMGen can be used to generate and manage your
-  online passwords.  Passwords are identified by arbitrarily chosen strings like
-  “alice@github” or “bob@reddit”.
+  online passwords.  Password lists are identified by arbitrarily chosen strings
+  like “alice@github” or “bob@reddit”.  Passwords of different lengths and
+  formats are supported.
 - **Selectable seed lengths** of 128, 192 or 256 bits.  Subwallets may have
 - **Selectable seed lengths** of 128, 192 or 256 bits.  Subwallets may have
   shorter seeds than their parent.
   shorter seeds than their parent.
 - **User-enhanced entropy:** All operations requiring random data will prompt
 - **User-enhanced entropy:** All operations requiring random data will prompt
@@ -145,6 +117,10 @@ the more prosaic 2048-word [BIP39 wordlist][bw] used in most wallets today.
   you to automate repetitive tasks using shell scripts.  Most of the
   you to automate repetitive tasks using shell scripts.  Most of the
   `mmgen-tool` utility’s commands can be piped.
   `mmgen-tool` utility’s commands can be piped.
 
 
+#### Supported platforms:
+
+Linux, Armbian, Raspbian, Windows/MSYS2
+
 ### Download/Install
 ### Download/Install
 
 
 > #### [Install a prebuilt bootable image (MMGenLive) on a USB stick][8]
 > #### [Install a prebuilt bootable image (MMGenLive) on a USB stick][8]

+ 18 - 17
doc/release-notes/release-notes-v0.12.0.md

@@ -4,11 +4,11 @@
 
 
  - XOR seed splitting: 7311f474, 237567bc, c7ca0c3d (see
  - XOR seed splitting: 7311f474, 237567bc, c7ca0c3d (see
    [XOR-Seed-Splitting:-Theory-and-Practice][xo] for additional information)
    [XOR-Seed-Splitting:-Theory-and-Practice][xo] for additional information)
- - ETH tracking-wallet balance caching, Parity light client optimizations:
-   d0f8c44b
  - Full BIP39 mnemonic support: 8519b68b, 8705e57b
  - Full BIP39 mnemonic support: 8519b68b, 8705e57b
  - Monero new-style mnemonic support: cfa16418
  - Monero new-style mnemonic support: cfa16418
- - New die-roll wallet format, interactive die-roll entry: c7786369, 4714ef84
+ - New dieroll wallet format, interactive dieroll entry: c7786369, 4714ef84
+ - ETH tracking-wallet balance caching, Parity light client optimizations:
+   d0f8c44b
 
 
 #### Other changes/additions/improvements:
 #### Other changes/additions/improvements:
 
 
@@ -17,28 +17,26 @@
  - Monero wallet creation/syncing tool reimplemented, now works under MSYS2:
  - Monero wallet creation/syncing tool reimplemented, now works under MSYS2:
    3951925a
    3951925a
  - New Tool API interface: f8056630
  - New Tool API interface: f8056630
- - Full automation of test suite, automatic starting/stopping of daemons
- - Plus lots of code cleanups, bugfixes, and additional tests!
+ - New [Daemon control interface][dc] and [test daemon start/stop utilities][ss]
+ - Full automation of test suite with automatic starting/stopping of daemons
+ - UTF8 password entry works reliably under MSYS2, warnings disabled
+ - Plus lots of code cleanups, bugfixes, new tests and [expanded documentation][w]
 
 
 This release has been tested on the following platforms:
 This release has been tested on the following platforms:
 
 
         Debian Buster / x86_64
         Debian Buster / x86_64
+        Ubuntu Bionic / x86_64 / qemu-x86_64
+        Armbian Bionic / Orange Pi PC2 (armv8) 
+        Raspbian Buster / Raspberry Pi B (armv7) (no Parity, no Monerod)
         Windows 10 Enterprise Eng. / MSYS2 / qemu-x86_64
         Windows 10 Enterprise Eng. / MSYS2 / qemu-x86_64
 
 
 and with the following coin daemon versions:
 and with the following coin daemon versions:
 
 
-        Bitcoin Core v0.19.0.1
-        Bitcoin-ABC v0.20.9
-        Litecoin Core v0.17.1
-        Monerod v0.15.0.1
-        Parity Ethereum v2.7.2
-
-Testing TBD on the following platforms:
-
-        Ubuntu Bionic / x86_64 / qemu-x86_64
-        Ubuntu Xenial (+Python 3.6.7) / x86_64
-        Armbian Bionic / Orange Pi PC2 (no Parity or Monerod)
-        Raspbian Stretch / Raspberry Pi B (no Parity or Monerod)
+        Bitcoin Core 0.17.1, 0.19.0.1
+        Bitcoin-ABC 0.21.0
+        Litecoin Core 0.17.1
+        Monerod 0.15.0.1
+        Parity Ethereum 2.7.2
 
 
 Altcoin address generation has been additionally tested using the following
 Altcoin address generation has been additionally tested using the following
 tools as references:
 tools as references:
@@ -48,3 +46,6 @@ tools as references:
         vanitygen-plus 22123128 (https://github.com/exploitagency/vanitygen-plus)
         vanitygen-plus 22123128 (https://github.com/exploitagency/vanitygen-plus)
 
 
 [xo]: https://github.com/mmgen/mmgen/wiki/XOR-Seed-Splitting:-Theory-and-Practice.md
 [xo]: https://github.com/mmgen/mmgen/wiki/XOR-Seed-Splitting:-Theory-and-Practice.md
+[dc]: https://github.com/mmgen/mmgen/blob/master/mmgen/daemon.py
+[ss]: https://github.com/mmgen/mmgen/blob/master/test/start-coin-daemons.py
+[w]: https://github.com/mmgen/mmgen/wiki

+ 18 - 3
mmgen/daemon.py

@@ -158,13 +158,20 @@ class Daemon(MMGenObject):
 		self.stop(silent=silent)
 		self.stop(silent=silent)
 		return self.start(silent=silent)
 		return self.start(silent=silent)
 
 
+	def test_socket(self,host,port,timeout=10):
+		import socket
+		try: socket.create_connection((host,port),timeout=timeout).close()
+		except: return False
+		else: return True
+
 	def wait_for_state(self,req_state):
 	def wait_for_state(self,req_state):
-		for i in range(200):
+		for i in range(300):
 			if self.state == req_state:
 			if self.state == req_state:
 				return True
 				return True
 			time.sleep(0.2)
 			time.sleep(0.2)
 		else:
 		else:
-			die(2,'Daemon wait timeout for {} {} exceeded'.format(self.daemon_id.upper(),self.network))
+			m = 'Wait for state {!r} timeout exceeded for daemon {} {}'
+			die(2,m.format(req_state,self.daemon_id.upper(),self.network))
 
 
 	@classmethod
 	@classmethod
 	def check_implement(cls):
 	def check_implement(cls):
@@ -221,6 +228,8 @@ class MoneroWalletDaemon(Daemon):
 
 
 	@property
 	@property
 	def state(self):
 	def state(self):
+		if not self.test_socket(g.monero_wallet_rpc_host,self.rpc_port):
+			return 'stopped'
 		from mmgen.rpc import MoneroWalletRPCConnection
 		from mmgen.rpc import MoneroWalletRPCConnection
 		try:
 		try:
 			MoneroWalletRPCConnection(
 			MoneroWalletRPCConnection(
@@ -291,7 +300,11 @@ class CoinDaemon(Daemon):
 		if network == 'regtest':
 		if network == 'regtest':
 			me.desc = 'regtest daemon'
 			me.desc = 'regtest daemon'
 			if test_suite:
 			if test_suite:
-				rel_datadir = os.path.join('test','data_dir','regtest',daemon_id)
+				rel_datadir = os.path.join(
+					'test',
+					'data_dir{}'.format('-α' if g.debug_utf8 else ''),
+					'regtest',
+					daemon_id )
 			else:
 			else:
 				me.datadir = os.path.join(g.data_dir_root,'regtest',daemon_id)
 				me.datadir = os.path.join(g.data_dir_root,'regtest',daemon_id)
 		elif test_suite:
 		elif test_suite:
@@ -431,6 +444,8 @@ class MoneroDaemon(CoinDaemon):
 
 
 	@property
 	@property
 	def state(self):
 	def state(self):
+		if not self.test_socket(g.monero_wallet_rpc_host,self.rpc_port):
+			return 'stopped'
 		cp = self.run_cmd(
 		cp = self.run_cmd(
 			[self.coind_exec]
 			[self.coind_exec]
 			+ self.shared_args
 			+ self.shared_args

+ 0 - 3
mmgen/globalvars.py

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

+ 0 - 4
mmgen/opts.py

@@ -294,10 +294,6 @@ def init(opts_data,add_opts=[],opt_filter=None,parse_only=False):
 		if not 'code' in opts_data:
 		if not 'code' in opts_data:
 			opts_data['code'] = {}
 			opts_data['code'] = {}
 		opts_data['code']['long_options'] = common_opts_data['code']
 		opts_data['code']['long_options'] = common_opts_data['code']
-		if g.debug_utf8:
-			for k in opts_data:
-				if type(opts_data[k]) == str:
-					opts_data[k] += '-α'
 		mmgen.share.Opts.print_help(opts_data,opt_filter) # exits
 		mmgen.share.Opts.print_help(opts_data,opt_filter) # exits
 
 
 	if g.bob or g.alice:
 	if g.bob or g.alice:

+ 2 - 2
mmgen/seed.py

@@ -866,12 +866,12 @@ class MMGenMnemonic(SeedSourceUnenc):
 
 
 		m  = 'Enter your {ml}-word seed phrase, hitting ENTER or SPACE after each word.\n'
 		m  = 'Enter your {ml}-word seed phrase, hitting ENTER or SPACE after each word.\n'
 		m += "Optionally, you may use pad characters.  Anything you type that's not a\n"
 		m += "Optionally, you may use pad characters.  Anything you type that's not a\n"
-		m += 'lowercase letter will be treated as a {lq}pad character{rq}, i.e. it will simply\n'
+		m += 'lowercase letter will be treated as a “pad character”, i.e. it will simply\n'
 		m += 'be discarded.  Pad characters may be typed before, after, or in the middle\n'
 		m += 'be discarded.  Pad characters may be typed before, after, or in the middle\n'
 		m += "of words.  For each word, once you've typed {lw} characters total (including\n"
 		m += "of words.  For each word, once you've typed {lw} characters total (including\n"
 		m += 'pad characters) any pad character will enter the word.'
 		m += 'pad characters) any pad character will enter the word.'
 
 
-		msg(m.format(ml=mn_len,lw=longest_word,lq=g.lq,rq=g.rq))
+		msg(m.format(ml=mn_len,lw=longest_word))
 
 
 		from string import ascii_lowercase
 		from string import ascii_lowercase
 		from mmgen.term import get_char_raw
 		from mmgen.term import get_char_raw

+ 14 - 5
mmgen/tool.py

@@ -495,8 +495,11 @@ class MMGenToolCmdMnemonic(MMGenToolCmdBase):
 
 
 	@staticmethod
 	@staticmethod
 	def _xmr_reduce(bytestr):
 	def _xmr_reduce(bytestr):
-		from mmgen.protocol import MoneroProtocol
-		return MoneroProtocol.preprocess_key(bytestr,None)
+		from mmgen.protocol import MoneroProtocol as mp
+		if len(bytestr) != mp.privkey_len:
+			m = '{!r}: invalid bit length for Monero private key (must be {})'
+			die(1,m.format(len(bytestr*8),mp.privkey_len*8))
+		return mp.preprocess_key(bytestr,None)
 
 
 	def _do_random_mn(self,nbytes:int,fmt:str):
 	def _do_random_mn(self,nbytes:int,fmt:str):
 		assert nbytes in (16,24,32), 'nbytes must be 16, 24 or 32'
 		assert nbytes in (16,24,32), 'nbytes must be 16, 24 or 32'
@@ -891,7 +894,13 @@ class MMGenToolCmdRPC(MMGenToolCmdBase):
 		return ret
 		return ret
 
 
 class MMGenToolCmdMonero(MMGenToolCmdBase):
 class MMGenToolCmdMonero(MMGenToolCmdBase):
-	"Monero wallet utilities"
+	"""
+	Monero wallet utilities
+
+	Note that the use of these commands requires private data to be exposed on
+	a network-connected machine in order to unlock the Monero wallets.  This is
+	a violation of MMGen's security policy.
+	"""
 
 
 	_monero_chain_height = None
 	_monero_chain_height = None
 	monerod_args = []
 	monerod_args = []
@@ -918,14 +927,14 @@ class MMGenToolCmdMonero(MMGenToolCmdBase):
 									xmr_keyaddrfile:str,
 									xmr_keyaddrfile:str,
 									blockheight:'(default: current height)' = 0,
 									blockheight:'(default: current height)' = 0,
 									addrs:'(integer range or list)' = ''):
 									addrs:'(integer range or list)' = ''):
-		"create Monero wallets from key-address list"
+		"create Monero wallets from a key-address list"
 		return self.monero_wallet_ops(  infile = xmr_keyaddrfile,
 		return self.monero_wallet_ops(  infile = xmr_keyaddrfile,
 										op = 'create',
 										op = 'create',
 										blockheight = blockheight,
 										blockheight = blockheight,
 										addrs = addrs)
 										addrs = addrs)
 
 
 	def syncmonerowallets(self,xmr_keyaddrfile:str,addrs:'(integer range or list)'=''):
 	def syncmonerowallets(self,xmr_keyaddrfile:str,addrs:'(integer range or list)'=''):
-		"sync Monero wallets from key-address list"
+		"sync Monero wallets from a key-address list"
 		return self.monero_wallet_ops(infile=xmr_keyaddrfile,op='sync',addrs=addrs)
 		return self.monero_wallet_ops(infile=xmr_keyaddrfile,op='sync',addrs=addrs)
 
 
 	def monero_wallet_ops(self,infile:str,op:str,blockheight=0,addrs='',monerod_args=[]):
 	def monero_wallet_ops(self,infile:str,op:str,blockheight=0,addrs='',monerod_args=[]):

+ 7 - 2
test/common.py

@@ -23,7 +23,7 @@ common.py: Shared routines and data for the MMGen test suites
 class TestSuiteException(Exception): pass
 class TestSuiteException(Exception): pass
 class TestSuiteFatalException(Exception): pass
 class TestSuiteFatalException(Exception): pass
 
 
-import os
+import os,time
 from mmgen.common import *
 from mmgen.common import *
 from mmgen.devtools import *
 from mmgen.devtools import *
 
 
@@ -166,13 +166,18 @@ def iqmsg_r(s):
 	if not opt.quiet: omsg_r(s)
 	if not opt.quiet: omsg_r(s)
 
 
 def start_test_daemons(*network_ids):
 def start_test_daemons(*network_ids):
+	if hasattr(opt,'no_daemon_autostart') and opt.no_daemon_autostart:
+		return
 	return test_daemons_ops(*network_ids,op='start')
 	return test_daemons_ops(*network_ids,op='start')
 
 
 def stop_test_daemons(*network_ids):
 def stop_test_daemons(*network_ids):
+	if hasattr(opt,'no_daemon_stop') and opt.no_daemon_stop:
+		return
 	return test_daemons_ops(*network_ids,op='stop')
 	return test_daemons_ops(*network_ids,op='stop')
 
 
 def restart_test_daemons(*network_ids):
 def restart_test_daemons(*network_ids):
-	return test_daemons_ops(*network_ids,op='restart')
+	stop_test_daemons(*network_ids)
+	return start_test_daemons(*network_ids)
 
 
 def test_daemons_ops(*network_ids,op):
 def test_daemons_ops(*network_ids,op):
 	if opt.no_daemon_autostart:
 	if opt.no_daemon_autostart:

+ 17 - 14
test/test-release.sh

@@ -37,18 +37,19 @@ rounds=100 rounds_min=20 rounds_mid=250 rounds_max=500
 xmr_addrs='3,99,2,22-24,101-104'
 xmr_addrs='3,99,2,22-24,101-104'
 
 
 dfl_tests='misc obj color unit hash ref altref alts xmr eth autosign btc btc_tn btc_rt bch bch_rt ltc ltc_rt tool tool2 gen'
 dfl_tests='misc obj color unit hash ref altref alts xmr eth autosign btc btc_tn btc_rt bch bch_rt ltc ltc_rt tool tool2 gen'
-extra_tests='autosign_minimal autosign_live etc ltc_tn bch_tn'
-noalt_tests='misc obj color unit hash ref autosign_minimal btc btc_tn btc_rt tool tool2 gen'
+extra_tests='autosign_btc autosign_live etc ltc_tn bch_tn'
+noalt_tests='misc obj color unit hash ref autosign_btc btc btc_tn btc_rt tool tool2 gen'
 quick_tests='misc obj color unit hash ref altref alts xmr eth autosign btc btc_rt tool tool2 gen'
 quick_tests='misc obj color unit hash ref altref alts xmr eth autosign btc btc_rt tool tool2 gen'
 qskip_tests='btc_tn bch bch_rt ltc ltc_rt'
 qskip_tests='btc_tn bch bch_rt ltc ltc_rt'
 
 
 PROGNAME=$(basename $0)
 PROGNAME=$(basename $0)
-while getopts hbCfFi:I:lOpRtvV OPT
+while getopts hAbCfFi:I:lOpRtvV OPT
 do
 do
 	case "$OPT" in
 	case "$OPT" in
 	h)  printf "  %-16s Test MMGen release\n" "${PROGNAME}:"
 	h)  printf "  %-16s Test MMGen release\n" "${PROGNAME}:"
 		echo   "  USAGE:           $PROGNAME [options] [tests or test group]"
 		echo   "  USAGE:           $PROGNAME [options] [tests or test group]"
 		echo   "  OPTIONS: '-h'  Print this help message"
 		echo   "  OPTIONS: '-h'  Print this help message"
+		echo   "           '-A'  Skip tests requiring altcoin modules or daemons"
 		echo   "           '-b'  Buffer keypresses for all invocations of 'test/test.py'"
 		echo   "           '-b'  Buffer keypresses for all invocations of 'test/test.py'"
 		echo   "           '-C'  Run tests in coverage mode"
 		echo   "           '-C'  Run tests in coverage mode"
 		echo   "           '-f'  Speed up the tests by using fewer rounds"
 		echo   "           '-f'  Speed up the tests by using fewer rounds"
@@ -100,6 +101,7 @@ do
 		echo
 		echo
 		echo   "  By default, all tests are run"
 		echo   "  By default, all tests are run"
 		exit ;;
 		exit ;;
+	A)  SKIP_ALT=1 ;;
 	b)  test_py+=" --buf-keypress" ;;
 	b)  test_py+=" --buf-keypress" ;;
 	C)  mkdir -p 'test/trace'
 	C)  mkdir -p 'test/trace'
 		touch 'test/trace.acc'
 		touch 'test/trace.acc'
@@ -151,7 +153,7 @@ case $1 in
 	'')        tests=$dfl_tests ;;
 	'')        tests=$dfl_tests ;;
 	'default') tests=$dfl_tests ;;
 	'default') tests=$dfl_tests ;;
 	'extra')   tests=$extra_tests ;;
 	'extra')   tests=$extra_tests ;;
-	'noalt')   tests=$noalt_tests ;;
+	'noalt')   tests=$noalt_tests SKIP_ALT=1 ;;
 	'quick')   tests=$quick_tests ;;
 	'quick')   tests=$quick_tests ;;
 	'qskip')   tests=$qskip_tests ;;
 	'qskip')   tests=$qskip_tests ;;
 	*)         tests="$*" ;;
 	*)         tests="$*" ;;
@@ -316,16 +318,16 @@ t_alts="
 	#   keyconv
 	#   keyconv
 	$gentest_py --all --type=legacy 2:keyconv $rounds
 	$gentest_py --all --type=legacy 2:keyconv $rounds
 	$gentest_py --all --type=compressed 2:keyconv $rounds
 	$gentest_py --all --type=compressed 2:keyconv $rounds
+	#   ethkey
+	$gentest_py --all 2:ethkey $rounds
 "
 "
 
 
-[ "$MSYS2" ] || { # no moneropy (pysha3), zcash-mini (golang), ethkey (?)
+[ "$MSYS2" ] || { # no moneropy (pysha3), zcash-mini (golang)
 	t_alts+="
 	t_alts+="
 		#   moneropy
 		#   moneropy
 		$gentest_py --all --coin=xmr 2:moneropy $rounds_min # very slow, be patient!
 		$gentest_py --all --coin=xmr 2:moneropy $rounds_min # very slow, be patient!
 		#   zcash-mini
 		#   zcash-mini
 		$gentest_py --all 2:zcash-mini $rounds_mid
 		$gentest_py --all 2:zcash-mini $rounds_mid
-		#   ethkey
-		$gentest_py --all 2:ethkey $rounds
 	"
 	"
 }
 }
 
 
@@ -343,7 +345,7 @@ else
 	mkdir -p $TMPDIR
 	mkdir -p $TMPDIR
 fi
 fi
 
 
-mmgen_tool_xmr="$mmgen_tool -q --accept-defaults --outdir $TMPDIR"
+mmgen_tool_xmr="$mmgen_tool -q --accept-defaults --outdir $TMPDIR --monero-wallet-rpc-password=passw0rd"
 i_xmr='Monero'
 i_xmr='Monero'
 s_xmr='Testing key-address file generation and wallet creation and sync operations for Monero'
 s_xmr='Testing key-address file generation and wallet creation and sync operations for Monero'
 s_xmr='The monerod (mainnet) daemon must be running for the following tests'
 s_xmr='The monerod (mainnet) daemon must be running for the following tests'
@@ -387,10 +389,10 @@ s_autosign='The bitcoin, bitcoin-abc and litecoin mainnet and testnet daemons mu
 t_autosign="$test_py autosign"
 t_autosign="$test_py autosign"
 f_autosign='Autosign test completed'
 f_autosign='Autosign test completed'
 
 
-i_autosign_minimal='Autosign Minimal'
-s_autosign_minimal='The bitcoin mainnet and testnet daemons must be running for the following test'
-t_autosign_minimal="$test_py autosign_minimal"
-f_autosign_minimal='Autosign Minimal test completed'
+i_autosign_btc='Autosign BTC'
+s_autosign_btc='The bitcoin mainnet and testnet daemons must be running for the following test'
+t_autosign_btc="$test_py autosign_btc"
+f_autosign_btc='Autosign BTC test completed'
 
 
 i_autosign_live='Autosign Live'
 i_autosign_live='Autosign Live'
 s_autosign_live="The bitcoin mainnet and testnet daemons must be running for the following test\n"
 s_autosign_live="The bitcoin mainnet and testnet daemons must be running for the following test\n"
@@ -402,7 +404,7 @@ f_autosign_live='Autosign Live test completed'
 i_btc='Bitcoin mainnet'
 i_btc='Bitcoin mainnet'
 s_btc='The bitcoin (mainnet) daemon must both be running for the following tests'
 s_btc='The bitcoin (mainnet) daemon must both be running for the following tests'
 t_btc="
 t_btc="
-	$test_py --exclude regtest,autosign_minimal,ref_altcoin
+	$test_py --exclude regtest,autosign,ref_altcoin
 	$test_py --segwit
 	$test_py --segwit
 	$test_py --segwit-random
 	$test_py --segwit-random
 	$test_py --bech32
 	$test_py --bech32
@@ -473,7 +475,6 @@ t_tool2="
 	$tooltest2_py --tool-api --coin=eth
 	$tooltest2_py --tool-api --coin=eth
 	$tooltest2_py --tool-api --coin=xmr
 	$tooltest2_py --tool-api --coin=xmr
 	$tooltest2_py --tool-api --coin=zec
 	$tooltest2_py --tool-api --coin=zec
-	$tooltest2_py --fork # run once with --fork so commands are actually executed
 	$tooltest2_py
 	$tooltest2_py
 	$tooltest2_py --testnet=1
 	$tooltest2_py --testnet=1
 	$tooltest2_py --coin=ltc
 	$tooltest2_py --coin=ltc
@@ -488,8 +489,10 @@ t_tool2="
 	$tooltest2_py --coin=eth --token=mm1
 	$tooltest2_py --coin=eth --token=mm1
 	$tooltest2_py --coin=eth --token=mm1 --testnet=1
 	$tooltest2_py --coin=eth --token=mm1 --testnet=1
 	$tooltest2_py --coin=etc
 	$tooltest2_py --coin=etc
+	$tooltest2_py --fork # run once with --fork so commands are actually executed
 "
 "
 f_tool2='tooltest2 tests completed'
 f_tool2='tooltest2 tests completed'
+[ "$SKIP_ALT" ] && t_tool2_skip='17 18'
 
 
 i_tool='Tooltest'
 i_tool='Tooltest'
 s_tool="The following tests will run '$tooltest_py' for all supported coins"
 s_tool="The following tests will run '$tooltest_py' for all supported coins"

+ 14 - 2
test/test.py

@@ -476,7 +476,7 @@ class CmdGroupMgr(object):
 #		'chainsplit':       ('TestSuiteChainsplit',{}),
 #		'chainsplit':       ('TestSuiteChainsplit',{}),
 		'ethdev':           ('TestSuiteEthdev',{}),
 		'ethdev':           ('TestSuiteEthdev',{}),
 		'autosign':         ('TestSuiteAutosign',{}),
 		'autosign':         ('TestSuiteAutosign',{}),
-		'autosign_minimal': ('TestSuiteAutosignMinimal',{'modname':'autosign'}),
+		'autosign_btc':     ('TestSuiteAutosignBTC',{'modname':'autosign'}),
 		'autosign_live':    ('TestSuiteAutosignLive',{'modname':'autosign'}),
 		'autosign_live':    ('TestSuiteAutosignLive',{'modname':'autosign'}),
 		'create_ref_tx':    ('TestSuiteRefTX',{'modname':'misc','full_data':True}),
 		'create_ref_tx':    ('TestSuiteRefTX',{'modname':'misc','full_data':True}),
 	}
 	}
@@ -491,7 +491,7 @@ class CmdGroupMgr(object):
 					'tool',
 					'tool',
 					'input',
 					'input',
 					'output',
 					'output',
-					'autosign_minimal',
+					'autosign',
 					'regtest',
 					'regtest',
 					'ethdev')
 					'ethdev')
 
 
@@ -600,6 +600,7 @@ class TestSuiteRunner(object):
 		self.rebuild_list = OrderedDict()
 		self.rebuild_list = OrderedDict()
 		self.gm = CmdGroupMgr()
 		self.gm = CmdGroupMgr()
 		self.repo_root = repo_root
 		self.repo_root = repo_root
+		self.skipped_warnings = []
 
 
 		if opt.log:
 		if opt.log:
 			self.log_fd = open(log_file,'a')
 			self.log_fd = open(log_file,'a')
@@ -863,6 +864,12 @@ class TestSuiteRunner(object):
 		if cmd == opt.exit_after:
 		if cmd == opt.exit_after:
 			sys.exit(0)
 			sys.exit(0)
 
 
+	def warn_skipped(self):
+		if self.skipped_warnings:
+			print(yellow('The following tests were skipped and may require attention:'))
+			r = '-' * 72 + '\n'
+			print(r+('\n'+r).join(self.skipped_warnings))
+
 	def process_retval(self,cmd,ret):
 	def process_retval(self,cmd,ret):
 		if type(ret).__name__ == 'MMGenPexpect':
 		if type(ret).__name__ == 'MMGenPexpect':
 			ret.ok()
 			ret.ok()
@@ -872,6 +879,9 @@ class TestSuiteRunner(object):
 			self.cmd_total += 1
 			self.cmd_total += 1
 		elif ret == 'skip':
 		elif ret == 'skip':
 			pass
 			pass
+		elif type(ret) == tuple and ret[0] == 'skip_warn':
+			self.skipped_warnings.append(
+				'Test {!r} was skipped:\n  {}'.format(cmd,'\n  '.join(ret[1].split('\n'))))
 		else:
 		else:
 			rdie(1,'{!r} returned {}'.format(cmd,ret))
 			rdie(1,'{!r} returned {}'.format(cmd,ret))
 
 
@@ -942,9 +952,11 @@ start_test_daemons(network_id)
 try:
 try:
 	tr = TestSuiteRunner(data_dir,trash_dir)
 	tr = TestSuiteRunner(data_dir,trash_dir)
 	tr.run_tests(usr_args)
 	tr.run_tests(usr_args)
+	tr.warn_skipped()
 	stop_test_daemons(network_id)
 	stop_test_daemons(network_id)
 except KeyboardInterrupt:
 except KeyboardInterrupt:
 	stop_test_daemons(network_id)
 	stop_test_daemons(network_id)
+	tr.warn_skipped()
 	die(1,'\ntest.py exiting at user request')
 	die(1,'\ntest.py exiting at user request')
 except TestSuiteException as e:
 except TestSuiteException as e:
 	ydie(1,e.args[0])
 	ydie(1,e.args[0])

+ 9 - 9
test/test_py_d/ts_autosign.py

@@ -40,14 +40,14 @@ class TestSuiteAutosign(TestSuiteBase):
 	)
 	)
 
 
 	def autosign_live(self):
 	def autosign_live(self):
-		return self.autosign_minimal(live=True)
+		return self.autosign_btc(live=True)
 
 
-	def autosign_minimal(self,live=False):
+	def autosign_btc(self,live=False):
 		return self.autosign(
 		return self.autosign(
-					coins=['btc','eth'],
+					coins=['btc'],
 					daemon_coins=['btc'],
 					daemon_coins=['btc'],
-					txfiles=['btc','eth','mm1','etc'],
-					txcount=8,
+					txfiles=['btc'],
+					txcount=3,
 					live=live)
 					live=live)
 
 
 	# tests everything except device detection, mount/unmount
 	# tests everything except device detection, mount/unmount
@@ -228,13 +228,13 @@ class TestSuiteAutosign(TestSuiteBase):
 		stop_test_daemons(*network_ids)
 		stop_test_daemons(*network_ids)
 		return ret
 		return ret
 
 
-class TestSuiteAutosignMinimal(TestSuiteAutosign):
-	'autosigning with BTC, ETH and ETC'
+class TestSuiteAutosignBTC(TestSuiteAutosign):
+	'autosigning with BTC'
 	cmd_group = (
 	cmd_group = (
-		('autosign_minimal', 'transaction autosigning (BTC,ETH,ETC)'),
+		('autosign_btc', 'transaction autosigning (BTC only)'),
 	)
 	)
 
 
-class TestSuiteAutosignLive(TestSuiteAutosignMinimal):
+class TestSuiteAutosignLive(TestSuiteAutosignBTC):
 	'live autosigning operations with device insertion/removal and LED check'
 	'live autosigning operations with device insertion/removal and LED check'
 	cmd_group = (
 	cmd_group = (
 		('autosign_live', 'transaction autosigning (BTC,ETH,ETC - test device insertion/removal + LED)'),
 		('autosign_live', 'transaction autosigning (BTC,ETH,ETC - test device insertion/removal + LED)'),

+ 8 - 6
test/test_py_d/ts_misc.py

@@ -135,16 +135,18 @@ class TestSuiteInput(TestSuiteBase):
 
 
 	def password_entry_noecho(self):
 	def password_entry_noecho(self):
 		if self.skip_for_win():
 		if self.skip_for_win():
-			msg('Perform this test by hand on MSWin with non-ASCII password abc-α:')
-			msg('  test/misc/password_entry.py')
-			return 'skip' # getpass() can't handle utf8, and pexpect double-escapes utf8, so skip
+			m = "getpass() doesn't work with pexpect.popen_spawn!\n"
+			m += 'Perform the following test by hand with non-ASCII password abc-α:\n'
+			m += '  test/misc/password_entry.py'
+			return ('skip_warn',m)
 		return self.password_entry('Enter passphrase: ',[])
 		return self.password_entry('Enter passphrase: ',[])
 
 
 	def password_entry_echo(self):
 	def password_entry_echo(self):
 		if self.skip_for_win():
 		if self.skip_for_win():
-			msg('Perform this test by hand on MSWin with non-ASCII password abc-α:')
-			msg('  test/misc/password_entry.py --echo-passphrase')
-			return 'skip' # pexpect double-escapes utf8, so skip
+			m = "getpass() doesn't work with pexpect.popen_spawn!\n"
+			m += 'Perform the following test by hand with non-ASCII password abc-α:\n'
+			m += '  test/misc/password_entry.py --echo-passphrase'
+			return ('skip_warn',m)
 		return self.password_entry('Enter passphrase (echoed): ',['--echo-passphrase'])
 		return self.password_entry('Enter passphrase (echoed): ',['--echo-passphrase'])
 
 
 	def _user_seed_entry(self,fmt,usr_rand=False,out_fmt=None):
 	def _user_seed_entry(self,fmt,usr_rand=False,out_fmt=None):

+ 2 - 2
test/test_py_d/ts_ref.py

@@ -231,12 +231,12 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared):
 
 
 	def ref_segwitaddrfile_chk(self):
 	def ref_segwitaddrfile_chk(self):
 		if not 'S' in g.proto.mmtypes:
 		if not 'S' in g.proto.mmtypes:
-			return skip('not supported')
+			return skip('not supported by {}'.format(g.proto.__name__))
 		return self.ref_addrfile_chk(ftype='segwitaddr',pat='{}.*Segwit'.format(nw_name))
 		return self.ref_addrfile_chk(ftype='segwitaddr',pat='{}.*Segwit'.format(nw_name))
 
 
 	def ref_bech32addrfile_chk(self):
 	def ref_bech32addrfile_chk(self):
 		if not 'B' in g.proto.mmtypes:
 		if not 'B' in g.proto.mmtypes:
-			return skip('not supported')
+			return skip('not supported by {}'.format(g.proto.__name__))
 		return self.ref_addrfile_chk(ftype='bech32addr',pat='{}.*Bech32'.format(nw_name))
 		return self.ref_addrfile_chk(ftype='bech32addr',pat='{}.*Bech32'.format(nw_name))
 
 
 	def ref_keyaddrfile_chk(self):
 	def ref_keyaddrfile_chk(self):

+ 7 - 6
test/test_py_d/ts_ref_3seed.py

@@ -150,10 +150,11 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
 		fn = os.path.split(t.written_to_file(capfirst(ocls.desc)))[-1]
 		fn = os.path.split(t.written_to_file(capfirst(ocls.desc)))[-1]
 		import re
 		import re
 		idx = int(self.test_name[-1]) - 1
 		idx = int(self.test_name[-1]) - 1
-		pat = r'{}-[0-9A-F]{{8}}\[{},1\].mmdat'.format(
+		pat = r'{}-[0-9A-F]{{8}}\[{},1\]{}.mmdat'.format(
 			self.chk_data['sids'][idx],
 			self.chk_data['sids'][idx],
-			self.chk_data['lens'][idx] )
-		assert re.match(pat,fn)
+			self.chk_data['lens'][idx],
+			'-α' if g.debug_utf8 else '')
+		assert re.match(pat,fn),'{} != {}'.format(pat,fn)
 		sid = os.path.basename(fn.split('-')[0])
 		sid = os.path.basename(fn.split('-')[0])
 		cmp_or_die(sid,self.seed_id,desc='Seed ID')
 		cmp_or_die(sid,self.seed_id,desc='Seed ID')
 		return t
 		return t
@@ -170,9 +171,9 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
 		if re_pat:
 		if re_pat:
 			import re
 			import re
 			pat = re_pat.format(sid,slen)
 			pat = re_pat.format(sid,slen)
-			assert re.match(pat,fn),'{} {}'.format(pat,fn)
+			assert re.match(pat,fn),'{} != {}'.format(pat,fn)
 		else:
 		else:
-			cmp_or_die('{}[{}].{}'.format(sid,slen,wcls.ext),fn)
+			cmp_or_die('{}[{}]{}.{}'.format(sid,slen,'-α' if g.debug_utf8 else '',wcls.ext),fn)
 		return t
 		return t
 
 
 	def ref_walletconv_words(self):        return self.ref_walletconv(ofmt='mn')
 	def ref_walletconv_words(self):        return self.ref_walletconv(ofmt='mn')
@@ -184,7 +185,7 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
 
 
 	def ref_walletconv_incog(self,ofmt='incog',ext='mmincog'):
 	def ref_walletconv_incog(self,ofmt='incog',ext='mmincog'):
 		args = ['-r0','-p1']
 		args = ['-r0','-p1']
-		pat = r'{}-[0-9A-F]{{8}}-[0-9A-F]{{8}}\[{},1\].' + ext
+		pat = r'{}-[0-9A-F]{{8}}-[0-9A-F]{{8}}\[{},1\]' + ('-α' if g.debug_utf8 else '') + '.' + ext
 		return self.ref_walletconv(ofmt=ofmt,extra_args=args,re_pat=pat)
 		return self.ref_walletconv(ofmt=ofmt,extra_args=args,re_pat=pat)
 
 
 	def ref_walletconv_xincog(self):
 	def ref_walletconv_xincog(self):

+ 4 - 2
test/test_py_d/ts_regtest.py

@@ -630,9 +630,11 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 		return t
 		return t
 
 
 	def _get_mempool(self):
 	def _get_mempool(self):
-		disable_debug()
+		if not g.debug_utf8:
+			disable_debug()
 		ret = self.spawn('mmgen-regtest',['mempool']).read()
 		ret = self.spawn('mmgen-regtest',['mempool']).read()
-		restore_debug()
+		if not g.debug_utf8:
+			restore_debug()
 		m = re.search(r'(\[\s*"[a-f0-9]{64}"\s*])',ret) # allow for extra output by handler at end
 		m = re.search(r'(\[\s*"[a-f0-9]{64}"\s*])',ret) # allow for extra output by handler at end
 		return json.loads(m.group(1))
 		return json.loads(m.group(1))