-
Notifications
You must be signed in to change notification settings - Fork 419
Allow KeysManager
to opt-into the new remote_key
derivation
#4117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Allow KeysManager
to opt-into the new remote_key
derivation
#4117
Conversation
🎉 This PR is now ready for review! |
Codecov Report❌ Patch coverage is
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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
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.
b8179e0
to
6fc6072
Compare
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.
6fc6072
to
9871b97
Compare
There was a problem hiding this 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, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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>( |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
👋 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. |
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. |
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. |
Needs an upgrade test, but otherwise should be good.