Skip to content

Conversation

TheBlueMatt
Copy link
Collaborator

The `remote_key` derived by default in `KeysManager` depends on the
chanel's `channel_keys_id`, which generally has sufficient entropy
that without it the `remote_key` cannot be re-derived. In disaster
case where there is no remaining state except the `KeysManager`'s
`seed`, this results in lost funds, even if the counterparty
force-closes the channel.

Luckily, because of the `static_remote_key` feature, there's no
need for this. If the `remote_key` we derive is one of a countable
set, we can simply scan the chain for outputs to our `remote_key`s.

Here we finally allow users to opt into the new derivation scheme,
using the new derivation scheme for `remote_key`s for new and
spliced channels if a new `KeysManager::new` argument is set to
`true`.

Needs an upgrade test, but otherwise should be good.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Sep 24, 2025

🎉 This PR is now ready for review!
Please choose at least one reviewer by assigning them on the right bar.
If no reviewers are assigned within 10 minutes, I'll automatically assign one.
Once the first reviewer has submitted a review, a second will be assigned if required.

@tnull tnull self-requested a review September 24, 2025 14:48
Copy link

codecov bot commented Sep 24, 2025

Codecov Report

❌ Patch coverage is 96.02273% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.54%. Comparing base (7926fb9) to head (9871b97).

Files with missing lines Patch % Lines
lightning/src/sign/mod.rs 94.91% 6 Missing ⚠️
lightning-dns-resolver/src/lib.rs 66.66% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4117   +/-   ##
=======================================
  Coverage   88.54%   88.54%           
=======================================
  Files         179      179           
  Lines      134333   134408   +75     
  Branches   134333   134408   +75     
=======================================
+ Hits       118939   119015   +76     
- Misses      12634    12640    +6     
+ Partials     2760     2753    -7     
Flag Coverage Δ
fuzzing 21.76% <30.82%> (-0.03%) ⬇️
tests 88.38% <96.02%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The `remote_key` derived by default in `KeysManager` depends on the
chanel's `channel_keys_id`, which generally has sufficient entropy
that without it the `remote_key` cannot be re-derived. In disaster
case where there is no remaining state except the `KeysManager`'s
`seed`, this results in lost funds, even if the counterparty
force-closes the channel.

Luckily, because of the `static_remote_key` feature, there's no
need for this. If the `remote_key` we derive is one of a countable
set, we can simply scan the chain for outputs to our `remote_key`s.

Here we set up such new derivation, adding logic to derive one of
1000 possible `remote_key`s (which translates to 2000 potential
`script_pubkey`s on chain). We also update the spending code to
check which of the two derivation formats where used and sign with
the correct key.
The `remote_key` derived by default in `KeysManager` depends on the
chanel's `channel_keys_id`, which generally has sufficient entropy
that without it the `remote_key` cannot be re-derived. In disaster
case where there is no remaining state except the `KeysManager`'s
`seed`, this results in lost funds, even if the counterparty
force-closes the channel.

Luckily, because of the `static_remote_key` feature, there's no
need for this. If the `remote_key` we derive is one of a countable
set, we can simply scan the chain for outputs to our `remote_key`s.

In the next commit, we'll start using different `remote_key`s based
on a config knob the user sets, but with the current
`ChannelSigner::pubkeys` API this would be invalid - we can't
return a different set of keys for a re-derived `ChannelSigner`.
Luckily, this isn't actually how LDK uses `ChannelSigner::pubkeys`,
it actually only calls it when it wants a new set of pubkeys,
either for a new channel or a splice.

Thus, here, we rename `ChannelSigner::pubkeys` to
`ChannelSigner::new_pubkeys` and update documentation to match.
@TheBlueMatt TheBlueMatt force-pushed the 2025-09-actually-static-remote_key branch from b8179e0 to 6fc6072 Compare September 25, 2025 00:21
@TheBlueMatt TheBlueMatt marked this pull request as ready for review September 25, 2025 00:21
The `remote_key` derived by default in `KeysManager` depends on the
chanel's `channel_keys_id`, which generally has sufficient entropy
that without it the `remote_key` cannot be re-derived. In disaster
case where there is no remaining state except the `KeysManager`'s
`seed`, this results in lost funds, even if the counterparty
force-closes the channel.

Luckily, because of the `static_remote_key` feature, there's no
need for this. If the `remote_key` we derive is one of a countable
set, we can simply scan the chain for outputs to our `remote_key`s.

Here we finally allow users to opt into the new derivation scheme,
using the new derivation scheme for `remote_key`s for new and
spliced channels if a new `KeysManager::new` argument is set to
`true`.
In the previous commit we (finally) allowed users to opt into a
static `remote_key` derivation scheme, enabling them to scan the
chain for funds on counterparty commitment transactions without any
state at all.

This is only possible, however, of course, if they have the full
list of scripts to scan the chain for, which we expose here.
@TheBlueMatt TheBlueMatt force-pushed the 2025-09-actually-static-remote_key branch from 6fc6072 to 9871b97 Compare September 25, 2025 00:44
Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a first pass, makes sense to me.

I do however wonder if we still see the chance of making this an interface, so that users could have the funds getting spent directly to their on-chain wallet.

pub(crate) const P2TR_KEY_PATH_WITNESS_WEIGHT: u64 = 1 /* witness items */
+ 1 /* schnorr sig len */ + 64 /* schnorr sig */;

/// If a [`KeysManager`] is built with [`KeysManager::new`] with `v2_remote_key_derivation` set,
Copy link
Contributor

@tnull tnull Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option we discussed was to create an interface for remote-key-derivation so that users could override this and have the funds spent directly to their on-chain wallet. That would save on a sweep transaction, the separate chain scanning, and would provide substantially more privacy even. Do you think this approach would still be feasible? Would this PR make it harder to go for that approach if that's our end goal?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No we should still do that, but I don't think when we do that we'll drop this logic - we'll still want a default key derivation, I assume?

funding_key: SecretKey, revocation_base_key: SecretKey, payment_key_v1: SecretKey,
payment_key_v2: SecretKey, delayed_payment_base_key: SecretKey, htlc_base_key: SecretKey,
commitment_seed: [u8; 32], channel_keys_id: [u8; 32], rand_bytes_unique_start: [u8; 32],
payment_key_v2: SecretKey, v2_remote_key_derivation: bool,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what circumstances wouldn't users want to set v2_remote_key_derivation, given that it only applies to new/spliced channels? Do we even need that flag, or should this always be true?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It breaks downgrade.

/// If `v2_remote_key_derivation` is set, the `script_pubkey`s which receive funds on-chain when
/// our counterparty force-closes will be one of a static set of [`STATIC_PAYMENT_KEY_COUNT`]*2
/// possible `script_pubkey`s. This only applies to new or spliced channels, however if this is
/// set you *MUST NOT* downgrade to a version of LDK prior to 0.2.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we somehow persist something that fails the downgrade to enforce users won't ever attempt to?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I'd love to, but its not clear to me how - none of the keys stuff gets persisted and doing some crazy dance to (a) detect a default KeysManager and then (b) persist a upgrade-breaking flag in ChannelMonitor seems too hacky to be worth it.

/// Only channels opened or spliced when using a [`KeysManager`] with the
/// `v2_remote_key_derivation` argument to [`KeysManager::new`] will close to such scripts,
/// other channels will close to a randomly-generated `script_pubkey`.
pub fn possible_v2_counterparty_closed_balance_spks<C: Signing>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we expect this API to be used in practice? Seems like a manual step won't get us far? Do we need to watch the chain for the user and generate SpendableOutput events?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are recovering from a disaster data loss scenario in a wallet with some kind of sync like electrum, I think you'll need this to register the scripts to watch? In the no-loss case no this should not be used.

@ldk-reviews-bot
Copy link

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

@TheBlueMatt
Copy link
Collaborator Author

I do however wonder if we still see the chance of making this an interface, so that users could have the funds getting spent directly to their on-chain wallet.

Yea, we should definitely still consider redoing the keys interface so that this key is fetched separate from the normal channel keys to make it easier to override. Of course users should be able to override it no problem already, but making it easier would be good.

@TheBlueMatt
Copy link
Collaborator Author

It might be worth noting that with anchor channels you can't just trivially use a regular on-chain wallet key - the output is actually a P2WSH of the key and CSV 1, so I think we shouldn't just fully require the user give us a key.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants