Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
biggroup_secp256r1.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: not started, auditors: [], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
8
38
40
41namespace detail {
42
61
63{
64 using uint256 = bb::numeric::uint256_t;
65
66 constexpr uint256 n = ::bb::secp256r1::fr::modulus;
67 constexpr uint256 half_n = n >> 1;
68 constexpr uint256 bound = uint256(1) << 128;
69
70 // Extended Euclidean Algo invariant maintained across the loop body (and on entry):
71 // v_prev · s ≡ u_prev (mod n) and v_curr · s ≡ u_curr (mod n)
72 // Verified for the initial values below: 0 · s ≡ n ≡ 0 and 1 · s ≡ s.
73 // The update v_next = v_prev − q · v_curr, u_next = u_prev − q · u_curr
74 // (where q = ⌊u_prev / u_curr⌋) preserves it by linearity.
75 uint256 u_prev = n;
76 uint256 u_curr = uint256(s);
79
80 while (u_curr >= bound && u_curr != 0) {
81 const uint256 q = u_prev / u_curr;
82 const uint256 u_next = u_prev - q * u_curr;
83 const ::bb::secp256r1::fr v_next = v_prev - ::bb::secp256r1::fr(q) * v_curr;
84 u_prev = u_curr;
85 v_prev = v_curr;
86 u_curr = u_next;
87 v_curr = v_next;
88 }
89
90 const uint256 v_uint = uint256(v_curr);
91
93 result.alpha = u_curr;
94 if (v_uint <= half_n) {
95 result.beta_is_negative = false;
96 result.beta_abs = v_uint;
97 } else {
98 result.beta_is_negative = true;
99 result.beta_abs = n - v_uint;
100 }
101 return result;
102}
103
104} // namespace detail
105
106template <typename C, class Fq, class Fr, class G>
107template <typename, typename>
109 const element& pubkey, const Fr& u1, const Fr& u2)
110{
111 using FrNative = typename G::Fr;
112 using AffineNative = typename G::affine_element;
113 using ElementNative = typename G::element;
114
115 C* builder = pubkey.get_context();
116
117 // T₁ = u₁ · G via the Pedersen-style fixed-base plookup tables
118 element T1 = element::secp256r1_fixed_base_mul(u1);
119
120 // T₂ = u₂ · Q via fake-GLV
121 //
122 // The fake-GLV path cannot compute u₂·Q for u₂ ∈ {0, ±1}:
123 // - u₂ = 0 → T₂_native = 0·Q = O makes witness generation fail downstream.
124 // - u₂ = ±1 → T₂ = ±Q collides with Q in batch_mul's tables (incomplete addition divides by zero).
125 // Substitute u₂_safe = 2 internally so witness generation completes for any input. The returned
126 // element is then wrong for u₂ ∈ {0, ±1}; CALLERS MUST gate signature validity on u₂ ∉ {0, ±1} via a
127 // separate `!u2_is_degenerate` check (see `ecdsa_verify_signature`).
128 const Fr neg_one_fr = -Fr::one();
129 const bool_ct u2_is_degenerate = (u2 == Fr::zero()) || (u2 == Fr::one()) || (u2 == neg_one_fr);
130 Fr u2_safe = Fr::conditional_assign(u2_is_degenerate, Fr(2), u2);
131
132 // fake-GLV idea: find α₂, |β₂| such that: α₂·Q − β₂·T₂ = O and α₂, |β₂| < 2¹²⁸
133 const FrNative u2_n(uint256_t(u2_safe.get_value() % Fr::modulus_u512));
134 const AffineNative pubkey_native = pubkey.get_value();
135 const AffineNative T2_native(ElementNative(pubkey_native) * u2_n);
136 const auto decomp2 = detail::compute_secp256r1_fake_glv_decomposition(u2_n);
137
138 // Witness and range-constrain (α₂, |β₂|).
139 field_ct alpha2_field(witness_ct(builder, bb::fr(decomp2.alpha)));
140 field_ct beta2_abs_field(witness_ct(builder, bb::fr(decomp2.beta_abs)));
141 alpha2_field.create_range_constraint(128, "secp256r1 fake-GLV: α₂ ≥ 2¹²⁸");
142 beta2_abs_field.create_range_constraint(128, "secp256r1 fake-GLV: |β₂| ≥ 2¹²⁸");
143
144 // Rule out (α, β) = (0, 0): with β = 0 the identity α·Q − β·T₂ = O collapses to α·Q = O, satisfied by any
145 // T₂ whenever α = 0, which would leave T₂ unconstrained and allow forgery.
146 beta2_abs_field.assert_is_not_zero("secp256r1 fake-GLV: β₂ must be nonzero");
147
148 // Create witnesses for α₂, |β₂| and sign of β₂
149 const bool_ct beta2_neg(witness_ct(builder, decomp2.beta_is_negative));
150 const field_ct zero_witness = field_ct::from_witness_index(builder, builder->zero_idx());
151
152 // Pack each 128-bit native field_ct as a "short" bigfield using the (lo, hi) constructor
153 // The 128-bit value goes in as the low half (lo 136 bits) and the high half (hi 120 bits) is set to zero.
154 Fr alpha2_fr(alpha2_field, zero_witness);
155 Fr beta2_abs_fr(beta2_abs_field, zero_witness);
156
157 // Validate the decomposition: β₂_signed · u₂ ≡ α₂ (mod n)
158 Fr neg_beta2_fr = -beta2_abs_fr;
159 Fr beta2_signed_fr = Fr::conditional_assign(beta2_neg, neg_beta2_fr, beta2_abs_fr);
160 (beta2_signed_fr * u2_safe).assert_equal(alpha2_fr, "secp256r1 fake-GLV: β₂ · u₂ ≠ α₂ (mod n)");
161
162 // Witness T₂ and verify it's not at infinity
163 element T2 = element::from_witness(builder, T2_native);
164 T2.is_point_at_infinity().assert_equal(bool_ct(false), "secp256r1 fake-GLV: T₂ at infinity");
165
166 // T₂_msm absorbs the sign of β₂ so the 2-MSM has positive scalars only:
167 // if β₂ > 0 → T₂_msm = -T₂, so |β₂| · T₂_msm = -β₂ · T₂
168 // if β₂ < 0 → T₂_msm = +T₂, so |β₂| · T₂_msm = β₂_abs · T₂ = -β₂ · T₂
169 element T2_neg = -T2;
170 element T2_msm = T2_neg.conditional_select(T2, beta2_neg);
171
172 // Verify α₂·Q − β₂·T₂ = O via the generic 1-bit-NAF Strauss MSM
173 element msm = element::batch_mul(
174 { pubkey, T2_msm }, { alpha2_fr, beta2_abs_fr }, /*max_num_bits=*/128, /*with_edgecases=*/false);
175 msm.is_point_at_infinity().assert_equal(bool_ct(true), "secp256r1 fake-GLV: α₂·Q − β₂·T₂ ≠ O");
176
177 // Return (T₁ + T₂) = u₁·G + u₂_safe·Q paired with the soundness flag (false iff substitution fired).
178 return { T1 + T2, !u2_is_degenerate };
179}
180
206template <typename C, class Fq, class Fr, class G>
207template <typename, typename>
209{
210 using AffineNative = typename G::affine_element;
211 using FqNative = typename G::Fq;
215
216 constexpr size_t NUM_WINDOWS = FBT::NUM_WINDOWS;
217 constexpr size_t NUM_WINDOWS_LO = FBT::NUM_WINDOWS_LO;
218 constexpr size_t NUM_WINDOWS_HI = FBT::NUM_WINDOWS_HI;
219
220 C* builder = u.get_context();
221 BB_ASSERT(builder != nullptr, "secp256r1_fixed_base_mul: u must be associated with a circuit context.");
222
223 // Step 1: extract u_low and u_high from u's bigfield binary basis limbs.
224 // u_low = limbs[0] + 2^NUM_LIMB_BITS · limbs[1] < 2^(2·NUM_LIMB_BITS) = 2^136
225 // u_high = limbs[2] + 2^NUM_LIMB_BITS · limbs[3] < 2^(NUM_LIMB_BITS + NUM_LAST_LIMB_BITS) = 2^120
226 // The bigfield invariant ties u_low and u_high to u itself; the plookup mechanism asserts that the
227 // per-window byte slices reconstruct u_low and u_high (via the column-1 accumulator structure).
228 const bb::fr limb_shift = bb::fr(uint256_t(1) << Fr::NUM_LIMB_BITS);
229 const field_ct u_low = u.get_limb(0).element + (u.get_limb(1).element * limb_shift);
230 const field_ct u_high = u.get_limb(2).element + (u.get_limb(3).element * limb_shift);
231
232 // Step 2: drive u_low / u_high through the 10 secp256r1 fixed-base MultiTables. Each call returns
233 // per-window (limb_a, limb_b) pairs via the C2/C3 columns (step size 0 ⇒ no accumulation).
234 auto read = [&](MultiTableId id, const field_ct& key) {
236 };
237 const auto xlo_lo = read(MultiTableId::SECP256R1_FIXED_BASE_XLO_LO, u_low);
238 const auto xlo_hi = read(MultiTableId::SECP256R1_FIXED_BASE_XLO_HI, u_high);
239 const auto xhi_lo = read(MultiTableId::SECP256R1_FIXED_BASE_XHI_LO, u_low);
240 const auto xhi_hi = read(MultiTableId::SECP256R1_FIXED_BASE_XHI_HI, u_high);
241 const auto ylo_lo = read(MultiTableId::SECP256R1_FIXED_BASE_YLO_LO, u_low);
242 const auto ylo_hi = read(MultiTableId::SECP256R1_FIXED_BASE_YLO_HI, u_high);
243 const auto yhi_lo = read(MultiTableId::SECP256R1_FIXED_BASE_YHI_LO, u_low);
244 const auto yhi_hi = read(MultiTableId::SECP256R1_FIXED_BASE_YHI_HI, u_high);
245
246 // Step 3: assemble 32 elements. Window j of the full decomposition corresponds to:
247 // - lo-half index j for j ∈ [0, NUM_WINDOWS_LO)
248 // - hi-half index (j − NUM_WINDOWS_LO) for j ∈ [NUM_WINDOWS_LO, NUM_WINDOWS)
249 // The 4-limb `unsafe_construct_from_limbs` recomputes prime_basis_limb in-circuit (see docstring).
251 auto build_element = [&](const auto& xlo_r, const auto& xhi_r, const auto& ylo_r, const auto& yhi_r, size_t j) {
252 const Fq x = Fq::unsafe_construct_from_limbs(
253 xlo_r[ColumnIdx::C2][j], xlo_r[ColumnIdx::C3][j], xhi_r[ColumnIdx::C2][j], xhi_r[ColumnIdx::C3][j]);
254 const Fq y = Fq::unsafe_construct_from_limbs(
255 ylo_r[ColumnIdx::C2][j], ylo_r[ColumnIdx::C3][j], yhi_r[ColumnIdx::C2][j], yhi_r[ColumnIdx::C3][j]);
256 return element(x, y, /*assert_on_curve=*/false);
257 };
258 for (size_t j = 0; j < NUM_WINDOWS_LO; ++j) {
259 looked_up[j] = build_element(xlo_lo, xhi_lo, ylo_lo, yhi_lo, j);
260 }
261 for (size_t j = 0; j < NUM_WINDOWS_HI; ++j) {
262 looked_up[NUM_WINDOWS_LO + j] = build_element(xlo_hi, xhi_hi, ylo_hi, yhi_hi, j);
263 }
264
265 // Step 4: chain-add the 32 looked-up points to defer per-add y-coordinate computation.
266 auto chain = element::chain_add_start(looked_up[0], looked_up[1]);
267 for (size_t i = 2; i < NUM_WINDOWS; ++i) {
268 chain = element::chain_add(looked_up[i], chain);
269 }
270 const element raw_result = element::chain_add_end(chain);
271
272 // Step 5: subtract the constant total offset (= sum of all per-window offsets) to recover u·G.
273 const AffineNative total_offset_affine = FBT::total_offset();
274 const element offset_correction(Fq(FqNative(total_offset_affine.x)),
275 Fq(FqNative(total_offset_affine.y)),
276 /*assert_on_curve=*/false);
277 return raw_result - offset_correction;
278}
279
280} // namespace bb::stdlib::element_default
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
Per-window plookup tables for the secp256r1 fixed-base scalar multiplication.
Implements boolean logic in-circuit.
Definition bool.hpp:60
void assert_equal(const bool_t &rhs, std::string const &msg="bool_t::assert_equal") const
Implements copy constraint for bool_t elements.
Definition bool.cpp:433
NativeGroup::affine_element get_value() const
Definition biggroup.hpp:347
element conditional_select(const element &other, const bool_ct &predicate) const
Selects this if predicate is false, other if predicate is true.
Definition biggroup.hpp:237
static field_t from_witness_index(Builder *ctx, uint32_t witness_index)
Definition field.cpp:67
void create_range_constraint(size_t num_bits, std::string const &msg="field_t::range_constraint") const
Let x = *this.normalize(), constrain x.v < 2^{num_bits}.
Definition field.cpp:921
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
static plookup::ReadData< field_pt > get_lookup_accumulators(const plookup::MultiTableId id, const field_pt &key_a, const field_pt &key_b=0, const bool is_2_to_1_lookup=false)
Definition plookup.cpp:19
AluTraceBuilder builder
Definition alu.test.cpp:124
stdlib::witness_t< Builder > witness_ct
field< FrParams > fr
fake_glv_decomposition compute_secp256r1_fake_glv_decomposition(const ::bb::secp256r1::fr &s)
void read(B &it, field2< base_field, Params > &value)
field< Bn254FrParams > fr
Definition fr.hpp:155
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Curve::ScalarField Fr
grumpkin::fq Fq
static constexpr field one()
static constexpr uint256_t modulus
static constexpr field zero()
Native fake-GLV decomposition for a secp256r1 scalar.
VectorField result