Writing the Secure Process

Secure Process

The Secure Process is the core FHE logic for your E3 Program. It runs within your selected Compute Provider's environment, ultimately producing and publishing a ciphertext output that is decrypted by your Ciphernode Committee. To facilitate this, Enclave provides a Compute Provider package (opens in a new tab) to simplify writing the Secure Process with any Compute Provider.

Using the Compute Provider Package

To simplify integration with Enclave, use the provided Compute Provider package.

Benefits:

  • Handles Merkle Tree Construction: Recreates the input Merkle tree inside the compute environment.
  • Simplifies Proof Generation: Manages proof creation for computation verification.
  • Abstracts Complexity: Allows you to focus on your computation logic.

Implementation:

  • Import the Compute Provider package into your project.
  • Use its functions to handle tasks such as input processing and proof generation.

Example:

use compute_provider::{ComputeInput, ComputeManager, ComputeProvider, ComputeResult, FHEInputs};
 
// Implement ComputeProvider trait for your chosen provider
pub struct Risc0Provider;
 
impl ComputeProvider for Risc0Provider {
    type Output = Risc0Output;
 
    fn prove(&self, input: &ComputeInput) -> Self::Output {
        // Implement proof generation using RISC Zero / SP1 or any other provider
        // ...
    }
}

Writing the Secure Process

Your Secure Process defines the core computation logic and runs inside the Compute Provider's environment. Below are the key steps to implement it effectively:

Steps:

  1. Define the Computation: Specify the exact computation your E3 program needs to perform.
  2. Implement the Logic: Write the Secure Process using the Compute Provider's supported language (e.g., Rust for RISC Zero).
  3. Handle Encrypted Inputs: Ensure the program can process encrypted data correctly.
  4. Focus on Computation: Use the Compute Provider package to handle additional tasks like Merkle tree verification and proof verification, so you can focus on your computation logic.

Example (Rust with RISC Zero):

use fhe::bfv::{BfvParameters, Ciphertext};
use fhe_traits::{Deserialize, Serialize};
use std::sync::Arc;
 
/// Your secure computation function
pub fn fhe_processor(fhe_inputs: &FHEInputs) -> Vec<u8> {
    // Deserialize parameters
    let params = Arc::new(BfvParameters::try_deserialize(&fhe_inputs.params).unwrap());
 
    // Initialize sum
    let mut sum = Ciphertext::zero(&params);
 
    // Sum all ciphertexts
    for ciphertext_bytes in &fhe_inputs.ciphertexts {
        let ciphertext = Ciphertext::from_bytes(&ciphertext_bytes.0, &params).unwrap();
        sum += &ciphertext;
    }
 
    // Serialize the result
    sum.to_bytes()
}

Running the Secure Process

To run the Secure Process, use the Compute Provider ComputeManager to execute the program. It expects:

  • The ComputeProvider implementation
  • The FHEInputs struct that consists of the FHE parameters and the ciphertexts to use.
  • The Secure Process function fhe_processor
  • A boolean flag use_parallel to indicate whether to use parallel processing.
  • An optional batch_size that will be used for parallel processing. Must be a power of 2.
// Run the secure process inside the Compute Provider
pub fn run_compute(params: FHEInputs) -> Result<(Risc0Output, Vec<u8>)> {
    // Use the previously implemented Risc0Provider
    let risc0_provider = Risc0Provider;
 
    // Create the ComputeManager with the provider, params, and the secure process function
    let mut provider = ComputeManager::new(risc0_provider, params, fhe_processor, false, None);
 
    // Execute the program and get the output
    let output: (Risc0Output, Vec<u8>) = provider.start();
 
    Ok(output)
}