Key Management and Identity
June 19, 2026 · View on GitHub
Mostrix strictly follows the Mostro protocol's key management specifications to ensure privacy, security, and deterministic recoverability of user accounts and trades.
Deterministic Derivation (NIP-06)
Mostrix uses BIP-39 mnemonics and NIP-06 for deterministic key derivation. In normal user mode, the 12-word mnemonic is generated and stored in the SQLite users table on first startup, and settings.toml derives nsec_privkey from the corresponding identity key (index 0) so DB and settings keys stay in sync. Admin keys are configured separately via admin_privkey and can be rotated from the Settings tab.
Derivation Path
The project uses the standard Mostro derivation path:
m/44'/1237'/38383'/0/X
Where X is the index:
X = 0: Identity Key.X >= 1: Trade Keys.
Key Rotation and Backup Prompts
The Settings tab includes a Generate New Keys option (User mode or Admin mode, depending on the current role).
- User mode: Mostrix generates a new 12-word mnemonic, deletes/recreates the DB user identity, and updates
settings.toml’snsec_privkey. - Admin mode: Mostrix rotates the admin keypair and updates
settings.toml’sadmin_privkey.
In both cases Mostrix shows a backup popup displaying the newly generated 12 words. The mnemonic must be saved, and Mostrix should be restarted after saving so all derived keys and in-memory state use the new values.
On very first launch, when Mostrix must bootstrap a brand-new settings.toml, it also shows the backup popup as an overlay on the initial Orders/Disputes tab (it does not force switching to the Settings tab).
Identity Key (Index 0)
The Identity Key is the user's long-term Nostr identity. It is used for:
- Building reputation across the Mostro network.
- Signing the Seal (kind 13) in NIP-59 Gift Wrap events in "Normal Mode".
- Acting as the primary point of contact for the Mostro daemon for rating updates.
Trade Keys (Index 1+)
To maximize privacy, Mostrix derives a fresh ephemeral trade key for every new order or taken trade.
- Role: Signs the Rumor (kind 1) inside the NIP-59 Gift Wrap.
- Privacy: Ensures that trades are not easily linkable to the user's primary identity by external observers.
Admin Shared Keys for Disputes
In admin mode, Mostrix also uses per‑dispute shared keys for the dispute chat system. These are not derived directly from the mnemonic path above, but from an ECDH operation between the admin identity key and each party’s trade pubkey.
-
Derivation:
- When an admin takes a dispute, the client derives two shared secrets using:
- The admin secret key (
admin_privkeyfromsettings.toml), and - The buyer’s trade pubkey / seller’s trade pubkey from the dispute.
- The admin secret key (
- ECDH is performed via
nostr_sdk::util::generate_shared_key, and the resulting bytes are wrapped into aKeysinstance. - These keys are persisted as hex‑encoded secrets in the
admin_disputestable as:buyer_shared_key_hexseller_shared_key_hex
- When an admin takes a dispute, the client derives two shared secrets using:
-
Usage:
- The shared keys act as per‑(dispute, party) chat identities:
- Outgoing admin chat messages are sent as NIP‑59
GiftWrapevents addressed to the shared key’s public key. - Incoming messages are fetched by querying
Kind::GiftWrapevents to that same shared key pubkey and decrypting with the shared secret.
- Outgoing admin chat messages are sent as NIP‑59
- Both admin and counterparty can independently derive the same shared key, mirroring the
mostro-chatmodel. - Per‑party last‑seen timestamps (
buyer_chat_last_seen,seller_chat_last_seen) are used together with these keys to implement incremental, restart‑safe admin chat sync.
- The shared keys act as per‑(dispute, party) chat identities:
-
Validation: When saving a new dispute, if buyer and seller pubkeys differ but the two derived shared keys are identical, the client logs an error (
Shared keys for dispute … are identical for different buyer/seller pubkeys; chat may be broken). This guards against bad relay data or parsing issues. A unit test insrc/util/chat_utils.rsasserts that different counterparty pubkeys yield different shared keys.
NIP-59 Gift Wrap Structure (protocol v1)
Mostrix implements NIP-59 for protocol v1 and all P2P chat. Protocol v2 Mostro DMs use signed kind 14 via wrap_message_with — identity proof moves inside the NIP-44 ciphertext (see MESSAGE_FLOW_AND_PROTOCOL.md).
1. Normal Mode (Reputation Enabled)
In this mode, Mostro can link the trade to your identity key for reputation purposes, but other Nostr users cannot.
- Wrap (Kind 1059): Signed by a random ephemeral key.
- Seal (Kind 13): Signed by the Identity Key (Index 0).
- Rumor (Kind 1): Signed by the Trade Key (Index N).
2. Full Privacy Mode
In this mode, Mostro cannot link the trade to your identity key. You operate anonymously without reputation.
- Wrap (Kind 1059): Signed by a random ephemeral key.
- Seal (Kind 13): Signed by the Trade Key (Index N).
- Rumor (Kind 1): Signed by the Trade Key (Index N).
Trade Index Incrementation
Whenever a user creates or takes an order, the last_trade_index is incremented and stored in the database.
Implementation: src/util/order_utils/take_order.rs:66
let next_idx = user.last_trade_index.unwrap_or(1) + 1;
let trade_keys = user.derive_trade_keys(next_idx)?;
let _ = User::update_last_trade_index(pool, next_idx).await;
Database Persistence
Derivation Logic
The derivation logic for trade keys uses the trade_index as the child index in the derivation path.
Implementation: src/models.rs:86
pub fn derive_trade_keys(&self, trade_index: i64) -> Result<Keys> {
let account: u32 = NOSTR_ORDER_EVENT_KIND as u32;
let keys = Keys::from_mnemonic_advanced(
&self.mnemonic,
None,
Some(account),
Some(trade_index as u32),
Some(0),
)?;
Ok(keys)
}
Database Persistence
Maintaining the state of trade indices is critical. If the trade_index associated with an order is lost, the client will be unable to decrypt messages from Mostro or the counterparty for that specific trade.
The users Table
The local SQLite database stores the mnemonic and the latest index used. On first run, settings.toml’s nsec_privkey is derived from the DB identity key (index 0) corresponding to this mnemonic, so user identity remains deterministic across restarts.
Source: src/db.rs:55
CREATE TABLE IF NOT EXISTS users (
i0_pubkey char(64) PRIMARY KEY,
mnemonic TEXT,
last_trade_index INTEGER,
created_at INTEGER
);
The orders Table
Each order entry also stores the specific trade_keys (or the index) used, allowing the client to re-derive the correct key during startup synchronization or when receiving DMs.
Stateless Recovery Strategy
Mostrix avoids storing full message histories locally. Instead, it uses the deterministic nature of the keys:
- On startup, the client retrieves all active order IDs and their associated
trade_indexfrom the database. - It re-derives the corresponding
Trade Keys. - It queries Nostr relays for recent
GiftWrapevents (NIP-59) directed to those specific trade public keys. - This allows the client to reconstruct the current state of any active trade without needing a heavy local message database.