L{CORE} Privacy Architecture
Attribution: The attestor layer of L{CORE} is built on Reclaim Protocol's attestor-core. This documentation describes how we've extended their open-source TEE infrastructure for IoT attestation use cases.
Last Updated: 2025-01-14
Status: Production Design
About This Architecture
L{CORE} combines two open-source projects to create decentralized IoT attestation:
-
Reclaim Protocol's attestor-core — The TEE-secured attestation layer that handles proof generation, signature verification, and zkTLS. This is Reclaim's code, not ours.
-
Cartesi Rollups — The deterministic compute layer that stores attestations in SQLite with fraud-proof verification.
Our contribution is the IoT integration layer: device SDKs (C, Python, TypeScript), did:key identity management, privacy bucketing for sensor data, and the bridge between IoT devices and Reclaim's attestor infrastructure.
Why L{CORE}?
| Benefit | Description |
|---|
| No Lock-In | Deploy on any EVM chain. Run on any infrastructure. Switch chains without rewriting your application. |
| No Fees | Zero protocol fees. Zero token requirements. You pay gas costs on your chosen chain—that's it. |
| Full Compute | Not a sandbox. Full Linux environment. Run SQLite, Python libraries, existing codebases—anything that runs on Linux. |
| Self-Sovereign | Run your own attestors. Own your infrastructure. No dependency on external networks or third-party uptime. |
| Device-First | C SDK for resource-constrained embedded devices. Real IoT attestation, not just mobile apps. |
Compatibility
| Component | Supported |
|---|
| Chains | Arbitrum, Arbitrum Orbit chains (Locale Network, City Chains) |
| Infrastructure | Self-hosted, AWS, GCP, Azure, EigenCloud TEE |
| Devices | ESP32, Arduino, ARM Cortex-M, Raspberry Pi, NVIDIA Jetson |
| Languages | Python, TypeScript, C |
Limitations
| Limitation | Details |
|---|
| EVM only | Currently requires EVM-compatible chain for settlement |
| Determinism required | Cartesi rollup code must be deterministic (no external network calls, no random) |
| TEE for production | Full security guarantees require TEE deployment (EigenCloud) |
| Latency | On-chain settlement adds latency vs centralized alternatives |
Table of Contents
- Executive Summary
- Problem Statement
- Architecture Overview
- EigenCloud Deployment Architecture
- Encryption Model
- Data Flow Diagrams
- Database Schema
- Access Control Model
- API Reference
- Key Management
- Security Considerations
- Compliance
- Deployment Checklist
Executive Summary
L{CORE} is a privacy-preserving attestation data layer built on Cartesi rollups (Arbitrum Sepolia L2). It stores user attestation data (financial info, employment status, identity claims) in a way that:
- Keeps data in Cartesi - Full database lives in Cartesi's SQLite, benefiting from fraud-proof verification
- Protects PII - All sensitive outputs are encrypted; only authorized parties can decrypt
- Enables privacy-preserving queries - Aggregate statistics available publicly, individual data protected
- Maintains regulatory compliance - GDPR/CCPA compliant by design
Built With
- Reclaim Protocol - zkTLS proofs for data verification
- Cartesi - Deterministic Linux VM for verifiable computation
- EigenCloud - TEE infrastructure for secure deployment
The Core Innovation
All Cartesi outputs (notices, reports) containing sensitive data are encrypted with an admin public key. Only the TEE Attestor holds the corresponding private key and can decrypt responses. External parties see only encrypted blobs.
Problem Statement
Cartesi's Transparency Model
Cartesi is designed for verifiable computation, not private computation:
- InputBox - Anyone can submit inputs (public)
- Notices - Published on-chain, publicly verifiable (public)
- Reports - Returned to callers, logged (semi-public)
- Vouchers - Execute on L1 (public)
This transparency is a feature for gaming, DeFi calculations, and governance. But it's a critical flaw for personal data.
Our Requirements
| Requirement | Cartesi Default | Our Need |
|---|
| Attestation data storage | Public | Private |
| Query responses | Public | Encrypted |
| Aggregate statistics | N/A | Public (anonymized) |
| Access control | None | Role-based |
| PII protection | None | Mandatory |
Legal Requirements
- GDPR (EU): Personal data must be protected; data subjects control access
- CCPA (California): Consumers can opt out of data sharing
- GLBA (US Financial): Financial data requires safeguards
Exposing PII on a public blockchain violates all of these.
Architecture Overview
flowchart TB
subgraph Clients["External Clients"]
U[Users via dApp]
D[dApps - Locale, Marketplace]
A[Admins Dashboard]
end
subgraph TEE["TEE Attestor Container :8001"]
AUTH[Authentication Layer]
AUTHZ[Authorization Layer]
KEY[Admin Private Key]
AUTH --> AUTHZ
AUTHZ --> KEY
end
subgraph Cartesi["Cartesi Rollup Container :10000"]
RS[Rollup Server]
DAPP[L\{CORE\} DApp]
PUB[Admin Public Key]
DB[(SQLite Database)]
RS --> DAPP
DAPP --> PUB
DAPP --> DB
end
subgraph Chain["Arbitrum Sepolia"]
IB[InputBox Contract]
FP[Fraud Proof System]
end
Clients -->|HTTPS| TEE
TEE -->|Internal HTTP| Cartesi
Cartesi -->|Settlement| Chain
style TEE fill:#4CAF50,color:#fff
style Cartesi fill:#2196F3,color:#fff
style Chain fill:#9C27B0,color:#fff
Component Responsibilities
| Component | Responsibility |
|---|
| TEE Attestor | Authentication, authorization, encryption/decryption, external API |
| Cartesi Rollup | State management, deterministic computation, SQLite storage |
| Arbitrum | Settlement, fraud proofs, InputBox contract |
EigenCloud Deployment Architecture
L{CORE} runs as two separate containers in EigenCloud's TEE environment:
flowchart TB
subgraph EigenCloud["EigenCloud TEE Infrastructure"]
subgraph C1["Container 1: Attestor"]
direction TB
A1[Node.js 20]
A2[Reclaim SDK]
A3[Encryption Service]
A4[External APIs]
A1 --> A2
A1 --> A3
A1 --> A4
end
subgraph C2["Container 2: Cartesi Node"]
direction TB
B1[RISC-V VM]
B2[L\{CORE\} DApp]
B3[(SQLite)]
B1 --> B2
B2 --> B3
end
end
subgraph External["External Services"]
RC[Reclaim Protocol]
SB[(Supabase)]
RPC[Blockchain RPC]
end
C1 <-->|Port 10000| C2
C1 --> External
style C1 fill:#4CAF50,color:#fff
style C2 fill:#2196F3,color:#fff
Why Two Containers?
| Requirement | Attestor | Cartesi Node |
|---|
| External Network | YES - Reclaim, Supabase, RPC | NO - Must be deterministic |
| State Management | Stateless | Full SQLite state |
| Scaling | Horizontal | Vertical |
| Port | 8001 | 10000 |
Critical Design Decision: Cartesi rollups MUST be deterministic to enable fraud proofs. Any external network call would break determinism. By separating containers:
- Attestor handles all non-deterministic operations (external APIs, encryption with random nonces)
- Cartesi Node remains purely deterministic (state derived only from InputBox inputs)
Production Deployment
| Service | IP Address | Port | Image |
|---|
| Cartesi Node | ${CARTESI_NODE_IP} | 10000 | modernsociety/lcore-cartesi-node:eigencloud |
| Attestor | ${ATTESTOR_IP} | 8001 | modernsociety/lcore-attestor:eigencloud |
Contract Addresses (Arbitrum Sepolia)
| Contract | Address |
|---|
| DApp | 0xAE0863401D5B953b89cad8a5E7c98f5136E9C26d |
| InputBox | 0x59b22D57D4f067708AB0c00552767405926dc768 |
| Authority | 0x08cC70a34EA78F35a871822F685dCB99EE079A08 |
| History | 0xF1A186AFC0C794dA242fcE50052592dDA30F0457 |
Encryption Model
Overview
We use asymmetric encryption (public/private key pair) to protect sensitive outputs:
sequenceDiagram
participant C as Cartesi DApp
participant A as Attestor (TEE)
participant U as User/dApp
Note over C: Has Admin PUBLIC Key
Note over A: Has Admin PRIVATE Key
C->>C: Prepare sensitive response
C->>C: Generate ephemeral keypair
C->>C: Encrypt with admin public key
C->>A: Return encrypted blob
A->>A: Decrypt with admin private key
A->>A: Generate TEE signature proof
A->>U: Return plaintext + proof
Encryption Algorithm
Algorithm: X25519-XSalsa20-Poly1305 (NaCl "box")
Why this algorithm:
- Fast (important for RISC-V performance)
- Well-audited (libsodium/tweetnacl)
- Authenticated encryption (integrity + confidentiality)
- Available in both Node.js and RISC-V
What Gets Encrypted
| Data Type | Encrypted? | Reason |
|---|
| Attestation data (parameters, context) | YES | Contains PII |
| Attestation metadata (id, owner, provider) | YES | Can identify individuals |
| Bucket values for specific users | YES | Reveals financial info |
| Aggregate counts ("47 users have >$10k") | NO | Anonymized, no PII |
| Provider schemas | NO | Public configuration |
| Schema admins list | NO | Public configuration |
| Error messages | NO | No PII |
interface EncryptedOutput {
version: 1;
algorithm: 'nacl-box';
nonce: string;
ciphertext: string;
publicKey: string;
}
Decryption Proofs
When the Attestor decrypts an encrypted response, it generates a decryption proof - a TEE signature proving that the decryption was performed correctly by a trusted enclave.
interface DecryptionProof {
ciphertextHash: string;
plaintextHash: string;
timestamp: number;
teeAddress: string;
signature: string;
}
Data Flow Diagrams
Flow 1: Attestation Ingestion
sequenceDiagram
participant U as User dApp
participant A as Attestor (TEE)
participant R as Rollup Server
participant L as L\{CORE\} DApp
U->>A: 1. Create claim
A->>A: 2. Sign claim (TEE signature)
A->>A: 3. Discretize (bucketize data)
A->>R: 4. Submit to /input/advance
R->>L: 5. Forward to DApp
L->>L: 6. Verify TEE signature
L->>L: 7. Check schema exists
L->>L: 8. Store attestation
L->>L: 9. Store buckets
L->>L: 10. Store encrypted data
L->>R: 11. Encrypted Notice
R->>A: 12. Encrypted response
A->>A: 13. Decrypt with admin key
A->>U: 14. Success response
Flow 2: dApp Query (with Grant)
sequenceDiagram
participant D as dApp (Locale)
participant A as Attestor (TEE)
participant R as Rollup Server
participant L as L\{CORE\} DApp
D->>A: 1. Query with API key + grant
A->>A: 2. Verify API key
A->>R: 3. Submit to /inspect
R->>L: 4. Forward to DApp
L->>L: 5. Check grant exists
L->>L: 6. Verify not expired
L->>L: 7. Fetch attestation
L->>L: 8. Encrypt response
L->>R: 9. Encrypted Report
R->>A: 10. Encrypted response
A->>A: 11. Decrypt with admin key
A->>D: 12. Plaintext data
Flow 3: Public Aggregate Query
sequenceDiagram
participant P as Anyone (Public)
participant A as Attestor
participant R as Rollup Server
participant L as L\{CORE\} DApp
P->>A: 1. Aggregate query (no auth)
A->>R: 2. Submit query
R->>L: 3. Forward to DApp
L->>L: 4. Compute aggregate
L->>L: 5. Check k-anonymity
L->>L: 6. Return COUNT only (NOT encrypted)
L->>R: 7. Plaintext Report
R->>A: 8. Plaintext response
A->>P: 9. { count: 47 }
Database Schema
Tables Overview
erDiagram
provider_schemas {
string provider PK
string flow_type PK
int version
string domain
json bucket_definitions
json data_keys
int freshness_half_life
}
schema_admins {
string wallet_address PK
string added_by
bool can_add_providers
bool can_add_admins
}
encryption_config {
string key_id PK
string public_key
string algorithm
int created_at
}
attestations {
string id PK
string attestation_hash
string owner_address
string domain
string provider
string flow_type
int valid_from
int valid_until
string tee_signature
string status
float freshness_score
}
attestation_buckets {
string attestation_id FK
string bucket_key
string bucket_value
}
attestation_data {
string attestation_id FK
string data_key
string encrypted_value
string encryption_key_id
}
access_grants {
string id PK
string attestation_id FK
string grantee_address
string granted_by
json data_keys
string grant_type
int expires_at_input
string status
}
attestations ||--o{ attestation_buckets : has
attestations ||--o{ attestation_data : has
attestations ||--o{ access_grants : has
provider_schemas ||--o{ attestations : defines
Privacy Classifications
| Table | Classification | Notes |
|---|
attestations | PRIVATE | Contains owner addresses, can identify users |
attestation_buckets | PRIVATE | Reveals financial info |
attestation_data | PRIVATE | Raw PII (already encrypted) |
access_grants | PRIVATE | Links entities |
provider_schemas | PUBLIC | Configuration only |
schema_admins | PUBLIC | Admin list |
encryption_config | PUBLIC | Public key is public |
Access Control Model
Actor Types
| Actor | Authentication | Can Access |
|---|
| Anonymous | None | Public aggregates only |
| User | Wallet signature | Their own attestation metadata, grant management |
| dApp | API key | Attestations they've been granted access to |
| Admin | Admin session token | Everything |
Permission Matrix
| Action | Anonymous | User | dApp | Admin |
|---|
| View aggregate counts | ✅ | ✅ | ✅ | ✅ |
| View own attestation list | ❌ | ✅ | ❌ | ✅ |
| View attestation data | ❌ | ❌ | ✅ (with grant) | ✅ |
| Create access grant | ❌ | ✅ (own data) | ❌ | ✅ |
| Revoke access grant | ❌ | ✅ (own grants) | ❌ | ✅ |
| Register provider schema | ❌ | ❌ | ❌ | ✅ |
| Add schema admin | ❌ | ❌ | ❌ | ✅ |
| View all attestations | ❌ | ❌ | ❌ | ✅ |
API Reference
Cartesi Inspect Queries (Port 10000)
Direct queries to the Cartesi node use URL-encoded JSON:
curl "http://${CARTESI_NODE_IP}:10000/inspect/$(python3 -c "import urllib.parse; print(urllib.parse.quote('{\"type\":\"QUERY_TYPE\",\"params\":{}}'))")"
Available Query Types:
| Query Type | Parameters | Description |
|---|
all_provider_schemas | {} | List all registered provider schemas |
attestations_by_owner | {owner, limit, offset} | Get attestations for an owner |
check_access | {attestation_id, grantee} | Check if grantee has access |
aggregate_by_bucket | {domain, bucket_key} | Get anonymized bucket counts |
Attestor API Endpoints (Port 8001)
Public Endpoints
GET /api/health
GET /api/lcore/health
GET /api/lcore/status
GET /api/lcore/aggregates/count-by-bucket?domain=lending&bucket_key=balance
GET /api/lcore/aggregates/count-by-provider?domain=lending
User Endpoints (Wallet Signature Required)
POST /api/lcore/user/attestations
POST /api/lcore/user/grants
POST /api/lcore/user/revoke-grant
dApp Endpoints (API Key Required)
GET /api/lcore/dapp/attestation/:attestation_id
GET /api/lcore/dapp/grants
Admin Endpoints (Admin Auth Required)
POST /api/lcore/schema-admin
POST /api/lcore/provider-schema
GET /api/lcore/admin/attestations
Key Management
Key Separation
| Key | Purpose | Algorithm | Location |
|---|
| Admin Encryption Key | Encrypt/decrypt L{CORE} responses | NaCl box (X25519) | Private in TEE, Public in Cartesi |
| TEE Signing Key | Sign attestations & proofs | ECDSA (secp256k1) | Private in TEE |
Key Generation
node -e "
const nacl = require('tweetnacl');
const keypair = nacl.box.keyPair();
console.log('LCORE_ADMIN_PUBLIC_KEY=' + Buffer.from(keypair.publicKey).toString('base64'));
console.log('LCORE_ADMIN_PRIVATE_KEY=' + Buffer.from(keypair.secretKey).toString('base64'));
"
Key Storage
| Key | Location | Protection |
|---|
| Admin Public Key | Cartesi encryption_config table | None needed (public) |
| Admin Private Key | Attestor environment variable | TEE enclave |
Security Considerations
Threat Model
| Threat | Mitigation |
|---|
| Anyone reads Cartesi outputs | Outputs encrypted with admin key |
| Attacker submits malicious input | Signature verification in DApp |
| Replay attack on queries | Nonce/timestamp in signed messages |
| TEE compromise | Key rotation capability, audit logs |
| Admin key theft | Key stored in TEE, never transmitted |
| Brute force on encrypted data | NaCl box is computationally secure |
What We Don't Protect Against
| Threat | Why Not Mitigated |
|---|
| Compromised TEE operator | Trust assumption; use remote attestation |
| Quantum computing | NaCl not post-quantum; upgrade path available |
| Side-channel in RISC-V | Out of scope for application layer |
Compliance
GDPR Compliance
| Requirement | How We Comply |
|---|
| Lawful basis | User consent (creates attestation voluntarily) |
| Data minimization | Only store what's needed, bucketize |
| Right to access | User can query their attestations |
| Right to erasure | Revocation marks as deleted |
| Data protection | Encrypted outputs, access control |
| Breach notification | Encrypted data; breach reveals nothing |
CCPA Compliance
| Requirement | How We Comply |
|---|
| Right to know | User can list their attestations |
| Right to delete | Revocation available |
| Right to opt-out | User controls grants |
| Non-discrimination | Access control is uniform |
Deployment Checklist
Pre-Deployment
Deployment
Post-Deployment Verification
Ongoing
Document History
| Version | Date | Changes |
|---|
| 2.0 | 2025-01-14 | Updated for EigenCloud deployment, Mermaid diagrams |
| 1.0 | 2025-01-12 | Initial architecture design |