use primitives::algebra::elliptic_curve::{Point, Scalar};
use serde::{Deserialize, Serialize};
use super::{errors::CircuitError, Circuit, Gate, GateIndex};
use crate::config::{name_tag, tag_name, MpcConfig};
const CIRCUIT_FORMAT_VERSION: u8 = 3;
#[derive(Serialize, Deserialize, Default)]
#[serde(bound(
serialize = "Scalar<C::Curve>: Serialize, Point<C::Curve>: Serialize",
deserialize = "Scalar<C::Curve>: Deserialize<'de>, Point<C::Curve>: Deserialize<'de>"
))]
#[repr(C)]
struct CompressedCircuit<C: MpcConfig> {
pub format_version: u8,
pub curve_tag: [u8; 32],
pub mpc_field_tag: [u8; 32],
pub ops: Vec<Gate<C>>,
pub output_gates: Vec<GateIndex>,
}
impl<C: MpcConfig> From<&Circuit<C>> for CompressedCircuit<C> {
fn from(value: &Circuit<C>) -> Self {
CompressedCircuit {
format_version: CIRCUIT_FORMAT_VERSION,
curve_tag: name_tag::<C::Curve>(),
mpc_field_tag: name_tag::<C::Field>(),
ops: value.iter_gates().cloned().collect(),
output_gates: value.iter_output_indices().copied().collect(),
}
}
}
impl<C: MpcConfig> TryFrom<CompressedCircuit<C>> for Circuit<C> {
type Error = CircuitError<C>;
fn try_from(circuit: CompressedCircuit<C>) -> Result<Self, Self::Error> {
if circuit.format_version != CIRCUIT_FORMAT_VERSION {
return Err(CircuitError::UnsupportedFormatVersion {
expected: CIRCUIT_FORMAT_VERSION,
found: circuit.format_version,
});
}
let expected_curve = name_tag::<C::Curve>();
if circuit.curve_tag != expected_curve {
return Err(CircuitError::CurveMismatch {
expected: tag_name(&expected_curve),
found: tag_name(&circuit.curve_tag),
});
}
let expected_field = name_tag::<C::Field>();
if circuit.mpc_field_tag != expected_field {
return Err(CircuitError::MpcFieldMismatch {
expected: tag_name(&expected_field),
found: tag_name(&circuit.mpc_field_tag),
});
}
let mut res = Self {
gates: Vec::with_capacity(circuit.ops.len()),
inputs: Vec::new(),
outputs: Vec::with_capacity(circuit.output_gates.len()),
};
for gate in circuit.ops.into_iter() {
res.add_gate(gate)?;
}
for index in circuit.output_gates.into_iter() {
res.add_output(index)?;
}
Ok(res)
}
}
mod bincode {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::{Circuit, CompressedCircuit};
use crate::config::MpcConfig;
impl<C: MpcConfig> Serialize for Circuit<C> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let circuit_serde: CompressedCircuit<C> = self.into();
circuit_serde.serialize(serializer)
}
}
impl<'de, C: MpcConfig> Deserialize<'de> for Circuit<C> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let circuit_serde = CompressedCircuit::<C>::deserialize(deserializer)
.map_err(serde::de::Error::custom)?;
let circuit = circuit_serde.try_into();
circuit.map_err(serde::de::Error::custom)
}
}
}
#[cfg(test)]
mod tests {
use primitives::utils::codec::bincode_io;
use super::*;
use crate::{
circuit::latest::{
tests::create_add_tree_circuit,
AlgebraicType,
FieldShareBinaryOp,
Input,
},
config::DefaultConfig as C,
};
#[test]
fn valid_circuit() {
let mut circuit = Circuit::<C>::new();
let input_gate1 = circuit
.add_gate(Gate::Input(Input::SecretPlaintext {
inputer: 0,
algebraic_type: AlgebraicType::ScalarField,
batch_size: 1,
}))
.unwrap();
assert_eq!(input_gate1, 0);
let input_gate2 = circuit
.add_gate(Gate::Input(Input::SecretPlaintext {
inputer: 1,
algebraic_type: AlgebraicType::ScalarField,
batch_size: 1,
}))
.unwrap();
assert_eq!(input_gate2, 1);
let add_gate = circuit
.add_gate(Gate::FieldShareBinaryOp {
x: input_gate1,
y: input_gate2,
op: FieldShareBinaryOp::Add,
})
.unwrap();
assert_eq!(add_gate, 2);
circuit.add_output(add_gate).unwrap();
assert_eq!(
circuit.iter_output_indices().copied().collect::<Vec<_>>(),
vec![2]
);
}
#[test]
fn test_ser_circuit_bincode() {
let circuit = create_add_tree_circuit(18);
let serialized = bincode_io::serialize(&circuit).unwrap();
let circuit_de: Circuit<C> = bincode_io::deserialize(&serialized).unwrap();
assert_eq!(circuit, circuit_de);
}
#[test]
fn test_cross_field_rejection() {
use crate::config::Gf2_128Config;
let circuit = create_add_tree_circuit::<C>(2);
let bin = bincode_io::serialize(&circuit).unwrap();
let err = bincode_io::deserialize::<Circuit<Gf2_128Config>>(&bin).unwrap_err();
assert!(
err.to_string().contains("MPC field"),
"unexpected error: {err}"
);
assert_eq!(
bincode_io::deserialize::<Circuit<C>>(&bin).unwrap(),
circuit
);
}
#[test]
fn test_version_and_curve_rejection() {
let circuit = create_add_tree_circuit::<C>(2);
let bin = bincode_io::serialize(&circuit).unwrap();
let mut wrong_version = bin.clone();
wrong_version[0] ^= 0xFF;
let err = bincode_io::deserialize::<Circuit<C>>(&wrong_version).unwrap_err();
assert!(
err.to_string().contains("format version"),
"unexpected error: {err}"
);
let mut wrong_curve = bin;
wrong_curve[1] ^= 0xFF;
let err = bincode_io::deserialize::<Circuit<C>>(&wrong_curve).unwrap_err();
assert!(err.to_string().contains("curve"), "unexpected error: {err}");
}
#[test]
fn test_circuit_id_commits_to_field() {
use crate::{circuit::circuit_id::CircuitId, config::Gf2_128Config};
let id_default = CircuitId::of(&create_add_tree_circuit::<C>(2));
let id_gf2_128 = CircuitId::of(&create_add_tree_circuit::<Gf2_128Config>(2));
assert_ne!(id_default.as_bytes(), id_gf2_128.as_bytes());
}
}