Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 34 additions & 37 deletions crates/compute-provider/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ To use this library, add it to your `Cargo.toml`:

```toml
[dependencies]
e3-compute-provider = { git = "https://github.com/gnosisguild/interfold.git", path = "crates/compute-provider"}
e3-compute-provider = { git = "https://github.com/theinterfold/interfold.git" }
```

## Usage
Expand All @@ -26,56 +26,52 @@ To use the library, follow these steps:

1. Create an instance of the `ComputeManager` with your desired configuration.
2. Call the `start` method to begin the computation process.
3. The method will return the computed ciphertext and the corresponding proof.
3. The method returns the provider output together with the computed ciphertext bytes.

```rust
use anyhow::Result;
use e3_compute_provider::{ComputeInput, ComputeManager, ComputeProvider, ComputeResult, FHEInputs};
use voting_core::fhe_processor;

// Define your Risc0Provider struct and implement the ComputeProvider trait
pub fn run_compute(params: FHEInputs) -> Result<(Risc0Output, Vec<u8>)> {
let risc0_provider = Risc0Provider;
let mut provider = ComputeManager::new(risc0_provider, params, fhe_processor, false, None);
let output = provider.start();
Ok(output)
use e3_compute_provider::{ComputeManager, ComputeProvider, FHEInputs};
use my_program::fhe_processor;

pub fn run_compute<P>(params: FHEInputs, provider: P) -> (P::Output, Vec<u8>)
where
P: ComputeProvider + Send + Sync,
{
let mut manager = ComputeManager::new(provider, params, fhe_processor, false, None);
manager.start()
}
```

## Risc0 Example
`fhe_processor` is your own function. It must match the exported `FHEProcessor` alias,
`fn(&FHEInputs) -> Vec<u8>`.

Here's a more detailed example of how to use the Compute Manager with Risc0:
## Implementing a provider

`ComputeProvider` has one method and one associated type. Everything else is yours to choose:

```rust
use e3_compute_provider::{ComputeInput, ComputeManager, ComputeProvider, ComputeResult, FHEInputs};
use methods::VOTING_ELF;
use risc0_ethereum_contracts::groth16;
use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext};
use serde::{Deserialize, Serialize};

pub struct Risc0Provider;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Risc0Output {
pub result: ComputeResult,
pub seal: Vec<u8>,
use e3_compute_provider::{ComputeInput, ComputeProvider};

pub struct MyProvider;

pub struct MyOutput {
pub proof: Vec<u8>,
}

impl ComputeProvider for Risc0Provider {
type Output = Risc0Output;
impl ComputeProvider for MyProvider {
type Output = MyOutput;

fn prove(&self, input: &ComputeInput) -> Self::Output {
// Implementation details
// Prove that `input` produced its committed result, however your
// backend does that, and return whatever the caller needs.
MyOutput { proof: Vec::new() }
}
}
pub fn run_compute(params: FHEInputs) -> Result<(Risc0Output, Vec<u8>)> {
let risc0_provider = Risc0Provider;
let mut provider = ComputeManager::new(risc0_provider, params, fhe_processor, false, None);
let output: (Risc0Output, Vec<u8>) = provider.start();
Ok(output)
}
```

This example demonstrates how to create a Risc0Provider, use it with the ComputeManager, and measure
the execution time of the computation.
The repository's RISC Zero and Boundless providers live in `e3-support-host`. That crate is in a
separate workspace, so the dependency above does not pull it in. Inside an Interfold checkout, its
`run_risc0_compute` and `run_compute` entry points wrap the two backends, and
`crates/support/host/src/lib.rs` is the reference implementation to read.

## Configuration

Expand All @@ -85,4 +81,5 @@ The `ComputeManager::new()` function takes several parameters:
- `fhe_inputs`: The FHE inputs for the computation
- `fhe_processor`: A function to process the FHE inputs
- `use_parallel`: A boolean indicating whether to use parallel processing
- `batch_size`: An optional batch size for parallel processing, must be a power of 2
- `batch_size`: The number of ciphertexts per parallel chunk, read only when `use_parallel` is true.
It defaults to 2 when not supplied.
84 changes: 48 additions & 36 deletions crates/support/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ graph TD
fulfillment.
- **`types/`** — Shared request, webhook, proof-domain, guest-input, and journal types.
- **`methods/`** — RISC Zero build crate. Compiles the guest program.
- **`guest/`** — The RISC Zero zkVM guest program. Runs `fhe_processor` (homomorphic ciphertext
summation) and commits the domain-bound `ComputeJournal`.
- **`methods/guest/`** — The RISC Zero zkVM guest program. Runs `fhe_processor` (homomorphic
ciphertext summation) and commits the domain-bound `ComputeJournal`.
- **`program/`** — The FHE processor (`fhe_processor`): sums BFV ciphertexts homomorphically.

## Webhook Payload Format
Expand All @@ -57,7 +57,7 @@ The callback server receives a tagged-enum JSON payload:
```json
{
"status": "completed",
"e3_id": 123,
"e3_id": "123",
"ciphertext": "0x...",
"ciphertext_commitment": "0x...",
"proof": "0x..."
Expand All @@ -67,7 +67,7 @@ The callback server receives a tagged-enum JSON payload:
**Failure:**

```json
{ "status": "failed", "e3_id": 123, "error": "Computation failed: ..." }
{ "status": "failed", "e3_id": "123", "error": "Computation failed: ..." }
```

This matches the format expected by CRISP and `E3ProgramServer` in `crates/program-server`.
Expand Down Expand Up @@ -98,13 +98,14 @@ program:
pinata_jwt: '${PINATA_JWT}'
program_url: 'https://gateway.pinata.cloud/ipfs/Qm...' # after upload (Step 3)
onchain: true
# Optional — custom auction params (defaults shown):
# min_price_eth: 0.001
# max_price_eth: 0.03
# timeout_secs: 1200
# lock_timeout_secs: 600
# ramp_up_secs: 120
# lock_collateral_zkc: 5.0
# Built-in auction defaults, shown for reference. Leave these fields unset.
# If you set one, `interfold program start` fails. See #1812.
# min_price_eth: 0.00005
# max_price_eth: 0.002
# timeout_secs: 600
# lock_timeout_secs: 300
# ramp_up_secs: 60
# lock_collateral_zkc: 2.0
```

### Step 2: Compile the RISC Zero Guest Program
Expand Down Expand Up @@ -144,28 +145,33 @@ This boots the ciphernodes, which listen for E3 requests, perform DKG, and await
interfold program start
```

This starts the Docker container running `e3-support-app` on port 13151. If Boundless config is
present, it will submit proofs to the Boundless market. Otherwise it falls back to dev mode.
This starts the Docker container that runs `e3-support-app` on port 13151. The `risc0_dev_mode`
value selects the proving backend, as shown in Step 1. `0` submits proofs to the Boundless market.
`1` returns fake proofs. The default is `1` when the field is unset. A Boundless request with
missing credentials fails instead of using dev mode.

### Step 6: Submit an E3 Request

The E3 request is submitted on-chain by the instigator (e.g., CRISP coordination server):

```solidity
// On-chain: Interfold.request(params)
interfold.request(E3RequestParams({
threshold: [M, N],
interfold.request(IInterfold.E3RequestParams({
committeeSize: IInterfold.CommitteeSize.Minimum,
inputWindow: [start, end],
e3Program: crispProgramAddress,
e3ProgramParams: encodedParams,
e3Program: IE3Program(crispProgramAddress),
paramSet: paramSetIndex, // registered via setParamSet
computeProviderParams: "",
customParams: ""
customParams: encodedRoundConfig, // CRISPProgram decodes seven values from this
expectedFeeToken: IERC20(feeTokenAddress),
expectedCryptoConfigId: cryptoConfigId,
maxFee: maxFee
}));
```

This triggers:

1. Fee payment (1 USDC)
1. Payment of the quoted fee in the active fee token
2. Committee selection via sortition
3. DKG (C0-C5 proofs) → committee public key published on-chain
4. Stage → `KeyPublished`
Expand All @@ -179,11 +185,11 @@ server:
curl -X POST http://localhost:13151/run_compute \
-H "Content-Type: application/json" \
-d '{
"e3_id": 1,
"e3_id": "1",
"chain_id": 31337,
"interfold_address": "0x1111111111111111111111111111111111111111",
"encryption_scheme_id": "0x...",
"committee_public_key": "0x...",
"committee_public_key_hash": "0x...",
"params": "0x...",
"ciphertext_inputs": [["0x...", 0], ["0x...", 1]],
"callback_url": "http://host.local:4000/state/add-result"
Expand All @@ -192,12 +198,15 @@ curl -X POST http://localhost:13151/run_compute \

The program server:

1. Returns `{"status":"processing","e3_id":1}` immediately
1. Returns `{"status":"processing","e3_id":"1"}` immediately
2. Runs FHE computation (homomorphic sum) locally → ciphertext output
3. Submits proof request to Boundless market
4. Waits for a prover to fulfill the request
5. Sends webhook callback with
`{"status":"completed","e3_id":1,"ciphertext":"0x...","ciphertext_commitment":"0x...","proof":"0x..."}`
`{"status":"completed","e3_id":"1","ciphertext":"0x...","ciphertext_commitment":"0x...","proof":"0x..."}`
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Steps 3 and 4 belong to the Boundless path that Step 1 configures. With `risc0_dev_mode: 1` the
server runs the same computation and returns a fake proof instead.

### Step 8: Webhook Handler Publishes On-Chain

Expand All @@ -207,9 +216,11 @@ The callback server (e.g., CRISP) receives the webhook and calls:
interfold.publishCiphertextOutput(e3Id, ciphertextOutput, ciphertextCommitment, proof);
```

The proof binds the chain, Interfold contract, E3, encryption scheme, committee key, output, and
SAFE commitment. The protocol verifier checks these fields before the application verifier. Both
checks must pass before the E3 can remain in `CiphertextReady`.
The proof binds nine values. Five identify the context: the chain, the Interfold contract, the E3,
the encryption scheme, and the committee key hash. Four come from the computation: the output hash,
the SAFE commitment, the parameter hash, and the input root. The protocol verifier checks these
fields before the E3 program verifier. Both checks must pass before the E3 can remain in
`CiphertextReady`.

### Step 9: Decryption & Completion

Expand All @@ -221,7 +232,7 @@ rewards distributed.

## Boundless Offer Parameters

All parameters are configurable via environment variables (or `interfold.config.yaml`). Defaults:
`build_offer()` reads these environment variables. Defaults:

| Parameter | Env Var | Default | Description |
| ------------ | ------------------------------- | --------- | ---------------------------- |
Expand All @@ -232,15 +243,12 @@ All parameters are configurable via environment variables (or `interfold.config.
| Ramp-up | `BOUNDLESS_RAMP_UP_SECS` | `60` | Price ramp-up period (sec) |
| Collateral | `BOUNDLESS_LOCK_COLLATERAL_ZKC` | `2.0` | ZKC locked per request |

These can also be set in `interfold.config.yaml` under `program.risc0.boundless`:
`interfold program start` always uses these defaults. Neither route to change them works today: the
`program.risc0.boundless` fields make the launcher exit, and the environment variables never reach
the container. See #1812.

```yaml
boundless:
min_price_eth: 0.002
max_price_eth: 0.05
timeout_secs: 1800
# ...
```
To use other values, open a shell in the container, export the variables there, and start
`e3-support-app` yourself. `./scripts/dev.sh` opens such a shell.

---

Expand All @@ -254,7 +262,8 @@ boundless:
./scripts/build.sh --push
```

The container is also built by the GitHub workflow at `.github/workflows/support-docker.yml`.
CI builds the container in the `build_e3_support_risc0` job of `.github/workflows/ci.yml`. The
`build-e3-support-release` job in `.github/workflows/releases.yml` builds release images.

## Development

Expand All @@ -278,6 +287,9 @@ cargo run --bin e3-support-app
./curl_test.sh
```

`fixtures/payload.json` is out of date and the request fails to deserialize. Use the Step 7 body
until the fixture is refreshed.

NOTE: This is outside of the main workspace because it needs to be run within its own context in
order to isolate risc0.

Expand Down
Loading
Loading