Merge pull request #700 from PR0M3TH3AN/codex/extend-handle_new_seed_setup-for-nostr-restore

Add Nostr restore option
This commit is contained in:
thePR0M3TH3AN
2025-07-31 14:25:27 -04:00
committed by GitHub
3 changed files with 98 additions and 2 deletions

View File

@@ -32,6 +32,7 @@ SeedPass now uses the `portalocker` library for cross-platform file locking. No
- [Running the Application](#running-the-application)
- [Managing Multiple Seeds](#managing-multiple-seeds)
- [Additional Entry Types](#additional-entry-types)
- [Recovery](#recovery)
- [Building a standalone executable](#building-a-standalone-executable)
- [Packaging with Briefcase](#packaging-with-briefcase)
- [Security Considerations](#security-considerations)
@@ -541,6 +542,18 @@ seedpass config set nostr_retry_delay 1
The default configuration uses **50,000** PBKDF2 iterations. Increase this value for stronger password hashing or lower it for faster startup (not recommended). Offline Mode skips all Nostr communication, keeping your data local until you re-enable syncing. Quick Unlock stores a hashed copy of your password in the encrypted config so that after the initial unlock, subsequent operations won't prompt for the password until you exit the program. Avoid enabling Quick Unlock on shared machines.
### Recovery
If you previously backed up your vault to Nostr you can restore it during the
initial setup:
1. Start SeedPass and choose option **4** when prompted to set up a seed.
2. Paste your BIP-85 seed phrase when asked.
3. SeedPass initializes the profile and attempts to download the encrypted vault
from the configured relays.
4. A success message confirms the vault was restored. If no data is found a
failure message is shown and a new empty vault is created.
## Running Tests
SeedPass includes a small suite of unit tests located under `src/tests`. **Before running `pytest`, be sure to install the test requirements.** Activate your virtual environment and run `pip install -r src/requirements.txt` to ensure all testing dependencies are available. Then run the tests with **pytest**. Use `-vv` to see INFO-level log messages from each passing test:

View File

@@ -808,8 +808,8 @@ class PasswordManager:
choice = input(
"Do you want to (1) Paste in an existing seed in full "
"(2) Enter an existing seed one word at a time or "
"(3) Generate a new seed? (1/2/3): "
"(2) Enter an existing seed one word at a time, "
"(3) Generate a new seed, or (4) Restore from Nostr? (1/2/3/4): "
).strip()
if choice == "1":
@@ -818,6 +818,15 @@ class PasswordManager:
self.setup_existing_seed(method="words")
elif choice == "3":
self.generate_new_seed()
elif choice == "4":
fp = self.setup_existing_seed()
if fp:
success = self.attempt_initial_sync()
if success:
print(colored("Vault restored from Nostr.", "green"))
else:
print(colored("Failed to download vault from Nostr.", "red"))
return
else:
print(colored("Invalid choice. Exiting.", "red"))
sys.exit(1)

View File

@@ -0,0 +1,74 @@
from pathlib import Path
from helpers import create_vault, dummy_nostr_client, TEST_SEED, TEST_PASSWORD
from seedpass.core.entry_management import EntryManager
from seedpass.core.backup import BackupManager
from seedpass.core.config_manager import ConfigManager
from seedpass.core.manager import PasswordManager, EncryptionMode
def _init_pm(dir_path: Path, client) -> PasswordManager:
vault, enc_mgr = create_vault(dir_path, TEST_SEED, TEST_PASSWORD)
cfg_mgr = ConfigManager(vault, dir_path)
backup_mgr = BackupManager(dir_path, cfg_mgr)
entry_mgr = EntryManager(vault, backup_mgr)
pm = PasswordManager.__new__(PasswordManager)
pm.encryption_mode = EncryptionMode.SEED_ONLY
pm.encryption_manager = enc_mgr
pm.vault = vault
pm.entry_manager = entry_mgr
pm.backup_manager = backup_mgr
pm.config_manager = cfg_mgr
pm.nostr_client = client
pm.fingerprint_dir = dir_path
pm.current_fingerprint = "fp"
pm.is_dirty = False
return pm
def test_handle_new_seed_setup_restore_from_nostr(monkeypatch, tmp_path, capsys):
client, _relay = dummy_nostr_client.__wrapped__(tmp_path / "srv", monkeypatch)
dir_a = tmp_path / "A"
dir_b = tmp_path / "B"
dir_a.mkdir()
dir_b.mkdir()
pm_src = _init_pm(dir_a, client)
pm_src.notify = lambda *a, **k: None
pm_src.entry_manager.add_entry("site1", 12)
pm_src.sync_vault()
pm_new = PasswordManager.__new__(PasswordManager)
pm_new.encryption_mode = EncryptionMode.SEED_ONLY
pm_new.nostr_client = client
pm_new.notify = lambda *a, **k: None
def finalize(seed, *, password=None):
vault, enc_mgr = create_vault(dir_b, seed, TEST_PASSWORD)
cfg_mgr = ConfigManager(vault, dir_b)
backup_mgr = BackupManager(dir_b, cfg_mgr)
entry_mgr = EntryManager(vault, backup_mgr)
pm_new.encryption_manager = enc_mgr
pm_new.vault = vault
pm_new.entry_manager = entry_mgr
pm_new.backup_manager = backup_mgr
pm_new.config_manager = cfg_mgr
pm_new.fingerprint_dir = dir_b
pm_new.current_fingerprint = "fp"
pm_new.nostr_client = client
return "fp"
monkeypatch.setattr(pm_new, "_finalize_existing_seed", finalize)
monkeypatch.setattr("seedpass.core.manager.masked_input", lambda *_: TEST_SEED)
inputs = iter(["4"])
monkeypatch.setattr("builtins.input", lambda *a, **k: next(inputs))
pm_new.handle_new_seed_setup()
out = capsys.readouterr().out
assert "Vault restored from Nostr" in out
labels = [e[1] for e in pm_new.entry_manager.list_entries()]
assert labels == ["site1"]