bc_components/signing/
signing_public_key.rs

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