quartz_enclave_core/
lib.rs

1/*!
2# Quartz Core Enclave
3
4This crate provides a *framework* for writing quartz application enclaves and implements the *core*
5enclave logic for the quartz handshake protocol. Quartz enforces all user <> enclave communication
6to happen via a blockchain for replay protection.
7
8At a high level, the code here implements:
9- The quartz enclave framework which includes various components that enable app devs to write
10  secure enclaves with replay protection. This includes trait definitions and default implementations.
11- Core enclave logic for the quartz handshake. This includes -
12    - **Event handlers** for handling core events.
13    - **Request handlers** for handling core requests.
14    - gRPC service implementation for the request handlers.
15
16---
17
18## Framework Design
19
20The framework separates trusted and untrusted code by defining two abstractions - the host and the
21enclave, each represented by a separate trait.
22
23### Host vs. Enclave Separation
24
25The **host** (untrusted) code is responsible for:
26- Identifying which chain events the application wants to handle.
27- Collecting all necessary on-chain data for each event to form a provable request
28  to the enclave (e.g., by fetching on-chain state, light client proofs, merkle proofs, etc.).
29- Calling the enclave with the created request.
30- Sending transactions to the blockchain on behalf of the enclave (AKA the response).
31
32The **enclave** (trusted) code is responsible for:
33- Determining which *requests* these events correspond to.
34- Verifying request data integrity (via light client proofs and merkle proofs).
35- Handling each request securely inside the TEE.
36- (Optionally) generating responses to be posted to the chain.
37- Attesting to the responses using remote-attestation (to be verified on-chain).
38
39Through this layered approach:
40- **Host** code (generally) runs outside the TEE, bridging the blockchain and the enclave.
41- **Enclave** code runs inside a Gramine-based TEE, protecting private data and cryptographic
42  operations.
43
44---
45
46## Lifecycle of a request
47
48Below is a simplified lifecycle for a typical user <> enclave interaction involving a quartz app
49enclave:
50
511. **User** *sends a request to the contract* (on-chain).
522. **Contract** *triggers an event* reflecting that new request.
533. **Host** (untrusted) *listens for relevant events* from the chain.
544. On seeing an event, the **Host** *constructs an enclave request* that encapsulates all the relevant
55   data for handling the event.
565. The **Host** then *calls the enclave* with that request.
576. **Enclave** (trusted) *handles the request*, verifies the data, performs the necessary
58  computations, and (optionally) returns an attested response.
597. The **Host** *sends the response* back to the chain, e.g. via a transaction.
60
61---
62
63## Usage
64See the app enclaves in the `examples` directory for usage examples.
65
66*/
67
68#![doc = include_str!("../README.md")]
69// #![forbid(unsafe_code)]
70#![warn(
71    clippy::checked_conversions,
72    clippy::panic,
73    clippy::panic_in_result_fn,
74    trivial_casts,
75    trivial_numeric_casts,
76    rust_2018_idioms,
77    unused_lifetimes,
78    unused_import_braces,
79    unused_qualifications
80)]
81
82use std::path::PathBuf;
83
84use anyhow::anyhow;
85use cosmrs::AccountId;
86use log::{debug, trace, warn};
87use quartz_contract_core::state::Config;
88use serde::{Deserialize, Serialize};
89use tokio::{
90    fs::File,
91    io::{AsyncReadExt, AsyncWriteExt},
92    sync::mpsc,
93};
94
95use crate::{
96    attestor::{Attestor, DefaultAttestor},
97    backup_restore::{Backup, Export, Import},
98    key_manager::{default::DefaultKeyManager, shared::SharedKeyManager, KeyManager},
99    store::{default::DefaultStore, Store},
100};
101
102pub mod attestor;
103pub mod backup_restore;
104pub mod chain_client;
105pub mod event;
106pub mod grpc;
107pub mod handler;
108pub mod host;
109pub mod key_manager;
110pub mod proof_of_publication;
111pub mod store;
112pub mod types;
113
114/// A type alias for a default, thread-safe enclave.
115///
116/// `DefaultSharedEnclave` is a specialization of [`DefaultEnclave`] that uses the default
117/// attestation and storage components, along with a shared key-manager and store to allow safe
118/// concurrent access.
119pub type DefaultSharedEnclave<C, K = DefaultKeyManager> =
120    DefaultEnclave<C, DefaultAttestor, SharedKeyManager<K>, DefaultStore>;
121
122/// Represents the core functionality running inside a TEE.
123///
124/// An `Enclave` is the trusted environment where sensitive logic and data
125/// reside. It provides access to three essential components:
126///
127/// 1. [`Attestor`]: Generates attestations, proving that the code truly runs within the expected
128///    enclave.
129/// 2. [`KeyManager`]: Manages a cryptographic key for encrypted communication with the enclave.
130///    The associated public key is published on-chain and private requests are expected to be
131///    encrypted to this public key so they can only be decrypted inside the enclave.
132/// 3. [`Store`]: A basic data store for core data used during the handshake.
133///
134/// For convenience, Quartz includes a default generic implementation (e.g. [`DefaultEnclave`]) that
135/// may suffice for many applications.
136#[async_trait::async_trait]
137pub trait Enclave: Send + Sync + 'static {
138    /// The type of attestor used by this enclave, handling generation of attestation quotes.
139    type Attestor: Attestor;
140    /// The type of key-manager used by this enclave, providing a cryptographic key for encryption.
141    type KeyManager: KeyManager;
142    /// The type of store used by this enclave, managing enclave state required for the handshake.
143    type Store: Store;
144
145    /// Returns a handle of this enclave's attestor. Since this async code, the expectation is that
146    /// the instance is shared and thread-safe. (e.g. behind a mutex)
147    async fn attestor(&self) -> Self::Attestor;
148
149    /// Returns a handle of this enclave's key-manager. Since this async code, the expectation is that
150    /// the instance is shared and thread-safe. (e.g. behind a mutex)
151    async fn key_manager(&self) -> Self::KeyManager;
152
153    /// Returns a handle of this enclave's store. Since this async code, the expectation is that
154    /// the instance is shared and thread-safe. (e.g. behind a mutex)
155    async fn store(&self) -> &Self::Store;
156}
157
158/// Notification the enclave may emit.
159#[derive(Debug, Clone)]
160pub enum Notification {
161    /// Fired once the enclave finishes its remote-attestation handshake.
162    HandshakeComplete,
163}
164
165/// The default generic implementation of the [`Enclave`] trait for convenience.
166/// Includes a generic context for additional application-specific data or configuration.
167#[derive(Clone, Debug)]
168pub struct DefaultEnclave<C, A = DefaultAttestor, K = DefaultKeyManager, S = DefaultStore> {
169    pub attestor: A,
170    pub key_manager: K,
171    pub store: S,
172    pub ctx: C,
173    pub notifier_tx: mpsc::Sender<Notification>,
174}
175
176impl<C: Send + Sync + 'static> DefaultSharedEnclave<C> {
177    pub fn shared(
178        attestor: DefaultAttestor,
179        config: Config,
180        ctx: C,
181    ) -> (DefaultSharedEnclave<C>, mpsc::Receiver<Notification>) {
182        let (notifier_tx, notifier_rx) = mpsc::channel(10); // ← NEW
183
184        (
185            DefaultSharedEnclave {
186                attestor,
187                key_manager: SharedKeyManager::wrapping(DefaultKeyManager::default()),
188                store: DefaultStore::new(config),
189                ctx,
190                notifier_tx,
191            },
192            notifier_rx,
193        )
194    }
195
196    /// Consumes a `DefaultEnclave` and returns another one with the specified key-manager.
197    pub fn with_key_manager<K: KeyManager>(
198        self,
199        key_manager: K,
200    ) -> DefaultEnclave<C, <Self as Enclave>::Attestor, K, <Self as Enclave>::Store> {
201        debug!("Updating enclave with new key manager");
202        DefaultEnclave {
203            attestor: self.attestor,
204            key_manager,
205            store: self.store,
206            ctx: self.ctx,
207            notifier_tx: self.notifier_tx,
208        }
209    }
210}
211
212#[async_trait::async_trait]
213impl<C, A, K, S> Enclave for DefaultEnclave<C, A, K, S>
214where
215    C: Send + Sync + 'static,
216    A: Attestor + Clone,
217    K: KeyManager + Clone,
218    S: Store<Contract = AccountId> + Clone,
219{
220    type Attestor = A;
221    type KeyManager = K;
222    type Store = S;
223
224    async fn attestor(&self) -> Self::Attestor {
225        trace!("Retrieving enclave attestor");
226        self.attestor.clone()
227    }
228
229    async fn key_manager(&self) -> Self::KeyManager {
230        trace!("Retrieving enclave key manager");
231        self.key_manager.clone()
232    }
233
234    async fn store(&self) -> &Self::Store {
235        trace!("Retrieving enclave store");
236        &self.store
237    }
238}
239
240#[derive(Clone, Serialize, Deserialize)]
241pub struct DefaultBackup {
242    store: Vec<u8>,
243    key_manager: Vec<u8>,
244    attestor: Vec<u8>,
245    ctx: Vec<u8>,
246}
247
248#[async_trait::async_trait]
249impl<C, A, K, S> Backup for DefaultEnclave<C, A, K, S>
250where
251    C: Send + Sync + Export + Import,
252    A: Attestor + Clone + Export + Import,
253    K: KeyManager + Clone + Export + Import,
254    S: Store<Contract = AccountId> + Clone + Export + Import,
255{
256    type Config = PathBuf;
257    type Error = anyhow::Error;
258
259    async fn backup(&self, config: Self::Config) -> Result<(), Self::Error> {
260        trace!("Backing up to {config:?}");
261
262        let exported_store = self
263            .store
264            .export()
265            .await
266            .map_err(|e| anyhow!("store export failed: {e:?}"))?;
267        let exported_key_manager = self
268            .key_manager
269            .export()
270            .await
271            .map_err(|e| anyhow!("key-manager export failed: {e:?}"))?;
272        let exported_attestor = self
273            .attestor
274            .export()
275            .await
276            .map_err(|e| anyhow!("attestor export failed: {e:?}"))?;
277        let exported_ctx = self
278            .ctx
279            .export()
280            .await
281            .map_err(|e| anyhow!("ctx export failed: {e:?}"))?;
282        let backup = DefaultBackup {
283            store: exported_store,
284            key_manager: exported_key_manager,
285            attestor: exported_attestor,
286            ctx: exported_ctx,
287        };
288        let backup_ser = serde_json::to_vec(&backup).expect("infallible serializer");
289
290        let mut sealed_file = File::create(config)
291            .await
292            .map_err(|e| anyhow!("backup file creation failed: {e:?}"))?;
293        sealed_file
294            .write_all(backup_ser.as_slice())
295            .await
296            .map_err(|e| anyhow!("backup writes failed: {e:?}"))?;
297
298        Ok(())
299    }
300
301    async fn has_backup(&self, config: Self::Config) -> bool {
302        config.is_file()
303    }
304
305    async fn try_restore(&mut self, config: Self::Config) -> Result<(), Self::Error> {
306        trace!("Restoring from {config:?}");
307
308        let mut sealed_file = File::open(config).await?;
309        let mut backup_ser = vec![];
310        sealed_file.read_to_end(&mut backup_ser).await?;
311        let backup: DefaultBackup = serde_json::from_slice(&backup_ser)?;
312
313        let imported_store = S::import(backup.store)
314            .await
315            .map_err(|e| anyhow!("store import failed: {e:?}"))?;
316        let imported_key_manager = K::import(backup.key_manager)
317            .await
318            .map_err(|e| anyhow!("key-manager import failed: {e:?}"))?;
319        let imported_attestor = A::import(backup.attestor)
320            .await
321            .map_err(|e| anyhow!("attestor import failed: {e:?}"))?;
322        let imported_ctx = C::import(backup.ctx)
323            .await
324            .map_err(|e| anyhow!("ctx import failed: {e:?}"))?;
325
326        self.store = imported_store;
327        self.key_manager = imported_key_manager;
328        self.attestor = imported_attestor;
329        self.ctx = imported_ctx;
330
331        // if restored from a previous backup - manually notify host of handshake completion
332        self.notifier_tx
333            .send(Notification::HandshakeComplete)
334            .await
335            .expect("Receiver half of the channel must NOT be closed");
336
337        Ok(())
338    }
339}