-
Notifications
You must be signed in to change notification settings - Fork 111
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
RFC-3: Add hop limit to ILP packet #330
Comments
I think this can exist outside the packet in the same data space as the transfer amount and expiry. This avoids the need to set it to 0 for hashing |
I just remembered a thought I had a while ago: Unlike IP, ILP is actually stateful. So when a payment hits a routing loop, it will eventually reach a connector that already has a previous transfer in that payment chain prepared. So we could make a rule that someone (connector/ledger/plugin, not sure which) will reject a transfer if it has a transfer with the same condition already prepared. That would break the current proposal for optimistic-over-universal (OOU) which proposes to use a well-known condition. But there is a different scheme that would still work: We would define a format that all transport layer protocols built on OOU would follow. For example: OOU payments start with the bytes Benefits of this approach are:
Unless someone sees an issue with this alternative, I'll withdraw this proposal. |
maybe for dealing with the OOU issue, it makes more sense to say: if the condition, and the destination, and the amount, and the data, are all the same, then it's the same payment looping. if the condition is the same (as it will be for two simultaneous OOU payments) but some of the other three things are different, then we know these are two simultaneous but unrelated OOU payments. |
I think we should make it mandatory that the condition is different for all payments. WDYT? |
I don't think that's a good idea. Doing so just means that anyone who would want to use a well-known condition will then be forced to use random unfulfillable conditions. If people want to build protocols that use unfulfillable payments on top of ILP it'll be nearly impossible to stop it. If that's going to happen it's better for connectors to specifically handle it than force protocol designers to make it indistinguishable. |
Why is that a problem? |
Actually, bigger problem with making it mandatory for all conditions to be different: that makes it trivial to prevent someone's payment from going through, just by sending a payment with the same condition across its path before it completes. This is why we originally made transfer IDs unique on a per-ledger basis and made the connector change the transfer ID. You can't change the condition so you can't enforce uniqueness across the chain. |
I think there are other ways to signal to connectors that a payment is intended to fail. As a connector I think it would be useful to be able to rely on the assumption that conditions are always unique |
How realistic is this? You're talking about front-running a payment along part of it's path that would require some pretty complex observation of the first transfers and the ability to inject transfers ahead of it. That seems unlikely or near impossible to actually do and I'm not sure why you would do it. |
For one thing that would make it impossible to use a public ledger. It would be super easy to look at escrow transactions going into the ledger, guess from the interledger address what route the packet is going to take, and block it. You could do it just because you don't like a certain blockchain and want the users of that chain to have a bad experience. Going back to the original discussion, why would it be beneficial to force conditions to be unique? If we're just talking about detecting loops, I like @michielbdejong's suggestion that you can look if the condition, data, and destination address are the same as a payment you are already waiting on the fulfillment for. I think it's a good bet that at least one of those would be different and I can't think of a reasonable use case that would involve sending an exact duplicate like that. |
It's not clear to me how you propose they would do this? You assume that knowing the connector means I know how they'll route the next hop and that I can somehow front-run that. |
I think people will assume they are and will build stuff based on that assumption. It's a foot-gun we can avoid by simply saying they MUST be unique per sender. A better way to express it is that fulfillments MUST include a some randomness per tx. |
We continue to make this assumption anyway by building everything using PSK |
As a sender you SHOULD make all of your payment conditions unique UNLESS you know they are unfulfillable anyway. However, that is very different than enforcing global condition uniqueness.
There should be no assumptions about this. The spec should clearly state what should or must be unique and in what context.
Really? I think that would be so easy. Let's say there are between 1 and 20 routes a payment could take from the connector to the destination. The connector is going to wait until the transfer is confirmed in the ledger before forwarding it, so you might have a lot of time if we're talking about a slow ledger. Then you construct a couple of payments that go across the likely routes. It actually doesn't even matter if they are fulfilled payments because global condition uniqueness would mean you have to reject a second prepared payment with the same condition. Even if you assume this wouldn't work all the time (I think it could), it still means that you could intentionally degrade someone's quality of service just by watching public activity. |
Ok, I'm convinced 😄 That said, the same threat exists if it becomes known that a specific connector or connector implementation uses a specific set of fields to determine that a tx is unique. It is then possible to do the same thing right? |
Eek that's a good point. That might mean this isn't a good way of doing loop detection, basically because it'll always be easy to trick someone into thinking the real payment is a duplicate if you know what the payment looks like before it gets to them and can front-run the real one. |
Doing a loop detection by looking at everything except the expiry might work because if I wanted to front-run your payment then I'd have to make a real payment that will succeed and cost me money. Assuming the response is not sensitive (or is encrypted) the only risk then is that the original sender never gets the response (fulfillment and response data) but you could argue they can get that directly from the payee and now their payment wads paid by someone else 😄 |
This raises a very good reason why you should:
|
Actually, I think they would get it! If a connector gets a duplicate payment they are going to want to get double-paid and as an honest connector, they would have no issue passing the correct fulfillment data to both sources. Proposed behaviorWhen receiving a new payment, the connector will check if there is already a payment prepared with:
If there is, the connector does not forward the new incoming payment. But it does attach it to the existing payment's promise. So if the outgoing transfer is fulfilled, the connector will pass the fulfillment and attached data back to both sources. If it is rejected, the connector will pass the rejection information back to both sources. PropertiesAn attacker can use this to learn the fulfillment data, but that could be encrypted with a shared secret between the sender and receiver. Also, to use this attack the attacker has to know the condition, which means they're already tapping the payment chain somehow, so they're probably already able to see the fulfillment data go by as well. The attacker can't use this to disrupt the payment or steal money because, from the sender's perspective, the payment completes normally. The attacker is merely donating money to the connector and speeding up the payment slightly. In the case of an unintentional routing loop, this behavior stops the loop because any connector following these rules will not forward the payment the second time. The payment will time out. The behavior introduces some overhead because we have to check new payments against existing prepared ones. However, we already have to keep all prepared payments in memory, so this is merely adding an index and comparing each new payment against the index. Note that we can use a hash table which means the lookup is constant time. Game theoretical considerationsThe connector would have a temptation to only check the condition for uniqueness. After all, whenever they have two incoming payments with the same condition, they should only need to forward one of them. However, note that in practice, if they forward an attacker's payment (to the wrong destination or with the wrong data) and don't forward the legitimate payment, both payments will just time out and the connector will get nothing. So I think that even a greedy connector will follow the correct behavior outlined above because the payment has a higher chance of being completed and therefore the connector has a higher chance of making money. On the return path, they could fulfill all incoming payments with the same condition as soon as one of them is fulfilled. However, by that point, they have already forwarded the second payment, so they would likely prefer to wait for the actual fulfillment with the actual fulfillment data because they get the same amount of money, appear less faulty and are more likely to trigger repeat business. (Passing back correct fulfillment data = less dropped payment stream = more repeat business.) Finally, a selfish connector could keep track of all already fulfilled conditions and fulfill any new payments matching a known condition without data. This is, of course, something they can do regardless of our recommendation for loop prevention, so it's somewhat outside of the scope of this issue. What it means is that if an attacker can intercept your condition, somehow get it fulfilled before your payment even arrives and the connector in question is selfish/dodgy enough, they could indeed prevent you from getting fulfillment data. But I strongly suspect this wouldn't be much of an issue in practice. If I'm a merchant and I'm using @sharafian's connector which I detect has this selfish behavior, I'm going to switch asap. Since the issue only occurs when the attacker's payment can get fulfilled very quickly, i.e. the dodgy connector is very close to the receiver, I don't think it's likely to be worth it for connectors to do this, because they would be hurting their customers or customers' customers, which is likely going to make them unpopular very quickly. Further note that it all depends on an attacker that is willing to pay for the sender's payment just so they don't get fulfillment data and who can pass messages significantly faster than the network. That probably won't be common, certainly not common enough for connectors to risk their reputation over it. ConclusionThe behavior above is sufficient to prevent loops without enabling any new attacks. A hop limit field is not needed in Interledger. |
A hop limit (also known as time-to-live) is useful to prevent packets looping in a packet switching network forever. We previously assumed that the ILP amount could act as a quasi TTL: If a packet started looping, it would eventually run out of money.
However, that solution is undesirable for a number of reasons:
As a result, it seems far superior to set a hop limit explicitly.
I propose adding a field to the ILPv2 payment packet of type
UInt8
calledhopLimit
which is decremented each time the packet is forwarded. When this value reaches zero, the packet is rejected by the next connector with a new ILP Error F09 Hop Limit Exceeded.Transport layer protocols which hash the ILP packet should set this value to zero before hashing.
The text was updated successfully, but these errors were encountered: