For Professional and Business users. Covers setup, signing, verification, encryption, plugins, and key management.
Individual email users (Gmail, Outlook, Yahoo, etc.). TrustDID hosts your DID — no domain or web hosting required.
Teams and organizations. Your domain hosts your DID with department keys, multi-signer chains, and compliance features.
After any setup, key rotation, profile change, or DID update, run the configuration checker to verify your environment is consistent:
trustdid-check
This performs an extensive validation of your local ~/.trustdid environment, including:
publicKeyMultibase values#active key vs live domain copySet TRUSTDID_ENV=dev (or run trustdid-setup --env dev once) to check against the dev environment. Exit code is 0 (pass) or 1 (errors).
trustdid-setup --managed YOUR_TOKEN YOUR_ID
No domain required. TrustDID hosts your DID at users.trustdid.ca/user/{uuid}/did.json.
# Managed mode (after web enrollment)
trustdid-setup --managed YOUR_TOKEN YOUR_ID
# Self-managed mode (interactive)
trustdid-setup business --self-managed
Creates the organisation's master key (#active). This is the root of trust for the entire company.
--pull, --regenerate, and --profile push changes to the service instantly. Ideal for teams that want zero server administration.Important: In both modes, you are responsible for deploying the DID documents to your own domain for did:web resolution to work:
yourdomain.ca/.well-known/did.json + did.json.vrfy — org identityyourdomain.ca/public/{id}/did.json + did.json.vrfy — staff/department profilesIn managed mode, the TrustDID dashboard provides a downloadable ZIP bundle of all staged documents for easy deployment. This step can also be automated with CI/CD or a scheduled pull from the API.
Both modes generate the same cryptographic keys and DID documents. You can switch from self-managed to managed later with --activate.
The installer will prompt for:
trustdid.ca)did.json (subject-asserted; see Trusting Registries)On completion, the following files are created:
~/.trustdid/
config.json ← signing configuration
did.json ← DID document (deploy this)
dns_records.txt ← TLSA records (if DNSSEC enabled)
keys/
active/
signing.pem ← Ed25519 private key (keep secret!)
signing.pub ← Ed25519 public key
encryption.pem ← X25519 private key (keep secret!)
encryption.pub ← X25519 public key
signing_pq.pem ← ML-DSA-44 secret key (keep secret!)
signing_pq.pub ← ML-DSA-44 public key
backup/
trustdid_backup_*.tar.gz
Every TrustDID verification answers two independent questions. Document authenticity — is it genuine? — is objective and identical for every verifier: Verified / Inconsistent / Failed / Unsigned. Trusted signer — who vouches for it? — is answered relative to the trust registries you rely on: Yes / No / Unknown. The math tells you it's genuine — people tell you whether to trust it.
TrustDID Document Verification
==============================
Signer: did:web:acme.ca#active
Timestamp: 2026-08-12T14:03:22Z
Document authenticity Verified
Trusted signer Yes
Authentic and trusted
Verification normally fetches the signer’s DID document and asks your trust registries live. When neither can be reached, TrustDID falls back to copies it stored on an earlier run, kept under ~/.trustdid/cache/, so a laptop offline for a weekend still gets an answer instead of “could not resolve”.
A cached answer never passes itself off as a live one. The headline says so, and the report names every source it fell back to and how old the copy is:
Document authenticity Verified (from cache)
Trusted signer Yes
Offline: some checks used stored copies, not live sources.
- DID document: cached copy, 14h ago
A key revoked, or a registry entry withdrawn, after those were
stored would not be visible here. Re-run with a network
connection before relying on this for anything consequential.
Stored copies are usable for 72 hours. Past that they are ignored entirely and verification reports that it could not check — beyond a few days an unnoticed revocation is too likely for a snapshot to be worth trusting. Set TRUSTDID_CACHE_MAX_AGE to change the window (any duration, e.g. 24h); zero or a negative value disables cached reads altogether, so verification always requires a live source.
A document can be perfectly authentic and entirely untrusted — the signature is genuine, but no registry you rely on vouches for the signer. That is an honest answer, not a low score. See Verifying Documents for the full report format.
The "Trusted signer" answer comes from the trust registries your verifier consults — a registry vouches for the signers it lists. You decide which registries to rely on:
# Add a registry your verifier will consult (interactive)
trustdid-setup --add-trusted-registry
# List the registries you currently rely on
trustdid-setup --list-trusted-registries
# Stop relying on a registry
trustdid-setup --remove-trusted-registry DID
Provisioning asks this too: trustdid-setup --managed and business --self-managed end with "Add a trust registry your verifier should trust?" and run the same prompts. The interactive flow resolves the registry's DID, probes its TRQP v2.0 endpoint, and writes the entry to trustRegistries in your local ~/.trustdid/config.json. After adding one, run trustdid-check: it re-validates all configured registries — resolves each registry DID, probes TRQP v2.0, and reports public/private/token status.
In a business, set the list once. The admin lists the registries on the dashboard's Trusted registries page and every member's machine inherits it on the next sync — see Members & Shared Profiles. The commands above are for a Professional identity, a self-managed installation, or a registry only this machine should trust.
--add-trusted-registry — adds a registry your verifier consults about others. This is what drives the "Trusted signer" answer you see.--add-trust-registry — publishes your own claimed affiliation (a TrustRegistry service entry in your own did.json). Verifiers treat it as subject-asserted: you chose it, so it carries no trust weight by itself.Sign the DID document to produce a verification manifest (did.json.vrfy), then deploy both files to the web server.
# Sign the DID document
trustdid-sign ~/.trustdid/did.json
This generates did.json.vrfy alongside did.json. Deploy both to your web server:
https://trustdid.ca/.well-known/did.json
https://trustdid.ca/.well-known/did.json.vrfy
did:web:trustdid.ca
If you enabled DNSSEC during setup, the installer generated a dns_records.txt file. Add these DNS records to anchor your DID public key in the DNS system:
_did.trustdid.ca. IN URI 1 0 "did:web:trustdid.ca"
_did.trustdid.ca. IN TLSA 3 1 0 302a300506032b65700321...
These records strengthen the binding between your signing key and your domain. Without them, the key is merely declared by your DID document; with matching _did URI and TLSA records under your domain, verifiers report the binding as corroborated by DNS — and DNSSEC-signing those records is designed to let verifiers validate that corroboration cryptographically. Stronger binding makes impersonating your domain harder. It says nothing about trust: whether you are a trusted signer is answered by the registries that vouch for you.
If DNSSEC was not live when you first ran setup, you can embed the anchor later:
trustdid-setup --update-dnssec
After key rotation or domain changes, regenerate the URI and TLSA DNS records from your existing configuration:
trustdid-setup --dnsrecords
This reads your current config.json and did.json, and outputs fresh DNS records to dns_records.txt. No keys are modified.
Each department can have its own signing key. Use the --profile subcommand to add department or personal keys to an existing business config.
trustdid-setup business --profile
The installer detects the existing DID and prompts:
Found existing DID document with 3 key(s): active, active-encrypt, active-pq
Org key (#active) found. DNSSEC and Trust Registry
already configured by IT - no changes needed.
Key purpose:
[1] Department key (e.g. finance, hr, sales, legal)
[2] Personal key (nickname or auto-generated ID)
Select (1/2): 1
Department name (one word, lowercase letters/digits): finance
Key ID: #finance
The updated did.json now contains both the org key and the department key. The IT admin must:
did.json from the employeetrustdid-sign did.jsondid.json and did.json.vrfy to the web serverfinance, hr, sales, legal, dev
Adding department or personal keys merges the new profile into the existing config.json without
overwriting other profiles. The active profile and existing email mappings are preserved.
Personal keys work the same way as department keys, but are tied to an individual rather than a department. Select option [2] Personal key during setup.
Key purpose:
[1] Department key
[2] Personal key
Select (1/2): 2
Personal key identifier:
[1] Nickname (e.g. alice, laptop, home)
[2] Short UUID (auto-generated)
Select (1/2): 1
Nickname: alice
Key ID: #alice
The same re-deploy workflow applies: IT collects the updated DID document, re-signs, and re-deploys.
A business is more than one laptop. From the dashboard, the admin invites the people of the organisation; each of them gets their own identity did:web:<your-domain>:public:<label> and their own token, and their keys are generated on their own machine and never leave it. Role identities such as #finance are shared profiles: created once by the admin, handed to chosen members encrypted with each person's own key, and installed automatically by the service on their machines.
#finance), tick the holders, run the publish command once on your machine.trustdid-service (Step 10) so shared profiles and registries arrive on their own; without it, trustdid-setup --sync by hand.trustdid-check on any machine shows what is installed and what the business reports.Sign in to the dashboard and open Members. Add a person by email, or import a CSV (email, name, label, visibility) and confirm the preview. Each member is private (an opaque 8-character label) or public (a readable label you choose). Either way only the identity document is published; no list of members or email addresses is served. The seat count follows your licence.
The member receives an invite email with one command, also shown once on the page. It works once and expires in 7 days — treat it like a password-reset link.
An address outside your domain is accepted after a confirmation. From the same page you can resend an invite that expired, suspend a member (their token stops working and their machine learns it on the next sync; signing as them is refused until you reactivate), or remove them (see Day to day). The page shows each member's last sync, so you can see whose service is not running.
On the member's machine, install the software bundle and run the command from the invite:
trustdid-setup --managed <INVITE> <BUSINESS-ID>
This generates the member's Ed25519, X25519 and ML-DSA-44 keys locally, publishes their identity document, exchanges the invite for their personal token, and pulls the organisation's licence. From then on:
trustdid-sign <document> # signs as the member
trustdid-setup --sync # ask the business what is new (the service does this every 15 minutes)
trustdid-setup --sync --force # same, ignoring the cached answer
trustdid-check # shows the member, the last sync, and installed shared profiles
Running the invite command a second time on the same machine does nothing and says so. Enrolling the same account on a second machine is refused, because the keys published for that identity live on the first one; to move to a new machine, ask your admin for a fresh invite (which retires the old identity) or, for a Professional identity, rotate the keys from the new machine with --replace-keys after restoring your backup.
Each TrustDID account you enrol on a machine — a Professional identity, a business held as its administrator, a business joined as a member — is an identity in config.json, with its own token and licence. Enrolling a second one adds it beside the first. The profile you sign with decides which identity's licence and credential are used; the active profile is the default.
trustdid-setup --identities # what this machine holds, and which profiles sign under each
trustdid-setup --active jackvid # sign as this profile (and its identity) by default
trustdid-setup --remove-identity acme.ca # forget an account no profile uses any more
On the dashboard's Shared profiles page, create a profile (for example finance), list the email addresses that sign as it, and tick the members who should hold it. The page shows a one-time command; run it on the admin's machine:
trustdid-setup business --shared-profile finance --action-token <TOKEN>
That machine generates the profile's keys, publishes its document, signs the key bundle with the admin's own identity, encrypts one copy per holder to that person's published key, and uploads only the ciphertext. TrustDID never sees the private keys. Each holder's service installs the profile on its next poll after verifying the signature and checking the keys against the published document. Holders can then sign as it:
trustdid-sign --profile finance invoice.pdf
trq_ query token from the registry owner. Every member's service installs the list on its next sync, marked managed by the business; a registry you disable or remove disappears from their machines the same way. Members cannot edit those entries (--add-trusted-registry refuses them and points here), and anything they added themselves stays theirs.Every command talks to the stack recorded in config.json (trustdid-setup --env shows it, --env dev changes it, TRUSTDID_ENV overrides it for one command). A fresh machine takes it from the invite token itself, so the command from the email needs nothing more.
Sign any file type — PDF, DOCX, images, spreadsheets, anything:
# Basic signing (uses active profile)
trustdid-sign contract.pdf
# With metadata
trustdid-sign invoice.pdf --reason "Monthly invoice" --location "Ottawa, ON"
# Using a specific profile
trustdid-sign report.pdf --profile finance
# TUI mode (animated terminal UI)
trustdid-sign report.pdf --tui
Re-signing a file that already has a .vrfy asks what to do. Say so up front with --if-exists — ask, replace or keep. In a script or any non-interactive run there is nobody to ask, so the existing manifest is replaced:
# Never prompt; overwrite the old manifest
trustdid-sign contract.pdf --if-exists replace
# Never prompt; leave an existing manifest untouched
trustdid-sign contract.pdf --if-exists keep
This produces a .vrfy file alongside the original:
contract.pdf ← original document (unchanged)
contract.pdf.vrfy ← verification manifest (send both)
The .vrfy file contains:
| Argument | Description |
|---|---|
document |
Path to file to sign (required) |
--profile, -p |
Signing profile name (default: activeProfile from config) |
--reason, -r |
Reason for signing (optional, embedded in manifest) |
--location, -l |
Location information (optional) |
--tui |
Interactive terminal UI with animated signing steps |
Verify a signed document — all verification happens locally on your computer:
# Basic verification
trustdid-verify contract.pdf
# The .vrfy file is auto-detected (contract.pdf.vrfy)
# Or specify it explicitly:
trustdid-verify contract.pdf contract.pdf.vrfy
# TUI mode (animated terminal UI)
trustdid-verify contract.pdf --tui
# Output result as JSON (for scripting)
trustdid-verify contract.pdf --json
# Skip document hash check (email body verification)
trustdid-verify contract.pdf --skip-hash
The verifier checks:
The result reports the two axes separately: Document authenticity (Verified / Inconsistent / Failed / Unsigned) and Trusted signer (Yes / No / Unknown). For scripting, trustdid-verify exits 1 only when authenticity fails — "Trusted signer: No" still exits 0.
| Argument | Description |
|---|---|
document |
Path to document to verify (required) |
signature |
Path to .vrfy file (optional, auto-detected) |
--tui |
Interactive terminal UI with animated verification steps |
--json |
Output result as JSON (for scripting and automation) |
--skip-hash |
Skip document hash check (for email body verification) |
Encrypt a document for a specific recipient using their public encryption key:
# Encrypt for a recipient
trustdid-encrypt confidential.pdf alice@trustdid.ca
# Specify which key to use (skip interactive menu)
trustdid-encrypt report.pdf bob@example.com --key finance-encrypt
# TUI mode (animated terminal UI)
trustdid-encrypt --tui
This produces two files to send to the recipient:
confidential.pdf.enc ← encrypted data
confidential.pdf.meta ← encryption metadata
The recipient decrypts with their private key:
# Auto-detect profile from .meta
trustdid-decrypt confidential.pdf.enc
# Specify profile manually
trustdid-decrypt confidential.pdf.enc --profile finance
# TUI mode (animated terminal UI)
trustdid-decrypt --tui
| trustdid-encrypt | |
|---|---|
document |
File to encrypt |
email |
Recipient email address |
--key, -k |
Recipient key ID (skips interactive selection) |
--pub |
Direct recipient X25519 public key PEM file (bypass DID resolution) |
--out |
Output file path (default: <file>.enc) |
--profile, -p |
Sender profile name (for .meta) |
--tui |
Interactive terminal UI |
| trustdid-decrypt | |
encrypted |
Encrypted .enc file |
--meta |
Explicit .meta file path (default: auto-detect) |
--profile, -p |
Profile name for decryption (auto-detected from .meta) |
--key |
X25519 private key PEM file (bypass profile detection) |
--out |
Output directory (default: same as .enc file) |
--tui |
Interactive terminal UI |
Sign all files in a directory at once — ideal for website content, document libraries, or release packages:
# Sign all files in a directory
trustdid-sign-dir /var/www/html --profile active --reason "Release v2.1"
# Recursive (include subdirectories)
trustdid-sign-dir ./content --recursive --profile finance
# Dry run (list files without signing)
trustdid-sign-dir ./site --recursive --dry-run
# Exclude specific extensions
trustdid-sign-dir ./site --recursive --exclude .log --exclude .tmp
The script automatically skips: .vrfy, .meta, .enc, .pem, .pub, .key, .pyc files, and directories like .git, node_modules, __pycache__.
| Argument | Description |
|---|---|
directory |
Directory containing files to sign |
--profile, -p |
Signing profile name |
--reason, -r |
Reason for signing |
--location, -l |
Location information |
--recursive |
Include subdirectories |
--dry-run, -n |
List files without signing |
--exclude |
Additional extension to skip (repeatable) |
On a member's machine the service is also the sync agent. Every 15 minutes it asks the business what has changed for this member and installs shared profiles and trusted registries, pulls a renewed licence, and notes a suspension. POST /sync on the API port runs a pass now; /health reports the last sync under member. A machine without the service does the same with trustdid-setup --sync.
The TrustDID service runs a local REST API (port 8080) and a static file server (port 8000) that power the Outlook and Thunderbird plugins for email signing and verification.
# Install as Windows service (requires admin)
trustdid-service install
# Start the service
trustdid-service start
# Stop the service
trustdid-service stop
# Check whether it is running
trustdid-service status
# Uninstall
trustdid-service uninstall
# Run in foreground (all platforms)
trustdid-service run
# Custom ports
trustdid-service run --api-port 8443 --static-port 8444
# Print systemd unit template (Linux)
trustdid-service --systemd
Once the service is running, install the Outlook add-in manifest or the Thunderbird .xpi extension to enable automatic email signing and one-click verification.
| Argument | Description |
|---|---|
action |
run | install | start | stop | uninstall |
--api-port |
Flask API port (default: 8080) |
--static-port |
Plugin static file server port (default: 8000) |
--systemd |
Print systemd unit template and exit |
After setup, use the built-in test tools to confirm everything is working correctly:
# Test local environment (keys, config, DID document)
trustdid-test-local
# Test remote resolution (fetches your DID from the web, verifies signatures)
trustdid-test-remote
trustdid-test-local validates your local ~/.trustdid environment: config structure, key files, DID document consistency, and profile mappings — all without network access.
trustdid-test-remote performs an end-to-end test: resolves your DID from your published domain, fetches the public keys, and verifies that your local keys match what's published. This confirms that verification will succeed for anyone on the internet.
Pull the latest did.json and did.json.vrfy from the web server (useful after IT deploys changes):
trustdid-setup --pull
If did.json.vrfy has not been deployed yet, the pull will still succeed — only the did.json is required.
The reverse, for managed mode: upload the local did.json, did.json.vrfy and did.json.vrfy.log back to the service. Name a profile to push just that one.
# Push every profile
trustdid-setup --push
# Push one profile
trustdid-setup --push finance
Post-quantum protection is not optional and not an add-on. Every profile gets an ML-DSA-44 signing key when it is created, alongside Ed25519 and X25519, and rotation replaces all three together. There is no supported configuration in which a profile signs without a post-quantum key.
# Rotate a profile — all three key types together
trustdid-keytool --rotate finance
# Create a new key set (Ed25519 + X25519 + ML-DSA-44)
trustdid-keytool --generate --dir ~/.trustdid/keys/finance --label finance
A profile with no post-quantum key predates ML-DSA-44 support and should be recreated in full — remove it and run setup again — rather than patched a key at a time. A profile is a matched set: its keys, its DID document entries and its history are meant to have been issued together, and grafting one key onto an older set leaves a profile no version of the tooling would have produced. Signatures made with the old keys keep verifying either way, through keyHistory.
Use trustdid-keytool to manage your cryptographic keys:
# List all profiles with key ages
trustdid-keytool
# Generate fresh keys (Ed25519 + X25519 + ML-DSA-44)
trustdid-keytool --generate --dir ~/.trustdid/keys/finance --label finance
# Rotate keys: archive old, generate new, update DID
trustdid-keytool --rotate finance
Every rotation is recorded in keyHistory inside did.json and in a tamper-evident, append-only sidecar log, did.json.vrfy.log — each entry dual-signed (Ed25519 + ML-DSA-44) and hash-chained. Old signatures keep verifying against the key that was active at signing time. Deploy did.json.vrfy.log alongside did.json and did.json.vrfy, and verify the chain with:
trustdid-verify --verify-history
Each history entry records why the key changed: scheduled-rotation, key-compromise, key-loss-recovery, policy-change, or initial-setup.
Rotation defaults to scheduled-rotation. Pass --rotation-reason to record what actually happened — it accepts the same values, on both tools, and the entry is permanent:
trustdid-keytool --rotate finance --rotation-reason key-compromise
trustdid-keytool --rotate active --rotation-reason policy-change
trustdid-keytool --rotate is the only way to rotate keys. It retires the Ed25519, X25519 and ML-DSA-44 keys together, records the reason in keyHistory and the .vrfy.log, and issues a matched replacement set. trustdid-setup provisions identities; it does not rotate them.
did.json with trustdid-signdid.json and did.json.vrfy#active key, update the TLSA DNS recordkeyHistory and did.json.vrfy.logThe TrustDID browser extension for Chrome, Edge, and Firefox lets you verify signed web pages and documents, and sign emails in Gmail directly from your browser.
chrome://extensions/ (or edge://extensions/)chrome-edge/ folder from the downloads pageabout:debugging#/runtime/this-firefoxmanifest.json from the extracted firefox/ folderWhen you visit a page that has a .vrfy file, the icon turns blue. Click it to verify — it turns green when the signature is valid. In Gmail, the extension adds sign and verify buttons directly in the compose and read views.
Your TrustDID email identity creates a DID that looks like this:
did:web:users.trustdid.ca:user:52ab2c63
This resolves to a publicly accessible DID document at:
https://users.trustdid.ca/user/52ab2c63/did.json
did:web: — the DID method (web-based, no blockchain)users.trustdid.ca — the dedicated domain for hosted individual identities (TrustDID hosts it for you):user:52ab2c63 — your unique 8-character identifierShare your DID with anyone who needs to verify your documents or send you encrypted files.
Only the public keys are published in the DID document. Your signing and encryption private keys stay in ~/.trustdid/keys/ on your machine.
To remove your email identity and delete the remote DID document from users.trustdid.ca:
# Remove by email address (uses stored token)
trustdid-setup --remove you@example.com
# Remove by UUID
trustdid-setup --remove 52ab2c63
The removal process:
config.jsonThe --remove command does not delete your private keys from ~/.trustdid/keys/.
Previously signed documents remain verifiable even after removal.
| Command | Purpose | Key Arguments |
|---|---|---|
trustdid-setup |
Setup — create org/dept/personal/member keys and DID; sync a member machine; publish shared profiles; manage trusted registries | --managed [--replace-keys], --sync [--force], --env, --active, --identities, --remove-identity, --profile, --pull, --push [PROFILE], --remove, --update-dnssec, --dnsrecords, --add-trust-registry, --add-trusted-registry, --list-trusted-registries, --remove-trusted-registry DID, business --shared-profile, business --reconcile-did |
trustdid-keytool |
Key management — generate, rotate, list keys | --generate, --rotate <profile>, --list, --dir, --label |
trustdid-sign |
Sign any document | document, --profile, --reason, --location, --local, --tui |
trustdid-verify |
Verify a signed document | document, --tui, --json, --skip-hash, --verify-history |
trustdid-sign-dir |
Batch sign a directory | directory, --recursive, --dry-run, --exclude |
trustdid-encrypt |
Encrypt for a recipient | document, email, --key, --pub, --out, --tui |
trustdid-decrypt |
Decrypt a received file | encrypted, --profile, --key, --meta, --out, --tui |
trustdid-check |
Validate local environment — config, keys, DID consistency, trusted registries | --tui |
trustdid-test-local |
Test local config, keys, and DID document | --api-port, --static-port, --no-key-match |
trustdid-test-remote |
End-to-end test — resolve DID from web, verify keys match | --base, --did, --skip-register, --skip-web |
trustdid-service |
Local API + plugin server; on a member machine, polls the business for shared profiles | run/install/start/stop, --api-port, --static-port |
You're all set. Your organisation now has self-sovereign document signing, verification, and encryption — with post-quantum cryptography enabled by default.