Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
eccvm_circuit_builder.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: not started, auditors: [], date: YYYY-MM-DD }
3// external_1: { status: not started, auditors: [], date: YYYY-MM-DD }
4// external_2: { status: not started, auditors: [], date: YYYY-MM-DD }
5// =====================
6
7#pragma once
8
10#include "./msm_builder.hpp"
21
22namespace bb {
23
25 public:
29
30 using CycleScalar = typename CycleGroup::Fr;
31 using Element = typename CycleGroup::element;
33
34 static constexpr size_t NUM_SCALAR_BITS = bb::eccvm::NUM_SCALAR_BITS;
35 static constexpr size_t NUM_WNAF_DIGIT_BITS = bb::eccvm::NUM_WNAF_DIGIT_BITS;
36 static constexpr size_t NUM_WNAF_DIGITS_PER_SCALAR = bb::eccvm::NUM_WNAF_DIGITS_PER_SCALAR;
37 static constexpr uint64_t WNAF_MASK = bb::eccvm::WNAF_MASK;
38 static constexpr size_t POINT_TABLE_SIZE = bb::eccvm::POINT_TABLE_SIZE;
39 static constexpr size_t WNAF_DIGITS_PER_ROW = bb::eccvm::WNAF_DIGITS_PER_ROW;
40 static constexpr size_t ADDITIONS_PER_ROW = bb::eccvm::ADDITIONS_PER_ROW;
41
43 // `ScalarMul` represents a single scalar multiplication, i.e., a pair of a scalar and point on the curve,
44 // which will eventually be multiplied and accumulated.
46 // `MSM` is an ordered container of `ScalarMul`s
50
51 [[nodiscard]] uint32_t get_number_of_muls() const { return op_queue->get_number_of_muls(); }
52
54 {
55 const uint32_t num_muls = get_number_of_muls();
56
57 // `compute_precomputed_table` and `compute_wnaf_digits` are helper functions that will be used when we
58 // populate our vector of MSMs.
59
64 const auto compute_precomputed_table =
66 const auto d2 = Element(base_point).dbl();
68 table[POINT_TABLE_SIZE] = d2; // need this for later
69 table[POINT_TABLE_SIZE / 2] = base_point;
70 for (size_t i = 1; i < POINT_TABLE_SIZE / 2; ++i) {
71 table[i + POINT_TABLE_SIZE / 2] = Element(table[i + POINT_TABLE_SIZE / 2 - 1]) + d2;
72 }
73 for (size_t i = 0; i < POINT_TABLE_SIZE / 2; ++i) {
74 table[i] = -table[POINT_TABLE_SIZE - 1 - i];
75 }
76
77 Element::batch_normalize(&table[0], POINT_TABLE_SIZE + 1);
79 for (size_t i = 0; i < POINT_TABLE_SIZE + 1; ++i) {
80 result[i] = AffineElement(table[i].x, table[i].y);
81 }
82 return result;
83 };
93 const auto compute_wnaf_digits = [](uint256_t scalar) -> std::array<int, NUM_WNAF_DIGITS_PER_SCALAR> {
94 std::array<int, NUM_WNAF_DIGITS_PER_SCALAR> output;
95 int previous_slice = 0;
96 for (size_t i = 0; i < NUM_WNAF_DIGITS_PER_SCALAR; ++i) {
97 // slice the scalar into 4-bit chunks, starting with the least significant bits
98 uint64_t raw_slice = static_cast<uint64_t>(scalar) & WNAF_MASK;
99
100 bool is_even = ((raw_slice & 1ULL) == 0ULL);
101
102 int wnaf_slice = static_cast<int>(raw_slice);
103
104 if (i == 0 && is_even) {
105 // if least significant slice is even, we add 1 to create an odd value && set 'skew' to true
106 wnaf_slice += 1;
107 } else if (is_even) {
108 // for other slices, if it's even, we add 1 to the slice value, again to create an odd value,
109 // and subtract 16 from the previous slice to preserve the total scalar sum
110 static constexpr int borrow_constant = static_cast<int>(1ULL << NUM_WNAF_DIGIT_BITS);
111 previous_slice -= borrow_constant;
112 wnaf_slice += 1;
113 }
114
115 if (i > 0) {
116 const size_t idx = i - 1;
117 output[NUM_WNAF_DIGITS_PER_SCALAR - idx - 1] = previous_slice;
118 }
119 previous_slice = wnaf_slice;
120
121 // downshift raw_slice by 4 bits
122 scalar = scalar >> NUM_WNAF_DIGIT_BITS;
123 }
124
125 BB_ASSERT_EQ(scalar, 0U);
126
127 output[0] = previous_slice;
128
129 return output;
130 };
131 // the variables and vectors here correspond to the EC ops that we will actually do; in particular, we have
132 // compilation skipping logic for both when the scalar is 0 and when the EC point is the point-at-infinity, as
133 // terms of each of these types do not contribute to the final sum.
134
135 // more precisely, we will break up our op_queue into a sequence of MSMs, where we throw away computations that
136 // obviously don't contribute to the final desired value.
137 size_t msm_count = 0; // total number of MSMs
138 size_t active_mul_count =
139 0; // number of scalar multiplications required in the current MSM. Given a scalar n in F_q and a point P,
140 // we in general get *two* scalar multiplications, as we break up n into 128-bit chunks (using the extra
141 // endomorphism). this is an optimization.
142 std::vector<size_t>
143 msm_opqueue_index; // a vector recording which op from the op_queue we are performing in our VM.
145 msm_mul_index; // recording pairs, where the first element specifies "which MSM are we in" (via an index)
146 // and the second element specifies "which scalar multiplication is this in our VM simulation
147 // of this MSM". note that the second element, the `active_mul_count`, incorporates some
148 // skipping logic: what contributes to it are multiplications we actually need to perform.
149 // generically each scalar multiplication contributes to 2 VM mul operations, as we
150 // split up each Fq element into 2 128-bit elements.
151 std::vector<size_t> msm_sizes;
152
153 const auto& eccvm_ops = op_queue->get_eccvm_ops();
154 size_t op_idx = 0;
155 // populate opqueue and mul indices
156 for (const auto& op : eccvm_ops) {
157 if (op.op_code.mul) {
158 if ((op.z1 != 0 || op.z2 != 0) && !op.base_point.is_point_at_infinity()) {
159 msm_opqueue_index.push_back(op_idx);
160 msm_mul_index.emplace_back(msm_count, active_mul_count);
161 active_mul_count += static_cast<size_t>(op.z1 != 0) + static_cast<size_t>(op.z2 != 0);
162 }
163 } else if (active_mul_count > 0) {
164 msm_sizes.push_back(active_mul_count);
165 msm_count++;
166 active_mul_count = 0;
167 }
168 op_idx++;
169 }
170 // if last op is a mul we have not correctly computed the total number of msms
171 if (!eccvm_ops.empty() && eccvm_ops.back().op_code.mul && active_mul_count > 0) {
172 msm_sizes.push_back(active_mul_count);
173 msm_count++;
174 }
175
176 std::vector<MSM> result(
177 msm_count); // the vector we will return, containing all of the MSMs that our VM will have to perform.
178 // this amounts to breaking up our op-queue, splitting the elmenets of Fq into two 128
179 // bit scalars, and throwing out operations that a priori won't contribute.
180 for (size_t i = 0; i < msm_count; ++i) {
181 auto& msm = result[i];
182 msm.resize(msm_sizes[i]);
183 }
184 // populate result using the auxiliary vectors `msm_opqueue_index` and `msm_mul_index`, together with
185 // `eccvm_ops`. this first pass will *not* get the pc (program counter) correct. we explain why when we set it
186 // correctly.
187 parallel_for_range(msm_opqueue_index.size(), [&](size_t start, size_t end) {
188 for (size_t i = start; i < end; i++) {
189 const auto& op = eccvm_ops[msm_opqueue_index[i]];
190 auto [msm_index, mul_index] = msm_mul_index[i];
191 if (op.z1 != 0 && !op.base_point.is_point_at_infinity()) {
192 BB_ASSERT_GT(result.size(), msm_index);
193 BB_ASSERT_GT(result[msm_index].size(), mul_index);
194 result[msm_index][mul_index] = (ScalarMul{
195 .pc = 0,
196 .scalar = op.z1,
197 .base_point = op.base_point,
198 .wnaf_digits = compute_wnaf_digits(op.z1),
199 .wnaf_skew = (op.z1 & 1) == 0,
200 .precomputed_table = compute_precomputed_table(op.base_point),
201 });
202 mul_index++;
203 }
204 if (op.z2 != 0 && !op.base_point.is_point_at_infinity()) {
205 BB_ASSERT_GT(result.size(), msm_index);
206 BB_ASSERT_GT(result[msm_index].size(), mul_index);
207 auto endo_point = AffineElement{ op.base_point.x * FF::cube_root_of_unity(), -op.base_point.y };
208 result[msm_index][mul_index] = (ScalarMul{
209 .pc = 0,
210 .scalar = op.z2,
211 .base_point = endo_point,
212 .wnaf_digits = compute_wnaf_digits(op.z2),
213 .wnaf_skew = (op.z2 & 1) == 0,
214 .precomputed_table = compute_precomputed_table(endo_point),
215 });
216 }
217 }
218 });
219
220 // update pc. easier to do this serially but in theory could be optimized out
221 // We start pc at `num_muls` and decrement for each mul processed.
222 // This gives us two desired properties:
223 // 1: the value of pc at the 1st row = number of muls (easy to check)
224 // 2: the value of pc for the final mul = 1
225 // The latter point is valuable as it means that we can add empty rows (where pc = 0) and still satisfy our
226 // sumcheck relations that involve pc (if we did the other way around, starting at 1 and ending at num_muls,
227 // we create a discontinuity in pc values between the last transcript row and the following empty row).
228 // TL;DR we choose a decreasing `pc` so that the subsequent entries of the column (after the last entry) are 0.
229 // this is simply an optimization.
230 uint32_t pc = num_muls;
231 for (auto& msm : result) {
232 for (auto& mul : msm) {
233 mul.pc = pc;
234 pc--;
235 }
236 }
237
238 BB_ASSERT_EQ(pc, 0U);
239 return result;
240 }
241
243 {
245 for (const auto& msm : msms) {
246 for (const auto& mul : msm) {
247 result.push_back(mul);
248 }
249 }
250 return result;
251 }
252
253 [[nodiscard]] size_t get_estimated_num_finalized_gates() const
254 {
255 // TODO(https://github.com/AztecProtocol/aztec-packages/issues/2218): Reduce the amount of computation needed
256 // for this method
257 return op_queue->get_num_rows();
258 }
259
260 [[nodiscard]] size_t get_circuit_subgroup_size(const size_t num_rows) const
261 {
262
263 const auto num_rows_log2 = static_cast<size_t>(numeric::get_msb64(num_rows + NUM_DISABLED_ROWS_IN_SUMCHECK));
264 size_t num_rows_pow2 = 1UL << (num_rows_log2 + (1UL << num_rows_log2 == num_rows ? 0 : 1));
265 return num_rows_pow2;
266 }
267};
268} // namespace bb
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:59
ECCVMCircuitBuilder(std::shared_ptr< ECCOpQueue > &op_queue)
std::shared_ptr< ECCOpQueue > op_queue
static constexpr size_t NUM_SCALAR_BITS
static constexpr size_t ADDITIONS_PER_ROW
size_t get_estimated_num_finalized_gates() const
typename CycleGroup::affine_element AffineElement
static constexpr size_t POINT_TABLE_SIZE
static constexpr uint64_t WNAF_MASK
typename CycleGroup::element Element
std::vector< MSM > get_msms() const
static constexpr size_t NUM_WNAF_DIGITS_PER_SCALAR
size_t get_circuit_subgroup_size(const size_t num_rows) const
bb::eccvm::MSM< CycleGroup > MSM
static constexpr size_t WNAF_DIGITS_PER_ROW
static std::vector< ScalarMul > get_flattened_scalar_muls(const std::vector< MSM > &msms)
static constexpr size_t NUM_WNAF_DIGIT_BITS
typename CycleGroup::Fr CycleScalar
constexpr element dbl() const noexcept
group class. Represents an elliptic curve group element. Group is parametrised by Fq and Fr
Definition group.hpp:36
group_elements::affine_element< Fq, Fr, Params > affine_element
Definition group.hpp:42
group_elements::element< Fq, Fr, Params > element
Definition group.hpp:41
Fr_ Fr
Definition group.hpp:40
std::vector< ScalarMul< CycleGroup > > MSM
constexpr uint64_t get_msb64(const uint64_t in)
Definition get_msb.hpp:30
Entry point for Barretenberg command-line interface.
group< fq, fr, Bn254G1Params > g1
Definition g1.hpp:33
void parallel_for_range(size_t num_points, const std::function< void(size_t, size_t)> &func, size_t no_multhreading_if_less_or_equal)
Split a loop into several loops running in parallel.
Definition thread.cpp:102
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13