Enclave is now The Interfold. Documentation is being updated.
Writing the Secure Process

Secure Process

The Secure Process defines the core computation for your E3 Program. It runs within your selected Compute Provider's environment, ultimately producing a ciphertext output that is decrypted by your Ciphernode Committee. To facilitate this, Interfold 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 the Interfold, 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 e3_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 with Boundless / SP1 or any other provider
        // See https://docs.beboundless.xyz/ for production proving
        // ...
    }
}

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 Program 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 generation, so you can focus on your computation logic.

Example (Rust with RISC Zero + Boundless):

use e3_fhe_params::decode_bfv_params_arc;
use e3_compute_provider::FHEInputs;
use fhe::bfv::Ciphertext;
use fhe_traits::{DeserializeParametrized, Serialize};
 
/// Your secure computation function
pub fn fhe_processor(fhe_inputs: &FHEInputs) -> Vec<u8> {
    // Decode the FHE parameters
    let params = decode_bfv_params_arc(&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. Each ciphertext is a tuple of (bytes, index) where index is the position in the input set.
  • 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.

The FHEInputs struct is defined as:

pub struct FHEInputs {
    pub ciphertexts: Vec<(Vec<u8>, u64)>,  // (ciphertext_bytes, index)
    pub params: Vec<u8>,                    // serialized BFV parameters
}
// 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)
}