From 2f66de15212a5b6eb398e0919ae3ad4ec572fde0 Mon Sep 17 00:00:00 2001 From: ledwards2225 <98505400+ledwards2225@users.noreply.github.com> Date: Wed, 2 Aug 2023 08:46:15 -0700 Subject: [PATCH] feat: New stdlib Transcript (#1219) # Description Basic structure for a new stdlib Transcript corresponding to the new native transcript used by the Honk proving systems. Implements all functions required with the CAVEAT that no constraints are imposed for hashing in `get_challenge`. May be useful to do this properly at some point but avoiding it for now since it was non-trivial and the hash will change anyway. # Checklist: - [ ] I have reviewed my diff in github, line by line. - [ ] Every change is related to the PR description. - [ ] I have [linked](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) this pull request to the issue(s) that it resolves. - [ ] There are no unexpected formatting changes, superfluous debug logs, or commented-out code. - [ ] The branch has been merged or rebased against the head of its merge target. - [ ] I'm happy for the PR to be merged at the reviewer's next convenience. --- .../honk/transcript/transcript.hpp | 2 + .../recursion/honk/transcript/trancript.hpp | 98 +++++++++++ .../honk/transcript/transcript.test.cpp | 159 ++++++++++++++++++ .../barretenberg/stdlib/utility/utility.hpp | 99 +++++++++++ 4 files changed, 358 insertions(+) create mode 100644 circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/trancript.hpp create mode 100644 circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/transcript.test.cpp create mode 100644 circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/utility/utility.hpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/transcript/transcript.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/transcript/transcript.hpp index 1ee7a101af8..b503bd49e8d 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/transcript/transcript.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/honk/transcript/transcript.hpp @@ -65,7 +65,9 @@ class TranscriptManifest { */ template class BaseTranscript { // TODO(Adrian): Make these tweakable + public: static constexpr size_t HASH_OUTPUT_SIZE = 32; + private: static constexpr size_t MIN_BYTES_PER_CHALLENGE = 128 / 8; // 128 bit challenges size_t round_number = 0; diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/trancript.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/trancript.hpp new file mode 100644 index 00000000000..be4707af4a5 --- /dev/null +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/trancript.hpp @@ -0,0 +1,98 @@ +#pragma once + +#include "barretenberg/ecc/curves/bn254/fq.hpp" +#include "barretenberg/ecc/curves/bn254/fr.hpp" +#include "barretenberg/ecc/curves/bn254/g1.hpp" +#include "barretenberg/honk/sumcheck/polynomials/univariate.hpp" +#include "barretenberg/honk/transcript/transcript.hpp" + +#include "barretenberg/stdlib/primitives/bigfield/bigfield.hpp" +#include "barretenberg/stdlib/primitives/biggroup/biggroup.hpp" +#include "barretenberg/stdlib/primitives/field/field.hpp" +#include "barretenberg/stdlib/utility/utility.hpp" + +//TODO(luke): this namespace will be sensible once stdlib is moved out of the plonk namespace +namespace proof_system::plonk::stdlib::recursion::honk { +template class Transcript { + public: + using field_pt = field_t; + using FF = barretenberg::fr; + using VerifierTranscript = proof_system::honk::VerifierTranscript; + using StdlibTypes = utility::StdlibTypesUtility; + + static constexpr size_t HASH_OUTPUT_SIZE = VerifierTranscript::HASH_OUTPUT_SIZE; + + VerifierTranscript native_transcript; + Builder* builder; + + Transcript(Builder* builder, auto proof_data) + : native_transcript(proof_data) + , builder(builder){}; + + /** + * @brief Get the underlying native transcript manifest (primarily for debugging) + * + */ + auto get_manifest() const { return native_transcript.get_manifest(); }; + + /** + * @brief Compute the challenges (more than 1) indicated by labels + * + * @tparam Strings + * @param labels Names of the challenges to be computed + * @return std::array Array of challenges + */ + template std::array get_challenges(const Strings&... labels) + { + // Compute the indicated challenges from the native transcript + constexpr size_t num_challenges = sizeof...(Strings); + std::array native_challenges{}; + native_challenges = native_transcript.get_challenges(labels...); + + /* + * TODO(#1351): Do stdlib hashing here. E.g., for the current pedersen/blake setup, we could write data into a + * byte_array as it is received from prover, then compress via pedersen and apply blake3s. Not doing this now + * since it's a pain and we'll be revamping our hashing anyway. For now, simply convert the native hashes to + * stdlib types without adding any hashing constraints. + */ + std::array challenges; + for (size_t i = 0; i < num_challenges; ++i) { + challenges[i] = native_challenges[i]; + } + + return challenges; + } + + /** + * @brief Compute the single challenge indicated by the input label + * + * @param label Name of challenge + * @return field_pt Challenge + */ + field_pt get_challenge(const std::string& label) + { + // Compute the indicated challenge from the native transcript + auto native_challenge = native_transcript.get_challenge(label); + + // TODO(1351): Stdlib hashing here... + + return field_pt(native_challenge); + } + + /** + * @brief Extract a native element from the transcript and return a corresponding stdlib type + * + * @tparam T Type of the native element to be extracted + * @param label Name of the element + * @return The corresponding element of appropriate stdlib type + */ + template auto receive_from_prover(const std::string& label) + { + // Extract the native element from the native transcript + T element = native_transcript.template receive_from_prover(label); + + // Return the corresponding stdlib type + return StdlibTypes::from_witness(builder, element); + } +}; +} // namespace proof_system::plonk::stdlib::recursion::honk diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/transcript.test.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/transcript.test.cpp new file mode 100644 index 00000000000..33f70306c3a --- /dev/null +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/transcript/transcript.test.cpp @@ -0,0 +1,159 @@ +#include + +#include "barretenberg/ecc/curves/bn254/fr.hpp" +#include "barretenberg/ecc/curves/bn254/g1.hpp" +#include "barretenberg/honk/sumcheck/polynomials/univariate.hpp" +#include "barretenberg/honk/transcript/transcript.hpp" +#include "barretenberg/stdlib/recursion/honk/transcript/trancript.hpp" + +namespace proof_system::plonk::stdlib::recursion::honk { + +using Builder = UltraCircuitBuilder; + +using FF = barretenberg::fr; +using Commitment = barretenberg::g1::affine_element; +using Point = barretenberg::g1::element; +constexpr size_t LENGTH = 8; // arbitrary +using Univariate = proof_system::honk::sumcheck::Univariate; +using ProverTranscript = ::proof_system::honk::ProverTranscript; +using VerifierTranscript = ::proof_system::honk::VerifierTranscript; + +/** + * @brief Create some mock data and then add it to the transcript in various mock rounds + * + * @param prover_transcript + * @return auto proof_data + */ +auto generate_mock_proof_data(auto prover_transcript) +{ + uint32_t data = 25; + auto scalar = FF::random_element(); + auto commitment = Commitment::one(); + + std::array evaluations; + for (auto& eval : evaluations) { + eval = FF::random_element(); + } + auto univariate = Univariate(evaluations); + + // round 0 + prover_transcript.send_to_verifier("data", data); + prover_transcript.get_challenge("alpha"); + + // round 1 + prover_transcript.send_to_verifier("scalar", scalar); + prover_transcript.send_to_verifier("commitment", commitment); + prover_transcript.get_challenges("beta, gamma"); + + // round 2 + prover_transcript.send_to_verifier("univariate", univariate); + prover_transcript.get_challenges("gamma", "delta"); + + return prover_transcript.proof_data; +} + +/** + * @brief Perform series of verifier transcript operations + * @details Operations are designed to correspond to those performed by a prover transcript from which the verifier + * transcript was initialized. + * + * @param transcript Either a native or stdlib verifier transcript + */ +void perform_mock_verifier_transcript_operations(auto transcript) +{ + // round 0 + transcript.template receive_from_prover("data"); + transcript.get_challenge("alpha"); + + // round 1 + transcript.template receive_from_prover("scalar"); + transcript.template receive_from_prover("commitment"); + transcript.get_challenges("beta, gamma"); + + // round 2 + transcript.template receive_from_prover("univariate"); + transcript.get_challenges("gamma", "delta"); +} + +/** + * @brief Test basic transcript functionality and check circuit + * @details Implicitly ensures stdlib interface is identical to native + * @todo(luke): Underlying circuit is nearly trivial until transcript implements hashing constraints + */ +TEST(stdlib_honk_transcript, basic_transcript_operations) +{ + Builder builder; + + // Instantiate a Prover Transcript and use it to generate some mock proof data + ProverTranscript prover_transcript; + auto proof_data = generate_mock_proof_data(prover_transcript); + + // Instantiate a (native) Verifier Transcript with the proof data and perform some mock transcript operations + VerifierTranscript native_transcript(proof_data); + perform_mock_verifier_transcript_operations(native_transcript); + + // Confirm that Prover and Verifier transcripts have generated the same manifest via the operations performed + EXPECT_EQ(prover_transcript.get_manifest(), native_transcript.get_manifest()); + + // Instantiate a stdlib Transcript and perform the same operations + Transcript transcript{ &builder, proof_data }; + perform_mock_verifier_transcript_operations(transcript); + + // Confirm that the native and stdlib transcripts have generated the same manifest + EXPECT_EQ(transcript.get_manifest(), native_transcript.get_manifest()); + + // TODO(luke): This doesn't check much of anything until hashing is constrained in the stdlib transcript + EXPECT_TRUE(builder.check_circuit()); +} + +/** + * @brief Check that native and stdlib verifier transcript functions produce equivalent outputs + * + */ +TEST(stdlib_honk_transcript, return_values) +{ + Builder builder; + + // Define some mock data for a mock proof + auto scalar = FF::random_element(); + auto commitment = Commitment::one() * FF::random_element(); + + const size_t LENGTH = 10; // arbitrary + std::array evaluations; + for (auto& eval : evaluations) { + eval = FF::random_element(); + } + + // Construct a mock proof via the prover transcript + ProverTranscript prover_transcript; + prover_transcript.send_to_verifier("scalar", scalar); + prover_transcript.send_to_verifier("commitment", commitment); + prover_transcript.send_to_verifier("evaluations", evaluations); + prover_transcript.get_challenges("alpha, beta"); + auto proof_data = prover_transcript.proof_data; + + // Perform the corresponding operations with the native verifier transcript + VerifierTranscript native_transcript(proof_data); + auto native_scalar = native_transcript.template receive_from_prover("scalar"); + auto native_commitment = native_transcript.template receive_from_prover("commitment"); + auto native_evaluations = native_transcript.template receive_from_prover>("evaluations"); + auto [native_alpha, native_beta] = native_transcript.get_challenges("alpha", "beta"); + + // Perform the corresponding operations with the stdlib verifier transcript + Transcript stdlib_transcript{ &builder, proof_data }; + auto stdlib_scalar = stdlib_transcript.template receive_from_prover("scalar"); + auto stdlib_commitment = stdlib_transcript.template receive_from_prover("commitment"); + auto stdlib_evaluations = stdlib_transcript.template receive_from_prover>("evaluations"); + auto [stdlib_alpha, stdlib_beta] = stdlib_transcript.get_challenges("alpha", "beta"); + + // Confirm that return values are equivalent + EXPECT_EQ(native_scalar, stdlib_scalar.get_value()); + EXPECT_EQ(native_commitment, stdlib_commitment.get_value()); + for (size_t i = 0; i < LENGTH; ++i) { + EXPECT_EQ(native_evaluations[i], stdlib_evaluations[i].get_value()); + } + EXPECT_EQ(native_alpha, stdlib_alpha.get_value()); + EXPECT_EQ(native_beta, stdlib_beta.get_value()); +} + +} // namespace proof_system::plonk::stdlib::recursion::honk \ No newline at end of file diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/utility/utility.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/utility/utility.hpp new file mode 100644 index 00000000000..c7bca963fc4 --- /dev/null +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/utility/utility.hpp @@ -0,0 +1,99 @@ +#pragma once + +#include "barretenberg/ecc/curves/bn254/fq.hpp" +#include "barretenberg/ecc/curves/bn254/fr.hpp" +#include "barretenberg/ecc/curves/bn254/g1.hpp" +#include "barretenberg/honk/sumcheck/polynomials/univariate.hpp" +#include "barretenberg/honk/transcript/transcript.hpp" + +#include "barretenberg/stdlib/primitives/bigfield/bigfield.hpp" +#include "barretenberg/stdlib/primitives/biggroup/biggroup.hpp" +#include "barretenberg/stdlib/primitives/field/field.hpp" + +namespace proof_system::plonk::stdlib::recursion::utility { + +/** + * @brief Utility class for converting native types to corresponding stdlib types + * + * @details Used to facilitate conversion of various native types (uint32_t, field, group, Univarite, etc.) to + * corresponding stdlib types. Useful for example for obtaining stdlib types in the recursive trancript from native + * types upon deserialization from the native transcript. + * + * @todo Eliminate the need for these somehow? + * @tparam Builder + */ +template class StdlibTypesUtility { + using field_ct = field_t; + using witness_ct = witness_t; + using fq_ct = bigfield; + using element_ct = element; + using FF = barretenberg::fr; + using Commitment = barretenberg::g1::affine_element; + template using Univariate = proof_system::honk::sumcheck::Univariate; + + public: + /** + * @brief Construct stdlib field from uint32_t + * + * @param element + * @return field_ct + */ + static field_ct from_witness(Builder* builder, uint32_t native_element) + { + return field_ct::from_witness(builder, native_element); + } + + /** + * @brief Construct stdlib field from native field type + * + * @param native_element + * @return field_ct + */ + static field_ct from_witness(Builder* builder, FF native_element) + { + return field_ct::from_witness(builder, native_element); + } + + /** + * @brief Construct stdlib group from native affine group element type + * + * @param native_element + * @return field_ct + */ + static element_ct from_witness(Builder* builder, Commitment native_element) + { + return element_ct::from_witness(builder, native_element); + } + + /** + * @brief Construct field_t array from native field array + * @param native_element Array of FF + * @return std::array + */ + template + static std::array from_witness(Builder* builder, std::array native_element) + { + std::array element; + for (size_t i = 0; i < LENGTH; ++i) { + element[i] = field_ct::from_witness(builder, native_element[i]); + } + return element; + } + + /** + * @brief Construct field_t array from native Univariate type + * TODO(luke): do we need a stdlib Univariate or is std::array good enough? + * @param native_element + * @return std::array + */ + template + static std::array from_witness(Builder* builder, Univariate native_element) + { + std::array element; + for (size_t i = 0; i < LENGTH; ++i) { + element[i] = field_ct::from_witness(builder, native_element.value_at(i)); + } + return element; + } +}; +} // namespace proof_system::plonk::stdlib::recursion::utility \ No newline at end of file