openvm_ecc_guest/weierstrass.rs
1use alloc::vec::Vec;
2use core::ops::Mul;
3
4use openvm_algebra_guest::{Field, IntMod};
5
6use super::group::Group;
7
8/// Short Weierstrass curve affine point.
9pub trait WeierstrassPoint: Clone + Sized {
10 /// The `a` coefficient in the Weierstrass curve equation `y^2 = x^3 + a x + b`.
11 const CURVE_A: Self::Coordinate;
12 /// The `b` coefficient in the Weierstrass curve equation `y^2 = x^3 + a x + b`.
13 const CURVE_B: Self::Coordinate;
14 const IDENTITY: Self;
15
16 type Coordinate: Field;
17
18 /// The concatenated `x, y` coordinates of the affine point, where
19 /// coordinates are in little endian.
20 ///
21 /// **Warning**: The memory layout of `Self` is expected to pack
22 /// `x` and `y` contiguously with no unallocated space in between.
23 fn as_le_bytes(&self) -> &[u8];
24
25 /// Raw constructor without asserting point is on the curve.
26 ///
27 /// # Safety
28 /// - Caller must guarantee `(x, y)` is a valid point on the curve and in any required subgroup.
29 /// - Identity point must be represented by `(0, 0)`.
30 unsafe fn from_xy_unchecked(x: Self::Coordinate, y: Self::Coordinate) -> Self;
31 fn into_coords(self) -> (Self::Coordinate, Self::Coordinate);
32 fn x(&self) -> &Self::Coordinate;
33 fn y(&self) -> &Self::Coordinate;
34 fn x_mut(&mut self) -> &mut Self::Coordinate;
35 fn y_mut(&mut self) -> &mut Self::Coordinate;
36
37 /// Calls any setup required for this curve. The implementation should internally use `OnceBool`
38 /// to ensure that setup is only called once.
39 fn set_up_once();
40
41 /// Add implementation that handles identity and whether points are equal or not.
42 ///
43 /// # Safety
44 /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls
45 /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has
46 /// been called already.
47 fn add_assign_impl<const CHECK_SETUP: bool>(&mut self, p2: &Self);
48
49 /// Double implementation that handles identity.
50 ///
51 /// # Safety
52 /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls
53 /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has
54 /// been called already.
55 fn double_assign_impl<const CHECK_SETUP: bool>(&mut self);
56
57 /// # Safety
58 /// - Assumes self != +- p2 and self != identity and p2 != identity.
59 /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls
60 /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has
61 /// been called already.
62 unsafe fn add_ne_nonidentity<const CHECK_SETUP: bool>(&self, p2: &Self) -> Self;
63 /// # Safety
64 /// - Assumes self != +- p2 and self != identity and p2 != identity.
65 /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls
66 /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has
67 /// been called already.
68 unsafe fn add_ne_assign_nonidentity<const CHECK_SETUP: bool>(&mut self, p2: &Self);
69 /// # Safety
70 /// - Assumes self != +- p2 and self != identity and p2 != identity.
71 /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls
72 /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has
73 /// been called already.
74 unsafe fn sub_ne_nonidentity<const CHECK_SETUP: bool>(&self, p2: &Self) -> Self;
75 /// # Safety
76 /// - Assumes self != +- p2 and self != identity and p2 != identity.
77 /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls
78 /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has
79 /// been called already.
80 unsafe fn sub_ne_assign_nonidentity<const CHECK_SETUP: bool>(&mut self, p2: &Self);
81 /// # Safety
82 /// - Assumes self != identity and 2 * self != identity.
83 /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls
84 /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has
85 /// been called already.
86 unsafe fn double_nonidentity<const CHECK_SETUP: bool>(&self) -> Self;
87 /// # Safety
88 /// - Assumes self != identity and 2 * self != identity.
89 /// - If `CHECK_SETUP` is true, checks if setup has been called for this curve and if not, calls
90 /// `Self::set_up_once()`. Only set `CHECK_SETUP` to `false` if you are sure that setup has
91 /// been called already.
92 unsafe fn double_assign_nonidentity<const CHECK_SETUP: bool>(&mut self);
93
94 /// Constructs a point on the curve, including the identity point.
95 ///
96 /// # Safety
97 /// - This constructor does not perform any subgroup checks and only guarantees that the point
98 /// is on the curve.
99 #[inline(always)]
100 unsafe fn from_xy(x: Self::Coordinate, y: Self::Coordinate) -> Option<Self>
101 where
102 for<'a> &'a Self::Coordinate: Mul<&'a Self::Coordinate, Output = Self::Coordinate>,
103 {
104 if x == Self::Coordinate::ZERO && y == Self::Coordinate::ZERO {
105 Some(Self::IDENTITY)
106 } else {
107 Self::from_xy_nonidentity(x, y)
108 }
109 }
110
111 /// Constructs a point on the curve, excluding the identity point.
112 ///
113 /// # Safety
114 /// - This constructor does not perform any subgroup checks and only guarantees that the point
115 /// is a non-identity point on the curve.
116 #[inline(always)]
117 unsafe fn from_xy_nonidentity(x: Self::Coordinate, y: Self::Coordinate) -> Option<Self>
118 where
119 for<'a> &'a Self::Coordinate: Mul<&'a Self::Coordinate, Output = Self::Coordinate>,
120 {
121 let lhs = &y * &y;
122 let rhs = &x * &x * &x + &Self::CURVE_A * &x + &Self::CURVE_B;
123 if lhs != rhs {
124 return None;
125 }
126 Some(Self::from_xy_unchecked(x, y))
127 }
128}
129
130pub trait FromCompressed<Coordinate> {
131 /// Given `x`-coordinate,
132 ///
133 /// Decompresses a point from its x-coordinate and a recovery identifier which indicates
134 /// the parity of the y-coordinate. Given the x-coordinate, this function attempts to find the
135 /// corresponding y-coordinate that satisfies the elliptic curve equation. If successful, it
136 /// returns the point as an instance of Self. If the point cannot be decompressed, it returns
137 /// None.
138 ///
139 /// # Safety
140 /// This function does not perform subgroup checks and only checks whether the point is on the
141 /// curve.
142 fn decompress(x: Coordinate, rec_id: &u8) -> Option<Self>
143 where
144 Self: core::marker::Sized;
145}
146
147/// A trait for elliptic curves that bridges the openvm types and external types with
148/// CurveArithmetic etc. Implement this for external curves with corresponding openvm point and
149/// scalar types.
150pub trait IntrinsicCurve {
151 type Scalar: Clone;
152 type Point: Clone;
153
154 /// Multi-scalar multiplication.
155 /// The implementation may be specialized to use properties of the curve
156 /// (e.g., if the curve order is prime).
157 fn msm(coeffs: &[Self::Scalar], bases: &[Self::Point]) -> Self::Point;
158}
159
160// MSM using preprocessed table (windowed method)
161// Reference: modified from https://github.com/arkworks-rs/algebra/blob/master/ec/src/scalar_mul/mod.rs
162//
163// We specialize to Weierstrass curves and further make optimizations for when the curve order is
164// prime.
165
166/// Cached precomputations of scalar multiples of several base points.
167/// - `window_bits` is the window size used for the precomputation
168/// - `max_scalar_bits` is the maximum size of the scalars that will be multiplied
169/// - `table` is the precomputed table
170pub struct CachedMulTable<'a, C: IntrinsicCurve> {
171 /// Window bits. Must be > 0.
172 /// For alignment, we currently require this to divide 8 (bits in a byte).
173 pub window_bits: usize,
174 pub bases: &'a [C::Point],
175 /// `table[i][j] = (j + 2) * bases[i]` for `j + 2 < 2 ** window_bits`
176 table: Vec<Vec<C::Point>>,
177 /// Needed to return reference to the identity point.
178 identity: C::Point,
179}
180
181impl<'a, C: IntrinsicCurve> CachedMulTable<'a, C>
182where
183 C::Point: WeierstrassPoint + Group,
184 C::Scalar: IntMod,
185{
186 /// Constructor when each element of `bases` has prime torsion or is identity.
187 ///
188 /// Assumes that `window_bits` is less than (number of bits - 1) of the order of
189 /// subgroup generated by each non-identity `base`.
190 #[inline]
191 pub fn new_with_prime_order(bases: &'a [C::Point], window_bits: usize) -> Self {
192 C::Point::set_up_once();
193 assert!(window_bits > 0);
194 let window_size = 1 << window_bits;
195 let table = bases
196 .iter()
197 .map(|base| {
198 if base.is_identity() {
199 vec![<C::Point as Group>::IDENTITY; window_size - 2]
200 } else {
201 let mut multiples = Vec::with_capacity(window_size - 2);
202 for _ in 0..window_size - 2 {
203 // Because the order of `base` is prime, we are guaranteed that
204 // j * base != identity,
205 // j * base != +- base for j > 1,
206 // j * base + base != identity
207 let multiple = multiples
208 .last()
209 .map(|last| unsafe {
210 WeierstrassPoint::add_ne_nonidentity::<false>(last, base)
211 })
212 .unwrap_or_else(|| unsafe { base.double_nonidentity::<false>() });
213 multiples.push(multiple);
214 }
215 multiples
216 }
217 })
218 .collect();
219
220 Self {
221 window_bits,
222 bases,
223 table,
224 identity: <C::Point as Group>::IDENTITY,
225 }
226 }
227
228 #[inline(always)]
229 fn get_multiple(&self, base_idx: usize, scalar: usize) -> &C::Point {
230 if scalar == 0 {
231 &self.identity
232 } else if scalar == 1 {
233 unsafe { self.bases.get_unchecked(base_idx) }
234 } else {
235 unsafe { self.table.get_unchecked(base_idx).get_unchecked(scalar - 2) }
236 }
237 }
238
239 /// Computes `sum scalars[i] * bases[i]`.
240 ///
241 /// For implementation simplicity, currently only implemented when
242 /// `window_bits` divides 8 (number of bits in a byte).
243 #[inline]
244 pub fn windowed_mul(&self, scalars: &[C::Scalar]) -> C::Point {
245 C::Point::set_up_once();
246 assert_eq!(8 % self.window_bits, 0);
247 assert_eq!(scalars.len(), self.bases.len());
248 let windows_per_byte = 8 / self.window_bits;
249
250 let num_windows = C::Scalar::NUM_LIMBS * windows_per_byte;
251 let mask = (1u8 << self.window_bits) - 1;
252
253 // The current byte index (little endian) at the current step of the
254 // windowed method, across all scalars.
255 let mut limb_idx = C::Scalar::NUM_LIMBS;
256 // The current bit (little endian) within the current byte of the windowed
257 // method. The window will look at bits `bit_idx..bit_idx + window_bits`.
258 // bit_idx will always be in range [0, 8)
259 let mut bit_idx = 0;
260
261 let mut res = <C::Point as Group>::IDENTITY;
262 for outer in 0..num_windows {
263 if bit_idx == 0 {
264 limb_idx -= 1;
265 bit_idx = 8 - self.window_bits;
266 } else {
267 bit_idx -= self.window_bits;
268 }
269
270 if outer != 0 {
271 for _ in 0..self.window_bits {
272 // Note: this handles identity
273 // setup has been called above
274 res.double_assign_impl::<false>();
275 }
276 }
277 for (base_idx, scalar) in scalars.iter().enumerate() {
278 let scalar = (scalar.as_le_bytes()[limb_idx] >> bit_idx) & mask;
279 let summand = self.get_multiple(base_idx, scalar as usize);
280 // handles identity
281 // setup has been called above
282 res.add_assign_impl::<false>(summand);
283 }
284 }
285 res
286 }
287}
288
289/// Macro to generate a newtype wrapper for [AffinePoint](crate::AffinePoint)
290/// that implements elliptic curve operations by using the underlying field operations according to
291/// the [formulas](https://www.hyperelliptic.org/EFD/g1p/auto-shortw.html) for short Weierstrass curves.
292///
293/// The following imports are required:
294/// ```rust
295/// use core::ops::AddAssign;
296///
297/// use openvm_algebra_guest::{DivUnsafe, Field};
298/// use openvm_ecc_guest::{weierstrass::WeierstrassPoint, AffinePoint, Group};
299/// ```
300#[macro_export]
301macro_rules! impl_sw_affine {
302 // Assumes `a = 0` in curve equation. `$three` should be a constant expression for `3` of type
303 // `$field`.
304 ($struct_name:ident, $field:ty, $three:expr, $b:expr) => {
305 /// A newtype wrapper for [AffinePoint] that implements elliptic curve operations
306 /// by using the underlying field operations according to the [formulas](https://www.hyperelliptic.org/EFD/g1p/auto-shortw.html) for short Weierstrass curves.
307 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
308 #[repr(transparent)]
309 pub struct $struct_name(AffinePoint<$field>);
310
311 impl $struct_name {
312 pub const fn new(x: $field, y: $field) -> Self {
313 Self(AffinePoint::new(x, y))
314 }
315 }
316
317 impl WeierstrassPoint for $struct_name {
318 const CURVE_A: $field = <$field>::ZERO;
319 const CURVE_B: $field = $b;
320 const IDENTITY: Self = Self(AffinePoint::new(<$field>::ZERO, <$field>::ZERO));
321
322 type Coordinate = $field;
323
324 /// SAFETY: assumes that [$field] has internal representation in little-endian.
325 fn as_le_bytes(&self) -> &[u8] {
326 unsafe {
327 &*core::ptr::slice_from_raw_parts(
328 self as *const Self as *const u8,
329 core::mem::size_of::<Self>(),
330 )
331 }
332 }
333 unsafe fn from_xy_unchecked(x: Self::Coordinate, y: Self::Coordinate) -> Self {
334 Self(AffinePoint::new(x, y))
335 }
336 fn into_coords(self) -> (Self::Coordinate, Self::Coordinate) {
337 (self.0.x, self.0.y)
338 }
339 fn x(&self) -> &Self::Coordinate {
340 &self.0.x
341 }
342 fn y(&self) -> &Self::Coordinate {
343 &self.0.y
344 }
345 fn x_mut(&mut self) -> &mut Self::Coordinate {
346 &mut self.0.x
347 }
348 fn y_mut(&mut self) -> &mut Self::Coordinate {
349 &mut self.0.y
350 }
351
352 fn set_up_once() {
353 // There are no special opcodes for curve operations in this case, so no additional
354 // setup is required.
355 //
356 // Since the `Self::Coordinate` is abstract, any set up required by the field is not
357 // handled here.
358 }
359
360 fn add_assign_impl<const CHECK_SETUP: bool>(&mut self, p2: &Self) {
361 if self == &<Self as WeierstrassPoint>::IDENTITY {
362 *self = p2.clone();
363 } else if p2 == &<Self as WeierstrassPoint>::IDENTITY {
364 // do nothing
365 } else if self.x() == p2.x() {
366 if self.y() + p2.y() == <Self::Coordinate as openvm_algebra_guest::Field>::ZERO
367 {
368 *self = <Self as WeierstrassPoint>::IDENTITY;
369 } else {
370 unsafe {
371 self.double_assign_nonidentity::<CHECK_SETUP>();
372 }
373 }
374 } else {
375 unsafe {
376 self.add_ne_assign_nonidentity::<CHECK_SETUP>(p2);
377 }
378 }
379 }
380
381 #[inline(always)]
382 fn double_assign_impl<const CHECK_SETUP: bool>(&mut self) {
383 if self != &<Self as WeierstrassPoint>::IDENTITY {
384 unsafe {
385 self.double_assign_nonidentity::<CHECK_SETUP>();
386 }
387 }
388 }
389
390 unsafe fn double_nonidentity<const CHECK_SETUP: bool>(&self) -> Self {
391 use openvm_algebra_guest::DivUnsafe;
392 // lambda = (3*x1^2+a)/(2*y1)
393 // assume a = 0
394 let lambda = (&THREE * self.x() * self.x()).div_unsafe(self.y() + self.y());
395 // x3 = lambda^2-x1-x1
396 let x3 = &lambda * &lambda - self.x() - self.x();
397 // y3 = lambda * (x1-x3) - y1
398 let y3 = lambda * (self.x() - &x3) - self.y();
399 Self(AffinePoint::new(x3, y3))
400 }
401
402 #[inline(always)]
403 unsafe fn double_assign_nonidentity<const CHECK_SETUP: bool>(&mut self) {
404 *self = self.double_nonidentity::<CHECK_SETUP>();
405 }
406
407 unsafe fn add_ne_nonidentity<const CHECK_SETUP: bool>(&self, p2: &Self) -> Self {
408 use openvm_algebra_guest::DivUnsafe;
409 // lambda = (y2-y1)/(x2-x1)
410 // x3 = lambda^2-x1-x2
411 // y3 = lambda*(x1-x3)-y1
412 let lambda = (p2.y() - self.y()).div_unsafe(p2.x() - self.x());
413 let x3 = &lambda * &lambda - self.x() - p2.x();
414 let y3 = lambda * (self.x() - &x3) - self.y();
415 Self(AffinePoint::new(x3, y3))
416 }
417
418 #[inline(always)]
419 unsafe fn add_ne_assign_nonidentity<const CHECK_SETUP: bool>(&mut self, p2: &Self) {
420 *self = self.add_ne_nonidentity::<CHECK_SETUP>(p2);
421 }
422
423 unsafe fn sub_ne_nonidentity<const CHECK_SETUP: bool>(&self, p2: &Self) -> Self {
424 use openvm_algebra_guest::DivUnsafe;
425 // lambda = (y2+y1)/(x1-x2)
426 // x3 = lambda^2-x1-x2
427 // y3 = lambda*(x1-x3)-y1
428 let lambda = (p2.y() + self.y()).div_unsafe(self.x() - p2.x());
429 let x3 = &lambda * &lambda - self.x() - p2.x();
430 let y3 = lambda * (self.x() - &x3) - self.y();
431 Self(AffinePoint::new(x3, y3))
432 }
433
434 #[inline(always)]
435 unsafe fn sub_ne_assign_nonidentity<const CHECK_SETUP: bool>(&mut self, p2: &Self) {
436 *self = self.sub_ne_nonidentity::<CHECK_SETUP>(p2);
437 }
438 }
439
440 impl core::ops::Neg for $struct_name {
441 type Output = Self;
442
443 #[inline(always)]
444 fn neg(mut self) -> Self::Output {
445 self.0.y.neg_assign();
446 self
447 }
448 }
449
450 impl core::ops::Neg for &$struct_name {
451 type Output = $struct_name;
452
453 #[inline(always)]
454 fn neg(self) -> Self::Output {
455 self.clone().neg()
456 }
457 }
458
459 impl From<$struct_name> for AffinePoint<$field> {
460 fn from(value: $struct_name) -> Self {
461 value.0
462 }
463 }
464
465 impl From<AffinePoint<$field>> for $struct_name {
466 fn from(value: AffinePoint<$field>) -> Self {
467 Self(value)
468 }
469 }
470 };
471}
472
473/// Implements `Group` on `$struct_name` assuming that `$struct_name` implements `WeierstrassPoint`.
474/// Assumes that `Neg` is implemented for `&$struct_name`.
475#[macro_export]
476macro_rules! impl_sw_group_ops {
477 ($struct_name:ident, $field:ty) => {
478 impl Group for $struct_name {
479 type SelfRef<'a> = &'a Self;
480
481 const IDENTITY: Self = <Self as WeierstrassPoint>::IDENTITY;
482
483 #[inline(always)]
484 fn double(&self) -> Self {
485 if self.is_identity() {
486 self.clone()
487 } else {
488 unsafe { self.double_nonidentity::<true>() }
489 }
490 }
491
492 #[inline(always)]
493 fn double_assign(&mut self) {
494 self.double_assign_impl::<true>();
495 }
496
497 // This implementation is the same as the default implementation in the `Group` trait,
498 // but it was found that overriding the default implementation reduced the cycle count
499 // by 50% on the ecrecover benchmark.
500 // We hypothesize that this is due to compiler optimizations that are not possible when
501 // the `is_identity` function is defined in a different source file.
502 #[inline(always)]
503 fn is_identity(&self) -> bool {
504 self == &<Self as Group>::IDENTITY
505 }
506 }
507
508 impl core::ops::Add<&$struct_name> for $struct_name {
509 type Output = Self;
510
511 #[inline(always)]
512 fn add(mut self, p2: &$struct_name) -> Self::Output {
513 use core::ops::AddAssign;
514 self.add_assign(p2);
515 self
516 }
517 }
518
519 impl core::ops::Add for $struct_name {
520 type Output = Self;
521
522 #[inline(always)]
523 fn add(self, rhs: Self) -> Self::Output {
524 self.add(&rhs)
525 }
526 }
527
528 impl core::ops::Add<&$struct_name> for &$struct_name {
529 type Output = $struct_name;
530
531 #[inline(always)]
532 fn add(self, p2: &$struct_name) -> Self::Output {
533 if self.is_identity() {
534 p2.clone()
535 } else if p2.is_identity() {
536 self.clone()
537 } else if WeierstrassPoint::x(self) == WeierstrassPoint::x(p2) {
538 if self.y() + p2.y() == <$field as openvm_algebra_guest::Field>::ZERO {
539 <$struct_name as WeierstrassPoint>::IDENTITY
540 } else {
541 unsafe { self.double_nonidentity::<true>() }
542 }
543 } else {
544 unsafe { self.add_ne_nonidentity::<true>(p2) }
545 }
546 }
547 }
548
549 impl core::ops::AddAssign<&$struct_name> for $struct_name {
550 #[inline(always)]
551 fn add_assign(&mut self, p2: &$struct_name) {
552 self.add_assign_impl::<true>(p2);
553 }
554 }
555
556 impl core::ops::AddAssign for $struct_name {
557 #[inline(always)]
558 fn add_assign(&mut self, rhs: Self) {
559 self.add_assign(&rhs);
560 }
561 }
562
563 impl core::ops::Sub<&$struct_name> for $struct_name {
564 type Output = Self;
565
566 #[inline(always)]
567 fn sub(self, rhs: &$struct_name) -> Self::Output {
568 core::ops::Sub::sub(&self, rhs)
569 }
570 }
571
572 impl core::ops::Sub for $struct_name {
573 type Output = $struct_name;
574
575 #[inline(always)]
576 fn sub(self, rhs: Self) -> Self::Output {
577 self.sub(&rhs)
578 }
579 }
580
581 impl core::ops::Sub<&$struct_name> for &$struct_name {
582 type Output = $struct_name;
583
584 #[inline(always)]
585 fn sub(self, p2: &$struct_name) -> Self::Output {
586 if p2.is_identity() {
587 self.clone()
588 } else if self.is_identity() {
589 core::ops::Neg::neg(p2)
590 } else if WeierstrassPoint::x(self) == WeierstrassPoint::x(p2) {
591 if self.y() == p2.y() {
592 <$struct_name as WeierstrassPoint>::IDENTITY
593 } else {
594 unsafe { self.double_nonidentity::<true>() }
595 }
596 } else {
597 unsafe { self.sub_ne_nonidentity::<true>(p2) }
598 }
599 }
600 }
601
602 impl core::ops::SubAssign<&$struct_name> for $struct_name {
603 #[inline(always)]
604 fn sub_assign(&mut self, p2: &$struct_name) {
605 if p2.is_identity() {
606 // do nothing
607 } else if self.is_identity() {
608 *self = core::ops::Neg::neg(p2);
609 } else if WeierstrassPoint::x(self) == WeierstrassPoint::x(p2) {
610 if self.y() == p2.y() {
611 *self = <$struct_name as WeierstrassPoint>::IDENTITY
612 } else {
613 unsafe {
614 self.double_assign_nonidentity::<true>();
615 }
616 }
617 } else {
618 unsafe {
619 self.sub_ne_assign_nonidentity::<true>(p2);
620 }
621 }
622 }
623 }
624
625 impl core::ops::SubAssign for $struct_name {
626 #[inline(always)]
627 fn sub_assign(&mut self, rhs: Self) {
628 self.sub_assign(&rhs);
629 }
630 }
631 };
632}