Skip to main content

dryoc/
sign.rs

1//! # Public-key signatures
2//!
3//! This module provides libsodium-compatible Ed25519 signatures. A signer uses
4//! a secret key to sign a message. Anyone with the corresponding public key can
5//! verify that signature and detect changes to the message. Signatures do not
6//! encrypt the message.
7//!
8//! [`SigningKeyPair::sign`] signs a complete message with Ed25519. Use
9//! [`Ed25519phSigner`] when the message is too large to keep in memory or
10//! arrives in parts. It implements Ed25519ph (prehashed Ed25519, RFC 8032),
11//! which is a different signature scheme: its signatures cannot be verified by
12//! the single-part Ed25519 API, or vice versa.
13//!
14//! The verifier must obtain the signer's public key through a trusted channel.
15//! A signature only proves control of the matching secret key; it does not
16//! establish who owns that key.
17//!
18//! Keep signing and encryption keys separate. Although Ed25519 keys can be
19//! converted to X25519 keys or derived from the same seed, doing so couples two
20//! distinct security roles.
21//!
22//! Signing secret keys include both the seed and public key. Use
23//! [`secret_key_to_seed`], [`secret_key_to_public_key`],
24//! [`SigningKeyPair::to_seed`], or [`SigningKeyPair::to_public_key`] to extract
25//! those parts when interoperating with libsodium-style key storage.
26//!
27//! ## Rustaceous API example, single-part
28//!
29//! ```
30//! # #[cfg(feature = "alloc")]
31//! # {
32//! use dryoc::sign::*;
33//!
34//! // Generate a random keypair, using default types
35//! let keypair = StackSigningKeyPair::generate();
36//! let message = b"Fair is foul, and foul is fair: Hover through the fog and filthy air.";
37//!
38//! // Sign the message into a Vec-backed signed message
39//! let signed_message = keypair.sign_to_vecbox(message);
40//!
41//! // Verify the message signature
42//! signed_message
43//!     .verify(&keypair.public_key)
44//!     .expect("verification failed");
45//! # }
46//! ```
47//!
48//! ## Extracting key material
49//!
50//! ```
51//! use dryoc::sign::*;
52//!
53//! let seed = Seed::from([7u8; dryoc::constants::CRYPTO_SIGN_SEEDBYTES]);
54//! let keypair = StackSigningKeyPair::from_seed(&seed);
55//!
56//! let extracted_seed: Seed = keypair.to_seed();
57//! let extracted_public_key: PublicKey = keypair.to_public_key();
58//!
59//! assert_eq!(extracted_seed, seed);
60//! assert_eq!(extracted_public_key, keypair.public_key);
61//! ```
62//!
63//! ## Ed25519ph (multi-part) interface
64//!
65//! ```
66//! use dryoc::sign::*;
67//!
68//! // Generate a random keypair, using default types
69//! let keypair = StackSigningKeyPair::generate();
70//!
71//! // Initialize the Ed25519ph signer
72//! let mut signer = Ed25519phSigner::new();
73//! signer.update(b"This above all: to thine ownself be true.");
74//! signer.update(b"And it must follow, as the night the day,");
75//! signer.update(b"Thou canst not then be false to any man.");
76//!
77//! let signature: Signature = signer.finalize(&keypair.secret_key);
78//!
79//! // Ed25519ph signatures are verified with an `Ed25519phSigner` fed the same
80//! // message, not with `SignedMessage::verify`
81//! let mut verifier = Ed25519phSigner::new();
82//! verifier.update(b"This above all: to thine ownself be true.");
83//! verifier.update(b"And it must follow, as the night the day,");
84//! verifier.update(b"Thou canst not then be false to any man.");
85//! verifier
86//!     .verify(&signature, &keypair.public_key)
87//!     .expect("verification failed");
88//! ```
89//!
90//! ## Additional resources
91//!
92//! * See the [libsodium documentation](https://doc.libsodium.org/public-key_cryptography/public-key_signatures)
93//!   for more about public-key signatures
94//! * For shared-key encryption, see [`DryocSecretBox`](crate::dryocsecretbox)
95//! * For encrypted message streams, see [`DryocStream`](crate::dryocstream)
96//! * See the [`protected`] module for examples that store keys in protected
97//!   memory
98
99#[cfg(feature = "alloc")]
100use alloc::vec::Vec;
101use core::fmt;
102
103#[cfg(feature = "serde")]
104use serde::{Deserialize, Serialize};
105use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
106
107use crate::classic::crypto_sign::{
108    SignerState, crypto_sign_ed25519_sk_to_pk, crypto_sign_ed25519_sk_to_seed,
109    crypto_sign_final_verify, crypto_sign_init, crypto_sign_keypair_inplace,
110    crypto_sign_seed_keypair_inplace, crypto_sign_update, crypto_sign_verify_detached,
111};
112use crate::classic::crypto_sign_ed25519::{
113    crypto_sign_ed25519_detached, crypto_sign_ed25519ph_final_create,
114};
115use crate::constants::{
116    CRYPTO_SIGN_BYTES, CRYPTO_SIGN_PUBLICKEYBYTES, CRYPTO_SIGN_SECRETKEYBYTES,
117    CRYPTO_SIGN_SEEDBYTES,
118};
119use crate::error::{Error, ErrorContext};
120use crate::types::*;
121use crate::utils::{ct_eq_bytes, split_prefix};
122
123/// Stack-allocated public key for message signing.
124pub type PublicKey = StackByteArray<CRYPTO_SIGN_PUBLICKEYBYTES>;
125/// Stack-allocated secret key for message signing.
126pub type SecretKey = StackByteArray<CRYPTO_SIGN_SECRETKEYBYTES>;
127/// Stack-allocated seed for message signing.
128pub type Seed = StackByteArray<CRYPTO_SIGN_SEEDBYTES>;
129/// Stack-allocated signature for message signing.
130pub type Signature = StackByteArray<CRYPTO_SIGN_BYTES>;
131/// Heap-allocated message for message signing.
132#[cfg(feature = "alloc")]
133pub type Message = Vec<u8>;
134/// Stack-allocated signing keypair type alias.
135pub type StackSigningKeyPair = SigningKeyPair<PublicKey, SecretKey>;
136
137/// Checks if the given key is a valid prime-order Ed25519 public key.
138///
139/// The canonical compressed encoding is required. The high bit, which
140/// encodes the sign of the x-coordinate, may legitimately be set.
141///
142/// This is a strict prime-subgroup policy, not a generic Ed25519 signature
143/// validity predicate. Use it when an application or point-arithmetic
144/// protocol requires canonical, nonidentity, prime-order keys. Verify
145/// signatures with [`SignedMessage::verify`] or
146/// [`crypto_sign_verify_detached`] instead; some signature profiles
147/// intentionally define different point-acceptance rules.
148///
149/// Use [`crate::keypair::is_valid_public_key`] for X25519 keys used with
150/// [`crypto_box`](crate::classic::crypto_box).
151#[must_use]
152pub fn is_valid_public_key<PK: ByteArray<CRYPTO_SIGN_PUBLICKEYBYTES>>(key: &PK) -> bool {
153    crate::classic::crypto_core::crypto_core_ed25519_is_valid_point(key.as_array())
154}
155
156/// Extracts the Ed25519 seed from a signing secret key.
157#[must_use]
158pub fn secret_key_to_seed<
159    SeedOut: NewByteArray<CRYPTO_SIGN_SEEDBYTES>,
160    SigningSecretKey: ByteArray<CRYPTO_SIGN_SECRETKEYBYTES>,
161>(
162    secret_key: &SigningSecretKey,
163) -> SeedOut {
164    let mut seed = SeedOut::new_byte_array();
165    crypto_sign_ed25519_sk_to_seed(seed.as_mut_array(), secret_key.as_array());
166    seed
167}
168
169/// Extracts the Ed25519 public key from a signing secret key.
170#[must_use]
171pub fn secret_key_to_public_key<
172    PublicKeyOut: NewByteArray<CRYPTO_SIGN_PUBLICKEYBYTES>,
173    SigningSecretKey: ByteArray<CRYPTO_SIGN_SECRETKEYBYTES>,
174>(
175    secret_key: &SigningSecretKey,
176) -> PublicKeyOut {
177    let mut public_key = PublicKeyOut::new_byte_array();
178    crypto_sign_ed25519_sk_to_pk(public_key.as_mut_array(), secret_key.as_array());
179    public_key
180}
181
182#[derive(Zeroize, ZeroizeOnDrop, Clone)]
183#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
184/// An Ed25519 keypair for public-key signatures
185///
186/// Create keypairs with [`SigningKeyPair::generate`],
187/// [`SigningKeyPair::from_seed`], or [`SigningKeyPair::from_secret_key`].
188/// There is no `new` or [`Default`] constructor, so an all-zero secret key is
189/// never produced implicitly.
190pub struct SigningKeyPair<
191    PublicKey: ByteArray<CRYPTO_SIGN_PUBLICKEYBYTES> + Zeroize,
192    SecretKey: ByteArray<CRYPTO_SIGN_SECRETKEYBYTES> + Zeroize,
193> {
194    /// Public key
195    pub public_key: PublicKey,
196    /// Secret key
197    pub secret_key: SecretKey,
198}
199
200impl<
201    PublicKey: ByteArray<CRYPTO_SIGN_PUBLICKEYBYTES> + Zeroize,
202    SecretKey: ByteArray<CRYPTO_SIGN_SECRETKEYBYTES> + Zeroize,
203> fmt::Debug for SigningKeyPair<PublicKey, SecretKey>
204{
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        f.debug_struct("SigningKeyPair")
207            .field("public_key", &"[REDACTED]")
208            .field("secret_key", &"[REDACTED]")
209            .finish()
210    }
211}
212
213impl<
214    PublicKey: NewByteArray<CRYPTO_SIGN_PUBLICKEYBYTES> + Zeroize,
215    SecretKey: NewByteArray<CRYPTO_SIGN_SECRETKEYBYTES> + Zeroize,
216> SigningKeyPair<PublicKey, SecretKey>
217{
218    /// Generates a random signing keypair.
219    #[must_use]
220    pub fn generate() -> Self {
221        let mut public_key = PublicKey::new_byte_array();
222        let mut secret_key = SecretKey::new_byte_array();
223        crypto_sign_keypair_inplace(public_key.as_mut_array(), secret_key.as_mut_array());
224        Self {
225            public_key,
226            secret_key,
227        }
228    }
229
230    /// Derives a signing keypair from `secret_key`, and consumes it, returning
231    /// a new keypair. The consumed key is wiped, even if its type does not
232    /// wipe itself on drop.
233    #[must_use]
234    pub fn from_secret_key(mut secret_key: SecretKey) -> Self {
235        let mut seed = Zeroizing::new([0u8; 32]);
236        seed.copy_from_slice(&secret_key.as_slice()[..32]);
237        secret_key.zeroize();
238
239        Self::from_seed(&*seed)
240    }
241
242    /// Derives a signing keypair from `seed`, returning
243    /// a new keypair.
244    #[must_use]
245    pub fn from_seed<Seed: ByteArray<CRYPTO_SIGN_SEEDBYTES>>(seed: &Seed) -> Self {
246        let mut public_key = PublicKey::new_byte_array();
247        let mut secret_key = SecretKey::new_byte_array();
248
249        crypto_sign_seed_keypair_inplace(
250            public_key.as_mut_array(),
251            secret_key.as_mut_array(),
252            seed.as_array(),
253        );
254
255        Self {
256            public_key,
257            secret_key,
258        }
259    }
260}
261
262impl<
263    PublicKey: ByteArray<CRYPTO_SIGN_PUBLICKEYBYTES> + Zeroize,
264    SecretKey: ByteArray<CRYPTO_SIGN_SECRETKEYBYTES> + Zeroize,
265> SigningKeyPair<PublicKey, SecretKey>
266{
267    /// Extracts the Ed25519 seed from this keypair's secret key.
268    #[must_use]
269    pub fn to_seed<SeedOut: NewByteArray<CRYPTO_SIGN_SEEDBYTES>>(&self) -> SeedOut {
270        secret_key_to_seed(&self.secret_key)
271    }
272
273    /// Extracts the Ed25519 public key embedded in this keypair's secret key.
274    #[must_use]
275    pub fn to_public_key<PublicKeyOut: NewByteArray<CRYPTO_SIGN_PUBLICKEYBYTES>>(
276        &self,
277    ) -> PublicKeyOut {
278        secret_key_to_public_key(&self.secret_key)
279    }
280}
281
282impl<
283    'a,
284    PublicKey: ByteArray<CRYPTO_SIGN_PUBLICKEYBYTES> + core::convert::TryFrom<&'a [u8]> + Zeroize,
285    SecretKey: ByteArray<CRYPTO_SIGN_SECRETKEYBYTES> + core::convert::TryFrom<&'a [u8]> + Zeroize,
286> SigningKeyPair<PublicKey, SecretKey>
287{
288    /// Constructs a new signing keypair from key slices, consuming them. Does
289    /// not check validity or authenticity of keypair.
290    ///
291    /// # Errors
292    ///
293    /// Returns an error if either slice has the wrong length for its key type,
294    /// or if the target key type rejects the key bytes.
295    pub fn from_slices(public_key: &'a [u8], secret_key: &'a [u8]) -> Result<Self, Error> {
296        validate_length!(
297            exact CRYPTO_SIGN_PUBLICKEYBYTES,
298            public_key.len(),
299            crate::ErrorContext::PublicKey
300        );
301        validate_length!(
302            exact CRYPTO_SIGN_SECRETKEYBYTES,
303            secret_key.len(),
304            crate::ErrorContext::SecretKey
305        );
306
307        Ok(Self {
308            public_key: PublicKey::try_from(public_key)
309                .map_err(|_| Error::invalid_key(crate::ErrorContext::PublicKey))?,
310            secret_key: SecretKey::try_from(secret_key)
311                .map_err(|_| Error::invalid_key(crate::ErrorContext::SecretKey))?,
312        })
313    }
314}
315
316#[cfg(any(
317    all(feature = "protected", any(unix, windows)),
318    all(doc, not(doctest), feature = "std")
319))]
320#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
321pub mod protected {
322    //! # Protected memory for [`SigningKeyPair`] and [`SignedMessage`]
323    //!
324    //! ## Example
325    //! ```
326    //! use dryoc::sign::SigningKeyPair;
327    //! use dryoc::sign::protected::*;
328    //!
329    //! // Generate a random keypair, using default types
330    //! let keypair = SigningKeyPair::generate_locked_keypair().expect("keypair generate failed");
331    //! let message = Message::from_slice_into_locked(
332    //!     b"Fair is foul, and foul is fair: Hover through the fog and filthy air.",
333    //! )
334    //! .expect("message lock failed");
335    //!
336    //! // Sign the message, using default types (stack-allocated byte array, Vec<u8>)
337    //! let signed_message: LockedSignedMessage = keypair.sign(message);
338    //!
339    //! // Verify the message signature
340    //! signed_message
341    //!     .verify(&keypair.public_key)
342    //!     .expect("verification failed");
343    //!
344    //! // A keypair in locked, read-only memory signs the same way.
345    //! let readonly_keypair = LockedROSigningKeyPair::generate_readonly_locked_keypair()
346    //!     .expect("keypair generate failed");
347    //! let message = Message::from_slice_into_locked(b"By the pricking of my thumbs")
348    //!     .expect("message lock failed");
349    //! let signed_message: LockedSignedMessage = readonly_keypair.sign(message);
350    //! signed_message
351    //!     .verify(&readonly_keypair.public_key)
352    //!     .expect("verification failed");
353    //! ```
354    use super::*;
355    pub use crate::protected::*;
356
357    /// Heap-allocated, page-aligned public-key for signed messages,
358    /// for use with protected memory.
359    pub type PublicKey = HeapByteArray<CRYPTO_SIGN_PUBLICKEYBYTES>;
360    /// Heap-allocated, page-aligned secret-key for signed messages,
361    /// for use with protected memory.
362    pub type SecretKey = HeapByteArray<CRYPTO_SIGN_SECRETKEYBYTES>;
363    /// Heap-allocated, page-aligned seed for signed messages,
364    /// for use with protected memory.
365    pub type Seed = HeapByteArray<CRYPTO_SIGN_SEEDBYTES>;
366    /// Heap-allocated, page-aligned signature for signed messages,
367    /// for use with protected memory.
368    pub type Signature = HeapByteArray<CRYPTO_SIGN_BYTES>;
369    /// Heap-allocated, page-aligned message for signed messages,
370    /// for use with protected memory.
371    pub type Message = HeapBytes;
372
373    /// Heap-allocated, page-aligned public/secret keypair for message signing,
374    /// for use with protected memory.
375    pub type LockedSigningKeyPair = SigningKeyPair<Locked<PublicKey>, Locked<SecretKey>>;
376    /// Heap-allocated, page-aligned public/secret keypair for message signing,
377    /// in locked, read-only memory, for use with protected memory.
378    pub type LockedROSigningKeyPair = SigningKeyPair<LockedRO<PublicKey>, LockedRO<SecretKey>>;
379    /// Heap-allocated, page-aligned signed message, for use with protected
380    /// memory.
381    pub type LockedSignedMessage = SignedMessage<Locked<Signature>, Locked<Message>>;
382
383    impl
384        SigningKeyPair<
385            Locked<HeapByteArray<CRYPTO_SIGN_PUBLICKEYBYTES>>,
386            Locked<HeapByteArray<CRYPTO_SIGN_SECRETKEYBYTES>>,
387        >
388    {
389        /// Returns a new randomly generated locked signing keypair.
390        ///
391        /// # Errors
392        ///
393        /// Returns [`Error::Io`] if either allocation cannot be locked.
394        ///
395        /// # Panics
396        ///
397        /// Panics if either page-aligned allocation cannot be created, its
398        /// size cannot be represented with guard pages, or the operating
399        /// system's random number generator fails.
400        pub fn generate_locked_keypair() -> Result<Self, Error> {
401            let mut res = Self {
402                public_key: HeapByteArray::<CRYPTO_SIGN_PUBLICKEYBYTES>::new_locked()?,
403                secret_key: HeapByteArray::<CRYPTO_SIGN_SECRETKEYBYTES>::new_locked()?,
404            };
405
406            crypto_sign_keypair_inplace(
407                res.public_key.as_mut_array(),
408                res.secret_key.as_mut_array(),
409            );
410
411            Ok(res)
412        }
413    }
414
415    impl
416        SigningKeyPair<
417            LockedRO<HeapByteArray<CRYPTO_SIGN_PUBLICKEYBYTES>>,
418            LockedRO<HeapByteArray<CRYPTO_SIGN_SECRETKEYBYTES>>,
419        >
420    {
421        /// Returns a new randomly generated locked, read-only signing keypair.
422        ///
423        /// # Errors
424        ///
425        /// Returns [`Error::Io`] if either allocation cannot be locked or its
426        /// page permissions cannot be changed to read-only.
427        ///
428        /// # Panics
429        ///
430        /// Panics if either page-aligned allocation cannot be created, its
431        /// size cannot be represented with guard pages, or the operating
432        /// system's random number generator fails.
433        pub fn generate_readonly_locked_keypair() -> Result<Self, Error> {
434            let mut public_key = HeapByteArray::<CRYPTO_SIGN_PUBLICKEYBYTES>::new_locked()?;
435            let mut secret_key = HeapByteArray::<CRYPTO_SIGN_SECRETKEYBYTES>::new_locked()?;
436
437            crypto_sign_keypair_inplace(public_key.as_mut_array(), secret_key.as_mut_array());
438
439            let public_key = public_key.mprotect_readonly()?;
440            let secret_key = secret_key.mprotect_readonly()?;
441
442            Ok(Self {
443                public_key,
444                secret_key,
445            })
446        }
447    }
448}
449
450#[derive(Zeroize, Clone, Debug)]
451#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
452/// A signed message, for use with [`SigningKeyPair`].
453pub struct SignedMessage<
454    Signature: ByteArray<CRYPTO_SIGN_BYTES> + Zeroize,
455    Message: Bytes + Zeroize,
456> {
457    signature: Signature,
458    message: Message,
459}
460
461/// [Vec]-based signed message.
462#[cfg(feature = "alloc")]
463pub type VecSignedMessage = SignedMessage<Signature, Vec<u8>>;
464
465impl<
466    PublicKey: ByteArray<CRYPTO_SIGN_PUBLICKEYBYTES> + Zeroize,
467    SecretKey: ByteArray<CRYPTO_SIGN_SECRETKEYBYTES> + Zeroize,
468> SigningKeyPair<PublicKey, SecretKey>
469{
470    /// Signs `message` using this keypair, consuming the message, and returning
471    /// a new [`SignedMessage`]. The type of `message` should match that of the
472    /// target signed message.
473    #[must_use]
474    pub fn sign<Signature: NewByteArray<CRYPTO_SIGN_BYTES> + Zeroize, Message: Bytes + Zeroize>(
475        &self,
476        message: Message,
477    ) -> SignedMessage<Signature, Message> {
478        let mut signature = Signature::new_byte_array();
479        crypto_sign_ed25519_detached(
480            signature.as_mut_array(),
481            message.as_slice(),
482            self.secret_key.as_array(),
483        );
484
485        SignedMessage::<Signature, Message> { signature, message }
486    }
487
488    /// Signs a copy of `message`, returning a [`VecSignedMessage`].
489    /// Convenience wrapper for [`SigningKeyPair::sign`].
490    #[cfg(feature = "alloc")]
491    #[must_use]
492    pub fn sign_to_vecbox<Message: Bytes + ?Sized>(&self, message: &Message) -> VecSignedMessage {
493        self.sign(Vec::from(message.as_slice()))
494    }
495}
496
497/// Multi-part Ed25519ph (prehashed Ed25519, RFC 8032) signer and verifier.
498///
499/// This is libsodium's `crypto_sign_init`/`crypto_sign_update`/
500/// `crypto_sign_final_create`/`crypto_sign_final_verify` interface: the message
501/// is fed in parts, hashed with SHA-512, and the digest is signed. Ed25519ph is
502/// a different signature scheme from Ed25519, not an incremental way to compute
503/// the same signature: signatures from [`Ed25519phSigner::finalize`] do not
504/// verify with plain Ed25519 verification ([`SignedMessage::verify`],
505/// [`crypto_sign_verify_detached`]),
506/// and Ed25519 signatures from [`SigningKeyPair::sign`] do not verify with
507/// [`Ed25519phSigner::verify`]. Use it only when both sides agree on
508/// Ed25519ph.
509pub struct Ed25519phSigner {
510    state: SignerState,
511}
512
513impl Ed25519phSigner {
514    /// Returns a new Ed25519ph signer with an empty message.
515    #[must_use]
516    pub fn new() -> Self {
517        Self {
518            state: crypto_sign_init(),
519        }
520    }
521
522    /// Appends `message` to the message being signed or verified.
523    pub fn update<Message: Bytes + ?Sized>(&mut self, message: &Message) {
524        crypto_sign_update(&mut self.state, message.as_slice())
525    }
526
527    /// Finalizes this signer with `secret_key`, returning the Ed25519ph
528    /// signature of the accumulated message.
529    #[must_use]
530    pub fn finalize<
531        Signature: NewByteArray<CRYPTO_SIGN_BYTES>,
532        SecretKey: ByteArray<CRYPTO_SIGN_SECRETKEYBYTES>,
533    >(
534        self,
535        secret_key: &SecretKey,
536    ) -> Signature {
537        let mut signature = Signature::new_byte_array();
538
539        crypto_sign_ed25519ph_final_create(
540            self.state.state,
541            signature.as_mut_array(),
542            secret_key.as_array(),
543        );
544
545        signature
546    }
547
548    /// Verifies that `signature` is a valid Ed25519ph signature of the
549    /// accumulated message for `public_key`.
550    ///
551    /// # Errors
552    ///
553    /// Returns an error if `signature` is not a valid Ed25519ph signature for
554    /// the accumulated message and `public_key`, including when it is a plain
555    /// Ed25519 signature of that message.
556    pub fn verify<
557        Signature: ByteArray<CRYPTO_SIGN_BYTES>,
558        PublicKey: ByteArray<CRYPTO_SIGN_PUBLICKEYBYTES>,
559    >(
560        self,
561        signature: &Signature,
562        public_key: &PublicKey,
563    ) -> Result<(), Error> {
564        crypto_sign_final_verify(self.state, signature.as_array(), public_key.as_array())?;
565
566        Ok(())
567    }
568}
569
570impl Default for Ed25519phSigner {
571    fn default() -> Self {
572        Self::new()
573    }
574}
575
576impl<Signature: ByteArray<CRYPTO_SIGN_BYTES> + Zeroize, Message: Bytes + Zeroize>
577    SignedMessage<Signature, Message>
578{
579    /// Verifies that this signed message is valid for `public_key`.
580    ///
581    /// # Errors
582    ///
583    /// Returns an error if the signature is not valid for the message and
584    /// `public_key`.
585    pub fn verify<PublicKey: ByteArray<CRYPTO_SIGN_PUBLICKEYBYTES>>(
586        &self,
587        public_key: &PublicKey,
588    ) -> Result<(), Error> {
589        crypto_sign_verify_detached(
590            self.signature.as_array(),
591            self.message.as_slice(),
592            public_key.as_array(),
593        )
594    }
595}
596
597impl<
598    'a,
599    Signature: ByteArray<CRYPTO_SIGN_BYTES> + core::convert::TryFrom<&'a [u8]> + Zeroize,
600    Message: Bytes + From<&'a [u8]> + Zeroize,
601> SignedMessage<Signature, Message>
602{
603    /// Initializes a [`SignedMessage`] from a slice. Expects the first
604    /// [`CRYPTO_SIGN_BYTES`] bytes to contain the message signature,
605    /// with the remaining bytes containing the message.
606    ///
607    /// # Errors
608    ///
609    /// Returns an error if `bytes` is shorter than a signature or the
610    /// signature cannot be converted to the requested output type.
611    pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, Error> {
612        let (signature, message) =
613            split_prefix(bytes, CRYPTO_SIGN_BYTES, ErrorContext::SignedMessage)?;
614        Ok(Self {
615            signature: Signature::try_from(signature)
616                .map_err(|_| Error::invalid_encoding(ErrorContext::Signature))?,
617            message: Message::from(message),
618        })
619    }
620}
621
622impl<Signature: ByteArray<CRYPTO_SIGN_BYTES> + Zeroize, Message: Bytes + Zeroize>
623    SignedMessage<Signature, Message>
624{
625    /// Returns a new signed message from `signature` and `message`, consuming
626    /// each.
627    #[must_use]
628    pub fn from_parts(signature: Signature, message: Message) -> Self {
629        Self { signature, message }
630    }
631
632    /// Returns the signature.
633    pub fn signature(&self) -> &Signature {
634        &self.signature
635    }
636
637    /// Returns the signed message.
638    pub fn message(&self) -> &Message {
639        &self.message
640    }
641
642    /// Copies `self` into a new [`Vec`]
643    #[cfg(feature = "alloc")]
644    #[must_use]
645    pub fn to_vec(&self) -> Vec<u8> {
646        self.to_bytes()
647    }
648
649    /// Moves the signature and message out of this instance, returning them
650    /// as a tuple.
651    #[must_use]
652    pub fn into_parts(self) -> (Signature, Message) {
653        (self.signature, self.message)
654    }
655
656    /// Copies `self` into the target. Can be used with protected memory.
657    #[must_use]
658    pub fn to_bytes<Bytes: NewBytes + ResizableBytes>(&self) -> Bytes {
659        concat_bytes(self.signature.as_array(), self.message.as_slice())
660    }
661}
662
663impl<
664    PublicKey: ByteArray<CRYPTO_SIGN_PUBLICKEYBYTES> + Zeroize,
665    SecretKey: ByteArray<CRYPTO_SIGN_SECRETKEYBYTES> + Zeroize,
666> PartialEq<SigningKeyPair<PublicKey, SecretKey>> for SigningKeyPair<PublicKey, SecretKey>
667{
668    fn eq(&self, other: &Self) -> bool {
669        ct_eq_bytes(self.public_key.as_slice(), other.public_key.as_slice())
670            && ct_eq_bytes(self.secret_key.as_slice(), other.secret_key.as_slice())
671    }
672}
673
674impl<Signature: ByteArray<CRYPTO_SIGN_BYTES> + Zeroize, Message: Bytes + Zeroize>
675    PartialEq<SignedMessage<Signature, Message>> for SignedMessage<Signature, Message>
676{
677    fn eq(&self, other: &Self) -> bool {
678        ct_eq_bytes(self.signature.as_slice(), other.signature.as_slice())
679            && ct_eq_bytes(self.message.as_slice(), other.message.as_slice())
680    }
681}
682
683#[cfg(all(test, feature = "alloc"))]
684mod tests {
685    use super::*;
686
687    #[test]
688    fn signing_keypair_debug_redacts_keys_and_secret_key_reconstructs_keypair() {
689        let keypair = StackSigningKeyPair::generate();
690        let debug = format!("{keypair:?}");
691        let reconstructed = SigningKeyPair::from_secret_key(keypair.secret_key.clone());
692
693        assert_eq!(
694            debug,
695            "SigningKeyPair { public_key: \"[REDACTED]\", secret_key: \"[REDACTED]\" }"
696        );
697        assert_eq!(reconstructed, keypair);
698    }
699
700    #[test]
701    fn test_message_signing() {
702        let keypair = StackSigningKeyPair::generate();
703        let message = b"hello my frens";
704
705        let signed_message = keypair.sign_to_vecbox(message);
706
707        signed_message
708            .verify(&keypair.public_key)
709            .expect("verification failed");
710    }
711
712    #[test]
713    fn test_is_valid_public_key() {
714        use crate::edwards25519::test_vectors::{IDENTITY, NONCANONICAL_IDENTITY};
715
716        let keypair = StackSigningKeyPair::generate();
717        assert!(
718            is_valid_public_key(&keypair.public_key),
719            "generated Ed25519 key should pass validation"
720        );
721        let (valid_pk, _) = crate::classic::crypto_sign::crypto_sign_keypair();
722        assert!(
723            is_valid_public_key(&valid_pk),
724            "Ed25519 key from crypto_sign_keypair should pass validation"
725        );
726
727        let mut negative_basepoint =
728            curve25519_dalek::constants::ED25519_BASEPOINT_COMPRESSED.to_bytes();
729        negative_basepoint[31] |= 0x80;
730        assert!(
731            is_valid_public_key(&negative_basepoint),
732            "the Ed25519 x-coordinate sign bit should be accepted"
733        );
734
735        assert!(
736            !is_valid_public_key(&PublicKey::default()),
737            "zero key should be invalid"
738        );
739
740        assert!(
741            !is_valid_public_key(&IDENTITY),
742            "identity element should be invalid"
743        );
744
745        assert!(
746            !is_valid_public_key(&NONCANONICAL_IDENTITY),
747            "noncanonical identity encoding should be invalid"
748        );
749
750        let mut mixed_order = [0x99; CRYPTO_SIGN_PUBLICKEYBYTES];
751        mixed_order[0] = 0x95;
752        assert!(
753            !is_valid_public_key(&mixed_order),
754            "mixed-order Ed25519 key should fail the prime-subgroup policy"
755        );
756    }
757
758    #[test]
759    fn test_secret_key_extraction() {
760        let seed = Seed::generate();
761        let keypair = StackSigningKeyPair::from_seed(&seed);
762
763        let extracted_seed: Seed = keypair.to_seed();
764        let extracted_public_key: PublicKey = keypair.to_public_key();
765        assert_eq!(extracted_seed, seed);
766        assert_eq!(extracted_public_key, keypair.public_key);
767
768        let extracted_seed_array: [u8; CRYPTO_SIGN_SEEDBYTES] =
769            secret_key_to_seed(&keypair.secret_key);
770        let extracted_public_key_array: [u8; CRYPTO_SIGN_PUBLICKEYBYTES] =
771            secret_key_to_public_key(&keypair.secret_key);
772        assert_eq!(&extracted_seed_array, seed.as_array());
773        assert_eq!(&extracted_public_key_array, keypair.public_key.as_array());
774    }
775
776    /// RFC 8032 section 7.1 (Ed25519) tests 1-3 and section 7.3 (Ed25519ph):
777    /// `(seed, public key, message, signature)`.
778    const RFC8032_ED25519: [(&str, &str, &str, &str); 3] = [
779        (
780            "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
781            "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
782            "",
783            concat!(
784                "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155",
785                "5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
786            ),
787        ),
788        (
789            "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
790            "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
791            "72",
792            concat!(
793                "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da",
794                "085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
795            ),
796        ),
797        (
798            "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
799            "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
800            "af82",
801            concat!(
802                "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac",
803                "18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
804            ),
805        ),
806    ];
807    const RFC8032_ED25519PH: (&str, &str, &str, &str) = (
808        "833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42",
809        "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf",
810        "616263",
811        concat!(
812            "98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae41",
813            "31f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406",
814        ),
815    );
816
817    fn array<const N: usize>(hex: &str) -> StackByteArray<N> {
818        StackByteArray::try_from(hex::decode(hex).expect("hex").as_slice()).expect("length")
819    }
820
821    fn rfc_keypair(seed: &str, public_key: &str) -> SigningKeyPair<PublicKey, SecretKey> {
822        let keypair = SigningKeyPair::from_seed(&array::<CRYPTO_SIGN_SEEDBYTES>(seed));
823        assert_eq!(
824            keypair.public_key,
825            array::<CRYPTO_SIGN_PUBLICKEYBYTES>(public_key)
826        );
827        assert_eq!(
828            keypair.to_seed::<Seed>(),
829            array::<CRYPTO_SIGN_SEEDBYTES>(seed)
830        );
831        keypair
832    }
833
834    #[test]
835    fn rfc8032_detached_signatures_and_signed_message_wire_format() {
836        for (seed, public_key, message, signature) in RFC8032_ED25519 {
837            let keypair = rfc_keypair(seed, public_key);
838            let message = hex::decode(message).expect("hex");
839            let expected: Signature = array(signature);
840
841            let signed = keypair.sign_to_vecbox(message.as_slice());
842            assert_eq!(signed.signature, expected);
843            assert_eq!(signed.message, message);
844            signed.verify(&keypair.public_key).expect("verify failed");
845
846            let mut wire = expected.to_vec();
847            wire.extend_from_slice(&message);
848            assert_eq!(signed.to_vec(), wire);
849            let parsed = VecSignedMessage::from_bytes(&wire).expect("parse");
850            assert_eq!(parsed, signed);
851            parsed.verify(&keypair.public_key).expect("verify failed");
852
853            let (parsed_signature, parsed_message) = parsed.into_parts();
854            assert_eq!(parsed_signature, expected);
855            assert_eq!(parsed_message, message);
856            let rebuilt = VecSignedMessage::from_parts(parsed_signature, parsed_message);
857            assert_eq!(rebuilt.to_bytes::<Vec<u8>>(), wire);
858            rebuilt.verify(&keypair.public_key).expect("verify failed");
859
860            // `sign` with an explicit message type agrees with the Vec wrapper.
861            let signed_array: SignedMessage<Signature, Vec<u8>> = keypair.sign(message.clone());
862            assert_eq!(signed_array, signed);
863
864            // The secret key embeds the public key.
865            assert_eq!(keypair.to_public_key::<PublicKey>(), keypair.public_key);
866            assert_eq!(
867                SigningKeyPair::from_secret_key(keypair.secret_key.clone()),
868                keypair
869            );
870        }
871    }
872
873    #[test]
874    fn rfc8032_ed25519ph_vector_through_incremental_signer() {
875        let (seed, public_key, message, signature) = RFC8032_ED25519PH;
876        let keypair = rfc_keypair(seed, public_key);
877        let message = hex::decode(message).expect("hex");
878        let expected: Signature = array(signature);
879
880        let splits: [&[&[u8]]; 4] = [
881            &[&message],
882            &[&message[..1], &message[1..]],
883            &[&[], &message[..2], &message[2..], &[]],
884            &[&message[..1], &message[1..2], &message[2..]],
885        ];
886        for parts in splits {
887            let mut signer = Ed25519phSigner::new();
888            for part in parts {
889                signer.update(part);
890            }
891            let actual: Signature = signer.finalize(&keypair.secret_key);
892            assert_eq!(actual, expected, "split {parts:?}");
893
894            let mut verifier = Ed25519phSigner::default();
895            for part in parts {
896                verifier.update(part);
897            }
898            verifier
899                .verify(&expected, &keypair.public_key)
900                .expect("verify failed");
901        }
902
903        // Ed25519ph and pure Ed25519 signatures are distinct and not
904        // interchangeable.
905        let pure = keypair.sign_to_vecbox(message.as_slice());
906        assert_ne!(pure.signature, expected);
907        let mut verifier = Ed25519phSigner::new();
908        verifier.update(&message);
909        assert!(matches!(
910            verifier.verify(&pure.signature, &keypair.public_key),
911            Err(Error::AuthenticationFailed)
912        ));
913        assert!(matches!(
914            VecSignedMessage::from_parts(expected, message).verify(&keypair.public_key),
915            Err(Error::AuthenticationFailed)
916        ));
917    }
918
919    #[test]
920    fn tampered_signatures_messages_and_wrong_keys_are_rejected() {
921        let (seed, public_key, message, _) = RFC8032_ED25519[2];
922        let keypair = rfc_keypair(seed, public_key);
923        let other = rfc_keypair(RFC8032_ED25519[1].0, RFC8032_ED25519[1].1);
924        let message = hex::decode(message).expect("hex");
925        let signed = keypair.sign_to_vecbox(message.as_slice());
926
927        assert!(matches!(
928            signed.verify(&other.public_key),
929            Err(Error::AuthenticationFailed)
930        ));
931
932        for index in [0, 31, 32, CRYPTO_SIGN_BYTES - 1] {
933            let mut tampered = signed.clone();
934            tampered.signature[index] ^= 0x01;
935            assert!(matches!(
936                tampered.verify(&keypair.public_key),
937                Err(Error::AuthenticationFailed)
938            ));
939        }
940
941        let mut tampered = signed.clone();
942        tampered.message[0] ^= 0x80;
943        assert!(matches!(
944            tampered.verify(&keypair.public_key),
945            Err(Error::AuthenticationFailed)
946        ));
947        let mut truncated = signed.clone();
948        truncated.message.pop();
949        assert!(truncated.verify(&keypair.public_key).is_err());
950        let mut extended = signed.clone();
951        extended.message.push(0);
952        assert!(extended.verify(&keypair.public_key).is_err());
953
954        // Incremental verification rejects a signature over a different split
955        // message, and a wrong key.
956        let ph: Signature = {
957            let mut signer = Ed25519phSigner::new();
958            signer.update(&message);
959            signer.finalize(&keypair.secret_key)
960        };
961        let mut verifier = Ed25519phSigner::new();
962        verifier.update(&message[..1]);
963        assert!(verifier.verify(&ph, &keypair.public_key).is_err());
964        let mut verifier = Ed25519phSigner::new();
965        verifier.update(&message);
966        assert!(matches!(
967            verifier.verify(&ph, &other.public_key),
968            Err(Error::AuthenticationFailed)
969        ));
970
971        // The original is still valid after all rejections.
972        signed.verify(&keypair.public_key).expect("verify failed");
973    }
974
975    #[test]
976    fn signed_message_from_bytes_requires_a_full_signature() {
977        for len in [0, 1, CRYPTO_SIGN_BYTES - 1] {
978            assert!(matches!(
979                VecSignedMessage::from_bytes(&vec![0u8; len]),
980                Err(Error::InvalidLength {
981                    context: ErrorContext::SignedMessage,
982                    actual,
983                    ..
984                }) if actual == len
985            ));
986        }
987        let bare = VecSignedMessage::from_bytes(&[0x5au8; CRYPTO_SIGN_BYTES])
988            .expect("a lone signature is an empty message");
989        assert!(bare.message.is_empty());
990        assert_eq!(bare.signature.as_slice(), &[0x5au8; CRYPTO_SIGN_BYTES]);
991    }
992
993    #[test]
994    fn from_slices_accepts_exact_lengths_and_reports_the_short_side() {
995        let (seed, public_key, message, signature) = RFC8032_ED25519[0];
996        let keypair = rfc_keypair(seed, public_key);
997        let rebuilt = StackSigningKeyPair::from_slices(
998            keypair.public_key.as_slice(),
999            keypair.secret_key.as_slice(),
1000        )
1001        .expect("from_slices failed");
1002        assert_eq!(rebuilt, keypair);
1003        let signed = rebuilt.sign_to_vecbox(hex::decode(message).expect("hex").as_slice());
1004        assert_eq!(signed.signature, array::<CRYPTO_SIGN_BYTES>(signature));
1005
1006        for len in [
1007            0,
1008            CRYPTO_SIGN_PUBLICKEYBYTES - 1,
1009            CRYPTO_SIGN_PUBLICKEYBYTES + 1,
1010        ] {
1011            assert!(matches!(
1012                StackSigningKeyPair::from_slices(
1013                    &vec![0u8; len],
1014                    keypair.secret_key.as_slice(),
1015                ),
1016                Err(Error::InvalidLength {
1017                    context: ErrorContext::PublicKey,
1018                    actual,
1019                    ..
1020                }) if actual == len
1021            ));
1022        }
1023        for len in [
1024            0,
1025            CRYPTO_SIGN_SECRETKEYBYTES - 1,
1026            CRYPTO_SIGN_SECRETKEYBYTES + 1,
1027        ] {
1028            assert!(matches!(
1029                StackSigningKeyPair::from_slices(
1030                    keypair.public_key.as_slice(),
1031                    &vec![0u8; len],
1032                ),
1033                Err(Error::InvalidLength {
1034                    context: ErrorContext::SecretKey,
1035                    actual,
1036                    ..
1037                }) if actual == len
1038            ));
1039        }
1040    }
1041
1042    #[cfg(feature = "serde")]
1043    #[test]
1044    fn serde_round_trips_reproduce_rfc8032_signatures() {
1045        let (seed, public_key, message, signature) = RFC8032_ED25519[1];
1046        let keypair = rfc_keypair(seed, public_key);
1047        let message = hex::decode(message).expect("hex");
1048        let expected: Signature = array(signature);
1049
1050        let json = serde_json::to_string(&keypair).expect("serialize keypair");
1051        let decoded: SigningKeyPair<PublicKey, SecretKey> =
1052            serde_json::from_str(&json).expect("deserialize keypair");
1053        assert_eq!(decoded, keypair);
1054        let signed = decoded.sign_to_vecbox(message.as_slice());
1055        assert_eq!(signed.signature, expected);
1056
1057        let json = serde_json::to_string(&signed).expect("serialize signed message");
1058        let decoded: VecSignedMessage =
1059            serde_json::from_str(&json).expect("deserialize signed message");
1060        assert_eq!(decoded, signed);
1061        decoded.verify(&keypair.public_key).expect("verify failed");
1062
1063        // A field-level change in the encoding is caught by verification.
1064        let tampered = json.replacen(
1065            &format!("{}", expected[0]),
1066            &format!("{}", expected[0] ^ 1),
1067            1,
1068        );
1069        let decoded: VecSignedMessage = serde_json::from_str(&tampered).expect("deserialize");
1070        assert!(decoded.verify(&keypair.public_key).is_err());
1071    }
1072
1073    #[cfg(dryoc_native_tests)]
1074    mod native_tests {
1075        use super::*;
1076        use crate::native_test_util as sodium;
1077        use crate::utils::test_util::XorShift64;
1078
1079        #[test]
1080        fn incremental_signer_matches_libsodium_ed25519ph_for_split_updates() {
1081            let mut rng = XorShift64::new(0x6564_3235_3531_3970);
1082            for round in 0..8 {
1083                let keypair = StackSigningKeyPair::from_seed(&rng.next_bytes32());
1084                let message: Vec<u8> = (0..(round * 97) % 1023)
1085                    .map(|_| rng.next_u64() as u8)
1086                    .collect();
1087                let split = message.len() / 3;
1088                let parts: [&[u8]; 3] = [
1089                    &message[..split],
1090                    &message[split..2 * split],
1091                    &message[2 * split..],
1092                ];
1093
1094                let mut signer = Ed25519phSigner::new();
1095                for part in parts {
1096                    signer.update(&part);
1097                }
1098                let signature: Signature = signer.finalize(&keypair.secret_key);
1099                assert_eq!(
1100                    signature.as_array(),
1101                    &sodium::sign_ed25519ph(&parts, &keypair.secret_key)
1102                );
1103                assert!(sodium::sign_ed25519ph_verify(
1104                    &[&message],
1105                    &signature,
1106                    &keypair.public_key
1107                ));
1108
1109                let mut verifier = Ed25519phSigner::new();
1110                verifier.update(&message);
1111                verifier
1112                    .verify(&signature, &keypair.public_key)
1113                    .expect("verify failed");
1114            }
1115        }
1116
1117        #[test]
1118        fn detached_signatures_interoperate_with_libsodium() {
1119            let (seed, public_key, _, _) = RFC8032_ED25519PH;
1120            let keypair = rfc_keypair(seed, public_key);
1121            let (so_pk, so_sk) =
1122                sodium::sign_ed25519_seed_keypair(&hex::decode(seed).expect("hex"));
1123            assert_eq!(so_pk.as_slice(), keypair.public_key.as_slice());
1124            assert_eq!(so_sk.as_slice(), keypair.secret_key.as_slice());
1125
1126            let mut rng = XorShift64::new(0x7369_676e_6564_2121);
1127            for len in [0, 1, 63, 64, 65, 1023] {
1128                let message: Vec<u8> = (0..len).map(|_| rng.next_u64() as u8).collect();
1129                let signed = keypair.sign_to_vecbox(message.as_slice());
1130                let so_signature = sodium::sign_ed25519_detached(&message, &so_sk);
1131                assert_eq!(signed.signature.as_slice(), so_signature.as_slice());
1132                assert!(sodium::sign_ed25519_verify_detached(
1133                    signed.signature.as_slice(),
1134                    &message,
1135                    &so_pk
1136                ));
1137
1138                let so_signed = sodium::sign_ed25519(&message, &so_sk);
1139                let parsed = VecSignedMessage::from_bytes(&so_signed).expect("parse");
1140                assert_eq!(parsed, signed);
1141                parsed.verify(&keypair.public_key).expect("verify failed");
1142                assert_eq!(
1143                    sodium::sign_ed25519_open(&signed.to_vec(), &so_pk)
1144                        .expect("sodium verify failed"),
1145                    message
1146                );
1147            }
1148        }
1149    }
1150}