-
Notifications
You must be signed in to change notification settings - Fork 376
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
Support custom onion messages #1748
Support custom onion messages #1748
Conversation
597f4c3
to
a9883b1
Compare
Continuing from our offline discussion yesterday, given for offer messages we need the bytes when parsing anyhow, can't we leave |
Ah, I guess for custom message support we may need something like this. Though, given the above, wasting CPU shouldn't be a concern. |
In the context of:
My impression is that peers could still waste a significant amount of our processing time/heap if we don't satisfy this requirement 🤷♀️. This sorta makes sense to me because it could end up being a lot of vec allocs even if we just read bytes (and if we're reading a sufficient quantity of bytes, that could still add up?). But yeah, 100% also custom OMs. |
Thinking about this more, this actually prevents the more general case (outside of onion messages) where there can be more than one custom type in a TLV stream. Given we aren't later going to parse these messages unless the code actually understands the type, I think we only want to pass supported custom types to the macro (rather than a range). This would limit what actually gets parsed. Otherwise, in the more general case, a malicious sender could still provide a message with many custom types. For onion message payloads, we may still want to configure them as |
We'll fail mid-deserialization if more than one type in the given range is set, so thankfully this shouldn't be an issue. I'm a bit confused how we'd support user-provided custom types if we only parse types we understand? I think it's OK to punt the general case of multiple custom TLVs (I'm not clear on whether we ever plan to support this) but open to ideas here. |
I was referring to the more general case (not onion messages) where having multiple custom types is possible. We don't want
To be more precise, by "we" I meant some LDK component parameterized with a user-supplied handler. Currently, we have the decoder pass all the types LDK supports to For
My hesitancy here is that it is adding onion message semantics to |
lightning/src/util/ser_macros.rs
Outdated
@@ -175,7 +175,8 @@ macro_rules! decode_tlv { | |||
} | |||
|
|||
macro_rules! decode_tlv_stream { | |||
($stream: expr, {$(($type: expr, $field: ident, $fieldty: tt)),* $(,)*}) => { { | |||
($stream: expr, {$(($type: expr, $field: ident, $fieldty: tt)),* $(,)*} | |||
$(, ($custom_type: tt, $custom_value: ident, $custom_range_min: expr, $custom_range_max: expr))?) => { { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Catching up with the conv, IIUC each custom TLV inside the onion would have min/max bounds defined at the protocol-level ? Otherwise if DoS protections are per-implementation, you might fail deser of a message gauged by valid as the sending implementation. I still have in mind the use-case of sending Lightning transactions inside onions, and a commitment transaction size can dynamic on the number of HTLC outputs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC each custom TLV inside the onion would have min/max bounds defined at the protocol-level ?
I don't think the bounds are spec'd for custom TLVs, but yup.
I still have in mind the use-case of sending Lightning transactions inside onions, and a commitment transaction size can dynamic on the number of HTLC outputs.
Cool, that should be supported. Currently we support reading 1 TLV within the type range 64..u64::max_value()
(can be an invoice_request, etc or custom). Lmk if there's a change desired here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC each custom TLV inside the onion would have min/max bounds defined at the protocol-level ?
IIUC the closest spec is that the generalized TLV format defines that types between 2^16 and u64::max_value() are defined as custom types.
https://github.com/lightning/bolts/blob/16973e2b857e853308cafd59e42fa830d75b1642/01-messaging.md?plain=1#L131
Ah, I think I see what you're saying. To restate: in this scheme, we would be initializing each known type (including the custom type) as The one concern I have is that
We could have a separate |
a9883b1
to
f2ab71c
Compare
Sorta. I'm just saying to treat custom types like standard types since they should be known at compile-time, even if configured by the user. Then at the onion-message layer, enforce that there can only be one. Given that, I'm not really convinced there should be a concern with excessive heap allocations for onion message payloads since the number of allocations would be be limited to known types assuming each are parsed into
Yeah, would need to either pass a type or do something like
Yeah, I think calling
Ah, sorry. I guess "prevents" was a bit too strong or phrased poorly. I meant custom message support as written can't be reused for the more general case of multiple custom types. So we would have to add another param to |
We discussed this offline and concluded that we should first attempt replacing the in-range-read macro update with a lambda and see how that works. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Started reviewing this yesterday before the comment about first proceeding with the in-range-read macro. I'm leaving some comments in case there's value in them once this is picked up again :).
@@ -188,8 +193,15 @@ impl ReadableArgs<SharedSecret> for Payload { | |||
Ok(Payload::Forward(ForwardControlTlvs::Unblinded(tlvs))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
Hmmm I think the spec is a bit unclear here, but as the spec specifically states:
"Field numbers 64 and above are reserved for payloads for the final hop."
Given that phrasing, would we want this to fail and return anErr
ifmessage_type
is set to an even number and it's aPayload::Forward
? -
Given the discussion about custom TLV types in this PR, and that this PR adds support custom types for the
Payload
"data" TLVs, maybe it's also worth adding here that the onion message test vectors also includes custom tags/types inside theencrypted_tlv_stream
(the type 4 here aka our "control" TLVs). They're present for both Bob's (non-final hop) and Dave's (final hop) payloads. Would we like this PR to also update ourControlTlvs
to support such types, in order to be able to use theControlTlvs
enums to represent that for the test vectors, or is that out of scope for this PR as those aren't the actual onion message?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would we want this to fail and return an Err if message_type is set to an even number and it's a Payload::Forward?
Good catch, adding this!
Re (2) I didn't quite get that, are you asking if we plan to support users sending custom control
TLVs? I believe that'd be out-of-scope for now
} | ||
|
||
let two_unblinded_hops_om = "020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000000000000000000000000000000000000000000000000000e0135043304210200000000000000000000000000000000000000000000000000000000000000039500000000000000000000000000000058000000000000000000000000000000000000000000000000000000000000001204105e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b300000000000000000000000000000000000000000000000000000000000000"; | ||
let two_unblinded_hops_om = "020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000000000000000000000000000000000000000000000000000e0135043304210200000000000000000000000000000000000000000000000000000000000000029500000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000003604104b000000000000000000000000000000fd1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b200000000000000000000000000000000000000000000000000000000000000"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OT: Would we also like the payloads for the fuzzing to contain padding to better align with the spec? I could look into that for the add TLV padding PR that I haven't pushed yet (It's about time that I push it, so I'll do it asap).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yeah when padding is added the fuzz vectors will need to be updated again (since presumably most/all OMs will contain padding for at least one hop, which then changes the hmac of the payload). I can send you the test harness I set up to generate these vectors when the PR is open :) (no rush on opening it tho)
Prevents unresolved import error
f2ab71c
to
a0997ed
Compare
Codecov ReportBase: 90.72% // Head: 92.08% // Increases project coverage by
Additional details and impacted files@@ Coverage Diff @@
## main #1748 +/- ##
==========================================
+ Coverage 90.72% 92.08% +1.35%
==========================================
Files 87 87
Lines 46688 57396 +10708
Branches 46688 57396 +10708
==========================================
+ Hits 42360 52853 +10493
- Misses 4328 4543 +215
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report at Codecov. |
} | ||
message_type = Some(msg_type); | ||
decode_tlv!(msg_reader, message_bytes, vec_type); | ||
return Ok(true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jkczyz Should this bool be an enum? Should we get rid of the result and always return a trinary enum? Let me know what you prefer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had been thinking the custom decoder could signal an unknown type by returning a variant of DecodeError
(possibly a new variant). This would presumed to be returned only if none of the bytes were consumed (i.e., only when checking the type). Otherwise, another custom decoder could not process it if the previous one consumed some bytes. If we make a trait for the custom decoder, we could have a separate method for checking if it understands the type and another method for decoding, perhaps?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise, another custom decoder could not process it if the previous one consumed some bytes.
Hmm, do we have a use-case for this? A given TLV should really only have one possible decoder, so once we pass the type through to the decode logic the decode logic should be able to select the specific decoder required, I'd think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I think that I misread the ?
repetition as *
and thought this would handle known payloads (e.g., invoice_request
) as a separate custom decoder. I guess logic for reading such known types would be added to the closure?
Related: Will handling be separate from decoding? If so, will we need the type still to figure out if handler for a known type is needed or a custom type, IIUC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I was anticipating multiple reads being handled all within the same closure. For handling, I was anticipating we'd do something like the custom P2P message handler - have an enum with all the messages we handle, and allow the user to specify the type for a Custom
variant, which they'd presumably implement as their own enum.
a0997ed
to
ded359c
Compare
return Err(DecodeError::InvalidValue) | ||
} | ||
message_type = Some(msg_type); | ||
decode_tlv!(msg_reader, message_bytes, vec_type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to call decode_tlv
just to read_to_end
a reader, but, more generally, I don't think this is sufficient. Instead of reading the TLV into a Vec
, we should actually call a decode method based on the type. While we don't define any specific readers, we should have a trait, like CustomMessageReader, which we pass the reader here through to, and then have it read some generic enum that the user provides.
I think we should be able to clean up the tests to not use log assertions anymore after this, but would appreciate some conceptual feedback on the latest approach first |
95bf6cd
to
cadc6ad
Compare
4739d22
to
733e562
Compare
48c41d7
to
92dc561
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to squash
92dc561
to
17b22e0
Compare
Last commit needs to be reworded to describe the new behavior, also would kinda prefer it be split into the ser-changes/OM parts. |
#[derive(Debug)] | ||
/// The contents of an onion message. In the context of offers, this would be the invoice, invoice | ||
/// request, or invoice error. | ||
pub enum Message<T> where T: OnionMessageContents { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type name conflicts with wire::Message
which will break bindings (and be annoying for rust imports, in some cases).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe name it OnionMessageContents
and rename that OnionMessageType
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Went with Message --> OnionMessageContents
and OnionMessageContents --> CustomOnionMessageContents
Useful in decoding a custom message, so (a) the message type can be provided to the handler and (b) None can be returned if the message type is unknown. Used in upcoming commit(s) to support custom onion messages.
Useful for decoding custom or user-provided TLVs. See macro docs for more info. Used in upcoming commit(s) to support custom onion message TLVs
OnionMessenger::new will now take a custom onion message handler trait implementation. This handler will be used in upcoming commit(s) to handle inbound custom onion messages. The new trait also specifies what custom messages are supported via its associated type, CustomMessage. This associated type must implement a new CustomOnionMessagesContents trait, which requires custom messages to support being written, being read, and supplying their TLV type.
OnionMessageContents specifies the data TLV that the sender wants in the onion message. This enum only has one variant for now, Custom. When offers are added, additional variants for invoice, invoice_request, and invoice_error will be added. This commit does not actually implement sending the custom OM contents, just the API change.
17b22e0
to
bda54b1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One question I'd like an answer on, but if the answer is "yes", then I'm happy to land this.
if $decode_custom_tlv(t, &mut s)? { | ||
// If a custom TLV was successfully read (i.e. decode_custom_tlv returns true), | ||
// continue to the next TLV read. | ||
s.eat_remaining()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: technically this potentially violates the TLV spec, which mandates that the TLV be exactly the expected length. I don't think it matters much, and, really, for users writing custom messages I think we should maybe prefer to be more flexible here cause users may want the extensibility. Just highlighting.
This uses the work done in the preceding commits to implement encoding a user's custom TLV in outbound onion messages, and decoding custom TLVs in inbound onion messages, to be provided to the new CustomOnionMessageHandler.
Onion message data TLV types must be >= 64, enforce this on send
4905268
bda54b1
to
4905268
Compare
Disallowed any Only diff is:
|
In ser_macros, we now allow decode_tlv_stream to decode 1 custom TLV within a
specified range of TLV types. We also need to fail to mid-deserialization if there is
more than 1 TLV in that specified range, otherwise peers could waste our
processing time.
This change is needed to support offers and async payment onion messages, for
the above peers-can-waste-cpu reason.
TODO: more docs,
fix fuzz