1use std::borrow::Borrow;
94
95use thiserror::Error;
96
97use crate::cbor::{DecodeError, decode_cbor, encode_cbor};
98use crate::extensions::{Extension, Extensions};
99use crate::hash::Hash;
100use crate::identity::{Signature, SigningKey, VerifyingKey};
101use crate::logs::SeqNum;
102use crate::timestamp::Timestamp;
103use crate::traits::Digest;
104
105pub type RawOperation = (Vec<u8>, Option<Vec<u8>>);
107
108#[derive(Clone, Debug)]
110pub struct Operation<E = ()> {
111 pub hash: Hash,
112 pub header: Header<E>,
113 pub body: Option<Body>,
114}
115
116impl<E> Operation<E>
117where
118 E: Extensions,
119{
120 pub fn header(&self) -> &Header<E> {
122 &self.header
123 }
124
125 pub fn body(&self) -> Option<&Body> {
127 self.body.as_ref()
128 }
129}
130
131impl<E> PartialEq for Operation<E> {
132 fn eq(&self, other: &Self) -> bool {
133 self.hash.eq(&other.hash)
134 }
135}
136
137impl<E> Eq for Operation<E> {}
138
139impl<E> Borrow<Header<E>> for Operation<E> {
140 fn borrow(&self) -> &Header<E> {
141 &self.header
142 }
143}
144
145#[allow(clippy::non_canonical_partial_ord_impl)]
146impl<E> PartialOrd for Operation<E> {
147 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
148 Some(self.hash.cmp(&other.hash))
149 }
150}
151
152impl<E> Ord for Operation<E> {
153 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
154 self.hash.cmp(&other.hash)
155 }
156}
157
158impl<E> Digest<Hash> for Operation<E> {
159 fn hash(&self) -> Hash {
160 self.hash
161 }
162}
163
164#[derive(Clone, Debug, PartialEq, Eq)]
194#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
195pub struct Header<E = ()> {
196 pub version: u64,
198
199 pub verifying_key: VerifyingKey,
201
202 pub signature: Option<Signature>,
204
205 pub payload_size: u64,
207
208 pub payload_hash: Option<Hash>,
214
215 pub timestamp: Timestamp,
217
218 pub seq_num: SeqNum,
221
222 pub backlink: Option<Hash>,
225
226 pub extensions: E,
228}
229
230impl<E: Default> Default for Header<E> {
231 fn default() -> Self {
232 Self {
233 version: 1,
234 verifying_key: VerifyingKey::default(),
235 signature: None,
236 payload_size: 0,
237 payload_hash: None,
238 timestamp: Timestamp::now(),
239 seq_num: 0,
240 backlink: None,
241 extensions: E::default(),
242 }
243 }
244}
245
246impl<E> Header<E>
247where
248 E: Extensions,
249{
250 pub fn to_bytes(&self) -> Vec<u8> {
252 encode_cbor(self)
253 .expect("CBOR encoder failed due to an critical IO error")
256 }
257
258 pub fn sign(&mut self, signing_key: &SigningKey) {
263 self.signature = None;
265
266 let bytes = self.to_bytes();
267 self.signature = Some(signing_key.sign(&bytes));
268 }
269
270 pub fn verify(&self) -> bool {
273 match self.signature {
274 Some(claimed_signature) => {
275 let mut unsigned_header = self.clone();
276 unsigned_header.signature = None;
277 let unsigned_bytes = unsigned_header.to_bytes();
278 self.verifying_key
279 .verify(&unsigned_bytes, &claimed_signature)
280 }
281 None => false,
282 }
283 }
284
285 pub fn hash(&self) -> Hash {
289 Hash::digest(self.to_bytes())
290 }
291
292 pub fn extension<T>(&self) -> Option<T>
294 where
295 E: Extension<T>,
296 {
297 E::extract(self)
298 }
299}
300
301impl<E> Header<E> {
302 pub(crate) fn field_count(&self) -> usize {
306 let mut count = 6;
309
310 if self.signature.is_some() {
311 count += 1;
312 }
313
314 if self.payload_hash.is_some() {
315 count += 1;
316 }
317
318 if self.backlink.is_some() {
319 count += 1;
320 }
321
322 count
323 }
324}
325
326impl TryFrom<&[u8]> for Header {
327 type Error = DecodeError;
328
329 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
330 decode_cbor(value)
331 }
332}
333
334#[derive(Clone, Debug, PartialEq)]
336pub struct Body(pub(super) Vec<u8>);
337
338impl Body {
339 pub fn new(bytes: &[u8]) -> Self {
341 Self(bytes.to_vec())
342 }
343
344 pub fn to_bytes(&self) -> Vec<u8> {
346 self.0.clone()
347 }
348
349 pub fn as_bytes(&self) -> &[u8] {
350 &self.0
351 }
352
353 pub fn hash(&self) -> Hash {
355 Hash::digest(&self.0)
356 }
357
358 pub fn size(&self) -> u64 {
360 self.0.len() as u64
361 }
362}
363
364impl From<&[u8]> for Body {
365 fn from(value: &[u8]) -> Self {
366 Body::new(value)
367 }
368}
369
370impl From<Vec<u8>> for Body {
371 fn from(value: Vec<u8>) -> Self {
372 Body(value)
373 }
374}
375
376#[derive(Clone, Debug, Error)]
377pub enum OperationError {
378 #[error("operation version {0} is not supported, needs to be <= {1}")]
379 UnsupportedVersion(u64, u64),
380
381 #[error("operation needs to be signed")]
382 MissingSignature,
383
384 #[error("signature does not match claimed public key")]
385 SignatureMismatch,
386
387 #[error("sequence number can't be 0 when backlink is given")]
388 SeqNumMismatch,
389
390 #[error("payload hash and -size need to be defined together")]
391 InconsistentPayloadInfo,
392
393 #[error("needs payload hash in header when body is given")]
394 MissingPayloadHash,
395
396 #[error("payload hash and size do not match given body")]
397 PayloadMismatch,
398
399 #[error("logs can not contain operations of different authors")]
400 TooManyAuthors,
401
402 #[error("expected sequence number {0} but found {1}")]
403 SeqNumNonIncremental(u64, u64),
404
405 #[error("expected backlink but none was given")]
406 BacklinkMissing,
407
408 #[error("given backlink did not match previous operation")]
409 BacklinkMismatch,
410}
411
412pub fn validate_operation<E>(operation: impl Borrow<Operation<E>>) -> Result<(), OperationError>
423where
424 E: Extensions,
425{
426 let operation = operation.borrow();
427 validate_header(&operation.header)?;
428
429 let claimed_payload_size = operation.header.payload_size;
430 let claimed_payload_hash: Option<Hash> = match claimed_payload_size {
431 0 => None,
432 _ => {
433 let hash = operation
434 .header
435 .payload_hash
436 .ok_or(OperationError::MissingPayloadHash)?;
437 Some(hash)
438 }
439 };
440
441 if let Some(body) = &operation.body
442 && (claimed_payload_hash != Some(body.hash()) || claimed_payload_size != body.size())
443 {
444 return Err(OperationError::PayloadMismatch);
445 }
446
447 Ok(())
448}
449
450pub fn validate_header<E>(header: &Header<E>) -> Result<(), OperationError>
458where
459 E: Extensions,
460{
461 if !header.verify() {
462 return Err(OperationError::SignatureMismatch);
463 }
464
465 if header.version != 1 {
466 return Err(OperationError::UnsupportedVersion(header.version, 1));
467 }
468
469 if (header.payload_hash.is_some() && header.payload_size == 0)
470 || (header.payload_hash.is_none() && header.payload_size > 0)
471 {
472 return Err(OperationError::InconsistentPayloadInfo);
473 }
474
475 if header.backlink.is_some() && header.seq_num == 0 {
476 return Err(OperationError::SeqNumMismatch);
477 }
478
479 if header.backlink.is_none() && header.seq_num > 0 {
480 return Err(OperationError::BacklinkMissing);
481 }
482
483 Ok(())
484}
485
486pub fn validate_backlink<E>(
494 past_header: impl Borrow<Header<E>>,
495 header: impl Borrow<Header<E>>,
496) -> Result<(), OperationError>
497where
498 E: Extensions,
499{
500 let past_header = past_header.borrow();
501 let header = header.borrow();
502
503 if past_header.verifying_key != header.verifying_key {
504 return Err(OperationError::TooManyAuthors);
505 }
506
507 if past_header.seq_num + 1 != header.seq_num {
508 return Err(OperationError::SeqNumNonIncremental(
509 past_header.seq_num + 1,
510 header.seq_num,
511 ));
512 }
513
514 match header.backlink {
515 Some(backlink) => {
516 if past_header.hash() != backlink {
517 return Err(OperationError::BacklinkMismatch);
518 }
519 }
520 None => {
521 return Err(OperationError::BacklinkMissing);
522 }
523 }
524
525 Ok(())
526}
527
528#[cfg(test)]
529mod tests {
530 use serde::{Deserialize, Serialize};
531
532 use crate::{Extension, SigningKey};
533
534 use super::*;
535
536 #[test]
537 fn simple_extension_type_parameter() {
538 let signing_key = SigningKey::generate();
539 let body = Body::new("Hello, Sloth!".as_bytes());
540 let mut header = Header {
541 version: 1,
542 verifying_key: signing_key.verifying_key(),
543 signature: None,
544 payload_size: body.size(),
545 payload_hash: Some(body.hash()),
546 timestamp: Timestamp::now(),
547 seq_num: 0,
548 backlink: None,
549 extensions: (),
550 };
551
552 header.sign(&signing_key);
553 }
554
555 #[test]
556 fn sign_and_verify() {
557 let signing_key = SigningKey::generate();
558 let body = Body::new("Hello, Sloth!".as_bytes());
559 type CustomExtensions = ();
560
561 let mut header = Header {
562 version: 1,
563 verifying_key: signing_key.verifying_key(),
564 signature: None,
565 payload_size: body.size(),
566 payload_hash: Some(body.hash()),
567 timestamp: Timestamp::now(),
568 seq_num: 0,
569 backlink: None,
570 extensions: None::<CustomExtensions>,
571 };
572 assert!(!header.verify());
573
574 header.sign(&signing_key);
575 assert!(header.verify());
576
577 let operation = Operation {
578 hash: header.hash(),
579 header,
580 body: Some(body),
581 };
582 assert!(validate_operation(&operation).is_ok());
583 }
584
585 #[test]
586 fn valid_backlink_header() {
587 let signing_key = SigningKey::generate();
588
589 let mut header_0 = Header::<()> {
590 version: 1,
591 verifying_key: signing_key.verifying_key(),
592 signature: None,
593 payload_size: 0,
594 payload_hash: None,
595 timestamp: Timestamp::now(),
596 seq_num: 0,
597 backlink: None,
598 extensions: (),
599 };
600 header_0.sign(&signing_key);
601 assert!(validate_header(&header_0).is_ok());
602
603 let mut header_1 = Header::<()> {
604 version: 1,
605 verifying_key: signing_key.verifying_key(),
606 signature: None,
607 payload_size: 0,
608 payload_hash: None,
609 timestamp: Timestamp::now(),
610 seq_num: 1,
611 backlink: Some(header_0.hash()),
612 extensions: (),
613 };
614 header_1.sign(&signing_key);
615 assert!(validate_header(&header_1).is_ok());
616
617 assert!(validate_backlink(&header_0, &header_1).is_ok());
618 }
619
620 #[test]
621 fn invalid_operations() {
622 let signing_key = SigningKey::generate();
623 let body: Body = Body::new("Hello, Sloth!".as_bytes());
624
625 let header_base = Header::<()> {
626 version: 1,
627 verifying_key: signing_key.verifying_key(),
628 signature: None,
629 payload_size: body.size(),
630 payload_hash: Some(body.hash()),
631 timestamp: 0.into(),
632 seq_num: 0,
633 backlink: None,
634 extensions: (),
635 };
636
637 let mut header = header_base.clone();
639 header.version = 0;
640 header.sign(&signing_key);
641 assert!(matches!(
642 validate_header(&header),
643 Err(OperationError::UnsupportedVersion(0, 1))
644 ));
645
646 let mut header = header_base.clone();
648 header.verifying_key = SigningKey::generate().verifying_key();
649 header.sign(&signing_key);
650 assert!(matches!(
651 validate_header(&header),
652 Err(OperationError::SignatureMismatch)
653 ));
654
655 let mut header = header_base.clone();
657 header.seq_num = 1;
658 header.sign(&signing_key);
659 assert!(matches!(
660 validate_header(&header),
661 Err(OperationError::BacklinkMissing)
662 ));
663
664 let mut header = header_base.clone();
666 header.backlink = Some(Hash::digest(vec![4, 5, 6]));
667 header.sign(&signing_key);
668 assert!(matches!(
669 validate_header(&header),
670 Err(OperationError::SeqNumMismatch)
671 ));
672
673 let mut header = header_base.clone();
675 header.payload_size = 11;
676 header.sign(&signing_key);
677 assert!(matches!(
678 validate_operation(&Operation {
679 hash: header.hash(),
680 header,
681 body: Some(body.clone()),
682 }),
683 Err(OperationError::PayloadMismatch)
684 ));
685
686 let mut header = header_base.clone();
688 header.payload_hash = Some(Hash::digest(vec![4, 5, 6]));
689 header.sign(&signing_key);
690 assert!(matches!(
691 validate_operation(&Operation {
692 hash: header.hash(),
693 header,
694 body: Some(body.clone()),
695 }),
696 Err(OperationError::PayloadMismatch)
697 ));
698 }
699
700 #[test]
701 fn extensions() {
702 #[derive(Clone, Debug, Serialize, Deserialize)]
703 struct LogId(Hash);
704
705 #[derive(Clone, Debug, Serialize, Deserialize)]
706 struct Expiry(u64);
707
708 #[derive(Clone, Debug, Serialize, Deserialize)]
709 struct CustomExtensions {
710 log_id: Option<LogId>,
711 expires: Expiry,
712 }
713
714 impl Extension<LogId> for CustomExtensions {
715 fn extract(header: &Header<Self>) -> Option<LogId> {
716 if header.seq_num == 0 {
717 return Some(LogId(header.hash()));
718 };
719
720 header.extensions.log_id.clone()
721 }
722 }
723
724 impl Extension<Expiry> for CustomExtensions {
725 fn extract(header: &Header<Self>) -> Option<Expiry> {
726 Some(header.extensions.expires.clone())
727 }
728 }
729
730 let extensions = CustomExtensions {
731 log_id: None,
732 expires: Expiry(0123456),
733 };
734
735 let signing_key = SigningKey::generate();
736 let body: Body = Body::new("Hello, Sloth!".as_bytes());
737
738 let mut header = Header {
739 version: 1,
740 verifying_key: signing_key.verifying_key(),
741 signature: None,
742 payload_size: body.size(),
743 payload_hash: Some(body.hash()),
744 timestamp: 0.into(),
745 seq_num: 0,
746 backlink: None,
747 extensions: extensions.clone(),
748 };
749
750 header.sign(&signing_key);
751
752 let log_id: LogId = header.extension().unwrap();
755 let expiry: Expiry = header.extension().unwrap();
756
757 assert_eq!(header.hash(), log_id.0);
758 assert_eq!(extensions.expires.0, expiry.0);
759 }
760}