Skip to main content

dryoc/classic/
crypto_sign.rs

1//! # Public-key signatures
2//!
3//! This module implements libsodium's public-key signatures, based on Ed25519.
4//!
5//! ## Classic API example
6//!
7//! ```
8//! use dryoc::classic::crypto_sign::*;
9//! use dryoc::constants::CRYPTO_SIGN_BYTES;
10//!
11//! // Generate a random signing keypair
12//! let (public_key, secret_key) = crypto_sign_keypair();
13//! let message = b"These violent delights have violent ends...";
14//!
15//! // Signed message buffer needs to be correct length
16//! let mut signed_message = vec![0u8; message.len() + CRYPTO_SIGN_BYTES];
17//!
18//! // Sign the message, placing the result into `signed_message`
19//! crypto_sign(&mut signed_message, message, &secret_key).expect("sign failed");
20//!
21//! // Allocate a new buffer for opening the message
22//! let mut opened_message = vec![0u8; message.len()];
23//!
24//! // Open the signed message, verifying the signature
25//! crypto_sign_open(&mut opened_message, &signed_message, &public_key).expect("verify failed");
26//!
27//! assert_eq!(&opened_message, message);
28//!
29//! // Create an invalid message
30//! let mut invalid_signed_message = signed_message.clone();
31//! invalid_signed_message[5] = !invalid_signed_message[5];
32//!
33//! // An invalid message can't be verified
34//! crypto_sign_open(&mut opened_message, &invalid_signed_message, &public_key)
35//!     .expect_err("open should not succeed");
36//! ```
37//!
38//! ## Classic API example, detached mode
39//!
40//! ```
41//! use dryoc::classic::crypto_sign::*;
42//! use dryoc::constants::CRYPTO_SIGN_BYTES;
43//!
44//! // Generate a random signing keypair
45//! let (public_key, secret_key) = crypto_sign_keypair();
46//! let message = b"Brevity is the soul of wit.";
47//! let mut signature = [0u8; CRYPTO_SIGN_BYTES];
48//!
49//! // Sign our message
50//! crypto_sign_detached(&mut signature, message, &secret_key).expect("sign failed");
51//!
52//! // Verify the signature
53//! crypto_sign_verify_detached(&signature, message, &public_key).expect("verify failed");
54//! ```
55
56use super::crypto_sign_ed25519::*;
57pub use super::crypto_sign_ed25519::{
58    PublicKey, SecretKey, crypto_sign_ed25519_sk_to_pk, crypto_sign_ed25519_sk_to_seed,
59};
60use crate::error::Error;
61
62/// In-place variant of [`crypto_sign_keypair`].
63pub fn crypto_sign_keypair_inplace(public_key: &mut PublicKey, secret_key: &mut SecretKey) {
64    crypto_sign_ed25519_keypair_inplace(public_key, secret_key)
65}
66
67/// In-place variant of [`crypto_sign_seed_keypair`].
68pub fn crypto_sign_seed_keypair_inplace(
69    public_key: &mut PublicKey,
70    secret_key: &mut SecretKey,
71    seed: &[u8; 32],
72) {
73    crypto_sign_ed25519_seed_keypair_inplace(public_key, secret_key, seed)
74}
75
76/// Randomly generates a new Ed25519 `(PublicKey, SecretKey)` keypair that can
77/// be used for message signing.
78#[must_use]
79pub fn crypto_sign_keypair() -> (PublicKey, SecretKey) {
80    crypto_sign_ed25519_keypair()
81}
82
83/// Returns a keypair derived from `seed`, which can be used for message
84/// signing.
85#[must_use]
86pub fn crypto_sign_seed_keypair(seed: &[u8; 32]) -> (PublicKey, SecretKey) {
87    crypto_sign_ed25519_seed_keypair(seed)
88}
89
90/// Signs `message`, placing the result into `signed_message`. The length of
91/// `signed_message` should be the length of the message plus
92/// [`CRYPTO_SIGN_BYTES`](crate::constants::CRYPTO_SIGN_BYTES).
93///
94/// This function is compatible with libsodium's `crypto_sign`; the
95/// `ED25519_NONDETERMINISTIC` feature is not supported.
96///
97/// # Errors
98///
99/// Returns an error if `signed_message` is not exactly one signature longer
100/// than `message`, or signing fails.
101pub fn crypto_sign(
102    signed_message: &mut [u8],
103    message: &[u8],
104    secret_key: &SecretKey,
105) -> Result<(), Error> {
106    crypto_sign_ed25519(signed_message, message, secret_key)
107}
108
109/// Verifies the signature of `signed_message`, placing the result into
110/// `message`. The length of `message` should be the length of the signed
111/// message minus [`CRYPTO_SIGN_BYTES`](crate::constants::CRYPTO_SIGN_BYTES).
112///
113/// This function is compatible with libsodium's `crypto_sign_open`; the
114/// `ED25519_NONDETERMINISTIC` feature is not supported.
115///
116/// # Errors
117///
118/// Returns an error if `signed_message` is too short, `message` has the wrong
119/// length, or the signature or public key is invalid.
120pub fn crypto_sign_open(
121    message: &mut [u8],
122    signed_message: &[u8],
123    public_key: &PublicKey,
124) -> Result<(), Error> {
125    crypto_sign_ed25519_open(message, signed_message, public_key)
126}
127
128/// Signs `message`, placing the signature into `signature` upon success.
129/// Detached variant of [`crypto_sign_open`].
130///
131/// This function is compatible with libsodium's `crypto_sign_detached`; the
132/// `ED25519_NONDETERMINISTIC` feature is not supported.
133///
134/// # Errors
135///
136/// The fixed-size signature and secret-key types satisfy the current
137/// implementation's requirements, so this function does not return an error
138/// in normal use. The [`Result`] is retained for API compatibility.
139pub fn crypto_sign_detached(
140    signature: &mut Signature,
141    message: &[u8],
142    secret_key: &SecretKey,
143) -> Result<(), Error> {
144    crypto_sign_ed25519_detached(signature, message, secret_key);
145    Ok(())
146}
147
148/// Verifies that `signature` is a valid signature for `message` using the given
149/// `public_key`.
150///
151/// This function is compatible with libsodium's `crypto_sign_verify_detached`;
152/// the `ED25519_NONDETERMINISTIC` feature is not supported.
153///
154/// # Errors
155///
156/// Returns an error if `signature` or `public_key` is malformed, or if the
157/// signature does not authenticate `message`.
158pub fn crypto_sign_verify_detached(
159    signature: &Signature,
160    message: &[u8],
161    public_key: &PublicKey,
162) -> Result<(), Error> {
163    crypto_sign_ed25519_verify_detached(signature, message, public_key)
164}
165
166/// State for incremental signing interface.
167pub struct SignerState {
168    pub(crate) state: Ed25519SignerState,
169}
170
171/// Initializes the incremental signing interface.
172#[must_use]
173pub fn crypto_sign_init() -> SignerState {
174    SignerState {
175        state: crypto_sign_ed25519ph_init(),
176    }
177}
178
179/// Updates the signature for `state` with `message`.
180pub fn crypto_sign_update(state: &mut SignerState, message: &[u8]) {
181    crypto_sign_ed25519ph_update(&mut state.state, message)
182}
183
184/// Finalizes the incremental signature for `state`, using `secret_key`, copying
185/// the result into `signature` upon success, and consuming the state.
186///
187/// # Errors
188///
189/// The fixed-size signature and secret-key types satisfy the current
190/// implementation's requirements, so this function does not return an error
191/// in normal use. The [`Result`] is retained for API compatibility.
192pub fn crypto_sign_final_create(
193    state: SignerState,
194    signature: &mut Signature,
195    secret_key: &SecretKey,
196) -> Result<(), Error> {
197    crypto_sign_ed25519ph_final_create(state.state, signature, secret_key);
198    Ok(())
199}
200
201/// Verifies the computed signature for `state` and `public_key` matches
202/// `signature`, consuming the state.
203///
204/// # Errors
205///
206/// Returns an error if `signature` or `public_key` is malformed, or if the
207/// signature does not match the accumulated message.
208pub fn crypto_sign_final_verify(
209    state: SignerState,
210    signature: &Signature,
211    public_key: &PublicKey,
212) -> Result<(), Error> {
213    crypto_sign_ed25519ph_final_verify(state.state, signature, public_key)
214}
215
216#[cfg(test)]
217mod consistency_tests {
218    use super::*;
219    use crate::constants::CRYPTO_SIGN_BYTES;
220    use crate::test_prelude::*;
221    use crate::utils::test_util::XorShift64;
222
223    /// A combined signed message is the detached signature followed by the
224    /// message, for the empty, one-byte and 1023-byte messages (the RFC 8032
225    /// vector lengths); the signature verifies detached and the message opens.
226    #[test]
227    fn combined_signature_prefix_matches_detached() {
228        let mut rng = XorShift64::new(0x1f83_d9ab_fb41_bd6b);
229        let (public_key, secret_key) = crypto_sign_seed_keypair(&[21u8; 32]);
230        let mut random = Vec::with_capacity(1023);
231        while random.len() < 1023 {
232            random.extend_from_slice(&rng.next_bytes32());
233        }
234        random.truncate(1023);
235
236        for message in [&[][..], &[0x72], &random] {
237            let mut signature = [0u8; CRYPTO_SIGN_BYTES];
238            crypto_sign_detached(&mut signature, message, &secret_key).unwrap();
239
240            let mut signed_message = vec![0u8; message.len() + CRYPTO_SIGN_BYTES];
241            crypto_sign(&mut signed_message, message, &secret_key).unwrap();
242            assert_eq!(
243                signed_message[..CRYPTO_SIGN_BYTES],
244                signature,
245                "{}",
246                message.len()
247            );
248            assert_eq!(
249                signed_message[CRYPTO_SIGN_BYTES..],
250                *message,
251                "{}",
252                message.len()
253            );
254
255            crypto_sign_verify_detached(&signature, message, &public_key).unwrap();
256            let mut opened = vec![0xa5; message.len()];
257            crypto_sign_open(&mut opened, &signed_message, &public_key).unwrap();
258            assert_eq!(opened, message);
259        }
260    }
261
262    /// The incremental interface (Ed25519ph) gives one signature for a
263    /// message however it is split across updates, verifiable by a state fed
264    /// with any other split, and never the plain detached signature.
265    #[test]
266    fn incremental_signature_is_independent_of_split_points() {
267        let mut rng = XorShift64::new(0x5be0_cd19_137e_2179);
268        let (public_key, secret_key) = crypto_sign_seed_keypair(&[22u8; 32]);
269        let message: Vec<u8> = (0..3).flat_map(|_| rng.next_bytes32()).take(75).collect();
270
271        let mut whole = crypto_sign_init();
272        crypto_sign_update(&mut whole, &message);
273        let mut reference = [0u8; CRYPTO_SIGN_BYTES];
274        crypto_sign_final_create(whole, &mut reference, &secret_key).unwrap();
275
276        let mut byte_at_a_time = crypto_sign_init();
277        for byte in &message {
278            crypto_sign_update(&mut byte_at_a_time, core::slice::from_ref(byte));
279        }
280        crypto_sign_final_verify(byte_at_a_time, &reference, &public_key).unwrap();
281
282        for split in [0, 1, 63, 64, 65, 74, 75] {
283            let mut signer = crypto_sign_init();
284            crypto_sign_update(&mut signer, &message[..split]);
285            crypto_sign_update(&mut signer, &message[split..]);
286            let mut signature = [0u8; CRYPTO_SIGN_BYTES];
287            crypto_sign_final_create(signer, &mut signature, &secret_key).unwrap();
288            assert_eq!(signature, reference, "split {split}");
289        }
290
291        let mut detached = [0u8; CRYPTO_SIGN_BYTES];
292        crypto_sign_detached(&mut detached, &message, &secret_key).unwrap();
293        assert_ne!(reference, detached);
294        let mut verifier = crypto_sign_init();
295        crypto_sign_update(&mut verifier, &message);
296        assert!(matches!(
297            crypto_sign_final_verify(verifier, &detached, &public_key),
298            Err(Error::AuthenticationFailed)
299        ));
300        assert!(matches!(
301            crypto_sign_verify_detached(&reference, &message, &public_key),
302            Err(Error::AuthenticationFailed)
303        ));
304    }
305}
306
307#[cfg(all(test, dryoc_native_tests))]
308mod tests {
309    use super::*;
310    use crate::constants::{CRYPTO_SIGN_BYTES, CRYPTO_SIGN_PUBLICKEYBYTES};
311    use crate::test_prelude::*;
312
313    #[test]
314    fn combined_signing_rejects_invalid_buffer_lengths() {
315        let (public_key, secret_key) = crypto_sign_keypair();
316
317        let mut short_signed_message = [0u8; CRYPTO_SIGN_BYTES];
318        let error = crypto_sign(&mut short_signed_message, b"x", &secret_key)
319            .expect_err("the signed-message buffer should include the message");
320        assert!(matches!(
321            error,
322            Error::InvalidLength {
323                context: crate::ErrorContext::SignedMessage,
324                ..
325            }
326        ));
327
328        let mut message = [];
329        let short_input = [0u8; CRYPTO_SIGN_BYTES - 1];
330        let error = crypto_sign_open(&mut message, &short_input, &public_key)
331            .expect_err("a signed message must contain a full signature");
332        assert!(matches!(
333            error,
334            Error::InvalidLength {
335                context: crate::ErrorContext::SignedMessage,
336                ..
337            }
338        ));
339
340        let mut oversized_message = [0u8; 1];
341        let signature_only = [0u8; CRYPTO_SIGN_BYTES];
342        let error = crypto_sign_open(&mut oversized_message, &signature_only, &public_key)
343            .expect_err("the output length should match the embedded message");
344        assert!(matches!(
345            error,
346            Error::InvalidLength {
347                context: crate::ErrorContext::Message,
348                ..
349            }
350        ));
351    }
352
353    #[test]
354    fn verification_classifies_invalid_signatures_and_public_keys() {
355        let (public_key, secret_key) = crypto_sign_keypair();
356        let message = b"important message";
357        let mut signature = [0u8; CRYPTO_SIGN_BYTES];
358        crypto_sign_detached(&mut signature, message, &secret_key).expect("signing should succeed");
359
360        let mut tampered_signature = signature;
361        tampered_signature[CRYPTO_SIGN_BYTES - 1] ^= 1;
362        assert!(matches!(
363            crypto_sign_verify_detached(&tampered_signature, message, &public_key),
364            Err(Error::AuthenticationFailed)
365        ));
366        // The top byte of S is rejected by the scalar decoding before any
367        // curve arithmetic; a low bit of S (still below the group order) is a
368        // forgery that only the double-scalar multiplication can catch.
369        let mut tampered_s_low = signature;
370        tampered_s_low[32] ^= 1;
371        assert!(matches!(
372            crypto_sign_verify_detached(&tampered_s_low, message, &public_key),
373            Err(Error::AuthenticationFailed)
374        ));
375        // A flipped bit in R (still a valid curve point or not) and a changed
376        // message must both fail, not just a changed S.
377        for byte in [0, 15, 31] {
378            let mut tampered_r = signature;
379            tampered_r[byte] ^= 0x10;
380            assert!(matches!(
381                crypto_sign_verify_detached(&tampered_r, message, &public_key),
382                Err(Error::AuthenticationFailed)
383            ));
384        }
385        assert!(matches!(
386            crypto_sign_verify_detached(&signature, b"important massage", &public_key),
387            Err(Error::AuthenticationFailed)
388        ));
389        let (other_public_key, _) = crypto_sign_keypair();
390        assert!(matches!(
391            crypto_sign_verify_detached(&signature, message, &other_public_key),
392            Err(Error::AuthenticationFailed)
393        ));
394
395        assert!(matches!(
396            crypto_sign_verify_detached(&[0u8; CRYPTO_SIGN_BYTES], message, &public_key),
397            Err(Error::AuthenticationFailed)
398        ));
399
400        assert!(matches!(
401            crypto_sign_verify_detached(&signature, message, &[0u8; CRYPTO_SIGN_PUBLICKEYBYTES],),
402            Err(Error::InvalidKey {
403                context: crate::ErrorContext::Ed25519PublicKey,
404            })
405        ));
406    }
407
408    #[test]
409    fn test_crypto_sign() {
410        use base64::Engine as _;
411        use base64::engine::general_purpose;
412
413        use crate::native_test_util::{sign_ed25519, sign_ed25519_open};
414
415        for _ in 0..10 {
416            let (public_key, secret_key) = crypto_sign_keypair();
417            let message = b"important message";
418            let mut signed_message = vec![0u8; message.len() + CRYPTO_SIGN_BYTES];
419            crypto_sign(&mut signed_message, message, &secret_key).expect("sign failed");
420
421            let so_signed_message = sign_ed25519(message, &secret_key);
422
423            assert_eq!(
424                general_purpose::STANDARD.encode(&signed_message),
425                general_purpose::STANDARD.encode(&so_signed_message)
426            );
427
428            let so_m = sign_ed25519_open(&signed_message, &public_key).expect("verify failed");
429
430            assert_eq!(so_m, message);
431        }
432    }
433
434    #[test]
435    fn test_crypto_sign_open() {
436        use base64::Engine as _;
437        use base64::engine::general_purpose;
438
439        use crate::native_test_util::{sign_ed25519, sign_ed25519_open};
440
441        for _ in 0..10 {
442            let (public_key, secret_key) = crypto_sign_keypair();
443            let message = b"important message";
444            let mut signed_message = vec![0u8; message.len() + CRYPTO_SIGN_BYTES];
445            crypto_sign(&mut signed_message, message, &secret_key).expect("sign failed");
446
447            let so_signed_message = sign_ed25519(message, &secret_key);
448
449            assert_eq!(
450                general_purpose::STANDARD.encode(&signed_message),
451                general_purpose::STANDARD.encode(&so_signed_message)
452            );
453
454            let so_m = sign_ed25519_open(&signed_message, &public_key).expect("verify failed");
455
456            assert_eq!(so_m, message);
457
458            let mut opened_message = vec![0u8; message.len()];
459
460            crypto_sign_open(&mut opened_message, &signed_message, &public_key)
461                .expect("verify failed");
462
463            assert_eq!(opened_message, message);
464        }
465    }
466
467    #[test]
468    fn test_crypto_sign_detached() {
469        use crate::native_test_util::sign_ed25519_verify_detached;
470
471        for _ in 0..10 {
472            let (public_key, secret_key) = crypto_sign_keypair();
473            let message = b"important message";
474            let mut signature = [0u8; CRYPTO_SIGN_BYTES];
475            crypto_sign_detached(&mut signature, message, &secret_key).expect("sign failed");
476
477            assert!(sign_ed25519_verify_detached(
478                &signature,
479                message,
480                &public_key
481            ));
482
483            crypto_sign_verify_detached(&signature, message, &public_key).expect("verify failed");
484        }
485    }
486
487    #[test]
488    fn test_crypto_sign_incremental() {
489        use crate::native_test_util::{sign_ed25519ph, sign_ed25519ph_verify};
490        use crate::rng::copy_randombytes;
491
492        for _ in 0..10 {
493            let (public_key, secret_key) = crypto_sign_keypair();
494            let mut signer = crypto_sign_init();
495            let mut verifier = crypto_sign_init();
496
497            // libsodium's side absorbs the same three parts.
498            let mut so_parts = Vec::new();
499
500            for _ in 0..3 {
501                let mut randos = vec![0u8; 100];
502                copy_randombytes(&mut randos);
503
504                crypto_sign_update(&mut signer, &randos);
505                crypto_sign_update(&mut verifier, &randos);
506
507                so_parts.push(randos);
508            }
509            let so_parts: Vec<&[u8]> = so_parts.iter().map(Vec::as_slice).collect();
510
511            let mut signature = [0u8; CRYPTO_SIGN_BYTES];
512            crypto_sign_final_create(signer, &mut signature, &secret_key)
513                .expect("final create failed");
514
515            let so_signature = sign_ed25519ph(&so_parts, &secret_key);
516
517            assert_eq!(signature, so_signature);
518
519            crypto_sign_final_verify(verifier, &so_signature, &public_key).expect("verify failed");
520
521            assert!(sign_ed25519ph_verify(&so_parts, &signature, &public_key));
522        }
523    }
524}