Skip to content

Commit

Permalink
feat: New stdlib Transcript (#1219)
Browse files Browse the repository at this point in the history
# 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.
  • Loading branch information
ledwards2225 authored Aug 2, 2023
1 parent 624ffaf commit 2f66de1
Show file tree
Hide file tree
Showing 4 changed files with 358 additions and 0 deletions.
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

0 comments on commit 2f66de1

Please sign in to comment.