bc_components/signing/
signature_scheme.rs

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