1use bytes::Bytes;
32use ed25519_dalek::{Signature as EdSignature, Signer as _, SigningKey, VerifyingKey};
33use serde::{Deserialize, Serialize};
34
35use crate::codec::to_canonical_bytes;
36use crate::error::{Error, SignError};
37use crate::objects::{Commit, Operation, Signature};
38
39pub const ALGO_ED25519: &str = "ed25519";
41
42pub struct Signer {
48 inner: SigningKey,
49}
50
51impl Signer {
52 #[must_use]
58 pub fn from_seed_bytes(seed: [u8; 32]) -> Self {
59 Self {
60 inner: SigningKey::from_bytes(&seed),
61 }
62 }
63
64 #[must_use]
66 pub fn public_key_bytes(&self) -> [u8; 32] {
67 *self.inner.verifying_key().as_bytes()
68 }
69
70 pub fn sign_commit(&self, commit: &mut Commit) -> Result<(), Error> {
79 let bytes = canonical_bytes_for_commit(commit)?;
80 let sig = self.inner.sign(&bytes);
81 commit.signature = Some(signature_from_parts(
82 self.inner.verifying_key().as_bytes(),
83 &sig.to_bytes(),
84 ));
85 Ok(())
86 }
87
88 pub fn sign_operation(&self, op: &mut Operation) -> Result<(), Error> {
94 let bytes = canonical_bytes_for_operation(op)?;
95 let sig = self.inner.sign(&bytes);
96 op.signature = Some(signature_from_parts(
97 self.inner.verifying_key().as_bytes(),
98 &sig.to_bytes(),
99 ));
100 Ok(())
101 }
102}
103
104#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
108pub struct Revocation {
109 pub public_key: Bytes,
111 pub revoked_at: u64,
115 pub reason: String,
117}
118
119#[derive(Debug, Default)]
126pub struct Verifier {
127 revocations: Vec<Revocation>,
128}
129
130impl Verifier {
131 #[must_use]
133 pub fn new() -> Self {
134 Self::default()
135 }
136
137 #[must_use]
139 pub const fn with_revocations(revocations: Vec<Revocation>) -> Self {
140 Self { revocations }
141 }
142
143 pub fn verify_commit(&self, commit: &Commit) -> Result<(), SignError> {
150 let sig = commit.signature.as_ref().ok_or(SignError::NoSignature)?;
151 let (vk, ed_sig) = extract_verifier_inputs(sig)?;
152 let bytes =
153 canonical_bytes_for_commit(commit).map_err(|e| SignError::Encoding(e.to_string()))?;
154 vk.verify_strict(&bytes, &ed_sig)
155 .map_err(|_| SignError::InvalidSignature)?;
156 self.check_revocation(sig.public_key.as_ref(), commit.time)
157 }
158
159 pub fn verify_operation(&self, op: &Operation) -> Result<(), SignError> {
165 let sig = op.signature.as_ref().ok_or(SignError::NoSignature)?;
166 let (vk, ed_sig) = extract_verifier_inputs(sig)?;
167 let bytes =
168 canonical_bytes_for_operation(op).map_err(|e| SignError::Encoding(e.to_string()))?;
169 vk.verify_strict(&bytes, &ed_sig)
170 .map_err(|_| SignError::InvalidSignature)?;
171 self.check_revocation(sig.public_key.as_ref(), op.time)
172 }
173
174 fn check_revocation(&self, public_key: &[u8], time: u64) -> Result<(), SignError> {
175 for rev in &self.revocations {
176 if rev.public_key.as_ref() == public_key && time > rev.revoked_at {
177 return Err(SignError::RevokedKey {
178 revoked_at: rev.revoked_at,
179 time,
180 });
181 }
182 }
183 Ok(())
184 }
185}
186
187fn signature_from_parts(public_key: &[u8; 32], sig: &[u8; 64]) -> Signature {
190 Signature {
191 algo: ALGO_ED25519.into(),
192 public_key: Bytes::copy_from_slice(public_key),
193 sig: Bytes::copy_from_slice(sig),
194 }
195}
196
197fn extract_verifier_inputs(sig: &Signature) -> Result<(VerifyingKey, EdSignature), SignError> {
198 if sig.algo != ALGO_ED25519 {
199 return Err(SignError::WrongAlgorithm {
200 got: sig.algo.clone(),
201 });
202 }
203 let pk_arr: [u8; 32] = sig
204 .public_key
205 .as_ref()
206 .try_into()
207 .map_err(|_| SignError::MalformedKey)?;
208 let vk = VerifyingKey::from_bytes(&pk_arr).map_err(|_| SignError::MalformedKey)?;
209 let sig_arr: [u8; 64] = sig
210 .sig
211 .as_ref()
212 .try_into()
213 .map_err(|_| SignError::MalformedSignature)?;
214 let ed_sig = EdSignature::from_bytes(&sig_arr);
215 Ok((vk, ed_sig))
216}
217
218fn canonical_bytes_for_commit(commit: &Commit) -> Result<Vec<u8>, Error> {
219 let mut c = commit.clone();
220 c.signature = None;
221 Ok(to_canonical_bytes(&c)?.to_vec())
222}
223
224fn canonical_bytes_for_operation(op: &Operation) -> Result<Vec<u8>, Error> {
225 let mut o = op.clone();
226 o.signature = None;
227 Ok(to_canonical_bytes(&o)?.to_vec())
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use crate::id::{CODEC_RAW, ChangeId, Cid, Multihash};
234
235 fn raw(n: u32) -> Cid {
236 Cid::new(CODEC_RAW, Multihash::sha2_256(&n.to_be_bytes()))
237 }
238
239 fn sample_commit(time: u64) -> Commit {
240 Commit::new(
241 ChangeId::from_bytes_raw([1u8; 16]),
242 raw(1),
243 raw(2),
244 raw(3),
245 "alice@example.org",
246 time,
247 "init",
248 )
249 }
250
251 fn sample_operation(time: u64) -> Operation {
252 Operation::new(raw(1), "alice@example.org", time, "commit: init")
253 }
254
255 #[test]
256 fn sign_then_verify_commit() {
257 let signer = Signer::from_seed_bytes([0x42u8; 32]);
258 let mut c = sample_commit(1_000);
259 signer.sign_commit(&mut c).unwrap();
260 Verifier::new().verify_commit(&c).unwrap();
261 }
262
263 #[test]
264 fn sign_then_verify_operation() {
265 let signer = Signer::from_seed_bytes([0x21u8; 32]);
266 let mut op = sample_operation(1_000);
267 signer.sign_operation(&mut op).unwrap();
268 Verifier::new().verify_operation(&op).unwrap();
269 }
270
271 #[test]
272 fn verify_with_no_signature_errors() {
273 let c = sample_commit(1_000);
274 let err = Verifier::new().verify_commit(&c).unwrap_err();
275 assert!(matches!(err, SignError::NoSignature));
276 }
277
278 #[test]
279 fn tampered_commit_fails_verify() {
280 let signer = Signer::from_seed_bytes([0x42u8; 32]);
281 let mut c = sample_commit(1_000);
282 signer.sign_commit(&mut c).unwrap();
283 c.message = "I am a thief".into();
285 let err = Verifier::new().verify_commit(&c).unwrap_err();
286 assert!(matches!(err, SignError::InvalidSignature));
287 }
288
289 #[test]
290 fn wrong_algorithm_rejected() {
291 let signer = Signer::from_seed_bytes([0x1u8; 32]);
292 let mut c = sample_commit(1_000);
293 signer.sign_commit(&mut c).unwrap();
294 let sig = c.signature.as_mut().unwrap();
296 sig.algo = "rsa".into();
297 let err = Verifier::new().verify_commit(&c).unwrap_err();
298 assert!(matches!(err, SignError::WrongAlgorithm { .. }));
299 }
300
301 #[test]
302 fn malformed_key_length_rejected() {
303 let signer = Signer::from_seed_bytes([0x1u8; 32]);
304 let mut c = sample_commit(1_000);
305 signer.sign_commit(&mut c).unwrap();
306 let sig = c.signature.as_mut().unwrap();
307 sig.public_key = Bytes::from(vec![0u8; 16]); let err = Verifier::new().verify_commit(&c).unwrap_err();
309 assert!(matches!(err, SignError::MalformedKey));
310 }
311
312 #[test]
313 fn revocation_after_commit_time_still_valid() {
314 let signer = Signer::from_seed_bytes([0x42u8; 32]);
315 let mut c = sample_commit(1_000);
316 signer.sign_commit(&mut c).unwrap();
317 let verifier = Verifier::with_revocations(vec![Revocation {
318 public_key: Bytes::copy_from_slice(&signer.public_key_bytes()),
319 revoked_at: 2_000, reason: "rotated".into(),
321 }]);
322 verifier.verify_commit(&c).unwrap();
323 }
324
325 #[test]
326 fn revocation_before_commit_time_rejects() {
327 let signer = Signer::from_seed_bytes([0x42u8; 32]);
328 let mut c = sample_commit(1_000);
329 signer.sign_commit(&mut c).unwrap();
330 let verifier = Verifier::with_revocations(vec![Revocation {
331 public_key: Bytes::copy_from_slice(&signer.public_key_bytes()),
332 revoked_at: 500, reason: "compromised".into(),
334 }]);
335 let err = verifier.verify_commit(&c).unwrap_err();
336 match err {
337 SignError::RevokedKey { revoked_at, time } => {
338 assert_eq!(revoked_at, 500);
339 assert_eq!(time, 1_000);
340 }
341 e => panic!("wrong variant: {e:?}"),
342 }
343 }
344
345 #[test]
346 fn revocation_equals_commit_time_still_valid() {
347 let signer = Signer::from_seed_bytes([0x42u8; 32]);
349 let mut c = sample_commit(1_000);
350 signer.sign_commit(&mut c).unwrap();
351 let verifier = Verifier::with_revocations(vec![Revocation {
352 public_key: Bytes::copy_from_slice(&signer.public_key_bytes()),
353 revoked_at: 1_000, reason: "rotated".into(),
355 }]);
356 verifier.verify_commit(&c).unwrap();
357 }
358
359 #[test]
360 fn re_signing_is_idempotent() {
361 let signer = Signer::from_seed_bytes([0x42u8; 32]);
364 let mut c1 = sample_commit(1_000);
365 signer.sign_commit(&mut c1).unwrap();
366 let mut c2 = sample_commit(1_000);
367 signer.sign_commit(&mut c2).unwrap();
368 Verifier::new().verify_commit(&c1).unwrap();
369 Verifier::new().verify_commit(&c2).unwrap();
370 assert_eq!(c1.signature, c2.signature);
372 }
373}