1#![forbid(unsafe_code)]
46
47pub mod backend;
48pub mod client;
49pub mod compiler;
50pub mod config;
51pub mod consensus;
52pub mod coordinator;
53pub mod docs;
54pub mod error;
55pub mod input_file;
56pub mod networking;
57pub mod observability;
58pub mod prelude;
59pub mod program;
60pub mod runtime;
61pub mod server;
62pub mod types;
63pub mod vm;
64
65use std::path::{Path, PathBuf};
66use std::time::Duration;
67
68pub use backend::{
69 avss::{AvssBackend, AvssEngine},
70 honeybadger::HoneyBadgerBackend,
71 Backend, MpcBackend,
72};
73pub use client::{
74 ClientBuilder, ClientState, ClientSummary, ComputationHandle, ComputationStatus,
75 ComputationSummary, OffChainClientConfig, OffChainClientConfigBuilder, StoffelClient,
76};
77pub use compiler::CompilationOptions;
78pub use config::{
79 Curve, MpcConfig, MpcConfigBuilder, MpcConfigSummary, MpcSection, NetworkConfig,
80 NetworkConfigBuilder, NetworkConfigSummary, NetworkDeployment, NetworkDeploymentBuilder,
81 NetworkSection, PreprocessingConfig,
82};
83pub use consensus::{ConsensusGate, NodePublicKey, VerifiedOrdering};
84pub use coordinator::{
85 Coordinator, OffChainCoordinator, OffChainCoordinatorClient, OffChainCoordinatorServer,
86 ShareBound,
87};
88pub use error::{ConsensusError, CoordinatorError, Error, ErrorCategory, NetworkError, Result};
89pub use input_file::{load_client_inputs_file, load_named_inputs_file};
90pub use networking::{NetworkManager, QuicNetworkConfig, QuicNetworkManager};
91pub use observability::{
92 init_tracing, HealthStatus, OpenTelemetryGuard, ServerMetrics, ServerMetricsSnapshot,
93 TracingConfig, TracingConfigBuilder, TracingConfigSummary,
94};
95pub use program::{
96 BytecodeSummary, ClientMetadata, ClientMetadataSummary, FunctionMetadata, FunctionSummary,
97 Program, ProgramSummary,
98};
99pub use runtime::{LocalNetworkBuilder, RuntimeSummary, StoffelRuntime};
100pub use server::{
101 OffChainServerConfig, OffChainServerConfigBuilder, ServerBuilder, ServerState, ServerSummary,
102 StoffelServer,
103};
104pub use stoffel_vm_types::compiled_binary::FunctionType;
105pub use stoffel_vm_types::core_types::ShareType;
106pub use types::{
107 ClientId, ClientInputValue, ClientOutputValue, ClientValueType, FieldElement,
108 GeneratedProgramManifest, GroupElement, MaskIndex, PartyId, ProgramArgs, PublicKey, Round,
109 Share, TypedClientInputs, TypedClientOutputs, Value, ValueSummary,
110};
111pub use vm::LocalClientOutput;
112
113#[derive(Debug, Clone)]
114enum ProgramSource {
115 Source { source: String, filename: String },
116 File { path: std::path::PathBuf },
117 Bytecode(Vec<u8>),
118 Program(Program),
119}
120
121pub trait LoadableProgram {
123 fn load_program(self) -> Result<Program>;
124}
125
126impl LoadableProgram for Program {
127 fn load_program(self) -> Result<Program> {
128 Ok(self)
129 }
130}
131
132impl LoadableProgram for &Program {
133 fn load_program(self) -> Result<Program> {
134 Ok(self.clone())
135 }
136}
137
138impl LoadableProgram for &[u8] {
139 fn load_program(self) -> Result<Program> {
140 Program::from_bytecode(self)
141 }
142}
143
144impl LoadableProgram for Vec<u8> {
145 fn load_program(self) -> Result<Program> {
146 Program::from_bytecode(&self)
147 }
148}
149
150impl LoadableProgram for &Path {
151 fn load_program(self) -> Result<Program> {
152 Program::from_bytecode_file(self)
153 }
154}
155
156impl LoadableProgram for PathBuf {
157 fn load_program(self) -> Result<Program> {
158 Program::from_bytecode_file(self)
159 }
160}
161
162impl LoadableProgram for &PathBuf {
163 fn load_program(self) -> Result<Program> {
164 Program::from_bytecode_file(self)
165 }
166}
167
168#[derive(Debug, Clone)]
170pub struct Stoffel {
171 source: Option<ProgramSource>,
172 mpc_config: MpcConfig,
173 backend_explicit: bool,
174 network_config: Option<NetworkConfig>,
175 local_runner_path: Option<PathBuf>,
176 config_error: Option<String>,
177 inputs: Vec<(String, Value)>,
178 client_inputs: Vec<(u64, Vec<Value>)>,
179 expected_clients: Option<usize>,
180 client_output_counts: std::collections::HashMap<u64, u64>,
181 compiler_options: CompilationOptions,
182}
183
184impl Stoffel {
185 pub fn compile(source: &str) -> Result<Self> {
187 Ok(Self::from_source(ProgramSource::Source {
188 source: source.to_owned(),
189 filename: "<sdk>".to_owned(),
190 }))
191 }
192
193 pub fn compile_file(path: impl AsRef<Path>) -> Result<Self> {
195 Ok(Self::from_source(ProgramSource::File {
196 path: path.as_ref().to_path_buf(),
197 }))
198 }
199
200 pub fn load(bytecode: &[u8]) -> Result<Self> {
202 Ok(Self::from_source(ProgramSource::Bytecode(
203 bytecode.to_vec(),
204 )))
205 }
206
207 pub fn load_file(path: impl AsRef<Path>) -> Result<Self> {
209 let bytecode = std::fs::read(path)?;
210 Self::load(&bytecode)
211 }
212
213 pub fn load_program(program: impl LoadableProgram) -> Result<StoffelRuntime> {
234 Self::from_source(ProgramSource::Program(program.load_program()?)).build()
235 }
236
237 pub fn parties(mut self, n: usize) -> Self {
239 self.mpc_config.parties = n;
240 self
241 }
242
243 pub fn threshold(mut self, t: usize) -> Self {
245 self.mpc_config.threshold = t;
246 self
247 }
248
249 pub fn instance_id(mut self, id: u64) -> Self {
251 self.mpc_config.instance_id = id;
252 self
253 }
254
255 pub fn backend(mut self, backend: MpcBackend) -> Self {
257 self.mpc_config.backend = backend;
258 self.backend_explicit = true;
259 self
260 }
261
262 pub fn manifest<M: GeneratedProgramManifest>(self) -> Self {
264 self.backend(M::BACKEND)
265 }
266
267 pub fn honeybadger(mut self) -> Self {
269 self.mpc_config.backend = MpcBackend::HoneyBadger;
270 self.backend_explicit = true;
271 self
272 }
273
274 pub fn avss(mut self, curve: Curve) -> Self {
276 self.mpc_config.backend = MpcBackend::Avss { curve };
277 self.backend_explicit = true;
278 self
279 }
280
281 pub fn curve(mut self, curve: Curve) -> Self {
283 self.mpc_config.backend = MpcBackend::Avss { curve };
284 self.backend_explicit = true;
285 self
286 }
287
288 pub fn optimize(mut self, enabled: bool) -> Self {
290 self.compiler_options.optimize = enabled;
291 self
292 }
293
294 pub fn optimization_level(mut self, level: u8) -> Self {
296 self.compiler_options.optimization_level = level;
297 if level > 0 {
298 self.compiler_options.optimize = true;
299 }
300 self
301 }
302
303 pub fn print_ir(mut self, enabled: bool) -> Self {
305 self.compiler_options.print_ir = enabled;
306 self
307 }
308
309 pub fn compiler_options(mut self, options: CompilationOptions) -> Self {
311 self.compiler_options = options;
312 self
313 }
314
315 pub fn entry_points<I, S>(mut self, entry_points: I) -> Self
319 where
320 I: IntoIterator<Item = S>,
321 S: Into<String>,
322 {
323 self.compiler_options.entry_points = entry_points.into_iter().map(Into::into).collect();
324 self
325 }
326
327 pub fn with_inputs<V>(mut self, inputs: &[(&str, V)]) -> Self
329 where
330 V: Clone + Into<Value>,
331 {
332 self.inputs = inputs
333 .iter()
334 .map(|(name, value)| ((*name).to_owned(), value.clone().into()))
335 .collect();
336 self
337 }
338
339 pub fn with_input<V>(mut self, name: impl Into<String>, value: V) -> Self
341 where
342 V: Into<Value>,
343 {
344 self.inputs.push((name.into(), value.into()));
345 self
346 }
347
348 pub fn with_input_file(mut self, path: impl AsRef<Path>) -> Result<Self> {
350 self.inputs = input_file::load_named_inputs_file(path)?;
351 Ok(self)
352 }
353
354 pub fn with_client_input<V>(mut self, client_slot: u64, values: &[V]) -> Self
359 where
360 V: Clone + Into<Value>,
361 {
362 self.client_inputs.push((
363 client_slot,
364 values.iter().cloned().map(Into::into).collect(),
365 ));
366 self
367 }
368
369 pub fn with_client_input_file(mut self, path: impl AsRef<Path>) -> Result<Self> {
371 self.client_inputs = input_file::load_client_inputs_file(path)?;
372 Ok(self)
373 }
374
375 pub fn with_client_inputs<V>(mut self, inputs: &[(u64, &[V])]) -> Self
377 where
378 V: Clone + Into<Value>,
379 {
380 self.client_inputs = inputs
381 .iter()
382 .map(|(client_slot, values)| {
383 (
384 *client_slot,
385 values.iter().cloned().map(Into::into).collect(),
386 )
387 })
388 .collect();
389 self
390 }
391
392 pub fn expected_output_clients(mut self, n: usize) -> Self {
394 self.expected_clients = Some(n);
395 self
396 }
397
398 pub fn client_output_count(mut self, client_slot: u64, count: u64) -> Self {
404 self.client_output_counts.insert(client_slot, count);
405 self
406 }
407
408 pub fn network_config(mut self, config: NetworkConfig) -> Self {
410 self.network_config = Some(config);
411 self
412 }
413
414 pub fn network_config_file(mut self, path: impl AsRef<Path>) -> Self {
416 match NetworkConfig::from_toml_file(path) {
417 Ok(config) => self.network_config = Some(config),
418 Err(error) => self.config_error = Some(error.to_string()),
419 }
420 self
421 }
422
423 pub fn local_runner_path(mut self, path: impl AsRef<Path>) -> Self {
425 self.local_runner_path = Some(path.as_ref().to_path_buf());
426 self
427 }
428
429 pub fn build(self) -> Result<StoffelRuntime> {
431 if let Some(error) = self.config_error {
432 return Err(Error::Configuration(error));
433 }
434 let mut mpc_config = self.mpc_config;
435 let mut backend_explicit = self.backend_explicit;
436 if let Some(config) = &self.network_config {
437 mpc_config = config.to_mpc_config(mpc_config.instance_id)?;
438 backend_explicit = true;
439 }
440 mpc_config.validate()?;
441
442 let source = self
443 .source
444 .ok_or_else(|| Error::Configuration("no program source configured".to_owned()))?;
445 let program = match source {
446 ProgramSource::Source { source, filename } => compiler::compile_source_with_options(
447 &source,
448 &filename,
449 mpc_config.backend,
450 self.compiler_options,
451 )?,
452 ProgramSource::File { path } => compiler::compile_file_with_options(
453 &path,
454 mpc_config.backend,
455 self.compiler_options,
456 )?,
457 ProgramSource::Bytecode(bytecode) => {
458 let program = Program::from_bytecode(&bytecode)?;
459 let bytecode_backend =
460 backend_from_bytecode(program.bytecode_backend(), program.bytecode_curve())?;
461 if backend_explicit {
462 validate_bytecode_backend(&program, mpc_config.backend)?;
463 } else {
464 mpc_config.backend = bytecode_backend;
465 }
466 program
467 }
468 ProgramSource::Program(program) => {
469 let bytecode_backend =
470 backend_from_bytecode(program.bytecode_backend(), program.bytecode_curve())?;
471 if backend_explicit {
472 validate_bytecode_backend(&program, mpc_config.backend)?;
473 } else {
474 mpc_config.backend = bytecode_backend;
475 }
476 program
477 }
478 };
479 validate_bytecode_backend(&program, mpc_config.backend)?;
480 if program.is_empty() {
481 return Err(Error::Configuration(
482 "program must contain at least one function".to_owned(),
483 ));
484 }
485 if let Some(expected_clients) = self.expected_clients {
486 if expected_clients == 0 {
487 return Err(Error::Configuration(
488 "expected_clients must be greater than 0".to_owned(),
489 ));
490 }
491 program.validate_expected_clients(expected_clients)?;
492 }
493 if let Some(config) = &self.network_config {
494 validate_program_network_config(&program, config)?;
495 }
496 let mut runtime = StoffelRuntime::new(
497 program,
498 Some(mpc_config),
499 self.network_config,
500 self.local_runner_path,
501 self.inputs,
502 self.client_inputs,
503 self.expected_clients,
504 );
505 runtime.set_client_output_counts(self.client_output_counts);
506 Ok(runtime)
507 }
508
509 pub fn validate_client_inputs(self) -> Result<()> {
511 let runtime = self.build()?;
512 runtime.validate_client_inputs()
513 }
514
515 #[tracing::instrument(skip_all)]
524 pub async fn execute_local(self) -> Result<Vec<Value>> {
525 self.execute_local_function("main").await
526 }
527
528 #[tracing::instrument(skip_all, fields(function = name))]
530 pub async fn execute_local_function(self, name: &str) -> Result<Vec<Value>> {
531 let (runtime, entry) = self.build_for_local_execution(name)?;
532 vm::execute_local(&runtime, &entry).await
533 }
534
535 pub async fn execute_local_capturing_output(self) -> Result<(Vec<Value>, String)> {
541 self.execute_local_function_capturing_output("main").await
542 }
543
544 #[tracing::instrument(skip_all, fields(function = name))]
546 pub async fn execute_local_function_capturing_output(
547 self,
548 name: &str,
549 ) -> Result<(Vec<Value>, String)> {
550 let (runtime, entry) = self.build_for_local_execution(name)?;
551 let (values, program_output, _client_outputs) =
552 vm::execute_local_capturing_with_options(&runtime, &entry, Default::default()).await?;
553 Ok((values, program_output))
554 }
555
556 pub async fn execute_local_capturing_client_outputs(
561 self,
562 ) -> Result<(Vec<Value>, Vec<vm::LocalClientOutput>)> {
563 self.execute_local_function_capturing_client_outputs("main")
564 .await
565 }
566
567 #[tracing::instrument(skip_all, fields(function = name))]
569 pub async fn execute_local_function_capturing_client_outputs(
570 self,
571 name: &str,
572 ) -> Result<(Vec<Value>, Vec<vm::LocalClientOutput>)> {
573 let (runtime, entry) = self.build_for_local_execution(name)?;
574 let (values, _program_output, client_outputs) =
575 vm::execute_local_capturing_with_options(&runtime, &entry, Default::default()).await?;
576 Ok((values, client_outputs))
577 }
578
579 pub async fn execute_local_capturing_client_outputs_with_timeout(
582 self,
583 timeout: Duration,
584 ) -> Result<(Vec<Value>, Vec<vm::LocalClientOutput>)> {
585 self.execute_local_function_capturing_client_outputs_with_timeout("main", timeout)
586 .await
587 }
588
589 #[tracing::instrument(skip_all, fields(function = name))]
592 pub async fn execute_local_function_capturing_client_outputs_with_timeout(
593 self,
594 name: &str,
595 timeout: Duration,
596 ) -> Result<(Vec<Value>, Vec<vm::LocalClientOutput>)> {
597 if timeout.is_zero() {
598 return Err(Error::Configuration(
599 "local network timeout must be greater than zero".to_owned(),
600 ));
601 }
602 let (runtime, entry) = self.build_for_local_execution(name)?;
603 let (values, _program_output, client_outputs) = vm::execute_local_capturing_with_options(
604 &runtime,
605 &entry,
606 vm::LocalExecutionOptions {
607 runner_path: None,
608 timeout: Some(timeout),
609 },
610 )
611 .await?;
612 Ok((values, client_outputs))
613 }
614
615 #[tracing::instrument(skip_all)]
617 pub async fn execute_local_with_timeout(self, timeout: Duration) -> Result<Vec<Value>> {
618 self.execute_local_function_with_timeout("main", timeout)
619 .await
620 }
621
622 #[tracing::instrument(skip_all, fields(function = name))]
625 pub async fn execute_local_function_with_timeout(
626 self,
627 name: &str,
628 timeout: Duration,
629 ) -> Result<Vec<Value>> {
630 if timeout.is_zero() {
631 return Err(Error::Configuration(
632 "local network timeout must be greater than zero".to_owned(),
633 ));
634 }
635 let (runtime, entry) = self.build_for_local_execution(name)?;
636 vm::execute_local_with_options(
637 &runtime,
638 &entry,
639 vm::LocalExecutionOptions {
640 runner_path: None,
641 timeout: Some(timeout),
642 },
643 )
644 .await
645 }
646
647 pub fn execute_clear(self) -> Result<Vec<Value>> {
653 self.execute_clear_function("main")
654 }
655
656 pub fn execute_clear_function(self, name: &str) -> Result<Vec<Value>> {
658 let runtime = self.build()?;
659 vm::execute_clear(&runtime, name)
660 }
661
662 pub fn server(party_id: usize) -> ServerBuilder {
664 ServerBuilder::new(party_id)
665 }
666
667 pub fn client() -> ClientBuilder {
669 ClientBuilder::new()
670 }
671
672 fn from_source(source: ProgramSource) -> Self {
673 Self {
674 source: Some(source),
675 mpc_config: MpcConfig::default(),
676 backend_explicit: false,
677 network_config: None,
678 local_runner_path: None,
679 config_error: None,
680 inputs: Vec::new(),
681 client_inputs: Vec::new(),
682 expected_clients: None,
683 client_output_counts: std::collections::HashMap::new(),
684 compiler_options: CompilationOptions::default(),
685 }
686 }
687
688 fn build_for_local_execution(self, name: &str) -> Result<(StoffelRuntime, String)> {
689 if self.inputs.is_empty() {
690 return Ok((self.build()?, name.to_owned()));
691 }
692 if !self.client_inputs.is_empty() {
693 return Err(Error::Configuration(
694 "`with_inputs` and `with_client_input` cannot be combined for local execution"
695 .to_owned(),
696 ));
697 }
698 let parameter_types = local_source_parameter_types(&self.source, name)?;
699 let mut probe = self.clone();
700 probe.inputs.clear();
701 let probe_runtime = probe.build()?;
702 if probe_runtime.program().has_client_input_metadata() {
703 return Err(Error::Unsupported(
704 "SDK local named-input adapter does not support programs that already declare ClientStore input metadata"
705 .to_owned(),
706 ));
707 }
708
709 let function = probe_runtime
710 .program()
711 .function(name)
712 .ok_or_else(|| Error::FunctionNotFound(name.to_owned()))?;
713 let ordered_inputs =
714 ordered_inputs_for_parameters(&self.inputs, name, function.parameters())?;
715 let entry = unique_local_entry_name(probe_runtime.program(), "__stoffel_sdk_local_entry");
716 let input_shapes = ordered_inputs
717 .iter()
718 .enumerate()
719 .map(|(index, value)| {
720 if parameter_types
721 .as_ref()
722 .and_then(|types| types.get(index))
723 .is_some_and(|ty| !local_parameter_type_is_secret(ty))
724 {
725 crate::program::LocalInputShape::clear_from_value(value)
726 } else {
727 crate::program::LocalInputShape::secret_from_value(value)
728 }
729 })
730 .collect::<Vec<_>>();
731 let client_inputs = flatten_local_named_inputs(&ordered_inputs, &input_shapes)?;
732 let wrapped_program =
733 probe_runtime
734 .program()
735 .with_local_client_input_wrapper(name, &entry, &input_shapes)?;
736 let local_client_inputs = if client_inputs.is_empty() {
737 Vec::new()
738 } else {
739 vec![(0, client_inputs)]
740 };
741 Ok((
742 probe_runtime.with_program_and_client_inputs(wrapped_program, local_client_inputs),
743 entry,
744 ))
745 }
746}
747
748fn flatten_local_named_inputs(
749 values: &[Value],
750 shapes: &[crate::program::LocalInputShape],
751) -> Result<Vec<Value>> {
752 let mut out = Vec::new();
753 for (value, shape) in values.iter().zip(shapes) {
754 flatten_local_named_input(value, shape, &mut out)?;
755 }
756 Ok(out)
757}
758
759fn flatten_local_named_input(
760 value: &Value,
761 shape: &crate::program::LocalInputShape,
762 out: &mut Vec<Value>,
763) -> Result<()> {
764 match (value, shape) {
765 (_, crate::program::LocalInputShape::Clear(_)) => Ok(()),
766 (Value::List(values), crate::program::LocalInputShape::List(shapes)) => {
767 for (value, shape) in values.iter().zip(shapes) {
768 flatten_local_named_input(value, shape, out)?;
769 }
770 Ok(())
771 }
772 (Value::Object(fields), crate::program::LocalInputShape::Object(shapes)) => {
773 for (name, shape) in shapes {
774 if let Some(value) = fields.get(name) {
775 flatten_local_named_input(value, shape, out)?;
776 }
777 }
778 Ok(())
779 }
780 (_, crate::program::LocalInputShape::Share(_)) => {
781 out.push(value.clone());
782 Ok(())
783 }
784 _ => Err(Error::InvalidInput(
785 "local named-input wrapper shape does not match input value".to_owned(),
786 )),
787 }
788}
789
790fn local_source_parameter_types(
791 source: &Option<ProgramSource>,
792 function_name: &str,
793) -> Result<Option<Vec<String>>> {
794 let Some(source) = source else {
795 return Ok(None);
796 };
797 let source = match source {
798 ProgramSource::Source { source, .. } => source.clone(),
799 ProgramSource::File { path } => std::fs::read_to_string(path)?,
800 ProgramSource::Bytecode(_) | ProgramSource::Program(_) => return Ok(None),
801 };
802 Ok(parse_function_parameter_types(&source, function_name))
803}
804
805fn parse_function_parameter_types(source: &str, function_name: &str) -> Option<Vec<String>> {
806 let prefix = format!("def {function_name}(");
807 let start = source.find(&prefix)? + prefix.len();
808 let mut depth = 1usize;
809 let mut end = start;
810 for (offset, character) in source[start..].char_indices() {
811 match character {
812 '(' => depth += 1,
813 ')' => {
814 depth = depth.checked_sub(1)?;
815 if depth == 0 {
816 end = start + offset;
817 break;
818 }
819 }
820 _ => {}
821 }
822 }
823 if depth != 0 {
824 return None;
825 }
826
827 let params = &source[start..end];
828 let mut types = Vec::new();
829 for param in split_parameter_list(params) {
830 let (_, ty) = param.split_once(':')?;
831 types.push(ty.trim().to_owned());
832 }
833 Some(types)
834}
835
836fn split_parameter_list(params: &str) -> Vec<&str> {
837 let mut parts = Vec::new();
838 let mut start = 0;
839 let mut depth = 0usize;
840 for (index, character) in params.char_indices() {
841 match character {
842 '[' | '(' => depth += 1,
843 ']' | ')' => depth = depth.saturating_sub(1),
844 ',' if depth == 0 => {
845 let part = params[start..index].trim();
846 if !part.is_empty() {
847 parts.push(part);
848 }
849 start = index + character.len_utf8();
850 }
851 _ => {}
852 }
853 }
854 let part = params[start..].trim();
855 if !part.is_empty() {
856 parts.push(part);
857 }
858 parts
859}
860
861fn local_parameter_type_is_secret(ty: &str) -> bool {
862 ty.split(|character: char| {
863 !(character.is_ascii_alphanumeric()
864 || character == '_'
865 || character == '['
866 || character == ']')
867 })
868 .any(|token| token == "secret" || token == "Share" || token.contains("secret"))
869}
870
871fn ordered_inputs_for_parameters(
872 inputs: &[(String, Value)],
873 function_name: &str,
874 parameters: &[String],
875) -> Result<Vec<Value>> {
876 for (name, _) in inputs {
877 if !parameters.iter().any(|parameter| parameter == name) {
878 return Err(Error::InvalidInput(format!(
879 "unexpected input '{name}' for function '{function_name}'"
880 )));
881 }
882 }
883
884 let mut values = Vec::with_capacity(parameters.len());
885 for parameter in parameters {
886 let mut matches = inputs
887 .iter()
888 .filter(|(name, _)| name == parameter)
889 .map(|(_, value)| value);
890 let Some(value) = matches.next() else {
891 return Err(Error::InvalidInput(format!(
892 "missing input '{parameter}' for function '{function_name}'"
893 )));
894 };
895 if matches.next().is_some() {
896 return Err(Error::InvalidInput(format!(
897 "duplicate input '{parameter}' for function '{function_name}'"
898 )));
899 }
900 values.push(value.clone());
901 }
902 Ok(values)
903}
904
905fn unique_local_entry_name(program: &Program, base: &str) -> String {
906 if program.function(base).is_none() {
907 return base.to_owned();
908 }
909 for suffix in 1.. {
910 let candidate = format!("{base}_{suffix}");
911 if program.function(&candidate).is_none() {
912 return candidate;
913 }
914 }
915 unreachable!("unbounded suffix search should always find a unique function name")
916}
917
918fn backend_from_bytecode(
919 backend: stoffel_vm_types::compiled_binary::MpcBackend,
920 curve: stoffel_vm_types::compiled_binary::MpcCurve,
921) -> Result<MpcBackend> {
922 match backend {
923 stoffel_vm_types::compiled_binary::MpcBackend::HoneyBadger => Ok(MpcBackend::HoneyBadger),
924 stoffel_vm_types::compiled_binary::MpcBackend::Avss => Ok(MpcBackend::Avss {
925 curve: curve_from_bytecode(curve),
926 }),
927 }
928}
929
930fn curve_from_bytecode(curve: stoffel_vm_types::compiled_binary::MpcCurve) -> Curve {
931 match curve {
932 stoffel_vm_types::compiled_binary::MpcCurve::Bls12_381 => Curve::Bls12_381,
933 stoffel_vm_types::compiled_binary::MpcCurve::Bn254 => Curve::Bn254,
934 stoffel_vm_types::compiled_binary::MpcCurve::Curve25519 => Curve::Curve25519,
935 stoffel_vm_types::compiled_binary::MpcCurve::Ed25519 => Curve::Ed25519,
936 stoffel_vm_types::compiled_binary::MpcCurve::Secp256k1 => Curve::Secp256k1,
937 stoffel_vm_types::compiled_binary::MpcCurve::Secp256r1 => Curve::Secp256r1,
938 }
939}
940
941fn validate_bytecode_backend(program: &Program, expected: MpcBackend) -> Result<()> {
942 let actual = program.bytecode_backend();
943 if actual != expected.compiler_backend() {
944 return Err(Error::Configuration(format!(
945 "bytecode MPC backend ({actual:?}) does not match runtime backend ({expected})"
946 )));
947 }
948 let Some(expected_curve) = expected.curve() else {
949 return Ok(());
950 };
951 let actual_curve = curve_from_bytecode(program.bytecode_curve());
952 if actual_curve != expected_curve {
953 return Err(Error::Configuration(format!(
954 "bytecode AVSS curve ({actual_curve}) does not match runtime backend ({expected})"
955 )));
956 }
957 Ok(())
958}
959
960fn validate_program_network_config(program: &Program, config: &NetworkConfig) -> Result<()> {
961 config.validate_server_addresses()?;
962 program.validate_expected_clients(config.network.expected_clients)
963}