Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion voting-circuits/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
### Changed

- Narrowed internal module, gadget, and helper visibility so only curated circuit/prover APIs remain public.
- Update delegation padding notes to use synthetic, IVK-bound padding points with custom derivation, avoiding reuse of ordinary Zcash mainnet diversified-address indices.
- Update delegation padding notes to use synthetic, IVK-bound padding points with direct Orchard low-level primitive calls, avoiding reuse of ordinary Zcash mainnet diversified-address indices.

### Migration

Expand Down
4 changes: 2 additions & 2 deletions voting-circuits/src/delegation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,8 +455,8 @@ The result is constrained to the public input offsets in `GOV_NULL_PUBLIC_OFFSET
- **"Why are padding (dummy) notes bound to the real ivk, and why not a real Orchard address?"** — Padding slots must still pass condition 11 (`pk_d = [selected_ivk] * g_d`) using the ivk derived in condition 5; otherwise the circuit would need a per-slot bypass that real notes could also exploit. Instead of constructing a real Orchard address with `fvk.address_at(...)` (which would burn an otherwise-spendable diversifier index on every proof and tie padding to a real receive-capable address), the builder synthesizes a padding pair outside the Orchard address API:
- `g_d_pad = hash_to_curve("shielded-vote/padding-v1")(slot_index_le_bytes)` — domain-separated from Orchard's `DiversifyHash` and not derived through Orchard diversifier selection, so `(g_d_pad, pk_d_pad)` is intentionally not an `orchard::Address`.
- `pk_d_pad = [ivk_external] * g_d_pad` — satisfies condition 11 by construction. Padding always uses the **external** ivk (`is_internal = false`), independent of the real notes' scopes, so the `q_scope_select` mux selects `ivk_external` and the equality check holds.
- `rho`/`rseed` are sampled fresh (or replayed from `PrecomputedRandomness.padded_notes` for ZIP-244 sighash determinism), `psi`/`rcm` are derived from `rseed` exactly as Orchard does, and the note commitment / real nullifier are computed off-circuit by builder helpers that mirror Orchard's `NoteCommit` and `DeriveNullifier` bit-for-bit.
- `rho`/`rseed` are sampled fresh (or replayed from `PrecomputedRandomness.padded_notes` for ZIP-244 sighash determinism), `psi`/`rcm` are derived from `rseed` exactly as Orchard does, and the note commitment / real nullifier are computed off-circuit with Orchard's exposed low-level APIs.

The padding note still has `v = 0`, so condition 10 skips the Merkle root check via `v * (root - nc_root) = 0` and the auth path can be `MerklePath::dummy(...)`. Conditions 9 (note commitment integrity), 11 (address ownership against `ivk_external`), 12 (real nullifier), 13 (IMT non-membership against `nf_imt_root`), and 14 (alternate nullifier publication) all run unconditionally on the synthesized values. The published `gov_null` for a padding slot is harmless — the consuming protocol can ignore alternate nullifiers from zero-value slots or treat them as no-ops.

Because the in-circuit `NoteCommit` and `DeriveNullifier` consume the witnessed `(g_d, pk_d, v, rho, psi, rcm)` directly and re-derive the commitment / nullifier from those bits, the synthetic `g_d_pad` is opaque to the circuit — it is just a domain-separated point chosen outside Orchard's address derivation flow. The `NoteSlotWitness` keeps `g_d`/`pk_d` typed as `NonIdentityPallasPoint` (carrying Orchard's non-identity invariant so an accidental identity fails at the construction site rather than only at proof time via `NonIdentityPoint::new`), but `cm` is relaxed to plain `pallas::Point` because Orchard's `NoteCommitment` newtype has a private constructor and synthetic padding commitments cannot be wrapped — condition 9 re-derives and constrain-equals `cm`, so a malformed point would still be rejected at proof time.
Because the in-circuit `NoteCommit` and `DeriveNullifier` consume the witnessed `(g_d, pk_d, v, rho, psi, rcm)` directly and re-derive the commitment / nullifier from those bits, the synthetic `g_d_pad` is opaque to the circuit — it is just a domain-separated point chosen outside Orchard's address derivation flow. The `NoteSlotWitness` keeps `g_d`/`pk_d` typed as `NonIdentityPallasPoint` (carrying Orchard's non-identity invariant so an accidental identity fails at the construction site rather than only at proof time via `NonIdentityPoint::new`), but `cm` is relaxed to plain `pallas::Point` because the circuit consumes the underlying commitment point — condition 9 re-derives and constrain-equals `cm`, so a malformed point would still be rejected at proof time.
Loading
Loading