# bitwrap

[![CI](https://github.com/stackdump/bitwrap-io/actions/workflows/ci.yml/badge.svg)](https://github.com/stackdump/bitwrap-io/actions/workflows/ci.yml)
[![jsDelivr](https://data.jsdelivr.com/v1/package/gh/stackdump/bitwrap-io/badge)](https://www.jsdelivr.com/package/gh/stackdump/bitwrap-io)

**Anonymous on-chain voting with ZK proofs.**

Create polls where every vote is backed by a Groth16 proof. No one sees how you voted. Everyone can verify the result is correct. Deploy to any EVM chain.

**[bitwrap.io](https://bitwrap.io)** | **[Polls](https://bitwrap.io/poll)** | **[Editor](https://app.bitwrap.io)** | **[Docs](https://book.pflow.xyz)**

## Quick start

**Prerequisites:** Go ≥ 1.24. Everything else is optional — `forge` + `anvil` (Foundry) are only needed for the `make validate` pipeline that deploys generated contracts end-to-end; `npm` is only for the Playwright e2e suite.

```bash
git clone https://github.com/stackdump/bitwrap-io.git
cd bitwrap-io
make run    # serves on :8088
```

Three API calls to create a poll, cast a vote, and read results:

```bash
# Create a poll (requires wallet signature)
curl -X POST https://api.bitwrap.io/api/polls \
  -H "Content-Type: application/json" \
  -d '{"title":"Board Vote","choices":["Approve","Reject","Abstain"],...}'

# Cast a ZK-proven vote
curl -X POST https://api.bitwrap.io/api/polls/{id}/vote \
  -H "Content-Type: application/json" \
  -d '{"nullifier":"0x...","voteCommitment":"0x...","proof":"..."}'

# Get results (sealed while active, visible when closed)
curl https://api.bitwrap.io/api/polls/{id}/results
```

## How it works

**Vote.** Voters register with a commitment hash. When they cast a ballot, a nullifier (derived from `mimcHash(voterSecret, pollId)`) prevents double-voting without revealing identity.

**Prove.** Each vote generates a Groth16 proof attesting the voter is registered and the ballot is valid — without revealing the choice. The circuit verifies Merkle inclusion in the voter registry, nullifier binding, and vote range.

**Tally.** Results stay sealed until the poll closes. Once closed, the final tally is publicly verifiable — anyone can audit the proofs without accessing individual ballots.

### Sealed results

While a poll is active, the results endpoint only returns the vote count. Tallies, nullifiers, and commitments are hidden to prevent observers from diffing the tally after each vote and correlating timing to de-anonymize voters. Full results are exposed only after the poll is closed.

### Wallet-native auth

Poll creation requires an EIP-191 `personal_sign` signature from MetaMask or any Ethereum wallet. Voting secrets can be derived from wallet signatures, making the nullifier deterministic per voter per poll — no accounts, no passwords.

## What it does

- **ZK voting** — anonymous polls with nullifier-based double-vote prevention and on-chain proof verification. Sealed results prevent vote-timing correlation.
- **Visual editor** — draw places, transitions, and arcs in the browser. Models are stored as content-addressed JSON-LD.
- **Solidity generation** — produce deployable contracts and Foundry test suites from any template.
- **ZK circuits** — Groth16 circuits for transfer, mint, burn, approve, transferFrom, vestClaim, and voteCast, all **generated from the Petri net schema** (no hand-written gnark). One `.btw` source produces Solidity + Foundry tests + ZK circuits + witness builders.
- **Deploy bundle** — download complete Foundry projects for v1/v2 (`GET /api/bundle/vote`) and v3 homomorphic settlement (`GET /api/bundle/vote-v3`).
- **ERC templates** — start from ERC-20, ERC-721, ERC-1155, or a Vote template. Each is a complete Petri net with guards, arcs, and events.
- **`.btw` DSL** — a compact schema language for defining Petri net models.
- **Remix IDE plugin** — generate and deploy contracts inside Remix at [solver.bitwrap.io](https://solver.bitwrap.io).

## API

### Polls

```
POST /api/polls              Create poll (wallet signature required)
GET  /api/polls              List polls
GET  /api/polls/{id}         Get poll details
POST /api/polls/{id}/vote    Cast ZK-proven vote
POST /api/polls/{id}/close   Close poll (creator signature required)
POST /api/polls/{id}/reveal  Reveal vote choice (post-close)
GET  /api/polls/{id}/results Poll results (sealed while active)
```

### Models & templates

```
POST /api/save               Save JSON-LD model, returns {"cid": "..."}
GET  /o/{cid}                Load model by CID
GET  /img/{cid}.svg          Render model as SVG
POST /api/svg                Generate SVG from posted JSON-LD
GET  /api/templates          List ERC templates
GET  /api/templates/{id}     Get full template model
POST /api/solgen             Generate Solidity from template
POST /api/testgen            Generate Foundry tests from template
POST /api/compile            Compile .btw DSL to schema JSON
GET  /api/bundle/{template}  Download Foundry project (ZIP)
                            - /api/bundle/vote: v1/v2 poll + voteCast verifier
                            - /api/bundle/vote-v3: BitwrapZKPollV3 + voteCastHomomorphic_8 + tallyDecrypt_8 verifiers
```

### Operator tools

```
POST /api/polls/{id}/aggregate  Close a v3 poll (signed aggregate proof required)
GET  /api/polls/{id}/tally      Download the tally artifact (post-close)
```

A v3 poll can also be closed from the command line by a server operator who has
the creator's BabyJubJub secret key (`--sk-hex`) and their Ethereum private key
(`--eth-key`) or a pre-computed EIP-191 signature (`--signature`):

```sh
# Step 1: compute tallies and print the payload that needs to be signed
bitwrap close-poll <pollId> \
    --sk-hex=<creatorBabyJubJubSecretKeyHex> \
    --server=https://bitwrap.io

# Step 2: re-run with the signature once you have it
bitwrap close-poll <pollId> \
    --sk-hex=<hex> \
    --signature=<eip191sig> \
    --server=https://bitwrap.io

# Or: sign internally with an Ethereum private key (single step)
bitwrap close-poll <pollId> \
    --sk-hex=<hex> \
    --eth-key=<ethPrivKeyHex> \
    --server=https://bitwrap.io

# Optional: cache the compiled tallyDecrypt_8 circuit for fast restarts
bitwrap close-poll <pollId> --sk-hex=<hex> --eth-key=<hex> \
    --key-dir=./keys --server=https://bitwrap.io
```

The subcommand:
1. Fetches `/api/polls/{id}/votes` — the per-vote ciphertext list (one record per ballot).
2. Aggregates the ciphertexts per-bin across all ballots.
3. Decrypts each bin with the BabyJubJub secret key to recover the tally vector.
4. Compiles `tallyDecrypt_8` locally and runs `groth16.Prove` (Go-side — no WASM).
5. POSTs `{creator, signature, tallies, decryptProofBytes}` to `/api/polls/{id}/aggregate`.

v1/v2 polls are rejected with an informative error. This is the recommended path
until browser-side close lands.

For production use, prefer file-based or env-based key sources to avoid putting
secret bytes in shell history:

```sh
# File-based (mode 0o600 strongly recommended on the key files)
bitwrap close-poll <pollId> \
    --sk-hex-file=./creator-bjj.key \
    --eth-key-file=./creator-eth.key \
    --server=https://bitwrap.io

# Or via environment
BITWRAP_SK_HEX=<hex> BITWRAP_ETH_KEY=<hex> bitwrap close-poll <pollId> --server=...
```

### ZK prover

```
GET  /api/circuits              List available circuits
POST /api/prove                 Submit witness for proof generation
GET  /api/vk/{circuit}          Download verifying key (binary)
GET  /api/vk/{circuit}/solidity Download Solidity verifier contract
```

For v3, the bundle includes generated Solidity verifiers and `BitwrapZKPollV3` wiring for close-time settlement with the aggregate artifact (`data/polls/{id}/tally.json`).

## CDN

Client-side modules are available via jsDelivr:

```html
<script type="module">
import { mimcHash } from 'https://cdn.jsdelivr.net/gh/stackdump/bitwrap-io@latest/public/mimc.js';
import { MerkleTree } from 'https://cdn.jsdelivr.net/gh/stackdump/bitwrap-io@latest/public/merkle.js';
import { buildVoteCastWitness } from 'https://cdn.jsdelivr.net/gh/stackdump/bitwrap-io@latest/public/witness-builder.js';
</script>
```

| Module | Description |
|--------|-------------|
| `mimc.js` | MiMC-BN254 hash (pure BigInt, zero deps) |
| `merkle.js` | Fixed-depth binary Merkle tree with proof generation |
| `witness-builder.js` | Witness builders for all 7 ZK circuits |
| `petri-view.js` | `<petri-view>` web component for Petri net editing |

## Architecture

Single Go binary. Vanilla JS frontend. No npm, no React, no build step.

```
cmd/bitwrap/       Entry point (flags: -port, -data, -compile, -synthesize, -validate)
                   Subcommands: close-poll
dsl/               .btw lexer, parser, AST, builder
erc/               ERC token standard templates (020, 721, 1155, 4626, 5725, vote)
prover/            Groth16 prover service + generated circuits (prover/*_gen.go)
  synth/           Circuit synthesizer — metamodel.Schema → gnark source
solidity/          Solidity contract + test generation
internal/
  server/          HTTP handlers + poll lifecycle + wallet auth
  petri/           Petri net runtime (places, transitions, arcs, firing, reachability)
  metamodel/       Schema types (states, actions, arcs, guards, events, ZKOps)
  metamodel/guard/ Guard expression parser + evaluator
  seal/            CID computation (JSON-LD canonicalization via URDNA2015)
  store/           Filesystem storage (polls, votes, events)
  svg/             SVG rendering
public/            Frontend JS/CSS/HTML (embedded via go:embed)
arc/               (deprecated) — compat wrappers re-exporting internal/petri
```

## .btw schema language

Models can also be written as `.btw` files — a compact DSL for defining Petri net state machines. Here's a simplified poll:

```
schema Poll {
  version "1.0"

  register voterRegistry map[uint256]uint256 observable
  register nullifiers map[uint256]bool observable
  register tallies map[uint256]uint256 observable

  event VoteCast {
    nullifier: uint256 indexed
    choice: uint256
  }

  fn(castVote) {
    var nullifier uint256
    var choice uint256
    var weight uint256

    require(nullifiers[nullifier] == false)
    @event VoteCast

    castVote -|weight|> tallies
    castVote -|weight|> nullifiers
  }
}
```

The arrows (`-|weight|>`) are arcs — they describe how tokens flow through the net. `castVote` increments the tally for the chosen option and marks the nullifier as used, all in one atomic transition.

Compile to JSON: `bitwrap -compile poll.btw`

## Circuit synthesis

The same `.btw` source also drives the ZK circuits. A Petri net's structure maps naturally to Groth16 constraints:

| Petri net primitive | Circuit constraint |
|---|---|
| Keyed input arc (`BALANCES[from] -\|amount\|>`) | Merkle membership proof in pre-state tree |
| Keyed output arc (`-\|amount\|> BALANCES[to]`) | Post-state commitment hash |
| Guard (`BALANCES[from] >= amount`) | Range check via `ToBinary` |
| Role (`fn(mint) requires minter`) | `caller == minter` equality |
| ZKOp (nullifier-bind, commitment-bind) | Keccak/MiMC derivation constraint |

Run `bitwrap -synthesize examples/erc20.btw` and you get Go source for `TransferCircuit`, `ApproveCircuit`, `MintCircuit`, `BurnCircuit`, and `TransferFromCircuit` — each a `gnark` `frontend.Circuit` with correct public/private variable partitioning, Merkle-proof arrays sized per `State.MerkleDepth`, and guard-derived range checks. The Groth16 verifying key ships alongside the Solidity verifier so on-chain verification matches off-chain proving byte-for-byte.

There is no hand-written gnark code in this repo — `prover/circuits.go` is 70 lines of registration glue, and every circuit type lives in `prover/*_gen.go`. A `make gen-circuits && git diff --exit-code` check in CI catches stale generated output.

## License

MIT
