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