Skip to main content

Encryption & keys

Your node handles three kinds of key:

  • An on-chain signing key that is your node's identity. It is a per-node hot key that you provision and protect.
  • A short-lived ephemeral sealing key that prompts are sealed to. The node generates, rotates, and erases it on its own.
  • A per-request response key that encrypts the reply. It is established fresh for each request and bound to its ticket.

Your signing key​

Each node registers an Algorand signing key on-chain. It is the node's identity:

  • It signs the tickets you quote and the receipts you issue.
  • It authorizes the escrow opens and settlements that pay you.
  • It signs your node's rotating ephemeral sealing key, binding that key to your on-chain identity so a relay can't substitute its own.

Clients verify everything your node claims against this key, so:

  • It must be online. Reserve, settle, and the automatic key rotation all need it. A node whose signing key is unavailable can't serve.
  • It must be protected. Anyone with it can quote, receipt, and settle as you, and can drain the signing account's ALGO float. Treat it as a hot wallet with your revenue behind it.

The key is per node, not per operator. Your operator owner address, which receives payouts, is separate and stays cold, so a compromised node key exposes only that node and the tickets in flight through it.

Provisioning the signing mnemonic​

The signing account's 25-word mnemonic is the only secret you provision. The node refuses to start if the mnemonic for zs.signing_addr isn't loaded. Load it one of two ways:

  • Environment variable (simplest). The node picks up any env var whose name ends in _MNEMONIC. The label before the suffix is informational; the node matches by the address the mnemonic derives to:

    export OPERATOR_SIGNING_MNEMONIC="word1 word2 ... word25"

    For systemd, put it in a secrets.env file (mode 0600) and reference it with EnvironmentFile= rather than baking it into the unit.

  • Cloud secret manager (recommended for production). Set ZS_MNEMONIC_URLS to a comma-separated list of name=url pairs. Supported schemes: awssecretsmanager://, awsparamstore://, gcpsecretmanager://, azurekeyvault://, and file://. Each backend uses its standard credential discovery.

warning

Generate a fresh signing account; don't reuse a wallet you care about. It is a hot key that lives on the node. Your earnings settle to your separate owner address, not to the signing account.

How prompts reach you sealed​

The user's device encrypts each prompt to your node before sending it, so only ciphertext crosses the network. The relay that forwards a request can't read it (see Relays).

info

What "age" means here. age is an audited encryption format. ZeroSignal uses its hybrid public-key mode: an ephemeral X25519 key agreement establishes a shared secret, and ChaCha20-Poly1305 then encrypts and authenticates the payload. An "age recipient" or "age keypair" in these docs is the public or private half of one of these X25519 keys.

Your node holds no long-lived encryption key. There is no key file, no age-keygen step, no node.key, and no zs.identity_path setting. The register-node form takes only your signing address and base URL, so no encryption public key goes on chain. Instead, the node generates a short-lived ephemeral age recipient in memory, signs it under your signing key, and advertises it on /v1/zs/details as four fields:

FieldWhat it is
ephemeral_age_pubkeyThe public recipient clients seal to.
ephemeral_issued_at / ephemeral_expiryIts lifetime window.
ephemeral_sigThe signature under your signing key.

Before a client (or the proxy on its behalf) sends you anything, it checks that:

  • The ephemeral_sig matches your on-chain node key, so a relay can't swap in a recipient it controls.
  • The recipient is fresh, not expired, and within policy.

If the signature is missing, the key is stale, or no valid recipient is advertised, the sender treats your node as unroutable. There is no fallback to a stale or unsigned key.

Forward secrecy: nothing to rotate by hand​

The node rotates the ephemeral recipient about every 20 minutes and on every restart:

  1. Generates a fresh age keypair in memory.
  2. Signs the new public recipient and its expiry under your signing key.
  3. Advertises it on /v1/zs/details. The previous recipient stays decryptable for a further 10 minutes, so requests sealed just before rotation still complete. Its advertised lifetime is ~25 minutes, five longer than the rotation cadence, so clients stop preferring it about 5 minutes after the successor appears, well before the node stops decrypting it.
  4. Erases the old private key, which only ever lived in memory.

That erasure is the forward-secrecy guarantee. A retired recipient's private half never touched disk and is gone, so a later compromise of the host's on-disk secrets cannot decrypt traffic sealed to it.

Keep rotation healthy. A node whose rotation keeps failing has no fresh, signed recipient to advertise, so it returns 503 on /v1/zs/details and /v1/zs/reserve until it recovers. It stops taking traffic rather than serve with a weakened key.

How replies stay private​

The reply is encrypted too, with a different key from the prompt:

  • During reserve, a per-request response key is established and bound to the ticket, which carries a commitment to it. The client checks that commitment, so it knows the reply it decrypts came from the request it paid for.
  • Your node encrypts the streamed reply with fresh key material per response, so one request's key can't unlock another's, and a key recovered later can't decrypt past replies.

There is no response-key configuration.

What you can and can't see​

  • In standard mode, you can see the decrypted prompt. Your node decrypts it to run the model.
  • You can't see who sent it. The request arrives through a relay with no account or identity attached, only a pseudonymous payer address. You see the relay's address, not the user's.
  • As a relay, you can't read what you forward. The payload is sealed to the target node's key, not yours; you see only the sender's address, timing, and byte counts (see Relays).

Under tee.mode: dstack-tdx, only attested hardware can decrypt the prompt; see Confidential compute (TEE).

The exact ciphers, framing, and signing formats are part of the wire protocol. A full protocol specification is coming soon.