Self-hosted (open source)

Install the open-source VTA appliance, generate a signing identity, and connect PNM, all on infrastructure you control.

Run the VTA appliance on infrastructure you manage, such as your laptop for local development, or a server your organisation already operates. You install the vta binary, generate its signing identity with a single non-interactive setup file, and connect Personal Network Manager (PNM) to the instance you just started.

Install binariescargo install vta-service, pnm-cliGenerate Admin DIDpnm setupConfigure & start VTAvta setup --from setup.tomlMINTS THE VTA'S OWN did:webvhConnect PNMpnm setup continueYOUR MACHINE

Use this guide when:

  • You need infrastructure-level control over where the VTA runs and how it’s configured.
  • You’re doing local development or testing, and want a disposable instance.
  • Your organisation already operates the host you’ll deploy on.

For a fully managed option, use Deploy VTA appliance instead. Affinidi provisions and runs the infrastructure for you, in Isolated mode, which runs in an AWS Nitro Enclave, a Trusted Execution Environment (TEE), or in Shared mode, which runs on a shared network for evaluation and development purposes only.

Once your VTA is running, whichever path you took, continue to Create your first context.

Prerequisites

  • Install Rust version 1.95 or later (required for compiling the VTA and PNM CLI).
  • OS keyring support (macOS Keychain, GNOME Keyring, or Windows Credential Manager). This guide uses the keyring as the backend for the VTA’s master seed.
  • A host and port the VTA will listen on. Use http://localhost:8100 for local development, or your server’s public HTTPS URL if other machines need to reach it.

Step 1. Install the VTA and PNM binaries

This path uses two binaries:

  • vta: the appliance itself. It holds the master seed and serves signing and vault requests.
  • pnm: the operator CLI. You use it to configure and connect to the appliance remotely.

cargo install pulls both directly from the registry. Clone the source repository only if you want to build from source or inspect the code.

cargo install vta-service@0.65.0 --locked --registry crates-io
cargo install pnm-cli@0.45.0 --locked --registry crates-io

Confirm the installation

vta --help
pnm help

You should see vta’s subcommands (including setup and bootstrap-admin) and PNM’s subcommands (including setup, health, keys, and contexts).

Step 2. Generate your Administrator DID

pnm setup mints a did:key locally and stores its private key in your OS keyring. The DID starts in a pending state because no VTA exists yet to register it with. You generate it now so Step 3 can embed it directly into the VTA’s own setup file. That lets a single vta setup call both create the VTA and grant your DID admin access.

pnm setup --name "my-vta"

Sample output:

Pending VTA 'my-vta' created.
  Admin DID: did:key:z6Mk...

Next: set `admin_did = "did:key:z6Mk..."` in the VTA setup.toml, boot the VTA,
      then run: pnm setup continue my-vta --vta-did <did:...>
{"slug":"my-vta","admin_did":"did:key:z6Mk...","state":"pending"}

The summary lines go to stderr and the final JSON line to stdout, so a script can capture the JSON on its own. Copy the admin_did value. You need it in the next step.

Step 3. Write a setup file

vta setup --from <file> provisions the VTA from one TOML file, with no prompts. Create setup.toml:

# setup.toml
config_path = "./vta-data/config.toml"
data_dir    = "./vta-data/data"
vta_name    = "my-vta"
public_url  = "http://localhost:8100"

# Paste the admin_did printed in Step 2.
admin_did   = "did:key:z6Mk..."
admin_label = "ops"

[secrets]
backend = "keyring"
service = "my-vta"

[vta_did]
kind = "create_webvh"
url  = "http://localhost:8100"
SettingWhat it does
config_path, data_dirWhere setup writes config.toml and the on-disk key store. Treat both as sensitive: config.toml holds the JWT signing key.
vta_nameA human-readable name for this VTA, shown by vta config show and pnm setup.
public_urlThe VTA’s REST address, advertised in its DID document. For a DIDComm-only VTA, leave it out and set rest = false in a [services] block.
admin_did, admin_labelYour Administrator DID from Step 2, and a label for it. Setup seeds it as super-admin and seals the VTA.
[secrets]Where the master seed is stored. Required. keyring uses your OS credential manager, under the service name, which should be unique for each VTA on the host.
[vta_did]The VTA’s own DID. create_webvh, the recommended kind, mints a did:webvh hosted at url, with a publicly verifiable history. By default it also lists its did:scid:vh:1 alias in alsoKnownAs; to leave the alias out, add a [dids] block with scid_also_known_as = false. create_did_key mints a did:key, for local development. See VTA DID.

Two consequences of these settings:

  • Sealed VTA: while the VTA is sealed, offline commands such as vta acl create, vta keys secrets, vta import-did, and vta export-admin exit with an error until you run vta unseal. Read-only commands such as vta acl list keep working. For a multi-operator setup, consider seeding the admin later instead.
  • Plaintext store: the key store is written in plaintext, apart from imported keys, which are always encrypted. To encrypt the store and keep the JWT signing key out of config.toml, add a [hardened] block with enabled = true.

Connect to a DIDComm mediator (optional)

The file above configures REST only. Add a [messaging] block to use DIDComm, which you need to retrieve vault credentials and to back up the VTA with pnm backup export. The mediator must already be running: see DIDComm mediator deployment options.

[messaging]
kind = "existing"
did  = "did:webvh:...mediator-did..."

# Set true only if the mediator uses explicit_allow (closed) mode.
# Leave false for an explicit_deny (open) mediator.
setup_acl = false

# Turn TSP off for a DIDComm-only mediator, such as an Affinidi-hosted one.
[services]
tsp = false
SettingWhat it does
kind, didexisting connects to a running mediator, identified by its DID.
setup_acltrue for a closed (explicit_allow) mediator: the VTA adds an allow-all ACL entry for its own DID on the mediator when it connects at startup, and without it the mediator rejects the VTA’s messages. false for an open (explicit_deny) mediator.
[services] tspTSP is on by default, and setup refuses a mediator whose DID document shows no TSP. If setup cannot check, it warns and continues. Set false for a DIDComm-only mediator, such as an Affinidi-hosted one.

Step 4. Run setup

vta setup --from setup.toml

This generates a fresh 24-word BIP-39 mnemonic, stores it in your OS keyring, writes config.toml, mints the VTA’s did:webvh identity, and seeds your Administrator DID as super-admin. After progress lines for each of those, the output ends with a summary that includes the VTA’s own DID:

Setup complete.
  Config:   ./vta-data/config.toml
  Data dir: ./vta-data/data
  Name:     my-vta
  URL:      http://localhost:8100
  VTA DID:  did:webvh:QmVE1TQeCtg3aavpTqasqencJpagRr8JKdGRyoZ5Qx6kRp:localhost%3A8100
  Admin:    did:key:z6Mk... (sealed)

  Mnemonic was generated and stored in the configured backend.
  Capture an encrypted backup after the first admin connects:
    pnm backup export --output vta-backup.vtabak

Copy this value. You need it in Step 6.

Step 5. Start the VTA

Setup only prepared the configuration and the key store. This step starts the daemon that actually serves signing, vault, and identity requests over REST using what Step 4 created.

vta --config ./vta-data/config.toml

Leave this running. Open a new terminal for the remaining steps.

Step 6. Finish connecting PNM

Your PNM session has been pending since Step 2, waiting to learn which VTA it belongs to. This step binds it to the VTA DID that vta setup minted in Step 4, moving the session from pending to complete.

pnm resolves the VTA DID from the host in its did:webvh identifier, and connects to the VTA to check your admin access. By default it accepts only public hosts:

  • Local development (public_url on localhost): allow private hosts in the terminal you run pnm from, before you run the command below.

    export VTA_ALLOW_PRIVATE_ENDPOINTS=1
  • Public HTTPS URL: no extra setting is needed.

pnm setup continue my-vta --vta-did <vta-did-from-step-4>

Sample output:

Checking whether did:webvh:QmVE1TQeCtg3aavpTqasqencJpagRr8JKdGRyoZ5Qx6kRp:localhost%3A8100 accepts did:key:z6MkjLfAT61ZbMfUTytxjp6KnDh7hy9jE5YkQJNuouJEC4cU...
Bound VTA DID for 'my-vta': did:webvh:QmVE1TQeCtg3aavpTqasqencJpagRr8JKdGRyoZ5Qx6kRp:localhost%3A8100
did:webvh:QmVE1TQeCtg3aavpTqasqencJpagRr8JKdGRyoZ5Qx6kRp:localhost%3A8100 accepted did:key:z6MkjLfAT61ZbMfUTytxjp6KnDh7hy9jE5YkQJNuouJEC4cU: admin access is in place. PNM rotated to a fresh long-lived did:key (did:key:z6Mk...) and dropped the temp DID from the ACL.
Ready: try `pnm health`.
{"slug":"my-vta","admin_did":"did:key:z6Mk...","state":"complete"}

admin_did in setup.toml already granted the temporary DID admin access, so PNM confirms it and replaces it with a fresh, permanent did:key.

Confirm

Test 1: PNM authenticates and the VTA reports healthy

pnm health

Client DID shows the permanent did:key PNM rotated to in Step 6. Expected output:

  VTA: my-vta
  DID: did:webvh:QmVE1TQeCtg3aavpTqasqencJpagRr8JKdGRyoZ5Qx6kRp:localhost%3A8100

── VTA ───────────────────────────────────────────
  DID           did:webvh:QmVE1TQeCtg3aavpTqasqencJpagRr8JKdGRyoZ5Qx6kRp:localhost%3A8100
                ✓ resolves (webvh)
                claims (unverified): did:scid:vh:1:QmVE1TQeCtg3aavpTqasqencJpagRr8JKdGRyoZ5Qx6kRp?src=localhost%3A8100
  Mode          REST
  URL           http://localhost:8100 (from DID)
  Service       ✓ ok

── Authentication ────────────────────────────────
  Client DID    did:key:z6Mk...
  Token         ✓ valid (expires in 899s)

── Mediator ──────────────────────────────────────
  (not configured)

The output above is abridged. Between Authentication and Mediator, pnm health also prints a Deployment section with these rows:

  • Sealed: whether the VTA is sealed.
  • Storage: whether the VTA’s storage is encrypted at rest.
  • TSP: whether TSP is enabled.
  • TEE: the TEE type and whether it is detected, on a VTA that runs in a TEE.
  • Version: the VTA version, shown to administrators only.
  • Restored: whether the VTA’s state was restored from a backup, shown to administrators only.

If you configured a mediator in Step 3, the Mediator section shows the mediator’s DID, a resolution check, and a trust-ping round-trip, followed by a VTA DIDComm section with a trust-ping to the VTA. A VTA TSP section is added when the VTA advertises TSP.

Troubleshooting

SymptomLikely causeFix
vta exits with vta_did is not configured[vta_did] was omitted or set to kind = "skip" in setup.tomlAdd a [vta_did] block with kind = "create_webvh", set overwrite_config = true and data_dir_exists = "delete" in setup.toml, and re-run vta setup --from setup.toml.
vta setup fails with config file … already exists or data directory … already holds a storeYou ran vta setup more than once with the same pathsSet both overwrite_config = true and data_dir_exists = "delete" in setup.toml for a clean re-run, or point config_path and data_dir at new paths.
pnm health fails with a BlockedHost error naming localhostThe VTA DID is hosted on localhost, and pnm resolves DIDs from public hosts only by defaultRun export VTA_ALLOW_PRIVATE_ENDPOINTS=1 in the terminal you run pnm from, as in Step 6.
pnm health shows ✗ unreachable on ServiceThe VTA process is not running, or public_url in setup.toml does not match where vta is actually listeningConfirm vta --config ./vta-data/config.toml is still running, and that the port matches public_url.
vta exits with server error: io error: Address already in use (os error 48)Another process is already bound to the configured portStop the process holding the port (lsof -i :8100 on macOS/Linux), or edit [server].port in config.toml and the matching port in public_url, then restart vta.
vta panics with failed to open store: Store(Locked)A previous vta instance using the same data_dir is still runningStop that instance, then restart vta.
vta setup fails with services.tsp = true, but mediator … advertises no TSPTransport serviceThe mediator does not route TSP, and TSP is on by defaultAdd [services] with tsp = false, or use a TSP-capable mediator, then set overwrite_config = true and data_dir_exists = "delete" and re-run vta setup --from setup.toml.
pnm commands fail to authenticateNo session stored in your OS keyring, or the DID was removed from the VTA’s ACLRun pnm auth status. If no session exists, run pnm vta delete my-vta, then pnm setup --name "my-vta", and repeat from Step 3 with overwrite_config = true and data_dir_exists = "delete" set in setup.toml.

Next steps

  Create your first context and signing key

  Back up and restore VTA state

  Glossary: the terms used across these pages and in pnm output.