Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
12f0349
docs: add milestone-3 frontend evidence (Frontend Integration & Docum…
benceharomi Aug 3, 2026
3f2cf6f
docs: cite real CI-backed test coverage for deliverable 5
benceharomi Aug 4, 2026
3d757be
docs: drop internal comparison to test_0001_2 from the deliverable doc
benceharomi Aug 4, 2026
72a5ff4
docs: drop ZK framework selection fix from deliverable 5
benceharomi Aug 4, 2026
297e428
docs: link blueprint ID to registry staging page
benceharomi Aug 4, 2026
d42684f
docs: add public how-to for blueprint creation and on-chain verification
benceharomi Aug 5, 2026
03aecf9
docs: add PVM bytecode verification evidence
benceharomi Aug 5, 2026
66af664
docs(kusama-grant): rename milestone-3 overview to README.md, cross-l…
benceharomi Aug 5, 2026
dbbfbbd
docs(kusama-grant): cross-reference all 5 milestone-3 deliverables, d…
benceharomi Aug 5, 2026
f891af3
docs(kusama-grant): unify milestone-3 and top-level index with the ot…
benceharomi Aug 5, 2026
5166e58
docs(kusama-grant): fix broken relative links to src/app files
benceharomi Aug 5, 2026
66aafdc
docs(kusama-grant): add Groth16Verifier provenance to doc 07
benceharomi Aug 26, 2026
8ee56c8
docs(kusama-grant): add full provenance summary to doc 07
benceharomi Aug 26, 2026
b648cc0
docs(kusama-grant): add real DKIM revocation demo to milestone-3
benceharomi Aug 26, 2026
515b8cc
Merge remote-tracking branch 'origin/staging' into kusama-grant
benceharomi Aug 26, 2026
cf125f2
Merge remote-tracking branch 'origin/staging' into kusama-grant
benceharomi Aug 26, 2026
4377b98
docs(kusama-grant): document the tampered-proof Playwright test in do…
benceharomi Aug 26, 2026
da84263
docs(kusama-grant): add blueprint compile timestamp to doc 07's prove…
benceharomi Aug 26, 2026
24fd4cd
docs(kusama-grant): add milestone-3 manifest.json, dedupe repeated ci…
benceharomi Aug 26, 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
9 changes: 9 additions & 0 deletions docs/kusama-grant/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Kusama Grant Documentation Index

This directory tracks delivery evidence for Kusama grant milestones.

## Milestones

- Milestone 1 - DKIM Registry on Testnet: [zk-email-verify `packages/contracts/docs/kusama-grant/milestone-1`](https://github.com/zkemail/zk-email-verify/blob/kusama-grant/packages/contracts/docs/kusama-grant/milestone-1/README.md)
- Milestone 2 - ZK Verifier Contract Tooling: [sdk-images `circom/docs/kusama-grant/milestone-2`](https://github.com/zkemail/sdk-images/blob/kusama-grant/circom/docs/kusama-grant/milestone-2/README.md)
- Milestone 3 - Verification Pipeline + SDK & Frontend: [sdk-images `circom/docs/kusama-grant/milestone-3`](./milestone-3/README.md)
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 05 - Frontend Integration & Documentation

Milestone 3 frontend integration: the registry frontend supports target chain selection for new blueprints and lets users verify Circom proofs on-chain against the deployed verifier contract.

## Implementation Notes

### Chain selection

- **UI component:** [`src/app/create/[id]/createBlueprintSteps/EmailDetails.tsx`](../../../src/app/create/%5Bid%5D/createBlueprintSteps/EmailDetails.tsx) adds a `Target Chain` `<Select>` to **Step 3 (Optional Details)** of the blueprint creation wizard, with three options: Base Sepolia (`84532`), Ethereum Sepolia (`11155111`), and Paseo Testnet / Polkadot (`420420417`).
- **Store:** [`src/app/create/[id]/store.ts`](../../../src/app/create/%5Bid%5D/store.ts) initializes `verifierContract.chain` to `84532` (Base Sepolia); the component falls back to the same default when unset. The selected chain is used downstream by the compilation pipeline when deploying the Solidity verifier contract.
- Covered by [`tests/kusama/targetChainSelector.ts`](../../../tests/kusama/targetChainSelector.ts): confirms Paseo is both an available option and genuinely selected on the real `kusama_grant_paseo_e2e` blueprint. Runs in CI on every push (see Demonstration below).

### On-chain verification UI

- A **"Verify On-Chain"** button appears in two places: the proof list row ([`src/app/[id]/ProofRow.tsx`](../../../src/app/%5Bid%5D/ProofRow.tsx), `onVerifyOnChain` / `isVerifyingOnChainLoading`) and the proof detail page ([`src/app/[id]/proofs/[proofId]/page.tsx`](../../../src/app/%5Bid%5D/proofs/%5BproofId%5D/page.tsx), same pattern).
- Rendered only when a browser wallet (`window.ethereum`) is detected, the blueprint has a `verifierContract.address` set, and the proof is Circom-based (not Noir); disabled while the proof is still `InProgress`.
- Calls `blueprint.verifyProofOnChain(proof)` from `@zk-email/sdk` and shows a toast with the result.
- Covered by [`tests/kusama/verifyProofOnChain.ts`](../../../tests/kusama/verifyProofOnChain.ts) (real proof, asserts success) and [`tests/kusama/verifyTamperedProofOnChain.ts`](../../../tests/kusama/verifyTamperedProofOnChain.ts) (same real proof, corrupted in-flight, asserts failure). Runs in CI on every push (see Demonstration below).

### Dynamic block explorer links

- [`src/app/[id]/proofs/[proofId]/page.tsx`](../../../src/app/%5Bid%5D/proofs/%5BproofId%5D/page.tsx) builds the verifier contract address link from an `EXPLORER_MAP` keyed by chain ID: `84532` → `sepolia.basescan.org`, `11155111` → `sepolia.etherscan.io`, `420420417` → `blockscout-testnet.polkadot.io`. Falls back to `#` if the chain is unrecognized or no address is set.
- Also covered by [`tests/kusama/verifyProofOnChain.ts`](../../../tests/kusama/verifyProofOnChain.ts), which asserts the rendered link resolves to Blockscout specifically for this Paseo-targeted blueprint.

## Demonstration

```bash
bunx playwright test tests/kusama/
```

Four real, end-to-end Playwright tests under [`tests/kusama/`](../../../tests/kusama/), all against [`zkemailverify/kusama_grant_paseo_e2e`](https://registry-staging.onrender.com/e94e7f93-7575-4e26-a147-de894b19ce3e/versions) (blueprint `e94e7f93-7575-4e26-a147-de894b19ce3e`), compiled and deployed to Paseo through the current pipeline:

| Test | Exercises |
| --- | --- |
| [`targetChainSelector.ts`](../../../tests/kusama/targetChainSelector.ts) | Target Chain dropdown offers, and this blueprint has selected, Paseo Testnet (Polkadot). |
| [`generateProof.ts`](../../../tests/kusama/generateProof.ts) | Fresh remote proof generation end to end (email upload through "View Proof"). |
| [`verifyProofOnChain.ts`](../../../tests/kusama/verifyProofOnChain.ts) | Real "Verify On-Chain" button click against the deployed verifier ([`manifest.json#L20`](./manifest.json#L20)), asserting the explorer link resolves to Blockscout/Paseo specifically and that verification succeeds. |
| [`verifyTamperedProofOnChain.ts`](../../../tests/kusama/verifyTamperedProofOnChain.ts) | Same real proof, corrupted in-flight via route interception (the persisted proof is never touched), asserting the failure toast through the actual button. Added after a curator-reported bug (`blueprint.verifyProofOnChain` discarding the standalone call's result, fixed in [zk-email-sdk-js#104](https://github.com/zkemail/zk-email-sdk-js/pull/104)) - this test failed against `@zk-email/sdk@3.0.0-nightly.34` and passes against `3.0.0-nightly.36`, confirmed by real CI runs on both. |

Runs automatically in CI on every push to `staging`/`main` via [`.github/workflows/playwright.yml`](../../../.github/workflows/playwright.yml). Example passing `test` job, with all four tests including the tampered-proof case: [`manifest.json#L11-L14`](./manifest.json#L11-L14). For the current state of the branch, see the [Actions tab](https://github.com/zkemail/registry/actions/workflows/playwright.yml?query=branch%3Astaging).
51 changes: 51 additions & 0 deletions docs/kusama-grant/milestone-3/06_public_howto.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 06 - Public How-To: Creating a Blueprint and Verifying On-Chain on Paseo

Step-by-step walkthrough of the deliverable 5 golden path in the registry frontend: create a blueprint targeting Paseo, upload an email, generate a proof, and verify it both locally and on-chain, using only the UI.

## 1) Create a blueprint

Click **Create Blueprint** and fill in the pattern details: a name, description, etc.

![Pattern Details step](./assets/01-create-blueprint-step1.png)

On the next step, choose which fields to extract. Quick header extraction toggles (Subject, To field, Sender email, Sender domain, Timestamp) auto-write the regexes for you - each one can be marked Public or Private.

![Extract Fields step](./assets/02-create-blueprint-step2-1.png)

Scrolling down shows the generated regex for each field, an editable Max Length, and a live test against the sample email. Once it reads "All tests passed. Ready to compile", move on.

![Regex definition and test results](./assets/03-create-blueprint-step2-2.png)

On the final step, set **Target Chain** to **Paseo Testnet (Polkadot)** then click **Submit Blueprint**.

![Optional Details step with Paseo selected](./assets/04-create-blueprint-step3.png)

## 2) Wait for compilation

Compilation happens server-side: the circuit is compiled, a Solidity verifier is generated, and it's deployed to Paseo. Depending on complexity, this can take more than an hour - the blueprint's status badge reads **In Progress** in the meantime, with an option to cancel.

![Compilation in progress](./assets/05-compilation-in-progress.png)

Once the badge reads **Compiled**, the blueprint is ready to generate proofs against.

## 3) Upload an email and generate a proof

On the blueprint page, upload an `.eml` file matching the sender domain (or connect Gmail).

![Connect emails step](./assets/06-generate-proof-step1.png)

Select the email to prove and press **Remote Proving**.

![Select emails step](./assets/07-generate-proof-step2.png)

## 4) Verify the proof

Once generation finishes, the proof row shows its outputs, a validity checkmark, a button to download the proof, and both a **Verify** and a **Verify On-Chain** button. **Verify** checks the proof locally in the browser. **Verify On-Chain** calls the deployed verifier contract's view function on Paseo and reports the result of verification:

![Proof verified successfully on chain](./assets/08-verify-on-chain-success.png)

## 5) Find the deployed contract address

Open the proof's detail page to see the full generated proof, its public outputs, and the **Verifier Address** - the deployed contract on Paseo that `Verify On-Chain` calls. The address links out to the block explorer for independent inspection. The proof can also be downloaded from this page.

![Proof details page with verifier address](./assets/09-proof-details.png)
140 changes: 140 additions & 0 deletions docs/kusama-grant/milestone-3/07_bytecode_verification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# 07 - Bytecode Verification

Verifier contracts are generated and deployed dynamically per blueprint by the registry's compilation pipeline, so there's no single fixed contract to point at like milestone 1's `DKIMRegistry`. This walks through reproducing the deployed bytecode for a specific blueprint's verifier contract from its downloadable circuit bundle, and comparing it against what's actually on-chain.

## Deployment Manifest

| Field | Value |
| --- | --- |
| Blueprint | [`zkemailverify/kusama_grant_paseo_e2e`](https://registry-staging.onrender.com/e94e7f93-7575-4e26-a147-de894b19ce3e/versions) (`e94e7f93-7575-4e26-a147-de894b19ce3e`) |
| Contract | `ZKEmailVerifier` |
| Network | Polkadot Hub Testnet (Paseo Testnet), chain ID `420420417` |
| Address | [`0x72616B78d29d0cccBfEec1bf00E108885286D2f3`](https://blockscout-testnet.polkadot.io/address/0x72616B78d29d0cccBfEec1bf00E108885286D2f3) |
| `resolc` version | `0.5.0` (pinned in the bundle's `hardhat.config.ts`) |
| `solc` version | `0.8.30` |
| Optimizer | enabled, `runs = 10000` |
| PVM bytecode magic | `0x50564d0000` (`"PVM\0"` prefix - confirms genuine PolkaVM/RISC-V bytecode, not EVM) |
| Runtime bytecode hash (keccak256) | `0x5655a9fe87e1a1bd736b117c89a181c71f7985a682f419e3396574165a37ef55` |

## Download the circuit bundle

Every compiled blueprint has a downloadable `circuit.zip` containing the contract source, its `hardhat.config.ts`, and a `package.json` with the exact build script used to compile it:

[`https://registry-staging.onrender.com/e94e7f93-7575-4e26-a147-de894b19ce3e/download`](https://registry-staging.onrender.com/e94e7f93-7575-4e26-a147-de894b19ce3e/download)

`circuit.zip` is listed under "Downloads for Server Side Circom". Unzip it - the `contracts/` directory is a self-contained Hardhat project.

## Reproducible Command Flow

From the unzipped bundle's `contracts/` directory:

```bash
yarn install
yarn build
```

## Verification

- Source-code verification is **not currently possible for PolkaVM deployments.** The contract is `resolc`-compiled to PolkaVM/RISC-V bytecode; the Blockscout explorer's verification API and `@nomicfoundation/hardhat-verify` both only support EVM `solc`/Vyper bytecode, and `@parity/hardhat-polkadot` does not yet provide a resolc-aware verify task. This is a PolkaVM tooling gap, not a deployment issue.
- The contract is still fully visible on [Blockscout](https://blockscout-testnet.polkadot.io) (address, PolkaVM bytecode, transactions) and is exercisable via its read methods.

## Bytecode Provenance

Because automated source verification is not yet available for PolkaVM, provenance is established by comparing the locally compiled runtime bytecode against the on-chain code.

| Source | keccak256 |
| --- | --- |
| Locally compiled (`hh-artifacts/src/ZKEmailVerifier.sol/ZKEmailVerifier.json`) | `0x5655a9fe87e1a1bd736b117c89a181c71f7985a682f419e3396574165a37ef55` |
| On-chain (`0x72616B78d29d0cccBfEec1bf00E108885286D2f3`) | `0x5655a9fe87e1a1bd736b117c89a181c71f7985a682f419e3396574165a37ef55` |

The two hashes are identical: the deployed contract is exactly this bundle's source, compiled with the compiler settings in its own `hardhat.config.ts`. Reproduced independently twice from a clean unzip, both times matching.

### Reproduce

```bash
# on-chain runtime-bytecode hash
cast code 0x72616B78d29d0cccBfEec1bf00E108885286D2f3 \
--rpc-url https://services.polkadothub-rpc.com/testnet | cast keccak

# locally compiled runtime-bytecode hash (from the unzipped bundle's contracts/ directory,
# after yarn install && yarn build)
jq -r '.bytecode' hh-artifacts/src/ZKEmailVerifier.sol/ZKEmailVerifier.json | cast keccak

# PVM bytecode magic (first bytes should read 0x50564d00, "PVM\0")
cast code 0x72616B78d29d0cccBfEec1bf00E108885286D2f3 \
--rpc-url https://services.polkadothub-rpc.com/testnet | cut -c1-12
```

## Groth16Verifier Provenance

The wrapper's `GROTH16_VERIFIER()` is immutable and readable on-chain, so the address it points
to (and that contract's own bytecode) can be verified with the same method used above for the
wrapper itself, rather than just asserted.

| Field | Value |
| --- | --- |
| Contract | `Groth16Verifier` |
| Address (read from the wrapper's own `GROTH16_VERIFIER()`, not just asserted) | [`0xDfbcfE9D3C6ecdc0d614Ff671b5A3fd73E6d3DBC`](https://blockscout-testnet.polkadot.io/address/0xDfbcfE9D3C6ecdc0d614Ff671b5A3fd73E6d3DBC) |
| Source | `src/Groth16Verifier.sol` in the same `circuit.zip` bundle, same `hardhat.config.ts` (`resolc` `0.5.0`, `solc` `0.8.30`, optimizer `runs = 10000`) |
| PVM bytecode magic | `0x50564d0000` |
| Runtime bytecode hash (keccak256) | `0xc1d5f187d0d06a9a5a31f2254299fd0f3190efe19086e7192b3c3af430f586ee` |

Locally compiled and on-chain hashes are identical: the deployed Groth16Verifier is exactly this
bundle's source, same compiler settings, same provenance chain as the wrapper.

### Reproduce

```bash
# read the verifier address directly off the deployed wrapper, rather than trusting a stated address
cast call 0x72616B78d29d0cccBfEec1bf00E108885286D2f3 "GROTH16_VERIFIER()(address)" \
--rpc-url https://services.polkadothub-rpc.com/testnet

# on-chain runtime-bytecode hash of that address
cast code 0xDfbcfE9D3C6ecdc0d614Ff671b5A3fd73E6d3DBC \
--rpc-url https://services.polkadothub-rpc.com/testnet | cast keccak

# locally compiled runtime-bytecode hash (same unzipped bundle as above)
jq -r '.bytecode' hh-artifacts/src/Groth16Verifier.sol/Groth16Verifier.json | cast keccak

# PVM bytecode magic
cast code 0xDfbcfE9D3C6ecdc0d614Ff671b5A3fd73E6d3DBC \
--rpc-url https://services.polkadothub-rpc.com/testnet | cut -c1-12
```

## Full Provenance Summary

Everything above in one place: the `sdk-images` commit that generated this blueprint's contracts,
the downloadable artifacts that came out of it, and the two deployed contracts' runtime bytecode.
Compiler settings aren't re-stated here beyond what's already above - they're read directly from
the bundle's own `hardhat.config.ts`, not something separate to prove.

| Field | Value |
| --- | --- |
| Blueprint compile timestamp | `2026-08-03T17:40:26Z` (the blueprint's own `updated_at`, readable via the API - see Reproduce below) |
| sdk-images generator commit | [`a6e5c43`](https://github.com/zkemail/sdk-images/commit/a6e5c43) ("test(circom): generator-regression test + fixture README fix (#70)"), confirmed unchanged on `staging` between 2026-07-27 and the compile timestamp above |
| `circuit.zip` hash (sha256) | `edc4a3b89b7699911c2b402fdf0a64ac3a1091b240c70420534bb4bec9f40592` |
| `circuit.zkey` hash (sha256) | `e8358bbc0c70db9f3d76ed92473dcf6c1e8de5b8a3e1fb2ea7f13778a75c34fe` (from `circuit_zkey.zip`; the separate `circuit.zkey.gz` asset for this blueprint is a 43-byte stub, not the real file) |
| Wrapper (`ZKEmailVerifier`) runtime bytecode hash | `0x5655a9fe87e1a1bd736b117c89a181c71f7985a682f419e3396574165a37ef55` |
| Groth16Verifier runtime bytecode hash | `0xc1d5f187d0d06a9a5a31f2254299fd0f3190efe19086e7192b3c3af430f586ee` |
| Compiler settings | see the [Deployment Manifest](#deployment-manifest) above - `resolc 0.5.0`, `solc 0.8.30`, optimizer `runs = 10000`, all read from the bundle's own `hardhat.config.ts` |

### Reproduce

```bash
# circuit.zip hash
shasum -a 256 circuit.zip

# zkey hash (from the zip bundle, not the separate .gz asset)
unzip circuit_zkey.zip -d zkey_unzipped
shasum -a 256 zkey_unzipped/circuit.zkey

# blueprint compile timestamp, straight from the API (updated_at)
curl -s https://staging-conductor.zk.email/blueprint/e94e7f93-7575-4e26-a147-de894b19ce3e \
| jq -r '.updated_at.seconds' | xargs -I{} date -u -r {} "+%Y-%m-%dT%H:%M:%SZ"

# generator commit: confirm nothing touched the contract generator between a6e5c43
# and the blueprint's compile time
git log a6e5c43..origin/staging --oneline --before="2026-08-03T17:40:26Z" \
-- circom/src/contract.rs circom/templates/ circom/src/main.rs
# (empty output confirms a6e5c43 was still the live generator state)
```
Loading
Loading