1111#include " barretenberg/stdlib/primitives/curves/secp256k1.hpp"
1212#include " barretenberg/stdlib/primitives/curves/secp256r1.hpp"
1313#include " barretenberg/transcript/origin_tag.hpp"
14+ #include " gtest/gtest.h"
1415#include < vector>
1516
1617using namespace bb ;
@@ -166,6 +167,61 @@ template <typename Curve> class stdlibBiggroupSecp256k1 : public testing::Test {
166167
167168 EXPECT_CIRCUIT_CORRECTNESS (builder);
168169 }
170+
171+ static void test_secp256k1_ecdsa_mul_stagger_regression ()
172+ {
173+ // This test uses the same idea as the skew handling regression test above.
174+ // However, in this test, we use scalars to ensure that we are correctly handling the stagger offsets before
175+ // adding the skew points. The scalars s1, u1, u2 are chosen such that:
176+ // Public key: P = (s1 * G)
177+ //
178+ // u1 * G + u2 * (s1 * G) = ø
179+ //
180+ // where ø is the point at infinity. For this set of scalars, we have all the skews as 0.
181+ // This means that we will reach the point at infinity while adding the stagger fragments of the scalars.
182+ // Since we compute the wnaf with stagger offsets:
183+ //
184+ // compute_secp256k1_endo_wnaf<8, 2, 3>(u1);
185+ // compute_secp256k1_endo_wnaf<4, 0, 1>(u2);
186+ //
187+ // we have the following stagger offsets:
188+ // u1_low stagger bits: 2 <== add_3 = 2G
189+ // u1_high stagger bits: 3 <== add_1 = 3λG
190+ // u2_low stagger bits: 0
191+ // u2_high stagger bits: 1 <== add_2 = λG
192+ //
193+ // Hence we add these terms to the accumulator using addition chain:
194+ // acc += (((add_1) + add_2) + add_3).
195+ //
196+ // After adding add_2, the x-coordinate of the accumulator is equal to the x-coordinate of add_3.
197+ // Using incomplete addition formulae, we catch a circuit error as the addition is not valid if the
198+ // x-coordinates are equal. To avoid this, we use the complete addition formulae to add add_1, add_2, add_3
199+ // to the accumulator. The increases the circuit size for secp256k1_ecdsa_mul by 730 gates but we accept that
200+ // for now to ensure correctness.
201+ //
202+ const uint256_t scalar_g1 (" 0x9d496650d261d31af6aa4cf41e435ed739d0fe2c34728a21a0df5c66a3504ccd" );
203+ const uint256_t scalar_u1 (" 0xf3d9f52f0f55d3da6f902aa842aa604005633f3d165bc800f3a3aa661b18df5f" );
204+ const uint256_t scalar_u2 (" 0x1323b0342b1a56a076cbf5e3899156fbf3f439f2c3b0d5a95b9ef74622447f2e" );
205+
206+ // Check the assumptions
207+ ASSERT (scalar_g1 < fr::modulus);
208+ ASSERT (scalar_u1 < fr::modulus);
209+ ASSERT (scalar_u2 < fr::modulus);
210+ ASSERT ((fr (scalar_g1) * fr (scalar_u2) + fr (scalar_u1)).is_zero ());
211+ ASSERT ((g1::one * fr (scalar_u1) + (g1::one * fr (scalar_g1)) * fr (scalar_u2)).is_point_at_infinity ());
212+
213+ // Create the circuit
214+ Builder builder = Builder ();
215+ element_ct P_a = element_ct::from_witness (&builder, g1::one * fr (scalar_g1));
216+ scalar_ct u1 = scalar_ct::from_witness (&builder, fr (scalar_u1));
217+ scalar_ct u2 = scalar_ct::from_witness (&builder, fr (scalar_u2));
218+ auto output = element_ct::secp256k1_ecdsa_mul (P_a, u1, u2);
219+
220+ // Check that the output is the point at infinity
221+ EXPECT_EQ (output.is_point_at_infinity ().get_value (), true );
222+
223+ EXPECT_CIRCUIT_CORRECTNESS (builder);
224+ }
169225};
170226
171227// Then define the test types
@@ -192,3 +248,7 @@ TYPED_TEST(stdlibBiggroupSecp256k1, EcdsaMulSecp256k1SkewHandlingRegression)
192248{
193249 TestFixture::test_secp256k1_ecdsa_mul_skew_handling_regression ();
194250}
251+ TYPED_TEST (stdlibBiggroupSecp256k1, EcdsaMulSecp256k1StaggerRegression)
252+ {
253+ TestFixture::test_secp256k1_ecdsa_mul_stagger_regression ();
254+ }
0 commit comments