CHIP 2021-08 ZCEs: Zero-Confirmation Escrows

Zero-Confirmation Escrows (ZCEs) are contracts which enable instant, incentive-secure payments on Bitcoin Cash. They’re particularly useful in point-of-sale, ATM, and vending applications where payers have no prior or ongoing relationship with the payee.

Supporting wallets can add ZCEs to transactions to guarantee that the transaction will not be double-spent. Wallets can instantly make a long series of ZCE-secured payments using the same starting funds, and ZCEs require no holding periods or other delays in wallet user experiences.

ZCEs are a refinement of prior work made possible by improved contract tooling and the implementation of Double Spend Proofs (DSP) on the Bitcoin Cash network. They require no consensus changes and can be deployed without coordination. Once a critical mass of miners implement ZCE-claiming code, businesses can safely accept ZCE-secured transactions without delaying the payment experience to monitor the network.

The CHIP is here:

And @marty has already completed an end-to-end reference implementation. :rocket:

Reviews and feedback are deeply appreciated, either here or on GitHub issues. Thanks!

12 Likes

I read the spec and… I… I don’t know what to say.

If there are no serious bugs in this… this is just fucking genius.

It possibly ultimately solves the confirmation problem once and for all. Transactions will be instant and secure, just like that.

This is just so good it seems impossible.

3 Likes

Why is knowledge of the reclaim public key required for the miner claiming the forfeit? Shouldn’t proof of double spending be enough?

2 Likes

Thanks for reviewing the spec!

It’s enough to construct the unlocking bytecode, but the miner also needs to know what the full redeem bytecode is to create a transaction spending from the P2SH ZCE contract. Other than revealing the redeem bytecode, the reclaim transaction is also important for identifying ZCE-secured transactions, since they otherwise blend in for other network participants (ZCE outputs are just P2SH outputs, and can’t be differentiated from any other P2SH output until they are spent).

3 Likes

Fair enough.

My misunderstanding happened when tracing the contract and I assumed you needed to match the reclaim public key for both branches. That seemed like a weird requirement.

The last opcode on the first line is OP_EQUAL, I assumed OP_EQUALVERIFY.

For the miner to reclaim the output, they have to pass a value that doesn’t match the reclaim public key and that will be false for the OP_IF, so they don’t need to know that key.

The overall system does make memory pool handling harder. One of the big advantages of the first seen rule is that you don’t have to handle removal of long chains of transactions. The only exception been once per block received.

It may be worth adding a rule that miners shouldn’t build on ZCE outputs (other than the reclaim transaction).

2 Likes

Right, for efficiency, we choose the code path based on whether the item provided is the reclaim public key or something else. You can see the full execution for miner reclaims in Bitauth IDE – here’s the miner reclaim for the first input of a 1-tier merkle tree:

Thanks for digging in to this!

It’s worth noting that ZCE replacement is reasonably limited by 1) if there is any replacement, miners will be able to claim the largest ZCE of the available replacements, 2) replacements must take place within 5 seconds (this , and 3) replacements are ignored unless the ZCE grows by at least the replacements’ transaction fee (so the processing of replacements always costs a DOSer as much in additional ZCE “fees” as it would cost to create an equivalent-sized transaction).

So with all of that, if replacements ever happen, they pay the network for the trouble.

Even with those limitations, if there is still concern about DOS strategies based on replacing long transaction chains, we can simply disallow the use of ZCE transaction outputs for the 5 second lock-in period. (There’s no value beyond 5 seconds, since no honest nodes will accept replacements after that time.)

I’m not sure if it’s enough of a concern to justify adding that sort of additional logic, but 5 seconds of delay in the network accepting a transaction re-spending the ZCE output value probably won’t even be noticeable by users. (Especially since escrowed funds themselves already require a confirmation before they can be used by later ZCEs.)

I don’t have a strong preference either way; I would love to hear developers from the various node implementations weigh in.

1 Like

I am quite interested in the details regarding the “Eliminating One-UTXO-Per-Address Limitation” solution using the detached signature (PMv3) feature. Is there an outline or high level description on how it can be solved?

Currently many wallets do spend all UTXOs from the same address due to (valid) privacy reasons and given the number of services that sends all coins to one address (for instance tipping services, read.cash and peoples indifference to setup new withdrawal addresses on exchanges) it might be an UX issue to not be able to combine those outputs in one payment. And given that the inputs are required to have at least one confirmation the wallet can not combine those inputs to a new address and use that for the payment…

Edit: on a related note.
I think it would be a good idea to have a strong recommendation that wallets should not use an UTXO from block 661647 (BCH/XEC split) or earlier since there is no way for the wallet to know if those outputs have been spent or not on another chain.

2 Likes

Sure! You can see the SIGHASH_DETACHED” signing algorithm here. PMv3 allows multiple inputs to use a single signature covering the entirety of the transaction (“cross-input signature aggregation”). So there’s not much more to it – since all UTXOs from a single address would be signed with just one signature, it’s safe to use multiple UTXOs. The ZCE can only be claimed if the user’s wallet tried signing another message (like a conflicting transaction) with that same public key. So there’s no coordination needed between the ZCE and PMv3 CHIPs, but if PMv3 is deployed, we’ll get multi-UTXO-per-address for free.

This is a fantastic point. I think the best solution for both privacy and UX is PMv3, but it would probably be a good idea to recommend a safe, maximum-privacy strategy which works for now.

Without PMv3 I’m afraid we can’t fix the UX part, but privacy-wise, wallets should probably add all UTXOs for used addresses to the second reclaim transaction. It’s always broadcasted at the same time, and merging those inputs immediately doesn’t lose any further privacy. (If the wallet waits around to use them later, future transactions can be easily connected.) I’ll open an issue about that, thanks!

Ya, unfortunately, even that wouldn’t be enough. A lot of users have hierarchical-deterministic wallets from before the BCH/BTC split (and BSV and XEC), so wallets which could possibly have been imported (or are otherwise simply shared) are vulnerable, even if the UTXOs are new (if the user continued making transactions on the other chains).

I think that scenario is technically covered by Wallet UTXO Selection criteria 2, but maybe it would be valuable to add a more explicit caveat (with some bolding) at the end of the section… I’ll try to do that too, thanks!

@TierNolan thanks for bringing this up – I opened an issue:

The question for node developers is: how expensive is mempool handling of ZCE replacements if attackers can build long chains of transactions for each replacement? (Each replacement costs the attacker its minimum transaction fee, since the ZCE must be larger by at least that value.)

Do we need to prevent further transactions from building on reclaim transactions until the 5 second replacement window is over?

I’m now leaning toward “yes” for this, since the cost is quite low for a solution which provides complete protection (even for particularly naive node implementations).

Just out of curiosity so I get the vulnerability correct: Is it only the miner that finds the next block that can perform such “ZCE sniping” by keeping an index and do lookups of public keys on different chains?

As a pure DOS, the attack sequence, it would work as follows.

Attacker sends a chain of transactions.

A → B → C → … → Z

The attacker then sends B’ which is a ZCE transaction with a low escrow amount (e.g double the tx fees for the whole chain) and also C’ which is the reclaim transaction.

The replacement means that B and C are replaced by B’ and C’. This requires removing B to Z from the memory pool.

Part of the complexity is that the code must handle replacement. When they were removing the tx chain limit, they found that removing it completely made things much cleaner for the code. It took more effort to calculate the limit than handling long chains of transactions.

The developers can comment on exactly how it works now, but having to remove transactions during normal memory pool operation (rather than when handling new block arrivals) may be more complex than it appears.

Did you consider having special outputs that can be fast spent? You would have to move some money to these “pre-charged” outputs, so it is less user friendly, but that could be handled by wallets. These special outputs could have special rules to protect against long chains.

2 Likes

Eh… I clearly didn’t think about this long enough yesterday. If you just add the unused UTXOs as inputs to the reclaim transaction, that defeats the purpose of not adding them to the first transaction. :laughing:

So if we’re starting with a wallet with N same-address UTXOs, the best we can do is spend one in the ZCE, then merge the other UTXOs with the output of the ZCE reclaim transaction after 11 blocks have passed (ideally using CashFusion). And even that is messy, since the ZCE reclaim transaction outputs could otherwise be re-used in a further ZCE transaction after just 1 block (and they can even be passed through CashFusion within that block).

Another (possibly better) strategy would be for wallets to merge same-address UTXOs in advance (using CashFusion), and spending only to specially-derived “ZCE-ready” outputs which the wallet can be sure will not be re-used by other wallet clients sharing the same imported HD key. (Or at least the other wallet clients can be expected to take the same ZCE precautions.)

The downside of that approach is that must happen at some specific time before the ZCE transaction (if the user even opens the wallet), and it’s also hard for wallets to anticipate when the last payment to an address might be received (to reduce the number of merges needed, which leak access-timing information for the owner).

Anyways, I’m not sure if we can really make a general recommendation for all wallets on this issue. The issue is fully solved by PMv3 though, so it’s also probably not worth wallets expending a lot of development resources on temporary patches.

1 Like

Sort of, but we can also expect that any claimable-ZCE will be quickly claimed. If “naive” miners don’t run that logic themselves, someone else likely will create the claim transaction and broadcast it. (And anyone between them and a naive miner can swap the output to themselves.) I expect many miners will also ultimately run general-purpose, anyone-can-spend claiming logic (which will appreciatively pluck the double-spend info out of any transactions broadcasted by the earlier parties, claiming it without the reconnaissance work).

So we can expect that someone will grab the ZCE, even if very few miners are cognizant of their being a network-wide race.

Ah, right, thanks! I was focusing on the scenario where B is already a lower-escrow ZCE amount, and gets replaced with a B’ of a higher escrow amount. (The second case can be eliminated by disallowing spending of ZCE transaction outputs for the 5 second lock-in period.)

The non-ZCE chain replacement is definitely harder to handle without having some limit on unconfirmed transaction chains.

I would much rather we avoid any setup requirements, since they are almost guaranteed to regularly cause UX delays. Especially if the setup isn’t necessary for typical operation, but only as a sort of signaling mechanism for how honest nodes should handle broadcasting. (Also, I’m not confident this wouldn’t set us back to having all unconfirmed transactions incentivize fraud-as-a-service mining. Unconfirmed transaction chains can always be killed by a paid-off miner, and the more they’re relied upon, the more likely we are to see those miners in the wild.)

I think there are probably some reasonable solutions though. Maybe something that takes advantage of the 5 second lock-in period? (Reminder: 5 seconds is ~2x the expected time it takes for the entire network to hear a transaction, so all commercial ZCE activity can be expected to happen within this time. ZCEs which attempt to e.g. replace a transaction from 1 minute ago would never be accepted by a merchant, and are almost certainly malicious/DOS attempts.) How crazy would it be for node implementations to keep the last 5 seconds of mempool updates in some sort of pre-commit log? After 5 seconds, the “rollback instructions” can be safely thrown away.

I’ll have to think about it more. I’d love to hear from node implementation devs on this topic!

It depends on exactly on how much change is made. Your system doesn’t require a consensus rule change, though it does depend on node non-consensus/policy changes.

To prevent more than one transaction being built on an output, there needs to be covenant functionality. You need to place a restriction on the scripts of the outputs of the transaction that spends the current output.

Maybe a hard fork would be acceptable to have a flag or something. It requires that all outputs of the next transaction are unspendable until 1 block has passed.