crate: kinetic-core stage: 3 reading_time: 45 mins depends_on: [kinetic_kid::document::KidDocument, kinetic_kid::manifest::CapabilityManifest, ml_dsa::MlDsa65]
What Is This?
This document covers the loading, derivation, and persistent storage of post-quantum ML-DSA-65 signing keypairs in identity.rs.
The identity file serves as the root of trust for any node participating in the network. Without a functioning identity, a node cannot authorize namespaces, sign gossip messages, or establish authenticated P2P connections.
It outlines the authorization structures (AuthorizedKid and AuthorizedManifest) that bind cryptographic identities to human-readable network names (like .kin domains).
This module manages reconstructing post-quantum signature matrices from 32-byte seeds or BIP-39 mnemonic phrases.
It secures these seeds via AES-256-GCM encryption on the filesystem, wipes intermediate memory using the zeroize crate, and enforces filesystem access controls to defend against local privilege escalation.
-> See RUST_CONCEPTS.md for explanations of Zeroize, AES256Gcm, pbkdf2_hmac, and Nonce.
Why Kinetic Needs This
In a fully decentralized, self-sovereign environment like the Kinetic network, a node’s entire identity, authority, and reputation are completely defined by its cryptographic key material. Unlike traditional web systems where identity is tethered to and validated by a central certificate authority (like TLS certificates), Kinetic nodes must prove their identity autonomously. There is no central server to reset a password or recover an account. The node operator bears full sovereign responsibility for their cryptographic keys. However, Kinetic is built explicitly for the post-quantum era. It operates under the assumption that traditional cryptography will eventually fall to quantum computers. Post-quantum ML-DSA-65 (formerly known as Dilithium) keypairs are extremely large compared to classic Elliptic Curve Cryptography (ECC) or RSA keys. A single ML-DSA-65 public key is over 1 kilobyte in size. The private key matrix is substantially larger than the public key. Their sheer physical size makes them incredibly cumbersome to handle in everyday operations. They are difficult to transmit efficiently. They are effectively impossible to backup securely on physical paper without risk of transcription errors. They are complicated to store safely in environment variables.
Kinetic elegantly addresses this usability hurdle by taking a philosophical stance: it never persists or requires node operators to backup full, expanded ML-DSA-65 keypairs. Instead, it relies entirely on highly compressed, maximal-entropy 32-byte seeds as the root of all cryptographic derivation. These tiny 32-byte seeds can be seamlessly encoded as 24-word BIP-39 mnemonic seed phrases. This borrows a highly familiar, battle-tested, and user-friendly paradigm from cryptocurrency hardware wallets. This specific module implements the vital computational bridge. It expands these massive post-quantum keys from those tiny 32-byte seeds dynamically at runtime every single time the node starts up.
Furthermore, node operators frequently run their infrastructure on shared virtual private servers (VPS) or heavily multi-tenant cloud environments.
Because of this, the local private key material must be aggressively protected.
This protection must actively prevent side-channel filesystem extraction attacks.
It must stop malicious co-tenants from stealthily accessing the keys via directory traversal.
This strict threat model necessitates strict POSIX file permission enforcement at the operating system level.
It also requires military-grade AES encryption for node operators who choose to encrypt their local disk state.
Critically, it requires rapid intermediate memory zeroization (via the zeroize trait implementation) during key generation.
This ensures that the raw seed phrase does not linger in RAM where a subsequent core dump could expose it to attackers.
Lastly, the Kinetic network scales across multiple distinct topologies. This includes the public mainnet, isolated developer testnets, and private corporate subnets. The system requires foolproof, unforgeable mechanisms to prevent cross-network replay attacks. An attacker must never be able to take a valid identity authorization signature generated harmlessly on a local testnet and broadcast it maliciously on the mainnet. Such an attack could allow malicious actors to hijack namespaces or impersonate legitimate services across network boundaries. The tightly controlled, network-scoped signable byte structures implemented in this module provide the cryptographic guarantees that completely neutralize this attack vector at the lowest serialization layer.
How It Works
The lifecycle of an identity in Kinetic is meticulously broken down into distinct, heavily guarded operational phases. These phases include secure generation and safe persistence from a human-readable phrase. They include highly protected retrieval and decryption at runtime. They include the deployment of that loaded key to authorize network-specific data structures.
1. Secure Mnemonic Key Generation and Derivation
The critical process of translating a human-readable string into a post-quantum signing key relies on aggressive cryptographic key stretching. This is explicitly designed to mathematically thwart offline dictionary and brute-force permutation attacks.
-> See: kinetic-core/src/types/identity.rs — Lines 150 to 186
When the save_keypair_from_mnemonic function is invoked by the node bootstrapping process, it executes a rigid pipeline:
- Strict Mnemonic Validation: The function immediately parses the 24-word string using the
bip39crate. It strictly validates the mnemonic against the standard English wordlist. It also verifies the embedded checksum derived from the original entropy. If a user mistypes a single character or word, it immediately errors out withKIN-IDN-004. If the checksum is invalid, it errors out. This prevents the generation of an unrecoverable phantom key that the user cannot later restore. - Raw Entropy Extraction: The successfully validated mnemonic is converted into its raw 64-byte entropy seed form.
At this stage, no additional passphrase is used for the BIP-39 algorithm itself.
The empty string
""is explicitly passed to the derivation function. - Network-Specific Deterministic Salting: To ensure that the exact same seed phrase intentionally produces completely different mathematical keys across different network architectures, a salt is constructed dynamically at runtime.
The salt format is exclusively
format!("{}-seed-key-v1", network_id). This guarantees perfect cryptographic domain separation across networks. This means a testnet key derived from a phrase fundamentally cannot sign mainnet transactions, even if the node operator makes a configuration error. - Iterative Key Stretching (PBKDF2): The system heavily stretches the entropy using the
pbkdf2_hmacalgorithm. This algorithm is combined with the robustSha512hashing primitive. In release builds (triggered via the#[cfg(not(debug_assertions))]compiler flag), this runs through an astronomical 5,000,000 iterations. This extreme iteration count is critical for security against offline attacks. It ensures that even if a highly resourced attacker attempts to rapidly brute-force variations of a partially recovered seed, the sheer computational latency of millions of SHA-512 hashes per guess renders the attack mathematically infeasible on modern GPU clusters. - Post-Quantum Expansion: The resulting 32-byte derived output from the PBKDF2 operation is still not the final usable key.
It is explicitly fed into the
ml_dsa::SigningKey::<ml_dsa::MlDsa65>::from_seedconstructor. The internal ML-DSA-65 algorithms then mathematically expand this minimal, high-entropy seed. They expand it into the massive, multi-kilobyte matrix structures required for post-quantum signature generation.
Important
6. Aggressive Memory Zeroization: This is a crucial, non-negotiable security guarantee against memory introspection. Immediately after the ML-DSA-65 key is successfully populated, the intermediate 64-byte raw mnemonic seed must be wiped. The 32-byte derived PBKDF2 output must also be wiped. They both have the
.zeroize()method explicitly called upon them. This action physically overwrites the specific memory buffers in the process RAM with null bytes (0x00). If a malicious process attempts to execute a core dump a microsecond later, the seed material is already permanently destroyed in volatile memory. If an attacker uses a debugger to inspect the running process, they will only find null bytes.
2. Atomic File Persistence
Writing the 32-byte seed to the physical storage medium prioritizes data corruption prevention, transaction atomicity, and local operating system access control.
-> See: kinetic-core/src/types/identity.rs — Lines 187 to 210
Kinetic writes the newly derived seed to a temporary file with a .tmp extension alongside the intended destination path instead of overwriting existing key files directly.
Crucially, it utilizes the standard library’s OpenOptions::new() to apply strict 0o600 POSIX permissions upon file creation.
This specific UNIX permission integer is passed to the OS kernel.
It instructs the operating system kernel that only the exact user executing the node process is allowed to read or write to this file.
All group members are hard-blocked from reading the file contents at the OS filesystem layer.
All other generalized system users are also hard-blocked.
Once the exact 32-byte sequence is written to the operating system’s internal filesystem cache buffer, a vital stabilization step occurs.
The system call file.sync_all() is immediately invoked on the open file handle.
This system call forcefully bypasses the OS write cache.
It forces the operating system block layer to perform a synchronous hardware-level flush to the physical disk platter or NVMe SSD storage controller.
Only after the hardware controller explicitly acknowledges that the data is physically committed to non-volatile storage does the module proceed.
Finally, it utilizes the fs::rename operation to atomically swap the .tmp file over the active, primary key file path.
By leveraging the atomic guarantees of POSIX rename operations, Kinetic guarantees absolute state consistency.
It guarantees that a sudden data center power loss during the bootstrap sequence can never result in a corrupted identity file.
It guarantees that a kernel panic during saving will never leave an empty file.
It guarantees that an abrupt node termination will not result in a half-written file.
The identity state on disk is always strictly binary: completely successfully written, or entirely absent.
3. Hydrating the Identity at Runtime
When the Kinetic node initiates its startup sequence, it must rapidly locate this persistent seed on disk. It must then re-hydrate the ML-DSA-65 signing key matrix into operational memory. The module provides dual support for reading both raw plaintext seed files and highly secure AES-encrypted seed files.
Loading Plaintext Keys
-> See: kinetic-core/src/types/identity.rs — Lines 45 to 65
For a standard node runtime leveraging load_keypair, the system first probes the filesystem.
It does this by evaluating the ENV_KEY_PATH environment variable override (crate::constants::ENV_KEY_PATH).
If this override is absent, it elegantly falls back to utilizing the default configuration base directory.
It then appends the provided filename to this base path.
Upon successfully opening and reading the file, it enforces an uncompromising structural boundary check before processing the bytes.
If the file is not exactly 32 bytes in length, the operation is immediately rejected entirely.
It yields a CorruptedIdentityFile error (KIN-IDN-002) detailing the unexpected length.
This specific, rigid constraint prevents the accidental ingestion of full-sized key files that a user might mistakenly paste in.
It prevents the ingestion of malformed mnemonic string backups saved in text files.
It prevents failures caused by randomly corrupted filesystem sectors.
It strictly guarantees that only perfectly formed 32-byte seeds enter the complex ML-DSA-65 algorithms.
After validation, it simply copies the bytes into a fixed-size [0u8; 32] array.
Finally, it passes this array to the ml_dsa::SigningKey::<ml_dsa::MlDsa65>::from_seed() method.
Loading Encrypted Keys
-> See: kinetic-core/src/types/identity.rs — Lines 81 to 123
For highly security-conscious node operators utilizing load_encrypted_keypair, the underlying file layout is significantly more complex.
It expects a rigid, concatenated binary payload sequence rather than raw bytes:
- 16 bytes: A cryptographically secure random salt generated exclusively for the PBKDF2 stretching phase during the initial encryption pass.
- 12 bytes: A unique, cryptographically secure random Nonce (Number used once) required by the AES-GCM cipher algorithm logic.
- Remaining bytes: The actual AES encrypted ciphertext natively alongside the implicitly attached 16-byte GCM MAC (Message Authentication Code).
When hydrating an encrypted key, the plaintext password supplied by the node operator must be aggressively stretched.
This utilizes the exact same 5,000,000 iteration PBKDF2-HMAC-SHA512 process used in mnemonic generation.
The password is mathematically hashed against the dynamically extracted 16-byte salt parsed from the file header block.
This complex, highly intensive computational process deterministically derives the final 32-byte AES symmetric key required for decryption.
The Aes256Gcm cipher struct is then instantiated using this freshly derived key.
The 12-byte nonce slice is aligned and passed into the decrypt method.
The ciphertext payload is finally submitted for decryption.
AES-GCM is an authenticated encryption cipher, which is a critical security property for Kinetic.
This means it implicitly mathematically verifies the 16-byte MAC during the single decryption pass.
Consequently, any malicious tampering with the file bytes on disk will result in a hard, immediate decryption failure.
Any arbitrary bit-flipping caused by cosmic rays or failing SSD sectors will also result in immediate failure.
This yields a DecryptionFailed error.
It will absolutely never silently output garbage, unpredictable decrypted data into the highly sensitive ML-DSA-65 constructor.
This strictly prevents undefined cryptographic behavior at the core node level.
4. Fortified Cross-Network Replay Protection
When a user’s cryptographic identity is successfully loaded into memory, it is utilized to authorize network primitives.
A common, foundational example is securely linking a mathematical public key to the highly valuable saif.kin namespace document.
A critical security invariant in this decentralized architecture is that an authorization signature generated on a specific network layer must absolutely only be mathematically valid on that exact intended network.
It must never cross boundaries.
-> See: kinetic-types/src/identity.rs — Lines 35 to 55
Both the AuthorizedKid and AuthorizedManifest wrapper structures implement a crucial .signable_bytes(network_id) method.
Rather than naively passing the raw JSON string to the signature algorithm, they construct a tightly controlled binary payload.
Rather than using a loosely concatenated payload, they enforce a rigidly framed binary format:
network_idprefix (e.g., the raw UTF-8 bytes of the stringkin-mainnet-v1orkin-testnet-v2)- A literal protocol suffix byte string array: exactly
-auth-kid-v1or-auth-manifest-v1 - A 32-bit big-endian length prefix precisely encoding the length of the namespace string (via
u32::to_be_bytes()) - The exact UTF-8 bytes of the associated network name string itself
- A 32-bit big-endian length prefix encoding the exact byte length of the internal payload’s canonical representation
- The canonical, perfectly ordered byte string of the core payload document itself
By permanently hardcoding the environment’s network_id string at the very absolute start of the binary serialization, signatures are explicitly scoped.
A signature produced specifically for a local development network like kin-testnet structurally and mathematically cannot ever validate on the production kin-mainnet.
The raw byte payload that the ML-DSA-65 signature natively wraps will intrinsically and catastrophically differ due to this prefix boundary.
Furthermore, the strategic use of strict length prefixing ensures that a sophisticated malicious actor cannot creatively exploit byte boundaries to forge signatures.
Length prefixing is robustly achieved via the standard u32::to_be_bytes().
Without length prefixes, an attacker might attempt to creatively merge the trailing characters of a name with the leading characters of a payload.
This could theoretically maliciously forge a completely different structural context that happens to hash to the exact same overall byte value.
Length prefixes categorically eliminate this entire class of subtle boundary manipulation and semantic ambiguity attacks.
Key Pieces
load_keypair
- What it does: Reads a raw 32-byte seed sequence directly from the designated filesystem path.
- Validation: Strictly validates its exact physical byte length before any cryptographic processing begins.
- Expansion: Mathematically expands the 32-byte seed into a fully operational post-quantum ML-DSA-65 matrix keypair ready for rapid signature operations.
- Location:
kinetic-core/src/types/identity.rs:39 - Why it matters: This function represents the primary initialization path for all standard Kinetic nodes to acquire their operational cryptographic identity.
- Error Codes: Can return
KIN-IDN-001(IO error),KIN-IDN-002(Corrupted File), orKIN-IDN-003(Not Found). - Failure Impact: If this function fails due to filesystem errors, missing paths, or file corruption, the node is rendered cryptographically inert. It cannot sign internal gossip messages. It cannot participate in network consensus. It cannot authorize any namespace transactions.
load_encrypted_keypair
- What it does: Securely parses a concatenated AES-GCM encrypted binary payload from the local disk.
- Decryption Flow: It dynamically unpacks the embedded cryptographic salt and nonce headers from the unencrypted prefix bytes of the file.
- Key Stretching: It aggressively stretches the node operator’s provided plaintext password through 5,000,000 rounds of PBKDF2 to derive a symmetric AES decryption key.
- Finalization: It authenticated-decrypts the internal ciphertext using the MAC. It finally feeds the resulting raw 32 bytes into the ML-DSA-65 expansion algorithm.
- Location:
kinetic-core/src/types/identity.rs:69 - Why it matters: This function provides vital, defense-in-depth security for high-value node runners (such as foundational relay nodes, critical infrastructure providers, or high-value namespace owners).
- Threat Model: These high-value nodes might be specifically targeted by sophisticated server breaches or zero-day OS exploits designed to steal key files. The extreme 5-million iteration PBKDF2 stretch makes offline dictionary cracking operations of these stolen files computationally bankrupting for the attacker.
save_keypair_from_mnemonic
- What it does: Thoroughly validates and parses a standard 24-word BIP-39 mnemonic string provided by the user.
- Derivation: It algorithmically derives a network-specific cryptographic salt and heavily stretches the resulting entropy via the PBKDF2 algorithm.
- Persistence: It persistently saves the resulting 32-byte output atomically to disk hardware using temporary files and POSIX rename operations.
- Security: It enforces tight
0o600POSIX access control lists (ACLs) before explicitly and aggressively wiping intermediate RAM buffers containing the raw mnemonic entropy using the.zeroize()method. - Location:
kinetic-core/src/types/identity.rs:150 - Why it matters: This function is the sole, highly secure mechanism by which human-readable backups (which can be written on physical paper) are converted into operational, machine-readable post-quantum signature keys.
- Hardware Safety: The stringent atomic filesystem write operations guarantee absolute data safety against sudden hardware power interruptions or kernel panics during the highly vulnerable initial node provisioning sequence.
AuthorizedKid
- What it does: A comprehensive wrapper struct logically pairing a baseline
KidDocumentwith a specific.kinnetwork string name. - Binding Mechanism: It is crucially bounded by a mathematically unforgeable, network-scoped ML-DSA-65 owner signature verifying the precise structural attachment of the name to the public keys.
- Fields:
name: The.kinstring literal representing the human-readable namespace.kid_doc: The embedded raw KID document containing the node’s public keys.owner_signature: The raw bytes of the ML-DSA-65 post-quantum signature proving ownership.
- Location:
kinetic-types/src/identity.rs:15 - Why it matters: Within the Kinetic architectural ecosystem, floating decentralized identifiers (KIDs) constitute the base, anonymous identity layer.
- Identity Elevation: This specific struct elevates an isolated, floating KID by cryptographically anchoring and binding it to a highly specific, easily human-readable namespace. This transformation makes the identity highly routable. It makes it globally discoverable. It makes it socially relevant within the P2P overlay network.
AuthorizedManifest
- What it does: A structural mirror to the KID authorization logic, this struct firmly pairs a complex
CapabilityManifestwith a network namespace. - Composition: It optionally appends the parent contextual KID document. It binds all of these tightly together by the exact same network-scoped authorization signature.
- Fields:
name: The.kinstring literal representing the human-readable namespace.manifest: The embedded raw capability manifest document outlining node permissions and services.kid_doc: Optional inclusion of the parent identity document for immediate verification context.owner_signature: The raw bytes of the ML-DSA-65 post-quantum signature proving capability ownership.
- Location:
kinetic-types/src/identity.rs:59 - Why it matters: This struct enables abstract network capabilities (such as localized service hosting, sub-protocol participation rights, or highly restricted data access scopes) to be demonstrably and publicly owned by a specific
.kinname. - Consistency: The internal byte layout and structural serialization design perfectly mirror the
AuthorizedKidimplementation. This maintains cognitive and structural homogeneity across the codebase’s core authorization mechanics.
How This Connects to the Rest of Kinetic
- FORWARD DEPENDENCY: The fully hydrated ML-DSA-65 keys generated by this module are heavily consumed globally by the network transport layer.
-> See:
kinetic-core/src/network/peer.rsThese deeply derived keys are specifically utilized to digitally sign all internal network handshakes. This ensures every single P2P TCP connection is mutually authenticated using post-quantum cryptographic primitives. - FORWARD DEPENDENCY: The heavily scrutinized
AuthorizedKidandAuthorizedManifestserialization structs represent the primary, foundational payload structures physically transmitted over the global gossip network. -> See:kinetic-core/src/validation.rsThe mathematically sound.signable_bytes()method is heavily invoked downstream by the consensus logic. This is done to independently verify the structural integrity of floating namespaces broadcast by untrusted remote network peers. - CROSS-CRATE: This entire identity subsystem fundamentally relies on the isolated
kinetic-kidcrate for the baseline mathematical definitions of theKidDocumentandCapabilityManifestinternal structures. The logic defined here effectively serves as a higher-level organizational wrapper. It introduces the critical “name binding” and precise “network scope” validation metadata rigorously demanded by the active Peer-to-Peer operational layer.
Quick Reference
- Core Cryptographic Standard: ML-DSA-65 (NIST Post-Quantum Cryptography Standardization, formerly Dilithium).
- Physical Disk Footprint: Exactly 32 bytes strictly for plaintext seed operations.
- Encrypted Disk Footprint: Exactly 60 bytes for AES-encrypted payloads (16 byte Salt + 12 byte Nonce + 32 byte Ciphertext and appended MAC).
- Key Stretching Configuration: A strict requirement of
5,000,000PBKDF2 iterations utilizing the SHA-512 hashing algorithm on compiled release builds. (1,000 iterations for debug builds). - Operational Filesystem Permissions: Hardcoded to strictly enforce
0o600access (Read and Write by file owner exclusively) on all UNIX-like operating systems. - Cryptographic Replay Protection: Foundational protection implemented intrinsically via rigid network-prefixing at the raw binary serialization layer prior to any signature application.
- Memory Security Posture: Highly vulnerable intermediate RAM allocations are aggressively destroyed using the explicit
zeroizeoperational trait to neutralize sophisticated memory dump extraction exploits. - Key Derivation Path: Mnemonic Phrase -> 64-byte raw entropy -> PBKDF2-HMAC-SHA512 with network salt -> 32-byte derived seed -> ML-DSA-65 Expanded Key Matrix.
Open Questions / Things to Revisit
- PBKDF2 vs Argon2 Migration: The network is currently hardcoded to utilize
PBKDF2-HMAC-SHA512. While this algorithm is highly robust, time-tested, and officially NIST approved, modern cryptographic research indicates thatArgon2idprovides fundamentally superior, memory-hard resistance against highly optimized ASIC (Application-Specific Integrated Circuit) and GPU-cluster cracking attempts. We should deeply evaluate and potentially schedule migrating the core key derivation function for the encrypted file loading sequence to provide superior resistance against offline brute-forcing.
Warning
- Windows File Permissions Parity: The explicit, strict
0o600permission constraint is currently rigidly scoped via the#[cfg(unix)]compilation flag in Rust. We urgently need to investigate and ensure that native Windows OS compiled implementations correctly translate this requirement by aggressively restricting file Access Control Lists (ACLs) exclusively to the executing service user. Failure to do so risks trivial privilege escalation local reads on compromised Windows servers.
Important
- Browser WebAssembly (WASM) Ecosystem Support: Currently, the entire disk-based key loading and mnemonic saving logic is completely gated behind the
#[cfg(not(target_arch = "wasm32"))]compilation flag due to its reliance on standard library file I/O operations (std::fs). If the long-term architectural goal dictates that lightweight web browser clients need to natively generate or securely load keypairs, we must meticulously implement a highly secure storage adapter. This could utilize IndexedDB or asynchronous Web Crypto API backed storage that strictly adheres to the exact same stringent hardware-level security guarantees currently provided by the native POSIX filesystem adapter.
- Cryptographic Key Rotation APIs: There is currently absolutely no public API surface exposed in this file to gracefully rotate an operationally compromised 32-byte seed into a completely new seed. Simultaneously, the node must safely maintain the exact same historical identity bindings and network reputation. This missing feature is a major friction point for long-term node sustainability and requires immediate architectural design to prevent node reputation loss upon key compromise.
- Password Strength Enforcement: The
load_encrypted_keypairlogic relies entirely on the user providing a “strong password” during the initial encryption pass. There is currently no API enforcement during the creation phase of an encrypted keypair to ensure the password meets standard entropy requirements (length, character complexity). We should seriously consider adding an entropy validation pass using a library likezxcvbnbefore allowing encryption to proceed to disk.
Deep Dive: ML-DSA-65 vs Classical Keys
To truly understand why Kinetic architected the system around 32-byte seeds instead of storing the keys directly, one must understand ML-DSA-65. ML-DSA-65 is the NIST standardization of the Dilithium algorithm.
| Algorithm | Property |
|---|---|
| ML-DSA-65 | Unlike RSA which relies on integer factorization, ML-DSA-65 relies on the hardness of finding short vectors in a lattice. |
| ML-DSA-65 | Unlike ECC which relies on the discrete logarithm problem on elliptic curves, ML-DSA-65 uses module learning with errors (MLWE). |
Because quantum computers running Shor’s algorithm can trivially break RSA and ECC, those algorithms are obsolete for future-proof networks. However, the mathematical structures (matrices and polynomials) required for ML-DSA-65 are massive.
| Key Type | Size Details |
|---|---|
| ed25519 | A standard ed25519 private key is exactly 32 bytes. |
| ML-DSA-65 Private Key | An ML-DSA-65 private key is approximately 4,032 bytes. |
| ML-DSA-65 Public Key | An ML-DSA-65 public key is approximately 1,952 bytes. |
| ML-DSA-65 Signature | An ML-DSA-65 signature is approximately 3,309 bytes. |
If Kinetic forced users to back up 4,032 bytes of random private key data, physical paper backups would be impossible. It would require printing QR codes that are extremely dense and prone to scanning failures. By deterministically generating the 4,032 bytes from a 32-byte seed at runtime, the physical backup remains a simple 24-word phrase.
Byte Serialization Example
The replay protection mechanism requires strict byte layout adherence.
Consider a node authorizing the name saif.kin on kin-testnet.
The .signable_bytes("kin-testnet") method constructs the exact array passed to the signer.
kin-testnet-> 11 bytes ([107, 105, 110, 45, 116, 101, 115, 116, 110, 101, 116]).-auth-kid-v1-> 12 bytes ([45, 97, 117, 116, 104, 45, 107, 105, 100, 45, 118, 49]).- Length of name
saif.kin-> 8 bytes. As a 32-bit big-endian integer, this is[0, 0, 0, 8]. - The name itself
saif.kin-> 8 bytes ([115, 97, 105, 102, 46, 107, 105, 110]). - Length of canonical JSON ->
[0, 0, 1, 144](assuming 400 bytes). - The canonical JSON bytes.
If a malicious actor captures this signature and broadcasts it to
kin-mainnet, the validation logic will reconstruct the required bytes starting with[107, 105, 110, 45, 109, 97, 105, 110, 110, 101, 116]. Because the very first bytes of the hash input differ, the final hash differs entirely. The ML-DSA-65 signature verification will immediately mathematically reject the signature as invalid for the given payload.
Step-by-Step Flow: Encrypted Key Loading
Let’s walk through the exact execution trace when a user boots a node with an encrypted identity file.
- The node process starts and reads
KINETIC_KEY_PATH. - It calls
fs::readand pulls the entire file into a heap-allocatedVec<u8>. - It performs a boundary check:
bytes.len() < 16 + 12 + 16. If true, it returnsCorruptedIdentityFile. - It slices
&bytes[0..16]to extract the PBKDF2 salt. - It slices
&bytes[16..28]to extract the AES-GCM Nonce. - It slices
&bytes[28..]to extract the ciphertext and MAC. - It allocates a
[0u8; 32]buffer for the AES key on the stack. - It invokes
pbkdf2_hmac::<Sha512>. This blocks the thread, executing 5,000,000 SHA-512 hashes. - It instantiates
Aes256Gcm::newusing the resulting stack buffer. - It calls
cipher.decrypt. - AES-GCM calculates the authentication tag over the ciphertext and compares it to the MAC.
- If they match, the plaintext 32 bytes are returned.
- The plaintext bytes are passed to
ml_dsa::SigningKey::from_seed. - The fully hydrated key is returned to the node’s core state machine.
Detailed Error Analysis
The identity module utilizes a strict, custom error enum named IdentityError.
Each variant maps to a highly specific failure mode during the bootstrap process.
Understanding these errors is critical for debugging node initialization failures.
IdentityNotFound (KIN-IDN-003)
- Trigger: The
fs::readsystem call yields aNotFoundstandard library IO error. - Context: The node was started, but the
identity.binfile simply does not exist at the resolved path. - Resolution: The user must explicitly run
kinetic seed initto generate a new key. - Resolution (Recovery): Alternatively, the user must run
kinetic seed restoreand provide their 24-word backup phrase.
CorruptedIdentityFile (KIN-IDN-002)
- Trigger: The filesystem read succeeds, but the resulting byte vector length is unexpected.
- Context (Plaintext): The file length is not exactly
32bytes. - Context (Encrypted): The file length is less than
44bytes (16 salt + 12 nonce + 16 MAC). - Resolution: The file has been fundamentally tampered with or corrupted by the OS block layer. The user must delete the file and restore from their mnemonic backup.
DecryptionFailed
- Trigger: The
Aes256Gcm::decryptmethod returns an error. - Context: This occurs for two primary reasons. First, the user provided the incorrect password, resulting in the wrong AES key, causing MAC validation to fail. Second, the ciphertext bytes on disk were modified, causing MAC validation to fail even with the correct password.
- Resolution: The user must retry with the correct password. If they have forgotten the password, the encrypted file is mathematically useless, and they must restore from the mnemonic backup.
InvalidSeedPhrase (KIN-IDN-004)
- Trigger: The
bip39::Mnemonic::parse_inmethod returns an error. - Context: The user attempted to restore a node, but the phrase provided contains invalid words, incorrect spelling, or an invalid cryptographic checksum.
- Resolution: The user must carefully verify their physical backup and ensure all words strictly match the BIP-39 English dictionary specification.
Error Design Philosophy
The overarching philosophy of this module is to fail closed. If there is any ambiguity whatsoever regarding the integrity of the key material, the node will panic and exit. It will never attempt to automatically guess a password. It will never attempt to truncate a corrupted file to 32 bytes. It will never bypass MAC validation. This rigid “fail closed” architecture prevents the node from ever booting into an undefined cryptographic state.