Skip to content

NIP-46 pre-grants: hidden-volume relay-config storage #513

Description

@kwsantiago

Follow-up to #507 (PR #514, outer-volume scope shipped).

#507 added store_relay_config / get_relay_config / get_relay_config_or_default to HiddenStorage for the outer volume only. On a hidden-active session the current shipped behavior is:

  • get_relay_config returns Ok(None).
  • get_relay_config_or_default returns a fresh default.
  • store_relay_config errors with KeepError::NotImplemented.

Decision: Option (2) — hidden-active serve is interactive-only

We will not persist pre-grants on the hidden volume. keep nip46 grant (and the other grant-mutating subcommands) are rejected outright on a hidden-active session, and the only supported hidden-volume serve mode is interactive approval. Rationale:

  1. Blast radius vs. benefit. The hidden volume is a single flat encrypted blob ([encrypted length][encrypted bincode Vec][random padding]), not a redb database. Persisting relay configs there means changing the serialized payload to a { keys, relay_configs } struct. load_hidden_records swallows deserialize errors and returns Ok(Vec::new()), so a naive format change would make an existing hidden vault silently appear to have lost every key. Safe implementation requires a versioned blob format + migration on the most unrecoverable data we hold. Not worth it for a non-blocking use case.
  2. The real use case is already shipped. NIP-46 pre-grant CLI also works on hidden-vault serve paths #507's model is "outer-vault bunker with hidden vault available for real keys" — the bunker runs on the outer volume; the hidden vault holds real keys for interactive use. Nobody is blocked by the lack of hidden-active pre-grants.
  3. Tension with the deniability goal. Pre-grants exist to enable unattended/headless operation. A long-lived headless daemon bound to the hidden identity (hidden password resident in memory, hidden-identity relay traffic) is itself a behavioral side channel that undermines plausible deniability. Option (1) would encourage the one usage pattern that most weakens the threat model.

When this decision should be revisited

If a concrete product requirement emerges for an unattended headless bunker running directly on the hidden volume (not the outer one). At that point implement option (1) with an explicit blob format-version byte and a migration path — never a bare struct swap.

Scope (option 2)

  • Reject keep nip46 grant / revoke / auto-approve on a hidden-active session with a clear, intentional error message (not a generic failure). Current KeepError::NotImplemented on store_relay_config is the enforcement point; surface a user-facing message that says hidden-volume grants are unsupported and that hidden-volume serve is interactive-only.
  • get_relay_configOk(None) and get_relay_config_or_default → default on hidden-active sessions remain the contract (so cmd_serve_hidden degrades to interactive cleanly with no outer leak).
  • Document the supported hidden-volume serve mode (interactive only) in the relevant CLI/serve docs or help text.

Acceptance

  • Decision recorded (this issue): option (2).
  • Hidden-active grant/revoke/auto-approve produce a clear "hidden-volume grants unsupported; serve is interactive-only" message rather than a generic error.
  • Test: hidden-active store_relay_config is refused and nothing lands in the outer volume blob (already covered by relay_config_returns_none_on_hidden_volume; extend with the CLI-surfaced message if a CLI seam exists).
  • Doc note covering the interactive-only constraint.

(Option (1)'s hidden-blob on-disk layout is explicitly out of scope under this decision; it is the "revisit" path above.)

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestmediumnostr-frostNostr FROST coordination protocolp3Lowest PrioritysecuritySecurity-related issues

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions