Skip to main content

DKG Ceremony

In the DKG ceremony, the SSV operators generate the validator key shares together. The full private key is never constructed on any machine. The ceremony is run with ssv-dkg and involves two roles:

  • Operators — each runs a long-lived ssv-dkg service to take part in ceremonies.
  • Initiator — triggers the ceremony and collects its outputs. It can be one of the Operators or a separate entity.

How Vaults Use the DKG Ceremony

Setting up your Vault's validators across a cluster of SSV operators involves several stages:

  1. Generating the validator's key shares. The operators run a DKG ceremony that produces the validator's public key and one private key share per operator, each encrypted to that operator's RSA public key and written to keyshares.json.
  2. Registering the validator on the SSV Network. The validator's public key and encrypted shares from keyshares.json are registered on the SSV Network. Each operator's Sidecar can then read its own share.
  3. Depositing the validator's stake on the Beacon Chain. Each Sidecar reads its share, signs the deposit message, and sends its signature share to the Relayer, which combines the shares into the full deposit signature. The Operator Service then submits the deposit data to the Vault.

This guide covers the DKG setup with StakeWise Vaults. It assumes the Initiator is one of the operators and the ultimate owner of the setup.

Step 1: Run the DKG Service

Each operator runs a long-lived ssv-dkg service to take part in DKG ceremonies. It is separate from the SSV node (which performs the validator's ongoing duties, such as attestations and proposals) and reuses the operator's existing key.

Minimum Requirements

ssv-dkg relies heavily on cryptography, so computational power has a major impact on its performance. Demand rises with the number of validators created at once.

  • Minimum: an AWS t3.medium or equivalent machine dedicated to DKG.
  • Recommended: an AWS t3.large or higher tier.

Give the container at least 1 CPU and 500 MB of memory. With the docker run command below, add --cpus 1 --memory 500m.

  1. Create an operator.config.yaml in the same folder as your encrypted_private_key.json and password files:
privKey: ./data/encrypted_private_key.json
privKeyPassword: ./data/password
operatorID: 1 # your SSV operator ID
port: 3030
logLevel: info
logFilePath: ./data/debug.log
outputPath: ./data/output
Paths Must Be Relative

ssv-dkg accepts local paths only. An absolute path in --configPath, privKey, privKeyPassword, logFilePath, or outputPath makes it exit with should be local before the service starts. The container's working directory is /ssv-dkg, so ./data/... resolves to the mounted /ssv-dkg/data.

  1. Start the service:
docker run --restart unless-stopped --name ssv_dkg -p 3030:3030 \
-v "$(pwd)":/ssv-dkg/data -u `id -u $USER` -it \
"ssvlabs/ssv-dkg:v3.1.1" start-operator --configPath ./data/operator.config.yaml
Expose the DKG Port

The Initiator connects to your endpoint during a ceremony. Expose the port you configured on the machine firewall and in the Docker container command. Otherwise, your DKG node will not be available.

  1. Publish your DKG endpoint so Initiators can reach you. In the SSV webapp ↗, open your operator, choose Edit Details, and set the DKG endpoint as protocol:ip:port (a domain name works in place of the IP). Until you do, the webapp will not treat your operator as DKG-enabled and cannot build its --operatorsInfo entry.
All Operators Must Run the Same Version

ssv-dkg enforces an exact version match between the Initiator and every operator — a ceremony mixing versions fails. Agree on a tag with your cluster before anyone upgrades, and pin it rather than using latest.

Step 2: Generate the Key Shares

Webapp or CLI

This guide uses the SSV webapp ↗, which generates the ssv-dkg command with the operators data and config already filled in. The Initiator can also run ssv-dkg init directly by building a custom operators_info.json. See SSV's ssv-dkg client guide ↗.

  1. In the webapp, open Operators → Validator Clusters → Create a cluster and select your cluster's operators (search by ID or name; minimum 4). They must be DKG-enabled, meaning they run ssv-dkg start-operator (Step 1) with a reachable endpoint.
  2. Choose Generate new key shares → Offline → DKG.
  3. Set the number of validators and the Withdrawal Address to your Vault contract address, then confirm.
  4. Copy the generated command and run it on a machine with Docker. The webapp pre-fills --owner, --nonce, --withdrawAddress, --operatorIDs, and --operatorsInfo (each operator's ID, RSA key, and endpoint).
Check the Operators First

A stale endpoint or mismatched DKG metadata on any operator stops the ceremony before it begins. Swap init for ping in the generated command to healthcheck the whole set first, and fix any operator that fails before running the ceremony.

Example generated command
docker pull ssvlabs/ssv-dkg:v3.1.1 && docker run --rm -v "$(pwd)":/ssv-dkg/data -it \
"ssvlabs/ssv-dkg:v3.1.1" init \
--owner <YOUR_SSV_WALLET> --nonce <NONCE> \
--withdrawAddress <YOUR_VAULT_ADDRESS> \
--operatorIDs 1,2,3,4 \
--operatorsInfo '[{"id":1,"public_key":"LS0t...","ip":"https://5.4.3.2:3030"}, ...]' \
--network hoodi --validators 1 --outputPath ./data --tlsInsecure

For every flag and what it does, see SSV's command-line reference ↗.

The ceremony writes a ceremony-<timestamp>/ directory holding one sub-directory per validator, named after the owner nonce and the validator's public key:

Ceremony output
ceremony-<timestamp>/
└─ 000000-0x<validator public key>/
├─ deposit_data.json # Deposit data to activate the validator — not used by StakeWise (the Vault deposits)
├─ keyshares.json # Encrypted shares, to register on SSV
└─ proofs.json # Proofs used to reshare to new operators later

Creating more than one validator in a single ceremony adds aggregated deposit_data.json, keyshares.json, and proofs.json at the top of ceremony-<timestamp>/, covering every validator at once. With --validators 1 only the per-validator directory is written, so use the files inside it.

The keyshares.json holds each share encrypted to its operator's RSA key. For the full format, see SSV's keyshares.json structure ↗.

Keep the Proofs

Back up proofs.json. It lets you reshare the cluster to a different set of operators later without running a new ceremony.

Step 3: Register the Key Shares

The Initiator registers the key shares on SSV so the encrypted shares are published on-chain, where the Sidecars read each operator's share. You do not deposit here: in the StakeWise flow the Vault deposits later, once it has accumulated enough assets, through the Relayer (Step 6), not via the Ethereum Launchpad.

After the ceremony, the webapp shows a Deposit Validator step and a Register Validator step:

  1. Skip the Launchpad deposit. Click "My validator has been activated" to unlock registration. You are not depositing 32 ETH yourself.
  2. Click Register Validator, upload the ceremony's keyshares.json — the one inside the validator's 000000-0x... directory, or the aggregated file at the top of ceremony-<timestamp>/ if you created several validators at once — and complete registration (Fund the Cluster → Approve SSV → Register Validator), the same registration steps as Split Keys.

Step 4: Run the DVT Sidecar

Each operator runs one DVT Sidecar ↗ on the machine running their node. SSV publishes each operator's encrypted key share on-chain (in the ValidatorAdded event log), so in SSV mode the Sidecar does not read the share from a local file. Instead, it reads its encrypted share from on-chain and decrypts it with the operator's RSA key. It then signs the deposit and exit messages and submits the signature shares to the Relayer.

  1. Create the .env file from the repository's template:
cp .env.example .env
  1. In a data/ directory next to the .env, place this operator's encrypted_private_key.json and password — the same two files you used to run the SSV node and the DKG service.

  2. Set the values for your SSV operator. The container runs from /app, so every path must point inside the mounted /data:

# Network: mainnet or hoodi
NETWORK=mainnet

# DVT cluster type
CLUSTER_TYPE=SSV

# URL of your DVT Relayer
RELAYER_ENDPOINT=http://relayer:8000

# This operator's SSV key (same files used to run the node and the DKG service).
# This is the RSA operator key used to DECRYPT the on-chain share — not a validator key share.
SSV_OPERATOR_KEY_FILE=/data/encrypted_private_key.json
SSV_OPERATOR_PASSWORD_FILE=/data/password
# This operator's SSV operator ID
SSV_OPERATOR_ID=1

# Execution and consensus client endpoints (the execution endpoint is used to read the on-chain shares)
EXECUTION_ENDPOINT=http://execution:8545
CONSENSUS_ENDPOINT=http://consensus:5052

# Local database file
DATABASE=/data/dvt-operator-sidecar.db
Keep the Database

The Sidecar records which SSV ValidatorAdded events it has already scanned in DATABASE. Left at its default it lands in the read-only /app working directory, so point it at /data and keep the file — otherwise each restart rescans the chain from the cluster's first block.

  1. Run the container:
docker run \
-u $(id -u):$(id -g) \
--env-file .env \
-v $(pwd)/data:/data \
europe-west4-docker.pkg.dev/stakewiselabs/public/dvt-operator-sidecar:v2.1.0

Step 5: Run the DVT Relayer

The Initiator runs one DVT Relayer ↗ that serves the whole cluster. It collects the Sidecars' signature shares, reconstructs the full signatures, and serves the registration data to the Operator Service. It holds the Vault's Validators Manager wallet to sign registrations, but never has access to the validator key shares.

  1. Create the .env from the repository's template:
cp .env.example .env
  1. In a data/ directory, place the files the Relayer reads:

    • validators-manager-key.json and validators-manager-password.txt — the keystore and password for the Ethereum wallet set as your Vault's Validators Manager. This is an externally owned account (EOA) that signs registration transactions, not a validator signing key.

    • public_keys.txt — the distributed validator public keys, one per line. Each validator's 000000-0x... ceremony directory is named after its public key, and the same value is the pubkey field of the deposit_data.json inside it:

      jq -r '.[].pubkey' ceremony-*/*/deposit_data.json | sed 's/^/0x/' > data/public_keys.txt
  2. Set the values (file paths point inside the mounted /data):

# API server
RELAYER_HOST=0.0.0.0
RELAYER_PORT=8000

# BLS signature threshold — must match your cluster's threshold
SIGNATURE_THRESHOLD=3

# Network: mainnet or hoodi
NETWORK=mainnet

# Execution and consensus client endpoints
EXECUTION_ENDPOINT=http://execution:8545
CONSENSUS_ENDPOINT=http://consensus:5052

# Validator public keys to register, one per line
PUBLIC_KEYS_FILE=/data/public_keys.txt

# The Validators Manager wallet that signs registrations
VALIDATORS_MANAGER_KEY_FILE=/data/validators-manager-key.json
VALIDATORS_MANAGER_PASSWORD_FILE=/data/validators-manager-password.txt
  1. Pull and run the Relayer:
export DVT_RELAYER_VERSION=v1.1.0
docker run --rm -ti \
--env-file .env \
-v $(pwd)/data:/data \
-p 8000:8000 \
europe-west4-docker.pkg.dev/stakewiselabs/public/dvt-relayer:$DVT_RELAYER_VERSION
Restrict Access to the Relayer

The Relayer has no authentication, and it signs funding, withdrawal, and consolidation requests with the Validators Manager wallet. Anyone who can reach it can obtain a signature authorizing your Vault's validators to be withdrawn. Keep it on a private network reachable only by the Sidecars and the Operator Service, and do not publish port 8000 to the internet.

Step 6: Start the Operator Service

The Operator Service has no keystores here, so the Initiator runs it in Relayer (API Mode) pointing at the DVT Relayer.

First, set your Vault's Validators Manager to the address of the Relayer's validators-manager-key.json wallet. The Vault rejects registrations signed by any other address.

Then start the service:

./operator start-relayer --vault=[ENTER YOUR VAULT CONTRACT ADDRESS HERE] --relayer-endpoint=http://relayer:8000

See Relayer (API Mode) for the remaining options and the environment-variable form.

The Operator Service polls the Relayer for validators that have all required signatures and registers them in the Vault contract. At that moment the Vault deposits its pooled ETH, and the validator is created on the Beacon Chain. Once it activates, the SSV operators perform its duties using their shares.