Browse Source

BCH cashaddr: full support

- BCH addresses are now displayed in cashaddr format by default.  This may be
  overridden on the command line with --cashaddr=0, or in the config file by
  setting `bch_cashaddr` to false
- In tracking wallet views, the ‘h’ key toggles between legacy and cashaddr
  address display
- Transaction views display BCH addresses in both formats simultaneously
- The --usecashaddr=0 daemon option is no longer required and should be omitted

Testing:

    $ test/unit_tests.py -v cashaddr bip_hd.multicoin
    $ test/gentest.py --coin=bch -v --type=C 1 test/ref/bitcoin_cash/bchwallet.dump
    $ test/cmdtest.py -e bch_txview_cashaddr1 bch_txview_cashaddr2
    $ test/cmdtest.py --coin=bch -e txsend regtest.view autosign_automount
    $ test/cmdtest.py --coin=bch -n ref3_addr
The MMGen Project 2 months ago
parent
commit
8edc7da5a2

+ 8 - 3
mmgen/addr.py

@@ -157,9 +157,14 @@ class CoinAddr(HiliteStr, InitErrors, MMGenObject):
 		try:
 			ap = proto.decode_addr(addr)
 			assert ap, f'coin address {addr!r} could not be parsed'
-			me = str.__new__(cls, addr)
-			me.views = [addr]
-			me.view_pref = 0
+			if hasattr(ap, 'addr'):
+				me = str.__new__(cls, ap.addr)
+				me.views = ap.views
+				me.view_pref = ap.view_pref
+			else:
+				me = str.__new__(cls, addr)
+				me.views = [addr]
+				me.view_pref = 0
 			me.addr_fmt = ap.fmt
 			me.bytes = ap.bytes
 			me.ver_bytes = ap.ver_bytes

+ 3 - 1
mmgen/addrfile.py

@@ -86,7 +86,7 @@ class AddrFile(MMGenObject):
 		coin = proto.coin
 		mmtype = self.parent.al_id.mmtype
 		lbl_p2 = ':'.join(
-			([] if coin in ('BTC', 'BCH') else [coin])
+			([] if coin == 'BTC' or (coin == 'BCH' and not self.cfg.cashaddr) else [coin])
 			+ ([] if mmtype == 'E' or (mmtype == 'L' and not proto.testnet) else [mmtype.name.upper()])
 			+ ([proto.network.upper()] if proto.testnet else [])
 		)
@@ -206,6 +206,8 @@ class AddrFile(MMGenObject):
 			"""
 			label examples:
 			- Bitcoin legacy mainnet:           no label
+			- BCH legacy mainnet (no cashaddr): no label
+			- BCH legacy mainnet (cashaddr):    'BCH'
 			- Bitcoin legacy testnet:           'LEGACY:TESTNET'
 			- Bitcoin Segwit:                   'SEGWIT'
 			- Bitcoin Segwit testnet:           'SEGWIT:TESTNET'

+ 1 - 1
mmgen/addrlist.py

@@ -122,7 +122,7 @@ class AddrListIDStr(HiliteStr):
 			ret = fmt_str.format(s)
 		else:
 			proto = addrlist.proto
-			coin = 'BTC' if proto.coin == 'BCH' else proto.coin
+			coin = 'BTC' if proto.coin == 'BCH' and not addrlist.cfg.cashaddr else proto.coin
 			mmtype = addrlist.al_id.mmtype
 			ret = '{}{}{}[{}]'.format(
 				addrlist.al_id.sid,

+ 1 - 1
mmgen/bip_hd/__init__.py

@@ -136,7 +136,7 @@ def check_privkey(key_int):
 
 class BipHDConfig(Lockable):
 
-	supported_coins = ('btc', 'eth', 'doge', 'ltc')
+	supported_coins = ('btc', 'eth', 'doge', 'ltc', 'bch')
 
 	def __init__(self, base_cfg, coin, network, addr_type, from_path, no_path_checks):
 

+ 4 - 0
mmgen/cfg.py

@@ -195,6 +195,9 @@ class Config(Lockable):
 	carol        = False
 	regtest_user = ''
 
+	# altcoin:
+	cashaddr = True
+
 	# Monero:
 	monero_wallet_rpc_user     = 'monero'
 	monero_wallet_rpc_password = ''
@@ -273,6 +276,7 @@ class Config(Lockable):
 		'subseeds',
 		'testnet',
 		'usr_randchars',
+		'bch_cashaddr',
 		'bch_max_tx_fee',
 		'btc_max_tx_fee',
 		'eth_max_tx_fee',

+ 3 - 0
mmgen/data/mmgen.cfg

@@ -115,6 +115,9 @@
 ## Altcoin options ##
 #####################
 
+# Set this to false to prefer legacy BCH address format:
+# bch_cashaddr true
+
 # Set the maximum transaction fee for BCH:
 # bch_max_tx_fee 0.1
 

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-15.0.0
+15.1.dev1

+ 1 - 0
mmgen/opts.py

@@ -42,6 +42,7 @@ long_opts_data = {
 --, --accept-defaults      Accept defaults at all prompts
 --, --coin=c               Choose coin unit. Default: BTC. Current choice: {cu_dfl}
 --, --token=t              Specify an ERC20 token by address or symbol
+--, --cashaddr=0|1         Display BCH addresses in cashaddr format (default: 1)
 --, --color=0|1            Disable or enable color output (default: 1)
 --, --columns=N            Force N columns of output with certain commands
 --, --scroll               Use the curses-like scrolling interface for

+ 50 - 0
mmgen/proto/bch/params.py

@@ -12,7 +12,11 @@
 proto.bch.params: Bitcoin Cash protocol
 """
 
+from ...protocol import decoded_addr_multiview
+from ...addr import CoinAddr
 from ..btc.params import mainnet, _finfo
+from ..btc.common import b58chk_decode, b58chk_encode
+from .cashaddr import cashaddr_decode_addr, cashaddr_encode_addr, cashaddr_addr_types
 
 class mainnet(mainnet):
 	is_fork_of      = 'Bitcoin'
@@ -25,6 +29,50 @@ class mainnet(mainnet):
 	coin_amt        = 'BCHAmt'
 	max_tx_fee      = '0.1'
 	ignore_daemon_version = False
+	cashaddr_pfx    = 'bitcoincash'
+	cashaddr        = True
+
+	def decode_addr(self, addr):
+		if len(addr) >= 42: # cashaddr
+			if addr.islower():
+				pass
+			elif addr.isupper():
+				addr = addr.lower()
+			else:
+				raise ValueError(f'{addr}: address has mixed case!')
+			if ':' in addr:
+				assert addr.startswith(self.cashaddr_pfx), f'{addr}: address has invalid prefix!'
+			else:
+				addr = f'{self.cashaddr_pfx}:{addr}'
+			dec = cashaddr_decode_addr(addr)
+			ver_bytes = self.addr_fmt_to_ver_bytes[dec.addr_type]
+			return decoded_addr_multiview(
+				dec.bytes,
+				ver_bytes,
+				dec.addr_type,
+				addr,
+				[dec.payload, b58chk_encode(ver_bytes+dec.bytes)] if len(dec.bytes) == self.addr_len else
+				[dec.payload],
+				0)
+		else:
+			dec = self.decode_addr_bytes(b58chk_decode(addr))
+			enc = cashaddr_encode_addr(
+				cashaddr_addr_types[dec.fmt],
+				len(dec.bytes),
+				self.cashaddr_pfx,
+				dec.bytes)
+			return decoded_addr_multiview(*dec, enc.addr, [enc.payload, addr], 1)
+
+	def pubhash2addr(self, pubhash, addr_type):
+		return CoinAddr(
+			self,
+			cashaddr_encode_addr(
+				cashaddr_addr_types[addr_type],
+				len(pubhash),
+				self.cashaddr_pfx,
+				pubhash).addr
+				if self.cfg.cashaddr else
+			b58chk_encode(self.addr_fmt_to_ver_bytes[addr_type] + pubhash))
 
 	def pubhash2redeem_script(self,pubhash):
 		raise NotImplementedError
@@ -35,6 +83,8 @@ class mainnet(mainnet):
 class testnet(mainnet):
 	addr_ver_info  = { '6f': 'p2pkh', 'c4': 'p2sh' }
 	wif_ver_num    = { 'std': 'ef' }
+	cashaddr_pfx   = 'bchtest'
 
 class regtest(testnet):
 	halving_interval = 150
+	cashaddr_pfx     = 'bchreg'

+ 0 - 1
mmgen/proto/btc/daemon.py

@@ -79,7 +79,6 @@ class bitcoin_core_daemon(CoinDaemon):
 			['--pid='+self.pidfile,    self.use_pidfile],
 			['--daemon',               self.platform in ('linux', 'darwin') and not self.opt.no_daemonize],
 			['--fallbackfee=0.0002',   self.coin == 'BTC' and self.network == 'regtest'],
-			['--usecashaddr=0',        self.coin == 'BCH'],
 			['--deprecatedrpc=create_bdb', self.coin == 'BTC' and self.opt.bdb_wallet],
 			['--mempoolreplacement=1', self.coin == 'LTC'],
 			['--txindex=1',            self.coin == 'LTC' or self.network == 'regtest'],

+ 3 - 0
mmgen/proto/btc/tw/addresses.py

@@ -27,6 +27,9 @@ class BitcoinTwAddresses(TwAddresses,BitcoinTwRPC):
 		'Filters: show [E]mpty addrs, [u]sed addrs, all [L]abels',
 		'View/Print: pager [v]iew, [w]ide pager view, [p]rint{s}',
 		'Actions: [q]uit menu, r[e]draw, add [l]abel:']
+	prompt_fs_repl = {
+		'BCH': (1, 'Column options: toggle [D]ays/date/confs/block, cas[h]addr')
+	}
 	key_mappings = {
 		'a':'s_amt',
 		'A':'s_age',

+ 3 - 0
mmgen/proto/btc/tw/prune.py

@@ -23,6 +23,9 @@ class BitcoinTwAddressesPrune(BitcoinTwAddresses,TwAddressesPrune):
 		'Filters: show [E]mpty addrs, [U]sed addrs, all [L]abels',
 		'View/Actions: pager [v]iew, [w]ide view, r[e]draw{s}',
 		'Pruning: [q]uit pruning, [p]rune, [u]nprune, [c]lear prune list:']
+	prompt_fs_repl = {
+		'BCH': (1, 'Column options: toggle [D]ays/date/confs/block, cas[h]addr')
+	}
 	key_mappings = {
 		'a':'s_amt',
 		'A':'s_age',

+ 3 - 0
mmgen/proto/btc/tw/txhistory.py

@@ -240,6 +240,9 @@ class BitcoinTwTxHistory(TwTxHistory,BitcoinTwRPC):
 		'Column options: toggle [D]ays/date/confs/block, tx[i]d, [T]otal amt',
 		'View/Print: pager [v]iew, full pager [V]iew, [p]rint, full [P]rint{s}',
 		'Filters/Actions: show [u]nconfirmed, [q]uit menu, r[e]draw:']
+	prompt_fs_repl = {
+		'BCH': (1, 'Column options: toggle [D]ate/confs, cas[h]addr, tx[i]d, [T]otal amt')
+	}
 	key_mappings = {
 		'A':'s_age',
 		'n':'s_blockheight',

+ 3 - 0
mmgen/proto/btc/tw/unspent.py

@@ -33,6 +33,9 @@ class BitcoinTwUnspentOutputs(TwUnspentOutputs):
 		'Column options: toggle [D]ays/date/confs/block, gr[o]up, show [m]mgen addr',
 		'View options: pager [v]iew, [w]ide pager view{s}',
 		'Actions: [q]uit menu, [p]rint, r[e]draw, add [l]abel:']
+	prompt_fs_repl = {
+		'BCH': (1, 'Column options: toggle [D]ate/confs, cas[h]addr, gr[o]up, show [m]mgen addr')
+	}
 	key_mappings = {
 		't':'s_txid',
 		'a':'s_amt',

+ 14 - 1
mmgen/proto/btc/tx/info.py

@@ -89,6 +89,8 @@ class TxInfo(TxInfo):
 						get_mmid_fmt(e, is_input),
 						e.amt.fmt(iwidth=iwidth,color=True),
 						tx.dcoin )
+					if have_bch:
+						yield '{:3} [{}]\n'.format('', e.addr.hl(vp2, color=False))
 			else:
 				col1_w = len(str(len(io))) + 1
 				for n,e in enumerate(io_sorted()):
@@ -100,8 +102,12 @@ class TxInfo(TxInfo):
 						if is_input:
 							yield (n+1, 'tx,vout:', f'{e.txid.hl()},{red(str(e.vout))}')
 							yield ('',  'address:', f'{e.addr.hl(vp1)} {mmid_fmt}')
+							if have_bch:
+								yield ('', '', f'[{e.addr.hl(vp2, color=False)}]')
 						else:
 							yield (n+1, 'address:', f'{e.addr.hl(vp1)} {mmid_fmt}')
+							if have_bch:
+								yield ('', '', f'[{e.addr.hl(vp2, color=False)}]')
 						if e.comment:
 							yield ('',  'comment:', e.comment.hl())
 						yield     ('',  'amount:',  f'{e.amt.hl()} {tx.dcoin}')
@@ -112,7 +118,14 @@ class TxInfo(TxInfo):
 					yield '\n'.join('{:>{w}} {:<8} {}'.format(*d,w=col1_w) for d in gen()) + '\n\n'
 
 		tx = self.tx
-		vp1 = 0
+
+		if self.cfg._proto.coin == 'BCH':
+			have_bch = True
+			vp1 = 1 if not self.cfg.cashaddr else not self.cfg._proto.cashaddr
+			vp2 = (vp1 + 1) % 2
+		else:
+			have_bch = False
+			vp1 = 0
 
 		return (
 			'Displaying inputs and outputs in {} sort order'.format({'raw':'raw','addr':'address'}[sort])

+ 1 - 0
mmgen/protocol.py

@@ -27,6 +27,7 @@ from .objmethods import MMGenObject
 
 decoded_wif = namedtuple('decoded_wif',['sec','pubkey_type','compressed'])
 decoded_addr = namedtuple('decoded_addr', ['bytes', 'ver_bytes', 'fmt'])
+decoded_addr_multiview = namedtuple('mv_decoded_addr', ['bytes', 'ver_bytes', 'fmt', 'addr', 'views', 'view_pref'])
 parsed_addr = namedtuple('parsed_addr',['ver_bytes','data'])
 
 _finfo = namedtuple('fork_info',['height','hash','name','replayable'])

+ 8 - 0
mmgen/tw/view.py

@@ -90,6 +90,7 @@ class TwView(MMGenObject,metaclass=AsyncInit):
 	sort_key    = 'age'
 	display_hdr = ()
 	display_body = ()
+	prompt_fs_repl = {}
 	nodata_msg = '[no data for requested parameters]'
 	cols = 0
 	term_height = 0
@@ -122,6 +123,8 @@ class TwView(MMGenObject,metaclass=AsyncInit):
 	age_fmts_date_dependent = ('days','date','date_time')
 	_age_fmt = 'confs'
 
+	bch_addr_fmts = ('cashaddr', 'legacy')
+
 	age_col_params = {
 		'confs':     (7,  'Confs'),
 		'block':     (8,  'Block'),
@@ -195,7 +198,12 @@ class TwView(MMGenObject,metaclass=AsyncInit):
 			from .ctl import TwCtl
 			self.twctl = await TwCtl(cfg,proto,mode='w')
 		self.amt_keys = {'amt':'iwidth','amt2':'iwidth2'} if self.has_amt2 else {'amt':'iwidth'}
+		if repl := self.prompt_fs_repl.get(self.proto.coin):
+			self.prompt_fs_in[repl[0]] = repl[1]
 		self.prompt_fs = '\n'.join(self.prompt_fs_in)
+		if self.proto.coin == 'BCH':
+			self.key_mappings.update({'h': 'd_addr_view_pref'})
+			self.addr_view_pref = 1 if not self.cfg.cashaddr else not self.proto.cashaddr
 
 	@property
 	def age_w(self):

+ 4 - 2
test/cmdtest_py_d/ct_base.py

@@ -54,8 +54,7 @@ class CmdTestBase:
 		self.usr_rand_arg = f'-r{self.usr_rand_chars}'
 		self.tn_ext = ('','.testnet')[self.proto.testnet]
 		self.coin = self.proto.coin.lower()
-		self.bch_legacy = self.coin == 'bch'
-		self.fork = 'btc' if self.bch_legacy else self.coin
+		self.fork = 'btc' if self.coin == 'bch' and not cfg.cashaddr else self.coin
 		self.altcoin_pfx = '' if self.fork == 'btc' else f'-{self.proto.coin}'
 		if len(self.tmpdir_nums) == 1:
 			self.tmpdir_num = self.tmpdir_nums[0]
@@ -115,3 +114,6 @@ class CmdTestBase:
 
 	def noop(self):
 		return 'ok'
+
+	def _cashaddr_opt(self, val):
+		return [f'--cashaddr={val}'] if self.proto.coin == 'BCH' else []

+ 25 - 0
test/cmdtest_py_d/ct_misc.py

@@ -63,6 +63,10 @@ class CmdTestMisc(CmdTestBase):
 	passthru_opts = ('daemon_data_dir','rpc_port')
 	cmd_group = (
 		('rpc_backends',         'RPC backends'),
+		('bch_txview_legacy1',   "'mmgen-tool --coin=bch --cashaddr=0 txview terse=0'"),
+		('bch_txview_legacy2',   "'mmgen-tool --coin=bch --cashaddr=0 txview terse=1'"),
+		('bch_txview_cashaddr1', "'mmgen-tool --coin=bch --cashaddr=1 txview terse=0'"),
+		('bch_txview_cashaddr2', "'mmgen-tool --coin=bch --cashaddr=1 txview terse=1'"),
 		('xmrwallet_txview',     "'mmgen-xmrwallet' txview"),
 		('xmrwallet_txlist',     "'mmgen-xmrwallet' txlist"),
 		('coin_daemon_info',     "'examples/coin-daemon-info.py'"),
@@ -79,6 +83,27 @@ class CmdTestMisc(CmdTestBase):
 			t = self.spawn_chk('mmgen-tool',[f'--rpc-backend={b}','daemon_version'],extra_desc=f'({b})')
 		return t
 
+	def _bch_txview(self, view_pref, terse, expect):
+		if cfg.no_altcoin:
+			return 'skip'
+		tx = 'test/ref/bitcoin_cash/895108-BCH[2.65913].rawtx'
+		t = self.spawn('mmgen-tool', ['--coin=bch', f'--cashaddr={view_pref}', 'txview', tx, f'terse={terse}'])
+		#t = self.spawn('mmgen-tool', ['--coin=bch', '--longhelp'])
+		t.expect(expect)
+		return t
+
+	def bch_txview_legacy1(self):
+		return self._bch_txview(0, 0, '[qzuffa536e0eqfwz3smapckhlw9wge4p5spvx5j7h7]')
+
+	def bch_txview_legacy2(self):
+		return self._bch_txview(0, 1, '[qzuffa536e0eqfwz3smapckhlw9wge4p5spvx5j7h7]')
+
+	def bch_txview_cashaddr1(self):
+		return self._bch_txview(1, 0, '[1HpynST7vkLn8yNtdrqPfeghexZk4sdB3W]')
+
+	def bch_txview_cashaddr2(self):
+		return self._bch_txview(1, 1, '[1HpynST7vkLn8yNtdrqPfeghexZk4sdB3W]')
+
 	def xmrwallet_txview(self,op='txview'):
 		if cfg.no_altcoin:
 			return 'skip'

+ 22 - 2
test/cmdtest_py_d/ct_ref_3seed.py

@@ -214,8 +214,8 @@ class CmdTestRef3Seed(CmdTestBase,CmdTestShared):
 
 class CmdTestRef3Addr(CmdTestRef3Seed):
 	'generated reference address, key and password files for 128-, 192- and 256-bit seeds'
-	networks = ('btc', 'btc_tn', 'ltc', 'ltc_tn')
-	passthru_opts = ('coin', 'testnet')
+	networks = ('btc', 'btc_tn', 'ltc', 'ltc_tn', 'bch', 'bch_tn')
+	passthru_opts = ('coin', 'testnet', 'cashaddr')
 	tmpdir_nums = [26, 27, 28]
 	shared_deps = ['mmdat', pwfile]
 
@@ -224,6 +224,7 @@ class CmdTestRef3Addr(CmdTestRef3Seed):
 		'sids': ('FE3C6545', '1378FC64', '98831F3A'),
 		'refaddrgen_legacy_1': {
 			'btc': ('B230 7526 638F 38CB','A9DC 5A13 12CB 1317'),
+			'bch': ('026D AFE0 8C60 6CFF','B406 4937 D884 6E48'),
 			'ltc': ('2B23 5E97 848A B961','AEC3 E774 0B21 0202'),
 		},
 		'refaddrgen_segwit_1': {
@@ -236,14 +237,17 @@ class CmdTestRef3Addr(CmdTestRef3Seed):
 		},
 		'refaddrgen_compressed_1': {
 			'btc': ('95EB 8CC0 7B3B 7856','16E6 6170 154D 2202'),
+			'bch': ('C560 A343 CEAB 118E','3F56 8DC5 0383 CD78'),
 			'ltc': ('35D5 8ECA 9A42 46C3','15B3 5492 D3D3 6854'),
 		},
 		'refkeyaddrgen_legacy_1': {
 			'btc': ('CF83 32FB 8A8B 08E2','1F67 B73A FF8C 5D15'),
+			'bch': ('6909 4C64 119A 7681','7E48 5071 5E41 D1AE'),
 			'ltc': ('1896 A26C 7F14 2D01','FA0E CD4E ADAF DBF4'),
 		},
 		'refkeyaddrgen_compressed_1': {
 			'btc': ('E43A FA46 5751 720A','FDEE 8E45 1C0A 02AD'),
+			'bch': ('7068 9B37 8ABF 3E31','C688 29A5 BA4C 21B2'),
 			'ltc': ('7603 2FE3 2145 FFAD','3FE0 5A8E 5FBE FF3E'),
 		},
 		'refkeyaddrgen_segwit_1': {
@@ -265,10 +269,12 @@ class CmdTestRef3Addr(CmdTestRef3Seed):
 		'ref_hex2bip39_24_passwdgen_1': '91AF E735 A31D 72A0',
 		'refaddrgen_legacy_2': {
 			'btc': ('8C17 A5FA 0470 6E89','764C 66F9 7502 AAEA'),
+			'bch': ('8117 24B6 3FDA 6B40','E58C A8A4 C371 66AE'),
 			'ltc': ('2B77 A009 D5D0 22AD','51D1 979D 0A35 F24B'),
 		},
 		'refaddrgen_compressed_2': {
 			'btc': ('2615 8401 2E98 7ECA','A386 EE07 A356 906D'),
+			'bch': ('3364 0F9D 8355 2A53','3451 F741 0A8A FA56'),
 			'ltc': ('197C C48C 3C37 AB0F','8DDC 5FE3 BFF9 1226'),
 		},
 		'refaddrgen_segwit_2': {
@@ -281,10 +287,12 @@ class CmdTestRef3Addr(CmdTestRef3Seed):
 		},
 		'refkeyaddrgen_legacy_2': {
 			'btc': ('9648 5132 B98E 3AD9','1BD3 5A36 D51C 256D'),
+			'bch': ('C4D8 7C36 DC77 F8C2','953D 245C 8CFF AC72'),
 			'ltc': ('DBD4 FAB6 7E46 CD07','8822 3FDF FEC0 6A8C'),
 		},
 		'refkeyaddrgen_compressed_2': {
 			'btc': ('6D6D 3D35 04FD B9C3','94BF 4BCF 10B2 394B'),
+			'bch': ('3E7F C369 2AB9 BD58','0C99 14CD 5ADE 6782'),
 			'ltc': ('F5DA 9D60 6798 C4E9','7918 88DE 9096 DD7A'),
 		},
 		'refkeyaddrgen_segwit_2': {
@@ -306,10 +314,12 @@ class CmdTestRef3Addr(CmdTestRef3Seed):
 		'ref_hex2bip39_24_passwdgen_2': '0E8E 23C9 923F 7C2D',
 		'refaddrgen_legacy_3': {
 			'btc': ('6FEF 6FB9 7B13 5D91','424E 4326 CFFE 5F51'),
+			'bch': ('E580 43BB 0F96 AA93','630E 174A 8DDE 1BCE'),
 			'ltc': ('AD52 C3FE 8924 AAF0','4EBE 2E85 E969 1B30'),
 		},
 		'refaddrgen_compressed_3': {
 			'btc': ('A33C 4FDE F515 F5BC','6C48 AA57 2056 C8C8'),
+			'bch': ('E37B AF41 7997 A28C','0D5D 9A58 D6E9 92EE'),
 			'ltc': ('3FC0 8F03 C2D6 BD19','4C0A 49B6 2DD1 1BE0'),
 		},
 		'refaddrgen_segwit_3': {
@@ -322,10 +332,12 @@ class CmdTestRef3Addr(CmdTestRef3Seed):
 		},
 		'refkeyaddrgen_legacy_3': {
 			'btc': ('9F2D D781 1812 8BAD','88CC 5120 9A91 22C2'),
+			'bch': ('A0EE B039 48F4 24AE','B014 E0AB 5F87 EC64'),
 			'ltc': ('B804 978A 8796 3ED4','98B5 AC35 F334 0398'),
 		},
 		'refkeyaddrgen_compressed_3': {
 			'btc': ('420A 8EB5 A9E2 7814','F43A CB4A 81F3 F735'),
+			'bch': ('33E7 5C06 88CF 2792','6E09 FF73 B7C8 00D4'),
 			'ltc': ('8D1C 781F EB7F 44BC','05F3 5C68 FD31 FCEF'),
 		},
 		'refkeyaddrgen_segwit_3': {
@@ -379,8 +391,12 @@ class CmdTestRef3Addr(CmdTestRef3Seed):
 	def refaddrgen_compressed(self):
 		return self.call_addrgen('compressed')
 	def refaddrgen_segwit(self):
+		if cfg.coin == 'BCH':
+			return 'skip'
 		return self.call_addrgen('segwit')
 	def refaddrgen_bech32(self):
+		if cfg.coin == 'BCH':
+			return 'skip'
 		return self.call_addrgen('bech32')
 
 	def refkeyaddrgen_legacy(self):
@@ -388,8 +404,12 @@ class CmdTestRef3Addr(CmdTestRef3Seed):
 	def refkeyaddrgen_compressed(self):
 		return self.call_addrgen('compressed', 'keyaddrgen')
 	def refkeyaddrgen_segwit(self):
+		if cfg.coin == 'BCH':
+			return 'skip'
 		return self.call_addrgen('segwit', 'keyaddrgen')
 	def refkeyaddrgen_bech32(self):
+		if cfg.coin == 'BCH':
+			return 'skip'
 		return self.call_addrgen('bech32', 'keyaddrgen')
 
 	def pwgen(self, ftype, id_str, pwfmt=None, pwlen=None, extra_opts=[], stdout=False):

+ 54 - 6
test/cmdtest_py_d/ct_regtest.py

@@ -24,6 +24,8 @@ import os, json, time, re
 from decimal import Decimal
 
 from mmgen.proto.btc.regtest import MMGenRegtest
+from mmgen.proto.bch.cashaddr import b32a
+from mmgen.proto.btc.common import b58a
 from mmgen.color import yellow
 from mmgen.util import msg_r,die,gmsg,capfirst,fmt_list
 from mmgen.protocol import init_proto
@@ -394,6 +396,7 @@ class CmdTestRegtest(CmdTestBase,CmdTestShared):
 	'view': (
 		'viewing addresses and unspent outputs',
 		('alice_listaddresses_scroll',    'listaddresses (--scroll, interactive=1)'),
+		('alice_listaddresses_cashaddr',  'listaddresses (BCH cashaddr)'),
 		('alice_listaddresses_empty',     'listaddresses (no data)'),
 		('alice_listaddresses_menu',      'listaddresses (menu items)'),
 		('alice_listaddresses1',          'listaddresses'),
@@ -404,6 +407,7 @@ class CmdTestRegtest(CmdTestBase,CmdTestShared):
 		('alice_twview_days',             'twview (age_fmt=days)'),
 		('alice_twview_date',             'twview (age_fmt=date)'),
 		('alice_twview_date_time',        'twview (age_fmt=date_time)'),
+		('alice_twview_interactive_cashaddr', 'twview (interactive=1, BCH cashaddr)'),
 		('alice_txcreate_info',           'txcreate -i'),
 		('alice_txcreate_info_term',      'txcreate -i (pexpect_spawn)'),
 		('bob_send_to_alice_2addr',       'sending a TX to 2 addresses in Alice’s wallet'),
@@ -763,10 +767,10 @@ class CmdTestRegtest(CmdTestBase,CmdTestShared):
 		return self.user_bal('alice',rtBals[8])
 
 	def bob_bal1(self):
-		return self.user_bal('bob',rtFundAmt)
+		return self.user_bal('bob', rtFundAmt, self._cashaddr_opt(0))
 
 	def bob_bal2(self):
-		return self.user_bal('bob',rtBals[0])
+		return self.user_bal('bob', rtBals[0], self._cashaddr_opt(1))
 
 	def bob_bal2a(self):
 		return self.user_bal('bob',rtBals[0],args=['showempty=1','age_fmt=confs'])
@@ -827,11 +831,21 @@ class CmdTestRegtest(CmdTestBase,CmdTestShared):
 
 	def bob_twview2(self):
 		sid1 = self._get_user_subsid('bob','29L')
-		return self.user_twview('bob',chk=(sid1+':C:2','0.29'),sort='twmmid')
+		return self.user_twview(
+			'bob',
+			opts = self._cashaddr_opt(0),
+			chk  = (f'{sid1}:C:2', '0.29'),
+			sort = 'twmmid',
+			expect = rf'[{b58a}]{{8}}' if self.proto.coin == 'BCH' else None)
 
 	def bob_twview3(self):
 		sid2 = self._get_user_subsid('bob','127S')
-		return self.user_twview('bob',chk=(sid2+':C:3','0.127'),sort='amt')
+		return self.user_twview(
+			'bob',
+			opts = self._cashaddr_opt(1),
+			chk  = (f'{sid2}:C:3', '0.127'),
+			sort = 'amt',
+			expect = rf'[{b32a}]{{8}}' if self.proto.coin == 'BCH' else None)
 
 	def bob_subwallet_txcreate(self):
 		sid1 = self._get_user_subsid('bob','29L')
@@ -873,13 +887,17 @@ class CmdTestRegtest(CmdTestBase,CmdTestShared):
 
 	def bob_txhist1(self):
 		return self.user_txhist('bob',
+			opts = self._cashaddr_opt(1),
 			args = ['sort=age'],
-			expect = fr'\s1\).*\s{rtFundAmt}\s' )
+			expect = fr'\s1\).*\s{rtFundAmt}\s',
+			expect2 = rf'[{b32a}]{{8}}' if self.proto.coin == 'BCH' else None)
 
 	def bob_txhist2(self):
 		return self.user_txhist('bob',
+			opts = self._cashaddr_opt(0),
 			args = ['sort=blockheight','reverse=1','age_fmt=block'],
-			expect = fr'\s1\).*:{self.dfl_mmtype}:1\s' )
+			expect = fr'\s1\).*:{self.dfl_mmtype}:1\s',
+			expect2 = rf'[{b58a}]{{8}}' if self.proto.coin == 'BCH' else None)
 
 	def bob_txhist3(self):
 		return self.user_txhist('bob',
@@ -903,6 +921,13 @@ class CmdTestRegtest(CmdTestBase,CmdTestShared):
 		self.get_file_with_ext('out',delete_all=True)
 		t = self.spawn('mmgen-tool',
 			['--bob',f'--outdir={self.tmpdir}','txhist','age_fmt=date_time','interactive=true'] )
+		if self.proto.coin == 'BCH':
+			for expect, resp in (
+					(rf'[{b32a}]{{8}}', 'h'),
+					(rf'[{b58a}]{{8}}', 'h')
+				):
+				t.expect(expect, regex=True)
+				t.expect('draw:\b', resp, regex=True)
 		for resp in ('u','i','t','a','m','T','A','r','r','D','D','D','D','p','P','n','V'):
 			t.expect('draw:\b',resp,regex=True)
 		if t.pexpect_spawn:
@@ -1673,6 +1698,18 @@ class CmdTestRegtest(CmdTestBase,CmdTestShared):
 			t.expect(prompt, s)
 		return t
 
+	def alice_listaddresses_cashaddr(self):
+		if self.proto.coin != 'BCH':
+			return 'skip'
+		prompt = 'abel:\b'
+		expect = (
+			[rf'[{b32a}]{{8}}'],
+			[prompt, 'h'],
+			[rf'[{b58a}]{{8}}'],
+			[prompt, 'q']
+		)
+		return self._alice_listaddresses_interactive(expect=expect)
+
 	def alice_listaddresses_empty(self):
 		return self._alice_listaddresses_interactive(expect_menu='uuEq')
 
@@ -1731,6 +1768,17 @@ class CmdTestRegtest(CmdTestBase,CmdTestShared):
 			args = ['age_fmt=date_time'],
 			expect = (rtAmts[0],pat_date_time) )
 
+	def alice_twview_interactive_cashaddr(self):
+		if self.proto.coin != 'BCH':
+			return 'skip'
+		t = self.spawn('mmgen-tool', ['--alice', 'twview', 'interactive=true'])
+		prompt = 'abel:\b'
+		t.expect(rf'[{b32a}]{{8}}', regex=True)
+		t.expect(prompt, 'h')
+		t.expect(rf'[{b58a}]{{8}}', regex=True)
+		t.expect(prompt, 'q')
+		return t
+
 	def alice_txcreate_info(self,pexpect_spawn=False):
 		t = self.spawn('mmgen-txcreate',['--alice','-Bi'],pexpect_spawn=pexpect_spawn)
 		pats = (

+ 1 - 1
test/gentest.py

@@ -294,7 +294,7 @@ def do_ab_test(proto,scfg,addr_type,gen1,kg2,ag,tool,cache_data):
 		sec = PrivKey(proto,in_bytes,compressed=addr_type.compressed,pubkey_type=addr_type.pubkey_type)
 		data = kg1.gen_data(sec)
 		addr1 = ag.to_addr(data)
-		view_pref = 0
+		view_pref = 1 if proto.coin == 'BCH' else 0
 		tinfo = ( in_bytes, sec, sec.wif, type(kg1).__name__, type(kg2).__name__ if kg2 else tool.desc )
 
 		def do_msg():

+ 5 - 0
test/ref/bitcoin_cash/895108-BCH[2.65913].rawtx

@@ -0,0 +1,5 @@
+0bd373
+BCH MAINNET 895108 2.65913 20240926_071550 1000000
+0200000002eb4f3508ac2ca1ec5c2851274214aee247dfc60ae3c739bae926216f13b8ed4e0000000000ffffffffd751a1d02474628bffca80020a4f430a5ab97e52c8eb0327255bce8f406844610700000000ffffffff030859de01000000001976a9140d5ea98cdb1d5c41bbb73750c579352b63cb8b6e88aca029fb0d000000001976a914f956b8114948fb7d8b651fc23fef66e0087412f388ac63b07167010000001976a914295e2be7fb7ae4a00d8e0ed267da0e00f14eb00588ac00000000
+[{'vout': 0, 'txid': '4eedb8136f2126e9ba39c7e30ac6df47e2ae14422751285ceca12cac08354feb', 'scriptPubKey': '76a914b894f691d65f9025c28c37d0e2d7fb8ae466a1a488ac', 'comment': 'Alice\u2019s allowance', 'amt': '37.52425504', 'addr': 'bitcoincash:qzuffa536e0eqfwz3smapckhlw9wge4p5spvx5j7h7', 'confs': 574214, 'mmid': 'EB5572C5:L:1', 'sequence': 4294967295}, {'vout': 7, 'txid': '614468408fce5b252703ebc8527eb95a0a434f0a0280caff8b627424d0a151d7', 'scriptPubKey': '76a91460e025db040aaa5ed9a7a8051cb9e82f58df489588ac', 'comment': '', 'amt': '25.44058763', 'addr': 'bitcoincash:qpswqfwmqs925hke575q289eaqh43h6gj54ysy3xae', 'confs': 676627, 'sequence': 4294967295}]
+[{'addr': 'bitcoincash:qqx4a2vvmvw4csdmkum4p3tex54k8jutdctzd50x4u', 'amt': '0.31349', 'is_chg': False}, {'addr': 'bitcoincash:qru4dwq3f9y0klvtv50uy0l0vmsqsaqj7vugke5esy', 'amt': '2.34564', 'is_chg': False, 'mmid': 'EB5572C5:L:7'}, {'addr': 'bitcoincash:qq54u2l8ldawfgqd3c8dye76pcq0zn4sq5letrxcdf', 'amt': '60.30471267', 'is_chg': True, 'mmid': 'EB5572C5:L:8'}]

+ 57 - 0
test/ref/bitcoin_cash/bchwallet-testnet.dump

@@ -0,0 +1,57 @@
+# Wallet dump created by Bitcoin Cash Node v27.1.0-9f9aa5a6e
+# * Created on 2024-09-28T09:13:38Z
+# * Best block at time of backup was 0 (000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943),
+#   mined on 2011-02-02T23:16:42Z
+
+# extended private masterkey: tprv8ZgxMBicQKsPexYGMbCXgorSb94LSNHruGV8TcYGnvvAmfYhMsFgeLQNcZNGjpFNGZECug2pNiGx9CxgPfmm1MBai3ChRco9zvMhGgjCrbc
+
+cVp8wiSLfBHmFTM8ANuNQaGSYdaeGzQPTjzy35cctkD5NYpWVedV 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qqtw9k8rrz5yuuu8mpxqrrlt05nslxp3qqsswccy0t hdkeypath=m/0'/0'/636'
+cRLvPdrsqR3izskvzcz4cS8pGzLTsnMDA6Lo6vPepYhp8vEc7NnD 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qq57qrhm3r3w0cjjtr865l7qq47ed66gqqtz3dcqh2 hdkeypath=m/0'/0'/668'
+cNUKQrbW6y9usj4rn8cX1LTP4f3rDmLcouuQcex1d8UykdyNvGqN 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qp2f2fvxccmpdnkzc3h252d3gupzj9y9qqw7qa6wus hdkeypath=m/0'/0'/650'
+cRvhFvDo4DQ6xeFDyG8N6kfgff3FS2sJL2yRCYWeG9rFHHhBMqD5 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qp74u03veyxpwr0fr6mn5vamvr5gd8ufqqttu5hvz9 hdkeypath=m/0'/1'/438'
+cUxwL9tKy83GX5zUHtGbsfZK23Y4CQ6hKdqbCiMkLJpNMvRyboV2 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qrrdu293cnrdy0eyfc29pugulk339ddpqqnz5h86m4 hdkeypath=m/0'/0'/572'
+cUxUVnJ1CzoTCA4nsC1zKWJHwYYhNCGghc3iaLecnS5Yez57DTKV 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qzju897ltu2gwuy029grdesgp64dwv49qq7g4u4jrp hdkeypath=m/0'/1'/650'
+cNeXwV6a2XnvkmhxRJFjTBrZuFrmR8geii1AWzK8TyPxMd9R5yHX 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qql35gw4lcvjn7y0rtajtjvar566rpwzqqwg9v8wz6 hdkeypath=m/0'/1'/26'
+cNY9Aw3KUXbu2fVoNHJKEeyae2dcu6wkrehuUY5FtDPATaoyGqqy 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qqr52pjym9zx3q660ya989shtrs3dlk5qqjskuc2cz hdkeypath=m/0'/0'/245'
+cS8qYyAg8QGRGn1HPTpaT5yogQdhxy7EG2tJvhrNRVF4SSpevwcp 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qzycyx62gt2r9aa40zqry38mzcdplfkeqq79tkmya8 hdkeypath=m/0'/0'/59'
+cTdMWafCwAd9o5UDNQNwXfVRcAvybo98NguKyxDVCDRLrDRax3TL 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qz4rxgqy0duzf5kxhd7te53edqca9qklqqtf0jv8dc hdkeypath=m/0'/1'/575'
+cPtJLThtyGbj8QnmiuXiQhX6A4Mdb8DPyRYMYrQ4cLnKeRxXLdF6 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qzpwh9dh6j6g0juc50cl9dslcgmcs4hmqq9djlmk5j hdkeypath=m/0'/1'/425'
+cRzHF8okeRPFVMSewpAjWqfiWgjkjFxZqAEVPjiFt5ZCdyZUr3C7 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qp97umelqwpukmtaf72y083dy54lqmqdqy7yjcvjmj hdkeypath=m/0'/1'/559'
+cS99oNTaQB5RBpHDSHJEenyjmJhF9KyWDtq96J91wKHkBmbU8ANq 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qpc5qjz6lt0v5cjeg5z9h0u65sd3lpe9qydu3d30uf hdkeypath=m/0'/1'/765'
+cTkaVbTNPfYEZvVQum7vf7RWYy4hwqrg4BD1cnk2TE8fSe5LDiex 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qqqdmv4u2l8g3vttakq8a0vdpgw0v2fsqyutg60vpd hdkeypath=m/0'/1'/29'
+cNZqJhwScX3TrAF4pMcZnzku9QoLmfbamuxCVMVmZNJkubRyv4ht 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qz7vmyvj805g7ahnmrz623qg9t49jh6mqy8xhz29az hdkeypath=m/0'/1'/491'
+cV5FaqsEYyYWokVNRMGgoweUneU8AJhVJ8F8PrSc5XsL3g8fUT4A 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qz0u3kgqq5xrkj8zch8v28jypdvtaur5qyjfv33a4r hdkeypath=m/0'/1'/190'
+cUyaVudNPss23dKjJUfs4cqKDnvh1taigAhtHDRXone75VWmMbMN 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qp9mkm0u9jcuf8yzmntk497d8vccfw5hqy40lxngtk hdkeypath=m/0'/0'/978'
+cNLhJ4nomtwsvDKoFXGDondDZJFgfF2e8XTxDPERs9zP9NPqUDKg 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qpx96famcmfr7g7370qufkh58ffmwp4nqylaj6tsw2 hdkeypath=m/0'/0'/564'
+cR3JBZBk7RtbEwt3VMKqQimWZXKyhuidU5pwpLoY2GGNLphSnrsP 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qp5recsz4w0lsqxkudzjwe4syhqqpr8xqyx06hqhdt hdkeypath=m/0'/1'/329'
+cRkrg9gFU8e51RBW8ZXZYoEoktWRYLV3RZFL5aEdpfk6wkparAHD 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qqk48mrkq8hjzysdz2y53vs6u4ulkt8aqy7he5yr9x hdkeypath=m/0'/0'/569'
+cMzNpPeeXtYTVswwULSF6AAoa2nUBHb6myufP7AF4hoovMU4RAZh 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qr5fceuvff9khx5304aqkvxhzwmemngpqgcykgc7ts hdkeypath=m/0'/1'/495'
+cSJ4aztJyTfzsR2yRhQXBtYm6hoANLQHmV1fRodnepWaNps7N5Fy 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qzveklahtds9u4p0pt04f3vz4gznsccdqgqd9zn7gy hdkeypath=m/0'/1'/583'
+cTrDo25RNhpGw4j7Kn8oTVfYioJ7kVKPt52Vu8mbWQA7LZfQ9uTY 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qrd4ctzd9lfqksyxyh4rk3ydvs9ncfs4qgln04njqf hdkeypath=m/0'/0'/985'
+cRxbuanYahFoZxykkFJPCYpHLkhzGznmcDiE3pjJAbaALDy7t4iX 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qzg4jh3lakadpm0vmenms59javvevcfmqgtprztuje hdkeypath=m/0'/1'/48'
+cQcnMRahdsvAEUZo6GGMhCi3KJdhZiTE7PLjvoZFSWUHDZSZVpHz 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qzxmlct4rjemuw7l3mecc7lehkt8hxzjqg0ry8h6z8 hdkeypath=m/0'/1'/635'
+cV9hcAoazBJTRuDoBhDfMgrb5b484g2UechpaCCbbLhEvxktGVit 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qq95ljjemaf528krs6xgja9zpzd6656eqgg6f7atfa hdkeypath=m/0'/1'/387'
+cNc5Y7fwUFumaWxnq63FGACVK2Uy5aUCMCeqzEGEzLFBaU4oqLxW 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qrhqfqsetnxmc9ajwsfjwluefqy6uytsqgahzal7wx hdkeypath=m/0'/0'/353'
+cTmGh4sv7gDJUDe8PBsZQ77KoKpEZrpz9MTM6mxeTgSC5ikYbLNK 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qqeqttck033lrgac0smvnq44cryfqw5fqgfws59ujw hdkeypath=m/0'/0'/384'
+cUNh8FEFYuJYEb2dfRst1SwMPonbBbUxy5QGP2ednbxm9KahzY67 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qrspfwz5mwlgc55vxmjls2xy5yd3tkvwqga5xut3sc hdkeypath=m/0'/0'/493'
+cVQeD6M16HKPsR5BZdiKHk576cxoT1emU43w7R8XDQkeCi2oGg5y 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qzeq0wr07r75mkygukase2ye7dxg42asqgwhh8uhpa hdkeypath=m/0'/1'/442'
+cVCah2gzytEK4iBXVJLeTNHmvmGzLNqFLrR5dR1XtstMsCgwyCgC 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qz8cavad8ujsj5p8mnuycg6efcsupxlpqg0wd35j2e hdkeypath=m/0'/1'/687'
+cUFejVszCwodxV3B3ppB9CSbBc6yJ9e9RjGR4aK6Z5LPXnbdCEv2 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qrm9hrvw2stpnlkl0f6rje75ltv4ftpzqvvgawxhcd hdkeypath=m/0'/1'/469'
+cQEYTwzo22pEwE7T1zWATsL4eZ7gYYanQR3XxdLdo8RGpqd1KSeZ 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qqtgazx0dcyypeaxy50x00fve5y3zkeeqvrl76ku5k hdkeypath=m/0'/1'/910'
+cRoFNNQYRm5V72cokeAjoWUYuCjieZTjTvd6743m8cmYXazSp22m 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qzenr7dmrt68tcjm7daawn6srkcy76jtqvn92m6de2 hdkeypath=m/0'/1'/287'
+cSK2tKxkNZQojqtohor5yL7dSb4edeuG5qMh8JzUJeLZvN8cxukt 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qq2w0d3vw5202hse28p0la9rvcne5j24qv6t4hj58d hdkeypath=m/0'/0'/555'
+cVPsuH8v4PpLJieDFMYyXuRAVmFkVmRTHBGxEeGELo4QMPz8bYyZ 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qp8nn4zyy5v8su6v3c6sf2hq0nrxeqzuqvqs4h5xam hdkeypath=m/0'/0'/858'
+cPGthbqMbX6kCUXG72kZfYWZX1cwWhLUaRvKj1LeNTZTEQkEQKez 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qzy7flccw4g3nhh4mksxwwuz2smx85naqvcealz8pc hdkeypath=m/0'/0'/89'
+cV76D5Z8UxuskGaerD5VYR6oies1HUHDvq8eLuQ9dnEnrupiHesN 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qqeug07w2n6gjrnzflr8dp2x4ku3794dqv46y80v4e hdkeypath=m/0'/1'/849'
+cPi5RxfS2HHMWMt3YnY5x2JnLLis5wtYoX7uf8swYb2DaC93ja83 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qzn0nkz9klkctg3s08zgf7xwexung27qqv33s57lfa hdkeypath=m/0'/0'/445'
+cTmE2XrXjay4pN6rSQqGdQ61RfH71716i9Gr41XRE1H5am998HWT 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qq9vkfzflckgrzndj5um4xnxtzkwl5wtqvrurey9a6 hdkeypath=m/0'/1'/97'
+cQLSaL6A4taGdsWLGWQK2RrAjsRHE3iKQ9bQ4sNFFFJaPqeSzEdW 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qrje963zup3k4w0usvfcfm6xwm6rmah3qvp3y5p5f2 hdkeypath=m/0'/1'/807'
+cUwbXhFJzLvDM6cahsmjteUSPB9R86pNjRWbGiidcdnHQtZcjffS 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qpzqpt9nnqvrpzp4kydxrl87jq5qv2glqs3lty3jqv hdkeypath=m/0'/0'/76'
+cNnhDbhrvxMsmr8NQwuBubg2LqtdmyVF1cZCCUNZXtkQvCFzeisB 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qql8htcgd7fmk94q443jptzn0ulv06zqqsl0k7pxlh hdkeypath=m/0'/0'/51'
+cUqbctTgaD19iZ8FJjKMGr5YdqCXs3ti3KHqtZNPNryVaa9EA2RD 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qzz6rhxw92daxjawff92c2ghxfjjwkmqqszz0s68te hdkeypath=m/0'/0'/26'
+cQXB8bGdcPjyNVnrPwUKCdJcx4yNJoYauAGE4pZvcB8ZrAe1Z8ZT 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qqex9m5ty059pgqwryfffm9dzuapfg4rqskq3hqm0s hdkeypath=m/0'/1'/896'
+cNReXczzABpyPxPBAPK1ddwwV1aoy2xnqeMt9mXGH7EpqEiiisqm 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qr6gf7vx08rq0uchl0wwsh0jlnp9l6ayqsds94yuqd hdkeypath=m/0'/1'/712'
+cTZ1XaNjpTnr5dgCGAkeV4NZCxZhoYwRrJoenmAqbgWC2yzA1vLi 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qrhyd89258ultrjf3hsk72lg3n7wx3lxqsrvpgx04e hdkeypath=m/0'/1'/224'
+cSnbnKjTEinAxVMk4i7qDzAeC7duseKvVLninzCiVskSVjwSmAv4 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qp05qf87mkfgxs0hpgvgdchtj3zqex8wqs8h8954e9 hdkeypath=m/0'/1'/717'
+cVLJtk7kFFCAfEvj3ULNr59ogFhprhY4LmBSaKyepDtssuBV4aoj 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qz6pxp99qtvewl6zxa90268y6nwx3mhhqsfkf4d7ms hdkeypath=m/0'/0'/779'
+cTrScwV6E1uRtrcNufcDPJyord3AXgGiAz7HHPk88d5AFD6WVbUx 2024-09-28T09:13:20Z reserve=1 # addr=bchtest:qqeyp4gqhd3thj235xw8rkjxyg2hh7leqs55paumr5 hdkeypath=m/0'/1'/565'

+ 57 - 0
test/ref/bitcoin_cash/bchwallet.dump

@@ -0,0 +1,57 @@
+# Wallet dump created by Bitcoin Cash Node v27.1.0-9f9aa5a6e
+# * Created on 2024-09-28T09:01:27Z
+# * Best block at time of backup was 0 (000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f),
+#   mined on 2009-01-03T18:15:05Z
+
+# extended private masterkey: xprv9s21ZrQH143K3rtmqd4EARwck2w9PMoozEdPXdN3q7G34xYWJUi6f78acEx3go1648yunqrmRzcbjQDYuFTPAXnG1CUjqc7LZnkwMNg3QBG
+
+KzVH2yXbj8oKGPiBqqwze9CyfkE5bsCUVCt6hd8nVxwZg7MCjaVh 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qryzwf3zl5vl4txc4v03yuzymenfrfwmqqye26zhtv hdkeypath=m/0'/0'/13'
+KzF25zJ1SEZrWFbc1V7GnCrXWgUFCHXirpi5UDNZ83PTd524Qz9B 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qqw6uwxf096nmgfucnqjdlgnhu9jxyswqyuejq9cac hdkeypath=m/0'/0'/9'
+L4t5RMzGSUps2pVtFS2gfx1W9hTaLXKkLfdsowt6de3NUvd9g6kv 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qzjz6zrkjxcm0t7xv2p6d5ylrvnrj5x4pqrxczzt6d hdkeypath=m/0'/0'/3'
+L3WHKc7JY9QtnPQmrmk6w8rbMugBpy2ydNhzYpgoG6vxzTofxTAQ 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qz4vyfzutpp53nfnzqtwasavh9zvr45r9vequueu05 hdkeypath=m/0'/0'/4'
+L3C7osemZLh9dzUmtBpL3xz2zgkFuY1wt5ykFAevZigudUsSjHDd 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qz7uunmyx67nvujz9qg59yh2dedv4q6v9sddgmywe7 hdkeypath=m/0'/0'/2'
+L1ua9H1EJq6bGTgrj4SjSipEBMPhM4naE2udaGZxeYyXxmgEDWSG 2024-09-28T09:00:51Z hdseed=1 # addr=bitcoincash:qzaatxgp2p6dqjppu43jxwpsllyh94s6xy4dgksysv hdkeypath=s
+L2PZUxH9n9J6dLu8MUvKzpngcg1EJLQy9QfWfFtBj5yLQb1Uqr5L 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qpy40kh7nkfd9heuc5f0fy2yq9s400quxv8y4xntu6 hdkeypath=m/0'/0'/21'
+L3cPRuFrvewas5WyAksyYnNmjBJzkEsFtrTqKKJKEz5VpfW2FPcW 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qpneya8njpm6n6mfsrcj3s9evr9k67jyxs5gfrgzcj hdkeypath=m/0'/0'/17'
+KxRmkfp4cJy4inmxy7sPVjrqN6F8d2pxJoZf7rBfyYc4KK7wokdh 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qqa4ezt3gzf3j7uqcjgudntpwvlxvp5d8vjqav3rht hdkeypath=m/0'/0'/20'
+KzM9HmLtuD8skinau2eUtkMeaqjokiTXVgrYPzCw7ySJfSa6cQsb 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qz9aw6gv4d69v8lmd7srn2gcwda2t0a085w8xunskq hdkeypath=m/0'/0'/6'
+L2Xze59SbhtNUwv1JwUMzzA5RQB4dw3j8FZZdrt9QC7ErSB5w4L1 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qpmx2se4glcfc83mtm4eglthhyqm7ee52ss72alnmy hdkeypath=m/0'/0'/19'
+KwdDU4G4rppt773ois2BULKGP8Lr6PoBVtWjfMGYD8WJAbq1Fk67 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qrq6cm6cml3xyn63c7lgdncqszqka7a0tu4zlkfynh hdkeypath=m/0'/0'/0'
+Kxfb92ePatCnDcjpJhdtqvyWfE63KWNtqiNpbmzuecFQRvPB2giB 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qzuq9zummldyc5xjxsqgauprm2rqqjmqvvy9jcflq3 hdkeypath=m/0'/0'/12'
+L1FpyeQFFmk1YkFC4FU4WhTjDETi9TRYDxVqZC48phUigfJnCGLf 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qq2vn4qf04pwjrfddn8zwx0afqv9u674dul0p9tqyz hdkeypath=m/0'/0'/7'
+KxeABzNJ32G81B2KYis3QgCmPH2LmgrvsLKUyTbFTa9TBBpMXpGU 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qpm786fm57k3qr9m2yrpj33exgvedrha0ua9pk5dfz hdkeypath=m/0'/0'/8'
+KwfUszgfu1SLJAydoHDz6HYCsj5x7yeLUfEkWkRBk6pHHQmdvdNT 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qzz2gms7w3g0u2jqrgwqf00pnpw4xv5w3g68pn0lwl hdkeypath=m/0'/0'/10'
+KyFEkJhebt7ZnzaLTGBokkeKECj3eUHSMLVz16LmQiKE9YkYjnDx 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qzudzz8um9thpe3n6sx6y2s83g3f5gffnvzvydmrd2 hdkeypath=m/0'/0'/15'
+KzjQPk5xVmsqfTai9Qnwsw8PrUxSjoLT9TqXi2iXrCHAPqFEN9KL 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qz2wp0tsd2222hf2nw23rhvjp8cgyext4gnuruujts hdkeypath=m/0'/0'/16'
+KwrEu8kc7FWbAJnfpGniYVvLkCyQXdsZANtYmfXEzRnTUoNTFkL5 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qr5d0gr79c5gcy6f46x3f997aw98ttaxkszemm2sc5 hdkeypath=m/0'/0'/5'
+L1QrY5tecZ6pQ7FjaUG3ZFWPy55qm7JRjivaPzp7tUHorLVpxsts 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qzf8e846e070d4ms6rkrrcwfhfvqgzvhhvjemgw90y hdkeypath=m/0'/0'/14'
+L4fjz4VZL8MyhSXXnmqCaPpJ1gpDJSWVkEj9JwGqHNh3ftFt7fSK 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qq5j965zc0ynlyx7gu8a3ay6ynwpgjcwmuge4nx9d7 hdkeypath=m/0'/0'/11'
+Kx5doQ9MZZNNjdaR9AjV18SFojQf3dDyvqR8xgu5f99kaPFrxfih 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qqgjaznt55w8p6445jvlp3l99vpd58pqugvgdw54m6 hdkeypath=m/0'/0'/18'
+Ky4UDQL9uJn9BsgqhEpyZzCwKM4S3VZbjj7MizaZv4qoktSco4SK 2024-09-28T09:00:51Z reserve=1 # addr=bitcoincash:qqq5jwnp0lhszkly4href262wnl3e8dxaqx67edhwj hdkeypath=m/0'/0'/1'
+L4Pg3BFXMD4GxfsDujY56is1GDt7TuFUzq9D8EpzmZAMoxG5VGe1 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qzq2l67xz9zver23lyhqdn6j4fxa0v32qqgy7tuxpx hdkeypath=m/0'/1'/392'
+KzmaLSVCL5RythXGPUw4uyJQDS2j5qXxcLUxWkQbDsRpKTiMyr9h 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qqt30vesj72xegyxhhu5vph9s474gjt9qq0vcxvp8k hdkeypath=m/0'/1'/217'
+KxNiUoG6xoWn1dBqDW6QU5n1hSD1rdPNPPfv8BqnMsdPDViYtDMG 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qzmrdx6m0lmn5qxcg2p7yghkndwtusunqqzhy0a937 hdkeypath=m/0'/0'/991'
+Kwf7frjVxR8geZwMGr4YY4rd3urTyThTQmPwmzZHsRmJYopuVEdR 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qzjp3udaawrwzjpp0ha3r2u8v0cpcmvaqqqgrhvhvp hdkeypath=m/0'/1'/146'
+L3RXLas6ZmBKENVQB9PCgFqZqvpCaJH8yc5SD29e3ysfViaodV2K 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qqa267qf9a07z6la89hvr6dppyckzfkwqq20wrhypu hdkeypath=m/0'/0'/965'
+L1Uyd6thQJnRbFr9ytWPJz1ogZLZNdhfTgmpDt16FwANedb7oteS 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qzkxc8c5ull9qc485yyjzh06tcmkw4l7qq9seqpqy7 hdkeypath=m/0'/0'/963'
+L2LpY1dUY5VL9bDqc11zLzCSJKEmfkAZNaJRwjrbk2HJFKkM2fmr 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qzvrcr3f46czytk3wr3runlqwznv9p3zqy9lzjvetm hdkeypath=m/0'/1'/927'
+KweRCR3RkpfhZvRRMFBpq16z3XpB29N6UguN8VWjD4PQr96kmqVP 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qzllfzpwshvhjxe98y7vq3sm0ulz42peqy2ngg2xpz hdkeypath=m/0'/0'/268'
+L3GYWNvrReSpL6ED6bmN6NmVoPrJeQg2363BBZNAbfJXE8ofTcjP 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qzgtmde25a7q29tpu4jf38dvkcujw4j2qyh0l62alz hdkeypath=m/0'/0'/332'
+L3VwEfgMaWViYkY2xdvZknGMNHSWbfqjrokpzX5KAL2w347Tahho 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qzvqr39nmtylzhzpf7cjjxzgjv5syx6dqyl38cuyt3 hdkeypath=m/0'/0'/884'
+KydYvd86K9d88CdxWbTNrM3gnoos4jvKENfgiDtPraKiEYmv4MNX 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qpwssgatqta6nx5ru58r20w8qcrzaxrjqyvvz5q7n5 hdkeypath=m/0'/0'/876'
+L2Ka61jjXQvNKFsZXJyB6YhYcSeSSpKqPy6zW5ABVXftxT9Bqjwu 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qraclt7lpx6um64s3ruyqtznt0hhn6vnqyyjf04z6y hdkeypath=m/0'/1'/260'
+L2Bko5eZ4nSSowtZZuUUZHTQEkr5sHvzjGprkG9QwGqpf7AUhHV3 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qr78tq3cgdv5h0ywqep89fyhy34l9t4dqyc5dcenvm hdkeypath=m/0'/1'/865'
+L45JdFaTJe4TagqcsMCo7WHfKHyitKDG6eC5BoeiVF7yqfU4BG44 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qpzu3sq2ljmvmwr0rjdxjg0xal26yyh2qynadya0r5 hdkeypath=m/0'/0'/704'
+L4ZfyviTt2Js2nH2X6ogGNoLnoauma9VfZYioga9f5iJzp1yKBsj 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qqff4nfsr7zwt3rw79u75gtnr72velcxqgn30n9dn9 hdkeypath=m/0'/1'/745'
+Kz3oWqWx8p4bYT11rQG6GjADNC5ykuUtpRb7pBWQogQDGRYBQzyc 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qz85l6lfl7d7qzg9c3nmtg958kvfzqsgqgvv2pr0zs hdkeypath=m/0'/0'/456'
+L5SZrXNW8Z9yvYdUkruhBNwcKEjJVHXT5zH78HXDYGCjM1HDoPRC 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qpcp3jvcxu89a5lxmdkns29ejxxn53fgqgv0l9ksna hdkeypath=m/0'/1'/515'
+KzLmpFiekUUpwchKLw14m1nDsx5UVvzvEwU6PY7qCb8bFFdfgVue 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qzv0v5wq7vqxeq9fuz5rm8f7azu2g43wqgvrlkxzyf hdkeypath=m/0'/1'/885'
+L4g6VoesU45xfbD1dpjKn4bSEQz26dumPD4vsTc4FFAyhphwxnbh 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qzp565dxv8efvmc3uh4z4h0ylds0cn36qgegl9fm5p hdkeypath=m/0'/0'/741'
+Kxwbnh2SjK5yoZwPTZ69VP4J6fnTEYPFtPbys9S7xU8xFxq512Zm 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qqea3z09daw8tj25w47npuwx5lsp9njaqgwddrunhc hdkeypath=m/0'/1'/246'
+L15Eg9gUugCAZvMRUi3282xb8Q9J7d5tiVawtJejZieHX457jgdk 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qrt7ej2m2hzu5yshuutm2pka6l8n0nnxqgqx92plfm hdkeypath=m/0'/1'/801'
+L2K5S297cfQnEx4tkaSS1xan6N5HVoH5TARavLEgdQbcPhhPbEjT 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qqe2kanrhh720y00uk6c5w63rd43gr4hqgwkgrrn33 hdkeypath=m/0'/1'/737'
+KyXyudwnHEDFvE1mVFY5m175Xw45BUF5bLMxJ1Xi5fHCqMoQ6kbk 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qqc0g42hx2czecul7u94ke7fh3mvh3ggqv3mrq8suu hdkeypath=m/0'/0'/518'
+KyXJuaTB4JwhHFvp6pHHFhNUv5JHfjTc1CJpfX1SCvZqEkW6Mg4f 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qr9kx4a6puadxwtm3vjqtf5hkjaa56qwqv7nqnczgm hdkeypath=m/0'/1'/39'
+KzcozomWBQgkGq1FB94UBqdBQeAaWHj6bP9wLwUVu7Vh2WiorTqF 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qp0p9t0075uf56nm2t2x2gc4xd7909ceqvccy6gsdp hdkeypath=m/0'/0'/376'
+KwS6Ln2E3bFqYNW1y7AAfWXNSuJdTmksmi1a3nt4TULmRqg44rMC 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qpj7z3vx762z0m2cnf92xtc8yw5e65ftqv99x2u3nv hdkeypath=m/0'/1'/628'
+L4BGf9Ri2ELjNgAyLipKX3j2vLst7t3jSK7XBhmk5cf2Z8SxAMEk 2024-09-28T09:00:52Z reserve=1 # addr=bitcoincash:qpmhuh97dpa00c2ha406fh7kcu6k68f0qvqnkj6d7c hdkeypath=m/0'/0'/892'

+ 2 - 1
test/scrambletest.py

@@ -142,7 +142,8 @@ def make_coin_test_data():
 		coin, mmtype = id_str.split('_', 1) if '_' in id_str else (id_str, None)
 		opts = list_gen(
 			[f'--coin={coin}'],
-			[f'--type={mmtype}', mmtype]
+			[f'--type={mmtype}', mmtype],
+			[ '--cashaddr=0', coin == 'bch']
 		)
 		yield ('mmgen-addrgen', opts, [], [], test_data, 'address')
 

+ 11 - 2
test/test-release.d/cfg.sh

@@ -197,10 +197,16 @@ init_tests() {
 	"
 
 	d_bch="overall operations with emulated RPC data (Bitcoin Cash Node)"
-	t_bch="- $cmdtest_py --coin=bch --exclude regtest"
+	t_bch="
+		- $cmdtest_py --coin=bch --exclude regtest
+		- $cmdtest_py --coin=bch --cashaddr=0 ref3_addr
+	"
 
 	d_bch_tn="overall operations with emulated RPC data (Bitcoin Cash Node testnet)"
-	t_bch_tn="- $cmdtest_py --coin=bch --testnet=1"
+	t_bch_tn="
+		- $cmdtest_py --coin=bch --testnet=1
+		- $cmdtest_py --coin=bch --testnet=1 --cashaddr=0 ref3_addr
+	"
 
 	d_bch_rt="overall operations using the regtest network (Bitcoin Cash Node)"
 	t_bch_rt="- $cmdtest_py --coin=bch regtest"
@@ -286,6 +292,9 @@ init_tests() {
 		a $gentest_py --coin=ltc --type=segwit 1 $REFDIR/litecoin/ltcwallet-segwit.dump
 		a $gentest_py --coin=ltc --type=bech32 1 $REFDIR/litecoin/ltcwallet-bech32.dump
 		a $gentest_py --coin=ltc --type=compressed --testnet=1 1 $REFDIR/litecoin/ltcwallet-testnet.dump
+		a $gentest_py --coin=bch --type=compressed --cashaddr=0 1 $REFDIR/bitcoin_cash/bchwallet.dump
+		a $gentest_py --coin=bch --type=compressed --cashaddr=1 1 $REFDIR/bitcoin_cash/bchwallet.dump
+		a $gentest_py --coin=bch --type=compressed --testnet=1 1 $REFDIR/bitcoin_cash/bchwallet-testnet.dump
 		- # libsecp256k1 vs python-ecdsa:
 		- $gentest_py --type=legacy 1:2 $rounds100x
 		- $gentest_py --type=compressed 1:2 $rounds100x

+ 1 - 1
test/unit_tests_d/ut_bip_hd.py

@@ -151,7 +151,7 @@ vectors_multicoin = {
 	'doge':         'DFX88RXpi4S4W24YVvuMgbdUcCAYNeEYGd',
 	'avax-c':       '0x373731f4d885Fc7Da05498F9f0804a87A14F891b',
 	'ltc_bech32':   'ltc1q3uh5ga5cp9kkdfx6a52uymxj9keq4tpzep7er0',
-	'bch_cashaddr': 'bitcoincash:qpqpcllprftg4s0chdgkpxhxv23wfymq3gj7n0a9vw',
+	'bch_compressed': 'bitcoincash:qpqpcllprftg4s0chdgkpxhxv23wfymq3gj7n0a9vw',
 	'bsc_smart':    '0x373731f4d885Fc7Da05498F9f0804a87A14F891b',
 	'bnb_beacon':   'bnb179c3ymltqm4utlp089zxqeta5dvn48a305rhe5',
 }

+ 51 - 1
test/unit_tests_d/ut_cashaddr.py

@@ -9,10 +9,23 @@ altcoin_dep = True
 from collections import namedtuple
 
 from mmgen.proto.bch.cashaddr import cashaddr_parse_addr, cashaddr_decode_addr, cashaddr_encode_addr
+from mmgen.addr import CoinAddr
 
-from ..include.common import vmsg
+from ..include.common import cfg, vmsg
+
+from mmgen.protocol import init_proto
+proto = init_proto(cfg, 'bch')
 
 # Source: https://upgradespecs.bitcoincashnode.org/cashaddr
+alias_data = """
+1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu  bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a
+1KXrWXciRDZUpQwQmuM1DbwsKDLYAYsVLR  bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy
+16w1D5WRVKJuZUsSRzdLp9w3YGcgoxDXb   bitcoincash:qqq3728yw0y47sqn6l2na30mcw6zm78dzqre909m2r
+3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC  bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq
+3LDsS579y7sruadqu11beEJoTjdFiFCdX4  bitcoincash:pr95sy3j9xwd2ap32xkykttr4cvcu7as4yc93ky28e
+31nwvkZwyPdgzjBJZXfDmSWsC4ZLKpYyUw  bitcoincash:pqq3728yw0y47sqn6l2na30mcw6zm78dzq5ucqzc37
+"""
+
 vec_data = """
 F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9
 20 0  bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2
@@ -78,6 +91,11 @@ class unit_tests:
 						yield t(int(d[0]), int(d[1]), d[2], data)
 		return list(gen())
 
+	@property
+	def aliases(self):
+		t = namedtuple('aliases', ['legacy', 'cashaddr'])
+		return [t(*a.split()) for a in alias_data.splitlines() if a]
+
 	def encode(self, name, ut, desc='low-level address encoding'):
 		data = None
 		for v in self.vectors:
@@ -99,3 +117,35 @@ class unit_tests:
 			ret = cashaddr_decode_addr(v.addr)
 			assert ret.bytes.hex() == v.data
 		return True
+
+	def coinaddr(self, name, ut, desc='CoinAddr class'):
+		for e in self.aliases:
+			for addr in (
+					e.cashaddr.upper(),
+					e.cashaddr,
+					e.cashaddr.split(':')[1],
+					e.legacy,
+				):
+				a = CoinAddr(proto, addr)
+				vmsg(addr)
+				assert e.legacy == a.views[1]
+				assert e.cashaddr == a.proto.cashaddr_pfx + ':' + a.views[0]
+			vmsg('')
+		return True
+
+	def errors(self, name, ut, desc='error handling'):
+		# could do these in objtest.py:
+		def bad1(): a = CoinAddr(proto, self.aliases[0].cashaddr.replace('g','G'))
+		def bad2(): a = CoinAddr(proto, 'x' + self.aliases[0].cashaddr)
+		def bad3(): a = CoinAddr(proto, self.aliases[0].cashaddr[:-1])
+		def bad4(): a = CoinAddr(proto, self.aliases[0].cashaddr[:-1]+'i')
+		def bad5(): a = CoinAddr(proto, self.aliases[0].cashaddr[:-1]+'x')
+
+		ut.process_bad_data((
+			('case',     'ObjectInitError', 'mixed case',     bad1),
+			('prefix',   'ObjectInitError', 'invalid prefix', bad2),
+			('data',     'ObjectInitError', 'too short',      bad3),
+			('b32 char', 'ObjectInitError', 'substring',      bad4),
+			('chksum',   'ObjectInitError', 'checksum',       bad5),
+		))
+		return True