Skip to content

Commit

Permalink
feat: (bb) 128-bit challenges (#8406)
Browse files Browse the repository at this point in the history
This PR modifies our Transcript class to achieve the following:

every time a hash function is used to generate challenges, the output is
split into two 128-bit field elements to generate 2 challenges per hash

This change gives us the following benefits:

1. the amount of hashing required to fold/verifier proofs is reduced
2. where challenges map to Verifier scalar multiplications, those scalar
muls are now half-width and can be more efficiently evaluated (requires
additional code to support)

Closes AztecProtocol/barretenberg#741.

---------

Co-authored-by: lucasxia01 <[email protected]>
Co-authored-by: Maxim Vezenov <[email protected]>
  • Loading branch information
3 people authored Sep 10, 2024
1 parent ac88f30 commit d5b2397
Show file tree
Hide file tree
Showing 19 changed files with 264 additions and 1,269 deletions.
115 changes: 69 additions & 46 deletions barretenberg/cpp/src/barretenberg/dsl/acir_proofs/honk_contract.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,34 +292,45 @@ struct Transcript {
Fr lookupGrandProductDelta;
}
library TranscriptLib
{
function generateTranscript(Honk.Proof memory proof,
Honk.VerificationKey memory vk,
bytes32[] calldata publicInputs) internal view returns(Transcript memory t)
library TranscriptLib {
function generateTranscript(Honk.Proof memory proof, bytes32[] calldata publicInputs, uint256 publicInputsSize)
internal
view
returns (Transcript memory t)
{
(t.eta, t.etaTwo, t.etaThree) = generateEtaChallenge(proof, publicInputs);
Fr previousChallenge;
(t.eta, t.etaTwo, t.etaThree, previousChallenge) = generateEtaChallenge(proof, publicInputs, publicInputsSize);
(t.beta, t.gamma) = generateBetaAndGammaChallenges(t.etaThree, proof);
(t.beta, t.gamma, previousChallenge) = generateBetaAndGammaChallenges(previousChallenge, proof);
t.alphas = generateAlphaChallenges(t.gamma, proof);
(t.alphas, previousChallenge) = generateAlphaChallenges(previousChallenge, proof);
t.gateChallenges = generateGateChallenges(t.alphas[NUMBER_OF_ALPHAS - 1]);
(t.gateChallenges, previousChallenge) = generateGateChallenges(previousChallenge);
t.sumCheckUChallenges = generateSumcheckChallenges(proof, t.gateChallenges[CONST_PROOF_SIZE_LOG_N - 1]);
t.rho = generateRhoChallenge(proof, t.sumCheckUChallenges[CONST_PROOF_SIZE_LOG_N - 1]);
(t.sumCheckUChallenges, previousChallenge) = generateSumcheckChallenges(proof, previousChallenge);
(t.rho, previousChallenge) = generateRhoChallenge(proof, previousChallenge);
t.zmY = generateZMYChallenge(t.rho, proof);
(t.zmY, previousChallenge) = generateZMYChallenge(previousChallenge, proof);
(t.zmX, t.zmZ) = generateZMXZChallenges(t.zmY, proof);
(t.zmX, t.zmZ, previousChallenge) = generateZMXZChallenges(previousChallenge, proof);
return t;
}
function generateEtaChallenge(Honk.Proof memory proof, bytes32[] calldata publicInputs)
internal view returns(Fr eta, Fr etaTwo, Fr etaThree)
function splitChallenge(Fr challenge) internal pure returns (Fr first, Fr second) {
uint256 challengeU256 = uint256(Fr.unwrap(challenge));
uint256 lo = challengeU256 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
uint256 hi = challengeU256 >> 128;
first = FrLib.fromBytes32(bytes32(lo));
second = FrLib.fromBytes32(bytes32(hi));
}
function generateEtaChallenge(Honk.Proof memory proof, bytes32[] calldata publicInputs, uint256 publicInputsSize)
internal
view
returns (Fr eta, Fr etaTwo, Fr etaThree, Fr previousChallenge)
{
bytes32[3 + NUMBER_OF_PUBLIC_INPUTS + 12] memory round0;
bytes32[] memory round0 = new bytes32[](3 + NUMBER_OF_PUBLIC_INPUTS + 12);
round0[0] = bytes32(proof.circuitSize);
round0[1] = bytes32(proof.publicInputsSize);
round0[2] = bytes32(proof.publicInputsOffset);
Expand All @@ -342,13 +353,14 @@ library TranscriptLib
round0[3 + NUMBER_OF_PUBLIC_INPUTS + 10] = bytes32(proof.w3.y_0);
round0[3 + NUMBER_OF_PUBLIC_INPUTS + 11] = bytes32(proof.w3.y_1);
eta = FrLib.fromBytes32(keccak256(abi.encodePacked(round0)));
etaTwo = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(eta))));
etaThree = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(etaTwo))));
previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(round0)));
(eta, etaTwo) = splitChallenge(previousChallenge);
previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(previousChallenge))));
Fr unused;
(etaThree, unused) = splitChallenge(previousChallenge);
}
function generateBetaAndGammaChallenges(Fr previousChallenge, Honk.Proof memory proof)
internal view returns(Fr beta, Fr gamma)
function generateBetaAndGammaChallenges(Fr previousChallenge, Honk.Proof memory proof) internal view returns (Fr beta, Fr gamma, Fr nextPreviousChallenge)
{
bytes32[13] memory round1;
round1[0] = FrLib.toBytes32(previousChallenge);
Expand All @@ -365,13 +377,12 @@ library TranscriptLib
round1[11] = bytes32(proof.w4.y_0);
round1[12] = bytes32(proof.w4.y_1);
beta = FrLib.fromBytes32(keccak256(abi.encodePacked(round1)));
gamma = FrLib.fromBytes32(keccak256(abi.encodePacked(beta)));
nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(round1)));
(beta, gamma) = splitChallenge(nextPreviousChallenge);
}
// Alpha challenges non-linearise the gate contributions
function generateAlphaChallenges(Fr previousChallenge, Honk.Proof memory proof)
internal view returns(Fr[NUMBER_OF_ALPHAS] memory alphas)
function generateAlphaChallenges(Fr previousChallenge, Honk.Proof memory proof) internal view returns (Fr[NUMBER_OF_ALPHAS] memory alphas, Fr nextPreviousChallenge)
{
// Generate the original sumcheck alpha 0 by hashing zPerm and zLookup
uint256[9] memory alpha0;
Expand All @@ -385,52 +396,63 @@ library TranscriptLib
alpha0[7] = proof.zPerm.y_0;
alpha0[8] = proof.zPerm.y_1;
alphas[0] = FrLib.fromBytes32(keccak256(abi.encodePacked(alpha0)));
nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(alpha0)));
(alphas[0], alphas[1]) = splitChallenge(nextPreviousChallenge);
Fr prevChallenge = alphas[0];
for (uint256 i = 1; i < NUMBER_OF_ALPHAS; i++) {
prevChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(prevChallenge))));
alphas[i] = prevChallenge;
for (uint256 i = 1; i < NUMBER_OF_ALPHAS / 2; i++) {
nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(nextPreviousChallenge))));
(alphas[2 * i], alphas[2 * i + 1]) = splitChallenge(nextPreviousChallenge);
}
if (((NUMBER_OF_ALPHAS & 1) == 1) && (NUMBER_OF_ALPHAS > 2)) {
nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(nextPreviousChallenge))));
Fr unused;
(alphas[NUMBER_OF_ALPHAS - 1], unused) = splitChallenge(nextPreviousChallenge);
}
}
function generateGateChallenges(Fr previousChallenge) internal view returns(Fr[CONST_PROOF_SIZE_LOG_N] memory gateChallenges)
function generateGateChallenges(Fr previousChallenge) internal view returns (Fr[CONST_PROOF_SIZE_LOG_N] memory gateChallenges, Fr nextPreviousChallenge)
{
for (uint256 i = 0; i < CONST_PROOF_SIZE_LOG_N; i++) {
previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(previousChallenge))));
gateChallenges[i] = previousChallenge;
Fr unused;
(gateChallenges[i], unused) = splitChallenge(previousChallenge);
}
nextPreviousChallenge = previousChallenge;
}
function generateSumcheckChallenges(Honk.Proof memory proof, Fr prevChallenge)
internal view returns(Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckChallenges)
function generateSumcheckChallenges(Honk.Proof memory proof, Fr prevChallenge) internal view returns (Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckChallenges, Fr nextPreviousChallenge)
{
for (uint256 i = 0; i < CONST_PROOF_SIZE_LOG_N; i++) {
Fr[BATCHED_RELATION_PARTIAL_LENGTH + 1] memory univariateChal;
univariateChal[0] = prevChallenge;
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1098): memcpy
for (uint256 j = 0; j < BATCHED_RELATION_PARTIAL_LENGTH; j++) {
univariateChal[j + 1] = proof.sumcheckUnivariates[i][j];
}
sumcheckChallenges[i] = FrLib.fromBytes32(keccak256(abi.encodePacked(univariateChal)));
prevChallenge = sumcheckChallenges[i];
prevChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(univariateChal)));
Fr unused;
(sumcheckChallenges[i], unused) = splitChallenge(prevChallenge);
}
nextPreviousChallenge = prevChallenge;
}
function generateRhoChallenge(Honk.Proof memory proof, Fr prevChallenge) internal view returns(Fr rho)
function generateRhoChallenge(Honk.Proof memory proof, Fr prevChallenge) internal view returns (Fr rho, Fr nextPreviousChallenge)
{
Fr[NUMBER_OF_ENTITIES + 1] memory rhoChallengeElements;
rhoChallengeElements[0] = prevChallenge;
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1098): memcpy
for (uint256 i = 0; i < NUMBER_OF_ENTITIES; i++) {
rhoChallengeElements[i + 1] = proof.sumcheckEvaluations[i];
}
rho = FrLib.fromBytes32(keccak256(abi.encodePacked(rhoChallengeElements)));
nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(rhoChallengeElements)));
Fr unused;
(rho, unused) = splitChallenge(nextPreviousChallenge);
}
function generateZMYChallenge(Fr previousChallenge, Honk.Proof memory proof) internal view returns(Fr zeromorphY)
function generateZMYChallenge(Fr previousChallenge, Honk.Proof memory proof) internal view returns (Fr zeromorphY, Fr nextPreviousChallenge)
{
uint256[CONST_PROOF_SIZE_LOG_N * 4 + 1] memory zmY;
zmY[0] = Fr.unwrap(previousChallenge);
Expand All @@ -442,11 +464,12 @@ library TranscriptLib
zmY[4 + i * 4] = proof.zmCqs[i].y_1;
}
zeromorphY = FrLib.fromBytes32(keccak256(abi.encodePacked(zmY)));
nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(zmY)));
Fr unused;
(zeromorphY, unused) = splitChallenge(nextPreviousChallenge);
}
function generateZMXZChallenges(Fr previousChallenge, Honk.Proof memory proof)
internal view returns(Fr zeromorphX, Fr zeromorphZ)
function generateZMXZChallenges(Fr previousChallenge, Honk.Proof memory proof) internal pure returns (Fr zeromorphX, Fr zeromorphZ, Fr nextPreviousChallenge)
{
uint256[4 + 1] memory buf;
buf[0] = Fr.unwrap(previousChallenge);
Expand All @@ -456,8 +479,8 @@ library TranscriptLib
buf[3] = proof.zmCq.y_0;
buf[4] = proof.zmCq.y_1;
zeromorphX = FrLib.fromBytes32(keccak256(abi.encodePacked(buf)));
zeromorphZ = FrLib.fromBytes32(keccak256(abi.encodePacked(zeromorphX)));
nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(buf)));
(zeromorphX, zeromorphZ) = splitChallenge(nextPreviousChallenge);
}
}
Expand Down Expand Up @@ -1215,7 +1238,7 @@ contract HonkVerifier is IVerifier
}
// Generate the fiat shamir challenges for the whole protocol
Transcript memory t = TranscriptLib.generateTranscript(p, vk, publicInputs);
Transcript memory t = TranscriptLib.generateTranscript(p, publicInputs, vk.publicInputsSize);
// Compute the public input delta
t.publicInputsDelta =
Expand Down
1 change: 1 addition & 0 deletions barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ class Bn254FqParams {

// The modulus is larger than BN254 scalar field modulus, so it maps to two BN254 scalars
static constexpr size_t NUM_BN254_SCALARS = 2;
static constexpr size_t MAX_BITS_PER_ENDOMORPHISM_SCALAR = 128;
};

using fq = field<Bn254FqParams>;
Expand Down
1 change: 1 addition & 0 deletions barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class Bn254FrParams {

// This is a BN254 scalar, so it represents one BN254 scalar
static constexpr size_t NUM_BN254_SCALARS = 1;
static constexpr size_t MAX_BITS_PER_ENDOMORPHISM_SCALAR = 128;
};

using fr = field<Bn254FrParams>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,11 @@ template <typename Flavor> void OinkRecursiveVerifier_<Flavor>::verify()
commitments.z_perm = transcript->template receive_from_prover<Commitment>(domain_separator + labels.z_perm);

RelationSeparator alphas;
for (size_t idx = 0; idx < alphas.size(); idx++) {
alphas[idx] = transcript->template get_challenge<FF>(domain_separator + "alpha_" + std::to_string(idx));
std::array<std::string, Flavor::NUM_SUBRELATIONS - 1> args;
for (size_t idx = 0; idx < alphas.size(); ++idx) {
args[idx] = domain_separator + "alpha_" + std::to_string(idx);
}
alphas = transcript->template get_challenges<FF>(args);

verification_key->relation_parameters =
RelationParameters<FF>{ eta, eta_two, eta_three, beta, gamma, public_input_delta };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1446,5 +1446,6 @@ template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator/
template class cycle_group<bb::StandardCircuitBuilder>;
template class cycle_group<bb::UltraCircuitBuilder>;
template class cycle_group<bb::MegaCircuitBuilder>;
template struct cycle_group<bb::CircuitSimulatorBN254>::cycle_scalar;

} // namespace bb::stdlib
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ template <typename Builder> class cycle_group {
* free from the `batch_mul` algorithm, making the range checks performed by `bigfield` largely redundant.
*/
struct cycle_scalar {
static constexpr size_t LO_BITS = plookup::FixedBaseParams::BITS_PER_LO_SCALAR;
static constexpr size_t LO_BITS = field_t::native::Params::MAX_BITS_PER_ENDOMORPHISM_SCALAR;
static constexpr size_t HI_BITS = NUM_BITS - LO_BITS;
field_t lo;
field_t hi;
Expand Down
20 changes: 18 additions & 2 deletions barretenberg/cpp/src/barretenberg/stdlib/transcript/transcript.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
#include "barretenberg/crypto/poseidon2/poseidon2.hpp"
#include "barretenberg/stdlib/hash/poseidon2/poseidon2.hpp"
#include "barretenberg/stdlib/primitives/field/field_conversion.hpp"
#include "barretenberg/stdlib/primitives/group/cycle_group.hpp"
#include "barretenberg/transcript/transcript.hpp"

namespace bb::stdlib::recursion::honk {

template <typename Builder> struct StdlibTranscriptParams {
Expand All @@ -19,7 +19,23 @@ template <typename Builder> struct StdlibTranscriptParams {
Builder* builder = data[0].get_context();
return stdlib::poseidon2<Builder>::hash(*builder, data);
}

/**
* @brief Split a challenge field element into two half-width challenges
* @details `lo` is 128 bits and `hi` is 126 bits.
* This should provide significantly more than our security parameter bound: 100 bits
*
* @param challenge
* @return std::array<Fr, 2>
*/
static inline std::array<Fr, 2> split_challenge(const Fr& challenge)
{
// use existing field-splitting code in cycle_scalar
using cycle_scalar = typename stdlib::cycle_group<Builder>::cycle_scalar;
const cycle_scalar scalar = cycle_scalar(challenge);
scalar.lo.create_range_constraint(cycle_scalar::LO_BITS);
scalar.hi.create_range_constraint(cycle_scalar::HI_BITS);
return std::array<Fr, 2>{ scalar.lo, scalar.hi };
}
template <typename T> static inline T convert_challenge(const Fr& challenge)
{
Builder* builder = challenge.get_context();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once
#include "barretenberg/ecc/curves/bn254/bn254.hpp"
#include "barretenberg/ecc/curves/bn254/fr.hpp"
#include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp"
#include "barretenberg/plonk_honk_shared/arithmetization/gate_data.hpp"
#include "barretenberg/plonk_honk_shared/types/aggregation_object_type.hpp"
#include "barretenberg/plonk_honk_shared/types/circuit_type.hpp"
Expand Down Expand Up @@ -38,6 +40,7 @@ namespace bb {
class CircuitSimulatorBN254 {
public:
using FF = bb::fr;
using EmbeddedCurve = std::conditional_t<std::same_as<FF, bb::g1::coordinate_field>, curve::BN254, curve::Grumpkin>;
static constexpr CircuitType CIRCUIT_TYPE = CircuitType::ULTRA;
static constexpr std::string_view NAME_STRING = "SIMULATOR";
bool contains_recursive_proof = false;
Expand Down
Loading

0 comments on commit d5b2397

Please sign in to comment.