Skip to content

chore: field_conversion clean-up/int audit#16898

Merged
ledwards2225 merged 27 commits intomerge-train/barretenbergfrom
si/field-conversion-audit
Sep 19, 2025
Merged

chore: field_conversion clean-up/int audit#16898
ledwards2225 merged 27 commits intomerge-train/barretenbergfrom
si/field-conversion-audit

Conversation

@iakovenkos
Copy link
Contributor

@iakovenkos iakovenkos commented Sep 9, 2025

🧾 Audit Context

stdlib::field_conversion provides the serialization/deserialization methods for stdlib::Transcript.

🛠️ Changes Made

  • Simplified the logic by making it more uniform and getting rid of redundant arguments
  • Enabled on_curve checks for Ultra deserialization of group elements (BN254 and Grumpkin)
  • Removed redundant range constraints in fr --> bigfield conversion and optimized the constraint ensuring that fr = lo + hi * shift. Note that limbs were constrained to be (136, 118) bits, although such range constraints are enforced by bigfield(low, hi) constructor.
  • Optimized point_at_infinity check for Grumpkin points.
  • Expanded docs and made them Doxygen-friendly
  • Added tests and an issue that will be resolved in a follow-up
  • The vks and circuit sizes have changed due to enabled checks and the small optimizations described above

📌 Notes for Reviewers

Agreed with @suyash67 that existing point-at-infinity issue AztecProtocol/barretenberg#1527 should be handled by ensuring the consistent representation of points at infinity in all groups. Added a test that will fail once the issue is resolved.

* @tparam Builder
*/
template <typename Builder> void cycle_group<Builder>::validate_is_on_curve() const
template <typename Builder> void cycle_group<Builder>::validate_on_curve() const
Copy link
Contributor Author

@iakovenkos iakovenkos Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to match the naming pattern in other groups

template <typename Builder> class cycle_group {
public:
using field_t = stdlib::field_t<Builder>;
using BaseField = field_t;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

biggroup and goblin have this alias, seems useful for uniform group element handling in field_conversion

T out(x, y, check_point_at_infinity<Builder, T>(fr_vec));
// Note that in the case of bn254 with Mega arithmetization, the check is delegated to ECCVM, see
// `on_curve_check` in `ECCVMTranscriptRelationImpl`.
out.validate_on_curve();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in-circuit on_curve checks


// Recursively verify CIVC proof
auto mega_vk = std::make_shared<VerificationKey>(builder, key_fields);
auto mega_vk = std::make_shared<VerificationKey>(key_fields);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and in several places below, builder is redundant as field_conversion can extract from the fields being passed while ensuring that those are in-circuit.

// Check the size of the recursive verifier
if constexpr (std::same_as<RecursiveFlavor, MegaZKRecursiveFlavor_<UltraCircuitBuilder>>) {
uint32_t NUM_GATES_EXPECTED = 796978;
uint32_t NUM_GATES_EXPECTED = 803143;
Copy link
Contributor Author

@iakovenkos iakovenkos Sep 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More gates due to enabled on-curve checks

element();
element(const typename NativeGroup::affine_element& input);
element(const Fq& x, const Fq& y);
element(const Fq& x, const Fq& y, const bool_ct& is_infinity);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels like a very natural constructor, here and in other groups

auto sum = low + hi * shift;
builder.assert_equal(f.witness_index, sum.witness_index, "assert_equal");
const fr<Builder> zero = fr<Builder>::from_witness_index(&builder, 0);
fr<Builder>::evaluate_linear_identity(hi * shift, low, -fr_element, zero);
Copy link
Contributor Author

@iakovenkos iakovenkos Sep 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minus 1 gate thanks to combining the addition with constraining to be 0

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!


std::vector<fr<Builder>> fr_vec{ low, hi };
return convert_from_bn254_frs<Builder, fq<Builder>>(builder, fr_vec);
return fq<Builder>(low, hi);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not calling convert_from_bn254_frs cause it's not needed/seems convoluted

*
* @return
*/
template <typename Builder, typename T> bool_t<Builder> check_point_at_infinity(std::span<const fr<Builder>> fr_vec)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isolated the check to handle grumpkin and bn254 uniformly in deserialization

@iakovenkos iakovenkos self-assigned this Sep 16, 2025
@iakovenkos iakovenkos marked this pull request as ready for review September 16, 2025 12:45
@iakovenkos iakovenkos requested a review from jeanmon as a code owner September 16, 2025 12:45
}
}
}
tamper_with_libra_eval(tampered_proof.translator_proof);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the same here, the tampering before would simply invalidate on_curve check. switched to modifying one of the evals

// Check the size of the recursive verifier
if constexpr (std::same_as<RecursiveFlavor, MegaZKRecursiveFlavor_<UltraCircuitBuilder>>) {
uint32_t NUM_GATES_EXPECTED = 796978;
uint32_t NUM_GATES_EXPECTED = 801953;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as a result of enabled on_curve checks

// Efficiently compute ((x^2 + 5 y^2) == 0)
const fr<Builder> x_sqr = fr_vec[0].sqr();
const fr<Builder> y = fr_vec[1];
return (y.madd(y, x_sqr * bb::fr(5)).is_zero());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be:

const fr<Builder> five_y = fr_vec[1] * bb::fr(5);
return (y.madd(five_y, x_sqr)).is_zero());

as the comment says:

$$x^2 + 5y^2 = 0 \textsf{ mod } p$$

BB_ASSERT_EQ(static_cast<uint256_t>(low_val) + (static_cast<uint256_t>(hi_val) << NUM_BITS_IN_TWO_LIMBS),
value,
"field_conversion: limb decomposition");
// checks this decomposition low + hi * 2^64 = value with an assert_equal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the comment needs to be corrected to: low + hi * 2^68

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, stale comment, fixed

using bigfield_ct = fq<Builder>;

if constexpr (IsAnyOf<T, field_ct>) {
;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra semicolon?

{
using Builder = TypeParam;

{ // Serialize and deserialize the Grumpkin generator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be worth adding serialize and deserializing random point(s) on curve for testing along with the edge-cases you've already covered.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, added loops testing 50 random points for both curves

{
constexpr uint64_t NUM_BITS_IN_TWO_LIMBS = 2 * NUM_LIMB_BITS; // 136
constexpr uint64_t UPPER_TWO_LIMB_BITS = TOTAL_BITS - NUM_BITS_IN_TWO_LIMBS; // 118
ASSERT(!fr_element.is_constant());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ledwards2225 after our discussions, i think this should use split_unique from your pr

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually merged your changes from cycle-group-5 to my follow-up and nuked the entire method, see stdlib::field_conversion::convert_challenge in #17034

if constexpr (IsAnyOf<Builder, UltraCircuitBuilder>) {
EXPECT_THROW_OR_ABORT(group_element = bn254_element<Builder>::from_witness(&builder, group_element_val),
"");
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no failure because this is checked in eccvm right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exactly

{
using Builder = TypeParam;
Builder builder;
const fr<Builder> zero(fr<Builder>::from_witness_index(&builder, 0));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should try to avoid this pattern of assuming index 0 corresponds to value 0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oof agree

auto y = bb::stdlib::field_conversion::convert_from_bn254_frs<Builder, T>(frs);
bool expected = std::is_same_v<Builder, UltraCircuitBuilder> ? !point_at_infinity : true;

EXPECT_EQ(x.get_value() == y.get_value(), expected);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nano-nit: in/out seems clearer than x/y in a context where x/y can be plausibly confused with point coordinates

Copy link
Contributor

@ledwards2225 ledwards2225 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me - nice simplifications

Copy link
Contributor

@suyash67 suyash67 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool stuff, thanks for the amazing documentation!

@ledwards2225 ledwards2225 merged commit b31eec9 into merge-train/barretenberg Sep 19, 2025
6 checks passed
@ledwards2225 ledwards2225 deleted the si/field-conversion-audit branch September 19, 2025 19:44
github-merge-queue bot pushed a commit that referenced this pull request Sep 22, 2025
BEGIN_COMMIT_OVERRIDE
chore: Tube-related clean up (#17064)
chore: cycle group cleanup #5 (#17026)
chore: `field_conversion` clean-up/int audit (#16898)
fix: bigfield veridise audit fixes (#16842)
END_COMMIT_OVERRIDE
fcarreiro pushed a commit that referenced this pull request Sep 22, 2025
### 🧾 Audit Context

`stdlib::field_conversion` provides the serialization/deserialization
methods for `stdlib::Transcript`.

### 🛠️ Changes Made

- Simplified the logic by making it more uniform and getting rid of
redundant arguments
- Enabled `on_curve` checks for Ultra deserialization of group elements
(BN254 and Grumpkin)
- Removed redundant range constraints in `fr --> bigfield` conversion
and optimized the constraint ensuring that `fr = lo + hi * shift`. Note
that limbs were constrained to be (136, 118) bits, although such range
constraints are enforced by `bigfield(low, hi)` constructor.
- Optimized `point_at_infinity` check for Grumpkin points.
- Expanded docs and made them Doxygen-friendly
- Added tests and [an
issue](AztecProtocol/barretenberg#1541) that
will be resolved in a follow-up
- The vks and circuit sizes have changed due to enabled checks and the
small optimizations described above

### 📌 Notes for Reviewers

Agreed with @suyash67 that existing point-at-infinity issue
AztecProtocol/barretenberg#1527 should be
handled by ensuring the consistent representation of points at infinity
in all groups. Added a test that will fail once the issue is resolved.

---------

Co-authored-by: ledwards2225 <l.edwards.d@gmail.com>
mralj pushed a commit that referenced this pull request Oct 13, 2025
`stdlib::field_conversion` provides the serialization/deserialization
methods for `stdlib::Transcript`.

- Simplified the logic by making it more uniform and getting rid of
redundant arguments
- Enabled `on_curve` checks for Ultra deserialization of group elements
(BN254 and Grumpkin)
- Removed redundant range constraints in `fr --> bigfield` conversion
and optimized the constraint ensuring that `fr = lo + hi * shift`. Note
that limbs were constrained to be (136, 118) bits, although such range
constraints are enforced by `bigfield(low, hi)` constructor.
- Optimized `point_at_infinity` check for Grumpkin points.
- Expanded docs and made them Doxygen-friendly
- Added tests and [an
issue](AztecProtocol/barretenberg#1541) that
will be resolved in a follow-up
- The vks and circuit sizes have changed due to enabled checks and the
small optimizations described above

Agreed with @suyash67 that existing point-at-infinity issue
AztecProtocol/barretenberg#1527 should be
handled by ensuring the consistent representation of points at infinity
in all groups. Added a test that will fail once the issue is resolved.

---------

Co-authored-by: ledwards2225 <l.edwards.d@gmail.com>
ludamad pushed a commit that referenced this pull request Dec 16, 2025
### 🧾 Audit Context

`stdlib::field_conversion` provides the serialization/deserialization
methods for `stdlib::Transcript`.

### 🛠️ Changes Made

- Simplified the logic by making it more uniform and getting rid of
redundant arguments
- Enabled `on_curve` checks for Ultra deserialization of group elements
(BN254 and Grumpkin)
- Removed redundant range constraints in `fr --> bigfield` conversion
and optimized the constraint ensuring that `fr = lo + hi * shift`. Note
that limbs were constrained to be (136, 118) bits, although such range
constraints are enforced by `bigfield(low, hi)` constructor.
- Optimized `point_at_infinity` check for Grumpkin points.
- Expanded docs and made them Doxygen-friendly
- Added tests and [an
issue](AztecProtocol/barretenberg#1541) that
will be resolved in a follow-up
- The vks and circuit sizes have changed due to enabled checks and the
small optimizations described above

### 📌 Notes for Reviewers

Agreed with @suyash67 that existing point-at-infinity issue
AztecProtocol/barretenberg#1527 should be
handled by ensuring the consistent representation of points at infinity
in all groups. Added a test that will fail once the issue is resolved.

---------

Co-authored-by: ledwards2225 <l.edwards.d@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants