Key management
Who holds which key, where it lives, how it is rotated and what a compromise allows. The authority each key has
on-chain is in AUTHORITY_MATRIX.md; this document is about the keys themselves.
Inventory
| Key | Signs | Lives in | Held by |
|---|---|---|---|
| H1…Hn (issuer signers) | Issue, burn, clawback, issuer configuration (ceremony.Quorum: 4 signers 2 / 3; launch: 1 signer, 1 / 1) | Launch: one software key on the signer's laptop (§1a; owner decision 2026-10-31) | A person, never the platform |
| T1…Tn (Treasury signers) | Refill and burn from COLD, COLD configuration, signer changes on HOT/SPONSOR/FUNDER | Launch: the same key as the issuer signer | The same person |
role:AUTH | Trustline authorize / revoke (issuer, weight 1 = LOW only) | Platform signer | Worker |
role:HOT | Payments from the hot Treasury (sales float) | Platform signer | Worker |
role:SPONSOR | Sponsoring new customer accounts' reserves | Platform signer | Worker |
role:FUNDER | Topping up channel accounts with XLM | Platform signer | Worker |
role:CHANNEL | Transaction source (sequence and fee) for every platform transaction | Platform signer | Worker |
custody:CUSTOMER | Customer accounts (SELL, TRANSFER) | Platform signer | Worker |
onboard:<customer> | Only the customer account's creation transaction, which sets its master weight to 0 | Derived per customer from the onboarding seed (SIGNER_ONBOARD_SEED_FILE with the vault backend), never stored | Worker |
| Database, MFA encryption key, session secrets | — | Secret store of the deployment | Operators |
Human keys never touch the platform: the platform builds an envelope, the signer verifies it on their own device and
uploads only a signature, which the platform verifies against the signer's public key and the hash
(txmanager.AddSignature). Configuration carries only their public keys (ISSUER_SIGNERS, TREASURY_SIGNERS).
Signer backends
SIGNER_BACKEND selects how the online keys are held.
| Backend | Status | Allowed |
|---|---|---|
derived | Implemented. Every online key is derived with HKDF-SHA256 from one 32-byte SIGNER_MASTER_SEED and a per-role label | APP_ENV local, test or uat and a non-public network. It refuses APP_ENV=production and the Mainnet passphrase (signer.NewDerived) |
vault | Implemented (§2). HashiCorp Vault or OpenBao Transit holds the online keys as non-exportable Ed25519 keys and signs hashes on request | Every environment; the production choice (owner decision 2026-10-31) |
Devsigners (stand-in H/T) | Implemented for demos and tests | Local standalone network only |
The derived master seed is therefore acceptable for local, demo and Testnet use only. The demo seed in
deploy/demo/demo.env is public by design and works only on the local network.
1a. The launch signer key (software key on a laptop)
The owner chose one human signer holding a software key on a laptop. To keep that as safe as it can be:
- A laptop used only for signing: full-disk encryption, a strong login password, automatic screen lock, OS updates, no other software, browser use only for the admin console.
- The key is created on that laptop with the Stellar CLI (
stellar keys generate dara-signer), and kept in its encrypted keystore or OS keychain. Only the public key (G…) leaves the laptop, intoISSUER_SIGNERSandTREASURY_SIGNERS. - Signing: copy the envelope from Admin → Signing; decode it on the laptop
(
stellar xdr decode --type TransactionEnvelope) and check the network passphrase, source accounts, operations, assets and amounts; sign it (stellar tx sign --sign-with-key dara-signer --network mainnet); paste the printed signed envelope with your public key on the Signing page (a bare base64 signature is accepted too). The server checks the signature against the proposal's hash. Never sign an envelope that did not come from the Signing page. (Commands as in recent Stellar CLI releases; check them againststellar --helpof the installed version.) - Backup: one encrypted copy of the secret key, offline (a USB key in a safe, or paper), tested once a quarter by checking that its public key matches.
- If the laptop is lost or may be compromised: treat it as an incident at once (
../operations/INCIDENT_RESPONSE.md, key compromise). If the key is still available, rotate it immediately (§Rotation, human signer).
2. Vault Transit (online keys)
SIGNER_BACKEND=vault. The operator runs Vault or OpenBao, then creates the keys and the backend's token with
deploy/vault/setup-transit.sh (PREFIX=dara-mainnet-):
- keys
<prefix>auth,hot,sponsor,funder,channel,custody-customer: Ed25519, not exportable, no plaintext backup, deletion not allowed; - a policy that allows only
readon those keys andupdateon theirsignpath, and a periodic orphan token bound to it (renew within 768 h). The backend can never export, rotate, delete or reconfigure a key.
The backend refuses a key that is exportable, allows plaintext backup or is derived; it pins each key to the
version whose public key it resolved (a rotation inside Vault would otherwise change the Stellar signer silently)
and verifies every signature locally before using it. dara keys prints the public keys for the ceremony.
Secrets come from files: VAULT_TOKEN_FILE, SIGNER_ONBOARD_SEED_FILE (64 hex characters, from
openssl rand -hex 32); TLS with VAULT_CACERT. Health feeds the trading gate (signer_unavailable).
make vault-test runs the backend against a throwaway OpenBao and applies a Vault-signed transaction on local
Stellar.
Rotating an online key held in Vault is the rotation in the next section: create a new Vault key (new name), add it to the account on-chain, remove the old one, then point the backend at it.
Rotation
| Key | How | Approval |
|---|---|---|
role:HOT, role:SPONSOR, role:FUNDER | Add the new key as signer of the account, remove the old one (SetOptions). These accounts carry every Treasury signer (weight 10 each) beside the online key, high threshold 20 (ceremony.OnlineLayout) | The online key plus one Treasury signer, or two Treasury signers |
role:AUTH (issuer signer, weight 1) | Replace on each issuer account | The issuer's high threshold (launch: the one signer) |
role:CHANNEL | Channel accounts have only the channel key (weight 1). Rotation = create new channel accounts with the new key, register them, then merge the old ones into the funder | Operations; no human signature (channels hold only fee XLM) |
custody:CUSTOMER | Customer accounts have only the custody key (weight 1, master 0). Planned rotation: the old key adds the new one and removes itself, in batches. If the old key is compromised it cannot be trusted to do that: freeze, claw back, re-issue to fresh accounts | Rotation: operations with four-eyes; compromise: CLAWBACK_POLICY.md |
| Human signer | Remaining signers replace the signer at the high threshold. With one signer, only that signer can replace its own key, and only while it still has it | Four-eyes in the console plus the on-chain threshold |
SIGNER_MASTER_SEED (non-production) | Rotating it changes every derived key: a new account ceremony | — |
After any rotation the account manifest is unchanged (accounts stay), but the startup check (app.VerifyAccounts)
compares on-chain signers with the configured keys and refuses to start until they agree.
Backup and loss
- Human keys: with three or more signers, losing one is survivable (the thresholds from
ceremony.Quorumstay reachable;TestLayoutsForEachSignerCount) and the others rotate it out. With the launch configuration of one signer there is no tolerance: losing the key locks the issuer and the cold Treasury for good (accepted risk,../decisions/OWNER_DECISIONS_20261031.md). The owner chose no hardware key; an encrypted offline copy of the secret key (§1a) is still strongly advised. - Online keys (Vault): Vault's own snapshots (Raft) back up the sealed keys; the unseal keys or the auto-unseal key are held as the Vault policy requires. HOT, SPONSOR, FUNDER and the issuer's AUTH slot also have human signers who can add a replacement key; channels are simply replaced.
- Customer custody key: it is the only signer of every customer account, so it must be backed up like the issuer's keys. If it were lost, the tokens are still recoverable by the issuer (clawback, then re-issue to new accounts); the accounts' XLM reserves are sponsored and are released when the accounts are merged or abandoned.
Compromise
The impact of each key is in AUTHORITY_MATRIX.md §2. Detection is the indexer: any movement or authority change
without a matching intent opens a CRITICAL incident that stops trading. Response: ../operations/INCIDENT_RESPONSE.md.
Rules enforced in code
- The signer is the only package that touches private keys; business code passes references.
- A signer is bound to one network at construction; Stellar signatures cover the hash, which commits to the network passphrase, so signatures cannot be replayed across networks.
- Dev signers and the derived backend refuse Mainnet;
bootstrap-local,seedanddemorefuse every network but local. - Secrets never appear in logs (
config.LogValueredacts them); seed files are git-ignored.