pub mod black_box_functions;
pub mod brillig;
pub mod opcodes;
use crate::{
native_types::{Expression, Witness},
serialization::{deserialize_any_format, serialize_with_format_from_env},
};
use acir_field::AcirField;
pub use opcodes::Opcode;
use thiserror::Error;
use std::{io::prelude::*, num::ParseIntError, str::FromStr};
use base64::Engine;
use flate2::Compression;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeserializationError};
use std::collections::BTreeSet;
use self::{brillig::BrilligBytecode, opcodes::BlockId};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, Hash)]
#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
pub enum ExpressionWidth {
#[default]
Unbounded,
Bounded {
width: usize,
},
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Hash)]
#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
pub struct Program<F: AcirField> {
pub functions: Vec<Circuit<F>>,
pub unconstrained_functions: Vec<BrilligBytecode<F>>,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Hash)]
#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
pub struct Circuit<F: AcirField> {
pub current_witness_index: u32,
pub opcodes: Vec<Opcode<F>>,
pub expression_width: ExpressionWidth,
pub private_parameters: BTreeSet<Witness>,
pub public_parameters: PublicInputs,
pub return_values: PublicInputs,
pub assert_messages: Vec<(OpcodeLocation, AssertionPayload<F>)>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
pub enum ExpressionOrMemory<F> {
Expression(Expression<F>),
Memory(BlockId),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
pub struct AssertionPayload<F> {
pub error_selector: u64,
pub payload: Vec<ExpressionOrMemory<F>>,
}
#[derive(Debug, Copy, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
pub struct ErrorSelector(u64);
impl ErrorSelector {
pub fn new(integer: u64) -> Self {
ErrorSelector(integer)
}
pub fn as_u64(&self) -> u64 {
self.0
}
}
impl Serialize for ErrorSelector {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.to_string().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ErrorSelector {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
let as_u64 = s.parse().map_err(serde::de::Error::custom)?;
Ok(ErrorSelector(as_u64))
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
pub enum OpcodeLocation {
Acir(usize),
Brillig { acir_index: usize, brillig_index: usize },
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct AcirOpcodeLocation(usize);
impl std::fmt::Display for AcirOpcodeLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl AcirOpcodeLocation {
pub fn new(index: usize) -> Self {
AcirOpcodeLocation(index)
}
pub fn index(&self) -> usize {
self.0
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct BrilligOpcodeLocation(pub usize);
impl OpcodeLocation {
pub fn to_brillig_location(self) -> Option<BrilligOpcodeLocation> {
match self {
OpcodeLocation::Brillig { brillig_index, .. } => {
Some(BrilligOpcodeLocation(brillig_index))
}
_ => None,
}
}
}
impl std::fmt::Display for OpcodeLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OpcodeLocation::Acir(index) => write!(f, "{index}"),
OpcodeLocation::Brillig { acir_index, brillig_index } => {
write!(f, "{acir_index}.{brillig_index}")
}
}
}
}
#[derive(Error, Debug)]
pub enum OpcodeLocationFromStrError {
#[error("Invalid opcode location string: {0}")]
InvalidOpcodeLocationString(String),
}
impl FromStr for OpcodeLocation {
type Err = OpcodeLocationFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<_> = s.split('.').collect();
if parts.is_empty() || parts.len() > 2 {
return Err(OpcodeLocationFromStrError::InvalidOpcodeLocationString(s.to_string()));
}
fn parse_components(parts: Vec<&str>) -> Result<OpcodeLocation, ParseIntError> {
match parts.len() {
1 => {
let index = parts[0].parse()?;
Ok(OpcodeLocation::Acir(index))
}
2 => {
let acir_index = parts[0].parse()?;
let brillig_index = parts[1].parse()?;
Ok(OpcodeLocation::Brillig { acir_index, brillig_index })
}
_ => unreachable!("`OpcodeLocation` has too many components"),
}
}
parse_components(parts)
.map_err(|_| OpcodeLocationFromStrError::InvalidOpcodeLocationString(s.to_string()))
}
}
impl std::fmt::Display for BrilligOpcodeLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let index = self.0;
write!(f, "{index}")
}
}
impl<F: AcirField> Circuit<F> {
pub fn num_vars(&self) -> u32 {
self.current_witness_index + 1
}
pub fn circuit_arguments(&self) -> BTreeSet<Witness> {
self.private_parameters.union(&self.public_parameters.0).cloned().collect()
}
pub fn public_inputs(&self) -> PublicInputs {
let public_inputs =
self.public_parameters.0.union(&self.return_values.0).cloned().collect();
PublicInputs(public_inputs)
}
}
impl<F: Serialize + AcirField> Program<F> {
fn write<W: Write>(&self, writer: W) -> std::io::Result<()> {
let buf = serialize_with_format_from_env(self)?;
let mut encoder = flate2::write::GzEncoder::new(writer, Compression::default());
encoder.write_all(&buf)?;
encoder.finish()?;
Ok(())
}
pub fn serialize_program(program: &Self) -> Vec<u8> {
let mut program_bytes: Vec<u8> = Vec::new();
program.write(&mut program_bytes).expect("expected circuit to be serializable");
program_bytes
}
pub fn serialize_program_base64<S>(program: &Self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let program_bytes = Program::serialize_program(program);
let encoded_b64 = base64::engine::general_purpose::STANDARD.encode(program_bytes);
s.serialize_str(&encoded_b64)
}
}
impl<F: AcirField + for<'a> Deserialize<'a>> Program<F> {
fn read<R: Read>(reader: R) -> std::io::Result<Self> {
let mut gz_decoder = flate2::read::GzDecoder::new(reader);
let mut buf = Vec::new();
gz_decoder.read_to_end(&mut buf)?;
let program = deserialize_any_format(&buf)?;
Ok(program)
}
pub fn deserialize_program(serialized_circuit: &[u8]) -> std::io::Result<Self> {
Program::read(serialized_circuit)
}
pub fn deserialize_program_base64<'de, D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let bytecode_b64: String = Deserialize::deserialize(deserializer)?;
let program_bytes = base64::engine::general_purpose::STANDARD
.decode(bytecode_b64)
.map_err(D::Error::custom)?;
let circuit = Self::deserialize_program(&program_bytes).map_err(D::Error::custom)?;
Ok(circuit)
}
}
impl<F: AcirField> std::fmt::Display for Circuit<F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "current witness index : _{}", self.current_witness_index)?;
let write_witness_indices =
|f: &mut std::fmt::Formatter<'_>, indices: &[u32]| -> Result<(), std::fmt::Error> {
write!(f, "[")?;
for (index, witness_index) in indices.iter().enumerate() {
write!(f, "_{witness_index}")?;
if index != indices.len() - 1 {
write!(f, ", ")?;
}
}
writeln!(f, "]")
};
write!(f, "private parameters indices : ")?;
write_witness_indices(
f,
&self
.private_parameters
.iter()
.map(|witness| witness.witness_index())
.collect::<Vec<_>>(),
)?;
write!(f, "public parameters indices : ")?;
write_witness_indices(f, &self.public_parameters.indices())?;
write!(f, "return value indices : ")?;
write_witness_indices(f, &self.return_values.indices())?;
for opcode in &self.opcodes {
writeln!(f, "{opcode}")?;
}
Ok(())
}
}
impl<F: AcirField> std::fmt::Debug for Circuit<F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
impl<F: AcirField> std::fmt::Display for Program<F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (func_index, function) in self.functions.iter().enumerate() {
writeln!(f, "func {func_index}")?;
writeln!(f, "{function}")?;
}
for (func_index, function) in self.unconstrained_functions.iter().enumerate() {
writeln!(f, "unconstrained func {func_index}")?;
writeln!(f, "{:?}", function.bytecode)?;
}
Ok(())
}
}
impl<F: AcirField> std::fmt::Debug for Program<F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, Hash)]
#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
pub struct PublicInputs(pub BTreeSet<Witness>);
impl PublicInputs {
pub fn indices(&self) -> Vec<u32> {
self.0.iter().map(|witness| witness.witness_index()).collect()
}
pub fn contains(&self, index: usize) -> bool {
self.0.contains(&Witness(index as u32))
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use super::{
Circuit, Compression, Opcode, PublicInputs,
opcodes::{BlackBoxFuncCall, FunctionInput},
};
use crate::{
circuit::{ExpressionWidth, Program},
native_types::Witness,
};
use acir_field::{AcirField, FieldElement};
use serde::{Deserialize, Serialize};
fn and_opcode<F: AcirField>() -> Opcode<F> {
Opcode::BlackBoxFuncCall(BlackBoxFuncCall::AND {
lhs: FunctionInput::witness(Witness(1), 4),
rhs: FunctionInput::witness(Witness(2), 4),
output: Witness(3),
})
}
fn range_opcode<F: AcirField>() -> Opcode<F> {
Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE {
input: FunctionInput::witness(Witness(1), 8),
})
}
fn keccakf1600_opcode<F: AcirField>() -> Opcode<F> {
let inputs: Box<[FunctionInput<F>; 25]> =
Box::new(std::array::from_fn(|i| FunctionInput::witness(Witness(i as u32 + 1), 8)));
let outputs: Box<[Witness; 25]> = Box::new(std::array::from_fn(|i| Witness(i as u32 + 26)));
Opcode::BlackBoxFuncCall(BlackBoxFuncCall::Keccakf1600 { inputs, outputs })
}
#[test]
fn serialization_roundtrip() {
let circuit = Circuit {
current_witness_index: 5,
expression_width: ExpressionWidth::Unbounded,
opcodes: vec![and_opcode::<FieldElement>(), range_opcode()],
private_parameters: BTreeSet::new(),
public_parameters: PublicInputs(BTreeSet::from_iter(vec![Witness(2), Witness(12)])),
return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(4), Witness(12)])),
assert_messages: Default::default(),
};
let program = Program { functions: vec![circuit], unconstrained_functions: Vec::new() };
fn read_write<F: Serialize + for<'a> Deserialize<'a> + AcirField>(
program: Program<F>,
) -> (Program<F>, Program<F>) {
let bytes = Program::serialize_program(&program);
let got_program = Program::deserialize_program(&bytes).unwrap();
(program, got_program)
}
let (circ, got_circ) = read_write(program);
assert_eq!(circ, got_circ);
}
#[test]
fn test_serialize() {
let circuit = Circuit {
current_witness_index: 0,
expression_width: ExpressionWidth::Unbounded,
opcodes: vec![
Opcode::AssertZero(crate::native_types::Expression {
mul_terms: vec![],
linear_combinations: vec![],
q_c: FieldElement::from(8u128),
}),
range_opcode(),
and_opcode(),
keccakf1600_opcode(),
],
private_parameters: BTreeSet::new(),
public_parameters: PublicInputs(BTreeSet::from_iter(vec![Witness(2)])),
return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(2)])),
assert_messages: Default::default(),
};
let program = Program { functions: vec![circuit], unconstrained_functions: Vec::new() };
let json = serde_json::to_string_pretty(&program).unwrap();
let deserialized = serde_json::from_str(&json).unwrap();
assert_eq!(program, deserialized);
}
#[test]
fn does_not_panic_on_invalid_circuit() {
use std::io::Write;
let bad_circuit = "I'm not an ACIR circuit".as_bytes();
let mut zipped_bad_circuit = Vec::new();
let mut encoder =
flate2::write::GzEncoder::new(&mut zipped_bad_circuit, Compression::default());
encoder.write_all(bad_circuit).unwrap();
encoder.finish().unwrap();
let deserialization_result: Result<Program<FieldElement>, _> =
Program::deserialize_program(&zipped_bad_circuit);
assert!(deserialization_result.is_err());
}
#[test]
fn circuit_display_snapshot() {
let circuit = Circuit {
current_witness_index: 3,
expression_width: ExpressionWidth::Unbounded,
opcodes: vec![
Opcode::AssertZero(crate::native_types::Expression {
mul_terms: vec![],
linear_combinations: vec![(FieldElement::from(2u128), Witness(1))],
q_c: FieldElement::from(8u128),
}),
range_opcode(),
and_opcode(),
keccakf1600_opcode(),
],
private_parameters: BTreeSet::new(),
public_parameters: PublicInputs(BTreeSet::from_iter(vec![Witness(2)])),
return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(2)])),
assert_messages: Default::default(),
};
insta::assert_snapshot!(
circuit.to_string(),
@r"
current witness index : _3
private parameters indices : []
public parameters indices : [_2]
return value indices : [_2]
EXPR [ (2, _1) 8 ]
BLACKBOX::RANGE [(_1, 8)] []
BLACKBOX::AND [(_1, 4), (_2, 4)] [_3]
BLACKBOX::KECCAKF1600 [(_1, 8), (_2, 8), (_3, 8), (_4, 8), (_5, 8), (_6, 8), (_7, 8), (_8, 8), (_9, 8), (_10, 8), (_11, 8), (_12, 8), (_13, 8), (_14, 8), (_15, 8), (_16, 8), (_17, 8), (_18, 8), (_19, 8), (_20, 8), (_21, 8), (_22, 8), (_23, 8), (_24, 8), (_25, 8)] [_26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50]
"
);
}
mod props {
use acir_field::FieldElement;
use proptest::prelude::*;
use proptest::test_runner::{TestCaseResult, TestRunner};
use crate::circuit::Program;
use crate::native_types::{WitnessMap, WitnessStack};
use crate::serialization::*;
const MAX_SIZE_RANGE: usize = 5;
const SIZE_RANGE_KEY: &str = "PROPTEST_MAX_DEFAULT_SIZE_RANGE";
acir_field::field_wrapper!(TestField, FieldElement);
impl Arbitrary for TestField {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
any::<u128>().prop_map(|v| Self(FieldElement::from(v))).boxed()
}
}
#[allow(unsafe_code)]
fn run_with_max_size_range<T, F>(cases: u32, f: F)
where
T: Arbitrary,
F: Fn(T) -> TestCaseResult,
{
let orig_size_range = std::env::var(SIZE_RANGE_KEY).ok();
if orig_size_range.is_none() {
unsafe {
std::env::set_var(SIZE_RANGE_KEY, MAX_SIZE_RANGE.to_string());
}
}
let mut runner = TestRunner::new(ProptestConfig { cases, ..Default::default() });
let result = runner.run(&any::<T>(), f);
unsafe {
std::env::set_var(SIZE_RANGE_KEY, orig_size_range.unwrap_or_default());
}
result.unwrap();
}
#[test]
fn prop_program_proto_roundtrip() {
run_with_max_size_range(100, |program: Program<TestField>| {
let bz = proto_serialize(&program);
let de = proto_deserialize(&bz)?;
prop_assert_eq!(program, de);
Ok(())
});
}
#[test]
fn prop_program_bincode_roundtrip() {
run_with_max_size_range(100, |program: Program<TestField>| {
let bz = bincode_serialize(&program)?;
let de = bincode_deserialize(&bz)?;
prop_assert_eq!(program, de);
Ok(())
});
}
#[test]
fn prop_program_msgpack_roundtrip() {
run_with_max_size_range(100, |(program, compact): (Program<TestField>, bool)| {
let bz = msgpack_serialize(&program, compact)?;
let de = msgpack_deserialize(&bz)?;
prop_assert_eq!(program, de);
Ok(())
});
}
#[test]
fn prop_program_roundtrip() {
run_with_max_size_range(10, |program: Program<TestField>| {
let bz = Program::serialize_program(&program);
let de = Program::deserialize_program(&bz)?;
prop_assert_eq!(program, de);
Ok(())
});
}
#[test]
fn prop_witness_stack_proto_roundtrip() {
run_with_max_size_range(10, |witness: WitnessStack<TestField>| {
let bz = proto_serialize(&witness);
let de = proto_deserialize(&bz)?;
prop_assert_eq!(witness, de);
Ok(())
});
}
#[test]
fn prop_witness_stack_bincode_roundtrip() {
run_with_max_size_range(10, |witness: WitnessStack<TestField>| {
let bz = bincode_serialize(&witness)?;
let de = bincode_deserialize(&bz)?;
prop_assert_eq!(witness, de);
Ok(())
});
}
#[test]
fn prop_witness_stack_msgpack_roundtrip() {
run_with_max_size_range(10, |(witness, compact): (WitnessStack<TestField>, bool)| {
let bz = msgpack_serialize(&witness, compact)?;
let de = msgpack_deserialize(&bz)?;
prop_assert_eq!(witness, de);
Ok(())
});
}
#[test]
fn prop_witness_stack_roundtrip() {
run_with_max_size_range(10, |witness: WitnessStack<TestField>| {
let bz = witness.serialize()?;
let de = WitnessStack::deserialize(bz.as_slice())?;
prop_assert_eq!(witness, de);
Ok(())
});
}
#[test]
fn prop_witness_map_proto_roundtrip() {
run_with_max_size_range(10, |witness: WitnessMap<TestField>| {
let bz = proto_serialize(&witness);
let de = proto_deserialize(&bz)?;
prop_assert_eq!(witness, de);
Ok(())
});
}
#[test]
fn prop_witness_map_bincode_roundtrip() {
run_with_max_size_range(10, |witness: WitnessMap<TestField>| {
let bz = bincode_serialize(&witness)?;
let de = bincode_deserialize(&bz)?;
prop_assert_eq!(witness, de);
Ok(())
});
}
#[test]
fn prop_witness_map_msgpack_roundtrip() {
run_with_max_size_range(10, |(witness, compact): (WitnessMap<TestField>, bool)| {
let bz = msgpack_serialize(&witness, compact)?;
let de = msgpack_deserialize(&bz)?;
prop_assert_eq!(witness, de);
Ok(())
});
}
#[test]
fn prop_witness_map_roundtrip() {
run_with_max_size_range(10, |witness: WitnessMap<TestField>| {
let bz = witness.serialize()?;
let de = WitnessMap::deserialize(bz.as_slice())?;
prop_assert_eq!(witness, de);
Ok(())
});
}
}
}