lnaddr: clean-up SEGWIT_HRP vs BOLT11_HRP confusion
With signet, SEGWIT_HRP != BOLT11_HRP, so the previous "currency" string became a flawed concept. Instead we pass around net objects now.
This commit is contained in:
@@ -26,7 +26,7 @@
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from .util import inv_dict
|
from .util import inv_dict, all_subclasses
|
||||||
from . import bitcoin
|
from . import bitcoin
|
||||||
|
|
||||||
|
|
||||||
@@ -47,7 +47,17 @@ BIP39_WALLET_FORMATS = read_json('bip39_wallet_formats.json', [])
|
|||||||
|
|
||||||
class AbstractNet:
|
class AbstractNet:
|
||||||
|
|
||||||
BLOCK_HEIGHT_FIRST_LIGHTNING_CHANNELS = 0
|
NET_NAME: str
|
||||||
|
TESTNET: bool
|
||||||
|
WIF_PREFIX: int
|
||||||
|
ADDRTYPE_P2PKH: int
|
||||||
|
ADDRTYPE_P2SH: int
|
||||||
|
SEGWIT_HRP: str
|
||||||
|
BOLT11_HRP: str
|
||||||
|
GENESIS: str
|
||||||
|
BLOCK_HEIGHT_FIRST_LIGHTNING_CHANNELS: int = 0
|
||||||
|
BIP44_COIN_TYPE: int
|
||||||
|
LN_REALM_BYTE: int
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def max_checkpoint(cls) -> int:
|
def max_checkpoint(cls) -> int:
|
||||||
@@ -171,6 +181,8 @@ class BitcoinSignet(BitcoinTestnet):
|
|||||||
LN_DNS_SEEDS = []
|
LN_DNS_SEEDS = []
|
||||||
|
|
||||||
|
|
||||||
|
NETS_LIST = tuple(all_subclasses(AbstractNet))
|
||||||
|
|
||||||
# don't import net directly, import the module instead (so that net is singleton)
|
# don't import net directly, import the module instead (so that net is singleton)
|
||||||
net = BitcoinMainnet
|
net = BitcoinMainnet
|
||||||
|
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ class SendScreen(CScreen, Logger):
|
|||||||
def set_ln_invoice(self, invoice: str):
|
def set_ln_invoice(self, invoice: str):
|
||||||
try:
|
try:
|
||||||
invoice = str(invoice).lower()
|
invoice = str(invoice).lower()
|
||||||
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
lnaddr = lndecode(invoice)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.app.show_info(invoice + _(" is not a valid Lightning invoice: ") + repr(e)) # repr because str(Exception()) == ''
|
self.app.show_info(invoice + _(" is not a valid Lightning invoice: ") + repr(e)) # repr because str(Exception()) == ''
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1969,7 +1969,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||||||
def parse_lightning_invoice(self, invoice):
|
def parse_lightning_invoice(self, invoice):
|
||||||
"""Parse ln invoice, and prepare the send tab for it."""
|
"""Parse ln invoice, and prepare the send tab for it."""
|
||||||
try:
|
try:
|
||||||
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
lnaddr = lndecode(invoice)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise LnDecodeException(e) from e
|
raise LnDecodeException(e) from e
|
||||||
pubkey = bh2u(lnaddr.pubkey.serialize())
|
pubkey = bh2u(lnaddr.pubkey.serialize())
|
||||||
@@ -2180,7 +2180,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
def show_lightning_invoice(self, invoice: LNInvoice):
|
def show_lightning_invoice(self, invoice: LNInvoice):
|
||||||
lnaddr = lndecode(invoice.invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
lnaddr = lndecode(invoice.invoice)
|
||||||
d = WindowModalDialog(self, _("Lightning Invoice"))
|
d = WindowModalDialog(self, _("Lightning Invoice"))
|
||||||
vbox = QVBoxLayout(d)
|
vbox = QVBoxLayout(d)
|
||||||
grid = QGridLayout()
|
grid = QGridLayout()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import time
|
|||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import Optional, TYPE_CHECKING
|
from typing import Optional, TYPE_CHECKING, Type
|
||||||
|
|
||||||
import random
|
import random
|
||||||
import bitstring
|
import bitstring
|
||||||
@@ -15,6 +15,7 @@ from .bitcoin import hash160_to_b58_address, b58_address_to_hash160, TOTAL_COIN_
|
|||||||
from .segwit_addr import bech32_encode, bech32_decode, CHARSET
|
from .segwit_addr import bech32_encode, bech32_decode, CHARSET
|
||||||
from . import segwit_addr
|
from . import segwit_addr
|
||||||
from . import constants
|
from . import constants
|
||||||
|
from .constants import AbstractNet
|
||||||
from . import ecc
|
from . import ecc
|
||||||
from .bitcoin import COIN
|
from .bitcoin import COIN
|
||||||
|
|
||||||
@@ -83,57 +84,42 @@ def bitarray_to_u5(barr):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def encode_fallback(fallback: str, currency):
|
def encode_fallback(fallback: str, net: Type[AbstractNet]):
|
||||||
""" Encode all supported fallback addresses.
|
""" Encode all supported fallback addresses.
|
||||||
"""
|
"""
|
||||||
if currency in [constants.BitcoinMainnet.SEGWIT_HRP, constants.BitcoinTestnet.SEGWIT_HRP]:
|
wver, wprog_ints = segwit_addr.decode_segwit_address(net.SEGWIT_HRP, fallback)
|
||||||
wver, wprog_ints = segwit_addr.decode_segwit_address(currency, fallback)
|
if wver is not None:
|
||||||
if wver is not None:
|
wprog = bytes(wprog_ints)
|
||||||
wprog = bytes(wprog_ints)
|
|
||||||
else:
|
|
||||||
addrtype, addr = b58_address_to_hash160(fallback)
|
|
||||||
if is_p2pkh(currency, addrtype):
|
|
||||||
wver = 17
|
|
||||||
elif is_p2sh(currency, addrtype):
|
|
||||||
wver = 18
|
|
||||||
else:
|
|
||||||
raise ValueError("Unknown address type for {}".format(currency))
|
|
||||||
wprog = addr
|
|
||||||
return tagged('f', bitstring.pack("uint:5", wver) + wprog)
|
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("Support for currency {} not implemented".format(currency))
|
addrtype, addr = b58_address_to_hash160(fallback)
|
||||||
|
if addrtype == net.ADDRTYPE_P2PKH:
|
||||||
|
wver = 17
|
||||||
|
elif addrtype == net.ADDRTYPE_P2SH:
|
||||||
|
wver = 18
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown address type {addrtype} for {net}")
|
||||||
|
wprog = addr
|
||||||
|
return tagged('f', bitstring.pack("uint:5", wver) + wprog)
|
||||||
|
|
||||||
|
|
||||||
def parse_fallback(fallback, currency):
|
def parse_fallback(fallback, net: Type[AbstractNet]):
|
||||||
if currency in [constants.BitcoinMainnet.SEGWIT_HRP, constants.BitcoinTestnet.SEGWIT_HRP]:
|
wver = fallback[0:5].uint
|
||||||
wver = fallback[0:5].uint
|
if wver == 17:
|
||||||
if wver == 17:
|
addr = hash160_to_b58_address(fallback[5:].tobytes(), net.ADDRTYPE_P2PKH)
|
||||||
addr=hash160_to_b58_address(fallback[5:].tobytes(), base58_prefix_map[currency][0])
|
elif wver == 18:
|
||||||
elif wver == 18:
|
addr = hash160_to_b58_address(fallback[5:].tobytes(), net.ADDRTYPE_P2SH)
|
||||||
addr=hash160_to_b58_address(fallback[5:].tobytes(), base58_prefix_map[currency][1])
|
elif wver <= 16:
|
||||||
elif wver <= 16:
|
witprog = fallback[5:] # cut witver
|
||||||
witprog = fallback[5:] # cut witver
|
witprog = witprog[:len(witprog) // 8 * 8] # can only be full bytes
|
||||||
witprog = witprog[:len(witprog) // 8 * 8] # can only be full bytes
|
witprog = witprog.tobytes()
|
||||||
witprog = witprog.tobytes()
|
addr = segwit_addr.encode_segwit_address(net.SEGWIT_HRP, wver, witprog)
|
||||||
addr = segwit_addr.encode_segwit_address(currency, wver, witprog)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
else:
|
else:
|
||||||
addr=fallback.tobytes()
|
return None
|
||||||
return addr
|
return addr
|
||||||
|
|
||||||
|
|
||||||
# Map of classical and witness address prefixes
|
BOLT11_HRP_INV_DICT = {net.BOLT11_HRP: net for net in constants.NETS_LIST}
|
||||||
base58_prefix_map = {
|
|
||||||
constants.BitcoinMainnet.SEGWIT_HRP : (constants.BitcoinMainnet.ADDRTYPE_P2PKH, constants.BitcoinMainnet.ADDRTYPE_P2SH),
|
|
||||||
constants.BitcoinTestnet.SEGWIT_HRP : (constants.BitcoinTestnet.ADDRTYPE_P2PKH, constants.BitcoinTestnet.ADDRTYPE_P2SH)
|
|
||||||
}
|
|
||||||
|
|
||||||
def is_p2pkh(currency, prefix):
|
|
||||||
return prefix == base58_prefix_map[currency][0]
|
|
||||||
|
|
||||||
def is_p2sh(currency, prefix):
|
|
||||||
return prefix == base58_prefix_map[currency][1]
|
|
||||||
|
|
||||||
# Tagged field containing BitArray
|
# Tagged field containing BitArray
|
||||||
def tagged(char, l):
|
def tagged(char, l):
|
||||||
@@ -178,13 +164,10 @@ def pull_tagged(stream):
|
|||||||
return (CHARSET[tag], stream.read(length * 5), stream)
|
return (CHARSET[tag], stream.read(length * 5), stream)
|
||||||
|
|
||||||
def lnencode(addr: 'LnAddr', privkey) -> str:
|
def lnencode(addr: 'LnAddr', privkey) -> str:
|
||||||
# see https://github.com/lightningnetwork/lightning-rfc/pull/844
|
|
||||||
if constants.net.NET_NAME == "signet":
|
|
||||||
addr.currency = constants.net.BOLT11_HRP
|
|
||||||
if addr.amount:
|
if addr.amount:
|
||||||
amount = addr.currency + shorten_amount(addr.amount)
|
amount = addr.net.BOLT11_HRP + shorten_amount(addr.amount)
|
||||||
else:
|
else:
|
||||||
amount = addr.currency if addr.currency else ''
|
amount = addr.net.BOLT11_HRP if addr.net else ''
|
||||||
|
|
||||||
hrp = 'ln' + amount
|
hrp = 'ln' + amount
|
||||||
|
|
||||||
@@ -221,7 +204,7 @@ def lnencode(addr: 'LnAddr', privkey) -> str:
|
|||||||
route = bitstring.BitArray(pubkey) + bitstring.pack('intbe:32', feebase) + bitstring.pack('intbe:32', feerate) + bitstring.pack('intbe:16', cltv)
|
route = bitstring.BitArray(pubkey) + bitstring.pack('intbe:32', feebase) + bitstring.pack('intbe:32', feerate) + bitstring.pack('intbe:16', cltv)
|
||||||
data += tagged('t', route)
|
data += tagged('t', route)
|
||||||
elif k == 'f':
|
elif k == 'f':
|
||||||
data += encode_fallback(v, addr.currency)
|
data += encode_fallback(v, addr.net)
|
||||||
elif k == 'd':
|
elif k == 'd':
|
||||||
# truncate to max length: 1024*5 bits = 639 bytes
|
# truncate to max length: 1024*5 bits = 639 bytes
|
||||||
data += tagged_bytes('d', v.encode()[0:639])
|
data += tagged_bytes('d', v.encode()[0:639])
|
||||||
@@ -270,7 +253,7 @@ def lnencode(addr: 'LnAddr', privkey) -> str:
|
|||||||
|
|
||||||
|
|
||||||
class LnAddr(object):
|
class LnAddr(object):
|
||||||
def __init__(self, *, paymenthash: bytes = None, amount=None, currency=None, tags=None, date=None,
|
def __init__(self, *, paymenthash: bytes = None, amount=None, net: Type[AbstractNet] = None, tags=None, date=None,
|
||||||
payment_secret: bytes = None):
|
payment_secret: bytes = None):
|
||||||
self.date = int(time.time()) if not date else int(date)
|
self.date = int(time.time()) if not date else int(date)
|
||||||
self.tags = [] if not tags else tags
|
self.tags = [] if not tags else tags
|
||||||
@@ -279,7 +262,7 @@ class LnAddr(object):
|
|||||||
self.payment_secret = payment_secret
|
self.payment_secret = payment_secret
|
||||||
self.signature = None
|
self.signature = None
|
||||||
self.pubkey = None
|
self.pubkey = None
|
||||||
self.currency = constants.net.SEGWIT_HRP if currency is None else currency
|
self.net = constants.net if net is None else net # type: Type[AbstractNet]
|
||||||
self._amount = amount # type: Optional[Decimal] # in bitcoins
|
self._amount = amount # type: Optional[Decimal] # in bitcoins
|
||||||
self._min_final_cltv_expiry = 18
|
self._min_final_cltv_expiry = 18
|
||||||
|
|
||||||
@@ -330,7 +313,7 @@ class LnAddr(object):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "LnAddr[{}, amount={}{} tags=[{}]]".format(
|
return "LnAddr[{}, amount={}{} tags=[{}]]".format(
|
||||||
hexlify(self.pubkey.serialize()).decode('utf-8') if self.pubkey else None,
|
hexlify(self.pubkey.serialize()).decode('utf-8') if self.pubkey else None,
|
||||||
self.amount, self.currency,
|
self.amount, self.net.BOLT11_HRP,
|
||||||
", ".join([k + '=' + str(v) for k, v in self.tags])
|
", ".join([k + '=' + str(v) for k, v in self.tags])
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -367,12 +350,9 @@ class SerializableKey:
|
|||||||
def serialize(self):
|
def serialize(self):
|
||||||
return self.pubkey.get_public_key_bytes(True)
|
return self.pubkey.get_public_key_bytes(True)
|
||||||
|
|
||||||
def lndecode(invoice: str, *, verbose=False, expected_hrp=None) -> LnAddr:
|
def lndecode(invoice: str, *, verbose=False, net=None) -> LnAddr:
|
||||||
if expected_hrp is None:
|
if net is None:
|
||||||
expected_hrp = constants.net.SEGWIT_HRP
|
net = constants.net
|
||||||
# see https://github.com/lightningnetwork/lightning-rfc/pull/844
|
|
||||||
if constants.net.NET_NAME == "signet":
|
|
||||||
expected_hrp = constants.net.BOLT11_HRP
|
|
||||||
decoded_bech32 = bech32_decode(invoice, ignore_long_length=True)
|
decoded_bech32 = bech32_decode(invoice, ignore_long_length=True)
|
||||||
hrp = decoded_bech32.hrp
|
hrp = decoded_bech32.hrp
|
||||||
data = decoded_bech32.data
|
data = decoded_bech32.data
|
||||||
@@ -387,8 +367,8 @@ def lndecode(invoice: str, *, verbose=False, expected_hrp=None) -> LnAddr:
|
|||||||
if not hrp.startswith('ln'):
|
if not hrp.startswith('ln'):
|
||||||
raise ValueError("Does not start with ln")
|
raise ValueError("Does not start with ln")
|
||||||
|
|
||||||
if not hrp[2:].startswith(expected_hrp):
|
if not hrp[2:].startswith(net.BOLT11_HRP):
|
||||||
raise ValueError("Wrong Lightning invoice HRP " + hrp[2:] + ", should be " + expected_hrp)
|
raise ValueError(f"Wrong Lightning invoice HRP {hrp[2:]}, should be {net.BOLT11_HRP}")
|
||||||
|
|
||||||
data = u5_to_bitarray(data)
|
data = u5_to_bitarray(data)
|
||||||
|
|
||||||
@@ -403,7 +383,7 @@ def lndecode(invoice: str, *, verbose=False, expected_hrp=None) -> LnAddr:
|
|||||||
|
|
||||||
m = re.search("[^\\d]+", hrp[2:])
|
m = re.search("[^\\d]+", hrp[2:])
|
||||||
if m:
|
if m:
|
||||||
addr.currency = m.group(0)
|
addr.net = BOLT11_HRP_INV_DICT[m.group(0)]
|
||||||
amountstr = hrp[2+m.end():]
|
amountstr = hrp[2+m.end():]
|
||||||
# BOLT #11:
|
# BOLT #11:
|
||||||
#
|
#
|
||||||
@@ -453,7 +433,7 @@ def lndecode(invoice: str, *, verbose=False, expected_hrp=None) -> LnAddr:
|
|||||||
s.read(16).uintbe)
|
s.read(16).uintbe)
|
||||||
addr.tags.append(('t', e))
|
addr.tags.append(('t', e))
|
||||||
elif tag == 'f':
|
elif tag == 'f':
|
||||||
fallback = parse_fallback(tagdata, addr.currency)
|
fallback = parse_fallback(tagdata, addr.net)
|
||||||
if fallback:
|
if fallback:
|
||||||
addr.tags.append(('f', fallback))
|
addr.tags.append(('f', fallback))
|
||||||
else:
|
else:
|
||||||
@@ -532,13 +512,3 @@ def lndecode(invoice: str, *, verbose=False, expected_hrp=None) -> LnAddr:
|
|||||||
addr.pubkey = SerializableKey(ecc.ECPubkey.from_sig_string(sigdecoded[:64], sigdecoded[64], hrp_hash))
|
addr.pubkey = SerializableKey(ecc.ECPubkey.from_sig_string(sigdecoded[:64], sigdecoded[64], hrp_hash))
|
||||||
|
|
||||||
return addr
|
return addr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# run using
|
|
||||||
# python3 -m electrum.lnaddr <invoice> <expected hrp>
|
|
||||||
# python3 -m electrum.lnaddr lntb1n1pdlcakepp5e7rn0knl0gm46qqp9eqdsza2c942d8pjqnwa5903n39zu28sgk3sdq423jhxapqv3hkuct5d9hkucqp2rzjqwyx8nu2hygyvgc02cwdtvuxe0lcxz06qt3lpsldzcdr46my5epmj9vk9sqqqlcqqqqqqqlgqqqqqqgqjqdhnmkgahfaynuhe9md8k49xhxuatnv6jckfmsjq8maxta2l0trh5sdrqlyjlwutdnpd5gwmdnyytsl9q0dj6g08jacvthtpeg383k0sq542rz2 tb1n
|
|
||||||
import sys
|
|
||||||
print(lndecode(sys.argv[1], expected_hrp=sys.argv[2]))
|
|
||||||
|
|||||||
@@ -1398,7 +1398,7 @@ class LNWallet(LNWorker):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _check_invoice(invoice: str, *, amount_msat: int = None) -> LnAddr:
|
def _check_invoice(invoice: str, *, amount_msat: int = None) -> LnAddr:
|
||||||
addr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
addr = lndecode(invoice)
|
||||||
if addr.is_expired():
|
if addr.is_expired():
|
||||||
raise InvoiceError(_("This invoice has expired"))
|
raise InvoiceError(_("This invoice has expired"))
|
||||||
if amount_msat: # replace amt in invoice. main usecase is paying zero amt invoices
|
if amount_msat: # replace amt in invoice. main usecase is paying zero amt invoices
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from electrum.lnaddr import shorten_amount, unshorten_amount, LnAddr, lnencode,
|
|||||||
from electrum.segwit_addr import bech32_encode, bech32_decode
|
from electrum.segwit_addr import bech32_encode, bech32_decode
|
||||||
from electrum import segwit_addr
|
from electrum import segwit_addr
|
||||||
from electrum.lnutil import UnknownEvenFeatureBits, derive_payment_secret_from_payment_preimage, LnFeatures
|
from electrum.lnutil import UnknownEvenFeatureBits, derive_payment_secret_from_payment_preimage, LnFeatures
|
||||||
|
from electrum import constants
|
||||||
|
|
||||||
from . import ElectrumTestCase
|
from . import ElectrumTestCase
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ class TestBolt11(ElectrumTestCase):
|
|||||||
"lnbc1m1ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpu9rflz25dx0qw6kdg05u0c5hdc30yq6ga6ew4pz86n244va45nchns9zrs3wjxznsqnt37hz7pswvc56wvuhxcjyd6k3lqf4ujynyxuspmvr078"),
|
"lnbc1m1ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpu9rflz25dx0qw6kdg05u0c5hdc30yq6ga6ew4pz86n244va45nchns9zrs3wjxznsqnt37hz7pswvc56wvuhxcjyd6k3lqf4ujynyxuspmvr078"),
|
||||||
(LnAddr(date=timestamp, paymenthash=RHASH, amount=Decimal('1'), tags=[('h', longdescription)]),
|
(LnAddr(date=timestamp, paymenthash=RHASH, amount=Decimal('1'), tags=[('h', longdescription)]),
|
||||||
"lnbc11ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs2qjafckq94q3js6lvqz2kmenn9ysjejyj8fm4hlx0xtqhaxfzlxjappkgp0hmm40dnuan4v3jy83lqjup2n0fdzgysg049y9l9uc98qq07kfd3"),
|
"lnbc11ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs2qjafckq94q3js6lvqz2kmenn9ysjejyj8fm4hlx0xtqhaxfzlxjappkgp0hmm40dnuan4v3jy83lqjup2n0fdzgysg049y9l9uc98qq07kfd3"),
|
||||||
(LnAddr(date=timestamp, paymenthash=RHASH, currency='tb', tags=[('f', 'mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP'), ('h', longdescription)]),
|
(LnAddr(date=timestamp, paymenthash=RHASH, net=constants.BitcoinTestnet, tags=[('f', 'mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP'), ('h', longdescription)]),
|
||||||
"lntb1ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfpp3x9et2e20v6pu37c5d9vax37wxq72un98hp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsr9zktgu78k8p9t8555ve37qwfvqn6ga37fnfwhgexmf20nzdpmuhwvuv7zra3xrh8y2ggxxuemqfsgka9x7uzsrcx8rfv85c8pmhq9gq4sampn"),
|
"lntb1ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfpp3x9et2e20v6pu37c5d9vax37wxq72un98hp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsr9zktgu78k8p9t8555ve37qwfvqn6ga37fnfwhgexmf20nzdpmuhwvuv7zra3xrh8y2ggxxuemqfsgka9x7uzsrcx8rfv85c8pmhq9gq4sampn"),
|
||||||
(LnAddr(date=timestamp, paymenthash=RHASH, amount=24, tags=[
|
(LnAddr(date=timestamp, paymenthash=RHASH, amount=24, tags=[
|
||||||
('r', [(unhexlify('029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'), unhexlify('0102030405060708'), 1, 20, 3),
|
('r', [(unhexlify('029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'), unhexlify('0102030405060708'), 1, 20, 3),
|
||||||
@@ -109,7 +110,7 @@ class TestBolt11(ElectrumTestCase):
|
|||||||
for lnaddr1, invoice_str1 in tests:
|
for lnaddr1, invoice_str1 in tests:
|
||||||
invoice_str2 = lnencode(lnaddr1, PRIVKEY)
|
invoice_str2 = lnencode(lnaddr1, PRIVKEY)
|
||||||
self.assertEqual(invoice_str1, invoice_str2)
|
self.assertEqual(invoice_str1, invoice_str2)
|
||||||
lnaddr2 = lndecode(invoice_str2, expected_hrp=lnaddr1.currency)
|
lnaddr2 = lndecode(invoice_str2, net=lnaddr1.net)
|
||||||
self.compare(lnaddr1, lnaddr2)
|
self.compare(lnaddr1, lnaddr2)
|
||||||
|
|
||||||
def test_n_decoding(self):
|
def test_n_decoding(self):
|
||||||
@@ -134,11 +135,11 @@ class TestBolt11(ElectrumTestCase):
|
|||||||
|
|
||||||
def test_min_final_cltv_expiry_decoding(self):
|
def test_min_final_cltv_expiry_decoding(self):
|
||||||
lnaddr = lndecode("lnsb500u1pdsgyf3pp5nmrqejdsdgs4n9ukgxcp2kcq265yhrxd4k5dyue58rxtp5y83s3qdqqcqzystrggccm9yvkr5yqx83jxll0qjpmgfg9ywmcd8g33msfgmqgyfyvqhku80qmqm8q6v35zvck2y5ccxsz5avtrauz8hgjj3uahppyq20qp6dvwxe",
|
lnaddr = lndecode("lnsb500u1pdsgyf3pp5nmrqejdsdgs4n9ukgxcp2kcq265yhrxd4k5dyue58rxtp5y83s3qdqqcqzystrggccm9yvkr5yqx83jxll0qjpmgfg9ywmcd8g33msfgmqgyfyvqhku80qmqm8q6v35zvck2y5ccxsz5avtrauz8hgjj3uahppyq20qp6dvwxe",
|
||||||
expected_hrp="sb")
|
net=constants.BitcoinSimnet)
|
||||||
self.assertEqual(144, lnaddr.get_min_final_cltv_expiry())
|
self.assertEqual(144, lnaddr.get_min_final_cltv_expiry())
|
||||||
|
|
||||||
lnaddr = lndecode("lntb15u1p0m6lzupp5zqjthgvaad9mewmdjuehwddyze9d8zyxcc43zhaddeegt37sndgsdq4xysyymr0vd4kzcmrd9hx7cqp7xqrrss9qy9qsqsp5vlhcs24hwm747w8f3uau2tlrdkvjaglffnsstwyamj84cxuhrn2s8tut3jqumepu42azyyjpgqa4w9w03204zp9h4clk499y2umstl6s29hqyj8vv4as6zt5567ux7l3f66m8pjhk65zjaq2esezk7ll2kcpljewkg",
|
lnaddr = lndecode("lntb15u1p0m6lzupp5zqjthgvaad9mewmdjuehwddyze9d8zyxcc43zhaddeegt37sndgsdq4xysyymr0vd4kzcmrd9hx7cqp7xqrrss9qy9qsqsp5vlhcs24hwm747w8f3uau2tlrdkvjaglffnsstwyamj84cxuhrn2s8tut3jqumepu42azyyjpgqa4w9w03204zp9h4clk499y2umstl6s29hqyj8vv4as6zt5567ux7l3f66m8pjhk65zjaq2esezk7ll2kcpljewkg",
|
||||||
expected_hrp="tb")
|
net=constants.BitcoinTestnet)
|
||||||
self.assertEqual(30, lnaddr.get_min_final_cltv_expiry())
|
self.assertEqual(30, lnaddr.get_min_final_cltv_expiry())
|
||||||
|
|
||||||
def test_min_final_cltv_expiry_roundtrip(self):
|
def test_min_final_cltv_expiry_roundtrip(self):
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import binascii
|
|||||||
import os, sys, re, json
|
import os, sys, re, json
|
||||||
from collections import defaultdict, OrderedDict
|
from collections import defaultdict, OrderedDict
|
||||||
from typing import (NamedTuple, Union, TYPE_CHECKING, Tuple, Optional, Callable, Any,
|
from typing import (NamedTuple, Union, TYPE_CHECKING, Tuple, Optional, Callable, Any,
|
||||||
Sequence, Dict, Generic, TypeVar, List, Iterable)
|
Sequence, Dict, Generic, TypeVar, List, Iterable, Set)
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import decimal
|
import decimal
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
@@ -72,6 +72,14 @@ def inv_dict(d):
|
|||||||
return {v: k for k, v in d.items()}
|
return {v: k for k, v in d.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def all_subclasses(cls) -> Set:
|
||||||
|
"""Return all (transitive) subclasses of cls."""
|
||||||
|
res = set(cls.__subclasses__())
|
||||||
|
for sub in res.copy():
|
||||||
|
res |= all_subclasses(sub)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
ca_path = certifi.where()
|
ca_path = certifi.where()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user