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 crate::{InternalError, SignatureError};
17
18use curve25519_dalek::scalar::{clamp_integer, Scalar};
19
20#[cfg(feature = "zeroize")]
21use zeroize::{Zeroize, ZeroizeOnDrop};
22
23// These are used in the functions that are made public when the hazmat feature is set
24use crate::{Signature, VerifyingKey};
25use curve25519_dalek::digest::{generic_array::typenum::U64, Digest};
26
27/// Contains the secret scalar and domain separator used for generating signatures.
28///
29/// This is used internally for signing.
30///
31/// In the usual Ed25519 signing algorithm, `scalar` and `hash_prefix` are defined such that
32/// `scalar || hash_prefix = H(sk)` where `sk` is the signing key and `H` is SHA-512.
33/// **WARNING:** Deriving the values for these fields in any other way can lead to full key
34/// recovery, as documented in [`raw_sign`] and [`raw_sign_prehashed`].
35///
36/// Instances of this secret are automatically overwritten with zeroes when they fall out of scope.
37pub struct ExpandedSecretKey {
38 /// The secret scalar used for signing
39 pub scalar: Scalar,
40 /// The domain separator used when hashing the message to generate the pseudorandom `r` value
41 pub hash_prefix: [u8; 32],
42}
43
44#[cfg(feature = "zeroize")]
45impl Drop for ExpandedSecretKey {
46 fn drop(&mut self) {
47 self.scalar.zeroize();
48 self.hash_prefix.zeroize()
49 }
50}
51
52#[cfg(feature = "zeroize")]
53impl ZeroizeOnDrop for ExpandedSecretKey {}
54
55// Some conversion methods for `ExpandedSecretKey`. The signing methods are defined in
56// `signing.rs`, since we need them even when `not(feature = "hazmat")`
57impl ExpandedSecretKey {
58 /// Construct an `ExpandedSecretKey` from an array of 64 bytes. In the spec, the bytes are the
59 /// output of a SHA-512 hash. This clamps the first 32 bytes and uses it as a scalar, and uses
60 /// the second 32 bytes as a domain separator for hashing.
61 pub fn from_bytes(bytes: &[u8; 64]) -> Self {
62 // TODO: Use bytes.split_array_ref once it’s in MSRV.
63 let mut scalar_bytes: [u8; 32] = [0u8; 32];
64 let mut hash_prefix: [u8; 32] = [0u8; 32];
65 scalar_bytes.copy_from_slice(&bytes[00..32]);
66 hash_prefix.copy_from_slice(&bytes[32..64]);
67
68 // For signing, we'll need the integer, clamped, and converted to a Scalar. See
69 // PureEdDSA.keygen in RFC 8032 Appendix A.
70 let scalar = Scalar::from_bytes_mod_order(clamp_integer(scalar_bytes));
71
72 ExpandedSecretKey {
73 scalar,
74 hash_prefix,
75 }
76 }
77
78 /// Construct an `ExpandedSecretKey` from a slice of 64 bytes.
79 ///
80 /// # Returns
81 ///
82 /// A `Result` whose okay value is an EdDSA `ExpandedSecretKey` or whose error value is an
83 /// `SignatureError` describing the error that occurred, namely that the given slice's length
84 /// is not 64.
85 pub fn from_slice(bytes: &[u8]) -> Result<Self, SignatureError> {
86 // Try to coerce bytes to a [u8; 64]
87 bytes.try_into().map(Self::from_bytes).map_err(|_| {
88 InternalError::BytesLength {
89 name: "ExpandedSecretKey",
90 length: 64,
91 }
92 .into()
93 })
94 }
95}
96
97impl TryFrom<&[u8]> for ExpandedSecretKey {
98 type Error = SignatureError;
99
100 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
101 Self::from_slice(bytes)
102 }
103}
104
105/// Compute an ordinary Ed25519 signature over the given message. `CtxDigest` is the digest used to
106/// calculate the pseudorandomness needed for signing. According to the Ed25519 spec, `CtxDigest =
107/// Sha512`.
108///
109/// # ⚠️ Unsafe
110///
111/// Do NOT use this function unless you absolutely must. Using the wrong values in
112/// `ExpandedSecretKey` can leak your signing key. See
113/// [here](https://github.com/MystenLabs/ed25519-unsafe-libs) for more details on this attack.
114pub fn raw_sign<CtxDigest>(
115 esk: &ExpandedSecretKey,
116 message: &[u8],
117 verifying_key: &VerifyingKey,
118) -> Signature
119where
120 CtxDigest: Digest<OutputSize = U64>,
121{
122 esk.raw_sign::<CtxDigest>(message, verifying_key)
123}
124
125/// Compute a signature over the given prehashed message, the Ed25519ph algorithm defined in
126/// [RFC8032 §5.1][rfc8032]. `MsgDigest` is the digest function used to hash the signed message.
127/// `CtxDigest` is the digest function used to calculate the pseudorandomness needed for signing.
128/// According to the Ed25519 spec, `MsgDigest = CtxDigest = Sha512`.
129///
130/// # ⚠️ Unsafe
131//
132/// Do NOT use this function unless you absolutely must. Using the wrong values in
133/// `ExpandedSecretKey` can leak your signing key. See
134/// [here](https://github.com/MystenLabs/ed25519-unsafe-libs) for more details on this attack.
135///
136/// # Inputs
137///
138/// * `esk` is the [`ExpandedSecretKey`] being used for signing
139/// * `prehashed_message` is an instantiated hash digest with 512-bits of
140/// output which has had the message to be signed previously fed into its
141/// state.
142/// * `verifying_key` is a [`VerifyingKey`] which corresponds to this secret key.
143/// * `context` is an optional context string, up to 255 bytes inclusive,
144/// which may be used to provide additional domain separation. If not
145/// set, this will default to an empty string.
146///
147/// `scalar` and `hash_prefix` are usually selected such that `scalar || hash_prefix = H(sk)` where
148/// `sk` is the signing key
149///
150/// # Returns
151///
152/// A `Result` whose `Ok` value is an Ed25519ph [`Signature`] on the
153/// `prehashed_message` if the context was 255 bytes or less, otherwise
154/// a `SignatureError`.
155///
156/// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1
157#[cfg(feature = "digest")]
158#[allow(non_snake_case)]
159pub fn raw_sign_prehashed<CtxDigest, MsgDigest>(
160 esk: &ExpandedSecretKey,
161 prehashed_message: MsgDigest,
162 verifying_key: &VerifyingKey,
163 context: Option<&[u8]>,
164) -> Result<Signature, SignatureError>
165where
166 MsgDigest: Digest<OutputSize = U64>,
167 CtxDigest: Digest<OutputSize = U64>,
168{
169 esk.raw_sign_prehashed::<CtxDigest, MsgDigest>(prehashed_message, verifying_key, context)
170}
171
172/// The ordinary non-batched Ed25519 verification check, rejecting non-canonical R
173/// values.`CtxDigest` is the digest used to calculate the pseudorandomness needed for signing.
174/// According to the Ed25519 spec, `CtxDigest = Sha512`.
175pub fn raw_verify<CtxDigest>(
176 vk: &VerifyingKey,
177 message: &[u8],
178 signature: &ed25519::Signature,
179) -> Result<(), SignatureError>
180where
181 CtxDigest: Digest<OutputSize = U64>,
182{
183 vk.raw_verify::<CtxDigest>(message, signature)
184}
185
186/// The batched Ed25519 verification check, rejecting non-canonical R values. `MsgDigest` is the
187/// digest used to hash the signed message. `CtxDigest` is the digest used to calculate the
188/// pseudorandomness needed for signing. According to the Ed25519 spec, `MsgDigest = CtxDigest =
189/// Sha512`.
190#[cfg(feature = "digest")]
191#[allow(non_snake_case)]
192pub fn raw_verify_prehashed<CtxDigest, MsgDigest>(
193 vk: &VerifyingKey,
194 prehashed_message: MsgDigest,
195 context: Option<&[u8]>,
196 signature: &ed25519::Signature,
197) -> Result<(), SignatureError>
198where
199 MsgDigest: Digest<OutputSize = U64>,
200 CtxDigest: Digest<OutputSize = U64>,
201{
202 vk.raw_verify_prehashed::<CtxDigest, MsgDigest>(prehashed_message, context, signature)
203}
204
205#[cfg(test)]
206mod test {
207 #![allow(clippy::unwrap_used)]
208
209 use super::*;
210
211 use rand::{rngs::OsRng, CryptoRng, RngCore};
212
213 // Pick distinct, non-spec 512-bit hash functions for message and sig-context hashing
214 type CtxDigest = blake2::Blake2b512;
215 type MsgDigest = sha3::Sha3_512;
216
217 impl ExpandedSecretKey {
218 // Make a random expanded secret key for testing purposes. This is NOT how you generate
219 // expanded secret keys IRL. They're the hash of a seed.
220 fn random<R: RngCore + CryptoRng>(mut rng: R) -> Self {
221 let mut bytes = [0u8; 64];
222 rng.fill_bytes(&mut bytes);
223 ExpandedSecretKey::from_bytes(&bytes)
224 }
225 }
226
227 // Check that raw_sign and raw_verify work when a non-spec CtxDigest is used
228 #[test]
229 fn sign_verify_nonspec() {
230 // Generate the keypair
231 let rng = OsRng;
232 let esk = ExpandedSecretKey::random(rng);
233 let vk = VerifyingKey::from(&esk);
234
235 let msg = b"Then one day, a piano fell on my head";
236
237 // Sign and verify
238 let sig = raw_sign::<CtxDigest>(&esk, msg, &vk);
239 raw_verify::<CtxDigest>(&vk, msg, &sig).unwrap();
240 }
241
242 // Check that raw_sign_prehashed and raw_verify_prehashed work when distinct, non-spec
243 // MsgDigest and CtxDigest are used
244 #[cfg(feature = "digest")]
245 #[test]
246 fn sign_verify_prehashed_nonspec() {
247 use curve25519_dalek::digest::Digest;
248
249 // Generate the keypair
250 let rng = OsRng;
251 let esk = ExpandedSecretKey::random(rng);
252 let vk = VerifyingKey::from(&esk);
253
254 // Hash the message
255 let msg = b"And then I got trampled by a herd of buffalo";
256 let mut h = MsgDigest::new();
257 h.update(msg);
258
259 let ctx_str = &b"consequences"[..];
260
261 // Sign and verify prehashed
262 let sig = raw_sign_prehashed::<CtxDigest, MsgDigest>(&esk, h.clone(), &vk, Some(ctx_str))
263 .unwrap();
264 raw_verify_prehashed::<CtxDigest, MsgDigest>(&vk, h, Some(ctx_str), &sig).unwrap();
265 }
266}