title: kinetic-core/src/types/dns.rs stage: 4 reading_time: 30 minutes depends_on: kinetic-types::dns, kinetic-core/src/error/dns.rs
What Is This?
This module serves as the boundary between the external internet and Kinetic’s internal DNS state machine.
It takes raw DNS zone data—submitted as JSON payloads—and enforces strict validation before allowing it to persist into the decentralized ledger.
In the Kinetic ecosystem, Domain Name System (DNS) resolution is not just about
translating legacy domains like example.com into an IPv4 address. It is the
fundamental routing layer for the decentralized web. Because of this dual
mandate, this file handles both traditional internet DNS concepts (such as A,
AAAA, CNAME, and TXT records) alongside Kinetic-native decentralized routing
primitives (such as libp2p Peer IDs, Kinetic Key Identifiers, and IPFS content
hashes).
Specifically, this file in kinetic-core implements the DnsZoneExt extension
trait. This trait binds heavy, computationally expensive validation logic and
memory-safe parsing directly to the bare, lightweight DnsZone data structures
that are defined over in the kinetic-types crate.
Why Kinetic Needs This
When you build a decentralized peer-to-peer network, the data you receive from the outside world is fundamentally adversarial. You cannot trust the client submitting the data. If Kinetic validator nodes were to blindly accept whatever DNS JSON payloads were broadcasted to them, the network would collapse almost immediately due to a variety of attack vectors. Here is a detailed breakdown of why this specific validation layer is an absolute necessity:
- State Bloat Protection: In a decentralized network architecture, every full node has to store the global state of the network. Storage is a premium resource.
Warning
If a malicious actor could publish a single DNS zone containing 10,000 garbage records, they could rapidly fill up the hard drives of every node on the network, effectively executing a resource-exhaustion attack for pennies in transaction fees. This file steps in and enforces a strict, hard-coded 50-record cap per DNS zone to guarantee bounded state growth.
- Denial of Service (DoS) Prevention via JSON Bombs: Parsing JSON is
computationally expensive and heavily utilizes the application’s call stack.
Attackers often deploy “JSON bombs”—deeply nested recursive structures like
[[[[{"a": "b"}]]]]. When standard JSON parsers attempt to evaluate these, they recurse so deeply that they blow past the operating system’s stack limit, instantly crashing the node. This module intentionally utilizesserde_json::from_slicewhich contains explicit, hard-coded recursion limits to protect the node’s memory architecture. - Delayed Routing Integrity: If a user submits an invalid libp2p
PeerId(e.g., a string missing a few characters) or a malformed IPFS CID, and the consensus network accepts it without checking, the network will not crash immediately. Instead, the error lies dormant in the database. Hours or days later, when an innocent client attempts to connect to that peer, the client’s routing engine will panic or fail. We must fail early and loudly. This file ensures that invalid routing parameters never make it into the state database. - Standard DNS Specification Compatibility: Legacy DNS has strict, time-
tested rules. For example, DNS RFCs mandate that a
CNAMErecord acts as an exclusive alias—it cannot coexist on the same label as an IP address or a TXT record. Kinetic nodes must enforce these legacy rules flawlessly so that Kinetic domains can seamlessly bridge back to standard Web2 DNS resolvers (like Cloudflare or Google) without breaking them. - Deterministic Cryptographic Verification: In a decentralized environment, nodes must prove ownership over their routing changes. The validation structures contained in this file ensure that the bytes being cryptographically signed by the user are highly deterministic and resistant to parsing ambiguities, preventing signature-spoofing attacks.
How It Works
This file orchestrates a highly robust, multi-stage processing pipeline. It takes an array of raw, untrusted bytes from the network and walks it through parsing, normalization, capacity checking, and deep cryptographic validation.
Stage 1: Safe and Bounded Payload Parsing
The entire process begins when the network receives a DNS update request. At
this point, the request is nothing more than an array of raw bytes (&[u8]).
-> See: kinetic-core/src/types/dns.rs — Lines 28-30
A naive implementation might convert these bytes into a Rust String (which
requires the CPU to perform an expensive, full-pass UTF-8 validation) and then
parse that string into JSON. Instead, the code directly invokes
serde_json::from_slice.
This function operates directly on the underlying byte array. Because it
operates on bytes, it is extremely fast and can perform zero-copy
deserialization where possible, meaning it borrows strings directly from the
byte array instead of allocating new memory for them. More importantly, it
utilizes Serde’s built-in recursion limits, protecting the node from the stack-
overflow DoS attacks mentioned earlier. If the JSON is invalid, it throws a
standard error, halting the transaction instantly.
Stage 2: Normalization and Case-Insensitivity
By standard definition, DNS labels are supposed to be entirely case-insensitive.
A user visiting API.kinetic.host expects to be routed to the exact same
destination as a user visiting api.kinetic.host. However, underneath the hood,
the data is stored in a Rust HashMap. A hash map calculates hashes based on
precise bytes, meaning it treats "API" and "api" as two completely distinct,
non-colliding keys.
-> See: kinetic-core/src/types/dns.rs — Lines 31-36
To solve this discrepancy without frustrating the user by rejecting their
payload, the parser automatically normalizes the data. It achieves this by
allocating a brand new HashMap. It then drains the contents of the original,
raw parsed map. For every single key-value pair, it converts the key to
lowercase.
It utilizes the powerful Rust idiom:
.entry(k.to_lowercase()).or_default().extend(v). This looks up the lowercase
key in the new map. If it doesn’t exist, it gracefully inserts an empty vector.
Then, it extends that vector with the incoming records. If a user poorly
formatted their payload and submitted separate records for both API and api,
this logic safely and efficiently merges all of those records into a single,
unified api array.
Stage 3: Network Capacity Limits
Once the data structure is fully normalized, the validate() method takes
command of the pipeline.
-> See: kinetic-core/src/types/dns.rs — Lines 55-60
Before it even looks at the content of the records, it performs a purely
quantitative network capacity check. It iterates over all the values (the
vectors of records) contained within the hash map, maps them to their respective
lengths, and sums them up into a single integer.
If the total number of records across the entire DNS zone exceeds 50, the
function immediately aborts, returning a TooManyRecords error.
Important
This is a critical security constraint. It definitively bounds the maximum size of a single DNS state entry in the blockchain, ensuring that validation times and state synchronization metrics remain predictable and lightweight.
Stage 4: Label Syntax Enforcement
Next, the validator enters a loop, evaluating every single label (the subdomain
string, like www, api, or @) provided in the zone.
-> See: kinetic-core/src/types/dns.rs — Lines 61-78
It applies a battery of string-manipulation checks to ensure compliance with DNS standards:
- Length Limits: The label absolutely cannot be empty (length of 0), and it cannot exceed 63 characters in length. This is a strict limitation inherited from legacy DNS architecture.
- Root and Wildcards: It explicitly bypasses further string-character checks
if the label is precisely
@(which represents the root apex of the domain) or*(which represents a wildcard routing mechanism). - Hyphen Edge-Case Rules: The label cannot start with a hyphen, nor can it
end with a hyphen (e.g.,
-wwworapi-are strictly invalid). - Character Allowlist: It iterates character by character over the string.
If a character is not an ASCII alphanumeric, a hyphen, or an underscore, it
throws an
InvalidLabelCharacterserror. No emojis or arbitrary Unicode are allowed in raw labels without punycode encoding.
Stage 5: CNAME Exclusivity and Collision Avoidance
-> See: kinetic-core/src/types/dns.rs — Lines 80-85
This stage enforces one of the most commonly misunderstood rules of DNS RFCs. If
a label contains a CNAME (Canonical Name) record, that label is acting as a
hard, redirecting alias to another domain entirely. Therefore, it is
mathematically and logically impossible for that label to also contain an A
record (an IP address) or a TXT record alongside it.
The validator checks the array of records. If any single record is a CNAME
variant, the code asserts that the total length of the array must be exactly 1.
If the length is greater than 1, it realizes a collision has occurred and throws
an InvalidCnameConfiguration error, saving the user from a broken domain
setup.
Stage 6: Granular Record-Specific Validation
Finally, it matches on the specific enum variant of each individual record and
applies bespoke, tight validation rules based on the intended destination.
-> See: kinetic-core/src/types/dns.rs — Lines 87-122
- TXT Records: Must be strictly bounded to 255 bytes or smaller, throwing
TxtRecordTooLongif violated. - CNAME Targets: The destination domain string cannot be empty, and it cannot exceed 253 characters.
- Peer IDs: The raw string is passed to
libp2p_identity::PeerId::from_str. If the underlying libp2p cryptographic library cannot parse it into a valid, structurally sound peer identifier, the record is rejected. This guarantees routing integrity later in the node’s lifecycle. - KIDs (Kinetic Identifiers): Kinetic Identity Documents must strictly begin
with the exact prefix
did:kin:. If a user attempts to map a domain to a different decentralized identifier method (likedid:eth:ordid:pkh:), the system will emit a warning log and reject the payload, ensuring only native identities are bound. - IPFS CIDs: IPFS Content Identifiers must not be overly long (capped at 100
characters), and they must begin with either
Qm(indicating an older CIDv0 Base58 encoded hash) orb(indicating a newer CIDv1 Base32 encoded hash).
Stage 7: Host Routing Serialization Protocol
While not part of the validate() function for the overarching zone, the
HostRoutingRecord contains its own complex serialization pipeline for
cryptographic validation.
-> See: kinetic-types/src/dns.rs — Lines 61-79
When a routing record is signed, the signable_bytes() method constructs a
deterministic byte array. It pre-allocates a vector with exact capacity to
prevent memory reallocation lag. It then pushes the bytes in a highly structured
format: first a network ID, then a hardcoded string -routing-v1, then the
length of the host ID as a 4-byte big-endian integer, the host ID itself, the
length of the peer ID as a 4-byte big-endian integer, the peer ID itself, and
finally an 8-byte big-endian integer representing the Drand beacon timestamp.
This rigidly structured byte payload guarantees that no two varying
configurations can ever yield the same hash for signing.
Key Pieces
The Extension Trait Pattern (DnsZoneExt)
-> See: kinetic-core/src/types/dns.rs — Lines 9-16
-> See RUST_CONCEPTS.md for an explanation of the Extension Trait Pattern and the Orphan Rule. We use this to bolt validation logic onto DnsZone which is defined in the external kinetic-types crate.
DnsRecord Enum (from kinetic-types)
-> See: kinetic-types/src/dns.rs — Lines 19-40
This is a strongly typed enumeration defining every possible standard and
decentralized DNS record type the Kinetic ecosystem understands. It utilizes the
Serde attribute #[serde(tag = "type", content = "value")] to create an
internally tagged representation for clean JSON parsing. Here is how the
variants break down:
A(Ipv4Addr): Stores a standard IPv4 address object for legacy routing.AAAA(Ipv6Addr): Stores a standard IPv6 address object.CNAME(String): A canonical alias string pointing to another domain label entirely.TXT(String): An arbitrary text field often utilized for domain ownership verification protocols.PeerId(String): Contains a libp2p cryptographic peer identifier, bypassing IP routing entirely in favor of direct peer-to-peer transport.KID(String): Contains a Kinetic Key Identifier (did:kin:...), mapping the domain to a globally authorized decentralized identity document.IPFS(String): Contains a content identifier hash, allowing a domain to seamlessly serve decentralized static web pages stored on IPFS.Other: The catch-all variant utilizing#[serde(other)]. If a newer network upgrade introduces a record type that an older node does not understand, this variant catches it, preventing a deserialization crash and ensuring forward- compatibility.
DnsZone Struct (from kinetic-types)
-> See: kinetic-types/src/dns.rs — Lines 11-17
What it does: A structural wrapper that encapsulates a HashMap<String, Vec<DnsRecord>>.
Why it matters: Notice the strategic #[serde(default)] attribute placed
above the HashMap definition. If an end-user submits a JSON payload with an
entirely empty body {} instead of the expected {"records": {}}, Serde will
not panic and reject it. It will realize the records field is missing, look up
the default implementation for a Rust HashMap (which is an empty map), and
automatically substitute it. This makes the API endpoint extremely resilient and
forgiving to slightly malformed but logically harmless inputs.
HostRoutingRecord (from kinetic-types)
-> See: kinetic-types/src/dns.rs — Lines 42-81
What it does: Maps a persistent, decentralized host identifier string to a
fluid, currently active libp2p PeerId.
Why it matters: While standard DNS maps domains to static IP addresses, a
peer-to-peer node’s active network connection (its peer ID and IP address) can
fluctuate wildly as it disconnects, reboots, or changes networks. This record is
highly dynamic.
Crucially, look at the signable_bytes method provided in the implementation
block. When a user applies a cryptographic signature to this record to prove
they own the domain, the system does not just stringify the JSON and sign it.
JSON keys can be arbitrarily rearranged by different libraries, which would
cause the signature verification to fail unpredictably.
Instead, the code aggressively packs the data into a strict, length-prefixed
byte array format. By explicitly length-prefixing the individual strings before
concatenating them together, it mathematically ensures that a clever attacker
cannot dynamically shift bytes between different fields to trick the signature
verification algorithm into validating a malicious payload.
It also integrates a drand_kyn field. Instead of relying on a highly
unreliable local UNIX timestamp to prove exactly when the routing record was
generated, it utilizes the block number from the Drand distributed randomness
beacon. This provides a globally verifiable, entirely decentralized clock
mechanism, ensuring that attackers absolutely cannot replay older, intercepted
routing records to maliciously hijack network traffic.
How This Connects to the Rest of Kinetic
CROSS-CRATE: The Divide Between kinetic-types and kinetic-core
A common question when viewing this file is: Why aren’t the DnsZone struct and
the DnsZoneExt validation trait simply bundled together in the exact same
file?
The answer lies in strict dependency management. The kinetic-types crate is
purposefully engineered to be universally importable across any platform
architecture. A lightweight WebAssembly wallet running inside a mobile browser
can import kinetic-types to properly serialize a DNS request. That browser
environment does not need—and likely cannot support—the heavy cryptographic
validation dependencies required to verify it, because the backend network will
perform that validation anyway. By brutally splitting the raw data types from
the complex validation logic, Kinetic maintains a very clean, modular, and fast-
compiling dependency graph.
FORWARD DEPENDENCY: The State Transition Machine (Consensus)
When a validator node receives a transaction block containing a DNS state update
payload, the core consensus engine will intercept the raw bytes and immediately
pass them into the DnsZone::parse_payload(bytes) function defined in this
file.
If this function returns a validation error, the transaction is immediately
marked as structurally invalid. The user loses their transaction gas fee, and
the global state is never modified. The consensus layer trusts this specific
file implicitly and exclusively to act as the ultimate gatekeeper of state
purity.
FORWARD DEPENDENCY: The P2P Peer Routing Table
When a node resolves a domain like application.kin and receives a
decentralized PeerId in response, it passes that raw string directly down into
the libp2p networking stack to attempt a direct connection. If the string was
malformed, the connection engine would panic or silently swallow the failure.
Because this file strictly and definitively ran
libp2p_identity::PeerId::from_str during the initial block validation phase,
the routing layer can blindly trust that the string it pulled from the database
is a cryptographically sound identifier, eliminating the need for redundant
string checks during active routing.
Quick Reference
- Maximum Total Records: Hard-capped at precisely 50 records per domain zone to prevent unmitigated state bloat.
- Label Validation Parameters: Must be exactly 1 to 63 characters in length.
Only alphanumeric characters, hyphens (but explicitly not at the leading or
trailing edges), and underscores are permitted. The special
@(apex) and*(wildcard) labels bypass these specific string checks entirely. - Record Collision Mechanics: A label housing a
CNAMEredirect record cannot possess any other records whatsoever; it demands exclusivity. - Field Size Constraints: Standard
TXTrecords are heavily constrained to 255 bytes.CNAMEtargets are constrained to 253 characters. - JSON Payload Structure: Enforces
{"type": "...", "value": "..."}formatting via Serde internal tags, diverging from raw nested structs. - Data Normalization: All subdomain labels are automatically cast to
lowercase before permanent storage. Duplicate labels submitted in varying
cases (like
ApiandAPI) are seamlessly merged together utilizing hash map entry logic. - P2P Ecosystem Integrations: Strictly enforces the internal string
structure of libp2p
PeerIds and native KineticKIDs before they touch the database. - Dynamic Routing Safety: The
HostRoutingRecordstruct utilizes length- prefixed byte concatenation for deterministic cryptographic signing, anchored exclusively by adrand_kyndecentralized timestamp rather than local system clocks.
Open Questions / Things to Revisit
- TXT Record Constraints and Developer Experience: Should
TXTrecords really be strictly limited to a single 255-byte string limitation? Legacy standard DNS actually allows for multiple 255-byte chunks that can be conceptually concatenated by the client application. If external application developers need to store large cryptographic verification keys (like a Matrix server verification token or a domain-ownership proof) directly inside a TXT record, this hard 255-byte limit could cause massive developer friction and require hacky protocol workarounds. - The “Unknown Record” Silently Passing: The
DnsRecord::Otherfallback variant is an excellent architectural choice for forwards-compatibility, but during the validation loop, the code does exactly this:DnsRecord::Other => {}. It simply ignores it. This means a malicious actor could theoretically submit payloads filled entirely withOtherrecords, bypassing all structural checks while still taking up valuable slots within the 50-record limit. Is this intentional behavior, or shouldOtherrecords be explicitly rejected during active zone updates to prevent state-spamming? - Rudimentary IPFS CID Validation Mechanics: The current CID validation
logic simply checks if the provided string begins with
Qmorb. This is extremely rudimentary and highly trusting of the user. It does not verify the Base58 or Base32 encoding, nor does it verify the cryptographic multihash structure nested inside the CID. This implies that a blatantly invalid CID (like the stringb-this-is-garbage-data-that-will-fail) will easily pass the validation layer, which could cause downstream IPFS resolver clients to crash when they attempt to fetch the non-existent content. We should strongly consider importing the officialcidcrate to perform actual, rigorous parsing at the edge.