1use core::fmt;
2
3use crate::Rng;
4use ed25519_dalek::Signer;
5use ed25519_dalek::Verifier;
6
7#[derive(Debug, PartialEq)]
8pub enum Ed25519Error {
9 InvalidPublicKey,
11}
12
13impl fmt::Display for Ed25519Error {
14 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15 match self {
16 Ed25519Error::InvalidPublicKey => write!(f, "Invalid Ed25519 public key bytes"),
17 }
18 }
19}
20
21#[cfg(feature = "std")]
22impl std::error::Error for Ed25519Error {}
23
24#[derive(Clone)]
25pub struct Ed25519PrivateKey {
26 inner: ed25519_dalek::SigningKey,
27}
28
29#[derive(Clone)]
30pub struct Ed25519PublicKey {
31 inner: ed25519_dalek::VerifyingKey,
32}
33
34impl Ed25519PrivateKey {
35 pub fn from_bytes(seed: &[u8; 32]) -> Self {
36 Ed25519PrivateKey {
37 inner: ed25519_dalek::SigningKey::from_bytes(seed),
38 }
39 }
40
41 pub fn generate(rng: &mut dyn Rng) -> Self {
42 let mut seed = [0u8; 32];
43 rng.fill_bytes(&mut seed);
44 Self::from_bytes(&seed)
45 }
46
47 pub fn private_bytes(&self) -> [u8; 32] {
48 self.inner.to_bytes()
49 }
50
51 pub fn public_key(&self) -> Ed25519PublicKey {
52 Ed25519PublicKey {
53 inner: self.inner.verifying_key(),
54 }
55 }
56
57 pub fn sign(&self, message: &[u8]) -> [u8; 64] {
58 #[cfg(all(
59 feature = "ed25519-aws-lc",
60 any(target_arch = "x86_64", target_arch = "aarch64")
61 ))]
62 if let Some(signature) = self.sign_aws_lc(message) {
63 return signature;
64 }
65 self.inner.sign(message).to_bytes()
66 }
67
68 #[cfg(all(
71 feature = "ed25519-aws-lc",
72 any(target_arch = "x86_64", target_arch = "aarch64")
73 ))]
74 fn sign_aws_lc(&self, message: &[u8]) -> Option<[u8; 64]> {
75 crate::aws_lc_init();
76 let mut key = zeroize::Zeroizing::new([0u8; 64]);
78 key[..32].copy_from_slice(self.inner.as_bytes());
79 key[32..].copy_from_slice(self.inner.verifying_key().as_bytes());
80 let mut signature = [0u8; 64];
81 let ok = unsafe {
84 aws_lc_sys::ED25519_sign(
85 signature.as_mut_ptr(),
86 message.as_ptr(),
87 message.len(),
88 key.as_ptr(),
89 )
90 };
91 (ok == 1).then_some(signature)
92 }
93}
94
95impl Ed25519PublicKey {
96 pub fn from_bytes(data: &[u8; 32]) -> Result<Self, Ed25519Error> {
101 ed25519_dalek::VerifyingKey::from_bytes(data)
102 .map(|inner| Ed25519PublicKey { inner })
103 .map_err(|_| Ed25519Error::InvalidPublicKey)
104 }
105
106 pub fn public_bytes(&self) -> [u8; 32] {
107 self.inner.to_bytes()
108 }
109
110 pub fn verify(&self, signature: &[u8; 64], message: &[u8]) -> bool {
111 let sig = ed25519_dalek::Signature::from_bytes(signature);
112 self.inner.verify(message, &sig).is_ok()
113 }
114}
115
116#[cfg(feature = "ed25519-batch")]
126pub fn verify_batch(items: &[(&Ed25519PublicKey, &[u8; 64], &[u8])]) -> bool {
127 use alloc::vec::Vec;
128 let messages: Vec<&[u8]> = items.iter().map(|(_, _, message)| *message).collect();
129 let signatures: Vec<ed25519_dalek::Signature> = items
130 .iter()
131 .map(|(_, signature, _)| ed25519_dalek::Signature::from_bytes(signature))
132 .collect();
133 let keys: Vec<ed25519_dalek::VerifyingKey> =
134 items.iter().map(|(key, _, _)| key.inner).collect();
135 ed25519_dalek::verify_batch(&messages, &signatures, &keys).is_ok()
136}
137
138#[cfg(test)]
139mod tests {
140
141 #[cfg(all(
142 feature = "ed25519-aws-lc",
143 any(target_arch = "x86_64", target_arch = "aarch64")
144 ))]
145 #[test]
146 fn aws_lc_signs_the_same_bytes_as_dalek() {
147 use ed25519_dalek::Signer;
148 for seed in 0u8..32 {
149 let key = Ed25519PrivateKey::from_bytes(&[seed.wrapping_mul(37).wrapping_add(1); 32]);
150 for len in [0usize, 1, 32, 64, 500] {
151 let message: Vec<u8> = (0..len).map(|i| (i as u8) ^ seed).collect();
152 let expected = key.inner.sign(&message).to_bytes();
153 assert_eq!(key.sign_aws_lc(&message), Some(expected));
154 assert_eq!(key.sign(&message), expected);
155 assert!(key.public_key().verify(&expected, &message));
156 }
157 }
158 }
159
160 use super::*;
161
162 #[cfg(feature = "ed25519-batch")]
163 #[test]
164 fn batch_accepts_valid_signatures_and_rejects_any_bad_one() {
165 let keys: alloc::vec::Vec<Ed25519PrivateKey> = (0u8..8)
166 .map(|i| Ed25519PrivateKey::from_bytes(&[i + 1; 32]))
167 .collect();
168 let messages: alloc::vec::Vec<[u8; 32]> = (0u8..8).map(|i| [i; 32]).collect();
169 let publics: alloc::vec::Vec<Ed25519PublicKey> =
170 keys.iter().map(|key| key.public_key()).collect();
171 let mut signatures: alloc::vec::Vec<[u8; 64]> = keys
172 .iter()
173 .zip(&messages)
174 .map(|(key, message)| key.sign(message))
175 .collect();
176 let items = |signatures: &[[u8; 64]]| -> bool {
177 let batch: alloc::vec::Vec<(&Ed25519PublicKey, &[u8; 64], &[u8])> = publics
178 .iter()
179 .zip(signatures)
180 .zip(&messages)
181 .map(|((key, signature), message)| (key, signature, &message[..]))
182 .collect();
183 verify_batch(&batch)
184 };
185 assert!(items(&signatures));
186 assert!(verify_batch(&[]));
187 signatures[5][0] ^= 1;
188 assert!(!items(&signatures));
189 }
190
191 struct Rfc8032Vector {
192 seed: &'static str,
193 public_key: &'static str,
194 message: &'static str,
195 signature: &'static str,
196 }
197
198 const RFC8032_VECTORS: &[Rfc8032Vector] = &[
199 Rfc8032Vector {
200 seed: "9d61b19deffd5a60ba844af492ec2cc4\
201 4449c5697b326919703bac031cae7f60",
202 public_key: "d75a980182b10ab7d54bfed3c964073a\
203 0ee172f3daa62325af021a68f707511a",
204 message: "",
205 signature: "e5564300c360ac729086e2cc806e828a\
206 84877f1eb8e5d974d873e06522490155\
207 5fb8821590a33bacc61e39701cf9b46b\
208 d25bf5f0595bbe24655141438e7a100b",
209 },
210 Rfc8032Vector {
211 seed: "4ccd089b28ff96da9db6c346ec114e0f\
212 5b8a319f35aba624da8cf6ed4fb8a6fb",
213 public_key: "3d4017c3e843895a92b70aa74d1b7ebc\
214 9c982ccf2ec4968cc0cd55f12af4660c",
215 message: "72",
216 signature: "92a009a9f0d4cab8720e820b5f642540\
217 a2b27b5416503f8fb3762223ebdb69da\
218 085ac1e43e15996e458f3613d0f11d8c\
219 387b2eaeb4302aeeb00d291612bb0c00",
220 },
221 Rfc8032Vector {
222 seed: "c5aa8df43f9f837bedb7442f31dcb7b1\
223 66d38535076f094b85ce3a2e0b4458f7",
224 public_key: "fc51cd8e6218a1a38da47ed00230f058\
225 0816ed13ba3303ac5deb911548908025",
226 message: "af82",
227 signature: "6291d657deec24024827e69c3abe01a3\
228 0ce548a284743a445e3680d7db5ac3ac\
229 18ff9b538d16f290ae67f760984dc659\
230 4a7c15e9716ed28dc027beceea1ec40a",
231 },
232 ];
233
234 #[test]
235 fn test_ed25519_sign_verify_roundtrip() {
236 let seed = [42u8; 32];
237 let key = Ed25519PrivateKey::from_bytes(&seed);
238 let pubkey = key.public_key();
239 let msg = b"Hello, Ed25519!";
240 let sig = key.sign(msg);
241 assert!(pubkey.verify(&sig, msg));
242 }
243
244 #[test]
245 fn test_ed25519_verify_tampered() {
246 let seed = [42u8; 32];
247 let key = Ed25519PrivateKey::from_bytes(&seed);
248 let pubkey = key.public_key();
249 let msg = b"Hello, Ed25519!";
250 let sig = key.sign(msg);
251 assert!(!pubkey.verify(&sig, b"Hello, Ed25519?"));
252 }
253
254 #[test]
255 fn test_ed25519_pubkey_deterministic() {
256 let seed = [1u8; 32];
257 let key1 = Ed25519PrivateKey::from_bytes(&seed);
258 let key2 = Ed25519PrivateKey::from_bytes(&seed);
259 assert_eq!(
260 key1.public_key().public_bytes(),
261 key2.public_key().public_bytes()
262 );
263 }
264
265 #[test]
266 fn test_ed25519_rfc8032_known_answers() {
267 for vector in RFC8032_VECTORS {
268 let seed: [u8; 32] = decode_hex(vector.seed).try_into().unwrap();
269 let expected_public: [u8; 32] = decode_hex(vector.public_key).try_into().unwrap();
270 let message = decode_hex(vector.message);
271 let expected_signature: [u8; 64] = decode_hex(vector.signature).try_into().unwrap();
272 let key = Ed25519PrivateKey::from_bytes(&seed);
273
274 assert_eq!(key.private_bytes(), seed);
275 assert_eq!(key.public_key().public_bytes(), expected_public);
276 assert_eq!(key.sign(&message), expected_signature);
277 assert!(key.public_key().verify(&expected_signature, &message));
278 assert!(Ed25519PublicKey::from_bytes(&expected_public)
279 .unwrap()
280 .verify(&expected_signature, &message));
281 }
282 }
283
284 #[test]
285 fn test_ed25519_rejects_signature_and_message_mutations() {
286 let key = Ed25519PrivateKey::from_bytes(&[0x42; 32]);
287 let public = key.public_key();
288 let message = b"Reticulum signature compatibility";
289 let signature = key.sign(message);
290
291 for index in [0, 31, 32, 63] {
292 let mut mutated = signature;
293 mutated[index] ^= 0x80;
294 assert!(!public.verify(&mutated, message));
295 }
296
297 let mut mutated_message = *b"Reticulum signature compatibility";
298 mutated_message[0] ^= 1;
299 assert!(!public.verify(&signature, &mutated_message));
300 }
301
302 #[test]
303 fn test_ed25519_rejects_noncanonical_scalar() {
304 let key = Ed25519PrivateKey::from_bytes(&[0x24; 32]);
305 let public = key.public_key();
306 let message = b"noncanonical signature scalar";
307 let mut signature = key.sign(message);
308
309 signature[32..].copy_from_slice(&[
310 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9,
311 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
312 0x00, 0x00, 0x00, 0x10,
313 ]);
314
315 assert!(!public.verify(&signature, message));
316 }
317
318 #[test]
319 fn test_ed25519_invalid_public_key_bytes_return_error() {
320 let mut invalid = [0u8; 32];
323 invalid[0] = 2;
324 assert!(matches!(
325 Ed25519PublicKey::from_bytes(&invalid),
326 Err(Ed25519Error::InvalidPublicKey)
327 ));
328 }
329
330 #[test]
331 fn test_ed25519_valid_public_key_bytes_roundtrip() {
332 let public = Ed25519PrivateKey::from_bytes(&[7; 32]).public_key();
333 let bytes = public.public_bytes();
334 let decoded = Ed25519PublicKey::from_bytes(&bytes).unwrap();
335 assert_eq!(decoded.public_bytes(), bytes);
336 }
337
338 fn decode_hex(hex: &str) -> alloc::vec::Vec<u8> {
339 let compact: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
340 (0..compact.len())
341 .step_by(2)
342 .map(|index| u8::from_str_radix(&compact[index..index + 2], 16).unwrap())
343 .collect()
344 }
345}