barretenberg
Loading...
Searching...
No Matches
verification_key.hpp
1#pragma once
2
3#include "../../primitives/curves/bn254.hpp"
4#include "../../primitives/memory/rom_table.hpp"
5#include "../../primitives/uint/uint.hpp"
6#include "barretenberg/crypto/pedersen_hash/pedersen.hpp"
7#include "barretenberg/ecc/curves/bn254/fq12.hpp"
8#include "barretenberg/ecc/curves/bn254/pairing.hpp"
9#include "barretenberg/plonk/proof_system/public_inputs/public_inputs.hpp"
10#include "barretenberg/plonk/proof_system/types/polynomial_manifest.hpp"
11#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp"
12#include "barretenberg/polynomials/evaluation_domain.hpp"
13#include "barretenberg/polynomials/polynomial_arithmetic.hpp"
14#include "barretenberg/srs/factories/crs_factory.hpp"
15#include "barretenberg/stdlib/hash/pedersen/pedersen.hpp"
16#include <map>
17
18namespace proof_system::plonk::stdlib::recursion {
19
31template <class Builder, size_t bits_per_element = 248> struct PedersenPreimageBuilder {
34
35 Builder* context;
36
37 PedersenPreimageBuilder(Builder* ctx = nullptr)
38 : context(ctx){};
39
40 field_pt hash()
41 {
42 // we can only use relaxed range checks in pedersen::compress iff bits_per_element < modulus bits
43 static_assert(bits_per_element < uint256_t(barretenberg::fr::modulus).get_msb());
44
45 if (current_bit_counter != 0) {
46 const uint256_t down_shift = uint256_t(1) << uint256_t((bits_per_element - current_bit_counter));
47 for (auto& x : work_element) {
48 x = x / barretenberg::fr(down_shift);
49 }
51 }
52
53 // TODO(@maramihali #2796) replace this with a Poseidon hash once we have one implemented.
54 // The current algorithm is splits the buffer into a running hash of size-2 hashes.
55 // We do this because, for UltraPlonk, size-2 Pedersehashes are more efficient than larger hashes as this small
56 // hash can utilize plookup tables.
57 // Once we implement an efficient Poseidon hash: we should change this to a straighforward hash of a vector of
58 // field elements. N.B. If we do a plain Pedersen vector-hash instead of this pairwise method, the Noir
59 // recursion circuit size goes beyond 2^19 which breaks many tests.
60 // Poseidon should not have this issue as ideally it is more efficient!
61 field_t<Builder> hashed = 0;
62 if (preimage_data.size() < 2) {
64 } else {
66 for (size_t i = 2; i < preimage_data.size(); ++i) {
68 }
69 }
70 return hashed;
71 }
72
77 std::vector<field_pt> preimage_data;
78
84 std::vector<field_pt> work_element;
85
86 size_t current_bit_counter = 0;
87
88 void add_element(const field_pt& element) { slice_element(element, 256); }
89
90 void add_element_with_existing_range_constraint(const field_pt& element, const size_t num_bits)
91 {
92 slice_element(element, num_bits);
93 }
94
109 void slice_element(const field_pt& element, const size_t num_bits)
110 {
111 ASSERT(context != nullptr);
112 uint256_t element_u256(element.get_value());
113 size_t hi_bits = bits_per_element - current_bit_counter;
114 if (hi_bits >= num_bits) {
115 // hmm
116 size_t new_bit_counter = current_bit_counter + num_bits;
117 field_pt hi = element;
118 const size_t leftovers = bits_per_element - new_bit_counter;
119 field_pt buffer_shift = field_pt(context, barretenberg::fr(uint256_t(1) << ((uint64_t)leftovers)));
120 work_element.emplace_back(hi * buffer_shift);
121 current_bit_counter = new_bit_counter;
122 if (current_bit_counter == bits_per_element) {
123 current_bit_counter = 0;
125
126 work_element = std::vector<field_pt>();
127 }
128 return;
129 }
130 const size_t lo_bits = num_bits - hi_bits;
131 field_pt lo = witness_t(context, barretenberg::fr(element_u256.slice(0, lo_bits)));
132 field_pt hi = witness_t(context, barretenberg::fr(element_u256.slice(lo_bits, 256)));
133 lo.create_range_constraint(lo_bits);
134 hi.create_range_constraint(hi_bits);
135 field_pt shift(context, barretenberg::fr(uint256_t(1ULL) << static_cast<uint64_t>(lo_bits)));
136 if (!element.is_constant() || !lo.is_constant() || !hi.is_constant()) {
137 lo.add_two(hi * shift, -element).assert_equal(0);
138 }
139
140 constexpr uint256_t modulus = barretenberg::fr::modulus;
141 constexpr size_t modulus_bits = modulus.get_msb();
142
143 // If our input is a full field element we must validate the sum of our slices is < p
144 if (num_bits >= modulus_bits) {
145 const field_pt r_lo = field_pt(context, modulus.slice(0, lo_bits));
146 const field_pt r_hi = field_pt(context, modulus.slice(lo_bits, num_bits));
147
148 bool need_borrow = (uint256_t(lo.get_value()) > uint256_t(r_lo.get_value()));
149 field_pt borrow = field_pt::from_witness(context, need_borrow);
150
151 // directly call `create_new_range_constraint` to avoid creating an arithmetic gate
152 if constexpr (HasPlookup<Builder>) {
153 context->create_new_range_constraint(borrow.get_witness_index(), 1, "borrow");
154 } else {
155 context->create_range_constraint(borrow.get_witness_index(), 1, "borrow");
156 }
157 // Hi range check = r_hi - y_hi - borrow
158 // Lo range check = r_lo - y_lo + borrow * 2^{126}
159 field_t res_hi = (r_hi - hi) - borrow;
160 field_t res_lo = (r_lo - lo) + (borrow * (uint256_t(1) << lo_bits));
161
162 res_hi.create_range_constraint(modulus_bits + 1 - lo_bits);
163 res_lo.create_range_constraint(lo_bits);
164 }
165 current_bit_counter = (current_bit_counter + num_bits) % bits_per_element;
166
167 // if current_bit_counter == 0 we've rolled over
168 if (current_bit_counter == 0) {
169 work_element.emplace_back(hi);
171 preimage_data.push_back(lo);
172 work_element = std::vector<field_pt>();
173 } else {
174 work_element.emplace_back(hi);
176 field_t lo_shift(
177 context,
178 barretenberg::fr(uint256_t(1ULL) << ((bits_per_element - static_cast<uint64_t>(current_bit_counter)))));
179 work_element = std::vector<field_pt>();
180 work_element.emplace_back(lo * lo_shift);
181 }
182 };
183};
184
185template <typename Builder> struct evaluation_domain {
186 static evaluation_domain from_field_elements(const std::vector<field_t<Builder>>& fields)
187 {
188 evaluation_domain domain;
189 domain.root = fields[0];
190
191 domain.root_inverse = domain.root.invert();
192 domain.domain = fields[1];
193 domain.domain_inverse = domain.domain.invert();
194 domain.generator = fields[2];
195 domain.generator_inverse = domain.generator.invert();
196 domain.size = domain.domain;
197 return domain;
198 }
199
200 static evaluation_domain from_witness(Builder* ctx, const barretenberg::evaluation_domain& input)
201 {
202 evaluation_domain domain;
203 domain.root = witness_t<Builder>(ctx, input.root);
204 domain.root_inverse = domain.root.invert();
205 domain.domain = witness_t<Builder>(ctx, input.domain);
206 domain.domain_inverse = domain.domain.invert();
207 domain.generator = witness_t<Builder>(ctx, input.generator);
208 domain.generator_inverse = domain.generator.invert();
209 domain.size = domain.domain;
210 return domain;
211 }
212
213 static evaluation_domain from_constants(Builder* ctx, const barretenberg::evaluation_domain& input)
214 {
215 evaluation_domain domain;
216 domain.root = field_t<Builder>(ctx, input.root);
217 domain.root_inverse = field_t<Builder>(ctx, input.root_inverse);
218 domain.domain = field_t<Builder>(ctx, input.domain);
219 domain.domain_inverse = field_t<Builder>(ctx, input.domain_inverse);
220 domain.generator = field_t<Builder>(ctx, input.generator);
221 domain.generator_inverse = field_t<Builder>(ctx, input.generator_inverse);
222 domain.size = domain.domain;
223 return domain;
224 }
225
226 field_t<Builder> root;
227 field_t<Builder> root_inverse;
228 field_t<Builder> domain;
229 field_t<Builder> domain_inverse;
230 field_t<Builder> generator;
231 field_t<Builder> generator_inverse;
232 uint32<Builder> size;
233};
234
235template <typename Curve> struct verification_key {
236 using Builder = typename Curve::Builder;
237
238 static std::shared_ptr<verification_key> from_field_elements(
239 Builder* ctx,
240 const std::vector<field_t<Builder>>& fields,
241 bool inner_proof_contains_recursive_proof = false,
242 std::array<uint32_t, 16> recursive_proof_public_input_indices = {})
243 {
244 std::vector<fr> fields_raw;
245 std::shared_ptr<verification_key> key = std::make_shared<verification_key>();
246 key->context = ctx;
247
248 key->polynomial_manifest = PolynomialManifest(Builder::CIRCUIT_TYPE);
249 key->domain = evaluation_domain<Builder>::from_field_elements({ fields[0], fields[1], fields[2] });
250
251 key->n = fields[3];
252 key->num_public_inputs = fields[4];
253
254 // NOTE: For now `contains_recursive_proof` and `recursive_proof_public_input_indices` need to be circuit
255 // constants!
256 key->contains_recursive_proof = inner_proof_contains_recursive_proof;
257 for (size_t i = 0; i < 16; ++i) {
258 auto x = recursive_proof_public_input_indices[i];
259 key->recursive_proof_public_input_indices.emplace_back(x);
260 }
261
262 size_t count = 22;
263 for (const auto& descriptor : key->polynomial_manifest.get()) {
264 if (descriptor.source == PolynomialSource::SELECTOR || descriptor.source == PolynomialSource::PERMUTATION) {
265
266 const auto x_lo = fields[count++];
267 const auto x_hi = fields[count++];
268 const auto y_lo = fields[count++];
269 const auto y_hi = fields[count++];
270 const typename Curve::BaseField x(x_lo, x_hi);
271 const typename Curve::BaseField y(y_lo, y_hi);
272 const typename Curve::Group element(x, y);
273
274 key->commitments.insert({ std::string(descriptor.commitment_label), element });
275 }
276 }
277
278 return key;
279 }
280
286 static std::shared_ptr<verification_key> from_witness(Builder* ctx,
287 const std::shared_ptr<plonk::verification_key>& input_key)
288 {
289 std::shared_ptr<verification_key> key = std::make_shared<verification_key>();
290 // Native data:
291 key->context = ctx;
292 key->reference_string = input_key->reference_string;
293 key->polynomial_manifest = input_key->polynomial_manifest;
294
295 // Circuit types:
296 key->n = witness_t<Builder>(ctx, barretenberg::fr(input_key->circuit_size));
297 key->num_public_inputs = witness_t<Builder>(ctx, input_key->num_public_inputs);
298 key->domain = evaluation_domain<Builder>::from_witness(ctx, input_key->domain);
299 key->contains_recursive_proof = input_key->contains_recursive_proof;
300 key->recursive_proof_public_input_indices = input_key->recursive_proof_public_input_indices;
301 for (const auto& [tag, value] : input_key->commitments) {
302 // We do not perform on_curve() circuit checks when constructing the Curve::Group element.
303 // The assumption is that the circuit creator is honest and that the verification key hash (or some other
304 // method) will be used to ensure the provided key matches the key produced by the circuit creator.
305 // If the circuit creator is not honest, the entire set of circuit constraints being proved over cannot be
306 // trusted!
307 const typename Curve::BaseField x = Curve::BaseField::from_witness(ctx, value.x);
308 const typename Curve::BaseField y = Curve::BaseField::from_witness(ctx, value.y);
309 key->commitments.insert({ tag, typename Curve::Group(x, y) });
310 }
311
312 return key;
313 }
314
315 static std::shared_ptr<verification_key> from_constants(Builder* ctx,
316 const std::shared_ptr<plonk::verification_key>& input_key)
317 {
318 std::shared_ptr<verification_key> key = std::make_shared<verification_key>();
319 key->context = ctx;
320 key->n = field_t<Builder>(ctx, input_key->circuit_size);
321 key->num_public_inputs = field_t<Builder>(ctx, input_key->num_public_inputs);
322 key->contains_recursive_proof = input_key->contains_recursive_proof;
323 key->recursive_proof_public_input_indices = input_key->recursive_proof_public_input_indices;
324
325 key->domain = evaluation_domain<Builder>::from_constants(ctx, input_key->domain);
326
327 for (const auto& [tag, value] : input_key->commitments) {
328 key->commitments.insert({ tag, typename Curve::Group(value) });
329 }
330
331 key->reference_string = input_key->reference_string;
332 key->polynomial_manifest = input_key->polynomial_manifest;
333
334 return key;
335 }
336
337 void validate_key_is_in_set(const std::vector<std::shared_ptr<plonk::verification_key>>& keys_in_set)
338 {
339 const auto circuit_key_compressed = hash();
340 bool found = false;
341 // if we're using Plookup, use a ROM table to index the keys
342 if constexpr (HasPlookup<Builder>) {
343 field_t<Builder> key_index(witness_t<Builder>(context, 0));
344 std::vector<field_t<Builder>> compressed_keys;
345 for (size_t i = 0; i < keys_in_set.size(); ++i) {
346 barretenberg::fr compressed = hash_native(keys_in_set[i]);
347 compressed_keys.emplace_back(compressed);
348 if (compressed == circuit_key_compressed.get_value()) {
349 key_index = witness_t<Builder>(context, i);
350 found = true;
351 }
352 }
353 if (!found) {
354 context->failure(
355 "verification_key::validate_key_is_in_set failed - input key is not in the provided set!");
356 }
357 rom_table<Builder> key_table(compressed_keys);
358
359 const auto output_key = key_table[key_index];
360 output_key.assert_equal(circuit_key_compressed);
361 } else {
362 bool_t<Builder> is_valid(false);
363 for (const auto& key : keys_in_set) {
364 barretenberg::fr compressed = hash_native(key);
365 is_valid = is_valid || (circuit_key_compressed == compressed);
366 }
367
368 is_valid.assert_equal(true);
369 }
370 }
371
372 public:
373 field_t<Builder> hash()
374 {
375 PedersenPreimageBuilder<Builder> preimage_buffer(context);
376
377 field_t<Builder> circuit_type =
378 witness_t<Builder>::create_constant_witness(context, static_cast<uint32_t>(Builder::CIRCUIT_TYPE));
379 domain.generator.create_range_constraint(16, "domain.generator");
380 domain.domain.create_range_constraint(32, "domain.generator");
381 num_public_inputs.create_range_constraint(32, "num_public_inputs");
382 preimage_buffer.add_element_with_existing_range_constraint(circuit_type, 8);
383 preimage_buffer.add_element_with_existing_range_constraint(domain.generator, 16); // coset generator is small
384 preimage_buffer.add_element_with_existing_range_constraint(domain.domain, 32);
385 preimage_buffer.add_element_with_existing_range_constraint(num_public_inputs, 32);
386 constexpr size_t limb_bits = Curve::BaseField::NUM_LIMB_BITS;
387 constexpr size_t last_limb_bits = 256 - (limb_bits * 3);
388 for (const auto& [tag, selector] : commitments) {
389 const auto& x = selector.x;
390 const auto& y = selector.y;
391 preimage_buffer.add_element_with_existing_range_constraint(y.binary_basis_limbs[3].element, last_limb_bits);
392 preimage_buffer.add_element_with_existing_range_constraint(y.binary_basis_limbs[2].element, limb_bits);
393 preimage_buffer.add_element_with_existing_range_constraint(y.binary_basis_limbs[1].element, limb_bits);
394 preimage_buffer.add_element_with_existing_range_constraint(y.binary_basis_limbs[0].element, limb_bits);
395 preimage_buffer.add_element_with_existing_range_constraint(x.binary_basis_limbs[3].element, last_limb_bits);
396 preimage_buffer.add_element_with_existing_range_constraint(x.binary_basis_limbs[2].element, limb_bits);
397 preimage_buffer.add_element_with_existing_range_constraint(x.binary_basis_limbs[1].element, limb_bits);
398 preimage_buffer.add_element_with_existing_range_constraint(x.binary_basis_limbs[0].element, limb_bits);
399 }
400 preimage_buffer.add_element(domain.root);
401 field_t<Builder> hashed_key = preimage_buffer.hash();
402 return hashed_key;
403 }
404
405 static barretenberg::fr hash_native(const std::shared_ptr<plonk::verification_key>& key,
406 const size_t hash_index = 0)
407 {
408 std::vector<uint8_t> preimage_data;
409
410 preimage_data.push_back(static_cast<uint8_t>(Builder::CIRCUIT_TYPE));
411
412 const uint256_t domain = key->domain.domain;
413 const uint256_t generator = key->domain.generator;
414 const uint256_t num_public_inputs = key->num_public_inputs;
415
416 ASSERT(domain < (uint256_t(1) << 32));
417 ASSERT(generator < (uint256_t(1) << 16));
418 ASSERT(num_public_inputs < (uint256_t(1) << 32));
419
420 write(preimage_data, static_cast<uint16_t>(uint256_t(key->domain.generator)));
421 write(preimage_data, static_cast<uint32_t>(uint256_t(key->domain.domain)));
422 write(preimage_data, static_cast<uint32_t>(key->num_public_inputs));
423 for (const auto& [tag, selector] : key->commitments) {
424 write(preimage_data, selector.y);
425 write(preimage_data, selector.x);
426 }
427
428 write(preimage_data, key->domain.root);
429
430 return crypto::pedersen_hash::hash_buffer(preimage_data, hash_index);
431 }
432
433 // Circuit Types:
435 field_t<Builder> num_public_inputs;
436 field_t<Builder> z_pow_n;
437
439
440 std::map<std::string, typename Curve::Group> commitments;
441
442 // Native data:
443
444 std::shared_ptr<barretenberg::srs::factories::VerifierCrs<curve::BN254>> reference_string;
445
446 PolynomialManifest polynomial_manifest;
447 // Used to check in the circuit if a proof contains any aggregated state.
448 bool contains_recursive_proof = false;
449 std::vector<uint32_t> recursive_proof_public_input_indices;
450 size_t program_width = 4;
451 Builder* context;
452};
453
454} // namespace proof_system::plonk::stdlib::recursion
static Fq hash_buffer(const std::vector< uint8_t > &input, GeneratorContext context={})
Given an arbitrary length of bytes, convert them to fields and hash the result using the default gene...
Definition: pedersen.cpp:69
Definition: uint256.hpp:25
constexpr uint256_t slice(uint64_t start, uint64_t end) const
Definition: uint256_impl.hpp:157
Definition: standard_circuit_builder.hpp:12
Definition: polynomial_manifest.hpp:142
Definition: biggroup.hpp:22
Definition: field.hpp:10
static field_t accumulate(const std::vector< field_t > &to_add)
Definition: field.cpp:936
void assert_equal(const field_t &rhs, std::string const &msg="field_t::assert_equal") const
Constrain that this field is equal to the given field.
Definition: field.cpp:749
stdlib class that evaluates in-circuit pedersen hashes, consistent with behavior in crypto::pedersen_...
Definition: pedersen.hpp:18
Definition: witness.hpp:10
Contains all the headers required to adequately compile the types defined in circuit_builders_fwd....
Definition: circuit_builders.hpp:11
Constructs a packed buffer of field elements to be fed into a Pedersen hash function Goal is to conca...
Definition: verification_key.hpp:31
std::vector< field_pt > preimage_data
preimage_data is a bit-array where bits_per_element number of bits are packed into a single field ele...
Definition: verification_key.hpp:77
void slice_element(const field_pt &element, const size_t num_bits)
Populate preimage_data with element whose size is known to be num_bits. preimage_data is treated as a...
Definition: verification_key.hpp:109
std::vector< field_pt > work_element
work_element represents the leading element to be added into preimage_data. Vector is composed of fie...
Definition: verification_key.hpp:84
static std::shared_ptr< verification_key > from_witness(Builder *ctx, const std::shared_ptr< plonk::verification_key > &input_key)
Converts a 'native' verification key into a standard library type, instantiating the input_key parame...
Definition: verification_key.hpp:286