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}