For the complete documentation index, see llms.txt
This guide covers how to set up and operate an independent server type key server. For an overview comparing decentralized and independent server types, see Seal Server Overview.
Use this guide to operate a Seal key server in either of the following scenarios:
Use the relevant package ID <SEAL_PACKAGE_ID> to register your key server on the Sui network <NETWORK>:
When running an independent server, you can choose between Open or Permissioned mode:
You can choose the option that best fits your deployment model and security requirements. The following sections provide more details on both options. Also see Seal CLI for reference.
In Open mode, the key server allows decryption requests for Seal policies from any package. This mode is ideal for testing or for deployments where the key server is operated as a best-effort service without direct user liability.
Before starting the key server, you must generate a BLS master key pair. This command outputs both the master secret key and the public key.
cargo run --bin seal-cli genkey
Master key: <MASTER_KEY>
Public key: <MASTER_PUBKEY>
To make the key server discoverable by Seal clients, register it onchain using create_and_transfer_v2_independent_server:
sui client switch --env <NETWORK>
sui client active-address # fund this if necessary
sui client call --function create_and_transfer_v2_independent_server --module key_server --package <SEAL_PACKAGE_ID> --args <YOUR_SERVER_NAME> https://<YOUR_URL> 0 <MASTER_PUBKEY>
# outputs object of type key_server::KeyServer <KEY_SERVER_OBJECT_ID> (may output additional objects)
To start the key server in Open mode, run the command cargo run --bin key-server, but before running the server, set the following environment variables:
MASTER_KEY: The master secret key generated using the seal-cli tool.CONFIG_PATH: The path to a .yaml configuration file that specifies key server settings. For the configuration file format, see the example config.In the config file, make sure to:
Testnet, Mainnet, or !Devnet (which requires a seal_package field).
node_url in the config or set the NODE_URL environment variable.!Open.key_server_object_id field to <KEY_SERVER_OBJECT_ID>, the ID of the key server object you registered onchain.
```shell
CONFIG_PATH=crates/key-server/key-server-config.yaml MASTER_KEY=
Alternatively, run with Docker:
```shell
docker build -t seal-key-server . --build-arg GIT_REVISION="$(git describe --always --abbrev=12 --dirty --exclude '*')"
docker run -p 2024:2024 -v $(pwd)/crates/key-server/key-server-config.yaml:/config/key-server-config.yaml \
-e CONFIG_PATH=/config/key-server-config.yaml \
-e MASTER_KEY=<MASTER_KEY> \
seal-key-server
In Permissioned mode, the key server only allows decryption requests for Seal policies from explicitly allowlisted packages. This is the recommended mode for B2B deployments where tighter access control and client-specific key separation are required.
Start by generating a master seed for the key server. Use the seal-cli tool as cargo run --bin seal-cli gen-seed. This command outputs the secret master seed which should be stored securely.
cargo run --bin seal-cli gen-seed
Seed: <MASTER_SEED>
Next, create a configuration file in .yaml format following the instructions in the example configuration and with the following properties:
!Permissioned.server_mode: !Permissioned
client_configs:
Set the environment variable MASTER_KEY to the master secret seed generated by the seal-cli tool, and the environment variable CONFIG_PATH pointing to a .yaml configuration file. Run the server using cargo run --bin key-server. It should abort after printing a list of unassigned derived public keys (search for logs with the text Unassigned derived public key).
# MASTER_KEY=<MASTER_SEED> CONFIG_PATH=crates/key-server/key-server-config.yaml cargo run --bin key-server
MASTER_KEY=0x680d7268095510940a3cce0d0cfdbd82b3422f776e6da46c90eb36f25ce2b30e CONFIG_PATH=crates/key-server/key-server-config.yaml cargo run --bin key-server
2025-06-15T02:02:56.303459Z INFO key_server: Unassigned derived public key with index 0: "<PUBKEY_0>"
2025-06-15T02:02:56.303957Z INFO key_server: Unassigned derived public key with index 1: "<PUBKEY_1>"
2025-06-15T02:02:56.304418Z INFO key_server: Unassigned derived public key with index 2: "<PUBKEY_2>"
Each supported client must have a registered onchain key server object to enable discovery and policy validation.
Follow these steps to add every new client to a Permissioned key server:
<POLICY_PACKAGE_ID_0>, <POLICY_PACKAGE_ID_1>).create_and_transfer_v2_independent_server.
0; for the nth client, n-1).| Index | Derived Public Key |
| ——– | ——- |
| 0 | <PUBKEY_0> |
| 1 | <PUBKEY_1> |
| <INDEX> | <PUBKEY_<INDEX>> |
# The 0 between the key server URL and public key arguments is a fixed or static value for all client registrations
sui client call --function create_and_transfer_v2_independent_server --module key_server --package <SEAL_PACKAGE_ID> --args <YOUR_SERVER_NAME> https://<YOUR_URL> 0 <PUBKEY_<INDEX>>
# outputs object of type key_server::KeyServer <KEY_SERVER_OBJECT_ID_<INDEX>> (may output additional objects)
client_master_key to type Derived.derivation_index as <INDEX> (use 0 for the first client; for the nth client, use n-1).key_server_object_id to the value <KEY_SERVER_OBJECT_ID_<INDEX>> from the registration output in the preceding section.package_ids.:::info
You can map multiple different packages from a developer to the same client (for example, for different features or apps). However, if the developer later decides to export the client key, access will be revoked for all packages mapped to that client. Confirm whether they prefer separate client per package (allowing for granular revocation) or a single consolidated client (allowing for simpler operations).
:::
:::info
When adding a package for a feature or app, you must add the package ID of the package’s first published version. This ensures that the key server continues to recognize the package after upgrades. You do not need to add new versions of a package to a client’s allowlist.
:::
For example:
- name: "alice" # not used in code, identifier for your own information
client_master_key: !Derived
derivation_index: <INDEX>
key_server_object_id: "<KEY_SERVER_OBJECT_ID_<INDEX>>"
package_ids:
- "<POLICY_PACKAGE_ID_1>"
- "<POLICY_PACKAGE_ID_2>"
<KEY_SERVER_OBJECT_ID_<INDEX>> with the client.
MASTER_KEY=<MASTER_SEED> CONFIG_PATH=crates/key-server/key-server-config.yaml cargo run --bin key-server
Or with Docker:
docker run -p 2024:2024 \
-v $(pwd)/crates/key-server/key-server-config.yaml:/config/key-server-config.yaml \
-e CONFIG_PATH=/config/key-server-config.yaml \
-e MASTER_KEY=<MASTER_SEED> \
seal-key-server
To add another client in the future, repeat the preceding steps. Register a new unassigned derived public key onchain, note the returned key_server_object_id, add a config entry with the next derivation_index and the client’s package_ids, then restart the server. The logs show the next unassigned indices and public keys.
In rare cases where you need to export a client key:
seal-cli tool as cargo run --bin seal-cli derive-key --seed $MASTER_SEED --index X. Replace X with the derivation_index of the key you want to export. The tool outputs the corresponding master key, which can be imported by another key server if needed.Here’s an example command assuming the key server owner is exporting the key at index 0:
cargo run --bin seal-cli derive-key --seed <MASTER_SEED> --index 0
Master key: <CLIENT_MASTER_KEY>
Public key: <CLIENT_MASTER_PUBKEY>
client_master_key type to Exported.deprecated_derivation_index field with the derivation index.For example:
- name: "bob"
client_master_key: !Exported
deprecated_derivation_index: 0
Here’s an example Sui CLI command assuming the export of <KEY_SERVER_OBJECT_ID_0>:
sui transfer --object-id <KEY_SERVER_OBJECT_ID_0> --to <NEW_OWNER_ADDRESS>
The owner of <NEW_OWNER_ADDRESS> can now run:
sui client call --function update --module key_server --package <SEAL_PACKAGE_ID> --args <KEY_SERVER_OBJECT_ID_0> https://<NEW_URL>
client_master_key set to type Imported.BOB_BLS_KEY. This name is used later.<KEY_SERVER_OBJECT_ID_0>.For example:
- name: "bob"
client_master_key: !Imported
env_var: "BOB_BLS_KEY"
key_server_object_id: "<KEY_SERVER_OBJECT_ID_0>"
package_ids:
- "<POLICY_PACKAGE_ID>"
CONFIG_PATH=crates/key-server/key-server-config.yaml BOB_BLS_KEY=<CLIENT_MASTER_KEY> MASTER_KEY=<MASTER_SEED> cargo run --bin key-server
Or run with Docker:
docker run -p 2024:2024 \
-v $(pwd)/crates/key-server/key-server-config.yaml:/config/key-server-config.yaml \
-e CONFIG_PATH=/config/key-server-config.yaml \
-e BOB_BLS_KEY=<CLIENT_MASTER_KEY> \
-e MASTER_KEY=<MASTER_SEED> \
seal-key-server
The server is initialized with a master key (or seed), which must be kept secure. You can store this key using a cloud-based Key Management System (KMS), or in a self-managed software or hardware vault. If you’re importing keys, those should be protected using the same secure storage approach.