bc_components/signing/
signature.rs

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