1# -*- coding: utf-8 -*- 2# 3# Electrum - lightweight Bitcoin client 4# Copyright (C) 2018 The Electrum developers 5# 6# Permission is hereby granted, free of charge, to any person 7# obtaining a copy of this software and associated documentation files 8# (the "Software"), to deal in the Software without restriction, 9# including without limitation the rights to use, copy, modify, merge, 10# publish, distribute, sublicense, and/or sell copies of the Software, 11# and to permit persons to whom the Software is furnished to do so, 12# subject to the following conditions: 13# 14# The above copyright notice and this permission notice shall be 15# included in all copies or substantial portions of the Software. 16# 17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 22# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24# SOFTWARE. 25 26import io 27import hashlib 28from typing import Sequence, List, Tuple, NamedTuple, TYPE_CHECKING, Dict, Any, Optional 29from enum import IntEnum, IntFlag 30 31from . import ecc 32from .crypto import sha256, hmac_oneshot, chacha20_encrypt 33from .util import bh2u, profiler, xor_bytes, bfh 34from .lnutil import (get_ecdh, PaymentFailure, NUM_MAX_HOPS_IN_PAYMENT_PATH, 35 NUM_MAX_EDGES_IN_PAYMENT_PATH, ShortChannelID, OnionFailureCodeMetaFlag) 36from .lnmsg import OnionWireSerializer, read_bigsize_int, write_bigsize_int 37from . import lnmsg 38 39if TYPE_CHECKING: 40 from .lnrouter import LNPaymentRoute 41 42 43HOPS_DATA_SIZE = 1300 # also sometimes called routingInfoSize in bolt-04 44TRAMPOLINE_HOPS_DATA_SIZE = 400 45LEGACY_PER_HOP_FULL_SIZE = 65 46PER_HOP_HMAC_SIZE = 32 47 48 49class UnsupportedOnionPacketVersion(Exception): pass 50class InvalidOnionMac(Exception): pass 51class InvalidOnionPubkey(Exception): pass 52 53 54class LegacyHopDataPayload: 55 56 def __init__(self, *, short_channel_id: bytes, amt_to_forward: int, outgoing_cltv_value: int): 57 self.short_channel_id = ShortChannelID(short_channel_id) 58 self.amt_to_forward = amt_to_forward 59 self.outgoing_cltv_value = outgoing_cltv_value 60 61 def to_bytes(self) -> bytes: 62 ret = self.short_channel_id 63 ret += int.to_bytes(self.amt_to_forward, length=8, byteorder="big", signed=False) 64 ret += int.to_bytes(self.outgoing_cltv_value, length=4, byteorder="big", signed=False) 65 ret += bytes(12) # padding 66 if len(ret) != 32: 67 raise Exception('unexpected length {}'.format(len(ret))) 68 return ret 69 70 def to_tlv_dict(self) -> dict: 71 d = { 72 "amt_to_forward": {"amt_to_forward": self.amt_to_forward}, 73 "outgoing_cltv_value": {"outgoing_cltv_value": self.outgoing_cltv_value}, 74 "short_channel_id": {"short_channel_id": self.short_channel_id}, 75 } 76 return d 77 78 @classmethod 79 def from_bytes(cls, b: bytes) -> 'LegacyHopDataPayload': 80 if len(b) != 32: 81 raise Exception('unexpected length {}'.format(len(b))) 82 return LegacyHopDataPayload( 83 short_channel_id=b[:8], 84 amt_to_forward=int.from_bytes(b[8:16], byteorder="big", signed=False), 85 outgoing_cltv_value=int.from_bytes(b[16:20], byteorder="big", signed=False), 86 ) 87 88 @classmethod 89 def from_tlv_dict(cls, d: dict) -> 'LegacyHopDataPayload': 90 return LegacyHopDataPayload( 91 short_channel_id=d["short_channel_id"]["short_channel_id"] if "short_channel_id" in d else b"\x00" * 8, 92 amt_to_forward=d["amt_to_forward"]["amt_to_forward"], 93 outgoing_cltv_value=d["outgoing_cltv_value"]["outgoing_cltv_value"], 94 ) 95 96 97class OnionHopsDataSingle: # called HopData in lnd 98 99 def __init__(self, *, is_tlv_payload: bool, payload: dict = None): 100 self.is_tlv_payload = is_tlv_payload 101 if payload is None: 102 payload = {} 103 self.payload = payload 104 self.hmac = None 105 self._raw_bytes_payload = None # used in unit tests 106 107 def to_bytes(self) -> bytes: 108 hmac_ = self.hmac if self.hmac is not None else bytes(PER_HOP_HMAC_SIZE) 109 if self._raw_bytes_payload is not None: 110 ret = write_bigsize_int(len(self._raw_bytes_payload)) 111 ret += self._raw_bytes_payload 112 ret += hmac_ 113 return ret 114 if not self.is_tlv_payload: 115 ret = b"\x00" # realm==0 116 legacy_payload = LegacyHopDataPayload.from_tlv_dict(self.payload) 117 ret += legacy_payload.to_bytes() 118 ret += hmac_ 119 if len(ret) != LEGACY_PER_HOP_FULL_SIZE: 120 raise Exception('unexpected length {}'.format(len(ret))) 121 return ret 122 else: # tlv 123 payload_fd = io.BytesIO() 124 OnionWireSerializer.write_tlv_stream(fd=payload_fd, 125 tlv_stream_name="tlv_payload", 126 **self.payload) 127 payload_bytes = payload_fd.getvalue() 128 with io.BytesIO() as fd: 129 fd.write(write_bigsize_int(len(payload_bytes))) 130 fd.write(payload_bytes) 131 fd.write(hmac_) 132 return fd.getvalue() 133 134 @classmethod 135 def from_fd(cls, fd: io.BytesIO) -> 'OnionHopsDataSingle': 136 first_byte = fd.read(1) 137 if len(first_byte) == 0: 138 raise Exception(f"unexpected EOF") 139 fd.seek(-1, io.SEEK_CUR) # undo read 140 if first_byte == b'\x00': 141 # legacy hop data format 142 b = fd.read(LEGACY_PER_HOP_FULL_SIZE) 143 if len(b) != LEGACY_PER_HOP_FULL_SIZE: 144 raise Exception(f'unexpected length {len(b)}') 145 ret = OnionHopsDataSingle(is_tlv_payload=False) 146 legacy_payload = LegacyHopDataPayload.from_bytes(b[1:33]) 147 ret.payload = legacy_payload.to_tlv_dict() 148 ret.hmac = b[33:] 149 return ret 150 elif first_byte == b'\x01': 151 # reserved for future use 152 raise Exception("unsupported hop payload: length==1") 153 else: 154 hop_payload_length = read_bigsize_int(fd) 155 hop_payload = fd.read(hop_payload_length) 156 if hop_payload_length != len(hop_payload): 157 raise Exception(f"unexpected EOF") 158 ret = OnionHopsDataSingle(is_tlv_payload=True) 159 ret.payload = OnionWireSerializer.read_tlv_stream(fd=io.BytesIO(hop_payload), 160 tlv_stream_name="tlv_payload") 161 ret.hmac = fd.read(PER_HOP_HMAC_SIZE) 162 assert len(ret.hmac) == PER_HOP_HMAC_SIZE 163 return ret 164 165 def __repr__(self): 166 return f"<OnionHopsDataSingle. is_tlv_payload={self.is_tlv_payload}. payload={self.payload}. hmac={self.hmac}>" 167 168 169class OnionPacket: 170 171 def __init__(self, public_key: bytes, hops_data: bytes, hmac: bytes): 172 assert len(public_key) == 33 173 assert len(hops_data) in [HOPS_DATA_SIZE, TRAMPOLINE_HOPS_DATA_SIZE] 174 assert len(hmac) == PER_HOP_HMAC_SIZE 175 self.version = 0 176 self.public_key = public_key 177 self.hops_data = hops_data # also called RoutingInfo in bolt-04 178 self.hmac = hmac 179 if not ecc.ECPubkey.is_pubkey_bytes(public_key): 180 raise InvalidOnionPubkey() 181 182 def to_bytes(self) -> bytes: 183 ret = bytes([self.version]) 184 ret += self.public_key 185 ret += self.hops_data 186 ret += self.hmac 187 if len(ret) - 66 not in [HOPS_DATA_SIZE, TRAMPOLINE_HOPS_DATA_SIZE]: 188 raise Exception('unexpected length {}'.format(len(ret))) 189 return ret 190 191 @classmethod 192 def from_bytes(cls, b: bytes): 193 if len(b) - 66 not in [HOPS_DATA_SIZE, TRAMPOLINE_HOPS_DATA_SIZE]: 194 raise Exception('unexpected length {}'.format(len(b))) 195 version = b[0] 196 if version != 0: 197 raise UnsupportedOnionPacketVersion('version {} is not supported'.format(version)) 198 return OnionPacket( 199 public_key=b[1:34], 200 hops_data=b[34:-32], 201 hmac=b[-32:] 202 ) 203 204 205def get_bolt04_onion_key(key_type: bytes, secret: bytes) -> bytes: 206 if key_type not in (b'rho', b'mu', b'um', b'ammag', b'pad'): 207 raise Exception('invalid key_type {}'.format(key_type)) 208 key = hmac_oneshot(key_type, msg=secret, digest=hashlib.sha256) 209 return key 210 211 212def get_shared_secrets_along_route(payment_path_pubkeys: Sequence[bytes], 213 session_key: bytes) -> Sequence[bytes]: 214 num_hops = len(payment_path_pubkeys) 215 hop_shared_secrets = num_hops * [b''] 216 ephemeral_key = session_key 217 # compute shared key for each hop 218 for i in range(0, num_hops): 219 hop_shared_secrets[i] = get_ecdh(ephemeral_key, payment_path_pubkeys[i]) 220 ephemeral_pubkey = ecc.ECPrivkey(ephemeral_key).get_public_key_bytes() 221 blinding_factor = sha256(ephemeral_pubkey + hop_shared_secrets[i]) 222 blinding_factor_int = int.from_bytes(blinding_factor, byteorder="big") 223 ephemeral_key_int = int.from_bytes(ephemeral_key, byteorder="big") 224 ephemeral_key_int = ephemeral_key_int * blinding_factor_int % ecc.CURVE_ORDER 225 ephemeral_key = ephemeral_key_int.to_bytes(32, byteorder="big") 226 return hop_shared_secrets 227 228 229def new_onion_packet(payment_path_pubkeys: Sequence[bytes], session_key: bytes, 230 hops_data: Sequence[OnionHopsDataSingle], associated_data: bytes, trampoline=False) -> OnionPacket: 231 num_hops = len(payment_path_pubkeys) 232 assert num_hops == len(hops_data) 233 hop_shared_secrets = get_shared_secrets_along_route(payment_path_pubkeys, session_key) 234 235 data_size = TRAMPOLINE_HOPS_DATA_SIZE if trampoline else HOPS_DATA_SIZE 236 filler = _generate_filler(b'rho', hops_data, hop_shared_secrets, data_size) 237 next_hmac = bytes(PER_HOP_HMAC_SIZE) 238 239 # Our starting packet needs to be filled out with random bytes, we 240 # generate some deterministically using the session private key. 241 pad_key = get_bolt04_onion_key(b'pad', session_key) 242 mix_header = generate_cipher_stream(pad_key, data_size) 243 244 # compute routing info and MAC for each hop 245 for i in range(num_hops-1, -1, -1): 246 rho_key = get_bolt04_onion_key(b'rho', hop_shared_secrets[i]) 247 mu_key = get_bolt04_onion_key(b'mu', hop_shared_secrets[i]) 248 hops_data[i].hmac = next_hmac 249 stream_bytes = generate_cipher_stream(rho_key, data_size) 250 hop_data_bytes = hops_data[i].to_bytes() 251 mix_header = mix_header[:-len(hop_data_bytes)] 252 mix_header = hop_data_bytes + mix_header 253 mix_header = xor_bytes(mix_header, stream_bytes) 254 if i == num_hops - 1 and len(filler) != 0: 255 mix_header = mix_header[:-len(filler)] + filler 256 packet = mix_header + associated_data 257 next_hmac = hmac_oneshot(mu_key, msg=packet, digest=hashlib.sha256) 258 259 return OnionPacket( 260 public_key=ecc.ECPrivkey(session_key).get_public_key_bytes(), 261 hops_data=mix_header, 262 hmac=next_hmac) 263 264 265def calc_hops_data_for_payment( 266 route: 'LNPaymentRoute', 267 amount_msat: int, 268 final_cltv: int, *, 269 total_msat=None, 270 payment_secret: bytes = None) -> Tuple[List[OnionHopsDataSingle], int, int]: 271 272 """Returns the hops_data to be used for constructing an onion packet, 273 and the amount_msat and cltv to be used on our immediate channel. 274 """ 275 if len(route) > NUM_MAX_EDGES_IN_PAYMENT_PATH: 276 raise PaymentFailure(f"too long route ({len(route)} edges)") 277 # payload that will be seen by the last hop: 278 amt = amount_msat 279 cltv = final_cltv 280 hop_payload = { 281 "amt_to_forward": {"amt_to_forward": amt}, 282 "outgoing_cltv_value": {"outgoing_cltv_value": cltv}, 283 } 284 # for multipart payments we need to tell the receiver about the total and 285 # partial amounts 286 if payment_secret is not None: 287 hop_payload["payment_data"] = { 288 "payment_secret": payment_secret, 289 "total_msat": total_msat, 290 "amount_msat": amt 291 } 292 hops_data = [OnionHopsDataSingle( 293 is_tlv_payload=route[-1].has_feature_varonion(), payload=hop_payload)] 294 # payloads, backwards from last hop (but excluding the first edge): 295 for edge_index in range(len(route) - 1, 0, -1): 296 route_edge = route[edge_index] 297 is_trampoline = route_edge.is_trampoline() 298 if is_trampoline: 299 amt += route_edge.fee_for_edge(amt) 300 cltv += route_edge.cltv_expiry_delta 301 hop_payload = { 302 "amt_to_forward": {"amt_to_forward": amt}, 303 "outgoing_cltv_value": {"outgoing_cltv_value": cltv}, 304 "short_channel_id": {"short_channel_id": route_edge.short_channel_id}, 305 } 306 hops_data.append( 307 OnionHopsDataSingle( 308 is_tlv_payload=route[edge_index-1].has_feature_varonion(), 309 payload=hop_payload)) 310 if not is_trampoline: 311 amt += route_edge.fee_for_edge(amt) 312 cltv += route_edge.cltv_expiry_delta 313 hops_data.reverse() 314 return hops_data, amt, cltv 315 316 317def _generate_filler(key_type: bytes, hops_data: Sequence[OnionHopsDataSingle], 318 shared_secrets: Sequence[bytes], data_size:int) -> bytes: 319 num_hops = len(hops_data) 320 321 # generate filler that matches all but the last hop (no HMAC for last hop) 322 filler_size = 0 323 for hop_data in hops_data[:-1]: 324 filler_size += len(hop_data.to_bytes()) 325 filler = bytearray(filler_size) 326 327 for i in range(0, num_hops-1): # -1, as last hop does not obfuscate 328 # Sum up how many frames were used by prior hops. 329 filler_start = data_size 330 for hop_data in hops_data[:i]: 331 filler_start -= len(hop_data.to_bytes()) 332 # The filler is the part dangling off of the end of the 333 # routingInfo, so offset it from there, and use the current 334 # hop's frame count as its size. 335 filler_end = data_size + len(hops_data[i].to_bytes()) 336 337 stream_key = get_bolt04_onion_key(key_type, shared_secrets[i]) 338 stream_bytes = generate_cipher_stream(stream_key, 2 * data_size) 339 filler = xor_bytes(filler, stream_bytes[filler_start:filler_end]) 340 filler += bytes(filler_size - len(filler)) # right pad with zeroes 341 342 return filler 343 344 345def generate_cipher_stream(stream_key: bytes, num_bytes: int) -> bytes: 346 return chacha20_encrypt(key=stream_key, 347 nonce=bytes(8), 348 data=bytes(num_bytes)) 349 350 351class ProcessedOnionPacket(NamedTuple): 352 are_we_final: bool 353 hop_data: OnionHopsDataSingle 354 next_packet: OnionPacket 355 trampoline_onion_packet: OnionPacket 356 357 358# TODO replay protection 359def process_onion_packet( 360 onion_packet: OnionPacket, 361 associated_data: bytes, 362 our_onion_private_key: bytes, 363 is_trampoline=False) -> ProcessedOnionPacket: 364 if not ecc.ECPubkey.is_pubkey_bytes(onion_packet.public_key): 365 raise InvalidOnionPubkey() 366 shared_secret = get_ecdh(our_onion_private_key, onion_packet.public_key) 367 # check message integrity 368 mu_key = get_bolt04_onion_key(b'mu', shared_secret) 369 calculated_mac = hmac_oneshot( 370 mu_key, msg=onion_packet.hops_data+associated_data, 371 digest=hashlib.sha256) 372 if onion_packet.hmac != calculated_mac: 373 raise InvalidOnionMac() 374 # peel an onion layer off 375 rho_key = get_bolt04_onion_key(b'rho', shared_secret) 376 data_size = TRAMPOLINE_HOPS_DATA_SIZE if is_trampoline else HOPS_DATA_SIZE 377 stream_bytes = generate_cipher_stream(rho_key, 2 * data_size) 378 padded_header = onion_packet.hops_data + bytes(data_size) 379 next_hops_data = xor_bytes(padded_header, stream_bytes) 380 next_hops_data_fd = io.BytesIO(next_hops_data) 381 hop_data = OnionHopsDataSingle.from_fd(next_hops_data_fd) 382 # trampoline 383 trampoline_onion_packet = hop_data.payload.get('trampoline_onion_packet') 384 if trampoline_onion_packet: 385 top_version = trampoline_onion_packet.get('version') 386 top_public_key = trampoline_onion_packet.get('public_key') 387 top_hops_data = trampoline_onion_packet.get('hops_data') 388 top_hops_data_fd = io.BytesIO(top_hops_data) 389 top_hmac = trampoline_onion_packet.get('hmac') 390 trampoline_onion_packet = OnionPacket( 391 public_key=top_public_key, 392 hops_data=top_hops_data_fd.read(TRAMPOLINE_HOPS_DATA_SIZE), 393 hmac=top_hmac) 394 # calc next ephemeral key 395 blinding_factor = sha256(onion_packet.public_key + shared_secret) 396 blinding_factor_int = int.from_bytes(blinding_factor, byteorder="big") 397 next_public_key_int = ecc.ECPubkey(onion_packet.public_key) * blinding_factor_int 398 next_public_key = next_public_key_int.get_public_key_bytes() 399 next_onion_packet = OnionPacket( 400 public_key=next_public_key, 401 hops_data=next_hops_data_fd.read(data_size), 402 hmac=hop_data.hmac) 403 if hop_data.hmac == bytes(PER_HOP_HMAC_SIZE): 404 # we are the destination / exit node 405 are_we_final = True 406 else: 407 # we are an intermediate node; forwarding 408 are_we_final = False 409 return ProcessedOnionPacket(are_we_final, hop_data, next_onion_packet, trampoline_onion_packet) 410 411 412class FailedToDecodeOnionError(Exception): pass 413 414 415class OnionRoutingFailure(Exception): 416 417 def __init__(self, code: int, data: bytes): 418 self.code = code 419 self.data = data 420 421 def __repr__(self): 422 return repr((self.code, self.data)) 423 424 def to_bytes(self) -> bytes: 425 ret = self.code.to_bytes(2, byteorder="big") 426 ret += self.data 427 return ret 428 429 @classmethod 430 def from_bytes(cls, failure_msg: bytes): 431 failure_code = int.from_bytes(failure_msg[:2], byteorder='big') 432 try: 433 failure_code = OnionFailureCode(failure_code) 434 except ValueError: 435 pass # unknown failure code 436 failure_data = failure_msg[2:] 437 return OnionRoutingFailure(failure_code, failure_data) 438 439 def code_name(self) -> str: 440 if isinstance(self.code, OnionFailureCode): 441 return str(self.code.name) 442 return f"Unknown error ({self.code!r})" 443 444 def decode_data(self) -> Optional[Dict[str, Any]]: 445 try: 446 message_type, payload = OnionWireSerializer.decode_msg(self.to_bytes()) 447 except lnmsg.FailedToParseMsg: 448 payload = None 449 return payload 450 451 452def construct_onion_error( 453 reason: OnionRoutingFailure, 454 onion_packet: OnionPacket, 455 our_onion_private_key: bytes, 456) -> bytes: 457 # create payload 458 failure_msg = reason.to_bytes() 459 failure_len = len(failure_msg) 460 pad_len = 256 - failure_len 461 assert pad_len >= 0 462 error_packet = failure_len.to_bytes(2, byteorder="big") 463 error_packet += failure_msg 464 error_packet += pad_len.to_bytes(2, byteorder="big") 465 error_packet += bytes(pad_len) 466 # add hmac 467 shared_secret = get_ecdh(our_onion_private_key, onion_packet.public_key) 468 um_key = get_bolt04_onion_key(b'um', shared_secret) 469 hmac_ = hmac_oneshot(um_key, msg=error_packet, digest=hashlib.sha256) 470 error_packet = hmac_ + error_packet 471 # obfuscate 472 ammag_key = get_bolt04_onion_key(b'ammag', shared_secret) 473 stream_bytes = generate_cipher_stream(ammag_key, len(error_packet)) 474 error_packet = xor_bytes(error_packet, stream_bytes) 475 return error_packet 476 477 478def _decode_onion_error(error_packet: bytes, payment_path_pubkeys: Sequence[bytes], 479 session_key: bytes) -> Tuple[bytes, int]: 480 """Returns the decoded error bytes, and the index of the sender of the error.""" 481 num_hops = len(payment_path_pubkeys) 482 hop_shared_secrets = get_shared_secrets_along_route(payment_path_pubkeys, session_key) 483 for i in range(num_hops): 484 ammag_key = get_bolt04_onion_key(b'ammag', hop_shared_secrets[i]) 485 um_key = get_bolt04_onion_key(b'um', hop_shared_secrets[i]) 486 stream_bytes = generate_cipher_stream(ammag_key, len(error_packet)) 487 error_packet = xor_bytes(error_packet, stream_bytes) 488 hmac_computed = hmac_oneshot(um_key, msg=error_packet[32:], digest=hashlib.sha256) 489 hmac_found = error_packet[:32] 490 if hmac_computed == hmac_found: 491 return error_packet, i 492 raise FailedToDecodeOnionError() 493 494 495def decode_onion_error(error_packet: bytes, payment_path_pubkeys: Sequence[bytes], 496 session_key: bytes) -> (OnionRoutingFailure, int): 497 """Returns the failure message, and the index of the sender of the error.""" 498 decrypted_error, sender_index = _decode_onion_error(error_packet, payment_path_pubkeys, session_key) 499 failure_msg = get_failure_msg_from_onion_error(decrypted_error) 500 return failure_msg, sender_index 501 502 503def get_failure_msg_from_onion_error(decrypted_error_packet: bytes) -> OnionRoutingFailure: 504 # get failure_msg bytes from error packet 505 failure_len = int.from_bytes(decrypted_error_packet[32:34], byteorder='big') 506 failure_msg = decrypted_error_packet[34:34+failure_len] 507 # create failure message object 508 return OnionRoutingFailure.from_bytes(failure_msg) 509 510 511 512# TODO maybe we should rm this and just use OnionWireSerializer and onion_wire.csv 513BADONION = OnionFailureCodeMetaFlag.BADONION 514PERM = OnionFailureCodeMetaFlag.PERM 515NODE = OnionFailureCodeMetaFlag.NODE 516UPDATE = OnionFailureCodeMetaFlag.UPDATE 517class OnionFailureCode(IntEnum): 518 INVALID_REALM = PERM | 1 519 TEMPORARY_NODE_FAILURE = NODE | 2 520 PERMANENT_NODE_FAILURE = PERM | NODE | 2 521 REQUIRED_NODE_FEATURE_MISSING = PERM | NODE | 3 522 INVALID_ONION_VERSION = BADONION | PERM | 4 523 INVALID_ONION_HMAC = BADONION | PERM | 5 524 INVALID_ONION_KEY = BADONION | PERM | 6 525 TEMPORARY_CHANNEL_FAILURE = UPDATE | 7 526 PERMANENT_CHANNEL_FAILURE = PERM | 8 527 REQUIRED_CHANNEL_FEATURE_MISSING = PERM | 9 528 UNKNOWN_NEXT_PEER = PERM | 10 529 AMOUNT_BELOW_MINIMUM = UPDATE | 11 530 FEE_INSUFFICIENT = UPDATE | 12 531 INCORRECT_CLTV_EXPIRY = UPDATE | 13 532 EXPIRY_TOO_SOON = UPDATE | 14 533 INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS = PERM | 15 534 _LEGACY_INCORRECT_PAYMENT_AMOUNT = PERM | 16 535 FINAL_EXPIRY_TOO_SOON = 17 536 FINAL_INCORRECT_CLTV_EXPIRY = 18 537 FINAL_INCORRECT_HTLC_AMOUNT = 19 538 CHANNEL_DISABLED = UPDATE | 20 539 EXPIRY_TOO_FAR = 21 540 INVALID_ONION_PAYLOAD = PERM | 22 541 MPP_TIMEOUT = 23 542 TRAMPOLINE_FEE_INSUFFICIENT = NODE | 51 543 TRAMPOLINE_EXPIRY_TOO_SOON = NODE | 52 544 545 546# don't use these elsewhere, the names are ambiguous without context 547del BADONION; del PERM; del NODE; del UPDATE 548