-- libertaria/identity.jan -- Cryptographic identity for sovereign agents -- Exit is Voice: Identity can be rotated, expired, or burned module Identity exposing ( Identity , create, rotate, burn , is_valid, is_expired , public_key, fingerprint , sign, verify ) import crypto.{ed25519, hash} import time.{timestamp, duration} -- Core identity type with cryptographic material and metadata type Identity = { public_key: ed25519.PublicKey , secret_key: ed25519.SecretKey -- Encrypted at rest , created_at: timestamp.Timestamp , expires_at: ?timestamp.Timestamp -- Optional expiry , rotated_from: ?fingerprint.Fingerprint -- Chain of custody , revoked: bool } -- Create new sovereign identity -- Fresh keypair, no history, self-sovereign fn create() -> Identity let (pk, sk) = ed25519.generate_keypair() let now = timestamp.now() { public_key = pk , secret_key = sk , created_at = now , expires_at = null , rotated_from = null , revoked = false } -- Rotate identity: New keys, linked provenance -- Old identity becomes invalid after grace period fn rotate(old: Identity) -> (Identity, Identity) assert not old.revoked "Cannot rotate revoked identity" let (new_pk, new_sk) = ed25519.generate_keypair() let now = timestamp.now() let old_fp = fingerprint.of_identity(old) let new_id = { public_key = new_pk , secret_key = new_sk , created_at = now , expires_at = null , rotated_from = some(old_fp) , revoked = false } -- Old identity gets short grace period then auto-expires let grace_period = duration.hours(24) let expired_old = { old with expires_at = some(now + grace_period) } (new_id, expired_old) -- Burn identity: Cryptographic deletion -- After burn, no messages can be signed, verification still works for history fn burn(id: Identity) -> Identity { id with secret_key = ed25519.zero_secret(id.secret_key) , revoked = true , expires_at = some(timestamp.now()) } -- Check if identity is currently valid fn is_valid(id: Identity) -> bool not id.revoked and not is_expired(id) -- Check if identity has expired fn is_expired(id: Identity) -> bool match id.expires_at with | null -> false | some(t) -> timestamp.now() > t -- Get public key for sharing/verification fn public_key(id: Identity) -> ed25519.PublicKey id.public_key -- Get fingerprint (short, unique identifier) fn fingerprint(id: Identity) -> fingerprint.Fingerprint fingerprint.of_key(id.public_key) -- Sign message with this identity fn sign(id: Identity, message: bytes.Bytes) -> signature.Signature assert is_valid(id) "Cannot sign with invalid identity" ed25519.sign(id.secret_key, message) -- Verify signature against this identity's public key fn verify(id: Identity, message: bytes.Bytes, sig: signature.Signature) -> bool ed25519.verify(id.public_key, message, sig)