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}