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::constants::ED25519_BASEPOINT_TABLE;
33use curve25519_dalek::edwards::EdwardsPoint;
34use curve25519_dalek::scalar::Scalar;
35use zeroize::Zeroize;
36
37use super::crypto_core::decompress_canonical_ed25519_point;
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::error::Error;
45use crate::sha512::Sha512;
46
47/// Type alias for an Ed25519 public key.
48pub type PublicKey = [u8; CRYPTO_SIGN_ED25519_PUBLICKEYBYTES];
49/// Type alias for an Ed25519 secret key with seed bytes.
50pub type SecretKey = [u8; CRYPTO_SIGN_ED25519_SECRETKEYBYTES];
51/// Type alias for an Ed25519 signature.
52pub type Signature = [u8; CRYPTO_SIGN_ED25519_BYTES];
53
54const DOM2PREFIX: &[u8] = b"SigEd25519 no Ed25519 collisions\x01\x00";
55
56/// In-place variant of [`crypto_sign_ed25519_seed_keypair`].
57#[inline]
58pub(crate) fn crypto_sign_ed25519_seed_keypair_inplace(
59    public_key: &mut PublicKey,
60    secret_key: &mut SecretKey,
61    seed: &[u8; CRYPTO_SIGN_ED25519_SEEDBYTES],
62) {
63    let mut hash: [u8; CRYPTO_HASH_SHA512_BYTES] = Sha512::compute(seed);
64
65    let mut clamped = clamp_hash(&mut hash);
66    let mut sk = Scalar::from_bytes_mod_order(clamped);
67    clamped.zeroize();
68
69    let pk = (ED25519_BASEPOINT_TABLE * &sk).compress();
70    secret_key[..CRYPTO_SIGN_ED25519_SEEDBYTES].copy_from_slice(seed);
71    secret_key[CRYPTO_SIGN_ED25519_SEEDBYTES..].copy_from_slice(pk.as_bytes());
72
73    public_key.copy_from_slice(pk.as_bytes());
74
75    sk.zeroize();
76}
77
78/// Generates an Ed25519 keypair from `seed` which can be used for signing
79/// messages.
80pub(crate) fn crypto_sign_ed25519_seed_keypair(
81    seed: &[u8; CRYPTO_SIGN_ED25519_SEEDBYTES],
82) -> (PublicKey, SecretKey) {
83    let mut public_key = PublicKey::default();
84    let mut secret_key = [0u8; CRYPTO_SIGN_ED25519_SECRETKEYBYTES];
85
86    crypto_sign_ed25519_seed_keypair_inplace(&mut public_key, &mut secret_key, seed);
87
88    (public_key, secret_key)
89}
90
91/// In-place variant of [`crypto_sign_ed25519_keypair`].
92#[inline]
93pub(crate) fn crypto_sign_ed25519_keypair_inplace(
94    public_key: &mut PublicKey,
95    secret_key: &mut SecretKey,
96) {
97    use crate::rng::copy_randombytes;
98    let mut seed = [0u8; CRYPTO_SIGN_ED25519_SEEDBYTES];
99    copy_randombytes(&mut seed);
100    crypto_sign_ed25519_seed_keypair_inplace(public_key, secret_key, &seed);
101    seed.zeroize();
102}
103
104/// Generates a random Ed25519 keypair which can be used for signing
105/// messages.
106pub(crate) fn crypto_sign_ed25519_keypair() -> (PublicKey, SecretKey) {
107    let mut public_key = PublicKey::default();
108    let mut secret_key = [0u8; CRYPTO_SIGN_ED25519_SECRETKEYBYTES];
109    crypto_sign_ed25519_keypair_inplace(&mut public_key, &mut secret_key);
110
111    (public_key, secret_key)
112}
113
114fn clamp_hash(
115    hash: &mut [u8; CRYPTO_HASH_SHA512_BYTES],
116) -> [u8; CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES] {
117    let mut scalar = [0u8; CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES];
118    scalar.copy_from_slice(&hash[..CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES]);
119    hash.zeroize();
120    scalar[0] &= 248;
121    scalar[31] &= 127;
122    scalar[31] |= 64;
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_canonical_ed25519_point(ed25519_public_key)
140        .filter(|point| !point.is_small_order() && point.is_torsion_free())
141        .ok_or(Error::invalid_key(crate::ErrorContext::Ed25519PublicKey))?;
142    x25519_public_key.copy_from_slice(ep.to_montgomery().as_bytes());
143
144    Ok(())
145}
146
147/// Converts an Ed25519 secret key `ed25519_secret_key` into an X25519 secret
148/// key, placing the result into `x25519_secret_key`.
149///
150/// Compatible with libsodium's `crypto_sign_ed25519_sk_to_curve25519`.
151pub fn crypto_sign_ed25519_sk_to_curve25519(
152    x25519_secret_key: &mut [u8; CRYPTO_SCALARMULT_CURVE25519_BYTES],
153    ed25519_secret_key: &SecretKey,
154) {
155    let mut hash: [u8; CRYPTO_HASH_SHA512_BYTES] = Sha512::compute(&ed25519_secret_key[..32]);
156    let mut scalar = clamp_hash(&mut hash);
157    x25519_secret_key.copy_from_slice(&scalar);
158    scalar.zeroize()
159}
160
161/// Extracts the Ed25519 seed from `secret_key`, placing the result into `seed`.
162///
163/// Compatible with libsodium's `crypto_sign_ed25519_sk_to_seed`.
164pub fn crypto_sign_ed25519_sk_to_seed(
165    seed: &mut [u8; CRYPTO_SIGN_ED25519_SEEDBYTES],
166    secret_key: &SecretKey,
167) {
168    seed.copy_from_slice(&secret_key[..CRYPTO_SIGN_ED25519_SEEDBYTES]);
169}
170
171/// Extracts the Ed25519 public key from `secret_key`, placing the result into
172/// `public_key`.
173///
174/// Compatible with libsodium's `crypto_sign_ed25519_sk_to_pk`.
175pub fn crypto_sign_ed25519_sk_to_pk(public_key: &mut PublicKey, secret_key: &SecretKey) {
176    public_key.copy_from_slice(
177        &secret_key[CRYPTO_SIGN_ED25519_SEEDBYTES..CRYPTO_SIGN_ED25519_SECRETKEYBYTES],
178    );
179}
180
181pub(crate) fn crypto_sign_ed25519(
182    signed_message: &mut [u8],
183    message: &[u8],
184    secret_key: &SecretKey,
185) -> Result<(), Error> {
186    if signed_message.len() != message.len() + CRYPTO_SIGN_ED25519_BYTES {
187        Err(length_error!(
188            crate::ErrorContext::SignedMessage,
189            signed_message.len(),
190            exact message.len() + CRYPTO_SIGN_ED25519_BYTES
191        ))
192    } else {
193        let (sig, sm) = signed_message.split_at_mut(CRYPTO_SIGN_ED25519_BYTES);
194        let sig: &mut [u8; CRYPTO_SIGN_ED25519_BYTES] =
195            <&mut [u8; CRYPTO_SIGN_ED25519_BYTES]>::try_from(sig).unwrap();
196        sm.copy_from_slice(message);
197        crypto_sign_ed25519_detached(sig, message, secret_key)
198    }
199}
200
201pub(crate) fn crypto_sign_ed25519_detached(
202    signature: &mut Signature,
203    message: &[u8],
204    secret_key: &SecretKey,
205) -> Result<(), Error> {
206    crypto_sign_ed25519_detached_impl(signature, message, secret_key, false)
207}
208
209#[inline]
210fn crypto_sign_ed25519_detached_impl(
211    signature: &mut Signature,
212    message: &[u8],
213    secret_key: &SecretKey,
214    prehashed: bool,
215) -> Result<(), Error> {
216    if signature.len() != CRYPTO_SIGN_ED25519_BYTES {
217        Err(length_error!(
218            crate::ErrorContext::Signature,
219            signature.len(),
220            exact CRYPTO_SIGN_ED25519_BYTES
221        ))
222    } else {
223        let mut az: [u8; CRYPTO_HASH_SHA512_BYTES] = Sha512::compute(&secret_key[..32]);
224
225        let mut hasher = Sha512::new();
226        if prehashed {
227            hasher.update(DOM2PREFIX);
228        }
229        hasher.update(&az[32..]);
230        hasher.update(message);
231        let mut nonce: [u8; CRYPTO_HASH_SHA512_BYTES] = hasher.finalize();
232
233        signature[32..].copy_from_slice(&secret_key[32..]);
234
235        let mut r = Scalar::from_bytes_mod_order_wide(&nonce);
236        let big_r = (ED25519_BASEPOINT_TABLE * &r).compress();
237
238        signature[..32].copy_from_slice(big_r.as_bytes());
239
240        let mut hasher = Sha512::new();
241        if prehashed {
242            hasher.update(DOM2PREFIX);
243        }
244        hasher.update(signature);
245        hasher.update(message);
246        let mut hram: [u8; CRYPTO_HASH_SHA512_BYTES] = hasher.finalize();
247
248        let mut k = Scalar::from_bytes_mod_order_wide(&hram);
249        let mut clamped = clamp_hash(&mut az);
250        let mut signing_scalar = Scalar::from_bytes_mod_order(clamped);
251        clamped.zeroize();
252        let mut sig = (k * signing_scalar) + r;
253
254        signature[32..].copy_from_slice(sig.as_bytes());
255
256        az.zeroize();
257        nonce.zeroize();
258        hram.zeroize();
259        r.zeroize();
260        k.zeroize();
261        signing_scalar.zeroize();
262        sig.zeroize();
263
264        Ok(())
265    }
266}
267
268pub(crate) fn crypto_sign_ed25519_verify_detached(
269    signature: &Signature,
270    message: &[u8],
271    public_key: &PublicKey,
272) -> Result<(), Error> {
273    crypto_sign_ed25519_verify_detached_impl(signature, message, public_key, false)
274}
275
276fn crypto_sign_ed25519_verify_detached_impl(
277    signature: &Signature,
278    message: &[u8],
279    public_key: &PublicKey,
280    prehashed: bool,
281) -> Result<(), Error> {
282    let s_bytes = *<&[u8; CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES]>::try_from(&signature[32..])
283        .map_err(|_| Error::AuthenticationFailed)?;
284    let s = Option::<Scalar>::from(Scalar::from_canonical_bytes(s_bytes))
285        .ok_or(Error::AuthenticationFailed)?;
286    let r_bytes = <&[u8; CRYPTO_SIGN_ED25519_PUBLICKEYBYTES]>::try_from(&signature[..32])
287        .map_err(|_| Error::AuthenticationFailed)?;
288    let big_r = decompress_canonical_ed25519_point(r_bytes).ok_or(Error::AuthenticationFailed)?;
289    if big_r.is_small_order() {
290        return Err(Error::AuthenticationFailed);
291    }
292    let pk = decompress_canonical_ed25519_point(public_key)
293        .ok_or(Error::invalid_key(crate::ErrorContext::Ed25519PublicKey))?;
294    if pk.is_small_order() {
295        return Err(Error::invalid_key(crate::ErrorContext::Ed25519PublicKey));
296    }
297
298    let mut hasher = Sha512::new();
299    if prehashed {
300        hasher.update(DOM2PREFIX);
301    }
302    hasher.update(&signature[..32]);
303    hasher.update(public_key);
304    hasher.update(message);
305    let h: [u8; CRYPTO_HASH_SHA512_BYTES] = hasher.finalize();
306
307    let k = Scalar::from_bytes_mod_order_wide(&h);
308
309    let sig_r = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(-pk), &s);
310
311    if sig_r == big_r {
312        Ok(())
313    } else {
314        Err(Error::AuthenticationFailed)
315    }
316}
317
318pub(crate) fn crypto_sign_ed25519_open(
319    message: &mut [u8],
320    signed_message: &[u8],
321    public_key: &PublicKey,
322) -> Result<(), Error> {
323    if signed_message.len() < CRYPTO_SIGN_ED25519_BYTES {
324        Err(length_error!(
325            crate::ErrorContext::SignedMessage,
326            signed_message.len(),
327            min CRYPTO_SIGN_ED25519_BYTES
328        ))
329    } else if message.len() != signed_message.len() - CRYPTO_SIGN_ED25519_BYTES {
330        Err(length_error!(
331            crate::ErrorContext::Message,
332            message.len(),
333            exact signed_message.len() - CRYPTO_SIGN_ED25519_BYTES
334        ))
335    } else {
336        let (sig, sm) = signed_message.split_at(CRYPTO_SIGN_ED25519_BYTES);
337        let sig: &[u8; CRYPTO_SIGN_ED25519_BYTES] =
338            <&[u8; CRYPTO_SIGN_ED25519_BYTES]>::try_from(sig).unwrap();
339        crypto_sign_ed25519_verify_detached(sig, sm, public_key)?;
340        message.copy_from_slice(sm);
341        Ok(())
342    }
343}
344
345pub(crate) struct Ed25519SignerState {
346    hasher: Sha512,
347}
348
349pub(crate) fn crypto_sign_ed25519ph_init() -> Ed25519SignerState {
350    Ed25519SignerState {
351        hasher: Sha512::new(),
352    }
353}
354
355pub(crate) fn crypto_sign_ed25519ph_update(state: &mut Ed25519SignerState, message: &[u8]) {
356    state.hasher.update(message)
357}
358
359pub(crate) fn crypto_sign_ed25519ph_final_create(
360    state: Ed25519SignerState,
361    signature: &mut Signature,
362    secret_key: &SecretKey,
363) -> Result<(), Error> {
364    let mut hash: [u8; CRYPTO_HASH_SHA512_BYTES] = state.hasher.finalize();
365    let res = crypto_sign_ed25519_detached_impl(signature, &hash, secret_key, true);
366    hash.zeroize();
367    res
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
385    const ED25519_GROUP_ORDER: [u8; 32] = [
386        0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde,
387        0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
388        0x00, 0x10,
389    ];
390
391    pub(super) fn add_group_order_to_s(signature: &mut Signature) {
392        let mut carry = 0u16;
393        for (s, order) in signature[32..].iter_mut().zip(ED25519_GROUP_ORDER) {
394            let sum = u16::from(*s) + u16::from(order) + carry;
395            *s = sum as u8;
396            carry = sum >> 8;
397        }
398        assert_eq!(carry, 0, "a reduced Ed25519 scalar plus L fits in 256 bits");
399    }
400
401    #[test]
402    fn verification_rejects_s_plus_group_order() {
403        let message = b"malleability regression";
404        let (public_key, secret_key) = crypto_sign_ed25519_seed_keypair(&[7u8; 32]);
405
406        let mut signature = [0u8; CRYPTO_SIGN_ED25519_BYTES];
407        crypto_sign_ed25519_detached(&mut signature, message, &secret_key).unwrap();
408        crypto_sign_ed25519_verify_detached(&signature, message, &public_key).unwrap();
409        add_group_order_to_s(&mut signature);
410        assert!(matches!(
411            crypto_sign_ed25519_verify_detached(&signature, message, &public_key),
412            Err(Error::AuthenticationFailed)
413        ));
414
415        let mut signed_message = vec![0u8; message.len() + CRYPTO_SIGN_ED25519_BYTES];
416        crypto_sign_ed25519(&mut signed_message, message, &secret_key).unwrap();
417        let embedded_signature =
418            <&mut Signature>::try_from(&mut signed_message[..CRYPTO_SIGN_ED25519_BYTES]).unwrap();
419        add_group_order_to_s(embedded_signature);
420        let mut opened_message = vec![0u8; message.len()];
421        assert!(matches!(
422            crypto_sign_ed25519_open(&mut opened_message, &signed_message, &public_key),
423            Err(Error::AuthenticationFailed)
424        ));
425
426        let mut signer = crypto_sign_ed25519ph_init();
427        crypto_sign_ed25519ph_update(&mut signer, message);
428        let mut prehash_signature = [0u8; CRYPTO_SIGN_ED25519_BYTES];
429        crypto_sign_ed25519ph_final_create(signer, &mut prehash_signature, &secret_key).unwrap();
430        add_group_order_to_s(&mut prehash_signature);
431
432        let mut verifier = crypto_sign_ed25519ph_init();
433        crypto_sign_ed25519ph_update(&mut verifier, message);
434        assert!(matches!(
435            crypto_sign_ed25519ph_final_verify(verifier, &prehash_signature, &public_key),
436            Err(Error::AuthenticationFailed)
437        ));
438    }
439
440    #[test]
441    fn public_key_conversion_rejects_invalid_edwards_points() {
442        let identity = {
443            let mut point = [0u8; 32];
444            point[0] = 1;
445            point
446        };
447        let noncanonical_identity = {
448            let mut point = [0xff; 32];
449            point[0] = 0xee;
450            point[31] = 0x7f;
451            point
452        };
453        let mixed_order = (curve25519_dalek::constants::ED25519_BASEPOINT_POINT
454            + curve25519_dalek::constants::EIGHT_TORSION[1])
455            .compress()
456            .to_bytes();
457        let mixed_point = decompress_canonical_ed25519_point(&mixed_order).unwrap();
458        assert!(!mixed_point.is_small_order());
459        assert!(!mixed_point.is_torsion_free());
460
461        for invalid_key in [identity, noncanonical_identity, mixed_order] {
462            let mut output = [0xa5; CRYPTO_SCALARMULT_CURVE25519_BYTES];
463            assert!(crypto_sign_ed25519_pk_to_curve25519(&mut output, &invalid_key).is_err());
464            assert_eq!(
465                output, [0xa5; CRYPTO_SCALARMULT_CURVE25519_BYTES],
466                "conversion failure must not modify the output"
467            );
468        }
469    }
470
471    #[test]
472    fn public_key_conversion_accepts_valid_high_sign_bit() {
473        let basepoint = curve25519_dalek::constants::ED25519_BASEPOINT_COMPRESSED.to_bytes();
474        let mut negative_basepoint = basepoint;
475        negative_basepoint[31] |= 0x80;
476
477        let mut positive_output = [0u8; CRYPTO_SCALARMULT_CURVE25519_BYTES];
478        let mut negative_output = [0u8; CRYPTO_SCALARMULT_CURVE25519_BYTES];
479        crypto_sign_ed25519_pk_to_curve25519(&mut positive_output, &basepoint).unwrap();
480        crypto_sign_ed25519_pk_to_curve25519(&mut negative_output, &negative_basepoint).unwrap();
481        assert_eq!(positive_output, negative_output);
482    }
483}
484
485#[cfg(all(test, dryoc_native_tests))]
486mod tests {
487    use base64::Engine as _;
488    use base64::engine::general_purpose;
489
490    use super::*;
491    use crate::rng::copy_randombytes;
492
493    #[test]
494    fn test_keypair_seed() {
495        use sodiumoxide::crypto::sign;
496
497        for _ in 0..10 {
498            let mut seed = [0u8; CRYPTO_SIGN_ED25519_SEEDBYTES];
499            copy_randombytes(&mut seed);
500
501            let (pk, sk) = crypto_sign_ed25519_seed_keypair(&seed);
502
503            let (so_pk, so_sk) =
504                sign::keypair_from_seed(&sign::Seed::from_slice(&seed).expect("seed failed"));
505
506            assert_eq!(
507                general_purpose::STANDARD.encode(pk),
508                general_purpose::STANDARD.encode(so_pk.0)
509            );
510            assert_eq!(
511                general_purpose::STANDARD.encode(sk),
512                general_purpose::STANDARD.encode(so_sk.0)
513            );
514        }
515    }
516
517    #[test]
518    fn test_key_conversion() {
519        use libsodium_sys::{
520            crypto_sign_ed25519_pk_to_curve25519 as so_crypto_sign_ed25519_pk_to_curve25519,
521            crypto_sign_ed25519_sk_to_curve25519 as so_crypto_sign_ed25519_sk_to_curve25519,
522        };
523
524        for _ in 0..10 {
525            let (pk, sk) = crypto_sign_ed25519_keypair();
526            let mut xpk = [0u8; CRYPTO_SCALARMULT_CURVE25519_BYTES];
527            let mut xsk = [0u8; CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES];
528            crypto_sign_ed25519_pk_to_curve25519(&mut xpk, &pk).expect("pk failed");
529            crypto_sign_ed25519_sk_to_curve25519(&mut xsk, &sk);
530
531            let mut so_xpk = [0u8; CRYPTO_SCALARMULT_CURVE25519_BYTES];
532            let mut so_xsk = [0u8; CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES];
533
534            unsafe {
535                so_crypto_sign_ed25519_pk_to_curve25519(so_xpk.as_mut_ptr(), pk.as_ptr());
536                so_crypto_sign_ed25519_sk_to_curve25519(so_xsk.as_mut_ptr(), sk.as_ptr());
537            }
538
539            assert_eq!(
540                general_purpose::STANDARD.encode(xpk),
541                general_purpose::STANDARD.encode(so_xpk)
542            );
543            assert_eq!(
544                general_purpose::STANDARD.encode(xsk),
545                general_purpose::STANDARD.encode(so_xsk)
546            );
547        }
548    }
549
550    #[test]
551    fn test_invalid_public_key_conversion_compatibility() {
552        use libsodium_sys::crypto_sign_ed25519_pk_to_curve25519 as sodium_convert;
553
554        let identity = {
555            let mut point = [0u8; 32];
556            point[0] = 1;
557            point
558        };
559        let noncanonical_identity = {
560            let mut point = [0xff; 32];
561            point[0] = 0xee;
562            point[31] = 0x7f;
563            point
564        };
565        let mixed_order = (curve25519_dalek::constants::ED25519_BASEPOINT_POINT
566            + curve25519_dalek::constants::EIGHT_TORSION[1])
567            .compress()
568            .to_bytes();
569
570        for invalid_key in [identity, noncanonical_identity, mixed_order] {
571            let mut output = [0u8; CRYPTO_SCALARMULT_CURVE25519_BYTES];
572            let dryoc_result = crypto_sign_ed25519_pk_to_curve25519(&mut output, &invalid_key);
573            let sodium_result =
574                unsafe { sodium_convert(output.as_mut_ptr(), invalid_key.as_ptr()) };
575            assert!(dryoc_result.is_err());
576            assert_eq!(sodium_result, -1);
577        }
578    }
579
580    #[test]
581    fn test_noncanonical_signature_scalar_compatibility() {
582        use libsodium_sys::crypto_sign_verify_detached as sodium_verify;
583
584        let message = b"malleability regression";
585        let (public_key, secret_key) = crypto_sign_ed25519_seed_keypair(&[7u8; 32]);
586        let mut signature = [0u8; CRYPTO_SIGN_ED25519_BYTES];
587        crypto_sign_ed25519_detached(&mut signature, message, &secret_key).unwrap();
588        super::regression_tests::add_group_order_to_s(&mut signature);
589
590        assert!(crypto_sign_ed25519_verify_detached(&signature, message, &public_key).is_err());
591        let sodium_result = unsafe {
592            sodium_verify(
593                signature.as_ptr(),
594                message.as_ptr(),
595                message.len() as u64,
596                public_key.as_ptr(),
597            )
598        };
599        assert_eq!(sodium_result, -1);
600    }
601
602    #[test]
603    fn test_secret_key_extraction() {
604        use libsodium_sys::{
605            crypto_sign_ed25519_sk_to_pk as so_crypto_sign_ed25519_sk_to_pk,
606            crypto_sign_ed25519_sk_to_seed as so_crypto_sign_ed25519_sk_to_seed,
607        };
608
609        for _ in 0..10 {
610            let (pk, sk) = crypto_sign_ed25519_keypair();
611            let mut seed = [0u8; CRYPTO_SIGN_ED25519_SEEDBYTES];
612            let mut extracted_pk = [0u8; CRYPTO_SIGN_ED25519_PUBLICKEYBYTES];
613            crypto_sign_ed25519_sk_to_seed(&mut seed, &sk);
614            crypto_sign_ed25519_sk_to_pk(&mut extracted_pk, &sk);
615
616            let mut so_seed = [0u8; CRYPTO_SIGN_ED25519_SEEDBYTES];
617            let mut so_pk = [0u8; CRYPTO_SIGN_ED25519_PUBLICKEYBYTES];
618
619            unsafe {
620                so_crypto_sign_ed25519_sk_to_seed(so_seed.as_mut_ptr(), sk.as_ptr());
621                so_crypto_sign_ed25519_sk_to_pk(so_pk.as_mut_ptr(), sk.as_ptr());
622            }
623
624            assert_eq!(seed, so_seed);
625            assert_eq!(extracted_pk, pk);
626            assert_eq!(extracted_pk, so_pk);
627        }
628    }
629}