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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user