Cache circuit-side padded-note IMT proofs#32
Merged
Conversation
build_governance_pczt stores dummy_nullifiers via make_dummy_note, which constructs each padded slot's note with NoteValue::from_raw(1) so Keystone renders the padded actions in the PCZT. The delegation circuit, however, constructs its padded slots with NoteValue::ZERO (voting-circuits' builder fills empty inputs with zero-value notes so they don't perturb the ballot weight math). The two notes share rho/rseed/address but differ in value, so their Orchard nullifiers differ. Pre-#31, the bundle builder's PirImtProvider could fall back to fetching from PIR on demand, which silently bridged the gap — the IMT lookup was always made for the 0-zat nullifier, the cache miss triggered a fresh fetch, and the stale 1-zat dummy_nullifiers we'd cached were never consulted. After #31 + fb31bb7 ("Remove submit-time delegation PIR fallback"), the provider is initialised purely from the precompute cache, so the mismatch surfaces as `IMT error: missing precomputed IMT proof for nullifier <hex>` and the proof never builds. Fix in precompute_delegation_pir + build_and_prove_delegation: derive the 0-zat circuit-side padded nullifiers from the stored padded_note_secrets (rho, rseed) plus the FVK extracted from the notes' UFVK, exactly the way voting-circuits/.../delegation/builder.rs:200-213+230 does. Both call sites now key the IMT cache off these, so the bundle builder's lookup hits. precompute_delegation_pir gains a network_id parameter so it can decode the UFVK; build_and_prove_delegation already had it and threads it through. The dummy_nullifiers column is left intact — it's still part of the PCZT/ Keystone metadata flow.
Bundles the circuit-side padded nullifier fix shipped in this branch. Matches the team's release-commit pattern (cf. 461fa59 "Release zcash_voting 0.2.2").
czarcas7ic
approved these changes
May 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The delegation PIR precompute introduced in #31 was caching IMT non-membership proofs keyed by the wrong nullifier. Now that the submit-time PIR fallback is gone (
fb31bb70), the cache miss surfaces directly asIMT error: missing precomputed IMT proof for nullifier <hex>and the proof never builds.Root cause:
build_governance_pcztpopulatesdummy_nullifiersviamake_dummy_note, which constructs each padded-slot note withNoteValue::from_raw(1)so Keystone renders the padded actions in the PCZT. The delegation circuit, however, fills its padded slots withNoteValue::ZERO(pervoting-circuits/.../delegation/builder.rs:200-213+230). The two notes sharerho/rseed/addressbut differ in value, so their nullifiers differ. Pre-#31 this didn't fire because the bundle builder'sPirImtProviderfetched the (0-zat) proof on demand viapir_client, silently bridging the gap. Afterfb31bb70removed that fallback, the only IMT data the provider sees is the precompute cache — keyed by the wrong nullifier.Fix
New
padded_nullifiers_for_circuithelper derives the 0-zat circuit-side nullifiers from the storedpadded_note_secrets(rho, rseed) plus the FVK extracted from the notes' UFVK. Bothprecompute_delegation_pirandbuild_and_prove_delegationnow use it instead ofload_dummy_nullifiers.precompute_delegation_pirgains anetwork_idparameter (needed for UFVK decode); the FFI signature change is in valargroup/zcash-swift-wallet-sdk#TBD.The
dummy_nullifierscolumn is left intact — it remains part of the PCZT/Keystone metadata flow and may still be referenced by future Keystone-side work.Verification
cargo check -p zcash_votingclean against local-wired tree.A0DFB6105AFE8956E84818E644D43B612DEC25CF63C5B4C996A2A3EF562DC6B4lands on chain. Precompute reportscached=0 fetched=5for a 4-real-note bundle (4 real + 1 padded), then submit hits the cache (5 cached, 0 fetched) and the proof builds in ~2s.Release
Bumps to 0.2.4 in the second commit. Once merged + published, valargroup/zcash-swift-wallet-sdk#TBD picks it up.