Merge pull request #10853 from f321x/swapserver_negative_nonce
What changed, and why it matters
This patch fixes a small bug in how Electrum checks proof-of-work (PoW) nonces from Nostr swap server announcements. Previously, a missing or negative nonce was treated as having zero PoW, which is correct for missing nonces but the code also allowed negative nonces to be passed into the hash calculation. The patch now explicitly rejects negative nonces and moves the PoW check inside the try block so any parsing error skips the announcement. In practical terms, a malicious or buggy swap server could have advertised a negative nonce; depending on the exact behavior of the hash function with negative numbers, this could have caused a crash, incorrect PoW evaluation, or unexpected filtering of swap servers.
Treat this as a low-to-moderate hardening fix. Users running versions before this merge should upgrade if they use submarine swaps / Nostr-based swap server discovery. Developers should audit whether negative or malformed nonces could have reached any other hash helpers and add unit tests for boundary nonce values.
Security signals we found
Input validation gap: negative PoW nonce accepted
Potential DoS/crash due to unexpected negative value in cryptographic hash path
Validation moved into exception-handled parsing block
Submarine swap server discovery relies on client-side PoW filtering
Evidence from the diff
In electrum/submarine_swaps.py, the call to get_nostr_ann_pow_amount() was moved inside the try/except that parses pow_nonce from the Nostr announcement, so failures (including negative nonces) cause the announcement to be skipped. In electrum/util.py, get_nostr_ann_pow_amount() now returns 0 (no PoW) if nonce is None/0 OR if it is negative. Previously, only falsy nonces returned 0; negative integers would proceed to bytes.fromhex-like formatting and SHA-256 hashing. The exact downstream effect depends on how negative ints are serialized before hashing, but it is at minimum a validation bypass and potentially a DoS/crash vector.
Changed components
electrum/submarine_swaps.pyelectrum/util.pyNostr-based swap server announcement PoW verificationInspect captured patch +2 / −2
### electrum/submarine_swaps.py
@@ -2079,9 +2079,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 49/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.