Reference.
Anatomy of a halo 2 Orchard proof
The 4992 bytes the Orchard simulator emits are 156 group-element or scalar slots of 32 bytes each. Halo 2 emits them in a fixed order determined by the PLONKish protocol structure. Fiat-Shamir challenges (theta, beta, gamma, y, x, x_1, x_2, x_3, x_4) are not written; the verifier re-derives them by hashing the transcript so far. The structure below is for a one-Action Orchard proof; multi-Action bundles multiply most regions by the number of actions.
-
Advice commitments. One curve point per advice
column. Orchard ships around ten advice columns; each is a
Pedersen commitment to that column's polynomial. Source:
halo2_proofs::plonk::prover::create_proof. -
Lookup permuted commitments. Two curve points
per lookup argument (
A'andS'). Orchard uses several lookups for table-driven range checks. - Permutation Z + lookup Z. Permutation argument running products, chunked by max_degree (the Orchard PK uses three chunks), plus one Z per lookup.
-
Vanishing h pieces. The quotient polynomial
h(X), split into chunks of degree ≤ n − 1. - Evaluations at x. One scalar per (column, rotation) pair: advice, fixed, permutation, lookup, h piece. This is where the gate equation balance is checked.
-
Multipoint reduction. A single batched IPA at
point
x_3combines openings at all rotation points using random-linear-combination challengex_4. -
IPA tail.
k = log2(n)rounds of(Li, Ri)curve points, one final scalara, one final blinder. For Orchard'sn = 211polynomial, that's 11 IPA rounds plus two final scalars.
SNARK families compared
Halo 2's proof size is the largest in the well-known SNARK families. The tradeoff is no trusted setup: every other line below requires a multi-party ceremony whose output, if compromised, lets an adversary forge proofs forever. Halo 2's Pasta cycle plus the inner-product argument gets transparency at the cost of larger proofs and slower verification.
| System | Curve | Proof size | Verify | Trusted setup |
|---|---|---|---|---|
| Groth16 | BN254 | 192 B | ~3 ms | per-circuit |
| PLONK (KZG) | BN254 | ~480 B | ~10 ms | universal |
| Bulletproofs | Ristretto | ~1.3 KB | linear in n | none |
| Halo 2 (Orchard) | Pasta (vesta) | 4992 B | ~30 ms | none |
| STARK (FRI) | field-only | ~40 to 250 KB | ~10 to 100 ms | none |
The 4992 bytes per Action is also why Orchard transactions are large on chain: a single shielded transaction with multiple actions can run to tens of kilobytes just for the proofs. That cost is what buys Zcash a shielded pool that needs no trusted setup ceremony. Sapling, the previous Zcash shielded pool, uses Groth16 and required a multi-party setup ceremony; if any one participant kept their secret randomness, that participant could forge unlimited Sapling proofs. Orchard's Halo 2 design eliminates that attack surface entirely at the cost of larger, slower-to-verify proofs.
Orchard terminology
Terms used throughout the Orchard page. The Zcash protocol specification defines them across §3 (Concepts) and §4 (Abstract Protocol); the Action ZK relation that ties them together is section 4.18.4.
- anchor
- The root of the Orchard note-commitment Merkle tree at the moment the prover constructed the spend. A consensus node accepts the proof only if this root appears in a recent enough on-chain block, which is what prevents proving spends against a non-existent tree state.
- cv_net
- The Pedersen value commitment to the net of (spend value)
minus (output value), blinded by a uniformly sampled trapdoor
rcv. The binding signature later proves the prover knew the total trapdoor across all Actions, which is how Orchard enforces value balance without revealing it. - nf_old
- The nullifier:
nf = ExtractP([PRFnfOrchardnk(ρ) + ψ]·KOrchard + cm). A deterministic function of the spent note plus the spender's viewing key: Poseidon-based PRF on ρ, blinded by ψ, fixed-base scalar mul ofKOrchard, point-add with the note commitment, take the x-coordinate. Adding the result to the Orchard nullifier set prevents double-spending. Without the viewing key no outside observer can link the nullifier to the spent note; with it, the spender can prove the link. - rk
- The spend-authorization verification key
(
ak) randomized by a per-spend trapdoorα. The randomization unlinks this Action's signature from the spending key's underlying public key while still letting the spender sign with the corresponding randomized signing key. - cmx
- The output note's commitment, x-coordinate only. The Sinsemilla-derived note commitment is a Pasta curve point; Zcash transactions carry only the x-coordinate to save space and the verifier reconstructs y.
- Sinsemilla
- A SNARK-friendly hash function used in Orchard for note commitments and the Merkle tree. Designed for a small circuit footprint when proved inside Halo 2.
- Poseidon
- The other SNARK-friendly hash used in Orchard, for the nullifier derivation. Optimised for low arithmetic-constraint cost.
- RedPallas
- The signature scheme over the Pallas curve used for both the
binding signature and the per-Action spend-auth signature.
Rerandomisable (the
rkmechanism), compatible with batch verification. - FullViewingKey
- The capability to see all incoming and outgoing notes for a
given spending key, without the ability to spend. Decomposes
into
ak(auth) +nk(nullifier-deriving) +ovk(outgoing-viewing).
Frequently asked questions
What does "without witness" actually mean?
The simulator never has access to a specific spending key, note, or Merkle path that someone wants to keep private. It samples its own arbitrary witness (which happens to be a valid one for some statement) and proves that. Because the Orchard Action relation admits an enormous family of valid witnesses for any given public Instance, an observer who sees only the public Instance cannot tell which witness the prover used. That property is what makes the verifier learn nothing about which specific note was spent.
Why is the proof 4992 bytes when Groth16 proofs are 192 bytes?
Halo 2 is transparent: no per-circuit or universal trusted setup. The construction trades proof size and verification time for that property. Groth16 needs a per-circuit ceremony whose toxic waste, if retained, lets an adversary forge proofs forever. Zcash chose Halo 2 for Orchard specifically to eliminate this attack surface.
Is this the same code Zcash uses on mainnet?
The proof-generation path calls
orchard::circuit::Proof::create and
Proof::verify directly. Those are the same
production wrappers around
halo2_proofs::plonk::create_proof and
verify_proof that
zcashd and
zebrad link against. The simulator's contribution
is sampling a witness uniformly and driving the production
prover with it. A small downstream patch exposes six read-only
accessors (ProvingKey::{inner,params},
VerifyingKey::{inner,params},
Instance::to_halo2_instance,
SigningMetadata::{alpha,is_dummy}) so a
programmable transcript can be plumbed in and external
verifiers can build the per-column instance scalars; otherwise
the only available path uses Blake2b.
Could I use this to fake a Zcash transaction?
No. The "what would happen on mainnet" section on the Orchard page lists the five non-cryptographic reasons a consensus node would reject it. The simulator breaks no cryptographic property; it relies on the protocol doing more than cryptography for soundness against double-spend.
Why does the simulator return random-looking byte sequences?
That's the whole point. Honest Halo 2 proofs are uniform over the proof byte space (conditional on the public Instance), and the simulator's output is statistically indistinguishable from honest output. If a reader could see a pattern in the bytes, the protocol would leak. The two-proofs-same-witness panel on the Orchard page makes this concrete: two independent proofs of the same statement share roughly half their bytes by coincidence, which is exactly what zero-knowledge requires.
Why does Foundations mention a "byte-level no-witness construction" that isn't here?
For relations with a unique witness, the WI-to-ZK collapse on multi-witness relations does not apply: the simulator can't sample a different witness, so it has no way to make its output transcript distribution match the honest prover's. The only escape is to construct the proof bytes directly from the public statement plus programmed challenges, never sampling a witness in the first place — a byte-level emit that walks the verifier's check equations and solves for each forced value.
For relations like Orchard's, with many valid witnesses per public Instance, the construction is not needed. The simulator samples a uniform witness internally, runs the honest prover with a programmed Fiat-Shamir transcript, and outputs the result. Three facts together close the ZK gap: (i) Pasta-Pedersen polynomial commitments are perfectly hiding under uniform blinders, so the simulator's commitments carry no information about which witness was sampled; (ii) the Fiat-Shamir challenges are programmed rather than hashed from witness-dependent data; (iii) the witness was sampled uniformly from the witness set, so its marginal distribution conditional on the public statement matches the honest prover's. Composed, the simulator's output transcript is computationally indistinguishable from honest in the random-oracle model — the textbook ZK property.
No separate byte-level construction is implemented in this
crate, because neither the toy MulCircuit relation
(every nonzero a is half of a valid witness) nor
the Orchard Action relation (any valid spending-key /
note / Merkle-path tuple) is unique-witness. Closing the
byte-level path is research-status work whose payoff is for
unique-witness relations not deployed here.
References
Foundational papers for the constructions exercised on these pages:
- Halo: Recursive Proof Composition without a Trusted Setup. Bowe, Grigg, Hopwood. 2019. eprint.iacr.org/2019/1021. The construction that made transparent recursive SNARKs over Pasta curves practical. Halo 2 is the production implementation.
- PLONK: Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of Knowledge. Gabizon, Williamson, Ciobotaru. 2019. eprint.iacr.org/2019/953. The PLONKish arithmetisation Halo 2 inherits: permutation argument, lookup arguments, custom gates.
- Bulletproofs: Short Proofs for Confidential Transactions and More. Bünz, Bootle, Boneh, Poelstra, Wuille, Maxwell. 2018. eprint.iacr.org/2017/1066. The inner-product argument Halo 2 uses for polynomial commitment opening. Section 3 contains the IPA the simulator programs.
- Witness Indistinguishable and Witness Hiding Protocols. Feige, Shamir. 1990. dl.acm.org/doi/10.1145/100216.100272. The original definition separating WI from ZK. Section 4 contains the witness-uniformity argument the Orchard relation satisfies.
- Zcash protocol specification. zips.z.cash/protocol/protocol.pdf. Section 3.7 introduces Action transfers; section 4.6 defines the Action description data structure; section 4.18.4 is the Action ZK relation the simulator proves. Sections 4.14 and 4.15 specify the binding and spend-authorization signatures abstractly; section 5.4.7 instantiates them as RedPallas.
- ZIP 244: Transaction Identifier Non-Malleability. zips.z.cash/zip-0244. The sighash derivation mainnet nodes use to authenticate the bundle's signatures.
- The Halo 2 book. zcash.github.io/halo2/. Reference for proof structure, circuit API, and Fiat-Shamir conventions.
Running it yourself
Every part of these pages is reproducible from a clean checkout.
The crate is pure Rust plus a small JS glue layer. The
real-Orchard path uses the upstream orchard and
halo2_proofs crates plus a small downstream patch
that exposes six read-only accessors
(ProvingKey::{inner,params},
VerifyingKey::{inner,params},
Instance::to_halo2_instance,
SigningMetadata::{alpha,is_dummy}).
Fast tests (about 3 seconds)
cargo test --features orchard
9 tests exercising the IPA, MulCircuit halo2 paths, and the
proof byte-structure measurement. None of these run the real
Orchard prover (gated under #[ignore] so the
default run stays fast).
Real Orchard tests (about 2.5 minutes)
cargo test --features orchard -- --ignored
9 tests driving the production Orchard prover end-to-end on sampled witnesses: single Action verifies, multi-Action bundle verifies, signed bundle verifies, ROM-programmable variant verifies, tampered proof rejected, wrong Instance rejected, arbitrary-value spend verifies, byte distributions uniform.
The single-threaded web demo
PROFILE=release FEATURES=wasm-orchard bash build-wasm.sh
(cd web && python3 serve.py 8000)
xdg-open http://localhost:8000
Builds a ~4 MB single-threaded WASM module and serves the
static HTML/JS at localhost:8000. No backend;
everything runs in the browser tab.
The parallel web demo (4 to 8 times faster prove)
rustup component add rust-src --toolchain nightly
bash build-wasm-parallel.sh
(cd web && python3 serve.py 8000)
xdg-open http://localhost:8000
Builds a ~9 MB parallel WASM module (~7 MB once shrunk by
wasm-opt -O3) via
wasm-bindgen-rayon so halo2's FFT and MSM steps
inside Proof::create dispatch across a
SharedArrayBuffer-backed pool of inner Web Workers.
Requires a nightly Rust toolchain and the bundled
serve.py, which sets the
Cross-Origin-{Opener,Embedder}-Policy headers the
browser needs to enable SharedArrayBuffer. The page
auto-detects the parallel build at load time and falls back to
the single-threaded one if pkg-parallel/ isn't
present. Compute remains 100% client-side; the headers are pure
browser-security metadata.
The CLI
cargo run --features orchard --release --bin orchard-cli -- --seed 42 --pretty
Runs the same simulator from the command line, no browser. The JSON output matches the schema the Orchard page's "Download proof as JSON" button produces, so a CLI-generated proof and a browser-generated proof can be diffed structurally or pasted into either environment for verification.