From 125d5d4d03a421d610ecdbd629f3faeb4fd2bd3b Mon Sep 17 00:00:00 2001 From: Greg Nagy Date: Fri, 1 May 2026 18:15:54 +0200 Subject: [PATCH 1/4] Expose delegation PIR precompute API --- Cargo.lock | 32 ++++++++--- Cargo.toml | 2 +- .../Rust/Voting/VotingRustBackend.swift | 47 ++++++++++++++++ .../Rust/Voting/VotingTypes.swift | 13 +++++ rust/src/voting.rs | 55 +++++++++++++++++++ 5 files changed, 139 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e552ca8a..fec46bb67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2805,7 +2805,7 @@ dependencies = [ "tracing", "tracing-subscriber", "uuid", - "voting-circuits", + "voting-circuits 0.4.1", "xz2", "zcash_address", "zcash_client_backend", @@ -6659,8 +6659,6 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "vote-commitment-tree" version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fb2456313f571003c0bed8d38fb166d78da85653caf828823c1a82f4e1aef1" dependencies = [ "anyhow", "ff", @@ -6676,8 +6674,6 @@ dependencies = [ [[package]] name = "vote-commitment-tree-client" version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0267343903fef0eb494f3f6337318249c1daf420a9ea0e73dc7856de26040f1f" dependencies = [ "base64", "clap", @@ -6691,6 +6687,26 @@ dependencies = [ "vote-commitment-tree", ] +[[package]] +name = "voting-circuits" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7830eca6c294cff758bc4885ca8bd2fbdf90f9998f586c5c4cfe2d1b559555f6" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "halo2_gadgets 0.4.0", + "halo2_poseidon", + "halo2_proofs", + "incrementalmerkletree", + "lazy_static", + "orchard", + "pasta_curves", + "rand 0.8.5", + "sinsemilla", +] + [[package]] name = "voting-circuits" version = "0.4.1" @@ -7570,9 +7586,7 @@ dependencies = [ [[package]] name = "zcash_voting" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f3ff3f3efe5addd1ac61e281863b4e4b9910d3250f2110d3da9b2bf5fae118" +version = "0.1.1" dependencies = [ "blake2b_simd", "ff", @@ -7594,7 +7608,7 @@ dependencies = [ "thiserror 2.0.17", "vote-commitment-tree", "vote-commitment-tree-client", - "voting-circuits", + "voting-circuits 0.3.1", "zcash_keys", "zcash_primitives", "zcash_protocol", diff --git a/Cargo.toml b/Cargo.toml index 9e11df34e..34654002d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,7 @@ rust_decimal = { version = "1", default-features = false, features = ["c-repr"] # - The "static" feature is required for the "compression" default feature of arti-client. xz2 = { version = "0.1", features = ["static"] } -zcash_voting = "0.2.2" +zcash_voting = { path = "../zcash_voting/zcash_voting" } voting-circuits = { version = "0.4.1", features = ["share-reveal"] } zcash_keys = { version = "0.13", features = ["orchard"] } incrementalmerkletree = { version = "0.8", default-features = false } diff --git a/Sources/ZcashLightClientKit/Rust/Voting/VotingRustBackend.swift b/Sources/ZcashLightClientKit/Rust/Voting/VotingRustBackend.swift index 2e371848b..e7842c145 100644 --- a/Sources/ZcashLightClientKit/Rust/Voting/VotingRustBackend.swift +++ b/Sources/ZcashLightClientKit/Rust/Voting/VotingRustBackend.swift @@ -538,6 +538,53 @@ extension VotingRustBackend { // MARK: - Delegation proof extension VotingRustBackend { + /// Resolve the round PIR endpoint, fetch needed ZKP #1 IMT proofs, and cache them in the voting DB. + /// + /// This performs the network PIR lookup only; proof construction still happens in + /// `buildAndProveDelegation`. + public func precomputeDelegationPir( + roundId: String, + bundleIndex: UInt32, + notes: [VotingNoteInfo], + pirEndpoints: [String], + expectedSnapshotHeight: UInt64, + pirResolver: PirSnapshotResolver = PirSnapshotResolver() + ) async throws -> VotingDelegationPirPrecomputeResult { + let pirServerUrl = try await pirResolver.resolve( + endpoints: pirEndpoints, + expectedSnapshotHeight: expectedSnapshotHeight + ) + + let dbh = try requireHandle() + let roundIdBytes = [UInt8](roundId.utf8) + let notesJson = try JSONEncoder().encode(notes) + let notesBytes = [UInt8](notesJson) + let urlBytes = [UInt8](pirServerUrl.utf8) + + let ptr: UnsafeMutablePointer? = roundIdBytes.withUnsafeBufferPointer { ridBuf in + notesBytes.withUnsafeBufferPointer { notesBuf in + urlBytes.withUnsafeBufferPointer { urlBuf in + zcashlc_voting_precompute_delegation_pir( + dbh, + ridBuf.baseAddress, + UInt(ridBuf.count), + bundleIndex, + notesBuf.baseAddress, + UInt(notesBuf.count), + urlBuf.baseAddress, + UInt(urlBuf.count) + ) + } + } + } + + guard let ptr else { + throw VotingRustBackendError.rustError(lastErrorMessage(fallback: "`precompute_delegation_pir` failed")) + } + defer { zcashlc_free_boxed_slice(ptr) } + return try decodeJSON(from: ptr) + } + /// Build and prove the delegation ZKP. Long-running; reports progress via callback. /// /// Pass every PIR endpoint configured for the round and the round's expected diff --git a/Sources/ZcashLightClientKit/Rust/Voting/VotingTypes.swift b/Sources/ZcashLightClientKit/Rust/Voting/VotingTypes.swift index e748d493b..b3bdf5a56 100644 --- a/Sources/ZcashLightClientKit/Rust/Voting/VotingTypes.swift +++ b/Sources/ZcashLightClientKit/Rust/Voting/VotingTypes.swift @@ -227,6 +227,19 @@ public struct VotingDelegationProofResult: Codable, Sendable { } } +// MARK: - Delegation PIR Precompute Result (JSON) + +/// Result of precomputing and caching PIR proofs needed by delegation proving. +public struct VotingDelegationPirPrecomputeResult: Codable, Sendable { + public let cachedCount: UInt32 + public let fetchedCount: UInt32 + + enum CodingKeys: String, CodingKey { + case cachedCount = "cached_count" + case fetchedCount = "fetched_count" + } +} + // MARK: - Delegation Submission (JSON) /// Delegation submission payload. diff --git a/rust/src/voting.rs b/rust/src/voting.rs index 5128d8129..b8149a24f 100644 --- a/rust/src/voting.rs +++ b/rust/src/voting.rs @@ -294,6 +294,22 @@ impl From for JsonDelegationProofResult { } } +/// JSON-serializable DelegationPirPrecomputeResult. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct JsonDelegationPirPrecomputeResult { + pub cached_count: u32, + pub fetched_count: u32, +} + +impl From for JsonDelegationPirPrecomputeResult { + fn from(r: voting::DelegationPirPrecomputeResult) -> Self { + Self { + cached_count: r.cached_count, + fetched_count: r.fetched_count, + } + } +} + /// JSON-serializable DelegationSubmission. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct JsonDelegationSubmission { @@ -1421,6 +1437,45 @@ pub unsafe extern "C" fn zcashlc_voting_generate_note_witnesses( // B. VotingDatabase methods — Delegation proof // ============================================================================= +/// Precompute and cache delegation PIR IMT proofs for ZKP #1. +/// +/// Returns JSON-encoded `DelegationPirPrecomputeResult` as `*mut FfiBoxedSlice`, or null on error. +/// +/// # Safety +/// +/// - `db` must be a valid, non-null `VotingDatabaseHandle` pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zcashlc_voting_precompute_delegation_pir( + db: *mut VotingDatabaseHandle, + round_id: *const u8, + round_id_len: usize, + bundle_index: u32, + notes_json: *const u8, + notes_json_len: usize, + pir_server_url: *const u8, + pir_server_url_len: usize, +) -> *mut crate::ffi::BoxedSlice { + let db = AssertUnwindSafe(db); + let res = catch_panic(|| { + let handle = + unsafe { db.as_ref() }.ok_or_else(|| anyhow!("VotingDatabaseHandle is null"))?; + let round_id_str = unsafe { str_from_ptr(round_id, round_id_len) }?; + let notes_bytes = unsafe { bytes_from_ptr(notes_json, notes_json_len) }; + let json_notes: Vec = serde_json::from_slice(notes_bytes)?; + let core_notes: Vec = json_notes.into_iter().map(Into::into).collect(); + let pir_url = unsafe { str_from_ptr(pir_server_url, pir_server_url_len) }?; + + let result = handle + .db + .precompute_delegation_pir(&round_id_str, bundle_index, &core_notes, &pir_url) + .map_err(|e| anyhow!("precompute_delegation_pir failed: {}", e))?; + + let json_result: JsonDelegationPirPrecomputeResult = result.into(); + json_to_boxed_slice(&json_result) + }); + unwrap_exc_or_null(res) +} + /// Build and prove the real delegation ZKP (#1). Long-running. /// /// Returns JSON-encoded `DelegationProofResult` as `*mut FfiBoxedSlice`, or null on error. From e00775b3a96f3a3b1873fbc39e2501c735d9baca Mon Sep 17 00:00:00 2001 From: Greg Nagy Date: Fri, 1 May 2026 19:19:17 +0200 Subject: [PATCH 2/4] Point SDK at zcash voting PIR branch --- Cargo.lock | 29 ++++++----------------------- Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fec46bb67..2c4afda0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2805,7 +2805,7 @@ dependencies = [ "tracing", "tracing-subscriber", "uuid", - "voting-circuits 0.4.1", + "voting-circuits", "xz2", "zcash_address", "zcash_client_backend", @@ -6659,6 +6659,7 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "vote-commitment-tree" version = "0.1.1" +source = "git+https://github.com/valargroup/zcash_voting.git?branch=greg%2Fzkp1-pir-prep#1aa07a149b6f4938ccf3382c4d3ce9747385d528" dependencies = [ "anyhow", "ff", @@ -6674,6 +6675,7 @@ dependencies = [ [[package]] name = "vote-commitment-tree-client" version = "0.1.1" +source = "git+https://github.com/valargroup/zcash_voting.git?branch=greg%2Fzkp1-pir-prep#1aa07a149b6f4938ccf3382c4d3ce9747385d528" dependencies = [ "base64", "clap", @@ -6687,26 +6689,6 @@ dependencies = [ "vote-commitment-tree", ] -[[package]] -name = "voting-circuits" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7830eca6c294cff758bc4885ca8bd2fbdf90f9998f586c5c4cfe2d1b559555f6" -dependencies = [ - "blake2b_simd", - "ff", - "group", - "halo2_gadgets 0.4.0", - "halo2_poseidon", - "halo2_proofs", - "incrementalmerkletree", - "lazy_static", - "orchard", - "pasta_curves", - "rand 0.8.5", - "sinsemilla", -] - [[package]] name = "voting-circuits" version = "0.4.1" @@ -7586,7 +7568,8 @@ dependencies = [ [[package]] name = "zcash_voting" -version = "0.1.1" +version = "0.2.3" +source = "git+https://github.com/valargroup/zcash_voting.git?branch=greg%2Fzkp1-pir-prep#1aa07a149b6f4938ccf3382c4d3ce9747385d528" dependencies = [ "blake2b_simd", "ff", @@ -7608,7 +7591,7 @@ dependencies = [ "thiserror 2.0.17", "vote-commitment-tree", "vote-commitment-tree-client", - "voting-circuits 0.3.1", + "voting-circuits", "zcash_keys", "zcash_primitives", "zcash_protocol", diff --git a/Cargo.toml b/Cargo.toml index 34654002d..dbf0ace62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,7 @@ rust_decimal = { version = "1", default-features = false, features = ["c-repr"] # - The "static" feature is required for the "compression" default feature of arti-client. xz2 = { version = "0.1", features = ["static"] } -zcash_voting = { path = "../zcash_voting/zcash_voting" } +zcash_voting = { git = "https://github.com/valargroup/zcash_voting.git", branch = "greg/zkp1-pir-prep" } voting-circuits = { version = "0.4.1", features = ["share-reveal"] } zcash_keys = { version = "0.13", features = ["orchard"] } incrementalmerkletree = { version = "0.8", default-features = false } From 04d4fd630de6fcbc4bbe28885d404134a7b84ffe Mon Sep 17 00:00:00 2001 From: Greg Nagy Date: Fri, 1 May 2026 19:31:26 +0200 Subject: [PATCH 3/4] Document temporary zcash voting branch pin --- Cargo.lock | 6 +++--- Cargo.toml | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c4afda0c..20bfb3d85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6659,7 +6659,7 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "vote-commitment-tree" version = "0.1.1" -source = "git+https://github.com/valargroup/zcash_voting.git?branch=greg%2Fzkp1-pir-prep#1aa07a149b6f4938ccf3382c4d3ce9747385d528" +source = "git+https://github.com/valargroup/zcash_voting.git?branch=greg%2Fzkp1-pir-prep#fb31bb706ef943a9b8daa5a59663ea9eaf64a6c4" dependencies = [ "anyhow", "ff", @@ -6675,7 +6675,7 @@ dependencies = [ [[package]] name = "vote-commitment-tree-client" version = "0.1.1" -source = "git+https://github.com/valargroup/zcash_voting.git?branch=greg%2Fzkp1-pir-prep#1aa07a149b6f4938ccf3382c4d3ce9747385d528" +source = "git+https://github.com/valargroup/zcash_voting.git?branch=greg%2Fzkp1-pir-prep#fb31bb706ef943a9b8daa5a59663ea9eaf64a6c4" dependencies = [ "base64", "clap", @@ -7569,7 +7569,7 @@ dependencies = [ [[package]] name = "zcash_voting" version = "0.2.3" -source = "git+https://github.com/valargroup/zcash_voting.git?branch=greg%2Fzkp1-pir-prep#1aa07a149b6f4938ccf3382c4d3ce9747385d528" +source = "git+https://github.com/valargroup/zcash_voting.git?branch=greg%2Fzkp1-pir-prep#fb31bb706ef943a9b8daa5a59663ea9eaf64a6c4" dependencies = [ "blake2b_simd", "ff", diff --git a/Cargo.toml b/Cargo.toml index dbf0ace62..8474ac172 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ rust_decimal = { version = "1", default-features = false, features = ["c-repr"] # - The "static" feature is required for the "compression" default feature of arti-client. xz2 = { version = "0.1", features = ["static"] } +# TODO: switch back to a released zcash_voting version after the PIR precompute crate release. zcash_voting = { git = "https://github.com/valargroup/zcash_voting.git", branch = "greg/zkp1-pir-prep" } voting-circuits = { version = "0.4.1", features = ["share-reveal"] } zcash_keys = { version = "0.13", features = ["orchard"] } From 53d688b5062b1e7dda45c8ba436cbf96867ad8b9 Mon Sep 17 00:00:00 2001 From: Greg Nagy Date: Fri, 1 May 2026 20:11:36 +0200 Subject: [PATCH 4/4] Bump to the new zcash_voting --- Cargo.lock | 9 ++++++--- Cargo.toml | 3 +-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 20bfb3d85..b668855d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6659,7 +6659,8 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "vote-commitment-tree" version = "0.1.1" -source = "git+https://github.com/valargroup/zcash_voting.git?branch=greg%2Fzkp1-pir-prep#fb31bb706ef943a9b8daa5a59663ea9eaf64a6c4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fb2456313f571003c0bed8d38fb166d78da85653caf828823c1a82f4e1aef1" dependencies = [ "anyhow", "ff", @@ -6675,7 +6676,8 @@ dependencies = [ [[package]] name = "vote-commitment-tree-client" version = "0.1.1" -source = "git+https://github.com/valargroup/zcash_voting.git?branch=greg%2Fzkp1-pir-prep#fb31bb706ef943a9b8daa5a59663ea9eaf64a6c4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0267343903fef0eb494f3f6337318249c1daf420a9ea0e73dc7856de26040f1f" dependencies = [ "base64", "clap", @@ -7569,7 +7571,8 @@ dependencies = [ [[package]] name = "zcash_voting" version = "0.2.3" -source = "git+https://github.com/valargroup/zcash_voting.git?branch=greg%2Fzkp1-pir-prep#fb31bb706ef943a9b8daa5a59663ea9eaf64a6c4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5a74a2f4cfd32b9ac6e61767ce6c0513418d07692ef1222dead39df27ec335" dependencies = [ "blake2b_simd", "ff", diff --git a/Cargo.toml b/Cargo.toml index 8474ac172..8adbf8408 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,8 +80,7 @@ rust_decimal = { version = "1", default-features = false, features = ["c-repr"] # - The "static" feature is required for the "compression" default feature of arti-client. xz2 = { version = "0.1", features = ["static"] } -# TODO: switch back to a released zcash_voting version after the PIR precompute crate release. -zcash_voting = { git = "https://github.com/valargroup/zcash_voting.git", branch = "greg/zkp1-pir-prep" } +zcash_voting = "0.2.3" voting-circuits = { version = "0.4.1", features = ["share-reveal"] } zcash_keys = { version = "0.13", features = ["orchard"] } incrementalmerkletree = { version = "0.8", default-features = false }