bc_components/signing/
signing_public_key.rs

1use crate::{
2    tags, ECKeyBase, ECPublicKey, Ed25519PublicKey, MLDSAPublicKey, SchnorrPublicKey, Signature,
3    Verifier,
4};
5use bc_ur::prelude::*;
6use ssh_key::public::PublicKey as SSHPublicKey;
7
8/// A public key used for verifying digital signatures.
9///
10/// `SigningPublicKey` is an enum representing different types of signing public keys,
11/// including elliptic curve schemes (ECDSA, Schnorr), Edwards curve schemes (Ed25519),
12/// post-quantum schemes (ML-DSA), and SSH keys.
13///
14/// This type implements the `Verifier` trait, allowing it to verify signatures of
15/// the appropriate type.
16///
17/// # Examples
18///
19/// Creating and using a signing public key pair:
20///
21/// ```
22/// use bc_components::{SignatureScheme, Signer, Verifier};
23///
24/// // Create a key pair
25/// let (private_key, public_key) = SignatureScheme::Schnorr.keypair();
26///
27/// // Sign a message
28/// let message = b"Hello, world!";
29/// let signature = private_key.sign(&message).unwrap();
30///
31/// // Verify the signature
32/// assert!(public_key.verify(&signature, &message));
33/// ```
34///
35/// # CBOR Serialization
36///
37/// `SigningPublicKey` can be serialized to and from CBOR with appropriate tags:
38///
39/// ```
40/// use bc_components::{SignatureScheme, SigningPublicKey};
41/// use dcbor::prelude::*;
42///
43/// // Create a key pair and get the public key
44/// let (_, public_key) = SignatureScheme::Schnorr.keypair();
45///
46/// // Convert to CBOR
47/// let cbor: CBOR = public_key.clone().into();
48/// let data = cbor.to_cbor_data();
49///
50/// // Convert back from CBOR
51/// let recovered = SigningPublicKey::from_tagged_cbor_data(&data).unwrap();
52///
53/// // The keys should be equal
54/// assert_eq!(public_key, recovered);
55/// ```
56#[derive(Clone, Debug, PartialEq, Eq, Hash)]
57pub enum SigningPublicKey {
58    /// A Schnorr public key (BIP-340, x-only)
59    Schnorr(SchnorrPublicKey),
60
61    /// An ECDSA public key (compressed, 33 bytes)
62    ECDSA(ECPublicKey),
63
64    /// An Ed25519 public key
65    Ed25519(Ed25519PublicKey),
66
67    /// An SSH public key
68    SSH(SSHPublicKey),
69
70    /// A post-quantum ML-DSA public key
71    MLDSA(MLDSAPublicKey),
72}
73
74impl SigningPublicKey {
75    /// Creates a new signing public key from a Schnorr public key.
76    ///
77    /// # Arguments
78    ///
79    /// * `key` - A BIP-340 Schnorr public key
80    ///
81    /// # Returns
82    ///
83    /// A new signing public key containing the Schnorr key
84    ///
85    /// # Examples
86    ///
87    /// ```
88    /// use bc_components::{SchnorrPublicKey, SigningPublicKey};
89    ///
90    /// // Create a Schnorr public key
91    /// let schnorr_key = SchnorrPublicKey::from_data([0u8; 32]);
92    ///
93    /// // Create a signing public key from it
94    /// let signing_key = SigningPublicKey::from_schnorr(schnorr_key);
95    /// ```
96    pub fn from_schnorr(key: SchnorrPublicKey) -> Self {
97        Self::Schnorr(key)
98    }
99
100    /// Creates a new signing public key from an ECDSA public key.
101    ///
102    /// # Arguments
103    ///
104    /// * `key` - A compressed ECDSA public key
105    ///
106    /// # Returns
107    ///
108    /// A new signing public key containing the ECDSA key
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// use bc_components::{ECPrivateKey, SigningPublicKey, ECKey};
114    ///
115    /// // Create an EC private key and derive its public key
116    /// let private_key = ECPrivateKey::new();
117    /// let public_key = private_key.public_key();
118    ///
119    /// // Create a signing public key from it
120    /// let signing_key = SigningPublicKey::from_ecdsa(public_key);
121    /// ```
122    pub fn from_ecdsa(key: ECPublicKey) -> Self {
123        Self::ECDSA(key)
124    }
125
126    /// Creates a new signing public key from an Ed25519 public key.
127    ///
128    /// # Arguments
129    ///
130    /// * `key` - An Ed25519 public key
131    ///
132    /// # Returns
133    ///
134    /// A new signing public key containing the Ed25519 key
135    ///
136    /// # Examples
137    ///
138    /// ```
139    /// use bc_components::{Ed25519PrivateKey, SigningPublicKey};
140    ///
141    /// // Create an Ed25519 private key and get its public key
142    /// let private_key = Ed25519PrivateKey::new();
143    /// let public_key = private_key.public_key();
144    ///
145    /// // Create a signing public key from it
146    /// let signing_key = SigningPublicKey::from_ed25519(public_key);
147    /// ```
148    pub fn from_ed25519(key: Ed25519PublicKey) -> Self {
149        Self::Ed25519(key)
150    }
151
152    /// Creates a new signing public key from an SSH public key.
153    ///
154    /// # Arguments
155    ///
156    /// * `key` - An SSH public key
157    ///
158    /// # Returns
159    ///
160    /// A new signing public key containing the SSH key
161    pub fn from_ssh(key: SSHPublicKey) -> Self {
162        Self::SSH(key)
163    }
164
165    /// Returns the underlying Schnorr public key if this is a Schnorr key.
166    ///
167    /// # Returns
168    ///
169    /// Some reference to the Schnorr public key if this is a Schnorr key,
170    /// or None if it's a different key type.
171    ///
172    /// # Examples
173    ///
174    /// ```
175    /// use bc_components::{SignatureScheme, Signer};
176    ///
177    /// // Create a Schnorr key pair
178    /// let (private_key, public_key) = SignatureScheme::Schnorr.keypair();
179    ///
180    /// // We can access the Schnorr public key
181    /// assert!(public_key.to_schnorr().is_some());
182    ///
183    /// // Create an ECDSA key pair
184    /// let (_, ecdsa_public) = SignatureScheme::Ecdsa.keypair();
185    ///
186    /// // This will return None since it's not a Schnorr key
187    /// assert!(ecdsa_public.to_schnorr().is_none());
188    /// ```
189    pub fn to_schnorr(&self) -> Option<&SchnorrPublicKey> {
190        match self {
191            Self::Schnorr(key) => Some(key),
192            _ => None,
193        }
194    }
195
196    /// Returns the underlying ECDSA public key if this is an ECDSA key.
197    ///
198    /// # Returns
199    ///
200    /// Some reference to the ECDSA public key if this is an ECDSA key,
201    /// or None if it's a different key type.
202    pub fn to_ecdsa(&self) -> Option<&ECPublicKey> {
203        match self {
204            Self::ECDSA(key) => Some(key),
205            _ => None,
206        }
207    }
208
209    /// Returns the underlying SSH public key if this is an SSH key.
210    ///
211    /// # Returns
212    ///
213    /// Some reference to the SSH public key if this is an SSH key,
214    /// or None if it's a different key type.
215    pub fn to_ssh(&self) -> Option<&SSHPublicKey> {
216        match self {
217            Self::SSH(key) => Some(key),
218            _ => None,
219        }
220    }
221}
222
223/// Implementation of the Verifier trait for SigningPublicKey
224impl Verifier for SigningPublicKey {
225    /// Verifies a signature against a message.
226    ///
227    /// The type of signature must match the type of this key, and the
228    /// signature must be valid for the message, or the verification
229    /// will fail.
230    ///
231    /// # Arguments
232    ///
233    /// * `signature` - The signature to verify
234    /// * `message` - The message that was allegedly signed
235    ///
236    /// # Returns
237    ///
238    /// `true` if the signature is valid for the message, `false` otherwise
239    ///
240    /// # Examples
241    ///
242    /// ```
243    /// use bc_components::{SignatureScheme, Signer, Verifier};
244    ///
245    /// // Create a key pair
246    /// let (private_key, public_key) = SignatureScheme::Schnorr.keypair();
247    ///
248    /// // Sign a message
249    /// let message = b"Hello, world!";
250    /// let signature = private_key.sign(&message).unwrap();
251    ///
252    /// // Verify the signature with the correct message (should succeed)
253    /// assert!(public_key.verify(&signature, &message));
254    ///
255    /// // Verify the signature with an incorrect message (should fail)
256    /// assert!(!public_key.verify(&signature, &b"Tampered message"));
257    /// ```
258    fn verify(&self, signature: &Signature, message: &dyn AsRef<[u8]>) -> bool {
259        match self {
260            SigningPublicKey::Schnorr(key) => match signature {
261                Signature::Schnorr(sig) => key.schnorr_verify(sig, message),
262                _ => false,
263            },
264            SigningPublicKey::ECDSA(key) => match signature {
265                Signature::ECDSA(sig) => key.verify(sig, message),
266                _ => false,
267            },
268            SigningPublicKey::Ed25519(key) => match signature {
269                Signature::Ed25519(sig) => key.verify(sig, message),
270                _ => false,
271            },
272            SigningPublicKey::SSH(key) => match signature {
273                Signature::SSH(sig) => key.verify(sig.namespace(), message.as_ref(), sig).is_ok(),
274                _ => false,
275            },
276            SigningPublicKey::MLDSA(key) => match signature {
277                Signature::MLDSA(sig) => {
278                    key.verify(sig, message).map_err(|_| false).unwrap_or(false)
279                }
280                _ => false,
281            },
282        }
283    }
284}
285
286/// Implementation of AsRef for SigningPublicKey
287impl AsRef<SigningPublicKey> for SigningPublicKey {
288    /// Returns a reference to self.
289    fn as_ref(&self) -> &SigningPublicKey {
290        self
291    }
292}
293
294/// Implementation of the CBORTagged trait for SigningPublicKey
295impl CBORTagged for SigningPublicKey {
296    /// Returns the CBOR tags used for this type.
297    ///
298    /// For SigningPublicKey, the tag is 40022.
299    fn cbor_tags() -> Vec<Tag> {
300        tags_for_values(&[tags::TAG_SIGNING_PUBLIC_KEY])
301    }
302}
303
304/// Conversion from SigningPublicKey to CBOR
305impl From<SigningPublicKey> for CBOR {
306    /// Converts a SigningPublicKey to a tagged CBOR value.
307    fn from(value: SigningPublicKey) -> Self {
308        value.tagged_cbor()
309    }
310}
311
312/// Implementation of the CBORTaggedEncodable trait for SigningPublicKey
313impl CBORTaggedEncodable for SigningPublicKey {
314    /// Converts the SigningPublicKey to an untagged CBOR value.
315    ///
316    /// The CBOR encoding depends on the key type:
317    ///
318    /// - Schnorr: A byte string containing the 32-byte x-only public key
319    /// - ECDSA: An array containing the discriminator 1 and the 33-byte compressed public key
320    /// - Ed25519: An array containing the discriminator 2 and the 32-byte public key
321    /// - SSH: A tagged text string containing the OpenSSH-encoded public key
322    /// - ML-DSA: Delegates to the MLDSAPublicKey implementation
323    fn untagged_cbor(&self) -> CBOR {
324        match self {
325            SigningPublicKey::Schnorr(key) => CBOR::to_byte_string(key.data()),
326            SigningPublicKey::ECDSA(key) => {
327                vec![(1).into(), CBOR::to_byte_string(key.data())].into()
328            }
329            SigningPublicKey::Ed25519(key) => {
330                vec![(2).into(), CBOR::to_byte_string(key.data())].into()
331            }
332            SigningPublicKey::SSH(key) => {
333                let string = key.to_openssh().unwrap();
334                CBOR::to_tagged_value(tags::TAG_SSH_TEXT_PUBLIC_KEY, string)
335            }
336            SigningPublicKey::MLDSA(key) => key.clone().into(),
337        }
338    }
339}
340
341/// TryFrom implementation for converting CBOR to SigningPublicKey
342impl TryFrom<CBOR> for SigningPublicKey {
343    type Error = dcbor::Error;
344
345    /// Tries to convert a CBOR value to a SigningPublicKey.
346    ///
347    /// This is a convenience method that calls from_tagged_cbor.
348    fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
349        Self::from_tagged_cbor(cbor)
350    }
351}
352
353/// Implementation of the CBORTaggedDecodable trait for SigningPublicKey
354impl CBORTaggedDecodable for SigningPublicKey {
355    /// Creates a SigningPublicKey from an untagged CBOR value.
356    ///
357    /// # Arguments
358    ///
359    /// * `untagged_cbor` - The CBOR value to decode
360    ///
361    /// # Returns
362    ///
363    /// A Result containing the decoded SigningPublicKey or an error if decoding fails.
364    ///
365    /// # Format
366    ///
367    /// The CBOR value must be one of:
368    /// - A byte string (interpreted as a Schnorr public key)
369    /// - An array of length 2, where the first element is a discriminator (1 for ECDSA, 2 for Ed25519)
370    ///   and the second element is a byte string containing the key data
371    /// - A tagged value with a tag for ML-DSA or SSH keys
372    fn from_untagged_cbor(untagged_cbor: CBOR) -> dcbor::Result<Self> {
373        match untagged_cbor.clone().into_case() {
374            CBORCase::ByteString(data) => Ok(Self::Schnorr(SchnorrPublicKey::from_data_ref(data)?)),
375            CBORCase::Array(mut elements) => {
376                if elements.len() == 2 {
377                    let mut drain = elements.drain(0..);
378                    let ele_0 = drain.next().unwrap().into_case();
379                    let ele_1 = drain.next().unwrap().into_case();
380                    if let CBORCase::Unsigned(1) = ele_0 {
381                        if let CBORCase::ByteString(data) = ele_1 {
382                            return Ok(Self::ECDSA(ECPublicKey::from_data_ref(data)?));
383                        }
384                    } else if let CBORCase::Unsigned(2) = ele_0 {
385                        if let CBORCase::ByteString(data) = ele_1 {
386                            return Ok(Self::Ed25519(Ed25519PublicKey::from_data_ref(data)?));
387                        }
388                    }
389                }
390                return Err("invalid signing public key".into());
391            }
392            CBORCase::Tagged(tag, item) => match tag.value() {
393                tags::TAG_SSH_TEXT_PUBLIC_KEY => {
394                    let string = item.try_into_text()?;
395                    let key = SSHPublicKey::from_openssh(&string)
396                        .map_err(|_| "invalid SSH public key")?;
397                    Ok(Self::SSH(key))
398                }
399                tags::TAG_MLDSA_PUBLIC_KEY => {
400                    let key = MLDSAPublicKey::from_tagged_cbor(untagged_cbor)?;
401                    Ok(Self::MLDSA(key))
402                }
403                _ => return Err("invalid signing public key".into()),
404            },
405            _ => return Err("invalid signing public key".into()),
406        }
407    }
408}