# Rho Identity Model
The clean design is:
Every account gets a permanent opaque Rho ID and a Nostr-compatible controller
key.
Email, Google, Apple and GitHub are replaceable ways to locate and authenticate
into that Rho account.
The user then unlocks the controller key using either an encrypted-backup
passphrase or their self-managed key.
The provider login and cryptographic key solve different problems:
```text
Email / Google / Apple / GitHub
-> find and authenticate the Rho account
Passphrase / key file / signer
-> unlock and prove control of its cryptographic identity
```
## 1. Core Identity Model
Every user receives a permanent identifier:
```text
id/rho/01JY8Q7W6R4N2K...
```
Do not use the email, GitHub username or Nostr key as the permanent Rho ID.
Each account has:
```text
Rho identity
|-- permanent Rho ID
|-- current Nostr controller public key
|-- one or more private login methods
|-- zero or more public linked identities
|-- key-custody configuration
`-- public resource-resolution record
```
Example:
```json
{
"rho_id": "id/rho/01JY8Q7W6R4N2K",
"controller": {
"type": "nostr",
"pubkey": "73ab..."
},
"login_methods": [
{
"type": "email",
"value": "me@example.com",
"verified": true
},
{
"type": "github",
"issuer": "https://github.com",
"subject": "12345678"
}
],
"public_identities": [
"id/github/madhavajay"
],
"key_custody": {
"mode": "rho_encrypted_backup"
}
}
```
The Nostr key is the active cryptographic controller, while the Rho ID remains
permanent even if that key is eventually rotated.
## 2. Initial Account Creation
The first screen could be:
```text
Create your Rho identity
Continue with:
[ Email ]
[ Google ]
[ Apple ]
[ GitHub ]
or
[ Continue anonymously ]
```
You can add or remove sign-in methods later.
Microsoft can be added later, especially for university and institutional users.
### Email Signup
User enters an email.
Rho sends a short-lived OTP or magic link.
User verifies control of the mailbox.
Rho creates an opaque `id/rho/...`.
The client generates a Nostr-compatible keypair locally.
The user chooses a key-storage mode.
Rho stores the public account record and any permitted encrypted backup.
The client signs and publishes the initial Rho identity record.
An email OTP proves temporary access to the mailbox. It does not prove
employment, legal identity or institutional status.
### Google, Apple or GitHub Signup
User completes the provider's OAuth/OIDC flow.
Rho verifies the provider response server-side.
Rho identifies the external account using the provider's stable subject
identifier.
Rho creates the permanent `id/rho/...`.
The client generates the Nostr key.
The user selects key custody.
The initial identity record is signed and published.
For Google, store the stable `sub` identifier rather than using email as the
account's primary provider identifier; Google explicitly describes `sub` as
unique, stable and not reused.
Never auto-merge accounts merely because Google, GitHub or Apple reports the
same email address.
## 3. The Two Initial Key-Storage Choices
### Option A - Encrypted Backup With Rho
User-facing wording:
```text
Secure encrypted backup - recommended
Rho stores an encrypted copy of your identity key. Save a separate recovery
password in your password manager. Rho cannot use your identity key without that
password.
```
Process:
```text
generate Nostr private key locally
|
v
generate random Account Encryption Key
|
v
encrypt Nostr key with Account Encryption Key
|
v
derive wrapping key from recovery password
|
v
wrap Account Encryption Key
|
v
upload encrypted package to Rho
```
Rho stores ciphertext, salts, nonces and KDF parameters. It never needs the
passphrase or plaintext key.
The password should preferably be generated and stored by a password manager
rather than invented by the user.
On a new device:
```text
provider/email login
-> Rho finds account
-> downloads encrypted package
-> password manager fills recovery password
-> browser decrypts locally
-> browser verifies derived public key
-> identity unlocked
```
### Option B - Self-Managed Identity Key
User-facing wording:
```text
Manage my identity key myself - advanced
Download and securely store your identity key or recovery phrase. Rho cannot
recover it if every copy is lost.
```
The user receives one or more of:
- an encrypted Rho key backup file;
- a recovery phrase encoding the key;
- an advanced raw nsec export;
- connection to an external Nostr signer.
Rho stores only:
- the Rho ID;
- controller public key;
- linked login methods;
- public and private account metadata.
It stores no recoverable private-key blob.
On a new device:
```text
provider/email login
-> Rho finds account
-> asks user to import key file, enter recovery phrase or connect signer
-> verifies resulting public key
-> identity unlocked
```
## 4. Anonymous Account Creation
Anonymous users should receive the same type of permanent Rho ID:
```text
id/rho/01JY...
```
Do not use:
```text
id/anonymous/...
```
That would permanently classify the identity as anonymous even if the user later
links an email.
Anonymous signup:
```text
Continue anonymously
-> generate permanent Rho ID
-> generate Nostr key locally
-> choose encrypted backup or self-managed key
-> optionally choose a public alias
```
A truly anonymous account cannot use Rho-hosted encrypted backup unless Rho has
some private mechanism for locating the blob. Possible lookup mechanisms include:
- the Rho ID;
- an opaque recovery identifier;
- the Nostr public key;
- a locally stored account locator;
- a user-selected alias, with privacy caveats.
The simplest anonymous recovery package should therefore contain both:
```text
account locator
+
private key or decryption material
```
For example:
```json
{
"rho_id": "id/rho/01JY...",
"controller_pubkey": "73ab...",
"encrypted_key": "...",
"recovery_version": 1
}
```
Anonymous really means pseudonymous, not untraceable. IP addresses, timestamps,
uploaded resources and public-key activity can still correlate the user.
## 5. Initial Nostr/Rho Publication
After the key is available, the client creates a signed addressable event for
the Rho identity.
Conceptually:
```json
{
"kind": 30382,
"pubkey": "73ab...",
"created_at": 1781600000,
"tags": [
["d", "id/rho/01JY8Q7W6R4N2K"]
],
"content": {
"schema": "rho-identity-v1",
"rho_id": "id/rho/01JY8Q7W6R4N2K",
"revision": 1,
"public_identities": [],
"resources": {
"profile": "https://rho.biovault.net/id/01JY..."
}
}
}
```
Nostr addressable events are identified through the combination of event kind,
author public key and `d` tag, allowing a newer signed event to replace the
previous logical record.
Publish the same signed event to:
- Rho relay;
- partner relay A;
- partner relay B;
- optional public relays.
The client or Rho replication service tracks which relays accepted it.
## 6. Normal Login on a Known Device
A known device may keep the key locally encrypted using platform storage or
maintain an authorised local session.
Example:
```text
Click Continue with Google
-> Google authenticates user
-> Rho locates account
-> device already has local key
-> client signs a fresh challenge
-> Rho verifies the signature
-> full session begins
```
The user does not need to enter the recovery password every time.
The login provider identifies the account; the local key proves cryptographic
control.
For ordinary browsing, you could allow a limited session after provider
authentication alone. Require key unlock for:
- signing identity updates;
- decrypting private content;
- granting access;
- exporting or rotating keys;
- other sensitive operations.
## 7. Login on a New Device
### Encrypted-Backup User
1. Click email, Google, Apple or GitHub.
2. Provider authentication succeeds.
3. Rho locates `id/rho/...`.
4. Rho sends encrypted key package.
5. User retrieves recovery password from password manager.
6. Browser derives the decryption key.
7. Browser decrypts the Nostr key locally.
8. Browser derives and checks the public key.
9. Browser signs a one-time Rho challenge.
10. Rho verifies it and creates a session.
### Self-Managed User
1. Authenticate with linked provider.
2. Rho locates account.
3. Import key file, enter recovery phrase or connect signer.
4. Client derives the public key.
5. Confirm it equals the active account controller.
6. Sign challenge.
7. Start session.
A browser signer can expose the Nostr public key and sign events without
revealing the private key to the webpage through the NIP-07 interface.
## 8. Account Found But Key Unavailable
This must be an explicit UI state:
```text
Account found
Identity key locked
```
The user may be authenticated to Google or email, but cannot yet prove
cryptographic control.
Permit safe actions such as:
- viewing recovery instructions;
- downloading their encrypted key package;
- trying another linked login;
- connecting another previously authorised signer;
- starting an approved recovery flow.
Do not permit:
- changing the active controller;
- publishing signed identity updates;
- decrypting user-controlled encrypted resources;
- exporting data protected by the identity key;
- removing the final recovery method.
## 9. Linking Another Login Method
Example: the user initially signed up with email and wants to add GitHub.
```text
Sign in and unlock identity
-> Settings
-> Login and linked identities
-> Add GitHub
-> complete GitHub OAuth
-> Rho verifies GitHub identity
-> user confirms whether it is:
a private login method
a public linked identity
or both
```
Require recent strong authentication before linking:
- an unlocked Nostr key and signed challenge;
- or a recent passkey;
- possibly provider reauthentication for high-risk changes.
Store the GitHub provider's immutable account ID privately. The public identifier
may remain:
```text
id/github/madhavajay
```
The user then signs a new Rho identity event containing the public binding.
## 10. Private Login Methods Versus Public Identities
This distinction is essential.
A user may have:
```text
me@example.com
```
enabled for login but not publicly visible.
Store privately:
```json
{
"type": "email",
"normalized_email": "me@example.com",
"login_enabled": true,
"publicly_discoverable": false
}
```
Only explicitly published identities should appear in public lookup:
```json
{
"public_identities": [
{
"id": "id/github/madhavajay",
"verification": "rho-attested"
}
]
}
```
Google and Apple are usually best kept as private login methods.
GitHub, ORCID, domain names and public handles are more useful as public lookup
identities.
## 11. Updating the Public Record
When an identity is linked, unlinked or a resource changes:
1. Fetch the latest local identity record.
2. Increment its revision.
3. Update the desired fields.
4. Set a newer `created_at`.
5. Sign it with the active controller.
6. Publish the exact signed event to each configured relay.
7. Record relay acknowledgements.
8. Retry unavailable relays later.
Example update:
```json
{
"rho_id": "id/rho/01JY...",
"revision": 4,
"public_identities": [
"id/github/madhavajay",
"id/orcid/0000-0002-..."
],
"resources": {
"profile": "https://rho.biovault.net/id/01JY...",
"manifest": "https://example.org/rho.json"
}
}
```
The older signed event may still exist on some relays, but resolvers select the
newest valid logical record.
## 12. Public Lookup
Someone searches for:
```text
id/github/madhavajay
```
The Rho resolver returns:
```text
id/github/madhavajay
|
v verified binding
id/rho/01JY...
|
v active controller
nostr:npub1...
```
Example result:
```json
{
"matched_identity": "id/github/madhavajay",
"rho_id": "id/rho/01JY...",
"controller_pubkey": "73ab...",
"verification": {
"status": "verified",
"method": "github_oauth",
"issuer": "rho.biovault.net"
},
"relays": [
"wss://relay.rho.biovault.net",
"wss://relay.partner.example"
]
}
```
The user can give people any public linked identifier. They never need to know
or share their npub unless they want to.
## 13. Unlinking Identities
Before unlinking, check that the user will retain:
- at least one login method, or
- a clearly acknowledged Nostr-only/self-managed path.
Example:
```text
Remove Google login?
You will still be able to sign in with:
- me@example.com
- GitHub
- self-managed identity key
```
If the identity was public, publish a new signed record without that binding.
Do not delete historical Nostr events under the assumption that all relays will
honour deletion. Publish the new current state and, where useful, a revocation
statement.
## 14. Switching From Encrypted Rho Backup to Self-Managed
User authenticates and unlocks the key.
User downloads an encrypted backup or raw advanced export.
Rho asks them to prove restoration, for example by signing a challenge from the
exported package.
User requests removal of Rho-hosted ciphertext.
Rho deletes it from active storage.
Account custody mode changes to `self_managed`.
Be honest about deletion:
The encrypted package is deleted from active storage but may remain temporarily
in backups according to the published retention policy.
Because Rho only had ciphertext, temporary backup retention is less dangerous
than retaining plaintext keys.
## 15. Switching From Self-Managed to Encrypted Rho Backup
User unlocks or imports the key locally.
Client verifies it matches the account's active public key.
Client generates a random account-encryption key.
Client encrypts the private key locally.
Client wraps the account key with a recovery-password-derived key.
Client uploads only ciphertext.
Rho changes custody mode to `rho_encrypted_backup`.
No controller rotation is necessary.
## 16. Forgotten Recovery Password
### User Still Has the Key on Another Device
Authenticate using a linked provider.
Use the existing device/key to prove controller possession.
Generate a new recovery password.
Rewrap the account-encryption key.
Upload the replacement package.
### User Has a Self-Managed Backup
Import it, prove key possession and create a fresh encrypted backup.
### User Has Neither Key Nor Password
Provider authentication can locate the account but cannot decrypt the controller
key.
Possible outcomes:
- use another previously authorised signer;
- use an additional recovery wrapper;
- use an explicitly enabled assisted-recovery mechanism;
- rotate to a new controller after sufficient account reverification;
- otherwise, cryptographic control is permanently lost.
The product must never imply that email alone can decrypt a non-custodial
backup.
## 17. Lost Email or Provider Account
If the user retains the identity key, this is straightforward:
```text
unlock identity key
-> sign into Rho using another linked method or Nostr challenge
-> link replacement email/provider
-> remove lost provider
```
That is one of the key decentralisation benefits: losing Google or an
institutional email does not necessarily mean losing the Rho identity.
If the user has only one provider login and an encrypted key package but cannot
access that provider, you need an account-location recovery process. Possible
recovery inputs include:
- permanent Rho ID;
- Nostr public key;
- previously downloaded recovery package;
- another linked public identity;
- support-assisted proof, where policy permits.
## 18. Lost Private Key With Provider Access Retained
This is different from losing the passphrase.
If the key itself is truly gone and no recoverable encrypted copy remains, the
old Nostr controller cannot be recovered.
Rho may allow controller rotation:
```text
old controller: npub-old
new controller: npub-new
```
Recovery policy could require two or more of:
- verified email access;
- Google/Apple/GitHub provider access;
- existing passkey;
- recovery code;
- institutional SSO;
- support review;
- waiting period;
- notifications to all linked methods.
Rho then publishes an updated controller record and new attestations.
However, generic Nostr software may regard the new public key as a different
identity. The permanent Rho ID preserves continuity inside the Rho system.
## 19. Suspected or Confirmed Key Compromise
A compromised Nostr private key is more serious than a forgotten recovery
password.
Process:
1. Freeze sensitive account operations.
2. Authenticate through independent linked methods.
3. Generate a new controller key.
4. Publish an old-key-to-new-key transition if the old key is still accessible.
5. Publish a new-key acceptance.
6. Update Rho's authoritative controller mapping.
7. Reissue relevant attestations.
8. Re-encrypt future resources to the new key.
9. Revoke delegated device keys.
10. Warn that past encrypted material may remain compromised.
If the attacker controls the old key, timestamp alone may not settle conflicting
updates. The Rho recovery policy and trusted attestations become the tie-breaker.
## 20. Multiple Accounts Accidentally Created
Example:
```text
Account A created with email
Account B created later with GitHub
```
Do not automatically merge them based on matching emails.
Offer an explicit merge process requiring control of both accounts:
```text
unlock controller A
+
unlock controller B
+
authenticate linked methods
-> choose surviving Rho ID
-> move permitted bindings
-> publish migration records
-> retire secondary Rho identity
```
If the user cannot prove control of both, do not merge.
## 21. Provider Identity Already Linked Elsewhere
If someone attempts to attach a Google or GitHub subject already linked to
another Rho account:
```text
This sign-in method is already associated with another Rho identity.
```
Then offer:
- sign into that account;
- cancel;
- start an account-recovery process;
- merge only after proving control of both accounts.
Never silently move the provider identity.
## 22. Email Address Changes or Is Recycled
Use the verified email primarily as a login credential, not the permanent
account ID.
If an institutional mailbox is later assigned to another person, the new holder
must not automatically inherit the old Rho identity.
Mitigations:
- require the cryptographic key for sensitive changes;
- periodically reverify institutional email bindings;
- distinguish historical verification from current verification;
- expire public institutional attestations;
- require stronger recovery than email OTP alone.
## 23. What Rho Needs to Store
### Account Database
- `rho_id`
- account status
- `created_at`
- current controller pubkey
- controller history
- key custody mode
- security/recovery policy
- account version
### Login Bindings
For each method:
- binding ID
- `rho_id`
- provider type
- provider issuer
- provider subject
- normalized email where applicable
- verification timestamp
- login enabled
- recovery enabled
- publicly discoverable
- last used
- revoked timestamp
Provider issuer + subject should be the uniqueness key where available.
### Public Identity Bindings
- `rho_id`
- public identifier
- verification method
- verification issuer
- verification status
- `verified_at`
- `expires_at`
- `revoked_at`
- corresponding Nostr event ID
### Encrypted Key Package, Only for Hosted-Backup Users
- format version
- encrypted private-key payload
- wrapped account-encryption key
- KDF algorithm and parameters
- salt
- cipher algorithm
- nonce
- associated-data version
- `created_at`
- `superseded_at`
No plaintext key and no recovery password.
### WebAuthn/Passkeys, if Added
Store:
- credential ID
- credential public key
- opaque user handle
- credential metadata
- `created_at`
- last used
- `revoked_at`
WebAuthn credentials are created and retained by an authenticator and are scoped
to the relying party's origins.
### Relay State
- event ID
- logical record address
- revision
- relay URL
- publish status
- acknowledgement
- last checked
- retry count
### Security Records
- login audit log
- link/unlink events
- controller rotation events
- recovery attempts
- rate-limit state
- security notifications
Avoid placing sensitive email addresses, provider subjects or account-recovery
data into public Nostr events.
## 24. What the Browser or Client Temporarily Handles
Depending on custody mode:
- plaintext Nostr key while unlocked;
- recovery password;
- account-encryption key;
- decrypted private resources;
- signing requests.
Minimise how long plaintext keys remain in memory. Clear them on logout, timeout
or tab closure where practical.
A dedicated signer is safer because the webpage receives signatures rather than
the private key. NIP-07 standardises a browser-provided `window.nostr` signing
interface.
## 25. Session States
Use explicit states rather than treating login as binary.
### UNAUTHENTICATED
### ACCOUNT_AUTHENTICATED
Provider/email verified; account located.
### IDENTITY_UNLOCKED
Controller key available and challenge signed.
### ELEVATED
Recent strong authentication for high-risk action.
Suggested permissions:
| Unauthenticated | Public lookup |
| Account authenticated | Account recovery UI, encrypted-package download, basic settings |
| Identity unlocked | Sign, decrypt, link identities, update public records |
| Elevated | Key export, controller rotation, deleting final recovery method |
## 26. Recommended First-Version UX
### Signup
```text
Create your Rho identity
[ Email ]
[ Google ]
[ Apple ]
[ GitHub ]
[ Anonymous ]
Then:
How should your identity key be protected?
Recommended:
[ Store an encrypted backup with Rho ]
Advanced:
[ I will manage my key myself ]
You can switch later.
```
### Login
```text
Sign in
[ Email ]
[ Google ]
[ Apple ]
[ GitHub ]
[ Use identity key ]
After account authentication:
Account found
[ Unlock using recovery password ]
[ Import my key ]
[ Connect signer ]
```
On known devices, unlocking can happen automatically through locally protected
key storage.
## 27. Important Wording Corrections
Avoid:
```text
Rho holds your key.
```
Use:
```text
Rho stores an encrypted backup of your key.
```
Avoid:
```text
Anonymous account cannot be identified.
```
Use:
```text
Pseudonymous account with no linked external identity.
```
Avoid:
```text
Email recovers your identity.
```
Use:
```text
Email helps locate and authenticate your Rho account. Your recovery password,
key backup or approved recovery process restores cryptographic control.
```
## Recommended Launch Scope
For the first implementation, support:
- email OTP;
- Google;
- Apple;
- GitHub;
- anonymous signup;
- opaque permanent `id/rho/...`;
- one Nostr-compatible active controller;
- Rho-hosted client-encrypted backup;
- self-managed key export/import;
- public/private distinction for linked identities;
- signed addressable Rho identity records;
- publication to the Rho relay plus configured replicas;
- explicit account-authenticated versus identity-unlocked session states;
- controller rotation through a carefully defined recovery policy.
Passkeys, external Nostr signers, institutional SSO, multiple delegated keys and
threshold recovery can then be added without changing the underlying identity
model.