Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 1 addition & 5 deletions artifacts/checksums.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
bf095310e0fd04c48eff6d9cb3cefc9063af9f19289e2c133891f7cfefe580a3 cw_amaci-aarch64.wasm
faa7d0f2e1fbc9878223d8c5b048bc5d41237360de840c7ca1c730aa9840213a cw_amaci_registry-aarch64.wasm
86583bd5db998cff1bd9b45c62e9d207875fa3ae63929d69afeacf4cb3938fab cw_api_saas-aarch64.wasm
9b20be65d366f0c05a678448c3cf90a9717ed394e0e77df5aec71cfed7df80e4 cw_maci-aarch64.wasm
904c160324e3f943f5841e4acfdb61402332c228d3fad4345f9c1f9c0b23f237 cw_test-aarch64.wasm
8f4809e83856f8b5f21def9e28953bfcd90281797e90987b595f325756e7c1cf cw_amaci-test-vkeys-aarch64.wasm
274 changes: 274 additions & 0 deletions contracts/amaci/DEPLOYMENT_TESTNET.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
# cw-amaci Hybrid (MACI + AHE) — Testnet Deployment Guide

This document covers deploying the `cw-amaci` contract — including the new
**Hybrid MACI + Additively-Homomorphic-Encryption (AHE) ballot verifier**
(`QueryMsg::VerifyHybridBallot`) — to the DoraFactory testnet (`vota-testnet`).

It assumes you already have `dorad` (or any CosmWasm-compatible chain CLI) and a
funded testnet account. It does **not** cover setting up a local devnet.

---

## 1. What is actually "ready" right now

| Area | Status |
|---|---|
| `BallotValidity` circuit + state-tree Merkle inclusion (Phase 1) | ✅ circuit compiled, SDK wired, demo verified end-to-end |
| `BallotValidityOnchain` circuit (single `inputHash`, contract-compatible) | ✅ compiled, zkey/vkey generated, local proof↔hash parity verified |
| `cw-amaci::QueryMsg::VerifyHybridBallot` on-chain verifier | ✅ implemented, unit-tested, **e2e-tested through the real wasm `query` entry point** |
| Poseidon state-leaf hashing parity (SDK ⇄ `maci-utils`) | ✅ Rust parity test (`test_hybrid_demo_state_leaf_parity`) |
| Production wasm build (default features, no `test-vkeys`) | ✅ compiles for `wasm32-unknown-unknown` |
| `test-vkeys` wasm build (adds lightweight 2-1-1-5 circuit) | ✅ compiles for `wasm32-unknown-unknown` |
| Downstream contracts (`cw-amaci-registry`, `cw-api-saas`) still build/test against the changed `cw-amaci` | ✅ no breakage |
| Aggregated homomorphic tally + threshold decryption committee flow | ⚠️ still off-chain only (`mpc-coordinator-demo`), **not** part of this contract change |
| A `ProcessHybridMessages` batch proof executed **on-chain** | ⚠️ not implemented — only the per-voter `ballotValidity` proof has an on-chain verifier today |

So: **the ballot-integrity half of the hybrid design is genuinely on-chain and testable now.** The
homomorphic aggregation/decryption half is still a coordinator-side demo, exactly as scoped in
this phase. `VerifyHybridBallot` is a pure read-only query — it doesn't change any existing
MACI/aMACI execution paths, so it's safe to ship without affecting normal rounds.

---

## 2. Test coverage (run before you deploy)

```bash
cd maci
cargo test -p cw-amaci --lib # 52 passed, 0 failed, 2 ignored
cargo test -p cw-amaci-registry --lib # 42 passed, 0 failed
cargo test -p cw-api-saas --lib # 21 passed, 0 failed
```

Of the 52 `cw-amaci` tests, 3 are new **real e2e tests** for the hybrid feature
(`multitest::tests::test::e2e_verify_hybrid_ballot_*`). Unlike the lower-level
`contract::tests::hybrid_ballot_proof_*` unit tests (which call the Rust helper directly), these
go through `cw_multi_test`'s full `instantiate` → `query` dispatch — i.e. the same code path a
real chain node executes:

- `e2e_verify_hybrid_ballot_accepts_real_proof_via_query_entrypoint` — a real Groth16 proof
(generated by `mpc-coordinator-demo/scripts/exportHybridVkeyProof.ts`) verifies as `true`.
- `e2e_verify_hybrid_ballot_rejects_tampered_state_root_via_query_entrypoint` — replaying the
same proof against a different `stateRoot` is rejected.
- `e2e_verify_hybrid_ballot_rejects_tampered_ahe_commitment_via_query_entrypoint` — replaying
against a different `aheCommitment` is rejected.

If you want to add your own fixture, regenerate it with:

```bash
cd mpc-coordinator-demo
npx ts-node scripts/exportHybridVkeyProof.ts # writes hybridBallotFixture.json
```

---

## 3. Building the deployable wasm

### 3.1 Two build variants — pick one intentionally

`match_vkeys()` in `circuit_params.rs` gates which `MaciParameters` (circuit size) an
`InstantiateMsg` is allowed to use, based on Cargo features:

| Build | Feature flags | Accepted `MaciParameters` | Use case |
|---|---|---|---|
| **Production** | *(default, no extra flags)* | `state_tree_depth=9, int_state_tree_depth=4, vote_option_tree_depth=3, message_batch_size=125` only | Real rounds, matches what's already live on `vota-ash` / `vota-testnet` (`maciCodeId` 106 / 134) |
| **Test-vkeys demo** | `--features test-vkeys` | production size **or** `2-1-1-5` (small, fast-to-prove) | Quick hybrid-feature demos on testnet — matches the circuit size already used throughout this session's `mpc-coordinator-demo` |

⚠️ **Do not use the `test-vkeys` build for a round that holds real value.** It accepts a
lightweight circuit whose trusted setup is a local `circomkit` dev ceremony, not an audited
production ceremony. It's fine for a labelled "demo/testnet trial" round.

`VerifyHybridBallot` itself is unaffected by this choice — the hybrid ballot vkey
(`circuit_params::hybrid_ballot_vkey()`) is hardcoded unconditionally and works on any
successfully-instantiated contract regardless of which `MaciParameters` you chose.

### 3.2 Recommended: official Docker optimizer (reproducible build)

```bash
cd maci
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/rust-optimizer-arm64:0.12.11 # Apple Silicon host
# or cosmwasm/rust-optimizer:0.12.11 # x86_64 host
```

This produces `artifacts/cw_amaci-aarch64.wasm` (or `-x86_64`) plus a matching
`artifacts/checksums.txt` entry, using the **default (production)** features.

**Known gotcha found while validating this guide**: on an Apple Silicon Docker host, the
`rust-optimizer-arm64:0.12.11` image is missing `clang`, which the `blake` crate (pulled in
transitively by the Groth16 verifier's `pairing`/`bellman-ce` fork) needs to build its C shim —
the build fails with `ToolNotFound: failed to find tool "clang"`. Two ways around it:

- Run the optimizer step on an x86_64 host/CI runner (e.g. a GitHub Actions `ubuntu-latest`
job) using `cosmwasm/rust-optimizer:0.12.11`, **or**
- Use the newer unified `cosmwasm/optimizer:0.16.x` image, which bundles `clang` — but note it
currently only ships an `amd64` image, so on an Apple Silicon host without a native arm64 tag
it runs under emulation and can take 20+ minutes.

### 3.3 Fallback: native build + `wasm-opt` (verified to work, faster locally)

If you just need a working artifact quickly (e.g. to unblock a testnet trial while CI produces
the canonical reproducible build), this path was verified during this session:

```bash
cd maci
rustup target add wasm32-unknown-unknown # if not already installed
cargo build -p cw-amaci --release --target wasm32-unknown-unknown
# add --features test-vkeys here if you want the 2-1-1-5 demo circuit too

wasm-opt -Os --signext-lowering \
target/wasm32-unknown-unknown/release/cw_amaci.wasm \
-o cw_amaci_optimized.wasm
```

Measured sizes for this build (production features, no `test-vkeys`):

- Unoptimized: **2,938,341 bytes** (~2.8 MB)
- After `wasm-opt -Os`: **2,242,231 bytes** (~2.1 MB)

This is comparable to the already-live `cw-amaci` code on `vota-testnet`/`vota-ash`, so size is
not expected to be a blocker on DoraFactory's chain.

---

## 4. Deploying to `vota-testnet`

Known network parameters (from `packages/sdk/src/libs/const.ts`):

| Field | Value |
|---|---|
| Chain ID | `vota-testnet` |
| RPC | `https://vota-testnet-rpc.dorafactory.org` |
| REST | `https://vota-testnet-rest.dorafactory.org` |
| Address prefix | `dora` |
| Fee denom | `peaka` (1 DORA = 10¹⁸ peaka) |
| Existing registry (production code, **not yours to reconfigure**) | `dora13c8aecstyxrhax9znvvh5zey89edrmd2k5va57pxvpe3fxtfsfeqlhsjnd` |

You have two deployment shapes. For validating the hybrid feature, **(A) is what you want.**

### A. Standalone instantiate (recommended for this trial)

Store your own code and instantiate directly — full control, no dependency on the shared
production registry.

```bash
# 1. Store the code
dorad tx wasm store cw_amaci_optimized.wasm \
--from <your-key> --chain-id vota-testnet \
--node https://vota-testnet-rpc.dorafactory.org \
--gas auto --gas-adjustment 1.3 --gas-prices 10000000000peaka \
--broadcast-mode sync -y

# note the code_id from the tx result / `dorad query tx <hash>`
```

Instantiate (example uses the `test-vkeys` 2-1-1-5 shape; swap `parameters` to the 9-4-3-125
production shape if you built without `test-vkeys`):

```json
{
"parameters": {
"state_tree_depth": "2",
"int_state_tree_depth": "1",
"message_batch_size": "5",
"vote_option_tree_depth": "1"
},
"coordinator": { "x": "<coordinator pubkey x>", "y": "<coordinator pubkey y>" },
"admin": "dora1...",
"fee_recipient": "dora1...",
"operator": "dora1...",
"vote_option_map": ["Option 1", "Option 2", "Option 3", "Option 4", "Option 5"],
"round_info": { "title": "Hybrid AHE Demo", "description": "", "link": "" },
"voting_time": { "start_time": "<unix nanos>", "end_time": "<unix nanos>" },
"circuit_type": "0",
"certification_system": "0",
"poll_id": 1,
"voice_credit_mode": { "unified": { "amount": "100" } },
"registration_mode": {
"sign_up_with_static_whitelist": {
"whitelist": { "users": [{ "addr": "dora1...", "voice_credit_amount": null }] }
}
},
"deactivate_enabled": false,
"message_fee": "60000000000000000",
"deactivate_fee": "10000000000000000000",
"signup_fee": "30000000000000000",
"base_delay": 200,
"message_delay": 2,
"signup_delay": 1,
"deactivate_delay": 600
}
```

```bash
dorad tx wasm instantiate <code_id> '<the JSON above>' \
--from <your-key> --chain-id vota-testnet --label "hybrid-ahe-demo" \
--no-admin \
--node https://vota-testnet-rpc.dorafactory.org \
--gas auto --gas-adjustment 1.3 --gas-prices 10000000000peaka -y
```

`certification_system: "0"` means Groth16 — this is the only verification path implemented (a
value of `1` for Plonk is reserved in the schema but has no working verifier in this codebase;
don't use it).

### B. Via the shared registry (production-shaped flow)

If you have operator/admin rights on the already-deployed registry, its `ExecuteMsg::CreateRound`
instantiates a `cw-amaci` round using whatever `maci_code_id` the registry is currently configured
with. To point it at *your* new code (with the hybrid verifier), you'd need to update that
registry's stored code ID — out of scope for a self-serve testnet trial unless you own that
registry's admin key. Path (A) avoids this entirely.

---

## 5. Verifying the deployment

```bash
# Sanity: round metadata
dorad query wasm contract-state smart <contract_addr> '{"get_round_info":{}}' --node ...
dorad query wasm contract-state smart <contract_addr> '{"get_period":{}}' --node ...
dorad query wasm contract-state smart <contract_addr> '{"get_num_sign_up":{}}' --node ...
```

### Hybrid ballot verification (the new part)

`VerifyHybridBallot` works immediately after instantiate — it doesn't depend on signups, voting
periods, or any round state, only on the hardcoded hybrid vkey baked into the contract binary.

```bash
dorad query wasm contract-state smart <contract_addr> '{
"verify_hybrid_ballot": {
"kc": ["<Kc.x>", "<Kc.y>"],
"state_root": "<stateRoot>",
"state_idx": "<voter state index>",
"pub_key": ["<voter pubkey x>", "<voter pubkey y>"],
"ahe_commitment": "<aheCommitment>",
"proof": { "a": "<hex>", "b": "<hex>", "c": "<hex>" }
}
}' --node https://vota-testnet-rpc.dorafactory.org
```

Expect `{"data":true}` for a valid proof, `{"data":false}` for any tampered public value — this
mirrors exactly the three e2e tests described in §2. You can pull ready-made sample values from
`mpc-coordinator-demo/scripts/hybridBallotFixture.json` (regenerate via
`exportHybridVkeyProof.ts` if you need a fresh one bound to your own voter/session data).

Field naming reminder: this contract's JSON uses `snake_case` (`cw_serde` default), so it's
`state_root` / `state_idx` / `pub_key` / `ahe_commitment`, not camelCase.

---

## 6. Known limitations to communicate to anyone testing this round

- `VerifyHybridBallot` is a **read-only integrity check** for a single voter's ballot. It proves
"this ballot's committed voice-credit budget is authenticated against `stateRoot`" without
revealing the vote. It does **not** submit or tally anything on-chain.
- The actual vote content stays AHE-encrypted and is only decrypted, in aggregate, by the
off-chain threshold committee in `mpc-coordinator-demo` — this contract change doesn't move
that decryption on-chain.
- Coordinator batch processing (`ProcessHybridMessages`) still has no on-chain verifier; only the
per-voter `ballotValidity` proof does. If/when batch tallying needs to move on-chain too, it
will need its own `*Onchain`-style wrapper circuit and `QueryMsg`/`ExecuteMsg`, following the
same pattern used here.
Loading
Loading