ed25519_dalek/
hazmat.rs

1//! Low-level interfaces to ed25519 functions
2//!
3//! # ⚠️ Warning: Hazmat
4//!
5//! These primitives are easy-to-misuse low-level interfaces.
6//!
7//! If you are an end user / non-expert in cryptography, **do not use any of these functions**.
8//! Failure to use them correctly can lead to catastrophic failures including **full private key
9//! recovery.**
10
11// Permit dead code because 1) this module is only public when the `hazmat` feature is set, and 2)
12// even without `hazmat` we still need this module because this is where `ExpandedSecretKey` is
13// defined.
14#![allow(dead_code)]
15
16use core::fmt::Debug;
17
18use crate::{InternalError, SignatureError};
19
20use curve25519_dalek::scalar::{clamp_integer, Scalar};
21
22use subtle::{Choice, ConstantTimeEq};
23#[cfg(feature = "zeroize")]
24use zeroize::{Zeroize, ZeroizeOnDrop};
25
26// These are used in the functions that are made public when the hazmat feature is set
27use crate::{Signature, VerifyingKey};
28use curve25519_dalek::digest::{generic_array::typenum::U64, Digest};
29
30/// Contains the secret scalar and domain separator used for generating signatures.
31///
32/// This is used internally for signing.
33///
34/// In the usual Ed25519 signing algorithm, `scalar` and `hash_prefix` are defined such that
35/// `scalar || hash_prefix = H(sk)` where `sk` is the signing key and `H` is SHA-512.
36/// **WARNING:** Deriving the values for these fields in any other way can lead to full key
37/// recovery, as documented in [`raw_sign`] and [`raw_sign_prehashed`].
38///
39/// Instances of this secret are automatically overwritten with zeroes when they fall out of scope.
40pub struct ExpandedSecretKey {
41    /// The secret scalar used for signing
42    pub scalar: Scalar,
43    /// The domain separator used when hashing the message to generate the pseudorandom `r` value
44    pub hash_prefix: [u8; 32],
45}
46
47impl Debug for ExpandedSecretKey {
48    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
49        f.debug_struct("ExpandedSecretKey").finish_non_exhaustive() // avoids printing secrets
50    }
51}
52
53impl ConstantTimeEq for ExpandedSecretKey {
54    fn ct_eq(&self, other: &Self) -> Choice {
55        self.scalar.ct_eq(&other.scalar) & self.hash_prefix.ct_eq(&other.hash_prefix)
56    }
57}
58
59impl PartialEq for ExpandedSecretKey {
60    fn eq(&self, other: &Self) -> bool {
61        self.ct_eq(other).into()
62    }
63}
64
65impl Eq for ExpandedSecretKey {}
66
67#[cfg(feature = "zeroize")]
68impl Drop for ExpandedSecretKey {
69    fn drop(&mut self) {
70        self.scalar.zeroize();
71        self.hash_prefix.zeroize()
72    }
73}
74
75#[cfg(feature = "zeroize")]
76impl ZeroizeOnDrop for ExpandedSecretKey {}
77
78// Some conversion methods for `ExpandedSecretKey`. The signing methods are defined in
79// `signing.rs`, since we need them even when `not(feature = "hazmat")`
80impl ExpandedSecretKey {
81    /// Construct an `ExpandedSecretKey` from an array of 64 bytes. In the spec, the bytes are the
82    /// output of a SHA-512 hash. This clamps the first 32 bytes and uses it as a scalar, and uses
83    /// the second 32 bytes as a domain separator for hashing.
84    pub fn from_bytes(bytes: &[u8; 64]) -> Self {
85        // TODO: Use bytes.split_array_ref once it’s in MSRV.
86        let mut scalar_bytes: [u8; 32] = [0u8; 32];
87        let mut hash_prefix: [u8; 32] = [0u8; 32];
88        scalar_bytes.copy_from_slice(&bytes[00..32]);
89        hash_prefix.copy_from_slice(&bytes[32..64]);
90
91        // For signing, we'll need the integer, clamped, and converted to a Scalar. See
92        // PureEdDSA.keygen in RFC 8032 Appendix A.
93        let scalar = Scalar::from_bytes_mod_order(clamp_integer(scalar_bytes));
94
95        ExpandedSecretKey {
96            scalar,
97            hash_prefix,
98        }
99    }
100
101    /// Construct an `ExpandedSecretKey` from a slice of 64 bytes.
102    ///
103    /// # Returns
104    ///
105    /// A `Result` whose okay value is an EdDSA `ExpandedSecretKey` or whose error value is an
106    /// `SignatureError` describing the error that occurred, namely that the given slice's length
107    /// is not 64.
108    pub fn from_slice(bytes: &[u8]) -> Result<Self, SignatureError> {
109        // Try to coerce bytes to a [u8; 64]
110        bytes.try_into().map(Self::from_bytes).map_err(|_| {
111            InternalError::BytesLength {
112                name: "ExpandedSecretKey",
113                length: 64,
114            }
115            .into()
116        })
117    }
118}
119
120impl TryFrom<&[u8]> for ExpandedSecretKey {
121    type Error = SignatureError;
122
123    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
124        Self::from_slice(bytes)
125    }
126}
127
128/// Compute an ordinary Ed25519 signature over the given message. `CtxDigest` is the digest used to
129/// calculate the pseudorandomness needed for signing. According to the Ed25519 spec, `CtxDigest =
130/// Sha512`.
131///
132/// # ⚠️  Cryptographically Unsafe
133///
134/// Do NOT use this function unless you absolutely must. Using the wrong values in
135/// `ExpandedSecretKey` can leak your signing key. See
136/// [here](https://github.com/MystenLabs/ed25519-unsafe-libs) for more details on this attack.
137pub fn raw_sign<CtxDigest>(
138    esk: &ExpandedSecretKey,
139    message: &[u8],
140    verifying_key: &VerifyingKey,
141) -> Signature
142where
143    CtxDigest: Digest<OutputSize = U64>,
144{
145    esk.raw_sign::<CtxDigest>(message, verifying_key)
146}
147
148/// Compute a signature over the given prehashed message, the Ed25519ph algorithm defined in
149/// [RFC8032 §5.1][rfc8032]. `MsgDigest` is the digest function used to hash the signed message.
150/// `CtxDigest` is the digest function used to calculate the pseudorandomness needed for signing.
151/// According to the Ed25519 spec, `MsgDigest = CtxDigest = Sha512`.
152///
153/// # ⚠️  Cryptographically Unsafe
154//
155/// Do NOT use this function unless you absolutely must. Using the wrong values in
156/// `ExpandedSecretKey` can leak your signing key. See
157/// [here](https://github.com/MystenLabs/ed25519-unsafe-libs) for more details on this attack.
158///
159/// # Inputs
160///
161/// * `esk` is the [`ExpandedSecretKey`] being used for signing
162/// * `prehashed_message` is an instantiated hash digest with 512-bits of
163///   output which has had the message to be signed previously fed into its
164///   state.
165/// * `verifying_key` is a [`VerifyingKey`] which corresponds to this secret key.
166/// * `context` is an optional context string, up to 255 bytes inclusive,
167///   which may be used to provide additional domain separation.  If not
168///   set, this will default to an empty string.
169///
170/// `scalar` and `hash_prefix` are usually selected such that `scalar || hash_prefix = H(sk)` where
171/// `sk` is the signing key
172///
173/// # Returns
174///
175/// A `Result` whose `Ok` value is an Ed25519ph [`Signature`] on the
176/// `prehashed_message` if the context was 255 bytes or less, otherwise
177/// a `SignatureError`.
178///
179/// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1
180#[cfg(feature = "digest")]
181#[allow(non_snake_case)]
182pub fn raw_sign_prehashed<CtxDigest, MsgDigest>(
183    esk: &ExpandedSecretKey,
184    prehashed_message: MsgDigest,
185    verifying_key: &VerifyingKey,
186    context: Option<&[u8]>,
187) -> Result<Signature, SignatureError>
188where
189    MsgDigest: Digest<OutputSize = U64>,
190    CtxDigest: Digest<OutputSize = U64>,
191{
192    esk.raw_sign_prehashed::<CtxDigest, MsgDigest>(prehashed_message, verifying_key, context)
193}
194
195/// Compute an ordinary Ed25519 signature, with the message contents provided incrementally by
196/// updating a digest instance.
197///
198/// The `msg_update` closure provides the message content, updating a hasher argument. It will be
199/// called twice. This closure MUST leave its hasher in the same state (i.e., must hash the same
200/// values) after both calls. Otherwise it will produce an invalid signature.
201///
202/// `CtxDigest` is the digest used to calculate the pseudorandomness needed for signing. According
203/// to the Ed25519 spec, `CtxDigest = Sha512`.
204///
205/// # ⚠️  Cryptographically Unsafe
206///
207/// Do NOT use this function unless you absolutely must. Using the wrong values in
208/// `ExpandedSecretKey` can leak your signing key. See
209/// [here](https://github.com/MystenLabs/ed25519-unsafe-libs) for more details on this attack.
210pub fn raw_sign_byupdate<CtxDigest, F>(
211    esk: &ExpandedSecretKey,
212    msg_update: F,
213    verifying_key: &VerifyingKey,
214) -> Result<Signature, SignatureError>
215where
216    CtxDigest: Digest<OutputSize = U64>,
217    F: Fn(&mut CtxDigest) -> Result<(), SignatureError>,
218{
219    esk.raw_sign_byupdate::<CtxDigest, F>(msg_update, verifying_key)
220}
221
222/// The ordinary non-batched Ed25519 verification check, rejecting non-canonical R
223/// values.`CtxDigest` is the digest used to calculate the pseudorandomness needed for signing.
224/// According to the Ed25519 spec, `CtxDigest = Sha512`.
225pub fn raw_verify<CtxDigest>(
226    vk: &VerifyingKey,
227    message: &[u8],
228    signature: &ed25519::Signature,
229) -> Result<(), SignatureError>
230where
231    CtxDigest: Digest<OutputSize = U64>,
232{
233    vk.raw_verify::<CtxDigest>(message, signature)
234}
235
236/// The batched Ed25519 verification check, rejecting non-canonical R values. `MsgDigest` is the
237/// digest used to hash the signed message. `CtxDigest` is the digest used to calculate the
238/// pseudorandomness needed for signing. According to the Ed25519 spec, `MsgDigest = CtxDigest =
239/// Sha512`.
240#[cfg(feature = "digest")]
241#[allow(non_snake_case)]
242pub fn raw_verify_prehashed<CtxDigest, MsgDigest>(
243    vk: &VerifyingKey,
244    prehashed_message: MsgDigest,
245    context: Option<&[u8]>,
246    signature: &ed25519::Signature,
247) -> Result<(), SignatureError>
248where
249    MsgDigest: Digest<OutputSize = U64>,
250    CtxDigest: Digest<OutputSize = U64>,
251{
252    vk.raw_verify_prehashed::<CtxDigest, MsgDigest>(prehashed_message, context, signature)
253}
254
255#[cfg(test)]
256mod test {
257    #![allow(clippy::unwrap_used)]
258
259    use super::*;
260
261    use rand::{rngs::OsRng, CryptoRng, RngCore};
262
263    // Pick distinct, non-spec 512-bit hash functions for message and sig-context hashing
264    type CtxDigest = blake2::Blake2b512;
265    type MsgDigest = sha3::Sha3_512;
266
267    impl ExpandedSecretKey {
268        // Make a random expanded secret key for testing purposes. This is NOT how you generate
269        // expanded secret keys IRL. They're the hash of a seed.
270        fn random<R: RngCore + CryptoRng>(mut rng: R) -> Self {
271            let mut bytes = [0u8; 64];
272            rng.fill_bytes(&mut bytes);
273            ExpandedSecretKey::from_bytes(&bytes)
274        }
275    }
276
277    // Check that raw_sign and raw_verify work when a non-spec CtxDigest is used
278    #[test]
279    fn sign_verify_nonspec() {
280        // Generate the keypair
281        let rng = OsRng;
282        let esk = ExpandedSecretKey::random(rng);
283        let vk = VerifyingKey::from(&esk);
284
285        let msg = b"Then one day, a piano fell on my head";
286
287        // Sign and verify
288        let sig = raw_sign::<CtxDigest>(&esk, msg, &vk);
289        raw_verify::<CtxDigest>(&vk, msg, &sig).unwrap();
290    }
291
292    // Check that raw_sign_prehashed and raw_verify_prehashed work when distinct, non-spec
293    // MsgDigest and CtxDigest are used
294    #[cfg(feature = "digest")]
295    #[test]
296    fn sign_verify_prehashed_nonspec() {
297        use curve25519_dalek::digest::Digest;
298
299        // Generate the keypair
300        let rng = OsRng;
301        let esk = ExpandedSecretKey::random(rng);
302        let vk = VerifyingKey::from(&esk);
303
304        // Hash the message
305        let msg = b"And then I got trampled by a herd of buffalo";
306        let mut h = MsgDigest::new();
307        h.update(msg);
308
309        let ctx_str = &b"consequences"[..];
310
311        // Sign and verify prehashed
312        let sig = raw_sign_prehashed::<CtxDigest, MsgDigest>(&esk, h.clone(), &vk, Some(ctx_str))
313            .unwrap();
314        raw_verify_prehashed::<CtxDigest, MsgDigest>(&vk, h, Some(ctx_str), &sig).unwrap();
315    }
316
317    #[test]
318    fn sign_byupdate() {
319        // Generate the keypair
320        let rng = OsRng;
321        let esk = ExpandedSecretKey::random(rng);
322        let vk = VerifyingKey::from(&esk);
323
324        let msg = b"realistic";
325        // signatures are deterministic so we can compare with a good one
326        let good_sig = raw_sign::<CtxDigest>(&esk, msg, &vk);
327
328        let sig = raw_sign_byupdate::<CtxDigest, _>(
329            &esk,
330            |h| {
331                h.update(msg);
332                Ok(())
333            },
334            &vk,
335        );
336        assert!(sig.unwrap() == good_sig, "sign byupdate matches");
337
338        let sig = raw_sign_byupdate::<CtxDigest, _>(
339            &esk,
340            |h| {
341                h.update(msg);
342                Err(SignatureError::new())
343            },
344            &vk,
345        );
346        assert!(sig.is_err(), "sign byupdate failure propagates");
347
348        let sig = raw_sign_byupdate::<CtxDigest, _>(
349            &esk,
350            |h| {
351                h.update(&msg[..1]);
352                h.update(&msg[1..]);
353                Ok(())
354            },
355            &vk,
356        );
357        assert!(sig.unwrap() == good_sig, "sign byupdate two part");
358    }
359}