bc_components/signing/
signing_public_key.rs

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