Skip to content

Replace dev_key_service with TEE worker for omni-anchored EVM keypair derivation #74

@hanwencheng

Description

@hanwencheng

Background

Per Stage 7 design (#64) and aligned with the Wildmeta omni_account + TEE-signer pattern, the daemon's auth flow should converge on:

  • One user-facing identity (email / OAuth2) → omni_account anchor
  • Server-derived EVM wallet per omni_account (deterministic, never leaves the trust boundary)
  • No local key management by the operator — recovery via re-authenticating any linked identity in IdentityLinkStore

The pieces in place today:

The missing piece: a custodial signer that derives a keypair from omni_account and signs SIWE challenges on the user's behalf so the daemon never needs a local secp256k1 key.

Step 1 (this issue's first deliverable) — dev_key_service in mock-server

Ship a development-only signer module in crates/agentkeys-mock-server with the exact wire shape the eventual TEE worker will use, gated by env so production builds reject it.

Module

  • crates/agentkeys-mock-server/src/dev_key_service.rs
    • Loads master secret from \$DEV_KEY_SERVICE_MASTER_SECRET (32-byte hex). Refuse to enable if absent.
    • derive_keypair(omni_account) -> (secret_key, eth_address) via HKDF-SHA256 with info = "agentkeys-evm-wallet-v1:" || omni_account.
    • sign_eip191(omni_account, message) -> Signature.
    • Strong "DEV ONLY — replace with TEE" warnings in the file header + every public method.

Endpoints (env-gated, return 503 if DEV_KEY_SERVICE_MASTER_SECRET unset)

  • POST /dev/derive-address — body { omni_account }{ address }
  • POST /dev/sign-message — body { omni_account, message_hex }{ signature }

Daemon integration

Update crates/agentkeys-daemon/src/main.rs to use the new flow:

  1. Operator authenticates via email/OAuth2 → broker mints session JWT for omni_email
  2. Daemon calls backend /dev/derive-address with omni_email → derived EVM address
  3. Daemon calls broker /v1/wallet/link (auth: omni_email session JWT) to register the derived address
  4. Per-mint: daemon calls broker /v1/auth/wallet/start(derived_addr) → SIWE message
  5. Daemon calls backend /dev/sign-message(omni_email, siwe_message) → signature
  6. Daemon calls broker /v1/auth/wallet/verify → session JWT for omni_evm
  7. Daemon calls broker /v1/mint-oidc-jwt → OIDC JWT → AssumeRoleWithWebIdentity → AWS temp creds

The signer call (step 5) is direct daemon → backend, mirroring the eventual daemon → TEE attested channel.

Removes

  • agentkeys init --mock-token legacy bootstrap (replaced by email/OAuth2 + auto-derive)
  • /v1/auth/exchange legacy bearer shim (no caller after daemon migrates)
  • Broker → backend /session/validate round-trip (no caller after exchange shim deletes)

Step 2 (follow-up issue) — Replace dev_key_service with TEE worker

Same wire surface (/dev/* endpoints become /tee/* or stay), real TEE backing:

  • Master secret generated inside the enclave at first boot, sealed-data persisted
  • Remote attestation so daemon can verify the worker is genuine before sending omni → key derivation requests
  • Logs every signing operation with omni_account + message hash, no secret material

When TEE lands: dev_key_service.rs deletes; routing flips to TEE worker URL via env var; zero changes to daemon or broker.

Out of scope

  • Threshold signing for high-value omni_accounts
  • Master-secret rotation policy
  • Multi-region TEE replication
  • Production gating of DEV_KEY_SERVICE_MASTER_SECRET (compile-time cfg(not(production)) could come later)

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions