Skip to content

Conversation

@dbanks12
Copy link
Contributor

@dbanks12 dbanks12 commented Jan 9, 2026

Summary

  • Fix ECC tracegen add_predicate bug for points with same y-coordinate but different x-coordinate
  • Add constraining test for this edge case using real Grumpkin points

Description

The ECC tracegen had a bug where add_predicate was set to (!x_match && !y_match), requiring both coordinates to differ. However, the PIL constraint sel = double_op + add_op + INFINITY_PRED requires add_op = 1 whenever x-coordinates differ (regardless of y-coordinates).

This caused constraint failures when adding two points with different x but same y:

  • x_match = 0, y_match = 1
  • double_op = 0 (not doubling)
  • INFINITY_PRED = x_match * (1 - y_match) = 0
  • PIL requires: add_op = sel - double_op - INFINITY_PRED = 1
  • Bug produced: add_op = 0 → constraint sel = 0 + 0 + 0 = 0 ≠ 1 fails

Why this edge case exists

On Grumpkin (y² = x³ - 17), two distinct points can share the same y-coordinate due to cube roots of unity in BN254 Fr. If (x, y) is on the curve, then (ω·x, y) is also valid since ω³ = 1.

Fix

// Before (BUG):
bool add_predicate = (!x_match && !y_match);

// After:
bool add_predicate = !x_match;

Test plan

  • New constraining test EccAddSameYDifferentX using simulation → tracegen → PIL verification
  • Verified test fails without fix, passes with fix
  • All 26 existing ECC tests pass

🤖 Generated with https://claude.ai/code

Co-Authored-By: Claude noreply@anthropic.com

Copy link
Contributor Author

dbanks12 commented Jan 9, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@dbanks12 dbanks12 marked this pull request as ready for review January 9, 2026 20:12
@dbanks12 dbanks12 changed the title fix!: ecc add predicate tracegen bug fix!: ecc add predicate completeness bug Jan 9, 2026
@dbanks12 dbanks12 requested review from MirandaWood and removed request for IlyasRidhuan, fcarreiro and jeanmon January 9, 2026 20:14
Copy link
Contributor

@MirandaWood MirandaWood left a comment

Choose a reason for hiding this comment

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

Nice! I also found this here: #19462
But mine incorrectly assumes that we cannot have real coordinates with !x_match && y_match, so this PR should go in and I'll edit mine accordingly 🙌

MirandaWood added a commit that referenced this pull request Jan 12, 2026
@MirandaWood MirandaWood merged commit 66df336 into merge-train/avm Jan 12, 2026
15 of 16 checks passed
@MirandaWood MirandaWood deleted the db/ecc-bug branch January 12, 2026 14:12
@AztecBot AztecBot mentioned this pull request Jan 12, 2026
github-merge-queue bot pushed a commit that referenced this pull request Jan 12, 2026
BEGIN_COMMIT_OVERRIDE
feat(avm security): add static check for isolated/unused columns
(#19489)
feat(avm): use noop calldata hasher in fast sim (#19495)
chore(avm): rename indirect -> addressing mode (#19491)
chore(avm): small cursor optimizations
chore(avm):! rename indirect -> addressing mode (PIL) (#19493)
fix(avm): constraint when unwinding empty call stack (#19485)
feat(avm): Fuzz debug log and refactor env getter (#19494)
fix!: ecc add predicate completeness bug (#19471)
chore(avm): callstackmetadatacollector clarifications (#19490)
chore: sanity assert in execution for bytecode id (#19486)
fix!: sstore allowed injection of malicious write rows (#19470)
fix!: defensive ghost row constraints in bc_hashing pil (#19481)
fix(avm): fix execution::mov for mac? (#19507)
chore(avm)!: resolve execution TODOs (#19501)
fix!: multiple traces had ghost row injection vulnerabilities (#19480)
fix(avm): defensively copy MemoryValues (#19512)
feat: align TS and BB log levels (#19518)
END_COMMIT_OVERRIDE
MirandaWood added a commit that referenced this pull request Jan 14, 2026
### ECC Pre-Audit - Normalise infinities

Closes
[AVM-193](https://linear.app/aztec-labs/issue/AVM-193/normalise-input-infinity-points-to-ecc-add)

NOTE: The issue of no operation being set is now addressed in #19471 =>
this PR only normalises `inf`s so they always have `(0, 0)` coordinates
in `ecc.pil`. Though the circuit no longer fails, it does incorrectly
set the predicates (i.e. `double = 0` when doubling `inf`), which could
be a footgun.

In pre-audit it was found that our C++ elliptic curve point class
`StandardAffinePoint` accepts different representations of the infinity
(`O`) point - this makes sense as in noir we use `(0, 0)` and in BB we
use `((P+1)/2, 0)`. This would be fine if any ECC calculations in the
AVM _first_ checked `is_inf` and overrided any subsequent
coordinate-based operations. But the `ecc.pil` trace sets whether we
have a `double` or `add` operation based on coordinates without gating
by any `is_inf` checks:

```
        bool x_match = p.x() == q.x();
        bool y_match = p.y() == q.y();

        bool double_predicate = (x_match && y_match);
        bool add_predicate = (!x_match && !y_match);
        // If x match but the y's don't, the result is the infinity point when adding;
        bool infinity_predicate = (x_match && !y_match);
```

Assuming (understandably!) that two infinity points would share the same
coordinates, the above works fine, but we can input a BB-standard `O` as
`p` and a Noir-standard `O` as `q` from as early as the simulation call
without any errors/checks. ~This results in `!x_match && y_match` which
means none of `double_predicate`, `add_predicate`, or
`infinity_predicate` are true and the circuit falls over. See
c9da1ea for repro of this.~

~Though there is no current flow that throws here, I think it's worth
addressing as we can make the circuit fail with what should be valid
inputs.~



### Fix

The current approach is to normalise any _input_ infinity points (we
already normalise _resulting_ infinity points in `ecc` and `scalar_mul`)
at the simulation stage, so events with any infs always have `(0, 0)`
coordinates. This means:

- No changes required to `ecc.pil` or the `execution.pil` dispatch
- No changes to memory reads/writes
- The ecc simulator sets `x = 0, y = 0` if the point `is_inf` for
standard add and scalar mul events
- `ecc_mem.pil` now constrains that `ecc.pil` has `x = 0, y = 0` if the
point `is_inf` via the existing lookup (requires 4 more columns)

We could alternatively normalise the points in tracegen, but this means
we don't use the values emitted in the events, which feels a bit gross.
We could also add many gating constraints to `ecc.pil` to handle `inf`
edge cases, but this would be expensive and defeats the point a bit of
the trace assuming that the input points are on the curve and validated.
So this approach felt the smoothest!
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