bc_components/signing/
signature_scheme.rs

1use bc_rand::RandomNumberGenerator;
2#[cfg(feature = "ssh")]
3use ssh_key::Algorithm;
4
5use super::{SigningPrivateKey, SigningPublicKey};
6#[cfg(feature = "secp256k1")]
7use crate::ECPrivateKey;
8#[cfg(feature = "ed25519")]
9use crate::Ed25519PrivateKey;
10#[cfg(feature = "ssh")]
11use crate::PrivateKeyBase;
12#[cfg_attr(not(feature = "pqcrypto"), allow(unused_imports))]
13use crate::{Error, Result};
14
15/// Supported digital signature schemes.
16///
17/// This enum represents the various signature schemes supported in this crate,
18/// including elliptic curve schemes (ECDSA, Schnorr), Edwards curve schemes
19/// (Ed25519), post-quantum schemes (ML-DSA), and SSH-specific algorithms.
20///
21/// # Examples
22///
23/// ```
24/// use bc_components::SignatureScheme;
25///
26/// // Use the default signature scheme (Schnorr)
27/// let scheme = SignatureScheme::default();
28/// let (private_key, public_key) = scheme.keypair();
29/// ```
30///
31/// ```ignore
32/// # use bc_components::SignatureScheme;
33/// // Create a key pair using a specific signature scheme
34/// let (mldsa_private, mldsa_public) = SignatureScheme::MLDSA65.keypair();
35/// ```
36#[derive(Clone, Debug, PartialEq, Eq, Hash)]
37#[cfg_attr(feature = "secp256k1", derive(Default))]
38#[cfg_attr(
39    all(feature = "ed25519", not(feature = "secp256k1")),
40    derive(Default)
41)]
42pub enum SignatureScheme {
43    /// BIP-340 Schnorr signature scheme, used in Bitcoin Taproot (default)
44    #[cfg(feature = "secp256k1")]
45    #[cfg_attr(feature = "secp256k1", default)]
46    Schnorr,
47
48    /// ECDSA signature scheme using the secp256k1 curve
49    #[cfg(feature = "secp256k1")]
50    Ecdsa,
51
52    /// Ed25519 signature scheme (RFC 8032)
53    #[cfg(feature = "ed25519")]
54    #[cfg_attr(all(feature = "ed25519", not(feature = "secp256k1")), default)]
55    Ed25519,
56
57    /// ML-DSA44 post-quantum signature scheme (NIST level 2)
58    #[cfg(feature = "pqcrypto")]
59    MLDSA44,
60
61    /// ML-DSA65 post-quantum signature scheme (NIST level 3)
62    #[cfg(feature = "pqcrypto")]
63    MLDSA65,
64
65    /// ML-DSA87 post-quantum signature scheme (NIST level 5)
66    #[cfg(feature = "pqcrypto")]
67    MLDSA87,
68
69    /// Ed25519 signature scheme for SSH
70    #[cfg(feature = "ssh")]
71    SshEd25519,
72
73    // Disabled due to tests not working correctly for undiagnosed reasons.
74    // SshRsaSha256,
75    // SshRsaSha512,
76    /// DSA signature scheme for SSH
77    #[cfg(feature = "ssh")]
78    SshDsa,
79
80    /// ECDSA signature scheme with NIST P-256 curve for SSH
81    #[cfg(feature = "ssh")]
82    SshEcdsaP256,
83
84    /// ECDSA signature scheme with NIST P-384 curve for SSH
85    #[cfg(feature = "ssh")]
86    SshEcdsaP384,
87    // Disabled due to a bug in the ssh-key crate.
88    // See: https://github.com/RustCrypto/SSH/issues/232
89
90    // SSH-ECDSA NIST P-521
91    // SshEcdsaP521,
92}
93
94impl SignatureScheme {
95    /// Creates a new key pair for the signature scheme using the system's
96    /// secure random number generator.
97    ///
98    /// This is a convenience method that calls `keypair_opt` with an empty
99    /// comment.
100    ///
101    /// # Returns
102    ///
103    /// A tuple containing a signing private key and its corresponding public
104    /// key.
105    ///
106    /// # Examples
107    ///
108    /// ```ignore
109    /// # // Requires secp256k1 feature (enabled by default)
110    /// use bc_components::SignatureScheme;
111    ///
112    /// // Generate a Schnorr key pair
113    /// let (private_key, public_key) = SignatureScheme::Schnorr.keypair();
114    ///
115    /// // Use the default scheme (also Schnorr)
116    /// let (default_private, default_public) =
117    ///     SignatureScheme::default().keypair();
118    /// ```
119    pub fn keypair(&self) -> (SigningPrivateKey, SigningPublicKey) {
120        self.keypair_opt("")
121    }
122
123    /// Creates a new key pair for the signature scheme with an optional
124    /// comment.
125    ///
126    /// The comment is only used for SSH keys and is ignored for other schemes.
127    ///
128    /// # Arguments
129    ///
130    /// * `comment` - A string comment to include with SSH keys
131    ///
132    /// # Returns
133    ///
134    /// A tuple containing a signing private key and its corresponding public
135    /// key.
136    ///
137    /// # Examples
138    ///
139    /// ```ignore
140    /// use bc_components::SignatureScheme;
141    ///
142    /// // Generate an SSH Ed25519 key pair with a comment
143    /// let (ssh_private, ssh_public) =
144    ///     SignatureScheme::SshEd25519.keypair_opt("user@example.com");
145    /// ```
146    pub fn keypair_opt(
147        &self,
148        comment: impl Into<String>,
149    ) -> (SigningPrivateKey, SigningPublicKey) {
150        #[cfg_attr(not(feature = "ssh"), allow(unused_variables))]
151        let comment = comment.into();
152        #[allow(unreachable_patterns)]
153        match self {
154            #[cfg(feature = "secp256k1")]
155            Self::Schnorr => {
156                let private_key =
157                    SigningPrivateKey::new_schnorr(ECPrivateKey::new());
158                let public_key = private_key.public_key().unwrap();
159                (private_key, public_key)
160            }
161            #[cfg(feature = "secp256k1")]
162            Self::Ecdsa => {
163                let private_key =
164                    SigningPrivateKey::new_ecdsa(ECPrivateKey::new());
165                let public_key = private_key.public_key().unwrap();
166                (private_key, public_key)
167            }
168            #[cfg(feature = "ed25519")]
169            Self::Ed25519 => {
170                let private_key =
171                    SigningPrivateKey::new_ed25519(Ed25519PrivateKey::new());
172                let public_key = private_key.public_key().unwrap();
173                (private_key, public_key)
174            }
175            #[cfg(feature = "pqcrypto")]
176            Self::MLDSA44 => {
177                let (private_key, public_key) = crate::MLDSA::MLDSA44.keypair();
178                let private_key = SigningPrivateKey::MLDSA(private_key);
179                let public_key = SigningPublicKey::MLDSA(public_key);
180                (private_key, public_key)
181            }
182            #[cfg(feature = "pqcrypto")]
183            Self::MLDSA65 => {
184                let (private_key, public_key) = crate::MLDSA::MLDSA65.keypair();
185                let private_key = SigningPrivateKey::MLDSA(private_key);
186                let public_key = SigningPublicKey::MLDSA(public_key);
187                (private_key, public_key)
188            }
189            #[cfg(feature = "pqcrypto")]
190            Self::MLDSA87 => {
191                let (private_key, public_key) = crate::MLDSA::MLDSA87.keypair();
192                let private_key = SigningPrivateKey::MLDSA(private_key);
193                let public_key = SigningPublicKey::MLDSA(public_key);
194                (private_key, public_key)
195            }
196            #[cfg(feature = "ssh")]
197            Self::SshEd25519 => {
198                let private_key_base = PrivateKeyBase::new();
199                let private_key = private_key_base
200                    .ssh_signing_private_key(Algorithm::Ed25519, comment)
201                    .unwrap();
202                let public_key = private_key.public_key().unwrap();
203                (private_key, public_key)
204            }
205            // Self::SshRsaSha256 => {
206            //     let private_key_base = PrivateKeyBase::new();
207            //     let private_key = private_key_base
208            //         .ssh_signing_private_key(
209            //             Algorithm::Rsa { hash: Some(HashAlg::Sha256) },
210            //             comment
211            //         )
212            //         .unwrap();
213            //     let public_key = private_key.public_key().unwrap();
214            //     (private_key, public_key)
215            // }
216            // Self::SshRsaSha512 => {
217            //     let private_key_base = PrivateKeyBase::new();
218            //     let private_key = private_key_base
219            //         .ssh_signing_private_key(
220            //             Algorithm::Rsa { hash: Some(HashAlg::Sha512) },
221            //             comment
222            //         )
223            //         .unwrap();
224            //     let public_key = private_key.public_key().unwrap();
225            //     (private_key, public_key)
226            // }
227            #[cfg(feature = "ssh")]
228            Self::SshDsa => {
229                let private_key_base = PrivateKeyBase::new();
230                let private_key = private_key_base
231                    .ssh_signing_private_key(Algorithm::Dsa, comment)
232                    .unwrap();
233                let public_key = private_key.public_key().unwrap();
234                (private_key, public_key)
235            }
236            #[cfg(feature = "ssh")]
237            Self::SshEcdsaP256 => {
238                let private_key_base = PrivateKeyBase::new();
239                let private_key = private_key_base
240                    .ssh_signing_private_key(
241                        Algorithm::Ecdsa {
242                            curve: ssh_key::EcdsaCurve::NistP256,
243                        },
244                        comment,
245                    )
246                    .unwrap();
247                let public_key = private_key.public_key().unwrap();
248                (private_key, public_key)
249            }
250            #[cfg(feature = "ssh")]
251            Self::SshEcdsaP384 => {
252                let private_key_base = PrivateKeyBase::new();
253                let private_key = private_key_base
254                    .ssh_signing_private_key(
255                        Algorithm::Ecdsa {
256                            curve: ssh_key::EcdsaCurve::NistP384,
257                        },
258                        comment,
259                    )
260                    .unwrap();
261                let public_key = private_key.public_key().unwrap();
262                (private_key, public_key)
263            }
264            #[cfg(not(any(
265                feature = "secp256k1",
266                feature = "ed25519",
267                feature = "pqcrypto"
268            )))]
269            _ => unreachable!(),
270        }
271    }
272
273    /// Creates a key pair for the signature scheme using a provided random
274    /// number generator.
275    ///
276    /// This allows for deterministic key generation when using a seeded RNG.
277    /// Note that not all signature schemes support deterministic generation.
278    ///
279    /// # Arguments
280    ///
281    /// * `rng` - A mutable reference to a random number generator
282    /// * `comment` - A string comment to include with SSH keys
283    ///
284    /// # Returns
285    ///
286    /// A `Result` containing a tuple of signing private key and public key, or
287    /// an error if the signature scheme doesn't support deterministic
288    /// generation.
289    ///
290    /// # Examples
291    ///
292    /// ```ignore
293    /// # // Requires secp256k1 feature (enabled by default)
294    /// use bc_components::SignatureScheme;
295    /// use bc_rand::SecureRandomNumberGenerator;
296    ///
297    /// let mut rng = SecureRandomNumberGenerator;
298    ///
299    /// // Generate an ECDSA key pair with a specific RNG
300    /// let result = SignatureScheme::Ecdsa.keypair_using(&mut rng, "");
301    /// assert!(result.is_ok());
302    /// ```
303    pub fn keypair_using(
304        &self,
305        _rng: &mut impl RandomNumberGenerator,
306        comment: impl Into<String>,
307    ) -> Result<(SigningPrivateKey, SigningPublicKey)> {
308        #[cfg_attr(not(feature = "ssh"), allow(unused_variables))]
309        let comment = comment.into();
310        #[allow(unreachable_patterns)]
311        match self {
312            #[cfg(feature = "secp256k1")]
313            Self::Schnorr => {
314                let private_key = SigningPrivateKey::new_schnorr(
315                    ECPrivateKey::new_using(_rng),
316                );
317                let public_key = private_key.public_key().unwrap();
318                Ok((private_key, public_key))
319            }
320            #[cfg(feature = "secp256k1")]
321            Self::Ecdsa => {
322                let private_key =
323                    SigningPrivateKey::new_ecdsa(ECPrivateKey::new_using(_rng));
324                let public_key = private_key.public_key().unwrap();
325                Ok((private_key, public_key))
326            }
327            #[cfg(feature = "ed25519")]
328            Self::Ed25519 => {
329                let private_key = SigningPrivateKey::new_ed25519(
330                    Ed25519PrivateKey::new_using(_rng),
331                );
332                let public_key = private_key.public_key().unwrap();
333                Ok((private_key, public_key))
334            }
335            #[cfg(feature = "ssh")]
336            Self::SshEd25519 => {
337                let private_key_base = PrivateKeyBase::new_using(_rng);
338                let private_key = private_key_base
339                    .ssh_signing_private_key(Algorithm::Ed25519, comment)
340                    .unwrap();
341                let public_key = private_key.public_key().unwrap();
342                Ok((private_key, public_key))
343            }
344            #[cfg(feature = "ssh")]
345            Self::SshDsa => {
346                let private_key_base = PrivateKeyBase::new_using(_rng);
347                let private_key = private_key_base
348                    .ssh_signing_private_key(Algorithm::Dsa, comment)
349                    .unwrap();
350                let public_key = private_key.public_key().unwrap();
351                Ok((private_key, public_key))
352            }
353            #[cfg(feature = "ssh")]
354            Self::SshEcdsaP256 => {
355                let private_key_base = PrivateKeyBase::new_using(_rng);
356                let private_key = private_key_base
357                    .ssh_signing_private_key(
358                        Algorithm::Ecdsa {
359                            curve: ssh_key::EcdsaCurve::NistP256,
360                        },
361                        comment,
362                    )
363                    .unwrap();
364                let public_key = private_key.public_key().unwrap();
365                Ok((private_key, public_key))
366            }
367            #[cfg(feature = "ssh")]
368            Self::SshEcdsaP384 => {
369                let private_key_base = PrivateKeyBase::new_using(_rng);
370                let private_key = private_key_base
371                    .ssh_signing_private_key(
372                        Algorithm::Ecdsa {
373                            curve: ssh_key::EcdsaCurve::NistP384,
374                        },
375                        comment,
376                    )
377                    .unwrap();
378                let public_key = private_key.public_key().unwrap();
379                Ok((private_key, public_key))
380            }
381            #[cfg(feature = "pqcrypto")]
382            _ => Err(Error::general(
383                "Deterministic keypair generation not supported for this signature scheme",
384            )),
385            #[cfg(not(any(
386                feature = "secp256k1",
387                feature = "ed25519",
388                feature = "pqcrypto"
389            )))]
390            _ => unreachable!(),
391        }
392    }
393}