Browse Source

Key/address generation support for 144 altcoins

    Support for these coins is EXPERIMENTAL, use at your own risk

    EXAMPLE: generate 10 Dogecoin key/address pairs with your default wallet:

        `mmgen-keygen --coin=doge 1-10`

    Keys for different coins are distinct, so users needn't worry about key reuse.

    Supported alts: 2give,42,611,ac,acoin,alf,anc,apex,arco,arg,aur,b2x,bcf,bch,blk,bmc,bqc,bsty,btcd,btq,bucks,cann,cash,cat,cbx,ccn,cdn,chc,clam,con,cpc,crps,csh,dash,dcr,dfc,dgb,dgc,doge,doged,dope,dvc,efl,emc,emd,enrg,esp,etc,eth,fai,fc2,fibre,fjc,flo,flt,fst,ftc,gcr,good,grc,gun,ham,html5,hyp,icash,infx,inpay,ipc,jbs,judge,lana,lat,ldoge,lmc,ltc,mars,mcar,mec,mint,mobi,mona,moon,mrs,mue,mxt,myr,myriad,mzc,neos,neva,nka,nlg,nmc,nto,nvc,ok,omc,omni,onion,onx,part,pink,pivx,pkb,pnd,pot,ppc,ptc,pxc,qrk,rain,rbt,rby,rdd,ric,sdc,sib,smly,song,spr,start,sys,taj,tit,tpc,trc,ttc,tx,uno,via,vpn,vtc,wash,wdc,wisc,wkc,wsx,xcn,xgb,xmg,xpm,xpoke,xred,xst,xvc,zec,zet,zlq,zoom,zrc

	Test the new functionality with `scripts/test-release.sh -Pn master alts`

B2X support disabled pending further testing
MMGen 7 years ago
parent
commit
91ac2effb3

+ 1 - 1
README.md

@@ -15,7 +15,7 @@ an online and offline computer to provide a robust solution for securely
 storing, tracking, sending and receiving Bitcoins.
 
 The online computer is used only for tracking balances and creating and sending
-transactions.  **Thus it holds no private keys that can be hacked or stolen.**
+transactions.  Thus it holds no private keys that can be hacked or stolen.
 All transactions are signed offline: **your seed and private keys never touch a
 network-connected device.**  The offline computer used for wallet creation,
 address generation and transaction signing is typically a low-powered device

+ 2 - 3
cmds/mmgen-autosign

@@ -103,7 +103,7 @@ cmd_args = opts.init(opts_data,add_opts=['mmgen_keys_from_file','in_fmt'])
 
 import mmgen.tx
 from mmgen.txsign import txsign
-from mmgen.protocol import CoinProtocol
+from mmgen.protocol import CoinProtocol,init_coin
 
 if opt.stealth_led: opt.led = True
 
@@ -154,8 +154,7 @@ def do_umount():
 
 def sign_tx_file(txfile):
 	try:
-		g.coin = mmgen.tx.MMGenTX(txfile,md_only=True).coin
-		g.proto = CoinProtocol(g.coin,g.testnet)
+		init_coin(mmgen.tx.MMGenTX(txfile,md_only=True).coin)
 		reload(sys.modules['mmgen.tx'])
 		tx = mmgen.tx.MMGenTX(txfile)
 		rpc_init(reinit=True)

+ 20 - 6
mmgen/addr.py

@@ -32,7 +32,14 @@ def sc_dmsg(desc,data):
 		Msg('sc_debug_{}: {}'.format(desc,data))
 
 class AddrGenerator(MMGenObject):
-	def __new__(cls,gen_method):
+	def __new__(cls,addr_type):
+		if type(addr_type) == str: # allow override w/o check
+			gen_method = addr_type
+		elif type(addr_type) == MMGenAddrType:
+			assert addr_type in g.proto.mmtypes,'{}: invalid address type for coin {}'.format(addr_type,g.coin)
+			gen_method = addr_type.gen_method
+		else:
+			raise TypeError,'{}: incorrect argument type for {}()'.format(type(addr_type),cls.__name__)
 		d = {
 			'p2pkh':  AddrGeneratorP2PKH,
 			'segwit': AddrGeneratorSegwit,
@@ -108,7 +115,14 @@ class AddrGeneratorZcashZ(AddrGenerator):
 
 class KeyGenerator(MMGenObject):
 
-	def __new__(cls,pubkey_type,generator=None,silent=False):
+	def __new__(cls,addr_type,generator=None,silent=False):
+		if type(addr_type) == str: # allow override w/o check
+			pubkey_type = addr_type
+		elif type(addr_type) == MMGenAddrType:
+			assert addr_type in g.proto.mmtypes,'{}: invalid address type for coin {}'.format(addr_type,g.coin)
+			pubkey_type = addr_type.pubkey_type
+		else:
+			raise TypeError,'{}: incorrect argument type for {}()'.format(type(addr_type),cls.__name__)
 		if pubkey_type == 'std':
 			if cls.test_for_secp256k1(silent=silent) and generator != 1:
 				if not opt.key_generator or opt.key_generator == 2 or generator == 2:
@@ -330,8 +344,8 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
 		has_viewkey = self.al_id.mmtype.has_viewkey
 
 		if self.gen_addrs:
-			kg = KeyGenerator(pubkey_type)
-			ag = AddrGenerator(self.al_id.mmtype.gen_method)
+			kg = KeyGenerator(self.al_id.mmtype)
+			ag = AddrGenerator(self.al_id.mmtype)
 
 		t_addrs,num,pos,out = len(addrnums),0,0,AddrListList()
 		le = self.entry_type
@@ -529,8 +543,8 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
 			ret.append(a)
 
 		if self.has_keys and keypress_confirm('Check key-to-address validity?'):
-			kg = KeyGenerator(self.al_id.mmtype.pubkey_type)
-			ag = AddrGenerator(self.al_id.mmtype.gen_method)
+			kg = KeyGenerator(self.al_id.mmtype)
+			ag = AddrGenerator(self.al_id.mmtype)
 			llen = len(ret)
 			for n,e in enumerate(ret):
 				msg_r('\rVerifying keys %s/%s' % (n+1,llen))

+ 529 - 0
mmgen/altcoin.py

@@ -0,0 +1,529 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2017 Philemon <mmgen-py@yandex.com>
+#
+# 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/>.
+
+"""
+altcoin.py - Coin constants for Bitcoin-derived altcoins
+"""
+
+# Sources:
+#   lb: https://github.com/libbitcoin/libbitcoin/wiki/Altcoin-Version-Mappings
+#   pc: https://github.com/richardkiss/pycoin/blob/master/pycoin/networks/legacy_networks.py
+#   vg: https://github.com/exploitagency/vanitygen-plus/blob/master/keyconv.c
+#   wn: https://walletgenerator.net
+#   cc: https://www.cryptocompare.com/api/data/coinlist/ (names,symbols only)
+
+# WIP:
+#   NSR:  149/191 c/u,  63/('S'),  64/('S'|'T')
+#   NBT:  150/191 c/u,  25/('B'),  26/('B')
+
+import sys
+def msg(s): sys.stderr.write(s+'\n')
+
+class CoinInfo(object):
+	coin_constants = {}
+	coin_constants['mainnet'] = (
+#    NAME                     SYM        WIF     P2PKH             P2SH              SEGWIT TRUST
+#                                                     trust levels: 0=untested 1=low 2=med 3=high
+	('Bitcoin',               'BTC',     0x80,   (0x00,'1'),       (0x05,'3'),       True,  3),
+	('BitcoinSegwit2X',       'B2X',     0x80,   (0x00,'1'),       (0x05,'3'),       True,  2),
+	('Bcash',                 'BCH',     0x80,   (0x00,'1'),       (0x05,'3'),       False, 3),
+	('2GiveCoin',             '2GIVE',   0xa7,   (0x27,('G','H')), None,             False, 0),
+	('42Coin',                '42',      0x88,   (0x08,'4'),       None,             False, 1),
+	('ACoin',                 'ACOIN',   0xe6,   (0x17,'A'),       None,             False, 0),
+	('Alphacoin',             'ALF',     0xd2,   (0x52,('Z','a')), None,             False, 0),
+	('Anoncoin',              'ANC',     0x97,   (0x17,'A'),       None,             False, 1),
+	('Apexcoin',              'APEX',    0x97,   (0x17,'A'),       None,             False, 0),
+	('Aquariuscoin',          'ARCO',    0x97,   (0x17,'A'),       None,             False, 0),
+	('Argentum',              'ARG',     0x97,   (0x17,'A'),       (0x05,'3'),       False, 1),
+	('AsiaCoin',              'AC',      0x97,   (0x17,'A'),       (0x08,'4'),       False, 1),
+	('Auroracoin',            'AUR',     0x97,   (0x17,'A'),       None,             False, 1),
+	('BBQcoin',               'BQC',     0xd5,   (0x55,'b'),       None,             False, 1),
+	('BitcoinDark',           'BTCD',    0xbc,   (0x3c,'R'),       (0x55,'b'),       False, 1),
+	('BitcoinFast',           'BCF',     0xe0,   (0x60,('f','g')), None,             False, 0),
+	('BitQuark',              'BTQ',     0xba,   (0x3a,'Q'),       None,             False, 0),
+	('Blackcoin',             'BLK',     0x99,   (0x19,'B'),       (0x55,'b'),       False, 1),
+	('BlackmoonCrypto',       'BMC',     0x83,   (0x03,'2'),       None,             False, 0),
+	('BlockCat',              'CAT',     0x95,   (0x15,'9'),       None,             False, 0),
+	('CanadaECoin',           'CDN',     0x9c,   (0x1c,'C'),       (0x05,'3'),       False, 1),
+	('CannabisCoin',          'CANN',    0x9c,   (0x1c,'C'),       None,             False, 0),
+	('CannaCoin',             'CCN',     0x9c,   (0x1c,'C'),       (0x05,'3'),       False, 1),
+	('Capricoin',             'CPC',     0x9c,   (0x1c,'C'),       None,             False, 0),
+	('CashCoin',              'CASH',    0xa2,   (0x22,('E','F')), None,             False, 0),
+	('CashOut',               'CSH',     0xa2,   (0x22,('E','F')), None,             False, 0),
+	('ChainCoin',             'CHC',     0x9c,   (0x1c,'C'),       None,             False, 0),
+	('Clams',                 'CLAM',    0x85,   (0x89,'x'),       (0x0d,'6'),       False, 1),
+	('CoinMagi',              'XMG',     0x94,   (0x14,'9'),       None,             False, 0),
+	('Condensate',            'RAIN',    0xbc,   (0x3c,'R'),       None,             False, 0),
+	('CryptoBullion',         'CBX',     0x8b,   (0x0b,'5'),       None,             False, 0),
+	('Cryptonite',            'XCN',     0x80,   (0x1c,'C'),       None,             False, 0),
+	('CryptoPennies',         'CRPS',    0xc2,   (0x42,'T'),       None,             False, 0),
+	('Dash',                  'DASH',    0xcc,   (0x4c,'X'),       (0x10,'7'),       False, 1),
+	('Decred',                'DCR',     0x22de, (0x073f,'D'),     (0x071a,'D'),     False, 1),
+	('DeepOnion',             'ONION',   0x9f,   (0x1f,'D'),       None,             False, 1),
+	('Defcoin',               'DFC',     0x9e,   (0x1e,'D'),       (0x05,'3'),       False, 1),
+	('Devcoin',               'DVC',     0x80,   (0x00,'1'),       None,             False, 1),
+	('DigiByte',              'DGB',     0x80,   (0x1e,'D'),       (0x05,'3'),       False, 1),
+	('DigiCoin',              'DGC',     0x9e,   (0x1e,'D'),       (0x05,'3'),       False, 1),
+	('DogecoinDark',          'DOGED',   0x9e,   (0x1e,'D'),       (0x21,'E'),       False, 1),
+	('Dogecoin',              'DOGE',    0x9e,   (0x1e,'D'),       (0x16,('9','A')), False, 2),
+	('DopeCoin',              'DOPE',    0x88,   (0x08,'4'),       (0x05,'3'),       False, 1),
+	('EGulden',               'EFL',     0xb0,   (0x30,'L'),       (0x05,'3'),       False, 1),
+	('Emerald',               'EMD',     0xa2,   (0x22,('E','F')), None,             False, 0),
+	('Emercoin',              'EMC',     0x80,   (0x21,'E'),       (0x5c,'e'),       False, 2),
+	('EnergyCoin',            'ENRG',    0xdc,   (0x5c,'e'),       None,             False, 0),
+	('Espers',                'ESP',     0xa1,   (0x21,'E'),       None,             False, 0),
+	('Faircoin',              'FAI',     0xdf,   (0x5f,'f'),       (0x24,'F'),       False, 1),
+	('Fastcoin',              'FST',     0xe0,   (0x60,('f','g')), None,             False, 0),
+	('Feathercoin',           'FTC',     0x8e,   (0x0e,('6','7')), (0x05,'3'),       False, 2),
+	('Fibre',                 'FIBRE',   0xa3,   (0x23,'F'),       None,             False, 0),
+	('FlorinCoin',            'FLO',     0xb0,   (0x23,'F'),       None,             False, 0),
+	('Fluttercoin',           'FLT',     0xa3,   (0x23,'F'),       None,             False, 0),
+	('Fuel2Coin',             'FC2',     0x80,   (0x24,'F'),       None,             False, 0),
+	('Fujicoin',              'FJC',     0xa4,   (0x24,'F'),       None,             False, 0),
+	('Fujinto',               'NTO',     0xa4,   (0x24,'F'),       None,             False, 0),
+	('GlobalBoost',           'BSTY',    0xa6,   (0x26,'G'),       None,             False, 0),
+	('GlobalCurrencyReserve', 'GCR',     0x9a,   (0x26,'G'),       (0x61,'g'),       False, 1),
+	('GoldenBird',            'XGB',     0xaf,   (0x2f,('K','L')), None,             False, 0),
+	('Goodcoin',              'GOOD',    0xa6,   (0x26,'G'),       None,             False, 0),
+	('GridcoinResearch',      'GRC',     0xbe,   (0x3e,('R','S')), None,             False, 1),
+	('Gulden',                'NLG',     0xa6,   (0x26,'G'),       None,             False, 1),
+	('Guncoin',               'GUN',     0xa7,   (0x27,('G','H')), None,             False, 1),
+	('HamRadioCoin',          'HAM',     0x80,   (0x00,'1'),       None,             False, 1),
+	('HTML5Coin',             'HTML5',   0xa8,   (0x28,'H'),       None,             False, 0),
+	('HyperStake',            'HYP',     0xf5,   (0x75,'p'),       None,             False, 0),
+	('iCash',                 'ICASH',   0xcc,   (0x66,'i'),       None,             False, 0),
+	('ImperiumCoin',          'IPC',     0xb0,   (0x30,'L'),       None,             False, 0),
+	('IncaKoin',              'NKA',     0xb5,   (0x35,'N'),       None,             False, 0),
+	('Influxcoin',            'INFX',    0xe6,   (0x66,'i'),       None,             False, 0),
+	('InPay',                 'INPAY',   0xb7,   (0x37,'P'),       None,             False, 0),
+#	('iXcoin',                'IXC',     0x80,   (0x8a,'x'),       None,             False, 1),
+	('Judgecoin',             'JUDGE',   0xab,   (0x2b,'J'),       None,             False, 0),
+	('Jumbucks',              'JBS',     0xab,   (0x2b,'J'),       (0x69,'j'),       False, 2),
+	('Lanacoin',              'LANA',    0xb0,   (0x30,'L'),       None,             False, 0),
+	('Latium',                'LAT',     0x80,   (0x17,'A'),       None,             False, 0),
+	('Litecoin',              'LTC',     0xb0,   (0x30,'L'),       (0x05,'3'),       True,  3),
+	('LiteDoge',              'LDOGE',   0xab,   (0x5a,'d'),       None,             False, 0),
+	('LomoCoin',              'LMC',     0xb0,   (0x30,'L'),       None,             False, 0),
+	('Marscoin',              'MARS',    0xb2,   (0x32,'M'),       None,             False, 0),
+	('MarsCoin',              'MRS',     0xb2,   (0x32,'M'),       None,             False, 0),
+	('MartexCoin',            'MXT',     0xb2,   (0x32,'M'),       None,             False, 0),
+	('MasterCar',             'MCAR',    0xe6,   (0x17,'A'),       None,             False, 0),
+	('MazaCoin',              'MZC',     0xe0,   (0x32,'M'),       (0x09,('4','5')), False, 2),
+	('MegaCoin',              'MEC',     0xb2,   (0x32,'M'),       None,             False, 1),
+	('MintCoin',              'MINT',    0xb3,   (0x33,'M'),       None,             False, 0),
+	('Mobius',                'MOBI',    0x80,   (0x00,'1'),       None,             False, 0),
+	('MonaCoin',              'MONA',    0xb0,   (0x32,'M'),       (0x05,'3'),       False, 1),
+	('MonetaryUnit',          'MUE',     0x8f,   (0x0f,'7'),       (0x09,('4','5')), False, 1),
+	('MoonCoin',              'MOON',    0x83,   (0x03,'2'),       None,             False, 0),
+	('MyriadCoin',            'MYR',     0xb2,   (0x32,'M'),       (0x09,('4','5')), False, 1),
+	('Myriadcoin',            'MYRIAD',  0xb2,   (0x32,'M'),       None,             False, 1),
+	('Namecoin',              'NMC',     0xb4,   (0x34,('M','N')), (0x0d,'6'),       False, 1),
+	('Neoscoin',              'NEOS',    0xef,   (0x3f,'S'),       (0xbc,'2'),       False, 1),
+	('NevaCoin',              'NEVA',    0xb1,   (0x35,'N'),       None,             False, 0),
+	('Novacoin',              'NVC',     0x88,   (0x08,'4'),       (0x14,'9'),       False, 1),
+	('OKCash',                'OK',      0xb7,   (0x37,'P'),       (0x1c,'C'),       False, 1),
+	('Omnicoin',              'OMC',     0xf3,   (0x73,'o'),       None,             False, 1),
+	('Omni',                  'OMNI',    0xf3,   (0x73,'o'),       None,             False, 0),
+	('Onix',                  'ONX',     0x80,   (0x8a,'x'),       None,             False, 0),
+	('PandaCoin',             'PND',     0xb7,   (0x37,'P'),       (0x16,('9','A')), False, 1),
+	('ParkByte',              'PKB',     0xb7,   (0x37,'P'),       (0x1c,'C'),       False, 1),
+	('Particl',               'PART',    0x6c,   (0x38,'P'),       None,             False, 0),
+	('Paycoin',               'CON',     0xb7,   (0x37,'P'),       None,             False, 1),
+	('Peercoin',              'PPC',     0xb7,   (0x37,'P'),       (0x75,'p'),       False, 1),
+	('PesetaCoin',            'PTC',     0xaf,   (0x2f,('K','L')), None,             False, 1),
+	('PhoenixCoin',           'PXC',     0xb8,   (0x38,'P'),       None,             False, 0),
+	('PinkCoin',              'PINK',    0x83,   (0x03,'2'),       None,             False, 1),
+	('PIVX',                  'PIVX',    0xd4,   (0x1e,'D'),       None,             False, 0),
+	('PokeChain',             'XPOKE',   0x9c,   (0x1c,'C'),       None,             False, 0),
+	('Potcoin',               'POT',     0xb7,   (0x37,'P'),       (0x05,'3'),       False, 1),
+	('Primecoin',             'XPM',     0x97,   (0x17,'A'),       (0x53,'a'),       False, 1),
+	('Quark',                 'QRK',     0xba,   (0x3a,'Q'),       None,             False, 0),
+	('ReddCoin',              'RDD',     0xbd,   (0x3d,'R'),       None,             False, 1),
+	('Riecoin',               'RIC',     0x80,   (0x3c,'R'),       (0x05,'3'),       False, 2),
+	('Rimbit',                'RBT',     0xbc,   (0x3c,'R'),       None,             False, 0),
+	('Rubycoin',              'RBY',     0xbd,   (0x3d,'R'),       (0x55,'b'),       False, 1),
+	('ShadowCash',            'SDC',     0xbf,   (0x3f,'S'),       (0x7d,'s'),       False, 1),
+	('Sibcoin',               'SIB',     0x80,   (0x3f,'S'),       None,             False, 0),
+	('SixEleven',             '611',     0x80,   (0x34,('M','N')), None,             False, 0),
+	('SmileyCoin',            'SMLY',    0x99,   (0x19,'B'),       None,             False, 0),
+	('Songcoin',              'SONG',    0xbf,   (0x3f,'S'),       None,             False, 0),
+	('Spreadcoin',            'SPR',     0xbf,   (0x3f,'S'),       None,             False, 1),
+	('Startcoin',             'START',   0xfd,   (0x7d,'s'),       (0x05,'3'),       False, 1),
+	('StealthCoin',           'XST',     0xbe,   (0x3e,('R','S')), None,             False, 0),
+	('SwagBucks',             'BUCKS',   0x99,   (0x3f,'S'),       None,             False, 0),
+	('SysCoin',               'SYS',     0x80,   (0x00,'1'),       None,             False, 0),
+	('TajCoin',               'TAJ',     0x6f,   (0x41,'T'),       None,             False, 0),
+	('Templecoin',            'TPC',     0xc1,   (0x41,'T'),       (0x05,'3'),       False, 1),
+	('Terracoin',             'TRC',     0x80,   (0x00,'1'),       None,             False, 0),
+	('Titcoin',               'TIT',     0x80,   (0x00,'1'),       None,             False, 0),
+	('TittieCoin',            'TTC',     0xc1,   (0x41,'T'),       None,             False, 0),
+	('Transfer',              'TX',      0x99,   (0x42,'T'),       None,             False, 0),
+	('Unobtanium',            'UNO',     0xe0,   (0x82,'u'),       (0x1e,'D'),       False, 2),
+	('Vcash',                 'XVC',     0xc7,   (0x47,'V'),       None,             False, 0),
+	('Vertcoin',              'VTC',     0xc7,   (0x47,'V'),       (0x05,'3'),       False, 1),
+	('Viacoin',               'VIA',     0xc7,   (0x47,'V'),       (0x21,'E'),       False, 2),
+	('VpnCoin',               'VPN',     0xc7,   (0x47,'V'),       (0x05,'3'),       False, 1),
+	('WankCoin',              'WKC',     0x80,   (0x00,'1'),       None,             False, 1),
+	('WashingtonCoin',        'WASH',    0xc9,   (0x49,'W'),       None,             False, 0),
+	('WeAreSatoshi',          'WSX',     0x97,   (0x87,'w'),       None,             False, 0),
+	('WisdomCoin',            'WISC',    0x87,   (0x49,'W'),       None,             False, 0),
+	('WorldCoin',             'WDC',     0xc9,   (0x49,'W'),       None,             False, 1),
+	('XRealEstateDevcoin',    'XRED',    0x80,   (0x00,'1'),       None,             False, 0),
+	('ZetaCoin',              'ZET',     0xe0,   (0x50,'Z'),       None,             False, 0),
+	('ZiftrCoin',             'ZRC',     0xd0,   (0x50,'Z'),       (0x05,'3'),       False, 1),
+	('ZLiteQubit',            'ZLQ',     0xe0,   (0x26,'G'),       None,             False, 0),
+	('Zoomcoin',              'ZOOM',    0xe7,   (0x67,'i'),       (0x5c,'e'),       False, 1),
+	)
+
+	coin_constants['testnet'] = (
+	('Dash',        'DASH',  0xef,   (0x8c,'y'),       (0x13,('8','9')), False, 1),
+	('Decred',      'DCR',   0x230e, (0x0f21,'T'),     (0x0e6c,'S'),     False, 1),
+	('Dogecoin',    'DOGE',  0xf1,   (0x71,'n'),       (0xc4,'2'),       False, 2),
+	('Feathercoin', 'FTC',   0xc1,   (0x41,'T'),       (0xc4,'2'),       False, 2),
+	('Viacoin',     'VIA',   0xff,   (0x7f,'t'),       (0xc4,'2'),       False, 2),
+	('Emercoin',    'EMC',   0xef,   (0x6f,('m','n')), (0xc4,'2'),       False, 2),
+	)
+
+	coin_sources = (
+	('BTC',    'https://github.com/bitcoin/bitcoin/blob/master/src/chainparams.cpp'),
+	('EMC',    'https://github.com/emercoin/emercoin/blob/master/src/chainparams.cpp'), # checked mn,tn
+	('LTC',    'https://github.com/litecoin-project/litecoin/blob/master-0.10/src/chainparams.cpp'),
+	('DOGE',   'https://github.com/dogecoin/dogecoin/blob/master/src/chainparams.cpp'),
+	('RDD',    'https://github.com/reddcoin-project/reddcoin/blob/master/src/base58.h'),
+	('DASH',   'https://github.com/dashpay/dash/blob/master/src/chainparams.cpp'),
+	('PPC',    'https://github.com/belovachap/peercoin/blob/master/src/base58.h'),
+	('NMC',    'https://github.com/domob1812/namecore/blob/master/src/chainparams.cpp'),
+	('FTC',    'https://github.com/FeatherCoin/Feathercoin/blob/master-0.8/src/base58.h'),
+	('BLK',    'https://github.com/rat4/blackcoin/blob/master/src/chainparams.cpp'),
+	('NSR',    'https://nubits.com/nushares/introduction'),
+	('NBT',    'https://bitbucket.org/JordanLeePeershares/nubit/NuBit / src /base58.h'),
+	('MZC',    'https://github.com/MazaCoin/MazaCoin/blob/master/src/chainparams.cpp'),
+	('VIA',    'https://github.com/viacoin/viacoin/blob/master/src/chainparams.cpp'),
+	('RBY',    'https://github.com/rubycoinorg/rubycoin/blob/master/src/base58.h'),
+	('GRS',    'https://github.com/GroestlCoin/groestlcoin/blob/master/src/groestlcoin.cpp'),
+	('DGC',    'https://github.com/DGCDev/digitalcoin/blob/master/src/chainparams.cpp'),
+	('CCN',    'https://github.com/Cannacoin-Project/Cannacoin/blob/Proof-of-Stake/src/base58.h'),
+	('DGB',    'https://github.com/digibyte/digibyte/blob/master/src/chainparams.cpp'),
+	('MONA',   'https://github.com/monacoinproject/monacoin/blob/master-0.10/src/chainparams.cpp'),
+	('CLAM',   'https://github.com/nochowderforyou/clams/blob/master/src/chainparams.cpp'),
+	('XPM',    'https://github.com/primecoin/primecoin/blob/master/src/base58.h'),
+	('NEOS',   'https://github.com/bellacoin/neoscoin/blob/master/src/chainparams.cpp'),
+	('JBS',    'https://github.com/jyap808/jumbucks/blob/master/src/base58.h'),
+	('ZRC',    'https://github.com/ZiftrCOIN/ziftrcoin/blob/master/src/chainparams.cpp'),
+	('VTC',    'https://github.com/vertcoin/vertcoin/blob/master/src/base58.h'),
+	('NXT',    'https://bitbucket.org/JeanLucPicard/nxt/src and unofficial at https://github.com/Blackcomb/nxt'),
+	('MUE',    'https://github.com/MonetaryUnit/MUE-Src/blob/master/src/chainparams.cpp'),
+	('ZOOM',   'https://github.com/zoom-c/zoom/blob/master/src/base58.h'),
+	('VPN',    'https://github.com/Bit-Net/VpnCoin/blob/master/src/base58.h'),
+	('CDN',    'https://github.com/ThisIsOurCoin/canadaecoin/blob/master/src/base58.h'),
+	('SDC',    'https://github.com/ShadowProject/shadow/blob/master/src/chainparams.cpp'),
+	('PKB',    'https://github.com/parkbyte/ParkByte/blob/master/src/base58.h'),
+	('PND',    'https://github.com/coinkeeper/2015-04-19_21-22_pandacoin/blob/master/src/base58.h'),
+	('START',  'https://github.com/startcoin-project/startcoin/blob/master/src/base58.h'),
+	('GCR',    'https://github.com/globalcurrencyreserve/gcr/blob/master/src/chainparams.cpp'),
+	('NVC',    'https://github.com/novacoin-project/novacoin/blob/master/src/base58.h'),
+	('AC',     'https://github.com/AsiaCoin/AsiaCoinFix/blob/master/src/base58.h'),
+	('BTCD',   'https://github.com/jl777/btcd/blob/master/src/base58.h'),
+	('DOPE',   'https://github.com/dopecoin-dev/DopeCoinV3/blob/master/src/base58.h'),
+	('TPC',    'https://github.com/9cat/templecoin/blob/templecoin/src/base58.h'),
+	('OK',     'https://github.com/okcashpro/okcash/blob/master/src/chainparams.cpp'),
+	('DOGED',  'https://github.com/doged/dogedsource/blob/master/src/base58.h'),
+	('EFL',    'https://github.com/Electronic-Gulden-Foundation/egulden/blob/master/src/base58.h'),
+	('POT',    'https://github.com/potcoin/Potcoin/blob/master/src/base58.h'),
+	)
+
+	# Sources (see above) that are in agreement for these coins
+	# No check for segwit, p2sh check skipped if source doesn't support it
+	cross_checks = {
+		'2GIVE':  ['wn'],
+		'42':     ['vg','wn'],
+		'611':    ['wn'],
+		'AC':     ['lb','vg'],
+		'ACOIN':  ['wn'],
+		'ALF':    ['wn'],
+		'ANC':    ['vg','wn'],
+		'APEX':   ['wn'],
+		'ARCO':   ['wn'],
+		'ARG':    ['pc'],
+		'AUR':    ['vg','wn'],
+		'BCH':    ['wn'],
+		'BLK':    ['lb','vg','wn'],
+		'BQC':    ['vg','wn'],
+		'BSTY':   ['wn'],
+		'BTC':    ['lb','vg','wn'],
+		'BTCD':   ['lb','vg','wn'],
+		'BUCKS':  ['wn'],
+		'CASH':   ['wn'],
+		'CBX':    ['wn'],
+		'CCN':    ['lb','vg','wn'],
+		'CDN':    ['lb','vg','wn'],
+		'CHC':    ['wn'],
+		'CLAM':   ['lb','vg'],
+		'CON':    ['vg','wn'],
+		'CPC':    ['wn'],
+		'DASH':   ['lb','pc','vg','wn'],
+		'DCR':    ['pc'],
+		'DFC':    ['pc'],
+		'DGB':    ['lb','vg'],
+		'DGC':    ['lb','vg','wn'],
+		'DOGE':   ['lb','pc','vg','wn'],
+		'DOGED':  ['lb','vg','wn'],
+		'DOPE':   ['lb','vg'],
+		'DVC':    ['vg','wn'],
+		'EFL':    ['lb','vg','wn'],
+		'EMC':    ['vg'],
+		'EMD':    ['wn'],
+		'ESP':    ['wn'],
+		'FAI':    ['pc'],
+		'FC2':    ['wn'],
+		'FIBRE':  ['wn'],
+		'FJC':    ['wn'],
+		'FLO':    ['wn'],
+		'FLT':    ['wn'],
+		'FST':    ['wn'],
+		'FTC':    ['lb','pc','vg','wn'],
+		'GCR':    ['lb','vg'],
+		'GOOD':   ['wn'],
+		'GRC':    ['vg','wn'],
+		'GUN':    ['vg','wn'],
+		'HAM':    ['vg','wn'],
+		'HTML5':  ['wn'],
+		'HYP':    ['wn'],
+		'ICASH':  ['wn'],
+		'INFX':   ['wn'],
+		'IPC':    ['wn'],
+		'JBS':    ['lb','pc','vg','wn'],
+		'JUDGE':  ['wn'],
+		'LANA':   ['wn'],
+		'LAT':    ['wn'],
+		'LDOGE':  ['wn'],
+		'LMC':    ['wn'],
+		'LTC':    ['lb','vg','wn'],
+		'MARS':   ['wn'],
+		'MEC':    ['pc','wn'],
+		'MINT':   ['wn'],
+		'MOBI':   ['wn'],
+		'MONA':   ['lb','vg'],
+		'MOON':   ['wn'],
+		'MUE':    ['lb','vg'],
+		'MXT':    ['wn'],
+		'MYR':    ['pc'],
+		'MYRIAD': ['vg','wn'],
+		'MZC':    ['lb','pc','vg','wn'],
+		'NEOS':   ['lb','vg'],
+		'NEVA':   ['wn'],
+		'NKA':    ['wn'],
+		'NLG':    ['vg','wn'],
+		'NMC':    ['lb','vg'],
+		'NVC':    ['lb','vg','wn'],
+		'OK':     ['lb','vg'],
+		'OMC':    ['vg','wn'],
+		'ONION':  ['vg','wn'],
+		'PART':   ['wn'],
+		'PINK':   ['vg','wn'],
+		'PIVX':   ['wn'],
+		'PKB':    ['lb','vg','wn'],
+		'PND':    ['lb','vg','wn'],
+		'POT':    ['lb','vg','wn'],
+		'PPC':    ['lb','vg','wn'],
+		'PTC':    ['vg','wn'],
+		'PXC':    ['wn'],
+		'QRK':    ['wn'],
+		'RAIN':   ['wn'],
+		'RBT':    ['wn'],
+		'RBY':    ['lb','vg'],
+		'RDD':    ['vg','wn'],
+		'RIC':    ['pc','vg','wn'],
+		'SDC':    ['lb','vg'],
+		'SIB':    ['wn'],
+		'SMLY':   ['wn'],
+		'SONG':   ['wn'],
+		'SPR':    ['vg','wn'],
+		'START':  ['lb','vg'],
+		'SYS':    ['wn'],
+		'TAJ':    ['wn'],
+		'TIT':    ['wn'],
+		'TPC':    ['lb','vg'],
+		'TRC':    ['wn'],
+		'TTC':    ['wn'],
+		'TX':     ['wn'],
+		'UNO':    ['pc','vg','wn'],
+		'VIA':    ['lb','pc','vg','wn'],
+		'VPN':    ['lb','vg'],
+		'VTC':    ['lb','vg','wn'],
+		'WDC':    ['vg','wn'],
+		'WISC':   ['wn'],
+		'WKC':    ['vg','wn'],
+		'WSX':    ['wn'],
+		'XCN':    ['wn'],
+		'XGB':    ['wn'],
+		'XPM':    ['lb','vg','wn'],
+		'XST':    ['wn'],
+		'XVC':    ['wn'],
+		'ZET':    ['wn'],
+		'ZOOM':   ['lb','vg'],
+		'ZRC':    ['lb','vg']
+	}
+
+	# data is one of the coin_constants lists above
+	# normalize ints to hex, format width, add missing leading letters, set trust level from external_tests
+	# Insert a coin entry from outside source, set leading letters to '?' and trust to 0, then run fix_table()
+	# segwit column is updated manually for now
+	@classmethod
+	def fix_table(cls,data):
+		import re
+
+		def myhex(n):
+			return '0x{:0{}x}'.format(n,2 if n < 256 else 4)
+
+		def fix_col(line,n):
+			line[n] = list(line[n])
+			line[n][0] = myhex(line[n][0])
+			s1 = cls.find_addr_leading_symbol(int(line[n][0][2:],16))
+			m = 'Fixing coin {} [in data: {!r}] [computed: {}]'.format(line[0],line[n][1],s1)
+			if line[n][1] != '?':
+				assert s1 == line[n][1],'First letters do not match! {}'.format(m)
+			else:
+				msg(m)
+				line[n][1] = s1
+			line[n] = tuple(line[n])
+
+		old_sym = None
+		for sym in sorted([e[1] for e in data]):
+			if sym == old_sym:
+				msg("'{}': duplicate coin symbol in data!".format(sym))
+				sys.exit(2)
+			old_sym = sym
+
+		tt = cls.create_trust_table()
+
+		w = max(len(e[0]) for e in data)
+		fs = '\t({{:{}}} {{:10}} {{:7}} {{:17}} {{:17}} {{:6}} {{}}),'.format(w+3)
+		for line in data:
+			line = list(line)
+			line[2] = myhex(line[2])
+
+			fix_col(line,3)
+			if type(line[4]) == tuple: fix_col(line,4)
+
+			sym,trust = line[1],line[6]
+
+			for n in range(len(line)):
+				line[n] = repr(line[n])
+				line[n] = re.sub(r"'0x(..)'",r'0x\1',line[n])
+				line[n] = re.sub(r"'0x(....)'",r'0x\1',line[n])
+				line[n] = re.sub(r' ',r'',line[n]) + ('',',')[n != len(line)-1]
+
+			from mmgen.util import pmsg,pdie
+#			pmsg(sym)
+#			pdie(tt)
+			if sym in tt:
+				src = tt[sym]
+				if src != trust:
+					msg("Updating trust for coin '{}': {} -> {}".format(sym,trust,src))
+					line[6] = src
+			else:
+				if trust != 0:
+					msg("Downgrading trust for coin '{}': {} -> {}".format(sym,trust,0))
+					line[6] = 0
+
+			if sym in cls.cross_checks:
+				if int(line[6]) == 0 and len(cls.cross_checks[sym]) > 1:
+					msg("Upgrading trust for coin '{}': {} -> {}".format(sym,line[6],1))
+					line[6] = 1
+
+			print(fs.format(*line))
+		msg('Processed {} entries'.format(len(data)))
+
+	@classmethod
+	def find_addr_leading_symbol(cls,ver_num,verbose=False):
+
+		def phash2addr(ver_num,pk_hash):
+			from mmgen.protocol import _b58chk_encode
+			s = '{:0{}x}'.format(ver_num,2 if ver_num < 256 else 4) + pk_hash
+			lzeroes = (len(s) - len(s.lstrip('0'))) / 2 # non-zero only for ver num '00' (BTC p2pkh)
+			return ('1' * lzeroes) + _b58chk_encode(s)
+
+		low = phash2addr(ver_num,'00'*20)
+		high = phash2addr(ver_num,'ff'*20)
+
+		if verbose:
+			print('low address:  ' + low)
+			print('high address: ' + high)
+
+		l1,h1 = low[0],high[0]
+		return (l1,h1) if l1 != h1 else l1
+
+	@classmethod
+	def print_symbols(cls,include_names=False,reverse=False):
+		w = max(len(e[0]) for e in cls.coin_constants['mainnet'])
+		for line in cls.coin_constants['mainnet']:
+			if reverse:
+				print('{:6} {}'.format(line[1],line[0]))
+			else:
+				print(('','{:{}} '.format(line[0],w))[include_names] + line[1])
+
+	@classmethod
+	def create_trust_table(cls):
+		tt = {}
+		mn = cls.external_tests['mainnet']
+		for ext_prog in mn:
+			assert len(set(mn[ext_prog])) == len(mn[ext_prog]),"Duplicate entry in '{}'!".format(ext_prog)
+			for coin in mn[ext_prog]:
+				if coin in tt:
+					tt[coin] += 1
+				else:
+					tt[coin] = 1
+		for k in cls.trust_override:
+			tt[k] = cls.trust_override[k]
+		return tt
+
+	trust_override = {'BTC':3,'BCH':3,'LTC':3,'DASH':1,'EMC':2}
+	external_tests = {
+		'mainnet': {
+			'pycoin': (
+				# broken: DASH - only compressed, LTC segwit old fmt
+				'BTC','LTC','VIA','FTC','DOGE','MEC','MYR','UNO',
+				'JBS','MZC','RIC','DFC','FAI','ARG','ZEC','DCR'),
+			'pyethereum': ('ETH','ETC'),
+			'zcash_mini': ('ZEC',),
+			'keyconv': ( # all supported by vanitygen-plus 'keyconv' util
+				# broken: PIVX
+	 			'42','AC','AIB','ANC','ARS','ATMOS','AUR','BLK','BQC','BTC','TEST','BTCD','CCC','CCN','CDN',
+				'CLAM','CNC','CNOTE','CON','CRW','DEEPONION','DGB','DGC','DMD','DOGED','DOGE','DOPE',
+				'DVC','EFL','EMC','EXCL','FAIR','FLOZ','FTC','GAME','GAP','GCR','GRC','GRS','GUN','HAM','HODL',
+				'IXC','JBS','LBRY','LEAF','LTC','MMC','MONA','MUE','MYRIAD','MZC','NEOS','NLG','NMC','NVC',
+				'NYAN','OK','OMC','PIGGY','PINK','PKB','PND','POT','PPC','PTC','PTS','QTUM','RBY','RDD',
+				'RIC','SCA','SDC','SKC','SPR','START','SXC','TPC','UIS','UNO','VIA','VPN','VTC','WDC','WKC',
+				'WUBS', 'XC', 'XPM', 'YAC', 'ZOOM', 'ZRC')
+		},
+		'testnet': {
+			'pycoin': {
+				# broken: DASH - only compressed { 'DASH':'tDASH' }
+				'BTC':'XTN','LTC':'XLT','VIA':'TVI','FTC':'FTX','DOGE':'XDT','DCR':'DCRT'
+				},
+			'pyethereum': {},
+			'keyconv': {}
+		}
+	}
+	external_tests_segwit_compressed = {
+		'segwit': ('BTC'),
+		'compressed': (
+		'BTC','LTC','VIA','FTC','DOGE','DASH','MEC','MYR','UNO',
+		'JBS','MZC','RIC','DFC','FAI','ARG','ZEC','DCR','ZEC'),
+	}

+ 6 - 0
mmgen/main.py

@@ -51,3 +51,9 @@ def launch(what):
 			sys.stderr.write('\nUser interrupt\n')
 		except EOFError:
 			sys.stderr.write('\nEnd of file\n')
+		except Exception as e:
+			if os.getenv('MMGEN_TRACEBACK'):
+				raise
+			else:
+				sys.stderr.write('{!r}\n'.format(e[0]))
+				sys.exit(2)

+ 3 - 6
mmgen/main_split.py

@@ -96,12 +96,11 @@ if mmids[0] == mmids[1]:
 	die(2,'Both transactions have the same output! ({})'.format(mmids[0]))
 
 from mmgen.tx import MMGenSplitTX
-from mmgen.protocol import CoinProtocol
+from mmgen.protocol import init_coin
 
 if opt.tx_fees:
 	for idx,g_coin in ((1,opt.other_coin),(0,g.coin)):
-		g.coin = g_coin
-		g.proto = CoinProtocol(g.coin,g.testnet)
+		init_coin(g_coin)
 		opt.tx_fee = opt.tx_fees.split(',')[idx]
 		opts.opt_is_tx_fee(opt.tx_fee,'transaction fee') or sys.exit(1)
 
@@ -119,9 +118,7 @@ tx1.create_fn()
 
 gmsg("\nCreating transaction for short chain ({})".format(opt.other_coin))
 
-from mmgen.protocol import CoinProtocol
-g.coin = opt.other_coin
-g.proto = CoinProtocol(g.coin,g.testnet)
+init_coin(opt.other_coin)
 reload(sys.modules['mmgen.tx'])
 
 tx2 = MMGenSplitTX()

+ 1 - 1
mmgen/main_tool.py

@@ -31,7 +31,7 @@ supported commands), use '-' as the first argument.
 cmd_help = """
 Cryptocoin address/key operations (compressed public keys supported):
   addr2hexaddr   - convert coin address from base58 to hex format
-  hex2wif        - convert a private key from hex to WIF format (use 'pubkey_type=zcash_z' for zcash-z key)
+  hex2wif        - convert a private key from hex to WIF format (use '--type=zcash_z' for zcash-z key)
   pubhash2addr   - convert public key hash to address
   privhex2addr   - generate coin address from private key in hex format
   privhex2pubhex - generate a hex public key from a hex private key

+ 1 - 1
mmgen/main_txsend.py

@@ -48,7 +48,7 @@ if not opt.status: do_license_msg()
 
 from mmgen.tx import *
 
-tx = MMGenTX(infile) # sig check performed here
+tx = MMGenTX(infile,silent_open=True) # sig check performed here
 vmsg("Signed transaction file '%s' is valid" % infile)
 
 if not tx.marked_signed():

+ 19 - 1
mmgen/opts.py

@@ -170,9 +170,23 @@ def override_from_env():
 			gname = name[idx:].lower()
 			setattr(g,gname,set_for_type(val,getattr(g,gname),name,invert_bool))
 
+def warn_altcoins(trust_level):
+	if trust_level == None: return
+	tl = (red('COMPLETELY UNTESTED'),red('LOW'),yellow('MEDIUM'),green('HIGH'))
+	m = """
+Support for coin '{}' is EXPERIMENTAL.  The {pn} project assumes no
+responsibility for any loss of funds you may incur.
+This coin's {pn} testing status: {}
+Are you sure you want to continue?
+""".strip().format(g.coin,tl[trust_level],pn=g.proj_name)
+	if os.getenv('MMGEN_TEST_SUITE'):
+		msg(m); return
+	if not keypress_confirm(m):
+		sys.exit(0)
+
 def init(opts_f,add_opts=[],opt_filter=None):
 
-	from mmgen.protocol import CoinProtocol,BitcoinProtocol
+	from mmgen.protocol import CoinProtocol,BitcoinProtocol,init_genonly_altcoins
 	g.proto = BitcoinProtocol # this must be initialized to something before opts_f is called
 
 	# most, but not all, of these set the corresponding global var
@@ -239,6 +253,8 @@ def init(opts_f,add_opts=[],opt_filter=None):
 
 	if g.regtest: g.testnet = True # These are equivalent for now
 
+	altcoin_trust_level = init_genonly_altcoins(opt.coin)
+
 	# g.testnet is set, so we can set g.proto
 	g.proto = CoinProtocol(g.coin,g.testnet)
 
@@ -300,6 +316,8 @@ def init(opts_f,add_opts=[],opt_filter=None):
 	for k in ('prog_name','desc','usage','options','notes'):
 		if k in opts_data: del opts_data[k]
 
+	warn_altcoins(altcoin_trust_level)
+
 	return args
 
 def opt_is_tx_fee(val,desc):

+ 72 - 32
mmgen/protocol.py

@@ -84,10 +84,9 @@ class BitcoinProtocol(MMGenObject):
 		(None,'','b2x',True)
 	]
 	caps = ('rbf','segwit')
-	mmcaps = ('key','addr','rpc')
+	mmcaps = ('key','addr','rpc','tx')
 	base_coin = 'BTC'
 	addr_width = 34
-	addr_hex_width = 40
 
 	@staticmethod
 	def get_protocol_by_chain(chain):
@@ -207,7 +206,7 @@ class BitcoinCashTestnetProtocol(BitcoinCashProtocol):
 	addr_width     = 35
 
 class B2XProtocol(BitcoinProtocol):
-	daemon_name    = 'bitcoind-2x'
+	daemon_name     = 'bitcoind-2x'
 	daemon_data_dir = os.path.join(os.getenv('APPDATA'),'Bitcoin_2X') if g.platform == 'win' \
 						else os.path.join(g.home_dir,'.bitcoin-2x')
 	rpc_port        = 8338
@@ -218,12 +217,12 @@ class B2XProtocol(BitcoinProtocol):
 	]
 
 class B2XTestnetProtocol(B2XProtocol):
-	addr_ver_num         = { 'p2pkh': ('6f',('m','n')), 'p2sh':  ('c4','2') }
-	wif_ver_num          = { 'std': 'ef' }
-	data_subdir          = 'testnet'
-	daemon_data_subdir   = 'testnet5'
-	rpc_port             = 18338
-	addr_width     = 35
+	addr_ver_num       = { 'p2pkh': ('6f',('m','n')), 'p2sh':  ('c4','2') }
+	wif_ver_num        = { 'std': 'ef' }
+	data_subdir        = 'testnet'
+	daemon_data_subdir = 'testnet5'
+	rpc_port           = 18338
+	addr_width         = 35
 
 class LitecoinProtocol(BitcoinProtocol):
 	block0         = '12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2'
@@ -250,9 +249,7 @@ class LitecoinTestnetProtocol(LitecoinProtocol):
 	addr_width     = 35
 
 class BitcoinProtocolAddrgen(BitcoinProtocol): mmcaps = ('key','addr')
-class BitcoinProtocolKeygen(BitcoinProtocol):  mmcaps = ('key',)
 class BitcoinTestnetProtocolAddrgen(BitcoinTestnetProtocol): mmcaps = ('key','addr')
-class BitcoinTestnetProtocolKeygen(BitcoinTestnetProtocol):  mmcaps = ('key',)
 
 class EthereumProtocol(BitcoinProtocolAddrgen):
 
@@ -299,9 +296,8 @@ class ZcashProtocol(BitcoinProtocolAddrgen):
 		'zcash_z': ('169a','zc'),
 		'viewkey': ('0b1c','V') }
 	wif_ver_num  = { 'std': '80', 'zcash_z': 'ab36' }
-	mmtypes      = ('C','Z')
-	dfl_mmtype   = 'C'
-	addr_hex_width = 40
+	mmtypes      = ('L','C','Z')
+	dfl_mmtype   = 'L'
 
 	@classmethod
 	def preprocess_key(cls,hexpriv,pubkey_type): # zero the first four bits
@@ -328,35 +324,22 @@ class ZcashTestnetProtocol(ZcashProtocol):
 		'zcash_z': ('16b6','??'),
 		'viewkey': ('0b2a','??') }
 
-class DashProtocol(BitcoinProtocolAddrgen):
-	name         = 'dash'
-	base_coin    = 'DASH'
-	addr_ver_num = { 'p2pkh': ('4c','X'), 'p2sh':  ('10','7') }
-	wif_ver_num  = { 'std': 'cc' }
-	mmtypes      = ('C',)
-	dfl_mmtype   = 'C'
-
-class DashTestnetProtocol(DashProtocol):
-	# "Dash", "testnet", "tDASH", b'\xef', b'\x8c', b'\x13'
-	addr_ver_num   = { 'p2pkh': ('8c','y'), 'p2sh':  ('13','?') }
-	wif_ver_num    = { 'std': 'ef' }
-
 class CoinProtocol(MMGenObject):
 	coins = {
 		'btc': (BitcoinProtocol,BitcoinTestnetProtocol),
 		'bch': (BitcoinCashProtocol,BitcoinCashTestnetProtocol),
-		'b2x': (B2XProtocol,B2XTestnetProtocol),
 		'ltc': (LitecoinProtocol,LitecoinTestnetProtocol),
-		'dash': (DashProtocol,DashTestnetProtocol),
-		'zec': (ZcashProtocol,ZcashTestnetProtocol),
 		'eth': (EthereumProtocol,EthereumTestnetProtocol),
 		'etc': (EthereumClassicProtocol,EthereumClassicTestnetProtocol),
+		'zec': (ZcashProtocol,ZcashTestnetProtocol),
 	}
 	def __new__(cls,coin,testnet):
 		coin = coin.lower()
 		assert type(testnet) == bool
-		m = "'{}': not a valid coin. Valid choices are '{}'"
-		assert coin in cls.coins,m.format(coin,"','".join(cls.coins))
+		from mmgen.altcoin import CoinInfo as ci
+		all_coins = sorted(set([e[1].lower() for e in ci.coin_constants['mainnet']] + cls.coins.keys()))
+		m = "'{}': not a valid coin. Valid choices are {}"
+		assert coin in cls.coins,m.format(coin,','.join(all_coins))
 		return cls.coins[coin][testnet]
 
 	@classmethod
@@ -365,3 +348,60 @@ class CoinProtocol(MMGenObject):
 			if name == proto.__name__[:-8].lower():
 				return proto.base_coin
 		return False
+
+def init_genonly_altcoins(usr_coin,trust_level=None):
+	from mmgen.altcoin import CoinInfo as ci
+	if trust_level is None:
+		if not usr_coin or usr_coin.lower() in CoinProtocol.coins: return None
+		usr_coin = usr_coin.upper()
+		mn_coins = [e[1] for e in ci.coin_constants['mainnet']]
+		if usr_coin not in mn_coins: return None
+		trust_level = ci.coin_constants['mainnet'][mn_coins.index(usr_coin)][6]
+	data = {}
+	for k in ('mainnet','testnet'):
+		data[k] = [e for e in ci.coin_constants[k] if e[6] >= trust_level]
+	exec(make_init_genonly_altcoins_str(data))
+	return trust_level
+
+def make_init_genonly_altcoins_str(data):
+
+	def make_proto(e,testnet=False):
+		tn_str = 'Testnet' if testnet else ''
+		proto,coin = '{}{}Protocol'.format(e[0],tn_str),e[1]
+		if proto[0] in '0123456789': proto = 'X_'+proto
+		if proto in globals(): return ''
+		if coin.lower() in CoinProtocol.coins: return ''
+		def num2hexstr(n):
+			return '{:0{}x}'.format(n,2 if n < 256 else 4)
+		o  = ['class {}(Bitcoin{}ProtocolAddrgen):'.format(proto,tn_str)]
+		o += ["base_coin = '{}'".format(coin)]
+		o += ["name = '{}'".format(e[0].lower())]
+		o += ["nameCaps = '{}'".format(e[0])]
+		a = "addr_ver_num = {{ 'p2pkh': ({!r},{!r})".format(num2hexstr(e[3][0]),e[3][1])
+		b = ", 'p2sh':  ({!r},{!r})".format(num2hexstr(e[4][0]),e[4][1]) if e[4] else ''
+		o += [a+b+' }']
+		o += ["wif_ver_num = {{ 'std': {!r} }}".format(num2hexstr(e[2]))]
+		o += ["mmtypes = ('L','C'{})".format(",'S'" if e[5] else '')]
+		o += ["dfl_mmtype = '{}'".format('L')]
+		return '\n\t'.join(o) + '\n'
+
+	out = ''
+	for e in data['mainnet']:
+		out += make_proto(e)
+	for e in data['testnet']:
+		out += make_proto(e,testnet=True)
+
+	tn_coins = [e[1] for e in data['testnet']]
+	fs = "CoinProtocol.coins['{}'] = ({}Protocol,{})\n"
+	for e in data['mainnet']:
+		proto,coin = e[0],e[1]
+		if proto[0] in '0123456789': proto = 'X_'+proto
+		if proto+'Protocol' in globals(): continue
+		if coin.lower() in CoinProtocol.coins: continue
+		out += fs.format(coin.lower(),proto,('None',proto+'TestnetProtocol')[coin in tn_coins])
+#	print out
+	return out
+
+def init_coin(coin):
+	g.coin = coin
+	g.proto = CoinProtocol(coin,g.testnet)

+ 2 - 2
mmgen/tool.py

@@ -225,8 +225,8 @@ def print_convert_results(indata,enc,dec,dtype):
 
 from mmgen.obj import MMGenAddrType
 at = MMGenAddrType((hasattr(opt,'type') and opt.type) or g.proto.dfl_mmtype)
-kg = KeyGenerator(at.pubkey_type)
-ag = AddrGenerator(at.gen_method)
+kg = KeyGenerator(at)
+ag = AddrGenerator(at)
 
 def Hexdump(infile,cols=8,line_nums=True):
 	Msg(pretty_hexdump(

+ 29 - 9
mmgen/tx.py

@@ -266,7 +266,7 @@ class MMGenTX(MMGenObject):
 		desc = 'transaction outputs'
 		member_type = 'MMGenTxOutput'
 
-	def __init__(self,filename=None,md_only=False,caller=None):
+	def __init__(self,filename=None,md_only=False,caller=None,silent_open=False):
 		self.inputs      = self.MMGenTxInputList()
 		self.outputs     = self.MMGenTxOutputList()
 		self.send_amt    = g.proto.coin_amt('0')  # total amt minus change
@@ -285,7 +285,7 @@ class MMGenTX(MMGenObject):
 		self.locktime    = None
 
 		if filename:
-			self.parse_tx_file(filename,md_only=md_only)
+			self.parse_tx_file(filename,md_only=md_only,silent_open=silent_open)
 			if md_only: return
 			self.check_sigs() # marks the tx as signed
 
@@ -727,22 +727,42 @@ class MMGenTX(MMGenObject):
 		ret = g.rpch.gettransaction(self.coin_txid,on_fail='silent')
 		if not 'bip125-replaceable' in ret or not 'confirmations' in ret or ret['confirmations'] > 0:
 			return False
-		return -ret['confirmations'] + 1 # 1: replacement in mempool, 2: replacement confirmed
+		return -ret['confirmations'] + 1,ret # 1: replacement in mempool, 2: replacement confirmed
 
 	def is_in_utxos(self):
 		return 'txid' in g.rpch.getrawtransaction(self.coin_txid,True,on_fail='silent')
 
 	def get_status(self,status=False):
 		if self.is_in_mempool():
-			msg(('Warning: transaction is in mempool!','Transaction is in mempool')[status])
+			if status:
+				d = g.rpch.gettransaction(self.coin_txid,on_fail='silent')
+				r = '{}replaceable'.format(('NOT ','')[d['bip125-replaceable']=='yes'])
+				t = d['timereceived']
+				m = 'Sent {} ({} h/m/s ago)'
+				b = m.format(time.strftime('%c',time.gmtime(t)),secs_to_dhms(int(time.time()-t)))
+				if opt.quiet:
+					msg('Transaction is in mempool')
+				else:
+					msg('TX status: in mempool, {}\n{}'.format(r,b))
+			else:
+				msg('Warning: transaction is in mempool!')
 		elif self.is_in_wallet():
 			confs = self.is_in_wallet()
 			die(0,'Transaction has {} confirmation{}'.format(confs,suf(confs,'s')))
 		elif self.is_in_utxos():
 			die(2,red('ERROR: transaction is in the blockchain (but not in the tracking wallet)!'))
-		ret = self.is_replaced() # 1: replacement in mempool, 2: replacement confirmed
-		if ret:
-			die(1,'Transaction has been replaced'+('',', and the replacement TX is confirmed')[ret==2]+'!')
+		else:
+			ret = self.is_replaced() # ret[0]==1: replacement in mempool, ret[0]==2: replacement confirmed
+			if ret and ret[0]:
+				m1 = 'Transaction has been replaced'
+				m2 = ('',', and the replacement TX is confirmed')[ret[0]==2]
+				msg('{}{}!'.format(m1,m2))
+				if not opt.quiet:
+					msg('Replacing transactions:')
+					rt = ret[1]['walletconflicts']
+					for t,s in [(tx,'size' in g.rpch.getmempoolentry(tx,on_fail='silent')) for tx in rt]:
+						msg('  {}{}'.format(t,('',' in mempool')[s]))
+				die(0,'')
 
 	def send(self,prompt_user=True,exit_on_fail=False):
 
@@ -946,9 +966,9 @@ class MMGenTX(MMGenObject):
 
 		return out # TX label might contain non-ascii chars
 
-	def parse_tx_file(self,infile,md_only=False):
+	def parse_tx_file(self,infile,md_only=False,silent_open=False):
 
-		tx_data = get_lines_from_file(infile,self.desc+' data')
+		tx_data = get_lines_from_file(infile,self.desc+' data',silent=silent_open)
 
 		try:
 			desc = 'data'

+ 6 - 0
mmgen/util.py

@@ -212,6 +212,12 @@ def make_timestr(secs=None):
 	tv = time.gmtime(t)[:6]
 	return '{:04d}/{:02d}/{:02d} {:02d}:{:02d}:{:02d}'.format(*tv)
 
+def secs_to_dhms(secs):
+	dsecs = secs/3600
+	return '{}{:02d}:{:02d}:{:02d}'.format(
+		('','{} day{}, '.format(dsecs/24,suf(dsecs/24)))[dsecs > 24],
+		dsecs % 24, (secs/60) % 60, secs % 60)
+
 def secs_to_hms(secs):
 	return '{:02d}:{:02d}:{:02d}'.format(secs/3600, (secs/60) % 60, secs % 60)
 

+ 12 - 6
scripts/test-release.sh

@@ -100,6 +100,7 @@ f_obj='Data object test complete'
 i_alts='Gen-only altcoin'
 s_alts='The following tests will test generation operations for all supported altcoins'
 ROUNDS=100
+ROUNDS_LOW=20
 ROUNDS_SPEC=500
 t_alts=(
 	'test/scrambletest.py'
@@ -110,7 +111,6 @@ t_alts=(
 	"test/gentest.py --coin=ltc 2 $ROUNDS"
 	"test/gentest.py --coin=ltc --type=compressed 2 $ROUNDS"
 	"test/gentest.py --coin=ltc --type=segwit 2 $ROUNDS"
-	"test/gentest.py --coin=dash 2 $ROUNDS"
 	"test/gentest.py --coin=zec 2 $ROUNDS"
 	"test/gentest.py --coin=etc 2 $ROUNDS"
 	"test/gentest.py --coin=eth 2 $ROUNDS"
@@ -122,11 +122,15 @@ t_alts=(
 	"test/gentest.py --coin=ltc 2:ext $ROUNDS"
 	"test/gentest.py --coin=ltc --type=compressed 2:ext $ROUNDS"
 #	"test/gentest.py --coin=ltc --type=segwit 2:ext $ROUNDS" # pycoin generates old-style LTC Segwit addrs
-	"test/gentest.py --coin=dash 2:ext $ROUNDS"
-	"test/gentest.py --coin=zec 2:ext $ROUNDS"
 	"test/gentest.py --coin=etc 2:ext $ROUNDS"
 	"test/gentest.py --coin=eth 2:ext $ROUNDS"
-	"test/gentest.py --coin=zec --type=zcash_z 2:ext $ROUNDS_SPEC")
+	"test/gentest.py --coin=zec 2:ext $ROUNDS"
+	"test/gentest.py --coin=zec --type=zcash_z 2:ext $ROUNDS_SPEC"
+
+	"test/gentest.py --all 2:pycoin $ROUNDS_LOW"
+	"test/gentest.py --all 2:pyethereum $ROUNDS_LOW"
+	"test/gentest.py --all 2:keyconv $ROUNDS_LOW")
+
 f_alts='Gen-only altcoin tests completed'
 
 i_misc_ni='Miscellaneous operations (non-interactive)'
@@ -164,7 +168,8 @@ i_btc_rt='Bitcoin regtest'
 s_btc_rt="The following tests will test MMGen's regtest (Bob and Alice) mode"
 t_btc_rt=(
 	'test/test.py -On regtest'
-	'test/test.py -On regtest_split')
+#	'test/test.py -On regtest_split' # no official B2X support, so skip
+	)
 f_btc_rt='Regtest (Bob and Alice) mode tests for BTC completed'
 
 i_bch='Bitcoin cash (BCH)'
@@ -211,7 +216,6 @@ s_ltc_rt="The following tests will test MMGen's regtest (Bob and Alice) mode"
 t_ltc_rt=('test/test.py --coin=ltc -On regtest')
 f_ltc_rt='Regtest (Bob and Alice) mode tests for LTC completed'
 
-# TODO: ethereum support for tooltest
 i_tool='Tooltest'
 s_tool='The following tests will run test/tooltest.py for all supported coins'
 t_tool=(
@@ -222,6 +226,8 @@ t_tool=(
 	'test/tooltest.py --coin=eth cryptocoin'
 	'test/tooltest.py --coin=etc cryptocoin'
 	'test/tooltest.py --coin=dash cryptocoin'
+	'test/tooltest.py --coin=doge cryptocoin'
+	'test/tooltest.py --coin=emc cryptocoin'
 	'test/tooltest.py --coin=zec cryptocoin'
 	'test/tooltest.py --coin=zec --type=zcash_z cryptocoin')
 f_tool='tooltest tests completed'

+ 1 - 0
scripts/traceback.py

@@ -3,6 +3,7 @@ import sys,traceback,os
 sys.path.insert(0,'.')
 
 if 'TMUX' in os.environ: del os.environ['TMUX']
+os.environ['MMGEN_TRACEBACK'] = '1'
 
 f = open('my.err','w')
 

+ 2 - 4
scripts/tx-btc2bch.py

@@ -39,16 +39,14 @@ if g.coin != 'BTC':
 
 if len(cmd_args) != 1: opts.usage()
 
-from mmgen.protocol import CoinProtocol
-
 import mmgen.tx
 tx = mmgen.tx.MMGenTX(cmd_args[0])
 
 if opt.verbose:
 	gmsg('Original transaction is in {} format'.format(g.coin))
 
-g.coin = 'BCH'
-g.proto = CoinProtocol(g.coin,g.testnet)
+from mmgen.protocol import init_coin
+init_coin('BCH')
 
 reload(sys.modules['mmgen.tx'])
 

+ 1 - 0
setup.py

@@ -109,6 +109,7 @@ setup(
 		py_modules = [
 			'mmgen.__init__',
 			'mmgen.addr',
+			'mmgen.altcoin',
 			'mmgen.protocol',
 			'mmgen.color',
 			'mmgen.common',

+ 73 - 30
test/gentest.py

@@ -24,6 +24,7 @@ import sys,os
 pn = os.path.dirname(sys.argv[0])
 os.chdir(os.path.join(pn,os.pardir))
 sys.path.__setitem__(0,os.path.abspath(os.curdir))
+os.environ['MMGEN_TEST_SUITE'] = '1'
 
 from binascii import hexlify
 
@@ -33,10 +34,11 @@ from mmgen.obj import MMGenAddrType
 
 rounds = 100
 opts_data = lambda: {
-	'desc': "Test address generation in various ways",
+	'desc': 'Test address generation in various ways',
 	'usage':'[options] [spec] [rounds | dump file]',
 	'options': """
 -h, --help       Print this help message
+-a, --all        Test all supported coins for external generator 'ext'
 --, --longhelp   Print help message for long options (common options)
 -q, --quiet      Produce quieter output
 -t, --type=t     Specify address type (valid options: 'compressed','segwit','zcash_z')
@@ -62,8 +64,11 @@ EXAMPLES:
     (compare addrs generated with secp256k1 library to {dn} wallet dump)
 
   External libraries required for the 'ext' generator:
-    + pyethereum (for ETH,ETC)          https://github.com/ethereum/pyethereum
-    + pycoin     (for all other coins)  https://github.com/richardkiss/pycoin
+    + pyethereum (for ETH,ETC)           https://github.com/ethereum/pyethereum
+    + zcash-mini (for zcash_z addresses) https://github.com/FiloSottile/zcash-mini
+    + pycoin     (for supported coins)   https://github.com/richardkiss/pycoin
+    + keyconv    (for all other coins)   https://github.com/exploitagency/vanitygen-plus
+                 ('keyconv' generates uncompressed addresses only)
 """.format(prog='gentest.py',pnm=g.proj_name,snum=rounds,dn=g.proto.daemon_name)
 }
 
@@ -78,6 +83,12 @@ addr_type = MMGenAddrType(opt.type or g.proto.dfl_mmtype)
 def pyethereum_sec2addr(sec):
 	return sec,eth.privtoaddr(sec).encode('hex')
 
+def keyconv_sec2addr(sec):
+	p = sp.Popen(['keyconv','-C',g.coin,sec.wif],stderr=sp.PIPE,stdout=sp.PIPE)
+	o = p.stdout.read().splitlines()
+#	print p.stderr.read()
+	return o[1].split()[1],o[0].split()[1]
+
 def zcash_mini_sec2addr(sec):
 	p = sp.Popen(['zcash-mini','-key','-simple'],stderr=sp.PIPE,stdin=sp.PIPE,stdout=sp.PIPE)
 	p.stdin.write(sec.wif+'\n')
@@ -85,41 +96,52 @@ def zcash_mini_sec2addr(sec):
 	return sec.wif,o[0],o[-1]
 
 def pycoin_sec2addr(sec):
-	if g.testnet: # pycoin/networks/all.py pycoin/networks/legacy_networks.py
-		coin = { 'BTC':'XTN', 'LTC':'XLT', 'DASH':'tDASH' }[g.coin]
-	else:
-		coin = g.coin
+	coin = ci.external_tests['testnet']['pycoin'][g.coin] if g.testnet else g.coin
 	key = pcku.parse_key(sec,PREFIX_TRANSFORMS,coin)
 	if key is None: die(1,"can't parse {}".format(sec))
 	o = pcku.create_output(sec,key)[0]
-#	pmsg(o)
 	suf = ('_uncompressed','')[addr_type.compressed]
 	wif = o['wif{}'.format(suf)]
 	addr = o['p2sh_segwit' if addr_type.name == 'segwit' else '{}_address{}'.format(coin,suf)]
 	return wif,addr
 
+# pycoin/networks/all.py pycoin/networks/legacy_networks.py
 def init_external_prog():
-	global b_desc,ext_lib,ext_sec2addr,sp,eth,pcku,PREFIX_TRANSFORMS
-	if addr_type.name == 'zcash_z':
+	global b,b_desc,ext_lib,ext_sec2addr,sp,eth,pcku,PREFIX_TRANSFORMS
+	def test_support(k):
+		if b == k: return True
+		if b != 'ext' and b != k: return False
+		if g.coin in ci.external_tests['mainnet'][k] and not g.testnet: return True
+		if g.coin in ci.external_tests['testnet'][k]: return True
+		return False
+	if b == 'zcash_mini' or addr_type.name == 'zcash_z':
 		import subprocess as sp
 		ext_sec2addr = zcash_mini_sec2addr
 		ext_lib = 'zcash_mini'
-	elif addr_type.name == 'ethereum':
+	elif test_support('pyethereum'):
 		try:
 			import ethereum.utils as eth
 		except:
-			die(1,"Unable to import 'pyethereum' module. Is pyethereum installed?")
+			raise ImportError,"Unable to import 'pyethereum' module. Is pyethereum installed?"
 		ext_sec2addr = pyethereum_sec2addr
 		ext_lib = 'pyethereum'
-	else:
+	elif test_support('pycoin'):
 		try:
 			import pycoin.cmds.ku as pcku
 		except:
-			die(1,"Unable to import module 'ku'. Is pycoin installed?")
+			raise ImportError,"Unable to import module 'ku'. Is pycoin installed?"
 		PREFIX_TRANSFORMS = pcku.prefix_transforms_for_network(g.coin)
 		ext_sec2addr = pycoin_sec2addr
 		ext_lib = 'pycoin'
+	elif test_support('keyconv'):
+		import subprocess as sp
+		ext_sec2addr = keyconv_sec2addr
+		ext_lib = 'keyconv'
+	else:
+		m = '{}: coin supported by MMGen but unsupported by gentest.py for {}'
+		raise ValueError,m.format(g.coin,('mainnet','testnet')[g.testnet])
 	b_desc = ext_lib
+	b = 'ext'
 
 def match_error(sec,wif,a_addr,b_addr,a,b):
 	qmsg_r(red('\nERROR: Values do not match!'))
@@ -131,6 +153,14 @@ def match_error(sec,wif,a_addr,b_addr,a,b):
 """.format(sec,wif,a_addr,b_addr,pnm=g.proj_name,a=kg_a.desc,b=b_desc).rstrip())
 
 def compare_test():
+	for k in ('segwit','compressed'):
+		if addr_type.name == k and g.coin not in ci.external_tests_segwit_compressed[k]:
+			msg('{} testing not supported for coin {}'.format(addr_type.name.capitalize(),g.coin))
+			return
+	if 'ext_lib' in globals():
+		if g.coin not in ci.external_tests[('mainnet','testnet')[g.testnet]][ext_lib]:
+			msg("Coin '{}' incompatible with external generator '{}'".format(g.coin,ext_lib))
+			return
 	m = "Comparing address generators '{}' and '{}' for coin {}"
 	last_t = time.time()
 	qmsg(green(m.format(kg_a.desc,(ext_lib if b == 'ext' else kg_b.desc),g.coin)))
@@ -142,10 +172,10 @@ def compare_test():
 		sec = PrivKey(os.urandom(32),compressed=addr_type.compressed,pubkey_type=addr_type.pubkey_type)
 		ph = kg_a.to_pubhex(sec)
 		a_addr = ag.to_addr(ph)
-		if opt.type == 'zcash_z':
+		if addr_type.name == 'zcash_z':
 			a_vk = ag.to_viewkey(ph)
 		if b == 'ext':
-			if opt.type == 'zcash_z':
+			if addr_type.name == 'zcash_z':
 				b_wif,b_addr,b_vk = ext_sec2addr(sec)
 				if b_vk != a_vk:
 					match_error(sec,sec.wif,a_vk,b_vk,a,b)
@@ -195,6 +225,7 @@ def dump_test():
 			match_error(sec,wif,a_addr,b_addr,3,a)
 	qmsg(green(('\n','')[bool(opt.verbose)] + 'OK'))
 
+from mmgen.altcoin import CoinInfo as ci
 urounds,fh = None,None
 dump = []
 if len(cmd_args) == 2:
@@ -205,7 +236,7 @@ if len(cmd_args) == 2:
 		try:
 			fh = open(cmd_args[1])
 		except:
-			die(1,"Second argument must be filename or positive integer")
+			die(1,'Second argument must be filename or positive integer')
 		else:
 			for line in fh.readlines():
 				if 'addr=' in line:
@@ -224,31 +255,43 @@ except:
 		a = int(a)
 		assert 1 <= a <= len(g.key_generators)
 	except:
-		die(1,"First argument must be one or two generator IDs, colon separated")
+		die(1,'First argument must be one or two generator IDs, colon separated')
 else:
 	try:
 		a = int(a)
-		assert 1 <= a <= len(g.key_generators)
-		if b == 'ext':
+		assert 1 <= a <= len(g.key_generators),'{}: invalid key generator'.format(a)
+		if b in ('ext','pyethereum','pycoin','keyconv','zcash_mini'):
 			init_external_prog()
 		else:
 			b = int(b)
-			assert 1 <= b <= len(g.key_generators)
-		assert a != b
-	except:
-		die(1,"%s: invalid generator IDs" % cmd_args[0])
+			assert 1 <= b <= len(g.key_generators),'{}: invalid key generator'.format(b)
+		assert a != b,'Key generators are the same!'
+	except Exception as e:
+		die(1,'{}\n{}: invalid generator argument'.format(e[0],cmd_args[0]))
 
 from mmgen.addr import KeyGenerator,AddrGenerator
 from mmgen.obj import PrivKey
 
-kg_a = KeyGenerator(addr_type.pubkey_type,a)
-ag = AddrGenerator(addr_type.gen_method)
+kg_a = KeyGenerator(addr_type,a)
+ag = AddrGenerator(addr_type)
 
 if a and b:
-	if b != 'ext':
-		kg_b = KeyGenerator(addr_type.pubkey_type,b)
-		b_desc = kg_b.desc
-	compare_test()
+	if opt.all:
+		from mmgen.protocol import init_coin,init_genonly_altcoins
+		init_genonly_altcoins('btc',trust_level=0)
+		mmgen_supported = [e[1] for e in ci.coin_constants['mainnet']]
+		for coin in ci.external_tests[('mainnet','testnet')[g.testnet]][ext_lib]:
+			if coin not in mmgen_supported and ext_lib != 'pyethereum': continue
+			init_coin(coin)
+			tmp_addr_type = addr_type if addr_type in g.proto.mmtypes else MMGenAddrType(g.proto.dfl_mmtype)
+			kg_a = KeyGenerator(tmp_addr_type,a)
+			ag = AddrGenerator(tmp_addr_type)
+			compare_test()
+	else:
+		if b != 'ext':
+			kg_b = KeyGenerator(addr_type,b)
+			b_desc = kg_b.desc
+		compare_test()
 elif a and not fh:
 	speed_test()
 elif a and dump:

+ 7 - 5
test/scrambletest.py

@@ -24,6 +24,7 @@ import sys,os,subprocess
 repo_root = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),os.pardir)))
 os.chdir(repo_root)
 sys.path.__setitem__(0,repo_root)
+os.environ['MMGEN_TEST_SUITE'] = '1'
 
 # Import this _after_ local path's been added to sys.path
 from mmgen.common import *
@@ -60,13 +61,14 @@ test_data = OrderedDict([
 ('bch',           ('456d7f5f1c4bfe3b','(none)',         '',    '',          '1MU7EdgqYy9JX35L25hR6CmXXcSEBDAwyv')),
 ('bch_compressed',('bf98a4af5464a4ef','compressed',     '-C',  'COMPRESSED','1F97Jd89wwmu4ELadesAdGDzg3d8Y6j5iP')),
 ('ltc',           ('b11f16632e63ba92','ltc:legacy',     '-LTC','LTC',       'LMxB474SVfxeYdqxNrM1WZDZMnifteSMv1')),
-('ltc_compressed',('7ccf465d466ee7d3','ltc:compressed', '-LTC-C', 'LTC:COMPRESSED', 'LdkebBKVXSs6NNoPJWGM8KciDnL8LhXXjb')),
-('ltc_segwit',    ('9460f5ba15e82768','ltc:segwit',     '-LTC-S', 'LTC:SEGWIT',     'MQrY3vEbqKMBgegXrSaR93R2HoTDE5bKrY')),
+('ltc_compressed',('7ccf465d466ee7d3','ltc:compressed', '-LTC-C','LTC:COMPRESSED', 'LdkebBKVXSs6NNoPJWGM8KciDnL8LhXXjb')),
+('ltc_segwit',    ('9460f5ba15e82768','ltc:segwit',     '-LTC-S','LTC:SEGWIT',     'MQrY3vEbqKMBgegXrSaR93R2HoTDE5bKrY')),
 ('eth',           ('213ed116869b19f2','eth',            '-ETH', 'ETH','e704b6cfd9f0edb2e6cfbd0c913438d37ede7b35')),
 ('etc',           ('909def37096f5ab8','etc',            '-ETC', 'ETC','1a6acbef8c38f52f20d04ecded2992b04d8608d7')),
-('dash',          ('bb21cf88c198ab8c','dash:compressed','-DASH-C','DASH:COMPRESSED','XsjAJvCxkxYh55ZvCZMFEv2eJUVo5xxbwi')),
-('zec',           ('637f7b8117b524ed','zec:compressed', '-ZEC-C', 'ZEC:COMPRESSED', 't1d47QeTehQye4Mms1Lmx7dPjKVoTtHXKmu')),
-('zec_zcash_z',   ('b15570d033df9b1a','zec:zcash_z',    '-ZEC-Z', 'ZEC:ZCASH_Z',    'zcLMMsnzfFYZWU4avRBnuc83yh4jTtJXbtP32uWrs3ickzu1krMU4ppZCQPTwwfE9hLnRuFDSYF8VFW13aT9eeQK8aov3Ge')),
+('dash',          ('1319d347b021f952','dash:legacy',    '-DASH','DASH','XoK491fppGNZQUUS9uEFkT6L9u8xxVFJNJ')),
+('zec',           ('0bf9b5b20af7b5a0','zec:legacy',     '-ZEC', 'ZEC', 't1URz8BHxV38v3gsaN6oHQNKC16s35R9WkY')),
+('zec_zcash_z',   ('b15570d033df9b1a','zec:zcash_z',    '-ZEC-Z','ZEC:ZCASH_Z','zcLMMsnzfFYZWU4avRBnuc83yh4jTtJXbtP32uWrs3ickzu1krMU4ppZCQPTwwfE9hLnRuFDSYF8VFW13aT9eeQK8aov3Ge')),
+('emc',           ('7e1a29853d2db875','emc:legacy',      '-EMC', 'EMC','EU4L6x2b5QUb2gRQsBAAuB8TuPEwUxCNZU')),
 ])
 
 def run_tests():

+ 6 - 5
test/test.py

@@ -791,11 +791,12 @@ for a,b in cmd_group['regtest']:
 	cmd_list['regtest'].append(a)
 	cmd_data[a] = (17,b,[[[],17]])
 
-cmd_data['info_regtest_split'] = 'regtest mode with fork and coin split',[17]
-for a,b in cmd_group['regtest_split']:
-	cmd_list['regtest_split'].append(a)
-	cmd_data[a] = (19,b,[[[],19]])
-
+# disable until B2X officially supported
+# cmd_data['info_regtest_split'] = 'regtest mode with fork and coin split',[17]
+# for a,b in cmd_group['regtest_split']:
+# 	cmd_list['regtest_split'].append(a)
+# 	cmd_data[a] = (19,b,[[[],19]])
+#
 cmd_data['info_misc'] = 'miscellaneous operations',[18]
 for a,b in cmd_group['misc']:
 	cmd_list['misc'].append(a)

+ 1 - 0
test/tooltest.py

@@ -24,6 +24,7 @@ import sys,os,subprocess
 repo_root = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),os.pardir)))
 os.chdir(repo_root)
 sys.path.__setitem__(0,repo_root)
+os.environ['MMGEN_TEST_SUITE'] = '1'
 
 # Import this _after_ local path's been added to sys.path
 from mmgen.common import *