Skip to content

Use Creator::build_from_parts for governance PCZT construction#1

Merged
greg0x merged 1343 commits into
mainfrom
subtree/librustvoting/2026-03-12
Mar 12, 2026
Merged

Use Creator::build_from_parts for governance PCZT construction#1
greg0x merged 1343 commits into
mainfrom
subtree/librustvoting/2026-03-12

Conversation

@greg0x
Copy link
Copy Markdown
Contributor

@greg0x greg0x commented Mar 12, 2026

Switch governance PCZT construction from manual Creator::new() + set_orchard() to Creator::build_from_parts(), matching the same code path the wallet transaction builder uses.

The previous approach created an empty PCZT shell and then replaced the orchard bundle via set_orchard(). This meant the PCZT lacked proper tx version and network metadata that Keystone needs for correct sighash computation. build_from_parts populates all of this from PcztParts, including version selection based on the consensus branch ID.

Changes

  • action.rs: Replace Creator::new() + set_orchard() with Creator::build_from_parts(PcztParts { .. }), deriving TxVersion and Network from the consensus branch ID and coin type
  • librustvoting/Cargo.toml: Add zcash_primitives dep (for PcztParts, TxVersion), enable zcp-builder feature on pczt
  • Cargo.toml (workspace): Add zcash_primitives to the patch set (needs governance-extensions branch)

p0mvn and others added 30 commits February 27, 2026 19:04
Lower share reveal circuit K from 14 to 11
The identical Merkle conditional swap gate + Poseidon path synthesis
loop was duplicated across three circuits (vote_proof condition 1,
share_reveal condition 1, delegation/IMT condition 13). Extract into
`circuit::poseidon_merkle` with a reusable `MerkleSwapGate` and a
generic `synthesize_poseidon_merkle_path<DEPTH>()` function.

Also removes the duplicated local `assign_free_advice` helpers from
vote_proof and share_reveal in favour of the existing
`orchard::circuit::gadget::assign_free_advice`.

Net: ~340 lines of duplicated circuit logic replaced by a single
~90-line shared gadget.

Made-with: Cursor
7 tests using a minimal depth-4 test circuit with MockProver:
- position_zero_valid, position_nonzero_valid, all_right_child_valid
- wrong_root_fails, wrong_leaf_fails, wrong_sibling_fails,
  wrong_position_fails

Made-with: Cursor
extract shared Poseidon Merkle tree gadget
- Skip remaining bundles now shows a confirmation alert with locked-in/giving-up
  ZEC amounts instead of inline warning text
- After signing a bundle, return to idle state instead of auto-showing QR for
  the next bundle — user taps "Confirm with Keystone" to start each bundle
- Fix progress bar after skip: use signed bundle count as divisor and delete
  skipped bundles from DB so proof_generated works correctly on resume
- Recalculate votingWeight after skip to reflect only signed bundles' weight
  on the governance page
- Filter wallet notes by account (seed fingerprint + account index) so Keystone
  and hotkey accounts show only their own notes and eligible balance
- Navigate Keystone users directly to delegation signing screen instead of
  briefly flashing the proposal list
- Skip remaining bundles now shows a confirmation alert with locked-in/giving-up
  ZEC amounts instead of inline warning text
- After signing a bundle, return to idle state instead of auto-showing QR for
  the next bundle — user taps "Confirm with Keystone" to start each bundle
- Fix progress bar after skip: use signed bundle count as divisor and delete
  skipped bundles from DB so proof_generated works correctly on resume
- Recalculate votingWeight after skip to reflect only signed bundles' weight
  on the governance page
- Filter wallet notes by account (seed fingerprint + account index) so Keystone
  and hotkey accounts show only their own notes and eligible balance
- Navigate Keystone users directly to delegation signing screen instead of
  briefly flashing the proposal list
Keystone multi-bundle UX improvements and account-scoped note filtering
Drop raw tier data after YPIR construction — the YPIR server copies
everything into its own db_buf_aligned during YServer::new(), so keeping
the source bytes alive wastes ~6 GB. OwnedTierState now takes &[u8]
instead of Vec<u8> and no longer retains the data.

Also clear hint_0 Vec<u64> from OfflinePrecomputedValues after extracting
the hint bytes (hint_0 is unused during online query answering), and use
Bytes instead of Vec<u8> for HTTP-served data to avoid cloning ~112 MB
per hint request.

Row debug endpoints (/tier1/row/:idx, /tier2/row/:idx) now read from
disk instead of indexing into in-memory data.

Same fixes applied to the standalone pir-server binary, which previously
used Box::leak for data that was never needed again.
Replace the fixed-interval ticker in the helper processor with
exponentially-distributed random sleep intervals so that share
submissions form a Poisson process, preventing an observer from
correlating submission patterns with share readiness.

- Processor.Run() now samples inter-wake-up times from Exp(1/meanInterval)
  using crypto/rand instead of time.NewTicker
- Default MaxConcurrentProofs changed from 2 to 1 (sequential processing)
- Concurrency test updated to assert sequential execution

Made-with: Cursor
Poisson-distributed share processing for timing privacy
- Add Layer 3 intra-batch jitter (exponential, half the inter-cycle mean)
  so multiple shares becoming ready in the same cycle are not submitted
  as a burst. Deadline bypass skips jitter when <60s remain.
- Extract shared exponentialDelay() sampler from processor for reuse.
- Remove delay_seconds from share-scheduled log to prevent log-based
  timing correlation.
- Fix loadShare to include vote_end_time so the deadline bypass in
  processBatch has the data it needs.
- Change ProcessInterval default from 5s to 30s to match documentation.
- Add QueuedShare.VoteEndTime field.
- Add sdk/internal/README.md documenting the three-layer delay model.

Made-with: Cursor
Exponential sampling can produce near-zero delays (~1 in 700 shares
with a 12h mean), making those shares trivially linkable to the voter.
Add a configurable min_delay floor (default 90s) applied before the
deadline cap, so no share is ever submitted sooner than the floor
after receipt. The cap still takes precedence when the deadline is
imminent.

Made-with: Cursor
An observer polling the status endpoint could watch pending/submitted
counts change and narrow the submission time of individual shares,
especially in low-traffic rounds. The endpoint now returns only
{"status":"ok","tree":{...}} — enough for health probes (iOS circuit
breaker) and admin tree monitoring, without leaking queue state.

Made-with: Cursor
Helper privacy hardening: intra-batch jitter and log sanitization
…bility

c1_x is a public input (posted on-chain), so an observer who knows the
vote commitment tree leaves can enumerate all (vc, share_index, c1_x)
triples and brute-force link nullifiers to vote commitments in O(N*16).

Replace c1_x with the share commitment blind factor: it is never posted
on-chain, is already constrained in-circuit via condition 4
(share_comm = Poseidon(blind, c1_x, c2_x)), and is transitively bound
to the vote commitment through shares_hash. The two Poseidon usages are
domain-separated (ConstantLength<3> vs ConstantLength<4>).

Made-with: Cursor
zkp #3: use blind in share nullifier instead of public ciphertext coordinates
- Validate both compressed Pallas points via UnmarshalCiphertext in
  VerifyShareRevealProof before stripping sign bits; closes the gap
  where a flipped sign bit passes ZKP verification (circuit binds only
  x-coordinates) but corrupts HomomorphicAdd at tally time.
- Apply the same first-share validation in keeper AddToTally so a
  malformed baseline is rejected before it reaches the KV store.
- Copy-constrain pk_d_signed_for_nc to pk_d_signed in the delegation
  circuit (condition-5 / condition-11 link), making the intent explicit.
- Add tests: fake_real_note_nonzero_value_fails, different_ivk_per_note_fails
  (delegation), proposal_authority_exceeds_16_bits_fails, and
  shares_range_single_overflow_correct_sum_fails (vote_proof).
- Improve comments explaining the x-only ExtractP public-input convention
  in elgamal.rs and the out-of-circuit anchor-height validation path.

Made-with: Cursor
The fixture generator used synthetic x-coordinates (pallas::Base::from(100),
from(200)) that are arbitrary field elements, not x-coordinates of actual
Pallas curve points. This was fine for the ZKP circuit (which treats them as
field elements), but the new UnmarshalCiphertext check in VerifyShareRevealProof
correctly rejects them: 100³+5 is not a quadratic residue on the Pallas curve.

Fix:
- build_share_reveal_test_data now performs real ElGamal encryption (ea_sk=777,
  deterministic randomness) so C1 and C2 are genuine Pallas points. Their
  x-coordinates are valid curve x-coordinates, and their compressed encodings
  are valid compressed Pallas points.
- The fixture format gains 64 bytes at [1424..1488): the full compressed
  enc_share (C1 || C2 using GroupEncoding::to_bytes(), same format as
  curvey.ToAffineCompressed()). Total fixture size: 1424 → 1488 bytes.
- prove_test.go reads enc_share directly from the fixture instead of
  constructing it from the bare x-coordinate bytes (which lacked sign bits).
- testutil/fixtures.go: ValidRevealShare now uses IdentityCiphertextBytes()
  (valid compressed identity points) instead of a nullifierSeed-filled stub.
- Regenerate all fixture files.

Made-with: Cursor
greg0x pushed a commit that referenced this pull request Mar 12, 2026
greg0x pushed a commit that referenced this pull request Mar 12, 2026
Replace stub delegation proof with real Halo2 prover. The combined
build_and_prove_delegation() loads wallet notes, Merkle witnesses, and
IMT exclusion proofs, then generates a real delegation circuit proof.

Key changes:
- Implement build_and_prove_delegation in librustvoting (replaces
  build_delegation_witness + generate_delegation_proof stubs)
- Add reqwest blocking client for IMT server exclusion proof fetching
- Expose ImtProvider trait and ImtProofData for external consumers
- Merge FullNoteData into NoteInfo (single type across Rust/FFI/Swift)
- Remove legacy ProofResult type
- Remove dead WitnessBuilt phase variant
- Fix store_proof to use INSERT...ON CONFLICT (was silently discarding
  proof bytes due to missing row)
- Fix wallet notes query ordering to match witness ordering by position
- Update XCFramework bindings and documentation
greg0x pushed a commit that referenced this pull request Mar 12, 2026
greg0x pushed a commit that referenced this pull request Mar 12, 2026
)

* Ballot scaling in ZKP #1: convert zatoshi to ballot count

Replace the minimum-weight check (condition 8) with ballot scaling
that floor-divides v_total by 12,500,000 to produce num_ballots.
Condition 7 now hashes num_ballots into the VAN commitment instead
of the raw v_total.

Circuit constraints for condition 8:
- num_ballots * BALLOT_DIVISOR + remainder == v_total
- remainder < 2^24 (via shift-by-2^6 into 30-bit lookup check)
- 0 < num_ballots <= 2^30 (via nb_minus_one 30-bit range check)

Adds MulChip (c = a * b gate) used for the reconstruction constraint
and the remainder bit-shift.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Apply ballot scaling to VAN hashes in ZKP #2 and librustvoting

ZKP #1 now hashes num_ballots (not raw zatoshi) into VAN commitments.
Update all downstream VAN hash callers to match:
- vote_proof/builder.rs: convert total_note_value to num_ballots before
  VAN integrity hashing and share splitting
- governance.rs: construct_van now divides total_weight by BALLOT_DIVISOR
- Update test values to use weights >= 12,500,000 (one ballot minimum)
- Freeze new known-answer VAN test vector

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
greg0x added a commit that referenced this pull request Mar 12, 2026
The existing voting_flow test calls the orchard builder directly.
This new test exercises the production library stack: VotingDb
persistence, TreeClient HTTP sync from chain, witness generation,
and ZKP #2 proof generation — all through the librustvoting and
vote-commitment-tree-client APIs.

Changes:
- Make derive_spending_key public in librustvoting so tests can
  derive a SpendingKey from the same hotkey seed used in production
- Parametrize build_delegation_bundle_for_test to accept an optional
  SpendingKey (for seed-derived key consistency between ZKP #1 and #2)
- Add librustvoting + vote-commitment-tree-client deps to e2e-tests
  with the necessary [patch.crates-io] entries
greg0x pushed a commit that referenced this pull request Mar 12, 2026
Two early-return guards in VotingStore set delegationProofStatus = .complete
without calling startDelegationProof, meaning no ZKP #1 was generated, no
delegation TX submitted, and no VAN position stored for software-wallet users.

Now witnessVerificationCompleted and delegationApproved both route non-Keystone
users through startDelegationProof, which already handles the full pipeline
(proof generation, delegation TX submission, tree growth polling, VAN storage).
greg0x added a commit that referenced this pull request Mar 12, 2026
The helper server's background tree sync loop would sync past share
positions before they were marked for witness retention, causing
"no witness for position X" errors for every share. This happened
because shares arrive ~40s after the tree has already been synced
(the test spends that time generating ZKP #1 and #2).

TreeSync now tracks marked positions separately and provides
witness_or_resync() which rebuilds the tree from scratch with all
marked positions if the fast path fails. The API handler also marks
positions eagerly on share arrival.

Moved the helper server default port from 9090 to 9091 to avoid
conflicting with the chain's gRPC server. Updated e2e test defaults
to use 127.0.0.1 (avoids macOS IPv6 resolution issues) and port
1318 (matches init.sh).
greg0x added a commit that referenced this pull request Mar 12, 2026
Rewrote voting_flow_librustvoting.rs to exercise the complete path:
delegation (ZKP #1) → cast-vote (ZKP #2) → helper server (ZKP #3)
→ tally accumulation → auto-tally finalization → result verification.

Previously the test generated ZKP #3 inline and submitted reveal-share
directly to the chain. Now it sends share payloads to the helper
server, which handles tree sync, witness generation, ZKP #3 proof,
and chain submission — matching the production Zashi flow.

Key fixes: use the chain's EA public key (from ~/.zallyd/ea.pk)
instead of generating a random keypair, which was preventing
auto-tally decryption. Reduced the default vote window from 480s
to 180s since the helper server processes shares much faster than
inline ZKP #3 generation.
greg0x added a commit that referenced this pull request Mar 12, 2026
canConfirmVote was hardcoded to true. Now it checks isDelegationReady
(ZKP #1 complete) and !isSubmittingVote (no vote in-flight). Vote
buttons are disabled with 50% opacity until both conditions are met.

The proposal detail view now shows contextual states:
- "Preparing voting credentials..." while ZKP #1 is generating
- "Submitting vote..." spinner while on-chain pipeline runs
- Error banner with message when submission fails
- "Vote recorded" checkmark only after on-chain confirmation

The confirmation overlay also distinguishes between waiting for
delegation vs waiting for a previous vote submission.
greg0x added a commit that referenced this pull request Mar 12, 2026
The admin UI uses stub values for nc_root and nullifier_imt_root, which
causes ZKP #1 to fail because the Merkle roots don't match the real
chain state. This test fetches the real Sinsemilla-based nc_root from
lightwalletd (via grpcurl) and the real nullifier IMT root from the IMT
server, then creates a round that Zashi can actually delegate to.

Requires a running local chain with a confirmed ceremony, grpcurl, and
a reachable IMT server. Configurable via ZASHI_SNAPSHOT_HEIGHT,
ZASHI_LIGHTWALLETD, ZASHI_IMT_URL, and ZASHI_VOTE_WINDOW_SECS env vars.
greg0x added a commit that referenced this pull request Mar 12, 2026
The admin UI was creating voting rounds with SHA-256(orchard_frontier)
as nc_root, which never matches the real Sinsemilla-based root that
Zashi computes — causing all delegation proofs (ZKP #1) to fail.

This adds a Rust FFI function (zally_extract_nc_root) that parses the
hex-encoded orchard frontier from lightwalletd's TreeState and computes
the real Merkle root using MerkleHashOrchard. The Go chain binary calls
this via CGo, replacing the placeholder.

The admin UI now fetches real snapshot data (nc_root, nullifier IMT root,
blockhash) from GET /zally/v1/snapshot-data/{height} before creating a
round, falling back to stubs if the endpoint is unavailable.

Includes temporary [zkp1-verify] debug logging in ffi.rs for the ongoing
proof verification debugging.
greg0x added a commit that referenced this pull request Mar 12, 2026
The manual commitment_tree_root() in sdk/circuits was always placing
the running digest on the left when combining with parent nodes. But
when a parent is Some, it represents the completed *left* subtree —
the digest (right subtree) should go on the right side of combine().

This caused the chain's snapshot-data endpoint to return a different
nc_root than zcash_client_backend (used by Zashi and librustvoting),
making delegation proofs (ZKP #1) fail verification on-chain even
though the proof was generated correctly.

Added mainnet regression tests at height 3245500 to both the
sdk/circuits and nullifier-ingest implementations, pinned against
the known-correct value from zcash_client_backend.
greg0x pushed a commit that referenced this pull request Mar 12, 2026
Sequential packing algorithm sorts notes by value DESC and fills bundles
of up to 4 notes each, dropping bundles below BALLOT_DIVISOR (0.125 ZEC).
Eligible weight is quantized per-bundle to match VAN circuit constraints.

Multi-bundle delegation and voting: each bundle gets its own ZKP #1 proof,
delegation TX, VAN position, and per-proposal vote submission. Keystone
signing loops through bundles sequentially.

UX improvements:
- Show proposals immediately instead of blank spinner during tree state fetch
- Unified progress banner (witness preparation → delegation proof %)
- Countdown timer on proposal detail view
- No flash of status banners on cached round resume

Safety fixes:
- proof_generated requires VAN positions (not just proofs) for all bundles
- canConfirmVote gates on bundleCount > 0 to prevent resume race
- Share delegation retry (3 attempts, 2s delay) restored
- DB migration bumped to v3 for new bundles-based schema
greg0x pushed a commit that referenced this pull request Mar 12, 2026
- Add sentinel injection (k*2^250 for k=0..16) to pir-export to satisfy
  circuit gap-width constraint (#3)
- Change Tier 2 empty-leaf padding from Fp::zero() to -Fp::one() so
  trailing entries sort after real leaves, fixing binary search (#2)
- Make TierServer::answer_query() return Result with input validation
  (length checks, alignment) instead of panicking on malformed
  requests; handlers return HTTP 400 on error (#1)
- Replace unwrap/assert with fallible returns in pir-client and
  Tier0Data::from_bytes (#4)
greg0x pushed a commit that referenced this pull request Mar 12, 2026
Replace stub delegation proof with real Halo2 prover. The combined
build_and_prove_delegation() loads wallet notes, Merkle witnesses, and
IMT exclusion proofs, then generates a real delegation circuit proof.

Key changes:
- Implement build_and_prove_delegation in librustvoting (replaces
  build_delegation_witness + generate_delegation_proof stubs)
- Add reqwest blocking client for IMT server exclusion proof fetching
- Expose ImtProvider trait and ImtProofData for external consumers
- Merge FullNoteData into NoteInfo (single type across Rust/FFI/Swift)
- Remove legacy ProofResult type
- Remove dead WitnessBuilt phase variant
- Fix store_proof to use INSERT...ON CONFLICT (was silently discarding
  proof bytes due to missing row)
- Fix wallet notes query ordering to match witness ordering by position
- Update XCFramework bindings and documentation
greg0x pushed a commit that referenced this pull request Mar 12, 2026
)

* Ballot scaling in ZKP #1: convert zatoshi to ballot count

Replace the minimum-weight check (condition 8) with ballot scaling
that floor-divides v_total by 12,500,000 to produce num_ballots.
Condition 7 now hashes num_ballots into the VAN commitment instead
of the raw v_total.

Circuit constraints for condition 8:
- num_ballots * BALLOT_DIVISOR + remainder == v_total
- remainder < 2^24 (via shift-by-2^6 into 30-bit lookup check)
- 0 < num_ballots <= 2^30 (via nb_minus_one 30-bit range check)

Adds MulChip (c = a * b gate) used for the reconstruction constraint
and the remainder bit-shift.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Apply ballot scaling to VAN hashes in ZKP #2 and librustvoting

ZKP #1 now hashes num_ballots (not raw zatoshi) into VAN commitments.
Update all downstream VAN hash callers to match:
- vote_proof/builder.rs: convert total_note_value to num_ballots before
  VAN integrity hashing and share splitting
- governance.rs: construct_van now divides total_weight by BALLOT_DIVISOR
- Update test values to use weights >= 12,500,000 (one ballot minimum)
- Freeze new known-answer VAN test vector

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
greg0x added a commit that referenced this pull request Mar 12, 2026
The existing voting_flow test calls the orchard builder directly.
This new test exercises the production library stack: VotingDb
persistence, TreeClient HTTP sync from chain, witness generation,
and ZKP #2 proof generation — all through the librustvoting and
vote-commitment-tree-client APIs.

Changes:
- Make derive_spending_key public in librustvoting so tests can
  derive a SpendingKey from the same hotkey seed used in production
- Parametrize build_delegation_bundle_for_test to accept an optional
  SpendingKey (for seed-derived key consistency between ZKP #1 and #2)
- Add librustvoting + vote-commitment-tree-client deps to e2e-tests
  with the necessary [patch.crates-io] entries
greg0x pushed a commit that referenced this pull request Mar 12, 2026
Sequential packing algorithm sorts notes by value DESC and fills bundles
of up to 4 notes each, dropping bundles below BALLOT_DIVISOR (0.125 ZEC).
Eligible weight is quantized per-bundle to match VAN circuit constraints.

Multi-bundle delegation and voting: each bundle gets its own ZKP #1 proof,
delegation TX, VAN position, and per-proposal vote submission. Keystone
signing loops through bundles sequentially.

UX improvements:
- Show proposals immediately instead of blank spinner during tree state fetch
- Unified progress banner (witness preparation → delegation proof %)
- Countdown timer on proposal detail view
- No flash of status banners on cached round resume

Safety fixes:
- proof_generated requires VAN positions (not just proofs) for all bundles
- canConfirmVote gates on bundleCount > 0 to prevent resume race
- Share delegation retry (3 attempts, 2s delay) restored
- DB migration bumped to v3 for new bundles-based schema
@greg0x greg0x changed the title sync: monorepo changes 2026-03-12 Use Creator::build_from_parts for governance PCZT construction Mar 12, 2026
greg0x added a commit to valargroup/librustzcash that referenced this pull request Mar 12, 2026
Clean up PCZT APIs that are no longer needed now that governance PCZT construction uses `Creator::build_from_parts` (see valargroup/zcash_voting#1).

- Remove `Pczt::set_orchard()` — was only used by librustvoting to manually inject an orchard bundle after creating an empty PCZT shell. No longer needed since `build_from_parts` accepts the bundle directly.
- Narrow `orchard::Bundle::serialize_from` from `pub` to `pub(crate)` — only used internally by the creator role, no external callers.
@greg0x greg0x merged commit 4d8e601 into main Mar 12, 2026
greg0x added a commit to valargroup/librustzcash that referenced this pull request Mar 12, 2026
External governance protocols (shielded voting) need to:

- Replace the Orchard bundle in a PCZT after constructing a custom
  governance action (Pczt::set_orchard)
- Read back the spend_auth_sig after a hardware wallet signs the PCZT,
  so the signature can be threaded into a ZK delegation proof
- Serialize an orchard::pczt::Bundle into the PCZT wire format from
  outside the crate (Bundle::serialize_from)
- Construct an ephemeral SqliteShardStore from a raw connection to build
  Merkle witnesses without going through WalletDb
  (SqliteShardStore::from_connection)

Remove set_orchard and narrow serialize_from visibility (#3)

Clean up PCZT APIs that are no longer needed now that governance PCZT construction uses `Creator::build_from_parts` (see valargroup/zcash_voting#1).

- Remove `Pczt::set_orchard()` — was only used by librustvoting to manually inject an orchard bundle after creating an empty PCZT shell. No longer needed since `build_from_parts` accepts the bundle directly.
- Narrow `orchard::Bundle::serialize_from` from `pub` to `pub(crate)` — only used internally by the creator role, no external callers.
greg0x added a commit to valargroup/librustzcash that referenced this pull request Mar 13, 2026
External governance protocols (shielded voting) need to:

- Replace the Orchard bundle in a PCZT after constructing a custom
  governance action (Pczt::set_orchard)
- Read back the spend_auth_sig after a hardware wallet signs the PCZT,
  so the signature can be threaded into a ZK delegation proof
- Serialize an orchard::pczt::Bundle into the PCZT wire format from
  outside the crate (Bundle::serialize_from)
- Construct an ephemeral SqliteShardStore from a raw connection to build
  Merkle witnesses without going through WalletDb
  (SqliteShardStore::from_connection)

Remove set_orchard and narrow serialize_from visibility (#3)

Clean up PCZT APIs that are no longer needed now that governance PCZT construction uses `Creator::build_from_parts` (see valargroup/zcash_voting#1).

- Remove `Pczt::set_orchard()` — was only used by librustvoting to manually inject an orchard bundle after creating an empty PCZT shell. No longer needed since `build_from_parts` accepts the bundle directly.
- Narrow `orchard::Bundle::serialize_from` from `pub` to `pub(crate)` — only used internally by the creator role, no external callers.
greg0x added a commit to valargroup/librustzcash that referenced this pull request Mar 13, 2026
External governance protocols (shielded voting) need to:

- Replace the Orchard bundle in a PCZT after constructing a custom
  governance action (Pczt::set_orchard)
- Read back the spend_auth_sig after a hardware wallet signs the PCZT,
  so the signature can be threaded into a ZK delegation proof
- Serialize an orchard::pczt::Bundle into the PCZT wire format from
  outside the crate (Bundle::serialize_from)
- Construct an ephemeral SqliteShardStore from a raw connection to build
  Merkle witnesses without going through WalletDb
  (SqliteShardStore::from_connection)

Remove set_orchard and narrow serialize_from visibility (#3)

Clean up PCZT APIs that are no longer needed now that governance PCZT construction uses `Creator::build_from_parts` (see valargroup/zcash_voting#1).

- Remove `Pczt::set_orchard()` — was only used by librustvoting to manually inject an orchard bundle after creating an empty PCZT shell. No longer needed since `build_from_parts` accepts the bundle directly.
- Narrow `orchard::Bundle::serialize_from` from `pub` to `pub(crate)` — only used internally by the creator role, no external callers.
p0mvn added a commit that referenced this pull request Mar 21, 2026
…ipt lifecycle

Add the Rust-side infrastructure for the fire-persist-poll share reveal
confirmation system. After CastVote TX confirms, the wallet delegates
encrypted shares to helpers and polls for on-chain reveal confirmation
using deterministic share nullifiers.

Schema (migration v6):
- votes: add `van_authority_spent` column, separate from `submitted` —
  tracks CastVote TX confirmation for proposal_authority bitmask while
  `submitted` tracks share-reveal completion.
- share_delegations: PK now includes `helper_url` (one receipt per
  helper per share); renamed nullifier→share_nullifier,
  confirmed→reveal_confirmed; added `seq`, `submit_at` columns.

Storage operations:
- ShareDelegationReceipt and PendingShareRevealGroup types with serde.
- CRUD: store/list/clear receipts, mark_share_revealed_for_helper,
  list_pending_share_reveal_groups (joins votes.submitted=0).
- mark_van_authority_spent with rows-affected guard.
- store_vote uses ON CONFLICT to preserve submitted/van_authority_spent
  on crash-retry re-insert.
- Release DB mutex during long-running ZKP #1 proof generation so
  share-polling callers aren't blocked.

Nullifier derivation:
- compute_share_nullifier() chains vote_commitment_hash →
  share_nullifier_hash (Poseidon, same domain tags as ZKP #3 circuit).
- build_share_payloads includes share_nullifier in each SharePayload.
- round_id_bytes_from_hex rejects inputs >32 bytes.

Also fixes pre-existing build error in zkp2.rs (upstream voting-circuits
removed single_share parameter from build_vote_proof_from_delegation).

Made-with: Cursor
p0mvn added a commit that referenced this pull request Mar 21, 2026
…ipt lifecycle

Add the Rust-side infrastructure for the fire-persist-poll share reveal
confirmation system. After CastVote TX confirms, the wallet delegates
encrypted shares to helpers and polls for on-chain reveal confirmation
using deterministic share nullifiers.

Schema (migration v6):
- votes: add `van_authority_spent` column, separate from `submitted` —
  tracks CastVote TX confirmation for proposal_authority bitmask while
  `submitted` tracks share-reveal completion.
- share_delegations: PK now includes `helper_url` (one receipt per
  helper per share); renamed nullifier→share_nullifier,
  confirmed→reveal_confirmed; added `seq`, `submit_at` columns.

Storage operations:
- ShareDelegationReceipt and PendingShareRevealGroup types with serde.
- CRUD: store/list/clear receipts, mark_share_revealed_for_helper,
  list_pending_share_reveal_groups (joins votes.submitted=0).
- mark_van_authority_spent with rows-affected guard.
- store_vote uses ON CONFLICT to preserve submitted/van_authority_spent
  on crash-retry re-insert.
- Release DB mutex during long-running ZKP #1 proof generation so
  share-polling callers aren't blocked.

Nullifier derivation:
- compute_share_nullifier() chains vote_commitment_hash →
  share_nullifier_hash (Poseidon, same domain tags as ZKP #3 circuit).
- build_share_payloads includes share_nullifier in each SharePayload.
- round_id_bytes_from_hex rejects inputs >32 bytes.

Also fixes pre-existing build error in zkp2.rs (upstream voting-circuits
removed single_share parameter from build_vote_proof_from_delegation).

Made-with: Cursor
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.

3 participants