Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,9 @@ struct MsmScalar {
}
};

template <typename Builder> class MultiScalarMulInfinityTests : public ::testing::Test {
// Shared single-term MSM circuit helpers: build a one point/one scalar MSM constraint with predicate=1
// from explicit witness values, and run the resulting circuit.
template <typename Builder> class MsmSingleTermFixture : public ::testing::Test {
protected:
static void SetUpTestSuite() { bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); }

Expand Down Expand Up @@ -402,6 +404,8 @@ template <typename Builder> class MultiScalarMulInfinityTests : public ::testing
}
};

template <typename Builder> class MultiScalarMulInfinityTests : public MsmSingleTermFixture<Builder> {};

TYPED_TEST_SUITE(MultiScalarMulInfinityTests, BuilderTypes);

// scalar=0 → result = ∞: valid proof with out_point_is_infinite=1.
Expand Down Expand Up @@ -449,3 +453,135 @@ TYPED_TEST(MultiScalarMulInfinityTests, ForgedFiniteFlagOnInfinityResultFails)
EXPECT_TRUE(!ok || err.find("assert_eq") != std::string::npos)
<< "Forged finite flag on infinity result should fail";
}

// ============================================================
// Scalar field-bounds tests
// ============================================================
//
// The MSM opcode receives a Grumpkin scalar as two field limbs: lo (low 128 bits) and hi (next 126
// bits), reconstructing v = lo + hi * 2^128. cycle_scalar's public constructor adds an in-circuit
// check that v < r, where r == bb::fq::modulus is the Grumpkin scalar field modulus (and also the
// order of the Grumpkin group, since Grumpkin's scalar field is BN254's base field). batch_mul
// additionally range-constrains the limbs to lo < 2^128 and hi < 2^126. These tests pin behaviour
// at and beyond the modulus boundary: an out-of-range scalar must make the circuit unsatisfiable,
// and the group law's s ≡ s + r equivalence must not let a caller smuggle a non-canonical scalar
// through to barretenberg.

namespace {
// r = order of the Grumpkin group = bb::fq::modulus.
const uint256_t grumpkin_scalar_modulus = bb::fq::modulus;

// Build an MsmScalar straight from a uint256_t value, splitting at the 128-bit limb boundary with no
// modular reduction (so out-of-field values can be expressed).
MsmScalar msm_scalar_from_u256(const uint256_t& v)
{
return { MsmFF(v.slice(0, 128)), MsmFF(v.slice(128, 256)) };
}
} // namespace

template <typename Builder> class MultiScalarMulScalarBoundsTests : public MsmSingleTermFixture<Builder> {};

TYPED_TEST_SUITE(MultiScalarMulScalarBoundsTests, BuilderTypes);

// scalar == r: rejected. The in-circuit "scalar < r" check fails. (r·P = O, so the caller gains
// nothing by claiming the point at infinity as the result.)
TYPED_TEST(MultiScalarMulScalarBoundsTests, ScalarEqualToModulusFails)
{
BB_DISABLE_ASSERTS();
MsmGrumpkinPoint point = MsmGrumpkinPoint::random_element();
auto [constraint, witness] = TestFixture::make_msm(
MsmAcirPoint::from_native(point), msm_scalar_from_u256(grumpkin_scalar_modulus), MsmAcirPoint::infinity());

auto [ok, err] = TestFixture::run_circuit(constraint, witness);
EXPECT_FALSE(ok) << "scalar == Grumpkin scalar modulus must not produce a satisfiable circuit";
}

// scalar == r + 1: rejected, even though (r + 1)·P == 1·P == P. The in-circuit "scalar < r" check
// fails despite both limbs being within their range constraints.
TYPED_TEST(MultiScalarMulScalarBoundsTests, ScalarModulusPlusOneFails)
{
BB_DISABLE_ASSERTS();
MsmGrumpkinPoint point = MsmGrumpkinPoint::random_element();
auto [constraint, witness] = TestFixture::make_msm(MsmAcirPoint::from_native(point),
msm_scalar_from_u256(grumpkin_scalar_modulus + uint256_t(1)),
MsmAcirPoint::from_native(point));

auto [ok, err] = TestFixture::run_circuit(constraint, witness);
EXPECT_FALSE(ok) << "scalar == Grumpkin scalar modulus + 1 must not produce a satisfiable circuit";
}

// scalar == r - 1: the largest in-field scalar. This must prove fine.
TYPED_TEST(MultiScalarMulScalarBoundsTests, ScalarModulusMinusOneProves)
{
BB_DISABLE_ASSERTS();
MsmGrumpkinPoint point = MsmGrumpkinPoint::random_element();
bb::fq scalar_native = bb::fq(grumpkin_scalar_modulus - uint256_t(1));
MsmGrumpkinPoint result = point * scalar_native;
ASSERT_FALSE(result.is_point_at_infinity());
auto [constraint, witness] = TestFixture::make_msm(MsmAcirPoint::from_native(point),
msm_scalar_from_u256(grumpkin_scalar_modulus - uint256_t(1)),
MsmAcirPoint::from_native(result));

auto [ok, err] = TestFixture::run_circuit(constraint, witness);
EXPECT_TRUE(ok) << "scalar == Grumpkin scalar modulus - 1 (largest in-field scalar) should prove. err: " << err;
}

// scalar == 2^254 - 1: the largest value the (128 + 126)-bit limb encoding can represent. Both limbs
// satisfy their range constraints, so the only thing rejecting it is the in-circuit "scalar < r"
// check — exercising the "limbs in range but value out of field" path.
TYPED_TEST(MultiScalarMulScalarBoundsTests, MaxRepresentableScalarFails)
{
BB_DISABLE_ASSERTS();
MsmGrumpkinPoint point = MsmGrumpkinPoint::random_element();
uint256_t max_representable = (uint256_t(1) << 254) - uint256_t(1);
MsmGrumpkinPoint result = point * bb::fq(max_representable); // (2^254 - 1) mod r
auto [constraint, witness] = TestFixture::make_msm(
MsmAcirPoint::from_native(point), msm_scalar_from_u256(max_representable), MsmAcirPoint::from_native(result));

auto [ok, err] = TestFixture::run_circuit(constraint, witness);
EXPECT_FALSE(ok) << "scalar == 2^254 - 1 (> Grumpkin modulus) must not produce a satisfiable circuit";
}

// hi limb == 2^126 (one bit too wide), lo == 0, i.e. scalar value 2^254. Both the limb range
// constraint (hi < 2^126) and the "scalar < r" check reject it.
TYPED_TEST(MultiScalarMulScalarBoundsTests, ScalarWithOversizedHiLimbFails)
{
BB_DISABLE_ASSERTS();
MsmGrumpkinPoint point = MsmGrumpkinPoint::random_element();
uint256_t two_pow_254 = uint256_t(1) << 254;
MsmGrumpkinPoint result = point * bb::fq(two_pow_254);
MsmScalar scalar{ MsmFF(0), MsmFF(uint256_t(1) << 126) }; // hi has 127 bits
auto [constraint, witness] =
TestFixture::make_msm(MsmAcirPoint::from_native(point), scalar, MsmAcirPoint::from_native(result));

auto [ok, err] = TestFixture::run_circuit(constraint, witness);
EXPECT_FALSE(ok) << "scalar hi limb of 127 bits (value 2^254) must not produce a satisfiable circuit";
}

// Group-law equivalence does not transfer: s·P == (s + r)·P, but the circuit accepts only the
// canonical scalar s. Adding the Grumpkin modulus to a scalar cannot reprove the same output.
TYPED_TEST(MultiScalarMulScalarBoundsTests, AddingGrumpkinModulusDoesNotReproveSameOutput)
{
BB_DISABLE_ASSERTS();
MsmGrumpkinPoint point = MsmGrumpkinPoint::random_element();
bb::fq scalar_native = bb::fq(5);
MsmGrumpkinPoint result = point * scalar_native;
ASSERT_FALSE(result.is_point_at_infinity());

// Sanity: the canonical scalar proves the result.
{
auto [constraint, witness] = TestFixture::make_msm(
MsmAcirPoint::from_native(point), MsmScalar::from_native(scalar_native), MsmAcirPoint::from_native(result));
auto [ok, err] = TestFixture::run_circuit(constraint, witness);
EXPECT_TRUE(ok) << "canonical scalar should prove the MSM result. err: " << err;
}

// The non-canonical scalar s + r yields the same point mathematically, but the circuit rejects it.
{
auto [constraint, witness] = TestFixture::make_msm(MsmAcirPoint::from_native(point),
msm_scalar_from_u256(uint256_t(5) + grumpkin_scalar_modulus),
MsmAcirPoint::from_native(result));
auto [ok, err] = TestFixture::run_circuit(constraint, witness);
EXPECT_FALSE(ok) << "scalar s + r must not reprove the output of scalar s";
}
}
Loading