Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
straus_lookup_table.cpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Complete, auditors: [Luke], commit: a48c205d6dcd4338f5b83b4fda18bff6015be07b}
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
8#include "./cycle_group.hpp"
10
11namespace bb::stdlib {
12
26template <typename Builder>
28 const Element& base_point, const Element& offset_generator, size_t table_bits)
29{
30 const size_t table_size = 1UL << table_bits;
31 std::vector<Element> hints;
32 hints.emplace_back(offset_generator);
33 for (size_t i = 1; i < table_size; ++i) {
34 hints.emplace_back(hints[i - 1] + base_point);
35 }
36 return hints;
37}
38
50template <typename Builder>
52 const cycle_group<Builder>& base_point,
53 const cycle_group<Builder>& offset_generator,
54 size_t table_bits,
56 : _context(context)
57 , _table_bits(table_bits)
58 , tag(OriginTag(base_point.get_origin_tag(), offset_generator.get_origin_tag()))
59{
60 const size_t table_size = 1UL << table_bits;
62 point_table.resize(table_size);
63
64 // We want to support the case where input points are points at infinity.
65 // If base point is at infinity, we want every point in the table to just be `generator_point`.
66 // We achieve this via the following:
67 // 1: We create a "work_point" that is base_point if not at infinity, else it is set (arbitrarily) to "one"
68 // 2: When computing the point table, we use "work_point" in additions instead of the "base_point" (to prevent
69 // x-coordinate collisions in honest case) 3: When assigning to the point table, we conditionally assign either
70 // the output of the point addition (if not at infinity) or the generator point (if at infinity)
71 // 3: If point at infinity, conditionally (re)assign each entry in the table to be equal to the offset
72 // generator so that the final table is genuninely correct in all cases. (Otherwise, the table is unchanged
73 // from step 2)
74 cycle_group<Builder> fallback_point(Group::affine_one);
75 field_t modded_x =
76 field_t::conditional_assign(base_point.is_point_at_infinity(), fallback_point.x(), base_point.x());
77 field_t modded_y =
78 field_t::conditional_assign(base_point.is_point_at_infinity(), fallback_point.y(), base_point.y());
79 // The modded point is never at infinity since we fallback to Group::one when the base point is infinity.
80 // Use the private 4-arg constructor to avoid auto-detection gates.
81 cycle_group<Builder> modded_base_point(
82 modded_x, modded_y, /*is_infinity=*/bool_t<Builder>(modded_x.get_context(), false), /*assert_on_curve=*/false);
83 // We assume that the native hints (if present) do not account for the point at infinity edge case in the same way
84 // as above (i.e. replacing with "one") so we avoid using any provided hints in this case. (N.B. No efficiency is
85 // lost here since native addition with the point at infinity is nearly free).
86 const bool hints_available = hints.has_value() && !base_point.is_point_at_infinity().get_value();
87 auto get_hint = [&](size_t i) -> std::optional<AffineElement> {
88 if (!hints_available) {
89 return std::nullopt;
90 }
91 BB_ASSERT_LT(i, hints.value().size(), "Invalid hint index");
92 return std::optional<AffineElement>(hints.value()[i]);
93 };
94
95 if (base_point.is_constant() && !base_point.is_point_at_infinity().get_value()) {
96 // Case 1: if the input point is constant, it is cheaper to fix the point as a witness and then derive the
97 // table, than it is to derive the table and fix its witnesses to be constant! (due to group additions = 1 gate,
98 // and fixing x/y coords to be constant = 2 gates)
99
100 // base_point == offset_generator collapses the first table add into a degenerate G + G ecc_add gate;
101 // the non-doubling relation is identically zero when operands match, so is satisfied by any result coordinates.
102 BB_ASSERT(base_point.get_value() != offset_generator.get_value(),
103 "straus_lookup_table case-1: base_point must not coincide with offset_generator");
104
105 modded_base_point = cycle_group<Builder>::from_constant_witness(_context, modded_base_point.get_value());
106 point_table[0] = cycle_group<Builder>::from_constant_witness(_context, offset_generator.get_value());
107 for (size_t i = 1; i < table_size; ++i) {
108 point_table[i] = point_table[i - 1].unconditional_add(modded_base_point, get_hint(i - 1));
109 }
110 } else {
111 // Case 2: Point is non-constant witness so the table is derived via unconditional additions. We check the
112 // x_coordinates of all summand pairs are distinct via a batched product check to avoid individual modular
113 // inversions.
114 field_t coordinate_check_product = 1;
115 point_table[0] = offset_generator;
116 for (size_t i = 1; i < table_size; ++i) {
117 const field_t x_diff = point_table[i - 1].x() - modded_base_point.x();
118 coordinate_check_product *= x_diff;
119 point_table[i] = point_table[i - 1].unconditional_add(modded_base_point, get_hint(i - 1));
120 }
121 coordinate_check_product.assert_is_not_zero("straus_lookup_table x-coordinate collision");
122
123 // If the input base point was the point at infinity, the correct point table simply contains the offset
124 // generator at every entry. However, since we replaced the point at infinity with "one" when computing the
125 // table (see explanation above), we must conditionally correct the table entries here.
126 for (size_t i = 1; i < table_size; ++i) {
128 base_point.is_point_at_infinity(), offset_generator, point_table[i]);
129 }
130 }
131
132 // Construct a ROM array containing the point table
133 rom_id = context->create_ROM_array(table_size);
134 for (size_t i = 0; i < table_size; ++i) {
135 // Convert any constant points to witnesses constrained to equal the constant value for use in ROM array
136 if (point_table[i].is_constant()) {
137 const auto element = point_table[i].get_value();
139 }
140 std::array<uint32_t, 2> coordinate_indices = { point_table[i].x().get_witness_index(),
141 point_table[i].y().get_witness_index() };
142 context->set_ROM_element_pair(rom_id, i, coordinate_indices);
143 }
144}
145
157template <typename Builder> cycle_group<Builder> straus_lookup_table<Builder>::read(const field_t& _index)
158{
159 BB_ASSERT_LT(uint256_t(_index.get_value()).get_msb(), _table_bits, "straus_lookup_table read index out of bounds");
160
161 field_t index(_index);
162 // A ROM array index must be a witness; we convert constants to a witness constrained to equal the constant value
163 if (index.is_constant()) {
164 index = field_t::from_witness(_context, _index.get_value());
165 index.assert_equal(_index.get_value());
166 }
167 auto [x_idx, y_idx] = _context->read_ROM_array_pair(rom_id, index.get_witness_index());
168 field_t x = field_t::from_witness_index(_context, x_idx);
169 field_t y = field_t::from_witness_index(_context, y_idx);
170 // Merge tag of table with tag of index
173
174 // The result is known to not be the point at infinity due to the use of offset generators in the table.
175 // Use the private 4-arg constructor to avoid auto-detection gates.
176 return cycle_group<Builder>(x, y, /*is_infinity=*/bool_t<Builder>(_context, false), /*assert_on_curve=*/false);
177}
178
181
182} // namespace bb::stdlib
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_LT(left, right,...)
Definition assert.hpp:143
constexpr uint64_t get_msb() const
Implements boolean logic in-circuit.
Definition bool.hpp:60
bool get_value() const
Definition bool.hpp:125
cycle_group represents a group Element of the proving system's embedded curve, i.e....
const field_t & x() const
static cycle_group from_constant_witness(Builder *_context, const AffineElement &_in)
Converts a native AffineElement into a witness, but constrains the witness values to be known constan...
static cycle_group conditional_assign(const bool_t &predicate, const cycle_group &lhs, const cycle_group &rhs)
bool_t is_point_at_infinity() const
const field_t & y() const
AffineElement get_value() const
static field_t from_witness_index(Builder *ctx, uint32_t witness_index)
Definition field.cpp:67
Builder * get_context() const
Definition field.hpp:445
OriginTag get_origin_tag() const
Definition field.hpp:372
bb::fr get_value() const
Given a := *this, compute its value given by a.v * a.mul + a.add.
Definition field.cpp:838
static field_t from_witness(Builder *ctx, const bb::fr &input)
Definition field.hpp:480
void set_origin_tag(const OriginTag &new_tag) const
Definition field.hpp:358
static field_t conditional_assign(const bool_t< Builder > &predicate, const field_t &lhs, const field_t &rhs)
Definition field.hpp:398
void assert_is_not_zero(std::string const &msg="field_t::assert_is_not_zero") const
Constrain *this to be non-zero by establishing that it has an inverse.
Definition field.cpp:720
straus_lookup_table computes a lookup table of size 1 << table_bits
static std::vector< Element > compute_native_table(const Element &base_point, const Element &offset_generator, size_t table_bits)
Compute the output points generated when computing the Straus lookup table.
cycle_group< Builder > read(const field_t &index)
Given an _index witness, return straus_lookup_table[index]
StrictMock< MockContext > context
std::conditional_t< IsGoblinBigGroup< C, Fq, Fr, G >, element_goblin::goblin_element< C, goblin_field< C >, Fr, G >, element_default::element< C, Fq, Fr, G > > element
element wraps either element_default::element or element_goblin::goblin_element depending on parametr...
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13