stdlib-point-at-infinity

star 443

Guidelines for handling point-at-infinity in stdlib circuit types. Use when working on serialization, public inputs, or cycle_group/biggroup code.

AztecProtocol By AztecProtocol schedule Updated 2/13/2026

name: stdlib-point-at-infinity description: Guidelines for handling point-at-infinity in stdlib circuit types. Use when working on serialization, public inputs, or cycle_group/biggroup code.

Stdlib Point-at-Infinity Handling

Two stdlib element types for bn254

  • element_default::element (Ultra arithmetization): Separate _is_infinity bool_ct flag. Coordinates can be arbitrary when infinity is set. Has is_point_at_infinity() returning bool_ct.
  • goblin_element (Mega arithmetization): Represents infinity as (0,0) by construction. No separate infinity flag. No is_point_at_infinity() method on the circuit type. ECCVM enforces the (0,0) convention.
  • The alias element<Builder, Fq, Fr, G> resolves to one or the other via IsGoblinBigGroup.
  • Both types have get_value() returning a native affine_element with is_point_at_infinity().

cycle_group (Grumpkin) infinity handling

cycle_group has _is_infinity bool computed from x^2 + 5*y^2 == 0 (which has no non-trivial solutions in the field, since p == 2 mod 5). For (0,0) this gives 0 == 0, so infinity is auto-detected.

Non-canonical infinity: cycle_group operations (operator+, operator-, dbl, batch_mul) can produce points at infinity with non-canonical coordinates (_is_infinity=true but x,y != 0,0). This happens because the arithmetic uses conditional_assign to avoid division by zero -- the "real" coordinates are garbage when the infinity flag is set.

Observation boundaries: Canonicalization to (0,0) is deferred to these boundaries:

  • StdlibCodec::serialize_to_fields -- canonicalizes grumpkin_commitment via conditional_assign
  • cycle_group::set_public -- canonicalizes before exposing as public inputs
  • cycle_group::operator== and assert_equal -- handles infinity comparison

StdlibCodec::serialize_to_fields (field_conversion.hpp)

  • grumpkin_commitment: Canonicalizes infinity to (0,0) via conditional_assign. This IS needed because the IPA::accumulate -> full_verify_recursive path computes G_zero_1 + G_zero_2 * alpha using cycle_group arithmetic, which can produce non-canonical infinity when a malicious prover sends both G_zero values as (0,0).
  • bn254_commitment: Allows canonical (0,0) infinity; asserts (BB_ASSERT) that infinity points have zero coordinates. All existing code paths (public inputs, transcript) produce canonical (0,0) infinity, so the assert is a safety guard against misuse. No canonicalization is performed (unlike grumpkin_commitment), since there are no available code paths that produce non-canonical bn254 infinity.

Analyzing whether canonicalization is needed

Trace whether the value comes from:

  1. Deserialization (from public inputs via reconstruct_from_public, or from transcript via receive_from_prover): Coordinates are already canonical (0,0) for infinity. Canonicalization is a no-op.
  2. cycle_group arithmetic (operator+, operator-, dbl, batch_mul): Coordinates may be non-canonical when _is_infinity is true. Canonicalization IS needed.

Key production paths for grumpkin commitments through serialize_to_fields:

  • IPA add_claim_to_hash_buffer (verifier side): Commitment is deserialized from public inputs -> already canonical.
  • IPA accumulate hashing of G_zero: G_zero is deserialized from transcript -> already canonical.
  • IPA full_verify_recursive: Accumulated commitment is the result of cycle_group arithmetic (G_zero_1 + G_zero_2 * alpha) -> may be non-canonical if infinity -> canonicalization needed.
  • VK hashing (flavor.hpp to_field_elements/hash_with_origin_tagging): Commitments are deserialized from fields -> already canonical.

Recursive verification and malicious provers

For recursive verifier circuits, the circuit must be constructible even with malicious witness values (it just won't be satisfiable). This means:

  • Do NOT use BB_ASSERT on values a malicious prover can control -- it would crash circuit construction.
  • Use conditional_assign canonicalization instead, which produces correct circuit constraints regardless of witness values.
  • BB_ASSERT is appropriate for invariants that hold across all existing code paths (e.g., bn254_commitment in serialize_to_fields asserts canonical (0,0) form for infinity, since all paths that can reach it produce canonical infinity).

Common bug patterns to watch for

These patterns have caused repeated bugs across biggroup, cycle_group, ECCVM, AVM, and ECDSA:

1. Representation mismatch: internal sentinel vs (0,0)

BB's native affine_element uses an internal sentinel for infinity (MSB set on the x coordinate's raw representation, not a valid field element). Noir, AVM, and the transcript convention use (0,0). Any code that reads raw coordinates without checking is_point_at_infinity() (or without calling get_standard_form() on stdlib types) will see sentinel values, not (0,0). Always use the standard-form convention (0,0) at component boundaries.

2. Forgetting to propagate the infinity flag through conditional operations

When doing conditional_assign or conditional_select on points, both the coordinates AND the _is_infinity flag must be selected. Multiple ECDSA and biggroup bugs came from selecting x/y but leaving _is_infinity unchanged.

3. Incomplete addition formulas crash on infinity

Performance-optimized ECC (chain_add, Montgomery ladder) assumes inputs are never infinity and never equal. When infinity appears as an intermediate value, these formulas divide by zero or produce wrong results. If a code path can encounter infinity mid-computation, use complete addition (operator+) instead of chain_add_start/chain_add/chain_add_end. This costs ~2% more gates but is correct.

4. Constructor and validation bypasses

Constructors that accept a direct is_infinity flag can bypass on-curve validation (a point with is_infinity=true but x,y != 0 passes validate_on_curve because the check is skipped for infinity). The 4-argument biggroup constructor with explicit infinity flag is now private. Prefer the 2-argument (x, y) constructor which auto-detects infinity from x == 0 && y == 0.

5. Forgetting to canonicalize before comparison or hashing

cycle_group::operator== and assert_equal handle infinity correctly, but raw coordinate comparison does not. Before comparing or hashing point coordinates, ensure infinity points have canonical (0,0) coordinates. The observation boundary pattern (serialize_to_fields, set_public, operator==) exists for this reason.

Install via CLI
npx skills add https://github.com/AztecProtocol/aztec-packages --skill stdlib-point-at-infinity
Repository Details
star Stars 443
call_split Forks 607
navigation Branch main
article Path SKILL.md
More from Creator
AztecProtocol
AztecProtocol Explore all skills →