diff --git a/electrum/plugins/timelock_recovery/qt.py b/electrum/plugins/timelock_recovery/qt.py index 7b96a9a64..2d17a5eeb 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 27cb67947..9d42cf44f 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 93b7b72b6..bde63816a 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")