styrene-identity
Deterministic key hierarchy for Styrene mesh nodes. One root secret derives all protocol-specific keys — RNS, Yggdrasil, WireGuard, SSH, age, git signing, and per-agent delegation keys — via HKDF-SHA256 with domain separation.
Published on crates.io.
Quick start
[]
= "0.1"
Generate an identity
use ;
use IdentitySigner;
let provider = Boxnew;
let signer = new;
signer.generate.expect;
Derive keys from a root secret
use ;
let root_secret = ; // in practice, from a signer
let deriver = new;
// Flat-purpose keys
let git_seed = deriver.derive; // Ed25519 seed
let age_key = deriver.derive; // X25519 private key
let ssh_seed = deriver.derive; // Ed25519 seed
// Parameterized keys (two-level HKDF — structurally collision-free)
let github_ssh = deriver.derive_ssh_user_key.unwrap;
let agent_key = deriver.derive_agent_key.unwrap;
// All keys are deterministic: same root → same keys, always.
Public key derivation
use ;
use ;
let deriver = new;
let git_vk = ed25519_verifying_key;
let age_pk = x25519_public_key;
Lifecycle management with IdentityVault
use IdentityVault;
use ClosurePassphraseProvider;
let provider = Boxnew;
let vault = with_default_path;
// Create — refuses to overwrite (O_EXCL, no TOCTOU race)
vault.init.unwrap;
// Backup before risky operations
vault.backup.unwrap;
// Check existence
assert!;
Derivation hierarchy
root_secret (32 bytes)
│
HKDF-Extract(salt="styrene-identity-v1", IKM=root_secret) = PRK
│
├─ Expand(PRK, "styrene-rns-encryption-v1") → RNS X25519
├─ Expand(PRK, "styrene-rns-signing-v1") → RNS Ed25519
├─ Expand(PRK, "styrene-yggdrasil-v1") → Yggdrasil Ed25519
├─ Expand(PRK, "styrene-wireguard-v1") → WireGuard Curve25519
├─ Expand(PRK, "styrene-ssh-host-v1") → SSH host Ed25519
├─ Expand(PRK, "styrene-age-v1") → age X25519
├─ Expand(PRK, "styrene-git-signing-v1") → git signing Ed25519
│
├─ SSH user keys (two-level HKDF)
│ salt="styrene-identity-ssh-user-v1"
│ ├─ "github" → per-host SSH Ed25519
│ └─ "work" → per-host SSH Ed25519
│
└─ Agent signing keys (two-level HKDF)
salt="styrene-identity-agent-v1"
├─ "omegon-primary" → agent commit signing Ed25519
└─ "omegon-cleave-0" → worker commit signing Ed25519
Parameterized key families use two-level HKDF with distinct salts per family. Collisions between flat purposes, SSH user keys, and agent keys are structurally impossible — they derive from different IKM, different salts, and different HKDF trees.
Signer tiers
The IdentitySigner trait abstracts over four storage tiers. All tiers
produce the same 32-byte root secret — they are different access paths
to the same identity.
| Tier | Backend | Feature | Status |
|---|---|---|---|
| A | YubiKey FIDO2 hmac-secret | yubikey |
Implemented |
| B | iOS Secure Enclave / Android StrongBox | — | Planned |
| C | Bitwarden / 1Password / macOS Keychain | — | Planned |
| D | Encrypted file (argon2id + ChaCha20Poly1305) | file-signer (default) |
Implemented |
SignerChain tries signers in tier order (A→D), using the first available:
use SignerChain;
let chain = new_sorted;
let root = chain.root_secret.await?;
Feature flags
| Feature | Default | Enables |
|---|---|---|
file-signer |
yes | FileSigner, IdentityVault (argon2, chacha20poly1305) |
signing |
via file-signer | pubkey module (ed25519-dalek, x25519-dalek) |
yubikey |
no | YubiKeySigner (FIDO2 hmac-secret) |
ssh-agent |
no | StyreneAgent SSH agent protocol |
Minimal dependency footprint — disable default-features and pick only
what you need:
# Just the derivation hierarchy, no file I/O or crypto
= { = "0.1", = false }
# Derivation + public key helpers, no file signer
= { = "0.1", = false, = ["signing"] }
# Full file-based identity (default)
= "0.1"
File format
The Tier D identity file (~/.config/styrene/identity.key) is 97 bytes:
STID [version:1] [salt:32] [nonce:12] [ciphertext:32+16]
4B 1B 32B 12B 48B
- Encryption: argon2id (m=64MiB, t=3, p=1) → ChaCha20Poly1305
- Permissions: 0o600, set atomically at creation via
O_EXCL - Backward compat: legacy 92-byte headerless files (pre-v1) are still readable
Identity hash
The canonical identity hash is SHA-256 of the RNS signing Ed25519 public key, truncated to 16 bytes (32 hex chars). This is the mesh identity used by Signum, styrened, and cross-service attribution:
use ;
use ed25519_verifying_key;
use ;
let deriver = new;
let seed = deriver.derive;
let pubkey = ed25519_verifying_key;
let hash = digest;
let identity_hash = encode; // 32 hex chars
Git commit signing
Derived keys work with git's SSH signing (gpg.format = ssh). Agent keys
enable cryptographic distinction between human and agent commits:
| Committer | Key | Comment in git log --show-signature |
|---|---|---|
| Human | GitSigning |
styrene-git-signing |
| Agent | Agent("omegon-primary") |
styrene-agent:omegon-primary |
All keys trace back to the same root — one identity, multiple signers.
Security properties
- Zeroize-on-drop for all secret material (
RootSecret,KeyDeriverPRK,DerivedKeys, derived seeds) - No private keys on disk — the SSH agent derives keys in memory per request
- Domain-separated HKDF — fixed salt prevents collision with any other HKDF usage
- Hardened KDF — argon2id params exceed OWASP minimums
- Atomic file creation —
O_EXCLprevents overwrites with no TOCTOU race - Credential injection — passphrases and PINs via traits, never environment variables
See SECURITY.md for the full threat model and accepted risks.
Test vectors
From a root secret of 0x42 repeated 32 times:
RnsEncryption = aefdbd63fb6746c2edb73bba3bcb34f61909077f65fe033c9372b55f6ace0c0c
GitSigning = 6eb3d3ef12a2447f6de281d6f896eba20ad0b0add3bc6fce80499f36b7343842
SSH(github) = 3c261af80e084a637fd20e0f7274a4106702894f0d23c47e855f6c9adce20d75
Agent(omegon) = 4dd66edcda091a5e3d15aa3fb8ec32d81e212d94760b61915b1d6f204b0672e2
These are pinned in the test suite. Any implementation of the derivation hierarchy must reproduce them.
Ecosystem usage
| Crate/Binary | Dependency | Purpose |
|---|---|---|
| nex | styrene-identity = "0.1" |
nex identity init/show/link — generate and manage identities |
| aether | path dep | Mesh node identity and RBAC |
| auspex | path dep (signing only) | Operator identity for monitoring agents |
| vox | path dep | LXMF mesh identity |
| styrened | workspace member | Daemon identity — RNS, SSH agent, mesh signing |
License
MIT