Identity
StyreneIdentity is a deterministic key hierarchy. One 32-byte root secret derives all protocol-specific keys — SSH, git signing, age encryption, WireGuard, RNS mesh, and per-agent delegation — via HKDF-SHA256 with domain separation.
The root secret lives in an encrypted file (~/.config/styrene/identity.key), protected by argon2id + ChaCha20Poly1305. Optionally backed by a YubiKey via FIDO2 hmac-secret.
Why
Without StyreneIdentity, you have an SSH key on this machine, a different one on your laptop, a GPG key from three years ago, git signing configured on one machine but not the other, and an age key somewhere in your dotfiles — none of them related, none recoverable from the others.
StyreneIdentity replaces all of that with one root and a derivation tree. Run nex identity init once, and every key you need exists implicitly — same identity, same keys, any machine, recoverable from a single backup or a YubiKey tap.
Creating an identity
Via nex (workstation setup)
nex identity init
# Prompts for passphrase, generates ~/.config/styrene/identity.key
# Displays identity hash
nex identity show
# Displays hash, pubkey, SSH host key, age key
Via styrene daemon (first start)
When styrene daemon starts and no identity file exists, it generates one automatically. For containers, use --ephemeral to create a non-persistent identity.
Derivation tree
root_secret (32 bytes)
HKDF-Extract(salt="styrene-identity-v1") = PRK
│
├─ Signing → Ed25519 (THE identity — mesh, git, attribution)
├─ RnsEncryption → X25519 (RNS key exchange)
├─ Age → X25519 (file encryption)
├─ WireGuard → Curve25519 (VPN tunnel)
├─ SshHost → Ed25519 (machine identity)
├─ Yggdrasil → Ed25519 (overlay network)
├─ I2pSigning → Ed25519 (I2P destination signing)
├─ I2pEncryption → X25519 (I2P destination encryption)
├─ Tor → Ed25519 (onion v3 service key)
│
├─ I2P services (per-service, two-level HKDF)
│ └─ "my-service" → Ed25519 (per-service I2P destination)
│
├─ Onion services (per-service, two-level HKDF)
│ └─ "my-hidden" → Ed25519 (per-service onion v3 key)
│
├─ SSH user keys (per-label, two-level HKDF)
│ ├─ "github" → Ed25519
│ └─ "work" → Ed25519
│
└─ Agent keys (per-agent, two-level HKDF)
├─ "omegon-primary" → Ed25519
└─ "omegon-cleave-0" → Ed25519
Every key is deterministic: same root = same keys, always. Recovery of the root recovers everything.
Identity hash
The canonical identity hash is SHA-256 of the signing Ed25519 public key, truncated to 16 bytes (32 hex chars). This is the short, human-readable identifier used across Signum, styrened, and all mesh operations.
SSH key management
# Register an SSH key label
nex identity ssh --add github
# Export the pubkey (stdout, pipeable)
nex identity ssh github
# ssh-ed25519 AAAA... styrene-ssh-user-github
# List registered labels with fingerprints
nex identity ssh --list
Each label derives a unique Ed25519 key via two-level HKDF. The key material never touches disk — it’s derived on demand from the root secret.
Git signing
# Configure git to sign commits with your identity
nex identity git
# Prompts for name/email, saves to config
# Applies: gpg.format=ssh, user.signingkey, commit.gpgsign=true
# Check current config
nex identity git --show
Agent keys (e.g., omegon) are derived from the same root, so there’s a cryptographic chain proving delegation — not just a Co-Authored-By string anyone could type.
Profile signing
Profiles are the unit of fleet configuration. Sign them to prove authorship and prevent tampering:
# Sign a profile (resolves full extends chain, canonicalizes, signs)
nex profile sign styrene-lab/edge-profile
# Output: styrene-lab_edge-profile.signed.toml
# Verify a signed profile (public-key only, no passphrase needed)
nex profile verify styrene-lab_edge-profile.signed.toml
The signature covers:
- A canonicalization version header (
nex-profile-sig-v1) - The source ref (prevents rebinding — can’t present a profile from source A as source B)
- The fully resolved, merged TOML content (all extends/compose layers collapsed)
The signer’s public key is embedded in the signed output, so any machine can verify without needing the signer’s identity file.
Signum linking
# Enroll with a Signum hub for SSO across Forgejo, Grafana, etc.
nex identity link https://signum.styrene.io --code <INVITE>
This bridges the local mesh identity to the web services layer. The same Ed25519 pubkey signs a challenge, linking the StyreneIdentity to an OIDC account.
Linkability
All keys derived from one root are cryptographically linked. This is by design — it’s what makes attribution, recovery, and agent delegation work. But it means derived keys cannot provide anonymity.
For anonymous or pseudonymous use:
# Separate persistent identity (unlinkable to primary)
nex identity init --path ~/.config/styrene/pseudonym.key
# Ephemeral root (CSPRNG, never persisted, zeroized on drop)
# Available programmatically via RootSecret::ephemeral()
See the unlinkability model for the full threat model and anti-patterns.
Signer tiers
The root secret can be stored at different security levels. All tiers produce the same 32 bytes — they’re different access paths to the same identity.
| Tier | Backend | Status |
|---|---|---|
| A | YubiKey FIDO2 hmac-secret | Implemented (needs CLI wiring) |
| B | Platform secure element | Planned |
| C | Credential manager (Bitwarden, Keychain) | Planned |
| D | Encrypted file (argon2id + ChaCha20Poly1305) | Default |
Library
The styrene-identity crate is published on crates.io. Both nex and styrene use it as a dependency.
styrene-identity = { version = "0.2", features = ["file-signer"] }
See also
- Styrene daemon — uses identity for mesh signing
- Getting Started — install and create your first identity
- Components — where identity fits in the architecture