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}