⚠️ WARNING: This software has not been audited and is not production-ready. Use at your own risk.
Story Kernel is a Trusted Execution Environment (TEE) client for Story Protocol's Distributed Key Generation (DKG) system. It runs inside Intel SGX enclaves to provide secure key generation, management, and threshold decryption operations.
- Distributed Key Generation (DKG): Implements Pedersen DKG protocol for secure distributed key generation
- SGX Remote Attestation: Generates and verifies SGX quotes for trust establishment
- Sealed Storage: Keys are encrypted and sealed to the enclave's identity
- TDH2 Partial Decryption: Supports threshold decryption using the TDH2 scheme
- Light Client Verification: Verifies on-chain state using CometBFT light client
┌────────────────────────────────────────────────────────┐
│ Story Kernel │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Gramine SGX Enclave │ │
│ │ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │ │
│ │ │ DKG │ │ Seal/ │ │ Light │ │ │
│ │ │ Service │ │ Unseal │ │ Client │ │ │
│ │ └──────────┘ └──────────┘ └────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ gRPC API │
└──────────────────────────┼─────────────────────────────┘
│
Story Network
- Intel CPU with SGX support enabled in BIOS
All validators must use the exact same versions below to produce identical mr_enclave
(code commitment) values. The Gramine manifest content — including resolved library paths — is
measured into the enclave identity. Any version difference can produce a different code commitment.
| Component | Required Version | Why pinned |
|---|---|---|
| Ubuntu | 24.04 LTS | Library paths (/lib/x86_64-linux-gnu/) are Ubuntu-specific and baked into MRENCLAVE |
| Go | 1.24.0 | Different Go versions (including patch) produce different binaries → different MRENCLAVE |
| Gramine | 1.9 | gramine.libos and gramine.runtimedir() resolve to version-specific paths → different MRENCLAVE |
Why Ubuntu only? The manifest contains Ubuntu's multiarch library paths (
/lib/x86_64-linux-gnu/). RHEL-based distros use/lib64/instead. Since the manifest is measured into MRENCLAVE, all validators must use the same OS to share a single code commitment.
sudo apt update
sudo apt install -y build-essential cmake libssl-dev# Add Intel SGX repository
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key | sudo tee /etc/apt/keyrings/intel-sgx-keyring.asc > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/intel-sgx-keyring.asc arch=amd64] https://download.01.org/intel-sgx/sgx_repo/ubuntu noble main" | sudo tee /etc/apt/sources.list.d/intel-sgx.list
sudo apt update
# Install SGX libraries
sudo apt install -y libsgx-dcap-default-qpl libsgx-enclave-common libsgx-quote-exEdit /etc/sgx_default_qcnl.conf to set the PCCS endpoint:
{
"pccs_url": "https://global.acccache.azure.net/sgx/certification/v4/",
"collateral_service": "https://global.acccache.azure.net/sgx/certification/v4/"
}Version matters. All validators must install the same Gramine version via the same method (apt). Do not build from source — it produces different library paths.
sudo curl -fsSLo /usr/share/keyrings/gramine-keyring.gpg https://packages.gramineproject.io/gramine-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/gramine-keyring.gpg] https://packages.gramineproject.io/ noble main" | sudo tee /etc/apt/sources.list.d/gramine.list
sudo apt update
sudo apt install -y gramine=1.9Verify the installation:
gramine-manifest --version # should show 1.9git clone https://github.com/piplabs/story-kernel.git
cd story-kernel
# Build the binary with cb-mpc library
make build-with-cppStory Kernel uses a fixed data directory at /opt/story-kernel/ instead of the conventional
~/.story-kernel/ under the user's home directory.
Why /opt/story-kernel/?
In SGX, the Gramine manifest — including all file paths — is loaded into enclave memory and
measured into mr_enclave (code commitment). If the manifest contained a user-dependent path
like /home/ubuntu/.story-kernel/, every validator would need the exact same OS username to
produce matching code commitments. Since different cloud providers use different default users
(AWS: ubuntu, Azure: azureuser, GCP: varies), a user-dependent path would break
cross-environment reproducibility.
By using /opt/story-kernel/ — a fixed, OS-agnostic path following the
Filesystem Hierarchy Standard —
all validators produce identical manifests and therefore identical code commitments, regardless
of their OS user or cloud provider.
Note: While the data directory is OS-agnostic, the manifest still contains Ubuntu-specific system library paths (e.g.,
/lib/x86_64-linux-gnu/). See Software Requirements for details on OS support.
Setup:
sudo mkdir -p /opt/story-kernel
sudo chown $USER:$USER /opt/story-kernelmake gramine-manifestmake gramine-signmake gramine-enclave-infoThis will display the mr_enclave (code commitment) value needed for registration.
gramine-sgx story-kernel init --home /opt/story-kernelThis creates a configuration directory at /opt/story-kernel/ with a config.toml file.
Note: The
initcommand must be run separately because the production manifest hardcodesargvto["story-kernel", "start", "--home", "/opt/story-kernel"]. After initialization, the service starts automatically with the correct data directory.
Edit /opt/story-kernel/config.toml:
log-level = "info"
[grpc]
listen_addr = ":50051"
[light_client]
chain_id = "devnet-1"
rpc_addr = "http://localhost:26657"
primary_addr = "http://localhost:26657"
witness_addrs = ["http://witness1:26657", "http://witness2:26657"]
trusted_height = 1000000
trusted_hash = "ABCD1234..."By default, the gRPC server runs without TLS. To secure the connection between Story and story-kernel:
Generate certificates:
# Create CA (ECDSA P-256 recommended for performance)
openssl ecparam -genkey -name prime256v1 -out ca.key
openssl req -new -x509 -days 365 -key ca.key -out ca.crt -subj "/CN=Story-Kernel-CA"
# Create server cert (for story-kernel)
# NOTE: Replace SAN values with actual hostnames/IPs in production
openssl ecparam -genkey -name prime256v1 -out server.key
openssl req -new -key server.key -out server.csr -subj "/CN=story-kernel"
echo "subjectAltName = IP:127.0.0.1, DNS:localhost" > server-ext.cnf
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt -days 365 -extfile server-ext.cnf
# Create client cert (for story consensus client)
openssl ecparam -genkey -name prime256v1 -out client.key
openssl req -new -key client.key -out client.csr -subj "/CN=story-client"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt -days 365
# Restrict key file permissions
chmod 600 ca.key server.key client.keyCertificate rotation requires a service restart. Gramine SGX users must add cert paths to the manifest's
allowed_files.
Server-side TLS only (story-kernel verifies its identity to story):
# In /opt/story-kernel/config.toml
[grpc]
listen_addr = ":50051"
tls_cert_file = "/path/to/server.crt"
tls_key_file = "/path/to/server.key"Mutual TLS (both sides verify each other):
# In /opt/story-kernel/config.toml
[grpc]
listen_addr = ":50051"
tls_cert_file = "/path/to/server.crt"
tls_key_file = "/path/to/server.key"
tls_ca_file = "/path/to/ca.crt" # enables client cert verificationOn the story consensus client side, configure in story.toml:
[dkg]
kernel-endpoints = ["tls://127.0.0.1:50051"]
kernel-tls-ca-file = "/path/to/ca.crt"
kernel-tls-cert-file = "/path/to/client.crt" # for mTLS
kernel-tls-key-file = "/path/to/client.key" # for mTLSIf no TLS fields are set, the gRPC connection runs in plaintext (insecure) mode — no changes needed for existing deployments.
gramine-sgx story-kernelThe manifest's loader.argv is hardcoded to ["story-kernel", "start", "--home", "/opt/story-kernel"],
so no additional arguments are needed.
sudo tee /etc/systemd/system/story-kernel.service > /dev/null <<EOF
[Unit]
Description=Story DKG TEE Service
After=network.target
[Service]
User=$USER
WorkingDirectory=$HOME/story-kernel
ExecStart=/bin/bash -lc "gramine-sgx story-kernel 2>&1 | systemd-cat -t story-kernel"
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable story-kernel.service
sudo systemctl start story-kernel.service
# View logs
journalctl -fu story-kernelmake testmake proto-genmake lintInstall pre-commit hooks:
pip install pre-commit
pre-commit installstory-kernel/
├── cmd/ # CLI commands (init, start)
├── config/ # Configuration handling
├── crypto/ # Cryptographic utilities
├── enclave/ # SGX enclave operations (seal, quote)
├── proto/ # Protocol buffer definitions
├── server/ # gRPC server implementation
├── service/ # DKG service logic
├── store/ # State and key storage
├── story/ # Story chain client
└── types/ # Common types and protobuf conversions
The service exposes a gRPC API with the following methods:
| Method | Description |
|---|---|
GenerateAndSealKey |
Generate and seal Ed25519/Secp256k1 key pairs |
GenerateDeals |
Generate DKG deals for distribution |
ProcessDeals |
Process received DKG deals |
ProcessResponses |
Process DKG responses |
FinalizeDKG |
Finalize DKG and produce distributed key share |
PartialDecryptTDH2 |
Perform TDH2 partial decryption |
- Code Commitment: The
mr_enclavevalue uniquely identifies the enclave code. Any modification to the binary or the Gramine manifest changes this value. - Sealed Storage: Private keys are sealed using SGX sealing keys and can only be unsealed by the same enclave on the same platform.
- Remote Attestation: The service generates DCAP quotes that can be verified by remote parties.
- SGX Debug Mode: The manifest sets
sgx.debug = falsefor production. Debug mode disables enclave memory protection and must never be enabled in production. - File Access: The Gramine manifest restricts enclave file access to
/opt/story-kernel/only./etc/ssl/certs/is inallowed_files(nottrusted_files) because CA certificate bundles differ across machines and would break MRENCLAVE reproducibility. - Fixed Data Path: The data directory is
/opt/story-kernel/(not~/.story-kernel/) to ensure all validators produce the same MRENCLAVE regardless of OS user. See the Data Directory section for details. - MRENCLAVE Reproducibility: The entire Gramine manifest — every byte — is measured into the code commitment. All values that could vary (log level, binary name) are hardcoded in the manifest. See Software Requirements for the exact versions required.
Please read CONTRIBUTING.md for details on our code of conduct and the process for submitting pull requests.
For security concerns, please see SECURITY.md.
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.