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

Stage 1 · identity.rs — Authorization Containers for .kin Names

Crate: kinetic-types Source: kinetic-types/src/identity.rs — Lines 1–97 Estimated reading time: ~12 minutes Depends on: 01_lib.md (crate overview), 02_name.md (.kin name model)


What Is This?

identity.rs defines two authorization containers: AuthorizedKid and AuthorizedManifest. Each one wraps a cryptographic identity document or capability declaration and binds it to a specific .kin name, sealed with the name-owner’s digital signature. This is how Kinetic proves that the person who owns alice.kin is the one who chose to attach a particular key or capability — not a stranger injecting their own.


Why Kinetic Needs This

Without these containers, any node on the network could claim “I am alice.kin” by presenting any key document whatsoever — there would be no proof the name owner agreed. AuthorizedKid provides that proof for identity keys. AuthorizedManifest provides the same proof for capability rules (“this key may only sign name-transfer messages, nothing else”).

There is a second, more subtle problem: a valid cryptographic signature is just bytes. If the signing input is not locked to a specific network context, an attacker could take a signature produced on the production .kin network and replay it on a test network like .corp or .local, where the same key might have different privileges. The signable_bytes methods on both structs solve this by baking the network_id string directly into the bytes that get signed. No network_id → no valid signature.


How It Works

Concept 1 — What Is a KID?

KID stands for Key Identifier. Think of it as a decentralized identity card. It is a structured document that says: “Here are the public keys that represent this identity, here is who controls them, and here is the proof.” KID documents are the Kinetic equivalent of a DID Document in the W3C DID standard.

The actual KidDocument type lives in the kinetic-kid crate (Stage 3). Inside identity.rs it appears as kinetic_kid::document::KidDocument — Kinetic uses the full path because kinetic-types depends on kinetic-kid.

FORWARD DEPENDENCY: KidDocument — defined in kinetic-kid/src/document.rs. Full documentation in docs/learn/kid/ (Stage 3). Conceptually: a structured document containing the public keys and controller metadata for one Kinetic identity.

Concept 2 — What Is a CapabilityManifest?

A CapabilityManifest is a declaration of what a key is permitted to do. Rather than saying “this key can do everything”, Kinetic lets identity owners publish narrow permissions: “this key may only register sub-names”, or “this key may only publish data records, not transfer ownership.” This is capability-based access control applied to a decentralized network.

FORWARD DEPENDENCY: CapabilityManifest — defined in kinetic-kid/src/manifest.rs. Full documentation in docs/learn/kid/ (Stage 3). Conceptually: a signed list of permitted operations scoped to one key.

Concept 3 — AuthorizedKid: Binding an Identity to a Name

-> See: kinetic-types/src/identity.rs — Lines 20 to 57

AuthorizedKid has three fields:

  • name — the .kin name this authorization is for (e.g. "alice.kin").
  • kid_doc — the full KidDocument being attached. Contains public keys.
  • owner_signature — the signature the name-owner produced over signable_bytes(...).

When a node in Kinetic receives an AuthorizedKid, it:

  1. Reconstructs the expected byte sequence using signable_bytes(network_id).
  2. Verifies owner_signature against that byte sequence using the name-owner’s currently-registered public key.
  3. If it checks out, the kid_doc is accepted as the authorized identity for that name.

If any of those three fields is tampered with — the name changed, a different key inserted, the signature swapped — step 2 fails and the document is rejected.

Concept 4 — AuthorizedManifest: Binding Capabilities to a Name

-> See: kinetic-types/src/identity.rs — Lines 60 to 96

AuthorizedManifest has four fields:

  • name — same idea; the .kin name this manifest governs.
  • manifest — the CapabilityManifest declaring permitted operations.
  • kid_doc — an optional KidDocument. Sometimes you publish capabilities alongside the identity document; sometimes you publish them separately. Option<KidDocument> lets both cases exist without duplicating the struct.
  • owner_signature — same role as in AuthorizedKid; the name-owner’s seal of approval.

The Option<T> here is a deliberate design choice: a capability manifest may be published independently of a KID update. Forcing kid_doc to always be present would mean every manifest update re-uploads an unchanged identity document — wasted bandwidth and storage.

Concept 5 — Replay Protection and signable_bytes, Step by Step

This is the most important mechanism in the file. Read it carefully.

Warning

Why replay attacks exist: A cryptographic signature only proves “someone signed this exact sequence of bytes.” It says nothing about which network that signing was intended for. If two networks accept the same key, an attacker can lift a valid signature from one and present it as proof of authorization on the other.

How Kinetic defeats it: Every call to signable_bytes(network_id) begins by embedding the network’s unique identifier string into the byte payload before anything else. Different network, different prefix, different bytes, different signature required.

The byte layout for AuthorizedKid::signable_bytes:

-> See: kinetic-types/src/identity.rs — Lines 42 to 56

Walk through each piece in order:

  1. network_id bytes — e.g. b"kin" for the production network. This is the anti-replay anchor. A .corp node uses b"corp" here, producing a completely different byte sequence even for the same name and document.

  2. b"-auth-kid-v1" — a fixed ASCII tag. Together with step 1 this forms the full prefix kin-auth-kid-v1. The v1 suffix means this layout can be versioned if the format ever needs to change without breaking old signatures.

  3. u32_be(name.len()) — four bytes encoding the byte-length of the name as a big-endian 32-bit unsigned integer. This is called a length prefix. Without it, a parser cannot tell where the name ends and the JSON begins. An attacker could craft a name whose bytes overlap with the start of the JSON, confusing the verifier.

  4. name_bytes — the raw UTF-8 bytes of the .kin name. Length determined by step 3.

  5. u32_be(canon_json.len()) — four bytes encoding the byte-length of the canonical JSON. Same reason as step 3 — unambiguous boundary.

  6. canon_json_bytes — the canonical JSON serialization of the KidDocument. “Canonical” means the JSON is always produced in the same way regardless of how the struct was constructed — same key order, same whitespace rules (none). This is what canonicalize() does (see Concept 6 below).

For AuthorizedManifest::signable_bytes the layout is identical except step 2 uses b"-auth-manifest-v1" and step 6 serializes the CapabilityManifest instead.

-> See: kinetic-types/src/identity.rs — Lines 81 to 95

The implementation uses Vec::with_capacity(...) to pre-allocate the exact number of bytes needed before filling them in — a small but real performance optimization that avoids repeated heap reallocations as data is appended.

Concept 6 — What Does canonicalize() Do?

-> See: kinetic-types/src/identity.rs — Lines 44 and 83

UNVERIFIED — the canonicalize method is defined in kinetic-kid. The following is the expected behavior based on the naming convention and cryptographic context; verify against kinetic-kid documentation when Stage 3 is written.

canonicalize() converts a struct into a canonical JSON string — a JSON representation where key ordering is deterministic and no optional whitespace is included.

The problem it solves: JSON is not unique. The same Rust struct can serialize to {"name":"alice","keys":[...]} or {"keys":[...],"name":"alice"} depending on which serializer ran first. If two nodes produce different byte sequences for the same logical document, their signature hashes will differ and verification will fail randomly. Canonical JSON eliminates this by specifying exactly one valid serialization per document.

The .unwrap_or_default() call after canonicalize() means: if canonicalization somehow fails (e.g. a field contains a value that cannot serialize), produce an empty string rather than crashing. An empty canonical payload would cause signature verification to fail, which is the safe outcome.


Key Pieces

AuthorizedKid

File: kinetic-types/src/identity.rs — Lines 20–57 What it does: Wraps a KID document and proves the .kin name-owner authorized it. Why it matters: Without it, any node could attach any key to any name unchecked.

AuthorizedManifest

File: kinetic-types/src/identity.rs — Lines 60–96 What it does: Wraps a capability manifest and proves the name-owner authorized it. Why it matters: Enables scoped-permission keys; proves the name-owner set the scope.

AuthorizedKid::signable_bytes

File: kinetic-types/src/identity.rs — Lines 42–56 What it does: Produces a deterministic, network-scoped byte sequence for signing. Why it matters: This exact byte sequence is what gets signed and what gets verified. Any mismatch — wrong name, wrong network, wrong document — causes verification to fail.

AuthorizedManifest::signable_bytes

File: kinetic-types/src/identity.rs — Lines 81–95 What it does: Same role as above, for capability manifests. Why it matters: The -auth-manifest-v1 tag prevents manifest signatures and KID signatures from being confused with each other even on the same network.

#[derive(Debug, Clone, Serialize, Deserialize)]

File: kinetic-types/src/identity.rs — Lines 20 and 60 -> See RUST_CONCEPTS.md for an explanation of #[derive(...)].


How This Connects to the Rest of Kinetic

Both structs embed types from kinetic-kid directly:

FORWARD DEPENDENCY: kinetic_kid::document::KidDocument — used in AuthorizedKid.kid_doc (Line 25) and AuthorizedManifest.kid_doc (Line 67). Defined in kinetic-kid. When Stage 3 is documented, cross-reference: docs/learn/kid/XX_document.md.

FORWARD DEPENDENCY: kinetic_kid::manifest::CapabilityManifest — used in AuthorizedManifest.manifest (Line 65). Defined in kinetic-kid. When Stage 3 is documented, cross-reference: docs/learn/kid/XX_manifest.md.

The name field on both structs ties back to the .kin naming model introduced in 02_name.md. An AuthorizedKid is only meaningful if you already understand what a .kin name is and how ownership is established.

The owner_signature field (a Vec<u8> on both structs) is raw bytes. The actual signature verification logic — checking this signature against the name-owner’s key — lives in kinetic-verify (a later stage). kinetic-types only holds the data shape; it does not verify anything itself.

FORWARD DEPENDENCY: Signature verification of owner_signature — handled by kinetic-verify. When that stage is documented, add a cross-reference here.


Quick Reference

ItemWhat it isFile:Line
AuthorizedKidKID doc + name + owner sigidentity.rs:20–28
AuthorizedManifestManifest + name + optional KID + owner sigidentity.rs:60–70
signable_bytes (KID)Produces bytes to sign/verifyidentity.rs:42–56
signable_bytes (manifest)Same, for manifestsidentity.rs:81–95
Byte layout prefix{network_id}-auth-kid-v1 or -auth-manifest-v1identity.rs:43, 82
Length prefix (u32_be)4 bytes before each variable-length fieldidentity.rs:51–53
canonicalize()Deterministic JSON to bytesidentity.rs:44, 83
KidDocumentFORWARD DEPENDENCY — kinetic-kid Stage 3identity.rs:25, 67
CapabilityManifestFORWARD DEPENDENCY — kinetic-kid Stage 3identity.rs:65

Important

The anti-replay rule to remember: network_id is always the first bytes written. Change the network, change the required signature. Period.

The length-prefix rule: Every variable-length field is preceded by 4 bytes (u32, big-endian) saying how long it is. This makes the byte sequence parseable without ambiguity.


Open Questions / Things to Revisit

Note

  1. canonicalize() — UNVERIFIED. The behavior described in Concept 6 is inferred from naming and cryptographic convention. Once Stage 3 (kinetic-kid) is documented, confirm exactly which JSON canonicalization scheme is used (JCS? custom?), whether it sorts keys, and whether it handles nested structures deterministically.

  2. unwrap_or_default() on canonicalize. If canonicalization fails silently (empty string), the resulting signature will also be wrong silently. Is there an error path that surfaces canonicalization failures to callers? Worth checking whether this should return Result<Vec<u8>, Error> rather than Vec<u8>.

  3. Who verifies owner_signature? This file only defines the shape. The verification logic is deferred to kinetic-verify. When that crate is documented, confirm which key (from which field of KidDocument) is used to verify the signature and how the verifier knows which network_id to use.

  4. kid_doc is Option on AuthorizedManifest but required on AuthorizedKid. Was this intentional from the start, or an evolving design? If a manifest is published without a KID, how does a verifier know which public key to check the owner_signature against? This might be answered by looking at how the verifier resolves the name’s current key before checking the manifest.

  5. Version tag v1. The -auth-kid-v1 suffix implies a versioning plan. Is there a migration path documented for what happens when the signing format needs to change? Who increments the version, and how do nodes handle both v1 and v2 during transition?