Skip to content

Commit 871ddc8

Browse files
authored
chore: ec addition for non-zero points (#5858)
# Description ## Problem\* This PR performs 'safe' ec addition in Noir, assuming the EC ADD opcode is unsafe, i.e it does not handle point at infinity. an ec addition for the embedded curve dedicated to non-zero inputs. ## Summary\* embedded_curve_add in stdlib now handles corner cases for ec addition Add 2 methods: embedded_curve_add_not_nul which assumes the inputs are not null embedded_curve_add_unsafe which assumes inputs are not null and distinct (or identical) Enable the constant inputs for ec_add. ## Additional Context This PR should cause an overhead, because corner cases are not checked in Noir and in BB. However, we want to change BB so that EC ADD is not safe anymore which will improve the performance, so we need to add the safe version in Noir first. I am not sure whether we want to document this or not? ## Documentation\* Check one: - [X] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [X] I have tested the changes locally. - [X] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings.
1 parent 86d41b3 commit 871ddc8

3 files changed

Lines changed: 64 additions & 12 deletions

File tree

acvm-repo/acir/src/circuit/black_box_functions.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,15 @@ pub enum BlackBoxFunc {
164164
/// ultimately fail.
165165
RecursiveAggregation,
166166

167-
/// Addition over the embedded curve on which the witness is defined.
167+
/// Addition over the embedded curve on which the witness is defined
168+
/// The opcode makes the following assumptions but does not enforce them because
169+
/// it is more efficient to do it only when required. For instance, adding two
170+
/// points that are on the curve it guarantee to give a point on the curve.
171+
///
172+
/// It assumes that the points are on the curve.
173+
/// If the inputs are the same witnesses index, it will perform a doubling,
174+
/// If not, it assumes that the points' x-coordinates are not equal.
175+
/// It also assumes neither point is the infinity point.
168176
EmbeddedCurveAdd,
169177

170178
/// BigInt addition

compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,7 @@ impl<F: AcirField> AcirContext<F> {
14711471
| BlackBoxFunc::AND
14721472
| BlackBoxFunc::XOR
14731473
| BlackBoxFunc::AES128Encrypt
1474+
| BlackBoxFunc::EmbeddedCurveAdd
14741475
);
14751476
// Convert `AcirVar` to `FunctionInput`
14761477
let inputs = self.prepare_inputs_for_black_box_func_call(inputs, allow_constant_inputs)?;

noir_stdlib/src/embedded_curve_ops.nr

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -125,20 +125,63 @@ pub fn fixed_base_scalar_mul(scalar: EmbeddedCurveScalar) -> EmbeddedCurvePoint
125125
multi_scalar_mul([g1], [scalar])
126126
}
127127

128-
// This is a hack as returning an `EmbeddedCurvePoint` from a foreign function in brillig returns a [BrilligVariable::SingleAddr; 2] rather than BrilligVariable::BrilligArray
128+
/// This function only assumes that the points are on the curve
129+
/// It handles corner cases around the infinity point causing some overhead compared to embedded_curve_add_not_nul and embedded_curve_add_unsafe
130+
// This is a hack because returning an `EmbeddedCurvePoint` from a foreign function in brillig returns a [BrilligVariable::SingleAddr; 2] rather than BrilligVariable::BrilligArray
129131
// as is defined in the brillig bytecode format. This is a workaround which allows us to fix this without modifying the serialization format.
130132
// docs:start:embedded_curve_add
131-
fn embedded_curve_add(
132-
point1: EmbeddedCurvePoint,
133-
point2: EmbeddedCurvePoint
134-
) -> EmbeddedCurvePoint
135-
// docs:end:embedded_curve_add
136-
{
137-
let point_array = embedded_curve_add_array_return(point1, point2);
138-
let x = point_array[0];
139-
let y = point_array[1];
140-
EmbeddedCurvePoint { x, y, is_infinite: point_array[2] == 1 }
133+
pub fn embedded_curve_add(point1: EmbeddedCurvePoint, point2: EmbeddedCurvePoint) -> EmbeddedCurvePoint {
134+
// docs:end:embedded_curve_add
135+
let x_coordinates_match = point1.x == point2.x;
136+
let y_coordinates_match = point1.y == point2.y;
137+
let double_predicate = (x_coordinates_match & y_coordinates_match);
138+
let infinity_predicate = (x_coordinates_match & !y_coordinates_match);
139+
let point1_1 = EmbeddedCurvePoint { x: point1.x + (x_coordinates_match as Field), y: point1.y, is_infinite: x_coordinates_match };
140+
// point1_1 is guaranteed to have a different abscissa than point2
141+
let mut result = embedded_curve_add_unsafe(point1_1, point2);
142+
result.is_infinite = x_coordinates_match;
143+
144+
// dbl if x_match, y_match
145+
let double = embedded_curve_add_unsafe(point1, point1);
146+
result = if double_predicate { double } else { result };
147+
148+
// infinity if x_match, !y_match
149+
if point1.is_infinite {
150+
result= point2;
151+
}
152+
if point2.is_infinite {
153+
result = point1;
154+
}
155+
let mut result_is_infinity = infinity_predicate & (!point1.is_infinite & !point2.is_infinite);
156+
result.is_infinite = result_is_infinity | (point1.is_infinite & point2.is_infinite);
157+
result
141158
}
142159

143160
#[foreign(embedded_curve_add)]
144161
fn embedded_curve_add_array_return(_point1: EmbeddedCurvePoint, _point2: EmbeddedCurvePoint) -> [Field; 3] {}
162+
163+
/// This function assumes that:
164+
/// The points are on the curve, and
165+
/// The points don't share an x-coordinate, and
166+
/// Neither point is the infinity point.
167+
/// If it is used with correct input, the function ensures the correct non-zero result is returned.
168+
/// Except for points on the curve, the other assumptions are checked by the function. It will cause assertion failure if they are not respected.
169+
pub fn embedded_curve_add_not_nul(point1: EmbeddedCurvePoint, point2: EmbeddedCurvePoint) -> EmbeddedCurvePoint {
170+
assert(point1.x != point2.x);
171+
assert(!point1.is_infinite);
172+
assert(!point2.is_infinite);
173+
embedded_curve_add_unsafe(point1, point2)
174+
}
175+
176+
/// Unsafe ec addition
177+
/// If the inputs are the same, it will perform a doubling, but only if point1 and point2 are the same variable.
178+
/// If they have the same value but are different variables, the result will be incorrect because in this case
179+
/// it assumes (but does not check) that the points' x-coordinates are not equal.
180+
/// It also assumes neither point is the infinity point.
181+
pub fn embedded_curve_add_unsafe(point1: EmbeddedCurvePoint, point2: EmbeddedCurvePoint) -> EmbeddedCurvePoint {
182+
let point_array = embedded_curve_add_array_return(point1, point2);
183+
let x = point_array[0];
184+
let y = point_array[1];
185+
186+
EmbeddedCurvePoint { x, y, is_infinite: false }
187+
}

0 commit comments

Comments
 (0)