Skip to content

v0.1: Hybrid on-chain pair transport (replaces rendezvous relay + auth_requests table) #6

@hanwencheng

Description

@hanwencheng

Summary

Replace the v0 centralized pair relay (rendezvous table + auth_requests table + 6 HTTP endpoints + long-poll state machine) with on-chain pair transport, applying the same Pattern 4 decoupling (serve immediately, audit async via paymaster) to the pair flow that issue #5 applies to credential reads.

Full design notes: docs/spec/plans/development-stages.md — Stage 9 "Hybrid on-chain pair transport" section.
Background on Pattern 4: issue #5.
Background investigation: wiki/key-security.md, wiki/serve-and-audit.md.

Problem

The v0 pair flow uses centralized backend state (SQLite auth_requests + rendezvous_registrations tables, 6 relay endpoints, long-poll await_auth_decision) to connect daemons to master CLIs. Once credential audit moves on-chain under Pattern 4, this relay infrastructure becomes the only remaining centralized state — architecturally inconsistent and operationally duplicative. The pair flow should match the credential-read flow: on-chain transport, decoupled serve/audit, paymaster-funded.

Design

Flow

Phase 1 — Daemon bootstraps

daemon generates ephemeral keypair
  ↓
daemon builds pair request:
  { daemon_pubkey, scope, alias, nonce, valid_until }
  ↓
daemon submits to TEE
  ↓
TEE validates, stores internally, acknowledges daemon immediately (~50ms)
TEE asynchronously submits pair-request extrinsic to chain via paymaster
  ↓
daemon displays:
  - full request details (scope, alias, daemon pubkey fingerprint)
  - VVC (visual verification code) = derived client-side from extrinsic signature
Phase 2 — User approves on master

master CLI queries chain/TEE for pending pair requests for its wallet
  (filtered by soft time window, last N minutes)
  ↓
agentkeys approve  →  shows list:
  [1] daemon 0x44d3... scope=[openrouter] alias=my-agent VVC=472915
  ↓
user compares VVC + details with daemon display, picks matching entry
  ↓
master CLI submits approval to TEE
TEE mints child session, returns to daemon immediately (~50ms)
TEE asynchronously submits approval extrinsic to chain via paymaster
  (approval contains encrypted child session, encrypted to daemon_pubkey)
Phase 3 — Daemon receives

daemon receives child session from TEE (immediate, decoupled from chain)
  ↓
daemon decrypts with its own private key
  ↓
daemon stores session, begins serving MCP calls

Four design refinements

  1. VVC is client-derived, not on-chain. The chain stores the signed pair request; clients derive a short human-readable code from the extrinsic signature. No on-chain "OTP" field. Any client can independently compute the same VVC from the same signature — deterministic, no server involvement.

  2. Full request details displayed. CLI/webapp shows scope, alias, daemon pubkey fingerprint, VVC, and valid_until. Not just a 6-digit code — the user inspects exactly what they're approving. Client-side local verification.

  3. TTL recorded on chain (valid_until). The pair request includes a valid_until block number or timestamp as a first-class on-chain field. The pallet rejects approval extrinsics targeting expired pair requests. Hard TTL enforced at the protocol level.

  4. Latency decoupled (Pattern 4 consistency). Both pair request and approval are served by the TEE immediately; on-chain extrinsics follow asynchronously via paymaster. Pair latency = ~50ms, not ~12s. Same tradeoff as Pattern 4 credential reads: ~6s audit staleness, same failure handling questions.

Why VVC still matters (the decoy attack)

Without visual verification, an attacker who submits a decoy pair request to the same master wallet is indistinguishable from the legitimate daemon in the master CLI's pending list. The user picks the wrong one and approves the attacker's daemon. VVC is the user's only visual tiebreaker. On-chain signatures prove authenticity and integrity, but they do not prove "this is MY daemon, not some other daemon on the chain" — only the user's eyes comparing two displays can do that.

What this removes from the backend

Removed component Current LOC (approx)
rendezvous_registrations table + schema ~10
auth_requests table + schema ~15
register_rendezvous handler ~70
poll_rendezvous handler (long-poll) ~65
deliver_rendezvous handler ~55
open_auth_request handler ~105
fetch_auth_request handler ~70
approve_auth_request handler ~165
await_auth_decision handler (long-poll) ~75
Registration token management ~20
TTL enforcement in SQL ~15
Total removed ~665

Replaced by chain event reads + TEE approval logic (~200 LOC new), for a net reduction of ~465 LOC in the backend.

Deliverables

Heima pallet (v0.1, requires Kai review)

  • PairRequest extrinsic type: { daemon_pubkey, scope, alias, nonce, valid_until, parent_wallet }
  • ApprovePair extrinsic type: { pair_request_id, encrypted_child_session }
  • TTL enforcement: pallet rejects ApprovePair if current_block > valid_until
  • Events: PairRequestOpened, PairRequestApproved, PairRequestExpired
  • Indexable by parent_wallet for efficient master CLI queries

TEE worker (v0.1)

  • Decoupled pair-request processing: validate + store internally + ack daemon immediately, submit extrinsic async via paymaster
  • Decoupled approval processing: mint child session + return to daemon immediately, submit approval extrinsic async
  • Rate limit pair requests per wallet (reuse per-session rate limit from issue v0.1: TEE-side per-session read rate limit (abuse defense) #4)
  • Pair-request pending queue for async submission failure handling

CLI (v0 can start, v0.1 full)

  • agentkeys approve queries chain/TEE for pending pair requests for the master's wallet
  • Displays full details: scope, alias, daemon pubkey fingerprint, VVC, valid_until, age
  • VVC derivation: deterministic function of extrinsic signature (e.g., first 6 digits of SHA256(signature) mod 10^6)
  • Soft time-window filter: only shows requests from last N minutes (default 5)
  • Single-request auto-select: if exactly one pending request, highlight it as the default

Mock backend migration (v0)

  • Remove rendezvous_registrations table + 3 endpoints
  • Remove auth_requests table + 4 endpoints
  • Replace with mock chain-event simulation that matches the on-chain API shape
  • Update all Stage 4 tests to use the new flow

Documentation

  • Update wiki/serve-and-audit.md with a pair transport section
  • Update docs/manual-test-stage4.md pair flow tests
  • Update wiki/key-security.md references

Acceptance criteria

  • Daemon can pair with a master CLI using only chain events (no rendezvous relay, no auth_requests table)
  • Pair latency (daemon submit → daemon receives child session) < 200ms under Pattern 4 decoupling
  • Pair audit events appear on chain within ~10s
  • Expired pair requests (past valid_until) are rejected at the pallet level
  • VVC comparison works: two different pair requests produce different VVCs, user can distinguish
  • Decoy pair request attack: master CLI shows both, user can visually pick the correct one via VVC
  • All existing Stage 4 tests adapted and passing

Effort estimate

5-8 days (pallet work: 2-3d, TEE worker: 2-3d, CLI + mock migration: 1-2d). Blocked on Kai's review of the pallet-level pair request type and TTL enforcement.

Dependencies

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestv0.1development plan for blockchain backend integration

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions