Skip to content

Commit 739fe90

Browse files
authored
feat(avm): VM circuit handles tagged memory (AztecProtocol#3725)
Resolves AztecProtocol#3644
1 parent 78cf525 commit 739fe90

16 files changed

+1109
-403
lines changed

barretenberg/cpp/pil/avm/avm_mini.pil

+34-7
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ namespace avmMini(256);
2121
// DIV
2222
pol commit sel_op_div;
2323

24-
// Error boolean flag pertaining to an operation
25-
pol commit op_err;
24+
// Instruction memory tag (0: uninitialized, 1: u8, 2: u16, 3: u32, 4: u64, 5: u128, 6:field)
25+
pol commit in_tag;
26+
27+
// Errors
28+
pol commit op_err; // Boolean flag pertaining to an operation error
29+
pol commit tag_err; // Boolean flag (foreign key to memTrace.m_tag_err)
2630

2731
// A helper witness being the inverse of some value
2832
// to show a non-zero equality
@@ -49,8 +53,8 @@ namespace avmMini(256);
4953
pol commit mem_idx_a;
5054
pol commit mem_idx_b;
5155
pol commit mem_idx_c;
52-
53-
56+
57+
5458
// Track the last line of the execution trace. It does NOT correspond to the last row of the whole table
5559
// of size N. As this depends on the supplied bytecode, this polynomial cannot be constant.
5660
pol commit last;
@@ -63,6 +67,7 @@ namespace avmMini(256);
6367
sel_op_div * (1 - sel_op_div) = 0;
6468

6569
op_err * (1 - op_err) = 0;
70+
tag_err * (1 - tag_err) = 0; // Potential optimization (boolean constraint derivation from equivalence check to memTrace)?
6671

6772
mem_op_a * (1 - mem_op_a) = 0;
6873
mem_op_b * (1 - mem_op_b) = 0;
@@ -72,23 +77,38 @@ namespace avmMini(256);
7277
rwb * (1 - rwb) = 0;
7378
rwc * (1 - rwc) = 0;
7479

80+
// TODO: Constrain rwa, rwb, rwc to u32 type and 0 <= in_tag <= 6
81+
82+
// Set intermediate registers to 0 whenever tag_err occurs
83+
tag_err * ia = 0;
84+
tag_err * ib = 0;
85+
tag_err * ic = 0;
86+
7587
// Relation for addition over the finite field
88+
#[SUBOP_ADDITION_FF]
7689
sel_op_add * (ia + ib - ic) = 0;
7790

7891
// Relation for subtraction over the finite field
92+
#[SUBOP_SUBTRACTION_FF]
7993
sel_op_sub * (ia - ib - ic) = 0;
8094

8195
// Relation for multiplication over the finite field
96+
#[SUBOP_MULTIPLICATION_FF]
8297
sel_op_mul * (ia * ib - ic) = 0;
8398

8499
// Relation for division over the finite field
100+
// If tag_err == 1 in a division, then ib == 0 and op_err == 1.
101+
#[SUBOP_DIVISION_FF]
85102
sel_op_div * (1 - op_err) * (ic * ib - ia) = 0;
86103

87104
// When sel_op_div == 1, we want ib == 0 <==> op_err == 1
88105
// This can be achieved with the 2 following relations.
89106
// inv is an extra witness to show that we can invert ib, i.e., inv = ib^(-1)
90-
// If ib == 0, we have to set inv = 1 to satisfy the second relation.
107+
// If ib == 0, we have to set inv = 1 to satisfy the second relation,
108+
// because op_err == 1 from the first relation.
109+
#[SUBOP_DIVISION_ZERO_ERR1]
91110
sel_op_div * (ib * inv - 1 + op_err) = 0;
111+
#[SUBOP_DIVISION_ZERO_ERR2]
92112
sel_op_div * op_err * (1 - inv) = 0;
93113

94114
// op_err cannot be maliciously activated for a non-relevant
@@ -97,9 +117,10 @@ namespace avmMini(256);
97117
// Note that the above is even a stronger constraint, as it shows
98118
// that exactly one sel_op_XXX must be true.
99119
// At this time, we have only division producing an error.
120+
#[SUBOP_ERROR_RELEVANT_OP]
100121
op_err * (sel_op_div - 1) = 0;
101122

102-
// TODO: constraint that we stop execution at the first error
123+
// TODO: constraint that we stop execution at the first error (tag_err or op_err)
103124
// An error can only happen at the last sub-operation row.
104125

105126
// OPEN/POTENTIAL OPTIMIZATION: Dedicated error per relevant operation?
@@ -108,4 +129,10 @@ namespace avmMini(256);
108129
// Same for the relations related to the error activation:
109130
// (ib * inv - 1 + op_div_err) = 0 && op_err * (1 - inv) = 0
110131
// This works in combination with op_div_err * (sel_op_div - 1) = 0;
111-
// Drawback is the need to paralllelize the latter.
132+
// Drawback is the need to paralllelize the latter.
133+
134+
// Inter-table Constraints
135+
136+
// TODO: tag_err {clk} IS memTrace.m_tag_err {memTrace.m_clk}
137+
// TODO: Map memory trace with intermediate register values whenever there is no tag error, sthg like:
138+
// mem_op_a * (1 - tag_err) {mem_idx_a, clk, ia, rwa} IS m_sub_clk == 0 && 1 - m_tag_err {m_addr, m_clk, m_val, m_rw}

barretenberg/cpp/pil/avm/avm_mini_opt.pil

+19-2
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,34 @@ namespace memTrace(256);
22
col witness m_clk;
33
col witness m_sub_clk;
44
col witness m_addr;
5+
col witness m_tag;
56
col witness m_val;
67
col witness m_lastAccess;
8+
col witness m_last;
79
col witness m_rw;
10+
col witness m_in_tag;
11+
col witness m_tag_err;
12+
col witness m_one_min_inv;
813
(memTrace.m_lastAccess * (1 - memTrace.m_lastAccess)) = 0;
14+
(memTrace.m_last * (1 - memTrace.m_last)) = 0;
915
(memTrace.m_rw * (1 - memTrace.m_rw)) = 0;
10-
(((1 - avmMini.first) * (1 - memTrace.m_lastAccess)) * (memTrace.m_addr' - memTrace.m_addr)) = 0;
11-
(((((1 - avmMini.first) * (1 - avmMini.last)) * (1 - memTrace.m_lastAccess)) * (1 - memTrace.m_rw')) * (memTrace.m_val' - memTrace.m_val)) = 0;
16+
(memTrace.m_tag_err * (1 - memTrace.m_tag_err)) = 0;
17+
((1 - memTrace.m_lastAccess) * (memTrace.m_addr' - memTrace.m_addr)) = 0;
18+
(((1 - memTrace.m_lastAccess) * (1 - memTrace.m_rw')) * (memTrace.m_val' - memTrace.m_val)) = 0;
19+
(((1 - memTrace.m_lastAccess) * (1 - memTrace.m_rw')) * (memTrace.m_tag' - memTrace.m_tag)) = 0;
20+
((memTrace.m_lastAccess * (1 - memTrace.m_rw')) * memTrace.m_val') = 0;
21+
((memTrace.m_in_tag - memTrace.m_tag) * (1 - memTrace.m_one_min_inv)) = memTrace.m_tag_err;
22+
((1 - memTrace.m_tag_err) * memTrace.m_one_min_inv) = 0;
1223
namespace avmMini(256);
1324
col fixed clk(i) { i };
1425
col fixed first = [1] + [0]*;
1526
col witness sel_op_add;
1627
col witness sel_op_sub;
1728
col witness sel_op_mul;
1829
col witness sel_op_div;
30+
col witness in_tag;
1931
col witness op_err;
32+
col witness tag_err;
2033
col witness inv;
2134
col witness ia;
2235
col witness ib;
@@ -36,12 +49,16 @@ namespace avmMini(256);
3649
(avmMini.sel_op_mul * (1 - avmMini.sel_op_mul)) = 0;
3750
(avmMini.sel_op_div * (1 - avmMini.sel_op_div)) = 0;
3851
(avmMini.op_err * (1 - avmMini.op_err)) = 0;
52+
(avmMini.tag_err * (1 - avmMini.tag_err)) = 0;
3953
(avmMini.mem_op_a * (1 - avmMini.mem_op_a)) = 0;
4054
(avmMini.mem_op_b * (1 - avmMini.mem_op_b)) = 0;
4155
(avmMini.mem_op_c * (1 - avmMini.mem_op_c)) = 0;
4256
(avmMini.rwa * (1 - avmMini.rwa)) = 0;
4357
(avmMini.rwb * (1 - avmMini.rwb)) = 0;
4458
(avmMini.rwc * (1 - avmMini.rwc)) = 0;
59+
(avmMini.tag_err * avmMini.ia) = 0;
60+
(avmMini.tag_err * avmMini.ib) = 0;
61+
(avmMini.tag_err * avmMini.ic) = 0;
4562
(avmMini.sel_op_add * ((avmMini.ia + avmMini.ib) - avmMini.ic)) = 0;
4663
(avmMini.sel_op_sub * ((avmMini.ia - avmMini.ib) - avmMini.ic)) = 0;
4764
(avmMini.sel_op_mul * ((avmMini.ia * avmMini.ib) - avmMini.ic)) = 0;

barretenberg/cpp/pil/avm/mem_trace.pil

+68-9
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,50 @@ namespace memTrace(256);
77
pol commit m_clk;
88
pol commit m_sub_clk;
99
pol commit m_addr;
10+
pol commit m_tag; // Memory tag (0: uninitialized, 1: u8, 2: u16, 3: u32, 4: u64, 5: u128, 6:field)
1011
pol commit m_val;
1112
pol commit m_lastAccess; // Boolean (1 when this row is the last of a given address)
13+
pol commit m_last; // Boolean indicating the last row of the memory trace (not execution trace)
1214
pol commit m_rw; // Enum: 0 (read), 1 (write)
13-
15+
16+
pol commit m_in_tag; // Instruction memory tag ("foreign key" pointing to avmMini.in_tag)
17+
18+
// Error columns
19+
pol commit m_tag_err; // Boolean (1 if m_in_tag != m_tag is detected)
20+
21+
// Helper columns
22+
pol commit m_one_min_inv; // Extra value to prove m_in_tag != m_tag with error handling
23+
1424
// Type constraints
1525
m_lastAccess * (1 - m_lastAccess) = 0;
26+
m_last * (1 - m_last) = 0;
1627
m_rw * (1 - m_rw) = 0;
17-
28+
m_tag_err * (1 - m_tag_err) = 0;
29+
30+
// TODO: m_addr is u32 and 0 <= m_tag <= 6
31+
// (m_in_tag will be constrained through inclusion check to main trace)
32+
33+
// Remark: m_lastAccess == 1 on first row and therefore any relation with the
34+
// multiplicative term (1 - m_lastAccess) implicitly includes (1 - avmMini.first)
35+
// Similarly, this includes (1 - m_last) as well.
36+
1837
// m_lastAccess == 0 ==> m_addr' == m_addr
19-
(1 - avmMini.first) * (1 - m_lastAccess) * (m_addr' - m_addr) = 0;
38+
// Optimization: We removed the term (1 - avmMini.first)
39+
#[MEM_LAST_ACCESS_DELIMITER]
40+
(1 - m_lastAccess) * (m_addr' - m_addr) = 0;
2041

2142
// We need: m_lastAccess == 1 ==> m_addr' > m_addr
2243
// The above implies: m_addr' == m_addr ==> m_lastAccess == 0
2344
// This condition does not apply on the last row.
2445
// clk + 1 used as an expression for positive integers
2546
// TODO: Uncomment when lookups are supported
26-
// (1 - first) * (1 - last) * m_lastAccess { (m_addr' - m_addr) } in clk + 1; // Gated inclusion check. Is it supported?
47+
// (1 - first) * (1 - m_last) * m_lastAccess { (m_addr' - m_addr) } in clk + 1; // Gated inclusion check. Is it supported?
2748

2849
// TODO: following constraint
2950
// m_addr' == m_addr && m_clk == m_clk' ==> m_sub_clk' - m_sub_clk > 0
30-
// Can be enforced with (1 - first) * (1 - last) * (1 - m_lastAccess) { 6 * (m_clk' - m_clk) + m_sub_clk' - m_sub_clk } in clk + 1
51+
// Can be enforced with (1 - first) * (1 - m_lastAccess) { 6 * (m_clk' - m_clk) + m_sub_clk' - m_sub_clk } in clk + 1
3152

32-
// Alternatively to the above, one could require
53+
// Alternatively to the above, one could require
3354
// that m_addr' - m_addr is 0 or 1 (needs to add placeholders m_addr values):
3455
// (m_addr' - m_addr) * (m_addr' - m_addr) - (m_addr' - m_addr) = 0;
3556
// if m_addr' - m_addr is 0 or 1, the following is equiv. to m_lastAccess
@@ -40,8 +61,46 @@ namespace memTrace(256);
4061
// Note: in barretenberg, a shifted polynomial will be 0 on the last row (shift is not cyclic)
4162
// Note2: in barretenberg, if a poynomial is shifted, its non-shifted equivalent must be 0 on the first row
4263

43-
(1 - avmMini.first) * (1 - avmMini.last) * (1 - m_lastAccess) * (1 - m_rw') * (m_val' - m_val) = 0;
64+
// Optimization: We removed the term (1 - avmMini.first) and (1 - m_last)
65+
#[MEM_READ_WRITE_VAL_CONSISTENCY]
66+
(1 - m_lastAccess) * (1 - m_rw') * (m_val' - m_val) = 0;
4467

45-
// TODO: Constraint the first load from a given adress has value 0. (Consistency of memory initialization.)
68+
// m_lastAccess == 0 && m_rw' == 0 ==> m_tag == m_tag'
69+
// Optimization: We removed the term (1 - avmMini.first) and (1 - m_last)
70+
#[MEM_READ_WRITE_TAG_CONSISTENCY]
71+
(1 - m_lastAccess) * (1 - m_rw') * (m_tag' - m_tag) = 0;
72+
73+
// Constrain that the first load from a given address has value 0. (Consistency of memory initialization.)
74+
// We do not constrain that the m_tag == 0 as the 0 value is compatible with any memory type.
75+
// If we set m_lastAccess = 1 on the first row, we can enforce this (should be ok as long as we do not shift m_lastAccess):
76+
#[MEM_ZERO_INIT]
77+
m_lastAccess * (1 - m_rw') * m_val' = 0;
78+
79+
// Memory tag consistency check
80+
// We want to prove that m_in_tag == m_tag <==> m_tag_err == 0
81+
// We want to show that we can invert (m_in_tag - m_tag) when m_tag_err == 1,
82+
// i.e., m_tag_err == 1 ==> m_in_tag != m_tag
83+
// For this purpose, we need an extra column to store a witness
84+
// which can be used to show that (m_in_tag - m_tag) is invertible (non-zero).
85+
// We re-use the same zero (non)-equality technique as in SUBOP_DIVISION_ZERO_ERR1/2 applied
86+
// to (m_in_tag - m_tag) by replacing m_tag_err by 1 - m_tag_err because here
87+
// the equality to zero is not an error. Another modification
88+
// consists in storing 1 - (m_in_tag - m_tag)^(-1) in the extra witness column
89+
// instead of (m_in_tag - m_tag)^(-1) as this allows to store zero by default (i.e., when m_tag_err == 0).
90+
// The new column m_one_min_inv is set to 1 - (m_in_tag - m_tag)^(-1) when m_tag_err == 1
91+
// but must be set to 0 when tags are matching and m_tag_err = 0
92+
#[MEM_IN_TAG_CONSISTENCY_1]
93+
(m_in_tag - m_tag) * (1 - m_one_min_inv) - m_tag_err = 0;
94+
#[MEM_IN_TAG_CONSISTENCY_2]
95+
(1 - m_tag_err) * m_one_min_inv = 0;
96+
97+
// Correctness of two above checks MEM_IN_TAG_CONSISTENCY_1/2:
98+
// m_in_tag == m_tag ==> m_tag_err == 0 (first relation)
99+
// m_tag_err == 0 ==> m_one_min_inv == 0 by second relation. First relation ==> m_in_tag - m_tag == 0
100+
46101
// TODO: when introducing load/store as sub-operations, we will have to add consistency of intermediate
47-
// register values ia, ib, ic
102+
// register values ia, ib, ic
103+
104+
// Inter-table Constraints
105+
106+
// TODO: {m_clk, m_in_tag} IN {avmMini.clk, avmMini.in_tag}

0 commit comments

Comments
 (0)