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

spesmilo / electrum / 6606673856430080

15 Jul 2025 10:35PM UTC coverage: 59.8% (-0.003%) from 59.803%
6606673856430080

push

CirrusCI

SomberNight
verifier: fix off-by-one for max_checkpoint

if a wallet had a tx mined in the max_checkpoint block, in certain cases
we would leave it forever in the "unverified" state and remain stuck in "synchronizing..."

0 of 3 new or added lines in 2 files covered. (0.0%)

10 existing lines in 5 files now uncovered.

21985 of 36764 relevant lines covered (59.8%)

2.99 hits per line

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

79.38
/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
5✔
5
import enum
5✔
6
from collections import defaultdict
5✔
7
from typing import NamedTuple, List, Tuple, Mapping, Optional, TYPE_CHECKING, Union, Dict, Set, Sequence
5✔
8
import sys
5✔
9
import time
5✔
10
from functools import lru_cache
5✔
11

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

17
from .util import bfh, UserFacingException, list_enabled_bits
5✔
18
from .util import ShortID as ShortChannelID, format_short_id as format_short_channel_id
5✔
19

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

33

34
if TYPE_CHECKING:
5✔
35
    from .lnchannel import Channel, AbstractChannel
×
36
    from .lnrouter import LNPaymentRoute
×
37
    from .lnonion import OnionRoutingFailure
×
38
    from .simple_config import SimpleConfig
×
39

40

41
_logger = get_logger(__name__)
5✔
42

43

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

54
LN_MAX_FUNDING_SAT_LEGACY = pow(2, 24) - 1
5✔
55
DUST_LIMIT_MAX = 1000
5✔
56

57
SCRIPT_TEMPLATE_FUNDING = [opcodes.OP_2, OPPushDataPubkey, OPPushDataPubkey, opcodes.OP_2, opcodes.OP_CHECKMULTISIG]
5✔
58

59

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

65

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

69

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

73

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

77

78
def serialize_htlc_key(scid: bytes, htlc_id: int) -> str:
5✔
79
    return scid.hex() + ':%d' % htlc_id
5✔
80

81

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

86

87
@attr.s
5✔
88
class OnlyPubkeyKeypair(StoredObject):
5✔
89
    pubkey = attr.ib(type=bytes, converter=hex_to_bytes, repr=bytes_to_hex)
5✔
90

91

92
@attr.s
5✔
93
class Keypair(OnlyPubkeyKeypair):
5✔
94
    privkey = attr.ib(type=bytes, converter=hex_to_bytes, repr=bytes_to_hex)
5✔
95

96

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

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

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

214

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

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

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

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

255
        return LocalConfig(**kwargs)
×
256

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

267

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

274

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

282

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

291

292
CHANNEL_BACKUP_VERSION_LATEST = 2
5✔
293
KNOWN_CHANNEL_BACKUP_VERSIONS = (0, 1, 2, )
5✔
294
assert CHANNEL_BACKUP_VERSION_LATEST in KNOWN_CHANNEL_BACKUP_VERSIONS
5✔
295

296

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

304
    def funding_outpoint(self):
5✔
305
        return Outpoint(self.funding_txid, self.funding_index)
×
306

307
    def channel_id(self):
5✔
308
        chan_id, _ = channel_id_from_funding_tx(self.funding_txid, self.funding_index)
×
309
        return chan_id
×
310

311

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

317

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

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

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

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

407

408
class ScriptHtlc(NamedTuple):
5✔
409
    redeem_script: bytes
5✔
410
    htlc: 'UpdateAddHtlc'
5✔
411

412

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

420
    def to_str(self):
5✔
421
        return "{}:{}".format(self.txid, self.output_index)
5✔
422

423

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

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

454

455
class LightningError(Exception): pass
5✔
456
class UnableToDeriveSecret(LightningError): pass
5✔
457
class RemoteMisbehaving(LightningError): pass
5✔
458
class NotFoundChanAnnouncementForUpdate(Exception): pass
5✔
459

460

461
class InvalidGossipMsg(Exception):
5✔
462
    """e.g. signature check failed"""
463

464

465
class PaymentFailure(UserFacingException): pass
5✔
466

467

468
class NoPathFound(PaymentFailure):
5✔
469
    def __str__(self):
5✔
470
        return _('No path found')
5✔
471

472

473
class FeeBudgetExceeded(PaymentFailure):
5✔
474
    def __str__(self):
5✔
475
        return _('Fee budget exceeded')
×
476

477

478
class LNProtocolError(Exception):
5✔
479
    """Raised in peer methods to trigger an error message."""
480

481

482
class LNProtocolWarning(Exception):
5✔
483
    """Raised in peer methods to trigger a warning message."""
484

485

486
# TODO make some of these values configurable?
487
REDEEM_AFTER_DOUBLE_SPENT_DELAY = 30
5✔
488

489
CHANNEL_OPENING_TIMEOUT = 24*60*60
5✔
490

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

499

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

503
# the minimum cltv_expiry accepted for newly received HTLCs
504
# note: when changing, consider Blockchain.is_tip_stale()
505
MIN_FINAL_CLTV_DELTA_ACCEPTED = 144
5✔
506
# set it a tiny bit higher for invoices as blocks could get mined
507
# during forward path of payment
508
MIN_FINAL_CLTV_DELTA_FOR_INVOICE = MIN_FINAL_CLTV_DELTA_ACCEPTED + 3
5✔
509

510
# the deadline for offered HTLCs:
511
# the deadline after which the channel has to be failed and timed out on-chain
512
NBLOCK_DEADLINE_DELTA_AFTER_EXPIRY_FOR_OFFERED_HTLCS = 1
5✔
513

514
# the deadline for received HTLCs this node has fulfilled:
515
# the deadline after which the channel has to be failed and the HTLC fulfilled on-chain before its cltv_expiry
516
NBLOCK_DEADLINE_DELTA_BEFORE_EXPIRY_FOR_RECEIVED_HTLCS = 72
5✔
517

518
NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE = 28 * 144
5✔
519

520
MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED = 2016
5✔
521

522
# timeout after which we consider a zeroconf channel without funding tx to be failed
523
ZEROCONF_TIMEOUT = 60 * 10
5✔
524

525

526
class RevocationStore:
5✔
527
    # closely based on code in lightningnetwork/lnd
528

529
    START_INDEX = 2 ** 48 - 1
5✔
530

531
    def __init__(self, storage):
5✔
532
        if len(storage) == 0:
5✔
533
            storage['index'] = self.START_INDEX
5✔
534
            storage['buckets'] = {}
5✔
535
        self.storage = storage
5✔
536
        self.buckets = storage['buckets']
5✔
537

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

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

563

564
def count_trailing_zeros(index):
5✔
565
    """ BOLT-03 (where_to_put_secret) """
566
    try:
5✔
567
        return list(reversed(bin(index)[2:])).index("1")
5✔
568
    except ValueError:
×
569
        return 48
×
570

571

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

584

585
class ShachainElement(NamedTuple):
5✔
586
    secret: bytes
5✔
587
    index: int
5✔
588

589
    def __str__(self):
5✔
590
        return "ShachainElement(" + self.secret.hex() + "," + str(self.index) + ")"
×
591

592
    @stored_in('buckets', tuple)
5✔
593
    def read(*x):
5✔
594
        return ShachainElement(bfh(x[0]), int(x[1]))
5✔
595

596

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

608

609
def secret_to_pubkey(secret: int) -> bytes:
5✔
610
    assert type(secret) is int
5✔
611
    return ecc.ECPrivkey.from_secret_scalar(secret).get_public_key_bytes(compressed=True)
5✔
612

613

614
def derive_pubkey(basepoint: bytes, per_commitment_point: bytes) -> bytes:
5✔
615
    p = ecc.ECPubkey(basepoint) + ecc.GENERATOR * ecc.string_to_number(sha256(per_commitment_point + basepoint))
5✔
616
    return p.get_public_key_bytes()
5✔
617

618

619
def derive_privkey(secret: int, per_commitment_point: bytes) -> int:
5✔
620
    assert type(secret) is int
5✔
621
    basepoint_bytes = secret_to_pubkey(secret)
5✔
622
    basepoint = secret + ecc.string_to_number(sha256(per_commitment_point + basepoint_bytes))
5✔
623
    basepoint %= CURVE_ORDER
5✔
624
    return basepoint
5✔
625

626

627
def derive_blinded_pubkey(basepoint: bytes, per_commitment_point: bytes) -> bytes:
5✔
628
    k1 = ecc.ECPubkey(basepoint) * ecc.string_to_number(sha256(basepoint + per_commitment_point))
5✔
629
    k2 = ecc.ECPubkey(per_commitment_point) * ecc.string_to_number(sha256(per_commitment_point + basepoint))
5✔
630
    return (k1 + k2).get_public_key_bytes()
5✔
631

632

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

641

642
def derive_payment_basepoint(static_payment_secret: bytes, funding_pubkey: bytes) -> Keypair:
5✔
643
    assert isinstance(static_payment_secret, bytes)
×
644
    assert isinstance(funding_pubkey, bytes)
×
645
    payment_basepoint = ecc.ECPrivkey(sha256(static_payment_secret + funding_pubkey))
×
646
    return Keypair(
×
647
        pubkey=payment_basepoint.get_public_key_bytes(),
648
        privkey=payment_basepoint.get_secret_bytes()
649
    )
650

651

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

675

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

699

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

717
    p2wsh = bitcoin.redeem_script_to_address('p2wsh', script)
5✔
718
    weight = effective_htlc_tx_weight(success=success, has_anchors=has_anchors)
5✔
719
    fee = local_feerate * weight
5✔
720
    fee = fee // 1000 * 1000
5✔
721
    final_amount_sat = (amount_msat - fee) // 1000
5✔
722
    assert final_amount_sat > 0, final_amount_sat
5✔
723
    output = PartialTxOutput.from_address_and_value(p2wsh, final_amount_sat)
5✔
724
    return script, output
5✔
725

726

727
def make_htlc_tx_witness(
5✔
728
        remotehtlcsig: bytes,
729
        localhtlcsig: bytes,
730
        payment_preimage: bytes,
731
        witness_script: bytes
732
) -> bytes:
733
    assert type(remotehtlcsig) is bytes
5✔
734
    assert type(localhtlcsig) is bytes
5✔
735
    assert type(payment_preimage) is bytes
5✔
736
    assert type(witness_script) is bytes
5✔
737
    return construct_witness([0, remotehtlcsig, localhtlcsig, payment_preimage, witness_script])
5✔
738

739

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

758

759
def make_htlc_tx(*, cltv_abs: int, inputs: List[PartialTxInput], output: PartialTxOutput) -> PartialTransaction:
5✔
760
    assert type(cltv_abs) is int
5✔
761
    c_outputs = [output]
5✔
762
    tx = PartialTransaction.from_io(inputs, c_outputs, locktime=cltv_abs, version=2)
5✔
763
    return tx
5✔
764

765

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

791

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

818

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

855

856
WITNESS_TEMPLATE_OFFERED_HTLC = witness_template_offered_htlc(anchors=False)
5✔
857
WITNESS_TEMPLATE_OFFERED_HTLC_ANCHORS = witness_template_offered_htlc(anchors=True)
5✔
858

859

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

899

900
WITNESS_TEMPLATE_RECEIVED_HTLC = witness_template_received_htlc(anchors=False)
5✔
901
WITNESS_TEMPLATE_RECEIVED_HTLC_ANCHORS = witness_template_received_htlc(anchors=True)
5✔
902

903

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

932

933
def get_ordered_channel_configs(
5✔
934
        chan: 'AbstractChannel',
935
        for_us: bool
936
) -> Tuple[Union[LocalConfig, RemoteConfig], Union[LocalConfig, RemoteConfig]]:
937
    conf =       chan.config[LOCAL] if     for_us else chan.config[REMOTE]
5✔
938
    other_conf = chan.config[LOCAL] if not for_us else chan.config[REMOTE]
5✔
939
    return conf, other_conf
5✔
940

941

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

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

972

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

1001
    return {inverse_map[ctx_output_idx]: (ctx_output_idx, htlc_relative_idx)
5✔
1002
            for htlc_relative_idx, ctx_output_idx in enumerate(sorted(inverse_map))}
1003

1004

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

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

1056

1057
def make_funding_input(
5✔
1058
    local_funding_pubkey: bytes,
1059
    remote_funding_pubkey: bytes,
1060
    funding_pos: int,
1061
    funding_txid: str,
1062
    funding_sat: int
1063
) -> PartialTxInput:
1064

1065
    pubkeys = sorted([local_funding_pubkey.hex(), remote_funding_pubkey.hex()])
5✔
1066
    # commitment tx input
1067
    prevout = TxOutpoint(txid=bfh(funding_txid), out_idx=funding_pos)
5✔
1068
    c_input = PartialTxInput(prevout=prevout)
5✔
1069

1070
    ppubkeys = [descriptor.PubkeyProvider.parse(pk) for pk in pubkeys]
5✔
1071
    multi = descriptor.MultisigDescriptor(pubkeys=ppubkeys, thresh=2, is_sorted=True)
5✔
1072
    c_input.script_descriptor = descriptor.WSHDescriptor(subdescriptor=multi)
5✔
1073
    c_input._trusted_value_sats = funding_sat
5✔
1074
    return c_input
5✔
1075

1076

1077
class HTLCOwner(IntEnum):
5✔
1078
    LOCAL = 1
5✔
1079
    REMOTE = -LOCAL
5✔
1080

1081
    def inverted(self) -> 'HTLCOwner':
5✔
1082
        return -self
5✔
1083

1084
    def __neg__(self) -> 'HTLCOwner':
5✔
1085
        return HTLCOwner(super().__neg__())
5✔
1086

1087

1088
class Direction(IntEnum):
5✔
1089
    SENT = -1     # in the context of HTLCs: "offered" HTLCs
5✔
1090
    RECEIVED = 1  # in the context of HTLCs: "received" HTLCs
5✔
1091

1092

1093
SENT = Direction.SENT
5✔
1094
RECEIVED = Direction.RECEIVED
5✔
1095

1096
LOCAL = HTLCOwner.LOCAL
5✔
1097
REMOTE = HTLCOwner.REMOTE
5✔
1098

1099

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

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

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

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

1142
        # include anchors for outputs that materialize, include both if there are HTLCs present
1143
        if to_local_amt_msat // 1000 >= dust_limit_sat or htlc_outputs:
5✔
1144
            anchor_outputs.append(PartialTxOutput(scriptpubkey=local_anchor_script, value=FIXED_ANCHOR_SAT))
5✔
1145
        if to_remote_amt_msat // 1000 >= dust_limit_sat or htlc_outputs:
5✔
1146
            anchor_outputs.append(PartialTxOutput(scriptpubkey=remote_anchor_script, value=FIXED_ANCHOR_SAT))
5✔
1147

1148
    # if funder cannot afford feerate, their output might go negative, so take max(0, x) here
1149
    to_local_amt_msat = max(0, to_local_amt_msat)
5✔
1150
    to_remote_amt_msat = max(0, to_remote_amt_msat)
5✔
1151
    non_htlc_outputs.append(PartialTxOutput(scriptpubkey=local_script, value=to_local_amt_msat // 1000))
5✔
1152
    non_htlc_outputs.append(PartialTxOutput(scriptpubkey=remote_script, value=to_remote_amt_msat // 1000))
5✔
1153

1154
    c_outputs_filtered = list(filter(lambda x: x.value >= dust_limit_sat, non_htlc_outputs + htlc_outputs))
5✔
1155
    c_outputs = c_outputs_filtered + anchor_outputs
5✔
1156
    return htlc_outputs, c_outputs
5✔
1157

1158

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

1169

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

1177

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

1185

1186
def fee_for_htlc_output(*, feerate: int) -> int:
5✔
1187
    # feerate is in sat/kw
1188
    # returns fee in msat
1189
    return feerate * HTLC_OUTPUT_WEIGHT
5✔
1190

1191

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

1217

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

1246
    c_inputs = [c_input]
5✔
1247

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

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

1265
    htlc_outputs, c_outputs_filtered = make_commitment_outputs(
5✔
1266
        fees_per_participant=fees_per_participant,
1267
        local_amount_msat=local_amount,
1268
        remote_amount_msat=remote_amount,
1269
        local_script=address_to_script(local_address),
1270
        remote_script=address_to_script(remote_address),
1271
        htlcs=htlcs,
1272
        dust_limit_sat=dust_limit_sat,
1273
        has_anchors=has_anchors,
1274
        local_anchor_script=address_to_script(local_anchor_address) if local_anchor_address else None,
1275
        remote_anchor_script=address_to_script(remote_anchor_address) if remote_anchor_address else None
1276
    )
1277

1278
    assert sum(x.value for x in c_outputs_filtered) <= funding_sat, (c_outputs_filtered, funding_sat)
5✔
1279

1280
    # create commitment tx
1281
    tx = PartialTransaction.from_io(c_inputs, c_outputs_filtered, locktime=locktime, version=2)
5✔
1282
    return tx
5✔
1283

1284

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

1306

1307
def make_commitment_output_to_local_address(
5✔
1308
        revocation_pubkey: bytes, to_self_delay: int, delayed_pubkey: bytes) -> str:
1309
    local_script = make_commitment_output_to_local_witness_script(revocation_pubkey, to_self_delay, delayed_pubkey)
5✔
1310
    return bitcoin.redeem_script_to_address('p2wsh', local_script)
5✔
1311

1312

1313
def make_commitment_output_to_remote_witness_script(remote_payment_pubkey: bytes) -> bytes:
5✔
1314
    assert isinstance(remote_payment_pubkey, bytes)
5✔
1315
    script = construct_script([
5✔
1316
        remote_payment_pubkey,
1317
        opcodes.OP_CHECKSIGVERIFY,
1318
        opcodes.OP_1,
1319
        opcodes.OP_CHECKSEQUENCEVERIFY,
1320
    ])
1321
    return script
5✔
1322

1323

1324
def make_commitment_output_to_remote_address(remote_payment_pubkey: bytes, has_anchors: bool) -> str:
5✔
1325
    if has_anchors:
5✔
1326
        remote_script = make_commitment_output_to_remote_witness_script(remote_payment_pubkey)
5✔
1327
        return bitcoin.redeem_script_to_address('p2wsh', remote_script)
5✔
1328
    else:
1329
        return bitcoin.pubkey_to_address('p2wpkh', remote_payment_pubkey.hex())
5✔
1330

1331

1332
def make_commitment_output_to_anchor_witness_script(funding_pubkey: bytes) -> bytes:
5✔
1333
    assert isinstance(funding_pubkey, bytes)
5✔
1334
    script = construct_script([
5✔
1335
        funding_pubkey,
1336
        opcodes.OP_CHECKSIG,
1337
        opcodes.OP_IFDUP,
1338
        opcodes.OP_NOTIF,
1339
        opcodes.OP_16,
1340
        opcodes.OP_CHECKSEQUENCEVERIFY,
1341
        opcodes.OP_ENDIF,
1342
    ])
1343
    return script
5✔
1344

1345

1346
def make_commitment_output_to_anchor_address(funding_pubkey: bytes) -> str:
5✔
1347
    script = make_commitment_output_to_anchor_witness_script(funding_pubkey)
5✔
1348
    return bitcoin.redeem_script_to_address('p2wsh', script)
5✔
1349

1350

1351
def sign_and_get_sig_string(tx: PartialTransaction, local_config, remote_config):
5✔
1352
    tx.sign({local_config.multisig_key.pubkey: local_config.multisig_key.privkey})
5✔
1353
    sig = tx.inputs()[0].sigs_ecdsa[local_config.multisig_key.pubkey]
5✔
1354
    sig_64 = ecdsa_sig64_from_der_sig(sig[:-1])
5✔
1355
    return sig_64
5✔
1356

1357

1358
def funding_output_script(local_config: 'LocalConfig', remote_config: 'RemoteConfig') -> bytes:
5✔
1359
    return funding_output_script_from_keys(local_config.multisig_key.pubkey, remote_config.multisig_key.pubkey)
5✔
1360

1361

1362
def funding_output_script_from_keys(pubkey1: bytes, pubkey2: bytes) -> bytes:
5✔
1363
    pubkeys = sorted([pubkey1.hex(), pubkey2.hex()])
5✔
1364
    return transaction.multisig_script(pubkeys, 2)
5✔
1365

1366

1367
def get_obscured_ctn(ctn: int, funder: bytes, fundee: bytes) -> int:
5✔
1368
    mask = int.from_bytes(sha256(funder + fundee)[-6:], 'big')
5✔
1369
    return ctn ^ mask
5✔
1370

1371

1372
def extract_ctn_from_tx(tx: Transaction, txin_index: int, funder_payment_basepoint: bytes,
5✔
1373
                        fundee_payment_basepoint: bytes) -> int:
1374
    tx.deserialize()
5✔
1375
    locktime = tx.locktime
5✔
1376
    sequence = tx.inputs()[txin_index].nsequence
5✔
1377
    obs = ((sequence & 0xffffff) << 24) + (locktime & 0xffffff)
5✔
1378
    return get_obscured_ctn(obs, funder_payment_basepoint, fundee_payment_basepoint)
5✔
1379

1380

1381
def extract_ctn_from_tx_and_chan(tx: Transaction, chan: 'AbstractChannel') -> int:
5✔
1382
    funder_conf = chan.config[LOCAL] if     chan.is_initiator() else chan.config[REMOTE]
5✔
1383
    fundee_conf = chan.config[LOCAL] if not chan.is_initiator() else chan.config[REMOTE]
5✔
1384
    return extract_ctn_from_tx(tx, txin_index=0,
5✔
1385
                               funder_payment_basepoint=funder_conf.payment_basepoint.pubkey,
1386
                               fundee_payment_basepoint=fundee_conf.payment_basepoint.pubkey)
1387

1388

1389
def ctx_has_anchors(tx: Transaction):
5✔
1390
    output_values = [output.value for output in tx.outputs()]
×
1391
    if FIXED_ANCHOR_SAT in output_values:
×
1392
        return True
×
1393
    else:
1394
        return False
×
1395

1396

1397
class LnFeatureContexts(enum.Flag):
5✔
1398
    INIT = enum.auto()
5✔
1399
    NODE_ANN = enum.auto()
5✔
1400
    CHAN_ANN_AS_IS = enum.auto()
5✔
1401
    CHAN_ANN_ALWAYS_ODD = enum.auto()
5✔
1402
    CHAN_ANN_ALWAYS_EVEN = enum.auto()
5✔
1403
    INVOICE = enum.auto()
5✔
1404

1405

1406
LNFC = LnFeatureContexts
5✔
1407

1408
_ln_feature_direct_dependencies = defaultdict(set)  # type: Dict[LnFeatures, Set[LnFeatures]]
5✔
1409
_ln_feature_contexts = {}  # type: Dict[LnFeatures, LnFeatureContexts]
5✔
1410

1411

1412
class LnFeatures(IntFlag):
5✔
1413
    OPTION_DATA_LOSS_PROTECT_REQ = 1 << 0
5✔
1414
    OPTION_DATA_LOSS_PROTECT_OPT = 1 << 1
5✔
1415
    _ln_feature_contexts[OPTION_DATA_LOSS_PROTECT_OPT] = (LNFC.INIT | LnFeatureContexts.NODE_ANN)
5✔
1416
    _ln_feature_contexts[OPTION_DATA_LOSS_PROTECT_REQ] = (LNFC.INIT | LnFeatureContexts.NODE_ANN)
5✔
1417

1418
    INITIAL_ROUTING_SYNC = 1 << 3
5✔
1419
    _ln_feature_contexts[INITIAL_ROUTING_SYNC] = LNFC.INIT
5✔
1420

1421
    OPTION_UPFRONT_SHUTDOWN_SCRIPT_REQ = 1 << 4
5✔
1422
    OPTION_UPFRONT_SHUTDOWN_SCRIPT_OPT = 1 << 5
5✔
1423
    _ln_feature_contexts[OPTION_UPFRONT_SHUTDOWN_SCRIPT_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1424
    _ln_feature_contexts[OPTION_UPFRONT_SHUTDOWN_SCRIPT_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1425

1426
    GOSSIP_QUERIES_REQ = 1 << 6
5✔
1427
    GOSSIP_QUERIES_OPT = 1 << 7
5✔
1428
    _ln_feature_contexts[GOSSIP_QUERIES_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1429
    _ln_feature_contexts[GOSSIP_QUERIES_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1430

1431
    VAR_ONION_REQ = 1 << 8
5✔
1432
    VAR_ONION_OPT = 1 << 9
5✔
1433
    _ln_feature_contexts[VAR_ONION_OPT] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
5✔
1434
    _ln_feature_contexts[VAR_ONION_REQ] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
5✔
1435

1436
    GOSSIP_QUERIES_EX_REQ = 1 << 10
5✔
1437
    GOSSIP_QUERIES_EX_OPT = 1 << 11
5✔
1438
    _ln_feature_direct_dependencies[GOSSIP_QUERIES_EX_OPT] = {GOSSIP_QUERIES_OPT}
5✔
1439
    _ln_feature_contexts[GOSSIP_QUERIES_EX_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1440
    _ln_feature_contexts[GOSSIP_QUERIES_EX_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1441

1442
    OPTION_STATIC_REMOTEKEY_REQ = 1 << 12
5✔
1443
    OPTION_STATIC_REMOTEKEY_OPT = 1 << 13
5✔
1444
    _ln_feature_contexts[OPTION_STATIC_REMOTEKEY_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1445
    _ln_feature_contexts[OPTION_STATIC_REMOTEKEY_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1446

1447
    PAYMENT_SECRET_REQ = 1 << 14
5✔
1448
    PAYMENT_SECRET_OPT = 1 << 15
5✔
1449
    _ln_feature_direct_dependencies[PAYMENT_SECRET_OPT] = {VAR_ONION_OPT}
5✔
1450
    _ln_feature_contexts[PAYMENT_SECRET_OPT] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
5✔
1451
    _ln_feature_contexts[PAYMENT_SECRET_REQ] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
5✔
1452

1453
    BASIC_MPP_REQ = 1 << 16
5✔
1454
    BASIC_MPP_OPT = 1 << 17
5✔
1455
    _ln_feature_direct_dependencies[BASIC_MPP_OPT] = {PAYMENT_SECRET_OPT}
5✔
1456
    _ln_feature_contexts[BASIC_MPP_OPT] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
5✔
1457
    _ln_feature_contexts[BASIC_MPP_REQ] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
5✔
1458

1459
    OPTION_SUPPORT_LARGE_CHANNEL_REQ = 1 << 18
5✔
1460
    OPTION_SUPPORT_LARGE_CHANNEL_OPT = 1 << 19
5✔
1461
    _ln_feature_contexts[OPTION_SUPPORT_LARGE_CHANNEL_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1462
    _ln_feature_contexts[OPTION_SUPPORT_LARGE_CHANNEL_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1463

1464
    OPTION_ANCHOR_OUTPUTS_REQ = 1 << 20
5✔
1465
    OPTION_ANCHOR_OUTPUTS_OPT = 1 << 21
5✔
1466
    _ln_feature_direct_dependencies[OPTION_ANCHOR_OUTPUTS_OPT] = {OPTION_STATIC_REMOTEKEY_OPT}
5✔
1467
    _ln_feature_contexts[OPTION_ANCHOR_OUTPUTS_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1468
    _ln_feature_contexts[OPTION_ANCHOR_OUTPUTS_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1469

1470
    OPTION_ANCHORS_ZERO_FEE_HTLC_REQ = 1 << 22
5✔
1471
    OPTION_ANCHORS_ZERO_FEE_HTLC_OPT = 1 << 23
5✔
1472
    _ln_feature_direct_dependencies[OPTION_ANCHORS_ZERO_FEE_HTLC_OPT] = {OPTION_STATIC_REMOTEKEY_OPT}
5✔
1473
    _ln_feature_contexts[OPTION_ANCHORS_ZERO_FEE_HTLC_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1474
    _ln_feature_contexts[OPTION_ANCHORS_ZERO_FEE_HTLC_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1475

1476
    # Temporary number.
1477
    OPTION_TRAMPOLINE_ROUTING_REQ_ECLAIR = 1 << 148
5✔
1478
    OPTION_TRAMPOLINE_ROUTING_OPT_ECLAIR = 1 << 149
5✔
1479

1480
    _ln_feature_contexts[OPTION_TRAMPOLINE_ROUTING_REQ_ECLAIR] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
5✔
1481
    _ln_feature_contexts[OPTION_TRAMPOLINE_ROUTING_OPT_ECLAIR] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
5✔
1482

1483
    # We use a different bit because Phoenix cannot do end-to-end multi-trampoline routes
1484
    OPTION_TRAMPOLINE_ROUTING_REQ_ELECTRUM = 1 << 150
5✔
1485
    OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM = 1 << 151
5✔
1486

1487
    _ln_feature_contexts[OPTION_TRAMPOLINE_ROUTING_REQ_ELECTRUM] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
5✔
1488
    _ln_feature_contexts[OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM] = (LNFC.INIT | LNFC.NODE_ANN | LNFC.INVOICE)
5✔
1489

1490
    OPTION_SHUTDOWN_ANYSEGWIT_REQ = 1 << 26
5✔
1491
    OPTION_SHUTDOWN_ANYSEGWIT_OPT = 1 << 27
5✔
1492

1493
    _ln_feature_contexts[OPTION_SHUTDOWN_ANYSEGWIT_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1494
    _ln_feature_contexts[OPTION_SHUTDOWN_ANYSEGWIT_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1495

1496
    OPTION_ONION_MESSAGE_REQ = 1 << 38
5✔
1497
    OPTION_ONION_MESSAGE_OPT = 1 << 39
5✔
1498

1499
    _ln_feature_contexts[OPTION_ONION_MESSAGE_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1500
    _ln_feature_contexts[OPTION_ONION_MESSAGE_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1501

1502
    OPTION_CHANNEL_TYPE_REQ = 1 << 44
5✔
1503
    OPTION_CHANNEL_TYPE_OPT = 1 << 45
5✔
1504

1505
    _ln_feature_contexts[OPTION_CHANNEL_TYPE_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1506
    _ln_feature_contexts[OPTION_CHANNEL_TYPE_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1507

1508
    OPTION_SCID_ALIAS_REQ = 1 << 46
5✔
1509
    OPTION_SCID_ALIAS_OPT = 1 << 47
5✔
1510

1511
    _ln_feature_contexts[OPTION_SCID_ALIAS_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1512
    _ln_feature_contexts[OPTION_SCID_ALIAS_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1513

1514
    OPTION_ZEROCONF_REQ = 1 << 50
5✔
1515
    OPTION_ZEROCONF_OPT = 1 << 51
5✔
1516

1517
    _ln_feature_direct_dependencies[OPTION_ZEROCONF_OPT] = {OPTION_SCID_ALIAS_OPT}
5✔
1518
    _ln_feature_contexts[OPTION_ZEROCONF_REQ] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1519
    _ln_feature_contexts[OPTION_ZEROCONF_OPT] = (LNFC.INIT | LNFC.NODE_ANN)
5✔
1520

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

1537
    def for_init_message(self) -> 'LnFeatures':
5✔
1538
        features = LnFeatures(0)
5✔
1539
        for flag in list_enabled_ln_feature_bits(self):
5✔
1540
            if LnFeatureContexts.INIT & _ln_feature_contexts[1 << flag]:
5✔
1541
                features |= (1 << flag)
5✔
1542
        return features
5✔
1543

1544
    def for_node_announcement(self) -> 'LnFeatures':
5✔
1545
        features = LnFeatures(0)
×
1546
        for flag in list_enabled_ln_feature_bits(self):
×
1547
            if LnFeatureContexts.NODE_ANN & _ln_feature_contexts[1 << flag]:
×
1548
                features |= (1 << flag)
×
1549
        return features
×
1550

1551
    def for_invoice(self) -> 'LnFeatures':
5✔
1552
        features = LnFeatures(0)
5✔
1553
        for flag in list_enabled_ln_feature_bits(self):
5✔
1554
            if LnFeatureContexts.INVOICE & _ln_feature_contexts[1 << flag]:
5✔
1555
                features |= (1 << flag)
5✔
1556
        return features
5✔
1557

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

1573
    def min_len(self) -> int:
5✔
1574
        b = int.bit_length(self)
5✔
1575
        return b // 8 + int(bool(b % 8))
5✔
1576

1577
    def supports(self, feature: 'LnFeatures') -> bool:
5✔
1578
        """Returns whether given feature is enabled.
1579

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

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

1601
    if hasattr(IntFlag, "_numeric_repr_"):  # python 3.11+
5✔
1602
        # performance improvement (avoid base2<->base10), see #8403
UNCOV
1603
        _numeric_repr_ = hex
4✔
1604

1605
    def __repr__(self):
5✔
1606
        # performance improvement (avoid base2<->base10), see #8403
1607
        return f"<{self._name_}: {hex(self._value_)}>"
5✔
1608

1609
    def __str__(self):
5✔
1610
        # performance improvement (avoid base2<->base10), see #8403
1611
        return hex(self._value_)
5✔
1612

1613

1614
@stored_as('channel_type', _type=None)
5✔
1615
class ChannelType(IntFlag):
5✔
1616
    OPTION_LEGACY_CHANNEL = 0
5✔
1617
    OPTION_STATIC_REMOTEKEY = 1 << 12
5✔
1618
    OPTION_ANCHOR_OUTPUTS = 1 << 20
5✔
1619
    OPTION_ANCHORS_ZERO_FEE_HTLC_TX = 1 << 22
5✔
1620
    OPTION_SCID_ALIAS = 1 << 46
5✔
1621
    OPTION_ZEROCONF = 1 << 50
5✔
1622

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

1635
        final_channel_type.check_combinations()
5✔
1636
        return final_channel_type
5✔
1637

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

1647
    def complies_with_features(self, features: LnFeatures) -> bool:
5✔
1648
        flags = list_enabled_bits(self)
5✔
1649
        complies = True
5✔
1650
        for flag in flags:
5✔
1651
            feature = LnFeatures(1 << flag)
5✔
1652
            complies &= features.supports(feature)
5✔
1653
        return complies
5✔
1654

1655
    def to_bytes_minimal(self):
5✔
1656
        # MUST use the smallest bitmap possible to represent the channel type.
1657
        bit_length = self.value.bit_length()
×
1658
        byte_length = bit_length // 8 + int(bool(bit_length % 8))
×
1659
        return self.to_bytes(byte_length, byteorder='big')
×
1660

1661
    @property
5✔
1662
    def name_minimal(self):
5✔
1663
        if self.name:
×
1664
            return self.name.replace('OPTION_', '')
×
1665
        else:
1666
            return str(self)
×
1667

1668

1669
del LNFC  # name is ambiguous without context
5✔
1670

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

1689

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

1701

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

1711
    def __str__(self):
5✔
1712
        return (f"GossipTimestampFilter | first_timestamp={self.first_timestamp} | "
×
1713
                f"timestamp_range={self.timestamp_range}")
1714

1715
    def in_range(self, timestamp: int) -> bool:
5✔
1716
        return self.first_timestamp <= timestamp < self.first_timestamp + self.timestamp_range
×
1717

1718
    @classmethod
5✔
1719
    def from_payload(cls, payload) -> Optional['GossipTimestampFilter']:
5✔
1720
        try:
×
1721
            first_timestamp = payload['first_timestamp']
×
1722
            timestamp_range = payload['timestamp_range']
×
1723
        except KeyError:
×
1724
            return None
×
1725
        if first_timestamp >= 0xFFFFFFFF:
×
1726
            return None
×
1727
        return cls(first_timestamp, timestamp_range)
×
1728

1729

1730
class GossipForwardingMessage:
5✔
1731
    def __init__(self,
5✔
1732
                 msg: bytes,
1733
                 scid: Optional[ShortChannelID] = None,
1734
                 timestamp: Optional[int] = None,
1735
                 sender_node_id: Optional[bytes] = None):
1736
        self.scid: Optional[ShortChannelID] = scid
×
1737
        self.sender_node_id: Optional[bytes] = sender_node_id
×
1738
        self.msg = msg
×
1739
        self.timestamp = timestamp
×
1740

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

1752

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

1766

1767
class IncompatibleOrInsaneFeatures(Exception): pass
5✔
1768
class UnknownEvenFeatureBits(IncompatibleOrInsaneFeatures): pass
5✔
1769
class IncompatibleLightningFeatures(IncompatibleOrInsaneFeatures): pass
5✔
1770

1771

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

1799

1800
if hasattr(sys, "get_int_max_str_digits"):
5✔
1801
    # check that the user or other library has not lowered the limit (from default)
1802
    assert sys.get_int_max_str_digits() >= 4300, f"sys.get_int_max_str_digits() too low: {sys.get_int_max_str_digits()}"
5✔
1803

1804

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

1827

1828
def derive_payment_secret_from_payment_preimage(payment_preimage: bytes) -> bytes:
5✔
1829
    """Returns secret to be put into invoice.
1830
    Derivation is deterministic, based on the preimage.
1831
    Crucially the payment_hash must be derived in an independent way from this.
1832
    """
1833
    # Note that this could be random data too, but then we would need to store it.
1834
    # We derive it identically to clightning, so that we cannot be distinguished:
1835
    # https://github.com/ElementsProject/lightning/blob/faac4b28adee5221e83787d64cd5d30b16b62097/lightningd/invoice.c#L115
1836
    modified = bytearray(payment_preimage)
5✔
1837
    modified[0] ^= 1
5✔
1838
    return sha256(bytes(modified))
5✔
1839

1840

1841
def get_compressed_pubkey_from_bech32(bech32_pubkey: str) -> bytes:
5✔
1842
    decoded_bech32 = segwit_addr.bech32_decode(bech32_pubkey)
5✔
1843
    hrp = decoded_bech32.hrp
5✔
1844
    data_5bits = decoded_bech32.data
5✔
1845
    if decoded_bech32.encoding is None:
5✔
1846
        raise ValueError("Bad bech32 checksum")
×
1847
    if decoded_bech32.encoding != segwit_addr.Encoding.BECH32:
5✔
1848
        raise ValueError("Bad bech32 encoding: must be using vanilla BECH32")
×
1849
    if hrp != 'ln':
5✔
1850
        raise Exception('unexpected hrp: {}'.format(hrp))
×
1851
    data_8bits = segwit_addr.convertbits(data_5bits, 5, 8, False)
5✔
1852
    # pad with zeroes
1853
    COMPRESSED_PUBKEY_LENGTH = 33
5✔
1854
    data_8bits = data_8bits + ((COMPRESSED_PUBKEY_LENGTH - len(data_8bits)) * [0])
5✔
1855
    return bytes(data_8bits)
5✔
1856

1857

1858
def make_closing_tx(
5✔
1859
        local_funding_pubkey: bytes,
1860
        remote_funding_pubkey: bytes,
1861
        funding_txid: str,
1862
        funding_pos: int,
1863
        funding_sat: int,
1864
        outputs: List[PartialTxOutput]
1865
) -> PartialTransaction:
1866
    c_input = make_funding_input(local_funding_pubkey, remote_funding_pubkey, funding_pos, funding_txid, funding_sat)
5✔
1867
    c_input.nsequence = 0xFFFF_FFFF
5✔
1868
    tx = PartialTransaction.from_io([c_input], outputs, locktime=0, version=2)
5✔
1869
    return tx
5✔
1870

1871

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

1892

1893
def generate_keypair(node: BIP32Node, key_family: LnKeyFamily) -> Keypair:
5✔
1894
    node2 = node.subkey_at_private_derivation([key_family, 0, 0])
5✔
1895
    k = node2.eckey.get_secret_bytes()
5✔
1896
    cK = ecc.ECPrivkey(k).get_public_key_bytes()
5✔
1897
    return Keypair(cK, k)
5✔
1898

1899

1900
def generate_random_keypair() -> Keypair:
5✔
1901
    import secrets
×
1902
    k = secrets.token_bytes(32)
×
1903
    cK = ecc.ECPrivkey(k).get_public_key_bytes()
×
1904
    return Keypair(cK, k)
×
1905

1906

1907
NUM_MAX_HOPS_IN_PAYMENT_PATH = 20
5✔
1908
NUM_MAX_EDGES_IN_PAYMENT_PATH = NUM_MAX_HOPS_IN_PAYMENT_PATH
5✔
1909

1910

1911
@attr.s(frozen=True)
5✔
1912
class UpdateAddHtlc:
5✔
1913
    amount_msat = attr.ib(type=int, kw_only=True)
5✔
1914
    payment_hash = attr.ib(type=bytes, kw_only=True, converter=hex_to_bytes, repr=lambda val: val.hex())
5✔
1915
    cltv_abs = attr.ib(type=int, kw_only=True)
5✔
1916
    timestamp = attr.ib(type=int, kw_only=True)
5✔
1917
    htlc_id = attr.ib(type=int, kw_only=True, default=None)
5✔
1918

1919
    @staticmethod
5✔
1920
    @stored_in('adds', tuple)
5✔
1921
    def from_tuple(amount_msat, payment_hash, cltv_abs, htlc_id, timestamp) -> 'UpdateAddHtlc':
5✔
1922
        return UpdateAddHtlc(
5✔
1923
            amount_msat=amount_msat,
1924
            payment_hash=payment_hash,
1925
            cltv_abs=cltv_abs,
1926
            htlc_id=htlc_id,
1927
            timestamp=timestamp)
1928

1929
    def to_json(self):
5✔
1930
        return self.amount_msat, self.payment_hash, self.cltv_abs, self.htlc_id, self.timestamp
×
1931

1932

1933
class OnionFailureCodeMetaFlag(IntFlag):
5✔
1934
    BADONION = 0x8000
5✔
1935
    PERM     = 0x4000
5✔
1936
    NODE     = 0x2000
5✔
1937
    UPDATE   = 0x1000
5✔
1938

1939

1940
class PaymentFeeBudget(NamedTuple):
5✔
1941
    fee_msat: int
5✔
1942

1943
    # The cltv budget covers the cost of route to get to the destination, but excluding the
1944
    # cltv-delta the destination wants for itself. (e.g. "min_final_cltv_delta" is excluded)
1945
    cltv: int  # this is cltv-delta-like, no absolute heights here!
5✔
1946

1947
    #num_htlc: int
1948

1949
    @classmethod
5✔
1950
    def default(cls, *, invoice_amount_msat: int, config: 'SimpleConfig') -> 'PaymentFeeBudget':
5✔
1951
        millionths_orig = config.LIGHTNING_PAYMENT_FEE_MAX_MILLIONTHS
5✔
1952
        millionths = min(max(0, millionths_orig), 250_000)  # clamp into [0, 25%]
5✔
1953
        cutoff_orig = config.LIGHTNING_PAYMENT_FEE_CUTOFF_MSAT
5✔
1954
        cutoff = min(max(0, cutoff_orig), 10_000_000)  # clamp into [0, 10k sat]
5✔
1955
        if millionths != millionths_orig:
5✔
1956
            _logger.warning(
×
1957
                f"PaymentFeeBudget. found insane fee millionths in config. "
1958
                f"clamped: {millionths_orig}->{millionths}")
1959
        if cutoff != cutoff_orig:
5✔
1960
            _logger.warning(
×
1961
                f"PaymentFeeBudget. found insane fee cutoff in config. "
1962
                f"clamped: {cutoff_orig}->{cutoff}")
1963
        # for small payments, fees <= constant cutoff are fine
1964
        # for large payments, the max fee is percentage-based
1965
        fee_msat = invoice_amount_msat * millionths // 1_000_000
5✔
1966
        fee_msat = max(fee_msat, cutoff)
5✔
1967
        return PaymentFeeBudget(
5✔
1968
            fee_msat=fee_msat,
1969
            cltv=NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE,
1970
        )
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