Skip to main content

stoffel_vm/
cffi.rs

1//! C Foreign Function Interface (CFFI) for StoffelVM
2//!
3//! This module provides a C-compatible API for interacting with StoffelVM from languages
4//! like C, Go, Nim, JavaScript, Python, etc. It allows external code to:
5//!
6//! - Create and manage VM instances
7//! - Register foreign functions with the VM
8//! - Execute VM functions and retrieve results
9//! - Convert between VM and C-compatible types
10//!
11//! # Safety
12//!
13//! This module contains unsafe code due to the nature of FFI. Care must be taken when
14//! using these functions from other languages to ensure memory safety and proper resource
15//! management.
16//!
17//! # Example (C)
18//!
19//! ```c
20//! #include "stoffel_vm.h"
21//! #include <stdio.h>
22//!
23//! // Example C callback function
24//! int double_value(StoffelValue* args, int arg_count, StoffelValue* result) {
25//!     if (arg_count != 1 || args[0].value_type != STOFFEL_VALUE_INT) {
26//!         return -1; // Error
27//!     }
28//!     
29//!     result->value_type = STOFFEL_VALUE_INT;
30//!     result->data.int_val = args[0].data.int_val * 2;
31//!     return 0; // Success
32//! }
33//!
34//! int main() {
35//!     // Create a VM instance
36//!     VMHandle vm = stoffel_create_vm();
37//!     
38//!     // Register a foreign function
39//!     stoffel_register_foreign_function(vm, "double", double_value);
40//!     
41//!     // Execute a VM function
42//!     StoffelValue result;
43//!     int status = stoffel_execute(vm, "test_double", &result);
44//!     
45//!     // Clean up
46//!     stoffel_destroy_vm(vm);
47//!     return 0;
48//! }
49//! ```
50
51use std::ffi::{c_char, CStr, CString};
52use std::io::Cursor;
53use std::marker::PhantomPinned;
54use std::os::raw::{c_int, c_void};
55use std::ptr::NonNull;
56use std::sync::Arc;
57
58use crate::core_vm::VirtualMachine;
59use crate::foreign_functions::ForeignFunctionContext;
60use crate::net::hb_engine::{
61    HoneyBadgerEngineConfig, HoneyBadgerMpcEngine, HoneyBadgerPreprocessingConfig,
62};
63use crate::net::mpc_engine::MpcEngine;
64use stoffel_vm_types::compiled_binary::CompiledBinary;
65use stoffel_vm_types::core_types::{
66    ArrayRef, ForeignObjectRef, ObjectRef, ShareType, Value, DEFAULT_FIXED_POINT_FRACTIONAL_BITS,
67    F64,
68};
69use stoffelnet::transports::quic::QuicNetworkManager;
70
71/// Maximum share buffer size accepted from FFI callers (1 MB).
72/// Prevents accidental or malicious oversized allocations from C/SDK code.
73const MAX_FFI_SHARE_LEN: usize = 1_048_576;
74
75/// Write a `Vec<u8>` result through FFI out-pointers using the boxed-slice pattern.
76///
77/// The Vec is shrunk to exact length via `into_boxed_slice()`, then leaked as a raw
78/// pointer. The caller must free with the corresponding `*_free_bytes` function which
79/// reconstructs the `Box<[u8]>`.
80///
81/// # Safety
82/// `result_ptr` and `result_len_ptr` must be valid, non-null, aligned pointers.
83unsafe fn write_ffi_result_bytes(
84    bytes: Vec<u8>,
85    result_ptr: *mut *mut u8,
86    result_len_ptr: *mut usize,
87) {
88    let boxed = bytes.into_boxed_slice();
89    let len = boxed.len();
90    let ptr = Box::into_raw(boxed) as *mut u8;
91    *result_ptr = ptr;
92    *result_len_ptr = len;
93}
94
95/// Free bytes previously allocated via [`write_ffi_result_bytes`].
96///
97/// Reconstructs the `Box<[u8]>` and drops it.
98///
99/// # Safety
100/// `ptr` must have been produced by `write_ffi_result_bytes` with the matching `len`,
101/// and must not have been freed already.
102unsafe fn free_ffi_result_bytes(ptr: *mut u8, len: usize) {
103    if !ptr.is_null() {
104        let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, len));
105    }
106}
107
108/// Opaque pointer type for the VM
109pub type VMHandle = *mut c_void;
110
111/// Value types in StoffelVM exposed to C
112#[repr(C)]
113pub enum StoffelValueType {
114    /// Unit/void value
115    Unit = 0,
116    /// Integer value
117    Int = 1,
118    /// Float value
119    Float = 2,
120    /// Boolean value
121    Bool = 3,
122    /// String value
123    String = 4,
124    /// Object reference
125    Object = 5,
126    /// Array reference
127    Array = 6,
128    /// Foreign object reference
129    Foreign = 7,
130    /// Function closure
131    Closure = 8,
132}
133
134/// Union to hold the actual value data
135#[repr(C)]
136pub union StoffelValueData {
137    /// Integer value
138    pub int_val: i64,
139    /// Float value
140    pub float_val: f64,
141    /// Boolean value
142    pub bool_val: bool,
143    /// String value (C string)
144    pub string_val: *const c_char,
145    /// Object ID
146    pub object_id: usize,
147    /// Array ID
148    pub array_id: usize,
149    /// Foreign object ID
150    pub foreign_id: usize,
151    /// Closure ID (for future use)
152    pub closure_id: usize,
153}
154
155/// C-compatible representation of a StoffelVM value
156#[repr(C)]
157pub struct StoffelValue {
158    /// Type of the value
159    pub value_type: StoffelValueType,
160    /// Actual value data
161    pub data: StoffelValueData,
162}
163
164/// Type for C callback functions
165pub type CForeignFunction =
166    extern "C" fn(args: *const StoffelValue, arg_count: c_int, result: *mut StoffelValue) -> c_int;
167
168// ============================================================================
169// HoneyBadgerMpcEngine FFI Types
170// ============================================================================
171/// Opaque pointer type for HoneyBadgerMpcEngine
172///
173/// This type is used to pass engine handles across the FFI boundary.
174/// The actual `Arc<HoneyBadgerMpcEngine>` is stored inside a Box for stable pointer.
175#[repr(C)]
176pub struct HBEngineOpaque {
177    _data: (),
178    _marker: core::marker::PhantomData<(*mut u8, PhantomPinned)>,
179}
180/// Opaque pointer type for QuicNetworkManager
181///
182/// This type is used to pass network handles across the FFI boundary.
183#[repr(C)]
184pub struct HBNetworkOpaque {
185    _data: (),
186    _marker: core::marker::PhantomData<(*mut u8, PhantomPinned)>,
187}
188/// Error codes for HoneyBadgerMpcEngine FFI operations
189#[repr(C)]
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub enum HBEngineErrorCode {
192    /// Operation succeeded
193    HBEngineSuccess = 0,
194    /// Null pointer provided
195    HBEngineNullPointer = 1,
196    /// Engine not ready (preprocessing not complete)
197    HBEngineNotReady = 2,
198    /// Network error during MPC operation
199    HBEngineNetworkError = 3,
200    /// Preprocessing failed
201    HBEnginePreprocessingFailed = 4,
202    /// Multiplication operation failed
203    HBEngineMultiplyFailed = 5,
204    /// Share opening/reconstruction failed
205    HBEngineOpenShareFailed = 6,
206    /// Serialization/deserialization error
207    HBEngineSerializationError = 7,
208    /// Invalid share type provided
209    HBEngineInvalidShareType = 8,
210    /// Client input initialization failed
211    HBEngineClientInputFailed = 9,
212    /// Client shares retrieval failed
213    HBEngineGetClientSharesFailed = 10,
214    /// Tokio runtime creation failed
215    HBEngineRuntimeError = 11,
216    /// Invalid configuration parameters
217    HBEngineInvalidConfig = 12,
218}
219
220/// C-compatible representation of ShareType
221///
222/// The `kind` field indicates the type:
223/// - 0 = Int (width is the value)
224/// - 1 = Bool (width is 1 for true, 0 for false)
225/// - 2 = Float (width is the value)
226#[repr(C)]
227#[derive(Debug, Clone, Copy)]
228pub struct CShareType {
229    /// Type kind: 0=Int, 1=Bool, 2=Float
230    pub kind: u8,
231    /// Width/value depending on kind
232    pub width: i64,
233}
234
235impl TryFrom<CShareType> for ShareType {
236    type Error = String;
237
238    fn try_from(c: CShareType) -> Result<Self, Self::Error> {
239        match c.kind {
240            // kind 0 = SecretInt with bit_length
241            0 => {
242                let bit_length = positive_ffi_width(c.width, "secret integer bit length")?;
243                ShareType::try_secret_int(bit_length).map_err(String::from)
244            }
245            // kind 1 = Boolean (1-bit SecretInt)
246            1 => Ok(ShareType::boolean()),
247            // kind 2 = SecretFixedPoint (width encodes total_bits, fractional_bits assumed default)
248            2 => {
249                let total_bits = positive_ffi_width(c.width, "fixed-point total bits")?;
250                if total_bits <= 1 {
251                    return Err("fixed-point total bits must be greater than one".to_string());
252                }
253                let fractional_bits = DEFAULT_FIXED_POINT_FRACTIONAL_BITS.min(total_bits - 1);
254                ShareType::try_secret_fixed_point_from_bits(total_bits, fractional_bits)
255                    .map_err(String::from)
256            }
257            // kind 3 = SecretUInt with bit_length
258            3 => {
259                let bit_length = positive_ffi_width(c.width, "secret unsigned integer bit length")?;
260                ShareType::try_secret_uint(bit_length).map_err(String::from)
261            }
262            _ => Err(format!("unknown share type kind {}", c.kind)),
263        }
264    }
265}
266
267impl TryFrom<ShareType> for CShareType {
268    type Error = String;
269
270    fn try_from(st: ShareType) -> Result<Self, Self::Error> {
271        match st {
272            ShareType::SecretInt { bit_length } => Ok(CShareType {
273                kind: if bit_length == 1 { 1 } else { 0 },
274                width: i64::try_from(bit_length)
275                    .map_err(|_| "secret integer bit length exceeds C ABI range".to_string())?,
276            }),
277            ShareType::SecretUInt { bit_length } => Ok(CShareType {
278                kind: 3,
279                width: i64::try_from(bit_length).map_err(|_| {
280                    "secret unsigned integer bit length exceeds C ABI range".to_string()
281                })?,
282            }),
283            ShareType::SecretFixedPoint { precision } => Ok(CShareType {
284                kind: 2,
285                width: i64::try_from(precision.k())
286                    .map_err(|_| "fixed-point total bits exceeds C ABI range".to_string())?,
287            }),
288        }
289    }
290}
291
292fn positive_ffi_width(width: i64, name: &str) -> Result<usize, String> {
293    let value = usize::try_from(width).map_err(|_| format!("{name} must be non-negative"))?;
294    if value == 0 {
295        return Err(format!("{name} must be positive"));
296    }
297    Ok(value)
298}
299
300/// Creates a new VM instance
301///
302/// # Returns
303///
304/// A handle to the VM instance, or NULL if creation failed
305///
306/// # Safety
307///
308/// The returned handle must be freed with `stoffel_destroy_vm` to avoid memory leaks.
309#[no_mangle]
310pub extern "C" fn stoffel_create_vm() -> VMHandle {
311    let vm = Box::new(VirtualMachine::new());
312    Box::into_raw(vm) as VMHandle
313}
314
315/// Destroys a VM instance
316///
317/// # Arguments
318///
319/// * `handle` - Handle to the VM instance
320///
321/// # Safety
322///
323/// `handle` must be null or a live handle returned by `stoffel_create_vm`.
324/// It must not be used after this function is called, and it must not be freed
325/// more than once.
326#[no_mangle]
327pub unsafe extern "C" fn stoffel_destroy_vm(handle: VMHandle) {
328    if !handle.is_null() {
329        unsafe {
330            let _ = Box::from_raw(handle as *mut VirtualMachine);
331        }
332    }
333}
334
335/// Executes a VM function and returns the result
336///
337/// # Arguments
338///
339/// * `handle` - Handle to the VM instance
340/// * `function_name` - Name of the function to execute
341/// * `result` - Pointer to a StoffelValue to store the result
342///
343/// # Returns
344///
345/// 0 on success, non-zero on error
346///
347/// # Safety
348///
349/// `handle` must be a live VM handle returned by `stoffel_create_vm`.
350/// The function_name must be a valid null-terminated C string.
351/// The result pointer must be valid and point to enough memory to store a StoffelValue.
352#[no_mangle]
353pub unsafe extern "C" fn stoffel_execute(
354    handle: VMHandle,
355    function_name: *const c_char,
356    result: *mut StoffelValue,
357) -> c_int {
358    if handle.is_null() || function_name.is_null() || result.is_null() {
359        return -1;
360    }
361
362    let vm = unsafe { &mut *(handle as *mut VirtualMachine) };
363    let c_str = unsafe { CStr::from_ptr(function_name) };
364    let function_name = match c_str.to_str() {
365        Ok(s) => s,
366        Err(_) => return -2,
367    };
368
369    match vm.execute(function_name) {
370        Ok(value) => match value_to_stoffel_value(&value) {
371            Ok(converted) => {
372                unsafe {
373                    *result = converted;
374                }
375                0
376            }
377            Err(_) => -4,
378        },
379        Err(_) => -3,
380    }
381}
382
383/// Executes a VM function with arguments and returns the result
384///
385/// # Arguments
386///
387/// * `handle` - Handle to the VM instance
388/// * `function_name` - Name of the function to execute
389/// * `args` - Array of StoffelValue arguments
390/// * `arg_count` - Number of arguments
391/// * `result` - Pointer to a StoffelValue to store the result
392///
393/// # Returns
394///
395/// 0 on success, non-zero on error
396///
397/// # Safety
398///
399/// `handle` must be a live VM handle returned by `stoffel_create_vm`.
400/// The function_name must be a valid null-terminated C string.
401/// The args pointer must be valid and point to at least arg_count StoffelValue structs.
402/// The result pointer must be valid and point to enough memory to store a StoffelValue.
403#[no_mangle]
404pub unsafe extern "C" fn stoffel_execute_with_args(
405    handle: VMHandle,
406    function_name: *const c_char,
407    args: *const StoffelValue,
408    arg_count: c_int,
409    result: *mut StoffelValue,
410) -> c_int {
411    if handle.is_null()
412        || function_name.is_null()
413        || (arg_count > 0 && args.is_null())
414        || result.is_null()
415    {
416        return -1;
417    }
418
419    let vm = unsafe { &mut *(handle as *mut VirtualMachine) };
420    let c_str = unsafe { CStr::from_ptr(function_name) };
421    let function_name = match c_str.to_str() {
422        Ok(s) => s,
423        Err(_) => return -2,
424    };
425
426    // Guard against unreasonable arg counts from C callers
427    let arg_count = match usize::try_from(arg_count) {
428        Ok(count) if count <= 1024 => count,
429        _ => return -1,
430    };
431
432    if arg_count > 0 && args.is_null() {
433        return -1;
434    }
435
436    // Convert C args to Rust Values
437    let mut rust_args = Vec::with_capacity(arg_count);
438    for i in 0..arg_count {
439        let arg = unsafe { &*args.add(i) };
440        match stoffel_value_to_value(arg) {
441            Ok(value) => rust_args.push(value),
442            Err(_) => return -3,
443        }
444    }
445
446    match vm.execute_with_args(function_name, &rust_args) {
447        Ok(value) => match value_to_stoffel_value(&value) {
448            Ok(converted) => {
449                unsafe {
450                    *result = converted;
451                }
452                0
453            }
454            Err(_) => -5,
455        },
456        Err(_) => -4,
457    }
458}
459
460/// Wrapper for C foreign functions to be called from Rust
461struct CForeignFunctionWrapper {
462    func: CForeignFunction,
463}
464
465impl CForeignFunctionWrapper {
466    fn call(&self, ctx: ForeignFunctionContext) -> Result<Value, String> {
467        // Convert Rust args to C args
468        let c_args: Vec<StoffelValue> = ctx
469            .args()
470            .iter()
471            .map(value_to_stoffel_value)
472            .collect::<Result<Vec<_>, _>>()?;
473
474        let mut result = StoffelValue {
475            value_type: StoffelValueType::Unit,
476            data: StoffelValueData { int_val: 0 },
477        };
478
479        // Call the C function
480        let arg_count = c_int::try_from(c_args.len())
481            .map_err(|_| "Too many C callback arguments".to_string())?;
482        let status = (self.func)(c_args.as_ptr(), arg_count, &mut result);
483
484        if status != 0 {
485            return Err(format!("Foreign function returned error code: {}", status));
486        }
487
488        // Convert C result back to Rust
489        stoffel_value_to_value(&result)
490    }
491}
492
493/// Registers a C function with the VM
494///
495/// # Arguments
496///
497/// * `handle` - Handle to the VM instance
498/// * `name` - Name of the function to register
499/// * `func` - Pointer to the C function
500///
501/// # Returns
502///
503/// 0 on success, non-zero on error
504///
505/// # Safety
506///
507/// `handle` must be a live VM handle returned by `stoffel_create_vm`.
508/// The name must be a valid null-terminated C string.
509/// The func pointer must be valid and point to a function with the correct signature.
510#[no_mangle]
511pub unsafe extern "C" fn stoffel_register_foreign_function(
512    handle: VMHandle,
513    name: *const c_char,
514    func: CForeignFunction,
515) -> c_int {
516    if handle.is_null() || name.is_null() {
517        return -1;
518    }
519
520    let vm = unsafe { &mut *(handle as *mut VirtualMachine) };
521    let c_str = unsafe { CStr::from_ptr(name) };
522    let name = match c_str.to_str() {
523        Ok(s) => s,
524        Err(_) => return -2,
525    };
526
527    let wrapper = CForeignFunctionWrapper { func };
528
529    match vm.try_register_foreign_function(name, move |ctx| wrapper.call(ctx)) {
530        Ok(()) => 0,
531        Err(_) => -3,
532    }
533}
534
535/// Thread-safe wrapper for C pointers
536///
537/// This struct wraps a raw pointer in a way that makes it safe to share
538/// between threads. The actual safety is the responsibility of the C code
539/// that manages the pointer.
540///
541/// # Safety
542///
543/// The user must ensure that the pointer remains valid for the lifetime of this wrapper
544/// and that any operations on the pointer are thread-safe.
545#[allow(dead_code)]
546struct CForeignObject {
547    ptr: NonNull<c_void>,
548}
549
550// SAFETY: FFI callers opt into sharing this opaque pointer across VM threads.
551// The VM never dereferences it directly; synchronization and validity are the
552// responsibility of the foreign object implementation.
553unsafe impl Send for CForeignObject {}
554unsafe impl Sync for CForeignObject {}
555
556impl CForeignObject {
557    /// Creates a new CForeignObject from a raw pointer
558    ///
559    /// # Safety
560    ///
561    /// The pointer must be valid and must remain valid for the lifetime of the wrapper.
562    fn new(ptr: *mut c_void) -> Option<Self> {
563        NonNull::new(ptr).map(|ptr| CForeignObject { ptr })
564    }
565
566    /// Gets the raw pointer
567    ///
568    /// # Safety
569    ///
570    /// The caller must ensure that any operations on the pointer are thread-safe.
571    #[allow(dead_code)]
572    fn get_ptr(&self) -> *mut c_void {
573        self.ptr.as_ptr()
574    }
575}
576
577/// Registers a foreign object with the VM
578///
579/// # Arguments
580///
581/// * `handle` - Handle to the VM instance
582/// * `object` - Pointer to the object
583/// * `result` - Pointer to a StoffelValue to store the result
584///
585/// # Returns
586///
587/// 0 on success, non-zero on error
588///
589/// # Safety
590///
591/// `handle` must be a live VM handle returned by `stoffel_create_vm`.
592/// The object pointer must be valid and must remain valid for the lifetime of the VM.
593/// The result pointer must be valid and point to enough memory to store a StoffelValue.
594///
595/// # Notes
596///
597/// This function wraps the raw pointer in a thread-safe wrapper that implements
598/// Send and Sync. The actual safety of the pointer is the responsibility of the
599/// C code that manages it.
600#[no_mangle]
601pub unsafe extern "C" fn stoffel_register_foreign_object(
602    handle: VMHandle,
603    object: *mut c_void,
604    result: *mut StoffelValue,
605) -> c_int {
606    if handle.is_null() || object.is_null() || result.is_null() {
607        return -1;
608    }
609
610    let vm = unsafe { &mut *(handle as *mut VirtualMachine) };
611
612    // Create a thread-safe wrapper around the raw pointer
613    let foreign_object = match CForeignObject::new(object) {
614        Some(object) => object,
615        None => return -1,
616    };
617
618    // Register the wrapped object with the VM
619    let value = match vm.try_register_foreign_object_value(foreign_object) {
620        Ok(value) => value,
621        Err(_) => return -2,
622    };
623
624    // Convert the result to a StoffelValue
625    let converted = match value_to_stoffel_value(&value) {
626        Ok(converted) => converted,
627        Err(_) => return -2,
628    };
629    unsafe {
630        *result = converted;
631    }
632
633    0
634}
635
636/// Creates a new string in the VM
637///
638/// # Arguments
639///
640/// * `handle` - Handle to the VM instance
641/// * `str` - Pointer to a null-terminated C string
642/// * `result` - Pointer to a StoffelValue to store the result
643///
644/// # Returns
645///
646/// 0 on success, non-zero on error
647///
648/// # Safety
649///
650/// `handle` must be a live VM handle returned by `stoffel_create_vm`.
651/// The str pointer must be a valid null-terminated C string.
652/// The result pointer must be valid and point to enough memory to store a StoffelValue.
653#[no_mangle]
654pub unsafe extern "C" fn stoffel_create_string(
655    handle: VMHandle,
656    str: *const c_char,
657    result: *mut StoffelValue,
658) -> c_int {
659    if handle.is_null() || str.is_null() || result.is_null() {
660        return -1;
661    }
662
663    let c_str = unsafe { CStr::from_ptr(str) };
664    let rust_str = match c_str.to_str() {
665        Ok(s) => s,
666        Err(_) => return -2,
667    };
668
669    let value = Value::String(rust_str.to_string());
670
671    let converted = match value_to_stoffel_value(&value) {
672        Ok(converted) => converted,
673        Err(_) => return -3,
674    };
675    unsafe {
676        *result = converted;
677    }
678
679    0
680}
681
682/// Converts a Rust Value to a C-compatible StoffelValue
683///
684/// # Arguments
685///
686/// * `value` - Reference to a Rust Value
687///
688/// # Returns
689///
690/// A C-compatible StoffelValue or an error if no safe ABI conversion exists
691fn value_to_stoffel_value(value: &Value) -> Result<StoffelValue, String> {
692    match value {
693        Value::Unit => Ok(StoffelValue {
694            value_type: StoffelValueType::Unit,
695            data: StoffelValueData { int_val: 0 },
696        }),
697        Value::I64(n) => Ok(StoffelValue {
698            value_type: StoffelValueType::Int,
699            data: StoffelValueData { int_val: *n },
700        }),
701        Value::I32(n) => Ok(StoffelValue {
702            value_type: StoffelValueType::Int,
703            data: StoffelValueData {
704                int_val: i64::from(*n),
705            },
706        }),
707        Value::I16(n) => Ok(StoffelValue {
708            value_type: StoffelValueType::Int,
709            data: StoffelValueData {
710                int_val: i64::from(*n),
711            },
712        }),
713        Value::I8(n) => Ok(StoffelValue {
714            value_type: StoffelValueType::Int,
715            data: StoffelValueData {
716                int_val: i64::from(*n),
717            },
718        }),
719        Value::U8(n) => Ok(StoffelValue {
720            value_type: StoffelValueType::Int,
721            data: StoffelValueData {
722                int_val: i64::from(*n),
723            },
724        }),
725        Value::U16(n) => Ok(StoffelValue {
726            value_type: StoffelValueType::Int,
727            data: StoffelValueData {
728                int_val: i64::from(*n),
729            },
730        }),
731        Value::U32(n) => Ok(StoffelValue {
732            value_type: StoffelValueType::Int,
733            data: StoffelValueData {
734                int_val: i64::from(*n),
735            },
736        }),
737        Value::U64(n) => {
738            let int_val =
739                i64::try_from(*n).map_err(|_| format!("u64 value {n} exceeds C ABI int range"))?;
740            Ok(StoffelValue {
741                value_type: StoffelValueType::Int,
742                data: StoffelValueData { int_val },
743            })
744        }
745        Value::Float(f) => Ok(StoffelValue {
746            value_type: StoffelValueType::Float,
747            data: StoffelValueData { float_val: f.0 },
748        }),
749        Value::Bool(b) => Ok(StoffelValue {
750            value_type: StoffelValueType::Bool,
751            data: StoffelValueData { bool_val: *b },
752        }),
753        Value::String(s) => {
754            // Note: This creates a memory leak as we're not freeing the CString
755            // In a real implementation, you would need to handle this properly
756            let c_string = CString::new(s.as_str())
757                .map_err(|_| "String contains interior null byte".to_string())?;
758            let ptr = c_string.into_raw();
759            Ok(StoffelValue {
760                value_type: StoffelValueType::String,
761                data: StoffelValueData { string_val: ptr },
762            })
763        }
764        Value::Object(object_ref) => Ok(StoffelValue {
765            value_type: StoffelValueType::Object,
766            data: StoffelValueData {
767                object_id: object_ref.id(),
768            },
769        }),
770        Value::Array(array_ref) => Ok(StoffelValue {
771            value_type: StoffelValueType::Array,
772            data: StoffelValueData {
773                array_id: array_ref.id(),
774            },
775        }),
776        Value::Foreign(foreign_ref) => Ok(StoffelValue {
777            value_type: StoffelValueType::Foreign,
778            data: StoffelValueData {
779                foreign_id: foreign_ref.id(),
780            },
781        }),
782        Value::Closure(_) => Ok(StoffelValue {
783            value_type: StoffelValueType::Closure,
784            data: StoffelValueData { closure_id: 0 }, // Simplified
785        }),
786        Value::Share(_, _) => Err("Cannot convert secret share value to C ABI".to_string()),
787    }
788}
789
790/// Converts a C-compatible StoffelValue to a Rust Value
791///
792/// # Arguments
793///
794/// * `value` - Reference to a C-compatible StoffelValue
795///
796/// # Returns
797///
798/// A Result containing either the converted Rust Value or an error message
799fn stoffel_value_to_value(value: &StoffelValue) -> Result<Value, String> {
800    match value.value_type {
801        StoffelValueType::Unit => Ok(Value::Unit),
802        StoffelValueType::Int => unsafe { Ok(Value::I64(value.data.int_val)) },
803        StoffelValueType::Float => unsafe { Ok(Value::Float(F64(value.data.float_val))) },
804        StoffelValueType::Bool => unsafe { Ok(Value::Bool(value.data.bool_val)) },
805        StoffelValueType::String => unsafe {
806            if value.data.string_val.is_null() {
807                return Err("Null string pointer".to_string());
808            }
809            let c_str = CStr::from_ptr(value.data.string_val);
810            match c_str.to_str() {
811                Ok(s) => Ok(Value::String(s.to_string())),
812                Err(_) => Err("Invalid UTF-8 in string".to_string()),
813            }
814        },
815        StoffelValueType::Object => unsafe {
816            Ok(Value::from(ObjectRef::new(value.data.object_id)))
817        },
818        StoffelValueType::Array => unsafe { Ok(Value::from(ArrayRef::new(value.data.array_id))) },
819        StoffelValueType::Foreign => unsafe {
820            Ok(Value::from(ForeignObjectRef::new(value.data.foreign_id)))
821        },
822        StoffelValueType::Closure => Err("Closure conversion not implemented".to_string()),
823    }
824}
825
826/// Frees a string created by the VM
827///
828/// # Arguments
829///
830/// * `str` - Pointer to a C string created by the VM
831///
832/// # Safety
833///
834/// The str pointer must have been created by stoffel_create_string or similar functions.
835/// After this function is called, the pointer must not be used.
836#[no_mangle]
837pub unsafe extern "C" fn stoffel_free_string(str: *mut c_char) {
838    if !str.is_null() {
839        unsafe {
840            let _ = CString::from_raw(str);
841        }
842    }
843}
844
845/// Loads bytecode into the VM
846///
847/// # Arguments
848///
849/// * `handle` - Handle to the VM instance
850/// * `bytecode` - Pointer to bytecode data
851/// * `bytecode_len` - Length of bytecode data in bytes
852///
853/// # Returns
854///
855/// 0 on success, non-zero on error:
856/// - -1: Null handle or bytecode pointer
857/// - -2: Bytecode deserialization failed
858/// - -3: Function registration failed
859///
860/// # Safety
861///
862/// `handle` must be a live VM handle returned by `stoffel_create_vm`.
863/// The bytecode pointer must be valid and point to at least `bytecode_len` bytes.
864#[no_mangle]
865pub unsafe extern "C" fn stoffel_load_bytecode(
866    handle: VMHandle,
867    bytecode: *const u8,
868    bytecode_len: usize,
869) -> c_int {
870    if handle.is_null() || bytecode.is_null() {
871        return -1;
872    }
873
874    let vm = unsafe { &mut *(handle as *mut VirtualMachine) };
875
876    // Create a byte slice from the raw pointer
877    let bytes = unsafe { std::slice::from_raw_parts(bytecode, bytecode_len) };
878
879    // Deserialize the compiled binary
880    let mut cursor = Cursor::new(bytes);
881    let compiled_binary = match CompiledBinary::deserialize(&mut cursor) {
882        Ok(binary) => binary,
883        Err(_) => return -2,
884    };
885
886    // Convert to VM functions and register them
887    let vm_functions = match compiled_binary.try_to_vm_functions() {
888        Ok(functions) => functions,
889        Err(_) => return -3,
890    };
891
892    for function in vm_functions {
893        if vm.try_register_function(function).is_err() {
894            return -3;
895        }
896    }
897
898    0
899}
900
901// ============================================================================
902// HoneyBadgerMpcEngine FFI Functions
903// ============================================================================
904/// Creates a new HoneyBadgerMpcEngine
905///
906/// # Arguments
907///
908/// * `instance_id` - Unique identifier for this MPC instance
909/// * `party_id` - This party's ID (0 to n-1)
910/// * `n` - Total number of parties
911/// * `t` - Threshold (corruption tolerance)
912/// * `n_triples` - Number of Beaver triples to generate
913/// * `n_random` - Number of random shares to generate
914/// * `network_ptr` - Pointer to HBNetworkOpaque (QuicNetworkManager)
915///
916/// # Returns
917///
918/// Pointer to opaque engine handle, or null on failure
919///
920/// # Safety
921///
922/// `network_ptr` must be a valid, live pointer to an `HBNetworkOpaque`
923/// allocated by this FFI layer.
924#[no_mangle]
925pub unsafe extern "C" fn hb_engine_new(
926    instance_id: u64,
927    party_id: usize,
928    n: usize,
929    t: usize,
930    n_triples: usize,
931    n_random: usize,
932    network_ptr: *mut HBNetworkOpaque,
933) -> *mut HBEngineOpaque {
934    if network_ptr.is_null() {
935        return std::ptr::null_mut();
936    }
937
938    // Extract Arc<QuicNetworkManager> from the opaque pointer
939    let net: Arc<QuicNetworkManager> = unsafe {
940        let boxed = &*(network_ptr as *const Arc<QuicNetworkManager>);
941        Arc::clone(boxed)
942    };
943
944    let session = match crate::net::MpcSessionConfig::try_new(instance_id, party_id, n, t, net) {
945        Ok(session) => session,
946        Err(_) => return std::ptr::null_mut(),
947    };
948    let config = HoneyBadgerEngineConfig::new(
949        session,
950        HoneyBadgerPreprocessingConfig::new(n_triples, n_random),
951    );
952
953    match HoneyBadgerMpcEngine::<ark_bls12_381::Fr, ark_bls12_381::G1Projective>::from_config(
954        config,
955    ) {
956        Ok(engine) => {
957            // engine is Arc<HoneyBadgerMpcEngine>, box it for stable FFI pointer
958            Box::into_raw(Box::new(engine)) as *mut HBEngineOpaque
959        }
960        Err(_) => std::ptr::null_mut(),
961    }
962}
963/// Frees a HoneyBadgerMpcEngine instance
964///
965/// # Safety
966///
967/// `engine_ptr` must be null or a live pointer created by `hb_engine_new`.
968/// It must not be used after this call, and it must not be freed more than once.
969#[no_mangle]
970pub unsafe extern "C" fn hb_engine_free(engine_ptr: *mut HBEngineOpaque) {
971    if !engine_ptr.is_null() {
972        unsafe {
973            let _ = Box::from_raw(engine_ptr as *mut Arc<HoneyBadgerMpcEngine>);
974        }
975    }
976}
977/// Runs preprocessing (generates Beaver triples and random shares)
978///
979/// This is a blocking call that runs the async preprocessing protocol.
980/// Must be called before any computation operations.
981///
982/// # Returns
983///
984/// * HBEngineSuccess on success
985/// * Error code on failure
986///
987/// # Safety
988///
989/// `engine_ptr` must be null or a live pointer created by `hb_engine_new`.
990#[no_mangle]
991pub unsafe extern "C" fn hb_engine_start_async(
992    engine_ptr: *mut HBEngineOpaque,
993) -> HBEngineErrorCode {
994    if engine_ptr.is_null() {
995        return HBEngineErrorCode::HBEngineNullPointer;
996    }
997
998    let engine = unsafe { &*(engine_ptr as *const Arc<HoneyBadgerMpcEngine>) };
999
1000    // Create tokio runtime for blocking on async
1001    let rt = match tokio::runtime::Runtime::new() {
1002        Ok(rt) => rt,
1003        Err(_) => return HBEngineErrorCode::HBEngineRuntimeError,
1004    };
1005
1006    match rt.block_on(engine.start_async()) {
1007        Ok(()) => HBEngineErrorCode::HBEngineSuccess,
1008        Err(_) => HBEngineErrorCode::HBEnginePreprocessingFailed,
1009    }
1010}
1011/// Checks if the engine is ready (preprocessing complete)
1012///
1013/// # Returns
1014///
1015/// 1 if ready, 0 if not ready or null pointer
1016///
1017/// # Safety
1018///
1019/// `engine_ptr` must be null or a live pointer created by `hb_engine_new`.
1020#[no_mangle]
1021pub unsafe extern "C" fn hb_engine_is_ready(engine_ptr: *mut HBEngineOpaque) -> c_int {
1022    if engine_ptr.is_null() {
1023        return 0;
1024    }
1025
1026    let engine = unsafe { &*(engine_ptr as *const Arc<HoneyBadgerMpcEngine>) };
1027    if engine.is_ready() {
1028        1
1029    } else {
1030        0
1031    }
1032}
1033/// Performs secure multiplication of two shares
1034///
1035/// # Arguments
1036///
1037/// * `engine_ptr` - Engine handle
1038/// * `share_type` - Type information for the shares
1039/// * `left_ptr` - Pointer to left share bytes
1040/// * `left_len` - Length of left share
1041/// * `right_ptr` - Pointer to right share bytes
1042/// * `right_len` - Length of right share
1043/// * `result_ptr` - Output: pointer to result bytes (caller must free with hb_free_bytes)
1044/// * `result_len_ptr` - Output: length of result bytes
1045///
1046/// # Returns
1047///
1048/// * HBEngineSuccess on success, error code on failure
1049///
1050/// # Safety
1051///
1052/// `engine_ptr` must be a live pointer created by `hb_engine_new`.
1053/// `left_ptr` and `right_ptr` must point to `left_len` and `right_len` bytes
1054/// respectively. `result_ptr` and `result_len_ptr` must be valid output
1055/// pointers; successful results must be released with `hb_free_bytes`.
1056#[no_mangle]
1057pub unsafe extern "C" fn hb_engine_multiply_share_async(
1058    engine_ptr: *mut HBEngineOpaque,
1059    share_type: CShareType,
1060    left_ptr: *const u8,
1061    left_len: usize,
1062    right_ptr: *const u8,
1063    right_len: usize,
1064    result_ptr: *mut *mut u8,
1065    result_len_ptr: *mut usize,
1066) -> HBEngineErrorCode {
1067    if engine_ptr.is_null()
1068        || left_ptr.is_null()
1069        || right_ptr.is_null()
1070        || result_ptr.is_null()
1071        || result_len_ptr.is_null()
1072    {
1073        return HBEngineErrorCode::HBEngineNullPointer;
1074    }
1075
1076    if left_len > MAX_FFI_SHARE_LEN || right_len > MAX_FFI_SHARE_LEN {
1077        return HBEngineErrorCode::HBEngineInvalidShareType;
1078    }
1079    let engine = unsafe { &*(engine_ptr as *const Arc<HoneyBadgerMpcEngine>) };
1080    let left = unsafe { std::slice::from_raw_parts(left_ptr, left_len) };
1081    let right = unsafe { std::slice::from_raw_parts(right_ptr, right_len) };
1082    let ty = match ShareType::try_from(share_type) {
1083        Ok(ty) => ty,
1084        Err(_) => return HBEngineErrorCode::HBEngineInvalidShareType,
1085    };
1086
1087    let rt = match tokio::runtime::Runtime::new() {
1088        Ok(rt) => rt,
1089        Err(_) => return HBEngineErrorCode::HBEngineRuntimeError,
1090    };
1091
1092    match rt.block_on(engine.multiply_share_async(ty, left, right)) {
1093        Ok(result) => {
1094            unsafe {
1095                write_ffi_result_bytes(result.into_bytes(), result_ptr, result_len_ptr);
1096            }
1097            HBEngineErrorCode::HBEngineSuccess
1098        }
1099        Err(_) => HBEngineErrorCode::HBEngineMultiplyFailed,
1100    }
1101}
1102/// Opens (reconstructs) a shared value
1103///
1104/// # Arguments
1105///
1106/// * `engine_ptr` - Engine handle
1107/// * `share_type` - Type information for the share
1108/// * `share_ptr` - Pointer to share bytes
1109/// * `share_len` - Length of share bytes
1110/// * `result_ptr` - Output: StoffelValue containing the reconstructed value
1111///
1112/// # Returns
1113///
1114/// * HBEngineSuccess on success, error code on failure
1115///
1116/// # Safety
1117///
1118/// `engine_ptr` must be a live pointer created by `hb_engine_new`.
1119/// `share_ptr` must point to `share_len` bytes, and `result_ptr` must be a
1120/// valid output pointer.
1121#[no_mangle]
1122pub unsafe extern "C" fn hb_engine_open_share(
1123    engine_ptr: *mut HBEngineOpaque,
1124    share_type: CShareType,
1125    share_ptr: *const u8,
1126    share_len: usize,
1127    result_ptr: *mut StoffelValue,
1128) -> HBEngineErrorCode {
1129    if engine_ptr.is_null() || share_ptr.is_null() || result_ptr.is_null() {
1130        return HBEngineErrorCode::HBEngineNullPointer;
1131    }
1132
1133    if share_len > MAX_FFI_SHARE_LEN {
1134        return HBEngineErrorCode::HBEngineInvalidShareType;
1135    }
1136    let engine = unsafe { &*(engine_ptr as *const Arc<HoneyBadgerMpcEngine>) };
1137    let share_bytes = unsafe { std::slice::from_raw_parts(share_ptr, share_len) };
1138    let ty = match ShareType::try_from(share_type) {
1139        Ok(ty) => ty,
1140        Err(_) => return HBEngineErrorCode::HBEngineInvalidShareType,
1141    };
1142
1143    // open_share is sync in the current implementation (uses global registry)
1144    match engine.open_share(ty, share_bytes) {
1145        Ok(value) => {
1146            let value = crate::net::curve::clear_share_value_to_vm_value(ty, value);
1147            let converted = match value_to_stoffel_value(&value) {
1148                Ok(converted) => converted,
1149                Err(_) => return HBEngineErrorCode::HBEngineOpenShareFailed,
1150            };
1151            unsafe {
1152                *result_ptr = converted;
1153            }
1154            HBEngineErrorCode::HBEngineSuccess
1155        }
1156        Err(_) => HBEngineErrorCode::HBEngineOpenShareFailed,
1157    }
1158}
1159/// Initialize input shares from a client
1160///
1161/// # Arguments
1162///
1163/// * `engine_ptr` - Engine handle
1164/// * `client_id` - Client identifier
1165/// * `shares_data` - Serialized shares data (ark_serialize compressed format)
1166/// * `shares_len` - Length of shares data
1167///
1168/// # Returns
1169///
1170/// * HBEngineSuccess on success, error code on failure
1171///
1172/// # Note
1173///
1174/// The shares_data must be serialized using ark_serialize compressed format.
1175/// Format: [num_shares: u32][share1_bytes][share2_bytes]...
1176///
1177/// # Safety
1178///
1179/// `engine_ptr` must be a live pointer created by `hb_engine_new`.
1180/// `shares_data` must point to `shares_len` bytes.
1181#[no_mangle]
1182pub unsafe extern "C" fn hb_engine_init_client_input(
1183    engine_ptr: *mut HBEngineOpaque,
1184    client_id: u64,
1185    shares_data: *const u8,
1186    shares_len: usize,
1187) -> HBEngineErrorCode {
1188    use ark_bls12_381::Fr;
1189    use ark_serialize::CanonicalDeserialize;
1190    use stoffelmpc_mpc::honeybadger::robust_interpolate::robust_interpolate::RobustShare;
1191
1192    if engine_ptr.is_null() || shares_data.is_null() {
1193        return HBEngineErrorCode::HBEngineNullPointer;
1194    }
1195
1196    let engine = unsafe { &*(engine_ptr as *const Arc<HoneyBadgerMpcEngine>) };
1197    let shares_bytes = unsafe { std::slice::from_raw_parts(shares_data, shares_len) };
1198
1199    // Deserialize shares from bytes using ark_serialize
1200    // Format: [num_shares: u32][share1_bytes][share2_bytes]...
1201    if shares_len < 4 {
1202        return HBEngineErrorCode::HBEngineSerializationError;
1203    }
1204    let num_shares = match usize::try_from(u32::from_le_bytes([
1205        shares_bytes[0],
1206        shares_bytes[1],
1207        shares_bytes[2],
1208        shares_bytes[3],
1209    ])) {
1210        Ok(num_shares) => num_shares,
1211        Err(_) => return HBEngineErrorCode::HBEngineSerializationError,
1212    };
1213    let mut cursor = &shares_bytes[4..];
1214    let mut shares: Vec<RobustShare<Fr>> = Vec::with_capacity(num_shares);
1215
1216    for _ in 0..num_shares {
1217        match RobustShare::<Fr>::deserialize_compressed(&mut cursor) {
1218            Ok(share) => shares.push(share),
1219            Err(_) => return HBEngineErrorCode::HBEngineSerializationError,
1220        }
1221    }
1222
1223    let rt = match tokio::runtime::Runtime::new() {
1224        Ok(rt) => rt,
1225        Err(_) => return HBEngineErrorCode::HBEngineRuntimeError,
1226    };
1227
1228    let client_id = match usize::try_from(client_id) {
1229        Ok(client_id) => client_id,
1230        Err(_) => return HBEngineErrorCode::HBEngineInvalidConfig,
1231    };
1232
1233    match rt.block_on(engine.init_client_input(client_id, shares)) {
1234        Ok(()) => HBEngineErrorCode::HBEngineSuccess,
1235        Err(_) => HBEngineErrorCode::HBEngineClientInputFailed,
1236    }
1237}
1238/// Get shares for a specific client
1239///
1240/// # Arguments
1241///
1242/// * `engine_ptr` - Engine handle
1243/// * `client_id` - Client identifier
1244/// * `result_ptr` - Output: pointer to serialized shares (ark_serialize compressed format)
1245/// * `result_len_ptr` - Output: length of result
1246///
1247/// # Returns
1248///
1249/// * HBEngineSuccess on success, error code on failure
1250///
1251/// # Note
1252///
1253/// Caller must free the result bytes with hb_free_bytes.
1254/// Format: [num_shares: u32][share1_bytes][share2_bytes]...
1255///
1256/// # Safety
1257///
1258/// `engine_ptr` must be a live pointer created by `hb_engine_new`.
1259/// `result_ptr` and `result_len_ptr` must be valid output pointers; successful
1260/// results must be released with `hb_free_bytes`.
1261#[no_mangle]
1262pub unsafe extern "C" fn hb_engine_get_client_shares(
1263    engine_ptr: *mut HBEngineOpaque,
1264    client_id: u64,
1265    result_ptr: *mut *mut u8,
1266    result_len_ptr: *mut usize,
1267) -> HBEngineErrorCode {
1268    use ark_serialize::CanonicalSerialize;
1269
1270    if engine_ptr.is_null() || result_ptr.is_null() || result_len_ptr.is_null() {
1271        return HBEngineErrorCode::HBEngineNullPointer;
1272    }
1273
1274    let engine = unsafe { &*(engine_ptr as *const Arc<HoneyBadgerMpcEngine>) };
1275
1276    let rt = match tokio::runtime::Runtime::new() {
1277        Ok(rt) => rt,
1278        Err(_) => return HBEngineErrorCode::HBEngineRuntimeError,
1279    };
1280
1281    let client_id = match usize::try_from(client_id) {
1282        Ok(client_id) => client_id,
1283        Err(_) => return HBEngineErrorCode::HBEngineInvalidConfig,
1284    };
1285
1286    match rt.block_on(engine.get_client_shares(client_id)) {
1287        Ok(shares) => {
1288            // Serialize shares using ark_serialize
1289            // Format: [num_shares: u32][share1_bytes][share2_bytes]...
1290            let mut bytes = Vec::new();
1291            let share_count = match u32::try_from(shares.len()) {
1292                Ok(count) => count,
1293                Err(_) => return HBEngineErrorCode::HBEngineSerializationError,
1294            };
1295            bytes.extend_from_slice(&share_count.to_le_bytes());
1296            for share in &shares {
1297                if share.serialize_compressed(&mut bytes).is_err() {
1298                    return HBEngineErrorCode::HBEngineSerializationError;
1299                }
1300            }
1301            unsafe {
1302                write_ffi_result_bytes(bytes, result_ptr, result_len_ptr);
1303            }
1304            HBEngineErrorCode::HBEngineSuccess
1305        }
1306        Err(_) => HBEngineErrorCode::HBEngineGetClientSharesFailed,
1307    }
1308}
1309/// Get the party ID of the engine
1310///
1311/// # Safety
1312///
1313/// `engine_ptr` must be null or a live pointer created by `hb_engine_new`.
1314#[no_mangle]
1315pub unsafe extern "C" fn hb_engine_party_id(engine_ptr: *mut HBEngineOpaque) -> usize {
1316    if engine_ptr.is_null() {
1317        return 0;
1318    }
1319    let engine = unsafe { &*(engine_ptr as *const Arc<HoneyBadgerMpcEngine>) };
1320    engine.party().id()
1321}
1322/// Get the instance ID of the engine
1323///
1324/// # Safety
1325///
1326/// `engine_ptr` must be null or a live pointer created by `hb_engine_new`.
1327#[no_mangle]
1328pub unsafe extern "C" fn hb_engine_instance_id(engine_ptr: *mut HBEngineOpaque) -> u64 {
1329    if engine_ptr.is_null() {
1330        return 0;
1331    }
1332    let engine = unsafe { &*(engine_ptr as *const Arc<HoneyBadgerMpcEngine>) };
1333    engine.instance().id()
1334}
1335/// Get the protocol name (returns static string, do not free)
1336///
1337/// # Safety
1338///
1339/// `engine_ptr` must be null or a live pointer created by `hb_engine_new`.
1340#[no_mangle]
1341pub unsafe extern "C" fn hb_engine_protocol_name(engine_ptr: *mut HBEngineOpaque) -> *const c_char {
1342    static PROTOCOL_NAME: &[u8] = b"honeybadger-mpc\0";
1343    if engine_ptr.is_null() {
1344        return std::ptr::null();
1345    }
1346    PROTOCOL_NAME.as_ptr() as *const c_char
1347}
1348/// Get the network handle from the engine
1349///
1350/// Returns a cloned network pointer. Caller must free with hb_network_free.
1351///
1352/// # Safety
1353///
1354/// `engine_ptr` must be null or a live pointer created by `hb_engine_new`.
1355#[no_mangle]
1356pub unsafe extern "C" fn hb_engine_get_network(
1357    engine_ptr: *mut HBEngineOpaque,
1358) -> *mut HBNetworkOpaque {
1359    if engine_ptr.is_null() {
1360        return std::ptr::null_mut();
1361    }
1362
1363    let engine = unsafe { &*(engine_ptr as *const Arc<HoneyBadgerMpcEngine>) };
1364    let net = engine.net();
1365
1366    // Box the Arc for stable FFI pointer
1367    Box::into_raw(Box::new(net)) as *mut HBNetworkOpaque
1368}
1369/// Free a network handle obtained from hb_engine_get_network
1370///
1371/// # Safety
1372///
1373/// `network_ptr` must be null or a live pointer returned by
1374/// `hb_engine_get_network`. It must not be used after this call, and it must not
1375/// be freed more than once.
1376#[no_mangle]
1377pub unsafe extern "C" fn hb_network_free(network_ptr: *mut HBNetworkOpaque) {
1378    if !network_ptr.is_null() {
1379        unsafe {
1380            let _ = Box::from_raw(network_ptr as *mut Arc<QuicNetworkManager>);
1381        }
1382    }
1383}
1384/// Free bytes allocated by engine functions (e.g., multiply_share_async result)
1385///
1386/// # Safety
1387///
1388/// `ptr` must be null or a pointer returned by an FFI function in this module
1389/// with exactly the same `len`.
1390#[no_mangle]
1391pub unsafe extern "C" fn hb_free_bytes(ptr: *mut u8, len: usize) {
1392    unsafe {
1393        free_ffi_result_bytes(ptr, len);
1394    }
1395}
1396
1397#[cfg(test)]
1398mod tests {
1399    use super::*;
1400
1401    #[test]
1402    fn c_foreign_object_rejects_null_pointer() {
1403        assert!(CForeignObject::new(std::ptr::null_mut()).is_none());
1404    }
1405
1406    #[test]
1407    fn c_foreign_object_preserves_opaque_pointer() {
1408        let mut value = 42u8;
1409        let ptr = (&mut value as *mut u8).cast::<c_void>();
1410
1411        let object = CForeignObject::new(ptr).expect("non-null pointer is accepted");
1412
1413        assert_eq!(object.get_ptr(), ptr);
1414    }
1415}
1416
1417// ============================================================================
1418// HoneyBadgerMpcEngine FFI - Unit Tests
1419// ============================================================================
1420
1421#[cfg(test)]
1422mod hb_engine_tests {
1423    use super::*;
1424
1425    #[test]
1426    fn test_share_type_conversion_int() {
1427        let c_int = CShareType { kind: 0, width: 64 };
1428        let st = ShareType::try_from(c_int).expect("valid secret int share type");
1429        assert!(matches!(st, ShareType::SecretInt { bit_length: 64 }));
1430    }
1431
1432    #[test]
1433    fn test_share_type_conversion_bool() {
1434        // Both width values should result in boolean (1-bit SecretInt)
1435        let c_bool_true = CShareType { kind: 1, width: 1 };
1436        let st = ShareType::try_from(c_bool_true).expect("valid boolean share type");
1437        assert!(matches!(st, ShareType::SecretInt { bit_length: 1 }));
1438
1439        let c_bool_false = CShareType { kind: 1, width: 0 };
1440        let st = ShareType::try_from(c_bool_false).expect("valid boolean share type");
1441        assert!(matches!(st, ShareType::SecretInt { bit_length: 1 }));
1442    }
1443
1444    #[test]
1445    fn test_share_type_conversion_float() {
1446        let c_float = CShareType { kind: 2, width: 42 };
1447        let st = ShareType::try_from(c_float).expect("valid fixed-point share type");
1448        assert!(matches!(st, ShareType::SecretFixedPoint { .. }));
1449    }
1450
1451    #[test]
1452    fn share_type_conversion_rejects_invalid_int_width() {
1453        assert!(ShareType::try_from(CShareType { kind: 0, width: 0 }).is_err());
1454        assert!(ShareType::try_from(CShareType { kind: 0, width: -1 }).is_err());
1455    }
1456
1457    #[test]
1458    fn share_type_conversion_rejects_invalid_kind() {
1459        assert!(ShareType::try_from(CShareType {
1460            kind: 99,
1461            width: 64
1462        })
1463        .is_err());
1464    }
1465
1466    #[test]
1467    fn share_type_conversion_to_c_preserves_kind_and_width() {
1468        let c_int =
1469            CShareType::try_from(ShareType::secret_int(64)).expect("secret int should fit C ABI");
1470        assert_eq!(c_int.kind, 0);
1471        assert_eq!(c_int.width, 64);
1472
1473        let c_bool = CShareType::try_from(ShareType::boolean()).expect("bool should fit C ABI");
1474        assert_eq!(c_bool.kind, 1);
1475        assert_eq!(c_bool.width, 1);
1476
1477        let c_fixed = CShareType::try_from(ShareType::secret_fixed_point_from_bits(42, 16))
1478            .expect("fixed-point should fit C ABI");
1479        assert_eq!(c_fixed.kind, 2);
1480        assert_eq!(c_fixed.width, 42);
1481    }
1482
1483    #[cfg(target_pointer_width = "64")]
1484    #[test]
1485    fn share_type_conversion_to_c_rejects_unrepresentable_width() {
1486        let too_wide = ShareType::SecretInt {
1487            bit_length: usize::MAX,
1488        };
1489
1490        assert!(CShareType::try_from(too_wide).is_err());
1491    }
1492
1493    #[test]
1494    fn test_null_pointer_handling() {
1495        unsafe {
1496            assert_eq!(
1497                hb_engine_start_async(std::ptr::null_mut()),
1498                HBEngineErrorCode::HBEngineNullPointer
1499            );
1500
1501            assert_eq!(hb_engine_is_ready(std::ptr::null_mut()), 0);
1502            assert!(hb_engine_get_network(std::ptr::null_mut()).is_null());
1503            assert_eq!(hb_engine_party_id(std::ptr::null_mut()), 0);
1504            assert_eq!(hb_engine_instance_id(std::ptr::null_mut()), 0);
1505            assert!(hb_engine_protocol_name(std::ptr::null_mut()).is_null());
1506        }
1507    }
1508
1509    #[test]
1510    fn test_hb_engine_new_null_network() {
1511        let engine = unsafe { hb_engine_new(1, 0, 5, 1, 8, 16, std::ptr::null_mut()) };
1512        assert!(engine.is_null());
1513    }
1514
1515    #[test]
1516    fn test_hb_engine_new_invalid_topology_rejected() {
1517        let net = Arc::new(QuicNetworkManager::new());
1518        let net_ptr = &net as *const Arc<QuicNetworkManager> as *mut HBNetworkOpaque;
1519
1520        let engine = unsafe { hb_engine_new(1, 5, 5, 1, 8, 16, net_ptr) };
1521
1522        assert!(engine.is_null(), "party id outside n must be rejected");
1523    }
1524
1525    #[test]
1526    fn test_hb_engine_free_null() {
1527        // Should not crash
1528        unsafe { hb_engine_free(std::ptr::null_mut()) };
1529    }
1530
1531    #[test]
1532    fn test_hb_network_free_null() {
1533        // Should not crash
1534        unsafe { hb_network_free(std::ptr::null_mut()) };
1535    }
1536
1537    #[test]
1538    fn test_hb_free_bytes_null() {
1539        // Should not crash
1540        unsafe {
1541            hb_free_bytes(std::ptr::null_mut(), 0);
1542            hb_free_bytes(std::ptr::null_mut(), 10);
1543        }
1544    }
1545}
1546
1547// ============================================================================
1548// AVSS Engine FFI Types and Functions
1549//
1550// FFI export names keep the `adkg_` prefix for ABI compatibility with existing
1551// C/SDK consumers. Internal Rust types use the `Avss` prefix.
1552// ============================================================================
1553mod avss_ffi {
1554    use super::*;
1555    use crate::net::avss_engine::{AvssEngineConfig, AvssMpcEngine, AvssOperations};
1556    use crate::net::mpc_engine::MpcEngine;
1557    use ark_serialize::CanonicalDeserialize;
1558    use ark_std::rand::SeedableRng;
1559    use std::future::Future;
1560    use std::sync::Arc;
1561    use stoffelnet::transports::quic::QuicNetworkManager;
1562
1563    fn block_on_avss<Fut, T>(fut: Fut) -> Result<T, String>
1564    where
1565        Fut: Future<Output = Result<T, String>> + Send + 'static,
1566        T: Send + 'static,
1567    {
1568        match tokio::runtime::Handle::try_current() {
1569            Ok(handle) =>
1570            {
1571                #[allow(deprecated)]
1572                match handle.runtime_flavor() {
1573                    tokio::runtime::RuntimeFlavor::MultiThread => {
1574                        tokio::task::block_in_place(|| handle.block_on(fut))
1575                    }
1576                    tokio::runtime::RuntimeFlavor::CurrentThread => std::thread::spawn(move || {
1577                        let rt = tokio::runtime::Builder::new_current_thread()
1578                            .enable_all()
1579                            .build()
1580                            .map_err(|e| format!("failed to build tokio runtime: {}", e))?;
1581                        rt.block_on(fut)
1582                    })
1583                    .join()
1584                    .map_err(|_| "thread panicked in block_on_avss".to_string())?,
1585                    _ => Err("unsupported tokio runtime flavor".to_string()),
1586                }
1587            }
1588            Err(_) => {
1589                let rt = tokio::runtime::Builder::new_current_thread()
1590                    .enable_all()
1591                    .build()
1592                    .map_err(|e| format!("failed to build tokio runtime: {}", e))?;
1593                rt.block_on(fut)
1594            }
1595        }
1596    }
1597
1598    /// C-compatible curve configuration for AVSS.
1599    /// Variants are constructed by C/SDK callers via integer discriminants.
1600    #[repr(C)]
1601    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1602    #[allow(dead_code)]
1603    pub enum CAvssCurveConfig {
1604        Bls12_381 = 0,
1605        Bn254 = 1,
1606        Curve25519 = 2,
1607        Ed25519 = 3,
1608        Secp256k1 = 4,
1609        P256 = 5,
1610    }
1611
1612    impl CAvssCurveConfig {
1613        fn from_ffi(value: u32) -> Option<Self> {
1614            match value {
1615                0 => Some(CAvssCurveConfig::Bls12_381),
1616                1 => Some(CAvssCurveConfig::Bn254),
1617                2 => Some(CAvssCurveConfig::Curve25519),
1618                3 => Some(CAvssCurveConfig::Ed25519),
1619                4 => Some(CAvssCurveConfig::Secp256k1),
1620                5 => Some(CAvssCurveConfig::P256),
1621                _ => None,
1622            }
1623        }
1624    }
1625
1626    /// Internal wrapper that erases the generic (F, G) types behind trait objects.
1627    /// The opaque pointer stores a `Box<AvssFfiWrapper>`.
1628    struct AvssFfiWrapper {
1629        engine: Arc<dyn MpcEngine>,
1630        avss_ops: Arc<dyn AvssOperations + Send + Sync>,
1631    }
1632
1633    /// Opaque pointer type for AvssMpcEngine
1634    #[repr(C)]
1635    pub struct AvssEngineOpaque {
1636        _data: (),
1637        _marker: core::marker::PhantomData<(*mut u8, PhantomPinned)>,
1638    }
1639
1640    /// Error codes for AVSS engine FFI operations.
1641    /// Variants are matched by C/SDK callers via integer discriminants.
1642    #[repr(C)]
1643    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1644    #[allow(dead_code)]
1645    pub enum AvssEngineErrorCode {
1646        /// Operation succeeded
1647        Success = 0,
1648        /// Null pointer provided
1649        NullPointer = 1,
1650        /// Engine not ready
1651        NotReady = 2,
1652        /// Key generation failed
1653        KeyGenFailed = 3,
1654        /// Serialization error
1655        SerializationError = 4,
1656        /// Session not found
1657        SessionNotFound = 5,
1658        /// Invalid commitment index
1659        InvalidCommitmentIndex = 6,
1660        /// Tokio runtime creation failed
1661        RuntimeError = 7,
1662        /// Invalid curve configuration
1663        InvalidCurveConfig = 8,
1664    }
1665
1666    /// Generic helper: deserialize keys, create an `AvssMpcEngine<F, G>`, and wrap it.
1667    fn create_engine_inner<F, G>(
1668        instance_id: u64,
1669        party_id: usize,
1670        n: usize,
1671        t: usize,
1672        net: Arc<QuicNetworkManager>,
1673        sk_bytes: &[u8],
1674        pk_bytes: &[u8],
1675    ) -> Result<AvssFfiWrapper, String>
1676    where
1677        F: crate::net::curve::SupportedMpcField + ark_std::UniformRand,
1678        G: ark_ec::CurveGroup<ScalarField = F> + Send + Sync + 'static,
1679    {
1680        let sk_i: F = if sk_bytes.is_empty() {
1681            let mut rng = ark_std::rand::rngs::StdRng::from_entropy();
1682            F::rand(&mut rng)
1683        } else {
1684            CanonicalDeserialize::deserialize_compressed(sk_bytes)
1685                .map_err(|e| format!("failed to deserialize secret key: {}", e))?
1686        };
1687
1688        let pk_map: Vec<G> = Vec::<G>::deserialize_compressed(pk_bytes)
1689            .map_err(|e| format!("failed to deserialize public key map: {}", e))?;
1690        if pk_map.len() != n {
1691            return Err(format!(
1692                "public key map length {} does not match n={}",
1693                pk_map.len(),
1694                n
1695            ));
1696        }
1697
1698        let session = crate::net::MpcSessionConfig::try_new(instance_id, party_id, n, t, net)
1699            .map_err(|error| error.to_string())?;
1700        let engine = block_on_avss(AvssMpcEngine::<F, G>::from_config(AvssEngineConfig::new(
1701            session,
1702            sk_i,
1703            Arc::new(pk_map),
1704        )))?;
1705
1706        let engine_arc: Arc<AvssMpcEngine<F, G>> = engine;
1707        Ok(AvssFfiWrapper {
1708            engine: engine_arc.clone() as Arc<dyn MpcEngine>,
1709            avss_ops: engine_arc as Arc<dyn AvssOperations + Send + Sync>,
1710        })
1711    }
1712
1713    fn create_bls12381_engine(
1714        instance_id: u64,
1715        party_id: usize,
1716        n: usize,
1717        t: usize,
1718        net: Arc<QuicNetworkManager>,
1719        sk_bytes: &[u8],
1720        pk_bytes: &[u8],
1721    ) -> Result<AvssFfiWrapper, String> {
1722        create_engine_inner::<ark_bls12_381::Fr, ark_bls12_381::G1Projective>(
1723            instance_id,
1724            party_id,
1725            n,
1726            t,
1727            net,
1728            sk_bytes,
1729            pk_bytes,
1730        )
1731    }
1732
1733    fn create_bn254_engine(
1734        instance_id: u64,
1735        party_id: usize,
1736        n: usize,
1737        t: usize,
1738        net: Arc<QuicNetworkManager>,
1739        sk_bytes: &[u8],
1740        pk_bytes: &[u8],
1741    ) -> Result<AvssFfiWrapper, String> {
1742        create_engine_inner::<ark_bn254::Fr, ark_bn254::G1Projective>(
1743            instance_id,
1744            party_id,
1745            n,
1746            t,
1747            net,
1748            sk_bytes,
1749            pk_bytes,
1750        )
1751    }
1752
1753    fn create_curve25519_engine(
1754        instance_id: u64,
1755        party_id: usize,
1756        n: usize,
1757        t: usize,
1758        net: Arc<QuicNetworkManager>,
1759        sk_bytes: &[u8],
1760        pk_bytes: &[u8],
1761    ) -> Result<AvssFfiWrapper, String> {
1762        create_engine_inner::<ark_curve25519::Fr, ark_curve25519::EdwardsProjective>(
1763            instance_id,
1764            party_id,
1765            n,
1766            t,
1767            net,
1768            sk_bytes,
1769            pk_bytes,
1770        )
1771    }
1772
1773    fn create_ed25519_engine(
1774        instance_id: u64,
1775        party_id: usize,
1776        n: usize,
1777        t: usize,
1778        net: Arc<QuicNetworkManager>,
1779        sk_bytes: &[u8],
1780        pk_bytes: &[u8],
1781    ) -> Result<AvssFfiWrapper, String> {
1782        create_engine_inner::<ark_ed25519::Fr, ark_ed25519::EdwardsProjective>(
1783            instance_id,
1784            party_id,
1785            n,
1786            t,
1787            net,
1788            sk_bytes,
1789            pk_bytes,
1790        )
1791    }
1792
1793    fn create_secp256k1_engine(
1794        instance_id: u64,
1795        party_id: usize,
1796        n: usize,
1797        t: usize,
1798        net: Arc<QuicNetworkManager>,
1799        sk_bytes: &[u8],
1800        pk_bytes: &[u8],
1801    ) -> Result<AvssFfiWrapper, String> {
1802        create_engine_inner::<ark_secp256k1::Fr, ark_secp256k1::Projective>(
1803            instance_id,
1804            party_id,
1805            n,
1806            t,
1807            net,
1808            sk_bytes,
1809            pk_bytes,
1810        )
1811    }
1812
1813    fn create_p256_engine(
1814        instance_id: u64,
1815        party_id: usize,
1816        n: usize,
1817        t: usize,
1818        net: Arc<QuicNetworkManager>,
1819        sk_bytes: &[u8],
1820        pk_bytes: &[u8],
1821    ) -> Result<AvssFfiWrapper, String> {
1822        create_engine_inner::<ark_secp256r1::Fr, ark_secp256r1::Projective>(
1823            instance_id,
1824            party_id,
1825            n,
1826            t,
1827            net,
1828            sk_bytes,
1829            pk_bytes,
1830        )
1831    }
1832
1833    /// Creates a new AVSS engine
1834    ///
1835    /// # Arguments
1836    /// * `instance_id` - Unique instance identifier
1837    /// * `party_id` - This party's ID (0 to n-1)
1838    /// * `n` - Total number of parties
1839    /// * `t` - Threshold
1840    /// * `network_ptr` - Pointer to a QuicNetworkManager (same opaque type as HB)
1841    /// * `curve_config` - Curve configuration (0 = BLS12-381, 1 = BN254, 2 = Curve25519, 3 = Ed25519, 4 = secp256k1, 5 = P-256)
1842    /// * `sk_bytes` - Secret key bytes (serialized Fr element), or null for random key
1843    /// * `sk_len` - Length of secret key bytes
1844    /// * `pk_map_ptr` - Pointer to serialized public key map (required, must not be null)
1845    /// * `pk_map_len` - Length of public key map bytes (must be > 0)
1846    ///
1847    /// # Returns
1848    /// Pointer to opaque engine handle, or null on failure
1849    ///
1850    /// # Safety
1851    /// `network_ptr` must be a valid, live pointer to an
1852    /// `Arc<QuicNetworkManager>` allocated by this FFI layer. When non-null,
1853    /// `sk_bytes` must point to `sk_len` bytes. `pk_map_ptr` must point to
1854    /// `pk_map_len` bytes.
1855    #[no_mangle]
1856    pub unsafe extern "C" fn adkg_engine_new(
1857        instance_id: u64,
1858        party_id: usize,
1859        n: usize,
1860        t: usize,
1861        network_ptr: *mut c_void,
1862        curve_config: u32,
1863        sk_bytes: *const u8,
1864        sk_len: usize,
1865        pk_map_ptr: *const u8,
1866        pk_map_len: usize,
1867    ) -> *mut AvssEngineOpaque {
1868        if network_ptr.is_null() {
1869            return std::ptr::null_mut();
1870        }
1871        if pk_map_ptr.is_null() || pk_map_len == 0 {
1872            return std::ptr::null_mut();
1873        }
1874
1875        // Extract network from opaque pointer
1876        let net: Arc<QuicNetworkManager> = unsafe {
1877            let boxed = &*(network_ptr as *const Arc<QuicNetworkManager>);
1878            Arc::clone(boxed)
1879        };
1880
1881        let sk_slice = if sk_bytes.is_null() || sk_len == 0 {
1882            &[]
1883        } else {
1884            unsafe { std::slice::from_raw_parts(sk_bytes, sk_len) }
1885        };
1886
1887        let pk_slice = unsafe { std::slice::from_raw_parts(pk_map_ptr, pk_map_len) };
1888
1889        let curve_config = match CAvssCurveConfig::from_ffi(curve_config) {
1890            Some(config) => config,
1891            None => return std::ptr::null_mut(),
1892        };
1893
1894        let wrapper = match curve_config {
1895            CAvssCurveConfig::Bls12_381 => {
1896                create_bls12381_engine(instance_id, party_id, n, t, net, sk_slice, pk_slice)
1897            }
1898            CAvssCurveConfig::Bn254 => {
1899                create_bn254_engine(instance_id, party_id, n, t, net, sk_slice, pk_slice)
1900            }
1901            CAvssCurveConfig::Curve25519 => {
1902                create_curve25519_engine(instance_id, party_id, n, t, net, sk_slice, pk_slice)
1903            }
1904            CAvssCurveConfig::Ed25519 => {
1905                create_ed25519_engine(instance_id, party_id, n, t, net, sk_slice, pk_slice)
1906            }
1907            CAvssCurveConfig::Secp256k1 => {
1908                create_secp256k1_engine(instance_id, party_id, n, t, net, sk_slice, pk_slice)
1909            }
1910            CAvssCurveConfig::P256 => {
1911                create_p256_engine(instance_id, party_id, n, t, net, sk_slice, pk_slice)
1912            }
1913        };
1914
1915        match wrapper {
1916            Ok(w) => {
1917                if let Err(e) = w.engine.start() {
1918                    eprintln!("adkg_engine_new: engine start failed: {}", e);
1919                    return std::ptr::null_mut();
1920                }
1921                Box::into_raw(Box::new(w)) as *mut AvssEngineOpaque
1922            }
1923            Err(e) => {
1924                eprintln!("adkg_engine_new: {}", e);
1925                std::ptr::null_mut()
1926            }
1927        }
1928    }
1929
1930    /// Frees an AVSS engine instance
1931    ///
1932    /// # Safety
1933    /// `engine_ptr` must be null or a live pointer created by `adkg_engine_new`.
1934    /// It must not be used after this call, and it must not be freed more than
1935    /// once.
1936    #[no_mangle]
1937    pub unsafe extern "C" fn adkg_engine_free(engine_ptr: *mut AvssEngineOpaque) {
1938        if !engine_ptr.is_null() {
1939            unsafe {
1940                let _ = Box::from_raw(engine_ptr as *mut AvssFfiWrapper);
1941            }
1942        }
1943    }
1944
1945    /// Helper to get the wrapper from an opaque pointer
1946    unsafe fn get_wrapper<'a>(ptr: *mut AvssEngineOpaque) -> &'a AvssFfiWrapper {
1947        &*(ptr as *const AvssFfiWrapper)
1948    }
1949
1950    /// Generates a new distributed share under the given key name
1951    ///
1952    /// Returns serialized share bytes via out parameters on success.
1953    /// Caller must free result bytes with adkg_free_bytes.
1954    ///
1955    /// # Safety
1956    /// `engine_ptr` must be a live pointer created by `adkg_engine_new`.
1957    /// `key_name` must be a valid null-terminated C string. `result_ptr` and
1958    /// `result_len_ptr` must be valid output pointers; successful results must
1959    /// be released with `adkg_free_bytes`.
1960    #[no_mangle]
1961    pub unsafe extern "C" fn adkg_engine_generate_share(
1962        engine_ptr: *mut AvssEngineOpaque,
1963        key_name: *const c_char,
1964        result_ptr: *mut *mut u8,
1965        result_len_ptr: *mut usize,
1966    ) -> AvssEngineErrorCode {
1967        if engine_ptr.is_null()
1968            || key_name.is_null()
1969            || result_ptr.is_null()
1970            || result_len_ptr.is_null()
1971        {
1972            return AvssEngineErrorCode::NullPointer;
1973        }
1974
1975        let wrapper = unsafe { get_wrapper(engine_ptr) };
1976
1977        let c_str = unsafe { CStr::from_ptr(key_name) };
1978        let key_name_str = match c_str.to_str() {
1979            Ok(s) => s.to_string(),
1980            Err(_) => return AvssEngineErrorCode::SerializationError,
1981        };
1982
1983        let avss_ops = Arc::clone(&wrapper.avss_ops);
1984        match block_on_avss(async move { avss_ops.avss_generate_share(key_name_str).await }) {
1985            Ok(share_bytes) => {
1986                unsafe {
1987                    write_ffi_result_bytes(share_bytes, result_ptr, result_len_ptr);
1988                }
1989                AvssEngineErrorCode::Success
1990            }
1991            Err(_) => AvssEngineErrorCode::KeyGenFailed,
1992        }
1993    }
1994
1995    /// Get the public key for a stored share by key name
1996    ///
1997    /// Caller must free result bytes with adkg_free_bytes.
1998    ///
1999    /// # Safety
2000    /// `engine_ptr` must be a live pointer created by `adkg_engine_new`.
2001    /// `key_name` must be a valid null-terminated C string. `result_ptr` and
2002    /// `result_len_ptr` must be valid output pointers; successful results must
2003    /// be released with `adkg_free_bytes`.
2004    #[no_mangle]
2005    pub unsafe extern "C" fn adkg_engine_get_public_key(
2006        engine_ptr: *mut AvssEngineOpaque,
2007        key_name: *const c_char,
2008        result_ptr: *mut *mut u8,
2009        result_len_ptr: *mut usize,
2010    ) -> AvssEngineErrorCode {
2011        if engine_ptr.is_null()
2012            || key_name.is_null()
2013            || result_ptr.is_null()
2014            || result_len_ptr.is_null()
2015        {
2016            return AvssEngineErrorCode::NullPointer;
2017        }
2018
2019        let wrapper = unsafe { get_wrapper(engine_ptr) };
2020
2021        let c_str = unsafe { CStr::from_ptr(key_name) };
2022        let key_name_str = match c_str.to_str() {
2023            Ok(s) => s,
2024            Err(_) => return AvssEngineErrorCode::SerializationError,
2025        };
2026
2027        match wrapper.avss_ops.avss_get_commitment(key_name_str, 0) {
2028            Ok(bytes) => {
2029                unsafe {
2030                    write_ffi_result_bytes(bytes, result_ptr, result_len_ptr);
2031                }
2032                AvssEngineErrorCode::Success
2033            }
2034            Err(_) => AvssEngineErrorCode::SessionNotFound,
2035        }
2036    }
2037
2038    /// Get a commitment at a specific index for a stored share by key name
2039    ///
2040    /// Caller must free result bytes with adkg_free_bytes.
2041    ///
2042    /// # Safety
2043    /// `engine_ptr` must be a live pointer created by `adkg_engine_new`.
2044    /// `key_name` must be a valid null-terminated C string. `result_ptr` and
2045    /// `result_len_ptr` must be valid output pointers; successful results must
2046    /// be released with `adkg_free_bytes`.
2047    #[no_mangle]
2048    pub unsafe extern "C" fn adkg_engine_get_commitment(
2049        engine_ptr: *mut AvssEngineOpaque,
2050        key_name: *const c_char,
2051        index: usize,
2052        result_ptr: *mut *mut u8,
2053        result_len_ptr: *mut usize,
2054    ) -> AvssEngineErrorCode {
2055        if engine_ptr.is_null()
2056            || key_name.is_null()
2057            || result_ptr.is_null()
2058            || result_len_ptr.is_null()
2059        {
2060            return AvssEngineErrorCode::NullPointer;
2061        }
2062
2063        let wrapper = unsafe { get_wrapper(engine_ptr) };
2064
2065        let c_str = unsafe { CStr::from_ptr(key_name) };
2066        let key_name_str = match c_str.to_str() {
2067            Ok(s) => s,
2068            Err(_) => return AvssEngineErrorCode::SerializationError,
2069        };
2070
2071        match wrapper.avss_ops.avss_get_commitment(key_name_str, index) {
2072            Ok(bytes) => {
2073                unsafe {
2074                    write_ffi_result_bytes(bytes, result_ptr, result_len_ptr);
2075                }
2076                AvssEngineErrorCode::Success
2077            }
2078            Err(_) => AvssEngineErrorCode::InvalidCommitmentIndex,
2079        }
2080    }
2081
2082    /// Check if the engine is ready
2083    ///
2084    /// # Safety
2085    /// `engine_ptr` must be null or a live pointer created by `adkg_engine_new`.
2086    #[no_mangle]
2087    pub unsafe extern "C" fn adkg_engine_is_ready(engine_ptr: *mut AvssEngineOpaque) -> c_int {
2088        if engine_ptr.is_null() {
2089            return 0;
2090        }
2091        let wrapper = unsafe { get_wrapper(engine_ptr) };
2092        if wrapper.engine.is_ready() {
2093            1
2094        } else {
2095            0
2096        }
2097    }
2098
2099    /// Get the party ID
2100    ///
2101    /// # Safety
2102    /// `engine_ptr` must be null or a live pointer created by `adkg_engine_new`.
2103    #[no_mangle]
2104    pub unsafe extern "C" fn adkg_engine_party_id(engine_ptr: *mut AvssEngineOpaque) -> usize {
2105        if engine_ptr.is_null() {
2106            return 0;
2107        }
2108        let wrapper = unsafe { get_wrapper(engine_ptr) };
2109        wrapper.engine.party().id()
2110    }
2111
2112    /// Get the protocol name (returns static string, do not free)
2113    ///
2114    /// # Safety
2115    /// `engine_ptr` must be null or a live pointer created by `adkg_engine_new`.
2116    #[no_mangle]
2117    pub unsafe extern "C" fn adkg_engine_protocol_name(
2118        engine_ptr: *mut AvssEngineOpaque,
2119    ) -> *const c_char {
2120        static PROTOCOL_NAME: &[u8] = b"avss\0";
2121        if engine_ptr.is_null() {
2122            return std::ptr::null();
2123        }
2124        PROTOCOL_NAME.as_ptr() as *const c_char
2125    }
2126
2127    /// Free bytes allocated by AVSS engine functions
2128    ///
2129    /// # Safety
2130    /// `ptr` must be null or a pointer returned by an AVSS FFI function with
2131    /// exactly the same `len`.
2132    #[no_mangle]
2133    pub unsafe extern "C" fn adkg_free_bytes(ptr: *mut u8, len: usize) {
2134        unsafe {
2135            free_ffi_result_bytes(ptr, len);
2136        }
2137    }
2138
2139    #[cfg(test)]
2140    mod tests {
2141        use super::*;
2142        use ark_bls12_381::G1Projective;
2143        use ark_ec::PrimeGroup;
2144        use ark_serialize::CanonicalSerialize;
2145        use std::sync::Arc;
2146        use stoffelnet::transports::quic::QuicNetworkManager;
2147
2148        #[test]
2149        fn test_adkg_engine_new_null_network() {
2150            let engine = unsafe {
2151                adkg_engine_new(
2152                    1,
2153                    0,
2154                    5,
2155                    1,
2156                    std::ptr::null_mut(),
2157                    CAvssCurveConfig::Bls12_381 as u32,
2158                    std::ptr::null(),
2159                    0,
2160                    std::ptr::null(),
2161                    0,
2162                )
2163            };
2164            assert!(engine.is_null());
2165        }
2166
2167        #[test]
2168        fn test_adkg_engine_new_invalid_curve_config_rejected() {
2169            let n = 4usize;
2170            let net = Arc::new(QuicNetworkManager::new());
2171            let mut pk_map_bytes = Vec::new();
2172            vec![G1Projective::generator(); n]
2173                .serialize_compressed(&mut pk_map_bytes)
2174                .expect("serialize pk map");
2175            let net_ptr = &net as *const Arc<QuicNetworkManager> as *mut c_void;
2176
2177            let engine = unsafe {
2178                adkg_engine_new(
2179                    1,
2180                    0,
2181                    n,
2182                    1,
2183                    net_ptr,
2184                    99,
2185                    std::ptr::null(),
2186                    0,
2187                    pk_map_bytes.as_ptr(),
2188                    pk_map_bytes.len(),
2189                )
2190            };
2191            assert!(engine.is_null(), "invalid curve config must be rejected");
2192        }
2193
2194        #[test]
2195        fn test_adkg_engine_new_null_pk_map_rejected() {
2196            let net = Arc::new(QuicNetworkManager::new());
2197            let net_ptr = &net as *const Arc<QuicNetworkManager> as *mut c_void;
2198
2199            let engine = unsafe {
2200                adkg_engine_new(
2201                    1,
2202                    0,
2203                    4,
2204                    1,
2205                    net_ptr,
2206                    CAvssCurveConfig::Bls12_381 as u32,
2207                    std::ptr::null(),
2208                    0,
2209                    std::ptr::null(),
2210                    0,
2211                )
2212            };
2213            assert!(
2214                engine.is_null(),
2215                "constructor must reject null/empty public key map"
2216            );
2217        }
2218
2219        #[test]
2220        fn test_adkg_engine_new_without_tokio_runtime() {
2221            let n = 4usize;
2222            let net = Arc::new(QuicNetworkManager::new());
2223            let mut pk_map_bytes = Vec::new();
2224            vec![G1Projective::generator(); n]
2225                .serialize_compressed(&mut pk_map_bytes)
2226                .expect("serialize pk map");
2227            let net_ptr = &net as *const Arc<QuicNetworkManager> as *mut c_void;
2228
2229            let engine = unsafe {
2230                adkg_engine_new(
2231                    1,
2232                    0,
2233                    n,
2234                    1,
2235                    net_ptr,
2236                    CAvssCurveConfig::Bls12_381 as u32,
2237                    std::ptr::null(),
2238                    0,
2239                    pk_map_bytes.as_ptr(),
2240                    pk_map_bytes.len(),
2241                )
2242            };
2243            assert!(
2244                !engine.is_null(),
2245                "constructor should succeed without an ambient Tokio runtime"
2246            );
2247            unsafe { adkg_engine_free(engine) };
2248        }
2249
2250        #[test]
2251        fn test_adkg_engine_free_null() {
2252            unsafe { adkg_engine_free(std::ptr::null_mut()) };
2253        }
2254
2255        #[test]
2256        fn test_adkg_engine_is_ready_null() {
2257            assert_eq!(unsafe { adkg_engine_is_ready(std::ptr::null_mut()) }, 0);
2258        }
2259
2260        #[test]
2261        fn test_adkg_engine_party_id_null() {
2262            assert_eq!(unsafe { adkg_engine_party_id(std::ptr::null_mut()) }, 0);
2263        }
2264
2265        #[test]
2266        fn test_adkg_engine_protocol_name_null() {
2267            assert!(unsafe { adkg_engine_protocol_name(std::ptr::null_mut()) }.is_null());
2268        }
2269
2270        #[test]
2271        fn test_adkg_free_bytes_null() {
2272            unsafe {
2273                adkg_free_bytes(std::ptr::null_mut(), 0);
2274                adkg_free_bytes(std::ptr::null_mut(), 10);
2275            }
2276        }
2277    }
2278}