Releases: stackdump/bitwrap-io
v1.3.0 — v3 browser proving unblocked
Highlights
- v3 polls vote and close from a browser:
voteCastHomomorphic_8andtallyDecrypt_8now prove via the regularloadKeyspath; no more server-only close-poll workflow. Closes #3. - Root cause: gnark-crypto
ecc/<curve>/twistededwards/point.go::PointExtended.AddreadscurveParams.Dwithout theinitOnce.Do(initCurveParams)call its sibling Add/MixedAdd entry points all have. Anything that loads keys and proves — without first compiling a circuit in the same process — readcurveParams.D == 0, mis-computed the scalar-mul hint, and tripped the in-circuitscalarMulFakeGLVcheck (constraint not satisfied). Latent on amd64; surfaced in the wasm browser flow because the browser only loads, never compiles. Full chase indocs/wasm-prove-bug.md. - In-process workaround, no fork: one line at the top of
cmd/prover-wasm/main.go::main—_ = babyjubjub.GetEdwardsCurve()— arms gnark-crypto'ssync.Oncefor the rest of the process. The Go server doesn't need it (frontend.Compileruns at startup and trips the same init). - CI green again: the Go-touching jobs all needed
make wasmbeforemake build/go vet/go test(the//go:embed all:*.wasmdirective inpublic/embed.gorequires the artifact to exist). Race-detector test pass split on-shortso it doesn't time out under gnark's heavy ZK Setup. - Regression test:
prover/wasm32_loadkeys_only_test.gore-execsgo testin a subprocess to reproduce the bug deterministically — pre-fix it fails atconstraint not satisfied; post-fix it passes. Wired intomake test-wasm-provealongside the wasm-sidenoderepro.
Commits
- b2e4bc2 ci: race-detector on -short, separate full pass without race
- 2e600e2 ci: bump race-test timeout to 30m, drop v3+prod from playwright
- cbb918b ci: build wasm prover before vet/build/test/playwright jobs
- 472b76d fix: drop the gnark-crypto fork — workaround in our wasm main is enough
- 5327633 fix: gnark-crypto twistededwards.PointExtended.Add missing initOnce.Do
- 379007f fix(wasm): unblock v3 browser proving via in-wasm cs recompile (#3)
v1.2.0 — Auto-deploy via GitHub webhooks
Highlights
- Auto-deploy: GitHub webhook handler runs
git pull --ff-only && make build && services restarton push, gated by HMACDEPLOY_SECRET - Signed deploys: deploy verifies HEAD signature before fast-forward + build
- Dev wallet: signs client-side instead of via
/api/dev/sign - E2E: split out prod-targeted Playwright suite
Commits
v1.1.0 — ZK Poll: client-side verification
Highlights
- ZK Poll: in-browser verification, registry signing, batch scaling, coercion fix
Commits
- 6068ef7 ZK Poll: in-browser verify, registry signing, batch scaling, coercion fix
v0.7.2 — Reveal recovery via wallet re-sign
Small UX win on top of v0.7.1. The wallet is always the source of truth for a voter's secret — if localStorage is cleared or the voter switches devices, they can still reveal.
What changed
Before, the Reveal button only appeared on closed polls when the voter's browser still had the cached `{voterSecret, voteChoice, nullifier}` in localStorage. Clearing browser data, voting on phone and revealing on desktop, or cross-device voting in general all produced a dead-end error: "You can only reveal from the browser you voted from" — which wasn't true, just unimplemented.
Now the button is always visible on closed polls, and clicking it falls back to a wallet re-sign when the cache is empty:
- Path A — cached: localStorage has the secret + choice. Submit directly. Unchanged.
- Path B — recovery: voter picks which choice they cast (inline radio prompt), wallet re-signs the deterministic `bitwrap-vote:{pollId}` message, client re-derives `voterSecret` and recomputes the nullifier, server verifies `mimcHash(secret, choice) == storedCommitment`.
Wrong choice picks produce a friendly "that doesn't match your commitment; click Reveal again and pick a different option" — the voter retries with the right choice.
What this relies on
- EIP-191 `personal_sign` is deterministic in every modern wallet (RFC-6979 k). Re-signing `bitwrap-vote:{pollId}` with the same account always yields the same signature, so the same `voterSecret` is recovered.
- No new server endpoint, no new persistence. The reveal handler (`POST /api/polls/{id}/reveal`) already validated the commitment match; we just reach it from a different starting point.
Upgrade
`git pull && make build`. No config or schema change.
Diff: `3630a7b..2a8859f`
🤖 Generated with Claude Code
v0.7.1 — UX polish, docs, green CI
Cosmetic/ergonomic release on top of v0.7.0. No behavior changes — every test from the prior tag still passes.
Highlights
UX
- Vote progress UI — four-stage pill track (Sign → Witness → Prove → Submit) beneath the Cast Vote button replaces the bare spinner. Idle → active (pulsing accent) → done. Eliminates the "is it hung?" feeling during the ~10s proving flow.
- Logo + favicon — Petri-net wrap mark (rounded container with place → transition arc in accent-green). Served from `/logo.svg` and `/favicon.svg`; rendered in nav across `/`, `/poll`, `/editor`.
- Open Graph + Twitter meta in the landing page so link previews show the mark and tagline instead of blank cards.
Docs
- Prerequisites line in README — Go ≥ 1.24 required; `forge`/`anvil`/`npm` optional and called out.
- Circuit-synthesis section — explains the Petri-net → ZK-constraint mapping with a small lookup table. The v0.7.0 differentiator, told in 10 lines.
- Architecture tree refreshed — `prover/synth/` added, `arc/` flagged as deprecated compat layer.
- Package doc comment on `internal/server` so `go doc` isn't empty.
CI
- Green build restored — the `wallet` Playwright project had `headless: false` left over from an earlier Synpress/MetaMask experiment. Linux CI has no XServer, so every build since Phase 1 started was red. Switched to headless (the pure-JS wallet-fixture.js needs no browser extension), and all 26 Playwright tests pass.
Upgrade
`git pull && make build` — no config migration, no API change.
Full changelog: `git log v0.7.0..v0.7.1` (3 commits: CI fix, DSL `requires` syntax, polish batch).
🤖 Generated with Claude Code
v0.7.0 — Circuit synthesis from Petri net schema
Headline: Hand-written gnark circuits are gone. Every ZK circuit for every ERC template is now generated from metamodel.Schema, closing the "Petri net as single source of truth" loop for ZK proofs alongside the existing Solidity + Foundry pipelines. See #1.
Highlights
Phase 2 — Circuit synthesis (gh#1)
-
prover/synth/package: emits Go source files containing gnark circuit struct +Define()methods directly frommetamodel.Schema. -
All 7 circuits synthesized with byte-identical constraint shapes (parity verified via gnark compile):
Circuit Constraints Public Private mint 662 6 2 approve 662 6 2 burn 14,647 5 41 voteCast 15,261 6 43 transfer 15,967 6 42 vestClaim 15,968 6 43 transferFrom 18,012 7 42 -
prover/circuits.godropped — the hand-written version (478 lines) retired;prover/*_gen.goare the sole source of circuit definitions. -
Schema metadata extensions (
metamodel.Schema):State.MerkleDepth,State.HashFunc,Action.Roles,Action.ZKOps. Allomitempty— existing schema CIDs unchanged. -
New ZKOp primitives:
NullifierBind,CommitmentBind,RangeCheck. Express cryptographic obligations that aren't arc-derivable (nullifier hashes, vote-commitment binding). -
Guard expression compiler:
<state>[<key>] >= <amount>patterns automatically becomeapi.ToBinaryrange checks; non-ZK conjuncts (zero-address checks etc.) are correctly ignored. -
Deterministic output:
make gen-circuits && git diff --exit-codeis a CI gate.
Phase 1 — Vote security + UX
- Fixed registration-bypass vulnerability: unregistered voters could previously cast votes because
if poll.RegistryRoot == ""guards skipped validation. Server now rejects votes with an empty registry (400) and counts registration slots (409 when exhausted). - Runtime
rt.Enabled()gate (slice 2.7): the vote handler now replays events through the Petri net Runtime and checkscastVoteenablement, replacing the earlier manual event-count counter. IntroducedregistrySlotsTokenStatesoregisterVoter → registrySlots → castVoteis a proper token flow. - Sparse Merkle tree in
public/merkle.js: fixes a ~2-minute main-thread hang on vote. Depth-20 tree build drops from ~2M mimcHash ops to O(N·D) (~6ms for a single-voter tree). modInversesign bug inpublic/dev-wallet.jssecp256k1: JS BigInt division truncates toward zero, breaking extended Euclidean for negative inputs. This caused wrong public keys → invalid signatures rejected by the backend. One-line fix.- EIP-6963 provider discovery in
public/poll.js: Trust Wallet and MetaMask coexisting no longer collide onwindow.ethereumlast-writer-wins. - Client-side signing test fixture:
e2e/wallet-fixture.jsinjects awindow.ethereummock that performs real secp256k1 via the pure-JS path. 26 Playwright tests (up from 19).
Tooling & infrastructure
bitwrap -synthesize <template> -output <path>: new CLI for regenerating circuits.make gen-circuits: regenerates all ERC templates.make test-e2e-wallet: headless e2e with the real-signing fixture.- WASM prover moved to Web Worker (no main-thread freeze during proving).
Known gaps (follow-up work)
.btwDSL → synthesizer naming convention (gh#1 residue): the DSL produces uppercase state names (BALANCES), the generators expect lowercase (balances). Runningexamples/erc20.btwthrough-synthesizecurrently returns "schema has no synthesizable actions". Wiring a case-insensitive lookup (or normalizing IDs atdsl.Build) is a small follow-up.- Slice 2.6 synth parity tests are now self-comparisons (no hand-written twin to compare against). Harmless but could be simplified to pure smoke tests.
Upgrade notes
- No config / migration changes. Deploy is
git pull && make build && restart. - Circuit names are stable (
mint,transfer, etc.) so no prover-service client changes needed. prover/circuits.gois gone; if external consumers importedMintCircuitetc. from that file, they now come fromprover/erc020_gen.goand friends — same package, same names.
Full commit history: 55 commits from v0.6.3..v0.7.0. git log v0.6.3..v0.7.0 --oneline for the full list.
🤖 Generated with Claude Code
v1.0.0 — .btw DSL + Full E2E Pipeline
bitwrap 1.0
Write Petri net models in .btw, get validated Solidity smart contracts.
.btw DSL
./bitwrap -validate examples/erc20.btw parse examples/erc20.btw
schema ERC20 (3 states, 5 actions, 10 arcs)
solidity ERC20.sol (5023 bytes), tests (2125 bytes), genesis (1083 bytes)
compile ok
test 10 passed
deploy 0x5FbDB2315678afecb367f032d93F642f64180aa3
PASS
- Full syntax: registers, functions, guards, events, arcs, nested maps
map[address,address]uint256shorthand for nested mappings#and//comments--output dirsaves generated Foundry project- Human-readable errors: type mismatches, unknown identifiers, reserved names, duplicate declarations
Validated Pipeline
Every generated contract is proven to compile, deploy, and pass tests:
| Template | Forge Tests | On-Chain |
|---|---|---|
| ERC20 | 10/10 ✅ | 5/5 ✅ |
| ERC721 | 10/10 ✅ | 5/5 ✅ |
| ERC1155 | 12/12 ✅ | 7/7 ✅ |
| ERC4626 | 10/10 ✅ | 1/5 |
| ERC5725 | 10/10 ✅ | 4/6 |
| Vote | 6/6 ✅ | 2/3 |
Three-Way Execution Parity
Go runtime, JavaScript runtime, and generated Solidity produce identical state for the same inputs. Tested in CI.
Dev Wallet
Test the full poll flow without MetaMask:
./bitwrap -port 8088 -dev
# open http://localhost:8088/poll?dev-wallet10 anvil accounts available via /api/dev/sign for multi-signer testing.
CI Pipeline
5 parallel jobs: lint, build, test, js-parity, playwright (19 browser tests).
What's New Since v0.9.0
.btwDSL with nested map support (map[k1,k2]v)- DSL guardrails: type checking, reserved names, guard validation, arc validation
bitwrap -validatecommand with--outputflag#comment supportGenerateProduction()strips test backdoors- Go/JS/Solidity execution parity (arc weights, read-arc detection)
- Fix secp256k1 signature recovery for Go 1.25
- Built-in dev wallet with 10 anvil accounts
- 19 Playwright e2e tests in CI
- 4 example .btw files with syntax reference
v0.9.0 — E2E Validated Solidity Generation
What's New
Validated end-to-end Solidity generation pipeline: every generated ERC template now compiles, deploys, and passes all generated Foundry tests.
E2E Testing Pipeline
- Foundry E2E (
make test-e2e): generates contracts for all 6 templates, compiles withforge build, deploys to local anvil, exercises transitions on-chain using Petri net reachability BFS - Playwright E2E (
make test-playwright): browser smoke tests for landing page, editor, polls UI, and all API endpoints - 100% forge test pass rate: 58/58 generated tests pass across ERC20, ERC721, ERC1155, ERC4626, ERC5725, Vote
On-Chain Validation
| Template | Forge Tests | On-Chain Fires |
|---|---|---|
| ERC20 | 10/10 ✅ | 5/5 ✅ |
| ERC721 | 10/10 ✅ | 5/5 ✅ |
| ERC1155 | 12/12 ✅ | 7/7 ✅ |
| ERC4626 | 10/10 ✅ | 1/5 (external assets) |
| ERC5725 | 10/10 ✅ | 3/5 (multi-step deps) |
| Vote | 6/6 ✅ | 2/3 (ZK proof by design) |
Codegen Fixes
- Fix ERC721/1155 variable shadowing (
approved→to/isApproved) - Fix ERC4626 missing
maxWithdraw/maxRedeemview functions - Fix ERC5725 event
memorykeyword, struct field, vesting helper names - Fix state variable initialization for non-zero
Initialvalues - Fix scalar arc weight (use literal 1 instead of
amountparameter) - Read-arc detection: skip decrements for read-then-write map patterns
- ERC721 balance arcs use explicit weight 1 (NFTs transfer exactly 1)
- Vote genesisgen now includes constructor args
Petri Net Reachability
The e2e test uses BFS over the Petri net model to find firing sequences that cover all transitions, then executes them against deployed contracts with multi-actor orchestration (owner, token holder, spender roles).
v0.8.0 — Production Ready
What's New since v0.7.0
- Live demo poll on landing page — fetches active polls inline, click to vote
- Wallet-Native Auth callout — MetaMask integration surfaced in the "Why ZK Voting?" section
- Deploy to Your Chain section — Foundry bundle download with 3-command workflow on the landing page
- Poll test coverage — 18 new tests covering poll creation, voting (duplicate nullifiers, closed/expired polls), and sealed-results enforcement
- CI pipeline upgraded —
go vet+ race detector + JS parity checks, all green - README rewritten — leads with ZK voting, full poll API docs, sealed results explanation, voting .btw DSL example
CI
| Job | Status |
|---|---|
| lint | go vet |
| build | make build |
| test | go test -race (18 poll tests + existing suite) |
| js-parity | MiMC + Merkle parity + JS syntax |
v0.6.3: ZK Polls
bitwrap v0.6.3
Anonymous voting with zero-knowledge proofs. Create polls, cast secret ballots, verify results — all from a Petri net model.
ZK Poll System
- Vote template — visual Petri net model for voting state machines (voterRegistry, nullifiers, tallies, pollConfig)
- VoteCast circuit — Groth16 proof of voter eligibility and valid choice without revealing identity or choice
- Secret ballots — vote choice hidden behind blinded commitment
mimcHash(voterSecret, choice), can't be brute-forced - Circuit-enforced bounds —
maxChoicespublic input rejects out-of-range choices at the proof level - 5 public inputs: pollId, voterRegistryRoot, nullifier, voteCommitment, maxChoices
Poll Lifecycle
- Create — wallet signature required (EIP-191 personal_sign), rate limited (5/hr per IP + wallet)
- Vote — ZK proof generated client-side, server re-verifies via gnark, tally updated in real-time
- Close — only the creator's wallet can close
- Results — event-sourced through the Petri net runtime:
State(t) = fold(apply, initialState, events[0..t])
Privacy Model
| Data | Visibility |
|---|---|
| Voter identity | Hidden (nullifier unlinkable to registry leaf) |
| Vote choice | Hidden (blinded commitment, never persisted per-vote) |
| Voter secret | Hidden (never leaves browser) |
| Vote count | Public |
| Per-choice tallies | Public (derived from event log) |
On-Chain Governance
IVerifierinterface with Groth16 proof verification incastVote- Contract stores
voteCommitments[nullifier], not plaintext choices - Foundry bundle: contract + verifier + tests + deploy script (8/8 tests pass)
- Constructor:
(voterRegistryRoot, maxChoices, verifier)
Poll UI
bitwrap.io/poll— create, vote, close, view resultsbitwrap.io/poll#deploy— download Foundry bundle or Solidity contract- MetaMask integration for poll creation and wallet-derived voter secrets
API Endpoints
| Method | Path | Purpose |
|---|---|---|
POST |
/api/polls |
Create poll (wallet sig required) |
GET |
/api/polls/{id} |
Get poll config + state |
POST |
/api/polls/{id}/vote |
Submit ZK-proven vote |
POST |
/api/polls/{id}/close |
Close poll (creator only) |
POST |
/api/polls/{id}/reveal |
Reveal vote (fallback) |
GET |
/api/polls/{id}/results |
Tallies from event log |
GET |
/api/polls/{id}/nullifiers |
Public audit log |
Size Limits
- Title: 200 chars, Description: 2000 chars
- Choices: 2–256 (matches circuit's 8-bit range proof)
- Voter commitments: 10,000 max
- Duration: up to 90 days