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

kinetic-types :: Name Records

Stage: 1
Reading Time: ~10-12 minutes
Depends on: 06_vdf.md (for understanding the Reveal struct wrapped inside NameRecord::Standard)


What Is This?

This file (kinetic-types/src/name_record.rs) defines the ultimate source of truth for name ownership on the Kinetic network. It describes exactly what a .kin name record looks like when it is flying across the network as a UDP packet. It also describes how it looks when it is sitting statically in the Distributed Hash Table (DHT). In Kinetic, a NameRecord is the fundamental data structure that answers the question “Who owns this name and what does it resolve to?” They are the fundamental building block that turns a chaotic network of peers into an organized, discoverable domain system.

It also defines Heartbeats, which are cryptographic pings. Owners must periodically broadcast these pings to prove they are still alive and actively maintaining their names. Without both of these structures, Kinetic would not function as a decentralized naming system.


Why Kinetic Needs This

Without a standardized NameRecord, the Kinetic network would have no way to enforce name ownership or route traffic. When a user tries to access mysite.kin, the network has to ask the DHT for the record associated with that name. If that record didn’t exist in a universally understood format, the network couldn’t know whether the name is available. It also wouldn’t know who has the right to update its IP address, or what its current routing payload is.

Furthermore, Kinetic is completely decentralized. There is no central database, and there is no “Kinetic Inc.” to manually clean up abandoned names. If someone registers a name and loses their private key, or dies, that name could be locked forever. Without these mechanisms, the system would rapidly degrade into a graveyard of lost, unreachable names that nobody could manage.

Important

The Heartbeat mechanism solves this massive problem: If a node doesn’t see a valid, freshly signed heartbeat for a name within a specific timeframe, the network considers the name abandoned.

Once abandoned, the network allows it to be reclaimed by someone else. The NameRecord and Heartbeat structures are therefore the absolute foundation of Kinetic’s decentralized domain name system (DNS) replacement.


How It Works

The architecture of name ownership is bifurcated into two parallel tracks that share a common interface.

The Two Classes of Names

#![allow(unused)]
fn main() {
// See: kinetic-types/src/name_record.rs — Lines 45 to 61
}

The NameRecord is an enum with two distinct variants:

  1. Standard: This is how 99.9% of names are registered on Kinetic. A user performs Proof of Work, computes a Verifiable Delay Function (VDF), and publishes a Reveal. The network verifies the cryptography. This variant wraps the Reveal struct (which we look at in vdf.rs).
  2. Premium: These are special, typically 1-character apex names (like a.kin or x.kin). They are excluded from the standard mining process to prevent domain squatters from grabbing the most valuable network assets instantly. Instead of being mined, they are granted by the Governance Root Authority key. The Premium variant holds the grant details, including a timestamp and a direct signature from the authority.

Because both variants are packed into the single NameRecord enum, the rest of the networking code doesn’t have to care whether a name is Standard or Premium. The NameRecord provides uniform methods like .pubkey() or .payload() that automatically pull the right data out of whichever variant is being used. This is a powerful use of Rust’s pattern matching.

Preventing Enum Bloat

#![allow(unused)]
fn main() {
// See: kinetic-types/src/name_record.rs — Line 47
}

Notice that the Standard variant holds Box<crate::vdf::Reveal>.

Note

See RUST_CONCEPTS.md for Box<T> and why it is used here to prevent enum memory bloat.

Borrowing Data Instead of Copying

#![allow(unused)]
fn main() {
// See: kinetic-types/src/name_record.rs — Lines 64 to 94
}

The NameRecord implements several methods that return data, like pubkey(), payload(), and signature(). Notice that these methods return &[u8] instead of Vec<u8>.

Note

See RUST_CONCEPTS.md for &[u8] (Borrowed Byte Slice) and how it achieves zero-cost, read-only data access without expensive memory copies.

Signature Verification

#![allow(unused)]
fn main() {
// See: kinetic-types/src/name_record.rs — Lines 97 to 124
}

A name record is completely useless if someone can forge it. The verify_signature method ensures the data hasn’t been tampered with by a man-in-the-middle. For a Standard name, this simply delegates to the Reveal struct’s internal verification logic. For a Premium name, it does something more manual: It takes the name bytes, the zone payload, and the network ID. It packs them together into a single byte array. It then verifies the ML-DSA-65 post-quantum signature against the public key stored in the record. If the signature doesn’t match the bytes exactly, the record is rejected as fraudulent.

Important

By including the network_id in the signature, it prevents a record from one Kinetic testnet from being replayed on the mainnet.

Name Normalization

#![allow(unused)]
fn main() {
// See: kinetic-types/src/name_record.rs — Lines 133 to 139
}

When a user types MYSITE.KIN. or mysite.kin, they fundamentally mean the same thing. The normalize_name function aggressively converts the string to lowercase. It also strips all trailing dots. If Kinetic didn’t do this, a malicious actor could register MYSITE.KIN as a separate record from mysite.kin. This would fracture the network, confuse users, and enable phishing attacks. Normalization guarantees a single canonical form before any hashing occurs. It ensures that everyone is talking about the exact same sequence of bytes.

Storage Keys vs Heartbeat Keys

#![allow(unused)]
fn main() {
// See: kinetic-types/src/name_record.rs — Lines 145 and 169
}

To store a name in a Distributed Hash Table (DHT), you need a key. This key is essentially an address in the network where the data lives. Kinetic stores every name at 32 different addresses to ensure extreme fault tolerance. If 20 nodes go offline, the name is still safely reachable on 12 others.

  1. derive_storage_keys generates 32 keys by hashing the normalized name, an index i (0 to 31), the network ID, and the string -dht-v1.
  2. derive_heartbeat_keys generates 32 different keys by hashing the network ID, the string -hb-v1, the normalized name, and the index i.

The input fields are intentionally fed into the SHA-256 hasher in a completely different order. This guarantees that a storage key and a heartbeat key for the exact same name will never collide. If they did collide, a network node might accidentally overwrite a NameRecord with a Heartbeat packet in its local database.

Warning

By strictly separating the hashing domains (using -dht-v1 and -hb-v1), Kinetic prevents database poisoning.


Key Pieces

NameRecord

#![allow(unused)]
fn main() {
// See: kinetic-types/src/name_record.rs — Line 45
}

The central enum that dictates ownership across the entire platform. It is marked #[serde(untagged)], which means when it is serialized to JSON or binary, it doesn’t include an artificial "type": "Standard" field. Instead, Serde attempts to parse incoming bytes as Standard first. If that fails, it tries to parse them as Premium. This keeps the network wire format lean, self-describing, and bandwidth-efficient.

Heartbeat

#![allow(unused)]
fn main() {
// See: kinetic-types/src/name_record.rs — Line 18
}

A struct that proves the owner of a name is still alive and cares about the name. The owner signs it with their ML-DSA-65 private key. Without these heartbeats flowing through the network, the DHT would eventually drop the NameRecord. This frees it for someone else to register, preventing dead names from cluttering the namespace forever.

signable_bytes() (Inside Heartbeat)

#![allow(unused)]
fn main() {
// See: kinetic-types/src/name_record.rs — Line 28
}

This method prepares the exact sequence of bytes that the owner must sign to produce a valid heartbeat. It carefully packs the network ID, the literal string -heartbeat-v1, the length of the name, the name itself, and the drand kyn number. This strict, deterministic layout guarantees that the signature is completely locked to this exact context. An attacker cannot take a signature meant for a heartbeat and try to reuse it to authorize a payload update, because the prefix -heartbeat-v1 will not match the other format.

latest_drand_kyn (Inside Heartbeat)

#![allow(unused)]
fn main() {
// See: kinetic-types/src/name_record.rs — Line 22
}

A specific field inside the Heartbeat that references a time pulse from the drand network. This ties the heartbeat to an exact moment in real-world time. If this wasn’t here, an attacker could record an old heartbeat from three years ago. They could then replay it forever, preventing the name from ever expiring even if the owner died. This provides absolute replay protection.

M_REDUNDANCY

#![allow(unused)]
fn main() {
// See: kinetic-types/src/name_record.rs — Line 129
}

A constant set to 32. It defines the replication factor across the DHT. When you register a name or send a heartbeat, you aren’t sending it to one central server. You are sending it to 32 independent nodes to ensure high availability and censorship resistance.

normalize_name

#![allow(unused)]
fn main() {
// See: kinetic-types/src/name_record.rs — Line 133
}

A critical utility function that squashes case differences and removes trailing dots. MyName.kin. becomes myname.kin. This is executed before any keys are derived. It ensures canonical uniqueness across the entire global state.

payload()

#![allow(unused)]
fn main() {
// See: kinetic-types/src/name_record.rs — Line 81
}

A helper method on NameRecord that returns the raw byte slice (&[u8]) of the zone data. This payload contains the actual DNS records. These are the IP addresses, IPFS hashes, or Tor onion addresses that the name should resolve to. Notice it returns a borrowed &[u8], providing a read-only window into the data without triggering expensive memory copies.


How This Connects to the Rest of Kinetic

  • FORWARD DEPENDENCY: kinetic-dht: The 32 keys generated by derive_storage_keys and derive_heartbeat_keys are directly consumed by the routing layer in the kinetic-dht crate. The DHT uses these 32 hashes to figure out exactly which physical IP addresses need to receive the NameRecord packet.
  • CROSS-CRATE: crate::vdf::Reveal: The Standard variant tightly couples with the VDF crate. A standard name is entirely defined by the cryptographic proof that someone burned CPU time to claim it. The NameRecord is essentially just a transport wrapper around that Reveal.
  • FORWARD DEPENDENCY: kinetic-node (Liveness tracking): The background workers in kinetic-node use the Heartbeat struct to update local timestamps in their state maps. If the local timestamp for a name falls too far behind the current time, the node will aggressively purge the NameRecord from its local memory.
  • FORWARD DEPENDENCY: kinetic-client: The client-side CLI tools construct the Heartbeat objects by filling out latest_drand_kyn and generating the ML-DSA-65 signature before transmitting them to the network.

Quick Reference

ConceptDescription
NameRecordThe core data structure representing absolute name ownership.
Standard VariantRegistered via competitive mining (PoW + VDF). Wraps a heap-allocated Box<Reveal>.
Premium Variant1-character apex names granted manually by governance. Contains direct ML-DSA-65 signatures.
HeartbeatA cryptographic ping proving the owner still controls the name and is online.
Replay ProtectionHeartbeats include a latest_drand_kyn to prevent attackers from maliciously reusing old pings.
M_REDUNDANCY32. The exact number of DHT nodes that store a redundant copy of the name.
Key SeparationStorage keys and heartbeat keys are derived using different hashing orders to strictly prevent database collisions.
NormalizationAll names are forced lowercase and stripped of trailing dots to maintain a single, unbroken canonical hash.
payload()Provides a zero-cost, read-only view (&[u8]) into the actual DNS zone routing instructions.
signable_bytes()Generates the exact deterministic layout that a name owner must sign to prove liveness.

Open Questions / Things to Revisit

  1. Governance Key Rotation: The Premium names rely on a “Governance Root Authority key”. If that key is ever compromised or needs rotation, how does the network update all existing Premium names? This might need an explicit revocation or update mechanism built into a future network upgrade.
  2. Payload Size Limits: The NameRecord carries the payload (DNS zone data). However, there is no explicitly defined maximum size limit enforced anywhere in this specific file. A malicious user could theoretically attach a 500MB payload and overwhelm the DHT if bounds checking isn’t strictly enforced at the network ingress layer.
  3. Heartbeat Frequency: How often must a Heartbeat be sent? This file defines what a heartbeat is, but the actual TTL (Time To Live) is handled elsewhere. We should ensure the TTL math perfectly aligns with the Drand network’s pulse frequency to avoid edge cases where valid names are dropped.
  4. Signature Verification Overhead: For Premium names, we are concatenating strings and byte arrays (signable.extend_from_slice(...)) every single time we verify a signature. This involves dynamic memory allocation (Vec::new()) on the hot path. We might want to pre-allocate this vector or use a streaming hashing interface that takes parts individually to improve node throughput.