bandersnatch_ffi/
lib.rs

1//! Bandersnatch VRF FFI for Julia
2//!
3//! Provides C-compatible functions for:
4//! - Computing ticket IDs from VRF output points
5//! - Verifying ring VRF signatures
6
7use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
8
9// ABI version for compatibility checks
10pub const BANDERSNATCH_FFI_VERSION_MAJOR: u32 = 0;
11pub const BANDERSNATCH_FFI_VERSION_MINOR: u32 = 1;
12pub const BANDERSNATCH_FFI_VERSION_PATCH: u32 = 0;
13
14/// Get FFI version (major << 16 | minor << 8 | patch)
15#[no_mangle]
16pub extern "C" fn bandersnatch_version() -> u32 {
17    (BANDERSNATCH_FFI_VERSION_MAJOR << 16)
18        | (BANDERSNATCH_FFI_VERSION_MINOR << 8)
19        | BANDERSNATCH_FFI_VERSION_PATCH
20}
21
22// Error codes
23pub const BANDERSNATCH_OK: i32 = 0;
24pub const BANDERSNATCH_ERR_NULL_PTR: i32 = -1;
25pub const BANDERSNATCH_ERR_INVALID_POINT: i32 = -2;
26pub const BANDERSNATCH_ERR_INVALID_OUTPUT: i32 = -3;
27pub const BANDERSNATCH_ERR_INVALID_PROOF: i32 = -4;
28pub const BANDERSNATCH_ERR_VERIFY_FAILED: i32 = -5;
29pub const BANDERSNATCH_ERR_INVALID_INPUT: i32 = -6;
30use ark_vrf::reexports::ark_serialize;
31use ark_vrf::ring::Verifier as VerifierTrait;
32use ark_vrf::suites::bandersnatch::{
33    AffinePoint, Input, Output, PcsParams, Public, RingCommitment, RingProof, RingProofParams,
34    RingVerifier as ArkRingVerifier,
35};
36use once_cell::sync::OnceCell;
37use std::slice;
38
39// Embed SRS parameters (will be downloaded during build)
40static SRS_PARAMS: OnceCell<PcsParams> = OnceCell::new();
41
42// SRS file is embedded at compile time
43const SRS: &[u8] = include_bytes!(concat!(
44    env!("CARGO_MANIFEST_DIR"),
45    "/parameters/zcash-srs-2-11-uncompressed.bin"
46));
47
48fn get_pcs_params() -> &'static PcsParams {
49    SRS_PARAMS.get_or_init(|| {
50        PcsParams::deserialize_uncompressed(&SRS[..])
51            .expect("Failed to deserialize embedded SRS parameters")
52    })
53}
54
55/// Compute ticket ID from VRF output point (first 32 bytes of signature)
56///
57/// # Arguments
58/// * `output_ptr` - Pointer to 32-byte VRF output point (compressed)
59/// * `ticket_id_ptr` - Pointer to 32-byte buffer for ticket ID output
60///
61/// # Returns
62/// * 0 on success, non-zero on error
63#[no_mangle]
64pub extern "C" fn bandersnatch_compute_ticket_id(
65    output_ptr: *const u8,
66    ticket_id_ptr: *mut u8,
67) -> i32 {
68    if output_ptr.is_null() || ticket_id_ptr.is_null() {
69        return BANDERSNATCH_ERR_NULL_PTR;
70    }
71
72    let output_bytes = unsafe { slice::from_raw_parts(output_ptr, 32) };
73    let ticket_id_out = unsafe { slice::from_raw_parts_mut(ticket_id_ptr, 32) };
74
75    // Deserialize the affine point
76    let affine = match AffinePoint::deserialize_compressed(&output_bytes[..]) {
77        Ok(p) => p,
78        Err(_) => return BANDERSNATCH_ERR_INVALID_POINT,
79    };
80
81    // Create Output and compute hash
82    let output = Output::from(affine);
83    let hash = output.hash();
84
85    // Copy first 32 bytes of hash to output
86    ticket_id_out.copy_from_slice(&hash[..32]);
87
88    BANDERSNATCH_OK
89}
90
91/// Opaque handle for ring verifier
92pub struct RingVerifierHandle {
93    verifier: ArkRingVerifier,
94}
95
96/// Create a ring verifier from commitment
97///
98/// # Arguments
99/// * `commitment_ptr` - Pointer to serialized ring commitment
100/// * `commitment_len` - Length of commitment data
101/// * `ring_size` - Number of keys in the ring
102///
103/// # Returns
104/// * Pointer to verifier handle on success, null on error
105#[no_mangle]
106pub extern "C" fn bandersnatch_ring_verifier_new(
107    commitment_ptr: *const u8,
108    commitment_len: usize,
109    ring_size: usize,
110) -> *mut RingVerifierHandle {
111    if commitment_ptr.is_null() || ring_size == 0 {
112        return std::ptr::null_mut();
113    }
114
115    let commitment_bytes = unsafe { slice::from_raw_parts(commitment_ptr, commitment_len) };
116
117    // Deserialize commitment
118    let commitment = match RingCommitment::deserialize_compressed(&commitment_bytes[..]) {
119        Ok(c) => c,
120        Err(_) => return std::ptr::null_mut(),
121    };
122
123    // Get PCS params and create ring proof params
124    let pc_params = get_pcs_params().clone();
125    let params = match RingProofParams::from_pcs_params(ring_size, pc_params) {
126        Ok(p) => p,
127        Err(_) => return std::ptr::null_mut(),
128    };
129
130    // Create verifier key from commitment, then create verifier
131    let verifier_key = params.verifier_key_from_commitment(commitment);
132    let verifier = params.verifier(verifier_key);
133
134    Box::into_raw(Box::new(RingVerifierHandle { verifier }))
135}
136
137/// Free a ring verifier handle
138#[no_mangle]
139pub extern "C" fn bandersnatch_ring_verifier_free(handle: *mut RingVerifierHandle) {
140    if !handle.is_null() {
141        unsafe {
142            drop(Box::from_raw(handle));
143        }
144    }
145}
146
147/// Verify a ring VRF signature
148///
149/// # Arguments
150/// * `handle` - Verifier handle from bandersnatch_ring_verifier_new
151/// * `data_ptr` - VRF input data (e.g., "jam_ticket_seal" + entropy + attempt)
152/// * `data_len` - Length of input data
153/// * `signature_ptr` - Ring VRF signature (784 bytes: 32 output + 752 proof)
154/// * `signature_len` - Length of signature
155/// * `ticket_id_ptr` - Optional output buffer for ticket ID (32 bytes), can be null
156///
157/// # Returns
158/// * 0 on success (valid signature), non-zero on error/invalid
159#[no_mangle]
160pub extern "C" fn bandersnatch_ring_verify(
161    handle: *const RingVerifierHandle,
162    data_ptr: *const u8,
163    data_len: usize,
164    signature_ptr: *const u8,
165    signature_len: usize,
166    ticket_id_ptr: *mut u8,
167) -> i32 {
168    if handle.is_null() || data_ptr.is_null() || signature_ptr.is_null() {
169        return BANDERSNATCH_ERR_NULL_PTR;
170    }
171
172    if signature_len < 32 {
173        return BANDERSNATCH_ERR_INVALID_POINT;
174    }
175
176    let handle = unsafe { &*handle };
177    let data = unsafe { slice::from_raw_parts(data_ptr, data_len) };
178    let signature = unsafe { slice::from_raw_parts(signature_ptr, signature_len) };
179
180    // Extract VRF output (first 32 bytes) and proof (rest)
181    let output_bytes = &signature[..32];
182    let proof_bytes = &signature[32..];
183
184    // Deserialize output point
185    let affine = match AffinePoint::deserialize_compressed(&output_bytes[..]) {
186        Ok(p) => p,
187        Err(_) => return BANDERSNATCH_ERR_INVALID_OUTPUT,
188    };
189    let output = Output::from(affine);
190
191    // Deserialize proof
192    let proof = match RingProof::deserialize_compressed(&proof_bytes[..]) {
193        Ok(p) => p,
194        Err(_) => return BANDERSNATCH_ERR_INVALID_PROOF,
195    };
196
197    // Create VRF input from data (hashes to curve point)
198    let input = match Input::new(data) {
199        Some(i) => i,
200        None => return BANDERSNATCH_ERR_INVALID_INPUT,
201    };
202
203    // Verify
204    let result = Public::verify(input, output.clone(), &[], &proof, &handle.verifier);
205
206    if result.is_err() {
207        return BANDERSNATCH_ERR_VERIFY_FAILED;
208    }
209
210    // Optionally compute and return ticket ID
211    if !ticket_id_ptr.is_null() {
212        let ticket_id_out = unsafe { slice::from_raw_parts_mut(ticket_id_ptr, 32) };
213        let hash = output.hash();
214        ticket_id_out.copy_from_slice(&hash[..32]);
215    }
216
217    BANDERSNATCH_OK
218}
219
220/// Compute ring commitment from public keys
221///
222/// # Arguments
223/// * `keys_ptr` - Pointer to array of 32-byte public keys (concatenated)
224/// * `num_keys` - Number of keys
225/// * `commitment_ptr` - Output buffer for commitment
226/// * `commitment_len` - Pointer to length (in: buffer size, out: actual size)
227///
228/// # Returns
229/// * 0 on success, non-zero on error
230#[no_mangle]
231pub extern "C" fn bandersnatch_compute_ring_commitment(
232    keys_ptr: *const u8,
233    num_keys: usize,
234    commitment_ptr: *mut u8,
235    commitment_len: *mut usize,
236) -> i32 {
237    if keys_ptr.is_null() || commitment_ptr.is_null() || commitment_len.is_null() || num_keys == 0 {
238        return BANDERSNATCH_ERR_NULL_PTR;
239    }
240
241    let keys_data = unsafe { slice::from_raw_parts(keys_ptr, num_keys * 32) };
242    let max_len = unsafe { *commitment_len };
243
244    // Parse keys
245    let mut parsed_keys = Vec::with_capacity(num_keys);
246    for i in 0..num_keys {
247        let key_bytes = &keys_data[i * 32..(i + 1) * 32];
248        let affine = AffinePoint::deserialize_compressed(&key_bytes[..])
249            .unwrap_or_else(|_| RingProofParams::padding_point());
250        parsed_keys.push(affine);
251    }
252
253    // Get PCS params and create ring proof params
254    let pc_params = get_pcs_params().clone();
255    let params = match RingProofParams::from_pcs_params(num_keys, pc_params) {
256        Ok(p) => p,
257        Err(_) => return BANDERSNATCH_ERR_INVALID_INPUT,
258    };
259
260    // Construct verifier key and get commitment
261    let verifier_key = params.verifier_key(&parsed_keys);
262    let commitment = verifier_key.commitment();
263
264    // Serialize commitment
265    let mut bytes = Vec::new();
266    if commitment.serialize_compressed(&mut bytes).is_err() {
267        return BANDERSNATCH_ERR_INVALID_OUTPUT;
268    }
269
270    if bytes.len() > max_len {
271        return BANDERSNATCH_ERR_INVALID_PROOF; // Buffer too small
272    }
273
274    let commitment_out = unsafe { slice::from_raw_parts_mut(commitment_ptr, bytes.len()) };
275    commitment_out.copy_from_slice(&bytes);
276    unsafe {
277        *commitment_len = bytes.len();
278    }
279
280    BANDERSNATCH_OK
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    #[test]
288    fn test_ticket_id_computation() {
289        // Test with a valid (but dummy) output - this will fail with invalid point
290        // In real usage, the output comes from a valid signature
291        let output = [0u8; 32];
292        let mut ticket_id = [0u8; 32];
293
294        // This should fail because zeros is not a valid curve point
295        let result = bandersnatch_compute_ticket_id(output.as_ptr(), ticket_id.as_mut_ptr());
296        assert_ne!(result, 0);
297    }
298}