1use std::fmt;
8use std::io::Cursor;
9use std::path::Path;
10
11use serde::{Deserialize, Serialize};
12use stoffel_vm_types::compiled_binary::{
13 utils, ClientIoManifest, ClientIoSchema, CompiledBinary, CompiledFunction, CompiledInstruction,
14 FunctionType,
15};
16use stoffel_vm_types::core_types::{ShareType, Value as VmValue};
17use stoffel_vm_types::registers::DEFAULT_SECRET_REGISTER_START;
18
19use crate::compiler;
20use crate::error::{Error, Result};
21use crate::types::Value;
22
23#[derive(Debug, Clone)]
24pub struct Program {
25 binary: CompiledBinary,
26}
27
28#[derive(Debug, Clone, PartialEq)]
29pub(crate) enum LocalInputShape {
30 Clear(Value),
31 Share(ShareType),
32 List(Vec<LocalInputShape>),
33 Object(Vec<(String, LocalInputShape)>),
34}
35
36impl LocalInputShape {
37 pub(crate) fn secret_from_value(value: &Value) -> Self {
38 match value {
39 Value::List(values) => Self::List(
40 values
41 .iter()
42 .map(Self::secret_from_value)
43 .collect::<Vec<_>>(),
44 ),
45 Value::Object(fields) => Self::Object(
46 fields
47 .iter()
48 .map(|(name, value)| (name.clone(), Self::secret_from_value(value)))
49 .collect(),
50 ),
51 Value::I64(_)
52 | Value::U64(_)
53 | Value::Float(_)
54 | Value::String(_)
55 | Value::Bytes(_)
56 | Value::Unit => Self::Share(ShareType::default_secret_int()),
57 Value::Bool(_) => Self::Share(ShareType::SecretInt { bit_length: 1 }),
58 }
59 }
60
61 pub(crate) fn clear_from_value(value: &Value) -> Self {
62 match value {
63 Value::List(values) => Self::List(
64 values
65 .iter()
66 .map(Self::clear_from_value)
67 .collect::<Vec<_>>(),
68 ),
69 Value::Object(fields) => Self::Object(
70 fields
71 .iter()
72 .map(|(name, value)| (name.clone(), Self::clear_from_value(value)))
73 .collect(),
74 ),
75 value => Self::Clear(value.clone()),
76 }
77 }
78
79 fn share_count(&self) -> usize {
80 match self {
81 Self::Clear(_) => 0,
82 Self::Share(_) => 1,
83 Self::List(items) => items.iter().map(Self::share_count).sum(),
84 Self::Object(fields) => fields.iter().map(|(_, shape)| shape.share_count()).sum(),
85 }
86 }
87
88 fn share_types(&self, out: &mut Vec<ShareType>) {
89 match self {
90 Self::Clear(_) => {}
91 Self::Share(ty) => out.push(*ty),
92 Self::List(items) => {
93 for item in items {
94 item.share_types(out);
95 }
96 }
97 Self::Object(fields) => {
98 for (_, shape) in fields {
99 shape.share_types(out);
100 }
101 }
102 }
103 }
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
107pub struct ProgramSummary {
108 pub function_count: usize,
109 pub function_names: Vec<String>,
110 pub functions: Vec<FunctionSummary>,
111 pub total_instruction_count: usize,
112 pub total_register_count: usize,
113 pub bytecode_backend: String,
114 pub bytecode_curve: String,
115 pub client_count: usize,
116 pub client_slots: Vec<u64>,
117 pub clients: Vec<ClientMetadataSummary>,
118 pub total_client_input_count: usize,
119 pub total_client_output_count: usize,
120 pub minimum_expected_clients: usize,
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124pub struct FunctionSummary {
125 pub name: String,
126 pub arg_count: usize,
127 pub parameters: Vec<String>,
128 pub parameter_types: Vec<FunctionType>,
129 pub return_type: FunctionType,
130 pub register_count: usize,
131 pub instruction_count: usize,
132 pub upvalue_count: usize,
133 pub parent: Option<String>,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
137pub struct ClientMetadataSummary {
138 pub client_slot: u64,
139 pub input_count: usize,
140 pub output_count: usize,
141 pub inputs: Vec<ShareType>,
142 pub outputs: Vec<ShareType>,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
146pub struct BytecodeSummary {
147 pub byte_len: usize,
148 pub program: ProgramSummary,
149}
150
151impl Program {
152 pub fn new(binary: CompiledBinary) -> Self {
153 Self { binary }
154 }
155
156 pub fn from_bytecode(bytecode: &[u8]) -> Result<Self> {
157 compiler::load_bytecode(bytecode)
158 }
159
160 pub fn from_bytecode_file(path: impl AsRef<Path>) -> Result<Self> {
161 let bytecode = std::fs::read(path)?;
162 Self::from_bytecode(&bytecode)
163 }
164
165 pub fn to_bytecode(&self) -> Result<Vec<u8>> {
166 let mut bytes = Vec::new();
167 self.binary
168 .serialize(&mut Cursor::new(&mut bytes))
169 .map_err(|error| Error::Bytecode(format!("{error:?}")))?;
170 Ok(bytes)
171 }
172
173 pub fn save_bytecode(&self, path: impl AsRef<Path>) -> Result<()> {
174 utils::save_to_file(&self.binary, path)
175 .map_err(|error| Error::Bytecode(format!("{error:?}")))
176 }
177
178 pub fn bytecode_summary(&self) -> Result<BytecodeSummary> {
179 Ok(BytecodeSummary {
180 byte_len: self.to_bytecode()?.len(),
181 program: self.summary(),
182 })
183 }
184
185 pub fn disassemble(&self) -> String {
186 stoffellang::binary_converter::disassemble(&self.binary)
187 }
188
189 pub fn function_count(&self) -> usize {
190 self.binary.functions.len()
191 }
192
193 pub fn function_names(&self) -> impl Iterator<Item = &str> {
194 self.binary
195 .functions
196 .iter()
197 .map(|function| function.name.as_str())
198 }
199
200 pub fn total_instruction_count(&self) -> usize {
201 self.binary
202 .functions
203 .iter()
204 .map(|function| function.instructions.len())
205 .sum()
206 }
207
208 pub fn total_register_count(&self) -> usize {
209 self.binary
210 .functions
211 .iter()
212 .map(|function| function.register_count)
213 .sum()
214 }
215
216 pub fn is_empty(&self) -> bool {
217 self.binary.functions.is_empty()
218 }
219
220 pub fn summary(&self) -> ProgramSummary {
221 ProgramSummary {
222 function_count: self.function_count(),
223 function_names: self.function_names().map(ToOwned::to_owned).collect(),
224 functions: self
225 .functions()
226 .map(|function| function.summary())
227 .collect(),
228 total_instruction_count: self.total_instruction_count(),
229 total_register_count: self.total_register_count(),
230 bytecode_backend: bytecode_backend_name(self.bytecode_backend()).to_owned(),
231 bytecode_curve: bytecode_curve_name(self.bytecode_curve()).to_owned(),
232 client_count: self.client_count(),
233 client_slots: self.client_slots().collect(),
234 clients: self.clients().map(|client| client.summary()).collect(),
235 total_client_input_count: self.total_client_input_count(),
236 total_client_output_count: self.total_client_output_count(),
237 minimum_expected_clients: self.minimum_expected_clients(),
238 }
239 }
240
241 pub fn client_io_manifest(&self) -> &ClientIoManifest {
242 &self.binary.client_io_manifest
243 }
244
245 pub fn client_count(&self) -> usize {
246 self.binary.client_io_manifest.clients.len()
247 }
248
249 pub fn client_slots(&self) -> impl Iterator<Item = u64> + '_ {
250 self.binary
251 .client_io_manifest
252 .clients
253 .iter()
254 .map(|client| client.client_slot)
255 }
256
257 pub fn total_client_input_count(&self) -> usize {
258 self.binary
259 .client_io_manifest
260 .clients
261 .iter()
262 .map(|client| client.inputs.len())
263 .sum()
264 }
265
266 pub fn total_client_output_count(&self) -> usize {
267 self.binary
268 .client_io_manifest
269 .clients
270 .iter()
271 .map(|client| client.outputs.len())
272 .sum()
273 }
274
275 pub fn minimum_expected_clients(&self) -> usize {
276 self.binary
277 .client_io_manifest
278 .clients
279 .iter()
280 .map(|client| usize::try_from(client.client_slot).unwrap_or(usize::MAX))
281 .map(|client_slot| client_slot.saturating_add(1))
282 .max()
283 .unwrap_or(0)
284 }
285
286 pub fn has_client_io(&self) -> bool {
287 !self.binary.client_io_manifest.clients.is_empty()
288 }
289
290 pub fn has_client_input_metadata(&self) -> bool {
297 self.binary
298 .client_io_manifest
299 .clients
300 .iter()
301 .any(|client| !client.inputs.is_empty())
302 }
303
304 pub fn validate_expected_clients(&self, expected_clients: usize) -> Result<()> {
305 let minimum_expected_clients = self.minimum_expected_clients();
306 if minimum_expected_clients > expected_clients {
307 return Err(Error::Configuration(format!(
308 "program declares ClientStore slot(s) requiring expected_clients >= {minimum_expected_clients}, but expected_clients is {expected_clients}"
309 )));
310 }
311 Ok(())
312 }
313
314 pub fn validate_client_inputs(&self, inputs: &[(u64, &[Value])]) -> Result<()> {
315 let has_declared_clients = self.has_client_io();
316 if !has_declared_clients && inputs.is_empty() {
317 return Ok(());
318 }
319 if has_declared_clients
320 && inputs.is_empty()
321 && self.clients().any(|metadata| metadata.input_count() > 0)
322 {
323 return Err(Error::Configuration(
324 "program declares ClientStore input metadata; provide local client inputs"
325 .to_owned(),
326 ));
327 }
328 if !has_declared_clients {
329 let mut seen_slots = std::collections::HashSet::with_capacity(inputs.len());
330 for (client_slot, _values) in inputs {
331 if !seen_slots.insert(*client_slot) {
332 return Err(Error::Configuration(format!(
333 "client slot {client_slot} was provided more than once"
334 )));
335 }
336 }
337 return Ok(());
338 }
339
340 let mut seen_slots = std::collections::HashSet::with_capacity(inputs.len());
341 for (client_slot, values) in inputs {
342 if !seen_slots.insert(*client_slot) {
343 return Err(Error::Configuration(format!(
344 "client slot {client_slot} was provided more than once"
345 )));
346 }
347 let Some(metadata) = self.client(*client_slot) else {
348 return Err(Error::Configuration(format!(
349 "client slot {client_slot} is not declared in the program client IO manifest"
350 )));
351 };
352 if metadata.input_count() != values.len() {
353 return Err(Error::Configuration(format!(
354 "client slot {client_slot} expects {} inputs, got {}",
355 metadata.input_count(),
356 values.len()
357 )));
358 }
359 }
360
361 for metadata in self.clients() {
362 if metadata.input_count() > 0 && !seen_slots.contains(&metadata.client_slot()) {
363 return Err(Error::Configuration(format!(
364 "client slot {} is declared in the program client IO manifest but no input was provided",
365 metadata.client_slot()
366 )));
367 }
368 }
369
370 Ok(())
371 }
372
373 pub(crate) fn validate_owned_client_inputs(&self, inputs: &[(u64, Vec<Value>)]) -> Result<()> {
374 let borrowed = inputs
375 .iter()
376 .map(|(client_slot, values)| (*client_slot, values.as_slice()))
377 .collect::<Vec<_>>();
378 self.validate_client_inputs(&borrowed)
379 }
380
381 pub fn clients(&self) -> impl Iterator<Item = ClientMetadata<'_>> {
382 self.binary
383 .client_io_manifest
384 .clients
385 .iter()
386 .map(ClientMetadata)
387 }
388
389 pub fn client(&self, client_slot: u64) -> Option<ClientMetadata<'_>> {
390 self.binary
391 .client_io_manifest
392 .clients
393 .iter()
394 .find(|client| client.client_slot == client_slot)
395 .map(ClientMetadata)
396 }
397
398 pub fn bytecode_backend(&self) -> stoffel_vm_types::compiled_binary::MpcBackend {
399 self.binary.client_io_manifest.mpc_backend
400 }
401
402 pub fn bytecode_curve(&self) -> stoffel_vm_types::compiled_binary::MpcCurve {
403 self.binary.client_io_manifest.mpc_curve
404 }
405
406 pub(crate) fn with_local_client_input_wrapper(
407 &self,
408 call_name: &str,
409 entry_name: &str,
410 input_shapes: &[LocalInputShape],
411 ) -> Result<Self> {
412 if input_shapes.is_empty() {
413 return Err(Error::Configuration(
414 "local client input wrapper requires at least one input".to_owned(),
415 ));
416 }
417
418 let mut binary = self.binary.clone();
419 if binary
420 .functions
421 .iter()
422 .any(|function| function.name == entry_name)
423 {
424 return Err(Error::Configuration(format!(
425 "local client input wrapper entry '{entry_name}' already exists"
426 )));
427 }
428 let Some(_target_function) = binary
429 .functions
430 .iter()
431 .find(|function| function.name == call_name)
432 else {
433 return Err(Error::FunctionNotFound(call_name.to_owned()));
434 };
435 let input_count = input_shapes
436 .iter()
437 .map(LocalInputShape::share_count)
438 .sum::<usize>();
439 let mut input_types = Vec::with_capacity(input_count);
440 for shape in input_shapes {
441 shape.share_types(&mut input_types);
442 }
443
444 let mut instructions = Vec::with_capacity(input_count * 8 + input_shapes.len() + 2);
445 let mut registers = WrapperRegisters::new();
446 let mut next_share_index = 0usize;
447 let mut arg_registers = Vec::with_capacity(input_shapes.len());
448 for shape in input_shapes {
449 let register = emit_local_input_shape(
450 shape,
451 &mut binary,
452 &mut instructions,
453 &mut next_share_index,
454 &mut registers,
455 )?;
456 arg_registers.push(register);
457 }
458 for register in arg_registers {
459 instructions.push(CompiledInstruction::PUSHARG(register));
460 }
461 instructions.push(CompiledInstruction::CALL(call_name.to_owned()));
462 instructions.push(CompiledInstruction::RET(0));
463
464 let register_count = registers.register_count();
465 binary.functions.push(CompiledFunction {
466 name: entry_name.to_owned(),
467 register_count,
468 parameters: Vec::new(),
469 parameter_types: Vec::new(),
470 return_type: FunctionType::Unknown,
471 upvalues: Vec::new(),
472 parent: None,
473 labels: Default::default(),
474 instructions,
475 });
476
477 if input_count > 0 {
483 if let Some(client) = binary
484 .client_io_manifest
485 .clients
486 .iter_mut()
487 .find(|client| client.client_slot == 0)
488 {
489 client.inputs = input_types;
490 } else {
491 binary.client_io_manifest.clients.push(ClientIoSchema {
492 client_slot: 0,
493 inputs: input_types,
494 outputs: Vec::new(),
495 });
496 }
497 }
498
499 Ok(Self::new(binary))
500 }
501
502 pub fn functions(&self) -> impl Iterator<Item = FunctionMetadata<'_>> {
503 self.binary.functions.iter().map(FunctionMetadata)
504 }
505
506 pub fn function(&self, name: &str) -> Option<FunctionMetadata<'_>> {
507 self.binary
508 .functions
509 .iter()
510 .find(|function| function.name == name)
511 .map(FunctionMetadata)
512 }
513
514 pub fn main(&self) -> Option<FunctionMetadata<'_>> {
515 self.function("main")
516 }
517
518 pub fn binary(&self) -> &CompiledBinary {
519 &self.binary
520 }
521
522 pub fn validate_function_inputs(
523 &self,
524 function_name: &str,
525 inputs: &[(String, Value)],
526 ) -> Result<()> {
527 let function = self
528 .function(function_name)
529 .ok_or_else(|| Error::FunctionNotFound(function_name.to_owned()))?;
530 function.validate_inputs(inputs)
531 }
532
533 pub fn validate_function_args(&self, function_name: &str, args: &[Value]) -> Result<()> {
534 let function = self
535 .function(function_name)
536 .ok_or_else(|| Error::FunctionNotFound(function_name.to_owned()))?;
537 function.validate_args(args)
538 }
539}
540
541#[derive(Debug, Clone, Copy)]
542pub struct ClientMetadata<'a>(&'a ClientIoSchema);
543
544impl<'a> ClientMetadata<'a> {
545 pub fn client_slot(&self) -> u64 {
546 self.0.client_slot
547 }
548
549 pub fn input_count(&self) -> usize {
550 self.0.inputs.len()
551 }
552
553 pub fn output_count(&self) -> usize {
554 self.0.outputs.len()
555 }
556
557 pub fn inputs(&self) -> &'a [ShareType] {
558 &self.0.inputs
559 }
560
561 pub fn outputs(&self) -> &'a [ShareType] {
562 &self.0.outputs
563 }
564
565 pub fn summary(&self) -> ClientMetadataSummary {
566 ClientMetadataSummary {
567 client_slot: self.client_slot(),
568 input_count: self.input_count(),
569 output_count: self.output_count(),
570 inputs: self.inputs().to_vec(),
571 outputs: self.outputs().to_vec(),
572 }
573 }
574}
575
576impl fmt::Display for ClientMetadata<'_> {
577 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
578 write!(
579 f,
580 "client {}: {} input(s), {} output(s)",
581 self.client_slot(),
582 self.input_count(),
583 self.output_count()
584 )
585 }
586}
587
588#[derive(Debug, Clone, Copy)]
589pub struct FunctionMetadata<'a>(&'a CompiledFunction);
590
591impl<'a> FunctionMetadata<'a> {
592 pub fn name(&self) -> &str {
593 &self.0.name
594 }
595
596 pub fn arg_count(&self) -> usize {
597 self.0.parameters.len()
598 }
599
600 pub fn parameters(&self) -> &[String] {
601 &self.0.parameters
602 }
603
604 pub fn parameter_names(&self) -> impl Iterator<Item = &str> {
605 self.0.parameters.iter().map(String::as_str)
606 }
607
608 pub fn parameter_types(&self) -> &[FunctionType] {
609 &self.0.parameter_types
610 }
611
612 pub fn return_type(&self) -> &FunctionType {
613 &self.0.return_type
614 }
615
616 pub fn register_count(&self) -> usize {
617 self.0.register_count
618 }
619
620 pub fn instruction_count(&self) -> usize {
621 self.0.instructions.len()
622 }
623
624 pub fn upvalue_count(&self) -> usize {
625 self.0.upvalues.len()
626 }
627
628 pub fn parent(&self) -> Option<&str> {
629 self.0.parent.as_deref()
630 }
631
632 pub fn summary(&self) -> FunctionSummary {
633 FunctionSummary {
634 name: self.name().to_owned(),
635 arg_count: self.arg_count(),
636 parameters: self.parameters().to_vec(),
637 parameter_types: self.parameter_types().to_vec(),
638 return_type: self.return_type().clone(),
639 register_count: self.register_count(),
640 instruction_count: self.instruction_count(),
641 upvalue_count: self.upvalue_count(),
642 parent: self.parent().map(ToOwned::to_owned),
643 }
644 }
645
646 pub fn validate_inputs(&self, inputs: &[(String, Value)]) -> Result<()> {
647 for (name, _) in inputs {
648 if !self.0.parameters.iter().any(|parameter| parameter == name) {
649 return Err(Error::InvalidInput(format!(
650 "unexpected input '{name}' for function '{}'",
651 self.name()
652 )));
653 }
654 }
655
656 for parameter in &self.0.parameters {
657 let mut matches = inputs.iter().filter(|(name, _)| name == parameter);
658 let Some((_, value)) = matches.next() else {
659 return Err(Error::InvalidInput(format!(
660 "missing input '{parameter}' for function '{}'",
661 self.name()
662 )));
663 };
664 if matches.next().is_some() {
665 return Err(Error::InvalidInput(format!(
666 "duplicate input '{parameter}' for function '{}'",
667 self.name()
668 )));
669 }
670 let ty = self
671 .0
672 .parameter_types
673 .get(
674 self.0
675 .parameters
676 .iter()
677 .position(|name| name == parameter)
678 .unwrap_or(usize::MAX),
679 )
680 .unwrap_or(&FunctionType::Unknown);
681 validate_value_against_function_type(parameter, value, ty)?;
682 }
683
684 Ok(())
685 }
686
687 pub fn validate_args(&self, args: &[Value]) -> Result<()> {
688 let expected = self.arg_count();
689 let actual = args.len();
690 if actual != expected {
691 return Err(Error::InvalidInput(format!(
692 "function '{}' expects {expected} argument(s), got {actual}",
693 self.name()
694 )));
695 }
696
697 for (index, value) in args.iter().enumerate() {
698 let parameter = self
699 .0
700 .parameters
701 .get(index)
702 .map(String::as_str)
703 .unwrap_or("argument");
704 let ty = self
705 .0
706 .parameter_types
707 .get(index)
708 .unwrap_or(&FunctionType::Unknown);
709 validate_value_against_function_type(parameter, value, ty)?;
710 }
711
712 Ok(())
713 }
714}
715
716impl fmt::Display for FunctionMetadata<'_> {
717 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
718 write!(f, "{}(", self.name())?;
719 for (index, parameter) in self.parameter_names().enumerate() {
720 if index > 0 {
721 f.write_str(", ")?;
722 }
723 f.write_str(parameter)?;
724 }
725 write!(f, ")")
726 }
727}
728
729fn validate_value_against_function_type(
730 path: &str,
731 value: &Value,
732 ty: &FunctionType,
733) -> Result<()> {
734 let expected = ty.underlying_type();
735 if expected.is_unknown_like() {
736 return Ok(());
737 }
738
739 match expected {
740 FunctionType::Int { signed, bits } => validate_integer(path, value, *signed, *bits),
741 FunctionType::Float | FunctionType::Fixed { .. } => match value {
742 Value::Float(_) | Value::I64(_) | Value::U64(_) => Ok(()),
743 _ => invalid_type(path, expected, value),
744 },
745 FunctionType::String => match value {
746 Value::String(_) => Ok(()),
747 _ => invalid_type(path, expected, value),
748 },
749 FunctionType::Bool => match value {
750 Value::Bool(_) => Ok(()),
751 _ => invalid_type(path, expected, value),
752 },
753 FunctionType::Nil | FunctionType::Void => match value {
754 Value::Unit => Ok(()),
755 _ => invalid_type(path, expected, value),
756 },
757 FunctionType::List(element_type) => match value {
758 Value::List(values) => values.iter().enumerate().try_for_each(|(index, item)| {
759 validate_value_against_function_type(&format!("{path}[{index}]"), item, element_type)
760 }),
761 _ => invalid_type(path, expected, value),
762 },
763 FunctionType::Dict(key_type, value_type) => match value {
764 Value::Object(fields) => fields.iter().try_for_each(|(key, item)| {
765 if !key_type.is_unknown_like() && !matches!(key_type.underlying_type(), FunctionType::String) {
766 return Err(Error::InvalidInput(format!(
767 "input '{path}' expects {expected}, but SDK object inputs only provide string keys"
768 )));
769 }
770 validate_value_against_function_type(&format!("{path}.{key}"), item, value_type)
771 }),
772 _ => invalid_type(path, expected, value),
773 },
774 FunctionType::Object(_) => match value {
775 Value::Object(_) => Ok(()),
776 _ => invalid_type(path, expected, value),
777 },
778 FunctionType::Secret(_)
779 | FunctionType::Generic(_, _)
780 | FunctionType::TypeVar(_)
781 | FunctionType::Unknown => Ok(()),
782 }
783}
784
785fn validate_integer(path: &str, value: &Value, signed: bool, bits: u8) -> Result<()> {
786 let max_unsigned = if bits >= 64 {
787 u64::MAX
788 } else {
789 (1u64 << bits) - 1
790 };
791 if signed {
792 let max = if bits >= 64 {
793 i64::MAX
794 } else {
795 (1i64 << (bits - 1)) - 1
796 };
797 let min = if bits >= 64 {
798 i64::MIN
799 } else {
800 -(1i64 << (bits - 1))
801 };
802 match value {
803 Value::I64(value) if *value >= min && *value <= max => Ok(()),
804 Value::U64(value) if *value <= max as u64 => Ok(()),
805 _ => invalid_type(path, &FunctionType::Int { signed, bits }, value),
806 }
807 } else {
808 match value {
809 Value::U64(value) if *value <= max_unsigned => Ok(()),
810 Value::I64(value) if *value >= 0 && (*value as u64) <= max_unsigned => Ok(()),
811 _ => invalid_type(path, &FunctionType::Int { signed, bits }, value),
812 }
813 }
814}
815
816fn invalid_type(path: &str, expected: &FunctionType, value: &Value) -> Result<()> {
817 Err(Error::InvalidInput(format!(
818 "input '{path}' expects {expected}, got {}",
819 value.kind()
820 )))
821}
822
823fn bytecode_backend_name(backend: stoffel_vm_types::compiled_binary::MpcBackend) -> &'static str {
824 match backend {
825 stoffel_vm_types::compiled_binary::MpcBackend::HoneyBadger => "honeybadger",
826 stoffel_vm_types::compiled_binary::MpcBackend::Avss => "avss",
827 }
828}
829
830fn bytecode_curve_name(curve: stoffel_vm_types::compiled_binary::MpcCurve) -> &'static str {
831 match curve {
832 stoffel_vm_types::compiled_binary::MpcCurve::Bls12_381 => "bls12_381",
833 stoffel_vm_types::compiled_binary::MpcCurve::Bn254 => "bn254",
834 stoffel_vm_types::compiled_binary::MpcCurve::Curve25519 => "curve25519",
835 stoffel_vm_types::compiled_binary::MpcCurve::Ed25519 => "ed25519",
836 stoffel_vm_types::compiled_binary::MpcCurve::Secp256k1 => "secp256k1",
837 stoffel_vm_types::compiled_binary::MpcCurve::Secp256r1 => "p-256",
838 }
839}
840
841struct WrapperRegisters {
853 next_clear: usize,
854 next_secret: usize,
855 high_clear: usize,
856 high_secret: usize,
857 used_secret: bool,
858}
859
860impl WrapperRegisters {
861 const FIRST_CLEAR_REGISTER: usize = 2;
864
865 fn new() -> Self {
866 Self {
867 next_clear: Self::FIRST_CLEAR_REGISTER,
868 next_secret: DEFAULT_SECRET_REGISTER_START,
869 high_clear: Self::FIRST_CLEAR_REGISTER,
870 high_secret: DEFAULT_SECRET_REGISTER_START,
871 used_secret: false,
872 }
873 }
874
875 fn allocate_clear(&mut self) -> usize {
876 let register = self.next_clear;
877 self.next_clear += 1;
878 self.high_clear = self.high_clear.max(self.next_clear);
879 register
880 }
881
882 fn allocate_secret(&mut self) -> usize {
883 let register = self.next_secret;
884 self.next_secret += 1;
885 self.high_secret = self.high_secret.max(self.next_secret);
886 self.used_secret = true;
887 register
888 }
889
890 fn register_count(&self) -> usize {
891 if self.used_secret {
892 self.high_clear.max(self.high_secret)
893 } else {
894 self.high_clear
895 }
896 }
897}
898
899fn emit_local_input_shape(
900 shape: &LocalInputShape,
901 binary: &mut CompiledBinary,
902 instructions: &mut Vec<CompiledInstruction>,
903 next_share_index: &mut usize,
904 registers: &mut WrapperRegisters,
905) -> Result<usize> {
906 match shape {
907 LocalInputShape::Clear(value) => {
908 let dest = registers.allocate_clear();
909 let const_index = binary.constants.len();
910 binary
911 .constants
912 .push(clear_sdk_value_to_vm_constant(value)?);
913 instructions.push(CompiledInstruction::LDI(dest, const_index));
914 Ok(dest)
915 }
916 LocalInputShape::Share(share_type) => {
917 let dest = registers.allocate_secret();
918 let client_index_const = binary.constants.len();
919 binary.constants.push(VmValue::U64(0));
920 let share_index_const = binary.constants.len();
921 binary
922 .constants
923 .push(VmValue::U64(*next_share_index as u64));
924 *next_share_index += 1;
925 instructions.push(CompiledInstruction::LDI(0, client_index_const));
926 instructions.push(CompiledInstruction::LDI(1, share_index_const));
927 instructions.push(CompiledInstruction::PUSHARG(0));
928 instructions.push(CompiledInstruction::PUSHARG(1));
929 let take_method = match share_type {
934 ShareType::SecretInt { bit_length: 1 } => "ClientStore.take_share_bool",
935 ShareType::SecretFixedPoint { .. } => "ClientStore.take_share_fixed",
936 _ => "ClientStore.take_share",
937 };
938 instructions.push(CompiledInstruction::CALL(take_method.to_owned()));
939 instructions.push(CompiledInstruction::MOV(dest, 0));
940 Ok(dest)
941 }
942 LocalInputShape::List(items) => {
943 let dest = registers.allocate_clear();
944 instructions.push(CompiledInstruction::CALL("create_array".to_owned()));
945 instructions.push(CompiledInstruction::MOV(dest, 0));
946 for item in items {
947 let saved_clear = registers.next_clear;
953 let saved_secret = registers.next_secret;
954 let item_register = emit_local_input_shape(
955 item,
956 binary,
957 instructions,
958 next_share_index,
959 registers,
960 )?;
961 instructions.push(CompiledInstruction::PUSHARG(dest));
962 instructions.push(CompiledInstruction::PUSHARG(item_register));
963 instructions.push(CompiledInstruction::CALL("array_push".to_owned()));
964 registers.next_clear = saved_clear;
965 registers.next_secret = saved_secret;
966 }
967 Ok(dest)
968 }
969 LocalInputShape::Object(fields) => {
970 let dest = registers.allocate_clear();
971 instructions.push(CompiledInstruction::CALL("create_object".to_owned()));
972 instructions.push(CompiledInstruction::MOV(dest, 0));
973 for (field_name, field_shape) in fields {
974 let saved_clear = registers.next_clear;
977 let saved_secret = registers.next_secret;
978 let key_register = registers.allocate_clear();
979 let key_const = binary.constants.len();
980 binary.constants.push(VmValue::String(field_name.clone()));
981 instructions.push(CompiledInstruction::LDI(key_register, key_const));
982 let value_register = emit_local_input_shape(
983 field_shape,
984 binary,
985 instructions,
986 next_share_index,
987 registers,
988 )?;
989 instructions.push(CompiledInstruction::PUSHARG(dest));
990 instructions.push(CompiledInstruction::PUSHARG(key_register));
991 instructions.push(CompiledInstruction::PUSHARG(value_register));
992 instructions.push(CompiledInstruction::CALL("set_field".to_owned()));
993 registers.next_clear = saved_clear;
994 registers.next_secret = saved_secret;
995 }
996 Ok(dest)
997 }
998 }
999}
1000
1001fn clear_sdk_value_to_vm_constant(value: &Value) -> Result<VmValue> {
1002 match value {
1003 Value::I64(value) => Ok(VmValue::I64(*value)),
1004 Value::U64(value) => Ok(VmValue::U64(*value)),
1005 Value::Bool(value) => Ok(VmValue::Bool(*value)),
1006 Value::Float(value) => Ok(VmValue::Float((*value).into())),
1007 Value::String(value) => Ok(VmValue::String(value.clone())),
1008 Value::Unit => Ok(VmValue::Unit),
1009 Value::Bytes(_) => Err(Error::InvalidInput(
1010 "clear byte values cannot be embedded in local named-input wrappers".to_owned(),
1011 )),
1012 Value::List(_) | Value::Object(_) => Err(Error::InvalidInput(
1013 "internal error: structured clear value was not lowered recursively".to_owned(),
1014 )),
1015 }
1016}
1017
1018#[cfg(test)]
1019mod tests {
1020 use super::*;
1021 use crate::backend::MpcBackend;
1022
1023 #[test]
1024 fn local_client_input_wrapper_places_secret_inputs_in_secret_registers() -> Result<()> {
1025 let program = crate::compiler::compile_source(
1026 "def main(a: secret int64, b: secret int64) -> secret int64:\n return a + b",
1027 "test.stfl",
1028 MpcBackend::HoneyBadger,
1029 )?;
1030
1031 let wrapped = program.with_local_client_input_wrapper(
1032 "main",
1033 "__stoffel_sdk_local_entry",
1034 &[
1035 LocalInputShape::Share(ShareType::default_secret_int()),
1036 LocalInputShape::Share(ShareType::default_secret_int()),
1037 ],
1038 )?;
1039 let wrapper = wrapped
1040 .binary
1041 .functions
1042 .iter()
1043 .find(|function| function.name == "__stoffel_sdk_local_entry")
1044 .expect("wrapper function should be present");
1045
1046 assert!(wrapper.register_count >= DEFAULT_SECRET_REGISTER_START + 2);
1047 assert!(
1048 wrapper
1049 .instructions
1050 .contains(&CompiledInstruction::MOV(DEFAULT_SECRET_REGISTER_START, 0)),
1051 "first ClientStore share must be moved into a secret register"
1052 );
1053 assert!(
1054 wrapper.instructions.contains(&CompiledInstruction::MOV(
1055 DEFAULT_SECRET_REGISTER_START + 1,
1056 0
1057 )),
1058 "second ClientStore share must be moved into a secret register"
1059 );
1060
1061 let main_call = wrapper
1062 .instructions
1063 .iter()
1064 .position(|instruction| {
1065 matches!(instruction, CompiledInstruction::CALL(name) if name == "main")
1066 })
1067 .expect("wrapper should call main");
1068 assert_eq!(
1069 &wrapper.instructions[main_call - 2..main_call],
1070 &[
1071 CompiledInstruction::PUSHARG(DEFAULT_SECRET_REGISTER_START),
1072 CompiledInstruction::PUSHARG(DEFAULT_SECRET_REGISTER_START + 1),
1073 ]
1074 );
1075 assert!(
1076 !wrapper.instructions.iter().any(
1077 |instruction| matches!(instruction, CompiledInstruction::CALL(name) if name == "Share.open")
1078 ),
1079 "the local wrapper should return the VM value and leave share reveals to the runner"
1080 );
1081
1082 Ok(())
1083 }
1084
1085 #[test]
1086 fn local_client_input_wrapper_preserves_secret_bool_share_type() -> Result<()> {
1087 let program = crate::compiler::compile_source(
1088 "def main(a: list[secret bool]) -> bool:\n return a[0].reveal()",
1089 "test.stfl",
1090 MpcBackend::HoneyBadger,
1091 )?;
1092
1093 let wrapped = program.with_local_client_input_wrapper(
1094 "main",
1095 "__stoffel_sdk_local_entry",
1096 &[LocalInputShape::List(vec![
1097 LocalInputShape::Share(ShareType::SecretInt { bit_length: 1 }),
1098 LocalInputShape::Share(ShareType::SecretInt { bit_length: 1 }),
1099 ])],
1100 )?;
1101
1102 assert_eq!(
1103 wrapped.binary.client_io_manifest.clients[0].inputs,
1104 vec![
1105 ShareType::SecretInt { bit_length: 1 },
1106 ShareType::SecretInt { bit_length: 1 },
1107 ]
1108 );
1109 Ok(())
1110 }
1111
1112 #[test]
1113 fn local_client_input_wrapper_keeps_clear_only_inputs_in_clear_registers() -> Result<()> {
1114 let program = crate::compiler::compile_source(
1115 "def main(a: int64, b: int64) -> int64:\n return a + b",
1116 "test.stfl",
1117 MpcBackend::HoneyBadger,
1118 )?;
1119
1120 let wrapped = program.with_local_client_input_wrapper(
1121 "main",
1122 "__stoffel_sdk_local_entry",
1123 &[
1124 LocalInputShape::Clear(Value::I64(1)),
1125 LocalInputShape::Clear(Value::I64(2)),
1126 ],
1127 )?;
1128 let wrapper = wrapped
1129 .binary
1130 .functions
1131 .iter()
1132 .find(|function| function.name == "__stoffel_sdk_local_entry")
1133 .expect("wrapper function should be present");
1134
1135 assert!(wrapper.register_count < DEFAULT_SECRET_REGISTER_START);
1136 assert!(
1137 wrapped.binary.client_io_manifest.clients.is_empty(),
1138 "clear-only named input wrappers must not declare client input metadata"
1139 );
1140
1141 Ok(())
1142 }
1143}