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}