Keyrxng

WebAuthn Account Abstraction

Ubiquity · 2024 · Passkeys + deterministic EOA for seamless onboarding and reliable recovery.

So what?
<5 s auth flow duration
Role
Product Engineer
Year
2024
Stack
WebAuthn, Account Kit, TypeScript
Read narrative
<5 s auth flow duration

Problem

Onboarding friction and recovery anxiety for non-crypto natives needed to be eliminated without adding ceremony.

Approach

  1. Use WebAuthn to create a passkey at signup
  2. Derive a deterministic EOA from passkey + session + metadata + salt
  3. Interact through a smart account; sponsor gas selectively
  4. Regenerate the EOA for recovery using the same inputs

System diagram

flowchart LR
  User[User] --> OAuth[GitHub OAuth]
  OAuth --> Session[Supabase Session]
  Session --> Challenge[WebAuthn Challenge]
  Challenge --> Credential[Credential Creation]
  Credential --> Entropy[Multi-Source Entropy Pool]
  Entropy --> Mnemonic[12-Word Mnemonic]
  Mnemonic --> Key[Private Key Derivation]
  Key --> EOA[EOA Creation]
  EOA --> SAFE[SAFE Association]
  SAFE --> Tx[Sponsored Transaction Execution]

Outcome

Constraints

Design choices

Proof

Code excerpt — Deterministic entropy and salt construction

const authEntropies = [
  `${displayName}-${id.toString()}-${name}`,
  `${userOauth.id}-${userOauth.ca}-${userOauth.iid}`,
  `${cred.id}-${cred.type}-${cred.rawId}`,
  orgSaltWords.join("-"),
];
const userSalt = authEntropies.join("-");

Code excerpt — Credential creation boundary (browser)

export async function createCredential(user: User): Promise<Credential | null> {
  const publicKey = createCredentialOptions(createCredentialUser(user.displayName, user.name, String(user.id)));
  if (typeof navigator === "undefined") return null;
  try {
    return (await navigator.credentials.create({ publicKey })) as Credential;
  } catch (err) {
    return null;
  }
}

Demo evidence — 5s auth flow (passkey + OAuth) and SAFE integration (see references)

References