Skip to content

Draft: Add PIR spendability support to zcash_client_sqlite#13

Closed
p0mvn wants to merge 10 commits into
maint/zcash_client_sqlite-0.19.xfrom
spendability-pir
Closed

Draft: Add PIR spendability support to zcash_client_sqlite#13
p0mvn wants to merge 10 commits into
maint/zcash_client_sqlite-0.19.xfrom
spendability-pir

Conversation

@p0mvn
Copy link
Copy Markdown

@p0mvn p0mvn commented Apr 9, 2026

Use Private Information Retrieval to detect spent notes and fetch witnesses before sync completes, giving users early visibility into pending spends. Adds the full data flow (SDK synchronizer interface, Root reducer orchestration, shared state), a PIR setup screen in Advanced Settings with a persisted user toggle, and pending-spend UI treatment in transaction rows.

Motivation

During normal wallet sync, Orchard notes are not spendable until the
shard-tree scanner has processed enough blocks to construct a Merkle
authentication path for each note. For wallets that are catching up after
being offline, this can mean a significant delay before funds become
available.

Nullifier PIR (Private Information Retrieval) sidesteps this by querying
an external server for two pieces of information:

  1. Nullifier inclusion — has a note been spent on-chain? This lets the
    wallet mark notes as spent before the scanner confirms it.
  2. Merkle authentication paths — the server provides the witness data
    needed to spend a note, so the wallet does not need to wait for its
    local shard to be fully scanned.

Together, these allow the wallet to display accurate spendable balances
and build transactions within seconds of startup, rather than waiting for
a full scan.

Design Assumptions

  • Spendability check is triggered when the user opens the app for all notes in the wallet at the time
  • Keystone / PCZT path is kept out of the initial scope
  • The only new UI components are configuration in Advanced Settings (screenshots attached)
  • The main user-facing side-effect is immediate spendability (sub 10 seconds for 2 notes) when starting up
  • When spending, all notes must reference the same anchor height. The anchor height is refreshed right before confirmation and chosen for spending.
  • There is an automated retry mechanism if the broadcasting fails due to invalid anchor height

Video Demo

https://screen.studio/share/tm6q8yUz

Screenshots

image image

Related PRs

Changes

zcash_client_backend

  • WalletCommitmentTrees::get_pir_orchard_merkle_path — new trait
    method (with default no-op returning Ok(None)) for retrieving a
    PIR-provided Orchard Merkle path by commitment tree position.
  • PirOrchardWitness type alias for the witness tuple.
  • spendability-pir feature flag gating the above.
  • create_proposed_transactions gains a use_pir_witnesses parameter
    (feature-gated) that switches the Orchard witness source from the
    local ShardTree to stored PIR witnesses.

zcash_client_sqlite

  • New wallet::spendability_pir module with:
    • Nullifier gate queries (get_unspent_orchard_notes_for_pir)
    • Witness storage, retrieval, and validation (insert_pir_witness,
      get_pir_witness, validate_orchard_witness, etc.)
    • Helper types: UnspentOrchardNote, NoteNeedingWitness,
      PirWitnessRow, PirWitnessedNote, PirWitnessValidation
  • spendability_pir_tables migration creating the pir_witness_data
    table. The migration is unconditional (not feature-gated) to keep the
    migration DAG identical across all builds; the table is empty and
    unused when the feature is off.
  • get_wallet_summary treats Orchard notes with PIR witnesses as
    spendable even when their shard is not fully scanned.
  • Note selection (select_spendable_notes) accepts Orchard notes with
    PIR witnesses, bypassing the shard-scanned gate.
  • truncate_to_height unconditionally clears pir_witness_data to
    invalidate authentication paths after reorgs.
  • WalletCommitmentTrees::get_pir_orchard_merkle_path implemented for
    WalletDb, backed by the pir_witness_data table.
  • CHANGELOG updated under [Unreleased].

p0mvn added 5 commits April 9, 2026 17:08
Nullifier PIR lets the wallet discover Orchard note spendability by
querying an external server for nullifier inclusion and Merkle
authentication paths, rather than waiting for sequential shard-tree
scanning to complete. This significantly reduces the time before notes
become spendable.

The implementation is gated behind the `spendability-pir` feature flag
and comprises:

- A `wallet::spendability_pir` module with queries for unspent notes
  eligible for PIR checking, witness storage/retrieval/validation, and
  helper types.
- A `pir_witness_data` table via the `spendability_pir_tables` migration
  (unconditional, not feature-gated, to keep the migration DAG identical
  across all builds).
- `WalletCommitmentTrees::get_pir_orchard_merkle_path` trait method
  (with default no-op) in zcash_client_backend, implemented for
  `WalletDb` backed by the `pir_witness_data` table.
- Changes to `get_wallet_summary` and note selection that treat Orchard
  notes with PIR witnesses as spendable even when their shard is not
  fully scanned.
- Unconditional `pir_witness_data` cleanup in `truncate_to_height` to
  invalidate authentication paths after reorgs.

Made-with: Cursor
Replace intra-doc link to `zcash_client_sqlite::WalletDb` (unresolvable
from zcash_client_backend) with plain text, and apply rustfmt.

Made-with: Cursor
The feature flag and its optional dependencies (spend-client,
spend-types, witness-client from spendability-pir) were present in
the local wired Cargo.toml but missing from the committed version,
causing unexpected-cfg lint failures in CI.

Made-with: Cursor
Tests that call create_proposed_transactions_pir need
cfg(feature = "spendability-pir") in addition to cfg(feature = "orchard"),
since the method only exists when the feature is enabled.

Made-with: Cursor
Declare the feature flag so check-cfg recognizes it. The PIR network
client dependencies (spend-client, spend-types, witness-client) are
only available in the local wired build and are not included in the
committed Cargo.toml — they pull in transitive deps (valar-ypir,
spiral-rs) whose version requirements conflict with the existing
lockfile.

Made-with: Cursor
@p0mvn p0mvn force-pushed the spendability-pir branch from 3898be9 to 745b576 Compare April 9, 2026 23:38
p0mvn added 5 commits April 9, 2026 17:46
check_nullifiers_via_pir and fetch_witnesses_via_pir were never called —
the SDK's FFI layer in zcash-swift-wallet-sdk/rust/src/spendability.rs
calls spend_client and witness_client directly. Removing these also
eliminates the only use of spend-client and witness-client within
zcash_client_sqlite, so those crate dependencies are no longer needed
here.

Made-with: Cursor
The PCZT creation path doesn't need PIR witness selection — it always
uses the local ShardTree. The parameter was threaded through from the
FFI but never meaningfully varied; the caller (SDK) hardcoded the value
based on sync status, which is the wrong layer for that decision.

The shared build_proposed_transaction still accepts the parameter behind
cfg(spendability-pir) for the create_proposed_transactions path.

Made-with: Cursor
These tests exercise the PIR witness path in get_wallet_summary, which
is guarded by cfg!(feature = "spendability-pir") at compile time. When
built with only the orchard feature, the PIR path is dead and notes
are never promoted to spendable, causing the assertions to fail.

Made-with: Cursor
get_pir_witnessed_notes and PirWitnessedNote were unused by any caller
outside of tests. delete_all_pir_witnesses was debug scaffolding marked
for removal before merge. Drop all three along with their SQL, tests,
and the WalletDb forwarding methods.

Made-with: Cursor
@p0mvn p0mvn changed the title Add PIR spendability support to zcash_client_sqlite Draft: Add PIR spendability support to zcash_client_sqlite Apr 10, 2026
@p0mvn p0mvn marked this pull request as draft April 10, 2026 04:26
@p0mvn p0mvn marked this pull request as draft April 10, 2026 04:26
@p0mvn p0mvn closed this Apr 11, 2026
greg0x pushed a commit that referenced this pull request Apr 14, 2026
…f0768ea4..dd0ea2c3c5

dd0ea2c3c5 Merge pull request #23 from zcash/2026-03-doc-fixes
93b26b7db1 v0.4.1 release - minor doc fixups
d528fa82e3 Merge pull request #22 from zcash/2025-12-doc-git-subtree
b421ef0b34 Merge pull request #22 from zcash/2025-12-doc-git-subtree
4a26e54c36 Fix git subtree command syntax in README
34bb38b606 Merge pull request #20 from zcash/doc/get_block
a507182f92 Document GetBlock transparent data behavior and clarify nullifier RPCs
fc5cc9a4b1 Merge pull request #19 from zcash/deprecate_get_nullifiers
f0ebc72cad Mark `GetBlockNullifiers` and `GetBlockRangeNullifiers` as deprecated.
9a5f7a0eec Merge pull request #16 from zcash/2026-01-remove-fullHeader
f1095897d9 revert the previous PR that added BlockID.fullHeader
99d9bf9fff Merge pull request #15 from zcash/2025-12-compactblock-doc
bbdd689d73 either CompactBlock.{hash, prevHash}, or CompactBlock.header
aa42926ed3 Merge pull request #13 from pacu/add-README
da4d3d57d8 Add rationale suggested by @LarryRuane
4edd22465b fix type
e36df314b8 clean up whitespace and line width
215cb5a3f3 Add PR suggestions. Organize Clients section
3e78d5d872 Apply suggestions from code review
96b86544db Apply suggestion from @LarryRuane
6259b97df0 Apply suggestion from @nuttycom
f2fcef3e42 Apply suggestion from @nuttycom
8cc1e66b11 Apply suggestion from @LarryRuane
99132222dd Apply suggestion from @nuttycom
207cc5e6f9 create a README file with context and instructions
7392b9706f Merge pull request #12 from zcash/release/v0.4.0

git-subtree-dir: zcash_client_backend/lightwallet-protocol
git-subtree-split: dd0ea2c3c5827a433e62c2f936b89efa2dec5a9a
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.

1 participant