onion_message: use util.random_shuffled_copy instead rand sort
What changed, and why it matters
This commit replaces a shaky way of shuffling lists with a proper utility function. The old code sorted items by random numbers, which can produce biased or predictable ordering and is generally considered bad practice. The change itself is a small code-quality improvement, not a clear-cut security fix, but it removes a weak randomness pattern used when selecting Lightning Network channels and peers for routing private messages.
Treat as a minor hardening/cleanup change. Review `electrum.util.random_shuffled_copy` to confirm it uses a cryptographically secure random source appropriate for network privacy decisions. No urgent action required unless the old ordering was relied upon for a security property.
Security signals we found
Replaced non-cryptographic random sort key with a dedicated shuffle utility
Removed unused imports (dataclasses, MappingProxyType, random)
Change affects selection of Lightning channels and onion-message peers for blinded path construction
Pattern is a known anti-pattern: sorting by random() does not produce a uniformly random permutation
Evidence from the diff
The patch removes from random import random and dataclasses/MappingProxyType imports, and instead imports random_shuffled_copy from electrum.util. It replaces two sorted(list, key=lambda x: random()) calls in get_blinded_paths_to_me() with random_shuffled_copy(...). The affected lists are my_channels and my_onionmsg_peers, which are randomized before truncating to max_paths entries and using them to build blinded paths for BOLT-12-style onion messages. Sorting by random() is non-uniform and can leak information about relative ordering or be influenced by the sort algorithm’s behavior; using a dedicated shuffle utility is the correct approach.
Changed components
electrum/onion_message.pyget_blinded_paths_to_me()Blinded path construction for onion messagesInspect captured patch +3 / −8
diff --git a/electrum/onion_message.py b/electrum/onion_message.py
index 71634fc..9f3c415 100644
--- a/electrum/onion_message.py
+++ b/electrum/onion_message.py
@@ -27,9 +27,6 @@ import io
import os
import threading
import time
-import dataclasses
-from random import random
-from types import MappingProxyType
from typing import TYPE_CHECKING, Optional, Sequence, NamedTuple, Tuple, Union
@@ -44,7 +41,7 @@ from electrum.lnonion import (get_bolt04_onion_key, OnionPacket, process_onion_p
OnionHopsDataSingle, decrypt_onionmsg_data_tlv, encrypt_onionmsg_data_tlv,
get_shared_secrets_along_route, new_onion_packet, encrypt_hops_recipient_data)
from electrum.lnutil import LnFeatures, MIN_FINAL_CLTV_DELTA_ACCEPTED, MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED
-from electrum.util import OldTaskGroup, log_exceptions
+from electrum.util import OldTaskGroup, log_exceptions, random_shuffled_copy
def now():
@@ -425,8 +422,7 @@ def get_blinded_paths_to_me(
local_height = lnwallet.network.get_local_height()
if len(my_channels):
- # randomize list
- rchans = sorted(my_channels, key=lambda x: random())
+ rchans = random_shuffled_copy(my_channels)
for chan in rchans[:max_paths]:
hop_extras = None
if not onion_message: # add hop_extras and payinfo, assumption: len(blinded_path) == 2 (us and peer)
@@ -482,8 +478,7 @@ def get_blinded_paths_to_me(
my_onionmsg_peers = [peer for peer in lnwallet.lnpeermgr.peers.values() if
peer.their_features.supports(LnFeatures.OPTION_ONION_MESSAGE_OPT)]
if len(my_onionmsg_peers):
- # randomize list
- rpeers = sorted(my_onionmsg_peers, key=lambda x: random())
+ rpeers = random_shuffled_copy(my_onionmsg_peers)
for peer in rpeers[:max_paths]:
blinded_path = create_blinded_path(os.urandom(32), [peer.pubkey, mynodeid], final_recipient_data)
result.append(blinded_path)
Why this scored 16/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.