use serde::{Deserialize, Serialize};
use wincode::{SchemaRead, SchemaWrite};
use crate::{circuit::v2::Circuit, config::MpcConfig};
#[derive(
Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, SchemaRead, SchemaWrite,
)]
#[repr(C)]
pub struct CircuitId([u8; 32]);
impl CircuitId {
pub fn of<C: MpcConfig>(circuit: &Circuit<C>) -> Self {
let bytes =
bincode::serialize(circuit).expect("circuit bincode serialization is infallible");
Self(*blake3::hash(&bytes).as_bytes())
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
circuit::v2::{AlgebraicType, FieldShareBinaryOp, Gate, Input},
config::DefaultConfig as C,
};
fn input<C: MpcConfig>() -> Gate<C> {
Gate::Input(Input::SecretPlaintext {
inputer: 0,
algebraic_type: AlgebraicType::ScalarField,
batch_size: 1,
})
}
fn sample_circuit() -> Circuit<C> {
let mut circuit = Circuit::new();
let i0 = circuit.add_gate(input()).unwrap();
let i1 = circuit.add_gate(input()).unwrap();
let i2 = circuit.add_gate(input()).unwrap();
let i3 = circuit.add_gate(input()).unwrap();
let l = circuit
.add_gate(Gate::FieldShareBinaryOp {
x: i0,
y: i1,
op: FieldShareBinaryOp::Add,
})
.unwrap();
let r = circuit
.add_gate(Gate::FieldShareBinaryOp {
x: i2,
y: i3,
op: FieldShareBinaryOp::Add,
})
.unwrap();
let top = circuit
.add_gate(Gate::FieldShareBinaryOp {
x: l,
y: r,
op: FieldShareBinaryOp::Add,
})
.unwrap();
circuit.add_output(top).unwrap();
circuit
}
#[test]
fn deterministic() {
assert_eq!(
CircuitId::of(&sample_circuit()),
CircuitId::of(&sample_circuit())
);
}
#[test]
fn stable_across_serialization_roundtrip() {
let circuit = sample_circuit();
let bytes = bincode::serialize(&circuit).unwrap();
let restored: Circuit<C> = bincode::deserialize(&bytes).unwrap();
assert_eq!(CircuitId::of(&circuit), CircuitId::of(&restored));
}
#[test]
fn order_sensitive() {
let mut a = Circuit::<C>::new();
let a0 = a.add_gate(input()).unwrap();
let a1 = a
.add_gate(Gate::Input(Input::SecretPlaintext {
inputer: 1,
algebraic_type: AlgebraicType::ScalarField,
batch_size: 1,
}))
.unwrap();
let a2 = a
.add_gate(Gate::FieldShareBinaryOp {
x: a0,
y: a1,
op: FieldShareBinaryOp::Add,
})
.unwrap();
a.add_output(a2).unwrap();
let mut b = Circuit::<C>::new();
let b0 = b
.add_gate(Gate::Input(Input::SecretPlaintext {
inputer: 1,
algebraic_type: AlgebraicType::ScalarField,
batch_size: 1,
}))
.unwrap();
let b1 = b.add_gate(input()).unwrap();
let b2 = b
.add_gate(Gate::FieldShareBinaryOp {
x: b0,
y: b1,
op: FieldShareBinaryOp::Add,
})
.unwrap();
b.add_output(b2).unwrap();
assert_ne!(CircuitId::of(&a), CircuitId::of(&b));
}
#[test]
fn gate_sensitive() {
let mut add = Circuit::<C>::new();
let x = add.add_gate(input()).unwrap();
let y = add.add_gate(input()).unwrap();
let z = add
.add_gate(Gate::FieldShareBinaryOp {
x,
y,
op: FieldShareBinaryOp::Add,
})
.unwrap();
add.add_output(z).unwrap();
let mut mul = Circuit::<C>::new();
let x = mul.add_gate(input()).unwrap();
let y = mul.add_gate(input()).unwrap();
let z = mul
.add_gate(Gate::FieldShareBinaryOp {
x,
y,
op: FieldShareBinaryOp::Mul,
})
.unwrap();
mul.add_output(z).unwrap();
assert_ne!(CircuitId::of(&add), CircuitId::of(&mul));
}
#[test]
fn output_sensitive() {
let mut base = Circuit::<C>::new();
let x = base.add_gate(input()).unwrap();
let y = base.add_gate(input()).unwrap();
let z = base
.add_gate(Gate::FieldShareBinaryOp {
x,
y,
op: FieldShareBinaryOp::Add,
})
.unwrap();
let mut out_z = base.clone();
out_z.add_output(z).unwrap();
let mut out_x = base.clone();
out_x.add_output(x).unwrap();
assert_ne!(CircuitId::of(&out_z), CircuitId::of(&out_x));
}
}