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.