11include "precomputed.pil";
22
3- // Decomposes a field in radixes
4- // Roughly, what we do is keep track of an accumulator where we reconstruct value using the limbs. We can end when the accumulator is equal to the value (found).
5- // For overflow protection, we compare the limbs against the p limbs for the given radix. If we reach the last limb (is_unsafe) we assert that we are under p.
6- // Overflow in the accumulator can only happen in the unsafe limb. After the unsafe limb we have padding limbs, that we assert to be zero. This is used
7- // because users might want to decompose in more limbs that strictly needed for the radix.
8- // The following table is non-exhaustive, we do have some extra columns to compute these values.
9- // +-------+-------+------+--------+------------+------+-------------+----------+------------------+-------+------------+-----------+-------+-----+-----+---------+
10- // | value | radix | limb | p_limb | limb_index | acc | acc_under_p | exponent | not_padding_limb | found | safe_limbs | is_unsafe | start | end | sel | not_end |
11- // +-------+-------+------+--------+------------+------+-------------+----------+------------------+-------+------------+-----------+-------+-----+-----+---------+
12- // | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
13- // | 1337 | 10 | 7 | 7 | 0 | 7 | 0 | 1 | 1 | 0 | 76 | 0 | 1 | 0 | 1 | 1 |
14- // | 1337 | 10 | 3 | 1 | 1 | 37 | 0 | 10 | 1 | 0 | 76 | 0 | 0 | 0 | 1 | 1 |
15- // | 1337 | 10 | 3 | 6 | 2 | 337 | 1 | 100 | 1 | 0 | 76 | 0 | 0 | 0 | 1 | 1 |
16- // | 1337 | 10 | 1 | 5 | 3 | 1337 | 1 | 1000 | 1 | 1 | 76 | 0 | 0 | 0 | 1 | 1 |
17- // | 1337 | 10 | 0 | 9 | 4 | 1337 | 1 | 10000 | 1 | 1 | 76 | 0 | 0 | 0 | 1 | 1 |
18- // | 1337 | 10 | 0 | 2 | 76 | 1337 | 1 | 10**76 | 1 | 1 | 76 | 1 | 0 | 0 | 1 | 1 |
19- // | 1337 | 10 | 0 | 0 | 77 | 1337 | 0 | 0 | 0 | 1 | 76 | 0 | 0 | 1 | 1 | 0 |
20- // | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
21- // +-------+-------+------+--------+------------+------+-------------+----------+------------------+-------+------------+-----------+-------+-----+-----+---------+
3+ /*
4+ * Decomposes a field in radixes
5+ * Roughly, what we do is keep track of an accumulator where we reconstruct value using the limbs. We can end when the accumulator is equal to the value (found).
6+ * For overflow protection, we compare the limbs against the p limbs for the given radix. If we reach the last limb (is_unsafe) we assert that we are under p.
7+ * Overflow in the accumulator can only happen in the unsafe limb. After the unsafe limb we have padding limbs, that we assert to be zero. This is used
8+ * because users might want to decompose in more limbs that strictly needed for the radix. We also assert that the decomposition is complete, i.e., that the value
9+ * has been found. In other words, truncation error is not supported in this trace.
10+ *
11+ *
12+ * Usage:
13+ * sel_XX { value, limb_index, radix, limb, value_found
14+ * } in to_radix.sel {
15+ * to_radix.value, to_radix.limb_index, to_radix.radix, to_radix.limb, to_radix.found
16+ * };
17+ *
18+ * Optionally, column `value_found` can be omitted provided that the caller ensures that the decomposition is complete or
19+ * does not care about truncation error.
20+ *
21+ * Preconditions: 'radix' is in range [2, 256]. This trace cannot guarantee correctness if 'radix' is not in this range.
22+ *
23+ * Inputs:
24+ * - value: The value to decompose in little endian.
25+ * - limb_index: The index of the limb to lookup in little endian.
26+ * - radix: The radix to decompose the value in. Must be in range [2, 256].
27+ * Outputs:
28+ * - limb: The limb to lookup in little endian.
29+ * - value_found: Whether the value has been found, i.e., the decomposition is complete with the limbs from index 0 to index `limb_index`.
30+ *
31+ * The following table is non-exhaustive, we do have some extra columns to compute these values.
32+ * +-------+-------+------+--------+------------+------+-------------+----------+------------------+-------+------------+-----------+-------+-----+-----+---------+
33+ * | value | radix | limb | p_limb | limb_index | acc | acc_under_p | power | not_padding_limb | found | safe_limbs | is_unsafe | start | end | sel | NOT_END |
34+ * +-------+-------+------+--------+------------+------+-------------+----------+------------------+-------+------------+-----------+-------+-----+-----+---------+
35+ * | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
36+ * | 1337 | 10 | 7 | 7 | 0 | 7 | 0 | 1 | 1 | 0 | 76 | 0 | 1 | 0 | 1 | 1 |
37+ * | 1337 | 10 | 3 | 1 | 1 | 37 | 0 | 10 | 1 | 0 | 76 | 0 | 0 | 0 | 1 | 1 |
38+ * | 1337 | 10 | 3 | 6 | 2 | 337 | 1 | 100 | 1 | 0 | 76 | 0 | 0 | 0 | 1 | 1 |
39+ * | 1337 | 10 | 1 | 5 | 3 | 1337 | 1 | 1000 | 1 | 1 | 76 | 0 | 0 | 0 | 1 | 1 |
40+ * | 1337 | 10 | 0 | 9 | 4 | 1337 | 1 | 10000 | 1 | 1 | 76 | 0 | 0 | 0 | 1 | 1 |
41+ * .........................................
42+ * | 1337 | 10 | 0 | 2 | 76 | 1337 | 1 | 10**76 | 1 | 1 | 76 | 1 | 0 | 0 | 1 | 1 |
43+ * | 1337 | 10 | 0 | 0 | 77 | 1337 | 1 | 0 | 0 | 1 | 76 | 0 | 0 | 1 | 1 | 0 |
44+ * | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
45+ * +-------+-------+------+--------+------------+------+-------------+----------+------------------+-------+------------+-----------+-------+-----+-----+---------+
46+ *
47+ * Each computation is performed over a contiguous block of rows. The block is determined by the `start` and `end` selectors.
48+ * The number of rows in a computation block is at least the number of limbs for a complete decomposition. Tracegen determines
49+ * the number of "padding" rows for the extra high limbs depending on the need of the subtraces invoking this gadget. Note that
50+ * it does not pose a soundness isssue because this trace ensures that a present row is correct. If a row is not present, then
51+ * we have a completeness issue because the lookup will fail.
52+ */
2253namespace to_radix;
2354
2455 #[skippable_if]
@@ -29,73 +60,66 @@ namespace to_radix;
2960
3061 // Inputs to to_radix
3162 pol commit value;
32- pol commit radix; // Must be 256 >= radix >= 2
63+ pol commit radix; // Must be in range [2, 256] as a precondition.
3364 pol commit limb_index;
3465
3566 // Outputs of to_radix
3667 pol commit limb;
68+ pol commit found;
69+ found * (1 - found) = 0;
3770
38-
39- // LIFECYCLE
40-
71+ // ==== Multi-row Computation Selectors ====
72+ // See recipe in https://hackmd.io/moq6viBpRJeLpWrHAogCZw?view#Contiguous-Multi-Rows-Computation-Trace
4173 pol commit start;
4274 start * (1 - start) = 0;
43-
4475 pol commit end;
4576 end * (1 - end) = 0;
4677
47- // end and first_row are NAND
48- end * precomputed.first_row = 0;
4978 pol LATCH_CONDITION = end + precomputed.first_row;
5079
51- #[START_AFTER_LATCH ]
52- sel' * (start' - LATCH_CONDITION ) = 0;
80+ #[SEL_ON_START_OR_END ]
81+ (start + end) * (1 - sel ) = 0;
5382
54- // Selector must be 1 in a start row
55- #[SELECTOR_ON_START]
56- start * (1 - sel) = 0;
57- // Next selector must be current selector unless LATCH_CONDITION
58- #[SELECTOR_CONSISTENCY]
59- (sel' - sel) * (1 - LATCH_CONDITION) = 0;
60- // Selector must be 1 in an end row
61- end * (1 - sel) = 0;
83+ #[TRACE_CONTINUITY]
84+ (1 - LATCH_CONDITION) * (sel - sel') = 0;
6285
63- // Commited to reduce the degree of expressions
64- pol commit not_end;
65- sel * (1 - end) - not_end = 0;
86+ #[START_AFTER_LATCH]
87+ sel' * (start' - LATCH_CONDITION) = 0;
6688
89+ // `sel * (1 - end) = sel - end * sel` and since `end == 1 ==> sel == 1` we have `end * sel = end`.
90+ pol NOT_END = sel - end;
6791
68- // EXPONENTIATION
92+ // ===== RADIX POWERS COMPUTATION =====
6993
70- pol commit exponent ;
71- // We commit not_padding_limb instead of the more natural padding_limb because it's going to be used in a lookup
94+ pol commit power ;
95+ // We commit not_padding_limb instead of the more natural padding_limb because it's going to be used as a lookup selector.
7296 pol commit not_padding_limb;
7397 not_padding_limb * (1 - not_padding_limb) = 0;
7498
75- // exponent starts at 1
76- start * (exponent - 1) = 0;
77- // next exponent is current exponent * radix unless it's a padding limb
78- not_end * not_padding_limb' * (exponent * radix - exponent') = 0;
7999 // not_padding_limb starts at 1
80100 start * (1 - not_padding_limb) = 0;
81- // next not not_padding_limb is current not_padding_limb unless current limb is unsafe, where it changes to 0
82- not_end * ((0 - not_padding_limb) * is_unsafe_limb + not_padding_limb - not_padding_limb') = 0;
101+ // next `not_padding_limb` is current `not_padding_limb` unless current limb is unsafe, where it changes to 0
102+ // Note that unsafe_limbs is defined and constrained below.
103+ NOT_END * ((0 - not_padding_limb) * is_unsafe_limb + not_padding_limb - not_padding_limb') = 0;
83104
84- // Padding limbs have zero exponent
85- (1 - not_padding_limb) * exponent = 0;
105+ // power starts at 1
106+ start * (power - 1) = 0;
107+ // next power is current power * radix unless it's a padding limb
108+ NOT_END * not_padding_limb' * (power * radix - power') = 0;
86109
110+ // Padding limbs have zero power
111+ (1 - not_padding_limb) * power = 0;
87112
88- // ACCUMULATION
113+
114+ // ===== ACCUMULATION =====
89115
90116 pol commit acc;
91- pol commit found;
92- found * (1 - found) = 0;
93117
94118 // Limb index starts at zero
95119 start * (limb_index - 0) = 0;
96120
97121 // Limb index must increase
98- not_end * (limb_index + 1 - limb_index') = 0;
122+ NOT_END * (limb_index + 1 - limb_index') = 0;
99123
100124 // Range check limb so we can safely assert that it's less than radix.
101125 #[LIMB_RANGE]
@@ -106,7 +130,7 @@ namespace to_radix;
106130
107131 // Limb should be less than radix
108132 pol commit limb_radix_diff;
109- sel * (radix - 1 - limb - limb_radix_diff) = 0;
133+ sel * (radix - limb - 1 - limb_radix_diff) = 0;
110134
111135 #[LIMB_LESS_THAN_RADIX_RANGE]
112136 sel { limb_radix_diff }
@@ -117,22 +141,26 @@ namespace to_radix;
117141
118142 // On start, current acc must be equal to limb
119143 start * (acc - limb) = 0;
120- // Next acc must be current acc + next_exponent *next_limb
121- not_end * (acc + exponent ' * limb' - acc') = 0;
144+ // Next acc must be current acc + next_power *next_limb
145+ NOT_END * (acc + power ' * limb' - acc') = 0;
122146
123147 // found is 1 when value - acc = 0
124148 pol REM = value - acc;
125149 pol commit rem_inverse;
126- sel * (REM * (found * (1 - rem_inverse) + rem_inverse) - 1 + found) = 0;
150+ sel * (REM * (found * (1 - rem_inverse) + rem_inverse) - 1 + found) = 0; // @zero-check
127151
128152 // when found is 1, next limb is 0
129- not_end * found * limb' = 0;
153+ NOT_END * found * limb' = 0;
154+
155+ // As a consequence, when found is 1, the next acc value must be the same and
156+ // the next found value must be 1 as well. In this way, `found == 1` is propagated
157+ // until the end of the trace.
130158
131159 // We can only enable end when found is one
132160 (1 - found) * end = 0;
133161
134162
135- // OVERFLOW PROTECTION
163+ // ===== UNSAFE LIMBS COMPUTATION =====
136164
137165 pol commit safe_limbs;
138166
@@ -145,15 +173,13 @@ namespace to_radix;
145173 pol commit is_unsafe_limb;
146174 is_unsafe_limb * (1 - is_unsafe_limb) = 0;
147175
148- // If is_padding_limb, limb is zero
149- (1 - not_padding_limb) * limb = 0;
150- // If is_padding_limb, p_limb is zero
151- (1 - not_padding_limb) * p_limb = 0;
152-
153- // is_unsafe_limb is on when and only when limb_index == safe_limbs
154- pol safety_diff = limb_index - safe_limbs;
176+ // `is_unsafe_limb == 1` iff `limb_index == safe_limbs`
177+ pol SAFETY_DIFF = limb_index - safe_limbs;
155178 pol commit safety_diff_inverse;
156- sel * (safety_diff * (is_unsafe_limb * (1 - safety_diff_inverse) + safety_diff_inverse) - 1 + is_unsafe_limb) = 0;
179+ // @zero-check
180+ sel * (SAFETY_DIFF * (is_unsafe_limb * (1 - safety_diff_inverse) + safety_diff_inverse) - 1 + is_unsafe_limb) = 0;
181+
182+ // ===== FIELD OVERFLOW PROTECTION =====
157183
158184 // The limb of the modulus p decomposed by radix at this limb_index
159185 pol commit p_limb;
@@ -164,11 +190,16 @@ namespace to_radix;
164190 precomputed.sel_p_decomposition
165191 { precomputed.p_decomposition_radix, precomputed.p_decomposition_limb_index, precomputed.p_decomposition_limb };
166192
193+ // If is_padding_limb, limb is zero
194+ (1 - not_padding_limb) * limb = 0;
195+ // If is_padding_limb, p_limb is zero
196+ (1 - not_padding_limb) * p_limb = 0;
197+
167198 // We carry wether the accumulator is under p. If we reach the unsafe limb, we need to ensure that this is true.
168199 pol commit acc_under_p;
169200 acc_under_p * (1 - acc_under_p) = 0;
170201
171- // 1 when lt, 0 when gt or equal
202+ // 1 iff `limb < p_limb`
172203 pol commit limb_lt_p;
173204 limb_lt_p * (1 - limb_lt_p) = 0;
174205
@@ -206,20 +237,20 @@ namespace to_radix;
206237 // acc_under_p is on start is equal to limb_lt_p
207238 start * (acc_under_p - limb_lt_p) = 0;
208239 // acc_under_p' = limb_eq_p' ? acc_under_p : limb_lt_p'
209- not_end * ((acc_under_p - limb_lt_p') * limb_eq_p' + limb_lt_p' - acc_under_p') = 0;
240+ NOT_END * ((acc_under_p - limb_lt_p') * limb_eq_p' + limb_lt_p' - acc_under_p') = 0;
210241
211242 // On the the unsafe limb, we must be under p
212243 #[OVERFLOW_CHECK]
213244 is_unsafe_limb * (1 - acc_under_p) = 0;
214245
215246
216- // CONSTANT CONSISTENCY
247+ // ===== CONSTANT CONTINUITY =====
217248
218- #[CONSTANT_CONSISTENCY_RADIX ]
219- not_end * (radix - radix') = 0;
249+ #[RADIX_CONTINUITY ]
250+ NOT_END * (radix - radix') = 0;
220251
221- #[CONSTANT_CONSISTENCY_VALUE ]
222- not_end * (value - value') = 0;
252+ #[VALUE_CONTINUITY ]
253+ NOT_END * (value - value') = 0;
223254
224- #[CONSTANT_CONSISTENCY_SAFE_LIMBS ]
225- not_end * (safe_limbs - safe_limbs') = 0;
255+ #[SAFE_LIMBS_CONTINUITY ]
256+ NOT_END * (safe_limbs - safe_limbs') = 0;
0 commit comments