Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
biggroup_secp256k1.test.cpp
Go to the documentation of this file.
1#include "../bigfield/bigfield.hpp"
2#include "../biggroup/biggroup.hpp"
3#include "../bool/bool.hpp"
4#include "../field/field.hpp"
14#include "gtest/gtest.h"
15#include <vector>
16
17using namespace bb;
18
19namespace {
21}
22
23template <typename Curve> class stdlibBiggroupSecp256k1 : public testing::Test {
24 public:
25 // We always use bigfield for secp256k1 as the scalar field is large.
26 using element_ct = typename Curve::g1_bigfr_ct;
27 using scalar_ct = typename Curve::bigfr_ct;
28
29 using fq = typename Curve::fq;
30 using fr = typename Curve::fr;
31 using g1 = typename Curve::g1;
33 using element = typename g1::element;
34
35 using Builder = typename Curve::Builder;
38
39 static constexpr auto EXPECT_CIRCUIT_CORRECTNESS = [](Builder& builder, bool expected_result = true) {
40 info("num gates = ", builder.get_estimated_num_finalized_gates());
42 };
43
44 // Primitives functions for computing wnafs over secp256k1 scalars
45 template <size_t wnaf_size> void test_get_staggered_wnaf_fragment_value()
46 {
47 // Helper for easier invocation
48 auto get_val = [](uint64_t fragment_u64, uint64_t stagger, bool is_negative, bool wnaf_skew) {
49 return stdlib::element_default::element_test_accessor::
50 get_staggered_wnaf_fragment_value<Builder, typename Curve::fq_ct, scalar_ct, g1, wnaf_size>(
51 fragment_u64, stagger, is_negative, wnaf_skew);
52 };
53
54 // Test: stagger == 0 returns (0, wnaf_skew)
55 {
56 auto [frag, skew] = get_val(123, 0, false, false);
57 EXPECT_EQ(frag, 0);
58 EXPECT_EQ(skew, false);
59
60 auto [frag2, skew2] = get_val(456, 0, true, true);
61 EXPECT_EQ(frag2, 0);
62 EXPECT_EQ(skew2, true);
63 }
64 // Test: random positive values (honest case)
65 {
66 for (size_t i = 0; i < 20; ++i) {
67 // Check for various stagger values ≤ 10
68 for (size_t num_stagger_bits = 1; num_stagger_bits <= 10; ++num_stagger_bits) {
69 uint64_t input_frag = engine.get_random_uint32() % (1ULL << num_stagger_bits);
70 bool inp_skew = engine.get_random_uint32() & 1;
71 bool is_negative = false;
72
73 auto [output_frag, output_skew] = get_val(input_frag, num_stagger_bits, is_negative, inp_skew);
74
75 // input_frag - inp_skew * 2^t == output_wnaf - output_skew
76 int lhs = static_cast<int>(input_frag) - static_cast<int>(inp_skew * (1ULL << num_stagger_bits));
77
78 int output_wnaf_value =
79 (2 * static_cast<int>(output_frag) + 1) - static_cast<int>(1ULL << wnaf_size);
80 int rhs = output_wnaf_value - static_cast<int>(output_skew);
81
82 EXPECT_EQ(lhs, rhs);
83 }
84 }
85 }
86 // Test: random negative values
87 {
88 for (size_t i = 0; i < 20; ++i) {
89 // Check for various stagger values ≤ 10
90 for (size_t num_stagger_bits = 1; num_stagger_bits <= 10; ++num_stagger_bits) {
91 uint64_t input_frag = engine.get_random_uint32() % (1ULL << num_stagger_bits);
92 bool inp_skew = engine.get_random_uint32() & 1;
93 bool is_negative = true;
94
95 auto [output_frag, output_skew] = get_val(input_frag, num_stagger_bits, is_negative, inp_skew);
96
97 // In case of is_negative = true and input is even, we subtract 1 to make it odd and set skew = 1,
98 // which must be added to the output wnaf value.
99 // - input_frag + inp_skew * 2^t == output_wnaf + output_skew
100 int lhs = -static_cast<int>(input_frag) + static_cast<int>(inp_skew * (1ULL << num_stagger_bits));
101
102 int output_wnaf_value =
103 (2 * static_cast<int>(output_frag) + 1) - static_cast<int>(1ULL << wnaf_size);
104 int rhs = output_wnaf_value + static_cast<int>(output_skew);
105 EXPECT_EQ(lhs, rhs);
106 }
107 }
108 }
109 }
110
111 // Add the necessary utility methods used in tests
113 {
115 {
116 // Generate a random even scalar
117 fr scalar_a(fr::random_element());
118 if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) {
119 scalar_a -= fr(1); // skew bit is 1
120 }
121 scalar_ct x_a = scalar_ct::from_witness(&builder, scalar_a);
122 element_ct::template compute_secp256k1_endo_wnaf<4, 0, 3>(x_a, false);
123 element_ct::template compute_secp256k1_endo_wnaf<4, 1, 2>(x_a, false);
124 element_ct::template compute_secp256k1_endo_wnaf<4, 2, 1>(x_a, false);
125 element_ct::template compute_secp256k1_endo_wnaf<4, 3, 0>(x_a, false);
126 }
127 {
128 // Generate a random odd scalar
129 fr scalar_b(fr::random_element());
130 if ((uint256_t(scalar_b).get_bit(0) & 1) == 0) {
131 scalar_b += fr(1); // skew bit is 0
132 }
133 scalar_ct x_b = scalar_ct::from_witness(&builder, scalar_b);
134 element_ct::template compute_secp256k1_endo_wnaf<4, 0, 3>(x_b, false);
135 element_ct::template compute_secp256k1_endo_wnaf<4, 1, 2>(x_b, false);
136 element_ct::template compute_secp256k1_endo_wnaf<4, 2, 1>(x_b, false);
137 element_ct::template compute_secp256k1_endo_wnaf<4, 3, 0>(x_b, false);
138 }
139
141 }
142
144 {
146
147 // Generate a random even scalar
148 fr scalar_a(fr::random_element());
149 if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) {
150 scalar_a -= fr(1); // skew bit is 1
151 }
152 scalar_ct x_a = scalar_ct::from_witness(&builder, scalar_a);
153
154 // If we range constrain the wnaf entries, but the stagger is out of range, the circuit should
155 // fail.
156 element_ct::template compute_secp256k1_endo_wnaf</*wnaf_size=*/4, /*lo_stagger=*/10, /*hi_stagger=*/0>(
157 x_a, /*range_constrain_wnaf=*/true);
158
160 EXPECT_EQ(builder.err(), "biggroup_nafs: stagger fragment is not in range");
161 }
162
164 {
166 const uint512_t scalar_field_modulus = scalar_ct::modulus_u512;
167
168 // Generate a random scalar k < r (r is the scalar field modulus).
169 const fr scalar_a = fr::random_element();
170 scalar_ct scalar_a_ct = scalar_ct::from_witness(&builder, scalar_a);
171
172 // Generate a large scalar k' := (k + mr) > r where r is the scalar field modulus and m >= 1
173 // We need k' be larger than 256 bits to test the edge case properly. We achieve this by choosing
174 // m = 2^256 / r + 1, which guarantees that r < 2^256 < k'.
175 uint512_t m = ((uint512_t(1) << 256) / scalar_field_modulus) + 1;
176 uint512_t large_value = uint512_t(scalar_a) + (m * scalar_field_modulus);
177 scalar_ct large_scalar_ct = scalar_ct::create_from_u512_as_witness(&builder, large_value, true);
178 EXPECT_EQ(large_scalar_ct.get_value() >= (uint512_t(1) << 256), true);
179 EXPECT_EQ(large_scalar_ct.get_value() >= uint512_t(scalar_field_modulus), true);
180 EXPECT_EQ(large_scalar_ct.get_value() % uint512_t(scalar_field_modulus), uint512_t(scalar_a));
181
182 // circuit wnaf computation should work fine for scalar k
183 element_ct::template compute_secp256k1_endo_wnaf<4, 0, 1>(scalar_a_ct, false);
184
185 // circuit wnaf computation should also work for the large scalar k'
186 element_ct::template compute_secp256k1_endo_wnaf<4, 0, 1>(large_scalar_ct, false);
187
189 }
190
192 {
194 const uint512_t scalar_field_modulus = scalar_ct::modulus_u512;
195
196 // Generate a scalar k such that k < (2^256 - r)
197 const size_t num_allowed_bits = ((uint512_t(1) << 256) - scalar_field_modulus).get_msb();
198 const fr scalar_a = uint256_t(fr::random_element()) & ((uint256_t(1) << num_allowed_bits) - 1);
199 scalar_ct scalar_a_ct = scalar_ct::from_witness(&builder, scalar_a);
200
201 // Generate a large scalar k' := (k + r) < 2^256 (because k < 2^256 - r)
202 uint512_t large_value = uint512_t(scalar_a) + scalar_field_modulus;
203 scalar_ct large_scalar_ct = scalar_ct::create_from_u512_as_witness(&builder, large_value, true);
204 EXPECT_EQ(large_scalar_ct.get_value() < (uint512_t(1) << 256), true);
205 EXPECT_EQ(large_scalar_ct.get_value() >= uint512_t(scalar_field_modulus), true);
206 EXPECT_EQ(large_scalar_ct.get_value() % uint512_t(scalar_field_modulus), uint512_t(scalar_a));
207
208 // circuit wnaf computation should work fine for scalar k
209 element_ct::template compute_secp256k1_endo_wnaf<4, 0, 1>(scalar_a_ct, false);
210
211 // circuit wnaf computation should also work for the large scalar k'
212 element_ct::template compute_secp256k1_endo_wnaf<4, 0, 1>(large_scalar_ct, false);
213
215 }
216
218 {
220 { // Generate a random even scalar
221 fr scalar_a(fr::random_element());
222 if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) {
223 scalar_a -= fr(1); // skew bit is 1
224 }
225 scalar_ct x_a = scalar_ct::from_witness(&builder, scalar_a);
226 element_ct::template compute_secp256k1_endo_wnaf<8, 0, 3>(x_a, false);
227 element_ct::template compute_secp256k1_endo_wnaf<8, 1, 2>(x_a, false);
228 element_ct::template compute_secp256k1_endo_wnaf<8, 2, 1>(x_a, false);
229 element_ct::template compute_secp256k1_endo_wnaf<8, 3, 0>(x_a, false);
230 }
231 {
232 // Generate a random odd scalar
233 fr scalar_b(fr::random_element());
234 if ((uint256_t(scalar_b).get_bit(0) & 1) == 0) {
235 scalar_b += fr(1); // skew bit is 0
236 }
237 scalar_ct x_b = scalar_ct::from_witness(&builder, scalar_b);
238 element_ct::template compute_secp256k1_endo_wnaf<8, 0, 3>(x_b, false);
239 element_ct::template compute_secp256k1_endo_wnaf<8, 1, 2>(x_b, false);
240 element_ct::template compute_secp256k1_endo_wnaf<8, 2, 1>(x_b, false);
241 element_ct::template compute_secp256k1_endo_wnaf<8, 3, 0>(x_b, false);
242 }
243
245 }
246
248 {
250 size_t num_repetitions = 1;
251 for (size_t i = 0; i < num_repetitions; ++i) {
252 fr scalar_a(fr::random_element());
253 fr scalar_b(fr::random_element());
254 fr scalar_c(fr::random_element());
255 if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) {
256 scalar_a -= fr(1); // skew bit is 1
257 }
258 element_ct P_a = element_ct::from_witness(&builder, g1::one * scalar_c);
259 scalar_ct u1 = scalar_ct::from_witness(&builder, scalar_a);
260 scalar_ct u2 = scalar_ct::from_witness(&builder, scalar_b);
261
262 fr alo;
263 fr ahi;
264 fr blo;
265 fr bhi;
266
269
270 auto output = element_ct::secp256k1_ecdsa_mul(P_a, u1, u2);
271
272 auto expected = affine_element(g1::one * (scalar_c * scalar_b) + g1::one * scalar_a);
273 EXPECT_EQ(output.x.get_value().lo, uint256_t(expected.x));
274 EXPECT_EQ(output.y.get_value().lo, uint256_t(expected.y));
275 }
276
278 }
279
281 {
282 // The scalars s1, u1, u2 are chosen such that:
283 // Public key: P = (s1 * G)
284 //
285 // u1 * G + u2 * (s1 * G) = ø
286 //
287 // where ø is the point at infinity.
288 //
289 // The issue with such input was that we were not setting the point at infinity correctly
290 // while adding the skew points. For the cases when we reach the point at infinity and still have
291 // skew to add, we did not correctly set the flag _is_point_at_infinity. For this example, we have:
292 //
293 // u1_low skew: 0
294 // u1_high skew: 1
295 // u2_low skew: 1
296 // u2_high skew: 0
297 //
298 // After adding the u2_low skew (i.e., its base point), we get the point at infinity. Then we handle the
299 // u2 high skew as follows:
300 // result = acc ± u1_high_base_point
301 // result.x = u2_high_skew ? result.x : acc.x;
302 // result.y = u2_high_skew ? result.y : acc.y;
303 //
304 // However, we did not set the flag _is_point_at_infinity for result. We must copy the flag from the
305 // accumulator in this case, i.e., we must do:
306 // result.x = u2_high_skew ? result.x : acc.x;
307 // result.y = u2_high_skew ? result.y : acc.y;
308 // result._is_point_at_infinity = u2_high_skew ? result._is_point_at_infinity :
309 // acc._is_point_at_infinity;
310 //
311 // We define a new function `conditional_select` that does this operation and use it to handle the skew
312 // addition.
313 const uint256_t scalar_s1("0x66ad81e84534c20431c795de922fb592c3d8c68edcacfc6c5b52ab7ad10e47d3");
314 const uint256_t scalar_u1("0x37e0ba2e9c4dd42077fd751a7426a8484a8ff2928a6c85a651e4470b461c6215");
315 const uint256_t scalar_u2("0xdefbb9bbabde5b9f8d7175946e75babc2f11203a8bfb71beaeec1d7a2bff17dd");
316
317 // Check the assumptions
318 ASSERT(scalar_s1 < fr::modulus);
319 ASSERT(scalar_u1 < fr::modulus);
320 ASSERT(scalar_u2 < fr::modulus);
321 ASSERT((fr(scalar_s1) * fr(scalar_u2) + fr(scalar_u1)).is_zero());
322 ASSERT((g1::one * fr(scalar_u1) + (g1::one * fr(scalar_s1)) * fr(scalar_u2)).is_point_at_infinity());
323
324 // Check that the wnaf skews of the lo and hi parts of u2 are as expected
325 fr u2_lo;
326 fr u2_hi;
327 fr::split_into_endomorphism_scalars(fr(scalar_u2).from_montgomery_form(), u2_lo, u2_hi);
328 ASSERT(uint256_t(u2_lo).get_bit(0) == 0); // u2_lo skew is 1 (even)
329 ASSERT(uint256_t(u2_hi).get_bit(0) == 1); // u2_hi skew is 0 (odd)
330
332 element_ct P_a = element_ct::from_witness(&builder, g1::one * fr(scalar_s1));
333 scalar_ct u1 = scalar_ct::from_witness(&builder, fr(scalar_u1));
334 scalar_ct u2 = scalar_ct::from_witness(&builder, fr(scalar_u2));
335 auto output = element_ct::secp256k1_ecdsa_mul(P_a, u1, u2);
336
337 // Check that the output is the point at infinity
338 EXPECT_EQ(output.is_point_at_infinity().get_value(), true);
339
341 }
342
344 {
345 // This test uses the same idea as the skew handling regression test above.
346 // However, in this test, we use scalars to ensure that we are correctly handling the stagger offsets
347 // before adding the skew points. The scalars s1, u1, u2 are chosen such that: Public key: P = (s1 * G)
348 //
349 // u1 * G + u2 * (s1 * G) = ø
350 //
351 // where ø is the point at infinity. For this set of scalars, we have all the skews as 0.
352 // This means that we will reach the point at infinity while adding the stagger fragments of the
353 // scalars. Since we compute the wnaf with stagger offsets:
354 //
355 // compute_secp256k1_endo_wnaf<8, 2, 3>(u1, false);
356 // compute_secp256k1_endo_wnaf<4, 0, 1>(u2, false);
357 //
358 // we have the following stagger offsets:
359 // u1_low stagger bits: 2 <== add_3 = 2G
360 // u1_high stagger bits: 3 <== add_1 = 3λG
361 // u2_low stagger bits: 0
362 // u2_high stagger bits: 1 <== add_2 = λG
363 //
364 // Hence we add these terms to the accumulator using addition chain:
365 // acc += (((add_1) + add_2) + add_3).
366 //
367 // After adding add_2, the x-coordinate of the accumulator is equal to the x-coordinate of add_3.
368 // Using incomplete addition formulae, we catch a circuit error as the addition is not valid if the
369 // x-coordinates are equal. To avoid this, we use the complete addition formulae to add add_1, add_2,
370 // add_3 to the accumulator. The increases the circuit size for secp256k1_ecdsa_mul by 730 gates but we
371 // accept that for now to ensure correctness.
372 //
373 const uint256_t scalar_g1("0x9d496650d261d31af6aa4cf41e435ed739d0fe2c34728a21a0df5c66a3504ccd");
374 const uint256_t scalar_u1("0xf3d9f52f0f55d3da6f902aa842aa604005633f3d165bc800f3a3aa661b18df5f");
375 const uint256_t scalar_u2("0x1323b0342b1a56a076cbf5e3899156fbf3f439f2c3b0d5a95b9ef74622447f2e");
376
377 // Check the assumptions
378 ASSERT(scalar_g1 < fr::modulus);
379 ASSERT(scalar_u1 < fr::modulus);
380 ASSERT(scalar_u2 < fr::modulus);
381 ASSERT((fr(scalar_g1) * fr(scalar_u2) + fr(scalar_u1)).is_zero());
382 ASSERT((g1::one * fr(scalar_u1) + (g1::one * fr(scalar_g1)) * fr(scalar_u2)).is_point_at_infinity());
383
384 // Create the circuit
386 element_ct P_a = element_ct::from_witness(&builder, g1::one * fr(scalar_g1));
387 scalar_ct u1 = scalar_ct::from_witness(&builder, fr(scalar_u1));
388 scalar_ct u2 = scalar_ct::from_witness(&builder, fr(scalar_u2));
389 auto output = element_ct::secp256k1_ecdsa_mul(P_a, u1, u2);
390
391 // Check that the output is the point at infinity
392 EXPECT_EQ(output.is_point_at_infinity().get_value(), true);
393
395 }
396};
397
398// Then define the test types
400 testing::Types<stdlib::secp256k1<bb::UltraCircuitBuilder>, stdlib::secp256k1<bb::MegaCircuitBuilder>>;
401
402// Now register the test suite with the types
404
405// Define the individual tests
406TYPED_TEST(stdlibBiggroupSecp256k1, GetStaggeredWnafFragmentValue4bit)
407{
408 TestFixture::template test_get_staggered_wnaf_fragment_value<4>();
409}
410TYPED_TEST(stdlibBiggroupSecp256k1, GetStaggeredWnafFragmentValue8bit)
411{
412 TestFixture::template test_get_staggered_wnaf_fragment_value<8>();
413}
415{
416 TestFixture::test_wnaf_secp256k1();
417}
418TYPED_TEST(stdlibBiggroupSecp256k1, WnafSecp256k1StaggerOutOfRangeFails)
419{
420 TestFixture::test_wnaf_secp256k1_stagger_out_of_range_fails();
421}
422TYPED_TEST(stdlibBiggroupSecp256k1, WnafSecp256k1LargeScalarRegression1)
423{
424 TestFixture::test_wnaf_secp256k1_scalar_exceeding_modulus_regression_1();
425}
426TYPED_TEST(stdlibBiggroupSecp256k1, WnafSecp256k1LargeScalarRegression2)
427{
428 TestFixture::test_wnaf_secp256k1_scalar_exceeding_modulus_regression_2();
429}
431{
432 TestFixture::test_wnaf_8bit_secp256k1();
433}
435{
436 TestFixture::test_ecdsa_mul_secp256k1();
437}
438TYPED_TEST(stdlibBiggroupSecp256k1, EcdsaMulSecp256k1SkewHandlingRegression)
439{
440 TestFixture::test_secp256k1_ecdsa_mul_skew_handling_regression();
441}
442TYPED_TEST(stdlibBiggroupSecp256k1, EcdsaMulSecp256k1StaggerRegression)
443{
444 TestFixture::test_secp256k1_ecdsa_mul_stagger_regression();
445}
#define ASSERT(expression,...)
Definition assert.hpp:77
testing::Types< stdlib::secp256k1< bb::UltraCircuitBuilder >, stdlib::secp256k1< bb::MegaCircuitBuilder > > Secp256k1TestTypes
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
group_elements::affine_element< Fq, Fr, Params > affine_element
Definition group.hpp:42
static constexpr element one
Definition group.hpp:46
group_elements::element< Fq, Fr, Params > element
Definition group.hpp:41
constexpr uint64_t get_msb() const
Definition uintx.hpp:69
Implements boolean logic in-circuit.
Definition bool.hpp:59
static void test_wnaf_secp256k1_stagger_out_of_range_fails()
typename Curve::g1_bigfr_ct element_ct
static void test_secp256k1_ecdsa_mul_skew_handling_regression()
static void test_wnaf_secp256k1_scalar_exceeding_modulus_regression_1()
static constexpr auto EXPECT_CIRCUIT_CORRECTNESS
static void test_wnaf_secp256k1_scalar_exceeding_modulus_regression_2()
typename Curve::bigfr_ct scalar_ct
typename Curve::Builder Builder
typename g1::affine_element affine_element
static void test_secp256k1_ecdsa_mul_stagger_regression()
void info(Args... args)
Definition log.hpp:74
AluTraceBuilder builder
Definition alu.test.cpp:123
bool expected_result
uintx< uint256_t > uint512_t
Definition uintx.hpp:307
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:190
Entry point for Barretenberg command-line interface.
TYPED_TEST_SUITE(ShpleminiTest, TestSettings)
TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching)
This file contains part of the logic for the Origin Tag mechanism that tracks the use of in-circuit p...
static constexpr uint256_t modulus
static void split_into_endomorphism_scalars(const field &k, field &k1, field &k2)
static field random_element(numeric::RNG *engine=nullptr) noexcept
BB_INLINE constexpr field from_montgomery_form() const noexcept