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