Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
transcript.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// #define LOG_CHALLENGES
9// #define LOG_INTERACTIONS
10
23#include <atomic>
24#include <concepts>
25
26namespace bb {
27
28// TODO(https://github.com/AztecProtocol/barretenberg/issues/1226): univariates should also be logged
29template <typename T, typename... U>
31
32// A concept for detecting whether a type is native or in-circuit
33template <typename T>
35
36template <typename T, typename = void> struct is_iterable : std::false_type {};
37
38// this gets used only when we can call std::begin() and std::end() on that type
39template <typename T>
40struct is_iterable<T, std::void_t<decltype(std::begin(std::declval<T&>())), decltype(std::end(std::declval<T&>()))>>
41 : std::true_type {};
42
43template <typename T> constexpr bool is_iterable_v = is_iterable<T>::value;
44
45// A static counter for the number of transcripts created
46// This is used to generate unique labels for the transcript origin tags
47
48// ‘inline’ (since C++17) ensures a single shared definition with external linkage.
49inline std::atomic<size_t> unique_transcript_index{ 0 };
54template <typename Codec_, typename HashFunction> class BaseTranscript {
55 public:
56 using Codec = Codec_;
57 using DataType = typename Codec::DataType;
58 using Proof = std::vector<DataType>;
59
60 // Detects whether the transcript is in-circuit or not
61 static constexpr bool in_circuit = InCircuit<DataType>;
62
63 // The unique index of the transcript
64 size_t transcript_index = 0;
65
66 // The index of the current round of the transcript (used for the origin tag, round is only incremented if we switch
67 // from generating to receiving)
68 size_t round_index = 0;
69
70 // Indicates whether the transcript is receiving data from the prover
71 bool reception_phase = true;
72
74 {
75 // If we are in circuit, we need to get a unique index for the transcript
76 if constexpr (in_circuit) {
78 }
79 }
80
82 size_t num_frs_written = 0; // the number of bb::frs written to proof_data by the prover
83 size_t num_frs_read = 0; // the number of bb::frs read from proof_data by the verifier
84 size_t round_number = 0; // current round for manifest
85
86 private:
87 bool is_first_challenge = true; // indicates if this is the first challenge this transcript is generating
88 DataType previous_challenge{}; // default-initialized to zeros
89 std::vector<DataType>
90 current_round_data; // the data for the current round that will be hashed to generate challenges
91 std::vector<DataType>
92 independent_hash_buffer; // data that will be independently hashed to get the hash of an object
93
94 bool use_manifest = false; // indicates whether the manifest is turned on, currently only on for manifest tests.
95
96 // "Manifest" object that records a summary of the transcript interactions
98
108 {
109 // challenges need at least 110 bits in them to match the presumed security parameter of the BN254 curve.
110 BB_ASSERT_LTE(num_challenges, 2U);
111 // Prevent challenge generation if this is the first challenge we're generating,
112 // AND nothing was sent by the prover.
113 if (is_first_challenge) {
114 ASSERT(!current_round_data.empty());
115 }
116
117 // concatenate the previous challenge (if this is not the first challenge) with the current round data.
118 // TODO(Adrian): Do we want to use a domain separator as the initial challenge buffer?
119 // We could be cheeky and use the hash of the manifest as domain separator, which would prevent us from
120 // having to domain separate all the data. (See https://safe-hash.dev)
121 std::vector<DataType> full_buffer;
122 if (!is_first_challenge) {
123 // if not the first challenge, we can use the previous_challenge
124 full_buffer.emplace_back(previous_challenge);
125 } else {
126 // Update is_first_challenge for the future
127 is_first_challenge = false;
128 }
129 if (!current_round_data.empty()) {
130 // TODO(https://github.com/AztecProtocol/barretenberg/issues/832): investigate why
131 // full_buffer.insert(full_buffer.end(), current_round_data.begin(), current_round_data.end()); fails to
132 // compile with gcc
134 current_round_data.clear(); // clear the round data buffer since it has been used
135 }
136
137 // Hash the full buffer with poseidon2, which is believed to be a collision resistant hash function and a
138 // random oracle, removing the need to pre-hash to compress and then hash with a random oracle, as we
139 // previously did with Pedersen and Blake3s.
140 DataType new_challenge = HashFunction::hash(full_buffer);
141 std::array<DataType, 2> new_challenges = Codec::split_challenge(new_challenge);
142 // update previous challenge buffer for next time we call this function
143 previous_challenge = new_challenge;
144 return new_challenges;
145 };
146
147 protected:
148 Proof proof_data; // Contains the raw data sent by the prover.
149
156 void add_element_frs_to_hash_buffer(const std::string& label, std::span<const DataType> element_frs)
157 {
158 if (use_manifest) {
159 // Add an entry to the current round of the manifest
160 manifest.add_entry(round_number, label, element_frs.size());
161 }
162
163 current_round_data.insert(current_round_data.end(), element_frs.begin(), element_frs.end());
164 }
165
174 template <typename T> void serialize_to_buffer(const T& element, Proof& proof_data)
175 {
176 auto element_frs = Codec::serialize_to_fields(element);
177 proof_data.insert(proof_data.end(), element_frs.begin(), element_frs.end());
178 }
188 template <typename T> T deserialize_from_buffer(const Proof& proof_data, size_t& offset) const
189 {
190 constexpr size_t element_fr_size = Codec::template calc_num_fields<T>();
191 BB_ASSERT_LTE(offset + element_fr_size, proof_data.size());
192
193 auto element_frs = std::span{ proof_data }.subspan(offset, element_fr_size);
194 offset += element_fr_size;
195
196 auto element = Codec::template deserialize_from_fields<T>(element_frs);
197
198 return element;
199 }
200
201 public:
209 std::vector<DataType> export_proof()
210 {
211 std::vector<DataType> result(num_frs_written);
212 std::copy_n(proof_data.begin() + proof_start, num_frs_written, result.begin());
214 num_frs_written = 0;
215 return result;
216 };
217
218 void load_proof(const std::vector<DataType>& proof)
219 {
220 std::copy(proof.begin(), proof.end(), std::back_inserter(proof_data));
221 }
222
228 size_t size_proof_data() { return proof_data.size(); }
229
234 void enable_manifest() { use_manifest = true; }
235
244 static DataType hash(const std::vector<DataType>& data) { return HashFunction::hash(data); }
245
252 template <typename T> static std::vector<DataType> serialize(const T& element)
253 {
254 return Codec::serialize_to_fields(element);
255 }
256
257 template <typename T> static T deserialize(std::span<const DataType> frs)
258 {
259 return Codec::template deserialize_from_fields<T>(frs);
260 }
261
262 template <typename T> static size_t calc_num_data_types() { return Codec::template calc_num_fields<T>(); }
263
274 template <typename ChallengeType, typename... Strings>
275 std::array<ChallengeType, sizeof...(Strings)> get_challenges(const Strings&... labels)
276 {
277 constexpr size_t num_challenges = sizeof...(Strings);
278
279 if (use_manifest) {
280 // Add challenge labels for current round to the manifest
282 }
283
284 // In case the transcript is used for recursive verification, we need to sanitize current round data so we don't
285 // get an origin tag violation inside the hasher. We are doing this to ensure that the free witness tagged
286 // elements that are sent to the transcript and are assigned tags externally, don't trigger the origin tag
287 // security mechanism while we are hashing them
288 if constexpr (in_circuit) {
289 for (auto& element : current_round_data) {
290 element.unset_free_witness_tag();
291 }
292 }
293 // Compute the new challenge buffer from which we derive the challenges.
294
295 // Create challenges from Frs.
297
298 // Generate the challenges by iteratively hashing over the previous challenge.
299 for (size_t i = 0; i < num_challenges / 2; i += 1) {
300 auto challenge_buffer = get_next_duplex_challenge_buffer(2);
301 challenges[2 * i] = Codec::template convert_challenge<ChallengeType>(challenge_buffer[0]);
302 challenges[(2 * i) + 1] = Codec::template convert_challenge<ChallengeType>(challenge_buffer[1]);
303 }
304 if ((num_challenges & 1) == 1) {
305 auto challenge_buffer = get_next_duplex_challenge_buffer(1);
306 challenges[num_challenges - 1] = Codec::template convert_challenge<ChallengeType>(challenge_buffer[0]);
307 }
308
309 // In case the transcript is used for recursive verification, we can track proper Fiat-Shamir usage
310 if constexpr (in_circuit) {
311 // We are in challenge generation mode
312 if (reception_phase) {
313 reception_phase = false;
314 }
315 // Assign origin tags to the challenges
316 for (size_t i = 0; i < num_challenges; i++) {
317 challenges[i].set_origin_tag(OriginTag(transcript_index, round_index, /*is_submitted=*/false));
318 }
319 }
320 // Prepare for next round.
321 ++round_number;
322
323 return challenges;
324 }
325
332 template <typename ChallengeType, typename String, std::size_t N>
334 {
335 // Expand the array elements into the existing variadic get_challenges
336 return std::apply([this](auto const&... xs) { return this->get_challenges<ChallengeType>(xs...); }, labels);
337 }
338
343 template <typename ChallengeType>
345 const ChallengeType& round_challenge)
346 {
347 std::vector<ChallengeType> pows(num_powers);
348 pows[0] = round_challenge;
349 for (size_t i = 1; i < num_powers; i++) {
350 pows[i] = pows[i - 1].sqr();
351 }
352 return pows;
353 }
354
355 template <typename ChallengeType, typename String>
356 std::vector<ChallengeType> get_powers_of_challenge(const String& label, size_t num_challenges)
357 {
358 return compute_round_challenge_pows(num_challenges, get_challenge<ChallengeType>(label));
359 }
360
369 template <class T> void add_to_independent_hash_buffer([[maybe_unused]] const std::string& label, const T& element)
370 {
371 DEBUG_LOG(label, element);
372 // In case the transcript is used for recursive verification, we can track proper Fiat-Shamir usage
373 if constexpr (in_circuit) {
374 // The verifier is receiving data from the prover. If before this we were in the challenge generation phase,
375 // then we need to increment the round index
376 if (!reception_phase) {
377 reception_phase = true;
378 round_index++;
379 }
380 // If the element is iterable, then we need to assign origin tags to all the elements
381 if constexpr (is_iterable_v<T>) {
382 for (const auto& subelement : element) {
383 subelement.set_origin_tag(OriginTag(transcript_index, round_index, /*is_submitted=*/true));
384 }
385 } else {
386 // If the element is not iterable, then we need to assign an origin tag to the element
387 element.set_origin_tag(OriginTag(transcript_index, round_index, /*is_submitted=*/true));
388 }
389 }
390 auto element_frs = Codec::serialize_to_fields(element);
391
392#ifdef LOG_INTERACTIONS
393 if constexpr (Loggable<T>) {
394 info("independent hash buffer consumed: ", label, ": ", element);
395 }
396#endif
397 independent_hash_buffer.insert(independent_hash_buffer.end(), element_frs.begin(), element_frs.end());
398 }
399
406 {
407 // In case the transcript is used for recursive verification, we need to sanitize current round data so we don't
408 // get an origin tag violation inside the hasher
409 if constexpr (in_circuit) {
410 for (auto& element : independent_hash_buffer) {
411 element.unset_free_witness_tag();
412 }
413 }
414 DataType buffer_hash = HashFunction::hash(independent_hash_buffer);
416 return buffer_hash;
417 }
418
427 template <class T> void add_to_hash_buffer(const std::string& label, const T& element)
428 {
429 DEBUG_LOG(label, element);
430 // In case the transcript is used for recursive verification, we can track proper Fiat-Shamir usage
431 if constexpr (in_circuit) {
432 // The verifier is receiving data from the prover. If before this we were in the challenge generation phase,
433 // then we need to increment the round index
434 if (!reception_phase) {
435 reception_phase = true;
436 round_index++;
437 }
438 // If the element is iterable, then we need to assign origin tags to all the elements
439 if constexpr (is_iterable_v<T>) {
440 for (const auto& subelement : element) {
441 subelement.set_origin_tag(OriginTag(transcript_index, round_index, /*is_submitted=*/true));
442 }
443 } else {
444 // If the element is not iterable, then we need to assign an origin tag to the element
445 element.set_origin_tag(OriginTag(transcript_index, round_index, /*is_submitted=*/true));
446 }
447 }
448 auto elements = Codec::serialize_to_fields(element);
449
450#ifdef LOG_INTERACTIONS
451 if constexpr (Loggable<T>) {
452 info("consumed: ", label, ": ", element);
453 }
454#endif
455 add_element_frs_to_hash_buffer(label, elements);
456 }
457
471 template <class T> void send_to_verifier(const std::string& label, const T& element)
472 {
473 DEBUG_LOG(label, element);
474 auto element_frs = Codec::template serialize_to_fields<T>(element);
475 proof_data.insert(proof_data.end(), element_frs.begin(), element_frs.end());
476 num_frs_written += element_frs.size();
477
478#ifdef LOG_INTERACTIONS
479 if constexpr (Loggable<T>) {
480 info("sent: ", label, ": ", element);
481 }
482#endif
483 add_element_frs_to_hash_buffer(label, element_frs);
484 }
485
493 template <class T> T receive_from_prover(const std::string& label)
494 {
495 const size_t element_size = Codec::template calc_num_fields<T>();
496 BB_ASSERT_LTE(num_frs_read + element_size, proof_data.size());
497
498 auto element_frs = std::span{ proof_data }.subspan(num_frs_read, element_size);
499 // In case the transcript is used for recursive verification, we can track proper Fiat-Shamir usage
500 if constexpr (in_circuit) {
501 // The verifier is receiving data from the prover. If before this we were in the challenge generation phase,
502 // then we need to increment the round index
503 if (!reception_phase) {
504 reception_phase = true;
505 round_index++;
506 }
507 // Assign an origin tag to the elements going into the hash buffer
508 const auto element_origin_tag = OriginTag(transcript_index, round_index, /*is_submitted=*/true);
509 for (auto& subelement : element_frs) {
510 subelement.set_origin_tag(element_origin_tag);
511 }
512 }
513 num_frs_read += element_size;
514
515 add_element_frs_to_hash_buffer(label, element_frs);
516
517 auto element = Codec::template deserialize_from_fields<T>(element_frs);
518 DEBUG_LOG(label, element);
519
520 // Ensure that the element got assigned an origin tag
521 if constexpr (in_circuit) {
522 const auto element_origin_tag = OriginTag(transcript_index, round_index, /*is_submitted=*/true);
523 // If the element is iterable, then we need to check origin tags to all the elements
524 if constexpr (is_iterable_v<T>) {
525 for (auto& subelement : element) {
526 ASSERT(subelement.get_origin_tag() == element_origin_tag);
527 }
528 } else {
529 // If the element is not iterable, then we need to check an origin tag of the element
530 ASSERT(element.get_origin_tag() == element_origin_tag);
531 }
532 }
533#ifdef LOG_INTERACTIONS
534 if constexpr (Loggable<T>) {
535 info("received: ", label, ": ", element);
536 }
537#endif
538
539 return element;
540 }
541
549 const std::shared_ptr<BaseTranscript>& prover_transcript)
550 {
551 // We expect this function to only be used when the transcript has just been exported.
552 BB_ASSERT_EQ(prover_transcript->num_frs_written, static_cast<size_t>(0), "Expected to be empty");
553 auto verifier_transcript = std::make_shared<BaseTranscript>(*prover_transcript);
554 verifier_transcript->num_frs_read = static_cast<size_t>(verifier_transcript->proof_start);
555 verifier_transcript->proof_start = 0;
556 return verifier_transcript;
557 }
565 {
566 auto transcript = std::make_shared<BaseTranscript>();
567 constexpr uint32_t init{ 42 }; // arbitrary
568 transcript->send_to_verifier("Init", init);
569 return transcript;
570 };
571
580 {
581 auto verifier_transcript = std::make_shared<BaseTranscript>();
582 verifier_transcript->load_proof(transcript->proof_data);
583 [[maybe_unused]] auto _ = verifier_transcript->template receive_from_prover<DataType>("Init");
584 return verifier_transcript;
585 };
586
587 template <typename ChallengeType> ChallengeType get_challenge(const std::string& label)
588 {
589 ChallengeType result = get_challenges<ChallengeType>(label)[0];
590#if defined LOG_CHALLENGES || defined LOG_INTERACTIONS
591 info("challenge: ", label, ": ", result);
592#endif
593 DEBUG_LOG(label, result);
594 return result;
595 }
596
597 [[nodiscard]] TranscriptManifest get_manifest() const { return manifest; };
598
599 void print()
600 {
601 if (!use_manifest) {
602 info("Warning: manifest is not enabled!");
603 }
604 manifest.print();
605 }
606
651 {
652 ASSERT(current_round_data.empty(), "Branching a transcript with non empty round data");
653
654 BaseTranscript branched_transcript;
655
656 // Need to fetch_sub because the constructor automatically increases unique_transcript_index by 1
657 unique_transcript_index.fetch_sub(1);
658 branched_transcript.transcript_index = transcript_index;
659 branched_transcript.round_index = round_index;
660 branched_transcript.add_to_hash_buffer("init", previous_challenge);
661 round_index += BRANCHING_JUMP;
662
663 return branched_transcript;
664 }
665};
666
669
670template <typename Builder>
676
677} // namespace bb
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:88
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:163
#define ASSERT(expression,...)
Definition assert.hpp:77
Common transcript class for both parties. Stores the data for the current round, as well as the manif...
std::vector< DataType > independent_hash_buffer
static std::shared_ptr< BaseTranscript > verifier_init_empty(const std::shared_ptr< BaseTranscript > &transcript)
For testing: initializes transcript based on proof data then receives junk data produced by BaseTrans...
TranscriptManifest manifest
std::array< DataType, 2 > get_next_duplex_challenge_buffer(size_t num_challenges)
Compute next challenge c_next = H( Compress(c_prev || round_buffer) )
typename Codec::DataType DataType
TranscriptManifest get_manifest() const
void add_element_frs_to_hash_buffer(const std::string &label, std::span< const DataType > element_frs)
Adds challenge elements to the current_round_buffer and updates the manifest.
void enable_manifest()
Enables the manifest.
ChallengeType get_challenge(const std::string &label)
std::vector< DataType > export_proof()
Return the proof data starting at proof_start.
static constexpr bool in_circuit
DataType previous_challenge
void serialize_to_buffer(const T &element, Proof &proof_data)
Serializes object and appends it to proof_data.
static DataType hash(const std::vector< DataType > &data)
Static hash method that forwards to Codec hash.
std::array< ChallengeType, sizeof...(Strings)> get_challenges(const Strings &... labels)
After all the prover messages have been sent, finalize the round by hashing all the data and then cre...
void add_to_independent_hash_buffer(const std::string &label, const T &element)
Adds an element to an independent hash buffer.
std::vector< DataType > current_round_data
void add_to_hash_buffer(const std::string &label, const T &element)
Adds an element to the transcript.
std::vector< ChallengeType > compute_round_challenge_pows(const size_t num_powers, const ChallengeType &round_challenge)
Given δ, compute the vector [δ, δ^2,..., δ^2^num_powers].
std::vector< DataType > Proof
std::ptrdiff_t proof_start
T receive_from_prover(const std::string &label)
Reads the next element of type T from the transcript, with a predefined label, only used by verifier.
static std::shared_ptr< BaseTranscript > prover_init_empty()
For testing: initializes transcript with some arbitrary data so that a challenge can be generated aft...
std::array< ChallengeType, N > get_challenges(std::array< String, N > const &labels)
Wrapper around get_challenges to handle array of challenges.
void send_to_verifier(const std::string &label, const T &element)
Adds a prover message to the transcript, only intended to be used by the prover.
static size_t calc_num_data_types()
T deserialize_from_buffer(const Proof &proof_data, size_t &offset) const
Deserializes the frs starting at offset into the typed element and returns that element.
size_t size_proof_data()
Return the size of proof_data.
void load_proof(const std::vector< DataType > &proof)
BaseTranscript branch_transcript()
Branch a transcript to perform verifier-only computations.
static std::vector< DataType > serialize(const T &element)
Serialize a size_t to a vector of field elements.
DataType hash_independent_buffer()
Hashes the independent hash buffer and clears it.
static T deserialize(std::span< const DataType > frs)
static std::shared_ptr< BaseTranscript > convert_prover_transcript_to_verifier_transcript(const std::shared_ptr< BaseTranscript > &prover_transcript)
Convert a prover transcript to a verifier transcript.
std::vector< ChallengeType > get_powers_of_challenge(const String &label, size_t num_challenges)
void add_entry(size_t round, const std::string &element_label, size_t element_size)
void add_challenge(size_t round, Strings &... labels)
stdlib class that evaluates in-circuit poseidon2 hashes, consistent with behavior in crypto::poseidon...
Definition poseidon2.hpp:20
#define DEBUG_LOG(...)
void info(Args... args)
Definition log.hpp:74
const std::vector< FF > data
ssize_t offset
Definition engine.cpp:36
const auto init
Definition fr.bench.cpp:141
Entry point for Barretenberg command-line interface.
std::atomic< size_t > unique_transcript_index
constexpr bool is_iterable_v
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13