Skip to main content

dcrypt_algorithms/ec/p384/
point.rs

1//! P-384 elliptic curve point operations
2
3use crate::ec::p384::{
4    constants::{
5        P384_FIELD_ELEMENT_SIZE, P384_POINT_COMPRESSED_SIZE, P384_POINT_UNCOMPRESSED_SIZE,
6    },
7    field::FieldElement,
8    scalar::Scalar,
9};
10use crate::error::{validate, Error, Result};
11use dcrypt_internal::constant_time::{Choice, ConditionallySelectable};
12use dcrypt_internal::zeroing::{Zeroize, ZeroizeOnDrop, Zeroizing};
13use dcrypt_params::traditional::ecdsa::NIST_P384;
14
15/// Format of a serialized elliptic curve point
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum PointFormat {
18    /// SEC 1 identity point (`0x00`)
19    Identity,
20    /// Uncompressed format: 0x04 || x || y
21    Uncompressed,
22    /// Compressed format: 0x02/0x03 || x
23    Compressed,
24}
25
26/// P-384 elliptic curve point in affine coordinates (x, y)
27///
28/// Represents points on the NIST P-384 curve. The special point at infinity
29/// (identity element) is represented with is_identity = true.
30#[derive(Clone, Debug)]
31pub struct Point {
32    /// Whether this point is the identity element (point at infinity)
33    pub(crate) is_identity: Choice,
34    /// X coordinate in affine representation
35    pub(crate) x: FieldElement,
36    /// Y coordinate in affine representation  
37    pub(crate) y: FieldElement,
38}
39
40impl Zeroize for Point {
41    fn zeroize(&mut self) {
42        self.is_identity.zeroize();
43        self.x.zeroize();
44        self.y.zeroize();
45    }
46}
47
48impl Drop for Point {
49    fn drop(&mut self) {
50        self.zeroize();
51    }
52}
53
54impl ZeroizeOnDrop for Point {}
55
56/// P-384 point in Jacobian projective coordinates (X:Y:Z) for efficient arithmetic
57///
58/// Jacobian coordinates represent affine point (x,y) as (X:Y:Z) where:
59/// - x = X/Z²
60/// - y = Y/Z³  
61/// - Point at infinity has Z = 0
62// `Copy` is retained because `ConditionallySelectable` requires it. Explicit
63// scalar-derived projective values are always owned by `Zeroizing`, limiting
64// source-visible copies even though register copies cannot be guaranteed wiped.
65#[derive(Clone, Copy, Debug)]
66pub(crate) struct ProjectivePoint {
67    /// Whether this point is the identity element (point at infinity)
68    is_identity: Choice,
69    /// X coordinate in Jacobian representation
70    x: FieldElement,
71    /// Y coordinate in Jacobian representation
72    y: FieldElement,
73    /// Z coordinate (projective factor)
74    z: FieldElement,
75}
76
77impl Default for ProjectivePoint {
78    fn default() -> Self {
79        Self::identity()
80    }
81}
82
83impl Zeroize for ProjectivePoint {
84    fn zeroize(&mut self) {
85        self.is_identity.zeroize();
86        self.x.zeroize();
87        self.y.zeroize();
88        self.z.zeroize();
89    }
90}
91
92impl PartialEq for Point {
93    /// Constant-time equality comparison for elliptic curve points
94    fn eq(&self, other: &Self) -> bool {
95        let self_is_identity: bool = self.is_identity.into();
96        let other_is_identity: bool = other.is_identity.into();
97
98        if self_is_identity || other_is_identity {
99            return self_is_identity == other_is_identity;
100        }
101        self.x == other.x && self.y == other.y
102    }
103}
104
105impl Point {
106    /// Create a new elliptic curve point from uncompressed coordinates
107    ///
108    /// Validates that the given (x, y) coordinates satisfy the P-384 curve equation:
109    /// y² = x³ - 3x + b (mod p)
110    ///
111    /// Returns an error if the point is not on the curve.
112    pub fn new_uncompressed(
113        x: &[u8; P384_FIELD_ELEMENT_SIZE],
114        y: &[u8; P384_FIELD_ELEMENT_SIZE],
115    ) -> Result<Self> {
116        let x_fe = Zeroizing::new(FieldElement::from_bytes(x)?);
117        let y_fe = Zeroizing::new(FieldElement::from_bytes(y)?);
118
119        // Validate that the point lies on the curve
120        if !Self::is_on_curve(&x_fe, &y_fe) {
121            return Err(Error::param(
122                "P-384 Point",
123                "Point coordinates do not satisfy curve equation",
124            ));
125        }
126
127        Ok(Point {
128            is_identity: Choice::from(0),
129            x: x_fe.into_inner(),
130            y: y_fe.into_inner(),
131        })
132    }
133
134    /// Create the identity element (point at infinity)
135    ///
136    /// The identity element serves as the additive neutral element
137    /// for the elliptic curve group operation.
138    pub fn identity() -> Self {
139        Point {
140            is_identity: Choice::from(1),
141            x: FieldElement::zero(),
142            y: FieldElement::zero(),
143        }
144    }
145
146    /// Check if this point is the identity element
147    pub fn is_identity(&self) -> bool {
148        self.is_identity.into()
149    }
150
151    /// Get the x-coordinate as a byte array in big-endian format
152    pub fn x_coordinate_bytes(&self) -> [u8; P384_FIELD_ELEMENT_SIZE] {
153        self.x.to_bytes()
154    }
155
156    /// Get the y-coordinate as a byte array in big-endian format
157    pub fn y_coordinate_bytes(&self) -> [u8; P384_FIELD_ELEMENT_SIZE] {
158        self.y.to_bytes()
159    }
160
161    /// Detect point format from serialized bytes
162    ///
163    /// Analyzes the leading byte and length to determine the serialization format.
164    pub fn detect_format(bytes: &[u8]) -> Result<PointFormat> {
165        if bytes.is_empty() {
166            return Err(Error::param("P-384 Point", "Empty point data"));
167        }
168
169        match (bytes[0], bytes.len()) {
170            (0x00, 1) => Ok(PointFormat::Identity),
171            (0x04, P384_POINT_UNCOMPRESSED_SIZE) => Ok(PointFormat::Uncompressed),
172            (0x02 | 0x03, P384_POINT_COMPRESSED_SIZE) => Ok(PointFormat::Compressed),
173            _ => Err(Error::param(
174                "P-384 Point",
175                "Unknown or malformed point format",
176            )),
177        }
178    }
179
180    /// Serialize point to uncompressed format: 0x04 || x || y
181    ///
182    /// For the identity this fixed-width API returns an all-zero internal
183    /// sentinel. SEC 1 encodes identity as the single byte `0x00`, so the
184    /// sentinel is deliberately rejected by the deserializer and must not be
185    /// placed on the wire.
186    pub fn serialize_uncompressed(&self) -> [u8; P384_POINT_UNCOMPRESSED_SIZE] {
187        let mut result = [0u8; P384_POINT_UNCOMPRESSED_SIZE];
188
189        // Special encoding for the identity element
190        if self.is_identity() {
191            return result; // All zeros represents identity
192        }
193
194        // Standard uncompressed format: 0x04 || x || y
195        result[0] = 0x04;
196        self.x.write_bytes(&mut result[1..49]);
197        self.y.write_bytes(&mut result[49..97]);
198
199        result
200    }
201
202    /// Deserialize point from uncompressed byte format
203    ///
204    /// Supports the standard uncompressed format (0x04 || x || y) and
205    /// rejects non-canonical fixed-width encodings of the identity element.
206    pub fn deserialize_uncompressed(bytes: &[u8]) -> Result<Self> {
207        validate::length("P-384 Point", bytes.len(), P384_POINT_UNCOMPRESSED_SIZE)?;
208
209        // Validate uncompressed format indicator
210        if bytes[0] != 0x04 {
211            return Err(Error::param(
212                "P-384 Point",
213                "Invalid uncompressed point format (expected 0x04 prefix)",
214            ));
215        }
216
217        // Extract and validate coordinates
218        let mut x_bytes = Zeroizing::new([0u8; P384_FIELD_ELEMENT_SIZE]);
219        let mut y_bytes = Zeroizing::new([0u8; P384_FIELD_ELEMENT_SIZE]);
220
221        x_bytes.copy_from_slice(&bytes[1..49]);
222        y_bytes.copy_from_slice(&bytes[49..97]);
223
224        Self::new_uncompressed(&x_bytes, &y_bytes)
225    }
226
227    /// Serialize point to SEC 1 compressed format (0x02/0x03 || x)
228    ///
229    /// The compressed format uses:
230    /// - 0x02 prefix if y-coordinate is even
231    /// - 0x03 prefix if y-coordinate is odd
232    /// - Followed by the x-coordinate in big-endian format
233    ///
234    /// For the identity this fixed-width API returns an all-zero internal
235    /// sentinel, which is not a canonical SEC 1 encoding and is rejected by
236    /// the deserializer.
237    pub fn serialize_compressed(&self) -> [u8; P384_POINT_COMPRESSED_SIZE] {
238        let mut out = [0u8; P384_POINT_COMPRESSED_SIZE];
239
240        // Identity → all zeros
241        if self.is_identity() {
242            return out;
243        }
244
245        // Determine prefix based on y-coordinate parity
246        out[0] = if self.y.is_odd() { 0x03 } else { 0x02 };
247        self.x.write_bytes(&mut out[1..]);
248        out
249    }
250
251    /// Deserialize SEC 1 compressed point
252    ///
253    /// Recovers the full point from compressed format by computing y² = x³ - 3x + b
254    /// and finding the square root.
255    pub fn deserialize_compressed(bytes: &[u8]) -> Result<Self> {
256        validate::length(
257            "P-384 Compressed Point",
258            bytes.len(),
259            P384_POINT_COMPRESSED_SIZE,
260        )?;
261
262        let tag = bytes[0];
263        if tag != 0x02 && tag != 0x03 {
264            return Err(Error::param(
265                "P-384 Point",
266                "Invalid compressed point prefix (expected 0x02 or 0x03)",
267            ));
268        }
269
270        // Extract x-coordinate
271        let mut x_bytes = Zeroizing::new([0u8; P384_FIELD_ELEMENT_SIZE]);
272        x_bytes.copy_from_slice(&bytes[1..]);
273
274        let x_fe = Zeroizing::new(FieldElement::from_bytes(&x_bytes).map_err(|_| {
275            Error::param(
276                "P-384 Point",
277                "Invalid compressed point: x-coordinate yields quadratic non-residue",
278            )
279        })?);
280
281        // Compute right-hand side: y² = x³ - 3x + b
282        let x2 = Zeroizing::new(x_fe.square());
283        let x3 = Zeroizing::new(x2.mul(&x_fe));
284        let a = Zeroizing::new(FieldElement(FieldElement::A_M3)); // a = -3
285        let b = Zeroizing::new(FieldElement::from_bytes(&NIST_P384.b).unwrap());
286        let ax = Zeroizing::new(a.mul(&x_fe));
287        let x3_plus_ax = Zeroizing::new(x3.add(&ax));
288        let rhs = Zeroizing::new(x3_plus_ax.add(&b));
289
290        // Attempt to find square root
291        let y_fe = Zeroizing::new(rhs.sqrt().ok_or_else(|| {
292            Error::param(
293                "P-384 Point",
294                "Invalid compressed point: x-coordinate yields quadratic non-residue",
295            )
296        })?);
297
298        // Select the correct root based on parity
299        let y_matches = (y_fe.is_odd() && tag == 0x03) || (!y_fe.is_odd() && tag == 0x02);
300        let y_final = if y_matches {
301            Zeroizing::new(y_fe.into_inner())
302        } else {
303            // Use the negative root (p - y)
304            let modulus = Zeroizing::new(FieldElement::get_modulus());
305            Zeroizing::new(modulus.sub(&y_fe))
306        };
307
308        Ok(Point {
309            is_identity: Choice::from(0),
310            x: x_fe.into_inner(),
311            y: y_final.into_inner(),
312        })
313    }
314
315    /// Elliptic curve point addition using the group law
316    ///
317    /// Implements the abelian group operation for P-384 points.
318    pub fn add(&self, other: &Self) -> Self {
319        let p1 = Zeroizing::new(self.to_projective());
320        let p2 = Zeroizing::new(other.to_projective());
321        let result = Zeroizing::new(p1.add(&p2));
322        result.to_affine()
323    }
324
325    /// Elliptic curve point doubling: 2 * self
326    pub fn double(&self) -> Self {
327        let p = Zeroizing::new(self.to_projective());
328        let result = Zeroizing::new(p.double());
329        result.to_affine()
330    }
331
332    /// Scalar multiplication: compute scalar * self
333    ///
334    /// Uses constant-time double-and-add algorithm.
335    pub fn mul(&self, scalar: &Scalar) -> Result<Self> {
336        let scalar_bytes = scalar.as_secret_buffer().as_ref();
337
338        // Work in Jacobian/projective coordinates throughout
339        let base = Zeroizing::new(self.to_projective());
340        let mut result = Zeroizing::new(ProjectivePoint::identity());
341
342        for byte in scalar_bytes.iter() {
343            for bit_pos in (0..8).rev() {
344                let mut bit = (byte >> bit_pos) & 1;
345                let mut choice = Choice::from(bit);
346                bit.zeroize();
347
348                let doubled = Zeroizing::new(result.double());
349
350                // Always compute the addition
351                let result_added = Zeroizing::new(doubled.add(&base));
352
353                // Constant-time select: if bit is 1, use added result, else keep result
354                let selected = Zeroizing::new(ProjectivePoint::conditional_select(
355                    &doubled,
356                    &result_added,
357                    choice,
358                ));
359                result.zeroize();
360                *result = selected.into_inner();
361                choice.zeroize();
362            }
363        }
364
365        Ok(result.to_affine())
366    }
367
368    // Private helper methods
369
370    /// Validate that coordinates satisfy the P-384 curve equation
371    fn is_on_curve(x: &FieldElement, y: &FieldElement) -> bool {
372        // Left-hand side: y²
373        let y_squared = Zeroizing::new(y.square());
374
375        // Right-hand side: x³ - 3x + b
376        let x_squared = Zeroizing::new(x.square());
377        let x_cubed = Zeroizing::new(x_squared.mul(x));
378        let a_coeff = Zeroizing::new(FieldElement(FieldElement::A_M3)); // a = -3 mod p
379        let ax = Zeroizing::new(a_coeff.mul(x));
380        let b_coeff = Zeroizing::new(FieldElement::from_bytes(&NIST_P384.b).unwrap());
381
382        // Compute x³ - 3x + b
383        let x_cubed_plus_ax = Zeroizing::new(x_cubed.add(&ax));
384        let rhs = Zeroizing::new(x_cubed_plus_ax.add(&b_coeff));
385
386        *y_squared == *rhs
387    }
388
389    /// Convert affine point to Jacobian projective coordinates
390    fn to_projective(&self) -> ProjectivePoint {
391        let regular = Zeroizing::new(ProjectivePoint {
392            is_identity: Choice::from(0),
393            x: self.x,
394            y: self.y,
395            z: FieldElement::one(),
396        });
397        let identity = Zeroizing::new(ProjectivePoint::identity());
398        let selected = Zeroizing::new(ProjectivePoint::conditional_select(
399            &regular,
400            &identity,
401            self.is_identity,
402        ));
403        selected.into_inner()
404    }
405}
406
407impl ConditionallySelectable for ProjectivePoint {
408    #[inline(never)]
409    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
410        Self {
411            is_identity: Choice::conditional_select(&a.is_identity, &b.is_identity, choice),
412            x: FieldElement::conditional_select(&a.x, &b.x, choice),
413            y: FieldElement::conditional_select(&a.y, &b.y, choice),
414            z: FieldElement::conditional_select(&a.z, &b.z, choice),
415        }
416    }
417}
418
419impl ProjectivePoint {
420    /// Identity in Jacobian form: (0 : 1 : 0)
421    pub fn identity() -> Self {
422        ProjectivePoint {
423            is_identity: Choice::from(1),
424            x: FieldElement::zero(),
425            y: FieldElement::one(),
426            z: FieldElement::zero(),
427        }
428    }
429
430    /// Projective point addition using constant-time formulas
431    pub fn add(&self, other: &Self) -> Self {
432        // 1. Compute Generic Addition (assuming P != Q, neither is identity)
433        // Reference: "Guide to Elliptic Curve Cryptography" Algorithm 3.22
434        let z1_squared = Zeroizing::new(self.z.square());
435        let z2_squared = Zeroizing::new(other.z.square());
436        let z1_cubed = Zeroizing::new(z1_squared.mul(&self.z));
437        let z2_cubed = Zeroizing::new(z2_squared.mul(&other.z));
438
439        let u1 = Zeroizing::new(self.x.mul(&z2_squared)); // X1 · Z2²
440        let u2 = Zeroizing::new(other.x.mul(&z1_squared)); // X2 · Z1²
441        let s1 = Zeroizing::new(self.y.mul(&z2_cubed)); // Y1 · Z2³
442        let s2 = Zeroizing::new(other.y.mul(&z1_cubed)); // Y2 · Z1³
443
444        // Compute differences
445        let h = Zeroizing::new(u2.sub(&u1)); // X2·Z1² − X1·Z2²
446        let r = Zeroizing::new(s2.sub(&s1)); // Y2·Z1³ − Y1·Z2³
447
448        // General addition arithmetic
449        let h_squared = Zeroizing::new(h.square());
450        let h_cubed = Zeroizing::new(h_squared.mul(&h));
451        let v = Zeroizing::new(u1.mul(&h_squared));
452
453        // X3 = r² − h³ − 2·v
454        let r_squared = Zeroizing::new(r.square());
455        let two_v = Zeroizing::new(v.add(&v));
456        let x3_minus_h_cubed = Zeroizing::new(r_squared.sub(&h_cubed));
457        let x3 = Zeroizing::new(x3_minus_h_cubed.sub(&two_v));
458
459        // Y3 = r·(v − X3) − s1·h³
460        let v_minus_x3 = Zeroizing::new(v.sub(&x3));
461        let r_times_diff = Zeroizing::new(r.mul(&v_minus_x3));
462        let s1_times_h_cubed = Zeroizing::new(s1.mul(&h_cubed));
463        let y3 = Zeroizing::new(r_times_diff.sub(&s1_times_h_cubed));
464
465        // Z3 = Z1 · Z2 · h
466        let z1_times_z2 = Zeroizing::new(self.z.mul(&other.z));
467        let z3 = Zeroizing::new(z1_times_z2.mul(&h));
468
469        let generic_point = Zeroizing::new(Self {
470            is_identity: Choice::from(0),
471            x: x3.into_inner(),
472            y: y3.into_inner(),
473            z: z3.into_inner(),
474        });
475
476        // 2. Compute Doubling (fallback for P==Q)
477        let double_point = Zeroizing::new(self.double());
478
479        // 3. Select Result based on state
480        let mut h_is_zero = Choice::from((h.is_zero() as u8) & 1);
481        let mut r_is_zero = Choice::from((r.is_zero() as u8) & 1);
482
483        // Case: P == Q (h=0, r=0)
484        let mut p_eq_q = h_is_zero & r_is_zero;
485        // Case: P == -Q (h=0, r!=0)
486        let mut p_eq_neg_q = h_is_zero & !r_is_zero;
487
488        // Start with generic addition result
489        let mut result = Zeroizing::new(Self::conditional_select(
490            &generic_point,
491            &double_point,
492            p_eq_q,
493        ));
494
495        // If P == -Q, use identity
496        let identity = Zeroizing::new(Self::identity());
497        let selected = Zeroizing::new(Self::conditional_select(&result, &identity, p_eq_neg_q));
498        result.zeroize();
499        *result = selected.into_inner();
500
501        // If either input is identity, result is the other
502        let selected = Zeroizing::new(Self::conditional_select(&result, other, self.is_identity));
503        result.zeroize();
504        *result = selected.into_inner();
505        let selected = Zeroizing::new(Self::conditional_select(&result, self, other.is_identity));
506        result.zeroize();
507        *result = selected.into_inner();
508
509        h_is_zero.zeroize();
510        r_is_zero.zeroize();
511        p_eq_q.zeroize();
512        p_eq_neg_q.zeroize();
513        result.into_inner()
514    }
515
516    /// Projective point doubling using constant-time formulas
517    #[inline]
518    pub fn double(&self) -> Self {
519        // ── 1. Pre-computations ─────────────────────────────────
520        // Δ = Z₁²
521        let delta = Zeroizing::new(self.z.square());
522
523        // Γ = Y₁²
524        let gamma = Zeroizing::new(self.y.square());
525
526        // β = X₁·Γ
527        let beta = Zeroizing::new(self.x.mul(&gamma));
528
529        // α = 3·(X₁ − Δ)·(X₁ + Δ)       (valid because a = –3)
530        let x_plus_delta = Zeroizing::new(self.x.add(&delta));
531        let x_minus_delta = Zeroizing::new(self.x.sub(&delta));
532        let alpha_base = Zeroizing::new(x_plus_delta.mul(&x_minus_delta));
533        let two_alpha = Zeroizing::new(alpha_base.add(&alpha_base));
534        let alpha = Zeroizing::new(two_alpha.add(&alpha_base)); // ×3
535
536        // ── 2. Output coordinates ──────────────────────────────
537        // X₃ = α² − 8·β
538        let two_beta = Zeroizing::new(beta.add(&beta));
539        let four_beta = Zeroizing::new(two_beta.add(&two_beta));
540        let eight_beta = Zeroizing::new(four_beta.add(&four_beta));
541        let alpha_squared = Zeroizing::new(alpha.square());
542        let x3 = Zeroizing::new(alpha_squared.sub(&eight_beta));
543
544        // Z₃ = (Y₁ + Z₁)² − Γ − Δ
545        let y_plus_z = Zeroizing::new(self.y.add(&self.z));
546        let y_plus_z_squared = Zeroizing::new(y_plus_z.square());
547        let z3_minus_gamma = Zeroizing::new(y_plus_z_squared.sub(&gamma));
548        let z3 = Zeroizing::new(z3_minus_gamma.sub(&delta));
549
550        // Y₃ = α·(4·β − X₃) − 8·Γ²
551        let four_beta_minus_x3 = Zeroizing::new(four_beta.sub(&x3));
552        let alpha_times_difference = Zeroizing::new(alpha.mul(&four_beta_minus_x3));
553
554        let gamma_sq = Zeroizing::new(gamma.square());
555        let two_gamma_sq = Zeroizing::new(gamma_sq.add(&gamma_sq));
556        let four_gamma_sq = Zeroizing::new(two_gamma_sq.add(&two_gamma_sq));
557        let eight_gamma_sq = Zeroizing::new(four_gamma_sq.add(&four_gamma_sq));
558        let y3 = Zeroizing::new(alpha_times_difference.sub(&eight_gamma_sq));
559
560        let result = Zeroizing::new(Self {
561            is_identity: Choice::from(0),
562            x: x3.into_inner(),
563            y: y3.into_inner(),
564            z: z3.into_inner(),
565        });
566
567        // Explicitly handle identity or y=0 cases constant-time
568        let mut return_identity = self.is_identity | Choice::from(self.y.is_zero() as u8);
569
570        let identity = Zeroizing::new(Self::identity());
571        let selected = Zeroizing::new(Self::conditional_select(
572            &result,
573            &identity,
574            return_identity,
575        ));
576        return_identity.zeroize();
577        selected.into_inner()
578    }
579
580    /// Convert Jacobian projective coordinates back to affine coordinates
581    pub fn to_affine(&self) -> Point {
582        // Select a non-zero denominator for the identity so conversion does
583        // not branch on a scalar-derived result.
584        let one = Zeroizing::new(FieldElement::one());
585        let safe_z = Zeroizing::new(FieldElement::conditional_select(
586            &self.z,
587            &one,
588            self.is_identity,
589        ));
590        let z_inv = Zeroizing::new(
591            safe_z
592                .invert()
593                .expect("Non-zero Z coordinate should be invertible"),
594        );
595        let z_inv_squared = Zeroizing::new(z_inv.square());
596        let z_inv_cubed = Zeroizing::new(z_inv_squared.mul(&z_inv));
597
598        // Convert to affine coordinates: (x, y) = (X/Z², Y/Z³)
599        let x_affine = Zeroizing::new(self.x.mul(&z_inv_squared));
600        let y_affine = Zeroizing::new(self.y.mul(&z_inv_cubed));
601        let zero = Zeroizing::new(FieldElement::zero());
602        let x = Zeroizing::new(FieldElement::conditional_select(
603            &x_affine,
604            &zero,
605            self.is_identity,
606        ));
607        let y = Zeroizing::new(FieldElement::conditional_select(
608            &y_affine,
609            &zero,
610            self.is_identity,
611        ));
612
613        Point {
614            is_identity: self.is_identity,
615            x: x.into_inner(),
616            y: y.into_inner(),
617        }
618    }
619}