use core::fmt::Debug; use std::io::Read; use thiserror::Error; use rand_core::{RngCore, CryptoRng}; use zeroize::Zeroize; use ff::{PrimeField, PrimeFieldBits}; use group::{Group, GroupOps, GroupEncoding, prime::PrimeGroup}; #[cfg(any(test, feature = "dalek"))] mod dalek; #[cfg(any(test, feature = "ristretto"))] pub use dalek::{Ristretto, IetfRistrettoHram}; #[cfg(feature = "ed25519")] pub use dalek::{Ed25519, IetfEd25519Hram}; #[cfg(feature = "kp256")] mod kp256; #[cfg(feature = "secp256k1")] pub use kp256::{Secp256k1, IetfSecp256k1Hram}; #[cfg(feature = "p256")] pub use kp256::{P256, IetfP256Hram}; /// Set of errors for curve-related operations, namely encoding and decoding #[derive(Clone, Error, Debug)] pub enum CurveError { #[error("invalid scalar")] InvalidScalar, #[error("invalid point")] InvalidPoint, } /// Unified trait to manage a field/group // This should be moved into its own crate if the need for generic cryptography over ff/group // continues, which is the exact reason ff/group exists (to provide a generic interface) // elliptic-curve exists, yet it doesn't really serve the same role, nor does it use &[u8]/Vec // It uses GenericArray which will hopefully be deprecated as Rust evolves and doesn't offer enough // advantages in the modern day to be worth the hassle -- Kayaba pub trait Curve: Clone + Copy + PartialEq + Eq + Debug + Zeroize { /// Scalar field element type // This is available via G::Scalar yet `C::G::Scalar` is ambiguous, forcing horrific accesses type F: PrimeField + PrimeFieldBits + Zeroize; /// Group element type type G: Group + GroupOps + PrimeGroup + Zeroize; /// ID for this curve const ID: &'static [u8]; /// Generator for the group // While group does provide this in its API, privacy coins may want to use a custom basepoint fn generator() -> Self::G; /// Hash the given dst and data to a byte vector. Used to instantiate H4 and H5. fn hash_to_vec(dst: &[u8], data: &[u8]) -> Vec; /// Field element from hash. Used during key gen and by other crates under Serai as a general /// utility. Used to instantiate H1 and H3. #[allow(non_snake_case)] fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F; /// Hash the message for the binding factor. H4 from the IETF draft fn hash_msg(msg: &[u8]) -> Vec { Self::hash_to_vec(b"msg", msg) } /// Hash the commitments for the binding factor. H5 from the IETF draft fn hash_commitments(commitments: &[u8]) -> Vec { Self::hash_to_vec(b"com", commitments) } /// Hash the commitments and message to calculate the binding factor. H1 from the IETF draft fn hash_binding_factor(binding: &[u8]) -> Self::F { Self::hash_to_F(b"rho", binding) } /// Securely generate a random nonce. H3 from the IETF draft fn random_nonce(mut secret: Self::F, rng: &mut R) -> Self::F { let mut seed = vec![0; 32]; rng.fill_bytes(&mut seed); let mut repr = secret.to_repr(); secret.zeroize(); seed.extend(repr.as_ref()); for i in repr.as_mut() { i.zeroize(); } let res = Self::hash_to_F(b"nonce", &seed); seed.zeroize(); res } #[allow(non_snake_case)] fn F_len() -> usize { ::Repr::default().as_ref().len() } #[allow(non_snake_case)] fn G_len() -> usize { ::Repr::default().as_ref().len() } #[allow(non_snake_case)] fn read_F(r: &mut R) -> Result { let mut encoding = ::Repr::default(); r.read_exact(encoding.as_mut()).map_err(|_| CurveError::InvalidScalar)?; // ff mandates this is canonical let res = Option::::from(Self::F::from_repr(encoding)).ok_or(CurveError::InvalidScalar); for b in encoding.as_mut() { b.zeroize(); } res } #[allow(non_snake_case)] fn read_G(r: &mut R) -> Result { let mut encoding = ::Repr::default(); r.read_exact(encoding.as_mut()).map_err(|_| CurveError::InvalidPoint)?; let point = Option::::from(Self::G::from_bytes(&encoding)).ok_or(CurveError::InvalidPoint)?; // Ban the identity, per the FROST spec, and non-canonical points if (point.is_identity().into()) || (point.to_bytes().as_ref() != encoding.as_ref()) { Err(CurveError::InvalidPoint)?; } Ok(point) } }