lightning: change derivation of funding_pubkey
Ideally, given an on-chain backup, after the remote force-closes, we should be able to spend our anchor output,
to CPFP the remote commitment tx (assuming the channel used OPTION_ANCHORS).
To spend the anchor output, we need to be able to sign with the local funding_privkey.
Previously we derived the funding_key from the channel_seed (which comes from os.urandom).
Prior to anchors, there was no use case for signing with the funding_key given a channel backup.
Now with anchors, we should make its derivation deterministic somehow, in a way so that it can
be derived given just an on-chain backup.
- one way would be to put some more data into the existing OP_RETURN
- uses block space
- the OP_RETURNs can be disabled via "use_recoverable_channels"
- only the initiator can use OP_RETURNs (so what if channel is in incoming dir?)
- instead, new scheme for our funding_key:
- we derive the funding_privkey from the lnworker root secret (derived from our bip32 seed)
- for outgoing channels:
- lnworker_root_secret + remote_node_id + funding_tx_nlocktime
- for incoming channels:
- lnworker_root_secret + remote_node_id + remote_funding_pubkey
- a check is added to avoid reusing the same key between channels:
not letting to user open more than one channel with the same peer in a single block
- only the first 16 bytes of the remote_node_id are used, as the onchain backup OP_RETURNs only contain that
- as the funding_privkey cannot be derived from the channel_seed anymore, it is included in the
imported channel backups, which in turn need a new version defined
- a wallet db upgrade is used to update already stored imported cbs
- alternatively we could keep the imported cbs as-is, so no new version, no new funding_privkey field, as it is clearly somewhat redundant given on-chain backups can reconstruct it
- however adding the field seems easier
- otherwise the existing code would try to derive the funding_privkey from the channel_seed
- also note: atm there is no field in the imported backups to distinguish anchor channels vs static-remotekey channels
This commit is contained in:
@@ -415,6 +415,10 @@ class AbstractChannel(Logger, ABC):
|
||||
def get_funding_address(self) -> str:
|
||||
pass
|
||||
|
||||
def get_funding_tx(self) -> Optional[Transaction]:
|
||||
funding_txid = self.funding_outpoint.txid
|
||||
return self.lnworker.lnwatcher.adb.get_transaction(funding_txid)
|
||||
|
||||
@abstractmethod
|
||||
def get_sweep_address(self) -> str:
|
||||
"""Returns a wallet address we can use to sweep coins to.
|
||||
@@ -534,6 +538,12 @@ class ChannelBackup(AbstractChannel):
|
||||
self.logger.warning(
|
||||
f"local_payment_pubkey missing from (old-type) channel backup. "
|
||||
f"You should export and re-import a newer backup.")
|
||||
multisig_funding_keypair = None
|
||||
if multisig_funding_secret := cb.multisig_funding_privkey:
|
||||
multisig_funding_keypair = Keypair(
|
||||
privkey=multisig_funding_secret,
|
||||
pubkey=ecc.ECPrivkey(multisig_funding_secret).get_public_key_bytes(),
|
||||
)
|
||||
self.config[LOCAL] = LocalConfig.from_seed(
|
||||
channel_seed=cb.channel_seed,
|
||||
to_self_delay=cb.local_delay,
|
||||
@@ -542,6 +552,7 @@ class ChannelBackup(AbstractChannel):
|
||||
# 2. static_remotekey: to_remote sweep not necessary due to wallet address
|
||||
# 3. anchor outputs: sweep to_remote by deriving the key from the funding pubkeys
|
||||
static_remotekey=local_payment_pubkey,
|
||||
multisig_key=multisig_funding_keypair,
|
||||
# dummy values
|
||||
static_payment_key=None,
|
||||
dust_limit_sat=None,
|
||||
@@ -594,7 +605,9 @@ class ChannelBackup(AbstractChannel):
|
||||
return True
|
||||
|
||||
def create_sweeptxs_for_their_ctx(self, ctx):
|
||||
return sweep_their_ctx_to_remote_backup(chan=self, ctx=ctx)
|
||||
funding_tx = self.get_funding_tx()
|
||||
assert funding_tx
|
||||
return sweep_their_ctx_to_remote_backup(chan=self, ctx=ctx, funding_tx=funding_tx)
|
||||
|
||||
def create_sweeptxs_for_our_ctx(self, ctx):
|
||||
if self.is_imported:
|
||||
|
||||
@@ -46,7 +46,7 @@ from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc, ChannelConf
|
||||
IncompatibleLightningFeatures, derive_payment_secret_from_payment_preimage,
|
||||
ChannelType, LNProtocolWarning, validate_features, IncompatibleOrInsaneFeatures)
|
||||
from .lnutil import FeeUpdate, channel_id_from_funding_tx, PaymentFeeBudget
|
||||
from .lnutil import serialize_htlc_key
|
||||
from .lnutil import serialize_htlc_key, Keypair
|
||||
from .lntransport import LNTransport, LNTransportBase, LightningPeerConnectionClosed, HandshakeFailed
|
||||
from .lnmsg import encode_msg, decode_msg, UnknownOptionalMsgType, FailedToParseMsg
|
||||
from .interface import GracefulDisconnect
|
||||
@@ -673,7 +673,15 @@ class Peer(Logger, EventListener):
|
||||
self.logger.info(f"upfront shutdown script received: {upfront_shutdown_script}")
|
||||
return upfront_shutdown_script
|
||||
|
||||
def make_local_config(self, funding_sat: int, push_msat: int, initiator: HTLCOwner, channel_type: ChannelType) -> LocalConfig:
|
||||
def make_local_config(
|
||||
self,
|
||||
*,
|
||||
funding_sat: int,
|
||||
push_msat: int,
|
||||
initiator: HTLCOwner,
|
||||
channel_type: ChannelType,
|
||||
multisig_funding_keypair: Optional[Keypair], # if None, will get derived from channel_seed
|
||||
) -> LocalConfig:
|
||||
channel_seed = os.urandom(32)
|
||||
initial_msat = funding_sat * 1000 - push_msat if initiator == LOCAL else push_msat
|
||||
|
||||
@@ -692,6 +700,14 @@ class Peer(Logger, EventListener):
|
||||
static_payment_key = None
|
||||
static_remotekey = bytes.fromhex(wallet.get_public_key(addr))
|
||||
|
||||
if multisig_funding_keypair:
|
||||
for chan in self.lnworker.channels.values(): # check against all chans of lnworker, for sanity
|
||||
if multisig_funding_keypair.pubkey == chan.config[LOCAL].multisig_key.pubkey:
|
||||
raise Exception(
|
||||
"Refusing to reuse multisig_funding_keypair for new channel. "
|
||||
"Wait one block before opening another channel with this peer."
|
||||
)
|
||||
|
||||
dust_limit_sat = bitcoin.DUST_LIMIT_P2PKH
|
||||
reserve_sat = max(funding_sat // 100, dust_limit_sat)
|
||||
# for comparison of defaults, see
|
||||
@@ -702,6 +718,7 @@ class Peer(Logger, EventListener):
|
||||
channel_seed=channel_seed,
|
||||
static_remotekey=static_remotekey,
|
||||
static_payment_key=static_payment_key,
|
||||
multisig_key=multisig_funding_keypair,
|
||||
upfront_shutdown_script=upfront_shutdown_script,
|
||||
to_self_delay=self.network.config.LIGHTNING_TO_SELF_DELAY_CSV,
|
||||
dust_limit_sat=dust_limit_sat,
|
||||
@@ -787,7 +804,21 @@ class Peer(Logger, EventListener):
|
||||
'type': our_channel_type.to_bytes_minimal()
|
||||
}
|
||||
|
||||
local_config = self.make_local_config(funding_sat, push_msat, LOCAL, our_channel_type)
|
||||
if self.use_anchors():
|
||||
multisig_funding_keypair = lnutil.derive_multisig_funding_key_if_we_opened(
|
||||
funding_root_secret=self.lnworker.funding_root_keypair.privkey,
|
||||
remote_node_id_or_prefix=self.pubkey,
|
||||
nlocktime=funding_tx.locktime,
|
||||
)
|
||||
else:
|
||||
multisig_funding_keypair = None
|
||||
local_config = self.make_local_config(
|
||||
funding_sat=funding_sat,
|
||||
push_msat=push_msat,
|
||||
initiator=LOCAL,
|
||||
channel_type=our_channel_type,
|
||||
multisig_funding_keypair=multisig_funding_keypair,
|
||||
)
|
||||
# if it includes open_channel_tlvs: MUST include upfront_shutdown_script.
|
||||
open_channel_tlvs['upfront_shutdown_script'] = {
|
||||
'shutdown_scriptpubkey': local_config.upfront_shutdown_script
|
||||
@@ -1019,7 +1050,21 @@ class Peer(Logger, EventListener):
|
||||
if not channel_type.complies_with_features(self.features):
|
||||
raise Exception("sender has sent a channel type we don't support")
|
||||
|
||||
local_config = self.make_local_config(funding_sat, push_msat, REMOTE, channel_type)
|
||||
if self.use_anchors():
|
||||
multisig_funding_keypair = lnutil.derive_multisig_funding_key_if_they_opened(
|
||||
funding_root_secret=self.lnworker.funding_root_keypair.privkey,
|
||||
remote_node_id_or_prefix=self.pubkey,
|
||||
remote_funding_pubkey=payload['funding_pubkey'],
|
||||
)
|
||||
else:
|
||||
multisig_funding_keypair = None
|
||||
local_config = self.make_local_config(
|
||||
funding_sat=funding_sat,
|
||||
push_msat=push_msat,
|
||||
initiator=REMOTE,
|
||||
channel_type=channel_type,
|
||||
multisig_funding_keypair=multisig_funding_keypair,
|
||||
)
|
||||
|
||||
upfront_shutdown_script = self.upfront_shutdown_script_from_payload(
|
||||
payload, 'open')
|
||||
|
||||
@@ -19,7 +19,8 @@ from .lnutil import (make_commitment_output_to_remote_address, make_commitment_o
|
||||
get_ordered_channel_configs, get_per_commitment_secret_from_seed,
|
||||
RevocationStore, extract_ctn_from_tx_and_chan, UnableToDeriveSecret, SENT, RECEIVED,
|
||||
map_htlcs_to_ctx_output_idxs, Direction, make_commitment_output_to_remote_witness_script,
|
||||
derive_payment_basepoint, ctx_has_anchors, SCRIPT_TEMPLATE_FUNDING)
|
||||
derive_payment_basepoint, ctx_has_anchors, SCRIPT_TEMPLATE_FUNDING, Keypair,
|
||||
derive_multisig_funding_key_if_we_opened, derive_multisig_funding_key_if_they_opened)
|
||||
from .transaction import (Transaction, TxInput, PartialTxInput,
|
||||
PartialTxOutput, TxOutpoint, script_GetOp, match_script_against_template)
|
||||
from .simple_config import SimpleConfig
|
||||
@@ -483,7 +484,9 @@ def extract_funding_pubkeys_from_ctx(txin: TxInput) -> Tuple[bytes, bytes]:
|
||||
|
||||
def sweep_their_ctx_to_remote_backup(
|
||||
*, chan: 'ChannelBackup',
|
||||
ctx: Transaction) -> Optional[Dict[str, SweepInfo]]:
|
||||
ctx: Transaction,
|
||||
funding_tx: Transaction,
|
||||
) -> Optional[Dict[str, SweepInfo]]:
|
||||
txs = {} # type: Dict[str, SweepInfo]
|
||||
"""If we only have a backup, and the remote force-closed with their ctx,
|
||||
and anchors are enabled, we need to sweep to_remote."""
|
||||
@@ -493,7 +496,7 @@ def sweep_their_ctx_to_remote_backup(
|
||||
funding_pubkeys = extract_funding_pubkeys_from_ctx(ctx.inputs()[0])
|
||||
_logger.debug(f'checking their ctx for funding pubkeys: {[pk.hex() for pk in funding_pubkeys]}')
|
||||
# check which of the pubkey was ours
|
||||
for pubkey in funding_pubkeys:
|
||||
for fp_idx, pubkey in enumerate(funding_pubkeys):
|
||||
candidate_basepoint = derive_payment_basepoint(chan.lnworker.static_payment_key.privkey, funding_pubkey=pubkey)
|
||||
candidate_to_remote_address = make_commitment_output_to_remote_address(candidate_basepoint.pubkey, has_anchors=True)
|
||||
if ctx.get_output_idxs_from_address(candidate_to_remote_address):
|
||||
@@ -508,8 +511,33 @@ def sweep_their_ctx_to_remote_backup(
|
||||
return {}
|
||||
|
||||
# remote anchor
|
||||
# derive funding_privkey ("multisig_key")
|
||||
# note: for imported backups, we already have this as 'local_config.multisig_key'
|
||||
# but for on-chain backups, we need to derive it.
|
||||
# For symmetry, we derive it now regardless of type
|
||||
our_funding_pubkey = funding_pubkeys[fp_idx]
|
||||
their_funding_pubkey = funding_pubkeys[1 - fp_idx]
|
||||
remote_node_id = chan.node_id # for onchain backups, this is only the prefix
|
||||
if chan.is_initiator():
|
||||
funding_kp_cand = derive_multisig_funding_key_if_we_opened(
|
||||
funding_root_secret=chan.lnworker.funding_root_keypair.privkey,
|
||||
remote_node_id_or_prefix=remote_node_id,
|
||||
nlocktime=funding_tx.locktime,
|
||||
)
|
||||
else:
|
||||
funding_kp_cand = derive_multisig_funding_key_if_they_opened(
|
||||
funding_root_secret=chan.lnworker.funding_root_keypair.privkey,
|
||||
remote_node_id_or_prefix=remote_node_id,
|
||||
remote_funding_pubkey=their_funding_pubkey,
|
||||
)
|
||||
assert funding_kp_cand.pubkey == our_funding_pubkey, f"funding pubkey mismatch1. {chan.is_initiator()=}"
|
||||
our_ms_funding_keypair = funding_kp_cand
|
||||
# sanity check funding_privkey, if we had it already (if backup is imported):
|
||||
if local_config := chan.config.get(LOCAL):
|
||||
if txin := sweep_ctx_anchor(ctx=ctx, multisig_key=local_config.multisig_key):
|
||||
assert our_ms_funding_keypair == local_config.multisig_key, f"funding pubkey mismatch2. {chan.is_initiator()=}"
|
||||
|
||||
if our_ms_funding_keypair:
|
||||
if txin := sweep_ctx_anchor(ctx=ctx, multisig_key=our_ms_funding_keypair):
|
||||
txs[txin.prevout.to_str()] = SweepInfo(
|
||||
name='remote_anchor',
|
||||
csv_delay=0,
|
||||
@@ -517,9 +545,6 @@ def sweep_their_ctx_to_remote_backup(
|
||||
txin=txin,
|
||||
txout=None,
|
||||
)
|
||||
else:
|
||||
# fixme: onchain channel backups do not store the channel seed
|
||||
pass
|
||||
|
||||
# to_remote
|
||||
csv_delay = 1
|
||||
@@ -810,7 +835,7 @@ def sweep_their_ctx_to_remote(
|
||||
return txin
|
||||
|
||||
|
||||
def sweep_ctx_anchor(*, ctx: Transaction, multisig_key)-> Optional[PartialTxInput]:
|
||||
def sweep_ctx_anchor(*, ctx: Transaction, multisig_key: Keypair) -> Optional[PartialTxInput]:
|
||||
from .lnutil import make_commitment_output_to_anchor_address, make_commitment_output_to_anchor_witness_script
|
||||
local_funding_pubkey = multisig_key.pubkey
|
||||
local_anchor_address = make_commitment_output_to_anchor_address(local_funding_pubkey)
|
||||
|
||||
@@ -224,7 +224,8 @@ class LocalConfig(ChannelConfig):
|
||||
node = BIP32Node.from_rootseed(channel_seed, xtype='standard')
|
||||
keypair_generator = lambda family: generate_keypair(node, family)
|
||||
kwargs['per_commitment_secret_seed'] = keypair_generator(LnKeyFamily.REVOCATION_ROOT).privkey
|
||||
kwargs['multisig_key'] = keypair_generator(LnKeyFamily.MULTISIG)
|
||||
if kwargs['multisig_key'] is None:
|
||||
kwargs['multisig_key'] = keypair_generator(LnKeyFamily.MULTISIG)
|
||||
kwargs['htlc_basepoint'] = keypair_generator(LnKeyFamily.HTLC_BASE)
|
||||
kwargs['delayed_basepoint'] = keypair_generator(LnKeyFamily.DELAY_BASE)
|
||||
kwargs['revocation_basepoint'] = keypair_generator(LnKeyFamily.REVOCATION_BASE)
|
||||
@@ -279,8 +280,8 @@ class ChannelConstraints(StoredObject):
|
||||
funding_txn_minimum_depth = attr.ib(type=int)
|
||||
|
||||
|
||||
CHANNEL_BACKUP_VERSION_LATEST = 1
|
||||
KNOWN_CHANNEL_BACKUP_VERSIONS = (0, 1,)
|
||||
CHANNEL_BACKUP_VERSION_LATEST = 2
|
||||
KNOWN_CHANNEL_BACKUP_VERSIONS = (0, 1, 2, )
|
||||
assert CHANNEL_BACKUP_VERSION_LATEST in KNOWN_CHANNEL_BACKUP_VERSIONS
|
||||
|
||||
@attr.s
|
||||
@@ -315,6 +316,7 @@ class ImportedChannelBackupStorage(ChannelBackupStorage):
|
||||
remote_payment_pubkey = attr.ib(type=bytes, converter=hex_to_bytes)
|
||||
remote_revocation_pubkey = attr.ib(type=bytes, converter=hex_to_bytes)
|
||||
local_payment_pubkey = attr.ib(type=bytes, converter=hex_to_bytes) # type: Optional[bytes]
|
||||
multisig_funding_privkey = attr.ib(type=bytes, converter=hex_to_bytes) # type: Optional[bytes]
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
vds = BCDataStream()
|
||||
@@ -333,6 +335,7 @@ class ImportedChannelBackupStorage(ChannelBackupStorage):
|
||||
vds.write_string(self.host)
|
||||
vds.write_uint16(self.port)
|
||||
vds.write_bytes(self.local_payment_pubkey, 33)
|
||||
vds.write_bytes(self.multisig_funding_privkey, 32)
|
||||
return bytes(vds.input)
|
||||
|
||||
@staticmethod
|
||||
@@ -359,6 +362,10 @@ class ImportedChannelBackupStorage(ChannelBackupStorage):
|
||||
local_payment_pubkey = vds.read_bytes(33)
|
||||
else:
|
||||
local_payment_pubkey = None
|
||||
if version >= 2:
|
||||
multisig_funding_privkey = vds.read_bytes(32)
|
||||
else:
|
||||
multisig_funding_privkey = None
|
||||
return ImportedChannelBackupStorage(
|
||||
is_initiator=is_initiator,
|
||||
privkey=privkey,
|
||||
@@ -374,6 +381,7 @@ class ImportedChannelBackupStorage(ChannelBackupStorage):
|
||||
host=host,
|
||||
port=port,
|
||||
local_payment_pubkey=local_payment_pubkey,
|
||||
multisig_funding_privkey=multisig_funding_privkey,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -616,6 +624,54 @@ def derive_payment_basepoint(static_payment_secret: bytes, funding_pubkey: bytes
|
||||
)
|
||||
|
||||
|
||||
def derive_multisig_funding_key_if_we_opened(
|
||||
*,
|
||||
funding_root_secret: bytes,
|
||||
remote_node_id_or_prefix: bytes,
|
||||
nlocktime: int,
|
||||
) -> Keypair:
|
||||
from .lnworker import NODE_ID_PREFIX_LEN
|
||||
assert isinstance(funding_root_secret, bytes)
|
||||
assert len(funding_root_secret) == 32
|
||||
assert isinstance(remote_node_id_or_prefix, bytes)
|
||||
assert len(remote_node_id_or_prefix) in (NODE_ID_PREFIX_LEN, 33)
|
||||
assert isinstance(nlocktime, int)
|
||||
nlocktime_bytes = int.to_bytes(nlocktime, length=4, byteorder="little", signed=False)
|
||||
node_id_prefix = remote_node_id_or_prefix[0:NODE_ID_PREFIX_LEN]
|
||||
funding_key = ecc.ECPrivkey(bitcoin.bip340_tagged_hash(
|
||||
tag=b"electrum/ln_multisig_funding_key/we_opened",
|
||||
msg=funding_root_secret + node_id_prefix + nlocktime_bytes,
|
||||
))
|
||||
return Keypair(
|
||||
pubkey=funding_key.get_public_key_bytes(),
|
||||
privkey=funding_key.get_secret_bytes(),
|
||||
)
|
||||
|
||||
|
||||
def derive_multisig_funding_key_if_they_opened(
|
||||
*,
|
||||
funding_root_secret: bytes,
|
||||
remote_node_id_or_prefix: bytes,
|
||||
remote_funding_pubkey: bytes,
|
||||
) -> Keypair:
|
||||
from .lnworker import NODE_ID_PREFIX_LEN
|
||||
assert isinstance(funding_root_secret, bytes)
|
||||
assert len(funding_root_secret) == 32
|
||||
assert isinstance(remote_node_id_or_prefix, bytes)
|
||||
assert len(remote_node_id_or_prefix) in (NODE_ID_PREFIX_LEN, 33)
|
||||
assert isinstance(remote_funding_pubkey, bytes)
|
||||
assert len(remote_funding_pubkey) == 33
|
||||
node_id_prefix = remote_node_id_or_prefix[0:NODE_ID_PREFIX_LEN]
|
||||
funding_key = ecc.ECPrivkey(bitcoin.bip340_tagged_hash(
|
||||
tag=b"electrum/ln_multisig_funding_key/they_opened",
|
||||
msg=funding_root_secret + node_id_prefix + remote_funding_pubkey,
|
||||
))
|
||||
return Keypair(
|
||||
pubkey=funding_key.get_public_key_bytes(),
|
||||
privkey=funding_key.get_secret_bytes(),
|
||||
)
|
||||
|
||||
|
||||
def make_htlc_tx_output(
|
||||
amount_msat,
|
||||
local_feerate,
|
||||
@@ -1693,6 +1749,7 @@ class LnKeyFamily(IntEnum):
|
||||
BACKUP_CIPHER = 7 | BIP32_PRIME
|
||||
PAYMENT_SECRET_KEY = 8 | BIP32_PRIME
|
||||
NOSTR_KEY = 9 | BIP32_PRIME
|
||||
FUNDING_ROOT_KEY = 10 | BIP32_PRIME
|
||||
|
||||
|
||||
def generate_keypair(node: BIP32Node, key_family: LnKeyFamily) -> Keypair:
|
||||
|
||||
@@ -132,7 +132,7 @@ class LNWatcher(Logger, EventListener):
|
||||
if not keep_watching:
|
||||
await self.unwatch_channel(address, funding_outpoint)
|
||||
|
||||
async def sweep_commitment_transaction(self, funding_outpoint, closing_tx) -> bool:
|
||||
async def sweep_commitment_transaction(self, funding_outpoint: str, closing_tx: Transaction) -> bool:
|
||||
raise NotImplementedError() # implemented by subclasses
|
||||
|
||||
async def update_channel_state(self, *, funding_outpoint: str, funding_txid: str,
|
||||
|
||||
@@ -103,6 +103,7 @@ NUM_PEERS_TARGET = 4
|
||||
# onchain channel backup data
|
||||
CB_VERSION = 0
|
||||
CB_MAGIC_BYTES = bytes([0, 0, 0, CB_VERSION])
|
||||
NODE_ID_PREFIX_LEN = 16
|
||||
|
||||
|
||||
FALLBACK_NODE_LIST_TESTNET = (
|
||||
@@ -830,6 +831,7 @@ class LNWallet(LNWorker):
|
||||
self.backup_key = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.BACKUP_CIPHER).privkey
|
||||
self.static_payment_key = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.PAYMENT_BASE)
|
||||
self.payment_secret_key = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.PAYMENT_SECRET_KEY).privkey
|
||||
self.funding_root_keypair = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.FUNDING_ROOT_KEY)
|
||||
Logger.__init__(self)
|
||||
features = LNWALLET_FEATURES
|
||||
if self.config.ENABLE_ANCHOR_CHANNELS:
|
||||
@@ -1352,8 +1354,8 @@ class LNWallet(LNWorker):
|
||||
self.remove_channel(chan.channel_id)
|
||||
raise
|
||||
|
||||
def cb_data(self, node_id):
|
||||
return CB_MAGIC_BYTES + node_id[0:16]
|
||||
def cb_data(self, node_id: bytes) -> bytes:
|
||||
return CB_MAGIC_BYTES + node_id[0:NODE_ID_PREFIX_LEN]
|
||||
|
||||
def decrypt_cb_data(self, encrypted_data, funding_address):
|
||||
funding_scripthash = bytes.fromhex(address_to_scripthash(funding_address))
|
||||
@@ -1373,6 +1375,8 @@ class LNWallet(LNWorker):
|
||||
funding_sat: int,
|
||||
node_id: bytes,
|
||||
fee_est=None) -> PartialTransaction:
|
||||
from .wallet import get_locktime_for_new_transaction
|
||||
|
||||
outputs = [PartialTxOutput.from_address_and_value(DummyAddress.CHANNEL, funding_sat)]
|
||||
if self.has_recoverable_channels():
|
||||
dummy_scriptpubkey = make_op_return(self.cb_data(node_id))
|
||||
@@ -1382,6 +1386,9 @@ class LNWallet(LNWorker):
|
||||
outputs=outputs,
|
||||
fee=fee_est)
|
||||
tx.set_rbf(False)
|
||||
# rm randomness from locktime, as we use the locktime as entropy for deriving the funding_privkey
|
||||
# (and it would be confusing to get a collision as a consequence of the randomness)
|
||||
tx.locktime = get_locktime_for_new_transaction(self.network, include_random_component=False)
|
||||
return tx
|
||||
|
||||
def suggest_funding_amount(self, amount_to_pay, coins):
|
||||
@@ -2960,6 +2967,7 @@ class LNWallet(LNWorker):
|
||||
remote_revocation_pubkey = chan.config[REMOTE].revocation_basepoint.pubkey,
|
||||
remote_payment_pubkey = chan.config[REMOTE].payment_basepoint.pubkey,
|
||||
local_payment_pubkey=chan.config[LOCAL].payment_basepoint.pubkey,
|
||||
multisig_funding_privkey=chan.config[LOCAL].multisig_key.privkey,
|
||||
)
|
||||
|
||||
def export_channel_backup(self, channel_id):
|
||||
@@ -3095,7 +3103,7 @@ class LNWallet(LNWorker):
|
||||
encrypted_data = o2.scriptpubkey[2:]
|
||||
data = self.decrypt_cb_data(encrypted_data, funding_address)
|
||||
if data.startswith(CB_MAGIC_BYTES):
|
||||
node_id_prefix = data[4:]
|
||||
node_id_prefix = data[len(CB_MAGIC_BYTES):]
|
||||
if node_id_prefix is None:
|
||||
return
|
||||
funding_txid = tx.txid()
|
||||
|
||||
@@ -206,7 +206,11 @@ async def sweep(
|
||||
return tx
|
||||
|
||||
|
||||
def get_locktime_for_new_transaction(network: 'Network') -> int:
|
||||
def get_locktime_for_new_transaction(
|
||||
network: 'Network',
|
||||
*,
|
||||
include_random_component: bool = True,
|
||||
) -> int:
|
||||
# if no network or not up to date, just set locktime to zero
|
||||
if not network:
|
||||
return 0
|
||||
@@ -225,8 +229,9 @@ def get_locktime_for_new_transaction(network: 'Network') -> int:
|
||||
locktime = min(chain_height, server_height)
|
||||
# sometimes pick locktime a bit further back, to help privacy
|
||||
# of setups that need more time (offline/multisig/coinjoin/...)
|
||||
if random.randint(0, 9) == 0:
|
||||
locktime = max(0, locktime - random.randint(0, 99))
|
||||
if include_random_component:
|
||||
if random.randint(0, 9) == 0:
|
||||
locktime = max(0, locktime - random.randint(0, 99))
|
||||
locktime = max(0, locktime)
|
||||
return locktime
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ class WalletUnfinished(WalletFileException):
|
||||
# seed_version is now used for the version of the wallet file
|
||||
OLD_SEED_VERSION = 4 # electrum versions < 2.0
|
||||
NEW_SEED_VERSION = 11 # electrum versions >= 2.0
|
||||
FINAL_SEED_VERSION = 59 # electrum >= 2.7 will set this to prevent
|
||||
FINAL_SEED_VERSION = 60 # electrum >= 2.7 will set this to prevent
|
||||
# old versions from overwriting new format
|
||||
|
||||
|
||||
@@ -234,6 +234,7 @@ class WalletDBUpgrader(Logger):
|
||||
self._convert_version_57()
|
||||
self._convert_version_58()
|
||||
self._convert_version_59()
|
||||
self._convert_version_60()
|
||||
self.put('seed_version', FINAL_SEED_VERSION) # just to be sure
|
||||
|
||||
def _convert_wallet_type(self):
|
||||
@@ -1146,6 +1147,15 @@ class WalletDBUpgrader(Logger):
|
||||
self.data['channels'] = channels
|
||||
self.data['seed_version'] = 59
|
||||
|
||||
def _convert_version_60(self):
|
||||
if not self._is_upgrade_method_needed(59, 59):
|
||||
return
|
||||
cbs = self.data.get('imported_channel_backups', {})
|
||||
for channel_id, cb in list(cbs.items()):
|
||||
if 'multisig_funding_privkey' not in cb:
|
||||
cb['multisig_funding_privkey'] = None
|
||||
self.data['seed_version'] = 60
|
||||
|
||||
def _convert_imported(self):
|
||||
if not self._is_upgrade_method_needed(0, 13):
|
||||
return
|
||||
|
||||
@@ -156,6 +156,7 @@ if [[ $1 == "backup" ]]; then
|
||||
echo "alice opens channel"
|
||||
bob_node=$($bob nodeid)
|
||||
channel1=$($alice open_channel $bob_node 0.15 --password='')
|
||||
new_blocks 1 # cannot open multiple chans with same node in same block
|
||||
$alice setconfig use_recoverable_channels False
|
||||
channel2=$($alice open_channel $bob_node 0.15 --password='')
|
||||
new_blocks 3
|
||||
|
||||
@@ -1086,6 +1086,7 @@ class TestLNUtil(ElectrumTestCase):
|
||||
remote_payment_pubkey=bfh('02a1bbc818e2e88847016a93c223eb4adef7bb8becb3709c75c556b6beb3afe7bd'),
|
||||
remote_revocation_pubkey=bfh('022f28b7d8d1f05768ada3df1b0966083b8058e1e7197c57393e302ec118d7f0ae'),
|
||||
local_payment_pubkey=None,
|
||||
multisig_funding_privkey=None,
|
||||
),
|
||||
decoded_cb,
|
||||
)
|
||||
@@ -1113,6 +1114,7 @@ class TestLNUtil(ElectrumTestCase):
|
||||
remote_payment_pubkey=bfh('02a1bbc818e2e88847016a93c223eb4adef7bb8becb3709c75c556b6beb3afe7bd'),
|
||||
remote_revocation_pubkey=bfh('022f28b7d8d1f05768ada3df1b0966083b8058e1e7197c57393e302ec118d7f0ae'),
|
||||
local_payment_pubkey=bfh('0308d686712782a44b0cef220485ad83dae77853a5bf8501a92bb79056c9dcb25a'),
|
||||
multisig_funding_privkey=None,
|
||||
),
|
||||
decoded_cb,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user