pub mod black_box_functions;
pub mod brillig;
pub mod directives;
pub mod opcodes;
use crate::native_types::{Expression, Witness};
use acir_field::FieldElement;
pub use opcodes::Opcode;
use thiserror::Error;
use std::{io::prelude::*, num::ParseIntError, str::FromStr};
use base64::Engine;
use flate2::Compression;
use serde::{de::Error as DeserializationError, Deserialize, Deserializer, Serialize, Serializer};
use std::collections::BTreeSet;
use self::{brillig::BrilligBytecode, opcodes::BlockId};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum ExpressionWidth {
#[default]
Unbounded,
Bounded {
width: usize,
},
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct Program {
pub functions: Vec<Circuit>,
pub unconstrained_functions: Vec<BrilligBytecode>,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct Circuit {
pub current_witness_index: u32,
pub opcodes: Vec<Opcode>,
pub expression_width: ExpressionWidth,
pub private_parameters: BTreeSet<Witness>,
pub public_parameters: PublicInputs,
pub return_values: PublicInputs,
pub assert_messages: Vec<(OpcodeLocation, AssertionPayload)>,
pub recursive: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ExpressionOrMemory {
Expression(Expression),
Memory(BlockId),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AssertionPayload {
StaticString(String),
Dynamic( u64, Vec<ExpressionOrMemory>),
}
#[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: serde::Serializer,
{
self.0.to_string().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ErrorSelector {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
let as_u64 = s.parse().map_err(serde::de::Error::custom)?;
Ok(ErrorSelector(as_u64))
}
}
pub const STRING_ERROR_SELECTOR: ErrorSelector = ErrorSelector(0);
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub struct RawAssertionPayload {
pub selector: ErrorSelector,
pub data: Vec<FieldElement>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum ResolvedAssertionPayload {
String(String),
Raw(RawAssertionPayload),
}
#[derive(Debug, Copy, Clone)]
pub struct ResolvedOpcodeLocation {
pub acir_function_index: usize,
pub opcode_location: OpcodeLocation,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum OpcodeLocation {
Acir(usize),
Brillig { acir_index: usize, brillig_index: usize },
}
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 Circuit {
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 Program {
fn write<W: std::io::Write>(&self, writer: W) -> std::io::Result<()> {
let buf = bincode::serialize(self).unwrap();
let mut encoder = flate2::write::GzEncoder::new(writer, Compression::default());
encoder.write_all(&buf)?;
encoder.finish()?;
Ok(())
}
fn read<R: std::io::Read>(reader: R) -> std::io::Result<Self> {
let mut gz_decoder = flate2::read::GzDecoder::new(reader);
let mut buf_d = Vec::new();
gz_decoder.read_to_end(&mut buf_d)?;
bincode::deserialize(&buf_d)
.map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))
}
pub fn serialize_program(program: &Program) -> 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 deserialize_program(serialized_circuit: &[u8]) -> std::io::Result<Self> {
Program::read(serialized_circuit)
}
pub fn serialize_program_base64<S>(program: &Program, 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)
}
pub fn deserialize_program_base64<'de, D>(deserializer: D) -> Result<Program, D::Error>
where
D: Deserializer<'de>,
{
let bytecode_b64: String = serde::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 std::fmt::Display for Circuit {
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 std::fmt::Debug for Circuit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
impl std::fmt::Display for Program {
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 std::fmt::Debug for Program {
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)]
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::{
opcodes::{BlackBoxFuncCall, FunctionInput},
Circuit, Compression, Opcode, PublicInputs,
};
use crate::{
circuit::{ExpressionWidth, Program},
native_types::Witness,
};
use acir_field::FieldElement;
fn and_opcode() -> Opcode {
Opcode::BlackBoxFuncCall(BlackBoxFuncCall::AND {
lhs: FunctionInput { witness: Witness(1), num_bits: 4 },
rhs: FunctionInput { witness: Witness(2), num_bits: 4 },
output: Witness(3),
})
}
fn range_opcode() -> Opcode {
Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE {
input: FunctionInput { witness: Witness(1), num_bits: 8 },
})
}
fn keccakf1600_opcode() -> Opcode {
let inputs: Box<[FunctionInput; 25]> = Box::new(std::array::from_fn(|i| FunctionInput {
witness: Witness(i as u32 + 1),
num_bits: 8,
}));
let outputs: Box<[Witness; 25]> = Box::new(std::array::from_fn(|i| Witness(i as u32 + 26)));
Opcode::BlackBoxFuncCall(BlackBoxFuncCall::Keccakf1600 { inputs, outputs })
}
fn schnorr_verify_opcode() -> Opcode {
let public_key_x =
FunctionInput { witness: Witness(1), num_bits: FieldElement::max_num_bits() };
let public_key_y =
FunctionInput { witness: Witness(2), num_bits: FieldElement::max_num_bits() };
let signature: Box<[FunctionInput; 64]> = Box::new(std::array::from_fn(|i| {
FunctionInput { witness: Witness(i as u32 + 3), num_bits: 8 }
}));
let message: Vec<FunctionInput> = vec![FunctionInput { witness: Witness(67), num_bits: 8 }];
let output = Witness(68);
Opcode::BlackBoxFuncCall(BlackBoxFuncCall::SchnorrVerify {
public_key_x,
public_key_y,
signature,
message,
output,
})
}
#[test]
fn serialization_roundtrip() {
let circuit = Circuit {
current_witness_index: 5,
expression_width: ExpressionWidth::Unbounded,
opcodes: vec![and_opcode(), range_opcode(), schnorr_verify_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(),
recursive: false,
};
let program = Program { functions: vec![circuit], unconstrained_functions: Vec::new() };
fn read_write(program: Program) -> (Program, Program) {
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(),
schnorr_verify_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(),
recursive: false,
};
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 = Program::deserialize_program(&zipped_bad_circuit);
assert!(deserialization_result.is_err());
}
}