mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-07 14:58:56 +00:00
Merge pull request #780 from PR0M3TH3AN/codex/cache-bip-85-results-in-manager.py
Cache BIP85 derivations and skip unchanged snapshot chunks
This commit is contained in:
@@ -3,7 +3,7 @@ addopts = -n auto
|
||||
log_cli = true
|
||||
log_cli_level = WARNING
|
||||
log_level = WARNING
|
||||
testpaths = src/tests
|
||||
testpaths = src/tests tests
|
||||
markers =
|
||||
network: tests that require network connectivity
|
||||
stress: long running stress tests
|
||||
|
@@ -59,7 +59,18 @@ class SnapshotHandler:
|
||||
await self.ensure_manifest_is_current()
|
||||
await self._connect_async()
|
||||
manifest, chunks = prepare_snapshot(encrypted_bytes, limit)
|
||||
|
||||
existing: dict[str, str] = {}
|
||||
if self.current_manifest:
|
||||
for old in self.current_manifest.chunks:
|
||||
if old.hash and old.event_id:
|
||||
existing[old.hash] = old.event_id
|
||||
|
||||
for meta, chunk in zip(manifest.chunks, chunks):
|
||||
cached_id = existing.get(meta.hash)
|
||||
if cached_id:
|
||||
meta.event_id = cached_id
|
||||
continue
|
||||
content = base64.b64encode(chunk).decode("utf-8")
|
||||
builder = nostr_client.EventBuilder(
|
||||
nostr_client.Kind(KIND_SNAPSHOT_CHUNK), content
|
||||
|
@@ -180,6 +180,7 @@ class PasswordManager:
|
||||
self.error_queue: queue.Queue[Exception] = queue.Queue()
|
||||
self._current_notification: Optional[Notification] = None
|
||||
self._notification_expiry: float = 0.0
|
||||
self._bip85_cache: dict[tuple[int, int], bytes] = {}
|
||||
|
||||
# Track changes to trigger periodic Nostr sync
|
||||
self.is_dirty: bool = False
|
||||
@@ -212,6 +213,20 @@ class PasswordManager:
|
||||
self.fingerprint_manager.get_current_fingerprint_dir()
|
||||
)
|
||||
|
||||
def get_bip85_entropy(self, purpose: int, index: int, bytes_len: int = 32) -> bytes:
|
||||
"""Return deterministic entropy via the cached BIP-85 function."""
|
||||
|
||||
if self.bip85 is None:
|
||||
raise RuntimeError("BIP-85 is not initialized")
|
||||
return self.bip85.derive_entropy(
|
||||
index=index, bytes_len=bytes_len, app_no=purpose
|
||||
)
|
||||
|
||||
def clear_bip85_cache(self) -> None:
|
||||
"""Clear the internal BIP-85 cache."""
|
||||
|
||||
self._bip85_cache.clear()
|
||||
|
||||
def ensure_script_checksum(self) -> None:
|
||||
"""Initialize or verify the checksum of the manager script."""
|
||||
script_path = Path(__file__).resolve()
|
||||
@@ -1173,7 +1188,20 @@ class PasswordManager:
|
||||
try:
|
||||
seed_bytes = Bip39SeedGenerator(self.parent_seed).Generate()
|
||||
self.bip85 = BIP85(seed_bytes)
|
||||
self._bip85_cache = {}
|
||||
orig_derive = self.bip85.derive_entropy
|
||||
|
||||
def cached_derive(index: int, bytes_len: int, app_no: int = 39) -> bytes:
|
||||
key = (app_no, index)
|
||||
if key not in self._bip85_cache:
|
||||
self._bip85_cache[key] = orig_derive(
|
||||
index=index, bytes_len=bytes_len, app_no=app_no
|
||||
)
|
||||
return self._bip85_cache[key]
|
||||
|
||||
self.bip85.derive_entropy = cached_derive
|
||||
logging.debug("BIP-85 initialized successfully.")
|
||||
self.clear_bip85_cache()
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to initialize BIP-85: {e}", exc_info=True)
|
||||
print(colored(f"Error: Failed to initialize BIP-85: {e}", "red"))
|
||||
|
50
tests/perf/test_bip85_cache.py
Normal file
50
tests/perf/test_bip85_cache.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import time
|
||||
|
||||
from seedpass.core.manager import PasswordManager
|
||||
|
||||
|
||||
class SlowBIP85:
|
||||
"""BIP85 stub that simulates a costly derive."""
|
||||
|
||||
def __init__(self):
|
||||
self.calls = 0
|
||||
|
||||
def derive_entropy(self, index: int, bytes_len: int, app_no: int = 39) -> bytes:
|
||||
self.calls += 1
|
||||
time.sleep(0.01)
|
||||
return b"\x00" * bytes_len
|
||||
|
||||
|
||||
def _setup_manager(bip85: SlowBIP85) -> PasswordManager:
|
||||
pm = PasswordManager.__new__(PasswordManager)
|
||||
pm._bip85_cache = {}
|
||||
pm.bip85 = bip85
|
||||
orig = bip85.derive_entropy
|
||||
|
||||
def cached(index: int, bytes_len: int, app_no: int = 39) -> bytes:
|
||||
key = (app_no, index)
|
||||
if key not in pm._bip85_cache:
|
||||
pm._bip85_cache[key] = orig(index=index, bytes_len=bytes_len, app_no=app_no)
|
||||
return pm._bip85_cache[key]
|
||||
|
||||
bip85.derive_entropy = cached
|
||||
return pm
|
||||
|
||||
|
||||
def test_bip85_cache_benchmark():
|
||||
slow_uncached = SlowBIP85()
|
||||
start = time.perf_counter()
|
||||
for _ in range(3):
|
||||
slow_uncached.derive_entropy(1, 32, 32)
|
||||
uncached_time = time.perf_counter() - start
|
||||
|
||||
slow_cached = SlowBIP85()
|
||||
pm = _setup_manager(slow_cached)
|
||||
start = time.perf_counter()
|
||||
for _ in range(3):
|
||||
pm.get_bip85_entropy(32, 1)
|
||||
cached_time = time.perf_counter() - start
|
||||
|
||||
assert cached_time < uncached_time
|
||||
assert slow_uncached.calls == 3
|
||||
assert slow_cached.calls == 1
|
Reference in New Issue
Block a user