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::constants::CRYPTO_SIGN_BYTES;
61use crate::error::Error;
62
63/// In-place variant of [`crypto_sign_keypair`].
64pub fn crypto_sign_keypair_inplace(public_key: &mut PublicKey, secret_key: &mut SecretKey) {
65    crypto_sign_ed25519_keypair_inplace(public_key, secret_key)
66}
67
68/// In-place variant of [`crypto_sign_seed_keypair`].
69pub fn crypto_sign_seed_keypair_inplace(
70    public_key: &mut PublicKey,
71    secret_key: &mut SecretKey,
72    seed: &[u8; 32],
73) {
74    crypto_sign_ed25519_seed_keypair_inplace(public_key, secret_key, seed)
75}
76
77/// Randomly generates a new Ed25519 `(PublicKey, SecretKey)` keypair that can
78/// be used for message signing.
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.
85pub fn crypto_sign_seed_keypair(seed: &[u8; 32]) -> (PublicKey, SecretKey) {
86    crypto_sign_ed25519_seed_keypair(seed)
87}
88
89/// Signs `message`, placing the result into `signed_message`. The length of
90/// `signed_message` should be the length of the message plus
91/// [`CRYPTO_SIGN_BYTES`].
92///
93/// This function is compatible with libsodium's `crypto_sign`; the
94/// `ED25519_NONDETERMINISTIC` feature is not supported.
95///
96/// # Errors
97///
98/// Returns an error if `signed_message` is not exactly one signature longer
99/// than `message`, or signing fails.
100pub fn crypto_sign(
101    signed_message: &mut [u8],
102    message: &[u8],
103    secret_key: &SecretKey,
104) -> Result<(), Error> {
105    if signed_message.len() != message.len() + CRYPTO_SIGN_BYTES {
106        Err(length_error!(
107            crate::ErrorContext::SignedMessage,
108            signed_message.len(),
109            exact message.len() + CRYPTO_SIGN_BYTES
110        ))
111    } else {
112        crypto_sign_ed25519(signed_message, message, secret_key)
113    }
114}
115
116/// Verifies the signature of `signed_message`, placing the result into
117/// `message`. The length of `message` should be the length of the signed
118/// message minus [`CRYPTO_SIGN_BYTES`].
119///
120/// This function is compatible with libsodium's `crypto_sign_open`; the
121/// `ED25519_NONDETERMINISTIC` feature is not supported.
122///
123/// # Errors
124///
125/// Returns an error if `signed_message` is too short, `message` has the wrong
126/// length, or the signature or public key is invalid.
127pub fn crypto_sign_open(
128    message: &mut [u8],
129    signed_message: &[u8],
130    public_key: &PublicKey,
131) -> Result<(), Error> {
132    if signed_message.len() < CRYPTO_SIGN_BYTES {
133        Err(
134            length_error!(crate::ErrorContext::SignedMessage, signed_message.len(), min CRYPTO_SIGN_BYTES),
135        )
136    } else if message.len() != signed_message.len() - CRYPTO_SIGN_BYTES {
137        Err(length_error!(
138            crate::ErrorContext::Message,
139            message.len(),
140            exact signed_message.len() - CRYPTO_SIGN_BYTES
141        ))
142    } else {
143        crypto_sign_ed25519_open(message, signed_message, public_key)
144    }
145}
146
147/// Signs `message`, placing the signature into `signature` upon success.
148/// Detached variant of [`crypto_sign_open`].
149///
150/// This function is compatible with libsodium's `crypto_sign_detached`; the
151/// `ED25519_NONDETERMINISTIC` feature is not supported.
152///
153/// # Errors
154///
155/// The fixed-size signature and secret-key types satisfy the current
156/// implementation's requirements, so this function does not return an error
157/// in normal use. The [`Result`] is retained for API compatibility.
158pub fn crypto_sign_detached(
159    signature: &mut Signature,
160    message: &[u8],
161    secret_key: &SecretKey,
162) -> Result<(), Error> {
163    crypto_sign_ed25519_detached(signature, message, secret_key)
164}
165
166/// Verifies that `signature` is a valid signature for `message` using the given
167/// `public_key`.
168///
169/// This function is compatible with libsodium's `crypto_sign_verify_detached`;
170/// the `ED25519_NONDETERMINISTIC` feature is not supported.
171///
172/// # Errors
173///
174/// Returns an error if `signature` or `public_key` is malformed, or if the
175/// signature does not authenticate `message`.
176pub fn crypto_sign_verify_detached(
177    signature: &Signature,
178    message: &[u8],
179    public_key: &PublicKey,
180) -> Result<(), Error> {
181    crypto_sign_ed25519_verify_detached(signature, message, public_key)
182}
183
184/// State for incremental signing interface.
185pub struct SignerState {
186    state: Ed25519SignerState,
187}
188
189/// Initializes the incremental signing interface.
190pub fn crypto_sign_init() -> SignerState {
191    SignerState {
192        state: crypto_sign_ed25519ph_init(),
193    }
194}
195
196/// Updates the signature for `state` with `message`.
197pub fn crypto_sign_update(state: &mut SignerState, message: &[u8]) {
198    crypto_sign_ed25519ph_update(&mut state.state, message)
199}
200
201/// Finalizes the incremental signature for `state`, using `secret_key`, copying
202/// the result into `signature` upon success, and consuming the state.
203///
204/// # Errors
205///
206/// The fixed-size signature and secret-key types satisfy the current
207/// implementation's requirements, so this function does not return an error
208/// in normal use. The [`Result`] is retained for API compatibility.
209pub fn crypto_sign_final_create(
210    state: SignerState,
211    signature: &mut Signature,
212    secret_key: &SecretKey,
213) -> Result<(), Error> {
214    crypto_sign_ed25519ph_final_create(state.state, signature, secret_key)
215}
216
217/// Verifies the computed signature for `state` and `public_key` matches
218/// `signature`, consuming the state.
219///
220/// # Errors
221///
222/// Returns an error if `signature` or `public_key` is malformed, or if the
223/// signature does not match the accumulated message.
224pub fn crypto_sign_final_verify(
225    state: SignerState,
226    signature: &Signature,
227    public_key: &PublicKey,
228) -> Result<(), Error> {
229    crypto_sign_ed25519ph_final_verify(state.state, signature, public_key)
230}
231
232#[cfg(all(test, dryoc_native_tests))]
233mod tests {
234    use super::*;
235    use crate::constants::CRYPTO_SIGN_PUBLICKEYBYTES;
236
237    #[test]
238    fn combined_signing_rejects_invalid_buffer_lengths() {
239        let (public_key, secret_key) = crypto_sign_keypair();
240
241        let mut short_signed_message = [0u8; CRYPTO_SIGN_BYTES];
242        let error = crypto_sign(&mut short_signed_message, b"x", &secret_key)
243            .expect_err("the signed-message buffer should include the message");
244        assert!(matches!(
245            error,
246            Error::InvalidLength {
247                context: crate::ErrorContext::SignedMessage,
248                ..
249            }
250        ));
251
252        let mut message = [];
253        let short_input = [0u8; CRYPTO_SIGN_BYTES - 1];
254        let error = crypto_sign_open(&mut message, &short_input, &public_key)
255            .expect_err("a signed message must contain a full signature");
256        assert!(matches!(
257            error,
258            Error::InvalidLength {
259                context: crate::ErrorContext::SignedMessage,
260                ..
261            }
262        ));
263
264        let mut oversized_message = [0u8; 1];
265        let signature_only = [0u8; CRYPTO_SIGN_BYTES];
266        let error = crypto_sign_open(&mut oversized_message, &signature_only, &public_key)
267            .expect_err("the output length should match the embedded message");
268        assert!(matches!(
269            error,
270            Error::InvalidLength {
271                context: crate::ErrorContext::Message,
272                ..
273            }
274        ));
275    }
276
277    #[test]
278    fn verification_classifies_invalid_signatures_and_public_keys() {
279        let (public_key, secret_key) = crypto_sign_keypair();
280        let message = b"important message";
281        let mut signature = [0u8; CRYPTO_SIGN_BYTES];
282        crypto_sign_detached(&mut signature, message, &secret_key).expect("signing should succeed");
283
284        let mut tampered_signature = signature;
285        tampered_signature[CRYPTO_SIGN_BYTES - 1] ^= 1;
286        assert!(matches!(
287            crypto_sign_verify_detached(&tampered_signature, message, &public_key),
288            Err(Error::AuthenticationFailed)
289        ));
290
291        assert!(matches!(
292            crypto_sign_verify_detached(&[0u8; CRYPTO_SIGN_BYTES], message, &public_key),
293            Err(Error::AuthenticationFailed)
294        ));
295
296        assert!(matches!(
297            crypto_sign_verify_detached(&signature, message, &[0u8; CRYPTO_SIGN_PUBLICKEYBYTES],),
298            Err(Error::InvalidKey {
299                context: crate::ErrorContext::Ed25519PublicKey,
300            })
301        ));
302    }
303
304    #[test]
305    fn test_crypto_sign() {
306        use base64::Engine as _;
307        use base64::engine::general_purpose;
308        use sodiumoxide::crypto::sign;
309
310        for _ in 0..10 {
311            let (public_key, secret_key) = crypto_sign_keypair();
312            let message = b"important message";
313            let mut signed_message = vec![0u8; message.len() + CRYPTO_SIGN_BYTES];
314            crypto_sign(&mut signed_message, message, &secret_key).expect("sign failed");
315
316            let so_signed_message = sign::sign(
317                message,
318                &sign::SecretKey::from_slice(&secret_key).expect("secret key failed"),
319            );
320
321            assert_eq!(
322                general_purpose::STANDARD.encode(&signed_message),
323                general_purpose::STANDARD.encode(&so_signed_message)
324            );
325
326            let so_m = sign::verify(
327                &signed_message,
328                &sign::PublicKey::from_slice(&public_key).expect("public key failed"),
329            )
330            .expect("verify failed");
331
332            assert_eq!(so_m, message);
333        }
334    }
335
336    #[test]
337    fn test_crypto_sign_open() {
338        use base64::Engine as _;
339        use base64::engine::general_purpose;
340        use sodiumoxide::crypto::sign;
341
342        for _ in 0..10 {
343            let (public_key, secret_key) = crypto_sign_keypair();
344            let message = b"important message";
345            let mut signed_message = vec![0u8; message.len() + CRYPTO_SIGN_BYTES];
346            crypto_sign(&mut signed_message, message, &secret_key).expect("sign failed");
347
348            let so_signed_message = sign::sign(
349                message,
350                &sign::SecretKey::from_slice(&secret_key).expect("secret key failed"),
351            );
352
353            assert_eq!(
354                general_purpose::STANDARD.encode(&signed_message),
355                general_purpose::STANDARD.encode(&so_signed_message)
356            );
357
358            let so_m = sign::verify(
359                &signed_message,
360                &sign::PublicKey::from_slice(&public_key).expect("public key failed"),
361            )
362            .expect("verify failed");
363
364            assert_eq!(so_m, message);
365
366            let mut opened_message = vec![0u8; message.len()];
367
368            crypto_sign_open(&mut opened_message, &signed_message, &public_key)
369                .expect("verify failed");
370
371            assert_eq!(opened_message, message);
372        }
373    }
374
375    #[test]
376    fn test_crypto_sign_detached() {
377        use sodiumoxide::crypto::sign;
378
379        for _ in 0..10 {
380            let (public_key, secret_key) = crypto_sign_keypair();
381            let message = b"important message";
382            let mut signature = [0u8; CRYPTO_SIGN_BYTES];
383            crypto_sign_detached(&mut signature, message, &secret_key).expect("sign failed");
384
385            assert!(sign::verify_detached(
386                &sign::ed25519::Signature::from_bytes(&signature).expect("secret key failed"),
387                message,
388                &sign::PublicKey::from_slice(&public_key).expect("public key failed"),
389            ));
390
391            crypto_sign_verify_detached(&signature, message, &public_key).expect("verify failed");
392        }
393    }
394
395    #[test]
396    fn test_crypto_sign_incremental() {
397        use sodiumoxide::crypto::sign;
398
399        use crate::rng::copy_randombytes;
400
401        for _ in 0..10 {
402            let (public_key, secret_key) = crypto_sign_keypair();
403            let mut signer = crypto_sign_init();
404            let mut verifier = crypto_sign_init();
405
406            let mut so_signer = sign::State::init();
407            let mut so_verifier = sign::State::init();
408
409            for _ in 0..3 {
410                let mut randos = vec![0u8; 100];
411                copy_randombytes(&mut randos);
412
413                crypto_sign_update(&mut signer, &randos);
414                crypto_sign_update(&mut verifier, &randos);
415
416                so_signer.update(&randos);
417                so_verifier.update(&randos);
418            }
419
420            let mut signature = [0u8; CRYPTO_SIGN_BYTES];
421            crypto_sign_final_create(signer, &mut signature, &secret_key)
422                .expect("final create failed");
423
424            let so_signature = so_signer
425                .finalize(&sign::SecretKey::from_slice(&secret_key).expect("secret key failed"));
426
427            assert_eq!(signature, so_signature.to_bytes());
428
429            crypto_sign_final_verify(verifier, &so_signature.to_bytes(), &public_key)
430                .expect("verify failed");
431
432            assert!(so_signer.verify(
433                &sign::ed25519::Signature::from_bytes(&signature).expect("secret key failed"),
434                &sign::PublicKey::from_slice(&public_key).expect("public key failed"),
435            ));
436        }
437    }
438}