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