Skip to content

Commit b3a58e1

Browse files
author
artem-nm
committed
fix: use WASM Montgomery constants in coset_generator()
The WASM branch of coset_generator() was using native Montgomery form constants (R=2^256) instead of the WASM-specific constants (R=2^261). Every other constant accessor (cube_root_of_unity, get_root_of_unity, r_squared) correctly dispatches to Params::*_wasm_* variants — this was a copy-paste oversight. On WASM, coset_generator() returned field element 0x28d4a230...d2800001 instead of the correct value (5 for BN254 Fr). Added CosetGeneratorUsesCorrectConstants test that independently derives the expected value and catches this on any WASM test run.
1 parent 0daa54d commit b3a58e1

File tree

2 files changed

+59
-5
lines changed

2 files changed

+59
-5
lines changed

barretenberg/cpp/src/barretenberg/ecc/curves/field_params_constants.test.cpp

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,46 @@ TYPED_TEST_P(FieldConstantsTest, CosetGeneratorConsistency)
328328
EXPECT_EQ(expected, static_cast<uint512_t>(coset_generator_wasm));
329329
}
330330

331+
// Verify coset_generator() returns the correct field element by independently computing
332+
// Montgomery form from the raw integer value using platform-specific field arithmetic.
333+
// Before the fix, the WASM branch of coset_generator() used native Montgomery constants
334+
// (R=2^256) instead of WASM constants (R=2^261). On WASM, Fr(5) computes the WASM-Montgomery
335+
// encoding via self_to_montgomery_form(), while the buggy coset_generator() returned the
336+
// native encoding — a completely different field element.
337+
TYPED_TEST_P(FieldConstantsTest, CosetGeneratorUsesCorrectConstants)
338+
{
339+
using Params = typename TypeParam::Params;
340+
using Field = typename TypeParam::Field;
341+
Field coset_gen = Field::coset_generator();
342+
343+
// Recover the raw (non-Montgomery) integer value from the known-correct native constants.
344+
// native_encoding = g * R_native mod p, so g = native_encoding * R_native^{-1} mod p.
345+
// We compute this via uint512 arithmetic (independent of platform Montgomery machinery).
346+
uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 };
347+
uint256_t native_encoding{
348+
Params::coset_generator_0, Params::coset_generator_1, Params::coset_generator_2, Params::coset_generator_3
349+
};
350+
351+
// R_native = 2^256. Compute R_native^{-1} mod p via Fermat's little theorem: R^{-1} = R^{p-2} mod p.
352+
// But for a simpler approach: native_encoding IS g*R mod p, and Field(uint256_t) does self_to_montgomery_form()
353+
// which computes input * R mod p. So if we can extract g first, Field(g) gives us the correct platform encoding.
354+
//
355+
// Approach: use the known relationship wasm = native * 32 mod p (since R_wasm/R_native = 2^5).
356+
// On WASM: coset_generator() should return native * 32 mod p as its internal representation.
357+
// On native: coset_generator() should return native directly.
358+
uint512_t expected_wasm_encoding = (uint512_t(native_encoding) * 32) % mod;
359+
#if defined(__SIZEOF_INT128__) && !defined(__wasm__)
360+
Field expected(
361+
Params::coset_generator_0, Params::coset_generator_1, Params::coset_generator_2, Params::coset_generator_3);
362+
#else
363+
Field expected(static_cast<uint64_t>(expected_wasm_encoding.lo.data[0]),
364+
static_cast<uint64_t>(expected_wasm_encoding.lo.data[1]),
365+
static_cast<uint64_t>(expected_wasm_encoding.lo.data[2]),
366+
static_cast<uint64_t>(expected_wasm_encoding.lo.data[3]));
367+
#endif
368+
EXPECT_EQ(coset_gen, expected);
369+
}
370+
331371
REGISTER_TYPED_TEST_SUITE_P(FieldConstantsTest,
332372
Modulus,
333373
RSquared,
@@ -341,7 +381,8 @@ REGISTER_TYPED_TEST_SUITE_P(FieldConstantsTest,
341381
WasmPowMinus29,
342382
WasmCubeRootConsistency,
343383
WasmPrimitiveRootConsistency,
344-
CosetGeneratorConsistency);
384+
CosetGeneratorConsistency,
385+
CosetGeneratorUsesCorrectConstants);
345386

346387
using FieldTestTypes = ::testing::Types<Bn254FqTestConfig,
347388
Bn254FrTestConfig,
@@ -351,3 +392,16 @@ using FieldTestTypes = ::testing::Types<Bn254FqTestConfig,
351392
Secp256r1FrTestConfig>;
352393

353394
INSTANTIATE_TYPED_TEST_SUITE_P(AllFields, FieldConstantsTest, FieldTestTypes);
395+
396+
// BN254 Fr-specific test: the coset generator is the field element 5.
397+
// Fr(5) independently computes 5 → Montgomery form using the platform's arithmetic.
398+
// coset_generator() returns a precomputed Montgomery encoding.
399+
// If the precomputed encoding is for the wrong platform, these will differ.
400+
// Before the fix on WASM: Fr(5) = 5*R_wasm mod p, but coset_generator() = 5*R_native mod p → mismatch.
401+
TEST(CosetGeneratorBn254, CosetGeneratorMatchesFieldElement)
402+
{
403+
bb::fr coset_gen = bb::fr::coset_generator();
404+
bb::fr expected(5);
405+
EXPECT_EQ(coset_gen, expected) << "coset_generator() does not equal Fr(5) — "
406+
"likely using wrong Montgomery basis constants";
407+
}

barretenberg/cpp/src/barretenberg/ecc/fields/field_declarations.hpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,10 +289,10 @@ template <class Params_> struct alignas(32) field {
289289
};
290290
#else
291291
const field result{
292-
Params::coset_generator_0,
293-
Params::coset_generator_1,
294-
Params::coset_generator_2,
295-
Params::coset_generator_3,
292+
Params::coset_generator_wasm_0,
293+
Params::coset_generator_wasm_1,
294+
Params::coset_generator_wasm_2,
295+
Params::coset_generator_wasm_3,
296296
};
297297
#endif
298298

0 commit comments

Comments
 (0)