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}