OpenVTC — The Trust Community CLI
Repo: github.com/OpenVTC/openvtc
OpenVTC is the user-facing tool for participating in Verifiable Trust Communities. It’s a Rust CLI and TUI (terminal user interface) that orchestrates identity creation, relationship building, and credential exchange — making the complex machinery of decentralized trust accessible to end users.
What It Does
OpenVTC implements the First Person Protocol for the “Know Your Developer” use case. From a user’s perspective:
- Set up your identity — generate keys, create your Persona DID (did:webvh), host it on the domain of your choice
- Connect with people — send and accept relationship requests via DIDComm
- Build your trust network — exchange Relationship Credentials, receive endorsements, get witness attestations
- Participate in communities — join VTCs (presenting a Verifiable Invitation Credential where required), respond to community protocol messages — and since June 2026, belong to multiple communities at once, each under its own persona
Behind the scenes, OpenVTC orchestrates the VTA (for key management), DIDComm messaging (for communication), and credential issuance (for trust building).
Components
After the v0.2.0 workspace consolidation and v0.2.1 cleanup, the active crates are:
openvtc
The user-facing TUI binary (formerly openvtc-cli2). The unsuffixed name is intentional, matching the convention used by uv, ruff, deno, and cargo. Eight main-menu panels:
- Inbox — real-time task processing (auto-handles trust-pongs, relationship finalization, rejections; queues interactive tasks; detail views for inbound/outbound requests, VRCs, pings, informational messages)
- Relationships — list/detail/new-request views, inline alias editing, R-DID privacy toggle, trust-ping with RTT latency
- Credentials — Received/Issued tabs, raw VRC JSON in detail view, clipboard copy, VRC request and removal
- Settings — inline editing, config export/import, passphrase protection management, hardware token detection, factory reset
- VTA Service — VTA URL, DID, credential DID, key count, backend type
- Logs — scrollable timestamped activity log with copy
- Help/Status — DID clipboard copy hotkeys with visual feedback
- Quit
The openvtc-cli legacy prompt-driven binary was deleted in v0.2.0 — all ongoing work lives in openvtc.
openvtc-core
Shared library (formerly openvtc-lib, publish = false): config management, BIP-32 key derivation, relationship state machines, credential operations, and OpenPGP card support. After v0.2.0, no longer depends on ratatui or dialoguer — TUI deps don’t bleed into openvtc-service or robotic-maintainers.
openvtc-service
Background daemon that polls a DIDComm mediator for incoming messages and processes protocol requests. Currently handles maintainer list queries (https://kernel.org/maintainers/1.0/list).
did-git-sign
SSH/Git signing helper that uses the VTA as a signing oracle. Auto-configured during the setup wizard. Refuses to sign unless the parent process name starts with git or ssh-keygen, and writes every signing attempt — accepted or denied — to ~/.config/did-git-sign/audit.log. Since the multi-community work (T8, June 2026) the signing persona is a per-repo selection via env var or git config, so different communities can sign distinct repos.
The earlier openvtc-service (background DIDComm daemon) and robotic-maintainers (auto-accept test service) crates were removed in v0.2.1 (PR #63) — openvtc-service’s role is now covered inside the TUI’s own DIDComm session, and the test fixture role moved to the in-tree mediator harness.
Identity Model
OpenVTC uses a two-layer identity model:
- Persona DID (P-DID) — your primary, public identity, created as a did:webvh and hosted on the domain of your choice as a
did.jsonlfile - Relationship DIDs (R-DIDs) — private
did:peeridentifiers, one per relationship, so your Persona DID isn’t exposed in every interaction
Configuration
OpenVTC uses a three-tier configuration system:
| Tier | Storage | Contents |
|---|---|---|
| PublicConfig | JSON on disk | Non-sensitive settings |
| SecuredConfig | OS keyring/keychain | Cryptographic secrets |
| ProtectedConfig | Encrypted inside PublicConfig | Sensitive data encrypted with unlock code |
Protection modes:
- Unlock code — encrypt with a user-provided passphrase (HKDF-SHA256 + random nonce)
- Plaintext — no encryption (development only)
- Hardware token — encrypt via OpenPGP card (Nitrokey/YubiKey)
Multiple profiles are supported via the OPENVTC_CONFIG_PROFILE environment variable or -p flag.
Recent Development
The focus has shifted from security correctness alone (the v0.1.x pass), through feature completeness on a hardened base (v0.2.0), through architecting the multi-community model, to executing that pivot in full: as of the Banyan milestone tag (2026-06-22), OpenVTC holds one VTA account and many persona-backed community memberships, live.
June 2026 — multi-community executed (T1–T9 complete) + VIC join flow — Banyan milestone
The entire multi-community plan landed in a single month (80 commits, PRs #69–#149; ~21,600 insertions). The window ends with a lightweight milestone tag, Banyan (2026-06-22, at PR #148) — note the shift from vX.Y.Z tags to codenames. The workspace version is still 0.2.1 and the changelog hasn’t caught up; commit history, the DRAFT v5 spec, and the tag are ground truth.
T1–T9 all complete. Beyond the T1 foundation (config v2 + supervised multi-session manager, one recoverable DIDComm task per community session, 111), the slices landed in order: context_path hierarchy mirroring vti-common validation (T2, #112); State-A bootstrap split from the monolithic ~19-step wizard — account bootstrap mints no persona DID (T3, #113); communities overview with favourite toggle and a Ctrl+K community switcher (T4, #114); join flow with identity choice — reuse a persona or mint a fresh did:webvh — and live session registration without restart (T5, 116); pending-resolution lifecycle with the 7-day timeout → Expired (T6, #117); leave / archive / inactive-only delete with read-only styling (T7, #118); did-git-sign per-repo persona selection (T8, #119); and end-to-end integration tests against a real mediator and VTI’s MockVta (T9, #120–#122).
The join path got real — Verifiable Invitation Credentials + verdict-model admission. The spec’s “stub VP” placeholder (deferred decision D4) was replaced within the same window by a concrete credential mechanism:
- Join over DIDComm: submit_join to the VTC (#69), submit-receipt reconciliation (#71), credential delivery flipping Pending → Active (#72), minted-persona rollback on failed join (#74, #123), one DIDComm listener per persona (#79) — the messaging backbone of multi-community.
- VIC presentation at join (#127), VIC storage in the VTA credential vault (#130), subject-linkage proof to join under a fresh DID (#131), full VIC lifecycle management from the VTA panel (import/archive/soft-delete/restore/purge, #136).
- Verdict-model join (#136): a
VerdictResponsedrives admission — allow → Active, deny → Rejected, refer/request_more → Pending — plus DIDComm problem-report handling, fixing joins that previously sat stuck Pending for 7 days on rejection. - Join UX/robustness burst (#137–#145): community-matched VIC selection, Trust Task document framing, W3C-compliant join VP, identity-first join with per-persona VIC badges.
- Multi-membership + reciprocity: multiple memberships per community under distinct personas (#146); issuing a reciprocal member VMC back to the community (#147) and auto-answering a VTC
members/request-vmc(#148 — theBanyancommit). Membership is now bidirectional in practice, matching the spec’s bi-directional edge model.
Supporting streams in the same window:
- R-series architectural hardening (R17–R27, #89–#109): protocol state machine hoisted into
openvtc-core(new 1,800-linemessaging.rs),Arc<Mutex>flattened out of the domain model, typed credential registry replacing string-matched VC kinds, typed VTA/Auth errors, a TUI architecture design doc. - TUI responsiveness (#92–#97): background-dispatch pattern for network actions, coalesced/offloaded
Config::save, Argon2 KDF off the async runtime, cancellable mid-flight join/setup. - Security & privacy fixes (#82–#88):
VRCIssuednow gated on relationship, issuer binding, and proof verification; private-config contents no longer logged; DIDs truncated in handler logs; private key redacted fromdid-git-signdebug output; fuzzing groundwork (#125/#126) with feature-gatedArbitraryderives on parse-surface types. - Final fix of the window (#149, 06-27): repair R-DID
key_infoids that caused a mediator auth loop. - Dependency escalation tracking VTI’s cadence: vta-sdk 0.10 → 0.17 → 0.18.1 plus affinidi-tdk 0.8 in ten days — OpenVTC is functioning as the reference client for the VTI stack.
Still open: VP requirement discovery (the one unresolved spec item, D4), persona key rotation, per-community capabilities beyond the ported main page.
Post-v0.2.1 — multi-community design + T1 implementation
Design and implementation of OpenVTC’s multi-community pivot are landing in slices on top of v0.2.1.
- Multi-community design spec (PR #65, 2026-06-03) —
docs/design/multi-community-support.mdDRAFT v4 + presentation deck. Decisions D1–D17 settle persona-per-community choice (user picks per join, lazy persona creation), VTA-as-system-of-record (the local config holds only references and UX prefs; the VTA stores personas/keys/credentials), breaking-reset migration (v1 configs are not migrated; the CLI detects, informs, deletes, and runs setup from scratch), per-community read-only / archive / delete lifecycle, supervised concurrent sessions (one supervised task per community session, failure-isolated), 7-day client-side pending-join timeout,did-git-signper-repo persona selection.tasks/plan.md+tasks/todo.mdgive the dependency-ordered T1–T9 breakdown across four phases. - T1 active-identity API sketch (PR #66, 2026-06-04) — pins
IdentityContext/IdentityRegistry(inopenvtc-core) and the persona-keyed multi-session manager built as a thin layer overaffinidi-messaging-didcomm-service0.3.3 (whose listeners are already independent, auto-restarting tasks with dynamic add/remove). N=1 single-community on the new architecture is T1’s exit criterion. - VTA hierarchical contexts + MockVta folded in (PR #68, 2026-06-04) — VTI shipped server-enforced hierarchical context paths and a
MockVtatest harness; the multi-community spec dropped its “convention now, migrate later” sub-context fallback in favour of the canonical server-enforced model (D2/D9 updated). Spec → DRAFT v5. - T1 implementation slice (PR #67, 2026-06-07) — additive config v2 in
openvtc-core:Account { vta_did, vta_url, top_context_id, personas, communities },PersonaRecord(self-containeddid:webvh, VTA-managedKeyRefs),CommunityRecord(persona_ref, status, favourite, archived, member_since, per-community relationships and VRCs),CommunityStatus { Pending, Active, Left, Rejected, Removed, Expired }withis_active/is_read_only/is_inactive/needs_attention, stable-UUIDPersonaId(rotation-safe). Persona-keyedIdentityRegistryresolves communities → personas; a reused persona yields one shared DIDComm session serving multiple communities (matches the DIDCommDuplicateDidconstraint and the chosen isolation model). Still additive — not yet wired into the liveConfig/ load / save path; the consumer refactor,IdentityContext, and breaking-reset land in subsequent slices.
v0.2.1 — 2026-06-03 — nine CLI security/hardening fixes + crate retirement
- Nine CLI security and hardening fixes (PR #61)
- Minor dependency updates (PR #60);
arboard::Clipboardimport fix;vta-sdkbump to 0.7 (PR #62) openvtc-serviceandrobotic-maintainerscrates removed (PR #63). The DIDComm daemon role is covered by the TUI’s own DIDComm session integration; the auto-VRC-issuance fixture is no longer needed alongside the in-tree mediator harness. Workspace members, README, CONTRIBUTING, and SECURITY updated. Historical CHANGELOG entries preserved. Closes #21 (the secure-storage-backend work foropenvtc-serviceis obsolete).- Workspace bumps:
vta-sdk0.7 → 0.9;criterion0.7 → 0.8 (PR #64).
v0.2.0 — 2026-05-05 — major release (workspace consolidation + full TUI)
Headline functional changes:
- Workspace consolidation: the active CLI binary
openvtc-cli2renamed toopenvtc(matching the uv / ruff / deno / cargo convention); the supporting libraryopenvtc-librenamed toopenvtc-core; the legacy prompt-drivenopenvtc-clideleted - Full TUI main menu — eight panels: Inbox, Relationships, Credentials, Settings, VTA Service, Logs, Help/Status, Quit. Real-time inbox task processing, inline alias editing, R-DID privacy toggle, trust-ping with RTT latency, raw VRC JSON in detail view, scrollable activity log
- DIDComm service integration replacing ~370 lines of manual messaging with
affinidi-messaging-didcomm-service0.2 (Router-based dispatch, automatic reconnection, message pickup, multi-DID listener support, periodic 60s keepalive) - R-DID generation for both BIP32 and VTA backends, with dynamic R-DID listeners auto-added so message delivery works to relationship-specific DIDs
- VRC issuance from inbox with DataIntegrityProof signing; VRC rejection with message back to requester; friendly name in relationship requests, auto-set as contact alias on accept
ratatuianddialoguerdependencies removed fromopenvtc-coreso daemon and automation crates no longer pull in TUI deps transitively
Security pass folded into the same release branch:
- Per-entry random Argon2 salt with transparent v1→v2 migration for
derive_passphrase_key. The previous deterministic salt = SHA-256(info) meant two operators with the same passphrase produced byte-identical KEKs; the newpassphrase_encrypt_v2writes a magic-prefixed[OPV2 | salt(16) | nonce(12) | ct+tag]blob with a fresh random salt; decrypt auto-detects v1/v2. Argon2id parameters bumped to the OWASP “high-value KEK” floor (m=128 MiB, t=4, p=1) did-git-signsigning-policy hardening: the proxy refuses to sign unless the parent process name starts withgitorssh-keygen, and writes every signing attempt (accepted or denied) to~/.config/did-git-sign/audit.log(mode 0600)- DIDComm replay window + 1024-entry seen-message LRU in
process_inbound_message: drop messages outside ±48h / +5m skew, drop expired, dedupe by ID - Tagged-variant downgrade defence on
SecuredConfigFormat: switched the on-disk variant tag from#[serde(untagged)]to#[serde(tag = "format")]so every blob carries an explicit discriminator - W3C DID Core 1.0 syntax parser replacing the previous
did:prefix check; rejects bidi-override / zero-width chars in DID fields - Inbox display-name sanitisation strips bidi/control/ANSI; bounded DIDComm event channel (256-entry capacity);
did.jsonlwrite path is now the resolved profile dir - Three community PRs folded in: profile-name validation hardening (#57); cross-platform config paths via
dirs::config_dir()on Windows (#51, closes #47);SecuredConfigserde-format hardening (#34)
Test & CI changes:
- New in-process mediator harness wrapping the upstream
affinidi-messaging-test-mediator0.2 fixture; drops ~400 lines of in-tree fixture code and four dev-deps - End-to-end integration tests drive a real Alice → Mediator → Bob DIDComm round-trip, a
RelationshipRequestBodyround-trip, and a two-leg VRC request/reject — all in ~350 ms once the mediator is up - 38 new unit tests: setup-flow navigation (25 table-driven), BIP32 derivation (7 known-answer vectors), AES-GCM tampering (6)
- CI adds
cargo-deny(advisories + licenses + bans + sources) andcargo-llvm-covcoverage; MSRV bumped 1.91 → 1.94
v0.1.6 (did-git-sign integration) — 2026-04-30
- Auto-configure
did-git-signas part of setup wizard - Uninstall (lib + CLI subcommand)
- Show
did-git-signprincipal + SSH key on help screen - SSH-aware clipboard with OSC 52 + arboard fallback
--credentialpaste replaced with online VTA provisioningAdminRotatedprovisioning intent
v0.1.5 — 2026-04-15
- BIP32 seed and imported key material moved from plain
StringtoSecretString - Windows PowerShell examples and Windows secure-storage documentation
v0.1.4 — earlier April 2026
- VRC
valid-untilprompt handling fix
v0.1.3 — earlier April 2026
- Fixed a deterministic encryption vulnerability in unlock-code encryption (replaced seeded PRNG with HKDF-SHA256 + random nonce, with transparent legacy migration)
See also: verifiable-trust-agent, first-person-network, decentralized-trust-graph