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
-
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.
-
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.
-
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.
-
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)
TEE worker (v0.1)
CLI (v0 can start, v0.1 full)
Mock backend migration (v0)
Documentation
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
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_registrationstables, 6 relay endpoints, long-pollawait_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
Four design refinements
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.
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.
TTL recorded on chain (
valid_until). The pair request includes avalid_untilblock 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.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
rendezvous_registrationstable + schemaauth_requeststable + schemaregister_rendezvoushandlerpoll_rendezvoushandler (long-poll)deliver_rendezvoushandleropen_auth_requesthandlerfetch_auth_requesthandlerapprove_auth_requesthandlerawait_auth_decisionhandler (long-poll)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)
PairRequestextrinsic type:{ daemon_pubkey, scope, alias, nonce, valid_until, parent_wallet }ApprovePairextrinsic type:{ pair_request_id, encrypted_child_session }ApprovePairifcurrent_block > valid_untilPairRequestOpened,PairRequestApproved,PairRequestExpiredparent_walletfor efficient master CLI queriesTEE worker (v0.1)
CLI (v0 can start, v0.1 full)
agentkeys approvequeries chain/TEE for pending pair requests for the master's walletSHA256(signature)mod 10^6)Mock backend migration (v0)
rendezvous_registrationstable + 3 endpointsauth_requeststable + 4 endpointsDocumentation
wiki/serve-and-audit.mdwith a pair transport sectiondocs/manual-test-stage4.mdpair flow testswiki/key-security.mdreferencesAcceptance criteria
valid_until) are rejected at the pallet levelEffort 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
docs/spec/plans/development-stages.mdStage 9 — "Hybrid on-chain pair transport" design decisioncrates/agentkeys-mock-server/src/handlers/auth_request.rs— current auth_request handlers (to be removed)crates/agentkeys-mock-server/src/handlers/rendezvous.rs— current rendezvous relay (to be removed)crates/agentkeys-cli/src/lib.rs:372-444— currentcmd_approveflow