Skip to content

chore: makes ec addition opcode unsafe#8814

Open
guipublic wants to merge 54 commits intonextfrom
gd/unsafe_ec_add2
Open

chore: makes ec addition opcode unsafe#8814
guipublic wants to merge 54 commits intonextfrom
gd/unsafe_ec_add2

Conversation

@guipublic
Copy link
Contributor

@guipublic guipublic commented Sep 26, 2024

This PR is the same as #8374, but with some added runtime checks as suggested by @zac-williamson.
#8374 was merged and then rollback because it caused issues with e2e tests.
We should not merge this one until this issue is fixed, but I need the PR as ready for review in order to have the CI to run.

The PR makes the ACIR opcode 'EC ADD' unsafe because the safety checks are now done on the Noir side in the stdlib's embedded curve operations.
It will allow Aztec protocol circuit to use directly the opcode when the 'safety' is already known (and implied by previous operations).

EDIT: this unsafe EC ADD now handles points at infinity.
If they are constant, it works as expected
If not, it simply returns the infinity point.
This ensures that there is no performance impact (the checks are constant-time), but still support in a way the infinite points, i.e it does not crash and returns a valid point, although it is an incorrect result, when the infinite status cannot be resolved at constant time.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 26, 2024

Changes to circuit sizes

Generated at commit: 393fe779227f5114dd6044e3b1c5dda768b6f719, compared to commit: 1801f5b49fb3b153817a1596c6fd568f1c762fe5

🧾 Summary (100% most significant diffs)

Program ACIR opcodes (+/-) % Circuit size (+/-) %
private_kernel_reset_4_4_4_4_4_4_4_4_1 0 ➖ 0.00% -4 ✅ -0.01%
private_kernel_reset 0 ➖ 0.00% -64 ✅ -0.01%

Full diff report 👇
Program ACIR opcodes (+/-) % Circuit size (+/-) %
private_kernel_reset_4_4_4_4_4_4_4_4_1 34,897 (0) 0.00% 74,475 (-4) -0.01%
private_kernel_reset 91,933 (0) 0.00% 467,666 (-64) -0.01%

@guipublic guipublic changed the title chore: makes ec addition opcode unsafe **DO NOT MERGE** chore: makes ec addition opcode unsafe Oct 3, 2024
@guipublic guipublic requested a review from TomAFrench October 4, 2024 16:29
Copy link
Member

@TomAFrench TomAFrench left a comment

Choose a reason for hiding this comment

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

Happy to merge once CI is passing.

@guipublic guipublic requested a review from Rumata888 June 27, 2025 14:17
namespace acir_format {

// This functions assumes that:
// 1. the points are on the curve
Copy link
Contributor

Choose a reason for hiding this comment

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

What if the points are not on the curve? Where do we check that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is the responsibility of the user, that's the point of this ec-add-unsafe; to avoid as many check as possible.
The checks done by the function have no cost because they are constant-time.
One use case is when you are adding points coming from a previous addition, so you know that the points are on the curve (if you checked the first ones) and there is no need to check this.

Note that the ec-add in Noir performs most of the checks

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, makes sense

Copy link
Contributor

Choose a reason for hiding this comment

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

I just noticed that you don't detect the case where x_match by not y_match

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a check for this case:

  • if x match and y does not match, at compile time, then return 0
  • else, if x match, then return error
  • else: we assume x-coordinates are distincts and will do the unconditional add

input.input2_y,
input.input2_infinite,
has_valid_witness_assignments,
false,
Copy link
Contributor

Choose a reason for hiding this comment

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

These two ifs are equivalent, but you create the point with 'use_g1' being set to different values

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Any valid point would do here, it is just for consistency, as I use G1 for point1 and G2=2G1 for point2 elsewhere in the file.
But I don't mind changing it.

input.input1_y,
input.input1_infinite,
has_valid_witness_assignments,
true,
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here. Why is 1 true and 2 false?

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 it matters, because the two points must be distinct (else it would be a doubling).

// Runtime check that the inputs have not the same x coordinate, as assumed by the function.
ASSERT(input1_point.x.get_value() != input2_point.x.get_value());
}
result = input1_point.unconditional_add(input2_point);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are you using unconditional add? Are you checking that the inputs are different? If the values are the same, this could allow you to create any point as a result

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the whole point of the PR, the checks have been moved on the Noir side so that we can avoid them in some cases. See this comment in the PR description:
"It will allow Aztec protocol circuit to use directly the opcode when the 'safety' is already known (and implied by previous operations)."

Copy link
Contributor

Choose a reason for hiding this comment

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

You shouldn't remove the difference!=zero check. It is always necessary. There is no case that it is not necessary, when you are performing non-doubling additions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I disagree, else the function unconditional_add() would not exist. See the comments of the function:

  •    Only use this method if you know the x-coordinates of the operands cannot collide and none of the operands is a point at infinity
    

For instance it is used for batch mul:

  •     If `unconditional_add = true`, we use `::unconditional_add` instead of `::checked_unconditional_add`. Use with caution! Only should be `true` if we're doing an ULTRA fixed-base MSM so we know the points cannot collide with the offset generators.
    

Copy link
Contributor

@Rumata888 Rumata888 Jul 4, 2025

Choose a reason for hiding this comment

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

The function unconditional add is only used in 2 cases:

  1. We know that the values provided are from a CRS and getting a combination of those that would be equal is tantamount to solving the discrete log
  2. We use it in our own batch mul, when we specifically want to save 1 gate by batching non-zero checks.

However, these are extremely rare situations. It is extremely dangerous to leave the operation like this by default, because then it does not ensure the correctness of additions at all in 99% of cases. If you really want to allow the developer to disable this particular check, then there should be a setting that allows this (with loud disclaimers). It shouldn't be by default

Copy link
Contributor Author

@guipublic guipublic Jul 4, 2025

Choose a reason for hiding this comment

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

I will add a setting then.
In fact no, I will add the check because I don't want to have to change ACIR format.
I'll add the setting later, if this PR manage to go through!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

size_t generate_ec_add_constraint(EcAdd& ec_add_constraint, WitnessVector& witness_values)
{
using cycle_group_ct = bb::stdlib::cycle_group<Builder>;
witness_values.push_back(0);
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please add tests for all the edgecases? Right now we are only testing 1 case out of all the possibilities

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not sure because the edge cases are not expected to work.

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean the various combinations of constant and witness elements, cases where it turns into a double, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@guipublic guipublic requested a review from Rumata888 July 11, 2025 12:49
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.

5 participants