[Key derivation V2] Attach a version byte to channel_keys_id - #3887

Closed
tankyleo wants to merge 1 commit into
lightningdevkit:mainfrom
tankyleo:channel-keys-version
Closed

[Key derivation V2] Attach a version byte to channel_keys_id#3887
tankyleo wants to merge 1 commit into
lightningdevkit:mainfrom
tankyleo:channel-keys-version

Conversation

@tankyleo

@tankyleotankyleo commented Jun 24, 2025

Copy link
Copy Markdown
Contributor

Early, broken draft of a PR that would allow us to understand which key derivation we used for a particular channel_keys_id.

After this PR, we would ship a new derivation for to_remote outputs such that they may be recovered solely from the seed in case of loss of all channel state (including the channel_keys_id of the channel - which currently is required to derive the to_remote key).

An alternative approach could assume a certain byte in channel_keys_id has never been used, and we would rely on that byte to understand which key derivation to use. I've heard this assumption would break some downstream users, so pushed this approach instead.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 24, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems this already needs a rebase :)

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Concept ACK.

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

It might be good to include the actual v2 derivation in this PR, too, as it would be critical to see whether we can do the derivation in a backwards compatible manner everywhere. Also, would we necessarily break forwards compat everywhere with this?

Speaking off, please note that newly introduced fields need to have odd numbers to not automatically break forwards compatibility, as LDK follows the 'it's okay to be odd' rule for its serialization. This means that nodes would panic on downgrade if they encountered an even field that they aren't expecting.

(0, outpoint, required),
(1, channel_keys_id, option),
(2, output, required),
(4, channel_keys_version, (default_value, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This needs to be odd, as otherwise we'd break downgrades.

(15, counterparty_fulfilled_htlcs, option),
(17, initial_counterparty_commitment_info, option),
(19, channel_id, option),
(20, channel_keys_version, (default_value, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here, needs to be odd.

(10, self.channel_keys_id.id, required),
(12, self.channel_value_satoshis, required),
(13, self.channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
(14, self.channel_keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same, here, new field needs to be odd to not break downgrades.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I was hoping with 'no_write_default' to allow backwards compatibility iff the version is set to 0. I may be missing something let me know.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, ah, not sure if we'd want to add another serialization path just for this. Maybe it would be easier to make the version an Option<u8>? This would also make the behavior explicit and you could actually do manual migration steps depending on whether it's set (without leaning on a magic '0' value).

@tankyleotankyleoJun 24, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

If I recall correctly, for a None we would still write the even type, and make it impossible to downgrade ? Will check tomorrow :)

Or actually yes we could make it Option together with the option field type (not required)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It does seem like its a nicer API if we always have the version, vs having an Optional version, no? I guess as a new field it is kinda an Option... FWIW, I don't think we have to break the reads out nor add a new write type here, eg the following. I guess I don't have a strong opinion on option-or-not.

+impl_writeable_tlv_based!(DelayedPaymentOutputDescriptor, {
+ (0, outpoint, required),
+ (2, per_commitment_point, required),
+ (4, to_self_delay, required),
+ (6, output, required),
+ (8, revocation_pubkey, required),
+ (10, channel_keys_id_id, (legacy, [u8; 32], |obj: &DelayedPaymentOutputDescriptor| Some(obj.channel_keys_id.id))),
+ (12, channel_value_satoshis, required),
+ (13, channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
+ (14, channel_keys_id_vers, (legacy, u8, |obj: &DelayedPaymentOutputDescriptor| Some(obj.channel_keys_id.version))),
+ (x, channel_keys_id, (static_value, ChannelKeysId {
+ id: channel_keys_id_id.ok_or(DecodeError::InvalidValue)?,
+ version: channel_keys_id_vers.ok_or(DecodeError::InvalidValue)?,
+ })),
+});

(4, self.channel_keys_id.id, required),
(6, self.channel_value_satoshis, required),
(7, self.channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
(8, self.channel_keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here.

(0, self.value_satoshis, required),
(2, self.keys_id.id, required),
(4, self.transaction_parameters, (required: ReadableArgs, Some(value_satoshis.0.unwrap()))),
(6, self.keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here.

@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Concept ACK.

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

It might be good to include the actual v2 derivation in this PR, too, as it would be critical to see whether we can do the derivation in a backwards compatible manner everywhere. Also, would we necessarily break forwards compat everywhere with this?

Speaking off, please note that newly introduced fields need to have odd numbers to not automatically break forwards compatibility, as LDK follows the 'it's okay to be odd' rule for its serialization. This means that nodes would panic on downgrade if they encountered an even field that they aren't expecting.

Overall the idea is to allow backwards compat for version 0 and no backwards compat for anything else. For forward compat, when the field is not set, we assume it is 0.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This unfortunately already needs a rebase.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

Indeed, I remain not a huge fan of the version approach - the channel keys id is a public thing that isn't restricted to just our KeysManager (even if most nodes probably do use our KeysManager), so changing the public API because of a limitation of our KeysManager feels incredibly weird - what is the "version" for a SignerProvider that doesn't need it?

In our KeysManager, on the other hand, we do have room for a few extra bytes...currently the first 4 bytes are a channel-open counter since start. Defining the top byte being 0xff as "version two derivation" seems perfectly reasonable to me, I'm really quite confident no one has opened 4 billion channels in a single LDK instance without restarting :).

@tnull

tnull commented Jun 27, 2025

Copy link
Copy Markdown
Contributor

In our KeysManager, on the other hand, we do have room for a few extra bytes...currently the first 4 bytes are a channel-open counter since start.

Although note we previously established that we can’t go that way either as some of our users deployed a custom channel_keys_id derivation, ie, we can’t lean on the first few bytes being available/unset.

@TheBlueMatt

TheBlueMatt commented Jul 3, 2025

Copy link
Copy Markdown
Collaborator

Ah, I'd forgotten that (also, why on earth did anyone do that?!). So, then, yea, I guess a version byte is the only real option? Was there any other option? (discussion on the previous PR seems to hint that we had some other path to explore)

@tnull

tnull commented Jul 3, 2025

Copy link
Copy Markdown
Contributor

Ah, I'd forgotten that (also, why on earth did anyone do that?!). So, then, yea, I guess a version byte is the only real option? Was there any other option? (discussion on the previous PR seems to hint that we had some other path to explore)

Well, one third option we discussed was to always derive both variants and always just check which one to use, i.e., never set an explicit version anywhere. I do however dislike that approach, if we can avoid it, also since introducing a version does allow us to change the derivation scheme in the future without facing the same challenges again.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Ah, I mean hopefully we don't have to do that if we are more careful about the derivation scheme now? There's pretty little overhead to the extra derivation on construction and equality checks, so it shouldn't be too bad...

@ldk-reviews-bot

Copy link
Copy Markdown

Hi @tankyleo,

Thanks for your contributions to rust-lightning!

After too many struggles with bugs, outages, contributor bans, and, finally, a multi-week CI ban, the rust-lightning project is moving off of GitHub for day-to-day development.

You can still file issues and access the git tree here, but PRs will now take place exclusively at https://git.rust-bitcoin.org/. As such, this PR has been migrated to https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/3887

If you log in using GitHub (or otherwise link your GitHub account from https://git.rust-bitcoin.org/user/settings/security), ownership of your PRs, issues, and comments will automatically transfer. To push updates to this PR, you'll need to use git push git@gitea-ssh.bitcoin.ninja:lightningdevkit/rust-lightning YOUR_LOCAL_COMMIT_OR_BRANCH:tankyleo/channel-keys-version. This may require a permissions change - if it doesn't work initially just leave a comment and we'll get you access.

Sign up for freeto 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.

4 participants

@tankyleo@ldk-reviews-bot@tnull@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[Key derivation V2] Attach a version byte to channel_keys_id - #3887

Closed
tankyleo wants to merge 1 commit into
lightningdevkit:mainfrom
tankyleo:channel-keys-version
Closed

[Key derivation V2] Attach a version byte to channel_keys_id#3887
tankyleo wants to merge 1 commit into
lightningdevkit:mainfrom
tankyleo:channel-keys-version

Conversation

@tankyleo

@tankyleotankyleo commented Jun 24, 2025

Copy link
Copy Markdown
Contributor

Early, broken draft of a PR that would allow us to understand which key derivation we used for a particular channel_keys_id.

After this PR, we would ship a new derivation for to_remote outputs such that they may be recovered solely from the seed in case of loss of all channel state (including the channel_keys_id of the channel - which currently is required to derive the to_remote key).

An alternative approach could assume a certain byte in channel_keys_id has never been used, and we would rely on that byte to understand which key derivation to use. I've heard this assumption would break some downstream users, so pushed this approach instead.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 24, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems this already needs a rebase :)

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Concept ACK.

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

It might be good to include the actual v2 derivation in this PR, too, as it would be critical to see whether we can do the derivation in a backwards compatible manner everywhere. Also, would we necessarily break forwards compat everywhere with this?

Speaking off, please note that newly introduced fields need to have odd numbers to not automatically break forwards compatibility, as LDK follows the 'it's okay to be odd' rule for its serialization. This means that nodes would panic on downgrade if they encountered an even field that they aren't expecting.

(0, outpoint, required),
(1, channel_keys_id, option),
(2, output, required),
(4, channel_keys_version, (default_value, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This needs to be odd, as otherwise we'd break downgrades.

(15, counterparty_fulfilled_htlcs, option),
(17, initial_counterparty_commitment_info, option),
(19, channel_id, option),
(20, channel_keys_version, (default_value, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here, needs to be odd.

(10, self.channel_keys_id.id, required),
(12, self.channel_value_satoshis, required),
(13, self.channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
(14, self.channel_keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same, here, new field needs to be odd to not break downgrades.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I was hoping with 'no_write_default' to allow backwards compatibility iff the version is set to 0. I may be missing something let me know.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, ah, not sure if we'd want to add another serialization path just for this. Maybe it would be easier to make the version an Option<u8>? This would also make the behavior explicit and you could actually do manual migration steps depending on whether it's set (without leaning on a magic '0' value).

@tankyleotankyleoJun 24, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

If I recall correctly, for a None we would still write the even type, and make it impossible to downgrade ? Will check tomorrow :)

Or actually yes we could make it Option together with the option field type (not required)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It does seem like its a nicer API if we always have the version, vs having an Optional version, no? I guess as a new field it is kinda an Option... FWIW, I don't think we have to break the reads out nor add a new write type here, eg the following. I guess I don't have a strong opinion on option-or-not.

+impl_writeable_tlv_based!(DelayedPaymentOutputDescriptor, {
+ (0, outpoint, required),
+ (2, per_commitment_point, required),
+ (4, to_self_delay, required),
+ (6, output, required),
+ (8, revocation_pubkey, required),
+ (10, channel_keys_id_id, (legacy, [u8; 32], |obj: &DelayedPaymentOutputDescriptor| Some(obj.channel_keys_id.id))),
+ (12, channel_value_satoshis, required),
+ (13, channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
+ (14, channel_keys_id_vers, (legacy, u8, |obj: &DelayedPaymentOutputDescriptor| Some(obj.channel_keys_id.version))),
+ (x, channel_keys_id, (static_value, ChannelKeysId {
+ id: channel_keys_id_id.ok_or(DecodeError::InvalidValue)?,
+ version: channel_keys_id_vers.ok_or(DecodeError::InvalidValue)?,
+ })),
+});

(4, self.channel_keys_id.id, required),
(6, self.channel_value_satoshis, required),
(7, self.channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
(8, self.channel_keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here.

(0, self.value_satoshis, required),
(2, self.keys_id.id, required),
(4, self.transaction_parameters, (required: ReadableArgs, Some(value_satoshis.0.unwrap()))),
(6, self.keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here.

@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Concept ACK.

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

It might be good to include the actual v2 derivation in this PR, too, as it would be critical to see whether we can do the derivation in a backwards compatible manner everywhere. Also, would we necessarily break forwards compat everywhere with this?

Speaking off, please note that newly introduced fields need to have odd numbers to not automatically break forwards compatibility, as LDK follows the 'it's okay to be odd' rule for its serialization. This means that nodes would panic on downgrade if they encountered an even field that they aren't expecting.

Overall the idea is to allow backwards compat for version 0 and no backwards compat for anything else. For forward compat, when the field is not set, we assume it is 0.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This unfortunately already needs a rebase.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

Indeed, I remain not a huge fan of the version approach - the channel keys id is a public thing that isn't restricted to just our KeysManager (even if most nodes probably do use our KeysManager), so changing the public API because of a limitation of our KeysManager feels incredibly weird - what is the "version" for a SignerProvider that doesn't need it?

In our KeysManager, on the other hand, we do have room for a few extra bytes...currently the first 4 bytes are a channel-open counter since start. Defining the top byte being 0xff as "version two derivation" seems perfectly reasonable to me, I'm really quite confident no one has opened 4 billion channels in a single LDK instance without restarting :).

@tnull

tnull commented Jun 27, 2025

Copy link
Copy Markdown
Contributor

In our KeysManager, on the other hand, we do have room for a few extra bytes...currently the first 4 bytes are a channel-open counter since start.

Although note we previously established that we can’t go that way either as some of our users deployed a custom channel_keys_id derivation, ie, we can’t lean on the first few bytes being available/unset.

@TheBlueMatt

TheBlueMatt commented Jul 3, 2025

Copy link
Copy Markdown
Collaborator

Ah, I'd forgotten that (also, why on earth did anyone do that?!). So, then, yea, I guess a version byte is the only real option? Was there any other option? (discussion on the previous PR seems to hint that we had some other path to explore)

@tnull

tnull commented Jul 3, 2025

Copy link
Copy Markdown
Contributor

Ah, I'd forgotten that (also, why on earth did anyone do that?!). So, then, yea, I guess a version byte is the only real option? Was there any other option? (discussion on the previous PR seems to hint that we had some other path to explore)

Well, one third option we discussed was to always derive both variants and always just check which one to use, i.e., never set an explicit version anywhere. I do however dislike that approach, if we can avoid it, also since introducing a version does allow us to change the derivation scheme in the future without facing the same challenges again.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Ah, I mean hopefully we don't have to do that if we are more careful about the derivation scheme now? There's pretty little overhead to the extra derivation on construction and equality checks, so it shouldn't be too bad...

@ldk-reviews-bot

Copy link
Copy Markdown

Hi @tankyleo,

Thanks for your contributions to rust-lightning!

After too many struggles with bugs, outages, contributor bans, and, finally, a multi-week CI ban, the rust-lightning project is moving off of GitHub for day-to-day development.

You can still file issues and access the git tree here, but PRs will now take place exclusively at https://git.rust-bitcoin.org/. As such, this PR has been migrated to https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/3887

If you log in using GitHub (or otherwise link your GitHub account from https://git.rust-bitcoin.org/user/settings/security), ownership of your PRs, issues, and comments will automatically transfer. To push updates to this PR, you'll need to use git push git@gitea-ssh.bitcoin.ninja:lightningdevkit/rust-lightning YOUR_LOCAL_COMMIT_OR_BRANCH:tankyleo/channel-keys-version. This may require a permissions change - if it doesn't work initially just leave a comment and we'll get you access.

Sign up for freeto 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.

4 participants

@tankyleo@ldk-reviews-bot@tnull@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[Key derivation V2] Attach a version byte to channel_keys_id - #3887

Closed
tankyleo wants to merge 1 commit into
lightningdevkit:mainfrom
tankyleo:channel-keys-version
Closed

[Key derivation V2] Attach a version byte to channel_keys_id#3887
tankyleo wants to merge 1 commit into
lightningdevkit:mainfrom
tankyleo:channel-keys-version

Conversation

@tankyleo

@tankyleotankyleo commented Jun 24, 2025

Copy link
Copy Markdown
Contributor

Early, broken draft of a PR that would allow us to understand which key derivation we used for a particular channel_keys_id.

After this PR, we would ship a new derivation for to_remote outputs such that they may be recovered solely from the seed in case of loss of all channel state (including the channel_keys_id of the channel - which currently is required to derive the to_remote key).

An alternative approach could assume a certain byte in channel_keys_id has never been used, and we would rely on that byte to understand which key derivation to use. I've heard this assumption would break some downstream users, so pushed this approach instead.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 24, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems this already needs a rebase :)

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Concept ACK.

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

It might be good to include the actual v2 derivation in this PR, too, as it would be critical to see whether we can do the derivation in a backwards compatible manner everywhere. Also, would we necessarily break forwards compat everywhere with this?

Speaking off, please note that newly introduced fields need to have odd numbers to not automatically break forwards compatibility, as LDK follows the 'it's okay to be odd' rule for its serialization. This means that nodes would panic on downgrade if they encountered an even field that they aren't expecting.

(0, outpoint, required),
(1, channel_keys_id, option),
(2, output, required),
(4, channel_keys_version, (default_value, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This needs to be odd, as otherwise we'd break downgrades.

(15, counterparty_fulfilled_htlcs, option),
(17, initial_counterparty_commitment_info, option),
(19, channel_id, option),
(20, channel_keys_version, (default_value, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here, needs to be odd.

(10, self.channel_keys_id.id, required),
(12, self.channel_value_satoshis, required),
(13, self.channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
(14, self.channel_keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same, here, new field needs to be odd to not break downgrades.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I was hoping with 'no_write_default' to allow backwards compatibility iff the version is set to 0. I may be missing something let me know.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, ah, not sure if we'd want to add another serialization path just for this. Maybe it would be easier to make the version an Option<u8>? This would also make the behavior explicit and you could actually do manual migration steps depending on whether it's set (without leaning on a magic '0' value).

@tankyleotankyleoJun 24, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

If I recall correctly, for a None we would still write the even type, and make it impossible to downgrade ? Will check tomorrow :)

Or actually yes we could make it Option together with the option field type (not required)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It does seem like its a nicer API if we always have the version, vs having an Optional version, no? I guess as a new field it is kinda an Option... FWIW, I don't think we have to break the reads out nor add a new write type here, eg the following. I guess I don't have a strong opinion on option-or-not.

+impl_writeable_tlv_based!(DelayedPaymentOutputDescriptor, {
+ (0, outpoint, required),
+ (2, per_commitment_point, required),
+ (4, to_self_delay, required),
+ (6, output, required),
+ (8, revocation_pubkey, required),
+ (10, channel_keys_id_id, (legacy, [u8; 32], |obj: &DelayedPaymentOutputDescriptor| Some(obj.channel_keys_id.id))),
+ (12, channel_value_satoshis, required),
+ (13, channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
+ (14, channel_keys_id_vers, (legacy, u8, |obj: &DelayedPaymentOutputDescriptor| Some(obj.channel_keys_id.version))),
+ (x, channel_keys_id, (static_value, ChannelKeysId {
+ id: channel_keys_id_id.ok_or(DecodeError::InvalidValue)?,
+ version: channel_keys_id_vers.ok_or(DecodeError::InvalidValue)?,
+ })),
+});

(4, self.channel_keys_id.id, required),
(6, self.channel_value_satoshis, required),
(7, self.channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
(8, self.channel_keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here.

(0, self.value_satoshis, required),
(2, self.keys_id.id, required),
(4, self.transaction_parameters, (required: ReadableArgs, Some(value_satoshis.0.unwrap()))),
(6, self.keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here.

@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Concept ACK.

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

It might be good to include the actual v2 derivation in this PR, too, as it would be critical to see whether we can do the derivation in a backwards compatible manner everywhere. Also, would we necessarily break forwards compat everywhere with this?

Speaking off, please note that newly introduced fields need to have odd numbers to not automatically break forwards compatibility, as LDK follows the 'it's okay to be odd' rule for its serialization. This means that nodes would panic on downgrade if they encountered an even field that they aren't expecting.

Overall the idea is to allow backwards compat for version 0 and no backwards compat for anything else. For forward compat, when the field is not set, we assume it is 0.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This unfortunately already needs a rebase.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

Indeed, I remain not a huge fan of the version approach - the channel keys id is a public thing that isn't restricted to just our KeysManager (even if most nodes probably do use our KeysManager), so changing the public API because of a limitation of our KeysManager feels incredibly weird - what is the "version" for a SignerProvider that doesn't need it?

In our KeysManager, on the other hand, we do have room for a few extra bytes...currently the first 4 bytes are a channel-open counter since start. Defining the top byte being 0xff as "version two derivation" seems perfectly reasonable to me, I'm really quite confident no one has opened 4 billion channels in a single LDK instance without restarting :).

@tnull

tnull commented Jun 27, 2025

Copy link
Copy Markdown
Contributor

In our KeysManager, on the other hand, we do have room for a few extra bytes...currently the first 4 bytes are a channel-open counter since start.

Although note we previously established that we can’t go that way either as some of our users deployed a custom channel_keys_id derivation, ie, we can’t lean on the first few bytes being available/unset.

@TheBlueMatt

TheBlueMatt commented Jul 3, 2025

Copy link
Copy Markdown
Collaborator

Ah, I'd forgotten that (also, why on earth did anyone do that?!). So, then, yea, I guess a version byte is the only real option? Was there any other option? (discussion on the previous PR seems to hint that we had some other path to explore)

@tnull

tnull commented Jul 3, 2025

Copy link
Copy Markdown
Contributor

Ah, I'd forgotten that (also, why on earth did anyone do that?!). So, then, yea, I guess a version byte is the only real option? Was there any other option? (discussion on the previous PR seems to hint that we had some other path to explore)

Well, one third option we discussed was to always derive both variants and always just check which one to use, i.e., never set an explicit version anywhere. I do however dislike that approach, if we can avoid it, also since introducing a version does allow us to change the derivation scheme in the future without facing the same challenges again.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Ah, I mean hopefully we don't have to do that if we are more careful about the derivation scheme now? There's pretty little overhead to the extra derivation on construction and equality checks, so it shouldn't be too bad...

@ldk-reviews-bot

Copy link
Copy Markdown

Hi @tankyleo,

Thanks for your contributions to rust-lightning!

After too many struggles with bugs, outages, contributor bans, and, finally, a multi-week CI ban, the rust-lightning project is moving off of GitHub for day-to-day development.

You can still file issues and access the git tree here, but PRs will now take place exclusively at https://git.rust-bitcoin.org/. As such, this PR has been migrated to https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/3887

If you log in using GitHub (or otherwise link your GitHub account from https://git.rust-bitcoin.org/user/settings/security), ownership of your PRs, issues, and comments will automatically transfer. To push updates to this PR, you'll need to use git push git@gitea-ssh.bitcoin.ninja:lightningdevkit/rust-lightning YOUR_LOCAL_COMMIT_OR_BRANCH:tankyleo/channel-keys-version. This may require a permissions change - if it doesn't work initially just leave a comment and we'll get you access.

Sign up for freeto 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.

4 participants

@tankyleo@ldk-reviews-bot@tnull@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[Key derivation V2] Attach a version byte to channel_keys_id - #3887

Closed
tankyleo wants to merge 1 commit into
lightningdevkit:mainfrom
tankyleo:channel-keys-version
Closed

[Key derivation V2] Attach a version byte to channel_keys_id#3887
tankyleo wants to merge 1 commit into
lightningdevkit:mainfrom
tankyleo:channel-keys-version

Conversation

@tankyleo

@tankyleotankyleo commented Jun 24, 2025

Copy link
Copy Markdown
Contributor

Early, broken draft of a PR that would allow us to understand which key derivation we used for a particular channel_keys_id.

After this PR, we would ship a new derivation for to_remote outputs such that they may be recovered solely from the seed in case of loss of all channel state (including the channel_keys_id of the channel - which currently is required to derive the to_remote key).

An alternative approach could assume a certain byte in channel_keys_id has never been used, and we would rely on that byte to understand which key derivation to use. I've heard this assumption would break some downstream users, so pushed this approach instead.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 24, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems this already needs a rebase :)

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Concept ACK.

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

It might be good to include the actual v2 derivation in this PR, too, as it would be critical to see whether we can do the derivation in a backwards compatible manner everywhere. Also, would we necessarily break forwards compat everywhere with this?

Speaking off, please note that newly introduced fields need to have odd numbers to not automatically break forwards compatibility, as LDK follows the 'it's okay to be odd' rule for its serialization. This means that nodes would panic on downgrade if they encountered an even field that they aren't expecting.

(0, outpoint, required),
(1, channel_keys_id, option),
(2, output, required),
(4, channel_keys_version, (default_value, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This needs to be odd, as otherwise we'd break downgrades.

(15, counterparty_fulfilled_htlcs, option),
(17, initial_counterparty_commitment_info, option),
(19, channel_id, option),
(20, channel_keys_version, (default_value, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here, needs to be odd.

(10, self.channel_keys_id.id, required),
(12, self.channel_value_satoshis, required),
(13, self.channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
(14, self.channel_keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same, here, new field needs to be odd to not break downgrades.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I was hoping with 'no_write_default' to allow backwards compatibility iff the version is set to 0. I may be missing something let me know.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, ah, not sure if we'd want to add another serialization path just for this. Maybe it would be easier to make the version an Option<u8>? This would also make the behavior explicit and you could actually do manual migration steps depending on whether it's set (without leaning on a magic '0' value).

@tankyleotankyleoJun 24, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

If I recall correctly, for a None we would still write the even type, and make it impossible to downgrade ? Will check tomorrow :)

Or actually yes we could make it Option together with the option field type (not required)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It does seem like its a nicer API if we always have the version, vs having an Optional version, no? I guess as a new field it is kinda an Option... FWIW, I don't think we have to break the reads out nor add a new write type here, eg the following. I guess I don't have a strong opinion on option-or-not.

+impl_writeable_tlv_based!(DelayedPaymentOutputDescriptor, {
+ (0, outpoint, required),
+ (2, per_commitment_point, required),
+ (4, to_self_delay, required),
+ (6, output, required),
+ (8, revocation_pubkey, required),
+ (10, channel_keys_id_id, (legacy, [u8; 32], |obj: &DelayedPaymentOutputDescriptor| Some(obj.channel_keys_id.id))),
+ (12, channel_value_satoshis, required),
+ (13, channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
+ (14, channel_keys_id_vers, (legacy, u8, |obj: &DelayedPaymentOutputDescriptor| Some(obj.channel_keys_id.version))),
+ (x, channel_keys_id, (static_value, ChannelKeysId {
+ id: channel_keys_id_id.ok_or(DecodeError::InvalidValue)?,
+ version: channel_keys_id_vers.ok_or(DecodeError::InvalidValue)?,
+ })),
+});

(4, self.channel_keys_id.id, required),
(6, self.channel_value_satoshis, required),
(7, self.channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
(8, self.channel_keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here.

(0, self.value_satoshis, required),
(2, self.keys_id.id, required),
(4, self.transaction_parameters, (required: ReadableArgs, Some(value_satoshis.0.unwrap()))),
(6, self.keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here.

@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Concept ACK.

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

It might be good to include the actual v2 derivation in this PR, too, as it would be critical to see whether we can do the derivation in a backwards compatible manner everywhere. Also, would we necessarily break forwards compat everywhere with this?

Speaking off, please note that newly introduced fields need to have odd numbers to not automatically break forwards compatibility, as LDK follows the 'it's okay to be odd' rule for its serialization. This means that nodes would panic on downgrade if they encountered an even field that they aren't expecting.

Overall the idea is to allow backwards compat for version 0 and no backwards compat for anything else. For forward compat, when the field is not set, we assume it is 0.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This unfortunately already needs a rebase.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

Indeed, I remain not a huge fan of the version approach - the channel keys id is a public thing that isn't restricted to just our KeysManager (even if most nodes probably do use our KeysManager), so changing the public API because of a limitation of our KeysManager feels incredibly weird - what is the "version" for a SignerProvider that doesn't need it?

In our KeysManager, on the other hand, we do have room for a few extra bytes...currently the first 4 bytes are a channel-open counter since start. Defining the top byte being 0xff as "version two derivation" seems perfectly reasonable to me, I'm really quite confident no one has opened 4 billion channels in a single LDK instance without restarting :).

@tnull

tnull commented Jun 27, 2025

Copy link
Copy Markdown
Contributor

In our KeysManager, on the other hand, we do have room for a few extra bytes...currently the first 4 bytes are a channel-open counter since start.

Although note we previously established that we can’t go that way either as some of our users deployed a custom channel_keys_id derivation, ie, we can’t lean on the first few bytes being available/unset.

@TheBlueMatt

TheBlueMatt commented Jul 3, 2025

Copy link
Copy Markdown
Collaborator

Ah, I'd forgotten that (also, why on earth did anyone do that?!). So, then, yea, I guess a version byte is the only real option? Was there any other option? (discussion on the previous PR seems to hint that we had some other path to explore)

@tnull

tnull commented Jul 3, 2025

Copy link
Copy Markdown
Contributor

Ah, I'd forgotten that (also, why on earth did anyone do that?!). So, then, yea, I guess a version byte is the only real option? Was there any other option? (discussion on the previous PR seems to hint that we had some other path to explore)

Well, one third option we discussed was to always derive both variants and always just check which one to use, i.e., never set an explicit version anywhere. I do however dislike that approach, if we can avoid it, also since introducing a version does allow us to change the derivation scheme in the future without facing the same challenges again.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Ah, I mean hopefully we don't have to do that if we are more careful about the derivation scheme now? There's pretty little overhead to the extra derivation on construction and equality checks, so it shouldn't be too bad...

@ldk-reviews-bot

Copy link
Copy Markdown

Hi @tankyleo,

Thanks for your contributions to rust-lightning!

After too many struggles with bugs, outages, contributor bans, and, finally, a multi-week CI ban, the rust-lightning project is moving off of GitHub for day-to-day development.

You can still file issues and access the git tree here, but PRs will now take place exclusively at https://git.rust-bitcoin.org/. As such, this PR has been migrated to https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/3887

If you log in using GitHub (or otherwise link your GitHub account from https://git.rust-bitcoin.org/user/settings/security), ownership of your PRs, issues, and comments will automatically transfer. To push updates to this PR, you'll need to use git push git@gitea-ssh.bitcoin.ninja:lightningdevkit/rust-lightning YOUR_LOCAL_COMMIT_OR_BRANCH:tankyleo/channel-keys-version. This may require a permissions change - if it doesn't work initially just leave a comment and we'll get you access.

Sign up for freeto 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.

4 participants

@tankyleo@ldk-reviews-bot@tnull@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[Key derivation V2] Attach a version byte to channel_keys_id - #3887

Closed
tankyleo wants to merge 1 commit into
lightningdevkit:mainfrom
tankyleo:channel-keys-version
Closed

[Key derivation V2] Attach a version byte to channel_keys_id#3887
tankyleo wants to merge 1 commit into
lightningdevkit:mainfrom
tankyleo:channel-keys-version

Conversation

@tankyleo

@tankyleotankyleo commented Jun 24, 2025

Copy link
Copy Markdown
Contributor

Early, broken draft of a PR that would allow us to understand which key derivation we used for a particular channel_keys_id.

After this PR, we would ship a new derivation for to_remote outputs such that they may be recovered solely from the seed in case of loss of all channel state (including the channel_keys_id of the channel - which currently is required to derive the to_remote key).

An alternative approach could assume a certain byte in channel_keys_id has never been used, and we would rely on that byte to understand which key derivation to use. I've heard this assumption would break some downstream users, so pushed this approach instead.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 24, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems this already needs a rebase :)

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Concept ACK.

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

It might be good to include the actual v2 derivation in this PR, too, as it would be critical to see whether we can do the derivation in a backwards compatible manner everywhere. Also, would we necessarily break forwards compat everywhere with this?

Speaking off, please note that newly introduced fields need to have odd numbers to not automatically break forwards compatibility, as LDK follows the 'it's okay to be odd' rule for its serialization. This means that nodes would panic on downgrade if they encountered an even field that they aren't expecting.

(0, outpoint, required),
(1, channel_keys_id, option),
(2, output, required),
(4, channel_keys_version, (default_value, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This needs to be odd, as otherwise we'd break downgrades.

(15, counterparty_fulfilled_htlcs, option),
(17, initial_counterparty_commitment_info, option),
(19, channel_id, option),
(20, channel_keys_version, (default_value, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here, needs to be odd.

(10, self.channel_keys_id.id, required),
(12, self.channel_value_satoshis, required),
(13, self.channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
(14, self.channel_keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same, here, new field needs to be odd to not break downgrades.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I was hoping with 'no_write_default' to allow backwards compatibility iff the version is set to 0. I may be missing something let me know.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, ah, not sure if we'd want to add another serialization path just for this. Maybe it would be easier to make the version an Option<u8>? This would also make the behavior explicit and you could actually do manual migration steps depending on whether it's set (without leaning on a magic '0' value).

@tankyleotankyleoJun 24, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

If I recall correctly, for a None we would still write the even type, and make it impossible to downgrade ? Will check tomorrow :)

Or actually yes we could make it Option together with the option field type (not required)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It does seem like its a nicer API if we always have the version, vs having an Optional version, no? I guess as a new field it is kinda an Option... FWIW, I don't think we have to break the reads out nor add a new write type here, eg the following. I guess I don't have a strong opinion on option-or-not.

+impl_writeable_tlv_based!(DelayedPaymentOutputDescriptor, {
+ (0, outpoint, required),
+ (2, per_commitment_point, required),
+ (4, to_self_delay, required),
+ (6, output, required),
+ (8, revocation_pubkey, required),
+ (10, channel_keys_id_id, (legacy, [u8; 32], |obj: &DelayedPaymentOutputDescriptor| Some(obj.channel_keys_id.id))),
+ (12, channel_value_satoshis, required),
+ (13, channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
+ (14, channel_keys_id_vers, (legacy, u8, |obj: &DelayedPaymentOutputDescriptor| Some(obj.channel_keys_id.version))),
+ (x, channel_keys_id, (static_value, ChannelKeysId {
+ id: channel_keys_id_id.ok_or(DecodeError::InvalidValue)?,
+ version: channel_keys_id_vers.ok_or(DecodeError::InvalidValue)?,
+ })),
+});

(4, self.channel_keys_id.id, required),
(6, self.channel_value_satoshis, required),
(7, self.channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
(8, self.channel_keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here.

(0, self.value_satoshis, required),
(2, self.keys_id.id, required),
(4, self.transaction_parameters, (required: ReadableArgs, Some(value_satoshis.0.unwrap()))),
(6, self.keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here.

@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Concept ACK.

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

It might be good to include the actual v2 derivation in this PR, too, as it would be critical to see whether we can do the derivation in a backwards compatible manner everywhere. Also, would we necessarily break forwards compat everywhere with this?

Speaking off, please note that newly introduced fields need to have odd numbers to not automatically break forwards compatibility, as LDK follows the 'it's okay to be odd' rule for its serialization. This means that nodes would panic on downgrade if they encountered an even field that they aren't expecting.

Overall the idea is to allow backwards compat for version 0 and no backwards compat for anything else. For forward compat, when the field is not set, we assume it is 0.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This unfortunately already needs a rebase.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

Indeed, I remain not a huge fan of the version approach - the channel keys id is a public thing that isn't restricted to just our KeysManager (even if most nodes probably do use our KeysManager), so changing the public API because of a limitation of our KeysManager feels incredibly weird - what is the "version" for a SignerProvider that doesn't need it?

In our KeysManager, on the other hand, we do have room for a few extra bytes...currently the first 4 bytes are a channel-open counter since start. Defining the top byte being 0xff as "version two derivation" seems perfectly reasonable to me, I'm really quite confident no one has opened 4 billion channels in a single LDK instance without restarting :).

@tnull

tnull commented Jun 27, 2025

Copy link
Copy Markdown
Contributor

In our KeysManager, on the other hand, we do have room for a few extra bytes...currently the first 4 bytes are a channel-open counter since start.

Although note we previously established that we can’t go that way either as some of our users deployed a custom channel_keys_id derivation, ie, we can’t lean on the first few bytes being available/unset.

@TheBlueMatt

TheBlueMatt commented Jul 3, 2025

Copy link
Copy Markdown
Collaborator

Ah, I'd forgotten that (also, why on earth did anyone do that?!). So, then, yea, I guess a version byte is the only real option? Was there any other option? (discussion on the previous PR seems to hint that we had some other path to explore)

@tnull

tnull commented Jul 3, 2025

Copy link
Copy Markdown
Contributor

Ah, I'd forgotten that (also, why on earth did anyone do that?!). So, then, yea, I guess a version byte is the only real option? Was there any other option? (discussion on the previous PR seems to hint that we had some other path to explore)

Well, one third option we discussed was to always derive both variants and always just check which one to use, i.e., never set an explicit version anywhere. I do however dislike that approach, if we can avoid it, also since introducing a version does allow us to change the derivation scheme in the future without facing the same challenges again.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Ah, I mean hopefully we don't have to do that if we are more careful about the derivation scheme now? There's pretty little overhead to the extra derivation on construction and equality checks, so it shouldn't be too bad...

@ldk-reviews-bot

Copy link
Copy Markdown

Hi @tankyleo,

Thanks for your contributions to rust-lightning!

After too many struggles with bugs, outages, contributor bans, and, finally, a multi-week CI ban, the rust-lightning project is moving off of GitHub for day-to-day development.

You can still file issues and access the git tree here, but PRs will now take place exclusively at https://git.rust-bitcoin.org/. As such, this PR has been migrated to https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/3887

If you log in using GitHub (or otherwise link your GitHub account from https://git.rust-bitcoin.org/user/settings/security), ownership of your PRs, issues, and comments will automatically transfer. To push updates to this PR, you'll need to use git push git@gitea-ssh.bitcoin.ninja:lightningdevkit/rust-lightning YOUR_LOCAL_COMMIT_OR_BRANCH:tankyleo/channel-keys-version. This may require a permissions change - if it doesn't work initially just leave a comment and we'll get you access.

Sign up for freeto 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.

4 participants

@tankyleo@ldk-reviews-bot@tnull@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[Key derivation V2] Attach a version byte to channel_keys_id - #3887

Closed
tankyleo wants to merge 1 commit into
lightningdevkit:mainfrom
tankyleo:channel-keys-version
Closed

[Key derivation V2] Attach a version byte to channel_keys_id#3887
tankyleo wants to merge 1 commit into
lightningdevkit:mainfrom
tankyleo:channel-keys-version

Conversation

@tankyleo

@tankyleotankyleo commented Jun 24, 2025

Copy link
Copy Markdown
Contributor

Early, broken draft of a PR that would allow us to understand which key derivation we used for a particular channel_keys_id.

After this PR, we would ship a new derivation for to_remote outputs such that they may be recovered solely from the seed in case of loss of all channel state (including the channel_keys_id of the channel - which currently is required to derive the to_remote key).

An alternative approach could assume a certain byte in channel_keys_id has never been used, and we would rely on that byte to understand which key derivation to use. I've heard this assumption would break some downstream users, so pushed this approach instead.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 24, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems this already needs a rebase :)

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Concept ACK.

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

It might be good to include the actual v2 derivation in this PR, too, as it would be critical to see whether we can do the derivation in a backwards compatible manner everywhere. Also, would we necessarily break forwards compat everywhere with this?

Speaking off, please note that newly introduced fields need to have odd numbers to not automatically break forwards compatibility, as LDK follows the 'it's okay to be odd' rule for its serialization. This means that nodes would panic on downgrade if they encountered an even field that they aren't expecting.

(0, outpoint, required),
(1, channel_keys_id, option),
(2, output, required),
(4, channel_keys_version, (default_value, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This needs to be odd, as otherwise we'd break downgrades.

(15, counterparty_fulfilled_htlcs, option),
(17, initial_counterparty_commitment_info, option),
(19, channel_id, option),
(20, channel_keys_version, (default_value, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here, needs to be odd.

(10, self.channel_keys_id.id, required),
(12, self.channel_value_satoshis, required),
(13, self.channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
(14, self.channel_keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same, here, new field needs to be odd to not break downgrades.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I was hoping with 'no_write_default' to allow backwards compatibility iff the version is set to 0. I may be missing something let me know.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, ah, not sure if we'd want to add another serialization path just for this. Maybe it would be easier to make the version an Option<u8>? This would also make the behavior explicit and you could actually do manual migration steps depending on whether it's set (without leaning on a magic '0' value).

@tankyleotankyleoJun 24, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

If I recall correctly, for a None we would still write the even type, and make it impossible to downgrade ? Will check tomorrow :)

Or actually yes we could make it Option together with the option field type (not required)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It does seem like its a nicer API if we always have the version, vs having an Optional version, no? I guess as a new field it is kinda an Option... FWIW, I don't think we have to break the reads out nor add a new write type here, eg the following. I guess I don't have a strong opinion on option-or-not.

+impl_writeable_tlv_based!(DelayedPaymentOutputDescriptor, {
+ (0, outpoint, required),
+ (2, per_commitment_point, required),
+ (4, to_self_delay, required),
+ (6, output, required),
+ (8, revocation_pubkey, required),
+ (10, channel_keys_id_id, (legacy, [u8; 32], |obj: &DelayedPaymentOutputDescriptor| Some(obj.channel_keys_id.id))),
+ (12, channel_value_satoshis, required),
+ (13, channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
+ (14, channel_keys_id_vers, (legacy, u8, |obj: &DelayedPaymentOutputDescriptor| Some(obj.channel_keys_id.version))),
+ (x, channel_keys_id, (static_value, ChannelKeysId {
+ id: channel_keys_id_id.ok_or(DecodeError::InvalidValue)?,
+ version: channel_keys_id_vers.ok_or(DecodeError::InvalidValue)?,
+ })),
+});

(4, self.channel_keys_id.id, required),
(6, self.channel_value_satoshis, required),
(7, self.channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
(8, self.channel_keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here.

(0, self.value_satoshis, required),
(2, self.keys_id.id, required),
(4, self.transaction_parameters, (required: ReadableArgs, Some(value_satoshis.0.unwrap()))),
(6, self.keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here.

@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Concept ACK.

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

It might be good to include the actual v2 derivation in this PR, too, as it would be critical to see whether we can do the derivation in a backwards compatible manner everywhere. Also, would we necessarily break forwards compat everywhere with this?

Speaking off, please note that newly introduced fields need to have odd numbers to not automatically break forwards compatibility, as LDK follows the 'it's okay to be odd' rule for its serialization. This means that nodes would panic on downgrade if they encountered an even field that they aren't expecting.

Overall the idea is to allow backwards compat for version 0 and no backwards compat for anything else. For forward compat, when the field is not set, we assume it is 0.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This unfortunately already needs a rebase.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

Indeed, I remain not a huge fan of the version approach - the channel keys id is a public thing that isn't restricted to just our KeysManager (even if most nodes probably do use our KeysManager), so changing the public API because of a limitation of our KeysManager feels incredibly weird - what is the "version" for a SignerProvider that doesn't need it?

In our KeysManager, on the other hand, we do have room for a few extra bytes...currently the first 4 bytes are a channel-open counter since start. Defining the top byte being 0xff as "version two derivation" seems perfectly reasonable to me, I'm really quite confident no one has opened 4 billion channels in a single LDK instance without restarting :).

@tnull

tnull commented Jun 27, 2025

Copy link
Copy Markdown
Contributor

In our KeysManager, on the other hand, we do have room for a few extra bytes...currently the first 4 bytes are a channel-open counter since start.

Although note we previously established that we can’t go that way either as some of our users deployed a custom channel_keys_id derivation, ie, we can’t lean on the first few bytes being available/unset.

@TheBlueMatt

TheBlueMatt commented Jul 3, 2025

Copy link
Copy Markdown
Collaborator

Ah, I'd forgotten that (also, why on earth did anyone do that?!). So, then, yea, I guess a version byte is the only real option? Was there any other option? (discussion on the previous PR seems to hint that we had some other path to explore)

@tnull

tnull commented Jul 3, 2025

Copy link
Copy Markdown
Contributor

Ah, I'd forgotten that (also, why on earth did anyone do that?!). So, then, yea, I guess a version byte is the only real option? Was there any other option? (discussion on the previous PR seems to hint that we had some other path to explore)

Well, one third option we discussed was to always derive both variants and always just check which one to use, i.e., never set an explicit version anywhere. I do however dislike that approach, if we can avoid it, also since introducing a version does allow us to change the derivation scheme in the future without facing the same challenges again.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Ah, I mean hopefully we don't have to do that if we are more careful about the derivation scheme now? There's pretty little overhead to the extra derivation on construction and equality checks, so it shouldn't be too bad...

@ldk-reviews-bot

Copy link
Copy Markdown

Hi @tankyleo,

Thanks for your contributions to rust-lightning!

After too many struggles with bugs, outages, contributor bans, and, finally, a multi-week CI ban, the rust-lightning project is moving off of GitHub for day-to-day development.

You can still file issues and access the git tree here, but PRs will now take place exclusively at https://git.rust-bitcoin.org/. As such, this PR has been migrated to https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/3887

If you log in using GitHub (or otherwise link your GitHub account from https://git.rust-bitcoin.org/user/settings/security), ownership of your PRs, issues, and comments will automatically transfer. To push updates to this PR, you'll need to use git push git@gitea-ssh.bitcoin.ninja:lightningdevkit/rust-lightning YOUR_LOCAL_COMMIT_OR_BRANCH:tankyleo/channel-keys-version. This may require a permissions change - if it doesn't work initially just leave a comment and we'll get you access.

Sign up for freeto 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.

4 participants

@tankyleo@ldk-reviews-bot@tnull@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[Key derivation V2] Attach a version byte to channel_keys_id - #3887

Closed
tankyleo wants to merge 1 commit into
lightningdevkit:mainfrom
tankyleo:channel-keys-version
Closed

[Key derivation V2] Attach a version byte to channel_keys_id#3887
tankyleo wants to merge 1 commit into
lightningdevkit:mainfrom
tankyleo:channel-keys-version

Conversation

@tankyleo

@tankyleotankyleo commented Jun 24, 2025

Copy link
Copy Markdown
Contributor

Early, broken draft of a PR that would allow us to understand which key derivation we used for a particular channel_keys_id.

After this PR, we would ship a new derivation for to_remote outputs such that they may be recovered solely from the seed in case of loss of all channel state (including the channel_keys_id of the channel - which currently is required to derive the to_remote key).

An alternative approach could assume a certain byte in channel_keys_id has never been used, and we would rely on that byte to understand which key derivation to use. I've heard this assumption would break some downstream users, so pushed this approach instead.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 24, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems this already needs a rebase :)

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Concept ACK.

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

It might be good to include the actual v2 derivation in this PR, too, as it would be critical to see whether we can do the derivation in a backwards compatible manner everywhere. Also, would we necessarily break forwards compat everywhere with this?

Speaking off, please note that newly introduced fields need to have odd numbers to not automatically break forwards compatibility, as LDK follows the 'it's okay to be odd' rule for its serialization. This means that nodes would panic on downgrade if they encountered an even field that they aren't expecting.

(0, outpoint, required),
(1, channel_keys_id, option),
(2, output, required),
(4, channel_keys_version, (default_value, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This needs to be odd, as otherwise we'd break downgrades.

(15, counterparty_fulfilled_htlcs, option),
(17, initial_counterparty_commitment_info, option),
(19, channel_id, option),
(20, channel_keys_version, (default_value, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here, needs to be odd.

(10, self.channel_keys_id.id, required),
(12, self.channel_value_satoshis, required),
(13, self.channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
(14, self.channel_keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same, here, new field needs to be odd to not break downgrades.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I was hoping with 'no_write_default' to allow backwards compatibility iff the version is set to 0. I may be missing something let me know.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, ah, not sure if we'd want to add another serialization path just for this. Maybe it would be easier to make the version an Option<u8>? This would also make the behavior explicit and you could actually do manual migration steps depending on whether it's set (without leaning on a magic '0' value).

@tankyleotankyleoJun 24, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

If I recall correctly, for a None we would still write the even type, and make it impossible to downgrade ? Will check tomorrow :)

Or actually yes we could make it Option together with the option field type (not required)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It does seem like its a nicer API if we always have the version, vs having an Optional version, no? I guess as a new field it is kinda an Option... FWIW, I don't think we have to break the reads out nor add a new write type here, eg the following. I guess I don't have a strong opinion on option-or-not.

+impl_writeable_tlv_based!(DelayedPaymentOutputDescriptor, {
+ (0, outpoint, required),
+ (2, per_commitment_point, required),
+ (4, to_self_delay, required),
+ (6, output, required),
+ (8, revocation_pubkey, required),
+ (10, channel_keys_id_id, (legacy, [u8; 32], |obj: &DelayedPaymentOutputDescriptor| Some(obj.channel_keys_id.id))),
+ (12, channel_value_satoshis, required),
+ (13, channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
+ (14, channel_keys_id_vers, (legacy, u8, |obj: &DelayedPaymentOutputDescriptor| Some(obj.channel_keys_id.version))),
+ (x, channel_keys_id, (static_value, ChannelKeysId {
+ id: channel_keys_id_id.ok_or(DecodeError::InvalidValue)?,
+ version: channel_keys_id_vers.ok_or(DecodeError::InvalidValue)?,
+ })),
+});

(4, self.channel_keys_id.id, required),
(6, self.channel_value_satoshis, required),
(7, self.channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
(8, self.channel_keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here.

(0, self.value_satoshis, required),
(2, self.keys_id.id, required),
(4, self.transaction_parameters, (required: ReadableArgs, Some(value_satoshis.0.unwrap()))),
(6, self.keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here.

@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Concept ACK.

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

It might be good to include the actual v2 derivation in this PR, too, as it would be critical to see whether we can do the derivation in a backwards compatible manner everywhere. Also, would we necessarily break forwards compat everywhere with this?

Speaking off, please note that newly introduced fields need to have odd numbers to not automatically break forwards compatibility, as LDK follows the 'it's okay to be odd' rule for its serialization. This means that nodes would panic on downgrade if they encountered an even field that they aren't expecting.

Overall the idea is to allow backwards compat for version 0 and no backwards compat for anything else. For forward compat, when the field is not set, we assume it is 0.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This unfortunately already needs a rebase.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

Indeed, I remain not a huge fan of the version approach - the channel keys id is a public thing that isn't restricted to just our KeysManager (even if most nodes probably do use our KeysManager), so changing the public API because of a limitation of our KeysManager feels incredibly weird - what is the "version" for a SignerProvider that doesn't need it?

In our KeysManager, on the other hand, we do have room for a few extra bytes...currently the first 4 bytes are a channel-open counter since start. Defining the top byte being 0xff as "version two derivation" seems perfectly reasonable to me, I'm really quite confident no one has opened 4 billion channels in a single LDK instance without restarting :).

@tnull

tnull commented Jun 27, 2025

Copy link
Copy Markdown
Contributor

In our KeysManager, on the other hand, we do have room for a few extra bytes...currently the first 4 bytes are a channel-open counter since start.

Although note we previously established that we can’t go that way either as some of our users deployed a custom channel_keys_id derivation, ie, we can’t lean on the first few bytes being available/unset.

@TheBlueMatt

TheBlueMatt commented Jul 3, 2025

Copy link
Copy Markdown
Collaborator

Ah, I'd forgotten that (also, why on earth did anyone do that?!). So, then, yea, I guess a version byte is the only real option? Was there any other option? (discussion on the previous PR seems to hint that we had some other path to explore)

@tnull

tnull commented Jul 3, 2025

Copy link
Copy Markdown
Contributor

Ah, I'd forgotten that (also, why on earth did anyone do that?!). So, then, yea, I guess a version byte is the only real option? Was there any other option? (discussion on the previous PR seems to hint that we had some other path to explore)

Well, one third option we discussed was to always derive both variants and always just check which one to use, i.e., never set an explicit version anywhere. I do however dislike that approach, if we can avoid it, also since introducing a version does allow us to change the derivation scheme in the future without facing the same challenges again.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Ah, I mean hopefully we don't have to do that if we are more careful about the derivation scheme now? There's pretty little overhead to the extra derivation on construction and equality checks, so it shouldn't be too bad...

@ldk-reviews-bot

Copy link
Copy Markdown

Hi @tankyleo,

Thanks for your contributions to rust-lightning!

After too many struggles with bugs, outages, contributor bans, and, finally, a multi-week CI ban, the rust-lightning project is moving off of GitHub for day-to-day development.

You can still file issues and access the git tree here, but PRs will now take place exclusively at https://git.rust-bitcoin.org/. As such, this PR has been migrated to https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/3887

If you log in using GitHub (or otherwise link your GitHub account from https://git.rust-bitcoin.org/user/settings/security), ownership of your PRs, issues, and comments will automatically transfer. To push updates to this PR, you'll need to use git push git@gitea-ssh.bitcoin.ninja:lightningdevkit/rust-lightning YOUR_LOCAL_COMMIT_OR_BRANCH:tankyleo/channel-keys-version. This may require a permissions change - if it doesn't work initially just leave a comment and we'll get you access.

Sign up for freeto 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.

4 participants

@tankyleo@ldk-reviews-bot@tnull@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[Key derivation V2] Attach a version byte to channel_keys_id - #3887

Closed
tankyleo wants to merge 1 commit into
lightningdevkit:mainfrom
tankyleo:channel-keys-version
Closed

[Key derivation V2] Attach a version byte to channel_keys_id#3887
tankyleo wants to merge 1 commit into
lightningdevkit:mainfrom
tankyleo:channel-keys-version

Conversation

@tankyleo

@tankyleotankyleo commented Jun 24, 2025

Copy link
Copy Markdown
Contributor

Early, broken draft of a PR that would allow us to understand which key derivation we used for a particular channel_keys_id.

After this PR, we would ship a new derivation for to_remote outputs such that they may be recovered solely from the seed in case of loss of all channel state (including the channel_keys_id of the channel - which currently is required to derive the to_remote key).

An alternative approach could assume a certain byte in channel_keys_id has never been used, and we would rely on that byte to understand which key derivation to use. I've heard this assumption would break some downstream users, so pushed this approach instead.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 24, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems this already needs a rebase :)

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Concept ACK.

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

It might be good to include the actual v2 derivation in this PR, too, as it would be critical to see whether we can do the derivation in a backwards compatible manner everywhere. Also, would we necessarily break forwards compat everywhere with this?

Speaking off, please note that newly introduced fields need to have odd numbers to not automatically break forwards compatibility, as LDK follows the 'it's okay to be odd' rule for its serialization. This means that nodes would panic on downgrade if they encountered an even field that they aren't expecting.

(0, outpoint, required),
(1, channel_keys_id, option),
(2, output, required),
(4, channel_keys_version, (default_value, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This needs to be odd, as otherwise we'd break downgrades.

(15, counterparty_fulfilled_htlcs, option),
(17, initial_counterparty_commitment_info, option),
(19, channel_id, option),
(20, channel_keys_version, (default_value, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here, needs to be odd.

(10, self.channel_keys_id.id, required),
(12, self.channel_value_satoshis, required),
(13, self.channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
(14, self.channel_keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same, here, new field needs to be odd to not break downgrades.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I was hoping with 'no_write_default' to allow backwards compatibility iff the version is set to 0. I may be missing something let me know.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, ah, not sure if we'd want to add another serialization path just for this. Maybe it would be easier to make the version an Option<u8>? This would also make the behavior explicit and you could actually do manual migration steps depending on whether it's set (without leaning on a magic '0' value).

@tankyleotankyleoJun 24, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

If I recall correctly, for a None we would still write the even type, and make it impossible to downgrade ? Will check tomorrow :)

Or actually yes we could make it Option together with the option field type (not required)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It does seem like its a nicer API if we always have the version, vs having an Optional version, no? I guess as a new field it is kinda an Option... FWIW, I don't think we have to break the reads out nor add a new write type here, eg the following. I guess I don't have a strong opinion on option-or-not.

+impl_writeable_tlv_based!(DelayedPaymentOutputDescriptor, {
+ (0, outpoint, required),
+ (2, per_commitment_point, required),
+ (4, to_self_delay, required),
+ (6, output, required),
+ (8, revocation_pubkey, required),
+ (10, channel_keys_id_id, (legacy, [u8; 32], |obj: &DelayedPaymentOutputDescriptor| Some(obj.channel_keys_id.id))),
+ (12, channel_value_satoshis, required),
+ (13, channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
+ (14, channel_keys_id_vers, (legacy, u8, |obj: &DelayedPaymentOutputDescriptor| Some(obj.channel_keys_id.version))),
+ (x, channel_keys_id, (static_value, ChannelKeysId {
+ id: channel_keys_id_id.ok_or(DecodeError::InvalidValue)?,
+ version: channel_keys_id_vers.ok_or(DecodeError::InvalidValue)?,
+ })),
+});

(4, self.channel_keys_id.id, required),
(6, self.channel_value_satoshis, required),
(7, self.channel_transaction_parameters, (option: ReadableArgs, Some(channel_value_satoshis.0.unwrap()))),
(8, self.channel_keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here.

(0, self.value_satoshis, required),
(2, self.keys_id.id, required),
(4, self.transaction_parameters, (required: ReadableArgs, Some(value_satoshis.0.unwrap()))),
(6, self.keys_id.version, (no_write_default, 0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here.

@tankyleo

Copy link
Copy Markdown
ContributorAuthor

Concept ACK.

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

It might be good to include the actual v2 derivation in this PR, too, as it would be critical to see whether we can do the derivation in a backwards compatible manner everywhere. Also, would we necessarily break forwards compat everywhere with this?

Speaking off, please note that newly introduced fields need to have odd numbers to not automatically break forwards compatibility, as LDK follows the 'it's okay to be odd' rule for its serialization. This means that nodes would panic on downgrade if they encountered an even field that they aren't expecting.

Overall the idea is to allow backwards compat for version 0 and no backwards compat for anything else. For forward compat, when the field is not set, we assume it is 0.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This unfortunately already needs a rebase.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is basically 1:1 my preferred approach from #3391, which @TheBlueMatt wasn't the biggest fan of though.

Indeed, I remain not a huge fan of the version approach - the channel keys id is a public thing that isn't restricted to just our KeysManager (even if most nodes probably do use our KeysManager), so changing the public API because of a limitation of our KeysManager feels incredibly weird - what is the "version" for a SignerProvider that doesn't need it?

In our KeysManager, on the other hand, we do have room for a few extra bytes...currently the first 4 bytes are a channel-open counter since start. Defining the top byte being 0xff as "version two derivation" seems perfectly reasonable to me, I'm really quite confident no one has opened 4 billion channels in a single LDK instance without restarting :).

@tnull

tnull commented Jun 27, 2025

Copy link
Copy Markdown
Contributor

In our KeysManager, on the other hand, we do have room for a few extra bytes...currently the first 4 bytes are a channel-open counter since start.

Although note we previously established that we can’t go that way either as some of our users deployed a custom channel_keys_id derivation, ie, we can’t lean on the first few bytes being available/unset.

@TheBlueMatt

TheBlueMatt commented Jul 3, 2025

Copy link
Copy Markdown
Collaborator

Ah, I'd forgotten that (also, why on earth did anyone do that?!). So, then, yea, I guess a version byte is the only real option? Was there any other option? (discussion on the previous PR seems to hint that we had some other path to explore)

@tnull

tnull commented Jul 3, 2025

Copy link
Copy Markdown
Contributor

Ah, I'd forgotten that (also, why on earth did anyone do that?!). So, then, yea, I guess a version byte is the only real option? Was there any other option? (discussion on the previous PR seems to hint that we had some other path to explore)

Well, one third option we discussed was to always derive both variants and always just check which one to use, i.e., never set an explicit version anywhere. I do however dislike that approach, if we can avoid it, also since introducing a version does allow us to change the derivation scheme in the future without facing the same challenges again.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Ah, I mean hopefully we don't have to do that if we are more careful about the derivation scheme now? There's pretty little overhead to the extra derivation on construction and equality checks, so it shouldn't be too bad...

@ldk-reviews-bot

Copy link
Copy Markdown

Hi @tankyleo,

Thanks for your contributions to rust-lightning!

After too many struggles with bugs, outages, contributor bans, and, finally, a multi-week CI ban, the rust-lightning project is moving off of GitHub for day-to-day development.

You can still file issues and access the git tree here, but PRs will now take place exclusively at https://git.rust-bitcoin.org/. As such, this PR has been migrated to https://git.rust-bitcoin.org/lightningdevkit/rust-lightning/pulls/3887

If you log in using GitHub (or otherwise link your GitHub account from https://git.rust-bitcoin.org/user/settings/security), ownership of your PRs, issues, and comments will automatically transfer. To push updates to this PR, you'll need to use git push git@gitea-ssh.bitcoin.ninja:lightningdevkit/rust-lightning YOUR_LOCAL_COMMIT_OR_BRANCH:tankyleo/channel-keys-version. This may require a permissions change - if it doesn't work initially just leave a comment and we'll get you access.

Sign up for freeto 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.

4 participants

@tankyleo@ldk-reviews-bot@tnull@TheBlueMatt