//! Low-level interfaces to ed25519 functions //! //! # ⚠️ Warning: Hazmat //! //! These primitives are easy-to-misuse low-level interfaces. //! //! If you are an end user / non-expert in cryptography, **do not use any of these functions**. //! Failure to use them correctly can lead to catastrophic failures including **full private key //! recovery.** // Permit dead code because 1) this module is only public when the `hazmat` feature is set, and 2) // even without `hazmat` we still need this module because this is where `ExpandedSecretKey` is // defined. #![allow(dead_code)] use core::fmt::Debug; use crate::{InternalError, SignatureError}; use curve25519_dalek::scalar::{Scalar, clamp_integer}; use subtle::{Choice, ConstantTimeEq}; #[cfg(feature = "zeroize")] use zeroize::{Zeroize, ZeroizeOnDrop}; // These are used in the functions that are made public when the hazmat feature is set use crate::{Signature, VerifyingKey}; use curve25519_dalek::digest::{Digest, array::typenum::U64}; /// Contains the secret scalar and domain separator used for generating signatures. /// /// This is used internally for signing. /// /// In the usual Ed25519 signing algorithm, `scalar` and `hash_prefix` are defined such that /// `scalar || hash_prefix = H(sk)` where `sk` is the signing key and `H` is SHA-512. /// **WARNING:** Deriving the values for these fields in any other way can lead to full key /// recovery, as documented in [`raw_sign`] and [`raw_sign_prehashed`]. /// /// Instances of this secret are automatically overwritten with zeroes when they fall out of scope. pub struct ExpandedSecretKey { /// The secret scalar used for signing pub scalar: Scalar, /// The domain separator used when hashing the message to generate the pseudorandom `r` value pub hash_prefix: [u8; 32], } impl Debug for ExpandedSecretKey { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("ExpandedSecretKey").finish_non_exhaustive() // avoids printing secrets } } impl ConstantTimeEq for ExpandedSecretKey { fn ct_eq(&self, other: &Self) -> Choice { self.scalar.ct_eq(&other.scalar) & self.hash_prefix.ct_eq(&other.hash_prefix) } } impl PartialEq for ExpandedSecretKey { fn eq(&self, other: &Self) -> bool { self.ct_eq(other).into() } } impl Eq for ExpandedSecretKey {} #[cfg(feature = "zeroize")] impl Drop for ExpandedSecretKey { fn drop(&mut self) { self.scalar.zeroize(); self.hash_prefix.zeroize() } } #[cfg(feature = "zeroize")] impl ZeroizeOnDrop for ExpandedSecretKey {} // Some conversion methods for `ExpandedSecretKey`. The signing methods are defined in // `signing.rs`, since we need them even when `not(feature = "hazmat")` impl ExpandedSecretKey { /// Construct an `ExpandedSecretKey` from an array of 64 bytes. In the spec, the bytes are the /// output of a SHA-512 hash. This clamps the first 32 bytes and uses it as a scalar, and uses /// the second 32 bytes as a domain separator for hashing. pub fn from_bytes(bytes: &[u8; 64]) -> Self { // TODO: Use bytes.split_array_ref once it’s in MSRV. let mut scalar_bytes: [u8; 32] = [0u8; 32]; let mut hash_prefix: [u8; 32] = [0u8; 32]; scalar_bytes.copy_from_slice(&bytes[00..32]); hash_prefix.copy_from_slice(&bytes[32..64]); // For signing, we'll need the integer, clamped, and converted to a Scalar. See // PureEdDSA.keygen in RFC 8032 Appendix A. let scalar = Scalar::from_bytes_mod_order(clamp_integer(scalar_bytes)); ExpandedSecretKey { scalar, hash_prefix, } } /// Construct an `ExpandedSecretKey` from a slice of 64 bytes. /// /// # Returns /// /// A `Result` whose okay value is an EdDSA `ExpandedSecretKey` or whose error value is an /// `SignatureError` describing the error that occurred, namely that the given slice's length /// is not 64. pub fn from_slice(bytes: &[u8]) -> Result { // Try to coerce bytes to a [u8; 64] bytes.try_into().map(Self::from_bytes).map_err(|_| { InternalError::BytesLength { name: "ExpandedSecretKey", length: 64, } .into() }) } } impl TryFrom<&[u8]> for ExpandedSecretKey { type Error = SignatureError; fn try_from(bytes: &[u8]) -> Result { Self::from_slice(bytes) } } /// Compute an ordinary Ed25519 signature over the given message. `CtxDigest` is the digest used to /// calculate the pseudorandomness needed for signing. According to the Ed25519 spec, `CtxDigest = /// Sha512`. /// /// # ⚠️ Cryptographically Unsafe /// /// Do NOT use this function unless you absolutely must. Using the wrong values in /// `ExpandedSecretKey` can leak your signing key. See /// [here](https://github.com/MystenLabs/ed25519-unsafe-libs) for more details on this attack. pub fn raw_sign( esk: &ExpandedSecretKey, message: &[u8], verifying_key: &VerifyingKey, ) -> Signature where CtxDigest: Digest, { esk.raw_sign::(&[message], verifying_key) } /// Compute a signature over the given prehashed message, the Ed25519ph algorithm defined in /// [RFC8032 §5.1][rfc8032]. `MsgDigest` is the digest function used to hash the signed message. /// `CtxDigest` is the digest function used to calculate the pseudorandomness needed for signing. /// According to the Ed25519 spec, `MsgDigest = CtxDigest = Sha512`. /// /// # ⚠️ Cryptographically Unsafe // /// Do NOT use this function unless you absolutely must. Using the wrong values in /// `ExpandedSecretKey` can leak your signing key. See /// [here](https://github.com/MystenLabs/ed25519-unsafe-libs) for more details on this attack. /// /// # Inputs /// /// * `esk` is the [`ExpandedSecretKey`] being used for signing /// * `prehashed_message` is an instantiated hash digest with 512-bits of /// output which has had the message to be signed previously fed into its /// state. /// * `verifying_key` is a [`VerifyingKey`] which corresponds to this secret key. /// * `context` is an optional context string, up to 255 bytes inclusive, /// which may be used to provide additional domain separation. If not /// set, this will default to an empty string. /// /// `scalar` and `hash_prefix` are usually selected such that `scalar || hash_prefix = H(sk)` where /// `sk` is the signing key /// /// # Returns /// /// A `Result` whose `Ok` value is an Ed25519ph [`Signature`] on the /// `prehashed_message` if the context was 255 bytes or less, otherwise /// a `SignatureError`. /// /// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1 #[cfg(feature = "digest")] #[allow(non_snake_case)] pub fn raw_sign_prehashed( esk: &ExpandedSecretKey, prehashed_message: MsgDigest, verifying_key: &VerifyingKey, context: Option<&[u8]>, ) -> Result where MsgDigest: Digest, CtxDigest: Digest, { esk.raw_sign_prehashed::(prehashed_message, verifying_key, context) } /// Compute an ordinary Ed25519 signature, with the message contents provided incrementally by /// updating a digest instance. /// /// The `msg_update` closure provides the message content, updating a hasher argument. It will be /// called twice. This closure MUST leave its hasher in the same state (i.e., must hash the same /// values) after both calls. Otherwise it will produce an invalid signature. /// /// `CtxDigest` is the digest used to calculate the pseudorandomness needed for signing. According /// to the Ed25519 spec, `CtxDigest = Sha512`. /// /// # ⚠️ Cryptographically Unsafe /// /// Do NOT use this function unless you absolutely must. Using the wrong values in /// `ExpandedSecretKey` can leak your signing key. See /// [here](https://github.com/MystenLabs/ed25519-unsafe-libs) for more details on this attack. pub fn raw_sign_byupdate( esk: &ExpandedSecretKey, msg_update: F, verifying_key: &VerifyingKey, ) -> Result where CtxDigest: Digest, F: Fn(&mut CtxDigest) -> Result<(), SignatureError>, { esk.raw_sign_byupdate::(msg_update, verifying_key) } /// The ordinary non-batched Ed25519 verification check, rejecting non-canonical R /// values.`CtxDigest` is the digest used to calculate the pseudorandomness needed for signing. /// According to the Ed25519 spec, `CtxDigest = Sha512`. pub fn raw_verify( vk: &VerifyingKey, message: &[u8], signature: &ed25519::Signature, ) -> Result<(), SignatureError> where CtxDigest: Digest, { vk.raw_verify::(&[message], signature) } /// The batched Ed25519 verification check, rejecting non-canonical R values. `MsgDigest` is the /// digest used to hash the signed message. `CtxDigest` is the digest used to calculate the /// pseudorandomness needed for signing. According to the Ed25519 spec, `MsgDigest = CtxDigest = /// Sha512`. #[cfg(feature = "digest")] #[allow(non_snake_case)] pub fn raw_verify_prehashed( vk: &VerifyingKey, prehashed_message: MsgDigest, context: Option<&[u8]>, signature: &ed25519::Signature, ) -> Result<(), SignatureError> where MsgDigest: Digest, CtxDigest: Digest, { vk.raw_verify_prehashed::(prehashed_message, context, signature) } #[cfg(test)] mod test { #![allow(clippy::unwrap_used)] use super::*; use rand::{CryptoRng, TryRngCore, rngs::OsRng}; // Pick distinct, non-spec 512-bit hash functions for message and sig-context hashing type CtxDigest = blake2::Blake2b512; type MsgDigest = sha3::Sha3_512; impl ExpandedSecretKey { // Make a random expanded secret key for testing purposes. This is NOT how you generate // expanded secret keys IRL. They're the hash of a seed. fn random(rng: &mut R) -> Self { let mut bytes = [0u8; 64]; rng.fill_bytes(&mut bytes); ExpandedSecretKey::from_bytes(&bytes) } } // Check that raw_sign and raw_verify work when a non-spec CtxDigest is used #[test] fn sign_verify_nonspec() { // Generate the keypair let mut rng = OsRng.unwrap_err(); let esk = ExpandedSecretKey::random(&mut rng); let vk = VerifyingKey::from(&esk); let msg = b"Then one day, a piano fell on my head"; // Sign and verify let sig = raw_sign::(&esk, msg, &vk); raw_verify::(&vk, msg, &sig).unwrap(); } // Check that raw_sign_prehashed and raw_verify_prehashed work when distinct, non-spec // MsgDigest and CtxDigest are used #[cfg(feature = "digest")] #[test] fn sign_verify_prehashed_nonspec() { use curve25519_dalek::digest::Digest; // Generate the keypair let mut rng = OsRng.unwrap_err(); let esk = ExpandedSecretKey::random(&mut rng); let vk = VerifyingKey::from(&esk); // Hash the message let msg = b"And then I got trampled by a herd of buffalo"; let mut h = MsgDigest::new(); h.update(msg); let ctx_str = &b"consequences"[..]; // Sign and verify prehashed let sig = raw_sign_prehashed::(&esk, h.clone(), &vk, Some(ctx_str)) .unwrap(); raw_verify_prehashed::(&vk, h, Some(ctx_str), &sig).unwrap(); } #[test] fn sign_byupdate() { // Generate the keypair let mut rng = OsRng.unwrap_err(); let esk = ExpandedSecretKey::random(&mut rng); let vk = VerifyingKey::from(&esk); let msg = b"realistic"; // signatures are deterministic so we can compare with a good one let good_sig = raw_sign::(&esk, msg, &vk); let sig = raw_sign_byupdate::( &esk, |h| { h.update(msg); Ok(()) }, &vk, ); assert!(sig.unwrap() == good_sig, "sign byupdate matches"); let sig = raw_sign_byupdate::( &esk, |h| { h.update(msg); Err(SignatureError::new()) }, &vk, ); assert!(sig.is_err(), "sign byupdate failure propagates"); let sig = raw_sign_byupdate::( &esk, |h| { h.update(&msg[..1]); h.update(&msg[1..]); Ok(()) }, &vk, ); assert!(sig.unwrap() == good_sig, "sign byupdate two part"); } }