1
0

backups: restore from closing tx, sweep to_remote

* add a method for backups to sweep to_remote
* to_remote sweeping needs the payment_basepoint's private key
  to sign the sweep transaction
* we restore the private key from our funding multisig pubkey
  (pubished with the closing transaction) and a static payment key secret
This commit is contained in:
bitromortac
2021-09-15 09:47:58 +02:00
committed by ThomasV
parent f746e2cc6b
commit 3a3f5059b4
3 changed files with 89 additions and 9 deletions

View File

@@ -56,9 +56,10 @@ from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKey
ShortChannelID, map_htlcs_to_ctx_output_idxs,
fee_for_htlc_output, offered_htlc_trim_threshold_sat,
received_htlc_trim_threshold_sat, make_commitment_output_to_remote_address, FIXED_ANCHOR_SAT,
ChannelType, LNProtocolWarning)
ChannelType, LNProtocolWarning, ctx_has_anchors)
from .lnsweep import create_sweeptxs_for_our_ctx, create_sweeptxs_for_their_ctx
from .lnsweep import create_sweeptx_for_their_revoked_htlc, SweepInfo
from .lnsweep import create_sweeptx_their_backup_ctx
from .lnhtlc import HTLCManager
from .lnmsg import encode_msg, decode_msg
from .address_synchronizer import TX_HEIGHT_LOCAL
@@ -594,14 +595,19 @@ class ChannelBackup(AbstractChannel):
return True
def create_sweeptxs_for_their_ctx(self, ctx):
return {}
return create_sweeptx_their_backup_ctx(chan=self, ctx=ctx, sweep_address=self.get_sweep_address())
def create_sweeptxs_for_our_ctx(self, ctx):
if self.is_imported:
return create_sweeptxs_for_our_ctx(chan=self, ctx=ctx, sweep_address=self.get_sweep_address())
else:
# backup from op_return
return {}
return
def maybe_sweep_revoked_htlc(self, ctx: Transaction, htlc_tx: Transaction) -> Optional[SweepInfo]:
return None
def extract_preimage_from_htlc_txin(self, txin: TxInput) -> None:
return None
def get_funding_address(self):
return self.cb.funding_address

View File

@@ -18,14 +18,15 @@ from .lnutil import (make_commitment_output_to_remote_address, make_commitment_o
LOCAL, REMOTE, make_htlc_output_witness_script,
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)
from .transaction import (Transaction, TxOutput, PartialTransaction, PartialTxInput,
PartialTxOutput, TxOutpoint)
map_htlcs_to_ctx_output_idxs, Direction, make_commitment_output_to_remote_witness_script,
derive_payment_basepoint, ctx_has_anchors, SCRIPT_TEMPLATE_FUNDING)
from .transaction import (Transaction, TxInput, PartialTransaction, PartialTxInput,
PartialTxOutput, TxOutpoint, script_GetOp, match_script_against_template)
from .simple_config import SimpleConfig
from .logging import get_logger, Logger
if TYPE_CHECKING:
from .lnchannel import Channel, AbstractChannel
from .lnchannel import Channel, AbstractChannel, ChannelBackup
_logger = get_logger(__name__)
@@ -352,6 +353,69 @@ def extract_ctx_secrets(chan: 'Channel', ctx: Transaction):
return ctn, their_pcp, is_revocation, per_commitment_secret
def extract_funding_pubkeys_from_ctx(txin: TxInput) -> Tuple[bytes, bytes]:
"""Extract the two funding pubkeys from the published commitment transaction.
We expect to see a witness script of: OP_2 pk1 pk2 OP_2 OP_CHECKMULTISIG"""
elements = txin.witness_elements()
witness_script = elements[-1]
assert match_script_against_template(witness_script, SCRIPT_TEMPLATE_FUNDING)
parsed_script = [x for x in script_GetOp(witness_script)]
pubkey1 = parsed_script[1][1]
pubkey2 = parsed_script[2][1]
return (pubkey1, pubkey2)
def create_sweeptx_their_backup_ctx(
*, chan: 'ChannelBackup',
ctx: Transaction,
sweep_address: str) -> 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."""
if ctx_has_anchors(ctx):
# for anchors we need to sweep to_remote
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:
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):
our_payment_pubkey = candidate_basepoint
to_remote_address = candidate_to_remote_address
_logger.debug(f'found funding pubkey')
break
else:
return
else:
# we are dealing with static_remotekey which is locked to a wallet address
return {}
# to_remote
csv_delay = 1
our_payment_privkey = ecc.ECPrivkey(our_payment_pubkey.privkey)
output_idxs = ctx.get_output_idxs_from_address(to_remote_address)
if output_idxs:
output_idx = output_idxs.pop()
prevout = ctx.txid() + ':%d' % output_idx
sweep_tx = lambda: create_sweeptx_their_ctx_to_remote(
sweep_address=sweep_address,
ctx=ctx,
output_idx=output_idx,
our_payment_privkey=our_payment_privkey,
config=chan.lnworker.config,
has_anchors=True
)
txs[prevout] = SweepInfo(
name='their_ctx_to_remote_backup',
csv_delay=csv_delay,
cltv_abs=0,
gen_tx=sweep_tx)
return txs
def create_sweeptxs_for_their_ctx(
*, chan: 'Channel',
ctx: Transaction,

View File

@@ -21,7 +21,7 @@ from .util import format_short_id as format_short_channel_id
from .crypto import sha256, pw_decode_with_version_and_mac
from .transaction import (Transaction, PartialTransaction, PartialTxInput, TxOutpoint,
PartialTxOutput, opcodes, TxOutput)
PartialTxOutput, opcodes, TxOutput, OPPushDataPubkey)
from . import bitcoin, crypto, transaction
from . import descriptor
from .bitcoin import (redeem_script_to_address, address_to_script,
@@ -57,6 +57,8 @@ FIXED_ANCHOR_SAT = 330
LN_MAX_FUNDING_SAT_LEGACY = pow(2, 24) - 1
DUST_LIMIT_MAX = 1000
SCRIPT_TEMPLATE_FUNDING = [opcodes.OP_2, OPPushDataPubkey, OPPushDataPubkey, opcodes.OP_2, opcodes.OP_CHECKMULTISIG]
from .json_db import StoredObject, stored_in, stored_as
@@ -1264,6 +1266,14 @@ def extract_ctn_from_tx_and_chan(tx: Transaction, chan: 'AbstractChannel') -> in
funder_payment_basepoint=funder_conf.payment_basepoint.pubkey,
fundee_payment_basepoint=fundee_conf.payment_basepoint.pubkey)
def ctx_has_anchors(tx: Transaction):
output_values = [output.value for output in tx.outputs()]
if FIXED_ANCHOR_SAT in output_values:
return True
else:
return False
class LnFeatureContexts(enum.Flag):
INIT = enum.auto()