# @cef-ai/wallet-identity

WebAuthn-based identity layer for the SCP Wallet. Performs the WebAuthn ceremony,
derives Ed25519 and secp256k1 key material from the PRF extension output via HKDF,
and exposes a session vault as MobX-observable state.

## What it does

- **WebAuthn ceremony.** `WebAuthnCeremonyAdapter` wraps
  `navigator.credentials.create()` / `.get()` with the PRF extension.
- **PRF → keys.** PRF output is fed through HKDF to derive Ed25519 (Polkadot/Cere
  SS58, Solana) and secp256k1 (EVM) key material. Implemented via `@noble/curves`
  and `@noble/hashes`.
- **Session vault.** A closure-encapsulated key store (`createSessionVault()`); raw
  key material is never exposed as a property — only `InternalSign` closures are
  handed out.
- **Observable identity.** `IdentityImpl` exposes `isAuthenticated`, `addresses`,
  and `credentialId` as MobX observables so UI components re-render when the session
  changes.
- **Cross-tab sync.** `CrossTabSync` propagates logout across SPA tabs via
  `BroadcastChannel('scp-wallet-v2')`.

## Quick start

```ts
import { IdentityImpl, WebAuthnCeremonyAdapter } from '@cef-ai/wallet-identity';
import { ApiClient } from '@cef-ai/wallet-api-client';

const identity = new IdentityImpl({
  apiClient: new ApiClient({ baseUrl: 'https://wallet-api.example.com' }),
  ceremony: new WebAuthnCeremonyAdapter(),
});

await identity.register({ label: 'my-device' });
// or, on a return visit:
await identity.login();

console.log(identity.isAuthenticated); // true
console.log(identity.addresses); // { cere, evm, solana }
console.log(identity.credentialId); // base64url string
```

## Public exports consumed by the embed-sdk

- `IdentityImpl` — constructor + lifecycle (`register`, `login`, `logout`, `dispose`).
- `WebAuthnCeremonyAdapter` — real WebAuthn binding.
- `SoftAuthenticator` — test-only (see below).
- `CrossTabSync` — cross-tab logout.
- Address derivation helpers (`deriveCereAddress`, `deriveEvmAddress`,
  `deriveSolanaAddress`) and the `derivedKeys` HKDF routine.

## `SoftAuthenticator` — test-only

`SoftAuthenticator` is a deterministic in-memory replacement for
`WebAuthnCeremonyAdapter`. It exists **not** because Chromium's virtual
authenticator lacks PRF — `hasPrf` on `WebAuthn.addVirtualAuthenticator` works
and returns stable output (verified on Chrome 149) — but because CDP's
`Credential` carries no hmac-secret field, so PRF state cannot survive
export/re-import, and CDP's WebAuthn domain is per-_target_ while every wallet
ceremony runs in a popup. A CDP-driven suite could therefore log in exactly once
per target; without `SoftAuthenticator`, e2e tests cannot exercise the identity
layer across the popup boundary at all.

It is **opt-in only** via `VITE_USE_SOFT_AUTHENTICATOR=true` and is bound to
non-production builds. There is no path that loads it in a production bundle.

## Status

Workspace-internal — not published to npm.
