monero_ed25519/
compressed_point.rs

1use core::{
2  cmp::{Ordering, PartialOrd},
3  hash::{Hasher, Hash},
4};
5use std_shims::io::{self, Read, Write};
6
7use subtle::{Choice, ConstantTimeEq};
8use zeroize::Zeroize;
9
10use monero_io::read_bytes;
11
12use crate::Point;
13
14/// A compressed Ed25519 point.
15///
16/// [`curve25519_dalek::edwards::CompressedEdwardsY`], the [`curve25519_dalek`] version of this
17/// struct, exposes a [`curve25519_dalek::edwards::CompressedEdwardsY::decompress`] function that
18/// does not check the point is canonically encoded. This struct exposes a
19/// [`CompressedPoint::decompress`] function that does check the point is canonically encoded. For
20/// the exact details, please check its documentation.
21///
22/// The implementations of [`PartialOrd`], [`Ord`], and [`Hash`] are not guaranteed to execute in
23/// constant time.
24#[derive(Clone, Copy, Eq, Debug, Zeroize)]
25pub struct CompressedPoint([u8; 32]);
26
27impl ConstantTimeEq for CompressedPoint {
28  fn ct_eq(&self, other: &Self) -> Choice {
29    self.0.ct_eq(&other.0)
30  }
31}
32impl PartialEq for CompressedPoint {
33  /// This defers to `ConstantTimeEq::ct_eq`.
34  fn eq(&self, other: &Self) -> bool {
35    bool::from(self.ct_eq(other))
36  }
37}
38
39impl PartialOrd for CompressedPoint {
40  /// This executes in variable time.
41  fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
42    Some(self.cmp(other))
43  }
44}
45
46impl Ord for CompressedPoint {
47  /// This executes in variable time.
48  fn cmp(&self, other: &Self) -> Ordering {
49    self.0.cmp(&other.0)
50  }
51}
52
53impl Hash for CompressedPoint {
54  /// This executes in variable time.
55  fn hash<H: Hasher>(&self, hasher: &mut H) {
56    self.0.hash::<H>(hasher);
57  }
58}
59
60impl CompressedPoint {
61  /// The encoding of the identity point.
62  #[rustfmt::skip]
63  pub const IDENTITY: Self = Self([
64    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
65  ]);
66  /// The `G` generator for the Monero protocol.
67  pub const G: Self = Self(curve25519_dalek::constants::ED25519_BASEPOINT_COMPRESSED.to_bytes());
68  /// The `H` generator for the Monero protocol.
69  #[rustfmt::skip]
70  pub const H: Self = Self([
71    139, 101,  89, 112,  21,  55, 153, 175,  42, 234, 220, 159, 241, 173, 208, 234,
72    108, 114,  81, 213,  65,  84, 207, 169,  44,  23,  58,  13, 211, 156,  31, 148,
73  ]);
74
75  /// Read a [`CompressedPoint`] without checking if this point can be decompressed.
76  ///
77  /// This may run in variable time.
78  pub fn read<R: Read>(r: &mut R) -> io::Result<CompressedPoint> {
79    Ok(CompressedPoint(read_bytes(r)?))
80  }
81
82  /// Write a compressed point.
83  ///
84  /// This may run in variable time.
85  pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
86    w.write_all(&self.0)
87  }
88
89  /// Returns the raw bytes of the compressed point.
90  ///
91  /// This does not ensure these bytes represent a point of any validity, with no guarantees on
92  /// their contents.
93  pub fn to_bytes(&self) -> [u8; 32] {
94    self.0
95  }
96
97  /// Decompress a canonically-encoded Ed25519 point.
98  ///
99  /// Ed25519 is of order `8 * l`. This function ensures each of those `8 * l` points have a
100  /// singular encoding by checking points aren't encoded with an unreduced field element,
101  /// and aren't negative when the negative is equivalent (0 == -0).
102  ///
103  /// Since this decodes an Ed25519 point, it does not check the point is in the prime-order
104  /// subgroup. Torsioned points do have a canonical encoding, and only aren't canonical when
105  /// considered in relation to the prime-order subgroup.
106  pub fn decompress(&self) -> Option<Point> {
107    // TODO: Instead of re-compressing, check the edge cases with optimized algorithms
108    curve25519_dalek::edwards::CompressedEdwardsY(self.0)
109      .decompress()
110      // Ban points which are either unreduced or -0
111      .filter(|point| point.compress().to_bytes() == self.0)
112      .map(Point::from)
113  }
114}
115
116impl From<[u8; 32]> for CompressedPoint {
117  fn from(value: [u8; 32]) -> Self {
118    Self(value)
119  }
120}
121
122// This does not implement `From<CompressedPoint> for [u8; 32]` to ensure
123// `CompressedPoint::to_bytes`, with its docstring, is the source of truth.