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 8 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.
 storing, tracking, sending and receiving Bitcoins.
 
 
 The online computer is used only for tracking balances and creating and sending
 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
 All transactions are signed offline: **your seed and private keys never touch a
 network-connected device.**  The offline computer used for wallet creation,
 network-connected device.**  The offline computer used for wallet creation,
 address generation and transaction signing is typically a low-powered device
 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
 import mmgen.tx
 from mmgen.txsign import txsign
 from mmgen.txsign import txsign
-from mmgen.protocol import CoinProtocol
+from mmgen.protocol import CoinProtocol,init_coin
 
 
 if opt.stealth_led: opt.led = True
 if opt.stealth_led: opt.led = True
 
 
@@ -154,8 +154,7 @@ def do_umount():
 
 
 def sign_tx_file(txfile):
 def sign_tx_file(txfile):
 	try:
 	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'])
 		reload(sys.modules['mmgen.tx'])
 		tx = mmgen.tx.MMGenTX(txfile)
 		tx = mmgen.tx.MMGenTX(txfile)
 		rpc_init(reinit=True)
 		rpc_init(reinit=True)

+ 20 - 6
mmgen/addr.py

@@ -32,7 +32,14 @@ def sc_dmsg(desc,data):
 		Msg('sc_debug_{}: {}'.format(desc,data))
 		Msg('sc_debug_{}: {}'.format(desc,data))
 
 
 class AddrGenerator(MMGenObject):
 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 = {
 		d = {
 			'p2pkh':  AddrGeneratorP2PKH,
 			'p2pkh':  AddrGeneratorP2PKH,
 			'segwit': AddrGeneratorSegwit,
 			'segwit': AddrGeneratorSegwit,
@@ -108,7 +115,14 @@ class AddrGeneratorZcashZ(AddrGenerator):
 
 
 class KeyGenerator(MMGenObject):
 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 pubkey_type == 'std':
 			if cls.test_for_secp256k1(silent=silent) and generator != 1:
 			if cls.test_for_secp256k1(silent=silent) and generator != 1:
 				if not opt.key_generator or opt.key_generator == 2 or generator == 2:
 				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
 		has_viewkey = self.al_id.mmtype.has_viewkey
 
 
 		if self.gen_addrs:
 		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()
 		t_addrs,num,pos,out = len(addrnums),0,0,AddrListList()
 		le = self.entry_type
 		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)
 			ret.append(a)
 
 
 		if self.has_keys and keypress_confirm('Check key-to-address validity?'):
 		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)
 			llen = len(ret)
 			for n,e in enumerate(ret):
 			for n,e in enumerate(ret):
 				msg_r('\rVerifying keys %s/%s' % (n+1,llen))
 				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')
 			sys.stderr.write('\nUser interrupt\n')
 		except EOFError:
 		except EOFError:
 			sys.stderr.write('\nEnd of file\n')
 			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]))
 	die(2,'Both transactions have the same output! ({})'.format(mmids[0]))
 
 
 from mmgen.tx import MMGenSplitTX
 from mmgen.tx import MMGenSplitTX
-from mmgen.protocol import CoinProtocol
+from mmgen.protocol import init_coin
 
 
 if opt.tx_fees:
 if opt.tx_fees:
 	for idx,g_coin in ((1,opt.other_coin),(0,g.coin)):
 	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]
 		opt.tx_fee = opt.tx_fees.split(',')[idx]
 		opts.opt_is_tx_fee(opt.tx_fee,'transaction fee') or sys.exit(1)
 		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))
 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'])
 reload(sys.modules['mmgen.tx'])
 
 
 tx2 = MMGenSplitTX()
 tx2 = MMGenSplitTX()

+ 1 - 1
mmgen/main_tool.py

@@ -31,7 +31,7 @@ supported commands), use '-' as the first argument.
 cmd_help = """
 cmd_help = """
 Cryptocoin address/key operations (compressed public keys supported):
 Cryptocoin address/key operations (compressed public keys supported):
   addr2hexaddr   - convert coin address from base58 to hex format
   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
   pubhash2addr   - convert public key hash to address
   privhex2addr   - generate coin address from private key in hex format
   privhex2addr   - generate coin address from private key in hex format
   privhex2pubhex - generate a hex public key from a hex private key
   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 *
 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)
 vmsg("Signed transaction file '%s' is valid" % infile)
 
 
 if not tx.marked_signed():
 if not tx.marked_signed():

+ 19 - 1
mmgen/opts.py

@@ -170,9 +170,23 @@ def override_from_env():
 			gname = name[idx:].lower()
 			gname = name[idx:].lower()
 			setattr(g,gname,set_for_type(val,getattr(g,gname),name,invert_bool))
 			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):
 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
 	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
 	# 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
 	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.testnet is set, so we can set g.proto
 	g.proto = CoinProtocol(g.coin,g.testnet)
 	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'):
 	for k in ('prog_name','desc','usage','options','notes'):
 		if k in opts_data: del opts_data[k]
 		if k in opts_data: del opts_data[k]
 
 
+	warn_altcoins(altcoin_trust_level)
+
 	return args
 	return args
 
 
 def opt_is_tx_fee(val,desc):
 def opt_is_tx_fee(val,desc):

+ 72 - 32
mmgen/protocol.py

@@ -84,10 +84,9 @@ class BitcoinProtocol(MMGenObject):
 		(None,'','b2x',True)
 		(None,'','b2x',True)
 	]
 	]
 	caps = ('rbf','segwit')
 	caps = ('rbf','segwit')
-	mmcaps = ('key','addr','rpc')
+	mmcaps = ('key','addr','rpc','tx')
 	base_coin = 'BTC'
 	base_coin = 'BTC'
 	addr_width = 34
 	addr_width = 34
-	addr_hex_width = 40
 
 
 	@staticmethod
 	@staticmethod
 	def get_protocol_by_chain(chain):
 	def get_protocol_by_chain(chain):
@@ -207,7 +206,7 @@ class BitcoinCashTestnetProtocol(BitcoinCashProtocol):
 	addr_width     = 35
 	addr_width     = 35
 
 
 class B2XProtocol(BitcoinProtocol):
 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' \
 	daemon_data_dir = os.path.join(os.getenv('APPDATA'),'Bitcoin_2X') if g.platform == 'win' \
 						else os.path.join(g.home_dir,'.bitcoin-2x')
 						else os.path.join(g.home_dir,'.bitcoin-2x')
 	rpc_port        = 8338
 	rpc_port        = 8338
@@ -218,12 +217,12 @@ class B2XProtocol(BitcoinProtocol):
 	]
 	]
 
 
 class B2XTestnetProtocol(B2XProtocol):
 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):
 class LitecoinProtocol(BitcoinProtocol):
 	block0         = '12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2'
 	block0         = '12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2'
@@ -250,9 +249,7 @@ class LitecoinTestnetProtocol(LitecoinProtocol):
 	addr_width     = 35
 	addr_width     = 35
 
 
 class BitcoinProtocolAddrgen(BitcoinProtocol): mmcaps = ('key','addr')
 class BitcoinProtocolAddrgen(BitcoinProtocol): mmcaps = ('key','addr')
-class BitcoinProtocolKeygen(BitcoinProtocol):  mmcaps = ('key',)
 class BitcoinTestnetProtocolAddrgen(BitcoinTestnetProtocol): mmcaps = ('key','addr')
 class BitcoinTestnetProtocolAddrgen(BitcoinTestnetProtocol): mmcaps = ('key','addr')
-class BitcoinTestnetProtocolKeygen(BitcoinTestnetProtocol):  mmcaps = ('key',)
 
 
 class EthereumProtocol(BitcoinProtocolAddrgen):
 class EthereumProtocol(BitcoinProtocolAddrgen):
 
 
@@ -299,9 +296,8 @@ class ZcashProtocol(BitcoinProtocolAddrgen):
 		'zcash_z': ('169a','zc'),
 		'zcash_z': ('169a','zc'),
 		'viewkey': ('0b1c','V') }
 		'viewkey': ('0b1c','V') }
 	wif_ver_num  = { 'std': '80', 'zcash_z': 'ab36' }
 	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
 	@classmethod
 	def preprocess_key(cls,hexpriv,pubkey_type): # zero the first four bits
 	def preprocess_key(cls,hexpriv,pubkey_type): # zero the first four bits
@@ -328,35 +324,22 @@ class ZcashTestnetProtocol(ZcashProtocol):
 		'zcash_z': ('16b6','??'),
 		'zcash_z': ('16b6','??'),
 		'viewkey': ('0b2a','??') }
 		'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):
 class CoinProtocol(MMGenObject):
 	coins = {
 	coins = {
 		'btc': (BitcoinProtocol,BitcoinTestnetProtocol),
 		'btc': (BitcoinProtocol,BitcoinTestnetProtocol),
 		'bch': (BitcoinCashProtocol,BitcoinCashTestnetProtocol),
 		'bch': (BitcoinCashProtocol,BitcoinCashTestnetProtocol),
-		'b2x': (B2XProtocol,B2XTestnetProtocol),
 		'ltc': (LitecoinProtocol,LitecoinTestnetProtocol),
 		'ltc': (LitecoinProtocol,LitecoinTestnetProtocol),
-		'dash': (DashProtocol,DashTestnetProtocol),
-		'zec': (ZcashProtocol,ZcashTestnetProtocol),
 		'eth': (EthereumProtocol,EthereumTestnetProtocol),
 		'eth': (EthereumProtocol,EthereumTestnetProtocol),
 		'etc': (EthereumClassicProtocol,EthereumClassicTestnetProtocol),
 		'etc': (EthereumClassicProtocol,EthereumClassicTestnetProtocol),
+		'zec': (ZcashProtocol,ZcashTestnetProtocol),
 	}
 	}
 	def __new__(cls,coin,testnet):
 	def __new__(cls,coin,testnet):
 		coin = coin.lower()
 		coin = coin.lower()
 		assert type(testnet) == bool
 		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]
 		return cls.coins[coin][testnet]
 
 
 	@classmethod
 	@classmethod
@@ -365,3 +348,60 @@ class CoinProtocol(MMGenObject):
 			if name == proto.__name__[:-8].lower():
 			if name == proto.__name__[:-8].lower():
 				return proto.base_coin
 				return proto.base_coin
 		return False
 		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
 from mmgen.obj import MMGenAddrType
 at = MMGenAddrType((hasattr(opt,'type') and opt.type) or g.proto.dfl_mmtype)
 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):
 def Hexdump(infile,cols=8,line_nums=True):
 	Msg(pretty_hexdump(
 	Msg(pretty_hexdump(

+ 29 - 9
mmgen/tx.py

@@ -266,7 +266,7 @@ class MMGenTX(MMGenObject):
 		desc = 'transaction outputs'
 		desc = 'transaction outputs'
 		member_type = 'MMGenTxOutput'
 		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.inputs      = self.MMGenTxInputList()
 		self.outputs     = self.MMGenTxOutputList()
 		self.outputs     = self.MMGenTxOutputList()
 		self.send_amt    = g.proto.coin_amt('0')  # total amt minus change
 		self.send_amt    = g.proto.coin_amt('0')  # total amt minus change
@@ -285,7 +285,7 @@ class MMGenTX(MMGenObject):
 		self.locktime    = None
 		self.locktime    = None
 
 
 		if filename:
 		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
 			if md_only: return
 			self.check_sigs() # marks the tx as signed
 			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')
 		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:
 		if not 'bip125-replaceable' in ret or not 'confirmations' in ret or ret['confirmations'] > 0:
 			return False
 			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):
 	def is_in_utxos(self):
 		return 'txid' in g.rpch.getrawtransaction(self.coin_txid,True,on_fail='silent')
 		return 'txid' in g.rpch.getrawtransaction(self.coin_txid,True,on_fail='silent')
 
 
 	def get_status(self,status=False):
 	def get_status(self,status=False):
 		if self.is_in_mempool():
 		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():
 		elif self.is_in_wallet():
 			confs = self.is_in_wallet()
 			confs = self.is_in_wallet()
 			die(0,'Transaction has {} confirmation{}'.format(confs,suf(confs,'s')))
 			die(0,'Transaction has {} confirmation{}'.format(confs,suf(confs,'s')))
 		elif self.is_in_utxos():
 		elif self.is_in_utxos():
 			die(2,red('ERROR: transaction is in the blockchain (but not in the tracking wallet)!'))
 			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):
 	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
 		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:
 		try:
 			desc = 'data'
 			desc = 'data'

+ 6 - 0
mmgen/util.py

@@ -212,6 +212,12 @@ def make_timestr(secs=None):
 	tv = time.gmtime(t)[:6]
 	tv = time.gmtime(t)[:6]
 	return '{:04d}/{:02d}/{:02d} {:02d}:{:02d}:{:02d}'.format(*tv)
 	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):
 def secs_to_hms(secs):
 	return '{:02d}:{:02d}:{:02d}'.format(secs/3600, (secs/60) % 60, secs % 60)
 	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'
 i_alts='Gen-only altcoin'
 s_alts='The following tests will test generation operations for all supported altcoins'
 s_alts='The following tests will test generation operations for all supported altcoins'
 ROUNDS=100
 ROUNDS=100
+ROUNDS_LOW=20
 ROUNDS_SPEC=500
 ROUNDS_SPEC=500
 t_alts=(
 t_alts=(
 	'test/scrambletest.py'
 	'test/scrambletest.py'
@@ -110,7 +111,6 @@ t_alts=(
 	"test/gentest.py --coin=ltc 2 $ROUNDS"
 	"test/gentest.py --coin=ltc 2 $ROUNDS"
 	"test/gentest.py --coin=ltc --type=compressed 2 $ROUNDS"
 	"test/gentest.py --coin=ltc --type=compressed 2 $ROUNDS"
 	"test/gentest.py --coin=ltc --type=segwit 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=zec 2 $ROUNDS"
 	"test/gentest.py --coin=etc 2 $ROUNDS"
 	"test/gentest.py --coin=etc 2 $ROUNDS"
 	"test/gentest.py --coin=eth 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 2:ext $ROUNDS"
 	"test/gentest.py --coin=ltc --type=compressed 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=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=etc 2:ext $ROUNDS"
 	"test/gentest.py --coin=eth 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'
 f_alts='Gen-only altcoin tests completed'
 
 
 i_misc_ni='Miscellaneous operations (non-interactive)'
 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"
 s_btc_rt="The following tests will test MMGen's regtest (Bob and Alice) mode"
 t_btc_rt=(
 t_btc_rt=(
 	'test/test.py -On regtest'
 	'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'
 f_btc_rt='Regtest (Bob and Alice) mode tests for BTC completed'
 
 
 i_bch='Bitcoin cash (BCH)'
 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')
 t_ltc_rt=('test/test.py --coin=ltc -On regtest')
 f_ltc_rt='Regtest (Bob and Alice) mode tests for LTC completed'
 f_ltc_rt='Regtest (Bob and Alice) mode tests for LTC completed'
 
 
-# TODO: ethereum support for tooltest
 i_tool='Tooltest'
 i_tool='Tooltest'
 s_tool='The following tests will run test/tooltest.py for all supported coins'
 s_tool='The following tests will run test/tooltest.py for all supported coins'
 t_tool=(
 t_tool=(
@@ -222,6 +226,8 @@ t_tool=(
 	'test/tooltest.py --coin=eth cryptocoin'
 	'test/tooltest.py --coin=eth cryptocoin'
 	'test/tooltest.py --coin=etc cryptocoin'
 	'test/tooltest.py --coin=etc cryptocoin'
 	'test/tooltest.py --coin=dash 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 cryptocoin'
 	'test/tooltest.py --coin=zec --type=zcash_z cryptocoin')
 	'test/tooltest.py --coin=zec --type=zcash_z cryptocoin')
 f_tool='tooltest tests completed'
 f_tool='tooltest tests completed'

+ 1 - 0
scripts/traceback.py

@@ -3,6 +3,7 @@ import sys,traceback,os
 sys.path.insert(0,'.')
 sys.path.insert(0,'.')
 
 
 if 'TMUX' in os.environ: del os.environ['TMUX']
 if 'TMUX' in os.environ: del os.environ['TMUX']
+os.environ['MMGEN_TRACEBACK'] = '1'
 
 
 f = open('my.err','w')
 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()
 if len(cmd_args) != 1: opts.usage()
 
 
-from mmgen.protocol import CoinProtocol
-
 import mmgen.tx
 import mmgen.tx
 tx = mmgen.tx.MMGenTX(cmd_args[0])
 tx = mmgen.tx.MMGenTX(cmd_args[0])
 
 
 if opt.verbose:
 if opt.verbose:
 	gmsg('Original transaction is in {} format'.format(g.coin))
 	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'])
 reload(sys.modules['mmgen.tx'])
 
 

+ 1 - 0
setup.py

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

+ 73 - 30
test/gentest.py

@@ -24,6 +24,7 @@ import sys,os
 pn = os.path.dirname(sys.argv[0])
 pn = os.path.dirname(sys.argv[0])
 os.chdir(os.path.join(pn,os.pardir))
 os.chdir(os.path.join(pn,os.pardir))
 sys.path.__setitem__(0,os.path.abspath(os.curdir))
 sys.path.__setitem__(0,os.path.abspath(os.curdir))
+os.environ['MMGEN_TEST_SUITE'] = '1'
 
 
 from binascii import hexlify
 from binascii import hexlify
 
 
@@ -33,10 +34,11 @@ from mmgen.obj import MMGenAddrType
 
 
 rounds = 100
 rounds = 100
 opts_data = lambda: {
 opts_data = lambda: {
-	'desc': "Test address generation in various ways",
+	'desc': 'Test address generation in various ways',
 	'usage':'[options] [spec] [rounds | dump file]',
 	'usage':'[options] [spec] [rounds | dump file]',
 	'options': """
 	'options': """
 -h, --help       Print this help message
 -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)
 --, --longhelp   Print help message for long options (common options)
 -q, --quiet      Produce quieter output
 -q, --quiet      Produce quieter output
 -t, --type=t     Specify address type (valid options: 'compressed','segwit','zcash_z')
 -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)
     (compare addrs generated with secp256k1 library to {dn} wallet dump)
 
 
   External libraries required for the 'ext' generator:
   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)
 """.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):
 def pyethereum_sec2addr(sec):
 	return sec,eth.privtoaddr(sec).encode('hex')
 	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):
 def zcash_mini_sec2addr(sec):
 	p = sp.Popen(['zcash-mini','-key','-simple'],stderr=sp.PIPE,stdin=sp.PIPE,stdout=sp.PIPE)
 	p = sp.Popen(['zcash-mini','-key','-simple'],stderr=sp.PIPE,stdin=sp.PIPE,stdout=sp.PIPE)
 	p.stdin.write(sec.wif+'\n')
 	p.stdin.write(sec.wif+'\n')
@@ -85,41 +96,52 @@ def zcash_mini_sec2addr(sec):
 	return sec.wif,o[0],o[-1]
 	return sec.wif,o[0],o[-1]
 
 
 def pycoin_sec2addr(sec):
 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)
 	key = pcku.parse_key(sec,PREFIX_TRANSFORMS,coin)
 	if key is None: die(1,"can't parse {}".format(sec))
 	if key is None: die(1,"can't parse {}".format(sec))
 	o = pcku.create_output(sec,key)[0]
 	o = pcku.create_output(sec,key)[0]
-#	pmsg(o)
 	suf = ('_uncompressed','')[addr_type.compressed]
 	suf = ('_uncompressed','')[addr_type.compressed]
 	wif = o['wif{}'.format(suf)]
 	wif = o['wif{}'.format(suf)]
 	addr = o['p2sh_segwit' if addr_type.name == 'segwit' else '{}_address{}'.format(coin,suf)]
 	addr = o['p2sh_segwit' if addr_type.name == 'segwit' else '{}_address{}'.format(coin,suf)]
 	return wif,addr
 	return wif,addr
 
 
+# pycoin/networks/all.py pycoin/networks/legacy_networks.py
 def init_external_prog():
 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
 		import subprocess as sp
 		ext_sec2addr = zcash_mini_sec2addr
 		ext_sec2addr = zcash_mini_sec2addr
 		ext_lib = 'zcash_mini'
 		ext_lib = 'zcash_mini'
-	elif addr_type.name == 'ethereum':
+	elif test_support('pyethereum'):
 		try:
 		try:
 			import ethereum.utils as eth
 			import ethereum.utils as eth
 		except:
 		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_sec2addr = pyethereum_sec2addr
 		ext_lib = 'pyethereum'
 		ext_lib = 'pyethereum'
-	else:
+	elif test_support('pycoin'):
 		try:
 		try:
 			import pycoin.cmds.ku as pcku
 			import pycoin.cmds.ku as pcku
 		except:
 		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)
 		PREFIX_TRANSFORMS = pcku.prefix_transforms_for_network(g.coin)
 		ext_sec2addr = pycoin_sec2addr
 		ext_sec2addr = pycoin_sec2addr
 		ext_lib = 'pycoin'
 		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_desc = ext_lib
+	b = 'ext'
 
 
 def match_error(sec,wif,a_addr,b_addr,a,b):
 def match_error(sec,wif,a_addr,b_addr,a,b):
 	qmsg_r(red('\nERROR: Values do not match!'))
 	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())
 """.format(sec,wif,a_addr,b_addr,pnm=g.proj_name,a=kg_a.desc,b=b_desc).rstrip())
 
 
 def compare_test():
 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 {}"
 	m = "Comparing address generators '{}' and '{}' for coin {}"
 	last_t = time.time()
 	last_t = time.time()
 	qmsg(green(m.format(kg_a.desc,(ext_lib if b == 'ext' else kg_b.desc),g.coin)))
 	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)
 		sec = PrivKey(os.urandom(32),compressed=addr_type.compressed,pubkey_type=addr_type.pubkey_type)
 		ph = kg_a.to_pubhex(sec)
 		ph = kg_a.to_pubhex(sec)
 		a_addr = ag.to_addr(ph)
 		a_addr = ag.to_addr(ph)
-		if opt.type == 'zcash_z':
+		if addr_type.name == 'zcash_z':
 			a_vk = ag.to_viewkey(ph)
 			a_vk = ag.to_viewkey(ph)
 		if b == 'ext':
 		if b == 'ext':
-			if opt.type == 'zcash_z':
+			if addr_type.name == 'zcash_z':
 				b_wif,b_addr,b_vk = ext_sec2addr(sec)
 				b_wif,b_addr,b_vk = ext_sec2addr(sec)
 				if b_vk != a_vk:
 				if b_vk != a_vk:
 					match_error(sec,sec.wif,a_vk,b_vk,a,b)
 					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)
 			match_error(sec,wif,a_addr,b_addr,3,a)
 	qmsg(green(('\n','')[bool(opt.verbose)] + 'OK'))
 	qmsg(green(('\n','')[bool(opt.verbose)] + 'OK'))
 
 
+from mmgen.altcoin import CoinInfo as ci
 urounds,fh = None,None
 urounds,fh = None,None
 dump = []
 dump = []
 if len(cmd_args) == 2:
 if len(cmd_args) == 2:
@@ -205,7 +236,7 @@ if len(cmd_args) == 2:
 		try:
 		try:
 			fh = open(cmd_args[1])
 			fh = open(cmd_args[1])
 		except:
 		except:
-			die(1,"Second argument must be filename or positive integer")
+			die(1,'Second argument must be filename or positive integer')
 		else:
 		else:
 			for line in fh.readlines():
 			for line in fh.readlines():
 				if 'addr=' in line:
 				if 'addr=' in line:
@@ -224,31 +255,43 @@ except:
 		a = int(a)
 		a = int(a)
 		assert 1 <= a <= len(g.key_generators)
 		assert 1 <= a <= len(g.key_generators)
 	except:
 	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:
 else:
 	try:
 	try:
 		a = int(a)
 		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()
 			init_external_prog()
 		else:
 		else:
 			b = int(b)
 			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.addr import KeyGenerator,AddrGenerator
 from mmgen.obj import PrivKey
 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 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:
 elif a and not fh:
 	speed_test()
 	speed_test()
 elif a and dump:
 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)))
 repo_root = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),os.pardir)))
 os.chdir(repo_root)
 os.chdir(repo_root)
 sys.path.__setitem__(0,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
 # Import this _after_ local path's been added to sys.path
 from mmgen.common import *
 from mmgen.common import *
@@ -60,13 +61,14 @@ test_data = OrderedDict([
 ('bch',           ('456d7f5f1c4bfe3b','(none)',         '',    '',          '1MU7EdgqYy9JX35L25hR6CmXXcSEBDAwyv')),
 ('bch',           ('456d7f5f1c4bfe3b','(none)',         '',    '',          '1MU7EdgqYy9JX35L25hR6CmXXcSEBDAwyv')),
 ('bch_compressed',('bf98a4af5464a4ef','compressed',     '-C',  'COMPRESSED','1F97Jd89wwmu4ELadesAdGDzg3d8Y6j5iP')),
 ('bch_compressed',('bf98a4af5464a4ef','compressed',     '-C',  'COMPRESSED','1F97Jd89wwmu4ELadesAdGDzg3d8Y6j5iP')),
 ('ltc',           ('b11f16632e63ba92','ltc:legacy',     '-LTC','LTC',       'LMxB474SVfxeYdqxNrM1WZDZMnifteSMv1')),
 ('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')),
 ('eth',           ('213ed116869b19f2','eth',            '-ETH', 'ETH','e704b6cfd9f0edb2e6cfbd0c913438d37ede7b35')),
 ('etc',           ('909def37096f5ab8','etc',            '-ETC', 'ETC','1a6acbef8c38f52f20d04ecded2992b04d8608d7')),
 ('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():
 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_list['regtest'].append(a)
 	cmd_data[a] = (17,b,[[[],17]])
 	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]
 cmd_data['info_misc'] = 'miscellaneous operations',[18]
 for a,b in cmd_group['misc']:
 for a,b in cmd_group['misc']:
 	cmd_list['misc'].append(a)
 	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)))
 repo_root = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),os.pardir)))
 os.chdir(repo_root)
 os.chdir(repo_root)
 sys.path.__setitem__(0,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
 # Import this _after_ local path's been added to sys.path
 from mmgen.common import *
 from mmgen.common import *