# Verifying that the final trusted-setup keys come from the circuits in this repo

This directory contains scripts that verify the complete derivation chain of
the two Groth16 verification keys shipped in this repo — the same keys whose
values are hard-coded in the deployed `CommitmentVerifier.sol` and
`WithdrawalVerifier.sol`:

```
packages/circuits/circuits/{commitment,withdraw}.circom
    │  (1) circom 2.2.2 + circomkit 0.3.2 compile
    ▼
build/{commitment,withdraw}/{commitment,withdraw}.r1cs      (1537 / 36832 constraints)
    │  (2) Groth16 phase-2 MPC: 513 / 514 contributions (p0tion ceremony)
    │      on top of Perpetual Powers of Tau contribution #80,
    │      closed with a beacon contribution (Ethereum block 21989617)
    ▼
trusted-setup/final-keys/{commitment,withdraw}.zkey
    │  (3) snarkjs zkey export verificationkey
    ▼
trusted-setup/final-keys/{commitment,withdraw}.vkey         (the vk in the onchain verifiers)
```

(paths relative to `packages/circuits`)

## Checked-in artifacts under verification

All under `packages/circuits/trusted-setup/`:

| file | sha256 |
|---|---|
| `final-keys/commitment.zkey` | `494ae92d64098fda2a5649690ddc5821fcd7449ca5fe8ef99ee7447544d7e1f3` |
| `final-keys/commitment.vkey` | `7d48b4eb3dedc12fb774348287b587f0c18c3c7254cd60e9cf0f8b3636a570d8` |
| `final-keys/withdraw.zkey` | `2a893b42174c813566e5c40c715a8b90cd49fc4ecf384e3a6024158c3d6de677` |
| `final-keys/withdraw.vkey` | `666bd0983b20c1611543b04f7712e067fbe8cad69f07ada8a310837ff398d21e` |
| `last-keys/ragequit_00513.zkey` | `3296c04d2d82d60c6d05c5634cd31562cb03844f723adde44657dfe8d1db758c` |
| `last-keys/withdraw_00514.zkey` | `d64f9c7c52c2c2a97fb411ad99ce6ef1dd76437065313f13e1ffc3439c6b0cb3` |

plus two one-line text files documenting the closing beacon:
`beacon_hash` (`c87bd495d8ccfe9fb4158ee26d47e5ca2bcd4e853f80c9fdebf7e2921630e6e5`)
and `final_contribution_beacon` (`21989617`, the Ethereum block number whose
hash was used as the beacon).

## Trusted-setup ceremony facts

- The ceremony was run in March 2025 on [ceremony.pse.dev](https://ceremony.pse.dev)
  with PSE's [p0tion](https://github.com/privacy-scaling-explorations/p0tion)
  toolkit ([0xbow announcement](https://0xbow.io/blog/privacy-pools-trusted-setup-ceremony-join-us-in-strengthening-privacy)).
  In the ceremony the commitment circuit was named `ragequit`.
- Each final `.zkey` embeds the full MPC transcript (`snarkjs` section 10):
  **514 contributions for commitment, 515 for withdraw**. Contributors are
  recorded as `<github login>-<github user id>` (p0tion convention); the last
  record in each is a **beacon contribution** (type 1) whose hash equals the
  checked-in `beacon_hash` file, with `2^10` iterations.
- `last-keys/ragequit_00513.zkey` and `last-keys/withdraw_00514.zkey` are the
  ceremony states after the last regular contribution (p0tion naming:
  `<circuit>_<contribution index>.zkey`); the final keys extend them by the
  beacon contribution.
- **Phase 1**: p0tion pins the "prepared" Perpetual Powers of Tau
  contribution #80 files — see [`potFileDownloadMainUrl` in p0tion's constants.ts](https://github.com/privacy-scaling-explorations/p0tion/blob/main/packages/actions/src/helpers/constants.ts)
  and the file listing in the [perpetualpowersoftau README](https://github.com/privacy-scaling-explorations/perpetualpowersoftau).
  This is **not** the Hermez `powersOfTau28_hez_final_*` series (Hermez
  branched off PPoT at contribution #54), which is why verifying the final
  zkeys against the Hermez file fails with `INVALID: Invalid alpha1` —
  `vk_alpha_1`/`vk_beta_2` come verbatim from phase 1.
- Git history: the keys were added in `5d054f9`, replaced in `0862339`
  (`fix: apply beacon to last keys to generate final keys`) and reverted back
  in `e003976`; the current files are the `5d054f9` originals, which already
  contain the beacon and verify.

## Phase-1 powers-of-tau files

| kind | file | phase 1 of | sha256 |
|---|---|---|---|
| `final` | [`ppot_0080_16.ptau`](https://pse-trusted-setup-ppot.s3.eu-central-1.amazonaws.com/pot28_0080/ppot_0080_16.ptau) | the ceremony final zkeys | `ed3622a7c79b0b49aadd134ebbc5b77df8c8c59bccebdfd0d9bf2c1a51561cf9` |
| `build` | [`powersOfTau28_hez_final_16.ptau`](https://storage.googleapis.com/zkevm/ptau/powersOfTau28_hez_final_16.ptau) | the dev zkeys in `packages/circuits/build/` (control check only) | `1c401abb57c9ce531370f3015c3e75c0892e0f32b8b1e94ace0f6682d9695922` |

Neither upstream publishes hashes for these files. The values above (and the
BLAKE2b-512 ones in `lib/common.js`) were measured from the URLs on
2026-07-03; `fetch_ptau` enforces them so later runs are checked against the
same bytes this flow was validated with.

## Scripts

Node.js ≥ 18, `yarn` and `circom` are required; install the repo-pinned
toolchain first (`yarn install --frozen-lockfile`, giving `snarkjs@0.7.5`,
`circomkit@0.3.2`, `circomlib@2.0.5`, `maci-circuits@2.5.0`). The scripts can
be run from any directory; downloads and regenerated files go to the
gitignored `verification/artifacts/`. Or run everything:
`./verification/run_all.sh`

| script | what it does | proves |
|---|---|---|
| `yarn workspace @privacy-pool-core/circuits compile` (repo script) | compiles the circuits; `npx circomkit info <circuit>` should report 1537 (commitment) / 36832 (withdraw) constraints | `*.circom` ⇒ `*.r1cs` |
| `scripts/fetch_ptau` | downloads both phase-1 files (~150 MB total) into `artifacts/` and checks their hashes | — |
| `scripts/verify_zkeys --kind build` | control: `snarkjs zkey verify` of the checked-in dev zkeys against the Hermez file — confirms R1CS, ptau handling and snarkjs work in your environment | — |
| `scripts/verify_zkeys --kind final` | `snarkjs zkey verify` of the final zkeys against `ppot_0080_16.ptau`: recomputes the initial key from the R1CS + ptau and validates every one of the 514/515 embedded contributions including the deterministic beacon; additionally checks the embedded beacon hash equals `trusted-setup/beacon_hash` | `*.r1cs` + PPoT #80 + transcript ⇒ final `*.zkey` |
| `scripts/verify_final_vkeys` | exports the vkey from each final zkey and compares it with the checked-in `.vkey` (regenerated copies land in `artifacts/regenerated-vkeys/`) | final `*.zkey` ⇒ final `*.vkey` |

Every script accepts `--help`; `verify_zkeys` also takes `--ptau <path>` to
test a different phase-1 candidate and `--circuits <subset>` (the withdraw
check takes a few minutes).

All steps pass as of 2026-07-03 with Node v22.15.0, circom 2.2.2 and the
repo-pinned snarkjs 0.7.5.

## What a full pass proves — and what it does not

Proves: the checked-in final `.vkey` files are exactly the Groth16
verification keys produced by the Privacy Pools MPC ceremony **for exactly
the circuits compiled from this repo's `.circom` sources**, with the standard
MPC guarantee: sound unless every one of the ~513 phase-2 contributors
colluded, assuming an honest phase 1.

Not covered (separate checks if you care):

1. **Onchain deployment**: that the verifiers deployed onchain embed these
   vkeys. The repo scripts `yarn workspace @privacy-pool-core/circuits
   gencontract:{withdraw,commitment}` regenerate the verifier contracts from
   the final zkeys; compare those (or the vk constants) against the verified
   onchain sources.
2. **Phase-1 honesty and provenance**: `ppot_0080_16.ptau` is trusted as
   served by PSE's S3 bucket; upstream publishes no hashes for it.
   `snarkjs zkey verify` does check the ptau's internal pairing consistency,
   but tying it to the published PPoT contribution transcript (or recomputing
   the truncation from the full `ppot_0080.ptau`) is out of scope here.
3. **Participant attribution**: replaying the individual ceremony
   contributions / attestations from ceremony.pse.dev.
