From 4315fa4371707596fa89cab0577e6fdc1c022791 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 17 Mar 2021 18:11:55 +0100 Subject: [PATCH] BIP-0350: use bech32m for witness version 1+ addresses We have supported sending to any witness version since Electrum 3.0, using addresses as specified in BIP-0173 (bech32 encoding). BIP-0350 makes a breaking change in address encoding, and recommends using (and using only) a new encoding (bech32m) for sending to witness version 1 and later. The address encoding for currently in use witness v0 addresses remains the same, as in BIP-0173; following the BIP-0350 spec. closes https://github.com/spesmilo/electrum/issues/6949 related: https://github.com/bitcoin/bips/blob/cd3885c0fb9d140b111ff729294400ff5dcfc8e3/bip-0350.mediawiki https://github.com/bitcoin/bitcoin/pull/20861 --- electrum/bitcoin.py | 10 +- electrum/lnaddr.py | 31 ++++--- electrum/lnutil.py | 8 +- electrum/segwit_addr.py | 71 +++++++++----- electrum/tests/test_bitcoin.py | 144 ++++++++++++++++++++++++----- electrum/tests/test_bolt11.py | 16 ++-- electrum/tests/test_transaction.py | 11 ++- electrum/transaction.py | 2 +- 8 files changed, 216 insertions(+), 77 deletions(-) diff --git a/electrum/bitcoin.py b/electrum/bitcoin.py index cca69b5ae..68cb14353 100644 --- a/electrum/bitcoin.py +++ b/electrum/bitcoin.py @@ -395,7 +395,9 @@ def public_key_to_p2pkh(public_key: bytes, *, net=None) -> str: def hash_to_segwit_addr(h: bytes, witver: int, *, net=None) -> str: if net is None: net = constants.net - return segwit_addr.encode(net.SEGWIT_HRP, witver, h) + addr = segwit_addr.encode_segwit_address(net.SEGWIT_HRP, witver, h) + assert addr is not None + return addr def public_key_to_p2wpkh(public_key: bytes, *, net=None) -> str: if net is None: net = constants.net @@ -452,7 +454,7 @@ def address_to_script(addr: str, *, net=None) -> str: if net is None: net = constants.net if not is_address(addr, net=net): raise BitcoinException(f"invalid bitcoin address: {addr}") - witver, witprog = segwit_addr.decode(net.SEGWIT_HRP, addr) + witver, witprog = segwit_addr.decode_segwit_address(net.SEGWIT_HRP, addr) if witprog is not None: if not (0 <= witver <= 16): raise BitcoinException(f'impossible witness version: {witver}') @@ -482,7 +484,7 @@ def address_to_hash(addr: str, *, net=None) -> Tuple[OnchainOutputType, bytes]: if net is None: net = constants.net if not is_address(addr, net=net): raise BitcoinException(f"invalid bitcoin address: {addr}") - witver, witprog = segwit_addr.decode(net.SEGWIT_HRP, addr) + witver, witprog = segwit_addr.decode_segwit_address(net.SEGWIT_HRP, addr) if witprog is not None: if witver != 0: raise BitcoinException(f"not implemented handling for witver={witver}") @@ -714,7 +716,7 @@ def address_from_private_key(sec: str) -> str: def is_segwit_address(addr: str, *, net=None) -> bool: if net is None: net = constants.net try: - witver, witprog = segwit_addr.decode(net.SEGWIT_HRP, addr) + witver, witprog = segwit_addr.decode_segwit_address(net.SEGWIT_HRP, addr) except Exception as e: return False return witprog is not None diff --git a/electrum/lnaddr.py b/electrum/lnaddr.py index 82bab0345..3d2549b63 100644 --- a/electrum/lnaddr.py +++ b/electrum/lnaddr.py @@ -13,6 +13,7 @@ import bitstring from .bitcoin import hash160_to_b58_address, b58_address_to_hash160, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC from .segwit_addr import bech32_encode, bech32_decode, CHARSET +from . import segwit_addr from . import constants from . import ecc from .bitcoin import COIN @@ -81,18 +82,14 @@ def bitarray_to_u5(barr): ret.append(s.read(5).uint) return ret -def encode_fallback(fallback, currency): + +def encode_fallback(fallback: str, currency): """ Encode all supported fallback addresses. """ if currency in [constants.BitcoinMainnet.SEGWIT_HRP, constants.BitcoinTestnet.SEGWIT_HRP]: - fbhrp, witness = bech32_decode(fallback, ignore_long_length=True) - if fbhrp: - if fbhrp != currency: - raise ValueError("Not a bech32 address for this currency") - wver = witness[0] - if wver > 16: - raise ValueError("Invalid witness version {}".format(witness[0])) - wprog = u5_to_bitarray(witness[1:]) + wver, wprog_ints = segwit_addr.decode_segwit_address(currency, fallback) + if wver is not None: + wprog = bytes(wprog_ints) else: addrtype, addr = b58_address_to_hash160(fallback) if is_p2pkh(currency, addrtype): @@ -106,6 +103,7 @@ def encode_fallback(fallback, currency): else: raise NotImplementedError("Support for currency {} not implemented".format(currency)) + def parse_fallback(fallback, currency): if currency in [constants.BitcoinMainnet.SEGWIT_HRP, constants.BitcoinTestnet.SEGWIT_HRP]: wver = fallback[0:5].uint @@ -114,7 +112,10 @@ def parse_fallback(fallback, currency): elif wver == 18: addr=hash160_to_b58_address(fallback[5:].tobytes(), base58_prefix_map[currency][1]) elif wver <= 16: - addr=bech32_encode(currency, bitarray_to_u5(fallback)) + witprog = fallback[5:] # cut witver + witprog = witprog[:len(witprog) // 8 * 8] # can only be full bytes + witprog = witprog.tobytes() + addr = segwit_addr.encode_segwit_address(currency, wver, witprog) else: return None else: @@ -262,7 +263,7 @@ def lnencode(addr: 'LnAddr', privkey) -> str: sig = bytes(sig[1:]) + recovery_flag data += sig - return bech32_encode(hrp, bitarray_to_u5(data)) + return bech32_encode(segwit_addr.Encoding.BECH32, hrp, bitarray_to_u5(data)) class LnAddr(object): def __init__(self, *, paymenthash: bytes = None, amount=None, currency=None, tags=None, date=None, @@ -365,9 +366,13 @@ class SerializableKey: def lndecode(invoice: str, *, verbose=False, expected_hrp=None) -> LnAddr: if expected_hrp is None: expected_hrp = constants.net.SEGWIT_HRP - hrp, data = bech32_decode(invoice, ignore_long_length=True) - if not hrp: + decoded_bech32 = bech32_decode(invoice, ignore_long_length=True) + hrp = decoded_bech32.hrp + data = decoded_bech32.data + if decoded_bech32.encoding is None: raise ValueError("Bad bech32 checksum") + if decoded_bech32.encoding != segwit_addr.Encoding.BECH32: + raise ValueError("Bad bech32 encoding: must be using vanilla BECH32") # BOLT #11: # diff --git a/electrum/lnutil.py b/electrum/lnutil.py index 685e92f30..8263c9101 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -1165,7 +1165,13 @@ class LNPeerAddr: def get_compressed_pubkey_from_bech32(bech32_pubkey: str) -> bytes: - hrp, data_5bits = segwit_addr.bech32_decode(bech32_pubkey) + decoded_bech32 = segwit_addr.bech32_decode(bech32_pubkey) + hrp = decoded_bech32.hrp + data_5bits = decoded_bech32.data + if decoded_bech32.encoding is None: + raise ValueError("Bad bech32 checksum") + if decoded_bech32.encoding != segwit_addr.Encoding.BECH32: + raise ValueError("Bad bech32 encoding: must be using vanilla BECH32") if hrp != 'ln': raise Exception('unexpected hrp: {}'.format(hrp)) data_8bits = segwit_addr.convertbits(data_5bits, 5, 8, False) diff --git a/electrum/segwit_addr.py b/electrum/segwit_addr.py index 110ede551..b13a9175e 100644 --- a/electrum/segwit_addr.py +++ b/electrum/segwit_addr.py @@ -19,12 +19,29 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -"""Reference implementation for Bech32 and segwit addresses.""" +"""Reference implementation for Bech32/Bech32m and segwit addresses.""" +from enum import Enum +from typing import Tuple, Optional, Sequence, NamedTuple, List CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" _CHARSET_INVERSE = {x: CHARSET.find(x) for x in CHARSET} +BECH32_CONST = 1 +BECH32M_CONST = 0x2bc830a3 + + +class Encoding(Enum): + """Enumeration type to list the various supported encodings.""" + BECH32 = 1 + BECH32M = 2 + + +class DecodedBech32(NamedTuple): + encoding: Optional[Encoding] + hrp: Optional[str] + data: Optional[Sequence[int]] # 5-bit ints + def bech32_polymod(values): """Internal function that computes the Bech32 checksum.""" @@ -45,42 +62,50 @@ def bech32_hrp_expand(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 + check = bech32_polymod(bech32_hrp_expand(hrp) + data) + if check == BECH32_CONST: + return Encoding.BECH32 + elif check == BECH32M_CONST: + return Encoding.BECH32M + else: + return None -def bech32_create_checksum(hrp, data): +def bech32_create_checksum(encoding: Encoding, hrp: str, data: List[int]) -> List[int]: """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 + const = BECH32M_CONST if encoding == Encoding.BECH32M else BECH32_CONST + polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const 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) +def bech32_encode(encoding: Encoding, hrp: str, data: List[int]) -> str: + """Compute a Bech32 or Bech32m string given HRP and data values.""" + combined = data + bech32_create_checksum(encoding, hrp, data) return hrp + '1' + ''.join([CHARSET[d] for d in combined]) -def bech32_decode(bech: str, ignore_long_length=False): - """Validate a Bech32 string, and determine HRP and data.""" +def bech32_decode(bech: str, *, ignore_long_length=False) -> DecodedBech32: + """Validate a Bech32/Bech32m string, and determine HRP and data.""" bech_lower = bech.lower() if bech_lower != bech and bech.upper() != bech: - return (None, None) + return DecodedBech32(None, None, None) pos = bech.rfind('1') if pos < 1 or pos + 7 > len(bech) or (not ignore_long_length and len(bech) > 90): - return (None, None) + return DecodedBech32(None, None, None) # check that HRP only consists of sane ASCII chars if any(ord(x) < 33 or ord(x) > 126 for x in bech[:pos+1]): - return (None, None) + return DecodedBech32(None, None, None) bech = bech_lower hrp = bech[:pos] try: data = [_CHARSET_INVERSE[x] for x in bech[pos+1:]] except KeyError: - return (None, None) - if not bech32_verify_checksum(hrp, data): - return (None, None) - return (hrp, data[:-6]) + return DecodedBech32(None, None, None) + encoding = bech32_verify_checksum(hrp, data) + if encoding is None: + return DecodedBech32(None, None, None) + return DecodedBech32(encoding=encoding, hrp=hrp, data=data[:-6]) def convertbits(data, frombits, tobits, pad=True): @@ -106,11 +131,11 @@ def convertbits(data, frombits, tobits, pad=True): return ret -def decode(hrp, addr): +def decode_segwit_address(hrp: str, addr: Optional[str]) -> Tuple[Optional[int], Optional[Sequence[int]]]: """Decode a segwit address.""" if addr is None: return (None, None) - hrpgot, data = bech32_decode(addr) + encoding, hrpgot, data = bech32_decode(addr) if hrpgot != hrp: return (None, None) decoded = convertbits(data[1:], 5, 8, False) @@ -120,11 +145,15 @@ def decode(hrp, addr): return (None, None) if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32: return (None, None) + if (data[0] == 0 and encoding != Encoding.BECH32) or (data[0] != 0 and encoding != Encoding.BECH32M): + return (None, None) return (data[0], decoded) -def encode(hrp, witver, witprog): +def encode_segwit_address(hrp: str, witver: int, witprog: bytes) -> Optional[str]: """Encode a segwit address.""" - ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5)) - assert decode(hrp, ret) != (None, None) + encoding = Encoding.BECH32 if witver == 0 else Encoding.BECH32M + ret = bech32_encode(encoding, hrp, [witver] + convertbits(witprog, 8, 5)) + if decode_segwit_address(hrp, ret) == (None, None): + return None return ret diff --git a/electrum/tests/test_bitcoin.py b/electrum/tests/test_bitcoin.py index b5c0a59ec..ee57e6048 100644 --- a/electrum/tests/test_bitcoin.py +++ b/electrum/tests/test_bitcoin.py @@ -11,6 +11,7 @@ from electrum.bitcoin import (public_key_to_p2pkh, address_from_private_key, opcodes, base_encode, base_decode, BitcoinException) from electrum import bip32 from electrum import segwit_addr +from electrum.segwit_addr import DecodedBech32 from electrum.bip32 import (BIP32Node, convert_bip32_intpath_to_strpath, xpub_from_xprv, xpub_type, is_xprv, is_bip32_derivation, is_xpub, convert_bip32_path_to_list_of_uint32, @@ -426,12 +427,20 @@ class Test_bitcoin(ElectrumTestCase): self.assertEqual(add_number_to_script(2147483647), bfh('04ffffff7f')) def test_address_to_script(self): - # bech32 native segwit + # bech32/bech32m native segwit # test vectors from BIP-0173 + # note: the ones that are commented out have been invalidated by BIP-0350 self.assertEqual(address_to_script('BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4'), '0014751e76e8199196d454941c45d1b3a323f1433bd6') - self.assertEqual(address_to_script('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx'), '5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6') - self.assertEqual(address_to_script('BC1SW50QA3JX3S'), '6002751e') - self.assertEqual(address_to_script('bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj'), '5210751e76e8199196d454941c45d1b3a323') + # self.assertEqual(address_to_script('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx'), '5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6') + # self.assertEqual(address_to_script('BC1SW50QA3JX3S'), '6002751e') + # self.assertEqual(address_to_script('bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj'), '5210751e76e8199196d454941c45d1b3a323') + + # bech32/bech32m native segwit + # test vectors from BIP-0350 + self.assertEqual(address_to_script('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y'), '5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6') + self.assertEqual(address_to_script('BC1SW50QGDZ25J'), '6002751e') + self.assertEqual(address_to_script('bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs'), '5210751e76e8199196d454941c45d1b3a323') + self.assertEqual(address_to_script('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0'), '512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798') # invalid addresses (from BIP-0173) self.assertFalse(is_address('tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty')) @@ -445,6 +454,23 @@ class Test_bitcoin(ElectrumTestCase): self.assertFalse(is_address('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv')) self.assertFalse(is_address('bc1gmk9yu')) + # invalid addresses (from BIP-0350) + self.assertFalse(is_address('tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut')) + self.assertFalse(is_address('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd')) + self.assertFalse(is_address('tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf')) + self.assertFalse(is_address('BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL')) + self.assertFalse(is_address('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh')) + self.assertFalse(is_address('tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47')) + self.assertFalse(is_address('bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4')) + self.assertFalse(is_address('BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R')) + self.assertFalse(is_address('bc1pw5dgrnzv')) + self.assertFalse(is_address('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav')) + self.assertFalse(is_address('BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P')) + self.assertFalse(is_address('tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq')) + self.assertFalse(is_address('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf')) + self.assertFalse(is_address('tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j')) + self.assertFalse(is_address('bc1gmk9yu')) + # base58 P2PKH self.assertEqual(address_to_script('14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG'), '76a91428662c67561b95c79d2257d2a93d9d151c977e9188ac') self.assertEqual(address_to_script('1BEqfzh4Y3zzLosfGhw1AsqbEKVW6e1qHv'), '76a914704f4b81cadb7bf7e68c08cd3657220f680f863c88ac') @@ -453,58 +479,109 @@ class Test_bitcoin(ElectrumTestCase): self.assertEqual(address_to_script('35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT'), 'a9142a84cf00d47f699ee7bbc1dea5ec1bdecb4ac15487') self.assertEqual(address_to_script('3PyjzJ3im7f7bcV724GR57edKDqoZvH7Ji'), 'a914f47c8954e421031ad04ecd8e7752c9479206b9d387') - def test_bech32_decode(self): # bech32 native segwit # test vectors from BIP-0173 - self.assertEqual(('a', []), + self.assertEqual(DecodedBech32(segwit_addr.Encoding.BECH32, 'a', []), segwit_addr.bech32_decode('A12UEL5L')) - self.assertEqual(('a', []), + self.assertEqual(DecodedBech32(segwit_addr.Encoding.BECH32, 'a', []), segwit_addr.bech32_decode('a12uel5l')) - self.assertEqual(('an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio', []), + self.assertEqual(DecodedBech32(segwit_addr.Encoding.BECH32, 'an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio', []), segwit_addr.bech32_decode('an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs')) - self.assertEqual(('abcdef', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]), + self.assertEqual(DecodedBech32(segwit_addr.Encoding.BECH32, 'abcdef', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]), segwit_addr.bech32_decode('abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw')) - self.assertEqual(('1', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + self.assertEqual(DecodedBech32(segwit_addr.Encoding.BECH32, '1', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), segwit_addr.bech32_decode('11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j')) - self.assertEqual(('split', [24, 23, 25, 24, 22, 28, 1, 16, 11, 29, 8, 25, 23, 29, 19, 13, 16, 23, 29, 22, 25, 28, 1, 16, 11, 3, 25, 29, 27, 25, 3, 3, 29, 19, 11, 25, 3, 3, 25, 13, 24, 29, 1, 25, 3, 3, 25, 13]), + self.assertEqual(DecodedBech32(segwit_addr.Encoding.BECH32, 'split', [24, 23, 25, 24, 22, 28, 1, 16, 11, 29, 8, 25, 23, 29, 19, 13, 16, 23, 29, 22, 25, 28, 1, 16, 11, 3, 25, 29, 27, 25, 3, 3, 29, 19, 11, 25, 3, 3, 25, 13, 24, 29, 1, 25, 3, 3, 25, 13]), segwit_addr.bech32_decode('split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w')) - self.assertEqual(('?', []), + self.assertEqual(DecodedBech32(segwit_addr.Encoding.BECH32, '?', []), segwit_addr.bech32_decode('?1ezyfcl')) - self.assertEqual((None, None), + self.assertEqual(DecodedBech32(None, None, None), segwit_addr.bech32_decode('\x201nwldj5')) - self.assertEqual((None, None), + self.assertEqual(DecodedBech32(None, None, None), segwit_addr.bech32_decode('\x7f1axkwrx')) - self.assertEqual((None, None), + self.assertEqual(DecodedBech32(None, None, None), segwit_addr.bech32_decode('\x801eym55h')) - self.assertEqual((None, None), + self.assertEqual(DecodedBech32(None, None, None), segwit_addr.bech32_decode('an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx')) - self.assertEqual((None, None), + self.assertEqual(DecodedBech32(None, None, None), segwit_addr.bech32_decode('pzry9x0s0muk')) - self.assertEqual((None, None), + self.assertEqual(DecodedBech32(None, None, None), segwit_addr.bech32_decode('1pzry9x0s0muk')) - self.assertEqual((None, None), + self.assertEqual(DecodedBech32(None, None, None), segwit_addr.bech32_decode('x1b4n0q5v')) - self.assertEqual((None, None), + self.assertEqual(DecodedBech32(None, None, None), segwit_addr.bech32_decode('li1dgmt3')) - self.assertEqual((None, None), + self.assertEqual(DecodedBech32(None, None, None), segwit_addr.bech32_decode('de1lg7wt\xff')) - self.assertEqual((None, None), + self.assertEqual(DecodedBech32(None, None, None), segwit_addr.bech32_decode('A1G7SGD8')) - self.assertEqual((None, None), + self.assertEqual(DecodedBech32(None, None, None), segwit_addr.bech32_decode('10a06t8')) - self.assertEqual((None, None), + self.assertEqual(DecodedBech32(None, None, None), segwit_addr.bech32_decode('1qzzfhee')) + # test vectors from BIP-0350 + self.assertEqual(DecodedBech32(segwit_addr.Encoding.BECH32M, 'a', []), + segwit_addr.bech32_decode('A1LQFN3A')) + self.assertEqual(DecodedBech32(segwit_addr.Encoding.BECH32M, 'a', []), + segwit_addr.bech32_decode('a1lqfn3a')) + self.assertEqual(DecodedBech32(segwit_addr.Encoding.BECH32M, 'an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber1', []), + segwit_addr.bech32_decode('an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6')) + self.assertEqual(DecodedBech32(segwit_addr.Encoding.BECH32M, 'abcdef', [31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]), + segwit_addr.bech32_decode('abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx')) + self.assertEqual(DecodedBech32(segwit_addr.Encoding.BECH32M, '1', [31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31]), + segwit_addr.bech32_decode('11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8')) + self.assertEqual(DecodedBech32(segwit_addr.Encoding.BECH32M, 'split', [24, 23, 25, 24, 22, 28, 1, 16, 11, 29, 8, 25, 23, 29, 19, 13, 16, 23, 29, 22, 25, 28, 1, 16, 11, 3, 25, 29, 27, 25, 3, 3, 29, 19, 11, 25, 3, 3, 25, 13, 24, 29, 1, 25, 3, 3, 25, 13]), + segwit_addr.bech32_decode('split1checkupstagehandshakeupstreamerranterredcaperredlc445v')) + self.assertEqual(DecodedBech32(segwit_addr.Encoding.BECH32M, '?', []), + segwit_addr.bech32_decode('?1v759aa')) + + self.assertEqual(DecodedBech32(None, None, None), + segwit_addr.bech32_decode('\x201xj0phk')) + self.assertEqual(DecodedBech32(None, None, None), + segwit_addr.bech32_decode('\x7f1g6xzxy')) + self.assertEqual(DecodedBech32(None, None, None), + segwit_addr.bech32_decode('\x801vctc34')) + self.assertEqual(DecodedBech32(None, None, None), + segwit_addr.bech32_decode('an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4')) + self.assertEqual(DecodedBech32(None, None, None), + segwit_addr.bech32_decode('qyrz8wqd2c9m')) + self.assertEqual(DecodedBech32(None, None, None), + segwit_addr.bech32_decode('1qyrz8wqd2c9m')) + self.assertEqual(DecodedBech32(None, None, None), + segwit_addr.bech32_decode('y1b0jsk6g')) + self.assertEqual(DecodedBech32(None, None, None), + segwit_addr.bech32_decode('lt1igcx5c0')) + self.assertEqual(DecodedBech32(None, None, None), + segwit_addr.bech32_decode('in1muywd')) + self.assertEqual(DecodedBech32(None, None, None), + segwit_addr.bech32_decode('mm1crxm3i')) + self.assertEqual(DecodedBech32(None, None, None), + segwit_addr.bech32_decode('au1s5cgom')) + self.assertEqual(DecodedBech32(None, None, None), + segwit_addr.bech32_decode('M1VUXWEZ')) + self.assertEqual(DecodedBech32(None, None, None), + segwit_addr.bech32_decode('16plkw9')) + self.assertEqual(DecodedBech32(None, None, None), + segwit_addr.bech32_decode('1p2gdwpf')) + + class Test_bitcoin_testnet(TestCaseForTestnet): def test_address_to_script(self): - # bech32 native segwit + # bech32/bech32m native segwit # test vectors from BIP-0173 self.assertEqual(address_to_script('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7'), '00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262') self.assertEqual(address_to_script('tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy'), '0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433') + # bech32/bech32m native segwit + # test vectors from BIP-0350 + self.assertEqual(address_to_script('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7'), '00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262') + self.assertEqual(address_to_script('tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy'), '0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433') + self.assertEqual(address_to_script('tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c'), '5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433') + # invalid addresses (from BIP-0173) self.assertFalse(is_address('tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty')) self.assertFalse(is_address('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5')) @@ -517,6 +594,23 @@ class Test_bitcoin_testnet(TestCaseForTestnet): self.assertFalse(is_address('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv')) self.assertFalse(is_address('bc1gmk9yu')) + # invalid addresses (from BIP-0350) + self.assertFalse(is_address('tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut')) + self.assertFalse(is_address('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd')) + self.assertFalse(is_address('tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf')) + self.assertFalse(is_address('BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL')) + self.assertFalse(is_address('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh')) + self.assertFalse(is_address('tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47')) + self.assertFalse(is_address('bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4')) + self.assertFalse(is_address('BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R')) + self.assertFalse(is_address('bc1pw5dgrnzv')) + self.assertFalse(is_address('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav')) + self.assertFalse(is_address('BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P')) + self.assertFalse(is_address('tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq')) + self.assertFalse(is_address('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf')) + self.assertFalse(is_address('tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j')) + self.assertFalse(is_address('bc1gmk9yu')) + # base58 P2PKH self.assertEqual(address_to_script('mutXcGt1CJdkRvXuN2xoz2quAAQYQ59bRX'), '76a9149da64e300c5e4eb4aaffc9c2fd465348d5618ad488ac') self.assertEqual(address_to_script('miqtaRTkU3U8rzwKbEHx3g8FSz8GJtPS3K'), '76a914247d2d5b6334bdfa2038e85b20fc15264f8e5d2788ac') diff --git a/electrum/tests/test_bolt11.py b/electrum/tests/test_bolt11.py index 7646365ca..4841eb62b 100644 --- a/electrum/tests/test_bolt11.py +++ b/electrum/tests/test_bolt11.py @@ -6,6 +6,7 @@ import unittest from electrum.lnaddr import shorten_amount, unshorten_amount, LnAddr, lnencode, lndecode, u5_to_bitarray, bitarray_to_u5 from electrum.segwit_addr import bech32_encode, bech32_decode +from electrum import segwit_addr from electrum.lnutil import UnknownEvenFeatureBits, derive_payment_secret_from_payment_preimage, LnFeatures from . import ElectrumTestCase @@ -114,20 +115,21 @@ class TestBolt11(ElectrumTestCase): def test_n_decoding(self): # We flip the signature recovery bit, which would normally give a different # pubkey. - hrp, data = bech32_decode(lnencode(LnAddr(paymenthash=RHASH, amount=24, tags=[('d', '')]), PRIVKEY), True) + _, hrp, data = bech32_decode( + lnencode(LnAddr(paymenthash=RHASH, amount=24, tags=[('d', '')]), PRIVKEY), + ignore_long_length=True) databits = u5_to_bitarray(data) databits.invert(-1) - lnaddr = lndecode(bech32_encode(hrp, bitarray_to_u5(databits)), verbose=True) + lnaddr = lndecode(bech32_encode(segwit_addr.Encoding.BECH32, hrp, bitarray_to_u5(databits)), verbose=True) assert lnaddr.pubkey.serialize() != PUBKEY # But not if we supply expliciy `n` specifier! - hrp, data = bech32_decode(lnencode(LnAddr(paymenthash=RHASH, amount=24, - tags=[('d', ''), - ('n', PUBKEY)]), - PRIVKEY), True) + _, hrp, data = bech32_decode( + lnencode(LnAddr(paymenthash=RHASH, amount=24, tags=[('d', ''), ('n', PUBKEY)]), PRIVKEY), + ignore_long_length=True) databits = u5_to_bitarray(data) databits.invert(-1) - lnaddr = lndecode(bech32_encode(hrp, bitarray_to_u5(databits)), verbose=True) + lnaddr = lndecode(bech32_encode(segwit_addr.Encoding.BECH32, hrp, bitarray_to_u5(databits)), verbose=True) assert lnaddr.pubkey.serialize() == PUBKEY def test_min_final_cltv_expiry_decoding(self): diff --git a/electrum/tests/test_transaction.py b/electrum/tests/test_transaction.py index 575d65ae6..309f01b0f 100644 --- a/electrum/tests/test_transaction.py +++ b/electrum/tests/test_transaction.py @@ -158,12 +158,13 @@ class TestTransaction(ElectrumTestCase): # the inverse of this test is in test_bitcoin: test_address_to_script addr_from_script = lambda script: transaction.get_address_from_output_script(bfh(script)) - # bech32 native segwit - # test vectors from BIP-0173 + # bech32/bech32m native segwit + # test vectors from BIP-0173/BIP-0350 self.assertEqual('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4', addr_from_script('0014751e76e8199196d454941c45d1b3a323f1433bd6')) - self.assertEqual('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx', addr_from_script('5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6')) - self.assertEqual('bc1sw50qa3jx3s', addr_from_script('6002751e')) - self.assertEqual('bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj', addr_from_script('5210751e76e8199196d454941c45d1b3a323')) + self.assertEqual('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y', addr_from_script('5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6')) + self.assertEqual('bc1sw50qgdz25j', addr_from_script('6002751e')) + self.assertEqual('bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs', addr_from_script('5210751e76e8199196d454941c45d1b3a323')) + self.assertEqual('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0', addr_from_script('512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')) # almost but not quite self.assertEqual(None, addr_from_script('0013751e76e8199196d454941c45d1b3a323f1433b')) diff --git a/electrum/transaction.py b/electrum/transaction.py index 08d0c779e..98028cda4 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -687,7 +687,7 @@ class Transaction: # the estimation will not be precise. if addr is None: return 'p2wpkh' - witver, witprog = segwit_addr.decode(constants.net.SEGWIT_HRP, addr) + witver, witprog = segwit_addr.decode_segwit_address(constants.net.SEGWIT_HRP, addr) if witprog is not None: return 'p2wpkh' addrtype, hash_160_ = b58_address_to_hash160(addr)