1use 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
71const MAX_FFI_SHARE_LEN: usize = 1_048_576;
74
75unsafe 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
95unsafe 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
108pub type VMHandle = *mut c_void;
110
111#[repr(C)]
113pub enum StoffelValueType {
114 Unit = 0,
116 Int = 1,
118 Float = 2,
120 Bool = 3,
122 String = 4,
124 Object = 5,
126 Array = 6,
128 Foreign = 7,
130 Closure = 8,
132}
133
134#[repr(C)]
136pub union StoffelValueData {
137 pub int_val: i64,
139 pub float_val: f64,
141 pub bool_val: bool,
143 pub string_val: *const c_char,
145 pub object_id: usize,
147 pub array_id: usize,
149 pub foreign_id: usize,
151 pub closure_id: usize,
153}
154
155#[repr(C)]
157pub struct StoffelValue {
158 pub value_type: StoffelValueType,
160 pub data: StoffelValueData,
162}
163
164pub type CForeignFunction =
166 extern "C" fn(args: *const StoffelValue, arg_count: c_int, result: *mut StoffelValue) -> c_int;
167
168#[repr(C)]
176pub struct HBEngineOpaque {
177 _data: (),
178 _marker: core::marker::PhantomData<(*mut u8, PhantomPinned)>,
179}
180#[repr(C)]
184pub struct HBNetworkOpaque {
185 _data: (),
186 _marker: core::marker::PhantomData<(*mut u8, PhantomPinned)>,
187}
188#[repr(C)]
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub enum HBEngineErrorCode {
192 HBEngineSuccess = 0,
194 HBEngineNullPointer = 1,
196 HBEngineNotReady = 2,
198 HBEngineNetworkError = 3,
200 HBEnginePreprocessingFailed = 4,
202 HBEngineMultiplyFailed = 5,
204 HBEngineOpenShareFailed = 6,
206 HBEngineSerializationError = 7,
208 HBEngineInvalidShareType = 8,
210 HBEngineClientInputFailed = 9,
212 HBEngineGetClientSharesFailed = 10,
214 HBEngineRuntimeError = 11,
216 HBEngineInvalidConfig = 12,
218}
219
220#[repr(C)]
227#[derive(Debug, Clone, Copy)]
228pub struct CShareType {
229 pub kind: u8,
231 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 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 1 => Ok(ShareType::boolean()),
247 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 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#[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#[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#[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#[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 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 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
460struct CForeignFunctionWrapper {
462 func: CForeignFunction,
463}
464
465impl CForeignFunctionWrapper {
466 fn call(&self, ctx: ForeignFunctionContext) -> Result<Value, String> {
467 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 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 stoffel_value_to_value(&result)
490 }
491}
492
493#[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#[allow(dead_code)]
546struct CForeignObject {
547 ptr: NonNull<c_void>,
548}
549
550unsafe impl Send for CForeignObject {}
554unsafe impl Sync for CForeignObject {}
555
556impl CForeignObject {
557 fn new(ptr: *mut c_void) -> Option<Self> {
563 NonNull::new(ptr).map(|ptr| CForeignObject { ptr })
564 }
565
566 #[allow(dead_code)]
572 fn get_ptr(&self) -> *mut c_void {
573 self.ptr.as_ptr()
574 }
575}
576
577#[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 let foreign_object = match CForeignObject::new(object) {
614 Some(object) => object,
615 None => return -1,
616 };
617
618 let value = match vm.try_register_foreign_object_value(foreign_object) {
620 Ok(value) => value,
621 Err(_) => return -2,
622 };
623
624 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#[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
682fn 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 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 }, }),
786 Value::Share(_, _) => Err("Cannot convert secret share value to C ABI".to_string()),
787 }
788}
789
790fn 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#[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#[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 let bytes = unsafe { std::slice::from_raw_parts(bytecode, bytecode_len) };
878
879 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 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#[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 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 Box::into_raw(Box::new(engine)) as *mut HBEngineOpaque
959 }
960 Err(_) => std::ptr::null_mut(),
961 }
962}
963#[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#[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 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#[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#[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#[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 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#[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 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#[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 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#[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#[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#[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#[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::into_raw(Box::new(net)) as *mut HBNetworkOpaque
1368}
1369#[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#[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#[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 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 unsafe { hb_engine_free(std::ptr::null_mut()) };
1529 }
1530
1531 #[test]
1532 fn test_hb_network_free_null() {
1533 unsafe { hb_network_free(std::ptr::null_mut()) };
1535 }
1536
1537 #[test]
1538 fn test_hb_free_bytes_null() {
1539 unsafe {
1541 hb_free_bytes(std::ptr::null_mut(), 0);
1542 hb_free_bytes(std::ptr::null_mut(), 10);
1543 }
1544 }
1545}
1546
1547mod 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 #[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 struct AvssFfiWrapper {
1629 engine: Arc<dyn MpcEngine>,
1630 avss_ops: Arc<dyn AvssOperations + Send + Sync>,
1631 }
1632
1633 #[repr(C)]
1635 pub struct AvssEngineOpaque {
1636 _data: (),
1637 _marker: core::marker::PhantomData<(*mut u8, PhantomPinned)>,
1638 }
1639
1640 #[repr(C)]
1643 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1644 #[allow(dead_code)]
1645 pub enum AvssEngineErrorCode {
1646 Success = 0,
1648 NullPointer = 1,
1650 NotReady = 2,
1652 KeyGenFailed = 3,
1654 SerializationError = 4,
1656 SessionNotFound = 5,
1658 InvalidCommitmentIndex = 6,
1660 RuntimeError = 7,
1662 InvalidCurveConfig = 8,
1664 }
1665
1666 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 #[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 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 #[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 unsafe fn get_wrapper<'a>(ptr: *mut AvssEngineOpaque) -> &'a AvssFfiWrapper {
1947 &*(ptr as *const AvssFfiWrapper)
1948 }
1949
1950 #[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 #[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 #[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 #[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 #[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 #[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 #[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}