bc_components/signing/
signing_public_key.rs

1use bc_ur::prelude::*;
2#[cfg(feature = "ssh")]
3use ssh_key::public::PublicKey as SSHPublicKey;
4
5#[cfg(feature = "ed25519")]
6use crate::Ed25519PublicKey;
7#[cfg(feature = "pqcrypto")]
8use crate::MLDSAPublicKey;
9use crate::{Digest, Reference, ReferenceProvider, Signature, Verifier, tags};
10#[cfg(feature = "secp256k1")]
11use crate::{ECKeyBase, ECPublicKey, SchnorrPublicKey};
12
13/// A public key used for verifying digital signatures.
14///
15/// `SigningPublicKey` is an enum representing different types of signing public
16/// keys, including elliptic curve schemes (ECDSA, Schnorr), Edwards curve
17/// schemes (Ed25519), post-quantum schemes (ML-DSA), and SSH keys.
18///
19/// This type implements the `Verifier` trait, allowing it to verify signatures
20/// of the appropriate type.
21///
22/// # Examples
23///
24/// Creating and using a signing public key pair:
25///
26/// ```ignore
27/// # // Requires secp256k1 feature (enabled by default)
28/// use bc_components::{SignatureScheme, Signer, Verifier};
29///
30/// // Create a key pair
31/// let (private_key, public_key) = SignatureScheme::Schnorr.keypair();
32///
33/// // Sign a message
34/// let message = b"Hello, world!";
35/// let signature = private_key.sign(&message).unwrap();
36///
37/// // Verify the signature
38/// assert!(public_key.verify(&signature, &message));
39/// ```
40///
41/// # CBOR Serialization
42///
43/// `SigningPublicKey` can be serialized to and from CBOR with appropriate tags:
44///
45/// ```ignore
46/// # // Requires secp256k1 feature (enabled by default)
47/// use bc_components::{SignatureScheme, SigningPublicKey};
48/// use dcbor::prelude::*;
49///
50/// // Create a key pair and get the public key
51/// let (_, public_key) = SignatureScheme::Schnorr.keypair();
52///
53/// // Convert to CBOR
54/// let cbor: CBOR = public_key.clone().into();
55/// let data = cbor.to_cbor_data();
56///
57/// // Convert back from CBOR
58/// let recovered = SigningPublicKey::from_tagged_cbor_data(&data).unwrap();
59///
60/// // The keys should be equal
61/// assert_eq!(public_key, recovered);
62/// ```
63#[derive(Clone, Debug, PartialEq, Eq, Hash)]
64pub enum SigningPublicKey {
65    /// A Schnorr public key (BIP-340, x-only)
66    #[cfg(feature = "secp256k1")]
67    Schnorr(SchnorrPublicKey),
68
69    /// An ECDSA public key (compressed, 33 bytes)
70    #[cfg(feature = "secp256k1")]
71    ECDSA(ECPublicKey),
72
73    /// An Ed25519 public key
74    #[cfg(feature = "ed25519")]
75    Ed25519(Ed25519PublicKey),
76
77    /// An SSH public key
78    #[cfg(feature = "ssh")]
79    SSH(SSHPublicKey),
80
81    /// A post-quantum ML-DSA public key
82    #[cfg(feature = "pqcrypto")]
83    MLDSA(MLDSAPublicKey),
84}
85
86impl SigningPublicKey {
87    /// Creates a new signing public key from a Schnorr public key.
88    ///
89    /// # Arguments
90    ///
91    /// * `key` - A BIP-340 Schnorr public key
92    ///
93    /// # Returns
94    ///
95    /// A new signing public key containing the Schnorr key
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// # #[cfg(feature = "secp256k1")]
101    /// # {
102    /// use bc_components::{SchnorrPublicKey, SigningPublicKey};
103    ///
104    /// // Create a Schnorr public key
105    /// let schnorr_key = SchnorrPublicKey::from_data([0u8; 32]);
106    ///
107    /// // Create a signing public key from it
108    /// let signing_key = SigningPublicKey::from_schnorr(schnorr_key);
109    /// # }
110    /// ```
111    #[cfg(feature = "secp256k1")]
112    pub fn from_schnorr(key: SchnorrPublicKey) -> Self { Self::Schnorr(key) }
113
114    /// Creates a new signing public key from an ECDSA public key.
115    ///
116    /// # Arguments
117    ///
118    /// * `key` - A compressed ECDSA public key
119    ///
120    /// # Returns
121    ///
122    /// A new signing public key containing the ECDSA key
123    ///
124    /// # Examples
125    ///
126    /// ```
127    /// # #[cfg(feature = "secp256k1")]
128    /// # {
129    /// use bc_components::{ECKey, ECPrivateKey, SigningPublicKey};
130    ///
131    /// // Create an EC private key and derive its public key
132    /// let private_key = ECPrivateKey::new();
133    /// let public_key = private_key.public_key();
134    ///
135    /// // Create a signing public key from it
136    /// let signing_key = SigningPublicKey::from_ecdsa(public_key);
137    /// # }
138    /// ```
139    #[cfg(feature = "secp256k1")]
140    pub fn from_ecdsa(key: ECPublicKey) -> Self { Self::ECDSA(key) }
141
142    /// Creates a new signing public key from an Ed25519 public key.
143    ///
144    /// # Arguments
145    ///
146    /// * `key` - An Ed25519 public key
147    ///
148    /// # Returns
149    ///
150    /// A new signing public key containing the Ed25519 key
151    ///
152    /// # Examples
153    ///
154    /// ```
155    /// # #[cfg(feature = "ed25519")]
156    /// # {
157    /// use bc_components::{Ed25519PrivateKey, SigningPublicKey};
158    ///
159    /// // Create an Ed25519 private key and get its public key
160    /// let private_key = Ed25519PrivateKey::new();
161    /// let public_key = private_key.public_key();
162    ///
163    /// // Create a signing public key from it
164    /// let signing_key = SigningPublicKey::from_ed25519(public_key);
165    /// # }
166    /// ```
167    #[cfg(feature = "ed25519")]
168    pub fn from_ed25519(key: Ed25519PublicKey) -> Self { Self::Ed25519(key) }
169
170    /// Creates a new signing public key from an SSH public key.
171    ///
172    /// # Arguments
173    ///
174    /// * `key` - An SSH public key
175    ///
176    /// # Returns
177    ///
178    /// A new signing public key containing the SSH key
179    #[cfg(feature = "ssh")]
180    pub fn from_ssh(key: SSHPublicKey) -> Self { Self::SSH(key) }
181
182    /// Returns the underlying Schnorr public key if this is a Schnorr key.
183    ///
184    /// # Returns
185    ///
186    /// Some reference to the Schnorr public key if this is a Schnorr key,
187    /// or None if it's a different key type.
188    ///
189    /// # Examples
190    ///
191    /// ```
192    /// # #[cfg(feature = "secp256k1")]
193    /// # {
194    /// use bc_components::{SignatureScheme, Signer};
195    ///
196    /// // Create a Schnorr key pair
197    /// let (private_key, public_key) = SignatureScheme::Schnorr.keypair();
198    ///
199    /// // We can access the Schnorr public key
200    /// assert!(public_key.to_schnorr().is_some());
201    ///
202    /// // Create an ECDSA key pair
203    /// let (_, ecdsa_public) = SignatureScheme::Ecdsa.keypair();
204    ///
205    /// // This will return None since it's not a Schnorr key
206    /// assert!(ecdsa_public.to_schnorr().is_none());
207    /// # }
208    /// ```
209    #[cfg(feature = "secp256k1")]
210    pub fn to_schnorr(&self) -> Option<&SchnorrPublicKey> {
211        match self {
212            Self::Schnorr(key) => Some(key),
213            _ => None,
214        }
215    }
216
217    /// Returns the underlying ECDSA public key if this is an ECDSA key.
218    ///
219    /// # Returns
220    ///
221    /// Some reference to the ECDSA public key if this is an ECDSA key,
222    /// or None if it's a different key type.
223    #[cfg(feature = "secp256k1")]
224    pub fn to_ecdsa(&self) -> Option<&ECPublicKey> {
225        match self {
226            Self::ECDSA(key) => Some(key),
227            _ => None,
228        }
229    }
230
231    /// Returns the underlying SSH public key if this is an SSH key.
232    ///
233    /// # Returns
234    ///
235    /// Some reference to the SSH public key if this is an SSH key,
236    /// or None if it's a different key type.
237    #[cfg(feature = "ssh")]
238    pub fn to_ssh(&self) -> Option<&SSHPublicKey> {
239        match self {
240            Self::SSH(key) => Some(key),
241            #[cfg(any(
242                feature = "secp256k1",
243                feature = "ed25519",
244                feature = "pqcrypto"
245            ))]
246            _ => None,
247        }
248    }
249}
250
251/// Implementation of the Verifier trait for SigningPublicKey
252impl Verifier for SigningPublicKey {
253    /// Verifies a signature against a message.
254    ///
255    /// The type of signature must match the type of this key, and the
256    /// signature must be valid for the message, or the verification
257    /// will fail.
258    ///
259    /// # Arguments
260    ///
261    /// * `signature` - The signature to verify
262    /// * `message` - The message that was allegedly signed
263    ///
264    /// # Returns
265    ///
266    /// `true` if the signature is valid for the message, `false` otherwise
267    ///
268    /// # Examples
269    ///
270    /// ```ignore
271    /// # // Requires secp256k1 feature (enabled by default)
272    /// use bc_components::{SignatureScheme, Signer, Verifier};
273    ///
274    /// // Create a key pair
275    /// let (private_key, public_key) = SignatureScheme::Schnorr.keypair();
276    ///
277    /// // Sign a message
278    /// let message = b"Hello, world!";
279    /// let signature = private_key.sign(&message).unwrap();
280    ///
281    /// // Verify the signature with the correct message (should succeed)
282    /// assert!(public_key.verify(&signature, &message));
283    ///
284    /// // Verify the signature with an incorrect message (should fail)
285    /// assert!(!public_key.verify(&signature, &b"Tampered message"));
286    /// ```
287    #[allow(unreachable_patterns)]
288    fn verify(
289        &self,
290        _signature: &Signature,
291        _message: &dyn AsRef<[u8]>,
292    ) -> bool {
293        match self {
294            #[cfg(feature = "secp256k1")]
295            SigningPublicKey::Schnorr(key) => match _signature {
296                Signature::Schnorr(sig) => key.schnorr_verify(sig, _message),
297                _ => false,
298            },
299            #[cfg(feature = "secp256k1")]
300            SigningPublicKey::ECDSA(key) => match _signature {
301                Signature::ECDSA(sig) => key.verify(sig, _message),
302                _ => false,
303            },
304            #[cfg(feature = "ed25519")]
305            SigningPublicKey::Ed25519(key) => match _signature {
306                Signature::Ed25519(sig) => key.verify(sig, _message),
307                #[cfg(any(
308                    feature = "secp256k1",
309                    feature = "ssh",
310                    feature = "pqcrypto"
311                ))]
312                _ => false,
313            },
314            #[cfg(feature = "ssh")]
315            SigningPublicKey::SSH(key) => match _signature {
316                Signature::SSH(sig) => {
317                    key.verify(sig.namespace(), _message.as_ref(), sig).is_ok()
318                }
319                _ => false,
320            },
321            #[cfg(feature = "pqcrypto")]
322            SigningPublicKey::MLDSA(key) => match _signature {
323                Signature::MLDSA(sig) => key
324                    .verify(sig, _message)
325                    .map_err(|_| false)
326                    .unwrap_or(false),
327                _ => false,
328            },
329            #[cfg(not(any(
330                feature = "secp256k1",
331                feature = "ed25519",
332                feature = "ssh",
333                feature = "pqcrypto"
334            )))]
335            _ => unreachable!(),
336        }
337    }
338}
339
340/// Implementation of AsRef for SigningPublicKey
341impl AsRef<SigningPublicKey> for SigningPublicKey {
342    /// Returns a reference to self.
343    fn as_ref(&self) -> &SigningPublicKey { self }
344}
345
346/// Implementation of the CBORTagged trait for SigningPublicKey
347impl CBORTagged for SigningPublicKey {
348    /// Returns the CBOR tags used for this type.
349    ///
350    /// For SigningPublicKey, the tag is 40022.
351    fn cbor_tags() -> Vec<Tag> {
352        tags_for_values(&[tags::TAG_SIGNING_PUBLIC_KEY])
353    }
354}
355
356/// Conversion from SigningPublicKey to CBOR
357impl From<SigningPublicKey> for CBOR {
358    /// Converts a SigningPublicKey to a tagged CBOR value.
359    fn from(value: SigningPublicKey) -> Self { value.tagged_cbor() }
360}
361
362/// Implementation of the CBORTaggedEncodable trait for SigningPublicKey
363impl CBORTaggedEncodable for SigningPublicKey {
364    /// Converts the SigningPublicKey to an untagged CBOR value.
365    ///
366    /// The CBOR encoding depends on the key type:
367    ///
368    /// - Schnorr: A byte string containing the 32-byte x-only public key
369    /// - ECDSA: An array containing the discriminator 1 and the 33-byte
370    ///   compressed public key
371    /// - Ed25519: An array containing the discriminator 2 and the 32-byte
372    ///   public key
373    /// - SSH: A tagged text string containing the OpenSSH-encoded public key
374    /// - ML-DSA: Delegates to the MLDSAPublicKey implementation
375    #[allow(unreachable_patterns)]
376    fn untagged_cbor(&self) -> CBOR {
377        match self {
378            #[cfg(feature = "secp256k1")]
379            SigningPublicKey::Schnorr(key) => CBOR::to_byte_string(key.data()),
380            #[cfg(feature = "secp256k1")]
381            SigningPublicKey::ECDSA(key) => {
382                vec![(1).into(), CBOR::to_byte_string(key.data())].into()
383            }
384            #[cfg(feature = "ed25519")]
385            SigningPublicKey::Ed25519(key) => {
386                vec![(2).into(), CBOR::to_byte_string(key.data())].into()
387            }
388            #[cfg(feature = "ssh")]
389            SigningPublicKey::SSH(key) => {
390                let string = key.to_openssh().unwrap();
391                CBOR::to_tagged_value(tags::TAG_SSH_TEXT_PUBLIC_KEY, string)
392            }
393            #[cfg(feature = "pqcrypto")]
394            SigningPublicKey::MLDSA(key) => key.clone().into(),
395            #[cfg(not(any(
396                feature = "secp256k1",
397                feature = "ed25519",
398                feature = "ssh",
399                feature = "pqcrypto"
400            )))]
401            _ => unreachable!(),
402        }
403    }
404}
405
406/// TryFrom implementation for converting CBOR to SigningPublicKey
407impl TryFrom<CBOR> for SigningPublicKey {
408    type Error = dcbor::Error;
409
410    /// Tries to convert a CBOR value to a SigningPublicKey.
411    ///
412    /// This is a convenience method that calls from_tagged_cbor.
413    fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
414        Self::from_tagged_cbor(cbor)
415    }
416}
417
418/// Implementation of the CBORTaggedDecodable trait for SigningPublicKey
419impl CBORTaggedDecodable for SigningPublicKey {
420    /// Creates a SigningPublicKey from an untagged CBOR value.
421    ///
422    /// # Arguments
423    ///
424    /// * `untagged_cbor` - The CBOR value to decode
425    ///
426    /// # Returns
427    ///
428    /// A Result containing the decoded SigningPublicKey or an error if decoding
429    /// fails.
430    ///
431    /// # Format
432    ///
433    /// The CBOR value must be one of:
434    /// - A byte string (interpreted as a Schnorr public key)
435    /// - An array of length 2, where the first element is a discriminator (1
436    ///   for ECDSA, 2 for Ed25519) and the second element is a byte string
437    ///   containing the key data
438    /// - A tagged value with a tag for ML-DSA or SSH keys
439    fn from_untagged_cbor(untagged_cbor: CBOR) -> dcbor::Result<Self> {
440        match untagged_cbor.clone().into_case() {
441            CBORCase::ByteString(data) => {
442                #[cfg(feature = "secp256k1")]
443                {
444                    Ok(Self::Schnorr(SchnorrPublicKey::from_data_ref(data)?))
445                }
446                #[cfg(not(feature = "secp256k1"))]
447                {
448                    let _ = data;
449                    Err("Schnorr public key not available without secp256k1 feature".into())
450                }
451            }
452            CBORCase::Array(mut elements) => {
453                if elements.len() == 2 {
454                    let mut drain = elements.drain(0..);
455                    let ele_0 = drain.next().unwrap().into_case();
456                    #[cfg_attr(
457                        not(any(feature = "secp256k1", feature = "ed25519")),
458                        allow(unused_variables)
459                    )]
460                    let ele_1 = drain.next().unwrap().into_case();
461                    #[cfg(feature = "secp256k1")]
462                    if let CBORCase::Unsigned(1) = ele_0 {
463                        if let CBORCase::ByteString(data) = ele_1 {
464                            return Ok(Self::ECDSA(
465                                ECPublicKey::from_data_ref(data)?,
466                            ));
467                        }
468                    }
469                    #[cfg(not(feature = "secp256k1"))]
470                    let _ = ele_0;
471                    #[cfg(feature = "ed25519")]
472                    if let CBORCase::Unsigned(2) = ele_0 {
473                        if let CBORCase::ByteString(data) = ele_1 {
474                            return Ok(Self::Ed25519(
475                                Ed25519PublicKey::from_data_ref(data)?,
476                            ));
477                        }
478                    }
479                }
480                Err("invalid signing public key".into())
481            }
482            #[cfg_attr(not(feature = "ssh"), allow(unused_variables))]
483            CBORCase::Tagged(tag, item) => match tag.value() {
484                #[cfg(feature = "ssh")]
485                tags::TAG_SSH_TEXT_PUBLIC_KEY => {
486                    let string = item.try_into_text()?;
487                    let key = SSHPublicKey::from_openssh(&string)
488                        .map_err(|_| "invalid SSH public key")?;
489                    Ok(Self::SSH(key))
490                }
491                #[cfg(feature = "pqcrypto")]
492                tags::TAG_MLDSA_PUBLIC_KEY => {
493                    let key = MLDSAPublicKey::from_tagged_cbor(untagged_cbor)?;
494                    Ok(Self::MLDSA(key))
495                }
496                _ => Err("invalid signing public key".into()),
497            },
498            _ => Err("invalid signing public key".into()),
499        }
500    }
501}
502
503#[cfg(feature = "ssh")]
504impl ReferenceProvider for SSHPublicKey {
505    fn reference(&self) -> Reference {
506        let string = self.to_openssh().unwrap();
507        let bytes = string.as_bytes();
508        let digest = Digest::from_image(bytes);
509        Reference::from_digest(digest)
510    }
511}
512
513impl ReferenceProvider for SigningPublicKey {
514    fn reference(&self) -> Reference {
515        Reference::from_digest(Digest::from_image(
516            self.tagged_cbor().to_cbor_data(),
517        ))
518    }
519}
520
521impl std::fmt::Display for SigningPublicKey {
522    #[allow(unreachable_patterns)]
523    fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
524        #[cfg(any(
525            feature = "secp256k1",
526            feature = "ed25519",
527            feature = "ssh",
528            feature = "pqcrypto"
529        ))]
530        {
531            let display_key = match self {
532                #[cfg(feature = "secp256k1")]
533                SigningPublicKey::Schnorr(key) => key.to_string(),
534                #[cfg(feature = "secp256k1")]
535                SigningPublicKey::ECDSA(key) => key.to_string(),
536                #[cfg(feature = "ed25519")]
537                SigningPublicKey::Ed25519(key) => key.to_string(),
538                #[cfg(feature = "ssh")]
539                SigningPublicKey::SSH(key) => {
540                    format!("SSHPublicKey({})", key.ref_hex_short())
541                }
542                #[cfg(feature = "pqcrypto")]
543                SigningPublicKey::MLDSA(key) => key.to_string(),
544                _ => unreachable!(),
545            };
546            write!(
547                _f,
548                "SigningPublicKey({}, {})",
549                self.ref_hex_short(),
550                display_key
551            )
552        }
553        #[cfg(not(any(
554            feature = "secp256k1",
555            feature = "ed25519",
556            feature = "ssh",
557            feature = "pqcrypto"
558        )))]
559        {
560            match *self {}
561        }
562    }
563}