Skip to content

Publish /api/v1/identity — deployed lens's full hybrid federation key (Ed25519 + X25519 + ML-DSA-65 + ML-KEM-768) #20

Description

@emooreatx

Why

CIRISAgent 2.9.6 (and any future federation peer) needs the deployed lens's full hybrid federation identity to address it: Ed25519 + X25519 (transport KEX classical) + ML-DSA-65 + ML-KEM-768 (transport KEX PQ). Today there's no published endpoint — consumers would have to compose engine.local_public_key_b64() + engine.local_pqc_public_key_b64() via the internal API + derive X25519 from Ed25519 themselves + have no path to the ml-kem-768 half at all (it lives in the Edge runtime).

This issue covers the lens-side endpoint that publishes the aggregate. It's blocked on two prereqs that have to land first; see "chain" below.

What

GET /api/v1/identity — public, no auth, rate-limited + cached, returns the lens's federation hybrid identity bundle.

{
  "steward_key_id": "lens-steward",
  "steward_pqc_key_id": "lens-steward-mldsa",
  "ed25519_pubkey_b64": "kdtTafS49EAuPD3z8/zhwsoMp5d4LYs/CCZPEowiFUs=",
  "x25519_pubkey_b64": "kogV3xbe0Lh4JiKhiAlCproQrAYJVBZ77DRklrQs9Bw=",
  "ml_dsa_65_pubkey_b64": "GF2uj2PO2/LmUwM...(1952 b64)",
  "ml_kem_768_pubkey_b64": "...(1184 b64)" ,
  "did_key": "did:key:z6Mk...",
  "identity_hash": "sha256:...",
  "evaluated_at_unix_ms": 1750000000000
}

Implementation is a one-liner once the prereqs ship:

# api/identity.py (new module) or a section of accord_api.py
@router.get("/identity")
async def get_lens_identity() -> dict[str, Any]:
    engine = persist_engine.get_engine()
    if engine is None:
        raise HTTPException(503, "persist engine unavailable")
    return json.loads(engine.local_identity_aggregate())

Cache TTL = the lifetime of the worker process (the identity is stable; only changes on Engine re-init with a rotated steward key). Rate limit at the existing scoring-API tier (60/min/IP).

Caddy routing: /api/v1/identity is on the public list alongside /api/v1/accord/* and /api/v1/scoring/*. CLAUDE.md's Caddy section gets the line.

Chain — prerequisites

  1. CIRISPersist#198Engine.local_identity_aggregate() primitive lands the JSON bundle. Until this exists, the lens has no way to express the four keys in one call without composing two existing accessors + a derivation + a None.
  2. CIRISLensCore#43.1 — the host-Engine PyCapsule handshake that makes LensClient (and by extension install_relay) see the wheel-built host Engine. Until this lands, attempting install_relay(edge) in the cohabitation/pip setup throws RuntimeError: no process Engine.
  3. CIRISLens#18 §2 — wire init_edge_runtime(engine, ...) + install_relay(edge) into the lens-API startup hook. Until this lands, persist#198's ml_kem_768_pubkey_b64 stays None on the deployed lens (X25519 + Ed25519 + ML-DSA-65 are available unconditionally; ML-KEM-768 needs Edge).

When all three are in, this issue is a ~20-line lens-side patch:

  • Add api/identity.py (router) or extend accord_api.py
  • Wire into main.py's include_router
  • Update Caddyfile + CLAUDE.md "Public accord API" line to list /api/v1/identity

Test plan

  • Smoke against a docker-postgres harness with Edge wired: returns the 4-key bundle, all fields populated, identity_hash stable across requests.
  • Without Edge wired (pre-Adopt ciris-lens-core 1.0.0 — replace the in-tree cirislens-core; own the wire/scrub contract at the substrate #18 §2): returns the 3-key bundle (ed25519 + x25519 + ml_dsa_65), ml_kem_768_pubkey_b64: null.
  • Verify X25519 derivation against the live Ed25519: kogV3xbe0Lh4JiKhiAlCproQrAYJVBZ77DRklrQs9Bw= matches NaCl's crypto_sign_ed25519_pk_to_curve25519 over kdtTafS49EAuPD3z8/zhwsoMp5d4LYs/CCZPEowiFUs=.
  • Bridge probe round-trips: curl https://lens.ciris-services-1.ai/api/v1/identity returns the bundle agent 2.9.6 can address against.

References

  • CIRISPersist#198 (substrate primitive)
  • CIRISLensCore#43 / #43.1 (capsule handshake umbrella; cross-comment added)
  • CIRISLens#18 §2 (install_relay deployed-lens wiring)
  • CIRISVerify#47 (FederationHandshake X25519 + ML-KEM-768 hybrid KEX with HKDF-SHA256 binding — the wire shape the published identity has to be addressable against)
  • CIRISEdge#54 (closed; Edge consumed FederationHandshake on the transport side)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions