acir/circuit/
brillig.rs

1//! This module contains [Brillig][brillig] structures for integration within an ACIR circuit.
2//!
3//! [Brillig][brillig] is used in ACIR as a hint for the solver when executing the circuit.
4//! Executing Brillig does not generate any constraints and is the result of the compilation of an unconstrained function.
5//!
6//! Let's see an example with euclidean division.
7//! The normal way to compute `a/b`, where `a` and `b` are 8-bits integers, is to
8//! implement the Euclidean algorithm which computes in a loop (or recursively)
9//! modulus of the kind 'a mod b'. Doing this computation requires a lot of steps to
10//! be properly implemented in ACIR, especially the loop with a condition. However,
11//! euclidean division can be easily constrained with one assert-zero opcode:
12//! `a = bq+r`, assuming `q` is 8 bits and `r<b`. Since these assumptions can easily
13//! written with a few range opcodes, euclidean division can in fact be implemented
14//! with a small number of opcodes.
15//!
16//! However, in order to write these opcodes we need the result of the division
17//! which are the witness `q` and `r`. But from the constraint `a=bq+r`, how can the
18//! solver figure out how to solve `q` and `r` with only one equation? This is where
19//! brillig/unconstrained function come into action. We simply define a function that
20//! performs the usual Euclidean algorithm to compute `q` and `r` from `a` and `b`.
21//! Since executing Brillig does not generate constraints, it is not meaningful to the
22//! proving system but simply used by the solver to compute the values of `q` and
23//! `r`. The output witnesses `q` and `r` are then expected to be used by the proving system.
24//!
25//! In summary, executing Brillig will perform the computation defined by its
26//! bytecode, on the provided inputs, and assign the result to the output witnesses,
27//! without adding any constraints.
28
29use super::opcodes::BlockId;
30use crate::native_types::{Expression, Witness};
31use acir_field::AcirField;
32use brillig::Opcode as BrilligOpcode;
33use serde::{Deserialize, Serialize};
34
35/// Inputs for the Brillig VM. These are the initial inputs
36/// that the Brillig VM will use to start.
37#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)]
38#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
39pub enum BrilligInputs<F> {
40    Single(Expression<F>),
41    Array(Vec<Expression<F>>),
42    MemoryArray(BlockId),
43}
44
45impl<F: AcirField> std::fmt::Display for BrilligInputs<F> {
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        match self {
48            BrilligInputs::Single(expr) => expr.fmt(f),
49            BrilligInputs::Array(exprs) => {
50                let joined = exprs.iter().map(|e| format!("{e}")).collect::<Vec<_>>().join(", ");
51                write!(f, "[{joined}]")
52            }
53            BrilligInputs::MemoryArray(block_id) => block_id.fmt(f),
54        }
55    }
56}
57
58/// Outputs for the Brillig VM. Once the VM has completed
59/// execution, this will be the object that is returned.
60#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)]
61#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
62pub enum BrilligOutputs {
63    Simple(Witness),
64    Array(Vec<Witness>),
65}
66
67impl std::fmt::Display for BrilligOutputs {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        match self {
70            BrilligOutputs::Simple(witness) => write!(f, "{witness}"),
71            BrilligOutputs::Array(witnesses) => {
72                let joined =
73                    witnesses.iter().map(|w| format!("{w}")).collect::<Vec<_>>().join(", ");
74                write!(f, "[{joined}]")
75            }
76        }
77    }
78}
79
80/// This is purely a wrapper struct around a list of Brillig opcode's which represents
81/// a full Brillig function to be executed by the Brillig VM.
82/// This is stored separately on a program and accessed through a [BrilligFunctionId].
83#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Debug, Hash)]
84#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
85pub struct BrilligBytecode<F> {
86    #[serde(default)] // For backwards compatibility
87    pub function_name: String,
88    pub bytecode: Vec<BrilligOpcode<F>>,
89}
90
91/// Id for the function being called.
92/// Indexes into the table of Brillig function's specified in a [program][super::Program]
93#[derive(
94    Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash, Copy, Default, PartialOrd, Ord,
95)]
96#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
97#[serde(transparent)]
98pub struct BrilligFunctionId(pub u32);
99
100impl BrilligFunctionId {
101    pub fn as_usize(&self) -> usize {
102        self.0 as usize
103    }
104}
105
106impl std::fmt::Display for BrilligFunctionId {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        write!(f, "{}", self.0)
109    }
110}