Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
086e5a1
chore(protocol): record vrf upgrade artifacts
hmzakhalid Aug 28, 2026
3984c59
fix(protocol): compile secure bfv param selection [skip-doc-sync]
hmzakhalid Aug 28, 2026
e92aefd
feat(circuits): add secure small bfv verifiers
hmzakhalid Aug 28, 2026
a44da6a
feat: support multi-committee circuit artifacts
hmzakhalid Aug 28, 2026
db6f1a9
fix(crisp): align ballot preset with requests
hmzakhalid Aug 28, 2026
821cd99
chore: add generated BFV verifier variants
hmzakhalid Aug 28, 2026
9a5fa9a
chore(crisp): publish version 0.19.0
hmzakhalid Aug 28, 2026
7c3b251
chore: retrigger ci [skip-doc-sync]
hmzakhalid Aug 28, 2026
8ce3557
chore: sync CRISP client SDK lockfile
hmzakhalid Aug 28, 2026
a849f66
chore: simplify release circuit validation
hmzakhalid Aug 28, 2026
e842106
fix(crisp): harden secure parameter activation
hmzakhalid Aug 28, 2026
5badab9
fix(ci): keep committee gate dependency-free
hmzakhalid Aug 28, 2026
fc6d9d9
fix(ci): sync CRISP client workspace lock
hmzakhalid Aug 28, 2026
10143ff
fix(ci): defer circuit archive pin to release
hmzakhalid Aug 28, 2026
b3cde85
test(contracts): make request deadlines deterministic
hmzakhalid Aug 28, 2026
4f8e1a2
fix(crisp): preserve SDK worker path in Vite
hmzakhalid Aug 28, 2026
23391b9
test(contracts): anchor request timestamps
hmzakhalid Aug 28, 2026
f1f73b3
fix(circuits): validate complete release artifacts
hmzakhalid Aug 28, 2026
6c84189
test(crisp): isolate the application page
hmzakhalid Aug 28, 2026
5c92a08
fix(crisp): prebundle SDK poseidon dependency
hmzakhalid Aug 28, 2026
4914e48
fix(crypto): align secure BFV configuration
hmzakhalid Aug 29, 2026
50dd261
fix(release): gate publication on validated commits
hmzakhalid Aug 29, 2026
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
49 changes: 36 additions & 13 deletions .github/workflows/releases.yml
Original file line number Diff line number Diff line change
Expand Up @@ -447,12 +447,8 @@ jobs:
fi

# Guard against shipping stale or wrong-layout artifacts. The `circuit-artifacts`
# branch is populated out-of-band (`pnpm build:circuits --committee all --preset
# insecure-512` followed by `scripts/circuit-artifacts.ts push`). If it lags the
# circuit source, its SOURCE_HASH won't match the source we're releasing and its
# on-disk layout may predate the `{preset}/{committee}/{variant}` scheme that
# consumers (e3-zk-prover) resolve against. Fail loudly here rather than publishing
# a tarball that downstreams can't use — regenerate and re-push the branch.
# branch is populated out-of-band and may contain multiple preset/committee pairs.
# Fail loudly rather than publishing a tarball downstream nodes cannot use.
- name: Verify circuit artifacts match source
if: steps.pull.outputs.found == 'true'
run: |
Expand All @@ -464,24 +460,51 @@ jobs:
fi
if [[ "$PULLED_HASH" != "$EXPECTED_HASH" ]]; then
echo "::error::circuit-artifacts is stale (SOURCE_HASH=$PULLED_HASH, expected $EXPECTED_HASH)."
echo "::error::Rebuild and re-push: pnpm build:circuits --committee all --preset insecure-512 && pnpm tsx scripts/circuit-artifacts.ts push"
echo "::error::Rebuild and re-push the required preset/committee pairs, then run: pnpm store:circuits push"
exit 1
fi
# Assert the per-committee layout that `--committee all` produces is present, so an
# old flat (committee-less) build can never slip through even if the hash lined up.

while IFS= read -r stamp; do
preset=$(STAMP="$stamp" node -e 'const fs = require("fs"); const s = JSON.parse(fs.readFileSync(process.env.STAMP, "utf8")); console.log(s.preset || "")')
committee=$(STAMP="$stamp" node -e 'const fs = require("fs"); const s = JSON.parse(fs.readFileSync(process.env.STAMP, "utf8")); console.log(s.committee || "")')
stamp_hash=$(STAMP="$stamp" node -e 'const fs = require("fs"); const s = JSON.parse(fs.readFileSync(process.env.STAMP, "utf8")); console.log(s.sourceHash || "")')
if [[ -z "$preset" || -z "$committee" || -z "$stamp_hash" ]]; then
echo "::error::Invalid circuit build stamp: ${stamp#dist/circuits/}"
exit 1
fi
expected_stamp_hash=$(pnpm tsx scripts/build-circuits.ts hash --preset "$preset" --committee "$committee")
if [[ "$stamp_hash" != "$expected_stamp_hash" ]]; then
echo "::error::Stale circuit artifacts at ${preset}/${committee}: stamp=${stamp_hash}, expected=${expected_stamp_hash}."
exit 1
fi
done < <(find dist/circuits -name .build-stamp.json -type f | sort)

# Releases carry every supported preset/committee pair. Mainnet gates requests to secure
# BFV, while Sepolia/local deployments can test insecure or secure BFV at any committee
# size without rebuilding the release asset.
missing=0
for committee in minimum micro small; do
marker="dist/circuits/insecure-512/${committee}/default/dkg/pk/pk.json"
required_markers=()
for preset in insecure-512 secure-8192; do
for committee in minimum micro small; do
required_markers+=(
"dist/circuits/${preset}/${committee}/default/dkg/pk/pk.json"
"dist/circuits/${preset}/${committee}/default/threshold/pk_aggregation/pk_aggregation.json"
"dist/circuits/${preset}/${committee}/default/recursive_aggregation/dkg_aggregator/dkg_aggregator.json"
"dist/circuits/${preset}/${committee}/default/recursive_aggregation/decryption_aggregator/decryption_aggregator.json"
)
done
done
for marker in "${required_markers[@]}"; do
if [[ ! -f "$marker" ]]; then
echo "::error::Expected circuit artifact missing: ${marker#dist/circuits/}"
missing=1
fi
done
if [[ "$missing" != "0" ]]; then
echo "::error::circuit-artifacts was not built with --committee all; regenerate as above."
echo "::error::circuit-artifacts is missing required Sepolia or mainnet artifacts."
exit 1
fi
echo "✅ circuit-artifacts verified (SOURCE_HASH=$PULLED_HASH, per-committee layout present)"
echo "✅ circuit-artifacts verified (SOURCE_HASH=$PULLED_HASH, required chain artifacts present)"
Comment thread
hmzakhalid marked this conversation as resolved.
Outdated

- name: Create release archive
if: steps.pull.outputs.found == 'true'
Expand Down
24 changes: 21 additions & 3 deletions agent/CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jointly generate a threshold BFV key (DKG), compute over encrypted inputs, and t
output — every step backed by ZK proofs verified on-chain.

- Docs: https://docs.theinterfold.com · License: LGPL-3.0-only
- Unified version across all crates and npm packages (currently 0.4.0)
- Unified version across all crates and npm packages (currently 0.13.0)
- Reference app: **CRISP** (`examples/CRISP`, excluded from the workspace)

## Terminology
Expand Down Expand Up @@ -68,6 +68,24 @@ Run from repo root via pnpm scripts — not raw cargo/nargo/hardhat.
| Consistency checks | `pnpm check:committee` · `check:docs` · `check:addresses` · `check:invariants` · `check:license` · `check:verifiers` · `check:pnpm` · `check:size` |
| Release bump | `pnpm bump:versions X.Y.Z` |

## Chain-Specific BFV Config

The protocol release can carry more than one circuit artifact set. Current deployments use this
matrix:

- Ethereum mainnet supports `secure-8192/minimum`, `secure-8192/micro`, and `secure-8192/small`.
- Sepolia and local chains support `insecure-512` and `secure-8192` with `minimum`, `micro`, and
`small` committees.

`ActiveCryptoConfig.sol` selects the parameter sets and committee shapes supported by
`block.chainid`. Deployment tooling mirrors that matrix with `bfvConfigsForChain(chainId)` and reads
VK hashes from `dist/circuits/<preset>/<committee>/...`. A verifier router can sit behind the BFV
scheme mapping and dispatch proofs to the concrete verifier for each generated pair.

`circuits/bin/.active-preset.json` only records the local hydrated circuit cache. It can point at a
different pair than the target chain uses, provided `dist/circuits/` contains every required pair
for that chain.

## Conventions

- **Commits:** Conventional Commits, types `feat` / `fix` / `chore` only, optional scope, `!` for
Expand Down Expand Up @@ -105,7 +123,7 @@ opentelemetry/tracing.
`decrypted_shares_aggregation`
- **Recursive aggregation** (`circuits/bin/recursive_aggregation/`): fold kernels (`c2ab_fold`,
`c3_fold`, `c6_fold`, `node_fold`, `nodes_fold`, …) and the top-level `dkg_aggregator` /
`decryption_aggregator`, which produce the on-chain Honk verifiers (committed only for
`(insecure-512, minimum)`).
`decryption_aggregator`, which produce the on-chain Honk verifiers. The canonical committed root
matches `(insecure-512, minimum)`; other generated pairs live under `honk/<preset>/<committee>/`.
- `config` circuit validates preset constants (CRT moduli, bounds, parity matrices). Parity matrices
are generated by the Rust `generate_parity_matrices` binary — never hand-edit.
34 changes: 21 additions & 13 deletions agent/INVARIANTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,16 @@ design citation alone does not establish current runtime behavior.
controller-local sequence. On-chain snapshots, signed payloads, Rust persistence, and indexer keys
must preserve the complete `uint256`. — `Interfold.initialize`; `flow-trace/03`
- A request can select only the parameter set and committee shape in `ActiveCryptoConfig.sol`.
`pnpm build:circuits` generates that binding from the active preset. Governance cannot enable a
different parameter hash, `[H, N]`, or verifier threshold without rebuilding the circuits and
contracts. The request supplies the expected configuration ID, which binds the scheme, parameter
hash, and circuit version. Solidity snapshots it, and Rust rejects an event or stored E3 whose ID
differs from its local build. Pricing uses circuit threshold `T`, not on-chain viability value
`H`. `N <= numActiveOperators` at `requestCommittee`. — `flow-trace/03`
Mainnet supports `secure-8192` with `minimum`, `micro`, and `small` committees. Sepolia and local
chains support `insecure-512` and `secure-8192` with `minimum`, `micro`, and `small` committees.
Governance cannot enable a different parameter hash, `[H, N]`, or verifier threshold without
rebuilding the circuits and contracts for that pair. The request supplies the expected
configuration ID, which binds the scheme, parameter hash, and circuit version; committee size is
snapshotted separately. Solidity snapshots the ID, and Rust rejects an event or stored E3 whose ID
differs from its requested parameter set. BFV verifier mappings may point at routers, which
dispatch by public-input length and VK hash anchors to the concrete verifier for the generated
pair. Pricing uses circuit threshold `T`, not on-chain viability value `H`.
`N <= numActiveOperators` at `requestCommittee`. — `flow-trace/03`
- Sortition score is deterministic and identical on- and off-chain:
`score = keccak256(address ‖ ticket ‖ e3Id ‖ seed)`, where
`seed = keccak256(randomWord ‖ chainId ‖ registry ‖ e3Id ‖ requestId)`; top-N lowest win. Each E3
Expand Down Expand Up @@ -325,14 +329,14 @@ design citation alone does not establish current runtime behavior.

### Committee config sync (the `check:committee` gate)

- Committee `(N, T, H)` must be identical across **five** files:
`circuits/lib/src/configs/committee/active.nr`, `circuits/bin/.active-preset.json`,
`packages/interfold-contracts/scripts/utils.ts` (`BFV_DKG_H`/`BFV_THRESHOLD_T`), and
`crates/zk-helpers/src/ciphernodes_committee.rs`, plus
- Committee `(N, T, H)` must stay synchronized across:
`circuits/lib/src/configs/committee/active.nr`, `packages/interfold-contracts/scripts/utils.ts`
(`BFV_DKG_H`/`BFV_THRESHOLD_T`), `crates/zk-helpers/src/ciphernodes_committee.rs`, and
`packages/interfold-contracts/contracts/lib/ActiveCryptoConfig.sol`. The Solidity file also binds
the active BFV parameter-set hash. Drift means the next build silently produces verifiers or
proofs for the wrong configuration. Switch only with `pnpm build:circuits --committee <name>`;
enforced by `scripts/check-committee.sh`.
the BFV parameter-set hashes. `circuits/bin/.active-preset.json` records only the local hydrated
cache and may differ from the production chain pair. Drift means the next build silently produces
verifiers or proofs for the wrong configuration. Switch only with
`pnpm build:circuits --committee <name>`; enforced by `scripts/check-committee.sh`.
Comment thread
hmzakhalid marked this conversation as resolved.
Outdated
- Canonical sizes: `minimum` (3,1,2) · `micro` (9,4,5) · `small` (19,9,10) — must mirror `mod.nr`
and `CiphernodesCommitteeSize::values()`. — `scripts/circuit-constants.ts`
- Wrapper Solidity verifiers (`BfvPkVerifier`, `BfvDecryptionVerifier`) have an `(H, T)`-specific
Expand All @@ -353,6 +357,10 @@ design citation alone does not establish current runtime behavior.
`required_circuits_version`. Regenerate all dependent verification keys and Solidity verifiers
with the pinned Barretenberg version. A release archive from an older serialization format can
pass checksum verification but fail during ACIR decoding or proof generation.
- A circuit release archive that supports current deployments must include every
`insecure-512/{minimum,micro,small}` and `secure-8192/{minimum,micro,small}` pair.
`checksums.json` and `SHA256SUMS` must cover the archive contents. Nodes select the artifact
directory from the E3's on-chain parameter set and committee size.

### DKG / threshold structure

Expand Down
9 changes: 5 additions & 4 deletions agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ Requester calls: Interfold.request({
│ ├─ requestsPaused == false
│ ├─ Registry, bonding, slashing, refund, and ticket-token pointers form one
│ │ reciprocal dependency graph with matching operator membership
│ ├─ Resolve the build-generated active crypto configuration.
│ │ The current build is insecure-512 / minimum [H=2, N=3, T=1].
│ ├─ Validate the requested crypto configuration against the chain matrix.
│ │ Mainnet supports secure-8192 with minimum, micro, and small committees.
│ │ Sepolia and local chains support insecure-512 and secure-8192 with all committee sizes.
Comment thread
hmzakhalid marked this conversation as resolved.
│ │ A different parameter hash, committee shape, or verifier H/T is rejected.
│ ├─ inputWindow[0] >= block.timestamp (start in future)
│ ├─ inputWindow[1] >= inputWindow[0] (end after start)
Expand All @@ -87,7 +88,7 @@ Requester calls: Interfold.request({
├─ FEE CALCULATION:
│ ├─ totalFee = getE3Quote()
│ │ → InterfoldPricing validates the active circuit [T, H, N].
│ │ → InterfoldPricing validates the requested chain-supported circuit [T, H, N].
│ │ → The quote uses N for committee-wide work and H for required decryption shares.
│ │ → It also uses the time windows,
│ │ proof counts, availability, decryption/publication costs, and margin
Expand All @@ -97,7 +98,7 @@ Requester calls: Interfold.request({
│ │ → totalFee = serviceFee + randomnessFlatFee
│ │ → margin does not apply to randomnessFlatFee
│ ├─ Require the current fee token to equal expectedFeeToken
│ ├─ Require the active scheme, parameter hash, and circuit version to equal
│ ├─ Require the requested scheme, parameter hash, and circuit version to equal
│ │ expectedCryptoConfigId
Comment thread
hmzakhalid marked this conversation as resolved.
Outdated
│ ├─ Require totalFee <= maxFee
│ ├─ feeToken.transferFrom(requester, address(this), totalFee)
Expand Down
12 changes: 10 additions & 2 deletions crates/evm-helpers/src/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ use crate::events::E3Requested;

static NONCE_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));

fn crypto_config_id_for_param_set(param_set: u8) -> Result<B256> {
match param_set {
0 => Ok("0x04f3677e73b0f5066d6caf5cbd92e3fb2e38338edaf5cfc971ab28f7b684da78".parse()?),
1 => Ok("0x17654d80a8bd5631a6f52cc9f86ac091b352ac95943366a8a41e7336e9a920fc".parse()?),
_ => Err(eyre::eyre!("unsupported BFV parameter set: {}", param_set)),
}
}

/// Get the next pending nonce for a given address from the provider
async fn get_next_nonce<P>(provider: &P, address: Address) -> eyre::Result<u64>
where
Expand Down Expand Up @@ -140,7 +148,7 @@ sol! {
function getDeadlines(uint256 e3Id) external view returns (E3Deadlines memory deadlines);
function getTimeoutConfig() external view returns (E3TimeoutConfig memory config);
function feeToken() external view returns (address token);
function activeCryptoConfigId() external view returns (bytes32 configId);
function activeCryptoConfigId() external pure returns (bytes32 configId);
function e3CryptoConfigIds(uint256 e3Id) external view returns (bytes32 configId);
}
}
Expand Down Expand Up @@ -466,7 +474,7 @@ impl InterfoldWrite for InterfoldContract<ReadWrite> {

let contract = Interfold::new(self.contract_address, &self.provider);
let fee_token = contract.feeToken().call().await?;
let crypto_config_id = contract.activeCryptoConfigId().call().await?;
let crypto_config_id = crypto_config_id_for_param_set(param_set)?;

let quote_request = E3RequestParams {
committeeSize: committee_size,
Expand Down
11 changes: 5 additions & 6 deletions crates/fhe-params/src/presets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,11 @@ impl BfvPreset {
}
}

/// Default BFV preset used across the workspace.
/// Default BFV preset used for local development and tests.
///
/// This is the canonical preset for production (secure threshold 8192).
/// Use this constant when you need a single default rather than
/// hardcoding a specific preset. For the corresponding parameter set,
/// use [`default_param_set()`] or `BfvParamSet::from(DEFAULT_BFV_PRESET)`.
/// Production code that needs a chain-bound preset must select it explicitly from the active
/// protocol configuration. Use [`default_param_set()`] or `BfvParamSet::from(DEFAULT_BFV_PRESET)`
/// only when a fast local default is acceptable.
pub const DEFAULT_BFV_PRESET: BfvPreset = BfvPreset::InsecureThreshold512;

/// Returns the default BFV parameter set (same as `DEFAULT_BFV_PRESET` converted to [`BfvParamSet`]).
Expand Down Expand Up @@ -463,7 +462,7 @@ impl BfvPreset {
/// Returns the per-committee artifact directory: `"{preset}/{committee}"`.
///
/// Use this at runtime so each committee size resolves to its own compiled artifacts
/// (e.g. `"secure-8192/medium"`, `"insecure-512/micro"`).
/// (e.g. `"secure-8192/small"`, `"insecure-512/micro"`).
pub fn artifacts_dir_for_committee<C: AsRef<str>>(&self, committee: C) -> String {
format!("{}/{}", self.artifacts_dir(), committee.as_ref())
}
Expand Down
17 changes: 10 additions & 7 deletions examples/CRISP/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,23 @@ that gives it `secure-8192` verifiers to prove against.

## Procedure

Publish through `scripts/publish.ts`. It selects the preset from the release channel, builds the
packages, publishes in dependency order, and updates the standalone client lockfile after npm serves
the new SDK version.

Both channels build from the artifacts that `pnpm build:presets` archives under
`circuits/dist/<preset>/`, which git does not track. Run it whenever the circuits changed; the SDK
build refuses artifacts that are missing or older than the sources, comparing a content digest that
`stage-preset-artifacts.mjs` records at staging time.
`circuits/dist/<preset>/`, which git does not track. Run that command whenever the circuits change.
The SDK build refuses artifacts that are missing or older than the sources, using the content digest
that `stage-preset-artifacts.mjs` records at staging time.

```sh
pnpm -C examples/CRISP build:presets # slow: compiles both presets
cd examples/CRISP
pnpm -C examples/CRISP build:presets # slow: compiles both presets

# testing — insecure-512 under the `testing` tag, and moves the client
pnpm publish:packages --channel testing 0.19.0-insecure.0
pnpm -C examples/CRISP publish:packages --channel testing 0.19.0-insecure.0

# production — secure-8192 under the `latest` tag, and leaves the client alone
pnpm publish:packages --channel prod 0.19.0
pnpm -C examples/CRISP publish:packages --channel prod 0.19.0
```

Add `--dry-run` to print the exact steps for a channel without changing anything. The script bumps
Expand Down
Loading
Loading