1use curve25519_dalek::scalar::Scalar;
33use zeroize::Zeroize;
34
35use super::crypto_core::{
36 decompress_canonical_ed25519_point, decompress_prime_order_ed25519_point,
37};
38use crate::constants::{
39 CRYPTO_HASH_SHA512_BYTES, CRYPTO_SCALARMULT_CURVE25519_BYTES,
40 CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES, CRYPTO_SIGN_ED25519_BYTES,
41 CRYPTO_SIGN_ED25519_PUBLICKEYBYTES, CRYPTO_SIGN_ED25519_SECRETKEYBYTES,
42 CRYPTO_SIGN_ED25519_SEEDBYTES,
43};
44use crate::edwards25519::mul_base;
45use crate::error::Error;
46use crate::scalarmult_curve25519::clamp_scalar;
47use crate::sha512::Sha512;
48use crate::utils::zeroize_bytes;
49
50pub type PublicKey = [u8; CRYPTO_SIGN_ED25519_PUBLICKEYBYTES];
52pub type SecretKey = [u8; CRYPTO_SIGN_ED25519_SECRETKEYBYTES];
54pub type Signature = [u8; CRYPTO_SIGN_ED25519_BYTES];
56
57const DOM2PREFIX: &[u8] = b"SigEd25519 no Ed25519 collisions\x01\x00";
58
59#[inline]
61pub(crate) fn crypto_sign_ed25519_seed_keypair_inplace(
62 public_key: &mut PublicKey,
63 secret_key: &mut SecretKey,
64 seed: &[u8; CRYPTO_SIGN_ED25519_SEEDBYTES],
65) {
66 let mut hash: [u8; CRYPTO_HASH_SHA512_BYTES] = Sha512::compute(seed);
67
68 let mut clamped = clamp_hash(&mut hash);
69 let mut point = mul_base(&clamped);
70 let pk = point.compress();
71 zeroize_bytes(&mut clamped);
72 point.zeroize();
73
74 secret_key[..CRYPTO_SIGN_ED25519_SEEDBYTES].copy_from_slice(seed);
75 secret_key[CRYPTO_SIGN_ED25519_SEEDBYTES..].copy_from_slice(&pk);
76
77 public_key.copy_from_slice(&pk);
78}
79
80pub(crate) fn crypto_sign_ed25519_seed_keypair(
83 seed: &[u8; CRYPTO_SIGN_ED25519_SEEDBYTES],
84) -> (PublicKey, SecretKey) {
85 let mut public_key = PublicKey::default();
86 let mut secret_key = [0u8; CRYPTO_SIGN_ED25519_SECRETKEYBYTES];
87
88 crypto_sign_ed25519_seed_keypair_inplace(&mut public_key, &mut secret_key, seed);
89
90 (public_key, secret_key)
91}
92
93#[inline]
95pub(crate) fn crypto_sign_ed25519_keypair_inplace(
96 public_key: &mut PublicKey,
97 secret_key: &mut SecretKey,
98) {
99 use crate::rng::copy_randombytes;
100 let mut seed = [0u8; CRYPTO_SIGN_ED25519_SEEDBYTES];
101 copy_randombytes(&mut seed);
102 crypto_sign_ed25519_seed_keypair_inplace(public_key, secret_key, &seed);
103 seed.zeroize();
104}
105
106pub(crate) fn crypto_sign_ed25519_keypair() -> (PublicKey, SecretKey) {
109 let mut public_key = PublicKey::default();
110 let mut secret_key = [0u8; CRYPTO_SIGN_ED25519_SECRETKEYBYTES];
111 crypto_sign_ed25519_keypair_inplace(&mut public_key, &mut secret_key);
112
113 (public_key, secret_key)
114}
115
116fn clamp_hash(
117 hash: &mut [u8; CRYPTO_HASH_SHA512_BYTES],
118) -> [u8; CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES] {
119 let mut scalar = [0u8; CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES];
120 scalar.copy_from_slice(&hash[..CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES]);
121 zeroize_bytes(hash);
122 clamp_scalar(&mut scalar);
123 scalar
124}
125
126pub fn crypto_sign_ed25519_pk_to_curve25519(
136 x25519_public_key: &mut [u8; CRYPTO_SCALARMULT_CURVE25519_BYTES],
137 ed25519_public_key: &PublicKey,
138) -> Result<(), Error> {
139 let ep = decompress_prime_order_ed25519_point(ed25519_public_key)
140 .ok_or(Error::invalid_key(crate::ErrorContext::Ed25519PublicKey))?;
141 *x25519_public_key = ep.to_montgomery();
142
143 Ok(())
144}
145
146pub fn crypto_sign_ed25519_sk_to_curve25519(
151 x25519_secret_key: &mut [u8; CRYPTO_SCALARMULT_CURVE25519_BYTES],
152 ed25519_secret_key: &SecretKey,
153) {
154 let mut hash: [u8; CRYPTO_HASH_SHA512_BYTES] = Sha512::compute(&ed25519_secret_key[..32]);
155 let mut scalar = clamp_hash(&mut hash);
156 x25519_secret_key.copy_from_slice(&scalar);
157 scalar.zeroize()
158}
159
160pub fn crypto_sign_ed25519_sk_to_seed(
164 seed: &mut [u8; CRYPTO_SIGN_ED25519_SEEDBYTES],
165 secret_key: &SecretKey,
166) {
167 seed.copy_from_slice(&secret_key[..CRYPTO_SIGN_ED25519_SEEDBYTES]);
168}
169
170pub fn crypto_sign_ed25519_sk_to_pk(public_key: &mut PublicKey, secret_key: &SecretKey) {
175 public_key.copy_from_slice(
176 &secret_key[CRYPTO_SIGN_ED25519_SEEDBYTES..CRYPTO_SIGN_ED25519_SECRETKEYBYTES],
177 );
178}
179
180pub(crate) fn crypto_sign_ed25519(
181 signed_message: &mut [u8],
182 message: &[u8],
183 secret_key: &SecretKey,
184) -> Result<(), Error> {
185 validate_length!(
186 exact message.len() + CRYPTO_SIGN_ED25519_BYTES,
187 signed_message.len(),
188 crate::ErrorContext::SignedMessage
189 );
190
191 let (sig, sm) = signed_message.split_at_mut(CRYPTO_SIGN_ED25519_BYTES);
192 let sig: &mut [u8; CRYPTO_SIGN_ED25519_BYTES] =
193 <&mut [u8; CRYPTO_SIGN_ED25519_BYTES]>::try_from(sig).unwrap();
194 sm.copy_from_slice(message);
195 crypto_sign_ed25519_detached(sig, message, secret_key);
196 Ok(())
197}
198
199pub(crate) fn crypto_sign_ed25519_detached(
200 signature: &mut Signature,
201 message: &[u8],
202 secret_key: &SecretKey,
203) {
204 crypto_sign_ed25519_detached_impl(signature, message, secret_key, false)
205}
206
207#[inline]
208fn crypto_sign_ed25519_detached_impl(
209 signature: &mut Signature,
210 message: &[u8],
211 secret_key: &SecretKey,
212 prehashed: bool,
213) {
214 let mut az: [u8; CRYPTO_HASH_SHA512_BYTES] = Sha512::compute(&secret_key[..32]);
215
216 let mut hasher = Sha512::new();
217 if prehashed {
218 hasher.update(DOM2PREFIX);
219 }
220 hasher.update(&az[32..]);
221 hasher.update(message);
222 let mut nonce: [u8; CRYPTO_HASH_SHA512_BYTES] = hasher.finalize();
223
224 signature[32..].copy_from_slice(&secret_key[32..]);
225
226 let mut r = Scalar::from_bytes_mod_order_wide(&nonce);
227 let mut r_bytes = r.to_bytes();
228 let mut r_point = mul_base(&r_bytes);
229 let big_r = r_point.compress();
230 zeroize_bytes(&mut r_bytes);
231 r_point.zeroize();
232
233 signature[..32].copy_from_slice(&big_r);
234
235 let mut hasher = Sha512::new();
236 if prehashed {
237 hasher.update(DOM2PREFIX);
238 }
239 hasher.update(signature);
240 hasher.update(message);
241 let mut hram: [u8; CRYPTO_HASH_SHA512_BYTES] = hasher.finalize();
242
243 let mut k = Scalar::from_bytes_mod_order_wide(&hram);
244 let mut clamped = clamp_hash(&mut az);
245 let mut signing_scalar = Scalar::from_bytes_mod_order(clamped);
246 zeroize_bytes(&mut clamped);
247 let mut sig = (k * signing_scalar) + r;
248
249 signature[32..].copy_from_slice(sig.as_bytes());
250
251 zeroize_bytes(&mut az);
252 zeroize_bytes(&mut nonce);
253 zeroize_bytes(&mut hram);
254 r.zeroize();
255 k.zeroize();
256 signing_scalar.zeroize();
257 sig.zeroize();
258}
259
260pub(crate) fn crypto_sign_ed25519_verify_detached(
261 signature: &Signature,
262 message: &[u8],
263 public_key: &PublicKey,
264) -> Result<(), Error> {
265 crypto_sign_ed25519_verify_detached_impl(signature, message, public_key, false)
266}
267
268fn crypto_sign_ed25519_verify_detached_impl(
269 signature: &Signature,
270 message: &[u8],
271 public_key: &PublicKey,
272 prehashed: bool,
273) -> Result<(), Error> {
274 let s_bytes = *<&[u8; CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES]>::try_from(&signature[32..])
275 .map_err(|_| Error::AuthenticationFailed)?;
276 let s = Option::<Scalar>::from(Scalar::from_canonical_bytes(s_bytes))
277 .ok_or(Error::AuthenticationFailed)?;
278 let r_bytes = <&[u8; CRYPTO_SIGN_ED25519_PUBLICKEYBYTES]>::try_from(&signature[..32])
279 .map_err(|_| Error::AuthenticationFailed)?;
280 let Some(pk) = decompress_canonical_ed25519_point(public_key).filter(|pk| !pk.is_small_order())
284 else {
285 let r_valid =
286 decompress_canonical_ed25519_point(r_bytes).is_some_and(|r| !r.is_small_order());
287 return Err(if r_valid {
288 Error::invalid_key(crate::ErrorContext::Ed25519PublicKey)
289 } else {
290 Error::AuthenticationFailed
291 });
292 };
293
294 let mut hasher = Sha512::new();
295 if prehashed {
296 hasher.update(DOM2PREFIX);
297 }
298 hasher.update(&signature[..32]);
299 hasher.update(public_key);
300 hasher.update(message);
301 let h: [u8; CRYPTO_HASH_SHA512_BYTES] = hasher.finalize();
302
303 let k = Scalar::from_bytes_mod_order_wide(&h);
304
305 let sig_r = pk
307 .neg()
308 .double_scalar_mul_basepoint_vartime(&k.to_bytes(), &s.to_bytes());
309
310 if sig_r.compress() == *r_bytes && !sig_r.is_small_order() {
316 Ok(())
317 } else {
318 Err(Error::AuthenticationFailed)
319 }
320}
321
322pub(crate) fn crypto_sign_ed25519_open(
323 message: &mut [u8],
324 signed_message: &[u8],
325 public_key: &PublicKey,
326) -> Result<(), Error> {
327 validate_length!(
328 min CRYPTO_SIGN_ED25519_BYTES,
329 signed_message.len(),
330 crate::ErrorContext::SignedMessage
331 );
332 validate_length!(
333 exact signed_message.len() - CRYPTO_SIGN_ED25519_BYTES,
334 message.len(),
335 crate::ErrorContext::Message
336 );
337
338 let (sig, sm) = signed_message.split_at(CRYPTO_SIGN_ED25519_BYTES);
339 let sig: &[u8; CRYPTO_SIGN_ED25519_BYTES] =
340 <&[u8; CRYPTO_SIGN_ED25519_BYTES]>::try_from(sig).unwrap();
341 crypto_sign_ed25519_verify_detached(sig, sm, public_key)?;
342 message.copy_from_slice(sm);
343 Ok(())
344}
345
346pub(crate) struct Ed25519SignerState {
347 hasher: Sha512,
348}
349
350pub(crate) fn crypto_sign_ed25519ph_init() -> Ed25519SignerState {
351 Ed25519SignerState {
352 hasher: Sha512::new(),
353 }
354}
355
356pub(crate) fn crypto_sign_ed25519ph_update(state: &mut Ed25519SignerState, message: &[u8]) {
357 state.hasher.update(message)
358}
359
360pub(crate) fn crypto_sign_ed25519ph_final_create(
361 state: Ed25519SignerState,
362 signature: &mut Signature,
363 secret_key: &SecretKey,
364) {
365 let mut hash: [u8; CRYPTO_HASH_SHA512_BYTES] = state.hasher.finalize();
366 crypto_sign_ed25519_detached_impl(signature, &hash, secret_key, true);
367 hash.zeroize();
368}
369
370pub(crate) fn crypto_sign_ed25519ph_final_verify(
371 state: Ed25519SignerState,
372 signature: &Signature,
373 public_key: &PublicKey,
374) -> Result<(), Error> {
375 let mut hash: [u8; CRYPTO_HASH_SHA512_BYTES] = state.hasher.finalize();
376 let res = crypto_sign_ed25519_verify_detached_impl(signature, &hash, public_key, true);
377 hash.zeroize();
378 res
379}
380
381#[cfg(test)]
382mod regression_tests {
383 use super::*;
384 use crate::classic::crypto_core::{
385 decompress_canonical_ed25519_point, ed25519_is_torsion_free,
386 };
387 use crate::edwards25519::test_vectors::{IDENTITY, NONCANONICAL_IDENTITY, mixed_order_point};
388
389 pub(super) const ED25519_GROUP_ORDER: [u8; 32] = [
390 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde,
391 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
392 0x00, 0x10,
393 ];
394
395 pub(super) fn add_group_order_to_s(signature: &mut Signature) {
396 let mut carry = 0u16;
397 for (s, order) in signature[32..].iter_mut().zip(ED25519_GROUP_ORDER) {
398 let sum = u16::from(*s) + u16::from(order) + carry;
399 *s = sum as u8;
400 carry = sum >> 8;
401 }
402 assert_eq!(carry, 0, "a reduced Ed25519 scalar plus L fits in 256 bits");
403 }
404
405 #[test]
406 fn verification_rejects_s_plus_group_order() {
407 let message = b"malleability regression";
408 let (public_key, secret_key) = crypto_sign_ed25519_seed_keypair(&[7u8; 32]);
409
410 let mut signature = [0u8; CRYPTO_SIGN_ED25519_BYTES];
411 crypto_sign_ed25519_detached(&mut signature, message, &secret_key);
412 crypto_sign_ed25519_verify_detached(&signature, message, &public_key).unwrap();
413 add_group_order_to_s(&mut signature);
414 assert!(matches!(
415 crypto_sign_ed25519_verify_detached(&signature, message, &public_key),
416 Err(Error::AuthenticationFailed)
417 ));
418
419 let mut signed_message = vec![0u8; message.len() + CRYPTO_SIGN_ED25519_BYTES];
420 crypto_sign_ed25519(&mut signed_message, message, &secret_key).unwrap();
421 let embedded_signature =
422 <&mut Signature>::try_from(&mut signed_message[..CRYPTO_SIGN_ED25519_BYTES]).unwrap();
423 add_group_order_to_s(embedded_signature);
424 let mut opened_message = vec![0u8; message.len()];
425 assert!(matches!(
426 crypto_sign_ed25519_open(&mut opened_message, &signed_message, &public_key),
427 Err(Error::AuthenticationFailed)
428 ));
429
430 let mut signer = crypto_sign_ed25519ph_init();
431 crypto_sign_ed25519ph_update(&mut signer, message);
432 let mut prehash_signature = [0u8; CRYPTO_SIGN_ED25519_BYTES];
433 crypto_sign_ed25519ph_final_create(signer, &mut prehash_signature, &secret_key);
434 add_group_order_to_s(&mut prehash_signature);
435
436 let mut verifier = crypto_sign_ed25519ph_init();
437 crypto_sign_ed25519ph_update(&mut verifier, message);
438 assert!(matches!(
439 crypto_sign_ed25519ph_final_verify(verifier, &prehash_signature, &public_key),
440 Err(Error::AuthenticationFailed)
441 ));
442 }
443
444 #[test]
445 fn public_key_conversion_rejects_invalid_edwards_points() {
446 let mixed_order = mixed_order_point().compress().to_bytes();
447 let mixed_point = decompress_canonical_ed25519_point(&mixed_order).unwrap();
448 assert!(!mixed_point.is_small_order());
449 assert!(!ed25519_is_torsion_free(&mixed_point));
450
451 for invalid_key in [IDENTITY, NONCANONICAL_IDENTITY, mixed_order] {
452 let mut output = [0xa5; CRYPTO_SCALARMULT_CURVE25519_BYTES];
453 assert!(crypto_sign_ed25519_pk_to_curve25519(&mut output, &invalid_key).is_err());
454 assert_eq!(
455 output, [0xa5; CRYPTO_SCALARMULT_CURVE25519_BYTES],
456 "conversion failure must not modify the output"
457 );
458 }
459 }
460
461 #[test]
462 fn public_key_conversion_accepts_valid_high_sign_bit() {
463 let basepoint = curve25519_dalek::constants::ED25519_BASEPOINT_COMPRESSED.to_bytes();
464 let mut negative_basepoint = basepoint;
465 negative_basepoint[31] |= 0x80;
466
467 let mut positive_output = [0u8; CRYPTO_SCALARMULT_CURVE25519_BYTES];
468 let mut negative_output = [0u8; CRYPTO_SCALARMULT_CURVE25519_BYTES];
469 crypto_sign_ed25519_pk_to_curve25519(&mut positive_output, &basepoint).unwrap();
470 crypto_sign_ed25519_pk_to_curve25519(&mut negative_output, &negative_basepoint).unwrap();
471 assert_eq!(positive_output, negative_output);
472 }
473}
474
475#[cfg(test)]
476mod vector_tests {
477 use curve25519_dalek::constants::EIGHT_TORSION;
478
479 use super::regression_tests::ED25519_GROUP_ORDER;
480 use super::*;
481 use crate::scalarmult_curve25519::test_vectors::field_prime_plus;
482 use crate::test_prelude::*;
483 use crate::utils::test_util::hex_array as hex;
484
485 struct Vector {
487 seed: &'static str,
488 public_key: &'static str,
489 message: &'static str,
490 signature: &'static str,
491 }
492
493 const TEST_1024_MESSAGE: &str = concat!(
494 "08b8b2b733424243760fe426a4b54908632110a66c2f6591eabd3345e3e4eb98",
495 "fa6e264bf09efe12ee50f8f54e9f77b1e355f6c50544e23fb1433ddf73be84d8",
496 "79de7c0046dc4996d9e773f4bc9efe5738829adb26c81b37c93a1b270b20329d",
497 "658675fc6ea534e0810a4432826bf58c941efb65d57a338bbd2e26640f89ffbc",
498 "1a858efcb8550ee3a5e1998bd177e93a7363c344fe6b199ee5d02e82d522c4fe",
499 "ba15452f80288a821a579116ec6dad2b3b310da903401aa62100ab5d1a36553e",
500 "06203b33890cc9b832f79ef80560ccb9a39ce767967ed628c6ad573cb116dbef",
501 "efd75499da96bd68a8a97b928a8bbc103b6621fcde2beca1231d206be6cd9ec7",
502 "aff6f6c94fcd7204ed3455c68c83f4a41da4af2b74ef5c53f1d8ac70bdcb7ed1",
503 "85ce81bd84359d44254d95629e9855a94a7c1958d1f8ada5d0532ed8a5aa3fb2",
504 "d17ba70eb6248e594e1a2297acbbb39d502f1a8c6eb6f1ce22b3de1a1f40cc24",
505 "554119a831a9aad6079cad88425de6bde1a9187ebb6092cf67bf2b13fd65f270",
506 "88d78b7e883c8759d2c4f5c65adb7553878ad575f9fad878e80a0c9ba63bcbcc",
507 "2732e69485bbc9c90bfbd62481d9089beccf80cfe2df16a2cf65bd92dd597b07",
508 "07e0917af48bbb75fed413d238f5555a7a569d80c3414a8d0859dc65a46128ba",
509 "b27af87a71314f318c782b23ebfe808b82b0ce26401d2e22f04d83d1255dc51a",
510 "ddd3b75a2b1ae0784504df543af8969be3ea7082ff7fc9888c144da2af58429e",
511 "c96031dbcad3dad9af0dcbaaaf268cb8fcffead94f3c7ca495e056a9b47acdb7",
512 "51fb73e666c6c655ade8297297d07ad1ba5e43f1bca32301651339e22904cc8c",
513 "42f58c30c04aafdb038dda0847dd988dcda6f3bfd15c4b4c4525004aa06eeff8",
514 "ca61783aacec57fb3d1f92b0fe2fd1a85f6724517b65e614ad6808d6f6ee34df",
515 "f7310fdc82aebfd904b01e1dc54b2927094b2db68d6f903b68401adebf5a7e08",
516 "d78ff4ef5d63653a65040cf9bfd4aca7984a74d37145986780fc0b16ac451649",
517 "de6188a7dbdf191f64b5fc5e2ab47b57f7f7276cd419c17a3ca8e1b939ae49e4",
518 "88acba6b965610b5480109c8b17b80e1b7b750dfc7598d5d5011fd2dcc5600a3",
519 "2ef5b52a1ecc820e308aa342721aac0943bf6686b64b2579376504ccc493d97e",
520 "6aed3fb0f9cd71a43dd497f01f17c0e2cb3797aa2a2f256656168e6c496afc5f",
521 "b93246f6b1116398a346f1a641f3b041e989f7914f90cc2c7fff357876e506b5",
522 "0d334ba77c225bc307ba537152f3f1610e4eafe595f6d9d90d11faa933a15ef1",
523 "369546868a7f3a45a96768d40fd9d03412c091c6315cf4fde7cb68606937380d",
524 "b2eaaa707b4c4185c32eddcdd306705e4dc1ffc872eeee475a64dfac86aba41c",
525 "0618983f8741c5ef68d3a101e8a3b8cac60c905c15fc910840b94c00a0b9d0",
526 );
527
528 const ED25519_VECTORS: [Vector; 5] = [
530 Vector {
531 seed: "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
532 public_key: "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
533 message: "",
534 signature: "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
535 },
536 Vector {
537 seed: "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
538 public_key: "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
539 message: "72",
540 signature: "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
541 },
542 Vector {
543 seed: "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
544 public_key: "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
545 message: "af82",
546 signature: "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
547 },
548 Vector {
549 seed: "f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5",
550 public_key: "278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e",
551 message: TEST_1024_MESSAGE,
552 signature: "0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03",
553 },
554 Vector {
555 seed: "833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42",
556 public_key: "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf",
557 message: "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
558 signature: "dc2a4459e7369633a52b1bf277839a00201009a3efbf3ecb69bea2186c26b58909351fc9ac90b3ecfdfbc7c66431e0303dca179c138ac17ad9bef1177331a704",
559 },
560 ];
561
562 const ED25519PH_VECTOR: Vector = Vector {
564 seed: "833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42",
565 public_key: "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf",
566 message: "616263",
567 signature: "98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae4131f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406",
568 };
569
570 pub(super) fn rejected_point_encodings() -> Vec<[u8; 32]> {
576 let mut encodings = Vec::with_capacity(16);
577 for offset in [0, 1, 18] {
578 let low = field_prime_plus(offset);
579 let mut high = low;
580 high[31] |= 0x80;
581 encodings.extend([low, high]);
582 }
583 encodings.extend(EIGHT_TORSION.iter().map(|t| t.compress().to_bytes()));
584 let mut identity_negative_sign = [0u8; 32];
585 identity_negative_sign[0] = 1;
586 identity_negative_sign[31] = 0x80;
587 let mut minus_one_negative_sign = field_prime_plus(-1);
588 minus_one_negative_sign[31] |= 0x80;
589 encodings.extend([identity_negative_sign, minus_one_negative_sign]);
590 assert_eq!(encodings.len(), 16);
591 encodings
592 }
593
594 pub(super) fn rejected_scalars() -> [[u8; 32]; 3] {
597 let mut order_plus_one = ED25519_GROUP_ORDER;
598 order_plus_one[0] += 1;
599 [ED25519_GROUP_ORDER, order_plus_one, [0xff; 32]]
600 }
601
602 #[test]
603 fn rfc8032_ed25519_vectors() {
604 for (i, vector) in ED25519_VECTORS.iter().enumerate() {
605 let seed = hex(vector.seed);
606 let message = hex::decode(vector.message).expect("hex");
607 let expected_signature: Signature = hex(vector.signature);
608
609 let (public_key, secret_key) = crypto_sign_ed25519_seed_keypair(&seed);
610 assert_eq!(public_key, hex(vector.public_key), "public key {i}");
611 assert_eq!(secret_key[..32], seed, "secret key seed {i}");
612 assert_eq!(secret_key[32..], public_key, "secret key suffix {i}");
613
614 let mut signature = [0u8; CRYPTO_SIGN_ED25519_BYTES];
615 crypto_sign_ed25519_detached(&mut signature, &message, &secret_key);
616 assert_eq!(signature, expected_signature, "signature {i}");
617 crypto_sign_ed25519_verify_detached(&expected_signature, &message, &public_key)
618 .unwrap_or_else(|e| panic!("verify {i}: {e}"));
619
620 let mut signed_message = vec![0u8; message.len() + CRYPTO_SIGN_ED25519_BYTES];
621 crypto_sign_ed25519(&mut signed_message, &message, &secret_key).unwrap();
622 assert_eq!(
623 signed_message[..CRYPTO_SIGN_ED25519_BYTES],
624 expected_signature,
625 "combined signature {i}"
626 );
627 assert_eq!(
628 signed_message[CRYPTO_SIGN_ED25519_BYTES..],
629 message,
630 "combined message {i}"
631 );
632 let mut opened = vec![0xa5; message.len()];
633 crypto_sign_ed25519_open(&mut opened, &signed_message, &public_key)
634 .unwrap_or_else(|e| panic!("open {i}: {e}"));
635 assert_eq!(opened, message, "opened {i}");
636 }
637 }
638
639 #[test]
643 fn rfc8032_ed25519ph_vector() {
644 let seed = hex(ED25519PH_VECTOR.seed);
645 let message = hex::decode(ED25519PH_VECTOR.message).expect("hex");
646 assert_eq!(message, b"abc");
647 let expected_signature: Signature = hex(ED25519PH_VECTOR.signature);
648
649 let (public_key, secret_key) = crypto_sign_ed25519_seed_keypair(&seed);
650 assert_eq!(public_key, hex(ED25519PH_VECTOR.public_key));
651
652 for split in 0..=message.len() {
653 let mut signer = crypto_sign_ed25519ph_init();
654 crypto_sign_ed25519ph_update(&mut signer, &message[..split]);
655 crypto_sign_ed25519ph_update(&mut signer, &message[split..]);
656 let mut signature = [0u8; CRYPTO_SIGN_ED25519_BYTES];
657 crypto_sign_ed25519ph_final_create(signer, &mut signature, &secret_key);
658 assert_eq!(signature, expected_signature, "split {split}");
659
660 let mut verifier = crypto_sign_ed25519ph_init();
661 crypto_sign_ed25519ph_update(&mut verifier, &message[..message.len() - split]);
662 crypto_sign_ed25519ph_update(&mut verifier, &message[message.len() - split..]);
663 crypto_sign_ed25519ph_final_verify(verifier, &expected_signature, &public_key)
664 .unwrap_or_else(|e| panic!("split {split}: {e}"));
665 }
666
667 assert!(matches!(
668 crypto_sign_ed25519_verify_detached(&expected_signature, &message, &public_key),
669 Err(Error::AuthenticationFailed)
670 ));
671 let mut plain_signature = [0u8; CRYPTO_SIGN_ED25519_BYTES];
672 crypto_sign_ed25519_detached(&mut plain_signature, &message, &secret_key);
673 assert_ne!(plain_signature, expected_signature);
674 let mut verifier = crypto_sign_ed25519ph_init();
675 crypto_sign_ed25519ph_update(&mut verifier, &message);
676 assert!(matches!(
677 crypto_sign_ed25519ph_final_verify(verifier, &plain_signature, &public_key),
678 Err(Error::AuthenticationFailed)
679 ));
680 }
681
682 #[test]
683 fn verification_rejects_scalar_at_or_above_group_order() {
684 let message = b"scalar boundary";
685 let (public_key, secret_key) = crypto_sign_ed25519_seed_keypair(&[11u8; 32]);
686 let mut signed_message = vec![0u8; message.len() + CRYPTO_SIGN_ED25519_BYTES];
687 crypto_sign_ed25519(&mut signed_message, message, &secret_key).unwrap();
688
689 for s in rejected_scalars() {
690 let mut signature: Signature = signed_message[..CRYPTO_SIGN_ED25519_BYTES]
691 .try_into()
692 .unwrap();
693 signature[32..].copy_from_slice(&s);
694 assert!(
695 matches!(
696 crypto_sign_ed25519_verify_detached(&signature, message, &public_key),
697 Err(Error::AuthenticationFailed)
698 ),
699 "S {s:02x?}"
700 );
701
702 let mut tampered = signed_message.clone();
703 tampered[32..CRYPTO_SIGN_ED25519_BYTES].copy_from_slice(&s);
704 let mut opened = vec![0u8; message.len()];
705 assert!(
706 matches!(
707 crypto_sign_ed25519_open(&mut opened, &tampered, &public_key),
708 Err(Error::AuthenticationFailed)
709 ),
710 "S {s:02x?}"
711 );
712 }
713 }
714
715 #[test]
719 fn verification_rejects_noncanonical_and_small_order_points() {
720 let message = b"point encoding policy";
721 let (public_key, secret_key) = crypto_sign_ed25519_seed_keypair(&[12u8; 32]);
722 let mut signature = [0u8; CRYPTO_SIGN_ED25519_BYTES];
723 crypto_sign_ed25519_detached(&mut signature, message, &secret_key);
724
725 for encoding in rejected_point_encodings() {
726 let mut bad_r = signature;
727 bad_r[..32].copy_from_slice(&encoding);
728 assert!(
729 matches!(
730 crypto_sign_ed25519_verify_detached(&bad_r, message, &public_key),
731 Err(Error::AuthenticationFailed)
732 ),
733 "R {encoding:02x?}"
734 );
735 assert!(
736 matches!(
737 crypto_sign_ed25519_verify_detached(&signature, message, &encoding),
738 Err(Error::InvalidKey {
739 context: crate::ErrorContext::Ed25519PublicKey,
740 })
741 ),
742 "A {encoding:02x?}"
743 );
744 }
745 }
746
747 #[test]
749 fn verification_reports_invalid_r_before_invalid_a() {
750 let message = b"error precedence";
751 let (_, secret_key) = crypto_sign_ed25519_seed_keypair(&[15u8; 32]);
752 let mut signature = [0u8; CRYPTO_SIGN_ED25519_BYTES];
753 crypto_sign_ed25519_detached(&mut signature, message, &secret_key);
754
755 let encodings = rejected_point_encodings();
756 for bad_r in &encodings {
757 for bad_a in &encodings {
758 let mut bad = signature;
759 bad[..32].copy_from_slice(bad_r);
760 assert!(
761 matches!(
762 crypto_sign_ed25519_verify_detached(&bad, message, bad_a),
763 Err(Error::AuthenticationFailed)
764 ),
765 "R {bad_r:02x?}, A {bad_a:02x?}"
766 );
767 }
768 }
769 }
770
771 #[test]
776 fn verification_with_torsion_components() {
777 use curve25519_dalek::constants::ED25519_BASEPOINT_POINT as B;
778 use curve25519_dalek::scalar::Scalar;
779
780 let a = Scalar::from_bytes_mod_order([0x42; 32]);
781 let torsion = EIGHT_TORSION[1];
782 let public_key = (B * a + torsion).compress().to_bytes();
783 let r = Scalar::from_bytes_mod_order([0x17; 32]);
784
785 let (mut accepted, mut rejected) = (0, 0);
786 for (j, &small) in EIGHT_TORSION.iter().enumerate().skip(1) {
787 for (prime_part, expect_ok) in [(B * r, true), (B * Scalar::ZERO, false)] {
788 let big_r = (prime_part + small).compress().to_bytes();
789 let (message, k) = (0u32..)
792 .map(|i| {
793 let message = i.to_le_bytes();
794 let mut hasher = Sha512::new();
795 hasher.update(&big_r);
796 hasher.update(&public_key);
797 hasher.update(&message);
798 let h: [u8; CRYPTO_HASH_SHA512_BYTES] = hasher.finalize();
799 (message, Scalar::from_bytes_mod_order_wide(&h))
800 })
801 .find(|(_, k)| -(torsion * k) == small)
802 .unwrap();
803 let s = if expect_ok { r + k * a } else { k * a };
804 let mut signature = [0u8; CRYPTO_SIGN_ED25519_BYTES];
805 signature[..32].copy_from_slice(&big_r);
806 signature[32..].copy_from_slice(s.as_bytes());
807 let result = crypto_sign_ed25519_verify_detached(&signature, &message, &public_key);
808 if expect_ok {
809 result.unwrap_or_else(|e| panic!("T[{j}], mixed-order R: {e}"));
810 accepted += 1;
811 } else {
812 assert!(
813 matches!(result, Err(Error::AuthenticationFailed)),
814 "T[{j}], small-order R"
815 );
816 rejected += 1;
817 }
818 }
819 }
820 assert_eq!((accepted, rejected), (7, 7));
821 }
822
823 #[test]
826 fn open_failure_leaves_message_buffer_untouched() {
827 let message = b"failure atomicity";
828 let (public_key, secret_key) = crypto_sign_ed25519_seed_keypair(&[13u8; 32]);
829 let (other_public_key, _) = crypto_sign_ed25519_seed_keypair(&[14u8; 32]);
830 let mut signed_message = vec![0u8; message.len() + CRYPTO_SIGN_ED25519_BYTES];
831 crypto_sign_ed25519(&mut signed_message, message, &secret_key).unwrap();
832
833 let mut tampered_r = signed_message.clone();
834 tampered_r[0] ^= 1;
835 let mut tampered_s = signed_message.clone();
836 tampered_s[32] ^= 1;
837 let mut tampered_message = signed_message.clone();
838 tampered_message[CRYPTO_SIGN_ED25519_BYTES] ^= 1;
839
840 let sentinel = vec![0xa5; message.len()];
841 for (signed, key) in [
842 (&tampered_r, &public_key),
843 (&tampered_s, &public_key),
844 (&tampered_message, &public_key),
845 (&signed_message, &other_public_key),
846 ] {
847 let mut opened = sentinel.clone();
848 assert!(matches!(
849 crypto_sign_ed25519_open(&mut opened, signed, key),
850 Err(Error::AuthenticationFailed)
851 ));
852 assert_eq!(opened, sentinel);
853 }
854
855 let mut opened = sentinel.clone();
856 crypto_sign_ed25519_open(&mut opened, &signed_message, &public_key).unwrap();
857 assert_eq!(opened, message);
858 }
859}
860
861#[cfg(all(test, dryoc_native_tests))]
862mod tests {
863 use base64::Engine as _;
864 use base64::engine::general_purpose;
865
866 use super::*;
867 use crate::edwards25519::test_vectors::{IDENTITY, NONCANONICAL_IDENTITY, mixed_order_point};
868 use crate::rng::copy_randombytes;
869
870 #[test]
871 fn test_keypair_seed() {
872 use crate::native_test_util::sign_ed25519_seed_keypair;
873
874 for _ in 0..10 {
875 let mut seed = [0u8; CRYPTO_SIGN_ED25519_SEEDBYTES];
876 copy_randombytes(&mut seed);
877
878 let (pk, sk) = crypto_sign_ed25519_seed_keypair(&seed);
879
880 let (so_pk, so_sk) = sign_ed25519_seed_keypair(&seed);
881
882 assert_eq!(
883 general_purpose::STANDARD.encode(pk),
884 general_purpose::STANDARD.encode(so_pk)
885 );
886 assert_eq!(
887 general_purpose::STANDARD.encode(sk),
888 general_purpose::STANDARD.encode(so_sk)
889 );
890 }
891 }
892
893 #[test]
894 fn test_key_conversion() {
895 use libsodium_sys::{
896 crypto_sign_ed25519_pk_to_curve25519 as so_crypto_sign_ed25519_pk_to_curve25519,
897 crypto_sign_ed25519_sk_to_curve25519 as so_crypto_sign_ed25519_sk_to_curve25519,
898 };
899
900 crate::native_test_util::init();
901
902 for _ in 0..10 {
903 let (pk, sk) = crypto_sign_ed25519_keypair();
904 let mut xpk = [0u8; CRYPTO_SCALARMULT_CURVE25519_BYTES];
905 let mut xsk = [0u8; CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES];
906 crypto_sign_ed25519_pk_to_curve25519(&mut xpk, &pk).expect("pk failed");
907 crypto_sign_ed25519_sk_to_curve25519(&mut xsk, &sk);
908
909 let mut so_xpk = [0u8; CRYPTO_SCALARMULT_CURVE25519_BYTES];
910 let mut so_xsk = [0u8; CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES];
911
912 unsafe {
913 so_crypto_sign_ed25519_pk_to_curve25519(so_xpk.as_mut_ptr(), pk.as_ptr());
914 so_crypto_sign_ed25519_sk_to_curve25519(so_xsk.as_mut_ptr(), sk.as_ptr());
915 }
916
917 assert_eq!(
918 general_purpose::STANDARD.encode(xpk),
919 general_purpose::STANDARD.encode(so_xpk)
920 );
921 assert_eq!(
922 general_purpose::STANDARD.encode(xsk),
923 general_purpose::STANDARD.encode(so_xsk)
924 );
925 }
926 }
927
928 #[test]
929 fn test_invalid_public_key_conversion_compatibility() {
930 use libsodium_sys::crypto_sign_ed25519_pk_to_curve25519 as sodium_convert;
931
932 crate::native_test_util::init();
933
934 let mixed_order = mixed_order_point().compress().to_bytes();
935
936 for invalid_key in [IDENTITY, NONCANONICAL_IDENTITY, mixed_order] {
937 let mut output = [0u8; CRYPTO_SCALARMULT_CURVE25519_BYTES];
938 let dryoc_result = crypto_sign_ed25519_pk_to_curve25519(&mut output, &invalid_key);
939 let sodium_result =
940 unsafe { sodium_convert(output.as_mut_ptr(), invalid_key.as_ptr()) };
941 assert!(dryoc_result.is_err());
942 assert_eq!(sodium_result, -1);
943 }
944 }
945
946 #[test]
947 fn test_noncanonical_signature_scalar_compatibility() {
948 use libsodium_sys::crypto_sign_verify_detached as sodium_verify;
949
950 crate::native_test_util::init();
951
952 let message = b"malleability regression";
953 let (public_key, secret_key) = crypto_sign_ed25519_seed_keypair(&[7u8; 32]);
954 let mut signature = [0u8; CRYPTO_SIGN_ED25519_BYTES];
955 crypto_sign_ed25519_detached(&mut signature, message, &secret_key);
956 super::regression_tests::add_group_order_to_s(&mut signature);
957
958 assert!(crypto_sign_ed25519_verify_detached(&signature, message, &public_key).is_err());
959 let sodium_result = unsafe {
960 sodium_verify(
961 signature.as_ptr(),
962 message.as_ptr(),
963 message.len() as u64,
964 public_key.as_ptr(),
965 )
966 };
967 assert_eq!(sodium_result, -1);
968 }
969
970 fn sodium_verify_detached(
971 signature: &Signature,
972 message: &[u8],
973 public_key: &PublicKey,
974 ) -> bool {
975 crate::native_test_util::init();
976 let result = unsafe {
977 libsodium_sys::crypto_sign_verify_detached(
978 signature.as_ptr(),
979 message.as_ptr(),
980 message.len() as u64,
981 public_key.as_ptr(),
982 )
983 };
984 assert!(result == 0 || result == -1);
985 result == 0
986 }
987
988 #[test]
992 fn test_rejected_encodings_match_libsodium() {
993 let message = b"encoding policy compatibility";
994 let (public_key, secret_key) = crypto_sign_ed25519_seed_keypair(&[15u8; 32]);
995 let mut signature = [0u8; CRYPTO_SIGN_ED25519_BYTES];
996 crypto_sign_ed25519_detached(&mut signature, message, &secret_key);
997 assert!(sodium_verify_detached(&signature, message, &public_key));
998
999 for s in super::vector_tests::rejected_scalars() {
1000 let mut bad_s = signature;
1001 bad_s[32..].copy_from_slice(&s);
1002 assert!(crypto_sign_ed25519_verify_detached(&bad_s, message, &public_key).is_err());
1003 assert!(
1004 !sodium_verify_detached(&bad_s, message, &public_key),
1005 "S {s:02x?}"
1006 );
1007 }
1008
1009 for encoding in super::vector_tests::rejected_point_encodings() {
1010 let mut bad_r = signature;
1011 bad_r[..32].copy_from_slice(&encoding);
1012 assert!(crypto_sign_ed25519_verify_detached(&bad_r, message, &public_key).is_err());
1013 assert!(
1014 !sodium_verify_detached(&bad_r, message, &public_key),
1015 "R {encoding:02x?}"
1016 );
1017
1018 assert!(crypto_sign_ed25519_verify_detached(&signature, message, &encoding).is_err());
1019 assert!(
1020 !sodium_verify_detached(&signature, message, &encoding),
1021 "A {encoding:02x?}"
1022 );
1023 }
1024 }
1025
1026 #[test]
1032 fn test_mixed_order_points_match_libsodium() {
1033 use curve25519_dalek::constants::{ED25519_BASEPOINT_TABLE, EIGHT_TORSION};
1034 use curve25519_dalek::edwards::CompressedEdwardsY;
1035
1036 let seed = [16u8; 32];
1037 let (public_key, _) = crypto_sign_ed25519_seed_keypair(&seed);
1038 let mut h: [u8; CRYPTO_HASH_SHA512_BYTES] = Sha512::compute(&seed);
1039 let a = Scalar::from_bytes_mod_order(clamp_hash(&mut h));
1040 let a_point = CompressedEdwardsY(public_key).decompress().unwrap();
1041 let torsion = EIGHT_TORSION[1];
1042 let mixed_key = (a_point + torsion).compress().to_bytes();
1043 assert!(!crate::classic::crypto_core::crypto_core_ed25519_is_valid_point(&mixed_key));
1044
1045 let r = Scalar::from_bytes_mod_order([17u8; 32]);
1046 let big_r = (ED25519_BASEPOINT_TABLE * &r).compress().to_bytes();
1047 let challenge = |big_r: &[u8; 32], key: &[u8; 32], message: &[u8]| {
1048 let mut hasher = Sha512::new();
1049 hasher.update(big_r);
1050 hasher.update(key);
1051 hasher.update(message);
1052 Scalar::from_bytes_mod_order_wide(&hasher.finalize())
1053 };
1054 let (message, k) = (0u32..)
1055 .map(|i| {
1056 let message = format!("mixed order {i}").into_bytes();
1057 let k = challenge(&big_r, &mixed_key, &message);
1058 (message, k)
1059 })
1060 .find(|(_, k)| k.as_bytes()[0] & 7 == 0)
1061 .unwrap();
1062 let mut signature = [0u8; CRYPTO_SIGN_ED25519_BYTES];
1063 signature[..32].copy_from_slice(&big_r);
1064 signature[32..].copy_from_slice((r + k * a).as_bytes());
1065
1066 assert!(crypto_sign_ed25519_verify_detached(&signature, &message, &mixed_key).is_ok());
1067 assert!(sodium_verify_detached(&signature, &message, &mixed_key));
1068
1069 let mixed_r = ((ED25519_BASEPOINT_TABLE * &r) + torsion)
1072 .compress()
1073 .to_bytes();
1074 let k = challenge(&mixed_r, &public_key, &message);
1075 signature[..32].copy_from_slice(&mixed_r);
1076 signature[32..].copy_from_slice((r + k * a).as_bytes());
1077 assert!(matches!(
1078 crypto_sign_ed25519_verify_detached(&signature, &message, &public_key),
1079 Err(Error::AuthenticationFailed)
1080 ));
1081 assert!(!sodium_verify_detached(&signature, &message, &public_key));
1082 }
1083
1084 #[test]
1085 fn test_secret_key_extraction() {
1086 use libsodium_sys::{
1087 crypto_sign_ed25519_sk_to_pk as so_crypto_sign_ed25519_sk_to_pk,
1088 crypto_sign_ed25519_sk_to_seed as so_crypto_sign_ed25519_sk_to_seed,
1089 };
1090
1091 crate::native_test_util::init();
1092
1093 for _ in 0..10 {
1094 let (pk, sk) = crypto_sign_ed25519_keypair();
1095 let mut seed = [0u8; CRYPTO_SIGN_ED25519_SEEDBYTES];
1096 let mut extracted_pk = [0u8; CRYPTO_SIGN_ED25519_PUBLICKEYBYTES];
1097 crypto_sign_ed25519_sk_to_seed(&mut seed, &sk);
1098 crypto_sign_ed25519_sk_to_pk(&mut extracted_pk, &sk);
1099
1100 let mut so_seed = [0u8; CRYPTO_SIGN_ED25519_SEEDBYTES];
1101 let mut so_pk = [0u8; CRYPTO_SIGN_ED25519_PUBLICKEYBYTES];
1102
1103 unsafe {
1104 so_crypto_sign_ed25519_sk_to_seed(so_seed.as_mut_ptr(), sk.as_ptr());
1105 so_crypto_sign_ed25519_sk_to_pk(so_pk.as_mut_ptr(), sk.as_ptr());
1106 }
1107
1108 assert_eq!(seed, so_seed);
1109 assert_eq!(extracted_pk, pk);
1110 assert_eq!(extracted_pk, so_pk);
1111 }
1112 }
1113}