Encryption Key Configuration
This guide explains how to configure encryption keys for the L{CORE} attestation system. Understanding the key architecture is critical for successful deployment.
Understanding the Key Architecture
L{CORE} uses two separate encryption systems for different purposes:
| System | Purpose | Flow |
|---|---|---|
| INPUT Encryption | Protect device data on-chain | Attestor encrypts → Cartesi decrypts |
| OUTPUT Encryption | Protect query responses | Cartesi encrypts → Attestor decrypts |
These are independent systems with different keypairs. See Encryption Architecture for a detailed explanation.
Key Overview
| Key | Service | Location | Purpose |
|---|---|---|---|
LCORE_INPUT_PUBLIC_KEY | Attestor | .env file | Encrypt device data before on-chain submission |
LCORE_INPUT_PRIVATE_KEY | Cartesi | Dockerfile | Decrypt device data inside TEE |
LCORE_OUTPUT_PUBLIC_KEY | Cartesi | Dockerfile or API | Encrypt query responses |
LCORE_OUTPUT_PRIVATE_KEY | Attestor | .env file | Decrypt query responses |
The Key Baking Requirement
LCORE_INPUT_PRIVATE_KEY must be baked into the Cartesi Dockerfile, not passed via environment variables at runtime.
Why Keys Must Be Baked
The Cartesi Machine is a deterministic RISC-V emulator with specific constraints:
- Isolated Execution: The machine runs in complete isolation and cannot access external files or network resources
- Deterministic: Same inputs must always produce same outputs (required for fraud proofs)
- Sealed at Build Time: The machine image is frozen when you run
npx cartesi build
Because of these properties:
- Runtime environment variables (
.envfiles) do not reach the Cartesi machine - The
ARG/ENVpattern with--build-argis unreliable with the Cartesi CLI - The only reliable method is hardcoding the key directly in the Dockerfile
How to Bake Keys
In your Cartesi application's Dockerfile:
# Required: Input decryption key (must be baked in)
ENV LCORE_INPUT_PRIVATE_KEY=<your-base64-encoded-private-key>
# Optional: Output encryption public key (can also be set via API)
ENV LCORE_OUTPUT_PUBLIC_KEY=<your-base64-encoded-public-key>
Security Considerations
While hardcoding secrets in Dockerfiles is generally discouraged, this is acceptable for Cartesi TEE deployments because:
- The key is protected at runtime by the TEE hardware isolation
- The key cannot be extracted from a running TEE instance
- Remote attestation verifies the integrity of the execution environment
However, you should:
- Never commit keys to public repositories
- Store the Dockerfile with keys in a private repository or secure location
- Use different keys for development/staging/production environments
Generating Keypairs
L{CORE} uses NaCl box encryption (Curve25519-XSalsa20-Poly1305). Generate keypairs using:
Node.js
const nacl = require('tweetnacl');
// Generate INPUT encryption keypair
const inputKeyPair = nacl.box.keyPair();
console.log('# INPUT Encryption Keys');
console.log('# Public key → attestor/.env');
console.log('LCORE_INPUT_PUBLIC_KEY=' + Buffer.from(inputKeyPair.publicKey).toString('base64'));
console.log('# Private key → cartesi/Dockerfile (BAKE IN!)');
console.log('LCORE_INPUT_PRIVATE_KEY=' + Buffer.from(inputKeyPair.secretKey).toString('base64'));
// Generate OUTPUT encryption keypair
const outputKeyPair = nacl.box.keyPair();
console.log('\n# OUTPUT Encryption Keys');
console.log('LCORE_OUTPUT_PUBLIC_KEY=' + Buffer.from(outputKeyPair.publicKey).toString('base64'));
console.log('LCORE_OUTPUT_PRIVATE_KEY=' + Buffer.from(outputKeyPair.secretKey).toString('base64'));
Python
from nacl.public import PrivateKey
import base64
# Generate INPUT encryption keypair
input_private = PrivateKey.generate()
input_public = input_private.public_key
print("# INPUT Encryption Keys")
print(f"LCORE_INPUT_PUBLIC_KEY={base64.b64encode(bytes(input_public)).decode()}")
print(f"LCORE_INPUT_PRIVATE_KEY={base64.b64encode(bytes(input_private)).decode()}")
# Generate OUTPUT encryption keypair
output_private = PrivateKey.generate()
output_public = output_private.public_key
print("\n# OUTPUT Encryption Keys")
print(f"LCORE_OUTPUT_PUBLIC_KEY={base64.b64encode(bytes(output_public)).decode()}")
print(f"LCORE_OUTPUT_PRIVATE_KEY={base64.b64encode(bytes(output_private)).decode()}")
Configuring the Attestor
The Attestor needs the public INPUT key and the private OUTPUT key:
# attestor/.env
# For encrypting device data (INPUT system)
LCORE_INPUT_PUBLIC_KEY=<base64-encoded-public-key>
# For decrypting query responses (OUTPUT system)
LCORE_OUTPUT_PRIVATE_KEY=<base64-encoded-private-key>
Configuring the Cartesi Machine
The Cartesi machine needs the private INPUT key and the public OUTPUT key:
# cartesi/Dockerfile
# For decrypting device data (INPUT system) - REQUIRED
ENV LCORE_INPUT_PRIVATE_KEY=<base64-encoded-private-key>
# For encrypting query responses (OUTPUT system) - OPTIONAL
ENV LCORE_OUTPUT_PUBLIC_KEY=<base64-encoded-public-key>
After modifying the Dockerfile, you must rebuild the Cartesi machine:
npx cartesi build
Changing any ENV value in the Dockerfile creates a new template hash. You must deploy new smart contracts when the template hash changes. See EigenCloud Deployment for details.
Key Rotation Procedure
To rotate encryption keys:
-
Generate new keypairs using the scripts above
-
Update Cartesi Dockerfile with new private key:
ENV LCORE_INPUT_PRIVATE_KEY=<new-base64-private-key> -
Rebuild Cartesi machine (this changes the template hash):
npx cartesi build
npx cartesi hash # Note the new hash -
Deploy new contracts (required because template hash changed):
npx cartesi deploy --hosting self-hostedSee Cartesi Deployment docs for network options.
-
Update Attestor configuration with new public key:
# attestor/.env
LCORE_INPUT_PUBLIC_KEY=<new-base64-public-key> -
Redeploy both services with updated configurations
Verifying Key Configuration
Check Cartesi Machine Config
After building, verify keys are baked in:
cat .cartesi/image/config.json | jq '.env'
You should see your LCORE_INPUT_PRIVATE_KEY in the output (not empty).
Test Encryption Flow
- Submit a test attestation through the Attestor
- Check Cartesi logs for successful decryption
- Query the stored data to verify the round-trip
Common Mistakes
Passing Keys via Runtime Environment
Symptom: Decryption fails, "Failed to decrypt input" errors
Cause: Key was set in .env.eigencloud or passed at container runtime instead of baked into Dockerfile
Fix: Add key directly to Dockerfile with ENV directive
Mismatched Keypairs
Symptom: Decryption fails even with key baked in
Cause: Public key in Attestor doesn't match private key in Cartesi
Fix: Regenerate keypair and ensure both services use matching keys
Empty Key After Build
Symptom: .cartesi/image/config.json shows LCORE_INPUT_PRIVATE_KEY= (empty)
Cause: Used ARG without proper ENV assignment, or key not present at build time
Fix: Use ENV directly with the key value, not ARG + ENV pattern
Next Steps
- Encryption Architecture — Deep dive on the two encryption systems
- EigenCloud Deployment — Deploy to TEE infrastructure
- Troubleshooting — Common deployment issues