plugins: timelock_recovery: move checksum func to base class
What changed, and why it matters
This commit is a small internal code cleanup. It moves a checksum helper function from the Qt (graphical user interface) part of the timelock recovery plugin into the plugin's shared base class so it can be reused and tested without needing to load the Qt GUI code during tests. The checksum logic itself is unchanged, and there is no security fix or behavior change visible in the diff.
No security action required. This is a benign refactor. Reviewers may optionally verify that the moved checksum method remains byte-for-byte identical and that test coverage is preserved.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change refactors Plugin._checksum() in electrum/plugins/timelock_recovery/qt.py into TimelockRecoveryPlugin.json_checksum() in electrum/plugins/timelock_recovery/timelock_recovery.py. The implementation (SHA-256 of canonicalized JSON, truncated to 8 hex chars) is identical. Call sites in qt.py are updated, and the test file now imports from the base class instead of the Qt plugin class, avoiding a Qt import in the test suite. The commit message explicitly states the motivation: ‘don’t import qt from tests unless it is qt being tested’.
Changed components
electrum/plugins/timelock_recovery/qt.pyelectrum/plugins/timelock_recovery/timelock_recovery.pytests/test_timelock_recovery.pyInspect captured patch +24 / −20
diff --git a/electrum/plugins/timelock_recovery/qt.py b/electrum/plugins/timelock_recovery/qt.py
index 7b96a9a..2d17a5e 100644
--- a/electrum/plugins/timelock_recovery/qt.py
+++ b/electrum/plugins/timelock_recovery/qt.py
@@ -681,17 +681,6 @@ class Plugin(TimelockRecoveryPlugin):
return bool(download_dialog.exec())
- @classmethod
- def _checksum(cls, json_data: dict[str, Any]) -> str:
- # Assumes the values have a consistent json representation (not a key-value
- # object whose fields can be ordered in multiple ways).
- return hashlib.sha256(json.dumps(
- sorted(json_data.items()),
- skipkeys=False, ensure_ascii=False, check_circular=True,
- allow_nan=True, cls=None, indent=None, separators=(',', ':'),
- default=None, sort_keys=False,
- ).encode()).hexdigest()[:8]
-
def _save_recovery_plan_json(self, context: TimelockRecoveryContext, download_dialog: WindowModalDialog):
try:
# Open a Save As dialog to get the file path
@@ -728,7 +717,7 @@ class Plugin(TimelockRecoveryPlugin):
"recovery_outputs": [[tx_output.address, tx_output.value] for tx_output in context.recovery_tx.outputs()],
}
# Simple checksum to ensure the file is not corrupted by foolish users
- json_data["checksum"] = self._checksum(json_data)
+ json_data["checksum"] = self.json_checksum(json_data)
json.dump(json_data, json_file, indent=2)
download_dialog.show_message(_("File saved successfully"))
context.recovery_plan_saved = True
@@ -766,7 +755,7 @@ class Plugin(TimelockRecoveryPlugin):
"cancellation_amount": context.cancellation_tx.output_value(),
}
# Simple checksum to ensure the file is not corrupted by foolish users
- json_data["checksum"] = self._checksum(json_data)
+ json_data["checksum"] = self.json_checksum(json_data)
json.dump(json_data, f, indent=2)
download_dialog.show_message(_("File saved successfully"))
context.cancellation_plan_saved = True
diff --git a/electrum/plugins/timelock_recovery/timelock_recovery.py b/electrum/plugins/timelock_recovery/timelock_recovery.py
index 27cb679..9d42cf4 100644
--- a/electrum/plugins/timelock_recovery/timelock_recovery.py
+++ b/electrum/plugins/timelock_recovery/timelock_recovery.py
@@ -1,5 +1,8 @@
from datetime import datetime
-from typing import TYPE_CHECKING, Callable, List, Optional, Sequence, Tuple
+import hashlib
+import json
+from typing import TYPE_CHECKING, Callable, List, Optional, Sequence, Tuple, Any
+
from electrum.bitcoin import address_to_script
from electrum.plugin import BasePlugin
from electrum.transaction import PartialTxOutput, PartialTxInput, TxOutpoint
@@ -154,3 +157,14 @@ class TimelockRecoveryContext:
class TimelockRecoveryPlugin(BasePlugin):
def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name)
+
+ @classmethod
+ def json_checksum(cls, json_data: dict[str, Any]) -> str:
+ # Assumes the values have a consistent json representation (not a key-value
+ # object whose fields can be ordered in multiple ways).
+ return hashlib.sha256(json.dumps(
+ sorted(json_data.items()),
+ skipkeys=False, ensure_ascii=False, check_circular=True,
+ allow_nan=True, cls=None, indent=None, separators=(',', ':'),
+ default=None, sort_keys=False,
+ ).encode()).hexdigest()[:8]
diff --git a/tests/test_timelock_recovery.py b/tests/test_timelock_recovery.py
index 93b7b72..bde6381 100644
--- a/tests/test_timelock_recovery.py
+++ b/tests/test_timelock_recovery.py
@@ -1,15 +1,16 @@
from io import StringIO
-import json
-import os, sys
+import os
+import sys
+
from electrum.bitcoin import address_to_script
from electrum.fee_policy import FixedFeePolicy
-from electrum.plugins.timelock_recovery.timelock_recovery import TimelockRecoveryContext
from electrum.simple_config import SimpleConfig
from electrum.storage import WalletStorage
from electrum.transaction import PartialTxOutput
from electrum.wallet import Wallet
from electrum.wallet_db import WalletDB
-from electrum.plugins.timelock_recovery.qt import Plugin as TimelockRecoveryQtPlugin
+
+from electrum.plugins.timelock_recovery.timelock_recovery import TimelockRecoveryContext, TimelockRecoveryPlugin
from . import ElectrumTestCase
@@ -125,7 +126,7 @@ class TestTimelockRecovery(ElectrumTestCase):
# Non-ASCII characters must be serialized as-is (ensure_ascii=False),
# not escaped as \uXXXX sequences, before hashing.
json_data = {"wallet_name": "Ωmega Wörld Ñoño 日本語 中文 עברית العربية", "id": "abc-123"}
- result = TimelockRecoveryQtPlugin._checksum(json_data)
+ result = TimelockRecoveryPlugin.json_checksum(json_data)
self.assertEqual(result, "74674eca")
def test_checksum_bip_example(self):
@@ -167,5 +168,5 @@ class TestTimelockRecovery(ElectrumTestCase):
],
"metadata": "sig:825d6b3858c175c7fc16da3134030e095c4f9089c3c89722247eeedc08a7ef4f",
}
- result = TimelockRecoveryQtPlugin._checksum(json_data)
+ result = TimelockRecoveryPlugin.json_checksum(json_data)
self.assertEqual(result, "92f8b3da")
Why this scored 14/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.