CHIP 2021-08 ZCEs: Zero-Confirmation Escrows

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


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.


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.


Hey Jason, please don’t forget to make some Flipstarter to let us throw money at you, your invention is potentially genius beyond belief.


Testing to see if mentioning @escrowtim will allow him to post here.


Excited to announce that I’ve implemented Zero-Confirmation Escrows for BCHN in the following merge request:

Improvements to the ZCE Specification

While implementing this MR (and after discussing with @bitjson and @marty), it became clear that the implementation would be simpler (and more secure) with one change to the spec, which I have incorporated into the MR:

Make the ZCE locking script standard (rather than wrapping it in P2SH). That way, nodes can determine whether a transaction is ZCE-secured simply by inspecting it (without needing to wait for the subsequent escrowReclaimTx to reveal the redeem script).

This MR also features a more optimized version of the ZCE locking script (courtesy of @bitjson) that saves a few bytes of data and no longer requires the altstack.

DoS Mitigation

A special thanks to @TierNolan, who identified a potential DoS issue involving the replacement of long chains of unconfirmed transactions.

This MR addresses that issue by ensuring that a ZCE-secured transaction may only replace a double-spend (and its descendants) if doing so is economically rational (i.e. the value of the escrow on the ZCE-secured transaction exceeds the cumulative value of the miner fees on the transactions it replaces).

Any peers that repeatedly send ZCE-secured transactions without adequate escrow to warrant replacement get banned.

In my testing, I found that removing an unconfirmed chain of transactions from the mempool is ~30x faster than adding it:

* The data in the above table was collected by running the chain_of_double_spends_replaced_by_zce unit test in my MR and varying the length of the unconfirmed transaction chain.

Additionally, after setting up a local network of 3 interconnected BCHN regtest nodes, I discovered that nodes are able to propagate chained transactions over p2p at a maximum rate of ~30 tx/s. And since an attacker has only 5 seconds to build his chain of conflicting unconfirmed transactions, the maximum number of chained conflicting transactions that can be propagated network-wide in under 5 seconds is ~150, implying a worst-case replacement time of ~2.5ms per the table above.

Therefore, the low computational cost of replacement, combined with the DoS mitigation code above, should provide adequate DoS protection.

Proposed Testing and Activation Timeline

Because deploying ZCE requires a standardness change, it is critical that all participants in the BCH ecosystem (especially miners and merchants) activate ZCE standardness at the same time to ensure that non-ZCE zero-confirmation transactions remain secure. Therefore, the May 2023 upgrade would be an ideal time to activate ZCE on mainnet and would give us almost a full year to fully vet ZCE on testnet to ensure there are no unforseen issues.


Thank you to everyone who made Double-Spend Proofs a reality, especially @tom, @freetrader, @cculianu, and @im_uname. This MR would not have been possible without DSPs, which enabled ZCE to be implemented in less than 600 lines of code (excluding tests). And thank you to @bitjson and @marty who authored the ZCE CHIP and answered questions and made improvements to the spec as I was implementing this MR, as well as Awemany who pioneered the original Zero-Conf Forfeits mechanism on which ZCE is based.

Would love to get feedback on this implementation!


Q: would it be possible to make the P2SH hash constant for all contracts - by extracting variable parts into another output - entangled with the “main” contract using the new introspection opcodes. By entangled I mean: have each of the output’s redeem script require spending the other one in the same TX. Something like this: Introspection opcodes are so cool, here's a teaser : btc

1 Like

What is the business case for this?

So far no merchant seems to even be asking for double spent proofs, why does one feel a need to improve upon it?
The CHIP is awefully hand-wavy on this.

Small observation; a collaborating miner that mines a double spend (the only thing that DSP doesn’t protect against) is also not going to get stopped by this one. Which means that the confidence level can not honestly be advertised as better.

Not feeling the support at this time.

1 Like

Easy. The most important is: Instant transfer to exchanges.

Brick and Mortar stores are good with Double Spend Proofs. Internet Stores are good enough even with 1 or 2 confirmations.

But for instant deposits and quick withdrawals from/to exchanges (and probably gambling sites too) with no added risk is/will be definitely the killer feature.

I am loving your DSPs too, Tom, they are great BTW.


Really fantastic work @escrowtim! Thank you again for all the time and energy you’ve put into making this implementation happen. (And thanks to everyone already reviewing it!)


That’s definitely a valuable technique for many covenant use cases (for others reading, this is an idea behind convenant-tracking CashTokens). I don’t think it applies in this case though. 1) There are technically 14 contract permutations (supporting up to 14 merkle tree levels). 2) In this case, a constant P2SH hash breaks expectations of users and wallet infrastructure (a constant P2SH hash could help wallets “find” a latest covenant UTXO, but for ZCEs it would be counterproductive; wallets would need to look up not only UTXOs but also their sibling outputs to confirm ownership – requiring new indexes and unnecessary logic). 3) Using sibling outputs requires additional transaction size overhead, so any benefit would need to outweigh that cost.


Thanks for reviewing the idea!

Marty and I spent years thinking about ways for merchants to safely accept instant transactions (both point of sale and online) because we saw this issue set back real world Bitcoin (Cash) adoption countless times while working at BitPay (merchants cooling off or dropping support after being burned by the choice between UX and fraud risk). For merchants at significant payment volumes, this is among Bitcoin Cash’s most critical pain points to solve.

Double Spend Proofs are great! In my experience, merchants and payment processors are thrilled that people are working on zero-conf security again. However, there is serious, systemic risk for large merchants accepting non-ZCE, zero-conf transactions, even with DSPs. From Long-Term Security in the spec:

The presently-low risk of accepting zero-confirmation transactions is predicated on a common but unenforceable norm: the “first-seen” transaction mining policy. Because miners who do not follow this policy cannot be conclusively identified and punished by other network actors – and BCH is not the only SHA256-based chain on which miners can operate – the current behavior can only be considered stable while the “defraudable” volume of zero-confirmation commerce is less than the expected value of each block coinbase.

As block subsidies continue to be replaced by transaction fees and zero-confirmation commerce volume increases, the expected profitability of attack infrastructure may rise significantly. Unscrupulous mining pools could augment their income by offering zero-confirmation transaction reversal services which circumvent network monitoring strategies and occasionally succeed at reversing non-ZCE transactions approximately 10 minutes later.

ZCEs remain incentive-secure against this category of attacks, even as zero-confirmation commerce volume increases. With adequate escrow values, ZCE-secured transactions can be accepted with the same finality as single-confirmation transactions.

There are other reasons I’m more excited about ZCEs than alternatives:

I hope to convince you that this is not the case. There’s a much deeper discussion in Full-Value Escrow Requirement, but in short, such “fraud-as-a-service” miners are incentivized to double-cross the attacker (attackers risk >200% of each payment for the chance at a discount). We’ve even outlined a theoretical nuclear option we believe fully eliminates any expected profitability of fraud enterprises:

If a notable “fraud-as-a-service” miner were ever detected on the network, an additional mining policy could be implemented to solidify ZCE security: miners could ignore blocks which fail to claim sufficiently-aged ZCEs beyond some limit.

Because all miners are expected to eventually hear all transactions, blocks which fail to claim a significant sum of value from ZCEs of sufficient age can be assumed to originate from a miner engaged in zero-confirmation payment fraud. (A miner forgoing significant on-chain profits indicates that they are being paid a larger sum off-chain to modify their behavior.)

To ameliorate this fraud, honest miners can profitably provide a valuable service: ignore the offending block, claiming the ZCEs themselves in the next block. If all honest miners expect this behavior (and reasonable timing and value limits are established), the network can be expected to successfully drop the offending transactions.

The other miners can expect to make a profit at the fraudsters’ expense, and any users who were defrauded will automatically receive the payment they originally expected.


I asked why you think you need to improve a product when nobody is using the current solution. You state that there still is risk.

Should I read this as you saying that the reason merchants are not using double spent proofs (but simply accepting zero-conf without it) is because the risk is still too large?

At the end of the day, I think we have a good indication of demand (for more secure zero-conf) because a pretty neat product is activated on-main-chain and nobody is haressing Roger to add it to his product. Demand is very low. This has been proven empirically.

If a company doesn’t see demand for a type of product and then spends a year building a new one solving the exact same problem, the question of the CEO is going to be identical to what I asked; “what is the business case for this”.
And I still am hoping for an answer to that.