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}