Skip to main content

lexe_crypto/
ed25519.rs

1//! Ed25519 key pairs, signatures, and public keys.
2//!
3//!
4//! ## Why not use [`ring`] directly
5//!
6//! * [`ring`]'s APIs are often too limited or too inconvenient.
7//!
8//!
9//! ## Why ed25519
10//!
11//! * More compact pubkeys (32 B) and signatures (64 B) than RSA (aside: please
12//!   don't use RSA ever).
13//!
14//! * Faster sign (~3x) + verify (~2x) than ECDSA/secp256k1.
15//!
16//! * Deterministic signatures. No key leakage on accidental secret nonce leak
17//!   or reuse.
18//!
19//! * Better side-channel attack resistance.
20//!
21//!
22//! ## Why not ed25519
23//!
24//! * Deterministic signatures more vulnerable to fault injection attacks.
25//!
26//!   We could consider using hedged signatures `Sign(random-nonce || message)`.
27//!   Fortunately hedged signatures aren't fundamentally unsafe when nonces are
28//!   reused.
29//!
30//! * Small-order torsion subgroup -> signature malleability fun
31
32// TODO(phlip9): patch ring/rcgen so `Ed25519KeyPair`/`rcgen::KeyPair` derive
33//               `Zeroize`.
34
35// TODO(phlip9): Submit PR to ring for `Ed25519Ctx` support so we don't have to
36//               pre-hash.
37
38use std::{
39    fmt,
40    io::{Cursor, Write},
41    str::FromStr,
42};
43
44use lexe_byte_array::ByteArray;
45use lexe_hex::hex::{self, FromHex};
46use lexe_serde::impl_serde_hexstr_or_bytes;
47use lexe_sha256::sha256;
48use lexe_std::const_utils;
49use ref_cast::RefCast;
50use ring::signature::KeyPair as _;
51use serde_core::{de::Deserialize, ser::Serialize};
52
53#[cfg(doc)]
54use crate::ed25519;
55use crate::rng::{Crng, RngExt};
56
57pub const SECRET_KEY_LEN: usize = 32;
58pub const PUBLIC_KEY_LEN: usize = 32;
59pub const SIGNATURE_LEN: usize = 64;
60
61/// 96 B. The added overhead for a signed struct, on top of the serialized
62/// struct size.
63pub const SIGNED_STRUCT_OVERHEAD: usize = PUBLIC_KEY_LEN + SIGNATURE_LEN;
64
65/// An ed25519 secret key and public key.
66///
67/// Applications should always sign with a *key pair* rather than passing in
68/// the secret key and public key separately, to avoid attacks like
69/// [attacker controlled pubkey signing](https://github.com/MystenLabs/ed25519-unsafe-libs).
70pub struct KeyPair {
71    /// The ring key pair for actually signing things.
72    key_pair: ring::signature::Ed25519KeyPair,
73
74    /// Unfortunately, [`ring`] doesn't expose the `seed` after construction,
75    /// so we need to hold on to the seed if we ever need to serialize the key
76    /// pair later.
77    seed: [u8; 32],
78}
79
80/// An ed25519 public key.
81#[derive(Copy, Clone, Eq, Hash, PartialEq, RefCast)]
82#[repr(transparent)]
83pub struct PublicKey([u8; 32]);
84
85impl_serde_hexstr_or_bytes!(PublicKey);
86
87/// An ed25519 signature.
88#[derive(Copy, Clone, Eq, PartialEq, RefCast)]
89#[repr(transparent)]
90pub struct Signature([u8; 64]);
91
92/// `Signed<T>` is a "proof" that the signature `sig` on a [`Signable`] struct
93/// `T` was actually signed by `signer`.
94#[derive(Debug, Eq, PartialEq)]
95#[must_use]
96pub struct Signed<T: Signable> {
97    signer: PublicKey,
98    sig: Signature,
99    inner: T,
100}
101
102#[derive(Debug)]
103pub enum Error {
104    InvalidPkLength,
105    UnexpectedAlgorithm,
106    KeyDeserializeError,
107    PublicKeyMismatch,
108    InvalidSignature,
109    BcsDeserialize,
110    SignedTooShort,
111    UnexpectedSigner,
112}
113
114#[derive(Debug)]
115pub struct InvalidSignature;
116
117/// `Signable` types are types that can be signed with
118/// [`ed25519::KeyPair::sign_struct`](KeyPair::sign_struct).
119///
120/// `Signable` types must have a _globally_ unique domain separation value to
121/// prevent type confusion attacks. This value is effectively prepended to the
122/// signature in order to bind that signature to only this particular type.
123pub trait Signable {
124    /// Implementors will only need to fill in this value. An example is
125    /// `array::pad(*b"LEXE-REALM::RootSeed")`, used in the `RootSeed`.
126    const DOMAIN_SEPARATOR: [u8; 32];
127}
128
129// Blanket trait impl for &T.
130impl<T: Signable> Signable for &T {
131    const DOMAIN_SEPARATOR: [u8; 32] = T::DOMAIN_SEPARATOR;
132}
133
134// -- verify_signed_struct -- //
135
136/// Helper fn to pass to [`ed25519::verify_signed_struct`]
137/// that accepts any public key, so long as the signature is OK.
138pub fn accept_any_signer(_: &PublicKey) -> bool {
139    true
140}
141
142/// Verify a BCS-serialized and signed [`Signable`] struct.
143/// Returns the deserialized struct inside a [`Signed`] proof that it was in
144/// fact signed by the associated [`ed25519::PublicKey`].
145///
146/// Signed struct signatures are created using
147/// [`ed25519::KeyPair::sign_struct`](KeyPair::sign_struct).
148pub fn verify_signed_struct<'msg, T, F>(
149    is_expected_signer: F,
150    serialized: &'msg [u8],
151) -> Result<Signed<T>, Error>
152where
153    T: Signable + Deserialize<'msg>,
154    F: FnOnce(&'msg PublicKey) -> bool,
155{
156    let (signer, sig, ser_struct) = deserialize_signed_struct(serialized)?;
157
158    // ensure the signer is expected
159    if !is_expected_signer(signer) {
160        return Err(Error::UnexpectedSigner);
161    }
162
163    // verify the signature on this serialized struct. the sig should also
164    // commit to the domain separator for this type.
165    verify_signed_struct_inner(signer, sig, ser_struct, &T::DOMAIN_SEPARATOR)
166        .map_err(|_| Error::InvalidSignature)?;
167
168    // canonically deserialize the struct; assume it's bcs-serialized
169    let inner: T =
170        bcs::from_bytes(ser_struct).map_err(|_| Error::BcsDeserialize)?;
171
172    // wrap the deserialized struct in a "proof-carrying" type that can only
173    // be instantiated by actually verifying the signature.
174    Ok(Signed {
175        signer: *signer,
176        sig: *sig,
177        inner,
178    })
179}
180
181// NOTE: these fns are intentionally written as separate methods w/o
182// any generics to reduce binary size.
183
184fn deserialize_signed_struct(
185    serialized: &[u8],
186) -> Result<(&PublicKey, &Signature, &[u8]), Error> {
187    if serialized.len() < SIGNED_STRUCT_OVERHEAD {
188        return Err(Error::SignedTooShort);
189    }
190
191    // deserialize signer public key
192    let (signer, serialized) = serialized
193        .split_first_chunk::<PUBLIC_KEY_LEN>()
194        .expect("serialized.len() checked above");
195    let signer = PublicKey::from_ref(signer);
196
197    // deserialize signature
198    let (sig, ser_struct) = serialized
199        .split_first_chunk::<SIGNATURE_LEN>()
200        .expect("serialized.len() checked above");
201    let sig = Signature::from_ref(sig);
202
203    Ok((signer, sig, ser_struct))
204}
205
206fn verify_signed_struct_inner(
207    signer: &PublicKey,
208    sig: &Signature,
209    ser_struct: &[u8],
210    domain_separator: &[u8; 32],
211) -> Result<(), InvalidSignature> {
212    // ring doesn't let you digest multiple values into the inner SHA-512 digest
213    // w/o just allocating + copying, so we do a quick pre-hash outside.
214
215    let msg = sha256::digest_many(&[domain_separator.as_slice(), ser_struct]);
216    signer.verify_raw(msg.as_slice(), sig)
217}
218
219// -- impl KeyPair -- //
220
221impl KeyPair {
222    /// Create a new `ed25519::KeyPair` from a random 32-byte seed.
223    ///
224    /// Use this when deriving a key pair from a KDF like `RootSeed`.
225    pub fn from_seed(seed: &[u8; 32]) -> Self {
226        let key_pair = ring::signature::Ed25519KeyPair::from_seed_unchecked(
227            seed,
228        )
229        .expect("This should never fail, as the seed is exactly 32 bytes");
230        Self {
231            seed: *seed,
232            key_pair,
233        }
234    }
235
236    pub fn from_seed_owned(seed: [u8; 32]) -> Self {
237        let key_pair = ring::signature::Ed25519KeyPair::from_seed_unchecked(
238            &seed,
239        )
240        .expect("This should never fail, as the seed is exactly 32 bytes");
241        Self { seed, key_pair }
242    }
243
244    /// Create a new `ed25519::KeyPair` from a random 32-byte seed and the
245    /// expected public key. Will return an error if the derived public key
246    /// doesn't match.
247    pub fn from_seed_and_pubkey(
248        seed: &[u8; 32],
249        expected_pubkey: &[u8; 32],
250    ) -> Result<Self, Error> {
251        let key_pair =
252            ring::signature::Ed25519KeyPair::from_seed_and_public_key(
253                seed.as_slice(),
254                expected_pubkey.as_slice(),
255            )
256            .map_err(|_| Error::PublicKeyMismatch)?;
257        Ok(Self {
258            seed: *seed,
259            key_pair,
260        })
261    }
262
263    /// Sample a new `ed25519::KeyPair` from a cryptographic RNG.
264    ///
265    /// Use this when sampling a key pair for the first time or sampling an
266    /// ephemeral key pair.
267    pub fn from_rng(mut rng: &mut dyn Crng) -> Self {
268        Self::from_seed_owned(rng.gen_bytes())
269    }
270
271    /// Convert the current `ed25519::KeyPair` into a
272    /// [`ring::signature::Ed25519KeyPair`].
273    ///
274    /// Requires a small intermediate serialization step since [`ring`] key
275    /// pairs can't be cloned.
276    pub fn to_ring(&self) -> ring::signature::Ed25519KeyPair {
277        let pkcs8_bytes = self.serialize_pkcs8_der();
278        ring::signature::Ed25519KeyPair::from_pkcs8(&pkcs8_bytes).unwrap()
279    }
280
281    /// Convert the current `ed25519::KeyPair` into a
282    /// [`ring::signature::Ed25519KeyPair`] without an intermediate
283    /// serialization step.
284    pub fn into_ring(self) -> ring::signature::Ed25519KeyPair {
285        self.key_pair
286    }
287
288    /// Create a new `ed25519::KeyPair` from a short id number.
289    ///
290    /// NOTE: this should only be used in tests.
291    pub fn for_test(id: u64) -> Self {
292        const LEN: usize = std::mem::size_of::<u64>();
293
294        let mut seed = [0u8; 32];
295        seed[0..LEN].copy_from_slice(id.to_le_bytes().as_slice());
296        Self::from_seed(&seed)
297    }
298
299    /// Serialize the `ed25519::KeyPair` into PKCS#8 DER bytes.
300    pub fn serialize_pkcs8_der(&self) -> [u8; PKCS_LEN] {
301        serialize_keypair_pkcs8_der(&self.seed, self.public_key().as_inner())
302    }
303
304    /// Deserialize an `ed25519::KeyPair` from PKCS#8 DER bytes.
305    pub fn deserialize_pkcs8_der(bytes: &[u8]) -> Result<Self, Error> {
306        let (seed, expected_pubkey) = deserialize_keypair_pkcs8_der(bytes)
307            .ok_or(Error::KeyDeserializeError)?;
308        Self::from_seed_and_pubkey(seed, expected_pubkey)
309    }
310
311    /// The secret key or "seed" that generated this `ed25519::KeyPair`.
312    pub fn secret_key(&self) -> &[u8; 32] {
313        &self.seed
314    }
315
316    /// The [`PublicKey`] for this `KeyPair`.
317    pub fn public_key(&self) -> &PublicKey {
318        let pubkey_bytes =
319            <&[u8; 32]>::try_from(self.key_pair.public_key().as_ref()).unwrap();
320        PublicKey::from_ref(pubkey_bytes)
321    }
322
323    /// Sign a raw message with this `KeyPair`.
324    pub fn sign_raw(&self, msg: &[u8]) -> Signature {
325        let sig = self.key_pair.sign(msg);
326        Signature::try_from(sig.as_ref()).unwrap()
327    }
328
329    /// Canonically serialize and then sign a [`Signable`] struct `T` with this
330    /// `ed25519::KeyPair`.
331    ///
332    /// Returns a buffer that contains the signer [`PublicKey`] and generated
333    /// [`Signature`] pre-pended in front of the serialized `T`. Also returns a
334    /// [`Signed`] "proof" that asserts this `T` was signed by this key pair.
335    ///
336    /// Values are serialized using [`bcs`], a small binary format intended for
337    /// cryptographic canonical serialization.
338    ///
339    /// You can verify this signed struct using
340    /// [`ed25519::verify_signed_struct`]
341    pub fn sign_struct<'a, T: Signable + Serialize>(
342        &self,
343        value: &'a T,
344    ) -> Result<(Vec<u8>, Signed<&'a T>), bcs::Error> {
345        let signer = self.public_key();
346
347        let struct_ser_len =
348            bcs::serialized_size(value)? + SIGNED_STRUCT_OVERHEAD;
349        let mut out = Vec::with_capacity(struct_ser_len);
350
351        // out := signer || signature || serialized struct
352
353        out.extend_from_slice(signer.as_slice());
354        out.extend_from_slice([0u8; 64].as_slice());
355        bcs::serialize_into(&mut out, value)?;
356
357        // sign this serialized struct using a domain separator that is unique
358        // for this type.
359        let sig = self.sign_struct_inner(
360            &out[SIGNED_STRUCT_OVERHEAD..],
361            &T::DOMAIN_SEPARATOR,
362        );
363        out[PUBLIC_KEY_LEN..SIGNED_STRUCT_OVERHEAD]
364            .copy_from_slice(sig.as_slice());
365
366        Ok((
367            out,
368            Signed {
369                signer: *signer,
370                sig,
371                inner: value,
372            },
373        ))
374    }
375
376    // Use an inner function with no generics to avoid extra code
377    // monomorphization.
378    fn sign_struct_inner(
379        &self,
380        serialized: &[u8],
381        domain_separator: &[u8],
382    ) -> Signature {
383        let msg = sha256::digest_many(&[domain_separator, serialized]);
384        self.sign_raw(msg.as_slice())
385    }
386}
387
388impl fmt::Debug for KeyPair {
389    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390        f.debug_struct("ed25519::KeyPair")
391            .field("sk", &"..")
392            .field("pk", &hex::display(self.public_key().as_slice()))
393            .finish()
394    }
395}
396
397impl FromHex for KeyPair {
398    fn from_hex(s: &str) -> Result<Self, hex::DecodeError> {
399        <[u8; 32]>::from_hex(s).map(Self::from_seed_owned)
400    }
401}
402
403impl FromStr for KeyPair {
404    type Err = hex::DecodeError;
405    #[inline]
406    fn from_str(s: &str) -> Result<Self, Self::Err> {
407        Self::from_hex(s)
408    }
409}
410
411// -- impl PublicKey --- //
412
413impl PublicKey {
414    pub const fn new(bytes: [u8; 32]) -> Self {
415        // TODO(phlip9): check for malleability/small-order subgroup?
416        // https://github.com/aptos-labs/aptos-core/blob/3f437b5597b5d537d03755e599f395a2242f2b91/crates/aptos-crypto/src/ed25519.rs#L358
417        Self(bytes)
418    }
419
420    pub const fn from_ref(bytes: &[u8; 32]) -> &Self {
421        const_utils::const_ref_cast(bytes)
422    }
423
424    pub const fn as_slice(&self) -> &[u8] {
425        self.0.as_slice()
426    }
427
428    pub const fn into_inner(self) -> [u8; 32] {
429        self.0
430    }
431
432    pub const fn as_inner(&self) -> &[u8; 32] {
433        &self.0
434    }
435
436    /// Verify some raw bytes were signed by this public key.
437    pub fn verify_raw(
438        &self,
439        msg: &[u8],
440        sig: &Signature,
441    ) -> Result<(), InvalidSignature> {
442        ring::signature::UnparsedPublicKey::new(
443            &ring::signature::ED25519,
444            self.as_slice(),
445        )
446        .verify(msg, sig.as_slice())
447        .map_err(|_| InvalidSignature)
448    }
449
450    /// Like [`ed25519::verify_signed_struct`] but only allows signatures
451    /// produced by this `ed25519::PublicKey`.
452    pub fn verify_self_signed_struct<'msg, T: Signable + Deserialize<'msg>>(
453        &self,
454        serialized: &'msg [u8],
455    ) -> Result<Signed<T>, Error> {
456        let accept_self_signer = |signer| signer == self;
457        verify_signed_struct(accept_self_signer, serialized)
458    }
459}
460
461impl TryFrom<&[u8]> for PublicKey {
462    type Error = Error;
463
464    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
465        let pk =
466            <[u8; 32]>::try_from(bytes).map_err(|_| Error::InvalidPkLength)?;
467        Ok(Self::new(pk))
468    }
469}
470
471impl AsRef<[u8]> for PublicKey {
472    fn as_ref(&self) -> &[u8] {
473        self.as_slice()
474    }
475}
476
477impl AsRef<[u8; 32]> for PublicKey {
478    fn as_ref(&self) -> &[u8; 32] {
479        self.as_inner()
480    }
481}
482
483impl FromHex for PublicKey {
484    fn from_hex(s: &str) -> Result<Self, hex::DecodeError> {
485        <[u8; 32]>::from_hex(s).map(Self::new)
486    }
487}
488
489impl FromStr for PublicKey {
490    type Err = hex::DecodeError;
491    #[inline]
492    fn from_str(s: &str) -> Result<Self, Self::Err> {
493        Self::from_hex(s)
494    }
495}
496
497impl fmt::Display for PublicKey {
498    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
499        write!(f, "{}", hex::display(self.as_slice()))
500    }
501}
502
503impl fmt::Debug for PublicKey {
504    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
505        f.debug_tuple("ed25519::PublicKey")
506            .field(&hex::display(self.as_slice()))
507            .finish()
508    }
509}
510
511// -- impl Signature -- //
512
513impl Signature {
514    pub const fn new(sig: [u8; 64]) -> Self {
515        Self(sig)
516    }
517
518    pub const fn from_ref(sig: &[u8; 64]) -> &Self {
519        const_utils::const_ref_cast(sig)
520    }
521
522    pub const fn as_slice(&self) -> &[u8] {
523        self.0.as_slice()
524    }
525
526    pub const fn into_inner(self) -> [u8; 64] {
527        self.0
528    }
529
530    pub const fn as_inner(&self) -> &[u8; 64] {
531        &self.0
532    }
533}
534
535impl AsRef<[u8]> for Signature {
536    fn as_ref(&self) -> &[u8] {
537        self.as_slice()
538    }
539}
540
541impl AsRef<[u8; 64]> for Signature {
542    fn as_ref(&self) -> &[u8; 64] {
543        self.as_inner()
544    }
545}
546
547impl TryFrom<&[u8]> for Signature {
548    type Error = Error;
549    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
550        <[u8; 64]>::try_from(value)
551            .map(Signature)
552            .map_err(|_| Error::InvalidSignature)
553    }
554}
555
556impl FromHex for Signature {
557    fn from_hex(s: &str) -> Result<Self, hex::DecodeError> {
558        <[u8; 64]>::from_hex(s).map(Self::new)
559    }
560}
561
562impl FromStr for Signature {
563    type Err = hex::DecodeError;
564    #[inline]
565    fn from_str(s: &str) -> Result<Self, Self::Err> {
566        Self::from_hex(s)
567    }
568}
569
570impl fmt::Display for Signature {
571    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
572        write!(f, "{}", hex::display(self.as_slice()))
573    }
574}
575
576impl fmt::Debug for Signature {
577    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
578        f.debug_tuple("ed25519::Signature")
579            .field(&hex::display(self.as_slice()))
580            .finish()
581    }
582}
583
584// -- impl Signed -- //
585
586impl<T: Signable> Signed<T> {
587    pub fn into_parts(self) -> (PublicKey, Signature, T) {
588        (self.signer, self.sig, self.inner)
589    }
590
591    pub fn inner(&self) -> &T {
592        &self.inner
593    }
594
595    pub fn signer(&self) -> &PublicKey {
596        &self.signer
597    }
598
599    pub fn signature(&self) -> &Signature {
600        &self.sig
601    }
602
603    pub fn as_ref(&self) -> Signed<&T> {
604        Signed {
605            signer: self.signer,
606            sig: self.sig,
607            inner: &self.inner,
608        }
609    }
610}
611
612impl<T: Signable + Serialize> Signed<T> {
613    pub fn serialize(&self) -> Result<Vec<u8>, bcs::Error> {
614        let len = bcs::serialized_size(&self.inner)? + SIGNED_STRUCT_OVERHEAD;
615        let mut out = Vec::with_capacity(len);
616        let mut writer = Cursor::new(&mut out);
617
618        // out := signer || signature || serialized struct
619
620        writer.write_all(self.signer.as_slice()).unwrap();
621        writer.write_all(self.sig.as_slice()).unwrap();
622        bcs::serialize_into(&mut writer, &self.inner)?;
623
624        Ok(out)
625    }
626}
627
628impl<T: Signable + Clone> Signed<&T> {
629    pub fn cloned(&self) -> Signed<T> {
630        Signed {
631            signer: self.signer,
632            sig: self.sig,
633            inner: self.inner.clone(),
634        }
635    }
636}
637
638impl<T: Signable + Clone> Clone for Signed<T> {
639    fn clone(&self) -> Self {
640        Self {
641            signer: self.signer,
642            sig: self.sig,
643            inner: self.inner.clone(),
644        }
645    }
646}
647
648// --- impl Error --- //
649
650impl std::error::Error for Error {}
651
652impl fmt::Display for Error {
653    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
654        let msg = match self {
655            Self::InvalidPkLength =>
656                "ed25519 public key must be exactly 32 bytes",
657            Self::UnexpectedAlgorithm =>
658                "the algorithm OID doesn't match the standard ed25519 OID",
659            Self::KeyDeserializeError =>
660                "failed deserializing PKCS#8-encoded key pair",
661            Self::PublicKeyMismatch =>
662                "derived public key doesn't match expected public key",
663            Self::InvalidSignature => "invalid signature",
664            Self::BcsDeserialize =>
665                "error deserializing inner struct to verify",
666            Self::SignedTooShort => "signed struct is too short",
667            Self::UnexpectedSigner =>
668                "message was signed with a different key pair than expected",
669        };
670        f.write_str(msg)
671    }
672}
673
674// --- impl InvalidSignature --- //
675
676impl std::error::Error for InvalidSignature {}
677
678impl fmt::Display for InvalidSignature {
679    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
680        f.write_str("invalid signature")
681    }
682}
683
684#[cfg(any(test, feature = "test-utils"))]
685mod arbitrary_impls {
686    use proptest::{
687        arbitrary::{Arbitrary, any},
688        strategy::{BoxedStrategy, Strategy},
689    };
690
691    use super::*;
692
693    impl Arbitrary for KeyPair {
694        type Parameters = ();
695        type Strategy = BoxedStrategy<Self>;
696        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
697            any::<[u8; 32]>()
698                .prop_map(|seed| Self::from_seed(&seed))
699                .boxed()
700        }
701    }
702
703    impl Arbitrary for PublicKey {
704        type Parameters = ();
705        type Strategy = BoxedStrategy<Self>;
706        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
707            any::<[u8; 32]>().prop_map(Self::new).boxed()
708        }
709    }
710}
711
712// -- low-level PKCS#8 keypair serialization/deserialization -- //
713
714// Since ed25519 secret keys and public keys are always serialized with the same
715// size and the PKCS#8 v2 serialization always has the same "metadata" bytes,
716// we can just manually inline the constant "metadata" bytes here.
717
718// Note: The `PKCS_TEMPLATE_PREFIX` and `PKCS_TEMPLATE_MIDDLE` are pulled from
719// this pkcs8 "template" file in the `ring` repo.
720//
721// History: up to 2025-10-21, our ed25519 key pairs were not PKCS#8 v2
722// DER-encoded correctly. ring had the wrong "template" (which we vendored early
723// on), that was later fixed in 2023-10-01
724// <https://github.com/briansmith/ring/pull/1680>.
725//
726// # Correct PKCS#8 v2 template
727// $ hexdump -C ring/src/ec/curve25519/ed25519/ed25519_pkcs8_v2_template.der
728// 00000000  30 51 02 01 01 30 05 06  03 2b 65 70 04 22 04 20
729// 00000010  81 21 00
730//
731// # Previously, ring used an incorrect template...
732// $ hexdump -C ring/src/ec/curve25519/ed25519/ed25519_pkcs8_v2_template.der
733// 00000000  30 53 02 01 01 30 05 06  03 2b 65 70 04 22 04 20
734// 00000010  a1 23 03 21 00
735
736const PKCS_TEMPLATE_PREFIX: &[u8] = &[
737    0x30, 0x51, 0x02, 0x01, 0x01, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70,
738    0x04, 0x22, 0x04, 0x20,
739];
740const PKCS_TEMPLATE_PREFIX_BAD: &[u8] = &[
741    0x30, 0x53, 0x02, 0x01, 0x01, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70,
742    0x04, 0x22, 0x04, 0x20,
743];
744const PKCS_TEMPLATE_MIDDLE_BAD: &[u8] = &[0xa1, 0x23, 0x03, 0x21, 0x00];
745const PKCS_TEMPLATE_MIDDLE: &[u8] = &[0x81, 0x21, 0x00];
746const PKCS_TEMPLATE_KEY_IDX: usize = 16;
747
748// The length of an ed25519 key pair serialized as PKCS#8 v2 with embedded
749// public key.
750const PKCS_LEN: usize = PKCS_TEMPLATE_PREFIX.len()
751    + SECRET_KEY_LEN
752    + PKCS_TEMPLATE_MIDDLE.len()
753    + PUBLIC_KEY_LEN;
754const PKCS_LEN_BAD: usize = PKCS_TEMPLATE_PREFIX_BAD.len()
755    + SECRET_KEY_LEN
756    + PKCS_TEMPLATE_MIDDLE_BAD.len()
757    + PUBLIC_KEY_LEN;
758
759// Ensure these don't accidentally change.
760lexe_std::const_assert_usize_eq!(PKCS_LEN, 83);
761lexe_std::const_assert_usize_eq!(PKCS_LEN_BAD, 85);
762
763/// Formats a key pair as `prefix || key || middle || pk`, where `prefix`
764/// and `middle` are two pre-computed blobs.
765///
766/// Note: adapted from `ring`, which doesn't let you serialize as pkcs#8 via
767/// any public API...
768fn serialize_keypair_pkcs8_der(
769    secret_key: &[u8; 32],
770    public_key: &[u8; 32],
771) -> [u8; PKCS_LEN] {
772    let mut out = [0u8; PKCS_LEN];
773    let key_start_idx = PKCS_TEMPLATE_KEY_IDX;
774
775    let prefix = PKCS_TEMPLATE_PREFIX;
776    let middle = PKCS_TEMPLATE_MIDDLE;
777
778    let key_end_idx = key_start_idx + secret_key.len();
779    out[..key_start_idx].copy_from_slice(prefix);
780    out[key_start_idx..key_end_idx].copy_from_slice(secret_key);
781    out[key_end_idx..(key_end_idx + middle.len())].copy_from_slice(middle);
782    out[(key_end_idx + middle.len())..].copy_from_slice(public_key);
783
784    out
785}
786
787/// Deserialize the seed and pubkey for a key pair from its PKCS#8-encoded
788/// bytes.
789///
790/// Previously, `ring` used an incorrect PKCS#8 v2 format. For backwards
791/// compatibility, we'll support deserializing from the old, incorrect format.
792fn deserialize_keypair_pkcs8_der(
793    bytes: &[u8],
794) -> Option<(&[u8; 32], &[u8; 32])> {
795    let (seed, pubkey) = if bytes.len() == PKCS_LEN {
796        let seed_mid_pubkey = bytes.strip_prefix(PKCS_TEMPLATE_PREFIX)?;
797        let (seed, mid_pubkey) = seed_mid_pubkey.split_at(SECRET_KEY_LEN);
798        let pubkey = mid_pubkey.strip_prefix(PKCS_TEMPLATE_MIDDLE)?;
799        (seed, pubkey)
800    } else if bytes.len() == PKCS_LEN_BAD {
801        // Deserialize from old, incorrect PKCS#8 v2 format for compat
802        let seed_mid_pubkey = bytes.strip_prefix(PKCS_TEMPLATE_PREFIX_BAD)?;
803        let (seed, mid_pubkey) = seed_mid_pubkey.split_at(SECRET_KEY_LEN);
804        let pubkey = mid_pubkey.strip_prefix(PKCS_TEMPLATE_MIDDLE_BAD)?;
805        (seed, pubkey)
806    } else {
807        return None;
808    };
809
810    let seed = <&[u8; 32]>::try_from(seed).unwrap();
811    let pubkey = <&[u8; 32]>::try_from(pubkey).unwrap();
812
813    Some((seed, pubkey))
814}
815
816#[cfg(test)]
817mod test {
818    use lexe_std::array;
819    use proptest::{arbitrary::any, prop_assume, proptest, strategy::Strategy};
820    use proptest_derive::Arbitrary;
821    use serde::{Deserialize, Serialize};
822
823    use super::*;
824    use crate::rng::FastRng;
825
826    #[derive(Arbitrary, Serialize, Deserialize)]
827    struct SignableBytes(Vec<u8>);
828
829    impl Signable for SignableBytes {
830        const DOMAIN_SEPARATOR: [u8; 32] =
831            array::pad(*b"LEXE-REALM::SignableBytes");
832    }
833
834    impl fmt::Debug for SignableBytes {
835        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
836            f.debug_tuple("SignableBytes")
837                .field(&hex::display(&self.0))
838                .finish()
839        }
840    }
841
842    #[test]
843    fn test_serde_pkcs8_roundtrip() {
844        proptest!(|(seed in any::<[u8; 32]>())| {
845            let key_pair1 = KeyPair::from_seed(&seed);
846            let key_pair_bytes = key_pair1.serialize_pkcs8_der();
847            let key_pair2 =
848                KeyPair::deserialize_pkcs8_der(key_pair_bytes.as_slice())
849                    .unwrap();
850
851            assert_eq!(key_pair1.secret_key(), key_pair2.secret_key());
852            assert_eq!(key_pair1.public_key(), key_pair2.public_key());
853        });
854    }
855
856    #[test]
857    fn test_pkcs8_der_snapshot() {
858        #[track_caller]
859        fn assert_pkcs8_roundtrip(hexstr: &str) {
860            let der = hex::decode(hexstr).unwrap();
861            let _ = KeyPair::deserialize_pkcs8_der(&der).unwrap();
862        }
863
864        // old, incorrect PKCS#8 v2 format
865        assert_pkcs8_roundtrip(
866            "3053020101300506032b657004220420244ae26baa35db07ed4ea37908f111a8fa4cb81109f9897a133b8a8de6e800dca1230321007dc65033bee5975aab9bb06e1e514d29533173511446adc5a73a9540d2addbac",
867        );
868
869        // new, correct PKCS#8 v2 format
870        assert_pkcs8_roundtrip(
871            "3051020101300506032b657004220420244ae26baa35db07ed4ea37908f111a8fa4cb81109f9897a133b8a8de6e800dc8121007dc65033bee5975aab9bb06e1e514d29533173511446adc5a73a9540d2addbac",
872        );
873    }
874
875    // ```bash
876    // $ cargo test -p lexe-common --lib -- pkcs8_der_snapshot_data --nocapture --ignored
877    // ```
878    #[ignore]
879    #[test]
880    fn pkcs8_der_snapshot_data() {
881        let mut rng = FastRng::from_u64(202510211432);
882        let key = KeyPair::from_seed_owned(rng.gen_bytes());
883        println!("{}", hex::display(key.serialize_pkcs8_der().as_slice()));
884    }
885
886    #[test]
887    fn test_deserialize_pkcs8_different_lengths() {
888        for size in 0..=256 {
889            let bytes = vec![0x42_u8; size];
890            let _ = deserialize_keypair_pkcs8_der(&bytes);
891        }
892    }
893
894    // See: [RFC 8032 (EdDSA) > Test Vectors](https://www.rfc-editor.org/rfc/rfc8032.html#page-25)
895    #[test]
896    fn test_ed25519_test_vector() {
897        let sk: [u8; 32] = hex::decode_const(
898            b"c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
899        );
900        let pk: [u8; 32] = hex::decode_const(
901            b"fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
902        );
903        let msg: [u8; 2] = hex::decode_const(b"af82");
904        let sig: [u8; 64] = hex::decode_const(b"6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a");
905
906        let key_pair = KeyPair::from_seed(&sk);
907        let pubkey = key_pair.public_key();
908        assert_eq!(pubkey.as_inner(), &pk);
909
910        let sig2 = key_pair.sign_raw(&msg);
911        assert_eq!(&sig, sig2.as_inner());
912
913        pubkey.verify_raw(&msg, &sig2).unwrap();
914    }
915
916    // truncating the signed struct should cause the verification to fail.
917    #[test]
918    fn test_reject_truncated_sig() {
919        proptest!(|(
920            key_pair in any::<KeyPair>(),
921            msg in any::<SignableBytes>()
922        )| {
923            let pubkey = key_pair.public_key();
924
925            let (sig, signed) = key_pair.sign_struct(&msg).unwrap();
926            let sig2 = signed.serialize().unwrap();
927            assert_eq!(&sig, &sig2);
928
929            let _ = pubkey
930                .verify_self_signed_struct::<SignableBytes>(&sig)
931                .unwrap();
932
933            for trunc_len in 0..SIGNED_STRUCT_OVERHEAD {
934                pubkey
935                    .verify_self_signed_struct::<SignableBytes>(&sig[..trunc_len])
936                    .unwrap_err();
937                pubkey
938                    .verify_self_signed_struct::<SignableBytes>(&sig[(trunc_len+1)..])
939                    .unwrap_err();
940            }
941        });
942    }
943
944    // inserting some random bytes into the signed struct should cause the
945    // sig verification to fail
946    #[test]
947    fn test_reject_pad_sig() {
948        let cfg = proptest::test_runner::Config::with_cases(50);
949        proptest!(cfg, |(
950            key_pair in any::<KeyPair>(),
951            msg in any::<SignableBytes>(),
952            padding in any::<Vec<u8>>(),
953        )| {
954            prop_assume!(!padding.is_empty());
955
956            let pubkey = key_pair.public_key();
957
958            let (sig, signed) = key_pair.sign_struct(&msg).unwrap();
959            let sig2 = signed.serialize().unwrap();
960            assert_eq!(&sig, &sig2);
961
962            let _ = pubkey
963                .verify_self_signed_struct::<SignableBytes>(&sig)
964                .unwrap();
965
966            let mut sig2: Vec<u8> = Vec::with_capacity(sig.len() + padding.len());
967
968            for idx in 0..=sig.len() {
969                let (left, right) = sig.split_at(idx);
970
971                // sig2 := left || padding || right
972
973                sig2.clear();
974                sig2.extend_from_slice(left);
975                sig2.extend_from_slice(&padding);
976                sig2.extend_from_slice(right);
977
978                pubkey
979                    .verify_self_signed_struct::<SignableBytes>(&sig2)
980                    .unwrap_err();
981            }
982        });
983    }
984
985    // flipping some random bits in the signed struct should cause the
986    // verification to fail.
987    #[test]
988    fn test_reject_modified_sig() {
989        let arb_mutation = any::<Vec<u8>>()
990            .prop_filter("can't be empty or all zeroes", |m| {
991                !m.is_empty() && !m.iter().all(|x| x == &0u8)
992            });
993
994        proptest!(|(
995            key_pair in any::<KeyPair>(),
996            msg in any::<SignableBytes>(),
997            mut_offset in any::<usize>(),
998            mut mutation in arb_mutation,
999        )| {
1000            let pubkey = key_pair.public_key();
1001
1002            let (mut sig, signed) = key_pair.sign_struct(&msg).unwrap();
1003            let sig2 = signed.serialize().unwrap();
1004            assert_eq!(&sig, &sig2);
1005
1006            mutation.truncate(sig.len());
1007            prop_assume!(!mutation.is_empty() && !mutation.iter().all(|x| x == &0));
1008
1009            let _ = pubkey
1010                .verify_self_signed_struct::<SignableBytes>(&sig)
1011                .unwrap();
1012
1013            // xor in the mutation bytes to the signature to modify it. any
1014            // modified bit should cause the verification to fail.
1015            for (idx_mut, m) in mutation.into_iter().enumerate() {
1016                let idx_sig = idx_mut.wrapping_add(mut_offset) % sig.len();
1017                sig[idx_sig] ^= m;
1018            }
1019
1020            pubkey.verify_self_signed_struct::<SignableBytes>(&sig).unwrap_err();
1021        });
1022    }
1023
1024    #[test]
1025    fn test_sign_verify() {
1026        proptest!(|(key_pair in any::<KeyPair>(), msg in any::<Vec<u8>>())| {
1027            let pubkey = key_pair.public_key();
1028
1029            let sig = key_pair.sign_raw(&msg);
1030            pubkey.verify_raw(&msg, &sig).unwrap();
1031        });
1032    }
1033
1034    #[test]
1035    fn test_sign_verify_struct() {
1036        #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
1037        struct Foo(u32);
1038
1039        impl Signable for Foo {
1040            const DOMAIN_SEPARATOR: [u8; 32] = array::pad(*b"LEXE-REALM::Foo");
1041        }
1042
1043        #[derive(Debug, Serialize, Deserialize)]
1044        struct Bar(u32);
1045
1046        impl Signable for Bar {
1047            const DOMAIN_SEPARATOR: [u8; 32] = array::pad(*b"LEXE-REALM::Bar");
1048        }
1049
1050        fn arb_foo() -> impl Strategy<Value = Foo> {
1051            any::<u32>().prop_map(Foo)
1052        }
1053
1054        proptest!(|(key_pair in any::<KeyPair>(), foo in arb_foo())| {
1055            let signer = key_pair.public_key();
1056            let (sig, signed) =
1057                key_pair.sign_struct::<Foo>(&foo).unwrap();
1058            let sig2 = signed.serialize().unwrap();
1059            assert_eq!(&sig, &sig2);
1060
1061            let signed2 =
1062                signer.verify_self_signed_struct::<Foo>(&sig).unwrap();
1063            assert_eq!(signed, signed2.as_ref());
1064
1065            // trying to verify signature as another type with a valid
1066            // serialization is prevented by domain separation.
1067
1068            signer.verify_self_signed_struct::<Bar>(&sig).unwrap_err();
1069        });
1070    }
1071}