1use crate::{Did, Error, Result};
16use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
17use rand::rngs::OsRng;
18use std::fmt;
19
20pub struct RootKey {
32 signing_key: SigningKey,
33}
34
35impl fmt::Debug for RootKey {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 f.debug_struct("RootKey")
38 .field("did", &self.did().to_string())
39 .finish_non_exhaustive()
40 }
41}
42
43impl RootKey {
44 pub fn generate() -> Self {
46 Self {
47 signing_key: SigningKey::generate(&mut OsRng),
48 }
49 }
50
51 pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self> {
58 Ok(Self {
59 signing_key: SigningKey::from_bytes(bytes),
60 })
61 }
62
63 pub fn did(&self) -> Did {
65 Did::new(self.signing_key.verifying_key())
66 }
67
68 pub fn verifying_key(&self) -> VerifyingKey {
70 self.signing_key.verifying_key()
71 }
72
73 pub fn sign(&self, message: &[u8]) -> Signature {
75 self.signing_key.sign(message)
76 }
77
78 pub fn to_bytes(&self) -> [u8; 32] {
87 self.signing_key.to_bytes()
88 }
89}
90
91pub struct SessionKey {
102 signing_key: SigningKey,
103 root_did: Did,
104}
105
106impl fmt::Debug for SessionKey {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 let pubkey_fingerprint = {
110 let full = self.public_key_base58();
111 if full.len() > 8 {
112 format!("{}...", &full[..8])
113 } else {
114 full
115 }
116 };
117
118 f.debug_struct("SessionKey")
119 .field("root_did", &self.root_did.to_string())
120 .field("pubkey", &pubkey_fingerprint)
121 .finish_non_exhaustive()
122 }
123}
124
125impl SessionKey {
126 pub fn generate(root_did: Did) -> Self {
128 Self {
129 signing_key: SigningKey::generate(&mut OsRng),
130 root_did,
131 }
132 }
133
134 pub fn root_did(&self) -> &Did {
136 &self.root_did
137 }
138
139 pub fn verifying_key(&self) -> VerifyingKey {
141 self.signing_key.verifying_key()
142 }
143
144 pub fn sign(&self, message: &[u8]) -> Signature {
146 self.signing_key.sign(message)
147 }
148
149 pub fn public_key_base58(&self) -> String {
151 bs58::encode(self.signing_key.verifying_key().as_bytes()).into_string()
152 }
153}
154
155pub fn verify(public_key: &VerifyingKey, message: &[u8], signature: &Signature) -> Result<()> {
157 public_key
158 .verify(message, signature)
159 .map_err(|_| Error::InvalidSignature)
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_root_key_generation() {
168 let root = RootKey::generate();
169 let did = root.did();
170 assert!(did.to_string().starts_with("did:key:"));
171 }
172
173 #[test]
174 fn test_sign_verify() {
175 let root = RootKey::generate();
176 let message = b"hello world";
177 let signature = root.sign(message);
178
179 verify(&root.verifying_key(), message, &signature).unwrap();
180 }
181
182 #[test]
183 fn test_session_key() {
184 let root = RootKey::generate();
185 let session = SessionKey::generate(root.did());
186
187 assert_eq!(session.root_did(), &root.did());
188
189 let message = b"session message";
190 let sig = session.sign(message);
191 verify(&session.verifying_key(), message, &sig).unwrap();
192 }
193
194 #[test]
195 fn test_root_key_roundtrip() {
196 let root = RootKey::generate();
197 let bytes = root.to_bytes();
198 let restored = RootKey::from_bytes(&bytes).unwrap();
199
200 assert_eq!(root.did(), restored.did());
201 }
202
203 #[test]
204 fn test_root_key_debug_does_not_leak_secrets() {
205 let root = RootKey::generate();
206 let debug_output = format!("{:?}", root);
207
208 assert!(debug_output.contains("did:key:"));
210
211 assert!(
213 !debug_output.contains("FieldElement"),
214 "Debug output should not contain internal crypto field elements"
215 );
216 assert!(
217 !debug_output.contains("EdwardsPoint"),
218 "Debug output should not contain internal crypto types"
219 );
220 assert!(
221 !debug_output.to_lowercase().contains("secret"),
222 "Debug output should not reference 'secret'"
223 );
224
225 assert!(
227 debug_output.contains(".."),
228 "Debug should indicate hidden fields with .."
229 );
230 }
231
232 #[test]
233 fn test_session_key_debug_does_not_leak_secrets() {
234 let root = RootKey::generate();
235 let session = SessionKey::generate(root.did());
236 let debug_output = format!("{:?}", session);
237
238 assert!(debug_output.contains("root_did"));
240 assert!(debug_output.contains("pubkey"));
241
242 assert!(
244 debug_output.contains("...\""),
245 "Public key should be truncated in debug output"
246 );
247
248 assert!(
250 !debug_output.contains("FieldElement"),
251 "Debug output should not contain internal crypto field elements"
252 );
253 assert!(
254 !debug_output.contains("EdwardsPoint"),
255 "Debug output should not contain internal crypto types"
256 );
257 assert!(
258 !debug_output.to_lowercase().contains("secret"),
259 "Debug output should not reference 'secret'"
260 );
261 }
262}