Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
affine_element_impl.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Planned, auditors: [], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
8#include "./element.hpp"
11
12namespace bb::group_elements {
13template <class Fq, class Fr, class T>
15 : x(x)
16 , y(y)
17{}
18
19template <class Fq, class Fr, class T>
20template <typename BaseField, typename CompileTimeEnabled>
22{
23 uint256_t x_coordinate = compressed;
24 x_coordinate.data[3] = x_coordinate.data[3] & (~UINT256_TOP_LIMB_MSB);
25 bool y_bit = compressed.get_bit(255);
26
27 // Reject non-canonical encodings: the lower 255 bits encode x. If x_coordinate >= Fq::modulus,
28 // Fq(x_coordinate) silently reduces mod p, so two distinct compressed bytestrings differing by
29 // a multiple of p would decompress to the same point (encoding malleability).
30 if (x_coordinate >= Fq::modulus) {
31 return affine_element(Fq::zero(), Fq::zero());
32 }
33
34 Fq x = Fq(x_coordinate);
35 Fq y2 = (x.sqr() * x + T::b);
36 if constexpr (T::has_a) {
37 y2 += (x * T::a);
38 }
39 auto [is_quadratic_remainder, y] = y2.sqrt();
40 if (!is_quadratic_remainder) {
41 return affine_element(Fq::zero(), Fq::zero());
42 }
43 if (uint256_t(y).get_bit(0) != y_bit) {
44 y = -y;
45 }
46
47 return affine_element<Fq, Fr, T>(x, y);
48}
49
50template <class Fq, class Fr, class T>
51template <typename BaseField, typename CompileTimeEnabled>
53 const uint256_t& compressed) noexcept
54{
55 // Try x as a recovery candidate: check it is in [0, p), compute y² = x³ + ax + b,
56 // and return the point if y exists. Fq(x) reduces silently, so ensuring x is in [0, p) is necessary to prevent
57 // returning an incorrect pair (x mod q, y).
58 auto try_candidate = [](const uint256_t& x_coordinate) -> affine_element<Fq, Fr, T> {
59 if (x_coordinate >= Fq::modulus) {
60 return { Fq::zero(), Fq::zero() };
61 }
62 Fq x = Fq(x_coordinate);
63 Fq y2 = ((x.sqr() * x) + T::b);
64 if constexpr (T::has_a) {
65 y2 += (x * T::a);
66 }
67 auto [is_qr, y] = y2.sqrt();
69 };
70
71 return { try_candidate(compressed), try_candidate(compressed + Fr::modulus) };
72}
73
74template <class Fq, class Fr, class T>
80
81template <class Fq, class Fr, class T>
82constexpr affine_element<Fq, Fr, T> affine_element<Fq, Fr, T>::operator*(const Fr& exponent) const noexcept
83{
84 return bb::group_elements::element(*this) * exponent;
85}
86
87template <class Fq, class Fr, class T> constexpr affine_element<Fq, Fr, T> affine_element<Fq, Fr, T>::infinity()
88{
91 return e;
92}
93
94template <class Fq, class Fr, class T>
96{
98 result.self_set_infinity();
99 return result;
100}
101
102template <class Fq, class Fr, class T> constexpr void affine_element<Fq, Fr, T>::self_set_infinity() noexcept
103{
104 if constexpr (Fq::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
105 // We set the value of x equal to modulus to represent inifinty
106 x.data[0] = Fq::modulus.data[0];
107 x.data[1] = Fq::modulus.data[1];
108 x.data[2] = Fq::modulus.data[2];
109 x.data[3] = Fq::modulus.data[3];
110
111 // Clear y for memory hygiene
112 y = Fq::zero();
113 } else {
114 (*this).x = Fq::zero();
115 (*this).y = Fq::zero();
116 x.self_set_msb();
117 }
118}
119
120template <class Fq, class Fr, class T> constexpr bool affine_element<Fq, Fr, T>::is_point_at_infinity() const noexcept
121{
122 if constexpr (Fq::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
123 // We check if the value of x is equal to modulus to represent inifinty
124 return ((x.data[0] ^ Fq::modulus.data[0]) | (x.data[1] ^ Fq::modulus.data[1]) |
125 (x.data[2] ^ Fq::modulus.data[2]) | (x.data[3] ^ Fq::modulus.data[3])) == 0;
126
127 } else {
128 return (x.is_msb_set());
129 }
130}
131
132template <class Fq, class Fr, class T> constexpr bool affine_element<Fq, Fr, T>::on_curve() const noexcept
133{
134 if (is_point_at_infinity()) {
135 return true;
136 }
137 Fq xxx = x.sqr() * x + T::b;
138 Fq yy = y.sqr();
139 if constexpr (T::has_a) {
140 xxx += (x * T::a);
141 }
142 return (xxx == yy);
143}
144
145template <class Fq, class Fr, class T> bool affine_element<Fq, Fr, T>::is_in_prime_subgroup() const noexcept
146{
147 if (is_point_at_infinity()) {
148 return true;
149 }
150 // Weierstrass group law is unsound for off-curve coordinates, so the [r]·P trick can
151 // give a false positive on points that satisfy y² = x³ + b' for some b' ≠ b. Reject
152 // those up front.
153 if (!on_curve()) {
154 return false;
155 }
157
158 // To compute r * P, we convert modulus r to u256 and perform a left-to-right double-and-add.
159 constexpr uint256_t r = Fr::modulus;
160 const uint64_t r_msb = r.get_msb();
161
162 // Left-to-right double-and-add over the bits of r below the MSB. The MSB itself is consumed by
163 // initializing `acc` with `*this`. Loop terminates via unsigned underflow (i wraps past 0).
164 Element acc(*this);
165 for (uint64_t i = r_msb - 1; i < r_msb; --i) {
166 acc.self_dbl();
167 if (r.get_bit(i)) {
168 acc += *this;
169 }
170 }
171 return acc.is_point_at_infinity();
172}
173
174template <class Fq, class Fr, class T>
175constexpr bool affine_element<Fq, Fr, T>::operator==(const affine_element& other) const noexcept
176{
177 bool this_is_infinity = is_point_at_infinity();
178 bool other_is_infinity = other.is_point_at_infinity();
179 bool both_infinity = this_is_infinity && other_is_infinity;
180 bool only_one_is_infinity = this_is_infinity != other_is_infinity;
181 return !only_one_is_infinity && (both_infinity || ((x == other.x) && (y == other.y)));
182}
183
184template <class Fq, class Fr, class T>
185constexpr bool affine_element<Fq, Fr, T>::operator>(const affine_element& other) const noexcept
186{
187 if (is_point_at_infinity()) {
188 return false;
189 }
190 if (other.is_point_at_infinity()) {
191 return true;
192 }
193
194 if (x > other.x) {
195 return true;
196 }
197 if (x == other.x && y > other.y) {
198 return true;
199 }
200 return false;
201}
202
203template <class Fq, class Fr, class T>
205 const Fq& x, bool sign_bit) noexcept
206{
207 auto yy = x.sqr() * x + T::b;
208 if constexpr (T::has_a) {
209 yy += (x * T::a);
210 }
211 auto [found_root, y] = yy.sqrt();
212
213 if (found_root) {
214 if (uint256_t(y).get_bit(0) != sign_bit) {
215 y = -y;
216 }
217 return affine_element(x, y);
218 }
219 return std::nullopt;
220}
221
254template <class Fq, class Fr, class T>
256 uint8_t attempt_count) noexcept
258{
259 std::vector<uint8_t> target_seed(seed);
260 // expand by 2 bytes to cover incremental hash attempts
261 const size_t seed_size = seed.size();
262 for (size_t i = 0; i < 2; ++i) {
263 target_seed.push_back(0);
264 }
265 target_seed[seed_size] = attempt_count;
266 target_seed[seed_size + 1] = 0;
267 const auto hash_hi = blake3::blake3s_constexpr(&target_seed[0], target_seed.size());
268 target_seed[seed_size + 1] = 1;
269 const auto hash_lo = blake3::blake3s_constexpr(&target_seed[0], target_seed.size());
270 // custom serialize methods as common/serialize.hpp is not constexpr!
271 const auto read_uint256 = [](const uint8_t* in) {
272 const auto read_limb = [](const uint8_t* in, uint64_t& out) {
273 for (size_t i = 0; i < 8; ++i) {
274 out += static_cast<uint64_t>(in[i]) << ((7 - i) * 8);
275 }
276 };
277 uint256_t out = 0;
278 read_limb(&in[0], out.data[3]);
279 read_limb(&in[8], out.data[2]);
280 read_limb(&in[16], out.data[1]);
281 read_limb(&in[24], out.data[0]);
282 return out;
283 };
284 // interpret 64 byte hash output as a uint512_t, reduce to Fq element
285 //(512 bits of entropy ensures result is not biased as 512 >> Fq::modulus.get_msb())
286 Fq x(uint512_t(read_uint256(&hash_lo[0]), read_uint256(&hash_hi[0])));
287 bool sign_bit = hash_hi[0] > 127;
288 std::optional<affine_element> result = derive_from_x_coordinate(x, sign_bit);
289 if (result.has_value()) {
290 return result.value();
291 }
292 return hash_to_curve(seed, attempt_count + 1);
293}
294
295template <typename Fq, typename Fr, typename T>
297{
298 if (engine == nullptr) {
300 }
301
302 Fq x;
303 Fq y;
304 while (true) {
305 // Sample a random x-coordinate and check if it satisfies curve equation.
307 // Negate the y-coordinate based on a randomly sampled bit.
308 bool sign_bit = (engine->get_random_uint8() & 1) != 0;
309
310 std::optional<affine_element> result = derive_from_x_coordinate(x, sign_bit);
311
312 if (result.has_value()) {
313 return result.value();
314 }
315 }
316 throw_or_abort("affine_element::random_element error");
317 return affine_element<Fq, Fr, T>(x, y);
318}
319
320} // namespace bb::group_elements
bool is_in_prime_subgroup() const noexcept
Check that the point lies in the prime-order subgroup of size Fr::modulus.
static constexpr std::array< affine_element, 2 > from_compressed_unsafe(const uint256_t &compressed) noexcept
Reconstruct a point in affine coordinates from compressed form.
constexpr bool is_point_at_infinity() const noexcept
static affine_element random_element(numeric::RNG *engine=nullptr) noexcept
Samples a random point on the curve.
constexpr void self_set_infinity() noexcept
static constexpr affine_element infinity()
constexpr affine_element operator+(const affine_element &other) const noexcept
static constexpr affine_element from_compressed(const uint256_t &compressed) noexcept
Reconstruct a point in affine coordinates from compressed form.
static affine_element hash_to_curve(const std::vector< uint8_t > &seed, uint8_t attempt_count=0) noexcept
Hash a seed buffer into a point.
constexpr bool on_curve() const noexcept
constexpr bool operator==(const affine_element &other) const noexcept
static constexpr std::optional< affine_element > derive_from_x_coordinate(const Fq &x, bool sign_bit) noexcept
constexpr bool operator>(const affine_element &other) const noexcept
constexpr affine_element operator*(const Fr &exponent) const noexcept
constexpr affine_element set_infinity() const noexcept
element class. Implements ecc group arithmetic using Jacobian coordinates See https://hyperelliptic....
Definition element.hpp:35
constexpr bool get_bit(uint64_t bit_index) const
constexpr uint64_t get_msb() const
numeric::RNG & engine
uint256_t read_uint256(const uint8_t *data, size_t buffer_size=32)
AffineElement const size_t Fq *scratch_space noexcept
uintx< uint256_t > uint512_t
Definition uintx.hpp:309
RNG & get_randomness()
Definition engine.cpp:258
constexpr std::array< uint8_t, BLAKE3_OUT_LEN > blake3s_constexpr(const uint8_t *input, size_t input_size)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
bb::VectorAffineElementPushSpan< BaseParams > out
grumpkin::fq Fq
Curve::Element Element
static constexpr uint256_t modulus
static field random_element(numeric::RNG *engine=nullptr) noexcept
BB_INLINE constexpr field sqr() const noexcept
static constexpr field zero()
void throw_or_abort(std::string const &err)
VectorField result