TG-KEY-DISCOVERY
Key Discovery and Management Specification
public_key in https://trigguardai.com/.well-known/trigguard and as a JWK Set at https://trigguardai.com/.well-known/trigguard-keys.json (kid tgk_example_key_id, RSA 2048 / RS256). Production rotation, multiple-key sets, and the Ed25519 schema described below apply to the future Execution Authorization Gateway and are not yet shipped.
Abstract
This document specifies the key discovery mechanism for TrigGuard Execution Receipts. Public keys used to verify receipt signatures are published at a well-known URL, enabling offline verification without runtime dependency on TrigGuard services.
1. Overview
TrigGuard uses Ed25519 public-key cryptography for receipt signing. To verify signatures, clients need access to public keys. This specification defines:
- How keys are published
- Key metadata format
- Key rotation procedures
- Caching recommendations
2. Well-Known Endpoint
2.1 URL
GET /.well-known/trigguard-keys.json
2.2 Live URL
https://trigguardai.com/.well-known/trigguard-keys.json
Today this returns a single-entry JWK Set containing the Ed25519 conformance key. The schema described in subsequent sections (multiple keys, Ed25519, status lifecycle) applies to the future gateway.
2.3 Response Headers
| Header | Value |
|---|---|
Content-Type | application/json |
Cache-Control | max-age=3600 |
Access-Control-Allow-Origin | * |
2.4 Response Status
| Status | Meaning |
|---|---|
| 200 | Success |
| 503 | Service unavailable |
3. Response Schema
3.1 JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://www.trigguardai.com/schemas/keys.json",
"title": "TrigGuard Key Set",
"type": "object",
"required": ["keys", "issuer"],
"properties": {
"keys": {
"type": "array",
"items": { "$ref": "#/definitions/key" }
},
"issuer": {
"type": "string",
"format": "uri"
},
"documentation": {
"type": "string",
"format": "uri"
}
},
"definitions": {
"key": {
"type": "object",
"required": ["key_id", "algorithm", "public_key", "status"],
"properties": {
"key_id": { "type": "string" },
"algorithm": { "type": "string", "enum": ["Ed25519"] },
"public_key": { "type": "string" },
"status": { "type": "string", "enum": ["active", "deprecated", "revoked"] },
"created_at": { "type": "string", "format": "date-time" },
"expires_at": { "type": "string", "format": "date-time" },
"deprecated_at": { "type": "string", "format": "date-time" }
}
}
}
}3.2 Example Response
{
"keys": [
{
"key_id": "tg_prod_02",
"algorithm": "Ed25519",
"public_key": "MCowBQYDK2VwAyEAn8j/xb4Df2vB1sP+pzRw3Y5kLfKm9vE7h8QaXcW2rD0=",
"status": "active",
"created_at": "2026-03-01T00:00:00Z",
"expires_at": "2026-06-01T00:00:00Z"
},
{
"key_id": "tg_prod_01",
"algorithm": "Ed25519",
"public_key": "MCowBQYDK2VwAyEAz7Y2xK4pE8vN3mJ1cR9wB6fT5hL2qS0nG8jD4aX1kM0=",
"status": "deprecated",
"created_at": "2026-01-01T00:00:00Z",
"expires_at": "2026-04-01T00:00:00Z",
"deprecated_at": "2026-03-01T00:00:00Z"
}
],
"issuer": "https://trigguardai.com",
"documentation": "https://www.trigguardai.com/protocol"
}4. Key Fields
4.1 key_id
| Property | Value |
|---|---|
| Type | string |
| Format | tg_<environment>_<sequence> |
| Environment | prod, staging, dev |
Examples: tg_prod_01, tg_prod_02, tg_staging_01
4.2 algorithm
| Property | Value |
|---|---|
| Type | string |
| Allowed Values | Ed25519 |
Only Ed25519 is supported in this version of the specification.
4.3 public_key
| Property | Value |
|---|---|
| Type | string |
| Encoding | Base64 (RFC 4648) |
| Format | SubjectPublicKeyInfo (SPKI) |
4.4 status
| Status | Meaning | Verification |
|---|---|---|
active | Current signing key | Accept signatures |
deprecated | Previous key, still valid | Accept signatures |
revoked | Compromised or retired | Reject signatures |
5. Key Lifecycle
5.1 States
5.2 Rotation Schedule
| Event | Timeline |
|---|---|
| New key created | Q1, Q2, Q3, Q4 |
| Previous key deprecated | At new key creation |
| Deprecated key revoked | 90 days after deprecation |
5.3 Emergency Rotation
In case of key compromise:
- New key created immediately
- Compromised key revoked (not deprecated)
- Alert published to status page
- Affected receipts identified
6. Client Implementation
6.1 Key Fetching
Clients MUST:
- Request
/.well-known/trigguard-keys.json - Parse JSON response
- Store keys locally
- Handle HTTP errors gracefully
6.2 Caching
Clients SHOULD:
- Cache keys for at least 1 hour
- Refresh cache on signature verification failure
- Store cache persistently for offline verification
Clients MUST NOT:
- Cache keys indefinitely without refresh
- Ignore key expiration
6.3 Key Selection
When verifying a receipt:
- Extract
key_idfrom receipt - Look up key in cached key set
- If not found, refresh key cache
- If still not found, verification fails
6.4 Status Validation
| Key Status | Verification Action |
|---|---|
active | Accept |
deprecated | Accept |
revoked | Reject |
| not found | Refresh cache, then reject if still not found |
7. Examples
7.1 Fetching Keys (curl)
curl -s https://trigguardai.com/.well-known/trigguard-keys.json | jq
7.2 Key Lookup (Python)
import requests import json def fetch_keys(): response = requests.get( "https://trigguardai.com/.well-known/trigguard-keys.json", timeout=10 ) return response.json() def get_public_key(key_id): keys = fetch_keys() for key in keys["keys"]: if key["key_id"] == key_id and key["status"] != "revoked": return key["public_key"] return None
7.3 Full Verification (Python)
from nacl.signing import VerifyKey import base64 import json def verify_receipt(receipt, keys): # Find key key_id = receipt["key_id"] key_entry = next( (k for k in keys["keys"] if k["key_id"] == key_id), None ) if not key_entry or key_entry["status"] == "revoked": return False # Decode public key (last 32 bytes of SPKI) spki = base64.b64decode(key_entry["public_key"]) public_key_bytes = spki[-32:] # Construct payload payload = json.dumps({ "context_hash": receipt["context_hash"], "decision": receipt["decision"], "receipt_id": receipt["receipt_id"], "surface": receipt["surface"], "timestamp": receipt["timestamp"] }, separators=(',', ':'), sort_keys=True) # Verify signature = bytes.fromhex(receipt["signature"].replace("ed25519:", "")) verify_key = VerifyKey(public_key_bytes) try: verify_key.verify(payload.encode(), signature) return True except: return False
8. Security Considerations
8.1 Transport Security
Clients MUST:
- Use HTTPS for key fetching
- Verify TLS certificates
- Reject connections with invalid certificates
8.2 Key Pinning
Organizations with strict security requirements MAY:
- Pin specific keys in configuration
- Verify key hashes match expected values
- Alert on unexpected key changes
8.3 Offline Operation
For air-gapped environments:
- Keys can be distributed out-of-band
- Local key stores must be updated manually
- Document key hashes for manual verification
9. Conformance
Implementations MUST:
- Support the JSON schema defined in Section 3
- Respect key status (reject revoked keys)
- Use HTTPS for key fetching
Implementations SHOULD:
- Cache keys appropriately
- Handle key rotation gracefully
- Log key refresh events
References
Copyright © 2026 TrigGuard AI Limited. UK Company No. 16597262.