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}