Multi-Device Identity (Planned)#
Current MVP: Device-to-device pairing transfers the existing identity via the PDS as a relay. See pairing.md for the implemented protocol. Seed phrase derivation (below) is a future replacement that eliminates the need for an existing device.
Deterministic keypair derivation from a BIP-39 mnemonic. Same seed on any device produces the same X25519 keypair — no key sync protocol needed. Replaces the current plaintext keypair file at ~/.config/opake/accounts/<did>/identity.json.
Keypair Derivation#
flowchart TB
subgraph Generate ["First-time setup (opake init or first login)"]
direction TB
Entropy["128 bits of entropy<br/>(from OS CSPRNG)"] --> Mnemonic
Mnemonic["BIP-39 mnemonic<br/>(12 words)"]
end
subgraph Derive ["Keypair derivation (every login)"]
direction TB
Mnemonic --> PBKDF["BIP-39 seed derivation<br/>PBKDF2-HMAC-SHA512<br/>2048 rounds, salt = 'mnemonic'"]
PBKDF --> Seed["512-bit master seed"]
Seed --> HKDF["HKDF-SHA256<br/>info = 'opake-v1-x25519-identity'"]
HKDF --> PrivKey["32-byte X25519<br/>private key"]
PrivKey --> PubKey["X25519 public key<br/>(clamped, published to PDS)"]
end
style Generate fill:#1a1a2e,color:#eee
style Derive fill:#16213e,color:#eee
The mnemonic is the root secret — everything else is derived. Losing it means losing access to all encrypted data. The HKDF info string includes the schema version for domain separation, same convention as key wrapping.
First Login (New Device)#
sequenceDiagram
participant User
participant CLI
participant Crypto
participant PDS
User->>CLI: opake login <handle>
CLI->>User: No identity found. Enter seed phrase or generate new?
User-->>CLI: "abandon ability able about above absent ..."
CLI->>Crypto: BIP-39 validate (checksum, wordlist)
Crypto-->>CLI: valid
CLI->>Crypto: mnemonic → PBKDF2 → 512-bit seed
CLI->>Crypto: seed → HKDF-SHA256 → X25519 private key
Crypto-->>CLI: keypair (private + public)
CLI->>CLI: Save identity (private key to disk)
CLI->>PDS: com.atproto.server.createSession
PDS-->>CLI: { did, handle, accessJwt, refreshJwt }
CLI->>PDS: putRecord (publicKey/self)
PDS-->>CLI: { uri, cid }
CLI->>User: Logged in as <handle>
If the published public key doesn't match the derived one, the CLI warns — either a wrong seed phrase or a key was published from a different seed. The user decides whether to overwrite.
Generate New Identity#
sequenceDiagram
participant User
participant CLI
participant Crypto
User->>CLI: opake init
CLI->>Crypto: Generate 128 bits entropy (CSPRNG)
Crypto-->>CLI: entropy
CLI->>Crypto: BIP-39 encode (entropy → 12 words)
Crypto-->>CLI: mnemonic
CLI->>User: Your seed phrase (write this down):<br/>"abandon ability able about ..."
CLI->>User: Confirm by entering words 3, 7, 11
User-->>CLI: "able", "absent", "above"
CLI->>CLI: Verify matches
CLI->>Crypto: Derive keypair from mnemonic
Crypto-->>CLI: keypair
CLI->>CLI: Save identity to disk
CLI->>User: Identity created. Run `opake login` to connect to a PDS.
The confirmation step guards against clipboard-and-forget. The seed phrase is shown exactly once — the CLI never stores or displays it again.
Key Mismatch Recovery#
sequenceDiagram
participant User
participant CLI
participant PDS
User->>CLI: opake login <handle>
CLI->>CLI: Derive keypair from seed phrase
CLI->>PDS: getRecord (publicKey/self)
PDS-->>CLI: Published public key ≠ derived public key
CLI->>User: Warning: PDS has a different public key.<br/>This means either:<br/>1. Wrong seed phrase<br/>2. Key was published from another seed
CLI->>User: Overwrite published key? [y/N]
User-->>CLI: y
CLI->>PDS: putRecord (publicKey/self) with derived key
PDS-->>CLI: { uri, cid }
CLI->>User: Public key updated. Previous grants may be unreadable.
Overwriting the published key breaks any existing grants or keyring memberships that wrapped keys to the old public key. The CLI should be loud about this.