bc_components/signing/
signature_scheme.rs

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