From e0e438e2cba6fabeb12bdf27d3ae5cd21fc771aa Mon Sep 17 00:00:00 2001 From: Toby1009 <69885352+Toby1009@users.noreply.github.com> Date: Sun, 16 Aug 2026 09:54:19 +0800 Subject: [PATCH 01/10] fix(docs): correct stale API references in support and building guides Closes #1808. The request example, journal description, event signatures, JSON payload shapes, and Boundless defaults in these documents no longer matched the code. Every corrected value was checked against the current source, and the executable snippets were run: the Solidity literal compiles under solc 0.8.28, the JavaScript object encodes against the compiled Interfold ABI, the Rust snippet compiles against e3-compute-provider, and the YAML parses through the interfold CLI. Two known-stale items are left alone on purpose. The Result Verification paragraph in building-with-interfold.mdx keeps its current wording, and the committee_public_key field in the /run_compute example is already fixed by open PR #1772. templates/default/interfold.config.yaml is included because its comment described the Boundless auction fields as usable overrides, which now contradicts the README. See #1812 for the underlying flag mismatch. Co-Authored-By: Claude Opus 5 (1M context) --- crates/compute-provider/Readme.md | 66 ++++++++------- crates/support/README.md | 87 +++++++++++-------- docs/pages/building-with-interfold.mdx | 106 +++++++++++++----------- templates/default/interfold.config.yaml | 3 +- 4 files changed, 145 insertions(+), 117 deletions(-) diff --git a/crates/compute-provider/Readme.md b/crates/compute-provider/Readme.md index 13a956af6..e719edf6e 100644 --- a/crates/compute-provider/Readme.md +++ b/crates/compute-provider/Readme.md @@ -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 @@ -26,56 +26,55 @@ 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)> { - 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

(params: FHEInputs, provider: P) -> (P::Output, Vec) +where + P: ComputeProvider + Send + Sync, +{ + let mut manager = ComputeManager::new(provider, params, fhe_processor, false, None); + manager.start() } ``` +`fhe_processor` is your own function. It must match the exported `FHEProcessor` alias, +`fn(&FHEInputs) -> Vec`. The next section shows a concrete `ComputeProvider`. + ## Risc0 Example -Here's a more detailed example of how to use the Compute Manager with Risc0: +`crates/support` contains a working RISC Zero provider. The provider has the following structure: ```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)] +use e3_compute_provider::{ComputeInput, ComputeProvider}; +use e3_support_types::{ComputeDomain, ComputeJournal}; + +pub struct Risc0Provider { + domain: ComputeDomain, +} + +#[derive(Debug, Clone)] pub struct Risc0Output { - pub result: ComputeResult, + pub result: ComputeJournal, + pub bytes: Vec, pub seal: Vec, } impl ComputeProvider for Risc0Provider { type Output = Risc0Output; fn prove(&self, input: &ComputeInput) -> Self::Output { - // Implementation details + // Run the guest and wrap the receipt. See the full implementation. + todo!() } } -pub fn run_compute(params: FHEInputs) -> Result<(Risc0Output, Vec)> { - let risc0_provider = Risc0Provider; - let mut provider = ComputeManager::new(risc0_provider, params, fhe_processor, false, None); - let output: (Risc0Output, Vec) = 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. +To use that provider directly rather than write your own, call +`e3_support_host::run_risc0_compute(params, domain)`. The complete implementation, including the +Boundless variant, is in `crates/support/host/src/lib.rs`. ## Configuration @@ -85,4 +84,7 @@ 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`: An optional batch size, read only when `use_parallel` is true. It defaults to 2 when + not supplied. The constructor does not validate it, so `use_parallel: true` together with + `Some(0)` reaches `slice::chunks(0)` and panics. With `use_parallel: false` the value is never + read. Every current caller passes `use_parallel: false` and `batch_size: None`. diff --git a/crates/support/README.md b/crates/support/README.md index 4e5607a4d..334613594 100644 --- a/crates/support/README.md +++ b/crates/support/README.md @@ -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 @@ -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..." @@ -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`. @@ -98,13 +98,15 @@ 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. + # `interfold program start` forwards these fields as flags, but its launcher + # rejects the flags. If you set a field, the container does not start. + # 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 @@ -144,8 +146,10 @@ 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 configuration default is `1`. The provider reads the mode once per +request. A Boundless request with missing credentials fails instead of using dev mode. ### Step 6: Submit an E3 Request @@ -153,19 +157,22 @@ The E3 request is submitted on-chain by the instigator (e.g., CRISP coordination ```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` @@ -179,7 +186,7 @@ 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...", @@ -192,12 +199,12 @@ 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..."}` ### Step 8: Webhook Handler Publishes On-Chain @@ -207,9 +214,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 @@ -221,7 +230,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 | | ------------ | ------------------------------- | --------- | ---------------------------- | @@ -232,15 +241,15 @@ 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`: - -```yaml -boundless: - min_price_eth: 0.002 - max_price_eth: 0.05 - timeout_secs: 1800 - # ... -``` +Through `interfold program start` these six values are always the defaults. `build_offer()` still +runs on every Boundless request, but it finds every variable unset. The `program.risc0.boundless` +fields exist in `interfold.config.yaml` and `crates/support-scripts/src/program_risc0.rs` forwards +each one as a flag, but `crates/support-scripts/ctl/start` has no `case` for those six flags and +exits. The environment variables do not arrive either, because +`crates/support-scripts/ctl/container` passes no environment to `docker run`. Note that the inner +`scripts/container/start.sh` does parse and export all six, so the missing cases are in the outer +launcher. To use other values, export them yourself in a container shell before you start +`e3-support-app`. `./scripts/dev.sh` opens such a shell, and it forwards no environment either. --- @@ -254,7 +263,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 @@ -278,6 +288,11 @@ cargo run --bin e3-support-app ./curl_test.sh ``` +`fixtures/payload.json` predates the current `ComputeRequest` type. It sends a numeric `e3_id` and +omits `chain_id`, `interfold_address`, `encryption_scheme_id`, and `committee_public_key_hash`, so +the request fails to deserialize. Use the `/run_compute` body from Step 7 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. diff --git a/docs/pages/building-with-interfold.mdx b/docs/pages/building-with-interfold.mdx index 30979247c..c8b3638d7 100644 --- a/docs/pages/building-with-interfold.mdx +++ b/docs/pages/building-with-interfold.mdx @@ -1,8 +1,9 @@ # Building with the Interfold The Interfold smart contract acts as the central coordinator for all E3 operations. It manages -computation requests, input validation, Ciphernode Committees (CiCos), and result publication while -maintaining the security and privacy guarantees of the protocol. +computation requests, Ciphernode Committees (CiCos), and result publication while maintaining the +security and privacy guarantees of the protocol. Each E3 program validates its own requests, inputs, +and outputs. ## The Interfold Smart Contract @@ -10,11 +11,13 @@ maintaining the security and privacy guarantees of the protocol. - Manage E3 computation requests - Coordinate Ciphernode Committees -- Handle encrypted input submission -- Manage Merkle trees for input verification +- Store E3 program references and invoke their request-validation and output-verification hooks - Publish computation results - Emit events for off-chain services +Input submission and the input Merkle tree belong to each E3 program, not to Interfold. See +`IE3Program.publishInput`. + ### Key State Variables ```solidity @@ -47,7 +50,7 @@ contract Interfold { // Mapping of E3 payment amounts. mapping(uint256 e3Id => uint256 amount) public e3Payments; - // Maximum allowed duration for an E3's input window. + // Maximum allowed worst-case request-to-decryption duration. uint256 public maxDuration; // Auto-incrementing E3 identifier. @@ -61,9 +64,9 @@ Each computation is tracked by an `E3` struct: ```solidity struct E3 { - uint256 seed; // Random seed for committee selection + uint256 seed; // Random seed for the E3 computation CommitteeSize committeeSize; // Committee size enum (Minimum, Micro, Small) - uint256 requestBlock; // Block when E3 was requested + uint256 requestBlock; // Request timestamp, despite the name uint256[2] inputWindow; // [start, end] timestamps for input acceptance bytes32 encryptionSchemeId; // Encryption scheme identifier IE3Program e3Program; // E3 Program contract address @@ -75,6 +78,7 @@ struct E3 { bytes32 ciphertextOutput; // Hash of encrypted output bytes plaintextOutput; // Decrypted final result address requester; // Entity that requested computation + bytes32 ciphertextCommitment; // SAFE commitment to the decoded BFV ciphertext } ``` @@ -82,15 +86,15 @@ struct E3 { Each E3 progresses through these stages: -| Stage | Value | Description | -| -------------------- | ----- | ----------------------------------------------- | -| `None` | 0 | E3 does not exist | -| `Requested` | 1 | E3 submitted, committee selection initiated | -| `CommitteeFinalized` | 2 | Sortition complete, DKG started | -| `KeyPublished` | 3 | Committee public key published, inputs accepted | -| `CiphertextReady` | 4 | Encrypted output published, awaiting decryption | -| `Complete` | 5 | Plaintext output published, rewards distributed | -| `Failed` | 6 | Timeout or fault detected, refund initiated | +| Stage | Value | Description | +| -------------------- | ----- | ------------------------------------------------------------------------ | +| `None` | 0 | E3 does not exist | +| `Requested` | 1 | E3 submitted, committee selection initiated | +| `CommitteeFinalized` | 2 | Sortition complete, DKG started | +| `KeyPublished` | 3 | Committee public key published; the E3 program controls input acceptance | +| `CiphertextReady` | 4 | Encrypted output published, awaiting decryption | +| `Complete` | 5 | Plaintext output published, rewards distributed | +| `Failed` | 6 | Failure recorded. `processE3Failure` calculates refunds | ### View Functions @@ -104,14 +108,7 @@ function getE3(uint256 e3Id) external view returns (E3 memory e3); function getE3Stage(uint256 e3Id) external view returns (E3Stage stage); // Estimate the fee for an E3 request -function getE3Quote( - CommitteeSize committeeSize, - uint256[2] calldata inputWindow, - IE3Program e3Program, - uint8 paramSet, - bytes calldata computeProviderParams, - bytes calldata customParams -) external view returns (uint256 fee); +function getE3Quote(E3RequestParams calldata e3Params) external view returns (uint256 fee); // Quotes are cost-plus: modeled ciphernode costs plus marginBps. On success, // the treasury receives protocolShareBps of the gross E3 fee and the active @@ -141,26 +138,22 @@ function getTimeoutConfig() external view returns (E3TimeoutConfig memory config ### Request Flow -1. Users submit the request parameters: committee size, timing windows, program references, BFV - parameter set, and optional custom params. +1. Users submit one `E3RequestParams` value. It carries the committee size, the input window, the + program reference, the BFV parameter set, the application params, and the fee bounds the + requester accepts. ```solidity function request( - CommitteeSize committeeSize, - uint256[2] calldata inputWindow, - IE3Program e3Program, - uint8 paramSet, - bytes calldata computeProviderParams, - bytes calldata customParams - ) external returns (uint256 e3Id); + E3RequestParams calldata requestParams + ) external returns (uint256 e3Id, E3 memory e3); ``` -2. Contract validates the parameters, estimates the fee, and stores an `E3` record with the shared - seed used for deterministic ticket scoring. +2. Contract validates the parameters, quotes the fee, and stores an `E3` record with a seed for the + computation. The registry derives a separate committee seed after the request is final. 3. The E3 program's `validate` hook returns the encryption scheme ID, which is persisted on the `E3` struct. 4. Committee selection is delegated to the `ciphernodeRegistry` via `requestCommittee`. -5. `E3Requested(e3Id, e3, e3ProgramAddress)` is emitted for off-chain watchers. +5. `E3Requested(e3Id, e3, cryptoConfigId)` is emitted for off-chain watchers. ## Committee and Ciphernode Management @@ -180,8 +173,10 @@ Distributed Key Generation (DKG) to produce a shared public key. When the aggreg published, the CiphernodeRegistry calls `onCommitteePublished()` on the Interfold contract, which transitions the E3 to the `KeyPublished` stage. -Once the E3 reaches the `KeyPublished` stage and the input window opens -(`block.timestamp >= inputWindow[0]`), Data Providers can submit inputs to the computation. +Interfold does not gate input submission. Each E3 program does, in its own `publishInput`. The +recommended gate is the `KeyPublished` stage plus both bounds of `inputWindow`. The default template +checks only `inputWindow[1]`, so it accepts inputs from the `Requested` stage onward. `CRISPProgram` +checks the stage and both bounds. ## Input Publication @@ -204,8 +199,10 @@ program implementation). This allows you to: ### Merkle Tree Construction -The Interfold uses a Lean Merkle Tree implementation. Each input is hashed using the PoseidonT3 Hash -function, and the root is updated with each new input. +The input tree belongs to the E3 program, not to Interfold. The default template uses +`InternalLazyIMT` from `@zk-kit/lazy-imt.sol`. It inserts each SAFE ciphertext commitment directly +as a leaf. PoseidonT3 combines node pairs as the tree grows, and `_root()` computes the root on +read. Your E3 program should: @@ -236,11 +233,15 @@ the E3's public key. It is also recommended to bundle in proofs to validate: **Interfold Contract Events:** ```solidity -event E3Requested(uint256 e3Id, E3 e3, IE3Program indexed e3Program); +event E3Requested(uint256 e3Id, E3 e3, bytes32 indexed cryptoConfigId); event E3StageChanged(uint256 indexed e3Id, E3Stage previousStage, E3Stage newStage); -event CiphertextOutputPublished(uint256 indexed e3Id, bytes ciphertextOutput); +event CiphertextOutputPublished( + uint256 indexed e3Id, + bytes ciphertextOutput, + bytes32 ciphertextCommitment +); event PlaintextOutputPublished(uint256 indexed e3Id, bytes plaintextOutput, bytes proof); @@ -252,14 +253,14 @@ event RewardsDistributed(uint256 indexed e3Id, address[] nodes, uint256[] amount **Program Contract Events:** ```solidity -event InputPublished(uint256 indexed e3Id, bytes data, uint256 inputHash, uint256 index); +event InputPublished(uint256 indexed e3Id, bytes data, uint256 index); ``` ### Result Publication Flow 1. The Compute Provider submits a proof and ciphertext output. -2. The Interfold uses the E3 Program contract to verify the proof and emits - `CiphertextOutputPublished`. +2. Interfold verifies the proof twice. The request-time protocol verifier runs first, then the E3 + program verifier. Both checks must pass before Interfold emits `CiphertextOutputPublished`. 3. Ciphernodes decrypt the ciphertext output. 4. The plaintext result is then published and the Decryption Verifier validates the decryption proof. @@ -274,15 +275,24 @@ event InputPublished(uint256 indexed e3Id, bytes data, uint256 inputHash, uint25 const interfoldContract = new ethers.Contract(interfoldAddress, interfoldAbi, signer) const requestParams = { - threshold: [3, 5], + committeeSize, inputWindow: [inputWindowStart, inputWindowEnd], e3Program: e3ProgramAddress, - e3ProgramParams, + paramSet, computeProviderParams, customParams: '0x', + expectedFeeToken, + expectedCryptoConfigId, + maxFee, } -const tx = await interfoldContract.request(requestParams) +// `request` pulls the fee with `transferFrom`, so quote it and approve it first. +const feeTokenContract = new ethers.Contract(expectedFeeToken, erc20Abi, signer) +const fee = await interfoldContract.getE3Quote(requestParams) +await (await feeTokenContract.approve(interfoldAddress, fee)).wait() + +// `request` reverts with `FeeExceedsMaximum` when the quote is above `maxFee`. +const tx = await interfoldContract.request({ ...requestParams, maxFee: fee }) const receipt = await tx.wait() const e3Id = receipt.logs .map((log) => interfoldContract.interface.parseLog(log)) diff --git a/templates/default/interfold.config.yaml b/templates/default/interfold.config.yaml index aed306284..36dde74c0 100644 --- a/templates/default/interfold.config.yaml +++ b/templates/default/interfold.config.yaml @@ -30,7 +30,8 @@ program: # pinata_jwt: "PINATA_JWT" # For uploading programs # program_url: "https://gateway.pinata.cloud/ipfs/QmNMRAB7DW43JSmENfzGmD96G6sqaeBBNfTVrrq5WQae3D" # Pre-uploaded program # onchain: true # true = onchain requests, false = offchain - # Optional — custom auction parameters (defaults shown): + # Built-in auction defaults, shown for reference. Leave these unset, because + # `interfold program start` forwards them as flags that its launcher rejects. # min_price_eth: 0.00005 # max_price_eth: 0.002 # timeout_secs: 600 From 3feabcedaf09ef9a4ff05385925258af5229deea Mon Sep 17 00:00:00 2001 From: Toby1009 <69885352+Toby1009@users.noreply.github.com> Date: Sun, 16 Aug 2026 15:57:26 +0800 Subject: [PATCH 02/10] fix(docs): make the payload.json warning self-contained The warning pointed at the Step 7 request body, which still carries the field name that PR #1772 corrects. Following it produced a request that also fails. The warning now lists the required fields and points at the ComputeRequest type instead. Co-Authored-By: Claude Opus 5 (1M context) --- crates/support/README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/support/README.md b/crates/support/README.md index 334613594..3514e85c9 100644 --- a/crates/support/README.md +++ b/crates/support/README.md @@ -288,10 +288,11 @@ cargo run --bin e3-support-app ./curl_test.sh ``` -`fixtures/payload.json` predates the current `ComputeRequest` type. It sends a numeric `e3_id` and -omits `chain_id`, `interfold_address`, `encryption_scheme_id`, and `committee_public_key_hash`, so -the request fails to deserialize. Use the `/run_compute` body from Step 7 until the fixture is -refreshed. +`fixtures/payload.json` predates the current `ComputeRequest` type, so the request fails to +deserialize. It sends a numeric `e3_id`, and it omits four required fields. A working body carries +`e3_id` as a string plus `chain_id`, `interfold_address`, `encryption_scheme_id`, +`committee_public_key_hash`, `params`, and `ciphertext_inputs`. Build one from `ComputeRequest` in +`crates/support/types/src/lib.rs` 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. From b50554181066e6e25551cf3419248de2af8a79c2 Mon Sep 17 00:00:00 2001 From: Toby1009 <69885352+Toby1009@users.noreply.github.com> Date: Sun, 16 Aug 2026 16:02:17 +0800 Subject: [PATCH 03/10] fix(docs): use committee_public_key_hash in the run_compute example ComputeRequest declares committee_public_key_hash and the handler reads that field. Open PR #1772 corrects the same line as an incidental part of a feature change; both edits set the same value, so a conflict resolves to one line. Co-Authored-By: Claude Opus 5 (1M context) --- crates/support/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/support/README.md b/crates/support/README.md index 3514e85c9..8ce872021 100644 --- a/crates/support/README.md +++ b/crates/support/README.md @@ -190,7 +190,7 @@ curl -X POST http://localhost:13151/run_compute \ "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" From 8f2344431a46bd330a620f8db7298b64f97a87ad Mon Sep 17 00:00:00 2001 From: Toby1009 <69885352+Toby1009@users.noreply.github.com> Date: Sun, 16 Aug 2026 16:09:31 +0800 Subject: [PATCH 04/10] fix(docs): make the Boundless defaults note readable The paragraph had accumulated a clause per review round and named five internal files in a user-facing README. It now states what the reader can and cannot do, and points at #1812 for the launcher defect. Co-Authored-By: Claude Opus 5 (1M context) --- crates/support/README.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/crates/support/README.md b/crates/support/README.md index 8ce872021..8419f8bd2 100644 --- a/crates/support/README.md +++ b/crates/support/README.md @@ -241,15 +241,12 @@ rewards distributed. | Ramp-up | `BOUNDLESS_RAMP_UP_SECS` | `60` | Price ramp-up period (sec) | | Collateral | `BOUNDLESS_LOCK_COLLATERAL_ZKC` | `2.0` | ZKC locked per request | -Through `interfold program start` these six values are always the defaults. `build_offer()` still -runs on every Boundless request, but it finds every variable unset. The `program.risc0.boundless` -fields exist in `interfold.config.yaml` and `crates/support-scripts/src/program_risc0.rs` forwards -each one as a flag, but `crates/support-scripts/ctl/start` has no `case` for those six flags and -exits. The environment variables do not arrive either, because -`crates/support-scripts/ctl/container` passes no environment to `docker run`. Note that the inner -`scripts/container/start.sh` does parse and export all six, so the missing cases are in the outer -launcher. To use other values, export them yourself in a container shell before you start -`e3-support-app`. `./scripts/dev.sh` opens such a shell, and it forwards no environment either. +`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. + +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. --- From b513e07a99065d28a3d919b6f83c729a7fd11618 Mon Sep 17 00:00:00 2001 From: Toby1009 <69885352+Toby1009@users.noreply.github.com> Date: Sun, 16 Aug 2026 16:11:39 +0800 Subject: [PATCH 05/10] fix(docs): trim clauses that accumulated across review rounds Four passages had gained a clause per review to satisfy a finding, without anyone checking whether the passage still read. Removed the mechanism details that carry no consequence for a reader, and dropped the field list that duplicated the Step 7 body now that it is correct. Co-Authored-By: Claude Opus 5 (1M context) --- crates/support/README.md | 15 ++++++--------- docs/pages/building-with-interfold.mdx | 3 +-- templates/default/interfold.config.yaml | 4 ++-- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/crates/support/README.md b/crates/support/README.md index 8419f8bd2..a54f413f5 100644 --- a/crates/support/README.md +++ b/crates/support/README.md @@ -99,8 +99,7 @@ program: program_url: 'https://gateway.pinata.cloud/ipfs/Qm...' # after upload (Step 3) onchain: true # Built-in auction defaults, shown for reference. Leave these fields unset. - # `interfold program start` forwards these fields as flags, but its launcher - # rejects the flags. If you set a field, the container does not start. + # If you set one, `interfold program start` fails. See #1812. # min_price_eth: 0.00005 # max_price_eth: 0.002 # timeout_secs: 600 @@ -148,8 +147,8 @@ interfold program start 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 configuration default is `1`. The provider reads the mode once per -request. A Boundless request with missing credentials fails instead of using dev mode. +`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 @@ -285,11 +284,9 @@ cargo run --bin e3-support-app ./curl_test.sh ``` -`fixtures/payload.json` predates the current `ComputeRequest` type, so the request fails to -deserialize. It sends a numeric `e3_id`, and it omits four required fields. A working body carries -`e3_id` as a string plus `chain_id`, `interfold_address`, `encryption_scheme_id`, -`committee_public_key_hash`, `params`, and `ciphertext_inputs`. Build one from `ComputeRequest` in -`crates/support/types/src/lib.rs` until the fixture is refreshed. +`fixtures/payload.json` predates the current `ComputeRequest` type. It sends a numeric `e3_id` and +omits four required fields, so 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. diff --git a/docs/pages/building-with-interfold.mdx b/docs/pages/building-with-interfold.mdx index c8b3638d7..f13f84993 100644 --- a/docs/pages/building-with-interfold.mdx +++ b/docs/pages/building-with-interfold.mdx @@ -201,8 +201,7 @@ program implementation). This allows you to: The input tree belongs to the E3 program, not to Interfold. The default template uses `InternalLazyIMT` from `@zk-kit/lazy-imt.sol`. It inserts each SAFE ciphertext commitment directly -as a leaf. PoseidonT3 combines node pairs as the tree grows, and `_root()` computes the root on -read. +as a leaf, and PoseidonT3 combines node pairs as the tree grows. Your E3 program should: diff --git a/templates/default/interfold.config.yaml b/templates/default/interfold.config.yaml index 36dde74c0..9e364a441 100644 --- a/templates/default/interfold.config.yaml +++ b/templates/default/interfold.config.yaml @@ -30,8 +30,8 @@ program: # pinata_jwt: "PINATA_JWT" # For uploading programs # program_url: "https://gateway.pinata.cloud/ipfs/QmNMRAB7DW43JSmENfzGmD96G6sqaeBBNfTVrrq5WQae3D" # Pre-uploaded program # onchain: true # true = onchain requests, false = offchain - # Built-in auction defaults, shown for reference. Leave these unset, because - # `interfold program start` forwards them as flags that its launcher rejects. + # Built-in auction defaults, shown for reference. Leave these unset. + # If you set one, `interfold program start` fails. See #1812. # min_price_eth: 0.00005 # max_price_eth: 0.002 # timeout_secs: 600 From 28c8b421c892328ddcf8b6d66e13009387965457 Mon Sep 17 00:00:00 2001 From: Toby1009 <69885352+Toby1009@users.noreply.github.com> Date: Sun, 16 Aug 2026 16:21:34 +0800 Subject: [PATCH 06/10] fix(docs): point the batch_size note at its tracking issue The paragraph described an unvalidated input that panics, with nothing tracking it. Filed as #1815 and trimmed the note to match. Co-Authored-By: Claude Opus 5 (1M context) --- crates/compute-provider/Readme.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/compute-provider/Readme.md b/crates/compute-provider/Readme.md index e719edf6e..3a7e92dfa 100644 --- a/crates/compute-provider/Readme.md +++ b/crates/compute-provider/Readme.md @@ -85,6 +85,5 @@ The `ComputeManager::new()` function takes several parameters: - `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, read only when `use_parallel` is true. It defaults to 2 when - not supplied. The constructor does not validate it, so `use_parallel: true` together with - `Some(0)` reaches `slice::chunks(0)` and panics. With `use_parallel: false` the value is never - read. Every current caller passes `use_parallel: false` and `batch_size: None`. + not supplied. The constructor does not validate it, and `Some(0)` panics on the first parallel + run. See #1815. From 315092e18177015f40b3b9fd72039541f512b4a7 Mon Sep 17 00:00:00 2001 From: Toby1009 <69885352+Toby1009@users.noreply.github.com> Date: Sun, 16 Aug 2026 16:24:14 +0800 Subject: [PATCH 07/10] fix(docs): keep defect notes to what a reader can act on The batch_size note described a constructor that does not validate its input, which no reader needs and #1815 tracks. The payload.json note listed the missing fields the reader does not have to know. Both are now plain descriptions. Co-Authored-By: Claude Opus 5 (1M context) --- crates/compute-provider/Readme.md | 5 ++--- crates/support/README.md | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/compute-provider/Readme.md b/crates/compute-provider/Readme.md index 3a7e92dfa..d33ac707a 100644 --- a/crates/compute-provider/Readme.md +++ b/crates/compute-provider/Readme.md @@ -84,6 +84,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, read only when `use_parallel` is true. It defaults to 2 when - not supplied. The constructor does not validate it, and `Some(0)` panics on the first parallel - run. See #1815. +- `batch_size`: The number of ciphertexts per parallel chunk, read only when `use_parallel` is true. + It defaults to 2 when not supplied. diff --git a/crates/support/README.md b/crates/support/README.md index a54f413f5..938ab4822 100644 --- a/crates/support/README.md +++ b/crates/support/README.md @@ -284,9 +284,8 @@ cargo run --bin e3-support-app ./curl_test.sh ``` -`fixtures/payload.json` predates the current `ComputeRequest` type. It sends a numeric `e3_id` and -omits four required fields, so the request fails to deserialize. Use the Step 7 body until the -fixture is refreshed. +`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. From 9ca556f932f8c76b167741cc3dcaf28d5a4e6a46 Mon Sep 17 00:00:00 2001 From: Toby1009 <69885352+Toby1009@users.noreply.github.com> Date: Sun, 16 Aug 2026 16:32:45 +0800 Subject: [PATCH 08/10] fix(docs): drop a forward reference the next heading already makes Co-Authored-By: Claude Opus 5 (1M context) --- crates/compute-provider/Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/compute-provider/Readme.md b/crates/compute-provider/Readme.md index d33ac707a..d71be6d49 100644 --- a/crates/compute-provider/Readme.md +++ b/crates/compute-provider/Readme.md @@ -42,7 +42,7 @@ where ``` `fhe_processor` is your own function. It must match the exported `FHEProcessor` alias, -`fn(&FHEInputs) -> Vec`. The next section shows a concrete `ComputeProvider`. +`fn(&FHEInputs) -> Vec`. ## Risc0 Example From 3a30af8003823fcddfe9f4742264f4a46d02dba4 Mon Sep 17 00:00:00 2001 From: Toby1009 <69885352+Toby1009@users.noreply.github.com> Date: Mon, 17 Aug 2026 14:58:59 +0800 Subject: [PATCH 09/10] fix(docs): scope the Boundless steps in the processing flow The numbered flow described Boundless submission unconditionally, which stopped being accurate once the dev-mode default was documented. Steps 1, 2, and 5 run in both modes, so the condition is stated once after the list rather than repeated on each item. Co-Authored-By: Claude Opus 5 (1M context) --- crates/support/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/support/README.md b/crates/support/README.md index 938ab4822..6a93f5078 100644 --- a/crates/support/README.md +++ b/crates/support/README.md @@ -205,6 +205,9 @@ The program server: 5. Sends webhook callback with `{"status":"completed","e3_id":"1","ciphertext":"0x...","ciphertext_commitment":"0x...","proof":"0x..."}` +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 The callback server (e.g., CRISP) receives the webhook and calls: From f4b05bb3a6256ad19d817513a7e14f4d6666afb8 Mon Sep 17 00:00:00 2001 From: Toby1009 <69885352+Toby1009@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:12:48 +0800 Subject: [PATCH 10/10] fix(docs): correct the result-publication actor and make the provider example usable The publication flow named the Compute Provider as the submitter. The reference provider sends a webhook and the callback server calls publishCiphertextOutput, which the support README already described. The Risc0 Example imported a crate the installation section does not add, and its prove body was todo!(). Replaced with a self-contained impl and a sentence about where the RISC Zero and Boundless providers actually live. Also switched the template config secrets to ${VAR} form, since the loader expands only $VAR and ${VAR} and the comment said to use environment variables. Co-Authored-By: Claude Opus 5 (1M context) --- crates/compute-provider/Readme.md | 33 +++++++++++-------------- docs/pages/building-with-interfold.mdx | 3 ++- templates/default/interfold.config.yaml | 4 +-- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/crates/compute-provider/Readme.md b/crates/compute-provider/Readme.md index d71be6d49..34d51bad8 100644 --- a/crates/compute-provider/Readme.md +++ b/crates/compute-provider/Readme.md @@ -44,37 +44,34 @@ where `fhe_processor` is your own function. It must match the exported `FHEProcessor` alias, `fn(&FHEInputs) -> Vec`. -## Risc0 Example +## Implementing a provider -`crates/support` contains a working RISC Zero provider. The provider has the following structure: +`ComputeProvider` has one method and one associated type. Everything else is yours to choose: ```rust use e3_compute_provider::{ComputeInput, ComputeProvider}; -use e3_support_types::{ComputeDomain, ComputeJournal}; -pub struct Risc0Provider { - domain: ComputeDomain, -} +pub struct MyProvider; -#[derive(Debug, Clone)] -pub struct Risc0Output { - pub result: ComputeJournal, - pub bytes: Vec, - pub seal: Vec, +pub struct MyOutput { + pub proof: Vec, } -impl ComputeProvider for Risc0Provider { - type Output = Risc0Output; +impl ComputeProvider for MyProvider { + type Output = MyOutput; + fn prove(&self, input: &ComputeInput) -> Self::Output { - // Run the guest and wrap the receipt. See the full implementation. - todo!() + // Prove that `input` produced its committed result, however your + // backend does that, and return whatever the caller needs. + MyOutput { proof: Vec::new() } } } ``` -To use that provider directly rather than write your own, call -`e3_support_host::run_risc0_compute(params, domain)`. The complete implementation, including the -Boundless variant, is in `crates/support/host/src/lib.rs`. +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 diff --git a/docs/pages/building-with-interfold.mdx b/docs/pages/building-with-interfold.mdx index f13f84993..fd26c8b0e 100644 --- a/docs/pages/building-with-interfold.mdx +++ b/docs/pages/building-with-interfold.mdx @@ -257,7 +257,8 @@ event InputPublished(uint256 indexed e3Id, bytes data, uint256 index); ### Result Publication Flow -1. The Compute Provider submits a proof and ciphertext output. +1. The Compute Provider returns the proof and ciphertext output to a callback server, which submits + them to Interfold. 2. Interfold verifies the proof twice. The request-time protocol verifier runs first, then the E3 program verifier. Both checks must pass before Interfold emits `CiphertextOutputPublished`. 3. Ciphernodes decrypt the ciphertext output. diff --git a/templates/default/interfold.config.yaml b/templates/default/interfold.config.yaml index 9e364a441..ab2320def 100644 --- a/templates/default/interfold.config.yaml +++ b/templates/default/interfold.config.yaml @@ -26,8 +26,8 @@ program: # risc0_dev_mode: 0 # 0 = production (Boundless), 1 = dev mode (fake proofs) # boundless: # rpc_url: "https://sepolia.infura.io/v3/YOUR_KEY" - # private_key: "PRIVATE_KEY" # Use env vars for secrets - # pinata_jwt: "PINATA_JWT" # For uploading programs + # private_key: "${PRIVATE_KEY}" # Read from the environment + # pinata_jwt: "${PINATA_JWT}" # Read from the environment, for uploads # program_url: "https://gateway.pinata.cloud/ipfs/QmNMRAB7DW43JSmENfzGmD96G6sqaeBBNfTVrrq5WQae3D" # Pre-uploaded program # onchain: true # true = onchain requests, false = offchain # Built-in auction defaults, shown for reference. Leave these unset.