1use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
8
9pub 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#[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
22pub 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
39static SRS_PARAMS: OnceCell<PcsParams> = OnceCell::new();
41
42const 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#[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 let affine = match AffinePoint::deserialize_compressed(&output_bytes[..]) {
77 Ok(p) => p,
78 Err(_) => return BANDERSNATCH_ERR_INVALID_POINT,
79 };
80
81 let output = Output::from(affine);
83 let hash = output.hash();
84
85 ticket_id_out.copy_from_slice(&hash[..32]);
87
88 BANDERSNATCH_OK
89}
90
91pub struct RingVerifierHandle {
93 verifier: ArkRingVerifier,
94}
95
96#[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 let commitment = match RingCommitment::deserialize_compressed(&commitment_bytes[..]) {
119 Ok(c) => c,
120 Err(_) => return std::ptr::null_mut(),
121 };
122
123 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 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#[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#[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 let output_bytes = &signature[..32];
182 let proof_bytes = &signature[32..];
183
184 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 let proof = match RingProof::deserialize_compressed(&proof_bytes[..]) {
193 Ok(p) => p,
194 Err(_) => return BANDERSNATCH_ERR_INVALID_PROOF,
195 };
196
197 let input = match Input::new(data) {
199 Some(i) => i,
200 None => return BANDERSNATCH_ERR_INVALID_INPUT,
201 };
202
203 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 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#[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 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 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 let verifier_key = params.verifier_key(&parsed_keys);
262 let commitment = verifier_key.commitment();
263
264 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; }
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 let output = [0u8; 32];
292 let mut ticket_id = [0u8; 32];
293
294 let result = bandersnatch_compute_ticket_id(output.as_ptr(), ticket_id.as_mut_ptr());
296 assert_ne!(result, 0);
297 }
298}