AttendiBotAttendiBot

Signing & Verification

Ed25519 signing, keys, and public verification.

AttendiBot's core differentiator is cryptographically verifiable voice attendance. Every completed session and archived period snapshot is signed with Ed25519, so anyone can prove records were not tampered with.

What gets signed

Voice sessions

When a member leaves a tracked voice channel, AttendiBot:

  1. Finalizes the session (duration, user, channel, timestamps)
  2. Serializes the payload to canonical JSON
  3. Computes a SHA-256 hash of the canonical payload
  4. Signs the canonical bytes with the server's Ed25519 private key
  5. Stores the session UUID, payload hash, and signature in PostgreSQL

Period snapshots

When a leaderboard period resets, AttendiBot:

  1. Builds a snapshot payload (period metadata + all leaderboard entries)
  2. Signs the snapshot with the same Ed25519 key
  3. Archives the signed snapshot for later verification

Key lifecycle

Loading diagram…

1. Generate keys (once per server)

An admin generates an Ed25519 keypair with a passphrase:

  • Dashboard → Signing → Generate keys
  • Or /admin generate-keys passphrase:...

The private key is encrypted with AES-256-GCM using a key derived from the passphrase via Argon2id (memory-hard KDF). Only the encrypted blob and public key are stored in the database.

2. Unlock signing (after every restart)

The private key exists in memory only while unlocked. After every backend restart:

  • Dashboard → Signing → Unlock signing
  • Or /admin unlock-signing passphrase:...

If signing is locked, new voice sessions are not signed and completed sessions are dropped on leave.

3. Share the public key

Anyone verifying sessions needs the server's public key:

  • Dashboard → Signing → Public key
  • Or /admin public-key

The public key is safe to share publicly — it cannot sign new records.

Verification workflow

Public session verifier

Anyone can verify a session without logging in:

  1. Go to attendibot.com/#verifier
  2. Enter the session UUID
  3. AttendiBot fetches the session, recomputes the payload hash, and verifies the Ed25519 signature against the server's public key

API endpoint: GET /verify/session/{session_id}

Response:

{ "valid": true }

Verify in Discord

Admins can verify sessions and period archives directly:

/admin verify-session session_id:550e8400-e29b-41d4-a716-446655440000
/admin verify-period period_index:1

Verify period archives (public)

API endpoint: GET /verify/period/{guild_id}/{period_index}

Returns whether the archived period snapshot signature is valid.

Cryptographic details

ComponentAlgorithm
SigningEd25519 (ed25519-dalek)
Payload hashingSHA-256 of canonical JSON
Private key encryptionAES-256-GCM
Key derivationArgon2id (m=19456, t=2, p=1)
Public key encodingBase64

The signing implementation lives in backend/crates/shared/src/signing.rs:

  • generate_keypair() — create and encrypt keypair
  • decrypt_signing_key() — unlock with passphrase
  • sign_payload() — canonical JSON → hash → Ed25519 signature
  • verify_signature() — recompute hash, verify signature

Security best practices

  1. Use a strong passphrase (≥ 8 characters; longer is better)
  2. Unlock after every restart — automate alerts if signing stays locked
  3. Limit Manage Server access — only trusted admins can generate/unlock keys
  4. Share public keys openly — verification requires the public key, not the private key
  5. Back up your database — encrypted private keys and all session records live in PostgreSQL
  6. Key rotation — generating new keys invalidates verification of sessions signed with the old key; plan rotation carefully

What happens when signing fails

StateBehavior
No keys configuredSessions not recorded; admin prompted to generate keys
Keys locked (post-restart)Active sessions tracked in memory but dropped on leave
Wrong passphrase on unlockUnlock fails; signing remains locked
Invalid signature on verifyVerifier returns valid: false
Edit on GitHub

On this page