freedomlayer/offset

A proposal for atomic payments

Closed this issue · 12 comments

I added a new proposal for atomic payments.
The proposed new design protects the buyer against indefinite waiting for a payment to complete.
Any criticism and ideas are very welcome, and may be posted here.

Atomic payments proposal 05.05.2019

EDIT (Updated cancellation rules):
atomic payments proposal 08.05.2019

EDIT (Added fragmented multi-route payments):
atomic payments proposal 14.05.2019

spolu commented

Looks good!

You should use bcrypt for the hashlock. Overall the out of bound ness of confirmation is a bit confusing I find especially as commit message could be issued by the payer?

I'm confused about Cancel message - at first you proposed it could be sent by any node on the route (in case of network failure), but then there is:

Only the payment destination can issue a Cancel message

I'm assuming that at no stage the credits are frozen. Otherwise, even if in the end any node would be able to cancel transaction in case of network error, there could be e.g. 2 malicious nodes in a route.

What if E doesn't send any message to B? What if the preimage doesn't reach B, but stops on C or D? What if B gets the preimage but not C, then B holds that and only shows it to C much later, asking for a repayment of their debt -- while C at that point have canceled his debt with D and thus is harmed by the transaction.

Hi, I really appreciate the time you took to read the new design!
I wrote a new draft for the proposal incorporating some of the feedback:

atomic payments proposal 08.05.2019

@pzmarzly:

I'm confused about Cancel message

And rightfully so! I forgot thinking about the cases you mentioned. Thanks for the sharp eye.

I began fixing the cancellation sections in a new PR.
As a summary, these are the rules for cancellation in the fixed proposal:

  • During the Request phase (From the point the Request message leaves the
    buyer node), any node along the route can cancel the message.

  • After the Request message arrived at the seller node, only the seller may
    send a Cancel message.

  • If a node unfriends another node and there is an ongoing transaction going
    through the two nodes, the node closer to the origin of the transaction (to
    the buyer) will send a Cancel message back to the buyer. This is only
    possible for transactions for which a Commit message was not yet received.

After a Commit message was sent by the seller, the transaction can not be
cancelled (And therefore no Cancel messages should be sent).

An example case to consider:
A mediator node M both forwards the request message and sends a cancellation back to the buyer.

  • In that case if the transaction succeeds, the mediator node M will have
    to lose credits to the node after him when the credits are collected.
  • If the transaction fails, nothing special happens.

@fiatjaf:

What if E doesn't send any message to B?

I can understand this in a few ways, I will try to cover all of them.
Recall that in the next examples E is the seller and B is the Buyer on the
route:

B -- C -- D -- E

(1) What if E doesn't send an invoice to B?

In that case, no transaction will happen.

(2) E sends an Invoice to B, B sends a Request message to E, but E never sends a Response message back to E.

The buyer (B) will give up after a while (Determined by the application level). The seller (E) loses the sell.
The transaction will not happen, and the credits will stay frozen until the seller will issue a cancel message.
If the seller doesn't issue a cancel message the credits will stay frozen
forever, or until D unfriends E.

In case D unfriends E (This might happen eventually if E seems to never respond
for many transactions), D sends back a Cancel message for the transaction. This
message unfreezes all frozen credits related to the transaction.

(3) E sends an Invoice to B, B sends a Request message to E, E sends a
Response message to B, B hands a Confirmation to E.

(3.1) If E doesn't hand the goods to B, B will solve this dispute in the real world
(It's like paying at the supermarket and not getting the goods).

(3.2) E hands the goods to B but never sends a Commit message.

In this case B (the buyer) got the goods but E never got the money for the
goods.

Like in case (2), if the transaction keeps being stuck, D might decide to unfriend E at some point,
which will automatically send a Cancel message from D back to B.

What if the preimage doesn't reach B, but stops on C or D?

I assume you talk about the Commit message sent from E to B being stuck in the
middle, at C or D. As an example, Let's pick D as the point where the Commit
message is being stuck.

D is at a loss of credits, because he already paid to E. D earns credits by
forwarding the message back to C. In addition, C also earns credits by
forwarding the message back to B. Therefore it is both of D and C's interest to
forward the Commit message back to B.

Of course, D could decide to never forward the Commit message back to B.
In this case the transaction credits (From B to D) will stay frozen forever, or
until C unfriends D.

What if B gets the preimage but not C, then B holds that and only shows it to C much later, asking for a repayment of their debt -- while C at that point have canceled his debt with D and thus is harmed by the transaction.

Could you further clarify what do you refer to as preimage?
Did you mean srcPlainLock or destPlainLock? What stage of messages are we
present at?

@spolu:

You should use bcrypt for the hashlock

Will do. I have a question about this: what should be the amount of rounds?
I don't want nodes to have to do too much computational work to be able to send one
transaction.

Overall the out of bound ness of confirmation is a bit confusing

By "out of band" communication I mean communication that Offst does not
support on its own. It is something that a higher level application should
provice. Anywhere out of band communication is specified (Also specified as
a ===> styled line), this means some form of direct communication (Outside of
Offst) is used between the buyer and the seller.

For example, as a seller you should be able to send an Invoice by email, and
then get a Confirmation message by email. Finally, you provide the goods to the
buyer and feed this Confirmation message to your node to collect your credits.

Instead of email, any communication method could be used. It could be HTTPS or
NFC.

The Offst protocol does not support direct communication between nodes that are
not configured as direct friends, and so a higher level application should
take care of sending the Invoice, Confirmation and the goods. This was done
deliberately, because I wanted to allow more flexibility to the higher level
application.

I find especially as commit message could be issued by the payer?

It might be possible that I chose names you are not familiar with for the
messages. I called the message Commit because I was thinking about it like a
database's commit. If you are confused by the name, maybe it was not a good choice.

Do you have an idea for different names for the messages? It's still a good time to change then names (:
This is a summary of the current names:

Invoice        <=====[inv]========    (Out of band)

Request        ------[req]------->
Response       <-----[resp]-------

Confirmation   ======[conf]======>    (Out of band)
Goods          <=====[goods]======    (Out of band)

Commit         <-----[commit]-----
(Receipt)
               B --- C --- D --- E

By "preimage" I mean srcPlainLock.

Let me try to make my point clearer (as well as my understanding of the protocol, please correct me if I'm wrong):

Imagine A wants to send a payment to E on a route A-B-C-D-E.

  1. A will send a request to lock funds along the route with the srcHashLock.
  2. each peer will make a deal with their other peer (like B-C and C-D, for example) such that a debt will exist on that credit relationship if the srcPlainLock is known -- so, for example, first B will make a conditional arrangement with C so B will owe C 10 credits as long as C presents him the srcPlainLock for the given transaction. Only in possession of that conditional arrangement C will then make a similar arrangement with D. C now thinks: "once D shows me srcPlainLock I can go and show the same value to B and I don't lose anything.
  3. Continuing the initial example: then E will send a request back with the srcPlainLock, expecting that it will pass through D, then C, then B and finally reach A.

Problem

But that's not what's going to happen. Many bad things can happen in the meantime. Let's consider one example:

  • Once D knows the srcPlainLock C now owes D -- even if D doesn't tell C the srcPlainLock. Of course it will have to tell C, but it doesn't have to be now. D can wait perhaps one month.
  • Imagine D waits one month. C may even ask D for the srcPlainLock and D may be unresponsive. Maybe he doesn't have electricity, who knows.
  • The transaction may have a timeout. That timeout is reached. Now C cancels the conditional arrangement it had with B. B will not owe anything more to C.
  • Then suddenly D appears and shows srcPlainLock to C. C now owes D and B doesn't owe anything to C. C is in loss. What can C say? He did gave D that conditional agreement. And D will say: "hey, I'm paying E in your behalf, why aren't you paying me?".

You can't make these things atomic.

We can consider other situations derived from that:

  1. D gets srcPlainLock from E.
  2. D and B are secretly friends and they want to trick C and steal his money.
  3. D sends srcPlainLock to B secretly.
  4. B presents that to A and gets paid.
  5. Since C doesn't have the srcPlainLock C cannot ask B for his payment.
  6. D waits until C cancels his conditional payment with B and then presents srcPlainLock.

@fiatjaf: Thanks for further clarifying the problem! I think I understand now.

The transaction may have a timeout. That timeout is reached. Now C cancels the conditional arrangement it had with B. B will not owe anything more to C.

Transactions do not have timeout. What will actually happen in the described case is that C will wait forever for a Response or Commit message from D.
It is possible that D is doing this on purpose (Which is strange, because D is giving up on money he could make), or some unrecoverable failure happened to D.

The only options C has are:

  1. Wait forever. (Which is safe, but inconvenient).
  2. Unfriend D. In that case C indeed cancels the transaction (Cancellation is sent all the way to A).

I have seen ideas attempting to solve this issue using some kind of transaction timeouts, but I believe that any solution that involves an automatic timeout is not really safe and allows cheating (Like you pointed out in your example).

Unfriending is a violent operation. Probably this means that C is some person or organization in the real world, calling D on the phone and asking what's up? Why isn't your node online for the last month? If C and D can not reach an agreement, C can unfriend D. Unfriending means that messages will not pass anymore between C and D.

If C and D ever decide to configure friendship again, they will have to decide on an initial balance, and they will start from a clean slate of transactions.

During unfriending someone, one of the sides can probably lose money (Not more than the trust put on that friend), but this amount is documented and cryptographically signed. This should allow the two parties solve the issue in the real world.

You can't make these things atomic.

I don't know how to prove this formally, however I tend to agree with you here.
That said, I think that what we already have might be good enough. We do get an atomic payment experience for the buyer.
Regarding the rest of the nodes, we can only guarantee that the seller and all the chain leading to it will get their deserved credits eventually, or the credits will be frozen forever.

Well, generally I tend to agree that the current scheme may be sufficient, but when I think about it a little the problems come back to my mind and I believe it's not workable.

Unfriending is a violent operation. Probably this means that C is some person or organization in the real world, calling D on the phone and asking what's up? Why isn't your node online for the last month?

Timeouts don't solve it, but also this is a bad solution, because D could just be online but not pass the srcPlainLock on. When C calls it: "what's up? where's the preimage?", D would reply: "I didn't get it yet!". And the problem is: that may be true.

Besides being a terrible experience, payments without timeouts are still vulnerable to collusion attacks. Imagine D doesn't pass srcPlainLock to C, but then B appears on C's door with the srcPlainLock and demands a payment? What would C do? The following dialogue will follow:

-- Wait, what? How do you have that srcPlainLock if I didn't receive anything?
-- I don't know, I thought I had received it from you.
-- There's nothing in my node software!
-- Maybe there's a bug somewhere. Perhaps you could ask D.
-- He says he doesn't have it.
-- Well, what do we do now? I've already paid A, and you owe me this.

I would rather prefer having trusted commit registries. Like in the example instead of E sending the srcPlainLock back on the path D-C-B-A, E could just publish it on a commit registry. A, B, C, and D would be monitoring that registry and once they saw it there they could call the entire transaction "committed". It would be better to not use these hashlocks, however, otherwise E could still collude with C and harm D, better just have the commit registry be the sole authority for the transaction.

One more thing: if you agree that this is not atomic please don't call it atomic, it is misleading. People will join this thinking it's atomic but then something bad will happen.

Actually I think I'm wrong. Considering my above dialogue, I think it is a solvable situation: C will then take srcPlainLock from B and pass it to C, C would then show it to D and everybody would have their claims in place (although if someone is cheating that would result in some friendships being broken).

Ok, nice.

Now there's the problem of timeouts. It's really not cool to have payments pending for months, or even days. You buy something on the internet and you don't know if you're going to get that or not? What if you need it with urgency? You don't know if you should try buying it elsewhere?

You buy something on the internet and you don't know if you're going to get that or not?

This is exactly what this proposal is supposed to solve. I agree with you that buying something and being not sure about what happened for a long period of time is absolutely unacceptable.

With the proposed design you should be able to buy something "atomically": The moment you hand the seller the Confirmation message you get the goods. Looking at the Confirmation message, the seller should is able to verify immediately that a payment has begun (and can not be cancelled), and therefore it is safe to hand the goods to the buyer.

The part of indefinite waiting is the part where the seller collects his credits. This can actually take a very long time. So we managed to protect the buyer, but the seller can suffer waiting for the credits to be unfrozen.

Actually I got everything backwards. Disregard my last comment. I still think there are unsolvable problems, but I'm not in conditions of thinking about this anymore.

@fiatjaf: No worries, I really appreciate the time you took to think about this.
I should set up some TLA+ verification at some point, this will help making the thoughts about this process more rigorous, and also help verify it.

I believe that the new atomic payment protocol allows to perform multi route payments atomically pretty simply, so I added it to the specification.

Implemented and merged to master.
Great thanks for everyone helping out with this proposal!

One thing that is still missing is implementation of the hash locks using bcrypt as suggested by @spolou. (Currently implemented as trivial SHA512/256 hash)