Skip to main content

Key management

Certichain uses three kinds of keys: wallet keys that people hold, derived keys that the browser recomputes from wallet signatures and never stores, and server secrets set as environment variables. This page lists all of them by name. Values never appear in these docs; example.env lists the variable names.

Key hierarchy​

Wallet keys (held by people)​

KeyHeld byControlsIf lostIf stolen
Safe owner keys (3)Admin owners2 of 3 sign admin actions on the registries and timelock proposalsTwo of three still work; the Safe replaces the ownerOne key alone does nothing; two control the admin, slowed by the admin delay and the timelock
Guardian keyThe guardianPause; suspend an issuer; unpause in contract version 0.5.0The admin grants another guardianUnwanted pauses until the admin revokes the role
Issuer wallet keyEach institutionIssues and manages its credentials; derives its student salts, encryption key and badge keyThe institution can no longer issue or manage with that key; documents encrypted to it cannot be opened by the issuer (holders still open theirs with the package)See Threat model
Holder wallet keyEach student (optional)Receives the inbox, signs disclosures, manages access, derives a recipient keyThe issuer can bind a new wallet by invitationThe thief can act as the holder for those features; the package is a separate secret
Deployer key (PRIVATE_KEY, local only)The person deployingDeploys contracts; holds no admin role after a handoverNothing live depends on itTest ether only; the handbook says to drain and delete it after a handover

Derived keys (never stored)​

Derived valueTyped data signedDerivationLifetime
Student saltStudentSalt (domain Certichain Student Salt, no chain)keccak-256 of the signatureRecomputed when needed
Encryption key (X25519)EncryptionKey (domain Certichain keys, chain id), signed twiceHKDF-SHA256, salt certichain.enc-key.v1, then HPKE DeriveKeyPairMemory, for the page session
Open Badge key (Ed25519)OpenBadgeKey, signed twiceHKDF-SHA256, salt certichain.badge-key.v1Memory, for the page session
Access identity (Semaphore and X25519)AccessIdentity, signed twiceHKDF-SHA256, salt certichain.access-identity.v1, two labelled seedsMemory, or a file for a random identity
Document key (AES-256)None: random per document32 random bytesLives in the package, grants (wrapped), links and key update files

Details are in Signatures and Encryption. Derived keys depend on the wallet signing deterministically; a wallet that does not is refused.

Changing the issuer wallet changes derived values

Every derived value is a function of the issuer wallet's signature. If an institution moves to a new wallet key:

  • its student salts change, so enrolment commitments made with the old key no longer match new ones;
  • its encryption key changes, so the new key cannot open documents wrapped only to the old one;
  • its badge key changes, and badges need a new key binding.

In contract version 0.5.0, retiring a key in favour of a successor keeps earlier credentials valid and manageable by the successor, but the handbook notes that encrypted documents and custom record types stay with the key that created them. Plan key changes with Key retirement.

The key-derivation typed data includes a keyVersion. The app encrypts with version 1 and tries up to four versions when opening, which leaves room for a rotation of the derived key without changing wallets.

Server secrets (environment variables)​

Set as server-only variables (marked Sensitive on the hosting platform). Variables starting with NEXT_PUBLIC_ are public by design and hold addresses, never secrets.

VariablePurposeHolds fundsIf missing
SESSION_SECRETSigns issuer session cookies (HS256); at least 32 charactersNoSign-in and uploads are unavailable
PINATA_JWT (preferred), or PINATA_API_KEY and PINATA_API_SECRET_KEYThe scoped JWT pins, lists and removes containers; the key pair can only pinNo (billing)Uploads fail; existing pins stay. Without the JWT, issuers cannot find or remove their own copies
UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKENNonces, rate limits, monitor stateNoRoutes that need the store report that configuration is missing
STATUS_LIST_SIGNER_KEYSigns status lists; its address is published as NEXT_PUBLIC_STATUS_LIST_PUBLISHERNo, must stay emptyStatus lists are off
API_SIGNER_KEYSigns verification API answers; its address is published as NEXT_PUBLIC_API_SIGNERNo, must stay emptyThe API answers that it is unavailable
API_KEYSSHA-256 hashes of API keys for higher limitsNoCallers get anonymous limits
CRON_SECRETLets the scheduler call the monitorNoThe monitor refuses calls
OPS_WEBHOOK_URLWhere alerts are postedNoAlerts are not delivered
OPS_PROBE_API_KEYAPI key the canary probe usesNoThe probe uses anonymous limits
RPC_URL, RPC_URLS (server), NEXT_PUBLIC_RPC_URL, NEXT_PUBLIC_RPC_URLS (browser)Chain reads; may embed provider keysNo (billing)Fallback endpoints take over; with none, reads fail
INFURA_API_KEY, ETHERSCAN_API_KEYDeployment and contract verification scriptsNoDeploy and verify scripts cannot run

The monitor compares the address derived from each server signing key with the published address and alerts on a mismatch (src/lib/server/ops/wiring.ts). It also warns ahead of expiry dates listed in OPS_KEY_EXPIRIES (OPS_WARN_DAYS).

Rotation​

The handbook's keys runbook (docs/process/runbooks/keys.md) gives step-by-step procedures. In summary:

KeyHow to rotateWho is affected
Status list publisher, API signerCreate a new funds-free key, set it and its published address together, announce the new addressVerifiers who pinned the old address must update the pin
Pinata and RPC tokensCreate a new token, replace the variable, revoke the old tokenNo one, if done in that order
SESSION_SECRETReplace the valueEvery issuer signs in again
CRON_SECRET, OPS_WEBHOOK_URLReplace in the scheduler and the server togetherMonitoring only
Safe ownerSafe owner change, signed by the thresholdAdmin owners
GuardianAdmin revokes and grants GUARDIAN_ROLEGuardian only
Issuer walletRegister the new key and, in contract version 0.5.0, retire the old one in favour of it through the adminThe institution; see the warning above
Document keyReplace key on the issuer dashboard, then give the holder the key update fileThe holder; recipients of old grants and links lose access to new copies

Rules for anyone handling keys​

  • Never paste a private key, token or secret into code, logs, documents, issues or chat.
  • Server signing keys must hold no funds; if one receives funds, treat it as exposed.
  • Keep .env files out of version control (they are gitignored); use example.env for names only.
  • Each person who holds a wallet key should use a standard deterministic wallet account, not a contract wallet, for the features that derive keys.

Source files​

  • example.env (variable names)
  • src/lib/server/config.ts, src/lib/server/session.ts
  • src/lib/server/ops/wiring.ts
  • src/lib/commitments.ts, src/lib/doc-crypto/identity-key.ts, src/lib/vc/openbadges/standard/key.ts, src/lib/zk/access-identity.ts
  • src/services/documents/wallet-key-session.ts, src/services/domain/document-service.ts
  • blockchain/contracts/v03/IssuerRegistry.sol
  • docs/process/runbooks/keys.md, docs/process/runbooks/admin-keys.md (handbook)

Tests​

  • src/lib/server/config.test.ts
  • src/lib/doc-crypto/identity-key.test.ts, src/lib/vc/openbadges/standard/key.test.ts, src/lib/zk/access-identity.test.ts, src/lib/commitments.test.ts
  • blockchain/test/v03/KeyLineage.test.ts, blockchain/test/invariants/KeyLineage.invariants.test.ts
  • E2E journey e2e/journeys/key-retirement.journey.ts