Bech32 address support

- Address type code is 'B'
- Generate with `--type=B` or `--type=bech32`
This commit is contained in:
The MMGen Project 2018-03-05 09:46:35 +00:00
commit e4114eedf3
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
14 changed files with 262 additions and 25 deletions

View file

@ -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
mmgen/bech32.py Normal file
View file

@ -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

View file

@ -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')

View file

@ -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': """

View file

@ -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,

View file

@ -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)

View file

@ -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'

View file

@ -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')

View file

@ -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"

View file

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

View file

@ -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
}

View file

@ -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
}

View file

@ -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')),

View file

@ -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')