Coverage Report

Created: 2025-07-23 06:36

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