What changed, and why it matters
This commit fixes a small bug in Electrum's submarine swap feature. When a swap server advertises itself using a Nostr event, the client checks a 'proof-of-work nonce' to verify the server did enough computational work. Before the patch, a negative nonce could slip through and be treated as valid, potentially letting a low-effort or malicious swap server bypass the anti-spam check. The fix treats negative nonces the same as a missing nonce (zero proof-of-work bits).
Treat as a low-severity hardening patch. Users running submarine swaps should update to a version containing this commit. No immediate emergency response is warranted, but the fix should be included in the next release.
Security signals we found
Input validation hardening for externally supplied PoW nonce
Negative integer nonce could bypass or distort proof-of-work scoring
Nostr swap announcement parsing is an untrusted input surface
Fix is minimal and defensive, suggesting a recognized edge case
Evidence from the diff
In electrum/submarine_swaps.py, get_nostr_ann_pow_amount() was called before validating that pow_nonce was non-negative. A negative int passed to the hash computation would raise an exception only after the call, but the broader issue is that get_nostr_ann_pow_amount treated any truthy nonce as valid. In electrum/util.py, the guard now returns 0 for nonce < 0, ensuring negative nonces cannot be used to satisfy SWAPSERVER_POW_TARGET. The submarine_swaps.py change moves the pow_bits assignment inside the try/except so a negative-nonce-related failure is caught gracefully. This is a hardening fix for input validation in the swap server’s Nostr announcement PoW verification.
Changed components
electrum/submarine_swaps.pyelectrum/util.pyNostr swap announcement PoW verificationInspect captured patch +2 / −2
### electrum/submarine_swaps.py
@@ -2005,9 +2005,9 @@ async def _get_pairs_loop(self):
continue
try:
pow_nonce = int(content.get('pow_nonce', "0"), 16) # type: int
+ pow_bits = get_nostr_ann_pow_amount(bytes.fromhex(pubkey), pow_nonce)
except Exception:
continue
- pow_bits = get_nostr_ann_pow_amount(bytes.fromhex(pubkey), pow_nonce)
if pow_bits < self.config.SWAPSERVER_POW_TARGET:
self.logger.debug(f"too low pow: {pubkey}: pow: {pow_bits} nonce: {pow_nonce}")
continue
### electrum/util.py
@@ -2459,7 +2459,7 @@ async def gen_nostr_ann_pow(nostr_pubk: bytes, target_bits: int) -> Tuple[int, i
def get_nostr_ann_pow_amount(nostr_pubk: bytes, nonce: Optional[int]) -> int:
"""Return the amount of leading zero bits for a nostr announcement PoW."""
- if not nonce:
+ if not nonce or nonce < 0:
return 0
hash_function = hashlib.sha256
hash_len_bits = 256Why this scored 37/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.