Key Server Operations for Decentralized Server Type
This guide explains how to set up and operate a key server as a decentralized server type. For an overview of decentralized vs independent server types, see Seal Server Overview.
Overview
Decentralized key server type embeds distributed key trust directly into the infrastructure layer. To your application, it looks like a single logical key server. Internally, it enforces threshold cryptography across multiple independent operators.
Any group of operators can set up a committee — there is no central authority or permission required. Operators participate in a Distributed Key Generation (DKG) ceremony that produces distributed key shares. Each operator runs a key server that holds only its partial share. No single operator ever holds the full master key.
In addition to setting up the members of a decentralized key server, an aggregator server setup is also required. The aggregator collects encrypted partial responses from operators and combines them into a single encrypted result for clients. Because the aggregator is trustless and holds no key material, it can be operated by anyone, including a third party.
What you'll learn:
- How to run a fresh DKG to bootstrap a new committee and generate the initial key shares
- How to configure and start a key server for a committee member
- How to perform coordinated rotation ceremonies to update membership while preserving the same public key and object ID
- How to update a running key server to use the new key share after rotation
How DKG works:
The DKG process involves two roles: a coordinator that orchestrates the workflow, and committee members that participate in key generation and rotation. Both fresh DKG and key rotation follow the same three-phase process:
- Phase A — Registration: Members generate encryption keys and register them on-chain
- Phase B — Message creation: Members create and exchange DKG messages off-chain
- Phase C — Finalization: Members process messages and propose the committee configuration on-chain
The coordinator signals members when to move from one phase to the next.
Guide organization:
- Fresh DKG coordinator runbook
- Fresh DKG member runbook (includes key server setup)
- Key rotation coordinator runbook
- Key rotation member runbook (includes key server configuration updates)
Prerequisites
Before running the DKG or key rotation workflows, both the coordinator and all committee members must complete the following steps.
-
Establish a trusted communication channel (e.g. group chat, email) with all other participants. This channel is used for the coordinator and members to share files at certain steps of the workflow, for the coordinator to announce phase transitions, and to verify each other's on-chain addresses. Address verification can also happen on-chain (for example, via a governance action).
-
Install Sui by following the official installation guide.
sui --version
- Make sure you have a CLI wallet ready on the expected network with gas. This is the address you will use throughout the rest of the workflows, and the one you should share with the group for verification.
sui client active-env
sui client active-address
sui client gas
# to create new wallet
sui client new-address ed25519
# switch if needed
sui client switch --env testnet
sui client switch --address 0x...
# to fund wallet gas on testnet: faucet.sui.io
- Clone the Seal repository and set it as your working directory.
git clone https://github.com/MystenLabs/seal.git
cd seal
- Build the seal-committee-cli tool.
cargo run --bin seal-committee-cli -- --help
Fresh DKG process
Fresh DKG coordinator runbook
Follow these steps to initialize a new multi-party computation (MPC) committee using a fresh DKG.
- Prepare DKG configuration
a. Make sure your CLI has the expected network and active address with gas.
sui client active-env
sui client active-address
# switch if needed
sui client switch --env testnet
sui client switch --address 0x...
b. Create a clean working directory named dkg-state and copy the example configuration file:
# in seal/
rm -rf dkg-state && mkdir dkg-state
cp crates/seal-committee-cli/dkg.example.yaml dkg-state/dkg.yaml
c. Collect the on-chain addresses of all participating members. Then open dkg-state/dkg.yaml and update the following fields:
init-params:
NETWORK: Testnet # Target network
THRESHOLD: 2 # Committee threshold (t of n)
MEMBERS: # Addresses of all participating members
- 0x...
- 0x...
- 0x...
- Publish and initialize the committee
Run the publish-and-init command:
cargo run --bin seal-committee-cli -- publish-and-init
This command:
- Publishes the
seal_committeepackage onchain with upgrade capability - Extracts the InitCap and UpgradeCap from the publish transaction
- Initializes the committee state and attaches the UpgradeManager (which holds the UpgradeCap) to the committee
- Appends the committee identifiers to
dkg.yaml
For example:
publish-and-init:
COMMITTEE_PKG: 0x5b788ac96879a752afbd3608a202207d75cf0f03387bcb744bfb4930cc544a70
COMMITTEE_ID: 0x55241859c52f51dd149763769b8aa1e54de39b55acacda3f3a67629691247985
COORDINATOR_ADDRESS: 0xef91ea73b4423e3a6176b0a1c9c6e4619de45c9c4e7c0b4aae358e292707d8c2
- Distribute configuration and notify Phase A (Registration)
Share the updated dkg.yaml file with all committee members. Notify members to begin Phase A (Registration).
- Monitor member registration
Check on-chain registration status:
cargo run --bin seal-committee-cli -- check-committee
The output shows which members have registered and which are still pending. Wait until all are registered.
- Notify Phase B (Message creation)
Once all members are registered:
- Notify members to begin Phase B (Message creation).
- Monitor the off-chain storage (e.g. group chat) until all members upload their DKG message files.
- Collect and share DKG messages and notify Phase C (Finalization)
Collect message files into a single directory and share it with members. The number of messages must equal exactly the total number of committee members.
rm -rf dkg-messages && mkdir dkg-messages
mv path/to/message_0.json dkg-messages/
mv path/to/message_1.json dkg-messages/
# repeat until all n members have contributed a message
Notify members to begin Phase C (Finalization).
- Confirm committee finalization
Monitor on-chain state until all members have proposed and the committee is finalized:
cargo run --bin seal-committee-cli -- check-committee
When finalization completes, the output includes the KEY_SERVER_OBJ_ID. Share this object ID with all members so they can configure their key servers.
- Collect API keys for aggregator server
After all committee members have their key servers running, ask each member to generate API credentials for the aggregator.
Collect and share the following with the aggregator operator:
- API credentials for each committee member, including:
- The on-chain server name (the
PartialKeyServer.namefield) - The API key name
- The API key
- The on-chain server name (the
- The committee's
KEY_SERVER_OBJ_IDfrom the previous step.
With this information, the aggregator operator can set up the configuration and run the aggregator server. For configuration and startup instructions, see Aggregator Server.
Fresh DKG member runbook
Follow these steps to participate as a member in a fresh DKG.
- Share your address with the coordinator
Share your wallet address (MY_ADDRESS) with the coordinator. This address is used for all on-chain actions during the DKG.
Make sure:
- Your wallet is connected to the correct network.
- You have enough gas to submit transactions.
# check values
sui client active-address
sui client active-env
sui client gas
# switch values if needed
sui client switch --address <MY_ADDRESS>
sui client switch --env testnet
- Generate keys and register (Phase A: Registration)
a. Wait for the coordinator to announce Phase A (Registration) and send you the dkg.yaml file containing COMMITTEE_PKG and COMMITTEE_ID. Create a local working directory named dkg-state and move the file there:
# in seal/
rm -rf dkg-state && mkdir dkg-state
mv path/to/dkg.yaml dkg-state/
b. Look up the committee object ID on a Sui Explorer and verify that its member addresses and threshold match both dkg.yaml and the addresses shared in the trusted communication channel.
c. Then run the command to generate your keys and register them on-chain by providing your server URL and name:
cargo run --bin seal-committee-cli -- genkey-and-register \
-u <YOUR_SERVER_URL> \
-n <YOUR_SERVER_NAME>
This command:
- Generates DKG key material and stores it in
dkg-state/. Keep this directory secure. - Appends
DKG_ENC_PK,DKG_SIGNING_PK,MY_SERVER_URL,MY_SERVER_NAME, andMY_ADDRESS(fromsui client active-address) todkg.yaml. - Registers your public keys on-chain.
- Create and share your DKG message (Phase B: Message creation)
a. Wait for the coordinator to announce Phase B (Message creation). Then initialize your local DKG state and generate your message file:
cargo run --bin seal-committee-cli -- create-message
This command outputs a file named dkg-state/message_P.json, where P is your party ID.
b. Share this file with the coordinator.
- Process messages and propose the committee (Phase C: Finalization)
Wait for the coordinator to announce Phase C (Finalization) and provide a directory containing messages (for example, path/to/dkg-messages).
Move the directory into dkg-state and process the messages:
mv path/to/dkg-messages dkg-state/
cargo run --bin seal-committee-cli -- process-all-and-propose
This command:
- Processes all DKG messages from the directory.
- Appends the following fields to
dkg.yaml:KEY_SERVER_PK: New key server public key.PARTIAL_PKS_V0: Partial public keys for all members.MASTER_SHARE_V0: Your master share, used to start the key server. Back it up securely and do not share it with anyone.
- Proposes the committee on-chain by calling the
proposefunction.
- Configure and start your key server
a. Wait for the coordinator to confirm that the DKG is complete and share the KEY_SERVER_OBJ_ID.
b. Create a key-server-config.yaml file with your address (MY_ADDRESS) and the key server object ID (KEY_SERVER_OBJ_ID), and set the committee state to active:
Example config file:
server_mode: !Committee
member_address: '<MY_ADDRESS>'
key_server_obj_id: '<KEY_SERVER_OBJ_ID>'
committee_state: !Active
c. Start the key server, setting the config path and your master key share from dkg.yaml (use MASTER_SHARE_V0 for a fresh DKG):
CONFIG_PATH=crates/key-server/key-server-config.yaml MASTER_SHARE_V0=0x... cargo run --bin key-server
For infrastructure and deployment recommendations, see Infrastructure requirements.
- Generate API credentials for the aggregator
After your key server is running successfully, generate API credentials for aggregator access and share them with the coordinator.
- Generate an API key name and API key for your key server.
- Share the following details with the coordinator:
- Your server name (
MY_SERVER_NAMEfromdkg.yaml, corresponding to the on-chainPartialKeyServer.name) - API key name
- API key
- Your server name (
The coordinator passes these credentials to the aggregator operator, who uses them to authenticate requests to your key server.
- Backup and clean up local DKG state
Once your key server is running successfully, back up the MASTER_SHARE_V0 value. Then you can safely delete the local DKG state directory:
rm -rf dkg-state
Key rotation process
Use key rotation to update the committee membership. When rotating a committee, the set of continuing members, including those present in both the current and next committee, must be large enough to meet the threshold of the current committee.
This guide assumes:
- The current committee version is
X, and - The rotation produces the next committee version
X+1.
Key rotation coordinator runbook
- Prepare DKG configuration
a. Make sure your CLI has the expected network and active address with gas.
sui client active-env
sui client active-address
# switch if needed
sui client switch --env testnet
sui client switch --address 0x...
b. Create a clean working directory named dkg-state and copy the rotation example configuration:
# in seal/
rm -rf dkg-state && mkdir dkg-state
cp crates/seal-committee-cli/dkg-rotation.example.yaml dkg-state/dkg.yaml
c. Collect the addresses of all members in the new committee (including continuing members). Open dkg-state/dkg.yaml and update the following fields.
You can obtain KEY_SERVER_OBJ_ID from the key server configuration of any continuing member in the current committee.
init-params:
NETWORK: Testnet # Target network
THRESHOLD: 3 # Threshold for the new committee (t of n)
MEMBERS: # New committee members (may include continuing members)
- 0x...
- 0x...
- 0x...
- 0x...
# Rotation only params
init-rotation-params:
KEY_SERVER_OBJ_ID: 0x... # Key server object ID from the current committee
- Initialize the rotation
cargo run --bin seal-committee-cli -- init-rotation
This command:
- Fetches the current key server object to determine the existing committee ID and package ID.
- Initializes the new committee object on-chain.
- Appends the following fields to the
init-rotationsection indkg.yaml:COORDINATOR_ADDRESS: Address executing the rotationCOMMITTEE_PKG: Package ID of the committee contractCURRENT_COMMITTEE_ID: Current committee object IDCOMMITTEE_ID: New committee object ID
- Distribute configuration and notify Phase A (Registration)
Share the updated dkg.yaml file with all committee members. Notify members to begin Phase A (Registration).
- Monitor member registration
Check on-chain registration status:
cargo run --bin seal-committee-cli -- check-committee
The output shows which members have registered and which are still pending. Wait until all are registered.
- Notify Phase B (Message creation)
Once all members are registered:
- Notify members to begin Phase B (Message creation).
- Monitor the off-chain storage (e.g. group chat) until all members upload their DKG message files.
- Collect and share DKG messages and notify Phase C (Finalization)
a. Collect message files into a single directory and share it with members. The number of messages must equal exactly the threshold of the current committee.
rm -rf dkg-messages && mkdir dkg-messages
mv path/to/message_0.json dkg-messages/
mv path/to/message_1.json dkg-messages/
# repeat till the number of messages equals the threshold
b. Notify members to begin Phase C (Finalization).
c. Remind members that the current committee is at version X (e.g. 0) and it is rotating to target version X+1 (e.g. 1). This is used to annotate MASTER_SHARE_VX and MASTER_SHARE_VX+1 in the member commands.
- Confirm committee finalization
Monitor on-chain state until all members have proposed and the committee is finalized:
cargo run --bin seal-committee-cli -- check-committee
- Collect API keys for aggregator server
If new members join the committee during rotation or existing members change API credentials, ask each member to send API credentials for the aggregator.
Collect and share the following with the aggregator operator:
- API credentials for each committee member, including:
- The on-chain server name (the
PartialKeyServer.namefield) - The API key name
- The API key
- The on-chain server name (the
With this information, the aggregator operator can update the configuration and run the aggregator server. For configuration and startup instructions, see Aggregator Server.
Key rotation member runbook
Follow these steps to participate as a member in a key rotation.
- Share your address with the coordinator
Share your wallet address (MY_ADDRESS) with the coordinator. This address is used for all on-chain actions during the rotation.
Make sure:
- Your wallet is connected to the correct network.
- You have enough gas to submit transactions.
- Generate keys and register (Phase A: Registration)
a. Wait for the coordinator to announce Phase A (Registration) and send you the dkg.yaml file. The file includes COMMITTEE_PKG, CURRENT_COMMITTEE_ID, and COMMITTEE_ID. Create a local working directory named dkg-state and move the file there:
# in seal/
rm -rf dkg-state && mkdir dkg-state
mv path/to/dkg.yaml dkg-state/
b. Look up the committee object ID on a Sui Explorer and verify that its member addresses and threshold match both dkg.yaml and the addresses shared in the trusted communication channel.
c. Then run the command to generate your keys and register them on-chain by providing your server URL and name:
cargo run --bin seal-committee-cli -- genkey-and-register \
-u <YOUR_SERVER_URL> \
-n <YOUR_SERVER_NAME>
This command:
- Generates sensitive key material and stores it in
dkg-state/. Keep this directory secure. - Appends
DKG_ENC_PK,DKG_SIGNING_PK,MY_SERVER_URL,MY_SERVER_NAME, andMY_ADDRESS(fromsui client active-address) todkg.yaml. - Registers your public keys on-chain.
- Create and share your DKG message (Phase B: Message creation)
Wait for the coordinator to announce Phase B (Message creation).
For continuing members:
a. Initialize your DKG state and generate your message file. You must pass your current master share (MASTER_SHARE_VX). This is the master share environment variable value that your key server is currently running with.
cargo run --bin seal-committee-cli -- create-message -o <MASTER_SHARE_VX>
This command outputs dkg-state/message_P.json, where P is your party ID.
b. Share this file with the coordinator.
For new members:
Initialize your DKG state. No message file is generated (new members don't create messages during rotation).
cargo run --bin seal-committee-cli -- init-state
- Process messages and propose the rotation (Phase C: Finalization)
Wait for the coordinator to announce Phase C (Finalization) and provide a directory containing all DKG messages (for example, path/to/dkg-messages).
Move the directory into dkg-state and process the messages:
mv path/to/dkg-messages dkg-state/
cargo run --bin seal-committee-cli -- process-all-and-propose
This command:
- Processes all messages from the directory.
- Appends the following fields to
dkg.yaml:PARTIAL_PKS_VX+1: New partial public keys for all members.MASTER_SHARE_VX+1: Your new master share, used to start the key server. Back it up securely and do not share it with anyone.
- Proposes the rotation on-chain by calling
propose_for_rotation.
- Start or update your key server
For continuing members:
a. Update key-server-config.yaml
- Change the committee state from Active to Rotation mode.
- Set the target committee version to
X+1(increment 1 from the current versionX). If you are not sure whatXis in this rotation, check with your coordinator. - Leave other settings unchanged.
Example config file:
server_mode: !Committee
member_address: '<MY_ADDRESS>'
key_server_obj_id: '<KEY_SERVER_OBJ_ID>'
committee_state: !Rotation
target_version: <X+1> # Increment this
b. Restart the key server with both the old and new master shares (MASTER_SHARE_VX corresponds to the current committee version X and MASTER_SHARE_VX+1 corresponds to the next committee version X+1). The server monitors on-chain state and selects the correct share automatically.
CONFIG_PATH=crates/key-server/key-server-config.yaml \
MASTER_SHARE_VX=<MASTER_SHARE_VX> \
MASTER_SHARE_VX+1=<MASTER_SHARE_VX+1> \
cargo run --bin key-server
c. Wait for the coordinator to confirm that rotation is complete. Then update the config to Active mode and restart the server with only the new master share.
Example config file:
server_mode: !Committee
member_address: '<MY_ADDRESS>'
key_server_obj_id: '<KEY_SERVER_OBJ_ID>'
committee_state: !Active
CONFIG_PATH=crates/key-server/key-server-config.yaml \
MASTER_SHARE_VX+1=<MASTER_SHARE_VX+1> \
cargo run --bin key-server
For new members:
a. Create key-server-config.yaml. Since X+1 is your first committee version, start the key server directly in Active mode.
Example config file:
server_mode: !Committee
member_address: '<MY_ADDRESS>'
key_server_obj_id: '<KEY_SERVER_OBJ_ID>'
committee_state: !Active
b. Start the key server with the new master share MASTER_SHARE_VX+1 that corresponds to the new committee version X+1.
CONFIG_PATH=crates/key-server/key-server-config.yaml \
MASTER_SHARE_VX+1=<MASTER_SHARE_VX+1> \
cargo run --bin key-server
For infrastructure and deployment recommendations, see Infrastructure requirements.
- Generate API credentials for the aggregator (new members only)
If you are joining the committee as a new member during rotation, generate API credentials after your key server is up and running and share them with the coordinator.
a. Generate an API key name and API key for your key server. b. Share the following with the coordinator:
- Your server name (
MY_SERVER_NAMEfromdkg.yaml, corresponding to the on-chainPartialKeyServer.name) - API key name
- API key
The coordinator passes these credentials to the aggregator operator, who uses them to authenticate requests to your key server.
Continuing members typically do not need to share new credentials, since their existing API keys should already be configured in the aggregator. Share new credentials only if you are rotating your API keys.
- Backup and clean up local DKG state
Once your key server is running successfully, back up the MASTER_SHARE_VX+1 value. Then you can safely delete the local DKG state directory:
rm -rf dkg-state
Package Upgrade
The committee can upgrade the seal_committee Move package through a voting process. A contract upgrade can only happen if a threshold of committee members approves the new package digest.
The upgrade follows these steps:
- Compute package digest: Build the updated package and extract its digest.
- Committee voting: Committee members vote for the new package digest.
- Execute the upgrade: Once threshold is reached, authorize, execute and commit the upgrade.
Prerequisites
- Make sure you are on the expected network.
- Make sure you are using the same address as the one you used to participate in the current active committee.
<KEY_SERVER_OBJ_ID>can be found in your server's config filekey-server-config.yaml.
sui client active-env
sui client active-address
# Verify the current key server status and its committee members (that contains your address).
cargo run --bin seal-committee-cli -- check-key-server-status \
-k <KEY_SERVER_OBJ_ID> \
-n <NETWORK>
Steps
1. Compute the package digest
Pull the updated package code locally and verify updates. This command computes the package digest for the committee package (move/committee). Make sure that all committee members have the same digest and are ready for upgrade.
# in seal/
cargo run --bin seal-committee-cli -- package-digest -n <NETWORK>
This outputs:
Digest for package 'committee': 0xd0f13987e824f0f462911bc45d5a45004f4e3d752de2be939111274e862cc00c
2. Approve the upgrade
Each committee member approves for the upgrade using the locally built package, run:
cargo run --bin seal-committee-cli -- approve-upgrade \
-k <KEY_SERVER_OBJ_ID> \
-n <NETWORK>
Notes:
- To check the current upgrade proposal status (digest, votes, threshold), run:
cargo run --bin seal-committee-cli -- check-key-server-status \
-k <KEY_SERVER_OBJ_ID> \
-n <NETWORK>
- If needed, the local package is not what you want to approve, run this to reject it onchain.
cargo run --bin seal-committee-cli -- reject-upgrade \
-p <PACKAGE_PATH>
-k <KEY_SERVER_OBJ_ID> \
-n <NETWORK>
-
If needed, can run
approve-upgradeorreject-upgradeagain to change your vote option. -
If needed, if a threshold of committee members reject the upgrade, any member can reset the proposal to allow a new upgrade proposal.
cargo run --bin seal-committee-cli -- reset-proposal \
-k <KEY_SERVER_OBJ_ID> \
-n <NETWORK>
3. Authorize, execute and commit the upgrade
Once the threshold number of committee members approves the digest, any member run this to execute the upgrade:
cargo run --bin seal-committee-cli -- authorize-and-upgrade \
-k <KEY_SERVER_OBJ_ID> \
-n <NETWORK>
This command:
- Authorizes the upgrade (gets an UpgradeTicket).
- Performs the package upgrade with the ticket (gets an UpgradeReceipt).
- Commits the upgrade receipt.
Quick reference: Fresh DKG vs Key rotation
| Aspect | Fresh DKG | Key rotation |
|---|---|---|
| Purpose | Create a brand-new committee and keys | Update committee membership or threshold |
| Committee version | Starts at V0 | Rotates from VX to VX+1 |
| Coordinator init command | publish-and-init | init-rotation |
| Continuing members required | N/A | Must meet current threshold |
| Member Phase B command | All members: create-message | Continuing members: create-message -o <OLD_SHARE>New members: init-state |
| Old master share needed | N/A | Yes (continuing members must provide via -o flag) |
| Message creation | All members create messages | Only continuing members create messages |
| Key server startup | Start with MASTER_SHARE_V0 | Transition from MASTER_SHARE_VX to MASTER_SHARE_VX+1 |
| on-chain proposal function | propose | propose_for_rotation |
| Result | New key server object | Updated key server object's version |
Infrastructure requirements
The decentralized key server is initialized with a master share generated during the DKG ceremony, which must be kept secure. You can store it using a cloud-based Key Management System (KMS), or in a self-managed software or hardware vault.
The server is a lightweight, stateless service designed for easy horizontal scaling. Because it doesn't require persistent storage, you can run multiple instances behind a load balancer to increase availability and resilience. Each instance must have access to a trusted Sui full node — ideally one that's geographically close to reduce latency during policy checks and key operations.
To operate the server securely, it is recommended to place it behind an API gateway or reverse proxy. This allows you to:
- Expose the service over HTTPS and terminate SSL/TLS at the edge
- Enforce rate limiting and prevent abuse
- Authenticate requests using API keys or access tokens
- Optionally integrate usage tracking for commercial or billable offerings, such as logging access frequency per client or package
For observability, the server exposes Prometheus-compatible metrics on port 9184. You can access raw metrics by running curl http://0.0.0.0:9184. These metrics can also be visualized using tools like Grafana. The key server also includes a basic health check endpoint on port 2024: curl http://0.0.0.0:2024/health.
CORS configuration
Configure Cross-Origin Resource Sharing (CORS) on your server to allow applications to make requests directly from the browser. Use the following recommended headers:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Request-Id, Client-Sdk-Type, Client-Sdk-Version
Access-Control-Expose-Headers: x-keyserver-version
If your server requires an API key, make sure to include the corresponding HTTP header name in Access-Control-Allow-Headers as well.