Skip to content

Add ZSA circuit - #546

Open
ConstanceBeguier wants to merge 59 commits into
zcash:mainfrom
QED-it:zsa_circuit_v1
Open

Add ZSA circuit#546
ConstanceBeguier wants to merge 59 commits into
zcash:mainfrom
QED-it:zsa_circuit_v1

Conversation

@ConstanceBeguier

@ConstanceBeguier ConstanceBeguier commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

This PR lays the groundwork for ZSA support: notes, value commitments, and the Action circuit are now parameterized by an asset type, and a new OrchardCircuitVersion::ZSA circuit variant exists alongside the current circuit (now called "Vanilla" internally).

None of this is reachable yet through the builder: every public constructor still hard-codes the zatoshi asset and zsa_enabled = false, so bundle construction, encoding, and verification are unchanged.

Circuit

  • Add the ZSA circuit
  • Split circuit.rs into circuit_vanilla.rs and circuit_zsa.rs to support both circuit variants
  • Move derive_nullifier and value_commit_orchard out of gadget.rs to keep the file at a reasonable size

Struct changes (new fields to support ZSA)

  • Add AssetBase
  • Add asset and rseed_split_note to Note
  • Add split_flag to SpendInfo
  • Add zsa_enabled to Flags
  • Add asset to OutputInfo

Function changes

  • NoteCommitment::derive now takes asset as a parameter and evaluates the note commitment according to the asset (zatoshi or not)
  • Add ValueCommitment::derive_with_asset, which binds the commitment to an asset other than zatoshi
  • Proof::expected_proof_size now takes circuit_version as a parameter (the Vanilla and ZSA circuits do not have the same proof size)

Refactor

  • Move OrchardCircuitVersion out of the circuit module to avoid feature gating, keeping proof-size validation available without the circuit feature flag

I have left "TODO ZSA" markers for things to be handled in a follow-up PR. For example, in Note::from_parts, asset is hardcoded to zatoshi with the comment // TODO ZSA: asset should be a param, not hardcoded here.

ConstanceBeguier and others added 30 commits August 5, 2026 07:52
They are based on ZSA bundle which is not implemented in this PR.
And fix some TODO ZSA
#### Simplify the Circuit type and unify circuit configuration

Net -200 lines, no new public API breakage.

- Make `Circuit` a struct again instead of an enum: the common witnesses
plus optional ZSA witnesses (`Value::unknown()` for Vanilla versions).
This removes a breaking API change, the variant/version consistency
checks, and the `as_zsa().expect(...)` pattern. The circuit version is a
single field, and `CircuitVanilla`/`CircuitZsa` are built internally at
keygen/prove time.
- Share `configure` between the two circuit variations via a single
`configure_circuit(meta, is_zsa)` which builds everything common and
dispatches the `q_orchard` gate to `configure_vanilla_orchard_gate` or
`configure_zsa_orchard_gate`.
- Add a `zsa_keygen` test, the first coverage of
`VerifyingKey::build(ZSA)` and `CircuitZsa::configure`.

---------

Co-authored-by: Constance Beguier <constance@qed-it.com>
ConstanceBeguier and others added 29 commits August 25, 2026 11:36
- Tests for the `enable_zsa` key-support guards, covering all three
places that reject a ZSA statement under a key whose circuit version
cannot constrain it: `Proof::create`, `Proof::verify` and
`BatchValidator::add_bundle`.
- CHANGELOG: note that `derive_nullifier`, `commit_ivk` and
`note_commit` moved out of `circuit::gadget`. They were feature-public,
so the old paths breaking is an `unstable-voting-circuits` API change.
Groups all feature-only entries under one bullet.
- `Circuit::to_vanilla` returns `Err(Error::Synthesis)` instead of
panicking when a `Circuit` carries known ZSA witnesses for a Vanilla
circuit version. `Proof::create` returned that error for this case
before the circuit types were unified.
- `#[non_exhaustive]` on `OrchardCommitDomains`, matching
`OrchardHashDomains`. Matches on it already break in this release over
the new `NoteZsaCommit` variant, so requiring a wildcard arm now costs
downstream nothing.
from_action_context_unchecked asserts that spend.split_flag matches the
presence of the note's split seed, for every circuit version. Only the
Vanilla-specific panics were documented.
No constructor produces a note with a split seed, so the branch was
unreachable and untested. The test sets rseed_split_note directly and
pins both halves of the derivation: psi from the split seed, and the
NULLIFIER_L offset over the original note's commitment.
added a doc and a new test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants