Browse Source

Bech32 address support
- Address type code is 'B'
- Generate with `--type=B` or `--type=bech32`

MMGen 7 years ago
parent
commit
e4114eedf3

+ 10 - 0
mmgen/addr.py

@@ -42,6 +42,7 @@ class AddrGenerator(MMGenObject):
 		gen_methods = {
 			'p2pkh':    AddrGeneratorP2PKH,
 			'segwit':   AddrGeneratorSegwit,
+			'bech32':   AddrGeneratorBech32,
 			'ethereum': AddrGeneratorEthereum,
 			'zcash_z':  AddrGeneratorZcashZ,
 			'monero':   AddrGeneratorMonero}
@@ -68,6 +69,15 @@ class AddrGeneratorSegwit(AddrGenerator):
 		assert pubhex.compressed,'Uncompressed public keys incompatible with Segwit'
 		return HexStr(g.proto.pubhex2redeem_script(pubhex))
 
+class AddrGeneratorBech32(AddrGenerator):
+	def to_addr(self,pubhex):
+		assert pubhex.compressed,'Uncompressed public keys incompatible with Segwit'
+		from mmgen.protocol import hash160
+		return CoinAddr(g.proto.pubhash2bech32addr(hash160(pubhex)))
+
+	def to_segwit_redeem_script(self,pubhex):
+		raise NotImplementedError,'Segwit redeem script not supported by this address type'
+
 class AddrGeneratorEthereum(AddrGenerator):
 	def to_addr(self,pubhex):
 		assert type(pubhex) == PubKey

+ 106 - 0
mmgen/bech32.py

@@ -0,0 +1,106 @@
+#!/usr/bin/env python
+#
+# Source URL:
+# https://github.com/bitcoin/bitcoin/blob/v0.16.0/test/functional/test_framework/segwit_addr.py
+#
+# Copyright (c) 2017 Pieter Wuille
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#
+# Unaltered except for the following changes by the MMGen Project:
+#   'python3' changed to 'python' in the hashbang
+#   leading spaces converted to tabs
+#
+"""Reference implementation for Bech32 and segwit addresses."""
+
+CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
+
+def bech32_polymod(values):
+	"""Internal function that computes the Bech32 checksum."""
+	generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
+	chk = 1
+	for value in values:
+		top = chk >> 25
+		chk = (chk & 0x1ffffff) << 5 ^ value
+		for i in range(5):
+			chk ^= generator[i] if ((top >> i) & 1) else 0
+	return chk
+
+def bech32_hrp_expand(hrp):
+	"""Expand the HRP into values for checksum computation."""
+	return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]
+
+def bech32_verify_checksum(hrp, data):
+	"""Verify a checksum given HRP and converted data characters."""
+	return bech32_polymod(bech32_hrp_expand(hrp) + data) == 1
+
+def bech32_create_checksum(hrp, data):
+	"""Compute the checksum values given HRP and data."""
+	values = bech32_hrp_expand(hrp) + data
+	polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1
+	return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
+
+def bech32_encode(hrp, data):
+	"""Compute a Bech32 string given HRP and data values."""
+	combined = data + bech32_create_checksum(hrp, data)
+	return hrp + '1' + ''.join([CHARSET[d] for d in combined])
+
+def bech32_decode(bech):
+	"""Validate a Bech32 string, and determine HRP and data."""
+	if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
+			(bech.lower() != bech and bech.upper() != bech)):
+		return (None, None)
+	bech = bech.lower()
+	pos = bech.rfind('1')
+	if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:
+		return (None, None)
+	if not all(x in CHARSET for x in bech[pos+1:]):
+		return (None, None)
+	hrp = bech[:pos]
+	data = [CHARSET.find(x) for x in bech[pos+1:]]
+	if not bech32_verify_checksum(hrp, data):
+		return (None, None)
+	return (hrp, data[:-6])
+
+def convertbits(data, frombits, tobits, pad=True):
+	"""General power-of-2 base conversion."""
+	acc = 0
+	bits = 0
+	ret = []
+	maxv = (1 << tobits) - 1
+	max_acc = (1 << (frombits + tobits - 1)) - 1
+	for value in data:
+		if value < 0 or (value >> frombits):
+			return None
+		acc = ((acc << frombits) | value) & max_acc
+		bits += frombits
+		while bits >= tobits:
+			bits -= tobits
+			ret.append((acc >> bits) & maxv)
+	if pad:
+		if bits:
+			ret.append((acc << (tobits - bits)) & maxv)
+	elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
+		return None
+	return ret
+
+def decode(hrp, addr):
+	"""Decode a segwit address."""
+	hrpgot, data = bech32_decode(addr)
+	if hrpgot != hrp:
+		return (None, None)
+	decoded = convertbits(data[1:], 5, 8, False)
+	if decoded is None or len(decoded) < 2 or len(decoded) > 40:
+		return (None, None)
+	if data[0] > 16:
+		return (None, None)
+	if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32:
+		return (None, None)
+	return (data[0], decoded)
+
+def encode(hrp, witver, witprog):
+	"""Encode a segwit address."""
+	ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5))
+	if decode(hrp, ret) == (None, None):
+		return None
+	return ret

+ 1 - 1
mmgen/main_addrimport.py

@@ -74,7 +74,7 @@ cmd_args = opts.init(opts_data)
 
 def import_mmgen_list(infile):
 	al = (AddrList,KeyAddrList)[bool(opt.keyaddr_file)](infile)
-	if al.al_id.mmtype == 'S':
+	if al.al_id.mmtype in ('S','B'):
 		from mmgen.tx import segwit_is_active
 		if not segwit_is_active():
 			rdie(2,'Segwit is not active on this chain. Cannot import Segwit addresses')

+ 1 - 1
mmgen/main_tool.py

@@ -120,7 +120,7 @@ opts_data = lambda: {
 -q, --quiet           Produce quieter output
 -r, --usr-randchars=n Get 'n' characters of additional randomness from
                       user (min={g.min_urandchars}, max={g.max_urandchars})
--t, --type=t          Specify address type (valid options: 'compressed','segwit','zcash_z')
+-t, --type=t          Specify address type (valid options: 'compressed','segwit','bech32','zcash_z')
 -v, --verbose         Produce more verbose output
 """.format(g=g),
 	'notes': """

+ 9 - 1
mmgen/obj.py

@@ -394,7 +394,9 @@ class CoinAddr(str,Hilite,InitErrors,MMGenObject):
 		proto = g.proto.get_protocol_by_chain(chain)
 		vn = proto.addr_ver_num
 
-		if self.addr_fmt == 'p2sh' and 'p2sh2' in vn:
+		if self.addr_fmt == 'bech32':
+			return self[:len(proto.bech32_hrp)] == proto.bech32_hrp
+		elif self.addr_fmt == 'p2sh' and 'p2sh2' in vn:
 			return pfx_ok(vn['p2sh'][1]) or pfx_ok(vn['p2sh2'][1])
 		else:
 			return pfx_ok(vn[self.addr_fmt][1])
@@ -705,6 +707,12 @@ class MMGenAddrType(str,Hilite,InitErrors,MMGenObject):
 				'gen_method':'segwit',
 				'addr_fmt':'p2sh',
 				'desc':'Segwit P2SH-P2WPKH address' },
+		'B': {  'name':'bech32',
+				'pubkey_type':'std',
+				'compressed':True,
+				'gen_method':'bech32',
+				'addr_fmt':'bech32',
+				'desc':'Native Segwit (Bech32) address' },
 		'E': {  'name':'ethereum',
 				'pubkey_type':'std',
 				'compressed':False,

+ 3 - 0
mmgen/opts.py

@@ -302,6 +302,9 @@ def init(opts_f,add_opts=[],opt_filter=None):
 		g.rpc_user = rt.rpc_user
 		g.rpc_password = rt.rpc_password
 
+	if g.regtest and hasattr(g.proto,'bech32_hrp_rt'):
+		g.proto.bech32_hrp = g.proto.bech32_hrp_rt
+
 	# Check user-set opts without modifying them
 	if not check_opts(uopts):
 		sys.exit(1)

+ 24 - 1
mmgen/protocol.py

@@ -25,6 +25,7 @@ from binascii import unhexlify
 from mmgen.util import msg,pmsg,Msg,pdie
 from mmgen.obj import MMGenObject,BTCAmt,LTCAmt,BCHAmt,B2XAmt
 from mmgen.globalvars import g
+import mmgen.bech32 as bech32
 
 def hash160(hexnum): # take hex, return hex - OP_HASH160
 	return hashlib.new('ripemd160',hashlib.sha256(unhexlify(hexnum)).digest()).hexdigest()
@@ -67,7 +68,7 @@ class BitcoinProtocol(MMGenObject):
 	daemon_name     = 'bitcoind'
 	addr_ver_num    = { 'p2pkh': ('00','1'), 'p2sh':  ('05','3') }
 	wif_ver_num     = { 'std': '80' }
-	mmtypes         = ('L','C','S')
+	mmtypes         = ('L','C','S','B')
 	dfl_mmtype      = 'L'
 	data_subdir     = ''
 	rpc_port        = 8332
@@ -91,6 +92,8 @@ class BitcoinProtocol(MMGenObject):
 	# but OP_1 through OP_16 are encoded as 0x51 though 0x60 (81 to 96 in decimal).
 	witness_vernum_hex = '00'
 	witness_vernum     = int(witness_vernum_hex,16)
+	bech32_addr_width  = 40
+	bech32_hrp         = 'bc'
 
 	@staticmethod
 	def get_protocol_by_chain(chain):
@@ -129,6 +132,19 @@ class BitcoinProtocol(MMGenObject):
 
 	@classmethod
 	def verify_addr(cls,addr,hex_width,return_dict=False):
+
+		if 'B' in cls.mmtypes and addr[:len(cls.bech32_hrp)] == cls.bech32_hrp:
+			ret = bech32.decode(cls.bech32_hrp,addr)
+			if ret[0] != cls.witness_vernum:
+				msg('{}: Invalid witness version number'.format(ret[0]))
+			elif ret[1]:
+				return {
+					'hex': ''.join([chr(b) for b in ret[1]]).encode('hex'),
+					'format': 'bech32',
+					'width': cls.bech32_addr_width,
+				} if return_dict else True
+			return False
+
 		for addr_fmt in cls.addr_ver_num:
 			ver_num,pfx = cls.addr_ver_num[addr_fmt]
 			if type(pfx) == tuple:
@@ -173,6 +189,11 @@ class BitcoinProtocol(MMGenObject):
 	def pubhex2segwitaddr(cls,pubhex):
 		return cls.pubhash2addr(hash160(cls.pubhex2redeem_script(pubhex)),p2sh=True)
 
+	@classmethod
+	def pubhash2bech32addr(cls,pubhash):
+		d = [ord(b) for b in pubhash.decode('hex')]
+		return bech32.bech32_encode(cls.bech32_hrp,[cls.witness_vernum]+bech32.convertbits(d,8,5))
+
 class BitcoinTestnetProtocol(BitcoinProtocol):
 	addr_ver_num         = { 'p2pkh': ('6f',('m','n')), 'p2sh':  ('c4','2') }
 	wif_ver_num          = { 'std': 'ef' }
@@ -180,6 +201,8 @@ class BitcoinTestnetProtocol(BitcoinProtocol):
 	daemon_data_subdir   = 'testnet3'
 	rpc_port             = 18332
 	addr_width           = 35
+	bech32_hrp           = 'tb'
+	bech32_hrp_rt        = 'bcrt'
 
 class BitcoinCashProtocol(BitcoinProtocol):
 	# TODO: assumes MSWin user installs in custom dir 'Bitcoin_ABC'

+ 10 - 7
mmgen/tx.py

@@ -135,6 +135,8 @@ def scriptPubKey2addr(s):
 		return g.proto.pubhash2addr(s[6:-4],p2sh=False)
 	elif len(s) == 46 and s[:4] == 'a914' and s[-2:] == '87':
 		return g.proto.pubhash2addr(s[4:-2],p2sh=True)
+	elif len(s) == 44 and s[:4] == g.proto.witness_vernum_hex + '14':
+		return g.proto.pubhash2bech32addr(s[4:])
 	else:
 		raise NotImplementedError,'Unknown scriptPubKey ({})'.format(s)
 
@@ -372,7 +374,7 @@ class MMGenTX(MMGenObject):
 		return self.add_comment(self)
 
 	def has_segwit_inputs(self):
-		return any(i.mmid and i.mmid.mmtype == 'S' for i in self.inputs)
+		return any(i.mmid and i.mmid.mmtype in ('S','B') for i in self.inputs)
 
 	def compare_size_and_estimated_size(self):
 		vsize = self.estimate_size()
@@ -410,6 +412,7 @@ class MMGenTX(MMGenObject):
 				'L': isize_common + sig_size + pubkey_size_uncompressed, # = 180
 				'C': isize_common + sig_size + pubkey_size_compressed,   # = 148
 				'S': isize_common + 23,                                  # = 64
+				'B': isize_common + 0                                    # = 41
 			}
 			ret = sum(input_size[i.mmid.mmtype] for i in self.inputs if i.mmid)
 
@@ -420,8 +423,8 @@ class MMGenTX(MMGenObject):
 
 		def get_outputs_size():
 			# output bytes = amt: 8, byte_count: 1+, pk_script
-			# pk_script bytes: p2pkh: 25, p2sh: 23
-			return sum({'p2pkh':34,'p2sh':32}[o.addr.addr_fmt] for o in self.outputs)
+			# pk_script bytes: p2pkh: 25, p2sh: 23, bech32: 22
+			return sum({'p2pkh':34,'p2sh':32,'bech32':31}[o.addr.addr_fmt] for o in self.outputs)
 
 		# https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
 		# The witness is a serialization of all witness data of the transaction. Each txin is
@@ -434,7 +437,7 @@ class MMGenTX(MMGenObject):
 		def get_witness_size():
 			if not self.has_segwit_inputs(): return 0
 			wf_size = 1 + 1 + sig_size + 1 + pubkey_size_compressed # vInt vInt sig vInt pubkey = 108
-			return sum((1,wf_size)[bool(i.mmid) and i.mmid.mmtype == 'S'] for i in self.inputs)
+			return sum((1,wf_size)[bool(i.mmid) and i.mmid.mmtype in ('S','B')] for i in self.inputs)
 
 		isize = get_inputs_size()
 		osize = get_outputs_size()
@@ -716,10 +719,10 @@ class MMGenTX(MMGenObject):
 				assert type(ti['witness']) == list and len(ti['witness']) == 2, 'malformed witness'
 				assert len(ti['witness'][1]) == 66, 'incorrect witness pubkey length'
 				assert mmti.mmid, fs.format('witness-type','non-MMGen')
-				assert mmti.mmid.mmtype == 'S', fs.format('witness-type',mmti.mmid.mmtype)
+				assert mmti.mmid.mmtype in ('S','B'), fs.format('witness-type',mmti.mmid.mmtype)
 			else: # non-witness
 				if mmti.mmid:
-					assert mmti.mmid.mmtype != 'S', fs.format('signature in',mmti.mmid.mmtype)
+					assert mmti.mmid.mmtype not in ('S','B'), fs.format('signature in',mmti.mmid.mmtype)
 				assert not 'witness' in ti, 'non-witness input has witness'
 				# sig_size 72 (DER format), pubkey_size 'compressed':33, 'uncompressed':65
 				assert (200 < len(ti['scriptSig']) < 300), 'malformed scriptSig' # VERY rough check
@@ -727,7 +730,7 @@ class MMGenTX(MMGenObject):
 		return True
 
 	def has_segwit_outputs(self):
-		return any(o.mmid and o.mmid.mmtype == 'S' for o in self.outputs)
+		return any(o.mmid and o.mmid.mmtype in ('S','B') for o in self.outputs)
 
 	def is_in_mempool(self):
 		return 'size' in g.rpch.getmempoolentry(self.coin_txid,on_fail='silent')

+ 5 - 0
scripts/test-release.sh

@@ -143,6 +143,7 @@ if [ "$MINGW" ]; then
 	"$gentest_py --coin=btc 2 $rounds"
 	"$gentest_py --coin=btc --type=compressed 2 $rounds"
 	"$gentest_py --coin=btc --type=segwit 2 $rounds"
+	"$gentest_py --coin=btc --type=bech32 2 $rounds"
 	"$gentest_py --coin=ltc 2 $rounds"
 	"$gentest_py --coin=ltc --type=compressed 2 $rounds"
 	"$gentest_py --coin=ltc --type=segwit 2 $rounds"
@@ -156,6 +157,7 @@ else
 	"$gentest_py --coin=btc 2 $rounds"
 	"$gentest_py --coin=btc --type=compressed 2 $rounds"
 	"$gentest_py --coin=btc --type=segwit 2 $rounds"
+	"$gentest_py --coin=btc --type=bech32 2 $rounds"
 	"$gentest_py --coin=ltc 2 $rounds"
 	"$gentest_py --coin=ltc --type=compressed 2 $rounds"
 	"$gentest_py --coin=ltc --type=segwit 2 $rounds"
@@ -218,6 +220,7 @@ t_btc=(
 	"$test_py -On"
 	"$test_py -On --segwit dfl_wallet main ref ref_other"
 	"$test_py -On --segwit-random dfl_wallet main"
+	"$test_py -On --bech32 dfl_wallet main ref ref_other"
 	"$tooltest_py rpc"
 	"$python scripts/compute-file-chksum.py $REFDIR/*testnet.rawtx >/dev/null 2>&1")
 f_btc='You may stop the bitcoin (mainnet) daemon if you wish'
@@ -228,6 +231,7 @@ t_btc_tn=(
 	"$test_py -On --testnet=1"
 	"$test_py -On --testnet=1 --segwit dfl_wallet main ref ref_other"
 	"$test_py -On --testnet=1 --segwit-random dfl_wallet main"
+	"$test_py -On --testnet=1 --bech32 dfl_wallet main ref ref_other"
 	"$tooltest_py --testnet=1 rpc")
 f_btc_tn='You may stop the bitcoin testnet daemon if you wish'
 
@@ -309,6 +313,7 @@ t_gen=(
 	"$gentest_py -q 2 $REFDIR/btcwallet.dump"
 	"$gentest_py -q 1:2 $gen_rounds"
 	"$gentest_py -q --type=segwit 1:2 $gen_rounds"
+	"$gentest_py -q --type=bech32 1:2 $gen_rounds"
 	"$gentest_py -q --testnet=1 2 $REFDIR/btcwallet-testnet.dump"
 	"$gentest_py -q --testnet=1 1:2 $gen_rounds"
 	"$gentest_py -q --testnet=1 --type=segwit 1:2 $gen_rounds"

+ 1 - 0
setup.py

@@ -110,6 +110,7 @@ setup(
 			'mmgen.__init__',
 			'mmgen.addr',
 			'mmgen.altcoin',
+			'mmgen.bech32',
 			'mmgen.protocol',
 			'mmgen.color',
 			'mmgen.common',

+ 19 - 0
test/ref/98831F3A-B[1,31-33,500-501,1010-1011].addrs

@@ -0,0 +1,19 @@
+# MMGen address file
+#
+# This file is editable.
+# Everything following a hash symbol '#' is a comment and ignored by MMGen.
+# A text label of 32 characters or less may be added to the right of each
+# address, and it will be appended to the tracking wallet label upon import.
+# The label may contain any printable ASCII symbol.
+# Address data checksum for 98831F3A-B[1,31-33,500-501,1010-1011]: 9D2A D4B6 5117 F02E
+# Record this value to a secure location.
+98831F3A BECH32 {
+  1     bc1q8snv94j6959y3gkqv4gku0cm5mersnpucsvw5z
+  31    bc1qvan6huak4z6wynjhc5hjdv8vd2xj6zrjpc6xks
+  32    bc1q5nmmd6z2yavfwhl94t86n55k7vufvaz4xcvlud
+  33    bc1qp7uv0f0cm944r5mddqvqq828fjkau2ny427djj
+  500   bc1qj5l8yt9qysfx0xgsls8s0hqwjl59hq79agjzwp
+  501   bc1qma70d8wttaslfyupc4ff0z9avuf57sdzdfe80s
+  1010  bc1qqh6u944ta66gp85e2tzr9r5h9wfjfflyqn5rwl
+  1011  bc1qu5249g7zajgp56v3fhjw2dt7g08zsx66hs2sd8
+}

+ 19 - 0
test/ref/98831F3A-B[1,31-33,500-501,1010-1011].testnet.addrs

@@ -0,0 +1,19 @@
+# MMGen address file
+#
+# This file is editable.
+# Everything following a hash symbol '#' is a comment and ignored by MMGen.
+# A text label of 32 characters or less may be added to the right of each
+# address, and it will be appended to the tracking wallet label upon import.
+# The label may contain any printable ASCII symbol.
+# Address data checksum for 98831F3A-B[1,31-33,500-501,1010-1011]: BA07 0DD8 E2A6 2C5A
+# Record this value to a secure location.
+98831F3A BECH32 {
+  1     tb1q8snv94j6959y3gkqv4gku0cm5mersnpujkha03
+  31    tb1qvan6huak4z6wynjhc5hjdv8vd2xj6zrjt7p4dr
+  32    tb1q5nmmd6z2yavfwhl94t86n55k7vufvaz4v7hv87
+  33    tb1qp7uv0f0cm944r5mddqvqq828fjkau2nylv97fp
+  500   tb1qj5l8yt9qysfx0xgsls8s0hqwjl59hq79hwf34j
+  501   tb1qma70d8wttaslfyupc4ff0z9avuf57sdz80z55r
+  1010  tb1qqh6u944ta66gp85e2tzr9r5h9wfjffly240s4v
+  1011  tb1qu5249g7zajgp56v3fhjw2dt7g08zsx66ak3rk5
+}

+ 1 - 0
test/scrambletest.py

@@ -60,6 +60,7 @@ test_data = OrderedDict([
 ('btc',           ('456d7f5f1c4bfe3b','(none)',         '',    '',          '1MU7EdgqYy9JX35L25hR6CmXXcSEBDAwyv')),
 ('btc_compressed',('bf98a4af5464a4ef','compressed',     '-C',  'COMPRESSED','1F97Jd89wwmu4ELadesAdGDzg3d8Y6j5iP')),
 ('btc_segwit',    ('b56962d829ffc678','segwit',         '-S',  'SEGWIT',    '36TvVzU5mxSjJ3D9qKAmYzCV7iUqtTDezF')),
+('btc_bech32',    ('d09eea818f9ad17f','bech32',         '-B',  'BECH32',    'bc1q8snv94j6959y3gkqv4gku0cm5mersnpucsvw5z')),
 ('bch',           ('456d7f5f1c4bfe3b','(none)',         '',    '',          '1MU7EdgqYy9JX35L25hR6CmXXcSEBDAwyv')),
 ('bch_compressed',('bf98a4af5464a4ef','compressed',     '-C',  'COMPRESSED','1F97Jd89wwmu4ELadesAdGDzg3d8Y6j5iP')),
 ('ltc',           ('b11f16632e63ba92','ltc:legacy',     '-LTC','LTC',       'LMxB474SVfxeYdqxNrM1WZDZMnifteSMv1')),

+ 53 - 14
test/test.py

@@ -111,6 +111,7 @@ opts_data = lambda: {
 	'options': """
 -h, --help          Print this help message
 --, --longhelp      Print help message for long options (common options)
+-B, --bech32        Generate and use Bech32 addresses
 -b, --buf-keypress  Use buffered keypresses as with real human input
 -c, --print-cmdline Print the command line of each spawned command
 -C, --coverage      Produce code coverage info using trace module
@@ -161,12 +162,12 @@ txbump_fee = {'btc':'123s','bch':'567s','ltc':'12345s'}[coin_sel]
 
 rtFundAmt  = {'btc':'500','bch':'500','ltc':'5500'}[coin_sel]
 rtFee = {
-	'btc': ('20s','10s','60s','0.0001','10s','20s'),
+	'btc': ('20s','10s','60s','31s','10s','20s'),
 	'bch': ('20s','10s','60s','0.0001','10s','20s'),
 	'ltc': ('1000s','500s','1500s','0.05','400s','1000s')
 }[coin_sel]
 rtBals = {
-	'btc': ('499.9999488','399.9998282','399.9998147','399.9996875','6.79000000','993.20958750','999.99958750'),
+	'btc': ('499.9999488','399.9998282','399.9998147','399.9996877','13.00000000','986.99933647','999.99933647'),
 	'bch': ('499.9999416','399.9999124','399.99989','399.9997616','276.22339397','723.77626763','999.99966160'),
 	'ltc': ('5499.9971','5399.994085','5399.993545','5399.987145','13.00000000','10986.93714500','10999.93714500'),
 }[coin_sel]
@@ -174,11 +175,13 @@ rtBobOp3 = {'btc':'S:2','bch':'L:3','ltc':'S:2'}[coin_sel]
 
 if opt.segwit and 'S' not in g.proto.mmtypes:
 	die(1,'--segwit option incompatible with {}'.format(g.proto.__name__))
+if opt.bech32 and 'B' not in g.proto.mmtypes:
+	die(1,'--bech32 option incompatible with {}'.format(g.proto.__name__))
 
 def randbool():
 	return hexlify(os.urandom(1))[1] in '12345678'
 def get_segwit_bool():
-	return randbool() if opt.segwit_random else True if opt.segwit else False
+	return randbool() if opt.segwit_random else True if opt.segwit or opt.bech32 else False
 def disable_debug():
 	ds = os.getenv('MMGEN_DEBUG')
 	if ds is not None: os.environ['MMGEN_DEBUG'] = ''
@@ -316,6 +319,10 @@ cfgs = {
 			'btc': ('9914 6D10 2307 F348','7DBF 441F E188 8B37'),
 			'ltc': ('CC09 A190 B7DF B7CD','3676 4C49 14F8 1AD0'),
 		},
+		'addrfile_bech32_chk': {
+			'btc': ('C529 D686 31AA ACD4','13C5 493E 1CD7 2852'),
+			'ltc': ('CC09 A190 B7DF B7CD','3676 4C49 14F8 1AD0'),
+		},
 		'addrfile_compressed_chk': {
 			'btc': ('95EB 8CC0 7B3B 7856','629D FDE4 CDC0 F276'),
 			'ltc': ('35D5 8ECA 9A42 46C3','37E9 A36E 94A2 010F'),
@@ -328,6 +335,10 @@ cfgs = {
 			'btc': ('C13B F717 D4E8 CF59','4DB5 BAF0 45B7 6E81'),
 			'ltc': ('054B 9794 55B4 5D82','C373 0074 DEE6 B70A'),
 		},
+		'keyaddrfile_bech32_chk': {
+			'btc': ('934F 1C33 6C06 B18C','D994 DF67 6E53 6F5F'),
+			'ltc': ('054B 9794 55B4 5D82','C373 0074 DEE6 B70A'),
+		},
 		'keyaddrfile_compressed_chk': {
 			'btc': ('E43A FA46 5751 720A','B995 A6CF D1CD FAD0'),
 			'ltc': ('7603 2FE3 2145 FFAD','3248 356A C707 4A41'),
@@ -368,6 +379,9 @@ cfgs = {
 			'btc': ('91C4 0414 89E4 2089','3BA6 7494 8E2B 858D'),
 			'ltc': ('8F12 FA7B 9F12 594C','E79E F55B 1536 56F2'),
 		},
+		'addrfile_bech32_chk': {
+			'btc': ('2AA3 78DF B965 82EB','5B6A 4D12 820D BC3C'),
+		},
 		'addrfile_compressed_chk': {
 			'btc': ('2615 8401 2E98 7ECA','DF38 22AB AAB0 124E'),
 			'ltc': ('197C C48C 3C37 AB0F','5072 15DA 1A90 5E99'),
@@ -380,6 +394,9 @@ cfgs = {
 			'btc': ('C98B DF08 A3D5 204B','25F2 AEB6 AAAC 8BBE'),
 			'ltc': ('1829 7FE7 2567 CB91','1305 9007 E515 B66A'),
 		},
+		'keyaddrfile_bech32_chk': {
+			'btc': ('4A6B 3762 DF30 9368','A68A 53D4 874E 923C'),
+		},
 		'keyaddrfile_compressed_chk': {
 			'btc': ('6D6D 3D35 04FD B9C3','B345 9CD8 9EAE 5489'),
 			'ltc': ('F5DA 9D60 6798 C4E9','F928 113B C9D7 9DF5'),
@@ -420,6 +437,9 @@ cfgs = {
 			'btc': ('06C1 9C87 F25C 4EE6','58D1 7B6C E9F9 9C14'),
 			'ltc': ('63DF E42A 0827 21C3','1A3F 3016 2E2B F33A'),
 		},
+		'addrfile_bech32_chk': {
+			'btc': ('9D2A D4B6 5117 F02E','BA07 0DD8 E2A6 2C5A'),
+		},
 		'addrfile_compressed_chk': {
 			'btc': ('A33C 4FDE F515 F5BC','5186 02C2 535E B7D5'),
 			'ltc': ('3FC0 8F03 C2D6 BD19','535E 5CDC 1CA7 08D5'),
@@ -432,6 +452,9 @@ cfgs = {
 			'btc': ('A447 12C2 DD14 5A9B','0690 460D A600 D315'),
 			'ltc': ('E8A3 9F6E E164 A521','70ED 8557 5882 08A5'),
 		},
+		'keyaddrfile_bech32_chk': {
+			'btc': ('D0DD BDE3 87BE 15AE','2C7B 70E5 5F96 9B09'),
+		},
 		'keyaddrfile_compressed_chk': {
 			'btc': ('420A 8EB5 A9E2 7814','3243 DD92 809E FE8D'),
 			'ltc': ('8D1C 781F EB7F 44BC','678E 8EF9 1396 B140'),
@@ -443,6 +466,7 @@ cfgs = {
 		'ref_wallet':      '98831F3A-{}[256,1].mmdat'.format(('27F2BF93','E2687906')[g.testnet]),
 		'ref_addrfile':    '98831F3A{}[1,31-33,500-501,1010-1011]{}.addrs',
 		'ref_segwitaddrfile':'98831F3A{}-S[1,31-33,500-501,1010-1011]{}.addrs',
+		'ref_bech32addrfile':'98831F3A{}-B[1,31-33,500-501,1010-1011]{}.addrs',
 		'ref_keyaddrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.akeys.mmenc',
 		'ref_passwdfile':  '98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100].pws',
 		'ref_addrfile_chksum': {
@@ -453,6 +477,9 @@ cfgs = {
 			'btc': ('06C1 9C87 F25C 4EE6','58D1 7B6C E9F9 9C14'),
 			'ltc': ('63DF E42A 0827 21C3','1A3F 3016 2E2B F33A'),
 		},
+		'ref_bech32addrfile_chksum': {
+			'btc': ('9D2A D4B6 5117 F02E','BA07 0DD8 E2A6 2C5A'),
+		},
 		'ref_keyaddrfile_chksum': {
 			'btc': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2'),
 			'ltc': ('B804 978A 8796 3ED4','93A6 844C 8ECC BEF4'),
@@ -621,6 +648,7 @@ cmd_group['ref'] = (
 cmd_group['ref_other'] = (
 	('ref_addrfile_chk',   'saved reference address file'),
 	('ref_segwitaddrfile_chk','saved reference address file (segwit)'),
+	('ref_bech32addrfile_chk','saved reference address file (bech32)'),
 	('ref_keyaddrfile_chk','saved reference key-address file'),
 	('ref_passwdfile_chk', 'saved reference password file'),
 #	Create the fake inputs:
@@ -886,7 +914,8 @@ os.environ['MMGEN_NO_LICENSE'] = '1'
 os.environ['MMGEN_MIN_URANDCHARS'] = '3'
 os.environ['MMGEN_BOGUS_SEND'] = '1'
 
-def get_segwit_arg(cfg): return ([],['--type','segwit'])[cfg['segwit']]
+def get_segwit_arg(cfg):
+	return ['--type='+('segwit','bech32')[bool(opt.bech32)]] if cfg['segwit'] else []
 
 # Tell spawned programs they're running in the test suite
 os.environ['MMGEN_TEST_SUITE'] = '1'
@@ -1022,7 +1051,8 @@ def create_fake_unspent_entry(coinaddr,al_id=None,idx=None,lbl=None,non_mmgen=Fa
 	spk_beg,spk_end = (
 		('76a914','88ac'),
 		('a914','87'),
-		)[segwit and coinaddr.addr_fmt=='p2sh']
+		(g.proto.witness_vernum_hex+'14','')
+		)[segwit and (coinaddr.addr_fmt=='p2sh') + 2*(coinaddr.addr_fmt=='bech32')]
 	amt1,amt2 = {'btc':(10,40),'bch':(10,40),'ltc':(1000,4000)}[coin_sel]
 	return {
 		'account': '{}:{}'.format(g.proto.base_coin.lower(),coinaddr) if non_mmgen \
@@ -1451,7 +1481,7 @@ class MMGenTestSuite(object):
 
 	def addrgen(self,name,wf,pf=None,check_ref=False,ftype='addr',id_str=None,extra_args=[],mmtype=None):
 		if ftype[:4] != 'pass' and not mmtype:
-			if cfg['segwit']: mmtype = 'segwit'
+			if cfg['segwit']: mmtype = ('segwit','bech32')[bool(opt.bech32)]
 		cmd_pfx = (ftype,'pass')[ftype[:4]=='pass']
 		t = MMGenExpect(name,'mmgen-{}gen'.format(cmd_pfx),
 				['-d',cfg['tmpdir']] +
@@ -1459,7 +1489,8 @@ class MMGenTestSuite(object):
 				([],['--type='+str(mmtype)])[bool(mmtype)] +
 				([],[wf])[bool(wf)] +
 				([],[id_str])[bool(id_str)] +
-				[cfg['{}_idx_list'.format(cmd_pfx)]],extra_desc=('','(segwit)')[mmtype=='segwit'])
+				[cfg['{}_idx_list'.format(cmd_pfx)]],
+				extra_desc='({})'.format(mmtype) if mmtype in ('segwit','bech32') else '')
 		t.license()
 		t.passphrase('MMGen wallet',cfg['wpasswd'])
 		t.expect('Passphrase is OK')
@@ -1487,7 +1518,7 @@ class MMGenTestSuite(object):
 		self.addrgen(name,wf,pf=pf,check_ref=True)
 
 	def refaddrgen_compressed(self,name,wf,pf):
-		if opt.segwit:
+		if opt.segwit or opt.bech32:
 			msg('Skipping non-Segwit address generation'); return True
 		self.addrgen(name,wf,pf=pf,check_ref=True,mmtype='compressed')
 
@@ -1750,10 +1781,11 @@ class MMGenTestSuite(object):
 
 	def keyaddrgen(self,name,wf,pf=None,check_ref=False,mmtype=None):
 		if cfg['segwit'] and not mmtype:
-			mmtype = 'segwit'
+			mmtype = ('segwit','bech32')[bool(opt.bech32)]
 		args = ['-d',cfg['tmpdir'],usr_rand_arg,wf,cfg['addr_idx_list']]
 		t = MMGenExpect(name,'mmgen-keygen',
-				([],['--type='+str(mmtype)])[bool(mmtype)] + args,extra_desc=('','(segwit)')[mmtype=='segwit'])
+				([],['--type='+str(mmtype)])[bool(mmtype)] + args,
+				extra_desc='({})'.format(mmtype) if mmtype in ('segwit','bech32') else '')
 		t.license()
 		t.passphrase('MMGen wallet',cfg['wpasswd'])
 		chk = t.expect_getend(r'Checksum for key-address data .*?: ',regex=True)
@@ -1773,7 +1805,7 @@ class MMGenTestSuite(object):
 		self.keyaddrgen(name,wf,pf,check_ref=True)
 
 	def refkeyaddrgen_compressed(self,name,wf,pf):
-		if opt.segwit:
+		if opt.segwit or opt.bech32:
 			msg('Skipping non-Segwit key-address generation'); return True
 		self.keyaddrgen(name,wf,pf,check_ref=True,mmtype='compressed')
 
@@ -2088,7 +2120,8 @@ class MMGenTestSuite(object):
 		af_fn = cfg[af_key].format(pfx or altcoin_pfx,'' if coin else tn_ext)
 		af = os.path.join(ref_dir,(subdir or ref_subdir,'')[ftype=='passwd'],af_fn)
 		coin_arg = [] if coin == None else ['--coin='+coin]
-		t = MMGenExpect(name,'mmgen-tool',coin_arg+[ftype.replace('segwit','')+'file_chksum',af]+add_args)
+		tool_cmd = ftype.replace('segwit','').replace('bech32','')+'file_chksum'
+		t = MMGenExpect(name,'mmgen-tool',coin_arg+[tool_cmd,af]+add_args)
 		if ftype == 'keyaddr':
 			w = 'key-address data'
 			t.hash_preset(w,ref_kafile_hash_preset)
@@ -2153,6 +2186,12 @@ class MMGenTestSuite(object):
 		else:
 			self.ref_addrfile_chk(name,ftype='segwitaddr')
 
+	def ref_bech32addrfile_chk(self,name):
+		if not 'B' in g.proto.mmtypes:
+			msg_r('Skipping {} (not supported)'.format(name)); ok()
+		else:
+			self.ref_addrfile_chk(name,ftype='bech32addr')
+
 #	def txcreate8(self,name,addrfile):
 #		self.txcreate_common(name,sources=['8'])
 
@@ -2279,7 +2318,7 @@ class MMGenTestSuite(object):
 	def regtest_addrgen_alice(self,name): self.regtest_addrgen(name,'alice')
 
 	def regtest_addrimport(self,name,user,sid=None,addr_range='1-5',num_addrs=5):
-		id_strs = { 'legacy':'', 'compressed':'-C', 'segwit':'-S' }
+		id_strs = { 'legacy':'', 'compressed':'-C', 'segwit':'-S', 'bech32':'-B' }
 		if not sid: sid = self.regtest_user_sid(user)
 		from mmgen.addr import MMGenAddrType
 		for mmtype in g.proto.mmtypes:
@@ -2512,7 +2551,7 @@ class MMGenTestSuite(object):
 		amts = (a for a in (1.12345678,2.87654321,3.33443344,4.00990099,5.43214321))
 		outputs1 = ['{},{}'.format(a,amts.next()) for a in addrs]
 		sid = self.regtest_user_sid('bob')
-		l1,l2 = (':S',':S') if g.proto.cap('segwit') else (':L',':L')
+		l1,l2 = (':S',':B') if g.proto.cap('segwit') else (':L',':L')
 		outputs2 = [sid+':C:2,6.333', sid+':L:3,6.667',sid+l1+':4,0.123',sid+l2+':5']
 		return self.regtest_user_txdo(name,'bob',rtFee[5],outputs1+outputs2,'1-2')