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.

TierBackendStatus
AYubiKey FIDO2 hmac-secretImplemented (needs CLI wiring)
BPlatform secure elementPlanned
CCredential manager (Bitwarden, Keychain)Planned
DEncrypted 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

Graph