IBExWalletAPI
Advanced

Email-Based Key Recovery

Email-Based User Secret for Deterministic Key Recovery

This document describes a secure mechanism for generating a private key without storing it on the backend, while allowing the user to recover it later using only a token delivered by email. The backend must not be able to reconstruct the private key without the user’s email token.


Overview

The system uses a high-entropy email token as the user’s true secret.
The backend stores only a verifier of this token, never the token itself, and never the private key.

This achieves:

  • Non-custodial key ownership (backend cannot regenerate the key).
  • Simple UX: the user only needs to keep an email.
  • Deterministic regeneration of the same private key from the email token.
  • Zero storage of private keys server‑side.

Core Idea

  1. Backend generates a random 256-bit token T.
  2. Backend sends T to the user via email.
  3. Backend stores only a hash of T (e.g., Argon2id output).
  4. The client uses T to derive:
    privateKey = HKDF(T, info="email-derived-signer")
  5. Backend cannot regenerate the private key because it does not know T.

User recovery is possible simply by finding the email again.


Enrollment Flow

1. Backend generates token

T = random(32 bytes)
tokenHash = Argon2id(T, saltServer)
store(tokenHash, saltServer, externalUserId)
email(T to user)
  • T is never stored.
  • Only tokenHash + saltServer are kept.

2. Client receives token and derives key

Client extracts T (directly or from a URL fragment):

privateKey = HKDF(T, info="email-derived-signer")
publicKey  = derivePublicKey(privateKey)

Client registers publicKey with backend.

Backend stores:

externalUserId → publicKey

Backend still cannot regenerate privateKey.


Authentication Flow (Using Email Token)

  1. Client asks backend for login.
  2. Backend sends a challenge.
  3. Client re-derives:
    privateKey = HKDF(T, info="email-derived-signer")
    signature = sign(challenge, privateKey)
  4. Backend verifies signature using stored publicKey.

Recovery Flow

To recover the signer on a new device:

  1. User finds the email containing token T.
  2. Client derives the same private key:
    privateKey = HKDF(T, info="email-derived-signer")
  3. Client proves ownership by signing a challenge.
  4. Backend validates and grants access.

Backend cannot recover the key without the user; only the user has T.


Threat Model

Backend DB compromised

Attacker obtains:

  • tokenHash
  • saltServer
  • publicKey

Cannot:

  • Derive T (Argon2id is slow + unknown entropy source)
  • Derive privateKey
  • Authenticate as user

Email compromise

Attacker gains access to email → attacker can read T.

This is the intended trade-off:
security is anchored in email ownership, not backend trust.

User forgets token

If the user deletes or loses the email, the key becomes unrecoverable unless an external recovery method is implemented (e.g., Safe guardians).


Optional: Backup Encryption Variant

If the private key should be randomly generated, not derived from T:

  1. Client generates privateKey randomly.
  2. Backend sends T by email.
  3. Client derives:
    K = Argon2id(T, backupSalt)
    C = AES-GCM-Encrypt(K, privateKey)
  4. Backend stores only C + backupSalt.

To recover:

K = Argon2id(T, backupSalt)
privateKey = decrypt(C, K)

Backend never learns the key.


Summary

Using a high-entropy email token allows:

  • Deterministic private key derivation
  • No backend access to the private key
  • Simple user recovery (keep the email)
  • Strong security as long as the email account is trusted

This method is ideal for systems where users should own cryptographic keys without managing complex secrets or seed phrases.