§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.

Protocol 66 Integration: The P66 manifest field 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:

LoANameVerification MethodRecommended 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 no loa → treat as loa: 2
  • If registry_tier: "community" and no loa → treat as loa: 1
  • If neither field present → treat as loa: 1

Registry Tiers

See §18 Federation Protocol for full registry tier details. Summary for identity context:

TierWho Verifies ItMax LoA It Can IssueJWT 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

Robots MAY reject LoA 1 for control commands. When 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.

ScopeRecommended Minimum LoARationale
discover1Read-only discovery; acceptable from any identified source
transparency1EU AI Act mandatory; cannot be gated on LoA
observer1Read-only telemetry stream
status1Status queries; low risk
chat2Conversation; email verification reasonable baseline
control2 (recommended)Physical action commands; verified identity required
training2Data collection; subject consent audit requires known identity
config2Configuration changes; verified identity required
safety (ESTOP_CLEAR, config safety overrides)3 (recommended)Safety-critical; hardware token strongly recommended
Default is LoA 1 (backward compatible). Robots ship with 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: 1 and acknowledge the reduced security posture in the Protocol 66 manifest's identity_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 loa field is trusted. Community registry JWTs claiming loa: 3 MUST 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_control using the cached JWT's loa field. The cached JWT must still be within its exp window.

See Also