1use base64::{engine::general_purpose::STANDARD as B64, Engine};
5use ed25519_dalek::{Signer, SigningKey, VerifyingKey, Verifier, Signature};
6use rand::rngs::OsRng;
7use sha2::{Digest, Sha256};
8use serde_json::Value;
9
10use crate::errors::{AAPError, Result};
11
12pub struct KeyPair {
14 signing: SigningKey,
15}
16
17impl KeyPair {
18 pub fn generate() -> Self {
20 Self { signing: SigningKey::generate(&mut OsRng) }
21 }
22
23 pub fn public_key_b64(&self) -> String {
25 format!("ed25519:{}", B64.encode(self.signing.verifying_key().as_bytes()))
26 }
27
28 pub fn sign(&self, data: &[u8]) -> String {
30 let sig: Signature = self.signing.sign(data);
31 format!("ed25519:{}", B64.encode(sig.to_bytes()))
32 }
33}
34
35pub fn verify_signature(public_key_b64: &str, data: &[u8], signature_b64: &str) -> Result<()> {
37 let pub_bytes = B64
38 .decode(public_key_b64.trim_start_matches("ed25519:"))
39 .map_err(|e| AAPError::Signature(format!("invalid public key: {e}")))?;
40
41 let sig_bytes = B64
42 .decode(signature_b64.trim_start_matches("ed25519:"))
43 .map_err(|e| AAPError::Signature(format!("invalid signature: {e}")))?;
44
45 let key = VerifyingKey::from_bytes(
46 pub_bytes.as_slice().try_into()
47 .map_err(|_| AAPError::Signature("public key must be 32 bytes".into()))?,
48 ).map_err(|e| AAPError::Signature(format!("invalid key: {e}")))?;
49
50 let sig = Signature::from_bytes(
51 sig_bytes.as_slice().try_into()
52 .map_err(|_| AAPError::Signature("signature must be 64 bytes".into()))?,
53 );
54
55 key.verify(data, &sig)
56 .map_err(|_| AAPError::Signature("signature mismatch".into()))
57}
58
59pub fn sha256_of(data: &[u8]) -> String {
61 let hash = Sha256::digest(data);
62 format!("sha256:{}", hex::encode(hash))
63}
64
65pub fn hash_entry(v: &Value) -> String {
67 let canonical = serde_json::to_string(v).unwrap_or_default();
68 sha256_of(canonical.as_bytes())
69}
70
71pub fn signable(v: &Value) -> Result<Vec<u8>> {
73 let mut map = match v.as_object() {
74 Some(m) => m.clone(),
75 None => return Err(AAPError::Serde(serde_json::from_str::<Value>("").unwrap_err())),
76 };
77 map.remove("signature");
78 let sorted: serde_json::Map<String, Value> = map.into_iter().collect();
80 Ok(serde_json::to_vec(&Value::Object(sorted))?)
81}