chore: add tests around MSM invalid scalar handling#23176
Merged
Conversation
Member
Author
|
Adding some tests to document behaviour of this opcode. |
federicobarbacovi
approved these changes
May 13, 2026
Contributor
federicobarbacovi
left a comment
There was a problem hiding this comment.
Looks good, thanks for adding these tests!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds focused DSL tests for the
MultiScalarMulACIR opcode covering Grumpkin scalars that lie outside the Grumpkin scalar field (i.e.>= bb::fq::modulus), and writes up the resulting safety analysis. No production code changes — this is test coverage plus a small refactor to share the existing single-term MSM circuit helpers.What changed
barretenberg/cpp/src/barretenberg/dsl/acir_format/multi_scalar_mul.test.cpp:push_point,push_scalar,make_msm,run_circuit, CRS setup) out ofMultiScalarMulInfinityTestsinto a shared base fixtureMsmSingleTermFixture<Builder>.MultiScalarMulInfinityTestsnow derives from it; behaviour and test names are unchanged.MultiScalarMulScalarBoundsTests(run for bothUltraCircuitBuilderandMegaCircuitBuilder), driving the opcode through the normal ACIR → circuit path withpredicate = 1:vScalarEqualToModulusFailsrScalarModulusPlusOneFailsr + 1ScalarModulusMinusOneProvesr - 1(largest in-field scalar)MaxRepresentableScalarFails2^254 - 1(limbs in range, value> r)ScalarWithOversizedHiLimbFails2^254(hilimb= 2^126, 127 bits)AddingGrumpkinModulusDoesNotReproveSameOutput5vs5 + r5proves the output;5 + rdoes not, even though(5+r)·P = 5·PHere
r == bb::fq::modulusis the Grumpkin scalar field modulus, which is also the order of the Grumpkin group (Grumpkin's scalar field is BN254's base field). For the rejected cases the test claims the mathematically-correct reduced result(v mod r)·P, so the rejection is purely from the scalar-validity constraints, not a mismatched output.Test commands and results
Result: 18/18 passed (6 new tests × 2 builders, plus the 6 pre-existing infinity tests confirming the refactor is behaviour-preserving). For the rejected-scalar cases the circuit checker reports
circuit contains invalid witnesses: field_t::range_constraint— the borrow-comparison range checks added bycycle_scalar'svalidate_scalar_is_in_field.Running the full
*MultiScalarMul*filter also surfaces 8 failures inMultiScalarMul{None,Points,Scalars,Both}Constant.GenerateVKFromConstraints— these are pre-existing and unrelated: they throwbn254 g1 data not found at /aztec-packages-private/.bb-crsbecause the BN254 G1 CRS is not downloaded in this environment (VK generation needs it;CircuitChecker-based tests don't). They fail identically without this change.Safety report: out-of-field Grumpkin scalars in the MSM opcode
Background
The
MultiScalarMulopcode (dsl/acir_format/multi_scalar_mul.cpp) computessum(scalars[i] · points[i])on Grumpkin. Each scalar is supplied as two field-element limbs —scalar_lo(low 128 bits) andscalar_hi(next 126 bits) — and reconstructed asv = lo + hi · 2^128. The limb encoding can represent any value in[0, 2^254), which strictly contains[0, r)(r ≈ 2^253.2), so non-canonical scalars are expressible.to_grumpkin_scalar(dsl/acir_format/witness_constant.cpp) builds acycle_scalarfrom the two limbs via the publiccycle_scalar(lo, hi)constructor, which callsvalidate_scalar_is_in_field(). That adds in-circuit constraints (viavalidate_split_in_field_unsafe, a borrow-subtraction comparison) enforcingv < r. Separately,cycle_group::batch_mulrange-constrains the limbs tolo < 2^128andhi < 2^126as part of the MSM algorithm — andvalidate_split_in_field_unsaferelies on exactly those range constraints being applied.Answers
Is it impossible to prove a circuit which is passed an invalid Grumpkin scalar? Yes. Any scalar
v >= rmakes the circuit unsatisfiable:validate_scalar_is_in_fieldis violated (its hi/lo borrow range checks cannot all hold), so no satisfying witness exists — neither an honest nor a dishonest prover can produce a valid proof. A value>= 2^254additionally violates thehi < 2^126limb range constraint applied bybatch_mul. The check is sound because the limb range constraints it depends on are applied unconditionally inside the MSM. (Whenpredicateis witness-false the scalar is first replaced by the constant1, and during VK generation the limbs are overwritten with a dummy in-field value — so neither path can smuggle an out-of-field scalar past the check.)Can adding the Grumpkin modulus to a scalar prove the same output? No. Although
s·P = (s + r)·Pin the group, the circuit enforces a canonical scalar< r, sos + r(which is>= r) is rejected. There is no representable witness that both equalss + rand passesvalidate_scalar_is_in_field. Caller-side malleability of the scalar therefore cannot produce an alternate proof of an existing MSM output.AddingGrumpkinModulusDoesNotReproveSameOutputdemonstrates both halves of this.Other edge cases / boundary values.
r - 1is the largest accepted scalar and proves correctly;r,r + 1, and2^254 - 1are all rejected — the last shows the limb range constraints alone aren't enough and the explicit< rcheck does the work;2^254is rejected by both the limb range constraint and the field check.0is accepted and yields the point at infinity (already covered by the existingResultIsInfinitytest).Conclusion
Allowing invalid Grumpkin scalars through to barretenberg is safe in the soundness sense. If a caller (Noir, hand-written ACIR, or a buggy/malicious frontend) hands the MSM opcode a scalar outside
[0, r), the worst that happens is the circuit becomes unsatisfiable and proving fails — fail-closed. There is no soundness gap: an out-of-field scalar is not silently reduced into a different valid statement, ands + rcannot stand in fors. So canonicalising scalars does not have to be done entirely on the caller side; barretenberg's in-circuitvalidate_scalar_is_in_fieldis a real backstop. The only caveat is a liveness one — a frontend that emits a non-canonical scalar will produce circuits that cannot be proven, which is the desired behaviour.Created by claudebox · group:
cli-startup-research