/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 | | } |