sec1/
point.rs

1//! Support for the SEC1 `Elliptic-Curve-Point-to-Octet-String` and
2//! `Octet-String-to-Elliptic-Curve-Point` encoding algorithms.
3//!
4//! Described in [SEC1: Elliptic Curve Cryptography] (Version 2.0) section 2.3.3 (p.10).
5//!
6//! [SEC1: Elliptic Curve Cryptography]: https://www.secg.org/sec1-v2.pdf
7
8use crate::{Error, Result};
9use core::{
10    cmp::Ordering,
11    fmt::{self, Debug},
12    ops::Add,
13    str,
14};
15use generic_array::{
16    typenum::{U1, U28, U32, U48, U66},
17    ArrayLength, GenericArray,
18};
19
20#[cfg(feature = "alloc")]
21use alloc::boxed::Box;
22
23#[cfg(feature = "serde")]
24use serde::{de, ser, Deserialize, Serialize};
25
26#[cfg(feature = "subtle")]
27use subtle::{Choice, ConditionallySelectable};
28
29#[cfg(feature = "zeroize")]
30use zeroize::Zeroize;
31
32/// Trait for supported modulus sizes which precomputes the typenums for
33/// various point encodings so they don't need to be included as bounds.
34// TODO(tarcieri): replace this all with const generic expressions.
35pub trait ModulusSize: 'static + ArrayLength<u8> + Copy + Debug {
36    /// Size of a compressed point for the given elliptic curve when encoded
37    /// using the SEC1 `Elliptic-Curve-Point-to-Octet-String` algorithm
38    /// (including leading `0x02` or `0x03` tag byte).
39    type CompressedPointSize: 'static + ArrayLength<u8> + Copy + Debug;
40
41    /// Size of an uncompressed point for the given elliptic curve when encoded
42    /// using the SEC1 `Elliptic-Curve-Point-to-Octet-String` algorithm
43    /// (including leading `0x04` tag byte).
44    type UncompressedPointSize: 'static + ArrayLength<u8> + Copy + Debug;
45
46    /// Size of an untagged point for given elliptic curve, i.e. size of two
47    /// serialized base field elements.
48    type UntaggedPointSize: 'static + ArrayLength<u8> + Copy + Debug;
49}
50
51macro_rules! impl_modulus_size {
52    ($($size:ty),+) => {
53        $(impl ModulusSize for $size {
54            type CompressedPointSize = <$size as Add<U1>>::Output;
55            type UncompressedPointSize = <Self::UntaggedPointSize as Add<U1>>::Output;
56            type UntaggedPointSize = <$size as Add>::Output;
57        })+
58    }
59}
60
61impl_modulus_size!(U28, U32, U48, U66);
62
63/// SEC1 encoded curve point.
64///
65/// This type is an enum over the compressed and uncompressed encodings,
66/// useful for cases where either encoding can be supported, or conversions
67/// between the two forms.
68#[derive(Clone, Default)]
69pub struct EncodedPoint<Size>
70where
71    Size: ModulusSize,
72{
73    bytes: GenericArray<u8, Size::UncompressedPointSize>,
74}
75
76#[allow(clippy::len_without_is_empty)]
77impl<Size> EncodedPoint<Size>
78where
79    Size: ModulusSize,
80{
81    /// Decode elliptic curve point (compressed or uncompressed) from the
82    /// `Elliptic-Curve-Point-to-Octet-String` encoding described in
83    /// SEC 1: Elliptic Curve Cryptography (Version 2.0) section
84    /// 2.3.3 (page 10).
85    ///
86    /// <http://www.secg.org/sec1-v2.pdf>
87    pub fn from_bytes(input: impl AsRef<[u8]>) -> Result<Self> {
88        let input = input.as_ref();
89
90        // Validate tag
91        let tag = input
92            .first()
93            .cloned()
94            .ok_or(Error::PointEncoding)
95            .and_then(Tag::from_u8)?;
96
97        // Validate length
98        let expected_len = tag.message_len(Size::to_usize());
99
100        if input.len() != expected_len {
101            return Err(Error::PointEncoding);
102        }
103
104        let mut bytes = GenericArray::default();
105        bytes[..expected_len].copy_from_slice(input);
106        Ok(Self { bytes })
107    }
108
109    /// Decode elliptic curve point from raw uncompressed coordinates, i.e.
110    /// encoded as the concatenated `x || y` coordinates with no leading SEC1
111    /// tag byte (which would otherwise be `0x04` for an uncompressed point).
112    pub fn from_untagged_bytes(bytes: &GenericArray<u8, Size::UntaggedPointSize>) -> Self {
113        let (x, y) = bytes.split_at(Size::to_usize());
114        Self::from_affine_coordinates(x.into(), y.into(), false)
115    }
116
117    /// Encode an elliptic curve point from big endian serialized coordinates
118    /// (with optional point compression)
119    pub fn from_affine_coordinates(
120        x: &GenericArray<u8, Size>,
121        y: &GenericArray<u8, Size>,
122        compress: bool,
123    ) -> Self {
124        let tag = if compress {
125            Tag::compress_y(y.as_slice())
126        } else {
127            Tag::Uncompressed
128        };
129
130        let mut bytes = GenericArray::default();
131        bytes[0] = tag.into();
132        bytes[1..(Size::to_usize() + 1)].copy_from_slice(x);
133
134        if !compress {
135            bytes[(Size::to_usize() + 1)..].copy_from_slice(y);
136        }
137
138        Self { bytes }
139    }
140
141    /// Return [`EncodedPoint`] representing the additive identity
142    /// (a.k.a. point at infinity)
143    pub fn identity() -> Self {
144        Self::default()
145    }
146
147    /// Get the length of the encoded point in bytes
148    pub fn len(&self) -> usize {
149        self.tag().message_len(Size::to_usize())
150    }
151
152    /// Get byte slice containing the serialized [`EncodedPoint`].
153    pub fn as_bytes(&self) -> &[u8] {
154        &self.bytes[..self.len()]
155    }
156
157    /// Get boxed byte slice containing the serialized [`EncodedPoint`]
158    #[cfg(feature = "alloc")]
159    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
160    pub fn to_bytes(&self) -> Box<[u8]> {
161        self.as_bytes().to_vec().into_boxed_slice()
162    }
163
164    /// Is this [`EncodedPoint`] compact?
165    pub fn is_compact(&self) -> bool {
166        self.tag().is_compact()
167    }
168
169    /// Is this [`EncodedPoint`] compressed?
170    pub fn is_compressed(&self) -> bool {
171        self.tag().is_compressed()
172    }
173
174    /// Is this [`EncodedPoint`] the additive identity? (a.k.a. point at infinity)
175    pub fn is_identity(&self) -> bool {
176        self.tag().is_identity()
177    }
178
179    /// Compress this [`EncodedPoint`], returning a new [`EncodedPoint`].
180    pub fn compress(&self) -> Self {
181        match self.coordinates() {
182            Coordinates::Compressed { .. }
183            | Coordinates::Compact { .. }
184            | Coordinates::Identity => self.clone(),
185            Coordinates::Uncompressed { x, y } => Self::from_affine_coordinates(x, y, true),
186        }
187    }
188
189    /// Get the SEC1 tag for this [`EncodedPoint`]
190    pub fn tag(&self) -> Tag {
191        // Tag is ensured valid by the constructor
192        Tag::from_u8(self.bytes[0]).expect("invalid tag")
193    }
194
195    /// Get the [`Coordinates`] for this [`EncodedPoint`].
196    #[inline]
197    pub fn coordinates(&self) -> Coordinates<'_, Size> {
198        if self.is_identity() {
199            return Coordinates::Identity;
200        }
201
202        let (x, y) = self.bytes[1..].split_at(Size::to_usize());
203
204        if self.is_compressed() {
205            Coordinates::Compressed {
206                x: x.into(),
207                y_is_odd: self.tag() as u8 & 1 == 1,
208            }
209        } else if self.is_compact() {
210            Coordinates::Compact { x: x.into() }
211        } else {
212            Coordinates::Uncompressed {
213                x: x.into(),
214                y: y.into(),
215            }
216        }
217    }
218
219    /// Get the x-coordinate for this [`EncodedPoint`].
220    ///
221    /// Returns `None` if this point is the identity point.
222    pub fn x(&self) -> Option<&GenericArray<u8, Size>> {
223        match self.coordinates() {
224            Coordinates::Identity => None,
225            Coordinates::Compressed { x, .. } => Some(x),
226            Coordinates::Uncompressed { x, .. } => Some(x),
227            Coordinates::Compact { x } => Some(x),
228        }
229    }
230
231    /// Get the y-coordinate for this [`EncodedPoint`].
232    ///
233    /// Returns `None` if this point is compressed or the identity point.
234    pub fn y(&self) -> Option<&GenericArray<u8, Size>> {
235        match self.coordinates() {
236            Coordinates::Compressed { .. } | Coordinates::Identity => None,
237            Coordinates::Uncompressed { y, .. } => Some(y),
238            Coordinates::Compact { .. } => None,
239        }
240    }
241}
242
243impl<Size> AsRef<[u8]> for EncodedPoint<Size>
244where
245    Size: ModulusSize,
246{
247    #[inline]
248    fn as_ref(&self) -> &[u8] {
249        self.as_bytes()
250    }
251}
252
253#[cfg(feature = "subtle")]
254impl<Size> ConditionallySelectable for EncodedPoint<Size>
255where
256    Size: ModulusSize,
257    <Size::UncompressedPointSize as ArrayLength<u8>>::ArrayType: Copy,
258{
259    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
260        let mut bytes = GenericArray::default();
261
262        for (i, byte) in bytes.iter_mut().enumerate() {
263            *byte = u8::conditional_select(&a.bytes[i], &b.bytes[i], choice);
264        }
265
266        Self { bytes }
267    }
268}
269
270impl<Size> Copy for EncodedPoint<Size>
271where
272    Size: ModulusSize,
273    <Size::UncompressedPointSize as ArrayLength<u8>>::ArrayType: Copy,
274{
275}
276
277impl<Size> Debug for EncodedPoint<Size>
278where
279    Size: ModulusSize,
280{
281    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282        write!(f, "EncodedPoint({:?})", self.coordinates())
283    }
284}
285
286impl<Size: ModulusSize> Eq for EncodedPoint<Size> {}
287
288impl<Size> PartialEq for EncodedPoint<Size>
289where
290    Size: ModulusSize,
291{
292    fn eq(&self, other: &Self) -> bool {
293        self.as_bytes() == other.as_bytes()
294    }
295}
296
297impl<Size: ModulusSize> PartialOrd for EncodedPoint<Size>
298where
299    Size: ModulusSize,
300{
301    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
302        Some(self.cmp(other))
303    }
304}
305
306impl<Size: ModulusSize> Ord for EncodedPoint<Size>
307where
308    Size: ModulusSize,
309{
310    fn cmp(&self, other: &Self) -> Ordering {
311        self.as_bytes().cmp(other.as_bytes())
312    }
313}
314
315impl<Size: ModulusSize> TryFrom<&[u8]> for EncodedPoint<Size>
316where
317    Size: ModulusSize,
318{
319    type Error = Error;
320
321    fn try_from(bytes: &[u8]) -> Result<Self> {
322        Self::from_bytes(bytes)
323    }
324}
325
326#[cfg(feature = "zeroize")]
327impl<Size> Zeroize for EncodedPoint<Size>
328where
329    Size: ModulusSize,
330{
331    fn zeroize(&mut self) {
332        self.bytes.zeroize();
333        *self = Self::identity();
334    }
335}
336
337impl<Size> fmt::Display for EncodedPoint<Size>
338where
339    Size: ModulusSize,
340{
341    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342        write!(f, "{:X}", self)
343    }
344}
345
346impl<Size> fmt::LowerHex for EncodedPoint<Size>
347where
348    Size: ModulusSize,
349{
350    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351        for byte in self.as_bytes() {
352            write!(f, "{:02x}", byte)?;
353        }
354        Ok(())
355    }
356}
357
358impl<Size> fmt::UpperHex for EncodedPoint<Size>
359where
360    Size: ModulusSize,
361{
362    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363        for byte in self.as_bytes() {
364            write!(f, "{:02X}", byte)?;
365        }
366        Ok(())
367    }
368}
369
370/// Decode a SEC1-encoded point from hexadecimal.
371///
372/// Upper and lower case hexadecimal are both accepted, however mixed case is
373/// rejected.
374impl<Size> str::FromStr for EncodedPoint<Size>
375where
376    Size: ModulusSize,
377{
378    type Err = Error;
379
380    fn from_str(hex: &str) -> Result<Self> {
381        let mut buffer = GenericArray::<u8, Size::UncompressedPointSize>::default();
382        let decoded_len = hex.as_bytes().len() / 2;
383
384        if hex.as_bytes().len() % 2 != 0 || decoded_len > buffer.len() {
385            return Err(Error::PointEncoding);
386        }
387
388        let mut upper_case = None;
389
390        // Ensure all characters are valid and case is not mixed
391        for &byte in hex.as_bytes() {
392            match byte {
393                b'0'..=b'9' => (),
394                b'a'..=b'z' => match upper_case {
395                    Some(true) => return Err(Error::PointEncoding),
396                    Some(false) => (),
397                    None => upper_case = Some(false),
398                },
399                b'A'..=b'Z' => match upper_case {
400                    Some(true) => (),
401                    Some(false) => return Err(Error::PointEncoding),
402                    None => upper_case = Some(true),
403                },
404                _ => return Err(Error::PointEncoding),
405            }
406        }
407
408        for (digit, byte) in hex.as_bytes().chunks_exact(2).zip(buffer.iter_mut()) {
409            *byte = str::from_utf8(digit)
410                .ok()
411                .and_then(|s| u8::from_str_radix(s, 16).ok())
412                .ok_or(Error::PointEncoding)?;
413        }
414
415        Self::from_bytes(&buffer[..decoded_len])
416    }
417}
418
419#[cfg(feature = "serde")]
420#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
421impl<Size> Serialize for EncodedPoint<Size>
422where
423    Size: ModulusSize,
424{
425    #[cfg(not(feature = "alloc"))]
426    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
427    where
428        S: ser::Serializer,
429    {
430        self.as_bytes().serialize(serializer)
431    }
432
433    #[cfg(feature = "alloc")]
434    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
435    where
436        S: ser::Serializer,
437    {
438        use alloc::string::ToString;
439        if serializer.is_human_readable() {
440            self.to_string().serialize(serializer)
441        } else {
442            self.as_bytes().serialize(serializer)
443        }
444    }
445}
446
447#[cfg(feature = "serde")]
448#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
449impl<'de, Size> Deserialize<'de> for EncodedPoint<Size>
450where
451    Size: ModulusSize,
452{
453    #[cfg(not(feature = "alloc"))]
454    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
455    where
456        D: de::Deserializer<'de>,
457    {
458        use de::Error;
459        <&[u8]>::deserialize(deserializer)
460            .and_then(|slice| Self::from_bytes(slice).map_err(D::Error::custom))
461    }
462
463    #[cfg(feature = "alloc")]
464    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
465    where
466        D: de::Deserializer<'de>,
467    {
468        use de::Error;
469        if deserializer.is_human_readable() {
470            <&str>::deserialize(deserializer)?
471                .parse()
472                .map_err(D::Error::custom)
473        } else {
474            <&[u8]>::deserialize(deserializer)
475                .and_then(|bytes| Self::from_bytes(bytes).map_err(D::Error::custom))
476        }
477    }
478}
479
480/// Enum representing the coordinates of either compressed or uncompressed
481/// SEC1-encoded elliptic curve points.
482#[derive(Copy, Clone, Debug, Eq, PartialEq)]
483pub enum Coordinates<'a, Size: ModulusSize> {
484    /// Identity point (a.k.a. point at infinity)
485    Identity,
486
487    /// Compact curve point
488    Compact {
489        /// x-coordinate
490        x: &'a GenericArray<u8, Size>,
491    },
492
493    /// Compressed curve point
494    Compressed {
495        /// x-coordinate
496        x: &'a GenericArray<u8, Size>,
497
498        /// Is the y-coordinate odd?
499        y_is_odd: bool,
500    },
501
502    /// Uncompressed curve point
503    Uncompressed {
504        /// x-coordinate
505        x: &'a GenericArray<u8, Size>,
506
507        /// y-coordinate
508        y: &'a GenericArray<u8, Size>,
509    },
510}
511
512impl<'a, Size: ModulusSize> Coordinates<'a, Size> {
513    /// Get the tag octet needed to encode this set of [`Coordinates`]
514    pub fn tag(&self) -> Tag {
515        match self {
516            Coordinates::Compact { .. } => Tag::Compact,
517            Coordinates::Compressed { y_is_odd, .. } => {
518                if *y_is_odd {
519                    Tag::CompressedOddY
520                } else {
521                    Tag::CompressedEvenY
522                }
523            }
524            Coordinates::Identity => Tag::Identity,
525            Coordinates::Uncompressed { .. } => Tag::Uncompressed,
526        }
527    }
528}
529
530/// Tag byte used by the `Elliptic-Curve-Point-to-Octet-String` encoding.
531#[derive(Copy, Clone, Debug, Eq, PartialEq)]
532#[repr(u8)]
533pub enum Tag {
534    /// Identity point (`0x00`)
535    Identity = 0,
536
537    /// Compressed point with even y-coordinate (`0x02`)
538    CompressedEvenY = 2,
539
540    /// Compressed point with odd y-coordinate (`0x03`)
541    CompressedOddY = 3,
542
543    /// Uncompressed point (`0x04`)
544    Uncompressed = 4,
545
546    /// Compact point (`0x05`)
547    Compact = 5,
548}
549
550impl Tag {
551    /// Parse a tag value from a byte
552    pub fn from_u8(byte: u8) -> Result<Self> {
553        match byte {
554            0 => Ok(Tag::Identity),
555            2 => Ok(Tag::CompressedEvenY),
556            3 => Ok(Tag::CompressedOddY),
557            4 => Ok(Tag::Uncompressed),
558            5 => Ok(Tag::Compact),
559            _ => Err(Error::PointEncoding),
560        }
561    }
562
563    /// Is this point compact?
564    pub fn is_compact(self) -> bool {
565        matches!(self, Tag::Compact)
566    }
567
568    /// Is this point compressed?
569    pub fn is_compressed(self) -> bool {
570        matches!(self, Tag::CompressedEvenY | Tag::CompressedOddY)
571    }
572
573    /// Is this point the identity point?
574    pub fn is_identity(self) -> bool {
575        self == Tag::Identity
576    }
577
578    /// Compute the expected total message length for a message prefixed
579    /// with this tag (including the tag byte), given the field element size
580    /// (in bytes) for a particular elliptic curve.
581    pub fn message_len(self, field_element_size: usize) -> usize {
582        1 + match self {
583            Tag::Identity => 0,
584            Tag::CompressedEvenY | Tag::CompressedOddY => field_element_size,
585            Tag::Uncompressed => field_element_size * 2,
586            Tag::Compact => field_element_size,
587        }
588    }
589
590    /// Compress the given y-coordinate, returning a `Tag::Compressed*` value
591    fn compress_y(y: &[u8]) -> Self {
592        // Is the y-coordinate odd in the SEC1 sense: `self mod 2 == 1`?
593        if y.as_ref().last().expect("empty y-coordinate") & 1 == 1 {
594            Tag::CompressedOddY
595        } else {
596            Tag::CompressedEvenY
597        }
598    }
599}
600
601impl From<Tag> for u8 {
602    fn from(tag: Tag) -> u8 {
603        tag as u8
604    }
605}
606
607#[cfg(test)]
608mod tests {
609    use super::{Coordinates, Tag};
610    use core::str::FromStr;
611    use generic_array::{typenum::U32, GenericArray};
612    use hex_literal::hex;
613
614    #[cfg(feature = "alloc")]
615    use alloc::string::ToString;
616
617    #[cfg(feature = "subtle")]
618    use subtle::ConditionallySelectable;
619
620    type EncodedPoint = super::EncodedPoint<U32>;
621
622    /// Identity point
623    const IDENTITY_BYTES: [u8; 1] = [0];
624
625    /// Example uncompressed point
626    const UNCOMPRESSED_BYTES: [u8; 65] = hex!("0411111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222222222");
627
628    /// Example compressed point: `UNCOMPRESSED_BYTES` after point compression
629    const COMPRESSED_BYTES: [u8; 33] =
630        hex!("021111111111111111111111111111111111111111111111111111111111111111");
631
632    #[test]
633    fn decode_compressed_point() {
634        // Even y-coordinate
635        let compressed_even_y_bytes =
636            hex!("020100000000000000000000000000000000000000000000000000000000000000");
637
638        let compressed_even_y = EncodedPoint::from_bytes(&compressed_even_y_bytes[..]).unwrap();
639
640        assert!(compressed_even_y.is_compressed());
641        assert_eq!(compressed_even_y.tag(), Tag::CompressedEvenY);
642        assert_eq!(compressed_even_y.len(), 33);
643        assert_eq!(compressed_even_y.as_bytes(), &compressed_even_y_bytes[..]);
644
645        assert_eq!(
646            compressed_even_y.coordinates(),
647            Coordinates::Compressed {
648                x: &hex!("0100000000000000000000000000000000000000000000000000000000000000").into(),
649                y_is_odd: false
650            }
651        );
652
653        assert_eq!(
654            compressed_even_y.x().unwrap(),
655            &hex!("0100000000000000000000000000000000000000000000000000000000000000").into()
656        );
657        assert_eq!(compressed_even_y.y(), None);
658
659        // Odd y-coordinate
660        let compressed_odd_y_bytes =
661            hex!("030200000000000000000000000000000000000000000000000000000000000000");
662
663        let compressed_odd_y = EncodedPoint::from_bytes(&compressed_odd_y_bytes[..]).unwrap();
664
665        assert!(compressed_odd_y.is_compressed());
666        assert_eq!(compressed_odd_y.tag(), Tag::CompressedOddY);
667        assert_eq!(compressed_odd_y.len(), 33);
668        assert_eq!(compressed_odd_y.as_bytes(), &compressed_odd_y_bytes[..]);
669
670        assert_eq!(
671            compressed_odd_y.coordinates(),
672            Coordinates::Compressed {
673                x: &hex!("0200000000000000000000000000000000000000000000000000000000000000").into(),
674                y_is_odd: true
675            }
676        );
677
678        assert_eq!(
679            compressed_odd_y.x().unwrap(),
680            &hex!("0200000000000000000000000000000000000000000000000000000000000000").into()
681        );
682        assert_eq!(compressed_odd_y.y(), None);
683    }
684
685    #[test]
686    fn decode_uncompressed_point() {
687        let uncompressed_point = EncodedPoint::from_bytes(&UNCOMPRESSED_BYTES[..]).unwrap();
688
689        assert!(!uncompressed_point.is_compressed());
690        assert_eq!(uncompressed_point.tag(), Tag::Uncompressed);
691        assert_eq!(uncompressed_point.len(), 65);
692        assert_eq!(uncompressed_point.as_bytes(), &UNCOMPRESSED_BYTES[..]);
693
694        assert_eq!(
695            uncompressed_point.coordinates(),
696            Coordinates::Uncompressed {
697                x: &hex!("1111111111111111111111111111111111111111111111111111111111111111").into(),
698                y: &hex!("2222222222222222222222222222222222222222222222222222222222222222").into()
699            }
700        );
701
702        assert_eq!(
703            uncompressed_point.x().unwrap(),
704            &hex!("1111111111111111111111111111111111111111111111111111111111111111").into()
705        );
706        assert_eq!(
707            uncompressed_point.y().unwrap(),
708            &hex!("2222222222222222222222222222222222222222222222222222222222222222").into()
709        );
710    }
711
712    #[test]
713    fn decode_identity() {
714        let identity_point = EncodedPoint::from_bytes(&IDENTITY_BYTES[..]).unwrap();
715        assert!(identity_point.is_identity());
716        assert_eq!(identity_point.tag(), Tag::Identity);
717        assert_eq!(identity_point.len(), 1);
718        assert_eq!(identity_point.as_bytes(), &IDENTITY_BYTES[..]);
719        assert_eq!(identity_point.coordinates(), Coordinates::Identity);
720        assert_eq!(identity_point.x(), None);
721        assert_eq!(identity_point.y(), None);
722    }
723
724    #[test]
725    fn decode_invalid_tag() {
726        let mut compressed_bytes = COMPRESSED_BYTES.clone();
727        let mut uncompressed_bytes = UNCOMPRESSED_BYTES.clone();
728
729        for bytes in &mut [&mut compressed_bytes[..], &mut uncompressed_bytes[..]] {
730            for tag in 0..=0xFF {
731                // valid tags
732                if tag == 2 || tag == 3 || tag == 4 || tag == 5 {
733                    continue;
734                }
735
736                (*bytes)[0] = tag;
737                let decode_result = EncodedPoint::from_bytes(&*bytes);
738                assert!(decode_result.is_err());
739            }
740        }
741    }
742
743    #[test]
744    fn decode_truncated_point() {
745        for bytes in &[&COMPRESSED_BYTES[..], &UNCOMPRESSED_BYTES[..]] {
746            for len in 0..bytes.len() {
747                let decode_result = EncodedPoint::from_bytes(&bytes[..len]);
748                assert!(decode_result.is_err());
749            }
750        }
751    }
752
753    #[test]
754    fn from_untagged_point() {
755        let untagged_bytes = hex!("11111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222222222");
756        let uncompressed_point =
757            EncodedPoint::from_untagged_bytes(GenericArray::from_slice(&untagged_bytes[..]));
758        assert_eq!(uncompressed_point.as_bytes(), &UNCOMPRESSED_BYTES[..]);
759    }
760
761    #[test]
762    fn from_affine_coordinates() {
763        let x = hex!("1111111111111111111111111111111111111111111111111111111111111111");
764        let y = hex!("2222222222222222222222222222222222222222222222222222222222222222");
765
766        let uncompressed_point = EncodedPoint::from_affine_coordinates(&x.into(), &y.into(), false);
767        assert_eq!(uncompressed_point.as_bytes(), &UNCOMPRESSED_BYTES[..]);
768
769        let compressed_point = EncodedPoint::from_affine_coordinates(&x.into(), &y.into(), true);
770        assert_eq!(compressed_point.as_bytes(), &COMPRESSED_BYTES[..]);
771    }
772
773    #[test]
774    fn compress() {
775        let uncompressed_point = EncodedPoint::from_bytes(&UNCOMPRESSED_BYTES[..]).unwrap();
776        let compressed_point = uncompressed_point.compress();
777        assert_eq!(compressed_point.as_bytes(), &COMPRESSED_BYTES[..]);
778    }
779
780    #[cfg(feature = "subtle")]
781    #[test]
782    fn conditional_select() {
783        let a = EncodedPoint::from_bytes(&COMPRESSED_BYTES[..]).unwrap();
784        let b = EncodedPoint::from_bytes(&UNCOMPRESSED_BYTES[..]).unwrap();
785
786        let a_selected = EncodedPoint::conditional_select(&a, &b, 0.into());
787        assert_eq!(a, a_selected);
788
789        let b_selected = EncodedPoint::conditional_select(&a, &b, 1.into());
790        assert_eq!(b, b_selected);
791    }
792
793    #[test]
794    fn identity() {
795        let identity_point = EncodedPoint::identity();
796        assert_eq!(identity_point.tag(), Tag::Identity);
797        assert_eq!(identity_point.len(), 1);
798        assert_eq!(identity_point.as_bytes(), &IDENTITY_BYTES[..]);
799
800        // identity is default
801        assert_eq!(identity_point, EncodedPoint::default());
802    }
803
804    #[test]
805    fn decode_hex() {
806        let point = EncodedPoint::from_str(
807            "021111111111111111111111111111111111111111111111111111111111111111",
808        )
809        .unwrap();
810        assert_eq!(point.as_bytes(), COMPRESSED_BYTES);
811    }
812
813    #[cfg(feature = "alloc")]
814    #[test]
815    fn to_bytes() {
816        let uncompressed_point = EncodedPoint::from_bytes(&UNCOMPRESSED_BYTES[..]).unwrap();
817        assert_eq!(&*uncompressed_point.to_bytes(), &UNCOMPRESSED_BYTES[..]);
818    }
819
820    #[cfg(feature = "alloc")]
821    #[test]
822    fn to_string() {
823        let point = EncodedPoint::from_bytes(&COMPRESSED_BYTES[..]).unwrap();
824        assert_eq!(
825            point.to_string(),
826            "021111111111111111111111111111111111111111111111111111111111111111"
827        );
828    }
829}