Skip to main content

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:

SystemPurposeFlow
INPUT EncryptionProtect device data on-chainAttestor encrypts → Cartesi decrypts
OUTPUT EncryptionProtect query responsesCartesi encrypts → Attestor decrypts

These are independent systems with different keypairs. See Encryption Architecture for a detailed explanation.

Key Overview

KeyServiceLocationPurpose
LCORE_INPUT_PUBLIC_KEYAttestor.env fileEncrypt device data before on-chain submission
LCORE_INPUT_PRIVATE_KEYCartesiDockerfileDecrypt device data inside TEE
LCORE_OUTPUT_PUBLIC_KEYCartesiDockerfile or APIEncrypt query responses
LCORE_OUTPUT_PRIVATE_KEYAttestor.env fileDecrypt query responses

The Key Baking Requirement

Critical 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:

  1. Isolated Execution: The machine runs in complete isolation and cannot access external files or network resources
  2. Deterministic: Same inputs must always produce same outputs (required for fraud proofs)
  3. Sealed at Build Time: The machine image is frozen when you run npx cartesi build

Because of these properties:

  • Runtime environment variables (.env files) do not reach the Cartesi machine
  • The ARG/ENV pattern with --build-arg is 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
Template Hash Change

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:

  1. Generate new keypairs using the scripts above

  2. Update Cartesi Dockerfile with new private key:

    ENV LCORE_INPUT_PRIVATE_KEY=<new-base64-private-key>
  3. Rebuild Cartesi machine (this changes the template hash):

    npx cartesi build
    npx cartesi hash # Note the new hash
  4. Deploy new contracts (required because template hash changed):

    npx cartesi deploy --hosting self-hosted

    See Cartesi Deployment docs for network options.

  5. Update Attestor configuration with new public key:

    # attestor/.env
    LCORE_INPUT_PUBLIC_KEY=<new-base64-public-key>
  6. 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

  1. Submit a test attestation through the Attestor
  2. Check Cartesi logs for successful decryption
  3. 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