Skip to main content

elliptic_curve/
public_key.rs

1//! Elliptic curve public keys.
2
3use crate::{
4    AffinePoint, CurveArithmetic, CurveGroup, Error, NonZeroScalar, ProjectivePoint, Result,
5    SecretKey, point::NonIdentity,
6};
7use core::fmt::Debug;
8use group::Group;
9
10#[cfg(feature = "pkcs8")]
11use pkcs8::spki::{AlgorithmIdentifier, AssociatedAlgorithmIdentifier, ObjectIdentifier};
12
13#[cfg(feature = "pem")]
14use {
15    alloc::string::{String, ToString},
16    core::str::FromStr,
17};
18
19#[cfg(feature = "sec1")]
20use {
21    crate::{
22        FieldBytesSize,
23        ctutils::{Choice, CtOption},
24        point::PointCompression,
25        sec1::{CompressedPoint, FromSec1Point, ModulusSize, Sec1Point, ToSec1Point},
26    },
27    core::cmp::Ordering,
28};
29
30#[cfg(feature = "serde")]
31use serdect::serde::{Deserialize, Serialize, de, ser};
32
33#[cfg(all(feature = "alloc", feature = "pkcs8"))]
34use pkcs8::EncodePublicKey;
35
36#[cfg(all(feature = "alloc", feature = "sec1"))]
37use alloc::boxed::Box;
38
39#[cfg(any(feature = "pem", feature = "serde"))]
40use pkcs8::DecodePublicKey;
41
42#[cfg(all(feature = "sec1", feature = "pkcs8"))]
43use {
44    crate::{ALGORITHM_OID, pkcs8::AssociatedOid},
45    pkcs8::der,
46};
47
48/// Elliptic curve public keys.
49///
50/// This is a wrapper type for [`AffinePoint`] which ensures an inner
51/// non-identity point and provides a common place to handle encoding/decoding.
52///
53/// # Parsing "SPKI" Keys
54///
55/// X.509 `SubjectPublicKeyInfo` (SPKI) is a commonly used format for encoding
56/// public keys, notably public keys corresponding to PKCS#8 private keys.
57/// (especially ones generated by OpenSSL).
58///
59/// Keys in SPKI format are either binary (ASN.1 BER/DER), or PEM encoded
60/// (ASCII) and begin with the following:
61///
62/// ```text
63/// -----BEGIN PUBLIC KEY-----
64/// ```
65///
66/// To decode an elliptic curve public key from SPKI, enable the `pkcs8`
67/// feature of this crate (or the `pkcs8` feature of a specific RustCrypto
68/// elliptic curve crate) and use the
69/// [`elliptic_curve::pkcs8::DecodePublicKey`][`pkcs8::DecodePublicKey`]
70/// trait to parse it.
71///
72/// When the `pem` feature of this crate (or a specific RustCrypto elliptic
73/// curve crate) is enabled, a [`FromStr`] impl is also available.
74///
75/// # `serde` support
76///
77/// When the optional `serde` feature of this create is enabled, [`Serialize`]
78/// and [`Deserialize`] impls are provided for this type.
79///
80/// The serialization is binary-oriented and supports ASN.1 DER
81/// Subject Public Key Info (SPKI) as the encoding format.
82#[derive(Clone, Debug, Eq, PartialEq)]
83pub struct PublicKey<C>
84where
85    C: CurveArithmetic,
86{
87    point: AffinePoint<C>,
88}
89
90impl<C> PublicKey<C>
91where
92    C: CurveArithmetic,
93{
94    /// Convert an [`AffinePoint`] into a [`PublicKey`].
95    ///
96    /// # Errors
97    /// - if `point` is the additive identity.
98    pub fn from_affine(point: AffinePoint<C>) -> Result<Self> {
99        if ProjectivePoint::<C>::from(point).is_identity().into() {
100            Err(Error)
101        } else {
102            Ok(Self { point })
103        }
104    }
105
106    /// Compute a [`PublicKey`] from a secret [`NonZeroScalar`] value
107    /// (i.e. a secret key represented as a raw scalar value)
108    pub fn from_secret_scalar(scalar: &NonZeroScalar<C>) -> Self {
109        // `NonZeroScalar` ensures the resulting point is not the identity
110        Self {
111            point: ProjectivePoint::<C>::mul_by_generator(scalar).to_affine(),
112        }
113    }
114
115    /// Decode [`PublicKey`] (compressed or uncompressed) from the
116    /// `Elliptic-Curve-Point-to-Octet-String` encoding described in
117    /// SEC 1: Elliptic Curve Cryptography (Version 2.0) section
118    /// 2.3.3 (page 10).
119    ///
120    /// <http://www.secg.org/sec1-v2.pdf>
121    ///
122    /// # Errors
123    /// - if `bytes` is not a valid SEC1 encoding of an elliptic curve point.
124    /// - if the decoded curve point is the additive identity.
125    #[cfg(feature = "sec1")]
126    pub fn from_sec1_bytes(bytes: &[u8]) -> Result<Self>
127    where
128        FieldBytesSize<C>: ModulusSize,
129        AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
130    {
131        let point = Sec1Point::<C>::from_bytes(bytes).map_err(|_| Error)?;
132        Self::from_sec1_point(&point).into_option().ok_or(Error)
133    }
134
135    /// Convert this [`PublicKey`] into the
136    /// `Elliptic-Curve-Point-to-Octet-String` encoding described in
137    /// SEC 1: Elliptic Curve Cryptography (Version 2.0) section 2.3.3
138    /// (page 10).
139    ///
140    /// <http://www.secg.org/sec1-v2.pdf>
141    #[cfg(all(feature = "alloc", feature = "sec1"))]
142    pub fn to_sec1_bytes(&self) -> Box<[u8]>
143    where
144        C: PointCompression,
145        AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
146        FieldBytesSize<C>: ModulusSize,
147    {
148        Sec1Point::<C>::from(self).to_bytes()
149    }
150
151    /// Borrow the inner [`AffinePoint`] from this [`PublicKey`].
152    ///
153    /// In ECC, public keys are elliptic curve points.
154    pub fn as_affine(&self) -> &AffinePoint<C> {
155        &self.point
156    }
157
158    /// Convert this [`PublicKey`] to a [`ProjectivePoint`] for the given curve
159    pub fn to_projective(&self) -> ProjectivePoint<C> {
160        self.point.into()
161    }
162
163    /// Convert this [`PublicKey`] to a [`NonIdentity`] of the inner [`AffinePoint`]
164    pub fn to_nonidentity(&self) -> NonIdentity<AffinePoint<C>> {
165        NonIdentity::new_unchecked(self.point)
166    }
167}
168
169impl<C> AsRef<AffinePoint<C>> for PublicKey<C>
170where
171    C: CurveArithmetic,
172{
173    fn as_ref(&self) -> &AffinePoint<C> {
174        self.as_affine()
175    }
176}
177
178impl<C> Copy for PublicKey<C> where C: CurveArithmetic {}
179
180#[cfg(feature = "sec1")]
181impl<C> FromSec1Point<C> for PublicKey<C>
182where
183    C: CurveArithmetic,
184    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
185    FieldBytesSize<C>: ModulusSize,
186{
187    /// Initialize [`PublicKey`] from an [`Sec1Point`]
188    fn from_sec1_point(encoded_point: &Sec1Point<C>) -> CtOption<Self> {
189        AffinePoint::<C>::from_sec1_point(encoded_point).and_then(|point| {
190            // Defeating the point of `subtle`, but the use case is specifically a public key
191            let is_identity = Choice::from_u8_lsb(u8::from(encoded_point.is_identity()));
192            CtOption::new(PublicKey { point }, !is_identity)
193        })
194    }
195}
196
197#[cfg(feature = "sec1")]
198impl<C> ToSec1Point<C> for PublicKey<C>
199where
200    C: CurveArithmetic,
201    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
202    FieldBytesSize<C>: ModulusSize,
203{
204    /// Serialize this [`PublicKey`] as a SEC1 [`Sec1Point`], optionally applying
205    /// point compression
206    fn to_sec1_point(&self, compress: bool) -> Sec1Point<C> {
207        self.point.to_sec1_point(compress)
208    }
209}
210
211#[cfg(feature = "sec1")]
212impl<C> From<PublicKey<C>> for CompressedPoint<C>
213where
214    C: CurveArithmetic + PointCompression,
215    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
216    FieldBytesSize<C>: ModulusSize,
217{
218    fn from(public_key: PublicKey<C>) -> CompressedPoint<C> {
219        CompressedPoint::<C>::from(&public_key)
220    }
221}
222
223#[cfg(feature = "sec1")]
224impl<C> From<&PublicKey<C>> for CompressedPoint<C>
225where
226    C: CurveArithmetic + PointCompression,
227    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
228    FieldBytesSize<C>: ModulusSize,
229{
230    fn from(public_key: &PublicKey<C>) -> CompressedPoint<C> {
231        public_key
232            .to_sec1_point(true)
233            .as_bytes()
234            .try_into()
235            .expect("wrong compressed point size")
236    }
237}
238
239#[cfg(feature = "sec1")]
240impl<C> From<PublicKey<C>> for Sec1Point<C>
241where
242    C: CurveArithmetic + PointCompression,
243    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
244    FieldBytesSize<C>: ModulusSize,
245{
246    fn from(public_key: PublicKey<C>) -> Sec1Point<C> {
247        Sec1Point::<C>::from(&public_key)
248    }
249}
250
251#[cfg(feature = "sec1")]
252impl<C> From<&PublicKey<C>> for Sec1Point<C>
253where
254    C: CurveArithmetic + PointCompression,
255    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
256    FieldBytesSize<C>: ModulusSize,
257{
258    fn from(public_key: &PublicKey<C>) -> Sec1Point<C> {
259        public_key.to_sec1_point(C::COMPRESS_POINTS)
260    }
261}
262
263impl<C, P> From<NonIdentity<P>> for PublicKey<C>
264where
265    C: CurveArithmetic,
266    P: Copy + Into<AffinePoint<C>>,
267{
268    fn from(value: NonIdentity<P>) -> Self {
269        Self::from(&value)
270    }
271}
272
273impl<C, P> From<&NonIdentity<P>> for PublicKey<C>
274where
275    C: CurveArithmetic,
276    P: Copy + Into<AffinePoint<C>>,
277{
278    fn from(value: &NonIdentity<P>) -> Self {
279        Self {
280            point: value.to_point().into(),
281        }
282    }
283}
284
285impl<C> From<PublicKey<C>> for NonIdentity<AffinePoint<C>>
286where
287    C: CurveArithmetic,
288{
289    fn from(value: PublicKey<C>) -> Self {
290        Self::from(&value)
291    }
292}
293
294impl<C> From<&PublicKey<C>> for NonIdentity<AffinePoint<C>>
295where
296    C: CurveArithmetic,
297{
298    fn from(value: &PublicKey<C>) -> Self {
299        PublicKey::to_nonidentity(value)
300    }
301}
302
303impl<C> From<SecretKey<C>> for PublicKey<C>
304where
305    C: CurveArithmetic,
306{
307    fn from(secret_key: SecretKey<C>) -> Self {
308        secret_key.public_key()
309    }
310}
311
312impl<C> From<&SecretKey<C>> for PublicKey<C>
313where
314    C: CurveArithmetic,
315{
316    fn from(secret_key: &SecretKey<C>) -> PublicKey<C> {
317        secret_key.public_key()
318    }
319}
320
321#[cfg(feature = "sec1")]
322impl<C> PartialOrd for PublicKey<C>
323where
324    C: CurveArithmetic,
325    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
326    FieldBytesSize<C>: ModulusSize,
327{
328    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
329        Some(self.cmp(other))
330    }
331}
332
333#[cfg(feature = "sec1")]
334impl<C> Ord for PublicKey<C>
335where
336    C: CurveArithmetic,
337    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
338    FieldBytesSize<C>: ModulusSize,
339{
340    fn cmp(&self, other: &Self) -> Ordering {
341        // TODO(tarcieri): more efficient implementation?
342        // This is implemented this way to reduce bounds for `AffinePoint<C>`
343        self.to_sec1_point(false).cmp(&other.to_sec1_point(false))
344    }
345}
346
347#[cfg(feature = "sec1")]
348impl<C> TryFrom<CompressedPoint<C>> for PublicKey<C>
349where
350    C: CurveArithmetic,
351    FieldBytesSize<C>: ModulusSize,
352    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
353{
354    type Error = Error;
355
356    fn try_from(point: CompressedPoint<C>) -> Result<Self> {
357        Self::from_sec1_bytes(&point)
358    }
359}
360
361#[cfg(feature = "sec1")]
362impl<C> TryFrom<&CompressedPoint<C>> for PublicKey<C>
363where
364    C: CurveArithmetic,
365    FieldBytesSize<C>: ModulusSize,
366    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
367{
368    type Error = Error;
369
370    fn try_from(point: &CompressedPoint<C>) -> Result<Self> {
371        Self::from_sec1_bytes(point)
372    }
373}
374
375#[cfg(feature = "sec1")]
376impl<C> TryFrom<Sec1Point<C>> for PublicKey<C>
377where
378    C: CurveArithmetic,
379    FieldBytesSize<C>: ModulusSize,
380    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
381{
382    type Error = Error;
383
384    fn try_from(point: Sec1Point<C>) -> Result<Self> {
385        Self::from_sec1_bytes(point.as_bytes())
386    }
387}
388
389#[cfg(feature = "sec1")]
390impl<C> TryFrom<&Sec1Point<C>> for PublicKey<C>
391where
392    C: CurveArithmetic,
393    FieldBytesSize<C>: ModulusSize,
394    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
395{
396    type Error = Error;
397
398    fn try_from(point: &Sec1Point<C>) -> Result<Self> {
399        Self::from_sec1_bytes(point.as_bytes())
400    }
401}
402
403#[cfg(feature = "pkcs8")]
404impl<C> AssociatedAlgorithmIdentifier for PublicKey<C>
405where
406    C: AssociatedOid + CurveArithmetic,
407{
408    type Params = ObjectIdentifier;
409
410    const ALGORITHM_IDENTIFIER: AlgorithmIdentifier<ObjectIdentifier> = AlgorithmIdentifier {
411        oid: ALGORITHM_OID,
412        parameters: Some(C::OID),
413    };
414}
415
416#[cfg(feature = "pkcs8")]
417impl<C> TryFrom<pkcs8::SubjectPublicKeyInfoRef<'_>> for PublicKey<C>
418where
419    C: AssociatedOid + CurveArithmetic,
420    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
421    FieldBytesSize<C>: ModulusSize,
422{
423    type Error = pkcs8::spki::Error;
424
425    fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> pkcs8::spki::Result<Self> {
426        Self::try_from(&spki)
427    }
428}
429
430#[cfg(feature = "pkcs8")]
431impl<C> TryFrom<&pkcs8::SubjectPublicKeyInfoRef<'_>> for PublicKey<C>
432where
433    C: AssociatedOid + CurveArithmetic,
434    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
435    FieldBytesSize<C>: ModulusSize,
436{
437    type Error = pkcs8::spki::Error;
438
439    fn try_from(spki: &pkcs8::SubjectPublicKeyInfoRef<'_>) -> pkcs8::spki::Result<Self> {
440        spki.algorithm.assert_oids(ALGORITHM_OID, C::OID)?;
441
442        let public_key_bytes = spki
443            .subject_public_key
444            .as_bytes()
445            .ok_or_else(|| der::Tag::BitString.value_error().to_error())?;
446
447        Self::from_sec1_bytes(public_key_bytes)
448            .map_err(|_| der::Tag::BitString.value_error().to_error().into())
449    }
450}
451
452#[cfg(all(feature = "alloc", feature = "pkcs8"))]
453impl<C> EncodePublicKey for PublicKey<C>
454where
455    C: AssociatedOid + CurveArithmetic,
456    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
457    FieldBytesSize<C>: ModulusSize,
458{
459    fn to_public_key_der(&self) -> pkcs8::spki::Result<der::Document> {
460        let public_key_bytes = self.to_sec1_point(false);
461        let subject_public_key = der::asn1::BitStringRef::new(0, public_key_bytes.as_bytes())?;
462
463        pkcs8::SubjectPublicKeyInfo {
464            algorithm: Self::ALGORITHM_IDENTIFIER,
465            subject_public_key,
466        }
467        .try_into()
468    }
469}
470
471#[cfg(feature = "pem")]
472impl<C> FromStr for PublicKey<C>
473where
474    C: AssociatedOid + CurveArithmetic,
475    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
476    FieldBytesSize<C>: ModulusSize,
477{
478    type Err = Error;
479
480    fn from_str(s: &str) -> Result<Self> {
481        Self::from_public_key_pem(s).map_err(|_| Error)
482    }
483}
484
485#[cfg(feature = "pem")]
486#[allow(clippy::to_string_trait_impl)]
487impl<C> ToString for PublicKey<C>
488where
489    C: AssociatedOid + CurveArithmetic,
490    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
491    FieldBytesSize<C>: ModulusSize,
492{
493    fn to_string(&self) -> String {
494        self.to_public_key_pem(Default::default())
495            .expect("PEM encoding error")
496    }
497}
498
499#[cfg(feature = "serde")]
500impl<C> Serialize for PublicKey<C>
501where
502    C: AssociatedOid + CurveArithmetic,
503    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
504    FieldBytesSize<C>: ModulusSize,
505{
506    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
507    where
508        S: ser::Serializer,
509    {
510        let der = self.to_public_key_der().map_err(ser::Error::custom)?;
511        serdect::slice::serialize_hex_upper_or_bin(&der, serializer)
512    }
513}
514
515#[cfg(feature = "serde")]
516impl<'de, C> Deserialize<'de> for PublicKey<C>
517where
518    C: AssociatedOid + CurveArithmetic,
519    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
520    FieldBytesSize<C>: ModulusSize,
521{
522    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
523    where
524        D: de::Deserializer<'de>,
525    {
526        let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
527        Self::from_public_key_der(&der_bytes).map_err(de::Error::custom)
528    }
529}
530
531#[cfg(all(feature = "dev", test))]
532mod tests {
533    use crate::{dev::MockCurve, sec1::FromSec1Point};
534
535    type Sec1Point = crate::sec1::Sec1Point<MockCurve>;
536    type PublicKey = super::PublicKey<MockCurve>;
537
538    #[test]
539    fn from_sec1_point_rejects_identity() {
540        let identity = Sec1Point::identity();
541        assert!(bool::from(PublicKey::from_sec1_point(&identity).is_none()));
542    }
543}