-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathzkvm.circom
More file actions
293 lines (245 loc) · 8.13 KB
/
Copy pathzkvm.circom
File metadata and controls
293 lines (245 loc) · 8.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
pragma circom 2.1.6;
include "node_modules/circomlib/circuits/comparators.circom";
include "node_modules/circomlib/circuits/gates.circom";
template AND3() {
signal input in[3];
signal output out;
signal temp;
temp <== in[0] * in[1];
out <== temp * in[2];
}
// i is the column number
// bits is how many bits we need
// for the LessEqThan component
template ShouldCopy(i, bits) {
signal input sp;
signal input is_push;
signal input is_nop;
signal input is_add;
signal input is_mul;
// out = 1 if should copy
signal output out;
// sanity checks
is_add + is_mul + is_push + is_nop === 1;
is_nop * (1 - is_nop) === 0;
is_push * (1 - is_push) === 0;
is_add * (1 - is_add) === 0;
is_mul * (1 - is_mul) === 0;
// it's cheaper to compute ≠ 0 than > 0 to avoid
// converting the number to binary
signal spEqZero;
signal spGteOne;
spEqZero <== IsZero()(sp);
spGteOne <== 1 - spEqZero;
// it's cheaper to compute ≠ 0 and ≠ 1 than ≥ 2
signal spEqOne;
signal spGteTwo;
spEqOne <== IsEqual()([sp, 1]);
spGteTwo <== 1 - spEqOne * spEqZero;
// the current column is 1 or more
// below the stack pointer
signal oneBelowSp <== LessEqThan(bits)([i, sp - 1]);
// the current column is 3 or more
// below the stack pointer
signal threeBelowSP <== LessEqThan(bits)([i, sp - 3]);
// condition A
component a3A = AND3();
a3A.in[0] <== spGteOne;
a3A.in[1] <== oneBelowSp;
a3A.in[2] <== is_push + is_nop;
// condition B
component a3B = AND3();
a3B.in[0] <== spGteTwo;
a3B.in[1] <== threeBelowSP;
a3B.in[2] <== is_add + is_mul;
component or = OR();
or.a <== a3A.out;
or.b <== a3B.out;
out <== or.out;
}
template CopyStack(m) {
var nBits = 4;
signal output out[m];
signal input sp;
signal input is_add;
signal input is_mul;
signal input is_push;
signal input is_nop;
component ShouldCopys[m];
signal copy[m];
// loop over the columns
for (var i = 0; i < m; i++) {
ShouldCopys[i] = ShouldCopy(i, nBits);
ShouldCopys[i].sp <== sp;
ShouldCopys[i].is_add <== is_add;
ShouldCopys[i].is_mul <== is_mul;
ShouldCopys[i].is_push <== is_push;
ShouldCopys[i].is_nop <== is_nop;
out[i] <== ShouldCopys[i].out;
}
}
// n is how many instructions we can handle
// since all the instructions might be push,
// our stack needs capacity of up to n
template ZKVM(n) {
var NOP = 0;
var PUSH = 1;
var ADD = 2;
var MUL = 3;
signal input instr[2 * n];
// we add one extra row for sp because
// our algorithm always writes to the
// next row and we don't want to conditionally
// check for an array-out-of-bounds
signal output sp[n + 1];
signal output stack[n][n];
var IS_NOP = 0;
var IS_PUSH = 1;
var IS_ADD = 2;
var IS_MUL = 3;
var ARG = 4;
signal metaTable[n][5];
// first instruction must be PUSH or NOP
(instr[0] - PUSH) * (instr[0] - NOP) === 0;
signal first_op_is_push;
first_op_is_push <== IsEqual()([instr[0], PUSH]);
// if the first op is NOP, we are forcing the first
// value to be zero, but this is where the stack
// pointer is, so it doesn't matter
stack[0][0] <== first_op_is_push * instr[1];
// initialize the rest of the first stack to be zero
for (var i = 1; i < n; i++) {
stack[0][i] <== 0;
}
// we fill out the 0th elements to avoid
// uninitialzed signals
sp[0] <== 0;
sp[1] <== first_op_is_push;
metaTable[0][IS_PUSH] <== first_op_is_push;
metaTable[0][IS_NOP] <== 1 - first_op_is_push;
metaTable[0][IS_ADD] <== 0;
metaTable[0][IS_MUL] <== 0;
metaTable[0][ARG] <== instr[1];
// spBranch is what we add to the previous stack pointer
// based on the opcode. Could be 1, 0, or -1 depending on the
// opcode. Since the first opcode cannot be POP, -1 is not
// an option here.
var SAME = 0;
var INC = 1;
var DEC = 2;
signal spBranch[n][3];
spBranch[0][INC] <== first_op_is_push * 1;
spBranch[0][SAME] <== (1 - first_op_is_push) * 0;
spBranch[0][DEC] <== 0;
// populate the metaTable and the stack pointer
component EqPush[n];
component EqNop[n];
component EqAdd[n];
component EqMul[n];
component eqSP[n][n];
signal eqSPAndIsPush[n][n];
for (var i = 0; i < n; i++) {
eqSPAndIsPush[0][i] <== 0;
}
// signals and components for copying
component CopyStack[n];
signal previousCellIfShouldCopy[n][n];
for (var i = 0; i < n; i++) {
previousCellIfShouldCopy[0][i] <== 0;
}
component eqSPMinus2[n][n];
signal eqSPMinus2AndIsAdd[n][n];
signal eqSPMinus2AndIsMul[n][n];
for (var i = 0; i < n; i++) {
eqSPMinus2AndIsAdd[0][i] <== 0;
eqSPMinus2AndIsMul[0][i] <== 0;
}
// (the current column = sp - 2 and is_add) * sum
signal eqSPMinus2AndIsAddWithValue[n][n];
signal eqSPMinus2AndIsMulWithValue[n][n];
signal sum_result[n][n];
signal mul_result[n][n];
for (var i = 0; i < n; i++) {
eqSPMinus2AndIsAddWithValue[0][i] <== 0;
eqSPMinus2AndIsMulWithValue[0][i] <== 0;
sum_result[0][i] <== 0;
mul_result[0][i] <== 0;
}
for (var i = 1; i < n; i++) {
// check which opcode we are executing
EqPush[i] = IsEqual();
EqPush[i].in[0] <== instr[2 * i];
EqPush[i].in[1] <== PUSH;
metaTable[i][IS_PUSH] <== EqPush[i].out;
EqNop[i] = IsEqual();
EqNop[i].in[0] <== instr[2 * i];
EqNop[i].in[1] <== NOP;
metaTable[i][IS_NOP] <== EqNop[i].out;
EqAdd[i] = IsEqual();
EqAdd[i].in[0] <== instr[2 * i];
EqAdd[i].in[1] <== ADD;
metaTable[i][IS_ADD] <== EqAdd[i].out;
EqMul[i] = IsEqual();
EqMul[i].in[0] <== instr[2 * i];
EqMul[i].in[1] <== MUL;
metaTable[i][IS_MUL] <== EqMul[i].out;
// carry out the sums and muls
for (var j = 0; j < n - 1; j++) {
sum_result[i][j] <== stack[i - 1][j] + stack[i - 1][j + 1];
mul_result[i][j] <== stack[i - 1][j] * stack[i - 1][j + 1];
}
// these values cannot be used in practice because
// the stack doesn't go that high.
// However, we still need to initialize
// them because every column checks
// if it is sp - 1, even the last 2
for (var j = n - 1; j < n; j++) {
sum_result[i][j] <== 0;
mul_result[i][j] <== 0;
}
// get the instruction argument
metaTable[i][ARG] <== instr[2 * i + 1];
// if it is a push, write to the stack
// if it is a copy, write to the stack
CopyStack[i] = CopyStack(n);
CopyStack[i].sp <== sp[i];
CopyStack[i].is_push <== metaTable[i][IS_PUSH];
CopyStack[i].is_nop <== metaTable[i][IS_NOP];
CopyStack[i].is_add <== metaTable[i][IS_ADD];
CopyStack[i].is_mul <== metaTable[i][IS_MUL];
for (var j = 0; j < n; j++) {
previousCellIfShouldCopy[i][j] <== CopyStack[i].out[j] * stack[i - 1][j];
eqSP[i][j] = IsEqual();
eqSP[i][j].in[0] <== j;
eqSP[i][j].in[1] <== sp[i];
eqSPAndIsPush[i][j] <== eqSP[i][j].out * metaTable[i][IS_PUSH];
// check if the column is two less
// than the stack pointer
// if so, we prepare to write the sum or
// product here
// if the current instruction is add or mul
eqSPMinus2[i][j] = IsEqual();
eqSPMinus2[i][j].in[0] <== j;
eqSPMinus2[i][j].in[1] <== sp[i] - 2; // underflow doesn't matter
eqSPMinus2AndIsAdd[i][j] <== eqSPMinus2[i][j].out * metaTable[i][IS_ADD];
eqSPMinus2AndIsMul[i][j] <== eqSPMinus2[i][j].out * metaTable[i][IS_MUL];
eqSPMinus2AndIsAddWithValue[i][j] <== eqSPMinus2AndIsAdd[i][j] * sum_result[i][j];
eqSPMinus2AndIsMulWithValue[i][j] <== eqSPMinus2AndIsMul[i][j] * mul_result[i][j];
// we will either
// - PUSH
// - COPY or implicilty assign 0
// - ADD
// - MUL
stack[i][j] <== eqSPAndIsPush[i][j] * metaTable[i][ARG] + previousCellIfShouldCopy[i][j] + eqSPMinus2AndIsAddWithValue[i][j] + eqSPMinus2AndIsMulWithValue[i][j];
}
// write to the next row's stack pointer
spBranch[i][INC] <== metaTable[i][IS_PUSH] * (sp[i] + 1);
spBranch[i][SAME] <== metaTable[i][IS_NOP] * (sp[i]);
spBranch[i][DEC] <== (metaTable[i][IS_ADD] + metaTable[i][IS_MUL]) * (sp[i] - 1);
sp[i + 1] <== spBranch[i][INC] + spBranch[i][SAME] + spBranch[i][DEC];
}
}
component main = ZKVM(5);
/* INPUT = {
"instr": [1,3,1,6,1,2,3,0,3,0]
} */