What changed, and why it matters
This commit tightens how Electrum handles upfront 'prepayment' invoices for submarine swaps (a way to exchange on-chain and Lightning bitcoin). Previously the client calculated the prepayment using a local transaction-batcher fee estimate and had no check on what the swap server actually charged in its fee invoice. The patch switches the prepayment calculation to a known 'mining_fee' value and, crucially, rejects the swap if the server's fee invoice exceeds twice that announced mining fee. This closes a path where a malicious or buggy swap server could demand a larger prepayment than the user expected.
Review how self.mining_fee is negotiated or sourced from the swap server offer to ensure the bound itself cannot be manipulated; confirm the new validation is covered by tests; consider user-facing messaging when a swap is rejected due to fee mismatch.
Security signals we found
adds server-supplied fee bound validation
replaces local fee estimate with announced mining_fee for prepayment calculation
prevents swap server from inflating prepayment invoice
partial patch: does not show whether mining_fee itself is trustworthily obtained
Evidence from the diff
In electrum/submarine_swaps.py the hold-invoice creation logic now bases the prepay_amount_sat on self.mining_fee * 2 instead of self.get_fee_for_txbatcher() * 2, and the invoice message is renamed to ‘Submarine swap prepayment’. More significantly, during swap validation the client now parses the server’s fee_invoice and raises SwapServerError if fee_lnaddr.get_amount_sat() > self.mining_fee * 2. This enforces the advertised fee bound and prevents the server from silently increasing the prepayment.
Changed components
electrum/submarine_swaps.pySwapManagersubmarine swap prepayment invoice handlingInspect captured patch +7 / −2
diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py
index 4b008d6..fc331c0 100644
--- a/electrum/submarine_swaps.py
+++ b/electrum/submarine_swaps.py
@@ -571,7 +571,9 @@ class SwapManager(Logger):
) -> Tuple[SwapData, str, Optional[str]]:
"""creates a hold invoice"""
if prepay:
- prepay_amount_sat = self.get_fee_for_txbatcher() * 2
+ # server requests 2 * the mining fee as instantly settled prepayment so that the mining
+ # fees of the funding tx and potential timeout refund tx are always covered
+ prepay_amount_sat = self.mining_fee * 2
invoice_amount_sat = lightning_amount_sat - prepay_amount_sat
else:
invoice_amount_sat = lightning_amount_sat
@@ -593,7 +595,7 @@ class SwapManager(Logger):
_, prepay_invoice = self.lnworker.get_bolt11_invoice(
payment_hash=prepay_hash,
amount_msat=prepay_amount_sat * 1000,
- message='Submarine swap mining fees',
+ message='Submarine swap prepayment',
expiry=300,
fallback_address=None,
channels=channels,
@@ -978,6 +980,9 @@ class SwapManager(Logger):
# check that the lightning amount is what we requested
if fee_invoice:
fee_lnaddr = self.lnworker._check_bolt11_invoice(fee_invoice)
+ if fee_lnaddr.get_amount_sat() > self.mining_fee * 2:
+ raise SwapServerError(_("Mining fee requested by swap-server larger "
+ "than what was announced in their offer."))
invoice_amount += fee_lnaddr.get_amount_sat()
prepay_hash = fee_lnaddr.paymenthash
else:
Why this scored 47/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.