Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

API Publish Handlers: KIDs, Manifests, and Governance

Crate: kinetic-daemon Stage: 9 Reading time: 20 minutes Depends on: kinetic-kid (Stage 3), kinetic-core (Stage 7), kinetic-network (Stage 8)


What Is This?

This file documents the second half of the API publish handlers in kinetic-daemon/src/api/publish.rs (Lines 332-663).

While the first half of this file dealt with cryptographic commitments and reveals for the decentralized naming system (mapping human-readable names to public keys), this half is fundamentally about publishing higher-level network structures into the global state.

Specifically, this file contains the HTTP handlers that local clients, frontends, or command-line tools hit when they need to publish:

  • KIDs (Kinetic Identifiers): The core decentralized identity documents that define a user or an entity on the network.
  • Manifests (App Configurations): The application-specific connection routes and service definitions tied to a specific KID.
  • Governance Messages: System-wide administrative commands that update quorum rules, slash malicious actors, or migrate network parameters.

These endpoints act as the secure boundary between a local user’s requests and the broader kinetic peer-to-peer network.

They receive JSON payloads, cryptographically verify every single signature involved, ensure the user has the correct authorization, and then push that data out to the network.

They do this either by storing it redundantly in the Distributed Hash Table (DHT) or broadcasting it globally via the Gossipsub protocol.


Why Kinetic Needs This

The Kinetic daemon is the gateway.

The global peer-to-peer network has no central authority to validate data. If a node simply accepted any data stream and pushed it into the DHT, the network would collapse within minutes.

It would be trivial for a malicious actor to flood the DHT with fake identities, overwrite legitimate application manifests, or broadcast fake governance rules that disrupt network consensus.

These API handlers act as the first line of defense for the entire ecosystem. Before a daemon even asks the P2P network to store a piece of data, it rigorously validates the cryptography locally.

This architecture guarantees three critical invariants:

  1. Identity Integrity: You cannot publish an AuthorizedKid unless you prove you own the cryptographic keys associated with the registered human-readable Name attached to it.

  2. Application Integrity: You cannot publish a Manifest for an application unless you prove the signature on the manifest was generated by an authorized subkey listed inside the parent KID.

  3. Network Integrity: You cannot publish a Governance change unless the message is signed by a valid quorum key recognized by the current global governance state.

Without these specific handlers, local clients would have no secure, standardized HTTP interface to inject their identities, apps, and administrative actions into the kinetic global state.

The daemon handles the heavy lifting of P2P networking and complex cryptographic verification so that the client application doesn’t have to reinvent the wheel.


How It Works

This file contains three primary asynchronous handler functions.

Each follows a strict pipeline:

  • Role-Based Access Control (RBAC) check
  • Cryptographic Verification
  • Serialization
  • Network Broadcast.

We will dissect exactly how each step operates inside the Kinetic ecosystem.

1. Publishing a Kinetic Identifier (KID)

The handle_publish_kid function is the endpoint responsible for taking a user’s new or updated identity document and pushing it into the global DHT so that other peers can resolve it.

-> See: kinetic-daemon/src/api/publish.rs — Lines 326 to 430

Step 1: Role-Based Access Control (RBAC) The handler signature begins by extracting the Role from the Axum extension layers.

The API token that was used to authenticate this HTTP request must possess either the Publish or Admin role. If it does not, the handler immediately returns an Err containing a StatusCode::FORBIDDEN.

This ensures that read-only API keys cannot be abused to publish data.

Step 2: Inner Signature Verification The payload received from the client is an AuthorizedKid. This is an outer wrapper containing a KidDocument and a signature.

The very first cryptographic check is a call to: auth_kid.kid_doc.verify().

This verifies the mathematical consistency of the identity itself. Within a KID document, there is a primary identity key, and there may be several subkeys (for app signing, encryption, etc.).

This verify() call ensures that the primary identity key signed the document encompassing all of those subkeys. If this fails, the KID is inherently corrupt.

Step 3: Wrapper Signature Verification against Local State In Kinetic’s architecture, an AuthorizedKid is permanently linked to a registered human-readable Name (e.g., saif.kyn).

The network must ensure that the person trying to publish this KID actually owns the Name they are claiming. This is done by checking the “wrapper” signature (auth_kid.owner_signature).

-> See: kinetic-daemon/src/api/publish.rs — Lines 352 to 384

The node attempts to look up the NameRecord for the claimed name in its fast local key-value storage.

It constructs a lookup key by appending the name to: kinetic_core::constants::DB_PREFIX_REVEAL.

If it finds the record, it deserializes it into a NameRecord. Then, it performs a manual ML-DSA signature check.

It creates an ml_dsa::VerifyingKey from the public key slice in the record. It creates an ml_dsa::Signature object from the owner_signature slice in the request.

Crucially, it verifies this signature against: auth_kid.signable_bytes(NETWORK_ID).

Incorporating the NETWORK_ID into the signable bytes is a brilliant architectural decision to prevent cross-network replay attacks. A signature meant for the Kinetic Testnet cannot be intercepted and replayed on the Kinetic Mainnet, because the NETWORK_ID baked into the hashed bytes will differ.

Step 4: The Fallback “Allow” Decision What happens if the local node doesn’t have the NameRecord cached in its local database?

-> See: kinetic-daemon/src/api/publish.rs — Lines 385 to 389

If the node hasn’t seen the Reveal transaction for this name yet, it has no cryptographic way to verify the signature locally.

Instead of blocking the user, it logs a warning: "Could not find local reveal... Forwarding to DHT anyway".

It deliberately sets is_authorized = true and allows the request to proceed.

Why? Because in an eventually consistent P2P network, local state can lag behind global state. If an API node with a stale local cache blocked valid user activity, the user experience would degrade severely.

The architecture assumes that if the signature is actually invalid, the P2P network peers receiving the DHT PUT request will reject it.

Step 5: Serialization and DHT Publication Once authorized (or given the benefit of the doubt), the AuthorizedKid is serialized back into JSON bytes.

The node then calls: state.network.publish_redundant_payload(&fqdn, payload_bytes).

The DHT key used for this storage is the fully qualified DID string of the KID (e.g., did:kyn:12345...).

The redundant aspect means the network layer will attempt to store this on the Kademlia network’s K-closest peers to that hash, ensuring the data persists even if several peers go offline.

2. Publishing an Application Manifest

The handle_publish_manifest function processes application configurations.

While a KID defines who an entity is, a Manifest defines what services they offer, what APIs they expose, and what network addresses they can be reached at.

-> See: kinetic-daemon/src/api/publish.rs — Lines 438 to 576

Step 1: Local Wrapper Verification Exactly like the KID handler, the manifest handler first checks the owner_signature on the AuthorizedManifest wrapper against the local NameRecord to ensure the overarching name owner authorized this action.

Step 2: Network Resolution (The Blocking Pause) A Manifest cannot be verified in a vacuum.

A manifest asserts that it belongs to a specific KID, and it must be signed by an application key that is listed inside that parent KID document.

-> See: kinetic-daemon/src/api/publish.rs — Lines 504 to 514

To verify this linkage, the local node must fetch the current state of the KID from the global network.

It calls state.network.resolve_redundant_payload(did_str).await.

This means the API request physically pauses its execution thread, queries the Kademlia DHT, and waits for a response from remote peers.

If the network is offline, or if the KID has not yet propagated through the DHT, this lookup will fail, and the API request will terminate with a 404 NOT FOUND or 503 SERVICE UNAVAILABLE.

Step 3: Backwards-Compatible Deserialization -> See: kinetic-daemon/src/api/publish.rs — Lines 516 to 531

Once the byte payload for the KID is returned from the DHT, the node attempts to deserialize it.

It first attempts to parse it as an AuthorizedKid (the modern standard). However, to support network migrations and older testnet data, if that fails, it falls back to attempting to deserialize it as a legacy, raw KidDocument without the authorization wrapper.

Step 4: Manifest Cryptographic Verification With the parent KID document secured from the network, the code executes: auth_manifest.manifest.verify(&kid_doc).

This mathematical operation proves that the digital signature attached to the Manifest was produced by one of the valid subkeys listed in the KID document.

This prevents malicious actors from publishing fake manifests that redirect a legitimate application’s traffic to a rogue IP address.

Step 5: Derived DHT Key Generation and Publication -> See: kinetic-daemon/src/api/publish.rs — Lines 542 to 575

Where does a Manifest live on the Distributed Hash Table?

It cannot be stored under the exact same DHT key as the KID document itself, because doing so would cause a collision and overwrite the identity data.

Instead, the node takes the KID’s DID string, appends the string #manifest to it, and runs the combined string through a SHA-256 hash using the sha2::Digest trait.

The resulting raw bytes are hex-encoded to form a clean ASCII string. This derived hash becomes the new DHT key.

The manifest payload is then published redundantly across the network under this distinct derived key.

3. Publishing a Governance Message

The handle_publish_governance function is arguably the most sensitive endpoint in the file.

It is responsible for submitting global, system-wide state changes to the Kinetic network, such as slashing a malicious validator, updating block parameters, or triggering a hard fork migration.

-> See: kinetic-daemon/src/api/publish.rs — Lines 584 to 663

Step 1: In-Memory Global State Lock Governance changes affect the core operational rules of the node itself, not just user data.

-> See: kinetic-daemon/src/api/publish.rs — Lines 599 to 620

The code introduces a distinct block scope { ... }. Inside this scope, it acquires a lock on GLOBAL_GOVERNANCE_STATE. This is a global std::sync::Mutex that holds the node’s authoritative understanding of the current network rules.

The handler passes the message to: process_governance_message(&mut gov, &msg).

This function is complex; it validates the cryptographic signatures against the current recognized quorum keys, verifies sequence numbers to prevent replay attacks, and ensures the governance action is semantically valid.

Step 2: Synchronous Disk Persistence If the message is valid, the in-memory global state is mutated. Immediately after, while still holding the global Mutex lock, the code calls gov.save_to_disk(&path) to write the updated binary state to governance.bin.

This ensures that if the daemon crashes a millisecond later, the governance update is not lost.

Step 3: Mutex Block Scoping Architecture Notice that the Mutex lock is acquired and held inside the { ... } block, and the network broadcast happens after the block closes.

-> See: kinetic-daemon/src/api/publish.rs — Lines 640 to 662

This is a critical Rust architecture pattern when mixing synchronous Mutexes and asynchronous code.

The network broadcast uses .await, which is an asynchronous yield point.

If the code held the MutexGuard across that yield point, every single other thread or request in the daemon that needed to read or check governance rules would freeze until the network broadcast finished.

By wrapping the state update and disk I/O in a distinct lexical block, the MutexGuard is dropped gracefully and instantly before the async I/O begins, allowing the rest of the daemon to continue operating unimpeded.

Step 4: Gossipsub Broadcast (Flood Routing) Unlike KIDs and Manifests, governance messages are time-sensitive.

The network cannot wait for DHT propagation. Therefore, instead of storing the payload in the DHT, the node serializes the message and calls: state.network.broadcast_gossip(kinetic_core::constants::GOSSIP_TOPIC_GOVERNANCE, payload_bytes).await.

This uses the Gossipsub protocol. It immediately floods the message to all directly connected peers, who validate it and flood it to their connected peers. Within milliseconds, the governance message blankets the entire kinetic peer-to-peer network.


Key Pieces

  • handle_publish_kid

    • Where: kinetic-daemon/src/api/publish.rs:L326-L430
    • What: Accepts an AuthorizedKid JSON payload, validates inner identity signatures and outer name ownership signatures against the local database, and pushes it to the Kademlia DHT.
    • Why: This is the primary entry point for a user creating, rotating, or updating their decentralized identities in the Kinetic network.
  • handle_publish_manifest

    • Where: kinetic-daemon/src/api/publish.rs:L438-L576
    • What: Accepts an AuthorizedManifest, resolves the parent KID dynamically from the DHT, verifies the cryptographic linkage to ensure the app is authorized by the identity, hashes a derived key, and publishes the manifest to the DHT.
    • Why: This is how decentralized applications announce their connection details, APIs, and IP addresses to the broader ecosystem.
  • handle_publish_governance

    • Where: kinetic-daemon/src/api/publish.rs:L584-L663
    • What: Accepts a SignedGovernanceMessage, locks the global network state, validates the message against quorum rules, persists the changes locally to disk, and rapidly floods the message to all peers.
    • Why: Provides a secure, instantaneous pathway for network administrators to push real-time rule changes and network upgrades without requiring client restarts.

How This Connects to the Rest of Kinetic

  • CROSS-CRATE: AuthorizedKid, AuthorizedManifest, and KidDocument are defined and deeply explained in docs/learn/types/ and docs/learn/kid/.
  • CROSS-CRATE: The DHT publishing logic (publish_redundant_payload) and the Gossipsub flooding mechanism (broadcast_gossip) are core functionalities provided by kinetic-network (Stage 8).
  • CROSS-CRATE: The GLOBAL_GOVERNANCE_STATE Mutex and the process_governance_message business logic are central components of kinetic-core (Stage 7).
  • CROSS-CRATE: The manual ML-DSA signature verification and VerifyingKey instantiation directly leverage the cryptographic primitives outlined in kinetic-verify (Stage 2).

Quick Reference

| Action | API Endpoint Handler | P2P Mechanism | Storage Key / Topic | |—|—|—|—| | Publish KID | handle_publish_kid | Kademlia DHT Put | The KID’s exact DID string | | Publish Manifest | handle_publish_manifest | Kademlia DHT Put | SHA256 Hash of "{did}#manifest" | | Publish Governance | handle_publish_governance | Gossipsub Flooding | GOSSIP_TOPIC_GOVERNANCE |


Open Questions / Things to Revisit

  1. Local Reveal Fallback Security Profile: In both handle_publish_kid and handle_publish_manifest, if the local node doesn’t have the NameRecord cached, it assumes is_authorized = true and forwards the payload to the DHT anyway. While this smartly prevents local cache staleness from blocking legitimate users, it opens a potential vector for abuse. A malicious actor could spam an API node with KIDs for names the node hasn’t seen yet, forcing the node to perform computationally expensive DHT puts for garbage data. This is a potential DoS vector that requires architectural review. Should there be rate-limiting specifically for “uncached” publishes?

  2. Synchronous Disk I/O inside an API Handler: In handle_publish_governance, gov.save_to_disk(&path) is called synchronously inside the API handler while holding the global Mutex lock. If the underlying disk is under heavy load or experiences latency spikes, this could stall the entire API thread. More importantly, it will block all other governance reads across the daemon while waiting on disk I/O. Moving the disk persistence to a background worker channel via a message queue should be evaluated to keep the API layer responsive.

  3. Legacy Document Fallback Timeline: The handle_publish_manifest function gracefully attempts to deserialize raw KidDocument payloads from the DHT if the modern AuthorizedKid parsing fails. How long must the network support this fallback? Is there a coordinated deprecation strategy for raw documents, or will this legacy code live in the daemon indefinitely?