1use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
12use rand_core::OsRng;
13
14use crate::error::{CryptoError, Result};
15
16#[derive(Clone)]
21pub struct ControllerKeypair {
22 pub id: String,
24 signing: SigningKey,
25}
26
27impl ControllerKeypair {
28 #[must_use]
31 pub fn generate(id: String) -> Self {
32 let signing = SigningKey::generate(&mut OsRng);
33 Self { id, signing }
34 }
35
36 #[must_use]
42 pub fn from_seed(id: String, seed: [u8; 32]) -> Self {
43 Self {
44 id,
45 signing: SigningKey::from_bytes(&seed),
46 }
47 }
48
49 #[must_use]
52 pub fn ltpk(&self) -> [u8; 32] {
53 self.signing.verifying_key().to_bytes()
54 }
55
56 #[must_use]
63 pub fn seed(&self) -> [u8; 32] {
64 self.signing.to_bytes()
65 }
66
67 #[must_use]
70 pub fn sign(&self, msg: &[u8]) -> [u8; 64] {
71 self.signing.sign(msg).to_bytes()
72 }
73
74 pub(crate) fn signing_key(&self) -> SigningKey {
81 self.signing.clone()
82 }
83}
84
85impl PartialEq for ControllerKeypair {
86 fn eq(&self, other: &Self) -> bool {
87 self.id == other.id && self.seed() == other.seed()
88 }
89}
90
91impl Eq for ControllerKeypair {}
92
93pub fn verify_ed25519(ltpk: &[u8; 32], msg: &[u8], sig: &[u8; 64]) -> Result<()> {
103 let vk = VerifyingKey::from_bytes(ltpk).map_err(|_| CryptoError::Signature)?;
104 let signature = Signature::from_bytes(sig);
105 vk.verify_strict(msg, &signature)
106 .map_err(|_| CryptoError::Signature)
107}
108
109#[cfg(test)]
110#[allow(clippy::unwrap_used, clippy::expect_used)]
113mod tests {
114 use super::*;
115
116 fn h(s: &str) -> Vec<u8> {
117 hex::decode(s).unwrap()
118 }
119
120 const RFC8032_SEED: &str = "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb";
122 const RFC8032_PUBLIC: &str = "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c";
123 const RFC8032_MESSAGE: &str = "72";
124 const RFC8032_SIGNATURE: &str = "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00";
125
126 fn seed() -> [u8; 32] {
127 h(RFC8032_SEED).try_into().unwrap()
128 }
129
130 #[test]
131 fn from_seed_matches_rfc8032_public_key() {
132 let kp = ControllerKeypair::from_seed("controller".to_string(), seed());
133 assert_eq!(kp.ltpk().to_vec(), h(RFC8032_PUBLIC));
134 }
135
136 #[test]
137 fn sign_matches_rfc8032_signature() {
138 let kp = ControllerKeypair::from_seed("controller".to_string(), seed());
139 let sig = kp.sign(&h(RFC8032_MESSAGE));
140 assert_eq!(sig.to_vec(), h(RFC8032_SIGNATURE));
141 }
142
143 #[test]
144 fn verify_accepts_rfc8032_signature() {
145 let ltpk: [u8; 32] = h(RFC8032_PUBLIC).try_into().unwrap();
146 let sig: [u8; 64] = h(RFC8032_SIGNATURE).try_into().unwrap();
147 assert!(verify_ed25519(<pk, &h(RFC8032_MESSAGE), &sig).is_ok());
148 }
149
150 #[test]
151 fn verify_rejects_flipped_signature_bit() {
152 let ltpk: [u8; 32] = h(RFC8032_PUBLIC).try_into().unwrap();
153 let mut sig: [u8; 64] = h(RFC8032_SIGNATURE).try_into().unwrap();
154 sig[0] ^= 0x01;
155 assert!(matches!(
156 verify_ed25519(<pk, &h(RFC8032_MESSAGE), &sig),
157 Err(CryptoError::Signature)
158 ));
159 }
160
161 #[test]
162 fn verify_rejects_tampered_message() {
163 let ltpk: [u8; 32] = h(RFC8032_PUBLIC).try_into().unwrap();
164 let sig: [u8; 64] = h(RFC8032_SIGNATURE).try_into().unwrap();
165 assert!(matches!(
166 verify_ed25519(<pk, b"different message", &sig),
167 Err(CryptoError::Signature)
168 ));
169 }
170
171 #[test]
172 fn generate_then_sign_verify_roundtrips() {
173 let kp = ControllerKeypair::generate("aa:bb:cc".to_string());
174 assert_eq!(kp.ltpk().len(), 32);
175 let msg = b"pair-setup signing material";
176 let sig = kp.sign(msg);
177 assert!(verify_ed25519(&kp.ltpk(), msg, &sig).is_ok());
178 }
179}