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