Skip to content

Commit c61968b

Browse files
committed
moving more acir docs to code declaration and more structure documented
1 parent c757112 commit c61968b

File tree

10 files changed

+175
-123
lines changed

10 files changed

+175
-123
lines changed

acvm-repo/acir/README.md

-105
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,5 @@
11
# ACIR documentation (draft)
22

3-
## Abstract
4-
5-
This document describes the purpose of ACIR, what it is and how ACIR programs
6-
can be used by compilers and proving systems. It is intended to be a reference
7-
documentation for ACIR.
8-
9-
## Introduction
10-
11-
The purpose of ACIR is to make the link between a generic proving system, such
12-
as Aztec's Barretenberg, and a frontend, such as Noir, which describes user
13-
specific computations.
14-
15-
More precisely, Noir is a programming language for zero-knowledge proofs (ZKP)
16-
which allows users to write programs in an intuitive way using a high-level
17-
language close to Rust syntax. Noir is able to generate a proof of execution of
18-
a Noir program, using an external proving system. However, proving systems use
19-
specific low-level constrain-based languages. Similarly, frontends have their
20-
own internal representation in order to represent user programs.
21-
22-
The goal of ACIR is to provide a generic open-source intermediate
23-
representation close to proving system 'languages', but agnostic to a specific
24-
proving system, that can be used both by proving system as well as a target for
25-
frontends. So, at the end of the day, an ACIR program is just another
26-
representation of a program, dedicated to proving systems.
27-
28-
## Abstract Circuit Intermediate Representation
29-
ACIR stands for abstract circuit intermediate representation:
30-
- **abstract circuit**: circuits are a simple computation model where basic
31-
computation units, named gates, are connected with wires. Data flows
32-
through the wires while gates compute output wires based on their input.
33-
More formally, they are directed acyclic graphs (DAG) where the vertices
34-
are the gates and the edges are the wires. Due to the immutability nature
35-
of the wires (their value does not change during an execution), they are
36-
well suited for describing computations for ZKPs. Furthermore, we do not
37-
lose any expressiveness when using a circuit as it is well known that any
38-
bounded computation can be translated into an arithmetic circuit (i.e a
39-
circuit with only addition and multiplication gates).
40-
The term abstract here simply means that we do not refer to an actual physical
41-
circuit (such as an electronic circuit). Furthermore, we will not exactly use
42-
the circuit model, but another model even better suited to ZKPs, the constraint
43-
model (see below).
44-
- **intermediate representation**: The ACIR representation is intermediate
45-
because it lies between a frontend and its proving system. ACIR bytecode makes
46-
the link between noir compiler output and the proving system backend input.
47-
48-
## The constraint model
49-
50-
The first step for generating a proof that a specific program was executed, is
51-
to execute this program. Since the proving system is going to handle ACIR
52-
programs, we need in fact to execute an ACIR program, using the user-supplied
53-
inputs.
54-
55-
In ACIR terminology, the gates are called opcodes and the wires are called
56-
partial witnesses. However, instead of connecting the opcodes together through
57-
wires, we create constraints: an opcode constraints together a set of wires.
58-
This constraint model trivially supersedes the circuit model. For instance, an
59-
addition gate `output_wire = input_wire_1 + input_wire_2` can be expressed with
60-
the following arithmetic constraint:
61-
`output_wire - (input_wire_1 + input_wire_2) = 0`
62-
63-
64-
## Solving
65-
66-
Because of these constraints, executing an ACIR program is called solving the
67-
witnesses. From the witnesses representing the inputs of the program, whose
68-
values are supplied by the user, we find out what the other witnesses should be
69-
by executing/solving the constraints one-by-one in the order they were defined.
70-
71-
For instance, if `input_wire_1` and `input_wire_2` values are supplied as `3` and
72-
`8`, then we can solve the opcode
73-
`output_wire - (input_wire_1 + input_wire_2) = 0` by saying that `output_wire` is
74-
`11`.
75-
76-
In summary, the workflow is the following:
77-
1. user program -> (compilation) ACIR, a list of opcodes which constrain
78-
(partial) witnesses
79-
2. user inputs + ACIR -> (execution/solving) assign values to all the
80-
(partial) witnesses
81-
3. witness assignment + ACIR -> (proving system) proof
82-
83-
Although the ordering of opcode does not matter in theory, since a system of
84-
equations is not dependent on its ordering, in practice it matters a lot for the
85-
solving (i.e the performance of the execution). ACIR opcodes **must be ordered**
86-
so that each opcode can be resolved one after the other.
87-
88-
The values of the witnesses lie in the scalar field of the proving system. We
89-
will refer to it as `FieldElement` or ACIR field. The proving system needs the
90-
values of all the partial witnesses and all the constraints in order to generate
91-
a proof.
92-
93-
*Remark*: The value of a partial witness is unique and fixed throughout a program
94-
execution, although in some rare cases, multiple values are possible for a
95-
same execution and witness (when there are several valid solutions to the
96-
constraints). Having multiple possible values for a witness may indicate that
97-
the circuit is not safe.
98-
99-
*Remark*: Why do we use the term partial witnesses? It is because the proving
100-
system may create other constraints and witnesses (especially with
101-
`BlackBoxFuncCall`, see below). A proof refers to a full witness assignments
102-
and their constraints. ACIR opcodes and their partial witnesses are still an
103-
intermediate representation before getting the full list of constraints and
104-
witnesses. For the sake of simplicity, we will refer to witness instead of
105-
partial witness from now on.
106-
107-
1083
## ACIR Reference
1094

1105
We assume here that the proving system is Barretenberg. Some parameters may

acvm-repo/acir/src/circuit/brillig.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//! [Brillig][brillig] structures for integration within an ACIR circuit.
2+
13
use super::opcodes::BlockId;
24
use crate::native_types::{Expression, Witness};
35
use brillig::Opcode as BrilligOpcode;

acvm-repo/acir/src/circuit/mod.rs

+33-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//! The Abstract Circuit Intermediate Representation (ACIR)
2+
13
pub mod black_box_functions;
24
pub mod brillig;
35
pub mod opcodes;
@@ -39,7 +41,7 @@ pub enum ExpressionWidth {
3941
},
4042
}
4143

42-
/// A program represented by multiple ACIR circuits. The execution trace of these
44+
/// A program represented by multiple ACIR [circuit][Circuit]'s. The execution trace of these
4345
/// circuits is dictated by construction of the [crate::native_types::WitnessStack].
4446
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Hash)]
4547
#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
@@ -48,13 +50,20 @@ pub struct Program<F: AcirField> {
4850
pub unconstrained_functions: Vec<BrilligBytecode<F>>,
4951
}
5052

53+
/// Representation of a single ACIR circuit. The execution trace of this structure
54+
/// is dictated by the construction of a [crate::native_types::WitnessMap]
5155
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Hash)]
5256
#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
5357
pub struct Circuit<F: AcirField> {
54-
// current_witness_index is the highest witness index in the circuit. The next witness to be added to this circuit
55-
// will take on this value. (The value is cached here as an optimization.)
58+
/// current_witness_index is the highest witness index in the circuit. The next witness to be added to this circuit
59+
/// will take on this value. (The value is cached here as an optimization.)
5660
pub current_witness_index: u32,
61+
/// The circuit opcodes representing the relationship between witness values.
62+
///
63+
/// The opcodes should be further converted into a backend-specific circuit representation.
64+
/// When initial witness inputs are provided, these opcodes can also be used for generating an execution trace.
5765
pub opcodes: Vec<Opcode<F>>,
66+
/// Maximum width of the [expression][Expression]'s which will be constrained
5867
pub expression_width: ExpressionWidth,
5968

6069
/// The set of private inputs to the circuit.
@@ -76,20 +85,32 @@ pub struct Circuit<F: AcirField> {
7685
pub assert_messages: Vec<(OpcodeLocation, AssertionPayload<F>)>,
7786
}
7887

88+
/// Enumeration of either an [expression][Expression] or a [memory identifier][BlockId].
7989
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
8090
#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
8191
pub enum ExpressionOrMemory<F> {
8292
Expression(Expression<F>),
8393
Memory(BlockId),
8494
}
8595

96+
/// Payload tied to an assertion failure.
97+
/// This data allows users to specify feedback upon a constraint not being satisfied in the circuit.
8698
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
8799
#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
88100
pub struct AssertionPayload<F> {
101+
/// Selector that maps a hash of either a constant string or an internal compiler error type
102+
/// to an ABI type. The ABI type should then be used to appropriately resolve the payload data.
89103
pub error_selector: u64,
104+
/// The dynamic payload data.
105+
///
106+
/// Upon fetching the appropriate ABI type from the `error_selector`, the values
107+
/// in this payload can be decoded into the given ABI type.
108+
/// The payload is expected to be empty in the case of a constant string
109+
/// as the string can be contained entirely within the error type and ABI type.
90110
pub payload: Vec<ExpressionOrMemory<F>>,
91111
}
92112

113+
/// Value for differentiating error types. Used internally by an [AssertionPayload].
93114
#[derive(Debug, Copy, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
94115
pub struct ErrorSelector(u64);
95116

@@ -123,12 +144,19 @@ impl<'de> Deserialize<'de> for ErrorSelector {
123144
}
124145
}
125146

147+
/// A dynamic assertion payload whose data has been resolved.
148+
/// This is instantiated upon hitting an assertion failure.
126149
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
127150
pub struct RawAssertionPayload<F> {
151+
/// Selector to the respective ABI type the data in this payload represents
128152
pub selector: ErrorSelector,
153+
/// Resolved data that represents some ABI type.
154+
/// To be decoded in the final step of error resolution.
129155
pub data: Vec<F>,
130156
}
131157

158+
/// Enumeration of allowed assertion payloads.
159+
/// These can either be static strings or dynamic payloads.
132160
#[derive(Clone, PartialEq, Eq, Debug)]
133161
pub enum ResolvedAssertionPayload<F> {
134162
String(String),
@@ -156,6 +184,8 @@ pub enum OpcodeLocation {
156184
Brillig { acir_index: usize, brillig_index: usize },
157185
}
158186

187+
/// Index of Brillig opcode within a list of Brillig opcodes.
188+
/// To be used by callers for resolving debug information.
159189
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
160190
pub struct BrilligOpcodeLocation(pub usize);
161191

acvm-repo/acir/src/circuit/opcodes.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
//! ACIR opcodes
2+
//!
3+
//! This module defines the core set opcodes used in ACIR.
14
use super::brillig::{BrilligFunctionId, BrilligInputs, BrilligOutputs};
25

36
pub mod function_id;
@@ -29,6 +32,9 @@ impl BlockType {
2932
}
3033
}
3134

35+
/// Defines an operation within an ACIR circuit
36+
///
37+
/// Expects a type parameter `F` which implements [AcirField].
3238
#[allow(clippy::large_enum_variant)]
3339
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
3440
#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]

acvm-repo/acir/src/circuit/opcodes/memory_operation.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::native_types::{Expression, Witness};
22
use acir_field::AcirField;
33
use serde::{Deserialize, Serialize};
44

5+
/// Identifier for a block of memory
56
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash, Copy, Default)]
67
#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
78
pub struct BlockId(pub u32);

acvm-repo/acir/src/lib.rs

+97-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,105 @@
1+
//! The Abstract Circuit Intermediate Representation (ACIR)
2+
//!
3+
//! The purpose of ACIR is to make the link between a generic proving system, such
4+
//! as Aztec's Barretenberg, and a frontend, such as Noir, which describes user
5+
//! specific computations.
6+
//!
7+
//! More precisely, Noir is a programming language for zero-knowledge proofs (ZKP)
8+
//! which allows users to write programs in an intuitive way using a high-level
9+
//! language close to Rust syntax. Noir is able to generate a proof of execution of
10+
//! a Noir program, using an external proving system. However, proving systems use
11+
//! specific low-level constrain-based languages. Similarly, frontends have their
12+
//! own internal representation in order to represent user programs.
13+
//!
14+
//! The goal of ACIR is to provide a generic open-source intermediate
15+
//! representation close to proving system 'languages', but agnostic to a specific
16+
//! proving system, that can be used both by proving system as well as a target for
17+
//! frontends. So, at the end of the day, an ACIR program is just another
18+
//! representation of a program, dedicated to proving systems.
19+
//!
20+
//! ## Abstract Circuit Intermediate Representation
21+
//! ACIR stands for abstract circuit intermediate representation:
22+
//! - **abstract circuit**: circuits are a simple computation model where basic
23+
//! computation units, named gates, are connected with wires. Data flows
24+
//! through the wires while gates compute output wires based on their input.
25+
//! More formally, they are directed acyclic graphs (DAG) where the vertices
26+
//! are the gates and the edges are the wires. Due to the immutability nature
27+
//! of the wires (their value does not change during an execution), they are
28+
//! well suited for describing computations for ZKPs. Furthermore, we do not
29+
//! lose any expressiveness when using a circuit as it is well known that any
30+
//! bounded computation can be translated into an arithmetic circuit (i.e a
31+
//! circuit with only addition and multiplication gates).
32+
//! The term abstract here simply means that we do not refer to an actual physical
33+
//! circuit (such as an electronic circuit). Furthermore, we will not exactly use
34+
//! the circuit model, but another model even better suited to ZKPs, the constraint
35+
//! model (see below).
36+
//! - **intermediate representation**: The ACIR representation is intermediate
37+
//! because it lies between a frontend and its proving system. ACIR bytecode makes
38+
//! the link between noir compiler output and the proving system backend input.
39+
//!
40+
//! ## The constraint model
41+
//!
42+
//! The first step for generating a proof that a specific program was executed, is
43+
//! to execute this program. Since the proving system is going to handle ACIR
44+
//! programs, we need in fact to execute an ACIR program, using the user-supplied
45+
//! inputs.
46+
//!
47+
//! In ACIR terminology, the gates are called opcodes and the wires are called
48+
//! partial witnesses. However, instead of connecting the opcodes together through
49+
//! wires, we create constraints: an opcode constraints together a set of wires.
50+
//! This constraint model trivially supersedes the circuit model. For instance, an
51+
//! addition gate `output_wire = input_wire_1 + input_wire_2` can be expressed with
52+
//! the following arithmetic constraint:
53+
//! `output_wire - (input_wire_1 + input_wire_2) = 0`
54+
//!
55+
//! ## Solving
56+
//!
57+
//! Because of these constraints, executing an ACIR program is called solving the
58+
//! witnesses. From the witnesses representing the inputs of the program, whose
59+
//! values are supplied by the user, we find out what the other witnesses should be
60+
//! by executing/solving the constraints one-by-one in the order they were defined.
61+
//!
62+
//! For instance, if `input_wire_1` and `input_wire_2` values are supplied as `3` and
63+
//! `8`, then we can solve the opcode
64+
//! `output_wire - (input_wire_1 + input_wire_2) = 0` by saying that `output_wire` is
65+
//! `11`.
66+
//!
67+
//! In summary, the workflow is the following:
68+
//! 1. user program -> (compilation) ACIR, a list of opcodes which constrain
69+
//! (partial) witnesses
70+
//! 2. user inputs + ACIR -> (execution/solving) assign values to all the
71+
//! (partial) witnesses
72+
//! 3. witness assignment + ACIR -> (proving system) proof
73+
//!
74+
//! Although the ordering of opcode does not matter in theory, since a system of
75+
//! equations is not dependent on its ordering, in practice it matters a lot for the
76+
//! solving (i.e the performance of the execution). ACIR opcodes **must be ordered**
77+
//! so that each opcode can be resolved one after the other.
78+
//!
79+
//! The values of the witnesses lie in the scalar field of the proving system. We
80+
//! will refer to it as `FieldElement` or ACIR field. The proving system needs the
81+
//! values of all the partial witnesses and all the constraints in order to generate
82+
//! a proof.
83+
//!
84+
//! _Remark_: The value of a partial witness is unique and fixed throughout a program
85+
//! execution, although in some rare cases, multiple values are possible for a
86+
//! same execution and witness (when there are several valid solutions to the
87+
//! constraints). Having multiple possible values for a witness may indicate that
88+
//! the circuit is not safe.
89+
//!
90+
//! _Remark_: Why do we use the term partial witnesses? It is because the proving
91+
//! system may create other constraints and witnesses (especially with
92+
//! `BlackBoxFuncCall`, see below). A proof refers to a full witness assignments
93+
//! and their constraints. ACIR opcodes and their partial witnesses are still an
94+
//! intermediate representation before getting the full list of constraints and
95+
//! witnesses. For the sake of simplicity, we will refer to witness instead of
96+
//! partial witness from now on.
97+
198
#![cfg_attr(not(test), forbid(unsafe_code))] // `std::env::set_var` is used in tests.
299
#![warn(unreachable_pub)]
3100
#![warn(clippy::semicolon_if_nothing_returned)]
4101
#![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))]
5102

6-
// Arbitrary Circuit Intermediate Representation
7-
8103
pub mod circuit;
9104
pub mod native_types;
10105
mod proto;

0 commit comments

Comments
 (0)