Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Padding for Payment and Message Blinded Tlvs #3177

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

shaavan
Copy link
Contributor

@shaavan shaavan commented Jul 13, 2024

Description

This PR introduces padding for Payment and Message Blinded TLVs to ensure that the size of each packet in the path is uniform.

@shaavan
Copy link
Contributor Author

shaavan commented Jul 13, 2024

Notes:

  1. Padding Implementation:
    • The padding field is now expanded to contain Option<Vec>. This change allows the inclusion of padding within the packet while retaining the flexibility to leave the padding as None.
  2. Impact on Path Length:
    • With the added padding, the length of each individual packet increases, which results in a reduction of the maximum path length. Consequently, the max_path_length in forward_check_failures has been adjusted from 18 to 17.

@codecov-commenter
Copy link

codecov-commenter commented Jul 13, 2024

Codecov Report

Attention: Patch coverage is 97.43590% with 3 lines in your changes missing coverage. Please review.

Project coverage is 89.95%. Comparing base (bc1931b) to head (25aea5a).
Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
lightning/src/blinded_path/utils.rs 91.42% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3177      +/-   ##
==========================================
+ Coverage   89.62%   89.95%   +0.32%     
==========================================
  Files         127      127              
  Lines      103517   106639    +3122     
  Branches   103517   106639    +3122     
==========================================
+ Hits        92780    95926    +3146     
- Misses       8041     8072      +31     
+ Partials     2696     2641      -55     

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

@jkczyz jkczyz self-requested a review July 15, 2024 20:03
lightning/src/blinded_path/message.rs Outdated Show resolved Hide resolved
lightning/src/blinded_path/message.rs Outdated Show resolved Hide resolved
lightning/src/blinded_path/utils.rs Outdated Show resolved Hide resolved
lightning/src/blinded_path/message.rs Outdated Show resolved Hide resolved
lightning/src/blinded_path/message.rs Outdated Show resolved Hide resolved
lightning/src/blinded_path/message.rs Outdated Show resolved Hide resolved
@shaavan
Copy link
Contributor Author

shaavan commented Jul 18, 2024

Updated from pr3177.01 to pr3177.02 (diff):
Addressed @jkczyz's comments.

Changes:

  1. Reordered commits and separated the update to the Padding struct into a separate commit.
  2. Updated the Padding struct to contain a usize instead of a Vec to save on heap allocation, and made the Writable implementation more efficient.
  3. Introduced two f: commits to use iter clone instead of Vec allocation, keeping the code efficient.
  4. Introduced a test and a test utility to verify that blinded message and payment paths are properly padded, ensuring a consistent payload size.

@shaavan
Copy link
Contributor Author

shaavan commented Jul 18, 2024

Updated from pr3177.02 to pr3177.03 (diff):

Changes:

  1. Rebase on main to fix ci.

Diff post rebase:

--- a/lightning/src/ln/offers_tests.rs
+++ b/lightning/src/ln/offers_tests.rs
@@ -1789,7 +1789,7 @@ fn test_blinded_path_padding() {
        let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap();
        david.onion_messenger.handle_onion_message(&charlie_id, &onion_message);
 
-       let invoice = extract_invoice(david, &onion_message);
+       let (invoice, _) = extract_invoice(david, &onion_message);
        assert_eq!(invoice, expected_invoice);

lightning/src/blinded_path/message.rs Outdated Show resolved Hide resolved
lightning/src/blinded_path/message.rs Outdated Show resolved Hide resolved
@shaavan
Copy link
Contributor Author

shaavan commented Jul 22, 2024

Updated from pr3177.03 to pr3177.04 (diff):
Addressed @jkczyz comments

  1. Refactor max_length calculation to be cleaner.
  2. Introduce wrapper struct instead of tuple to make the code clearer to understand.
  3. Move the padding testing to onion_message/functional_tests.rs to simplify the setup, and test the code at a more appropriate place.

lightning/src/onion_message/packet.rs Outdated Show resolved Hide resolved
lightning/src/onion_message/packet.rs Outdated Show resolved Hide resolved
lightning/src/onion_message/packet.rs Outdated Show resolved Hide resolved
lightning/src/blinded_path/payment.rs Outdated Show resolved Hide resolved
lightning/src/onion_message/functional_tests.rs Outdated Show resolved Hide resolved
lightning/src/ln/offers_tests.rs Outdated Show resolved Hide resolved
@shaavan
Copy link
Contributor Author

shaavan commented Jul 23, 2024

Updated from pr3177.04 to pr3177.05 (diff):

Updates:

  1. Rebase on main.

@shaavan
Copy link
Contributor Author

shaavan commented Jul 23, 2024

Updated from pr3177.05 to pr3177.06 (diff):
Addressed @jkczyz comments

Changes:

  1. Squash commits.
  2. Introduce a generic struct WithPadding to keep the code DRY.
  3. Introduce a test for Blinded Payment Paths.
  4. Use saturating sub in padding length calculation to avoid overflows.

lightning/src/blinded_path/utils.rs Outdated Show resolved Hide resolved
lightning/src/blinded_path/utils.rs Outdated Show resolved Hide resolved
lightning/src/blinded_path/utils.rs Outdated Show resolved Hide resolved
lightning/src/blinded_path/utils.rs Outdated Show resolved Hide resolved
lightning/src/blinded_path/utils.rs Outdated Show resolved Hide resolved
lightning/src/blinded_path/utils.rs Outdated Show resolved Hide resolved
lightning/src/blinded_path/utils.rs Outdated Show resolved Hide resolved
lightning/src/ln/blinded_payment_tests.rs Outdated Show resolved Hide resolved
@shaavan
Copy link
Contributor Author

shaavan commented Jul 26, 2024

Updated from pr3177.06 to pr3177.07 (diff):
Addressed @jkczyz comments

  1. Update comments.
  2. Introduce debug_assert!() in Writeable for WithPadding.
  3. Properly assert!() the condition in test.

@shaavan
Copy link
Contributor Author

shaavan commented Jul 27, 2024

Updated from pr3177.07 to pr3177.08 (diff):
Addressed @jkczyz comments

Changes:

  1. Introduce an f: commit with an alternative approach to WithPadding.
  2. The new approach introduces padding as an optional field within the ForwardTlvs and the ReceiveTlvs struct.

lightning/src/blinded_path/message.rs Outdated Show resolved Hide resolved
lightning/src/blinded_path/payment.rs Outdated Show resolved Hide resolved
@shaavan
Copy link
Contributor Author

shaavan commented Aug 1, 2024

Update: From pr3177.08 to pr3177.09 (diff):

Changes based on @jkczyz's feedback:

  1. Fixed the imports.
  2. Removed BlindedPaymentTlvsRef and replaced it with BlindedPaymentTlvs. The former contained an immutable reference to the underlying TLVs, but the new implementation required mutability. Therefore, I transitioned to using BlindedPaymentTlvs directly, making BlindedPaymentTlvsRef redundant.

lightning/src/onion_message/packet.rs Outdated Show resolved Hide resolved
@@ -1772,6 +1772,7 @@ fn packet_payloads_and_keys<T: OnionMessageContents, S: secp256k1::Signing + sec
if let Some(ss) = prev_control_tlvs_ss.take() {
payloads.push((Payload::Forward(ForwardControlTlvs::Unblinded(
ForwardTlvs {
padding: None,
Copy link
Contributor

Choose a reason for hiding this comment

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

@TheBlueMatt One thing that I'm unsure of is whether we only need to pad the blinded path or if we should also pad the unblinded portion of the onion.

lightning/src/blinded_path/payment.rs Outdated Show resolved Hide resolved
@shaavan
Copy link
Contributor Author

shaavan commented Aug 14, 2024

Update: From pr3177.09 to pr3177.10 (diff):

Changes based on @jkczyz's feedback:

  1. Rename pad_tlvs -> pad_to_length.

@shaavan
Copy link
Contributor Author

shaavan commented Aug 29, 2024

Update: From pr3177.16 to pr3177.17 (diff):

Addressed @jkczyz comment

Changes:

  1. Update pad_to_length functions to use match instead of unwrap()
  2. Update the test to have non-zero context, to ensure the size of receive tlvs and forward tlvs vary before padding.

Copy link
Contributor

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

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

LGTM modulo one minor comment. Feel free to squash after fixing it.

lightning/src/blinded_path/message.rs Outdated Show resolved Hide resolved
@shaavan
Copy link
Contributor Author

shaavan commented Sep 2, 2024

Update: From pr3177.17 to pr3177.18 (diff):
Addressed @jkczyz comment

Changes:

  1. Reverted visibility changes for BlindedPath in BlindedMessagePath.

jkczyz
jkczyz previously approved these changes Sep 3, 2024
pub(crate) struct ForwardTlvs {
/// The padding data used to make all packets of a blinded path the same size
pub(crate) padding: Option<Padding>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are we storing a Padding here? Its not a real "thing", so it really seems like we should handle it purely at the serialization layer (ie in ForwardTlvs::write/read) rather than exposing it in ForwardTlvs across the crate. This would substantially reduce the size of this patch.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm... there was some earlier discussion around this approach and it was implemented here: b1436f8. The shortcoming was that you could use WithPadding to write an invalid TLV stream having two type 1 TLV records. Maybe we don't care so long as we always use it right?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, that design constraint assumes that we want to pad to make each hop equal (by looking at the max hop length), but I'm not sure that's what we want anyway. Doing so may leak something about the contents still - if we assume the last hop is the max, then we leak the type of context (okay, generally fine), but in the future may leak some info based on if some field is set or not. Instead, we could have a simple PADDING_TARGET_LENGTH constant and always pad to that length in both ForwardTlvs::write and ReceiveTlvs::write (with appropriate debug assertions that by default we never have hops longer than that).

This may not be appropriate for things that go in QR codes, but in those cases we may well not want to pad at all because we're super space constrained (and we'll want to just manually verify that we are always writing paths where forward hops are always the same length and receive hops are always the same length). But there it seems like we should WriteableArgs rather than Writeable?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, that design constraint assumes that we want to pad to make each hop equal (by looking at the max hop length), but I'm not sure that's what we want anyway. Doing so may leak something about the contents still - if we assume the last hop is the max, then we leak the type of context (okay, generally fine), but in the future may leak some info based on if some field is set or not. Instead, we could have a simple PADDING_TARGET_LENGTH constant and always pad to that length in both ForwardTlvs::write and ReceiveTlvs::write (with appropriate debug assertions that by default we never have hops longer than that).

My thoughts regarding padding was more in the context of dummy hops in order to hide which hop is the recipient, not necessarily to prevent leaking the intended use from a forwarder.

Regarding PADDING_TARGET_LENGTH, we couldn't use that for MessageContext::Custom since its size is arbitrary.

This may not be appropriate for things that go in QR codes, but in those cases we may well not want to pad at all because we're super space constrained (and we'll want to just manually verify that we are always writing paths where forward hops are always the same length and receive hops are always the same length). But there it seems like we should WriteableArgs rather than Writeable?

Just so we have some concrete numbers, for an Offer using compact blinded paths, we use 10 bytes per forward hop and 28 bytes for the receive hop. For non-compact blinded paths, the forward hops use 35 bytes each. So we would lose much of the savings by padding each to 28 bytes in the compact case.

Regarding WriteableArgs, we don't have such a trait (yet) -- only ReadableArgs. For use here, the length would need to be piped through construct_blinded_hops to encrypt_payload, and we'd need to change or have a variation of ChaChaPolyWriteAdapter to implement (and wrap a) WriteableArgs.

Though, I'm not sure I quite understand what you mean by "manually verify ..." above. Could you give a more concrete example with a blinded path for an offer?

Idle thought: I recall some conversations about concatenating blinded paths. Is it possible to take a blinded path from an offer and concatenate dummy hops to the end of it? That would seem useful when requesting an invoice to help obscure who the final recipient is, assuming all hops were equally padded.

Copy link
Collaborator

Choose a reason for hiding this comment

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

My thoughts regarding padding was more in the context of dummy hops in order to hide which hop is the recipient, not necessarily to prevent leaking the intended use from a forwarder.

Wait, this PR is just about padding to make individual hops equal-sized, right? Anyone who sees a BP can see how many hops it is.

Regarding PADDING_TARGET_LENGTH, we couldn't use that for MessageContext::Custom since its size is arbitrary.

Right, we'd want to just pad to some multiple of a round number.

Just so we have some concrete numbers, for an Offer using compact blinded paths, we use 10 bytes per forward hop and 28 bytes for the receive hop. For non-compact blinded paths, the forward hops use 35 bytes each. So we would lose much of the savings by padding each to 28 bytes in the compact case.

Right, I don't think we can waste that much.

Regarding WriteableArgs, we don't have such a trait (yet) -- only ReadableArgs. For use here, the length would need to be piped through construct_blinded_hops to encrypt_payload, and we'd need to change or have a variation of ChaChaPolyWriteAdapter to implement (and wrap a) WriteableArgs.

Mmm, yea, that would be a bit annoying code quantity. Another option would be a WithPadding wrapper that wraps a Forward/ReceiveTlvs and specifies the amount of padding we want. We could even drop the direct Writeable on {Forward,Receive}Tlvs at that point. I think my real complaint here is that the API ends up super surprising - a user might build a ReceiveTlvs object manually, add (or not) their own Padding and expect that to be used, but instead LDK silently replaces whatever the user told us to do with what LDK thinks it should do. I think LDK is probably right and should make that decision, but having it in the API is really weird.

Though, I'm not sure I quite understand what you mean by "manually verify ..." above. Could you give a more concrete example with a blinded path for an offer?

As in test outside of test cases but by hand.

Idle thought: I recall some conversations about concatenating blinded paths. Is it possible to take a blinded path from an offer and concatenate dummy hops to the end of it? That would seem useful when requesting an invoice to help obscure who the final recipient is, assuming all hops were equally padded.

Not sure its related, if we want to hide the hop count we can just add dummy hops, this doesn't hide the hop count.

Copy link
Contributor

Choose a reason for hiding this comment

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

Idle thought: I recall some conversations about concatenating blinded paths. Is it possible to take a blinded path from an offer and concatenate dummy hops to the end of it? That would seem useful when requesting an invoice to help obscure who the final recipient is, assuming all hops were equally padded.

I don't think the sender can add dummy hops, per the spec: "the recipient needs to fully validate each dummy hop's onion payload to detect tampering (and must ensure that these hops have been used and not truncated)."

Ah, right. I was discussing this a bit with @valentinewallace and we concluded there's maybe a reason to do it for onion messages (but not clear if you can concatenate blinded paths without the introduction point having helped build the first BP?), but certainly for payments we wouldn't as it reduces your information available on failures.

Yeah, for onion messages if the sender concatenates a blinded path from themselves to the recipient's intro node using next_blinding_override, then said intro node can't tell that they're the intro node. Adding dummy hops doesn't affect whether forwarding nodes can tell where they are along the path, IIUC.

Copy link
Contributor

Choose a reason for hiding this comment

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

We could even drop the direct Writeable on {Forward,Receive}Tlvs at that point.

Not sure if we could drop that because we need it to determine the serialized length without the padding. Maybe it would be implemented on those tupled with Option<u64> to determine whether or not to include padding?

Could have a method that calculates the serialized length without implementing the Writeable trait, I suppose.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think the sender can add dummy hops, per the spec: "the recipient needs to fully validate each dummy hop's onion payload to detect tampering (and must ensure that these hops have been used and not truncated)."

Do we do that? Not sure I quite understand what the spec is saying, though. How can the recipient "ensure that these hops have been used"? IIUC, dummy hops come after the recipient hop.

Yeah, for onion messages if the sender concatenates a blinded path from themselves to the recipient's intro node using next_blinding_override, then said intro node can't tell that they're the intro node. Adding dummy hops doesn't affect whether forwarding nodes can tell where they are along the path, IIUC.

Could you explain how that last part is the case?

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we do that? Not sure I quite understand what the spec is saying, though. How can the recipient "ensure that these hops have been used"? IIUC, dummy hops come after the recipient hop.

We don't add dummy hops atm, therefore we also don't support validation for them (since dummy hops are created by the recipient for themselves).

I think the intention of the spec is for dummy hops to be processed the same as normal hops, to avoid timing attacks. So IIUC they're supposed to be encoded along the lines of how we do phantom nodes, where we process it as a forwarding hop but the next hop is ourselves, and so on until we get to the innermost onion layer that contains our actual recipient payload. This is gleaned from discussions by eclair/cln, though, I would have to look at the eclair codebase to see how they actually do it.

Could you explain how that last part is the case?

I'm not sure how dummy hops could affect how intermediate nodes view their position in the path. Could you explain why you think that is the case? lol

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, right! The payload is always the same size (i.e., never shrinks), so a forwarder can't tell if the next hop is the recipient or not.

@shaavan
Copy link
Contributor Author

shaavan commented Sep 27, 2024

Update: From pr3177.18 to pr3177.19 (diff):

Changes:

  1. Rebase on main to resolve merge conflicts.

@github-advanced-security
Copy link

This pull request sets up GitHub code scanning for this repository. Once the scans have completed and the checks have passed, the analysis results for this pull request branch will appear on this overview. Once you merge this pull request, the 'Security' tab will show more code scanning analysis results (for example, for the default branch). Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results. For more information about GitHub code scanning, check out the documentation.

@shaavan
Copy link
Contributor Author

shaavan commented Oct 1, 2024

Update:
From pr3177.19 to pr3177.20 (diff):
Addressed feedback from @TheBlueMatt and @jkczyz.

Changes:

  1. Simplified the padding approach by removing padding as a field from {Forward, Receive} TLVs.
  2. Introduced a WithPadding generic that handles padding for the given TLVs.

Considerations & Shortcomings:

  • Instead of padding to a maximum value, WithPadding pads the TLVs to a rounded value. This avoids wasting space for very small packets while providing flexibility for future TLV expansions.
  • The current padding round-off value is 50 bytes. I believe this should offer enough buffer to obscure the internal TLV types, but would love to get feedback on a more optimal value.
  • This approach doesn't safeguard against the improper use of a 1 TLV record within the TLVs themselves. We may need to add a comment to prevent this misuse.

Todo:

  • Currently, this approach also pads the compact blinded path. I'll address this in the next update.

@shaavan
Copy link
Contributor Author

shaavan commented Oct 2, 2024

Update: From pr3177.20 to pr3177.21 (diff):
Addressed @TheBlueMatt comment

Changes:

  1. Update code so that we would not be padding the compact blinded paths.
  2. Introduce a test to check this condition.

1. This allows setting the length of padding at the time of writing.
2. This will be used in the following commit to allow setting the
   padding for blinded message paths, and blinded payment paths.
@shaavan
Copy link
Contributor Author

shaavan commented Oct 5, 2024

Update: From pr3177.21 to pr3177.22 (diff):

Changes:

  1. Rebase on main to resolve merge conflicts.

@shaavan
Copy link
Contributor Author

shaavan commented Oct 5, 2024

Update: From pr3177.22 to pr3177.23 (diff):

Changes:

Fixed the Padding approach.

  1. Previous version: Packets were padded individually based on compact forward TLVs. This incorrectly led to padding ReceiveTlvs, even when they belonged to a compact blinded path.
  2. Fix: Now, the entire blinded path remains unpadded if any short_channel_id is present. This ensures correct handling of compact paths and eliminates unnecessary padding.

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

Basically LGTM, thanks!

/// Reads padding to the end, ignoring what's read.
pub(crate) struct Padding {}
/// Represents the padding round off size (in bytes) that is used to pad [`BlindedHop`]
pub const PADDING_ROUND_OFF: usize = 50;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why 50? Are common blinded path hops 50 bytes? We should at least write down how big blinded path hops generally are to make sure we're not cutting right in the middle of common sizes here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why 50? Are common blinded path hops 50 bytes?

Seems this would depend on the received TLVs' size (i.e., how big MessageContext or PaymentContext is for the given use case).

We should at least write down how big blinded path hops generally are to make sure we're not cutting right in the middle of common sizes here.

Could you elaborate on this concern?

Copy link
Collaborator

Choose a reason for hiding this comment

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

My concern is if there's some feature of a blinded path which an observer wants to detect, we don't want to have a constant here that is exactly on the line between that feature and not that feature, otherwise we have gained nothing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

First off, I was able to determine the common sizes for various message and payment TLVs. You can check out the branch where I introduced the test.

After that, I ran some experiments with different padding sizes. It looks like P=50 and P=70 are both solid options. P=50 gave us 3 unique groups, and P=70 reduced it to 2 while keeping the extra padding fairly minimal.

Here's a table for comparison:

Original Sizes Rounded (P=50) Rounded (P=70)
35 50 70
70 100 70
10 50 70
28 50 70
62 100 70
96 100 140
96 100 140
96 100 140
27 50 70
42 50 70
26 50 70
26 50 70
128 150 140
53 100 70

Let me know which size you think we should go with! I'm happy to make any adjustments.

Thanks so much!

Copy link
Contributor

Choose a reason for hiding this comment

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

Could you label the rows by their types?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yea, I thought the question was what was the largest value we'd want to consider when picking the rounding size. I figure we should pick kinda the largest value we expect to send over lightning.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hello!

I’ve updated the htlc_minimum_msat to 1 billion (1 million sats) and adjusted quantity to None (instead of Some(1)) to better align with expected values.

Here’s the branch link for you to check out: branch.

I also ran an analysis to determine the best P value with minimal added padding for the payment TLVs, especially focusing on Payment::ReceiveTlvs. Here’s a quick look at the results:

Type Property Original Sizes Rounded (P=29) Rounded (P=30) Rounded (P=32)
Payment ForwardTlvs
With no Blinding Override 29 29 x 1 = 29 (No Padding) 30 x 1 = 30 (+1 size) 32 x 1 = 32 (+3 size)
With Blinding Override 29 29 x 1 = 29 (No Padding) 30 x 1 = 30 (+1 size) 32 x 1 = 32 (+3 size)
Payment ReceiveTlvs
For Offer 128 29 x 5 = 145 (+17 size) 30 x 5 = 150 (+22 size) 32 x 4 = 128 (No Padding)
For Refund 56 29 x 2 = 58 (+ 2 size) 30 x 2 = 60 (+4 size) 32 x 2 = 64 (+8 size)

From the looks of it, P=32 seems to be the optimal choice for our case with payment TLVs.
Let me know what you think about it! Thanks a lot!

Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't this depend on how many forward TLVs are included?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi!

So, I ran an analysis for different numbers of ForwardTlvs. When there are only a few ForwardTlvs, the best padding value, P, switches between Offer and Refund. However, once the count goes over 5, P=29 generally becomes the optimal choice.

Here’s the table I put together for reference. (I removed the P=30 column since it wasn’t optimal in any scenario.)

ForwardTlvs Count ReceiveTlvs Type Total Size (P=29) Total Size (P=32)
1 Offer 174 *160
1 Refund *87 96
2 Offer 203 *192
2 Refund *116 128
3 Offer 232 *224
3 Refund *145 160
4 Offer 261 *256
4 Refund *174 192
5 Offer 290 *288
5 Refund *203 224
6 Offer *319 320
6 Refund *232 256
7 Offer *348 352
7 Refund *261 288
8 Offer *377 384
8 Refund *290 320
9 Offer *406 416
9 Refund *319 352
10 Offer *435 448
10 Refund *348 384

By the way, in practice, what's the general number of forwardTlvs present in a blinded path?
Thanks a lot!

Copy link
Contributor

Choose a reason for hiding this comment

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

So, I ran an analysis for different numbers of ForwardTlvs. When there are only a few ForwardTlvs, the best padding value, P, switches between Offer and Refund. However, once the count goes over 5, P=29 generally becomes the optimal choice.

Thanks for putting this together.!

By the way, in practice, what's the general number of forwardTlvs present in a blinded path? Thanks a lot!

Probably no more than a two or three. I'd lean towards using 32 or something slightly higher to prevent a quantity in the ReceiveTlvs from adding another block of padding. Though including a payer note will already cause that.


/// Represents optional padding for encrypted payloads.
/// Padding is used to ensure payloads have a consistent length.
pub(crate) struct Padding {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we split Padding into a write-side one and a read-side one now that we only use the write-side one here and the read-side one elsewhere in the module? Also because its weird that length is only used on the write side.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That makes sense! I’m just wondering though, would introducing two separate structs for Padding add extra complexity to the code?

lightning/src/blinded_path/utils.rs Outdated Show resolved Hide resolved
lightning/src/blinded_path/utils.rs Outdated Show resolved Hide resolved
lightning/src/blinded_path/utils.rs Outdated Show resolved Hide resolved
/// Reads padding to the end, ignoring what's read.
pub(crate) struct Padding {}
/// Represents the padding round off size (in bytes) that is used to pad [`BlindedHop`]
pub const PADDING_ROUND_OFF: usize = 50;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why 50? Are common blinded path hops 50 bytes?

Seems this would depend on the received TLVs' size (i.e., how big MessageContext or PaymentContext is for the given use case).

We should at least write down how big blinded path hops generally are to make sure we're not cutting right in the middle of common sizes here.

Could you elaborate on this concern?

lightning/src/blinded_path/utils.rs Outdated Show resolved Hide resolved
@shaavan
Copy link
Contributor Author

shaavan commented Oct 10, 2024

Update: From pr3177.23 to pr3177.24 (diff):

Addressed @jkczyz comments

Changes:

  1. Fix typo, and visibility.

Add a generic `WithPadding` struct to handle padding for `ForwardTlvs`
and `ReceiveTlvs` used in `BlindedMessagePath` and `BlindedPaymentPath`.
This struct applies padding to the contained TLVs, rounding them off to
a specified value.

This design provides flexibility in padding TLVs of varying sizes. The
`PADDING_ROUND_OFF` value is chosen to be sufficiently large to
properly mask the data type of the contained TLVs.
A note of Compact Blinded Paths:

Compact Blinded paths are intended to be as short as possible. So
to maintain there compactness, we don't apply padding to them.
Add test to verify blinded message and payment path padding.
@shaavan
Copy link
Contributor Author

shaavan commented Oct 30, 2024

Update: From pr3177.24 to pr3177.25 (diff):
Addressed comments from @jkczyz and @TheBlueMatt.

Changes:

  1. Introduced different padding round-off for message and payment TLVs.
  2. Padding sizes were chosen based on the discussion in this thread.

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

Successfully merging this pull request may close these issues.

6 participants