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_num_finalized_gates_inefficient());
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 const uint512_t scalar_field_modulus = scalar_ct::modulus_u512;
147
148 // Generate a random scalar k < r (r is the scalar field modulus).
149 const fr scalar_a = fr::random_element();
150 scalar_ct scalar_a_ct = scalar_ct::from_witness(&builder, scalar_a);
151
152 // Generate a large scalar k' := (k + mr) > r where r is the scalar field modulus and m >= 1
153 // We need k' be larger than 256 bits to test the edge case properly. We achieve this by choosing
154 // m = 2^256 / r + 1, which guarantees that r < 2^256 < k'.
155 uint512_t m = ((uint512_t(1) << 256) / scalar_field_modulus) + 1;
156 uint512_t large_value = uint512_t(scalar_a) + (m * scalar_field_modulus);
157 scalar_ct large_scalar_ct = scalar_ct::create_from_u512_as_witness(&builder, large_value, true);
158 EXPECT_EQ(large_scalar_ct.get_value() >= (uint512_t(1) << 256), true);
159 EXPECT_EQ(large_scalar_ct.get_value() >= uint512_t(scalar_field_modulus), true);
160 EXPECT_EQ(large_scalar_ct.get_value() % uint512_t(scalar_field_modulus), uint512_t(scalar_a));
161
162 // circuit wnaf computation should work fine for scalar k
163 element_ct::template compute_secp256k1_endo_wnaf<4, 0, 1>(scalar_a_ct, false);
164
165 // circuit wnaf computation should also work for the large scalar k'
166 element_ct::template compute_secp256k1_endo_wnaf<4, 0, 1>(large_scalar_ct, false);
167
169 }
170
172 {
174 const uint512_t scalar_field_modulus = scalar_ct::modulus_u512;
175
176 // Generate a scalar k such that k < (2^256 - r)
177 const size_t num_allowed_bits = ((uint512_t(1) << 256) - scalar_field_modulus).get_msb();
178 const fr scalar_a = uint256_t(fr::random_element()) & ((uint256_t(1) << num_allowed_bits) - 1);
179 scalar_ct scalar_a_ct = scalar_ct::from_witness(&builder, scalar_a);
180
181 // Generate a large scalar k' := (k + r) < 2^256 (because k < 2^256 - r)
182 uint512_t large_value = uint512_t(scalar_a) + scalar_field_modulus;
183 scalar_ct large_scalar_ct = scalar_ct::create_from_u512_as_witness(&builder, large_value, true);
184 EXPECT_EQ(large_scalar_ct.get_value() < (uint512_t(1) << 256), true);
185 EXPECT_EQ(large_scalar_ct.get_value() >= uint512_t(scalar_field_modulus), true);
186 EXPECT_EQ(large_scalar_ct.get_value() % uint512_t(scalar_field_modulus), uint512_t(scalar_a));
187
188 // circuit wnaf computation should work fine for scalar k
189 element_ct::template compute_secp256k1_endo_wnaf<4, 0, 1>(scalar_a_ct, false);
190
191 // circuit wnaf computation should also work for the large scalar k'
192 element_ct::template compute_secp256k1_endo_wnaf<4, 0, 1>(large_scalar_ct, false);
193
195 }
196
198 {
200 { // Generate a random even scalar
201 fr scalar_a(fr::random_element());
202 if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) {
203 scalar_a -= fr(1); // skew bit is 1
204 }
205 scalar_ct x_a = scalar_ct::from_witness(&builder, scalar_a);
206 element_ct::template compute_secp256k1_endo_wnaf<8, 0, 3>(x_a, false);
207 element_ct::template compute_secp256k1_endo_wnaf<8, 1, 2>(x_a, false);
208 element_ct::template compute_secp256k1_endo_wnaf<8, 2, 1>(x_a, false);
209 element_ct::template compute_secp256k1_endo_wnaf<8, 3, 0>(x_a, false);
210 }
211 {
212 // Generate a random odd scalar
213 fr scalar_b(fr::random_element());
214 if ((uint256_t(scalar_b).get_bit(0) & 1) == 0) {
215 scalar_b += fr(1); // skew bit is 0
216 }
217 scalar_ct x_b = scalar_ct::from_witness(&builder, scalar_b);
218 element_ct::template compute_secp256k1_endo_wnaf<8, 0, 3>(x_b, false);
219 element_ct::template compute_secp256k1_endo_wnaf<8, 1, 2>(x_b, false);
220 element_ct::template compute_secp256k1_endo_wnaf<8, 2, 1>(x_b, false);
221 element_ct::template compute_secp256k1_endo_wnaf<8, 3, 0>(x_b, false);
222 }
223
225 }
226
228 {
230 size_t num_repetitions = 1;
231 for (size_t i = 0; i < num_repetitions; ++i) {
232 fr scalar_a(fr::random_element());
233 fr scalar_b(fr::random_element());
234 fr scalar_c(fr::random_element());
235 if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) {
236 scalar_a -= fr(1); // skew bit is 1
237 }
238 element_ct P_a = element_ct::from_witness(&builder, g1::one * scalar_c);
239 scalar_ct u1 = scalar_ct::from_witness(&builder, scalar_a);
240 scalar_ct u2 = scalar_ct::from_witness(&builder, scalar_b);
241
242 fr alo;
243 fr ahi;
244 fr blo;
245 fr bhi;
246
249
250 auto output = element_ct::secp256k1_ecdsa_mul(P_a, u1, u2);
251
252 auto expected = affine_element(g1::one * (scalar_c * scalar_b) + g1::one * scalar_a);
253 EXPECT_EQ(output.x().get_value().lo, uint256_t(expected.x));
254 EXPECT_EQ(output.y().get_value().lo, uint256_t(expected.y));
255 }
256
258 }
259
261 {
262 // The scalars s1, u1, u2 are chosen such that:
263 // Public key: P = (s1 * G)
264 //
265 // u1 * G + u2 * (s1 * G) = ø
266 //
267 // where ø is the point at infinity.
268 //
269 // The issue with such input was that we were not setting the point at infinity correctly
270 // while adding the skew points. For the cases when we reach the point at infinity and still have
271 // skew to add, we did not correctly set the flag _is_point_at_infinity. For this example, we have:
272 //
273 // u1_low skew: 0
274 // u1_high skew: 1
275 // u2_low skew: 1
276 // u2_high skew: 0
277 //
278 // After adding the u2_low skew (i.e., its base point), we get the point at infinity. Then we handle the
279 // u2 high skew as follows:
280 // result = acc ± u1_high_base_point
281 // result.x() = u2_high_skew ? result.x() : acc.x();
282 // result.y() = u2_high_skew ? result.y() : acc.y();
283 //
284 // However, we did not set the flag _is_point_at_infinity for result. We must copy the flag from the
285 // accumulator in this case, i.e., we must do:
286 // result.x() = u2_high_skew ? result.x() : acc.x();
287 // result.y() = u2_high_skew ? result.y() : acc.y();
288 // result._is_point_at_infinity = u2_high_skew ? result._is_point_at_infinity :
289 // acc._is_point_at_infinity;
290 //
291 // We define a new function `conditional_select` that does this operation and use it to handle the skew
292 // addition.
293 const uint256_t scalar_s1("0x66ad81e84534c20431c795de922fb592c3d8c68edcacfc6c5b52ab7ad10e47d3");
294 const uint256_t scalar_u1("0x37e0ba2e9c4dd42077fd751a7426a8484a8ff2928a6c85a651e4470b461c6215");
295 const uint256_t scalar_u2("0xdefbb9bbabde5b9f8d7175946e75babc2f11203a8bfb71beaeec1d7a2bff17dd");
296
297 // Check the assumptions
298 BB_ASSERT(scalar_s1 < fr::modulus);
299 BB_ASSERT(scalar_u1 < fr::modulus);
300 BB_ASSERT(scalar_u2 < fr::modulus);
301 BB_ASSERT((fr(scalar_s1) * fr(scalar_u2) + fr(scalar_u1)).is_zero());
302 BB_ASSERT((g1::one * fr(scalar_u1) + (g1::one * fr(scalar_s1)) * fr(scalar_u2)).is_point_at_infinity());
303
304 // Check that the wnaf skews of the lo and hi parts of u2 are as expected
305 fr u2_lo;
306 fr u2_hi;
307 fr::split_into_endomorphism_scalars(fr(scalar_u2).from_montgomery_form(), u2_lo, u2_hi);
308 BB_ASSERT(uint256_t(u2_lo).get_bit(0) == 0); // u2_lo skew is 1 (even)
309 BB_ASSERT(uint256_t(u2_hi).get_bit(0) == 1); // u2_hi skew is 0 (odd)
310
312 element_ct P_a = element_ct::from_witness(&builder, g1::one * fr(scalar_s1));
313 scalar_ct u1 = scalar_ct::from_witness(&builder, fr(scalar_u1));
314 scalar_ct u2 = scalar_ct::from_witness(&builder, fr(scalar_u2));
315 auto output = element_ct::secp256k1_ecdsa_mul(P_a, u1, u2);
316
317 // Check that the output is the point at infinity
318 EXPECT_EQ(output.is_point_at_infinity().get_value(), true);
319
321 }
322
324 {
325 // This test uses the same idea as the skew handling regression test above.
326 // However, in this test, we use scalars to ensure that we are correctly handling the stagger offsets
327 // before adding the skew points. The scalars s1, u1, u2 are chosen such that: Public key: P = (s1 * G)
328 //
329 // u1 * G + u2 * (s1 * G) = ø
330 //
331 // where ø is the point at infinity. For this set of scalars, we have all the skews as 0.
332 // This means that we will reach the point at infinity while adding the stagger fragments of the
333 // scalars. Since we compute the wnaf with stagger offsets:
334 //
335 // compute_secp256k1_endo_wnaf<8, 2, 3>(u1, false);
336 // compute_secp256k1_endo_wnaf<4, 0, 1>(u2, false);
337 //
338 // we have the following stagger offsets:
339 // u1_low stagger bits: 2 <== add_3 = 2G
340 // u1_high stagger bits: 3 <== add_1 = 3λG
341 // u2_low stagger bits: 0
342 // u2_high stagger bits: 1 <== add_2 = λG
343 //
344 // Hence we add these terms to the accumulator using addition chain:
345 // acc += (((add_1) + add_2) + add_3).
346 //
347 // After adding add_2, the x-coordinate of the accumulator is equal to the x-coordinate of add_3.
348 // Using incomplete addition formulae, we catch a circuit error as the addition is not valid if the
349 // x-coordinates are equal. To avoid this, we use the complete addition formulae to add add_1, add_2,
350 // add_3 to the accumulator. The increases the circuit size for secp256k1_ecdsa_mul by 730 gates but we
351 // accept that for now to ensure correctness.
352 //
353 const uint256_t scalar_g1("0x9d496650d261d31af6aa4cf41e435ed739d0fe2c34728a21a0df5c66a3504ccd");
354 const uint256_t scalar_u1("0xf3d9f52f0f55d3da6f902aa842aa604005633f3d165bc800f3a3aa661b18df5f");
355 const uint256_t scalar_u2("0x1323b0342b1a56a076cbf5e3899156fbf3f439f2c3b0d5a95b9ef74622447f2e");
356
357 // Check the assumptions
358 BB_ASSERT(scalar_g1 < fr::modulus);
359 BB_ASSERT(scalar_u1 < fr::modulus);
360 BB_ASSERT(scalar_u2 < fr::modulus);
361 BB_ASSERT((fr(scalar_g1) * fr(scalar_u2) + fr(scalar_u1)).is_zero());
362 BB_ASSERT((g1::one * fr(scalar_u1) + (g1::one * fr(scalar_g1)) * fr(scalar_u2)).is_point_at_infinity());
363
364 // Create the circuit
366 element_ct P_a = element_ct::from_witness(&builder, g1::one * fr(scalar_g1));
367 scalar_ct u1 = scalar_ct::from_witness(&builder, fr(scalar_u1));
368 scalar_ct u2 = scalar_ct::from_witness(&builder, fr(scalar_u2));
369 auto output = element_ct::secp256k1_ecdsa_mul(P_a, u1, u2);
370
371 // Check that the output is the point at infinity
372 EXPECT_EQ(output.is_point_at_infinity().get_value(), true);
373
375 }
376};
377
378// Then define the test types
380 testing::Types<stdlib::secp256k1<bb::UltraCircuitBuilder>, stdlib::secp256k1<bb::MegaCircuitBuilder>>;
381
382// Now register the test suite with the types
384
385// Define the individual tests
386TYPED_TEST(stdlibBiggroupSecp256k1, GetStaggeredWnafFragmentValue4bit)
387{
388 TestFixture::template test_get_staggered_wnaf_fragment_value<4>();
389}
390TYPED_TEST(stdlibBiggroupSecp256k1, GetStaggeredWnafFragmentValue8bit)
391{
392 TestFixture::template test_get_staggered_wnaf_fragment_value<8>();
393}
395{
396 TestFixture::test_wnaf_secp256k1();
397}
398TYPED_TEST(stdlibBiggroupSecp256k1, WnafSecp256k1LargeScalarRegression1)
399{
400 TestFixture::test_wnaf_secp256k1_scalar_exceeding_modulus_regression_1();
401}
402TYPED_TEST(stdlibBiggroupSecp256k1, WnafSecp256k1LargeScalarRegression2)
403{
404 TestFixture::test_wnaf_secp256k1_scalar_exceeding_modulus_regression_2();
405}
407{
408 TestFixture::test_wnaf_8bit_secp256k1();
409}
411{
412 TestFixture::test_ecdsa_mul_secp256k1();
413}
414TYPED_TEST(stdlibBiggroupSecp256k1, EcdsaMulSecp256k1SkewHandlingRegression)
415{
416 TestFixture::test_secp256k1_ecdsa_mul_skew_handling_regression();
417}
418TYPED_TEST(stdlibBiggroupSecp256k1, EcdsaMulSecp256k1StaggerRegression)
419{
420 TestFixture::test_secp256k1_ecdsa_mul_stagger_regression();
421}
#define BB_ASSERT(expression,...)
Definition assert.hpp:67
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
virtual uint32_t get_random_uint32()=0
constexpr uint64_t get_msb() const
Definition uintx.hpp:69
Implements boolean logic in-circuit.
Definition bool.hpp:59
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:75
AluTraceBuilder builder
Definition alu.test.cpp:124
bool expected_result
numeric::RNG & engine
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.
Definition api.hpp:5
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