lnonion: implement basis of varonion support

This commit is contained in:
SomberNight 2020-03-24 12:12:36 +01:00
parent 6ba08cc8d4
commit a66437f399
No known key found for this signature in database
GPG key ID: B33B5F232C6271E9
5 changed files with 326 additions and 107 deletions

View file

@ -4,6 +4,8 @@ import io
from typing import Callable, Tuple, Any, Dict, List, Sequence, Union, Optional from typing import Callable, Tuple, Any, Dict, List, Sequence, Union, Optional
from collections import OrderedDict from collections import OrderedDict
from .lnutil import OnionFailureCodeMetaFlag
class MalformedMsg(Exception): pass class MalformedMsg(Exception): pass
class UnknownMsgFieldType(MalformedMsg): pass class UnknownMsgFieldType(MalformedMsg): pass
@ -254,8 +256,19 @@ def _resolve_field_count(field_count_str: str, *, vars_dict: dict, allow_any=Fal
return field_count return field_count
def _parse_msgtype_intvalue_for_onion_wire(value: str) -> int:
msg_type_int = 0
for component in value.split("|"):
try:
msg_type_int |= int(component)
except ValueError:
msg_type_int |= OnionFailureCodeMetaFlag[component]
return msg_type_int
class LNSerializer: class LNSerializer:
def __init__(self):
def __init__(self, *, for_onion_wire: bool = False):
# TODO msg_type could be 'int' everywhere... # TODO msg_type could be 'int' everywhere...
self.msg_scheme_from_type = {} # type: Dict[bytes, List[Sequence[str]]] self.msg_scheme_from_type = {} # type: Dict[bytes, List[Sequence[str]]]
self.msg_type_from_name = {} # type: Dict[str, bytes] self.msg_type_from_name = {} # type: Dict[str, bytes]
@ -264,7 +277,10 @@ class LNSerializer:
self.in_tlv_stream_get_record_type_from_name = {} # type: Dict[str, Dict[str, int]] self.in_tlv_stream_get_record_type_from_name = {} # type: Dict[str, Dict[str, int]]
self.in_tlv_stream_get_record_name_from_type = {} # type: Dict[str, Dict[int, str]] self.in_tlv_stream_get_record_name_from_type = {} # type: Dict[str, Dict[int, str]]
path = os.path.join(os.path.dirname(__file__), "lnwire", "peer_wire.csv") if for_onion_wire:
path = os.path.join(os.path.dirname(__file__), "lnwire", "onion_wire.csv")
else:
path = os.path.join(os.path.dirname(__file__), "lnwire", "peer_wire.csv")
with open(path, newline='') as f: with open(path, newline='') as f:
csvreader = csv.reader(f) csvreader = csv.reader(f)
for row in csvreader: for row in csvreader:
@ -272,7 +288,10 @@ class LNSerializer:
if row[0] == "msgtype": if row[0] == "msgtype":
# msgtype,<msgname>,<value>[,<option>] # msgtype,<msgname>,<value>[,<option>]
msg_type_name = row[1] msg_type_name = row[1]
msg_type_int = int(row[2]) if for_onion_wire:
msg_type_int = _parse_msgtype_intvalue_for_onion_wire(str(row[2]))
else:
msg_type_int = int(row[2])
msg_type_bytes = msg_type_int.to_bytes(2, 'big') msg_type_bytes = msg_type_int.to_bytes(2, 'big')
assert msg_type_bytes not in self.msg_scheme_from_type, f"type collision? for {msg_type_name}" assert msg_type_bytes not in self.msg_scheme_from_type, f"type collision? for {msg_type_name}"
assert msg_type_name not in self.msg_type_from_name, f"type collision? for {msg_type_name}" assert msg_type_name not in self.msg_type_from_name, f"type collision? for {msg_type_name}"
@ -475,3 +494,6 @@ class LNSerializer:
_inst = LNSerializer() _inst = LNSerializer()
encode_msg = _inst.encode_msg encode_msg = _inst.encode_msg
decode_msg = _inst.decode_msg decode_msg = _inst.decode_msg
OnionWireSerializer = LNSerializer(for_onion_wire=True)

View file

@ -23,6 +23,7 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
import io
import hashlib import hashlib
from typing import Sequence, List, Tuple, NamedTuple, TYPE_CHECKING from typing import Sequence, List, Tuple, NamedTuple, TYPE_CHECKING
from enum import IntEnum, IntFlag from enum import IntEnum, IntFlag
@ -31,15 +32,16 @@ from . import ecc
from .crypto import sha256, hmac_oneshot, chacha20_encrypt from .crypto import sha256, hmac_oneshot, chacha20_encrypt
from .util import bh2u, profiler, xor_bytes, bfh from .util import bh2u, profiler, xor_bytes, bfh
from .lnutil import (get_ecdh, PaymentFailure, NUM_MAX_HOPS_IN_PAYMENT_PATH, from .lnutil import (get_ecdh, PaymentFailure, NUM_MAX_HOPS_IN_PAYMENT_PATH,
NUM_MAX_EDGES_IN_PAYMENT_PATH, ShortChannelID) NUM_MAX_EDGES_IN_PAYMENT_PATH, ShortChannelID, OnionFailureCodeMetaFlag)
from .lnmsg import OnionWireSerializer, read_bigsize_int, write_bigsize_int
if TYPE_CHECKING: if TYPE_CHECKING:
from .lnrouter import LNPaymentRoute from .lnrouter import LNPaymentRoute
HOPS_DATA_SIZE = 1300 # also sometimes called routingInfoSize in bolt-04 HOPS_DATA_SIZE = 1300 # also sometimes called routingInfoSize in bolt-04
PER_HOP_FULL_SIZE = 65 # HOPS_DATA_SIZE / 20 LEGACY_PER_HOP_FULL_SIZE = 65
NUM_STREAM_BYTES = HOPS_DATA_SIZE + PER_HOP_FULL_SIZE NUM_STREAM_BYTES = 2 * HOPS_DATA_SIZE
PER_HOP_HMAC_SIZE = 32 PER_HOP_HMAC_SIZE = 32
@ -48,64 +50,124 @@ class InvalidOnionMac(Exception): pass
class InvalidOnionPubkey(Exception): pass class InvalidOnionPubkey(Exception): pass
class OnionPerHop: class LegacyHopDataPayload:
def __init__(self, short_channel_id: bytes, amt_to_forward: bytes, outgoing_cltv_value: bytes): def __init__(self, *, short_channel_id: bytes, amt_to_forward: int, outgoing_cltv_value: int):
self.short_channel_id = ShortChannelID(short_channel_id) self.short_channel_id = ShortChannelID(short_channel_id)
self.amt_to_forward = amt_to_forward self.amt_to_forward = amt_to_forward
self.outgoing_cltv_value = outgoing_cltv_value self.outgoing_cltv_value = outgoing_cltv_value
def to_bytes(self) -> bytes: def to_bytes(self) -> bytes:
ret = self.short_channel_id ret = self.short_channel_id
ret += self.amt_to_forward ret += int.to_bytes(self.amt_to_forward, length=8, byteorder="big", signed=False)
ret += self.outgoing_cltv_value ret += int.to_bytes(self.outgoing_cltv_value, length=4, byteorder="big", signed=False)
ret += bytes(12) # padding ret += bytes(12) # padding
if len(ret) != 32: if len(ret) != 32:
raise Exception('unexpected length {}'.format(len(ret))) raise Exception('unexpected length {}'.format(len(ret)))
return ret return ret
def to_tlv_dict(self) -> dict:
d = {
"amt_to_forward": {"amt_to_forward": self.amt_to_forward},
"outgoing_cltv_value": {"outgoing_cltv_value": self.outgoing_cltv_value},
"short_channel_id": {"short_channel_id": self.short_channel_id},
}
return d
@classmethod @classmethod
def from_bytes(cls, b: bytes): def from_bytes(cls, b: bytes) -> 'LegacyHopDataPayload':
if len(b) != 32: if len(b) != 32:
raise Exception('unexpected length {}'.format(len(b))) raise Exception('unexpected length {}'.format(len(b)))
return OnionPerHop( return LegacyHopDataPayload(
short_channel_id=b[:8], short_channel_id=b[:8],
amt_to_forward=b[8:16], amt_to_forward=int.from_bytes(b[8:16], byteorder="big", signed=False),
outgoing_cltv_value=b[16:20] outgoing_cltv_value=int.from_bytes(b[16:20], byteorder="big", signed=False),
)
@classmethod
def from_tlv_dict(cls, d: dict) -> 'LegacyHopDataPayload':
return LegacyHopDataPayload(
short_channel_id=d["short_channel_id"]["short_channel_id"],
amt_to_forward=d["amt_to_forward"]["amt_to_forward"],
outgoing_cltv_value=d["outgoing_cltv_value"]["outgoing_cltv_value"],
) )
class OnionHopsDataSingle: # called HopData in lnd class OnionHopsDataSingle: # called HopData in lnd
def __init__(self, per_hop: OnionPerHop = None): def __init__(self, *, is_tlv_payload: bool, payload: dict = None):
self.realm = 0 self.is_tlv_payload = is_tlv_payload
self.per_hop = per_hop if payload is None:
payload = {}
self.payload = payload
self.hmac = None self.hmac = None
self._raw_bytes_payload = None # used in unit tests
def to_bytes(self) -> bytes: def to_bytes(self) -> bytes:
ret = bytes([self.realm]) hmac_ = self.hmac if self.hmac is not None else bytes(PER_HOP_HMAC_SIZE)
ret += self.per_hop.to_bytes() if self._raw_bytes_payload is not None:
ret += self.hmac if self.hmac is not None else bytes(PER_HOP_HMAC_SIZE) ret = write_bigsize_int(len(self._raw_bytes_payload))
if len(ret) != PER_HOP_FULL_SIZE: ret += self._raw_bytes_payload
raise Exception('unexpected length {}'.format(len(ret))) ret += hmac_
return ret return ret
if not self.is_tlv_payload:
ret = b"\x00" # realm==0
legacy_payload = LegacyHopDataPayload.from_tlv_dict(self.payload)
ret += legacy_payload.to_bytes()
ret += hmac_
if len(ret) != LEGACY_PER_HOP_FULL_SIZE:
raise Exception('unexpected length {}'.format(len(ret)))
return ret
else: # tlv
payload_fd = io.BytesIO()
OnionWireSerializer.write_tlv_stream(fd=payload_fd,
tlv_stream_name="tlv_payload",
**self.payload)
payload_bytes = payload_fd.getvalue()
with io.BytesIO() as fd:
fd.write(write_bigsize_int(len(payload_bytes)))
fd.write(payload_bytes)
fd.write(hmac_)
return fd.getvalue()
@classmethod @classmethod
def from_bytes(cls, b: bytes): def from_fd(cls, fd: io.BytesIO) -> 'OnionHopsDataSingle':
if len(b) != PER_HOP_FULL_SIZE: first_byte = fd.read(1)
raise Exception('unexpected length {}'.format(len(b))) if len(first_byte) == 0:
ret = OnionHopsDataSingle() raise Exception(f"unexpected EOF")
ret.realm = b[0] fd.seek(-1, io.SEEK_CUR) # undo read
if ret.realm != 0: if first_byte == b'\x00':
raise Exception('only realm 0 is supported') # legacy hop data format
ret.per_hop = OnionPerHop.from_bytes(b[1:33]) b = fd.read(LEGACY_PER_HOP_FULL_SIZE)
ret.hmac = b[33:] if len(b) != LEGACY_PER_HOP_FULL_SIZE:
return ret raise Exception(f'unexpected length {len(b)}')
ret = OnionHopsDataSingle(is_tlv_payload=False)
legacy_payload = LegacyHopDataPayload.from_bytes(b[1:33])
ret.payload = legacy_payload.to_tlv_dict()
ret.hmac = b[33:]
return ret
elif first_byte == b'\x01':
# reserved for future use
raise Exception("unsupported hop payload: length==1")
else:
hop_payload_length = read_bigsize_int(fd)
hop_payload = fd.read(hop_payload_length)
if hop_payload_length != len(hop_payload):
raise Exception(f"unexpected EOF")
ret = OnionHopsDataSingle(is_tlv_payload=True)
ret.payload = OnionWireSerializer.read_tlv_stream(fd=io.BytesIO(hop_payload),
tlv_stream_name="tlv_payload")
ret.hmac = fd.read(PER_HOP_HMAC_SIZE)
assert len(ret.hmac) == PER_HOP_HMAC_SIZE
return ret
class OnionPacket: class OnionPacket:
def __init__(self, public_key: bytes, hops_data: bytes, hmac: bytes): def __init__(self, public_key: bytes, hops_data: bytes, hmac: bytes):
assert len(public_key) == 33
assert len(hops_data) == HOPS_DATA_SIZE
assert len(hmac) == PER_HOP_HMAC_SIZE
self.version = 0 self.version = 0
self.public_key = public_key self.public_key = public_key
self.hops_data = hops_data # also called RoutingInfo in bolt-04 self.hops_data = hops_data # also called RoutingInfo in bolt-04
@ -163,13 +225,14 @@ def get_shared_secrets_along_route(payment_path_pubkeys: Sequence[bytes],
def new_onion_packet(payment_path_pubkeys: Sequence[bytes], session_key: bytes, def new_onion_packet(payment_path_pubkeys: Sequence[bytes], session_key: bytes,
hops_data: Sequence[OnionHopsDataSingle], associated_data: bytes) -> OnionPacket: hops_data: Sequence[OnionHopsDataSingle], associated_data: bytes) -> OnionPacket:
num_hops = len(payment_path_pubkeys) num_hops = len(payment_path_pubkeys)
assert num_hops == len(hops_data)
hop_shared_secrets = get_shared_secrets_along_route(payment_path_pubkeys, session_key) hop_shared_secrets = get_shared_secrets_along_route(payment_path_pubkeys, session_key)
filler = generate_filler(b'rho', num_hops, PER_HOP_FULL_SIZE, hop_shared_secrets) filler = _generate_filler(b'rho', hops_data, hop_shared_secrets)
next_hmac = bytes(PER_HOP_HMAC_SIZE) next_hmac = bytes(PER_HOP_HMAC_SIZE)
# Our starting packet needs to be filled out with random bytes, we # Our starting packet needs to be filled out with random bytes, we
# generate some determinstically using the session private key. # generate some deterministically using the session private key.
pad_key = get_bolt04_onion_key(b'pad', session_key) pad_key = get_bolt04_onion_key(b'pad', session_key)
mix_header = generate_cipher_stream(pad_key, HOPS_DATA_SIZE) mix_header = generate_cipher_stream(pad_key, HOPS_DATA_SIZE)
@ -178,9 +241,10 @@ def new_onion_packet(payment_path_pubkeys: Sequence[bytes], session_key: bytes,
rho_key = get_bolt04_onion_key(b'rho', hop_shared_secrets[i]) rho_key = get_bolt04_onion_key(b'rho', hop_shared_secrets[i])
mu_key = get_bolt04_onion_key(b'mu', hop_shared_secrets[i]) mu_key = get_bolt04_onion_key(b'mu', hop_shared_secrets[i])
hops_data[i].hmac = next_hmac hops_data[i].hmac = next_hmac
stream_bytes = generate_cipher_stream(rho_key, NUM_STREAM_BYTES) stream_bytes = generate_cipher_stream(rho_key, HOPS_DATA_SIZE)
mix_header = mix_header[:-PER_HOP_FULL_SIZE] hop_data_bytes = hops_data[i].to_bytes()
mix_header = hops_data[i].to_bytes() + mix_header mix_header = mix_header[:-len(hop_data_bytes)]
mix_header = hop_data_bytes + mix_header
mix_header = xor_bytes(mix_header, stream_bytes) mix_header = xor_bytes(mix_header, stream_bytes)
if i == num_hops - 1 and len(filler) != 0: if i == num_hops - 1 and len(filler) != 0:
mix_header = mix_header[:-len(filler)] + filler mix_header = mix_header[:-len(filler)] + filler
@ -203,32 +267,53 @@ def calc_hops_data_for_payment(route: 'LNPaymentRoute', amount_msat: int, final_
amt = amount_msat amt = amount_msat
cltv = final_cltv cltv = final_cltv
hops_data = [OnionHopsDataSingle(OnionPerHop(b"\x00" * 8, hop_payload = {
amt.to_bytes(8, "big"), "amt_to_forward": {"amt_to_forward": amt},
cltv.to_bytes(4, "big")))] "outgoing_cltv_value": {"outgoing_cltv_value": cltv},
"short_channel_id": {"short_channel_id": b"\x00" * 8}, # TODO omit if tlv
}
hops_data = [OnionHopsDataSingle(is_tlv_payload=False, # TODO
payload=hop_payload)]
for route_edge in reversed(route[1:]): for route_edge in reversed(route[1:]):
hops_data += [OnionHopsDataSingle(OnionPerHop(route_edge.short_channel_id, hop_payload = {
amt.to_bytes(8, "big"), "amt_to_forward": {"amt_to_forward": amt},
cltv.to_bytes(4, "big")))] "outgoing_cltv_value": {"outgoing_cltv_value": cltv},
"short_channel_id": {"short_channel_id": route_edge.short_channel_id},
}
hops_data += [OnionHopsDataSingle(is_tlv_payload=False, # TODO
payload=hop_payload)]
amt += route_edge.fee_for_edge(amt) amt += route_edge.fee_for_edge(amt)
cltv += route_edge.cltv_expiry_delta cltv += route_edge.cltv_expiry_delta
hops_data.reverse() hops_data.reverse()
return hops_data, amt, cltv return hops_data, amt, cltv
def generate_filler(key_type: bytes, num_hops: int, hop_size: int, def _generate_filler(key_type: bytes, hops_data: Sequence[OnionHopsDataSingle],
shared_secrets: Sequence[bytes]) -> bytes: shared_secrets: Sequence[bytes]) -> bytes:
filler_size = (NUM_MAX_HOPS_IN_PAYMENT_PATH + 1) * hop_size num_hops = len(hops_data)
# generate filler that matches all but the last hop (no HMAC for last hop)
filler_size = 0
for hop_data in hops_data[:-1]:
filler_size += len(hop_data.to_bytes())
filler = bytearray(filler_size) filler = bytearray(filler_size)
for i in range(0, num_hops-1): # -1, as last hop does not obfuscate for i in range(0, num_hops-1): # -1, as last hop does not obfuscate
filler = filler[hop_size:] # Sum up how many frames were used by prior hops.
filler += bytearray(hop_size) filler_start = HOPS_DATA_SIZE
stream_key = get_bolt04_onion_key(key_type, shared_secrets[i]) for hop_data in hops_data[:i]:
stream_bytes = generate_cipher_stream(stream_key, filler_size) filler_start -= len(hop_data.to_bytes())
filler = xor_bytes(filler, stream_bytes) # The filler is the part dangling off of the end of the
# routingInfo, so offset it from there, and use the current
# hop's frame count as its size.
filler_end = HOPS_DATA_SIZE + len(hops_data[i].to_bytes())
return filler[(NUM_MAX_HOPS_IN_PAYMENT_PATH-num_hops+2)*hop_size:] stream_key = get_bolt04_onion_key(key_type, shared_secrets[i])
stream_bytes = generate_cipher_stream(stream_key, NUM_STREAM_BYTES)
filler = xor_bytes(filler, stream_bytes[filler_start:filler_end])
filler += bytes(filler_size - len(filler)) # right pad with zeroes
return filler
def generate_cipher_stream(stream_key: bytes, num_bytes: int) -> bytes: def generate_cipher_stream(stream_key: bytes, num_bytes: int) -> bytes:
@ -260,8 +345,9 @@ def process_onion_packet(onion_packet: OnionPacket, associated_data: bytes,
# peel an onion layer off # peel an onion layer off
rho_key = get_bolt04_onion_key(b'rho', shared_secret) rho_key = get_bolt04_onion_key(b'rho', shared_secret)
stream_bytes = generate_cipher_stream(rho_key, NUM_STREAM_BYTES) stream_bytes = generate_cipher_stream(rho_key, NUM_STREAM_BYTES)
padded_header = onion_packet.hops_data + bytes(PER_HOP_FULL_SIZE) padded_header = onion_packet.hops_data + bytes(HOPS_DATA_SIZE)
next_hops_data = xor_bytes(padded_header, stream_bytes) next_hops_data = xor_bytes(padded_header, stream_bytes)
next_hops_data_fd = io.BytesIO(next_hops_data)
# calc next ephemeral key # calc next ephemeral key
blinding_factor = sha256(onion_packet.public_key + shared_secret) blinding_factor = sha256(onion_packet.public_key + shared_secret)
@ -269,10 +355,10 @@ def process_onion_packet(onion_packet: OnionPacket, associated_data: bytes,
next_public_key_int = ecc.ECPubkey(onion_packet.public_key) * blinding_factor_int next_public_key_int = ecc.ECPubkey(onion_packet.public_key) * blinding_factor_int
next_public_key = next_public_key_int.get_public_key_bytes() next_public_key = next_public_key_int.get_public_key_bytes()
hop_data = OnionHopsDataSingle.from_bytes(next_hops_data[:PER_HOP_FULL_SIZE]) hop_data = OnionHopsDataSingle.from_fd(next_hops_data_fd)
next_onion_packet = OnionPacket( next_onion_packet = OnionPacket(
public_key=next_public_key, public_key=next_public_key,
hops_data=next_hops_data[PER_HOP_FULL_SIZE:], hops_data=next_hops_data_fd.read(HOPS_DATA_SIZE),
hmac=hop_data.hmac hmac=hop_data.hmac
) )
if hop_data.hmac == bytes(PER_HOP_HMAC_SIZE): if hop_data.hmac == bytes(PER_HOP_HMAC_SIZE):
@ -365,12 +451,7 @@ def get_failure_msg_from_onion_error(decrypted_error_packet: bytes) -> OnionRout
return OnionRoutingFailureMessage(failure_code, failure_data) return OnionRoutingFailureMessage(failure_code, failure_data)
class OnionFailureCodeMetaFlag(IntFlag): # TODO maybe we should rm this and just use OnionWireSerializer and onion_wire.csv
BADONION = 0x8000
PERM = 0x4000
NODE = 0x2000
UPDATE = 0x1000
BADONION = OnionFailureCodeMetaFlag.BADONION BADONION = OnionFailureCodeMetaFlag.BADONION
PERM = OnionFailureCodeMetaFlag.PERM PERM = OnionFailureCodeMetaFlag.PERM
NODE = OnionFailureCodeMetaFlag.NODE NODE = OnionFailureCodeMetaFlag.NODE

View file

@ -1045,7 +1045,7 @@ class Peer(Logger):
local_height = self.network.get_local_height() local_height = self.network.get_local_height()
# create onion packet # create onion packet
final_cltv = local_height + min_final_cltv_expiry final_cltv = local_height + min_final_cltv_expiry
hops_data, amount_msat, cltv = calc_hops_data_for_payment(route, amount_msat, final_cltv) hops_data, amount_msat, cltv = calc_hops_data_for_payment(route, amount_msat, final_cltv) # TODO varonion
assert final_cltv <= cltv, (final_cltv, cltv) assert final_cltv <= cltv, (final_cltv, cltv)
secret_key = os.urandom(32) secret_key = os.urandom(32)
onion = new_onion_packet([x.node_id for x in route], secret_key, hops_data, associated_data=payment_hash) onion = new_onion_packet([x.node_id for x in route], secret_key, hops_data, associated_data=payment_hash)
@ -1145,9 +1145,8 @@ class Peer(Logger):
if not forwarding_enabled: if not forwarding_enabled:
self.logger.info(f"forwarding is disabled. failing htlc.") self.logger.info(f"forwarding is disabled. failing htlc.")
return OnionRoutingFailureMessage(code=OnionFailureCode.PERMANENT_CHANNEL_FAILURE, data=b'') return OnionRoutingFailureMessage(code=OnionFailureCode.PERMANENT_CHANNEL_FAILURE, data=b'')
dph = processed_onion.hop_data.per_hop next_chan_scid = processed_onion.hop_data.payload["short_channel_id"]["short_channel_id"]
next_chan = self.lnworker.get_channel_by_short_id(dph.short_channel_id) next_chan = self.lnworker.get_channel_by_short_id(next_chan_scid)
next_chan_scid = dph.short_channel_id
local_height = self.network.get_local_height() local_height = self.network.get_local_height()
if next_chan is None: if next_chan is None:
self.logger.info(f"cannot forward htlc. cannot find next_chan {next_chan_scid}") self.logger.info(f"cannot forward htlc. cannot find next_chan {next_chan_scid}")
@ -1159,7 +1158,7 @@ class Peer(Logger):
f"chan state {next_chan.get_state()}, peer state: {next_chan.peer_state}") f"chan state {next_chan.get_state()}, peer state: {next_chan.peer_state}")
data = outgoing_chan_upd_len + outgoing_chan_upd data = outgoing_chan_upd_len + outgoing_chan_upd
return OnionRoutingFailureMessage(code=OnionFailureCode.TEMPORARY_CHANNEL_FAILURE, data=data) return OnionRoutingFailureMessage(code=OnionFailureCode.TEMPORARY_CHANNEL_FAILURE, data=data)
next_cltv_expiry = int.from_bytes(dph.outgoing_cltv_value, 'big') next_cltv_expiry = processed_onion.hop_data.payload["outgoing_cltv_value"]["outgoing_cltv_value"]
if htlc.cltv_expiry - next_cltv_expiry < NBLOCK_OUR_CLTV_EXPIRY_DELTA: if htlc.cltv_expiry - next_cltv_expiry < NBLOCK_OUR_CLTV_EXPIRY_DELTA:
data = htlc.cltv_expiry.to_bytes(4, byteorder="big") + outgoing_chan_upd_len + outgoing_chan_upd data = htlc.cltv_expiry.to_bytes(4, byteorder="big") + outgoing_chan_upd_len + outgoing_chan_upd
return OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_CLTV_EXPIRY, data=data) return OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_CLTV_EXPIRY, data=data)
@ -1169,7 +1168,7 @@ class Peer(Logger):
return OnionRoutingFailureMessage(code=OnionFailureCode.EXPIRY_TOO_SOON, data=data) return OnionRoutingFailureMessage(code=OnionFailureCode.EXPIRY_TOO_SOON, data=data)
if max(htlc.cltv_expiry, next_cltv_expiry) > local_height + lnutil.NBLOCK_CLTV_EXPIRY_TOO_FAR_INTO_FUTURE: if max(htlc.cltv_expiry, next_cltv_expiry) > local_height + lnutil.NBLOCK_CLTV_EXPIRY_TOO_FAR_INTO_FUTURE:
return OnionRoutingFailureMessage(code=OnionFailureCode.EXPIRY_TOO_FAR, data=b'') return OnionRoutingFailureMessage(code=OnionFailureCode.EXPIRY_TOO_FAR, data=b'')
next_amount_msat_htlc = int.from_bytes(dph.amt_to_forward, 'big') next_amount_msat_htlc = processed_onion.hop_data.payload["amt_to_forward"]["amt_to_forward"]
forwarding_fees = fee_for_edge_msat( forwarding_fees = fee_for_edge_msat(
forwarded_amount_msat=next_amount_msat_htlc, forwarded_amount_msat=next_amount_msat_htlc,
fee_base_msat=lnutil.OUR_FEE_BASE_MSAT, fee_base_msat=lnutil.OUR_FEE_BASE_MSAT,
@ -1190,8 +1189,8 @@ class Peer(Logger):
"update_add_htlc", "update_add_htlc",
channel_id=next_chan.channel_id, channel_id=next_chan.channel_id,
id=next_htlc.htlc_id, id=next_htlc.htlc_id,
cltv_expiry=dph.outgoing_cltv_value, cltv_expiry=next_cltv_expiry,
amount_msat=dph.amt_to_forward, amount_msat=next_amount_msat_htlc,
payment_hash=next_htlc.payment_hash, payment_hash=next_htlc.payment_hash,
onion_routing_packet=processed_onion.next_packet.to_bytes() onion_routing_packet=processed_onion.next_packet.to_bytes()
) )
@ -1218,12 +1217,12 @@ class Peer(Logger):
if local_height + MIN_FINAL_CLTV_EXPIRY_ACCEPTED > htlc.cltv_expiry: if local_height + MIN_FINAL_CLTV_EXPIRY_ACCEPTED > htlc.cltv_expiry:
reason = OnionRoutingFailureMessage(code=OnionFailureCode.FINAL_EXPIRY_TOO_SOON, data=b'') reason = OnionRoutingFailureMessage(code=OnionFailureCode.FINAL_EXPIRY_TOO_SOON, data=b'')
return False, reason return False, reason
cltv_from_onion = int.from_bytes(processed_onion.hop_data.per_hop.outgoing_cltv_value, byteorder="big") cltv_from_onion = processed_onion.hop_data.payload["outgoing_cltv_value"]["outgoing_cltv_value"]
if cltv_from_onion != htlc.cltv_expiry: if cltv_from_onion != htlc.cltv_expiry:
reason = OnionRoutingFailureMessage(code=OnionFailureCode.FINAL_INCORRECT_CLTV_EXPIRY, reason = OnionRoutingFailureMessage(code=OnionFailureCode.FINAL_INCORRECT_CLTV_EXPIRY,
data=htlc.cltv_expiry.to_bytes(4, byteorder="big")) data=htlc.cltv_expiry.to_bytes(4, byteorder="big"))
return False, reason return False, reason
amount_from_onion = int.from_bytes(processed_onion.hop_data.per_hop.amt_to_forward, byteorder="big") amount_from_onion = processed_onion.hop_data.payload["amt_to_forward"]["amt_to_forward"]
if amount_from_onion > htlc.amount_msat: if amount_from_onion > htlc.amount_msat:
reason = OnionRoutingFailureMessage(code=OnionFailureCode.FINAL_INCORRECT_HTLC_AMOUNT, reason = OnionRoutingFailureMessage(code=OnionFailureCode.FINAL_INCORRECT_HTLC_AMOUNT,
data=htlc.amount_msat.to_bytes(8, byteorder="big")) data=htlc.amount_msat.to_bytes(8, byteorder="big"))

View file

@ -1075,3 +1075,11 @@ class UpdateAddHtlc:
def to_tuple(self): def to_tuple(self):
return (self.amount_msat, self.payment_hash, self.cltv_expiry, self.htlc_id, self.timestamp) return (self.amount_msat, self.payment_hash, self.cltv_expiry, self.htlc_id, self.timestamp)
class OnionFailureCodeMetaFlag(IntFlag):
BADONION = 0x8000
PERM = 0x4000
NODE = 0x2000
UPDATE = 0x1000

View file

@ -4,9 +4,9 @@ import shutil
import asyncio import asyncio
from electrum.util import bh2u, bfh, create_and_start_event_loop from electrum.util import bh2u, bfh, create_and_start_event_loop
from electrum.lnonion import (OnionHopsDataSingle, new_onion_packet, OnionPerHop, from electrum.lnonion import (OnionHopsDataSingle, new_onion_packet,
process_onion_packet, _decode_onion_error, decode_onion_error, process_onion_packet, _decode_onion_error, decode_onion_error,
OnionFailureCode) OnionFailureCode, OnionPacket)
from electrum import bitcoin, lnrouter from electrum import bitcoin, lnrouter
from electrum.constants import BitcoinTestnet from electrum.constants import BitcoinTestnet
from electrum.simple_config import SimpleConfig from electrum.simple_config import SimpleConfig
@ -111,7 +111,7 @@ class Test_LNRouter(TestCaseForTestnet):
cdb.sql_thread.join(timeout=1) cdb.sql_thread.join(timeout=1)
@needs_test_with_all_chacha20_implementations @needs_test_with_all_chacha20_implementations
def test_new_onion_packet(self): def test_new_onion_packet_legacy(self):
# test vector from bolt-04 # test vector from bolt-04
payment_path_pubkeys = [ payment_path_pubkeys = [
bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'), bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'),
@ -123,28 +123,127 @@ class Test_LNRouter(TestCaseForTestnet):
session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141') session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141')
associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242') associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242')
hops_data = [ hops_data = [
OnionHopsDataSingle(OnionPerHop( OnionHopsDataSingle(is_tlv_payload=False, payload={
bfh('0000000000000000'), bfh('0000000000000000'), bfh('00000000') "amt_to_forward": {"amt_to_forward": 0},
)), "outgoing_cltv_value": {"outgoing_cltv_value": 0},
OnionHopsDataSingle(OnionPerHop( "short_channel_id": {"short_channel_id": bfh('0000000000000000')},
bfh('0101010101010101'), bfh('0000000000000001'), bfh('00000001') }),
)), OnionHopsDataSingle(is_tlv_payload=False, payload={
OnionHopsDataSingle(OnionPerHop( "amt_to_forward": {"amt_to_forward": 1},
bfh('0202020202020202'), bfh('0000000000000002'), bfh('00000002') "outgoing_cltv_value": {"outgoing_cltv_value": 1},
)), "short_channel_id": {"short_channel_id": bfh('0101010101010101')},
OnionHopsDataSingle(OnionPerHop( }),
bfh('0303030303030303'), bfh('0000000000000003'), bfh('00000003') OnionHopsDataSingle(is_tlv_payload=False, payload={
)), "amt_to_forward": {"amt_to_forward": 2},
OnionHopsDataSingle(OnionPerHop( "outgoing_cltv_value": {"outgoing_cltv_value": 2},
bfh('0404040404040404'), bfh('0000000000000004'), bfh('00000004') "short_channel_id": {"short_channel_id": bfh('0202020202020202')},
)), }),
OnionHopsDataSingle(is_tlv_payload=False, payload={
"amt_to_forward": {"amt_to_forward": 3},
"outgoing_cltv_value": {"outgoing_cltv_value": 3},
"short_channel_id": {"short_channel_id": bfh('0303030303030303')},
}),
OnionHopsDataSingle(is_tlv_payload=False, payload={
"amt_to_forward": {"amt_to_forward": 4},
"outgoing_cltv_value": {"outgoing_cltv_value": 4},
"short_channel_id": {"short_channel_id": bfh('0404040404040404')},
}),
] ]
packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data) packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data)
self.assertEqual(bfh('0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71e87f9aab8f6378c6ff744c1f34b393ad28d065b535c1a8668d85d3b34a1b3befd10f7d61ab590531cf08000178a333a347f8b4072e216400406bdf3bf038659793a1f9e7abc789266cc861cabd95818c0fc8efbdfdc14e3f7c2bc7eb8d6a79ef75ce721caad69320c3a469a202f3e468c67eaf7a7cda226d0fd32f7b48084dca885d014698cf05d742557763d9cb743faeae65dcc79dddaecf27fe5942be5380d15e9a1ec866abe044a9ad635778ba61fc0776dc832b39451bd5d35072d2269cf9b040a2a2fba158a0d8085926dc2e44f0c88bf487da56e13ef2d5e676a8589881b4869ed4c7f0218ff8c6c7dd7221d189c65b3b9aaa71a01484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565a9f99728426ce2380a9580e2a9442481ceae7679906c30b1a0e21a10f26150e0645ab6edfdab1ce8f8bea7b1dee511c5fd38ac0e702c1c15bb86b52bca1b71e15b96982d262a442024c33ceb7dd8f949063c2e5e613e873250e2f8708bd4e1924abd45f65c2fa5617bfb10ee9e4a42d6b5811acc8029c16274f937dac9e8817c7e579fdb767ffe277f26d413ced06b620ede8362081da21cf67c2ca9d6f15fe5bc05f82f5bb93f8916bad3d63338ca824f3bbc11b57ce94a5fa1bc239533679903d6fec92a8c792fd86e2960188c14f21e399cfd72a50c620e10aefc6249360b463df9a89bf6836f4f26359207b765578e5ed76ae9f31b1cc48324be576e3d8e44d217445dba466f9b6293fdf05448584eb64f61e02903f834518622b7d4732471c6e0e22e22d1f45e31f0509eab39cdea5980a492a1da2aaac55a98a01216cd4bfe7abaa682af0fbff2dfed030ba28f1285df750e4d3477190dd193f8643b61d8ac1c427d590badb1f61a05d480908fbdc7c6f0502dd0c4abb51d725e92f95da2a8facb79881a844e2026911adcc659d1fb20a2fce63787c8bb0d9f6789c4b231c76da81c3f0718eb7156565a081d2be6b4170c0e0bcebddd459f53db2590c974bca0d705c055dee8c629bf854a5d58edc85228499ec6dde80cce4c8910b81b1e9e8b0f43bd39c8d69c3a80672729b7dc952dd9448688b6bd06afc2d2819cda80b66c57b52ccf7ac1a86601410d18d0c732f69de792e0894a9541684ef174de766fd4ce55efea8f53812867be6a391ac865802dbc26d93959df327ec2667c7256aa5a1d3c45a69a6158f285d6c97c3b8eedb09527848500517995a9eae4cd911df531544c77f5a9a2f22313e3eb72ca7a07dba243476bc926992e0d1e58b4a2fc8c7b01e0cad726237933ea319bad7537d39f3ed635d1e6c1d29e97b3d2160a09e30ee2b65ac5bce00996a73c008bcf351cecb97b6833b6d121dcf4644260b2946ea204732ac9954b228f0beaa15071930fd9583dfc466d12b5f0eeeba6dcf23d5ce8ae62ee5796359d97a4a15955c778d868d0ef9991d9f2833b5bb66119c5f8b396fd108baed7906cbb3cc376d13551caed97fece6f42a4c908ee279f1127fda1dd3ee77d8de0a6f3c135fa3f1cffe38591b6738dc97b55f0acc52be9753ce53e64d7e497bb00ca6123758df3b68fad99e35c04389f7514a8e36039f541598a417275e77869989782325a15b5342ac5011ff07af698584b476b35d941a4981eac590a07a092bb50342da5d3341f901aa07964a8d02b623c7b106dd0ae50bfa007a22d46c8772fa55558176602946cb1d11ea5460db7586fb89c6d3bcd3ab6dd20df4a4db63d2e7d52380800ad812b8640887e027e946df96488b47fbc4a4fadaa8beda4abe446fafea5403fae2ef'), self.assertEqual(bfh('0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71e87f9aab8f6378c6ff744c1f34b393ad28d065b535c1a8668d85d3b34a1b3befd10f7d61ab590531cf08000178a333a347f8b4072e216400406bdf3bf038659793a1f9e7abc789266cc861cabd95818c0fc8efbdfdc14e3f7c2bc7eb8d6a79ef75ce721caad69320c3a469a202f3e468c67eaf7a7cda226d0fd32f7b48084dca885d014698cf05d742557763d9cb743faeae65dcc79dddaecf27fe5942be5380d15e9a1ec866abe044a9ad635778ba61fc0776dc832b39451bd5d35072d2269cf9b040a2a2fba158a0d8085926dc2e44f0c88bf487da56e13ef2d5e676a8589881b4869ed4c7f0218ff8c6c7dd7221d189c65b3b9aaa71a01484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565a9f99728426ce2380a9580e2a9442481ceae7679906c30b1a0e21a10f26150e0645ab6edfdab1ce8f8bea7b1dee511c5fd38ac0e702c1c15bb86b52bca1b71e15b96982d262a442024c33ceb7dd8f949063c2e5e613e873250e2f8708bd4e1924abd45f65c2fa5617bfb10ee9e4a42d6b5811acc8029c16274f937dac9e8817c7e579fdb767ffe277f26d413ced06b620ede8362081da21cf67c2ca9d6f15fe5bc05f82f5bb93f8916bad3d63338ca824f3bbc11b57ce94a5fa1bc239533679903d6fec92a8c792fd86e2960188c14f21e399cfd72a50c620e10aefc6249360b463df9a89bf6836f4f26359207b765578e5ed76ae9f31b1cc48324be576e3d8e44d217445dba466f9b6293fdf05448584eb64f61e02903f834518622b7d4732471c6e0e22e22d1f45e31f0509eab39cdea5980a492a1da2aaac55a98a01216cd4bfe7abaa682af0fbff2dfed030ba28f1285df750e4d3477190dd193f8643b61d8ac1c427d590badb1f61a05d480908fbdc7c6f0502dd0c4abb51d725e92f95da2a8facb79881a844e2026911adcc659d1fb20a2fce63787c8bb0d9f6789c4b231c76da81c3f0718eb7156565a081d2be6b4170c0e0bcebddd459f53db2590c974bca0d705c055dee8c629bf854a5d58edc85228499ec6dde80cce4c8910b81b1e9e8b0f43bd39c8d69c3a80672729b7dc952dd9448688b6bd06afc2d2819cda80b66c57b52ccf7ac1a86601410d18d0c732f69de792e0894a9541684ef174de766fd4ce55efea8f53812867be6a391ac865802dbc26d93959df327ec2667c7256aa5a1d3c45a69a6158f285d6c97c3b8eedb09527848500517995a9eae4cd911df531544c77f5a9a2f22313e3eb72ca7a07dba243476bc926992e0d1e58b4a2fc8c7b01e0cad726237933ea319bad7537d39f3ed635d1e6c1d29e97b3d2160a09e30ee2b65ac5bce00996a73c008bcf351cecb97b6833b6d121dcf4644260b2946ea204732ac9954b228f0beaa15071930fd9583dfc466d12b5f0eeeba6dcf23d5ce8ae62ee5796359d97a4a15955c778d868d0ef9991d9f2833b5bb66119c5f8b396fd108baed7906cbb3cc376d13551caed97fece6f42a4c908ee279f1127fda1dd3ee77d8de0a6f3c135fa3f1cffe38591b6738dc97b55f0acc52be9753ce53e64d7e497bb00ca6123758df3b68fad99e35c04389f7514a8e36039f541598a417275e77869989782325a15b5342ac5011ff07af698584b476b35d941a4981eac590a07a092bb50342da5d3341f901aa07964a8d02b623c7b106dd0ae50bfa007a22d46c8772fa55558176602946cb1d11ea5460db7586fb89c6d3bcd3ab6dd20df4a4db63d2e7d52380800ad812b8640887e027e946df96488b47fbc4a4fadaa8beda4abe446fafea5403fae2ef'),
packet.to_bytes()) packet.to_bytes())
@needs_test_with_all_chacha20_implementations @needs_test_with_all_chacha20_implementations
def test_process_onion_packet(self): def test_new_onion_packet_mixed_payloads(self):
# test vector from bolt-04
payment_path_pubkeys = [
bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'),
bfh('0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c'),
bfh('027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007'),
bfh('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'),
bfh('02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145'),
]
session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141')
associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242')
hops_data = [
OnionHopsDataSingle(is_tlv_payload=False, payload={
"amt_to_forward": {"amt_to_forward": 0},
"outgoing_cltv_value": {"outgoing_cltv_value": 0},
"short_channel_id": {"short_channel_id": bfh('0000000000000000')},
}),
OnionHopsDataSingle(is_tlv_payload=True),
OnionHopsDataSingle(is_tlv_payload=True),
OnionHopsDataSingle(is_tlv_payload=True),
OnionHopsDataSingle(is_tlv_payload=False, payload={
"amt_to_forward": {"amt_to_forward": 4},
"outgoing_cltv_value": {"outgoing_cltv_value": 4},
"short_channel_id": {"short_channel_id": bfh('0404040404040404')},
}),
]
hops_data[1]._raw_bytes_payload = bfh("0101010101010101000000000000000100000001")
hops_data[2]._raw_bytes_payload = bfh("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")
hops_data[3]._raw_bytes_payload = bfh("0303030303030303000000000000000300000003")
packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data)
self.assertEqual(bfh('0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a710f8eaf9ccc768f66bb5dec1f7827f33c43fe2ddd05614c8283aa78e9e7573f87c50f7d61ab590531cf08000178a333a347f8b4072e1cea42da7552402b10765adae3f581408f35ff0a71a34b78b1d8ecae77df96c6404bae9a8e8d7178977d7094a1ae549f89338c0777551f874159eb42d3a59fb9285ad4e24883f27de23942ec966611e99bee1cee503455be9e8e642cef6cef7b9864130f692283f8a973d47a8f1c1726b6e59969385975c766e35737c8d76388b64f748ee7943ffb0e2ee45c57a1abc40762ae598723d21bd184e2b338f68ebff47219357bd19cd7e01e2337b806ef4d717888e129e59cd3dc31e6201ccb2fd6d7499836f37a993262468bcb3a4dcd03a22818aca49c6b7b9b8e9e870045631d8e039b066ff86e0d1b7291f71cefa7264c70404a8e538b566c17ccc5feab231401e6c08a01bd5edfc1aa8e3e533b96e82d1f91118d508924b923531929aea889fcdf057f5995d9731c4bf796fb0e41c885d488dcbc68eb742e27f44310b276edc6f652658149e7e9ced4edde5d38c9b8f92e16f6b4ab13d710ee5c193921909bdd75db331cd9d7581a39fca50814ed8d9d402b86e7f8f6ac2f3bca8e6fe47eb45fbdd3be21a8a8d200797eae3c9a0497132f92410d804977408494dff49dd3d8bce248e0b74fd9e6f0f7102c25ddfa02bd9ad9f746abbfa3379834bc2380d58e9d23237821475a1874484783a15d68f47d3dc339f38d9bf925655d5c946778680fd6d1f062f84128895aff09d35d6c92cca63d3f95a9ee8f2a84f383b4d6a087533e65de12fc8dcaf85777736a2088ff4b22462265028695b37e70963c10df8ef2458756c73007dc3e544340927f9e9f5ea4816a9fd9832c311d122e9512739a6b4714bba590e31caa143ce83cb84b36c738c60c3190ff70cd9ac286a9fd2ab619399b68f1f7447be376ce884b5913c8496d01cbf7a44a60b6e6747513f69dc538f340bc1388e0fde5d0c1db50a4dcb9cc0576e0e2474e4853af9623212578d502757ffb2e0e749695ed70f61c116560d0d4154b64dcf3cbf3c91d89fb6dd004dc19588e3479fcc63c394a4f9e8a3b8b961fce8a532304f1337f1a697a1bb14b94d2953f39b73b6a3125d24f27fcd4f60437881185370bde68a5454d816e7a70d4cea582effab9a4f1b730437e35f7a5c4b769c7b72f0346887c1e63576b2f1e2b3706142586883f8cf3a23595cc8e35a52ad290afd8d2f8bcd5b4c1b891583a4159af7110ecde092079209c6ec46d2bda60b04c519bb8bc6dffb5c87f310814ef2f3003671b3c90ddf5d0173a70504c2280d31f17c061f4bb12a978122c8a2a618bb7d1edcf14f84bf0fa181798b826a254fca8b6d7c81e0beb01bd77f6461be3c8647301d02b04753b0771105986aa0cbc13f7718d64e1b3437e8eef1d319359914a7932548c91570ef3ea741083ca5be5ff43c6d9444d29df06f76ec3dc936e3d180f4b6d0fbc495487c7d44d7c8fe4a70d5ff1461d0d9593f3f898c919c363fa18341ce9dae54f898ccf3fe792136682272941563387263c51b2a2f32363b804672cc158c9230472b554090a661aa81525d11876eefdcc45442249e61e07284592f1606491de5c0324d3af4be035d7ede75b957e879e9770cdde2e1bbc1ef75d45fe555f1ff6ac296a2f648eeee59c7c08260226ea333c285bcf37a9bbfa57ba2ab8083c4be6fc2ebe279537d22da96a07392908cf22b233337a74fe5c603b51712b43c3ee55010ee3d44dd9ba82bba3145ec358f863e04bbfa53799a7a9216718fd5859da2f0deb77b8e315ad6868fdec9400f45a48e6dc8ddbaeb3'),
packet.to_bytes())
@needs_test_with_all_chacha20_implementations
def test_process_onion_packet_mixed_payloads(self):
# this test is not from bolt-04, but is based on the one there;
# here the TLV payloads are actually sane...
payment_path_pubkeys = [
bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'),
bfh('0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c'),
bfh('027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007'),
bfh('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'),
bfh('02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145'),
]
payment_path_privkeys = [
bfh('4141414141414141414141414141414141414141414141414141414141414141'),
bfh('4242424242424242424242424242424242424242424242424242424242424242'),
bfh('4343434343434343434343434343434343434343434343434343434343434343'),
bfh('4444444444444444444444444444444444444444444444444444444444444444'),
bfh('4545454545454545454545454545454545454545454545454545454545454545'),
]
session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141')
associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242')
hops_data = [
OnionHopsDataSingle(is_tlv_payload=False, payload={
"amt_to_forward": {"amt_to_forward": 0},
"outgoing_cltv_value": {"outgoing_cltv_value": 0},
"short_channel_id": {"short_channel_id": bfh('0000000000000000')},
}),
OnionHopsDataSingle(is_tlv_payload=True, payload={
"amt_to_forward": {"amt_to_forward": 1},
"outgoing_cltv_value": {"outgoing_cltv_value": 1},
"short_channel_id": {"short_channel_id": bfh('0101010101010101')},
}),
OnionHopsDataSingle(is_tlv_payload=True, payload={
"amt_to_forward": {"amt_to_forward": 2},
"outgoing_cltv_value": {"outgoing_cltv_value": 2},
"short_channel_id": {"short_channel_id": bfh('0202020202020202')},
}),
OnionHopsDataSingle(is_tlv_payload=True, payload={
"amt_to_forward": {"amt_to_forward": 3},
"outgoing_cltv_value": {"outgoing_cltv_value": 3},
"short_channel_id": {"short_channel_id": bfh('0303030303030303')},
}),
OnionHopsDataSingle(is_tlv_payload=False, payload={
"amt_to_forward": {"amt_to_forward": 4},
"outgoing_cltv_value": {"outgoing_cltv_value": 4},
"short_channel_id": {"short_channel_id": bfh('0404040404040404')},
}),
]
packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data)
self.assertEqual(bfh('0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71bde5adfa90b337f34616d8673d09dd055937273045566ce537ffbe3f9d1f263dc10c7d61ae590536c609010079a232a247922a5395359a63dfbefb85f40317e23254f3023f7d4a98f746c9ab06647645ce55c67308e3c77dc87a1caeac51b03b23c60f05e536e1d757c8c1093e34accfc4f97b5920f6dd2069d5b9ddbb384c3ac575e999a92a4434470ab0aa040c4c3cace3162a405842a88be783e64fad54bd6727c23fc446b7ec0dc3eec5a03eb6c70ec2784911c9e6d274322ec465f0972eb8e771b149f319582ba64dbc2b8e56a3ea79002801c09354f1541cf79bd1dccf5d6bd6b6bacc87a0f24ce497e14e8037e5a79fb4d9ca63fe47f17765963e8f17468a5eaec19a6cca2bfc4e4a366fea3a92112a945856be55e45197ecbab523025e7589529c30cc8addc8fa39d23ef64fa2e51a219c3bd4d3c484832f8e5af16bc46cdba0403991f4fc1b74beef857acf15fefed82ac8678ca66d26262c681beddfdb485aa498813b1a6c5833f1339c1a35244ab76baa0ccaf681ec1f54004e387063335648a77b65d90dde74f1c4b0a729ca25fa53256f7db6d35818b4e5910ba78ec69cf3646bf248ef46cf9cc33062662de2afe4dcf005951b85fd759429fa1ae490b78b14132ccb791232a6c680f03634c0136817f51bf9603a0dba405e7b347830be4327fceccd4734456842b82cf6275393b279bc6ac93d743e00a2d6042960089f70c782ce554b9f73eeeefeea50df7f6f80de1c4e869a7b502f9a5df30d1175402fa780812d35c6d489a30bb0cea53a1088669a238cccf416ecb37f8d8e6ea1327b64979d48e937db69a44a902923a75113685a4aca4a8d9c62b388b48d9c9e2ab9c2df4d529223144de6e16f2dd95a063da79163b3fe006a80263cde4410648f7c3e1f4a7707f82eb0e209002d972c7e57b4ff8ce063fa7b4140f52f569f0cc8793a97a170613efb6b27ba3a0370f8ea74fc0d6aabba54e0ee967abc70e87b580d2aac244236b7752db9d83b159afc1faf6b44b697643235bf59e99f43428caff409d26b9139538865b1f5cf4699f9296088aca461209024ad1dd00e3566e4fde2117b7b3ffced6696b735816a00199890056de86dcbb1b930228143dbf04f07c0eb34370089ea55c43b2c4546cbe1ff0c3a6217d994af9b4225f4b5acb1e3129f5f5b98d381a4692a8561c670b2ee95869f9614e76bb07f623c5194e1c9d26334026f8f5437ec1cde526f914fa094a465f0adcea32b79bfa44d2562536b0d8366da9ee577666c1d5e39615444ca5c900b8199fafac002b8235688eaa0c6887475a913b37d9a4ed43a894ea4576102e5d475ae0b962240ea95fc367b7ac214a4f8682448a9c0d2eea35727bdedc235a975ecc8148a5b03d6291a051dbefe19c8b344d2713c6664dd94ced53c6be39a837fbf1169cca6a12b0a2710f443ba1afeecb51e94236b2a6ed1c2f365b595443b1515de86dcb8c67282807789b47c331cde2fdd721262bef165fa96b7919d11bc5f2022f5affffdd747c7dbe3de8add829a0a8913519fdf7dba4e8a7a25456d2d559746d39ea6ffa31c7b904792fb734bba30f2e1adf7457a994513a1807785fe7b22bf419d1f407f8e2db8b22c0512b078c0cfdfd599e6c4a9d0cc624b9e24b87f30541c3248cd6643df15d251775cc457df4ea6b4e4c5990d87541028c6f0eb28502db1c11a92797168d0b68cb0a0d345b3a3ad05fc4016862f403c64670c41a2c0c6d4e384f5f7da6a204a24530a51182fd7164f120e74a78decb1ab6cda6b9cfc68ac0a35f7a57e750ead65a8e0429cc16e733b9e4feaea25d06c1a4768'),
packet.to_bytes())
for i, privkey in enumerate(payment_path_privkeys):
processed_packet = process_onion_packet(packet, associated_data, privkey)
self.assertEqual(hops_data[i].to_bytes(), processed_packet.hop_data.to_bytes())
packet = processed_packet.next_packet
@needs_test_with_all_chacha20_implementations
def test_process_onion_packet_legacy(self):
# this test is not from bolt-04, but is based on the one there; # this test is not from bolt-04, but is based on the one there;
# except here we have the privkeys for these pubkeys # except here we have the privkeys for these pubkeys
payment_path_pubkeys = [ payment_path_pubkeys = [
@ -164,28 +263,38 @@ class Test_LNRouter(TestCaseForTestnet):
session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141') session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141')
associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242') associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242')
hops_data = [ hops_data = [
OnionHopsDataSingle(OnionPerHop( OnionHopsDataSingle(is_tlv_payload=False, payload={
bfh('0000000000000000'), bfh('0000000000000000'), bfh('00000000') "amt_to_forward": {"amt_to_forward": 0},
)), "outgoing_cltv_value": {"outgoing_cltv_value": 0},
OnionHopsDataSingle(OnionPerHop( "short_channel_id": {"short_channel_id": bfh('0000000000000000')},
bfh('0101010101010101'), bfh('0000000000000001'), bfh('00000001') }),
)), OnionHopsDataSingle(is_tlv_payload=False, payload={
OnionHopsDataSingle(OnionPerHop( "amt_to_forward": {"amt_to_forward": 1},
bfh('0202020202020202'), bfh('0000000000000002'), bfh('00000002') "outgoing_cltv_value": {"outgoing_cltv_value": 1},
)), "short_channel_id": {"short_channel_id": bfh('0101010101010101')},
OnionHopsDataSingle(OnionPerHop( }),
bfh('0303030303030303'), bfh('0000000000000003'), bfh('00000003') OnionHopsDataSingle(is_tlv_payload=False, payload={
)), "amt_to_forward": {"amt_to_forward": 2},
OnionHopsDataSingle(OnionPerHop( "outgoing_cltv_value": {"outgoing_cltv_value": 2},
bfh('0404040404040404'), bfh('0000000000000004'), bfh('00000004') "short_channel_id": {"short_channel_id": bfh('0202020202020202')},
)), }),
OnionHopsDataSingle(is_tlv_payload=False, payload={
"amt_to_forward": {"amt_to_forward": 3},
"outgoing_cltv_value": {"outgoing_cltv_value": 3},
"short_channel_id": {"short_channel_id": bfh('0303030303030303')},
}),
OnionHopsDataSingle(is_tlv_payload=False, payload={
"amt_to_forward": {"amt_to_forward": 4},
"outgoing_cltv_value": {"outgoing_cltv_value": 4},
"short_channel_id": {"short_channel_id": bfh('0404040404040404')},
}),
] ]
packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data) packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data)
self.assertEqual(bfh('0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f28368661954176cd9869da33d713aa219fcef1e5c806fef11e696bcc66844de8271c27974a049d041ffc5be934b8575c6ff4371f2f88d4edfd73e445534d3f6ae15b64b0d8308390bebf8d149002e31bdc283056477ba27c8054c248ad7306de31663a7c99ec65b251704041f7c4cc40a0016ba172fbf805ec59132a65a4c7eb1f41337931c5df0f840704535729262d30c6132d1b390f073edec8fa057176c6268b6ad06a82ff0229c3be444ee50b40686bc1306838b93c65771de1b6ca05dace1ff9814a6e58b2dd71e8244c83e28b2ed5a3b09e9e7df5c8c747e5765ba366a4f7407a6c6b0a32fb5521cce7cd668f7434c909c1be027d8595d85893e5f612c49a93eeeed80a78bab9c4a621ce0f6f5df7d64a9c8d435db19de192d9db522c7f7b4e201fc1b61a9bd3efd062ae24455d463818b01e2756c7d0691bc3ac4c017be34c9a8b2913bb1b94056bf7a21730afc3f254ffa41ca140a5d87ff470f536d08619e8004d50de2fe5954d6aa4a00570da397ba15ae9ea4d7d1f136256a9093f0a787a36cbb3520b6a3cf4d1b13b16bf399c4b0326da1382a90bd79cf92f4808c8c84eaa50a8ccf44acbde0e35b2e6b72858c8446d6a05f3ba70fb4adc70af27cea9bd1dc1ea35fb3cc236b8b9b69b614903db339b22ad5dc2ddda7ac65fd7de24e60b7dbba7aafc9d26c0f9fcb03f1bb85dfc21762f862620651db52ea703ae60aa7e07febf11caa95c4245a4b37eb9c233e1ab1604fb85849e7f49cb9f7c681c4d91b7c320eb4b89b9c6bcb636ceadda59f6ed47aa5b1ea0a946ea98f6ad2614e79e0d4ef96d6d65903adb0479709e03008bbdf3355dd87df7d68965fdf1ad5c68b6dc2761b96b10f8eb4c0329a646bf38cf4702002e61565231b4ac7a9cde63d23f7b24c9d42797b3c434558d71ed8bf9fcab2c2aee3e8b38c19f9edc3ad3dfe9ebba7387ce4764f97ed1c1a83552dff8315546761479a6f929c39bcca0891d4a967d1b77fa80feed6ae74ac82ed5fb7be225c3f2b0ebdc652afc2255c47bc318ac645bbf19c0819ff527ff6708a78e19c8ca3dc8087035e10d5ac976e84b71148586c8a5a7b26ed11b5b401ce7bb2ac532207eaa24d2f53aaa8024607da764d807c91489e82fcad04e6b8992a507119367f576ee5ffe6807d5723d60234d4c3f94adce0acfed9dba535ca375446a4e9b500b74ad2a66e1c6b0fc38933f282d3a4a877bceceeca52b46e731ca51a9534224a883c4a45587f973f73a22069a4154b1da03d307d8575c821bef0eef87165b9a1bbf902ecfca82ddd805d10fbb7147b496f6772f01e9bf542b00288f3a6efab32590c1f34535ece03a0587ca187d27a98d4c9aa7c044794baa43a81abbe307f51d0bda6e7b4cf62c4be553b176321777e7fd483d6cec16df137293aaf3ad53608e1c7831368675bb9608db04d5c859e7714edab3d2389837fa071f0795adfabc51507b1adbadc7f83e80bd4e4eb9ed1a89c9e0a6dc16f38d55181d5666b02150651961aab34faef97d80fa4e1960864dfec3b687fd4eadf7aa6c709cb4698ae86ae112f386f33731d996b9d41926a2e820c6ba483a61674a4bae03af37e872ffdc0a9a8a034327af17e13e9e7ac619c9188c2a5c12a6ebf887721455c0e2822e67a621ed49f1f50dfc38b71c29d0224954e84ced086c80de552cca3a14adbe43035901225bafc3db3b672c780e4fa12b59221f93690527efc16a28e7c63d1a99fc881f023b03a157076a7e999a715ed37521adb483e2477d75ba5a55d4abad22b024c5317334b6544f15971591c774d896229e4e668fc1c7958fbd76fa0b152a6f14c95692083badd066b6621367fd73d88ba8d860566e6d55b871d80c68296b80ae8847d'), self.assertEqual(bfh('0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f28368661954176cd9869da33d713aa219fcef1e5c806fef11e696bcc66844de8271c27974a049d041ffc5be934b8575c6ff4371f2f88d4edfd73e445534d3f6ae15b64b0d8308390bebf8d149002e31bdc283056477ba27c8054c248ad7306de31663a7c99ec65b251704041f7c4cc40a0016ba172fbf805ec59132a65a4c7eb1f41337931c5df0f840704535729262d30c6132d1b390f073edec8fa057176c6268b6ad06a82ff0229c3be444ee50b40686bc1306838b93c65771de1b6ca05dace1ff9814a6e58b2dd71e8244c83e28b2ed5a3b09e9e7df5c8c747e5765ba366a4f7407a6c6b0a32fb5521cce7cd668f7434c909c1be027d8595d85893e5f612c49a93eeeed80a78bab9c4a621ce0f6f5df7d64a9c8d435db19de192d9db522c7f7b4e201fc1b61a9bd3efd062ae24455d463818b01e2756c7d0691bc3ac4c017be34c9a8b2913bb1b94056bf7a21730afc3f254ffa41ca140a5d87ff470f536d08619e8004d50de2fe5954d6aa4a00570da397ba15ae9ea4d7d1f136256a9093f0a787a36cbb3520b6a3cf4d1b13b16bf399c4b0326da1382a90bd79cf92f4808c8c84eaa50a8ccf44acbde0e35b2e6b72858c8446d6a05f3ba70fb4adc70af27cea9bd1dc1ea35fb3cc236b8b9b69b614903db339b22ad5dc2ddda7ac65fd7de24e60b7dbba7aafc9d26c0f9fcb03f1bb85dfc21762f862620651db52ea703ae60aa7e07febf11caa95c4245a4b37eb9c233e1ab1604fb85849e7f49cb9f7c681c4d91b7c320eb4b89b9c6bcb636ceadda59f6ed47aa5b1ea0a946ea98f6ad2614e79e0d4ef96d6d65903adb0479709e03008bbdf3355dd87df7d68965fdf1ad5c68b6dc2761b96b10f8eb4c0329a646bf38cf4702002e61565231b4ac7a9cde63d23f7b24c9d42797b3c434558d71ed8bf9fcab2c2aee3e8b38c19f9edc3ad3dfe9ebba7387ce4764f97ed1c1a83552dff8315546761479a6f929c39bcca0891d4a967d1b77fa80feed6ae74ac82ed5fb7be225c3f2b0ebdc652afc2255c47bc318ac645bbf19c0819ff527ff6708a78e19c8ca3dc8087035e10d5ac976e84b71148586c8a5a7b26ed11b5b401ce7bb2ac532207eaa24d2f53aaa8024607da764d807c91489e82fcad04e6b8992a507119367f576ee5ffe6807d5723d60234d4c3f94adce0acfed9dba535ca375446a4e9b500b74ad2a66e1c6b0fc38933f282d3a4a877bceceeca52b46e731ca51a9534224a883c4a45587f973f73a22069a4154b1da03d307d8575c821bef0eef87165b9a1bbf902ecfca82ddd805d10fbb7147b496f6772f01e9bf542b00288f3a6efab32590c1f34535ece03a0587ca187d27a98d4c9aa7c044794baa43a81abbe307f51d0bda6e7b4cf62c4be553b176321777e7fd483d6cec16df137293aaf3ad53608e1c7831368675bb9608db04d5c859e7714edab3d2389837fa071f0795adfabc51507b1adbadc7f83e80bd4e4eb9ed1a89c9e0a6dc16f38d55181d5666b02150651961aab34faef97d80fa4e1960864dfec3b687fd4eadf7aa6c709cb4698ae86ae112f386f33731d996b9d41926a2e820c6ba483a61674a4bae03af37e872ffdc0a9a8a034327af17e13e9e7ac619c9188c2a5c12a6ebf887721455c0e2822e67a621ed49f1f50dfc38b71c29d0224954e84ced086c80de552cca3a14adbe43035901225bafc3db3b672c780e4fa12b59221f93690527efc16a28e7c63d1a99fc881f023b03a157076a7e999a715ed37521adb483e2477d75ba5a55d4abad22b024c5317334b6544f15971591c774d896229e4e668fc1c7958fbd76fa0b152a6f14c95692083badd066b6621367fd73d88ba8d860566e6d55b871d80c68296b80ae8847d'),
packet.to_bytes()) packet.to_bytes())
for i, privkey in enumerate(payment_path_privkeys): for i, privkey in enumerate(payment_path_privkeys):
processed_packet = process_onion_packet(packet, associated_data, privkey) processed_packet = process_onion_packet(packet, associated_data, privkey)
self.assertEqual(hops_data[i].per_hop.to_bytes(), processed_packet.hop_data.per_hop.to_bytes()) self.assertEqual(hops_data[i].to_bytes(), processed_packet.hop_data.to_bytes())
packet = processed_packet.next_packet packet = processed_packet.next_packet
@needs_test_with_all_chacha20_implementations @needs_test_with_all_chacha20_implementations