§8.7 — Registry Trust Anchors and Identity Verification v1.6
JWT tokens identify who issued a command, but before v1.6 there was no spec for how well the registry verified that identity. A JWT could be issued by a rogue registry to anyone. v1.6 adds Level of Assurance (LoA), registry tiers, and trust anchor discovery so robots in high-security environments can enforce meaningful identity verification.
min_loa_for_control (default 1, backward compatible) allows robots to declare a minimum identity assurance level for control-scope commands. This is enforced alongside the existing scope check — a JWT with the correct scope but insufficient LoA is rejected.
Level of Assurance (LoA)
LoA describes how thoroughly the issuing registry verified the human identity before issuing the JWT. v1.6 defines three levels:
| LoA | Name | Verification Method | Recommended Minimum Scope |
|---|---|---|---|
| 1 | Anonymous / Pseudonymous | No verification — self-asserted identity. Community registry default. | discover, observer, transparency |
| 2 | Email Verified | Verified email ownership (OTP or email link flow). Authoritative registry minimum. | status, control |
| 3 | Government ID / Hardware Token | Government-issued ID check OR FIDO2/WebAuthn hardware token bound to identity. | safety, config (safety overrides) |
LoA in JWT Claims
From v1.6, the loa field is required in all JWT claims:
// JWT payload — v1.6 required fields
{
"iss": "authoritative-registry.acme.com",
"sub": "user-uuid-alice",
"registry_tier": "authoritative",
"loa": 2, // NEW in v1.6 — REQUIRED
"scope": ["control"],
"aud": "rcan://rcan.dev/acme/arm/v1/unit-001",
"exp": 1741003600,
"iat": 1741000000,
"kid": "reg-key-2026a"
} Backward Compatibility
JWTs from v1.5 registries will not carry loa. v1.6 robots MUST apply the following default when loa is absent:
- If
registry_tier: "authoritative"and noloa→ treat asloa: 2 - If
registry_tier: "community"and noloa→ treat asloa: 1 - If neither field present → treat as
loa: 1
Registry Tiers
See §18 Federation Protocol for full registry tier details. Summary for identity context:
| Tier | Who Verifies It | Max LoA It Can Issue | JWT Self-Signed? |
|---|---|---|---|
| root | Pre-loaded trust anchor (e.g. RRF root key at rcan.dev) | 3 | Self-signed root (pre-loaded) |
| authoritative | Root registry — annual audit required | 3 | Signed by root registry key |
| community | Self-asserted — no external verification | 1 | Self-signed |
Community Registry Restrictions
min_loa_for_control: 2 is set in the Protocol 66 manifest, JWTs from community registries (which can only issue LoA 1) are automatically rejected for control scope. This is the recommended setting for any robot deployed outside a home environment.
Trust Anchor Discovery (DNSSEC)
Robots discover a registry's public key via DNSSEC TXT records at a well-known subdomain:
// TXT record format: _rcan-registry.<registry-domain>
// Note: _rcan-registry (identity) vs _rcan (federation — §18)
_rcan-registry.acme-registry.com. 3600 IN TXT
"v=rcan1; tier=authoritative; kfp=sha256:4a3f8c...; signed_by=rcan.dev"
// Fields:
// v=rcan1 — record version (required)
// tier= — "root" | "authoritative" | "community"
// kfp= — SHA-256 fingerprint of registry's Ed25519 signing key
// signed_by= — Issuing root registry domain (absent for root/community) Verification Flow
Robot receiving a JWT from acme-registry.com:
1. Check JWT iss field: "acme-registry.com"
2. DNS lookup (DNSSEC): _rcan-registry.acme-registry.com TXT
3. Parse TXT: tier=authoritative, kfp=sha256:4a3f..., signed_by=rcan.dev
4. Fetch JWKS: https://acme-registry.com/.well-known/rcan-keys.json
5. Find key matching JWT kid
6. Verify key fingerprint matches kfp in TXT record
7. (For authoritative/community) Verify signed_by chain against root public key
8. Validate JWT signature using matched public key
9. Enforce min_loa_for_control check against JWT loa field Pre-Loaded Root Trust Anchors
The RRF root public key MUST be pre-loaded into every RCAN-compliant robot at manufacture or first setup. It does not need to be fetched — it is the trust root itself.
// RRF root public key (pre-loaded; verify out-of-band at setup)
// Available at: https://rcan.dev/.well-known/rcan-root-key.json
{
"kid": "rcan-root-2026",
"alg": "Ed25519",
"use": "sig",
"x": "base64url-of-32-byte-ed25519-public-key",
"exp": 1893456000, // ~2030 — long-lived root key
"registry_tier": "root"
} Hardware Token Support (FIDO2/WebAuthn)
For LoA 3 verification, registries MUST support FIDO2/WebAuthn hardware token binding. This provides phishing-resistant identity verification even for remote robot commands.
FIDO2 Binding Flow
User Alice registers a hardware token (e.g. YubiKey) with acme-registry.com:
1. Alice initiates FIDO2 registration at https://acme-registry.com/auth/fido2/register
2. Registry generates a WebAuthn challenge
3. Alice's hardware token signs the challenge (origin-bound)
4. Registry stores: { user_id: alice-uuid, credential_id: "...", public_key_cose: "..." }
5. Registry sets loa=3 on Alice's account
When Alice requests a JWT:
6. Registry generates WebAuthn assertion challenge
7. Alice's hardware token signs the assertion
8. Registry verifies assertion using stored credential
9. Registry issues JWT with loa=3 and fido2_credential_id field FIDO2 JWT Extension Fields
// JWT claims for LoA 3 with FIDO2
{
"iss": "acme-registry.com",
"sub": "user-uuid-alice",
"registry_tier": "authoritative",
"loa": 3,
"fido2_credential_id": "base64url:...", // WebAuthn credential_id
"fido2_aaguid": "uuid", // Authenticator AAGUID (device class)
"scope": ["safety"],
"exp": 1741003600
} Identity Record Extension
Registry identity records (stored in the Robot Registry Foundation) gain these v1.6 fields:
// Registry identity record extension (v1.6)
{
"user_id": "user-uuid-alice",
"registry_id": "acme-registry.com",
"registry_tier": "authoritative",
"loa": 3,
"fido2_credential_id": "base64url:...", // null if no hardware token
"fido2_registered_at": 1741000000,
"email_verified": true,
"gov_id_verified": false // government ID check status
} LoA Enforcement Matrix
Recommended minimum LoA by scope. Robots MUST enforce the configured min_loa_for_control and MAY enforce higher minimums for specific scopes via local policy.
| Scope | Recommended Minimum LoA | Rationale |
|---|---|---|
discover | 1 | Read-only discovery; acceptable from any identified source |
transparency | 1 | EU AI Act mandatory; cannot be gated on LoA |
observer | 1 | Read-only telemetry stream |
status | 1 | Status queries; low risk |
chat | 2 | Conversation; email verification reasonable baseline |
control | 2 (recommended) | Physical action commands; verified identity required |
training | 2 | Data collection; subject consent audit requires known identity |
config | 2 | Configuration changes; verified identity required |
safety (ESTOP_CLEAR, config safety overrides) | 3 (recommended) | Safety-critical; hardware token strongly recommended |
min_loa_for_control: 1 to ensure existing v1.5 deployments are not broken. Operators SHOULD increase this to 2 for production deployments and to 3 for safety-critical commands.
Protocol 66 Manifest — min_loa_for_control
The Protocol 66 manifest gains a min_loa_for_control field in v1.6:
GET /api/safety/manifest
{
"protocol": 66,
"rcan_version": "1.6",
...
"min_loa_for_control": 2, // Default: 1 (backward compat). Range: 1-3.
"identity_config": {
"trusted_registry_tiers": ["root", "authoritative"], // community excluded
"require_loa3_for_safety": true, // safety scope min LoA
"fido2_required_for_loa3": true // hardware token enforcement
}
} Enforcement Logic
// Pseudocode: LoA check in message receive path
func check_loa(msg, jwt_claims, robot_config):
required_loa = robot_config.min_loa_for_control // default 1
// Elevate requirement for safety scope
if "safety" in msg.scope:
required_loa = max(required_loa, robot_config.min_loa_for_safety) // default 1
actual_loa = jwt_claims.get("loa", default_loa_for_tier(jwt_claims.registry_tier))
if actual_loa < required_loa:
raise LoAInsufficientError(
required=required_loa,
actual=actual_loa,
msg_id=msg.id
)
// → HTTP 403, error code LOA_INSUFFICIENT rcan-py Implementation
from rcan.identity import TrustAnchorVerifier, LoAEnforcer
# Verify a JWT's LoA claim against trust anchor
verifier = TrustAnchorVerifier(
root_public_key=RRF_ROOT_PUBLIC_KEY,
dns_resolver=dnssec_resolver,
min_loa=2, # Robot's configured minimum
)
claims = await verifier.verify(jwt_token)
# claims.loa == 2
# claims.registry_tier == "authoritative"
# Enforce LoA for an incoming message
enforcer = LoAEnforcer(robot_config.identity_config)
enforcer.check(msg, claims) # raises LoAInsufficientError if fails
# Check FIDO2 credential binding
if claims.loa == 3:
assert claims.fido2_credential_id is not None, "LoA 3 requires FIDO2 credential" Implementation Notes
- ESTOP is LoA-exempt: ESTOP (type 6 SAFETY, action=ESTOP) is always accepted regardless of LoA level. Protocol 66 invariant: ESTOP from any identified source is processed immediately. LoA checking applies to RESUME and ESTOP_CLEAR.
- Community registries in home environments: Home robots MAY use LoA 1 with community registries. Set
min_loa_for_control: 1and acknowledge the reduced security posture in the Protocol 66 manifest'sidentity_config.trusted_registry_tiers. - LoA downgrade attacks: A malicious registry cannot claim a higher LoA than its tier permits. Root and authoritative registries are verified by the trust chain before their JWT
loafield is trusted. Community registry JWTs claimingloa: 3MUST be rejected. - FIDO2 credential portability: FIDO2 credentials are origin-bound (to the registry domain). A credential registered at registry-1 cannot be used at registry-2. Cross-registry operations require the cross-registry consent flow (§18).
- LoA and offline mode: When a robot is in offline mode (§14), it continues to enforce
min_loa_for_controlusing the cached JWT'sloafield. The cached JWT must still be within itsexpwindow.
See Also
- §18 Registry Federation Protocol — registry tiers in cross-registry context
- Authentication — JWT structure and role-based access
- Safety & P66 Conformance —
min_loa_for_controlin Protocol 66 manifest - §8.6 Key Rotation — JWKS format used for registry key distribution
- §14 Offline Operation — LoA enforcement during offline mode