-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
[1/4] Route Blinding Receives: Groundwork #8752
base: master
Are you sure you want to change the base?
Conversation
Important Review skippedAuto reviews are limited to specific labels. Labels to auto review (1)
Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (invoked as PR comments)
Additionally, you can add CodeRabbit Configration File (
|
485b765
to
451bda7
Compare
e92e850
to
d537c35
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.
Awesome that you managed to do end-to-end payments to blinded routes using Bolt 11, very cool work and a big achievement 🎉! I have looked a bit ahead how everything fits together in the next PRs, but will study them more in depth.
// on `var_onion_option`, we can assume that all nodes | ||
// in a blinded path support TLV onions without this | ||
// being explicitly communicated to us. | ||
if isBlindedEdge && isTLVOptionBit(feature) { |
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.
Up to the commit here isBlindedEdge
could also be extracted from blindedPath
comparing node pubkeys, which is why I think the previous addition of of BlindedPayment()
to the AdditionalEdge
interface isn't needed. But I see that you later change this to blindedPathSet
. I'm not yet sure how that will work, maybe it would be better to shift the additions of BlindedPayment
to a commit where it becomes important.
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.
this whole PR is just set-up for the follow up PRs. It tries to get all the plumbing out the way in prep for those PRs so that there is less noise there.
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.
yes at the moment there is only a single blinded payment that this edge could have come from but eventually (in upcoming PR), it will be a set of paths. Then we dont want to have to iterate through every path etc etc to find the path this edge is part of. Also, if this is an introduction node, then it could be that this node is the intro node to multiple different payments in which case we cant just compare pub keys to know which one it is part of.
) | ||
} | ||
|
||
if constraints != nil { |
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.
this represents data that is used by the receiver, right? are constraints then needed in the receiver case?
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.
yes this is used by the receiver.
im not sure I follow the second part of the question?
// is the same size and so each call will overwrite the Padding record. | ||
// Note that calling PadBy with an n value of 0 will still result in a zero | ||
// length TLV entry being added. | ||
func (b *BlindedRouteData) PadBy(n int) { |
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.
wouldn't it make sense to implement the exact version? We could assume the type to be constant size and the length to be variable to make it easier. We would just need to know the bigint transitions, right? It would make this call easier and safer to use
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.
implement the exact version?
this cannot be done without going and changing the internals of the TLV package. Or do you mean doing the iterative stuff here instead of letting the caller do it?
if you mean that, then the reason why not that is cause only the caller knows the sizes of all the packages. it could be that all of them need to be buffered. Ie, when we call this the first time, we dont necessarily know the final size to pad by.
(more detail in commit message)
|
||
// BlindedPaymentPath holds all the information a payer needs to know about a | ||
// blinded path to a receiver of a payment. | ||
type BlindedPaymentPath struct { |
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 think it makes sense to put this into the zpay32 package, just wanted to note that there is also routing.BlindedPayment
, with similar data.
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.
just checking if there is an actionable item here or just a note?
d537c35
to
04d97a4
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.
Thanks for the review @bitromortac 🙏
Updated & responded
// on `var_onion_option`, we can assume that all nodes | ||
// in a blinded path support TLV onions without this | ||
// being explicitly communicated to us. | ||
if isBlindedEdge && isTLVOptionBit(feature) { |
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.
this whole PR is just set-up for the follow up PRs. It tries to get all the plumbing out the way in prep for those PRs so that there is less noise there.
) | ||
} | ||
|
||
if constraints != nil { |
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.
yes this is used by the receiver.
im not sure I follow the second part of the question?
// is the same size and so each call will overwrite the Padding record. | ||
// Note that calling PadBy with an n value of 0 will still result in a zero | ||
// length TLV entry being added. | ||
func (b *BlindedRouteData) PadBy(n int) { |
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.
implement the exact version?
this cannot be done without going and changing the internals of the TLV package. Or do you mean doing the iterative stuff here instead of letting the caller do it?
if you mean that, then the reason why not that is cause only the caller knows the sizes of all the packages. it could be that all of them need to be buffered. Ie, when we call this the first time, we dont necessarily know the final size to pad by.
(more detail in commit message)
|
||
// BlindedPaymentPath holds all the information a payer needs to know about a | ||
// blinded path to a receiver of a payment. | ||
type BlindedPaymentPath struct { |
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.
just checking if there is an actionable item here or just a note?
// on `var_onion_option`, we can assume that all nodes | ||
// in a blinded path support TLV onions without this | ||
// being explicitly communicated to us. | ||
if isBlindedEdge && isTLVOptionBit(feature) { |
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.
yes at the moment there is only a single blinded payment that this edge could have come from but eventually (in upcoming PR), it will be a set of paths. Then we dont want to have to iterate through every path etc etc to find the path this edge is part of. Also, if this is an introduction node, then it could be that this node is the intro node to multiple different payments in which case we cant just compare pub keys to know which one it is part of.
85a1b2a
to
aded983
Compare
@bitromortac: review reminder |
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.
Really cool change to allow for Blinded Paths in Bolt11 invoices, this PR looks really good ⭐️, will approve as soon as I reviewed the second one to gain more context.
) | ||
|
||
const ( | ||
// relayInfoSize is the number of bytes that the relay info of a blinded |
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.
According to BOLT04 the relay info does only include:
data:
[u16:cltv_expiry_delta]
[u32:fee_proportional_millionths]
[tu32:fee_base_msat]
I wonder why we include the min max htlc 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.
see blinded_payinfo
in the offers spec: https://github.com/rustyrussell/lightning-rfc/blob/f150782106ac604b8bb056dcd0c0cbe757274e1d/12-offer-encoding.md?plain=1#L631
took the idea from their.
Also, the relay_info
you are referring to is for the payload we deliver to each hop. This is a bit different as this is the relay info for the whole path. So the sender is given some more info about what size payment they can actually send down each path.
// EncodeBlindedHop writes the passed BlindedHopInfo to the given writer. | ||
// | ||
// 1) Blinded node pub key: 33 bytes | ||
// 2) Cipher text length: 2 bytes |
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.
isn't the length of the cypertext a bigsize type, at least that's also what is used below.
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.
at least that's also what is used below.
Not sure i see this?
The code below uses a fixed 2 byte length. We can change the encoding to bigsize if you think that would be better though?
@@ -6946,6 +6946,13 @@ func (r *rpcServer) DecodePayReq(ctx context.Context, | |||
// Convert between the `lnrpc` and `routing` types. | |||
routeHints := invoicesrpc.CreateRPCRouteHints(payReq.RouteHints) | |||
|
|||
blindedPaymentPaths, err := invoicesrpc.CreateRPCBlindedPayments( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we check that either route hints or the blinded path is provided not both ? I guess this is already checked in validateInvoice
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.
yeah to me cause this is Decode
- we dont need to check it here. Decode is just about giving more info about what the invoice contains and shouldnt break when we can actually decode the thing but we see some values dont make sense. For example this does also not check the signature
} | ||
|
||
numHops := len(p.Hops) | ||
if numHops > math.MaxUint8 { |
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.
Q: Is the limit of the number of hops als the overall onion package size (1300 bytes) ?
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.
yes definitely - the question is: should we do that check here? also - if we do, it probably makes sense to force it to be a number way less than 1300 right cause that 1300 still needs to include other hop payloads. So im wondering how we can determine a good magic number for this... perhaps just checking < 1300 is fine for now?
@@ -76,6 +76,10 @@ const ( | |||
// probing the recipient. | |||
fieldTypeS = 16 | |||
|
|||
// fieldTypeB contains blinded payment path information. This field may |
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.
Wondering if we should add an amendment to the BOLT11 spec or something were we mention that we use type=20 for blinded paths ?
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.
yes there will defs need to be a bolt11 spec addition
@@ -805,25 +889,42 @@ func TestNewInvoice(t *testing.T) { | |||
valid: true, | |||
encodedInvoice: "lnbcrt241pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66df5c8pqjjt4z4ymmuaxfx8eh5v7hmzs3wrfas8m2sz5qz56rw2lxy8mmgm4xln0ha26qkw6u3vhu22pss2udugr9g74c3x20slpcqjgq0el4h6", | |||
}, | |||
{ | |||
// Mainnet invoice with two blinded paths. |
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.
Q: maybe it makes sense to make this test-vector available via a blip ?
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.
well it would be part of the bolt11 addition right?
This commit is purely a refactor. In it, we let the `BlindedEdge` struct carry a pointer to the `BlindedPayment` that it was derived from. This is done now because later on in the PR series, we will need more information about the `BlindedPayment` that an edge was derived from. Since we now pass in the whole BlindedPayment, we swap out the `cipherText` member for a `hopIndex` member so that we dont carry around two sources of truth in the same struct.
Expand the AdditionalEdges interface with a BlindedPayment method. In upcoming commits, we will want to know if an AdditionalEdge was derived from a blinded payment or not and we will also need some information from the blinded payment it was derived from. So we expand the interface here to avoid needing to do type casts later on. The new method may return nil if the edge was not derived from a blinded payment.
Add a constructor for unified edge. In upcoming commits, we will add a new member to unifiedEdge and a constructor forces us to not forget to populate a required member.
Later on in this series, we will need to know during path finding if an edge we are traversing was derived from a blinded payment path. In preparation for that, we add a BlindedPayment member to the `unifiedEdge` struct.
We can make some assumptions about the feature set of a blinded edge without having to be explicity told what those features are. Specifically, this applies to the `var_onion_option` feature bits which can be assumed for any node advertising `option_route_blinding`. See [this](https://github.com/lightning/bolts/blob/db278ab9b2baa0b30cfe79fb3de39280595938d3/proposals/route-blinding.md?plain=1#L219) suggestion in the route blinding proposal. This will allow us to condense the information that a recipient needs to send to the sender. This change is tested by updating the TestBlindedRouteConstruction test to no longer set the Features field in the BlindedPayment struct.
For the final hop in a blinded route, the SCID and RelayInfo fields will _not_ be set. So these fields need to be converted to optional records. The existing BlindedRouteData constructor is also renamed to `NewNonFinalBlindedRouteData` in preparation for a `NewFinalBlindedRouteData` constructor which will be used to construct the blinded data for the final hop which will contain a much smaller set of data. The SCID and RelayInfo parameters of the constructor are left as non-pointers in order to force the caller to set them in the case that the constructor is called for non-final nodes. The other option would be to create a single constructor where all parameters are optional but I think this makes it easier for the caller to make a mistake.
Add the PathID (tlv type 6) field to BlindedRouteData. This will be used for the final hop of a blinded route. A new constructor is also added for BlindedRouteData which can specifically be used for the final hop.
When we start creating blinded paths to ourselves, we will want to be able to pad the data for each hop so that the `encrypted_recipient_data` for each hop is the same. We add a `PadBy` method that allows a caller to add a certain number of bytes to the padding field. Note that adding n bytes won't always mean that the encoded payload will increase by size n since there will be overhead for the type and lenght fields for the new TLV field. This will also be the case when the number of bytes added results in a BigSize bucket jump for TLV length field. The responsibility of ensuring that the final payloads are the same size is left to the caller who may need to call PadBy iteratively to achieve the goal. I decided to leave this to the caller since doing this at the actual TLV level will be quite intrusive & I think it is uneccessary to touch that code for this unique use case.
Only include the final hop's cltv delta in the total timelock calculation if the route does not include a blinded path. This is because in a blinded path, the final hops final cltv delta will be included in the blinded path's accumlated cltv delta value. With this commit, we remove the responsibility of remembering not to set the `finalHop.cltvDelta` from the caller of `newRoute`. The relevant test is updated accordingly.
In this commit, the ability is added to encode blinded payment paths and add them to a Bolt 11 invoice.
This commit adds a blinded_paths field to the PayReq proto message. A new helper called `CreateRPCBlindedPayments` is then added to convert the zpay32 type to the existing `lnrpc.BlindedPaymentPath` type and add this to the `PayReq` in the `DecodePayReq` rpc method.
aded983
to
55ed46c
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.
thanks for the review @ziggie1984! 🙏
) | ||
|
||
const ( | ||
// relayInfoSize is the number of bytes that the relay info of a blinded |
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.
see blinded_payinfo
in the offers spec: https://github.com/rustyrussell/lightning-rfc/blob/f150782106ac604b8bb056dcd0c0cbe757274e1d/12-offer-encoding.md?plain=1#L631
took the idea from their.
Also, the relay_info
you are referring to is for the payload we deliver to each hop. This is a bit different as this is the relay info for the whole path. So the sender is given some more info about what size payment they can actually send down each path.
// EncodeBlindedHop writes the passed BlindedHopInfo to the given writer. | ||
// | ||
// 1) Blinded node pub key: 33 bytes | ||
// 2) Cipher text length: 2 bytes |
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.
at least that's also what is used below.
Not sure i see this?
The code below uses a fixed 2 byte length. We can change the encoding to bigsize if you think that would be better though?
@@ -76,6 +76,10 @@ const ( | |||
// probing the recipient. | |||
fieldTypeS = 16 | |||
|
|||
// fieldTypeB contains blinded payment path information. This field may |
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.
yes there will defs need to be a bolt11 spec addition
@@ -805,25 +889,42 @@ func TestNewInvoice(t *testing.T) { | |||
valid: true, | |||
encodedInvoice: "lnbcrt241pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66df5c8pqjjt4z4ymmuaxfx8eh5v7hmzs3wrfas8m2sz5qz56rw2lxy8mmgm4xln0ha26qkw6u3vhu22pss2udugr9g74c3x20slpcqjgq0el4h6", | |||
}, | |||
{ | |||
// Mainnet invoice with two blinded paths. |
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.
well it would be part of the bolt11 addition right?
} | ||
|
||
numHops := len(p.Hops) | ||
if numHops > math.MaxUint8 { |
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.
yes definitely - the question is: should we do that check here? also - if we do, it probably makes sense to force it to be a number way less than 1300 right cause that 1300 still needs to include other hop payloads. So im wondering how we can determine a good magic number for this... perhaps just checking < 1300 is fine for now?
@@ -6946,6 +6946,13 @@ func (r *rpcServer) DecodePayReq(ctx context.Context, | |||
// Convert between the `lnrpc` and `routing` types. | |||
routeHints := invoicesrpc.CreateRPCRouteHints(payReq.RouteHints) | |||
|
|||
blindedPaymentPaths, err := invoicesrpc.CreateRPCBlindedPayments( |
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.
yeah to me cause this is Decode
- we dont need to check it here. Decode is just about giving more info about what the invoice contains and shouldnt break when we can actually decode the thing but we see some values dont make sense. For example this does also not check the signature
To make review a bit more manageable, I've split out some of the groundwork needed for
the main route blinding receives PR. This PR thus has no functional changes.
Tracking Issue: #6690
PR Overview:
BlindedPayment
associated with an edge in path finding later on.BlindedRouteData
that will be required for receives. Namely, PathID and Padding.