Skip to content

Conversation

@binarybaron
Copy link

@binarybaron binarybaron commented Nov 4, 2025

This PR introduces a change in the atomic swap protocol to discourage spam. It does so by attaching a fee to refunds. This fee will be burnt.

Current situation

The exchange rate of each swap is set at the start and can't be changed.
A swap either completes within 12 hours (this requires cooperation by both parties), or be cancelled.
A swap might still be cancelled after both parties lock their respective currency.

In that case the taker ("Bob") can issue a refund within a 24 hours window. He will get his Bitcoin back and the maker ("Alice") will get her Monero back.

The problem

This can be abused by malicious takers in two ways:

  1. A maker with significantly higher reserves than other makes might use part of it to block other maker's reserves.
    This is possible by initiating a swap and waiting for the maker to lock the Monero.
    Then not continue the swap and wait for a refund.
    This will only cost the malicious actor transaction fees for 3 Bitcoin transactions, while preventing the "small maker" from competing in the market for trading volume.
  2. A malicous actor might start a swap and decide only to complete it when the exchange rates change in their favor enough.
    If that doesn't happen they can refund for the low cost of 3 Bitcoin transactions.
    This allows the malicious actor to treat the swap like a (bascially) free Monero call option.

The solution

The root problem causing both these issues is that refunds are free (for the taker).
By attaching a cost to refunds (only refunding a part of the Bitcoin) this is no longer the case.
Thus both tactics are no longer profitable.

Makers will be able to set the extend of the refund fee. Takers will know the refund fee prior to starting a swap and decide whether to trade or not.

The refund fee does not go to the maker. That would create skewed incentives. The refund fee will be "burnt". The only way to retrieve it is for the maker to sign a transaction giving it back to the taker.

However, the maker will be able to voluntarily give the taker a full refund. This is what we call "amnesty" for now. This is intended such that makers can help out clueless takers who accidentally refunded.
This path is, however, purely voluntary and takers can't 100% rely on it.

Implementation

  • Add TxPartialRefund
  • Add TxRefundAmnesty
  • Change swap setup to transmit amnesty_amount, tx_partial_refund_fee, tx_refund_amnesty_fee, tx_partial_refund_encsig and optionally tx_full_refund_encsig and tx_refund_amnesty_sig
  • Add those same fields to the necessary states
    • Alice
    • Bob
  • Add new states to the Bob's state machine to handle the new scenarios
  • Implement Alice watching for TxFullRefund and TxPartialRefund
  • Desing + implement system for Alice to configure full refunds and amnesty
    • add config entry
    • update orchestrator wizard and add swap-controller command
  • Figure out how to make it all backwards compatible with old swaps stored in the database
image

@binarybaron binarybaron marked this pull request as draft November 4, 2025 19:12
@binarybaron
Copy link
Author

binarybaron commented Nov 4, 2025

This issue has been assigned a bounty (💰)

Current bounty:

Exceptions for this specific bounties

Due to the nature of this issue (cryptographic/protocol complexity rather than implementation complexity) the bounty will also be used to fund audits.

Bounty donation address

If you want to incentivize work on this issue, you can help increase the bounty by donating to the address below.

87UHJU9GA3qHn4sX4ZZndEERmqEXx94kuS3QFZ1XGVNg28YcWX2H9AqBh5G6Uc4cfmKRbTDQ3HgsoVKW1RuPHtzLSUEHrmN

Fine print

We use bounties to incentivize development and reward contributors. All issues available for a bounty have the Bounty 💰 label.

To receive the bounty of this issue, you agree to these conditions:

  • Bounties will be set and awarded at discretion of @binarybaron
  • An issue is considered resolved when the patch(es) proposed by the contributor is/are merged in the appropriate repository according to terms of the issue.
  • The first person who resolves an issue in its entirety will receive the entire amount of the bounty.
  • If the issue is resolved collaboratively by more than one person, the reward will be distributed among the contributors
  • Donating to the bounty does not guarantee that this issue will be completed and refunds are generally not issued. If they are then they are granted at the discretion of @binarybaron

@pokefan77
Copy link

Can you elaborate when partial refund will be done instead of full refund and punish?

It seems like Bob needs Alice to get 10% of his money back in case of partial refund.
Why 10% of Bobs money is at risk in case Alice doesnt coorporate, which happened before with some makers!

@Einliterflasche
Copy link

Yes. This update will essentially move some trust requirement from the maker (that the taker is not a spammer) to the taker (that the maker will refund).

However, this can be addressed as follows:

  • a decentralized reputation system. Takers who didn't get the refund can publish a proof, thereby discouraging other takers from swapping with this maker
  • takers might choose to only start swaps for which the maker has already provided the full refund address. Thus not requiring the maker to fully refund
  • the maker does not gain anything from withholding the refund, thereby he is not incentivized to do so

Everyone would be better off if this wasn't necessary, but it is. That's the sad truth.

@pokefan77
Copy link

I am not sure if i and others understand your words "takers might choose to only start swaps for which the maker has already provided the full refund address. Thus not requiring the maker to fully refund" can you rephrase it please? sorry

"the maker does not gain anything from withholding the refund, thereby he is not incentivized to do so"
your phrasing is totally misleading, confirming your narrative. My question was why Bobs 10% are at risk, and neutrally said the truth is: "the maker does not gain anything from releasing the refund, thereby he is not incentivized to do so"
Its the same situation as btc punished. the truth is that there is even ongoing discussion on matrix because somebody lost his big amount of money because the maker did not help him, because he has 0 incentive to do so.

Can you elaborate when partial refund will be done instead of full refund and punish?

@jstark2a
Copy link

jstark2a commented Nov 7, 2025

I am not sure if i and others understand your words "takers might choose to only start swaps for which the maker has already provided the full refund address. Thus not requiring the maker to fully refund" can you rephrase it please? sorry

I am also trying to understand but here is how I interpreted it: Makers will have the choice to offer full refunds or partial refunds. The taker will know the maker's refund policy before they begin a swap. The full refund policy is straightforward: If the taker cancels, they get the full refund. For the partial refund policy, if the taker cancels, they are not guaranteed to receive 100% of their money back. The purpose of this is to disincentivize malicious takers from abandoning swaps because they know there is no penalty for doing so. If you as a taker do not like the idea of a partial refund, you as a free market participant, can refuse to do business with that maker. Because partial refunds mean less risk to the maker, this will allow them to offer lower rates.

somebody lost his big amount of money because the maker did not help him, because he has 0 incentive to do so.

He lost his money due to negligence (or at least failure to understand the refund process). As the docs state "With most makers, you can still redeem the Monero even after being punished. This is, however, purely voluntary and we advise against relying on this."

@pokefan77
Copy link

That would mean either makers are ok with full refund = they automatically give it, or they are not ok with full refund, meaning they will also not help giving it. Its just 10% scam. Its not even remotely justified as fees of all makers are currently around 2%, why should a taker loose 10%?

@jstark2a
Copy link

jstark2a commented Nov 7, 2025

That would mean either makers are ok with full refund = they automatically give it, or they are not ok with full refund, meaning they will also not help giving it. Its just 10% scam. Its not even remotely justified as fees of all makers are currently around 2%, why should a taker loose 10%?

It's not a scam if you know what you are agreeing to. The taker also doesn't lose anything if they simply complete the swap like they agreed to. Makers are not obligated to hand out free 12 hour XMR call options.

Adding a partial refund support will actually help honest takers. If there is not penalty, a maker can lock up the liquidity of other makers that offer lower rates than them. Then they can jack up their rate because their competition is gone. This is effectively a Denial of Service attack against makers.

@pokefan77
Copy link

If thats the worry the penality fee should at maximum be the fee that they ask from the user for a completed trade, not a flat 10%

@Franz-Hermann-GT
Copy link

If thats the worry the penality fee should at maximum be the fee that they ask from the user for a completed trade, not a flat 10%

The XMR price movement also needs to be taken into account here — it can easily move 5–8% during those 12 hours.
In most cases, takers cancel the swap when the XMR price drops during that period. As mentioned earlier, this effectively works like a free call option.

@binarybaron
Copy link
Author

It is not a flat 10%. The 10% is purely for demonstrational purposes. The percentage can be dynamically configured by the maker.

@binarybaron
Copy link
Author

This issue has been assigned a bounty (💰)

Current bounty:

Exceptions for this specific bounties

Due to the nature of this issue (cryptographic/protocol complexity rather than implementation complexity) the bounty will also be used to fund audits.

Bounty donation address

If you want to incentivize work on this issue, you can help increase the bounty by donating to the address below.


87UHJU9GA3qHn4sX4ZZndEERmqEXx94kuS3QFZ1XGVNg28YcWX2H9AqBh5G6Uc4cfmKRbTDQ3HgsoVKW1RuPHtzLSUEHrmN

Fine print

We use bounties to incentivize development and reward contributors. All issues available for a bounty have the Bounty 💰 label.

To receive the bounty of this issue, you agree to these conditions:

  • Bounties will be set and awarded at discretion of @binarybaron

  • An issue is considered resolved when the patch(es) proposed by the contributor is/are merged in the appropriate repository according to terms of the issue.

  • The first person who resolves an issue in its entirety will receive the entire amount of the bounty.

  • If the issue is resolved collaboratively by more than one person, the reward will be distributed among the contributors

  • Donating to the bounty does not guarantee that this issue will be completed and refunds are generally not issued. If they are then they are granted at the discretion of @binarybaron

I wanted to specify this in a bit more in detail as they seems to be some confusion around the bounty on this issue.

In general donations to specific issues will NEVER go directly to neither me nor @Einliterflasche. Issue specific bounties are for funding work of outside contributors.

Donations to this issue will not necessarily result in me implementing this in a faster manner.

Donations will be used to potentially fund an outside contributor to give this a review to assess the security of this on the protocol level. As the funds collected here are unlikely to be enough to fund a full audit (something like TrailOfBits would do) this is going to be "light review" most likely with no attribution to the reviewer.

We are in talks with someone who is well trusted in the Monero community and is more than enough qualified.

If we don't find a reviewer, the funds will be used for other bounties.

@pokefan77
Copy link

Shouldnt a protocol change be fully audited?

@binarybaron
Copy link
Author

binarybaron commented Nov 8, 2025

Shouldnt a protocol change be fully audited?

No, not at all protocol changes necessarily require an audit. We are not introducing any new novel cryptography here.

I understand the concern here though. This will obviously be well tested.

@binarybaron binarybaron changed the title protocol: Partial refund protocol: Spam deterrence Nov 8, 2025
@Einliterflasche Einliterflasche force-pushed the protocol/partial-refund branch 2 times, most recently from 266e3a1 to c5e0871 Compare December 1, 2025 15:32
@Einliterflasche Einliterflasche force-pushed the protocol/partial-refund branch from c5e0871 to f46f4ee Compare December 1, 2025 16:01
// We sent Bob the encsig for the full refund path already, so we don't
// care about the partial refund path signatures of Bob anyway.
// We just save `None`.
if self.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO) == bitcoin::Amount::ZERO {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here as above, also perhaps extract this into some trait that allows checking if the amount is high enough to construct a transaction for it?

@Einliterflasche
Copy link

what reputation system are you refering to?

Currently there is no reputation system. But now that makers can, in theory, burn some of the refund of the takers we still want to incentivize not doing that.

The taker could proof that the maker burnt the funds by sharing TxRefundBurn. This means takers can avoid makers who are shown to haven often burnt refunds in the past.

pub mod containers;
pub mod images;

use anyhow as _;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this necessary? maybe we should hide some of these crates behind a flag if there only used for the binary?

// should go into the amnesty output
send_wallet_snapshot: bmrng::RequestReceiver<
bitcoin::Amount,
(swap_setup::alice::WalletSnapshot, bitcoin::Amount),
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extract this into a named tuple or add an explicit comment above this line (// (snapshot, amnesty_amount)

#[typeshare]
pub struct BidQuote {
/// The price at which the maker is willing to buy at.
#[serde(with = "::bitcoin::amount::serde::as_sat")]
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its good to keep these for completeness snake. We want the network protocols serialization to be defined as explicitly as possible.

pub enum SpotPriceError {
NoSwapsAccepted,
AmountBelowMinimum {
#[serde(with = "::bitcoin::amount::serde::as_sat")]
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, I think we should keep them

/// Bob locks Btc and Alice locks Xmr. Alice does not act so Bob does a partial
/// refund, waits for the remaining refund timelock, and then claims the amnesty.
#[tokio::test]
async fn given_partial_refund_bob_claims_amnesty_after_timelock() {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need another integration tests for the other paths

@pokefan77
Copy link

what reputation system are you refering to?

Currently there is no reputation system. But now that makers can, in theory, burn some of the refund of the takers we still want to incentivize not doing that.

The taker could proof that the maker burnt the funds by sharing TxRefundBurn. This means takers can avoid makers who are shown to haven often burnt refunds in the past.

while the idea is great its easily avoided: just swipe setup and get new maker identity if labeled as described above
as there is currently no benefit of maintaining a "Identity"

@Franz-Hermann-GT
Copy link

what reputation system are you refering to?

Currently there is no reputation system. But now that makers can, in theory, burn some of the refund of the takers we still want to incentivize not doing that.
The taker could proof that the maker burnt the funds by sharing TxRefundBurn. This means takers can avoid makers who are shown to haven often burnt refunds in the past.

while the idea is great its easily avoided: just swipe setup and get new maker identity if labeled as described above as there is currently no benefit of maintaining a "Identity"

I think there is an important asymmetry here: maker identities are much more costly to rotate than taker identities. A taker can reset their identity with a simple reinstall, while a maker typically runs long-lived infrastructure with stable liquidity and uptime.
This also means that a simple “burn count”–based reputation could unfairly penalize makers who are actively targeted by spammers. That would make a maker's identity look “bad” purely because he's being attacked, not because he misuses burns.
Additionally, due to the recent improvements, the number of swaps failing for purely technical reasons is converging towards zero. This means that most remaining aborts are behavioral rather than technical, which makes contextual interpretation of burns even more important.

@Franz-Hermann-GT
Copy link

As a side note, the previously used “went online X days ago” metric (or similar) could also be a useful additional signal for makers, as it naturally reflects identity persistence and operational stability.

@Franz-Hermann-GT
Copy link

Maintaining a simple taker peer ID–based blacklist can be quite effective, too

@jstark2a
Copy link

Maintaining a simple taker peer ID–based blacklist can be quite effective, too

I noticed the spammer (linked to the same btc), usually rotates peer ids now

@Einliterflasche
Copy link

Einliterflasche commented Dec 27, 2025

Bumping this from a draft PR to normal PR in order to run the tests.

TODO:

  • command for updating should_burn_on_refund
  • command for granting final amnesty
  • update taker gui to accommodate new states / transactions
  • add alice's commitment to TxRefundBurn to swap setup
  • add partial refund policy to asb quotes
  • gossip system for misbehaviour proofs (peer id of maker, commitment to TxRefundBurn and txid of published TxRefundBurn)
  • add reputation score based on proven misbehaviour to taker gui

@Einliterflasche
Copy link

Integration tests for full refund, partial refund + amnesty are passing in the stagenet environment. Also TODO: rename the transactions/states to something more sensical

@Einliterflasche Einliterflasche marked this pull request as ready for review December 27, 2025 17:15
@binarybaron
Copy link
Author

The tx_burn_refund transaction is modified such that Alice needs to leak her private key (used for just this one swap) when burning the refund. The same goes for the Bitcoin punish transaction. Additionally, Bob will transmit the encrypted signature to Alice by sending a Monero transaction to the shared Monero wallet where the encrypted signature is included in the tx_extra field of the transaction (tx_msg_reveal_enc_sig). The encrypted signature is XORd with the private view key of the shared Monero wallet.

This will allow Alice and Bob to construct a proof which can be shared with others. When verified a third party (Carol) can check whether:

  1. Alice partially burnt the refund despite Bob transmitting the encrypted signature in time (Alice misbehaved)
  2. Alice did not lock the Monero despite Bob locking his Bitcoin in time (Alice misbehaved)
  3. Alice punished Bob despite not locking the Monero herself (Alice misbehaved)
  4. Alice punished Bob despite him revealing the encrypted signature on chain (Alice misbehaved)
  5. Bob refused to transmit the encrypted signature despite Alice locking the Monero in time (Bob misbehaved)

This allows Carol to "blame" one of the parties for the failed swap and she can make decision not to trust the misbehaved party. Alice might share the proof to warn others of Bobs behavior. Other makers might blacklist Bob after verifying the proof. Bob might share the proof if Alice misbehaved. Other takers might decide not to swap with Alice anymore.

This essentially allows for a reputation system which is purely focused on negative examples.

A proof consists of:

  • The UUID of the swap
  • The private and public view key of the shared Monero wallet
  • The transaction hash of the Bitcoin tx_lock
  • The transaction has of the Bitcoin tx_punish
  • The transaction hash of the different Bitcoin refund transactions
  • The Monero amount and the Bitcoin amount negotiated at the start of the swap
  • Public keys of both Bob and Alice (specific to that swap)

Additionally, the signature is signed by both Bob and Alice using their Peer ID. The full public key is also included for each peer. Both Alice and Bob "commit" to the proof. Bob can also append the Monero transaction hash of the transaction that reveals the encrypted signature to Alice. Alice can append the Monero transaction hash of the transaction that funds the shared Monero wallet. These are included to make proof verification faster.

During verification Carol will do the following (for each of the cases):
First she will check if tx_lock was confirmed on chain within a reasonable time

  1. Carol checks if the tx_burn_partial_refund Bitcoin transaction can be found on chain. She extracts the revealed private key of Alice. She uses the committed to public key of Alice to verify that the private key is the correct one. If it isn't correct, Alice has misbehaved as she has revealed the wrong private key which prevents others from determining who is to blame. We therefore blame her.
    If the private key is correct, she scans for tx_msg_reveal_enc_sig. If she finds it, she extracts the tx_extra decodes with the private view key. She then uses Alice private key to determine if the encrypted signature is correct. If it is, Alice has misbehaved as she has burnt the refund despite Bob revealing the encrypted signature in time. If she doesn't find tx_msg_reveal_enc_sig Bob is to blame.
  2. Carol will scan the shared Monero wallet for a reasonable amount of blocks. She scans for the Monero lock transaction. If she cannot find it or the amount does't match, Alice has misbehaved as she has not locked her Monero in time.
  3. Similar to (2.) but Carol will also check if tx_punish is present on chain. If it is present and no Monero have been locked, Alice has misbehaved.
  4. Similar to (1.). Check if Bob revealed the correct encrypted signature on-chain. Check for existence of tx_punish on chain.
  5. (see 1.)

note: this needs to be written down in a cleaner spec and will be implemented in another PR (different from this one)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Alice Related to the maker state machine Bob Related to the taker state machine enhancement New feature or request protocol

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants