interledger / rfcs

Specifications for Interledger and related protocols
https://interledger.org
Other
456 stars 111 forks source link

RFC-3: Add hop limit to ILP packet #330

Closed justmoon closed 6 years ago

justmoon commented 6 years ago

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 called hopLimit 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.

adrianhopebailie commented 6 years ago

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

justmoon commented 6 years ago

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 OOU\0 (ASCII), followed by the 32 byte fulfillment.

Benefits of this approach are:

Unless someone sees an issue with this alternative, I'll withdraw this proposal.

michielbdejong commented 6 years ago

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.

adrianhopebailie commented 6 years ago

I think we should make it mandatory that the condition is different for all payments. WDYT?

emschwartz commented 6 years ago

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.

adrianhopebailie commented 6 years ago

Doing so just means that anyone who would want to use a well-known condition will then be forced to use random unfulfillable conditions

Why is that a problem?

emschwartz commented 6 years ago

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.

adrianhopebailie commented 6 years ago

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.

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

adrianhopebailie commented 6 years ago

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

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.

emschwartz commented 6 years ago

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.

adrianhopebailie commented 6 years ago

and block it

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.

adrianhopebailie commented 6 years ago

why would it be beneficial to force conditions to be unique?

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.

adrianhopebailie commented 6 years ago

We continue to make this assumption anyway by building everything using PSK

emschwartz commented 6 years ago

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.

I think people will assume they are and will build stuff based on that assumption.

There should be no assumptions about this. The spec should clearly state what should or must be unique and in what context.

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.

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.

adrianhopebailie commented 6 years ago

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?

emschwartz commented 6 years ago

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. ☹️

adrianhopebailie commented 6 years ago

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 :smile:

adrianhopebailie commented 6 years ago

This raises a very good reason why you should:

  1. Never use the fulfillment to carry something valuable back to the payer (this has been proposed before for virtual goods)
  2. Always use an encrypted transport
justmoon commented 6 years ago

@adrianhopebailie:

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)

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 behavior

When 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.

Properties

An 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 considerations

The 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.

Conclusion

The behavior above is sufficient to prevent loops without enabling any new attacks. A hop limit field is not needed in Interledger.

emschwartz commented 6 years ago

Sounds good.

Can we close this issue?

sappenin commented 6 years ago

Should this conclusion be documented somewhere? Maybe in Connector risk mitigations? Or somewhere else?

adrianhopebailie commented 6 years ago

@justmoon can we close this. I think it is mostly irrelevant with ILPv4 albeit an interesting discussion for anyone interested in the design decisions.

stale[bot] commented 6 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. \n\n If this issue is important, please feel free to bring it up on the next Interledger Community Group Call or in the Gitter chat.