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}