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}