diff --git a/Cargo.lock b/Cargo.lock index 8e552ca8a..b668855d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7570,9 +7570,9 @@ dependencies = [ [[package]] name = "zcash_voting" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f3ff3f3efe5addd1ac61e281863b4e4b9910d3250f2110d3da9b2bf5fae118" +checksum = "ed5a74a2f4cfd32b9ac6e61767ce6c0513418d07692ef1222dead39df27ec335" dependencies = [ "blake2b_simd", "ff", diff --git a/Cargo.toml b/Cargo.toml index 9e11df34e..8adbf8408 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 = "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 } 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.