actpub_httpsig/key/
ed25519.rs1use std::fmt;
4
5use aws_lc_rs::rand::SystemRandom;
6use aws_lc_rs::signature::{self, Ed25519KeyPair, KeyPair, UnparsedPublicKey};
7
8use crate::error::Error;
9
10pub(crate) const ED25519_PUBLIC_KEY_LEN: usize = 32;
12
13pub(crate) const ED25519_SIGNATURE_LEN: usize = 64;
15
16pub struct Ed25519SigningKey {
18 inner: Ed25519KeyPair,
19 pkcs8_der: Vec<u8>,
22}
23
24impl Ed25519SigningKey {
25 pub fn generate() -> Result<Self, Error> {
33 let rng = SystemRandom::new();
34 let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng)
35 .map_err(|_| Error::KeyGeneration("Ed25519 PKCS#8 generation failed"))?;
36 let inner = Ed25519KeyPair::from_pkcs8(pkcs8.as_ref())
37 .map_err(|_| Error::KeyGeneration("Ed25519 PKCS#8 parse after generate"))?;
38 Ok(Self {
39 inner,
40 pkcs8_der: pkcs8.as_ref().to_vec(),
41 })
42 }
43
44 pub fn from_pkcs8_der(der: &[u8]) -> Result<Self, Error> {
51 let inner = Ed25519KeyPair::from_pkcs8(der)
52 .map_err(|e| Error::InvalidPkcs8(format!("Ed25519: {e}")))?;
53 Ok(Self {
54 inner,
55 pkcs8_der: der.to_vec(),
56 })
57 }
58
59 #[must_use]
61 pub fn to_pkcs8_der(&self) -> &[u8] {
62 &self.pkcs8_der
63 }
64
65 #[must_use]
74 pub fn public_key(&self) -> Ed25519PublicKey {
75 #[allow(
76 clippy::expect_used,
77 reason = "aws-lc-rs guarantees an Ed25519 public key is exactly 32 bytes"
78 )]
79 let bytes: [u8; ED25519_PUBLIC_KEY_LEN] = self
80 .inner
81 .public_key()
82 .as_ref()
83 .try_into()
84 .expect("Ed25519 public key is exactly 32 bytes");
85 Ed25519PublicKey { bytes }
86 }
87
88 #[must_use]
90 pub fn sign(&self, message: &[u8]) -> Vec<u8> {
91 self.inner.sign(message).as_ref().to_vec()
92 }
93}
94
95impl fmt::Debug for Ed25519SigningKey {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 f.debug_struct("Ed25519SigningKey").finish_non_exhaustive()
99 }
100}
101
102#[derive(Clone, Copy, PartialEq, Eq, Hash)]
104pub struct Ed25519PublicKey {
105 bytes: [u8; ED25519_PUBLIC_KEY_LEN],
106}
107
108impl Ed25519PublicKey {
109 pub fn from_bytes(raw: &[u8]) -> Result<Self, Error> {
116 let bytes: [u8; ED25519_PUBLIC_KEY_LEN] =
117 raw.try_into().map_err(|_| Error::InvalidMultikeyLength {
118 expected: ED25519_PUBLIC_KEY_LEN,
119 actual: raw.len(),
120 })?;
121 Ok(Self { bytes })
122 }
123
124 #[must_use]
126 pub const fn as_bytes(&self) -> &[u8; ED25519_PUBLIC_KEY_LEN] {
127 &self.bytes
128 }
129
130 pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<(), Error> {
137 if signature.len() != ED25519_SIGNATURE_LEN {
138 return Err(Error::VerificationFailed);
139 }
140 UnparsedPublicKey::new(&signature::ED25519, self.bytes)
141 .verify(message, signature)
142 .map_err(|_| Error::VerificationFailed)
143 }
144}
145
146impl fmt::Debug for Ed25519PublicKey {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 f.debug_tuple("Ed25519PublicKey")
150 .field(&format_args!("{}", hex(&self.bytes)))
151 .finish()
152 }
153}
154
155#[allow(
156 clippy::expect_used,
157 reason = "writing to an owned `String` via `core::fmt::Write` is infallible; the `Result` only exists to satisfy the trait"
158)]
159fn hex(bytes: &[u8]) -> String {
160 use core::fmt::Write as _;
161 let mut out = String::with_capacity(bytes.len() * 2);
162 for b in bytes {
163 write!(out, "{b:02x}").expect("writing to an owned String is infallible");
164 }
165 out
166}
167
168#[cfg(test)]
169mod tests {
170 use pretty_assertions::assert_eq;
171
172 use super::*;
173
174 #[test]
175 fn generate_then_sign_and_verify_roundtrips() {
176 let key = Ed25519SigningKey::generate().expect("rng available");
177 let public = key.public_key();
178 let msg = b"activitypub inbox delivery";
179 let sig = key.sign(msg);
180 assert_eq!(sig.len(), ED25519_SIGNATURE_LEN);
181 public.verify(msg, &sig).expect("signature must verify");
182 }
183
184 #[test]
185 fn tampered_message_fails_verification() {
186 let key = Ed25519SigningKey::generate().expect("rng available");
187 let public = key.public_key();
188 let sig = key.sign(b"original message");
189 let err = public
190 .verify(b"tampered message", &sig)
191 .expect_err("tampered message must not verify");
192 assert!(matches!(err, Error::VerificationFailed));
193 }
194
195 #[test]
196 fn wrong_signature_length_is_rejected() {
197 let key = Ed25519SigningKey::generate().expect("rng available");
198 let public = key.public_key();
199 let err = public
200 .verify(b"msg", &[0u8; 32])
201 .expect_err("short signature must not verify");
202 assert!(matches!(err, Error::VerificationFailed));
203 }
204
205 #[test]
206 fn pkcs8_roundtrip_preserves_key() {
207 let original = Ed25519SigningKey::generate().expect("rng available");
208 let reloaded = Ed25519SigningKey::from_pkcs8_der(original.to_pkcs8_der())
209 .expect("reload must succeed");
210 assert_eq!(original.public_key(), reloaded.public_key());
211 }
212
213 #[test]
214 fn from_bytes_rejects_wrong_length() {
215 let err = Ed25519PublicKey::from_bytes(&[0u8; 16]).expect_err("16 bytes must be rejected");
216 assert!(matches!(
217 err,
218 Error::InvalidMultikeyLength {
219 expected: 32,
220 actual: 16
221 }
222 ));
223 }
224}