arcis_interface/types.rs
1/// Represents the kind of scalar value (signed vs unsigned integer).
2///
3/// This enum distinguishes between signed and unsigned integer types in circuit interfaces,
4/// allowing the system to properly serialize/deserialize values and generate correct Rust types.
5///
6/// # Examples
7///
8/// ```
9/// use arcis_interface::{Value, ScalarKind};
10///
11/// // Unsigned 32-bit integer (u32)
12/// let unsigned = Value::Scalar {
13/// size_in_bits: 32,
14/// kind: ScalarKind::Unsigned
15/// };
16///
17/// // Signed 32-bit integer (i32)
18/// let signed = Value::Scalar {
19/// size_in_bits: 32,
20/// kind: ScalarKind::Signed
21/// };
22/// ```
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24pub enum ScalarKind {
25 /// Unsigned integer type (u8, u16, u32, u64, u128).
26 ///
27 /// This is the default for backward compatibility with existing circuits
28 /// that were created before signed integer support was added.
29 #[default]
30 Unsigned,
31
32 /// Signed integer type (i8, i16, i32, i64, i128).
33 ///
34 /// Signed integers use two's complement representation in the MPC protocol.
35 Signed,
36}
37
38/// Circuit interface value types.
39///
40/// Represents all possible types that can be used as inputs or outputs in Arcium circuits.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum Value {
43 MScalar {
44 size_in_bits: usize,
45 },
46 MFloat {
47 size_in_bits: usize,
48 },
49 MBool,
50 /// Plaintext scalar value (integer).
51 ///
52 /// # Fields
53 /// * `size_in_bits` - Bit width of the scalar (8, 16, 32, 64, or 128)
54 /// * `kind` - Whether the scalar is signed or unsigned
55 ///
56 /// # Examples
57 /// * `Scalar { size_in_bits: 32, kind: ScalarKind::Unsigned }` represents u32
58 /// * `Scalar { size_in_bits: 64, kind: ScalarKind::Signed }` represents i64
59 Scalar {
60 size_in_bits: usize,
61 kind: ScalarKind,
62 },
63 Float {
64 size_in_bits: usize,
65 },
66 Bool,
67 Ciphertext {
68 size_in_bits: usize,
69 },
70 ArcisX25519Pubkey,
71 Point,
72 /// Bits supplied per node rather than read from the agreed public inputs -- fetched from a
73 /// URL, say. Nodes may hold different values for these until a `ConstrainPlaintextBits` gate
74 /// reconciles them, which is why they are a distinct type rather than `[Bool; N]`: keeping it
75 /// in the interface is what stops unsynchronized data being handed to a circuit that assumed
76 /// every node reads the same bytes.
77 ///
78 /// `count` is the number of bits, and maps to one
79 /// `Parameter::UnsynchronizedPlaintextBools(count)` on chain.
80 UnsynchronizedBools {
81 count: usize,
82 },
83 /// A byte of the AES ciphertext of an `Aes128Enc`, `Aes192Enc` or `Aes256Enc`, whatever the
84 /// key size. It maps to a `Parameter::AesEncryptedByte` on chain.
85 AesCiphertextByte,
86 Array(Vec<Value>),
87 Tuple(Vec<Value>),
88 Struct(Vec<Value>),
89}
90
91impl Value {
92 /// Returns the number of scalar field elements this value occupies.
93 ///
94 /// For primitive types (Scalar, Bool, Float, etc.), this returns 1.
95 /// For composite types (Array, Tuple, Struct), this recursively counts all scalar fields.
96 ///
97 /// # Examples
98 ///
99 /// ```
100 /// use arcis_interface::{Value, ScalarKind};
101 ///
102 /// let scalar = Value::Scalar { size_in_bits: 32, kind: ScalarKind::Unsigned };
103 /// assert_eq!(scalar.size_in_scalars(), 1);
104 ///
105 /// let tuple = Value::Tuple(vec![
106 /// Value::Scalar { size_in_bits: 32, kind: ScalarKind::Unsigned },
107 /// Value::Bool,
108 /// ]);
109 /// assert_eq!(tuple.size_in_scalars(), 2);
110 /// ```
111 pub fn size_in_scalars(&self) -> usize {
112 match self {
113 Self::MScalar { .. } => 1,
114 Self::MFloat { .. } => 1,
115 Self::Scalar { .. } => 1,
116 Self::Float { .. } => 1,
117 Self::MBool { .. } => 1,
118 Self::Bool { .. } => 1,
119 Self::Ciphertext { .. } => 1,
120 Self::ArcisX25519Pubkey => 1,
121 Self::Point => 1,
122 // One value, however many bits it carries: it maps to a single
123 // `Parameter::UnsynchronizedPlaintextBools(count)` and a single `ArgumentRef::Url`.
124 Self::UnsynchronizedBools { .. } => 1,
125 Self::AesCiphertextByte => 1,
126 Self::Array(c) | Self::Tuple(c) | Self::Struct(c) => {
127 c.iter().fold(0, |acc, x| acc + x.size_in_scalars())
128 }
129 }
130 }
131
132 /// Flattens composite types into a vector of primitive values.
133 ///
134 /// For primitive types, returns a single-element vector containing self.
135 /// For composite types (Array, Tuple, Struct), recursively flattens all nested values
136 /// into a flat vector of primitives.
137 ///
138 /// # Examples
139 ///
140 /// ```
141 /// use arcis_interface::{Value, ScalarKind};
142 ///
143 /// let scalar = Value::Scalar { size_in_bits: 32, kind: ScalarKind::Unsigned };
144 /// assert_eq!(scalar.flatten().len(), 1);
145 ///
146 /// let tuple = Value::Tuple(vec![
147 /// Value::Scalar { size_in_bits: 32, kind: ScalarKind::Unsigned },
148 /// Value::Tuple(vec![Value::Bool, Value::Bool]),
149 /// ]);
150 /// assert_eq!(tuple.flatten().len(), 3); // u32, bool, bool
151 /// ```
152 pub fn flatten(&self) -> Vec<Value> {
153 match self {
154 Self::Array(c) | Self::Tuple(c) | Self::Struct(c) => {
155 let mut v = Vec::new();
156 for a in c {
157 v.extend(a.flatten());
158 }
159 v
160 }
161 _ => vec![self.clone()],
162 }
163 }
164}
165
166#[derive(Debug, serde::Serialize)]
167pub struct CircuitInterface {
168 pub name: String,
169 pub inputs: Vec<Value>,
170 pub outputs: Vec<Value>,
171}
172
173impl CircuitInterface {
174 pub fn new(name: String, inputs: Vec<Value>, outputs: Vec<Value>) -> Self {
175 Self {
176 name,
177 inputs,
178 outputs,
179 }
180 }
181
182 pub fn serialize(&self) -> Result<String, serde_json::Error> {
183 serde_json::to_string(self)
184 }
185
186 pub fn from_json(input: &str) -> Result<Self, serde_json::Error> {
187 serde_json::from_str(input)
188 }
189}