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