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 , tag(OriginTag(base_point.get_origin_tag(), offset_generator.get_origin_tag()))
58{
59 const size_t table_size = 1UL << table_bits;
61 point_table.resize(table_size);
62
63 // We want to support the case where input points are points at infinity.
64 // If base point is at infinity, we want every point in the table to just be `generator_point`.
65 // We achieve this via the following:
66 // 1: We create a "work_point" that is base_point if not at infinity, else it is set (arbitrarily) to "one"
67 // 2: When computing the point table, we use "work_point" in additions instead of the "base_point" (to prevent
68 // x-coordinate collisions in honest case) 3: When assigning to the point table, we conditionally assign either
69 // the output of the point addition (if not at infinity) or the generator point (if at infinity)
70 // 3: If point at infinity, conditionally (re)assign each entry in the table to be equal to the offset
71 // generator so that the final table is genuninely correct in all cases. (Otherwise, the table is unchanged
72 // from step 2)
73 cycle_group<Builder> fallback_point(Group::affine_one);
74 field_t modded_x =
75 field_t::conditional_assign(base_point.is_point_at_infinity(), fallback_point.x(), base_point.x());
76 field_t modded_y =
77 field_t::conditional_assign(base_point.is_point_at_infinity(), fallback_point.y(), base_point.y());
78 // The modded point is never at infinity since we fallback to Group::one when the base point is infinity.
79 // Use the private 4-arg constructor to avoid auto-detection gates.
80 cycle_group<Builder> modded_base_point(
81 modded_x, modded_y, /*is_infinity=*/bool_t<Builder>(modded_x.get_context(), false), /*assert_on_curve=*/false);
82 // We assume that the native hints (if present) do not account for the point at infinity edge case in the same way
83 // as above (i.e. replacing with "one") so we avoid using any provided hints in this case. (N.B. No efficiency is
84 // lost here since native addition with the point at infinity is nearly free).
85 const bool hints_available = hints.has_value() && !base_point.is_point_at_infinity().get_value();
86 auto get_hint = [&](size_t i) -> std::optional<AffineElement> {
87 if (!hints_available) {
88 return std::nullopt;
89 }
90 BB_ASSERT_LT(i, hints.value().size(), "Invalid hint index");
91 return std::optional<AffineElement>(hints.value()[i]);
92 };
93
94 if (base_point.is_constant() && !base_point.is_point_at_infinity().get_value()) {
95 // Case 1: if the input point is constant, it is cheaper to fix the point as a witness and then derive the
96 // table, than it is to derive the table and fix its witnesses to be constant! (due to group additions = 1 gate,
97 // and fixing x/y coords to be constant = 2 gates)
98 modded_base_point = cycle_group<Builder>::from_constant_witness(_context, modded_base_point.get_value());
99 point_table[0] = cycle_group<Builder>::from_constant_witness(_context, offset_generator.get_value());
100 for (size_t i = 1; i < table_size; ++i) {
101 // Safe to use unconditional_add without collision checking due to constant points and inclusion of offset
102 // generator in table entries
103 point_table[i] = point_table[i - 1].unconditional_add(modded_base_point, get_hint(i - 1));
104 }
105 } else {
106 // Case 2: Point is non-constant witness so the table is derived via unconditional additions. We check the
107 // x_coordinates of all summand pairs are distinct via a batched product check to avoid individual modular
108 // inversions.
109 field_t coordinate_check_product = 1;
110 point_table[0] = offset_generator;
111 for (size_t i = 1; i < table_size; ++i) {
112 const field_t x_diff = point_table[i - 1].x() - modded_base_point.x();
113 coordinate_check_product *= x_diff;
114 point_table[i] = point_table[i - 1].unconditional_add(modded_base_point, get_hint(i - 1));
115 }
116 coordinate_check_product.assert_is_not_zero("straus_lookup_table x-coordinate collision");
117
118 // If the input base point was the point at infinity, the correct point table simply contains the offset
119 // generator at every entry. However, since we replaced the point at infinity with "one" when computing the
120 // table (see explanation above), we must conditionally correct the table entries here.
121 for (size_t i = 1; i < table_size; ++i) {
123 base_point.is_point_at_infinity(), offset_generator, point_table[i]);
124 }
125 }
126
127 // Construct a ROM array containing the point table
128 rom_id = context->create_ROM_array(table_size);
129 for (size_t i = 0; i < table_size; ++i) {
130 // Convert any constant points to witnesses constrained to equal the constant value for use in ROM array
131 if (point_table[i].is_constant()) {
132 const auto element = point_table[i].get_value();
134 }
135 std::array<uint32_t, 2> coordinate_indices = { point_table[i].x().get_witness_index(),
136 point_table[i].y().get_witness_index() };
137 context->set_ROM_element_pair(rom_id, i, coordinate_indices);
138 }
139}
140
150template <typename Builder> cycle_group<Builder> straus_lookup_table<Builder>::read(const field_t& _index)
151{
152 field_t index(_index);
153 // A ROM array index must be a witness; we convert constants to a witness constrained to equal the constant value
154 if (index.is_constant()) {
155 index = field_t::from_witness(_context, _index.get_value());
156 index.assert_equal(_index.get_value());
157 }
158 auto [x_idx, y_idx] = _context->read_ROM_array_pair(rom_id, index.get_witness_index());
159 field_t x = field_t::from_witness_index(_context, x_idx);
160 field_t y = field_t::from_witness_index(_context, y_idx);
161 // Merge tag of table with tag of index
164
165 // The result is known to not be the point at infinity due to the use of offset generators in the table.
166 // Use the private 4-arg constructor to avoid auto-detection gates.
167 return cycle_group<Builder>(x, y, /*is_infinity=*/bool_t<Builder>(_context, false), /*assert_on_curve=*/false);
168}
169
172
173} // namespace bb::stdlib
#define BB_ASSERT_LT(left, right,...)
Definition assert.hpp:143
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:431
OriginTag get_origin_tag() const
Definition field.hpp:358
bb::fr get_value() const
Given a := *this, compute its value given by a.v * a.mul + a.add.
Definition field.cpp:836
static field_t from_witness(Builder *ctx, const bb::fr &input)
Definition field.hpp:466
void set_origin_tag(const OriginTag &new_tag) const
Definition field.hpp:357
static field_t conditional_assign(const bool_t< Builder > &predicate, const field_t &lhs, const field_t &rhs)
Definition field.hpp:384
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:718
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