Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: New stdlib Transcript #1219

Merged
merged 11 commits into from
Aug 2, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ class TranscriptManifest {
*/
template <typename FF> 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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <typename Builder> class Transcript {
public:
using field_pt = field_t<Builder>;
using FF = barretenberg::fr;
using VerifierTranscript = proof_system::honk::VerifierTranscript<FF>;
using StdlibTypes = utility::StdlibTypesUtility<Builder>;

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<FF, sizeof...(Strings)> Array of challenges
*/
template <typename... Strings> std::array<field_pt, sizeof...(Strings)> get_challenges(const Strings&... labels)
{
// Compute the indicated challenges from the native transcript
constexpr size_t num_challenges = sizeof...(Strings);
std::array<FF, num_challenges> 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<field_pt, num_challenges> 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 <class T> auto receive_from_prover(const std::string& label)
{
// Extract the native element from the native transcript
T element = native_transcript.template receive_from_prover<T>(label);

// Return the corresponding stdlib type
return StdlibTypes::from_witness(builder, element);
}
};
} // namespace proof_system::plonk::stdlib::recursion::honk
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#include <gtest/gtest.h>

#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<FF, LENGTH>;
using ProverTranscript = ::proof_system::honk::ProverTranscript<FF>;
using VerifierTranscript = ::proof_system::honk::VerifierTranscript<FF>;

/**
* @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<FF, LENGTH> 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<uint32_t>("data");
transcript.get_challenge("alpha");

// round 1
transcript.template receive_from_prover<FF>("scalar");
transcript.template receive_from_prover<Commitment>("commitment");
transcript.get_challenges("beta, gamma");

// round 2
transcript.template receive_from_prover<Univariate>("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<Builder> 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<FF, LENGTH> 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<FF>("scalar");
auto native_commitment = native_transcript.template receive_from_prover<Commitment>("commitment");
auto native_evaluations = native_transcript.template receive_from_prover<std::array<FF, LENGTH>>("evaluations");
auto [native_alpha, native_beta] = native_transcript.get_challenges("alpha", "beta");

// Perform the corresponding operations with the stdlib verifier transcript
Transcript<Builder> stdlib_transcript{ &builder, proof_data };
auto stdlib_scalar = stdlib_transcript.template receive_from_prover<FF>("scalar");
auto stdlib_commitment = stdlib_transcript.template receive_from_prover<Commitment>("commitment");
auto stdlib_evaluations = stdlib_transcript.template receive_from_prover<std::array<FF, LENGTH>>("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
Original file line number Diff line number Diff line change
@@ -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 <typename Builder> class StdlibTypesUtility {
using field_ct = field_t<Builder>;
using witness_ct = witness_t<Builder>;
using fq_ct = bigfield<Builder, barretenberg::Bn254FqParams>;
using element_ct = element<Builder, fq_ct, field_ct, barretenberg::g1>;
using FF = barretenberg::fr;
using Commitment = barretenberg::g1::affine_element;
template <size_t LENGTH> using Univariate = proof_system::honk::sumcheck::Univariate<FF, LENGTH>;

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<field_ct, LENGTH>
*/
template <size_t LENGTH>
static std::array<field_ct, LENGTH> from_witness(Builder* builder, std::array<FF, LENGTH> native_element)
{
std::array<field_ct, LENGTH> 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<field_t> good enough?
* @param native_element
* @return std::array<field_ct, LENGTH>
*/
template <size_t LENGTH>
static std::array<field_ct, LENGTH> from_witness(Builder* builder, Univariate<LENGTH> native_element)
{
std::array<field_ct, LENGTH> 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