1
0

add static payment key

* in order to be able to sweep to_remote in an onchain backup scenario
  we need to retain the private key for the payment_basepoint
* to facilitate the above, we open a channel derived from a static
  secret (tied to the wallet seed), the static_payment_key combined with
the funding pubkey (multisig_key), which we can restore from the channel
closing transaction
This commit is contained in:
bitromortac
2021-09-22 11:12:38 +02:00
committed by ThomasV
parent 3951e07c53
commit 22f5ff0d0e
4 changed files with 43 additions and 8 deletions

View File

@@ -399,7 +399,6 @@ class AbstractChannel(Logger, ABC):
# auto-remove redeemed backups
self.lnworker.remove_channel_backup(self.channel_id)
@abstractmethod
def is_initiator(self) -> bool:
pass
@@ -538,8 +537,13 @@ class ChannelBackup(AbstractChannel):
self.config[LOCAL] = LocalConfig.from_seed(
channel_seed=cb.channel_seed,
to_self_delay=cb.local_delay,
# there are three cases of backups:
# 1. legacy: payment_basepoint will be derived
# 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,
# dummy values
static_payment_key=None,
dust_limit_sat=None,
max_htlc_value_in_flight_msat=None,
max_accepted_htlcs=None,
@@ -635,6 +639,9 @@ class ChannelBackup(AbstractChannel):
def get_sweep_address(self) -> str:
return self.lnworker.wallet.get_new_sweep_address_for_channel()
def has_anchors(self) -> Optional[bool]:
return None
def get_local_pubkey(self) -> bytes:
cb = self.cb
assert isinstance(cb, ChannelBackupStorage)

View File

@@ -681,11 +681,16 @@ class Peer(Logger, EventListener):
# flexibility to decide an address at closing time
upfront_shutdown_script = b''
assert channel_type & channel_type.OPTION_STATIC_REMOTEKEY
wallet = self.lnworker.wallet
assert wallet.txin_type == 'p2wpkh'
addr = wallet.get_new_sweep_address_for_channel()
static_remotekey = bytes.fromhex(wallet.get_public_key(addr))
if self.use_anchors():
static_payment_key = self.lnworker.static_payment_key
static_remotekey = None
else:
assert channel_type & channel_type.OPTION_STATIC_REMOTEKEY
wallet = self.lnworker.wallet
assert wallet.txin_type == 'p2wpkh'
addr = wallet.get_new_sweep_address_for_channel()
static_payment_key = None
static_remotekey = bytes.fromhex(wallet.get_public_key(addr))
dust_limit_sat = bitcoin.DUST_LIMIT_P2PKH
reserve_sat = max(funding_sat // 100, dust_limit_sat)
@@ -696,6 +701,7 @@ class Peer(Logger, EventListener):
local_config = LocalConfig.from_seed(
channel_seed=channel_seed,
static_remotekey=static_remotekey,
static_payment_key=static_payment_key,
upfront_shutdown_script=upfront_shutdown_script,
to_self_delay=self.network.config.LIGHTNING_TO_SELF_DELAY_CSV,
dust_limit_sat=dust_limit_sat,

View File

@@ -212,7 +212,6 @@ class LocalConfig(ChannelConfig):
@classmethod
def from_seed(cls, **kwargs):
channel_seed = kwargs['channel_seed']
static_remotekey = kwargs.pop('static_remotekey')
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
@@ -220,11 +219,23 @@ class LocalConfig(ChannelConfig):
kwargs['htlc_basepoint'] = keypair_generator(LnKeyFamily.HTLC_BASE)
kwargs['delayed_basepoint'] = keypair_generator(LnKeyFamily.DELAY_BASE)
kwargs['revocation_basepoint'] = keypair_generator(LnKeyFamily.REVOCATION_BASE)
if static_remotekey:
static_remotekey = kwargs.pop('static_remotekey')
static_payment_key = kwargs.pop('static_payment_key')
if static_payment_key:
# We derive the payment_basepoint from a static secret (derived from
# the wallet seed) and a public nonce that is revealed
# when the funding transaction is spent. This way we can restore the
# payment_basepoint, needed for sweeping in the event of a force close.
kwargs['payment_basepoint'] = derive_payment_basepoint(
static_payment_secret=static_payment_key.privkey,
funding_pubkey=kwargs['multisig_key'].pubkey
)
elif static_remotekey: # we automatically sweep to a wallet address
kwargs['payment_basepoint'] = OnlyPubkeyKeypair(static_remotekey)
else:
# we expect all our channels to use option_static_remotekey, so ending up here likely indicates an issue...
kwargs['payment_basepoint'] = keypair_generator(LnKeyFamily.PAYMENT_BASE)
return LocalConfig(**kwargs)
def validate_params(self, *, funding_sat: int, config: 'SimpleConfig', peer_features: 'LnFeatures') -> None:
@@ -586,6 +597,16 @@ def derive_blinded_privkey(basepoint_secret: bytes, per_commitment_secret: bytes
return int.to_bytes(sum, length=32, byteorder='big', signed=False)
def derive_payment_basepoint(static_payment_secret: bytes, funding_pubkey: bytes) -> Keypair:
assert isinstance(static_payment_secret, bytes)
assert isinstance(funding_pubkey, bytes)
payment_basepoint = ecc.ECPrivkey(sha256(static_payment_secret + funding_pubkey))
return Keypair(
pubkey=payment_basepoint.get_public_key_bytes(),
privkey=payment_basepoint.get_secret_bytes()
)
def make_htlc_tx_output(
amount_msat, local_feerate, revocationpubkey, local_delayedpubkey, success, to_self_delay,
) -> Tuple[bytes, PartialTxOutput]:

View File

@@ -829,6 +829,7 @@ class LNWallet(LNWorker):
self.db = wallet.db
self.node_keypair = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.NODE_KEY)
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
Logger.__init__(self)
features = LNWALLET_FEATURES