@ -1,18 +1,17 @@
#! /usr/bin/env python3
#! /usr/bin/env python3
# This was forked from https://github.com/rustyrussell/lightning-payencode/tree/acc16ec13a3fa1dc16c07af6ec67c261bd8aff23
# This was forked from https://github.com/rustyrussell/lightning-payencode/tree/acc16ec13a3fa1dc16c07af6ec67c261bd8aff23
import io
import re
import re
import time
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 , Type , Dict , Any
from typing import Optional , TYPE_CHECKING , Type , Dict , Any , Union , Sequence , List , Tuple
import random
import random
import bitstring
from . bitcoin import hash160_to_b58_address , b58_address_to_hash160 , TOTAL_COIN_SUPPLY_LIMIT_IN_BTC
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 . segwit_addr import bech32_encode , bech32_decode , CHARSET , CHARSET_INVERSE , convertbits
from . import segwit_addr
from . import segwit_addr
from . import constants
from . import constants
from . constants import AbstractNet
from . constants import AbstractNet
@ -75,25 +74,9 @@ def unshorten_amount(amount) -> Decimal:
else :
else :
return Decimal ( amount )
return Decimal ( amount )
_INT_TO_BINSTR = { a : ' 0 ' * ( 5 - len ( bin ( a ) [ 2 : ] ) ) + bin ( a ) [ 2 : ] for a in range ( 32 ) }
# Bech32 spits out array of 5-bit values. Shim here.
def u5_to_bitarray ( arr ) :
b = ' ' . join ( _INT_TO_BINSTR [ a ] for a in arr )
return bitstring . BitArray ( bin = b )
def bitarray_to_u5 ( barr ) :
assert barr . len % 5 == 0
ret = [ ]
s = bitstring . ConstBitStream ( barr )
while s . pos != s . len :
ret . append ( s . read ( 5 ) . uint )
return ret
def encode_fallback ( fallback : str , net : Type [ AbstractNet ] ) :
def encode_fallback_addr ( fallback : str , net : Type [ AbstractNet ] ) - > Sequence [ int ] :
""" Encode all supported fallback addresses.
""" Encode all supported fallback addresses. """
"""
wver , wprog_ints = segwit_addr . decode_segwit_address ( net . SEGWIT_HRP , fallback )
wver , wprog_ints = segwit_addr . decode_segwit_address ( net . SEGWIT_HRP , fallback )
if wver is not None :
if wver is not None :
wprog = bytes ( wprog_ints )
wprog = bytes ( wprog_ints )
@ -106,20 +89,20 @@ def encode_fallback(fallback: str, net: Type[AbstractNet]):
else :
else :
raise LnEncodeException ( f " Unknown address type { addrtype } for { net } " )
raise LnEncodeException ( f " Unknown address type { addrtype } for { net } " )
wprog = addr
wprog = addr
return tagged ( ' f ' , bitstring . pack ( " uint:5 " , wver ) + wprog )
data5 = convertbits ( wprog , 8 , 5 )
assert data5 is not None
return tagged5 ( ' f ' , [ wver ] + list ( data5 ) )
def parse_fallback ( fallback , net : Type [ AbstractNet ] ) :
def parse_fallback_addr ( data5 : Sequence [ int ] , net : Type [ AbstractNet ] ) - > Optional [ str ] :
wver = fallback [ 0 : 5 ] . uint
wver = data5 [ 0 ]
data8 = bytes ( convertbits ( data5 [ 1 : ] , 5 , 8 , False ) )
if wver == 17 :
if wver == 17 :
addr = hash160_to_b58_address ( fallback [ 5 : ] . tobytes ( ) , net . ADDRTYPE_P2PKH )
addr = hash160_to_b58_address ( data8 , net . ADDRTYPE_P2PKH )
elif wver == 18 :
elif wver == 18 :
addr = hash160_to_b58_address ( fallback [ 5 : ] . tobytes ( ) , net . ADDRTYPE_P2SH )
addr = hash160_to_b58_address ( data8 , net . ADDRTYPE_P2SH )
elif wver < = 16 :
elif wver < = 16 :
witprog = fallback [ 5 : ] # cut witver
addr = segwit_addr . encode_segwit_address ( net . SEGWIT_HRP , wver , data8 )
witprog = witprog [ : len ( witprog ) / / 8 * 8 ] # can only be full bytes
witprog = witprog . tobytes ( )
addr = segwit_addr . encode_segwit_address ( net . SEGWIT_HRP , wver , witprog )
else :
else :
return None
return None
return addr
return addr
@ -128,47 +111,52 @@ def parse_fallback(fallback, net: Type[AbstractNet]):
BOLT11_HRP_INV_DICT = { net . BOLT11_HRP : net for net in constants . NETS_LIST }
BOLT11_HRP_INV_DICT = { net . BOLT11_HRP : net for net in constants . NETS_LIST }
# Tagged field containing BitArray
def tagged5 ( char : str , data5 : Sequence [ int ] ) - > Sequence [ int ] :
def tagged ( char , l ) :
assert len ( data5 ) < ( 1 << 10 )
# Tagged fields need to be zero-padded to 5 bits.
return [ CHARSET_INVERSE [ char ] , len ( data5 ) >> 5 , len ( data5 ) & 31 ] + data5
while l . len % 5 != 0 :
l . append ( ' 0b0 ' )
return bitstring . pack ( " uint:5, uint:5, uint:5 " ,
def tagged8 ( char : str , data8 : Sequence [ int ] ) - > Sequence [ int ] :
CHARSET . find ( char ) ,
return tagged5 ( char , convertbits ( data8 , 8 , 5 ) )
( l . len / 5 ) / 32 , ( l . len / 5 ) % 32 ) + l
# Tagged field containing bytes
def tagged_bytes ( char , l ) :
return tagged ( char , bitstring . BitArray ( l ) )
def trim_to_min_length ( bits ) :
def int_to_data5 ( val : int , * , bit_len : int = None ) - > Sequence [ int ] :
""" Ensures ' bits ' have min number of leading zero es.
""" Represent big-endian number with as many 0-31 values as it takes.
Assumes ' bits ' is big - endian , and that it needs to be encoded in 5 bit blocks .
If ` bit_len ` is set , use exactly bit_len / / 5 values ( left - padded with zeroes ) .
"""
"""
bits = bits [ : ] # copy
if bit_len is not None :
# make sure we can be split into 5 bit blocks
assert bit_len % 5 == 0 , bit_len
while bits . len % 5 != 0 :
if val . bit_length ( ) > bit_len :
bits . prepend ( ' 0b0 ' )
raise ValueError ( f " { val =} too big for { bit_len =!r} " )
# Get minimal length by trimming leading 5 bits at a time.
ret = [ ]
while bits . startswith ( ' 0b00000 ' ) :
while val != 0 :
if len ( bits ) == 5 :
ret . append ( val % 32 )
break # v == 0
val / / = 32
bits = bits [ 5 : ]
if bit_len is not None :
return bits
ret . extend ( [ 0 ] * ( len ( ret ) - bit_len / / 5 ) )
ret . reverse ( )
# Discard trailing bits, convert to bytes.
return ret
def trim_to_bytes ( barr ) :
# Adds a byte if necessary.
b = barr . tobytes ( )
def int_from_data5 ( data5 : Sequence [ int ] ) - > int :
if barr . len % 8 != 0 :
total = 0
return b [ : - 1 ]
for v in data5 :
return b
total = 32 * total + v
return total
# Try to pull out tagged data: returns tag, tagged data and remainder.
def pull_tagged ( stream ) :
tag = stream . read ( 5 ) . uint
def pull_tagged ( data5 : bytearray ) - > Tuple [ str , Sequence [ int ] ] :
length = stream . read ( 5 ) . uint * 32 + stream . read ( 5 ) . uint
""" Try to pull out tagged data: returns tag, tagged data. Mutates data in-place. """
return ( CHARSET [ tag ] , stream . read ( length * 5 ) , stream )
if len ( data5 ) < 3 :
raise ValueError ( " Truncated field " )
length = data5 [ 1 ] * 32 + data5 [ 2 ]
if length > len ( data5 ) - 3 :
raise ValueError (
" Truncated {} field: expected {} values " . format ( CHARSET [ data5 [ 0 ] ] , length ) )
ret = ( CHARSET [ data5 [ 0 ] ] , data5 [ 3 : 3 + length ] )
del data5 [ : 3 + length ] # much faster than: data5=data5[offset:]
return ret
def lnencode ( addr : ' LnAddr ' , privkey ) - > str :
def lnencode ( addr : ' LnAddr ' , privkey ) - > str :
if addr . amount :
if addr . amount :
@ -179,17 +167,17 @@ def lnencode(addr: 'LnAddr', privkey) -> str:
hrp = ' ln ' + amount
hrp = ' ln ' + amount
# Start with the timestamp
# Start with the timestamp
data = bitstring . pack ( ' uint:35 ' , addr . date )
data5 = int_to_data5 ( addr . date , bit_len = 35 )
tags_set = set ( )
tags_set = set ( )
# Payment hash
# Payment hash
assert addr . paymenthash is not None
assert addr . paymenthash is not None
data + = tagged_bytes ( ' p ' , addr . paymenthash )
data5 + = tagged8 ( ' p ' , addr . paymenthash )
tags_set . add ( ' p ' )
tags_set . add ( ' p ' )
if addr . payment_secret is not None :
if addr . payment_secret is not None :
data + = tagged_bytes ( ' s ' , addr . payment_secret )
data5 + = tagged8 ( ' s ' , addr . payment_secret )
tags_set . add ( ' s ' )
tags_set . add ( ' s ' )
for k , v in addr . tags :
for k , v in addr . tags :
@ -202,39 +190,44 @@ def lnencode(addr: 'LnAddr', privkey) -> str:
raise LnEncodeException ( " Duplicate ' {} ' tag " . format ( k ) )
raise LnEncodeException ( " Duplicate ' {} ' tag " . format ( k ) )
if k == ' r ' :
if k == ' r ' :
route = bitstring . BitA rray( )
route = bytea rray( )
for step in v :
for step in v :
pubkey , channel , feebase , feerate , cltv = step
pubkey , scid , feebase , feerate , cltv = step
route . append ( bitstring . BitArray ( pubkey ) + bitstring . BitArray ( channel ) + bitstring . pack ( ' intbe:32 ' , feebase ) + bitstring . pack ( ' intbe:32 ' , feerate ) + bitstring . pack ( ' intbe:16 ' , cltv ) )
route + = pubkey
data + = tagged ( ' r ' , route )
route + = scid
route + = int . to_bytes ( feebase , length = 4 , byteorder = " big " , signed = False )
route + = int . to_bytes ( feerate , length = 4 , byteorder = " big " , signed = False )
route + = int . to_bytes ( cltv , length = 2 , byteorder = " big " , signed = False )
data5 + = tagged8 ( ' r ' , route )
elif k == ' t ' :
elif k == ' t ' :
pubkey , feebase , feerate , cltv = v
pubkey , feebase , feerate , cltv = v
route = bitstring . BitArray ( pubkey ) + bitstring . pack ( ' intbe:32 ' , feebase ) + bitstring . pack ( ' intbe:32 ' , feerate ) + bitstring . pack ( ' intbe:16 ' , cltv )
route = bytearray ( )
data + = tagged ( ' t ' , route )
route + = pubkey
route + = int . to_bytes ( feebase , length = 4 , byteorder = " big " , signed = False )
route + = int . to_bytes ( feerate , length = 4 , byteorder = " big " , signed = False )
route + = int . to_bytes ( cltv , length = 2 , byteorder = " big " , signed = False )
data5 + = tagged8 ( ' t ' , route )
elif k == ' f ' :
elif k == ' f ' :
if v is not None :
if v is not None :
data + = encode_fallback ( v , addr . net )
data5 + = encode_fallback_addr ( 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 ] )
data5 + = tagged8 ( ' d ' , v . encode ( ) [ 0 : 639 ] )
elif k == ' x ' :
elif k == ' x ' :
expirybits = bitstring . pack ( ' intbe:64 ' , v )
expirybits = int_to_data5 ( v )
expirybits = trim_to_min_length ( expirybits )
data5 + = tagged5 ( ' x ' , expirybits )
data + = tagged ( ' x ' , expirybits )
elif k == ' h ' :
elif k == ' h ' :
data + = tagged_bytes ( ' h ' , sha256 ( v . encode ( ' utf-8 ' ) ) . digest ( ) )
data5 + = tagged8 ( ' h ' , sha256 ( v . encode ( ' utf-8 ' ) ) . digest ( ) )
elif k == ' n ' :
elif k == ' n ' :
data + = tagged_bytes ( ' n ' , v )
data5 + = tagged8 ( ' n ' , v )
elif k == ' c ' :
elif k == ' c ' :
finalcltvbits = bitstring . pack ( ' intbe:64 ' , v )
finalcltvbits = int_to_data5 ( v )
finalcltvbits = trim_to_min_length ( finalcltvbits )
data5 + = tagged5 ( ' c ' , finalcltvbits )
data + = tagged ( ' c ' , finalcltvbits )
elif k == ' 9 ' :
elif k == ' 9 ' :
if v == 0 :
if v == 0 :
continue
continue
feature_bits = bitstring . BitArray ( uint = v , length = v . bit_length ( ) )
feature_bits = int_to_data5 ( v )
feature_bits = trim_to_min_length ( feature_bits )
data5 + = tagged5 ( ' 9 ' , feature_bits )
data + = tagged ( ' 9 ' , feature_bits )
else :
else :
# FIXME: Support unknown tags?
# FIXME: Support unknown tags?
raise LnEncodeException ( " Unknown tag {} " . format ( k ) )
raise LnEncodeException ( " Unknown tag {} " . format ( k ) )
@ -251,15 +244,16 @@ def lnencode(addr: 'LnAddr', privkey) -> str:
raise ValueError ( " Must include either ' d ' or ' h ' " )
raise ValueError ( " Must include either ' d ' or ' h ' " )
# We actually sign the hrp, then data (padded to 8 bits with zeroes).
# We actually sign the hrp, then data (padded to 8 bits with zeroes).
msg = hrp . encode ( " ascii " ) + data . tobytes ( )
msg = hrp . encode ( " ascii " ) + bytes ( convertbits ( data5 , 5 , 8 ) )
msg32 = sha256 ( msg ) . digest ( )
msg32 = sha256 ( msg ) . digest ( )
privkey = ecc . ECPrivkey ( privkey )
privkey = ecc . ECPrivkey ( privkey )
sig = privkey . ecdsa_sign_recoverable ( msg32 , is_compressed = False )
sig = privkey . ecdsa_sign_recoverable ( msg32 , is_compressed = False )
recovery_flag = bytes ( [ sig [ 0 ] - 27 ] )
recovery_flag = bytes ( [ sig [ 0 ] - 27 ] )
sig = bytes ( sig [ 1 : ] ) + recovery_flag
sig = bytes ( sig [ 1 : ] ) + recovery_flag
data + = sig
sig = bytes ( convertbits ( sig , 8 , 5 , False ) )
data5 + = sig
return bech32_encode ( segwit_addr . Encoding . BECH32 , hrp , bitarray_to_u5 ( data ) )
return bech32_encode ( segwit_addr . Encoding . BECH32 , hrp , data5 )
class LnAddr ( object ) :
class LnAddr ( object ) :
@ -393,6 +387,7 @@ 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 , net = None ) - > LnAddr :
def lndecode ( invoice : str , * , verbose = False , net = None ) - > LnAddr :
""" Parses a string into an LnAddr object.
""" Parses a string into an LnAddr object.
Can raise LnDecodeException or IncompatibleOrInsaneFeatures .
Can raise LnDecodeException or IncompatibleOrInsaneFeatures .
@ -401,7 +396,7 @@ def lndecode(invoice: str, *, verbose=False, net=None) -> LnAddr:
net = constants . net
net = constants . net
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
data5 = decoded_bech32 . data # "5" as in list of 5-bit integers
if decoded_bech32 . encoding is None :
if decoded_bech32 . encoding is None :
raise LnDecodeException ( " Bad bech32 checksum " )
raise LnDecodeException ( " Bad bech32 checksum " )
if decoded_bech32 . encoding != segwit_addr . Encoding . BECH32 :
if decoded_bech32 . encoding != segwit_addr . Encoding . BECH32 :
@ -416,13 +411,12 @@ def lndecode(invoice: str, *, verbose=False, net=None) -> LnAddr:
if not hrp [ 2 : ] . startswith ( net . BOLT11_HRP ) :
if not hrp [ 2 : ] . startswith ( net . BOLT11_HRP ) :
raise LnDecodeException ( f " Wrong Lightning invoice HRP { hrp [ 2 : ] } , should be { net . BOLT11_HRP } " )
raise LnDecodeException ( f " Wrong Lightning invoice HRP { hrp [ 2 : ] } , should be { net . BOLT11_HRP } " )
data = u5_to_bitarray ( data )
# Final signature 65 bytes, split it off.
# Final signature 65 bytes, split it off.
if len ( data ) < 65 * 8 :
if len ( data5 ) < 65 * 8 / / 5 :
raise LnDecodeException ( " Too short to contain signature " )
raise LnDecodeException ( " Too short to contain signature " )
sigdecoded = data [ - 65 * 8 : ] . tobytes ( )
sigdecoded = bytes ( convertbits ( data5 [ - 65 * 8 / / 5 : ] , 5 , 8 , False ) )
data = bitstring . ConstBitStream ( data [ : - 65 * 8 ] )
data5 = data5 [ : - 65 * 8 / / 5 ]
data5_remaining = bytearray ( data5 ) # note: bytearray is faster than list of ints
addr = LnAddr ( )
addr = LnAddr ( )
addr . pubkey = None
addr . pubkey = None
@ -439,17 +433,18 @@ def lndecode(invoice: str, *, verbose=False, net=None) -> LnAddr:
if amountstr != ' ' :
if amountstr != ' ' :
addr . amount = unshorten_amount ( amountstr )
addr . amount = unshorten_amount ( amountstr )
addr . date = data . read ( 35 ) . uint
addr . date = int_from_data5 ( data5_remaining [ : 7 ] )
data5_remaining = data5_remaining [ 7 : ]
while data . pos != data . len :
while data5_remaining :
tag , tagdata , data = pull_tagged ( data )
tag , tagdata = pull_tagged ( data5_remaining ) # mutates arg
# BOLT #11:
# BOLT #11:
#
#
# A reader MUST skip over unknown fields, an `f` field with unknown
# A reader MUST skip over unknown fields, an `f` field with unknown
# `version`, or a `p`, `h`, or `n` field which does not have
# `version`, or a `p`, `h`, or `n` field which does not have
# `data_length` 52, 52, or 53 respectively.
# `data_length` 52, 52, or 53 respectively.
data_length = len ( tagdata ) / 5
data_length = len ( tagdata )
if tag == ' r ' :
if tag == ' r ' :
# BOLT #11:
# BOLT #11:
@ -462,24 +457,43 @@ def lndecode(invoice: str, *, verbose=False, net=None) -> LnAddr:
# * `feebase` (32 bits, big-endian)
# * `feebase` (32 bits, big-endian)
# * `feerate` (32 bits, big-endian)
# * `feerate` (32 bits, big-endian)
# * `cltv_expiry_delta` (16 bits, big-endian)
# * `cltv_expiry_delta` (16 bits, big-endian)
tagdata = convertbits ( tagdata , 5 , 8 , False )
if not tagdata :
continue
route = [ ]
route = [ ]
s = bitstring . ConstBitStream ( tagdata )
with io . BytesIO ( bytes ( tagdata ) ) as s :
while s . pos + 264 + 64 + 32 + 32 + 16 < s . len :
while True :
route . append ( ( s . read ( 264 ) . tobytes ( ) ,
pubkey = s . read ( 33 )
s . read ( 64 ) . tobytes ( ) ,
scid = s . read ( 8 )
s . read ( 32 ) . uintbe ,
feebase = s . read ( 4 )
s . read ( 32 ) . uintbe ,
feerate = s . read ( 4 )
s . read ( 16 ) . uintbe ) )
cltv = s . read ( 2 )
if len ( cltv ) != 2 :
break # EOF
feebase = int . from_bytes ( feebase , byteorder = " big " )
feerate = int . from_bytes ( feerate , byteorder = " big " )
cltv = int . from_bytes ( cltv , byteorder = " big " )
route . append ( ( pubkey , scid , feebase , feerate , cltv ) )
if route :
addr . tags . append ( ( ' r ' , route ) )
addr . tags . append ( ( ' r ' , route ) )
elif tag == ' t ' :
elif tag == ' t ' :
s = bitstring . ConstBitStream ( tagdata )
tagdata = convertbits ( tagdata , 5 , 8 , False )
e = ( s . read ( 264 ) . tobytes ( ) ,
if not tagdata :
s . read ( 32 ) . uintbe ,
continue
s . read ( 32 ) . uintbe ,
route = [ ]
s . read ( 16 ) . uintbe )
with io . BytesIO ( bytes ( tagdata ) ) as s :
addr . tags . append ( ( ' t ' , e ) )
pubkey = s . read ( 33 )
feebase = s . read ( 4 )
feerate = s . read ( 4 )
cltv = s . read ( 2 )
if len ( cltv ) == 2 : # no EOF
feebase = int . from_bytes ( feebase , byteorder = " big " )
feerate = int . from_bytes ( feerate , byteorder = " big " )
cltv = int . from_bytes ( cltv , byteorder = " big " )
route . append ( ( pubkey , feebase , feerate , cltv ) )
addr . tags . append ( ( ' t ' , route ) )
elif tag == ' f ' :
elif tag == ' f ' :
fallback = parse_fallback ( tagdata , addr . net )
fallback = parse_fallback_addr ( tagdata , addr . net )
if fallback :
if fallback :
addr . tags . append ( ( ' f ' , fallback ) )
addr . tags . append ( ( ' f ' , fallback ) )
else :
else :
@ -488,41 +502,41 @@ def lndecode(invoice: str, *, verbose=False, net=None) -> LnAddr:
continue
continue
elif tag == ' d ' :
elif tag == ' d ' :
addr . tags . append ( ( ' d ' , trim_to_byte s( tagdata ) . decode ( ' utf-8 ' ) ) )
addr . tags . append ( ( ' d ' , bytes ( convertbit s( tagdata , 5 , 8 , False ) ) . decode ( ' utf-8 ' ) ) )
elif tag == ' h ' :
elif tag == ' h ' :
if data_length != 52 :
if data_length != 52 :
addr . unknown_tags . append ( ( tag , tagdata ) )
addr . unknown_tags . append ( ( tag , tagdata ) )
continue
continue
addr . tags . append ( ( ' h ' , trim_to_byte s( tagdata ) ) )
addr . tags . append ( ( ' h ' , bytes ( convertbit s( tagdata , 5 , 8 , False ) ) ) )
elif tag == ' x ' :
elif tag == ' x ' :
addr . tags . append ( ( ' x ' , tagdata . uint ) )
addr . tags . append ( ( ' x ' , int_from_data5 ( tagdata ) ) )
elif tag == ' p ' :
elif tag == ' p ' :
if data_length != 52 :
if data_length != 52 :
addr . unknown_tags . append ( ( tag , tagdata ) )
addr . unknown_tags . append ( ( tag , tagdata ) )
continue
continue
addr . paymenthash = trim_to_byte s( tagdata )
addr . paymenthash = bytes ( convertbit s( tagdata , 5 , 8 , False ) )
elif tag == ' s ' :
elif tag == ' s ' :
if data_length != 52 :
if data_length != 52 :
addr . unknown_tags . append ( ( tag , tagdata ) )
addr . unknown_tags . append ( ( tag , tagdata ) )
continue
continue
addr . payment_secret = trim_to_byte s( tagdata )
addr . payment_secret = bytes ( convertbit s( tagdata , 5 , 8 , False ) )
elif tag == ' n ' :
elif tag == ' n ' :
if data_length != 53 :
if data_length != 53 :
addr . unknown_tags . append ( ( tag , tagdata ) )
addr . unknown_tags . append ( ( tag , tagdata ) )
continue
continue
pubkeybytes = trim_to_byte s( tagdata )
pubkeybytes = bytes ( convertbit s( tagdata , 5 , 8 , False ) )
addr . pubkey = pubkeybytes
addr . pubkey = pubkeybytes
elif tag == ' c ' :
elif tag == ' c ' :
addr . tags . append ( ( ' c ' , tagdata . uint ) )
addr . tags . append ( ( ' c ' , int_from_data5 ( tagdata ) ) )
elif tag == ' 9 ' :
elif tag == ' 9 ' :
features = tagdata . uint
features = int_from_data5 ( tagdata )
addr . tags . append ( ( ' 9 ' , features ) )
addr . tags . append ( ( ' 9 ' , features ) )
# note: The features are not validated here in the parser,
# note: The features are not validated here in the parser,
# instead, validation is done just before we try paying the invoice (in lnworker._check_invoice).
# instead, validation is done just before we try paying the invoice (in lnworker._check_invoice).
@ -536,16 +550,17 @@ def lndecode(invoice: str, *, verbose=False, net=None) -> LnAddr:
print ( ' hex of signature data (32 byte r, 32 byte s): {} '
print ( ' hex of signature data (32 byte r, 32 byte s): {} '
. format ( hexlify ( sigdecoded [ 0 : 64 ] ) ) )
. format ( hexlify ( sigdecoded [ 0 : 64 ] ) ) )
print ( ' recovery flag: {} ' . format ( sigdecoded [ 64 ] ) )
print ( ' recovery flag: {} ' . format ( sigdecoded [ 64 ] ) )
data8 = bytes ( convertbits ( data5 , 5 , 8 , True ) )
print ( ' hex of data for signing: {} '
print ( ' hex of data for signing: {} '
. format ( hexlify ( hrp . encode ( " ascii " ) + data . tobytes ( ) ) ) )
. format ( hexlify ( hrp . encode ( " ascii " ) + data8 ) ) )
print ( ' SHA256 of above: {} ' . format ( sha256 ( hrp . encode ( " ascii " ) + data . tobytes ( ) ) . hexdigest ( ) ) )
print ( ' SHA256 of above: {} ' . format ( sha256 ( hrp . encode ( " ascii " ) + data8 ) . hexdigest ( ) ) )
# BOLT #11:
# BOLT #11:
#
#
# A reader MUST check that the `signature` is valid (see the `n` tagged
# A reader MUST check that the `signature` is valid (see the `n` tagged
# field specified below).
# field specified below).
addr . signature = sigdecoded [ : 65 ]
addr . signature = sigdecoded [ : 65 ]
hrp_hash = sha256 ( hrp . encode ( " ascii " ) + data . tobytes ( ) ) . digest ( )
hrp_hash = sha256 ( hrp . encode ( " ascii " ) + bytes ( convertbits ( data5 , 5 , 8 , True ) ) ) . digest ( )
if addr . pubkey : # Specified by `n`
if addr . pubkey : # Specified by `n`
# BOLT #11:
# BOLT #11:
#
#