Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
47 changes: 47 additions & 0 deletions Sources/ZcashLightClientKit/Rust/Voting/VotingRustBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<FfiBoxedSlice>? = 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
Expand Down
13 changes: 13 additions & 0 deletions Sources/ZcashLightClientKit/Rust/Voting/VotingTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
55 changes: 55 additions & 0 deletions rust/src/voting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,22 @@ impl From<voting::DelegationProofResult> for JsonDelegationProofResult {
}
}

/// JSON-serializable DelegationPirPrecomputeResult.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct JsonDelegationPirPrecomputeResult {
pub cached_count: u32,
pub fetched_count: u32,
}

impl From<voting::DelegationPirPrecomputeResult> 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 {
Expand Down Expand Up @@ -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<JsonNoteInfo> = serde_json::from_slice(notes_bytes)?;
let core_notes: Vec<voting::NoteInfo> = 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.
Expand Down
Loading