-
Notifications
You must be signed in to change notification settings - Fork 2
Improvements and security issues
If someone takes a PoP and maliciously publishes it on the bitcoin network, it could actually end up on the blockchain. If the proven transaction is on the blockchain, this problem can occur in the case of a reorg. If the proven transaction is 0-conf, then there is an extremely high risk that the PoP ends up on the blockchain. I still want the PoP to be a valid transaction, because it makes creating and validating PoPs simpler. So how can we ensure that PoPs never end up on the blockchain, or don't cause any big harm if they do?
Set the 4 version bytes to a version that don't exist for bitcoin transactions. For example 88888888. This would work as long as the non-existing version is non-existing. A solution like this would require transaction validation code and Transaction generating code to change in order to validate PoPs. This may not be big a problem, but it most likely causes friction in adoption.
Another approach (as suggested by Magnus Andersson on the Linköping Bitcoin Meetup) would be that the wallet add another output to an address of choice and put all value to that output, thus giving nothing to miners or the OP_RETURN output.
- The incentives for miners to include the PoP would be less that the incentives to include the proven transaction. This is because of the 0 fee of the PoP. The proven transaction may also have 0 fee, in which case the incentives are equal. Even then, the proven transaction have probably reached the network first, and would be included first because of that.
- Even if the PoP ends up on the blockchain the value would be transferred to the wallet again. No harm done for the payer, apart from a slight WTF moment when you discover that your funds were transferred to your own wallet instead of the merchant. Big harm to the merchant who may already have delivered the goods/service. They will have to sort it out manually.
The pay-to-self solution above have a weakness: It's harmful to the merchant if the original payment gets double spent.
Instead of adding output to self, what if we add all the outputs of the orignal payment exactly as is and put all the fees of that transaction on the first non-zero output of those outputs?
- The incentives for miners to include the PoP in a block is probably slightly less than in the pay-to-self case because the output list of the orignal transaction is probably bigger (more bytes) than a single pay-to-self output.
- If the PoP ends up on the blockchain, then the merchant still gets its money.
A problem with this approach is if the original outputs contains an OP_RETURN output. Only one OP_RETURN is allowed, so the OP_RETURN from the original transaction has to go. That output is deleted and its value is instead added to the PoP OP_RETURN output.
Another drawback here is that the PoP gets bloated with data that's not really used. It makes the transaction bigger, but it will probably not be noticable with respect to latency.
Setting nLockTime to the highest possible block (block 4999999999) would prevent the PoP from being included in a block for a really long time. This would also require to set the sequence number of at least one input to something other than ffffffff. Maybe setting ALL sequence numbers to 0.
This solution would significantly simplify the data structure. No extra copying of outputs needed. And no fiddling with OP_RETURN from the payment transaction.
To make PoP extendable, some sort of version attribute is needed. Martin Lie on the dev-list suggested that we put a version attribute right after the PoP literal. To make room for it we reduce the nonce to 4 bytes or reduce the "PoP" literal to 2, 1 or zero bytes. I think versioning is a good thing and I don't think we should reduce the size of the nonce. I think we should consider removing the PoP literal (see below) altogether to make room for a 2 byte version and make the nonce one byte bigger.
I currently check that the transaction provided in the PoP pays for the correct service before checking inputs and signatures. This should be done after signature checking.
This is because one could get information on what transactions pays for which services by simply sending unsigned (or erroneously signed) PoPs with a transaction id we're interested in.
A wallet creates a transaction T and publishes on the network, the merchant may actually receive another "version" of the transaction, T', due to a malleability attack. If the wallet creates a PoP for T, and tries to use it to access the merchants' services it won't work, because the merchant want proof for T', not T. This can be avoided by the wallet by listening on incoming transactions and in case T' comes in, it will replace T with T' in its database. Otherwise there's not qmuch we can do about this, or is there?
Since the PoP is not supposed to travel the bitcoin network, we don't necessarily need to identify it with the "PoP" literal. If the server expects a PoP it will treat incoming data as PoP. However, I'm not totally sure yet that this is true for all scenarios. Any input here would be greatly appreciated.
Maybe the txid will be optional in a future version. To make that transition easier we should put the mandatory nonce before the txid.
The name "Proof of Payment" is both boring and not correct. It's actually a proof that I own enough credentials so that I could have made a certain transaction. Any suggestions here are welcome. Maybe I'll announce a naming competition on Reddit with BTC and glory to the winner.
The nonce was an unsigned integer. That caused difficulties in the implementations on how to encode the the nonce in the different parts of the system, mostly related to big vs little endian endcoding. Insted i suggest that we switch to representing the nonce as a sequence of bytes. It's much more clear for the implementor how to interpret the nonce and its also easier to implement, because we don't have to convert to and from an integer representation.
Most numeric values in bitcoin are encoded in little endian, therefore it's relevant to switch to little endian for the version number. So instead of writing 0x00 0x01 in the version bytes, we write 0x01 0x00 to represent version 1.