Monero-BCH Atomic Swaps

As I said before, its tricky :laughing:

In the context of protocol development and even advertising, we have no malleability there (to the best of my knowledge) as it would require private keys to make changes and thus what leaves your wallet is what gets mined.

But then we have the birthday paradox, we have the mess of needing to use p2sh instead of the script being in the prev-out and indeed private keys outside of your control.
Its not an issue in 99% of the cases, but in real life using them unconfirmed is… tricky.

Well, I don’t think it is that tricky. I could be wrong (not going to read the whole paper on my Sunday afternoon), but that sounds like BS.

BCH has Schnorr signatures, which means the needed multi-signature capability required by the two scripted transactions on the BCH chain should be transparent to the blockchain, known only by the swap code Alice and Bob run.

I am trying to thouroughly understand the BTC XMR paper before commenting further. I am wary of subtle details in the protocols, scripting, or cryptography.

That’d be 3rd party malleability, i.e. anyone being able to change a signed TX’s TXID. I think this has been closed with HF 2019-11-15 Schnorr Multisig so you’re right – changing a TXID of a TX signed with multisig would require access to a (singular!) private key. Not keys. Key, just one, any one key from the set, regardless of the required number of signatures required by the script.

This is the literal definition of 2nd party malleability: a single signer can change the TX without approval from other co-signers even when required number of signatures is > 1.

Even if the scripts of the paper were “bare” (as opposed to P2SH), the problem would still be there.

Sorry, it’s not BS. Using Bitcoin specification of 2016, or BitcoinCash specifications prior to activating HF 2018-11-15 the script had no way to work around the problem. Paper authors were probably just unaware that we had covenant through OP_CAT, OP_SPLIT and OP_CHECKDATASIG. That’s understandable, to do covenants using 2018 spec is extremely brain-twisting. Thankfully, since 2022, we have introspection opcodes – meaning it’s more straight-forward to code covenants, and above I presented such covenant-based solution.

Why is 2nd party malleability a problem for the scheme?

Let’s first observe possible flows of BTC/BCH:

[Bob's P2PKH UTXO]
(fund swaplock)
[SWAPLOCK UTXO] --(complete swap)--> [Alice's P2PKH UTXO, Bob gets XMR]
(initiate refund, option available only if timelock t_0 expired)
[REFUND UTXO] --(complete refund)--> [Bob's P2PKH UTXO, Alice gets XMR]
(recovery and punish Bob, option available only if timelock t_1 expired)
[Alice's P2PKH UTXO, Bob gets nothing]

During the off-chain interactive setup phase, both parties pre-sign and exchange signatures for all multisigs and construct and verify XMR key shares.
Then, Bob goes first and funds the SWAPLOCK.
Alice sees that, waits for some confirmations, and then funds the XMR output.
Then, after the XMR TX gets some confirmations, Bob reveals the hashlock secret to Alice, opening the (complete swap) path for her. If he fails to do it, Alice has to wait for timelock to initiate refund.
Then, Alice opens the hashlock and signs the (complete swap). Bob sees the signature published and from it extracts the XMR key share he needs to collect XMR – this part is the verifiably encrypted signature (VES) crypto magic.
If Bob goes missing (his fault) Alice just waits for timelocks to expire and gets Bob’s BCH and he gets nothing and XMR is stuck forever (unless he shows up later with a good excuse and Alice shows grace and reveals her XMR share to him).

But what if Alice is malicious? She could just wait for the timelock t_0 to expire and race to submit a changed (initiate refund) TX by swapping her old signature with new one (it’s 2-of-2 she can’t change the TX template without Bob, but can change TXID). That would mean the pre-signed (complete refund) TX that Bob has would be useless and he couldn’t complete the refund without Alice generating a new signature for him.
Alice could then just wait for the timelock t_1 to expire, collect her BCH, leaving Bob with nothing, punishing him out of malice.

With covenants, Bob doesn’t need Alice to go through with the refund even if parent’s TXID changes - he can update the prevout refs all by himself without invalidating the spending path.

cc @tl121

1 Like

FWIW, the paper by Gugger (h4sh3d) is not the only proposed Monero atomic swap protocol. Some others that have not been implemented in production code AFAIK:

Hoenisch & Pino (2021) “Atomic swaps between Bitcoin and Monero.”

Hoenisch, Mazumdar, Moreno-Sánchez, & Ruj (2022) “Lightswap: An atomic swap does not require timeouts at both blockchains.” (needs tx presigning, which is not yet available on Monero)

Thyagarajan, Malavolta, & Moreno-Sánchez (2021) “Universal atomic swaps: Secure exchange of coins across all blockchains.”

Grunspan & Perez-Marco (2022) “Ping-pong swaps.”


Thanks! I skimmed the papers, some comments below.

The 1st paper you gave has a nice explanation of the original method by Gugger, I find it easier to read. @tl121 check out section 3 (“BTC to XMR atomic swaps”) in Hoenisch & Pino (2021) “Atomic swaps between Bitcoin and Monero.”

It highlights the problem of fees on BTC and how Bob could be exposed to a draining attack because he has to go first. IMO it’s less of a problem on BCH since our fees are significantly lower (also helped by the contracts being smaller). The above paper proposes an alternative where Alice goes first by funding XMR output, but it depends on having adaptor signatures on both chains (as opposed to just on BCH/BTC in the original protocol), which is pending more research.

The 2nd paper needs capability to pre-sign XMR, which is not supported at the moment.

The 3rd paper relies on some generic crypto magic which goes over my head :slight_smile: but it seems to be universally applicable and the transactions will look like ordinary P2PKH, so it is an improvement over HTLCs since it preserves privacy and transactions would be smaller, and also allows cyclic swaps: a multi-party chain of atomic swaps, such that either the whole chain goes through, or nothing does.

The 4th paper uses one-way payment channels to implement an interactive cross-chain swap. Parties simply update the channels in ping-pong fashion: transfer the currencies in small increments to each other until the swap is complete. This sounds promising, as we can have efficient such channels on BCH!

1 Like

I understand the logic of combining those concepts into one, but inventing a new type of malleability this way is mostly just confusing…

And, the important part is that we still are in a phase where the rest of the world is unaware that Bitcoin Cash fixed transaction malleability, so reusing that word for something very different is not something I think is smart.

I’ve had various people ask what malleability is still a problem in the protocol. The knowledge that we fixed them in previous hardforks is absolutely not common knowledge (the segwit loving people are basically saying if its not segwit, you still have malleability) So… My question to you is that maybe you can come up with something less loaded there. “One of the parties re-signed the tx and changed the txid” doesn’t feel like its anywhere near the same concept as malleability fixed with segwit. Transaction malleability - Bitcoin Wiki

1 Like

It is a case of “scriptSig Malleability” described there, e.g. in case of 2-of-2 multisig one of the parties can malleate the scriptSig as a whole without approval of the other party, which is why it’s called “2nd-party malleability”. In case of 2-of-3 with parties A, B, C: C could intercept a TX by A & B and replace either signature with his in order to change the TXID. This is solvable by:

  • SegWit (BTC)
  • PMv3 detached proofs (I think Radiant implemented this)
  • Using TX “idem” for prevout refs instead of TXID (BU was proposing this and implemented it for their new chain)
  • SIGHASH_NOINPUT (BCH solution, we construct the equivalent TX preimage using introspection opcodes and verify it using OP_CHECKDATASIG). This was also proposed in LN WP, pg. 57

SegWit BIP recognizes it as malleability in Motivation section:

In the case of an m-of-n CHECKMULTISIG script, a transaction is malleable only with agreement of m private key holders (as opposed to only 1 private key holder with BIP62)

We implemented BIP62, so the multisig problem persists - but it’s not really a problem for us because we have more advanced Script and can work around it by constructing SIGHASH_NOINPUT, or even better - directly use covenant to enforce particular BCH flow instead of relying on pre-signed multisig.

I did not invent the term “2nd-party malleability” I picked it up somewhere, not sure where, and searching for the term now I was able to find an old mention: Transaction Malleability: Threats and Solutions | SF Bitcoin Devs

thanks for the detailed answer. I’m not sure how this relates to my point, I already acknowledged your logical naming makes sense, I’m not arguing that.

I hope you still understood the gist of my post and please consider it.

1 Like

Are there still any malleability problems?

1 Like

Depends on how you define the “problem”. We implemented all of BIP-62 so closed off 3rd-party malleability.

We don’t have SegWit so it’s still possible for a single signer in multisig to generate a new signature to change TXID without approval of other signers, but this is not a problem for us because we don’t need to rely on multisig to enforce TX template - we have direct covenants for that.

Also, you can emulate SIGHASH_ANYPREVOUT by using introspection opcodes and OP_CHECKDATASIGVERIFY so it’s still possible to pre-sign descendant transactions without them being ruggable if parent TXID changes.


Implement newer scheme: Hoenisch, P. & Pino, L. (2021). Atomic Swaps between Bitcoin and Monero

  • Replace HTLC with PTLC
  • Covenant enforces the destination output in all 3 cases (fwd to Alice, fwd to Refund, fwd to Bob)
  • Contract structure is now exactly the same for both contracts (swaplock & refund)
  • Had Alice & Bob keys mixed up in past versions, fixed now
  • Bump version to v4
  • Remove old versions
  • Update readme


Bytecode sizes:

bytecode v4 size
swaplock 134
swaplock_swap 72
swaplock_refund_start 1
refund 134
refund_complete 72
refund_recover 1

Path sizes:

  • Happy: (swaplock + swaplock_swap) = 206
  • Refund: (swaplock + swaplock_refund_start) + (refund + refund_complete) = 341
  • Recover: (swaplock + swaplock_refund_start) + (refund + refund_recover) = 270

Kudos to user PHCtizen for helping me work out the new scheme!


This would be a very cool project.

Alright, the swap has finally been demonstrated on BCH & XMR mainnets!


  1. @PHCitizen published his Rust implementation and performed TXs on testnets
  2. Few hours later @mainnet_pat published a mannet TX, done using his independent implementation with mainnet-js and cashscript libraries alongside cross-compiled Rust WASM for DLEQ. Also, he optimized the BCH contract a little by removing an unnecessarry sha256 op (which I already updated - v4.1.0).
  3. Shortly after, PHCitizen performed TXs on mainnets and is waiting for review to claim the bounty.

We leave it to PHCitizen to complete and claim the bounty, and we wish him luck in his future endeavors!

@mainnet_pat is interested in building a real app out of this, and for that he’s seeking funding. I’m part of that flipstarter, too, but pat will be doing bulk of the work tho, I’m just doing some contract stuff & research.

I already went ahead and done some research, and realized this VES scheme will work just as well with BTC/LTC/DOGE, and can be made even simpler because those chains are scriptable (we just need OP_IF and OP_CHECKSEQUENCEVERIFY on their end), so we need just the 1 TX on BCH end for both swap & refund paths because the refund path will refund Bob directly instead of sending to the 2nd stage of the refund contract.

This is OK, because Alice doesn’t need Bob for the refund (as she does in XMR-BCH swap, where the 2nd stage is relying on another VES with roles inverted) because timelock on BTC/LTC/DOGE will just release refund path to her directly.

The BTC/LTC/DOGE contract is just this:

    // When Alice pulls from the BCH contract, Bob will learn Alice's
    // key share of this key and he can just sweep it by using this path.
    <key_combined_alice_bob_ves.public_key> OP_CHECKSIG
    // If timelock expires then Alice can sweep BTC back to herself using
    // her own key.
    <key_alice.public_key> OP_CHECKSIG

and I added it to my contract repo here: contracts/v4-BTC-CashTokens · master · ac-0353f40e / cross-chain-swap-ves · GitLab

Oh yeah, we can swap not just BCH but any BCH+CashTokens UTXO for XMR/BTC/LTC/DOGE. When swapping pure BCH the contract is lighter for 15 bytes of covenant code which verify CashTokens contents but other than that it all works the same.


PS @TierNolan you did BTC/LTC HTLC swaps many years back, yeah? Did you check out the new stuff (PTLCs, VES), what do you think of this scheme above?

I am searching for an independent party who can verify the atomicity of the PHCitizen’s proposed implementation, based on @bitcoincashautist 's BCH script contracts. If the implementation is verified as atomic, then PHCitizen would receive the 18.4 XMR + 2.97 BCH bounty.

Maybe someone knowledgeable about the scripts activated in May 2022 could help. It could be reasonable to pay a portion of the bounty to someone who is capable of confirming the implementation’s atomicity (or finding a flaw). Maybe the 2.4 XMR that was contributed to the bounty after PHCitizen implemented their solution, or the equivalent in BCH.


I’ve taken a look at the v4-XMR contract by @bitcoincashautist. While I haven’t done a deep dive on the cryptography used, I can verify the workings of the contract being a co-developer of CashScript.

I’ve found some outdated comments and asked to improve some comments.
Specifically, to clearly mention that the off-chain interaction that takes place in preparation for the contract (aliceSignatureVES) and that this setup is asymetrical (bobSignatureVES) is not yet revealed during the setup of the contract because bob needs to make sure refundLockingBytecode is honest.

PHCitizen’s impementation is fully written in rust is not using the Cashscript SDK, so I can’t verify his implementation.

If I needed to describe the workings of swap contract in my own words:

either 1) both parties collaborate and swap goes through in the Swaplock contract which uses an interactive construction of bobSignatureVES after the initial contract set-up
or a timelock expires and the funds are forwarded to the Refund contract
then 2) a refund of the original funds can (should) be initiated by bob
3) if bob fails to refund within a given timeframe (=is being dishonest) alice gets his BCH after all, bob gets nothing (there is still the option to rescue the locked XMR if alice shows grace)

The contract construction favors Alice, as only bob can be punished by losing his funds and not receiving anything in return. This is because bob is favored in the asymmetrical setup where he already gets aliceSignatureVES needed to initiate a refund.

Overall, the design is sensible and because of the refund- and punishment timelocks, honest behavior is incentivized.


We can verify that he uses my contract by comparing compiled bytecode with what he has here:

  • Rust: c3519dc4519d00c600cc949d00cb009c6300cd7888547978a85379bb675279b27500cd54798854790088686d6d7551
  • Output of cashc -h (v4.0.0, v4.1.0 will differ in 1 opcode, sha256): c3519dc4519d00c600cc949d00cb009c6300cd7888547978a85379bb675279b27500cd54798854790088686d6d7551

Note that both and have the same contract “tail”, it’s just the committed contract parameters that get changed depending on whether it’s 1st or 2nd stage.

This part of Rust code appends the variables with the contract tail:

also, we can look at published TX unlocking bytecode to further confirm:

304402204c837eb43ac0450426e224f4479aafc67520db5063f1d25a9a32b8017ce403aa022062132decb368365c7bcea94b0b763afc1c0b72d57aab4f7e198d6d334da3df1c 17a91443265bc939f862ca5c66dc9f8c4379578973b949875221027a7ce6addff4ed215b84fce6fa873fd9e0c0b8f7dc46aa6b45453af2cc4a24501976a91485bc957e1184eca3f8e3fac3bbec72bc135d8f6788ac02e803c3519dc4519d00c600cc949d00cb009c6300cd7888547978a85379bb675279b27500cd54798854790088686d6d7551


I have added optimized versions of contracts:

Contract parameters are now not at the “head” (as with contracts compiled from CashScript) but inserted where they are used. OP_IF and stack state is optimized, too, and we save 18 bytes / TX.

I changed the approach for BTC-BCH refunds to improve privacy: instead of forwarding direct to Bob’s P2PKH we now forward to a simple P2SH contract that will then forward UTXO’s contents to Bob’s P2PKH address.

This way, if happy path is used, there will be no way to tell, by just looking at BCH blockchain, whether the BCH contract was used for BTC-BCH swap or for XMR-BCH swap.

For pure BCH swaps, just remove the block of opcodes (15 bytes) that verify token state is being passed on.

Input bytecode sizes (redeem script + unlocking data) are now:

bytecode XMR-CashTokens XMR-BCH BTC-CashTokens BTC-BCH
swaplock + swaplock_swap 203 188 203 188
swaplock + swaplock_refund_start 131 116 131 116
refund + refund_complete 203 188 58 43
refund + refund_recover 131 116

The BTC variant of the contract is usable for swapping BCH with LTC and EVM chains, too.
For Taproot chains, we can get really small footprint on their end and also hide the fact that the TX is part of a swap (happy path will look like ordinary P2PK spend, and Taproot tweak would be used to reveal the refund when necessary).


Some updates:

@PHCitizen has successfully completed the bounty, congratulations!

@mainnet_pat has successfully raised flipstarter funds to build a swap platform, and there’s been some progress already, here’s the latest update from Pat on X:

Finished the first version of the DLEQ Tools package for Rust and JavaScript GitHub - mainnet-pat/dleq-tools
It features all routines necessary to build adaptor signatures and prove that both parties are being honest during swap

This package compiles to #nodejs and browser-compatible #wasm module. Check it out on NPM dleq-tools - npm

Next step is to build the message exchange protocol. Going to try out the #libp2p with #webrtc transport for direct browser to browser communication. Stay tuned


we need a good GUI for XMR-BCH atomic swaps wallet

and/or a common GUI wallet interface for both coin-swaps, coin-join and atomic swaps implementation done in one place