1use base64::engine::general_purpose::URL_SAFE_NO_PAD;
4use base64::Engine;
5use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
6use sha2::{Digest, Sha256};
7
8use crate::error::Error;
9
10pub fn generate_keypair() -> (SigningKey, VerifyingKey) {
12 let mut csprng = rand::rngs::OsRng;
13 let signing_key = SigningKey::generate(&mut csprng);
14 let verifying_key = signing_key.verifying_key();
15 (signing_key, verifying_key)
16}
17
18pub fn sign(payload: &[u8], key: &SigningKey) -> Signature {
20 key.sign(payload)
21}
22
23pub fn verify(payload: &[u8], signature: &Signature, key: &VerifyingKey) -> bool {
25 key.verify(payload, signature).is_ok()
26}
27
28pub fn sha256_hex(data: &[u8]) -> String {
30 let mut hasher = Sha256::new();
31 hasher.update(data);
32 hex::encode(hasher.finalize())
33}
34
35pub fn base64url_encode(data: &[u8]) -> String {
37 URL_SAFE_NO_PAD.encode(data)
38}
39
40pub fn base64url_decode(data: &str) -> Result<Vec<u8>, Error> {
42 URL_SAFE_NO_PAD
43 .decode(data)
44 .map_err(|e| Error::InvalidKey(format!("base64url decode failed: {e}")))
45}
46
47pub fn decode_verifying_key(b64: &str) -> Result<VerifyingKey, Error> {
49 let bytes = base64url_decode(b64)?;
50 let arr: [u8; 32] = bytes
51 .try_into()
52 .map_err(|_| Error::InvalidKey("public key must be 32 bytes".into()))?;
53 VerifyingKey::from_bytes(&arr)
54 .map_err(|e| Error::InvalidKey(format!("invalid Ed25519 public key: {e}")))
55}
56
57pub fn decode_signing_key(b64: &str) -> Result<SigningKey, Error> {
62 let bytes = base64url_decode(b64)?;
63 if bytes.len() != 32 && bytes.len() != 64 {
64 return Err(Error::InvalidKey(format!(
65 "private key must be 32 or 64 bytes, got {}",
66 bytes.len()
67 )));
68 }
69 let seed: [u8; 32] = bytes[..32]
70 .try_into()
71 .map_err(|_| Error::InvalidKey("invalid private key bytes".into()))?;
72 Ok(SigningKey::from_bytes(&seed))
73}
74
75pub fn generate_nonce() -> String {
77 use rand::Rng;
78 let mut rng = rand::thread_rng();
79 let bytes: [u8; 16] = rng.gen();
80 hex::encode(bytes)
81}
82
83use crypto_box::{
88 aead::{Aead, AeadCore, OsRng},
89 PublicKey as BoxPublicKey, SalsaBox, SecretKey as BoxSecretKey,
90};
91use curve25519_dalek::edwards::CompressedEdwardsY;
92
93pub fn ed25519_to_x25519_public(ed25519_pub: &[u8; 32]) -> Result<[u8; 32], Error> {
95 let compressed = CompressedEdwardsY::from_slice(ed25519_pub)
96 .map_err(|e| Error::InvalidKey(format!("invalid Ed25519 public key: {e}")))?;
97 let edwards = compressed
98 .decompress()
99 .ok_or_else(|| Error::InvalidKey("failed to decompress Ed25519 point".into()))?;
100 Ok(edwards.to_montgomery().to_bytes())
101}
102
103pub fn ed25519_to_x25519_private(signing_key: &SigningKey) -> [u8; 32] {
108 use sha2::Sha512;
110 let hash = <Sha512 as Digest>::digest(signing_key.as_bytes());
111 let mut key = [0u8; 32];
112 key.copy_from_slice(&hash[..32]);
113 key[0] &= 248;
114 key[31] &= 127;
115 key[31] |= 64;
116 key
117}
118
119pub fn encrypt_for(
123 plaintext: &[u8],
124 recipient_ed25519_pub: &[u8; 32],
125 sender_signing_key: &SigningKey,
126) -> Result<Vec<u8>, Error> {
127 let sender_x25519 = ed25519_to_x25519_private(sender_signing_key);
128 let recipient_x25519 = ed25519_to_x25519_public(recipient_ed25519_pub)?;
129
130 let sender_secret = BoxSecretKey::from(sender_x25519);
131 let recipient_public = BoxPublicKey::from(recipient_x25519);
132
133 let salsa_box = SalsaBox::new(&recipient_public, &sender_secret);
134 let nonce = SalsaBox::generate_nonce(&mut OsRng);
135 let encrypted = salsa_box
136 .encrypt(&nonce, plaintext)
137 .map_err(|e| Error::Signing(format!("encryption failed: {e}")))?;
138
139 let mut result = Vec::with_capacity(24 + encrypted.len());
140 result.extend_from_slice(&nonce);
141 result.extend_from_slice(&encrypted);
142 Ok(result)
143}
144
145pub fn decrypt_from(
149 ciphertext: &[u8],
150 sender_ed25519_pub: &[u8; 32],
151 recipient_signing_key: &SigningKey,
152) -> Result<Vec<u8>, Error> {
153 if ciphertext.len() < 24 {
154 return Err(Error::InvalidKey("ciphertext too short".into()));
155 }
156
157 let recipient_x25519 = ed25519_to_x25519_private(recipient_signing_key);
158 let sender_x25519 = ed25519_to_x25519_public(sender_ed25519_pub)?;
159
160 let recipient_secret = BoxSecretKey::from(recipient_x25519);
161 let sender_public = BoxPublicKey::from(sender_x25519);
162
163 let salsa_box = SalsaBox::new(&sender_public, &recipient_secret);
164
165 let nonce = crypto_box::Nonce::from_slice(&ciphertext[..24]);
166 salsa_box
167 .decrypt(nonce, &ciphertext[24..])
168 .map_err(|e| Error::Verification(format!("decryption failed: {e}")))
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn keypair_sign_verify_roundtrip() {
177 let (signing, verifying) = generate_keypair();
178 let msg = b"hello oaid v2";
179 let sig = sign(msg, &signing);
180 assert!(verify(msg, &sig, &verifying));
181 }
182
183 #[test]
184 fn wrong_key_rejects() {
185 let (signing, _) = generate_keypair();
186 let (_, other_verifying) = generate_keypair();
187 let sig = sign(b"hello", &signing);
188 assert!(!verify(b"hello", &sig, &other_verifying));
189 }
190
191 #[test]
192 fn sha256_empty() {
193 assert_eq!(
194 sha256_hex(b""),
195 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
196 );
197 }
198
199 #[test]
200 fn sha256_json() {
201 assert_eq!(
202 sha256_hex(b"{\"task\":\"search\"}"),
203 "0dfd9a0e52fe94a5e6311a6ef4643304c65636ae7fc316a0334e91c9665370af"
204 );
205 }
206
207 #[test]
208 fn base64url_roundtrip() {
209 let data = b"open agent id v2";
210 let encoded = base64url_encode(data);
211 let decoded = base64url_decode(&encoded).unwrap();
212 assert_eq!(decoded, data);
213 }
214
215 #[test]
216 fn decode_verifying_key_valid() {
217 let (_, vk) = generate_keypair();
218 let b64 = base64url_encode(vk.as_bytes());
219 let decoded = decode_verifying_key(&b64).unwrap();
220 assert_eq!(decoded.as_bytes(), vk.as_bytes());
221 }
222
223 #[test]
224 fn decode_signing_key_32_bytes() {
225 let (sk, _) = generate_keypair();
226 let b64 = base64url_encode(&sk.to_bytes());
227 let decoded = decode_signing_key(&b64).unwrap();
228 assert_eq!(decoded.to_bytes(), sk.to_bytes());
229 }
230
231 #[test]
232 fn nonce_length() {
233 let nonce = generate_nonce();
234 assert_eq!(nonce.len(), 32);
235 assert!(nonce.chars().all(|c| c.is_ascii_hexdigit()));
236 }
237
238 #[test]
239 fn key_conversion_deterministic() {
240 let (sk, vk) = generate_keypair();
241 let x_pub1 = ed25519_to_x25519_public(vk.as_bytes()).unwrap();
242 let x_pub2 = ed25519_to_x25519_public(vk.as_bytes()).unwrap();
243 assert_eq!(x_pub1, x_pub2);
244 assert_eq!(x_pub1.len(), 32);
245
246 let x_priv1 = ed25519_to_x25519_private(&sk);
247 let x_priv2 = ed25519_to_x25519_private(&sk);
248 assert_eq!(x_priv1, x_priv2);
249 }
250
251 #[test]
252 fn encrypt_decrypt_roundtrip() {
253 let (sender_sk, _sender_vk) = generate_keypair();
254 let (recipient_sk, recipient_vk) = generate_keypair();
255
256 let plaintext = b"hello agent world";
257 let ciphertext =
258 encrypt_for(plaintext, recipient_vk.as_bytes(), &sender_sk).unwrap();
259
260 assert_eq!(ciphertext.len(), 24 + 16 + plaintext.len());
262
263 let decrypted =
264 decrypt_from(&ciphertext, _sender_vk.as_bytes(), &recipient_sk).unwrap();
265 assert_eq!(decrypted, plaintext);
266 }
267
268 #[test]
269 fn decrypt_wrong_key_fails() {
270 let (sender_sk, sender_vk) = generate_keypair();
271 let (_recipient_sk, recipient_vk) = generate_keypair();
272 let (wrong_sk, _) = generate_keypair();
273
274 let plaintext = b"secret message";
275 let ciphertext =
276 encrypt_for(plaintext, recipient_vk.as_bytes(), &sender_sk).unwrap();
277
278 let result = decrypt_from(&ciphertext, sender_vk.as_bytes(), &wrong_sk);
279 assert!(result.is_err());
280 }
281
282 #[test]
283 fn encrypt_decrypt_empty_message() {
284 let (sender_sk, sender_vk) = generate_keypair();
285 let (recipient_sk, recipient_vk) = generate_keypair();
286
287 let ciphertext = encrypt_for(b"", recipient_vk.as_bytes(), &sender_sk).unwrap();
288 let decrypted =
289 decrypt_from(&ciphertext, sender_vk.as_bytes(), &recipient_sk).unwrap();
290 assert_eq!(decrypted, b"");
291 }
292
293 #[test]
294 fn rfc8032_test_vector_1() {
295 let seed_hex = "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60";
296 let seed_bytes = hex::decode(seed_hex).unwrap();
297 let sk = SigningKey::from_bytes(&seed_bytes.try_into().unwrap());
298 let sig = sign(b"", &sk);
299 let expected = "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b";
300 assert_eq!(hex::encode(sig.to_bytes()), expected);
301 }
302}