-
Notifications
You must be signed in to change notification settings - Fork 613
chore: makes ec addition opcode unsafe #8814
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 30 commits
4725f75
2c35a92
6c14ad3
bbff2f1
5b5fc0a
32de43a
2f3ea01
da1604f
3c60dcb
6baa710
d81f826
98c7ebe
bab195f
bb57173
249ab55
8e08f66
a0576c1
2cdf9ad
6aa64f2
01a50ac
e3e8220
06ef800
6519918
3a05675
56861f3
525a63f
304b758
8b4809c
403c972
99ebdf5
22b2e93
877231c
b8b5f75
c78a883
c5156dc
cdf21c2
298cd7d
e47b58b
f899533
d3a2daf
5feed4b
85e2b90
0cf8994
f0cf778
b64714b
6f4a199
2c133a8
ef05a7d
f4ae114
b521f62
c872ab2
ef821e3
f8555e8
c3bdb9b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| // ===================== | ||
|
|
||
| #include "ec_operations.hpp" | ||
| #include "barretenberg/dsl/acir_format/witness_constant.hpp" | ||
| #include "barretenberg/ecc/curves/bn254/fr.hpp" | ||
| #include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp" | ||
| #include "barretenberg/ecc/groups/affine_element.hpp" | ||
|
|
@@ -13,19 +14,126 @@ | |
|
|
||
| namespace acir_format { | ||
|
|
||
| // This functions assumes that: | ||
| // 1. the points are on the curve | ||
| // 2a. the points have not the same abssissa, OR | ||
| // 2b. the points are identical (same witness index or same value) | ||
| // If the points at infinity are known and constant, the function will work properly | ||
| // If not, and if the points are not identical, it is an error. | ||
| template <typename Builder> | ||
| void create_ec_add_constraint(Builder& builder, const EcAdd& input, bool has_valid_witness_assignments) | ||
| { | ||
| // Input to cycle_group points | ||
| // Cycle_group points are used by BB to lay out constraints on Grumpkin curve points | ||
| using cycle_group_ct = bb::stdlib::cycle_group<Builder>; | ||
|
|
||
| auto input1_point = to_grumpkin_point( | ||
| input.input1_x, input.input1_y, input.input1_infinite, has_valid_witness_assignments, builder); | ||
| auto input2_point = to_grumpkin_point( | ||
| input.input2_x, input.input2_y, input.input2_infinite, has_valid_witness_assignments, builder); | ||
| // Check if operands are the 'same' (same witness or same constant value). | ||
| bool x_match = false; | ||
| if (!input.input1_x.is_constant && !input.input2_x.is_constant) { | ||
| x_match = (input.input1_x.index == input.input2_x.index); | ||
| } else { | ||
| if (input.input1_x.is_constant && input.input2_x.is_constant) { | ||
| x_match = (input.input1_x.value == input.input2_x.value); | ||
| } | ||
| } | ||
| bool y_match = false; | ||
| if (!input.input1_y.is_constant && !input.input2_y.is_constant) { | ||
| y_match = (input.input1_y.index == input.input2_y.index); | ||
| } else { | ||
| if (input.input1_y.is_constant && input.input2_y.is_constant) { | ||
| y_match = (input.input1_y.value == input.input2_y.value); | ||
| } | ||
| } | ||
|
|
||
| cycle_group_ct result; | ||
| // If operands are the same, we double. | ||
| // Note that the doubling function handles the infinity case | ||
| if (x_match && y_match) { | ||
| cycle_group_ct input1_point; | ||
|
|
||
| // When there are no valid witness assignements, we need to define dummy values that will | ||
| // satisfy the doubling constraints, which we can do easily when the inputs are witness. | ||
| // If the is_infinity is a witness, we can simply set it to 1 | ||
| // Or, if the coordinates are witness, we can simply set them to a valid point on the curve (G1) | ||
| if (!input.input1_infinite.is_constant || (!input.input1_x.is_constant && !input.input1_y.is_constant)) { | ||
| input1_point = to_grumpkin_point( | ||
| input.input1_x, input.input1_y, input.input1_infinite, has_valid_witness_assignments, true, builder); | ||
| } else { | ||
| // If not, the coordinates are mixed constant/witness, and we generate witness so that the point is using | ||
| // only witnesses. | ||
| input1_point = to_witness_grumpkin_point( | ||
| input.input1_x, input.input1_y, input.input1_infinite, has_valid_witness_assignments, true, builder); | ||
| } | ||
| result = input1_point.dbl(); | ||
| } else { | ||
| // Regular addition | ||
| // if one point is (constant) zero, we simply return the other point. | ||
| if (input.input2_infinite.is_constant && input.input1_infinite.is_constant) { | ||
| if (get_value(input.input1_infinite, builder) == 1) { | ||
| // input1 is infinity, so we can just return input2 | ||
| result = to_grumpkin_point(input.input2_x, | ||
| input.input2_y, | ||
| input.input2_infinite, | ||
| has_valid_witness_assignments, | ||
| false, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| builder); | ||
|
|
||
| } else if (get_value(input.input2_infinite, builder) == 1) { | ||
| // input2 is infinity, so we can just return input1 | ||
| result = to_grumpkin_point(input.input1_x, | ||
| input.input1_y, | ||
| input.input1_infinite, | ||
| has_valid_witness_assignments, | ||
| true, | ||
| builder); | ||
| } else { | ||
|
|
||
| cycle_group_ct input1_point; | ||
| cycle_group_ct input2_point; | ||
| // all or nothing: the inputs must be all constant or all witness. Cf #1108 for more details. | ||
| if (!input.input1_x.is_constant || !input.input1_y.is_constant || !input.input1_infinite.is_constant || | ||
| !input.input2_x.is_constant || !input.input2_y.is_constant || !input.input2_infinite.is_constant) { | ||
| // One of the input is a witness, so we ensure that all inputs are witness, by creating witness for | ||
| // constant values. | ||
| input1_point = to_witness_grumpkin_point(input.input1_x, | ||
| input.input1_y, | ||
| input.input1_infinite, | ||
| has_valid_witness_assignments, | ||
| true, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. Why is 1 true and 2 false?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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). |
||
| builder); | ||
| input2_point = to_witness_grumpkin_point(input.input2_x, | ||
| input.input2_y, | ||
| input.input2_infinite, | ||
| has_valid_witness_assignments, | ||
| false, | ||
| builder); | ||
|
|
||
| } else { | ||
| input1_point = to_grumpkin_point(input.input1_x, | ||
| input.input1_y, | ||
| input.input1_infinite, | ||
| has_valid_witness_assignments, | ||
| true, | ||
| builder); | ||
| input2_point = to_grumpkin_point(input.input2_x, | ||
| input.input2_y, | ||
| input.input2_infinite, | ||
| has_valid_witness_assignments, | ||
| false, | ||
| builder); | ||
| } | ||
| // both points are not infinity, so we can use unconditional_add | ||
| if (has_valid_witness_assignments) { | ||
| // 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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I disagree, else the function
For instance it is used for batch mul:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function unconditional add is only used in 2 cases:
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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| } | ||
| } else { | ||
| // Some points could be at infinity, which is not supported by the function | ||
| ASSERT(false, "Unsupported EC ADDITION UNSAFE; is_infinite status must be known at compile time"); | ||
| } | ||
| } | ||
|
|
||
| // Addition | ||
| cycle_group_ct result = input1_point + input2_point; | ||
| cycle_group_ct standard_result = result.get_standard_form(); | ||
| auto x_normalized = standard_result.x.normalize(); | ||
| auto y_normalized = standard_result.y.normalize(); | ||
|
|
@@ -41,6 +149,7 @@ void create_ec_add_constraint(Builder& builder, const EcAdd& input, bool has_val | |
| } else { | ||
| builder.assert_equal(y_normalized.witness_index, input.result_y); | ||
| } | ||
|
|
||
| if (infinite.is_constant()) { | ||
| builder.fix_witness(input.result_infinite, infinite.get_value()); | ||
| } else { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,19 +20,18 @@ class EcOperations : public ::testing::Test { | |
|
|
||
| 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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| auto g1 = grumpkin::g1::affine_one; | ||
| cycle_group_ct input_point(g1); | ||
| // Doubling | ||
| cycle_group_ct result = input_point.dbl(); | ||
| auto g2 = g1 + g1; | ||
| auto affine_result = g1 + g2; | ||
|
|
||
| // add: x,y,x2,y2 | ||
| witness_values.push_back(g1.x); | ||
| witness_values.push_back(g1.y); | ||
| witness_values.push_back(g1.x); | ||
| witness_values.push_back(g1.y); | ||
| witness_values.push_back(result.x.get_value()); | ||
| witness_values.push_back(result.y.get_value()); | ||
| witness_values.push_back(g2.x); | ||
| witness_values.push_back(g2.y); | ||
| witness_values.push_back(affine_result.x); | ||
| witness_values.push_back(affine_result.y); | ||
| witness_values.push_back(fr(0)); | ||
| witness_values.push_back(fr(0)); | ||
| ec_add_constraint = EcAdd{ | ||
|
|
||
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, makes sense
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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: