schnorrkel/
points.rs

1// -*- mode: rust; -*-
2//
3// This file is part of schnorrkel.
4// Copyright (c) 2019 Web 3 Foundation
5// See LICENSE for licensing information.
6//
7// Authors:
8// - Jeff Burdges <jeff@web3.foundation>
9
10//! ### Ristretto point tooling
11//!
12//! We provide a `RistrettoBoth` type that contains both an uncompressed
13//! `RistrettoPoint` alongside its matching `CompressedRistretto`,
14//! which helps several protocols avoid duplicate ristretto compressions
15//! and/or decompressions.
16
17// We're discussing including some variant in curve25519-dalek directly in
18// https://github.com/dalek-cryptography/curve25519-dalek/pull/220
19
20use core::fmt::{Debug};
21
22use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
23use subtle::{ConstantTimeEq, Choice};
24// use curve25519_dalek::scalar::Scalar;
25
26use crate::errors::{SignatureError, SignatureResult};
27
28/// Compressed Ristretto point length
29pub const RISTRETTO_POINT_LENGTH: usize = 32;
30
31/// A `RistrettoBoth` contains both an uncompressed `RistrettoPoint`
32/// as well as the corresponding `CompressedRistretto`.  It provides
33/// a convenient middle ground for protocols that both hash compressed
34/// points to derive scalars for use with uncompressed points.
35#[derive(Copy, Clone, Default, Eq)] // PartialEq optimized below
36pub struct RistrettoBoth {
37    compressed: CompressedRistretto,
38    point: RistrettoPoint,
39}
40
41impl Debug for RistrettoBoth {
42    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
43        write!(f, "RistrettoPoint( {:?} )", self.compressed)
44    }
45}
46
47impl ConstantTimeEq for RistrettoBoth {
48    fn ct_eq(&self, other: &RistrettoBoth) -> Choice {
49        self.compressed.ct_eq(&other.compressed)
50    }
51}
52
53#[rustfmt::skip]
54impl RistrettoBoth {
55    const DESCRIPTION: &'static str = "A ristretto point represented as a 32-byte compressed point";
56
57    // I dislike getter methods, and prefer direct field access, but doing
58    // getters here permits the fields being private, and gives us faster
59    // equality comparisons.
60
61    /// Access the compressed Ristretto form
62    pub fn as_compressed(&self) -> &CompressedRistretto { &self.compressed }
63
64    /// Extract the compressed Ristretto form
65    pub fn into_compressed(self) -> CompressedRistretto { self.compressed }
66
67    /// Access the point form
68    pub fn as_point(&self) -> &RistrettoPoint { &self.point }
69
70    /// Extract the point form
71    pub fn into_point(self) -> RistrettoPoint { self.point }
72
73    /// Decompress into the `RistrettoBoth` format that also retains the
74    /// compressed form.
75    pub fn from_compressed(compressed: CompressedRistretto) -> SignatureResult<RistrettoBoth> {
76        Ok(RistrettoBoth {
77            point: compressed.decompress().ok_or(SignatureError::PointDecompressionError) ?,
78            compressed,
79        })
80    }
81
82    /// Compress into the `RistrettoBoth` format that also retains the
83    /// uncompressed form.
84    pub fn from_point(point: RistrettoPoint) -> RistrettoBoth {
85        RistrettoBoth {
86            compressed: point.compress(),
87            point,
88        }
89    }
90
91    /// Convert this public key to a byte array.
92    #[inline]
93    pub fn to_bytes(&self) -> [u8; RISTRETTO_POINT_LENGTH] {
94        self.compressed.to_bytes()
95    }
96
97    /// Construct a `RistrettoBoth` from a slice of bytes.
98    ///
99    /// # Example
100    ///
101    /// ```
102    /// use schnorrkel::points::RistrettoBoth;
103    /// use schnorrkel::PUBLIC_KEY_LENGTH;
104    /// use schnorrkel::SignatureError;
105    ///
106    /// # fn doctest() -> Result<RistrettoBoth, SignatureError> {
107    /// let public_key_bytes: [u8; PUBLIC_KEY_LENGTH] = [
108    ///    215,  90, 152,   1, 130, 177,  10, 183, 213,  75, 254, 211, 201, 100,   7,  58,
109    ///     14, 225, 114, 243, 218, 166,  35,  37, 175,   2,  26, 104, 247,   7,   81, 26];
110    ///
111    /// let public_key = RistrettoBoth::from_bytes(&public_key_bytes)?;
112    /// #
113    /// # Ok(public_key)
114    /// # }
115    /// #
116    /// # fn main() {
117    /// #     doctest();
118    /// # }
119    /// ```
120    ///
121    /// # Returns
122    ///
123    /// A `Result` whose okay value is an EdDSA `RistrettoBoth` or whose error value
124    /// is an `SignatureError` describing the error that occurred.
125    #[inline]
126    pub fn from_bytes(bytes: &[u8]) -> SignatureResult<RistrettoBoth> {
127        RistrettoBoth::from_bytes_ser("RistrettoPoint",RistrettoBoth::DESCRIPTION,bytes)
128    }
129
130    /// Variant of `RistrettoBoth::from_bytes` that propagates more informative errors.
131    #[inline]
132    pub fn from_bytes_ser(name: &'static str, description: &'static str, bytes: &[u8]) -> SignatureResult<RistrettoBoth> {
133        if bytes.len() != RISTRETTO_POINT_LENGTH {
134            return Err(SignatureError::BytesLengthError{
135                name, description, length: RISTRETTO_POINT_LENGTH,
136            });
137        }
138        let mut compressed = CompressedRistretto([0u8; RISTRETTO_POINT_LENGTH]);
139        compressed.0.copy_from_slice(&bytes[..32]);
140        RistrettoBoth::from_compressed(compressed)
141    }
142}
143
144serde_boilerplate!(RistrettoBoth);
145
146/// We hide fields largely so that only comparing the compressed forms works.
147impl PartialEq<Self> for RistrettoBoth {
148    fn eq(&self, other: &Self) -> bool {
149        let r = self.compressed.eq(&other.compressed);
150        debug_assert_eq!(r, self.point.eq(&other.point));
151        r
152    }
153}
154
155impl PartialOrd<RistrettoBoth> for RistrettoBoth {
156    fn partial_cmp(&self, other: &RistrettoBoth) -> Option<::core::cmp::Ordering> {
157        Some(self.cmp(other))
158    }
159}
160
161impl Ord for RistrettoBoth {
162    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
163        self.compressed.0.cmp(&other.compressed.0)
164    }
165}
166
167impl core::hash::Hash for RistrettoBoth {
168    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
169        self.compressed.0.hash(state);
170    }
171}