1use crate::hashchain::{canonicalize, event_value_for_hashing, ChainError};
18use base64::engine::general_purpose::URL_SAFE_NO_PAD;
19use base64::Engine as _;
20use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey};
21use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
22
23pub const SIGNATURE_ALG: &str = "ed25519";
25
26#[derive(Debug, thiserror::Error)]
28pub enum SignatureError {
29 #[error("event carries no signature (event_id={event_id})")]
31 Missing {
32 event_id: String,
34 },
35 #[error("signature algorithm {alg:?} not permitted in v0.x (ed25519 only)")]
37 UnpermittedAlgorithm {
38 alg: String,
40 },
41 #[error("unknown signing key {key_id:?}")]
43 UnknownKey {
44 key_id: String,
46 },
47 #[error("malformed key or signature: {0}")]
49 Malformed(String),
50 #[error("signature did not verify (event_id={event_id})")]
53 Invalid {
54 event_id: String,
56 },
57 #[error(transparent)]
59 Chain(#[from] ChainError),
60}
61
62pub struct Ed25519EventSigner {
64 key_id: String,
65 key: SigningKey,
66}
67
68impl Ed25519EventSigner {
69 pub fn from_pem(key_id: &str, private_key_pem: &str) -> Result<Self, SignatureError> {
76 let key = SigningKey::from_pkcs8_pem(private_key_pem)
77 .map_err(|e| SignatureError::Malformed(e.to_string()))?;
78 Ok(Self {
79 key_id: key_id.to_string(),
80 key,
81 })
82 }
83
84 #[must_use]
86 pub fn key_id(&self) -> &str {
87 &self.key_id
88 }
89
90 #[must_use]
92 pub fn sign(&self, data: &[u8]) -> String {
93 URL_SAFE_NO_PAD.encode(self.key.sign(data).to_bytes())
94 }
95}
96
97pub fn attestation_bytes(
104 event: &serde_json::Value,
105 prev_event_hash: Option<&str>,
106) -> Result<Vec<u8>, ChainError> {
107 let mut view = event_value_for_hashing(event);
108 if let Some(obj) = view.as_object_mut() {
109 obj.remove("signature");
110 }
111 let mut bytes = canonicalize(&view)?;
112 bytes.extend(prev_event_hash.unwrap_or("").as_bytes());
113 Ok(bytes)
114}
115
116pub fn verify_event_signature<S: std::hash::BuildHasher>(
127 event: &serde_json::Value,
128 trusted_keys: &std::collections::HashMap<String, String, S>,
129) -> Result<bool, SignatureError> {
130 let event_id = event
131 .get("event_id")
132 .and_then(|v| v.as_str())
133 .unwrap_or("<unknown>")
134 .to_string();
135 let sig = event
136 .get("signature")
137 .filter(|v| !v.is_null())
138 .ok_or_else(|| SignatureError::Missing {
139 event_id: event_id.clone(),
140 })?;
141
142 let alg = sig.get("alg").and_then(|v| v.as_str()).unwrap_or("");
143 if alg != SIGNATURE_ALG {
144 return Err(SignatureError::UnpermittedAlgorithm {
145 alg: alg.to_string(),
146 });
147 }
148 let key_id = sig.get("key_id").and_then(|v| v.as_str()).unwrap_or("");
149 let pem = trusted_keys
150 .get(key_id)
151 .ok_or_else(|| SignatureError::UnknownKey {
152 key_id: key_id.to_string(),
153 })?;
154 let verifying = VerifyingKey::from_public_key_pem(pem)
155 .map_err(|e| SignatureError::Malformed(e.to_string()))?;
156
157 let raw = URL_SAFE_NO_PAD
158 .decode(sig.get("value").and_then(|v| v.as_str()).unwrap_or(""))
159 .map_err(|e| SignatureError::Malformed(e.to_string()))?;
160 let signature =
161 Signature::from_slice(&raw).map_err(|e| SignatureError::Malformed(e.to_string()))?;
162
163 let prev = event.get("prev_event_hash").and_then(|v| v.as_str());
164 let data = attestation_bytes(event, prev)?;
165 Ok(verifying.verify(&data, &signature).is_ok())
166}
167
168pub fn verify_chain_signatures<S: std::hash::BuildHasher>(
176 events: &[serde_json::Value],
177 trusted_keys: &std::collections::HashMap<String, String, S>,
178 require_all: bool,
179) -> Result<usize, SignatureError> {
180 let mut verified = 0;
181 for ev in events {
182 let has_sig = ev.get("signature").is_some_and(|v| !v.is_null());
183 if !has_sig {
184 if require_all {
185 return Err(SignatureError::Missing {
186 event_id: ev
187 .get("event_id")
188 .and_then(|v| v.as_str())
189 .unwrap_or("<unknown>")
190 .to_string(),
191 });
192 }
193 continue;
194 }
195 if !verify_event_signature(ev, trusted_keys)? {
196 return Err(SignatureError::Invalid {
197 event_id: ev
198 .get("event_id")
199 .and_then(|v| v.as_str())
200 .unwrap_or("<unknown>")
201 .to_string(),
202 });
203 }
204 verified += 1;
205 }
206 Ok(verified)
207}