1use super::{SignatureAlgorithm, SigningKey, SigningKeyError, SigningPublicKeyInfo};
4use pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey};
5use sha2::digest::Update;
6
7#[expect(
8 clippy::large_enum_variant,
9 reason = "fixed-size inline private keys avoid a second secret allocation and indirection"
10)]
11enum EdDsaPrivateKey {
12 Ed25519(ed25519_dalek::SigningKey),
13 Ed448(ed448_goldilocks::SigningKey),
14}
15
16pub struct EdDsaSigningKey(EdDsaPrivateKey);
18
19impl EdDsaSigningKey {
20 pub fn from_pkcs8_der(
22 algorithm: SignatureAlgorithm,
23 der: &[u8],
24 ) -> Result<Self, SigningKeyError> {
25 let key = match algorithm.eddsa_key_algorithm() {
26 Some(SignatureAlgorithm::Ed25519) => EdDsaPrivateKey::Ed25519(
27 ed25519_dalek::SigningKey::from_pkcs8_der(der)
28 .map_err(|_| SigningKeyError::InvalidKeyDer)?,
29 ),
30 Some(SignatureAlgorithm::Ed448) => EdDsaPrivateKey::Ed448(
31 ed448_goldilocks::SigningKey::from_pkcs8_der(der)
32 .map_err(|_| SigningKeyError::InvalidKeyDer)?,
33 ),
34 _ => {
35 return Err(SigningKeyError::UnsupportedAlgorithm {
36 uri: algorithm.uri().to_owned(),
37 });
38 }
39 };
40 Ok(Self(key))
41 }
42
43 pub fn to_pkcs8_der(&self) -> Result<pkcs8::SecretDocument, SigningKeyError> {
45 match &self.0 {
46 EdDsaPrivateKey::Ed25519(key) => key.to_pkcs8_der(),
47 EdDsaPrivateKey::Ed448(key) => key.to_pkcs8_der(),
48 }
49 .map_err(|_| SigningKeyError::InvalidKeyDer)
50 }
51
52 fn algorithm(&self) -> SignatureAlgorithm {
53 match &self.0 {
54 EdDsaPrivateKey::Ed25519(_) => SignatureAlgorithm::Ed25519,
55 EdDsaPrivateKey::Ed448(_) => SignatureAlgorithm::Ed448,
56 }
57 }
58}
59
60impl SigningKey for EdDsaSigningKey {
61 fn sign_with_provider_context(
62 &self,
63 provider: &dyn crate::provider::CryptoProvider,
64 algorithm: SignatureAlgorithm,
65 context: &super::SignatureContext,
66 message: &[u8],
67 ) -> Result<Vec<u8>, SigningKeyError> {
68 if algorithm.eddsa_key_algorithm() != Some(self.algorithm()) {
69 return Err(SigningKeyError::UnsupportedAlgorithm {
70 uri: algorithm.uri().to_owned(),
71 });
72 }
73 match &self.0 {
74 EdDsaPrivateKey::Ed448(key) if algorithm == SignatureAlgorithm::Ed448 => key
75 .sign_ctx(context.as_bytes(), message)
76 .map(|sig| sig.to_bytes().to_vec())
77 .map_err(|_| SigningKeyError::InvalidKeyDer),
78 EdDsaPrivateKey::Ed448(key) => {
79 use ed448_goldilocks::{PreHasherXof, shake::Shake256};
80 key.sign_prehashed::<PreHasherXof<Shake256>>(
81 Some(context.as_bytes()),
82 Shake256::default().chain(message).into(),
83 )
84 .map(|sig| sig.to_bytes().to_vec())
85 .map_err(|_| SigningKeyError::InvalidKeyDer)
86 }
87 EdDsaPrivateKey::Ed25519(key) if algorithm == SignatureAlgorithm::Ed25519Ctx => {
88 Ok(sign_ed25519ctx(key, context, message).to_bytes().to_vec())
89 }
90 EdDsaPrivateKey::Ed25519(key) if algorithm == SignatureAlgorithm::Ed25519Ph => {
91 use sha2::Digest;
92 key.sign_prehashed(
93 sha2::Sha512::new_with_prefix(message),
94 Some(context.as_bytes()),
95 )
96 .map(|sig| sig.to_bytes().to_vec())
97 .map_err(|_| SigningKeyError::InvalidKeyDer)
98 }
99 EdDsaPrivateKey::Ed25519(_) if context.as_bytes().is_empty() => {
100 self.sign_with_provider(provider, algorithm, message)
101 }
102 _ => Err(SigningKeyError::UnsupportedAlgorithm {
103 uri: algorithm.uri().to_owned(),
104 }),
105 }
106 }
107 fn sign(
108 &self,
109 algorithm: SignatureAlgorithm,
110 message: &[u8],
111 ) -> Result<Vec<u8>, SigningKeyError> {
112 crate::provider::default_provider().sign(self, algorithm, message)
113 }
114
115 fn sign_with_provider(
116 &self,
117 _provider: &dyn crate::provider::CryptoProvider,
118 algorithm: SignatureAlgorithm,
119 message: &[u8],
120 ) -> Result<Vec<u8>, SigningKeyError> {
121 if algorithm != self.algorithm() {
122 return self.sign_with_provider_context(
123 _provider,
124 algorithm,
125 &super::SignatureContext::default(),
126 message,
127 );
128 }
129 Ok(match &self.0 {
133 EdDsaPrivateKey::Ed25519(key) => {
134 use signature::Signer;
135 key.sign(message).to_bytes().to_vec()
136 }
137 EdDsaPrivateKey::Ed448(key) => key.sign_raw(message).to_bytes().to_vec(),
138 })
139 }
140
141 fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
142 let public = match &self.0 {
143 EdDsaPrivateKey::Ed25519(key) => key.verifying_key().to_public_key_der(),
144 EdDsaPrivateKey::Ed448(key) => key.verifying_key().to_public_key_der(),
145 }
146 .map_err(|_| SigningKeyError::PublicKeyEncodingFailed)?;
147 Ok(SigningPublicKeyInfo::EdDsa {
148 algorithm: self.algorithm(),
149 spki_der: public.into_vec(),
150 })
151 }
152}
153
154pub(crate) fn validate_public_key(
155 algorithm: SignatureAlgorithm,
156 der: &[u8],
157) -> Result<(), super::SignatureVerificationError> {
158 if let SignatureAlgorithm::PostQuantum(algorithm) = algorithm {
159 #[cfg(feature = "experimental-pq")]
160 {
161 return super::post_quantum::validate_public_key(algorithm, der);
162 }
163 #[cfg(not(feature = "experimental-pq"))]
164 {
165 return Err(super::SignatureVerificationError::UnsupportedAlgorithm {
166 uri: algorithm.uri().to_owned(),
167 });
168 }
169 }
170 match algorithm.eddsa_key_algorithm() {
171 Some(SignatureAlgorithm::Ed25519) => {
172 ed25519_dalek::VerifyingKey::from_public_key_der(der).map(|_| ())
173 }
174 Some(SignatureAlgorithm::Ed448) => {
175 ed448_goldilocks::VerifyingKey::from_public_key_der(der).map(|_| ())
176 }
177 _ => {
178 return Err(super::SignatureVerificationError::UnsupportedAlgorithm {
179 uri: algorithm.uri().to_owned(),
180 });
181 }
182 }
183 .map_err(|_| super::SignatureVerificationError::InvalidKeyDer)
184}
185
186pub(crate) fn verify(
187 algorithm: SignatureAlgorithm,
188 der: &[u8],
189 message: &[u8],
190 signature: &[u8],
191) -> Result<bool, super::SignatureVerificationError> {
192 verify_with_context(
193 algorithm,
194 der,
195 &super::SignatureContext::default(),
196 message,
197 signature,
198 )
199}
200
201pub(crate) fn verify_with_context(
202 algorithm: SignatureAlgorithm,
203 der: &[u8],
204 context: &super::SignatureContext,
205 message: &[u8],
206 signature: &[u8],
207) -> Result<bool, super::SignatureVerificationError> {
208 use super::SignatureVerificationError as Error;
209 if let SignatureAlgorithm::PostQuantum(algorithm) = algorithm {
210 #[cfg(feature = "experimental-pq")]
211 {
212 return super::post_quantum::verify(algorithm, der, context, message, signature);
213 }
214 #[cfg(not(feature = "experimental-pq"))]
215 {
216 return Err(Error::UnsupportedAlgorithm {
217 uri: algorithm.uri().to_owned(),
218 });
219 }
220 }
221 match algorithm {
222 SignatureAlgorithm::Ed25519
223 | SignatureAlgorithm::Ed25519Ctx
224 | SignatureAlgorithm::Ed25519Ph => {
225 let key = ed25519_dalek::VerifyingKey::from_public_key_der(der)
226 .map_err(|_| Error::InvalidKeyDer)?;
227 let signature = ed25519_dalek::Signature::try_from(signature)
228 .map_err(|_| Error::InvalidSignatureFormat)?;
229 Ok(match algorithm {
230 SignatureAlgorithm::Ed25519 if context.as_bytes().is_empty() => {
231 key.verify_strict(message, &signature).is_ok()
232 }
233 SignatureAlgorithm::Ed25519Ctx => {
234 verify_ed25519ctx(&key, context, message, &signature)
235 }
236 SignatureAlgorithm::Ed25519Ph => {
237 use sha2::Digest;
238 key.verify_prehashed_strict(
239 sha2::Sha512::new_with_prefix(message),
240 Some(context.as_bytes()),
241 &signature,
242 )
243 .is_ok()
244 }
245 _ => {
246 return Err(Error::UnsupportedAlgorithm {
247 uri: algorithm.uri().to_owned(),
248 });
249 }
250 })
251 }
252 SignatureAlgorithm::Ed448 | SignatureAlgorithm::Ed448Ph => {
253 let key = ed448_goldilocks::VerifyingKey::from_public_key_der(der)
254 .map_err(|_| Error::InvalidKeyDer)?;
255 let signature = ed448_goldilocks::Signature::try_from(signature)
256 .map_err(|_| Error::InvalidSignatureFormat)?;
257 Ok(if algorithm == SignatureAlgorithm::Ed448 {
258 key.verify_ctx(&signature, context.as_bytes(), message)
259 .is_ok()
260 } else {
261 use ed448_goldilocks::{PreHasherXof, shake::Shake256};
262 key.verify_prehashed::<PreHasherXof<Shake256>>(
263 &signature,
264 Some(context.as_bytes()),
265 Shake256::default().chain(message).into(),
266 )
267 .is_ok()
268 })
269 }
270 _ => Err(Error::UnsupportedAlgorithm {
271 uri: algorithm.uri().to_owned(),
272 }),
273 }
274}
275
276fn ed25519ctx_hash(context: &super::SignatureContext) -> sha2::Sha512 {
277 use sha2::Digest;
278 sha2::Sha512::new_with_prefix(b"SigEd25519 no Ed25519 collisions")
282 .chain_update([0, context.as_bytes().len() as u8])
283 .chain_update(context.as_bytes())
284}
285
286fn sign_ed25519ctx(
287 key: &ed25519_dalek::SigningKey,
288 context: &super::SignatureContext,
289 message: &[u8],
290) -> ed25519_dalek::Signature {
291 use curve25519_dalek::{edwards::EdwardsPoint, scalar::Scalar};
292 use sha2::Digest;
293 use zeroize::Zeroizing;
294 let expansion = Zeroizing::new(<[u8; 64]>::from(sha2::Sha512::digest(key.as_bytes())));
295 let secret = ed25519_dalek::hazmat::ExpandedSecretKey::from_bytes(&expansion);
296 let nonce_hash = Zeroizing::new(<[u8; 64]>::from(
297 ed25519ctx_hash(context)
298 .chain_update(secret.hash_prefix)
299 .chain_update(message)
300 .finalize(),
301 ));
302 let nonce = Zeroizing::new(Scalar::from_bytes_mod_order_wide(&nonce_hash));
303 let r = EdwardsPoint::mul_base(&nonce).compress().to_bytes();
304 let challenge = Scalar::from_bytes_mod_order_wide(
305 &ed25519ctx_hash(context)
306 .chain_update(r)
307 .chain_update(key.verifying_key().as_bytes())
308 .chain_update(message)
309 .finalize()
310 .into(),
311 );
312 let s = Zeroizing::new(*nonce + challenge * secret.scalar);
313 ed25519_dalek::Signature::from_components(r, s.to_bytes())
314}
315
316fn verify_ed25519ctx(
317 key: &ed25519_dalek::VerifyingKey,
318 context: &super::SignatureContext,
319 message: &[u8],
320 signature: &ed25519_dalek::Signature,
321) -> bool {
322 use curve25519_dalek::{
323 edwards::{CompressedEdwardsY, EdwardsPoint},
324 scalar::Scalar,
325 };
326 use sha2::Digest;
327 let Some(s) = Option::<Scalar>::from(Scalar::from_canonical_bytes(*signature.s_bytes())) else {
328 return false;
329 };
330 let encoded_r = CompressedEdwardsY(*signature.r_bytes());
331 let Some(r) = encoded_r.decompress() else {
332 return false;
333 };
334 if key.is_weak() || r.is_small_order() || r.compress() != encoded_r {
338 return false;
339 }
340 let challenge = Scalar::from_bytes_mod_order_wide(
341 &ed25519ctx_hash(context)
342 .chain_update(signature.r_bytes())
343 .chain_update(key.as_bytes())
344 .chain_update(message)
345 .finalize()
346 .into(),
347 );
348 EdwardsPoint::vartime_double_scalar_mul_basepoint(&challenge, &(-key.to_edwards()), &s)
349 .compress()
350 == encoded_r
351}