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:
-
Identity Integrity: You cannot publish an
AuthorizedKidunless you prove you own the cryptographic keys associated with the registered human-readable Name attached to it. -
Application Integrity: You cannot publish a
Manifestfor an application unless you prove the signature on the manifest was generated by an authorized subkey listed inside the parent KID. -
Network Integrity: You cannot publish a
Governancechange 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
AuthorizedKidJSON 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.
- Where:
-
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.
- Where:
-
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.
- Where:
How This Connects to the Rest of Kinetic
- CROSS-CRATE:
AuthorizedKid,AuthorizedManifest, andKidDocumentare defined and deeply explained indocs/learn/types/anddocs/learn/kid/. - CROSS-CRATE: The DHT publishing logic (
publish_redundant_payload) and the Gossipsub flooding mechanism (broadcast_gossip) are core functionalities provided bykinetic-network(Stage 8). - CROSS-CRATE: The
GLOBAL_GOVERNANCE_STATEMutex and theprocess_governance_messagebusiness logic are central components ofkinetic-core(Stage 7). - CROSS-CRATE: The manual ML-DSA signature verification and
VerifyingKeyinstantiation directly leverage the cryptographic primitives outlined inkinetic-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
-
Local Reveal Fallback Security Profile: In both
handle_publish_kidandhandle_publish_manifest, if the local node doesn’t have theNameRecordcached, it assumesis_authorized = trueand 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? -
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. -
Legacy Document Fallback Timeline: The
handle_publish_manifestfunction gracefully attempts to deserialize rawKidDocumentpayloads from the DHT if the modernAuthorizedKidparsing 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?