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}