ZCA-229 Filter PIR endpoints by exact snapshot match before delegation#26
Closed
p0mvn wants to merge 1 commit into
Closed
ZCA-229 Filter PIR endpoints by exact snapshot match before delegation#26p0mvn wants to merge 1 commit into
p0mvn wants to merge 1 commit into
Conversation
The voting service config now ships an `expectedSnapshotHeight` for each round and a list of PIR endpoints. Previously the SDK forwarded the first configured endpoint blindly, so a PIR server stuck on an older (or unexpectedly newer) snapshot would silently produce a delegation proof the prover/audit cannot accept. `PirSnapshotResolver` probes every configured endpoint's `GET /root` and keeps only those whose `RootInfo.height` is **exactly equal** to the round's expected snapshot height (== rather than >=, since a PIR server ahead of the round is just as wrong as one behind it). The first matching endpoint in config order is chosen. If none match, resolution hard-fails with `noMatchingEndpoint` and per-endpoint diagnostics so callers can surface a clear "out of sync, try later" message. `VotingRustBackend.buildAndProveDelegation` now takes `pirEndpoints: [String]` + `expectedSnapshotHeight: UInt64` (with an optional injected resolver for tests) and resolves to a single URL before calling the Rust FFI. Made-with: Cursor
5 tasks
Author
|
Recreating against the correct base branch |
5 tasks
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
Linear: ZCA-229
The voting service config publishes an
expectedSnapshotHeightper round and a list of PIR endpoints. Before this change,buildAndProveDelegationtook a singlepirServerUrland the iOS caller forwarded the first configured endpoint blindly — so a PIR server stuck on an older snapshot (or accidentally ahead of the round) would silently produce a delegation proof that the prover/audit cannot accept.This adds a
PirSnapshotResolverinZcashLightClientKitthat, given the configured endpoints and the round's expected snapshot height:GET /rootin parallel.RootInfo.heightis exactly equal (==) to the expected snapshot height.==rather than>=because a PIR server ahead of the round is just as wrong as one behind — both will produce proofs that don't match the audit-anchored snapshot.PirSnapshotResolverError.noMatchingEndpointcarrying per-endpoint diagnostics (matched height, mismatched height, missing height, transport error) when nothing matches, so callers can surface a clear "out of sync, try in a few minutes" message instead of producing a junk proof.VotingRustBackend.buildAndProveDelegationis nowasync throwsand takespirEndpoints: [String] + expectedSnapshotHeight: UInt64(plus an optional injectedPirSnapshotProbingfor tests) instead of a singlepirServerUrl. It resolves to a single URL via the resolver before calling the Rust FFI.The
zodl-ioscaller side of this change is in a separate PR (depends on this one merging + an SPM bump).Test plan
xcodebuild test -scheme ZcashLightClientKit -only-testing:OfflineTests/PirSnapshotResolverTests -only-testing:OfflineTests/HTTPPirSnapshotProbeTests -only-testing:OfflineTests/HTTPPirSnapshotProbeWireShapeTests— 21/21 pass on iOS Simulator (iPhone 17 Pro).==policy), empty endpoint list rejected, error description contains expected snapshot + endpoint URLs, probe receives correct URLs/expected height.HTTPPirSnapshotProbeTestsexercisesHTTPPirSnapshotProbe.probe(...)end-to-end through aURLProtocolstub:height == expected → .matching,height < expected → .mismatched,height > expected → .mismatched(the regression that locks in==),height == null → .missingHeight, non-200 response →.unreachable("...503..."), malformed JSON →.unreachable("...decode..."), transport error →.unreachable,/rootpath appended (with and without trailing slash on base), invalid URL string →.unreachable(no crash).RootInfoJSON shape (num_ranges/pir_depthsnake_case,height: Option<u64>) againstvote-nullifier-pir.Notes
HTTPPirSnapshotProbe.timeoutfield was dropped (it was stored but unread once the session was constructed) and the doc comment now states explicitly thattimeoutis ignored when a caller-suppliedURLSessionis passed.==policy was an explicit product decision; the resolver and probe now name everything in terms of "matching" / "mismatched" rather than "fresh" / "stale" so that semantics aren't ambiguous to readers.Made with Cursor