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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
use vm_core::{Felt, Operation, ONE, ZERO};
use crate::{ExecutionError, Host, Process, QuadFelt};
// RANDOM LINEAR COMBINATION OPERATIONS
// ================================================================================================
impl<H> Process<H>
where
H: Host,
{
// COMBINE VALUES USING RANDOMNESS
// --------------------------------------------------------------------------------------------
/// Performs a single step in the computation of the random linear combination:
///
/// \sum_{i=0}^k{\alpha_i \cdot \left(\frac{T_i(x) - T_i(z)}{x - z} +
/// \frac{T_i(x) - T_i(g \cdot z)}{x - g \cdot z} \right)}
///
/// The instruction computes the numerators $\alpha_i \cdot (T_i(x) - T_i(z))$ and
/// $\alpha_i \cdot (T_i(x) - T_i(g \cdot z))$ and stores the values in two accumulators $p$
/// and $r$, respectively. This instruction is specialized to main trace columns i.e.
/// the values $T_i(x)$ are base field elements.
///
/// The instruction is used in the context of STARK proof verification in order to compute
/// the queries of the DEEP composition polynomial for FRI. It works in combination with
/// the `mem_stream` instruction where it is called 8 times in a row for each call to
/// `mem_stream`.
///
/// The stack transition of the instruction can be visualized as follows:
///
/// Input:
///
/// +------+------+------+------+------+------+------+------+------+------+------+------+------+------+------+---+
/// | T7 | T6 | T5 | T4 | T3 | T2 | T1 | T0 | p1 | p0 | r1 | r0 |x_addr|z_addr|a_addr| - |
/// +------+------+------+------+------+------+------+------+------+------+------+------+------+------+------+---+
///
///
/// Output:
///
/// +------+------+------+------+------+------+------+------+------+------+------+------+------+--------+--------+---+
/// | T0 | T7 | T6 | T5 | T4 | T3 | T2 | T1 | p1' | p0' | r1' | r0' |x_addr|z_addr+1|a_addr+1| - |
/// +------+------+------+------+------+------+------+------+------+------+------+------+------+--------+--------+---+
///
///
/// Here:
///
/// 1. Ti for i in 0..=7 stands for the the value of the i-th trace polynomial for the current
/// query i.e. T_i(x).
/// 2. (p0, p1) stands for an extension field element accumulating the values for the quotients
/// with common denominator (x - z).
/// 3. (r0, r1) stands for an extension field element accumulating the values for the quotients
/// with common denominator (x - gz).
/// 4. x_addr is the memory address from which we are loading the Ti's using the MSTREAM
/// instruction.
/// 5. z_addr is the memory address to the i-th OOD evaluations at z and gz
/// i.e. T_i(z):= (T_i(z)0, T_i(z)1) and T_i(gz):= (T_i(gz)0, T_i(gz)1).
/// 6. a_addr is the memory address of the i-th random element alpha_i used in batching
/// the trace polynomial quotients.
///
/// The instruction also makes use of the helper registers to hold the values of T_i(z), T_i(gz)
/// and alpha_i during the course of its execution.
pub(super) fn op_rcomb_base(&mut self) -> Result<(), ExecutionError> {
// --- read the T_i(x) value from the stack -----------------------------------------------
let [t7, t6, t5, t4, t3, t2, t1, t0] = self.get_trace_values();
// --- read the randomness from memory ----------------------------------------------------
let alpha = self.get_randomness();
// --- read the OOD values from memory ----------------------------------------------------
let [tz, tgz] = self.get_ood_values();
// --- read the accumulator values from stack ---------------------------------------------
let [p, r] = self.read_accumulators();
// --- compute the updated accumulator values ---------------------------------------------
let v0 = self.stack.get(7);
let tx = QuadFelt::new(v0, ZERO);
let [p_new, r_new] = [p + alpha * (tx - tz), r + alpha * (tx - tgz)];
// --- rotate the top 8 elements of the stack ---------------------------------------------
self.stack.set(0, t0);
self.stack.set(1, t7);
self.stack.set(2, t6);
self.stack.set(3, t5);
self.stack.set(4, t4);
self.stack.set(5, t3);
self.stack.set(6, t2);
self.stack.set(7, t1);
// --- update the accumulators ------------------------------------------------------------
self.stack.set(8, p_new.to_base_elements()[1]);
self.stack.set(9, p_new.to_base_elements()[0]);
self.stack.set(10, r_new.to_base_elements()[1]);
self.stack.set(11, r_new.to_base_elements()[0]);
// --- update the memory pointers ---------------------------------------------------------
self.stack.set(12, self.stack.get(12));
self.stack.set(13, self.stack.get(13) + ONE);
self.stack.set(14, self.stack.get(14) + ONE);
// --- copy the rest of the stack ---------------------------------------------------------
self.stack.copy_state(15);
// --- set the helper registers -----------------------------------------------------------
self.set_helper_reg(alpha, tz, tgz);
Ok(())
}
//// HELPER METHODS
//// ------------------------------------------------------------------------------------------
/// Returns the top 8 elements of the operand stack.
fn get_trace_values(&self) -> [Felt; 8] {
let v7 = self.stack.get(0);
let v6 = self.stack.get(1);
let v5 = self.stack.get(2);
let v4 = self.stack.get(3);
let v3 = self.stack.get(4);
let v2 = self.stack.get(5);
let v1 = self.stack.get(6);
let v0 = self.stack.get(7);
[v7, v6, v5, v4, v3, v2, v1, v0]
}
/// Returns randomness.
fn get_randomness(&mut self) -> QuadFelt {
let ctx = self.system.ctx();
let addr = self.stack.get(14);
let word = self.chiplets.read_mem(ctx, addr.as_int() as u32);
let a0 = word[0];
let a1 = word[1];
QuadFelt::new(a0, a1)
}
/// Returns the OOD values.
fn get_ood_values(&mut self) -> [QuadFelt; 2] {
let ctx = self.system.ctx();
let addr = self.stack.get(13);
let word = self.chiplets.read_mem(ctx, addr.as_int() as u32);
[QuadFelt::new(word[0], word[1]), QuadFelt::new(word[2], word[3])]
}
/// Reads the accumulator values.
fn read_accumulators(&self) -> [QuadFelt; 2] {
let p1 = self.stack.get(8);
let p0 = self.stack.get(9);
let p = QuadFelt::new(p0, p1);
let r1 = self.stack.get(10);
let r0 = self.stack.get(11);
let r = QuadFelt::new(r0, r1);
[p, r]
}
/// Populates helper registers with OOD values and randomness.
fn set_helper_reg(&mut self, alpha: QuadFelt, tz: QuadFelt, tgz: QuadFelt) {
let [a0, a1] = alpha.to_base_elements();
let [tz0, tz1] = tz.to_base_elements();
let [tgz0, tgz1] = tgz.to_base_elements();
let values = [tz0, tz1, tgz0, tgz1, a0, a1];
self.decoder.set_user_op_helpers(Operation::RCombBase, &values);
}
}
// TESTS
// ================================================================================================
#[cfg(test)]
mod tests {
use crate::{ContextId, Process, QuadFelt};
use alloc::borrow::ToOwned;
use alloc::vec::Vec;
use test_utils::{build_test, rand::rand_array};
use vm_core::{Felt, FieldElement, Operation, StackInputs, ONE, ZERO};
#[test]
fn rcombine_main() {
// --- build stack inputs -----------------------------------------------------------------
let mut inputs = rand_array::<Felt, 16>();
// set x_addr to
inputs[12] = Felt::ZERO;
// set z_addr pointer to x_addr + offset, where offset is a large enough value.
let offset = Felt::new(1000);
inputs[13] = inputs[12] + offset;
// set a_addr to z_addr + offset
inputs[14] = inputs[13] + offset;
inputs[15] = ZERO;
inputs.reverse();
// --- setup the operand stack ------------------------------------------------------------
let stack_inputs = StackInputs::new(inputs.to_vec()).expect("inputs lenght too long");
let mut process = Process::new_dummy_with_decoder_helpers(stack_inputs);
// --- setup memory -----------------------------------------------------------------------
let ctx = ContextId::root();
let tztgz = rand_array::<Felt, 4>();
process.chiplets.write_mem(
ctx,
inputs[2].as_int().try_into().expect("Shouldn't fail by construction"),
tztgz,
);
let a = rand_array::<Felt, 4>();
process.chiplets.write_mem(
ctx,
inputs[1].as_int().try_into().expect("Shouldn't fail by construction"),
a,
);
// --- execute RCOMB1 operation -----------------------------------------------------------
process.execute_op(Operation::RCombBase).unwrap();
// --- check that the top 8 stack elements are correctly rotated --------------------------
let stack_state = process.stack.trace_state();
inputs.reverse();
assert_eq!(stack_state[1], inputs[0]);
assert_eq!(stack_state[2], inputs[1]);
assert_eq!(stack_state[3], inputs[2]);
assert_eq!(stack_state[4], inputs[3]);
assert_eq!(stack_state[5], inputs[4]);
assert_eq!(stack_state[6], inputs[5]);
assert_eq!(stack_state[7], inputs[6]);
assert_eq!(stack_state[0], inputs[7]);
// --- check that the accumulator was updated correctly -----------------------------------
let p1 = inputs[8];
let p0 = inputs[9];
let p = QuadFelt::new(p0, p1);
let r1 = inputs[10];
let r0 = inputs[11];
let r = QuadFelt::new(r0, r1);
let tz0 = tztgz[0];
let tz1 = tztgz[1];
let tz = QuadFelt::new(tz0, tz1);
let tgz0 = tztgz[2];
let tgz1 = tztgz[3];
let tgz = QuadFelt::new(tgz0, tgz1);
let tx = QuadFelt::new(inputs[7], ZERO);
let a0 = a[0];
let a1 = a[1];
let alpha = QuadFelt::new(a0, a1);
let p_new = p + alpha * (tx - tz);
let r_new = r + alpha * (tx - tgz);
assert_eq!(p_new.to_base_elements()[1], stack_state[8]);
assert_eq!(p_new.to_base_elements()[0], stack_state[9]);
assert_eq!(r_new.to_base_elements()[1], stack_state[10]);
assert_eq!(r_new.to_base_elements()[0], stack_state[11]);
// --- check that memory pointers were updated --------------------------------------------
assert_eq!(inputs[12], stack_state[12]);
assert_eq!(inputs[13] + ONE, stack_state[13]);
assert_eq!(inputs[14] + ONE, stack_state[14]);
// --- check that the helper registers were updated correctly -----------------------------
let helper_reg_expected = [tz0, tz1, tgz0, tgz1, a0, a1];
assert_eq!(helper_reg_expected, process.decoder.get_user_op_helpers());
}
#[test]
fn prove_verify() {
let source = " begin
# I) Prepare memory and stack
# 1) Load T_i(x) for i=0,..,7
push.0 padw
adv_pipe
# 2) Load [T_i(z), T_i(gz)] for i=0,..,7
repeat.4
adv_pipe
end
# 3) Load [a0, a1, 0, 0] for i=0,..,7
repeat.4
adv_pipe
end
# 4) Clean up stack
dropw dropw dropw drop
# 5) Prepare stack
## a) Push pointers
push.10 # a_ptr
push.2 # z_ptr
push.0 # x_ptr
## b) Push accumulators
padw
## c) Add padding for mem_stream
padw padw
# II) Execute `rcomb_base` op
mem_stream
repeat.8
rcomb_base
end
end
";
// generate the data
let tx: [Felt; 8] = rand_array();
let tz_tgz: [QuadFelt; 16] = rand_array();
let a: [QuadFelt; 8] = rand_array();
// compute the expected values of the accumulators
let mut p = QuadFelt::ZERO;
let mut r = QuadFelt::ZERO;
let tz: Vec<QuadFelt> = tz_tgz.iter().step_by(2).map(|e| e.to_owned()).collect();
let tgz: Vec<QuadFelt> = tz_tgz.iter().skip(1).step_by(2).map(|e| e.to_owned()).collect();
for i in 0..8 {
p += a[i] * (QuadFelt::from(tx[i]) - tz[i]);
r += a[i] * (QuadFelt::from(tx[i]) - tgz[i]);
}
// prepare the advice stack with the generated data
let mut adv_stack = Vec::new();
let tz_tgz: Vec<Felt> = tz_tgz.iter().flat_map(|e| e.to_base_elements()).collect();
let a: Vec<Felt> = a
.iter()
.flat_map(|e| {
let element = e.to_base_elements();
[element[0], element[1], ZERO, ZERO]
})
.collect();
adv_stack.extend_from_slice(&tx);
adv_stack.extend_from_slice(&tz_tgz);
adv_stack.extend_from_slice(&a);
let adv_stack: Vec<u64> = adv_stack.iter().map(|e| e.as_int()).collect();
// create the expected operand stack
let mut expected = Vec::new();
// updated pointers
expected.extend_from_slice(&[ZERO, Felt::from(18_u8), Felt::from(10_u8), Felt::from(2_u8)]);
// updated accumulators
expected.extend_from_slice(&[
r.to_base_elements()[0],
r.to_base_elements()[1],
p.to_base_elements()[0],
p.to_base_elements()[1],
]);
// the top 8 stack elements should equal tx since 8 calls to `rcomb_base` implies 8 circular
// shifts of the top 8 elements i.e., the identity map on the top 8 element.
expected.extend_from_slice(&tx);
let expected: Vec<u64> = expected.iter().rev().map(|e| e.as_int()).collect();
let test = build_test!(source, &[], &adv_stack);
test.expect_stack(&expected);
let pub_inputs: Vec<u64> = Vec::new();
test.prove_and_verify(pub_inputs, false);
}
}