• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

spesmilo / electrum / 6330511049621504

25 Nov 2025 12:26PM UTC coverage: 62.326% (+0.1%) from 62.228%
6330511049621504

Pull #10230

CirrusCI

f321x
tests: lnpeer: test_dont_expire_htlcs

Adds unittest to test the dont_expire_htlcs logic
Pull Request #10230: lightning: refactor htlc switch

486 of 603 new or added lines in 9 files covered. (80.6%)

26 existing lines in 7 files now uncovered.

23583 of 37838 relevant lines covered (62.33%)

0.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.07
/electrum/lnutil.py
1
# Copyright (C) 2018 The Electrum developers
2
# Distributed under the MIT software license, see the accompanying
3
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
4
from enum import IntFlag, IntEnum
1✔
5
import enum
1✔
6
from collections import defaultdict
1✔
7
from typing import NamedTuple, List, Tuple, Mapping, Optional, TYPE_CHECKING, Union, Dict, Set, Sequence
1✔
8
import sys
1✔
9
import time
1✔
10
from functools import lru_cache
1✔
11

12
import electrum_ecc as ecc
1✔
13
from electrum_ecc import CURVE_ORDER, ecdsa_sig64_from_der_sig
1✔
14
from electrum_ecc.util import bip340_tagged_hash
1✔
15
import dataclasses
1✔
16
import attr
1✔
17

18
from .util import bfh, UserFacingException, list_enabled_bits, is_hex_str
1✔
19
from .util import ShortID as ShortChannelID, format_short_id as format_short_channel_id
1✔
20

21
from .crypto import sha256, pw_decode_with_version_and_mac
1✔
22
from .transaction import (
1✔
23
    Transaction, PartialTransaction, PartialTxInput, TxOutpoint, PartialTxOutput, opcodes, OPPushDataPubkey
24
)
25
from . import bitcoin, crypto, transaction, descriptor, segwit_addr
1✔
26
from .bitcoin import redeem_script_to_address, address_to_script, construct_witness, \
1✔
27
    construct_script, NLOCKTIME_BLOCKHEIGHT_MAX
28
from .i18n import _
1✔
29
from .bip32 import BIP32Node, BIP32_PRIME
1✔
30
from .transaction import BCDataStream, OPPushDataGeneric
1✔
31
from .logging import get_logger
1✔
32
from .fee_policy import FEERATE_PER_KW_MIN_RELAY_LIGHTNING
1✔
33
from .json_db import StoredObject, stored_in, stored_as
1✔
34

35

36
if TYPE_CHECKING:
37
    from .lnchannel import Channel, AbstractChannel
38
    from .lnrouter import LNPaymentRoute
39
    from .lnonion import OnionRoutingFailure
40
    from .simple_config import SimpleConfig
41

42

43
_logger = get_logger(__name__)
1✔
44

45

46
# defined in BOLT-03:
47
HTLC_TIMEOUT_WEIGHT = 663
1✔
48
HTLC_TIMEOUT_WEIGHT_ANCHORS = 666
1✔
49
HTLC_SUCCESS_WEIGHT = 703
1✔
50
HTLC_SUCCESS_WEIGHT_ANCHORS = 706
1✔
51
COMMITMENT_TX_WEIGHT = 724
1✔
52
COMMITMENT_TX_WEIGHT_ANCHORS = 1124
1✔
53
HTLC_OUTPUT_WEIGHT = 172
1✔
54
FIXED_ANCHOR_SAT = 330
1✔
55

56
LN_MAX_FUNDING_SAT_LEGACY = pow(2, 24) - 1
1✔
57
DUST_LIMIT_MAX = 1000
1✔
58

59
SCRIPT_TEMPLATE_FUNDING = [opcodes.OP_2, OPPushDataPubkey, OPPushDataPubkey, opcodes.OP_2, opcodes.OP_CHECKMULTISIG]
1✔
60

61

62
def channel_id_from_funding_tx(funding_txid: str, funding_index: int) -> Tuple[bytes, bytes]:
1✔
63
    funding_txid_bytes = bytes.fromhex(funding_txid)[::-1]
1✔
64
    i = int.from_bytes(funding_txid_bytes, 'big') ^ funding_index
1✔
65
    return i.to_bytes(32, 'big'), funding_txid_bytes
1✔
66

67

68
def hex_to_bytes(arg: Optional[Union[bytes, str]]) -> Optional[bytes]:
1✔
69
    return arg if isinstance(arg, bytes) else bytes.fromhex(arg) if arg is not None else None
1✔
70

71

72
def bytes_to_hex(arg: Optional[bytes]) -> Optional[str]:
1✔
73
    return repr(arg.hex()) if arg is not None else None
×
74

75

76
def json_to_keypair(arg: Union['OnlyPubkeyKeypair', dict]) -> Union['OnlyPubkeyKeypair', 'Keypair']:
1✔
77
    return arg if isinstance(arg, OnlyPubkeyKeypair) else Keypair(**arg) if len(arg) == 2 else OnlyPubkeyKeypair(**arg)
1✔
78

79

80
def serialize_htlc_key(scid: bytes, htlc_id: int) -> str:
1✔
81
    return scid.hex() + ':%d' % htlc_id
1✔
82

83

84
def deserialize_htlc_key(htlc_key: str) -> Tuple[bytes, int]:
1✔
85
    scid, htlc_id = htlc_key.split(':')
1✔
86
    return bytes.fromhex(scid), int(htlc_id)
1✔
87

88

89
@attr.s
1✔
90
class OnlyPubkeyKeypair(StoredObject):
1✔
91
    pubkey = attr.ib(type=bytes, converter=hex_to_bytes, repr=bytes_to_hex)
1✔
92

93

94
@attr.s
1✔
95
class Keypair(OnlyPubkeyKeypair):
1✔
96
    privkey = attr.ib(type=bytes, converter=hex_to_bytes, repr=bytes_to_hex)
1✔
97

98

99
@attr.s
1✔
100
class ChannelConfig(StoredObject):
1✔
101
    # shared channel config fields
102
    payment_basepoint = attr.ib(type=OnlyPubkeyKeypair, converter=json_to_keypair)
1✔
103
    multisig_key = attr.ib(type=OnlyPubkeyKeypair, converter=json_to_keypair)
1✔
104
    htlc_basepoint = attr.ib(type=OnlyPubkeyKeypair, converter=json_to_keypair)
1✔
105
    delayed_basepoint = attr.ib(type=OnlyPubkeyKeypair, converter=json_to_keypair)
1✔
106
    revocation_basepoint = attr.ib(type=OnlyPubkeyKeypair, converter=json_to_keypair)
1✔
107
    to_self_delay = attr.ib(type=int)  # applies to OTHER ctx
1✔
108
    dust_limit_sat = attr.ib(type=int)  # applies to SAME ctx
1✔
109
    max_htlc_value_in_flight_msat = attr.ib(type=int)  # max val of INCOMING htlcs
1✔
110
    max_accepted_htlcs = attr.ib(type=int)  # max num of INCOMING htlcs
1✔
111
    initial_msat = attr.ib(type=int)
1✔
112
    reserve_sat = attr.ib(type=int)  # applies to OTHER ctx
1✔
113
    htlc_minimum_msat = attr.ib(type=int)  # smallest value for INCOMING htlc
1✔
114
    upfront_shutdown_script = attr.ib(type=bytes, converter=hex_to_bytes, repr=bytes_to_hex)
1✔
115
    announcement_node_sig = attr.ib(type=bytes, converter=hex_to_bytes, repr=bytes_to_hex)
1✔
116
    announcement_bitcoin_sig = attr.ib(type=bytes, converter=hex_to_bytes, repr=bytes_to_hex)
1✔
117

118
    def validate_params(self, *, funding_sat: int, config: 'SimpleConfig', peer_features: 'LnFeatures') -> None:
1✔
119
        conf_name = type(self).__name__
×
120
        for key in (
×
121
                self.payment_basepoint,
122
                self.multisig_key,
123
                self.htlc_basepoint,
124
                self.delayed_basepoint,
125
                self.revocation_basepoint
126
        ):
127
            if not (len(key.pubkey) == 33 and ecc.ECPubkey.is_pubkey_bytes(key.pubkey)):
×
128
                raise Exception(f"{conf_name}. invalid pubkey in channel config")
×
129
        if funding_sat < MIN_FUNDING_SAT:
×
130
            raise Exception(f"funding_sat too low: {funding_sat} sat < {MIN_FUNDING_SAT}")
×
131
        if not peer_features.supports(LnFeatures.OPTION_SUPPORT_LARGE_CHANNEL_OPT):
×
132
            # MUST set funding_satoshis to less than 2^24 satoshi
133
            if funding_sat > LN_MAX_FUNDING_SAT_LEGACY:
×
134
                raise Exception(f"funding_sat too high: {funding_sat} sat > {LN_MAX_FUNDING_SAT_LEGACY} (legacy limit)")
×
135
        if funding_sat > config.LIGHTNING_MAX_FUNDING_SAT:
×
136
            raise Exception(f"funding_sat too high: {funding_sat} sat > {config.LIGHTNING_MAX_FUNDING_SAT} (config setting)")
×
137
        # MUST set push_msat to equal or less than 1000 * funding_satoshis
138
        if not (0 <= self.initial_msat <= 1000 * funding_sat):
×
139
            raise Exception(f"{conf_name}. insane initial_msat={self.initial_msat}. (funding_sat={funding_sat})")
×
140
        if self.reserve_sat < self.dust_limit_sat:
×
141
            raise Exception(f"{conf_name}. MUST set channel_reserve_satoshis greater than or equal to dust_limit_satoshis")
×
142
        if self.dust_limit_sat < bitcoin.DUST_LIMIT_UNKNOWN_SEGWIT:
×
143
            raise Exception(f"{conf_name}. dust limit too low: {self.dust_limit_sat} sat")
×
144
        if self.dust_limit_sat > DUST_LIMIT_MAX:
×
145
            raise Exception(f"{conf_name}. dust limit too high: {self.dust_limit_sat} sat")
×
146
        if self.reserve_sat > funding_sat // 100:
×
147
            raise Exception(f"{conf_name}. reserve too high: {self.reserve_sat}, funding_sat: {funding_sat}")
×
148
        if self.htlc_minimum_msat > 1_000:
×
149
            raise Exception(f"{conf_name}. htlc_minimum_msat too high: {self.htlc_minimum_msat} msat")
×
150
        HTLC_MINIMUM_MSAT_MIN = 0  # should be at least 1 really, but apparently some nodes are sending zero...
×
151
        if self.htlc_minimum_msat < HTLC_MINIMUM_MSAT_MIN:
×
152
            raise Exception(f"{conf_name}. htlc_minimum_msat too low: {self.htlc_minimum_msat} msat < {HTLC_MINIMUM_MSAT_MIN}")
×
153
        if self.max_accepted_htlcs < 5:
×
154
            raise Exception(f"{conf_name}. max_accepted_htlcs too low: {self.max_accepted_htlcs}")
×
155
        if self.max_accepted_htlcs > 483:
×
156
            raise Exception(f"{conf_name}. max_accepted_htlcs too high: {self.max_accepted_htlcs}")
×
157
        if self.to_self_delay > MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED:
×
158
            raise Exception(f"{conf_name}. to_self_delay too high: {self.to_self_delay} > {MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED}")
×
159
        if self.max_htlc_value_in_flight_msat < min(1000 * funding_sat, 90_000_000):
×
160
            raise Exception(f"{conf_name}. max_htlc_value_in_flight_msat is too small: {self.max_htlc_value_in_flight_msat}")
×
161

162
    @classmethod
1✔
163
    def cross_validate_params(
1✔
164
            cls,
165
            *,
166
            local_config: 'LocalConfig',
167
            remote_config: 'RemoteConfig',
168
            funding_sat: int,
169
            is_local_initiator: bool,  # whether we are the funder
170
            initial_feerate_per_kw: int,
171
            config: 'SimpleConfig',
172
            peer_features: 'LnFeatures',
173
            has_anchors: bool,
174
    ) -> None:
175
        # first we validate the configs separately
176
        local_config.validate_params(funding_sat=funding_sat, config=config, peer_features=peer_features)
×
177
        remote_config.validate_params(funding_sat=funding_sat, config=config, peer_features=peer_features)
×
178
        # now do tests that need access to both configs
179
        if is_local_initiator:
×
180
            funder, fundee = LOCAL, REMOTE
×
181
            funder_config, fundee_config = local_config, remote_config
×
182
        else:
183
            funder, fundee = REMOTE, LOCAL
×
184
            funder_config, fundee_config = remote_config, local_config
×
185
        # if channel_reserve_satoshis is less than dust_limit_satoshis within the open_channel message:
186
        #     MUST reject the channel.
187
        if remote_config.reserve_sat < local_config.dust_limit_sat:
×
188
            raise Exception("violated constraint: remote_config.reserve_sat < local_config.dust_limit_sat")
×
189
        # if channel_reserve_satoshis from the open_channel message is less than dust_limit_satoshis:
190
        #     MUST reject the channel.
191
        if local_config.reserve_sat < remote_config.dust_limit_sat:
×
192
            raise Exception("violated constraint: local_config.reserve_sat < remote_config.dust_limit_sat")
×
193
        # The receiving node MUST fail the channel if:
194
        #     the funder's amount for the initial commitment transaction is not
195
        #     sufficient for full fee payment.
196
        if funder_config.initial_msat < calc_fees_for_commitment_tx(
×
197
                num_htlcs=0,
198
                feerate=initial_feerate_per_kw,
199
                is_local_initiator=is_local_initiator,
200
                has_anchors=has_anchors,
201
        )[funder]:
202
            raise Exception(
×
203
                "the funder's amount for the initial commitment transaction "
204
                "is not sufficient for full fee payment")
205
        # The receiving node MUST fail the channel if:
206
        #     both to_local and to_remote amounts for the initial commitment transaction are
207
        #     less than or equal to channel_reserve_satoshis (see BOLT 3).
208
        if (max(local_config.initial_msat, remote_config.initial_msat)
×
209
                <= 1000 * max(local_config.reserve_sat, remote_config.reserve_sat)):
210
            raise Exception(
×
211
                "both to_local and to_remote amounts for the initial commitment "
212
                "transaction are less than or equal to channel_reserve_satoshis")
213
        if initial_feerate_per_kw < FEERATE_PER_KW_MIN_RELAY_LIGHTNING:
×
214
            raise Exception(f"feerate lower than min relay fee. {initial_feerate_per_kw} sat/kw.")
×
215

216

217
@stored_as('local_config')
1✔
218
@attr.s
1✔
219
class LocalConfig(ChannelConfig):
1✔
220
    channel_seed = attr.ib(type=bytes, converter=hex_to_bytes, repr=bytes_to_hex)  # type: Optional[bytes]
1✔
221
    funding_locked_received = attr.ib(type=bool)
1✔
222
    current_commitment_signature = attr.ib(type=bytes, converter=hex_to_bytes, repr=bytes_to_hex)
1✔
223
    current_htlc_signatures = attr.ib(type=bytes, converter=hex_to_bytes, repr=bytes_to_hex)
1✔
224
    per_commitment_secret_seed = attr.ib(type=bytes, converter=hex_to_bytes, repr=bytes_to_hex)
1✔
225

226
    @classmethod
1✔
227
    def from_seed(cls, **kwargs):
1✔
228
        channel_seed = kwargs['channel_seed']
×
229
        node = BIP32Node.from_rootseed(channel_seed, xtype='standard')
×
230

231
        def keypair_generator(family: 'LnKeyFamily') -> 'Keypair':
×
232
            return generate_keypair(node, family)
×
233

234
        kwargs['per_commitment_secret_seed'] = keypair_generator(LnKeyFamily.REVOCATION_ROOT).privkey
×
235
        if kwargs['multisig_key'] is None:
×
236
            kwargs['multisig_key'] = keypair_generator(LnKeyFamily.MULTISIG)
×
237
        kwargs['htlc_basepoint'] = keypair_generator(LnKeyFamily.HTLC_BASE)
×
238
        kwargs['delayed_basepoint'] = keypair_generator(LnKeyFamily.DELAY_BASE)
×
239
        kwargs['revocation_basepoint'] = keypair_generator(LnKeyFamily.REVOCATION_BASE)
×
240
        static_remotekey = kwargs.pop('static_remotekey')
×
241
        static_payment_key = kwargs.pop('static_payment_key')
×
242
        if static_payment_key:
×
243
            # We derive the payment_basepoint from a static secret (derived from
244
            # the wallet seed) and a public nonce that is revealed
245
            # when the funding transaction is spent. This way we can restore the
246
            # payment_basepoint, needed for sweeping in the event of a force close.
247
            kwargs['payment_basepoint'] = derive_payment_basepoint(
×
248
                static_payment_secret=static_payment_key.privkey,
249
                funding_pubkey=kwargs['multisig_key'].pubkey
250
            )
251
        elif static_remotekey:  # we automatically sweep to a wallet address
×
252
            kwargs['payment_basepoint'] = OnlyPubkeyKeypair(static_remotekey)
×
253
        else:
254
            # we expect all our channels to use option_static_remotekey, so ending up here likely indicates an issue...
255
            kwargs['payment_basepoint'] = keypair_generator(LnKeyFamily.PAYMENT_BASE)
×
256

257
        return LocalConfig(**kwargs)
×
258

259
    def validate_params(self, *, funding_sat: int, config: 'SimpleConfig', peer_features: 'LnFeatures') -> None:
1✔
260
        conf_name = type(self).__name__
×
261
        # run base checks regardless whether LOCAL/REMOTE config
262
        super().validate_params(funding_sat=funding_sat, config=config, peer_features=peer_features)
×
263
        # run some stricter checks on LOCAL config (make sure we ourselves do the sane thing,
264
        # even if we are lenient with REMOTE for compatibility reasons)
265
        HTLC_MINIMUM_MSAT_MIN = 1
×
266
        if self.htlc_minimum_msat < HTLC_MINIMUM_MSAT_MIN:
×
267
            raise Exception(f"{conf_name}. htlc_minimum_msat too low: {self.htlc_minimum_msat} msat < {HTLC_MINIMUM_MSAT_MIN}")
×
268

269

270
@stored_as('remote_config')
1✔
271
@attr.s
1✔
272
class RemoteConfig(ChannelConfig):
1✔
273
    next_per_commitment_point = attr.ib(type=bytes, converter=hex_to_bytes, repr=bytes_to_hex)
1✔
274
    current_per_commitment_point = attr.ib(default=None, type=bytes, converter=hex_to_bytes, repr=bytes_to_hex)
1✔
275

276

277
@stored_in('fee_updates')
1✔
278
@attr.s
1✔
279
class FeeUpdate(StoredObject):
1✔
280
    rate = attr.ib(type=int)  # in sat/kw
1✔
281
    ctn_local = attr.ib(default=None, type=int)
1✔
282
    ctn_remote = attr.ib(default=None, type=int)
1✔
283

284

285
@stored_as('constraints')
1✔
286
@attr.s
1✔
287
class ChannelConstraints(StoredObject):
1✔
288
    flags = attr.ib(type=int, converter=int)
1✔
289
    capacity = attr.ib(type=int)  # in sat
1✔
290
    is_initiator = attr.ib(type=bool)  # note: sometimes also called "funder"
1✔
291
    funding_txn_minimum_depth = attr.ib(type=int)
1✔
292

293

294
CHANNEL_BACKUP_VERSION_LATEST = 2
1✔
295
KNOWN_CHANNEL_BACKUP_VERSIONS = (0, 1, 2, )
1✔
296
assert CHANNEL_BACKUP_VERSION_LATEST in KNOWN_CHANNEL_BACKUP_VERSIONS
1✔
297

298

299
@attr.s
1✔
300
class ChannelBackupStorage(StoredObject):
1✔
301
    funding_txid = attr.ib(type=str)
1✔
302
    funding_index = attr.ib(type=int, converter=int)
1✔
303
    funding_address = attr.ib(type=str)
1✔
304
    is_initiator = attr.ib(type=bool)
1✔
305

306
    def funding_outpoint(self):
1✔
307
        return Outpoint(self.funding_txid, self.funding_index)
×
308

309
    def channel_id(self):
1✔
310
        chan_id, _ = channel_id_from_funding_tx(self.funding_txid, self.funding_index)
×
311
        return chan_id
×
312

313

314
@stored_in('onchain_channel_backups')
1✔
315
@attr.s
1✔
316
class OnchainChannelBackupStorage(ChannelBackupStorage):
1✔
317
    node_id_prefix = attr.ib(type=bytes, converter=hex_to_bytes)  # remote node pubkey
1✔
318

319

320
@stored_in('imported_channel_backups')
1✔
321
@attr.s
1✔
322
class ImportedChannelBackupStorage(ChannelBackupStorage):
1✔
323
    node_id = attr.ib(type=bytes, converter=hex_to_bytes)  # remote node pubkey
1✔
324
    privkey = attr.ib(type=bytes, converter=hex_to_bytes)  # local node privkey
1✔
325
    host = attr.ib(type=str)
1✔
326
    port = attr.ib(type=int, converter=int)
1✔
327
    channel_seed = attr.ib(type=bytes, converter=hex_to_bytes)
1✔
328
    local_delay = attr.ib(type=int, converter=int)
1✔
329
    remote_delay = attr.ib(type=int, converter=int)
1✔
330
    remote_payment_pubkey = attr.ib(type=bytes, converter=hex_to_bytes)
1✔
331
    remote_revocation_pubkey = attr.ib(type=bytes, converter=hex_to_bytes)
1✔
332
    local_payment_pubkey = attr.ib(type=bytes, converter=hex_to_bytes)  # type: Optional[bytes]
1✔
333
    multisig_funding_privkey = attr.ib(type=bytes, converter=hex_to_bytes)  # type: Optional[bytes]
1✔
334

335
    def to_bytes(self) -> bytes:
1✔
336
        vds = BCDataStream()
×
337
        vds.write_uint16(CHANNEL_BACKUP_VERSION_LATEST)
×
338
        vds.write_boolean(self.is_initiator)
×
339
        vds.write_bytes(self.privkey, 32)
×
340
        vds.write_bytes(self.channel_seed, 32)
×
341
        vds.write_bytes(self.node_id, 33)
×
342
        vds.write_bytes(bfh(self.funding_txid), 32)
×
343
        vds.write_uint16(self.funding_index)
×
344
        vds.write_string(self.funding_address)
×
345
        vds.write_bytes(self.remote_payment_pubkey, 33)
×
346
        vds.write_bytes(self.remote_revocation_pubkey, 33)
×
347
        vds.write_uint16(self.local_delay)
×
348
        vds.write_uint16(self.remote_delay)
×
349
        vds.write_string(self.host)
×
350
        vds.write_uint16(self.port)
×
351
        vds.write_bytes(self.local_payment_pubkey, 33)
×
352
        vds.write_bytes(self.multisig_funding_privkey, 32)
×
353
        return bytes(vds.input)
×
354

355
    @staticmethod
1✔
356
    def from_bytes(s: bytes) -> 'ImportedChannelBackupStorage':
1✔
357
        vds = BCDataStream()
1✔
358
        vds.write(s)
1✔
359
        version = vds.read_uint16()
1✔
360
        if version not in KNOWN_CHANNEL_BACKUP_VERSIONS:
1✔
361
            raise Exception(f"unknown version for channel backup: {version}")
×
362
        is_initiator = vds.read_boolean()
1✔
363
        privkey = vds.read_bytes(32)
1✔
364
        channel_seed = vds.read_bytes(32)
1✔
365
        node_id = vds.read_bytes(33)
1✔
366
        funding_txid = vds.read_bytes(32).hex()
1✔
367
        funding_index = vds.read_uint16()
1✔
368
        funding_address = vds.read_string()
1✔
369
        remote_payment_pubkey = vds.read_bytes(33)
1✔
370
        remote_revocation_pubkey = vds.read_bytes(33)
1✔
371
        local_delay = vds.read_uint16()
1✔
372
        remote_delay = vds.read_uint16()
1✔
373
        host = vds.read_string()
1✔
374
        port = vds.read_uint16()
1✔
375
        if version >= 1:
1✔
376
            local_payment_pubkey = vds.read_bytes(33)
1✔
377
        else:
378
            local_payment_pubkey = None
1✔
379
        if version >= 2:
1✔
380
            multisig_funding_privkey = vds.read_bytes(32)
×
381
        else:
382
            multisig_funding_privkey = None
1✔
383
        return ImportedChannelBackupStorage(
1✔
384
            is_initiator=is_initiator,
385
            privkey=privkey,
386
            channel_seed=channel_seed,
387
            node_id=node_id,
388
            funding_txid=funding_txid,
389
            funding_index=funding_index,
390
            funding_address=funding_address,
391
            remote_payment_pubkey=remote_payment_pubkey,
392
            remote_revocation_pubkey=remote_revocation_pubkey,
393
            local_delay=local_delay,
394
            remote_delay=remote_delay,
395
            host=host,
396
            port=port,
397
            local_payment_pubkey=local_payment_pubkey,
398
            multisig_funding_privkey=multisig_funding_privkey,
399
        )
400

401
    @staticmethod
1✔
402
    def from_encrypted_str(data: str, *, password: str) -> 'ImportedChannelBackupStorage':
1✔
403
        if not data.startswith('channel_backup:'):
1✔
404
            raise ValueError("missing or invalid magic bytes")
×
405
        encrypted = data[15:]
1✔
406
        decrypted = pw_decode_with_version_and_mac(encrypted, password)
1✔
407
        return ImportedChannelBackupStorage.from_bytes(decrypted)
1✔
408

409

410
class ScriptHtlc(NamedTuple):
1✔
411
    redeem_script: bytes
1✔
412
    htlc: 'UpdateAddHtlc'
1✔
413

414

415
# FIXME duplicate of TxOutpoint in transaction.py??
416
@stored_as('funding_outpoint')
1✔
417
@attr.s
1✔
418
class Outpoint(StoredObject):
1✔
419
    txid = attr.ib(type=str)
1✔
420
    output_index = attr.ib(type=int)
1✔
421

422
    def to_str(self):
1✔
423
        return "{}:{}".format(self.txid, self.output_index)
1✔
424

425

426
class HtlcLog(NamedTuple):
1✔
427
    success: bool
1✔
428
    amount_msat: int  # amount for receiver (e.g. from invoice)
1✔
429
    route: Optional['LNPaymentRoute'] = None
1✔
430
    preimage: Optional[bytes] = None
1✔
431
    error_bytes: Optional[bytes] = None
1✔
432
    failure_msg: Optional['OnionRoutingFailure'] = None
1✔
433
    sender_idx: Optional[int] = None
1✔
434
    trampoline_fee_level: Optional[int] = None
1✔
435

436
    def formatted_tuple(self):
1✔
437
        route = self.route
×
438
        route_str = '%d' % len(route)
×
439
        short_channel_id = None
×
440
        if not self.success:
×
441
            sender_idx = self.sender_idx
×
442
            failure_msg = self.failure_msg
×
443
            if sender_idx is not None:
×
444
                try:
×
445
                    short_channel_id = route[sender_idx + 1].short_channel_id
×
446
                except IndexError:
×
447
                    # payment destination reported error
448
                    short_channel_id = _("Destination node")
×
449
            message = failure_msg.code_name()
×
450
        else:
451
            short_channel_id = route[-1].short_channel_id
×
452
            message = _('Success')
×
453
        chan_str = str(short_channel_id) if short_channel_id else _("Unknown")
×
454
        return route_str, chan_str, message
×
455

456

457
class LightningError(Exception): pass
1✔
458
class UnableToDeriveSecret(LightningError): pass
1✔
459
class RemoteMisbehaving(LightningError): pass
1✔
460
class NotFoundChanAnnouncementForUpdate(Exception): pass
1✔
461

462

463
class InvalidGossipMsg(Exception):
1✔
464
    """e.g. signature check failed"""
465

466

467
class PaymentFailure(UserFacingException): pass
1✔
468
class PaymentSuccess(Exception): pass
1✔
469

470

471
class NoPathFound(PaymentFailure):
1✔
472
    def __str__(self):
1✔
473
        return _('No path found')
1✔
474

475

476
class FeeBudgetExceeded(PaymentFailure):
1✔
477
    def __str__(self):
1✔
478
        return _('Fee budget exceeded')
×
479

480

481
class LNProtocolError(Exception):
1✔
482
    """Raised in peer methods to trigger an error message."""
483

484

485
class LNProtocolWarning(Exception):
1✔
486
    """Raised in peer methods to trigger a warning message."""
487

488

489
# TODO make some of these values configurable?
490
REDEEM_AFTER_DOUBLE_SPENT_DELAY = 30
1✔
491

492
CHANNEL_OPENING_TIMEOUT = 24*60*60
1✔
493

494
# Small capacity channels are problematic for many reasons. As the onchain fees start to become
495
# significant compared to the capacity, things start to break down. e.g. the counterparty
496
# force-closing the channel costs much of the funds in the channel.
497
# Closing a channel uses ~200 vbytes onchain, feerates could spike to 100 sat/vbyte or even higher;
498
# that in itself is already 20_000 sats. This mining fee is reserved and cannot be used for payments.
499
# The value below is chosen arbitrarily to be one order of magnitude higher than that.
500
MIN_FUNDING_SAT = 200_000
1✔
501

502

503
##### CLTV-expiry-delta-related values
504
# see https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#cltv_expiry_delta-selection
505

506
# the minimum cltv_expiry accepted for newly received HTLCs
507
# note: when changing, consider Blockchain.is_tip_stale()
508
MIN_FINAL_CLTV_DELTA_ACCEPTED = 144
1✔
509

510
# buffer added to min_final_cltv_delta of created bolt11 invoices to make verifying the cltv delta
511
# of incoming payment htlcs reliable even if some blocks have been mined during forwarding
512
MIN_FINAL_CLTV_DELTA_BUFFER_INVOICE = 3
1✔
513

514
# the deadline for offered HTLCs:
515
# the deadline after which the channel has to be failed and timed out on-chain
516
NBLOCK_DEADLINE_DELTA_AFTER_EXPIRY_FOR_OFFERED_HTLCS = 1
1✔
517

518
# the deadline for received HTLCs this node has fulfilled:
519
# the deadline after which the channel has to be failed and the HTLC fulfilled on-chain before its cltv_expiry
520
NBLOCK_DEADLINE_DELTA_BEFORE_EXPIRY_FOR_RECEIVED_HTLCS = 72
1✔
521

522
NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE = 28 * 144
1✔
523

524
MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED = 2016
1✔
525

526
# timeout after which we consider a zeroconf channel without funding tx to be failed
527
ZEROCONF_TIMEOUT = 60 * 10
1✔
528

529

530
class RevocationStore:
1✔
531
    # closely based on code in lightningnetwork/lnd
532

533
    START_INDEX = 2 ** 48 - 1
1✔
534

535
    def __init__(self, storage):
1✔
536
        if len(storage) == 0:
1✔
537
            storage['index'] = self.START_INDEX
1✔
538
            storage['buckets'] = {}
1✔
539
        self.storage = storage
1✔
540
        self.buckets = storage['buckets']
1✔
541

542
    def add_next_entry(self, hsh):
1✔
543
        index = self.storage['index']
1✔
544
        new_element = ShachainElement(index=index, secret=hsh)
1✔
545
        bucket = count_trailing_zeros(index)
1✔
546
        for i in range(0, bucket):
1✔
547
            this_bucket = self.buckets[i]
1✔
548
            e = shachain_derive(new_element, this_bucket.index)
1✔
549
            if e != this_bucket:
1✔
550
                raise Exception("hash is not derivable: {} {} {}".format(e.secret.hex(), this_bucket.secret.hex(), this_bucket.index))
1✔
551
        self.buckets[bucket] = new_element
1✔
552
        self.storage['index'] = index - 1
1✔
553

554
    def retrieve_secret(self, index: int) -> bytes:
1✔
555
        assert index <= self.START_INDEX, index
1✔
556
        for i in range(0, 49):
1✔
557
            bucket = self.buckets.get(i)
1✔
558
            if bucket is None:
1✔
559
                raise UnableToDeriveSecret()
×
560
            try:
1✔
561
                element = shachain_derive(bucket, index)
1✔
562
            except UnableToDeriveSecret:
1✔
563
                continue
1✔
564
            return element.secret
1✔
565
        raise UnableToDeriveSecret()
×
566

567

568
def count_trailing_zeros(index):
1✔
569
    """ BOLT-03 (where_to_put_secret) """
570
    try:
1✔
571
        return list(reversed(bin(index)[2:])).index("1")
1✔
572
    except ValueError:
×
573
        return 48
×
574

575

576
def shachain_derive(element, to_index):
1✔
577
    def get_prefix(index, pos):
1✔
578
        mask = (1 << 64) - 1 - ((1 << pos) - 1)
1✔
579
        return index & mask
1✔
580
    from_index = element.index
1✔
581
    zeros = count_trailing_zeros(from_index)
1✔
582
    if from_index != get_prefix(to_index, zeros):
1✔
583
        raise UnableToDeriveSecret("prefixes are different; index not derivable")
1✔
584
    return ShachainElement(
1✔
585
        get_per_commitment_secret_from_seed(element.secret, to_index, zeros),
586
        to_index)
587

588

589
class ShachainElement(NamedTuple):
1✔
590
    secret: bytes
1✔
591
    index: int
1✔
592

593
    def __str__(self):
1✔
594
        return "ShachainElement(" + self.secret.hex() + "," + str(self.index) + ")"
×
595

596
    @stored_in('buckets', tuple)
1✔
597
    def read(*x):
1✔
598
        return ShachainElement(bfh(x[0]), int(x[1]))
1✔
599

600

601
def get_per_commitment_secret_from_seed(seed: bytes, i: int, bits: int = 48) -> bytes:
1✔
602
    """Generate per commitment secret."""
603
    per_commitment_secret = bytearray(seed)
1✔
604
    for bitindex in range(bits - 1, -1, -1):
1✔
605
        mask = 1 << bitindex
1✔
606
        if i & mask:
1✔
607
            per_commitment_secret[bitindex // 8] ^= 1 << (bitindex % 8)
1✔
608
            per_commitment_secret = bytearray(sha256(per_commitment_secret))
1✔
609
    bajts = bytes(per_commitment_secret)
1✔
610
    return bajts
1✔
611

612

613
def secret_to_pubkey(secret: int) -> bytes:
1✔
614
    assert type(secret) is int
1✔
615
    return ecc.ECPrivkey.from_secret_scalar(secret).get_public_key_bytes(compressed=True)
1✔
616

617

618
def derive_pubkey(basepoint: bytes, per_commitment_point: bytes) -> bytes:
1✔
619
    p = ecc.ECPubkey(basepoint) + ecc.GENERATOR * ecc.string_to_number(sha256(per_commitment_point + basepoint))
1✔
620
    return p.get_public_key_bytes()
1✔
621

622

623
def derive_privkey(secret: int, per_commitment_point: bytes) -> int:
1✔
624
    assert type(secret) is int
1✔
625
    basepoint_bytes = secret_to_pubkey(secret)
1✔
626
    basepoint = secret + ecc.string_to_number(sha256(per_commitment_point + basepoint_bytes))
1✔
627
    basepoint %= CURVE_ORDER
1✔
628
    return basepoint
1✔
629

630

631
def derive_blinded_pubkey(basepoint: bytes, per_commitment_point: bytes) -> bytes:
1✔
632
    k1 = ecc.ECPubkey(basepoint) * ecc.string_to_number(sha256(basepoint + per_commitment_point))
1✔
633
    k2 = ecc.ECPubkey(per_commitment_point) * ecc.string_to_number(sha256(per_commitment_point + basepoint))
1✔
634
    return (k1 + k2).get_public_key_bytes()
1✔
635

636

637
def derive_blinded_privkey(basepoint_secret: bytes, per_commitment_secret: bytes) -> bytes:
1✔
638
    basepoint = ecc.ECPrivkey(basepoint_secret).get_public_key_bytes(compressed=True)
×
639
    per_commitment_point = ecc.ECPrivkey(per_commitment_secret).get_public_key_bytes(compressed=True)
×
640
    k1 = ecc.string_to_number(basepoint_secret) * ecc.string_to_number(sha256(basepoint + per_commitment_point))
×
641
    k2 = ecc.string_to_number(per_commitment_secret) * ecc.string_to_number(sha256(per_commitment_point + basepoint))
×
642
    sum = (k1 + k2) % ecc.CURVE_ORDER
×
643
    return int.to_bytes(sum, length=32, byteorder='big', signed=False)
×
644

645

646
def derive_payment_basepoint(static_payment_secret: bytes, funding_pubkey: bytes) -> Keypair:
1✔
647
    assert isinstance(static_payment_secret, bytes)
×
648
    assert isinstance(funding_pubkey, bytes)
×
649
    payment_basepoint = ecc.ECPrivkey(sha256(static_payment_secret + funding_pubkey))
×
650
    return Keypair(
×
651
        pubkey=payment_basepoint.get_public_key_bytes(),
652
        privkey=payment_basepoint.get_secret_bytes()
653
    )
654

655

656
def derive_multisig_funding_key_if_we_opened(
1✔
657
    *,
658
    funding_root_secret: bytes,
659
    remote_node_id_or_prefix: bytes,
660
    nlocktime: int,
661
) -> Keypair:
662
    from .lnworker import NODE_ID_PREFIX_LEN
×
663
    assert isinstance(funding_root_secret, bytes)
×
664
    assert len(funding_root_secret) == 32
×
665
    assert isinstance(remote_node_id_or_prefix, bytes)
×
666
    assert len(remote_node_id_or_prefix) in (NODE_ID_PREFIX_LEN, 33)
×
667
    assert isinstance(nlocktime, int)
×
668
    nlocktime_bytes = int.to_bytes(nlocktime, length=4, byteorder="little", signed=False)
×
669
    node_id_prefix = remote_node_id_or_prefix[0:NODE_ID_PREFIX_LEN]
×
670
    funding_key = ecc.ECPrivkey(bip340_tagged_hash(
×
671
        tag=b"electrum/ln_multisig_funding_key/we_opened",
672
        msg=funding_root_secret + node_id_prefix + nlocktime_bytes,
673
    ))
674
    return Keypair(
×
675
        pubkey=funding_key.get_public_key_bytes(),
676
        privkey=funding_key.get_secret_bytes(),
677
    )
678

679

680
def derive_multisig_funding_key_if_they_opened(
1✔
681
    *,
682
    funding_root_secret: bytes,
683
    remote_node_id_or_prefix: bytes,
684
    remote_funding_pubkey: bytes,
685
) -> Keypair:
686
    from .lnworker import NODE_ID_PREFIX_LEN
×
687
    assert isinstance(funding_root_secret, bytes)
×
688
    assert len(funding_root_secret) == 32
×
689
    assert isinstance(remote_node_id_or_prefix, bytes)
×
690
    assert len(remote_node_id_or_prefix) in (NODE_ID_PREFIX_LEN, 33)
×
691
    assert isinstance(remote_funding_pubkey, bytes)
×
692
    assert len(remote_funding_pubkey) == 33
×
693
    node_id_prefix = remote_node_id_or_prefix[0:NODE_ID_PREFIX_LEN]
×
694
    funding_key = ecc.ECPrivkey(bip340_tagged_hash(
×
695
        tag=b"electrum/ln_multisig_funding_key/they_opened",
696
        msg=funding_root_secret + node_id_prefix + remote_funding_pubkey,
697
    ))
698
    return Keypair(
×
699
        pubkey=funding_key.get_public_key_bytes(),
700
        privkey=funding_key.get_secret_bytes(),
701
    )
702

703

704
def make_htlc_tx_output(
1✔
705
    amount_msat,
706
    local_feerate,
707
    revocationpubkey,
708
    local_delayedpubkey,
709
    success,
710
    to_self_delay,
711
    has_anchors: bool
712
) -> Tuple[bytes, PartialTxOutput]:
713
    assert type(amount_msat) is int
1✔
714
    assert type(local_feerate) is int
1✔
715
    script = make_commitment_output_to_local_witness_script(
1✔
716
        revocation_pubkey=revocationpubkey,
717
        to_self_delay=to_self_delay,
718
        delayed_pubkey=local_delayedpubkey,
719
    )
720

721
    p2wsh = bitcoin.redeem_script_to_address('p2wsh', script)
1✔
722
    weight = effective_htlc_tx_weight(success=success, has_anchors=has_anchors)
1✔
723
    fee = local_feerate * weight
1✔
724
    fee = fee // 1000 * 1000
1✔
725
    final_amount_sat = (amount_msat - fee) // 1000
1✔
726
    assert final_amount_sat > 0, final_amount_sat
1✔
727
    output = PartialTxOutput.from_address_and_value(p2wsh, final_amount_sat)
1✔
728
    return script, output
1✔
729

730

731
def make_htlc_tx_witness(
1✔
732
        remotehtlcsig: bytes,
733
        localhtlcsig: bytes,
734
        payment_preimage: bytes,
735
        witness_script: bytes
736
) -> bytes:
737
    assert type(remotehtlcsig) is bytes
1✔
738
    assert type(localhtlcsig) is bytes
1✔
739
    assert type(payment_preimage) is bytes
1✔
740
    assert type(witness_script) is bytes
1✔
741
    return construct_witness([0, remotehtlcsig, localhtlcsig, payment_preimage, witness_script])
1✔
742

743

744
def make_htlc_tx_inputs(
1✔
745
        htlc_output_txid: str,
746
        htlc_output_index: int,
747
        amount_msat: int,
748
        witness_script: bytes
749
) -> List[PartialTxInput]:
750
    assert type(htlc_output_txid) is str
1✔
751
    assert type(htlc_output_index) is int
1✔
752
    assert type(amount_msat) is int
1✔
753
    assert type(witness_script) is bytes
1✔
754
    txin = PartialTxInput(prevout=TxOutpoint(txid=bfh(htlc_output_txid), out_idx=htlc_output_index),
1✔
755
                          nsequence=0)
756
    txin.witness_script = witness_script
1✔
757
    txin.script_sig = b''
1✔
758
    txin._trusted_value_sats = amount_msat // 1000
1✔
759
    c_inputs = [txin]
1✔
760
    return c_inputs
1✔
761

762

763
def make_htlc_tx(*, cltv_abs: int, inputs: List[PartialTxInput], output: PartialTxOutput) -> PartialTransaction:
1✔
764
    assert type(cltv_abs) is int
1✔
765
    c_outputs = [output]
1✔
766
    tx = PartialTransaction.from_io(inputs, c_outputs, locktime=cltv_abs, version=2)
1✔
767
    return tx
1✔
768

769

770
def make_offered_htlc(
1✔
771
    *,
772
    revocation_pubkey: bytes,
773
    remote_htlcpubkey: bytes,
774
    local_htlcpubkey: bytes,
775
    payment_hash: bytes,
776
    has_anchors: bool,
777
) -> bytes:
778
    assert type(revocation_pubkey) is bytes
1✔
779
    assert type(remote_htlcpubkey) is bytes
1✔
780
    assert type(local_htlcpubkey) is bytes
1✔
781
    assert type(payment_hash) is bytes
1✔
782
    script_template = witness_template_offered_htlc(anchors=has_anchors)
1✔
783
    script = construct_script(
1✔
784
        script_template,
785
        values={
786
            2: bitcoin.hash_160(revocation_pubkey),
787
            7: remote_htlcpubkey,
788
            10: 32,
789
            16: local_htlcpubkey,
790
            21: crypto.ripemd(payment_hash),
791
        },
792
    )
793
    return script
1✔
794

795

796
def make_received_htlc(
1✔
797
    *,
798
    revocation_pubkey: bytes,
799
    remote_htlcpubkey: bytes,
800
    local_htlcpubkey: bytes,
801
    payment_hash: bytes,
802
    cltv_abs: int,
803
    has_anchors: bool,
804
) -> bytes:
805
    for i in [revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash]:
1✔
806
        assert type(i) is bytes
1✔
807
    assert type(cltv_abs) is int
1✔
808
    script_template = witness_template_received_htlc(anchors=has_anchors)
1✔
809
    script = construct_script(
1✔
810
        script_template,
811
        values={
812
            2: bitcoin.hash_160(revocation_pubkey),
813
            7: remote_htlcpubkey,
814
            10: 32,
815
            14: crypto.ripemd(payment_hash),
816
            18: local_htlcpubkey,
817
            23: cltv_abs,
818
        },
819
    )
820
    return script
1✔
821

822

823
def witness_template_offered_htlc(anchors: bool):
1✔
824
    return [
1✔
825
        opcodes.OP_DUP,
826
        opcodes.OP_HASH160,
827
        OPPushDataGeneric(None),
828
        opcodes.OP_EQUAL,
829
        opcodes.OP_IF,
830
        opcodes.OP_CHECKSIG,
831
        opcodes.OP_ELSE,
832
        OPPushDataGeneric(None),
833
        opcodes.OP_SWAP,
834
        opcodes.OP_SIZE,
835
        OPPushDataGeneric(lambda x: x==1),
836
        opcodes.OP_EQUAL,
837
        opcodes.OP_NOTIF,
838
        opcodes.OP_DROP,
839
        opcodes.OP_2,
840
        opcodes.OP_SWAP,
841
        OPPushDataGeneric(None),
842
        opcodes.OP_2,
843
        opcodes.OP_CHECKMULTISIG,
844
        opcodes.OP_ELSE,
845
        opcodes.OP_HASH160,
846
        OPPushDataGeneric(None),
847
        opcodes.OP_EQUALVERIFY,
848
        opcodes.OP_CHECKSIG,
849
        opcodes.OP_ENDIF,
850
    ] + ([
851
        opcodes.OP_1,
852
        opcodes.OP_CHECKSEQUENCEVERIFY,
853
        opcodes.OP_DROP,
854
    ] if anchors else [
855
    ]) + [
856
        opcodes.OP_ENDIF,
857
    ]
858

859

860
WITNESS_TEMPLATE_OFFERED_HTLC = witness_template_offered_htlc(anchors=False)
1✔
861
WITNESS_TEMPLATE_OFFERED_HTLC_ANCHORS = witness_template_offered_htlc(anchors=True)
1✔
862

863

864
def witness_template_received_htlc(anchors: bool):
1✔
865
    return [
1✔
866
        opcodes.OP_DUP,
867
        opcodes.OP_HASH160,
868
        OPPushDataGeneric(None),
869
        opcodes.OP_EQUAL,
870
        opcodes.OP_IF,
871
        opcodes.OP_CHECKSIG,
872
        opcodes.OP_ELSE,
873
        OPPushDataGeneric(None),
874
        opcodes.OP_SWAP,
875
        opcodes.OP_SIZE,
876
        OPPushDataGeneric(lambda x: x==1),
877
        opcodes.OP_EQUAL,
878
        opcodes.OP_IF,
879
        opcodes.OP_HASH160,
880
        OPPushDataGeneric(None),
881
        opcodes.OP_EQUALVERIFY,
882
        opcodes.OP_2,
883
        opcodes.OP_SWAP,
884
        OPPushDataGeneric(None),
885
        opcodes.OP_2,
886
        opcodes.OP_CHECKMULTISIG,
887
        opcodes.OP_ELSE,
888
        opcodes.OP_DROP,
889
        OPPushDataGeneric(None),
890
        opcodes.OP_CHECKLOCKTIMEVERIFY,
891
        opcodes.OP_DROP,
892
        opcodes.OP_CHECKSIG,
893
        opcodes.OP_ENDIF,
894
    ] + ([
895
        opcodes.OP_1,
896
        opcodes.OP_CHECKSEQUENCEVERIFY,
897
        opcodes.OP_DROP,
898
    ] if anchors else [
899
    ]) + [
900
        opcodes.OP_ENDIF,
901
    ]
902

903

904
WITNESS_TEMPLATE_RECEIVED_HTLC = witness_template_received_htlc(anchors=False)
1✔
905
WITNESS_TEMPLATE_RECEIVED_HTLC_ANCHORS = witness_template_received_htlc(anchors=True)
1✔
906

907

908
def make_htlc_output_witness_script(
1✔
909
    *,
910
    is_received_htlc: bool,
911
    remote_revocation_pubkey: bytes,
912
    remote_htlc_pubkey: bytes,
913
    local_htlc_pubkey: bytes,
914
    payment_hash: bytes,
915
    cltv_abs: Optional[int],
916
    has_anchors: bool,
917
) -> bytes:
918
    if is_received_htlc:
1✔
919
        return make_received_htlc(
1✔
920
            revocation_pubkey=remote_revocation_pubkey,
921
            remote_htlcpubkey=remote_htlc_pubkey,
922
            local_htlcpubkey=local_htlc_pubkey,
923
            payment_hash=payment_hash,
924
            cltv_abs=cltv_abs,
925
            has_anchors=has_anchors,
926
        )
927
    else:
928
        return make_offered_htlc(
1✔
929
            revocation_pubkey=remote_revocation_pubkey,
930
            remote_htlcpubkey=remote_htlc_pubkey,
931
            local_htlcpubkey=local_htlc_pubkey,
932
            payment_hash=payment_hash,
933
            has_anchors=has_anchors,
934
        )
935

936

937
def get_ordered_channel_configs(
1✔
938
        chan: 'AbstractChannel',
939
        for_us: bool
940
) -> Tuple[Union[LocalConfig, RemoteConfig], Union[LocalConfig, RemoteConfig]]:
941
    conf =       chan.config[LOCAL] if     for_us else chan.config[REMOTE]
1✔
942
    other_conf = chan.config[LOCAL] if not for_us else chan.config[REMOTE]
1✔
943
    return conf, other_conf
1✔
944

945

946
def possible_output_idxs_of_htlc_in_ctx(
1✔
947
        *,
948
        chan: 'Channel',
949
        pcp: bytes,
950
        subject: 'HTLCOwner',
951
        htlc_direction: 'Direction',
952
        ctx: Transaction,
953
        htlc: 'UpdateAddHtlc'
954
) -> Set[int]:
955
    amount_msat, cltv_abs, payment_hash = htlc.amount_msat, htlc.cltv_abs, htlc.payment_hash
1✔
956
    for_us = subject == LOCAL
1✔
957
    conf, other_conf = get_ordered_channel_configs(chan=chan, for_us=for_us)
1✔
958

959
    other_revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp)
1✔
960
    other_htlc_pubkey = derive_pubkey(other_conf.htlc_basepoint.pubkey, pcp)
1✔
961
    htlc_pubkey = derive_pubkey(conf.htlc_basepoint.pubkey, pcp)
1✔
962
    witness_script = make_htlc_output_witness_script(
1✔
963
        is_received_htlc=htlc_direction == RECEIVED,
964
        remote_revocation_pubkey=other_revocation_pubkey,
965
        remote_htlc_pubkey=other_htlc_pubkey,
966
        local_htlc_pubkey=htlc_pubkey,
967
        payment_hash=payment_hash,
968
        cltv_abs=cltv_abs,
969
        has_anchors=chan.has_anchors(),
970
    )
971
    htlc_address = redeem_script_to_address('p2wsh', witness_script)
1✔
972
    candidates = ctx.get_output_idxs_from_address(htlc_address)
1✔
973
    return {output_idx for output_idx in candidates
1✔
974
            if ctx.outputs()[output_idx].value == htlc.amount_msat // 1000}
975

976

977
def map_htlcs_to_ctx_output_idxs(
1✔
978
        *,
979
        chan: 'Channel',
980
        ctx: Transaction, pcp: bytes,
981
        subject: 'HTLCOwner',
982
        ctn: int
983
) -> Dict[Tuple['Direction', 'UpdateAddHtlc'], Tuple[int, int]]:
984
    """Returns a dict from (htlc_dir, htlc) to (ctx_output_idx, htlc_relative_idx)"""
985
    htlc_to_ctx_output_idx_map = {}  # type: Dict[Tuple[Direction, UpdateAddHtlc], int]
1✔
986
    unclaimed_ctx_output_idxs = set(range(len(ctx.outputs())))
1✔
987
    offered_htlcs = chan.included_htlcs(subject, SENT, ctn=ctn)
1✔
988
    offered_htlcs.sort(key=lambda htlc: htlc.cltv_abs)
1✔
989
    received_htlcs = chan.included_htlcs(subject, RECEIVED, ctn=ctn)
1✔
990
    received_htlcs.sort(key=lambda htlc: htlc.cltv_abs)
1✔
991
    for direction, htlcs in zip([SENT, RECEIVED], [offered_htlcs, received_htlcs]):
1✔
992
        for htlc in htlcs:
1✔
993
            cands = sorted(possible_output_idxs_of_htlc_in_ctx(
1✔
994
                chan=chan, pcp=pcp, subject=subject, htlc_direction=direction, ctx=ctx, htlc=htlc
995
            ))
996
            for ctx_output_idx in cands:
1✔
997
                if ctx_output_idx in unclaimed_ctx_output_idxs:
1✔
998
                    unclaimed_ctx_output_idxs.discard(ctx_output_idx)
1✔
999
                    htlc_to_ctx_output_idx_map[(direction, htlc)] = ctx_output_idx
1✔
1000
                    break
1✔
1001
    # calc htlc_relative_idx
1002
    inverse_map = {ctx_output_idx: (direction, htlc)
1✔
1003
                   for ((direction, htlc), ctx_output_idx) in htlc_to_ctx_output_idx_map.items()}
1004

1005
    return {inverse_map[ctx_output_idx]: (ctx_output_idx, htlc_relative_idx)
1✔
1006
            for htlc_relative_idx, ctx_output_idx in enumerate(sorted(inverse_map))}
1007

1008

1009
def make_htlc_tx_with_open_channel(
1✔
1010
        *, chan: 'Channel',
1011
        pcp: bytes,
1012
        subject: 'HTLCOwner',
1013
        ctn: int,
1014
        htlc_direction: 'Direction',
1015
        commit: Transaction,
1016
        ctx_output_idx: int,
1017
        htlc: 'UpdateAddHtlc',
1018
        name: str = None
1019
) -> Tuple[bytes, PartialTransaction]:
1020
    amount_msat, cltv_abs, payment_hash = htlc.amount_msat, htlc.cltv_abs, htlc.payment_hash
1✔
1021
    for_us = subject == LOCAL
1✔
1022
    conf, other_conf = get_ordered_channel_configs(chan=chan, for_us=for_us)
1✔
1023

1024
    delayedpubkey = derive_pubkey(conf.delayed_basepoint.pubkey, pcp)
1✔
1025
    other_revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp)
1✔
1026
    other_htlc_pubkey = derive_pubkey(other_conf.htlc_basepoint.pubkey, pcp)
1✔
1027
    htlc_pubkey = derive_pubkey(conf.htlc_basepoint.pubkey, pcp)
1✔
1028
    # HTLC-success for the HTLC spending from a received HTLC output
1029
    # if we do not receive, and the commitment tx is not for us, they receive, so it is also an HTLC-success
1030
    is_htlc_success = htlc_direction == RECEIVED
1✔
1031
    witness_script_of_htlc_tx_output, htlc_tx_output = make_htlc_tx_output(
1✔
1032
        amount_msat=amount_msat,
1033
        local_feerate=chan.get_feerate(subject, ctn=ctn),
1034
        revocationpubkey=other_revocation_pubkey,
1035
        local_delayedpubkey=delayedpubkey,
1036
        success=is_htlc_success,
1037
        to_self_delay=other_conf.to_self_delay,
1038
        has_anchors=chan.has_anchors(),
1039
    )
1040
    witness_script_in = make_htlc_output_witness_script(
1✔
1041
        is_received_htlc=is_htlc_success,
1042
        remote_revocation_pubkey=other_revocation_pubkey,
1043
        remote_htlc_pubkey=other_htlc_pubkey,
1044
        local_htlc_pubkey=htlc_pubkey,
1045
        payment_hash=payment_hash,
1046
        cltv_abs=cltv_abs,
1047
        has_anchors=chan.has_anchors(),
1048
    )
1049
    htlc_tx_inputs = make_htlc_tx_inputs(
1✔
1050
        commit.txid(), ctx_output_idx,
1051
        amount_msat=amount_msat,
1052
        witness_script=witness_script_in)
1053
    if chan.has_anchors():
1✔
1054
        htlc_tx_inputs[0].nsequence = 1
1✔
1055
    if is_htlc_success:
1✔
1056
        cltv_abs = 0
1✔
1057
    htlc_tx = make_htlc_tx(cltv_abs=cltv_abs, inputs=htlc_tx_inputs, output=htlc_tx_output)
1✔
1058
    return witness_script_of_htlc_tx_output, htlc_tx
1✔
1059

1060

1061
def make_funding_input(
1✔
1062
    local_funding_pubkey: bytes,
1063
    remote_funding_pubkey: bytes,
1064
    funding_pos: int,
1065
    funding_txid: str,
1066
    funding_sat: int
1067
) -> PartialTxInput:
1068

1069
    pubkeys = sorted([local_funding_pubkey.hex(), remote_funding_pubkey.hex()])
1✔
1070
    # commitment tx input
1071
    prevout = TxOutpoint(txid=bfh(funding_txid), out_idx=funding_pos)
1✔
1072
    c_input = PartialTxInput(prevout=prevout)
1✔
1073

1074
    ppubkeys = [descriptor.PubkeyProvider.parse(pk) for pk in pubkeys]
1✔
1075
    multi = descriptor.MultisigDescriptor(pubkeys=ppubkeys, thresh=2, is_sorted=True)
1✔
1076
    c_input.script_descriptor = descriptor.WSHDescriptor(subdescriptor=multi)
1✔
1077
    c_input._trusted_value_sats = funding_sat
1✔
1078
    return c_input
1✔
1079

1080

1081
class HTLCOwner(IntEnum):
1✔
1082
    LOCAL = 1
1✔
1083
    REMOTE = -LOCAL
1✔
1084

1085
    def inverted(self) -> 'HTLCOwner':
1✔
1086
        return -self
1✔
1087

1088
    def __neg__(self) -> 'HTLCOwner':
1✔
1089
        return HTLCOwner(super().__neg__())
1✔
1090

1091

1092
class Direction(IntEnum):
1✔
1093
    SENT = -1     # in the context of HTLCs: "offered" HTLCs
1✔
1094
    RECEIVED = 1  # in the context of HTLCs: "received" HTLCs
1✔
1095

1096

1097
SENT = Direction.SENT
1✔
1098
RECEIVED = Direction.RECEIVED
1✔
1099

1100
LOCAL = HTLCOwner.LOCAL
1✔
1101
REMOTE = HTLCOwner.REMOTE
1✔
1102

1103

1104
def make_commitment_outputs(
1✔
1105
    *,
1106
    fees_per_participant: Mapping[HTLCOwner, int],
1107
    local_amount_msat: int,
1108
    remote_amount_msat: int,
1109
    local_script: bytes,
1110
    remote_script: bytes,
1111
    htlcs: List[ScriptHtlc],
1112
    dust_limit_sat: int,
1113
    has_anchors: bool,
1114
    local_anchor_script: Optional[str],
1115
    remote_anchor_script: Optional[str]
1116
) -> Tuple[List[PartialTxOutput], List[PartialTxOutput]]:
1117

1118
    # determine HTLC outputs and trim below dust to know if anchors need to be included
1119
    htlc_outputs = []
1✔
1120
    for script, htlc in htlcs:
1✔
1121
        addr = bitcoin.redeem_script_to_address('p2wsh', script)
1✔
1122
        if htlc.amount_msat // 1000 > dust_limit_sat:
1✔
1123
            htlc_outputs.append(
1✔
1124
                PartialTxOutput(
1125
                    scriptpubkey=address_to_script(addr),
1126
                    value=htlc.amount_msat // 1000
1127
                ))
1128

1129
    # BOLT-03: "Base commitment transaction fees are extracted from the funder's amount;
1130
    #           if that amount is insufficient, the entire amount of the funder's output is used."
1131
    non_htlc_outputs = []
1✔
1132
    to_local_amt_msat = local_amount_msat - fees_per_participant[LOCAL]
1✔
1133
    to_remote_amt_msat = remote_amount_msat - fees_per_participant[REMOTE]
1✔
1134

1135
    anchor_outputs = []
1✔
1136
    # if no anchor scripts are set, we ignore anchor outputs, useful when this
1137
    # function is used to determine outputs for a collaborative close
1138
    if has_anchors and local_anchor_script and remote_anchor_script:
1✔
1139
        local_pays_anchors = bool(fees_per_participant[LOCAL])
1✔
1140
        # we always allocate for two anchor outputs even if they are not added
1141
        if local_pays_anchors:
1✔
1142
            to_local_amt_msat -= 2 * FIXED_ANCHOR_SAT * 1000
1✔
1143
        else:
1144
            to_remote_amt_msat -= 2 * FIXED_ANCHOR_SAT * 1000
1✔
1145

1146
        # include anchors for outputs that materialize, include both if there are HTLCs present
1147
        if to_local_amt_msat // 1000 >= dust_limit_sat or htlc_outputs:
1✔
1148
            anchor_outputs.append(PartialTxOutput(scriptpubkey=local_anchor_script, value=FIXED_ANCHOR_SAT))
1✔
1149
        if to_remote_amt_msat // 1000 >= dust_limit_sat or htlc_outputs:
1✔
1150
            anchor_outputs.append(PartialTxOutput(scriptpubkey=remote_anchor_script, value=FIXED_ANCHOR_SAT))
1✔
1151

1152
    # if funder cannot afford feerate, their output might go negative, so take max(0, x) here
1153
    to_local_amt_msat = max(0, to_local_amt_msat)
1✔
1154
    to_remote_amt_msat = max(0, to_remote_amt_msat)
1✔
1155
    non_htlc_outputs.append(PartialTxOutput(scriptpubkey=local_script, value=to_local_amt_msat // 1000))
1✔
1156
    non_htlc_outputs.append(PartialTxOutput(scriptpubkey=remote_script, value=to_remote_amt_msat // 1000))
1✔
1157

1158
    c_outputs_filtered = list(filter(lambda x: x.value >= dust_limit_sat, non_htlc_outputs + htlc_outputs))
1✔
1159
    c_outputs = c_outputs_filtered + anchor_outputs
1✔
1160
    return htlc_outputs, c_outputs
1✔
1161

1162

1163
def effective_htlc_tx_weight(success: bool, has_anchors: bool):
1✔
1164
    # for anchors-zero-fee-htlc we set an effective weight of zero
1165
    # we only trim htlcs below dust, as in the anchors commitment format,
1166
    # the fees for the hltc transaction don't need to be subtracted from
1167
    # the htlc output, but fees are taken from extra attached inputs
1168
    if has_anchors:
1✔
1169
        return 0 * HTLC_SUCCESS_WEIGHT_ANCHORS if success else 0 * HTLC_TIMEOUT_WEIGHT_ANCHORS
1✔
1170
    else:
1171
        return HTLC_SUCCESS_WEIGHT if success else HTLC_TIMEOUT_WEIGHT
1✔
1172

1173

1174
def offered_htlc_trim_threshold_sat(*, dust_limit_sat: int, feerate: int, has_anchors: bool) -> int:
1✔
1175
    # offered htlcs strictly below this amount will be trimmed (from ctx).
1176
    # feerate is in sat/kw
1177
    # returns value in sat
1178
    weight = effective_htlc_tx_weight(success=False, has_anchors=has_anchors)
1✔
1179
    return dust_limit_sat + weight * feerate // 1000
1✔
1180

1181

1182
def received_htlc_trim_threshold_sat(*, dust_limit_sat: int, feerate: int, has_anchors: bool) -> int:
1✔
1183
    # received htlcs strictly below this amount will be trimmed (from ctx).
1184
    # feerate is in sat/kw
1185
    # returns value in sat
1186
    weight = effective_htlc_tx_weight(success=True, has_anchors=has_anchors)
1✔
1187
    return dust_limit_sat + weight * feerate // 1000
1✔
1188

1189

1190
def fee_for_htlc_output(*, feerate: int) -> int:
1✔
1191
    # feerate is in sat/kw
1192
    # returns fee in msat
1193
    return feerate * HTLC_OUTPUT_WEIGHT
1✔
1194

1195

1196
def calc_fees_for_commitment_tx(
1✔
1197
        *, num_htlcs: int,
1198
        feerate: int,
1199
        is_local_initiator: bool,
1200
        round_to_sat: bool = True,
1201
        has_anchors: bool
1202
) -> Dict['HTLCOwner', int]:
1203
    # feerate is in sat/kw
1204
    # returns fees in msats
1205
    # note: BOLT-02 specifies that msat fees need to be rounded down to sat.
1206
    #       However, the rounding needs to happen for the total fees, so if the return value
1207
    #       is to be used as part of additional fee calculation then rounding should be done after that.
1208
    if has_anchors:
1✔
1209
        commitment_tx_weight = COMMITMENT_TX_WEIGHT_ANCHORS
1✔
1210
    else:
1211
        commitment_tx_weight = COMMITMENT_TX_WEIGHT
1✔
1212
    overall_weight = commitment_tx_weight + num_htlcs * HTLC_OUTPUT_WEIGHT
1✔
1213
    fee = feerate * overall_weight
1✔
1214
    if round_to_sat:
1✔
1215
        fee = fee // 1000 * 1000
1✔
1216
    return {
1✔
1217
        LOCAL: fee if is_local_initiator else 0,
1218
        REMOTE: fee if not is_local_initiator else 0,
1219
    }
1220

1221

1222
def make_commitment(
1✔
1223
        *,
1224
        ctn: int,
1225
        local_funding_pubkey: bytes,
1226
        remote_funding_pubkey: bytes,
1227
        remote_payment_pubkey: bytes,
1228
        funder_payment_basepoint: bytes,
1229
        fundee_payment_basepoint: bytes,
1230
        revocation_pubkey: bytes,
1231
        delayed_pubkey: bytes,
1232
        to_self_delay: int,
1233
        funding_txid: str,
1234
        funding_pos: int,
1235
        funding_sat: int,
1236
        local_amount: int,
1237
        remote_amount: int,
1238
        dust_limit_sat: int,
1239
        fees_per_participant: Mapping[HTLCOwner, int],
1240
        htlcs: List[ScriptHtlc],
1241
        has_anchors: bool
1242
) -> PartialTransaction:
1243
    c_input = make_funding_input(local_funding_pubkey, remote_funding_pubkey,
1✔
1244
                                 funding_pos, funding_txid, funding_sat)
1245
    obs = get_obscured_ctn(ctn, funder_payment_basepoint, fundee_payment_basepoint)
1✔
1246
    locktime = (0x20 << 24) + (obs & 0xffffff)
1✔
1247
    sequence = (0x80 << 24) + (obs >> 24)
1✔
1248
    c_input.nsequence = sequence
1✔
1249

1250
    c_inputs = [c_input]
1✔
1251

1252
    # commitment tx outputs
1253
    local_address = make_commitment_output_to_local_address(revocation_pubkey, to_self_delay, delayed_pubkey)
1✔
1254
    remote_address = make_commitment_output_to_remote_address(remote_payment_pubkey, has_anchors)
1✔
1255
    local_anchor_address = None
1✔
1256
    remote_anchor_address = None
1✔
1257
    if has_anchors:
1✔
1258
        local_anchor_address = make_commitment_output_to_anchor_address(local_funding_pubkey)
1✔
1259
        remote_anchor_address = make_commitment_output_to_anchor_address(remote_funding_pubkey)
1✔
1260
    # note: it is assumed that the given 'htlcs' are all non-dust (dust htlcs already trimmed)
1261

1262
    # BOLT-03: "Transaction Input and Output Ordering
1263
    #           Lexicographic ordering: see BIP69. In the case of identical HTLC outputs,
1264
    #           the outputs are ordered in increasing cltv_expiry order."
1265
    # so we sort by cltv_expiry now; and the later BIP69-sort is assumed to be *stable*
1266
    htlcs = list(htlcs)
1✔
1267
    htlcs.sort(key=lambda x: x.htlc.cltv_abs)
1✔
1268

1269
    htlc_outputs, c_outputs_filtered = make_commitment_outputs(
1✔
1270
        fees_per_participant=fees_per_participant,
1271
        local_amount_msat=local_amount,
1272
        remote_amount_msat=remote_amount,
1273
        local_script=address_to_script(local_address),
1274
        remote_script=address_to_script(remote_address),
1275
        htlcs=htlcs,
1276
        dust_limit_sat=dust_limit_sat,
1277
        has_anchors=has_anchors,
1278
        local_anchor_script=address_to_script(local_anchor_address) if local_anchor_address else None,
1279
        remote_anchor_script=address_to_script(remote_anchor_address) if remote_anchor_address else None
1280
    )
1281

1282
    assert sum(x.value for x in c_outputs_filtered) <= funding_sat, (c_outputs_filtered, funding_sat)
1✔
1283

1284
    # create commitment tx
1285
    tx = PartialTransaction.from_io(c_inputs, c_outputs_filtered, locktime=locktime, version=2)
1✔
1286
    return tx
1✔
1287

1288

1289
def make_commitment_output_to_local_witness_script(
1✔
1290
        revocation_pubkey: bytes,
1291
        to_self_delay: int,
1292
        delayed_pubkey: bytes,
1293
) -> bytes:
1294
    assert type(revocation_pubkey) is bytes
1✔
1295
    assert type(to_self_delay) is int
1✔
1296
    assert type(delayed_pubkey) is bytes
1✔
1297
    script = construct_script([
1✔
1298
        opcodes.OP_IF,
1299
        revocation_pubkey,
1300
        opcodes.OP_ELSE,
1301
        to_self_delay,
1302
        opcodes.OP_CHECKSEQUENCEVERIFY,
1303
        opcodes.OP_DROP,
1304
        delayed_pubkey,
1305
        opcodes.OP_ENDIF,
1306
        opcodes.OP_CHECKSIG,
1307
    ])
1308
    return script
1✔
1309

1310

1311
def make_commitment_output_to_local_address(
1✔
1312
        revocation_pubkey: bytes, to_self_delay: int, delayed_pubkey: bytes) -> str:
1313
    local_script = make_commitment_output_to_local_witness_script(revocation_pubkey, to_self_delay, delayed_pubkey)
1✔
1314
    return bitcoin.redeem_script_to_address('p2wsh', local_script)
1✔
1315

1316

1317
def make_commitment_output_to_remote_witness_script(remote_payment_pubkey: bytes) -> bytes:
1✔
1318
    assert isinstance(remote_payment_pubkey, bytes)
1✔
1319
    script = construct_script([
1✔
1320
        remote_payment_pubkey,
1321
        opcodes.OP_CHECKSIGVERIFY,
1322
        opcodes.OP_1,
1323
        opcodes.OP_CHECKSEQUENCEVERIFY,
1324
    ])
1325
    return script
1✔
1326

1327

1328
def make_commitment_output_to_remote_address(remote_payment_pubkey: bytes, has_anchors: bool) -> str:
1✔
1329
    if has_anchors:
1✔
1330
        remote_script = make_commitment_output_to_remote_witness_script(remote_payment_pubkey)
1✔
1331
        return bitcoin.redeem_script_to_address('p2wsh', remote_script)
1✔
1332
    else:
1333
        return bitcoin.pubkey_to_address('p2wpkh', remote_payment_pubkey.hex())
1✔
1334

1335

1336
def make_commitment_output_to_anchor_witness_script(funding_pubkey: bytes) -> bytes:
1✔
1337
    assert isinstance(funding_pubkey, bytes)
1✔
1338
    script = construct_script([
1✔
1339
        funding_pubkey,
1340
        opcodes.OP_CHECKSIG,
1341
        opcodes.OP_IFDUP,
1342
        opcodes.OP_NOTIF,
1343
        opcodes.OP_16,
1344
        opcodes.OP_CHECKSEQUENCEVERIFY,
1345
        opcodes.OP_ENDIF,
1346
    ])
1347
    return script
1✔
1348

1349

1350
def make_commitment_output_to_anchor_address(funding_pubkey: bytes) -> str:
1✔
1351
    script = make_commitment_output_to_anchor_witness_script(funding_pubkey)
1✔
1352
    return bitcoin.redeem_script_to_address('p2wsh', script)
1✔
1353

1354

1355
def sign_and_get_sig_string(tx: PartialTransaction, local_config, remote_config):
1✔
1356
    tx.sign({local_config.multisig_key.pubkey: local_config.multisig_key.privkey})
1✔
1357
    sig = tx.inputs()[0].sigs_ecdsa[local_config.multisig_key.pubkey]
1✔
1358
    sig_64 = ecdsa_sig64_from_der_sig(sig[:-1])
1✔
1359
    return sig_64
1✔
1360

1361

1362
def funding_output_script(local_config: 'LocalConfig', remote_config: 'RemoteConfig') -> bytes:
1✔
1363
    return funding_output_script_from_keys(local_config.multisig_key.pubkey, remote_config.multisig_key.pubkey)
1✔
1364

1365

1366
def funding_output_script_from_keys(pubkey1: bytes, pubkey2: bytes) -> bytes:
1✔
1367
    pubkeys = sorted([pubkey1.hex(), pubkey2.hex()])
1✔
1368
    return transaction.multisig_script(pubkeys, 2)
1✔
1369

1370

1371
def get_obscured_ctn(ctn: int, funder: bytes, fundee: bytes) -> int:
1✔
1372
    mask = int.from_bytes(sha256(funder + fundee)[-6:], 'big')
1✔
1373
    return ctn ^ mask
1✔
1374

1375

1376
def extract_ctn_from_tx(tx: Transaction, txin_index: int, funder_payment_basepoint: bytes,
1✔
1377
                        fundee_payment_basepoint: bytes) -> int:
1378
    tx.deserialize()
1✔
1379
    locktime = tx.locktime
1✔
1380
    sequence = tx.inputs()[txin_index].nsequence
1✔
1381
    obs = ((sequence & 0xffffff) << 24) + (locktime & 0xffffff)
1✔
1382
    return get_obscured_ctn(obs, funder_payment_basepoint, fundee_payment_basepoint)
1✔
1383

1384

1385
def extract_ctn_from_tx_and_chan(tx: Transaction, chan: 'AbstractChannel') -> int:
1✔
1386
    funder_conf = chan.config[LOCAL] if     chan.is_initiator() else chan.config[REMOTE]
1✔
1387
    fundee_conf = chan.config[LOCAL] if not chan.is_initiator() else chan.config[REMOTE]
1✔
1388
    return extract_ctn_from_tx(tx, txin_index=0,
1✔
1389
                               funder_payment_basepoint=funder_conf.payment_basepoint.pubkey,
1390
                               fundee_payment_basepoint=fundee_conf.payment_basepoint.pubkey)
1391

1392

1393
def ctx_has_anchors(tx: Transaction):
1✔
1394
    output_values = [output.value for output in tx.outputs()]
×
1395
    if FIXED_ANCHOR_SAT in output_values:
×
1396
        return True
×
1397
    else:
1398
        return False
×
1399

1400

1401
class LnFeatureContexts(enum.Flag):
1✔
1402
    INIT = enum.auto()
1✔
1403
    NODE_ANN = enum.auto()
1✔
1404
    CHAN_ANN_AS_IS = enum.auto()
1✔
1405
    CHAN_ANN_ALWAYS_ODD = enum.auto()
1✔
1406
    CHAN_ANN_ALWAYS_EVEN = enum.auto()
1✔
1407
    INVOICE = enum.auto()
1✔
1408

1409

1410
LNFC = LnFeatureContexts
1✔
1411

1412
_ln_feature_direct_dependencies = defaultdict(set)  # type: Dict[LnFeatures, Set[LnFeatures]]
1✔
1413
_ln_feature_contexts = {}  # type: Dict[LnFeatures, LnFeatureContexts]
1✔
1414

1415

1416
class LnFeatures(IntFlag):
1✔
1417
    OPTION_DATA_LOSS_PROTECT_REQ = 1 << 0
1✔
1418
    OPTION_DATA_LOSS_PROTECT_OPT = 1 << 1
1✔
1419
    _ln_feature_contexts[OPTION_DATA_LOSS_PROTECT_OPT] = (LNFC.INIT | LnFeatureContexts.NODE_ANN)
1✔
1420
    _ln_feature_contexts[OPTION_DATA_LOSS_PROTECT_REQ] = (LNFC.INIT | LnFeatureContexts.NODE_ANN)
1✔
1421

1422
    INITIAL_ROUTING_SYNC = 1 << 3
1✔
1423
    _ln_feature_contexts[INITIAL_ROUTING_SYNC] = LNFC.INIT
1✔
1424

1425
    OPTION_UPFRONT_SHUTDOWN_SCRIPT_REQ = 1 << 4
1✔
1426
    OPTION_UPFRONT_SHUTDOWN_SCRIPT_OPT = 1 << 5
1✔
1427
    _ln_feature_contexts[OPTION_UPFRONT_SHUTDOWN_SCRIPT_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1428
    _ln_feature_contexts[OPTION_UPFRONT_SHUTDOWN_SCRIPT_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1429

1430
    GOSSIP_QUERIES_REQ = 1 << 6
1✔
1431
    GOSSIP_QUERIES_OPT = 1 << 7
1✔
1432
    _ln_feature_contexts[GOSSIP_QUERIES_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1433
    _ln_feature_contexts[GOSSIP_QUERIES_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1434

1435
    VAR_ONION_REQ = 1 << 8
1✔
1436
    VAR_ONION_OPT = 1 << 9
1✔
1437
    _ln_feature_contexts[VAR_ONION_OPT] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
1✔
1438
    _ln_feature_contexts[VAR_ONION_REQ] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
1✔
1439

1440
    GOSSIP_QUERIES_EX_REQ = 1 << 10
1✔
1441
    GOSSIP_QUERIES_EX_OPT = 1 << 11
1✔
1442
    _ln_feature_direct_dependencies[GOSSIP_QUERIES_EX_OPT] = {GOSSIP_QUERIES_OPT}
1✔
1443
    _ln_feature_contexts[GOSSIP_QUERIES_EX_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1444
    _ln_feature_contexts[GOSSIP_QUERIES_EX_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1445

1446
    OPTION_STATIC_REMOTEKEY_REQ = 1 << 12
1✔
1447
    OPTION_STATIC_REMOTEKEY_OPT = 1 << 13
1✔
1448
    _ln_feature_contexts[OPTION_STATIC_REMOTEKEY_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1449
    _ln_feature_contexts[OPTION_STATIC_REMOTEKEY_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1450

1451
    PAYMENT_SECRET_REQ = 1 << 14
1✔
1452
    PAYMENT_SECRET_OPT = 1 << 15
1✔
1453
    _ln_feature_direct_dependencies[PAYMENT_SECRET_OPT] = {VAR_ONION_OPT}
1✔
1454
    _ln_feature_contexts[PAYMENT_SECRET_OPT] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
1✔
1455
    _ln_feature_contexts[PAYMENT_SECRET_REQ] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
1✔
1456

1457
    BASIC_MPP_REQ = 1 << 16
1✔
1458
    BASIC_MPP_OPT = 1 << 17
1✔
1459
    _ln_feature_direct_dependencies[BASIC_MPP_OPT] = {PAYMENT_SECRET_OPT}
1✔
1460
    _ln_feature_contexts[BASIC_MPP_OPT] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
1✔
1461
    _ln_feature_contexts[BASIC_MPP_REQ] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
1✔
1462

1463
    OPTION_SUPPORT_LARGE_CHANNEL_REQ = 1 << 18
1✔
1464
    OPTION_SUPPORT_LARGE_CHANNEL_OPT = 1 << 19
1✔
1465
    _ln_feature_contexts[OPTION_SUPPORT_LARGE_CHANNEL_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1466
    _ln_feature_contexts[OPTION_SUPPORT_LARGE_CHANNEL_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1467

1468
    OPTION_ANCHOR_OUTPUTS_REQ = 1 << 20
1✔
1469
    OPTION_ANCHOR_OUTPUTS_OPT = 1 << 21
1✔
1470
    _ln_feature_direct_dependencies[OPTION_ANCHOR_OUTPUTS_OPT] = {OPTION_STATIC_REMOTEKEY_OPT}
1✔
1471
    _ln_feature_contexts[OPTION_ANCHOR_OUTPUTS_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1472
    _ln_feature_contexts[OPTION_ANCHOR_OUTPUTS_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1473

1474
    OPTION_ANCHORS_ZERO_FEE_HTLC_REQ = 1 << 22
1✔
1475
    OPTION_ANCHORS_ZERO_FEE_HTLC_OPT = 1 << 23
1✔
1476
    _ln_feature_direct_dependencies[OPTION_ANCHORS_ZERO_FEE_HTLC_OPT] = {OPTION_STATIC_REMOTEKEY_OPT}
1✔
1477
    _ln_feature_contexts[OPTION_ANCHORS_ZERO_FEE_HTLC_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1478
    _ln_feature_contexts[OPTION_ANCHORS_ZERO_FEE_HTLC_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1479

1480
    # Temporary number.
1481
    OPTION_TRAMPOLINE_ROUTING_REQ_ECLAIR = 1 << 148
1✔
1482
    OPTION_TRAMPOLINE_ROUTING_OPT_ECLAIR = 1 << 149
1✔
1483

1484
    _ln_feature_contexts[OPTION_TRAMPOLINE_ROUTING_REQ_ECLAIR] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
1✔
1485
    _ln_feature_contexts[OPTION_TRAMPOLINE_ROUTING_OPT_ECLAIR] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
1✔
1486

1487
    # We use a different bit because Phoenix cannot do end-to-end multi-trampoline routes
1488
    OPTION_TRAMPOLINE_ROUTING_REQ_ELECTRUM = 1 << 150
1✔
1489
    OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM = 1 << 151
1✔
1490

1491
    _ln_feature_contexts[OPTION_TRAMPOLINE_ROUTING_REQ_ELECTRUM] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
1✔
1492
    _ln_feature_contexts[OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
1✔
1493

1494
    OPTION_SHUTDOWN_ANYSEGWIT_REQ = 1 << 26
1✔
1495
    OPTION_SHUTDOWN_ANYSEGWIT_OPT = 1 << 27
1✔
1496

1497
    _ln_feature_contexts[OPTION_SHUTDOWN_ANYSEGWIT_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1498
    _ln_feature_contexts[OPTION_SHUTDOWN_ANYSEGWIT_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1499

1500
    OPTION_ONION_MESSAGE_REQ = 1 << 38
1✔
1501
    OPTION_ONION_MESSAGE_OPT = 1 << 39
1✔
1502

1503
    _ln_feature_contexts[OPTION_ONION_MESSAGE_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1504
    _ln_feature_contexts[OPTION_ONION_MESSAGE_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1505

1506
    OPTION_CHANNEL_TYPE_REQ = 1 << 44
1✔
1507
    OPTION_CHANNEL_TYPE_OPT = 1 << 45
1✔
1508

1509
    _ln_feature_contexts[OPTION_CHANNEL_TYPE_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1510
    _ln_feature_contexts[OPTION_CHANNEL_TYPE_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1511

1512
    OPTION_SCID_ALIAS_REQ = 1 << 46
1✔
1513
    OPTION_SCID_ALIAS_OPT = 1 << 47
1✔
1514

1515
    _ln_feature_contexts[OPTION_SCID_ALIAS_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1516
    _ln_feature_contexts[OPTION_SCID_ALIAS_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1517

1518
    OPTION_ZEROCONF_REQ = 1 << 50
1✔
1519
    OPTION_ZEROCONF_OPT = 1 << 51
1✔
1520

1521
    _ln_feature_direct_dependencies[OPTION_ZEROCONF_OPT] = {OPTION_SCID_ALIAS_OPT}
1✔
1522
    _ln_feature_contexts[OPTION_ZEROCONF_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1523
    _ln_feature_contexts[OPTION_ZEROCONF_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
1✔
1524

1525
    def validate_transitive_dependencies(self) -> bool:
1✔
1526
        # for all even bit set, set corresponding odd bit:
1527
        features = self  # copy
1✔
1528
        flags = list_enabled_bits(features)
1✔
1529
        for flag in flags:
1✔
1530
            if flag % 2 == 0:
1✔
1531
                features |= 1 << get_ln_flag_pair_of_bit(flag)
1✔
1532
        # Check dependencies. We only check that the direct dependencies of each flag set
1533
        # are satisfied: this implies that transitive dependencies are also satisfied.
1534
        flags = list_enabled_bits(features)
1✔
1535
        for flag in flags:
1✔
1536
            for dependency in _ln_feature_direct_dependencies[1 << flag]:
1✔
1537
                if not (dependency & features):
1✔
1538
                    return False
1✔
1539
        return True
1✔
1540

1541
    def for_init_message(self) -> 'LnFeatures':
1✔
1542
        features = LnFeatures(0)
1✔
1543
        for flag in list_enabled_ln_feature_bits(self):
1✔
1544
            if LnFeatureContexts.INIT & _ln_feature_contexts[1 << flag]:
1✔
1545
                features |= (1 << flag)
1✔
1546
        return features
1✔
1547

1548
    def for_node_announcement(self) -> 'LnFeatures':
1✔
1549
        features = LnFeatures(0)
×
1550
        for flag in list_enabled_ln_feature_bits(self):
×
1551
            if LnFeatureContexts.NODE_ANN & _ln_feature_contexts[1 << flag]:
×
1552
                features |= (1 << flag)
×
1553
        return features
×
1554

1555
    def for_invoice(self) -> 'LnFeatures':
1✔
1556
        features = LnFeatures(0)
1✔
1557
        for flag in list_enabled_ln_feature_bits(self):
1✔
1558
            if LnFeatureContexts.INVOICE & _ln_feature_contexts[1 << flag]:
1✔
1559
                features |= (1 << flag)
1✔
1560
        return features
1✔
1561

1562
    def for_channel_announcement(self) -> 'LnFeatures':
1✔
1563
        features = LnFeatures(0)
×
1564
        for flag in list_enabled_ln_feature_bits(self):
×
1565
            ctxs = _ln_feature_contexts[1 << flag]
×
1566
            if LnFeatureContexts.CHAN_ANN_AS_IS & ctxs:
×
1567
                features |= (1 << flag)
×
1568
            elif LnFeatureContexts.CHAN_ANN_ALWAYS_EVEN & ctxs:
×
1569
                if flag % 2 == 0:
×
1570
                    features |= (1 << flag)
×
1571
            elif LnFeatureContexts.CHAN_ANN_ALWAYS_ODD & ctxs:
×
1572
                if flag % 2 == 0:
×
1573
                    flag = get_ln_flag_pair_of_bit(flag)
×
1574
                features |= (1 << flag)
×
1575
        return features
×
1576

1577
    def min_len(self) -> int:
1✔
1578
        b = int.bit_length(self)
1✔
1579
        return b // 8 + int(bool(b % 8))
1✔
1580

1581
    def supports(self, feature: 'LnFeatures') -> bool:
1✔
1582
        """Returns whether given feature is enabled.
1583

1584
        Helper function that tries to hide the complexity of even/odd bits.
1585
        For example, instead of:
1586
          bool(myfeatures & LnFeatures.VAR_ONION_OPT or myfeatures & LnFeatures.VAR_ONION_REQ)
1587
        you can do:
1588
          myfeatures.supports(LnFeatures.VAR_ONION_OPT)
1589
        """
1590
        if (1 << (feature.bit_length() - 1)) != feature:
1✔
1591
            raise ValueError(f"'feature' cannot be a combination of features: {feature}")
1✔
1592
        if feature.bit_length() % 2 == 0:  # feature is OPT
1✔
1593
            feature_other = feature >> 1
1✔
1594
        else:  # feature is REQ
1595
            feature_other = feature << 1
1✔
1596
        return (self & feature != 0) or (self & feature_other != 0)
1✔
1597

1598
    def get_names(self) -> Sequence[str]:
1✔
1599
        r = []
1✔
1600
        for flag in list_enabled_bits(self):
1✔
1601
            feature_name = LnFeatures(1 << flag).name
1✔
1602
            r.append(feature_name or f"bit_{flag}")
1✔
1603
        return r
1✔
1604

1605
    if hasattr(IntFlag, "_numeric_repr_"):  # python 3.11+
1✔
1606
        # performance improvement (avoid base2<->base10), see #8403
1607
        _numeric_repr_ = hex
×
1608

1609
    def __repr__(self):
1✔
1610
        # performance improvement (avoid base2<->base10), see #8403
1611
        return f"<{self._name_}: {hex(self._value_)}>"
1✔
1612

1613
    def __str__(self):
1✔
1614
        # performance improvement (avoid base2<->base10), see #8403
1615
        return hex(self._value_)
1✔
1616

1617

1618
@stored_as('channel_type', _type=None)
1✔
1619
class ChannelType(IntFlag):
1✔
1620
    OPTION_LEGACY_CHANNEL = 0
1✔
1621
    OPTION_STATIC_REMOTEKEY = 1 << 12
1✔
1622
    OPTION_ANCHOR_OUTPUTS = 1 << 20
1✔
1623
    OPTION_ANCHORS_ZERO_FEE_HTLC_TX = 1 << 22
1✔
1624
    OPTION_SCID_ALIAS = 1 << 46
1✔
1625
    OPTION_ZEROCONF = 1 << 50
1✔
1626

1627
    def discard_unknown_and_check(self):
1✔
1628
        """Discards unknown flags and checks flag combination."""
1629
        flags = list_enabled_bits(self)
1✔
1630
        known_channel_types = []
1✔
1631
        for flag in flags:
1✔
1632
            channel_type = ChannelType(1 << flag)
1✔
1633
            if channel_type.name:
1✔
1634
                known_channel_types.append(channel_type)
1✔
1635
        final_channel_type = known_channel_types[0]
1✔
1636
        for channel_type in known_channel_types[1:]:
1✔
1637
            final_channel_type |= channel_type
1✔
1638

1639
        final_channel_type.check_combinations()
1✔
1640
        return final_channel_type
1✔
1641

1642
    def check_combinations(self):
1✔
1643
        basic_type = self & ~(ChannelType.OPTION_SCID_ALIAS | ChannelType.OPTION_ZEROCONF)
1✔
1644
        if basic_type not in [
1✔
1645
                ChannelType.OPTION_STATIC_REMOTEKEY,
1646
                ChannelType.OPTION_ANCHOR_OUTPUTS | ChannelType.OPTION_STATIC_REMOTEKEY,
1647
                ChannelType.OPTION_ANCHORS_ZERO_FEE_HTLC_TX | ChannelType.OPTION_STATIC_REMOTEKEY
1648
        ]:
1649
            raise ValueError("Channel type is not a valid flag combination.")
×
1650

1651
    def complies_with_features(self, features: LnFeatures) -> bool:
1✔
1652
        flags = list_enabled_bits(self)
1✔
1653
        complies = True
1✔
1654
        for flag in flags:
1✔
1655
            feature = LnFeatures(1 << flag)
1✔
1656
            complies &= features.supports(feature)
1✔
1657
        return complies
1✔
1658

1659
    def to_bytes_minimal(self):
1✔
1660
        # MUST use the smallest bitmap possible to represent the channel type.
1661
        bit_length = self.value.bit_length()
×
1662
        byte_length = bit_length // 8 + int(bool(bit_length % 8))
×
1663
        return self.to_bytes(byte_length, byteorder='big')
×
1664

1665
    @property
1✔
1666
    def name_minimal(self):
1✔
1667
        if self.name:
×
1668
            return self.name.replace('OPTION_', '')
×
1669
        else:
1670
            return str(self)
×
1671

1672

1673
del LNFC  # name is ambiguous without context
1✔
1674

1675
# features that are actually implemented and understood in our codebase:
1676
# (note: this is not what we send in e.g. init!)
1677
# (note: specify both OPT and REQ here)
1678
LN_FEATURES_IMPLEMENTED = (
1✔
1679
        LnFeatures(0)
1680
        | LnFeatures.OPTION_DATA_LOSS_PROTECT_OPT | LnFeatures.OPTION_DATA_LOSS_PROTECT_REQ
1681
        | LnFeatures.GOSSIP_QUERIES_OPT | LnFeatures.GOSSIP_QUERIES_REQ
1682
        | LnFeatures.OPTION_STATIC_REMOTEKEY_OPT | LnFeatures.OPTION_STATIC_REMOTEKEY_REQ
1683
        | LnFeatures.VAR_ONION_OPT | LnFeatures.VAR_ONION_REQ
1684
        | LnFeatures.PAYMENT_SECRET_OPT | LnFeatures.PAYMENT_SECRET_REQ
1685
        | LnFeatures.BASIC_MPP_OPT | LnFeatures.BASIC_MPP_REQ
1686
        | LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM | LnFeatures.OPTION_TRAMPOLINE_ROUTING_REQ_ELECTRUM
1687
        | LnFeatures.OPTION_SHUTDOWN_ANYSEGWIT_OPT | LnFeatures.OPTION_SHUTDOWN_ANYSEGWIT_REQ
1688
        | LnFeatures.OPTION_CHANNEL_TYPE_OPT | LnFeatures.OPTION_CHANNEL_TYPE_REQ
1689
        | LnFeatures.OPTION_SCID_ALIAS_OPT | LnFeatures.OPTION_SCID_ALIAS_REQ
1690
        | LnFeatures.OPTION_ANCHORS_ZERO_FEE_HTLC_OPT | LnFeatures.OPTION_ANCHORS_ZERO_FEE_HTLC_REQ
1691
)
1692

1693

1694
def get_ln_flag_pair_of_bit(flag_bit: int) -> int:
1✔
1695
    """Ln Feature flags are assigned in pairs, one even, one odd. See BOLT-09.
1696
    Return the other flag from the pair.
1697
    e.g. 6 -> 7
1698
    e.g. 7 -> 6
1699
    """
1700
    if flag_bit % 2 == 0:
1✔
1701
        return flag_bit + 1
1✔
1702
    else:
1703
        return flag_bit - 1
1✔
1704

1705

1706
class GossipTimestampFilter:
1✔
1707
    def __init__(self, first_timestamp: int, timestamp_range: int):
1✔
1708
        self.first_timestamp = first_timestamp
×
1709
        self.timestamp_range = timestamp_range
×
1710
        # True once we sent them the requested gossip and only forward
1711
        self.only_forwarding = False
×
1712
        if first_timestamp >= int(time.time()) - 20:
×
1713
            self.only_forwarding = True
×
1714

1715
    def __str__(self):
1✔
1716
        return (f"GossipTimestampFilter | first_timestamp={self.first_timestamp} | "
×
1717
                f"timestamp_range={self.timestamp_range}")
1718

1719
    def in_range(self, timestamp: int) -> bool:
1✔
1720
        return self.first_timestamp <= timestamp < self.first_timestamp + self.timestamp_range
×
1721

1722
    @classmethod
1✔
1723
    def from_payload(cls, payload) -> Optional['GossipTimestampFilter']:
1✔
1724
        try:
×
1725
            first_timestamp = payload['first_timestamp']
×
1726
            timestamp_range = payload['timestamp_range']
×
1727
        except KeyError:
×
1728
            return None
×
1729
        if first_timestamp >= 0xFFFFFFFF:
×
1730
            return None
×
1731
        return cls(first_timestamp, timestamp_range)
×
1732

1733

1734
class GossipForwardingMessage:
1✔
1735
    def __init__(self,
1✔
1736
                 msg: bytes,
1737
                 scid: Optional[ShortChannelID] = None,
1738
                 timestamp: Optional[int] = None,
1739
                 sender_node_id: Optional[bytes] = None):
1740
        self.scid: Optional[ShortChannelID] = scid
×
1741
        self.sender_node_id: Optional[bytes] = sender_node_id
×
1742
        self.msg = msg
×
1743
        self.timestamp = timestamp
×
1744

1745
    @classmethod
1✔
1746
    def from_payload(cls, payload: dict) -> Optional['GossipForwardingMessage']:
1✔
1747
        try:
1✔
1748
            msg = payload['raw']
1✔
1749
            scid = ShortChannelID.normalize(payload.get('short_channel_id'))
×
1750
            sender_node_id = payload.get('sender_node_id')
×
1751
            timestamp = payload.get('timestamp')
×
1752
        except KeyError:
1✔
1753
            return None
1✔
1754
        return cls(msg, scid, timestamp, sender_node_id)
×
1755

1756

1757
def list_enabled_ln_feature_bits(features: int) -> tuple[int, ...]:
1✔
1758
    """Returns a list of enabled feature bits. If both opt and req are set, only
1759
    req will be included in the result."""
1760
    all_enabled_bits = list_enabled_bits(features)
1✔
1761
    single_feature_bits: set[int] = set()
1✔
1762
    for bit in all_enabled_bits:
1✔
1763
        if bit % 2 == 0:  # even bit, always added
1✔
1764
            single_feature_bits.add(bit)
1✔
1765
        elif bit - 1 not in single_feature_bits:
1✔
1766
            # add if we haven't already added the corresponding req (even) bit
1767
            single_feature_bits.add(bit)
1✔
1768
    return tuple(sorted(single_feature_bits))
1✔
1769

1770

1771
class IncompatibleOrInsaneFeatures(Exception): pass
1✔
1772
class UnknownEvenFeatureBits(IncompatibleOrInsaneFeatures): pass
1✔
1773
class IncompatibleLightningFeatures(IncompatibleOrInsaneFeatures): pass
1✔
1774

1775

1776
def ln_compare_features(our_features: 'LnFeatures', their_features: int) -> 'LnFeatures':
1✔
1777
    """Returns negotiated features.
1778
    Raises IncompatibleLightningFeatures if incompatible.
1779
    """
1780
    our_flags = set(list_enabled_bits(our_features))
1✔
1781
    their_flags = set(list_enabled_bits(their_features))
1✔
1782
    # check that they have our required features, and disable the optional features they don't have
1783
    for flag in our_flags:
1✔
1784
        if flag not in their_flags and get_ln_flag_pair_of_bit(flag) not in their_flags:
1✔
1785
            # they don't have this feature we wanted :(
1786
            if flag % 2 == 0:  # even flags are compulsory
1✔
1787
                raise IncompatibleLightningFeatures(f"remote does not support {LnFeatures(1 << flag)!r}")
1✔
1788
            our_features ^= 1 << flag  # disable flag
1✔
1789
        else:
1790
            # They too have this flag.
1791
            # For easier feature-bit-testing, if this is an even flag, we also
1792
            # set the corresponding odd flag now.
1793
            if flag % 2 == 0 and our_features & (1 << flag):
1✔
1794
                our_features |= 1 << get_ln_flag_pair_of_bit(flag)
1✔
1795
    # check that we have their required features
1796
    for flag in their_flags:
1✔
1797
        if flag not in our_flags and get_ln_flag_pair_of_bit(flag) not in our_flags:
1✔
1798
            # we don't have this feature they wanted :(
1799
            if flag % 2 == 0:  # even flags are compulsory
1✔
1800
                raise IncompatibleLightningFeatures(f"remote wanted feature we don't have: {LnFeatures(1 << flag)!r}")
1✔
1801
    return our_features
1✔
1802

1803

1804
if hasattr(sys, "get_int_max_str_digits"):
1✔
1805
    # check that the user or other library has not lowered the limit (from default)
1806
    assert sys.get_int_max_str_digits() >= 4300, f"sys.get_int_max_str_digits() too low: {sys.get_int_max_str_digits()}"
1✔
1807

1808

1809
@lru_cache(maxsize=1000)  # massive speedup for the hot path of channel_db.load_data()
1✔
1810
def validate_features(features: int) -> LnFeatures:
1✔
1811
    """Raises IncompatibleOrInsaneFeatures if
1812
    - a mandatory feature is listed that we don't recognize, or
1813
    - the features are inconsistent
1814
    For convenience, returns the parsed features.
1815
    """
1816
    if features.bit_length() > 10_000:
1✔
1817
        # This is an implementation-specific limit for how high feature bits we allow.
1818
        # Needed as LnFeatures subclasses IntFlag, and uses ints internally.
1819
        # See https://docs.python.org/3/library/stdtypes.html#integer-string-conversion-length-limitation
1820
        raise IncompatibleOrInsaneFeatures(f"features bitvector too large: {features.bit_length()=} > 10_000")
×
1821
    features = LnFeatures(features)
1✔
1822
    enabled_features = list_enabled_bits(features)
1✔
1823
    for fbit in enabled_features:
1✔
1824
        if (1 << fbit) & LN_FEATURES_IMPLEMENTED == 0 and fbit % 2 == 0:
1✔
1825
            raise UnknownEvenFeatureBits(fbit)
1✔
1826
    if not features.validate_transitive_dependencies():
1✔
1827
        raise IncompatibleOrInsaneFeatures(f"not all transitive dependencies are set. "
1✔
1828
                                           f"features={features}")
1829
    return features
1✔
1830

1831

1832
def get_compressed_pubkey_from_bech32(bech32_pubkey: str) -> bytes:
1✔
1833
    decoded_bech32 = segwit_addr.bech32_decode(bech32_pubkey)
1✔
1834
    hrp = decoded_bech32.hrp
1✔
1835
    data_5bits = decoded_bech32.data
1✔
1836
    if decoded_bech32.encoding is None:
1✔
1837
        raise ValueError("Bad bech32 checksum")
×
1838
    if decoded_bech32.encoding != segwit_addr.Encoding.BECH32:
1✔
1839
        raise ValueError("Bad bech32 encoding: must be using vanilla BECH32")
×
1840
    if hrp != 'ln':
1✔
1841
        raise Exception('unexpected hrp: {}'.format(hrp))
×
1842
    data_8bits = segwit_addr.convertbits(data_5bits, 5, 8, False)
1✔
1843
    # pad with zeroes
1844
    COMPRESSED_PUBKEY_LENGTH = 33
1✔
1845
    data_8bits = data_8bits + ((COMPRESSED_PUBKEY_LENGTH - len(data_8bits)) * [0])
1✔
1846
    return bytes(data_8bits)
1✔
1847

1848

1849
def make_closing_tx(
1✔
1850
        local_funding_pubkey: bytes,
1851
        remote_funding_pubkey: bytes,
1852
        funding_txid: str,
1853
        funding_pos: int,
1854
        funding_sat: int,
1855
        outputs: List[PartialTxOutput]
1856
) -> PartialTransaction:
1857
    c_input = make_funding_input(local_funding_pubkey, remote_funding_pubkey, funding_pos, funding_txid, funding_sat)
1✔
1858
    c_input.nsequence = 0xFFFF_FFFF
1✔
1859
    tx = PartialTransaction.from_io([c_input], outputs, locktime=0, version=2)
1✔
1860
    return tx
1✔
1861

1862

1863
# key derivation
1864
# originally based on lnd/keychain/derivation.go
1865
# notes:
1866
# - Add a new path for each use case. Do not reuse existing paths.
1867
#   (to avoid having to carefully consider if reuse would be safe)
1868
# - Always prefer to use hardened derivation for new paths you add.
1869
#   (to avoid having to carefully consider if unhardened would be safe)
1870
class LnKeyFamily(IntEnum):
1✔
1871
    MULTISIG = 0 | BIP32_PRIME
1✔
1872
    REVOCATION_BASE = 1 | BIP32_PRIME
1✔
1873
    HTLC_BASE = 2 | BIP32_PRIME
1✔
1874
    PAYMENT_BASE = 3 | BIP32_PRIME
1✔
1875
    DELAY_BASE = 4 | BIP32_PRIME
1✔
1876
    REVOCATION_ROOT = 5 | BIP32_PRIME
1✔
1877
    NODE_KEY = 6
1✔
1878
    BACKUP_CIPHER = 7 | BIP32_PRIME
1✔
1879
    PAYMENT_SECRET_KEY = 8 | BIP32_PRIME
1✔
1880
    NOSTR_KEY = 9 | BIP32_PRIME
1✔
1881
    FUNDING_ROOT_KEY = 10 | BIP32_PRIME
1✔
1882

1883

1884
def generate_keypair(node: BIP32Node, key_family: LnKeyFamily) -> Keypair:
1✔
1885
    node2 = node.subkey_at_private_derivation([key_family, 0, 0])
1✔
1886
    k = node2.eckey.get_secret_bytes()
1✔
1887
    cK = ecc.ECPrivkey(k).get_public_key_bytes()
1✔
1888
    return Keypair(cK, k)
1✔
1889

1890

1891
def generate_random_keypair() -> Keypair:
1✔
1892
    import secrets
×
1893
    k = secrets.token_bytes(32)
×
1894
    cK = ecc.ECPrivkey(k).get_public_key_bytes()
×
1895
    return Keypair(cK, k)
×
1896

1897

1898
NUM_MAX_HOPS_IN_PAYMENT_PATH = 20
1✔
1899
NUM_MAX_EDGES_IN_PAYMENT_PATH = NUM_MAX_HOPS_IN_PAYMENT_PATH
1✔
1900

1901

1902
@dataclasses.dataclass(frozen=True, kw_only=True)
1✔
1903
class UpdateAddHtlc:
1✔
1904
    amount_msat: int
1✔
1905
    payment_hash: bytes
1✔
1906
    cltv_abs: int
1✔
1907
    htlc_id: Optional[int] = dataclasses.field(default=None)
1✔
1908
    timestamp: int = dataclasses.field(default_factory=lambda: int(time.time()))
1✔
1909

1910
    @staticmethod
1✔
1911
    @stored_in('adds', tuple)
1✔
1912
    def from_tuple(amount_msat, rhash, cltv_abs, htlc_id, timestamp) -> 'UpdateAddHtlc':
1✔
1913
        return UpdateAddHtlc(
1✔
1914
            amount_msat=amount_msat,
1915
            payment_hash=bytes.fromhex(rhash),
1916
            cltv_abs=cltv_abs,
1917
            htlc_id=htlc_id,
1918
            timestamp=timestamp)
1919

1920
    def to_json(self):
1✔
1921
        self._validate()
×
1922
        return dataclasses.astuple(self)
×
1923

1924
    def _validate(self):
1✔
1925
        assert isinstance(self.amount_msat, int), self.amount_msat
1✔
1926
        assert isinstance(self.payment_hash, bytes) and len(self.payment_hash) == 32
1✔
1927
        assert isinstance(self.cltv_abs, int) and self.cltv_abs <= NLOCKTIME_BLOCKHEIGHT_MAX, self.cltv_abs
1✔
1928
        assert isinstance(self.htlc_id, int) or self.htlc_id is None, self.htlc_id
1✔
1929
        assert isinstance(self.timestamp, int), self.timestamp
1✔
1930

1931
    def __post_init__(self):
1✔
1932
        self._validate()
1✔
1933

1934

1935
# Note: these states are persisted in the wallet file.
1936
# Do not modify them without performing a wallet db upgrade
1937
# todo: if this changes again states could also be persisted by name instead of int value as done for ChannelState
1938
class RecvMPPResolution(IntEnum):
1✔
1939
    WAITING = 0  # set is not complete yet, waiting for arrival of the remaining htlcs
1✔
1940
    EXPIRED = 1  # preimage must not be revealed
1✔
1941
    COMPLETE = 2  # set is complete but could still be failed (e.g. due to cltv timeout)
1✔
1942
    FAILED = 3  # preimage must not be revealed
1✔
1943
    SETTLING = 4  # Must not be failed, should be settled asap.
1✔
1944
                  # Also used when forwarding (for upstream), in which case a downstream
1945
                  # forwarding failure could still result in transitioning to FAILED.
1946

1947

1948
r = RecvMPPResolution
1✔
1949
allowed_mpp_set_transitions = (
1✔
1950
    (r.WAITING, r.EXPIRED),
1951
    (r.WAITING, r.FAILED),
1952
    (r.WAITING, r.COMPLETE),
1953
    (r.WAITING, r.SETTLING),  # normal htlc forwarding
1954

1955
    (r.COMPLETE, r.SETTLING),
1956
    (r.COMPLETE, r.FAILED),
1957
    (r.COMPLETE, r.EXPIRED),  # this should only realistically happen for payment bundles
1958

1959
    (r.SETTLING, r.FAILED),  # forwarding failure, hold invoice callback gets unregistered, and we don't have preimage
1960

1961
    (r.EXPIRED, r.FAILED),  # doesn't seem useful but also not dangerous
1962
)
1963
del r
1✔
1964

1965

1966
class ReceivedMPPHtlc(NamedTuple):
1✔
1967
    scid: ShortChannelID
1✔
1968
    htlc: UpdateAddHtlc
1✔
1969
    unprocessed_onion: str
1✔
1970

1971
    def __repr__(self):
1✔
1972
        return f"{self.scid}, {self.htlc=}, {self.unprocessed_onion[:15]=}..."
1✔
1973

1974
    @staticmethod
1✔
1975
    def from_tuple(scid, htlc, unprocessed_onion) -> 'ReceivedMPPHtlc':
1✔
NEW
1976
        assert is_hex_str(unprocessed_onion) and is_hex_str(scid)
×
NEW
1977
        return ReceivedMPPHtlc(
×
1978
            scid=ShortChannelID(bytes.fromhex(scid)),
1979
            htlc=UpdateAddHtlc.from_tuple(*htlc),
1980
            unprocessed_onion=unprocessed_onion,
1981
        )
1982

1983

1984
class ReceivedMPPStatus(NamedTuple):
1✔
1985
    resolution: RecvMPPResolution
1✔
1986
    htlcs: set[ReceivedMPPHtlc]
1✔
1987
    # parent_set_key is needed as trampoline allows MPP to be nested, the parent_set_key is the
1988
    # payment key of the final mpp set (derived from inner trampoline onion payment secret)
1989
    # to which the separate trampoline sets htlcs get added once they are complete.
1990
    # https://github.com/lightning/bolts/pull/829/commits/bc7a1a0bc97b2293e7f43dd8a06529e5fdcf7cd2
1991
    parent_set_key: str = None
1✔
1992

1993
    def get_first_htlc_timestamp(self) -> Optional[int]:
1✔
1994
        return min([mpp_htlc.htlc.timestamp for mpp_htlc in self.htlcs], default=None)
1✔
1995

1996
    def get_closest_cltv_abs(self) -> Optional[int]:
1✔
1997
        return min([mpp_htlc.htlc.cltv_abs for mpp_htlc in self.htlcs], default=None)
1✔
1998

1999
    def get_payment_hash(self) -> Optional[bytes]:
1✔
2000
        mpp_htlcs = iter(self.htlcs)
1✔
2001
        first_mpp_htlc = next(mpp_htlcs, None)
1✔
2002
        payment_hash = first_mpp_htlc.htlc.payment_hash if first_mpp_htlc else None
1✔
2003
        for mpp_htlc in mpp_htlcs:
1✔
2004
            assert mpp_htlc.htlc.payment_hash == payment_hash, "mpp set with inconsistent payment hashes"
1✔
2005
        return payment_hash
1✔
2006

2007
    @staticmethod
1✔
2008
    @stored_in('received_mpp_htlcs', tuple)
1✔
2009
    def from_tuple(resolution, htlc_list, parent_set_key=None) -> 'ReceivedMPPStatus':
1✔
NEW
2010
        assert isinstance(resolution, int)
×
NEW
2011
        htlc_set = set(ReceivedMPPHtlc.from_tuple(*htlc_data) for htlc_data in htlc_list)
×
UNCOV
2012
        return ReceivedMPPStatus(
×
2013
            resolution=RecvMPPResolution(resolution),
2014
            htlcs=htlc_set,
2015
            parent_set_key=parent_set_key,
2016
        )
2017

2018

2019
class OnionFailureCodeMetaFlag(IntFlag):
1✔
2020
    BADONION = 0x8000
1✔
2021
    PERM     = 0x4000
1✔
2022
    NODE     = 0x2000
1✔
2023
    UPDATE   = 0x1000
1✔
2024

2025

2026
class PaymentFeeBudget(NamedTuple):
1✔
2027
    fee_msat: int
1✔
2028

2029
    # The cltv budget covers the cost of route to get to the destination, but excluding the
2030
    # cltv-delta the destination wants for itself. (e.g. "min_final_cltv_delta" is excluded)
2031
    cltv: int  # this is cltv-delta-like, no absolute heights here!
1✔
2032

2033
    #num_htlc: int
2034

2035
    @classmethod
1✔
2036
    def from_invoice_amount(
1✔
2037
        cls,
2038
        *,
2039
        invoice_amount_msat: int,
2040
        config: 'SimpleConfig',
2041
        max_cltv_delta: Optional[int] = None,
2042
        max_fee_msat: Optional[int] = None,
2043
    ) -> 'PaymentFeeBudget':
2044
        if max_fee_msat is None:
1✔
2045
            max_fee_msat = PaymentFeeBudget._calculate_fee_msat(
1✔
2046
                invoice_amount_msat=invoice_amount_msat,
2047
                config=config,
2048
            )
2049
        if max_cltv_delta is None:
1✔
2050
            max_cltv_delta = NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE
1✔
2051
        assert max_cltv_delta > 0, max_cltv_delta
1✔
2052
        return PaymentFeeBudget(
1✔
2053
            fee_msat=max_fee_msat,
2054
            cltv=max_cltv_delta,
2055
        )
2056

2057
    @classmethod
1✔
2058
    def _calculate_fee_msat(
1✔
2059
        cls,
2060
        *,
2061
        invoice_amount_msat: int,
2062
        config: 'SimpleConfig',
2063
        fee_millionths: Optional[int] = None,
2064
        fee_cutoff_msat: Optional[int] = None,
2065
    ) -> int:
2066
        if fee_millionths is None:
1✔
2067
            fee_millionths = config.LIGHTNING_PAYMENT_FEE_MAX_MILLIONTHS
1✔
2068
        if fee_cutoff_msat is None:
1✔
2069
            fee_cutoff_msat = config.LIGHTNING_PAYMENT_FEE_CUTOFF_MSAT
1✔
2070
        millionths_clamped = min(max(0, fee_millionths), 250_000)  # clamp into [0, 25%]
1✔
2071
        cutoff_clamped = min(max(0, fee_cutoff_msat), 10_000_000)  # clamp into [0, 10k sat]
1✔
2072
        if fee_millionths != millionths_clamped:
1✔
2073
            _logger.warning(
×
2074
                f"PaymentFeeBudget. found insane fee millionths in config. "
2075
                f"clamped: {fee_millionths}->{millionths_clamped}")
2076
        if fee_cutoff_msat != cutoff_clamped:
1✔
2077
            _logger.warning(
×
2078
                f"PaymentFeeBudget. found insane fee cutoff in config. "
2079
                f"clamped: {fee_cutoff_msat}->{cutoff_clamped}")
2080
        # for small payments, fees <= constant cutoff are fine
2081
        # for large payments, the max fee is percentage-based
2082
        fee_msat = invoice_amount_msat * millionths_clamped // 1_000_000
1✔
2083
        fee_msat = max(fee_msat, cutoff_clamped)
1✔
2084
        return fee_msat
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc