In this post I'll try to explain some design choices for BIP 120, Proof of Payment, and also comment on some critique against it.
Basically a Proof of Payment looks exactly like a Bitcoin transaction:
Note how Pop(T) in indistiguashable from a Bitcoin transaction. If it would be broadcast on the Bitcoin network, the transaction would not be accepted because of the nLockTime=49999999 (Max height ). The inputs of the PoP are the same as the inputs of T, but the signers may be different as in the case with the multisig input. The output contains a PoP version, currently 1, the txid of the transaction to prove and a nonce. The nonce is typically generated by the party requesting the PoP.
My reasoning is that reusing the transaction data structure will make it easier to implement PoP, because existing Bitcoin software can be used as is to sign the PoP and verify the signatures of the PoP. A drawback is that we have to use the quirk with nLockTime and nSeq to make sure that the PoP cannot act as a double spend, should it appear on the Bitcoin network.
Only 6 bytes nonce
48 bits might seem small for a nonce. It's a consequence of chosing an OP_RETURN output to represent PoP specific data. There was a policy implemented by most nodes to only relay 40 bytes of OP_RETURN data. To avoid possible problems (there shouldn't be any) in different walllet implementations due to this limit, I chose to obey that 40 bytes limit, and that left me with a 6 bytes nonce. The nonce is supposed to prevent an attacker from intercepting an in-flight PoP, or otherwise steal a PoP, and then use it to access a service requesting it. Since the service is generating the nonce, the attacker would have to repeatedly request a pop request from the service and hope to get a matching nonce. The service could pretty easily detect a brute force attack like this, unless it's massively distributed. It could also enforce a small delay for each request. Additionaly that delay could be automatically increased if it detects an increasing amount of requests.
Incompatibility with CoinJoin
You could argue that PoP would not work with CoinJoin, and therefore it is no good. It seems like CoinJoin might become a commonly used transparent layer in bitcoin and if that's true PoP will not be able to do it's job. On one hand that's not good, but on the other hand, the purpose of CoinJoin is to make payments untraceable, so it shouldn't come as a surprise that there is no way to prove a certain payment if that payment is made through CoinJoin.
One way around this is to ditch BIP120 and instead give the wallet a key at the time of the payment. That key could be used to sign challenges from the service. The wallet itself would probably be the one generating the key, and sending the public key to the service. Drawbacks with these sorts (pre-created tokens) of solutions are:
But still, working around an anonymity feature like CoinJoin will defeat it's purpose. Someone with access to both the wallet and the service will be able to use the token to prove that a payment has been made.
What about Lightning Network?
I don't know. As far as I can see, BIP120 will only be able to prove the opening (commitment) transaction. So if LN becomes a reality, some token-based system would probably have to be implemented instead.
Why not just use a single key from the payment?
While this might be enough for ordinary P2PKH inputs, it does not fit the bill for more complex payments, like 2-of-3 multisig inputs. What single key would you use?
I hope this answers some of the critique I've seen against BIP120.