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> from_bytes(input: impl AsRef<[u8]>) -> Result<Self>89 pub fn from_bytes(input: impl AsRef<[u8]>) -> Result<Self> { 90 let input = input.as_ref(); 91 92 // Validate tag 93 let tag = input 94 .first() 95 .cloned() 96 .ok_or(Error::PointEncoding) 97 .and_then(Tag::from_u8)?; 98 99 // Validate length 100 let expected_len = tag.message_len(Size::to_usize()); 101 102 if input.len() != expected_len { 103 return Err(Error::PointEncoding); 104 } 105 106 let mut bytes = GenericArray::default(); 107 bytes[..expected_len].copy_from_slice(input); 108 Ok(Self { bytes }) 109 } 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). from_untagged_bytes(bytes: &GenericArray<u8, Size::UntaggedPointSize>) -> Self114 pub fn from_untagged_bytes(bytes: &GenericArray<u8, Size::UntaggedPointSize>) -> Self { 115 let (x, y) = bytes.split_at(Size::to_usize()); 116 Self::from_affine_coordinates(x.into(), y.into(), false) 117 } 118 119 /// Encode an elliptic curve point from big endian serialized coordinates 120 /// (with optional point compression) from_affine_coordinates( x: &GenericArray<u8, Size>, y: &GenericArray<u8, Size>, compress: bool, ) -> Self121 pub fn from_affine_coordinates( 122 x: &GenericArray<u8, Size>, 123 y: &GenericArray<u8, Size>, 124 compress: bool, 125 ) -> Self { 126 let tag = if compress { 127 Tag::compress_y(y.as_slice()) 128 } else { 129 Tag::Uncompressed 130 }; 131 132 let mut bytes = GenericArray::default(); 133 bytes[0] = tag.into(); 134 bytes[1..(Size::to_usize() + 1)].copy_from_slice(x); 135 136 if !compress { 137 bytes[(Size::to_usize() + 1)..].copy_from_slice(y); 138 } 139 140 Self { bytes } 141 } 142 143 /// Return [`EncodedPoint`] representing the additive identity 144 /// (a.k.a. point at infinity) identity() -> Self145 pub fn identity() -> Self { 146 Self::default() 147 } 148 149 /// Get the length of the encoded point in bytes len(&self) -> usize150 pub fn len(&self) -> usize { 151 self.tag().message_len(Size::to_usize()) 152 } 153 154 /// Get byte slice containing the serialized [`EncodedPoint`]. as_bytes(&self) -> &[u8]155 pub fn as_bytes(&self) -> &[u8] { 156 &self.bytes[..self.len()] 157 } 158 159 /// Get boxed byte slice containing the serialized [`EncodedPoint`] 160 #[cfg(feature = "alloc")] to_bytes(&self) -> Box<[u8]>161 pub fn to_bytes(&self) -> Box<[u8]> { 162 self.as_bytes().to_vec().into_boxed_slice() 163 } 164 165 /// Is this [`EncodedPoint`] compact? is_compact(&self) -> bool166 pub fn is_compact(&self) -> bool { 167 self.tag().is_compact() 168 } 169 170 /// Is this [`EncodedPoint`] compressed? is_compressed(&self) -> bool171 pub fn is_compressed(&self) -> bool { 172 self.tag().is_compressed() 173 } 174 175 /// Is this [`EncodedPoint`] the additive identity? (a.k.a. point at infinity) is_identity(&self) -> bool176 pub fn is_identity(&self) -> bool { 177 self.tag().is_identity() 178 } 179 180 /// Compress this [`EncodedPoint`], returning a new [`EncodedPoint`]. compress(&self) -> Self181 pub fn compress(&self) -> Self { 182 match self.coordinates() { 183 Coordinates::Compressed { .. } 184 | Coordinates::Compact { .. } 185 | Coordinates::Identity => self.clone(), 186 Coordinates::Uncompressed { x, y } => Self::from_affine_coordinates(x, y, true), 187 } 188 } 189 190 /// Get the SEC1 tag for this [`EncodedPoint`] tag(&self) -> Tag191 pub fn tag(&self) -> Tag { 192 // Tag is ensured valid by the constructor 193 Tag::from_u8(self.bytes[0]).expect("invalid tag") 194 } 195 196 /// Get the [`Coordinates`] for this [`EncodedPoint`]. 197 #[inline] coordinates(&self) -> Coordinates<'_, Size>198 pub fn coordinates(&self) -> Coordinates<'_, Size> { 199 if self.is_identity() { 200 return Coordinates::Identity; 201 } 202 203 let (x, y) = self.bytes[1..].split_at(Size::to_usize()); 204 205 if self.is_compressed() { 206 Coordinates::Compressed { 207 x: x.into(), 208 y_is_odd: self.tag() as u8 & 1 == 1, 209 } 210 } else if self.is_compact() { 211 Coordinates::Compact { x: x.into() } 212 } else { 213 Coordinates::Uncompressed { 214 x: x.into(), 215 y: y.into(), 216 } 217 } 218 } 219 220 /// Get the x-coordinate for this [`EncodedPoint`]. 221 /// 222 /// Returns `None` if this point is the identity point. x(&self) -> Option<&GenericArray<u8, Size>>223 pub fn x(&self) -> Option<&GenericArray<u8, Size>> { 224 match self.coordinates() { 225 Coordinates::Identity => None, 226 Coordinates::Compressed { x, .. } => Some(x), 227 Coordinates::Uncompressed { x, .. } => Some(x), 228 Coordinates::Compact { x } => Some(x), 229 } 230 } 231 232 /// Get the y-coordinate for this [`EncodedPoint`]. 233 /// 234 /// Returns `None` if this point is compressed or the identity point. y(&self) -> Option<&GenericArray<u8, Size>>235 pub fn y(&self) -> Option<&GenericArray<u8, Size>> { 236 match self.coordinates() { 237 Coordinates::Compressed { .. } | Coordinates::Identity => None, 238 Coordinates::Uncompressed { y, .. } => Some(y), 239 Coordinates::Compact { .. } => None, 240 } 241 } 242 } 243 244 impl<Size> AsRef<[u8]> for EncodedPoint<Size> 245 where 246 Size: ModulusSize, 247 { 248 #[inline] as_ref(&self) -> &[u8]249 fn as_ref(&self) -> &[u8] { 250 self.as_bytes() 251 } 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 { conditional_select(a: &Self, b: &Self, choice: Choice) -> Self260 fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { 261 let mut bytes = GenericArray::default(); 262 263 for (i, byte) in bytes.iter_mut().enumerate() { 264 *byte = u8::conditional_select(&a.bytes[i], &b.bytes[i], choice); 265 } 266 267 Self { bytes } 268 } 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 { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 283 write!(f, "EncodedPoint({:?})", self.coordinates()) 284 } 285 } 286 287 impl<Size: ModulusSize> Eq for EncodedPoint<Size> {} 288 289 impl<Size> PartialEq for EncodedPoint<Size> 290 where 291 Size: ModulusSize, 292 { eq(&self, other: &Self) -> bool293 fn eq(&self, other: &Self) -> bool { 294 self.as_bytes() == other.as_bytes() 295 } 296 } 297 298 impl<Size> Hash for EncodedPoint<Size> 299 where 300 Size: ModulusSize, 301 { hash<H: Hasher>(&self, state: &mut H)302 fn hash<H: Hasher>(&self, state: &mut H) { 303 self.as_bytes().hash(state) 304 } 305 } 306 307 impl<Size: ModulusSize> PartialOrd for EncodedPoint<Size> 308 where 309 Size: ModulusSize, 310 { partial_cmp(&self, other: &Self) -> Option<Ordering>311 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 312 Some(self.cmp(other)) 313 } 314 } 315 316 impl<Size: ModulusSize> Ord for EncodedPoint<Size> 317 where 318 Size: ModulusSize, 319 { cmp(&self, other: &Self) -> Ordering320 fn cmp(&self, other: &Self) -> Ordering { 321 self.as_bytes().cmp(other.as_bytes()) 322 } 323 } 324 325 impl<Size: ModulusSize> TryFrom<&[u8]> for EncodedPoint<Size> 326 where 327 Size: ModulusSize, 328 { 329 type Error = Error; 330 try_from(bytes: &[u8]) -> Result<Self>331 fn try_from(bytes: &[u8]) -> Result<Self> { 332 Self::from_bytes(bytes) 333 } 334 } 335 336 #[cfg(feature = "zeroize")] 337 impl<Size> Zeroize for EncodedPoint<Size> 338 where 339 Size: ModulusSize, 340 { zeroize(&mut self)341 fn zeroize(&mut self) { 342 self.bytes.zeroize(); 343 *self = Self::identity(); 344 } 345 } 346 347 impl<Size> fmt::Display for EncodedPoint<Size> 348 where 349 Size: ModulusSize, 350 { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result351 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 352 write!(f, "{:X}", self) 353 } 354 } 355 356 impl<Size> fmt::LowerHex for EncodedPoint<Size> 357 where 358 Size: ModulusSize, 359 { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 361 write!(f, "{:x}", HexDisplay(self.as_bytes())) 362 } 363 } 364 365 impl<Size> fmt::UpperHex for EncodedPoint<Size> 366 where 367 Size: ModulusSize, 368 { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result369 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 370 write!(f, "{:X}", HexDisplay(self.as_bytes())) 371 } 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 from_str(hex: &str) -> Result<Self>384 fn from_str(hex: &str) -> Result<Self> { 385 let mut buf = GenericArray::<u8, Size::UncompressedPointSize>::default(); 386 base16ct::mixed::decode(hex, &mut buf) 387 .map_err(|_| Error::PointEncoding) 388 .and_then(Self::from_bytes) 389 } 390 } 391 392 #[cfg(feature = "serde")] 393 impl<Size> Serialize for EncodedPoint<Size> 394 where 395 Size: ModulusSize, 396 { serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> where S: ser::Serializer,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 { deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error> where D: de::Deserializer<'de>,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`] tag(&self) -> Tag453 pub fn tag(&self) -> Tag { 454 match self { 455 Coordinates::Compact { .. } => Tag::Compact, 456 Coordinates::Compressed { y_is_odd, .. } => { 457 if *y_is_odd { 458 Tag::CompressedOddY 459 } else { 460 Tag::CompressedEvenY 461 } 462 } 463 Coordinates::Identity => Tag::Identity, 464 Coordinates::Uncompressed { .. } => Tag::Uncompressed, 465 } 466 } 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 from_u8(byte: u8) -> Result<Self>491 pub fn from_u8(byte: u8) -> Result<Self> { 492 match byte { 493 0 => Ok(Tag::Identity), 494 2 => Ok(Tag::CompressedEvenY), 495 3 => Ok(Tag::CompressedOddY), 496 4 => Ok(Tag::Uncompressed), 497 5 => Ok(Tag::Compact), 498 _ => Err(Error::PointEncoding), 499 } 500 } 501 502 /// Is this point compact? is_compact(self) -> bool503 pub fn is_compact(self) -> bool { 504 matches!(self, Tag::Compact) 505 } 506 507 /// Is this point compressed? is_compressed(self) -> bool508 pub fn is_compressed(self) -> bool { 509 matches!(self, Tag::CompressedEvenY | Tag::CompressedOddY) 510 } 511 512 /// Is this point the identity point? is_identity(self) -> bool513 pub fn is_identity(self) -> bool { 514 self == Tag::Identity 515 } 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. message_len(self, field_element_size: usize) -> usize520 pub fn message_len(self, field_element_size: usize) -> usize { 521 1 + match self { 522 Tag::Identity => 0, 523 Tag::CompressedEvenY | Tag::CompressedOddY => field_element_size, 524 Tag::Uncompressed => field_element_size * 2, 525 Tag::Compact => field_element_size, 526 } 527 } 528 529 /// Compress the given y-coordinate, returning a `Tag::Compressed*` value compress_y(y: &[u8]) -> Self530 fn compress_y(y: &[u8]) -> Self { 531 // Is the y-coordinate odd in the SEC1 sense: `self mod 2 == 1`? 532 if y.as_ref().last().expect("empty y-coordinate") & 1 == 1 { 533 Tag::CompressedOddY 534 } else { 535 Tag::CompressedEvenY 536 } 537 } 538 } 539 540 impl TryFrom<u8> for Tag { 541 type Error = Error; 542 try_from(byte: u8) -> Result<Self>543 fn try_from(byte: u8) -> Result<Self> { 544 Self::from_u8(byte) 545 } 546 } 547 548 impl From<Tag> for u8 { from(tag: Tag) -> u8549 fn from(tag: Tag) -> u8 { 550 tag as u8 551 } 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] decode_compressed_point()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] decode_uncompressed_point()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] decode_identity()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] decode_invalid_tag()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] decode_truncated_point()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] from_untagged_point()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] from_affine_coordinates()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] compress()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] conditional_select()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] identity()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] decode_hex()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] to_bytes()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] to_string()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 } 777