Keytrace#
Identity verification for ATProto. Link your decentralized identity (DID) to external accounts like GitHub, LinkedIn, Instagram, DNS, and Mastodon with cryptographically signed attestations.
What is Keytrace?#
Keytrace allows Bluesky users to prove ownership of external accounts by:
- Creating a claim - Post a verification token to your GitHub gist, DNS TXT record, or other supported platform
- Verification - Keytrace fetches and validates the proof contains your DID
- Attestation - A cryptographic signature is created and stored in your ATProto repo as a
dev.keytrace.claimrecord
Claims are user-owned, portable, and stored directly in your ATProto repository.
Project Structure#
keytrace/
├── apps/
│ └── keytrace.dev/ # Nuxt 3 web application
├── packages/
│ ├── runner/ # Core verification library (@keytrace/runner)
│ └── lexicon/ # ATProto lexicon schemas
Development#
# Install dependencies
yarn install
# Start development server
yarn dev
# Run tests
yarn test
# Type checking
yarn typecheck
# Format code
yarn format
How Verification Works#
The runner package implements a recipe-based verification system:
- Service Providers match claim URIs to verification strategies
- Recipes define verification steps as JSON specifications
- Verification Steps are composable actions:
http-get,dns-txt,css-select,json-path,regex-match
Example flow:
User submits gist URL
→ Match URI to GitHub provider
→ Execute recipe: HTTP GET → CSS select → regex match for DID
→ Extract identity metadata (username, avatar)
→ Create attestation signature
→ Write dev.keytrace.claim to user's ATProto repo
ATProto Lexicons#
dev.keytrace.claim- Identity claim linking a DID to an external accountdev.keytrace.recipe- Verification recipe specificationdev.keytrace.key- Daily signing key for attestationsdev.keytrace.signature- Cryptographic attestation structure
Deployment#
Publishing Packages#
Use the deploy script to bump versions and publish all packages to npm:
./scripts/deploy.sh patch # 0.0.1 → 0.0.2
./scripts/deploy.sh minor # 0.0.2 → 0.1.0
./scripts/deploy.sh major # 0.1.0 → 1.0.0
This will:
- Bump versions in
@keytrace/runner,@keytrace/claims, and@keytrace/lexicon - Build all packages
- Publish to npm
- Create a git commit and tag
After running, push to remote:
git push && git push --tags
Adding a New Service Provider#
Want to add support for a new platform (e.g., GitLab, Codeberg, Tangled.) The key requirement is that the platform must have some way for users to publicly post text content that Keytrace can fetch and verify — things like profile bios, public posts, gists, comments, or files.
Proof of Identity Pattern#
Every service provider follows the same pattern:
- The user places a proof string (containing their DID) somewhere publicly readable on the service
- Keytrace fetches that public URL and checks that the proof string is present
- Metadata (username, avatar, profile URL) is extracted from the response
Good proof locations include: public gists/snippets, profile bios, DNS TXT records, public repos, pinned posts, or any content the user controls that's fetchable via HTTP.
You need to be careful that it's a place where only the identity can post. For example you can post a GitHub gist with a keytrace DID but the comments can also contain keytrace DIDs for other people. This could be used to make a false claim.
What You Need to Create#
All you need to touch is the packages/runner/ package — the web app picks up new providers automatically via the /api/services endpoint and a shared useServiceRegistry composable.
-
Create a provider file in
packages/runner/src/serviceProviders/implementing theServiceProviderinterface:id,name,homepage— basic metadatareUri— regex to match claim URIsui— wizard configuration (icon, instructions, proof template, input labels)processURI()— converts a matched URI into fetch + verification configpostprocess()— extracts identity metadata (username, avatar, profile URL)getProofText()— generates the proof string for the usertests— URI match test cases
-
Register it in
packages/runner/src/serviceProviders/index.ts -
Icon: Set
ui.iconto a Lucide icon name (e.g.,"github","globe","shield") and the web app renders it automatically. If your service needs a custom SVG icon, add a component toapps/keytrace.dev/components/icons/and register it in theiconMapinapps/keytrace.dev/composables/useServiceRegistry.ts. Setui.iconDisplay: "raw"for standalone SVGs (like npm/tangled) that shouldn't be wrapped in a circular badge.
Using Claude Code to Add a Provider#
From the repo root, try a prompt like:
Add a new service provider for [ServiceName]. Users will prove their identity by [describe the proof location, e.g. "creating a public snippet on GitLab containing their DID", or "adding their DID to their Codeberg profile bio"]. The proof URL format is [e.g. "https://gitlab.com/-/snippets/:id"]. Look at the existing providers in
packages/runner/src/serviceProviders/for the pattern — especiallygithub.tsfor an HTTP+JSON example ordns.tsfor a simpler one. Register the new provider in the index file and add URI match tests.
That should give Claude Code enough to:
- Create a new file in
packages/runner/src/serviceProviders/ - Implement the
ServiceProviderinterface (URI regex,processURI,uiconfig,getProofText, test cases) - Register it in
packages/runner/src/serviceProviders/index.ts
What Goes in the PR#
When you open your PR, please include:
- How to create a proof: Step-by-step instructions for how a user creates the public proof on the service (e.g., "go to gitlab.com/-/snippets/new, paste this, make it public")
- Example proof URL: A real or realistic example URL so I can test the flow
- Any API quirks: Rate limits, auth requirements, non-standard response formats, CORS issues, etc.
- Fetcher needs: The existing fetchers are
http,dns, andactivitypub. If your service needs something different, note that
License#
MIT