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.kinname this authorization is for (e.g."alice.kin").kid_doc— the fullKidDocumentbeing attached. Contains public keys.owner_signature— the signature the name-owner produced oversignable_bytes(...).
When a node in Kinetic receives an AuthorizedKid, it:
- Reconstructs the expected byte sequence using
signable_bytes(network_id). - Verifies
owner_signatureagainst that byte sequence using the name-owner’s currently-registered public key. - If it checks out, the
kid_docis 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.kinname this manifest governs.manifest— theCapabilityManifestdeclaring permitted operations.kid_doc— an optionalKidDocument. 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 inAuthorizedKid; 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:
-
network_idbytes — e.g.b"kin"for the production network. This is the anti-replay anchor. A.corpnode usesb"corp"here, producing a completely different byte sequence even for the same name and document. -
b"-auth-kid-v1"— a fixed ASCII tag. Together with step 1 this forms the full prefixkin-auth-kid-v1. Thev1suffix means this layout can be versioned if the format ever needs to change without breaking old signatures. -
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. -
name_bytes— the raw UTF-8 bytes of the.kinname. Length determined by step 3. -
u32_be(canon_json.len())— four bytes encoding the byte-length of the canonical JSON. Same reason as step 3 — unambiguous boundary. -
canon_json_bytes— the canonical JSON serialization of theKidDocument. “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 whatcanonicalize()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
| Item | What it is | File:Line |
|---|---|---|
AuthorizedKid | KID doc + name + owner sig | identity.rs:20–28 |
AuthorizedManifest | Manifest + name + optional KID + owner sig | identity.rs:60–70 |
signable_bytes (KID) | Produces bytes to sign/verify | identity.rs:42–56 |
signable_bytes (manifest) | Same, for manifests | identity.rs:81–95 |
| Byte layout prefix | {network_id}-auth-kid-v1 or -auth-manifest-v1 | identity.rs:43, 82 |
| Length prefix (u32_be) | 4 bytes before each variable-length field | identity.rs:51–53 |
canonicalize() | Deterministic JSON to bytes | identity.rs:44, 83 |
KidDocument | FORWARD DEPENDENCY — kinetic-kid Stage 3 | identity.rs:25, 67 |
CapabilityManifest | FORWARD DEPENDENCY — kinetic-kid Stage 3 | identity.rs:65 |
Important
The anti-replay rule to remember:
network_idis 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
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.
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 returnResult<Vec<u8>, Error>rather thanVec<u8>.Who verifies
owner_signature? This file only defines the shape. The verification logic is deferred tokinetic-verify. When that crate is documented, confirm which key (from which field ofKidDocument) is used to verify the signature and how the verifier knows whichnetwork_idto use.
kid_docisOptiononAuthorizedManifestbut required onAuthorizedKid. 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 theowner_signatureagainst? This might be answered by looking at how the verifier resolves the name’s current key before checking the manifest.Version tag
v1. The-auth-kid-v1suffix 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 bothv1andv2during transition?