bc_components/signing/signature.rs
1use bc_crypto::{
2 ECDSA_SIGNATURE_SIZE, ED25519_SIGNATURE_SIZE, SCHNORR_SIGNATURE_SIZE,
3};
4use bc_ur::prelude::*;
5use ssh_key::{LineEnding, SshSig};
6
7use super::SignatureScheme;
8use crate::{Error, MLDSASignature, Result, tags};
9
10/// A digital signature created with various signature algorithms.
11///
12/// `Signature` is an enum representing different types of digital signatures:
13///
14/// - `Schnorr`: A BIP-340 Schnorr signature (64 bytes)
15/// - `ECDSA`: An ECDSA signature using the secp256k1 curve (64 bytes)
16/// - `Ed25519`: An Ed25519 signature (64 bytes)
17/// - `SSH`: An SSH signature in various formats
18/// - `MLDSA`: A post-quantum ML-DSA signature
19///
20/// Signatures can be serialized to and from CBOR with appropriate tags.
21///
22/// # Examples
23///
24/// ```
25/// use bc_components::{SignatureScheme, Signer, Verifier};
26///
27/// // Create a key pair using Schnorr
28/// let (private_key, public_key) = SignatureScheme::Schnorr.keypair();
29///
30/// // Sign a message
31/// let message = b"Hello, world!";
32/// let signature = private_key.sign(&message).unwrap();
33///
34/// // The signature can be verified with the corresponding public key
35/// assert!(public_key.verify(&signature, &message));
36/// ```
37///
38/// Converting to and from CBOR:
39///
40/// ```
41/// use bc_components::{SignatureScheme, Signer};
42/// use dcbor::prelude::*;
43///
44/// // Create a signature
45/// let (private_key, _) = SignatureScheme::Schnorr.keypair();
46/// let message = b"Hello, world!";
47/// let signature = private_key.sign(&message).unwrap();
48///
49/// // Convert to CBOR
50/// let cbor: CBOR = signature.clone().into();
51/// let data = cbor.to_cbor_data();
52///
53/// // Convert back from CBOR
54/// let recovered =
55/// bc_components::Signature::from_tagged_cbor_data(&data).unwrap();
56///
57/// // The signatures should be identical
58/// assert_eq!(signature, recovered);
59/// ```
60#[derive(Clone)]
61pub enum Signature {
62 /// A BIP-340 Schnorr signature (64 bytes)
63 Schnorr([u8; SCHNORR_SIGNATURE_SIZE]),
64
65 /// An ECDSA signature using the secp256k1 curve (64 bytes)
66 ECDSA([u8; ECDSA_SIGNATURE_SIZE]),
67
68 /// An Ed25519 signature (64 bytes)
69 Ed25519([u8; ED25519_SIGNATURE_SIZE]),
70
71 /// An SSH signature
72 SSH(SshSig),
73
74 /// A post-quantum ML-DSA signature
75 MLDSA(MLDSASignature),
76}
77
78/// Implementation of equality comparison for Signature
79impl PartialEq for Signature {
80 /// Compares two signatures for equality.
81 ///
82 /// Signatures are equal if they have the same type and the same signature
83 /// data. Signatures of different types (e.g., Schnorr vs ECDSA) are
84 /// never equal.
85 fn eq(&self, other: &Self) -> bool {
86 match (self, other) {
87 (Self::Schnorr(a), Self::Schnorr(b)) => a == b,
88 (Self::ECDSA(a), Self::ECDSA(b)) => a == b,
89 (Self::Ed25519(a), Self::Ed25519(b)) => a == b,
90 (Self::SSH(a), Self::SSH(b)) => a == b,
91 (Self::MLDSA(a), Self::MLDSA(b)) => a.as_bytes() == b.as_bytes(),
92 _ => false,
93 }
94 }
95}
96
97impl Signature {
98 /// Creates a Schnorr signature from a 64-byte array.
99 ///
100 /// # Arguments
101 ///
102 /// * `data` - The 64-byte signature data
103 ///
104 /// # Returns
105 ///
106 /// A new Schnorr signature
107 ///
108 /// # Examples
109 ///
110 /// ```
111 /// use bc_components::Signature;
112 ///
113 /// let data = [0u8; 64]; // In practice, this would be a real signature
114 /// let signature = Signature::schnorr_from_data(data);
115 /// ```
116 pub fn schnorr_from_data(data: [u8; SCHNORR_SIGNATURE_SIZE]) -> Self {
117 Self::Schnorr(data)
118 }
119
120 /// Creates a Schnorr signature from a byte slice.
121 ///
122 /// # Arguments
123 ///
124 /// * `data` - A byte slice containing the signature data
125 ///
126 /// # Returns
127 ///
128 /// A `Result` containing the signature or an error if the data is not
129 /// exactly 64 bytes in length.
130 ///
131 /// # Examples
132 ///
133 /// ```
134 /// use bc_components::Signature;
135 ///
136 /// let data = vec![0u8; 64]; // In practice, this would be a real signature
137 /// let signature = Signature::schnorr_from_data_ref(&data).unwrap();
138 /// ```
139 pub fn schnorr_from_data_ref(data: impl AsRef<[u8]>) -> Result<Self> {
140 let data = data.as_ref();
141 if data.len() != SCHNORR_SIGNATURE_SIZE {
142 return Err(Error::invalid_size(
143 "Schnorr signature",
144 SCHNORR_SIGNATURE_SIZE,
145 data.len(),
146 ));
147 }
148 let mut arr = [0u8; SCHNORR_SIGNATURE_SIZE];
149 arr.copy_from_slice(data);
150 Ok(Self::schnorr_from_data(arr))
151 }
152
153 /// Creates an ECDSA signature from a 64-byte array.
154 ///
155 /// # Arguments
156 ///
157 /// * `data` - The 64-byte signature data
158 ///
159 /// # Returns
160 ///
161 /// A new ECDSA signature
162 ///
163 /// # Examples
164 ///
165 /// ```
166 /// use bc_components::Signature;
167 ///
168 /// let data = [0u8; 64]; // In practice, this would be a real signature
169 /// let signature = Signature::ecdsa_from_data(data);
170 /// ```
171 pub fn ecdsa_from_data(data: [u8; ECDSA_SIGNATURE_SIZE]) -> Self {
172 Self::ECDSA(data)
173 }
174
175 /// Creates an ECDSA signature from a byte slice.
176 ///
177 /// # Arguments
178 ///
179 /// * `data` - A byte slice containing the signature data
180 ///
181 /// # Returns
182 ///
183 /// A `Result` containing the signature or an error if the data is not
184 /// exactly 64 bytes in length.
185 ///
186 /// # Examples
187 ///
188 /// ```
189 /// use bc_components::Signature;
190 ///
191 /// let data = vec![0u8; 64]; // In practice, this would be a real signature
192 /// let signature = Signature::ecdsa_from_data_ref(&data).unwrap();
193 /// ```
194 pub fn ecdsa_from_data_ref(data: impl AsRef<[u8]>) -> Result<Self> {
195 let data = data.as_ref();
196 if data.len() != ECDSA_SIGNATURE_SIZE {
197 return Err(Error::invalid_size(
198 "ECDSA signature",
199 ECDSA_SIGNATURE_SIZE,
200 data.len(),
201 ));
202 }
203 let mut arr = [0u8; ECDSA_SIGNATURE_SIZE];
204 arr.copy_from_slice(data);
205 Ok(Self::ecdsa_from_data(arr))
206 }
207
208 /// Creates an Ed25519 signature from a 64-byte array.
209 ///
210 /// # Arguments
211 ///
212 /// * `data` - The 64-byte signature data
213 ///
214 /// # Returns
215 ///
216 /// A new Ed25519 signature
217 ///
218 /// # Examples
219 ///
220 /// ```
221 /// use bc_components::Signature;
222 ///
223 /// let data = [0u8; 64]; // In practice, this would be a real signature
224 /// let signature = Signature::ed25519_from_data(data);
225 /// ```
226 pub fn ed25519_from_data(data: [u8; ED25519_SIGNATURE_SIZE]) -> Self {
227 Self::Ed25519(data)
228 }
229
230 /// Creates an Ed25519 signature from a byte slice.
231 ///
232 /// # Arguments
233 ///
234 /// * `data` - A byte slice containing the signature data
235 ///
236 /// # Returns
237 ///
238 /// A `Result` containing the signature or an error if the data is not
239 /// exactly 64 bytes in length.
240 ///
241 /// # Examples
242 ///
243 /// ```
244 /// use bc_components::Signature;
245 ///
246 /// let data = vec![0u8; 64]; // In practice, this would be a real signature
247 /// let signature = Signature::ed25519_from_data_ref(&data).unwrap();
248 /// ```
249 pub fn ed25519_from_data_ref(data: impl AsRef<[u8]>) -> Result<Self> {
250 let data = data.as_ref();
251 if data.len() != ED25519_SIGNATURE_SIZE {
252 return Err(Error::invalid_size(
253 "Ed25519 signature",
254 ED25519_SIGNATURE_SIZE,
255 data.len(),
256 ));
257 }
258 let mut arr = [0u8; ED25519_SIGNATURE_SIZE];
259 arr.copy_from_slice(data);
260 Ok(Self::Ed25519(arr))
261 }
262
263 /// Creates an SSH signature from an `SshSig` object.
264 ///
265 /// # Arguments
266 ///
267 /// * `sig` - The SSH signature object
268 ///
269 /// # Returns
270 ///
271 /// A new SSH signature
272 pub fn from_ssh(sig: SshSig) -> Self { Self::SSH(sig) }
273
274 /// Returns the Schnorr signature data if this is a Schnorr signature.
275 ///
276 /// # Returns
277 ///
278 /// Some reference to the 64-byte signature data if this is a Schnorr
279 /// signature, or None if it's a different signature type.
280 ///
281 /// # Examples
282 ///
283 /// ```
284 /// use bc_components::{SignatureScheme, Signer};
285 ///
286 /// // Create a Schnorr signature
287 /// let (private_key, _) = SignatureScheme::Schnorr.keypair();
288 /// let message = b"Hello, world!";
289 /// let signature = private_key.sign(&message).unwrap();
290 ///
291 /// // We can access the Schnorr signature data
292 /// assert!(signature.to_schnorr().is_some());
293 ///
294 /// // Create an ECDSA signature
295 /// let (ecdsa_key, _) = SignatureScheme::Ecdsa.keypair();
296 /// let ecdsa_sig = ecdsa_key.sign(&message).unwrap();
297 ///
298 /// // This will return None since it's not a Schnorr signature
299 /// assert!(ecdsa_sig.to_schnorr().is_none());
300 /// ```
301 pub fn to_schnorr(&self) -> Option<&[u8; SCHNORR_SIGNATURE_SIZE]> {
302 match self {
303 Self::Schnorr(sig) => Some(sig),
304 _ => None,
305 }
306 }
307
308 /// Returns the ECDSA signature data if this is an ECDSA signature.
309 ///
310 /// # Returns
311 ///
312 /// Some reference to the 64-byte signature data if this is an ECDSA
313 /// signature, or None if it's a different signature type.
314 pub fn to_ecdsa(&self) -> Option<&[u8; ECDSA_SIGNATURE_SIZE]> {
315 match self {
316 Self::ECDSA(sig) => Some(sig),
317 _ => None,
318 }
319 }
320
321 /// Returns the SSH signature if this is an SSH signature.
322 ///
323 /// # Returns
324 ///
325 /// Some reference to the SSH signature if this is an SSH signature,
326 /// or None if it's a different signature type.
327 pub fn to_ssh(&self) -> Option<&SshSig> {
328 match self {
329 Self::SSH(sig) => Some(sig),
330 _ => None,
331 }
332 }
333
334 /// Determines the signature scheme used to create this signature.
335 ///
336 /// # Returns
337 ///
338 /// A `Result` containing the signature scheme, or an error if the
339 /// signature scheme cannot be determined (e.g., for unsupported SSH
340 /// algorithms).
341 ///
342 /// # Examples
343 ///
344 /// ```
345 /// use bc_components::{SignatureScheme, Signer};
346 ///
347 /// // Create a signature with ECDSA
348 /// let (private_key, _) = SignatureScheme::Ecdsa.keypair();
349 /// let message = b"Hello, world!";
350 /// let signature = private_key.sign(&message).unwrap();
351 ///
352 /// // Get the signature scheme
353 /// let scheme = signature.scheme().unwrap();
354 /// assert_eq!(scheme, SignatureScheme::Ecdsa);
355 /// ```
356 pub fn scheme(&self) -> Result<SignatureScheme> {
357 match self {
358 Self::Schnorr(_) => Ok(SignatureScheme::Schnorr),
359 Self::ECDSA(_) => Ok(SignatureScheme::Ecdsa),
360 Self::Ed25519(_) => Ok(SignatureScheme::Ed25519),
361 Self::SSH(sig) => match sig.algorithm() {
362 ssh_key::Algorithm::Dsa => Ok(SignatureScheme::SshDsa),
363 ssh_key::Algorithm::Ecdsa { curve } => match curve {
364 ssh_key::EcdsaCurve::NistP256 => {
365 Ok(SignatureScheme::SshEcdsaP256)
366 }
367 ssh_key::EcdsaCurve::NistP384 => {
368 Ok(SignatureScheme::SshEcdsaP384)
369 }
370 _ => Err(Error::ssh("Unsupported SSH ECDSA curve")),
371 },
372 ssh_key::Algorithm::Ed25519 => Ok(SignatureScheme::SshEd25519),
373 _ => Err(Error::ssh("Unsupported SSH signature algorithm")),
374 },
375 Self::MLDSA(sig) => match sig.level() {
376 crate::MLDSA::MLDSA44 => Ok(SignatureScheme::MLDSA44),
377 crate::MLDSA::MLDSA65 => Ok(SignatureScheme::MLDSA65),
378 crate::MLDSA::MLDSA87 => Ok(SignatureScheme::MLDSA87),
379 },
380 }
381 }
382}
383
384/// Debug implementation for Signature
385impl std::fmt::Debug for Signature {
386 /// Formats the signature for display.
387 ///
388 /// For binary signatures (Schnorr, ECDSA, Ed25519), displays the
389 /// hex-encoded signature data. For SSH and ML-DSA signatures, displays
390 /// the signature object.
391 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
392 match self {
393 Signature::Schnorr(data) => f
394 .debug_struct("Schnorr")
395 .field("data", &hex::encode(data))
396 .finish(),
397 Signature::ECDSA(data) => f
398 .debug_struct("ECDSA")
399 .field("data", &hex::encode(data))
400 .finish(),
401 Signature::Ed25519(data) => f
402 .debug_struct("Ed25519")
403 .field("data", &hex::encode(data))
404 .finish(),
405 Signature::SSH(sig) => {
406 f.debug_struct("SSH").field("sig", sig).finish()
407 }
408 Signature::MLDSA(sig) => {
409 f.debug_struct("MLDSA").field("sig", sig).finish()
410 }
411 }
412 }
413}
414
415/// Implementation of AsRef for Signature
416impl AsRef<Signature> for Signature {
417 /// Returns a reference to self.
418 fn as_ref(&self) -> &Signature { self }
419}
420
421/// Implementation of the CBORTagged trait for Signature
422impl CBORTagged for Signature {
423 /// Returns the CBOR tags used for this type.
424 ///
425 /// For Signature, the tag is 40020.
426 fn cbor_tags() -> Vec<dcbor::Tag> {
427 tags_for_values(&[tags::TAG_SIGNATURE])
428 }
429}
430
431/// Conversion from Signature to CBOR
432impl From<Signature> for CBOR {
433 /// Converts a Signature to a tagged CBOR value.
434 fn from(value: Signature) -> Self { value.tagged_cbor() }
435}
436
437/// Implementation of the CBORTaggedEncodable trait for Signature
438impl CBORTaggedEncodable for Signature {
439 /// Converts the Signature to an untagged CBOR value.
440 ///
441 /// The CBOR encoding depends on the signature type:
442 ///
443 /// - Schnorr: A byte string containing the 64-byte signature
444 /// - ECDSA: An array containing the discriminator 1 and the 64-byte
445 /// signature
446 /// - Ed25519: An array containing the discriminator 2 and the 64-byte
447 /// signature
448 /// - SSH: A tagged text string containing the PEM-encoded signature
449 /// - ML-DSA: Delegates to the MLDSASignature implementation
450 fn untagged_cbor(&self) -> CBOR {
451 match self {
452 Signature::Schnorr(data) => CBOR::to_byte_string(data),
453 Signature::ECDSA(data) => {
454 vec![(1).into(), CBOR::to_byte_string(data)].into()
455 }
456 Signature::Ed25519(data) => {
457 vec![(2).into(), CBOR::to_byte_string(data)].into()
458 }
459 Signature::SSH(sig) => {
460 let pem = sig.to_pem(LineEnding::LF).unwrap();
461 CBOR::to_tagged_value(tags::TAG_SSH_TEXT_SIGNATURE, pem)
462 }
463 Signature::MLDSA(sig) => sig.clone().into(),
464 }
465 }
466}
467
468/// TryFrom implementation for converting CBOR to Signature
469impl TryFrom<CBOR> for Signature {
470 type Error = dcbor::Error;
471
472 /// Tries to convert a CBOR value to a Signature.
473 ///
474 /// This is a convenience method that calls from_tagged_cbor.
475 fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
476 Self::from_tagged_cbor(cbor)
477 }
478}
479
480/// Implementation of the CBORTaggedDecodable trait for Signature
481impl CBORTaggedDecodable for Signature {
482 /// Creates a Signature from an untagged CBOR value.
483 ///
484 /// # Arguments
485 ///
486 /// * `cbor` - The CBOR value to decode
487 ///
488 /// # Returns
489 ///
490 /// A Result containing the decoded Signature or an error if decoding fails.
491 ///
492 /// # Format
493 ///
494 /// The CBOR value must be one of:
495 /// - A byte string (interpreted as a Schnorr signature)
496 /// - An array of length 2, where the first element is 1 (ECDSA) or 2
497 /// (Ed25519) and the second element is a byte string containing the
498 /// signature data
499 /// - A tagged value with a tag for MLDSA or SSH signatures
500 fn from_untagged_cbor(cbor: CBOR) -> dcbor::Result<Self> {
501 match cbor.clone().into_case() {
502 CBORCase::ByteString(bytes) => {
503 Ok(Self::schnorr_from_data_ref(bytes)?)
504 }
505 CBORCase::Array(mut elements) => {
506 if elements.len() == 2 {
507 let mut drain = elements.drain(0..);
508 let ele_0 = drain.next().unwrap().into_case();
509 let ele_1 = drain.next().unwrap().into_case();
510 match ele_0 {
511 CBORCase::ByteString(data) => {
512 return Ok(Self::schnorr_from_data_ref(data)?);
513 }
514 CBORCase::Unsigned(1) => {
515 if let CBORCase::ByteString(data) = ele_1 {
516 return Ok(Self::ecdsa_from_data_ref(data)?);
517 }
518 }
519 CBORCase::Unsigned(2) => {
520 if let CBORCase::ByteString(data) = ele_1 {
521 return Ok(Self::ed25519_from_data_ref(data)?);
522 }
523 }
524 _ => (),
525 }
526 }
527 Err("Invalid signature format".into())
528 }
529 CBORCase::Tagged(tag, item) => match tag.value() {
530 tags::TAG_MLDSA_SIGNATURE => {
531 let sig = MLDSASignature::try_from(cbor)?;
532 Ok(Self::MLDSA(sig))
533 }
534 tags::TAG_SSH_TEXT_SIGNATURE => {
535 let string = item.try_into_text()?;
536 let pem = SshSig::from_pem(string)
537 .map_err(|_| "Invalid PEM format")?;
538 Ok(Self::SSH(pem))
539 }
540 _ => Err("Invalid signature format".into()),
541 },
542 _ => Err("Invalid signature format".into()),
543 }
544 }
545}