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}