1use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
10
11use super::derive::DerivedKey;
12use crate::error::JoyError;
13
14pub struct IdentityKeypair {
16 signing_key: SigningKey,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct PublicKey(VerifyingKey);
22
23impl IdentityKeypair {
24 pub fn from_derived_key(key: &DerivedKey) -> Self {
26 let signing_key = SigningKey::from_bytes(key.as_bytes());
27 Self { signing_key }
28 }
29
30 pub fn from_signing_key(key: SigningKey) -> Self {
32 Self { signing_key: key }
33 }
34
35 pub fn from_seed(seed: &[u8; 32]) -> Self {
37 let signing_key = SigningKey::from_bytes(seed);
38 Self { signing_key }
39 }
40
41 pub fn from_random() -> Self {
43 use rand::rngs::OsRng;
44 let signing_key = SigningKey::generate(&mut OsRng);
45 Self { signing_key }
46 }
47
48 pub fn from_token_seed(token: &str, project_id: &str) -> Self {
51 use sha2::{Digest, Sha256};
52 let mut hasher = Sha256::new();
53 hasher.update(token.as_bytes());
54 hasher.update(project_id.as_bytes());
55 let hash = hasher.finalize();
56 let mut seed = [0u8; 32];
57 seed.copy_from_slice(&hash);
58 Self::from_seed(&seed)
59 }
60
61 pub fn public_key(&self) -> PublicKey {
63 PublicKey(self.signing_key.verifying_key())
64 }
65
66 pub fn sign(&self, message: &[u8]) -> Vec<u8> {
68 let sig: Signature = self.signing_key.sign(message);
69 sig.to_bytes().to_vec()
70 }
71
72 pub fn to_seed_bytes(&self) -> [u8; 32] {
76 self.signing_key.to_bytes()
77 }
78}
79
80impl PublicKey {
81 pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<(), JoyError> {
83 let sig = Signature::from_slice(signature)
84 .map_err(|e| JoyError::AuthFailed(format!("invalid signature: {e}")))?;
85 self.0
86 .verify(message, &sig)
87 .map_err(|_| JoyError::AuthFailed("signature verification failed".into()))
88 }
89
90 pub fn to_hex(&self) -> String {
92 hex::encode(self.0.as_bytes())
93 }
94
95 pub fn from_hex(s: &str) -> Result<Self, JoyError> {
97 let bytes =
98 hex::decode(s).map_err(|e| JoyError::AuthFailed(format!("invalid public key: {e}")))?;
99 let arr: [u8; 32] = bytes
100 .try_into()
101 .map_err(|_| JoyError::AuthFailed("public key must be 32 bytes".into()))?;
102 let key = VerifyingKey::from_bytes(&arr)
103 .map_err(|e| JoyError::AuthFailed(format!("invalid Ed25519 key: {e}")))?;
104 Ok(Self(key))
105 }
106}
107
108impl Drop for IdentityKeypair {
109 fn drop(&mut self) {
110 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::auth::derive;
120
121 const TEST_PASSPHRASE: &str = "correct horse battery staple extra words";
122
123 fn test_keypair() -> (IdentityKeypair, DerivedKey) {
124 let salt = derive::Salt::from_hex(
125 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
126 )
127 .unwrap();
128 let key = derive::derive_key(TEST_PASSPHRASE, &salt).unwrap();
129 let keypair = IdentityKeypair::from_derived_key(&key);
130 (keypair, key)
131 }
132
133 #[test]
134 fn keypair_deterministic() {
135 let salt = derive::Salt::from_hex(
136 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
137 )
138 .unwrap();
139 let k1 = derive::derive_key(TEST_PASSPHRASE, &salt).unwrap();
140 let k2 = derive::derive_key(TEST_PASSPHRASE, &salt).unwrap();
141 let kp1 = IdentityKeypair::from_derived_key(&k1);
142 let kp2 = IdentityKeypair::from_derived_key(&k2);
143 assert_eq!(kp1.public_key(), kp2.public_key());
144 }
145
146 #[test]
147 fn sign_verify_roundtrip() {
148 let (keypair, _) = test_keypair();
149 let message = b"hello world";
150 let signature = keypair.sign(message);
151 assert!(keypair.public_key().verify(message, &signature).is_ok());
152 }
153
154 #[test]
155 fn verify_wrong_message() {
156 let (keypair, _) = test_keypair();
157 let signature = keypair.sign(b"original");
158 assert!(keypair
159 .public_key()
160 .verify(b"tampered", &signature)
161 .is_err());
162 }
163
164 #[test]
165 fn verify_wrong_key() {
166 let (keypair, _) = test_keypair();
167 let signature = keypair.sign(b"hello");
168
169 let salt = derive::generate_salt();
171 let other_key =
172 derive::derive_key("alpha bravo charlie delta echo foxtrot", &salt).unwrap();
173 let other_kp = IdentityKeypair::from_derived_key(&other_key);
174 assert!(other_kp.public_key().verify(b"hello", &signature).is_err());
175 }
176
177 #[test]
178 fn public_key_hex_roundtrip() {
179 let (keypair, _) = test_keypair();
180 let pk = keypair.public_key();
181 let hex = pk.to_hex();
182 let parsed = PublicKey::from_hex(&hex).unwrap();
183 assert_eq!(pk, parsed);
184 }
185}