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