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