Skip to content

Commit 12a093d

Browse files
feat: (bb) remove redundant constraints on field/group elements when using goblin plonk (#8409)
This PR adds distinct classes for goblin plonk group elements and coordinate scalars in the stdlib so that we can refine the implementation of these objects without proliferating edge cases in the rest of our codebase This Pr reduces the cost of `ProtogalaxyRecursiveTests/0.RecursiveFoldingTest` from 24,630 gates to 14,106 gates. `stdlib::element` is now a class alias that points to either the default element class definition or the goblin plonk class definition depending on whether goblin plonk is supported. This allows us to apply the following improvements/useful restrictions: 1. goblin plonk group elements no longer apply `on_curve` checks when created (performed in the eccvm) 2. goblin plonk coordinate field elements no longer have range constraints applied to them (performed in the translator circuit) 3. goblin plonk coordinate field elements no longer generate constraints when `assert_is_in_field` is applied (performed in the translator circuit) 4. goblin plonk coordinate field elements do not have arithmetic operations exposed (manipulation of goblin plonk group elements should happen exclusively through the eccvm) In addition, this PR improve the handling of checking whether bn254 points are at infinity when consuming points from a transcript via `field_conversion`
1 parent aa67a14 commit 12a093d

18 files changed

+617
-276
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#pragma once
2+
#include "../bigfield/bigfield.hpp"
3+
#include "../circuit_builders/circuit_builders_fwd.hpp"
4+
#include "../field/field.hpp"
5+
6+
namespace bb::stdlib {
7+
8+
/**
9+
* @brief goblin_field wraps x/y coordinates of bn254 group elements when using goblin
10+
* @details this class exists because we do not want to parametrise goblin bn254 coordinates with bigfield.
11+
* bigfield generates a large number of constraints to apply checks that are not needed for goblin coordinates
12+
* This is because, in the goblin context we can apply the following heuristics:
13+
* 1. goblin coordinate field elements are range-constrained in the Translator circuit (no need to range
14+
* constrain here)
15+
* 2. field elements that come out of the ECCVM are well-formed, we do not need to call `assert_is_in_field`
16+
* 3. there should be no need to apply arithmetic to goblin coordinate field elements in-circuit
17+
* Having a distinct class for `goblin_field` allows us to harvest these optimisations without a proliferation
18+
* of edge cases and bloated logic in other classes
19+
* @tparam Builder
20+
*/
21+
template <class Builder> class goblin_field {
22+
public:
23+
static constexpr uint1024_t DEFAULT_MAXIMUM_REMAINDER =
24+
bigfield<Builder, bb::Bn254FqParams>::DEFAULT_MAXIMUM_REMAINDER;
25+
static constexpr size_t NUM_LIMBS = bigfield<Builder, bb::Bn254FqParams>::NUM_LIMBS;
26+
static constexpr size_t NUM_LIMB_BITS = bigfield<Builder, bb::Bn254FqParams>::NUM_LIMB_BITS;
27+
static constexpr size_t NUM_LAST_LIMB_BITS = bigfield<Builder, bb::Bn254FqParams>::NUM_LAST_LIMB_BITS;
28+
29+
using field_ct = stdlib::field_t<Builder>;
30+
using bool_ct = stdlib::bool_t<Builder>;
31+
std::array<field_ct, 2> limbs;
32+
33+
// constructors mirror bigfield constructors
34+
goblin_field()
35+
: limbs{ 0, 0 }
36+
{}
37+
goblin_field(Builder* parent_context, const uint256_t& value)
38+
{
39+
(*this) = goblin_field(bb::fq(value));
40+
limbs[0].context = parent_context;
41+
limbs[1].context = parent_context;
42+
}
43+
goblin_field(bb::fq input)
44+
{
45+
uint256_t converted(input);
46+
uint256_t lo_v = converted.slice(0, NUM_LIMB_BITS * 2);
47+
uint256_t hi_v = converted.slice(NUM_LIMB_BITS * 2, NUM_LIMB_BITS * 3 + NUM_LAST_LIMB_BITS);
48+
limbs = { bb::fr(lo_v), bb::fr(hi_v) };
49+
}
50+
goblin_field(field_ct lo, field_ct hi)
51+
: limbs{ lo, hi }
52+
{}
53+
54+
// N.B. this method is because AggregationState expects group element coordinates to be split into 4 slices
55+
// (we could update to only use 2 for Mega but that feels complex)
56+
goblin_field(field_ct lolo, field_ct lohi, field_ct hilo, field_ct hihi, [[maybe_unused]] bool can_overflow = false)
57+
: limbs{ lolo + lohi * (uint256_t(1) << NUM_LIMB_BITS), hilo + hihi * (uint256_t(1) << NUM_LIMB_BITS) }
58+
{}
59+
60+
void assert_equal(const goblin_field& other) const
61+
{
62+
limbs[0].assert_equal(other.limbs[0]);
63+
limbs[1].assert_equal(other.limbs[1]);
64+
}
65+
static goblin_field zero() { return goblin_field{ 0, 0 }; }
66+
67+
static goblin_field from_witness(Builder* ctx, bb::fq input)
68+
{
69+
uint256_t converted(input);
70+
uint256_t lo_v = converted.slice(0, NUM_LIMB_BITS * 2);
71+
uint256_t hi_v = converted.slice(NUM_LIMB_BITS * 2, NUM_LIMB_BITS * 3 + NUM_LAST_LIMB_BITS);
72+
field_ct lo = field_ct::from_witness(ctx, lo_v);
73+
field_ct hi = field_ct::from_witness(ctx, hi_v);
74+
return goblin_field(lo, hi);
75+
}
76+
77+
static goblin_field conditional_assign(const bool_ct& predicate, const goblin_field& lhs, goblin_field& rhs)
78+
{
79+
goblin_field result;
80+
result.limbs = {
81+
field_ct::conditional_assign(predicate, lhs.limbs[0], rhs.limbs[0]),
82+
field_ct::conditional_assign(predicate, lhs.limbs[1], rhs.limbs[1]),
83+
};
84+
return result;
85+
}
86+
87+
// matches the interface for bigfield
88+
uint512_t get_value() const
89+
{
90+
uint256_t lo = limbs[0].get_value();
91+
uint256_t hi = limbs[1].get_value();
92+
uint256_t result = lo + (hi << 136);
93+
return result;
94+
}
95+
96+
// matches the interface for bigfield
97+
uint512_t get_maximum_value() const { return (*this).get_value(); }
98+
99+
Builder* get_context() const
100+
{
101+
if (limbs[0].get_context()) {
102+
return limbs[0].get_context();
103+
}
104+
return limbs[1].get_context();
105+
}
106+
107+
// done in the translator circuit
108+
void assert_is_in_field(){};
109+
};
110+
template <typename C> inline std::ostream& operator<<(std::ostream& os, goblin_field<C> const& v)
111+
{
112+
return os << "{ " << v.limbs[0] << " , " << v.limbs[1] << " }";
113+
}
114+
} // namespace bb::stdlib

barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup.hpp

+30-34
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include "../bigfield/bigfield.hpp"
4+
#include "../bigfield/goblin_field.hpp"
45
#include "../byte_array/byte_array.hpp"
56
#include "../circuit_builders/circuit_builders_fwd.hpp"
67
#include "../field/field.hpp"
@@ -9,21 +10,16 @@
910
#include "barretenberg/ecc/curves/bn254/g1.hpp"
1011
#include "barretenberg/ecc/curves/secp256k1/secp256k1.hpp"
1112
#include "barretenberg/ecc/curves/secp256r1/secp256r1.hpp"
13+
#include "barretenberg/stdlib/primitives/biggroup/biggroup_goblin.hpp"
1214

13-
// TODO(https://github.com/AztecProtocol/barretenberg/issues/707) If using a a circuit builder with Goblin, which is
14-
// designed to have efficient bb::g1 operations, a developer might accidentally write inefficient circuits
15-
// using biggroup functions that do not use the OpQueue. We use this concept to prevent compilation of such functions.
16-
template <typename Builder, typename NativeGroup>
17-
concept IsNotGoblinInefficiencyTrap = !(IsMegaBuilder<Builder> && std::same_as<NativeGroup, bb::g1>);
18-
19-
namespace bb::stdlib {
15+
namespace bb::stdlib::element_default {
2016

2117
// ( ͡° ͜ʖ ͡°)
2218
template <class Builder, class Fq, class Fr, class NativeGroup> class element {
2319
public:
2420
using bool_ct = stdlib::bool_t<Builder>;
2521
using biggroup_tag = element; // Facilitates a constexpr check IsBigGroup
26-
22+
using BaseField = Fq;
2723
struct secp256k1_wnaf {
2824
std::vector<field_t<Builder>> wnaf;
2925
field_t<Builder> positive_skew;
@@ -177,22 +173,13 @@ template <class Builder, class Fq, class Fr, class NativeGroup> class element {
177173
* We can chain repeated point additions together, where we only require 2 non-native field multiplications per
178174
* point addition, instead of 3
179175
**/
180-
static chain_add_accumulator chain_add_start(const element& p1, const element& p2)
181-
requires(IsNotGoblinInefficiencyTrap<Builder, NativeGroup>);
182-
static chain_add_accumulator chain_add(const element& p1, const chain_add_accumulator& accumulator)
183-
requires(IsNotGoblinInefficiencyTrap<Builder, NativeGroup>);
184-
static element chain_add_end(const chain_add_accumulator& accumulator)
185-
requires(IsNotGoblinInefficiencyTrap<Builder, NativeGroup>);
186-
187-
element montgomery_ladder(const element& other) const
188-
requires(IsNotGoblinInefficiencyTrap<Builder, NativeGroup>);
189-
element montgomery_ladder(const chain_add_accumulator& accumulator)
190-
requires(IsNotGoblinInefficiencyTrap<Builder, NativeGroup>);
191-
element multiple_montgomery_ladder(const std::vector<chain_add_accumulator>& to_add) const
192-
requires(IsNotGoblinInefficiencyTrap<Builder, NativeGroup>);
193-
194-
element quadruple_and_add(const std::vector<element>& to_add) const
195-
requires(IsNotGoblinInefficiencyTrap<Builder, NativeGroup>);
176+
static chain_add_accumulator chain_add_start(const element& p1, const element& p2);
177+
static chain_add_accumulator chain_add(const element& p1, const chain_add_accumulator& accumulator);
178+
static element chain_add_end(const chain_add_accumulator& accumulator);
179+
element montgomery_ladder(const element& other) const;
180+
element montgomery_ladder(const chain_add_accumulator& accumulator);
181+
element multiple_montgomery_ladder(const std::vector<chain_add_accumulator>& to_add) const;
182+
element quadruple_and_add(const std::vector<element>& to_add) const;
196183

197184
typename NativeGroup::affine_element get_value() const
198185
{
@@ -222,12 +209,6 @@ template <class Builder, class Fq, class Fr, class NativeGroup> class element {
222209
const size_t max_num_bits = 0,
223210
const bool with_edgecases = false);
224211

225-
// TODO(https://github.com/AztecProtocol/barretenberg/issues/707) max_num_bits is unused; could implement and use
226-
// this to optimize other operations.
227-
static element goblin_batch_mul(const std::vector<element>& points,
228-
const std::vector<Fr>& scalars,
229-
const size_t max_num_bits = 0);
230-
231212
// we want to conditionally compile this method iff our curve params are the BN254 curve.
232213
// This is a bit tricky to do with `std::enable_if`, because `bn254_endo_batch_mul` is a member function of a class
233214
// template
@@ -938,16 +919,31 @@ template <class Builder, class Fq, class Fr, class NativeGroup> class element {
938919
typename std::conditional<HasPlookup<Builder>, batch_lookup_table_plookup<>, batch_lookup_table_base>::type;
939920
};
940921

941-
template <typename T>
942-
concept IsBigGroup = std::is_same_v<typename T::biggroup_tag, T>;
943-
944922
template <typename C, typename Fq, typename Fr, typename G>
945923
inline std::ostream& operator<<(std::ostream& os, element<C, Fq, Fr, G> const& v)
946924
{
947925
return os << "{ " << v.x << " , " << v.y << " }";
948926
}
949-
} // namespace bb::stdlib
927+
} // namespace bb::stdlib::element_default
950928

929+
namespace bb::stdlib {
930+
template <typename T>
931+
concept IsBigGroup = std::is_same_v<typename T::biggroup_tag, T>;
932+
933+
template <typename Builder, class Fq, class Fr, class NativeGroup>
934+
concept IsGoblinBigGroup =
935+
IsMegaBuilder<Builder> && std::same_as<Fq, bb::stdlib::bigfield<Builder, bb::Bn254FqParams>> &&
936+
std::same_as<Fr, bb::stdlib::field_t<Builder>> && std::same_as<NativeGroup, bb::g1>;
937+
938+
/**
939+
* @brief element wraps either element_default::element or element_goblin::goblin_element depending on parametrisation
940+
* @details if C = MegaBuilder, G = bn254, Fq = bigfield<C, bb::Bn254FqParams>, Fr = field_t then we're cooking
941+
*/
942+
template <typename C, typename Fq, typename Fr, typename G>
943+
using element = std::conditional_t<IsGoblinBigGroup<C, Fq, Fr, G>,
944+
element_goblin::goblin_element<C, goblin_field<C>, Fr, G>,
945+
element_default::element<C, Fq, Fr, G>>;
946+
} // namespace bb::stdlib
951947
#include "biggroup_batch_mul.hpp"
952948
#include "biggroup_bn254.hpp"
953949
#include "biggroup_goblin.hpp"

barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup.test.cpp

+17-16
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ namespace {
1616
auto& engine = numeric::get_debug_randomness();
1717
}
1818

19+
template <typename T>
20+
concept HasGoblinBuilder = IsMegaBuilder<typename T::Curve::Builder>;
21+
1922
// One can only define a TYPED_TEST with a single template paramter.
2023
// Our workaround is to pass parameters of the following type.
2124
template <typename _Curve, bool _use_bigfield = false> struct TestType {
@@ -1191,9 +1194,6 @@ using TestTypes = testing::Types<TestType<stdlib::bn254<bb::StandardCircuitBuild
11911194

11921195
TYPED_TEST_SUITE(stdlib_biggroup, TestTypes);
11931196

1194-
template <typename T>
1195-
concept HasGoblinBuilder = IsMegaBuilder<typename T::Curve::Builder>;
1196-
11971197
TYPED_TEST(stdlib_biggroup, add)
11981198
{
11991199

@@ -1304,7 +1304,7 @@ HEAVY_TYPED_TEST(stdlib_biggroup, multiple_montgomery_ladder)
13041304
HEAVY_TYPED_TEST(stdlib_biggroup, compute_naf)
13051305
{
13061306
// ULTRATODO: make this work for secp curves
1307-
if constexpr (TypeParam::Curve::type == CurveType::BN254) {
1307+
if constexpr ((TypeParam::Curve::type == CurveType::BN254) && !HasGoblinBuilder<TypeParam>) {
13081308
size_t num_repetitions = 1;
13091309
for (size_t i = 0; i < num_repetitions; i++) {
13101310
TestFixture::test_compute_naf();
@@ -1318,8 +1318,8 @@ HEAVY_TYPED_TEST(stdlib_biggroup, compute_naf)
13181318
HEAVY_TYPED_TEST(stdlib_biggroup, wnaf_batch_mul)
13191319
{
13201320
if constexpr (HasPlookup<typename TypeParam::Curve::Builder>) {
1321-
if constexpr (HasGoblinBuilder<TypeParam>) {
1322-
GTEST_SKIP() << "https://github.com/AztecProtocol/barretenberg/issues/707";
1321+
if constexpr (TypeParam::Curve::type == CurveType::BN254 && HasGoblinBuilder<TypeParam>) {
1322+
GTEST_SKIP();
13231323
} else {
13241324
TestFixture::test_compute_wnaf();
13251325
};
@@ -1332,8 +1332,8 @@ HEAVY_TYPED_TEST(stdlib_biggroup, wnaf_batch_mul)
13321332
HEAVY_TYPED_TEST(stdlib_biggroup, wnaf_batch_mul_edge_cases)
13331333
{
13341334
if constexpr (HasPlookup<typename TypeParam::Curve::Builder>) {
1335-
if constexpr (HasGoblinBuilder<TypeParam>) {
1336-
GTEST_SKIP() << "https://github.com/AztecProtocol/barretenberg/issues/707";
1335+
if constexpr (TypeParam::Curve::type == CurveType::BN254 && HasGoblinBuilder<TypeParam>) {
1336+
GTEST_SKIP();
13371337
} else {
13381338
TestFixture::test_compute_wnaf();
13391339
};
@@ -1346,7 +1346,8 @@ HEAVY_TYPED_TEST(stdlib_biggroup, wnaf_batch_mul_edge_cases)
13461346
case where Fr is a bigfield. */
13471347
HEAVY_TYPED_TEST(stdlib_biggroup, compute_wnaf)
13481348
{
1349-
if constexpr (!HasPlookup<typename TypeParam::Curve::Builder> && TypeParam::use_bigfield) {
1349+
if constexpr ((!HasPlookup<typename TypeParam::Curve::Builder> && TypeParam::use_bigfield) ||
1350+
(TypeParam::Curve::type == CurveType::BN254 && HasGoblinBuilder<TypeParam>)) {
13501351
GTEST_SKIP();
13511352
} else {
13521353
TestFixture::test_compute_wnaf();
@@ -1360,8 +1361,8 @@ HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_short_scalars)
13601361
if constexpr (TypeParam::use_bigfield) {
13611362
GTEST_SKIP();
13621363
} else {
1363-
if constexpr (HasGoblinBuilder<TypeParam>) {
1364-
GTEST_SKIP() << "https://github.com/AztecProtocol/barretenberg/issues/707";
1364+
if constexpr (TypeParam::Curve::type == CurveType::BN254 && HasGoblinBuilder<TypeParam>) {
1365+
GTEST_SKIP();
13651366
} else {
13661367
TestFixture::test_batch_mul_short_scalars();
13671368
};
@@ -1372,8 +1373,8 @@ HEAVY_TYPED_TEST(stdlib_biggroup, wnaf_batch_mul_128_bit)
13721373
if constexpr (TypeParam::use_bigfield) {
13731374
GTEST_SKIP();
13741375
} else {
1375-
if constexpr (HasGoblinBuilder<TypeParam>) {
1376-
GTEST_SKIP() << "https://github.com/AztecProtocol/barretenberg/issues/707";
1376+
if constexpr (TypeParam::Curve::type == CurveType::BN254 && HasGoblinBuilder<TypeParam>) {
1377+
GTEST_SKIP();
13771378
} else {
13781379
TestFixture::test_wnaf_batch_mul_128_bit();
13791380
};
@@ -1393,7 +1394,7 @@ HEAVY_TYPED_TEST(stdlib_biggroup, bn254_endo_batch_mul)
13931394
{
13941395
if constexpr (TypeParam::Curve::type == CurveType::BN254 && !TypeParam::use_bigfield) {
13951396
if constexpr (HasGoblinBuilder<TypeParam>) {
1396-
GTEST_SKIP() << "https://github.com/AztecProtocol/barretenberg/issues/707";
1397+
GTEST_SKIP();
13971398
} else {
13981399
TestFixture::test_bn254_endo_batch_mul();
13991400
};
@@ -1405,7 +1406,7 @@ HEAVY_TYPED_TEST(stdlib_biggroup, mixed_mul_bn254_endo)
14051406
{
14061407
if constexpr (TypeParam::Curve::type == CurveType::BN254 && !TypeParam::use_bigfield) {
14071408
if constexpr (HasGoblinBuilder<TypeParam>) {
1408-
GTEST_SKIP() << "https://github.com/AztecProtocol/barretenberg/issues/707";
1409+
GTEST_SKIP();
14091410
} else {
14101411
TestFixture::test_mixed_mul_bn254_endo();
14111412
};
@@ -1438,4 +1439,4 @@ HEAVY_TYPED_TEST(stdlib_biggroup, ecdsa_mul_secp256k1)
14381439
} else {
14391440
GTEST_SKIP();
14401441
}
1441-
}
1442+
}

barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_batch_mul.hpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
#include "barretenberg/stdlib/primitives/biggroup/biggroup_edgecase_handling.hpp"
44
#include <cstddef>
5-
namespace bb::stdlib {
5+
namespace bb::stdlib::element_default {
66

77
/**
88
* @brief Multiscalar multiplication that utilizes 4-bit wNAF lookup tables.
@@ -62,4 +62,4 @@ element<C, Fq, Fr, G> element<C, Fq, Fr, G>::wnaf_batch_mul(const std::vector<el
6262
accumulator -= offset_generators.second;
6363
return accumulator;
6464
}
65-
} // namespace bb::stdlib
65+
} // namespace bb::stdlib::element_default

0 commit comments

Comments
 (0)