The circuit
The statement it proves, the six checks that enforce it, the Orchard constraint left out on purpose, the measured shape, the adversarial tests, and the two limits.
There is a note commitment in the Ironwood tree at anchor X, its value is at least N, I hold the key that can spend it, its tag is T, and this claim is for Solana account A.
Nothing about which note leaves the proof. Not the commitment, not its position in the tree, not its value, not the address it was sent to, and not how far above the threshold it actually sits.
Constraints
Six checks hold the statement up. Three are Zcash's own, reused chip for chip from the Orchard Action circuit.
Two are Zolar's, because a Zcash transaction has no reason to make them. One reaches a Zcash guarantee by a different route.
The reuse is possible because the orchard crate publishes the internal chips of its Action circuit behind a feature flag built for this shape of statement: proving something about a note you hold without spending it.
Ironwood changed none of the three. It kept Halo 2 and the repaired Orchard circuit, and the crate says in its own source that the circuit is pool-agnostic.
- Note commitment integrity Zcash
- The committed note is the note being described.
- Merkle path validity Zcash
- The commitment sits in a tree that hashes to the given anchor.
- Address integrity Zcash
- The note is addressed to this key, not merely observed by it.
- Value clears the threshold Zolar
- A transaction asks about a balance and never about a floor, so Zcash had no reason to build this.
- Tag integrity Zolar
- A per-note handle for anti-replay that publishes nothing Zcash will later reveal.
- Spend authority reached differently
- Proved in-circuit as one fixed-base multiplication, rather than by checking a signature outside it.
The dropped constraint
Orchard's gate reads "either the old value is zero, or the root equals the anchor". The disjunction exists so a transaction can carry dummy spends, because a zero-value note needs no path into a real tree. Copied here it is a door-sized hole: witness a value of zero, witness any path at all, and the Merkle check evaporates.
So the check here is unconditional. There are no dummy notes at this layer and no transaction to balance, and one test is named after the hole it closes.
The tag
The tag is a per-note handle for anti-replay, and it is deliberately not the nullifier. Solana has to be able to see the same note used twice, or one note mints unlimited attestations and every sybil-resistance claim built on this layer is a lie.
Zcash already has a unique-per-note value for that job, and publishing it would be wrong: a nullifier appears on the Zcash chain the moment the note is spent, so an attestation carrying one promises that on the day you spend, anyone can link that spend to your Solana account.
- Derived through a second hash Poseidon
- The inner value is the first step of Zcash's own nullifier derivation, so publishing it would publish a preimage. The outer hash separates them.
- Stable per note, distinct between notes stable
- Inherited from the same pair that makes a nullifier unique.
- The domain constant lives in the verifying key in vk
- It is not a public input, so no verifier can forget to check it and no prover can choose it.
- Independent of the Solana account by design
- That independence is what makes it useful. Two accounts cannot each mint from one note, because both produce the same tag. Account binding is a separate public input, deliberately.
Spend authority
Without this check the statement would rest on a viewing key, which is weaker than it reads. A full viewing key can be handed out, so one custodian's balance could be proved by everyone they gave it to.
Zcash reaches the same guarantee by publishing a re-randomised key and checking a signature outside the circuit. That route ends at a Pallas signature verification, and Solana has no Pallas syscall at all.
Proving knowledge of the spending key inside the circuit costs one fixed-base multiplication and settles it here.
Measured shape
Measured on a laptop with four performance cores. The single-threaded column is the one to plan against.
- Prove, multicore 0.62 s
- On a laptop, with threads available.
- Prove, single thread 2.10 s
- The number to plan against. WebAssembly has no threads without atomics, so a browser starts here.
- Verify, native Halo 2 8 ms
- The native check, not the Solana path.
- Proving key generation 1.27 s
- Once, at startup.
- Circuit size k = 11
- 2,048 rows. A k of 10 does not fit.
- Public inputs 5
- Out of the twenty that fit in a single Solana transaction.
- Proof size 4,960 B
- 155 field and group elements. This is what the proof carries, and it is not what verifying it costs: that is paced by the reference string, which is 2,048 terms at this k.
- Columns 10 · 14 · 1
- Advice, fixed and instance, plus 44 selectors and 4 lookup arguments.
Tests
There are thirteen, and every one asserts something that must fail. A circuit that proves the honest case is easy and worth nothing on its own, because the Orchard bug sat inside a circuit whose honest path was flawless for four years.
These reach past the API guards and fill the circuit directly, the way an attacker would.
- Claiming more than the note holds
- Bypasses the guard and lies to the circuit itself.
- A threshold that is not an unsigned integer
- Negative one as a threshold, whose difference from any small value lands back inside a 64-bit range.
- A zero-value note skipping the Merkle check
- The Orchard hole that was left out on purpose.
- The tag equalling the inner hash
- Proves the outer hash is wired, not merely documented.
- The tag depending on the Solana account
- Sybil resistance. One note must produce one tag, whoever is claiming it.
- A proof transferring between accounts
- Run against a real proof rather than a mock prover.
Limits
Three things this proof does not do. All three are real, all three are deliberate, and all three belong on the surface before anyone is shown a demo.
- It does not prove the note is unspent
- Only the nullifier can answer that, and publishing the nullifier is exactly what the tag exists to avoid. What bounds the claim instead is the age of the anchor. An attestation is a statement about one moment, and the interface prints which moment.
- One note, not a balance
- A holder with ten one-ZEC notes can prove one, not five. Aggregating notes means a larger circuit, and that is later work rather than a demo-week correction. Until it exists, any copy promising a balance is wrong.
- Nothing has reached Solana
- The circuit now runs against real notes: the scanner builds the commitment tree, takes the path, and the proof is verified before it is shown. What does not exist is everything after that. No attester signs it, no program reads it, and no attestation has ever been written.
Architecture
Where each part runs, why the wrapper that was supposed to join them cannot exist, what the attester quorum replaced it with, and why Zolar serves the Ironwood pool only.
Measurements
Verification cost on Solana, the curve gap, why the wrapper cannot be built, proving times in a browser, wallet sync, and the infrastructure that is running.