Task-completion circuit
Circom 2.0 circuit that proves a task’s acceptance predicate held against committed inputs.
Owner: zk-circuit-engineer Depends on: 01 Blocks: 06, 09 References: backend PDF §2.4 (Proof of Completion — Groth16 Integration), §5.1 (Proof-system-adjacent audit lines), §5.2 (Trusted-setup MPC with ≥20 participants), §6.1 (batch verification roadmap — out of M1 scope beyond the hook)
Goal
A Circom 2.0 circuit + trusted-setup plan that lets an agent prove, in zero-knowledge, that: (1) it possesses a result matching the task's committed task_hash, (2) the result satisfies the declared success criteria, (3) the result was produced within the task deadline. On-chain verification lives in spec 06 via Light Protocol bn254 pairings.
M1 ships: one circuit file, its compiled wasm + r1cs, snarkjs test proofs, documented constraint count, and a written trusted-setup ceremony plan (execution is a separate calendar event before any real value is escrowed).
Public inputs (verified on-chain)
Ordering is load-bearing — spec 06 hard-codes the same order.
task_hash— Poseidon2(salt, task_description) — 1 field elementresult_hash— Poseidon2(result_preimage) — 1 field elementdeadline— unix seconds, packed as field element — 1 field elementsubmitted_at— unix seconds — 1 field elementcriteria_root— Merkle root of declared success-criteria predicates — 1 field element
Total public inputs: 5 field elements (bn254 scalar field).
Private inputs (witness)
result_preimage: Array<field, N_RESULT>— chunked result content, N_RESULT = 32 field elements (M1 default; covers up to ~1 KB results; reviewer may tighten)task_preimage: Array<field, N_TASK>— N_TASK = 16 field elementssalt: fieldcriteria_satisfied: Array<bit, K>— K = 8 criteria bits (M1 default)criteria_path: Array<field, log2(K)>— Merkle path forcriteria_rootcriteria_index: Array<bit, log2(K)>
Constraints (what the circuit enforces)
Poseidon2(salt, task_preimage) == task_hashPoseidon2(result_preimage) == result_hashMerkleVerify(criteria_satisfied, criteria_path, criteria_index) == criteria_rootAND(criteria_satisfied[0..K]) == 1— every declared criterion is truesubmitted_at <= deadline— range check via bit decomposition (32-bit bound on unix seconds fits comfortably; enforcedeadline - submitted_at >= 0via a non-negative range proof on a 64-bit field)
Hash choice: Poseidon2 over bn254. Rationale: bn254-native, ~200 constraints per hash vs ~24k for keccak. Note: on-chain task_hash computed by TaskMarket is also Poseidon2 — documented in spec 07. If the reviewer prefers keccak for EVM portability, circuit constraint budget must expand (see below).
Constraint budget (M1 default, reviewer may tighten)
| Component | Constraints |
|---|---|
| Poseidon2(task_preimage) | ~3_200 |
| Poseidon2(result_preimage) | ~6_400 |
| Merkle verify depth-3 over K=8 | ~600 |
| AND of 8 bits + range checks | ~100 |
submitted_at <= deadline (64-bit range) | ~130 |
| Plumbing | ~500 |
| Target total | ~11_000 constraints |
Hard ceiling for M1: 20_000 constraints. snarkjs proving time target: < 2s on a 16-core x86 worker; GPU target < 500ms. Verification cost on-chain (spec 06): ~400k CUs single proof, per §2.4.
Deliverables
circuits/
├── task_completion/
│ ├── task_completion.circom # top-level
│ ├── components/
│ │ ├── poseidon2.circom # vendored or via circomlib
│ │ └── merkle_verifier.circom
│ ├── inputs/
│ │ └── sample_input.json
│ ├── scripts/
│ │ ├── compile.sh # circom --r1cs --wasm --sym
│ │ ├── setup.sh # ptau fetch + groth16 setup (test srs)
│ │ ├── prove.sh
│ │ └── verify.sh
│ ├── build/ # gitignored
│ └── README.md
The test trusted setup uses powersOfTau28_hez_final_15.ptau (Hermez-contributed, sized for ~32k constraints). The test SRS is for local/dev only — clearly labelled in README; mainnet proofs must use the MPC ceremony output below.
Trusted-setup plan (MPC ceremony)
Per §5.2: ≥ 20 participants, including independent cryptographers. Ceremony artifacts published publicly.
- Phase 1 — use existing publicly-audited Powers of Tau (Hermez
powersOfTau28_hez_final). No custom Phase 1; piggybacks on Semaphore/Tornado lineage. - Phase 2 — circuit-specific, run by SAEP. Plan:
- Tool:
snarkjs groth16 contributein sequence; each contributor runs a clean room (air-gapped VM, webcam-recorded random beacon source). - Participant target: 25 (buffer over 20) — mix of SAEP team (≤ 5), invited cryptographers (≥ 10), independent community (≥ 10). Names and affiliations published before ceremony.
- Randomness beacon: published block hash from a bitcoin block selected post-last-contribution (prevents any contributor from knowing the beacon in advance).
- Transcript: each contribution's hash chained, all
*.zkeyartifacts + attestation signatures archived on Arweave (permanent). - Verification:
snarkjs zkey verifyrun independently by ≥ 3 parties on the final zkey. - Kill-switch: if any participant leaks randomness, ceremony is re-run from scratch. There is no partial-trust fallback.
- Tool:
- Gate: no mainnet escrow references a proof until the MPC ceremony zkey is published and the verifying key is loaded into the on-chain
VerifierKeyPDA via governance.
M1 devnet can proceed against the test SRS — explicitly labelled "devnet only" in reports/05-circuit.md and in the portal UI.
Circuit file outline (no implementation, just shape)
pragma circom 2.1.5;
include "components/poseidon2.circom";
include "components/merkle_verifier.circom";
template TaskCompletion(N_TASK, N_RESULT, K) {
signal input task_hash;
signal input result_hash;
signal input deadline;
signal input submitted_at;
signal input criteria_root;
signal private input task_preimage[N_TASK];
signal private input result_preimage[N_RESULT];
signal private input salt;
signal private input criteria_satisfied[K];
signal private input criteria_path[log2(K)];
signal private input criteria_index[log2(K)];
// 1. task_hash binding
// 2. result_hash binding
// 3. merkle verify
// 4. AND of criteria
// 5. submitted_at <= deadline (64-bit range)
}
component main { public [task_hash, result_hash, deadline, submitted_at, criteria_root] } =
TaskCompletion(16, 32, 8);
Test plan
- snarkjs end-to-end: generate witness from
sample_input.json, prove, verify off-chain. CI step under.github/workflows/ci.yml. - Negative tests:
- Tampered
result_preimage→result_hashmismatch → proving fails submitted_at > deadline→ range check fails- Missing criterion bit → AND check fails
- Wrong Merkle path → verify fails
- Tampered
- Constraint count printed to stdout during
compile.sh; committed as a snapshot inreports/05-circuit.md. CI asserts count < 20_000. - Proving time measured on CI runner and on a reference GPU worker; baseline written to report.
- Determinism: same input → same proof bytes (given same srs + same randomness). Non-determinism flagged.
Events (off-chain, proof-gen service contract)
The circuit itself emits nothing. Proof-gen (spec 09) publishes ProofGenerated { task_id, proof_bytes, public_inputs } into IACP. ProofVerifier (spec 06) consumes the proof + public inputs on-chain.
Errors (circuit compile/prove layer)
InvalidWitness— witness generation fails (preimage mismatch)ConstraintViolation— any of the 5 enforced constraints falseRangeCheckFailed—submitted_at > deadlineor values exceed 64-bit fieldMerkleProofInvalid
Invariants
- Any valid proof implies
Poseidon2(result_preimage) == result_hash; on-chainresult_hashcan be trusted as binding to real content. - Valid proof ⇒
submitted_at <= deadline; no post-deadline submission can be verified. - Changing the circuit requires a new verifying key + governance rotation (spec 06). Old proofs do not verify against a new VK.
- Public-input ordering in the circuit matches spec 06 exactly.
Security checks (backend §5.1 / §5.2 adjacent)
- Proof system trusted setup: ceremony plan above meets §5.2 (≥ 20 participants, transcript published, independent cryptographers).
- No partial trust: test SRS never used on mainnet. Clearly gated via program state (
VerifierKey.is_productionflag, see spec 06). - Input encoding: unix timestamps bounded to 64 bits pre-range-check to prevent field wraparound.
- Hash domain separation: Poseidon2 invocations use distinct parameter sets / salts for
task_hashvsresult_hash(reviewer confirms via circomlib version pin). - Side channels: proof-gen service (spec 09) runs on dedicated workers; no shared-memory with untrusted workloads.
Open questions for reviewer / cryptographer review
- Poseidon2 vs Poseidon vs keccak: current pick Poseidon2 for constraint efficiency; confirm circomlib binding version and audit status.
N_RESULT = 32,K = 8— upper bounds guessed; real task results may exceed 1 KB. If so, either chunk and batch-prove (M2), or raiseN_RESULTwith linear constraint growth.- Whether
criteria_rootshould itself be bound on-chain (spec 07 open question).
Done-checklist
-
task_completion.circomcompiles with circom 2.1+ -
compile.shemits r1cs, wasm, sym; constraint count logged and < 20_000 - Test SRS generated and
snarkjs groth16 fullprovesucceeds onsample_input.json - Verifying key exported to
build/verification_key.json(consumed by spec 06 fixtures) - All negative tests pass (witness rejection expected)
- Proving time benchmark on CI and GPU reference recorded
-
reports/05-circuit-zk.mdcontains: constraint breakdown, proving-time table, ceremony participant target list (placeholders), test-SRS warning - MPC ceremony plan reviewed by ≥ 1 external cryptographer (sign-off captured)
- No mainnet artifact produced from test SRS (CI guard)