Skip to main content

dryoc/classic/
crypto_sign_ed25519.rs

1//! # Ed25519 signing helpers
2//!
3//! This module implements libsodium's Ed25519 helper functions, including
4//! Ed25519 to Curve25519 conversion and secret-key extraction. You can use the
5//! conversion functions when you want to sign messages with the same keys used
6//! to encrypt messages (i.e., using a public-key box).
7//!
8//! Generally speaking, you should avoid signing and encrypting with the same
9//! keypair. Additionally, an encrypted box doesn't need to be separately signed
10//! as it already includes a message authentication code.
11//!
12//! ## Classic API example
13//!
14//! ```
15//! use dryoc::classic::crypto_sign::{
16//!     crypto_sign_ed25519_sk_to_pk, crypto_sign_ed25519_sk_to_seed, crypto_sign_seed_keypair,
17//! };
18//! use dryoc::constants::{CRYPTO_SIGN_PUBLICKEYBYTES, CRYPTO_SIGN_SEEDBYTES};
19//!
20//! let seed = [7u8; CRYPTO_SIGN_SEEDBYTES];
21//! let (public_key, secret_key) = crypto_sign_seed_keypair(&seed);
22//!
23//! let mut extracted_seed = [0u8; CRYPTO_SIGN_SEEDBYTES];
24//! let mut extracted_public_key = [0u8; CRYPTO_SIGN_PUBLICKEYBYTES];
25//! crypto_sign_ed25519_sk_to_seed(&mut extracted_seed, &secret_key);
26//! crypto_sign_ed25519_sk_to_pk(&mut extracted_public_key, &secret_key);
27//!
28//! assert_eq!(extracted_seed, seed);
29//! assert_eq!(extracted_public_key, public_key);
30//! ```
31
32use 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
50/// Type alias for an Ed25519 public key.
51pub type PublicKey = [u8; CRYPTO_SIGN_ED25519_PUBLICKEYBYTES];
52/// Type alias for an Ed25519 secret key with seed bytes.
53pub type SecretKey = [u8; CRYPTO_SIGN_ED25519_SECRETKEYBYTES];
54/// Type alias for an Ed25519 signature.
55pub type Signature = [u8; CRYPTO_SIGN_ED25519_BYTES];
56
57const DOM2PREFIX: &[u8] = b"SigEd25519 no Ed25519 collisions\x01\x00";
58
59/// In-place variant of [`crypto_sign_ed25519_seed_keypair`].
60#[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
80/// Generates an Ed25519 keypair from `seed` which can be used for signing
81/// messages.
82pub(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/// In-place variant of [`crypto_sign_ed25519_keypair`].
94#[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
106/// Generates a random Ed25519 keypair which can be used for signing
107/// messages.
108pub(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
126/// Converts an Ed25519 public key `ed25519_public_key` into an X25519 public
127/// key, placing the result into `x25519_public_key` upon success.
128///
129/// Compatible with libsodium's `crypto_sign_ed25519_pk_to_curve25519`.
130///
131/// # Errors
132///
133/// Returns an error if `ed25519_public_key` is noncanonical, has small order,
134/// is not on the curve, or is not in the main subgroup.
135pub 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
146/// Converts an Ed25519 secret key `ed25519_secret_key` into an X25519 secret
147/// key, placing the result into `x25519_secret_key`.
148///
149/// Compatible with libsodium's `crypto_sign_ed25519_sk_to_curve25519`.
150pub 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
160/// Extracts the Ed25519 seed from `secret_key`, placing the result into `seed`.
161///
162/// Compatible with libsodium's `crypto_sign_ed25519_sk_to_seed`.
163pub 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
170/// Extracts the Ed25519 public key from `secret_key`, placing the result into
171/// `public_key`.
172///
173/// Compatible with libsodium's `crypto_sign_ed25519_sk_to_pk`.
174pub 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    // `R` is not decompressed on the accepting path (see the final check);
281    // when `A` is rejected it is, so that an invalid or small-order `R` is
282    // still reported first.
283    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    // R' = [k](-A) + [s]B must equal R; both sides are public.
306    let sig_r = pk
307        .neg()
308        .double_scalar_mul_basepoint_vartime(&k.to_bytes(), &s.to_bytes());
309
310    // `R` must be the canonical encoding of a point of order above 8 equal
311    // to `R'`. The canonical encoding of `R'` is `R`'s bytes exactly when they
312    // decompress canonically to `R'`, so comparing encodings is that check
313    // without `R`'s square root (an inversion is much cheaper), and `R'` has
314    // the order of `R`.
315    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    /// RFC 8032 section 7.1 vector: seed, public key, message and signature.
486    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    /// RFC 8032 section 7.1: TEST 1, 2, 3, 1024 and SHA(abc).
529    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    /// RFC 8032 section 7.3, TEST abc: Ed25519ph with the SHA(abc) key.
563    const ED25519PH_VECTOR: Vector = Vector {
564        seed: "833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42",
565        public_key: "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf",
566        message: "616263",
567        signature: "98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae4131f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406",
568    };
569
570    /// Point encodings every verifier must refuse as `R` or `A`: the three
571    /// non-canonical `y >= p` values `p`, `p + 1` and `2^255 - 1` with both
572    /// sign bits, the eight small-order points, and the two alternate
573    /// encodings of the `x = 0` points (`y = 1` and `y = -1` with the sign
574    /// bit set).
575    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    /// `S` values a canonical verifier must refuse: `L`, `L + 1` and
595    /// `2^256 - 1`.
596    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    /// The incremental interface is Ed25519ph: it produces the section 7.3
640    /// signature however the message is split, and its signatures are not
641    /// interchangeable with plain Ed25519 ones over the same bytes.
642    #[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    /// A non-canonical or small-order `R` fails authentication; the same
716    /// encodings as `A` are rejected as an invalid key, before any curve
717    /// arithmetic.
718    #[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    /// With both `R` and `A` rejected, `R`'s failure is the one reported.
748    #[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    /// Signatures under a mixed-order key `A = [a]B + T` (`T` of order 8)
772    /// whose equation `[s]B = R + [k]A` holds: accepted when `R` has a
773    /// prime-order part (`R = [r]B + P` with `P = -[k]T`), rejected when `R`
774    /// is the small-order point `P` itself (`r = 0`), as libsodium does.
775    #[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                // Find a message whose `k` makes `-[k]T` equal the chosen
790                // small-order part, so the equation holds.
791                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    /// `crypto_sign_open` verifies before it copies: a failed open leaves
824    /// the caller's message buffer exactly as it was.
825    #[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    /// libsodium (1.0.18 and later: `sc25519_is_canonical`,
989    /// `ge25519_is_canonical`, `ge25519_has_small_order`) refuses the same
990    /// `S` values and the same `R` and `A` encodings.
991    #[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    /// Verification checks `A` for small order but, like libsodium, not for
1027    /// membership in the prime-order subgroup: a signature made for the
1028    /// mixed-order key `A + T` (`T` of order 8) verifies whenever the
1029    /// challenge `k` is a multiple of 8, and a torsion component in `R`
1030    /// never verifies. Both outcomes agree with libsodium.
1031    #[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        // A torsion component in R: the challenge commits to the encoding of
1070        // R + T, but [S]B - [k]A is R.
1071        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}